Skip to content

Instantly share code, notes, and snippets.

@Adrodoc
Last active March 26, 2018 15:58
Show Gist options
  • Save Adrodoc/6dd49836ce8a01e26d2643ab8d1ff207 to your computer and use it in GitHub Desktop.
Save Adrodoc/6dd49836ce8a01e26d2643ab8d1ff207 to your computer and use it in GitHub Desktop.
claiming.lua
-- adrodoc55/claiming.lua
require "mickkay.wol.Spell"
local setmultimap = require "adrodoc55.setmultimap"
local cities = require "mickkay.cities"
local datastore = require "mickkay.datastore"
local pkg = {}
local store
local width = 16
local frequency = 1
local heads = {}
-- Mapping of chunk vector to all head positions with areas that intersect the chunk
local headsByChunk = {}
local citiesProxy
function saveData()
datastore.save(store, heads)
end
local getChunksIntersecting
function loadData()
data = datastore.load(store) or {}
heads = {}
for i,headPos in pairs(data) do
table.insert(heads,Vec3.new(headPos))
end
headsByChunk = {}
for i,head in pairs(heads) do
local chunks = getChunksIntersecting(head)
for j,chunk in pairs(chunks) do
setmultimap.put(headsByChunk, chunk, head)
end
end
end
local updatePlayer
local removeBrokenHeads
local onRightClickBlockEvent
local getBlock
local isPlayerHead
local isHeadOfOwner
local isOverlapping
local destroy
function pkg.start(storePos, options)
store = storePos or spell.pos
spell:singleton("mickkay.claim")
options = options or {}
width = options.width or width
frequency = options.frequency or frequency
citiesProxy = cities.proxy()
loadData()
local queue = Events.connect("RightClickBlockEvent")
local lastCycle = Time.gametime
local timeout = frequency
while true do
local dirty = false
local event = queue:next(timeout)
local timeSlept = Time.gametime - lastCycle
timeout = math.min(timeout, frequency-timeSlept)
if timeout < 1 then
timeout = frequency
end
if event then
dirty = onRightClickBlockEvent(event)
else
local players = Entities.find("@a")
for i,player in pairs(players) do
updatePlayer(player)
end
dirty = removeBrokenHeads()
end
if dirty then
saveData()
end
end
end
Help.on(pkg.start) [[
Allows players to 'claim' an area by placing their own head in the center of it. An area is protected by setting other players that enter the area into adventure mode. Areas can be shared between multiple players by placing multiple heads on top of each other (same x and z coordinate). If two areas overlap each other, the intersection is protected from both players.
Options:
- width: How many blocks around the skull are protected. Default is 16 which results in 33x33 areas.
- frequency: Every 'frequency' ticks the gamemodes of players are updated and all skulls are checked to make sure they are still there.
]]
function pkg.stop()
spell:singleton("mickkay.claim")
end
Help.on(pkg.stop) [[
Disables 'claiming' of areas. Existing areas are kept persistent.
]]
function updatePlayer(player)
if player.dimension ~= 0 then
return -- claiming is only supported in the overworld
end
local pos = player.pos
local chunkX = pos.x // 16
local chunkZ = pos.z // 16
local chunk = chunkX..'/'..chunkZ
local heads = headsByChunk[chunk]
local ownHeadsXZ = {}
local foreignHeadsXZ = {}
if heads then
for i,head in pairs(heads) do
if head.x - width < pos.x
and head.z - width < pos.z
and head.x + width + 1 > pos.x
and head.z + width + 1 > pos.z
then
local xz = head.x.."/"..head.z
local block = getBlock(head)
if isPlayerHead(block) then
if isHeadOfOwner(block, player.name) then
ownHeadsXZ[xz] = true
else
foreignHeadsXZ[xz] = true
end
end
end
end
end
for xz,_ in pairs(ownHeadsXZ) do
foreignHeadsXZ[xz] = nil
end
local mayBuild = next(foreignHeadsXZ) == nil
if not mayBuild and player.gamemode == "survival" then
player.gamemode = "adventure"
elseif mayBuild and player.gamemode == "adventure" then
player.gamemode = "survival"
end
end
function removeBrokenHeads()
local dirty = false
for i=#heads,1,-1 do
local head = heads[i]
local block = getBlock(head)
if not isPlayerHead(block) then
table.remove(heads, i)
local chunks = getChunksIntersecting(head)
for i,chunk in pairs(chunks) do
setmultimap.remove(headsByChunk, chunk, head)
end
dirty = true
end
end
return dirty
end
function isHeadOfOwner(block, owner)
return isPlayerHead(block) and block.nbt.Owner.Name == owner
end
function getBlock(pos)
spell.pos = pos
return spell.block
end
function isPlayerHead(block)
return block.name == "skull" and block.nbt.Owner and block.nbt.Owner.Name
end
function onRightClickBlockEvent(event)
if event.player.dimension ~= 0 then
return false -- claiming is only supported in the overworld
end
spell.pos = event.pos
spell:move(event.face)
local block = spell.block
if not isPlayerHead(block) then
return false -- not dirty
end
local head = spell.pos
if event.player.gamemode ~= "creative" and not citiesProxy.isInsideCityCenter(head) then
-- undo setting the head
destroy(head)
return false -- not dirty
end
-- prevent overlapping areas
if isOverlapping(head) then
spell:execute('tellraw %s {"text":"This would overlap with a different claimed area","color":"dark_purple"}', event.player.name)
-- undo setting the head
destroy(head)
return false -- not dirty
end
-- new head is accepted
table.insert(heads, head)
local chunks = getChunksIntersecting(head)
for i,chunk in pairs(chunks) do
setmultimap.put(headsByChunk, chunk, head)
end
return true -- dirty
end
function isOverlapping(head)
local block = getBlock(head)
local owner = block.nbt.Owner.Name
local ownerHeadsXZ = {}
local foreignHeadsXZ = {}
local chunks = getChunksIntersecting(head)
for i,chunk in pairs(chunks) do
local nearbyHeads = headsByChunk[chunk]
if nearbyHeads then
for j,nearbyHead in pairs(nearbyHeads) do
if nearbyHead.x + width*2 >= head.x
and head.x + width*2 >= nearbyHead.x
and nearbyHead.z + width*2 >= head.z
and head.z + width*2 >= nearbyHead.z
then
local xz = nearbyHead.x.."/"..nearbyHead.z
local nearbyBlock = getBlock(nearbyHead)
if isPlayerHead(nearbyBlock) then
if isHeadOfOwner(nearbyBlock, owner) then
ownerHeadsXZ[xz] = true
else
foreignHeadsXZ[xz] = true
end
end
end
end
end
end
if foreignHeadsXZ[head.x.."/"..head.z] then
return false -- placing a head ontop of another head is always allowed
end
for xz,_ in pairs(ownerHeadsXZ) do
foreignHeadsXZ[xz] = nil
end
return next(foreignHeadsXZ) ~= nil
end
function destroy(pos)
spell:execute("setblock "..pos.x.." "..pos.y.." "..pos.z.." air 0 destroy")
end
function getChunksIntersecting(head)
local minChunkX = (head.x - width) // 16
local maxChunkX = (head.x + width + 1) // 16
local minChunkZ = (head.z - width) // 16
local maxChunkZ = (head.z + width + 1) // 16
local chunks = {}
for chunkX=minChunkX,maxChunkX,1 do
for chunkZ=minChunkZ,maxChunkZ,1 do
table.insert(chunks, chunkX..'/'..chunkZ)
end
end
return chunks
end
return pkg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment