package com.infinityraider.agricraft.tiles;
import com.agricraft.agricore.util.TypeHelper;
import com.infinityraider.agricraft.api.crop.IAdditionalCropData;
import com.infinityraider.agricraft.api.items.IAgriClipperItem;
import com.infinityraider.agricraft.api.items.IAgriRakeItem;
import com.infinityraider.agricraft.api.items.IAgriTrowelItem;
import com.infinityraider.agricraft.apiimpl.*;
import com.infinityraider.agricraft.farming.PlantStats;
import com.infinityraider.agricraft.blocks.BlockCrop;
import com.infinityraider.agricraft.reference.AgriCraftConfig;
import com.infinityraider.agricraft.init.AgriItems;
import com.infinityraider.agricraft.items.ItemDebugger;
import com.infinityraider.agricraft.reference.Constants;
import com.infinityraider.infinitylib.utility.WorldHelper;
import com.infinityraider.infinitylib.block.tile.TileEntityBase;
import com.infinityraider.infinitylib.utility.debug.IDebuggable;
import net.minecraft.block.SoundType;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.SoundCategory;
import com.agricraft.agricore.core.AgriCore;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import javax.annotation.Nullable;
import java.util.*;
import javax.annotation.Nonnull;
import com.infinityraider.agricraft.api.plant.IAgriPlant;
import com.infinityraider.agricraft.api.stat.IAgriStat;
import com.infinityraider.agricraft.api.crop.IAgriCrop;
import com.infinityraider.agricraft.api.fertilizer.IAgriFertilizer;
import com.infinityraider.agricraft.init.AgriBlocks;
import com.infinityraider.agricraft.api.misc.IAgriDisplayable;
import com.infinityraider.agricraft.api.seed.AgriSeed;
import com.infinityraider.agricraft.api.soil.IAgriSoil;
import com.infinityraider.agricraft.reference.AgriNBT;
import java.util.function.Consumer;
public class TileEntityCrop extends TileEntityBase implements IAgriCrop, IDebuggable, IAgriDisplayable {
public static final Class[] ITEM_EXCLUDES = new Class[]{
IAgriRakeItem.class,
IAgriClipperItem.class,
IAgriTrowelItem.class,
ItemDebugger.class
};
private int growthStage;
private boolean crossCrop = false;
// Just in case...
private IAgriStat stats;
private IAgriPlant plant;
private IAdditionalCropData data;
// =========================================================================
// Crop in world logic
// =========================================================================
public void growthTick() {
if (!this.isRemote()) {
if (this.isCrossCrop() && AgriCraftConfig.crossOverChance > this.getRandom().nextDouble()) {
this.crossOver();
} else if (!this.hasSeed()) {
this.spawn();
} else if (this.isMature()) {
this.spread();
} else if ((this.plant.getGrowthChanceBase() + this.stats.getGrowth() * this.plant.getGrowthChanceBonus()) * AgriCraftConfig.growthMultiplier > this.getRandom().nextDouble()) {
this.applyGrowthTick();
}
}
}
public boolean plantSeed(EntityPlayer player, ItemStack stack) {
boolean success = false;
if (!this.isRemote()) {
//is the cropEmpty a cross-crop or does it already have a plant
if (!this.isCrossCrop() && !this.hasPlant()) {
//the SEED can be planted here
Optional<AgriSeed> seed = SeedRegistry.getInstance().valueOf(stack);
success = seed.isPresent()
&& seed.get().getPlant().getGrowthRequirement().isMet(this.getWorld(), pos)
&& this.setSeed(seed.get());
}
}
if (success && !player.isCreative()) {
stack.stackSize--;
}
return success;
}
public boolean onCropRightClicked(EntityPlayer player, ItemStack heldItem) {
//only make things happen serverside
if (!this.isRemote()) {
if (heldItem == null) {
this.onHarvested(player);
} else if (TypeHelper.isAnyType(heldItem.getItem(), ITEM_EXCLUDES)) {
// Allow the excludes to do their things.
return false;
} else if (FertilizerRegistry.getInstance().hasAdapter(heldItem)) {
Optional<IAgriFertilizer> fert = FertilizerRegistry.getInstance().valueOf(heldItem);
return fert.isPresent() && fert.get().applyFertilizer(player, this.getWorld(), this.getPos(), this, heldItem, this.getRandom());
} else if (plantSeed(player, heldItem)) {
return true;
} //check to see if the player clicked with crops (crosscrop attempt)
else if (heldItem.getItem() == AgriItems.getInstance().CROPS) {
if (!this.isCrossCrop() && !player.isCreative()) {
heldItem.stackSize--;
}
this.setCrossCrop(true);
} else {
//harvest operation
this.onHarvested(player);
}
}
//Returning true will prevent other things from happening
return true;
}
public void onCropBroken(boolean shouldPerformDrops) {
if (!this.isRemote()) {
if (shouldPerformDrops) {
//drop items if the player is not in creative
this.getDrops(stack -> WorldHelper.spawnItemInWorld(this.worldObj, this.pos, stack), true);
}
if (this.hasPlant()) {
this.getPlant().ifPresent(p -> p.onRemove(this.getWorld(), pos));
}
this.getWorld().removeTileEntity(pos);
this.getWorld().setBlockToAir(pos);
}
}
public void getDrops(Consumer<ItemStack> consumer, boolean includeCropSticks) {
if (includeCropSticks) {
consumer.accept(new ItemStack(AgriItems.getInstance().CROPS, this.isCrossCrop() ? 2 : 1));
}
if (this.plant != null && this.stats != null) {
if (this.plant.getSeedDropChanceBase() + this.growthStage * this.plant.getSeedDropChanceBonus() > this.getRandom().nextDouble()) {
this.getSeed().ifPresent(seed -> consumer.accept(seed.toStack()));
}
if (this.isMature()) {
this.getFruits(consumer, this.getRandom());
}
}
}
public void applyBoneMeal() {
if (!this.isRemote()) {
if (this.hasPlant()) {
this.setGrowthStage(this.growthStage + 2 + this.getRandom().nextInt(3));
} else if (this.isCrossCrop() && AgriCraftConfig.fertilizerMutation) {
this.crossOver();
}
}
}
// =========================================================================
// IPlantProvider Methods
// =========================================================================
@Override
public final boolean hasPlant() {
return this.plant != null;
}
@Nonnull
@Override
public final Optional<IAgriPlant> getPlant() {
return Optional.ofNullable(this.plant);
}
// =========================================================================
// IStatProvider Methods
// =========================================================================
@Override
public final boolean hasStat() {
return this.stats != null;
}
@Nonnull
@Override
public final Optional<IAgriStat> getStat() {
return Optional.ofNullable(this.stats);
}
// =========================================================================
// Misc.
// =========================================================================
@Override
public boolean isCrossCrop() {
return crossCrop;
}
@Override
public void setCrossCrop(boolean status) {
if (!this.isRemote() && !this.hasPlant()) {
this.crossCrop = status;
SoundType type = Blocks.PLANKS.getSoundType(null, null, null, null);
worldObj.playSound(null, (double) ((float) xCoord() + 0.5F), (double) ((float) yCoord() + 0.5F), (double) ((float) zCoord() + 0.5F), type.getPlaceSound(), SoundCategory.BLOCKS, (type.getVolume() + 1.0F) / 2.0F, type.getPitch() * 0.8F);
this.markForUpdate();
}
}
@Override
public int getGrowthStage() {
return this.growthStage;
}
@Override
public void setGrowthStage(int stage) {
if (!this.isRemote() && this.hasPlant() && this.stats != null) {
if (stage < 0) {
stage = 0;
} else if (stage >= this.plant.getGrowthStages()) {
stage = this.plant.getGrowthStages() - 1;
}
if (stage != this.growthStage) {
this.growthStage = stage;
this.markForUpdate();
}
}
}
@Override
public boolean acceptsPlant(IAgriPlant plant) {
return plant != null && !this.hasPlant() && !this.isCrossCrop();
}
@Override
public boolean setPlant(IAgriPlant plant) {
if (!this.isRemote() && !this.crossCrop && !this.hasPlant() && plant != null) {
this.plant = plant;
plant.onPlanted(worldObj, pos);
IAdditionalCropData new_data = plant.getInitialCropData(worldObj, getPos(), this);
if (new_data != null) {
this.data = new_data;
}
this.markForUpdate();
return true;
} else {
return false;
}
}
@Override
public Optional<IAgriPlant> removePlant() {
if (!this.isRemote()) {
final IAgriPlant oldPlant = this.plant;
this.plant = null;
this.data = null;
if (oldPlant != null) {
oldPlant.onRemove(worldObj, pos);
}
this.markForUpdate();
return Optional.ofNullable(oldPlant);
} else {
return Optional.empty();
}
}
@Override
public boolean acceptsStat(IAgriStat stat) {
return true;
}
@Override
public boolean setStat(IAgriStat stat) {
if (!this.isRemote()) {
this.stats = stat;
return stat != null;
}
return false;
}
@Override
public Optional<IAgriStat> removeStat() {
if (!this.isRemote()) {
final IAgriStat old = this.stats;
this.stats = new PlantStats();
this.markForUpdate();
return Optional.ofNullable(old);
}
return Optional.empty();
}
@Override
public boolean isFertile(IAgriPlant plant) {
return worldObj.isAirBlock(this.pos.up())
&& plant.getGrowthRequirement().isMet(this.worldObj, pos);
}
@SideOnly(Side.CLIENT)
public float getCropHeight() {
return hasPlant() ? plant.getHeight(getBlockMetadata()) : Constants.UNIT * 13;
}
@Override
public boolean isMature() {
return getPlant().map(plant -> this.growthStage + 1 >= plant.getGrowthStages()).orElse(false);
}
@Override
public Optional<IAgriSoil> getSoil() {
return SoilRegistry.getInstance().getSoil(this.worldObj.getBlockState(this.pos.down()));
}
// =========================================================================
// IWeedable Methods
// =========================================================================
@Override
public boolean spawn() {
if (!this.isRemote() && !hasPlant()) {
for (IAgriPlant p : PlantRegistry.getInstance().getPlants()) {
if (p.getSpawnChance() > this.getRandom().nextDouble() && this.isFertile(p)) {
this.setCrossCrop(false);
this.setStat(new PlantStats());
this.setPlant(p);
return true;
}
}
}
return false;
}
@Override
public boolean spread() {
if (!this.isRemote() && this.hasPlant() && this.plant.getSpreadChance() > this.getRandom().nextDouble()) {
for (IAgriCrop crop : this.getNeighbours()) {
if (!this.getPlant().equals(crop.getPlant())) {
if (!crop.hasPlant() && !crop.isCrossCrop()) {
crop.setPlant(plant);
return true;
} else if (this.plant.isAggressive() && crop.getStat().map(s -> s.getStrength()).orElse((byte) 0) < this.getStat().map(s -> s.getStrength()).orElse((byte) 0) * this.getRandom().nextDouble()) {
crop.setCrossCrop(false);
crop.setPlant(plant);
return true;
}
}
}
}
return false;
}
// =========================================================================
// IFertilizable Methods
// =========================================================================
@Override
public boolean acceptsFertilizer(IAgriFertilizer fertilizer) {
if (this.crossCrop) {
return AgriCraftConfig.fertilizerMutation && fertilizer.canTriggerMutation();
}
return this.hasPlant() && this.plant.isFertilizable();
}
@Override
public boolean onApplyFertilizer(IAgriFertilizer fertilizer, Random rand) {
if (this.hasPlant() && this.plant.isFertilizable() && this.getGrowthStage() < Constants.MATURE) {
((BlockCrop) AgriBlocks.getInstance().CROP).grow(getWorld(), rand, getPos(), getWorld().getBlockState(getPos()));
return true;
} else if (this.isCrossCrop() && AgriCraftConfig.fertilizerMutation && fertilizer.canTriggerMutation()) {
this.crossOver();
return true;
}
return false;
}
// =========================================================================
// IHarvestable methods.
// =========================================================================
@Override
public boolean onHarvested(@Nullable EntityPlayer player) {
if (!this.isRemote()) {
if (this.isCrossCrop()) {
this.setCrossCrop(false);
WorldHelper.spawnItemInWorld(this.worldObj, this.pos, new ItemStack(AgriItems.getInstance().CROPS, 1));
return false;
} else if (this.canBeHarvested()) {
this.getFruits(stack -> WorldHelper.spawnItemInWorld(this.worldObj, this.pos, stack), this.getRandom());
this.setGrowthStage(0);
return true;
}
}
return false;
}
// =========================================================================
// IRakeable methods.
// =========================================================================
@Override
public boolean onRaked(@Nullable EntityPlayer player) {
if (!this.isRemote() && this.canBeRaked()) {
this.getDrops(stack -> WorldHelper.spawnItemInWorld(this.worldObj, this.pos, stack), false);
this.setGrowthStage(0);
this.removeSeed();
return true;
} else {
return false;
}
}
// =========================================================================
// Other
// =========================================================================
@Override
public TileEntity getTileEntity() {
return this;
}
@Override
public IAdditionalCropData getAdditionalCropData() {
return this.data;
}
@Override
public void validate() {
super.validate();
if (this.hasPlant()) {
plant.onValidate(worldObj, pos, this);
}
}
@Override
public void invalidate() {
super.invalidate();
if (this.hasPlant()) {
plant.onInvalidate(worldObj, pos, this);
}
}
@Override
public void onChunkUnload() {
super.onChunkUnload();
if (this.hasPlant()) {
plant.onChunkUnload(worldObj, pos, this);
}
}
@Override
public void writeTileNBT(NBTTagCompound tag) {
if (this.stats != null) {
this.stats.writeToNBT(tag);
tag.setInteger(AgriNBT.META, growthStage);
}
tag.setBoolean(AgriNBT.CROSS_CROP, crossCrop);
if (plant != null) {
tag.setString(AgriNBT.SEED, plant.getId());
}
if (getAdditionalCropData() != null) {
tag.setTag(AgriNBT.INVENTORY, getAdditionalCropData().writeToNBT());
}
//AgriCore.getLogger("Plant-Tag").debug("Write Tag: {0}", tag);
}
@Override
public void readTileNBT(NBTTagCompound tag) {
this.stats = StatRegistry.getInstance().valueOf(tag).orElse(null);
if (tag.hasKey(AgriNBT.META)) {
this.growthStage = tag.getInteger(AgriNBT.META);
}
this.crossCrop = tag.getBoolean(AgriNBT.CROSS_CROP);
this.plant = PlantRegistry.getInstance().getPlant(tag.getString(AgriNBT.SEED));
if (tag.hasKey(AgriNBT.INVENTORY) && this.plant != null) {
this.data = plant.readCropDataFromNBT(tag.getCompoundTag(AgriNBT.INVENTORY));
}
//AgriCore.getLogger("Plant-Tag").debug("Read Tag: {0}", tag);
}
/**
* Apply a GROWTH increment
*/
public void applyGrowthTick() {
int meta = getGrowthStage();
if (!this.isRemote() && hasPlant() && growthStage < plant.getGrowthStages() && this.isFertile()) {
plant.onAllowedGrowthTick(worldObj, pos, this, meta);
setGrowthStage(meta + 1);
/* TODO: Announce Growth Tick Via API! */
}
}
/**
* the code that makes the crop cross with neighboring crops
*/
public void crossOver() {
MutationEngine.getInstance().attemptCross(this, this.worldObj.rand);
}
/**
* @return a list with all neighbours of type <code>TileEntityCrop</code> in
* the NORTH, SOUTH, EAST and WEST DIRECTION
*/
@Override
public List<IAgriCrop> getNeighbours() {
return WorldHelper.getTileNeighbors(worldObj, pos, IAgriCrop.class);
}
/**
* @return a list with only mature neighbours of type
* <code>TileEntityCrop</code>
*/
@Override
public List<IAgriCrop> getMatureNeighbours() {
List<IAgriCrop> neighbours = getNeighbours();
neighbours.removeIf((p) -> !(p.hasPlant() && p.isMature()));
return neighbours;
}
@Override
public void addServerDebugInfo(Consumer<String> consumer) {
consumer.accept("CROP:");
if (this.crossCrop) {
consumer.accept(" - This is a crosscrop");
} else if (this.hasSeed()) {
if (this.plant.isWeed()) {
consumer.accept(" - This crop has weeds");
} else {
consumer.accept(" - This crop has a plant");
}
Optional<IAgriStat> stats = this.getStat();
consumer.accept(" - Plant: " + this.plant.getPlantName());
consumer.accept(" - Id: " + this.plant.getId());
consumer.accept(" - Stage: " + this.getGrowthStage());
consumer.accept(" - Stages: " + this.plant.getGrowthStages());
consumer.accept(" - Meta: " + this.getGrowthStage());
consumer.accept(" - Growth: " + stats.map(IAgriStat::getGrowth).orElse((byte) 1));
consumer.accept(" - Gain: " + stats.map(IAgriStat::getGain).orElse((byte) 1));
consumer.accept(" - Strength: " + stats.map(IAgriStat::getStrength).orElse((byte) 1));
consumer.accept(" - Fertile: " + this.isFertile());
consumer.accept(" - Mature: " + this.isMature());
consumer.accept(" - AgriSoil: " + this.plant.getGrowthRequirement().getSoils().stream()
.findFirst().map(IAgriSoil::getId).orElse("None")
);
} else {
consumer.accept(" - This crop has no plant");
}
}
@Override
public void addClientDebugInfo(Consumer<String> consumer) {
consumer.accept(" - Texture: " + this.plant.getPrimaryPlantTexture(this.getGrowthStage()).toString());
}
@Override
public void addDisplayInfo(List<String> information) {
// Add Soil Information
information.add("Soil: " + this.getSoil().map(IAgriSoil::getName).orElse("Unknown"));
if (this.hasPlant()) {
//Add the SEED name.
information.add(AgriCore.getTranslator().translate("agricraft_tooltip.seed") + ": " + this.plant.getSeedName());
//Add the GROWTH.
if (this.isMature()) {
information.add(AgriCore.getTranslator().translate("agricraft_tooltip.growth") + ": " + AgriCore.getTranslator().translate("agricraft_tooltip.mature"));
} else {
information.add(AgriCore.getTranslator().translate("agricraft_tooltip.growth") + ": " + (int) (100.0 * this.getGrowthStage() / this.plant.getGrowthStages()) + "%");
}
//Add the ANALYZED data.
if (this.stats != null && this.stats.isAnalyzed()) {
this.stats.addStats(information);
} else {
information.add(AgriCore.getTranslator().translate("agricraft_tooltip.analyzed"));
}
//Add the fertility information.
information.add(AgriCore.getTranslator().translate(this.isFertile() ? "agricraft_tooltip.fertile" : "agricraft_tooltip.notFertile"));
} else {
information.add(AgriCore.getTranslator().translate("agricraft_tooltip.empty"));
}
}
}