summaryrefslogblamecommitdiffstats
path: root/src/ClientHandle.cpp
blob: 319af9ed34c52b82869424654afacc7fc7708606 (plain) (tree)
1
2
3
4
5
6
7
8
9
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606

                                                                                              


                         
                            
                                   
                            
                      
                                      
                                             
                                     
                      
                 
                   
                         
                      

                             

                                
                             
                                  
 
                 
 
                                   

                            
                                        
                          
                            
 

                       

                    

 
                                                                                             
                                  
 


                                                                                                               
                                                                                     

                                         






















                                                                                                                       















                                                                                              
                                   
                      

                                    
 
                                                   
        





                                                                                                                  
                                                     







                               
                                                                             
        
                                                                          
 











                                                              
                                                                               



                                                      
                                            
                 

                                

         
                         
         
                                                             

         
                                                                  
                                                      
        



                                                 





 
                                 
 











                                                                              





                                                                        
                                     





 

                                             








                                                                      



                                                                                                              



                                            

                                                                                                                                  





                             





 



                                                            
                                                                                                               
         
                                 





 
                                                                                







                                        

                            


                                                            
 








                                                                                

                                                        
                                                                 
         
 
                                     
 

                                                                               
                                                                                                         

                                                                              

                                                    

                                       
                                                 



                                                                     
                                                             


                    
                                                                                
 

                                                               

                      
                               


                                   

                                    

                                  


                               


                                             
                                                                           







                                      
                                                                     





                                 

                                                                                   














































                                                                                                                                       
                                                                             
                                                                          








                                                                                                               

                                                                  


                                                

                                                                  






















                                                                                                                                                   
                                                           
 





                                                                          


                                              
                                                            


                                                     

                                                                                                 
                 
                                                             



















                                                                                          




                                                                                              






         




                                                                  




                                                                    







                            
                                                                                  
 
                                             
                                
 
                                                                                                 

                          
                             



                                                                                    
                                                                                                                   
                    





 
                                                                                      

                                                 
                                            
         

                                                                                                                                              
         
                                                                           
         

                                                                                                                                                     
         
        
                                                                                                                                 





 
                                                                                                                
 


                                                                  

                                        





 
                                                                                                                    
 





                                                                                        
          
                                     







                                                               
                                                          






                                                                                                                 



                                                                                      
                                                                                                                                   
 
                                            


                                                                                         

         
                              

                                               





 

                                                                                             
                                     
         

                                                                               
         
                                         
         
                                                      
                                                          


                                         





                                                                                                                       




                                                                              
         






                                                                                                                                                  
 






                                                                                  






















































                                                                                                                     


                          
                                                                                          



                                                    


                                       



                                   




                              


                          


                                                 
 
                                                          




                              
                                                                                                     




                                                                 

                                              


                                                                               
                
                                                                              


            
                                                                                     
         





 
                                                                                                                    
 



                                                                   







                                                                                      






                                                                                                            
 








                                                                                                                                                                        

                                                     







                                                                                                               

                                                        







                                                                                                                                                                               
                                                                                                              
                         




















                                                                                                                





                                                             






                                                                                                                                                                        
                                                                                                                                       


                               








                                                        
 

 
                                                                                                                                                       






                                                
         
                                                               

                       
        

                                                 
                                                                                            





                                                                         
                                                                                                                                                       
         

                                                                                                                           
                       
         
        




                                                                                                                                              
 
            
                                                                                                     
                                                                                                     
         
         
                                                                                                         

                       
 










                                                                                                                                   
                                                             
                                                                     
                                                                                           

                                                                                               
                                                                                                                             






                                           
 
                                                                                                                                               
                                                                              
 

                                                
                                                                                         


                 
 
 


 
                                                                                                                                                        






                                                                                           
         


                                                                                                                  
                                                                

                       
         


                                      

                                    
                                         
                                                        
                                                                                                                                            

         
                                                                                               
        
                                      
         
                                                     
                       
         

                                              
                                                                                                                  
                                                                       

                                                                                                                     
                                                                                                  
                                                      
 
                                                                                                                                                 





 
                                                                                                                                                                             
 
                                                                     
                                                                                               

          

                                              



                                                                                                                                    




                                                                                                                              
                                                   

                                                                                    

                                                                                                             


                       

                                               
        

                                          
                                                                                                   


                       
                                                                            
 
                                                                                            
         


                                                                                                        
                                                                                                  
                                                                                      
                  

                                                                                                                                                      
                                                   
                 
                                                                                    
                                                                                   
                 

                       
 


                                                                                    
                                                                         
        
                                                                 
         




                                                                                                                                                                  

                                                                                                                                                  

                                                                                                                                                             
         


                                                                                       
                                                                           
         



                                                                                                                           




                                                                  

                                                            
                 

                                                                                                         
                               
                 
                       



                                                                                                                                           
                 


                                                                            
                                                                                                             


                                                                                                                                      
 
 
 

 
                                                                                                                                                                                 
 
                                                                                      






                                              


                                    

                                                                                                




                                                                                                    
        

                                                                                              
                                                                                               
            

                                                                                                                      
                                                                                                            
                 

                                                                                                                        
                 
         
         



                                                                                                                                    
                                                                                 







                                                                                                      


            
                                                                                  



                                                                                                        
                 

                                                                                                                                        
                 
                    
                 
                                                                                    
                        





                                                                                                                    


                                                                                                     
 

                                                                                                                                              
                                                                         
                         
                                                                                              


                            



                                                                                                                

                                                                                 
                                                                                                                                      


                                               

                 
        










                                                                                                                                                                        








                                                                                                                                                                                      
                                            
         
                                                                 
         
                                                             
                                                                                                                                                                      

                                                                      
                                                                                                                    
                                                                                                                                                                                 





 
                                                         
 
                                                                                                                   
        






                                                               













                                                                          
                                                 





 
                                                                                        
 




                                                         
                                             
                                             

                                               





 
                                                                                                                                                          
 





                                                                                        








                                                               




                                                           
                                             
                                          





 
                                                     
 
                                                                                   



                                             






















                                                                                                                                            
                                                                                     





 
                                                       
 
                                                               
                                                                                                                       





 








                                                                         
                                                      
 
                                              





 
                                                                                                                             
 

                                                                           

                                                                        







                                                                                                           
                                                                                     





 




                                                          

                                              
                                                                                                      





 
                                                                             
 

                                                 
                                                                              
                           
         




                                                                      



                                                                                                                  
                                                                   
                                             






                                                                               


                       
                                                    



                                                              
                                                                  
                         

                                                                                           




                                                                                                                                  

                                                          

                 
                                   

                   
                                       

                                              




                                                                







                                       
                             



                          
                            
                                                                           





 
                                                              
 
                                                                                                     


                                                                                 
                           






                  
                                                      
 
                                      






                                                                          


 



                                                                                   
                                                                                                             











                                                                                  
                                                                          
 
                                                  




                                                                                                      









                                                        
         

                                                                                                      
         
















                                                                                                      





 












                                       



                                                                   
                                                                                        











                                                  
                                                                
 





                                                                                                                                         

                                               




                                                                                                                                                
                                                                      













                                                                                                
                 








                                                                                                                                       
                 
                                    








                                                                

                                                                           
                        





















                                                                                                    

                                                                                            

                                          
                                     





 



                                                    

                                                                                 
         
                             
         
 








                                    










                                                                                                







                                                                           

                                                                    
         
                                                                        


                          




                             
                                                                        
                                                                    
         



                                                 
                              
                                 
         







                                                                                      
         

                                        
                                     
         


                                                                         
                 
                                                   
                 
                                                                     
                 
                                                                                                                                                                                    

                 
        
                                                   
                                    
                                                 





 










































                                                                                                                  
                                                                                         
 
                                                          





 
                                                                                                                                
 
                                                                                                 





 
                                                                                                              
 
                                                                                          





 
                                                                                                                            
 
                                                                                            





 
                                                                                                   
 

                                                                                         
                                                                    





 
                                                                                                                    
 













































































                                                                                                                                                                                     
                                                                                                                    























                                                                                                                        





 








                                                              
                                                                                                  
 

                                 








                                                                                                                 













                                                                                                                                                                              








                                                                                               





 
                                                                                         
 
                                                          





 
                                                               
 
                                                





 
                                                            
 

                         
                                                                                


                                                     





 








                                                                          








                                                                                                                 
                                                                                                        
 
                                                                     





 
                                                                
 


                                                                                             





 
                                                            


                                                                                             
                                             





 
                                                                
 
                                                 





 
                                                                                                      


                                                                                             
                                                                        





 
                                                                                                          


                                                                                             









                                                                             





 
                                                                
 
                                                 





 
                                                                                                                                                                              
 
                                                              
         
                                              



                                 
                                     

                                                                                                            





 
                                                      
 
                                             







                                    
                                 





 
                                                                                             
 
                                                                     





 








                                                                                                           








                                                                                       








                                                               








                                                                                                                                                                                                                
                                                             
 
                                              




 







                                                                   
 
                                                                                   
 
                                                               





 








                                          
                                                                                 
 
                                                             





 
                                            
 
                                         





 
                                            
 





                                                                                                                                    





 
                                            
 
                                         





 

                                                             





                                                              
                                                             


                                                                                                 
                                              





 








                                                                                           








                                                                                    
                                     
 
                                  





 
                                        
 
                                     





 
                                                               
 
                                                





 


























                                                                                                                                  
                                                                                                                                   
 
                                                                                            





 
                                                                                                           
 
                                                                                        





 
                                                                               
 
                                                          





 
                                                        
 
                                        





 
                                                                                                                            
 
                                                                                          





 
                                                                                                                                                     
 
                                                                                 





 








                                                                             
                                                                
 
                                                 





 
                                                                             
 
                                                                  





 
                                                                       
 
                                                            





 
                                                               
 
                                                        




 
                                                                       
 
                                                         




 
 



                                                                                                          
 



                                                  





 
                                                                                                   
 
                                                                       




 
                                                   
 
                                           





 
                                                                
 
                                                 




 
 
                                                             
 
                                              





 
                                                            
 
                                             








                                                                                             





 








                                                      








                                                             



















                                                                      








                                                                                  

                                                                            




                                    









                                                                                                                                             




                                    











                                                                                                                                            
                                          
 
                                                                                                 
                                                                                                                         
                                      

                  
 
 


 
                                                      
 
                                                                                                                           

                       
                                                                        
                               
                  





 
                                                           
 

                                                                                                                                   

                  
 
 


 
                                                                    
 
                                                                                                       
                                  

                                              








                                                     



                                               

                                                      
         

                                                             
                                          












                                                    

                                                                                    
                                                    
         
                                                                                                      

         
                  




 
 








































































































































































































































































































































































































































































































































































































































































































































































































































































































                                                                                                                                                                                                                                    
 
#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/ChestEntity.h"
#include "BlockEntities/CommandBlockEntity.h"
#include "BlockEntities/SignEntity.h"
#include "UI/Window.h"
#include "Item.h"
#include "Piston.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 "FastRandom.h"

#include "md5/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

/** How many ticks before the socket is closed after the client is destroyed (#31) */
static const int TICKS_BEFORE_CLOSE = 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_ViewDistance(a_ViewDistance),
	m_IPString(a_Socket->GetIPString()),
	m_OutgoingData(64 KiB),
	m_Player(NULL),
	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_BlockDigAnimStage(-1),
	m_HasStartedDigging(false),
	m_TicksSinceDestruction(0),
	m_State(csConnected),
	m_ShouldCheckDownloaded(false),
	m_NumExplosionsThisTick(0),
	m_UniqueID(0),
	m_HasSentPlayerChunk(false),
	m_Locale("en_GB")
{
	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 >= csDestroyedWaiting);  // 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 != NULL)
	{
		cWorld * World = m_Player->GetWorld();
		if (!m_Username.empty() && (World != NULL))
		{
			// Send the Offline PlayerList packet:
			World->BroadcastPlayerListItem(*m_Player, false, this);
		}
		if (World != NULL)
		{
			World->RemovePlayer(m_Player);
			m_Player->Destroy();
		}
		delete m_Player;
		m_Player = NULL;
	}

	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 = NULL;
	
	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 != NULL) && (m_Player->GetWorld() != NULL))
	{
		RemoveFromAllChunks();
		m_Player->GetWorld()->RemoveClientFromChunkSender(this);
	}
	m_State = csDestroyedWaiting;
}





void cClientHandle::GenerateOfflineUUID(void)
{
	m_UUID = GenerateOfflineUUID(m_Username);
}





