From f21afdc5923924ec50dd3cc3a90102fbcb4895f5 Mon Sep 17 00:00:00 2001 From: Michael Vorburger Date: Mon, 4 Apr 2022 02:43:42 +0200 Subject: [PATCH] Initial version of new JVM API (incl. Unregistrable Script) --- api-jvm-impl/build.gradle | 4 + .../storeys/japi/impl/EventsImpl.java | 71 +++++++++++++++++ .../storeys/japi/impl/MinecraftJvmImpl.java | 57 +++++++++++++ .../minecraft/storeys/japi/impl/Scripts.java | 36 +++++++++ .../storeys/japi/impl}/Unregisterable.java | 2 +- .../storeys/japi/util/CommandExceptions.java | 79 +++++++++++++++++++ .../minecraft/storeys/japi/util/Texts.java | 46 +++++++++++ api-jvm/build.gradle | 5 ++ .../minecraft/storeys/japi/Callback.java | 25 ++++++ .../minecraft/storeys/japi/Events.java | 28 +++++++ .../minecraft/storeys/japi/Minecraft.java | 45 +++++++++++ .../minecraft/storeys/japi/Script.java | 28 +++++++ .../minecraft/storeys/api/Minecraft.java | 3 +- docs/architecture.md | 9 ++- example/build.gradle | 4 + .../storeys/example/ExampleScript.java | 50 ++++++++++++ settings.gradle | 2 +- storeys/build.gradle | 3 + .../storeys/commands/StoryCommand.java | 2 +- .../minecraft/storeys/events/Callback.java | 2 + .../storeys/events/ConditionService.java | 1 + .../storeys/events/EventService.java | 1 + .../storeys/events/ScriptCommand.java | 3 +- .../storeys/model/ActionException.java | 2 +- .../storeys/model/CommandAction.java | 2 + .../storeys/plugin/AbstractStoreysPlugin.java | 13 ++- storeys/src/main/resources/hello.story | 2 +- .../storeys/model/parser/StoryParserTest.java | 2 - web/build.gradle | 5 +- .../storeys/api/impl/TokenCommand.java | 2 +- .../storeys/web/ActionsConsumer.java | 2 +- .../storeys/web/LocationToolListener.java | 2 +- .../minecraft/storeys/web/LoginCommand.java | 4 +- .../storeys/web/StoreysWebPlugin.java | 5 +- 34 files changed, 524 insertions(+), 23 deletions(-) create mode 100644 api-jvm-impl/build.gradle create mode 100644 api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/EventsImpl.java create mode 100644 api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/MinecraftJvmImpl.java create mode 100644 api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/Scripts.java rename {storeys/src/main/java/ch/vorburger/minecraft/storeys/events => api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl}/Unregisterable.java (94%) create mode 100644 api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/util/CommandExceptions.java create mode 100644 api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/util/Texts.java create mode 100644 api-jvm/build.gradle create mode 100644 api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Callback.java create mode 100644 api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Events.java create mode 100644 api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Minecraft.java create mode 100644 api-jvm/src/main/java/ch/vorburger/minecraft/storeys/japi/Script.java create mode 100644 example/build.gradle create mode 100644 example/src/main/java/ch/vorburger/minecraft/storeys/example/ExampleScript.java 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..72023785 --- /dev/null +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/EventsImpl.java @@ -0,0 +1,71 @@ +/* + * 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 java.util.Collection; +import java.util.Optional; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandMapping; +import org.spongepowered.api.command.CommandResult; +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 static final Logger LOG = LoggerFactory.getLogger(EventsImpl.class); + + private final Object plugin; + private final Collection unregistrables = new ConcurrentLinkedQueue<>(); + + EventsImpl(Object plugin) { + this.plugin = plugin; + } + + @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.isPresent()) { + LOG.error("Could not register new command, because it's already present: /" + 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..a4517dd7 --- /dev/null +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/MinecraftJvmImpl.java @@ -0,0 +1,57 @@ +/* + * 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..358e4299 --- /dev/null +++ b/api-jvm-impl/src/main/java/ch/vorburger/minecraft/storeys/japi/impl/Scripts.java @@ -0,0 +1,36 @@ +/* + * 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; + +public final class Scripts { + + // TODO Make Scripts @Inject-able... + + @CheckReturnValue public static Unregisterable init(Object plugin, Script script) { + EventsImpl e = new EventsImpl(plugin); + 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 0963521d..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,7 +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..267dbdbe --- /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 @s 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 @s The End"); + }); + e.whenCommand("another", m -> { + m.cmd("/title @s 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 ddb96953..2be01f09 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ -include 'test-utils', 'engine', 'api', 'storeys', 'web', 'scratch3', 'test-mineflayer' +include 'test-utils', 'engine', 'api-jvm', 'api-jvm-impl', 'example', 'storeys', 'api', 'web', 'scratch3', 'test-mineflayer' rootProject.name = 'minecraft-storeys-maker' diff --git a/storeys/build.gradle b/storeys/build.gradle index 714739ad..9a588d22 100644 --- a/storeys/build.gradle +++ b/storeys/build.gradle @@ -1,6 +1,9 @@ dependencies { + api project(':api-jvm-impl') api 'ch.vorburger.minecraft.osgi:api:1.0.0' + + implementation project(':example') implementation('com.spotify:futures-extra:4.0.0') { exclude group: 'com.google.guava' } 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 7bb9f2c0..9f112b7d 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,13 +24,13 @@ 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; 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 db9a16d7..35387081 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 f3b34e64..fd22c5d1 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 @@ -19,6 +19,7 @@ package ch.vorburger.minecraft.storeys.events; import ch.vorburger.minecraft.osgi.api.PluginInstance; +import ch.vorburger.minecraft.storeys.japi.impl.Unregisterable; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; 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 b5febdc7..c74f49e9 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 @@ -19,9 +19,10 @@ package ch.vorburger.minecraft.storeys.events; 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; 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 ebb6d475..c3ea4a10 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 @@ -30,6 +30,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/plugin/AbstractStoreysPlugin.java b/storeys/src/main/java/ch/vorburger/minecraft/storeys/plugin/AbstractStoreysPlugin.java index 34c36473..277c2e79 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 @@ -22,7 +22,10 @@ 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; @@ -55,9 +58,11 @@ public abstract class AbstractStoreysPlugin extends AbstractPlugin { private CommandMapping narrateCommandMapping; private CommandMapping storyCommandMapping; + 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); + LOG.info("See https://github.com/OASIS-learn-study/minecraft-storeys-maker for how to use /story and /narrate commands"); + start(this, configDir); } protected void start(PluginInstance plugin, Path configDir) throws Exception { @@ -69,6 +74,8 @@ 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)); + + example = Scripts.init(plugin, new ExampleScript()); } @Listener public void onGameStoppingServer(GameStoppingServerEvent event) throws Exception { @@ -76,6 +83,8 @@ protected void start(PluginInstance plugin, Path configDir) throws Exception { } protected void stop() throws Exception { + example.unregister(); + if (narrateCommandMapping != null) { commandManager.removeMapping(narrateCommandMapping); } diff --git a/storeys/src/main/resources/hello.story b/storeys/src/main/resources/hello.story index e99374fa..832899f8 100644 --- a/storeys/src/main/resources/hello.story +++ b/storeys/src/main/resources/hello.story @@ -19,6 +19,6 @@ But first of all, I'd like to introduce you to a good friend of mine - let me ta @Chestnut Howdy. I'm Chestnut. -You can find out more on https://github.com/vorburger/minecraft-storeys-maker +You can find out more on https://github.com/OASIS-learn-study/minecraft-storeys-maker We hope you like this mod. Now go and make your own STORY! 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 5a0a57af..5c5e1bf7 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 @@ -56,8 +56,6 @@ public class StoryParserTest { - private final Action[] emptyList = new Action[] { new NopAction(), new NopAction(), new NopAction() }; - @BeforeClass public static void initialize() throws Exception { TestPlainTextSerializer.inject(); } diff --git a/web/build.gradle b/web/build.gradle index ee838e2c..d1ed216a 100644 --- a/web/build.gradle +++ b/web/build.gradle @@ -40,10 +40,13 @@ shadowJar { // https://imperceptiblethoughts.com/shadow/configuration/filtering/ // NB "not being able to filter entire transitive dependency graphs" // so instead of using exclude dependency we just do explicit include: + include(project(':api')) include(project(':scratch3')) - include(project(':storeys')) + include(project(':api-jvm')) + include(project(':api-jvm-impl')) + include(project(':example')) include(dependency('ch.vorburger.minecraft.osgi:api')) include(dependency('com.spotify:futures-extra')) 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 c4b372bf..975064b2 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 c87cc5ba..41ae1369 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 @@ -23,7 +23,7 @@ import ch.vorburger.minecraft.osgi.api.PluginInstance; 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 a3f3e515..15843661 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 6baf86aa..82284376 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 53deedf7..69fa26c4 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; @@ -45,7 +44,7 @@ import org.spongepowered.api.config.DefaultConfig; import org.spongepowered.api.plugin.Plugin; -@Plugin(id = "storeys-web", name = "Vorburger.ch's Storeys with Web API", version = "1.0", description = "Makes entities narrate story lines so you can make your own movie in Minecraft", url = "https://github.com/vorburger/minecraft-storeys-maker", authors = "Michael Vorburger.ch") +@Plugin(id = "storeys-web", name = "Vorburger.ch's Storeys with Web API", version = "1.0", description = "Makes entities narrate story lines so you can make your own movie in Minecraft", url = "https://github.com/OASIS-learn-study/minecraft-storeys-maker", authors = "Michael Vorburger.ch") public class StoreysWebPlugin extends AbstractStoreysPlugin implements Listeners { // do not extend StoreysPlugin, because we exclude that class in shadowJar @@ -65,7 +64,7 @@ public class StoreysWebPlugin extends AbstractStoreysPlugin implements Listeners 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);