9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 11:29:17 +00:00

refactor crops

This commit is contained in:
XiaoMoMi
2025-03-28 20:25:31 +08:00
parent 44c2316cd2
commit 736627ab8b
17 changed files with 270 additions and 143 deletions

View File

@@ -62,4 +62,4 @@ categories:
- default:flame_cane
- default:gunpowder_block
- default:solid_gunpowder_block
- default:ender_pearl_crop_seed
- default:ender_pearl_flower_seed

View File

@@ -4,7 +4,7 @@ i18n:
item.fairy_flower: "Fairy Flower"
item.reed: "Reed"
item.flame_cane: "Flame Cane"
item.ender_pearl_crop_seed: "Ender Pearl Flower Seeds"
item.ender_pearl_flower_seed: "Ender Pearl Flower Seeds"
item.bench: "Bench"
item.table_lamp: "Table Lamp"
item.wooden_chair: "Wooden Chair"
@@ -44,7 +44,7 @@ i18n:
item.fairy_flower: "仙灵花"
item.reed: "芦苇"
item.flame_cane: "烈焰甘蔗"
item.ender_pearl_crop_seed: "末影珍珠花种子"
item.ender_pearl_flower_seed: "末影珍珠花种子"
item.bench: "长椅"
item.table_lamp: "台灯"
item.wooden_chair: "木椅"

View File

@@ -35,18 +35,18 @@ items:
behavior:
type: block_item
block: default:flame_cane
default:ender_pearl_crop_seed:
default:ender_pearl_flower_seed:
material: paper
custom-model-data: 4003
data:
item-name: "<!i><i18n:item.ender_pearl_crop_seed>"
item-name: "<!i><i18n:item.ender_pearl_flower_seed>"
model:
template: default:model/simplified_generated
arguments:
path: "minecraft:block/custom/ender_pearl_crop_seed"
path: "minecraft:block/custom/ender_pearl_flower_seed"
behavior:
type: block_item
block: default:ender_pearl_crop
block: default:ender_pearl_flower
blocks:
default:fairy_flower:
settings:
@@ -179,15 +179,15 @@ blocks:
age=5:
appearance: default
id: 7
default:ender_pearl_crop:
default:ender_pearl_flower:
settings:
template:
- default:hardness/none
- default:sound/grass
overrides:
item: default:ender_pearl_crop_seed
item: default:ender_pearl_flower_seed
push-reaction: DESTROY
map-color: 15
map-color: 24
is-randomly-ticking: true
behavior:
type: crop_block
@@ -196,64 +196,47 @@ blocks:
bottom-blocks:
- minecraft:end_stone
loot:
pools:
- rolls: 1
entries:
- type: item
item: "default:ender_pearl_crop_seed"
conditions:
- type: survives_explosion
- rolls:
type: uniform
min: "1"
max: "5"
entries:
- type: item
item: "minecraft:ender_pearl"
conditions:
- type: crop_ripe
template: default:loot_table/seed_crop
arguments:
crop_item: minecraft:ender_pearl
crop_seed: default:ender_pearl_flower_seed
ripe_age: 4
states:
properties:
age:
type: int
default: 0
range: 0~5
range: 0~3
appearances:
default:
stage_0:
state: "tripwire:0"
models:
- path: "minecraft:block/custom/ender_pearl_flower_stage_0"
stage_1:
state: "tripwire:1"
models:
- path: "minecraft:block/custom/ender_pearl_flower_stage_1"
stage_2:
state: "tripwire:2"
models:
- path: "minecraft:block/custom/ender_pearl_flower_stage_2"
stage_3:
state: "sugar_cane:3"
models:
- path: "minecraft:block/custom/ender_pearl_crop_stage1"
generation:
parent: "minecraft:block/custom/tinted_cross"
textures:
"cross": "minecraft:block/custom/ender_pearl_crop_stage1"
ripe:
state: "sugar_cane:4"
models:
- path: "minecraft:block/custom/ender_pearl_crop_stage2"
generation:
parent: "minecraft:block/custom/tinted_cross"
textures:
"cross": "minecraft:block/custom/ender_pearl_crop_stage2"
- path: "minecraft:block/custom/ender_pearl_flower_stage_3"
variants:
age=0:
appearance: default
id: 8
appearance: stage_0
id: 0
age=1:
appearance: default
id: 9
appearance: stage_1
id: 1
age=2:
appearance: default
id: 10
appearance: stage_2
id: 2
age=3:
appearance: default
id: 11
age=4:
appearance: default
id: 12
age=5:
appearance: ripe
id: 13
appearance: stage_3
id: 8
recipes:
default:paper_from_reed:
type: shaped

