summaryrefslogblamecommitdiffstats
path: root/src/Entities/Player.cpp
blob: 7d8c3a191c05296c783f92632c3d9bce50da79bf (plain) (tree)
1
2
3
4
5
6
7
8
9
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
 


                                                                                              

                          
                           
                         
                      
                                  
                              
                                      
                                         
                                              
                    

                                 
                          
                            
 
                                           
                             
 



                                     
                       
                         
                      
 

                               


                                           

























                                                                                  
                                







                               


 








                                              
 
 
                                                                   
                                  




                                    


                                   
                                                                           
                                     
                                 
                          





                                 




                               

                               
                                         
                        
                          

                                                             
                                                                      

                           
 
                                                                                               
 



                                                        

                                 
 
                                 
                                 

                                    


                                            


                                                                                                                                                             
 

                                                                                                                                          
                                                                                                                 
                                                

                  
 
                                                           
                                    
 

                                   



                                                
                                                                                                         



                                          
         






                                                                                                    





 
                       
 
                                                                                                                  
 

















                                                             
         
                                                     

         

                                      
         
                                                                                                   
         


                                                                                       





 
                                                         
 










                                    
         
                                                   
         








                                                                                              





 
                               
 

                                            
 
 
 
 







                                                                                       





 
                                                   
 




                                                                                                         
 
                                  
 

                                         
 

                    
 
 
 
 
 







                                                                                                                               
 
                                  
 

                                                  
 
 




                                                
 





                                                                                                                        





 
                                    
 




                                                                            
 
 
 
 
 






                                                                                                            
 

                   
 
 
 
 
 





                                                                                                             





 
                                                 
 
                                                                                  
         
                       
         
 





                                                                                                                                     
 







                                     





 
                                           
 
                                                              
 
                                                                                              
         
                                            
                       
         
 


                                     
 
 
 
 
 



                                                                                                    
 
 
 
 
 



                                                   
 
 
 
 
 



                                                                        
 
 





                                                   
         
                             
         



                                                                     





 
                                                    
 
                                                             
         
                                                                                             






         
                                               
 
                                                                            
         
                       

         
                                                                                                
 


                                                                                                                  





 
                               
 





                                                                   





 
                                
 

                                
 








                                                                                   
         
                       
         
                                                       





 
                               
 

                                                





 
                                            
 

                                                                                         





 
                                                  
 


                                                                              
 
 
 





                                                                                                 





 
                                       
 
                       
         
                                        
         
                               
         




                                           
         
 
 
 
 

 
                                               
 





                                                                                               





 
                                                  
 





                                                                                               





 
                                               
 







                                                                                               





 
                                          
 

                                                                  
         
                            

                       
 



                                                                                
 

                                                





 
                                           
 
                                           
         
                            

                       
 

                                             





 
                                      
 



                                 
 

                                              





 
                                                         
 



                                         
 
                                                        
 

                                       
         
                                                          
         
 

                                                                   





 
                                               
 

                                              





 
                                                                 
 


                                              





 
                               
 



                                                                                          
 
                                               





 
                                        
 
                                     


                       
 





                                                                                               





 
                                                                             
 
                                      
 













                                                                                                               





 
                                              
 
                               
 



                                            
 
                                                                   
 


                                                                                        
 



                                         
 
                                 
         
                                                     
         
                                                                                                
 


                                                                                
 
                                      
         

































                                                                                                                                                          



















                                                                                                                   
         




                                                                                        





 
                                        
 


                                                            
         











                                                                                             
         

                                                                              





 
                           
 
                                   
 

                                  
 



                                     
 



                                           
 

                               
 
                                                                     
         
                                                                      
         
            
         

                                                                                          

         
                         





 
                                        
 
                        





 
                                            
 
                                                         





 
                                            
 

                                                      
 
 
 




                                                      





 
                                             
 
                                                       





 
                                             
 
                                                       





 
                                       
 

                                                                                 
 






                                             





 
                                     
 
                             



                       
                   
         







                                                
         




 
 
                                 
 
                               
         






                                                                    
         

                      




 
 
                                            
 
                                                                                           
         
                       
         
 
                                         
         

                                   
 


                                                     





 
                                           
 




                                                    
 










                                                                                





 
                                                                
 
                                                                                           
         
                       
         

                      
 
 
 
 
 



                                                      
 
 
 




                                                           





 
                                                           
 

                                                       
 
 
 
 



                                                           





 
                                                           
 

                                                       
 
 
 
 
 


                                                         





 
                                                                                        
 
                                                                        




 
 
                                                           
 
                                            





 
                                                                            
 
                                                          





 
                                                          
 
                                                            





 
                                                                  
 
                                                                    




 
 
                                                                 
 
                                                  



 
 
 
                                                                         
 
                                                          





 
                                            
 
                                             





 
                                               
 
                                                          
         
                                                                                              


                       
                                     
         

                                       
         
 





                                                                           

                                                 
 
                          
 
                                                          





 

                               





                                                          




                                 
                  


                                  




                                 
 


                                  





                                                                                     
         





 
                                                     
 

                                                  
         
                       

         

                                                                   
         
                       
         
 







                                                                                
         


                                                





 

                                                                           
                                                              
                                                                                                                               
         
                                                      
                                                     
                                        
 

                                                     





 

















                                                 

                        


                                                 
                           
                                                      
                                                





 










                                                                      
















                                                                                


                                              
 



                                                                            
 










                                                          
 
                                                                        
 
                                  
 




 






















                                                                                                                   
                                                     
 
                          





 

                                         
                                                                                                               
                                                       






                                                     
                                                                                                        






         


















                                                                        






                                                             
 

                                                             








                                                                              
                                                                                     
                                                   
         
                                                         
                 
                                    
                 
                                                   
 
                                        






                     
                                                                                                     
 




                                                                          
         


                                                                                                                    
                                    

                                                     
                 

                                           

                 
 
                                                

                                                                         







                                     
                                                                      
         

                                                                       

         

                                                          





 

















                                      
                                              




















                                                                        












                                                                             
                                             

                     

                                                            
         


                                                                             
                                                                                                            
                 
 
                                                                                                                                                                    
 

                                                    
         
 
                         





 


















                                                                                   


                                         

                                         
         


                                                                          
 
                                                  
                 
                                                                         


                    
                                     

                 
 
                         








                                              
                                
 






                         
                                               
 
                   
 
                                   
                                                           


                            
 
                                                                                    
                                                                          
















                                                                                                            
                                                

                                                                     
                                  
                                                                        



                                    
 



                                                                                        
                                                            

                                                                     



                                                                


                                    
 
                                                
                                                                                               
                                                                                                                    
          
 
                               


                                                          
                     
 
 
 
 
 
 
                                                                           

                                       
                
                                               
         
                                                                                                      

                             


                                                    
                                                                                           

                             
                  
 
                                 
                         

                                                               
         



                                                                           
                             

         
                                


                                                             


                                                           
                                               




                                                             


                                                                                    

         
                                                                        
                                                                                  



                                                                                      


                                                                       
                                                                                                                  
 



















                                                                                            
                                                                                            




                                             
 
                                                    



                                                                      
                                                                                           
 
                                                                   
                                                               
                               


                                                          
 
 


                                                                          
                                                                                                         
 



                                                                                                                    
                                                                                            




                                                               
 

                                                                                            
          
 






                    




























                                                                                          

                          
                                                                







                                                           
                                                          





                                                            


                                                                                      














                                                                                             
                         


                                                          

                                                        
                                                                       
                                                               







                                                            
                                                 
                                                


                                                        
                                                       
                                                        

                                                                   
 
                                                           
                                                     



                                                


                                                                                                                             

                             
                                                                                            
         

                                                                                                                              
                  

                             
 



                                                                                                                    

                                                                                            

                   
         
                                                                                    


                             






                    
                                             
 

                                                             


                       
 
                                                                                          

 



 














                                                                                            


























                                                                                                                                                                         

                              
                                                       
 
                                                          
         
                                                                

                       
 














                                                                                           
                                                                              
                                                      





                                            
                                                                                  


                                                                                               
                                                       
                         
                                                                      

                                                       
                                                                         


                         
            
         
                                    
         





 





                                                               
                                                                     
                 
                                           

                                    
          






                            

                                    
                                                    
 
                                                  



                             
                                                      














                                      
                                                                                         
 




                                         
 
                                                                                            
                                    
         

                               
                                                                     


                                                                        
                                 
                 
                                                            
                         
                                                                                                                                   
                         
                 
                                     
                 







                                                                                        
                                                                                

                                      
                 














                                                                                       
                 

                    


                                                                           
                                                                     


                                                                                                                
                         
                                                      
                                                                                                                                                 
                         
                                                                                                  
                 




                                                      

                                                                                                           

                                                
                                                                                           

                                                              

                                                                                                            












                                                       


                                             

                                                                

                           
                                                   
         


                                   
                                                             
         
                                                              
                                                                
                                                                                      



                                                                                    








                                                                                      
         

                                                                             





 




















                                                                                                                             

                         









                                                                                         
                                                                                    
















                                                                                                        







                                                                                


                                   
                                                                                                     





                                                                                                        


















                                                                       
                                                                                
                 

                                                                                 
 
                                                                  


                                             
                                                                
                                                            
                         








                                                                              
          





 

                                                           




                                                                                                
                                                                                           



                             








                                                                                                        
                                                                                                   




                                     


                                                               



                                                                                            
                                                                                                           
 
                                           

                                                          
 






                    



















                                                                      




                                                   
                                           


                       
                                    





 

                      














                                                                    
                        


                              

                                                                                   
                                                                 
                                                          
                                                    


                                                        
                                                                    
                         
                                    


                                                                                               
                                 
                                 
                                                                              




                                               




 
 
                                                      
 
                                             
 
                                
                               
                           
                                           


                            


































                                                                                



 
 














                                                        
                                                                                              



                                                                            
                                                                                                     











                                                             






                                                                                              
         
                                                                                                                                  
                 




                                                                                                                                                 

                 



                                                               
 
                                                 

                                                              
         
                                                          
                                                         

         
                                               

                                                                              
         
                                                              

                                  



                                                                



                 
                                           

                                                                                                                               
                                    

         
                                                

                          
                                    

         
                           





 
                                                          
 





                                                                        


                                                                                                  
                                                                                                       
 


                                                                   
                                                              








                                                                         


                                                                                                            
 




 
                                                
 






                                                              
         

                                                        

         

















































































































































































































































































































































































































                                                                                                                                                                  








                                                                      
 

#include "Globals.h"  // NOTE: MSVC stupidness requires this to be the same across all modules

#include "Player.h"
#include "../Mobs/Wolf.h"
#include "../Mobs/Horse.h"
#include "../BoundingBox.h"
#include "../ChatColor.h"
#include "../Server.h"
#include "../UI/InventoryWindow.h"
#include "../UI/WindowOwner.h"
#include "../Bindings/PluginManager.h"
#include "../BlockEntities/BlockEntity.h"
#include "../BlockEntities/EnderChestEntity.h"
#include "../Root.h"
#include "../Chunk.h"
#include "../Items/ItemHandler.h"
#include "../FastRandom.h"
#include "../ClientHandle.h"

#include "../WorldStorage/StatSerializer.h"
#include "../CompositeChat.h"

#include "../Blocks/BlockHandler.h"
#include "../Blocks/BlockSlab.h"
#include "../Blocks/ChunkInterface.h"

#include "../IniFile.h"
#include "../JsonUtils.h"
#include "json/json.h"

#include "../CraftingRecipes.h"

// 6000 ticks or 5 minutes
#define PLAYER_INVENTORY_SAVE_INTERVAL 6000

