package com.hypixel.hytale.server.npc.instructions.builders; import com.google.gson.JsonElement; import com.hypixel.hytale.server.npc.asset.builder.Builder; import com.hypixel.hytale.server.npc.asset.builder.BuilderBase; import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState; import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper; import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper; import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport; import com.hypixel.hytale.server.npc.asset.builder.BuilderValidationHelper; import com.hypixel.hytale.server.npc.asset.builder.FeatureEvaluatorHelper; import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder; import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder; import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator; import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator; import com.hypixel.hytale.server.npc.instructions.ActionList; import com.hypixel.hytale.server.npc.instructions.BodyMotion; import com.hypixel.hytale.server.npc.instructions.HeadMotion; import com.hypixel.hytale.server.npc.instructions.Instruction; import com.hypixel.hytale.server.npc.instructions.Sensor; import com.hypixel.hytale.server.npc.util.expression.ExecutionContext; import com.hypixel.hytale.server.npc.util.expression.Scope; import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class BuilderInstruction extends BuilderBase { public static final String[] ANTECEDENT = new String[]{"TreeMode"}; public static final String[] SUBSEQUENT = new String[]{"Continue"}; protected final BuilderObjectReferenceHelper sensorBuilderObjectReferenceHelper = new BuilderObjectReferenceHelper<>(Sensor.class, this); protected final BuilderObjectReferenceHelper bodyMotionBuilderObjectReferenceHelper = new BuilderObjectReferenceHelper<>(BodyMotion.class, this); protected final BuilderObjectReferenceHelper headMotionBuilderObjectReferenceHelper = new BuilderObjectReferenceHelper<>(HeadMotion.class, this); protected final BuilderObjectReferenceHelper actionsBuilderObjectReferenceHelper = new BuilderObjectReferenceHelper<>(ActionList.class, this); protected final BuilderObjectListHelper steps = new BuilderObjectListHelper<>(Instruction.class, this); protected String name; protected String tag; protected boolean continueAfter; protected final DoubleHolder chance = new DoubleHolder(); protected final BooleanHolder enabled = new BooleanHolder(); protected boolean actionsBlocking; protected boolean actionsAtomic; protected boolean treeMode; protected final BooleanHolder invertTreeModeResult = new BooleanHolder(); protected String currentStateName; @Nonnull @Override public String getShortDescription() { return "An instruction with Sensor, and Motions and Actions, or a list of nested instructions."; } @Nonnull @Override public String getLongDescription() { return "An instruction with Sensor, and Motions and Actions, or a list of nested instructions."; } @Nonnull @Override public BuilderDescriptorState getBuilderDescriptorState() { return BuilderDescriptorState.WorkInProgress; } @Nonnull @Override public Builder readConfig(@Nonnull JsonElement data) { FeatureEvaluatorHelper features = new FeatureEvaluatorHelper(); BuilderValidationHelper helper = new BuilderValidationHelper( this.fileName, features, this.internalReferenceResolver, this.stateHelper, this.instructionContextHelper, this.extraInfo, this.evaluators, this.readErrors ); if (this.requiresName()) { this.requireString(data, "Name", v -> this.name = v, null, BuilderDescriptorState.Stable, "Name for referencing", null); } else { this.getString(data, "Name", v -> this.name = v, null, null, BuilderDescriptorState.Stable, "Optional name for descriptor", null); } this.increaseDepth(); this.getString( data, "Tag", v -> this.tag = v, null, StringNullOrNotEmptyValidator.get(), BuilderDescriptorState.Experimental, "Internal identifier tag for debugging", null ); this.getBoolean(data, "Enabled", this.enabled, true, BuilderDescriptorState.Stable, "Whether this step should be enabled on the NPC", null); this.getObject( data, "Sensor", this.sensorBuilderObjectReferenceHelper, BuilderDescriptorState.Stable, "Sensor for testing if step can be applied", "Sensor for testing if step can be applied. If not supplied, will always match", helper ); features.lock(); this.getObject(data, "BodyMotion", this.bodyMotionBuilderObjectReferenceHelper, BuilderDescriptorState.Stable, "Body motion to be executed", null, helper); this.getObject(data, "HeadMotion", this.headMotionBuilderObjectReferenceHelper, BuilderDescriptorState.Stable, "Head motion to be executed", null, helper); this.getObject(data, "Actions", this.actionsBuilderObjectReferenceHelper, BuilderDescriptorState.Stable, "Actions to be executed", null, helper); this.getBoolean( data, "ActionsBlocking", b -> this.actionsBlocking = b, false, BuilderDescriptorState.Stable, "Do not execute an action unless the previous action could execute", null ); this.getBoolean( data, "ActionsAtomic", b -> this.actionsAtomic = b, false, BuilderDescriptorState.Stable, "Only execute actions if all actions can be executed", null ); this.getArray( data, "Instructions", this.steps, null, BuilderDescriptorState.Stable, "Optional nested list of instructions", null, new BuilderValidationHelper( this.fileName, null, this.internalReferenceResolver, this.stateHelper, this.instructionContextHelper, this.extraInfo, this.evaluators, this.readErrors ) ); this.getBoolean( data, "Continue", v -> this.continueAfter = v, false, BuilderDescriptorState.WorkInProgress, "Continue after this step was executed", null ); this.getDouble( data, "Weight", this.chance, 1.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Weighted chance of picking this step in a random instruction", null ); this.getBoolean( data, "TreeMode", v -> this.treeMode = v, false, BuilderDescriptorState.Stable, "Whether this step and its contents should be treated like a traditional behaviour tree.", "Whether this step and its contents should be treated like a traditional behaviour tree, i.e. will continue if all child steps fail" ); this.getBoolean( data, "InvertTreeModeResult", this.invertTreeModeResult, false, BuilderDescriptorState.Stable, "Whether or not to invert the result of TreeMode evaluation when passing up to parent TreeMode steps", null ); this.decreaseDepth(); this.validateOneOrNonePresent("BodyMotion", this.bodyMotionBuilderObjectReferenceHelper, "Steps", this.steps); this.validateOneOrNonePresent("HeadMotion", this.headMotionBuilderObjectReferenceHelper, "Steps", this.steps); this.validateOneOrNonePresent("Actions", this.actionsBuilderObjectReferenceHelper, "Steps", this.steps); this.validateBooleanImplicationAnyAntecedent(ANTECEDENT, new boolean[]{this.treeMode}, true, SUBSEQUENT, new boolean[]{this.continueAfter}, false); return this; } @Nullable public Instruction build(@Nonnull BuilderSupport builderSupport) { if (!this.enabled.get(builderSupport.getExecutionContext())) { return null; } else { Sensor sensor; if (this.sensorBuilderObjectReferenceHelper.isPresent()) { sensor = this.getSensor(builderSupport); if (sensor == null) { return null; } } else { sensor = Sensor.NULL; } if (this.currentStateName != null) { builderSupport.pushCurrentStateName(this.currentStateName); } Instruction[] instructionList = this.hasNestedInstructions() ? this.getSteps(builderSupport) : null; if (instructionList == null && !this.hasActions() && !this.hasBodyMotion() && !this.hasHeadMotion()) { if (this.currentStateName != null) { builderSupport.popCurrentStateName(); } return null; } else { if (this.currentStateName != null) { builderSupport.popCurrentStateName(); } return new Instruction(this, sensor, instructionList, builderSupport); } } } @Override public boolean validate( String configName, @Nonnull NPCLoadTimeValidationHelper validationHelper, @Nonnull ExecutionContext context, Scope globalScope, @Nonnull List errors ) { if (!this.enabled.get(context)) { return true; } else { if (this.currentStateName != null) { validationHelper.pushCurrentStateName(this.currentStateName); } boolean result = this.sensorBuilderObjectReferenceHelper.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.bodyMotionBuilderObjectReferenceHelper.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.headMotionBuilderObjectReferenceHelper.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.actionsBuilderObjectReferenceHelper.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.steps.validate(configName, validationHelper, this.builderManager, context, globalScope, errors); if (this.currentStateName != null) { validationHelper.popCurrentStateName(); } validationHelper.clearParentSensorOnce(); return result; } } @Nonnull @Override public Class category() { return Instruction.class; } @Override public final boolean isEnabled(ExecutionContext context) { return this.enabled.get(context); } @Override public void setCurrentStateName(String name) { this.currentStateName = name; } public boolean hasActions() { return this.actionsBuilderObjectReferenceHelper.isPresent(); } public boolean hasBodyMotion() { return this.bodyMotionBuilderObjectReferenceHelper.isPresent(); } public boolean hasHeadMotion() { return this.headMotionBuilderObjectReferenceHelper.isPresent(); } public boolean hasNestedInstructions() { return this.steps.isPresent() && !this.steps.hasNoElements(); } @Nullable public Sensor getSensor(@Nonnull BuilderSupport builderSupport) { return this.sensorBuilderObjectReferenceHelper.build(builderSupport); } @Nullable public BodyMotion getBodyMotion(@Nonnull BuilderSupport builderSupport) { return this.bodyMotionBuilderObjectReferenceHelper.build(builderSupport); } @Nullable public HeadMotion getHeadMotion(@Nonnull BuilderSupport support) { return this.headMotionBuilderObjectReferenceHelper.build(support); } @Nonnull public ActionList getActionList(@Nonnull BuilderSupport builderSupport) { ActionList actions = this.actionsBuilderObjectReferenceHelper.build(builderSupport); if (actions == null) { return ActionList.EMPTY_ACTION_LIST; } else { if (actions != ActionList.EMPTY_ACTION_LIST) { actions.setAtomic(this.actionsAtomic); actions.setBlocking(this.actionsBlocking); } return actions; } } @Nonnull public Instruction[] getSteps(@Nonnull BuilderSupport support) { List stepList = this.steps.build(support); return stepList == null ? Instruction.EMPTY_ARRAY : stepList.toArray(Instruction[]::new); } @Nullable public String getName() { return this.name; } public String getTag() { return this.tag; } public boolean isContinueAfter() { return this.continueAfter; } public double getChance(@Nonnull BuilderSupport support) { return this.chance.get(support.getExecutionContext()); } public boolean isTreeMode() { return this.treeMode; } public boolean isInvertTreeModeResult(@Nonnull BuilderSupport support) { return this.invertTreeModeResult.get(support.getExecutionContext()); } protected boolean requiresName() { return false; } }