summaryrefslogtreecommitdiff
path: root/src/main/kotlin/io/dico/parcels2/defaultimpl/DefaultParcelGenerator.kt
blob: 7e75f52bba18e2eed63dc14d1531c57935933947 (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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
package io.dico.parcels2.defaultimpl

import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.RegionTraversal
import io.dico.parcels2.blockvisitor.Worker
import io.dico.parcels2.blockvisitor.WorktimeLimiter
import io.dico.parcels2.options.DefaultGeneratorOptions
import io.dico.parcels2.util.*
import org.bukkit.*
import org.bukkit.block.Biome
import org.bukkit.block.Block
import org.bukkit.block.BlockFace
import org.bukkit.block.Skull
import org.bukkit.block.data.BlockData
import org.bukkit.block.data.type.Sign
import org.bukkit.block.data.type.Slab
import java.util.Random

private val airType = Bukkit.createBlockData(Material.AIR)

class DefaultParcelGenerator(val name: String, private val o: DefaultGeneratorOptions) : ParcelGenerator() {
    private var _world: World? = null
    override val world: World
        get() {
            if (_world == null) _world = Bukkit.getWorld(name)!!.also {
                maxHeight = it.maxHeight
                return it
            }
            return _world!!
        }

    private var maxHeight = 0
    val sectionSize = o.parcelSize + o.pathSize
    val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
    val makePathMain = o.pathSize > 2
    val makePathAlt = o.pathSize > 4

    private inline fun <T> generate(chunkX: Int,
                                    chunkZ: Int,
                                    floor: T, wall:
                                    T, pathMain: T,
                                    pathAlt: T,
                                    fill: T,
                                    setter: (Int, Int, Int, T) -> Unit) {

        val floorHeight = o.floorHeight
        val parcelSize = o.parcelSize
        val sectionSize = sectionSize
        val pathOffset = pathOffset
        val makePathMain = makePathMain
        val makePathAlt = makePathAlt

        // parcel bottom x and z
        // umod is unsigned %: the result is always >= 0
        val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize
        val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize

        var curHeight: Int
        var x: Int
        var z: Int
        for (cx in 0..15) {
            for (cz in 0..15) {
                x = (pbx + cx) % sectionSize - pathOffset
                z = (pbz + cz) % sectionSize - pathOffset
                curHeight = floorHeight

                val type = when {
                    (x in 0 until parcelSize && z in 0 until parcelSize) -> floor
                    (x in -1..parcelSize && z in -1..parcelSize) -> {
                        curHeight++
                        wall
                    }
                    (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt
                    (makePathMain) -> pathMain
                    else -> {
                        curHeight++
                        wall
                    }
                }

                for (y in 0 until curHeight) {
                    setter(cx, y, cz, fill)
                }
                setter(cx, curHeight, cz, type)
            }
        }
    }

    override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
        val out = Bukkit.createChunkData(world)
        generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type ->
            out.setBlock(x, y, z, type)
        }
        return out
    }

    override fun populate(world: World?, random: Random?, chunk: Chunk?) {
        // do nothing
    }

    override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
        val fix = if (o.parcelSize.even) 0.5 else 0.0
        return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
    }

    override fun makeParcelBlockManager(worktimeLimiter: WorktimeLimiter): ParcelBlockManager {
        return ParcelBlockManagerImpl(worktimeLimiter)
    }

    override fun makeParcelLocator(container: ParcelContainer): ParcelLocator {
        return ParcelLocatorImpl(container)
    }

    private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
        val sectionSize = sectionSize
        val parcelSize = o.parcelSize
        val absX = x - o.offsetX - pathOffset
        val absZ = z - o.offsetZ - pathOffset
        val modX = absX umod sectionSize
        val modZ = absZ umod sectionSize
        if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
            return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1)
        }
        return null
    }

    private inner class ParcelLocatorImpl(val container: ParcelContainer) : ParcelLocator {
        override val world: World = this@DefaultParcelGenerator.world
        override fun getParcelAt(x: Int, z: Int): Parcel? {
            return convertBlockLocationToId(x, z, container::getParcelById)
        }

        override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
            return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(world.name, world.uid, idx, idz) }
        }
    }

    @Suppress("DEPRECATION")
    private inner class ParcelBlockManagerImpl(override val worktimeLimiter: WorktimeLimiter) : ParcelBlockManager {
        override val world: World = this@DefaultParcelGenerator.world

        override fun getBottomBlock(parcel: ParcelId): Vec2i = Vec2i(
            sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
            sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
        )

        override fun getHomeLocation(parcel: ParcelId): Location {
            val bottom = getBottomBlock(parcel)
            val x = bottom.x + (o.parcelSize - 1) / 2.0
            val z = bottom.z - 2
            return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
        }

        override fun setOwnerBlock(parcel: ParcelId, owner: PlayerProfile?) {
            val b = getBottomBlock(parcel)

            val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
            val signBlock = world.getBlockAt(b.x - 2, o.floorHeight + 1, b.z - 1)
            val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)

            if (owner == null) {
                wallBlock.blockData = o.wallType
                signBlock.type = Material.AIR
                skullBlock.type = Material.AIR
            } else {

                val wallBlockType: BlockData = if (o.wallType is Slab)
                    (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
                else
                    o.wallType

                wallBlock.blockData = wallBlockType

                signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as Sign).apply { rotation = BlockFace.NORTH }

                val sign = signBlock.state as org.bukkit.block.Sign
                sign.setLine(0, "${parcel.x},${parcel.z}")
                sign.setLine(2, owner.name)
                sign.update()

                skullBlock.type = Material.PLAYER_HEAD
                val skull = skullBlock.state as Skull
                if (owner is PlayerProfile.Real) {
                    skull.owningPlayer = owner.playerUnchecked
                } else {
                    skull.owner = owner.name
                }
                skull.rotation = BlockFace.WEST
                skull.update()
            }
        }

        override fun setBiome(parcel: ParcelId, biome: Biome): Worker = worktimeLimiter.submit {
            val world = world
            val b = getBottomBlock(parcel)
            val parcelSize = o.parcelSize
            for (x in b.x until b.x + parcelSize) {
                for (z in b.z until b.z + parcelSize) {
                    markSuspensionPoint()
                    world.setBiome(x, z, biome)
                }
            }
        }

        override fun clearParcel(parcel: ParcelId): Worker = worktimeLimiter.submit {
            val bottom = getBottomBlock(parcel)
            val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
            val blocks = RegionTraversal.DOWNWARD.regionTraverser(region)
            val blockCount = region.blockCount.toDouble()

            val world = world
            val floorHeight = o.floorHeight
            val airType = airType
            val floorType = o.floorType
            val fillType = o.fillType

            for ((index, vec) in blocks.withIndex()) {
                markSuspensionPoint()
                val y = vec.y
                val blockType = when {
                    y > floorHeight -> airType
                    y == floorHeight -> floorType
                    else -> fillType
                }
                world[vec].blockData = blockType
                setProgress((index + 1) / blockCount)
            }
        }

        override fun doBlockOperation(parcel: ParcelId, direction: RegionTraversal, operation: (Block) -> Unit): Worker = worktimeLimiter.submit {
            val bottom = getBottomBlock(parcel)
            val region = Region(Vec3i(bottom.x, 0, bottom.z), Vec3i(o.parcelSize, maxHeight + 1, o.parcelSize))
            val blocks = direction.regionTraverser(region)
            val blockCount = region.blockCount.toDouble()
            val world = world

            for ((index, vec) in blocks.withIndex()) {
                markSuspensionPoint()
                operation(world[vec])
                setProgress((index + 1) / blockCount)
            }
        }

    }

}