diff --git a/xsofy/status.lg b/xsofy/status.lg index 2013a259..adab3250 100644 --- a/xsofy/status.lg +++ b/xsofy/status.lg @@ -6,6 +6,8 @@ ;; :ttl — turns remaining ;; :dmg — damage per tick (optional) ;; :skip-turn — entity can't act (optional) +;; :no-decay — ttl is owned by a dedicated system; the generic tick +;; leaves it alone (optional) ;; :color — tint for rendering (def status-defs @@ -15,7 +17,11 @@ :confused {:color [200 100 255] :bg [40 20 50] :name "confused"} :slowed {:color [140 140 180] :bg [30 30 40] :name "slowed"} :haste {:color [255 255 100] :bg [40 40 15] :name "haste"} - :webbed {:color [200 200 210] :bg [30 30 32] :name "webbed"} + ;; :webbed ttl counts remaining struggles; the movement struggle branches + ;; (world/player-move, ai/try-move) decrement it and trample the web on + ;; the last one. Generic decay raced them and freed the creature with the + ;; web intact (#161), so the tick must not touch it. + :webbed {:color [200 200 210] :bg [30 30 32] :name "webbed" :no-decay true} :swimming {:color [60 100 180] :bg [10 20 50] :name "swimming"}}) ;; --- Apply / Remove --- @@ -51,8 +57,10 @@ world (reduce (fn [w [stype sdata]] - ;; Bail out if a prior tick (or external code) removed the entity. - (if (nil? (get-in w [:entities entity-id])) + ;; Bail out if a prior tick (or external code) removed the entity, + ;; or if the status decays through its own system (:no-decay). + (if (or (nil? (get-in w [:entities entity-id])) + (:no-decay (get status-defs stype))) w (let [def_ (get status-defs stype) ttl (dec (or (:ttl sdata) 1)) diff --git a/xsofy/test/run.lg b/xsofy/test/run.lg index 346b5c14..d5b0b171 100644 --- a/xsofy/test/run.lg +++ b/xsofy/test/run.lg @@ -15,6 +15,7 @@ [xsofy.test.dag-test] [xsofy.test.container-trigger-test] [xsofy.test.fire-test] + [xsofy.test.status-test] [xsofy.test.seed-test] [xsofy.test.mapgen-audit-test] [xsofy.test.replay-test] diff --git a/xsofy/test/status_test.lg b/xsofy/test/status_test.lg new file mode 100644 index 00000000..1cf14bf9 --- /dev/null +++ b/xsofy/test/status_test.lg @@ -0,0 +1,40 @@ +(ns xsofy.test.status-test + (:require [test :refer [deftest is testing]] + [xsofy.terrain :as terrain] + [xsofy.fire :as fire] + [xsofy.status :as status] + [xsofy.world :as world])) + +;; Regression tests for #161: webs no longer trampled. +;; +;; :webbed ttl is decremented by the movement struggle branches, which +;; trample the web on the last struggle. The generic status tick also +;; decremented it, so the ttl drained twice per turn and expired inside +;; the tick — which removes the status silently, without trampling. The +;; struggle branch then never saw ttl <= 1 and the web survived every +;; escape. :webbed is now :no-decay: the struggle branches own its ttl. + +(deftest generic-tick-leaves-webbed-alone + (testing ":no-decay statuses keep their ttl through the generic tick" + (let [w {:entities {:e {:id :e :pos [1 1]}}} + w (status/apply-status w :e :webbed 2) + w (status/apply-status w :e :confused 2) + w (status/tick-entity-statuses w :e)] + ;; still webbed, ttl untouched + (is (= 2 (get-in w [:entities :e :statuses :webbed :ttl]))) + ;; the normal decay path still works + (is (= 1 (get-in w [:entities :e :statuses :confused :ttl]))) + (let [w (status/tick-entity-statuses w :e)] + (is (status/has-status? w :e :webbed)) + (is (not (status/has-status? w :e :confused))))))) + +(deftest webbed-player-break-free-tramples-web + (testing "player struggling free of a web leaves ash, not an intact web" + (let [w (world/make-world 30 20 1) + [px py] (get-in w [:entities :player :pos]) + _ (terrain/tset! (:terrain w) (:width w) px py fire/web-tile) + w (status/apply-status w :player :webbed 2) + w (world/player-move w 1 0) ;; struggle: ttl 2 -> 1 + w (world/player-move w 1 0)] ;; final struggle: break free + trample + (is (not (status/has-status? w :player :webbed))) + (is (= fire/ash-tile (terrain/tget (:terrain w) (:width w) px py))))))