namespace
{

/** Returns the old Offline UUID generated before becoming vanilla compliant. */
cUUID GetOldStyleOfflineUUID(const AString & a_PlayerName)
{
	// Use lowercase username
	auto BaseUUID = cUUID::GenerateVersion3(StrToLower(a_PlayerName)).ToRaw();
	// Clobber a full nibble around the variant bits
	BaseUUID[8] = (BaseUUID[8] & 0x0f) | 0x80;

	cUUID Ret;
	Ret.FromRaw(BaseUUID);
	return Ret;
}





/** Returns the folder for the player data based on the UUID given.
This can be used both for online and offline UUIDs. */
AString GetUUIDFolderName(const cUUID & a_Uuid)
{
	AString UUID = a_Uuid.ToShortString();

	AString res("players/");
	res.append(UUID, 0, 2);
	res.push_back('/');
	return res;
}

}  // namespace (anonymous)





const int cPlayer::MAX_HEALTH = 20;

const int cPlayer::MAX_FOOD_LEVEL = 20;

/** Number of ticks it takes to eat an item */
const int cPlayer::EATING_TICKS = 30;





cPlayer::cPlayer(const std::shared_ptr<cClientHandle> & a_Client) :
	Super(etPlayer, 0.6, 1.8),
	m_bVisible(true),
	m_FoodLevel(MAX_FOOD_LEVEL),
	m_FoodSaturationLevel(5.0),
	m_FoodTickTimer(0),
	m_FoodExhaustionLevel(0.0),
	m_Stance(0.0),
	m_Inventory(*this),
	m_EnderChestContents(9, 3),
	m_DefaultWorldPath(cRoot::Get()->GetDefaultWorld()->GetDataPath()),
	m_GameMode(eGameMode_NotSet),
	m_ClientHandle(a_Client),
	m_IsFrozen(false),
	m_NormalMaxSpeed(1.0),
	m_SprintingMaxSpeed(1.3),
	m_FlyingMaxSpeed(1.0),
	m_IsCrouched(false),
	m_IsSprinting(false),
	m_IsFlying(false),
	m_IsFishing(false),
	m_CanFly(false),
	m_EatingFinishTick(-1),
	m_LifetimeTotalXp(0),
	m_CurrentXp(0),
	m_IsChargingBow(false),
	m_BowCharge(0),
	m_FloaterID(cEntity::INVALID_ID),
	m_Team(nullptr),
	m_bIsInBed(false),
	m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL),
	m_bIsTeleporting(false),
	m_UUID((a_Client != nullptr) ? a_Client->GetUUID() : cUUID{}),
	m_SkinParts(0),
	m_MainHand(mhRight)
{
	ASSERT(GetName().length() <= 16);  // Otherwise this player could crash many clients...

	m_InventoryWindow = new cInventoryWindow(*this);
	m_CurrentWindow = m_InventoryWindow;
	m_InventoryWindow->OpenedByPlayer(*this);

	SetMaxHealth(MAX_HEALTH);
	m_Health = MAX_HEALTH;

	cWorld * World = nullptr;
	if (!LoadFromDisk(World))
	{
		m_Inventory.Clear();
		SetPosX(World->GetSpawnX());
		SetPosY(World->GetSpawnY());
		SetPosZ(World->GetSpawnZ());

		// This is a new player. Set the player spawn point to the spawn point of the default world
		SetBedPos(Vector3i(static_cast<int>(World->GetSpawnX()), static_cast<int>(World->GetSpawnY()), static_cast<int>(World->GetSpawnZ())), World);

		m_EnchantmentSeed = GetRandomProvider().RandInt<unsigned int>();  // Use a random number to seed the enchantment generator

		FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}",
			GetName(), GetPosition()
		);
	}

	m_LastGroundHeight = static_cast<float>(GetPosY());
	m_Stance = GetPosY() + 1.62;

	if (m_GameMode == gmNotSet)
	{
		if (World->IsGameModeCreative())
		{
			m_CanFly = true;
		}
		if (World->IsGameModeSpectator())  // Otherwise Player will fall out of the world on join
		{
			m_CanFly = true;
			m_IsFlying = true;
		}
	}

	if (m_GameMode == gmSpectator)  // If player is reconnecting to the server in spectator mode
	{
		m_CanFly = true;
		m_IsFlying = true;
		m_bVisible = false;
	}
}





cPlayer::~cPlayer(void)
{
	LOGD("Deleting cPlayer \"%s\" at %p, ID %d", GetName().c_str(), static_cast<void *>(this), GetUniqueID());

	// "Times ragequit":
	m_Stats.AddValue(Statistic::LeaveGame);

	SaveToDisk();

	delete m_InventoryWindow;

	LOGD("Player %p deleted", static_cast<void *>(this));
}





int cPlayer::CalcLevelFromXp(int a_XpTotal)
{
	// level 0 to 15
	if (a_XpTotal <= XP_TO_LEVEL15)
	{
		return a_XpTotal / XP_PER_LEVEL_TO15;
	}

	// level 30+
	if (a_XpTotal > XP_TO_LEVEL30)
	{
		return static_cast<int>((151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7);
	}

	// level 16 to 30
	return static_cast<int>((29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal)))) / 3);
}





const std::set<UInt32> & cPlayer::GetKnownRecipes() const
{
	return m_KnownRecipes;
}





int cPlayer::XpForLevel(int a_Level)
{
	// level 0 to 15
	if (a_Level <= 15)
	{
		return a_Level * XP_PER_LEVEL_TO15;
	}

	// level 30+
	if (a_Level >= 31)
	{
		return static_cast<int>((3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220);
	}

	// level 16 to 30
	return static_cast<int>((1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360);
}





int cPlayer::GetXpLevel() const
{
	return CalcLevelFromXp(m_CurrentXp);
}





float cPlayer::GetXpPercentage() const
{
	int currentLevel = CalcLevelFromXp(m_CurrentXp);
	int currentLevel_XpBase = XpForLevel(currentLevel);

	return static_cast<float>(m_CurrentXp - currentLevel_XpBase) /
		static_cast<float>(XpForLevel(1 + currentLevel) - currentLevel_XpBase);
}





bool cPlayer::SetCurrentExperience(int a_CurrentXp)
{
	if (!(a_CurrentXp >= 0) || (a_CurrentXp > (std::numeric_limits<int>::max() - m_LifetimeTotalXp)))
	{
		LOGWARNING("Tried to update experiece with an invalid Xp value: %d", a_CurrentXp);
		return false;  // oops, they gave us a dodgey number
	}

	m_CurrentXp = a_CurrentXp;

	// Update experience:
	m_ClientHandle->SendExperience();

	return true;
}





int cPlayer::DeltaExperience(int a_Xp_delta)
{
	if (a_Xp_delta > (std::numeric_limits<int>().max() - m_CurrentXp))
	{
		// Value was bad, abort and report
		LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the int datatype. Ignoring.", a_Xp_delta);
		return -1;  // Should we instead just return the current Xp?
	}

	m_CurrentXp += a_Xp_delta;

	// Make sure they didn't subtract too much
	m_CurrentXp = std::max(m_CurrentXp, 0);


	// Update total for score calculation
	if (a_Xp_delta > 0)
	{
		m_LifetimeTotalXp += a_Xp_delta;
	}

	LOGD("Player \"%s\" gained / lost %d experience, total is now: %d", GetName().c_str(), a_Xp_delta, m_CurrentXp);

	// Set experience to be updated:
	m_ClientHandle->SendExperience();

	return m_CurrentXp;
}





void cPlayer::StartChargingBow(void)
{
	LOGD("Player \"%s\" started charging their bow", GetName().c_str());
	m_IsChargingBow = true;
	m_BowCharge = 0;
	m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
}





int cPlayer::FinishChargingBow(void)
{
	LOGD("Player \"%s\" finished charging their bow at a charge of %d", GetName().c_str(), m_BowCharge);
	int res = m_BowCharge;
	m_IsChargingBow = false;
	m_BowCharge = 0;
	m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());

	return res;
}





void cPlayer::CancelChargingBow(void)
{
	LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge);
	m_IsChargingBow = false;
	m_BowCharge = 0;
	m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
}





void cPlayer::SetTouchGround(bool a_bTouchGround)
{
	if (IsGameModeSpectator())  // You can fly through the ground in Spectator
	{
		return;
	}

	UNUSED(a_bTouchGround);
	/* Unfortunately whatever the reason, there are still desyncs in on-ground status between the client and server. For example:
		1. Walking off a ledge (whatever height)
		2. Initial login
	Thus, it is too risky to compare their value against ours and kick them for hacking */
}





void cPlayer::Heal(int a_Health)
{
	Super::Heal(a_Health);
	m_ClientHandle->SendHealth();
}





void cPlayer::SetFoodLevel(int a_FoodLevel)
{
	int FoodLevel = Clamp(a_FoodLevel, 0, MAX_FOOD_LEVEL);

	if (cRoot::Get()->GetPluginManager()->CallHookPlayerFoodLevelChange(*this, FoodLevel))
	{
		m_FoodSaturationLevel = 5.0;
		return;
	}

	m_FoodLevel = FoodLevel;
	m_ClientHandle->SendHealth();
}





void cPlayer::SetFoodSaturationLevel(double a_FoodSaturationLevel)
{
	m_FoodSaturationLevel = Clamp(a_FoodSaturationLevel, 0.0, static_cast<double>(m_FoodLevel));
}





void cPlayer::SetFoodTickTimer(int a_FoodTickTimer)
{
	m_FoodTickTimer = a_FoodTickTimer;
}





void cPlayer::SetFoodExhaustionLevel(double a_FoodExhaustionLevel)
{
	m_FoodExhaustionLevel = Clamp(a_FoodExhaustionLevel, 0.0, 40.0);
}





bool cPlayer::Feed(int a_Food, double a_Saturation)
{
	if (IsSatiated())
	{
		return false;
	}

	SetFoodSaturationLevel(m_FoodSaturationLevel + a_Saturation);
	SetFoodLevel(m_FoodLevel + a_Food);
	return true;
}





void cPlayer::AddFoodExhaustion(double a_Exhaustion)
{
	if (!(IsGameModeCreative() || IsGameModeSpectator()))
	{
		m_FoodExhaustionLevel = std::min(m_FoodExhaustionLevel + a_Exhaustion, 40.0);
	}
}





void cPlayer::TossItems(const cItems & a_Items)
{
	if (IsGameModeSpectator())  // Players can't toss items in spectator
	{
		return;
	}

	m_Stats.AddValue(Statistic::Drop, static_cast<cStatManager::StatValue>(a_Items.Size()));

	const auto Speed = (GetLookVector() + Vector3d(0, 0.2, 0)) * 6;  // A dash of height and a dollop of speed
	const auto Position = GetEyePosition() - Vector3d(0, 0.2, 0);  // Correct for eye-height weirdness
	m_World->SpawnItemPickups(a_Items, Position, Speed, true);  // 'true' because created by player
}





void cPlayer::StartEating(void)
{
	// Set the timer:
	m_EatingFinishTick = m_World->GetWorldAge() + EATING_TICKS;

	// Send the packets:
	m_World->BroadcastEntityAnimation(*this, 3);
	m_World->BroadcastEntityMetadata(*this);
}





void cPlayer::FinishEating(void)
{
	// Reset the timer:
	m_EatingFinishTick = -1;

	// Send the packets:
	m_ClientHandle->SendEntityStatus(*this, esPlayerEatingAccepted);
	m_World->BroadcastEntityMetadata(*this);

	// consume the item:
	cItem Item(GetEquippedItem());
	Item.m_ItemCount = 1;
	cItemHandler * ItemHandler = cItemHandler::GetItemHandler(Item.m_ItemType);
	if (!ItemHandler->EatItem(this, &Item))
	{
		return;
	}
	ItemHandler->OnFoodEaten(m_World, this, &Item);
}





