9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2026-01-04 15:41:38 +00:00

优化粒子行为实现

This commit is contained in:
XiaoMoMi
2025-09-14 20:49:53 +08:00
parent 458ad6d477
commit 708aa394a5
21 changed files with 495 additions and 317 deletions

View File

@@ -34,7 +34,8 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block");
public static final Key DIRECTIONAL_ATTACHED_BLOCK = Key.from("craftengine:directional_attached_block");
public static final Key LIQUID_FLOWABLE_BLOCK = Key.from("craftengine:liquid_flowable_block");
public static final Key PARTICLE_BLOCK = Key.from("craftengine:particle_block");
public static final Key SIMPLE_PARTICLE_BLOCK = Key.from("craftengine:simple_particle_block");
public static final Key WALL_TORCH_PARTICLE_BLOCK = Key.from("craftengine:wall_torch_particle_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -67,6 +68,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY);
register(DIRECTIONAL_ATTACHED_BLOCK, DirectionalAttachedBlockBehavior.FACTORY);
register(LIQUID_FLOWABLE_BLOCK, LiquidFlowableBlockBehavior.FACTORY);
register(PARTICLE_BLOCK, ParticleBlockBehavior.FACTORY);
register(SIMPLE_PARTICLE_BLOCK, SimpleParticleBlockBehavior.FACTORY);
register(WALL_TORCH_PARTICLE_BLOCK, WallTorchParticleBlockBehavior.FACTORY);
}
}

View File

@@ -1,75 +0,0 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes;
import net.momirealms.craftengine.bukkit.block.entity.BaseParticleBlockEntity;
import net.momirealms.craftengine.bukkit.block.entity.ParticleBlockEntity;
import net.momirealms.craftengine.bukkit.block.entity.WallParticleBlockEntity;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.Vec3d;
import java.util.List;
import java.util.Map;
public class ParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior {
public static final Factory FACTORY = new Factory();
private final List<ParticleData> particles;
private final boolean inWall;
public ParticleBlockBehavior(CustomBlock customBlock, List<ParticleData> particles, boolean inWall) {
super(customBlock);
this.particles = particles;
this.inWall = inWall;
}
public List<ParticleData> particles() {
return particles;
}
@Override
public <T extends BlockEntity> BlockEntityType<T> blockEntityType() {
return EntityBlockBehavior.blockEntityTypeHelper(this.inWall ? BukkitBlockEntityTypes.WALL_PARTICLE : BukkitBlockEntityTypes.PARTICLE);
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return this.inWall ? new WallParticleBlockEntity(pos, state) : new ParticleBlockEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
if (this.particles().isEmpty()) return null;
return EntityBlockBehavior.createTickerHelper(BaseParticleBlockEntity::tick);
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
List<ParticleData> particles = ResourceConfigUtils.parseConfigAsList(arguments.getOrDefault("particles", List.of()), ParticleData::fromMap);
boolean inWall = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("in-wall", false), "in-wall");
return new ParticleBlockBehavior(block, particles, inWall);
}
}
public record ParticleData(Key particle, Vec3d locationOffset, Vec3d offset, int count, double speed) {
public static ParticleData fromMap(Map<String, Object> arguments) {
Key particle = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("type"), "warning.config.block.behavior.particle.missing_type"));
Vec3d locationOffset = ResourceConfigUtils.getAsVec3d(arguments.get("location-offset"), "location-offset");
Vec3d offset = ResourceConfigUtils.getAsVec3d(arguments.get("offset"), "offset");
int count = ResourceConfigUtils.getAsInt(arguments.getOrDefault("count", 1), "count");
double speed = ResourceConfigUtils.getAsDouble(arguments.get("speed"), "speed");
return new ParticleData(particle, locationOffset, offset, count, speed);
}
}
}

View File

