-
-
Notifications
You must be signed in to change notification settings - Fork 632
/
ClientHandle.cpp
3034 lines (2113 loc) · 72.6 KB
/
ClientHandle.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "ClientHandle.h"
#include "Server.h"
#include "World.h"
#include "Entities/Pickup.h"
#include "Bindings/PluginManager.h"
#include "Entities/Player.h"
#include "Inventory.h"
#include "BlockEntities/BeaconEntity.h"
#include "BlockEntities/ChestEntity.h"
#include "BlockEntities/CommandBlockEntity.h"
#include "BlockEntities/SignEntity.h"
#include "UI/Window.h"
#include "Item.h"
#include "Mobs/Monster.h"
#include "ChatColor.h"
#include "OSSupport/Socket.h"
#include "OSSupport/Timer.h"
#include "Items/ItemHandler.h"
#include "Blocks/BlockHandler.h"
#include "Blocks/BlockSlab.h"
#include "Blocks/ChunkInterface.h"
#include "Root.h"
#include "Protocol/Authenticator.h"
#include "MersenneTwister.h"
#include "Protocol/ProtocolRecognizer.h"
#include "CompositeChat.h"
#include "Items/ItemSword.h"
#include "polarssl/md5.h"
/** Maximum number of explosions to send this tick, server will start dropping if exceeded */
#define MAX_EXPLOSIONS_PER_TICK 20
/** Maximum number of block change interactions a player can perform per tick - exceeding this causes a kick */
#define MAX_BLOCK_CHANGE_INTERACTIONS 20
#define RECI_RAND_MAX (1.f/RAND_MAX)
inline int fRadRand(MTRand & r1, int a_BlockCoord)
{
return a_BlockCoord * 32 + (int)(16 * ((float)r1.rand() * RECI_RAND_MAX) * 16 - 8);
}
int cClientHandle::s_ClientCount = 0;
////////////////////////////////////////////////////////////////////////////////
// cClientHandle:
cClientHandle::cClientHandle(const cSocket * a_Socket, int a_ViewDistance) :
m_UsedViewDistance(a_ViewDistance),
m_SetViewDistance(a_ViewDistance),
m_IPString(a_Socket->GetIPString()),
m_OutgoingData(64 KiB),
m_Player(nullptr),
m_HasSentDC(false),
m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login
m_LastStreamedChunkZ(0x7fffffff),
m_TimeSinceLastPacket(0),
m_Ping(1000),
m_PingID(1),
m_PingStartTime(0),
m_LastPingTime(1000),
m_BlockDigAnimStage(-1),
m_BlockDigAnimSpeed(0),
m_BlockDigAnimX(0),
m_BlockDigAnimY(256), // Invalid Y, so that the coords don't get picked up
m_BlockDigAnimZ(0),
m_HasStartedDigging(false),
m_LastDigBlockX(0),
m_LastDigBlockY(256), // Invalid Y, so that the coords don't get picked up
m_LastDigBlockZ(0),
m_State(csConnected),
m_ShouldCheckDownloaded(false),
m_NumExplosionsThisTick(0),
m_NumBlockChangeInteractionsThisTick(0),
m_UniqueID(0),
m_HasSentPlayerChunk(false),
m_Locale("en_GB"),
m_ProtocolVersion(0)
{
m_Protocol = new cProtocolRecognizer(this);
s_ClientCount++; // Not protected by CS because clients are always constructed from the same thread
m_UniqueID = s_ClientCount;
cTimer t1;
m_LastPingTime = t1.GetNowTime();
LOGD("New ClientHandle created at %p", this);
}
cClientHandle::~cClientHandle()
{
ASSERT(m_State == csDestroyed); // Has Destroy() been called?
LOGD("Deleting client \"%s\" at %p", GetUsername().c_str(), this);
{
cCSLock Lock(m_CSChunkLists);
m_LoadedChunks.clear();
m_ChunksToSend.clear();
}
if (m_Player != nullptr)
{
cWorld * World = m_Player->GetWorld();
if (World != nullptr)
{
if (!m_Username.empty())
{
// Send the Offline PlayerList packet:
World->BroadcastPlayerListRemovePlayer(*m_Player, this);
}
World->RemovePlayer(m_Player, true); // Must be called before cPlayer::Destroy() as otherwise cChunk tries to delete the player, and then we do it again
m_Player->Destroy();
}
delete m_Player;
m_Player = nullptr;
}
if (!m_HasSentDC)
{
SendDisconnect("Server shut down? Kthnxbai");
}
// Close the socket as soon as it sends all outgoing data:
cRoot::Get()->GetServer()->RemoveClient(this);
delete m_Protocol;
m_Protocol = nullptr;
LOGD("ClientHandle at %p deleted", this);
}
void cClientHandle::Destroy(void)
{
{
cCSLock Lock(m_CSDestroyingState);
if (m_State >= csDestroying)
{
// Already called
return;
}
m_State = csDestroying;
}
// DEBUG:
LOGD("%s: client %p, \"%s\"", __FUNCTION__, this, m_Username.c_str());
if ((m_Player != nullptr) && (m_Player->GetWorld() != nullptr))
{
RemoveFromAllChunks();
m_Player->GetWorld()->RemoveClientFromChunkSender(this);
}
m_State = csDestroyed;
}
void cClientHandle::GenerateOfflineUUID(void)
{
m_UUID = GenerateOfflineUUID(m_Username);
}
AString cClientHandle::FormatChatPrefix(bool ShouldAppendChatPrefixes, AString a_ChatPrefixS, AString m_Color1, AString m_Color2)
{
if (ShouldAppendChatPrefixes)
return Printf("%s[%s] %s", m_Color1.c_str(), a_ChatPrefixS.c_str(), m_Color2.c_str());
else
return Printf("%s", m_Color1.c_str());
}
AString cClientHandle::FormatMessageType(bool ShouldAppendChatPrefixes, eMessageType a_ChatPrefix, const AString &a_AdditionalData)
{
switch (a_ChatPrefix)
{
case mtCustom: return "";
case mtFailure: return FormatChatPrefix(ShouldAppendChatPrefixes, "INFO", cChatColor::Rose, cChatColor::White);
case mtInformation: return FormatChatPrefix(ShouldAppendChatPrefixes, "INFO", cChatColor::Yellow, cChatColor::White);
case mtSuccess: return FormatChatPrefix(ShouldAppendChatPrefixes, "INFO", cChatColor::Green, cChatColor::White);
case mtWarning: return FormatChatPrefix(ShouldAppendChatPrefixes, "WARN", cChatColor::Rose, cChatColor::White);
case mtFatal: return FormatChatPrefix(ShouldAppendChatPrefixes, "FATAL", cChatColor::Red, cChatColor::White);
case mtDeath: return FormatChatPrefix(ShouldAppendChatPrefixes, "DEATH", cChatColor::Gray, cChatColor::White);
case mtJoin: return FormatChatPrefix(ShouldAppendChatPrefixes, "JOIN", cChatColor::Yellow, cChatColor::White);
case mtLeave: return FormatChatPrefix(ShouldAppendChatPrefixes, "LEAVE", cChatColor::Yellow, cChatColor::White);
case mtPrivateMessage:
{
if (ShouldAppendChatPrefixes)
{
return Printf("%s[MSG: %s] %s%s", cChatColor::LightBlue, a_AdditionalData.c_str(), cChatColor::White, cChatColor::Italic);
}
else
{
return Printf("%s: %s", a_AdditionalData.c_str(), cChatColor::LightBlue);
}
}
}
ASSERT(!"Unhandled chat prefix type!");
return "";
}
AString cClientHandle::GenerateOfflineUUID(const AString & a_Username)
{
// Online UUIDs are always version 4 (random)
// We use Version 3 (MD5 hash) UUIDs for the offline UUIDs
// This guarantees that they will never collide with an online UUID and can be distinguished.
// Proper format for a version 3 UUID is:
// xxxxxxxx-xxxx-3xxx-yxxx-xxxxxxxxxxxx where x is any hexadecimal digit and y is one of 8, 9, A, or B
// Note that we generate a short UUID (without the dashes)
// First make the username lowercase:
AString lcUsername = StrToLower(a_Username);
// Generate an md5 checksum, and use it as base for the ID:
unsigned char MD5[16];
md5((const unsigned char *)lcUsername.c_str(), lcUsername.length(), MD5);
MD5[6] &= 0x0f; // Need to trim to 4 bits only...
MD5[8] &= 0x0f; // ... otherwise %01x overflows into two chars
return Printf("%02x%02x%02x%02x%02x%02x3%01x%02x8%01x%02x%02x%02x%02x%02x%02x%02x",
MD5[0], MD5[1], MD5[2], MD5[3],
MD5[4], MD5[5], MD5[6], MD5[7],
MD5[8], MD5[9], MD5[10], MD5[11],
MD5[12], MD5[13], MD5[14], MD5[15]
);
}
bool cClientHandle::IsUUIDOnline(const AString & a_UUID)
{
// Online UUIDs are always version 4 (random)
// We use Version 3 (MD5 hash) UUIDs for the offline UUIDs
// This guarantees that they will never collide with an online UUID and can be distinguished.
// The version-specifying char is at pos #12 of raw UUID, pos #14 in dashed-UUID.
switch (a_UUID.size())
{
case 32:
{
// This is the UUID format without dashes, the version char is at pos #12:
return (a_UUID[12] == '4');
}
case 36:
{
// This is the UUID format with dashes, the version char is at pos #14:
return (a_UUID[14] == '4');
}
}
return false;
}
void cClientHandle::Kick(const AString & a_Reason)
{
if (m_State >= csAuthenticating) // Don't log pings
{
LOGINFO("Kicking player %s for \"%s\"", m_Username.c_str(), StripColorCodes(a_Reason).c_str());
}
SendDisconnect(a_Reason);
}
void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties)
{
if (m_State != csAuthenticating)
{
return;
}
ASSERT(m_Player == nullptr);
m_Username = a_Name;
// Only assign UUID and properties if not already pre-assigned (BungeeCord sends those in the Handshake packet):
if (m_UUID.empty())
{
m_UUID = a_UUID;
}
if (m_Properties.empty())
{
m_Properties = a_Properties;
}
// Send login success (if the protocol supports it):
m_Protocol->SendLoginSuccess();
// Spawn player (only serversided, so data is loaded)
m_Player = new cPlayer(this, GetUsername());
cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
if (World == nullptr)
{
World = cRoot::Get()->GetDefaultWorld();
}
if (m_Player->GetGameMode() == eGameMode_NotSet)
{
m_Player->LoginSetGameMode(World->GetGameMode());
}
m_Player->SetIP (m_IPString);
if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*m_Player))
{
cRoot::Get()->BroadcastChatJoin(Printf("%s has joined the game", GetUsername().c_str()));
LOGINFO("Player %s has joined the game", m_Username.c_str());
}
m_ConfirmPosition = m_Player->GetPosition();
// Return a server login packet
m_Protocol->SendLogin(*m_Player, *World);
// Send Weather if raining:
if ((World->GetWeather() == 1) || (World->GetWeather() == 2))
{
m_Protocol->SendWeather(World->GetWeather());
}
// Send time:
m_Protocol->SendTimeUpdate(World->GetWorldAge(), World->GetTimeOfDay(), World->IsDaylightCycleEnabled());
// Send contents of the inventory window
m_Protocol->SendWholeInventory(*m_Player->GetWindow());
// Send health
m_Player->SendHealth();
// Send experience
m_Player->SendExperience();
// Send player list items
SendPlayerListAddPlayer(*m_Player);
World->BroadcastPlayerListAddPlayer(*m_Player);
World->SendPlayerList(m_Player);
m_Player->Initialize(*World);
m_State = csAuthenticated;
// Query player team
m_Player->UpdateTeam();
// Send scoreboard data
World->GetScoreBoard().SendTo(*this);
// Send statistics
SendStatistics(m_Player->GetStatManager());
// Delay the first ping until the client "settles down"
// This should fix #889, "BadCast exception, cannot convert bit to fm" error in client
cTimer t1;
m_LastPingTime = t1.GetNowTime() + 3000; // Send the first KeepAlive packet in 3 seconds
cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*m_Player);
}
bool cClientHandle::StreamNextChunk(void)
{
if ((m_State < csAuthenticated) || (m_State >= csDestroying))
{
return true;
}
ASSERT(m_Player != nullptr);
int ChunkPosX = m_Player->GetChunkX();
int ChunkPosZ = m_Player->GetChunkZ();
if ((m_LastStreamedChunkX == ChunkPosX) && (m_LastStreamedChunkZ == ChunkPosZ))
{
// All chunks are already loaded. Abort loading.
return true;
}
// Get the look vector and normalize it.
Vector3d Position = m_Player->GetEyePosition();
Vector3d LookVector = m_Player->GetLookVector();
LookVector.Normalize();
// Lock the list
cCSLock Lock(m_CSChunkLists);
// High priority: Load the chunks that are in the view-direction of the player (with a radius of 3)
for (int Range = 0; Range < m_UsedViewDistance; Range++)
{
Vector3d Vector = Position + LookVector * cChunkDef::Width * Range;
// Get the chunk from the x/z coords.
int RangeX, RangeZ = 0;
cChunkDef::BlockToChunk(FloorC(Vector.x), FloorC(Vector.z), RangeX, RangeZ);
for (size_t X = 0; X < 7; X++)
{
for (size_t Z = 0; Z < 7; Z++)
{
int ChunkX = RangeX + ((X >= 4) ? (3 - X) : X);
int ChunkZ = RangeZ + ((Z >= 4) ? (3 - Z) : Z);
cChunkCoords Coords(ChunkX, ChunkZ);
// Checks if the chunk is in distance
if ((Diff(ChunkX, ChunkPosX) > m_UsedViewDistance) || (Diff(ChunkZ, ChunkPosZ) > m_UsedViewDistance))
{
continue;
}
// If the chunk already loading/loaded -> skip
if (
(std::find(m_ChunksToSend.begin(), m_ChunksToSend.end(), Coords) != m_ChunksToSend.end()) ||
(std::find(m_LoadedChunks.begin(), m_LoadedChunks.end(), Coords) != m_LoadedChunks.end())
)
{
continue;
}
// Unloaded chunk found -> Send it to the client.
Lock.Unlock();
StreamChunk(ChunkX, ChunkZ, ((Range <= 2) ? cChunkSender::E_CHUNK_PRIORITY_HIGH : cChunkSender::E_CHUNK_PRIORITY_MEDIUM));
return false;
}
}
}
// Low priority: Add all chunks that are in range. (From the center out to the edge)
for (int d = 0; d <= m_UsedViewDistance; ++d) // cycle through (square) distance, from nearest to furthest
{
// For each distance add chunks in a hollow square centered around current position:
cChunkCoordsList CurcleChunks;
for (int i = -d; i <= d; ++i)
{
CurcleChunks.push_back(cChunkCoords(ChunkPosX + d, ChunkPosZ + i));
CurcleChunks.push_back(cChunkCoords(ChunkPosX - d, ChunkPosZ + i));
}
for (int i = -d + 1; i < d; ++i)
{
CurcleChunks.push_back(cChunkCoords(ChunkPosX + i, ChunkPosZ + d));
CurcleChunks.push_back(cChunkCoords(ChunkPosX + i, ChunkPosZ - d));
}
// For each the CurcleChunks list and send the first unloaded chunk:
for (cChunkCoordsList::iterator itr = CurcleChunks.begin(), end = CurcleChunks.end(); itr != end; ++itr)
{
cChunkCoords Coords = *itr;
// If the chunk already loading/loaded -> skip
if (
(std::find(m_ChunksToSend.begin(), m_ChunksToSend.end(), Coords) != m_ChunksToSend.end()) ||
(std::find(m_LoadedChunks.begin(), m_LoadedChunks.end(), Coords) != m_LoadedChunks.end())
)
{
continue;
}
// Unloaded chunk found -> Send it to the client.
Lock.Unlock();
StreamChunk(Coords.m_ChunkX, Coords.m_ChunkZ, cChunkSender::E_CHUNK_PRIORITY_LOW);
return false;
}
}
// All chunks are loaded -> Sets the last loaded chunk coordinates to current coordinates
m_LastStreamedChunkX = ChunkPosX;
m_LastStreamedChunkZ = ChunkPosZ;
return true;
}
void cClientHandle::UnloadOutOfRangeChunks(void)
{
int ChunkPosX = FAST_FLOOR_DIV((int)m_Player->GetPosX(), cChunkDef::Width);
int ChunkPosZ = FAST_FLOOR_DIV((int)m_Player->GetPosZ(), cChunkDef::Width);
cChunkCoordsList ChunksToRemove;
{
cCSLock Lock(m_CSChunkLists);
for (cChunkCoordsList::iterator itr = m_LoadedChunks.begin(); itr != m_LoadedChunks.end();)
{
int DiffX = Diff((*itr).m_ChunkX, ChunkPosX);
int DiffZ = Diff((*itr).m_ChunkZ, ChunkPosZ);
if ((DiffX > m_UsedViewDistance) || (DiffZ > m_UsedViewDistance))
{
ChunksToRemove.push_back(*itr);
itr = m_LoadedChunks.erase(itr);
}
else
{
++itr;
}
}
for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end();)
{
int DiffX = Diff((*itr).m_ChunkX, ChunkPosX);
int DiffZ = Diff((*itr).m_ChunkZ, ChunkPosZ);
if ((DiffX > m_UsedViewDistance) || (DiffZ > m_UsedViewDistance))
{
itr = m_ChunksToSend.erase(itr);
}
else
{
++itr;
}
}
}
for (cChunkCoordsList::iterator itr = ChunksToRemove.begin(); itr != ChunksToRemove.end(); ++itr)
{
m_Player->GetWorld()->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this);
SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ);
}
}
void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority)
{
if (m_State >= csDestroying)
{
// Don't stream chunks to clients that are being destroyed
return;
}
cWorld * World = m_Player->GetWorld();
ASSERT(World != nullptr);
if (World->AddChunkClient(a_ChunkX, a_ChunkZ, this))
{
{
cCSLock Lock(m_CSChunkLists);
m_LoadedChunks.push_back(cChunkCoords(a_ChunkX, a_ChunkZ));
m_ChunksToSend.push_back(cChunkCoords(a_ChunkX, a_ChunkZ));
}
World->SendChunkTo(a_ChunkX, a_ChunkZ, a_Priority, this);
}
}
// Removes the client from all chunks. Used when switching worlds or destroying the player
void cClientHandle::RemoveFromAllChunks()
{
cWorld * World = m_Player->GetWorld();
if (World != nullptr)
{
World->RemoveClientFromChunks(this);
}
{
// Reset all chunk lists:
cCSLock Lock(m_CSChunkLists);
m_LoadedChunks.clear();
m_ChunksToSend.clear();
m_SentChunks.clear();
// Also reset the LastStreamedChunk coords to bogus coords,
// so that all chunks are streamed in subsequent StreamChunks() call (FS #407)
m_LastStreamedChunkX = 0x7fffffff;
m_LastStreamedChunkZ = 0x7fffffff;
}
}
void cClientHandle::HandleNPCTrade(int a_SlotNum)
{
// TODO
LOGWARNING("%s: Not implemented yet", __FUNCTION__);
}
void cClientHandle::HandlePing(void)
{
// Somebody tries to retrieve information about the server
AString Reply;
const cServer & Server = *cRoot::Get()->GetServer();
Printf(Reply, "%s%s%i%s%i",
Server.GetDescription().c_str(),
cChatColor::Delimiter,
Server.GetNumPlayers(),
cChatColor::Delimiter,
Server.GetMaxPlayers()
);
Kick(Reply);
}
bool cClientHandle::HandleLogin(int a_ProtocolVersion, const AString & a_Username)
{
// If the protocol version hasn't been set yet, set it now:
if (m_ProtocolVersion == 0)
{
m_ProtocolVersion = a_ProtocolVersion;
}
m_Username = a_Username;
// Let the plugins know about this event, they may refuse the player:
if (cRoot::Get()->GetPluginManager()->CallHookLogin(*this, a_ProtocolVersion, a_Username))
{
Destroy();
return false;
}
// Schedule for authentication; until then, let the player wait (but do not block)
m_State = csAuthenticating;
cRoot::Get()->GetAuthenticator().Authenticate(GetUniqueID(), GetUsername(), m_Protocol->GetAuthServerID());
return true;
}
void cClientHandle::HandleCreativeInventory(short a_SlotNum, const cItem & a_HeldItem)
{
// This is for creative Inventory changes
if (!m_Player->IsGameModeCreative())
{
LOGWARNING("Got a CreativeInventoryAction packet from user \"%s\" while not in creative mode. Ignoring.", m_Username.c_str());
return;
}
if (m_Player->GetWindow()->GetWindowType() != cWindow::wtInventory)
{
LOGWARNING("Got a CreativeInventoryAction packet from user \"%s\" while not in the inventory window. Ignoring.", m_Username.c_str());
return;
}
m_Player->GetWindow()->Clicked(*m_Player, 0, a_SlotNum, (a_SlotNum >= 0) ? caLeftClick : caLeftClickOutside, a_HeldItem);
}
void cClientHandle::HandlePlayerAbilities(bool a_CanFly, bool a_IsFlying, float FlyingSpeed, float WalkingSpeed)
{
UNUSED(FlyingSpeed); // Ignore the client values for these
UNUSED(WalkingSpeed);
m_Player->SetCanFly(a_CanFly);
m_Player->SetFlying(a_IsFlying);
}
void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ, double a_Stance, bool a_IsOnGround)
{
if ((m_Player == nullptr) || (m_State != csPlaying))
{
// The client hasn't been spawned yet and sends nonsense, we know better
return;
}
/*
// TODO: Invalid stance check
if ((a_PosY >= a_Stance) || (a_Stance > a_PosY + 1.65))
{
LOGD("Invalid stance");
SendPlayerMoveLook();
return;
}
*/
// If the player has moved too far, "repair" them:
Vector3d Pos(a_PosX, a_PosY, a_PosZ);
if ((m_Player->GetPosition() - Pos).SqrLength() > 100 * 100)
{
LOGD("Too far away (%0.2f), \"repairing\" the client", (m_Player->GetPosition() - Pos).Length());
SendPlayerMoveLook();
return;
}
// If a jump just started, process food exhaustion:
if ((a_PosY > m_Player->GetPosY()) && !a_IsOnGround && m_Player->IsOnGround())
{
// we only add this exhaustion if the player is not swimming - otherwise we end up with both jump + swim exhaustion
if (!m_Player->IsSwimming())
{
m_Player->GetStatManager().AddValue(statJumps, 1);
m_Player->AddFoodExhaustion(m_Player->IsSprinting() ? 0.8 : 0.2);
}
}
m_Player->MoveTo(Pos);
m_Player->SetStance(a_Stance);
m_Player->SetTouchGround(a_IsOnGround);
}
void cClientHandle::HandlePluginMessage(const AString & a_Channel, const AString & a_Message)
{
if (a_Channel == "REGISTER")
{
if (HasPluginChannel(a_Channel))
{
SendPluginMessage("UNREGISTER", a_Channel);
return; // Can't register again if already taken - kinda defeats the point of plugin messaging!
}
RegisterPluginChannels(BreakApartPluginChannels(a_Message));
}
else if (a_Channel == "UNREGISTER")
{
UnregisterPluginChannels(BreakApartPluginChannels(a_Message));
}
else if (!HasPluginChannel(a_Channel))
{
// Ignore if client sent something but didn't register the channel first
LOGD("Player %s sent a plugin message on channel \"%s\", but didn't REGISTER it first", GetUsername().c_str(), a_Channel.c_str());
SendPluginMessage("UNREGISTER", a_Channel);
return;
}
cPluginManager::Get()->CallHookPluginMessage(*this, a_Channel, a_Message);
}
AStringVector cClientHandle::BreakApartPluginChannels(const AString & a_PluginChannels)
{
// Break the string on each NUL character.
// Note that StringSplit() doesn't work on this because NUL is a special char - string terminator
size_t len = a_PluginChannels.size();
size_t first = 0;
AStringVector res;
for (size_t i = 0; i < len; i++)
{
if (a_PluginChannels[i] != 0)
{
continue;
}
if (i > first)
{
res.push_back(a_PluginChannels.substr(first, i - first));
}
first = i + 1;
} // for i - a_PluginChannels[]
if (first < len)
{
res.push_back(a_PluginChannels.substr(first, len - first));
}
return res;
}
void cClientHandle::RegisterPluginChannels(const AStringVector & a_ChannelList)
{
for (AStringVector::const_iterator itr = a_ChannelList.begin(), end = a_ChannelList.end(); itr != end; ++itr)
{
m_PluginChannels.insert(*itr);
} // for itr - a_ChannelList[]
}
void cClientHandle::UnregisterPluginChannels(const AStringVector & a_ChannelList)
{
for (AStringVector::const_iterator itr = a_ChannelList.begin(), end = a_ChannelList.end(); itr != end; ++itr)
{
m_PluginChannels.erase(*itr);
} // for itr - a_ChannelList[]
}
void cClientHandle::HandleBeaconSelection(int a_PrimaryEffect, int a_SecondaryEffect)
{
cWindow * Window = m_Player->GetWindow();
if ((Window == nullptr) || (Window->GetWindowType() != cWindow::wtBeacon))
{
return;
}
cBeaconWindow * BeaconWindow = (cBeaconWindow *) Window;
if (Window->GetSlot(*m_Player, 0)->IsEmpty())
{
return;
}
cEntityEffect::eType PrimaryEffect = cEntityEffect::effNoEffect;
if ((a_PrimaryEffect >= 0) && (a_PrimaryEffect <= (int)cEntityEffect::effSaturation))
{
PrimaryEffect = (cEntityEffect::eType)a_PrimaryEffect;
}
cEntityEffect::eType SecondaryEffect = cEntityEffect::effNoEffect;
if ((a_SecondaryEffect >= 0) && (a_SecondaryEffect <= (int)cEntityEffect::effSaturation))
{
SecondaryEffect = (cEntityEffect::eType)a_SecondaryEffect;
}
Window->SetSlot(*m_Player, 0, cItem());
BeaconWindow->GetBeaconEntity()->SetPrimaryEffect(PrimaryEffect);
// Valid effect check. Vanilla don't check this, but we do it :)
if (
(SecondaryEffect == cEntityEffect::effNoEffect) ||
(SecondaryEffect == cEntityEffect::effRegeneration) ||
(SecondaryEffect == BeaconWindow->GetBeaconEntity()->GetPrimaryEffect())
)
{
BeaconWindow->GetBeaconEntity()->SetSecondaryEffect(SecondaryEffect);
}
else
{
BeaconWindow->GetBeaconEntity()->SetSecondaryEffect(cEntityEffect::effNoEffect);
}
m_Player->CloseWindow(true);
}
void cClientHandle::HandleCommandBlockBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_NewCommand)
{
cWorld * World = m_Player->GetWorld();
if (World->AreCommandBlocksEnabled())
{
World->SetCommandBlockCommand(a_BlockX, a_BlockY, a_BlockZ, a_NewCommand);
SendChat("Successfully set command block command", mtSuccess);
}
else
{
SendChat("Command blocks are not enabled on this server", mtFailure);
}
}
void cClientHandle::HandleCommandBlockEntityChange(int a_EntityID, const AString & a_NewCommand)
{
// TODO
LOGWARNING("%s: Not implemented yet", __FUNCTION__);
}
void cClientHandle::HandleAnvilItemName(const AString & a_ItemName)
{
if ((m_Player->GetWindow() == nullptr) || (m_Player->GetWindow()->GetWindowType() != cWindow::wtAnvil))
{
return;
}
if (a_ItemName.length() <= 30)
{
((cAnvilWindow *)m_Player->GetWindow())->SetRepairedItemName(a_ItemName, m_Player);
}
}
void cClientHandle::HandleLeftClick(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, char a_Status)
{
LOGD("HandleLeftClick: {%i, %i, %i}; Face: %i; Stat: %i",
a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_Status
);
m_NumBlockChangeInteractionsThisTick++;
if (!CheckBlockInteractionsRate())
{
Kick("Too many blocks were destroyed per unit time - hacked client?");
return;
}
if ((a_Status == DIG_STATUS_STARTED) || (a_Status == DIG_STATUS_FINISHED))
{
if (a_BlockFace == BLOCK_FACE_NONE)
{
return;
}
/* Check for clickthrough-blocks:
When the user breaks a fire block, the client send the wrong block location.
We must find the right block with the face direction. */
int BlockX = a_BlockX;
int BlockY = a_BlockY;
int BlockZ = a_BlockZ;
AddFaceDirection(BlockX, BlockY, BlockZ, a_BlockFace);
if (cBlockInfo::GetHandler(m_Player->GetWorld()->GetBlock(BlockX, BlockY, BlockZ))->IsClickedThrough())
{
a_BlockX = BlockX;
a_BlockY = BlockY;
a_BlockZ = BlockZ;
}
if (
((Diff(m_Player->GetPosX(), (double)a_BlockX) > 6) ||
(Diff(m_Player->GetPosY(), (double)a_BlockY) > 6) ||
(Diff(m_Player->GetPosZ(), (double)a_BlockZ) > 6))
)
{
m_Player->GetWorld()->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
return;
}
}
cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager();
if (PlgMgr->CallHookPlayerLeftClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_Status))
{
// A plugin doesn't agree with the action, replace the block on the client and quit:
m_Player->GetWorld()->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
return;
}
switch (a_Status)
{
case DIG_STATUS_DROP_HELD: // Drop held item
{
if (PlgMgr->CallHookPlayerTossingItem(*m_Player))
{
// A plugin doesn't agree with the tossing. The plugin itself is responsible for handling the consequences (possible inventory mismatch)