home · contact · privacy
Use direction names instead of numbers in player move communication.
[plomrogue] / src / server / run.c
1 /* src/server/run.c */
2
3 #define _POSIX_C_SOURCE 200809L
4 #include "run.h"
5 #include <stddef.h> /* NULL */
6 #include <stdint.h> /* uint8_t, uint16_t, uint32_t */
7 #include <stdio.h> /* FILE, printf(), fflush() */
8 #include <stdlib.h> /* free(), atoi() */
9 #include <string.h> /* strlen(), strcmp() strncmp(), strdup() */
10 #include <unistd.h> /* access() */
11 #include "../common/parse_file.h" /* set_err_line_options(), token_from_line(),
12                                    * err_line()
13                                    */
14 #include "../common/readwrite.h" /* try_fopen(), try_fcose(), try_fwrite(),
15                                   * try_fgets(), textfile_width(), try_fputc()
16                                   */
17 #include "../common/rexit.h" /* exit_trouble(), exit_err() */
18 #include "../common/try_malloc.h" /* try_malloc() */
19 #include "ai.h" /* ai() */
20 #include "cleanup.h" /* set_cleanup_flag(), unset_cleanup_flag() */
21 #include "field_of_view.h" /* build_fov_map() */
22 #include "hardcoded_strings.h" /* s */
23 #include "init.h" /* remake_world() */
24 #include "io.h" /* io_round(), save_world() */
25 #include "map.h" /* remake_map() */
26 #include "thing_actions.h" /* ThingAction */
27 #include "things.h" /* Thing, get_thing(), own_thing(), add_thing(),
28                      * get_thing_action_id_by_name(), get_player()
29                      */
30 #include "world.h" /* world */
31
32
33
34 /* Parse/apply god command in "tok0"/"tok1" on "t" owning another thing. */
35 static uint8_t parse_carry(char * tok0, char * tok1, struct Thing * t);
36
37 /* Parse/apply god commansd in "tok0"/"tok1" on positioning a thing "t". */
38 static uint8_t parse_position(char* tok0, char * tok1, struct Thing * t);
39
40 /* Parse/apply god command in "tok0"/"tok1" oo setting "t"'s thing type. */
41 static uint8_t parse_thing_type(char * tok0, char * tok1, struct Thing * t);
42
43 /* Parse/apply god command in "tok0"/"tok1" on setting up thing "t". */
44 static uint8_t parse_thing_command(char * tok0, char * tok1, struct Thing * t);
45
46 /* Parse/apply god command on enabling/disabling generation of fields of view on
47  * god commands that may affect them, via static global "do_fov". On enabling,
48  * (re-)generate all animate things' fields of view.
49  */
50 static uint8_t parse_do_fov(char * tok0, char * tok1);
51
52 /* Parse/apply god command in "tok0"/"tok1" manipulating a thing's state. */
53 static uint8_t parse_thing_manipulation(char * tok0, char * tok1);
54
55 /* Parse player command "tok0" with no argument to player action, comment on
56  * invalidity of non-zero "tok1" (but do not abort in that case).
57  */
58 static uint8_t parse_player_command_0arg(char * tok0, char * tok1);
59
60 /* If "string" and "comparand" match in string, set "c_to_set" to value."  */
61 static uint8_t set_char_by_string_comparison(char * string, char * comparand,
62                                              char * c_to_set, char value);
63
64 /* Parse player command "tok0" with one argument "tok1" to player action. */
65 static uint8_t parse_player_command_1arg(char * tok0, char * tok1);
66
67 /* Parse/apply commadn "tok0" with argument "tok1" and test the line for further
68  * tokens, commenting on their invalidity (but don't abort on findingthem).
69  */
70 static uint8_t parse_command_1arg(char * tok0, char * tok1);
71
72
73 /* Compares first line of server out file to world.server_test, aborts if they
74  * don't match, but not before unsetting the flags deleting files in the server
75  * directory, for in that case those must be assumed to belong to another server
76  * process.
77  */
78 static void server_test();
79
80 /* Run the game world and its inhabitants (and their actions) until the player
81  * avatar is free to receive new commands (or is dead).
82  */
83 static void turn_over();
84
85
86
87 /* Do god commands to create / position things generate their fields of view? */
88 static uint8_t do_fov = 0;
89
90
91
92 static uint8_t parse_carry(char * tok0, char * tok1, struct Thing * t)
93 {
94     uint8_t id;
95     if (parse_val(tok0, tok1, s[S_CMD_CARRIES], '8', (char *) &id))
96     {
97         if (!err_line(id == t->id, "Thing cannot carry itself."))
98         {
99             struct Thing * o = get_thing(world.things, id, 0);
100             if (!err_line(!o, "Thing cannot carry thing that does not exist."))
101             {
102                 own_thing(&(t->owns), &world.things, id);
103                 o->pos = t->pos;
104             }
105         }
106         return 1;
107     }
108     return 0;
109 }
110
111
112
113 static uint8_t parse_position(char* tok0, char * tok1, struct Thing * t)
114 {
115     char axis = 0;
116     if      (!strcmp(tok0, s[S_CMD_POS_Y]))
117     {
118         axis = 'y';
119     }
120     else if (!strcmp(tok0, s[S_CMD_POS_X]))
121     {
122         axis = 'x';
123     }
124     if (axis && !parsetest_int(tok1, '8'))
125     {
126         uint8_t length = atoi(tok1);
127         char * err = "Position is outside of map.";
128         if (!err_line(length >= world.map.length, err))
129         {
130             if      ('y' == axis)
131             {
132                 t->pos.y = length;
133             }
134             else if ('x' == axis)
135             {
136                 t->pos.x = length;
137             }
138             free(t->fov_map);
139             t->fov_map= do_fov && t->lifepoints ? build_fov_map(t) : t->fov_map;
140         }
141         return 1;
142     }
143     return 0;
144 }
145
146
147
148 static uint8_t parse_thing_type(char * tok0, char * tok1, struct Thing * t)
149 {
150     uint8_t type;
151     if (parse_val(tok0, tok1, s[S_CMD_TYPE], '8', (char *) &type))
152     {
153         struct ThingType * tt = world.thing_types;
154         for (; NULL != tt && type != tt->id; tt = tt->next);
155         if (!err_line(!tt, "Thing type does not exist."))
156         {
157             t->type = type;
158         }
159         return 1;
160     }
161     return 0;
162 }
163
164
165
166 static uint8_t parse_thing_command(char * tok0, char * tok1, struct Thing * t)
167 {
168     uint8_t command;
169     if (parse_val(tok0, tok1, s[S_CMD_COMMAND], '8', (char *) &command))
170     {
171         if (!command)
172         {
173             t->command = command;
174             return 1;
175         }
176         struct ThingAction * ta = world.thing_actions;
177         for (; NULL != ta && command != ta->id; ta = ta->next);
178         if (!err_line(!ta, "Thing action does not exist."))
179         {
180             t->command = command;
181         }
182         return 1;
183     }
184     return 0;
185 }
186
187
188
189 static uint8_t parse_do_fov(char * tok0, char * tok1)
190 {
191     if (parse_val(tok0, tok1, s[S_CMD_DO_FOV], '8', (char *) &do_fov))
192     {
193         if (do_fov)
194         {
195             struct Thing * ti;
196             for (ti = world.things; ti; ti = ti->next)
197             {
198                 ti->fov_map = ti->lifepoints ? build_fov_map(ti) : ti->fov_map;
199             }
200         }
201         return 1;
202     }
203     return 0;
204 }
205
206
207
208 static uint8_t parse_thing_manipulation(char * tok0, char * tok1)
209 {
210     uint8_t id;
211     static struct Thing * t = NULL;
212     if (t && (   parse_thing_type(tok0, tok1, t)
213               || parse_thing_command(tok0, tok1, t)
214               || parse_val(tok0, tok1, s[S_CMD_ARGUMENT], '8', (char *)&t->arg)
215               || parse_val(tok0,tok1,s[S_CMD_PROGRESS],'8',(char *)&t->progress)
216               || parse_val(tok0, tok1, s[S_CMD_LIFEPOINTS], '8',
217                                                         (char *) &t->lifepoints)
218               || parse_position(tok0, tok1, t)
219               || parse_carry(tok0, tok1, t)));
220     else if (parse_val(tok0, tok1, s[S_CMD_THING], '8', (char *) &id))
221     {
222         t = get_thing(world.things, id, 1);
223         if (!t)
224         {
225             t = add_thing(id, 0, 0);
226             set_cleanup_flag(CLEANUP_THINGS);
227             t->fov_map= do_fov && t->lifepoints ? build_fov_map(t) : t->fov_map;
228         }
229     }
230     else
231     {
232         return 0;
233     }
234     return 1;
235 }
236
237
238
239 static uint8_t parse_player_command_0arg(char * tok0, char * tok1)
240 {
241     struct Thing * player = get_player();
242     if (!strcmp(tok0, s[S_CMD_WAIT]) || !strcmp(tok0, s[S_CMD_PICKUP]))
243     {
244         player->command = get_thing_action_id_by_name(tok0);
245         player->arg = 0;
246         turn_over();
247         err_line (NULL != tok1, "No arguments expected, ignoring arguments.");
248         return 1;
249     }
250     return 0;
251 }
252
253
254
255 static uint8_t set_char_by_string_comparison(char * string, char * comparand,
256                                              char * c_to_set, char value)
257 {
258     if (!strcmp(string, comparand))
259     {
260         * c_to_set = value;
261         return 1;
262     }
263     return 0;
264 }
265
266
267
268 static uint8_t parse_player_command_1arg(char * tok0, char * tok1)
269 {
270     struct Thing * player = get_player();
271     if (
272            parse_val(tok0, tok1, s[S_CMD_DROP], '8', (char *) &player->arg)
273         || parse_val(tok0, tok1, s[S_CMD_USE], '8', (char *) &player->arg))
274     {
275         player->command = get_thing_action_id_by_name(tok0);
276         turn_over();
277     }
278     else if (!strcmp(tok0, s[S_CMD_MOVE]))
279     {
280         char dir = '\0';
281         if (!(   set_char_by_string_comparison(tok1, "east",       &dir, 'd')
282               || set_char_by_string_comparison(tok1, "south-east", &dir, 'c')
283               || set_char_by_string_comparison(tok1, "south-west", &dir, 'x')
284               || set_char_by_string_comparison(tok1, "west",       &dir, 's')
285               || set_char_by_string_comparison(tok1, "north-west", &dir, 'w')
286               || set_char_by_string_comparison(tok1, "north-east", &dir, 'e')))
287         {
288             return 0;
289         }
290         player->arg = dir;
291         player->command = get_thing_action_id_by_name(tok0);
292         turn_over();
293     }
294     else
295     {
296         return 0;
297     }
298     return 1;
299 }
300
301
302
303 static uint8_t parse_command_1arg(char * tok0, char * tok1)
304 {
305     char * tok2 = token_from_line(NULL);
306     if (   parse_thing_manipulation(tok0, tok1)
307         || parse_player_command_1arg(tok0, tok1)
308         || parse_val(tok0, tok1, s[S_CMD_SEED_RAND], 'U', (char *) &world.seed)
309         || parse_val(tok0, tok1, s[S_CMD_TURN], 'u', (char *) &world.turn)
310         || parse_do_fov(tok0, tok1));
311     else if (parse_val(tok0,tok1,s[S_CMD_SEED_MAP],'U',(char *)&world.seed_map))
312
313     {
314         remake_map();
315     }
316     else if (parse_val(tok0, tok1, s[S_CMD_MAKE_WORLD],'U',(char *)&world.seed))
317     {
318         remake_world();
319     }
320     else
321     {
322         return 0;
323     }
324     char * err = "But one argument expected, ignoring further arguments.";
325     err_line (NULL != tok2, err);
326     return 1;
327 }
328
329
330
331 static void server_test()
332 {
333     char * f_name = "server_test()";
334     char test[10 + 1 + 10 + 1 + 1];
335     FILE * file = try_fopen(s[S_PATH_OUT], "r", f_name);
336     try_fgets(test, 10 + 10 + 1 + 1, file, f_name);
337     try_fclose(file, f_name);
338     if (strcmp(test, world.server_test))
339     {
340         unset_cleanup_flag(CLEANUP_WORLDSTATE);
341         unset_cleanup_flag(CLEANUP_OUT);
342         unset_cleanup_flag(CLEANUP_IN);
343         char * msg = "Server test string in server output file does not match. "
344                      "This indicates that the current server process has been "
345                      "superseded by another one.";
346         exit_err(1, msg);
347     }
348 }
349
350
351
352 static void turn_over()
353 {
354     struct Thing * player = get_player();
355     struct Thing * thing = player;
356     uint16_t start_turn = world.turn;
357     while (    0 < player->lifepoints
358            || (0 == player->lifepoints && start_turn == world.turn))
359     {
360         if (NULL == thing)
361         {
362             world.turn++;
363             thing = world.things;
364         }
365         if (0 < thing->lifepoints)
366         {
367             if (0 == thing->command)
368             {
369                 if (thing == player)
370                 {
371                     break;
372                 }
373                 ai(thing);
374             }
375             thing->progress++;
376             struct ThingAction * ta = world.thing_actions;
377             while (ta->id != thing->command)
378             {
379                 ta = ta->next;
380             }
381             if (thing->progress == ta->effort)
382             {
383                 ta->func(thing);
384                 thing->command = 0;
385                 thing->progress = 0;
386             }
387         }
388         thing = thing->next;
389     }
390 }
391
392
393
394 static void record_msg(char * msg)
395 {
396     char * f_name = "record_msg()";
397     char * path_tmp;
398     FILE * file_tmp = atomic_write_start(s[S_PATH_RECORD], &path_tmp);
399     if (!access(s[S_PATH_RECORD], F_OK))
400     {
401         FILE * file_read = try_fopen(s[S_PATH_RECORD], "r", f_name);
402         uint32_t linemax = textfile_width(file_read);
403         char * line = try_malloc(linemax + 1, f_name);
404         while (try_fgets(line, linemax + 1, file_read, f_name))
405         {
406             try_fwrite(line, strlen(line), 1, file_tmp, f_name);
407         }
408         free(line);
409         try_fclose(file_read, f_name);
410     }
411     try_fwrite(msg, strlen(msg), 1, file_tmp, f_name);
412     try_fputc('\n', file_tmp, f_name);
413     atomic_write_finish(file_tmp, s[S_PATH_RECORD], path_tmp);
414 }
415
416
417
418 extern void obey_msg(char * msg, uint8_t do_record)
419 {
420     set_err_line_options("Trouble with message: ", msg, 0, 0);
421     char * msg_copy = strdup(msg);
422     char * tok0 = token_from_line(msg_copy);
423     if (NULL != tok0)
424     {
425         char * tok1 = token_from_line(NULL);
426         if (    parse_player_command_0arg(tok0, tok1)
427             || (tok1 && parse_command_1arg(tok0, tok1)))
428         {
429             world.do_update = 1;
430             if (do_record)
431             {
432                 save_world();
433                 record_msg(msg);
434             }
435             free(msg_copy);
436             return;
437         }
438     }
439     err_line(1, "Unknown command or bad number of tokens.");
440     free(msg_copy);
441 }
442
443
444
445 extern uint8_t io_loop()
446 {
447     char * f_name = "io_loop()";
448     while (1)
449     {
450         server_test();
451         char * msg = io_round();
452         if (NULL == msg)
453         {
454             continue;
455         }
456         if (world.is_verbose)
457         {
458             exit_trouble(-1 == printf("Input: %s\n", msg), f_name, "printf()");
459         }
460         if (!strcmp("QUIT", msg))
461         {
462             free(msg);
463             return 1;
464         }
465         if (!strcmp("PING", msg))
466         {
467             free(msg);
468             char * pong = "PONG\n";
469             try_fwrite(pong, strlen(pong), 1, world.file_out, f_name);
470             fflush(world.file_out);
471             continue;
472         }
473         if (world.replay)
474         {
475             free(msg);
476             return 0;
477         }
478         obey_msg(msg, 1);
479         free(msg);
480     }
481 }