From dcfa9e6c367964f56de938d45afb381d807005b5 Mon Sep 17 00:00:00 2001 From: Xenmai Date: Sun, 3 Jun 2018 17:43:26 +0200 Subject: [PATCH] Entity Scripts! --- .../denizen2sponge/Denizen2Sponge.java | 4 + .../Denizen2SpongeImplementation.java | 1 + .../commands/entity/SpawnCommand.java | 50 +++- .../spongescripts/EntityScript.java | 224 ++++++++++++++++++ .../spongescripts/ItemScript.java | 4 +- .../tags/objects/EntityTag.java | 47 +++- .../utilities/EntityTemplate.java | 46 ++++ 7 files changed, 363 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/denizenscript/denizen2sponge/spongescripts/EntityScript.java create mode 100644 src/main/java/com/denizenscript/denizen2sponge/utilities/EntityTemplate.java diff --git a/src/main/java/com/denizenscript/denizen2sponge/Denizen2Sponge.java b/src/main/java/com/denizenscript/denizen2sponge/Denizen2Sponge.java index 57965ca..ff48c44 100644 --- a/src/main/java/com/denizenscript/denizen2sponge/Denizen2Sponge.java +++ b/src/main/java/com/denizenscript/denizen2sponge/Denizen2Sponge.java @@ -28,6 +28,7 @@ import com.denizenscript.denizen2sponge.spongeevents.Denizen2SpongeLoadedEvent; import com.denizenscript.denizen2sponge.spongeevents.Denizen2SpongeLoadingEvent; import com.denizenscript.denizen2sponge.spongescripts.AdvancementScript; +import com.denizenscript.denizen2sponge.spongescripts.EntityScript; import com.denizenscript.denizen2sponge.spongescripts.GameCommandScript; import com.denizenscript.denizen2sponge.spongescripts.ItemScript; import com.denizenscript.denizen2sponge.tags.handlers.*; @@ -89,6 +90,8 @@ public static Text parseColor(String inp) { public static final Map itemScripts = new HashMap<>(); + public static final Map entityScripts = new HashMap<>(); + static { YAMLConfiguration tconfig = null; try { @@ -266,6 +269,7 @@ public void onServerStart(GamePreInitializationEvent event) { Denizen2Core.register("command", GameCommandScript::new); Denizen2Core.register("advancement", AdvancementScript::new); Denizen2Core.register("item", ItemScript::new); + Denizen2Core.register("entity", EntityScript::new); // Tag Types Denizen2Core.customSaveLoaders.put("BlockTypeTag", BlockTypeTag::getFor); Denizen2Core.customSaveLoaders.put("CuboidTag", CuboidTag::getFor); diff --git a/src/main/java/com/denizenscript/denizen2sponge/Denizen2SpongeImplementation.java b/src/main/java/com/denizenscript/denizen2sponge/Denizen2SpongeImplementation.java index 66c1078..09c3a61 100644 --- a/src/main/java/com/denizenscript/denizen2sponge/Denizen2SpongeImplementation.java +++ b/src/main/java/com/denizenscript/denizen2sponge/Denizen2SpongeImplementation.java @@ -24,6 +24,7 @@ public void preReload() { AdvancementScript.oldAdvancementScripts = new HashSet<>(AdvancementScript.currentAdvancementScripts.keySet()); AdvancementScript.currentAdvancementScripts.clear(); Denizen2Sponge.itemScripts.clear(); + Denizen2Sponge.entityScripts.clear(); } @Override diff --git a/src/main/java/com/denizenscript/denizen2sponge/commands/entity/SpawnCommand.java b/src/main/java/com/denizenscript/denizen2sponge/commands/entity/SpawnCommand.java index c47bd42..adcba8e 100644 --- a/src/main/java/com/denizenscript/denizen2sponge/commands/entity/SpawnCommand.java +++ b/src/main/java/com/denizenscript/denizen2sponge/commands/entity/SpawnCommand.java @@ -6,15 +6,21 @@ import com.denizenscript.denizen2core.tags.AbstractTagObject; import com.denizenscript.denizen2core.tags.objects.BooleanTag; import com.denizenscript.denizen2core.tags.objects.MapTag; +import com.denizenscript.denizen2core.utilities.CoreUtilities; import com.denizenscript.denizen2core.utilities.debugging.ColorSet; +import com.denizenscript.denizen2sponge.Denizen2Sponge; import com.denizenscript.denizen2sponge.tags.objects.EntityTag; -import com.denizenscript.denizen2sponge.tags.objects.EntityTypeTag; import com.denizenscript.denizen2sponge.tags.objects.LocationTag; import com.denizenscript.denizen2sponge.utilities.DataKeys; +import com.denizenscript.denizen2sponge.utilities.EntityTemplate; import com.denizenscript.denizen2sponge.utilities.UtilLocation; +import com.denizenscript.denizen2sponge.utilities.Utilities; +import org.spongepowered.api.Sponge; import org.spongepowered.api.data.key.Key; import org.spongepowered.api.entity.Entity; import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.event.cause.EventContextKeys; +import org.spongepowered.api.event.cause.entity.spawn.SpawnTypes; import java.util.Map; @@ -64,20 +70,42 @@ public int getMaximumArguments() { @Override public void execute(CommandQueue queue, CommandEntry entry) { - EntityTypeTag entityTypeTag = EntityTypeTag.getFor(queue.error, entry.getArgumentObject(queue, 0)); - EntityType entityType = entityTypeTag.getInternal(); LocationTag locationTag = LocationTag.getFor(queue.error, entry.getArgumentObject(queue, 1)); UtilLocation location = locationTag.getInternal(); if (location.world == null) { queue.handleError(entry, "Invalid location with no world in Spawn command!"); return; } - Entity entity = location.world.createEntity(entityType, location.toVector3d()); + String str = entry.getArgumentObject(queue, 0).toString(); + EntityType entType = (EntityType) Utilities.getTypeWithDefaultPrefix(EntityType.class, str); + Entity entity; + boolean fromScript; MapTag propertyMap = new MapTag(); + if (entType != null) { + fromScript = false; + entity = location.world.createEntity(entType, location.toVector3d()); + } + else { + String strLow = CoreUtilities.toLowerCase(str); + if (Denizen2Sponge.entityScripts.containsKey(strLow)) { + EntityTemplate template = Denizen2Sponge.entityScripts.get(strLow).getEntityCopy(queue); + entType = template.type; + propertyMap = template.properties; + fromScript = true; + entity = location.world.createEntity(entType, location.toVector3d()); + } + else { + queue.handleError(entry, "No entity types nor scripts found for id '" + str + "'."); + return; + } + } if (entry.arguments.size() > 2) { - propertyMap = MapTag.getFor(queue.error, entry.getArgumentObject(queue, 2)); + MapTag moreProperties = MapTag.getFor(queue.error, entry.getArgumentObject(queue, 2)); + propertyMap.getInternal().putAll(moreProperties.getInternal()); + } + if (!propertyMap.getInternal().isEmpty()) { for (Map.Entry mapEntry : propertyMap.getInternal().entrySet()) { - if (mapEntry.getKey().equalsIgnoreCase("rotation")) { + if (mapEntry.getKey().equalsIgnoreCase("orientation")) { LocationTag rot = LocationTag.getFor(queue.error, mapEntry.getValue()); entity.setRotation(rot.getInternal().toVector3d()); } @@ -92,11 +120,13 @@ public void execute(CommandQueue queue, CommandEntry entry) { } } if (queue.shouldShowGood()) { - queue.outGood("Spawning an entity of type " + ColorSet.emphasis + entityType.getId() - + ColorSet.good + " with the following properties: " + ColorSet.emphasis - + propertyMap.debug() + ColorSet.good + " at location " + ColorSet.emphasis - + locationTag.debug() + ColorSet.good + "..."); + queue.outGood("Spawning an entity " + ColorSet.emphasis + + (fromScript ? "from script " + str : "of type " + entType.getId()) + + ColorSet.good + " with the following additional properties: " + + ColorSet.emphasis + propertyMap.debug() + ColorSet.good + " at location " + + ColorSet.emphasis + locationTag.debug() + ColorSet.good + "..."); } + Sponge.getCauseStackManager().addContext(EventContextKeys.SPAWN_TYPE, SpawnTypes.CUSTOM); boolean passed = location.world.spawnEntity(entity); // TODO: "Cause" argument! if (queue.shouldShowGood()) { diff --git a/src/main/java/com/denizenscript/denizen2sponge/spongescripts/EntityScript.java b/src/main/java/com/denizenscript/denizen2sponge/spongescripts/EntityScript.java new file mode 100644 index 0000000..f41626f --- /dev/null +++ b/src/main/java/com/denizenscript/denizen2sponge/spongescripts/EntityScript.java @@ -0,0 +1,224 @@ +package com.denizenscript.denizen2sponge.spongescripts; + +import com.denizenscript.denizen2core.Denizen2Core; +import com.denizenscript.denizen2core.arguments.Argument; +import com.denizenscript.denizen2core.commands.CommandQueue; +import com.denizenscript.denizen2core.scripts.CommandScript; +import com.denizenscript.denizen2core.tags.AbstractTagObject; +import com.denizenscript.denizen2core.tags.objects.BooleanTag; +import com.denizenscript.denizen2core.tags.objects.MapTag; +import com.denizenscript.denizen2core.tags.objects.ScriptTag; +import com.denizenscript.denizen2core.utilities.Action; +import com.denizenscript.denizen2core.utilities.CoreUtilities; +import com.denizenscript.denizen2core.utilities.ErrorInducedException; +import com.denizenscript.denizen2core.utilities.Tuple; +import com.denizenscript.denizen2core.utilities.debugging.ColorSet; +import com.denizenscript.denizen2core.utilities.debugging.Debug; +import com.denizenscript.denizen2core.utilities.yaml.StringHolder; +import com.denizenscript.denizen2core.utilities.yaml.YAMLConfiguration; +import com.denizenscript.denizen2sponge.Denizen2Sponge; +import com.denizenscript.denizen2sponge.tags.objects.EntityTypeTag; +import com.denizenscript.denizen2sponge.utilities.EntityTemplate; +import com.denizenscript.denizen2sponge.utilities.Utilities; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.item.ItemType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class EntityScript extends CommandScript { + + // <--[explanation] + // @Since 0.5.0 + // @Name Entity Scripts + // @Group Script Types + // @Description + // An entity script is a type of script that fully defines specific entity template, which can + // be used to spawn entities afterwards. Keys in an entity script define which properties the + // final entity will have. + // + // An entity script can be used in place of any normal entity type, by putting the name of the + // entity script where a script expects an entity type. A script may block this from user input by + // requiring a valid EntityTypeTag input, which will not recognize an item script. + // + // The entity script name may not be the same as an existing entity type name. + // + // Entities generated from an entity script will remember their type using flag "_d2_script". + // To stop this from occurring, set key "plain" to "true". + // + // Set key "static" to "true" on the entity script to make it load once at startup and simply be duplicated on + // all usages. If static is false or unspecified, the entity script will be loaded from data at each call + // requesting it. This is likely preferred if any tags are used within the script. + // + // All options listed below are used to define the entity's specific details. + // They all support tags on input. All options other than "base" may use the automatically + // included definition tag <[base]> to get a map of the base entity's properties. + // + // Set key "base" directly to an EntityTypeTag of the basic entity type to use. You may also + // use an existing entity script to inherit its properties. Be careful to not list the + // entity script within itself, even indirectly, as this can cause recursion errors. + // + // Set key "display name" directly to a TextTag or FormattedTextTag value of the name the entity should have. + // + // Set key "flags" as a section and within it put all flags keyed by name and with the value that each flag should hold. + // If you wish to dynamically structure the mapping, see the "keys" option for specifying that. + // + // To specify other values, create a section labeled "keys" and within it put any valid item keys. + // TODO: Create and reference an explanation of basic entity keys. + // --> + + public EntityScript(String name, YAMLConfiguration section) { + super(name, section); + entityScriptName = CoreUtilities.toLowerCase(name); + } + + public final String entityScriptName; + + private static final CommandQueue FORCE_TO_STATIC = new CommandQueue(); // Special case recursive static generation helper + + @Override + public boolean init() { + if (super.init()) { + try { + prepValues(); + Action error = (es) -> { + throw new ErrorInducedException(es); + }; + if (contents.contains("static") && BooleanTag.getFor(error, contents.getString("static")).getInternal()) { + staticEntity = getEntityCopy(FORCE_TO_STATIC); + } + } + catch (ErrorInducedException ex) { + Debug.error("Entity generation for " + ColorSet.emphasis + title + ColorSet.warning + ": " + ex.getMessage()); + return false; + } + Denizen2Sponge.entityScripts.put(entityScriptName, this); + return true; + } + return false; + } + + public EntityTemplate staticEntity = null; + + public EntityTemplate getEntityCopy(CommandQueue queue) { + if (staticEntity != null) { + return staticEntity.copy(); + } + return generateEntity(queue); + } + + public Argument displayName, plain, base; + + public List> otherValues, flags; + + public void prepValues() { + Action error = (es) -> { + throw new ErrorInducedException(es); + }; + if (Sponge.getRegistry().getType(ItemType.class, title).isPresent()) { + Debug.error("Entity script " + title + " may be unusable: a base entity type exists with that name!"); + } + if (contents.contains("display name")) { + displayName = Denizen2Core.splitToArgument(contents.getString("display name"), true, true, error); + } + if (contents.contains("plain")) { + plain = Denizen2Core.splitToArgument(contents.getString("plain"), true, true, error); + } + if (contents.contains("base")) { + base = Denizen2Core.splitToArgument(contents.getString("base"), true, true, error); + } + else { + throw new ErrorInducedException("Base key is missing. Cannot generate!"); + } + if (contents.contains("flags")) { + flags = new ArrayList<>(); + YAMLConfiguration sec = contents.getConfigurationSection("flags"); + for (StringHolder key : sec.getKeys(false)) { + Argument arg = Denizen2Core.splitToArgument(sec.getString(key.str), true, true, error); + flags.add(new Tuple<>(CoreUtilities.toUpperCase(key.low), arg)); + } + } + if (contents.contains("keys")) { + otherValues = new ArrayList<>(); + YAMLConfiguration sec = contents.getConfigurationSection("keys"); + for (StringHolder key : sec.getKeys(false)) { + Argument arg = Denizen2Core.splitToArgument(sec.getString(key.str), true, true, error); + otherValues.add(new Tuple<>(CoreUtilities.toUpperCase(key.low), arg)); + } + } + } + + public AbstractTagObject parseVal(CommandQueue queue, Argument arg, HashMap varBack) { + Action error = (es) -> { + throw new ErrorInducedException(es); + }; + return arg.parse(queue, varBack, getDebugMode(), error); + } + + public EntityTemplate generateEntity(CommandQueue queue) { + Action error = (es) -> { + throw new ErrorInducedException(es); + }; + EntityTemplate ent; + HashMap varBack = new HashMap<>(); + String baseStr = parseVal(queue, base, varBack).toString(); + EntityType entType = (EntityType) Utilities.getTypeWithDefaultPrefix(EntityType.class, baseStr); + if (entType != null) { + ent = new EntityTemplate(entType); + } + else { + String baseLow = CoreUtilities.toLowerCase(baseStr); + if (Denizen2Sponge.entityScripts.containsKey(baseLow)) { + EntityTemplate baseEnt = Denizen2Sponge.entityScripts.get(baseLow).getEntityCopy(queue); + ent = new EntityTemplate(baseEnt); + MapTag baseProperties = new MapTag(baseEnt.properties.getInternal()); + baseProperties.getInternal().put("type", new EntityTypeTag(ent.type)); + varBack.put("base", baseProperties); + } + else { + throw new ErrorInducedException("No entity types or scripts found for id '" + baseStr + "'."); + } + } + MapTag properties = new MapTag(); + if (displayName != null) { + properties.getInternal().put("display_name", parseVal(queue, displayName, varBack)); + } + if (otherValues != null) { + for (Tuple input : otherValues) { + properties.getInternal().put(input.one, parseVal(queue, input.two, varBack)); + } + } + MapTag flagsMap; + AbstractTagObject ato = ent.properties.getInternal().get("flagmap"); + if (ato != null) { + flagsMap = new MapTag(((MapTag) ato).getInternal()); + } + else { + flagsMap = new MapTag(); + } + if (flags != null) { + for (Tuple flagVal : flags) { + flagsMap.getInternal().put(flagVal.one, parseVal(queue, flagVal.two, varBack)); + } + } + if (plain == null || !BooleanTag.getFor(error, parseVal(queue, plain, varBack)).getInternal()) { + flagsMap.getInternal().put("_d2_script", new ScriptTag(this)); + } + if (!flagsMap.getInternal().isEmpty()) { + properties.getInternal().put("flagmap", flagsMap); + } + ent.addProperties(properties); + if (queue == FORCE_TO_STATIC && contents.contains("static") + && BooleanTag.getFor(error, contents.getString("static")).getInternal()) { + staticEntity = ent; + } + return ent; + } + + @Override + public boolean isExecutable(String section) { + return false; + } +} diff --git a/src/main/java/com/denizenscript/denizen2sponge/spongescripts/ItemScript.java b/src/main/java/com/denizenscript/denizen2sponge/spongescripts/ItemScript.java index 701fce9..faf512e 100644 --- a/src/main/java/com/denizenscript/denizen2sponge/spongescripts/ItemScript.java +++ b/src/main/java/com/denizenscript/denizen2sponge/spongescripts/ItemScript.java @@ -181,10 +181,10 @@ public ItemStack generateItem(CommandQueue queue) { if (displayName != null) { AbstractTagObject ato = parseVal(queue, displayName, varBack); if (ato instanceof FormattedTextTag) { - its = its.add(Keys.DISPLAY_NAME, ((FormattedTextTag) ato).getInternal()); + its.add(Keys.DISPLAY_NAME, ((FormattedTextTag) ato).getInternal()); } else { - its = its.add(Keys.DISPLAY_NAME, Denizen2Sponge.parseColor(ato.toString())); + its.add(Keys.DISPLAY_NAME, Denizen2Sponge.parseColor(ato.toString())); } } if (lore != null) { diff --git a/src/main/java/com/denizenscript/denizen2sponge/tags/objects/EntityTag.java b/src/main/java/com/denizenscript/denizen2sponge/tags/objects/EntityTag.java index 2ed8798..f24e903 100644 --- a/src/main/java/com/denizenscript/denizen2sponge/tags/objects/EntityTag.java +++ b/src/main/java/com/denizenscript/denizen2sponge/tags/objects/EntityTag.java @@ -6,6 +6,7 @@ import com.denizenscript.denizen2core.utilities.Action; import com.denizenscript.denizen2core.utilities.CoreUtilities; import com.denizenscript.denizen2core.utilities.Function2; +import com.denizenscript.denizen2sponge.spongescripts.EntityScript; import com.denizenscript.denizen2sponge.utilities.DataKeys; import com.denizenscript.denizen2sponge.utilities.Utilities; import com.denizenscript.denizen2sponge.utilities.flags.FlagHelper; @@ -63,6 +64,23 @@ public Entity getInternal() { public final static HashMap> handlers = new HashMap<>(); + public EntityScript getSourceScript() { + Optional fm = internal.get(FlagHelper.FLAGMAP); + if (fm.isPresent()) { + MapTag flags = fm.get().flags; + if (flags.getInternal().containsKey("_d2_script")) { + AbstractTagObject scriptObj = flags.getInternal().get("_d2_script"); + if (scriptObj instanceof ScriptTag) { + ScriptTag script = (ScriptTag) scriptObj; + if (script.getInternal() instanceof EntityScript) { + return (EntityScript) script.getInternal(); + } + } + } + } + return null; + } + public String friendlyName() { if (internal instanceof Player) { return ((Player) internal).getName() + "/" + internal.getUniqueId(); @@ -850,7 +868,7 @@ else if (ent instanceof Ageable) { }); // <--[tag] // @Since 0.3.0 - // @Name EntityTagTag.target_entities[] + // @Name EntityTag.target_entities[] // @Updated 2017/10/17 // @Group Entity Target // @ReturnType ListTag @@ -971,6 +989,33 @@ else if (ent instanceof Ageable) { // @Returns whether an entity is currently glowing. // --> handlers.put("glowing", (dat, obj) -> new BooleanTag(((EntityTag) obj).internal.get(Keys.GLOWING).get())); + // <--[tag] + // @Since 0.5.0 + // @Name EntityTag.is_script + // @Updated 2018/05/29 + // @Group General Information + // @ReturnType BooleanTag + // @Returns whether the entity was sourced from a script. + // --> + handlers.put("is_script", (dat, obj) -> new BooleanTag(((EntityTag) obj).getSourceScript() != null)); + // <--[tag] + // @Since 0.5.0 + // @Name EntityTag.script + // @Updated 2018/05/29 + // @Group General Information + // @ReturnType ScriptTag + // @Returns the script this entity was spawned from, if any. + // --> + handlers.put("script", (dat, obj) -> { + EntityScript src = ((EntityTag) obj).getSourceScript(); + if (src == null) { + if (!dat.hasFallback()) { + dat.error.run("Entity was not sourced from a script."); + } + return new NullTag(); + } + return new ScriptTag(src); + }); } public static EntityTag getFor(Action error, String text) { diff --git a/src/main/java/com/denizenscript/denizen2sponge/utilities/EntityTemplate.java b/src/main/java/com/denizenscript/denizen2sponge/utilities/EntityTemplate.java new file mode 100644 index 0000000..1724fc2 --- /dev/null +++ b/src/main/java/com/denizenscript/denizen2sponge/utilities/EntityTemplate.java @@ -0,0 +1,46 @@ +package com.denizenscript.denizen2sponge.utilities; + +import com.denizenscript.denizen2core.tags.AbstractTagObject; +import com.denizenscript.denizen2core.tags.objects.MapTag; +import com.denizenscript.denizen2core.utilities.ErrorInducedException; +import org.spongepowered.api.data.key.Key; +import org.spongepowered.api.entity.EntityArchetype; +import org.spongepowered.api.entity.EntityType; + +import java.util.Map; + +public class EntityTemplate { + + public EntityType type; + + public MapTag properties; + + public EntityTemplate(EntityType entType) { + type = entType; + properties = new MapTag(); + } + + public EntityTemplate(EntityTemplate base) { + type = base.type; + properties = new MapTag(base.properties.getInternal()); + } + + public void addProperties(MapTag prop) { + EntityArchetype arch = EntityArchetype.of(type); + for (Map.Entry entry : prop.getInternal().entrySet()) { + Key k = DataKeys.getKeyForName(entry.getKey()); + if (k == null) { + throw new ErrorInducedException("Key '" + entry.getKey() + "' does not seem to exist."); + } + if (!arch.supports(k)) { + throw new ErrorInducedException("Entity type '" + Utilities.getIdWithoutDefaultPrefix(type.getId()) + + "' does not support key '" + entry.getKey() + "'."); + } + properties.getInternal().put(entry.getKey(), entry.getValue()); + } + } + + public EntityTemplate copy() { + return new EntityTemplate(this); + } +}