AString cClientHandle::GenerateOfflineUUID(const AString & a_Username)
{
	// 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
	
	// Generate an md5 checksum, and use it as base for the ID:
	MD5 Checksum(a_Username);
	AString UUID = Checksum.hexdigest();
	UUID[12] = '3';  // Version 3 UUID
	UUID[16] = '8';  // Variant 1 UUID
	
	// Now the digest doesn't have the UUID slashes, but the client requires them, so add them into the appropriate positions:
	UUID.insert(8, "-");
	UUID.insert(13, "-");
	UUID.insert(18, "-");
	UUID.insert(23, "-");
	
	return UUID;
}





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)
{
	if (m_State != csAuthenticating)
	{
		return;
	}
	
	ASSERT( m_Player == NULL );

	m_Username = a_Name;
	m_UUID = a_UUID;
	
	// 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 == NULL)
	{
		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());

	// Send contents of the inventory window
	m_Protocol->SendWholeInventory(*m_Player->GetWindow());

	// Send health
	m_Player->SendHealth();

	// Send experience
	m_Player->SendExperience();
	
	m_Player->Initialize(World);
	m_State = csAuthenticated;

	// Query player team
	m_Player->UpdateTeam();

	// Send scoreboard data
	World->GetScoreBoard().SendTo(*this);

	cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*m_Player);
}





void cClientHandle::StreamChunks(void)
{
	if ((m_State < csAuthenticated) || (m_State >= csDestroying))
	{
		return;
	}

	ASSERT(m_Player != NULL);
	
	int ChunkPosX = FAST_FLOOR_DIV((int)m_Player->GetPosX(), cChunkDef::Width);
	int ChunkPosZ = FAST_FLOOR_DIV((int)m_Player->GetPosZ(), cChunkDef::Width);
	if ((ChunkPosX == m_LastStreamedChunkX) && (ChunkPosZ == m_LastStreamedChunkZ))
	{
		// Already streamed for this position
		return;
	}
	m_LastStreamedChunkX = ChunkPosX;
	m_LastStreamedChunkZ = ChunkPosZ;
	
	LOGD("Streaming chunks centered on [%d, %d], view distance %d", ChunkPosX, ChunkPosZ, m_ViewDistance);
	
	cWorld * World = m_Player->GetWorld();
	ASSERT(World != NULL);

	// Remove all loaded chunks that are no longer in range; deferred to out-of-CS:
	cChunkCoordsList RemoveChunks;
	{
		cCSLock Lock(m_CSChunkLists);
		for (cChunkCoordsList::iterator itr = m_LoadedChunks.begin(); itr != m_LoadedChunks.end();)
		{
			int RelX = (*itr).m_ChunkX - ChunkPosX;
			int RelZ = (*itr).m_ChunkZ - ChunkPosZ;
			if ((RelX > m_ViewDistance) || (RelX < -m_ViewDistance) || (RelZ > m_ViewDistance) || (RelZ < -m_ViewDistance))
			{
				RemoveChunks.push_back(*itr);
				itr = m_LoadedChunks.erase(itr);
			}
			else
			{
				++itr;
			}
		}  // for itr - m_LoadedChunks[]
		for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end();)
		{
			int RelX = (*itr).m_ChunkX - ChunkPosX;
			int RelZ = (*itr).m_ChunkZ - ChunkPosZ;
			if ((RelX > m_ViewDistance) || (RelX < -m_ViewDistance) || (RelZ > m_ViewDistance) || (RelZ < -m_ViewDistance))
			{
				itr = m_ChunksToSend.erase(itr);
			}
			else
			{
				++itr;
			}
		}  // for itr - m_ChunksToSend[]
	}
	for (cChunkCoordsList::iterator itr = RemoveChunks.begin(); itr != RemoveChunks.end(); ++itr)
	{
		World->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this);
		m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ);
	}  // for itr - RemoveChunks[]
	
	// Add all chunks that are in range and not yet in m_LoadedChunks:
	// Queue these smartly - from the center out to the edge
	for (int d = 0; d <= m_ViewDistance; ++d)  // cycle through (square) distance, from nearest to furthest
	{
		// For each distance add chunks in a hollow square centered around current position:
		for (int i = -d; i <= d; ++i)
		{
			StreamChunk(ChunkPosX + d, ChunkPosZ + i);
			StreamChunk(ChunkPosX - d, ChunkPosZ + i);
		}  // for i
		for (int i = -d + 1; i < d; ++i)
		{
			StreamChunk(ChunkPosX + i, ChunkPosZ + d);
			StreamChunk(ChunkPosX + i, ChunkPosZ - d);
		}  // for i
	}  // for d
	
	// Touch chunks GENERATEDISTANCE ahead to let them generate:
	for (int d = m_ViewDistance + 1; d <= m_ViewDistance + GENERATEDISTANCE; ++d)  // cycle through (square) distance, from nearest to furthest
	{
		// For each distance touch chunks in a hollow square centered around current position:
		for (int i = -d; i <= d; ++i)
		{
			World->TouchChunk(ChunkPosX + d, ZERO_CHUNK_Y, ChunkPosZ + i);
			World->TouchChunk(ChunkPosX - d, ZERO_CHUNK_Y, ChunkPosZ + i);
		}  // for i
		for (int i = -d + 1; i < d; ++i)
		{
			World->TouchChunk(ChunkPosX + i, ZERO_CHUNK_Y, ChunkPosZ + d);
			World->TouchChunk(ChunkPosX + i, ZERO_CHUNK_Y, ChunkPosZ - d);
		}  // for i
	}  // for d
}




void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ)
{
	if (m_State >= csDestroying)
	{
		// Don't stream chunks to clients that are being destroyed
		return;
	}
	
	cWorld * World = m_Player->GetWorld();
	ASSERT(World != NULL);

	if (World->AddChunkClient(a_ChunkX, a_ChunkZ, this))
	{
		{
			cCSLock Lock(m_CSChunkLists);
			m_LoadedChunks.push_back(cChunkCoords(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ));
			m_ChunksToSend.push_back(cChunkCoords(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ));
		}
		World->SendChunkTo(a_ChunkX, a_ChunkZ, 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 != NULL)
	{
		World->RemoveClientFromChunks(this);
	}
	
	{
		cCSLock Lock(m_CSChunkLists);
		m_LoadedChunks.clear();
		m_ChunksToSend.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::HandlePing(void)
{
	// Somebody tries to retrieve information about the server
	AString Reply;
	Printf(Reply, "%s%s%i%s%i", 
		cRoot::Get()->GetServer()->GetDescription().c_str(),
		cChatColor::Delimiter.c_str(),
		cRoot::Get()->GetServer()->GetNumPlayers(),
		cChatColor::Delimiter.c_str(),
		cRoot::Get()->GetServer()->GetMaxPlayers()
	);
	Kick(Reply.c_str());
}





bool cClientHandle::HandleLogin(int a_ProtocolVersion, const AString & a_Username)
{
	LOGD("LOGIN %s", a_Username.c_str());
	m_Username = a_Username;

	if (cRoot::Get()->GetPluginManager()->CallHookLogin(this, a_ProtocolVersion, a_Username))
	{
		Destroy();
		return false;
	}

	// Schedule for authentication; until then, let them 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 == NULL) || (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->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 == "MC|AdvCdm")
	{
		// Command block, set text, Client -> Server
		HandleCommandBlockMessage(a_Message.c_str(), a_Message.size());
	}
	else if (a_Channel == "MC|Brand")
	{
		// Client <-> Server branding exchange
		SendPluginMessage("MC|Brand", "MCServer");
	}
	else 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::HandleCommandBlockMessage(const char * a_Data, unsigned int a_Length)
{
	if (a_Length < 14)
	{
		SendChat("Failure setting command block command; bad request", mtFailure);
		LOGD("Malformed MC|AdvCdm packet.");
		return;
	}

	cByteBuffer Buffer(a_Length);
	Buffer.Write(a_Data, a_Length);

	int BlockX, BlockY, BlockZ;

	AString Command;

	char Mode;

	Buffer.ReadChar(Mode);

	switch (Mode)
	{
		case 0x00:
		{
			Buffer.ReadBEInt(BlockX);
			Buffer.ReadBEInt(BlockY);
			Buffer.ReadBEInt(BlockZ);

			Buffer.ReadVarUTF8String(Command);
			break;
		}

		default:
		{
			SendChat("Failure setting command block command; unhandled mode", mtFailure);
			LOGD("Unhandled MC|AdvCdm packet mode.");
			return;
		}
	}

	cWorld * World = m_Player->GetWorld();

	if (World->AreCommandBlocksEnabled())
	{
		World->SetCommandBlockCommand(BlockX, BlockY, BlockZ, Command);
		
		SendChat("Successfully set command block command", mtSuccess);
	}
	else
	{
		SendChat("Command blocks are not enabled on this server", mtFailure);
	}
}





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;
	}

	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)
				return;
			}

			m_Player->TossEquippedItem();
			return;
		}

		case DIG_STATUS_SHOOT_EAT:
		{
			cItemHandler * ItemHandler = cItemHandler::GetItemHandler(m_Player->GetEquippedItem());
			if (ItemHandler->IsFood())
			{
				m_Player->AbortEating();
				return;
			}
			else
			{
				if (PlgMgr->CallHookPlayerShooting(*m_Player))
				{
					// A plugin doesn't agree with the action. The plugin itself is responsible for handling the consequences (possible inventory mismatch)
					return;
				}
				ItemHandler->OnItemShoot(m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
			}
			return;
		}
		
		case DIG_STATUS_STARTED:
		{
			BLOCKTYPE  OldBlock;
			NIBBLETYPE OldMeta;
			m_Player->GetWorld()->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, OldBlock, OldMeta);
			HandleBlockDigStarted(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, OldBlock, OldMeta);
			return;
		}
		
		case DIG_STATUS_FINISHED:
		{
			BLOCKTYPE  OldBlock;
			NIBBLETYPE OldMeta;
			m_Player->GetWorld()->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, OldBlock, OldMeta);
			HandleBlockDigFinished(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, OldBlock, OldMeta);
			return;
		}
		
		case DIG_STATUS_CANCELLED:
		{
			// Block breaking cancelled by player
			return;
		}

		case DIG_STATUS_DROP_STACK:
		{
			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)
				return;
			}
			m_Player->TossEquippedItem(64); // Toss entire slot - if there aren't enough items, the maximum will be ejected
			return;
		}

		default:
		{
			ASSERT(!"Unhandled DIG_STATUS");
			return;
		}
	}  // switch (a_Status)
}





void cClientHandle::HandleBlockDigStarted(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_OldBlock, NIBBLETYPE a_OldMeta)
{
	if (
		m_HasStartedDigging &&
		(a_BlockX == m_LastDigBlockX) &&
		(a_BlockY == m_LastDigBlockY) &&
		(a_BlockZ == m_LastDigBlockZ)
	)
	{
		// It is a duplicate packet, drop it right away
		return;
	}
	
	if (
		m_Player->IsGameModeCreative() &&
		ItemCategory::IsSword(m_Player->GetInventory().GetEquippedItem().m_ItemType)
	)
	{
		// Players can't destroy blocks with a Sword in the hand.
		return;
	}
	
	if (cRoot::Get()->GetPluginManager()->CallHookPlayerBreakingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_OldBlock, a_OldMeta))
	{
		// A plugin doesn't agree with the breaking. Bail out. Send the block back to the client, so that it knows:
		m_Player->GetWorld()->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
		return;
	}
	
	// Set the last digging coords to the block being dug, so that they can be checked in DIG_FINISHED to avoid dig/aim bug in the client:
	m_HasStartedDigging = true;
	m_LastDigBlockX = a_BlockX;
	m_LastDigBlockY = a_BlockY;
	m_LastDigBlockZ = a_BlockZ;

	if (
		(m_Player->IsGameModeCreative()) ||  // In creative mode, digging is done immediately
		cBlockInfo::IsOneHitDig(a_OldBlock)  // One-hit blocks get destroyed immediately, too
	)
	{
		HandleBlockDigFinished(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_OldBlock, a_OldMeta);
		return;
	}

	// Start dig animation
	// TODO: calculate real animation speed
	// TODO: Send animation packets even without receiving any other packets
	m_BlockDigAnimSpeed = 10;
	m_BlockDigAnimX = a_BlockX;
	m_BlockDigAnimY = a_BlockY;
	m_BlockDigAnimZ = a_BlockZ;
	m_BlockDigAnimStage = 0;
	m_Player->GetWorld()->BroadcastBlockBreakAnimation(m_UniqueID, m_BlockDigAnimX, m_BlockDigAnimY, m_BlockDigAnimZ, 0, this);

	cWorld * World = m_Player->GetWorld();
	cChunkInterface ChunkInterface(World->GetChunkMap());
	cBlockHandler * Handler = cBlockInfo::GetHandler(a_OldBlock);
	Handler->OnDigging(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ);

	cItemHandler * ItemHandler = cItemHandler::GetItemHandler(m_Player->GetEquippedItem());
	ItemHandler->OnDiggingBlock(World, m_Player, m_Player->GetEquippedItem(), a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);

	// Check for clickthrough-blocks:
	if (a_BlockFace != BLOCK_FACE_NONE)
	{
		int pX = a_BlockX;
		int pY = a_BlockY;
		int pZ = a_BlockZ;

		AddFaceDirection(pX, pY, pZ, a_BlockFace); // Get the block in front of the clicked coordinates (m_bInverse defaulted to false)
		Handler = cBlockInfo::GetHandler(World->GetBlock(pX, pY, pZ));

		if (Handler->IsClickedThrough())
		{
			Handler->OnDigging(ChunkInterface, *World, m_Player, pX, pY, pZ);
		}
	}
}





