Skip to content

Project: spider bounty

Lesson 17.6

A small server mod made only of mixins. Spiders become worth hunting:

  • spiders and cave spiders drop double experience,
  • the player who kills one sees “Spider bounty!” above the hotbar,
  • spiders are knocked back half as far, so they stay in the fight,
  • players take no fall damage from 4 blocks or less (vanilla: 3), so jumping down to chase a spider doesn’t hurt.

Every step uses something from this chapter.

Step 1: the project

> clight new lua spider_bounty --name "Spider Bounty"
> cd spider_bounty

All the targets the mod uses run on the server, so the players need nothing installed. Say so with "side": "server" and "sync": "server_only" in clight.mod.json (The manifest):

{
  "schema": 1,
  "id": "spider_bounty",
  "version": "1.0.0",
  "name": "Spider Bounty",
  "description": "Double experience from spiders, a bounty message, less spider knockback and softer landings.",
  "authors": ["Alex"],
  "license": "MIT",
  "api": 1,
  "game": ">=26.3 <26.4",
  "side": "server",
  "sync": "server_only",
  "runtime": { "type": "lua", "entry": "scripts/main.lua" },
  "permissions": []
}

Step 2: double experience

entity.mob.xp_reward returns the experience a mob drops. A "return" mixin doubles it (Head, return and cancel). A filter takes one id, so the mod adds one mixin per spider type:

-- scripts/main.lua
local SPIDERS = { "minecraft:spider", "minecraft:cave_spider" }
local XP_FACTOR = 2

for _, id in ipairs(SPIDERS) do
  cl.mixin.inject("entity.mob.xp_reward", "return", function(ci)
    ci.ret = ci.ret * XP_FACTOR
  end, { filter = id })
end

Try it: /summon minecraft:spider ~ ~ ~3 and kill it with a sword. A spider normally drops 5 experience; now it drops 10.

Step 3: the bounty message

entity.living.die runs when an entity dies, and ci.args.source.attacker is who killed it. The mod only looks here, so it uses priority = "monitor": it runs after every other mod, it can’t change anything by accident, and it is skipped when another mod cancelled the death (Priorities, conflicts and debugging).

for _, id in ipairs(SPIDERS) do
  cl.mixin.inject("entity.living.die", "head", function(ci)
    local killer = ci.args.source.attacker
    if killer and killer:is_player() then
      killer:send_action_bar("Spider bounty! Double experience.")
    end
  end, { filter = id, priority = "monitor" })
end

attacker is nil when nobody is responsible (a spider that falls into lava), hence the killer and check.

Step 4: less knockback for spiders

entity.living.hurt has a value knockback_strength (vanilla 0.4). A value mixin halves it for spiders, and nothing else about the hit changes (Points and values):

for _, id in ipairs(SPIDERS) do
  cl.mixin.inject("entity.living.hurt", "value:knockback_strength", function(ci)
    ci.value = ci.value / 2
  end, { filter = id })
end

Step 5: softer landings

entity.living.cause_fall_damage has a value safe_distance: how far you can fall without damage (players: 3 blocks). Raise it to 4 for players:

cl.mixin.inject("entity.living.cause_fall_damage", "value:safe_distance", function(ci)
  ci.value = ci.value + 1
end, { filter = "minecraft:player" })

Try it: jump down 4 blocks. Without the mod you lose half a heart, with it nothing.

Step 6: check it

Start the game with clight run, open a world with cheats, and type /clight mixins:

entity.living.hurt
  value:knockback_strength  spider_bounty  priority 0  calls 0
  value:knockback_strength  spider_bounty  priority 0  calls 0
entity.living.die
  head  spider_bounty  priority 10000  calls 0
  head  spider_bounty  priority 10000  calls 0
entity.living.cause_fall_damage
  value:safe_distance  spider_bounty  priority 0  calls 0
entity.mob.xp_reward
  return  spider_bounty  priority 0  calls 0
  return  spider_bounty  priority 0  calls 0

Fight a spider and look again: the calls count up. /clight mixins conflicts should say no mixin conflicts. If a line stays at calls 0 after you killed a spider, check the filter id for typos. The same table is in logs/clight-mixins.txt.

The whole script

-- Spider Bounty: double experience from spiders, a bounty message, less spider knockback, softer landings.
local SPIDERS = { "minecraft:spider", "minecraft:cave_spider" }
local XP_FACTOR = 2

for _, id in ipairs(SPIDERS) do
  cl.mixin.inject("entity.mob.xp_reward", "return", function(ci)
    ci.ret = ci.ret * XP_FACTOR
  end, { filter = id })

  cl.mixin.inject("entity.living.die", "head", function(ci)
    local killer = ci.args.source.attacker
    if killer and killer:is_player() then
      killer:send_action_bar("Spider bounty! Double experience.")
    end
  end, { filter = id, priority = "monitor" })

  cl.mixin.inject("entity.living.hurt", "value:knockback_strength", function(ci)
    ci.value = ci.value / 2
  end, { filter = id })
end

cl.mixin.inject("entity.living.cause_fall_damage", "value:safe_distance", function(ci)
  ci.value = ci.value + 1
end, { filter = "minecraft:player" })

cl.log("Spider Bounty loaded")

Ideas

  • Read XP_FACTOR from a config file (Config), so server owners can choose.
  • A bounty board: count spider kills per player (by uuid) and reward every tenth one with killer:give("minecraft:string", 8).
  • Make spiders a little tougher at night with a head mixin on entity.living.hurt that lowers ci.args.amount.

Recap: a mod can be nothing but mixins: a return for the reward, a monitor head to watch, value mixins for gentle tweaks, and /clight mixins to see that they run.