-- -- 3d Blocks Builder by LJ Sonic -- -- Main idea from Ors -- -- You may use this script in your own mods, as long as you give credits. -- -- Blocks Builder thinker parameters: -- Front texture offsets: number of blocks on x-y axis -- Back texture offsets: top-left corner coordinates -- Front sector floor height: bottom of the Blocks Builder -- Front sector ceiling height: number of blocks on z axis -- Back sector floor height: number of available blocks types -- Back sector ceiling height: index of the first FOF sector -- Lenght: blocks size -- Flags: -- Block Enemies: disable direct build -- Touch Pad parameters: -- Front texture offsets: top-left corner coordinates -- Tag: Touch Pad ID -- x-y variations: size of the keys on x-y axis -- Console variables local aimBuild = CV_RegisterVar({"aimbuild", "On", 0, CV_OnOff}) local allowFloating = CV_RegisterVar({"floating", "On", CV_NETVAR, CV_OnOff}) local buildingDistance = CV_RegisterVar({"buildingdistance", "384", CV_NETVAR, CV_Natural}) local aimBuildPrecision = CV_RegisterVar({"aimbuildprecision", "4", CV_NETVAR, CV_Natural}) local buildingDelay = CV_RegisterVar({"buildingdelay", "6", CV_NETVAR, CV_Natural}) -- List of Blocks Builder thinkers local datas = nil -- Returns the index of the control sector associated with the given coordinates local function getIndex(tk, x, y, z, n) return tk.index + tk.sizeX * tk.sizeY * tk.sizeZ * n + tk.sizeX * tk.sizeY * z + tk.sizeX * y + x end -- Returns the coordinates relative to the player's position on a Blocks Builder grid local function getBlockCoordinates(tk, x, y, z) if x < tk.x x = $1 - tk.size + 1 end if y > tk.y y = $1 + tk.size - 1 end if z < tk.z z = $1 - tk.size + 1 end return (x - tk.x) / tk.size, (tk.y - y) / tk.size, (z - tk.z) / tk.size end -- Returns the coordinates relative to the player's aiming on a Blocks Builder grid for a given distance local function getAimedBlockCoordinates(tk, player, distance) return getBlockCoordinates(tk, player.mo.x + distance * FixedMul(cos(player.mo.angle), cos(player.aiming)), player.mo.y + distance * FixedMul(sin(player.mo.angle), cos(player.aiming)), player.viewz + distance * sin(player.aiming)) end -- Returns the coordinates relative to the player's position on a touch pad local function getKeyCoordinates(line, mobj) return (mobj.x - line.frontside.textureoffset) / line.dx, (line.frontside.rowoffset - mobj.y) / line.dy end local function checkForPlayer(player, tk, x, y, z) if player.mo.x + 16 * FRACUNIT > tk.x + x * tk.size and player.mo.x - 16 * FRACUNIT < tk.x + (x + 1) * tk.size and player.mo.y - 16 * FRACUNIT < tk.y - y * tk.size and player.mo.y + 16 * FRACUNIT > tk.y - (y + 1) * tk.size and player.mo.z <= tk.z + (z + 1) * tk.size --and player.mo.z + 56 * FRACUNIT > tk.z + z * tk.size -- Placing blocks over players would kill them --if player.mo.z + 56 * FRACUNIT < tk.z + z * tk.size return 2 else return 1 end return 1 end return 0 end local function checkForPlayers(tk, x, y, z) local someoneHere = 0 local playerHere = 0 for player in players.iterate playerHere = checkForPlayer(player, tk, x, y, z) if playerHere == 1 return 1 end if playerHere == 2 someoneHere = 2 end end return someoneHere end -- Creates a new Blocks Builder thinker local function addBlocksBuilderThinker(line, mobj) if not datas datas = P_SpawnMobj(0, 0, 0, MT_GFZFLOWER1) datas.flags = MF_NOTHINK | MF_NOSECTOR datas.blocksBuilderThinkers = {} end table.insert(datas.blocksBuilderThinkers, { index = line.backsector.ceilingheight / FRACUNIT, size = FixedHypot(line.dx, line.dy), sizeX = line.frontside.textureoffset / FRACUNIT, sizeY = line.frontside.rowoffset / FRACUNIT, sizeZ = line.frontsector.ceilingheight / FRACUNIT, x = line.backside.textureoffset, y = line.backside.rowoffset, z = line.frontsector.floorheight, typesNumber = line.backsector.floorheight / FRACUNIT, flags = line.flags, blocks = {} }) local tk = datas.blocksBuilderThinkers[#datas.blocksBuilderThinkers] for i1 = 1, tk.sizeX table.insert(tk.blocks, {}) for i2 = 1, tk.sizeY table.insert(tk.blocks[i1], {}) for i3 = 1, tk.sizeZ table.insert(tk.blocks[i1][i2], 0) end end end end -- Places or removes a block of given type to a specific location local function setBlock(tk, x, y, z, n) if n == nil print("ERROR: one of the parameters is a nil value !") return end if n > tk.typesNumber print("Block type "..n.." does not exist !") return end if x < 0 or x >= tk.sizeX or y < 0 or y >= tk.sizeY print("Out of limits !") return end if z >= tk.sizeZ print("Blocks height cannot exceed "..(tk.sizeZ - 1).." !") return end local top, bottom local block = tk.blocks[x + 1][y + 1][z + 1] local sector if block == n return end if block != 0 --sector = sectors[getIndex(tk, x, y, z, tk.blocks[x + 1][y + 1][z + 1] - 1)] --sector.floorheight = tk.z - 8 * FRACUNIT --sector.ceilingheight = tk.z - 8 * FRACUNIT top = z while top < tk.sizeZ if tk.blocks[x + 1][y + 1][top + 1] != block break end top = $1 + 1 end top = $1 - 1 bottom = z while bottom >= 0 if tk.blocks[x + 1][y + 1][bottom + 1] != block break end bottom = $1 - 1 end bottom = $1 + 1 if top != z sectors[getIndex(tk, x, y, top, block - 1)].floorheight = tk.z + (z + 1) * tk.size else sector = sectors[getIndex(tk, x, y, top, block - 1)] sector.floorheight = tk.z - 8 * FRACUNIT sector.ceilingheight = tk.z - 8 * FRACUNIT end if bottom != z sector = sectors[getIndex(tk, x, y, z - 1, block - 1)] sector.ceilingheight = tk.z + z * tk.size sector.floorheight = tk.z + bottom * tk.size end end tk.blocks[x + 1][y + 1][z + 1] = n if n != 0 --sector = sectors[getIndex(tk, x, y, z, n - 1)] --sector.ceilingheight = tk.z + (z + 1) * tk.size --sector.floorheight = tk.z + z * tk.size top = z while top < tk.sizeZ if tk.blocks[x + 1][y + 1][top + 1] != n break end top = $1 + 1 end top = $1 - 1 bottom = z while bottom >= 0 if tk.blocks[x + 1][y + 1][bottom + 1] != n break end bottom = $1 - 1 end bottom = $1 + 1 for i = bottom, top - 1 sector = sectors[getIndex(tk, x, y, i, n - 1)] sector.floorheight = tk.z - 8 * FRACUNIT sector.ceilingheight = tk.z - 8 * FRACUNIT end sector = sectors[getIndex(tk, x, y, top, n - 1)] sector.ceilingheight = tk.z + (top + 1) * tk.size sector.floorheight = tk.z + bottom * tk.size //Increase blocks counter else //Decrease blocks counter end end local function fill(player, x1, y1, z1, x2, y2, z2, n, tk) if player and (not x1 or not y1 or not z1 or not z2 or not y2 or not z2 or not n) CONS_Printf(player, "Parameters: [thinker]") return end x1, y1, z1, x2, y2, z2, n = tonumber(x1), tonumber(y1), tonumber(z1), tonumber(x2), tonumber(y2), tonumber(z2), tonumber(n) if not(x1 >= 0) or not(y1 >= 0) or not(z1 >= 0) or not(x2 >= 0) or not(y2 >= 0) or not(z2 >= 0) or not(n >= 0) CONS_Printf(player, "Coordinates and type can only be positive numbers !") return end tk = tonumber(tk) if not tk and tk != 0 tk = 1 end if tk <= 0 or tk > #datas.blocksBuilderThinkers CONS_Printf(player, "Blocks Builder thinker #"..tk.." doesn't exist !") return end tk = datas.blocksBuilderThinkers[tk] for player in players.iterate if player.mo.x + player.mo.radius >= tk.x and player.mo.x - player.mo.radius <= tk.x + tk.sizeX * tk.size and player.mo.y - player.mo.radius <= tk.y and player.mo.y + player.mo.radius >= tk.y - tk.sizeY * tk.size return end end for x = x1, x2 for y = y1, y2 for z = z1, z2 setBlock(tk, x, y, z, n) end end end end local function fillFloor(player, z, n, tk) if not x1 or not y1 or not z1 or not z2 or not y2 or not z2 or not n CONS_Printf(player, "Parameters: [thinker]") return end z, n = tonumber(z), tonumber(n) if not(z >= 0) or not(n >= 0) CONS_Printf(player, "Coordinates and type can only be positive numbers !") return end tk = tonumber(tk) if not tk and tk != 0 tk = 1 end if tk <= 0 or tk > #datas.blocksBuilderThinkers CONS_Printf(player, "Blocks Builder thinker "..tk.." doesn't exist !") return end tk = datas.blocksBuilderThinkers[tk] for x = 0, tk.x - 1 for y = 0, tk.y - 1 setBlock(tk, x, y, z, n) end end end -- When a player walks on a touch pad local function pressTouchPad(line, mobj) local x, y = getKeyCoordinates(line, mobj) if checkForPlayers(datas.blocksBuilderThinkers[line.tag], x, y, mobj.player.z) != 0 return end if mobj.player.addingBlocks setBlock(datas.blocksBuilderThinkers[line.tag], x, y, mobj.player.z, mobj.player.blocksType) else setBlock(datas.blocksBuilderThinkers[line.tag], x, y, mobj.player.z, 0) end end -- When a player presses a blocks height switcher local function pressHeightSwitcher(line, mobj) mobj.player.z = line.frontsector.floorheight / FRACUNIT end -- When a player presses a blocks type switcher local function pressTypeSwitcher(line, mobj) mobj.player.blocksType = line.frontsector.floorheight / FRACUNIT end -- When a player presses a fill button local function pressFillButton(line, mobj) fill(nil, line.frontside.textureoffset / FRACUNIT, line.frontside.rowoffset / FRACUNIT, line.frontsector.floorheight / FRACUNIT, line.backside.textureoffset / FRACUNIT, line.backside.rowoffset / FRACUNIT, line.backsector.floorheight / FRACUNIT, line.frontsector.ceilingheight / FRACUNIT, line.tag) end addHook("LinedefExecute", addBlocksBuilderThinker, "BLOCKSBUILDERTHINKER") addHook("LinedefExecute", pressTouchPad, "TOUCHPAD") addHook("LinedefExecute", pressHeightSwitcher, "HEIGHTSWITCHER") addHook("LinedefExecute", pressTypeSwitcher, "TYPESWITCHER") addHook("LinedefExecute", pressFillButton, "FILL") local function directBuilding(player, tk) local px, py, pz = getBlockCoordinates(tk, player.mo.x, player.mo.y, player.mo.z)// local x, y, z -- Old way for placing blocks --if angle <= ANGLE_67h and angle > _292h x = x + 1 end --if angle <= ANGLE_157h and angle > ANGLE_22h y = y - 1 end --if angle <= ANGLE_247h or angle > ANGLE_112h x = x - 1 end --if angle <= ANGLE_337h and angle > ANGLE_202h y = y + 1 end if aimBuild.value if player.pressingBuildKey == 0 player.pressingBuildKey = buildingDelay.value - 1 else player.pressingBuildKey = $1 - 1 return end //px, py, pz = player.mo.x, player.mo.y, player.mo.viewz local distance = 0 while distance <= buildingDistance.value x, y, z = getAimedBlockCoordinates(tk, player, distance) if x >= 0 and x < tk.sizeX and y >= 0 and y < tk.sizeY and z < tk.sizeZ if z < 0 break end if tk.blocks[x + 1][y + 1][z + 1] != 0 break end end distance = $1 + aimBuildPrecision.value end if x < 0 or x >= tk.sizeX or y < 0 or y >= tk.sizeY return end if distance <= buildingDistance.value x, y, z = getAimedBlockCoordinates(tk, player, distance - aimBuildPrecision.value) /*if checkForPlayer(player, tk, x, y, z) != 0 player.addingBlocks = false x, y, z = getAimedBlockCoordinates(tk, player, distance) if z < 0 return end else player.addingBlocks = true x, y, z = getAimedBlockCoordinates(tk, player, distance - aimBuildPrecision.value) end*/ if player.cmd.buttons & BT_CUSTOM2 player.addingBlocks = false x, y, z = getAimedBlockCoordinates(tk, player, distance) if z < 0 return end else player.addingBlocks = true x, y, z = getAimedBlockCoordinates(tk, player, distance - aimBuildPrecision.value) end local oldFlags = {} local someoneHere = checkForPlayers(tk, x, y, z) if someoneHere == 1 return end if someoneHere == 2 for player in players.iterate player.oldFlags = player.mo.flags ////player.mo.flags = $1 | (MF_NOCLIP)// | MF_NOBLOCKMAP | MF_NOSECTOR | MF_NOCLIPHEIGHT) end end if player.addingBlocks setBlock(tk, x, y, z, player.blocksType) else setBlock(tk, x, y, z, 0) end if someoneHere == 2 for player in players.iterate //player.mo.flags = player.oldFlags end end end else //px, py, pz = getBlockCoordinates(tk, player.mo.x, player.mo.y, player.mo.z) x, y, z = getBlockCoordinates(tk, player.mo.x + tk.size / FRACUNIT * FixedMul(cos(player.mo.angle), cos(player.aiming)), player.mo.y + tk.size / FRACUNIT * FixedMul(sin(player.mo.angle), cos(player.aiming)), player.viewz + tk.size / FRACUNIT * sin(player.aiming)) if x == px and y == py and z == pz x, y, z = getBlockCoordinates(tk, player.mo.x + tk.size / FRACUNIT * FixedMul(cos(player.mo.angle), cos(player.aiming)) * 3 / 2, player.mo.y + tk.size / FRACUNIT * FixedMul(sin(player.mo.angle), cos(player.aiming)) * 3 / 2, player.viewz + tk.size / FRACUNIT * sin(player.aiming) * 3 / 2) end if x < 0 or x >= tk.sizeX or y < 0 or y >= tk.sizeY or z < 0 or z >= tk.sizeZ return end if player.pressingBuildKey == 0 if tk.blocks[x + 1][y + 1][z + 1] == 0 player.addingBlocks = true else player.addingBlocks = false end end local oldFlags = {} local someoneHere = checkForPlayers(tk, x, y, z) if someoneHere == 1 return end if someoneHere == 2 for player in players.iterate player.oldFlags = player.mo.flags ////player.mo.flags = $1 | (MF_NOCLIP)// | MF_NOBLOCKMAP | MF_NOSECTOR | MF_NOCLIPHEIGHT) end end if player.addingBlocks if tk.blocks[x + 1][y + 1][z + 1] == 0 setBlock(tk, x, y, z, player.blocksType) end else setBlock(tk, x, y, z, 0) end if someoneHere == 2 for player in players.iterate //player.mo.flags = player.oldFlags end end end end -- Allows players building directly in the Blocks Builder local function checkBuildingPlayers() for player in players.iterate if player.floating and allowFloating.value local angle = player.mo.angle - 16384 * FRACUNIT if angle < -32768 * FRACUNIT angle = $1 + 65536 * FRACUNIT end player.mo.momx, player.mo.momy, player.mo.momz = 0, 0, 0 if player.cmd.forwardmove > 0 P_InstaThrust(player.mo, player.mo.angle, 16 * FRACUNIT) end if player.cmd.forwardmove < 0 P_InstaThrust(player.mo, player.mo.angle, -16 * FRACUNIT) end if player.cmd.sidemove < 0 P_Thrust(player.mo, angle, -16 * FRACUNIT) end if player.cmd.sidemove > 0 P_Thrust(player.mo, angle, 16 * FRACUNIT) end if P_IsObjectOnGround(player.mo) player.floating = false player.mo.flags = $1 & ~MF_NOGRAVITY else if player.cmd.buttons & BT_JUMP P_SetObjectMomZ(player.mo, 12 * FRACUNIT) end if player.cmd.buttons & BT_USE P_SetObjectMomZ(player.mo, -12 * FRACUNIT) end end end if player.cmd.buttons & (BT_CUSTOM1 | BT_TOSSFLAG | BT_CUSTOM2 | BT_CUSTOM3 | BT_USE) if not(player.pflags & PF_NIGHTSMODE) and not(player.pflags & PF_STASIS) if player.cmd.buttons & BT_USE if player.pflags & PF_JUMPED and allowFloating.value player.floating = true player.mo.flags = $1 | MF_NOGRAVITY player.pflags = $1 & ~PF_JUMPED player.mo.momx, player.mo.momy, player.mo.momz = 0, 0, 0 end else for _, tk in ipairs(datas.blocksBuilderThinkers) if player.mo.x >= tk.x - 4 * tk.size and player.mo.x <= tk.x + (tk.sizeX + 5) * tk.size and player.mo.y <= tk.y + 4 * tk.size and player.mo.y >= tk.y - (tk.sizeY + 5) * tk.size if player.cmd.buttons & BT_CUSTOM3 if not player.pressingTypeKey player.blocksType = $1 + 1 if player.blocksType > tk.typesNumber player.blocksType = 1 end player.pressingTypeKey = true end else if not(tk.flags & 1<<1) directBuilding(player, tk) end end end end end end else -- Key released player.pressingBuildKey = 0 player.pressingTypeKey = false end end end addHook("ThinkFrame", checkBuildingPlayers) -- On map loading local function mapLoad() for player in players.iterate player.z = 0 player.addingBlocks = true player.blocksType = 1 player.pressingTypeKey = false end end -- On map switching local function mapChange() datas = nil end addHook("MapLoad", mapLoad) addHook("MapChange", mapChange) -- Console commands local function setHeight(player, n) n = tonumber(n) if not n and n != 0 CONS_Printf(player, "You have to specify a height !") return end if n < 0 CONS_Printf(player, "The height has to be a positive number !") return end player.z = n end local function setAddingBlocksOn(player) player.addingBlocks = true end local function setAddingBlocksOff(player) player.addingBlocks = false end local function setBlocksType(player, n) n = tonumber(n) if not n and n != 0 CONS_Printf(player, "You have to specify a block type !") return end if n < 0 CONS_Printf(player, "The block type has to be a positive number !") return end player.blocksType = n end -- Basic commands COM_AddCommand("block", setBlocksType, 0) COM_AddCommand("b", setBlocksType, 0) -- Utility commands COM_AddCommand("z", setHeight, 0) COM_AddCommand("create", setAddingBlocksOn, 0) COM_AddCommand("c", setAddingBlocksOn, 0) COM_AddCommand("remove", setAddingBlocksOff, 0) COM_AddCommand("r", setAddingBlocksOff, 0) COM_AddCommand("fill", fill, 0) COM_AddCommand("fillfloor", fillFloor, 0)