1 /* 2 * Copyright (c) 2017-2019 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: Copyright (c) 2017-2019 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/selery/source/selery/entity/human.d, selery/entity/human.d) 28 */ 29 module selery.entity.human; 30 31 import std.math : isNaN, sin, cos, PI; 32 import std.random : uniform01; 33 34 import selery.about; 35 import selery.config : Difficulty; 36 import selery.effect : Effect, Effects; 37 import selery.enchantment : Enchantments; 38 import selery.entity.entity : Entities, Rotation; 39 import selery.entity.interfaces; 40 import selery.entity.living : Healing, Living; 41 import selery.event.world.damage; 42 import selery.event.world.entity : EntityHealEvent; 43 import selery.inventory.inventory : PlayerInventory; 44 import selery.item.slot : Slot; 45 import selery.math.vector : BlockPosition, EntityPosition, entityPosition; 46 import selery.player.player : Player; 47 import selery.util.util : call; 48 import selery.world.world : World; 49 50 class Human : Living, Collector, Shooter, PotionThrower { 51 52 public static immutable WIDTH = .6f; 53 public static immutable HEIGHT = 1.8f; 54 55 private bool n_spawned; 56 57 protected Hunger m_hunger; 58 59 private float m_experience; 60 private uint m_level; 61 62 public EntityPosition m_spawn; 63 64 public Skin skin; 65 66 public PlayerInventory inventory; 67 68 private tick_t last_attack = 0; 69 70 private tick_t regeneration_tick = 0; 71 private tick_t starvation_tick = 0; 72 73 public this(World world, EntityPosition position, Skin skin) { 74 super(world, position, 20, 20); 75 this.m_hunger = Hunger(20, 20); 76 //this.m_exp = Experience(); 77 this.m_experience = 0; 78 this.m_level = 0; 79 this.m_spawn = position; 80 this.skin = skin; 81 this.n_eye_height = 1.62; 82 this.m_body_yaw = Rotation.FRONT; 83 this.inventory = new PlayerInventory(this); 84 this.setSize(.6f, 1.8f); 85 } 86 87 public override pure nothrow @property @safe @nogc string type() { 88 return "player"; 89 } 90 91 public override void tick() { 92 super.tick(); 93 if(this.starvation_tick != 0 && this.ticks % 80 == this.starvation_tick) { 94 if(this.health > (this.world.difficulty == Difficulty.hard ? 0 : (this.world.difficulty == Difficulty.normal ? 1 : 10))) { 95 this.attack(new EntityStarveEvent(this)); 96 } 97 } else if(this.regeneration_tick != 0 && this.ticks % (this.hunger == 20 ? 10 : 80) == this.regeneration_tick && this.world.naturalRegeneration) { 98 this.heal(new EntityHealEvent(this, 1)); 99 this.exhaust(Exhaustion.NATURAL_REGENERATION); 100 } 101 } 102 103 public final @property @safe @nogc bool spawned() { 104 return this.n_spawned; 105 } 106 107 /** 108 * Get the spawn point 109 */ 110 public @property @safe @nogc EntityPosition spawn() { 111 return this.m_spawn; 112 } 113 114 /** 115 * Set the spawn point 116 */ 117 public @property @safe EntityPosition spawn(EntityPosition spawn) { 118 return this.m_spawn = spawn; 119 } 120 121 /// ditto 122 public @property @safe EntityPosition spawn(BlockPosition spawn) { 123 return this.spawn = spawn.entityPosition; 124 } 125 126 protected override void die() { 127 super.die(); 128 this.clearEffects(); 129 this.recalculateColors(); 130 this.ticking = false; 131 //drop the content of the inventory 132 foreach(Slot slot ; this.inventory.full) { 133 if(!slot.empty && Enchantments.curseOfVanishing !in slot.item) { 134 float f0 = uniform01!float(this.world.random) * .5f; 135 float f1 = uniform01!float(this.world.random) * PI * 2; 136 this.world.drop(slot, this.position + [0, 1.3, 0], EntityPosition(-sin(f1) * f0, .2, cos(f1) * f0)); 137 } 138 } 139 this.inventory.empty = true; 140 this.inventory.update = PlayerInventory.ALL; 141 } 142 143 protected override @property @safe @nogc tick_t despawnAfter() { 144 return 0; 145 } 146 147 public override void despawn() { 148 //do nothing, wait for the respawn 149 this.n_spawned = false; 150 // despawn from players 151 this.viewers!Player.call!"sendDespawnEntity"(this); 152 } 153 154 protected void respawn() { 155 this.ticking = true; 156 this.m_health.reset(); 157 this.m_hunger.reset(); 158 this.hungerUpdated(); 159 this.firstspawn(); 160 this.onFire = false; 161 this.sprinting = false; 162 this.sneaking = false; 163 this.move(this.spawn, 0, 0, 0); 164 //show again to viewers 165 this.viewers!Player.call!"sendSpawnEntity"(this); 166 } 167 168 public void firstspawn() { 169 this.n_spawned = true; 170 this.sprinting = false; 171 this.inventory.reset(); 172 } 173 174 // used for limiting the attacks 175 176 public final @property @safe bool canAttack() { 177 return this.last_attack + 10 < this.ticks; 178 } 179 180 public override void attackImpl(EntityDamageEvent event) { 181 super.attackImpl(event); 182 if(!event.cancelled) { 183 this.last_attack = this.ticks; 184 this.exhaust(Exhaustion.DAMAGED); 185 //all the kinds of damages damage the armour in mcpe 186 /*foreach(uint index, Slot slot; this.inventory.armor) { 187 if(slot !is null && cast(Armor)slot.item) { 188 slot.consume(1); 189 this.inventory.armor(index, slot.consumed ? null : slot); 190 if(!slot.consumed) this.inventory.update_viewers &= PlayerInventory.ARMOR ^ 0xF; 191 } 192 }*/ 193 } 194 } 195 196 // hunger functions 197 198 public @property @safe @nogc uint hunger() { 199 return this.m_hunger.hunger; 200 } 201 202 public @property @safe uint hunger(uint hunger) { 203 this.m_hunger.hunger = hunger; 204 this.hungerUpdated(); 205 return hunger; 206 } 207 208 public @safe void exhaust(float amount) { 209 if(this.world.depleteHunger && this.world.difficulty != Difficulty.peaceful) { 210 uint old = this.hunger; 211 this.m_hunger.exhaust(amount); 212 if(old != this.hunger) this.hunger = this.hunger; 213 } 214 } 215 216 public @safe void saturate(float amount) { 217 this.m_hunger.saturate(amount); 218 } 219 220 public final @property @safe @nogc float saturation() { 221 return this.m_hunger.saturation; 222 } 223 224 public final @property @safe @nogc float experience() { 225 return this.m_experience; 226 } 227 228 public final @property @safe float experience(float experience) { 229 this.m_experience = experience; 230 this.experienceUpdated(); 231 return this.m_experience; 232 } 233 234 public final @property @safe @nogc uint level() { 235 return this.m_level; 236 } 237 238 public final @property @safe uint level(uint level) { 239 this.m_level = level; 240 this.experienceUpdated(); 241 return this.m_level; 242 } 243 244 protected @trusted void hungerUpdated() { 245 if(this.hunger == 0 && this.starvation_tick == 0) { 246 //start starvation 247 this.starvation_tick = (this.ticks - 1) % 80; 248 } else if((this.hunger > 18 || this.world.difficulty == Difficulty.peaceful) && this.regeneration_tick == 0 && this.healthNoAbs < this.maxHealthNoAbs) { 249 //start natural regeneration 250 this.regeneration_tick = (this.ticks - 1) % 80; 251 } else if(this.hunger > 0 && this.starvation_tick != 0) { 252 //stop starvation 253 this.starvation_tick = 0; 254 } else if(this.hunger <= 18 && this.regeneration_tick != 0) { 255 //stop regeneration 256 this.regeneration_tick = 0; 257 } 258 } 259 260 protected override @trusted void healthUpdated() { 261 super.healthUpdated(); 262 if(this.healthNoAbs == this.maxHealthNoAbs && this.regeneration_tick != 0) { 263 this.regeneration_tick = 0; 264 } else if(this.healthNoAbs < this.maxHealthNoAbs && this.regeneration_tick == 0 && this.hunger > 18) { 265 this.regeneration_tick = (this.ticks - 1) % 80; 266 } 267 } 268 269 protected @trusted void experienceUpdated() {} 270 271 public override @trusted bool onCollect(Collectable collectable) { 272 return false; 273 } 274 275 } 276 277 struct Hunger { 278 279 public float exhaustion = 0; 280 public float saturation = 5; 281 282 public uint m_hunger; 283 public immutable uint max; 284 285 public @safe @nogc this(uint hunger, uint max) { 286 this.m_hunger = hunger; 287 //this.saturation = hunger; 288 this.max = max; 289 } 290 291 public @safe @nogc void reset() { 292 this.exhaustion = 0; 293 this.saturation = 5; 294 this.m_hunger = this.max; 295 } 296 297 public @safe void exhaust(float amount) { 298 this.exhaustion += amount; 299 if(this.exhaustion >= 4) { 300 this.desaturate(1); 301 this.exhaustion %= 4; 302 } 303 } 304 305 public @safe @nogc void saturate(float amount) { 306 this.saturation += amount; 307 if(this.saturation > this.hunger) this.saturation = this.hunger; 308 } 309 310 public @safe @nogc void desaturate(float amount) { 311 this.saturation -= amount; 312 if(this.saturation < 0) { 313 if(this.hunger > 0) this.m_hunger--; 314 this.saturation = 0; 315 } 316 } 317 318 public @property @safe @nogc uint hunger() { 319 return this.m_hunger; 320 } 321 322 public @property @safe @nogc uint hunger(uint hunger) { 323 if(hunger > this.max) hunger = this.max; 324 return this.m_hunger = hunger; 325 } 326 327 } 328 329 enum Exhaustion : float { 330 331 WALKING = .01, 332 SNEAKING = .005, 333 SWIMMING = .015, 334 BREAKING_BLOCK = .025, 335 SPRINTING = .1, 336 JUMPING = .2, 337 ATTACKING = .3, 338 DAMAGED = .3, 339 SPRINTED_JUMP = .8, 340 NATURAL_REGENERATION = 3 341 342 } 343 344 struct Skin { 345 346 public static Skin STEVE; 347 public static Skin ALEX; 348 349 public static immutable ushort NORMAL_LENGTH = 32 * 64 * 4; 350 public static immutable ushort COMPLEX_LENGTH = 64 * 64 * 4; 351 352 private bool _valid = false; 353 354 private string _name; 355 private immutable(ubyte)[] _data; 356 private immutable(ubyte)[] _cape; 357 private string _geometry_name; 358 private immutable(ubyte)[] _geometry_data; 359 360 public @safe this(string name, ubyte[] data, ubyte[] cape=[], string geometryName="", ubyte[] geometryData=[]) { 361 this._name = name; 362 this._data = data.idup; 363 this._cape = cape.idup; 364 this._geometry_name = geometryName; 365 this._geometry_data = geometryData.idup; 366 if(data.length == NORMAL_LENGTH || data.length == COMPLEX_LENGTH) { 367 this._valid = true; 368 } 369 } 370 371 public pure nothrow @property @safe @nogc bool valid() { 372 return this._valid; 373 } 374 375 public pure nothrow @property @safe @nogc string name() { 376 return this._name; 377 } 378 379 public pure nothrow @property @safe @nogc immutable(ubyte)[] data() { 380 return this._data; 381 } 382 383 public pure nothrow @property @safe @nogc immutable(ubyte)[] cape() { 384 return this._cape; 385 } 386 387 public pure nothrow @property @safe @nogc string geometryName() { 388 return this._geometry_name; 389 } 390 391 public pure nothrow @property @safe @nogc immutable(ubyte)[] geometryData() { 392 return this._geometry_data; 393 } 394 395 }