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