1 #include <stdio.h>
2 #include "console/console.h"
3 #include "console/console_type.h"
4 #include "game/gui/menu_background.h"
5 #include "game/utils/settings.h"
6 #include "video/video.h"
7 
8 #define HISTORY_MAX 100
9 #define BUFFER_INC(b) (((b) + 1) % sizeof(con->output))
10 #define BUFFER_DEC(b) (((b) + sizeof(con->output) - 1) % sizeof(con->output))
11 
12 #define CURSOR_STR "\x7f"
13 
14 // Console State
15 console *con = NULL;
16 
17 // defined in console_cmd.c
18 void console_init_cmd();
19 
make_argv(char * p,char ** argv)20 int make_argv(char *p, char **argv) {
21     // split line into argv, warning: does not handle quoted strings
22     int argc = 0;
23     while(isspace(*p)) { ++p; }
24     while(*p) {
25         if(argv != NULL) { argv[argc] = p; }
26         while(*p && !isspace(*p)) { ++p; }
27         if(argv != NULL && *p) { *p++ = '\0'; }
28         while(isspace(*p)) { ++p; }
29         ++argc;
30     }
31     return argc;
32 }
33 
console_add_history(const char * input,unsigned int len)34 void console_add_history(const char *input, unsigned int len) {
35     iterator it;
36     list_iter_begin(&con->history, &it);
37 
38     char *input2 = iter_next(&it);
39     if(input2 != NULL) {
40         if(strcmp(input, input2) != 0) {
41             if(list_size(&con->history) == HISTORY_MAX) {
42                 list_iter_end(&con->history, &it);
43                 list_delete(&con->history, &it);
44             }
45             list_prepend(&con->history, input, len);
46         }
47     } else if(input2 == NULL) {
48         list_prepend(&con->history, input, len);
49     }
50     con->histpos = -1;
51 }
52 
console_handle_line(game_state * gs)53 void console_handle_line(game_state *gs) {
54     if(con->input[0] == '\0') {
55         console_output_addline(">");
56     } else {
57         char input_copy[sizeof(con->input)];
58         memcpy(input_copy, con->input, sizeof(con->input));
59         int argc = make_argv(con->input, NULL);
60         if(argc > 0) {
61             char *argv[argc];
62             void *val = 0;
63             unsigned int len;
64             make_argv(con->input, argv);
65             if(!hashmap_sget(&con->cmds, argv[0], &val, &len)) {
66                 command *cmd = val;
67                 int err = cmd->func(gs, argc, argv);
68                 if(err == 0)
69                 {
70                     console_output_add("> ");
71                     console_output_add(argv[0]);
72                     console_output_addline(" SUCCESS");
73                 } else {
74                     char buf[12];
75                     sprintf(buf, "%d", err);
76                     console_output_add("> ");
77                     console_output_add(argv[0]);
78                     console_output_add(" ERROR:");
79                     console_output_addline(buf);
80                 }
81                 console_add_history(input_copy, sizeof(input_copy));
82             } else {
83                 console_output_add("> ");
84                 console_output_add(argv[0]);
85                 console_output_addline(" NOT RECOGNIZED");
86             }
87         } else {
88             console_output_addline(">");
89         }
90     }
91 }
92 
console_output_scroll_to_end()93 void console_output_scroll_to_end() {
94     // iterate the output buffer backward to count up to 16 lines or 480 chars, whichever comes first
95     unsigned int lines = 0;
96     unsigned int si;
97     if(con->output_overflowing) {
98         si = BUFFER_DEC(con->output_head);
99         si = BUFFER_DEC(si);
100     } else if(con->output_tail == con->output_head) {
101         si = con->output_tail;
102     } else {
103         si = BUFFER_DEC(con->output_tail);
104     }
105 
106     for(;
107         si != con->output_head;
108         si = BUFFER_DEC(si)) {
109 
110         if(con->output[si] == '\n') {
111             lines++;
112 
113             if(lines >= 16) {
114                 si = BUFFER_INC(si);
115                 break;
116             }
117         }
118     }
119 
120     con->output_pos = si;
121 }
122 
console_output_scroll_up(unsigned int lines)123 void console_output_scroll_up(unsigned int lines) {
124     unsigned int l = 0;
125     if(con->output_pos != con->output_head) {
126         con->output_pos = BUFFER_DEC(con->output_pos);
127     }
128     while(con->output_pos != con->output_head) {
129         con->output_pos = BUFFER_DEC(con->output_pos);
130         if(con->output[con->output_pos] == '\n') {
131             l++;
132             if(l == lines){
133                 con->output_pos = BUFFER_INC(con->output_pos);
134                 break;
135             }
136         }
137     }
138 }
139 
console_output_scroll_down(unsigned int lines)140 void console_output_scroll_down(unsigned int lines) {
141     unsigned int l = 0;
142     while(con->output_pos != con->output_tail) {
143         con->output_pos = BUFFER_INC(con->output_pos);
144         if(con->output[con->output_pos] == '\n') {
145             l++;
146             if(l == lines){
147                 con->output_pos = BUFFER_INC(con->output_pos);
148                 break;
149             }
150         }
151     }
152 }
153 
console_output_add(const char * text)154 void console_output_add(const char *text) {
155     size_t len = strlen(text);
156     for(size_t i = 0;i < len;++i) {
157         char c = text[i];
158         con->output[con->output_tail] = c;
159         con->output_tail = BUFFER_INC(con->output_tail);
160         if(con->output_tail == con->output_head) {
161             // buffer is overflowing
162             con->output_head = BUFFER_INC(con->output_head);
163             con->output_overflowing = 1;
164         }
165     }
166 
167     console_output_scroll_to_end();
168 }
console_output_addline(const char * text)169 void console_output_addline(const char *text) {
170     console_output_add(text);
171     console_output_add("\n");
172 }
173 
console_output_render()174 void console_output_render() {
175     int x = 0;
176     int y = 0;
177     unsigned int lines = 0;
178     const color textcolor = color_create(121, 121, 121, 255);
179     for(unsigned int i = con->output_pos;
180         i != con->output_tail && lines < 15;
181         i = BUFFER_INC(i)) {
182 
183         char c = con->output[i];
184         if(c == '\n') {
185             x = 0;
186             y += font_small.h;
187             lines++;
188         } else {
189             // TODO add word wrapping?
190             font_render_char(&font_small, c, x, y+con->ypos-100, textcolor);
191             x += font_small.w;
192         }
193     }
194 }
195 
console_init()196 int console_init() {
197     if(con != NULL) return 1;
198     con = malloc(sizeof(console));
199     con->isopen = 0;
200     con->ownsinput = 0;
201     con->ypos = 0;
202     con->ticks = 0;
203     con->dir = 0;
204     con->input[0] = '\0';
205     con->output[0] = '\0';
206     con->output_head = 0;
207     con->output_tail = 0;
208     con->output_pos = 0;
209     con->output_overflowing = 0;
210     con->histpos = -1;
211     con->histpos_changed = 0;
212     list_create(&con->history);
213     hashmap_create(&con->cmds, 8);
214     menu_background_create(&con->background, 322, 101);
215 
216     console_init_cmd();
217 
218     // Print the header
219     for(int i=0;i<37;i++) {
220         console_output_add(CURSOR_STR);
221     }
222     console_output_addline("");
223     console_output_addline(CURSOR_STR "                                   " CURSOR_STR "\n"
224                            CURSOR_STR " OpenOMF Debug Console Cheat Sheet " CURSOR_STR "\n"
225                            CURSOR_STR "                                   " CURSOR_STR "\n"
226                            CURSOR_STR " PageUp - Scroll Up                " CURSOR_STR "\n"
227                            CURSOR_STR " PageDn - Scroll Down              " CURSOR_STR "\n"
228                            CURSOR_STR " Up     - Reissue Previous Command " CURSOR_STR "\n"
229                            CURSOR_STR " Down   - Reissue Next Command     " CURSOR_STR "\n"
230                            CURSOR_STR " Enter  - Execute Current Command  " CURSOR_STR "\n"
231                            CURSOR_STR " --------------------------------- " CURSOR_STR "\n"
232                            CURSOR_STR " Type in Help to explore more      " CURSOR_STR "\n"
233                            CURSOR_STR " --------------------------------- " CURSOR_STR "\n"
234                            CURSOR_STR "                                   " CURSOR_STR);
235     for(int i=0;i<37;i++) {
236         console_output_add(CURSOR_STR);
237     }
238     console_output_addline("\n");
239 
240     return 0;
241 }
242 
console_close()243 void console_close() {
244     surface_free(&con->background);
245     list_free(&con->history);
246     hashmap_free(&con->cmds);
247     free(con);
248 }
249 
console_event(game_state * gs,SDL_Event * e)250 void console_event(game_state *gs, SDL_Event *e) {
251     if (e->type == SDL_TEXTINPUT) {
252         size_t len = strlen(con->input);
253         if (strlen(e->text.text) == 1) {
254             // make sure it is not a unicode sequence
255             unsigned char c = e->text.text[0];
256             if (c >= 32 && c <= 126) {
257                 // only allow ASCII through
258                 if (len < sizeof(con->input)-1) {
259                     con->input[len+1] = '\0';
260                     con->input[len] = c;
261                 }
262             }
263         }
264     } else if (e->type == SDL_KEYDOWN) {
265         size_t len = strlen(con->input);
266         unsigned char scancode = e->key.keysym.scancode;
267         /*if ((code >= SDLK_a && code <= SDLK_z) || (code >= SDLK_0 && code <= SDLK_9) || code == SDLK_SPACE || code == SDLK) {*/
268         // SDLK_UP and SDLK_DOWN does not work here
269         if(scancode == SDL_SCANCODE_UP) {
270             if(con->histpos < HISTORY_MAX && con->histpos < (signed int)(list_size(&con->history)-1)) {
271                 con->histpos++;
272                 con->histpos_changed = 1;
273             }
274         } else if(scancode == SDL_SCANCODE_DOWN) {
275             if(con->histpos > -1) {
276                 con->histpos--;
277                 con->histpos_changed = 1;
278             }
279         } else if(scancode == SDL_SCANCODE_LEFT) {
280             // TODO move cursor to the left
281         } else if(scancode == SDL_SCANCODE_RIGHT) {
282             // TODO move cursor to the right
283         } else if (scancode == SDL_SCANCODE_BACKSPACE || scancode == SDL_SCANCODE_DELETE) {
284             if (len > 0) {
285                 con->input[len-1] = '\0';
286             }
287         } else if (scancode == SDL_SCANCODE_RETURN || scancode == SDL_SCANCODE_KP_ENTER) {
288             // send the input somewhere and clear the input line
289             console_handle_line(gs);
290             con->input[0] = '\0';
291         } else if(scancode == SDL_SCANCODE_PAGEUP) {
292             console_output_scroll_up(1);
293         } else if(scancode == SDL_SCANCODE_PAGEDOWN) {
294             console_output_scroll_down(1);
295         }
296     }
297 }
298 
console_render()299 void console_render() {
300     if (con->ypos > 0) {
301         if(con->histpos != -1 && con->histpos_changed) {
302             char *input = list_get(&con->history, con->histpos);
303             memcpy(con->input, input, sizeof(con->input));
304             con->histpos_changed = 0;
305         }
306         if(con->histpos == -1 && con->histpos_changed) {
307             con->input[0] = '\0';
308             con->histpos_changed = 0;
309         }
310         video_render_sprite(&con->background, -1, con->ypos - 101, BLEND_ALPHA, 0);
311         int t = con->ticks / 2;
312         // input line
313         font_render(&font_small, con->input, 0 , con->ypos - 7, color_create(121, 121, 121, 255));
314         //cursor
315         font_render(&font_small, CURSOR_STR, strlen(con->input)*font_small.w , con->ypos - 7, color_create(121 - t, 121 - t, 121 - t, 255));
316         console_output_render();
317     }
318 }
319 
console_tick()320 void console_tick() {
321     if (con->isopen && con->ypos < 100) {
322         con->ypos+=4;
323         if(settings_get()->video.instant_console) { con->ypos = 100; }
324     } else if (!con->isopen && con->ypos > 0) {
325         con->ypos-=4;
326         if(settings_get()->video.instant_console) { con->ypos = 0; }
327     }
328     if(!con->dir) {
329         con->ticks++;
330     } else {
331         con->ticks--;
332     }
333     if(con->ticks > 120) {
334         con->dir = 1;
335     }
336     if(con->ticks == 0) {
337         con->dir = 0;
338     }
339 }
340 
console_add_cmd(const char * name,command_func func,const char * doc)341 void console_add_cmd(const char *name, command_func func, const char *doc) {
342     command c;
343     c.func = func;
344     c.doc = doc;
345     hashmap_sput(&con->cmds, name, &c, sizeof(command));
346 }
347 
console_remove_cmd(const char * name)348 void console_remove_cmd(const char *name) {
349     hashmap_sdel(&con->cmds, name);
350 }
351 
console_window_is_open()352 int console_window_is_open() {
353     return con->isopen;
354 }
355 
console_window_open()356 void console_window_open() {
357     if (!SDL_IsTextInputActive()) {
358         SDL_StartTextInput();
359         con->ownsinput = 1;
360     } else {
361         con->ownsinput = 0;
362     }
363     con->isopen = 1;
364 }
365 
console_window_close()366 void console_window_close() {
367     if (con->ownsinput) {
368         SDL_StopTextInput();
369     }
370     con->isopen = 0;
371 }
372