Пример ECS плагина
In this guide you will learn how to create a simple poison system utilizing all of the features you previously learned about Hytale's ECS system
Практический пример: система отравления
Примените все полученные вами знания для создания эффекта отравления. При применении к любой сущности наносит урон через определенные промежутки времени, пока у эффекта не истечет срок действия и он не исчезнет.
package scot.oskar.hytaletemplate.components;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import javax.annotation.Nullable;
public class PoisonComponent implements Component<EntityStore> {
private float damagePerTick;
private float tickInterval;
private int remainingTicks;
private float elapsedTime;
// Add static getter and setter to store componentType in the component instead of getting it from the plugin singleton.
// - This allows anyone to call PoisonComponent.getComponentType() instead of ExamplePlugin.get().getPoisonComponentType();
// - You could also update the setter to be a singleton if desired.
// - Adding a throw condition if getComponentType() returns null could also be good.
private static ComponentType<EntityStore, PoisonComponent> type;
public static ComponentType<EntityStore, PoisonComponent> getComponentType(){
return this.type;
}
public static void setComponentType(ComponentType<EntityStore, PoisonComponent> type){
this.type = type;
}
// A builder codec is needed to allow components to be saved and loaded from disk
public static final BuilderCodec<PoisonComponent> CODEC = BuilderCodec
.builder(PoisonComponent.class, PoisonComponent::new)
.append(
new KeyedCodec<>("DamagePerTick", Codec.FLOAT),
(component, value) -> component.damagePerTick = value,
component -> component.damagePerTick
).add()
.append(
new KeyedCodec<>("TickInterval", Codec.FLOAT),
(component, value) -> component.tickInterval = value,
component -> component.tickInterval
).add()
.append(
new KeyedCodec<>("RemainingTicks", Codec.INTEGER),
(component, value) -> component.remainingTicks = value,
component -> component.remainingTicks
)
.add()
.append(
new KeyedCodec<>("ElapsedTime", Codec.FLOAT),
(component, value) -> component.elapsedTime = value,
component -> component.elapsedTime
)
.add()
.build();
public PoisonComponent() {
this(5f, 1.0f, 10);
}
public PoisonComponent(float damagePerTick, float tickInterval, int totalTicks) {
this.damagePerTick = damagePerTick;
this.tickInterval = tickInterval;
this.remainingTicks = totalTicks;
this.elapsedTime = 0f;
}
public PoisonComponent(PoisonComponent other) {
this.damagePerTick = other.damagePerTick;
this.tickInterval = other.tickInterval;
this.remainingTicks = other.remainingTicks;
this.elapsedTime = other.elapsedTime;
}
@Nullable
@Override
public Component<EntityStore> clone() {
return new PoisonComponent(this);
}
public float getDamagePerTick() {
return damagePerTick;
}
public float getTickInterval() {
return tickInterval;
}
public int getRemainingTicks() {
return remainingTicks;
}
public float getElapsedTime() {
return elapsedTime;
}
public void addElapsedTime(float dt) {
this.elapsedTime += dt;
}
public void resetElapsedTime() {
this.elapsedTime = 0f;
}
public void decrementRemainingTicks() {
this.remainingTicks--;
}
public boolean isExpired() {
return this.remainingTicks <= 0;
}
}package scot.oskar.hytaletemplate.systems;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.SystemGroup;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.server.core.modules.entity.damage.Damage;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageCause;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageModule;
import com.hypixel.hytale.server.core.modules.entity.damage.DamageSystems;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import scot.oskar.hytaletemplate.components.PoisonComponent;
public class PoisonSystem extends EntityTickingSystem<EntityStore> {
private final ComponentType<EntityStore, PoisonComponent> poisonComponentType;
public PoisonSystem(ComponentType<EntityStore, PoisonComponent> poisonComponentType) {
this.poisonComponentType = poisonComponentType;
}
@Override
public void tick(float dt, int index, @Nonnull ArchetypeChunk<EntityStore> archetypeChunk,
@Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
// You could also just call PoisonComponent.getComponentType() instead of taking in the passed in variable here.
PoisonComponent poison = archetypeChunk.getComponent(index, poisonComponentType);
Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
poison.addElapsedTime(dt);
if (poison.getElapsedTime() >= poison.getTickInterval()) {
poison.resetElapsedTime();
Damage damage = new Damage(Damage.NULL_SOURCE, DamageCause.OUT_OF_WORLD, poison.getDamagePerTick());
DamageSystems.executeDamage(ref, commandBuffer, damage);
poison.decrementRemainingTicks();
}
if (poison.isExpired()) {
commandBuffer.removeComponent(ref, poisonComponentType);
}
}
@Nullable
@Override
public SystemGroup<EntityStore> getGroup() {
return DamageModule.get().getGatherDamageGroup();
}
@Nonnull
@Override
public Query<EntityStore> getQuery() {
return Query.and(this.poisonComponentType);
}
}package scot.oskar.hytaletemplate.commands;
public class ExampleCommand extends AbstractPlayerCommand {
public ExampleCommand() {
super("test", "Super test command!");
}
@Override
protected void execute(@Nonnull CommandContext commandContext, @Nonnull Store<EntityStore> store,
@Nonnull Ref<EntityStore> ref, @Nonnull PlayerRef playerRef, @Nonnull World world) {
Player player = store.getComponent(ref, Player.getComponentType());
PoisonComponent poison = new PoisonComponent(3f, 0.5f, 8);
store.addComponent(ref, PoisonComponent.getComponentType(), poison);
player.sendMessage(Message.raw("You have been poisoned!").color(Color.GREEN).bold(true));
}
}package scot.oskar.hytaletemplate;
public final class ExamplePlugin extends JavaPlugin {
private static ExamplePlugin instance;
public ExamplePlugin(@Nonnull JavaPluginInit init) {
super(init);
instance = this;
}
@Override
protected void setup() {
this.getCommandRegistry().registerCommand(new ExampleCommand());
this.getEventRegistry().registerGlobal(PlayerReadyEvent.class, ExampleEvent::onPlayerReady);
this.getEventRegistry().registerGlobal(PlayerChatEvent.class, ChatFormatter::onPlayerChat);
// Register the component & set the type in the component class so its easier to retrieve.
ComponentType<EntityStore, PoisonComponent> poisonComponentType = this.getEntityStoreRegistry()
.registerComponent(PoisonComponent.class, PoisonComponent::new);
PoisonComponent.setComponentType(poisonComponentType);
this.getEntityStoreRegistry().registerSystem(new PoisonSystem(PoisonComponent.getComponentType()));
}
public static ExamplePlugin get() {
return instance;
}
}Этот запрос использует только poisonComponentType, что значит, что система будет действовать на все сущности с PoisonComponent, не только на игроков Это делает её гибкой, появляется возможность применить эффект на NPC, мобах или других сущностях. Система ставит себя в GatherDamageGroup, так что урон, нанесённый ей, проходит через полный пайплайн урона, включая снижение брони и проверку неуязвимости.
