CmdrDats / clj-minecraft

Clojure Bukkit plugin, Minecraft.
74 stars 35 forks source link

Updated for Minecraft 1.12.1 #50

Closed TimJSwan89 closed 7 years ago

TimJSwan89 commented 7 years ago

Updated to work with new repository location: spigot instead of bukkit. Also, updated dependencies to available bukkit version number and a deprecated 'painting' object to its new 'hanging' replacement. Works in Minecraft 1.12.1 Special thanks to @MagentaRuby

CmdrDats commented 7 years ago

This is really rad, thank you :)

MagentaRuby commented 7 years ago

Is it alright if we post this on spigotmc.org? I plan on making a few improvements, and it will stay open source.

On Sun, Sep 3, 2017 at 4:42 PM, CmdrDats notifications@github.com wrote:

Merged #50 https://github.com/CmdrDats/clj-minecraft/pull/50.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/CmdrDats/clj-minecraft/pull/50#event-1232736368, or mute the thread https://github.com/notifications/unsubscribe-auth/AKI8eJmKbxUSumrliCsDE-Mc0efQOk1fks5sex1VgaJpZM4PLT2v .

CmdrDats commented 7 years ago

I'd definitely be keen on that idea, sure :) it might even get me to pick this project up again - I got a bit put off when I couldn't post the plugin to bukkit because of the bytecode generation security risk (which I completely understand)..

Would you prefer to take over the project and have mine point to yours - or perhaps I can give you direct push access to this repo?

TimJSwan89 commented 7 years ago

If you think that you won't be available for approval at times, we would like to have shared ownership. If you don't plan on doing major updates, then sure, have it point to ours.

Thanks for creating such a great project! Could you add more to the README file with some simple examples of actions like placing blocks, killing players, or putting messages into chat?

CmdrDats commented 7 years ago

Cool - I'm around quite a bit, but more than happy to share ownership :)

As for the readme bits - I have spent a bit of time before working on the wiki: https://github.com/CmdrDats/clj-minecraft/wiki - I see some of the links are broken now, but the side nav still lets you go to the various pages.. Maybe that'll be a good starting point?

TimJSwan89 commented 7 years ago

I'm having trouble knowing how to use this REPL. Your wiki says to use: (log/info "Player %s has fallen off a cliff and caused and NPE." player-name) However, when I do that, I get CompilerException java.lang.RuntimeException: No such namespace: log, compiling:(NO_SOURCE_PATH:1:1) In your video, you have (broadcast-msg "text goes here") but that returns the message CompilerException java.lang.RuntimeException: Unable to resolve symbol: broadcast-msg in this context, compiling:(NO_SOURCE_PATH:1:1) The README has cljminecraft.core=> (defn mybreakfn [ev] {:msg (format "You broke a %s" (.getBlock ev))}) which is different from the other two sources. Also, how do you exit the core state to return to the user state. Also, what is :msg and what is [ev] and curly brackets for? Can you get a count of different possible methods of communicating with the server? I don't mind learning some closure, but some of this seems like it interfaces with the server and I can't recognize what is local or not. I'm still trying to get a 'hello world' to broadcast and to place a block at a specific location.

CmdrDats commented 7 years ago

Hi Tim,

There is some clojure knowledge implied in using the mod, which is why I don't go into too much detail here, but I can give you a basic rundown so you can see what you're actually looking at, and then I'll address your specific issues - I'm going to try to keep it as short as I can:

Clojure has 4 data structure that defines its entire syntax: lists (), vectors [], maps {} and sets #{} - It then has a few primitives: symbols info, strings "fallen off cliff", numbers 3748, boolean true, keywords :msg. Commas in data structures are optional and usually left off - so (1 2 3) would be a list of 3 numbers, [:a b "c"] would be a vector with a keyword, symbol and string, {:name "CmdrDats" "random" 10} would be a map with key/value pairs :name/"CmdrDats" and "random"/10 - anything (effectively) is a valid key or value in clojure maps.

