diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d287c81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Created by .ignore support plugin (hsz.mobi) +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +.idea +*.iml +.project +/run diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..b905d01 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Example Plugin for PowerNukkit +This is an example plugin which can also be used as template to start your own plugin. + +As an example I created a plugin named clone-me, it creates a clone of yourself when you run `/clone` +and gives you a flower if you hit the clone and then despawn the clone. It also send some fancy messages. + +These is enough to serve as an example on how to: +- Begin a new plugin +- Create event listeners and handlers +- Create custom commands +- Format text +- Spawn NPCs +- Despawn NPCs +- Detect attacks +- Make entities invulnerable +- Create and fill a `plugin.yml` file +- Debug your plugin properly + +## Cloning and importing +1. Just do a normal `git clone https://github.com/PowerNukkit/ExamplePlugin.git` (or the URL of your own git repository) +2. Import the `pom.xml` file with your IDE, it should do the rest by itself + +## Debugging +1. Create a zip file containing only your `plugin.yml` file +2. Rename the zip file to change the extension to jar +3. Create an empty folder anywhere, that will be your server folder. + _Note: You don't need to place the PowerNukkit jar in the folder, your IDE will load it from the maven classpath._ +4. Create a folder named `plugins` inside your server folder + _Note: It is needed to bootstrap your plugin, your IDE will load your plugin classes from the classpath automatically, + so it needs to have only the `plugin.yml` file._ +5. Move the jar file that contains only the `plugin.yml` to the `plugins` folder +6. Create a new Application run configuration setting the working directory to the server folder and the main class to: `cn.nukkit.Nukkit` +![](https://i.imgur.com/NUrrZab.png) +7. Now you can run in debug mode. If you change the `plugin.yml` you will need to update the jar file that you've made. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5cdf765 --- /dev/null +++ b/pom.xml @@ -0,0 +1,23 @@ + + 4.0.0 + + ExamplePlugin + example-plugin + org.powernukkit.plugins + 0.1.0-SNAPSHOT + jar + + + + org.powernukkit + powernukkit + 1.3.1.4-PN + + + + + 1.8 + 1.8 + + diff --git a/src/main/java/org/powernukkit/plugins/example/CloneCommand.java b/src/main/java/org/powernukkit/plugins/example/CloneCommand.java new file mode 100644 index 0000000..1272601 --- /dev/null +++ b/src/main/java/org/powernukkit/plugins/example/CloneCommand.java @@ -0,0 +1,32 @@ +package org.powernukkit.plugins.example; + +import cn.nukkit.Player; +import cn.nukkit.command.Command; +import cn.nukkit.command.CommandExecutor; +import cn.nukkit.command.CommandSender; +import cn.nukkit.entity.EntityHuman; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.TextFormat; + +public class CloneCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) { + if (!(commandSender instanceof Player)) { + commandSender.sendMessage(TextFormat.DARK_RED+""+TextFormat.BOLD + "Error!"+TextFormat.RESET+TextFormat.RED+" Only players can execute this command!"); + return false; + } + Player player = (Player) commandSender; + player.saveNBT(); + EntityHuman human = new EntityHuman(player.getChunk(), new CompoundTag() + .put("Pos", player.namedTag.get("Pos").copy()) + .put("Rotation", player.namedTag.get("Rotation").copy()) + .put("Motion", player.namedTag.get("Motion").copy()) + .put("Skin", player.namedTag.get("Skin").copy()) + .putBoolean("IsCloned", true) + ); + human.setSkin(player.getSkin()); + human.spawnToAll(); + player.sendMessage(TextFormat.DARK_GREEN+""+TextFormat.BOLD+"Success!"+TextFormat.RESET+TextFormat.GREEN+" You have been cloned!"); + return true; + } +} diff --git a/src/main/java/org/powernukkit/plugins/example/CloneListener.java b/src/main/java/org/powernukkit/plugins/example/CloneListener.java new file mode 100644 index 0000000..00a897d --- /dev/null +++ b/src/main/java/org/powernukkit/plugins/example/CloneListener.java @@ -0,0 +1,45 @@ +package org.powernukkit.plugins.example; + +import cn.nukkit.Player; +import cn.nukkit.block.BlockID; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityHuman; +import cn.nukkit.event.EventHandler; +import cn.nukkit.event.Listener; +import cn.nukkit.event.entity.EntityDamageByEntityEvent; +import cn.nukkit.item.Item; +import cn.nukkit.utils.TextFormat; + +public class CloneListener implements Listener { + @EventHandler + public void onCloneDamage(EntityDamageByEntityEvent event) { + Entity entity = event.getEntity(); + + // Affect only the clones + if (!(entity instanceof EntityHuman) + || !entity.namedTag.getBoolean("IsCloned")) { + return; + } + + // Makes the clones invulnerable to non-player damage + if (!(event.getDamager() instanceof Player)) { + event.setCancelled(); + return; + } + + // If the clone is hit by a player, despawn it + Player player = (Player) event.getDamager(); + player.sendMessage(TextFormat.GOLD+""+TextFormat.BOLD+"WOW!"+TextFormat.RESET+TextFormat.YELLOW+" You found a clone!"); + entity.close(); + + // And give the player a present + Item flowerItem = Item.getBlock(BlockID.RED_FLOWER); + flowerItem.setCustomName(TextFormat.RESET+""+TextFormat.RED+"Congratulations!"); + flowerItem.setLore(TextFormat.RESET+""+TextFormat.LIGHT_PURPLE+"This is a present for your finding!"); + + // The will guarantee that the player receive the present by dropping it on the floor if the inventory is full + for (Item drop: player.getInventory().addItem(flowerItem)) { + player.getLevel().dropItem(player, drop); + } + } +} diff --git a/src/main/java/org/powernukkit/plugins/example/CloneMePlugin.java b/src/main/java/org/powernukkit/plugins/example/CloneMePlugin.java new file mode 100644 index 0000000..8a47df3 --- /dev/null +++ b/src/main/java/org/powernukkit/plugins/example/CloneMePlugin.java @@ -0,0 +1,52 @@ +package org.powernukkit.plugins.example; + +import cn.nukkit.command.Command; +import cn.nukkit.command.CommandSender; +import cn.nukkit.command.PluginCommand; +import cn.nukkit.plugin.PluginBase; + +public class CloneMePlugin extends PluginBase { + @Override + public void onEnable() { + getLogger().info("Hello world! :D"); + if (System.getProperty("os.name").startsWith("Windows")) { + getLogger().warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + getLogger().warning("!!! ATTENTION WINDOWS USER !!!"); + getLogger().warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + getLogger().warning("To connect to the server in localhost you must allow the game to access localhost:"); + getLogger().warning("1. Open PowerShell as admin"); + getLogger().warning("2. Run this command: CheckNetIsolation LoopbackExempt -a -n=\"Microsoft.MinecraftUWP_8wekyb3d8bbwe\""); + getLogger().warning("3. Restart your computer (if needed, try to restart your game first)"); + getLogger().warning("This issue occurs due to loopback restrictions on Windows 10 UWP apps"); + } + + getLogger().info("TIP: Make sure your break points are set to pause ONLY THE THREAD and NOT ALL THREADS!"); + getLogger().info("https://imgur.com/ygwen76"); + getLogger().info("If you do this, you won't get disconnected when you hit a break point"); + + getLogger().info("TIP: If you are using IntelliJ, use Ctrl+F9 (Build Project) to apply non-structural java changes without restart"); + + // TODO: Make it easier + // This make the command be executed in a separated class, you need to choose if you want + // it being executed there or in the onCommand bellow, you can't use both + // Simple commands are fine in onCommand but complex command might be more organized + // in their own classes. Also make sure you register this command in plugin.yml + ((PluginCommand) getCommand("cloneme")).setExecutor(new CloneCommand()); + + // You must register your listeners to capture events + // You can make this class implement the Listener itself and invoke registerEvents(this, this) + // But again, if the listener gets too complicated it might be better to group them in different classes + getServer().getPluginManager().registerEvents(new CloneListener(), this); + } + + @Override + public void onDisable() { + getLogger().info("Goodbye world :("); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + // You can also override this command instead of setting an executor in onEnable if you prefer + return false; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..25b9989 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,78 @@ +# Required +# The final name of your plugin, other plugins may use this name to declare dependency on your plugin +name: CloneMe + +# Required +# The name of your class file that overrides PluginBase +main: org.powernukkit.plugins.example.CloneMePlugin + +# Required +# The version of your plugin, it's recommended to follow the https://semver.org/ standard +version: "1.0.0" + +# Required +# The minimum Cloudburst Nukkit API version, it's not the PowerNukkit version! +# Setting an outdated version usually don't cause issues so it's not a thing to worry too much +api: ["1.0.0"] + +# Optional +# At which time your plugin will load. +# Valid options are: POSTWORLD, STARTUP +# Default value is POSTWORLD +load: POSTWORLD + +# Optional +# Your name or your organization name or how people identify you +author: Nukkit Project + +# Optional +# Explain in few words what this plugin does +description: Example plugin showing the API + +# Optional +# A link where the admins can find more info about your plugin +website: https://github.com/Nukkit/ExamplePlugin + +# Optional +# Every sub-item must be a block, the key is the command name, you can create as many commands as you want +# Make sure to keep the exact same alignment, it is important +commands: + # The key is how the command will be used, like /cloneme . Avoid uppercase, the client game don't like it so much and may crash. + cloneme: + # Optional + # This information will be displayed in /help + description: Example command + + # Optional + # This information will be displayed in /help and when your command executor returns false + usage: "/example" + + # Optional + # The permission required to use your command. It's a good practice to always define one even if the command can be used by everybody + # More information below + permission: cloneme.cmd.use + +# Optional +# Although you don't need to register your permissions here for them to work, it's good to allow the server owners to see all them quicker +# This section also allows to customize their behavior and provide more information about them +# You can also create group of permission +# Also, every sub-item must be a block just like the commands block above +permissions: + # The key must be the same value you use to define the permission + # It's a good practice to prefix it with your plugin name and create segmentation and then group them to make the permission management easier + cloneme.cmd.use: + # Optional + # This can be used by permission management plugins to show details about the permission key + description: "Allows the user to run the example command" + + # Optional + # If a player don't have an allow-deny definition for this key, this default value will be used + # Valid options: + # - true: the permission is granted by default to everybody + # - false: the permission is revoked by default to everybody, including admins and OP + # - op: the permission is revoked by default to everybody, but is granted to OP players + # - notop: the permission is granted by default to everybody, but is revoked to OP players + # Default value is false + default: true + +# TODO Add all possible configuration here