Skip to content

Add the Evil Tree Distraction & Diversion - #1256

Open
HarleyGilpin wants to merge 18 commits into
GregHib:mainfrom
HarleyGilpin:evil-tree
Open

Add the Evil Tree Distraction & Diversion#1256
HarleyGilpin wants to merge 18 commits into
GregHib:mainfrom
HarleyGilpin:evil-tree

Conversation

@HarleyGilpin

@HarleyGilpin HarleyGilpin commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Builds on the existing evil-tree work in progress and finishes it so the event can be played end to end: a seedling sprouts somewhere in the world, grows into a tree, gets chopped and burned down, and pays out rewards.

image

Reference: 2011 wiki for the numbers and the flow.

Event lifecycle

Two world timers drive the event — evil_tree_spawn schedules the next tree, evil_tree ticks the live one. Timers rather than World.queue because clearing a queue invokes its pending block, which would respawn a tree during teardown.

A tree sprouts two minutes after startup and two hours after the last one died, both configurable, and its location is announced when world.messages is on.

Growth

Saplings advance a stage every seed_health growth points, gained either from the growth timer or from nurturing, which pays the tier's farming experience spread across the stage. Roots briefly shoot out of the ground near a sprouting sapling, once per player per tree. The final stage displaces anyone standing where the 3x3 tree appears, sets the tree's health and bursts four roots out of the ground.

Chopping and burning

  • Chopping the trunk uses the woodcutting success rates of the matching log and takes a point of health per successful chop; the tree swaps to its damaged and dying models at two thirds and one third health.
  • Chopping a root yields evil tree kindling and dies after three to seven successful chops. A replacement root bursts out every minute, shoving and stunning anyone stood on top of it.
  • Lighting kindling against the trunk places a fire in one of the twelve spots around the tree; every lit fire burns a point of health per tick cycle.
  • Lightning strikes at ten, twenty and thirty minutes, capping the tree at half then a quarter of its health before felling it outright.

Rewards

Kindling is the currency. It's handed in to the leprechaun or taken from the reward stump (varbit 1542 picks the reward or blank stump per player), capped at two hundred a day, paying coins, noted logs and "evil tree magic" scaled by the amount handed in. Evil tree magic halves the chance of felling a tree, escorts logs to the bank once the inventory is full and doubles the bird's nest rate.

Players can help with two trees a day; inspecting a tree doesn't count towards that.

Fixes to the work in progress

  • The growth timer registered player handlers (timerStart/timerTick) against an npc timer, so the body never ran and a zero interval task span every tick.
  • A string concatenation in the stage when ("evil_tree_young" + "evil_tree_young_large") meant neither young stage matched.
  • The burning code checked and removed kindling, the Lunar Diplomacy item, rather than evil_tree_kindling.
  • chop and light were never wired to an option, so a single click advanced a whole stage with no level check, animation or experience.
  • wander_radius isn't a valid npc parameter and threw on startup; wander ranges live in the npc_ranges table.
  • tree_xp held the wiki's root experience values; corrected both columns against the wiki, along with elder's burn experience and woodcutting level.
  • The spirit tree referenced an object id (strange_sapling) that doesn't exist, so its sapling branch was unreachable, and its countdown was a placeholder. Both now work off the live event state.

Testing

EvilTreeTest covers growth by nurture and by timer, the growth spurt, chopping the trunk and the roots, root death, burning kindling, fire damage, the lightning strikes, death and respawn scheduling, the reward stump, the tool loan, and both daily limits. Full suite passes.

GregHib and others added 11 commits September 1, 2026 17:17
Completes the work-in-progress evil tree event so it can be played end to
end: a seedling sprouts, grows into a tree, gets chopped and burned down,
and pays out rewards.

Event lifecycle
- Two world timers drive the event: `evil_tree_spawn` schedules the next
  tree, `evil_tree` ticks the live one. Timers are used over `World.queue`
  because clearing a queue invokes its pending block, which would respawn
  a tree during teardown.
- A tree sprouts two minutes after startup and two hours after the last one
  died, both configurable, and its location is announced when world
  messages are enabled.

Growth
- Saplings advance a stage every `seed_health` growth points, gained either
  from the growth timer or from nurturing, which pays the tier's farming
  experience spread across the stage.
- Roots briefly shoot out of the ground near a sprouting sapling, once per
  player per tree.
- The final stage displaces anyone standing where the three by three tree
  appears, sets the tree's health and bursts four roots out of the ground.

Chopping and burning
- Chopping the trunk uses the woodcutting success rates of the matching log
  and takes a point of health per successful chop; the tree swaps to its
  damaged and dying models at two thirds and one third health.
- Chopping a root yields evil tree kindling and dies after three to seven
  successful chops. A replacement root bursts out every minute, shoving and
  stunning anyone stood on top of it.