void cClientHandle::HandleBlockDigFinished(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_OldBlock, NIBBLETYPE a_OldMeta)
{
	if (
		!m_HasStartedDigging ||           // Hasn't received the DIG_STARTED packet
		(m_LastDigBlockX != a_BlockX) ||  // DIG_STARTED has had different pos
		(m_LastDigBlockY != a_BlockY) ||
		(m_LastDigBlockZ != a_BlockZ)
	)
	{
		LOGD("Prevented a dig/aim bug in the client (finish {%d, %d, %d} vs start {%d, %d, %d}, HSD: %s)",
			a_BlockX, a_BlockY, a_BlockZ,
			m_LastDigBlockX, m_LastDigBlockY, m_LastDigBlockZ,
			(m_HasStartedDigging ? "True" : "False")
		);
		return;
	}

	m_HasStartedDigging = false;
	if (m_BlockDigAnimStage != -1)
	{
		// End dig animation
		m_BlockDigAnimStage = -1;
		// It seems that 10 ends block animation
		m_Player->GetWorld()->BroadcastBlockBreakAnimation(m_UniqueID, m_BlockDigAnimX, m_BlockDigAnimY, m_BlockDigAnimZ, 10, this);
	}

	cItemHandler * ItemHandler = cItemHandler::GetItemHandler(m_Player->GetEquippedItem());
	
	if (a_OldBlock == E_BLOCK_AIR)
	{
		LOGD("Dug air - what the function?");
		return;
	}
	
	cWorld * World = m_Player->GetWorld();
	ItemHandler->OnBlockDestroyed(World, m_Player, m_Player->GetEquippedItem(), a_BlockX, a_BlockY, a_BlockZ);
	// The ItemHandler is also responsible for spawning the pickups
	cChunkInterface ChunkInterface(World->GetChunkMap());
	BlockHandler(a_OldBlock)->OnDestroyedByPlayer(ChunkInterface,*World, m_Player, a_BlockX, a_BlockY, a_BlockZ);
	World->BroadcastSoundParticleEffect(2001, a_BlockX, a_BlockY, a_BlockZ, a_OldBlock, this);
	World->DigBlock(a_BlockX, a_BlockY, a_BlockZ);

	cRoot::Get()->GetPluginManager()->CallHookPlayerBrokenBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_OldBlock, a_OldMeta);
}





void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, const cItem & a_HeldItem)
{
	LOGD("HandleRightClick: {%d, %d, %d}, face %d, HeldItem: %s",
		a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, ItemToFullString(a_HeldItem).c_str()
	);
	
	cWorld * World = m_Player->GetWorld();
	
	cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager();
	if (PlgMgr->CallHookPlayerRightClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
	{
		// A plugin doesn't agree with the action, replace the block on the client and quit:
		cChunkInterface ChunkInterface(World->GetChunkMap());
		BLOCKTYPE BlockType = World->GetBlock(a_BlockX, a_BlockY, a_BlockZ);
		cBlockHandler * BlockHandler = cBlockInfo::GetHandler(BlockType);
		BlockHandler->OnCancelRightClick(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
		
		if (a_BlockFace != BLOCK_FACE_NONE)
		{
			AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
			World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
			World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, m_Player); //2 block high things
		}
		return;
	}

	m_NumBlockChangeInteractionsThisTick++;
	
	if (!CheckBlockInteractionsRate())
	{
		Kick("Too many blocks were placed/interacted with per unit time - hacked client?");
		return;
	}
	
	const cItem & Equipped = m_Player->GetInventory().GetEquippedItem();

	if ((Equipped.m_ItemType != a_HeldItem.m_ItemType) && (a_HeldItem.m_ItemType != -1))
	{
		// Only compare ItemType, not meta (torches have different metas)
		// The -1 check is there because sometimes the client sends -1 instead of the held item 
		//  ( http://forum.mc-server.org/showthread.php?tid=549&pid=4502#pid4502 )
		LOGWARN("Player %s tried to place a block that was not equipped (exp %d, got %d)",
			m_Username.c_str(), Equipped.m_ItemType, a_HeldItem.m_ItemType
		);
		
		// Let's send the current world block to the client, so that it can immediately "let the user know" that they haven't placed the block
		if (a_BlockFace != BLOCK_FACE_NONE)
		{
			AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
			World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
		}
		return;
	}

	BLOCKTYPE BlockType;
	NIBBLETYPE BlockMeta;
	World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
	cBlockHandler * BlockHandler = cBlockInfo::GetHandler(BlockType);
	
	if (BlockHandler->IsUseable() && !m_Player->IsCrouched())
	{
		if (PlgMgr->CallHookPlayerUsingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta))
		{
			// A plugin doesn't agree with using the block, abort
			return;
		}
		cChunkInterface ChunkInterface(World->GetChunkMap());
		BlockHandler->OnUse(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ);
		PlgMgr->CallHookPlayerUsedBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta);
		return;
	}
	
	cItemHandler * ItemHandler = cItemHandler::GetItemHandler(Equipped.m_ItemType);
	
	if (ItemHandler->IsPlaceable() && (a_BlockFace != BLOCK_FACE_NONE))
	{
		HandlePlaceBlock(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, *ItemHandler);
	}
	else if (ItemHandler->IsFood())
	{
		if (m_Player->IsSatiated())
		{
			// The player is satiated, they cannot eat
			return;
		}
		m_Player->StartEating();
		if (PlgMgr->CallHookPlayerEating(*m_Player))
		{
			// A plugin won't let us eat, abort (send the proper packets to the client, too):
			m_Player->AbortEating();
			return;
		}
		return;
	}
	else
	{
		if (PlgMgr->CallHookPlayerUsingItem(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
		{
			// A plugin doesn't agree with using the item, abort
			return;
		}
		ItemHandler->OnItemUse(World, m_Player, Equipped, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
		PlgMgr->CallHookPlayerUsedItem(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ);
	}
}





void cClientHandle::HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, cItemHandler & a_ItemHandler)
{
	BLOCKTYPE EquippedBlock = (BLOCKTYPE)(m_Player->GetEquippedItem().m_ItemType);
	if (a_BlockFace < 0)
	{
		// Clicked in air
		return;
	}
	
	cWorld * World = m_Player->GetWorld();

	BLOCKTYPE ClickedBlock;
	NIBBLETYPE ClickedBlockMeta;
	NIBBLETYPE EquippedBlockDamage = (NIBBLETYPE)(m_Player->GetEquippedItem().m_ItemDamage);

	if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
	{
		// The block is being placed outside the world, ignore this packet altogether (#128)
		return;
	}
	
	World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta);

	// Special slab handling - placing a slab onto another slab produces a dblslab instead:
	if (
		cBlockSlabHandler::IsAnySlabType(ClickedBlock) &&               // Is there a slab already?
		cBlockSlabHandler::IsAnySlabType(EquippedBlock) &&              // Is the player placing another slab?
		((ClickedBlockMeta & 0x07) == EquippedBlockDamage) &&           // Is it the same slab type?
		(
			(a_BlockFace == BLOCK_FACE_TOP) ||                          // Clicking the top of a bottom slab
			(a_BlockFace == BLOCK_FACE_BOTTOM)                          // Clicking the bottom of a top slab
		)
	)
	{
		// Coordinates at clicked block, which was an eligible slab, and either top or bottom faces were clicked
		// If clicked top face and slab occupies the top voxel, we want a slab to be placed above it (therefore increment Y)
		// Else if clicked bottom face and slab occupies the bottom voxel, decrement Y for the same reason
		// Don't touch coordinates if anything else because a dblslab opportunity is present
		if ((ClickedBlockMeta & 0x08) && (a_BlockFace == BLOCK_FACE_TOP))
		{
			++a_BlockY;
		}
		else if (!(ClickedBlockMeta & 0x08) && (a_BlockFace == BLOCK_FACE_BOTTOM))
		{
			--a_BlockY;
		}
		World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, ClickedBlock, ClickedBlockMeta);
	}
	else
	{
		// Check if the block ignores build collision (water, grass etc.):
		if (
			BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision() ||
			BlockHandler(ClickedBlock)->DoesIgnoreBuildCollision(m_Player, ClickedBlockMeta)
			)
		{
			cChunkInterface ChunkInterface(World->GetChunkMap());
			BlockHandler(ClickedBlock)->OnDestroyedByPlayer(ChunkInterface, *World, m_Player, a_BlockX, a_BlockY, a_BlockZ);
		}
		else
		{
			AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
			
			if ((a_BlockY < 0) || (a_BlockY >= cChunkDef::Height))
			{
				// The block is being placed outside the world, ignore this packet altogether (#128)
				return;
			}
			
			NIBBLETYPE PlaceMeta;
			BLOCKTYPE PlaceBlock;
			World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, PlaceBlock, PlaceMeta);

			// Clicked on side of block, make sure that placement won't be cancelled if there is a slab able to be double slabbed.
			// No need to do combinability (dblslab) checks, client will do that here.
			if (cBlockSlabHandler::IsAnySlabType(PlaceBlock))
			{
				// It's a slab, don't do checks and proceed to double-slabbing
			}
			else
			{
				if (
					!BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision() &&
					!BlockHandler(PlaceBlock)->DoesIgnoreBuildCollision(m_Player, PlaceMeta)
					)
				{
					// Tried to place a block *into* another?
					// Happens when you place a block aiming at side of block with a torch on it or stem beside it
					return;
				}
			}
		}
	}
	
	BLOCKTYPE BlockType;
	NIBBLETYPE BlockMeta;
	if (!a_ItemHandler.GetPlacementBlockTypeMeta(World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta))
	{
		// Handler refused the placement, send that information back to the client:
		World->SendBlockTo(a_BlockX, a_BlockY, a_BlockY, m_Player);
		return;
	}
	
	cBlockHandler * NewBlock = BlockHandler(BlockType);

	if (cRoot::Get()->GetPluginManager()->CallHookPlayerPlacingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta))
	{
		// A plugin doesn't agree with placing the block, revert the block on the client:
		World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, m_Player);
		return;
	}
	
	// The actual block placement:
	World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
	if (!m_Player->IsGameModeCreative())
	{
		m_Player->GetInventory().RemoveOneEquippedItem();
	}
	cChunkInterface ChunkInterface(World->GetChunkMap());
	NewBlock->OnPlacedByPlayer(ChunkInterface,*World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta);
	
	// Step sound with 0.8f pitch is used as block placement sound
	World->BroadcastSoundEffect(NewBlock->GetStepSound(), a_BlockX * 8, a_BlockY * 8, a_BlockZ * 8, 1.0f, 0.8f);
	cRoot::Get()->GetPluginManager()->CallHookPlayerPlacedBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta);
}





void cClientHandle::HandleChat(const AString & a_Message)
{
	// We no longer need to postpone message processing, because the messages already arrive in the Tick thread
	
	// If a command, perform it:
	AString Message(a_Message);
	if (cRoot::Get()->GetServer()->Command(*this, Message))
	{
		return;
	}
	
	// Not a command, broadcast as a message:
	cCompositeChat Msg;
	AString Color = m_Player->GetColor();
	if (Color.length() == 3)
	{
		Color = AString("@") + Color[2];
	}
	else
	{
		Color.empty();
	}
	Msg.AddTextPart(AString("<") + m_Player->GetName() + "> ", Color);
	Msg.ParseText(a_Message);
	Msg.UnderlineUrls();
	m_Player->GetWorld()->BroadcastChat(Msg);
}





