]> git.localhorst.tv Git - blank.git/blob - src/client/client.cpp
some experiements with state sync
[blank.git] / src / client / client.cpp
1 #include "InitialState.hpp"
2 #include "InteractiveState.hpp"
3 #include "MasterState.hpp"
4
5 #include "../app/Environment.hpp"
6 #include "../app/init.hpp"
7 #include "../app/TextureIndex.hpp"
8 #include "../model/CompositeModel.hpp"
9
10 #include <iostream>
11 #include <glm/gtx/io.hpp>
12
13 using namespace std;
14
15
16 namespace blank {
17 namespace client {
18
19 InitialState::InitialState(MasterState &master)
20 : master(master)
21 , message() {
22         message.Position(glm::vec3(0.0f), Gravity::CENTER);
23         message.Set(master.GetEnv().assets.large_ui_font, "logging in");
24 }
25
26 void InitialState::OnEnter() {
27
28 }
29
30 void InitialState::Handle(const SDL_Event &evt) {
31         if (evt.type == SDL_QUIT) {
32                 master.Quit();
33         }
34 }
35
36 void InitialState::Update(int dt) {
37         master.Update(dt);
38 }
39
40 void InitialState::Render(Viewport &viewport) {
41         message.Render(viewport);
42 }
43
44
45 // TODO: this clutter is a giant mess
46 InteractiveState::InteractiveState(MasterState &master, uint32_t player_id)
47 : master(master)
48 , block_types()
49 , save(master.GetEnv().config.GetWorldPath(master.GetWorldConf().name, master.GetClientConf().host))
50 , world(block_types, master.GetWorldConf())
51 , interface(
52         master.GetInterfaceConf(),
53         master.GetEnv(),
54         world,
55         world.AddPlayer(master.GetInterfaceConf().player_name, player_id)
56 )
57 , chunk_renderer(*interface.GetPlayer().chunks)
58 , skeletons()
59 , loop_timer(16)
60 , update_timer(16)
61 , player_hist() {
62         TextureIndex tex_index;
63         master.GetEnv().loader.LoadBlockTypes("default", block_types, tex_index);
64         chunk_renderer.LoadTextures(master.GetEnv().loader, tex_index);
65         chunk_renderer.FogDensity(master.GetWorldConf().fog_density);
66         skeletons.Load();
67         // TODO: better solution for initializing HUD
68         interface.SelectNext();
69         loop_timer.Start();
70         update_timer.Start();
71 }
72
73 void InteractiveState::OnEnter() {
74         master.GetEnv().window.GrabMouse();
75 }
76
77 void InteractiveState::Handle(const SDL_Event &event) {
78         switch (event.type) {
79                 case SDL_KEYDOWN:
80                         interface.HandlePress(event.key);
81                         break;
82                 case SDL_KEYUP:
83                         interface.HandleRelease(event.key);
84                         break;
85                 case SDL_MOUSEBUTTONDOWN:
86                         interface.HandlePress(event.button);
87                         break;
88                 case SDL_MOUSEBUTTONUP:
89                         interface.HandleRelease(event.button);
90                         break;
91                 case SDL_MOUSEMOTION:
92                         interface.Handle(event.motion);
93                         break;
94                 case SDL_MOUSEWHEEL:
95                         interface.Handle(event.wheel);
96                         break;
97                 case SDL_QUIT:
98                         master.Quit();
99                         break;
100                 default:
101                         break;
102         }
103 }
104
105 void InteractiveState::Update(int dt) {
106         loop_timer.Update(dt);
107         update_timer.Update(dt);
108         master.Update(dt);
109
110         interface.Update(dt);
111         while (loop_timer.HitOnce()) {
112                 world.Update(loop_timer.Interval());
113                 loop_timer.PopIteration();
114         }
115         chunk_renderer.Update(dt);
116
117         Entity &player = *interface.GetPlayer().entity;
118
119         if (update_timer.Hit()) {
120                 PushPlayerUpdate(player);
121         }
122
123         glm::mat4 trans = player.Transform(player.ChunkCoords());
124         glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f));
125         glm::vec3 up(trans * glm::vec4(0.0f, 1.0f, 0.0f, 0.0f));
126         master.GetEnv().audio.Position(player.Position());
127         master.GetEnv().audio.Velocity(player.Velocity());
128         master.GetEnv().audio.Orientation(dir, up);
129 }
130
131 void InteractiveState::PushPlayerUpdate(const Entity &player) {
132         std::uint16_t packet = master.GetClient().SendPlayerUpdate(player);
133         if (player_hist.size() < 16) {
134                 player_hist.emplace_back(player.GetState(), packet);
135         } else {
136                 auto entry = player_hist.begin();
137                 entry->state = player.GetState();
138                 entry->packet = packet;
139                 player_hist.splice(player_hist.end(), player_hist, entry);
140         }
141 }
142
143 void InteractiveState::MergePlayerCorrection(uint16_t seq, const EntityState &corrected_state) {
144         if (player_hist.empty()) return;
145
146         auto entry = player_hist.begin();
147         auto end = player_hist.end();
148
149         // drop anything older than the fix
150         while (entry != end) {
151                 int pack_diff = int16_t(seq) - int16_t(entry->packet);
152                 if (pack_diff > 0) {
153                         entry = player_hist.erase(entry);
154                 } else {
155                         break;
156                 }
157         }
158
159         EntityState replay_state(corrected_state);
160         EntityState &player_state = interface.GetPlayer().entity->GetState();
161
162         if (entry != end) {
163                 entry->state.chunk_pos = replay_state.chunk_pos;
164                 entry->state.block_pos = replay_state.block_pos;
165                 ++entry;
166         }
167
168         while (entry != end) {
169                 replay_state.velocity = entry->state.velocity;
170                 replay_state.Update(16);
171                 entry->state.chunk_pos = replay_state.chunk_pos;
172                 entry->state.block_pos = replay_state.block_pos;
173                 ++entry;
174         }
175
176         glm::vec3 displacement(replay_state.Diff(player_state));
177         const float disp_squared = dot(displacement, displacement);
178
179         if (disp_squared < 16.0f * numeric_limits<float>::epsilon()) {
180                 return;
181         }
182
183         constexpr float warp_thresh = 1.0f;
184         constexpr float max_disp = 0.0001f; // (1/100)^2
185
186         if (disp_squared > warp_thresh) {
187                 player_state.chunk_pos = replay_state.chunk_pos;
188                 player_state.block_pos = replay_state.block_pos;
189         } else if (disp_squared < max_disp) {
190                 player_state.block_pos += displacement;
191         } else {
192                 displacement *= 0.01f / sqrt(disp_squared);
193                 player_state.block_pos += displacement;
194         }
195 }
196
197 void InteractiveState::Render(Viewport &viewport) {
198         Entity &player = *interface.GetPlayer().entity;
199         viewport.WorldPosition(player.Transform(player.ChunkCoords()));
200         chunk_renderer.Render(viewport);
201         world.Render(viewport);
202         interface.Render(viewport);
203 }
204
205
206 MasterState::MasterState(
207         Environment &env,
208         const World::Config &wc,
209         const Interface::Config &ic,
210         const Client::Config &cc)
211 : env(env)
212 , world_conf(wc)
213 , intf_conf(ic)
214 , client_conf(cc)
215 , state()
216 , client(cc)
217 , init_state(*this)
218 , login_packet(-1)
219 , update_status()
220 , update_timer(16) {
221         client.GetConnection().SetHandler(this);
222         update_timer.Start();
223 }
224
225 void MasterState::Quit() {
226         if (!client.GetConnection().Closed()) {
227                 client.SendPart();
228         }
229         env.state.PopUntil(this);
230 }
231
232
233 void MasterState::OnEnter() {
234         login_packet = client.SendLogin(intf_conf.player_name);
235         env.state.Push(&init_state);
236 }
237
238
239 void MasterState::Handle(const SDL_Event &event) {
240
241 }
242
243
244 void MasterState::Update(int dt) {
245         update_timer.Update(dt);
246         client.Handle();
247         client.Update(dt);
248 }
249
250
251 void MasterState::Render(Viewport &) {
252
253 }
254
255
256 void MasterState::OnPacketLost(uint16_t id) {
257         if (id == login_packet) {
258                 login_packet = client.SendLogin(intf_conf.player_name);
259         }
260 }
261
262 void MasterState::OnTimeout() {
263         if (client.GetConnection().Closed()) {
264                 // TODO: push disconnected message
265                 cout << "connection timed out" << endl;
266                 Quit();
267         }
268 }
269
270 void MasterState::On(const Packet::Join &pack) {
271         pack.ReadWorldName(world_conf.name);
272
273         if (state) {
274                 // changing worlds
275                 cout << "server changing worlds to \"" << world_conf.name << '"' << endl;
276         } else {
277                 // joining game
278                 cout << "joined game \"" << world_conf.name << '"' << endl;
279                 // server received our login
280                 login_packet = -1;
281         }
282
283         uint32_t player_id;
284         pack.ReadPlayerID(player_id);
285         state.reset(new InteractiveState(*this, player_id));
286
287         pack.ReadPlayerState(state->GetInterface().GetPlayer().entity->GetState());
288
289         env.state.PopAfter(this);
290         env.state.Push(state.get());
291 }
292
293 void MasterState::On(const Packet::Part &pack) {
294         if (state) {
295                 // kicked
296                 cout << "kicked by server" << endl;
297         } else {
298                 // join refused
299                 cout << "login refused by server" << endl;
300         }
301         Quit();
302 }
303
304 void MasterState::On(const Packet::SpawnEntity &pack) {
305         if (!state) {
306                 cout << "got entity spawn before world was created" << endl;
307                 Quit();
308                 return;
309         }
310         uint32_t entity_id;
311         pack.ReadEntityID(entity_id);
312         Entity &entity = state->GetWorld().ForceAddEntity(entity_id);
313         UpdateEntity(entity_id, pack.Seq());
314         pack.ReadEntity(entity);
315         uint32_t skel_id;
316         pack.ReadSkeletonID(skel_id);
317         CompositeModel *skel = state->GetSkeletons().ByID(skel_id);
318         if (skel) {
319                 skel->Instantiate(entity.GetModel());
320         }
321         cout << "spawned entity " << entity.Name() << " at " << entity.AbsolutePosition() << endl;
322 }
323
324 void MasterState::On(const Packet::DespawnEntity &pack) {
325         if (!state) {
326                 cout << "got entity despawn before world was created" << endl;
327                 Quit();
328                 return;
329         }
330         uint32_t entity_id;
331         pack.ReadEntityID(entity_id);
332         ClearEntity(entity_id);
333         for (Entity &entity : state->GetWorld().Entities()) {
334                 if (entity.ID() == entity_id) {
335                         entity.Kill();
336                         cout << "despawned entity " << entity.Name() << " at " << entity.AbsolutePosition() << endl;
337                         return;
338                 }
339         }
340 }
341
342 void MasterState::On(const Packet::EntityUpdate &pack) {
343         if (!state) {
344                 cout << "got entity update before world was created" << endl;
345                 Quit();
346                 return;
347         }
348
349         auto world_iter = state->GetWorld().Entities().begin();
350         auto world_end = state->GetWorld().Entities().end();
351
352         uint32_t count = 0;
353         pack.ReadEntityCount(count);
354
355         for (uint32_t i = 0; i < count; ++i) {
356                 uint32_t entity_id = 0;
357                 pack.ReadEntityID(entity_id, i);
358
359                 while (world_iter != world_end && world_iter->ID() < entity_id) {
360                         ++world_iter;
361                 }
362                 if (world_iter == world_end) {
363                         // nothing can be done from here
364                         return;
365                 }
366                 if (world_iter->ID() == entity_id) {
367                         if (UpdateEntity(entity_id, pack.Seq())) {
368                                 pack.ReadEntityState(world_iter->GetState(), i);
369                         }
370                 }
371         }
372 }
373
374 bool MasterState::UpdateEntity(uint32_t entity_id, uint16_t seq) {
375         auto entry = update_status.find(entity_id);
376         if (entry == update_status.end()) {
377                 update_status.emplace(entity_id, UpdateStatus{ seq, update_timer.Elapsed() });
378                 return true;
379         }
380
381         int pack_diff = int16_t(seq) - int16_t(entry->second.last_packet);
382         int time_diff = update_timer.Elapsed() - entry->second.last_update;
383         entry->second.last_update = update_timer.Elapsed();
384
385         if (pack_diff > 0 || time_diff > 1500) {
386                 entry->second.last_packet = seq;
387                 return true;
388         } else {
389                 return false;
390         }
391 }
392
393 void MasterState::ClearEntity(uint32_t entity_id) {
394         update_status.erase(entity_id);
395 }
396
397 void MasterState::On(const Packet::PlayerCorrection &pack) {
398         if (!state) {
399                 cout << "got player correction without a player :S" << endl;
400                 Quit();
401                 return;
402         }
403         uint16_t pack_seq;
404         EntityState corrected_state;
405         pack.ReadPacketSeq(pack_seq);
406         pack.ReadPlayerState(corrected_state);
407         state->MergePlayerCorrection(pack_seq, corrected_state);
408 }
409
410 }
411 }