This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Tag365's profile picture

Can't place blocks in my multiplayer game

Started by Tag365, 06 April 2015 - 02:31 PM
Tag365 #1
Posted 06 April 2015 - 04:31 PM
This will be a game where you can build stuff and allows you to play with other people. I currently have a few blocks in the script and will add more features.

The problem I need to fix is a bug that results in block updates not updating the terrain in the game, client or server side. Clicking on the screen is supposed to cause the block you clicked to turn into dirt or grass, however it is not updating my world at all. It sends chunk information just fine, so why can't it update a single block??

Here is the script source.


local tArgs = {...}
term.redirect(term.native())
print("Please Wait...")

local sides = {"left", "right", "front", "back", "top", "bottom"}
local serialize, unserialize = textutils.serialize, textutils.unserialize
local scrwidth, scrheight = term.getSize()
local scrcenx, scrceny = math.floor(scrwidth*.5), math.ceil(scrheight*.5)
--term.setBackgroundColor(colors.blue)

local function yield()
        os.queueEvent( "sleep" )
        coroutine.yield( "sleep" )
end

-- Find a modem
function getModem()
	while true do
		for k, v in ipairs(sides) do
			if peripheral.getType(v) == "modem" then
				selectedside = v
				return v
			end
		end
		sleep()
	end
end
getModem()

rednet.open(selectedside)

local draw = {}
local world = {}
local save = {}
local server = {}
local client = {}
local colorTab = {["0"] = 1, ["1"] = 2, ["2"] = 2^2, ["3"] = 2^3, ["4"] = 2^4, ["5"] = 2^5, ["6"] = 2^6, ["7"] = 2^7, ["8"] = 2^8, ["9"] = 2^9, ["A"] = 2^10, ["B"] = 2^11, ["C"] = 2^12, ["D"] =  2^13, ["E"] = 2^14, ["F"] = 2^15}
local blocks = {
{"3", "3", " "}, {"7", "8", "-"}, {"7", "8", "0"}, {"5", "C", "~"}, {"C", "C", " "}, {"9", "8", "8"}, {"C", "8", "8"}
}
local servertimer = os.startTimer(.25)
local myCom = os.getComputerID()
local localposx, localposy = 0, 0

-- set table variables
world.currentWorld = "NewWorld"
world.terrain = {}
world.renderDistance = 4
world.seed = 1
save.savedFilesDirectory = "CrBu/"
server.connected = {}
server.coTab = {}
client.connectedTo = -1

-- Draw API --
function draw.clear()
	term.setCursorPos(1, 1)
	term.clear()	
end

function draw.setColorsToBlock(blockID)
	term.setTextColor(colorTab[blocks[blockID][1]])
	term.setBackgroundColor(colorTab[blocks[blockID][2]])
end

function draw.terrainChunk(x, y)
	local localposx, localposy = localposx - scrcenx, localposy - scrceny
	local setColorsToBlock = draw.setColorsToBlock
	--term.clear()
	if not world.terrain[x] then return end
	if not world.terrain[x][y] then return end
	local chunk = world.terrain[x][y]
	local x, y = x*16, y*16
	for x2=1, 16 do
		for y2=1, 16 do
			if y2 + y - localposy > 0 and y2 + y - localposy <= scrheight and x2 + x - localposx > 0 and x2 + x - localposx <= scrwidth then
				if chunk[x2][y2][1] > 1 then
					setColorsToBlock(chunk[x2][y2][1])
					term.setCursorPos((x2 + x) - localposx, (y2 + y) - localposy)
					term.write(blocks[chunk[x2][y2][1]][3])
				end
			end
		end
	end
end

-- Draws the terrain.
function draw.terrain()
	local localposx, localposy = localposx - scrcenx, localposy - scrceny
	local size = world.renderDistance
	for x=-size + math.floor(localposx/16), size + math.floor(localposx/16) do
		for y=-size + math.floor(localposy/16), size + math.floor(localposy/16) do
			draw.terrainChunk(x, y)
		end
	end
end