void cClientHandle::HandlePlayerLook(float a_Rotation, float a_Pitch, bool a_IsOnGround)
{
	if ((m_Player == NULL) || (m_State != csPlaying))
	{
		return;
	}
	
	m_Player->SetYaw        (a_Rotation);
	m_Player->SetHeadYaw    (a_Rotation);
	m_Player->SetPitch      (a_Pitch);
	m_Player->SetTouchGround(a_IsOnGround);
}





void cClientHandle::HandlePlayerMoveLook(double a_PosX, double a_PosY, double a_PosZ, double a_Stance, float a_Rotation, float a_Pitch, bool a_IsOnGround)
{
	if ((m_Player == NULL) || (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;
	}
	*/
	
	m_Player->MoveTo(Vector3d(a_PosX, a_PosY, a_PosZ));
	m_Player->SetStance     (a_Stance);
	m_Player->SetTouchGround(a_IsOnGround);
	m_Player->SetHeadYaw    (a_Rotation);
	m_Player->SetYaw        (a_Rotation);
	m_Player->SetPitch      (a_Pitch);
}





void cClientHandle::HandleAnimation(char a_Animation)
{
	if (cPluginManager::Get()->CallHookPlayerAnimation(*m_Player, a_Animation))
	{
		// Plugin disagrees, bail out
		return;
	}

	// Because the animation ID sent to servers by clients are different to those sent back, we need this
	switch (a_Animation)
	{
		case 0: // No animation - wiki.vg doesn't say that client has something specific for it, so I suppose it will just become -1
		case 1:
		case 2:
		case 3:
		{
			a_Animation--; // Offset by -1
			break;
		}
		case 5: 
		case 6:
		case 7:
		{
			a_Animation -= 2; // Offset by -2
			break;
		}
		default: // Anything else is the same
			break;
	}

	m_Player->GetWorld()->BroadcastEntityAnimation(*m_Player, a_Animation, this);
}





void cClientHandle::HandleSlotSelected(short a_SlotNum)
{
	m_Player->GetInventory().SetEquippedSlotNum(a_SlotNum);
	m_Player->GetWorld()->BroadcastEntityEquipment(*m_Player, 0, m_Player->GetInventory().GetEquippedItem(), this);
}





void cClientHandle::HandleSteerVehicle(float a_Forward, float a_Sideways)
{
	m_Player->SteerVehicle(a_Forward, a_Sideways);
}





void cClientHandle::HandleWindowClose(char a_WindowID)
{
	m_Player->CloseWindowIfID(a_WindowID);
}





void cClientHandle::HandleWindowClick(char a_WindowID, short a_SlotNum, eClickAction a_ClickAction, const cItem & a_HeldItem)
{
	LOGD("WindowClick: WinID %d, SlotNum %d, action: %s, Item %s x %d",
		a_WindowID, a_SlotNum, ClickActionToString(a_ClickAction),
		ItemToString(a_HeldItem).c_str(), a_HeldItem.m_ItemCount
	);
	
	cWindow * Window = m_Player->GetWindow();
	if (Window == NULL)
	{
		LOGWARNING("Player \"%s\" clicked in a non-existent window. Ignoring", m_Username.c_str());
		return;
	}
	
	Window->Clicked(*m_Player, a_WindowID, a_SlotNum, a_ClickAction, a_HeldItem);
}





void cClientHandle::HandleUpdateSign(
	int a_BlockX, int a_BlockY, int a_BlockZ, 
	const AString & a_Line1, const AString & a_Line2, 
	const AString & a_Line3, const AString & a_Line4
)
{
	cWorld * World = m_Player->GetWorld();
	World->UpdateSign(a_BlockX, a_BlockY, a_BlockZ, a_Line1, a_Line2, a_Line3, a_Line4, m_Player);
}





void cClientHandle::HandleUseEntity(int a_TargetEntityID, bool a_IsLeftClick)
{
	// TODO: Let plugins interfere via a hook
	
	// If it is a right click, call the entity's OnRightClicked() handler:
	if (!a_IsLeftClick)
	{
		class cRclkEntity : public cEntityCallback
		{
			cPlayer & m_Player;
			virtual bool Item(cEntity * a_Entity) override
			{
				if (cPluginManager::Get()->CallHookPlayerRightClickingEntity(m_Player, *a_Entity))
				{
					return false;
				}
				a_Entity->OnRightClicked(m_Player);
				return false;
			}
		public:
			cRclkEntity(cPlayer & a_Player) : m_Player(a_Player) {}
		} Callback (*m_Player);
		
		cWorld * World = m_Player->GetWorld();
		World->DoWithEntityByID(a_TargetEntityID, Callback);
		return;
	}

	// If it is a left click, attack the entity:
	class cDamageEntity : public cEntityCallback
	{
		virtual bool Item(cEntity * a_Entity) override
		{
			if (!a_Entity->GetWorld()->IsPVPEnabled())
			{
				// PVP is disabled, disallow players hurting other players:
				if (a_Entity->IsPlayer())
				{
					// Player is hurting another player which is not allowed when PVP is disabled so ignore it
					return true;
				}
			}
			a_Entity->TakeDamage(*m_Attacker);
			return false;
		}
	public:
		cPawn * m_Attacker;
	} Callback;

	Callback.m_Attacker = m_Player;

	cWorld * World = m_Player->GetWorld();
	if (World->DoWithEntityByID(a_TargetEntityID, Callback))
	{
		// Any kind of an attack implies food exhaustion
		m_Player->AddFoodExhaustion(0.3);
	}
}





void cClientHandle::HandleRespawn(void)
{
	if (m_Player == NULL)
	{
		Destroy();
		return;
	}
	m_Player->Respawn();
	cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*m_Player);
}





void cClientHandle::HandleDisconnect(const AString & a_Reason)
{
	LOGD("Received d/c packet from %s with reason \"%s\"", m_Username.c_str(), a_Reason.c_str());

	cRoot::Get()->GetPluginManager()->CallHookDisconnect(m_Player, a_Reason);

	m_HasSentDC = true;
	Destroy();
}





void cClientHandle::HandleKeepAlive(int a_KeepAliveID)
{
	if (a_KeepAliveID == m_PingID)
	{
		cTimer t1;
		m_Ping = (short)((t1.GetNowTime() - m_PingStartTime) / 2);
	}
}





bool cClientHandle::HandleHandshake(const AString & a_Username)
{
	if (!cRoot::Get()->GetPluginManager()->CallHookHandshake(this, a_Username))
	{
		if (cRoot::Get()->GetServer()->GetNumPlayers() >= cRoot::Get()->GetServer()->GetMaxPlayers())
		{
			Kick("The server is currently full :(-- Try again later");
			return false;
		}
	}
	return true;
}





void cClientHandle::HandleEntityCrouch(int a_EntityID, bool a_IsCrouching)
{
	if (a_EntityID != m_Player->GetUniqueID())
	{
		// We should only receive entity actions from the entity that is performing the action
		return;
	}

	m_Player->SetCrouch(a_IsCrouching);
}





void cClientHandle::HandleEntityLeaveBed(int a_EntityID)
{
	if (a_EntityID != m_Player->GetUniqueID())
	{
		// We should only receive entity actions from the entity that is performing the action
		return;
	}

	m_Player->GetWorld()->BroadcastEntityAnimation(*m_Player, 2);
}





void cClientHandle::HandleEntitySprinting(int a_EntityID, bool a_IsSprinting)
{
	if (a_EntityID != m_Player->GetUniqueID())
	{
		// We should only receive entity actions from the entity that is performing the action
		return;
	}

	m_Player->SetSprint(a_IsSprinting);
}





void cClientHandle::HandleUnmount(void)
{
	if (m_Player == NULL)
	{
		return;
	}
	m_Player->Detach();
}





void cClientHandle::HandleTabCompletion(const AString & a_Text)
{
	AStringVector Results;
	m_Player->GetWorld()->TabCompleteUserName(a_Text, Results);
	cRoot::Get()->GetPluginManager()->TabCompleteCommand(a_Text, Results, m_Player);
	if (Results.empty())
	{
		return;
	}
	std::sort(Results.begin(), Results.end());
	SendTabCompletionResults(Results);
}





void cClientHandle::SendData(const char * a_Data, size_t a_Size)
{
	if (m_HasSentDC)
	{
		// This could crash the client, because they've already unloaded the world etc., and suddenly a wild packet appears (#31)
		return;
	}
	
	{
		cCSLock Lock(m_CSOutgoingData);
		
		// _X 2012_09_06: We need an overflow buffer, usually when streaming the initial chunks
		if (m_OutgoingDataOverflow.empty())
		{
			// No queued overflow data; if this packet fits into the ringbuffer, put it in, otherwise put it in the overflow buffer:
			size_t CanFit = m_OutgoingData.GetFreeSpace();
			if (CanFit > a_Size)
			{
				CanFit = a_Size;
			}
			if (CanFit > 0)
			{
				m_OutgoingData.Write(a_Data, CanFit);
			}
			if (a_Size > CanFit)
			{
				m_OutgoingDataOverflow.append(a_Data + CanFit, a_Size - CanFit);
			}
		}
		else
		{
			// There is a queued overflow. Append to it, then send as much from its front as possible
			m_OutgoingDataOverflow.append(a_Data, a_Size);
			int CanFit = m_OutgoingData.GetFreeSpace();
			if (CanFit > 128)
			{
				// No point in moving the data over if it's not large enough - too much effort for too little an effect
				m_OutgoingData.Write(m_OutgoingDataOverflow.data(), CanFit);
				m_OutgoingDataOverflow.erase(0, CanFit);
			}
		}
	}  // Lock(m_CSOutgoingData)
	
	// Notify SocketThreads that we have something to write:
	cRoot::Get()->GetServer()->NotifyClientWrite(this);
}





void cClientHandle::MoveToWorld(cWorld & a_World, bool a_SendRespawnPacket)
{
	UNUSED(a_World);
	ASSERT(m_Player != NULL);
	
	if (a_SendRespawnPacket)
	{
		SendRespawn();
	}

	cWorld * World = m_Player->GetWorld();
		
	// Remove all associated chunks:
	cChunkCoordsList Chunks;
	{
		cCSLock Lock(m_CSChunkLists);
		std::swap(Chunks, m_LoadedChunks);
		m_ChunksToSend.clear();
	}
	for (cChunkCoordsList::iterator itr = Chunks.begin(), end = Chunks.end(); itr != end; ++itr)
	{
		World->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this);
		m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ);
	}  // for itr - Chunks[]
	
	// StreamChunks() called in cPlayer::MoveToWorld() after new world has been set
	// Meanwhile here, we set last streamed values to bogus ones so everything is resent
	m_LastStreamedChunkX = 0x7fffffff;
	m_LastStreamedChunkZ = 0x7fffffff;
	m_HasSentPlayerChunk = false;
}





bool cClientHandle::CheckBlockInteractionsRate(void)
{
	ASSERT(m_Player != NULL);
	ASSERT(m_Player->GetWorld() != NULL);

	if (m_NumBlockChangeInteractionsThisTick > MAX_BLOCK_CHANGE_INTERACTIONS)
	{
		return false;
	}

	return true;
}





void cClientHandle::Tick(float a_Dt)
{
	// Handle clients that are waiting for final close while destroyed:
	if (m_State == csDestroyedWaiting)
	{
		m_TicksSinceDestruction += 1;  // This field is misused for the timeout counting
		if (m_TicksSinceDestruction > TICKS_BEFORE_CLOSE)
		{
			m_State = csDestroyed;
		}
		return;
	}
	
	// Process received network data:
	AString IncomingData;
	{
		cCSLock Lock(m_CSIncomingData);
		std::swap(IncomingData, m_IncomingData);
	}
	m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
	
	m_TimeSinceLastPacket += a_Dt;
	if (m_TimeSinceLastPacket > 30000.f)  // 30 seconds time-out
	{
		SendDisconnect("Nooooo!! You timed out! D: Come back!");
		Destroy();
	}
	
	if (m_Player == NULL)
	{
		return;
	}
	
	// If the chunk the player's in was just sent, spawn the player:
	if (m_HasSentPlayerChunk && (m_State == csDownloadingWorld))
	{
		m_Protocol->SendPlayerMoveLook();
		m_State = csPlaying;
	}

	// Send a ping packet:
	if (m_State == csPlaying)
	{
		cTimer t1;
		if ((m_LastPingTime + cClientHandle::PING_TIME_MS <= t1.GetNowTime()))
		{
			m_PingID++;
			m_PingStartTime = t1.GetNowTime();
			m_Protocol->SendKeepAlive(m_PingID);
			m_LastPingTime = m_PingStartTime;
		}
	}

	// Handle block break animation:
	if (m_BlockDigAnimStage > -1)
	{
		int lastAnimVal = m_BlockDigAnimStage;
		m_BlockDigAnimStage += (int)(m_BlockDigAnimSpeed * a_Dt);
		if (m_BlockDigAnimStage > 9000)
		{
			m_BlockDigAnimStage = 9000;
		}
		if (m_BlockDigAnimStage / 1000 != lastAnimVal / 1000)
		{
			m_Player->GetWorld()->BroadcastBlockBreakAnimation(m_UniqueID, m_BlockDigAnimX, m_BlockDigAnimY, m_BlockDigAnimZ, (char)(m_BlockDigAnimStage / 1000), this);
		}
	}
	
	// Reset explosion & block change counters:
	m_NumExplosionsThisTick = 0;
	m_NumBlockChangeInteractionsThisTick = 0;
}





