diff options
Diffstat (limited to 'internal/game/act_effects.go')
| -rw-r--r-- | internal/game/act_effects.go | 75 |
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) { |