@@ -0,0 +1,64 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes;
import net.momirealms.craftengine.bukkit.block.entity.SimpleParticleBlockEntity;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
import java.util.List;
import java.util.Map;
public class SimpleParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior {
public static final Factory FACTORY = new Factory();
public final ParticleConfig[] particles;
public final int tickInterval;
public SimpleParticleBlockBehavior(CustomBlock customBlock, ParticleConfig[] particles, int tickInterval) {
super(customBlock);
this.particles = particles;
this.tickInterval = tickInterval;
}
public ParticleConfig[] particles() {
return this.particles;
}
public int tickInterval() {
return tickInterval;
}
@Override
public <T extends BlockEntity> BlockEntityType<T> blockEntityType() {
return EntityBlockBehavior.blockEntityTypeHelper(BukkitBlockEntityTypes.SIMPLE_PARTICLE);
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return new SimpleParticleBlockEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
if (this.particles.length == 0) return null;
return EntityBlockBehavior.createTickerHelper(SimpleParticleBlockEntity::tick);
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
List<ParticleConfig> particles = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "particles", "particle"), ParticleConfig::fromMap$blockEntity);
int tickInterval = ResourceConfigUtils.getAsInt(arguments.getOrDefault("tick-interval", 10), "tick-interval");
return new SimpleParticleBlockBehavior(block, particles.toArray(new ParticleConfig[0]), tickInterval);
}
}
}

View File

@@ -0,0 +1,78 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes;
import net.momirealms.craftengine.bukkit.block.entity.WallTorchParticleBlockEntity;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
import java.util.List;
import java.util.Map;
public class WallTorchParticleBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior {
public static final Factory FACTORY = new Factory();
public final ParticleConfig[] particles;
public final int tickInterval;
public final Property<HorizontalDirection> facingProperty;
public WallTorchParticleBlockBehavior(CustomBlock customBlock, ParticleConfig[] particles, int tickInterval, Property<HorizontalDirection> facingProperty) {
super(customBlock);
this.particles = particles;
this.tickInterval = tickInterval;
this.facingProperty = facingProperty;
}
public ParticleConfig[] particles() {
return this.particles;
}
public int tickInterval() {
return tickInterval;
}
public Property<HorizontalDirection> facingProperty() {
return facingProperty;
}
@Override
public <T extends BlockEntity> BlockEntityType<T> blockEntityType() {
return EntityBlockBehavior.blockEntityTypeHelper(BukkitBlockEntityTypes.WALL_TORCH_PARTICLE);
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return new WallTorchParticleBlockEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
if (this.particles.length == 0) return null;
return EntityBlockBehavior.createTickerHelper(WallTorchParticleBlockEntity::tick);
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
List<ParticleConfig> particles = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(arguments, "particles", "particle"), ParticleConfig::fromMap$blockEntity);
int tickInterval = ResourceConfigUtils.getAsInt(arguments.getOrDefault("tick-interval", 10), "tick-interval");
Property<HorizontalDirection> directionProperty = (Property<HorizontalDirection>) block.getProperty("facing");
if (directionProperty == null) {
throw new LocalizedResourceConfigException("warning.config.block.behavior.wall_torch_particle.missing_facing");
}
return new WallTorchParticleBlockBehavior(block, particles.toArray(new ParticleConfig[0]), tickInterval, directionProperty);
}
}
}

View File

@@ -0,0 +1,14 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.world.BlockPos;
public abstract class AbstractAnimateTickBlockEntity extends BlockEntity {
protected int tickCount;
public AbstractAnimateTickBlockEntity(BlockEntityType<? extends BlockEntity> type, BlockPos pos, ImmutableBlockState blockState) {
super(type, pos, blockState);
}
}

View File

@@ -1,27 +0,0 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity;
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.World;
public abstract class BaseParticleBlockEntity extends BlockEntity {
protected final ParticleBlockBehavior behavior;
protected int tickCount;
public BaseParticleBlockEntity(BlockEntityType<? extends BlockEntity> type, BlockPos pos, ImmutableBlockState blockState) {
super(type, pos, blockState);
this.behavior = super.blockState.behavior().getAs(ParticleBlockBehavior.class).orElseThrow();
}
public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, BaseParticleBlockEntity particle) {
particle.tickCount++;
if (particle.tickCount % 10 != 0) return;
particle.animateTick(state, ceWorld.world(), blockPos);
}
public abstract void animateTick(ImmutableBlockState state, World level, BlockPos pos);
}