When coding in clojure, you're simply constructing lists - the Clojure evaluator reads in the code and whenever it sees a list (ie, round parens ()), it'll assume the first element in the list is a function that it needs to call with the rest of the elements as the arguments.

so (broadcast-msg "text goes here") is literally a list with a symbol and a string - the evaluator looks at the first element and tries to resolve broadcast-msg to a function to call it with `"text goes here".

Clojure is then further organised into namespaces to avoid name clashing, so the cljminecraft.core you see means that you're in that namespace - usually it's a 1-1 relationship with the file/folder, similar to Java. Also similar to Java, you need to import (or in clojure parlance require) other namespaces to declare that you use them - so in the case of log/info, if it says there's no namespace log, it means that you're not in a namespace that has declared that require

Vars (the named things in namespaces) are defined with the def function - so (def x 100) would map the symbol x literally to 100

The syntax for a function is (fn [x] x) where fn is the function that creates functions, the [x] vector is where the named parameters go and the x is the body of the function. The last form implicitly returns its value from the function, so (fn [x] (+ x 1)) would be the function to add one to a number. You could call it directly like this ((fn [x] (+ x 1)) 10) => 11 - because the evaluator will see that the result of the first form of the list is a function and run it..

One other syntax you'll come across for functions is the # - (fn [x] (+ x 1)) is exactly equivalent to #(+ % 1) - this is often used for inline functions you want to pass to other functions.. (particularly when you do collection transformations using map, filter, reduce)

You can mix the def and fn to create namespaced functions : (def add (fn [x] (+ x 1))), but because that's such a common thing to do, you can use defn : (defn add [x] (+ x 1))) - which is shorthand for the same thing.

From the repl, you can add a require with: (require '[cljminecraft.logging :as log]) - notice that it's a tiny bit different that the one in the actual files, which is (:require [cljminecraft.logging :as log]) - this is because it's just some data parameters for the ns function to use, it calls require later. This will solve the log/info problem.

For the broadcast-msg issue, you'll see that this namespace : https://github.com/CmdrDats/clj-minecraft/blob/master/src/cljminecraft/player.clj has the broadcast function (not sure why it's not broadcast-msg though, I can't find that anywhere in the code) - so, this should work in the repl: (require '[cljminecraft.player :as player]) (player/broadcast "Hello everyone")

The (defn mybreakfn [ev] {:msg (format "You broke a %s" (.getBlock ev))}) creates a function in the current namespace named mybreakfn - which taken in an event object (I haven't spoken about java interop, but the (.getBlock ev) is a direct java call on the even object) and returns a map with :msg keyword and the formatted string.

It's just a standalone function though, it can't do anything on its own, so it needs to be registered, hence the (ev/register-event @clj-plugin "block.block-break" #'mybreakfn) call.. That calls the register-event function in https://github.com/CmdrDats/clj-minecraft/blob/master/src/cljminecraft/events.clj#L16 - for the current plugin, listening for the "block.block-break" event and will call mybreakfn when it happens. You'll notice a special syntax #'- that sends the actual var name to the register-event instead of the function value itself - in other words, if you redefine mybreakfn, it'll fire the new one instead of holding onto the old function.

For the world generation, https://github.com/CmdrDats/clj-minecraft/wiki/World-generation should give you a start there?

If you have more questions or anything I said above is confusing (I tried to keep it very short, so I had to skip stuff), please fire away, I'm happy to help where I can :)

TimJSwan89 commented 7 years ago

Thank you! I'm not sure if we should communicate through another method. Ruby and I are attempting to upgrade the wiki drastically. I've been attempting to use the world generation wiki. Entering (:require [cljminecraft.blocks :as bl]) gives a response: CompilerException java.lang.ClassNotFoundException: cljminecraft.blocks, compiling:(NO_SOURCE_PATH:1:1) However, entering (in-ns 'cljminecraft.blocks) gives a response: #<Namespace cljminecraft.blocks> and updates the namespace: cljminecraft.blocks=> So should I change that on the wiki?

Also, I attempted to run one of the examples:

cljminecraft.blocks=> (def ctx (setup-context (first (.getOnlinePlayers (bk/server)))))
#'cljminecraft.blocks/ctx

Okay, so that worked. Continuing:

cljminecraft.blocks=> (defn box [] [(forward 8) (right 8) (back 8) (left 8)])
#'cljminecraft.blocks/box

Nice. So, I tried the final action:

cljminecraft.blocks=> (run-actions ctx (material :air) (extrude :up 10 (forward 10) (right 10) (back 8) (left 2) (back 2) (left 8)))

ArityException Wrong number of args (2) passed to: blocks$fork  clojure.lang.AFn.throwArity (AFn.java:437)
cljminecraft.blocks=> 

Okay, so I understand that there's an argument number mismatch. However, right now I am interested in correcting my debugging mindset as how to look at this problem from a standpoint of multiple files and locations. AFn.java is reported on the error as the file. It's a java file, not a closure file, so it confuses me a bit, not only because the language is different than the parsing error, but because I don't know if I actually should look in the java file or if I even have access to it.

Ruby helped me get out of my university/local/simple mindset when I demanded special help when debugging your original project. When I got a particular error about dependencies, instead of trusting the error messages too much, I was guided to go to the source of the update and see what's been changed or replaced and simply attempt updating the project according to that.

Ruby is better at the file, system, and framework stuff than I am. I'm more of the theory person so this stuff kills me, but I need to conquer it. If you have time, could you write a complete and working example set of REPL Clojure commands that result in a 5x5 filled cube of glass around the player? I think that would give me a good skeleton for what I am trying to accomplish.

CmdrDats commented 7 years ago

Ha, ye - we probably should be chatting another way :) I've created a gitter chat - let's see how that pans out? I'm not sure how much time I'll get to answer questions - work is super busy at the moment (heavy deadlines), but will try my best.

The (:require [...]) form is meant for use within code, not so much the repl - if you tweak it by taking off the : and adding a quote before the [, then it'll work within the repl though - so (require '[cljminecraft.blocks :as bl]) - then you can access the same functions by prefixing everything with bl/ - ie (bl/run-actions ctx (bl/material :air) (forward 10))

There's a bug in the extrude code that I hadn't gotten to fixing. I see I got all fancy with a defaction macro, so it's not obvious how it's supposed to get fixed. My guess is that it should be:

(defn extrude [direction x & actions]
  (for [c (range x)]
     [{:action :move :direction direction :distance c}
      (fork actions)]))

The AFN.java error is because it comes from the Clojure runtime environment, which is written in Java :) AFN is the Abstract Function call class which deals with calling functions. The Clojure errors aren't the best, but when you filter out the java guk and look for function names/files in the stacktrace that look like it's your project stuff, that'll likely be where the problem lies.

I would love to build a working example for you, but I'm currently working every night solid (after putting kiddies to sleep) till past midnight to hit deadlines, so it might be a while before I can get minecraft + spigot + clj-minecraft setup to get this all fixed up.

TimJSwan89 commented 7 years ago

By the way, I typed something in incorrectly in my comment earlier, but the result of what I said should be the same: the thing I ran didn't work.

user=> (in-ns 'cljminecraft.blocks)
#<Namespace cljminecraft.blocks>
cljminecraft.blocks=> (def ctx (setup-context (first (.getOnlinePlayers (bk/server)))))
#'cljminecraft.blocks/ctx
cljminecraft.blocks=> (defn box [] [(forward 8) (right 8) (back 8) (left 8)])
#'cljminecraft.blocks/box
cljminecraft.blocks=> (run-actions ctx (extend :up 5 (box)))

IllegalArgumentException 5 is not a protocol  clojure.core/extend (core_deftype.clj:742)
cljminecraft.blocks=> 

Earlier, I incorrectly tried: cljminecraft.blocks=> (run-actions ctx (material :air) (extrude :up 10 (forward 10) (right 10) (back 8) (left 2) (back 2) (left 8))) instead of cljminecraft.blocks=> (run-actions ctx (extend :up 5 (box))) which also didn't work, of course.

I'm just saying this to reduce any confusion caused.