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/block/farming.d, selery/block/farming.d)
28  */
29 module selery.block.farming;
30 
31 import std.random : dice, uniform, randomShuffle;
32 
33 import selery.about : block_t, item_t;
34 import selery.block.block : Block, Update;
35 import selery.block.blocks : Blocks;
36 import selery.block.solid;
37 import selery.entity.entity : Entity;
38 import selery.item.item : Item;
39 import selery.item.items : Items;
40 import selery.item.slot : Slot;
41 import selery.item.tool : Tools;
42 import selery.math.vector : BlockPosition;
43 import selery.player.player : Player;
44 import selery.world.world : World;
45 
46 static import sul.blocks;
47 
48 class FertileTerrainBlock(bool hydrated) : MineableBlock {
49 
50 	private block_t wetter, dryer;
51 
52 	public this(sul.blocks.Block data, block_t wetter, block_t dryer) {
53 		super(data, MiningTool(false, Tools.shovel, Tools.wood), Drop(Items.dirt, 1));
54 		this.wetter = wetter;
55 		this.dryer = dryer;
56 	}
57 
58 	public final override pure nothrow @property @safe @nogc bool doRandomTick() {
59 		return true;
60 	}
61 
62 	public override void onRandomTick(World world, BlockPosition position) {
63 		static if(hydrated) {
64 			//TODO check if the water still there, otherwise convert to dryer
65 		} else {
66 			//TODO check if this block could be hydrated and convert to wetter, otherwise convert to dryer (if not 0)
67 		}
68 	}
69 
70 	/**
71 	 * Searches for water in the section.
72 	 * Returns: true if water was found, false otherwise
73 	 */
74 	/*protected @property bool searchWater() {
75 		foreach(uint x ; 0..this.section.opDollar!0) {
76 			foreach(uint y ; 0..this.section.opDollar!1) {
77 				foreach(uint z ; 0..this.section.opDollar!2) {
78 					Block b;
79 					if((b = this.section[x, y, z]) !is null && b == Blocks.WATER) {
80 						this.water = new BlockPosition(x, y, z);
81 						return true;
82 					}
83 				}
84 			}
85 		}
86 		return false;
87 	}*/
88 
89 	public override void onUpdated(World world, BlockPosition position, Update update) {
90 		Block up = world[position + [0, 1, 0]];
91 		if(up.solid) world[position] = Blocks.dirt;
92 		//TODO moved by piston
93 		//TODO stepped
94 	}
95 
96 }
97 
98 class FarmingBlock : MineableBlock {
99 
100 	public this(sul.blocks.Block data, Drop[] drops) {
101 		super(data, MiningTool.init, drops);
102 	}
103 	
104 	public override void onUpdated(World world, BlockPosition position, Update update) {
105 		if(world[position - [0, 1, 0]] != Blocks.farmland) {
106 			world.drop(this, position);
107 			world[position] = Blocks.air;
108 		}
109 	}
110 
111 }
112 
113 class GrowingFarmingBlock : FarmingBlock {
114 
115 	public this(sul.blocks.Block data, Drop[] drops) {
116 		super(data, drops);
117 	}
118 
119 	public override pure nothrow @property @safe @nogc bool doRandomTick() {
120 		return true;
121 	}
122 
123 	public override void onRandomTick(World world, BlockPosition position) {
124 		immutable float p = world[position - [0, 1, 0]] != Blocks.farmland7 ? .125 : .25;
125 		if(dice(world.random, 1f - p, p)) {
126 			this.grow(world, position);
127 		}
128 	}
129 
130 	public abstract void grow(World, BlockPosition);
131 
132 }
133 
134 class StageCropBlock : GrowingFarmingBlock {
135 
136 	private block_t next;
137 
138 	public this(sul.blocks.Block data, Drop[] drops, block_t next) {
139 		super(data, drops);
140 		this.next = 0;
141 	}
142 
143 	public this(sul.blocks.Block data, block_t next, Drop[] drops=[]) {
144 		this(data, drops, next);
145 	}
146 
147 	public override void grow(World world, BlockPosition position) {
148 		world[position] = this.next;
149 	}
150 
151 }
152 
153 class FruitCropBlock(bool isArray) : GrowingFarmingBlock {
154 
155 	static if(isArray) {
156 		alias grow_t = block_t[4];
157 	} else {
158 		alias grow_t = block_t;
159 	}
160 
161 	private grow_t growTo;
162 
163 	public this(sul.blocks.Block data, Drop[] drops, grow_t growTo) {
164 		super(data, drops);
165 	}
166 
167 	public this(sul.blocks.Block data, grow_t growTo, Drop[] drops=[]) {
168 		this(data, drops, growTo);
169 	}
170 
171 	public override void grow(World world, BlockPosition position) {
172 		//search for a place to grow
173 		BlockPosition[] positions = [position + [1, 0, 0], position + [0, 0, 1], position - [1, 0, 0], position - [0, 0, 1]];
174 		randomShuffle(positions, world.random);
175 		foreach(BlockPosition pos ; positions) {
176 			if(world[pos] == Blocks.air) {
177 				Block s = world[pos - [0, 1, 0]];
178 				if(s == Blocks.dirts) {
179 					static if(isArray) {
180 						ubyte face;
181 						if(position.x == pos.x) {
182 							if(position.z > pos.z) face = Facing.north;
183 							else face = Facing.south;
184 						} else {
185 							if(position.x > pos.x) face = Facing.west;
186 							else face = Facing.east;
187 						}
188 						world[pos] = this.growTo[face];
189 					} else {
190 						world[pos] = this.growTo;
191 					}
192 					break;
193 				}
194 			}
195 		}
196 	}
197 
198 }
199 
200 class ChanceCropBlock : StageCropBlock {
201 
202 	private ubyte a, b;
203 
204 	public this(sul.blocks.Block data, Drop[] drops, block_t next, ubyte a, ubyte b) {
205 		super(data, drops, next);
206 		this.a = a;
207 		this.b = b;
208 	}
209 
210 	public this(sul.blocks.Block data, block_t next, Drop[] drops, ubyte a, ubyte b) {
211 		this(data, drops, next, a, b);
212 	}
213 
214 	public override void onRandomTick(World world, BlockPosition position) {
215 		if(uniform(0, this.b, world.random) < this.a) {
216 			//TODO call this.grow ?
217 			super.onRandomTick(world, position);
218 		}
219 	}
220 
221 }
222 
223 //class StemBlock(bool isArray) : FruitCropBlock!isArray {
224 
225 template StemBlock(T) {
226 
227 	class StemBlock : T {
228 
229 		private item_t drop;
230 
231 		public this(E...)(sul.blocks.Block data, item_t drop, E args) {
232 			super(data, args);
233 			this.drop = drop;
234 		}
235 
236 		public override Slot[] drops(World world, Player player, Item item) {
237 			immutable amount = dice(world.random, 100, 20, 4, 1);
238 			if(amount) {
239 				auto func = world.items.getConstructor(this.drop);
240 				if(func !is null) {
241 					Slot[] ret;
242 					foreach(i ; 0..amount) {
243 						ret ~= Slot(func(0), 1);
244 					}
245 					return ret;
246 				}
247 			}
248 			return [];
249 		}
250 
251 	}
252 
253 }
254 
255 //class GrowingBlock(sul.blocks.Block sb, block_t next, block_t[] compare, size_t height, bool waterNeeded, block_t[] requiredBlock, Drop drops) : MineableBlock!(sb, MiningTool.init, drops) {
256 
257 class GrowingBlock(bool needsWater) : MineableBlock {
258 
259 	private block_t next;
260 	private block_t[] compare, requiredBlock;
261 	private size_t height;
262 
263 	public this(sul.blocks.Block data, Drop[] drops, block_t next, block_t[] compare, block_t[] requiredBlock, size_t height) {
264 		super(data, MiningTool.init, drops);
265 		this.next = next;
266 		this.compare = compare;
267 		this.requiredBlock = requiredBlock;
268 		this.height = height;
269 	}
270 
271 	public override void onRandomTick(World world, BlockPosition position) {
272 		//TODO check if there's water around
273 		if(this.next == 0) { //TODO do this check when constructed
274 			@property bool tooHigh() {
275 				size_t h = 1;
276 				auto pos = position - [0, 1, 0];
277 				while(world[pos] == compare && ++h < this.height) pos -= [0, 1, 0];
278 				return h >= this.height;
279 			}
280 			auto up = position + [0, 1, 0];
281 			if(world[up] == Blocks.air && !tooHigh) {
282 				world[up] = this.compare[0];
283 			}
284 		} else {
285 			world[position] = this.next;
286 		}
287 	}
288 
289 	public override void onUpdated(World world, BlockPosition position, Update update) {
290 		auto down = world[position - [0, 1, 0]];
291 		if(down != this.compare && (down != this.requiredBlock || !this.searchWater(world, position))) {
292 			world.drop(this, position);
293 			world[position] = Blocks.air;
294 		}
295 	}
296 
297 	private bool searchWater(World world, BlockPosition position) {
298 		static if(needsWater) {
299 			foreach(p ; [[0, -1, 1], [1, -1, 0], [0, -1, -1], [-1, -1, 0]]) {
300 				if(world[position + p] == Blocks.water) return true;
301 			}
302 			return false;
303 		} else {
304 			return true;
305 		}
306 	}
307 
308 }
309 
310 class SugarCanesBlock : GrowingBlock!true {
311 
312 	public this(sul.blocks.Block data, block_t next) {
313 		super(data, [Drop(Items.sugarCanes, 1)], next, Blocks.sugarCanes, [Blocks.sand, Blocks.redSand, Blocks.dirt, Blocks.coarseDirt, Blocks.podzol, Blocks.grass], 3);
314 	}
315 
316 }
317 
318 class CactusBlock : GrowingBlock!false {
319 
320 	public this(sul.blocks.Block data, block_t next) {
321 		super(data, [Drop(Items.cactus, 1)], next, Blocks.cactus, [Blocks.sand, Blocks.redSand], 3);
322 	}
323 
324 	//TODO do cactus damage on step and on contact
325 
326 }
327 
328 class NetherCropBlock : MineableBlock {
329 
330 	public this(sul.blocks.Block data, Drop drop) {
331 		super(data, MiningTool.init, drop);
332 	}
333 
334 	public override void onUpdated(World world, BlockPosition position, Update update) {
335 		auto pos = position - [0, 1, 0];
336 		if(world[pos] != Blocks.soulSand) {
337 			world.drop(this, position);
338 			world[pos] = Blocks.air;
339 		}
340 	}
341 
342 }
343 
344 class StageNetherCropBlock : NetherCropBlock {
345 
346 	private block_t next;
347 
348 	public this(sul.blocks.Block data, block_t next, Drop drop) {
349 		super(data, drop);
350 		this.next = next;
351 	}
352 
353 	public override pure nothrow @property @safe @nogc bool doRandomTick() {
354 		return true;
355 	}
356 
357 	public override void onRandomTick(World world, BlockPosition position) {
358 		world[position] = this.next;
359 	}
360 
361 }
362 
363 class BeansBlock : MineableBlock {
364 
365 	private BlockPosition facing;
366 
367 	public this(sul.blocks.Block data, MiningTool miningTool, Drop drop, ubyte facing) {
368 		super(data, miningTool, [drop]);
369 		if(facing == Facing.north) {
370 			this.facing = BlockPosition(0, 0, 1);
371 		} else if(facing == Facing.south) {
372 			this.facing = BlockPosition(0, 0, -1);
373 		} else if(facing == Facing.west) {
374 			this.facing = BlockPosition(1, 0, 0);
375 		} else {
376 			this.facing = BlockPosition(-1, 0, 0);
377 		}
378 	}
379 
380 	public override void onUpdated(World world, BlockPosition position, Update update) {
381 		//TODO verify facing
382 		auto attached = position + this.facing;
383 		if(world[attached] != Blocks.jungleWood) {
384 			world.drop(this, position);
385 			world[attached] = Blocks.air;
386 		}
387 	}
388 
389 }
390 
391 class GrowingBeansBlock : BeansBlock {
392 
393 	private block_t next;
394 
395 	public this(sul.blocks.Block data, MiningTool miningTool, Drop drop, ubyte facing, block_t next) {
396 		super(data, miningTool, drop, facing);
397 	}
398 
399 	public override pure nothrow @property @safe @nogc bool doRandomTick() {
400 		return true;
401 	}
402 	
403 	public override void onRandomTick(World world, BlockPosition position) {
404 		world[position] = this.next; // every random tick?
405 	}
406 
407 }