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