Last active
March 26, 2018 15:58
-
-
Save Adrodoc/6dd49836ce8a01e26d2643ab8d1ff207 to your computer and use it in GitHub Desktop.
claiming.lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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