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