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