aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/help/combat.yaml8
-rw-r--r--internal/game/sys_combat.go36
2 files changed, 37 insertions, 7 deletions
diff --git a/data/help/combat.yaml b/data/help/combat.yaml
index ac4cf65..bbed338 100644
--- a/data/help/combat.yaml
+++ b/data/help/combat.yaml
@@ -44,4 +44,10 @@ description: |
To escape combat, move in any direction. You must wait for the mob
to miss an attack — on a miss the 1-2 tick run delay begins and you
leave the room; on a hit the escape fails ("Can't escape!") and you
- stay in combat. See 'help movement' for details.
+ stay in combat. Three hits in a row while fleeing triggers a
+ "power through" relief that lets you flee anyway on the third hit.
+ See 'help movement' for details.
+
+ Aggressive mobs attack with a 33% chance per tick the player is in
+ their room — including the arrival tick, so straight-through runners
+ are not safe.
diff --git a/internal/game/sys_combat.go b/internal/game/sys_combat.go
index bf4f882..e23d94b 100644
--- a/internal/game/sys_combat.go
+++ b/internal/game/sys_combat.go
@@ -2,9 +2,9 @@ package game
import (
"fmt"
+ "math/rand"
"sort"
- "thehouseoficarus/internal/engine"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
)
@@ -68,6 +68,14 @@ func (g *Game) dropItemsOnDeath(p *player.Player) {
}
}
+// aggroChancePerTick is the probability per engine tick that an aggressive mob
+// in the player's room initiates combat against them. The first roll happens
+// inline on arrival (so a player who enters and immediately moves again — the
+// 1-tick single-move case — still risks being caught ~33% of the time); if the
+// initial roll fails, a per-tick retry subscriber keeps rolling for any
+// subsequent tick the player lingers in the room.
+const aggroChancePerTick = 0.33
+
func (g *Game) checkAggro(sess *net.Session) {
p := sess.Player
@@ -97,14 +105,25 @@ func (g *Game) checkAggro(sess *net.Session) {
continue
}
aggMob := mob
- g.Ticks.Subscribe(engine.ToTicks(1), func() bool {
+
+ // rollAggro attempts to start combat this instant. It returns true
+ // when aggro should stop retrying that mob for this player — either
+ // because it succeeded, because the mob/player is no longer eligible,
+ // or because the roll failed and the caller subscribed a per-tick
+ // retry. The retry subscriber returns the negation of the inline
+ // call's "stop" so it stays subscribed only while the player is
+ // still a valid target the roll hasn't yet hit.
+ rollAggro := func() (stop bool) {
if g.Combat.Get(p.Name) != nil {
- return false
+ return true
}
if aggMob.HP <= 0 || aggMob.RoomID != p.RoomID {
- return false
+ return true
}
if g.Combat.IsMobInCombat(aggMob.InstanceID) {
+ return true
+ }
+ if rand.Float64() >= aggroChancePerTick {
return false
}
attacker := aggMob.Name
@@ -113,8 +132,13 @@ func (g *Game) checkAggro(sess *net.Session) {
}
sess.WriteLine(fmt.Sprintf("%s attacks you!", g.colorize(sess, "mob", attacker)))
g.startCombat(sess, p, aggMob)
- return false
- })
+ return true
+ }
+
+ if rollAggro() {
+ break
+ }
+ g.Ticks.Subscribe(1, func() bool { return !rollAggro() })
break
}
}