-- Draws the heads up display.
function draw.hud()
	term.setTextColor(colors.white)
	term.setBackgroundColor(colors.black)
	term.setCursorPos(1, 1)
	term.write("Position: "..localposx..", "..localposy)
end

-- Draws the player list.
function draw.playerList()
	
	
end

-- Use this to draw a game frame.
function draw.game()
	term.setBackgroundColor(colors.blue)
	term.clear()
	draw.terrain()
	draw.hud()
	term.setCursorPos(scrcenx, scrceny)
	term.write("A")
end

-- World API --
function world.generateChunk(x, y)
	--print("Generating chunk "..x, y)
	if not world.terrain[x] then
		world.terrain[x] = {}
	end
	if not world.terrain[x][y] then
		world.terrain[x][y] = {}
	end
	local chunk = world.terrain[x][y]
	for x2=1, 16 do
		chunk[x2] = {}
		for y2=1, 16 do
			if y2 + y*16 < -127 then -- Generate Air
				chunk[x2][y2] = {1}
			elseif y2 + y*16 < -126 then -- Generate Grass
				chunk[x2][y2] = {4}
			elseif y2 + y*16 < -123 then -- Generate Dirt
				chunk[x2][y2] = {5}
			else
				if math.random() > .98 then
					chunk[x2][y2] = {6}
				elseif math.random() > .88 then
					chunk[x2][y2] = {7}
				else
					chunk[x2][y2] = {2}
				end
			end
		end
	end
end

function world.unloadChunk(x, y)
	save.saveChunk(x, y)
	world.terrain[x][y] = nil
end

-- Save API --
function save.saveChunk(x, y)
	local chunk = world.terrain[x][y]
	
end

-- Saves the world.
function save.saveWorld()
	
	
end

function save.loadChunk(x, y)
	world.generateChunk(x, y)
end

function save.loadWorld()
	
end

-- Server API --
-- Run when a new player has connected for the first time
function server.newPlayer(player)
	server.connected[player] = {0, 0}
end

function server.loadPlayer(player)
	server.connected[player] = {0, 0}
	--local file = 
end

-- Used to set up a player for gameplay
function server.initalizePlayer(player)
	if fs.exists(save.savedFilesDirectory..world.currentWorld.."/"..player) then
		server.newPlayer(player)
	else
		server.loadPlayer(player)
	end
end

-- Used to safely unload the player's inventory
function server.savePlayer(player)
	
end

-- Coroutine that sends a chunk to a player.
function server.sendChunk(player, x, y)
	if world.terrain[x] then
		if not world.terrain[x][y] then
			save.loadChunk(x, y)
		end
	else
		save.loadChunk(x, y)
	end
	local message = serialize({"Requested Chunk", x, y, world.terrain[x][y]})
	for k=1, 4 do
		rednet.send(player, message)
		sleep()
	end
end

-- Coroutine that handles a server.
function server.handleServer()
	
end

function server.shutDownServer()
	
	
end


-- Run when the timer runs out so we send an update to the computers
function server.sendUpdate()
	
	
	--servertimer = os.startTimer(.25)
end

function server.isPlayerConnected()
	return true
end

function server.updateBlock(chunkX, chunkY, x, y, value)
	print("Received Block Update.")
	world.terrain[chunkX][chunkY][x][y] = value
	local string = serialize({"Block Updated", chunkX, chunkY, x, y, value})
	for k=1, 2 do
		sleep()
		rednet.broadcast(string)
	end
end

