]> git.localhorst.tv Git - blank.git/blob - src/ui/ui.cpp
remove default binding of "Return" in keymap
[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, player.GetEntity().ChunkCoords(), aim_world)) {
101                         aim_world = WorldCollision();
102                 }
103                 if (!world.Intersection(aim, 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                         client_ctrl.SetAudio(!config.audio.enabled);
601                         break;
602                 case Keymap::TOGGLE_VIDEO:
603                         client_ctrl.SetVideo(!config.video.world);
604                         break;
605                 case Keymap::TOGGLE_HUD:
606                         client_ctrl.SetHUD(!config.video.hud);
607                         break;
608                 case Keymap::TOGGLE_DEBUG:
609                         client_ctrl.SetDebug(!config.video.debug);
610                         break;
611                 case Keymap::CAMERA_NEXT:
612                         client_ctrl.NextCamera();
613                         break;
614
615                 default:
616                         break;
617         }
618 }
619
620 void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
621         if (!config.input.keyboard) return;
622
623         switch (keymap.Lookup(event)) {
624                 case Keymap::MOVE_FORWARD:
625                         rev.z = 0;
626                         UpdateMovement();
627                         break;
628                 case Keymap::MOVE_BACKWARD:
629                         fwd.z = 0;
630                         UpdateMovement();
631                         break;
632                 case Keymap::MOVE_LEFT:
633                         rev.x = 0;
634                         UpdateMovement();
635                         break;
636                 case Keymap::MOVE_RIGHT:
637                         fwd.x = 0;
638                         UpdateMovement();
639                         break;
640                 case Keymap::MOVE_UP:
641                         fwd.y = 0;
642                         UpdateMovement();
643                         break;
644                 case Keymap::MOVE_DOWN:
645                         rev.y = 0;
646                         UpdateMovement();
647                         break;
648
649                 case Keymap::PRIMARY:
650                         player_ctrl.StopPrimaryAction();
651                         break;
652                 case Keymap::SECONDARY:
653                         player_ctrl.StopSecondaryAction();
654                         break;
655                 case Keymap::TERTIARY:
656                         player_ctrl.StopTertiaryAction();
657                         break;
658
659                 default:
660                         break;
661         }
662 }
663
664 void Interface::Handle(const SDL_MouseMotionEvent &event) {
665         if (locked || !config.input.mouse) return;
666         player_ctrl.TurnHead(
667                 event.yrel * config.input.pitch_sensitivity,
668                 event.xrel * config.input.yaw_sensitivity);
669 }
670
671 void Interface::HandlePress(const SDL_MouseButtonEvent &event) {
672         if (!config.input.mouse) return;
673
674         switch (event.button) {
675                 case SDL_BUTTON_LEFT:
676                         player_ctrl.StartPrimaryAction();
677                         break;
678                 case SDL_BUTTON_RIGHT:
679                         player_ctrl.StartSecondaryAction();
680                         break;
681                 case SDL_BUTTON_MIDDLE:
682                         player_ctrl.StartTertiaryAction();
683                         break;
684         }
685 }
686
687 void Interface::HandleRelease(const SDL_MouseButtonEvent &event) {
688         if (!config.input.mouse) return;
689
690         switch (event.button) {
691                 case SDL_BUTTON_LEFT:
692                         player_ctrl.StopPrimaryAction();
693                         break;
694                 case SDL_BUTTON_RIGHT:
695                         player_ctrl.StopSecondaryAction();
696                         break;
697                 case SDL_BUTTON_MIDDLE:
698                         player_ctrl.StopTertiaryAction();
699                         break;
700         }
701 }
702
703
704 void Interface::Handle(const SDL_MouseWheelEvent &event) {
705         if (!config.input.mouse) return;
706
707         if (event.y < 0) {
708                 InvRel(1);
709         } else if (event.y > 0) {
710                 InvRel(-1);
711         }
712 }
713
714 void Interface::UpdateMovement() {
715         player_ctrl.SetMovement(glm::vec3(fwd - rev));
716 }
717
718 void Interface::InvAbs(int s) {
719         int slot = s % num_slots;
720         while (slot < 0) {
721                 slot += num_slots;
722         }
723         player_ctrl.SelectInventory(slot);
724 }
725
726 void Interface::InvRel(int delta) {
727         InvAbs(player_ctrl.GetPlayer().GetInventorySlot() + delta);
728 }
729
730
731 Keymap::Keymap()
732 : codemap{ NONE } {
733
734 }
735
736 void Keymap::Map(SDL_Scancode scancode, Action action) {
737         if (scancode > MAX_SCANCODE) {
738                 throw std::runtime_error("refusing to map scancode: too damn high");
739         }
740         codemap[scancode] = action;
741 }
742
743 Keymap::Action Keymap::Lookup(SDL_Scancode scancode) const {
744         if (scancode < NUM_SCANCODES) {
745                 return codemap[scancode];
746         } else {
747                 return NONE;
748         }
749 }
750
751
752 void Keymap::LoadDefault() {
753         Map(SDL_SCANCODE_UP, MOVE_FORWARD);
754         Map(SDL_SCANCODE_W, MOVE_FORWARD);
755         Map(SDL_SCANCODE_DOWN, MOVE_BACKWARD);
756         Map(SDL_SCANCODE_S, MOVE_BACKWARD);
757         Map(SDL_SCANCODE_LEFT, MOVE_LEFT);
758         Map(SDL_SCANCODE_A, MOVE_LEFT);
759         Map(SDL_SCANCODE_RIGHT, MOVE_RIGHT);
760         Map(SDL_SCANCODE_D, MOVE_RIGHT);
761         Map(SDL_SCANCODE_SPACE, MOVE_UP);
762         Map(SDL_SCANCODE_RSHIFT, MOVE_UP);
763         Map(SDL_SCANCODE_LSHIFT, MOVE_DOWN);
764         Map(SDL_SCANCODE_LCTRL, MOVE_DOWN);
765         Map(SDL_SCANCODE_RCTRL, MOVE_DOWN);
766
767         Map(SDL_SCANCODE_TAB, INV_NEXT);
768         Map(SDL_SCANCODE_RIGHTBRACKET, INV_NEXT);
769         Map(SDL_SCANCODE_LEFTBRACKET, INV_PREVIOUS);
770         Map(SDL_SCANCODE_1, INV_1);
771         Map(SDL_SCANCODE_2, INV_2);
772         Map(SDL_SCANCODE_3, INV_3);
773         Map(SDL_SCANCODE_4, INV_4);
774         Map(SDL_SCANCODE_5, INV_5);
775         Map(SDL_SCANCODE_6, INV_6);
776         Map(SDL_SCANCODE_7, INV_7);
777         Map(SDL_SCANCODE_8, INV_8);
778         Map(SDL_SCANCODE_9, INV_9);
779         Map(SDL_SCANCODE_0, INV_10);
780
781         Map(SDL_SCANCODE_INSERT, SECONDARY);
782         Map(SDL_SCANCODE_MENU, TERTIARY);
783         Map(SDL_SCANCODE_DELETE, PRIMARY);
784         Map(SDL_SCANCODE_BACKSPACE, PRIMARY);
785
786         Map(SDL_SCANCODE_F1, TOGGLE_HUD);
787         Map(SDL_SCANCODE_F2, TOGGLE_VIDEO);
788         Map(SDL_SCANCODE_F3, TOGGLE_DEBUG);
789         Map(SDL_SCANCODE_F4, TOGGLE_AUDIO);
790         Map(SDL_SCANCODE_F5, CAMERA_NEXT);
791
792         Map(SDL_SCANCODE_ESCAPE, EXIT);
793 }
794
795
796 void Keymap::Load(std::istream &is) {
797         TokenStreamReader in(is);
798         std::string key_name;
799         std::string action_name;
800         SDL_Scancode key;
801         Action action;
802         while (in.HasMore()) {
803                 if (in.Peek().type == Token::STRING) {
804                         in.ReadString(key_name);
805                         key = SDL_GetScancodeFromName(key_name.c_str());
806                 } else {
807                         key = SDL_Scancode(in.GetInt());
808                 }
809                 in.Skip(Token::EQUALS);
810                 in.ReadIdentifier(action_name);
811                 action = StringToAction(action_name);
812                 if (in.HasMore() && in.Peek().type == Token::SEMICOLON) {
813                         in.Skip(Token::SEMICOLON);
814                 }
815                 Map(key, action);
816         }
817 }
818
819 void Keymap::Save(std::ostream &out) {
820         for (unsigned int i = 0; i < NUM_SCANCODES; ++i) {
821                 if (codemap[i] == NONE) continue;
822
823                 const char *str = SDL_GetScancodeName(SDL_Scancode(i));
824                 if (str && *str) {
825                         out << '"';
826                         while (*str) {
827                                 if (*str == '"') {
828                                         out << "\\\"";
829                                 } else {
830                                         out << *str;
831                                 }
832                                 ++str;
833                         }
834                         out << '"';
835                 } else {
836                         out << i;
837                 }
838
839                 out << " = " << ActionToString(codemap[i]) << std::endl;;
840         }
841 }
842
843
844 namespace {
845
846 std::map<std::string, Keymap::Action> action_map = {
847         { "none", Keymap::NONE },
848         { "move_forward", Keymap::MOVE_FORWARD },
849         { "move_backward", Keymap::MOVE_BACKWARD },
850         { "move_left", Keymap::MOVE_LEFT },
851         { "move_right", Keymap::MOVE_RIGHT },
852         { "move_up", Keymap::MOVE_UP },
853         { "move_down", Keymap::MOVE_DOWN },
854
855         { "primary", Keymap::PRIMARY },
856         { "secondary", Keymap::SECONDARY },
857         { "tertiary", Keymap::TERTIARY },
858
859         { "inventory_next", Keymap::INV_NEXT },
860         { "inventory_prev", Keymap::INV_PREVIOUS },
861         { "inventory_1", Keymap::INV_1 },
862         { "inventory_2", Keymap::INV_2 },
863         { "inventory_3", Keymap::INV_3 },
864         { "inventory_4", Keymap::INV_4 },
865         { "inventory_5", Keymap::INV_5 },
866         { "inventory_6", Keymap::INV_6 },
867         { "inventory_7", Keymap::INV_7 },
868         { "inventory_8", Keymap::INV_8 },
869         { "inventory_9", Keymap::INV_9 },
870         { "inventory_10", Keymap::INV_10 },
871
872         { "toggle_audio", Keymap::TOGGLE_AUDIO },
873         { "toggle_video", Keymap::TOGGLE_VIDEO },
874         { "toggle_hud", Keymap::TOGGLE_HUD },
875         { "toggle_debug", Keymap::TOGGLE_DEBUG },
876         { "camera_next", Keymap::CAMERA_NEXT },
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 }