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/item/tool.d, selery/item/tool.d)
28  */
29 module selery.item.tool;
30 
31 import std.conv : ConvException;
32 import std.json : JSONValue, JSON_TYPE;
33 
34 import sel.nbt.tags;
35 
36 import selery.about;
37 import selery.block.block : Block, Faces;
38 import selery.block.blocks : Blocks;
39 import selery.entity.entity : Entity;
40 import selery.entity.living : Living;
41 import selery.item.item : Item;
42 import selery.math.vector : BlockPosition;
43 import selery.player.player : Player;
44 import selery.util.color : Color, Colorable;
45 
46 static import sul.items;
47 
48 enum Durability : ushort {
49 
50 	gold = 31,
51 	wood = 60,
52 	stone = 132,
53 	iron = 251,
54 	diamond = 1562,
55 	shears = 238,
56 	fishingRod = 65,
57 	flintAndSteel = 65,
58 	carrotOnAStick = 26,
59 	bow = 385,
60 	
61 }
62 
63 enum Tools : ubyte {
64 
65 	none = 0,
66 	shovel = 1,
67 	pickaxe = 2,
68 	axe = 3,
69 	hoe = 4,
70 	armor = 5,
71 	shears = 6,
72 	sword = 8, // flag
73 
74 	all = 0,
75 	wood = 1,
76 	stone = 2,
77 	gold = 3,
78 	iron = 4,
79 	diamond = 5,
80 
81 }
82 
83 abstract class Tool : Item {
84 	
85 	protected ushort damage;
86 	
87 	public @safe this(E...)(E args) {
88 		static if(E.length > 0 && is(typeof(E[0]) : int)) {
89 			super(args[1..$]);
90 			this.damage = args[0] & ushort.max;
91 		} else {
92 			super(args);
93 		}
94 	}
95 	
96 	public override pure nothrow @property @safe @nogc ushort javaMeta() {
97 		return this.damage;
98 	}
99 	
100 	public override pure nothrow @property @safe @nogc ushort bedrockMeta() {
101 		return this.damage;
102 	}
103 	
104 	public override pure nothrow @property @safe @nogc bool tool() {
105 		return true;
106 	}
107 
108 	public abstract pure nothrow @property @safe @nogc ushort durability();
109 	
110 	public override pure nothrow @property @safe @nogc bool finished() {
111 		return this.damage >= this.durability;
112 	}
113 	
114 	protected void applyDamage(ushort amount) {
115 		if(!this.unbreakable) this.damage += amount;
116 	}
117 	
118 	alias unbreakable = super.unbreakable;
119 	
120 	public override @property @safe bool unbreakable(bool unbreakable) {
121 		if(super.unbreakable(unbreakable)) {
122 			this.damage = 0;
123 			return true;
124 		} else {
125 			return false;
126 		}
127 	}
128 	
129 }
130 
131 class ToolItem(sul.items.Item si, ubyte _type, ubyte _material, ushort _durability, uint _attack=1) : Tool {
132 	
133 	public @safe this(E...)(E args) {
134 		super(args);
135 	}
136 
137 	public override pure nothrow @property @safe @nogc const sul.items.Item data() {
138 		return si;
139 	}
140 	
141 	public override pure nothrow @property @safe @nogc ubyte toolType() {
142 		return _type;
143 	}
144 	
145 	public override pure nothrow @property @safe @nogc ubyte toolMaterial() {
146 		return _material;
147 	}
148 	
149 	public override pure nothrow @property @safe @nogc ushort durability() {
150 		return _durability;
151 	}
152 	
153 	public override pure nothrow @property @safe @nogc uint attack() {
154 		return _attack;
155 	}
156 	
157 	alias slot this;
158 	
159 }
160 
161 class SwordItem(sul.items.Item si, ubyte material, ushort durability, uint attack) : ToolItem!(si, Tools.sword, material, durability, attack) {
162 
163 	public this(E...)(E args) {
164 		super(args);
165 	}
166 	
167 	public override bool destroyOn(Player player, Block block, BlockPosition position) {
168 		if(!block.instantBreaking) {
169 			this.applyDamage(2);
170 			return true;
171 		}
172 		return false;
173 	}
174 	
175 	public override bool attackOnEntity(Player player, Entity entity) {
176 		if(cast(Living)entity) {
177 			this.applyDamage(1);
178 			return true;
179 		}
180 		return false;
181 	}
182 	
183 	alias slot this;
184 	
185 }
186 
187 class MiningItem(sul.items.Item si, ubyte tool, ubyte material, ushort durability, uint attack) : ToolItem!(si, tool, material, durability, attack) {
188 
189 	public this(E...)(E args) {
190 		super(args);
191 	}
192 
193 	public override bool destroyOn(Player player, Block block, BlockPosition position) {
194 		if(!block.instantBreaking) {
195 			this.applyDamage(1);
196 			return true;
197 		}
198 		return false;
199 	}
200 
201 	public override bool attackOnEntity(Player player, Entity entity) {
202 		if(cast(Living)entity) {
203 			this.applyDamage(2);
204 			return true;
205 		}
206 		return false;
207 	}
208 	
209 	alias slot this;
210 
211 }
212 
213 alias PickaxeItem(sul.items.Item si, ubyte material, ushort durability, uint attack) = MiningItem!(si, Tools.pickaxe, material, durability, attack);
214 
215 alias AxeItem(sul.items.Item si, ubyte material, ushort durability, uint attack) = MiningItem!(si, Tools.axe, material, durability, attack);
216 
217 class ShovelItem(sul.items.Item si, ubyte material, ushort durability, uint attack) : MiningItem!(si, Tools.shovel, material, durability, attack) {
218 
219 	public this(E...)(E args) {
220 		super(args);
221 	}
222 
223 	public override bool useOnBlock(Player player, Block block, BlockPosition position, ubyte face) {
224 		if(face != Faces.DOWN && block == Blocks.grass && player.world[position + [0, 1, 0]] == Blocks.air) {
225 			player.world[position] = Blocks.grassPath;
226 			this.applyDamage(1);
227 			return true;
228 		}
229 		return false;
230 	}
231 	
232 	alias slot this;
233 
234 }
235 
236 class HoeItem(sul.items.Item si, ubyte material, ushort durability) : ToolItem!(si, Tools.hoe, material, durability, 1) {
237 
238 	public this(E...)(E args) {
239 		super(args);
240 	}
241 
242 	public override bool useOnBlock(Player player, Block block, BlockPosition position, ubyte face) {
243 		if(face != Faces.DOWN && (block == [Blocks.dirt, Blocks.grass, Blocks.grassPath]) && player.world[position + [0, 1, 0]] == Blocks.air) {
244 			player.world[position] = Blocks.farmland0;
245 			this.applyDamage(1);
246 			return true;
247 		}
248 		return false;
249 	}
250 
251 	public override bool attackOnEntity(Player player, Entity entity) {
252 		if(cast(Living)entity) {
253 			this.applyDamage(1);
254 			return true;
255 		}
256 		return false;
257 	}
258 	
259 	alias slot this;
260 
261 }
262 
263 interface Armor {
264 
265 	public enum ubyte helmet = 0;
266 	public enum ubyte chestplate = 1;
267 	public enum ubyte leggings = 2;
268 	public enum ubyte boots = 3;
269 
270 	public alias cap = helmet;
271 	public alias tunic = chestplate;
272 	public alias pants = leggings;
273 
274 	public @property ubyte type();
275 
276 	public @property uint protection();
277 
278 	public bool doDamage(Player player);
279 
280 }
281 
282 class ArmorItem(sul.items.Item si, ushort durability, ubyte atype, uint aprotection, E...) : ToolItem!(si, Tools.armor, 0, durability, 1, E), Armor if(atype <= 3) {
283 
284 	public this(E...)(E args) {
285 		super(args);
286 	}
287 
288 	public override @property ubyte type() {
289 		return atype;
290 	}
291 
292 	public override @property uint protection() {
293 		return aprotection;
294 	}
295 
296 	public override bool doDamage(Player player) {
297 		this.applyDamage(1);
298 		return true;
299 	}
300 	
301 	alias slot this;
302 
303 }
304 
305 class ColorableArmorItem(sul.items.Item si, ushort durability, ubyte atype, uint aprotection) : ArmorItem!(si, durability, atype, aprotection), Colorable {
306 
307 	private Color m_color;
308 
309 	public this(E...)(E args) {
310 		static if(E.length && is(typeof(E[0] == Color))) {
311 			if(args[0] !is null) {
312 				super(args[1..$]);
313 				this.color = args[0];
314 			}
315 		} else {
316 			super(args);
317 		}
318 	}
319 
320 	public override @trusted void parseJSON(JSONValue data) {
321 		super.parseJSON(data);
322 		if(data.type == JSON_TYPE.OBJECT) {
323 			auto c = "color" in data;
324 			if(c) {
325 				if(c.type == JSON_TYPE.STRING && c.str.length == 6) {
326 					try {
327 						auto color = Color.fromString(c.str);
328 						if(color !is null) this.color = color;
329 					} catch(ConvException) {}
330 				} else if(c.type == JSON_TYPE.INTEGER) {
331 					this.color = Color.fromRGB(cast(uint)c.integer);
332 				}
333 			}
334 		}
335 	}
336 
337 	public override void parseJavaCompound(Compound compound) {
338 		super.parseJavaCompound(compound);
339 		compound = compound.get!Compound("", compound);
340 		auto display = compound.get!Compound("display", new Compound());
341 		if(display.has!Int("color")) this.color = Color.fromRGB(display.getValue!Int("color", 0));
342 	}
343 
344 	public override void parseBedrockCompound(Compound compound) {
345 		super.parseBedrockCompound(compound);
346 		compound = compound.get!Compound("", compound);
347 		if(compound.has!Int("customColor")) this.color = Color.fromRGB(compound.getValue!Int("customColor", 0));
348 	}
349 
350 	/**
351 	 * Gets the item's custom colour.
352 	 */
353 	public override pure nothrow @property @safe @nogc Color color() {
354 		return this.m_color;
355 	}
356 
357 	/**
358 	 * Sets the item's custom colour.
359 	 */
360 	public override @property Color color(Color color) {
361 		if(color is null) {
362 			// remove
363 			{
364 				auto display = this.m_pc_tag.get!Compound("display", null);
365 				display.remove("color");
366 				if(display.empty) {
367 					this.m_pc_tag.remove("display");
368 					if(this.m_pc_tag.empty) this.m_pc_tag = null;
369 				}
370 			}
371 			{
372 				this.m_pe_tag.remove("customColor");
373 				if(this.m_pe_tag.empty) this.m_pe_tag = null;
374 			}
375 		} else {
376 			// add
377 			uint rgb = color.rgb;
378 			{
379 				auto cc = new Named!Int("color", rgb);
380 				if(this.m_pc_tag is null) this.m_pc_tag = new Compound(new Named!Compound("display", cc));
381 				else if(!this.m_pc_tag.has!Compound("display")) this.m_pc_tag["display"] = new Compound(cc);
382 				else this.m_pc_tag.get!Compound("display", null)[] = cc;
383 			}
384 			{
385 				auto cc = new Named!Int("customColor", rgb);
386 				if(this.m_pe_tag is null) this.m_pe_tag = new Compound(cc);
387 				else this.m_pe_tag[] = cc;
388 			}
389 		}
390 		return this.m_color = color;
391 	}
392 	
393 	alias slot this;
394 
395 }
396 
397 class PlaceableArmor(sul.items.Item si, E...) : ArmorItem!(si, 1, 0, 0) {
398 
399 	public this(E...)(E args) {
400 		super(args);
401 	}
402 
403 	public override pure nothrow @property @safe @nogc bool tool() {
404 		return false;
405 	}
406 
407 	public override bool doDamage(Player player) {
408 		return false;
409 	}
410 	
411 	alias slot this;
412 
413 }