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