diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bfa550 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +*.iml +target/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..13ffea4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: java +jdk: +- oraclejdk8 + +deploy: + provider: releases + api_key: + secure: c+ryMtzs7ckqTw7GgX35WkEKjYWlTMxIZMTjdGwJ/s4Oljm2tQWgSzri8cwXO/ygebGRna6tw2eY6qdjOeIFkjbnUFYWP0FhRaHR3yZIGe6YfKOXDBF21QyVja1/3fOodXRiOdVHuaWaI/9DxWbXjVmtaHIjQdcxb5JTwuUPZE4H22RP6YTY8xX5dVMetELVSP8vNu/UASENpv/itQzGtcKwRon2txoeo66eNGdjO4YHYzvCOilH5VG86L6rGbSzNaBqsjZohPoZOcYT77r6WpiwsD2w0f38aU/n7+dWrTqe5/nv3XgL9FDu2aIfPzSiku5Th0Kh3aiSJmApT6HzV++1uDoyDoVirsvM5oO5vSlOIRjtmyNjwUQQRTuz5jjehAvVxx9ciN2jaVPl6hDV0wAvDgKToFChSMO6myaYRAiI4rZ8f7i1bELoD4ErM9bscwiEo0k00hK3SZvBWgds5VObifMXlcdw46fE4jcbL/9a7jTiBREnqmeOERKuI9RqHhm2oGI2DVE30sgmoyyhwP6MKVToKSYimUmOCw+R8tlwmPs4zkEenDN5FkIBX8JbXzIMnbbygBtVUELnkGgwVtq3jIj4RRYwZDmkm7ZSmdhzac+unw5Kxdt3a5fxQhMXV36j2v8LIq7YnXwzzwt/smh22HoMdUov9NsXLzJ7k+A= + file: target/PlayerPositions-*.jar + file_glob: true + on: + repo: the-obsidian/PlayerPositions + tags: true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9bca104 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2015 Jacob Gillespie + +Permission is hereby granted, free of charge, to any person ob- +taining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restric- +tion, including without limitation the rights to use, copy, modi- +fy, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is fur- +nished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +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 NONIN- +FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ff2db2 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# PlayerPositions [![Build Status](https://travis-ci.org/the-obsidian/PlayerPositions.svg?branch=master)](https://travis-ci.org/the-obsidian/PlayerPositions) + +Synchronizes Minecraft groups with Discourse groups + +## Dependencies + +* Vault + +## Installation + +1. Download the [latest release](https://github.com/the-obsidian/PlayerPositions/releases) from GitHub +1. Add it to your `plugins` folder +1. Either run Bukkit/Spigot once to generate `PlayerPositions/config.yml` or create it using the guide below. +1. All done! + +## Configuration + +PlayerPositions has several options that can be configured in the `config.yml` file: + +```yaml +# The URL of your Discourse installation (without the trailing slash) +discourse-url: http://forum.example.com + +# A mapping between Discourse groups (by integer ID) and Minecraft groups (by name) +groups: + + # Add user to groupname when they are in Discourse group 4 + - discourse: 4 + minecraft: groupname + + # Remove user from groupname when they are not in Discourse group 4 + - discourse: 4 + minecraft: groupname + remove: true + + # Add user to guestgroup if they do not have any Discourse groups + - discourse: 0 + minecraft: guestgroup +``` + +`discourse` keys are the IDs of your chosen Discourse groups. A negative number means the absence of the group, so `discourse: -20` would target users who were not a member of group `20`. `0` is a special group meaning users who are not a member of any Discourse groups. + +## Features + +* Synchronizes Minecraft groups with Discourse groups on player join + +## Upcoming Features + +* Add more hooks for synchronization, including recurring tasks and possibly webhooks diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cbcd820 --- /dev/null +++ b/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + gg.obsidian + PlayerPositions + 1.0.0 + Writes player positions to a file for the dynamic map + https://github.com/the-obsidian/PlayerPositions + + + scm:git:https://github.com/the-obsidian/PlayerPositions.git + scm:git:https://github.com/the-obsidian/PlayerPositions.git + https://github.com/the-obsidian/PlayerPositions + + + jar + + + UTF-8 + 1.0.0-beta-1103 + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/groups/public/ + + + + + + org.spigotmc + spigot-api + 1.8.8-R0.1-SNAPSHOT + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + + + ${project.basedir}/src/main/kotlin + clean install + + + + . + true + ${basedir}/src/main/resources + + *.yml + + + + + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + true + false + + + org.spigotmc:* + + + + + + + + + diff --git a/src/main/kotlin/gg/obsidian/playerpositions/Configuration.kt b/src/main/kotlin/gg/obsidian/playerpositions/Configuration.kt new file mode 100644 index 0000000..2aeeff9 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/playerpositions/Configuration.kt @@ -0,0 +1,33 @@ +package gg.obsidian.playerpositions + +import java.text.SimpleDateFormat + +class Configuration(val plugin: Plugin) { + + var SAVE_INTERVAL: Long = 3000 + var OUTPUT_FILE = "world/markers.json" + var WRITE_SPAWN = false + var DATE_FORMAT = "yyyyMMdd HH:mm:ss" + + var dateFormat: SimpleDateFormat = SimpleDateFormat("yyyyMMdd HH:mm:ss") + var interval: Long = SAVE_INTERVAL / 50 + + fun load() { + plugin.reloadConfig() + + SAVE_INTERVAL = plugin.getConfig().getLong("save-interval") + OUTPUT_FILE = plugin.getConfig().getString("output-file") + WRITE_SPAWN = plugin.getConfig().getBoolean("write-spawn") + DATE_FORMAT = plugin.getConfig().getString("date-format") + + try { + dateFormat = SimpleDateFormat(DATE_FORMAT) + } catch (e: Exception) { + plugin.logger.warning("Invalid date format, defaulting to yyyyMMdd HH:mm:ss") + dateFormat = SimpleDateFormat("yyyyMMdd HH:mm:ss") + } + + // Convert to 20 ticks-per-second + interval = SAVE_INTERVAL / 50 + } +} diff --git a/src/main/kotlin/gg/obsidian/playerpositions/EventListener.kt b/src/main/kotlin/gg/obsidian/playerpositions/EventListener.kt new file mode 100644 index 0000000..42c6018 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/playerpositions/EventListener.kt @@ -0,0 +1,31 @@ +package gg.obsidian.playerpositions + +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerLoginEvent +import org.bukkit.event.player.PlayerMoveEvent +import org.bukkit.event.player.PlayerQuitEvent + +class EventListener(val plugin: Plugin): Listener { + + @EventHandler(priority = EventPriority.MONITOR) + fun onPlayerMove(event: PlayerMoveEvent) { + plugin.updatePlayer(event.player) + } + + @EventHandler(priority = EventPriority.MONITOR) + fun onPlayerLogin(event: PlayerLoginEvent) { + plugin.updatePlayer(event.player) + } + + @EventHandler(priority = EventPriority.MONITOR) + fun onPlayerTeleport(event: PlayerMoveEvent) { + plugin.updatePlayer(event.player) + } + + @EventHandler(priority = EventPriority.MONITOR) + fun onPlayerQuit(event: PlayerQuitEvent) { + plugin.removePlayer(event.player) + } +} diff --git a/src/main/kotlin/gg/obsidian/playerpositions/Plugin.kt b/src/main/kotlin/gg/obsidian/playerpositions/Plugin.kt new file mode 100644 index 0000000..8b6c722 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/playerpositions/Plugin.kt @@ -0,0 +1,87 @@ +package gg.obsidian.playerpositions + +import org.bukkit.entity.Player +import org.bukkit.event.Listener +import org.bukkit.plugin.java.JavaPlugin +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import java.io.File +import java.util.* + +class Plugin : JavaPlugin(), Listener { + + val config = Configuration(this) + val eventListener = EventListener(this) + + val lastSeen = HashMap() + var hasUpdated = true + + override fun onEnable() { + val configFile = File(dataFolder, "config.yml") + if (!configFile.exists()) { + getConfig().options().copyDefaults(true) + saveConfig() + } + + config.load() + + server.scheduler.scheduleSyncRepeatingTask(this, TimerTask(this), config.interval, config.interval) + server.pluginManager.registerEvents(eventListener, this) + } + + fun updatePlayer(player: Player) { + hasUpdated = true + synchronized(lastSeen) { + lastSeen.put(player, config.dateFormat.format(Date())) + } + } + + fun removePlayer(player: Player) { + hasUpdated = true + synchronized(lastSeen) { + lastSeen.remove(player) + } + } + + fun getJSON(): JSONArray? { + if (!hasUpdated) return null + + hasUpdated = false + + val jsonList = JSONArray() + var out: JSONObject + + if (config.WRITE_SPAWN) { + for (world in server.worlds) { + out = JSONObject() + out.put("msg", "Spawn") + out.put("id", 1) + out.put("world", world.name) + out.put("x", world.spawnLocation.x) + out.put("y", world.spawnLocation.y) + out.put("z", world.spawnLocation.z) + jsonList.add(out) + } + } + + for (player in server.onlinePlayers) { + out = JSONObject() + out.put("msg", player.name) + out.put("id", 4) + out.put("world", player.location.world.name) + out.put("x", player.location.x) + out.put("y", player.location.y) + out.put("z", player.location.z) + out.put("uuid", player.uniqueId.toString()) + + synchronized(lastSeen) { + val s = lastSeen.get(player) + if (s != null) out.put("timestamp", s) + } + + jsonList.add(out) + } + + return jsonList + } +} diff --git a/src/main/kotlin/gg/obsidian/playerpositions/TimerTask.kt b/src/main/kotlin/gg/obsidian/playerpositions/TimerTask.kt new file mode 100644 index 0000000..2a588a1 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/playerpositions/TimerTask.kt @@ -0,0 +1,21 @@ +package gg.obsidian.playerpositions + +import java.io.BufferedWriter +import java.io.FileWriter +import java.io.IOException +import java.io.PrintWriter + +class TimerTask(val plugin: Plugin): Runnable { + override fun run() { + val jsonList = plugin.getJSON() ?: return + + try { + val writer = PrintWriter(BufferedWriter(FileWriter(plugin.config.OUTPUT_FILE))) + writer.print(jsonList) + writer.close() + } catch (e: IOException) { + plugin.logger.severe("Unable to write to " + plugin.config.OUTPUT_FILE + ": " + e.message) + e.printStackTrace() + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..d21d3c2 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,4 @@ +save-interval: 3000 +output-file: world/markers.json +write-spawn: false +date-format: yyyyMMdd HH:mm:ss diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..96657eb --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,9 @@ +name: PlayerPositions +version: ${project.version} +description: ${project.description} + +author: Jacob Gillespie +website: ${project.url} + +main: gg.obsidian.playerpositions.Plugin +