void cClientHandle::ServerTick(float a_Dt)
{
	// Handle clients that are waiting for final close while destroyed:
	if (m_State == csDestroyedWaiting)
	{
		// Do not wait while the client is not in the world, simply cut them off.
		m_State = csDestroyed;
		return;
	}
	
	// Process received network data:
	AString IncomingData;
	{
		cCSLock Lock(m_CSIncomingData);
		std::swap(IncomingData, m_IncomingData);
	}
	m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
	
	if (m_State == csAuthenticated)
	{
		StreamChunks();

		// Remove the client handle from the server, it will be ticked from its cPlayer object from now on
		cRoot::Get()->GetServer()->ClientMovedToWorld(this);
		
		// Add the player to the world (start ticking from there):
		m_State = csDownloadingWorld;
		m_Player->GetWorld()->AddPlayer(m_Player);
		return;
	}
	
	m_TimeSinceLastPacket += a_Dt;
	if (m_TimeSinceLastPacket > 30000.f)  // 30 seconds time-out
	{
		SendDisconnect("Nooooo!! You timed out! D: Come back!");
		Destroy();
	}
}





void cClientHandle::SendAttachEntity(const cEntity & a_Entity, const cEntity * a_Vehicle)
{
	m_Protocol->SendAttachEntity(a_Entity, a_Vehicle);
}





void cClientHandle::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType)
{
	m_Protocol->SendBlockAction(a_BlockX, a_BlockY, a_BlockZ, a_Byte1, a_Byte2, a_BlockType);
}





void cClientHandle::SendBlockBreakAnim(int a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage)
{
	m_Protocol->SendBlockBreakAnim(a_EntityID, a_BlockX, a_BlockY, a_BlockZ, a_Stage);
}





void cClientHandle::SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
	m_Protocol->SendBlockChange(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta);
}





void cClientHandle::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes)
{
	ASSERT(!a_Changes.empty());  // We don't want to be sending empty change packets!
	
	m_Protocol->SendBlockChanges(a_ChunkX, a_ChunkZ, a_Changes);
}





void cClientHandle::SendChat(const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData)
{
	bool ShouldAppendChatPrefixes = true;

	if (GetPlayer()->GetWorld() == NULL)
	{
		cWorld * World = cRoot::Get()->GetWorld(GetPlayer()->GetLoadedWorldName());
		if (World == NULL)
		{
			World = cRoot::Get()->GetDefaultWorld();
		}

		if (!World->ShouldUseChatPrefixes())
		{
			ShouldAppendChatPrefixes = false;
		}
	}
	else if (!GetPlayer()->GetWorld()->ShouldUseChatPrefixes())
	{
		ShouldAppendChatPrefixes = false;
	}

	AString Message;

	switch (a_ChatPrefix)
	{
		case mtCustom: break;
		case mtFailure:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[INFO] %s", cChatColor::Rose.c_str(), cChatColor::White.c_str());
			else
				Message = Printf("%s", cChatColor::Rose.c_str());
			break;
		}
		case mtInformation:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[INFO] %s", cChatColor::Yellow.c_str(), cChatColor::White.c_str());
			else
				Message = Printf("%s", cChatColor::Yellow.c_str());
			break;
		}
		case mtSuccess:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[INFO] %s", cChatColor::Green.c_str(), cChatColor::White.c_str());
			else
				Message = Printf("%s", cChatColor::Green.c_str());
			break;
		}
		case mtWarning:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[WARN] %s", cChatColor::Rose.c_str(), cChatColor::White.c_str());
			else
				Message = Printf("%s", cChatColor::Rose.c_str());
			break;
		}
		case mtFatal:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[FATAL] %s", cChatColor::Red.c_str(), cChatColor::White.c_str());
			else
				Message = Printf("%s", cChatColor::Red.c_str());
			break;
		}
		case mtDeath:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[DEATH] %s", cChatColor::Gray.c_str(), cChatColor::White.c_str());
			else
				Message = Printf("%s", cChatColor::Gray.c_str());
			break;
		}
		case mtPrivateMessage:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[MSG: %s] %s%s", cChatColor::LightBlue.c_str(), a_AdditionalData.c_str(), cChatColor::White.c_str(), cChatColor::Italic.c_str());
			else
				Message = Printf("%s: %s", a_AdditionalData.c_str(), cChatColor::LightBlue.c_str());
			break;
		}
		case mtJoin:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[JOIN] %s", cChatColor::Yellow.c_str(), cChatColor::White.c_str());
			else
				Message = Printf("%s", cChatColor::Yellow.c_str());
			break;
		}
		case mtLeave:
		{
			if (ShouldAppendChatPrefixes)
				Message = Printf("%s[LEAVE] %s", cChatColor::Yellow.c_str(), cChatColor::White.c_str());
			else
				Message = Printf("%s", cChatColor::Yellow.c_str());
			break;
		}
		default: ASSERT(!"Unhandled chat prefix type!"); return;
	}

	Message.append(a_Message);

	m_Protocol->SendChat(Message);
}





void cClientHandle::SendChat(const cCompositeChat & a_Message)
{
	m_Protocol->SendChat(a_Message);
}





void cClientHandle::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer)
{
	ASSERT(m_Player != NULL);
	
	// Check chunks being sent, erase them from m_ChunksToSend:
	bool Found = false;
	{
		cCSLock Lock(m_CSChunkLists);
		for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end(); ++itr)
		{
			if ((itr->m_ChunkX == a_ChunkX) && (itr->m_ChunkZ == a_ChunkZ))
			{
				m_ChunksToSend.erase(itr);
				Found = true;
				break;
			}
		}  // for itr - m_ChunksToSend[]
	}
	if (!Found)
	{
		// This just sometimes happens. If you have a reliably replicatable situation for this, go ahead and fix it
		// It's not a big issue anyway, just means that some chunks may be compressed several times
		// LOGD("Refusing to send    chunk [%d, %d] to client \"%s\" at [%d, %d].", ChunkX, ChunkZ, m_Username.c_str(), m_Player->GetChunkX(), m_Player->GetChunkZ());
		return;
	}
	
	m_Protocol->SendChunkData(a_ChunkX, a_ChunkZ, a_Serializer);

	// If it is the chunk the player's in, make them spawn (in the tick thread):
	if ((m_State == csAuthenticated) || (m_State == csDownloadingWorld))
	{
		if ((a_ChunkX == m_Player->GetChunkX()) && (a_ChunkZ == m_Player->GetChunkZ()))
		{
			m_HasSentPlayerChunk = true;
		}
	}
}





void cClientHandle::SendCollectPickup(const cPickup & a_Pickup, const cPlayer & a_Player)
{
	m_Protocol->SendCollectPickup(a_Pickup, a_Player);
}





void cClientHandle::SendDestroyEntity(const cEntity & a_Entity)
{
	m_Protocol->SendDestroyEntity(a_Entity);
}





void cClientHandle::SendDisconnect(const AString & a_Reason)
{
	if (!m_HasSentDC)
	{
		LOGD("Sending a DC: \"%s\"", StripColorCodes(a_Reason).c_str());
		m_Protocol->SendDisconnect(a_Reason);
		m_HasSentDC = true;
	}
}





void cClientHandle::SendEditSign(int a_BlockX, int a_BlockY, int a_BlockZ)
{
	m_Protocol->SendEditSign(a_BlockX, a_BlockY, a_BlockZ);
}





void cClientHandle::SendEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration)
{
	m_Protocol->SendEntityEffect(a_Entity, a_EffectID, a_Amplifier, a_Duration);
}





void cClientHandle::SendEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item)
{
	m_Protocol->SendEntityEquipment(a_Entity, a_SlotNum, a_Item);
}





void cClientHandle::SendEntityHeadLook(const cEntity & a_Entity)
{
	ASSERT(a_Entity.GetUniqueID() != m_Player->GetUniqueID());  // Must not send for self
	
	m_Protocol->SendEntityHeadLook(a_Entity);
}





void cClientHandle::SendEntityLook(const cEntity & a_Entity)
{
	ASSERT(a_Entity.GetUniqueID() != m_Player->GetUniqueID());  // Must not send for self
	
	m_Protocol->SendEntityLook(a_Entity);
}





void cClientHandle::SendEntityMetadata(const cEntity & a_Entity)
{
	m_Protocol->SendEntityMetadata(a_Entity);
}





void cClientHandle::SendEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ)
{
	ASSERT(a_Entity.GetUniqueID() != m_Player->GetUniqueID());  // Must not send for self
	
	m_Protocol->SendEntityRelMove(a_Entity, a_RelX, a_RelY, a_RelZ);
}





void cClientHandle::SendEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ)
{
	ASSERT(a_Entity.GetUniqueID() != m_Player->GetUniqueID());  // Must not send for self
	
	m_Protocol->SendEntityRelMoveLook(a_Entity, a_RelX, a_RelY, a_RelZ);
}





void cClientHandle::SendEntityStatus(const cEntity & a_Entity, char a_Status)
{
	m_Protocol->SendEntityStatus(a_Entity, a_Status);
}





void cClientHandle::SendEntityVelocity(const cEntity & a_Entity)
{
	m_Protocol->SendEntityVelocity(a_Entity);
}





void cClientHandle::SendExplosion(double a_BlockX, double a_BlockY, double a_BlockZ, float a_Radius, const cVector3iArray & a_BlocksAffected, const Vector3d & a_PlayerMotion)
{
	if (m_NumExplosionsThisTick > MAX_EXPLOSIONS_PER_TICK)
	{
		LOGD("Dropped an explosion!");
		return;
	}
	
	// Update the statistics:
	m_NumExplosionsThisTick += 1;
	
	m_Protocol->SendExplosion(a_BlockX, a_BlockY, a_BlockZ, a_Radius, a_BlocksAffected, a_PlayerMotion);
}





void cClientHandle::SendGameMode(eGameMode a_GameMode)
{
	m_Protocol->SendGameMode(a_GameMode);
}





void cClientHandle::SendHealth(void)
{
	m_Protocol->SendHealth();
}





void cClientHandle::SendInventorySlot(char a_WindowID, short a_SlotNum, const cItem & a_Item)
{
	m_Protocol->SendInventorySlot(a_WindowID, a_SlotNum, a_Item);
}





void cClientHandle::SendMapColumn(int a_ID, int a_X, int a_Y, const Byte * a_Colors, unsigned int a_Length)
{
	m_Protocol->SendMapColumn(a_ID, a_X, a_Y, a_Colors, a_Length);
}





void cClientHandle::SendMapDecorators(int a_ID, const cMapDecoratorList & a_Decorators)
{
	m_Protocol->SendMapDecorators(a_ID, a_Decorators);
}





void cClientHandle::SendMapInfo(int a_ID, unsigned int a_Scale)
{
	m_Protocol->SendMapInfo(a_ID, a_Scale);
}





void cClientHandle::SendParticleEffect(const AString & a_ParticleName, float a_SrcX, float a_SrcY, float a_SrcZ, float a_OffsetX, float a_OffsetY, float a_OffsetZ, float a_ParticleData, int a_ParticleAmmount)
{
	m_Protocol->SendParticleEffect(a_ParticleName, a_SrcX, a_SrcY, a_SrcZ, a_OffsetX, a_OffsetY, a_OffsetZ, a_ParticleData, a_ParticleAmmount);
}





void cClientHandle::SendPickupSpawn(const cPickup & a_Pickup)
{
	m_Protocol->SendPickupSpawn(a_Pickup);
}