- Lighting kindling against the trunk places a fire in one of the twelve
  spots around the tree; every lit fire burns a point of health per tick
  cycle.
- Lightning strikes at ten, twenty and thirty minutes, capping the tree at
  half then a quarter of its health before felling it outright.

Rewards
- Kindling is handed in to the leprechaun, or taken from the reward stump,
  capped at two hundred a day and paying coins, noted logs and evil tree
  magic scaled by the amount handed in.
- Evil tree magic halves the chance of felling a tree, escorts logs to the
  bank once the inventory is full and doubles the bird's nest rate.
- Players can help with two trees a day; inspecting a tree doesn't count.

Also fixes several defects in the existing work in progress: the growth
timer registered player handlers against an npc timer so it never ran, a
string concatenation in the stage `when` meant neither young stage matched,
the burning code used the Lunar Diplomacy kindling item, the chop and light
loops were never wired up to an option, and `wander_radius` isn't a valid
npc parameter. The spirit tree now gives a real countdown to the next tree
and its sapling branch is reachable.
`GameObjects.remove` emitted its despawn event midway through its own
bookkeeping, before clearing the object's map flag. An object added by a
handler claimed that flag, and the clear which followed wiped it again.

The client had already been sent the ObjectAddition, so the new object was
drawn, but `map` held zero for that tile and layer, which `getLayer` treats
as empty. Every server side lookup - `getLayer`, `findOrNull`, `contains`
and so the object interaction dispatch - found nothing there. The object
was visible and completely inert.

Evil tree roots hit this: they burst out as a temporary `_spawn` model and
the settled root is added in its place from an `objectDespawn` handler, so
the roots could be seen but never chopped.

Emit the despawn once the map flag is cleared and any original object
underneath has been restored, so handlers only ever observe a settled tile.
Two existing tests pinned the old ordering of the event against the
original's re-add and have been updated to the new one.

Drives the evil tree root tests through the real burst and settle path
rather than placing roots into the event state by hand, which is why the
suite stayed green while the feature was broken in game.
The woodcutting loop breaks out on a full inventory before it ever reaches
`addLog`, so the banking branch added for evil tree magic was unreachable
and logs simply stopped being cut.

Let the loop continue while the buff is going to escort the log to the
bank, keeping the "inventory is too full" exit for everyone else and for
the log types the buff doesn't carry.

Also stores the buff's remaining duration in ticks rather than seconds. It
was counted down by a timer whose one second interval rounds to a single
tick, so the buff ran out in about six tenths of the time it was granted
for.
Testing the reward buff's woodcutting effects meant growing a tree, felling
it and handing in kindling first. `::evil_tree_magic [minutes]` grants it
directly, defaulting to the thirty minutes an elder tree pays out, and
takes it away again with 0.
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTreeState.kt Outdated
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTreeLeprechaun.kt Outdated
Comment thread game/src/main/kotlin/content/skill/woodcutting/Woodcutting.kt Outdated
Drops the EvilTreeState object: state read by the leprechaun and spirit
tree lives in EvilTree.Companion, everything else is an instance field.
Renames the global Player helpers to evilTreeDailyReset and
evilTreeInteract, and moves the chop success chance functions into the
Woodcutting companion instead of leaving them at top level.
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt Outdated
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt Outdated
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt Outdated
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt Outdated
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt Outdated
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt Outdated
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt Outdated
Comment thread game/src/main/kotlin/content/activity/evil_tree/EvilTree.kt
Fires were added with the default centre piece shape but object 11425
only has wall and wall decoration models, so the client drew nothing
and the fire fought the root for the same ground layer tile. Fires are
now wall decorations lit on the tile the player is standing on, refusing
with "That part of the tree is already on fire!" when it is taken.

Roots now throw back anyone chopping or lighting fires beside them, and
bursting roots force the player away with an exact movement instead of a
walk request the interaction ignored, cancelling what they were doing.
The dodge uses animation 6999 with the stun graphic, the notice graphic
is played on the ground and the tree faces displaced players.
Roots only lash out at players chopping or lighting the tree itself
while standing beside them.
The burning animation (9593) belongs to the varbit multilocs the fires
sit under on the real map, which the client uses to animate the child
model, so a bare fire object never moves. Play the animation on the
object when it is lit and replay it for players entering the zone later,
since object animations are not part of the zone data sent on arrival.
The client only builds an animated scene object when the definition
has an animation, transforms or a varbit, and its animate-object packet
ignores everything else, so a bare fire (11425) can never move. The
real map uses eight varbit multilocs around the tree (14887, 15253 and
siblings) carrying animation 9593, one per side and corner. Spawn those
instead, pick the one on the player's side of the tree, and keep every
nearby player's fire varbits in step when a fire is lit, burns out, the
tree dies, or they walk into the area.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants