diff --git a/api-jvm-impl/build.gradle b/api-jvm-impl/build.gradle new file mode 100644 index 00000000..3e10edd2 --- /dev/null +++ b/api-jvm-impl/build.gradle @@ -0,0 +1,4 @@ + +dependencies { + api project(':api-jvm') +} diff --git a/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/EventsImpl.java b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/EventsImpl.java new file mode 100644 index 00000000..7b31d13e --- /dev/null +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/EventsImpl.java @@ -0,0 +1,72 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi.impl; + +import ch.vorburger.minecraft.storeys.japi.Callback; +import ch.vorburger.minecraft.storeys.japi.Events; +import ch.vorburger.minecraft.storeys.japi.Script; +import ch.vorburger.minecraft.storeys.japi.util.CommandExceptions; +import ch.vorburger.minecraft.storeys.japi.util.Texts; +import java.util.Collection; +import java.util.Optional; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandMapping; +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.command.spec.CommandSpec; + +/** + * {@link Events} implementation. + * + * Created via {@link Scripts}. + * + * The "lifecycle" of this is NOT a Singleton, + * but one for each new (possibly reloaded) {@link Script} instance. + */ +class EventsImpl implements Events, Unregisterable { + + private final Object plugin; + private final CommandSource source; + private final Collection unregistrables = new ConcurrentLinkedQueue<>(); + + EventsImpl(Object plugin, CommandSource source) { + this.plugin = plugin; + this.source = source; + } + + @Override public void whenCommand(String name, Callback callback) { + CommandSpec spec = CommandSpec.builder().executor((src, args) -> { + CommandExceptions.doOrThrow("/" + name, () -> callback.invoke(new MinecraftJvmImpl(src))); + return CommandResult.success(); + }).build(); + Optional opt = Sponge.getCommandManager().register(plugin, spec, name); + if (opt.isEmpty()) { + source.sendMessage(Texts.inRed("Could not register /" + name)); + return; + } + unregistrables.add(() -> Sponge.getCommandManager().removeMapping(opt.get())); + } + + @Override public void unregister() { + for (Unregisterable unregisterable : unregistrables) { + unregisterable.unregister(); + } + } +} diff --git a/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/MinecraftJvmImpl.java b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/MinecraftJvmImpl.java new file mode 100644 index 00000000..8411461b --- /dev/null +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/MinecraftJvmImpl.java @@ -0,0 +1,58 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi.impl; + +import static java.util.Objects.requireNonNull; + +import ch.vorburger.minecraft.storeys.japi.Events; +import ch.vorburger.minecraft.storeys.japi.Minecraft; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +/** + * {@link Minecraft} implementation. + * + * Created indirectly (by EventsImpl) via {@link Scripts}. + * + * The "lifecycle" of this is NOT a Singleton, + * but one for each instance (not just kind of) of an event registered on {@link Events}, + * such as custom command, when right clicked, when player joined, when inside, etc. + */ +class MinecraftJvmImpl implements Minecraft { + + private final CommandSource source; + + MinecraftJvmImpl(CommandSource source) { + this.source = source; + } + + @Override public void cmd(String command) { + String commandWithoutSlash = requireNonNull(command, "command").trim(); + if (commandWithoutSlash.startsWith("/")) { + commandWithoutSlash = commandWithoutSlash.substring(1); + } + Sponge.getCommandManager().process(source, requireNonNull(commandWithoutSlash, "commandWithoutSlash")); + } + + @Override public Player player() { + // TODO if (source instanceof Player), else... error handling TBD (not just log, but explain it to source) + return (Player) source; + } +} diff --git a/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/Scripts.java b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/Scripts.java new file mode 100644 index 00000000..f807f2e7 --- /dev/null +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/Scripts.java @@ -0,0 +1,37 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi.impl; + +import ch.vorburger.minecraft.storeys.japi.Script; +import com.google.errorprone.annotations.CheckReturnValue; +import org.spongepowered.api.command.CommandSource; + +public final class Scripts { + + // TODO CommandSource argument here still smells wrong (it should be "later") + + @CheckReturnValue public static Unregisterable init(Object plugin, CommandSource source, Script script) { + EventsImpl e = new EventsImpl(plugin, source); + script.init(e); + return e; + } + + private Scripts() { + } +} diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/Unregisterable.java b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/Unregisterable.java similarity index 94% rename from storeys/src/main/java/ch/vorburger/minecraft/storeys/events/Unregisterable.java rename to api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/Unregisterable.java index 39ca64b5..68309a1a 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/Unregisterable.java +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/Unregisterable.java @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -package ch.vorburger.minecraft.storeys.events; +package ch.vorburger.minecraft.storeys.japi.impl; public interface Unregisterable { diff --git a/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/util/CommandExceptions.java b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/util/CommandExceptions.java new file mode 100644 index 00000000..1e0aaf73 --- /dev/null +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/util/CommandExceptions.java @@ -0,0 +1,79 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi.util; + +import java.util.concurrent.Callable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongepowered.api.command.CommandException; + +/** + * Utilities for {@link CommandException}. + * + * @author Michael Vorburger.ch + */ +public final class CommandExceptions { + + // Copy/pasted from https://github.com/vorburger/ch.vorburger.minecraft.osgi/blob/master/ch.vorburger.minecraft.osgi.api/src/main/java/ch/vorburger/minecraft/utils/CommandExceptions.java + + private static final Logger LOG = LoggerFactory.getLogger(CommandExceptions.class); + + private CommandExceptions() { + } + + /** + * Invoke 'callable' and return its value, + * or rethrow any Exception from it wrapped in a CommandException, + * with description. + * + * @param description a humand-readable description of the Callable (used in the CommandException) + * @param callable the code to invoke + * @return the value returned by the callable + * @throws CommandException in case the callable failed with an Exception + */ + public static T getOrThrow(String description, Callable callable) throws CommandException { + try { + return callable.call(); + } catch (Exception cause) { + // TODO see isDeveloper() idea in Texts.fromThrowable + throw new CommandException(Texts.fromThrowable(description, cause), cause, true); + } + } + + public static void doOrThrow(String description, RunnableWithException runnable) throws CommandException { + try { + runnable.run(); + } catch (Exception cause) { + // TODO once the cause is properly throw to the user incl. stack trace, this log is probably redundant; then remove? + LOG.error("doOrThrow()", cause); + // TODO see isDeveloper() idea in Texts.fromThrowable + throw new CommandException(Texts.fromThrowable(description, cause), cause, true); + } + } + + public static CommandException create(String message) { + return new CommandException(Texts.inRed(message)); + } + + @FunctionalInterface + public interface RunnableWithException { + void run() throws Exception; + } + +} diff --git a/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/util/Texts.java b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/util/Texts.java new file mode 100644 index 00000000..21c14cbb --- /dev/null +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/util/Texts.java @@ -0,0 +1,46 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi.util; + +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.format.TextColors; + +/** + * Utilities for {@link Text}. + * + * @author Michael Vorburger.ch + */ +public final class Texts { + + // Copy/pasted from https://github.com/vorburger/ch.vorburger.minecraft.osgi/blob/master/ch.vorburger.minecraft.osgi.api/src/main/java/ch/vorburger/minecraft/utils/Texts.java + + private Texts() { + } + + public static Text fromThrowable(String prefix, Throwable throwable) { + // TODO have a Player isDeveloper flag (or Permission, probably..) + // developers get to see the cause stack trace? ;) Noob do not. + return Text.builder().color(TextColors.RED).append(Text.of(prefix + throwable.getMessage())).build(); + // TODO add StackTrace here - with links being able to click on to jump into sources!!! + } + + public static Text inRed(String content) { + return Text.builder().color(TextColors.RED).append(Text.of(content)).build(); + } +} diff --git a/api-jvm/build.gradle b/api-jvm/build.gradle new file mode 100644 index 00000000..df973035 --- /dev/null +++ b/api-jvm/build.gradle @@ -0,0 +1,5 @@ + +// TODO Build refactoring so that this module inherits only spongeapi WITHOUT everything else from the subprojects {} in ../build.gradle + +dependencies { +} diff --git a/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Callback.java b/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Callback.java new file mode 100644 index 00000000..6b9ee84e --- /dev/null +++ b/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Callback.java @@ -0,0 +1,25 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi; + +@FunctionalInterface +public interface Callback { + + void invoke(Minecraft m) throws Exception; +} diff --git a/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Events.java b/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Events.java new file mode 100644 index 00000000..0aae4eba --- /dev/null +++ b/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Events.java @@ -0,0 +1,28 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi; + +public interface Events { + + // This is intentionally NOT returning CommandResult, to keep it simple, for scripting. + void whenCommand(String name, Callback callback); + + // TODO add more "event handlers" here; see + // https://github.com/OASIS-learn-study/minecraft-storeys-maker/blob/develop/api/src/main/typescript/observable-wrapper.ts +} diff --git a/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Minecraft.java b/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Minecraft.java new file mode 100644 index 00000000..48468d06 --- /dev/null +++ b/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Minecraft.java @@ -0,0 +1,45 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi; + +import org.spongepowered.api.entity.living.player.Player; + +// see also the similar but Vert.x-based interface Minecraft in the api/ module +// as well as https://github.com/OASIS-learn-study/minecraft-storeys-maker/blob/develop/api/src/main/typescript/observable-wrapper.ts +public interface Minecraft { + + // TODO should this only include things which are not possible with standard Minecraft Commands, + // so e.g. there won't be a showTitle() = /title (https://minecraft.fandom.com/wiki/Commands/title), + // or is it "convenient" to have title() and say() etc. methods here, matching standard commands? + + // CommandSource is "implicit", not an explicit argument; it's hidden passed through from the Events registration. + + // These methods intentionally do not return e.g. a CompletionStage. + + /** + * Run Minecraft commands, see https://minecraft.fandom.com/wiki/Commands. + */ + void cmd(String command); + + // TODO void wait(int seconds); + + // Following are things that are useful "context" for Scripts + + Player player(); +} diff --git a/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Script.java b/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Script.java new file mode 100644 index 00000000..cd1d4bb5 --- /dev/null +++ b/api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Script.java @@ -0,0 +1,28 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.japi; + +/** + * Script should be initialized by ch.vorburger.minecraft.storeys.japi.impl.ScriptLoader. + */ +public interface Script { + + void init(Events e); + +} diff --git a/api/src/main/java/ch/vorburger/minecraft/storeys/api/Minecraft.java b/api/src/main/java/ch/vorburger/minecraft/storeys/api/Minecraft.java index 426b0d98..10cfdad1 100644 --- a/api/src/main/java/ch/vorburger/minecraft/storeys/api/Minecraft.java +++ b/api/src/main/java/ch/vorburger/minecraft/storeys/api/Minecraft.java @@ -27,6 +27,7 @@ @VertxGen @ProxyGen +// see also the similar but not Vert.x-based interfaces Events & Minecraft in the api-java/ module public interface Minecraft { void showTitle(String playerUUID, String message, Handler> handler); @@ -36,6 +37,7 @@ public interface Minecraft { /** * Runs a Minecraft command. * This does not register a new command, but runs one. + * * @param command one single command without the starting slash */ void runCommand(String playerUUID, String command, Handler> handler); diff --git a/docs/architecture.md b/docs/architecture.md index 81195072..538802f4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,11 +2,14 @@ ## Java Code -* `api/` is (should be) Storeys' API, for both JS with Vert.x client + Java +* `api/` is Storeys' Remote JavaScript with Vert.x API client +* `api-jvm/` is Storeys' Local JVM API for in-process Local JavaScript scripts (no Vert.x) & Java. It should only depend on the Sponge API, nothing else. +* `api-jvm-impl/` implements the `api-jvm/` API. It should only depend on `api-jvm` (and on `storeys`, later at the _grand inversion_ when we flip it upside down) +* `example/` is a simple sample plugin written in Java. It should only depend on `api-jvm`, nothing else. * `engine/` will be an interactive dialogs runtime, useable both in Minecraft and standalone -* `storeys/` is the core module including `/narrate` and `.story` DSL +* `storeys/` is the original core project and includes the `/narrate` command and `.story` DSL with `/story` _(TODO factor out Story DSL into `dsl/` module)_ * `test-utils/` is a minor technical utility for classpath duplication detection -* `web/` contains the Vert.x server back-end for both the Scratch integration and JS +* `web/` implements `api/` with a Vert.x server back-end for (a) Remote Scratch, (b) Scratch Server, (c) hand-written Remote JS (running both b+c within Node.JS) ## JavaScript Code diff --git a/example/build.gradle b/example/build.gradle new file mode 100644 index 00000000..c53d8e6b --- /dev/null +++ b/example/build.gradle @@ -0,0 +1,4 @@ + +dependencies { + implementation project(':api-jvm') +} diff --git a/example/src/main/java/ch/vorburger/minecraft/storeys/example/ExampleScript.java b/example/src/main/java/ch/vorburger/minecraft/storeys/example/ExampleScript.java new file mode 100644 index 00000000..c4107299 --- /dev/null +++ b/example/src/main/java/ch/vorburger/minecraft/storeys/example/ExampleScript.java @@ -0,0 +1,50 @@ +/* + * ch.vorburger.minecraft.storeys + * + * Copyright (C) 2016 - 2018 Michael Vorburger.ch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package ch.vorburger.minecraft.storeys.example; + +import ch.vorburger.minecraft.storeys.japi.Events; +import ch.vorburger.minecraft.storeys.japi.Script; +import org.spongepowered.api.item.ItemTypes; + +/** + * Example Script. + * Simpler than full-blown Sponge API plugin. + * While this is written in Java, the idea is that this should look almost identical in TypeScript. + */ +public class ExampleScript implements Script { + + @Override public void init(Events e) { + e.whenCommand("example", m -> { + // TODO test that this is correctly asynchronously chained - each line wait for execution... + m.cmd("/title hello, world"); + m.cmd("/tp 232 63 216 -180 25"); + m.cmd("/narrate Piggy Hello! I'm Piggy."); + m.cmd("/say Message in the Chat"); + m.cmd("/title The End"); + }); + e.whenCommand("another", m -> { + m.cmd("/title Namaste. Curry pour tous!"); + if (!m.player().getInventory().contains(ItemTypes.FISHING_ROD)) { + m.cmd("/say There may be a fishing rod hidden somewhere… look for it, and then catch a fish!"); + } else { + m.cmd("/say Go fishing with the rod in your inventory.."); + } + }); + } +} diff --git a/settings.gradle b/settings.gradle index 7ba0b080..a351cc51 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ -include 'test-utils', 'engine', 'api', 'storeys', 'web', 'scratch3' +include 'test-utils', 'engine', 'api-jvm', 'api-jvm-impl', 'example', 'storeys', 'api', 'web', 'scratch3' rootProject.name = 'minecraft-storeys-maker' diff --git a/storeys/build.gradle b/storeys/build.gradle index b29554c0..98323f39 100644 --- a/storeys/build.gradle +++ b/storeys/build.gradle @@ -3,6 +3,9 @@ plugins { } dependencies { + implementation project(':api-jvm-impl') + implementation project(':example') + implementation 'org.osgi:org.osgi.core:6.0.0' implementation 'ch.vorburger.minecraft.osgi:api:1.0.0' implementation('com.spotify:futures-extra:4.0.0') { diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/commands/StoryCommand.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/commands/StoryCommand.java index f7826ce7..52645b9e 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/commands/StoryCommand.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/commands/StoryCommand.java @@ -24,20 +24,18 @@ import ch.vorburger.minecraft.storeys.ReadingSpeed; import ch.vorburger.minecraft.storeys.StoryPlayer; +import ch.vorburger.minecraft.storeys.japi.util.CommandExceptions; import ch.vorburger.minecraft.storeys.model.ActionContext; import ch.vorburger.minecraft.storeys.model.Story; import ch.vorburger.minecraft.storeys.model.parser.FileStoryRepository; import ch.vorburger.minecraft.storeys.model.parser.StoryParser; import ch.vorburger.minecraft.storeys.model.parser.StoryRepository; import ch.vorburger.minecraft.storeys.util.Command; -import ch.vorburger.minecraft.utils.CommandExceptions; import com.google.common.collect.ImmutableList; import java.io.File; import java.nio.file.Path; import java.util.List; - import javax.inject.Inject; - import org.spongepowered.api.command.CommandCallable; import org.spongepowered.api.command.CommandException; import org.spongepowered.api.command.CommandResult; diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/Callback.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/Callback.java index f2add33f..722b487a 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/Callback.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/Callback.java @@ -23,6 +23,8 @@ @FunctionalInterface public interface Callback { + // TODO replace (eventually) with ch.vorburger.minecraft.storeys.japi.Callback ? + void call(Player invoker) throws Exception; } diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/ConditionService.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/ConditionService.java index 87a4ddaa..09fa19fa 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/ConditionService.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/ConditionService.java @@ -21,6 +21,7 @@ import static java.util.Objects.requireNonNull; import ch.vorburger.minecraft.osgi.api.PluginInstance; +import ch.vorburger.minecraft.storeys.japi.impl.Unregisterable; import com.google.common.annotations.VisibleForTesting; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/EventService.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/EventService.java index db3ee7f6..ae1417dd 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/EventService.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/EventService.java @@ -25,6 +25,7 @@ import java.util.function.Consumer; import ch.vorburger.minecraft.osgi.api.PluginInstance; +import ch.vorburger.minecraft.storeys.japi.impl.Unregisterable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongepowered.api.Sponge; diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/ScriptCommand.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/ScriptCommand.java index 481080ac..23dca28a 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/ScriptCommand.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/events/ScriptCommand.java @@ -18,13 +18,13 @@ */ package ch.vorburger.minecraft.storeys.events; -import java.util.List; - import ch.vorburger.minecraft.osgi.api.PluginInstance; +import ch.vorburger.minecraft.storeys.japi.impl.Unregisterable; +import ch.vorburger.minecraft.storeys.japi.util.CommandExceptions; import ch.vorburger.minecraft.storeys.util.Command; import ch.vorburger.minecraft.storeys.util.Commands; -import ch.vorburger.minecraft.utils.CommandExceptions; import com.google.common.collect.ImmutableList; +import java.util.List; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.CommandCallable; import org.spongepowered.api.command.CommandException; diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/ActionException.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/ActionException.java index 3bd99a8c..0caa95fe 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/ActionException.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/ActionException.java @@ -18,7 +18,7 @@ */ package ch.vorburger.minecraft.storeys.model; -import ch.vorburger.minecraft.utils.Texts; +import ch.vorburger.minecraft.storeys.japi.util.Texts; import org.spongepowered.api.text.Text; import org.spongepowered.api.util.TextMessageException; diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/CommandAction.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/CommandAction.java index 67b52f54..c524f939 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/CommandAction.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/CommandAction.java @@ -31,6 +31,8 @@ public class CommandAction extends MainThreadAction { + // TODO This should eventually just delegate to ch.vorburger.minecraft.storeys.japi.impl.MinecraftJvmImpl.cmd(String) + private static final Logger LOG = LoggerFactory.getLogger(CommandAction.class); // TODO It would be good if there was a way to know when a /command was "done" .. diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/DynamicAction.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/DynamicAction.java index a002f6c1..e72299f4 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/DynamicAction.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/model/DynamicAction.java @@ -19,6 +19,8 @@ package ch.vorburger.minecraft.storeys.model; import ch.vorburger.minecraft.storeys.StoryPlayer; +import ch.vorburger.minecraft.storeys.japi.Events; +import ch.vorburger.minecraft.storeys.japi.Minecraft; import ch.vorburger.minecraft.storeys.model.parser.StoryParser; import ch.vorburger.minecraft.storeys.model.parser.SyntaxErrorException; import java.util.concurrent.CompletableFuture; @@ -34,12 +36,14 @@ public class DynamicAction implements Action { private final StoryParser storyParser; private final StoryPlayer storyPlayer; + private final Events api; private String script = ""; - @Inject public DynamicAction(StoryParser storyParser, StoryPlayer storyPlayer) { + @Inject public DynamicAction(StoryParser storyParser, StoryPlayer storyPlayer, Events api) { this.storyParser = storyParser; this.storyPlayer = storyPlayer; + this.api = api; } @Override public void setParameter(String param) { @@ -52,6 +56,7 @@ public class DynamicAction implements Action { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); engine.put("player", context.getCommandSource()); + engine.put("e", api); try { String storyText = (String) engine.eval(PREFIX + script + POSTFIX); diff --git a/storeys/src/main/java/ch/vorburger/minecraft/storeys/plugin/AbstractStoreysPlugin.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/plugin/AbstractStoreysPlugin.java index bf5c5f02..483f9187 100644 --- a/storeys/src/main/java/ch/vorburger/minecraft/storeys/plugin/AbstractStoreysPlugin.java +++ b/storeys/src/main/java/ch/vorburger/minecraft/storeys/plugin/AbstractStoreysPlugin.java @@ -18,21 +18,23 @@ */ package ch.vorburger.minecraft.storeys.plugin; -import java.nio.file.Path; - -import javax.inject.Inject; - import ch.vorburger.minecraft.osgi.api.AbstractPlugin; import ch.vorburger.minecraft.osgi.api.PluginInstance; import ch.vorburger.minecraft.storeys.commands.NarrateCommand; import ch.vorburger.minecraft.storeys.commands.StoryCommand; +import ch.vorburger.minecraft.storeys.example.ExampleScript; import ch.vorburger.minecraft.storeys.guard.GuardGameModeJoinListener; +import ch.vorburger.minecraft.storeys.japi.impl.Scripts; +import ch.vorburger.minecraft.storeys.japi.impl.Unregisterable; import ch.vorburger.minecraft.storeys.util.Commands; import com.google.inject.Injector; +import java.nio.file.Path; +import javax.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongepowered.api.command.CommandManager; import org.spongepowered.api.command.CommandMapping; +import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.event.EventManager; import org.spongepowered.api.event.Listener; @@ -46,25 +48,22 @@ public abstract class AbstractStoreysPlugin extends AbstractPlugin { private static final Logger LOG = LoggerFactory.getLogger(AbstractStoreysPlugin.class); @Inject - @ConfigDir(sharedRoot = false) - private Path configDir; + @ConfigDir(sharedRoot = false) private Path configDir; - @Inject - protected Injector pluginInjector; + @Inject protected Injector pluginInjector; - @Inject - private EventManager eventManager; + @Inject private EventManager eventManager; - @Inject - private CommandManager commandManager; + @Inject private CommandManager commandManager; private CommandMapping narrateCommandMapping; private CommandMapping storyCommandMapping; - @Listener - public void onGameStartingServer(GameStartingServerEvent event) throws Exception { + private Unregisterable example; + + @Listener public void onGameStartingServer(GameStartingServerEvent event) throws Exception { LOG.info("See https://github.com/vorburger/minecraft-storeys-maker for how to use /story and /narrate commands"); - start(this, this.configDir); + start(this, configDir); } protected void start(PluginInstance plugin, Path configDir) throws Exception { @@ -76,14 +75,19 @@ protected void start(PluginInstance plugin, Path configDir) throws Exception { }); storyCommandMapping = Commands.register(plugin, pluginInjector.getInstance(StoryCommand.class)); narrateCommandMapping = Commands.register(plugin, pluginInjector.getInstance(NarrateCommand.class)); + + // TODO CommandSource should not be required here! :( + CommandSource commandSource = null; + example = Scripts.init(plugin, commandSource, new ExampleScript()); } - @Listener - public void onGameStoppingServer(GameStoppingServerEvent event) throws Exception { + @Listener public void onGameStoppingServer(GameStoppingServerEvent event) throws Exception { stop(); } protected void stop() throws Exception { + example.unregister(); + if (narrateCommandMapping != null) { commandManager.removeMapping(narrateCommandMapping); } diff --git a/storeys/src/test/java/ch/vorburger/minecraft/storeys/model/DynamicActionTest.java b/storeys/src/test/java/ch/vorburger/minecraft/storeys/model/DynamicActionTest.java index e83c8279..bfd23b0f 100644 --- a/storeys/src/test/java/ch/vorburger/minecraft/storeys/model/DynamicActionTest.java +++ b/storeys/src/test/java/ch/vorburger/minecraft/storeys/model/DynamicActionTest.java @@ -49,7 +49,7 @@ public void execute() throws IOException { // given StoryParser storyParser = StoryParserTest.getStoryParser(); String storyText = new ClassLoaderResourceStoryRepository().getStoryScript("dynamic-test"); - DynamicAction dynamicAction = new DynamicAction(storyParser, new StoryPlayer()); + DynamicAction dynamicAction = new DynamicAction(storyParser, new StoryPlayer(), null); dynamicAction.setParameter(storyText); Player commandSource = mock(Player.class); CarriedInventory inventory = mock(CarriedInventory.class); diff --git a/storeys/src/test/java/ch/vorburger/minecraft/storeys/model/parser/StoryParserTest.java b/storeys/src/test/java/ch/vorburger/minecraft/storeys/model/parser/StoryParserTest.java index 003a9ab1..22f4a5a0 100644 --- a/storeys/src/test/java/ch/vorburger/minecraft/storeys/model/parser/StoryParserTest.java +++ b/storeys/src/test/java/ch/vorburger/minecraft/storeys/model/parser/StoryParserTest.java @@ -71,7 +71,7 @@ public static StoryParser getStoryParser() { ActionWaitHelper actionWaitHelper = new ActionWaitHelper(pluginInstance); CommandMapping commandMapping = new CommandMapping(() -> new CommandAction(pluginInstance, scheduler), () -> new NarrateAction(new Narrator(pluginInstance)), () -> new TitleAction(actionWaitHelper), - () -> new AwaitAction(actionWaitHelper), () -> new DynamicAction(null, null), LocationAction::new, + () -> new AwaitAction(actionWaitHelper), () -> new DynamicAction(null, null, null), LocationAction::new, () -> new MessageAction(actionWaitHelper)); return new StoryParser(commandMapping); diff --git a/web/build.gradle b/web/build.gradle index 283c499d..c9f60ec0 100644 --- a/web/build.gradle +++ b/web/build.gradle @@ -5,6 +5,7 @@ plugins { } dependencies { + implementation project(':api-jvm-impl') implementation project(':api') implementation(project(':storeys')) { // because this is already "shadowed" into the :storeys JAR diff --git a/web/src/main/java/ch/vorburger/minecraft/storeys/api/impl/TokenCommand.java b/web/src/main/java/ch/vorburger/minecraft/storeys/api/impl/TokenCommand.java index 66ae30d7..4d170d29 100644 --- a/web/src/main/java/ch/vorburger/minecraft/storeys/api/impl/TokenCommand.java +++ b/web/src/main/java/ch/vorburger/minecraft/storeys/api/impl/TokenCommand.java @@ -18,9 +18,9 @@ */ package ch.vorburger.minecraft.storeys.api.impl; +import ch.vorburger.minecraft.storeys.japi.util.CommandExceptions; import ch.vorburger.minecraft.storeys.simple.TokenProvider; import ch.vorburger.minecraft.storeys.util.Command; -import ch.vorburger.minecraft.utils.CommandExceptions; import com.google.common.collect.ImmutableList; import java.util.List; import org.spongepowered.api.command.CommandCallable; diff --git a/web/src/main/java/ch/vorburger/minecraft/storeys/web/ActionsConsumer.java b/web/src/main/java/ch/vorburger/minecraft/storeys/web/ActionsConsumer.java index a7958fef..65580dd9 100644 --- a/web/src/main/java/ch/vorburger/minecraft/storeys/web/ActionsConsumer.java +++ b/web/src/main/java/ch/vorburger/minecraft/storeys/web/ActionsConsumer.java @@ -28,7 +28,7 @@ import ch.vorburger.minecraft.storeys.events.ConditionService; import ch.vorburger.minecraft.storeys.events.EventService; import ch.vorburger.minecraft.storeys.events.ScriptCommand; -import ch.vorburger.minecraft.storeys.events.Unregisterable; +import ch.vorburger.minecraft.storeys.japi.impl.Unregisterable; import ch.vorburger.minecraft.storeys.simple.impl.NotLoggedInException; import com.google.common.base.Splitter; import io.vertx.core.Handler; diff --git a/web/src/main/java/ch/vorburger/minecraft/storeys/web/LocationToolListener.java b/web/src/main/java/ch/vorburger/minecraft/storeys/web/LocationToolListener.java index eb654f63..846e8921 100644 --- a/web/src/main/java/ch/vorburger/minecraft/storeys/web/LocationToolListener.java +++ b/web/src/main/java/ch/vorburger/minecraft/storeys/web/LocationToolListener.java @@ -22,7 +22,7 @@ import ch.vorburger.minecraft.storeys.events.Condition; import ch.vorburger.minecraft.storeys.events.ConditionService; import ch.vorburger.minecraft.storeys.events.LocatableInBoxCondition; -import ch.vorburger.minecraft.storeys.events.Unregisterable; +import ch.vorburger.minecraft.storeys.japi.impl.Unregisterable; import ch.vorburger.minecraft.storeys.model.LocationToolAction; import ch.vorburger.minecraft.storeys.web.location.LocationHitBox; import ch.vorburger.minecraft.storeys.web.location.LocationPairSerializer; diff --git a/web/src/main/java/ch/vorburger/minecraft/storeys/web/LoginCommand.java b/web/src/main/java/ch/vorburger/minecraft/storeys/web/LoginCommand.java index 89f439c7..aa182e25 100644 --- a/web/src/main/java/ch/vorburger/minecraft/storeys/web/LoginCommand.java +++ b/web/src/main/java/ch/vorburger/minecraft/storeys/web/LoginCommand.java @@ -18,9 +18,9 @@ */ package ch.vorburger.minecraft.storeys.web; +import ch.vorburger.minecraft.storeys.japi.util.CommandExceptions; import ch.vorburger.minecraft.storeys.simple.TokenProvider; import ch.vorburger.minecraft.storeys.util.Command; -import ch.vorburger.minecraft.utils.CommandExceptions; import com.google.common.collect.ImmutableList; import java.io.UnsupportedEncodingException; import java.net.URL; @@ -40,7 +40,7 @@ import org.spongepowered.api.text.format.TextColors; /** - * Minecraft console command to login to ScratchX. + * Minecraft console command to login to Scratch. */ public class LoginCommand implements Command { diff --git a/web/src/main/java/ch/vorburger/minecraft/storeys/web/StoreysWebPlugin.java b/web/src/main/java/ch/vorburger/minecraft/storeys/web/StoreysWebPlugin.java index c35286b3..904cd23f 100644 --- a/web/src/main/java/ch/vorburger/minecraft/storeys/web/StoreysWebPlugin.java +++ b/web/src/main/java/ch/vorburger/minecraft/storeys/web/StoreysWebPlugin.java @@ -20,7 +20,6 @@ import ch.vorburger.minecraft.osgi.api.Listeners; import ch.vorburger.minecraft.osgi.api.PluginInstance; -import ch.vorburger.minecraft.storeys.api.Minecraft; import ch.vorburger.minecraft.storeys.api.impl.MinecraftImpl; import ch.vorburger.minecraft.storeys.api.impl.TokenCommand; import ch.vorburger.minecraft.storeys.plugin.AbstractStoreysPlugin; @@ -74,7 +73,7 @@ public void start(PluginInstance plugin, Path configDir) throws Exception { Injector injector = pluginInjector.createChildInjector(binder -> { binder.bind(TokenProvider.class).to(TokenProviderImpl.class); - binder.bind(Minecraft.class).to(MinecraftImpl.class); + binder.bind(ch.vorburger.minecraft.storeys.api.Minecraft.class).to(MinecraftImpl.class); binder.bind(EventBusSender.class).to(MinecraftVerticle.class); binder.bind(new TypeLiteral>>(){}).to(ActionsConsumer.class); // TODO read from some configuration