summaryrefslogtreecommitdiff
path: root/src/main/kotlin/io/dico/parcels2/blockvisitor/Schematic.kt
blob: df3cfab43afdb91d6941ac06b0aeb9311439b11f (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
package io.dico.parcels2.blockvisitor

import io.dico.parcels2.JobFunction
import io.dico.parcels2.JobScope
import io.dico.parcels2.util.math.Region
import io.dico.parcels2.util.math.Vec3i
import io.dico.parcels2.util.math.get
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.World
import org.bukkit.block.Sign
import org.bukkit.block.data.BlockData

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

class Schematic {
    val size: Vec3i get() = _size!!
    private var _size: Vec3i? = null
        set(value) {
            field?.let { throw IllegalStateException() }
            field = value
        }

    private var blockDatas: Array<BlockData?>? = null
    private val extra = mutableListOf<Pair<Vec3i, ExtraBlockChange>>()
    private var isLoaded = false; private set
    private val traverser: RegionTraverser = RegionTraverser.upward

    suspend fun JobScope.load(world: World, region: Region) {
        _size = region.size

        val data = arrayOfNulls<BlockData>(region.blockCount).also { blockDatas = it }
        val blocks = traverser.traverseRegion(region)
        val total = region.blockCount.toDouble()

        loop@ for ((index, vec) in blocks.withIndex()) {
            markSuspensionPoint()
            setProgress(index / total)

            val block = world[vec]
            if (block.y > 255) continue
            val blockData = block.blockData
            data[index] = blockData

            val extraChange = when (blockData.material) {
                Material.SIGN,
                Material.WALL_SIGN -> SignStateChange(block.state as Sign)
                else -> continue@loop
            }

            extra += (vec - region.origin) to extraChange
        }

        isLoaded = true
    }

    suspend fun JobScope.paste(world: World, position: Vec3i) {
        if (!isLoaded) throw IllegalStateException()

        val region = Region(position, _size!!)
        val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight)
        val blockDatas = blockDatas!!
        var postponed = hashMapOf<Vec3i, BlockData>()

        val total = region.blockCount.toDouble()
        var processed = 0

        for ((index, vec) in blocks.withIndex()) {
            markSuspensionPoint()
            setProgress(index / total)

            val block = world[vec]
            val type = blockDatas[index] ?: air
            if (type !== air && isAttachable(type.material)) {
                val supportingBlock = vec + getSupportingBlock(type)

                if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
                    block.blockData = type
                    setProgress(++processed / total)
                } else {
                    postponed[vec] = type
                }

            } else {
                block.blockData = type
                setProgress(++processed / total)
            }
        }

        while (!postponed.isEmpty()) {
            markSuspensionPoint()
            val newMap = hashMapOf<Vec3i, BlockData>()
            for ((vec, type) in postponed) {
                val supportingBlock = vec + getSupportingBlock(type)
                if (supportingBlock in postponed && supportingBlock != vec) {
                    newMap[vec] = type
                } else {
                    world[vec].blockData = type
                    setProgress(++processed / total)
                }
            }
            postponed = newMap
        }

        // Should be negligible so we don't track progress
        for ((vec, extraChange) in extra) {
            markSuspensionPoint()
            val block = world[position + vec]
            extraChange.update(block)
        }
    }

    fun getLoadTask(world: World, region: Region): JobFunction = {
        load(world, region)
    }

    fun getPasteTask(world: World, position: Vec3i): JobFunction = {
        paste(world, position)
    }

}