package net.alcuria.umbracraft.engine.scripts; import java.util.Set; import net.alcuria.umbracraft.Game; import net.alcuria.umbracraft.annotations.Order; import net.alcuria.umbracraft.annotations.Tooltip; import net.alcuria.umbracraft.editor.Editor; import net.alcuria.umbracraft.engine.entities.Entity; import net.alcuria.umbracraft.util.StringUtils; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ObjectMap; /** A block command to allow simple script control flow based based on some * preconditions. This cannot parse any arbitrary logical expression. It is * instead limited to only testing up to two separate conditions. * @author Andrew Keturi */ public class ConditionalCommand extends BlockCommand { /** Methods of comparison for a {@link ConditionalCommand} * @author Andrew Keturi */ public static enum ConditionalComparison { OPT_0_EQU("=="), OPT_1_NEQ("!="), OPT_2_GT(">"), OPT_3_GTE(">="), OPT_4_LT("<"), OPT_5_LTE("<="); public String friendly; private ConditionalComparison(String friendly) { this.friendly = friendly; } @Override public String toString() { return friendly; } } /** Represents a logical operator in a conditional command * @author Andrew Keturi */ public static enum LogicalOperator { OPT_0_OR("||"), OPT_1_AND("&&"); public String friendly; private LogicalOperator(String friendly) { this.friendly = friendly; } @Override public String toString() { return friendly; } } private transient ScriptCommand calculatedNext; @Tooltip("The comparison operator") @Order(2) public ConditionalComparison comparison = ConditionalComparison.OPT_0_EQU; @Tooltip("The second comparison operator") @Order(6) public ConditionalComparison comparison2 = ConditionalComparison.OPT_0_EQU; /** The else command */ public ScriptCommand elseBlock = new EmptyCommand(); @Tooltip("Add an else statement") @Order(8) public boolean includeElse; private boolean isNested; @Order(4) public LogicalOperator logicalOperator = LogicalOperator.OPT_0_OR; @Tooltip("The comparison value, either a variable/flag or a constant") @Order(1) public String value1 = ""; @Tooltip("The comparison value, either a variable/flag or a constant") @Order(3) public String value2 = ""; @Tooltip("The comparison value, either a variable/flag or a constant") @Order(5) public String value3 = ""; @Tooltip("The comparison value, either a variable/flag or a constant") @Order(7) public String value4 = ""; @Override public ScriptCommand copy() { ConditionalCommand cmd = new ConditionalCommand(); cmd.comparison = comparison; cmd.comparison2 = comparison2; cmd.elseBlock = elseBlock; cmd.includeElse = includeElse; cmd.logicalOperator = logicalOperator; cmd.value1 = value1; cmd.value2 = value2; cmd.value3 = value3; cmd.value4 = value4; return cmd; } /** @return the next {@link ScriptCommand} instruction */ public ScriptCommand getCalculated() { return calculatedNext; } @Override public Set<String> getFilter() { return null; } @Override public String getName() { if (StringUtils.isNotEmpty(value3) && StringUtils.isNotEmpty(value4)) { return String.format("Conditional: %s %s %s %s %s %s %s", value1, comparison, value2, logicalOperator, value3, comparison2, value4); } return String.format("Conditional: %s %s %s", value1, comparison, value2); } @Override public ObjectMap<String, Array<String>> getSuggestions() { return new ObjectMap<String, Array<String>>() { { for (int i = 1; i <= 4; i++) { put("value" + i, new Array<String>() { { addAll(Editor.db().flags().keys()); addAll(Editor.db().variables().keys()); } }); } } }; } /** determine the value of a particular string. If it's an integer we treat * it as a number, but if it's a String we need to dig into the * variable/flag managers. */ private int getValue(String val) { if (StringUtils.isNumber(val)) { return Integer.parseInt(val); } else if (Game.variables().exists(val)) { return Game.variables().get(val); } else if (Game.flags().isSet(val)) { return 1; } else if (StringUtils.isNotEmpty(val) && val.equalsIgnoreCase("true")) { return 1; } return 0; } /** @return <code>true</code> if the next command is nested */ public boolean isNextNested() { return isNested; } @Override public void onCompleted() { } @Override public void onStarted(Entity entity) { // 1. test the condition boolean valid = false; if (StringUtils.isNotEmpty(value3) && StringUtils.isNotEmpty(value4)) { // if third and fourth values are present, use logical operator to test condition if (logicalOperator == LogicalOperator.OPT_0_OR) { valid = testCondition(value1, value2, comparison) || testCondition(value3, value4, comparison2); } else if (logicalOperator == LogicalOperator.OPT_1_AND) { valid = testCondition(value1, value2, comparison) && testCondition(value3, value4, comparison2); } } else { // just use the first two values to test the condition valid = testCondition(value1, value2, comparison); } // 2. determine which command comes next (inside conditional, inside else, or after conditional) if (valid) { calculatedNext = block; // go inside the block isNested = true; } else if (includeElse) { calculatedNext = elseBlock; // go inside the else isNested = true; } else { calculatedNext = getNext(); // fuck it, next instruction isNested = false; } // 3. mark command for completion complete(); } private boolean testCondition(String valueA, String valueB, ConditionalComparison comp) { switch (comp) { case OPT_0_EQU: return getValue(valueA) == getValue(valueB); case OPT_1_NEQ: return getValue(valueA) != getValue(valueB); case OPT_2_GT: return getValue(valueA) > getValue(valueB); case OPT_3_GTE: return getValue(valueA) >= getValue(valueB); case OPT_4_LT: return getValue(valueA) < getValue(valueB); case OPT_5_LTE: return getValue(valueA) <= getValue(valueB); default: return false; } } @Override public void update() { } }