void cClientHandle::SendPaintingSpawn(const cPainting & a_Painting)
{
	m_Protocol->SendPaintingSpawn(a_Painting);
}





void cClientHandle::SendEntityAnimation(const cEntity & a_Entity, char a_Animation)
{
	m_Protocol->SendEntityAnimation(a_Entity, a_Animation);
}





void cClientHandle::SendPlayerAbilities()
{
	m_Protocol->SendPlayerAbilities();
}





void cClientHandle::SendPlayerListItem(const cPlayer & a_Player, bool a_IsOnline)
{
	m_Protocol->SendPlayerListItem(a_Player, a_IsOnline);
}





void cClientHandle::SendPlayerMaxSpeed(void)
{
	m_Protocol->SendPlayerMaxSpeed();
}





void cClientHandle::SendPlayerMoveLook(void)
{
	/*
	LOGD("Sending PlayerMoveLook: {%0.2f, %0.2f, %0.2f}, stance %0.2f, OnGround: %d",
		m_Player->GetPosX(), m_Player->GetPosY(), m_Player->GetPosZ(), m_Player->GetStance(), m_Player->IsOnGround() ? 1 : 0
	);
	*/
	m_Protocol->SendPlayerMoveLook();
}





void cClientHandle::SendPlayerPosition(void)
{
	m_Protocol->SendPlayerPosition();
}





void cClientHandle::SendPlayerSpawn(const cPlayer & a_Player)
{
	if (a_Player.GetUniqueID() == m_Player->GetUniqueID())
	{
		// Do NOT send this packet to myself
		return;
	}
	
	LOGD("Spawning player \"%s\" on client \"%s\" @ %s", 
		a_Player.GetName().c_str(), GetPlayer()->GetName().c_str(), GetIPString().c_str()
	);
	
	m_Protocol->SendPlayerSpawn(a_Player);
}





void cClientHandle::SendPluginMessage(const AString & a_Channel, const AString & a_Message)
{
	m_Protocol->SendPluginMessage(a_Channel, a_Message);
}





void cClientHandle::SendRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID)
{
	m_Protocol->SendRemoveEntityEffect(a_Entity, a_EffectID);
}





void cClientHandle::SendRespawn(void)
{
	m_Protocol->SendRespawn();
}





void cClientHandle::SendExperience(void)
{
	m_Protocol->SendExperience();
}





void cClientHandle::SendExperienceOrb(const cExpOrb & a_ExpOrb)
{
	m_Protocol->SendExperienceOrb(a_ExpOrb);
}





void cClientHandle::SendScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode)
{
	m_Protocol->SendScoreboardObjective(a_Name, a_DisplayName, a_Mode);
}





void cClientHandle::SendScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode)
{
	m_Protocol->SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode);
}





void cClientHandle::SendDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display)
{
	m_Protocol->SendDisplayObjective(a_Objective, a_Display);
}





void cClientHandle::SendSoundEffect(const AString & a_SoundName, int a_SrcX, int a_SrcY, int a_SrcZ, float a_Volume, float a_Pitch)
{
	m_Protocol->SendSoundEffect(a_SoundName, a_SrcX, a_SrcY, a_SrcZ, a_Volume, a_Pitch);
}





void cClientHandle::SendSoundParticleEffect(int a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data)
{
	m_Protocol->SendSoundParticleEffect(a_EffectID, a_SrcX, a_SrcY, a_SrcZ, a_Data);
}





void cClientHandle::SendSpawnFallingBlock(const cFallingBlock & a_FallingBlock)
{
	m_Protocol->SendSpawnFallingBlock(a_FallingBlock);
}





void cClientHandle::SendSpawnMob(const cMonster & a_Mob)
{
	m_Protocol->SendSpawnMob(a_Mob);
}





void cClientHandle::SendSpawnObject(const cEntity & a_Entity, char a_ObjectType, int a_ObjectData, Byte a_Yaw, Byte a_Pitch)
{
	m_Protocol->SendSpawnObject(a_Entity, a_ObjectType, a_ObjectData, a_Yaw, a_Pitch);
}





void cClientHandle::SendSpawnVehicle(const cEntity & a_Vehicle, char a_VehicleType, char a_VehicleSubType) // VehicleSubType is specific to Minecarts
{
	m_Protocol->SendSpawnVehicle(a_Vehicle, a_VehicleType, a_VehicleSubType);
}





void cClientHandle::SendTabCompletionResults(const AStringVector & a_Results)
{
	m_Protocol->SendTabCompletionResults(a_Results);
}





void cClientHandle::SendTeleportEntity(const cEntity & a_Entity)
{
	m_Protocol->SendTeleportEntity(a_Entity);
}





void cClientHandle::SendThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ)
{
	m_Protocol->SendThunderbolt(a_BlockX, a_BlockY, a_BlockZ);
}





void cClientHandle::SendTimeUpdate(Int64 a_WorldAge, Int64 a_TimeOfDay)
{
	m_Protocol->SendTimeUpdate(a_WorldAge, a_TimeOfDay);
}





void cClientHandle::SendUnloadChunk(int a_ChunkX, int a_ChunkZ)
{
	m_Protocol->SendUnloadChunk(a_ChunkX, a_ChunkZ);
}




void cClientHandle::SendUpdateBlockEntity(cBlockEntity & a_BlockEntity)
{
	m_Protocol->SendUpdateBlockEntity(a_BlockEntity);
}





void cClientHandle::SendUpdateSign(
	int a_BlockX, int a_BlockY, int a_BlockZ,
	const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4
)
{
	m_Protocol->SendUpdateSign(
		a_BlockX, a_BlockY, a_BlockZ,
		a_Line1, a_Line2, a_Line3, a_Line4
	);
}





void cClientHandle::SendUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ )
{
	m_Protocol->SendUseBed(a_Entity, a_BlockX, a_BlockY, a_BlockZ);
}




void cClientHandle::SendWeather(eWeather a_Weather)
{
	m_Protocol->SendWeather(a_Weather);
}





void cClientHandle::SendWholeInventory(const cWindow & a_Window)
{
	m_Protocol->SendWholeInventory(a_Window);
}





void cClientHandle::SendWindowClose(const cWindow & a_Window)
{
	m_Protocol->SendWindowClose(a_Window);
}





void cClientHandle::SendWindowOpen(const cWindow & a_Window)
{
	m_Protocol->SendWindowOpen(a_Window);
}





void cClientHandle::SendWindowProperty(const cWindow & a_Window, int a_Property, int a_Value)
{
	m_Protocol->SendWindowProperty(a_Window, a_Property, a_Value);
}





const AString & cClientHandle::GetUsername(void) const
{
	return m_Username;
}





void cClientHandle::SetUsername( const AString & a_Username )
{
	m_Username = a_Username;
}





void cClientHandle::SetViewDistance(int a_ViewDistance)
{
	if (a_ViewDistance < MIN_VIEW_DISTANCE)
	{
		a_ViewDistance = MIN_VIEW_DISTANCE;
	}
	if (a_ViewDistance > MAX_VIEW_DISTANCE)
	{
		a_ViewDistance = MAX_VIEW_DISTANCE;
	}
	m_ViewDistance = a_ViewDistance;
	
	// Need to re-stream chunks for the change to become apparent:
	StreamChunks();
}





bool cClientHandle::HasPluginChannel(const AString & a_PluginChannel)
{
	return (m_PluginChannels.find(a_PluginChannel) != m_PluginChannels.end());
}





bool cClientHandle::WantsSendChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ)
{
	if (m_State >= csDestroying)
	{
		return false;
	}
	
	cCSLock Lock(m_CSChunkLists);
	return (std::find(m_ChunksToSend.begin(), m_ChunksToSend.end(), cChunkCoords(a_ChunkX, a_ChunkY, a_ChunkZ)) != m_ChunksToSend.end());
}





void cClientHandle::AddWantedChunk(int a_ChunkX, int a_ChunkZ)
{
	if (m_State >= csDestroying)
	{
		return;
	}
	
	LOGD("Adding chunk [%d, %d] to wanted chunks for client %p", a_ChunkX, a_ChunkZ, this);
	cCSLock Lock(m_CSChunkLists);
	if (std::find(m_ChunksToSend.begin(), m_ChunksToSend.end(), cChunkCoords(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ)) == m_ChunksToSend.end())
	{
		m_ChunksToSend.push_back(cChunkCoords(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ));
	}
}





void cClientHandle::PacketBufferFull(void)
{
	// Too much data in the incoming queue, the server is probably too busy, kick the client:
	LOGERROR("Too much data in queue for client \"%s\" @ %s, kicking them.", m_Username.c_str(), m_IPString.c_str());
	SendDisconnect("Server busy");
	Destroy();
}





void cClientHandle::PacketUnknown(UInt32 a_PacketType)
{
	LOGERROR("Unknown packet type 0x%x from client \"%s\" @ %s", a_PacketType, m_Username.c_str(), m_IPString.c_str());

	AString Reason;
	Printf(Reason, "Unknown [C->S] PacketType: 0x%x", a_PacketType);
	SendDisconnect(Reason);
	Destroy();
}





void cClientHandle::PacketError(unsigned char a_PacketType)
{
	LOGERROR("Protocol error while parsing packet type 0x%02x; disconnecting client \"%s\"", a_PacketType, m_Username.c_str());
	SendDisconnect("Protocol error");
	Destroy();
}





void cClientHandle::DataReceived(const char * a_Data, size_t a_Size)
{
	// Data is received from the client, store it in the buffer to be processed by the Tick thread:
	m_TimeSinceLastPacket = 0;
	cCSLock Lock(m_CSIncomingData);
	m_IncomingData.append(a_Data, a_Size);
}





void cClientHandle::GetOutgoingData(AString & a_Data)
{
	// Data can be sent to client
	{
		cCSLock Lock(m_CSOutgoingData);
		m_OutgoingData.ReadAll(a_Data);
		m_OutgoingData.CommitRead();
		a_Data.append(m_OutgoingDataOverflow);
		m_OutgoingDataOverflow.clear();
	}

	// Disconnect player after all packets have been sent
	if (m_HasSentDC && a_Data.empty())
	{
		Destroy();
	}
}





void cClientHandle::SocketClosed(void)
{
	// The socket has been closed for any reason
	
	LOGD("Player %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str());

	if (m_Username != "") // Ignore client pings
	{
		cRoot::Get()->GetPluginManager()->CallHookDisconnect(m_Player, "Player disconnected");
	}

	Destroy();
}





void cClientHandle::HandleEnchantItem(Byte & WindowID, Byte & Enchantment)
{
	cEnchantingWindow * Window = (cEnchantingWindow*)m_Player->GetWindow();
	cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player);

	if (!cItem::IsEnchantable(Item.m_ItemType) && Item.m_ItemType != E_ITEM_BOOK)
	{
		return;
	}

	int BaseEnchantmentLevel = Window->GetPropertyValue(Enchantment);

	// Step 1 from Enchanting
	int Enchantability = 1;

	if (Item.m_ItemType == E_ITEM_WOODEN_SWORD || Item.m_ItemType == E_ITEM_WOODEN_PICKAXE || Item.m_ItemType == E_ITEM_WOODEN_SHOVEL || Item.m_ItemType == E_ITEM_WOODEN_AXE || Item.m_ItemType == E_ITEM_WOODEN_HOE)
	{
		Enchantability = 15;
	}
	else if (Item.m_ItemType == E_ITEM_LEATHER_CAP || Item.m_ItemType == E_ITEM_LEATHER_TUNIC || Item.m_ItemType == E_ITEM_LEATHER_PANTS || Item.m_ItemType == E_ITEM_LEATHER_BOOTS)
	{
		Enchantability = 15;
	}
	else if (Item.m_ItemType == E_ITEM_STONE_SWORD || Item.m_ItemType == E_ITEM_STONE_PICKAXE || Item.m_ItemType == E_ITEM_STONE_SHOVEL || Item.m_ItemType == E_ITEM_STONE_AXE || Item.m_ItemType == E_ITEM_STONE_HOE)
	{
		Enchantability = 5;
	}
	else if (Item.m_ItemType == E_ITEM_IRON_HELMET || Item.m_ItemType == E_ITEM_IRON_CHESTPLATE || Item.m_ItemType == E_ITEM_IRON_LEGGINGS || Item.m_ItemType == E_ITEM_IRON_BOOTS)
	{
		Enchantability = 9;
	}
	else if (Item.m_ItemType == E_ITEM_IRON_SWORD || Item.m_ItemType == E_ITEM_IRON_PICKAXE || Item.m_ItemType == E_ITEM_IRON_SHOVEL || Item.m_ItemType == E_ITEM_IRON_AXE || Item.m_ItemType == E_ITEM_IRON_HOE)
	{
		Enchantability = 14;
	}
	else if (Item.m_ItemType == E_ITEM_CHAIN_HELMET || Item.m_ItemType == E_ITEM_CHAIN_CHESTPLATE || Item.m_ItemType == E_ITEM_CHAIN_LEGGINGS || Item.m_ItemType == E_ITEM_CHAIN_BOOTS)
	{
		Enchantability = 12;
	}
	else if (Item.m_ItemType == E_ITEM_DIAMOND_HELMET || Item.m_ItemType == E_ITEM_DIAMOND_CHESTPLATE || Item.m_ItemType == E_ITEM_DIAMOND_LEGGINGS || Item.m_ItemType == E_ITEM_DIAMOND_BOOTS)
	{
		Enchantability = 10;
	}
	else if (Item.m_ItemType == E_ITEM_DIAMOND_SWORD || Item.m_ItemType == E_ITEM_DIAMOND_PICKAXE || Item.m_ItemType == E_ITEM_DIAMOND_SHOVEL || Item.m_ItemType == E_ITEM_DIAMOND_AXE || Item.m_ItemType == E_ITEM_DIAMOND_HOE)
	{
		Enchantability = 10;
	}
	else if (Item.m_ItemType == E_ITEM_GOLD_HELMET || Item.m_ItemType == E_ITEM_GOLD_CHESTPLATE || Item.m_ItemType == E_ITEM_GOLD_LEGGINGS || Item.m_ItemType == E_ITEM_GOLD_BOOTS)
	{
		Enchantability = 25;
	}
	else if (Item.m_ItemType == E_ITEM_GOLD_SWORD || Item.m_ItemType == E_ITEM_GOLD_PICKAXE || Item.m_ItemType == E_ITEM_GOLD_SHOVEL || Item.m_ItemType == E_ITEM_GOLD_AXE || Item.m_ItemType == E_ITEM_GOLD_HOE)
	{
		Enchantability = 22;
	}

	cFastRandom Random;
	int ModifiedEnchantmentLevel = BaseEnchantmentLevel + (int)Random.NextFloat((float)Enchantability / 4) + (int)Random.NextFloat((float)Enchantability / 4) + 1;
	float RandomBonus = 1.0F + (Random.NextFloat(1) + Random.NextFloat(1) - 1.0F) * 0.15F;

	int FinalEnchantmentLevel = (int)(ModifiedEnchantmentLevel * RandomBonus + 0.5F);

	// Step 2 and 3 from Enchanting
	cEnchantmentsVector enchantments;

	if (ItemCategory::IsSword(Item.m_ItemType))
	{
		// Sharpness
		if (FinalEnchantmentLevel >= 34 && FinalEnchantmentLevel <= 54)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Sharpness=4"));
		}
		else if (FinalEnchantmentLevel >= 23 && FinalEnchantmentLevel <= 43)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Sharpness=3"));
		}
		else if (FinalEnchantmentLevel >= 12 && FinalEnchantmentLevel <= 32)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Sharpness=2"));
		}
		else if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 21)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Sharpness=1"));
		}

		// Smite
		if (FinalEnchantmentLevel >= 29 && FinalEnchantmentLevel <= 49)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Smite=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 41)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Smite=3"));
		}
		else if (FinalEnchantmentLevel >= 13 && FinalEnchantmentLevel <= 33)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Smite=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 25)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Smite=1"));
		}

		// Bane of Arthropods
		if (FinalEnchantmentLevel >= 29 && FinalEnchantmentLevel <= 49)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("BaneOfArthropods=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 41)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("BaneOfArthropods=3"));
		}
		else if (FinalEnchantmentLevel >= 13 && FinalEnchantmentLevel <= 33)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("BaneOfArthropods=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 25)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("BaneOfArthropods=1"));
		}

		// Knockback
		if (FinalEnchantmentLevel >= 25 && FinalEnchantmentLevel <= 75)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Knockback=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 55)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Knockback=1"));
		}

		// Fire Aspect
		if (FinalEnchantmentLevel >= 30 && FinalEnchantmentLevel <= 80)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("FireAspect=2"));
		}
		else if (FinalEnchantmentLevel >= 10 && FinalEnchantmentLevel <= 60)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("FireAspect=1"));
		}

		// Looting
		if (FinalEnchantmentLevel >= 33 && FinalEnchantmentLevel <= 83)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Looting=3"));
		}
		else if (FinalEnchantmentLevel >= 24 && FinalEnchantmentLevel <= 74)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Looting=2"));
		}
		else if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 65)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Looting=1"));
		}
	}

	else if (ItemCategory::IsTool(Item.m_ItemType))
	{
		// Efficiency
		if (FinalEnchantmentLevel >= 31 && FinalEnchantmentLevel <= 81)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Efficiency=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 71)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Efficiency=3"));
		}
		else if (FinalEnchantmentLevel >= 11 && FinalEnchantmentLevel <= 61)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Efficiency=2"));
		}
		else if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 51)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Efficiency=1"));
		}

		// Silk Touch
		if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 65)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("SilkTouch=1"));
		}

		// Fortune
		if (FinalEnchantmentLevel >= 33 && FinalEnchantmentLevel <= 83)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Fortune=3"));
		}
		else if (FinalEnchantmentLevel >= 24 && FinalEnchantmentLevel <= 74)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Fortune=2"));
		}
		else if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 65)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Fortune=1"));
		}
	}

	else if (ItemCategory::IsArmor(Item.m_ItemType))
	{
		// Protection
		if (FinalEnchantmentLevel >= 34 && FinalEnchantmentLevel <= 54)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Protection=4"));
		}
		else if (FinalEnchantmentLevel >= 23 && FinalEnchantmentLevel <= 43)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Protection=3"));
		}
		else if (FinalEnchantmentLevel >= 12 && FinalEnchantmentLevel <= 32)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Protection=2"));
		}
		else if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 21)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Protection=1"));
		}

		// Fire Protection
		if (FinalEnchantmentLevel >= 34 && FinalEnchantmentLevel <= 46)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FireProtection=4"));
		}
		else if (FinalEnchantmentLevel >= 26 && FinalEnchantmentLevel <= 38)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FireProtection=3"));
		}
		else if (FinalEnchantmentLevel >= 18 && FinalEnchantmentLevel <= 30)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FireProtection=2"));
		}
		else if (FinalEnchantmentLevel >= 10 && FinalEnchantmentLevel <= 22)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FireProtection=1"));
		}

		// Blast Protection
		if (FinalEnchantmentLevel >= 29 && FinalEnchantmentLevel <= 41)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("BlastProtection=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 33)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("BlastProtection=3"));
		}
		else if (FinalEnchantmentLevel >= 13 && FinalEnchantmentLevel <= 25)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("BlastProtection=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 17)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("BlastProtection=1"));
		}

		// Projectile Protection
		if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 36)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("ProjectileProtection=4"));
		}
		else if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 30)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("ProjectileProtection=3"));
		}
		else if (FinalEnchantmentLevel >= 9 && FinalEnchantmentLevel <= 24)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("ProjectileProtection=2"));
		}
		else if (FinalEnchantmentLevel >= 3 && FinalEnchantmentLevel <= 18)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("ProjectileProtection=1"));
		}

		// Thorns
		if (FinalEnchantmentLevel >= 50 && FinalEnchantmentLevel <= 100)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Thorns=3"));
		}
		else if (FinalEnchantmentLevel >= 30 && FinalEnchantmentLevel <= 80)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Thorns=2"));
		}
		else if (FinalEnchantmentLevel >= 10 && FinalEnchantmentLevel <= 60)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Thorns=1"));
		}


		if (ItemCategory::IsHelmet(Item.m_ItemType))
		{
			// Respiration
			if (FinalEnchantmentLevel >= 30 && FinalEnchantmentLevel <= 60)
			{
				AddEnchantmentWeight(enchantments, 2, cEnchantments("Respiration=3"));
			}
			else if (FinalEnchantmentLevel >= 20 && FinalEnchantmentLevel <= 50)
			{
				AddEnchantmentWeight(enchantments, 2, cEnchantments("Respiration=2"));
			}
			else if (FinalEnchantmentLevel >= 10 && FinalEnchantmentLevel <= 40)
			{
				AddEnchantmentWeight(enchantments, 2, cEnchantments("Respiration=1"));
			}

			// Aqua Affinity
			if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 41)
			{
				AddEnchantmentWeight(enchantments, 2, cEnchantments("AquaAffinity=1"));
			}
		}

		else if (ItemCategory::IsBoots(Item.m_ItemType))
		{
			// Feather Fall
			if (FinalEnchantmentLevel >= 23 && FinalEnchantmentLevel <= 33)
			{
				AddEnchantmentWeight(enchantments, 5, cEnchantments("FeatherFalling=4"));
			}
			else if (FinalEnchantmentLevel >= 17 && FinalEnchantmentLevel <= 27)
			{
				AddEnchantmentWeight(enchantments, 5, cEnchantments("FeatherFalling=3"));
			}
			else if (FinalEnchantmentLevel >= 11 && FinalEnchantmentLevel <= 21)
			{
				AddEnchantmentWeight(enchantments, 5, cEnchantments("FeatherFalling=2"));
			}
			else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 15)
			{
				AddEnchantmentWeight(enchantments, 5, cEnchantments("FeatherFalling=1"));
			}
		}
	}

	else if (Item.m_ItemType == E_ITEM_BOW)
	{
		// Power
		if (FinalEnchantmentLevel >= 31 && FinalEnchantmentLevel <= 46)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Power=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 36)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Power=3"));
		}
		else if (FinalEnchantmentLevel >= 11 && FinalEnchantmentLevel <= 26)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Power=2"));
		}
		else if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 16)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Power=1"));
		}

		// Punch
		if (FinalEnchantmentLevel >= 32 && FinalEnchantmentLevel <= 57)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Punch=2"));
		}
		else if (FinalEnchantmentLevel >= 12 && FinalEnchantmentLevel <= 37)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Punch=1"));
		}

		// Flame and Infinity
		if (FinalEnchantmentLevel >= 20 && FinalEnchantmentLevel <= 50)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Flame=1"));
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Infinity=1"));
		}
	}

	else if (Item.m_ItemType == E_ITEM_FISHING_ROD)
	{
		// Luck of the Sea and Lure
		if (FinalEnchantmentLevel >= 33 && FinalEnchantmentLevel <= 83)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("LuckOfTheSea=3"));
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Lure=3"));
		}
		else if (FinalEnchantmentLevel >= 24 && FinalEnchantmentLevel <= 74)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("LuckOfTheSea=2"));
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Lure=2"));
		}
		else if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 65)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("LuckOfTheSea=1"));
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Lure=1"));
		}
	}

	else if (Item.m_ItemType == E_ITEM_BOOK)
	{
		// All enchantments

		// Sharpness
		if (FinalEnchantmentLevel >= 34 && FinalEnchantmentLevel <= 54)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Sharpness=4"));
		}
		else if (FinalEnchantmentLevel >= 23 && FinalEnchantmentLevel <= 43)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Sharpness=3"));
		}
		else if (FinalEnchantmentLevel >= 12 && FinalEnchantmentLevel <= 32)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Sharpness=2"));
		}
		else if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 21)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Sharpness=1"));
		}

		// Smite
		if (FinalEnchantmentLevel >= 29 && FinalEnchantmentLevel <= 49)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Smite=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 41)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Smite=3"));
		}
		else if (FinalEnchantmentLevel >= 13 && FinalEnchantmentLevel <= 33)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Smite=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 25)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Smite=1"));
		}

		// Bane of Arthropods
		if (FinalEnchantmentLevel >= 29 && FinalEnchantmentLevel <= 49)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("BaneOfArthropods=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 41)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("BaneOfArthropods=3"));
		}
		else if (FinalEnchantmentLevel >= 13 && FinalEnchantmentLevel <= 33)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("BaneOfArthropods=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 25)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("BaneOfArthropods=1"));
		}

		// Knockback
		if (FinalEnchantmentLevel >= 25 && FinalEnchantmentLevel <= 75)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Knockback=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 55)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("Knockback=1"));
		}

		// Fire Aspect
		if (FinalEnchantmentLevel >= 30 && FinalEnchantmentLevel <= 80)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("FireAspect=2"));
		}
		else if (FinalEnchantmentLevel >= 10 && FinalEnchantmentLevel <= 60)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("FireAspect=1"));
		}

		// Looting
		if (FinalEnchantmentLevel >= 33 && FinalEnchantmentLevel <= 83)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Looting=3"));
		}
		else if (FinalEnchantmentLevel >= 24 && FinalEnchantmentLevel <= 74)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Looting=2"));
		}
		else if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 65)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Looting=1"));
		}

		// Efficiency
		if (FinalEnchantmentLevel >= 31 && FinalEnchantmentLevel <= 81)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Efficiency=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 71)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Efficiency=3"));
		}
		else if (FinalEnchantmentLevel >= 11 && FinalEnchantmentLevel <= 61)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Efficiency=2"));
		}
		else if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 51)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Efficiency=1"));
		}

		// Silk Touch
		if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 65)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("SilkTouch=1"));
		}

		// Fortune
		if (FinalEnchantmentLevel >= 33 && FinalEnchantmentLevel <= 83)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Fortune=3"));
		}
		else if (FinalEnchantmentLevel >= 24 && FinalEnchantmentLevel <= 74)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Fortune=2"));
		}
		else if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 65)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Fortune=1"));
		}

		// Protection
		if (FinalEnchantmentLevel >= 34 && FinalEnchantmentLevel <= 54)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Protection=4"));
		}
		else if (FinalEnchantmentLevel >= 23 && FinalEnchantmentLevel <= 43)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Protection=3"));
		}
		else if (FinalEnchantmentLevel >= 12 && FinalEnchantmentLevel <= 32)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Protection=2"));
		}
		else if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 21)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Protection=1"));
		}

		// Fire Protection
		if (FinalEnchantmentLevel >= 34 && FinalEnchantmentLevel <= 46)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FireProtection=4"));
		}
		else if (FinalEnchantmentLevel >= 26 && FinalEnchantmentLevel <= 38)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FireProtection=3"));
		}
		else if (FinalEnchantmentLevel >= 18 && FinalEnchantmentLevel <= 30)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FireProtection=2"));
		}
		else if (FinalEnchantmentLevel >= 10 && FinalEnchantmentLevel <= 22)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FireProtection=1"));
		}

		// Blast Protection
		if (FinalEnchantmentLevel >= 29 && FinalEnchantmentLevel <= 41)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("BlastProtection=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 33)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("BlastProtection=3"));
		}
		else if (FinalEnchantmentLevel >= 13 && FinalEnchantmentLevel <= 25)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("BlastProtection=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 17)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("BlastProtection=1"));
		}

		// Projectile Protection
		if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 36)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("ProjectileProtection=4"));
		}
		else if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 30)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("ProjectileProtection=3"));
		}
		else if (FinalEnchantmentLevel >= 9 && FinalEnchantmentLevel <= 24)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("ProjectileProtection=2"));
		}
		else if (FinalEnchantmentLevel >= 3 && FinalEnchantmentLevel <= 18)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("ProjectileProtection=1"));
		}

		// Thorns
		if (FinalEnchantmentLevel >= 50 && FinalEnchantmentLevel <= 100)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Thorns=3"));
		}
		else if (FinalEnchantmentLevel >= 30 && FinalEnchantmentLevel <= 80)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Thorns=2"));
		}
		else if (FinalEnchantmentLevel >= 10 && FinalEnchantmentLevel <= 60)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Thorns=1"));
		}

		// Respiration
		if (FinalEnchantmentLevel >= 30 && FinalEnchantmentLevel <= 60)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Respiration=3"));
		}
		else if (FinalEnchantmentLevel >= 20 && FinalEnchantmentLevel <= 50)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Respiration=2"));
		}
		else if (FinalEnchantmentLevel >= 10 && FinalEnchantmentLevel <= 40)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Respiration=1"));
		}

		// Aqua Affinity
		if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 41)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("AquaAffinity=1"));
		}

		// Feather Fall
		if (FinalEnchantmentLevel >= 23 && FinalEnchantmentLevel <= 33)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FeatherFalling=4"));
		}
		else if (FinalEnchantmentLevel >= 17 && FinalEnchantmentLevel <= 27)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FeatherFalling=3"));
		}
		else if (FinalEnchantmentLevel >= 11 && FinalEnchantmentLevel <= 21)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FeatherFalling=2"));
		}
		else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 15)
		{
			AddEnchantmentWeight(enchantments, 5, cEnchantments("FeatherFalling=1"));
		}

		// Power
		if (FinalEnchantmentLevel >= 31 && FinalEnchantmentLevel <= 46)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Power=4"));
		}
		else if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 36)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Power=3"));
		}
		else if (FinalEnchantmentLevel >= 11 && FinalEnchantmentLevel <= 26)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Power=2"));
		}
		else if (FinalEnchantmentLevel >= 1 && FinalEnchantmentLevel <= 16)
		{
			AddEnchantmentWeight(enchantments, 10, cEnchantments("Power=1"));
		}

		// Punch
		if (FinalEnchantmentLevel >= 32 && FinalEnchantmentLevel <= 57)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Punch=2"));
		}
		else if (FinalEnchantmentLevel >= 12 && FinalEnchantmentLevel <= 37)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Punch=1"));
		}

		// Flame and Infinity
		if (FinalEnchantmentLevel >= 20 && FinalEnchantmentLevel <= 50)
		{
			AddEnchantmentWeight(enchantments, 2, cEnchantments("Flame=1"));
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Infinity=1"));
		}

		// Luck of the Sea and Lure
		if (FinalEnchantmentLevel >= 33 && FinalEnchantmentLevel <= 83)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("LuckOfTheSea=3"));
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Lure=3"));
		}
		else if (FinalEnchantmentLevel >= 24 && FinalEnchantmentLevel <= 74)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("LuckOfTheSea=2"));
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Lure=2"));
		}
		else if (FinalEnchantmentLevel >= 15 && FinalEnchantmentLevel <= 65)
		{
			AddEnchantmentWeight(enchantments, 1, cEnchantments("LuckOfTheSea=1"));
			AddEnchantmentWeight(enchantments, 1, cEnchantments("Lure=1"));
		}

		Item.m_ItemType = E_ITEM_ENCHANTED_BOOK;
	}

	// Unbreaking
	if (FinalEnchantmentLevel >= 21 && FinalEnchantmentLevel <= 71)
	{
		AddEnchantmentWeight(enchantments, 5, cEnchantments("Unbreaking=3"));
	}
	else if (FinalEnchantmentLevel >= 13 && FinalEnchantmentLevel <= 63)
	{
		AddEnchantmentWeight(enchantments, 5, cEnchantments("Unbreaking=2"));
	}
	else if (FinalEnchantmentLevel >= 5 && FinalEnchantmentLevel <= 55)
	{
		AddEnchantmentWeight(enchantments, 5, cEnchantments("Unbreaking=1"));
	}

	int RandomEnchantment1 = (int)floor(Random.NextFloat(1) * enchantments.size());
	cEnchantments Enchantment1 = enchantments[RandomEnchantment1];
	Item.m_Enchantments.AddFromString(Enchantment1.ToString());
	enchantments.erase(std::remove(enchantments.begin(), enchantments.end(), Enchantment1), enchantments.end());

	float NewEnchantmentLevel = (float)BaseEnchantmentLevel;

	// Next Enchantment (Second)
	NewEnchantmentLevel = NewEnchantmentLevel / 2;
	float SecondEnchantmentChance = (NewEnchantmentLevel + 1) / 50 * 100;
	if (Random.NextFloat(100) <= SecondEnchantmentChance)
	{
		// Checking for conflicting enchantments
		CheckEnchantmentConflicts(enchantments, Enchantment1);

		if (enchantments.size() > 0)
		{
			int RandomEnchantment2 = (int)floor(Random.NextFloat(1) * enchantments.size());

			cEnchantments Enchantment2 = enchantments[RandomEnchantment2];
			Item.m_Enchantments.AddFromString(Enchantment2.ToString());
			enchantments.erase(std::remove(enchantments.begin(), enchantments.end(), Enchantment2), enchantments.end());

			// Checking for conflicting enchantments
			CheckEnchantmentConflicts(enchantments, Enchantment2);
		}
	}

	// Next Enchantment (Third)
	NewEnchantmentLevel = NewEnchantmentLevel / 2;
	float ThirdEnchantmentChance = (NewEnchantmentLevel + 1) / 50 * 100;
	if (Random.NextFloat(100) <= ThirdEnchantmentChance)
	{
		if (enchantments.size() > 0)
		{
			int RandomEnchantment3 = (int)floor(Random.NextFloat(1) * enchantments.size());
			cEnchantments Enchantment3 = enchantments[RandomEnchantment3];
			Item.m_Enchantments.AddFromString(Enchantment3.ToString());
			enchantments.erase(std::remove(enchantments.begin(), enchantments.end(), Enchantment3), enchantments.end());

			// Checking for conflicting enchantments
			CheckEnchantmentConflicts(enchantments, Enchantment3);
		}
	}

	// Next Enchantment (Fourth)
	NewEnchantmentLevel = NewEnchantmentLevel / 2;
	float FourthEnchantmentChance = (NewEnchantmentLevel + 1) / 50 * 100;
	if (Random.NextFloat(100) <= FourthEnchantmentChance)
	{
		if (enchantments.size() > 0)
		{
			int RandomEnchantment4 = (int)floor(Random.NextFloat(1) * enchantments.size());
			cEnchantments Enchantment4 = enchantments[RandomEnchantment4];
			Item.m_Enchantments.AddFromString(Enchantment4.ToString());
			enchantments.erase(std::remove(enchantments.begin(), enchantments.end(), Enchantment4), enchantments.end());
		}
	}

	if (m_Player->DeltaExperience(-m_Player->XpForLevel(Window->GetPropertyValue(Enchantment))) >= 0 || m_Player->IsGameModeCreative())
	{
		Window->m_SlotArea->SetSlot(0, *m_Player, Item);
		Window->SendSlot(*m_Player, Window->m_SlotArea, 0);
		Window->BroadcastWholeWindow();

		Window->SetProperty(0, 0, *m_Player);
		Window->SetProperty(1, 0, *m_Player);
		Window->SetProperty(2, 0, *m_Player);
	}
}





