diff --git a/build.gradle.kts b/build.gradle.kts index 0cadbec..2ad5a12 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { implementation(libs.kotlin.stdlib) implementation(libs.yamlkt) implementation(libs.fastboard) + implementation(libs.bundles.ktoml) } // Project Settings diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 19e58cf..4f23a33 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ cloudPaper = "2.0.0-SNAPSHOT" jspecify = "1.0.0" yamlkt = "0.13.0" fastboard = "2.1.5" +ktoml = "0.7.1" [plugins] shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } @@ -33,3 +34,9 @@ cloudPaper = { module = "org.incendo:cloud-paper", version.ref = "cloudPaper" } jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" } yamlkt = { module = "net.mamoe.yamlkt:yamlkt", version.ref = "yamlkt" } fastboard = { module = "fr.mrmicky:fastboard", version.ref = "fastboard" } +ktoml-core = { module = "com.akuleshov7:ktoml-core", version.ref = "ktoml"} + +[bundles] +ktoml = [ + "ktoml-core" +] diff --git a/src/main/kotlin/be4rjp/sclat/Sclat.kt b/src/main/kotlin/be4rjp/sclat/Sclat.kt index ff2e34c..4ec104b 100644 --- a/src/main/kotlin/be4rjp/sclat/Sclat.kt +++ b/src/main/kotlin/be4rjp/sclat/Sclat.kt @@ -16,6 +16,7 @@ import be4rjp.sclat.api.holo.PlayerHolograms import be4rjp.sclat.api.utils.TextAnimation import be4rjp.sclat.commands.SclatCommandExecutor import be4rjp.sclat.config.Config +import be4rjp.sclat.config.NewConfig import be4rjp.sclat.data.DataMgr import be4rjp.sclat.data.DataMgr.armorStandMap import be4rjp.sclat.data.DataMgr.blockDataMap @@ -99,7 +100,8 @@ class Sclat : // --------------------------Load config------------------------------ sclatLogger.info("Loading config files...") conf = Config() - conf!!.loadConfig() + conf?.loadConfig() + NewConfig.load() for (mapname in conf!!.mapConfig!!.getConfigurationSection("Maps")!!.getKeys(false)) { val worldName: String? = conf!!.mapConfig!!.getString("Maps." + mapname + ".WorldName") Bukkit.createWorld(WorldCreator(worldName!!)) @@ -396,6 +398,7 @@ class Sclat : */ for (`as` in armorStandMap.keys) `as`!!.remove() conf!!.saveConfig() + NewConfig.save() for (`as` in DataMgr.al) `as`!!.remove() diff --git a/src/main/kotlin/be4rjp/sclat/api/serializer/UUIDSerializer.kt b/src/main/kotlin/be4rjp/sclat/api/serializer/UUIDSerializer.kt new file mode 100644 index 0000000..7821112 --- /dev/null +++ b/src/main/kotlin/be4rjp/sclat/api/serializer/UUIDSerializer.kt @@ -0,0 +1,23 @@ +package be4rjp.sclat.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.util.UUID + +object UUIDSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) + + override fun serialize( + encoder: Encoder, + value: UUID, + ) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString()) +} diff --git a/src/main/kotlin/be4rjp/sclat/api/utils/DailyRefreshSet.kt b/src/main/kotlin/be4rjp/sclat/api/utils/DailyRefreshSet.kt new file mode 100644 index 0000000..0c94f3e --- /dev/null +++ b/src/main/kotlin/be4rjp/sclat/api/utils/DailyRefreshSet.kt @@ -0,0 +1,44 @@ +@file:UseSerializers(UUIDSerializer::class) + +package be4rjp.sclat.api.utils + +import be4rjp.sclat.api.serializer.UUIDSerializer +import be4rjp.sclat.extension.ZONE_TOKYO +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit +import java.util.UUID + +@Serializable +data class DailyRefreshSet( + var nextResetEpoch: Long = 0L, + var uuids: MutableSet = mutableSetOf(), +) { + private fun calculateNextReset(): Long = + ZonedDateTime + .now(ZONE_TOKYO) + .truncatedTo(ChronoUnit.DAYS) // today 00:00:00 + .plusDays(1) // tomorrow 00:00:00 + .toInstant() + .toEpochMilli() + + private fun checkRefresh() { + if (System.currentTimeMillis() >= nextResetEpoch) { + uuids.clear() + nextResetEpoch = calculateNextReset() + } + } + + operator fun plus(uuid: UUID) { + checkRefresh() + uuids.add(uuid) + } + + operator fun minus(uuid: UUID) { + checkRefresh() + uuids.remove(uuid) + } + + operator fun contains(uuid: UUID) = uuids.contains(uuid) +} diff --git a/src/main/kotlin/be4rjp/sclat/config/Config.kt b/src/main/kotlin/be4rjp/sclat/config/Config.kt index fcc5909..832f564 100644 --- a/src/main/kotlin/be4rjp/sclat/config/Config.kt +++ b/src/main/kotlin/be4rjp/sclat/config/Config.kt @@ -31,6 +31,7 @@ class Config { private set var emblemUserdata: FileConfiguration? = null private set + private val parent = File("plugins/Sclat") private val psf = File(parent, "class.yml") private val weaponf = File(parent, "mainnweapon.yml") @@ -106,4 +107,7 @@ class Config { val uUIDCash: FileConfiguration get() = idCash!! + + companion object { + } } diff --git a/src/main/kotlin/be4rjp/sclat/config/LoginBonusRewardConfig.kt b/src/main/kotlin/be4rjp/sclat/config/LoginBonusRewardConfig.kt new file mode 100644 index 0000000..935ea75 --- /dev/null +++ b/src/main/kotlin/be4rjp/sclat/config/LoginBonusRewardConfig.kt @@ -0,0 +1,9 @@ +package be4rjp.sclat.config + +import kotlinx.serialization.Serializable + +@Serializable +data class LoginBonusRewardConfig( + val money: Int = 5000, + val ticket: Int = 50, +) diff --git a/src/main/kotlin/be4rjp/sclat/config/NewConfig.kt b/src/main/kotlin/be4rjp/sclat/config/NewConfig.kt new file mode 100644 index 0000000..0eb50d4 --- /dev/null +++ b/src/main/kotlin/be4rjp/sclat/config/NewConfig.kt @@ -0,0 +1,56 @@ +package be4rjp.sclat.config + +import be4rjp.sclat.api.utils.DailyRefreshSet +import be4rjp.sclat.extension.loadToml +import be4rjp.sclat.extension.saveToml +import be4rjp.sclat.loginbonus.LoginBonus +import be4rjp.sclat.sclatLogger +import java.io.File +import java.util.function.Supplier + +/** + * Migrating from Config.kt + */ +object NewConfig { + private val parent = File("plugins/Sclat") + lateinit var loginBonusReward: LoginBonusRewardConfig + private set + + fun load() { + sclatLogger.info(">>> Loading config...") + loginBonusReward = loadTomlConfig("login_bonus", ::LoginBonusRewardConfig) + LoginBonus.refreshSet = loadTomlConfig("login_bonus_claimed", ::DailyRefreshSet) + sclatLogger.info("<<< All config loaded.") + } + + fun save() { + sclatLogger.info(">>> Saving config...") + saveTomlConfig("login_bonus_claimed", LoginBonus.refreshSet) + sclatLogger.info("<<< All config saved.") + } + + private inline fun saveTomlConfig( + name: String, + value: T, + ) { + parent.resolve("$name.toml").let { file -> + saveToml(file, value) + sclatLogger.info("-> Config ${file.name} saved!") + } + } + + private inline fun loadTomlConfig( + name: String, + default: Supplier, + ): T = + parent.resolve("$name.toml").let { file -> + if (!file.exists()) { + saveToml(file, default.get()).also { + sclatLogger.warn("${file.name} was missing. Wrote default config.") + } + } + loadToml(file).also { + sclatLogger.info("-> Config ${file.name} loaded!") + } + } +} diff --git a/src/main/kotlin/be4rjp/sclat/extension/LocalDate.kt b/src/main/kotlin/be4rjp/sclat/extension/LocalDate.kt new file mode 100644 index 0000000..a3b3b4e --- /dev/null +++ b/src/main/kotlin/be4rjp/sclat/extension/LocalDate.kt @@ -0,0 +1,8 @@ +package be4rjp.sclat.extension + +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +val SIMPLE_DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + +fun LocalDate.toSimpleFormat(): String = this.format(SIMPLE_DATE_FORMATTER) diff --git a/src/main/kotlin/be4rjp/sclat/extension/Toml.kt b/src/main/kotlin/be4rjp/sclat/extension/Toml.kt new file mode 100644 index 0000000..0d9b6de --- /dev/null +++ b/src/main/kotlin/be4rjp/sclat/extension/Toml.kt @@ -0,0 +1,33 @@ +package be4rjp.sclat.extension + +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.TomlIndentation +import com.akuleshov7.ktoml.TomlInputConfig +import com.akuleshov7.ktoml.TomlOutputConfig +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import java.io.File + +val toml = + Toml( + inputConfig = + TomlInputConfig( + ignoreUnknownNames = true, + allowEmptyValues = true, + allowNullValues = true, + allowEscapedQuotesInLiteralStrings = true, + allowEmptyToml = true, + ignoreDefaultValues = false, + ), + outputConfig = + TomlOutputConfig( + indentation = TomlIndentation.NONE, + ), + ) + +inline fun loadToml(file: File): T = toml.decodeFromString(file.readText()) + +inline fun saveToml( + file: File, + value: T, +) = file.writeText(toml.encodeToString(value)) diff --git a/src/main/kotlin/be4rjp/sclat/extension/ZoneId.kt b/src/main/kotlin/be4rjp/sclat/extension/ZoneId.kt new file mode 100644 index 0000000..67b53c0 --- /dev/null +++ b/src/main/kotlin/be4rjp/sclat/extension/ZoneId.kt @@ -0,0 +1,8 @@ +package be4rjp.sclat.extension + +import java.time.LocalDate +import java.time.ZoneId + +val ZONE_TOKYO: ZoneId = ZoneId.of("Asia/Tokyo") + +fun ZoneId.toLocalDate(): LocalDate = LocalDate.now(this) diff --git a/src/main/kotlin/be4rjp/sclat/loginbonus/LoginBonus.kt b/src/main/kotlin/be4rjp/sclat/loginbonus/LoginBonus.kt new file mode 100644 index 0000000..8b59dd2 --- /dev/null +++ b/src/main/kotlin/be4rjp/sclat/loginbonus/LoginBonus.kt @@ -0,0 +1,32 @@ +package be4rjp.sclat.loginbonus + +import be4rjp.sclat.api.utils.DailyRefreshSet +import be4rjp.sclat.config.NewConfig +import be4rjp.sclat.manager.PlayerStatusMgr +import org.bukkit.entity.Player +import java.util.UUID + +object LoginBonus { + var refreshSet: DailyRefreshSet = DailyRefreshSet() + internal set + + fun isClaimable(playerUUID: UUID) = playerUUID !in refreshSet + + fun markClaimed(playerUUID: UUID) = refreshSet.plus(playerUUID) + + fun unmarkClaimed(playerUUID: UUID) = refreshSet.minus(playerUUID) + + /** + * Try to claim daily login bonus + * + * @param player target player + * @return is succeeded + */ + fun tryClaim(player: Player): Boolean { + if (!isClaimable(player.uniqueId)) return false + PlayerStatusMgr.addMoney(player, NewConfig.loginBonusReward.money) + PlayerStatusMgr.addTicket(player, NewConfig.loginBonusReward.ticket) + markClaimed(player.uniqueId) + return true + } +} diff --git a/src/main/kotlin/be4rjp/sclat/manager/GameMgr.kt b/src/main/kotlin/be4rjp/sclat/manager/GameMgr.kt index 63c81d1..e275ced 100644 --- a/src/main/kotlin/be4rjp/sclat/manager/GameMgr.kt +++ b/src/main/kotlin/be4rjp/sclat/manager/GameMgr.kt @@ -8,6 +8,7 @@ import be4rjp.sclat.api.ServerType import be4rjp.sclat.api.SoundType import be4rjp.sclat.api.player.PlayerData import be4rjp.sclat.api.player.PlayerSettings +import be4rjp.sclat.config.NewConfig import be4rjp.sclat.data.DataMgr import be4rjp.sclat.data.DataMgr.beaconMap import be4rjp.sclat.data.DataMgr.getBeaconFromplayer @@ -24,6 +25,7 @@ import be4rjp.sclat.data.DataMgr.sprinklerMap import be4rjp.sclat.data.PaintData import be4rjp.sclat.gui.LootBox import be4rjp.sclat.gui.OpenGUI +import be4rjp.sclat.loginbonus.LoginBonus import be4rjp.sclat.packet.PacketHandler import be4rjp.sclat.plugin import be4rjp.sclat.server.EquipmentClient @@ -130,12 +132,19 @@ class GameMgr : Listener { Sclat.conf!! .uUIDCash .set(player.uniqueId.toString(), player.name) + + // On lobby handling if (Sclat.type == ServerType.LOBBY) { // Add user-specific hologram // RankingHolograms rankingHolograms = new RankingHolograms(player); // DataMgr.setRankingHolograms(player, rankingHolograms); // PlayerStatusMgr.HologramUpdateRunnable(player); Sclat.playerHolograms.add(player) + if (LoginBonus.tryClaim(player)) { + player.sendMessage( + "${ChatColor.GOLD}ログインボーナス!${ChatColor.WHITE} お金 ${ChatColor.GREEN}+${NewConfig.loginBonusReward.money}${ChatColor.WHITE} & チケット ${ChatColor.GREEN}+${NewConfig.loginBonusReward.ticket}${ChatColor.WHITE}", + ) + } } if (Sclat.type != ServerType.MATCH) {