summaryrefslogtreecommitdiff
path: root/src/main/kotlin/io/dico/parcels2/command/ParcelTarget.kt
blob: 9ba3c25c239b9e406f1c6062ee34ca9627b16333 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package io.dico.parcels2.command

import io.dico.dicore.command.parameter.ArgumentBuffer
import io.dico.dicore.command.parameter.Parameter
import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.DEFAULT_KIND
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.ID
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_FAKE
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_REAL
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.PREFER_OWNED_FOR_DEFAULT
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.REAL
import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.math.Vec2i
import io.dico.parcels2.util.math.floor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player

sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDefault: Boolean) {

    abstract suspend fun getParcelSuspend(storage: Storage): Parcel?

    class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) :
        ParcelTarget(world, parsedKind, isDefault) {
        override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel()
        fun getParcel() = id?.let { world.getParcelById(it) }
        val isPath: Boolean get() = id == null
    }

    class ByOwner(
        world: ParcelWorld,
        owner: PlayerProfile,
        val index: Int,
        parsedKind: Int,
        isDefault: Boolean,
        val onResolveFailure: (() -> Unit)? = null
    ) : ParcelTarget(world, parsedKind, isDefault) {
        init {
            if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
        }

        var owner = owner; private set

        suspend fun resolveOwner(storage: Storage): Boolean {
            val owner = owner
            if (owner is PlayerProfile.Unresolved) {
                this.owner = owner.tryResolveSuspendedly(storage) ?: if (parsedKind and OWNER_FAKE != 0) PlayerProfile.Fake(owner.name)
                else run { onResolveFailure?.invoke(); return false }
            }
            return true
        }

        override suspend fun getParcelSuspend(storage: Storage): Parcel? {
            onResolveFailure?.let { resolveOwner(storage) }

            val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
            val ownedParcels = ownedParcelsSerialized
                .filter { it.worldId.equals(world.id) }
                .map { world.getParcelById(it.x, it.z) }

            return ownedParcels.getOrNull(index)
        }
    }

    annotation class TargetKind(val kind: Int) {
        companion object : ParameterConfig<TargetKind, Int>(TargetKind::class.java) {
            const val ID = 1 // ID
            const val OWNER_REAL = 2 // an owner backed by a UUID
            const val OWNER_FAKE = 4 // an owner not backed by a UUID

            const val OWNER = OWNER_REAL or OWNER_FAKE // any owner
            const val ANY = ID or OWNER_REAL or OWNER_FAKE // any
            const val REAL = ID or OWNER_REAL // no owner not backed by a UUID

            const val DEFAULT_KIND = REAL

            const val PREFER_OWNED_FOR_DEFAULT =
                8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default
            // instead of parcel that the player is in

            override fun toParameterInfo(annotation: TargetKind): Int {
                return annotation.kind
            }
        }
    }

    class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) :
        ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {

        override fun parse(
            parameter: Parameter<ParcelTarget, Int>,
            sender: CommandSender,
            buffer: ArgumentBuffer
        ): ParcelTarget {
            var input = buffer.next()!!
            val worldString = input.substringBefore("/", missingDelimiterValue = "")
            input = input.substringAfter("/")

            val world = if (worldString.isEmpty()) {
                val player = requirePlayer(sender, parameter, "the world")
                parcelProvider.getWorld(player.world)
                    ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world")
            } else {
                parcelProvider.getWorld(worldString)
                    ?: invalidInput(parameter, "$worldString is not a parcel world")
            }

            val kind = parameter.paramInfo ?: DEFAULT_KIND
            if (input.contains(',')) {
                if (kind and ID == 0) invalidInput(parameter,
                    "You must specify a parcel by OWNER, that is, an owner and index")
                return ByID(world, getId(parameter, input), kind, false)
            }

            if (kind and OWNER == 0) invalidInput(parameter,
                "You must specify a parcel by ID, that is, the x and z component separated by a comma")
            val (owner, index) = getHomeIndex(parameter, kind, sender, input)
            return ByOwner(world,
                owner,
                index,
                kind,
                false,
                onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
        }

        private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
            val x = input.substringBefore(',').run {
                toIntOrNull() ?: invalidInput(parameter, "ID(x) must be an integer, $this is not an integer")
            }
            val z = input.substringAfter(',').run {
                toIntOrNull() ?: invalidInput(parameter, "ID(z) must be an integer, $this is not an integer")
            }
            return Vec2i(x, z)
        }

        private fun getHomeIndex(
            parameter: Parameter<*, *>,
            kind: Int,
            sender: CommandSender,
            input: String
        ): Pair<PlayerProfile, Int> {
            val splitIdx = input.indexOf(':')
            val ownerString: String
            val index: Int?

            val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex

            if (splitIdx == -1) {

                if (speciallyParsedIndex == null) {
                    // just the index.
                    index = input.toIntOrNull()
                    ownerString = if (index == null) input else ""
                } else {
                    // just the owner.
                    index = speciallyParsedIndex
                    ownerString = input
                }

            } else {
                if (speciallyParsedIndex != null) {
                    invalidInput(parameter, "Duplicate home index")
                }

                ownerString = input.substring(0, splitIdx)

                val indexString = input.substring(splitIdx + 1)
                index = indexString.toIntOrNull()
                    ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
            }

            val owner = (if (ownerString.isEmpty())
                PlayerProfile(requirePlayer(sender, parameter, "the player"))
            else
                PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0))
                ?: invalidInput(parameter, "\'$ownerString\' is not a valid player name")

            return owner to (index ?: 0)
        }

        private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player {
            if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName")
            return sender
        }

        override fun getDefaultValue(
            parameter: Parameter<ParcelTarget, Int>,
            sender: CommandSender,
            buffer: ArgumentBuffer
        ): ParcelTarget? {
            val kind = parameter.paramInfo ?: DEFAULT_KIND
            val useLocation = when {
                kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
                kind and ID != 0 -> true
                kind and OWNER_REAL != 0 -> false
                else -> return null
            }

            val player = requirePlayer(sender, parameter, "the parcel")
            val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter,
                "You must be in a parcel world to omit the parcel")
            if (useLocation) {
                val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
                return ByID(world, id, kind, true)
            }

            return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true)
        }
    }

}