]> git.localhorst.tv Git - blank.git/blob - src/ui/ui.cpp
split composite model in template and instance
[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 , ctrl(world.Player())
109 , hud(world.BlockTypes(), env.assets.small_ui_font)
110 , aim{{ 0, 0, 0 }, { 0, 0, -1 }}
111 , aim_world()
112 , aim_entity()
113 , outline()
114 , outline_transform(1.0f)
115 , counter_text()
116 , position_text()
117 , orientation_text()
118 , block_text()
119 , last_block()
120 , last_entity(nullptr)
121 , messages(env.assets.small_ui_font)
122 , msg_timer(5000)
123 , config(config)
124 , place_timer(256)
125 , remove_timer(256)
126 , remove(0)
127 , selection(0)
128 , place_sound(env.assets.LoadSound("thump"))
129 , remove_sound(env.assets.LoadSound("plop"))
130 , fwd(0)
131 , rev(0)
132 , debug(false) {
133         counter_text.Position(glm::vec3(-25.0f, 25.0f, 0.0f), Gravity::NORTH_EAST);
134         counter_text.Foreground(glm::vec4(1.0f));
135         counter_text.Background(glm::vec4(0.5f));
136         position_text.Position(glm::vec3(-25.0f, 25.0f + env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
137         position_text.Foreground(glm::vec4(1.0f));
138         position_text.Background(glm::vec4(0.5f));
139         orientation_text.Position(glm::vec3(-25.0f, 25.0f + 2 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
140         orientation_text.Foreground(glm::vec4(1.0f));
141         orientation_text.Background(glm::vec4(0.5f));
142         block_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
143         block_text.Foreground(glm::vec4(1.0f));
144         block_text.Background(glm::vec4(0.5f));
145         block_text.Set(env.assets.small_ui_font, "Block: none");
146         entity_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
147         entity_text.Foreground(glm::vec4(1.0f));
148         entity_text.Background(glm::vec4(0.5f));
149         entity_text.Set(env.assets.small_ui_font, "Entity: none");
150         messages.Position(glm::vec3(25.0f, -25.0f, 0.0f), Gravity::SOUTH_WEST);
151         messages.Foreground(glm::vec4(1.0f));
152         messages.Background(glm::vec4(0.5f));
153         hud.DisplayNone();
154 }
155
156
157 void Interface::HandlePress(const SDL_KeyboardEvent &event) {
158         if (config.keyboard_disabled) return;
159
160         switch (env.keymap.Lookup(event)) {
161                 case Keymap::MOVE_FORWARD:
162                         rev.z = 1;
163                         break;
164                 case Keymap::MOVE_BACKWARD:
165                         fwd.z = 1;
166                         break;
167                 case Keymap::MOVE_LEFT:
168                         rev.x = 1;
169                         break;
170                 case Keymap::MOVE_RIGHT:
171                         fwd.x = 1;
172                         break;
173                 case Keymap::MOVE_UP:
174                         fwd.y = 1;
175                         break;
176                 case Keymap::MOVE_DOWN:
177                         rev.y = 1;
178                         break;
179
180                 case Keymap::BLOCK_FACE:
181                         FaceBlock();
182                         break;
183                 case Keymap::BLOCK_TURN:
184                         TurnBlock();
185                         break;
186                 case Keymap::BLOCK_NEXT:
187                         SelectNext();
188                         break;
189                 case Keymap::BLOCK_PREV:
190                         SelectPrevious();
191                         break;
192
193                 case Keymap::BLOCK_PLACE:
194                         PlaceBlock();
195                         break;
196                 case Keymap::BLOCK_PICK:
197                         PickBlock();
198                         break;
199                 case Keymap::BLOCK_REMOVE:
200                         RemoveBlock();
201                         break;
202
203                 case Keymap::TOGGLE_COLLISION:
204                         ToggleCollision();
205                         break;
206
207                 case Keymap::PRINT_BLOCK:
208                         PrintBlockInfo();
209                         break;
210                 case Keymap::PRINT_CHUNK:
211                         PrintChunkInfo();
212                         break;
213                 case Keymap::PRINT_LIGHT:
214                         PrintLightInfo();
215                         break;
216                 case Keymap::PRINT_SELECTION:
217                         PrintSelectionInfo();
218                         break;
219
220                 case Keymap::TOGGLE_VISUAL:
221                         ToggleVisual();
222                         break;
223                 case Keymap::TOGGLE_DEBUG:
224                         ToggleDebug();
225                         break;
226                 case Keymap::TOGGLE_AUDIO:
227                         ToggleAudio();
228                         break;
229
230                 case Keymap::EXIT:
231                         env.state.Pop();
232                         break;
233
234                 default:
235                         break;
236         }
237 }
238
239 void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
240         if (config.keyboard_disabled) return;
241
242         switch (env.keymap.Lookup(event)) {
243                 case Keymap::MOVE_FORWARD:
244                         rev.z = 0;
245                         break;
246                 case Keymap::MOVE_BACKWARD:
247                         fwd.z = 0;
248                         break;
249                 case Keymap::MOVE_LEFT:
250                         rev.x = 0;
251                         break;
252                 case Keymap::MOVE_RIGHT:
253                         fwd.x = 0;
254                         break;
255                 case Keymap::MOVE_UP:
256                         fwd.y = 0;
257                         break;
258                 case Keymap::MOVE_DOWN:
259                         rev.y = 0;
260                         break;
261
262                 default:
263                         break;
264         }
265 }
266
267 void Interface::FaceBlock() {
268         selection.SetFace(Block::Face((selection.GetFace() + 1) % Block::FACE_COUNT));
269         hud.Display(selection);
270 }
271
272 void Interface::TurnBlock() {
273         selection.SetTurn(Block::Turn((selection.GetTurn() + 1) % Block::TURN_COUNT));
274         hud.Display(selection);
275 }
276
277 void Interface::ToggleCollision() {
278         ctrl.Controlled().WorldCollidable(!ctrl.Controlled().WorldCollidable());
279         if (ctrl.Controlled().WorldCollidable()) {
280                 PostMessage("collision on");
281         } else {
282                 PostMessage("collision off");
283         }
284 }
285
286 void Interface::PrintBlockInfo() {
287         std::cout << std::endl;
288         if (!aim_world) {
289                 PostMessage("not looking at any block");
290                 Ray aim = ctrl.Aim();
291                 std::stringstream s;
292                 s << "aim ray: " << aim.orig << ", " << aim.dir;
293                 PostMessage(s.str());
294                 return;
295         }
296         std::stringstream s;
297         s << "looking at block " << aim_world.block
298                 << " " << aim_world.BlockCoords()
299                 << " of chunk " << aim_world.GetChunk().Position()
300         ;
301         PostMessage(s.str());
302         Print(aim_world.GetBlock());
303 }
304
305 void Interface::PrintChunkInfo() {
306         std::cout << std::endl;
307         if (!aim_world) {
308                 PostMessage("not looking at any block");
309                 return;
310         }
311         std::stringstream s;
312         s << "looking at chunk " << aim_world.GetChunk().Position();
313         PostMessage(s.str());
314
315         PostMessage("  neighbors:");
316         if (aim_world.GetChunk().HasNeighbor(Block::FACE_LEFT)) {
317                 s.str("");
318                 s << " left  " << aim_world.GetChunk().GetNeighbor(Block::FACE_LEFT).Position();
319                 PostMessage(s.str());
320         }
321         if (aim_world.GetChunk().HasNeighbor(Block::FACE_RIGHT)) {
322                 s.str("");
323                 s << " right " << aim_world.GetChunk().GetNeighbor(Block::FACE_RIGHT).Position();
324                 PostMessage(s.str());
325         }
326         if (aim_world.GetChunk().HasNeighbor(Block::FACE_UP)) {
327                 s.str("");
328                 s << " up    " << aim_world.GetChunk().GetNeighbor(Block::FACE_UP).Position();
329                 PostMessage(s.str());
330         }
331         if (aim_world.GetChunk().HasNeighbor(Block::FACE_DOWN)) {
332                 s.str("");
333                 s << " down  " << aim_world.GetChunk().GetNeighbor(Block::FACE_DOWN).Position();
334                 PostMessage(s.str());
335         }
336         if (aim_world.GetChunk().HasNeighbor(Block::FACE_FRONT)) {
337                 s.str("");
338                 s << " front " << aim_world.GetChunk().GetNeighbor(Block::FACE_FRONT).Position();
339                 PostMessage(s.str());
340         }
341         if (aim_world.GetChunk().HasNeighbor(Block::FACE_BACK)) {
342                 s.str("");
343                 s << " back  " << aim_world.GetChunk().GetNeighbor(Block::FACE_BACK).Position();
344                 PostMessage(s.str());
345         }
346         std::cout << std::endl;
347 }
348
349 void Interface::PrintLightInfo() {
350         std::stringstream s;
351         s
352                 << "light level " << world.PlayerChunk().GetLight(world.Player().Position())
353                 << " at position " << world.Player().Position()
354         ;
355         PostMessage(s.str());
356 }
357
358 void Interface::PrintSelectionInfo() {
359         std::cout << std::endl;
360         Print(selection);
361 }
362
363 void Interface::Print(const Block &block) {
364         std::stringstream s;
365         s << "type: " << block.type
366                 << ", face: " << block.GetFace()
367                 << ", turn: " << block.GetTurn()
368         ;
369         PostMessage(s.str());
370 }
371
372 void Interface::ToggleAudio() {
373         config.audio_disabled = !config.audio_disabled;
374         if (config.audio_disabled) {
375                 PostMessage("audio off");
376         } else {
377                 PostMessage("audio on");
378         }
379 }
380
381 void Interface::ToggleVisual() {
382         config.visual_disabled = !config.visual_disabled;
383         if (config.visual_disabled) {
384                 PostMessage("visual off");
385         } else {
386                 PostMessage("visual on");
387         }
388 }
389
390 void Interface::ToggleDebug() {
391         debug = !debug;
392         if (debug) {
393                 UpdateCounter();
394                 UpdatePosition();
395                 UpdateOrientation();
396                 UpdateBlockInfo();
397                 UpdateEntityInfo();
398         }
399 }
400
401 void Interface::UpdateCounter() {
402         std::stringstream s;
403         s << std::setprecision(3) <<
404                 "avg: " << env.counter.Average().running << "ms, "
405                 "peak: " << env.counter.Peak().running << "ms";
406         std::string text = s.str();
407         counter_text.Set(env.assets.small_ui_font, text);
408 }
409
410 void Interface::UpdatePosition() {
411         std::stringstream s;
412         s << std::setprecision(3) << "pos: " << ctrl.Controlled().AbsolutePosition();
413         position_text.Set(env.assets.small_ui_font, s.str());
414 }
415
416 void Interface::UpdateOrientation() {
417         std::stringstream s;
418         s << std::setprecision(3) << "pitch: " << rad2deg(ctrl.Pitch())
419                 << ", yaw: " << rad2deg(ctrl.Yaw());
420         orientation_text.Set(env.assets.small_ui_font, s.str());
421 }
422
423 void Interface::UpdateBlockInfo() {
424         if (aim_world) {
425                 const Block &block = aim_world.GetBlock();
426                 if (last_block != block) {
427                         std::stringstream s;
428                         s << "Block: "
429                                 << aim_world.GetType().label
430                                 << ", face: " << block.GetFace()
431                                 << ", turn: " << block.GetTurn();
432                         block_text.Set(env.assets.small_ui_font, s.str());
433                         last_block = block;
434                 }
435         } else {
436                 if (last_block != Block()) {
437                         std::stringstream s;
438                         s << "Block: none";
439                         block_text.Set(env.assets.small_ui_font, s.str());
440                         last_block = Block();
441                 }
442         }
443 }
444
445 void Interface::UpdateEntityInfo() {
446         if (aim_entity) {
447                 if (last_entity != aim_entity.entity) {
448                         std::stringstream s;
449                         s << "Entity: " << aim_entity.entity->Name();
450                         entity_text.Set(env.assets.small_ui_font, s.str());
451                         last_entity = aim_entity.entity;
452                 }
453         }
454 }
455
456
457 void Interface::Handle(const SDL_MouseMotionEvent &event) {
458         if (config.mouse_disabled) return;
459         ctrl.RotateYaw(event.xrel * config.yaw_sensitivity);
460         ctrl.RotatePitch(event.yrel * config.pitch_sensitivity);
461 }
462
463 void Interface::HandlePress(const SDL_MouseButtonEvent &event) {
464         if (config.mouse_disabled) return;
465
466         if (event.button == SDL_BUTTON_LEFT) {
467                 RemoveBlock();
468                 remove_timer.Start();
469         } else if (event.button == SDL_BUTTON_MIDDLE) {
470                 PickBlock();
471         } else if (event.button == SDL_BUTTON_RIGHT) {
472                 PlaceBlock();
473                 place_timer.Start();
474         }
475 }
476
477 void Interface::HandleRelease(const SDL_MouseButtonEvent &event) {
478         if (config.mouse_disabled) return;
479
480         if (event.button == SDL_BUTTON_LEFT) {
481                 remove_timer.Stop();
482         } else if (event.button == SDL_BUTTON_RIGHT) {
483                 place_timer.Stop();
484         }
485 }
486
487 void Interface::PickBlock() {
488         if (!aim_world) return;
489         selection = aim_world.GetBlock();
490         hud.Display(selection);
491 }
492
493 void Interface::PlaceBlock() {
494         if (!aim_world) return;
495
496         glm::vec3 next_pos = aim_world.BlockCoords() + aim_world.normal;
497         BlockLookup next_block(&aim_world.GetChunk(), next_pos);
498         if (next_block) {
499         }
500         next_block.SetBlock(selection);
501
502         if (config.audio_disabled) return;
503         const Entity &player = ctrl.Controlled();
504         env.audio.Play(
505                 place_sound,
506                 aim_world.GetChunk().ToSceneCoords(player.ChunkCoords(), next_pos)
507         );
508 }
509
510 void Interface::RemoveBlock() noexcept {
511         if (!aim_world) return;
512         aim_world.SetBlock(remove);
513
514         if (config.audio_disabled) return;
515         const Entity &player = ctrl.Controlled();
516         env.audio.Play(
517                 remove_sound,
518                 aim_world.GetChunk().ToSceneCoords(player.ChunkCoords(), aim_world.BlockCoords())
519         );
520 }
521
522
523 void Interface::Handle(const SDL_MouseWheelEvent &event) {
524         if (config.mouse_disabled) return;
525
526         if (event.y < 0) {
527                 SelectNext();
528         } else if (event.y > 0) {
529                 SelectPrevious();
530         }
531 }
532
533 void Interface::SelectNext() {
534         ++selection.type;
535         if (size_t(selection.type) >= world.BlockTypes().Size()) {
536                 selection.type = 1;
537         }
538         hud.Display(selection);
539 }
540
541 void Interface::SelectPrevious() {
542         --selection.type;
543         if (selection.type <= 0) {
544                 selection.type = world.BlockTypes().Size() - 1;
545         }
546         hud.Display(selection);
547 }
548
549
550 void Interface::PostMessage(const char *msg) {
551         messages.PushLine(msg);
552         msg_timer.Reset();
553         msg_timer.Start();
554         std::cout << msg << std::endl;
555 }
556
557
558 void Interface::Update(int dt) {
559         ctrl.Velocity(glm::vec3(fwd - rev) * config.move_velocity);
560         ctrl.Update(dt);
561
562         msg_timer.Update(dt);
563         place_timer.Update(dt);
564         remove_timer.Update(dt);
565
566         aim = ctrl.Aim();
567         CheckAim();
568
569         if (msg_timer.HitOnce()) {
570                 msg_timer.Stop();
571         }
572
573         if (remove_timer.Hit()) {
574                 RemoveBlock();
575                 CheckAim();
576         }
577
578         if (place_timer.Hit()) {
579                 PlaceBlock();
580                 CheckAim();
581         }
582
583         if (debug) {
584                 if (env.counter.Changed()) {
585                         UpdateCounter();
586                 }
587                 UpdatePosition();
588                 UpdateOrientation();
589         }
590 }
591
592 namespace {
593
594 OutlineModel::Buffer outl_buf;
595
596 }
597
598 void Interface::CheckAim() {
599         if (!world.Intersection(aim, glm::mat4(1.0f), ctrl.Controlled().ChunkCoords(), aim_world)) {
600                 aim_world = WorldCollision();
601         }
602         if (!world.Intersection(aim, glm::mat4(1.0f), ctrl.Controlled(), aim_entity)) {
603                 aim_entity = EntityCollision();
604         }
605         if (aim_world && aim_entity) {
606                 // got both, pick the closest one
607                 if (aim_world.depth < aim_entity.depth) {
608                         UpdateOutline();
609                         aim_entity = EntityCollision();
610                 } else {
611                         aim_world = WorldCollision();
612                 }
613         } else if (aim_world) {
614                 UpdateOutline();
615         }
616         if (debug) {
617                 UpdateBlockInfo();
618                 UpdateEntityInfo();
619         }
620 }
621
622 void Interface::UpdateOutline() {
623         outl_buf.Clear();
624         aim_world.GetType().FillOutlineModel(outl_buf);
625         outline.Update(outl_buf);
626         outline_transform = aim_world.GetChunk().Transform(world.Player().ChunkCoords());
627         outline_transform *= aim_world.BlockTransform();
628         outline_transform *= glm::scale(glm::vec3(1.005f));
629 }
630
631
632 void Interface::Render(Viewport &viewport) noexcept {
633         if (config.visual_disabled) return;
634
635         if (aim_world) {
636                 PlainColor &outline_prog = viewport.WorldOutlineProgram();
637                 outline_prog.SetM(outline_transform);
638                 outline.Draw();
639         }
640
641         if (debug) {
642                 counter_text.Render(viewport);
643                 position_text.Render(viewport);
644                 orientation_text.Render(viewport);
645                 if (aim_world) {
646                         block_text.Render(viewport);
647                 } else if (aim_entity) {
648                         entity_text.Render(viewport);
649                 }
650         }
651
652         if (msg_timer.Running()) {
653                 messages.Render(viewport);
654         }
655
656         hud.Render(viewport);
657 }
658
659
660 Keymap::Keymap()
661 : codemap{ NONE } {
662
663 }
664
665 void Keymap::Map(SDL_Scancode scancode, Action action) {
666         if (scancode > MAX_SCANCODE) {
667                 throw std::runtime_error("refusing to map scancode: too damn high");
668         }
669         codemap[scancode] = action;
670 }
671
672 Keymap::Action Keymap::Lookup(SDL_Scancode scancode) {
673         if (scancode < NUM_SCANCODES) {
674                 return codemap[scancode];
675         } else {
676                 return NONE;
677         }
678 }
679
680
681 void Keymap::LoadDefault() {
682         Map(SDL_SCANCODE_UP, MOVE_FORWARD);
683         Map(SDL_SCANCODE_W, MOVE_FORWARD);
684         Map(SDL_SCANCODE_DOWN, MOVE_BACKWARD);
685         Map(SDL_SCANCODE_S, MOVE_BACKWARD);
686         Map(SDL_SCANCODE_LEFT, MOVE_LEFT);
687         Map(SDL_SCANCODE_A, MOVE_LEFT);
688         Map(SDL_SCANCODE_RIGHT, MOVE_RIGHT);
689         Map(SDL_SCANCODE_D, MOVE_RIGHT);
690         Map(SDL_SCANCODE_SPACE, MOVE_UP);
691         Map(SDL_SCANCODE_RSHIFT, MOVE_UP);
692         Map(SDL_SCANCODE_LSHIFT, MOVE_DOWN);
693         Map(SDL_SCANCODE_LCTRL, MOVE_DOWN);
694         Map(SDL_SCANCODE_RCTRL, MOVE_DOWN);
695
696         Map(SDL_SCANCODE_Q, BLOCK_FACE);
697         Map(SDL_SCANCODE_E, BLOCK_TURN);
698         Map(SDL_SCANCODE_TAB, BLOCK_NEXT);
699         Map(SDL_SCANCODE_RIGHTBRACKET, BLOCK_NEXT);
700         Map(SDL_SCANCODE_LEFTBRACKET, BLOCK_PREV);
701
702         Map(SDL_SCANCODE_INSERT, BLOCK_PLACE);
703         Map(SDL_SCANCODE_RETURN, BLOCK_PLACE);
704         Map(SDL_SCANCODE_MENU, BLOCK_PICK);
705         Map(SDL_SCANCODE_DELETE, BLOCK_REMOVE);
706         Map(SDL_SCANCODE_BACKSPACE, BLOCK_REMOVE);
707
708         Map(SDL_SCANCODE_N, TOGGLE_COLLISION);
709         Map(SDL_SCANCODE_F1, TOGGLE_VISUAL);
710         Map(SDL_SCANCODE_F3, TOGGLE_DEBUG);
711         Map(SDL_SCANCODE_F4, TOGGLE_AUDIO);
712
713         Map(SDL_SCANCODE_B, PRINT_BLOCK);
714         Map(SDL_SCANCODE_C, PRINT_CHUNK);
715         Map(SDL_SCANCODE_L, PRINT_LIGHT);
716         Map(SDL_SCANCODE_P, PRINT_SELECTION);
717
718         Map(SDL_SCANCODE_ESCAPE, EXIT);
719 }
720
721
722 void Keymap::Load(std::istream &is) {
723         TokenStreamReader in(is);
724         std::string key_name;
725         std::string action_name;
726         SDL_Scancode key;
727         Action action;
728         while (in.HasMore()) {
729                 if (in.Peek().type == Token::STRING) {
730                         in.ReadString(key_name);
731                         key = SDL_GetScancodeFromName(key_name.c_str());
732                 } else {
733                         key = SDL_Scancode(in.GetInt());
734                 }
735                 in.Skip(Token::EQUALS);
736                 in.ReadIdentifier(action_name);
737                 action = StringToAction(action_name);
738                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
739                         in.Skip(Token::SEMICOLON);
740                 }
741                 Map(key, action);
742         }
743 }
744
745 void Keymap::Save(std::ostream &out) {
746         for (unsigned int i = 0; i < NUM_SCANCODES; ++i) {
747                 if (codemap[i] == NONE) continue;
748
749                 const char *str = SDL_GetScancodeName(SDL_Scancode(i));
750                 if (str && *str) {
751                         out << '"';
752                         while (*str) {
753                                 if (*str == '"') {
754                                         out << "\\\"";
755                                 } else {
756                                         out << *str;
757                                 }
758                                 ++str;
759                         }
760                         out << '"';
761                 } else {
762                         out << i;
763                 }
764
765                 out << " = " << ActionToString(codemap[i]) << std::endl;;
766         }
767 }
768
769
770 const char *Keymap::ActionToString(Action action) {
771         switch (action) {
772                 default:
773                 case NONE:
774                         return "none";
775                 case MOVE_FORWARD:
776                         return "move_forward";
777                 case MOVE_BACKWARD:
778                         return "move_backward";
779                 case MOVE_LEFT:
780                         return "move_left";
781                 case MOVE_RIGHT:
782                         return "move_right";
783                 case MOVE_UP:
784                         return "move_up";
785                 case MOVE_DOWN:
786                         return "move_down";
787                 case BLOCK_FACE:
788                         return "block_face";
789                 case BLOCK_TURN:
790                         return "block_turn";
791                 case BLOCK_NEXT:
792                         return "block_next";
793                 case BLOCK_PREV:
794                         return "block_prev";
795                 case BLOCK_PLACE:
796                         return "block_place";
797                 case BLOCK_PICK:
798                         return "block_pick";
799                 case BLOCK_REMOVE:
800                         return "block_remove";
801                 case TOGGLE_COLLISION:
802                         return "toggle_collision";
803                 case TOGGLE_AUDIO:
804                         return "toggle_audio";
805                 case TOGGLE_VISUAL:
806                         return "toggle_visual";
807                 case TOGGLE_DEBUG:
808                         return "toggle_debug";
809                 case PRINT_BLOCK:
810                         return "print_block";
811                 case PRINT_CHUNK:
812                         return "print_chunk";
813                 case PRINT_LIGHT:
814                         return "print_light";
815                 case PRINT_SELECTION:
816                         return "print_selection";
817                 case EXIT:
818                         return "exit";
819         }
820 }
821
822 Keymap::Action Keymap::StringToAction(const std::string &str) {
823         if (str == "move_forward") {
824                 return MOVE_FORWARD;
825         } else if (str == "move_backward") {
826                 return MOVE_BACKWARD;
827         } else if (str == "move_left") {
828                 return MOVE_LEFT;
829         } else if (str == "move_right") {
830                 return MOVE_RIGHT;
831         } else if (str == "move_up") {
832                 return MOVE_UP;
833         } else if (str == "move_down") {
834                 return MOVE_DOWN;
835         } else if (str == "block_face") {
836                 return BLOCK_FACE;
837         } else if (str == "block_turn") {
838                 return BLOCK_TURN;
839         } else if (str == "block_next") {
840                 return BLOCK_NEXT;
841         } else if (str == "block_prev") {
842                 return BLOCK_PREV;
843         } else if (str == "block_place") {
844                 return BLOCK_PLACE;
845         } else if (str == "block_pick") {
846                 return BLOCK_PICK;
847         } else if (str == "block_remove") {
848                 return BLOCK_REMOVE;
849         } else if (str == "toggle_collision") {
850                 return TOGGLE_COLLISION;
851         } else if (str == "toggle_audio") {
852                 return TOGGLE_AUDIO;
853         } else if (str == "toggle_visual") {
854                 return TOGGLE_VISUAL;
855         } else if (str == "toggle_debug") {
856                 return TOGGLE_DEBUG;
857         } else if (str == "print_block") {
858                 return PRINT_BLOCK;
859         } else if (str == "print_chunk") {
860                 return PRINT_CHUNK;
861         } else if (str == "print_light") {
862                 return PRINT_LIGHT;
863         } else if (str == "print_selection") {
864                 return PRINT_SELECTION;
865         } else if (str == "exit") {
866                 return EXIT;
867         } else {
868                 return NONE;
869         }
870 }
871
872 }