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 }