├── deps.edn ├── .gitignore ├── server ├── deps.edn └── server.properties ├── bin ├── install-client ├── start-client └── start-server ├── README.md └── repl_sessions ├── s01_warmup.clj ├── s04_palettes_and_textures.clj ├── s03_drawing_shapes.clj └── s02_locations_blocks_entities.clj /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.11.1"} 2 | com.lambdaisland/witchcraft {:mvn/version "0.36.317"}} 3 | 4 | :aliases 5 | {:launcher-api 6 | {:extra-deps {sk.tomsik68/mclauncher-api {:mvn/version "0.3.7"} 7 | progrock/progrock {:mvn/version "0.1.2"}}}} 8 | 9 | :mvn/repos 10 | {"sk_tomsik68" {:url "https://raw.githubusercontent.com/tomsik68/maven-repo/master/"}}} 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | banned-ips.json 2 | banned-players.json 3 | bukkit.yml 4 | cache 5 | commands.yml 6 | eula.txt 7 | help.yml 8 | libraries 9 | logs 10 | ops.json 11 | paper-1.18.2.jar 12 | paper.yml 13 | permissions.yml 14 | plugins 15 | spigot.yml 16 | usercache.json 17 | version_history.json 18 | versions 19 | whitelist.json 20 | world 21 | world_nether 22 | world_the_end 23 | client 24 | .cpcache 25 | .clj-kondo/ 26 | .lsp/ 27 | server/.nrepl-port 28 | -------------------------------------------------------------------------------- /server/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["code"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.0"} 3 | com.lambdaisland/witchcraft {:mvn/version "RELEASE"} 4 | refactor-nrepl/refactor-nrepl {:mvn/version "2.5.1"} 5 | clojure2d/clojure2d {:mvn/version "1.4.4"} 6 | cider/cider-nrepl {:mvn/version "RELEASE"}} 7 | 8 | :mvn/repos 9 | {"sk_tomsik68" {:url "https://raw.githubusercontent.com/tomsik68/maven-repo/master/"}}} 10 | -------------------------------------------------------------------------------- /bin/install-client: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BIN_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) 4 | 5 | if [ -f "$BIN_DIR/../client/versions/1.18.2/1.18.2.jar" ]; then 6 | printf "\033[32m[install-client]\033[0m Client already installed, exiting.\n" 7 | exit 1 8 | fi 9 | 10 | cd "$BIN_DIR/.." 11 | exec clojure -A:launcher-api -M -e '(do 12 | (require (quote [lambdaisland.witchcraft.launcher-api :as l])) 13 | (l/update-minecraft (l/launcher-backend "client") "1.18.2")) 14 | ' 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clojure + Minecraft Workshop, ClojureD 2022 2 | 3 | __Hinweis:__ Dieses Repo ist ein Fork von 4 | [lambdaisland/witchcraft-workshop](https://github.com/lambdaisland/witchcraft-workshop). 5 | Ich habe [einige 6 | Anpassungen](https://github.com/henrik42/witchcraft-workshop/activity?ref=main) 7 | gemacht, damit wir das Repo für eine 8 | [Programmier-AG](https://github.com/henrik42/fpic) unter Windows verwenden 9 | konnten. Vielen Dank an die Autoren des Original-Repos! 10 | 11 | Die Erläuterungen des Original-Repos habe ich hier entfernt bzw. nicht 12 | übersetzt. [Hier](https://github.com/henrik42/fpic/blob/main/minecraft.md) 13 | findest du Erläuterungen zur Installation und Verwendung auf Deutsch. 14 | 15 | --- 16 | 17 | # Fliegende Kühe 18 | 19 | Mit diesem Repo kannst du dir lokal einen Minecraft-Server und -Client aufsetzen 20 | und dich anschließend mit dem Server verbinden und das Spiel durch ein 21 | [Clojure](https://clojure.org/)-Programm steuern. Die kannst [Kühe 22 | fliegen](https://github.com/henrik42/fpic/blob/main/minecraft-3.clj) lassen oder 23 | [Mauern aus Gold 24 | bauen](https://github.com/henrik42/fpic/blob/main/minecraft-4.clj). 25 | 26 | Falls du Clojure lernen möchtest, hilft dir vielleicht [dieser 27 | Text](https://github.com/henrik42/fpic/blob/main/inhalte.md). 28 | 29 | Viel Spaß dabei! -------------------------------------------------------------------------------- /server/server.properties: -------------------------------------------------------------------------------- 1 | #Minecraft server properties 2 | #Sat Apr 27 10:52:26 CEST 2024 3 | enable-jmx-monitoring=false 4 | rcon.port=25575 5 | level-seed=34 6 | gamemode=survival 7 | enable-command-block=false 8 | enable-query=false 9 | generator-settings={} 10 | level-name=world 11 | motd=ClojureD Workshop 12 | query.port=25565 13 | pvp=true 14 | generate-structures=true 15 | difficulty=peaceful 16 | network-compression-threshold=256 17 | require-resource-pack=false 18 | max-tick-time=60000 19 | use-native-transport=true 20 | max-players=4 21 | online-mode=false 22 | enable-status=true 23 | allow-flight=true 24 | broadcast-rcon-to-ops=true 25 | view-distance=10 26 | server-ip= 27 | resource-pack-prompt= 28 | allow-nether=true 29 | server-port=25565 30 | enable-rcon=false 31 | sync-chunk-writes=true 32 | op-permission-level=4 33 | prevent-proxy-connections=false 34 | hide-online-players=false 35 | resource-pack= 36 | entity-broadcast-range-percentage=100 37 | simulation-distance=10 38 | rcon.password= 39 | player-idle-timeout=0 40 | debug=false 41 | force-gamemode=false 42 | rate-limit=0 43 | hardcore=false 44 | white-list=false 45 | broadcast-console-to-ops=true 46 | spawn-npcs=true 47 | spawn-animals=true 48 | function-permission-level=2 49 | level-type=default 50 | text-filtering-config= 51 | spawn-monsters=true 52 | enforce-whitelist=false 53 | resource-pack-sha1= 54 | spawn-protection=16 55 | max-world-size=29999984 56 | -------------------------------------------------------------------------------- /bin/start-client: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## 4 | ## Dieses Skript macht folgendes: 5 | ## 6 | ## * Download des MC Clients 7 | ## * Start des MC Clients 8 | ## 9 | ## Du wirst wahrscheinlich schon einen Minecraft-Launcher bei dir installiert haben. Falls 10 | ## das der Fall ist, kannst du darüber einfach Minecraft-Java-Edition 1.18.2 starten. 11 | ## Falls du keinen Launcher hast, kannst du dieses Skript nutzen, um die "Minecraft GUI" zu starten. 12 | ## 13 | ## Dieses ist ein Bash-Skript https://de.wikipedia.org/wiki/Bash_(Shell). 14 | ## Du benötigst eine Bash, um dieses Skript auszuführen. Du kannst das Skript 15 | ## NICHT direkt aus einer Windows-CMD-Shell oder Windows-Powershell heraus aufrufen. Wenn du unter Windows 16 | ## arbeitest, kannst du dir z.B. "Git for Windows Portable" (https://git-scm.com/download/win) installieren. 17 | ## Darin ist eine Bash enthalten, die du unter Windows starten kannst und aus dieser Bash heraus kannst 18 | ## du dann dieses Skript aufrufen. 19 | ## 20 | ## Du kannst dieses Skript z.B. wie folgt aufrufen. Zuvor musst du aber in der Bash mit "cd" in das 21 | ## Minecraft-Workshop-Wurzel-Verzeichnis wechseln. Den Namen hugo sollest ersetzen. 22 | ## 23 | ## JAVA_CMD=/c/java/jdk-17.0.8+7/bin/java ./bin/start-client hugo 24 | ## 25 | ## Falls der Download aus irgendwelchen Gründen abbricht und damit nicht vollständig erfolgt ist, musst du die 26 | ## Dateien unter client/ löschen und das Skript erneut aufrufen. 27 | ## 28 | 29 | ## Mit Java 17 getestet. 30 | : ${JAVA_CMD:? Bitte JAVA_CMD setzen.} 31 | 32 | MC_VERSION="1.18.2" 33 | BIN_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) 34 | cd "$BIN_DIR/.." 35 | 36 | if [ -z "$1" ]; then 37 | echo "Usage: $0 " 38 | exit 1 39 | fi 40 | 41 | if [ ! -f "$BIN_DIR/../client/versions/$MC_VERSION/$MC_VERSION.jar" ]; then 42 | printf "\033[32m[start-client]\033[0m Client ist noch nicht installiert. Installation gestartet ...\n" 43 | clojure -A:launcher-api -M -e '(do 44 | (require (quote [lambdaisland.witchcraft.launcher-api :as l])) 45 | (l/update-minecraft (l/launcher-backend "client") "'"$MC_VERSION"'")) 46 | ' 47 | fi 48 | 49 | printf "\033[32m[start-client]\033[0m GUI wird gestartet. Das dauert einen Augenblick ....\n" 50 | 51 | clojure -A:launcher-api -M -e ' 52 | (do 53 | (require (quote [lambdaisland.witchcraft.launcher-api :as l]) 54 | (quote [clojure.string :as str])) 55 | 56 | (defn launch-cmd-str [backend session version] 57 | (let [args (l/launch-cmd backend session version)] 58 | (str/join " " (cons (first args) 59 | (map (comp 60 | #(if (str/includes? %1 "\\\\") 61 | (str "'\''" %1 "'\''") 62 | %1) 63 | l/shellquote) (next args)))))) 64 | 65 | (println (launch-cmd-str 66 | (l/launcher-backend "client") 67 | (l/session {:username "'"$1"'" :session-id "x" :uuid (str (random-uuid))}) 68 | "'"$MC_VERSION"'"))) 69 | ' | sed "s^java^${JAVA_CMD}^" | sh 70 | -------------------------------------------------------------------------------- /repl_sessions/s01_warmup.clj: -------------------------------------------------------------------------------- 1 | (ns s01-warmup 2 | "Some fun things to help you get a taste of Witchcraft." 3 | (:require [lambdaisland.witchcraft :as wc] 4 | [lambdaisland.witchcraft.markup :as markup])) 5 | 6 | (wc/set-time 1000) 7 | (wc/set-game-rule :do-daylight-cycle false) 8 | 9 | ;; Welcome to the Witchcraft workshop! Hopefully you managed to get your server 10 | ;; running, and managed to connect to it, both from the game (bin/start-client, 11 | ;; then connect to localhost:25565), and with your editor where you are reading 12 | ;; this, using nREPL (connect to localhost:25555). 13 | 14 | ;; If so then go ahead and start going through this file, this is an introduction 15 | ;; you can go through by yourself while we go around and make sure everyone has a 16 | ;; working setup. Once everyone is up and running we'll move on to part two. 17 | 18 | ;; If all went well you are standing on some grass with some birch and oak trees 19 | ;; nearby. Have a look around! You can move around with the mouse + w/a/s/d 20 | ;; keys. Try breaking a block by holding the left mouse button. Good job, you're 21 | ;; playing minecraft. 22 | 23 | ;; We want to manipulate this world you find yourself in, through a Clojure REPL. 24 | ;; But it's a big world, and we like to be able to see the changes we make, so 25 | ;; let's figure out where we are in the world first. 26 | 27 | ;; If there's only one player online then this will get you that player, or pass 28 | ;; it a player name to be explicit 29 | 30 | (def me (wc/player #_"sunnyplexus")) 31 | 32 | (wc/send-title me (markup/fAnCy "Hello from Clojure!" [:dark-blue :dark-green])) 33 | 34 | ;; `loc` will give you the location of something as a Clojure mapcat 35 | 36 | (wc/loc me) 37 | 38 | (wc/send-title me (pr-str (wc/locv me))) 39 | 40 | ;; (You can also try `locv`, `block`, `get-block`, `target-block`, `x`, `y`, 41 | ;; `z`, `inventory`) 42 | 43 | ;; Let's save our current location, so we have a point of reference 44 | (defonce anchor (wc/loc me)) 45 | (prn anchor) 46 | 47 | ;; If it starts getting night and you can't see what you're doing, then time 48 | ;; travel back to the morning. 49 | (wc/set-time 0) 50 | 51 | ;; Let's start with a splash, this puts a water block 5 blocks above your head 52 | (wc/set-blocks 53 | [[0 5 0 :water]] 54 | {:anchor anchor}) 55 | 56 | ;; And take it away again 57 | (wc/undo!) 58 | 59 | ;; Let's put a beautiful blue and purple ball in the sky 60 | (wc/set-blocks 61 | (for [x (range -6 7) 62 | y (range -6 7) 63 | z (range -6 7) 64 | :when (< (wc/distance [x y z] [0 0 0]) 5.3)] 65 | [x y z (rand-nth [:lapis-block :amethyst-block])]) 66 | {:anchor (wc/add anchor [0 12 0])}) 67 | 68 | ;; Want to have a better look from above? Either allow yourself to fly, as you 69 | ;; would in creative. 70 | 71 | (wc/fly! me) 72 | 73 | ;; Now pressing SPACE will move you upwards, and SHIFT will get you down. Once 74 | ;; you've landed press SPACE twice to fly again. 75 | 76 | ;; Let's try some other things, let's spawn some chickens! 77 | 78 | (def chickens 79 | (doall 80 | (repeatedly 10 #(wc/spawn (wc/add anchor [0 4 0]) :chicken)))) 81 | 82 | ;; And blow them up! 83 | 84 | (doseq [chicken chickens] 85 | (wc/despawn chicken) 86 | ;; Witchcraft will convert `chicken` to a location 87 | (wc/create-explosion chicken 5.0) 88 | (Thread/sleep 500)) 89 | 90 | ;; Let's give ourselves some gear: a pickaxe with silk touch, some protection, 91 | ;; and some food to munch on. 92 | 93 | (wc/into-inventory 94 | me 95 | [{:material :diamond-pickaxe 96 | :enchants {:silk-touch 1} 97 | :lore [[:<> 98 | [:gold "A "] 99 | [:white "gossamer "] 100 | [:gold "pick"]]]} 101 | 102 | {:material :netherite-chestplate 103 | :enchants {:protection 5 104 | :blast-protection 5}} 105 | 106 | {:material :netherite-boots 107 | :enchants {:feather-falling 5}} 108 | 109 | [:golden-carrot 64]]) 110 | 111 | ;; You can jump around the server a bit to see different landscapes and biomes. 112 | ;; Note that this can put a lot of strain on your CPU (and battery), since it 113 | ;; needs to generate all that new landscape. 114 | (wc/teleport me [(rand-int 10000) 100 (rand-int 10000)]) 115 | -------------------------------------------------------------------------------- /bin/start-server: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | ## 4 | ## Dieses Skript macht folgendes: 5 | ## 6 | ## * Download des PaperMC Servers von MC_DOWNLOAD_URL nach server/paper-1.18.2.jar 7 | ## * Download des Witchcraft-Plugins nach server/plugins/witchcraft-plugin-0.7.37-for-paper-1.18-shaded.jar 8 | ## * Start des Servers 9 | ## 10 | ## Dieses ist ein Bash-Skript https://de.wikipedia.org/wiki/Bash_(Shell). 11 | ## Du benötigst eine Bash, um dieses Skript auszuführen. Du kannst das Skript 12 | ## NICHT direkt aus einer Windows-CMD-Shell oder Windows-Powershell heraus aufrufen. Wenn du unter Windows 13 | ## arbeitest, kannst du dir z.B. "Git for Windows Portable" (https://git-scm.com/download/win) installieren. 14 | ## Darin ist eine Bash enthalten, die du unter Windows starten kannst und aus dieser Bash heraus kannst 15 | ## du dann dieses Skript aufrufen. 16 | ## 17 | ## Du kannst dieses Skript z.B. wie folgt aufrufen. Zuvor musst du aber in der Bash mit "cd" in das 18 | ## Minecraft-Workshop-Wurzel-Verzeichnis wechseln. 19 | ## 20 | ## JAVA_CMD=/c/java/jdk-17.0.8+7/bin/java ./bin/start-server 21 | ## 22 | ## Falls der Download aus irgendwelchen Gründen abbricht und damit nicht vollständig erfolgt ist, musst du die 23 | ## Dateien server/paper-1.18.2.jar und server/plugins/witchcraft-plugin-0.7.37-for-paper-1.18-shaded.jar löschen 24 | ## und das Skript erneut aufrufen. 25 | ## 26 | 27 | ## Mit Java 17 getestet. 28 | : ${JAVA_CMD:? Bitte JAVA_CMD setzen.} 29 | 30 | MC_DOWNLOAD_URL=${MC_DOWNLOAD_URL:-https://api.papermc.io/v2/projects/paper/versions/1.18.2/builds/388/downloads/paper-1.18.2-388.jar} 31 | 32 | MC_VERSION="1.18.2" 33 | WITCHRAFT_PLUGIN_VERSION="0.7.37" 34 | 35 | # Wir nutzen als Default 1G, weil wir auf den PCs der Schule während der Programmier-AG Probleme mit 4G hatten. 36 | # Im Original wurde 4G verwendet. 37 | MEMORY="${MEMORY:-1G}" 38 | 39 | ############################################################################### 40 | 41 | MC_SERVER_JAR="paper-${MC_VERSION}.jar" 42 | WITCHRAFT_PLUGIN_JAR="witchcraft-plugin-${WITCHRAFT_PLUGIN_VERSION}-for-paper-1.18-shaded.jar" 43 | 44 | BIN_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) 45 | cd "$BIN_DIR/../server" 46 | 47 | trace() { 48 | echo "+" "$@" 49 | "$@" 50 | } 51 | 52 | exec_trace() { 53 | echo "+" "$@" 54 | exec "$@" 55 | } 56 | 57 | if [ ! -f "eula.txt" ]; then 58 | printf "\033[32m[start-server]\033[0m eula.txt not found. Do you agree to the Minecraft End User License Agreement (https://account.mojang.com/documents/minecraft_eula)? [y/N] " 59 | read -r res 60 | if [ "$res" = "$(printf 'y\n')" ] || [ "$res" = "$(printf 'Y\n')" ] ; then 61 | trace echo 'eula=true' > eula.txt 62 | else 63 | printf "\033[33m[start-server]\033[0m Minecraft won't run without an eula.txt, aborting." 64 | exit 1 65 | fi 66 | fi 67 | 68 | if [ ! -f "$MC_SERVER_JAR" ]; then 69 | printf "\033[32m[start-server]\033[0m %s not found, downloading.\n" "${MC_SERVER_JAR}" 70 | trace curl ${CURL_OPTS} --progress-bar -L ${MC_DOWNLOAD_URL} -o $MC_SERVER_JAR 71 | fi 72 | 73 | if [ ! -f "plugins/$WITCHRAFT_PLUGIN_JAR" ]; then 74 | printf "\033[32m[start-server]\033[0m %s not found, downloading.\n" "plugins/${WITCHRAFT_PLUGIN_JAR}" 75 | mkdir -p plugins 76 | trace curl ${CURL_OPTS} --progress-bar -L "https://github.com/lambdaisland/witchcraft-plugin/releases/download/v0.7.35/${WITCHRAFT_PLUGIN_JAR}" -o "plugins/$WITCHRAFT_PLUGIN_JAR" 77 | fi 78 | 79 | # https://blog.airplane.gg/aikar-flags/ 80 | # https://aikar.co/mcflags.html 81 | exec_trace "$JAVA_CMD" \ 82 | -Xms"$MEMORY" \ 83 | -Xmx"$MEMORY" \ 84 | -XX:+UseG1GC \ 85 | -XX:+ParallelRefProcEnabled \ 86 | -XX:MaxGCPauseMillis=200 \ 87 | -XX:+UnlockExperimentalVMOptions \ 88 | -XX:+DisableExplicitGC \ 89 | -XX:+AlwaysPreTouch \ 90 | -XX:G1HeapWastePercent=5 \ 91 | -XX:G1MixedGCCountTarget=4 \ 92 | -XX:G1MixedGCLiveThresholdPercent=90 \ 93 | -XX:G1RSetUpdatingPauseTimePercent=5 \ 94 | -XX:SurvivorRatio=32 \ 95 | -XX:+PerfDisableSharedMem \ 96 | -XX:MaxTenuringThreshold=1 \ 97 | -XX:G1NewSizePercent=30 \ 98 | -XX:G1MaxNewSizePercent=40 \ 99 | -XX:G1HeapRegionSize=8M \ 100 | -XX:G1ReservePercent=20 \ 101 | -XX:InitiatingHeapOccupancyPercent=15 \ 102 | -jar "$MC_SERVER_JAR" "$@" 103 | -------------------------------------------------------------------------------- /repl_sessions/s04_palettes_and_textures.clj: -------------------------------------------------------------------------------- 1 | (ns s04-palettes-textures 2 | (:require [lambdaisland.witchcraft :as wc] 3 | [lambdaisland.witchcraft.cursor :as c] 4 | [lambdaisland.witchcraft.matrix :as m] 5 | [lambdaisland.witchcraft.palette :as palette] 6 | [lambdaisland.witchcraft.shapes :as shapes])) 7 | 8 | (def me (wc/player)) 9 | 10 | ;; So far we've looked at the shape of things with the shapes API, but in order 11 | ;; to put something into the world you need to decide which kinds of blocks to 12 | ;; use. Minecraft creators talk a lot about "palettes" and "gradients". 13 | ;; 14 | ;; If you're not a seasoned minecrafter it can be hard to know which options 15 | ;; there are, how do you find out about blocks that go well with other blocks? 16 | ;; For these things the palette namespace comes to the resque. 17 | ;; 18 | ;; I've used a color picker to pick an RGB color (google "color picker"), this 19 | ;; is kind of a warm salmon color. 20 | 21 | (palette/nearest-material [235 64 52]) 22 | ;;=> :red-wool 23 | 24 | ;; Turns out the closest equivalent in the game is the red wool block. But 25 | ;; building an entire structure out of red wool is kind of boring. What you want 26 | ;; to do is "texturize" it, replace some of them with other blocks that add some 27 | ;; variation. 28 | 29 | ;; Let's see what has a similar color. The neighbors function returns a sequence 30 | ;; of all materials in the game, sorted by the color distance from the given 31 | ;; block. 32 | 33 | (def anchor (wc/add (wc/target-block me) [0 3 0])) 34 | 35 | ;; Let's take the top matches, and lay them out in a 5x5 square so we can have a 36 | ;; better look. 37 | 38 | (let [materials (->> :red-wool 39 | palette/neighbors 40 | (map first) 41 | (take 24) 42 | (cons :red-wool) 43 | (partition 5))] 44 | (wc/set-blocks 45 | (for [x (range 5) 46 | y (range 5) 47 | z [0]] 48 | [x y z (nth (nth materials x) y)]) 49 | {:anchor anchor})) 50 | 51 | ;; You can see some of the shortcomings here, for instance the bookcase is in 52 | ;; there because red is the most common color in that texture, but it's clearly 53 | ;; not a red block. The empty spot is for a lightning rod, which is a material 54 | ;; but not a full block. Still, we can see some good candidates to combine. You 55 | ;; can use F3 and point at a block if you're not sure what the material is 56 | ;; called. 57 | 58 | ;; I'm going to pick out a few and see if we can come up with a nice use for 59 | ;; them: 60 | 61 | :red-wool 62 | :red-concrete 63 | :nether-wart-block 64 | :red-mushroom-block 65 | 66 | ;; And I'll keep this one aside as a sort of "accent" block 67 | 68 | :red-glazed-terracotta 69 | 70 | ;; rand-palette takes a map from material to probability, and returns a 71 | ;; material. You can evaluate this a few times to see for yourself. We'll start 72 | ;; with having all blocks be of equal weight. Let's put up a wall with this. 73 | 74 | (palette/rand-palette 75 | {:red-wool 1 76 | :red-concrete 1 77 | :nether-wart-block 1 78 | :red-mushroom-block 1}) 79 | 80 | (wc/set-blocks 81 | (for [x (range 10) 82 | y (range 10) 83 | z [0]] 84 | [x y z 85 | (palette/rand-palette 86 | {:red-wool 1 87 | :red-concrete 1 88 | :nether-wart-block 1 89 | :red-mushroom-block 1})]) 90 | {:anchor anchor}) 91 | 92 | ;; Not sure if it looks like anything... but it's cool. 93 | 94 | ;; After some experimentation I settled on a pattern of mostly nether-wart, 95 | ;; accentuated with red wool and concrete. I decided the mushroom blocks were a 96 | ;; bit too particular. 97 | 98 | (wc/set-blocks 99 | (for [x (range 10) 100 | y (range 10) 101 | z [0]] 102 | [x y z 103 | (palette/rand-palette 104 | {:red-wool 2 105 | :red-concrete 1 106 | :nether-wart-block 20 107 | :red-mushroom-block 0})]) 108 | {:anchor anchor}) 109 | 110 | ;; All of the `shapes` take a `:material` option, this can be a simple 111 | ;; material (as a keyword), or a function which takes the current position, and 112 | ;; returns a material. 113 | 114 | ;; Here you notice that a palette like this can give a very different impression 115 | ;; at a distance than up-close. Try adding in the mushroom blocks again, to get 116 | ;; a more "mottled" appearance. 117 | 118 | (def red-palette (fn [& _] 119 | (palette/rand-palette 120 | {:red-wool 2 121 | :red-concrete 1 122 | :nether-wart-block 20 123 | :red-mushroom-block 0}))) 124 | 125 | (wc/set-blocks 126 | (shapes/torus {:radius 60 127 | :tube-radius 12 128 | :material red-palette 129 | :start [0 50 0]}) 130 | {:anchor anchor}) 131 | 132 | ;; You can also play with the fact that the material function receives the x/y/z of 133 | ;; the block: 134 | 135 | (wc/set-blocks 136 | (shapes/torus {:radius 60 137 | :tube-radius 12 138 | :material (fn [[x y z]] 139 | (cond 140 | (< 65 y 73) 141 | :orange-stained-glass 142 | (<= 73 y) 143 | :air 144 | :else 145 | (palette/rand-palette 146 | {:red-wool (Math/abs x) 147 | :orange-concrete (Math/abs z)}))) 148 | :start [0 74 0]}) 149 | {:anchor anchor}) 150 | 151 | ;; The palette namespace also contains functions to help you construct "gradients". 152 | ;; Let's see what that looks like. 153 | 154 | ;; With this it tries to interpolate the block colors, finding intermediate blocks 155 | ;; that complete the sequence: 156 | 157 | (let [materials (palette/material-gradient :pink-terracotta :red-sandstone 8)] 158 | (wc/set-blocks 159 | (for [x (range 8) 160 | y [0] 161 | z [0]] 162 | [x y z (nth materials x)]) 163 | {:anchor anchor})) 164 | 165 | ;; I'm not entirely convinced that that red terracotta should be in there, but the 166 | ;; rest looks quite nice. Our gradient then becomes: 167 | 168 | :pink-terracotta 169 | :stripped-acacia-log 170 | :acacia-planks 171 | :cut-red-sandstone 172 | :red-sandstone 173 | 174 | ;; To actually make it work like a gradient you want to randomize the transitions a 175 | ;; bit. Otherwise you get this very sudden change: 176 | 177 | (let [gradient (palette/gradient-gen {:palette 178 | [:pink-terracotta 179 | :pink-terracotta 180 | :stripped-acacia-log 181 | :acacia-planks 182 | :cut-red-sandstone 183 | :red-sandstone] 184 | ;; alternative pallette that's a bit more 185 | ;; pronounced. 186 | #_ 187 | [:pink-terracotta 188 | :pink-terracotta 189 | :brown-mushroom-block 190 | :lime-terracotta 191 | :slime-block 192 | :emerald-block] 193 | :spread 16 194 | :bleed-distance 4 195 | :bleed 0.7})] 196 | (wc/set-blocks 197 | (shapes/tube {:length 80 198 | :direction [0 1 0] 199 | :radius 10 200 | :inner-radius 8.5 201 | :material (fn [[x y z]] 202 | (gradient y))}) 203 | {:anchor anchor})) 204 | 205 | (wc/undo!) 206 | (wc/clear-weather) 207 | -------------------------------------------------------------------------------- /repl_sessions/s03_drawing_shapes.clj: -------------------------------------------------------------------------------- 1 | (ns s03-drawing-shapes 2 | "In this section we'll create a shape out of smaller shapes, and show how you 3 | can start building cool things through code." 4 | (:require [lambdaisland.witchcraft :as wc] 5 | [lambdaisland.witchcraft.shapes :as shapes] 6 | [lambdaisland.witchcraft.matrix :as m] 7 | [lambdaisland.witchcraft.gallery.big-chicken 8 | :as chicken 9 | :refer [chicken-shape]])) 10 | 11 | (def me (wc/player)) 12 | 13 | ;; Let's use the shapes API to create some structures. Start by finding a place 14 | ;; where you have a good vantage point. If you're on the world seed we're 15 | ;; using (34), then this is a good spot. If you're using a different spot then 16 | ;; save it here so you can always jump back. 17 | 18 | (.getSeed (wc/world "world")) ;;=> 34 19 | 20 | ;; (wc/loc me) 21 | (wc/teleport 22 | me 23 | {:x 2580.45 24 | :y 119.0 25 | :z -120.69 26 | :pitch 15.45 27 | :yaw -20.98 28 | :world "world"}) 29 | 30 | ;; Now fly into the valley and pick a block that's sort of in the middle of your 31 | ;; view, we'll build our things relative to this point, so this will be our 32 | ;; anchor. 33 | 34 | ;; (wc/target-block me) 35 | (def anchor {:x 2603 :y 95 :z -59}) 36 | 37 | ;; Let's change that to a nice recognizable color 38 | (wc/set-block anchor :pink-wool) 39 | 40 | ;; We can go one step further and put two more points to help us recognize which 41 | ;; direction is which. This will put a blue block towards positive X, and a red 42 | ;; block towards positive z. 43 | 44 | (wc/set-blocks 45 | [[0 1 0 :pink-wool] 46 | [7 1 0 :blue-wool] 47 | [0 1 7 :red-wool]] 48 | {:anchor anchor}) 49 | 50 | 51 | ;; And try to build something there, this chicken is part of the witchcraft 52 | ;; gallery, a number of example namespaces that you can explore. 53 | 54 | (wc/set-blocks 55 | (chicken-shape (wc/add anchor {:y 5}))) 56 | 57 | ;; You can have a look at what `chicken-shape` returns 58 | (chicken-shape (wc/add anchor {:y 5})) 59 | 60 | ;; Ok, let's get rid of that and do our own thing 61 | 62 | (wc/undo!) 63 | 64 | ;; We're going to draw a version of the clown emoji! 🤡 65 | 66 | ;; The plan is this 67 | ;; - draw a big beige-ish ball that will be the head 68 | ;; - stick a bright red nose in the middle 69 | ;; - give it eyes surrounded by blue makeup 70 | ;; - give it a mouth, this one is trickier but we can get there by creating a 71 | ;; ball and slicing out the part we need 72 | ;; - generate clouds of pink wool for the hair 73 | 74 | ;; The center of the head will be thirty blocks above ground, so we'll use that 75 | ;; as our new anchor point for all that follows. 76 | (def clown-anchor (wc/add anchor [0 30 0])) 77 | 78 | ;; The head is easy enough, 30 blocks above ground, and with a 14.5 block 79 | ;; diameter. We won't actually get half blocks, but I've found that using 80 | ;; non-integer numbers leads to slightly nicer shapes sometimes. This uses a 81 | ;; distance function to find all blocks within a certain distance for the 82 | ;; center. We'll use white terracotta, which really has a more pinkish hue. 83 | 84 | ;; You can play around with these things, I've put these together with a bunch 85 | ;; of trial and error on the REPL, `(wc/undo!)` is your friend! 86 | 87 | (wc/set-blocks 88 | (shapes/ball {:center [0 0 0] ;; this is optional since it's [0 0 0] 89 | :radius 14.5 90 | :material :white-terracotta}) 91 | {:anchor clown-anchor}) 92 | 93 | ;; Draw the nose. Here the three anchor points come in handy, the red wool block 94 | ;; is towards positive z relative to the pink block, so we know that the nose 95 | ;; needs to be on the negative-z side of the face for us to see it. 96 | 97 | (wc/set-blocks 98 | (shapes/ball {:center [0 0 -14] 99 | :radius 2.4 100 | :material :red-concrete}) 101 | {:anchor clown-anchor}) 102 | 103 | ;; For the eye we'll use another for loop, making the inner two blocks black, 104 | ;; the outer blocks blue, and the ones in the middle white. 105 | 106 | ;; Note that with everything we're doing we're working first with relative 107 | ;; coordinates (around [0 0 0]), which we then offset to a specific place in the 108 | ;; world. This means this code can be used to draw the same shape elsewhere with 109 | ;; ease. 110 | 111 | (def eye 112 | (for [x (range 5) 113 | y (range 6) 114 | z [0]] 115 | [x y z 116 | (cond 117 | (and (#{2} x) (#{2 3} y)) :coal-block 118 | (or (#{0 4} x) (#{0 5} y)) :light-blue-concrete 119 | :else :white-concrete)])) 120 | 121 | (wc/set-blocks eye {:anchor (wc/add clown-anchor [3 1 -13])}) 122 | (wc/set-blocks eye {:anchor (wc/add clown-anchor [-8 1 -13])}) 123 | 124 | ;; The mouth is trickier, we'll start with a ball that's red on the outside and 125 | ;; white on the inside, then use `filter` to only retain a single "slice" from 126 | ;; the bottom middle. 127 | 128 | (wc/set-blocks 129 | (filter (fn [[x y z]] 130 | (and (< -9 x 9) 131 | (< y -4) 132 | (< -2 z 2))) 133 | (shapes/ball {:center [0 0 0] 134 | :radius 9.5 135 | :material :red-wool 136 | :fill :white-wool})) 137 | {:anchor (wc/add clown-anchor {:z -12})}) 138 | 139 | ;; Exercise for the reader: see if you can make the top line of the mouth red as 140 | ;; well. 141 | 142 | ;; The hair is trickier, we want it to be fluffy and cloudy. You could try 143 | ;; combining a bunch of smaller balls, but we'll do something else. 144 | 145 | ;; This function returns an infinite sequence of random locations, at most delta 146 | ;; blocks away in any direction from [0 0 0]. We want it to return both positive 147 | ;; and negative numbers, hence the (- (* 2 ...) ...). 148 | 149 | (defn random-positions [delta] 150 | (repeatedly #(do [(- (* 2 (rand) delta) delta) 151 | (- (* 2 (rand) delta) delta) 152 | (- (* 2 (rand) delta) delta)]))) 153 | 154 | ;; let's see what that looks like. Limit the result with `take`, or your server 155 | ;; will freeze up! 156 | 157 | (take 10 (random-positions 10)) 158 | 159 | ;; You can draw the result to get an idea, add a material to the location 160 | ;; vectors, and put it somewhere where we can see it, next to the clown. The 161 | ;; result is basically a sparse cube. 162 | 163 | (wc/set-blocks 164 | (map #(conj % :polished-andesite) 165 | (take 1000 (random-positions 10))) 166 | {:anchor (wc/add anchor [-30 30 0])}) 167 | 168 | (wc/undo!) 169 | 170 | ;; Now we filter these, we use a distance function to get blocks that are within 171 | ;; a ball around the center, rather than within a cube. We use some 172 | ;; randomization as well, blocks that are closer to the center are more likely 173 | ;; to be retained than blocks that are further away. 174 | 175 | (defn surrounding-positions [delta] 176 | (filter (fn [[x y z]] 177 | (< 178 | (wc/distance [0 0 0] [x y z]) 179 | (* delta (rand)))) 180 | (random-positions delta))) 181 | 182 | ;; You can see that in action too: 183 | 184 | (wc/set-blocks 185 | (map #(conj % :polished-andesite) 186 | (take 500 (surrounding-positions 10))) 187 | {:anchor (wc/add anchor [-30 30 0])}) 188 | 189 | ;; Put these in the right spot, and we have our fluffy tufts of hair 190 | 191 | (wc/set-blocks 192 | (map #(conj % :pink-wool) 193 | (take 500 (surrounding-positions 8.5))) 194 | {:anchor (wc/add anchor [-11 40 -3])}) 195 | 196 | (wc/set-blocks 197 | (map #(conj % :pink-wool) 198 | (take 500 (surrounding-positions 8.5))) 199 | {:anchor (wc/add anchor [11 40 -3])}) 200 | 201 | ;; So far we've only used the ball shapes, but you can try out the other 202 | ;; functions in the shapes namespace, like `torus`, `line`, `tube`, `torus` or 203 | ;; `rectube`, to make your own creation. In the process you might want to find 204 | ;; nice materials that give you the color and texture you want, that's what 205 | ;; we'll talk about in the next section. 206 | 207 | ;; The shapes API works well with the matric API, which gives you a number of 208 | ;; linear algebra operations with vectors and matrices. 209 | 210 | 211 | (doseq [angle [0 Math/PI (/ Math/PI 2) (- (/ Math/PI 2))]] 212 | (wc/set-blocks 213 | (->> chicken/chicken 214 | (m/rotate angle :x :z) 215 | (m/translate (wc/target-block me))))) 216 | -------------------------------------------------------------------------------- /repl_sessions/s02_locations_blocks_entities.clj: -------------------------------------------------------------------------------- 1 | (ns s02-locations-blocks-entities 2 | "Let's look at the core concepts of Minecraft. This section is a bit more 3 | theoretical, but it'll give you a much better foundation for what comes next, 4 | and hopefully give you a feeling for how the Witchcraft API is designed. It's 5 | quite intuitive once you see the patterns." 6 | (:require [lambdaisland.witchcraft :as wc])) 7 | 8 | (def me (wc/player)) 9 | (wc/set-game-rule :do-daylight-cycle false) 10 | (wc/set-time 2000) 11 | 12 | ;; In this section we'll look at the core concepts that Minecraft is made of, 13 | ;; and the objects used to represent them. Witchcraft goes to great lengths to 14 | ;; make sure you don't have to deal with the actual Java classes, converting to 15 | ;; and from Clojure data structures as necessary. Still it's good to have a 16 | ;; sense of what is there. 17 | 18 | ;; The Java classes we'll run into are from the Bukkit API. This is an 19 | ;; implementation-independent open-source API for Minecraft that many modded 20 | ;; servers implement, so that you are isolated from the implemenentation details 21 | ;; of the proprietary Minecraft code. 22 | 23 | ;; Let's start with locations. We need to position things in a three-dimensional 24 | ;; world, and to represent the position of a thing in that world Bukkit uses a 25 | ;; org.bukkit.Location 26 | 27 | (wc/location me) 28 | (wc/loc me) 29 | (wc/locv me) 30 | 31 | (wc/location me) 32 | (wc/location {:x 123 :y 44 :z 66 :pitch 20 :yaw 99}) 33 | (wc/location [123 44 66]) 34 | 35 | ;; This returns an instance of `org.bukkit.Location`. Note that witchraft 36 | ;; installs a print-handler, which is why it still looks kind of Clojure-y. We 37 | ;; don't currently install reader functions though, so this tagged literal is 38 | ;; not readable when used in source code. 39 | 40 | (class (wc/location me));; => org.bukkit.Location 41 | 42 | ;; Let's see what's in there, [[bean]] is a Clojure function that calls 43 | ;; all "getters" on a Java object, and returns them as a map. 44 | (bean (wc/location me)) 45 | 46 | ;; There's a bunch of things here, but the things that matter are `x`, `y`, `z`, 47 | ;; `pitch`, `yaw`, and `world`. These make up a location "value". 48 | 49 | ;; Of course Witchcraft has a more convenient function which gives you a nice 50 | ;; Clojure map: 51 | 52 | (wc/loc me) 53 | 54 | ;; There's also [[wc/locv]], (alias: [[wc/xyz]]) which just returns the x/y/z 55 | ;; values as a vector, great for destructuring. 56 | 57 | (let [[x y z] (wc/locv me)] 58 | ,,,) 59 | 60 | ;; Witchcraft functions will transparently accept all three, so you can ask for 61 | ;; the block at a given location using a map, a vector, or a Location 62 | ;; object (and more, anything that we can reasonably coerce to a Location). The 63 | ;; API is very forgiving like that. 64 | 65 | (wc/get-block {:x 0 :y 62 :z 100}) 66 | (wc/get-block [0 62 100]) 67 | (wc/get-block (wc/location me)) 68 | 69 | ;; - `x`: east-west position 70 | ;; - `z`: north-east position 71 | ;; - `y`: height level. Ranges from -64 to 320, with 62 being sea level. 72 | ;; - `world`: Minecraft has multiple worlds, you are currently in the 73 | ;; Overworld (simply called `"world"` in the API). There's also the Nether and 74 | ;; the End. 75 | 76 | ;; - `pitch`: the "tilt" of an entity, ranging from -90 (looking straight down), 77 | ;; over 0 (looking ahead) to 90 (looking straight up) 78 | ;; - `yaw`: the direction you are facing. 0=south, 90=west, 180=north, 270=east 79 | 80 | ;; If you press F3 you'll get a debug screen where you can also find your 81 | ;; current location. 82 | 83 | ;; Now compare these two: 84 | 85 | (wc/loc me) 86 | (wc/loc (wc/get-target-block me)) ; this is the block you are looking at, 87 | ; it gets a thin black outline if you're 88 | ; close enough 89 | 90 | ;; The player location has floating point x/y/z, as well as pitch and yaw. The 91 | ;; block location has integer x/y/z, and no pitch or yaw. 92 | 93 | ;; The Minecraft world consists of two things: blocks and entities. Blocks are 94 | ;; things like dirt, stone, wood, leaves, flowers, lanterns, all the things that 95 | ;; are placed in the world at a fixed integer x/y/z coordinate. 96 | 97 | ;; Entities are all the "loose" things, these can be "mobile entities" 98 | ;; or "mobs" (from animals, villagers, players), items you can pick up, mine 99 | ;; carts, boats, the little green experience orbs, arrows, or blocks that are 100 | ;; falling down. 101 | 102 | ;; Entities have a type, blocks have a material. 103 | 104 | (keys wc/entity-types) 105 | (keys wc/materials) 106 | 107 | ;; There's a [[wc/add]] function for manipulating locations, it will return the 108 | ;; same type you give it as its first argument. The remaining arguments can be 109 | ;; anything location-like. 110 | 111 | (wc/add (wc/location me) [0 3 0]) 112 | (wc/add (wc/loc me) [0 3 0]) 113 | (wc/add (wc/locv me) [0 3 0]) 114 | 115 | ;; Blocks in the world are represented by a `org.bukkit.block.Block`, besides 116 | ;; their location their main defining feature is their material. Here too we 117 | ;; have a function that returns the Bukkit class instance ([[get-block]]), and 118 | ;; one that returns a plain Clojure map ([[block]]). 119 | 120 | (wc/get-block [38 68 -1260]) 121 | (wc/block [38 68 -1260]) 122 | 123 | ;; If you are using the world seed that we configured for the 124 | ;; workshop (level-seed=35), then this should find a block of Obsidian at this 125 | ;; location. Obsidian blocks always look the same, they don't have a direction 126 | ;; or any other variants. 127 | 128 | ;; Let's go see what we're actually looking at 129 | 130 | (wc/loc me) 131 | (wc/teleport me {:x 34 :y 69 :z -1268 :pitch 15.3 :yaw -17.6 :world "world"}) 132 | 133 | ;; In front of this obsidian block there's a chest, and chests do have a 134 | ;; direction, and some other variant info represented by a "block-data" map 135 | 136 | (wc/block [38 67 -1261]) 137 | 138 | ;; Notice the `get-block` vs `block` and `location` vs `loc`. There's a pattern here. 139 | ;; 140 | ;; - location / loc 141 | ;; - get-block / block 142 | ;; - get-target-block / target-block 143 | ;; - get-inventory / inventory 144 | ;; - material / mat 145 | ;; 146 | ;; Some use `get-` for the Bukkit object version and omit the `get-` for the 147 | ;; Clojure version, others use an abbreviated form for the Clojure version. The 148 | ;; shorter of the two is always the Clojure version! 149 | 150 | 151 | ;; As with locations, if you need to specify a block you can use a map, vector, 152 | ;; or Block object. Let's put an anvil next to the chest, you can play around 153 | ;; with giving it a different direction. 154 | 155 | (wc/set-block {:x 37 :y 67 :z -1261 :material :anvil :direction :east}) 156 | (wc/set-block [37 67 -1261 :anvil {:direction :north}]) 157 | 158 | ;; Now that we're here at this broken portal, let's fix it and go see what the 159 | ;; Nether looks like. We'll fix this Nether portal with Obsidian blocks, and 160 | ;; then light it up with flint and steel. 161 | 162 | (wc/set-blocks (for [x (range 4) 163 | y (range 5) 164 | z [0]] 165 | [x y z (if (or (#{0 3} x) 166 | (#{0 4} y)) 167 | :obsidian 168 | :air)]) 169 | {:anchor [35 67 -1260]}) 170 | 171 | (wc/add-inventory me :flint-and-steel) 172 | ;; use this to click on the bottom layer of obsidian blocks 173 | 174 | ;; The nether is a scary place, you can come back through the portal, or teleport back. 175 | 176 | (wc/teleport me {:x 34 :y 69 :z -1268 :pitch 15.3 :yaw -17.6 :world "world"}) 177 | 178 | ;; For completeness sake let's also find an end portal. These are normally 179 | ;; rather hard to find, but we'll just make one ourselves. 180 | 181 | ;; Keep an eye on this pattern: a x/y/z for expression, used with set-blocks 182 | ;; and :anchor. 183 | 184 | (wc/set-blocks 185 | (for [x (range 5) 186 | y [-1 0 1 2] 187 | z (range 5)] 188 | (cond 189 | (and (= y -1) 190 | (or (#{0 4} x) 191 | (#{0 4} z)) 192 | (not (and (#{0 4} x) 193 | (#{0 4} z)))) 194 | [x y z :end-portal-frame {:facing (cond 195 | (= 0 x) :east 196 | (= 4 x) :west 197 | (= 0 z) :south 198 | (= 4 z) :north) 199 | :eye true}] 200 | (and (= y -1) 201 | (not (#{0 4} x)) 202 | (not (#{0 4} z))) 203 | [x y z :end-portal] 204 | 205 | :else 206 | [x y z :air])) 207 | {:anchor (wc/add me [0 1 5])}) 208 | --------------------------------------------------------------------------------