Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- VERSION_NUMBER = '2.8.0'
- VERSION_NAME = 'The Refactoring Conundrum'
- os.loadAPI('loader')
- loader.loadPackages()
- --restore creative to those who had it
- --clear experience points upon using the homecomer or reseting
- --make game to autostart
- --TODO move these to the Game object
- --contains a list of all posible Buildzones (built a bit later)
- local LOCS = {}
- local cleanbuildzone = false
- local catalogue_elements = {} --keep the initialized catalogue elements here instead of in the game object to avoid recursive containment in the table "game"
- local buildzones = {} --keep the initialized buildzones here instead of in the game object to avoid recursive containment in the table "game"
- local number_of_players_adventure --will keep the number of players in adventure mode on the server
- local number_of_players_creative --will keep the number of players in creative mode on the server
- --creates a grid of absolute references (0,0) (1,0)
- --rename to buildGameField
- function buildGrid(w,h)
- debug.log("creating the LOCS table with all buildzones in this game...")
- local grid = readGridFromFile()
- if grid then
- -- grid was read from the file successfully
- debug.log("LOCS table read from the local file")
- else
- --generate file from scratch
- grid = {}
- for z=0,h-1 do
- for x=0,w-1 do
- table.insert(grid,{x=x,z=z,played=false})
- end
- end
- debug.log("LOCS table created from scratch")
- end
- return grid
- end
- function readGridFromFile()
- local result = nil
- --fs.makeDir("/records")
- --local filename = "/records/_buildzones-list.json"
- --local filename = "_buildzones-list.json"
- --local filename_with_path = registry.RECORDS_FOLDER.."/"..filename
- if file_exists(registry.BUILDZONES_LIST_FILE) then
- ---read from file
- result = json.decodeFromFile(registry.BUILDZONES_LIST_FILE)
- end
- return result
- end
- function writeGridToFile()
- fs.makeDir(registry.RECORDS_FOLDER)
- --local filename = "_buildzones-list.json"
- --local filename_with_path = registry.RECORDS_FOLDER.."/"..filename
- local file = fs.open(registry.BUILDZONES_LIST_FILE,"w")
- file.write(json.encodePretty(LOCS))
- file.close()
- end
- --- Constructor for playerdata object
- -- @param name the player minecraft name as a string
- -- @param x the players current X coord
- -- @param y the players current Y coord
- -- @param z the players current Z coord
- -- @table p The new player object
- -- @return p The new player object
- function newPlayerData(name,x,y,z)
- local p = {
- name = name, -- the players minecraft name (String)
- x = x, -- the players last X coord
- y = y, -- the players last Y coord
- z = z -- the players last Z coord
- }
- return p
- end
- --return a list of all players in the game world as player objects
- local function getAllPos(selector)
- --debug.log("starting getAllPos with selector: "..selector)
- --commands.gamerule("logAdminCommands",true) --it seems we need this to be on for successful tp message back
- --os.sleep(2)
- local result, message = commands.tp("@a["..selector.."]","~ ~ ~")
- --commands.gamerule("logAdminCommands",false) --and then disable back off to prevent the game from flooding the chat log
- --local result, message = commands.exec("tp @a["..selector.."] ~ ~ ~")
- local names = {}
- if result == true then
- for i,result in ipairs(message) do
- --debug.log("starting message decoding: "..result)
- local wordpattern = "[^, ]+"
- local numberpattern = "[%-% ]%d+[%.]%d+"
- local words,numbers = {},{}
- --debug.log("finding words: "..result)
- for word in string.gmatch(result, wordpattern) do
- table.insert(words,word)
- end
- --debug.log("finding numbers: "..result)
- for number in string.gmatch(result, numberpattern) do
- table.insert(numbers,number)
- end
- local coords = {
- x = math.floor(numbers[1]),
- y = math.floor(numbers[2]),
- z = math.floor(numbers[3])
- }
- local name = words[2]
- --debug.log("inserting into names list: "..name)
- table.insert(names,newPlayerData(name,coords.x,coords.y,coords.z))
- end
- end
- --debug.log("getAllPos completed")
- return names
- end
- --returns a list of player objects containing all players who are standing on the given block, and who also are in the given selection
- --TODO this must support blocks with variants too
- local function getAllOnBlockType(block,selector)
- local result, message = commands.exec("execute @a["..selector.."] ~ ~ ~ detect ~ ~-1 ~ "..block.." -1 tp @p[r=1] ~ ~ ~")
- local names = {}
- if result == true then
- for i,result in ipairs(message) do
- local wordpattern = "[^, ]+"
- local numberpattern = "[%-% ]%d+[%.]%d+"
- local words,numbers = {},{}
- for word in string.gmatch(result, wordpattern) do
- table.insert(words,word)
- end
- for number in string.gmatch(result, numberpattern) do
- table.insert(numbers,number)
- end
- if numbers[1] and numbers[2] and numbers[3] then
- local coords = {
- x = math.floor(numbers[1]),
- y = math.floor(numbers[2]),
- z = math.floor(numbers[3])
- }
- local name = words[2]
- table.insert(names,newPlayerData(name,coords.x,coords.y,coords.z))
- --print("Found a player - getOnBlock")
- debug.log("player "..name.." is on "..block.." at: "..coords.x..", "..coords.y..", "..coords.z)
- else
- debug.log("Error in getAllOnBlockType: Coordinate Numbers were missing")
- end
- end
- end
- return names
- end
- ---BEGIN HOMECOMER RELATED FUNCTIONS
- --gives a player a HOMECOMER egg with their name on it. Removes all spawn eggs first
- local function giveHomecomer(name)
- commands.clear(name,'spawn_egg')
- commands.give(name,'spawn_egg 1 '..registry.HOMECOMER_VALUE..' {CanPlaceOn:["'..registry.BLOCKS.PHVFLOOR.block..'","'..registry.BLOCKS.CAMP_FLOOR.block..'","'..registry.BLOCKS._matWhiteSmooth.block..'","'..registry.BLOCKS._matWhiteSmooth2.block..'","'..registry.BLOCKS._matYellowSmooth.block..'","'..registry.BLOCKS._matPinkSmooth.block..'","'..registry.BLOCKS._matBlueSmooth.block..'","'..registry.BLOCKS._matPurpleSmooth.block..'","'..registry.BLOCKS._matWhiteTextured.block..'","'..registry.BLOCKS._matYellowTextured.block..'","'..registry.BLOCKS._matPinkTextured.block..'","'..registry.BLOCKS._matBlueTextured.block..'","'..registry.BLOCKS._matPurpleTextured.block..'"],display:{Name:"HOMECOMER - '..name..'",Lore:[Use this on the floor to return to spawn]}}')
- end
- --returns a list of HOMECOMER entities and their names
- local function getHomecomers()
- local result, message = commands.exec("execute @e[type="..registry.HOMECOMER_TYPE.."] ~ ~ ~ tp @e[r=1] ~ ~ ~")
- local names = {}
- if result == true then
- for i,result in ipairs(message) do
- local wordpattern = "[^, ]+"
- local numberpattern = "[%-% ]%d+[%.]%d+"
- local words,numbers = {},{}
- for word in string.gmatch(result, wordpattern) do
- table.insert(words,word)
- --print(word)
- end
- for number in string.gmatch(result, numberpattern) do
- table.insert(numbers,number)
- end
- if numbers[1] and numbers[2] and numbers[3] then
- local coords = {
- x = math.floor(numbers[1]),
- y = math.floor(numbers[2]),
- z = math.floor(numbers[3])
- }
- local name = words[4]
- table.insert(names,newPlayerData(name,coords.x,coords.y,coords.z))
- debug.log("homecomer '"..name.."' is at: "..coords.x..", "..coords.y..", "..coords.z)
- else
- --print("Error: Coordinate Numbers were missing")
- end
- end
- end
- return names
- end
- --- Counts active players
- -- Now that we changed how player lists work, just counting the length
- -- does not reveal how many players are currently playing
- -- we need to count based on status
- -- @param kew A Kew object
- -- @return result The playercount as an Int
- local function countActivePlayers(kew)
- local result = 0
- for name, playerdata in pairs(kew.playerlist) do
- if playerdata.status == registry.PLAYER_STATUS.IN_GAME then
- result = result + 1
- end
- end
- return result
- end
- --- Removes a player from a game queue
- -- As playerlists are now playername indexed, we can just check if the
- -- player is in the list. If the player is in the list, we want to change
- -- his status to 1 (left with Homecomer). End the phase if this was the
- -- last player.
- -- @param game The game object
- -- @param playername The players name as a string
- local function removePlayerFromKew(game,playername)
- for _,kew in pairs(game.queues) do
- if (kew.playerlist[playername]) then
- debug.log("changing status for player '"..playername.."' at buildzone "..kew.buildzone.locid.." to 1 - left-with-homecomer")
- kew.playerlist[playername].status = registry.PLAYER_STATUS.LEFT_WITH_HOMECOMER
- end
- local number_of_active_players = countActivePlayers(kew)
- debug.log("players left in buildzone "..kew.buildzone.locid..": "..number_of_active_players)
- if number_of_active_players == 0 and kew.phase == registry.QUEUE_PHASE.PLAYING then
- --game can be ended as no players are left
- kew.timer = 0
- end
- end
- end
- --teleports a player ot the spawn area
- local function movePlayerToSpawn(playername)
- resetPlayer(playername)
- --print("before TP")
- commands.tp(playername,registry.SPAWN.x,registry.SPAWN.y,registry.SPAWN.z,registry.SPAWN.a1,registry.SPAWN.a2)
- --print("after TP")
- if registry.ANNOUNCE_ENGLISH then
- commands.async.tellraw(playername,'["",{"text":"You used your HOMECOMER and TELEPORTED BACK TO SPAWN","color":"white"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- commands.async.tellraw(playername,'["",{"text":"Du hast den HEIMKEHRER benutzt um dich zurück zum Anfang zu teleportieren.","color":"gold"}]')
- end
- end
- --takes a list of homecomers and deals with all those players, moving them back to spawn and removing them from game
- --also gives them a new homecomer
- local function dealWithHomecomers(game, homecomers)
- for _,homecomer in pairs(homecomers) do
- --remove player from current game if in game
- removePlayerFromKew(game,homecomer.name)
- --teleport them back to spawn
- movePlayerToSpawn(homecomer.name)
- --give a new homecomer
- giveHomecomer(homecomer.name)
- --particle effects
- --teleport message
- end
- --kill all homecomers
- if #homecomers>0 then
- commands.tp("@e[type="..registry.HOMECOMER_TYPE.."]",100000,200,100000)
- commands.kill("@e[type="..registry.HOMECOMER_TYPE.."]")
- os.sleep(#homecomers*0.2)
- end
- end
- ---
- function checkForHomecomers(game)
- --execute on all homecomers
- local homecomers = getHomecomers()
- dealWithHomecomers(game,homecomers)
- end
- --- END OF HOMECOMER FUNCTIONS
- --creates a villager with special items
- local function spawnVillager(x,y,z)
- commands.summon("Villager",x,y,z,'{Invulnerable:1,CustomName:Wool_Seller,Profession:2,Career:1,CareerLevel:6,Offers:{Recipes:[ {buy:{id:emerald,Count:1},sell:{id:wool,Count:'..registry.WOOL_PER_EMERALD..',tag:{CanPlaceOn:["minecraft:diamond_block","minecraft:clay","minecraft:wool","minecraft:stained_hardened_clay"]}}}, {buy:{id:emerald,Count:1},sell:{id:stone_pickaxe,Count:1,Damage:'..131-registry.PICKAXE_USES..',tag:{CanDestroy:["minecraft:wool"]}}} ]}}')
- end
- --displays a time as experience points to a selection of players
- local function displayTime(selector,minutes,seconds)
- --commands.title("@a["..selector.."]","subtitle",'{text:"Time left: '..minutes..":"..seconds..'",color:red,bold:false,underlined:false,italic:false,strikethrough:false,obfuscated:false}')
- commands.async.xp("-1000000L","@a["..selector.."]")
- local secondstot = (minutes * 60) + seconds
- commands.async.xp(tostring(secondstot).."L","@a["..selector.."]")
- end
- --simply runs displayTime on a list of players
- local function displayTimeToGroup(playerlist,minutes,seconds)
- for name,player in pairs(playerlist) do
- displayTime("name="..player.name,minutes,seconds)
- end
- end
- --- Updates the Time (XP bar) for players in a buildzone
- --Uses a new playerlist object to only send the time to the active players
- -- @param A new playerlist object
- -- @param minutes How many minutes left
- -- @param seconds How many seconds are left
- local function displayTimeToPlayers(playerlist,minutes,seconds)
- local activeList = {}
- for name,player in pairs(playerlist) do
- if player.status == registry.PLAYER_STATUS.IN_GAME then
- table.insert(activeList,player)
- end
- end
- displayTimeToGroup(activeList,minutes,seconds)
- end
- local function playerListToString(playerlist)
- --debug.log("converting list to string: "..json.encode(playerlist))
- local s = ""
- if tableLength(playerlist) > 0 then
- --debug.log("it comes to the for loop")
- for name, player in pairs(playerlist) do
- --debug.log("adding name: "..player.name)
- if (player.status) then
- s = s..", "..player.name.."["..player.status.."]"
- else
- s = s..", "..player.name
- end
- end
- end
- --if
- --string.sub(m, 4)
- return s:sub(3)
- end
- --displays a title to a selection of players
- local function displayTitle(selector,text,subtext)
- commands.async.title("@a["..selector.."]","subtitle",'{text:"'..subtext..'"}')
- commands.async.title("@a["..selector.."]","title",'{text:"'..text..'"}')
- end
- --simply runs displayTitle on a list of players
- local function displayTitleToGroup(playerlist,text,subtext)
- debug.log("displayTitleToGroup: "..text.."-"..subtext)
- for name,player in pairs(playerlist) do
- displayTitle("name="..player.name,text,subtext)
- end
- end
- --- Sends a title message to active players in a buildzone
- --Uses a new playerlist object to only send the title to the active players
- -- @param A new playerlist object
- -- @param text The message to send
- local function displayTitleToPlayers(playerlist,text,subtext)
- debug.log("displayTitleToPlayers: "..text.."-"..subtext)
- local activeList = {}
- for name,player in pairs(playerlist) do
- if player.status == registry.PLAYER_STATUS.IN_GAME then
- table.insert(activeList,player)
- end
- end
- displayTitleToGroup(activeList,text,subtext)
- end
- --teleports a list of players to an exact place and sends them a message about it
- local function teleportToPoint(x,y,z,playerlist,clear,textEN, textDE)
- for name,player in pairs(playerlist) do
- player.x = x
- player.y = y
- player.z = z
- --commands.async.gamemode(2,player.name)
- if clear then
- commands.async.clear(player.name,"wool")
- commands.async.clear(player.name,"stone_pickaxe")
- end
- commands.tp("@a[name="..player.name.."]",x,y,z)
- if registry.ANNOUNCE_ENGLISH then
- --print("text is: ", textEN)
- commands.async.tellraw(player.name,'["",{"text":"'..textEN..'","color":"white"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- commands.async.tellraw(player.name,'["",{"text":"'..textDE..'","color":"gold"}]')
- end
- end
- end
- --- Teleports a list of players randomly about a buildzone
- -- @param buildzone the target buildzone object
- -- @param playerlist a name or number indexed playerlist
- -- @param textEN An english message to send to english players
- -- @param textEN An german message to send to german players
- --TODO make this take a box instead of buildzone and move to the mcset package or player package
- local function scatterIntoZone(buildzone,playerlist,textEN, textDE)
- debug.log("teleporting to buildzone #"..buildzone.locid.." the players: "..playerListToString(playerlist))
- local minX = buildzone.x
- local minZ = buildzone.z
- local maxX = buildzone.x+buildzone.w
- local maxZ = buildzone.z+buildzone.w
- for name,player in pairs(playerlist) do
- local targetX = math.floor(math.random(minX,maxX))
- local targetZ = math.floor(math.random(minZ,maxZ))
- --print("targetX", targetX)
- --print("targetZ", targetZ)
- --print("buildzone.y+5", (buildzone.y+5))
- --print("player ",player)
- --print(json.encodePretty(player))
- --print("textEN", textEN)
- --print("textDE", textDE)
- --debug.log("teleport from scatterIntoZone")
- teleportToPoint(targetX, buildzone.y+5, targetZ, {player}, false, textEN, textDE)
- end
- debug.log("teleporting players to buildzone #"..buildzone.locid.." complete")
- end
- --- Teleports a list of players to the centre of a given buildzone
- -- @param buildzone the target buildzone object
- -- @param playerlist a name or number indexed playerlist
- -- @param textEN An english message to send to english players
- -- @param textEN An german message to send to german players
- local function teleportToZone(buildzone,playerlist,textEN, textDE)
- teleportToPoint(buildzone.x+2+(buildzone.w/2),buildzone.y+5,buildzone.z+2+(buildzone.w/2),playerlist,true,textEN, textDE)
- end
- --gives the same list of items to a list of players
- local function giveItems(playerlist,itemlist)
- debug.log("Giving initial inventory items to players: "..playerListToString(playerlist))
- local given = 0
- for name,player in pairs(playerlist) do
- --commands.async.clear(player.name)
- for j,item in ipairs(itemlist) do
- debug.log("giving item >"..item.."<")
- commands.async.give("@a[name="..player.name.."]",item)
- given = given +1
- end
- giveHomecomer(player.name)
- debug.log("Gave initial inventory items to '"..player.name.."'")
- end
- debug.log("Giving initial inventory items to players...DONE!")
- return given
- end
- --- Finds the next free location for a buildzone in a given list of spots.
- -- Searches each zone in zones for an empty location.
- -- Zone are absolute positions (0,0),(0,1) ect.
- -- This uses the coords of FIRST_ZONE as given in the registry to determine
- -- the position of the zones for checking.
- -- @param zones A list of zones from BuildLocs()
- -- @param force Boolean. True will mean it returns the first location no matter what
- -- @return x The X value of the next unused location in world-coordinates
- -- @return y The Y value of the next unused location in world-coordinates
- -- @return z The Z value of the next unused location in world-coordinates
- -- @return locid The index of the returned zone
- local function findNextLoc(zones,force)
- local x,y,z,locid = 0,0,0,1
- for i,loc in ipairs(LOCS) do
- locid = i
- -- these are the coordinates of this LOC in the minecraft world
- x = registry.FIRST_ZONE.x+(loc.x*( registry.BUILDZONE_WIDTH + registry.BUILDZONE_OFFSET*registry.GRIDCELL_SIZE))
- y = registry.FIRST_ZONE.y
- z = registry.FIRST_ZONE.z+(loc.z*( registry.BUILDZONE_WIDTH + registry.BUILDZONE_OFFSET*registry.GRIDCELL_SIZE))
- --print("testing for available zone at: "..x..", "..y..", "..z)
- --print("which is at grid cell at: "..loc.x..", "..loc.z)
- --local result,message = commands.testforblock(x,y+registry.BUILDZONE_FLOOR_HEIGHT,z,"minecraft:air") -- this was used for the testing based on the minecraft model
- local result = true
- if loc.played then
- result = false
- --print("zone has been played")
- end
- --print("testing done")
- --force the first zone to be selected unless it is taken in the "zones" parameter
- if force then result = true end
- --checks if the zone is already in the list of unavailable zones passed as parameter
- local zonefree = true
- for i,zone in ipairs(zones) do
- if zone.x == x and zone.z == z then
- zonefree = false
- end
- end
- --print("next position free is ",loc.x*width,oy,loc.z*width)
- --if result then print("true") else print("false") end
- if result and zonefree then
- debug.log("Using loc at: "..x..", "..y..", "..z.." with locid: "..locid)
- return x,y,z,locid --returns the coordinates of the new zone, plus its id in the LOCS table
- end
- end
- debug.log("ERROR: findNextLoc failed")
- return nil,nil,nil, nil --returns empty if no zone is available
- end
- --[[ looks like the moveBuildzone is not used anymore
- --- Assigns the next unused available play area to an abstract buildzone object
- -- This takes a Buildzone and an array of Absolute references
- -- from BuildGrid(). It finds the next area by using FindNextLoc().
- -- When we change a buildzones coords it is important to update locid and
- -- the selector so that all systems know where the buildzone is.
- -- @param buildzone the buildzone object to be moved
- -- @param zones a list of zones to choose from
- -- @return a boolean result. True if the buildzone was moved
- function moveBuildzone(buildzone,zones)
- local x,y,z,locid = findNextLoc(zones)
- if x and y and z and locid then
- --print("moved buildzone from "..buildzone.x..","..buildzone.z.." to "..x..","..y)
- local w = buildzone.w
- buildzone.x,buildzone.y,buildzone.z = x,y,z
- buildzone.locid = locid --reassign the location id corresponding to the LOCS item for the grid cell of the moved zone
- buildzone.selector = "x="..x..",y="..tostring(y-1)..",z="..z..",dx="..w..",dy=256,dz="..w
- ---buildzone.structures = {} --a list of all vocabularies which have been contructed
- return true
- end
- return false
- end
- --]]
- --multi builder to create sets of buildzones using the buildzone constructor
- local function initBuildzones(quant,elements)--,width)--,floorHeight)
- local result = {}
- for i=1,quant do
- --print("locating available slot")
- local x,y,z,locid = findNextLoc(result)
- if x and y and z and locid then
- table.insert(result,newBuildZone(x,y,z,elements,locid))
- debug.log("Created a new Buildzone #"..locid.." at:"..x..", "..y..", "..z)
- else
- --print("failed to make new buildzone")
- end
- end
- local remaining = registry.NUMBER_OF_BUILDZONES - #result
- --print("doing this remaining thing")
- for i=1, remaining do
- local x,y,z,locid = findNextLoc(result,true)
- if x and y and z and locid then
- debug.log("forced new buildzone #"..locid.." at:"..x..", "..y..", "..z)
- table.insert(result,newBuildZone(x,y,z,elements,locid))
- else
- debug.log("failed to force init a new buildzone")
- end
- end
- return result
- end
- --- Buildzone constructor. Enforces some data structure
- -- Buildzones record information about the structures which are built
- -- inside them, as well as which elements of the catalogue are
- -- available to be built in them. Buildzones record where they are in
- -- three ways (locid, selector and coords). Buildzones do not know
- -- who is playing in them right now, or how long is left in a game
- -- @param x Where you first want to place the buildzone
- -- @param y Where you first want to place the buildzone
- -- @param z Where you first want to place the buildzone
- -- @param elementZones An array of catalogue items that can be built here
- -- @param locid The buildzones starting location in the buildLocs() array
- function newBuildZone(x,y,z,elementZones,locid)
- local nbz = {}
- nbz.x ,nbz.y ,nbz.z ,nbz.w = x,y,z,registry.BUILDZONE_WIDTH
- nbz.selector = "x="..(nbz.x-2)..",y="..(nbz.y-3)..",z="..(nbz.z-2)..",dx="..(nbz.w+4)..",dy=256,dz="..(nbz.w+4)
- nbz.structures = {} --a list of all vocabularies names which have been contructed
- nbz.buildings = {} --a list of all vocabularies with full data (x,y,z,id,name) which have been contructed
- nbz.filledSlots = 0 --to count how many slots have been filled with buildings. the matrix is 7x7x20 slots. one slot is 9x9x9 blocks big
- nbz.greenSlots = 0 --to count how many of the slots are green. the matrix is 7x7x20 slots. one slot is 9x9x9 blocks big
- --nbz.variety = {} -- this stores how many buildings of each type are there. it is indexed on vocab.id and the value is the number of buildings from type vocab.id
- nbz.waitingForCheck = {}
- nbz.highest = 0
- nbz.elements = elementZones
- nbz.locid = locid
- return nbz
- end
- --[[
- --- Kew constructor. Enforces some data structure
- -- Kews are used for timekeeping and to record which players are
- -- interacting where. Each queue can only have 1 buildzone, but may have
- -- many players. Buildzones may move location, but Kews do not.
- -- @param buildzone The buildzone object to associate with this Kew
- -- @param maxplayers How many players are allowed to play in the buildzone
- -- @param portal A portal object created with newPortal()
- -- @return The constructed Kew Object
- function newQueue(buildzone,maxplayers,portal)
- debug.log("creating a new game queue for buildzone #"..buildzone.locid)
- local q = {}
- q.timer = 1
- q.phase = registry.QUEUE_PHASE.DORMANT
- q.victory = false
- q.phases = {
- {
- name = "Selecting Players",
- length = registry.PHASE_LENGTH_WAIT
- --displaylength = 15 --this field is not used
- },
- {
- name = "Game In Progress",
- length = registry.PHASE_LENGTH_GAME
- --displaylength = 70 --this field is not used
- },
- {
- name = "Round Complete",
- length = registry.PHASE_LENGTH_OVER
- --displaylength = 5 --this field is not used
- }
- }
- q.playerlist = {} --needs a metatable to know how to convert to string
- setmetatable(q.playerlist, {__tostring = playerListToString}) -- TODO put this as playerlist:new() in the player package
- q.maxplayers = maxplayers
- q.portal = portal
- q.buildzone = buildzone
- local timestamp = math.floor(os.clock())
- q.filename = timestamp.."at"..q.buildzone.x.."_"..q.buildzone.z --TODO this needs to be set and reset in onEnter_play
- return q
- end
- --]]
- --[[
- function setGameSettings(game)
- -- assemble a table of the current settings that affect the world design
- -- TODO: make the following read first from the settings file and then load the defaults from registry
- local settings = {}
- settings.grid_cell_size = registry.GRIDCELL_SIZE
- settings.grid_cell_count = registry.GRIDCELL_COUNT
- settings.catalogue_slot_count = registry.CATALOGUE_SLOTCOUNT
- settings.play_area_offset = registry.PLAY_AREA_OFFSET
- settings.trenches = registry.TRENCHES
- --settings.game_field = registry.GAME_FIELD
- --settings.game_field_max = registry.GAME_FIELD_MAX
- settings.buildzone_offset = registry.BUILDZONE_OFFSET
- settings.catalogue_slot_size = registry.CATALOGUE_SLOTSIZE
- settings.catalogue_slot_offset = registry.CATALOGUE_SLOT_OFFSET
- settings.spawnzone_size = registry.SPAWNZONE_SIZE
- settings.element_height = registry.ELEMENT_HEIGHT
- settings.portal_count = registry.PORTAL_COUNT
- settings.buildzone_count = registry.BUILDZONE_COUNT
- settings.portal_offset = registry.PORTAL_OFFSET
- settings.portal_size = registry.PORTAL_SIZE
- settings.portal_height = registry.PORTAL_HEIGHT
- settings.computer = mcget.computerPosition()
- game.settings = settings -- store them into the game object
- game.last_settings = nil
- local filename = "last_settings.json"
- local filename_with_path = registry.RECORDS_FOLDER..filename
- if file_exists(filename_with_path) then
- debug.log("game has been run before - will store the last settings in the game object")
- -- read a table of the settigns used last time from the file
- local last_settings = json.decodeFromFile(filename_with_path)
- game.last_settings = last_settings -- return the last settigns as well so that catalogue slots can adapt
- end
- --update the last settings file it
- debug.log("saving current settgins as last_settings_file for next time the 'play' script is run")
- file_write(json.encodePretty(settings), filename, registry.RECORDS_FOLDER)
- --fs.makeDir(registry.RECORDS_FOLDER)
- --local file = fs.open(filename_with_path,"w")
- --file.write(json.encodePretty(settings))
- --file.close()
- end
- function setGameSpecialBlocks(game)
- game.blocks = registry.BLOCKS
- end
- --]]
- --- Checks if there is a change of the parameters that define the game area geometry since the last run
- -- Currently if there is a change it returns the redo_everything result
- -- TODO if game has been run and there is change affecting the catalogue area add 8 to result for partial rebuild only the catalogue areas
- -- TODO if game has been run and there is change affecting the play area add 4 to result for partial rebuild only the play areas
- -- TODO if game has been run and there is change affecting the spawn area add 2 to result for partial rebuild only the spawn areas
- function checkForGameAreaParametersChanges(game)
- --local redo_nothing = 0
- --local redo_spawn_area = 2
- --local redo_play_area = 4
- --local redo_catalogue = 8
- --local redo_everything = redo_spawn_area + redo_play_area + redo_catalogue
- --
- --local result = redo_nothing
- local result = {}
- result.redo_everything = false
- result.redo_spawn_area = false
- result.redo_play_area = false
- result.redo_catalogue = false
- --
- -- assemble a table of the current settings that affect the world design
- local settings = game.settings
- --[[
- settings.grid_cell_size = registry.GRIDCELL_SIZE
- settings.grid_cell_count = registry.GRIDCELL_COUNT
- settings.catalogue_slot_count = registry.CATALOGUE_SLOTCOUNT
- settings.play_area_offset = registry.PLAY_AREA_OFFSET
- settings.trenches = registry.TRENCHES
- settings.game_field = registry.GAME_FIELD
- settings.buildzone_offset = registry.BUILDZONE_OFFSET
- settings.catalogue_slot_size = registry.CATALOGUE_SLOTSIZE
- settings.catalogue_slot_offset = registry.CATALOGUE_SLOT_OFFSET
- --]]
- --local filename = "last_settings.json"
- --local filename_with_path = registry.RECORDS_FOLDER.."/"..filename
- --if file_exists(filename_with_path) then
- if game.last_settings then
- debug.log("game has been run before - will compare last with current settings")
- -- read a table of the settigns used last time from the file
- local last_settings = game.last_settings
- --result.last_settings = last_settings -- return the last settigns as well so that catalogue slots can adapt
- --compare the two tables
- if settings.game_field.countX ~= last_settings.game_field.countX
- or settings.game_field.countZ ~= last_settings.game_field.countZ
- then
- --delete the buildzones.json file
- debug.log("deleting buildzone list file because settings have changed")
- fs.delete(registry.BUILDZONES_LIST_FILE)
- result.redo_play_area = true
- end
- if settings.grid_cell_size ~= last_settings.grid_cell_size then result.redo_everything = true end
- if settings.grid_cell_count ~= last_settings.grid_cell_count then result.redo_play_area = true end
- if settings.catalogue_slot_count.x ~= last_settings.catalogue_slot_count.x or
- settings.catalogue_slot_count.z ~= last_settings.catalogue_slot_count.z
- then result.redo_catalogue = true end
- if settings.play_area_offset.x ~= last_settings.play_area_offset.x or
- settings.play_area_offset.z ~= last_settings.play_area_offset.z
- then
- result.redo_play_area = true
- result.redo_catalogue = true
- end
- if settings.trenches.WIDTH ~= last_settings.trenches.WIDTH or
- settings.trenches.DEPTH ~= last_settings.trenches.DEPTH
- then result.redo_everything = true end
- if settings.buildzone_offset ~= last_settings.buildzone_offset then result.redo_play_area = true end
- if settings.catalogue_slot_size.x ~= last_settings.catalogue_slot_size.x or
- settings.catalogue_slot_size.z ~= last_settings.catalogue_slot_size.z
- then result.redo_catalogue = true end
- if settings.catalogue_slot_offset ~= last_settings.catalogue_slot_offset then
- result.redo_play_area = true
- result.redo_catalogue = true
- end
- else
- debug.log("game has not been run before - will generate world")
- -- delete any buildzones list file that might be left there
- debug.log("deleting buildzone list file in case it exists")
- fs.delete(registry.BUILDZONES_LIST_FILE)
- --end return 2+4+8 for full regeneration
- result.redo_everything = true
- end
- --last used settings file doesn't exist
- --create it
- --debug.log("saving last settings file")
- --fs.makeDir(registry.RECORDS_FOLDER)
- --local file = fs.open(filename_with_path,"w")
- --file.write(json.encodePretty(settings))
- --file.close()
- --
- return result
- end
- --- Creates a game object and initializes game management functions
- -- This function is a bit large, and should be placed in its own module
- -- at some point.
- -- The game object creates and stores the complete Catalogue
- -- The game object creates and stores a complete list of all Buildzones
- -- The game object creates and stores a complete list of all Kews
- -- The game object only tracks players who are NOT in a Kew
- -- The game object knows where the spawn is
- -- This function rebuilds the Catalogue and Buildzones in the gameworld
- -- if any settings have changed.
- -- This function adds scoreboards to your Minecraft world so that 20kb
- -- operates correctly.
- -- This function runs GameRule commands to set up your Minecraft world.
- -- If DEBUG_MODE is on, then Kews are fixed to Play Phase with a large
- -- time limit
- -- This function kills all Villagers; it is the only way to be sure.
- -- @return game The Game container object.
- function setup()
- debug.log("Starting Setup function.")
- local game = newGameManager()
- game.version_number = VERSION_NUMBER
- game.version_name = VERSION_NAME
- game.settings, game.last_settings = loader.getGameSettings()
- checkGameSettings( game.settings )
- game.settings.computer = mcget.computerPosition()
- debug.log('getting starting items')
- game.settings.starting_items = loader.getStartingItems(game.settings)
- debug.log("Starting items are: "..json.encode(game.settings.starting_items))
- -- put the settigns into the game object
- -- game.settings, game.lastsettings =
- --setGameSettings(game)
- --setGameSpecialBlocks(game)
- ---SETTINGS = registry.loadCustomSettings()
- debug.log("Game object created")
- --SET UP THE GAME AREA
- debug.log("Setting up game area...")
- --do a file check to see if setting up the game area is needed
- debug.log("Checking for changed settings...")
- local rebuild = checkForGameAreaParametersChanges(game)
- debug.log("Need to recreate: "..json.encode(rebuild))
- game.spawnzone_box = getSpawnAreaLocation(game.settings)
- --check if spawn area needs rebuilding
- if rebuild.redo_everything or rebuild.redo_spawn_area then
- portal.drawSpawnZone(game.settings)
- end
- if rebuild.redo_everything or rebuild.redo_play_area then
- debug.log("building play area...")
- --debug.log("fillBox from inside build play area")
- mcset.fillBox(
- box_offset( box ( registry.PLAY_AREAS.x ,
- registry.PLAY_AREAS.y,
- registry.PLAY_AREAS.z,
- registry.PLAY_AREAS.dx,
- -registry.TRENCHES.DEPTH,
- registry.PLAY_AREAS.dz),
- registry.TRENCHES.WIDTH),
- registry.BLOCKS.AIR
- )
- mcset.fillBox(
- box(registry.PLAY_AREAS.x ,
- registry.PLAY_AREAS.y,
- registry.PLAY_AREAS.z,
- registry.PLAY_AREAS.dx,
- -registry.PLAY_AREAS.y,
- registry.PLAY_AREAS.dz),
- registry.BLOCKS.DARK_GRID
- )
- local x,y,z = registry.FIRST_ZONE.x, registry.FIRST_ZONE.y, registry.FIRST_ZONE.z
- for i=0,registry.GAME_FIELD.countZ - 1 do
- for k=0,registry.GAME_FIELD.countX - 1 do
- local xpos = x + k*( registry.BUILDZONE_WIDTH + registry.BUILDZONE_OFFSET*registry.GRIDCELL_SIZE)
- local ypos = y
- local zpos = z + i*( registry.BUILDZONE_WIDTH + registry.BUILDZONE_OFFSET*registry.GRIDCELL_SIZE)
- mcset.fillRing(box(xpos,ypos,zpos,registry.BUILDZONE_WIDTH,1,registry.BUILDZONE_WIDTH),registry.BLOCKS.WHITE_GRID)
- end
- end
- mcset.fillGrid(
- box( registry.PLAY_AREAS.x ,
- registry.PLAY_AREAS.y,
- registry.PLAY_AREAS.z,
- registry.PLAY_AREAS.dx,
- 1,
- registry.PLAY_AREAS.dz),
- registry.computer.x,
- registry.computer.y,
- registry.computer.z,
- registry.GRIDCELL_SIZE,
- registry.BLOCKS.WHITE_GRID
- )
- end
- --catalogue slots creation
- debug.log("initializing catalogue elements...")
- game.catalogue = catalogue.initCatalogue(game.settings)
- --draw the catalogue slots in Minecraft
- catalogue.drawCatalogue(game)
- debug.log("Catalogue elements successfully initialized!")
- --kill all villagers
- mcset.killAllEntities("Villager")
- debug.log("Villagers destroyed")
- -- init the portals
- debug.log("Making Portal objects.")
- game.portals = portal.initPortals(game.settings)
- debug.log("starting the building of portals...")
- for i, p in ipairs(game.portals) do
- debug.log("building of portal:",i)
- portal.drawPortal( p )
- end
- debug.log("building of portals DONE!")
- --buildzone creation
- debug.log("Making building zone objects.")
- --game.builds appears to store the games currently in progress
- --game.builds = initBuildzones(registry.NUMBER_OF_BUILDZONES,game.elements,registry.BUILDZONE_WIDTH)--,registry.BUILDZONE_FLOOR_HEIGHT)
- buildzones = initBuildzones(registry.NUMBER_OF_BUILDZONES,game.catalogue,registry.BUILDZONE_WIDTH)--,registry.BUILDZONE_FLOOR_HEIGHT)
- --for i,build in ipairs(game.builds) do
- for i,build in ipairs(buildzones) do
- --print("portal "..i.." is "..tostring(game.portals[i]))
- table.insert(game.queues,newQueue(build, registry.MAX_PLAYERS_PER_GAME, game.portals[i]))
- end
- debug.log("updating portals to match queue states...")
- for i, kew in ipairs(game.queues) do
- debug.log("updating of portal:",i)
- portal.drawPortal( kew.portal, kew.phase)
- end
- debug.log("updating of portals DONE!")
- ----
- debug.log("Adding scoreboards.")
- --print(#registry.VOCABS_DATA)
- for i=1,#registry.VOCABS_DATA do
- commands.scoreboard("objectives","add","building_"..i,"dummy")
- end
- commands.scoreboard("objectives","add","highscores","dummy","Best Neighbourhoods")
- if registry.SHOW_HIGHSCORES then
- commands.scoreboard("objectives","setdisplay","sidebar","highscores")
- end
- commands.scoreboard("objectives","add","VillagerLife","dummy")
- commands.scoreboard("objectives","add","built","dummy", "Structures Built")
- commands.scoreboard("objectives","add","highest","dummy", "Personal Highest")
- commands.scoreboard("objectives","add","played","dummy","Games Played")
- commands.scoreboard("objectives","setdisplay","list","played")
- commands.title("@a","times",0,30,30) -- what does this do?
- ----
- debug.log("setting the needed preferences of the minecraft world.")
- --TODO set world spawn point to computer location
- commands.gamerule("doDaylightCycle",false) --
- commands.gamerule("keepInventory",true)
- commands.gamerule("doTileDrops",false) --
- commands.gamerule("sendCommandFeedback", true)
- commands.gamerule("logAdminCommands",true) -- it looks like this needs to be on for the tp funtion used in getAllPos to return its message properly
- commands.gamerule("commandBlockOutput",false) --
- commands.time("set",6000) --
- ---
- math.randomseed( os.time() )
- debug.log("Computer clock is: "..os.clock())
- debug.log("20.000 BLOCKS is ready to run!")
- return game
- end
- --- Runs a single step of the game loop
- -- Runs the game object through each of these update steps in order.
- -- See the comments on each of the steps.
- -- @param game The game object as created in setup()
- function update(game)
- --debug.log("updating game state")
- local elapsed = updateClock(game)
- --update players
- --debug.log("update: will checkPlayers")
- checkPlayers(game)
- --debug.log("update: will doTimerUpdates")
- doTimerUpdates(game,elapsed)
- --debug.log("update: will doPhaseUpdates")
- doPhaseUpdates(game)
- --debug.log("update: will doPhaseEnds")
- doPhaseEnds(game)
- --debug.log("update: will checkBoundaries")
- checkBoundaries(game)
- --debug.log("update: will checkForHomecomers")
- checkForHomecomers(game)
- --debug.log("update: will allocateWaiters")
- allocateWaiters(game)
- end
- --- Calculates elapsed time during a game tick
- -- Updates the given game objects clock using the operating system time
- -- @param game The game object as created in setup()
- -- @return elapsed How much time has elapsed since time was updated
- function updateClock(game)
- game.nowTime = os.clock()
- local elapsed = game.nowTime - game.lastClock
- game.lastClock = game.nowTime
- return elapsed
- end
- --- Updates all kews in the game object based on elapsed time
- -- Since Kews do the timekeeping, let them know how much time as elapsed.
- -- @param game The game object as created in setup()
- -- @param elapsed Seconds as calculated by updateClock()
- function doTimerUpdates(game,elapsed)
- for i,kew in ipairs(game.queues) do
- --print("updating timer for zone: ", i)
- --print("timer is : ", kew.timer)
- --print("phase is : ", kew.phase)
- kew.timer = kew.timer - elapsed
- end
- end
- --- Deal with players who should be in Buildzones
- -- Checks all Kews (and therefore Buildzones). If the Kew is in Phase 2
- -- (Play Phase), then check on all players. If those players are out
- -- of bounds, move them back to the closest edge of the buildzone.
- -- This also sends them a bi-lingual warning if there were moved.
- -- @param game The game object as created in setup()
- function checkBoundaries(game)
- --debug.log("begin checking boundaries")
- for i,kew in ipairs(game.queues) do
- --debug.log("checking boundaries for buildzone #"..kew.buildzone.locid)
- if kew.phase == registry.QUEUE_PHASE.PLAYING then
- --boundaries
- --debug.log("buildzone #"..kew.buildzone.locid.." is in phase 2")
- local x_min = kew.buildzone.x-2
- local x_max = kew.buildzone.x+kew.buildzone.w+4
- local z_min = kew.buildzone.z-2
- local z_max = kew.buildzone.z+kew.buildzone.w+4
- --local toBeCorrected = {}
- for name,player in pairs(kew.playerlist) do
- --debug.log("checking boundaries for player "..player.name.." indexed as "..name)
- if player.status == registry.PLAYER_STATUS.IN_GAME then
- --debug.log("player "..name.." is active")
- --debug.log("calling getAllPos for player "..player.name)
- local listOfOne = getAllPos('m=2,name='..player.name)
- --debug.log("getAllPos for player "..player.name.." completed")
- if listOfOne and listOfOne[1] then
- --debug.log("getAllPos for player "..player.name.." returned a value")
- player.x = listOfOne[1].x
- player.y = listOfOne[1].y
- player.z = listOfOne[1].z
- local changed = false
- if player.x > x_max then
- changed = true
- player.x = x_max-4
- end
- if player.x < x_min then
- changed = true
- player.x = x_min+4
- end
- if player.z > z_max then
- changed = true
- player.z = z_max-4
- end
- if player.z < z_min then
- changed = true
- player.z = z_min+4
- end
- if changed then
- teleportToPoint(player.x,kew.buildzone.y+2,player.z,{player},false,
- "TELEPORTED BACK TO GAME: Please stay inside the building zone or use HOMECOMER to leave the game!",
- "Zurück ins Spiel teleportiert: Bitte bleib innerhalb des Baufeldes oder nutze den HEIMKEHRER um das Spiel zu verlassen!")
- end
- end
- end
- --debug.log("Completed: checking boundaries for player "..player.name.." indexed as "..name)
- end
- end
- end
- --debug.log("end checking boundaries")
- end
- --- Check if a Buildzone is worth saving
- -- Many plays of a buildzone will be junk. Here you can set the logic
- -- which determines if a buildzone was valuable.
- -- Currently always returns that the zone is valuable.
- -- @param buildzone The buildzone to check.
- -- @return True is the buildzone was junk
- function checkIfBuildzoneIsCrap(buildzone)
- debug.log("Buildzone was ok")
- return false
- end
- --- Everything that happens when the Play Phase finishes due to time limit.
- -- Removes the ring which denotes a game in progress.
- -- Removes Detector Blocks and then fills the floor in.
- -- Checks if the buildzone was valuable and if so it will clean it.
- -- Sets the zone played state based on value. A played zone will not be
- -- overwritten by future buildzones.
- -- Bilingual messages are sent to players to let them know what happened.
- -- @param kew A kew object that contains a Buildzone
- function cleanAfterGameOver(kew)
- local buildzone = kew.buildzone
- debug.log("cleanAfterGameOver started for buildzone #".. buildzone.locid)
- commands.async.setblock(buildzone.x,buildzone.y,buildzone.z,registry.BLOCKS.VICTORY_MARKER.block)
- mcset.fillRing(box(buildzone.x,buildzone.y,buildzone.z,buildzone.w,1,buildzone.w),registry.BLOCKS.AIR)
- --for each level remove playing blocks like detectors
- for h=1,256-buildzone.y do
- commands.async.fill(buildzone.x,buildzone.y+h,buildzone.z,buildzone.x+buildzone.w,buildzone.y+h,buildzone.z+buildzone.w,"minecraft:air 0","replace",registry.BLOCKS.DETECT.block,registry.BLOCKS.DETECT.variant)
- commands.async.fill(buildzone.x,buildzone.y+h,buildzone.z,buildzone.x+buildzone.w,buildzone.y+h,buildzone.z+buildzone.w,"minecraft:air 0","replace",registry.BLOCKS.PLUG.block,registry.BLOCKS.PLUG.variant)
- commands.async.fill(buildzone.x,buildzone.y+h,buildzone.z,buildzone.x+buildzone.w,buildzone.y+h,buildzone.z+buildzone.w,"minecraft:air 0","replace",registry.BLOCKS.BUILDING_GARDEN.block,registry.BLOCKS.BUILDING_GARDEN.variant)
- commands.async.fill(buildzone.x,buildzone.y+h,buildzone.z,buildzone.x+buildzone.w,buildzone.y+h,buildzone.z+buildzone.w,"minecraft:air 0","replace",registry.BLOCKS.BUILDING_HOUSE.block,registry.BLOCKS.BUILDING_HOUSE.variant)
- end
- --replaces air on the bottom level with flooring to show the area is completed
- commands.async.fill(buildzone.x,buildzone.y,buildzone.z,buildzone.x+buildzone.w,buildzone.y,buildzone.z+buildzone.w,registry.BLOCKS.CAMP_FLOOR.block,registry.BLOCKS.CAMP_FLOOR.variant,"replace",registry.BLOCKS.PLUG.block,registry.BLOCKS.PLUG.variant)
- commands.async.fill(buildzone.x,buildzone.y+1,buildzone.z,buildzone.x+buildzone.w,buildzone.y,buildzone.z+buildzone.w,registry.BLOCKS.PHVFLOOR.block,registry.BLOCKS.PHVFLOOR.variant,"replace","minecraft:air","0")
- ---add here a message to the players that they can see their finished game on the webviewer OR that their game was not save because they built too little
- local wasCrap = checkIfBuildzoneIsCrap(buildzone)
- local gameovermessageEN
- local gameovermessageDE
- if wasCrap then
- --mark this buildzone for replacement
- --change the flag to played=false
- debug.log("cleanAfterGameOver is crap ".. buildzone.locid)
- updateZonePlayedState(buildzone,false)
- gameovermessageEN = "Thank you for playing IBA_GAME! This game will be discarded because you built less than "..registry.MIN_BUILDINGS.." buildings. Play another game or check what others have built at: www.20000blocks.com"
- gameovermessageDE = "Vielen Dank, dass du IBA_GAME gespielt hast! Diese Runde wird verworfen, da weniger als "..registry.MIN_BUILDINGS.." Gebäude gebaut wurden. Starte eine neue Runde oder schau dir die Spielergebnisse anderer Spieler an unter: www.2000blocks.com"
- else
- --change the flag to played=true
- debug.log("cleanAfterGameOver otherwise ".. buildzone.locid)
- updateZonePlayedState(buildzone,true)
- gameovermessageEN = "Thank you for playing IBA_GAME! Play another game or look for your game result at: www.20000blocks.com"
- gameovermessageDE = "Vielen Dank, dass du IBA_GAME gespielt hast! Starte eine neue Runde oder schau dir deine Spielergebnisse an unter: www.20000blocks.com"
- end
- for name, player in pairs(kew.playerlist) do
- if registry.ANNOUNCE_ENGLISH then
- -- announce success in English
- commands.async.tellraw(player.name,'["",{"text":"'..gameovermessageEN..'","color":"white"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- -- announce success in German
- commands.async.tellraw(player.name,'["",{"text":"'..gameovermessageDE..'","color":"gold"}]')
- end
- end
- end
- --- Changes a given locid to played.
- -- Strange that the LOCS object is a global and not in the registry.
- -- That is probably bad. This function also saves the given location to
- -- file.
- -- @param buildzone The buildzone that was just finished
- -- @param newstate Boolean Has the location been played successfully?
- function updateZonePlayedState(buildzone, newstate)
- debug.log("updating buildzone's #"..buildzone.locid.." played stated to: "..tostring(newstate))
- --change the flag to played=newstate
- LOCS[buildzone.locid].played = newstate
- --and write the LOCS object to the json file
- writeGridToFile()
- end
- --- Process timekeeping and update Phases of Kews.
- -- Run every game step. Iterate over all Kews and update their phase
- -- based on the remaining time in kew.timer.
- -- If the kew is on the Play Phase, then do the gameplay logic
- -- stored in updatePlayedZone().
- -- Update player timers (xp bar timer) and give players a large warning
- -- title if the game is almost over.
- -- @param game The game object as created by setup()
- function doPhaseUpdates(game)
- for i,kew in ipairs(game.queues) do
- local minutes = string.format("%02d",math.floor(kew.timer/60))
- local seconds = string.format("%02d",math.floor(kew.timer - (minutes*60)))
- if kew.timer <= 0 then
- minutes = "00"
- seconds = "00"
- end
- if kew.phase == registry.QUEUE_PHASE.DORMANT then
- --waiting phase
- -- Never start the wait phase until someone joins
- if countActivePlayers(kew) == 0 then
- kew.timer = kew.phases[kew.phase].length
- else
- -- but if we have a player then start now
- kew.timer = 0
- end
- --displayTitleToPlayers(kew.playerlist,"Game starting!")
- displayTimeToPlayers(kew.playerlist,minutes,seconds)
- elseif kew.phase == registry.QUEUE_PHASE.PLAYING then
- --playing phase
- --debug.log("buildzone #"..)
- local victory = updatePlayedZone(kew) --currently victory updatePlayedZone returns always false
- displayTimeToPlayers(kew.playerlist,minutes,seconds)
- if victory then
- kew.timer = -2
- end
- -- unless finish if all players quit
- if countActivePlayers(kew) == 0 then
- kew.timer = -1
- end
- elseif kew.phase == registry.QUEUE_PHASE.GAMEOVER then
- --end phase
- displayTimeToPlayers(kew.playerlist,minutes,seconds)
- end
- end
- end
- --- Update the Minecraft Scoreboards based on what players built.
- -- This runs after a buildzone is completed it currently rewards all
- -- participants with more played score.
- -- @param kew A Kew object which has just moved from Phase PLAYING to GAMEOVER.
- function processHighscores(kew)
- local buildzone = kew.buildzone
- for name,player in pairs(kew.playerlist) do
- commands.async.scoreboard("players","add",player.name,"played",1)
- end
- end
- --- Export a Kews buildzone detail once it is complete
- -- Requires a kew so it can access the playerlist.
- -- @param kew The Kew object which contains players and a buildzone
- -- @param saveOnline Boolean true: save the game also online to the database; false: save only in a file locally
- -- TODO move this to an IO package ???
- local function exportKewData(kew, saveOnline)
- local buildzone = kew.buildzone
- local saved = {}
- saved.position =
- {
- x=buildzone.x,
- y=buildzone.y,
- z=buildzone.z
- }
- saved.players = {}
- for name, player in pairs(kew.playerlist) do
- table.insert(saved.players,player.name)
- end
- --saved.structures = buildzone.structures
- saved.buildings = buildzone.buildings
- --saved.totals = tracker.tallyTable(buildzone.buildings)
- --saved.highest = buildzone.highest
- saved.stats = {
- --cityVersion = registry.CITY_VERSION,
- --height = buildzone.highest,
- --densityIndex = math.floor(100*buildzone.filledSlots/49), -- the density index is made from built area (filledSlots) over the ground area (7x7 slots = 49)
- --greenIndex = math.floor(100*buildzone.greenSlots/49), --the green index is made from green area (greenSlots) over the ground area (7x7 slots = 49)
- --variety = tableLength(buildzone.variety),
- timeCompleted = math.floor(os.clock()),
- gameLength = registry.PHASE_LENGTH_GAME
- }
- fs.makeDir(registry.RECORDS_FOLDER)
- local file = fs.open(registry.RECORDS_FOLDER.."/"..kew.filename..".json","w")
- local filecontent = json.encodePretty(saved)
- debug.log("saving the game at buildzone "..kew.buildzone.locid.." to local file...")
- file.write(filecontent)
- file.close()
- debug.log("local file saved: "..registry.RECORDS_FOLDER.."/"..kew.filename..".json")
- if saveOnline then --for now this is disabled until we figure out how things look in the webGL viewer
- debug.log("saving the game at buildzone "..kew.buildzone.locid.." online...")
- --writeToDatabase(kew.filename,filecontent,saved.position.x,saved.position.z)
- --debug.log("saved")
- end
- end
- -- this function writes to the online database that we use to display models in the webGL viewer
- local function writeToDatabase(name,data,x,z)
- -- the user agent needs to be renamed otherwise the dfeult one is Java and that is blocked by the .htaccess file on the website
- local headers = {
- [ "User-Agent" ] = "20.000 BLOCKS"
- }
- local link = http.post(
- "http://www.20000blocks.com/DatabaseAccess/UploadModel.php",
- "name="..texturlEncode(name).."&"..
- "content="..texturlEncode(data).."&"..
- "x="..x.."&"..
- "z="..z,
- headers
- )
- local linkURL = link.readAll()
- if linkURL then
- --message texts
- local msg_EN = 'The latest game result has been uploaded to the webviewer.\n'
- local msg_DE = 'Die neueste Runde wurde in den Webviewer geladen.\n'
- local linkText_EN = 'See it and share it!'
- local linkText_DE = 'Schau es und teile es!'
- local hoverText_EN = 'Click here to see and share the last game!'
- local hoverText_DE = 'Click here to see and share the last game!'
- --message text with the link
- local linkmsg_EN = '["",{"text":"'..msg_EN..'","color":"white","bold":false},{"text":"'..linkText_EN..'","color":"blue","underlined":true,"clickEvent":{"action":"open_url","value":"'..linkURL..'"},"hoverEvent":{"action":"show_text","value":{"text":"","extra":[{"text":"'..hoverText_EN..'","color":"gold"}]}},"bold":false}]'
- local linkmsg_DE = '["",{"text":"'..msg_DE..'","color":"gold","bold":false},{"text":"'..linkText_DE..'","color":"blue","underlined":true,"clickEvent":{"action":"open_url","value":"'..linkURL..'"},"hoverEvent":{"action":"show_text","value":{"text":"","extra":[{"text":"'..hoverText_DE..'","color":"gold"}]}},"bold":false}]'
- -- announce success in English
- commands.async.tellraw("@a",linkmsg_EN)
- if registry.ANNOUNCE_GERMAN then
- --announce success in German
- commands.async.tellraw("@a",linkmsg_DE)
- end
- end
- end
- function switchVersion(id, relative) --id is the new version, relative is whether it is incrmenting the last version (true means the number in id will be added to the current version, false is id is the new version number)
- http.post(
- "http://www.20000blocks.com/DatabaseAccess/SwitchVersion.php",
- "id="..texturlEncode(id).."&"..
- "relative="..texturlEncode(relative)
- )
- end
- --- Move from the Wait Phase to the Play Phase
- -- Anything that needs to be done ONCE before the Play Phase starts
- -- happens here.
- -- The buildzone is moved to a clean area.
- -- The players in the Kew are moved to the buildzone.
- -- The buildzone is cleaned up (now that the chunks are loaded
- -- The buildzone is prepares (detector blocks placed)
- -- Players recieve starting items
- -- A friendly message is shown for them to begin
- -- Kew Phase and Timer are updated
- -- @param kew The Kew to update
- function endWaitPhase(kew)--, game)
- --moveBuildzone(kew.buildzone,game.builds)
- --teleportToZone(kew.buildzone,kew.playerlist,"Your game has started! BUILD A HOUSE!", "Das Spiel hat begonnen! BAUE EIN HAUS!")--teleport selected players
- debug.log("ending the wait phase for buildzone "..kew.buildzone.locid)
- cleanBuildzone(kew.buildzone)
- prepareBuildzone(kew.buildzone)--prepare build zone
- --giveItems(kew.playerlist,registry.STARTING_ITEMS) --Starting items are given as players join now, not all at once
- displayTitleToPlayers(kew.playerlist,"BUILD!","Place your resources and stand on a detector block.")
- kew.victory = false
- --displayTime(kew.buildzone.selector,0,0)
- kew.phase = registry.QUEUE_PHASE.PLAYING
- kew.timer = kew.phases[kew.phase].length
- portal.drawPortal(kew.portal,kew.phase)
- debug.log("wait phase for buildzone "..kew.buildzone.locid.. " - ended")
- end
- --- Move from the Play Phase to the End Phase
- -- Anything that needs to be done ONCE before the End Phase starts
- -- happens here.
- -- Player highscores are updated
- -- The Kew and Buildzone data is saved to file
- -- The buildzone is given a clean and checked for value
- -- Kew Phase and Timer are updated
- -- @param kew The Kew to update
- function endPlayPhase(kew)
- debug.log("ending the play phase for buildzone "..kew.buildzone.locid)
- processHighscores(kew)
- exportKewData(kew,true) -- saves the final state of the game and writes it to the online database
- cleanAfterGameOver(kew)
- kew.phase = registry.QUEUE_PHASE.GAMEOVER
- --displayTime(kew.buildzone.selector,0,0)
- kew.timer = kew.phases[kew.phase].length
- displayTitleToPlayers(kew.playerlist,"Times Up!","Use HOMECOMER to return to spawn")
- portal.drawPortal(kew.portal,kew.phase)
- debug.log("play phase for buildzone "..kew.buildzone.locid.. " - ended")
- end
- --- Move from the End Phase to the Wait Phase
- -- Anything that needs to be done ONCE before the End Phase starts
- -- happens here.
- -- Players are removed from the Kews playerlist
- -- Kew Phase and Timer are updated
- -- @param kew The Kew to update
- function endEndPhase(kew)
- debug.log("ending the final phase for buildzone "..kew.buildzone.locid)
- removePlayersFromKew(kew)
- kew.phase = registry.QUEUE_PHASE.DORMANT
- --displayTime(kew.buildzone.selector,0,0)
- kew.timer = kew.phases[kew.phase].length
- portal.drawPortal(kew.portal, kew.phase)
- debug.log("final phase for buildzone "..kew.buildzone.locid.. " - ended")
- end
- --- Calculate end of Phase and change to next Phase
- -- This code runs ONCE at the end of each phase and what actually
- -- happens is specific to which phase the given kew is in.
- -- Kew timers are filled and Kew Phases are updated here only.
- -- @param game The game object as created in setup()
- -- TODO try using the state pattern (http://gameprogrammingpatterns.com/state.html) and assign the corresponding endPhase function to a pointer and run it from there
- -- another example here http://lua-users.org/wiki/FiniteStateMachine
- function doPhaseEnds(game)
- for i,kew in ipairs(game.queues) do
- if kew.timer <= 0 then
- if kew.phase == registry.QUEUE_PHASE.DORMANT then
- --waiting phase ends goto play phase
- endWaitPhase(kew) --, game)
- elseif kew.phase == registry.QUEUE_PHASE.PLAYING then
- --playing phase ends goto end phase
- endPlayPhase(kew)
- elseif kew.phase == registry.QUEUE_PHASE.GAMEOVER and countActivePlayers(kew) == 0 then
- --end phase ends goto waiting phase
- endEndPhase(kew)
- end
- end
- end
- end
- -- Replaces important blocks such as Detectors
- -- Replaces everything that is needed to start the game. Does not
- -- rebuild the floor, or clear anything away. based on the settings it
- -- creates a full grid, or a partial grid, or no grid of Detectors
- -- it also places the ring, although this is disabled for now
- -- @param buildzone The buildzone to prepare
- function prepareBuildzone(buildzone)
- debug.log("Preparing buildzone "..buildzone.locid.." ...")
- local bz = buildzone
- local x,y,z,w = bz.x,bz.y,bz.z,bz.w
- --debug.log("fillBox from inside prepareBuildzone")
- --place the white grid accross the full buildzone as a base
- --commands.async.fill(x,y,z,x+w,y,z+w,registry.BLOCKS.CAMP_FLOOR.block)
- mcset.fillBox(box(x,y,z,w,1,w),registry.BLOCKS.CAMP_FLOOR)
- --mcset.fillRing(buildzone.x,buildzone.y-1,buildzone.z,buildzone.w,registry.BLOCKS.CONSTRUCTION.block) --this draws the construction stripe around the buildzone
- --create the grid of detectors surrounded by plus plugs
- if registry.DO_GRID then
- --mcset.fillGrid()
- local halfCell = math.floor(registry.GRIDCELL_SIZE/2)
- for x=0,registry.GRIDCELL_COUNT-1 do
- for z=0,registry.GRIDCELL_COUNT-1 do
- local rand = math.random()*100
- if rand > registry.GRID_HOLE_CHANCE then --and result then
- commands.async.setblock(bz.x+(x*registry.GRIDCELL_SIZE)+halfCell,bz.y+1,bz.z+(z*registry.GRIDCELL_SIZE)+halfCell,registry.BLOCKS.DETECT.block,registry.BLOCKS.DETECT.variant,"replace","minecraft:air")
- commands.async.fill(bz.x+(x*registry.GRIDCELL_SIZE)+halfCell-registry.PLUG_LENGTH, bz.y,bz.z+(z*registry.GRIDCELL_SIZE)+halfCell,bz.x+(x*registry.GRIDCELL_SIZE)+halfCell+registry.PLUG_LENGTH,bz.y,bz.z+(z*registry.GRIDCELL_SIZE)+halfCell,registry.BLOCKS.PLUG.block)
- commands.async.fill(bz.x+(x*registry.GRIDCELL_SIZE)+halfCell,bz.y,bz.z+(z*registry.GRIDCELL_SIZE)+halfCell-registry.PLUG_LENGTH,bz.x+(x*registry.GRIDCELL_SIZE)+halfCell,bz.y,bz.z+(z*registry.GRIDCELL_SIZE)+halfCell+registry.PLUG_LENGTH,registry.BLOCKS.PLUG.block)
- end
- end
- end
- end
- --mark the game in the LOCS array as not available anymore, and save the updated game grid to the grid file
- --change the flag to played=true
- --print("prepareBuildzone", buildzone.locid)
- updateZonePlayedState(buildzone,true)
- debug.log("Buildzone "..buildzone.locid.." is prepared.")
- end
- -- Removes everything in a buildzone
- -- Literally replaces every block inside the buildzone with air and
- -- then removes all floating items.
- -- @param buildzone A buildzone to clean
- function cleanBuildzone(buildzone)
- debug.log("Cleaning buildzone "..buildzone.locid.." ...")
- --for each level, remove all blocks
- --debug.log("fillBox from inside cleanBuildzone")
- mcset.fillBox(box(buildzone.x-1, buildzone.y, buildzone.z-1, buildzone.w+2, 256-buildzone.y, buildzone.w+2), registry.BLOCKS.AIR)
- --for h=buildzone.y,255 do
- -- commands.async.fill(buildzone.x,h,buildzone.z,buildzone.x+buildzone.w,h,buildzone.z+buildzone.w,"minecraft:air")
- --end
- --remove all floating items in the loaded part of the world
- commands.async.kill("@e[type=item]")
- debug.log("Buildzone "..buildzone.locid.." is cleaned.")
- end
- --- Takes players out of a Kews playerlist
- -- Gives all players a message telling them to return to spawn and
- -- then removes them from the playerlist. This releases them from being
- -- trapped in the buildzone boundary.
- -- @param kew The Kew to clear players from
- function removePlayersFromKew(kew)
- debug.log("removing all players from the list for buildzone "..kew.buildzone.locid.." ...")
- for name, player in pairs(kew.playerlist) do
- if registry.ANNOUNCE_ENGLISH then
- -- announce success in English
- commands.async.tellraw(player.name,'["",{"text":"TIME OUT! GAME COMPLETE! Use your HOMECOMER to return to spawn.","color":"white"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- -- announce success in German
- commands.async.tellraw(player.name,'["",{"text":"ENDE! SPIEL ABGESCHLOSSEN! Nutze den HEIMKEHRER um zum Anfang zurück zu kehren.","color":"gold"}]')
- end
- end
- kew.playerlist = {}
- debug.log("Player list for buildzone "..kew.buildzone.locid.." is now empty")
- end
- --- Resets a player to be harmless
- -- Use this when you want to be sure a player cannot modify blocks
- -- and that they have no wool to place. You can also send them a friendly
- -- message about why you took their stuff!
- -- @param playername String of the players name
- -- @param message String A friendly message for the given player
- function resetPlayer(playername,message)
- debug.log("resetPlayer: reseting player '"..playername.."' to adventure mdoe with no wool and pickaxe")
- commands.tell(playername,message)
- commands.async.gamemode(2,playername)
- commands.async.clear(playername,"minecraft:wool")
- commands.async.clear(playername,"minecraft:stone_pickaxe")
- end
- --- Checks if given player is in a buildzone
- -- Uses a playerdata object and the buildzone selector
- -- @param player A playerdata object from newPlayerData()
- -- @param buildzone A buildzone object to get the selector from
- -- @return result True if the player is in the buildzone
- function checkForPlayerInBuildzone(player,buildzone)
- local result,message = commands.testfor('@a[name='..player.name..','..buildzone.selector..']')
- return result
- end
- --[[
- --- Checks if a given player is in a given waiting area
- -- Builds a selector for the portal from the given portal
- -- @param player A playerdata object from newPlayerData()
- -- @param portal A portal from a Kew object
- -- @return result True if the player is in the wait area
- function checkForPlayerInPortal(player,portal) -- TODO see if this function is needed, currently not used anywhere
- --local selector = "x="..portal.zone.corner_x..",y="..portal.zone.corner_y..",z="..portal.zone.corner_z..",dx="..portal.zone.corner_..",dy=1,dz="..registry.PORTAL.SIZE
- local selector = box_base(portal.zone).selector --gets a selector for only the bottom layer of blocks, that way when we fill that layer with blocks we 'close' the portal
- local result,message = commands.testfor('@a[name='..player.name..','..selector..']')
- return result
- end
- --- Checks if a given player is in a given area
- -- Builds a selector for the area from the given area parameters
- -- @param player A playerdata object from newPlayerData()
- -- @param area Specified as a table with x,y,z,w,l,h fields
- -- @return result True if the player is in the wait area
- function checkForPlayerInArea(player,area) -- TODO see if this function is needed, currently not used anywhere
- local selector = "x="..area.x..",y="..area.y..",z="..area.z..",dx="..area.w..",dy="..area.h..",dz="..area.l
- --debug.log("checking for player '"..player.name.."' in area: "..selector)
- local result,message = commands.testfor('@a[name='..player.name..','..selector..']')
- return result
- end
- --]]
- --- Gets a list of all players in a given Portal
- -- Builds a selector for the portal from the given portal
- -- @param portal A portal from a Kew object
- -- @return result A list of playerdata objects from GetAllPos()
- function checkPortal(portal)
- --local selector = "x="..portal.x..",y="..portal.y..",z="..portal.z..",dx="..registry.PORTAL.SIZE..",dy=1,dz="..registry.PORTAL.SIZE
- local selector = box_base(box_remove_base(portal.zone)).selector --gets a selector for only the second layer of blocks, that way when we fill that layer with blocks we 'close' the portal
- debug.log("checkPortal(): checking portal {"..selector.."}")
- local result = getAllPos('m=2,'..selector)
- debug.log("result is:"..json.encode(result))
- return result
- end
- --- Updates our information on all players we think are in the game
- -- Checks the waitlist and all kews. Each player is checked if they are
- -- still online and if they are playing in a buildzone.
- -- @param game A game object as created by setup()
- function checkPlayers(game)
- --local selector = "x="..registry.WAITZONE.x..",y="..(registry.WAITZONE.y-2)..",z="..registry.WAITZONE.z..",dx="..registry.WAITZONE.w..",dy=256,dz="..registry.WAITZONE.l
- --local loggedIn = getAllPos('m=2,'..selector)
- --get all players in adventure mode and sort them around
- local loggedIn = getAllPos('m=2')
- number_of_players_adventure = tableLength(loggedIn)
- --number_of_players_creative = tableLength(getAllPos('m=1')) --disabled for now until we figure how to deal with the glitches
- --refresh waitlist
- --game.waitlist = loggedIn --the .waitlist property is not used anymore
- --check currently playing players
- for l,kew in ipairs(game.queues) do
- for name,builder in pairs(kew.playerlist) do
- local isPlaying = checkForPlayerInBuildzone(builder,kew.buildzone)
- --remove players who are already in kews from the waitlist
- for j, player in ipairs(loggedIn) do
- if player.name == builder.name then
- --table.remove(loggedIn,j) --doing it this way created problems as the iterator skips an element after removal
- loggedIn[j] = nil --doing it this way is safe
- end
- end
- --if the game is in progress and the player is not found then remove them from the gamekew
- if not isPlaying and kew.phase == registry.QUEUE_PHASE.PLAYING then
- --table.remove(kew.playerlist,i)
- kew.playerlist[builder.name].status = registry.PLAYER_STATUS.LEFT_OTHERWISE
- --print("Removed "..builder.name.." from game in progress")
- end
- end
- end
- --check if players are in the spawn area
- --debug.log("the loggedIn table is:"..json.encode(loggedIn))
- for j, player in ipairs(loggedIn) do
- local isInSpawnArea = checkForPlayerInArea(player, registry.MAIN_SPAWN_AREA)
- if isInSpawnArea then
- loggedIn[j] = nil
- end
- end
- --if there are still players in loggedIn then they must be force moved to spawn
- if tableLength(loggedIn) > 0 then
- debug.log("teleporting free roaming players back to spawn area: "..playerListToString(loggedIn))
- teleportToPoint(registry.SPAWN.x,registry.SPAWN.y,registry.SPAWN.z,loggedIn,true,"You have been teleported to the spawn area. Use the portals to join a game", "(Translate to DE)You have been teleported to the spawn area. Use the portals to join a game")
- end
- end
- function addToGame(kew,waiter)
- -- check if player already exists in playerlist
- -- teleport them
- -- add to playerlist if not
- -- add starting items if not
- -- update player status if they exist
- -- if playerlist is full, fill in the portal
- local buildzone = kew.buildzone
- local textEN = "You should never see this text"
- local textDE = "You should never see this text (in German)"
- if (#kew.playerlist > 0 and kew.playerlist[waiter.name]) then
- -- player has been here before so just change status to 0 and tp them
- --print("player was here before")
- kew.playerlist[waiter.name].status = registry.PLAYER_STATUS.IN_GAME
- textEN = "Welcome back to the buildzone."
- textDE = "Welcome back to the buildzone (TRANSLATE)"
- debug.log("Adding '"..waiter.name.."' as RETURNING player to buildzone #"..buildzone.locid)
- else
- --print("player was NOT here before")
- kew.playerlist[waiter.name] = waiter
- kew.playerlist[waiter.name].status = registry.PLAYER_STATUS.IN_GAME
- giveItems({waiter},registry.STARTING_ITEMS)
- textEN = "Welcome to the buildzone. Build your first structure."
- textDE = "Welcome to the buildzone. Build your first structure. (TRANSLATE)"
- debug.log("Adding '"..waiter.name.."' as NEW player to buildzone #"..buildzone.locid)
- end
- --print("buildzone", buildzone)
- --print("waiter", waiter)
- --print("textEN", textEN)
- --print("textDE", textDE)
- scatterIntoZone(buildzone, {waiter}, textEN, textDE)
- --teleportToPoint(buildzone.x+2+(buildzone.w/2),buildzone.y+5,buildzone.z+2+(buildzone.w/2),{waiter},false,textEN, textDE)
- end
- function inZoneChangeMode(_box,mode)
- --debug.log("changing every in wait zone to adventure mode")
- local selector = _box.selector
- commands.async.gamemode(mode,"@a["..selector.."]")
- end
- -- Adds waiting players to Kew playerlists
- -- checks which players are in which portals and adds them to games
- -- based on that. If there are any creative mode players in the zone,
- -- change them to adventure mode.
- -- @param game the game object as created by setup()
- function allocateWaiters(game)
- for i, kew in ipairs(game.queues) do
- --debug.log("allocateWaiters: the queues for loop start")
- if (kew.phase == registry.QUEUE_PHASE.DORMANT or kew.phase == registry.QUEUE_PHASE.PLAYING) and countActivePlayers(kew) < kew.maxplayers then
- --debug.log("allocateWaiters: the if statement start")
- inZoneChangeMode(kew.portal.zone,2)
- local waiters = checkPortal(kew.portal)
- if #waiters > 0 then debug.log("found player in portal") end
- for index,waiter in ipairs(waiters) do
- --debug.log("allocateWaiters: the waiters for loop start")
- addToGame(kew,waiter)
- --debug.log("allocateWaiters: the waiters for loop end")
- end
- --debug.log("allocateWaiters: the if statement end")
- end
- --debug.log("allocateWaiters: the queues for loop end")
- end
- --debug.log("allocateWaiters: completed")
- end
- --- Adds a new request to check a Detector Block safely
- -- Makes sure that incoming check requests dont exist already
- -- Players can still have multiple requests, but no two requests from
- -- the same tile will exist. Requests are processed at a rate of one per
- -- game step, so its important that we dont have duplicates as it slows
- -- the game loop down a lot.
- -- @param player A playerdata Object as created by newPlayerData().
- -- @param buildzone A buildzone to which the request should be added.
- function addToChecklist(player,buildzone)
- for _, detector in ipairs(buildzone.waitingForCheck) do
- if detector.x == player.x and detector.y == player.y and detector.z == player.z then
- return false
- end
- end
- table.insert(buildzone.waitingForCheck,player)
- return true
- end
- --- Cleans barriers from a buildzone
- -- removes all barrier blocks from a buildzone which are used to
- -- carve space in Elements.
- -- TODO This should be updated to only clean an area
- -- the size of an element specified with x,y,z.
- -- @param buildzone A buildzone that should be cleaned
- function cleanBarriers(buildzone)
- --debug.log()
- for h=0,200 do
- commands.async.fill(buildzone.x,buildzone.y+h,buildzone.z,buildzone.x+buildzone.w,buildzone.y+h,buildzone.z+buildzone.w,"minecraft:air",0,"replace","minecraft:barrier")
- end
- end
- --- Update a buildzone that is in the Play Phase
- -- Detection and Game Logic is mostly kept here. Meat and Potatoes time.
- -- Wow, actually most of this is disabled for now.
- -- If there are waiting requests for checking a detector block, do
- -- the first one in the list.
- -- Check it against the catalogue we have stored in the buildzone
- -- (not the complete catalogue). If it matches then clone in the new
- -- Building, give Rewards and clean Barriers.
- -- Currently always returns False for victory.
- -- @param kew a Kew object which contains a Buildzone
- -- @return victory Boolean, Did this placement cause a victory?
- function updatePlayedZone(kew)
- local buildzone = kew.buildzone
- local victory = false
- local buildzoneSelector = buildzone.selector
- --get all players on a detector block, add them to the list of things to check
- local detectLocations = getAllOnBlockType(registry.BLOCKS['DETECT'].block,buildzoneSelector)
- --print(#detectLocations.." Players standing on detectors")
- for _, player in ipairs(detectLocations) do
- addToChecklist(player,buildzone)
- end
- --DEAL WITH THE DETECTOR AT THE TOP OF THE LIST IF THERE IS ONE
- if #buildzone.waitingForCheck > 0 then
- --DO PARTICLE EFFECTS IF A DETECTING BLOCK THAT IS DETECTING
- for i,loc in ipairs(buildzone.waitingForCheck) do
- mcset.search(loc.x,loc.y+1,loc.z)
- end
- local totalResult = false
- local checked = table.remove(buildzone.waitingForCheck,1)
- local x,y,z,name = checked.x,checked.y,checked.z,checked.name
- for i,element in pairs(buildzone.elements) do
- local result,message = commands.testforblocks( element.keyX, element.keyY, element.keyZ, element.keyX+element.sizeX, element.keyY+registry.ELEMENT_HEIGHT, element.keyZ+element.sizeZ, x-math.floor(element.sizeX/2), y-1, z-math.floor(element.sizeZ/2),"masked")
- if result then
- --clone in the correct vocab
- local cloneres,clonemes = commands.clone( element.elementX, element.elementY, element.elementZ, element.elementX+element.sizeX, element.elementY+registry.ELEMENT_HEIGHT, element.elementZ+element.sizeZ, x-math.floor(element.sizeX/2), y-1, z-math.floor(element.sizeZ/2),"masked")
- --debug.log(clonemes[1])
- --commands.async.give(name,element.reward)
- debug.log("successful detect of element #"..i.." in buildzone #"..buildzone.locid.." at:"..x..", "..y..", "..z)
- -- announce success in English
- --[[
- local rewardType = 'nature'
- local rewardTypeDE = 'grüne'
- if element.rewardUrban then
- rewardType = 'urban'
- rewardTypeDE = 'urbane'
- end
- --]]
- if registry.ANNOUNCE_ENGLISH then
- --announce success in English
- --commands.async.tellraw(name,'["",{"text":"You built a '..element.nameEN.. ' ('..element.typeEN..'), which is '..element.height..'m tall and gives '..element.slots..' density pts, '..element.greenSlots..' green pts and '..element.rewardCount..'x '..rewardType..' resource!","color":"white"}]')
- commands.async.tellraw(name,'["",{"text":"You built an element!","color":"white"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- -- announce success in German
- --commands.async.tellraw(name,'["",{"text":"Du hast ein '..element.typeDE..' | '..element.nameDE.. ' gebaut, das '..element.height..' Meter hoch ist und dir '..element.slots..' Punkte für die Dichte einbringt, jedoch '..element.greenSlots..' Punkte für Grünflächen und '..element.rewardCount..' '..rewardTypeDE..' Ressourcen!","color":"gold"}]')
- commands.async.tellraw(name,'["",{"text":"Du hast ein element gebaut!","color":"gold"}]')
- end
- --clear out barrier blocks
- cleanBarriers(buildzone)
- --ADD THE NEW STRUCTURE TO THE RECORDS
- --table.insert(buildzone.structures,element.nameEN)
- --record the place of the element as x, y, z and element id
- local building = {id=element.id,xpos=x,ypos=y-1,zpos=z,name="element_"..element.id,time=os.clock(),player=name}
- table.insert(buildzone.buildings, building)
- --save the game incrementally
- exportKewData(kew,false) -- saves the new state of the game locally to a file, doesn't write to the database yet
- --[[ DISABLED UNTIL THE NEW GOAL SYSTEM IS IMPLEMENTED
- buildzone.greenSlots = buildzone.greenSlots + element.greenSlots
- buildzone.filledSlots = buildzone.filledSlots + element.slots
- local newHeight = y + element.height - buildzone.y-1 -- the Y coordinate of the highest block above the ground. Our world has its ground at 55 which is in buildzone.y, subtracting 1 to compensate for player height
- if newHeight > buildzone.highest then
- buildzone.highest = newHeight
- end
- -- count variety
- local varietyId = element.id -- we use a variety id instead of the element id because vocab id 1,2,3,4 for example are all houses so it is the same variety
- --we add only one type of house, one type of green house, one type of garden and one type of extension and we skip the two risers
- --]]
- --[[
- if vocab.id == 2 or vocab.id == 3 or vocab.id == 4 then varietyId = 1 end-- only one type for the 4 different orientations of the house
- if vocab.id == 14 or vocab.id == 15 or vocab.id == 16 then varietyId = 13 end-- only one type for the 4 different orientations of the house extension
- if vocab.id == 26 or vocab.id == 27 or vocab.id == 28 then varietyId = 25 end-- only one type for the 4 different orientations of the house garden extension
- if vocab.id == 38 or vocab.id == 39 or vocab.id == 40 then varietyId = 37 end-- only one type for the 4 different orientations of the green roof house
- if varietyId ~= 17 and varietyId ~= 18 then --skip the two riser as they are not buildings
- --]]
- --[[
- if buildzone.variety[varietyId] then
- --print("increasing existing item")
- buildzone.variety[varietyId] = buildzone.variety[varietyId] + 1
- else
- --print("adding new item")
- buildzone.variety[varietyId] = 1
- end
- --end
- --- CHECK FOR PERSONAL RECORDS
- --- check if the new structure is the highest
- --- CHANGE here to live detect the contribution of the new structure to the 4 goals and update them
- local personalbest = tracker.getScore(name,"highest")
- if personalbest.count < newHeight then
- --commands.async.tell(name,"You just topped your personal record for highest structure!")
- commands.async.scoreboard("players","add",name,"highest",1)
- if registry.ANNOUNCE_ENGLISH then
- -- announce success in English
- commands.async.tellraw(name,'["",{"text":"You just topped your personal record for highest neighbourhood!","color":"green"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- -- announce success in German
- commands.async.tellraw(name,'["",{"text":"Du hast soeben deinen persönlichen Rekord für die höchste Nachbarschaft gebrochen!","color":"gold"}]')
- end
- end
- ---
- ---
- -- CHECK if placing the current structure would result in beating a server-wide record on the 4 goals
- --calculate total slots - FOR GOAL "DENSEST NEIGHBOURHOOD"
- local most = tracker.getScore("Densest_[points]","highscores")
- local Kint = math.floor(100 * buildzone.filledSlots / 49) -- Kint is the density index made from built area (filledSlots) over ground area (7x7 slots = 49)
- if Kint > most.count then
- commands.async.scoreboard("players","set","Densest_[points]","highscores",Kint)
- if registry.ANNOUNCE_ENGLISH then
- -- announce success in English
- commands.async.tellraw("@a",'["",{"text":"Great! '..name.. ' just topped the record for the DENSEST NEIGHBOURHOOD!","color":"green"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- -- announce success in German
- commands.async.tellraw("@a",'["",{"text":"Sehr gut! '..name.. ' hat einen neuen Rekord für die DICHTESTE NACHBARSCHAFT aufgestellt!","color":"gold"}]')
- end
- end
- -- FOR THE GOAL "MOST DIVERSE NEIGHBOURHOOD"
- -- here we need to count how many varieties of buildings there are
- --local structures = tracker.tallyTable(buildzone.structures) -- this counts the variety of buildings in a game
- local mostDiverse = tracker.getScore("Most-Diverse_[out-of-26]","highscores")
- local typeCount = tableLength(buildzone.variety)
- --print("variety count is: "..typeCount)
- if typeCount > mostDiverse.count then
- commands.async.scoreboard("players","set","Most-Diverse_[out-of-26]","highscores", typeCount)
- if registry.ANNOUNCE_ENGLISH then
- -- announce success in English
- commands.async.tellraw("@a",'["",{"text":"Wow! '..name.. ' just topped the record for the MOST DIVERSE NEIGHBOURHOOD!","color":"green"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- -- announce success in German
- commands.async.tellraw("@a",'["",{"text":"Wow! '..name.. ' hat soeben einen neuen Rekord für die VIELSEITIGSTE NACHBARSCHAFT aufgestellt!","color":"gold"}]')
- end
- end
- -- FOR THE GOAL "GREENEST NEIGHBOURHOOD"
- -- here we need to count the number of green vocabs
- local greenest = tracker.getScore("Greenest_[points]","highscores")
- local Gint = math.floor(100*buildzone.greenSlots/49) --Gint is the green index, made from green area (greenSlots) over ground area (7x7 slots = 49)
- if Gint > greenest.count then
- commands.async.scoreboard("players","set","Greenest_[points]","highscores",Gint)
- if registry.ANNOUNCE_ENGLISH then
- -- announce success in English
- commands.async.tellraw("@a",'["",{"text":"Awesome! '..name.. ' just topped the record for the GREENEST NEIGHBOURHOOD!","color":"green"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- -- announce success in German
- commands.async.tellraw("@a",'["",{"text":"Klasse! '..name.. ' hat einen neuen Rekord für die GRÜNSTE NACHBARSCHAFT aufgestellt!","color":"gold"}]')
- end
- end
- --calculate highest placement -- FOR THE GOAL "TALLEST NEIGHBOURHOOD"
- local highest = tracker.getScore("Tallest_[meters]","highscores")
- if buildzone.highest > highest.count then
- commands.async.scoreboard("players","set","Tallest_[meters]","highscores",buildzone.highest)
- if registry.ANNOUNCE_ENGLISH then
- -- announce success in English
- commands.async.tellraw("@a",'["",{"text":"Incredible! '..name..' just topped the record for TALLEST NEIGHBOURHOOD!","color":"green"}]')
- end
- if registry.ANNOUNCE_GERMAN then
- -- announce success in German
- commands.async.tellraw("@a",'["",{"text":"Unglaublich! '..name..' hat einen neuen Rekord für die HÖCHSTE NACHBARSCHAFT aufgestellt!","color":"gold"}]')
- end
- end
- --increase the "how many structures did i build" score for the building player
- commands.async.scoreboard("players","add",name,"built",1)
- commands.async.scoreboard("players","add",name,"building_"..element.id,1)
- --]]
- totalResult = true
- break
- end
- end
- if totalResult then
- --yey win, do a happy time
- mcset.successParticle(x,y,z)
- else
- --no vocab found so do a fail particle
- --announce in English
- commands.async.tellraw(name,'["",{"text":"The shape you have built does not match any shape in the catalogue, try a different one.","color":"red"}]')
- if registry.ANNOUNCE_GERMAN then
- -- announce in German
- commands.async.tellraw(name,'["",{"text":"Die Kombination, die du gebaut hast passt leider zu keinem Gebäude, versuche es mit einer anderen Form.","color":"gold"}]')
- end
- mcset.failParticle(x,y-1,z)
- debug.log("The key that player activated is not in the catalogue.")
- end
- end
- return victory
- end
- -- the main function
- function MAIN()
- debug.log("Starting 20.000 BLOCKS")
- debug.log("Version: "..VERSION_NUMBER.." - "..VERSION_NAME)
- debug.log(_G._VERSION) -- log the Lua version number
- --LOCS = buildGrid(registry.GAME_FIELD.countX, registry.GAME_FIELD.countZ)
- local game = setup()
- if not game then
- return
- end
- --print("20.000 BLOCKS is running...")
- --local skip = false
- --if not skip then
- debug.log("20.000 BLOCKS is running...")
- while true do
- --debug.log("beginning of main game loop")
- debug.printState(game, registry.DEBUG_MODE)
- --debug.log("begin update(game)")
- --debug.log(to_string(game))
- update(game)
- --debug.log("end update(game)")
- --debug.log(to_string(game))
- --make always nice weather
- --commands.async.weather("clear",10000)
- --if Q is held then exit the game loop
- local event, key, isHeld = os.pullEvent("key")
- if key == keys.q and isHeld then break end
- -- this needs to be quite high to avoid players experiencing glitches
- -- where they are returned back to their last location from few seconds ago
- -- if they move fast. this happens becuase we use the teleport command
- -- to get player locations by teleporting them to their current location
- os.sleep(2)
- --
- --debug.log("end of main game loop")
- end
- --end
- -- do the things we want to do on exit
- debug.log("Quiting 20.000 BLOCKS...")
- loader.unloadPackages()
- end
- -- process command line parameters
- local tArgs = { ... } -- get the command line arguments
- if #tArgs == 1 then
- cleanbuildzone = tArgs[1]
- elseif #tArgs > 1 then
- print("Usage: play <true(cleans buildzone)/false(default, doesnt clean)>")
- return
- end
- -- run the game
- MAIN()
Add Comment
Please, Sign In to add comment