View File

@@ -6,6 +6,6 @@ import net.momirealms.craftengine.core.block.entity.BlockEntityTypes;
public class BukkitBlockEntityTypes extends BlockEntityTypes {
public static final BlockEntityType<SimpleStorageBlockEntity> SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new);
public static final BlockEntityType<ParticleBlockEntity> PARTICLE = register(BlockEntityTypeKeys.PARTICLE, ParticleBlockEntity::new);
public static final BlockEntityType<WallParticleBlockEntity> WALL_PARTICLE = register(BlockEntityTypeKeys.WALL_PARTICLE, WallParticleBlockEntity::new);
public static final BlockEntityType<SimpleParticleBlockEntity> SIMPLE_PARTICLE = register(BlockEntityTypeKeys.SIMPLE_PARTICLE, SimpleParticleBlockEntity::new);
public static final BlockEntityType<WallTorchParticleBlockEntity> WALL_TORCH_PARTICLE = register(BlockEntityTypeKeys.WALL_TORCH_PARTICLE, WallTorchParticleBlockEntity::new);
}

View File

@@ -1,31 +0,0 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
public class ParticleBlockEntity extends BaseParticleBlockEntity {
public ParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) {
super(BukkitBlockEntityTypes.PARTICLE, pos, blockState);
}
@Override
public void animateTick(ImmutableBlockState state, World level, BlockPos pos) {
for (ParticleBlockBehavior.ParticleData particle : behavior.particles()) {
Vec3d location = particle.locationOffset().add(pos.x(), pos.y(), pos.z());
level.spawnParticle(
location,
particle.particle(),
particle.count(),
particle.offset().x(),
particle.offset().y(),
particle.offset().z(),
particle.speed(),
null, null
);
}
}
}

View File

@@ -0,0 +1,45 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.block.behavior.SimpleParticleBlockBehavior;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.SimpleContext;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
public class SimpleParticleBlockEntity extends AbstractAnimateTickBlockEntity {
private final SimpleParticleBlockBehavior behavior;
private final Context context = SimpleContext.of(ContextHolder.empty());
public SimpleParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) {
super(BukkitBlockEntityTypes.SIMPLE_PARTICLE, pos, blockState);
this.behavior = blockState.behavior().getAs(SimpleParticleBlockBehavior.class).orElseThrow();
}
public void animateTick(ImmutableBlockState state, World level, BlockPos pos) {
for (ParticleConfig particle : this.behavior.particles) {
Vec3d location = new Vec3d(super.pos.x() + particle.x.getDouble(context), super.pos.y() + particle.y.getDouble(context), super.pos.z() + particle.z.getDouble(context));
level.spawnParticle(
location,
particle.particleType,
particle.count.getInt(context),
particle.xOffset.getDouble(context),
particle.yOffset.getDouble(context),
particle.zOffset.getDouble(context),
particle.speed.getDouble(context),
particle.particleData,
context
);
}
}
public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, SimpleParticleBlockEntity particle) {
particle.tickCount++;
if (particle.tickCount % particle.behavior.tickInterval != 0) return;
particle.animateTick(state, ceWorld.world(), blockPos);
}
}

View File