View File

@@ -953,7 +953,12 @@ templates#recipes:
# loot tables
templates#loot_tables:
# drop one item
# template: default:loot_table/basic
# arguments:
# item: the item
default:loot_table/basic:
pools:
- rolls: 1
@@ -962,7 +967,12 @@ templates#loot_tables:
entries:
- type: item
item: "{item}"
# drop with silk touch
# template: default:loot_table/silk_touch
# arguments:
# item: the item
default:loot_table/silk_touch:
pools:
- rolls: 1
@@ -972,7 +982,78 @@ templates#loot_tables:
entries:
- type: item
item: "{item}"
# drop ores
# crop drops
# template: default:loot_table/seed_crop
# arguments:
# crop_item: the ripe crop
# crop_seed: the seed of the crop
# ripe_age: the max age
default:loot_table/seed_crop:
pools:
- rolls: 1
entries:
- type: alternatives
children:
- type: item
item: "{crop_item}"
conditions:
- type: match_block_property
properties:
age: "{ripe_age}"
- type: item
item: "{crop_seed}"
- rolls: 1
conditions:
- type: match_block_property
properties:
age: "{ripe_age}"
entries:
- type: item
item: "{crop_seed}"
functions:
- type: apply_bonus
enchantment: minecraft:fortune
formula:
type: binomial_with_bonus_count
extra: 3
probability: 0.5714286
# template: default:loot_table/crop
# arguments:
# crop_item: the ripe crop
# ripe_age: the max age
default:loot_table/crop:
pools:
- rolls: 1
entries:
- type: item
item: "{crop_item}"
- rolls: 1
conditions:
- type: match_block_property
properties:
age: "{ripe_age}"
entries:
- type: item
item: "{crop_item}"
functions:
- type: apply_bonus
enchantment: minecraft:fortune
formula:
type: binomial_with_bonus_count
extra: 3
probability: 0.5714286
# ore drops
# template: default:loot_table/ore
# arguments:
# ore_block: the ore block
# ore_drop: the drops of the ore
# min_exp: the min exp to drop
# max_exp: the max exp to drop
default:loot_table/ore:
pools:
- rolls: 1
@@ -997,7 +1078,37 @@ templates#loot_tables:
type: uniform
min: "{min_exp}"
max: "{max_exp}"
# leaves
# template: default:loot_table/ore_no_exp
# arguments:
# ore_block: the ore block
# ore_drop: the drops of the ore
default:loot_table/ore_no_exp:
pools:
- rolls: 1
entries:
- type: alternatives
children:
- type: item
item: "{ore_block}"
conditions:
- type: enchantment
predicate: minecraft:silk_touch>=1
- type: item
item: "{ore_drop}"
functions:
- type: apply_bonus
enchantment: minecraft:fortune
formula:
type: ore_drops
- type: explosion_decay
# leaves drops
# template: default:loot_table/leaves
# arguments:
# leaves: the leaves block
# sapling: the sapling item
default:loot_table/leaves:
pools:
- rolls: 1

View File

@@ -1,26 +0,0 @@
{
"ambientocclusion": false,
"textures": {
"particle": "#cross"
},
"elements": [
{ "from": [ 0.8, 0, 8 ],
"to": [ 15.2, 16, 8 ],
"rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": true },
"shade": false,
"faces": {
"north": { "uv": [ 0, 0, 16, 16 ], "texture": "#cross" },
"south": { "uv": [ 0, 0, 16, 16 ], "texture": "#cross" }
}
},
{ "from": [ 8, 0, 0.8 ],
"to": [ 8, 16, 15.2 ],
"rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": true },
"shade": false,
"faces": {
"west": { "uv": [ 0, 0, 16, 16 ], "texture": "#cross" },
"east": { "uv": [ 0, 0, 16, 16 ], "texture": "#cross" }
}
}
]
}

View File

@@ -182,11 +182,6 @@ public final class CraftEngineBlocks {
builder.withParameter(LootParameters.PLAYER, serverPlayer);
builder.withOptionalParameter(LootParameters.TOOL, serverPlayer.getItemInHand(InteractionHand.MAIN_HAND));
}
if (state.behavior() instanceof CropBlockBehavior cropBlockBehavior) {
if (cropBlockBehavior.isMaxAge(state)) {
builder.withParameter(LootParameters.CROP_RIPE, true);
}
}
for (Item<?> item : state.getDrops(builder, world)) {
world.dropItemNaturally(vec3d, item);
}