void cPlayer::AbortEating(void)
{
	m_EatingFinishTick = -1;
	m_World->BroadcastEntityMetadata(*this);
}





void cPlayer::ClearInventoryPaintSlots(void)
{
	// Clear the list of slots that are being inventory-painted. Used by cWindow only
	m_InventoryPaintSlots.clear();
}





void cPlayer::AddInventoryPaintSlot(int a_SlotNum)
{
	// Add a slot to the list for inventory painting. Used by cWindow only
	m_InventoryPaintSlots.push_back(a_SlotNum);
}





const cSlotNums & cPlayer::GetInventoryPaintSlots(void) const
{
	// Return the list of slots currently stored for inventory painting. Used by cWindow only
	return m_InventoryPaintSlots;
}





double cPlayer::GetMaxSpeed(void) const
{
	if (m_IsFlying)
	{
		return m_FlyingMaxSpeed;
	}
	else if (m_IsSprinting)
	{
		return m_SprintingMaxSpeed;
	}
	else
	{
		return m_NormalMaxSpeed;
	}
}





void cPlayer::SetNormalMaxSpeed(double a_Speed)
{
	m_NormalMaxSpeed = a_Speed;
	if (!m_IsSprinting && !m_IsFlying && !m_IsFrozen)
	{
		// If we are frozen, we do not send this yet. We send when unfreeze() is called
		m_ClientHandle->SendPlayerMaxSpeed();
	}
}





void cPlayer::SetSprintingMaxSpeed(double a_Speed)
{
	m_SprintingMaxSpeed = a_Speed;
	if (m_IsSprinting && !m_IsFlying && !m_IsFrozen)
	{
		// If we are frozen, we do not send this yet. We send when unfreeze() is called
		m_ClientHandle->SendPlayerMaxSpeed();
	}
}





void cPlayer::SetFlyingMaxSpeed(double a_Speed)
{
	m_FlyingMaxSpeed = a_Speed;

	// Update the flying speed, always:
	if (!m_IsFrozen)
	{
		// If we are frozen, we do not send this yet. We send when unfreeze() is called
		m_ClientHandle->SendPlayerAbilities();
	}
}





void cPlayer::SetCrouch(bool a_IsCrouched)
{
	// Set the crouch status, broadcast to all visible players
	if (a_IsCrouched == m_IsCrouched)
	{
		// No change
		return;
	}

	if (a_IsCrouched)
	{
		cRoot::Get()->GetPluginManager()->CallHookPlayerCrouched(*this);
	}

	m_IsCrouched = a_IsCrouched;
	m_World->BroadcastEntityMetadata(*this);
}





void cPlayer::SetSprint(bool a_IsSprinting)
{
	if (a_IsSprinting == m_IsSprinting)
	{
		// No change
		return;
	}

	m_IsSprinting = a_IsSprinting;
	m_ClientHandle->SendPlayerMaxSpeed();
}





void cPlayer::SetCanFly(bool a_CanFly)
{
	if (a_CanFly == m_CanFly)
	{
		return;
	}

	m_CanFly = a_CanFly;
	m_ClientHandle->SendPlayerAbilities();
}





void cPlayer::SetCustomName(const AString & a_CustomName)
{
	if (m_CustomName == a_CustomName)
	{
		return;
	}

	m_World->BroadcastPlayerListRemovePlayer(*this);

	m_CustomName = a_CustomName;
	if (m_CustomName.length() > 16)
	{
		m_CustomName = m_CustomName.substr(0, 16);
	}

	m_World->BroadcastPlayerListAddPlayer(*this);
	m_World->BroadcastSpawnEntity(*this, m_ClientHandle.get());
}





void cPlayer::SetBedPos(const Vector3i & a_Pos)
{
	m_LastBedPos = a_Pos;
	m_SpawnWorldName = m_World->GetName();
}





void cPlayer::SetBedPos(const Vector3i & a_Pos, cWorld * a_World)
{
	m_LastBedPos = a_Pos;
	ASSERT(a_World != nullptr);
	m_SpawnWorldName = a_World->GetName();
}





cWorld * cPlayer::GetBedWorld()
{
	if (const auto World = cRoot::Get()->GetWorld(m_SpawnWorldName); World != nullptr)
	{
		return World;
	}

	return cRoot::Get()->GetDefaultWorld();
}





void cPlayer::SetFlying(bool a_IsFlying)
{
	if (a_IsFlying == m_IsFlying)
	{
		return;
	}

	m_IsFlying = a_IsFlying;
	if (!m_IsFrozen)
	{
		// If we are frozen, we do not send this yet. We send when unfreeze() is called
		m_ClientHandle->SendPlayerAbilities();
	}
}





void cPlayer::NotifyNearbyWolves(cPawn * a_Opponent, bool a_IsPlayerInvolved)
{
	ASSERT(a_Opponent != nullptr);

	m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), 16), [&] (cEntity & a_Entity)
		{
			if (a_Entity.IsMob())
			{
				auto & Mob = static_cast<cMonster&>(a_Entity);
				if (Mob.GetMobType() == mtWolf)
				{
					auto & Wolf = static_cast<cWolf&>(Mob);
					Wolf.ReceiveNearbyFightInfo(GetUUID(), a_Opponent, a_IsPlayerInvolved);
				}
			}
			return false;
		}
	);
}





void cPlayer::KilledBy(TakeDamageInfo & a_TDI)
{
	Super::KilledBy(a_TDI);

	if (m_Health > 0)
	{
		return;  //  not dead yet =]
	}

	m_bVisible = false;  // So new clients don't see the player

	// Detach player from object / entity. If the player dies, the server still says
	// that the player is attached to the entity / object
	Detach();

	// Puke out all the items
	cItems Pickups;
	m_Inventory.CopyToItems(Pickups);
	m_Inventory.Clear();

	if (GetName() == "Notch")
	{
		Pickups.Add(cItem(E_ITEM_RED_APPLE));
	}
	m_Stats.AddValue(Statistic::Drop, static_cast<cStatManager::StatValue>(Pickups.Size()));

	m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10);
	SaveToDisk();  // Save it, yeah the world is a tough place !
	cPluginManager * PluginManager = cRoot::Get()->GetPluginManager();

	if (a_TDI.Attacker == nullptr)
	{
		const AString DamageText = [&]
			{
				switch (a_TDI.DamageType)
				{
					case dtRangedAttack:    return "was shot";
					case dtLightning:       return "was plasmified by lightining";
					case dtFalling:         return GetRandomProvider().RandBool() ? "fell to death" : "hit the ground too hard";
					case dtDrowning:        return "drowned";
					case dtSuffocating:     return GetRandomProvider().RandBool() ? "git merge'd into a block" : "fused with a block";
					case dtStarving:        return "forgot the importance of food";
					case dtCactusContact:   return "was impaled on a cactus";
					case dtMagmaContact:    return "discovered the floor was lava";
					case dtLavaContact:     return "was melted by lava";
					case dtPoisoning:       return "died from septicaemia";
					case dtWithering:       return "is a husk of their former selves";
					case dtOnFire:          return "forgot to stop, drop, and roll";
					case dtFireContact:     return "burnt themselves to death";
					case dtInVoid:          return "somehow fell out of the world";
					case dtPotionOfHarming: return "was magicked to death";
					case dtEnderPearl:      return "misused an ender pearl";
					case dtAdmin:           return "was administrator'd";
					case dtExplosion:       return "blew up";
					case dtAttack:          return "was attacked by thin air";
					case dtEnvironment:     return "played too much dress up";  // This is not vanilla - added a own pun
				}
				UNREACHABLE("Unsupported damage type");
			}();
		AString DeathMessage = Printf("%s %s", GetName().c_str(), DamageText.c_str());
		PluginManager->CallHookKilled(*this, a_TDI, DeathMessage);
		if (DeathMessage != AString(""))
		{
			GetWorld()->BroadcastChatDeath(DeathMessage);
		}
	}
	else if (a_TDI.Attacker->IsPlayer())
	{
		cPlayer * Killer = static_cast<cPlayer *>(a_TDI.Attacker);
		AString DeathMessage = Printf("%s was killed by %s", GetName().c_str(), Killer->GetName().c_str());
		PluginManager->CallHookKilled(*this, a_TDI, DeathMessage);
		if (DeathMessage != AString(""))
		{
			GetWorld()->BroadcastChatDeath(DeathMessage);
		}
	}
	else
	{
		AString KillerClass = a_TDI.Attacker->GetClass();
		KillerClass.erase(KillerClass.begin());  // Erase the 'c' of the class (e.g. "cWitch" -> "Witch")
		AString DeathMessage = Printf("%s was killed by a %s", GetName().c_str(), KillerClass.c_str());
		PluginManager->CallHookKilled(*this, a_TDI, DeathMessage);
		if (DeathMessage != AString(""))
		{
			GetWorld()->BroadcastChatDeath(DeathMessage);
		}
	}

	m_Stats.AddValue(Statistic::Deaths);
	m_Stats.SetValue(Statistic::TimeSinceDeath, 0);

	m_World->GetScoreBoard().AddPlayerScore(GetName(), cObjective::otDeathCount, 1);
}





void cPlayer::Killed(cEntity * a_Victim)
{
	cScoreboard & ScoreBoard = m_World->GetScoreBoard();

	if (a_Victim->IsPlayer())
	{
		m_Stats.AddValue(Statistic::PlayerKills);

		ScoreBoard.AddPlayerScore(GetName(), cObjective::otPlayerKillCount, 1);
	}
	else if (a_Victim->IsMob())
	{
		if (static_cast<cMonster *>(a_Victim)->GetMobFamily() == cMonster::mfHostile)
		{
			AwardAchievement(Statistic::AchKillEnemy);
		}

		m_Stats.AddValue(Statistic::MobKills);
	}

	ScoreBoard.AddPlayerScore(GetName(), cObjective::otTotalKillCount, 1);
}





void cPlayer::Respawn(void)
{
	ASSERT(m_World != nullptr);

	m_Health = GetMaxHealth();
	SetInvulnerableTicks(20);

	// Reset food level:
	m_FoodLevel = MAX_FOOD_LEVEL;
	m_FoodSaturationLevel = 5.0;
	m_FoodExhaustionLevel = 0.0;

	// Reset Experience
	m_CurrentXp = 0;
	m_LifetimeTotalXp = 0;
	// ToDo: send score to client? How?

	// Extinguish the fire:
	StopBurning();

	if (const auto BedWorld = GetBedWorld(); m_World != BedWorld)
	{
		MoveToWorld(*BedWorld, GetLastBedPos(), false, false);
	}
	else
	{
		m_ClientHandle->SendRespawn(m_World->GetDimension(), true);
		TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z);
	}

	SetVisible(true);
}





double cPlayer::GetEyeHeight(void) const
{
	return m_Stance;
}





Vector3d cPlayer::GetEyePosition(void) const
{
	return Vector3d( GetPosX(), m_Stance, GetPosZ());
}





bool cPlayer::IsGameModeCreative(void) const
{
	return (GetEffectiveGameMode() == gmCreative);
}





bool cPlayer::IsGameModeSurvival(void) const
{
	return (GetEffectiveGameMode() == gmSurvival);
}





bool cPlayer::IsGameModeAdventure(void) const
{
	return (GetEffectiveGameMode() == gmAdventure);
}





bool cPlayer::IsGameModeSpectator(void) const
{
	return (GetEffectiveGameMode() == gmSpectator);
}





bool cPlayer::CanMobsTarget(void) const
{
	return (IsGameModeSurvival() || IsGameModeAdventure()) && (m_Health > 0);
}





