Created
June 27, 2018 20:35
-
-
Save kbroman/54003999307cc76dc60b60e34436b9f1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
LIB = "packages/" | |
#LIB = NULL | |
# These were not on CRAN, so it did not install to "packages/" | |
# library ("curl") | |
# library ("devtools") | |
# devtools::install_github ('ropenscilabs/miner') | |
# devtools::install_github ('ropenscilabs/craft') | |
library (miner) | |
library (craft) | |
library (igraph) | |
# library (Rmaze) | |
# Material constants | |
AIR = 0 | |
STONE = 1 | |
DIRT = 3 | |
SAPLING = 6 | |
WATER_FLOWING = 8 | |
WATER_STATIONARY = 9 | |
LAVA_FLOWING = 10 | |
LAVA_STATIONARY = 11 | |
SAND = 12 | |
GRAVEL = 13 | |
WOOD = 17 | |
LEAVES = 18 | |
COBWEB = 30 | |
GRASS_TALL = 31 | |
WOOL = 35 | |
SNOW = 78 | |
ICE = 79 | |
STAIRS_WOOD = 53 | |
STAIRS_COBBLESTONE = 67 | |
STAIRS_BRICK = 108 | |
STAIRS_STONE_BRICK = 109 | |
SNOW_BLOCK = 80 | |
CACTUS = 81 | |
SUGAR_CANE = 83 | |
# jack-o-lantern 86 | |
# lit jack-o-lantern 91 | |
MUSHROOM = 99 | |
RED_MUSHROOM = 100 | |
MELON = 103 | |
FLOWER_YELLOW = 37 | |
FLOWER_CYAN = 38 | |
MUSHROOM_BROWN = 39 | |
MUSHROOM_RED = 40 | |
GLASS = 20 | |
BRICK = 45 | |
DIAMOND_BLOCK = 57 | |
WOOD_PLANKS = 125 | |
STAINED_GLASS_BLOCK = 95 | |
STAINED_GLASS_PANE = 160 | |
STONE_BRICK = 98 | |
TORCH = 50 | |
DOOR_WOOD = 64 | |
STONE_PRESSURE_PLATE = 70 | |
FENCE = 85 | |
CARPET = 171 | |
# Color constants | |
WHITE = 0 | |
ORANGE = 1 | |
MAGENTA = 2 | |
LIGHT_BLUE = 3 | |
YELLOW = 4 | |
LIME = 5 | |
PINK = 6 | |
GREY = 7 | |
LIGHT_GREY = 8 | |
CYAN = 9 | |
PURPLE = 10 | |
BLUE = 11 | |
BROWN = 12 | |
GREEN = 13 | |
RED = 14 | |
MAX_COLOR = BLACK = 15 | |
# Direction is "E (+x), W (-x), S (+z), or N (-z), | |
# 0 for E, 1 for W, 2 for S, 3 for N. | |
# These match the stair style ID. | |
# CHANGE TO BE DOOR STYLE ID, BUT UPDATE flipDir WHEN YOU DO! | |
EAST <- 0 | |
WEST <- 1 | |
SOUTH <- 2 | |
NORTH <- 3 | |
# Door style constants. Add/OR these together for the style ID. | |
DOOR_TOP = 0x8 | |
DOOR_BOTTOM = 0x0 | |
DOOR_OPEN = 0x4 | |
DOOR_CLOSED = 0x0 | |
# Note that these do NOT match the stair style ID. | |
DOOR_EAST = 0x0 | |
DOOR_SOUTH = 0x1 | |
DOOR_WEST = 0x2 | |
DOOR_NORTH = 0x3 | |
# General functions for connecting to the game world: | |
# mc_connect: Connect to a Minecraft server | |
# chatPost: Make a chat message appear to players in their clients | |
# getChatPosts: Retrieve the most recent chat message | |
# mc_close: Close the connection to the Minecraft server | |
# This is the local host IP. Connects to the Minecraft server on this machine. | |
mcConnect <- function (ip = '127.0.0.1') { | |
mc_connect (ip) | |
chatPost ("R connection established!") | |
} | |
mcClose <- function () { | |
mc_close () | |
} | |
# Functions for interacting with player location and direction (the angle of | |
# their gaze). Note that in Minecraft, x is East, y is Up and z is South, and | |
# the unit is one block. (A block is a 1-unit cube.) The player figure is about | |
# 2 units tall. | |
# getPlayerIds: Get a list of players in the world | |
# getPlayerPos: Find the position of a specified player | |
# getPlayerDirection: Get a unit vector representing player direction | |
# getPlayerRotation: Return the angle of the player's direction in the x-z plane | |
# getPlayerPitch: Return the up/down angle of the player's direction | |
# setPlayerPos: Teleport a player to a specified location | |
# Functions for interacting with the Minecraft world. | |
# getHeight: Find the height of the world (the y value of the highest non-air | |
# point) at a given x-z location | |
# getBlock: Return the type of block (air, wood etc) in a given location, as a | |
# code. See the provided data frame mc_items for a list of blocks and item | |
# codes. | |
# setBlock: change the block type at a given location. Use this to place items | |
# in the world. | |
# getBlocks: Return the block types in a rectangular region, as a 3-dimensional | |
# array. | |
# setBlocks: Fill a rectangular region with a specific block type | |
# getBlockHits: Return the locations of blocks recently struck by a player's | |
# iron sword | |
whereami <- function (playerId = NULL) | |
{ | |
getPlayerPos (playerId, tile = T) | |
} | |
# Allows you to set a cuboid of blocks, but also specify the style ID (e.g., color, | |
# direction of stairs) | |
setBlocksStyle <- function (x0, y0, z0, x1, y1, z1, id, style = 0, debugFlag = FALSE) | |
{ | |
for (x in x0:x1) { | |
for (y in y0:y1) { | |
for (z in z0:z1) { | |
if (debugFlag) { print (paste ("set block at:", | |
x, y, z, collapse = " ")) } | |
setBlock (x, y, z, id, style) | |
} | |
} | |
} | |
} | |
# Allows you to set a cuboid of blocks with a random percentages of different | |
# blocks or block styles. Does not support different style vectors per block. | |
# | |
# Example: stained glass color mix | |
# setBlocksMix (77, 28, -1033, 33, 38, -1033, 160, | |
# c (.25, .2, .2, .2, .15), | |
# style = c (BLUE, LIGHT_BLUE, PURPLE, MAGENTA, GREEN)) | |
setBlocksMix <- function (x0, y0, z0, x1, y1, z1, ids, probs, styles = 0, | |
debugFlag = FALSE) | |
{ | |
# Length times width times height | |
totalBlocks <- (abs (x1 - x0) + 1) * (abs (y1 - y0) + 1) * (abs (z1 - z0) + 1) | |
if (debugFlag) print (paste ("totalBlocks:", totalBlocks)) | |
if (length (ids) > 1) { | |
if (length (ids) != length (probs)) { | |
print ("Number of block IDs does not match number of probabilities.") | |
return () | |
} | |
# Create the total array of block IDs to set. | |
blockArray <- sample (ids, totalBlocks, replace = TRUE, probs) | |
} else { | |
# Repeat the same block throughout the cuboid. | |
blockArray <- rep (ids, totalBlocks) | |
} | |
if (debugFlag) print (paste (c("blockArray:", blockArray), collapse = " ")) | |
if (length (styles) > 1) { | |
if ((length (ids) > 1) && (length (ids) != length (styles))) { | |
print ("Number of styles does not match number of block IDs.") | |
return () | |
} | |
# Create the total array of styles to set. | |
styleArray <- sample (styles, totalBlocks, replace = TRUE, probs) | |
} else { | |
# Repeat the same style throughout the cuboid. | |
styleArray <- rep (styles, totalBlocks) | |
} | |
if (debugFlag) print (paste (c("styleArray:", styleArray), collapse = " ")) | |
# Build the specified blocks and styles. | |
i <- 1 | |
for (x in x0:x1) { | |
for (y in y0:y1) { | |
for (z in z0:z1) { | |
if (debugFlag) print (paste ("i:", i, | |
"setting x:", x, "y: ", y, "z:", z, | |
"block:", blockArray[i], | |
"style:", styleArray[i], collapse = " ")) | |
setBlock (x, y, z, blockArray[i], styleArray[i]) | |
i <- i + 1 | |
} | |
} | |
} | |
} | |
# Clears out a cube around the player. | |
clearSpace <- function (length, playerId = NULL) { | |
pos <- getPlayerPos (playerId, tile = TRUE) | |
pos[1] <- pos[1] - length / 2 | |
pos[3] <- pos[3] - length / 2 | |
setBlocks (pos[1], pos[2], pos[3], | |
pos[1] + length, pos[2] + length, pos[3] + length, AIR) | |
} | |
# This doesn't work. Not sure why. | |
# | |
# setBlocks (one[1], one[2],one[3],two[1],two[3],AIR, getPlayerIds()) | |
# setBlocks (one[1], one[2],one[3],three[1],three[2],three[3],AIR, getPlayerIds()) | |
# setBlocks (one[1], one[2],one[3],four[1],four[2],four[3],AIR, getPlayerIds()) | |
# setBlocks (one[1], one[2],one[3],five[1],five[2],five[3],AIR, getPlayerIds()) | |
onecolonfive <- function () { | |
setBlocks (one[1], one[2],one[3],two[1],two[3],AIR) | |
setBlocks (one[1], one[2],one[3],three[1],three[2],three[3],AIR) | |
setBlocks (one[1], one[2],one[3],four[1],four[2],four[3],AIR) | |
setBlocks (one[1], one[2],one[3],five[1],five[2],five[3],AIR) | |
} | |
# Returns the opposite direction | |
flipDir <- function (direction) { | |
direction <- direction + 1 | |
if (direction == NORTH + 1) { | |
direction <- SOUTH | |
} | |
else if (direction == WEST + 1) | |
direction <- EAST | |
return (direction) | |
} | |
# Figure out the largest value of direction and translate to a compass direction. | |
getPlayerCompassDir <- function (playerId = NULL) { | |
vec <- getPlayerDirection (playerId) | |
# E (+x), W (-x), S (+z), or N (-z) | |
# See if magnitude of x is bigger than magnitude of z. Ignore y. | |
if (abs(vec[1]) > abs(vec[3])) { | |
if (vec[1] > 0) | |
return (EAST) | |
else | |
return (WEST) | |
} else { | |
if (vec[3] > 0) | |
return (SOUTH) | |
else | |
return (NORTH) | |
} | |
} | |
#' Draws a line | |
#' | |
#' Implements Bresenham's line-drawing algorithm in 3D space. Takes two points, | |
#' where each point is a vector of 3 coordinates, and constructs a line between | |
#' the two points. | |
#' Based on the Python code here: | |
#' https://gist.github.com/theJollySin/2ba8fbbb7d008b0d1f15 | |
#' @param p0 vector of the first endpoint | |
#' @param p1 vector of the second endpoint | |
#' @param blockType Minecraft block ID to draw the line with | |
#' @param blockStyle Minecraft block style ID to draw the line with (e.g., color) | |
#' @param debugFlag Set to true to get debugging information printed to console | |
#' | |
#' @export | |
drawLine <- function (p0, p1, blockType = STONE, blockStyle = 0, | |
debugFlag = TRUE) { | |
x <- x0 <- p0[1] | |
y <- y0 <- p0[2] | |
z <- z0 <- p0[3] | |
x1 <- p1[1] | |
y1 <- p1[2] | |
z1 <- p1[3] | |
# Calculate deltas. | |
dx = x1 - x0 | |
dy = y1 - y0 | |
dz = z1 - z0 | |
# Calculate the increments. | |
sx <- sign (dx) | |
sy <- sign (dy) | |
sz <- sign (dz) | |
# Figure out just the magnitude of change so we can decide which | |
# variable to loop/count through | |
dx = abs (dx) | |
dy = abs (dy) | |
dz = abs (dz) | |
if (debugFlag) print (paste ("dx: ", dx, ", dy: ", dy, | |
", dz: ", dz, | |
"sx: ", sx, ", sy: ", sy, | |
", sz: ", sz)) | |
# The largest difference is the one we'll loop/count through, cuz the other | |
# variables may or may not increase by 1 for each increase of the one with | |
# the largest difference. If the max is a tie, it doesn't matter which one it | |
# loops through. | |
# dz is the largest | |
if ((dz > dx) && (dz > dy)) | |
{ | |
if (debugFlag) print ("dz largest") | |
err_x <- dz / 2.0 | |
err_y <- dz / 2.0 | |
while (z != z1) { | |
if (debugFlag) print (paste ("adding: ", x, y, z)) | |
setBlock (x, y, z, blockType, blockStyle) | |
err_x <- err_x - dx | |
if (err_x < 0) { | |
x <- x + sx | |
err_x <- err_x + dz | |
} | |
err_y <- err_y - dy | |
if (err_y < 0) { | |
y <- y + sy | |
err_y <- err_y + dz | |
} | |
z <- z + sz | |
} | |
# dx largest | |
} else if (dx > dy) { | |
if (debugFlag) print ("dx largest") | |
err_z = dx / 2.0 | |
err_y = dx / 2.0 | |
while (x != x1) { | |
if (debugFlag) print (paste ("adding: ", x, y, z)) | |
setBlock (x, y, z, blockType, blockStyle) | |
err_y <- err_y - dy | |
if (err_y < 0) { | |
y <- y + sy | |
err_y <- err_y + dx | |
} | |
err_z <- err_z - dz | |
if (err_z < 0) { | |
z <- z + sz | |
err_z <- err_z + dx | |
} | |
x <- x + sx | |
} | |
# dy largest | |
} else { | |
if (debugFlag) print ("dy largest") | |
err_x = dy / 2.0 | |
err_z = dy / 2.0 | |
while (y != y1) { | |
if (debugFlag) print (paste ("adding: ", x, y, z)) | |
setBlock (x, y, z, blockType, blockStyle) | |
err_x <- err_x - dx | |
if (err_x < 0) { | |
x <- x + sx | |
err_x <- err_x + dy | |
} | |
err_z <- err_z - dz | |
if (err_z < 0) { | |
z <- z + sz | |
err_z <- err_z + dy | |
} | |
y <- y + sy | |
} | |
} | |
if (debugFlag) print (paste ("adding: ", p1[1], p1[2], p1[3])) | |
setBlock (p1[1], p1[2], p1[3], blockType, blockStyle) | |
} | |
#' Builds stairs | |
#' | |
#' Build stairs up or down from player position in the specified direction. | |
#' For now, width should be odd so that we can have a central staircase and | |
#' extend to the right and left of it to widen it. | |
#' @param down Whether to build stairs downward. If false, builds upward | |
#' @param stopHt The height at which to stop building. | |
#' @param stopOnNotAir Whether to stop if about to build where something exists. | |
#' @param width How wide to build the staircase. MUST BE AN ODD NUMBER | |
#' @param stairId The block ID for the stairs. | |
#' @param blockId The block ID for the block underneath each stair. Can set to AIR | |
#' if you don't want this block. | |
#' @param debugFlag Set true if you want some debugging information printed to the | |
#' console | |
#' @export | |
buildStairs <- function (down = TRUE, stopHt = 0, stopOnNotAir = TRUE, | |
width = 1, stairId = STAIRS_STONE_BRICK, | |
blockId = STONE_BRICK, | |
buildBlock = TRUE, debugFlag = FALSE) | |
{ | |
# Get current player position and heading. | |
Pos <- getPlayerPos (tile = TRUE) | |
x <- Pos[1] | |
y <- Pos[2] | |
z <- Pos[3] | |
if (down) y <- y - 1 | |
direction <- getPlayerCompassDir () | |
if (debugFlag) print (paste ("x: ", x, ", y: ", y, ", z: ", z, ", | |
dir: ", direction)) | |
# Make sure the input makes sense. | |
checkDown <- (stopHt > y) | |
if (!(xor (checkDown, down))) { | |
print (paste ("Input invalid. Current height: ", y, " Stop height: ", | |
stopHt, "Down? ", down)) | |
return () | |
} | |
# Calculate how many blocks to extend in either direction from the middle. | |
# (split this into L and R components that differ if width is even) | |
widthExtend <- ceiling ((width - 1) / 2) | |
# Set zInc and xInc based on direction. | |
# E (+x), W (-x), S (+z), or N (-z) | |
zInc <- 0 | |
xInc <- 0 | |
if (direction == NORTH) { | |
zInc <- -1 | |
zWidthInc <- 0 | |
xWidthInc <- 1 | |
} else if (direction == SOUTH) { | |
zInc <- 1 | |
zWidthInc <- 0 | |
xWidthInc <- 1 | |
} else if (direction == EAST) { | |
xInc <- 1 | |
xWidthInc <- 0 | |
zWidthInc <- 1 | |
} else { | |
xInc <- -1 | |
xWidthInc <- 0 | |
zWidthInc <- 1 | |
} | |
# Set stairDir depending on direction and if going up or down. | |
# Add validity check to make sure program will stop! | |
if (down) { | |
yInc <- -1 | |
stairDir <- flipDir (direction) | |
stopHt <- stopHt - 1 | |
} else { | |
yInc <- 1 | |
stairDir <- direction | |
stopHt <- stopHt + 1 | |
} | |
# Start one block in that direction (don't build right under the player) | |
x <- x + xInc | |
z <- z + zInc | |
if (debugFlag) print (paste ("Adj x: ", x, ", y: ", y, ", z: ", z)) | |
# Build to the specified height. | |
while (y != stopHt) { | |
# Check if the stair would destroy anything. | |
if (stopOnNotAir) { | |
AirCheck <- getBlocks ( x - (widthExtend * xWidthInc), y, | |
z - (widthExtend * zWidthInc), | |
x + (widthExtend * xWidthInc), y, | |
z + (widthExtend * zWidthInc) ) | |
if (!(all (AirCheck == AIR))) { | |
print ("Stopping before building stair into blocks!") | |
if (debugFlag) print (paste ("Stop x: ", x, ", y: ", y, ", z: ", z)) | |
break | |
} | |
} | |
# Build the stair. | |
setBlocksStyle (x - (widthExtend * xWidthInc), | |
y, z - (widthExtend * zWidthInc), | |
x + (widthExtend * xWidthInc), | |
y, z + (widthExtend * zWidthInc), | |
stairId, stairDir) | |
if (buildBlock) | |
{ | |
# Check if the block under the stair would destroy anything. | |
# ADD CHECK TO SKIP IF UP AND FIRST BLOCK! | |
if (stopOnNotAir) { | |
AirCheck <- getBlocks ( x - (widthExtend * xWidthInc), y - 1, | |
z - (widthExtend * zWidthInc), | |
x + (widthExtend * xWidthInc), y - 1, | |
z + (widthExtend * zWidthInc) ) | |
if (!(all (AirCheck == AIR))) { | |
print ("Stopping before building block into blocks!") | |
if (debugFlag) print (paste ("Stop x: ", x, ", y: ", y, | |
", z: ", z)) | |
break | |
} | |
} | |
# Build the block beneath the stair. | |
setBlocks (x - (widthExtend * xWidthInc), | |
y - 1, z - (widthExtend * zWidthInc), | |
x + (widthExtend * xWidthInc), | |
y - 1, z + (widthExtend * zWidthInc), | |
blockId) | |
} | |
# Advance to the next iteration. | |
x <- x + xInc | |
z <- z + zInc | |
y <- y + yInc | |
} | |
} | |
# Creates a building of specified dimensions and materials. | |
buildBuilding <- function (length = 8, width = 6, height = 5, color = BLUE, | |
foundation = STONE, wall = BRICK, | |
floorBoards = WOOD_PLANKS, carpet = CARPET, | |
windowMaterial = GLASS, gap = AIR) | |
{ | |
if (length == 0) { | |
length = sample (20:50, 1) | |
} | |
if (width == 0) { | |
width = sample (10:40, 1) | |
} | |
if (height == 0) { | |
height = sample (10:50, 1) | |
} | |
# Get the player position | |
pos <- getPlayerPos (tile = TRUE) | |
x <- pos[1] | |
y <- pos[2] | |
z <- pos[3] | |
x = x - (width / 2) | |
z = z - (width / 2) | |
# Build the foundation. | |
setBlocks (x, y - 2, z, | |
x + width, y - 2, z + length, foundation) | |
# Build the outer shell of the house | |
setBlocks (x, y, z, | |
x + width, y + height, z + length, wall) | |
# Carve the insides out with AIR | |
setBlocks (x + 1, y, z + 1, | |
x + width - 1, y + height - 1, z + length - 1, gap) | |
# Build the floor and carpet it. | |
setBlocks (x + 1, y - 1, z + 1, | |
x + width - 1, y - 1, z + length - 1, floorBoards) | |
setBlocksStyle (x + 1, y, z + 1, | |
x + width - 1, y, z + length - 1, carpet, color) | |
# Build the door. | |
setBlocks (x + width / 2, y, z, | |
x + width / 2, y + 1, z, gap) | |
buildDoor (x + width / 2, y, z) | |
# Build a window. RANDOMIZE THESE! | |
# setBlocks (x + 2, y + 2, z, | |
# x + 3, y + 3, z, windowMaterial) | |
# | |
# # Build another window | |
# setBlocks (x, y + 3, z + 1, | |
# x, y + 4, z + 2, windowMaterial) | |
} | |
# Copied and pasted from somewhere, but I forget where. Maybe | |
# https://ropenscilabs.github.io/miner_book/rendering-the-r-logo-in-minecraft.html#load-and-prepare-an-image-file-1 | |
buildRLogo <- function (playerId = NULL, height = 80, width = 70, debugFlag = TRUE) | |
{ | |
# For buildRLogo | |
library (plyr, lib.loc = LIB) | |
library (magrittr, lib.loc = LIB) | |
library (imager, lib.loc = LIB) | |
url <- "https://www.r-project.org/logo/Rlogo.png" | |
file <- basename (url) | |
download.file (url, file) | |
# Next, let's use the imager package to read it into R. | |
# library (imager, lib = LIB) | |
logo <- load.image (file) | |
# Print to see the size of the image. | |
if (debugFlag) print (logo) | |
# The image is stored as a 4-dimensional array: horizontal and vertical | |
# position, time, and color. | |
dim (logo) | |
# Let's reduce the size to 80x70. | |
logo <- resize (logo, height, width) | |
# There is a bit of shading in the logo, but mostly there are three colors: | |
# transparent, gray, and blue. If we look at a histogram of the first color | |
# channel (red), we can see the three pieces: | |
# par (mar = c (5.1, 4.1, 0.6, 0.6), las = 1) | |
# hist (logo[,,,1], breaks=100, main="", | |
# xlab="Red color intensity") | |
# So let's truncate at 0.05 and 0.4. | |
logo[] <- cut (logo, c(-Inf, 0.05, 0.4, Inf)) | |
logo <- logo[,,1,1] | |
# Render in Minecraft | |
# Now let's load the miner package, connect to the MineCraft server, and find a | |
# spot to place the logo. | |
host_pos <- getPlayerPos (playerId, tile = TRUE) | |
host_pos[2] <- host_pos[2] + 10 | |
print (host_pos) | |
# Now let's render the R logo using gray and blue wool, leaving parts | |
# transparent. First, we pick out the blue and gray wool blocks, to get the | |
# item and style IDs. | |
blue <- find_item ("Blue Wool") | |
gray <- find_item ("Light Gray Wool") | |
# Now we try rendering the logo. | |
for (i in 1:nrow (logo)) { | |
for (j in 1:ncol (logo)) { | |
if (logo[i, j] == 2) | |
setBlock (host_pos[1] + (nrow (logo) - i), | |
host_pos[2] + (ncol (logo) - j), | |
host_pos[3], | |
blue[2], blue[3]) | |
if(logo[i,j] == 3) | |
setBlock (host_pos[1] + (nrow (logo) - i), | |
host_pos[2] + (ncol (logo) - j), | |
host_pos[3], | |
gray[2], gray[3]) | |
} | |
} | |
} | |
#' Builds a door | |
#' | |
#' Build a door in the wall (or any non-AIR block) that the player is looking | |
#' at, opening in that same direction. If desired, also puts a pressure plate in | |
#' front of and behind the door to automatically open it. Alternatively, can | |
#' specify the coordinates and direction manually. | |
#' | |
#' @param x, y, z coordinates of where to put the door. If NULL, will calculate | |
#' from player's position and direction they are facing. | |
#' @param direction The compass direction the door should open in. | |
#' @param doorBlock The material to create the door out of. | |
#' @pressurePlate Whether to put pressure plates around the door. | |
#' @pressurePlateBlock The material to create the pressure plate out of. | |
#' @export | |
buildDoor <- function (x = NULL, y = NULL, z = NULL, direction = NULL, | |
doorBlock = DOOR_WOOD, pressurePlate = TRUE, | |
pressurePlateBlock = STONE_PRESSURE_PLATE, | |
playerId = NULL) | |
{ | |
# Find out where the player is looking. | |
if (is.null (direction)) | |
direction <- getPlayerCompassDir () | |
# E (+x), W (-x), S (+z), or N (-z) | |
xInc <- zInc <- 0 | |
if (direction == EAST) { | |
doorDir <- DOOR_EAST | |
xInc <- 1 | |
} else if (direction == WEST) { | |
doorDir <- DOOR_WEST | |
xInc <- -1 | |
} else if (direction == SOUTH) { | |
doorDir <- DOOR_SOUTH | |
zInc <- 1 | |
} else { | |
doorDir <- DOOR_NORTH | |
zInc <- -1 | |
} | |
if (is.null (x) || is.null (y) || is.null (z)) { | |
pos <- getPlayerPos (playerId, tile = TRUE) | |
# Move in the direction the player is looking, and up one in case there | |
# is carpet | |
x <- pos[1] + xInc | |
y <- pos[2] + 1 | |
z <- pos[3] + zInc | |
isNotAir <- getBlock (x, y, z, FALSE) | |
# Scan in the direction the player is looking until a non-air block is | |
# found. | |
while (isNotAir == AIR) { | |
x <- x + xInc | |
z <- z + zInc | |
isNotAir <- getBlock (x, y, z, FALSE) | |
} | |
# Move back down to the floor. | |
y <- y - 1 | |
} | |
# Need to clear the space first, or else the top of the door will be covered. | |
setBlocks (x, y, z, x, y + 1, z, AIR) | |
# Create the door! It's two blocks high and the top and bottom blocks need | |
# to be specifically specified. | |
setBlock (x, y, z, doorBlock, DOOR_BOTTOM + doorDir) | |
setBlock (x, y + 1, z, doorBlock, DOOR_TOP + doorDir) | |
# If requested, add a pressure plate in front of and behind the door (thus | |
# automatically opening it when you approach it!) | |
if (pressurePlate) { | |
setBlock (x + xInc, y, z + zInc, pressurePlateBlock) | |
setBlock (x - xInc, y, z - zInc, pressurePlateBlock) | |
} | |
} | |
# Fences a square area. | |
buildFence <- function (length = 8, fenceBlock = FENCE, | |
digHole = TRUE, putCarpet = TRUE, playerId = NULL, | |
debugFlag = TRUE) | |
{ | |
totalLen <- 4 * length | |
heightArray <- vector (length = totalLen) | |
pos <- getPlayerPos (playerId, tile = TRUE) | |
xStart <- pos[1] - length / 2 | |
yStart <- pos[2] | |
zStart <- pos[3] - length / 2 | |
i <- 1 | |
x <- xStart | |
if (debugFlag) print (paste ("i:", i, | |
"xStart:", xStart, "yStart: ", yStart, | |
"zStart:", zStart, collapse = " ")) | |
# Scope out the heights we'll be building. | |
for (z in zStart:(zStart + length)) { | |
if (debugFlag) print (paste ("1 i:", i, "x:", x, "z:", z, collapse = " ")) | |
heightArray[i] <- getHeight (x, z) | |
setBlock (x, heightArray[i], z, fenceBlock) | |
# Checks if there is a height disparity. | |
if (z != zStart) | |
{ | |
if (heightArray[i] < heightArray[i - 1]) | |
{ | |
setBlocks (x, heightArray[i], z, | |
x, heightArray[i - 1], z, fenceBlock) | |
} else if (heightArray[i] > heightArray[i - 1]) { | |
setBlocks (x, heightArray[i - 1], z - 1, | |
x, heightArray[i], z - 1, fenceBlock) | |
} | |
} | |
i <- i + 1 | |
} | |
for (x in xStart:(xStart + length)) { | |
if (debugFlag) print (paste ("2 i:", i, "x:", x, "z:", z, collapse = " ")) | |
heightArray[i] <- getHeight (x, z) | |
setBlock (x, heightArray[i], z, fenceBlock) | |
if (x != xStart) | |
{ | |
if (heightArray[i] < heightArray[i - 1]) | |
{ | |
setBlocks (x, heightArray[i], z, | |
x, heightArray[i - 1], z, fenceBlock) | |
} else if (heightArray[i] > heightArray[i - 1]) { | |
setBlocks (x - 1, heightArray[i - 1], z, | |
x - 1, heightArray[i], z, fenceBlock) | |
} | |
} | |
i <- i + 1 | |
} | |
for (z in (zStart + length):zStart) { | |
if (debugFlag) print (paste ("3 i:", i, "x:", x, "z:", z, collapse = " ")) | |
heightArray[i] <- getHeight (x, z) | |
setBlock (x, heightArray[i], z, fenceBlock) | |
if (z != zStart + length) | |
{ | |
if (heightArray[i] < heightArray[i - 1]) | |
{ | |
setBlocks (x, heightArray[i], z, | |
x, heightArray[i - 1], z, fenceBlock) | |
} else if (heightArray[i] > heightArray[i - 1]) { | |
setBlocks (x, heightArray[i - 1], z + 1, | |
x, heightArray[i], z + 1, fenceBlock) | |
} | |
} | |
i <- i + 1 | |
} | |
for (x in (xStart + length):xStart) { | |
if (debugFlag) print (paste ("4 i:", i, "x:", x, "z:", z, collapse = " ")) | |
heightArray[i] <- getHeight (x, z) | |
setBlock (x, heightArray[i], z, fenceBlock) | |
if (x != xStart + length) | |
{ | |
if (heightArray[i] < heightArray[i - 1]) | |
{ | |
setBlocks (x, heightArray[i], z, | |
x, heightArray[i - 1], z, fenceBlock) | |
} else if (heightArray[i] > heightArray[i - 1]) { | |
setBlocks (x + 1, heightArray[i - 1], z, | |
x + 1, heightArray[i], z, fenceBlock) | |
} | |
} | |
i <- i + 1 | |
} | |
# return (heightArray) | |
} | |
# r1 is biggest radius, r3 is smallest | |
drawNestedDonuts <- function (r1, r2, r3, blockId1, blockId2, style1 = 0, style2 = 0, | |
playerId = NULL, position = 0, | |
debugFlag = FALSE) | |
{ | |
drawDonut (r1, r2, blockId1, style = style1, playerId = playerId, | |
position = position, debugFlag) | |
drawDonut (r1, r3, blockId2, style = style2, playerId = playerId, position = position, | |
debugFlag) | |
} | |
# R is outer radius, r is inner radius | |
# Code ported by Felix Ling from Python code by Alexander Pruss and under the MIT license | |
drawDonut <- function (R, r, blockId = GLASS, style = 0, position = 0, | |
playerId = NULL, debugFlag = FALSE) | |
{ | |
# Get current player position and heading. | |
Pos <- getPlayerPos (playerId, tile = TRUE) | |
mcx <- Pos[1] | |
mcy <- Pos[2] | |
mcz <- Pos[3] | |
if (debugFlag) print (paste ("mcx:", mcx, "mcyy:", mcy, "mcz:", mcz)) | |
# if (debugFlag) print (paste ("x range:", , "mcyy:", mcy, "mcz:", mcz)) | |
for (x in ((-R - r):(R + r))) { | |
for (y in ((-R - r):(R + r))) { | |
xy_dist <- sqrt (x**2 + y**2) | |
if (xy_dist > 0) { | |
ringx <- x / xy_dist * R # nearest point on major ring | |
ringy <- y / xy_dist * R | |
ring_dist_sq <- (x-ringx)**2 + (y-ringy)**2 | |
for (z in ((-R-r):(R+r))) { | |
# if (debugFlag) print (paste ("x:", x, "y:", y, "z:", z)) | |
if (ring_dist_sq + z**2 <= r**2) | |
{ | |
# if (debugFlag) print (paste ("x:", x, "y:", y, "z:", z)) | |
if (debugFlag) print ("SETTING BLOCK!") | |
if (position == 0) { | |
setBlock (mcx + x, mcy + z, mcz + y, blockId, style) | |
} else if (position == 1) { | |
# Flip y and z | |
setBlock (mcx + x, mcy + y, mcz + z, blockId, style) | |
} else if (position == 2) { | |
# Flip x and z | |
setBlock (mcx + z, mcy + y, mcz + x, blockId, style) | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
# Code by Alexander Pruss and under the MIT license | |
# Error in done[x + r + 1, y + r + 1, z + r + 1] : subscript out of bounds | |
# Is done necessary? program resets it before each one. No, it preserves it | |
# in each loop! Oh no, this won't work! | |
ball <- function (x0, y0, z0, r, block_type, debugFlag = TRUE) | |
{ | |
if (debugFlag) print (paste ("done sz:", (2 * r)^3, "len:", (2 * r))) | |
done <- array (0, c (2 * r + 1, 2 * r + 1, 2 * r + 1)) | |
for (x in (-r:r)) { | |
for (y in (-r:r)) { | |
for (z in (-r:r)) { | |
if (x^2 + y^2 + z^2 <= r^2) { | |
if (debugFlag) print (paste ("checking done at", x + r, y + r, z + r)) | |
# Done is a set, which in Python is unordered with no unique | |
# elements. Might be better to have a parallel 3D array to mark | |
# it there? But what bounds for it? | |
if (done[x + r + 1, y + r + 1, z + r + 1] == 0) { | |
if (debugFlag) print (paste ("setting at", x0 + x, y0 + y, z0 + z)) | |
setBlock (x0+x, y0+y, z0+z, block_type) | |
done[x + r + 1, y + r + 1, z + r + 1] <- 1 | |
} | |
} | |
} | |
} | |
} | |
return (done) | |
} | |
# Shows what kinds of blocks you'll be able to find from a mine. | |
mineBarplot <- function (length = 30, height = 3, playerId = NULL) | |
{ | |
pos <- getPlayerPos (tile = TRUE) | |
# Read the block IDs in a 30x3x30 slice of the mine. | |
# Mine <- getBlocks (-30, -54, -37, 0, -51, -7) | |
mine <- getBlocks (pos[1] - length / 2, pos[2], pos[3] - length / 2, | |
pos[1] + length / 2, pos[2] + height - 1, | |
pos[3] + length / 2) | |
# There were over 1,000 stone blocks. Filter them out! Should also filter out | |
# air, dirt, and torches! | |
# MineNoStone <- Mine[Mine != STONE] | |
mineNoStone <- mine[!(mine %in% c (AIR, STONE, DIRT, TORCH))] | |
# We're mostly interested in the counts, so convert the 3D array to a 2-way | |
# table. | |
countsNoStone = table (mineNoStone) | |
# This plots the counts vs. the block IDs. | |
barplot (countsNoStone, col = "blue") | |
# Manually substitute the names. | |
# dimnames (countsNoStone) <- list (c ("cobblestone", "water", "gravel", "gold ore", | |
# "iron ore", "coal ore", | |
# "lapis lazuli ore", | |
# "diamond ore","redstone ore")) | |
# dimnames (countsNoStone) <- list (c ("cobblestone", "gravel", | |
# "iron ore", "coal ore", | |
# "lapis lazuli ore", | |
# "diamond ore","redstone ore")) | |
# This plots the counts vs. the block names. | |
barplot (countsNoStone, col = "blue", main = paste (length, "block mine")) | |
} | |
# The pos1 and pos2 arguments are position vectors defining the cubois space of | |
# the mountain. This is temporary until the program scans the perimeter of the base. | |
# Need to add plants, leaves, gravel, sand, cobweb, etc. to the checkIds!! | |
NOT_SOLID <- c (AIR, WATER_STATIONARY, WATER_FLOWING, | |
LAVA_STATIONARY, LAVA_FLOWING, SAPLING, MUSHROOM, MUSHROOM_RED, | |
MUSHROOM_BROWN, ICE, SNOW_BLOCK, SNOW, SAND, GRAVEL, WOOD, LEAVES, | |
GRASS_TALL, SUGAR_CANE) | |
hollowMountain <- function (playerId = NULL, pos1, pos2, | |
checkIds = NOT_SOLID, | |
hardLimit = 50000, debugFlag = FALSE) | |
{ | |
# Circle around the perimeter of the base of the mountain. NOTDONE! | |
# Find the rectangle that contains the entire perimeter. NOTDONE! | |
# Loop through every square of the rectangle and get the highest height. NOTDONE! | |
# Get 3D array of blocks. | |
mtnArray <- getBlocks(pos1[1], pos1[2], pos1[3], pos2[1], pos2[2], pos2[3]) | |
arrayDim <- dim (mtnArray) | |
# Create a parallel 3D array to mark which blocks will be carved. | |
carveArray <- array (FALSE, arrayDim) | |
# Loop through all 3 dimensions of the mountain cuboid. See which blocks have | |
# only solid neighbors, and mark those as safe to remove. Starting at 2 and | |
# stopping one block short of the limit to avoid the very edges because | |
# getNeighbors can't handle that right now! | |
limit <- 0 | |
for (i in 2:(arrayDim[1] - 1)) { | |
for (j in 2:(arrayDim[2] - 1)) { | |
for (k in 2:(arrayDim[3] - 1)) { | |
# Get a vector of the 27 blocks at and around this block. | |
chkVec <- getNeighbors(i, j, k, mtnArray, debugFlag) | |
# Make sure the block and none of its neighbors are in the | |
# list of "non-solid" blocks provided. | |
if (!(any (chkVec %in% checkIds))) { | |
carveArray [i, j, k] = TRUE | |
} | |
# Make sure we don't go forever! | |
limit <- limit + 1 | |
if (limit > hardLimit) { | |
print ("HIT LIMIT, EXITING!") | |
return (NULL) | |
} | |
} | |
} | |
} | |
# Loop through all 3 dimensions of the mountain cuboid again and remove the | |
# ones marked in the previous loop. We could not do this during the previous | |
# loop because clearing one block would affect the check for its neighbors! | |
limit <- 0 | |
for (i in 2:(arrayDim[1] - 1)) { | |
for (j in 2:(arrayDim[2] - 1)) { | |
for (k in 2:(arrayDim[3] - 1)) { | |
if (carveArray [i, j, k]) { | |
# See if the block being carved is desired, in which case save it | |
# to place beside the player. | |
# block <- getBlock (pos1[1] + i - 1, | |
# pos1[2] + j - 1, | |
# pos1[3] + k - 1) | |
# if (debugFlag) print (paste ("block:", block, "mtn:", mtnArray[i, j, k])) | |
# Carve out the block. Need to subtract 1 because the first index | |
# of an array in R is 1 instead of 0. | |
setBlock (pos1[1] + i - 1, pos1[2] + j - 1, pos1[3] + k - 1, AIR) | |
} | |
# Make sure we don't go forever! | |
limit <- limit + 1 | |
if (limit > hardLimit) { | |
print ("HIT LIMIT, EXITING!") | |
return (NULL) | |
} | |
} | |
} | |
} | |
return (carveArray) | |
} | |
findPerimeter <- function (playerId = NULL, direction = NULL, hardLimit = 50000) | |
{ | |
# We'll add the perimeter blocks to this as we go around. | |
perimiter <- list () | |
# Find out where the player is looking. | |
if (is.null (direction)) | |
direction <- getPlayerCompassDir () | |
pos <- getPlayerPos (playerId, tile = TRUE) | |
# Move in the direction the player is looking. Up one to be at eye level | |
# (and in case there is carpet or grass). | |
x <- pos[1] + xInc | |
y <- pos[2] + 1 | |
z <- pos[3] + zInc | |
isNotAir <- getBlock (x, y, z, FALSE) | |
# Scan in the direction the player is looking until a non-air block is | |
# found. | |
while (isNotAir == AIR) { | |
x <- x + xInc | |
z <- z + zInc | |
isNotAir <- getBlock (x, y, z, FALSE) | |
} | |
i <- 1 | |
perimiter[[i]] <- c (x, y, z) | |
stopX <- x | |
stopZ <- z | |
# Scan at right angles to the direction the player is looking for | |
# the block's neighbor. | |
repeat { | |
direction <- rotateDoorDir (direction, clockwise = TRUE, amount = 90) | |
incVec <- dirToCoordInc (direction) | |
testX <- x + incVec[1] | |
testZ <- z + incVec[3] | |
isNotAir <- getBlock (testX, y, testZ, FALSE) | |
if (isNotAir == AIR) | |
{ | |
# Hmm, may need to rethink this. May need block/air pairs? Handle case where | |
# a little tip sticks out. Or maybe just allow a block to be listed twice | |
} | |
if ((x == stopX) && (z == stopZ)) | |
break | |
# Sanity check to make sure we don't run forever. | |
i <- i + 1 | |
if (i > hardLimit) { | |
print ("Error, hard limit has been reached. Stopping!") | |
return (NULL) | |
} | |
} | |
} | |
compassToDoorDir <- function (direction) | |
{ | |
if (direction == EAST) { | |
compassDir <- DOOR_EAST | |
} else if (direction == SOUTH) { | |
compassDir <- DOOR_SOUTH | |
} else if (direction == WEST) { | |
compassDir <- DOOR_WEST | |
} else if (direction == NORTH) { | |
compassDir <- DOOR_NORTH | |
} else { | |
print ("Direction must be an integer between 0 and 3. Use the DOOR_<dir> constants.") | |
return (NULL) | |
} | |
return (compassDir) | |
} | |
doorToCompassDir <- function (direction) | |
{ | |
if (direction == DOOR_EAST) { | |
doorDir <- EAST | |
} else if (direction == DOOR_SOUTH) { | |
doorDir <- SOUTH | |
} else if (direction == DOOR_WEST) { | |
doorDir <- WEST | |
} else if (direction == DOOR_NORTH) { | |
doorDir <- NORTH | |
} else { | |
print ("Direction must be an integer between 0 and 3. Use the NORTH, SOUTH, etc. constants.") | |
return (NULL) | |
} # else return error | |
return (doorDir) | |
} | |
MAX_COMPASS <- 4 | |
rotateDoorDir <- function (direction, clockwise = TRUE, | |
amount = 90) | |
{ | |
# DOOR_EAST = 0x0 | |
# DOOR_SOUTH = 0x1 | |
# DOOR_WEST = 0x2 | |
# DOOR_NORTH = 0x3 | |
# EAST <- 0 | |
# WEST <- 1 | |
# SOUTH <- 2 | |
# NORTH <- 3 | |
if (clockwise) { | |
increment <- 1 | |
} else { | |
increment <- -1 | |
} | |
if (amount == 90) { | |
magnitude <- 1 | |
} else if (amount == 180) { | |
magnitude <- 2 | |
} else if (amount == 45) { | |
magnitude <- 0.5 | |
} else { | |
print ("Amount must be 45, 90, or 180.") | |
return (NULL) | |
} | |
increment <- increment * magnitude | |
direction <- (direction + increment) %% MAX_COMPASS | |
return (direction) | |
} | |
dirToCoordInc <- function (doorDir) | |
{ | |
zInc <- 0 | |
xInc <- 0 | |
if (doorDir == DOOR_NORTH) { | |
zInc <- -1 | |
} else if (doorDir == DOOR_SOUTH) { | |
zInc <- 1 | |
} else if (doorDir == DOOR_EAST) { | |
xInc <- 1 | |
} else if (doorDir == DOOR_WEST) { | |
xInc <- -1 | |
} else { | |
print ("Direction must be an integer between 0 and 3. Use the DOOR_<dir> constants.") | |
return (NULL) | |
} | |
incVec <- c (xInc, 0, zInc) | |
return (incVec) | |
} | |
# This must not be called at the very edge of the array! x - 1 to x + 1 must be | |
# valid references, and same goes for y and z. | |
getNeighbors <- function (x, y, z, srcArray, debugFlag = FALSE) | |
{ | |
rtnVec <- vector (length = 3^3) | |
rtnVecIdx <- 1 | |
if (debugFlag) print (paste ("rtnVec:", rtnVec)) | |
# Add a check that we don't exceed the boundaries of the srcArray! NOTDONE! | |
# Read the block IDs of the location and its neighbors (including diagonal | |
# ones) and place the IDs into the rtnVec. | |
for (i in (x - 1):(x + 1)) { | |
for (j in (y - 1):(y + 1)) { | |
for (k in (z - 1):(z + 1)) { | |
if (debugFlag) print (paste ("i, j, k:", i, j, k)) | |
if (debugFlag) print (paste ("src:", srcArray[i, j, k])) | |
rtnVec[rtnVecIdx] <- srcArray[i, j, k] | |
rtnVecIdx <- rtnVecIdx + 1 | |
# if ((i == x) && (j == y) && (k == z)) | |
# next | |
} | |
} | |
} | |
return (rtnVec) | |
} | |
DEFAULT_FILENAME <- "data/minecraft/mc-skyIsland.csv" | |
MAZE_FILE <- "data/minecraft/3dmaze3.csv" | |
buildCsv <- function (fileName, playerId = NULL, height = 0, debugFlag = TRUE) | |
{ | |
pos <- getPlayerPos (playerId, tile = TRUE) | |
playerPosX <- pos[1] | |
playerPosY <- pos[2] + height | |
playerPosZ <- pos[3] | |
data = read.csv (fileName, header = FALSE) | |
sizeX <- data[1, 1] | |
sizeY <- data[1, 2] | |
sizeZ <- data[1, 3] | |
if (debugFlag) print (paste (sizeX, sizeY, sizeZ)) | |
# The start of the array is the 3rd row, after the dimensions and a blank | |
# row. | |
offsetRow <- 2 | |
for (y in 1:sizeY) { | |
if (debugFlag) print (paste ("y:", y)) | |
for (x in 1:sizeX) { | |
if (debugFlag) print (paste ("x:", x)) | |
for (z in 1:sizeZ) { | |
if (debugFlag) print (paste ("z:", z, | |
"Data Index: ", x + offsetRow, z)) | |
# Read the block ID. The decimal portion is the style ID. | |
blockIdStyle <- data [x + offsetRow, z] | |
blockStyle <- round ((blockIdStyle %% 1) * 100) | |
blockId <- trunc (blockIdStyle) | |
if (debugFlag) print (sprintf ("blockIdStyle: %f, blockId: %d, blockStyle: %d", | |
blockIdStyle, blockId, blockStyle)) | |
# Should never see NAs, but it indicates the format was not what we | |
# expected. | |
if (is.na (blockId)) { | |
print ("Bad block ID!") | |
return (NULL) | |
} | |
setBlock (playerPosX + x, playerPosY + y, playerPosZ + z, | |
blockId, blockStyle) | |
} | |
} | |
# After each y layer, x will reset to 1, and there's also a blank row. | |
offsetRow <- offsetRow + sizeX + 1 | |
} | |
} | |
# buildRainbowWool <- function (blocks = 8, playerId = NULL) | |
# { | |
# created <- 0 | |
# if (blocks <= 0) { | |
# blocks <- 8 | |
# } | |
# pos <- getPlayerPos (playerId, tile = TRUE) | |
# | |
# | |
# while (created < blocks) { | |
# sample (pos[1]+5:pos[1]-5, 1) | |
# | |
# blocks <- blocks + 1 | |
# } | |
# | |
# } | |
# | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment