diff --git a/.gitignore b/.gitignore index c476faf..d13336a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,10 +28,6 @@ bin/ *.DS_Store -# fabric - -run/ - # java hs_err_*.log diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 0000000..d444b0f --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + id("maven-publish") + kotlin("jvm") + kotlin("plugin.serialization") +} + +kotlin { + explicitApi() +} + +val kotlinVersion: String by rootProject +val kotlinCoroutinesVersion: String by rootProject +val kotlinSerializationVersion: String by rootProject +val atomicFuVersion: String by rootProject +val kotlinDatetimeVersion: String by rootProject + +dependencies { + implementation("net.kyori:adventure-api:4.14.0") + implementation("net.kyori:adventure-text-minimessage:4.14.0") + api("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") + api("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlinCoroutinesVersion") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinSerializationVersion") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion") + api("org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlinSerializationVersion") + api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") + api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinDatetimeVersion") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + testImplementation("io.mockk:mockk:1.13.8") +} + +tasks.test { + useJUnitPlatform() +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCity.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCity.kt new file mode 100644 index 0000000..616cc3d --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCity.kt @@ -0,0 +1,36 @@ +package br.com.gamemods.minecity.api + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.service.namedplayer.NamedPlayerService + +private lateinit var currentInstance: MineCity + +/** + * Interface that allows other mods to interact with MineCity. + */ +public interface MineCity { + /** + * The current platform implementation + */ + public val platform: MineCityPlatform + + /** + * Allows to get the player name and UUIDs with easy + */ + public val players: NamedPlayerService + + /** + * This companion object allows MineCity interface to be used directly in kotlin delegating all API calls to + * the instance that is set at [instance]. + * + * @property instance Access to the MineCity API implementation, must be modified only by MineCity itself, can be accessed freely. + */ + public companion object: MineCity by currentInstance { + public var instance: MineCity + get() = currentInstance + @InternalMineCityApi + set(value) { + currentInstance = value + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCityPlatform.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCityPlatform.kt new file mode 100644 index 0000000..7f84263 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/MineCityPlatform.kt @@ -0,0 +1,24 @@ +package br.com.gamemods.minecity.api + +import br.com.gamemods.minecity.api.client.MineCityClient +import br.com.gamemods.minecity.api.server.MineCityServer + +/** + * Platform specific implementations needed to run MineCity + */ +public interface MineCityPlatform { + /** + * The platform MineCity API implementation. + */ + public val core: MineCity + + /** + * `null` if this is not running on a client instance or the client was not laded. + */ + public val client: MineCityClient? + + /** + * `null` if this is running on a client that is connected to a server and not hosting it. + */ + public val server: MineCityServer? +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/PlatformDependent.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/PlatformDependent.kt new file mode 100644 index 0000000..a28b1fd --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/PlatformDependent.kt @@ -0,0 +1,13 @@ +package br.com.gamemods.minecity.api.annotation + +/** + * Indicates that the annotated element has behavior that varies depending on the currently running platform. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY, + AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR +) +@RequiresOptIn( + level = RequiresOptIn.Level.WARNING, message = "This API behaves differently depending on the Minecraft platform, take care." +) +public annotation class PlatformDependent diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/internal/InternalMineCityApi.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/internal/InternalMineCityApi.kt new file mode 100644 index 0000000..6cb0877 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/internal/InternalMineCityApi.kt @@ -0,0 +1,16 @@ +package br.com.gamemods.minecity.api.annotation.internal + +/** + * Indicates that the annotated element should only be used by MineCity implementation, not API users. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY, + AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR +) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, message = "This is an internal MineCity API that " + + "should not be used from outside of br.com.gamemods.minecity. No compatibility guarantees are provided. " + + "It is recommended to report your use-case of internal API to MineCity2 issue tracker, " + + "so stable API could be provided instead" +) +public annotation class InternalMineCityApi diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/internal/InternalMineCityMixin.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/internal/InternalMineCityMixin.kt new file mode 100644 index 0000000..d8e4bf7 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/internal/InternalMineCityMixin.kt @@ -0,0 +1,16 @@ +package br.com.gamemods.minecity.api.annotation.internal + +/** + * Indicates that the annotated element should only be used by MineCity Mixin, not API users. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY, + AnnotationTarget.PROPERTY_SETTER +) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, message = "This is an internal MineCity Mixin that " + + "should not be used from outside of br.com.gamemods.minecity's mixins. No compatibility guarantees are provided. " + + "It is recommended to report your use-case of internal API to MineCity2 issue tracker, " + + "so stable API could be provided instead" +) +public annotation class InternalMineCityMixin diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/side/ClientSideOnly.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/side/ClientSideOnly.kt new file mode 100644 index 0000000..87d1560 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/side/ClientSideOnly.kt @@ -0,0 +1,11 @@ +package br.com.gamemods.minecity.api.annotation.side + +/** + * Indicates that the annotated element must be executed only on client side and might crash on server. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, message = "Make sure this is being executed on client side" +) +public annotation class ClientSideOnly diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/side/ServerSideOnly.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/side/ServerSideOnly.kt new file mode 100644 index 0000000..4efac5d --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/side/ServerSideOnly.kt @@ -0,0 +1,11 @@ +package br.com.gamemods.minecity.api.annotation.side + +/** + * Indicates that the annotated element must be executed only on server side and might crash on client. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, message = "Make sure this is being executed on server side" +) +public annotation class ServerSideOnly diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/ASyncFriendly.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/ASyncFriendly.kt new file mode 100644 index 0000000..16e9418 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/ASyncFriendly.kt @@ -0,0 +1,8 @@ +package br.com.gamemods.minecity.api.annotation.threading + +/** + * Indicates that the annotated element can be executed outside the main server thread without problems. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER) +public annotation class ASyncFriendly diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/AsyncOnly.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/AsyncOnly.kt new file mode 100644 index 0000000..a200c23 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/AsyncOnly.kt @@ -0,0 +1,11 @@ +package br.com.gamemods.minecity.api.annotation.threading + +/** + * Indicates that the annotated element must NOT be executed on the main server thread to prevent crashes, lag or bad behaviours. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, message = "Make sure this is NOT being executed on the main server thread" +) +public annotation class AsyncOnly diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/SyncFriendly.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/SyncFriendly.kt new file mode 100644 index 0000000..f4013b0 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/SyncFriendly.kt @@ -0,0 +1,8 @@ +package br.com.gamemods.minecity.api.annotation.threading + +/** + * Indicates that the annotated element can be executed only on the main server thread without problems. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER) +public annotation class SyncFriendly diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/SyncOnly.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/SyncOnly.kt new file mode 100644 index 0000000..c5cee64 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/annotation/threading/SyncOnly.kt @@ -0,0 +1,11 @@ +package br.com.gamemods.minecity.api.annotation.threading + +/** + * Indicates that the annotated element must be executed only on the main server thread to prevent crashes or bad behaviours. + */ +@Retention(value = AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.PROPERTY_GETTER) +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, message = "Make sure this is being executed on the main server thread" +) +public annotation class SyncOnly diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/City.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/City.kt new file mode 100644 index 0000000..29bcfc5 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/City.kt @@ -0,0 +1,21 @@ +package br.com.gamemods.minecity.api.claim + +import br.com.gamemods.minecity.api.id.CityId +import br.com.gamemods.minecity.api.serializer.MiniComponent +import kotlinx.serialization.Serializable + +/** + * A city in MineCity represents a group of claims, cities may also have mayors and elections. + * + * @property id ID of this city + * @property name Visible name of this city + * @property parent If this city is child of another + * @property mayors A list of all mayors this city had, last on list is last on start time. + */ +@Serializable +public data class City( + val name: MiniComponent, + val id: CityId = CityId(), + val parent: CityId? = null, + val mayors: List = emptyList(), +) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/CityMayor.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/CityMayor.kt new file mode 100644 index 0000000..175ee5d --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/CityMayor.kt @@ -0,0 +1,31 @@ +package br.com.gamemods.minecity.api.claim + +import br.com.gamemods.minecity.api.serializer.UniqueId +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable +import kotlin.time.Duration + +/** + * Data about a mayor of a city. + * + * @property playerId The player ID of the mayor + * @property votes How many votes did this mayor receive during election + * @property totalVotes How may votes were cast to all candidates of an election + * @property since Since when this player started to be a mayor + * @property until When this player stops being mayor + * @property electionDuration How long did the election last + * @property candidates How many candidates were available, including this mayor + * @property voters How many people could vote, may include this mayor + */ +@Serializable +public data class CityMayor( + val playerId: UniqueId, + val votes: Int = 0, + val totalVotes: Int = 0, + val candidates: Int = 0, + val voters: Int = 0, + val until: Instant? = null, + val electionDuration: Duration = Duration.ZERO, + val since: Instant = Clock.System.now(), +) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/Claim.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/Claim.kt new file mode 100644 index 0000000..dc003d0 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/Claim.kt @@ -0,0 +1,17 @@ +package br.com.gamemods.minecity.api.claim + +import br.com.gamemods.minecity.api.id.ClaimId +import br.com.gamemods.minecity.api.serializer.UniqueId +import kotlinx.serialization.Serializable + +@Serializable +public data class Claim( + val id: ClaimId = ClaimId(), + val shape: Set, + val parentId: ClaimId? = null, + val owner: UniqueId? = null, + val rent: ClaimRentContract? = null, + val city: City? = null, + val isAdmin: Boolean = false, + val settings: ClaimSettings = ClaimSettings(), +) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimFlagValue.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimFlagValue.kt new file mode 100644 index 0000000..156d7b0 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimFlagValue.kt @@ -0,0 +1,32 @@ +package br.com.gamemods.minecity.api.claim + +import br.com.gamemods.minecity.api.serializer.MiniComponent +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +public sealed class ClaimFlagValue { + public abstract val value: Any + + @Serializable + @SerialName("component") + public data class MiniComponentValue(override val value: MiniComponent): ClaimFlagValue() + + @Serializable + @SerialName("empty") + public data object Empty: ClaimFlagValue() { + override val value: Unit = Unit + } + + @Serializable + @SerialName("string") + public data class StringValue(override val value: String): ClaimFlagValue() + + @Serializable + @SerialName("boolean") + public data class BooleanValue(override val value: Boolean): ClaimFlagValue() + + @Serializable + @SerialName("int") + public data class IntValue(override val value: Int): ClaimFlagValue() +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimRentContract.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimRentContract.kt new file mode 100644 index 0000000..d4b22a7 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimRentContract.kt @@ -0,0 +1,38 @@ +package br.com.gamemods.minecity.api.claim + +import br.com.gamemods.minecity.api.math.VirtualMoney +import br.com.gamemods.minecity.api.serializer.UniqueId +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable +import kotlin.time.Duration + +/** + * Information about an active rent, used by claims that is rented to someone. + * + * @property rentedBy Who set this claim on rent + * @property rentedTo Who got the claim + * @property originalSettings Backup of how the claim settings were before the rent + * @property since The start time of the rent + * @property expiresAt When the current contract expires, the rent may be prolonged by [maxExpiredTolerance] + * @property maxExpiredTolerance How long after the [expiresAt] that the rent must still be active, charging an extra [expiredCharges]. + * @property renewedAt The last time this rent was renewed on this contract + * @property renewals How many times this rent was renewed on this contract + * @property renewalPrice The price to pay for the next renewal + * @property totalPaidAmount The full amount paid since the start of the rent. + * @property expiredCharges The amount to be paid in case it's paid during the [maxExpiredTolerance] + */ +@Serializable +public data class ClaimRentContract( + val rentedBy: UniqueId, + val rentedTo: UniqueId, + val originalSettings: ClaimSettings, + val since: Instant, + val expiresAt: Instant, + val maxExpiredTolerance: Duration, + val originalPrice: VirtualMoney, + val totalPaidAmount: VirtualMoney, + val renewalPrice: VirtualMoney = originalPrice, + val expiredCharges: VirtualMoney? = null, + val renewals: Int = 0, + val renewedAt: Instant? = null, +) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimSettings.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimSettings.kt new file mode 100644 index 0000000..5a7ddff --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimSettings.kt @@ -0,0 +1,19 @@ +package br.com.gamemods.minecity.api.claim + +import br.com.gamemods.minecity.api.id.ClamFlagId +import br.com.gamemods.minecity.api.id.ClamPermissionId +import kotlinx.serialization.Serializable + +/** + * Flags, permission and trust levels of a claim. + * + * @property defaultFlags The initial flags, used when calculating the effective flags + * @property defaultPermissions The initial permissions, used when calculating the effective permissions + * @property trustLevels The trust levels details + */ +@Serializable +public data class ClaimSettings( + val defaultFlags: Map = emptyMap(), + val defaultPermissions: Map = emptyMap(), + val trustLevels: List = emptyList(), +) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimShape.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimShape.kt new file mode 100644 index 0000000..36ef4f0 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/ClaimShape.kt @@ -0,0 +1,20 @@ +package br.com.gamemods.minecity.api.claim + +import br.com.gamemods.minecity.api.id.ClaimId +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.shape.CombinedShape +import kotlinx.serialization.Serializable + +/** + * Represents the shape of a claim in a given world. + * + * @property shape The world where this shape resides. + * @property worldId The shape of this claim in this world. + * @property subClaimIds IDs of child claims that also has a shape in this world. + */ +@Serializable +public data class ClaimShape( + val worldId: WorldId, + val shape: CombinedShape, + val subClaimIds: Set, +) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/TrustLevel.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/TrustLevel.kt new file mode 100644 index 0000000..18bca81 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/claim/TrustLevel.kt @@ -0,0 +1,25 @@ +package br.com.gamemods.minecity.api.claim + +import br.com.gamemods.minecity.api.id.ClamFlagId +import br.com.gamemods.minecity.api.id.ClamPermissionId +import br.com.gamemods.minecity.api.id.TrustLevelId +import br.com.gamemods.minecity.api.serializer.MiniComponent +import br.com.gamemods.minecity.api.serializer.UniqueId +import kotlinx.serialization.Serializable + +/** + * A snapshot of the state of a trust level. + * + * @property id The ID of the trust level + * @property displayName The name of this trust level + * @property flags Claim flags that this trust level sets or unsets. + * @property permissions Claim permissions that this trust level sets or unsets. + */ +@Serializable +public data class TrustLevel( + val id: TrustLevelId, + val displayName: MiniComponent, + val players: Set = emptySet(), + val flags: Map = emptyMap(), + val permissions: Map = emptyMap() +) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/client/MineCityClient.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/client/MineCityClient.kt new file mode 100644 index 0000000..44de920 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/client/MineCityClient.kt @@ -0,0 +1,12 @@ +package br.com.gamemods.minecity.api.client + +import br.com.gamemods.minecity.api.MineCityPlatform + +/** + * Client side instance that handles GUI and client features + * + * @property platform The platform where MineCity is running + */ +public interface MineCityClient { + public val platform: MineCityPlatform +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/CityId.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/CityId.kt new file mode 100644 index 0000000..5c6dec5 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/CityId.kt @@ -0,0 +1,16 @@ +package br.com.gamemods.minecity.api.id + +import br.com.gamemods.minecity.api.serializer.UniqueId +import kotlinx.serialization.Serializable +import java.util.* + +/** + * UUID identifier that identifies a city registered on MineCity + */ +@Serializable +@JvmInline +public value class CityId(public val uuid: UniqueId): Comparable { + public constructor(): this(UUID.randomUUID()) + override fun compareTo(other: CityId): Int = uuid.compareTo(other.uuid) + override fun toString(): String = uuid.toString() +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClaimId.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClaimId.kt new file mode 100644 index 0000000..eb9b1e5 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClaimId.kt @@ -0,0 +1,16 @@ +package br.com.gamemods.minecity.api.id + +import br.com.gamemods.minecity.api.serializer.UniqueId +import kotlinx.serialization.Serializable +import java.util.* + +/** + * UUID identifier that identifies a claim registered on MineCity + */ +@Serializable +@JvmInline +public value class ClaimId(public val uuid: UniqueId): Comparable { + public constructor(): this(UUID.randomUUID()) + override fun compareTo(other: ClaimId): Int = uuid.compareTo(other.uuid) + override fun toString(): String = uuid.toString() +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamFlagId.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamFlagId.kt new file mode 100644 index 0000000..2390676 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamFlagId.kt @@ -0,0 +1,12 @@ +package br.com.gamemods.minecity.api.id + +import kotlinx.serialization.Serializable + +/** + * String identifier that identifies the ID of a flag that can be applied on MineCity claims. + */ +@Serializable +@JvmInline +public value class ClamFlagId(private val id: String) { + override fun toString(): String = id +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamPermissionId.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamPermissionId.kt new file mode 100644 index 0000000..3c01e2a --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/ClamPermissionId.kt @@ -0,0 +1,12 @@ +package br.com.gamemods.minecity.api.id + +import kotlinx.serialization.Serializable + +/** + * String identifier that identifies the ID of a permission that can be configured on MineCity claims. + */ +@Serializable +@JvmInline +public value class ClamPermissionId(private val id: String) { + override fun toString(): String = id +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/NamedPlayer.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/NamedPlayer.kt new file mode 100644 index 0000000..e710486 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/NamedPlayer.kt @@ -0,0 +1,50 @@ +package br.com.gamemods.minecity.api.id + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.serializer.UniqueId +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +/** + * Holds a player UUID, name and when the name was updated. + * + * Must be instantiated and changed only by MineCity, all objects are stored in a pool and reused. + * + * @property uuid The player UUID + * @property name The player name as of [time] + * @property time The time which the [name] was updated + */ +@Serializable +public class NamedPlayer @InternalMineCityApi constructor( + public val uuid: UniqueId, + @set:InternalMineCityApi + public var name: String, + @set:InternalMineCityApi + public var time: Instant, +): Comparable { + override fun compareTo(other: NamedPlayer): Int { + val diff = name.compareTo(other.name) + return if (diff != 0) diff + else uuid.compareTo(other.uuid) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as NamedPlayer + + return uuid == other.uuid + } + + override fun hashCode(): Int { + return uuid.hashCode() + } + + override fun toString(): String { + return "NamedPlayer(uuid=$uuid, name='$name')" + } + + public operator fun component0(): UniqueId = uuid + public operator fun component1(): String = name +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/TrustLevelId.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/TrustLevelId.kt new file mode 100644 index 0000000..02b2a89 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/TrustLevelId.kt @@ -0,0 +1,19 @@ +package br.com.gamemods.minecity.api.id + +import br.com.gamemods.minecity.api.serializer.UniqueId +import kotlinx.serialization.Serializable +import java.util.* + +/** + * UUID identifier that identifies a custom trust level registered on MineCity + */ +@Serializable +@JvmInline +public value class TrustLevelId(public val uuid: UniqueId): Comparable { + public constructor(): this(UUID.randomUUID()) + override fun compareTo(other: TrustLevelId): Int = uuid.compareTo(other.uuid) + override fun toString(): String = uuid.toString() + public companion object { + public val EVERYONE: TrustLevelId = TrustLevelId(UniqueId(0L, 0L)) + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/VirtualCurrencyId.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/VirtualCurrencyId.kt new file mode 100644 index 0000000..63f4e78 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/VirtualCurrencyId.kt @@ -0,0 +1,15 @@ +package br.com.gamemods.minecity.api.id + +import br.com.gamemods.minecity.api.annotation.PlatformDependent +import kotlinx.serialization.Serializable + +/** + * String identifier that identifies a currency in the current platform. + */ +@Serializable +@JvmInline +public value class VirtualCurrencyId @PlatformDependent constructor(private val id: String): Comparable { + @PlatformDependent + override fun toString(): String = id + override fun compareTo(other: VirtualCurrencyId): Int = id.compareTo(other.id) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/id/WorldId.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/WorldId.kt new file mode 100644 index 0000000..29b9067 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/id/WorldId.kt @@ -0,0 +1,15 @@ +package br.com.gamemods.minecity.api.id + +import br.com.gamemods.minecity.api.annotation.PlatformDependent +import kotlinx.serialization.Serializable + +/** + * String identifier that identifies a world in the current platform. + */ +@Serializable +@JvmInline +public value class WorldId @PlatformDependent constructor(private val string: String): Comparable { + @PlatformDependent + override fun toString(): String = string + override fun compareTo(other: WorldId): Int = string.compareTo(other.string) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/VirtualMoney.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/VirtualMoney.kt new file mode 100644 index 0000000..70a96bd --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/VirtualMoney.kt @@ -0,0 +1,17 @@ +package br.com.gamemods.minecity.api.math + +import br.com.gamemods.minecity.api.id.VirtualCurrencyId +import br.com.gamemods.minecity.api.serializer.Decimal +import kotlinx.serialization.Serializable + +/** + * Represents a money amount out of a virtual currency from an economy plugin/mod. + * + * @property currency The currency id of this amount. + * @property quantity The amount of [currency], can be fractional. + */ +@Serializable +public data class VirtualMoney( + val currency: VirtualCurrencyId, + val quantity: Decimal, +) diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockGridPositioned.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockGridPositioned.kt new file mode 100644 index 0000000..affccad --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockGridPositioned.kt @@ -0,0 +1,16 @@ +package br.com.gamemods.minecity.api.math.pos + +/** + * Something that exists on the Minecraft block grid. + */ +public interface BlockGridPositioned { + /** + * The [BlockPosition] where this object is located. + */ + public fun toBlockPos(): BlockPosition + + /** + * The [ChunkPosition] where this object is located. + */ + public fun toChunkPos(): ChunkPosition = toBlockPos().toChunkPos() +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockGridPositionedLocation.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockGridPositionedLocation.kt new file mode 100644 index 0000000..d5bf79b --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockGridPositionedLocation.kt @@ -0,0 +1,8 @@ +package br.com.gamemods.minecity.api.math.pos + +/** + * Something that exists on the Minecraft block grid and is possible to determine the WorldID + */ +public interface BlockGridPositionedLocation: BlockGridPositioned, Location { + override fun toBlockPos(): BlockLocation +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockLocation.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockLocation.kt new file mode 100644 index 0000000..d7c895a --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockLocation.kt @@ -0,0 +1,70 @@ +package br.com.gamemods.minecity.api.math.pos + +import br.com.gamemods.minecity.api.annotation.PlatformDependent +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure + +/** + * A readonly XYZ position and WorldId of a block. + */ +@Serializable(with = BlockLocation.Serializer::class) +public interface BlockLocation: BlockPosition, BlockGridPositionedLocation { + override fun toBlockPos(): BlockLocation = this + + public companion object { + @set:InternalMineCityApi + public lateinit var constructor: (worldId: WorldId, x: Int, y: Int, z: Int) -> BlockLocation + + @JvmName("get") + public operator fun invoke(worldId: WorldId, x: Int, y: Int, z: Int): BlockLocation = constructor(worldId, x, y, z) + } + + public object Serializer: KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("br.com.gamemods.minecity.api.math.BlockLocation") { + element("x") + element("y") + element("z") + element("world") + } + + @OptIn(PlatformDependent::class) + override fun deserialize(decoder: Decoder): BlockLocation { + return decoder.decodeStructure(descriptor) { + var x = 0 + var y = 0 + var z = 0 + var world = "" + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> x = decodeIntElement(descriptor, 0) + 1 -> y = decodeIntElement(descriptor, 1) + 2 -> z = decodeIntElement(descriptor, 2) + 3 -> world = decodeStringElement(descriptor, 3) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + BlockLocation(WorldId(world), x, y, z) + } + } + + @OptIn(PlatformDependent::class) + override fun serialize(encoder: Encoder, value: BlockLocation) { + val composite = encoder.beginStructure(descriptor) + composite.encodeIntElement(descriptor, 0, value.x) + composite.encodeIntElement(descriptor, 1, value.y) + composite.encodeIntElement(descriptor, 2, value.z) + composite.encodeStringElement(descriptor, 3, value.worldId.toString()) + composite.endStructure(descriptor) + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockPosition.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockPosition.kt new file mode 100644 index 0000000..38c1817 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/BlockPosition.kt @@ -0,0 +1,66 @@ +package br.com.gamemods.minecity.api.math.pos + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure + +/** + * A readonly XYZ position of a bock. + */ +@Serializable(with = BlockPosition.Serializer::class) +public interface BlockPosition: BlockGridPositioned { + public val x: Int + public val y: Int + public val z: Int + + override fun toBlockPos(): BlockPosition = this + + public companion object { + @set:InternalMineCityApi + public lateinit var constructor: (x: Int, y: Int, z: Int) -> BlockPosition + + @JvmName("get") + public operator fun invoke(x: Int, y: Int, z: Int): BlockPosition = constructor(x, y, z) + } + + public object Serializer: KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("br.com.gamemods.minecity.api.math.BlockPosition") { + element("x") + element("y") + element("z") + } + + override fun deserialize(decoder: Decoder): BlockPosition { + return decoder.decodeStructure(descriptor) { + var x = 0 + var y = 0 + var z = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> x = decodeIntElement(descriptor, 0) + 1 -> y = decodeIntElement(descriptor, 1) + 2 -> z = decodeIntElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + BlockPosition(x, y, z) + } + } + + override fun serialize(encoder: Encoder, value: BlockPosition) { + val composite = encoder.beginStructure(descriptor) + composite.encodeIntElement(descriptor, 0, value.x) + composite.encodeIntElement(descriptor, 1, value.y) + composite.encodeIntElement(descriptor, 2, value.z) + composite.endStructure(descriptor) + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/ChunkLocation.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/ChunkLocation.kt new file mode 100644 index 0000000..516826e --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/ChunkLocation.kt @@ -0,0 +1,64 @@ +package br.com.gamemods.minecity.api.math.pos + +import br.com.gamemods.minecity.api.annotation.PlatformDependent +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure + +/** + * A readonly XZ position and WorldID of a chunk on the chunk grid. + */ +@Serializable(with = ChunkLocation.Serializer::class) +public interface ChunkLocation: ChunkPosition, Location { + public companion object { + @set:InternalMineCityApi + public lateinit var constructor: (worldId: WorldId, x: Int, z: Int) -> ChunkLocation + + @JvmName("get") + public operator fun invoke(worldId: WorldId, x: Int, z: Int): ChunkLocation = constructor(worldId, x, z) + } + + public object Serializer: KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("br.com.gamemods.minecity.api.math.ChunkLocation") { + element("x") + element("z") + element("worldId") + } + + @OptIn(PlatformDependent::class) + override fun deserialize(decoder: Decoder): ChunkLocation { + return decoder.decodeStructure(descriptor) { + var x = 0 + var z = 0 + var worldId = "" + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> x = decodeIntElement(descriptor, 0) + 1 -> z = decodeIntElement(descriptor, 1) + 2 -> worldId = decodeStringElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + ChunkLocation(WorldId(worldId), x, z) + } + } + + @OptIn(PlatformDependent::class) + override fun serialize(encoder: Encoder, value: ChunkLocation) { + val composite = encoder.beginStructure(descriptor) + composite.encodeIntElement(descriptor, 0, value.x) + composite.encodeIntElement(descriptor, 1, value.z) + composite.encodeStringElement(descriptor, 2, value.worldId.toString()) + composite.endStructure(descriptor) + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/ChunkPosition.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/ChunkPosition.kt new file mode 100644 index 0000000..e4ac007 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/ChunkPosition.kt @@ -0,0 +1,65 @@ +package br.com.gamemods.minecity.api.math.pos + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure + +/** + * A readonly XZ position of a chunk on the chunk grid. + */ +@Serializable(with = ChunkPosition.Serializer::class) +public interface ChunkPosition { + public val x: Int + public val z: Int + + public val minBlock: BlockPosition + public val maxBlock: BlockPosition + public val xBlockRange: IntRange + public val zBlockRange: IntRange + + + public companion object { + @set:InternalMineCityApi + public lateinit var constructor: (x: Int, z: Int) -> ChunkPosition + + @JvmName("get") + public operator fun invoke(x: Int, z: Int): ChunkPosition = constructor(x, z) + } + + public object Serializer: KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("br.com.gamemods.minecity.api.math.ChunkPosition") { + element("x") + element("z") + } + + override fun deserialize(decoder: Decoder): ChunkPosition { + return decoder.decodeStructure(descriptor) { + var x = 0 + var z = 0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> x = decodeIntElement(descriptor, 0) + 1 -> z = decodeIntElement(descriptor, 1) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + ChunkPosition(x, z) + } + } + + override fun serialize(encoder: Encoder, value: ChunkPosition) { + val composite = encoder.beginStructure(descriptor) + composite.encodeIntElement(descriptor, 0, value.x) + composite.encodeIntElement(descriptor, 1, value.z) + composite.endStructure(descriptor) + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/EntityLocation.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/EntityLocation.kt new file mode 100644 index 0000000..9660d02 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/EntityLocation.kt @@ -0,0 +1,74 @@ +package br.com.gamemods.minecity.api.math.pos + +import br.com.gamemods.minecity.api.annotation.PlatformDependent +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure + +/** + * A readonly XYZ position and WorldId of an entity. + */ +@Serializable(with = EntityLocation.Serializer::class) +public interface EntityLocation: EntityPosition, BlockGridPositionedLocation { + override fun toBlockPos(): BlockLocation = BlockLocation(worldId, x.toInt(), y.toInt(), z.toInt()) + + public companion object { + @set:InternalMineCityApi + public lateinit var constructor: (worldId: WorldId, x: Double, y: Double, z: Double) -> EntityLocation + + @JvmName("get") + public operator fun invoke(worldId: WorldId, x: Double, y: Double, z: Double): EntityLocation = constructor(worldId, x, y, z) + @JvmName("get") + public operator fun invoke(worldId: WorldId, x: Float, y: Float, z: Float): EntityLocation = this(worldId, x.toDouble(), y.toDouble(), z.toDouble()) + @JvmName("get") + public operator fun invoke(worldId: WorldId, x: Int, y: Int, z: Int): EntityLocation = this(worldId, x.toDouble(), y.toDouble(), z.toDouble()) + } + + public object Serializer: KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("br.com.gamemods.minecity.api.math.EntityLocation") { + element("x") + element("y") + element("z") + element("worldId") + } + + @OptIn(PlatformDependent::class) + override fun deserialize(decoder: Decoder): EntityLocation { + return decoder.decodeStructure(descriptor) { + var x = 0.0 + var y = 0.0 + var z = 0.0 + var worldId = "" + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> x = decodeDoubleElement(descriptor, 0) + 1 -> y = decodeDoubleElement(descriptor, 1) + 2 -> z = decodeDoubleElement(descriptor, 2) + 3 -> worldId = decodeStringElement(descriptor, 3) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + EntityLocation(WorldId(worldId), x, y, z) + } + } + + @OptIn(PlatformDependent::class) + override fun serialize(encoder: Encoder, value: EntityLocation) { + val composite = encoder.beginStructure(descriptor) + composite.encodeDoubleElement(descriptor, 0, value.x) + composite.encodeDoubleElement(descriptor, 1, value.y) + composite.encodeDoubleElement(descriptor, 2, value.z) + composite.encodeStringElement(descriptor, 3, value.worldId.toString()) + composite.endStructure(descriptor) + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/EntityPosition.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/EntityPosition.kt new file mode 100644 index 0000000..9a69a2b --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/EntityPosition.kt @@ -0,0 +1,70 @@ +package br.com.gamemods.minecity.api.math.pos + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure + +/** + * A readonly XYZ position of an entity. + */ +@Serializable(with = EntityPosition.Serializer::class) +public interface EntityPosition: BlockGridPositioned { + public val x: Double + public val y: Double + public val z: Double + + override fun toBlockPos(): BlockPosition = BlockPosition(x.toInt(), y.toInt(), z.toInt()) + + public companion object { + @set:InternalMineCityApi + public lateinit var constructor: (x: Double, y: Double, z: Double) -> EntityPosition + + @JvmName("get") + public operator fun invoke(x: Double, y: Double, z: Double): EntityPosition = constructor(x, y, z) + @JvmName("get") + public operator fun invoke(x: Float, y: Float, z: Float): EntityPosition = this(x.toDouble(), y.toDouble(), z.toDouble()) + @JvmName("get") + public operator fun invoke(x: Int, y: Int, z: Int): EntityPosition = this(x.toDouble(), y.toDouble(), z.toDouble()) + } + + public object Serializer: KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("br.com.gamemods.minecity.api.math.EntityPosition") { + element("x") + element("y") + element("z") + } + + override fun deserialize(decoder: Decoder): EntityPosition { + return decoder.decodeStructure(descriptor) { + var x = 0.0 + var y = 0.0 + var z = 0.0 + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> x = decodeDoubleElement(descriptor, 0) + 1 -> y = decodeDoubleElement(descriptor, 1) + 2 -> z = decodeDoubleElement(descriptor, 2) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + EntityPosition(x, y, z) + } + } + + override fun serialize(encoder: Encoder, value: EntityPosition) { + val composite = encoder.beginStructure(descriptor) + composite.encodeDoubleElement(descriptor, 0, value.x) + composite.encodeDoubleElement(descriptor, 1, value.y) + composite.encodeDoubleElement(descriptor, 2, value.z) + composite.endStructure(descriptor) + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/Location.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/Location.kt new file mode 100644 index 0000000..b565435 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/pos/Location.kt @@ -0,0 +1,12 @@ +package br.com.gamemods.minecity.api.math.pos + +import br.com.gamemods.minecity.api.id.WorldId + +/** + * An object with a location aligned to [worldId]. + * + * @property worldId The ID of the world where this location exists. + */ +public sealed interface Location { + public val worldId: WorldId +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Combined2D.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Combined2D.kt new file mode 100644 index 0000000..0f6add8 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Combined2D.kt @@ -0,0 +1,15 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * An additive combination of multiple 2D shapes + */ +@Serializable +@SerialName("combined2d") +public class Combined2D(override val components: Set): Shape2D(), CombinedShape { + override fun contains(x: Int, z: Int): Boolean = components.any { it.contains(x, z) } + override fun intersects(pos: ChunkPosition): Boolean = components.any { it.intersects(pos) } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Combined3D.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Combined3D.kt new file mode 100644 index 0000000..9d61e24 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Combined3D.kt @@ -0,0 +1,16 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * An additive combination of multiple 3D shapes + */ +@Serializable +@SerialName("combined3d") +public class Combined3D(override val components: Set): Shape3D(), CombinedShape { + override fun contains(x: Int, y: Int, z: Int): Boolean = components.any { it.contains(x, y, z) } + override fun intersects(pos: ChunkPosition): Boolean = components.any { it.intersects(pos) } + override fun to2D(y: Double): Combined2D = Combined2D(components.map { it.to2D(y) }.toSet()) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/CombinedShape.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/CombinedShape.kt new file mode 100644 index 0000000..57e446a --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/CombinedShape.kt @@ -0,0 +1,15 @@ +package br.com.gamemods.minecity.api.math.shape + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * An additive combination of multiple shapes + * + * @property components The shapes that part of the area of this shape, they don't need to connect. + */ +@Serializable +@SerialName("combined") +public sealed interface CombinedShape: Shape { + public val components: Set +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Cuboid.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Cuboid.kt new file mode 100644 index 0000000..0502795 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Cuboid.kt @@ -0,0 +1,40 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +/** + * Represents a rectangle (or square) storing the points with lowest and highest values. + */ +@Serializable +@SerialName("cuboid") +public data class Cuboid( + val minX: Int, + val minY: Int, + val minZ: Int, + val maxX: Int, + val maxY: Int, + val maxZ: Int, +): Shape3D() { + @Transient + private val xRange = minX..maxX + @Transient + private val yRange = minY..maxY + @Transient + private val zRange = minZ..maxZ + + /** + * Discards [minY] and [maxY] to create a [Rectangle] + */ + public fun toRectangle(): Rectangle = Rectangle(minX, minZ, maxX, maxZ) + + override fun to2D(y: Double): Shape2D { + return if (y.toInt() in yRange) toRectangle() + else Empty2D + } + + override fun contains(x: Int, y: Int, z: Int): Boolean = x in xRange && y in yRange && z in zRange + override fun intersects(pos: ChunkPosition): Boolean = Rectangle.chunkIntersectLogic(xRange, zRange, pos) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Empty2D.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Empty2D.kt new file mode 100644 index 0000000..75a92df --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Empty2D.kt @@ -0,0 +1,15 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents the lack of a shape, an absense on the universe. + */ +@Serializable +@SerialName("empty2d") +public data object Empty2D: Shape2D(), EmptyShape { + override fun intersects(pos: ChunkPosition): Boolean = false + override fun contains(x: Int, z: Int): Boolean = false +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Empty3D.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Empty3D.kt new file mode 100644 index 0000000..fba3971 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Empty3D.kt @@ -0,0 +1,16 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents the lack of a shape, an absense on the universe. + */ +@Serializable +@SerialName("empty3d") +public data object Empty3D: Shape3D(), EmptyShape { + override fun to2D(y: Double): Empty2D = Empty2D + override fun intersects(pos: ChunkPosition): Boolean = false + override fun contains(x: Int, y: Int, z: Int): Boolean = false +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/EmptyShape.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/EmptyShape.kt new file mode 100644 index 0000000..39d692c --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/EmptyShape.kt @@ -0,0 +1,11 @@ +package br.com.gamemods.minecity.api.math.shape + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents the lack of a shape, an absense on the universe. + */ +@Serializable +@SerialName("empty") +public sealed interface EmptyShape: Shape diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Rectangle.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Rectangle.kt new file mode 100644 index 0000000..457f50a --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Rectangle.kt @@ -0,0 +1,60 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +/** + * Represents a rectangle (or square) storing the points with lowest and highest values. + */ +@Serializable +@SerialName("rectangle") +public data class Rectangle( + val minX: Int, + val minZ: Int, + val maxX: Int, + val maxZ: Int, +): Shape2D() { + @Transient + public val xRange: IntRange = minX..maxX + @Transient + public val zRange: IntRange = minZ..maxZ + override fun contains(x: Int, z: Int): Boolean = x in xRange && z in zRange + override fun intersects(pos: ChunkPosition): Boolean = chunkIntersectLogic(xRange, zRange, pos) + + /** + * Creates a [Cuboid] using the data from this rectangle added with [minY] and [maxY]. + */ + public fun to3D(minY: Int = Int.MIN_VALUE, maxY: Int = Int.MAX_VALUE): Cuboid = Cuboid(minX, minY, minZ, maxX, maxY, maxZ) + + internal companion object { + internal fun chunkIntersectLogic(xRange: IntRange, zRange: IntRange, pos: ChunkPosition): Boolean { + val minX = xRange.first + val maxX = xRange.last + val minZ = zRange.first + val maxZ = zRange.last + + val chunkMinX = pos.minBlock.x + val chunkMinZ = pos.minBlock.z + val chunkMaxX = pos.maxBlock.x + val chunkMaxZ = pos.maxBlock.z + + // Create ranges for chunk x and z coordinates + val chunkXRange = chunkMinX..chunkMaxX + val chunkZRange = chunkMinZ..chunkMaxZ + + // Check if any of the rectangle's corners are inside the chunk + if ((minX in chunkXRange && minZ in chunkZRange) || // Bottom left corner + (minX in chunkXRange && maxZ in chunkZRange) || // Top left corner + (maxX in chunkXRange && minZ in chunkZRange) || // Bottom right corner + (maxX in chunkXRange && maxZ in chunkZRange) // Top right corner + ) { + return true + } + + // Check if the chunk is completely inside the rectangle + return minX <= chunkMinX && maxX >= chunkMaxX && minZ <= chunkMinZ && maxZ >= chunkMaxZ + } + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape.kt new file mode 100644 index 0000000..e97e398 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape.kt @@ -0,0 +1,36 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.BlockGridPositioned +import br.com.gamemods.minecity.api.math.pos.BlockPosition +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import kotlinx.serialization.Serializable + +/** + * A geometric shape with unknown amount of dimensions + */ +@Serializable +public sealed interface Shape { + /** + * Removes the Y coordinate, getting the equivalent [Shape2D]. + * + * Returns itself if this is already a [Shape2D]. + * + * @param y The y coordinate to calculate the 2D slice on a 3D environment. + */ + public fun to2D(y: Double): Shape2D + + /** + * Checks if the given chunk coordinate intersects with this shape. + */ + public fun intersects(pos: ChunkPosition): Boolean + + /** + * Checks if the given 2D point is part of the area of this shape. + */ + public operator fun contains(pos: BlockPosition): Boolean + + /** + * Checks if the given 2D point is part of the area of this shape. + */ + public operator fun contains(pos: BlockGridPositioned): Boolean = contains(pos.toBlockPos()) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape2D.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape2D.kt new file mode 100644 index 0000000..ca6db06 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape2D.kt @@ -0,0 +1,30 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.BlockPosition +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A 2D geometric shape for X and Z coordinates. + * + * Y coordinates are ignored when using a 3D positions. + */ +@Serializable +@SerialName("2d") +public sealed class Shape2D: Shape { + /** + * Returns itself. + */ + @Deprecated("2D shapes always returns itself on to2D()", ReplaceWith("")) + final override fun to2D(y: Double): Shape2D = this + + /** + * Checks if the given 2D point is part of the area of this shape. + */ + public abstract fun contains(x: Int, z: Int): Boolean + + /** + * Checks if the given position is part of the area of this shape. + */ + final override fun contains(pos: BlockPosition): Boolean = contains(pos.x, pos.z) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape3D.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape3D.kt new file mode 100644 index 0000000..65d58a5 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/math/shape/Shape3D.kt @@ -0,0 +1,22 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.BlockPosition +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A 3D geometric shape for X, Y, and Z coordinates. + */ +@Serializable +@SerialName("3d") +public sealed class Shape3D: Shape { + /** + * Checks if the given 3D point is part of the area of this shape. + */ + public abstract fun contains(x: Int, y: Int, z: Int): Boolean + + /** + * Checks if the given position is part of the area of this shape. + */ + override fun contains(pos: BlockPosition): Boolean = contains(pos.x, pos.y, pos.z) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/BigDecimalSerializer.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/BigDecimalSerializer.kt new file mode 100644 index 0000000..93383a6 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/BigDecimalSerializer.kt @@ -0,0 +1,24 @@ +package br.com.gamemods.minecity.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +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.math.BigDecimal + +/** + * [BigDecimal] configured to be serialized with [BigDecimalSerializer] + */ +public typealias Decimal = @Serializable(with = BigDecimalSerializer::class) BigDecimal + +/** + * Serializes [BigDecimal] as string using [BigDecimal.toPlainString] + */ +public object BigDecimalSerializer: KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.math.BigDecimal", PrimitiveKind.STRING) + override fun deserialize(decoder: Decoder): BigDecimal = BigDecimal(decoder.decodeString()) + override fun serialize(encoder: Encoder, value: BigDecimal): Unit = encoder.encodeString(value.toPlainString()) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/MiniMessageSerializer.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/MiniMessageSerializer.kt new file mode 100644 index 0000000..5be23a2 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/MiniMessageSerializer.kt @@ -0,0 +1,31 @@ +package br.com.gamemods.minecity.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +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 net.kyori.adventure.text.Component +import net.kyori.adventure.text.minimessage.MiniMessage + +/** + * [Component] configured to be serialized using [MiniMessageSerializer] + */ +public typealias MiniComponent = @Serializable(with = MiniMessageSerializer::class) Component + +/** + * Serializes [Component] as string using the Adventure's mini message format. + */ +public object MiniMessageSerializer: KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("br.com.gamemods.minecity.api.serializer.MiniMessage", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Component { + return MiniMessage.miniMessage().deserialize(decoder.decodeString()) + } + + override fun serialize(encoder: Encoder, value: Component) { + encoder.encodeString(MiniMessage.miniMessage().serialize(value)) + } +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/UUIDSerializer.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/UUIDSerializer.kt new file mode 100644 index 0000000..1b553fb --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/serializer/UUIDSerializer.kt @@ -0,0 +1,24 @@ +package br.com.gamemods.minecity.api.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +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] configured to be serialized with [UUIDSerializer] + */ +public typealias UniqueId = @Serializable(with = UUIDSerializer::class) UUID + +/** + * Serializes UUID as string. + */ +public object UUIDSerializer: KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.util.UUID", PrimitiveKind.STRING) + override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString()) + override fun serialize(encoder: Encoder, value: UUID): Unit = encoder.encodeString(value.toString()) +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/server/MineCityServer.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/server/MineCityServer.kt new file mode 100644 index 0000000..d51ad8c --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/server/MineCityServer.kt @@ -0,0 +1,18 @@ +package br.com.gamemods.minecity.api.server + +import br.com.gamemods.minecity.api.MineCityPlatform +import kotlinx.coroutines.CoroutineScope + +/** + * Server side instance that handles all server features + * + * @property platform The platform where MineCity is running + */ +public interface MineCityServer: CoroutineScope { + public val platform: MineCityPlatform + + /** + * Checks if the current thread is the main thread + */ + public fun isSync(): Boolean +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/service/claim/ClaimService.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/service/claim/ClaimService.kt new file mode 100644 index 0000000..e26ee01 --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/service/claim/ClaimService.kt @@ -0,0 +1,41 @@ +package br.com.gamemods.minecity.api.service.claim + +import br.com.gamemods.minecity.api.annotation.threading.SyncFriendly +import br.com.gamemods.minecity.api.claim.Claim +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.pos.BlockGridPositioned +import br.com.gamemods.minecity.api.math.pos.BlockGridPositionedLocation +import kotlinx.coroutines.Deferred + +/** + * Manages claim loading, creation, destruction, modifications... + */ +public interface ClaimService { + /** + * Gets a loaded claim for the given position. + */ + @SyncFriendly + public operator fun get(worldId: WorldId, pos: BlockGridPositioned): Claim? + + /** + * Gets a loaded claim for the given position. + */ + @SyncFriendly + public operator fun get(pos: BlockGridPositionedLocation): Claim? = get(pos.worldId, pos) + /////////////////////////////// + /** + * Gets or load a claim for the given position. + */ + public fun load(worldId: WorldId, pos: BlockGridPositioned): Deferred + /** + * Gets or load a claim for the given position. + */ + public fun load(pos: BlockGridPositionedLocation): Deferred = load(pos.worldId, pos) + /////////////////////////////// + /** + * Creates a new claim. + * + * @return The current claim state, can be different from the one received on [claim]. + */ + public fun create(claim: Claim): Deferred +} diff --git a/api/src/main/kotlin/br/com/gamemods/minecity/api/service/namedplayer/NamedPlayerService.kt b/api/src/main/kotlin/br/com/gamemods/minecity/api/service/namedplayer/NamedPlayerService.kt new file mode 100644 index 0000000..8b3b2ef --- /dev/null +++ b/api/src/main/kotlin/br/com/gamemods/minecity/api/service/namedplayer/NamedPlayerService.kt @@ -0,0 +1,33 @@ +package br.com.gamemods.minecity.api.service.namedplayer + +import br.com.gamemods.minecity.api.annotation.threading.SyncFriendly +import br.com.gamemods.minecity.api.id.NamedPlayer +import kotlinx.coroutines.Deferred +import java.util.* + +/** + * Manages the [NamedPlayer] objects. + */ +public interface NamedPlayerService { + /** + * Gets [NamedPlayer] for the given player [uuid], attempts to use a cached value before starting a query. + */ + public operator fun get(uuid: UUID): Deferred + + /** + * Gets [NamedPlayer] for the given player [name], attempts to use a cached value before starting a query. + */ + public operator fun get(name: String): Deferred + + /** + * Checks if the given player [uuid] has ready-to-use cached value. + */ + @SyncFriendly + public operator fun contains(uuid: UUID): Boolean + + /** + * Checks if the given player [name] has ready-to-use cached value. + */ + @SyncFriendly + public operator fun contains(name: String): Boolean +} diff --git a/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/RectangleTest.kt b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/RectangleTest.kt new file mode 100644 index 0000000..df11b43 --- /dev/null +++ b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/RectangleTest.kt @@ -0,0 +1,43 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import org.junit.jupiter.api.BeforeAll +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class RectangleTest { + companion object { + @BeforeAll + @JvmStatic + fun setup() { + TestEnv.setupPositions() + } + } + + @Test + fun contains() { + val rectangle = Rectangle(0, 0, 10, 10) + assertTrue(rectangle.contains(0, 0)) + assertTrue(rectangle.contains(10, 10)) + assertTrue(rectangle.contains(5, 5)) + assertFalse(rectangle.contains(-1, 0)) + assertFalse(rectangle.contains(-10, 0)) + assertFalse(rectangle.contains(0, -1)) + assertFalse(rectangle.contains(0, -10)) + assertFalse(rectangle.contains(11, 0)) + assertFalse(rectangle.contains(0, 11)) + } + + @Test + fun intersects() { + val rectangle1 = Rectangle(5, 5, 10, 10) + val rectangle2 = Rectangle(5, 5, 17, 10) + val chunk1 = ChunkPosition(0, 0) + val chunk2 = ChunkPosition(1, 0) + assertTrue(rectangle1.intersects(chunk1)) + assertFalse(rectangle1.intersects(chunk2)) + assertTrue(rectangle2.intersects(chunk1)) + assertTrue(rectangle2.intersects(chunk2)) + } +} diff --git a/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestBlockLocation.kt b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestBlockLocation.kt new file mode 100644 index 0000000..eb5ec5f --- /dev/null +++ b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestBlockLocation.kt @@ -0,0 +1,11 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.pos.BlockLocation +import br.com.gamemods.minecity.api.math.pos.BlockPosition + +data class TestBlockLocation(override val worldId: WorldId, val pos: TestBlockPosition): BlockLocation, BlockPosition by pos { + constructor(worldId: WorldId, x: Int, y: Int, z: Int): this(worldId, TestBlockPosition(x, y, z)) + + override fun toBlockPos(): BlockLocation = this +} diff --git a/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestBlockPosition.kt b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestBlockPosition.kt new file mode 100644 index 0000000..4a3fa8e --- /dev/null +++ b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestBlockPosition.kt @@ -0,0 +1,5 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.BlockPosition + +data class TestBlockPosition(override val x: Int, override val y: Int, override val z: Int): BlockPosition diff --git a/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestChunkLocation.kt b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestChunkLocation.kt new file mode 100644 index 0000000..53f98f8 --- /dev/null +++ b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestChunkLocation.kt @@ -0,0 +1,10 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.pos.ChunkLocation +import br.com.gamemods.minecity.api.math.pos.ChunkPosition + +@Suppress("unused") +data class TestChunkLocation(override val worldId: WorldId, val pos: TestChunkPosition): ChunkLocation, ChunkPosition by pos { + constructor(worldId: WorldId, x: Int, z: Int): this(worldId, TestChunkPosition(x, z)) +} diff --git a/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestChunkPosition.kt b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestChunkPosition.kt new file mode 100644 index 0000000..983e385 --- /dev/null +++ b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestChunkPosition.kt @@ -0,0 +1,11 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.BlockPosition +import br.com.gamemods.minecity.api.math.pos.ChunkPosition + +data class TestChunkPosition(override val x: Int, override val z: Int): ChunkPosition { + override val minBlock: BlockPosition get() = TestBlockPosition(x shl 4, Int.MIN_VALUE, z shl 4) + override val maxBlock: BlockPosition get() = TestBlockPosition((x shl 4) + 15, Int.MAX_VALUE, (z shl 4) + 15) + override val xBlockRange: IntRange get() = (x shl 4)..((x shl 4) + 15) + override val zBlockRange: IntRange get() = (z shl 4)..((z shl 4) + 15) +} diff --git a/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEntityLocation.kt b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEntityLocation.kt new file mode 100644 index 0000000..80c0ca7 --- /dev/null +++ b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEntityLocation.kt @@ -0,0 +1,15 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.pos.BlockLocation +import br.com.gamemods.minecity.api.math.pos.EntityLocation +import br.com.gamemods.minecity.api.math.pos.EntityPosition + +@Suppress("unused") +data class TestEntityLocation(override val worldId: WorldId, val pos: TestEntityPosition): EntityLocation, EntityPosition by pos { + constructor(worldId: WorldId, x: Double, y: Double, z: Double): this(worldId, TestEntityPosition(x, y, z)) + + override fun toBlockPos(): BlockLocation { + return super.toBlockPos() + } +} diff --git a/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEntityPosition.kt b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEntityPosition.kt new file mode 100644 index 0000000..a9cbd81 --- /dev/null +++ b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEntityPosition.kt @@ -0,0 +1,5 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.math.pos.EntityPosition + +data class TestEntityPosition(override val x: Double, override val y: Double, override val z: Double): EntityPosition diff --git a/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEnv.kt b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEnv.kt new file mode 100644 index 0000000..c5d76cd --- /dev/null +++ b/api/src/test/kotlin/br/com/gamemods/minecity/api/math/shape/TestEnv.kt @@ -0,0 +1,17 @@ +package br.com.gamemods.minecity.api.math.shape + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.BlockLocation +import br.com.gamemods.minecity.api.math.pos.BlockPosition +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import br.com.gamemods.minecity.api.math.pos.EntityPosition + +object TestEnv { + @OptIn(InternalMineCityApi::class) + fun setupPositions() { + BlockPosition.constructor = ::TestBlockPosition + BlockLocation.constructor = ::TestBlockLocation + ChunkPosition.constructor = ::TestChunkPosition + EntityPosition.constructor = ::TestEntityPosition + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 77c7639..1502750 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,97 +1,14 @@ plugins { - id("fabric-loom") version "1.4-SNAPSHOT" id("maven-publish") - id("org.jetbrains.kotlin.jvm") version "1.9.21" + kotlin("jvm") version "1.9.21" apply false + kotlin("plugin.serialization") version "1.9.21" apply false } -version = project.findProperty("mod_version") as String -group = project.findProperty("maven_group") as String +allprojects { + version = "2.0.0-SNAPSHOT" + group = "br.com.gamemods.minecity" -base { - archivesName.set(project.findProperty("archives_base_name") as String) -} - -repositories { - // Add repositories to retrieve artifacts from in here. - // You should only use this when depending on other mods because - // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. - // See https://docs.gradle.org/current/userguide/declaring_repositories.html - // for more information about repositories. -} - -loom { - splitEnvironmentSourceSets() - - mods { - mods.create("minecity") { - sourceSet(sourceSets.main.get()) - sourceSet(sourceSets.getByName("client")) - } - } -} - -dependencies { - // To change the versions see the gradle.properties file - minecraft("com.mojang:minecraft:${project.findProperty("minecraft_version")}") - mappings("net.fabricmc:yarn:${project.findProperty("yarn_mappings")}:v2") - modImplementation("net.fabricmc:fabric-loader:${project.findProperty("loader_version")}") - - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation("net.fabricmc.fabric-api:fabric-api:${project.findProperty("fabric_version")}") - modImplementation("net.fabricmc:fabric-language-kotlin:${project.findProperty("fabric_kotlin_version")}") - // Uncomment the following line to enable the deprecated Fabric API modules. - // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time. - - // modImplementation("net.fabricmc.fabric-api:fabric-api-deprecated:${project.findProperty("fabric_version")}") -} - -tasks.processResources { - inputs.property("version", project.version) - - filesMatching("fabric.mod.json") { - expand("version" to project.version) - } -} - -tasks.withType { - options.release.set(17) -} - -tasks.withType { - kotlinOptions { - jvmTarget = "17" - } -} - -java { - // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task - // if it is present. - // If you remove this line, sources will not be generated. - withSourcesJar() - - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -tasks.jar { - from("LICENSE") { - rename { "${it}_${project.base.archivesName.get()}"} - } -} - -// configure the maven publication -publishing { - publications { - create("mavenJava") { - from(components["java"]) - } - } - - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. repositories { - // Add repositories to publish to here. - // Notice: This block does NOT have the same function as the block in the top level. - // The repositories here will be used for publishing your artifact, not for - // retrieving dependencies. + mavenCentral() } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 0000000..43b5e11 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("maven-publish") + kotlin("jvm") + kotlin("plugin.serialization") +} + +val inlineLoggerVersion: String by rootProject + +dependencies { + api(project(":api")) + compileOnly("com.michael-bull.kotlin-inline-logger:kotlin-inline-logger:$inlineLoggerVersion") +} diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/MineCityCore.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/MineCityCore.kt new file mode 100644 index 0000000..faee2b5 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/MineCityCore.kt @@ -0,0 +1,64 @@ +package br.com.gamemods.minecity.core + +import br.com.gamemods.minecity.api.MineCity +import br.com.gamemods.minecity.api.MineCityPlatform +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.annotation.side.ServerSideOnly +import br.com.gamemods.minecity.api.service.namedplayer.NamedPlayerService +import br.com.gamemods.minecity.core.service.world.WorldService +import com.github.michaelbull.logging.InlineLogger + +/** + * Implements the main functionalities of MineCity in a platform independent way. + * + * @property log The default plugin logger, wrapped by an [InlineLogger] + * @property platform The MineCity platform implementations + * @property worlds Allows access to Minecraft worlds. + */ +@InternalMineCityApi +class MineCityCore( + inline val log: InlineLogger, + override val platform: MineCityPlatform, + val worlds: WorldService, + override val players: NamedPlayerService, +): MineCity { + + /** + * Executed when Minecraft is initializing + */ + fun onInitialize() { + log.info { "MineCityCore is initializing..." } + } + + /** + * Executed when Minecraft is loading a server environment to host + */ + @ServerSideOnly + fun onServerStarting() { + log.info { "MineCityCore: Server is starting" } + } + + /** + * Executed when Minecraft server is ready to do the first server side tick. + */ + @ServerSideOnly + fun onServerStarted() { + log.info { "MineCityCore started" } + } + + /** + * Executed when Minecraft server started the shutdown procedures. + */ + @ServerSideOnly + fun onServerStopping() { + log.info { "MineCityCore: Server is stopping..." } + } + + /** + * Executed when Minecraft server has been shutdown and is about to be destructed. + */ + @ServerSideOnly + fun onServerStopped() { + log.info { "MineCityCore: Server has stopped..." } + } +} diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/dispatchers/AsyncDispatcher.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/dispatchers/AsyncDispatcher.kt new file mode 100644 index 0000000..4f83807 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/dispatchers/AsyncDispatcher.kt @@ -0,0 +1,17 @@ +package br.com.gamemods.minecity.core.dispatchers + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +@JvmSynthetic +@PublishedApi +internal lateinit var asyncDispatcher: CoroutineDispatcher + +@Suppress("UnusedReceiverParameter") +@InternalMineCityApi +inline var Dispatchers.Async: CoroutineDispatcher + get() = asyncDispatcher + set(value) { + asyncDispatcher = value + } diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/dispatchers/SyncDispatcher.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/dispatchers/SyncDispatcher.kt new file mode 100644 index 0000000..8d62e0a --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/dispatchers/SyncDispatcher.kt @@ -0,0 +1,17 @@ +package br.com.gamemods.minecity.core.dispatchers + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +@JvmSynthetic +@PublishedApi +internal lateinit var syncDispatcher: CoroutineDispatcher + +@Suppress("UnusedReceiverParameter") +@InternalMineCityApi +inline var Dispatchers.Sync: CoroutineDispatcher + get() = syncDispatcher + set(value) { + syncDispatcher = value + } diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/service/world/WorldService.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/service/world/WorldService.kt new file mode 100644 index 0000000..9bf7ead --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/service/world/WorldService.kt @@ -0,0 +1,19 @@ +package br.com.gamemods.minecity.core.service.world + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.core.wrapper.server.WorldWrapper +import kotlinx.coroutines.Deferred + +/** + * Allows access to Minecraft worlds. + */ +@InternalMineCityApi +interface WorldService { + /** + * Gets the world by its id. + * + * Runs immediately when sync. + */ + operator fun get(worldId: WorldId): Deferred +} diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/BlockPositionWrapper.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/BlockPositionWrapper.kt new file mode 100644 index 0000000..7538129 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/BlockPositionWrapper.kt @@ -0,0 +1,10 @@ +package br.com.gamemods.minecity.core.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.BlockPosition + +/** + * Wraps a native BlockPos object and gives access to its features + */ +@InternalMineCityApi +interface BlockPositionWrapper: Wrapper, BlockPosition diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/ChunkPositionWrapper.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/ChunkPositionWrapper.kt new file mode 100644 index 0000000..cd9b5e5 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/ChunkPositionWrapper.kt @@ -0,0 +1,10 @@ +package br.com.gamemods.minecity.core.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.ChunkPosition + +/** + * Wraps a native ChunkPos object and gives access to its features + */ +@InternalMineCityApi +interface ChunkPositionWrapper: Wrapper, ChunkPosition diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/EntityPositionWrapper.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/EntityPositionWrapper.kt new file mode 100644 index 0000000..d6d534f --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/EntityPositionWrapper.kt @@ -0,0 +1,10 @@ +package br.com.gamemods.minecity.core.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.EntityPosition + +/** + * Wraps a native EntityPos object and gives access to its features + */ +@InternalMineCityApi +interface EntityPositionWrapper: Wrapper, EntityPosition diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/Wrapper.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/Wrapper.kt new file mode 100644 index 0000000..e3d9647 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/Wrapper.kt @@ -0,0 +1,26 @@ +package br.com.gamemods.minecity.core.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi + +/** + * Wraps a value, should be used only on `value class`es. + * + * @property native The native object that is being wrapped + */ +@InternalMineCityApi +interface Wrapper { + val native: Any + + /** + * Adds a shortcut to wrap an object to a wrapper. + * + * @property O The class that is being wrapped by [W] + * @property W The class that wraps [O] + * @property constructor The constructor of [W] that receives only [O] as parameter. + * + * @property wrapper Shorthand for wrapping [O] into [W]. + */ + abstract class WrapperClass(@PublishedApi internal val constructor: (O) -> W) { + inline val O.wrapper get() = constructor(this) + } +} diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/MinecraftServerWrapper.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/MinecraftServerWrapper.kt new file mode 100644 index 0000000..e440627 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/MinecraftServerWrapper.kt @@ -0,0 +1,19 @@ +package br.com.gamemods.minecity.core.wrapper.server + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.core.wrapper.Wrapper + +/** + * Wraps a native MinecraftServer object and gives access to its features + * + * @property serverIp The IP being used to host the server + */ +@InternalMineCityApi +interface MinecraftServerWrapper: Wrapper { + val serverIp: String + + /** + * Checks if the current thread is the main thread + */ + fun isSync(): Boolean +} diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/ServerWorldWrapper.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/ServerWorldWrapper.kt new file mode 100644 index 0000000..bf1bc86 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/ServerWorldWrapper.kt @@ -0,0 +1,9 @@ +package br.com.gamemods.minecity.core.wrapper.server + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi + +/** + * Wraps a native ServerWorld object and gives access to its features. + */ +@InternalMineCityApi +interface ServerWorldWrapper: WorldWrapper diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/WorldChunkWrapper.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/WorldChunkWrapper.kt new file mode 100644 index 0000000..a5011e1 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/WorldChunkWrapper.kt @@ -0,0 +1,15 @@ +package br.com.gamemods.minecity.core.wrapper.server + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import br.com.gamemods.minecity.core.wrapper.Wrapper + +/** + * Wraps a native WorldChunk object and gives access to its features. + * + * @property pos The X and Z position of this chunk. + */ +@InternalMineCityApi +interface WorldChunkWrapper: Wrapper { + val pos: ChunkPosition +} diff --git a/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/WorldWrapper.kt b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/WorldWrapper.kt new file mode 100644 index 0000000..2a3d716 --- /dev/null +++ b/core/src/main/kotlin/br/com/gamemods/minecity/core/wrapper/server/WorldWrapper.kt @@ -0,0 +1,14 @@ +package br.com.gamemods.minecity.core.wrapper.server + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.core.wrapper.Wrapper + +/** + * Wraps a native World object and gives access to its features. + * + * @property id A string that identifies this world in this platform. + */ +@InternalMineCityApi +interface WorldWrapper: Wrapper { + val id: String +} diff --git a/gradle.properties b/gradle.properties index 966109c..8a27369 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,17 +2,10 @@ org.gradle.jvmargs=-Xmx1G org.gradle.parallel=true -# Fabric Properties -# check these on https://fabricmc.net/develop -minecraft_version=1.20.1 -yarn_mappings=1.20.1+build.10 -loader_version=0.14.24 -fabric_kotlin_version=1.10.15+kotlin.1.9.21 - -# Mod Properties -mod_version=1.0.0 -maven_group=br.com.gamemods.minecity -archives_base_name=minecity - -# Dependencies -fabric_version=0.91.0+1.20.1 \ No newline at end of file +# https://github.com/FabricMC/fabric-language-kotlin#bundled-libraries +inlineLoggerVersion=1.0.5 +kotlinVersion=1.9.21 +kotlinCoroutinesVersion=1.7.3 +kotlinSerializationVersion=1.6.1 +atomicFuVersion=0.23.0 +kotlinDatetimeVersion=0.4.1 diff --git a/platform/fabric/.gitignore b/platform/fabric/.gitignore new file mode 100644 index 0000000..7216929 --- /dev/null +++ b/platform/fabric/.gitignore @@ -0,0 +1,3 @@ +# fabric + +run/ diff --git a/platform/fabric/build.gradle.kts b/platform/fabric/build.gradle.kts new file mode 100644 index 0000000..5796dee --- /dev/null +++ b/platform/fabric/build.gradle.kts @@ -0,0 +1,102 @@ +plugins { + id("fabric-loom") version "1.4-SNAPSHOT" + id("maven-publish") + kotlin("jvm") + kotlin("plugin.serialization") +} + +// Fabric Properties +// check these on https://fabricmc.net/develop +val minecraftVersion="1.20.1" +val yarnMappings="1.20.1+build.10" +val loaderVersion="0.14.24" +val fabricKotlinVersion="1.10.15+kotlin.1.9.21" + +// Dependencies +val fabricVersion="0.91.0+1.20.1" +val inlineLoggerVersion: String by rootProject + +base { + archivesName.set("minecity-fabric") +} + +loom { + splitEnvironmentSourceSets() + + mods { + mods.create("minecity") { + sourceSet(sourceSets.main.get()) + sourceSet(sourceSets.getByName("client")) + } + } +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft("com.mojang:minecraft:$minecraftVersion") + mappings("net.fabricmc:yarn:$yarnMappings:v2") + modImplementation("net.fabricmc:fabric-loader:$loaderVersion") + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation("net.fabricmc.fabric-api:fabric-api:$fabricVersion") + modImplementation("net.fabricmc:fabric-language-kotlin:$fabricKotlinVersion") + modImplementation(include("net.kyori:adventure-platform-fabric:5.9.0")!!) + // Uncomment the following line to enable the deprecated Fabric API modules. + // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time. + + // modImplementation("net.fabricmc.fabric-api:fabric-api-deprecated:${project.findProperty("fabric_version")}") + implementation(include(project(":core"))!!) + + compileOnly("com.michael-bull.kotlin-inline-logger:kotlin-inline-logger:$inlineLoggerVersion") +} + +tasks.processResources { + inputs.property("version", project.version) + + filesMatching("fabric.mod.json") { + expand("version" to project.version) + } +} + +tasks.withType { + options.release.set(17) +} + +tasks.withType { + kotlinOptions { + jvmTarget = "17" + } +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.jar { + from("LICENSE") { + rename { "${it}_${project.base.archivesName.get()}"} + } +} + +// configure the maven publication +publishing { + publications { + create("mavenJava") { + from(components["java"]) + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} diff --git a/src/client/java/br/com/gamemods/minecity/fabric/client/mixin/ExampleClientMixin.java b/platform/fabric/src/client/java/br/com/gamemods/minecity/fabric/client/mixin/ExampleClientMixin.java similarity index 83% rename from src/client/java/br/com/gamemods/minecity/fabric/client/mixin/ExampleClientMixin.java rename to platform/fabric/src/client/java/br/com/gamemods/minecity/fabric/client/mixin/ExampleClientMixin.java index abab988..ce39176 100644 --- a/src/client/java/br/com/gamemods/minecity/fabric/client/mixin/ExampleClientMixin.java +++ b/platform/fabric/src/client/java/br/com/gamemods/minecity/fabric/client/mixin/ExampleClientMixin.java @@ -1,5 +1,6 @@ package br.com.gamemods.minecity.fabric.client.mixin; +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityMixin; import net.minecraft.client.MinecraftClient; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -7,6 +8,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(MinecraftClient.class) +@InternalMineCityMixin public class ExampleClientMixin { @Inject(at = @At("HEAD"), method = "run") private void run(CallbackInfo info) { diff --git a/platform/fabric/src/client/kotlin/br/com/gamemods/minecity/fabric/client/MineCityFabricClient.kt b/platform/fabric/src/client/kotlin/br/com/gamemods/minecity/fabric/client/MineCityFabricClient.kt new file mode 100644 index 0000000..1a7578c --- /dev/null +++ b/platform/fabric/src/client/kotlin/br/com/gamemods/minecity/fabric/client/MineCityFabricClient.kt @@ -0,0 +1,21 @@ +package br.com.gamemods.minecity.fabric.client + +import br.com.gamemods.minecity.api.MineCityPlatform +import br.com.gamemods.minecity.api.annotation.UsedByReflection +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.annotation.side.ClientSideOnly +import br.com.gamemods.minecity.api.client.MineCityClient +import br.com.gamemods.minecity.fabric.MineCityFabric +import net.fabricmc.api.ClientModInitializer + +@InternalMineCityApi +@UsedByReflection("fabric.mod.json, client entrypoint") +object MineCityFabricClient : ClientModInitializer, MineCityClient { + override val platform: MineCityPlatform get() = MineCityFabric + + @ClientSideOnly + override fun onInitializeClient() { + // This entrypoint is suitable for setting up client-specific logic, such as rendering. + MineCityFabric.client = this + } +} diff --git a/src/client/resources/minecity.client.mixins.json b/platform/fabric/src/client/resources/minecity.client.mixins.json similarity index 100% rename from src/client/resources/minecity.client.mixins.json rename to platform/fabric/src/client/resources/minecity.client.mixins.json diff --git a/src/main/java/br/com/gamemods/minecity/fabric/common/mixin/ExampleMixin.java b/platform/fabric/src/main/java/br/com/gamemods/minecity/fabric/common/mixin/ExampleMixin.java similarity index 84% rename from src/main/java/br/com/gamemods/minecity/fabric/common/mixin/ExampleMixin.java rename to platform/fabric/src/main/java/br/com/gamemods/minecity/fabric/common/mixin/ExampleMixin.java index d0a7615..c86dd45 100644 --- a/src/main/java/br/com/gamemods/minecity/fabric/common/mixin/ExampleMixin.java +++ b/platform/fabric/src/main/java/br/com/gamemods/minecity/fabric/common/mixin/ExampleMixin.java @@ -1,5 +1,6 @@ package br.com.gamemods.minecity.fabric.common.mixin; +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityMixin; import net.minecraft.server.MinecraftServer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -7,6 +8,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(MinecraftServer.class) +@InternalMineCityMixin public class ExampleMixin { @Inject(at = @At("HEAD"), method = "loadWorld") private void init(CallbackInfo info) { diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/MineCityFabric.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/MineCityFabric.kt new file mode 100644 index 0000000..998a47e --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/MineCityFabric.kt @@ -0,0 +1,118 @@ +package br.com.gamemods.minecity.fabric + +import br.com.gamemods.minecity.api.MineCity +import br.com.gamemods.minecity.api.MineCityPlatform +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.annotation.side.ServerSideOnly +import br.com.gamemods.minecity.api.client.MineCityClient +import br.com.gamemods.minecity.api.math.pos.* +import br.com.gamemods.minecity.core.MineCityCore +import br.com.gamemods.minecity.core.dispatchers.Async +import br.com.gamemods.minecity.core.dispatchers.Sync +import br.com.gamemods.minecity.fabric.math.pos.FabricBlockLocation +import br.com.gamemods.minecity.fabric.math.pos.FabricChunkLocation +import br.com.gamemods.minecity.fabric.math.pos.FabricEntityLocation +import br.com.gamemods.minecity.fabric.server.MineCityFabricServer +import br.com.gamemods.minecity.fabric.service.FabricNamedPlayerService +import br.com.gamemods.minecity.fabric.service.FabricWorldService +import br.com.gamemods.minecity.fabric.wrapper.FabricBlockPosWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricChunkPosWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricEntityPosWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricMinecraftServerWrapper.Companion.wrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricServerWorldWrapper.Companion.wrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricWorldChunkWrapper.Companion.wrapper +import com.github.michaelbull.logging.InlineLogger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher +import net.fabricmc.api.ModInitializer +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents +import net.minecraft.server.MinecraftServer +import net.minecraft.server.world.ServerWorld +import net.minecraft.world.chunk.WorldChunk + +/** + * Common MineCity Fabric mod entrypoint, also implements [MineCityPlatform]. + */ +@InternalMineCityApi +object MineCityFabric : ModInitializer, MineCityPlatform { + override lateinit var core: MineCityCore + override var client: MineCityClient? = null + override var server: MineCityFabricServer? = null; private set + private val logger = InlineLogger("minecity") + + private fun matchServer(server: MinecraftServer): MineCityFabricServer? { + return MineCityFabric.server?.takeIf { it.mcServer.native === server } + } + + @OptIn(ServerSideOnly::class) + override fun onInitialize() { + Dispatchers.Async = Dispatchers.Default + Dispatchers.Sync = Dispatchers.Main + BlockPosition.constructor = ::FabricBlockPosWrapper + BlockLocation.constructor = ::FabricBlockLocation + ChunkPosition.constructor = ::FabricChunkPosWrapper + ChunkLocation.constructor = ::FabricChunkLocation + EntityPosition.constructor = ::FabricEntityPosWrapper + EntityLocation.constructor = ::FabricEntityLocation + core = MineCityCore( + log = logger, + platform = this, + worlds = FabricWorldService(this), + players = FabricNamedPlayerService(this), + ) + MineCity.instance = core + core.onInitialize() + ServerLifecycleEvents.SERVER_STARTING.register(this::handleServerStarting) + ServerLifecycleEvents.SERVER_STARTED.register(this::handleServerStarted) + ServerLifecycleEvents.SERVER_STOPPING.register(this::handleServerStopping) + ServerLifecycleEvents.SERVER_STOPPED.register(this::handleServerStopped) + ServerChunkEvents.CHUNK_LOAD.register(this::handleChunkLoad) + ServerChunkEvents.CHUNK_UNLOAD.register(this::handleChunkUnload) + } + + @ServerSideOnly + private fun handleServerStarting(server: MinecraftServer) { + Dispatchers.Sync = server.asCoroutineDispatcher() + val serverPlatform = MineCityFabricServer(server.wrapper) + MineCityFabric.server = serverPlatform + serverPlatform.onServerStarting() + } + + @ServerSideOnly + private fun handleServerStarted(server: MinecraftServer) { + matchServer(server)?.onServerStarted() + } + + @ServerSideOnly + private fun handleServerStopping(server: MinecraftServer) { + Dispatchers.Sync = Dispatchers.Main + matchServer(server)?.onServerStopping() + } + + @ServerSideOnly + private fun handleServerStopped(server: MinecraftServer) { + matchServer(server)?.onServerStopped() + } + + @ServerSideOnly + private fun handleChunkLoad(world: ServerWorld, chunk: WorldChunk) { + matchServer(world.server)?.onChunkLoad(world.wrapper, chunk.wrapper) + } + + @ServerSideOnly + private fun handleChunkUnload(world: ServerWorld, chunk: WorldChunk) { + matchServer(world.server)?.onChunkUnload(world.wrapper, chunk.wrapper) + } + + /** + * If [MineCityFabric.server] is not null runs [server], fails otherwise. + */ + inline fun runOnServer(crossinline server: (MineCityFabricServer) -> R): R { + val mcs = MineCityFabric.server + if (mcs != null) { + return server(mcs) + } + error("No server!") + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricBlockLocation.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricBlockLocation.kt new file mode 100644 index 0000000..5d66a55 --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricBlockLocation.kt @@ -0,0 +1,17 @@ +package br.com.gamemods.minecity.fabric.math.pos + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.pos.BlockLocation +import br.com.gamemods.minecity.core.wrapper.BlockPositionWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricBlockPosWrapper + +@InternalMineCityApi +data class FabricBlockLocation( + override val worldId: WorldId, + val blockPos: FabricBlockPosWrapper +): BlockLocation, BlockPositionWrapper by blockPos { + constructor(worldId: WorldId, x: Int, y: Int, z: Int): this(worldId, FabricBlockPosWrapper(x, y, z)) + + override fun toBlockPos(): BlockLocation = this +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricChunkLocation.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricChunkLocation.kt new file mode 100644 index 0000000..d35f442 --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricChunkLocation.kt @@ -0,0 +1,15 @@ +package br.com.gamemods.minecity.fabric.math.pos + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.pos.ChunkLocation +import br.com.gamemods.minecity.core.wrapper.ChunkPositionWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricChunkPosWrapper + +@InternalMineCityApi +data class FabricChunkLocation( + override val worldId: WorldId, + val chunkPos: FabricChunkPosWrapper +): ChunkLocation, ChunkPositionWrapper by chunkPos { + constructor(worldId: WorldId, x: Int, z: Int): this(worldId, FabricChunkPosWrapper(x, z)) +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricEntityLocation.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricEntityLocation.kt new file mode 100644 index 0000000..bdc93fc --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/math/pos/FabricEntityLocation.kt @@ -0,0 +1,17 @@ +package br.com.gamemods.minecity.fabric.math.pos + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.api.math.pos.BlockLocation +import br.com.gamemods.minecity.api.math.pos.EntityLocation +import br.com.gamemods.minecity.core.wrapper.EntityPositionWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricEntityPosWrapper + +@InternalMineCityApi +data class FabricEntityLocation( + override val worldId: WorldId, + val entityPos: FabricEntityPosWrapper +): EntityLocation, EntityPositionWrapper by entityPos { + constructor(worldId: WorldId, x: Double, y: Double, z: Double): this(worldId, FabricEntityPosWrapper(x, y, z)) + override fun toBlockPos(): BlockLocation = super.toBlockPos() +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/server/MineCityFabricServer.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/server/MineCityFabricServer.kt new file mode 100644 index 0000000..3e83e4e --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/server/MineCityFabricServer.kt @@ -0,0 +1,79 @@ +package br.com.gamemods.minecity.fabric.server + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.annotation.side.ServerSideOnly +import br.com.gamemods.minecity.api.server.MineCityServer +import br.com.gamemods.minecity.core.dispatchers.Sync +import br.com.gamemods.minecity.fabric.MineCityFabric +import br.com.gamemods.minecity.fabric.wrapper.FabricMinecraftServerWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricServerWorldWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricWorldChunkWrapper +import com.github.michaelbull.logging.InlineLogger +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext + +/** + * Handle server-side procedures. + */ +@InternalMineCityApi +class MineCityFabricServer(val mcServer: FabricMinecraftServerWrapper): MineCityServer { + override val coroutineContext: CoroutineContext = Dispatchers.Sync + CoroutineName("${mcServer.serverIp} (Sync)") + private val log = InlineLogger() + override val platform: MineCityFabric get() = MineCityFabric + + @ServerSideOnly + fun onServerStarting() { + log.info { "Server is starting..." } + platform.core.onServerStarting() + } + + @ServerSideOnly + fun onServerStarted() { + log.info { "Server started" } + platform.core.onServerStarted() + } + + @ServerSideOnly + fun onServerStopping() { + log.info { "Server is stopping..." } + cancel("The server is stopping") + platform.core.onServerStopping() + } + + @ServerSideOnly + fun onServerStopped() { + log.info { "Server stopped" } + platform.core.onServerStopped() + } + + @ServerSideOnly + fun onChunkLoad(serverWorld: FabricServerWorldWrapper, worldChunk: FabricWorldChunkWrapper) { + log.trace { "Chunk loaded: ${worldChunk.pos} at ${serverWorld.id} " } + } + + @ServerSideOnly + fun onChunkUnload(serverWorld: FabricServerWorldWrapper, worldChunk: FabricWorldChunkWrapper) { + log.trace { "Chunk unloaded: ${worldChunk.pos} at ${serverWorld.id}" } + } + + override fun isSync() = mcServer.isSync() + + /** + * Runs immediately when on main thread, schedules a sync task otherwise. + */ + fun syncOnly(action: () -> R): Deferred { + return if (!isSync()) { + async { + action() + } + } else { + try { + CompletableDeferred(action()) + } catch (e: Throwable) { + CompletableDeferred().apply { + completeExceptionally(e) + } + } + } + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricNamedPlayerService.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricNamedPlayerService.kt new file mode 100644 index 0000000..03700a0 --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricNamedPlayerService.kt @@ -0,0 +1,79 @@ +package br.com.gamemods.minecity.fabric.service + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.NamedPlayer +import br.com.gamemods.minecity.api.service.namedplayer.NamedPlayerService +import br.com.gamemods.minecity.fabric.MineCityFabric +import com.google.common.cache.CacheBuilder +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.datetime.Clock +import java.util.* +import java.util.concurrent.TimeUnit + +@InternalMineCityApi +class FabricNamedPlayerService(private val platform: MineCityFabric): NamedPlayerService { + private val uuid2named = CacheBuilder.newBuilder().expireAfterAccess(30L, TimeUnit.MINUTES).build>() + private val name2named = CacheBuilder.newBuilder().expireAfterAccess(30L, TimeUnit.MINUTES).build>() + + override fun get(uuid: UUID): Deferred { + @Suppress("DuplicatedCode") + return uuid2named.get(uuid) { + platform.runOnServer { server -> + server.syncOnly { + server.mcServer.native.playerManager.getPlayer(uuid)?.let { player -> + NamedPlayer(player.uuid, player.entityName, Clock.System.now()).also { named -> + name2named.put(named.name, CompletableDeferred(named)) + } + } + } + } + } + } + + override fun get(name: String): Deferred { + @Suppress("DuplicatedCode") + return name2named.get(name) { + platform.runOnServer { server -> + server.syncOnly { + server.mcServer.native.playerManager.getPlayer(name)?.let { player -> + NamedPlayer(player.uuid, player.entityName, Clock.System.now()).also { named -> + uuid2named.put(named.uuid, CompletableDeferred(named)) + } + } + } + } + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + override fun contains(uuid: UUID): Boolean { + return uuid2named.getIfPresent(uuid)?.let { deferred -> + if (deferred.isCompleted) { + try { + deferred.getCompleted() != null + } catch (e: Throwable) { + false + } + } else { + false + } + } ?: false + } + + @OptIn(ExperimentalCoroutinesApi::class) + override fun contains(name: String): Boolean { + return name2named.getIfPresent(name)?.let { deferred -> + if (deferred.isCompleted) { + try { + deferred.getCompleted() != null + } catch (e: Throwable) { + false + } + } else { + false + } + } ?: false + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricWorldService.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricWorldService.kt new file mode 100644 index 0000000..0281616 --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/service/FabricWorldService.kt @@ -0,0 +1,40 @@ +package br.com.gamemods.minecity.fabric.service + +import br.com.gamemods.minecity.api.annotation.PlatformDependent +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.id.WorldId +import br.com.gamemods.minecity.core.service.world.WorldService +import br.com.gamemods.minecity.core.wrapper.server.WorldWrapper +import br.com.gamemods.minecity.fabric.MineCityFabric +import br.com.gamemods.minecity.fabric.wrapper.FabricServerWorldWrapper.Companion.wrapper +import kotlinx.coroutines.Deferred +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.util.Identifier +import net.minecraft.world.World + +@InternalMineCityApi +class FabricWorldService(val platform: MineCityFabric): WorldService { + override fun get(worldId: WorldId): Deferred { + return platform.runOnServer { server -> + server.syncOnly { + server.mcServer.native.getWorld(worldId.toKey())?.wrapper + } + } + } + + companion object { + /** + * Finds the [RegistryKey] for this [WorldId] + */ + @OptIn(PlatformDependent::class) + fun WorldId.toKey(): RegistryKey { + val id = toString() + val index = id.indexOf(':') + check(index >= 0) { + "Failed to parse the WorldId $id" + } + return RegistryKey.of(RegistryKeys.WORLD, Identifier.of(id.substring(0, index), id.substring(index+1))) + } + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricBlockPosWrapper.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricBlockPosWrapper.kt new file mode 100644 index 0000000..935fb49 --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricBlockPosWrapper.kt @@ -0,0 +1,26 @@ +package br.com.gamemods.minecity.fabric.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import br.com.gamemods.minecity.core.wrapper.BlockPositionWrapper +import br.com.gamemods.minecity.core.wrapper.Wrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricChunkPosWrapper.Companion.wrapper +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.ChunkSectionPos + +/** + * Wraps [BlockPos] and gives access to its features to MineCity core. + */ +@JvmInline +@InternalMineCityApi +value class FabricBlockPosWrapper(override val native: BlockPos): BlockPositionWrapper { + companion object : Wrapper.WrapperClass(::FabricBlockPosWrapper) + constructor(x: Int, y: Int, z: Int): this(BlockPos(x, y, z)) + override val x: Int get() = native.x + override val y: Int get() = native.y + override val z: Int get() = native.z + override fun toChunkPos(): ChunkPosition = ChunkSectionPos.from(native).toChunkPos().wrapper + override fun toString(): String { + return "FabricBlockPosWrapper(x=$x,y=$y,z=$z)" + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricChunkPosWrapper.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricChunkPosWrapper.kt new file mode 100644 index 0000000..dbf1b98 --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricChunkPosWrapper.kt @@ -0,0 +1,31 @@ +package br.com.gamemods.minecity.fabric.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.BlockPosition +import br.com.gamemods.minecity.core.wrapper.ChunkPositionWrapper +import br.com.gamemods.minecity.core.wrapper.Wrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricBlockPosWrapper.Companion.wrapper +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.ChunkPos + +/** + * Wraps [ChunkPos] and gives access to its features to MineCity core. + */ +@JvmInline +@InternalMineCityApi +value class FabricChunkPosWrapper(override val native: ChunkPos): ChunkPositionWrapper { + companion object : Wrapper.WrapperClass(::FabricChunkPosWrapper) + constructor(x: Int, z: Int): this(ChunkPos(x, z)) + override val x: Int get() = native.x + override val z: Int get() = native.z + + override val minBlock: BlockPosition get() = BlockPos(native.startX, Int.MIN_VALUE, native.startZ).wrapper + override val maxBlock: BlockPosition get() = BlockPos(native.endX, Int.MAX_VALUE, native.endZ).wrapper + + override val xBlockRange: IntRange get() = native.startX..native.endX + override val zBlockRange: IntRange get() = native.startZ..native.endZ + + override fun toString(): String { + return "FabricChunkPosWrapper(x=$x,z=$z)" + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricEntityPosWrapper.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricEntityPosWrapper.kt new file mode 100644 index 0000000..1746163 --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricEntityPosWrapper.kt @@ -0,0 +1,26 @@ +package br.com.gamemods.minecity.fabric.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import br.com.gamemods.minecity.core.wrapper.EntityPositionWrapper +import br.com.gamemods.minecity.core.wrapper.Wrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricChunkPosWrapper.Companion.wrapper +import net.minecraft.util.math.ChunkSectionPos +import net.minecraft.util.math.Vec3d + +/** + * Wraps [Vec3d] as if it was an entity position and gives access to its features to MineCity core. + */ +@JvmInline +@InternalMineCityApi +value class FabricEntityPosWrapper(override val native: Vec3d): EntityPositionWrapper { + companion object : Wrapper.WrapperClass(::FabricEntityPosWrapper) + constructor(x: Double, y: Double, z: Double): this(Vec3d(x, y, z)) + override val x: Double get() = native.x + override val y: Double get() = native.y + override val z: Double get() = native.z + override fun toChunkPos(): ChunkPosition = ChunkSectionPos.from(native).toChunkPos().wrapper + override fun toString(): String { + return "FabricEntityPosWrapper(x=$x,y=$y,z=$z)" + } +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricMinecraftServerWrapper.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricMinecraftServerWrapper.kt new file mode 100644 index 0000000..67ad90d --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricMinecraftServerWrapper.kt @@ -0,0 +1,24 @@ +package br.com.gamemods.minecity.fabric.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.core.wrapper.Wrapper +import br.com.gamemods.minecity.core.wrapper.server.MinecraftServerWrapper +import net.minecraft.server.MinecraftServer + +/** + * Wraps [MinecraftServer] and gives access to its features to MineCity core. + */ +@JvmInline +@InternalMineCityApi +value class FabricMinecraftServerWrapper(override val native: MinecraftServer): MinecraftServerWrapper { + companion object: Wrapper.WrapperClass(::FabricMinecraftServerWrapper) + + override val serverIp get() = native.serverIp + + override fun isSync() = native.isOnThread + + override fun toString(): String { + return "FabricMinecraftServerWrapper(serverIp=$serverIp)" + } + +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricServerWorldWrapper.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricServerWorldWrapper.kt new file mode 100644 index 0000000..beda0fb --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricServerWorldWrapper.kt @@ -0,0 +1,24 @@ +package br.com.gamemods.minecity.fabric.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.core.wrapper.Wrapper +import br.com.gamemods.minecity.core.wrapper.server.ServerWorldWrapper +import net.minecraft.server.world.ServerWorld +import net.minecraft.world.chunk.WorldChunk + +/** + * Wraps [WorldChunk] and gives access to its features to MineCity core. + */ +@JvmInline +@InternalMineCityApi +value class FabricServerWorldWrapper(override val native: ServerWorld): ServerWorldWrapper { + companion object: Wrapper.WrapperClass(::FabricServerWorldWrapper) + + override val id get() = native.registryKey.value.toString() + + override fun toString(): String { + return "FabricServerWorldWrapper(id=$id)" + } + + +} diff --git a/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricWorldChunkWrapper.kt b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricWorldChunkWrapper.kt new file mode 100644 index 0000000..3e1ee31 --- /dev/null +++ b/platform/fabric/src/main/kotlin/br/com/gamemods/minecity/fabric/wrapper/FabricWorldChunkWrapper.kt @@ -0,0 +1,23 @@ +package br.com.gamemods.minecity.fabric.wrapper + +import br.com.gamemods.minecity.api.annotation.internal.InternalMineCityApi +import br.com.gamemods.minecity.api.math.pos.ChunkPosition +import br.com.gamemods.minecity.core.wrapper.Wrapper +import br.com.gamemods.minecity.core.wrapper.server.WorldChunkWrapper +import br.com.gamemods.minecity.fabric.wrapper.FabricChunkPosWrapper.Companion.wrapper +import net.minecraft.world.chunk.WorldChunk + +/** + * Wraps [WorldChunk] and gives access to its features to MineCity core. + */ +@JvmInline +@InternalMineCityApi +value class FabricWorldChunkWrapper(override val native: WorldChunk): WorldChunkWrapper { + companion object: Wrapper.WrapperClass(::FabricWorldChunkWrapper) + + override val pos: ChunkPosition get() = native.pos.wrapper + override fun toString(): String { + val pos = pos + return "FabricWorldChunkWrapper(x=${pos.x},z=${pos.z})" + } +} diff --git a/src/main/resources/assets/minecity/icon.png b/platform/fabric/src/main/resources/assets/minecity/icon.png similarity index 100% rename from src/main/resources/assets/minecity/icon.png rename to platform/fabric/src/main/resources/assets/minecity/icon.png diff --git a/src/main/resources/fabric.mod.json b/platform/fabric/src/main/resources/fabric.mod.json similarity index 84% rename from src/main/resources/fabric.mod.json rename to platform/fabric/src/main/resources/fabric.mod.json index dd7bc56..1d424a7 100644 --- a/src/main/resources/fabric.mod.json +++ b/platform/fabric/src/main/resources/fabric.mod.json @@ -17,13 +17,13 @@ "entrypoints": { "main": [ { - "value": "br.com.gamemods.minecity.fabric.common.MineCity", + "value": "br.com.gamemods.minecity.fabric.common.MineCityFabric", "adapter": "kotlin" } ], "client": [ { - "value": "br.com.gamemods.minecity.fabric.client.MineCityClient", + "value": "br.com.gamemods.minecity.fabric.client.MineCityFabricClient", "adapter": "kotlin" } ] diff --git a/src/main/resources/minecity.mixins.json b/platform/fabric/src/main/resources/minecity.mixins.json similarity index 100% rename from src/main/resources/minecity.mixins.json rename to platform/fabric/src/main/resources/minecity.mixins.json diff --git a/settings.gradle.kts b/settings.gradle.kts index edbdeee..59e1235 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,3 +8,5 @@ pluginManagement { gradlePluginPortal() } } + +include(":api", ":core", ":platform:fabric") diff --git a/src/client/kotlin/br/com/gamemods/minecity/fabric/client/MineCityClient.kt b/src/client/kotlin/br/com/gamemods/minecity/fabric/client/MineCityClient.kt deleted file mode 100644 index 3146bba..0000000 --- a/src/client/kotlin/br/com/gamemods/minecity/fabric/client/MineCityClient.kt +++ /dev/null @@ -1,9 +0,0 @@ -package br.com.gamemods.minecity.fabric.client - -import net.fabricmc.api.ClientModInitializer - -object MineCityClient : ClientModInitializer { - override fun onInitializeClient() { - // This entrypoint is suitable for setting up client-specific logic, such as rendering. - } -} diff --git a/src/main/kotlin/br/com/gamemods/minecity/fabric/common/MineCity.kt b/src/main/kotlin/br/com/gamemods/minecity/fabric/common/MineCity.kt deleted file mode 100644 index 3fc656b..0000000 --- a/src/main/kotlin/br/com/gamemods/minecity/fabric/common/MineCity.kt +++ /dev/null @@ -1,15 +0,0 @@ -package br.com.gamemods.minecity.fabric.common - -import net.fabricmc.api.ModInitializer -import org.slf4j.LoggerFactory - -object MineCity : ModInitializer { - private val logger = LoggerFactory.getLogger("minecity") - - override fun onInitialize() { - // This code runs as soon as Minecraft is in a mod-load-ready state. - // However, some things (like resources) may still be uninitialized. - // Proceed with mild caution. - logger.info("Hello Fabric world!") - } -}