AString cPlayer::GetIP(void) const
{
	return m_ClientHandle->GetIPString();
}





void cPlayer::SetTeam(cTeam * a_Team)
{
	if (m_Team == a_Team)
	{
		return;
	}

	if (m_Team)
	{
		m_Team->RemovePlayer(GetName());
	}

	m_Team = a_Team;

	if (m_Team)
	{
		m_Team->AddPlayer(GetName());
	}
}





cTeam * cPlayer::UpdateTeam(void)
{
	if (m_World == nullptr)
	{
		SetTeam(nullptr);
	}
	else
	{
		cScoreboard & Scoreboard = m_World->GetScoreBoard();

		SetTeam(Scoreboard.QueryPlayerTeam(GetName()));
	}

	return m_Team;
}





void cPlayer::OpenWindow(cWindow & a_Window)
{
	if (cRoot::Get()->GetPluginManager()->CallHookPlayerOpeningWindow(*this, a_Window))
	{
		return;
	}

	if (&a_Window != m_CurrentWindow)
	{
		CloseWindow(false);
	}

	a_Window.OpenedByPlayer(*this);
	m_CurrentWindow = &a_Window;
	a_Window.SendWholeWindow(*GetClientHandle());
}





void cPlayer::CloseWindow(bool a_CanRefuse)
{
	if (m_CurrentWindow == nullptr)
	{
		m_CurrentWindow = m_InventoryWindow;
		return;
	}

	if (m_CurrentWindow->ClosedByPlayer(*this, a_CanRefuse) || !a_CanRefuse)
	{
		// Close accepted, go back to inventory window (the default):
		m_CurrentWindow = m_InventoryWindow;
	}
	else
	{
		// Re-open the window
		m_CurrentWindow->OpenedByPlayer(*this);
		m_CurrentWindow->SendWholeWindow(*GetClientHandle());
	}
}





void cPlayer::CloseWindowIfID(char a_WindowID, bool a_CanRefuse)
{
	if ((m_CurrentWindow == nullptr) || (m_CurrentWindow->GetWindowID() != a_WindowID))
	{
		return;
	}
	CloseWindow();
}





void cPlayer::SendMessage(const AString & a_Message)
{
	m_ClientHandle->SendChat(a_Message, mtCustom);
}





void cPlayer::SendMessageInfo(const AString & a_Message)
{
	m_ClientHandle->SendChat(a_Message, mtInformation);
}





void cPlayer::SendMessageFailure(const AString & a_Message)
{
	m_ClientHandle->SendChat(a_Message, mtFailure);
}





void cPlayer::SendMessageSuccess(const AString & a_Message)
{
	m_ClientHandle->SendChat(a_Message, mtSuccess);
}





void cPlayer::SendMessageWarning(const AString & a_Message)
{
	m_ClientHandle->SendChat(a_Message, mtWarning);
}





void cPlayer::SendMessageFatal(const AString & a_Message)
{
	m_ClientHandle->SendChat(a_Message, mtFailure);
}





void cPlayer::SendMessagePrivateMsg(const AString & a_Message, const AString & a_Sender)
{
	m_ClientHandle->SendChat(a_Message, mtPrivateMessage, a_Sender);
}





void cPlayer::SendMessage(const cCompositeChat & a_Message)
{
	m_ClientHandle->SendChat(a_Message);
}





void cPlayer::SendMessageRaw(const AString & a_MessageRaw, eChatType a_Type)
{
	m_ClientHandle->SendChatRaw(a_MessageRaw, a_Type);
}





void cPlayer::SendSystemMessage(const AString & a_Message)
{
	m_ClientHandle->SendChatSystem(a_Message, mtCustom);
}





void cPlayer::SendAboveActionBarMessage(const AString & a_Message)
{
	m_ClientHandle->SendChatAboveActionBar(a_Message, mtCustom);
}





void cPlayer::SendSystemMessage(const cCompositeChat & a_Message)
{
	m_ClientHandle->SendChatSystem(a_Message);
}





void cPlayer::SendAboveActionBarMessage(const cCompositeChat & a_Message)
{
	m_ClientHandle->SendChatAboveActionBar(a_Message);
}





const AString & cPlayer::GetName(void) const
{
	return m_ClientHandle->GetUsername();
}





void cPlayer::SetGameMode(eGameMode a_GameMode)
{
	if ((a_GameMode < gmMin) || (a_GameMode >= gmMax))
	{
		LOGWARNING("%s: Setting invalid gamemode: %d", GetName().c_str(), a_GameMode);
		return;
	}

	if (m_GameMode == a_GameMode)
	{
		// Gamemode already set
		return;
	}

	// Detach, if the player is switching from or to the spectator mode
	if ((m_GameMode == gmSpectator) || (a_GameMode == gmSpectator))
	{
		Detach();
	}

	m_GameMode = a_GameMode;
	m_ClientHandle->SendGameMode(a_GameMode);

	SetCapabilities();

	m_World->BroadcastPlayerListUpdateGameMode(*this);
}





void cPlayer::SetCapabilities()
{
	// Fly ability
	if (IsGameModeCreative() || IsGameModeSpectator())
	{
		SetCanFly(true);
	}
	else
	{
		SetFlying(false);
		SetCanFly(false);
	}

	// Visible
	if (IsGameModeSpectator())
	{
		SetVisible(false);
	}
	else
	{
		SetVisible(true);
	}

	// Set for spectator
	if (IsGameModeSpectator())
	{
		// Clear the current dragging item of the player
		if (GetWindow() != nullptr)
		{
			m_DraggingItem.Empty();
			GetClientHandle()->SendInventorySlot(-1, -1, m_DraggingItem);
		}
	}
}





void cPlayer::AwardAchievement(const Statistic a_Ach)
{
	// Check if the prerequisites are met:
	if (!m_Stats.SatisfiesPrerequisite(a_Ach))
	{
		return;
	}

	// Increment the statistic and check if we already have it:
	if (m_Stats.AddValue(a_Ach) != 1)
	{
		return;
	}

	if (m_World->ShouldBroadcastAchievementMessages())
	{
		cCompositeChat Msg;
		Msg.SetMessageType(mtSuccess);
		// TODO: cCompositeChat should not use protocol-specific strings
		// Msg.AddShowAchievementPart(GetName(), nameNew);
		Msg.AddTextPart("Achivement get!");
		m_World->BroadcastChat(Msg);
	}

	// Achievement Get!
	m_ClientHandle->SendStatistics(m_Stats);
}





void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
{
	//  ask plugins to allow teleport to the new position.
	if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ)))
	{
		SetPosition({a_PosX, a_PosY, a_PosZ});
		FreezeInternal(GetPosition(), false);
		m_bIsTeleporting = true;

		m_ClientHandle->SendPlayerMoveLook();
	}
}





void cPlayer::Freeze(const Vector3d & a_Location)
{
	FreezeInternal(a_Location, true);
}





bool cPlayer::IsFrozen()
{
	return m_IsFrozen;
}





void cPlayer::Unfreeze()
{
	GetClientHandle()->SendPlayerAbilities();
	GetClientHandle()->SendPlayerMaxSpeed();

	m_IsFrozen = false;
	BroadcastMovementUpdate(m_ClientHandle.get());
	GetClientHandle()->SendPlayerPosition();
}





void cPlayer::SendRotation(double a_YawDegrees, double a_PitchDegrees)
{
	SetYaw(a_YawDegrees);
	SetPitch(a_PitchDegrees);
	m_ClientHandle->SendPlayerMoveLook();
}





void cPlayer::SpectateEntity(cEntity * a_Target)
{
	if ((a_Target == nullptr) || (static_cast<cEntity *>(this) == a_Target))
	{
		GetClientHandle()->SendCameraSetTo(*this);
		m_AttachedTo = nullptr;
		return;
	}

	m_AttachedTo = a_Target;
	GetClientHandle()->SendCameraSetTo(*m_AttachedTo);
}





Vector3d cPlayer::GetThrowStartPos(void) const
{
	Vector3d res = GetEyePosition();

	// Adjust the position to be just outside the player's bounding box:
	res.x += 0.16 * cos(GetPitch());
	res.y += -0.1;
	res.z += 0.16 * sin(GetPitch());

	return res;
}





Vector3d cPlayer::GetThrowSpeed(double a_SpeedCoeff) const
{
	Vector3d res = GetLookVector();
	res.Normalize();

	// TODO: Add a slight random change (+-0.0075 in each direction)

	return res * a_SpeedCoeff;
}





eGameMode cPlayer::GetEffectiveGameMode(void) const
{
	// Since entities' m_World aren't set until Initialize, but cClientHandle sends the player's gamemode early
	// the below block deals with m_World being nullptr when called.

	auto World = m_World;

	if (World == nullptr)
	{
		World = cRoot::Get()->GetDefaultWorld();
	}
	else if (IsWorldChangeScheduled())
	{
		World = m_WorldChangeInfo.m_NewWorld;
	}

	return (m_GameMode == gmNotSet) ? World->GetGameMode() : m_GameMode;
}





void cPlayer::ForceSetSpeed(const Vector3d & a_Speed)
{
	SetSpeed(a_Speed);
}





void cPlayer::SetVisible(bool a_bVisible)
{
	// Need to Check if the player or other players are in gamemode spectator, but will break compatibility
	if (a_bVisible && !m_bVisible)  // Make visible
	{
		m_bVisible = true;
		m_World->BroadcastSpawnEntity(*this);
	}
	if (!a_bVisible && m_bVisible)
	{
		m_bVisible = false;
		m_World->BroadcastDestroyEntity(*this, m_ClientHandle.get());  // Destroy on all clients
	}
}





MTRand cPlayer::GetEnchantmentRandomProvider()
{
	return m_EnchantmentSeed;
}





void cPlayer::PermuteEnchantmentSeed()
{
	// Get a new random integer and save that as the seed:
	m_EnchantmentSeed = GetRandomProvider().RandInt<unsigned int>();
}





bool cPlayer::HasPermission(const AString & a_Permission)
{
	if (a_Permission.empty())
	{
		// Empty permission request is always granted
		return true;
	}

	AStringVector Split = StringSplit(a_Permission, ".");

	// Iterate over all restrictions; if any matches, then return failure:
	for (auto & Restriction: m_SplitRestrictions)
	{
		if (PermissionMatches(Split, Restriction))
		{
			return false;
		}
	}  // for Restriction - m_SplitRestrictions[]

	// Iterate over all granted permissions; if any matches, then return success:
	for (auto & Permission: m_SplitPermissions)
	{
		if (PermissionMatches(Split, Permission))
		{
			return true;
		}
	}  // for Permission - m_SplitPermissions[]

	// No granted permission matches
	return false;
}





bool cPlayer::PermissionMatches(const AStringVector & a_Permission, const AStringVector & a_Template)
{
	// Check the sub-items if they are the same or there's a wildcard:
	size_t lenP = a_Permission.size();
	size_t lenT = a_Template.size();
	size_t minLen = std::min(lenP, lenT);
	for (size_t i = 0; i < minLen; i++)
	{
		if (a_Template[i] == "*")
		{
			// Has matched so far and now there's a wildcard in the template, so the permission matches:
			return true;
		}
		if (a_Permission[i] != a_Template[i])
		{
			// Found a mismatch
			return false;
		}
	}

	// So far all the sub-items have matched
	// If the sub-item count is the same, then the permission matches
	return (lenP == lenT);
}





AString cPlayer::GetColor(void) const
{
	if (m_MsgNameColorCode.empty() || (m_MsgNameColorCode == "-"))
	{
		// Color has not been assigned, return an empty string:
		return AString();
	}

	// Return the color, including the delimiter:
	return cChatColor::Delimiter + m_MsgNameColorCode;
}





AString cPlayer::GetPrefix(void) const
{
	return m_MsgPrefix;
}





AString cPlayer::GetSuffix(void) const
{
	return m_MsgSuffix;
}





AString cPlayer::GetPlayerListName(void) const
{
	const AString & Color = GetColor();

	if (HasCustomName())
	{
		return m_CustomName;
	}
	else if ((GetName().length() <= 14) && !Color.empty())
	{
		return Printf("%s%s", Color.c_str(), GetName().c_str());
	}
	else
	{
		return GetName();
	}
}





void cPlayer::SetDraggingItem(const cItem & a_Item)
{
	if (GetWindow() != nullptr)
	{
		m_DraggingItem = a_Item;
		GetClientHandle()->SendInventorySlot(-1, -1, m_DraggingItem);
	}
}





void cPlayer::TossEquippedItem(char a_Amount)
{
	cItems Drops;
	cItem DroppedItem(GetInventory().GetEquippedItem());
	if (!DroppedItem.IsEmpty())
	{
		char NewAmount = a_Amount;
		if (NewAmount > GetInventory().GetEquippedItem().m_ItemCount)
		{
			NewAmount = GetInventory().GetEquippedItem().m_ItemCount;  // Drop only what's there
		}

		GetInventory().GetHotbarGrid().ChangeSlotCount(GetInventory().GetEquippedSlotNum() /* Returns hotbar subslot, which HotbarGrid takes */, -a_Amount);

		DroppedItem.m_ItemCount = NewAmount;
		Drops.push_back(DroppedItem);
	}

	TossItems(Drops);
}





void cPlayer::ReplaceOneEquippedItemTossRest(const cItem & a_Item)
{
	auto PlacedCount = GetInventory().ReplaceOneEquippedItem(a_Item);
	char ItemCountToToss = a_Item.m_ItemCount - static_cast<char>(PlacedCount);

	if (ItemCountToToss == 0)
	{
		return;
	}

	cItem Pickup = a_Item;
	Pickup.m_ItemCount = ItemCountToToss;
	TossPickup(Pickup);
}





void cPlayer::TossHeldItem(char a_Amount)
{
	cItems Drops;
	cItem & Item = GetDraggingItem();
	if (!Item.IsEmpty())
	{
		char OriginalItemAmount = Item.m_ItemCount;
		Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount);
		Drops.push_back(Item);

		if (OriginalItemAmount > a_Amount)
		{
			Item.m_ItemCount = OriginalItemAmount - a_Amount;
		}
		else
		{
			Item.Empty();
		}
	}

	TossItems(Drops);
}





void cPlayer::TossPickup(const cItem & a_Item)
{
	cItems Drops;
	Drops.push_back(a_Item);

	TossItems(Drops);
}





bool cPlayer::LoadFromDisk(cWorldPtr & a_World)
{
	LoadRank();

	// Load from the UUID file:
	if (LoadFromFile(GetUUIDFileName(m_UUID), a_World))
	{
		return true;
	}

	// Check for old offline UUID filename, if it exists migrate to new filename
	cUUID OfflineUUID = cClientHandle::GenerateOfflineUUID(GetName());
	auto OldFilename = GetUUIDFileName(GetOldStyleOfflineUUID(GetName()));
	auto NewFilename = GetUUIDFileName(m_UUID);
	// Only move if there isn't already a new file
	if (!cFile::IsFile(NewFilename) && cFile::IsFile(OldFilename))
	{
		cFile::CreateFolderRecursive(GetUUIDFolderName(m_UUID));  // Ensure folder exists to move to
		if (
			cFile::Rename(OldFilename, NewFilename) &&
			(m_UUID == OfflineUUID) &&
			LoadFromFile(NewFilename, a_World)
		)
		{
			return true;
		}
	}

	// Load from the offline UUID file, if allowed:
	const char * OfflineUsage = " (unused)";
	if (cRoot::Get()->GetServer()->ShouldLoadOfflinePlayerData())
	{
		OfflineUsage = "";
		if (LoadFromFile(GetUUIDFileName(OfflineUUID), a_World))
		{
			return true;
		}
	}

	// Load from the old-style name-based file, if allowed:
	if (cRoot::Get()->GetServer()->ShouldLoadNamedPlayerData())
	{
		AString OldStyleFileName = Printf("players/%s.json", GetName().c_str());
		if (LoadFromFile(OldStyleFileName, a_World))
		{
			// Save in new format and remove the old file
			if (SaveToDisk())
			{
				cFile::Delete(OldStyleFileName);
			}
			return true;
		}
	}

	// None of the files loaded successfully
	LOG("Player data file not found for %s (%s, offline %s%s), will be reset to defaults.",
		GetName().c_str(), m_UUID.ToShortString().c_str(), OfflineUUID.ToShortString().c_str(), OfflineUsage
	);

	if (a_World == nullptr)
	{
		a_World = cRoot::Get()->GetDefaultWorld();
	}
	return false;
}





bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
{
	// Load the data from the file:
	cFile f;
	if (!f.Open(a_FileName, cFile::fmRead))
	{
		// This is a new player whom we haven't seen yet, bail out, let them have the defaults
		return false;
	}
	AString buffer;
	if (f.ReadRestOfFile(buffer) != f.GetSize())
	{
		LOGWARNING("Cannot read player data from file \"%s\"", a_FileName.c_str());
		return false;
	}
	f.Close();

	// Parse the JSON format:
	Json::Value root;
	AString ParseError;
	if (!JsonUtils::ParseString(buffer, root, &ParseError))
	{
		FLOGWARNING(
			"Cannot parse player data in file \"{0}\":\n  {1}",
			a_FileName, ParseError
		);
		return false;
	}

	// Load the player data:
	Json::Value & JSON_PlayerPosition = root["position"];
	if (JSON_PlayerPosition.size() == 3)
	{
		SetPosX(JSON_PlayerPosition[0].asDouble());
		SetPosY(JSON_PlayerPosition[1].asDouble());
		SetPosZ(JSON_PlayerPosition[2].asDouble());
		m_LastPosition = GetPosition();
	}

	Json::Value & JSON_PlayerRotation = root["rotation"];
	if (JSON_PlayerRotation.size() == 3)
	{
		SetYaw      (static_cast<float>(JSON_PlayerRotation[0].asDouble()));
		SetPitch    (static_cast<float>(JSON_PlayerRotation[1].asDouble()));
		SetRoll     (static_cast<float>(JSON_PlayerRotation[2].asDouble()));
	}

	m_Health              = root.get("health",         0).asFloat();
	m_AirLevel            = root.get("air",            MAX_AIR_LEVEL).asInt();
	m_FoodLevel           = root.get("food",           MAX_FOOD_LEVEL).asInt();
	m_FoodSaturationLevel = root.get("foodSaturation", MAX_FOOD_LEVEL).asDouble();
	m_FoodTickTimer       = root.get("foodTickTimer",  0).asInt();
	m_FoodExhaustionLevel = root.get("foodExhaustion", 0).asDouble();
	m_LifetimeTotalXp     = root.get("xpTotal",        0).asInt();
	m_CurrentXp           = root.get("xpCurrent",      0).asInt();
	m_IsFlying            = root.get("isflying",       0).asBool();
	m_EnchantmentSeed     = root.get("enchantmentSeed", GetRandomProvider().RandInt<unsigned int>()).asUInt();

	Json::Value & JSON_KnownItems = root["knownItems"];
	for (UInt32 i = 0; i < JSON_KnownItems.size(); i++)
	{
		cItem Item;
		Item.FromJson(JSON_KnownItems[i]);
		m_KnownItems.insert(Item);
	}

	const auto & RecipeNameMap = cRoot::Get()->GetCraftingRecipes()->GetRecipeNameMap();

	Json::Value & JSON_KnownRecipes = root["knownRecipes"];
	for (UInt32 i = 0; i < JSON_KnownRecipes.size(); i++)
	{
		auto RecipeId = RecipeNameMap.find(JSON_KnownRecipes[i].asString());
		if (RecipeId != RecipeNameMap.end())
		{
			m_KnownRecipes.insert(RecipeId->second);
		}
	}

	m_GameMode = static_cast<eGameMode>(root.get("gamemode", eGameMode_NotSet).asInt());

	if (m_GameMode == eGameMode_Creative)
	{
		m_CanFly = true;
	}

	m_Inventory.LoadFromJson(root["inventory"]);

	int equippedSlotNum = root.get("equippedItemSlot", 0).asInt();
	m_Inventory.SetEquippedSlotNum(equippedSlotNum);

	cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents);

	m_CurrentWorldName = root.get("world", "world").asString();
	a_World = cRoot::Get()->GetWorld(GetLoadedWorldName());
	if (a_World == nullptr)
	{
		a_World = cRoot::Get()->GetDefaultWorld();
	}


	m_LastBedPos.x = root.get("SpawnX", a_World->GetSpawnX()).asInt();
	m_LastBedPos.y = root.get("SpawnY", a_World->GetSpawnY()).asInt();
	m_LastBedPos.z = root.get("SpawnZ", a_World->GetSpawnZ()).asInt();
	m_SpawnWorldName = root.get("SpawnWorld", cRoot::Get()->GetDefaultWorld()->GetName()).asString();

	try
	{
		// Load the player stats.
		// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
		StatSerializer::Load(m_Stats, m_DefaultWorldPath, GetUUID().ToLongString());
	}
	catch (...)
	{
		LOGWARNING("Failed loading player statistics");
	}

	FLOGD("Player {0} was read from file \"{1}\", spawning at {2:.2f} in world \"{3}\"",
		GetName(), a_FileName, GetPosition(), a_World->GetName()
	);

	return true;
}





void cPlayer::OpenHorseInventory()
{
	if (
		(m_AttachedTo == nullptr) ||
		!m_AttachedTo->IsMob()
	)
	{
		return;
	}

	auto & Mob = static_cast<cMonster &>(*m_AttachedTo);

	if (Mob.GetMobType() != mtHorse)
	{
		return;
	}

	auto & Horse = static_cast<cHorse &>(Mob);
	// The client sends requests for untame horses as well but shouldn't actually open
	if (Horse.IsTame())
	{
		Horse.PlayerOpenWindow(*this);
	}
}





bool cPlayer::SaveToDisk()
{
	cFile::CreateFolderRecursive(GetUUIDFolderName(m_UUID));

	// create the JSON data
	Json::Value JSON_PlayerPosition;
	JSON_PlayerPosition.append(Json::Value(GetPosX()));
	JSON_PlayerPosition.append(Json::Value(GetPosY()));
	JSON_PlayerPosition.append(Json::Value(GetPosZ()));

	Json::Value JSON_PlayerRotation;
	JSON_PlayerRotation.append(Json::Value(GetYaw()));
	JSON_PlayerRotation.append(Json::Value(GetPitch()));
	JSON_PlayerRotation.append(Json::Value(GetRoll()));

	Json::Value JSON_Inventory;
	m_Inventory.SaveToJson(JSON_Inventory);

	Json::Value JSON_EnderChestInventory;
	cEnderChestEntity::SaveToJson(JSON_EnderChestInventory, m_EnderChestContents);

	Json::Value JSON_KnownItems;
	for (const auto & KnownItem : m_KnownItems)
	{
		Json::Value JSON_Item;
		KnownItem.GetJson(JSON_Item);
		JSON_KnownItems.append(JSON_Item);
	}

	Json::Value JSON_KnownRecipes;
	for (auto KnownRecipe : m_KnownRecipes)
	{
		auto Recipe = cRoot::Get()->GetCraftingRecipes()->GetRecipeById(KnownRecipe);
		JSON_KnownRecipes.append(Recipe->m_RecipeName);
	}

	Json::Value root;
	root["position"]            = JSON_PlayerPosition;
	root["rotation"]            = JSON_PlayerRotation;
	root["inventory"]           = JSON_Inventory;
	root["knownItems"]          = JSON_KnownItems;
	root["knownRecipes"]        = JSON_KnownRecipes;
	root["equippedItemSlot"]    = m_Inventory.GetEquippedSlotNum();
	root["enderchestinventory"] = JSON_EnderChestInventory;
	root["health"]              = m_Health;
	root["xpTotal"]             = m_LifetimeTotalXp;
	root["xpCurrent"]           = m_CurrentXp;
	root["air"]                 = m_AirLevel;
	root["food"]                = m_FoodLevel;
	root["foodSaturation"]      = m_FoodSaturationLevel;
	root["foodTickTimer"]       = m_FoodTickTimer;
	root["foodExhaustion"]      = m_FoodExhaustionLevel;
	root["isflying"]            = IsFlying();
	root["lastknownname"]       = GetName();
	root["SpawnX"]              = GetLastBedPos().x;
	root["SpawnY"]              = GetLastBedPos().y;
	root["SpawnZ"]              = GetLastBedPos().z;
	root["SpawnWorld"]          = m_SpawnWorldName;
	root["enchantmentSeed"]     = m_EnchantmentSeed;
	root["world"]               = m_CurrentWorldName;
	root["gamemode"]            = static_cast<int>(m_GameMode);

	auto JsonData = JsonUtils::WriteStyledString(root);
	AString SourceFile = GetUUIDFileName(m_UUID);

	cFile f;
	if (!f.Open(SourceFile, cFile::fmWrite))
	{
		LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot open file. Player will lose their progress.",
			GetName().c_str(), SourceFile.c_str()
		);
		return false;
	}
	if (f.Write(JsonData.c_str(), JsonData.size()) != static_cast<int>(JsonData.size()))
	{
		LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot save data. Player will lose their progress. ",
			GetName().c_str(), SourceFile.c_str()
		);
		return false;
	}

	try
	{
		// Save the player stats.
		// We use the default world name (like bukkit) because stats are shared between dimensions / worlds.
		// TODO: save together with player.dat, not in some other place.
		StatSerializer::Save(m_Stats, m_DefaultWorldPath, GetUUID().ToLongString());
	}
	catch (...)
	{
		LOGWARNING("Could not save stats for player %s", GetName().c_str());
		return false;
	}

	return true;
}





void cPlayer::UseEquippedItem(short a_Damage)
{
	// No durability loss in creative or spectator modes:
	if (IsGameModeCreative() || IsGameModeSpectator())
	{
		return;
	}

	UseItem(cInventory::invHotbarOffset + m_Inventory.GetEquippedSlotNum(), a_Damage);
}





void cPlayer::UseEquippedItem(cItemHandler::eDurabilityLostAction a_Action)
{
	// Get item being used:
	cItem Item = GetEquippedItem();

	// Get base damage for action type:
	short Dmg = cItemHandler::GetItemHandler(Item)->GetDurabilityLossByAction(a_Action);

	UseEquippedItem(Dmg);
}





void cPlayer::UseItem(int a_SlotNumber, short a_Damage)
{
	const cItem & Item = m_Inventory.GetSlot(a_SlotNumber);
	if (Item.IsEmpty())
	{
		return;
	}

	// Ref: https://minecraft.gamepedia.com/Enchanting#Unbreaking
	unsigned int UnbreakingLevel = Item.m_Enchantments.GetLevel(cEnchantments::enchUnbreaking);
	double chance = ItemCategory::IsArmor(Item.m_ItemType)
		? (0.6 + (0.4 / (UnbreakingLevel + 1))) : (1.0 / (UnbreakingLevel + 1));

	// When durability is reduced by multiple points
	// Unbreaking is applied for each point of reduction.
	std::binomial_distribution<short> Dist(a_Damage, chance);
	short ReducedDamage = Dist(GetRandomProvider().Engine());
	if (m_Inventory.DamageItem(a_SlotNumber, ReducedDamage))
	{
		m_World->BroadcastSoundEffect("entity.item.break", GetPosition(), 0.5f, static_cast<float>(0.75 + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
	}
}





void cPlayer::HandleFood(void)
{
	// Ref.: https://minecraft.gamepedia.com/Hunger

	if (IsGameModeCreative() || IsGameModeSpectator())
	{
		// Hunger is disabled for Creative and Spectator
		return;
	}

	// Apply food exhaustion that has accumulated:
	if (m_FoodExhaustionLevel > 4.0)
	{
		m_FoodExhaustionLevel -= 4.0;

		if (m_FoodSaturationLevel > 0.0)
		{
			m_FoodSaturationLevel = std::max(m_FoodSaturationLevel - 1.0, 0.0);
		}
		else
		{
			SetFoodLevel(m_FoodLevel - 1);
		}
	}

	// Heal or damage, based on the food level, using the m_FoodTickTimer:
	if ((m_FoodLevel >= 18) || (m_FoodLevel <= 0))
	{
		m_FoodTickTimer++;
		if (m_FoodTickTimer >= 80)
		{
			m_FoodTickTimer = 0;

			if ((m_FoodLevel >= 18) && (GetHealth() < GetMaxHealth()))
			{
				// Regenerate health from food, incur 3 pts of food exhaustion:
				Heal(1);
				AddFoodExhaustion(3.0);
			}
			else if ((m_FoodLevel <= 0) && (m_Health > 1))
			{
				// Damage from starving
				TakeDamage(dtStarving, nullptr, 1, 1, 0);
			}
		}
	}
	else
	{
		m_FoodTickTimer = 0;
	}
}





void cPlayer::HandleFloater()
{
	if (GetEquippedItem().m_ItemType == E_ITEM_FISHING_ROD)
	{
		return;
	}
	m_World->DoWithEntityByID(m_FloaterID, [](cEntity & a_Entity)
		{
			a_Entity.Destroy();
			return true;
		}
	);
	SetIsFishing(false);
}





bool cPlayer::IsClimbing(void) const
{
	const auto Position = GetPosition().Floor();

	if (!cChunkDef::IsValidHeight(Position.y))
	{
		return false;
	}

	BLOCKTYPE Block = m_World->GetBlock(Position);
	switch (Block)
	{
		case E_BLOCK_LADDER:
		case E_BLOCK_VINES:
		{
			return true;
		}
		default: return false;
	}
}





void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos, bool a_PreviousIsOnGround)
{
	if (m_bIsTeleporting)
	{
		m_bIsTeleporting = false;
		return;
	}

	const auto Value = FloorC<cStatManager::StatValue>(a_DeltaPos.Length() * 100 + 0.5);
	if (m_AttachedTo == nullptr)
	{
		if (IsFlying())
		{
			m_Stats.AddValue(Statistic::FlyOneCm, Value);
			// May be flying and doing any of the following:
		}

		if (IsClimbing())
		{
			if (a_DeltaPos.y > 0.0)  // Going up
			{
				m_Stats.AddValue(Statistic::ClimbOneCm, FloorC<cStatManager::StatValue>(a_DeltaPos.y * 100 + 0.5));
			}
		}
		else if (IsInWater())
		{
			if (m_IsHeadInWater)
			{
				m_Stats.AddValue(Statistic::WalkUnderWaterOneCm, Value);
			}
			else
			{
				m_Stats.AddValue(Statistic::WalkOnWaterOneCm, Value);
			}
			AddFoodExhaustion(0.00015 * static_cast<double>(Value));
		}
		else if (IsOnGround())
		{
			if (IsCrouched())
			{
				m_Stats.AddValue(Statistic::CrouchOneCm, Value);
				AddFoodExhaustion(0.0001 * static_cast<double>(Value));
			}
			if (IsSprinting())
			{
				m_Stats.AddValue(Statistic::SprintOneCm, Value);
				AddFoodExhaustion(0.001 * static_cast<double>(Value));
			}
			else
			{
				m_Stats.AddValue(Statistic::WalkOneCm, Value);
				AddFoodExhaustion(0.0001 * static_cast<double>(Value));
			}
		}
		else
		{
			// If a jump just started, process food exhaustion:
			if ((a_DeltaPos.y > 0.0) && a_PreviousIsOnGround)
			{
				m_Stats.AddValue(Statistic::Jump, 1);
				AddFoodExhaustion((IsSprinting() ? 0.008 : 0.002) * static_cast<double>(Value));
			}
			else if (a_DeltaPos.y < 0.0)
			{
				// Increment statistic
				m_Stats.AddValue(Statistic::FallOneCm, static_cast<cStatManager::StatValue>(std::abs(a_DeltaPos.y) * 100 + 0.5));
			}
			// TODO: good opportunity to detect illegal flight (check for falling tho)
		}
	}
	else
	{
		switch (m_AttachedTo->GetEntityType())
		{
			case cEntity::etMinecart: m_Stats.AddValue(Statistic::MinecartOneCm, Value); break;
			case cEntity::etBoat:     m_Stats.AddValue(Statistic::BoatOneCm,     Value); break;
			case cEntity::etMonster:
			{
				cMonster * Monster = static_cast<cMonster *>(m_AttachedTo);
				switch (Monster->GetMobType())
				{
					case mtPig:   m_Stats.AddValue(Statistic::PigOneCm,   Value); break;
					case mtHorse: m_Stats.AddValue(Statistic::HorseOneCm, Value); break;
					default: break;
				}
				break;
			}
			default: break;
		}
	}
}





void cPlayer::LoadRank(void)
{
	// Load the values from cRankManager:
	cRankManager * RankMgr = cRoot::Get()->GetRankManager();
	m_Rank = RankMgr->GetPlayerRankName(m_UUID);
	if (m_Rank.empty())
	{
		m_Rank = RankMgr->GetDefaultRank();
	}
	else
	{
		// Update the name:
		RankMgr->UpdatePlayerName(m_UUID, GetName());
	}
	m_Permissions = RankMgr->GetPlayerPermissions(m_UUID);
	m_Restrictions = RankMgr->GetPlayerRestrictions(m_UUID);
	RankMgr->GetRankVisuals(m_Rank, m_MsgPrefix, m_MsgSuffix, m_MsgNameColorCode);

	// Break up the individual permissions on each dot, into m_SplitPermissions:
	m_SplitPermissions.clear();
	m_SplitPermissions.reserve(m_Permissions.size());
	for (auto & Permission: m_Permissions)
	{
		m_SplitPermissions.push_back(StringSplit(Permission, "."));
	}  // for Permission - m_Permissions[]

	// Break up the individual restrictions on each dot, into m_SplitRestrictions:
	m_SplitRestrictions.clear();
	m_SplitRestrictions.reserve(m_Restrictions.size());
	for (auto & Restriction: m_Restrictions)
	{
		m_SplitRestrictions.push_back(StringSplit(Restriction, "."));
	}  // for itr - m_Restrictions[]
}





bool cPlayer::PlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
	sSetBlockVector blk{{a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta}};
	return PlaceBlocks(blk);
}





