]> git.localhorst.tv Git - blank.git/blob - src/ui/ui.cpp
special treatment for players
[blank.git] / src / ui / ui.cpp
1 #include "HUD.hpp"
2 #include "Interface.hpp"
3 #include "Keymap.hpp"
4
5 #include "../app/Assets.hpp"
6 #include "../app/Environment.hpp"
7 #include "../app/FrameCounter.hpp"
8 #include "../app/init.hpp"
9 #include "../audio/Audio.hpp"
10 #include "../graphics/Font.hpp"
11 #include "../graphics/Viewport.hpp"
12 #include "../io/TokenStreamReader.hpp"
13 #include "../model/shapes.hpp"
14 #include "../world/BlockLookup.hpp"
15 #include "../world/World.hpp"
16
17 #include <algorithm>
18 #include <cmath>
19 #include <iostream>
20 #include <sstream>
21 #include <glm/gtc/matrix_transform.hpp>
22 #include <glm/gtx/io.hpp>
23
24
25 namespace blank {
26
27 HUD::HUD(const BlockTypeRegistry &types, const Font &font)
28 : types(types)
29 , font(font)
30 , block()
31 , block_buf()
32 , block_transform(1.0f)
33 , block_label()
34 , block_visible(false)
35 , crosshair() {
36         block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f));
37         block_transform = glm::scale(block_transform, glm::vec3(50.0f));
38         block_transform = glm::rotate(block_transform, 3.5f, glm::vec3(1.0f, 0.0f, 0.0f));
39         block_transform = glm::rotate(block_transform, 0.35f, glm::vec3(0.0f, 1.0f, 0.0f));
40
41         OutlineModel::Buffer buf;
42         buf.vertices = std::vector<glm::vec3>({
43                 { -10.0f,   0.0f, 0.0f }, { 10.0f,  0.0f, 0.0f },
44                 {   0.0f, -10.0f, 0.0f }, {  0.0f, 10.0f, 0.0f },
45         });
46         buf.indices = std::vector<OutlineModel::Index>({
47                 0, 1, 2, 3
48         });
49         buf.colors.resize(4, { 10.0f, 10.0f, 10.0f });
50         crosshair.Update(buf);
51
52         block_label.Position(
53                 glm::vec3(50.0f, 85.0f, 0.0f),
54                 Gravity::NORTH_WEST,
55                 Gravity::NORTH
56         );
57         block_label.Foreground(glm::vec4(1.0f));
58         block_label.Background(glm::vec4(0.5f));
59 }
60
61
62 void HUD::DisplayNone() {
63         block_visible = false;
64 }
65
66 void HUD::Display(const Block &b) {
67         const BlockType &type = types.Get(b.type);
68
69         block_buf.Clear();
70         type.FillEntityModel(block_buf, b.Transform());
71         block.Update(block_buf);
72
73         block_label.Set(font, type.label);
74
75         block_visible = type.visible;
76 }
77
78
79 void HUD::Render(Viewport &viewport) noexcept {
80         viewport.ClearDepth();
81
82         PlainColor &outline_prog = viewport.HUDOutlineProgram();
83         viewport.EnableInvertBlending();
84         viewport.SetCursor(glm::vec3(0.0f), Gravity::CENTER);
85         outline_prog.SetM(viewport.Cursor());
86         crosshair.Draw();
87
88         if (block_visible) {
89                 DirectionalLighting &world_prog = viewport.HUDProgram();
90                 world_prog.SetLightDirection({ 1.0f, 3.0f, 5.0f });
91                 // disable distance fog
92                 world_prog.SetFogDensity(0.0f);
93
94                 viewport.DisableBlending();
95                 world_prog.SetM(block_transform);
96                 block.Draw();
97                 block_label.Render(viewport);
98         }
99 }
100
101
102 Interface::Interface(
103         const Config &config,
104         Environment &env,
105         World &world)
106 : env(env)
107 , world(world)
108 // let's assume this succeeds and hope for the best for now
109 , ctrl(*world.AddPlayer(config.player_name))
110 , hud(world.BlockTypes(), env.assets.small_ui_font)
111 , aim{{ 0, 0, 0 }, { 0, 0, -1 }}
112 , aim_world()
113 , aim_entity()
114 , outline()
115 , outline_transform(1.0f)
116 , counter_text()
117 , position_text()
118 , orientation_text()
119 , block_text()
120 , last_block()
121 , last_entity(nullptr)
122 , messages(env.assets.small_ui_font)
123 , msg_timer(5000)
124 , config(config)
125 , place_timer(256)
126 , remove_timer(256)
127 , remove(0)
128 , selection(0)
129 , place_sound(env.loader.LoadSound("thump"))
130 , remove_sound(env.loader.LoadSound("plop"))
131 , fwd(0)
132 , rev(0)
133 , debug(false) {
134         counter_text.Position(glm::vec3(-25.0f, 25.0f, 0.0f), Gravity::NORTH_EAST);
135         counter_text.Foreground(glm::vec4(1.0f));
136         counter_text.Background(glm::vec4(0.5f));
137         position_text.Position(glm::vec3(-25.0f, 25.0f + env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
138         position_text.Foreground(glm::vec4(1.0f));
139         position_text.Background(glm::vec4(0.5f));
140         orientation_text.Position(glm::vec3(-25.0f, 25.0f + 2 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
141         orientation_text.Foreground(glm::vec4(1.0f));
142         orientation_text.Background(glm::vec4(0.5f));
143         block_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
144         block_text.Foreground(glm::vec4(1.0f));
145         block_text.Background(glm::vec4(0.5f));
146         block_text.Set(env.assets.small_ui_font, "Block: none");
147         entity_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
148         entity_text.Foreground(glm::vec4(1.0f));
149         entity_text.Background(glm::vec4(0.5f));
150         entity_text.Set(env.assets.small_ui_font, "Entity: none");
151         messages.Position(glm::vec3(25.0f, -25.0f, 0.0f), Gravity::SOUTH_WEST);
152         messages.Foreground(glm::vec4(1.0f));
153         messages.Background(glm::vec4(0.5f));
154         hud.DisplayNone();
155 }
156
157
158 void Interface::HandlePress(const SDL_KeyboardEvent &event) {
159         if (config.keyboard_disabled) return;
160
161         switch (env.keymap.Lookup(event)) {
162                 case Keymap::MOVE_FORWARD:
163                         rev.z = 1;
164                         break;
165                 case Keymap::MOVE_BACKWARD:
166                         fwd.z = 1;
167                         break;
168                 case Keymap::MOVE_LEFT:
169                         rev.x = 1;
170                         break;
171                 case Keymap::MOVE_RIGHT:
172                         fwd.x = 1;
173                         break;
174                 case Keymap::MOVE_UP:
175                         fwd.y = 1;
176                         break;
177                 case Keymap::MOVE_DOWN:
178                         rev.y = 1;
179                         break;
180
181                 case Keymap::BLOCK_FACE:
182                         FaceBlock();
183                         break;
184                 case Keymap::BLOCK_TURN:
185                         TurnBlock();
186                         break;
187                 case Keymap::BLOCK_NEXT:
188                         SelectNext();
189                         break;
190                 case Keymap::BLOCK_PREV:
191                         SelectPrevious();
192                         break;
193
194                 case Keymap::BLOCK_PLACE:
195                         PlaceBlock();
196                         break;
197                 case Keymap::BLOCK_PICK:
198                         PickBlock();
199                         break;
200                 case Keymap::BLOCK_REMOVE:
201                         RemoveBlock();
202                         break;
203
204                 case Keymap::TOGGLE_COLLISION:
205                         ToggleCollision();
206                         break;
207
208                 case Keymap::TOGGLE_VISUAL:
209                         ToggleVisual();
210                         break;
211                 case Keymap::TOGGLE_DEBUG:
212                         ToggleDebug();
213                         break;
214                 case Keymap::TOGGLE_AUDIO:
215                         ToggleAudio();
216                         break;
217
218                 case Keymap::EXIT:
219                         env.state.Pop();
220                         break;
221
222                 default:
223                         break;
224         }
225 }
226
227 void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
228         if (config.keyboard_disabled) return;
229
230         switch (env.keymap.Lookup(event)) {
231                 case Keymap::MOVE_FORWARD:
232                         rev.z = 0;
233                         break;
234                 case Keymap::MOVE_BACKWARD:
235                         fwd.z = 0;
236                         break;
237                 case Keymap::MOVE_LEFT:
238                         rev.x = 0;
239                         break;
240                 case Keymap::MOVE_RIGHT:
241                         fwd.x = 0;
242                         break;
243                 case Keymap::MOVE_UP:
244                         fwd.y = 0;
245                         break;
246                 case Keymap::MOVE_DOWN:
247                         rev.y = 0;
248                         break;
249
250                 default:
251                         break;
252         }
253 }
254
255 void Interface::FaceBlock() {
256         selection.SetFace(Block::Face((selection.GetFace() + 1) % Block::FACE_COUNT));
257         hud.Display(selection);
258 }
259
260 void Interface::TurnBlock() {
261         selection.SetTurn(Block::Turn((selection.GetTurn() + 1) % Block::TURN_COUNT));
262         hud.Display(selection);
263 }
264
265 void Interface::ToggleCollision() {
266         ctrl.Controlled().WorldCollidable(!ctrl.Controlled().WorldCollidable());
267         if (ctrl.Controlled().WorldCollidable()) {
268                 PostMessage("collision on");
269         } else {
270                 PostMessage("collision off");
271         }
272 }
273
274 void Interface::ToggleAudio() {
275         config.audio_disabled = !config.audio_disabled;
276         if (config.audio_disabled) {
277                 PostMessage("audio off");
278         } else {
279                 PostMessage("audio on");
280         }
281 }
282
283 void Interface::ToggleVisual() {
284         config.visual_disabled = !config.visual_disabled;
285         if (config.visual_disabled) {
286                 PostMessage("visual off");
287         } else {
288                 PostMessage("visual on");
289         }
290 }
291
292 void Interface::ToggleDebug() {
293         debug = !debug;
294         if (debug) {
295                 UpdateCounter();
296                 UpdatePosition();
297                 UpdateOrientation();
298                 UpdateBlockInfo();
299                 UpdateEntityInfo();
300         }
301 }
302
303 void Interface::UpdateCounter() {
304         std::stringstream s;
305         s << std::setprecision(3) <<
306                 "avg: " << env.counter.Average().running << "ms, "
307                 "peak: " << env.counter.Peak().running << "ms";
308         std::string text = s.str();
309         counter_text.Set(env.assets.small_ui_font, text);
310 }
311
312 void Interface::UpdatePosition() {
313         std::stringstream s;
314         s << std::setprecision(3) << "pos: " << ctrl.Controlled().AbsolutePosition();
315         position_text.Set(env.assets.small_ui_font, s.str());
316 }
317
318 void Interface::UpdateOrientation() {
319         std::stringstream s;
320         s << std::setprecision(3) << "pitch: " << rad2deg(ctrl.Pitch())
321                 << ", yaw: " << rad2deg(ctrl.Yaw());
322         orientation_text.Set(env.assets.small_ui_font, s.str());
323 }
324
325 void Interface::UpdateBlockInfo() {
326         if (aim_world) {
327                 const Block &block = aim_world.GetBlock();
328                 if (last_block != block) {
329                         std::stringstream s;
330                         s << "Block: "
331                                 << aim_world.GetType().label
332                                 << ", face: " << block.GetFace()
333                                 << ", turn: " << block.GetTurn();
334                         block_text.Set(env.assets.small_ui_font, s.str());
335                         last_block = block;
336                 }
337         } else {
338                 if (last_block != Block()) {
339                         std::stringstream s;
340                         s << "Block: none";
341                         block_text.Set(env.assets.small_ui_font, s.str());
342                         last_block = Block();
343                 }
344         }
345 }
346
347 void Interface::UpdateEntityInfo() {
348         if (aim_entity) {
349                 if (last_entity != aim_entity.entity) {
350                         std::stringstream s;
351                         s << "Entity: " << aim_entity.entity->Name();
352                         entity_text.Set(env.assets.small_ui_font, s.str());
353                         last_entity = aim_entity.entity;
354                 }
355         }
356 }
357
358
359 void Interface::Handle(const SDL_MouseMotionEvent &event) {
360         if (config.mouse_disabled) return;
361         ctrl.RotateYaw(event.xrel * config.yaw_sensitivity);
362         ctrl.RotatePitch(event.yrel * config.pitch_sensitivity);
363 }
364
365 void Interface::HandlePress(const SDL_MouseButtonEvent &event) {
366         if (config.mouse_disabled) return;
367
368         if (event.button == SDL_BUTTON_LEFT) {
369                 RemoveBlock();
370                 remove_timer.Start();
371         } else if (event.button == SDL_BUTTON_MIDDLE) {
372                 PickBlock();
373         } else if (event.button == SDL_BUTTON_RIGHT) {
374                 PlaceBlock();
375                 place_timer.Start();
376         }
377 }
378
379 void Interface::HandleRelease(const SDL_MouseButtonEvent &event) {
380         if (config.mouse_disabled) return;
381
382         if (event.button == SDL_BUTTON_LEFT) {
383                 remove_timer.Stop();
384         } else if (event.button == SDL_BUTTON_RIGHT) {
385                 place_timer.Stop();
386         }
387 }
388
389 void Interface::PickBlock() {
390         if (!aim_world) return;
391         selection = aim_world.GetBlock();
392         hud.Display(selection);
393 }
394
395 void Interface::PlaceBlock() {
396         if (!aim_world) return;
397
398         glm::vec3 next_pos = aim_world.BlockCoords() + aim_world.normal;
399         BlockLookup next_block(&aim_world.GetChunk(), next_pos);
400         if (next_block) {
401         }
402         next_block.SetBlock(selection);
403
404         if (config.audio_disabled) return;
405         const Entity &player = ctrl.Controlled();
406         env.audio.Play(
407                 place_sound,
408                 aim_world.GetChunk().ToSceneCoords(player.ChunkCoords(), next_pos)
409         );
410 }
411
412 void Interface::RemoveBlock() noexcept {
413         if (!aim_world) return;
414         aim_world.SetBlock(remove);
415
416         if (config.audio_disabled) return;
417         const Entity &player = ctrl.Controlled();
418         env.audio.Play(
419                 remove_sound,
420                 aim_world.GetChunk().ToSceneCoords(player.ChunkCoords(), aim_world.BlockCoords())
421         );
422 }
423
424
425 void Interface::Handle(const SDL_MouseWheelEvent &event) {
426         if (config.mouse_disabled) return;
427
428         if (event.y < 0) {
429                 SelectNext();
430         } else if (event.y > 0) {
431                 SelectPrevious();
432         }
433 }
434
435 void Interface::SelectNext() {
436         ++selection.type;
437         if (size_t(selection.type) >= world.BlockTypes().Size()) {
438                 selection.type = 1;
439         }
440         hud.Display(selection);
441 }
442
443 void Interface::SelectPrevious() {
444         --selection.type;
445         if (selection.type <= 0) {
446                 selection.type = world.BlockTypes().Size() - 1;
447         }
448         hud.Display(selection);
449 }
450
451
452 void Interface::PostMessage(const char *msg) {
453         messages.PushLine(msg);
454         msg_timer.Reset();
455         msg_timer.Start();
456         std::cout << msg << std::endl;
457 }
458
459
460 void Interface::Update(int dt) {
461         ctrl.Velocity(glm::vec3(fwd - rev) * config.move_velocity);
462         ctrl.Update(dt);
463
464         msg_timer.Update(dt);
465         place_timer.Update(dt);
466         remove_timer.Update(dt);
467
468         aim = ctrl.Aim();
469         CheckAim();
470
471         if (msg_timer.HitOnce()) {
472                 msg_timer.Stop();
473         }
474
475         if (remove_timer.Hit()) {
476                 RemoveBlock();
477                 CheckAim();
478         }
479
480         if (place_timer.Hit()) {
481                 PlaceBlock();
482                 CheckAim();
483         }
484
485         if (debug) {
486                 if (env.counter.Changed()) {
487                         UpdateCounter();
488                 }
489                 UpdatePosition();
490                 UpdateOrientation();
491         }
492 }
493
494 namespace {
495
496 OutlineModel::Buffer outl_buf;
497
498 }
499
500 void Interface::CheckAim() {
501         if (!world.Intersection(aim, glm::mat4(1.0f), ctrl.Controlled().ChunkCoords(), aim_world)) {
502                 aim_world = WorldCollision();
503         }
504         if (!world.Intersection(aim, glm::mat4(1.0f), ctrl.Controlled(), aim_entity)) {
505                 aim_entity = EntityCollision();
506         }
507         if (aim_world && aim_entity) {
508                 // got both, pick the closest one
509                 if (aim_world.depth < aim_entity.depth) {
510                         UpdateOutline();
511                         aim_entity = EntityCollision();
512                 } else {
513                         aim_world = WorldCollision();
514                 }
515         } else if (aim_world) {
516                 UpdateOutline();
517         }
518         if (debug) {
519                 UpdateBlockInfo();
520                 UpdateEntityInfo();
521         }
522 }
523
524 void Interface::UpdateOutline() {
525         outl_buf.Clear();
526         aim_world.GetType().FillOutlineModel(outl_buf);
527         outline.Update(outl_buf);
528         outline_transform = aim_world.GetChunk().Transform(Player().ChunkCoords());
529         outline_transform *= aim_world.BlockTransform();
530         outline_transform *= glm::scale(glm::vec3(1.005f));
531 }
532
533
534 void Interface::Render(Viewport &viewport) noexcept {
535         if (config.visual_disabled) return;
536
537         if (aim_world) {
538                 PlainColor &outline_prog = viewport.WorldOutlineProgram();
539                 outline_prog.SetM(outline_transform);
540                 outline.Draw();
541         }
542
543         if (debug) {
544                 counter_text.Render(viewport);
545                 position_text.Render(viewport);
546                 orientation_text.Render(viewport);
547                 if (aim_world) {
548                         block_text.Render(viewport);
549                 } else if (aim_entity) {
550                         entity_text.Render(viewport);
551                 }
552         }
553
554         if (msg_timer.Running()) {
555                 messages.Render(viewport);
556         }
557
558         hud.Render(viewport);
559 }
560
561
562 Keymap::Keymap()
563 : codemap{ NONE } {
564
565 }
566
567 void Keymap::Map(SDL_Scancode scancode, Action action) {
568         if (scancode > MAX_SCANCODE) {
569                 throw std::runtime_error("refusing to map scancode: too damn high");
570         }
571         codemap[scancode] = action;
572 }
573
574 Keymap::Action Keymap::Lookup(SDL_Scancode scancode) {
575         if (scancode < NUM_SCANCODES) {
576                 return codemap[scancode];
577         } else {
578                 return NONE;
579         }
580 }
581
582
583 void Keymap::LoadDefault() {
584         Map(SDL_SCANCODE_UP, MOVE_FORWARD);
585         Map(SDL_SCANCODE_W, MOVE_FORWARD);
586         Map(SDL_SCANCODE_DOWN, MOVE_BACKWARD);
587         Map(SDL_SCANCODE_S, MOVE_BACKWARD);
588         Map(SDL_SCANCODE_LEFT, MOVE_LEFT);
589         Map(SDL_SCANCODE_A, MOVE_LEFT);
590         Map(SDL_SCANCODE_RIGHT, MOVE_RIGHT);
591         Map(SDL_SCANCODE_D, MOVE_RIGHT);
592         Map(SDL_SCANCODE_SPACE, MOVE_UP);
593         Map(SDL_SCANCODE_RSHIFT, MOVE_UP);
594         Map(SDL_SCANCODE_LSHIFT, MOVE_DOWN);
595         Map(SDL_SCANCODE_LCTRL, MOVE_DOWN);
596         Map(SDL_SCANCODE_RCTRL, MOVE_DOWN);
597
598         Map(SDL_SCANCODE_Q, BLOCK_FACE);
599         Map(SDL_SCANCODE_E, BLOCK_TURN);
600         Map(SDL_SCANCODE_TAB, BLOCK_NEXT);
601         Map(SDL_SCANCODE_RIGHTBRACKET, BLOCK_NEXT);
602         Map(SDL_SCANCODE_LEFTBRACKET, BLOCK_PREV);
603
604         Map(SDL_SCANCODE_INSERT, BLOCK_PLACE);
605         Map(SDL_SCANCODE_RETURN, BLOCK_PLACE);
606         Map(SDL_SCANCODE_MENU, BLOCK_PICK);
607         Map(SDL_SCANCODE_DELETE, BLOCK_REMOVE);
608         Map(SDL_SCANCODE_BACKSPACE, BLOCK_REMOVE);
609
610         Map(SDL_SCANCODE_N, TOGGLE_COLLISION);
611         Map(SDL_SCANCODE_F1, TOGGLE_VISUAL);
612         Map(SDL_SCANCODE_F3, TOGGLE_DEBUG);
613         Map(SDL_SCANCODE_F4, TOGGLE_AUDIO);
614
615         Map(SDL_SCANCODE_ESCAPE, EXIT);
616 }
617
618
619 void Keymap::Load(std::istream &is) {
620         TokenStreamReader in(is);
621         std::string key_name;
622         std::string action_name;
623         SDL_Scancode key;
624         Action action;
625         while (in.HasMore()) {
626                 if (in.Peek().type == Token::STRING) {
627                         in.ReadString(key_name);
628                         key = SDL_GetScancodeFromName(key_name.c_str());
629                 } else {
630                         key = SDL_Scancode(in.GetInt());
631                 }
632                 in.Skip(Token::EQUALS);
633                 in.ReadIdentifier(action_name);
634                 action = StringToAction(action_name);
635                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
636                         in.Skip(Token::SEMICOLON);
637                 }
638                 Map(key, action);
639         }
640 }
641
642 void Keymap::Save(std::ostream &out) {
643         for (unsigned int i = 0; i < NUM_SCANCODES; ++i) {
644                 if (codemap[i] == NONE) continue;
645
646                 const char *str = SDL_GetScancodeName(SDL_Scancode(i));
647                 if (str && *str) {
648                         out << '"';
649                         while (*str) {
650                                 if (*str == '"') {
651                                         out << "\\\"";
652                                 } else {
653                                         out << *str;
654                                 }
655                                 ++str;
656                         }
657                         out << '"';
658                 } else {
659                         out << i;
660                 }
661
662                 out << " = " << ActionToString(codemap[i]) << std::endl;;
663         }
664 }
665
666
667 const char *Keymap::ActionToString(Action action) {
668         switch (action) {
669                 default:
670                 case NONE:
671                         return "none";
672                 case MOVE_FORWARD:
673                         return "move_forward";
674                 case MOVE_BACKWARD:
675                         return "move_backward";
676                 case MOVE_LEFT:
677                         return "move_left";
678                 case MOVE_RIGHT:
679                         return "move_right";
680                 case MOVE_UP:
681                         return "move_up";
682                 case MOVE_DOWN:
683                         return "move_down";
684                 case BLOCK_FACE:
685                         return "block_face";
686                 case BLOCK_TURN:
687                         return "block_turn";
688                 case BLOCK_NEXT:
689                         return "block_next";
690                 case BLOCK_PREV:
691                         return "block_prev";
692                 case BLOCK_PLACE:
693                         return "block_place";
694                 case BLOCK_PICK:
695                         return "block_pick";
696                 case BLOCK_REMOVE:
697                         return "block_remove";
698                 case TOGGLE_COLLISION:
699                         return "toggle_collision";
700                 case TOGGLE_AUDIO:
701                         return "toggle_audio";
702                 case TOGGLE_VISUAL:
703                         return "toggle_visual";
704                 case TOGGLE_DEBUG:
705                         return "toggle_debug";
706                 case EXIT:
707                         return "exit";
708         }
709 }
710
711 Keymap::Action Keymap::StringToAction(const std::string &str) {
712         if (str == "move_forward") {
713                 return MOVE_FORWARD;
714         } else if (str == "move_backward") {
715                 return MOVE_BACKWARD;
716         } else if (str == "move_left") {
717                 return MOVE_LEFT;
718         } else if (str == "move_right") {
719                 return MOVE_RIGHT;
720         } else if (str == "move_up") {
721                 return MOVE_UP;
722         } else if (str == "move_down") {
723                 return MOVE_DOWN;
724         } else if (str == "block_face") {
725                 return BLOCK_FACE;
726         } else if (str == "block_turn") {
727                 return BLOCK_TURN;
728         } else if (str == "block_next") {
729                 return BLOCK_NEXT;
730         } else if (str == "block_prev") {
731                 return BLOCK_PREV;
732         } else if (str == "block_place") {
733                 return BLOCK_PLACE;
734         } else if (str == "block_pick") {
735                 return BLOCK_PICK;
736         } else if (str == "block_remove") {
737                 return BLOCK_REMOVE;
738         } else if (str == "toggle_collision") {
739                 return TOGGLE_COLLISION;
740         } else if (str == "toggle_audio") {
741                 return TOGGLE_AUDIO;
742         } else if (str == "toggle_visual") {
743                 return TOGGLE_VISUAL;
744         } else if (str == "toggle_debug") {
745                 return TOGGLE_DEBUG;
746         } else if (str == "exit") {
747                 return EXIT;
748         } else {
749                 return NONE;
750         }
751 }
752
753 }