]> git.localhorst.tv Git - blank.git/blob - src/app/runtime.cpp
random stuff
[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 == "player.name") {
79                         in.ReadString(player.name);
80                 } else if (name == "video.dblbuf") {
81                         in.ReadBoolean(video.dblbuf);
82                 } else if (name == "video.vsync") {
83                         in.ReadBoolean(video.vsync);
84                 } else if (name == "video.msaa") {
85                         in.ReadNumber(video.msaa);
86                 } else if (name == "video.hud") {
87                         in.ReadBoolean(video.hud);
88                 } else if (name == "video.world") {
89                         in.ReadBoolean(video.world);
90                 } else if (name == "video.debug") {
91                         in.ReadBoolean(video.debug);
92                 }
93                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
94                         in.Skip(Token::SEMICOLON);
95                 }
96         }
97 }
98
99 void Config::Save(std::ostream &out) {
100         out << "audio.enabled = " << (audio.enabled ? "yes" : "no") << ';' << std::endl;
101         out << "input.keyboard = " << (input.keyboard ? "on" : "off") << ';' << std::endl;
102         out << "input.mouse = " << (input.keyboard ? "on" : "off") << ';' << std::endl;
103         out << "input.pitch_sensitivity = " << input.pitch_sensitivity << ';' << std::endl;
104         out << "input.yaw_sensitivity = " << input.yaw_sensitivity << ';' << std::endl;
105         out << "net.host = \"" << net.host << "\";" << std::endl;
106         out << "net.port = " << net.port << ';' << std::endl;
107         out << "player.name = \"" << player.name << "\";" << std::endl;
108         out << "video.dblbuf = " << (video.dblbuf ? "on" : "off") << ';' << std::endl;
109         out << "video.vsync = " << (video.vsync ? "on" : "off") << ';' << std::endl;
110         out << "video.msaa = " << video.msaa << ';' << std::endl;
111         out << "video.hud = " << (video.hud ? "on" : "off") << ';' << std::endl;
112         out << "video.world = " << (video.world ? "on" : "off") << ';' << std::endl;
113         out << "video.debug = " << (video.debug ? "on" : "off") << ';' << std::endl;
114 }
115
116
117 HeadlessEnvironment::HeadlessEnvironment(const Config &config)
118 : config(config)
119 , loader(config.asset_path)
120 , counter()
121 , state()
122 , rng(
123 #ifdef BLANK_PROFILING
124 0
125 #else
126 std::time(nullptr)
127 #endif
128 ){
129         for (int i = 0; i < 4; ++i) {
130                 rng.Next<int>();
131         }
132 }
133
134 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name) const {
135         return save_path + "worlds/" + world_name + '/';
136 }
137
138 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name, const string &host_name) const {
139         return save_path + "cache/" + host_name + '/' + world_name + '/';
140 }
141
142 Environment::Environment(Window &win, const Config &config)
143 : HeadlessEnvironment(config)
144 , assets(loader)
145 , audio()
146 , viewport()
147 , window(win)
148 , keymap()
149 , msg_state(*this) {
150         viewport.Clear();
151         window.Flip();
152         keymap.LoadDefault();
153
154         string keys_path = config.save_path + "keys.conf";
155         if (!is_file(keys_path)) {
156                 std::ofstream file(keys_path);
157                 keymap.Save(file);
158         } else {
159                 std::ifstream file(keys_path);
160                 keymap.Load(file);
161         }
162 }
163
164 void Environment::ShowMessage(const char *msg) {
165         cout << msg << endl;
166         msg_state.SetMessage(msg);
167         state.Push(&msg_state);
168 }
169
170
171 Runtime::Runtime() noexcept
172 : name("blank")
173 , mode(NORMAL)
174 , target(STANDALONE)
175 , n(0)
176 , t(0)
177 , config() {
178
179 }
180
181
182 void Runtime::Initialize(int argc, const char *const *argv) {
183         ReadArgs(argc, argv);
184         if (mode == ERROR) return;
185         ReadPreferences();
186         ReadArgs(argc, argv);
187 }
188
189 void Runtime::ReadArgs(int argc, const char *const *argv) {
190         if (argc <= 0) return;
191         name = argv[0];
192
193         bool options = true;
194         bool error = false;
195
196         for (int i = 1; i < argc; ++i) {
197                 const char *arg = argv[i];
198                 if (!arg || arg[0] == '\0') {
199                         cerr << "warning: found empty argument at position " << i << endl;
200                         continue;
201                 }
202                 if (options && arg[0] == '-') {
203                         if (arg[1] == '\0') {
204                                 cerr << "warning: incomplete option list at position " << i << endl;
205                         } else if (arg[1] == '-') {
206                                 if (arg[2] == '\0') {
207                                         // stopper
208                                         options = false;
209                                 } else {
210                                         const char *param = arg + 2;
211                                         // long option
212                                         if (strcmp(param, "no-vsync") == 0) {
213                                                 config.game.video.vsync = false;
214                                         } else if (strcmp(param, "no-keyboard") == 0) {
215                                                 config.game.input.keyboard = false;
216                                         } else if (strcmp(param, "no-mouse") == 0) {
217                                                 config.game.input.mouse = false;
218                                         } else if (strcmp(param, "no-hud") == 0) {
219                                                 config.game.video.hud = false;
220                                         } else if (strcmp(param, "no-audio") == 0) {
221                                                 config.game.audio.enabled = false;
222                                         } else if (strcmp(param, "standalone") == 0) {
223                                                 target = STANDALONE;
224                                         } else if (strcmp(param, "server") == 0) {
225                                                 target = SERVER;
226                                         } else if (strcmp(param, "client") == 0) {
227                                                 target = CLIENT;
228                                         } else if (strcmp(param, "asset-path") == 0) {
229                                                 ++i;
230                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
231                                                         cerr << "missing argument to --asset-path" << endl;
232                                                         error = true;
233                                                 } else {
234                                                         config.env.asset_path = argv[i];
235                                                 }
236                                         } else if (strcmp(param, "host") == 0) {
237                                                 ++i;
238                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
239                                                         cerr << "missing argument to --host" << endl;
240                                                         error = true;
241                                                 } else {
242                                                         config.game.net.host = argv[i];
243                                                 }
244                                         } else if (strcmp(param, "port") == 0) {
245                                                 ++i;
246                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
247                                                         cerr << "missing argument to --port" << endl;
248                                                         error = true;
249                                                 } else {
250                                                         config.game.net.port = strtoul(argv[i], nullptr, 10);
251                                                 }
252                                         } else if (strcmp(param, "player-name") == 0) {
253                                                 ++i;
254                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
255                                                         cerr << "missing argument to --player-name" << endl;
256                                                         error = true;
257                                                 } else {
258                                                         config.game.player.name = argv[i];
259                                                 }
260                                         } else if (strcmp(param, "save-path") == 0) {
261                                                 ++i;
262                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
263                                                         cerr << "missing argument to --save-path" << endl;
264                                                         error = true;
265                                                 } else {
266                                                         config.env.save_path = argv[i];
267                                                 }
268                                         } else if (strcmp(param, "world-name") == 0) {
269                                                 ++i;
270                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
271                                                         cerr << "missing argument to --world-name" << endl;
272                                                         error = true;
273                                                 } else {
274                                                         config.world.name = argv[i];
275                                                 }
276                                         } else {
277                                                 cerr << "unknown option " << arg << endl;
278                                                 error = true;
279                                         }
280                                 }
281                         } else {
282                                 // short options
283                                 for (int j = 1; arg[j] != '\0'; ++j) {
284                                         switch (arg[j]) {
285                                                 case 'd':
286                                                         config.game.video.dblbuf = false;
287                                                         break;
288                                                 case 'm':
289                                                         ++i;
290                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
291                                                                 cerr << "missing argument to -m" << endl;
292                                                                 error = true;
293                                                         } else {
294                                                                 config.game.video.msaa = strtoul(argv[i], nullptr, 10);
295                                                         }
296                                                         break;
297                                                 case 'n':
298                                                         ++i;
299                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
300                                                                 cerr << "missing argument to -n" << endl;
301                                                                 error = true;
302                                                         } else {
303                                                                 n = strtoul(argv[i], nullptr, 10);
304                                                         }
305                                                         break;
306                                                 case 's':
307                                                         ++i;
308                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
309                                                                 cerr << "missing argument to -s" << endl;
310                                                                 error = true;
311                                                         } else {
312                                                                 config.gen.seed = strtoul(argv[i], nullptr, 10);
313                                                         }
314                                                         break;
315                                                 case 't':
316                                                         ++i;
317                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
318                                                                 cerr << "missing argument to -t" << endl;
319                                                                 error = true;
320                                                         } else {
321                                                                 t = strtoul(argv[i], nullptr, 10);
322                                                         }
323                                                         break;
324                                                 case '-':
325                                                         // stopper
326                                                         options = false;
327                                                         break;
328                                                 default:
329                                                         cerr << "unknown option " << arg[j] << endl;
330                                                         error = true;
331                                                         break;
332                                         }
333                                 }
334                         }
335                 } else {
336                         cerr << "unable to interpret argument "
337                                 << i << " (" << arg << ")" << endl;
338                         error = true;
339                 }
340         }
341
342         if (error) {
343                 mode = ERROR;
344         } else if (n > 0) {
345                 if (t > 0) {
346                         mode = FIXED_FRAME_LIMIT;
347                 } else {
348                         mode = FRAME_LIMIT;
349                 }
350         } else if (t > 0) {
351                 mode = TIME_LIMIT;
352         } else {
353                 mode = NORMAL;
354         }
355 }
356
357 void Runtime::ReadPreferences() {
358         if (config.env.asset_path.empty()) {
359                 config.env.asset_path = default_asset_path();
360         } else if (
361                 config.env.asset_path[config.env.asset_path.size() - 1] != '/' &&
362                 config.env.asset_path[config.env.asset_path.size() - 1] != '\\'
363         ) {
364                 config.env.asset_path += '/';
365         }
366         if (config.env.save_path.empty()) {
367                 config.env.save_path = default_save_path();
368         } else if (
369                 config.env.save_path[config.env.save_path.size() - 1] != '/' &&
370                 config.env.save_path[config.env.save_path.size() - 1] != '\\'
371         ) {
372                 config.env.save_path += '/';
373         }
374
375         string prefs_path = config.env.save_path + "prefs.conf";
376         if (is_file(prefs_path)) {
377                 ifstream file(prefs_path);
378                 config.game.Load(file);
379         } else {
380                 make_dirs(config.env.save_path);
381                 ofstream file(prefs_path);
382                 config.game.Save(file);
383         }
384 }
385
386 int Runtime::Execute() {
387         if (mode == ERROR) {
388                 return 1;
389         }
390
391         InitHeadless init_headless;
392
393         switch (target) {
394                 default:
395                 case STANDALONE:
396                         RunStandalone();
397                         break;
398                 case SERVER:
399                         RunServer();
400                         break;
401                 case CLIENT:
402                         RunClient();
403                         break;
404         }
405
406         return 0;
407 }
408
409 void Runtime::RunStandalone() {
410         Init init(config.game.video.dblbuf, config.game.video.msaa);
411
412         Environment env(init.window, config.env);
413         env.viewport.VSync(config.game.video.vsync);
414
415         WorldSave save(config.env.GetWorldPath(config.world.name));
416         if (save.Exists()) {
417                 save.Read(config.world);
418                 save.Read(config.gen);
419         } else {
420                 save.Write(config.world);
421                 save.Write(config.gen);
422         }
423
424         Application app(env);
425         standalone::MasterState world_state(env, config.game, config.gen, config.world, save);
426         app.PushState(&world_state);
427         Run(app);
428 }
429
430 void Runtime::RunServer() {
431         HeadlessEnvironment env(config.env);
432
433         WorldSave save(config.env.GetWorldPath(config.world.name));
434         if (save.Exists()) {
435                 save.Read(config.world);
436                 save.Read(config.gen);
437         } else {
438                 save.Write(config.world);
439                 save.Write(config.gen);
440         }
441
442         HeadlessApplication app(env);
443         server::ServerState server_state(env, config.gen, config.world, save, config.game);
444         app.PushState(&server_state);
445         Run(app);
446 }
447
448 void Runtime::RunClient() {
449         Init init(config.game.video.dblbuf, config.game.video.msaa);
450
451         Environment env(init.window, config.env);
452         env.viewport.VSync(config.game.video.vsync);
453
454         Application app(env);
455         client::MasterState client_state(env, config.game, config.world);
456         app.PushState(&client_state);
457         Run(app);
458 }
459
460 void Runtime::Run(HeadlessApplication &app) {
461         switch (mode) {
462                 default:
463                 case NORMAL:
464                         app.Run();
465                         break;
466                 case FRAME_LIMIT:
467                         app.RunN(n);
468                         break;
469                 case TIME_LIMIT:
470                         app.RunT(t);
471                         break;
472                 case FIXED_FRAME_LIMIT:
473                         app.RunS(n, t);
474                         break;
475         }
476 }
477
478 }