@@ -1,51 +0,0 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.block.behavior.ParticleBlockBehavior;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
public class WallParticleBlockEntity extends BaseParticleBlockEntity {
public WallParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) {
super(BukkitBlockEntityTypes.WALL_PARTICLE, pos, blockState);
}
@Override
public void animateTick(ImmutableBlockState state, World level, BlockPos pos) {
Direction direction = null;
for (Property<?> property : state.getProperties()) {
if (!property.name().equals("facing")) continue;
if (property.valueClass() == Direction.class) {
direction = (Direction) state.get(property);
break;
} else if (property.valueClass() == HorizontalDirection.class) {
direction = ((HorizontalDirection) state.get(property)).toDirection();
break;
}
}
if (direction != null) {
direction = direction.opposite();
}
for (ParticleBlockBehavior.ParticleData particle : behavior.particles()) {
Vec3d location = particle.locationOffset().add(pos.x(), pos.y(), pos.z());
if (direction != null) {
location = location.add(0.27 * direction.stepX(), 0.22, 0.27 * direction.stepZ());
}
level.spawnParticle(
location,
particle.particle(),
particle.count(),
particle.offset().x(),
particle.offset().y(),
particle.offset().z(),
particle.speed(),
null, null
);
}
}
}

View File

@@ -0,0 +1,55 @@
package net.momirealms.craftengine.bukkit.block.entity;
import net.momirealms.craftengine.bukkit.block.behavior.WallTorchParticleBlockBehavior;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.SimpleContext;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
public class WallTorchParticleBlockEntity extends AbstractAnimateTickBlockEntity {
private final WallTorchParticleBlockBehavior behavior;
private final Context context = SimpleContext.of(ContextHolder.empty());
public WallTorchParticleBlockEntity(BlockPos pos, ImmutableBlockState blockState) {
super(BukkitBlockEntityTypes.WALL_TORCH_PARTICLE, pos, blockState);
this.behavior = blockState.behavior().getAs(WallTorchParticleBlockBehavior.class).orElseThrow();
}
public void animateTick(ImmutableBlockState state, World level, BlockPos pos) {
HorizontalDirection direction = state.get(this.behavior.facingProperty);
if (direction == null) return;
Vec3d center = Vec3d.atCenterOf(pos);
HorizontalDirection opposite = direction.opposite();
for (ParticleConfig particle : this.behavior.particles) {
Vec3d location = new Vec3d(
center.x() + particle.x.getDouble(context) * opposite.stepX(),
center.y() + particle.y.getDouble(context),
center.z() + particle.z.getDouble(context) * opposite.stepZ()
);
level.spawnParticle(
location,
particle.particleType,
particle.count.getInt(context),
particle.xOffset.getDouble(context),
particle.yOffset.getDouble(context),
particle.zOffset.getDouble(context),
particle.speed.getDouble(context),
particle.particleData,
context
);
}
}
public static void tick(CEWorld ceWorld, BlockPos blockPos, ImmutableBlockState state, WallTorchParticleBlockEntity particle) {
particle.tickCount++;
if (particle.tickCount % particle.behavior.tickInterval != 0) return;
particle.animateTick(state, ceWorld.world(), blockPos);
}
}

View File

@@ -106,11 +106,11 @@ public class BukkitWorld implements World {
}
@Override
public void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @Nullable Context context) {
public void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context) {
Particle particleType = ParticleUtils.getParticle(particle);
if (particleType == null) return;
org.bukkit.World platformWorld = platformWorld();
platformWorld.spawnParticle(particleType, location.x(), location.y(), location.z(), count, xOffset, yOffset, zOffset, speed, extraData == null || context == null ? null : ParticleUtils.toBukkitParticleData(extraData, context, platformWorld, location.x(), location.y(), location.z()));
platformWorld.spawnParticle(particleType, location.x(), location.y(), location.z(), count, xOffset, yOffset, zOffset, speed, extraData == null ? null : ParticleUtils.toBukkitParticleData(extraData, context, platformWorld, location.x(), location.y(), location.z()));
}
@Override

View File

@@ -70,12 +70,18 @@ blocks:
support-types:
- center
- type: liquid_flowable_block
- type: particle_block
- type: simple_particle_block
tick-interval: 10
particles:
- type: smoke
location-offset: 0.5,0.7,0.5
- type: flame
location-offset: 0.5,0.7,0.5
- particle: smoke
x: 0.5
y: 0.7
z: 0.5
- particle: dust
color: 138,43,226
x: 0.5
y: 0.7
z: 0.5
default:amethyst_wall_torch:
loot:
template: default:loot_table/basic
@@ -94,13 +100,17 @@ blocks:
behavior:
- type: directional_attached_block
- type: liquid_flowable_block
- type: particle_block
in-wall: true
- type: wall_torch_particle_block
particles:
- type: smoke
location-offset: 0.5,0.7,0.5
- type: flame
location-offset: 0.5,0.7,0.5
- particle: smoke
x: 0.27
y: 0.42
z: 0.27
- particle: dust
color: 138,43,226
x: 0.27
y: 0.42
z: 0.27
states:
properties:
facing:

View File

@@ -320,7 +320,7 @@ warning.config.block.behavior.grass.missing_feature: "<yellow>Issue found in fil
warning.config.block.behavior.double_high.missing_half: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'half' property for 'double_block' behavior.</yellow>"
warning.config.block.behavior.change_over_time.missing_next_block: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'next_block' argument for 'change_over_time_block' behavior.</yellow>"
warning.config.block.behavior.surface_attached.missing_facing: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'facing' property for 'surface_attached_block' behavior.</yellow>"
warning.config.block.behavior.particle.missing_type: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'type' argument for 'particle_block' behavior.</yellow>"
warning.config.block.behavior.wall_torch_particle.missing_facing: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'facing' property for 'wall_torch_particle_block' behavior.</yellow>"
warning.config.model.generation.missing_parent: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'parent' argument in 'generation' section.</yellow>"
warning.config.model.generation.conflict: "<yellow>Issue found in file <arg:0> - Failed to generate model for '<arg:1>' as two or more configurations attempt to generate different json models with the same path: '<arg:2>'.</yellow>"
warning.config.model.generation.invalid_display_position: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid display position '<arg:2>' in 'generation.display' section. Allowed display positions: [<arg:3>]</yellow>"

View File

@@ -314,7 +314,7 @@ warning.config.block.behavior.grass.missing_feature: "<yellow>在文件 <arg:0>
warning.config.block.behavior.double_high.missing_half: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'double_block' 行为缺少必需的 'half' 属性</yellow>"
warning.config.block.behavior.change_over_time.missing_next_block: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'change_over_time_block' 行为缺少必需的 'next-block' 参数</yellow>"
warning.config.block.behavior.surface_attached.missing_facing: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'surface_attached_block' 行为缺少必需的 'facing' 属性</yellow>"
warning.config.block.behavior.particle.missing_type: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 的 'particle' 段落缺少必需的 'type' 参数</yellow>"
warning.config.block.behavior.wall_torch_particle.missing_facing: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 的 'wall_torch_particle_block' 行为缺少必需的 'facing' 属性</yellow>"
warning.config.model.generation.missing_parent: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 的 'generation' 段落缺少必需的 'parent' 参数</yellow>"
warning.config.model.generation.conflict: "<yellow>在文件 <arg:0> 发现问题 - 无法为 '<arg:1>' 生成模型 存在多个配置尝试使用相同路径 '<arg:2>' 生成不同的 JSON 模型</yellow>"
warning.config.model.generation.invalid_display_position: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 在 'generation.display' 区域使用了无效的 display 位置类型 '<arg:2>'. 可用展示类型: [<arg:3>]</yellow>"

View File

@@ -7,6 +7,6 @@ public final class BlockEntityTypeKeys {
public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite");
public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage");
public static final Key PARTICLE = Key.of("craftengine:particle");
public static final Key WALL_PARTICLE = Key.of("craftengine:wall_particle");
public static final Key SIMPLE_PARTICLE = Key.of("craftengine:simple_particle");
public static final Key WALL_TORCH_PARTICLE = Key.of("craftengine:wall_torch_particle");
}

View File

@@ -1,115 +1,25 @@
package net.momirealms.craftengine.core.plugin.context.function;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.Condition;
import net.momirealms.craftengine.core.plugin.context.Context;
import net.momirealms.craftengine.core.plugin.context.number.NumberProvider;
import net.momirealms.craftengine.core.plugin.context.number.NumberProviders;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.LazyReference;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.Position;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.core.world.particle.*;
import net.momirealms.craftengine.core.world.particle.ParticleConfig;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
public class ParticleFunction<CTX extends Context> extends AbstractConditionalFunction<CTX> {
public static final Map<Key, java.util.function.Function<Map<String, Object>, ParticleData>> DATA_TYPES = new HashMap<>();
private final ParticleConfig config;
static {
registerParticleData(map -> new BlockStateData(
LazyReference.lazyReference(new Supplier<>() {
final String blockState = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("block-state"), "warning.config.function.particle.missing_block_state");
@Override
public BlockStateWrapper get() {
return CraftEngine.instance().blockManager().createBlockState(this.blockState);
}
})),
ParticleTypes.BLOCK, ParticleTypes.FALLING_DUST, ParticleTypes.DUST_PILLAR, ParticleTypes.BLOCK_CRUMBLE, ParticleTypes.BLOCK_MARKER);
registerParticleData(map -> new ColorData(
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(","))),
ParticleTypes.ENTITY_EFFECT, ParticleTypes.TINTED_LEAVES);
registerParticleData(map -> new JavaTypeData(
ResourceConfigUtils.getAsFloat(map.get("charge"), "charge")),
ParticleTypes.SCULK_CHARGE);
registerParticleData(map -> new JavaTypeData(
ResourceConfigUtils.getAsInt(map.get("shriek"), "shriek")),
ParticleTypes.SHRIEK);
registerParticleData(map -> new DustData(
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")),
ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")),
ParticleTypes.DUST);
registerParticleData(map -> new DustTransitionData(
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("from"), "warning.config.function.particle.missing_from").split(",")),
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("to"), "warning.config.function.particle.missing_to").split(",")),
ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")),
ParticleTypes.DUST_COLOR_TRANSITION);
registerParticleData(map -> new ItemStackData(
LazyReference.lazyReference(new Supplier<>() {
final Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("item"), "warning.config.function.particle.missing_item"));
@Override
public Item<?> get() {
return CraftEngine.instance().itemManager().createWrappedItem(this.itemId, null);
}
})
),
ParticleTypes.ITEM);
registerParticleData(map -> new VibrationData(
NumberProviders.fromObject(map.getOrDefault("target-x", 0)),
NumberProviders.fromObject(map.getOrDefault("target-y", 0)),
NumberProviders.fromObject(map.getOrDefault("target-z", 0)),
NumberProviders.fromObject(map.getOrDefault("arrival-time", 10))),
ParticleTypes.VIBRATION);
registerParticleData(map -> new TrailData(
NumberProviders.fromObject(map.getOrDefault("target-x", 0)),
NumberProviders.fromObject(map.getOrDefault("target-y", 0)),
NumberProviders.fromObject(map.getOrDefault("target-z", 0)),
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")),
NumberProviders.fromObject(map.getOrDefault("duration", 10))),
ParticleTypes.TRAIL);
}
public static void registerParticleData(java.util.function.Function<Map<String, Object>, ParticleData> function, Key... types) {
for (Key type : types) {
DATA_TYPES.put(type, function);
}
}
private final Key particleType;
private final NumberProvider x;
private final NumberProvider y;
private final NumberProvider z;
private final NumberProvider count;
private final NumberProvider xOffset;
private final NumberProvider yOffset;
private final NumberProvider zOffset;
private final NumberProvider speed;
private final ParticleData particleData;
public ParticleFunction(Key particleType, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider count,
NumberProvider xOffset, NumberProvider yOffset, NumberProvider zOffset, NumberProvider speed, ParticleData particleData, List<Condition<CTX>> predicates) {
public ParticleFunction(ParticleConfig config, List<Condition<CTX>> predicates) {
super(predicates);
this.particleType = particleType;
this.count = count;
this.xOffset = xOffset;
this.yOffset = yOffset;
this.zOffset = zOffset;
this.speed = speed;
this.x = x;
this.y = y;
this.z = z;
this.particleData = particleData;
this.config = config;
}
@Override
@@ -117,8 +27,8 @@ public class ParticleFunction<CTX extends Context> extends AbstractConditionalFu
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
if (optionalWorldPosition.isPresent()) {
World world = optionalWorldPosition.get().world();
Position position = new Vec3d(this.x.getDouble(ctx), this.y.getDouble(ctx), this.z.getDouble(ctx));
world.spawnParticle(position, this.particleType, this.count.getInt(ctx), this.xOffset.getDouble(ctx), this.yOffset.getDouble(ctx), this.zOffset.getDouble(ctx), this.speed.getDouble(ctx), this.particleData, ctx);
Position position = new Vec3d(config.x.getDouble(ctx), config.y.getDouble(ctx), config.z.getDouble(ctx));
world.spawnParticle(position, config.particleType, config.count.getInt(ctx), config.xOffset.getDouble(ctx), config.yOffset.getDouble(ctx), config.zOffset.getDouble(ctx), config.speed.getDouble(ctx), config.particleData, ctx);
}
}
@@ -135,17 +45,7 @@ public class ParticleFunction<CTX extends Context> extends AbstractConditionalFu
@Override
public Function<CTX> create(Map<String, Object> arguments) {
Key particleType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("particle"), "warning.config.function.particle.missing_particle"));
NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "<arg:position.x>"));
NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "<arg:position.y>"));
NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "<arg:position.z>"));
NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1));
NumberProvider xOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-x", 0));
NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0));
NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0));
NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0));
return new ParticleFunction<>(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed,
Optional.ofNullable(ParticleFunction.DATA_TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null), getPredicates(arguments));
return new ParticleFunction<>(ParticleConfig.fromMap$function(arguments), getPredicates(arguments));
}
}
}

View File

@@ -3,10 +3,26 @@ package net.momirealms.craftengine.core.util;
import org.jetbrains.annotations.NotNull;
public enum HorizontalDirection {
NORTH,
SOUTH,
WEST,
EAST;
NORTH(0, -1),
SOUTH(0, 1),
WEST(-1, 0),
EAST(1, 0);
private final int adjX;
private final int adjZ;
HorizontalDirection(int adjX, int adjZ) {
this.adjX = adjX;
this.adjZ = adjZ;
}
public int stepX() {
return this.adjX;
}
public int stepZ() {
return this.adjZ;
}
public Direction toDirection() {
return switch (this) {

View File

@@ -58,7 +58,7 @@ public interface World {
void levelEvent(int id, BlockPos pos, int data);
void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @Nullable Context context);
void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context);
long time();

View File

@@ -0,0 +1,101 @@
package net.momirealms.craftengine.core.world.particle;
import net.momirealms.craftengine.core.plugin.context.number.NumberProvider;
import net.momirealms.craftengine.core.plugin.context.number.NumberProviders;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.util.Map;
import java.util.Optional;
public class ParticleConfig {
public final Key particleType;
public final NumberProvider x;
public final NumberProvider y;
public final NumberProvider z;
public final NumberProvider count;
public final NumberProvider xOffset;
public final NumberProvider yOffset;
public final NumberProvider zOffset;
public final NumberProvider speed;
public final ParticleData particleData;
public ParticleConfig(Key particleType, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider count, NumberProvider xOffset, NumberProvider yOffset, NumberProvider zOffset, NumberProvider speed, ParticleData particleData) {
this.particleType = particleType;
this.x = x;
this.y = y;
this.z = z;
this.count = count;
this.xOffset = xOffset;
this.yOffset = yOffset;
this.zOffset = zOffset;
this.speed = speed;
this.particleData = particleData;
}
public static ParticleConfig fromMap$function(Map<String, Object> arguments) {
Key particleType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("particle"), "warning.config.function.particle.missing_particle"));
NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "<arg:position.x>"));
NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "<arg:position.y>"));
NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "<arg:position.z>"));
NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1));
NumberProvider xOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-x", 0));
NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0));
NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0));
NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0));
return new ParticleConfig(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed, Optional.ofNullable(ParticleDataTypes.TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null));
}
public static ParticleConfig fromMap$blockEntity(Map<String, Object> arguments) {
Key particleType = Key.of(arguments.getOrDefault("particle", "flame").toString());
NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", 0));
NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", 0));
NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", 0));
NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1));
NumberProvider xOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-x", 0));
NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0));
NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0));
NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0));
return new ParticleConfig(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed, Optional.ofNullable(ParticleDataTypes.TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null));
}
public Key particleType() {
return particleType;
}
public NumberProvider x() {
return x;
}
public NumberProvider y() {
return y;
}
public NumberProvider z() {
return z;
}
public NumberProvider count() {
return count;
}
public NumberProvider xOffset() {
return xOffset;
}
public NumberProvider yOffset() {
return yOffset;
}
public NumberProvider zOffset() {
return zOffset;
}
public NumberProvider speed() {
return speed;
}
public ParticleData particleData() {
return particleData;
}
}

