Fork me on GitHub

Hey @devurandom, I looked at that a tiny bit, main conclusion is that there is nothing in Bukkit for this, so we can't really offer a cross-server solution in Witchcraft. I've been meaning to have a look at the Citizens API but haven't really had the chance.


There are some tutorials how to do it with Fabric, which supposedly has a more stable API than NMS, but Fabric does not seem to be supported on a Paper server:


There is am implementation of the Paper API on top of Fabric (, but it seems to still be missing some features: • Do you have experience with running Witchcraft on Cardboard?


Interesting, I did not know about Cardboard... this is all such a big space to explore. It's funny that Fabric was created to get rid of Bukkit/Forge style APIs, instead using NMS directly, but there's such a big legacy of Bukkit plugins that it makes sense for people to do this.


Targeting Cardboard with Witchcraft should be very doable, we're all set up as well to deal with whatever little incompatibliities there are in a transparent way

🎉 1

Hi @U07FP7QJ0! I took a look at Citizens, but for that I appear to have to create classes with Java annotations (cf. -- the event handler methods on the trait have an EventHandler annotation). To get those I appear to need gen-class (`proxy` does not appear to support Java annotations), which means I need to compile the code and have the resulting class on the classpath. However compile refuses to obey, because it needs the code to be in a file. I have very little experience with Java->Clojure interop so far and feel like I am going down a (the wrong) rabbit hole here. Do you have an advice?


I see, that sounds very annoying. At this point I'd probably start looking at the Java code to see how they do things to see what the options are...

🙏 1

I've done a lot of Clojure/Java interop but I don't have a good answer off the top of my head, I'd have to look at it closer and try things out. This kind of stuff is where they need concrete classes is where the interop gets super annoying, so maybe citizens isn't for us. Sometimes you can peel back a layer and use some of the functionality in a more direct way, thus bypassing all this java wiring stuff, but hard to say right now.


I also briefly tried to use proxy to see how far I get with that, but Citizens complains that Trait class must have a no-arguments constructor, which apparently cannot be satisfied by proxy.

(defonce MyTrait
  (proxy [Trait] ["mytrait"]))
(.. CitizensAPI
    (registerTrait (.. TraitInfo
                       (create (class MyTrait))
                       (withName "mytrait"))))


I'll see if I still have time to poke at this a bit. I had a bit of look at their code and as usual everything is locked down so a workaround to avoid making concrete classes would be quite involved. I think gen-class might be the best option in the end... generate a thin wrapper that dispatches to plain functions so you can redefine them afterwards. That will include a compilation step, and figuring out where the compiled output should go so it gets picked up.


I would add the compiled dispatcher class to a classes directory and add that to the :path in deps.edn, right?


The code that I could not get to work because of the constructor issue:

(def trait-name "mytrait")

(defonce myTrait
  (proxy [Trait] [trait-name]
    (load [^DataKey key])
    (save [^DataKey key])
    (run [])
    (onAttach []
      (println trait-name "attached to" (.getNPC this)))
    (onDespawn [])
    (onSpawn [])
    (onRemove [])))

(defn register-trait [^Class trait-class ^String trait-name]
  (.. CitizensAPI
      (registerTrait (.. TraitInfo
                         (create trait-class)
                         (withName trait-name)))))

(register-trait (class myTrait) trait-name)


Meanwhile I went from traits to playing with behaviours and that works reasonably well:

(defonce thief (.. CitizensAPI
                   (createNPC EntityType/PLAYER "fullwall")))

(.spawn thief (-> (wc/player)

(.. thief
    (setTarget (-> (wc/player)

(comment (.. thief
             (addGoal (WanderGoal/createWithNPC thief) 1)))

; A barebones version of TargetNearbyEntityGoal:
(def meanBehaviour
  (proxy [Behavior] []
    (reset [])
    (run []
    (shouldExecute []
      (if (.. thief isSpawned)
        (do (.. thief
                (setTarget (wc/player) true))

(.. thief

(.. thief
     (addBehavior meanBehaviour 1))
This is extremely barebones, but it seems I am on the right path. 🙂


hey, that's great!


I think ByteBuddy may hold the answer to using Traits

(.. (net.bytebuddy.ByteBuddy.)
    (subclass Trait)
    (load (lambdaisland.classpath/context-classloader))
;;=> net.citizensnpcs.api.trait.Trait$ByteBuddy$dImbs8fA


Or maybe not 😄 not that easy to work with it seems


ok had a better look at AbstractNPC and I think this might actually be enough

(defonce thief (.. CitizensAPI
                   (createNPC EntityType/PLAYER "fullwall")))

(def t
  (proxy [Trait] ["my-trait"]
    (click [e] (println "Trait got click!"))
    (onAttach [] (println "Trait got attached!"))))

(.addTrait thief t)


no need for the whole class registry shenanigans

🎉 1

Oof, thanks for finding this. I was looking for such method, but must somehow have missed it.


To handle events I would hook into the global event handlers that I already know from Witchcraft's block manipulation, right? Then I just need a map to find the NPC object for an entity, but can avoid the @EventHandler Java annotation and thus gen-class.


Yes, I think so. But might need a small update to detect the new event types


I just merged a bunch of improvements and additions I did over christmas, including an addition to the gallery of a classic mob spawner

🎉 2
minecraft 2

released in witchcraft 0.8.201


if anyone else got creative over the holidays please share screenshots!