]> git.localhorst.tv Git - blank.git/blob - src/ui/ui.cpp
use "forces" for entity control and RK4 integrator
[blank.git] / src / ui / ui.cpp
1 #include "ClientController.hpp"
2 #include "DirectInput.hpp"
3 #include "HUD.hpp"
4 #include "InteractiveManipulator.hpp"
5 #include "Interface.hpp"
6 #include "Keymap.hpp"
7 #include "PlayerController.hpp"
8
9 #include "../app/Assets.hpp"
10 #include "../app/Config.hpp"
11 #include "../app/Environment.hpp"
12 #include "../app/FrameCounter.hpp"
13 #include "../app/init.hpp"
14 #include "../audio/Audio.hpp"
15 #include "../audio/SoundBank.hpp"
16 #include "../graphics/Font.hpp"
17 #include "../graphics/Viewport.hpp"
18 #include "../io/TokenStreamReader.hpp"
19 #include "../model/bounds.hpp"
20 #include "../world/BlockLookup.hpp"
21 #include "../world/World.hpp"
22 #include "../world/WorldManipulator.hpp"
23
24 #include <algorithm>
25 #include <cmath>
26 #include <iostream>
27 #include <map>
28 #include <sstream>
29 #include <glm/gtc/matrix_transform.hpp>
30 #include <glm/gtx/rotate_vector.hpp>
31 #include <glm/gtx/io.hpp>
32
33
34 namespace blank {
35
36 PlayerController::PlayerController(World &world, Player &player)
37 : world(world)
38 , player(player)
39 , move_dir(0.0f)
40 , pitch(0.0f)
41 , yaw(0.0f)
42 , dirty(true)
43 , aim_world()
44 , aim_entity() {
45
46 }
47
48 void PlayerController::SetMovement(const glm::vec3 &m) noexcept {
49         if (dot(m, m) > 1.0f) {
50                 move_dir = normalize(m);
51         } else {
52                 move_dir = m;
53         }
54         Invalidate();
55 }
56
57 void PlayerController::TurnHead(float dp, float dy) noexcept {
58         pitch += dp;
59         if (pitch > PI / 2) {
60                 pitch = PI / 2;
61         } else if (pitch < -PI / 2) {
62                 pitch = -PI / 2;
63         }
64         yaw += dy;
65         if (yaw > PI) {
66                 yaw -= PI * 2;
67         } else if (yaw < -PI) {
68                 yaw += PI * 2;
69         }
70         Invalidate();
71 }
72
73 void PlayerController::SelectInventory(int i) noexcept {
74         player.SetInventorySlot(i);
75 }
76
77 int PlayerController::InventorySlot() const noexcept {
78         return player.GetInventorySlot();
79 }
80
81 void PlayerController::Invalidate() noexcept {
82         dirty = true;
83 }
84
85 void PlayerController::UpdatePlayer() noexcept {
86         constexpr float max_vel = 0.005f;
87         if (dirty) {
88                 player.GetEntity().Orientation(glm::quat(glm::vec3(pitch, yaw, 0.0f)));
89                 player.GetEntity().TargetVelocity(glm::rotateY(move_dir * max_vel, yaw));
90
91                 Ray aim = player.Aim();
92                 if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity().ChunkCoords(), aim_world)) {
93                         aim_world = WorldCollision();
94                 }
95                 if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity(), aim_entity)) {
96                         aim_entity = EntityCollision();
97                 }
98                 if (aim_world && aim_entity) {
99                         // got both, pick the closest one
100                         if (aim_world.depth < aim_entity.depth) {
101                                 aim_entity = EntityCollision();
102                         } else {
103                                 aim_world = WorldCollision();
104                         }
105                 }
106                 dirty = false;
107         }
108 }
109
110
111 DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip)
112 : PlayerController(world, player)
113 , manip(manip)
114 , place_timer(256)
115 , remove_timer(256) {
116
117 }
118
119 void DirectInput::Update(int dt) {
120         Invalidate(); // world has changed in the meantime
121         UpdatePlayer();
122
123         remove_timer.Update(dt);
124         if (remove_timer.Hit()) {
125                 RemoveBlock();
126         }
127
128         place_timer.Update(dt);
129         if (place_timer.Hit()) {
130                 PlaceBlock();
131         }
132 }
133
134 void DirectInput::StartPrimaryAction() {
135         if (!remove_timer.Running()) {
136                 RemoveBlock();
137                 remove_timer.Start();
138         }
139 }
140
141 void DirectInput::StopPrimaryAction() {
142         remove_timer.Stop();
143 }
144
145 void DirectInput::StartSecondaryAction() {
146         if (!place_timer.Running()) {
147                 PlaceBlock();
148                 place_timer.Start();
149         }
150 }
151
152 void DirectInput::StopSecondaryAction() {
153         place_timer.Stop();
154 }
155
156 void DirectInput::StartTertiaryAction() {
157         PickBlock();
158 }
159
160 void DirectInput::StopTertiaryAction() {
161         // nothing
162 }
163
164 void DirectInput::PickBlock() {
165         UpdatePlayer();
166         if (!BlockFocus()) return;
167         SelectInventory(BlockFocus().GetBlock().type - 1);
168 }
169
170 void DirectInput::PlaceBlock() {
171         UpdatePlayer();
172         if (!BlockFocus()) return;
173
174         BlockLookup next_block(BlockFocus().chunk, BlockFocus().BlockPos(), Block::NormalFace(BlockFocus().normal));
175         if (!next_block) {
176                 return;
177         }
178         manip.SetBlock(next_block.GetChunk(), next_block.GetBlockIndex(), Block(InventorySlot() + 1));
179         Invalidate();
180 }
181
182 void DirectInput::RemoveBlock() {
183         UpdatePlayer();
184         if (!BlockFocus()) return;
185         manip.SetBlock(BlockFocus().GetChunk(), BlockFocus().block, Block(0));
186         Invalidate();
187 }
188
189
190 HUD::HUD(Environment &env, Config &config, const Player &player)
191 : env(env)
192 , config(config)
193 , player(player)
194 // block focus
195 , outline()
196 , outline_transform(1.0f)
197 , outline_visible(false)
198 // "inventory"
199 , block()
200 , block_buf()
201 , block_transform(1.0f)
202 , block_label()
203 , block_visible(false)
204 // debug overlay
205 , counter_text()
206 , position_text()
207 , orientation_text()
208 , block_text()
209 , show_block(false)
210 , show_entity(false)
211 // message box
212 , messages(env.assets.small_ui_font)
213 , msg_timer(5000)
214 // crosshair
215 , crosshair() {
216         // "inventory"
217         block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f));
218         block_transform = glm::scale(block_transform, glm::vec3(50.0f));
219         block_transform = glm::rotate(block_transform, 3.5f, glm::vec3(1.0f, 0.0f, 0.0f));
220         block_transform = glm::rotate(block_transform, 0.35f, glm::vec3(0.0f, 1.0f, 0.0f));
221         block_label.Position(
222                 glm::vec3(50.0f, 85.0f, 0.0f),
223                 Gravity::NORTH_WEST,
224                 Gravity::NORTH
225         );
226         block_label.Foreground(glm::vec4(1.0f));
227         block_label.Background(glm::vec4(0.5f));
228
229         // debug overlay
230         counter_text.Position(glm::vec3(-25.0f, 25.0f, 0.0f), Gravity::NORTH_EAST);
231         counter_text.Foreground(glm::vec4(1.0f));
232         counter_text.Background(glm::vec4(0.5f));
233         position_text.Position(glm::vec3(-25.0f, 25.0f + env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
234         position_text.Foreground(glm::vec4(1.0f));
235         position_text.Background(glm::vec4(0.5f));
236         orientation_text.Position(glm::vec3(-25.0f, 25.0f + 2 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
237         orientation_text.Foreground(glm::vec4(1.0f));
238         orientation_text.Background(glm::vec4(0.5f));
239         block_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
240         block_text.Foreground(glm::vec4(1.0f));
241         block_text.Background(glm::vec4(0.5f));
242         block_text.Set(env.assets.small_ui_font, "Block: none");
243         entity_text.Position(glm::vec3(-25.0f, 25.0f + 4 * env.assets.small_ui_font.LineSkip(), 0.0f), Gravity::NORTH_EAST);
244         entity_text.Foreground(glm::vec4(1.0f));
245         entity_text.Background(glm::vec4(0.5f));
246         entity_text.Set(env.assets.small_ui_font, "Entity: none");
247
248         // message box
249         messages.Position(glm::vec3(25.0f, -25.0f, 0.0f), Gravity::SOUTH_WEST);
250         messages.Foreground(glm::vec4(1.0f));
251         messages.Background(glm::vec4(0.5f));
252
253         // crosshair
254         OutlineMesh::Buffer buf;
255         buf.vertices = std::vector<glm::vec3>({
256                 { -10.0f,   0.0f, 0.0f }, { 10.0f,  0.0f, 0.0f },
257                 {   0.0f, -10.0f, 0.0f }, {  0.0f, 10.0f, 0.0f },
258         });
259         buf.indices = std::vector<OutlineMesh::Index>({
260                 0, 1, 2, 3
261         });
262         buf.colors.resize(4, { 10.0f, 10.0f, 10.0f });
263         crosshair.Update(buf);
264 }
265
266 namespace {
267
268 OutlineMesh::Buffer outl_buf;
269
270 }
271
272 void HUD::FocusBlock(const Chunk &chunk, int index) {
273         const Block &block = chunk.BlockAt(index);
274         const BlockType &type = chunk.Type(index);
275         outl_buf.Clear();
276         type.FillOutlineMesh(outl_buf);
277         outline.Update(outl_buf);
278         outline_transform = chunk.Transform(player.GetEntity().ChunkCoords());
279         outline_transform *= chunk.ToTransform(Chunk::ToPos(index), index);
280         outline_transform *= glm::scale(glm::vec3(1.005f));
281         outline_visible = true;
282         {
283                 std::stringstream s;
284                 s << "Block: "
285                         << type.label
286                         << ", face: " << block.GetFace()
287                         << ", turn: " << block.GetTurn();
288                 block_text.Set(env.assets.small_ui_font, s.str());
289         }
290         show_block = true;
291         show_entity = false;
292 }
293
294 void HUD::FocusEntity(const Entity &entity) {
295         {
296                 std::stringstream s;
297                 s << "Entity: " << entity.Name();
298                 entity_text.Set(env.assets.small_ui_font, s.str());
299         }
300         show_block = false;
301         show_entity = true;
302 }
303
304 void HUD::FocusNone() {
305         outline_visible = false;
306         show_block = false;
307         show_entity = false;
308 }
309
310 void HUD::DisplayNone() {
311         block_visible = false;
312 }
313
314 void HUD::Display(const BlockType &type) {
315         block_buf.Clear();
316         type.FillEntityMesh(block_buf);
317         block.Update(block_buf);
318
319         block_label.Set(env.assets.small_ui_font, type.label);
320
321         block_visible = type.visible;
322 }
323
324
325 void HUD::UpdateDebug() {
326         UpdateCounter();
327         UpdatePosition();
328         UpdateOrientation();
329 }
330
331 void HUD::UpdateCounter() {
332         std::stringstream s;
333         s << std::setprecision(3) <<
334                 "avg: " << env.counter.Average().running << "ms, "
335                 "peak: " << env.counter.Peak().running << "ms";
336         std::string text = s.str();
337         counter_text.Set(env.assets.small_ui_font, text);
338 }
339
340 void HUD::UpdatePosition() {
341         std::stringstream s;
342         s << std::setprecision(3) << "pos: " << player.GetEntity().AbsolutePosition();
343         position_text.Set(env.assets.small_ui_font, s.str());
344 }
345
346 void HUD::UpdateOrientation() {
347         //std::stringstream s;
348         //s << std::setprecision(3) << "pitch: " << rad2deg(ctrl.Pitch())
349         //      << ", yaw: " << rad2deg(ctrl.Yaw());
350         //orientation_text.Set(env.assets.small_ui_font, s.str());
351 }
352
353 void HUD::PostMessage(const char *msg) {
354         messages.PushLine(msg);
355         msg_timer.Reset();
356         msg_timer.Start();
357         std::cout << msg << std::endl;
358 }
359
360
361 void HUD::Update(int dt) {
362         msg_timer.Update(dt);
363         if (msg_timer.HitOnce()) {
364                 msg_timer.Stop();
365         }
366
367         if (config.video.debug) {
368                 if (env.counter.Changed()) {
369                         UpdateCounter();
370                 }
371                 UpdatePosition();
372                 UpdateOrientation();
373         }
374 }
375
376 void HUD::Render(Viewport &viewport) noexcept {
377         // block focus
378         if (outline_visible && config.video.world) {
379                 PlainColor &outline_prog = viewport.WorldOutlineProgram();
380                 outline_prog.SetM(outline_transform);
381                 outline.Draw();
382         }
383
384         // clear depth buffer so everything renders above the world
385         viewport.ClearDepth();
386
387         if (config.video.hud) {
388                 // "inventory"
389                 if (block_visible) {
390                         DirectionalLighting &world_prog = viewport.HUDProgram();
391                         world_prog.SetLightDirection({ 1.0f, 3.0f, 5.0f });
392                         // disable distance fog
393                         world_prog.SetFogDensity(0.0f);
394
395                         viewport.DisableBlending();
396                         world_prog.SetM(block_transform);
397                         block.Draw();
398                         block_label.Render(viewport);
399                 }
400
401                 // message box
402                 if (msg_timer.Running()) {
403                         messages.Render(viewport);
404                 }
405
406                 // crosshair
407                 PlainColor &outline_prog = viewport.HUDOutlineProgram();
408                 viewport.EnableInvertBlending();
409                 viewport.SetCursor(glm::vec3(0.0f), Gravity::CENTER);
410                 outline_prog.SetM(viewport.Cursor());
411                 crosshair.Draw();
412         }
413
414         // debug overlay
415         if (config.video.debug) {
416                 counter_text.Render(viewport);
417                 position_text.Render(viewport);
418                 orientation_text.Render(viewport);
419                 if (show_block) {
420                         block_text.Render(viewport);
421                 } else if (show_entity) {
422                         entity_text.Render(viewport);
423                 }
424         }
425 }
426
427
428 InteractiveManipulator::InteractiveManipulator(Audio &audio, const SoundBank &sounds, Entity &player)
429 : player(player)
430 , audio(audio)
431 , sounds(sounds) {
432
433 }
434
435 void InteractiveManipulator::SetBlock(Chunk &chunk, int index, const Block &block) {
436         const BlockType &old_type = chunk.Type(index);
437         chunk.SetBlock(index, block);
438         const BlockType &new_type = chunk.Type(index);
439         glm::vec3 coords = chunk.ToSceneCoords(player.ChunkCoords(), Chunk::ToCoords(index));
440         if (new_type.id == 0) {
441                 if (old_type.remove_sound >= 0) {
442                         audio.Play(sounds[old_type.remove_sound], coords);
443                 }
444         } else {
445                 if (new_type.place_sound >= 0) {
446                         audio.Play(sounds[new_type.place_sound], coords);
447                 }
448         }
449 }
450
451
452 Interface::Interface(
453         Config &config,
454         const Keymap &keymap,
455         PlayerController &pc,
456         ClientController &cc)
457 : config(config)
458 , keymap(keymap)
459 , player_ctrl(pc)
460 , client_ctrl(cc)
461 , fwd(0)
462 , rev(0)
463 , slot(0)
464 , num_slots(10) {
465
466 }
467
468
469 void Interface::HandlePress(const SDL_KeyboardEvent &event) {
470         if (!config.input.keyboard) return;
471
472         Keymap::Action action = keymap.Lookup(event);
473         switch (action) {
474                 case Keymap::MOVE_FORWARD:
475                         rev.z = 1;
476                         UpdateMovement();
477                         break;
478                 case Keymap::MOVE_BACKWARD:
479                         fwd.z = 1;
480                         UpdateMovement();
481                         break;
482                 case Keymap::MOVE_LEFT:
483                         rev.x = 1;
484                         UpdateMovement();
485                         break;
486                 case Keymap::MOVE_RIGHT:
487                         fwd.x = 1;
488                         UpdateMovement();
489                         break;
490                 case Keymap::MOVE_UP:
491                         fwd.y = 1;
492                         UpdateMovement();
493                         break;
494                 case Keymap::MOVE_DOWN:
495                         rev.y = 1;
496                         UpdateMovement();
497                         break;
498
499                 case Keymap::PRIMARY:
500                         player_ctrl.StartPrimaryAction();
501                         break;
502                 case Keymap::SECONDARY:
503                         player_ctrl.StartSecondaryAction();
504                         break;
505                 case Keymap::TERTIARY:
506                         player_ctrl.StartTertiaryAction();
507                         break;
508
509                 case Keymap::INV_NEXT:
510                         InvRel(1);
511                         break;
512                 case Keymap::INV_PREVIOUS:
513                         InvRel(-1);
514                         break;
515                 case Keymap::INV_1:
516                 case Keymap::INV_2:
517                 case Keymap::INV_3:
518                 case Keymap::INV_4:
519                 case Keymap::INV_5:
520                 case Keymap::INV_6:
521                 case Keymap::INV_7:
522                 case Keymap::INV_8:
523                 case Keymap::INV_9:
524                 case Keymap::INV_10:
525                         InvAbs(action - Keymap::INV_1);
526                         break;
527
528                 case Keymap::EXIT:
529                         client_ctrl.Exit();
530                         break;
531
532                 case Keymap::TOGGLE_AUDIO:
533                         config.audio.enabled = !config.audio.enabled;
534                         client_ctrl.SetAudio(config.audio.enabled);
535                         break;
536                 case Keymap::TOGGLE_VIDEO:
537                         config.video.world = !config.video.world;
538                         client_ctrl.SetVideo(config.video.world);
539                         break;
540                 case Keymap::TOGGLE_HUD:
541                         config.video.hud = !config.video.hud;
542                         client_ctrl.SetHUD(config.video.hud);
543                         break;
544                 case Keymap::TOGGLE_DEBUG:
545                         config.video.debug = !config.video.debug;
546                         client_ctrl.SetDebug(config.video.debug);
547                         break;
548
549                 default:
550                         break;
551         }
552 }
553
554 void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
555         if (!config.input.keyboard) return;
556
557         switch (keymap.Lookup(event)) {
558                 case Keymap::MOVE_FORWARD:
559                         rev.z = 0;
560                         UpdateMovement();
561                         break;
562                 case Keymap::MOVE_BACKWARD:
563                         fwd.z = 0;
564                         UpdateMovement();
565                         break;
566                 case Keymap::MOVE_LEFT:
567                         rev.x = 0;
568                         UpdateMovement();
569                         break;
570                 case Keymap::MOVE_RIGHT:
571                         fwd.x = 0;
572                         UpdateMovement();
573                         break;
574                 case Keymap::MOVE_UP:
575                         fwd.y = 0;
576                         UpdateMovement();
577                         break;
578                 case Keymap::MOVE_DOWN:
579                         rev.y = 0;
580                         UpdateMovement();
581                         break;
582
583                 case Keymap::PRIMARY:
584                         player_ctrl.StopPrimaryAction();
585                         break;
586                 case Keymap::SECONDARY:
587                         player_ctrl.StopSecondaryAction();
588                         break;
589                 case Keymap::TERTIARY:
590                         player_ctrl.StopTertiaryAction();
591                         break;
592
593                 default:
594                         break;
595         }
596 }
597
598 void Interface::Handle(const SDL_MouseMotionEvent &event) {
599         if (!config.input.mouse) return;
600         player_ctrl.TurnHead(
601                 event.yrel * config.input.pitch_sensitivity,
602                 event.xrel * config.input.yaw_sensitivity);
603 }
604
605 void Interface::HandlePress(const SDL_MouseButtonEvent &event) {
606         if (!config.input.mouse) return;
607
608         switch (event.button) {
609                 case SDL_BUTTON_LEFT:
610                         player_ctrl.StartPrimaryAction();
611                         break;
612                 case SDL_BUTTON_RIGHT:
613                         player_ctrl.StartSecondaryAction();
614                         break;
615                 case SDL_BUTTON_MIDDLE:
616                         player_ctrl.StartTertiaryAction();
617                         break;
618         }
619 }
620
621 void Interface::HandleRelease(const SDL_MouseButtonEvent &event) {
622         if (!config.input.mouse) return;
623
624         switch (event.button) {
625                 case SDL_BUTTON_LEFT:
626                         player_ctrl.StopPrimaryAction();
627                         break;
628                 case SDL_BUTTON_RIGHT:
629                         player_ctrl.StopSecondaryAction();
630                         break;
631                 case SDL_BUTTON_MIDDLE:
632                         player_ctrl.StopTertiaryAction();
633                         break;
634         }
635 }
636
637
638 void Interface::Handle(const SDL_MouseWheelEvent &event) {
639         if (!config.input.mouse) return;
640
641         if (event.y < 0) {
642                 InvRel(1);
643         } else if (event.y > 0) {
644                 InvRel(-1);
645         }
646 }
647
648 void Interface::UpdateMovement() {
649         player_ctrl.SetMovement(glm::vec3(fwd - rev));
650 }
651
652 void Interface::InvAbs(int s) {
653         slot = s % num_slots;
654         while (slot < 0) {
655                 slot += num_slots;
656         }
657         player_ctrl.SelectInventory(slot);
658 }
659
660 void Interface::InvRel(int delta) {
661         InvAbs(slot + delta);
662 }
663
664
665 Keymap::Keymap()
666 : codemap{ NONE } {
667
668 }
669
670 void Keymap::Map(SDL_Scancode scancode, Action action) {
671         if (scancode > MAX_SCANCODE) {
672                 throw std::runtime_error("refusing to map scancode: too damn high");
673         }
674         codemap[scancode] = action;
675 }
676
677 Keymap::Action Keymap::Lookup(SDL_Scancode scancode) const {
678         if (scancode < NUM_SCANCODES) {
679                 return codemap[scancode];
680         } else {
681                 return NONE;
682         }
683 }
684
685
686 void Keymap::LoadDefault() {
687         Map(SDL_SCANCODE_UP, MOVE_FORWARD);
688         Map(SDL_SCANCODE_W, MOVE_FORWARD);
689         Map(SDL_SCANCODE_DOWN, MOVE_BACKWARD);
690         Map(SDL_SCANCODE_S, MOVE_BACKWARD);
691         Map(SDL_SCANCODE_LEFT, MOVE_LEFT);
692         Map(SDL_SCANCODE_A, MOVE_LEFT);
693         Map(SDL_SCANCODE_RIGHT, MOVE_RIGHT);
694         Map(SDL_SCANCODE_D, MOVE_RIGHT);
695         Map(SDL_SCANCODE_SPACE, MOVE_UP);
696         Map(SDL_SCANCODE_RSHIFT, MOVE_UP);
697         Map(SDL_SCANCODE_LSHIFT, MOVE_DOWN);
698         Map(SDL_SCANCODE_LCTRL, MOVE_DOWN);
699         Map(SDL_SCANCODE_RCTRL, MOVE_DOWN);
700
701         Map(SDL_SCANCODE_TAB, INV_NEXT);
702         Map(SDL_SCANCODE_RIGHTBRACKET, INV_NEXT);
703         Map(SDL_SCANCODE_LEFTBRACKET, INV_PREVIOUS);
704         Map(SDL_SCANCODE_1, INV_1);
705         Map(SDL_SCANCODE_2, INV_2);
706         Map(SDL_SCANCODE_3, INV_3);
707         Map(SDL_SCANCODE_4, INV_4);
708         Map(SDL_SCANCODE_5, INV_5);
709         Map(SDL_SCANCODE_6, INV_6);
710         Map(SDL_SCANCODE_7, INV_7);
711         Map(SDL_SCANCODE_8, INV_8);
712         Map(SDL_SCANCODE_9, INV_9);
713         Map(SDL_SCANCODE_0, INV_10);
714
715         Map(SDL_SCANCODE_INSERT, SECONDARY);
716         Map(SDL_SCANCODE_RETURN, SECONDARY);
717         Map(SDL_SCANCODE_MENU, TERTIARY);
718         Map(SDL_SCANCODE_DELETE, PRIMARY);
719         Map(SDL_SCANCODE_BACKSPACE, PRIMARY);
720
721         Map(SDL_SCANCODE_F1, TOGGLE_HUD);
722         Map(SDL_SCANCODE_F2, TOGGLE_VIDEO);
723         Map(SDL_SCANCODE_F3, TOGGLE_DEBUG);
724         Map(SDL_SCANCODE_F4, TOGGLE_AUDIO);
725
726         Map(SDL_SCANCODE_ESCAPE, EXIT);
727 }
728
729
730 void Keymap::Load(std::istream &is) {
731         TokenStreamReader in(is);
732         std::string key_name;
733         std::string action_name;
734         SDL_Scancode key;
735         Action action;
736         while (in.HasMore()) {
737                 if (in.Peek().type == Token::STRING) {
738                         in.ReadString(key_name);
739                         key = SDL_GetScancodeFromName(key_name.c_str());
740                 } else {
741                         key = SDL_Scancode(in.GetInt());
742                 }
743                 in.Skip(Token::EQUALS);
744                 in.ReadIdentifier(action_name);
745                 action = StringToAction(action_name);
746                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
747                         in.Skip(Token::SEMICOLON);
748                 }
749                 Map(key, action);
750         }
751 }
752
753 void Keymap::Save(std::ostream &out) {
754         for (unsigned int i = 0; i < NUM_SCANCODES; ++i) {
755                 if (codemap[i] == NONE) continue;
756
757                 const char *str = SDL_GetScancodeName(SDL_Scancode(i));
758                 if (str && *str) {
759                         out << '"';
760                         while (*str) {
761                                 if (*str == '"') {
762                                         out << "\\\"";
763                                 } else {
764                                         out << *str;
765                                 }
766                                 ++str;
767                         }
768                         out << '"';
769                 } else {
770                         out << i;
771                 }
772
773                 out << " = " << ActionToString(codemap[i]) << std::endl;;
774         }
775 }
776
777
778 namespace {
779
780 std::map<std::string, Keymap::Action> action_map = {
781         { "none", Keymap::NONE },
782         { "move_forward", Keymap::MOVE_FORWARD },
783         { "move_backward", Keymap::MOVE_BACKWARD },
784         { "move_left", Keymap::MOVE_LEFT },
785         { "move_right", Keymap::MOVE_RIGHT },
786         { "move_up", Keymap::MOVE_UP },
787         { "move_down", Keymap::MOVE_DOWN },
788
789         { "primary", Keymap::PRIMARY },
790         { "secondary", Keymap::SECONDARY },
791         { "tertiary", Keymap::TERTIARY },
792
793         { "inventory_next", Keymap::INV_NEXT },
794         { "inventory_prev", Keymap::INV_PREVIOUS },
795         { "inventory_1", Keymap::INV_1 },
796         { "inventory_2", Keymap::INV_2 },
797         { "inventory_3", Keymap::INV_3 },
798         { "inventory_4", Keymap::INV_4 },
799         { "inventory_5", Keymap::INV_5 },
800         { "inventory_6", Keymap::INV_6 },
801         { "inventory_7", Keymap::INV_7 },
802         { "inventory_8", Keymap::INV_8 },
803         { "inventory_9", Keymap::INV_9 },
804         { "inventory_10", Keymap::INV_10 },
805
806         { "toggle_audio", Keymap::TOGGLE_AUDIO },
807         { "toggle_video", Keymap::TOGGLE_VIDEO },
808         { "toggle_hud", Keymap::TOGGLE_HUD },
809         { "toggle_debug", Keymap::TOGGLE_DEBUG },
810
811         { "exit", Keymap::EXIT },
812 };
813
814 }
815
816 const char *Keymap::ActionToString(Action action) {
817         for (const auto &entry : action_map) {
818                 if (action == entry.second) {
819                         return entry.first.c_str();
820                 }
821         }
822         return "none";
823 }
824
825 Keymap::Action Keymap::StringToAction(const std::string &str) {
826         auto entry = action_map.find(str);
827         if (entry != action_map.end()) {
828                 return entry->second;
829         } else {
830                 std::cerr << "unknown action \"" << str << '"' << std::endl;
831                 return NONE;
832         }
833 }
834
835 }