View File

@@ -0,0 +1,77 @@
package net.momirealms.craftengine.core.world.particle;
import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.context.number.NumberProviders;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.LazyReference;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public final class ParticleDataTypes {
public static final Map<Key, java.util.function.Function<Map<String, Object>, ParticleData>> TYPES = new HashMap<>();
static {
registerParticleData(map -> new BlockStateData(
LazyReference.lazyReference(new Supplier<>() {
final String blockState = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("block-state"), "warning.config.function.particle.missing_block_state");
@Override
public BlockStateWrapper get() {
return CraftEngine.instance().blockManager().createBlockState(this.blockState);
}
})),
ParticleTypes.BLOCK, ParticleTypes.FALLING_DUST, ParticleTypes.DUST_PILLAR, ParticleTypes.BLOCK_CRUMBLE, ParticleTypes.BLOCK_MARKER);
registerParticleData(map -> new ColorData(
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(","))),
ParticleTypes.ENTITY_EFFECT, ParticleTypes.TINTED_LEAVES);
registerParticleData(map -> new JavaTypeData(
ResourceConfigUtils.getAsFloat(map.get("charge"), "charge")),
ParticleTypes.SCULK_CHARGE);
registerParticleData(map -> new JavaTypeData(
ResourceConfigUtils.getAsInt(map.get("shriek"), "shriek")),
ParticleTypes.SHRIEK);
registerParticleData(map -> new DustData(
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")),
ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")),
ParticleTypes.DUST);
registerParticleData(map -> new DustTransitionData(
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("from"), "warning.config.function.particle.missing_from").split(",")),
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("to"), "warning.config.function.particle.missing_to").split(",")),
ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")),
ParticleTypes.DUST_COLOR_TRANSITION);
registerParticleData(map -> new ItemStackData(
LazyReference.lazyReference(new Supplier<>() {
final Key itemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("item"), "warning.config.function.particle.missing_item"));
@Override
public Item<?> get() {
return CraftEngine.instance().itemManager().createWrappedItem(this.itemId, null);
}
})
),
ParticleTypes.ITEM);
registerParticleData(map -> new VibrationData(
NumberProviders.fromObject(map.getOrDefault("target-x", 0)),
NumberProviders.fromObject(map.getOrDefault("target-y", 0)),
NumberProviders.fromObject(map.getOrDefault("target-z", 0)),
NumberProviders.fromObject(map.getOrDefault("arrival-time", 10))),
ParticleTypes.VIBRATION);
registerParticleData(map -> new TrailData(
NumberProviders.fromObject(map.getOrDefault("target-x", 0)),
NumberProviders.fromObject(map.getOrDefault("target-y", 0)),
NumberProviders.fromObject(map.getOrDefault("target-z", 0)),
Color.fromStrings(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")),
NumberProviders.fromObject(map.getOrDefault("duration", 10))),
ParticleTypes.TRAIL);
}
public static void registerParticleData(java.util.function.Function<Map<String, Object>, ParticleData> function, Key... types) {
for (Key type : types) {
TYPES.put(type, function);
}
}
}