View File

@@ -139,11 +139,6 @@ public class BlockEventListener implements Listener {
builder.withParameter(LootParameters.LOCATION, vec3d);
builder.withParameter(LootParameters.PLAYER, serverPlayer);
builder.withOptionalParameter(LootParameters.TOOL, itemInHand);
if (state.behavior() instanceof CropBlockBehavior cropBlockBehavior) {
if (cropBlockBehavior.isMaxAge(state)) {
builder.withParameter(LootParameters.CROP_RIPE, true);
}
}
for (Item<Object> item : state.getDrops(builder, world)) {
world.dropItemNaturally(vec3d, item);
}
@@ -302,11 +297,6 @@ public class BlockEventListener implements Listener {
if (yield < 1f) {
builder.withParameter(LootParameters.EXPLOSION_RADIUS, 1.0f / yield);
}
if (state.behavior() instanceof CropBlockBehavior cropBlockBehavior) {
if (cropBlockBehavior.isMaxAge(state)) {
builder.withParameter(LootParameters.CROP_RIPE, true);
}
}
for (Item<Object> item : state.getDrops(builder, world)) {
world.dropItemNaturally(vec3d, item);
}

View File

@@ -71,11 +71,6 @@ public class BushBlockBehavior extends AbstractBlockBehavior {
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
builder.withParameter(LootParameters.LOCATION, vec3d);
builder.withParameter(LootParameters.WORLD, world);
if (this instanceof CropBlockBehavior cropBlockBehavior) {
if (cropBlockBehavior.isMaxAge(state)) {
builder.withParameter(LootParameters.CROP_RIPE, true);
}
}
for (Item<Object> item : previousState.getDrops(builder, world)) {
world.dropItemNaturally(vec3d, item);
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.block.CustomBlock;
@@ -37,6 +38,7 @@ public class CropBlockBehavior extends BushBlockBehavior {
public final boolean isMaxAge(Object state) {
return this.getAge(state) >= this.ageProperty.max;
}
public final boolean isMaxAge(ImmutableBlockState state) {
return this.getAge(state) >= this.ageProperty.max;
}
@@ -48,6 +50,7 @@ public class CropBlockBehavior extends BushBlockBehavior {
public final int getAge(Object state) {
return getCEBlockState(state).get(ageProperty);
}
public final int getAge(ImmutableBlockState state) {
return state.get(ageProperty);
}

View File

@@ -14,9 +14,7 @@
# Q: When do I need to configure this file?
# A: When the number of real block IDs is insufficient, but there are still available appearances.
# By default, the plugin only registers an additional 112 oak leaf block states (for the default configuration needs [>=28 states]).
minecraft:oak_leaves: 112
minecraft:oak_sapling: 1
minecraft:birch_sapling: 1
minecraft:spruce_sapling: 1
@@ -24,5 +22,5 @@ minecraft:jungle_sapling: 1
minecraft:dark_oak_sapling: 1
minecraft:acacia_sapling: 1
minecraft:cherry_sapling: 1
minecraft:anvil: 2
minecraft:anvil: 2
minecraft:sugarcane: 14

View File

@@ -10,6 +10,7 @@ import net.momirealms.craftengine.core.loot.function.LootFunction;
import net.momirealms.craftengine.core.loot.function.LootFunctions;
import net.momirealms.craftengine.core.loot.number.NumberProvider;
import net.momirealms.craftengine.core.loot.number.NumberProviders;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.World;
import org.jetbrains.annotations.Nullable;
@@ -43,19 +44,19 @@ public class LootTable<T> {
NumberProvider rolls = NumberProviders.fromObject(pool.getOrDefault("rolls", 1));
NumberProvider bonus_rolls = NumberProviders.fromObject(pool.getOrDefault("bonus_rolls", 0));
List<LootCondition> conditions = Optional.ofNullable(pool.get("conditions"))
.map(it -> LootConditions.fromMapList((List<Map<String, Object>>) it))
.map(it -> LootConditions.fromMapList(MiscUtils.castToMapListOrThrow(it, () -> new RuntimeException("'conditions' should be a map list, current type: " + it.getClass().getSimpleName()))))
.orElse(Lists.newArrayList());
List<LootEntryContainer<T>> containers = Optional.ofNullable(pool.get("entries"))
.map(it -> (List<LootEntryContainer<T>>) new ArrayList<LootEntryContainer<T>>(LootEntryContainers.fromMapList((List<Map<String, Object>>) it)))
.map(it -> (List<LootEntryContainer<T>>) new ArrayList<LootEntryContainer<T>>(LootEntryContainers.fromMapList(MiscUtils.castToMapListOrThrow(it, () -> new RuntimeException("'entries' should be a map list, current type: " + it.getClass().getSimpleName())))))
.orElse(Lists.newArrayList());
List<LootFunction<T>> functions = Optional.ofNullable(pool.get("functions"))
.map(it -> (List<LootFunction<T>>) new ArrayList<LootFunction<T>>(LootFunctions.fromMapList((List<Map<String, Object>>) it)))
.map(it -> (List<LootFunction<T>>) new ArrayList<LootFunction<T>>(LootFunctions.fromMapList(MiscUtils.castToMapListOrThrow(it, () -> new RuntimeException("'functions' should be a map list, current type: " + it.getClass().getSimpleName())))))
.orElse(Lists.newArrayList());
lootPools.add(new LootPool<>(containers, conditions, functions, rolls, bonus_rolls));
}
return new LootTable<>(lootPools,
Optional.ofNullable(map.get("functions"))
.map(it -> (List<LootFunction<T>>) new ArrayList<LootFunction<T>>(LootFunctions.fromMapList((List<Map<String, Object>>) it)))
.map(it -> (List<LootFunction<T>>) new ArrayList<LootFunction<T>>(LootFunctions.fromMapList(MiscUtils.castToMapListOrThrow(it, () -> new RuntimeException("'functions' should be a map list, current type: " + it.getClass().getSimpleName())))))
.orElse(Lists.newArrayList())
);
}

View File

@@ -1,29 +0,0 @@
package net.momirealms.craftengine.core.loot.condition;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.util.Key;
import java.util.Map;
public class CropRipeCondition implements LootCondition {
public static final Factory FACTORY = new Factory();
public static final CropRipeCondition INSTANCE = new CropRipeCondition();
@Override
public Key type() {
return LootConditions.CROP_RIPE;
}
@Override
public boolean test(LootContext lootContext) {
return lootContext.getOptionalParameter(LootParameters.CROP_RIPE).orElse(false);
}
public static class Factory implements LootConditionFactory {
@Override
public LootCondition create(Map<String, Object> arguments) {
return INSTANCE;
}
}
}

View File

@@ -14,6 +14,7 @@ import java.util.function.Predicate;
public class LootConditions {
public static final Key MATCH_ITEM = Key.from("craftengine:match_item");
public static final Key MATCH_BLOCK_PROPERTY = Key.from("craftengine:match_block_property");
public static final Key TABLE_BONUS = Key.from("craftengine:table_bonus");
public static final Key SURVIVES_EXPLOSION = Key.from("craftengine:survives_explosion");
public static final Key ANY_OF = Key.from("craftengine:any_of");
@@ -21,10 +22,10 @@ public class LootConditions {
public static final Key ENCHANTMENT = Key.from("craftengine:enchantment");
public static final Key INVERTED = Key.from("craftengine:inverted");
public static final Key FALLING_BLOCK = Key.from("craftengine:falling_block");
public static final Key CROP_RIPE = Key.from("craftengine:crop_ripe");
static {
register(MATCH_ITEM, MatchItemCondition.FACTORY);
register(MATCH_BLOCK_PROPERTY, MatchBlockPropertyCondition.FACTORY);
register(TABLE_BONUS, TableBonusCondition.FACTORY);
register(SURVIVES_EXPLOSION, SurvivesExplosionCondition.FACTORY);
register(ANY_OF, AnyOfCondition.FACTORY);
@@ -32,7 +33,6 @@ public class LootConditions {
register(ENCHANTMENT, EnchantmentCondition.FACTORY);
register(INVERTED, InvertedCondition.FACTORY);
register(FALLING_BLOCK, FallingCondition.FACTORY);
register(CROP_RIPE, CropRipeCondition.FACTORY);
}
public static void register(Key key, LootConditionFactory factory) {

View File

@@ -0,0 +1,58 @@
package net.momirealms.craftengine.core.loot.condition;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.Pair;
import java.util.*;
public class MatchBlockPropertyCondition implements LootCondition {
public static final Factory FACTORY = new Factory();
private final List<Pair<String, String>> properties;
public MatchBlockPropertyCondition(List<Pair<String, String>> properties) {
this.properties = properties;
}
@Override
public Key type() {
return LootConditions.MATCH_BLOCK_PROPERTY;
}
@Override
public boolean test(LootContext lootContext) {
return lootContext.getOptionalParameter(LootParameters.BLOCK_STATE).map(state -> {
CustomBlock block = state.owner().value();
for (Pair<String, String> property : this.properties) {
Property<?> propertyIns = block.getProperty(property.left());
if (propertyIns == null) {
return false;
}
if (!state.get(propertyIns).toString().toLowerCase(Locale.ENGLISH).equals(property.right())) {
return false;
}
}
return true;
}).orElse(false);
}
public static class Factory implements LootConditionFactory {
@SuppressWarnings("unchecked")
@Override
public LootCondition create(Map<String, Object> arguments) {
Map<String, Object> properties = (Map<String, Object>) arguments.get("properties");
if (properties == null) {
throw new IllegalArgumentException("Missing 'properties' argument for 'match_block_property'");
}
List<Pair<String, String>> propertyList = new ArrayList<>();
for (Map.Entry<String, Object> entry : properties.entrySet()) {
propertyList.add(new Pair<>(entry.getKey(), entry.getValue().toString()));
}
return new MatchBlockPropertyCondition(propertyList);
}
}
}

View File

@@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.registry.Registries;
import net.momirealms.craftengine.core.registry.WritableRegistry;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.ResourceKey;
import java.util.Collections;
@@ -78,9 +79,11 @@ public class ApplyBonusCountFunction<T> extends AbstractLootConditionalFunction<
public static class Formulas {
public static final Key ORE_DROPS = Key.of("craftengine:ore_drops");
public static final Key CROP_DROPS = Key.of("craftengine:binomial_with_bonus_count");
static {
register(ORE_DROPS, OreDrops.FACTORY);
register(CROP_DROPS, CropDrops.FACTORY);
}
public static void register(Key key, FormulaFactory factory) {
@@ -133,4 +136,40 @@ public class ApplyBonusCountFunction<T> extends AbstractLootConditionalFunction<
}
}
}
public static class CropDrops implements Formula {
public static final Factory FACTORY = new Factory();
private final int extra;
private final float probability;
public CropDrops(int extra, float probability) {
this.extra = extra;
this.probability = probability;
}
@Override
public int apply(int initialCount, int enchantmentLevel) {
for (int i = 0; i < enchantmentLevel + this.extra; i++) {
if (RandomUtils.generateRandomFloat(0,1) < this.probability) {
initialCount++;
}
}
return initialCount;
}
@Override
public Key type() {
return Formulas.CROP_DROPS;
}
public static class Factory implements FormulaFactory {
@Override
public Formula create(Map<String, Object> arguments) {
int extra = MiscUtils.getAsInt(arguments.getOrDefault("extra", 1));
float probability = MiscUtils.getAsFloat(arguments.getOrDefault("probability", 0.5f));
return new CropDrops(extra, probability);
}
}
}
}

View File

@@ -18,5 +18,4 @@ public class LootParameters {
public static final ContextKey<Player> PLAYER = new ContextKey<>(Key.of("craftengine:player"));
public static final ContextKey<Item<?>> TOOL = new ContextKey<>(Key.of("craftengine:tool"));
public static final ContextKey<ImmutableBlockState> BLOCK_STATE = new ContextKey<>(Key.of("craftengine:block_state"));
public static final ContextKey<Boolean> CROP_RIPE = new ContextKey<>(Key.of("craftengine:crop_ripe"));
}

View File

@@ -6,6 +6,7 @@ import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class MiscUtils {
@@ -22,6 +23,15 @@ public class MiscUtils {
throw new IllegalArgumentException("Expected Map, got: " + obj.getClass().getSimpleName());
}
@SuppressWarnings("unchecked")
public static List<Map<String, Object>> castToMapListOrThrow(Object obj, Supplier<RuntimeException> exceptionSupplier) {
if (obj instanceof List<?> list) {
return (List<Map<String, Object>>) list;
} else {
throw exceptionSupplier.get();
}
}
@SuppressWarnings("unchecked")
public static List<Object> castToList(Object obj, boolean allowNull) {
if (allowNull && obj == null) {