-- v1.3.0 local piston = {} local redstone = require("redstone") local npcManager = require("npcManager") local expandedDefines = require("expandedDefines") local insert = table.insert piston.name = "piston" piston.id = NPC_ID piston.order = 0.74 piston.config = npcManager.setNpcSettings({ id = piston.id, gfxwidth = 32, gfxheight = 32, gfxoffsetx = 0, gfxoffsety = 0, invisible = false, frames = 1, framespeed = 8, framestyle = 0, width = 32, height = 32, nogravity = true, notcointransformable = true, jumphurt = true, noblockcollision = true, nohurt = true, noyoshi = true, blocknpc = true, playerblock = true, playerblocktop = true, npcblock = true, disabledespawn = false, alwayspull = false, pushMax = 320, interval = 8, deadzone = 16 }) -- =============== -- === PISTONS === -- =============== -- Determines if a block is immovable by a piston local immovableblock = table.map({}) redstone.blockImmovable = function(b) if immovableblock[b.id] then return true end end -- Determines if an NPC is immovable by a piston redstone.npcImmovable = function(n) if (n and n.data and n.data.pistImmovable) then return true end end -- Determines if a player is immovable by a piston redstone.playerImmovable = function(p) if p:mem(0x4A, FIELD_BOOL) then return true end end -- Determines if a block is intangible local intangibleBlock = table.map(expandedDefines.BLOCK_SEMISOLID..expandedDefines.BLOCK_SIZEABLE..expandedDefines.BLOCK_NONSOLID) redstone.blockIgnore = function(b) if intangibleBlock[b.id] then return true end end -- Determines if an NPC is intangible redstone.npcIgnore = function(n) if (n and n.data and n.data.pistIgnore) or redstone.is.dust(n.id) or n:mem(0x12C, FIELD_WORD) ~= 0 then return true end end -- Determines if an player is intangible redstone.playerIgnore = function(p) return false end local sfxextend = Audio.SfxOpen(Misc.resolveFile("piston-extend.ogg")) local sfxretract = Audio.SfxOpen(Misc.resolveFile("piston-retract.ogg")) function piston.updateRedArea(n) local data = n.data if data.origframe == 0 then data.easybox.x, data.easybox.y = n.x - data.easybox.width, n.y + 2 data.hardbox.x, data.hardbox.y = n.x - data.hardbox.width, data.easybox.y data.deadbox.x, data.deadbox.y = data.hardbox.x - data.deadbox.width - 2, data.easybox.y data.intbox.x, data.intbox.y = n.x - data.intbox.width, data.easybox.y data.pullbox.x, data.pullbox.y = n.x - data.pullbox.width, data.easybox.y elseif data.origframe == 1 then data.easybox.x, data.easybox.y = n.x + 2, n.y - data.easybox.height data.hardbox.x, data.hardbox.y = data.easybox.x, n.y - data.hardbox.height + 2 data.deadbox.x, data.deadbox.y = data.easybox.x, data.hardbox.y - data.deadbox.height - 2 data.intbox.x, data.intbox.y = data.easybox.x, n.y - data.intbox.height data.pullbox.x, data.pullbox.y = data.easybox.x, n.y - data.pullbox.height elseif data.origframe == 2 then data.easybox.x, data.easybox.y = n.x + n.width, n.y + 2 data.hardbox.x, data.hardbox.y = data.easybox.x, data.easybox.y data.deadbox.x, data.deadbox.y = data.hardbox.x + data.hardbox.width + 2, data.easybox.y data.intbox.x, data.intbox.y = data.easybox.x, data.easybox.y data.pullbox.x, data.pullbox.y = data.easybox.x, data.easybox.y elseif data.origframe == 3 then data.easybox.x, data.easybox.y = n.x + 2, n.y + n.height data.hardbox.x, data.hardbox.y = data.easybox.x, data.easybox.y data.deadbox.x, data.deadbox.y = data.easybox.x, data.hardbox.y + data.hardbox.height + 2 data.intbox.x, data.intbox.y = data.easybox.x, data.easybox.y data.pullbox.x, data.pullbox.y = data.easybox.x, data.easybox.y end end local function newredarea(n) local data = n.data local config_hor = redstone.component.piston_ehor.config local config_ver = redstone.component.piston_ever.config if data.frameX == 0 then return Colliders.Box(0, 0, data.pistdiff - 2, config_hor.height - 4), Colliders.Box(0, 0, piston.config.pushMax, config_hor.height - 4), Colliders.Box(0, 0, piston.config.deadzone, config_hor.height - 4), Colliders.Box(0, 0, piston.config.interval, config_hor.height - 4), Colliders.Box(0, 0, 2, config_hor.height - 4) elseif data.frameX == 1 then return Colliders.Box(0, 0, config_ver.width - 4, data.pistdiff - 2), Colliders.Box(0, 0, config_ver.width - 4, piston.config.pushMax), Colliders.Box(0, 0, config_ver.width - 4, piston.config.deadzone), Colliders.Box(0, 0, config_ver.width - 4, piston.config.interval), Colliders.Box(0, 0, config_ver.width - 4, 2) elseif data.frameX == 2 then return Colliders.Box(0, 0, data.pistdiff - 2, config_hor.height - 4), Colliders.Box(0, 0, piston.config.pushMax, config_hor.height - 4), Colliders.Box(0, 0, piston.config.deadzone, config_hor.height - 4), Colliders.Box(0, 0, piston.config.interval, config_hor.height - 4), Colliders.Box(0, 0, 2, config_hor.height - 4) else return Colliders.Box(0, 0, config_ver.width - 4, data.pistdiff - 2), Colliders.Box(0, 0, config_ver.width - 4, piston.config.pushMax), Colliders.Box(0, 0, config_ver.width - 4, piston.config.deadzone), Colliders.Box(0, 0, config_ver.width - 4, piston.config.interval), Colliders.Box(0, 0, config_ver.width - 4, 2) end end function piston.prime(n) local data = n.data data.frameX = data._settings.dir or 0 data.frameY = data._settings.type or 0 data.animFrame = data.animFrame or 0 data.animTimer = data.animTimer or 0 data.powerTimer = data.powerTimer or 0 if not redstone.is.piston(n.id) then if redstone.is.piston_ehor(n.id) then if data.frameX == 0 then data.origframe = 0 else data.origframe = 2 end elseif redstone.is.piston_ever(n.id) then if data.frameX == 0 then data.origframe = 1 else data.origframe = 3 end end else data.origframe = data.frameX end if data.origframe == 0 then data.pistdiff = redstone.component.piston_ehor.config.width - piston.config.width data.newframe = 0 data.newid = redstone.component.piston_ehor.id data.offX = 0.5*(piston.config.width - redstone.component.piston_ehor.config.width) data.offY = 0 data.direction = vector.v2(-1, 0) elseif data.origframe == 1 then data.pistdiff = redstone.component.piston_ever.config.height - piston.config.height data.newframe = 0 data.newid = redstone.component.piston_ever.id data.offX = 0 data.offY = 0.5*(piston.config.height - redstone.component.piston_ever.config.height) data.direction = vector.v2(0, -1) elseif data.origframe == 2 then data.pistdiff = redstone.component.piston_ehor.config.width - piston.config.width data.newframe = 1 data.newid = redstone.component.piston_ehor.id data.offX = -0.5*(piston.config.width - redstone.component.piston_ehor.config.width) data.offY = 0 data.direction = vector.v2(1, 0) elseif data.origframe == 3 then data.pistdiff = redstone.component.piston_ever.config.height - piston.config.height data.newframe = 1 data.newid = redstone.component.piston_ever.id data.offX = 0 data.offY = -0.5*(piston.config.height - redstone.component.piston_ever.config.height) data.direction = vector.v2(0, 1) end data.easybox, data.hardbox, data.deadbox, data.intbox, data.pullbox = newredarea(n) end local function transformPiston(n, data) n:transform(data.newid) data.frameX = data.newframe data.observ = true data.powerTimer = 0 n.x = n.x + data.offX n.y = n.y + data.offY end local function findFirstEmpty(n, data) local empty = true -- check easy push for k, c in ipairs({Block, NPC, Player}) do for _, v in ipairs(c.getIntersecting(data.easybox.x, data.easybox.y, data.easybox.x + data.easybox.width, data.easybox.y + data.easybox.height)) do if (k == 1 and redstone.blockImmovable(v)) or (k == 2 and redstone.npcImmovable(v)) or (k == 3 and data.frameY == 2 and redstone.playerImmovable(v)) then return -1 elseif (k == 1 and not redstone.blockIgnore(v)) or (k == 2 and not redstone.npcIgnore(v)) or (k == 3 and data.frameY == 2 and not redstone.playerIgnore(v)) then empty = false break end end end if empty then return 0 end -- check hard push data.intbox.x, data.intbox.y = data.intbox.x + data.direction.x*data.pistdiff, data.intbox.y + data.direction.y*data.pistdiff for i = data.pistdiff, piston.config.pushMax, piston.config.interval do empty = true for k, c in ipairs({Block, NPC, Player}) do for _, v in ipairs(c.getIntersecting(data.intbox.x, data.intbox.y, data.intbox.x + data.intbox.width, data.intbox.y + data.intbox.height)) do if (k == 1 and redstone.blockImmovable(v)) or (k == 2 and redstone.npcImmovable(v)) or (k == 3 and data.frameY == 2 and redstone.playerImmovable(v)) then return -1 elseif (k == 1 and not redstone.blockIgnore(v)) or (k == 2 and not redstone.npcIgnore(v)) or (k == 3 and data.frameY == 2 and not redstone.playerIgnore(v)) then empty = false break end end end data.intbox.x, data.intbox.y = data.intbox.x + data.direction.x*piston.config.interval, data.intbox.y + data.direction.y*piston.config.interval if empty then return i end end -- check dead zone for k, c in ipairs({Block, NPC, Player}) do for _, v in ipairs(c.getIntersecting(data.deadbox.x, data.deadbox.y, data.deadbox.x + data.deadbox.width, data.deadbox.y + data.deadbox.height)) do if (k == 1 and not redstone.blockIgnore(v)) or (k == 2 and not redstone.npcIgnore(v)) or (k == 3 and data.frameY == 2) then return -1 end end end return piston.config.pushMax end local function getEasy(n, data) local list = {} for k, c in ipairs({Block, NPC, Player}) do for _, v in ipairs(c.getIntersecting(data.easybox.x, data.easybox.y, data.easybox.x + data.easybox.width, data.easybox.y + data.easybox.height)) do if (k == 1 and redstone.blockImmovable(v)) or (k == 2 and redstone.npcImmovable(v)) or (k == 3 and data.frameY == 2 and redstone.playerImmovable(v)) then return -1 elseif ((k == 1 and not redstone.blockIgnore(v)) or (k == 2 and not redstone.npcIgnore(v))) or (k == 3) and v ~= n then insert(list, v) end end end return list end local function coordHardPush(n, d, l) if d == 0 then return n.x - l, n.y, l, n.height elseif d == 1 then return n.x, n.y - l, n.width, l elseif d == 2 then return n.x + n.width, n.y, l, n.height else return n.x, n.y + n.height, n.width, l end end local function getHard(n, data, l) local list = {} local x, y, w, h = coordHardPush(n, data.frameX, l) for k, c in ipairs({Block, NPC}) do for _, v in ipairs(c.getIntersecting(x, y, x + w, y + h)) do if (k == 1 and not redstone.blockIgnore(v)) or (k == 2 and not redstone.npcIgnore(v)) and v ~= n then insert(list, v) end end end x, y, w, h = coordHardPush(n, data.frameX, l + 32) for _, v in ipairs(Player.getIntersecting(x, y, x + w, y + h)) do -- Misc.dialog("A")/ if not redstone.playerIgnore(v) then insert(list, v) end end return list end function piston.onRedTick(n) local data = n.data data.observ = false if data.power > 0 then piston.updateRedArea(n) local l = findFirstEmpty(n, data) if l == -1 then data.power = 0 return end local easylist = getEasy(n, data) if type(easylist) == "number" then data.power = 0 return end local hardlist = getHard(n, data, l) if redstone.onScreenSound(n) then SFX.play(sfxextend) end for _, v in ipairs(hardlist) do v.x, v.y = v.x + data.pistdiff*data.direction.x, v.y + data.pistdiff*data.direction.y end for _, v in ipairs(easylist) do v.data = v.data or {} v.data.stick = lunatime.tick() if data.frameX == 0 then v.x = n.x - data.pistdiff - v.width elseif data.frameX == 1 then v.y = n.y - data.pistdiff - v.height elseif data.frameX == 2 then v.x = n.x + n.width + data.pistdiff else v.y = n.y + n.height + data.pistdiff end end Block.spawn(1,0,0):delete() transformPiston(n, n.data) end end local function shouldpull(n, k, v) if k == 1 then if redstone.npcImmovable(v) or redstone.npcIgnore(v) then return false end elseif k == 2 then if redstone.blockImmovable(v) or redstone.blockIgnore(v) then return false end elseif k == 3 and n.data.frameY == 2 then if redstone.playerImmovable(v) or redstone.playerIgnore(v) then return false end end if piston.config.alwayspull or n.data.frameY == 2 then return true else return not (v.data and v.data.stick and (lunatime.tick() - v.data.stick <= 6)) end end function piston.ext_onTick(n) local data = n.data if data.power == 0 then piston.updateRedArea(n) n.x = n.x - data.offX n.y = n.y - data.offY data.frameX = data.origframe n:transform(piston.id) data.observ = true if redstone.onScreenSound(n) then SFX.play(sfxretract) end if data.frameY > 0 then for k, f in ipairs({NPC, Block, Player}) do for _, v in ipairs(f.getIntersecting(data.pullbox.x, data.pullbox.y, data.pullbox.x + data.pullbox.width, data.pullbox.y + data.pullbox.height)) do if shouldpull(n, k, v) and not v ~= n then v.x = v.x - data.pistdiff*data.direction.x v.y = v.y - data.pistdiff*data.direction.y end end end Block.spawn(1,0,0):delete() end data.powerTimer = 0 else if data.powerTimer == 1 then data.observ = false end data.powerTimer = data.powerTimer + 1 end data.power = 0 end function piston.onRedDraw(n) redstone.drawNPC(n) end redstone.register(piston) return piston