void cPlayer::SendBlocksAround(int a_BlockX, int a_BlockY, int a_BlockZ, int a_Range)
{
	// Collect the coords of all the blocks to send:
	sSetBlockVector blks;
	for (int y = a_BlockY - a_Range + 1; y < a_BlockY + a_Range; y++)
	{
		for (int z = a_BlockZ - a_Range + 1; z < a_BlockZ + a_Range; z++)
		{
			for (int x = a_BlockX - a_Range + 1; x < a_BlockX + a_Range; x++)
			{
				blks.emplace_back(x, y, z, E_BLOCK_AIR, 0);  // Use fake blocktype, it will get set later on.
			}
		}
	}  // for y

	// Get the values of all the blocks:
	if (!m_World->GetBlocks(blks, false))
	{
		LOGD("%s: Cannot query all blocks, not sending an update", __FUNCTION__);
		return;
	}

	// Divide the block changes by their respective chunks:
	std::unordered_map<cChunkCoords, sSetBlockVector, cChunkCoordsHash> Changes;
	for (const auto & blk: blks)
	{
		Changes[cChunkCoords(blk.m_ChunkX, blk.m_ChunkZ)].push_back(blk);
	}  // for blk - blks[]
	blks.clear();

	// Send the blocks for each affected chunk:
	for (auto itr = Changes.cbegin(), end = Changes.cend(); itr != end; ++itr)
	{
		m_ClientHandle->SendBlockChanges(itr->first.m_ChunkX, itr->first.m_ChunkZ, itr->second);
	}
}





bool cPlayer::DoesPlacingBlocksIntersectEntity(const sSetBlockVector & a_Blocks)
{
	// Compute the bounding box for each block to be placed
	std::vector<cBoundingBox> PlacementBoxes;
	cBoundingBox PlacingBounds(0, 0, 0, 0, 0, 0);
	bool HasInitializedBounds = false;
	for (auto blk: a_Blocks)
	{
		int x = blk.GetX();
		int y = blk.GetY();
		int z = blk.GetZ();
		cBoundingBox BlockBox = cBlockHandler::For(blk.m_BlockType).GetPlacementCollisionBox(
			m_World->GetBlock({ x - 1, y, z }),
			m_World->GetBlock({ x + 1, y, z }),
			(y == 0) ? E_BLOCK_AIR : m_World->GetBlock({ x, y - 1, z }),
			(y == cChunkDef::Height - 1) ? E_BLOCK_AIR : m_World->GetBlock({ x, y + 1, z }),
			m_World->GetBlock({ x, y, z - 1 }),
			m_World->GetBlock({ x, y, z + 1 })
		);
		BlockBox.Move(x, y, z);

		PlacementBoxes.push_back(BlockBox);

		if (HasInitializedBounds)
		{
			PlacingBounds = PlacingBounds.Union(BlockBox);
		}
		else
		{
			PlacingBounds = BlockBox;
			HasInitializedBounds = true;
		}
	}

	cWorld * World = GetWorld();

	// Check to see if any entity intersects any block being placed
	return !World->ForEachEntityInBox(PlacingBounds, [&](cEntity & a_Entity)
		{
			// The distance inside the block the entity can still be.
			const double EPSILON = 0.0005;

			if (!a_Entity.DoesPreventBlockPlacement())
			{
				return false;
			}
			auto EntBox = a_Entity.GetBoundingBox();
			for (auto BlockBox : PlacementBoxes)
			{
				// Put in a little bit of wiggle room
				BlockBox.Expand(-EPSILON, -EPSILON, -EPSILON);
				if (EntBox.DoesIntersect(BlockBox))
				{
					return true;
				}
			}
			return false;
		}
	);
}





bool cPlayer::PlaceBlocks(const sSetBlockVector & a_Blocks)
{
	if (DoesPlacingBlocksIntersectEntity(a_Blocks))
	{
		// Abort - re-send all the current blocks in the a_Blocks' coords to the client:
		for (auto blk2: a_Blocks)
		{
			m_World->SendBlockTo(blk2.GetX(), blk2.GetY(), blk2.GetZ(), *this);
		}
		return false;
	}

	// Call the "placing" hooks; if any fail, abort:
	cPluginManager * pm = cPluginManager::Get();
	for (auto blk: a_Blocks)
	{
		if (pm->CallHookPlayerPlacingBlock(*this, blk))
		{
			// Abort - re-send all the current blocks in the a_Blocks' coords to the client:
			for (auto blk2: a_Blocks)
			{
				m_World->SendBlockTo(blk2.GetX(), blk2.GetY(), blk2.GetZ(), *this);
			}
			return false;
		}
	}  // for blk - a_Blocks[]

	cChunkInterface ChunkInterface(m_World->GetChunkMap());
	for (auto blk: a_Blocks)
	{
		// Set the blocks:
		m_World->PlaceBlock(blk.GetAbsolutePos(), blk.m_BlockType, blk.m_BlockMeta);

		// Notify the blockhandlers:
		cBlockHandler::For(blk.m_BlockType).OnPlacedByPlayer(ChunkInterface, *m_World, *this, blk);

		// Call the "placed" hooks:
		pm->CallHookPlayerPlacedBlock(*this, blk);
	}

	return true;
}





void cPlayer::SetSkinParts(int a_Parts)
{
	m_SkinParts = a_Parts & spMask;
	m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
}





void cPlayer::SetMainHand(eMainHand a_Hand)
{
	m_MainHand = a_Hand;
	m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
}





void cPlayer::AttachTo(cEntity * a_AttachTo)
{
	// Different attach, if this is a spectator
	if (IsGameModeSpectator())
	{
		SpectateEntity(a_AttachTo);
		return;
	}

	Super::AttachTo(a_AttachTo);
}





void cPlayer::Detach()
{
	if (m_AttachedTo == nullptr)
	{
		// The player is not attached to anything. Bail out.
		return;
	}

	// Different detach, if this is a spectator
	if (IsGameModeSpectator())
	{
		GetClientHandle()->SendCameraSetTo(*this);
		TeleportToEntity(*m_AttachedTo);
		m_AttachedTo = nullptr;
		return;
	}

	Super::Detach();
	int PosX = POSX_TOINT;
	int PosY = POSY_TOINT;
	int PosZ = POSZ_TOINT;

	// Search for a position within an area to teleport player after detachment
	// Position must be solid land with two air blocks above.
	// If nothing found, player remains where they are
	for (int x = PosX - 1; x <= (PosX + 1); ++x)
	{
		for (int y = PosY; y <= (PosY + 3); ++y)
		{
			for (int z = PosZ - 1; z <= (PosZ + 1); ++z)
			{
				if (
					(m_World->GetBlock({ x, y, z }) == E_BLOCK_AIR) &&
					(m_World->GetBlock({ x, y + 1, z }) == E_BLOCK_AIR) &&
					cBlockInfo::IsSolid(m_World->GetBlock({ x, y - 1, z }))
				)
				{
					TeleportToCoords(x + 0.5, y, z + 0.5);
					return;
				}
			}
		}
	}
}





AString cPlayer::GetUUIDFileName(const cUUID & a_UUID)
{
	AString UUID = a_UUID.ToLongString();

	AString res("players/");
	res.append(UUID, 0, 2);
	res.push_back('/');
	res.append(UUID, 2, AString::npos);
	res.append(".json");
	return res;
}





void cPlayer::FreezeInternal(const Vector3d & a_Location, bool a_ManuallyFrozen)
{
	SetSpeed(0, 0, 0);
	SetPosition(a_Location);
	m_IsFrozen = true;
	m_IsManuallyFrozen = a_ManuallyFrozen;

	double NormalMaxSpeed = GetNormalMaxSpeed();
	double SprintMaxSpeed = GetSprintingMaxSpeed();
	double FlyingMaxpeed = GetFlyingMaxSpeed();
	bool IsFlying = m_IsFlying;

	// Set the client-side speed to 0
	m_NormalMaxSpeed = 0;
	m_SprintingMaxSpeed = 0;
	m_FlyingMaxSpeed = 0;
	m_IsFlying = true;

	// Send the client its fake speed and max speed of 0
	GetClientHandle()->SendPlayerMoveLook();
	GetClientHandle()->SendPlayerAbilities();
	GetClientHandle()->SendPlayerMaxSpeed();
	GetClientHandle()->SendEntityVelocity(*this);

	// Keep the server side speed variables as they were in the first place
	m_NormalMaxSpeed = NormalMaxSpeed;
	m_SprintingMaxSpeed = SprintMaxSpeed;
	m_FlyingMaxSpeed = FlyingMaxpeed;
	m_IsFlying = IsFlying;
}





float cPlayer::GetLiquidHeightPercent(NIBBLETYPE a_Meta)
{
	if (a_Meta >= 8)
	{
		a_Meta = 0;
	}
	return static_cast<float>(a_Meta + 1) / 9.0f;
}





bool cPlayer::IsInsideWater()
{
	BLOCKTYPE Block = m_World->GetBlock(Vector3d(GetPosX(), m_Stance, GetPosZ()).Floor());
	if ((Block != E_BLOCK_WATER) && (Block != E_BLOCK_STATIONARY_WATER))
	{
		return false;
	}
	NIBBLETYPE Meta = GetWorld()->GetBlockMeta(Vector3d(GetPosX(), m_Stance, GetPosZ()).Floor());
	float f = GetLiquidHeightPercent(Meta) - 0.11111111f;
	float f1 = static_cast<float>(m_Stance + 1) - f;
	bool flag = (m_Stance < f1);
	return flag;
}





float cPlayer::GetDigSpeed(BLOCKTYPE a_Block)
{
	// Based on: https://minecraft.gamepedia.com/Breaking#Speed

	// Get the base speed multiplier of the equipped tool for the mined block
	float MiningSpeed = GetEquippedItem().GetHandler()->GetBlockBreakingStrength(a_Block);

	// If we can harvest the block then we can apply material and enchantment bonuses
	if (GetEquippedItem().GetHandler()->CanHarvestBlock(a_Block))
	{
		if (MiningSpeed > 1.0f)  // If the base multiplier for this block is greater than 1, now we can check enchantments
		{
			unsigned int EfficiencyModifier = GetEquippedItem().m_Enchantments.GetLevel(cEnchantments::eEnchantment::enchEfficiency);
			if (EfficiencyModifier > 0)  // If an efficiency enchantment is present, apply formula as on wiki
			{
				MiningSpeed += (EfficiencyModifier * EfficiencyModifier) + 1;
			}
		}
	}
	else  // If we can't harvest the block then no bonuses:
	{
		MiningSpeed = 1;
	}

	// Haste increases speed by 20% per level
	auto Haste = GetEntityEffect(cEntityEffect::effHaste);
	if (Haste != nullptr)
	{
		int intensity = Haste->GetIntensity() + 1;
		MiningSpeed *= 1.0f + (intensity * 0.2f);
	}

	// Mining fatigue decreases speed a lot
	auto MiningFatigue = GetEntityEffect(cEntityEffect::effMiningFatigue);
	if (MiningFatigue != nullptr)
	{
		int intensity = MiningFatigue->GetIntensity();
		switch (intensity)
		{
			case 0:  MiningSpeed *= 0.3f;     break;
			case 1:  MiningSpeed *= 0.09f;    break;
			case 2:  MiningSpeed *= 0.0027f;  break;
			default: MiningSpeed *= 0.00081f; break;

		}
	}

	// 5x speed loss for being in water
	if (IsInsideWater() && !(GetEquippedItem().m_Enchantments.GetLevel(cEnchantments::eEnchantment::enchAquaAffinity) > 0))
	{
		MiningSpeed /= 5.0f;
	}

	// 5x speed loss for not touching ground
	if (!IsOnGround())
	{
		MiningSpeed /= 5.0f;
	}

	return MiningSpeed;
}





float cPlayer::GetMiningProgressPerTick(BLOCKTYPE a_Block)
{
	// Based on https://minecraft.gamepedia.com/Breaking#Calculation
	// If we know it's instantly breakable then quit here:
	if (cBlockInfo::IsOneHitDig(a_Block))
	{
		return 1;
	}

	const bool CanHarvest = GetEquippedItem().GetHandler()->CanHarvestBlock(a_Block);
	const float BlockHardness = cBlockInfo::GetHardness(a_Block) * (CanHarvest ? 1.5f : 5.0f);
	ASSERT(BlockHardness > 0);  // Can't divide by 0 or less, IsOneHitDig should have returned true

	// LOGD("Time to mine block = %f", BlockHardness/DigSpeed);
	// Number of ticks to mine = (20 * BlockHardness)/DigSpeed;
	// Therefore take inverse to get fraction mined per tick:
	return GetDigSpeed(a_Block) / (20.0f * BlockHardness);
}





