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_FACTORfrom a config file (Config), so server owners can choose. - A bounty board: count spider kills per player (by
uuid) and reward every tenth one withkiller:give("minecraft:string", 8). - Make spiders a little tougher at night with a head mixin on
entity.living.hurtthat lowersci.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.