aboutsummaryrefslogtreecommitdiff
path: root/internal/game/act_effects.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/act_effects.go')
-rw-r--r--internal/game/act_effects.go75
1 files changed, 63 insertions, 12 deletions
diff --git a/internal/game/act_effects.go b/internal/game/act_effects.go
index cb60c5d..cadd05e 100644
--- a/internal/game/act_effects.go
+++ b/internal/game/act_effects.go
@@ -44,12 +44,14 @@ const (
// 5. set_player_flags (player scope; cascades via setPlayerFlag)
// 6. take_item (player scope)
// 7. give_item (player scope; honors 28-slot inventory limit)
-// 8. heal (player scope; clamps to MaxHP)
-// 9. credits (player scope; negative is gated by affordability)
-// 10. spawn_mob (player scope: player-owned; global scope: world-owned)
-// 11. despawn_mob (player scope: owner-filtered; global scope: all)
-// 12. teleport (player scope; re-runs look + on_enter of target)
-// 13. aps_node (player scope; marks "aps_node_<roomID>" player flag)
+// 8. drop_table (player scope; resolves weighted drops into inventory,
+// overflow to ground; supports credits pseudo-item)
+// 9. heal (player scope; clamps to MaxHP)
+// 10. credits (player scope; negative is gated by affordability)
+// 11. spawn_mob (player scope: player-owned; global scope: world-owned)
+// 12. despawn_mob (player scope: owner-filtered; global scope: all)
+// 13. teleport (player scope; re-runs look + on_enter of target)
+// 14. aps_node (player scope; marks "aps_node_<roomID>" player flag)
func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Step, roomID int, sc effectScope, flagValue any) {
if step == nil {
return
@@ -97,7 +99,7 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste
}
if sc == scopeGlobal {
- // 10/11. world-owned spawn/despawn.
+ // 11/12. world-owned spawn/despawn.
if step.SpawnMob != nil {
g.spawnWorldTriggerMob(step.SpawnMob, roomID)
}
@@ -138,7 +140,17 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste
}
}
- // 8. heal
+ // 8. drop_table — weighted loot into inventory, overflow to ground.
+ if len(step.DropTable) > 0 {
+ for _, d := range behavior.ResolveDropList(g.DataDir, step.DropTable) {
+ if d.ItemID == "" {
+ continue
+ }
+ g.giveTriggerDrop(sess, p, &d)
+ }
+ }
+
+ // 9. heal
if step.Heal > 0 {
p.HP += step.Heal
if maxHP := p.MaxHP(); p.HP > maxHP {
@@ -150,7 +162,7 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste
}
}
- // 9. credits (signed; negative is gated).
+ // 10. credits (signed; negative is gated).
if step.Credits != 0 {
if step.Credits < 0 {
if p.Credits >= -step.Credits {
@@ -165,7 +177,7 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste
}
}
- // 10/11. spawn_mob / despawn_mob (player-owned / owner-filtered).
+ // 11/12. spawn_mob / despawn_mob (player-owned / owner-filtered).
if step.SpawnMob != nil {
g.spawnTriggerMob(sess, p, step.SpawnMob, roomID)
}
@@ -173,12 +185,12 @@ func (g *Game) applyStep(sess *net.Session, p *player.Player, step *behavior.Ste
g.despawnTriggerMobs(step.DespawnMob, p.Name)
}
- // 12. teleport
+ // 13. teleport
if step.Teleport > 0 && sess != nil {
g.teleportPlayer(sess, p, step.Teleport)
}
- // 13. aps_node — dynamic room-id flag, kept as a dedicated effect.
+ // 14. aps_node — dynamic room-id flag, kept as a dedicated effect.
if step.ApsNode {
g.setPlayerFlag(p, "aps_node_"+strconv.Itoa(p.RoomID), true)
g.AccountStore.SaveCharacter(p)
@@ -200,6 +212,45 @@ func (g *Game) writePlayerMessage(sess *net.Session, msg string, playerName stri
sess.WriteLine(rendered)
}
+// giveTriggerDrop awards a resolved DropEntry to a player. Item goes to the
+// first free inventory slot; if inventory is full it falls to the ground
+// (reserved for the player). "credits" pseudo-item adds credits directly.
+func (g *Game) giveTriggerDrop(sess *net.Session, p *player.Player, drop *behavior.DropEntry) {
+ if p == nil {
+ return
+ }
+ qty := drop.Quantity
+ if qty <= 0 {
+ qty = 1
+ }
+
+ if drop.ItemID == "credits" {
+ p.Credits += qty
+ if sess != nil {
+ sess.WriteLine(fmt.Sprintf("You receive %d credits.", qty))
+ }
+ g.AccountStore.SaveCharacter(p)
+ return
+ }
+
+ name := drop.ItemID
+ lootDef, _ := g.ItemStore.Load(drop.ItemID)
+ if lootDef != nil && lootDef.Name != "" {
+ name = lootDef.Name
+ }
+
+ freeSlot := p.FirstFreeSlot()
+ if freeSlot == -1 {
+ g.World.AddReservedItem(p.RoomID, drop.ItemID, qty, p.Name)
+ sess.WriteLine(fmt.Sprintf("You receive %s. It falls to the ground.", g.itemColorize(sess, lootDef, name)))
+ return
+ }
+
+ p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: drop.ItemID, Quantity: qty})
+ sess.WriteLine(fmt.Sprintf("You receive %s.", g.itemColorize(sess, lootDef, name)))
+ g.AccountStore.SaveCharacter(p)
+}
+
// applyTalkStep fires a talk node/option's Action (a single Step)
// synchronously at scopePlayer, scoped to the player's current room.
func (g *Game) applyTalkStep(sess *net.Session, step *behavior.Step) {