bool cPlayer::CanInstantlyMine(BLOCKTYPE a_Block)
{
	// Based on: https://minecraft.gamepedia.com/Breaking#Calculation

	// If the dig speed is greater than 30 times the hardness, then the wiki says we can instantly mine:
	return GetDigSpeed(a_Block) > (30 * cBlockInfo::GetHardness(a_Block));
}





void cPlayer::AddKnownItem(const cItem & a_Item)
{
	if (a_Item.m_ItemType < 0)
	{
		return;
	}

	auto Response = m_KnownItems.insert(a_Item.CopyOne());
	if (!Response.second)
	{
		// The item was already known, bail out:
		return;
	}

	// Process the recipes that got unlocked by this newly-known item:
	auto Recipes = cRoot::Get()->GetCraftingRecipes()->FindNewRecipesForItem(a_Item, m_KnownItems);
	for (const auto & RecipeId : Recipes)
	{
		AddKnownRecipe(RecipeId);
	}
}





void cPlayer::AddKnownRecipe(UInt32 a_RecipeId)
{
	auto Response = m_KnownRecipes.insert(a_RecipeId);
	if (!Response.second)
	{
		// The recipe was already known, bail out:
		return;
	}
	m_ClientHandle->SendUnlockRecipe(a_RecipeId);
}





void cPlayer::TickFreezeCode()
{
	if (m_IsFrozen)
	{
		if ((!m_IsManuallyFrozen) && (GetClientHandle()->IsPlayerChunkSent()))
		{
			// If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent
			Unfreeze();

			// Pull the player out of any solids that might have loaded on them.
			PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk()));
			if (RelSuccess)
			{
				int NewY = Rel.y;
				if (NewY < 0)
				{
					NewY = 0;
				}
				while (NewY < cChunkDef::Height - 2)
				{
					// If we find a position with enough space for the player
					if (
						!cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY, Rel.z)) &&
						!cBlockInfo::IsSolid(Chunk->GetBlock(Rel.x, NewY + 1, Rel.z))
					)
					{
						// If the found position is not the same as the original
						if (NewY != Rel.y)
						{
							SetPosition(GetPosition().x, NewY, GetPosition().z);
							GetClientHandle()->SendPlayerPosition();
						}
						break;
					}
					++NewY;
				}
			}
		}
		else if (GetWorld()->GetWorldAge() % 100 == 0)
		{
			// Despite the client side freeze, the player may be able to move a little by
			// Jumping or canceling flight. Re-freeze every now and then
			FreezeInternal(GetPosition(), m_IsManuallyFrozen);
		}
	}
	else
	{
		if (!GetClientHandle()->IsPlayerChunkSent() || (!GetParentChunk()->IsValid()))
		{
			FreezeInternal(GetPosition(), false);
		}
	}
}





void cPlayer::ApplyArmorDamage(int a_DamageBlocked)
{
	short ArmorDamage = static_cast<short>(std::max(a_DamageBlocked / 4, 1));

	for (int i = 0; i < 4; i++)
	{
		UseItem(cInventory::invArmorOffset + i, ArmorDamage);
	}
}





void cPlayer::BroadcastMovementUpdate(const cClientHandle * a_Exclude)
{
	if (!m_IsFrozen && m_Speed.SqrLength() > 0.001)
	{
		// If the player is not frozen, has a non-zero speed,
		// send the speed to the client so he is forced to move so:
		m_ClientHandle->SendEntityVelocity(*this);
	}

	// Since we do no physics processing for players, speed will otherwise never decrease:
	m_Speed.Set(0, 0, 0);

	Super::BroadcastMovementUpdate(a_Exclude);
}





bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
{
	// Filters out damage for creative mode / friendly fire.

	if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin))
	{
		if (IsGameModeCreative() || IsGameModeSpectator())
		{
			// No damage / health in creative or spectator mode if not void or plugin damage
			return false;
		}
	}

	if ((a_TDI.Attacker != nullptr) && (a_TDI.Attacker->IsPlayer()))
	{
		cPlayer * Attacker = static_cast<cPlayer *>(a_TDI.Attacker);

		if ((m_Team != nullptr) && (m_Team == Attacker->m_Team))
		{
			if (!m_Team->AllowsFriendlyFire())
			{
				// Friendly fire is disabled
				return false;
			}
		}
	}

	if (Super::DoTakeDamage(a_TDI))
	{
		// Any kind of damage adds food exhaustion
		AddFoodExhaustion(0.3f);
		m_ClientHandle->SendHealth();

		// Tell the wolves
		if (a_TDI.Attacker != nullptr)
		{
			if (a_TDI.Attacker->IsPawn())
			{
				NotifyNearbyWolves(static_cast<cPawn*>(a_TDI.Attacker), true);
			}
		}
		m_Stats.AddValue(Statistic::DamageTaken, FloorC<cStatManager::StatValue>(a_TDI.FinalDamage * 10 + 0.5));
		return true;
	}
	return false;
}





float cPlayer::GetEnchantmentBlastKnockbackReduction()
{
	if (
		IsGameModeSpectator() ||
		(IsGameModeCreative() && !IsOnGround())
	)
	{
		return 1;  // No impact from explosion
	}

	return Super::GetEnchantmentBlastKnockbackReduction();
}





void cPlayer::OnAddToWorld(cWorld & a_World)
{
	Super::OnAddToWorld(a_World);

	// Update world name tracking:
	m_CurrentWorldName = m_World->GetName();

	// Fix to stop the player falling through the world, until we get serversided collision detection:
	FreezeInternal(GetPosition(), false);

	// Set capabilities based on new world:
	SetCapabilities();

	// Send contents of the inventory window:
	m_ClientHandle->SendWholeInventory(*m_CurrentWindow);

	// Send health (the respawn packet, which understandably resets health, is also used for world travel...):
	m_ClientHandle->SendHealth();

	// Send experience, similar story with the respawn packet:
	m_ClientHandle->SendExperience();

	// Send hotbar active slot (also reset by respawn):
	m_ClientHandle->SendHeldItemChange(m_Inventory.GetEquippedSlotNum());

	// Update player team:
	UpdateTeam();

	// Send scoreboard data:
	m_World->GetScoreBoard().SendTo(*m_ClientHandle);

	// Update the view distance:
	m_ClientHandle->SetViewDistance(m_ClientHandle->GetRequestedViewDistance());

	// Send current weather of target world:
	m_ClientHandle->SendWeather(a_World.GetWeather());

	// Send time:
	m_ClientHandle->SendTimeUpdate(a_World.GetWorldAge(), a_World.GetTimeOfDay(), a_World.IsDaylightCycleEnabled());

	// Finally, deliver the notification hook:
	cRoot::Get()->GetPluginManager()->CallHookPlayerSpawned(*this);
}





void cPlayer::OnRemoveFromWorld(cWorld & a_World)
{
	Super::OnRemoveFromWorld(a_World);

	// Remove any references to this player pointer by windows in the old world:
	CloseWindow(false);

	// Remove the client handle from the world:
	m_World->RemoveClientFromChunks(m_ClientHandle.get());

	if (m_ClientHandle->IsDestroyed())  // Note: checking IsWorldChangeScheduled not appropriate here since we can disconnecting while having a scheduled warp
	{
		// Disconnecting, do the necessary cleanup.
		// This isn't in the destructor to avoid crashing accessing destroyed objects during shutdown.

		if (!cRoot::Get()->GetPluginManager()->CallHookPlayerDestroyed(*this))
		{
			cRoot::Get()->BroadcastChatLeave(Printf("%s has left the game", GetName().c_str()));
			LOGINFO("Player %s has left the game", GetName().c_str());
		}

		// Remove ourself from everyone's lists:
		cRoot::Get()->BroadcastPlayerListsRemovePlayer(*this);

		// Atomically decrement player count (in world thread):
		cRoot::Get()->GetServer()->PlayerDestroyed();

		// We're just disconnecting. The remaining code deals with going through portals, so bail:
		return;
	}

	const auto DestinationDimension = m_WorldChangeInfo.m_NewWorld->GetDimension();

	// Award relevant achievements:
	if (DestinationDimension == dimEnd)
	{
		AwardAchievement(Statistic::AchTheEnd);
	}
	else if (DestinationDimension == dimNether)
	{
		AwardAchievement(Statistic::AchPortal);
	}

	// Clear sent chunk lists from the clienthandle:
	m_ClientHandle->RemoveFromWorld();

	// The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
	m_ClientHandle->InvalidateCachedSentChunk();

	// Clientside warp start:
	m_ClientHandle->SendRespawn(DestinationDimension, false);
}





void cPlayer::SpawnOn(cClientHandle & a_Client)
{
	if (!m_bVisible || (m_ClientHandle.get() == (&a_Client)))
	{
		return;
	}

	LOGD("Spawing %s on %s", GetName().c_str(), a_Client.GetUsername().c_str());

	a_Client.SendPlayerSpawn(*this);
	a_Client.SendEntityHeadLook(*this);
	a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem());
	a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots());
	a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings());
	a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate());
	a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet());
}





void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
	m_ClientHandle->Tick(a_Dt.count());

	if (m_ClientHandle->IsDestroyed())
	{
		Destroy();
		return;
	}

	if (!m_ClientHandle->IsPlaying())
	{
		// We're not yet in the game, ignore everything:
		return;
	}

	m_Stats.AddValue(Statistic::PlayOneMinute);
	m_Stats.AddValue(Statistic::TimeSinceDeath);

	if (IsCrouched())
	{
		m_Stats.AddValue(Statistic::SneakTime);
	}

	// Handle the player detach, when the player is in spectator mode
	if (
		(IsGameModeSpectator()) &&
		(m_AttachedTo != nullptr) &&
		(
			(m_AttachedTo->IsDestroyed()) ||  // Watching entity destruction
			(m_AttachedTo->GetHealth() <= 0) ||  // Watching entity dead
			(IsCrouched())  // Or the player wants to be detached
		)
	)
	{
		Detach();
	}

	if (!a_Chunk.IsValid())
	{
		// Players are ticked even if the parent chunk is invalid.
		// We've processed as much as we can, bail:
		return;
	}

	ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid()));
	ASSERT(a_Chunk.IsValid());

	// Handle a frozen player:
	TickFreezeCode();

	if (
		m_IsFrozen ||  // Don't do Tick updates if frozen
		IsWorldChangeScheduled()  // If we're about to change worlds (e.g. respawn), abort processing all world interactions (GH #3939)
	)
	{
		return;
	}

	Super::Tick(a_Dt, a_Chunk);

	// Handle charging the bow:
	if (m_IsChargingBow)
	{
		m_BowCharge += 1;
	}

	BroadcastMovementUpdate(m_ClientHandle.get());

	if (m_Health > 0)  // make sure player is alive
	{
		m_World->CollectPickupsByPlayer(*this);

		if ((m_EatingFinishTick >= 0) && (m_EatingFinishTick <= m_World->GetWorldAge()))
		{
			FinishEating();
		}

		HandleFood();
	}

	if (m_IsFishing)
	{
		HandleFloater();
	}

	// Update items (e.g. Maps)
	m_Inventory.UpdateItems();

	if (m_TicksUntilNextSave == 0)
	{
		SaveToDisk();
		m_TicksUntilNextSave = PLAYER_INVENTORY_SAVE_INTERVAL;
	}
	else
	{
		m_TicksUntilNextSave--;
	}
}