function server.hearEvents()
while true do
	--yield()
	for k, v in pairs(server.coTab) do
		if coroutine.status(v) ~= "dead" then
			coroutine.resume(v)
		end
	end
	local event, p1, p2, p3, p4, p5 = os.pullEvent()
	if event == "timer" then
		server.sendUpdate()
	elseif event == "rednet_message" then
		if p2 == "Connect" then
			server.initalizePlayer(p1)
		elseif server.isPlayerConnected(p1) then
			local values = unserialize(p2)
			if values[1] == "Chat" then
				--rednet.broadcast()
			elseif values[1] == "Update Block" then
				local v = values
				rednet.broadcast(serialize({"Block Updated", v[2], v[3], v[4], v[5], v[6]}))
				--local co, err = coroutine.create(server.updateBlock, tonumber(v[2]), tonumber(v[3]), tonumber(v[4]), tonumber(v[5]), tonumber(v[6]))
				--print(coroutine.resume(co, v[2], v[3], v[4], v[5], v[6]))
				--server.coTab[#server.coTab + 1] = co
			elseif values[1] == "NeedsNewChunk" then
				local co, err = coroutine.create(server.sendChunk, p1, tonumber(values[2]), tonumber(values[3]))
				coroutine.resume(co, p1, values[2], values[3])
				server.coTab[#server.coTab + 1] = co
			elseif values[1] == "Move Left" then
				server.connected[p1][1] = server.connected[p1][1] - 1
				rednet.send(p1, serialize({"Position Update", server.connected[p1][1], server.connected[p1][2]}))
			elseif values[1] == "Move Right" then
				server.connected[p1][1] = server.connected[p1][1] + 1
				rednet.send(p1, serialize({"Position Update", server.connected[p1][1], server.connected[p1][2]}))
			elseif values[1] == "Move Up" then
				server.connected[p1][2] = server.connected[p1][2] - 1
				rednet.send(p1, serialize({"Position Update", server.connected[p1][1], server.connected[p1][2]}))
			elseif values[1] == "Move Down" then
				server.connected[p1][2] = server.connected[p1][2] + 1
				rednet.send(p1, serialize({"Position Update", server.connected[p1][1], server.connected[p1][2]}))
			elseif values[1] == "Disconnect" then
				server.coTab[#server.coTab + 1] = coroutine.create(server.savePlayer, p1)
			end
		end
	end
end
end

function server.initalizeServer()
	server.isServer = true
	-- Now that server has started connect to the internal server.
	while true do
		parallel.waitForAny(server.hearEvents, client.handleClient)
		--sleep()
	end
end

-- Client API --


-- Sends an update to the server.
function client.sendUpdate(values)
	for i=1, 1 do
		sleep()
		rednet.send(client.connectedTo, serialize(values))
	end
end

function client.loadChunksInitial(sz)
	for x=math.floor(localposx/16) - sz, math.floor(localposx/16) + sz do
		if not world.terrain[x] then world.terrain[x] = {} end
		for y=math.floor(localposy/16) - sz, math.floor(localposy/16) + sz do
			client.sendUpdate({"NeedsNewChunk", x, y})
			--sleep()
		end
	end	
end

function client.loadChunksCoroutine(sz)
	while true do
		for x=math.floor(localposx/16) - sz, math.floor(localposx/16) + sz do
			if not world.terrain[x] then world.terrain[x] = {} end
			for y=math.floor(localposy/16) - sz, math.floor(localposy/16) + sz do
				if not world.terrain[x][y] then
					client.sendUpdate({"NeedsNewChunk", x, y})
					coroutine.yield()
					sleep()
				end
			end
		end
	end
end

-- Connects to a server.
function client.connect(computer)
	print("Connecting to server...")
	rednet.send(computer, "Connect")
	client.connectedTo = computer
	print("Loading Chunks...")
	client.sendUpdate({"NeedsNewChunk", math.floor(localposx/16), math.floor(localposy/16)})
	for sz=1, 2 do
		client.loadChunksInitial(sz)
	end
	draw.game()
end

function client.onRednetMessage(param1, param2, param3, param4)
	
end

function client.updateBlock(chunkX, chunkY, x, y, newBlock)
	for k=1, 10 do
		sleep()
		client.sendUpdate({"Update Block", chunkX, chunkY, x, y, newBlock})
	end
end

function client.handleClient(computer)
	computer = computer or myCom
	client.connect(computer)
	--local loadco = coroutine.create(client.loadChunksCoroutine, 2)
	local blockupdate = coroutine.create(client.updateBlock, 0, 0, 1, 1, 4)
	while true do
		if coroutine.status(blockupdate) ~= "dead" then
			coroutine.resume(blockupdate)
			--print(coroutine.resume(blockupdate))
		end
		--coroutine.resume(loadco, 2)
		--yield()
		local event, p1, p2, p3, p4, p5 = os.pullEvent()
		if event == "rednet_message" then
			--if p1 == client.connectedTo then
				local values = unserialize(p2) or {}
				if values[1] == "Position Update" then
					localposx, localposy = values[2], values[3]
					if not world.terrain[math.floor(localposx/16)] then
						world.terrain[math.floor(localposx/16)] = {}
					end
					if not world.terrain[math.floor(localposx/16)][math.floor(localposy/16)] then
						client.sendUpdate({"NeedsNewChunk", math.floor(localposx/16), math.floor(localposy/16)})
					end
				elseif values[1] == "Block Updated" then
					local v = values[2]
					world.terrain[v[1]][v[2]][v[3]][v[4]] = v[5]
					draw.game()
				elseif values[1] == "Requested Chunk" then
					world.terrain[values[2]][values[3]] = values[4]
				end
			--end
		end
		if event == "key" then
			if p1 == keys.up then
				client.sendUpdate({"Move Up"})
				localposy = localposy - 1
			elseif p1 == keys.down then
				client.sendUpdate({"Move Down"})
				localposy = localposy + 1
			elseif p1 == keys.left then
				client.sendUpdate({"Move Left"})
				localposx = localposx - 1
			elseif p1 == keys.right then
				client.sendUpdate({"Move Right"})
				localposx = localposx + 1
			end
			draw.game()
		end
		if event == "mouse_click" then
			--local localposx, localposy = localposx - scrcenx, localposy - scrceny
			local clickedx, clickedy = (p2 - scrcenx) - localposx, (p3 - scrceny) - localposy
			local chunkX, chunkY = math.floor((clickedx)/16), math.floor((clickedy)/16)
			local x, y = math.floor((clickedx) - (chunkX*16)) + 1, math.floor((clickedy) - (chunkY*16)) + 1
			blockupdate = coroutine.create(client.updateBlock, chunkX, chunkY, x, y, 4)
			--print(blockupdate)
			--print(chunkX..", "..chunkY..", "..x..", "..y..", "..4)
			--draw.game()
		end
	end
end

-- End of Functions --
draw.clear()

-- Main Loop
if not tArgs[1] then
	print("Starting Server...")
	server.initalizeServer()
else
	client.handleClient(tonumber(tArgs[1]))
end
Edited on 06 April 2015 - 02:37 PM
Bomb Bloke #2
Posted 06 April 2015 - 10:25 PM
When you resume "blockupdate" the first time, you need not pass event data. Every time after that until it's dead, you do - and you have to pass only the events it's asking for, those being timer events (due to its use of sleep).
Tag365 #3
Posted 07 April 2015 - 08:57 PM
I know that, so why is it not working?
Bomb Bloke #4
Posted 07 April 2015 - 11:10 PM
Because you're not doing that. You're simply resuming your coroutine without ever paying attention to what event data it wants, or passing any event data to it.

local function myFunc()
	for i = 1, 10 do
		print(i)
		sleep(1)
	end
end

-- Initialise coroutine:
local myCoroutine = coroutine.create(myFunc)
local ok, requestedEvent = coroutine.resume(myCoroutine)

-- Run coroutine to completion:
while coroutine.status(myCoroutine) ~= "dead" do
	myEvent = {os.pullEvent(requestedEvent)}
	ok, requestedEvent = coroutine.resume(myCoroutine, unpack(myEvent))
end

-- Coroutine has finished execution.
Tag365 #5
Posted 09 April 2015 - 07:10 PM
I'm not passing event data to client.updateblock.
Tag365 #6
Posted 09 April 2015 - 07:32 PM
I had to move the client block update code for it to start working. Now I can place blocks.
Edited on 09 April 2015 - 05:32 PM