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