void cClientHandle::AddEnchantmentWeight(cEnchantmentsVector & a_Enchantments, int a_Weight, cEnchantments a_Enchantment)
{
	for (int i = 0; i < a_Weight; i++)
	{
		a_Enchantments.push_back(a_Enchantment);
	}
}





void cClientHandle::RemoveEnchantmentFromVector(cEnchantmentsVector & a_Enchantments, int a_EnchantmentID)
{
	for (cEnchantmentsVector::iterator it = a_Enchantments.begin(); it != a_Enchantments.end(); ++it)
	{
		int EnchantmentID = atoi(StringSplit((*it).ToString(), "=")[0].c_str());

		if (EnchantmentID == a_EnchantmentID)
		{
			a_Enchantments.erase(std::remove(a_Enchantments.begin(), a_Enchantments.end(), *it), a_Enchantments.end());
			break;
		}
	}
}





void cClientHandle::CheckEnchantmentConflicts(cEnchantmentsVector & a_Enchantments, cEnchantments a_FirstEnchantment)
{
	int FirstEnchantmentID = atoi(StringSplit(a_FirstEnchantment.ToString(), "=")[0].c_str());

	if (FirstEnchantmentID == cEnchantments::enchProtection)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchFireProtection);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchBlastProtection);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchProjectileProtection);
	}
	else if (FirstEnchantmentID == cEnchantments::enchFireProtection)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchProtection);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchBlastProtection);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchProjectileProtection);
	}
	else if (FirstEnchantmentID == cEnchantments::enchBlastProtection)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchProtection);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchFireProtection);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchProjectileProtection);
	}
	else if (FirstEnchantmentID == cEnchantments::enchProjectileProtection)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchProtection);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchFireProtection);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchBlastProtection);
	}

	else if (FirstEnchantmentID == cEnchantments::enchSharpness)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchSmite);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchBaneOfArthropods);
	}
	else if (FirstEnchantmentID == cEnchantments::enchSmite)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchSharpness);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchBaneOfArthropods);
	}
	else if (FirstEnchantmentID == cEnchantments::enchBaneOfArthropods)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchSharpness);
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchSmite);
	}
	else if (FirstEnchantmentID == cEnchantments::enchSilkTouch)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchFortune);
	}
	else if (FirstEnchantmentID == cEnchantments::enchFortune)
	{
		RemoveEnchantmentFromVector(a_Enchantments, cEnchantments::enchSilkTouch);
	}
}