mirror of
https://gitlab.com/hamburghammer/playtime.git
synced 2024-05-19 01:14:37 +02:00
Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
dfcd3cd35a | |||
ad2afd85d5 | |||
e8fff7b200 | |||
0895514469 |
|
@ -32,10 +32,16 @@ unit-test:
|
|||
artifacts:
|
||||
expire_in: 3 days
|
||||
paths:
|
||||
- build/reports/tests/test/index.html
|
||||
- build/reports/tests/test/
|
||||
reports:
|
||||
junit: build/test-results/test/TEST-*.xml
|
||||
|
||||
coverage:
|
||||
stage: analyse
|
||||
script:
|
||||
- "./gradlew $GRADLE_OPTS clean jacocoTestReport"
|
||||
- "cat build/reports/jacoco/test/html/index.html"
|
||||
|
||||
lint:
|
||||
stage: analyse
|
||||
script:
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# PlayTime
|
||||
[![pipeline status](https://gitlab.com/hamburghammer/playtime/badges/dev/pipeline.svg)](https://gitlab.com/hamburghammer/playtime/-/commits/dev)
|
||||
[![coverage report](https://gitlab.com/hamburghammer/playtime/badges/dev/coverage.svg)](https://gitlab.com/hamburghammer/playtime/-/commits/dev)
|
||||
|
||||
It's a plugin for a [Spigot](https://www.spigotmc.org) Minecraft server to log the time players spend on the server and returns it to them.
|
||||
|
||||
|
@ -17,9 +19,14 @@ It's a plugin for a [Spigot](https://www.spigotmc.org) Minecraft server to log t
|
|||
|
||||
`playtime` shows the total play time on the server (Format DD:HH:MM)
|
||||
|
||||
`playtimeof <player>` shows the total play time of the player (Format DD:HH:MM)
|
||||
|
||||
`toptime` shows the top 5 player with the highest total playtime
|
||||
|
||||
## Development
|
||||
|
||||
The plugin gets build with gradle. It is important to run the shadowJar task to include all dependencies.
|
||||
|
||||
It's entirely written in Kotlin and for testing it uses [Mockk](https://mockk.io/) and [Junit5](https://junit.org/junit5/).
|
||||
|
||||
This plugin uses the [linter from Pinterest for Kotlin](https://www.kotlinresources.com/library/ktlint/)
|
||||
|
|
|
@ -3,6 +3,7 @@ plugins {
|
|||
id("org.jlleitschuh.gradle.ktlint") version "9.0.0"
|
||||
id("com.github.johnrengelman.shadow") version "5.2.0"
|
||||
id("org.jetbrains.dokka") version "0.10.0"
|
||||
jacoco
|
||||
}
|
||||
|
||||
group = "de.augustodwenger"
|
||||
|
@ -45,11 +46,23 @@ tasks {
|
|||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
events("skipped", "failed")
|
||||
// events("passed", "skipped", "failed")
|
||||
}
|
||||
extensions.configure(JacocoTaskExtension::class) {
|
||||
classDumpDir = file("$buildDir/jacoco/classpathdumps")
|
||||
}
|
||||
}
|
||||
dokka {
|
||||
outputFormat = "html"
|
||||
outputDirectory = "$buildDir/dokka"
|
||||
}
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.isEnabled = false
|
||||
csv.isEnabled = false
|
||||
html.isEnabled = true
|
||||
}
|
||||
dependsOn("test")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import commands.PlayTime
|
||||
import commands.PlayTimeOfCommand
|
||||
import commands.TestCommand
|
||||
import commands.TopTime
|
||||
import commands.UpTime
|
||||
import listener.JoinListener
|
||||
import listener.QuitListener
|
||||
|
@ -31,6 +32,7 @@ class Main : JavaPlugin() {
|
|||
this.getCommand("playtime")?.setExecutor(PlayTime())
|
||||
this.getCommand("uptime")?.setExecutor(UpTime())
|
||||
this.getCommand("playtimeof")?.setExecutor(PlayTimeOfCommand())
|
||||
this.getCommand("toptime")?.setExecutor(TopTime())
|
||||
|
||||
server.pluginManager.registerEvents(JoinListener(), this)
|
||||
server.pluginManager.registerEvents(QuitListener(), this)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import java.time.Duration
|
||||
import java.util.UUID
|
||||
import models.Player
|
||||
|
||||
interface PlayerTime {
|
||||
/**
|
||||
|
@ -28,6 +29,19 @@ interface PlayerTime {
|
|||
*/
|
||||
fun timePlayed(uuid: UUID): Duration
|
||||
|
||||
/**
|
||||
* Search for the players with the highest playtime
|
||||
*
|
||||
* @return List<Player>
|
||||
*/
|
||||
fun getTopPlayers(): List<Player>
|
||||
|
||||
/**
|
||||
* Formats the duration to a simple String
|
||||
*
|
||||
* @param duration Duration
|
||||
* @return String (DD:HH:MM)
|
||||
*/
|
||||
fun timeToString(duration: Duration): String {
|
||||
val days: String =
|
||||
if (duration.toDaysPart() < 10) """0${duration.toDaysPart()}""" else """${duration.toDaysPart()}"""
|
||||
|
|
|
@ -13,7 +13,7 @@ import models.Player
|
|||
class SimplePlayerTime(private val db: PlayerTimeDB) : PlayerTime {
|
||||
|
||||
/**
|
||||
* Adds the player to the playerMap.
|
||||
* Adds the player to the [db].
|
||||
* It creates an new player if it's no persisted and adds/refreshes the join time
|
||||
*
|
||||
* @param uuid the actual uuid of a player
|
||||
|
@ -34,20 +34,18 @@ class SimplePlayerTime(private val db: PlayerTimeDB) : PlayerTime {
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates the playtime and saves it
|
||||
* Updates the playtime and saves it from the player with the [uuid]
|
||||
*
|
||||
* @param uuid UUID
|
||||
*/
|
||||
override fun updatePlayTime(uuid: UUID) {
|
||||
val player = db.findById(uuid)
|
||||
val playTime = timePlayed(uuid)
|
||||
player.playTime = playTime
|
||||
player.playTime = calculatePlaytime(player)
|
||||
db.save(player)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the playtime including the old time
|
||||
* if the stats got save it should use the save time instead of the join time
|
||||
* Returns the playtime of the player with the the [uuid]
|
||||
*
|
||||
* @param uuid player uuid
|
||||
*
|
||||
|
@ -55,6 +53,27 @@ class SimplePlayerTime(private val db: PlayerTimeDB) : PlayerTime {
|
|||
*/
|
||||
override fun timePlayed(uuid: UUID): Duration {
|
||||
val player = db.findById(uuid)
|
||||
return calculatePlaytime(player)
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the players with the highest playtime
|
||||
*
|
||||
* @return List<Player> in a descending order by playtime
|
||||
*/
|
||||
override fun getTopPlayers(): List<Player> {
|
||||
val playerList: List<Player> = db.findAll().onEach { player -> player.playTime = calculatePlaytime(player) }
|
||||
return playerList.sortedByDescending { player -> player.playTime }
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the time based on the join and on the last save time of the player
|
||||
* if the playtime got saved it should use the saved time instead of the join time
|
||||
*
|
||||
* @param player Player
|
||||
* @return Duration the playtime of the [player]
|
||||
*/
|
||||
private fun calculatePlaytime(player: Player): Duration {
|
||||
val playTime: Duration =
|
||||
if ((player.lastSave != null) && Duration.between(player.lastSave, player.joinTime).isNegative) {
|
||||
Duration.between(player.lastSave, LocalDateTime.now())
|
||||
|
|
28
src/main/kotlin/commands/TopTime.kt
Normal file
28
src/main/kotlin/commands/TopTime.kt
Normal file
|
@ -0,0 +1,28 @@
|
|||
package commands
|
||||
|
||||
import PlayerTime
|
||||
import PlayerTimeService
|
||||
import java.util.stream.Collectors
|
||||
import org.bukkit.command.Command
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
|
||||
/**
|
||||
* Command to get the top players with the most playtime
|
||||
*
|
||||
* @property playerTime PlayerTime
|
||||
* @property playerListSize Long default = 5
|
||||
* @constructor
|
||||
*/
|
||||
class TopTime(private val playerTime: PlayerTime = PlayerTimeService, private val playerListSize: Long = 5) : CommandExecutor {
|
||||
|
||||
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
|
||||
val playerTopList = playerTime.getTopPlayers().stream().limit(playerListSize).collect(Collectors.toList()).toList()
|
||||
|
||||
val sb = StringBuilder().appendln("Top list")
|
||||
playerTopList.forEach { sb.appendln("${it.playerName}: ${playerTime.timeToString(it.playTime)}") }
|
||||
sender.sendMessage(sb.toString())
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package db
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import java.io.File
|
||||
import java.time.LocalDateTime
|
||||
import java.util.UUID
|
||||
import models.Player
|
||||
import mu.KotlinLogging
|
||||
|
||||
/**
|
||||
* FileDB manages the file based PlayerTime DB and provides basic functionality to interact with it
|
||||
|
@ -14,6 +16,8 @@ import models.Player
|
|||
*/
|
||||
class FilePlayerTimeDB(private val path: File = File("./plugins/PlayTime/")) : PlayerTimeDB {
|
||||
|
||||
val logger = KotlinLogging.logger {}
|
||||
|
||||
private val gson = Gson()
|
||||
|
||||
/**
|
||||
|
@ -100,6 +104,24 @@ class FilePlayerTimeDB(private val path: File = File("./plugins/PlayTime/")) : P
|
|||
throw PlayerNotFoundException("""The player with the name $name was not found""")
|
||||
}
|
||||
|
||||
/**
|
||||
* Should read all player and return theme
|
||||
*
|
||||
* @return List<Player>
|
||||
*/
|
||||
override fun findAll(): List<Player> {
|
||||
val playerList = mutableListOf<Player>()
|
||||
val fileList = path.listFiles() ?: return emptyList()
|
||||
fileList.forEach { file: File ->
|
||||
try {
|
||||
playerList.add(gson.fromJson(file.readText(), Player::class.java))
|
||||
} catch (e: JsonSyntaxException) {
|
||||
logger.error { "The player with the ID ${{ file.name }} could not be read out of the files!" }
|
||||
}
|
||||
}
|
||||
return playerList.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Writs a player into an existing file
|
||||
* To create a new player file @see createPlayer(player: Player)
|
||||
|
|
|
@ -45,4 +45,11 @@ interface PlayerFinder {
|
|||
* @throws PlayerNotFoundException
|
||||
*/
|
||||
fun findByName(name: String): Player
|
||||
|
||||
/**
|
||||
* Should read all player and return theme
|
||||
*
|
||||
* @return List<Player>
|
||||
*/
|
||||
fun findAll(): List<Player>
|
||||
}
|
||||
|
|
|
@ -16,4 +16,7 @@ commands:
|
|||
description: "Shows the up time of the server"
|
||||
playtimeof:
|
||||
usage: /<command> <player>
|
||||
description: "Shows the playtime of the player"
|
||||
description: "Shows the playtime of the player"
|
||||
toptime:
|
||||
usage: /<command>
|
||||
description: "Shows top players with the highest playtime"
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
import java.time.Duration
|
||||
import java.util.UUID
|
||||
import models.Player
|
||||
|
||||
class FakePlayerTime : PlayerTime {
|
||||
|
||||
|
@ -15,4 +16,8 @@ class FakePlayerTime : PlayerTime {
|
|||
override fun timePlayed(uuid: UUID): Duration {
|
||||
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
override fun getTopPlayers(): List<Player> {
|
||||
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
}
|
||||
|
|
105
src/test/kotlin/commands/TopTimeTest.kt
Normal file
105
src/test/kotlin/commands/TopTimeTest.kt
Normal file
|
@ -0,0 +1,105 @@
|
|||
package commands
|
||||
|
||||
import SimplePlayerTime
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.unmockkAll
|
||||
import java.time.Duration
|
||||
import java.util.UUID
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import models.Player
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
class TopTimeTest {
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `send string`() {
|
||||
val captureList = mutableListOf<String>()
|
||||
|
||||
val uuid1 = UUID.randomUUID()
|
||||
val uuid2 = UUID.randomUUID()
|
||||
val uuid3 = UUID.randomUUID()
|
||||
val player1 = Player(uuid1, "player1", playTime = Duration.ZERO)
|
||||
val player2 = Player(uuid2, "player2", playTime = Duration.ofMinutes(10))
|
||||
val player3 = Player(uuid3, "player3", playTime = Duration.ofHours(2))
|
||||
|
||||
val mockPlayTime = mockk<SimplePlayerTime>()
|
||||
every { mockPlayTime.getTopPlayers() } returns listOf(player3, player2, player1)
|
||||
every { mockPlayTime.timeToString(any()) } returns "00:00:00"
|
||||
|
||||
val topTime = TopTime(mockPlayTime)
|
||||
|
||||
val mockCommandSender = mockk<CommandSender>()
|
||||
every { mockCommandSender.sendMessage(capture(captureList)) } just Runs
|
||||
|
||||
assertTrue(topTime.onCommand(mockCommandSender, mockk(), "", arrayOf()))
|
||||
|
||||
assertTrue(captureList.isNotEmpty())
|
||||
assertNotEquals("", captureList.first())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `correct formatted string output`() {
|
||||
val captureList = mutableListOf<String>()
|
||||
|
||||
val uuid1 = UUID.randomUUID()
|
||||
val uuid2 = UUID.randomUUID()
|
||||
val uuid3 = UUID.randomUUID()
|
||||
val player1 = Player(uuid1, "player1", playTime = Duration.ZERO)
|
||||
val player2 = Player(uuid2, "player2", playTime = Duration.ofMinutes(10))
|
||||
val player3 = Player(uuid3, "player3", playTime = Duration.ofHours(2))
|
||||
|
||||
val mockPlayTime = mockk<SimplePlayerTime>()
|
||||
every { mockPlayTime.getTopPlayers() } returns listOf(player3, player2, player1)
|
||||
every { mockPlayTime.timeToString(any()) } returns "00:00:00"
|
||||
|
||||
val mockCommandSender = mockk<CommandSender>()
|
||||
every { mockCommandSender.sendMessage(capture(captureList)) } just Runs
|
||||
|
||||
val topTime = TopTime(mockPlayTime)
|
||||
assertTrue(topTime.onCommand(mockCommandSender, mockk(), "", arrayOf()))
|
||||
|
||||
assertEquals(
|
||||
"""Top list
|
||||
player3: 00:00:00
|
||||
player2: 00:00:00
|
||||
player1: 00:00:00
|
||||
""", captureList.first().toString()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `show only top 6 player`() {
|
||||
val playerListSize = 6
|
||||
val defaultNewLines = 2
|
||||
val captureList = mutableListOf<String>()
|
||||
|
||||
val playerList = mutableListOf<Player>()
|
||||
for (i in 1..7) {
|
||||
playerList.add(Player(UUID.randomUUID(), "$i", playTime = Duration.ZERO))
|
||||
}
|
||||
|
||||
val mockPlayTime = mockk<SimplePlayerTime>()
|
||||
every { mockPlayTime.getTopPlayers() } returns playerList.toList()
|
||||
every { mockPlayTime.timeToString(any()) } returns "00:00:00"
|
||||
|
||||
val mockCommandSender = mockk<CommandSender>()
|
||||
every { mockCommandSender.sendMessage(capture(captureList)) } just Runs
|
||||
|
||||
val topTime = TopTime(mockPlayTime, playerListSize.toLong())
|
||||
assertTrue(topTime.onCommand(mockCommandSender, mockk(), "", arrayOf()))
|
||||
|
||||
assertEquals(playerListSize + defaultNewLines, captureList.first().split("\n").count())
|
||||
}
|
||||
}
|
|
@ -156,4 +156,40 @@ class FilePlayerTimeDBTest {
|
|||
assertFalse(fileDB.existsByName("foo"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `find all players`() {
|
||||
val playerName = "playerName"
|
||||
val fileDB = FilePlayerTimeDB(dbDir)
|
||||
val uuid = UUID.randomUUID()
|
||||
val uuid2 = UUID.randomUUID()
|
||||
val player = Player(uuid, playerName)
|
||||
val player2 = Player(uuid2, playerName)
|
||||
|
||||
File(dbDir, uuid.toString()).writeText(Gson().toJson(player))
|
||||
File(dbDir, uuid2.toString()).writeText(Gson().toJson(player2))
|
||||
|
||||
val playerList = fileDB.findAll()
|
||||
assertFalse(playerList.isEmpty())
|
||||
assertEquals(2, playerList.size)
|
||||
assertTrue(playerList.contains(player))
|
||||
assertTrue(playerList.contains(player2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `find all players if there is no one`() {
|
||||
val fileDB = FilePlayerTimeDB(dbDir)
|
||||
|
||||
assertTrue(fileDB.findAll().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error by deserialization`() {
|
||||
val fileDB = FilePlayerTimeDB(dbDir)
|
||||
val uuid = UUID.randomUUID()
|
||||
|
||||
File(dbDir, uuid.toString()).writeText("Foo")
|
||||
|
||||
assertTrue(fileDB.findAll().isEmpty())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ import java.time.LocalDateTime
|
|||
import java.util.UUID
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import models.Player
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SimplePlayerTimeTest {
|
||||
|
||||
|
@ -113,6 +113,24 @@ class SimplePlayerTimeTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update the play time and save`() {
|
||||
val capturedPlayers = mutableListOf<Player>()
|
||||
val uuid = UUID.randomUUID()
|
||||
val player = Player(uuid, "", playTime = Duration.ZERO)
|
||||
|
||||
val mockDBPlayerTimeDB = mockk<PlayerTimeDB>()
|
||||
every { mockDBPlayerTimeDB.findById(uuid) } returns player
|
||||
every { mockDBPlayerTimeDB.save(capture(capturedPlayers)) } just Runs
|
||||
|
||||
val simplePlayerTime = SimplePlayerTime(mockDBPlayerTimeDB)
|
||||
|
||||
simplePlayerTime.updatePlayTime(uuid)
|
||||
|
||||
assertEquals(1, capturedPlayers.size)
|
||||
assertNotEquals(Duration.ZERO, capturedPlayers.first().playTime)
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class TimePlayedTest {
|
||||
|
||||
|
@ -184,4 +202,42 @@ class SimplePlayerTimeTest {
|
|||
println(playTime.toMinutes())
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class TopPlayer {
|
||||
|
||||
@Test
|
||||
fun `get list with players`() {
|
||||
val player1 = Player(UUID.randomUUID(), "player1", playTime = Duration.ZERO)
|
||||
val player2 = Player(UUID.randomUUID(), "player2", playTime = Duration.ofMinutes(10))
|
||||
|
||||
val mockPlayerTimeDB = mockk<PlayerTimeDB>()
|
||||
every { mockPlayerTimeDB.findAll() } returns listOf(player1, player2)
|
||||
|
||||
val playerService = SimplePlayerTime(mockPlayerTimeDB)
|
||||
|
||||
val topPlayer = playerService.getTopPlayers()
|
||||
|
||||
assertFalse(topPlayer.isEmpty())
|
||||
assertEquals(2, topPlayer.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get list with players sorted by most time`() {
|
||||
val player1 = Player(UUID.randomUUID(), "player1", playTime = Duration.ZERO)
|
||||
val player2 = Player(UUID.randomUUID(), "player2", playTime = Duration.ofMinutes(10))
|
||||
val player3 = Player(UUID.randomUUID(), "player3", playTime = Duration.ofHours(2))
|
||||
|
||||
val mockPlayerTimeDB = mockk<PlayerTimeDB>()
|
||||
every { mockPlayerTimeDB.findAll() } returns listOf(player1, player2, player3)
|
||||
|
||||
val playerService = SimplePlayerTime(mockPlayerTimeDB)
|
||||
|
||||
val topPlayer = playerService.getTopPlayers()
|
||||
|
||||
assertEquals(player3, topPlayer[0])
|
||||
assertEquals(player2, topPlayer[1])
|
||||
assertEquals(player1, topPlayer[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue