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