1 /*
2  * Copyright (c) 2017-2018 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: 2017-2018 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 }