]> git.localhorst.tv Git - blank.git/blob - src/client/client.cpp
transmit player input from client to server
[blank.git] / src / client / client.cpp
1 #include "ChunkRequester.hpp"
2 #include "InitialState.hpp"
3 #include "InteractiveState.hpp"
4 #include "MasterState.hpp"
5
6 #include "../app/Environment.hpp"
7 #include "../app/init.hpp"
8 #include "../app/TextureIndex.hpp"
9 #include "../model/CompositeModel.hpp"
10 #include "../io/WorldSave.hpp"
11 #include "../world/ChunkStore.hpp"
12
13 #include <iostream>
14 #include <glm/gtx/io.hpp>
15
16 using namespace std;
17
18
19 namespace blank {
20 namespace client {
21
22 ChunkRequester::ChunkRequester(
23         ChunkStore &store,
24         const WorldSave &save
25 ) noexcept
26 : store(store)
27 , save(save) {
28
29 }
30
31 void ChunkRequester::Update(int dt) {
32         // check if there's chunks waiting to be loaded
33         LoadN(10);
34
35         // store a few chunks as well
36         constexpr int max_save = 10;
37         int saved = 0;
38         for (Chunk &chunk : store) {
39                 if (chunk.ShouldUpdateSave()) {
40                         save.Write(chunk);
41                         ++saved;
42                         if (saved >= max_save) {
43                                 break;
44                         }
45                 }
46         }
47 }
48
49 int ChunkRequester::ToLoad() const noexcept {
50         return store.EstimateMissing();
51 }
52
53 void ChunkRequester::LoadOne() {
54         if (!store.HasMissing()) return;
55
56         Chunk::Pos pos = store.NextMissing();
57         Chunk *chunk = store.Allocate(pos);
58         if (!chunk) {
59                 // chunk store corrupted?
60                 return;
61         }
62
63         if (save.Exists(pos)) {
64                 save.Read(*chunk);
65                 // TODO: request chunk from server with cache tag
66         } else {
67                 // TODO: request chunk from server
68         }
69 }
70
71 void ChunkRequester::LoadN(std::size_t n) {
72         std::size_t end = std::min(n, std::size_t(ToLoad()));
73         for (std::size_t i = 0; i < end && store.HasMissing(); ++i) {
74                 LoadOne();
75         }
76 }
77
78
79 InitialState::InitialState(MasterState &master)
80 : master(master)
81 , message() {
82         message.Position(glm::vec3(0.0f), Gravity::CENTER);
83         message.Set(master.GetEnv().assets.large_ui_font, "logging in");
84 }
85
86 void InitialState::OnEnter() {
87
88 }
89
90 void InitialState::Handle(const SDL_Event &evt) {
91         if (evt.type == SDL_QUIT) {
92                 master.Quit();
93         }
94 }
95
96 void InitialState::Update(int dt) {
97         master.Update(dt);
98 }
99
100 void InitialState::Render(Viewport &viewport) {
101         message.Render(viewport);
102 }
103
104
105 // TODO: this clutter is a giant mess
106 InteractiveState::InteractiveState(MasterState &master, uint32_t player_id)
107 : master(master)
108 , block_types()
109 , save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetConfig().net.host))
110 , world(block_types, master.GetWorldConf())
111 , player(*world.AddPlayer(master.GetConfig().player.name))
112 , hud(master.GetEnv(), master.GetConfig(), player)
113 , manip(master.GetEnv(), player.GetEntity())
114 , input(world, player, master.GetClient())
115 , interface(master.GetConfig(), master.GetEnv().keymap, input, *this)
116 // TODO: looks like chunk requester and receiver can and should be merged
117 , chunk_requester(world.Chunks(), save)
118 , chunk_receiver(world.Chunks())
119 , chunk_renderer(player.GetChunks())
120 , skeletons()
121 , loop_timer(16)
122 , sky(master.GetEnv().loader.LoadCubeMap("skybox")) {
123         if (!save.Exists()) {
124                 save.Write(master.GetWorldConf());
125         }
126         TextureIndex tex_index;
127         master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index);
128         interface.SetInventorySlots(block_types.Size() - 1);
129         chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index);
130         chunk_renderer.FogDensity(master.GetWorldConf().fog_density);
131         skeletons.Load();
132         loop_timer.Start();
133 }
134
135 void InteractiveState::OnEnter() {
136         master.GetEnv().window.GrabMouse();
137 }
138
139 void InteractiveState::Handle(const SDL_Event &event) {
140         switch (event.type) {
141                 case SDL_KEYDOWN:
142                         interface.HandlePress(event.key);
143                         break;
144                 case SDL_KEYUP:
145                         interface.HandleRelease(event.key);
146                         break;
147                 case SDL_MOUSEBUTTONDOWN:
148                         interface.HandlePress(event.button);
149                         break;
150                 case SDL_MOUSEBUTTONUP:
151                         interface.HandleRelease(event.button);
152                         break;
153                 case SDL_MOUSEMOTION:
154                         interface.Handle(event.motion);
155                         break;
156                 case SDL_MOUSEWHEEL:
157                         interface.Handle(event.wheel);
158                         break;
159                 case SDL_QUIT:
160                         master.Quit();
161                         break;
162                 default:
163                         break;
164         }
165 }
166
167 void InteractiveState::Update(int dt) {
168         input.Update(dt);
169         if (input.BlockFocus()) {
170                 hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block);
171         } else if (input.EntityFocus()) {
172                 hud.FocusEntity(*input.EntityFocus().entity);
173         } else {
174                 hud.FocusNone();
175         }
176         hud.Display(block_types[player.GetInventorySlot() + 1]);
177         loop_timer.Update(dt);
178         master.Update(dt);
179         chunk_receiver.Update(dt);
180         chunk_requester.Update(dt);
181
182         hud.Update(dt);
183         int world_dt = 0;
184         while (loop_timer.HitOnce()) {
185                 world.Update(loop_timer.Interval());
186                 world_dt += loop_timer.Interval();
187                 loop_timer.PopIteration();
188         }
189         chunk_renderer.Update(dt);
190
191         if (world_dt > 0) {
192                 input.PushPlayerUpdate(world_dt);
193         }
194
195         glm::mat4 trans = player.GetEntity().Transform(player.GetEntity().ChunkCoords());
196         glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
197         glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
198         master.GetEnv().audio.Position(player.GetEntity().Position());
199         master.GetEnv().audio.Velocity(player.GetEntity().Velocity());
200         master.GetEnv().audio.Orientation(dir, up);
201 }
202
203 void InteractiveState::Render(Viewport &viewport) {
204         viewport.WorldPosition(player.GetEntity().Transform(player.GetEntity().ChunkCoords()));
205         if (master.GetConfig().video.world) {
206                 chunk_renderer.Render(viewport);
207                 world.Render(viewport);
208                 sky.Render(viewport);
209         }
210         hud.Render(viewport);
211 }
212
213 void InteractiveState::MergePlayerCorrection(std::uint16_t pack, const EntityState &state) {
214         input.MergePlayerCorrection(pack, state);
215 }
216
217 void InteractiveState::SetAudio(bool b) {
218         master.GetConfig().audio.enabled = b;
219         if (b) {
220                 hud.PostMessage("Audio enabled");
221         } else {
222                 hud.PostMessage("Audio disabled");
223         }
224 }
225
226 void InteractiveState::SetVideo(bool b) {
227         master.GetConfig().video.world = b;
228         if (b) {
229                 hud.PostMessage("World rendering enabled");
230         } else {
231                 hud.PostMessage("World rendering disabled");
232         }
233 }
234
235 void InteractiveState::SetHUD(bool b) {
236         master.GetConfig().video.hud = b;
237         if (b) {
238                 hud.PostMessage("HUD rendering enabled");
239         } else {
240                 hud.PostMessage("HUD rendering disabled");
241         }
242 }
243
244 void InteractiveState::SetDebug(bool b) {
245         master.GetConfig().video.debug = b;
246         if (b) {
247                 hud.PostMessage("Debug rendering enabled");
248         } else {
249                 hud.PostMessage("Debug rendering disabled");
250         }
251 }
252
253 void InteractiveState::Exit() {
254         master.Quit();
255 }
256
257
258 MasterState::MasterState(
259         Environment &env,
260         Config &config,
261         const World::Config &wc)
262 : env(env)
263 , config(config)
264 , world_conf(wc)
265 , state()
266 , client(config.net)
267 , init_state(*this)
268 , login_packet(-1)
269 , update_status()
270 , update_timer(16) {
271         client.GetConnection().SetHandler(this);
272         update_timer.Start();
273 }
274
275 void MasterState::Quit() {
276         if (!client.GetConnection().Closed()) {
277                 client.SendPart();
278         }
279         env.state.PopUntil(this);
280 }
281
282
283 void MasterState::OnEnter() {
284         login_packet = client.SendLogin(config.player.name);
285         env.state.Push(&init_state);
286 }
287
288
289 void MasterState::Handle(const SDL_Event &event) {
290
291 }
292
293
294 void MasterState::Update(int dt) {
295         update_timer.Update(dt);
296         client.Handle();
297         client.Update(dt);
298 }
299
300
301 void MasterState::Render(Viewport &) {
302
303 }
304
305
306 void MasterState::OnPacketLost(uint16_t id) {
307         if (id == login_packet) {
308                 login_packet = client.SendLogin(config.player.name);
309         }
310 }
311
312 void MasterState::OnTimeout() {
313         if (client.GetConnection().Closed()) {
314                 // TODO: push disconnected message
315                 cout << "connection timed out" << endl;
316                 Quit();
317         }
318 }
319
320 void MasterState::On(const Packet::Join &pack) {
321         pack.ReadWorldName(world_conf.name);
322
323         if (state) {
324                 // changing worlds
325                 cout << "server changing worlds to \"" << world_conf.name << '"' << endl;
326         } else {
327                 // joining game
328                 cout << "joined game \"" << world_conf.name << '"' << endl;
329                 // server received our login
330                 login_packet = -1;
331         }
332
333         uint32_t player_id;
334         pack.ReadPlayerID(player_id);
335         state.reset(new InteractiveState(*this, player_id));
336
337         pack.ReadPlayerState(state->GetPlayer().GetEntity().GetState());
338
339         env.state.PopAfter(this);
340         env.state.Push(state.get());
341 }
342
343 void MasterState::On(const Packet::Part &pack) {
344         if (state) {
345                 // kicked
346                 cout << "kicked by server" << endl;
347         } else {
348                 // join refused
349                 cout << "login refused by server" << endl;
350         }
351         Quit();
352 }
353
354 void MasterState::On(const Packet::SpawnEntity &pack) {
355         if (!state) {
356                 cout << "got entity spawn before world was created" << endl;
357                 Quit();
358                 return;
359         }
360         uint32_t entity_id;
361         pack.ReadEntityID(entity_id);
362         Entity &entity = state->GetWorld().ForceAddEntity(entity_id);
363         UpdateEntity(entity_id, pack.Seq());
364         pack.ReadEntity(entity);
365         uint32_t skel_id;
366         pack.ReadSkeletonID(skel_id);
367         CompositeModel *skel = state->GetSkeletons().ByID(skel_id);
368         if (skel) {
369                 skel->Instantiate(entity.GetModel());
370         }
371         cout << "spawned entity #" << entity_id << "  (" << entity.Name()
372                 << ") at " << entity.AbsolutePosition() << endl;
373 }
374
375 void MasterState::On(const Packet::DespawnEntity &pack) {
376         if (!state) {
377                 cout << "got entity despawn before world was created" << endl;
378                 Quit();
379                 return;
380         }
381         uint32_t entity_id;
382         pack.ReadEntityID(entity_id);
383         ClearEntity(entity_id);
384         for (Entity &entity : state->GetWorld().Entities()) {
385                 if (entity.ID() == entity_id) {
386                         entity.Kill();
387                         cout << "despawned entity #" << entity_id << " (" << entity.Name() << ") at " << entity.AbsolutePosition() << endl;
388                         return;
389                 }
390         }
391 }
392
393 void MasterState::On(const Packet::EntityUpdate &pack) {
394         if (!state) {
395                 cout << "got entity update before world was created" << endl;
396                 Quit();
397                 return;
398         }
399
400         auto world_iter = state->GetWorld().Entities().begin();
401         auto world_end = state->GetWorld().Entities().end();
402
403         uint32_t count = 0;
404         pack.ReadEntityCount(count);
405
406         for (uint32_t i = 0; i < count; ++i) {
407                 uint32_t entity_id = 0;
408                 pack.ReadEntityID(entity_id, i);
409
410                 while (world_iter != world_end && world_iter->ID() < entity_id) {
411                         ++world_iter;
412                 }
413                 if (world_iter == world_end) {
414                         // nothing can be done from here
415                         return;
416                 }
417                 if (world_iter->ID() == entity_id) {
418                         if (UpdateEntity(entity_id, pack.Seq())) {
419                                 pack.ReadEntityState(world_iter->GetState(), i);
420                         }
421                 }
422         }
423 }
424
425 bool MasterState::UpdateEntity(uint32_t entity_id, uint16_t seq) {
426         auto entry = update_status.find(entity_id);
427         if (entry == update_status.end()) {
428                 update_status.emplace(entity_id, UpdateStatus{ seq, update_timer.Elapsed() });
429                 return true;
430         }
431
432         int16_t pack_diff = int16_t(seq) - int16_t(entry->second.last_packet);
433         int time_diff = update_timer.Elapsed() - entry->second.last_update;
434         entry->second.last_update = update_timer.Elapsed();
435
436         if (pack_diff > 0 || time_diff > 1500) {
437                 entry->second.last_packet = seq;
438                 return true;
439         } else {
440                 return false;
441         }
442 }
443
444 void MasterState::ClearEntity(uint32_t entity_id) {
445         update_status.erase(entity_id);
446 }
447
448 void MasterState::On(const Packet::PlayerCorrection &pack) {
449         if (!state) {
450                 cout << "got player correction without a player :S" << endl;
451                 Quit();
452                 return;
453         }
454         uint16_t pack_seq;
455         EntityState corrected_state;
456         pack.ReadPacketSeq(pack_seq);
457         pack.ReadPlayerState(corrected_state);
458         state->MergePlayerCorrection(pack_seq, corrected_state);
459 }
460
461 void MasterState::On(const Packet::ChunkBegin &pack) {
462         if (!state) {
463                 cout << "got chunk data, but the world has not been created yet" << endl;
464                 cout << "great, this will totally screw up everything :(" << endl;
465                 return;
466         }
467         state->GetChunkReceiver().Handle(pack);
468 }
469
470 void MasterState::On(const Packet::ChunkData &pack) {
471         if (!state) {
472                 cout << "got chunk data, but the world has not been created yet" << endl;
473                 cout << "great, this will totally screw up everything :(" << endl;
474                 return;
475         }
476         state->GetChunkReceiver().Handle(pack);
477 }
478
479 }
480 }