diff options
| author | historia <[not public]> | 2026-06-17 02:27:20 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-17 02:27:20 -0400 |
| commit | 731adf2e0fbcac9a32179da077299ed6465af41e (patch) | |
| tree | 29d368917f258350d426f2680bec59feeb464eaf /internal/game | |
| parent | 389e307f205f9b7edf604fb9f86e1038ead736ef (diff) | |
| download | thehouseoficarus-731adf2e0fbcac9a32179da077299ed6465af41e.tar.gz | |
feat: movement overhauled. agility-enhancing items added.
Diffstat (limited to 'internal/game')
| -rw-r--r-- | internal/game/action.go | 1 | ||||
| -rw-r--r-- | internal/game/action_state.go | 2 | ||||
| -rw-r--r-- | internal/game/cmd_attack.go | 9 | ||||
| -rw-r--r-- | internal/game/cmd_move.go | 73 | ||||
| -rw-r--r-- | internal/game/cmd_prompt.go | 9 | ||||
| -rw-r--r-- | internal/game/cmd_remove.go | 48 | ||||
| -rw-r--r-- | internal/game/cmd_walk.go | 7 | ||||
| -rw-r--r-- | internal/game/color.go | 10 | ||||
| -rw-r--r-- | internal/game/game.go | 36 | ||||
| -rw-r--r-- | internal/game/tick.go | 38 |
10 files changed, 207 insertions, 26 deletions
diff --git a/internal/game/action.go b/internal/game/action.go index 04e2b0a..c08295c 100644 --- a/internal/game/action.go +++ b/internal/game/action.go @@ -199,6 +199,7 @@ func (g *Game) CancelAction(p *player.Player) { p.Action = nil p.ActionState = nil p.WalkSequence = nil + p.ClearMoveState() } func (g *Game) AdvanceActions() { diff --git a/internal/game/action_state.go b/internal/game/action_state.go index c5d086e..b28e170 100644 --- a/internal/game/action_state.go +++ b/internal/game/action_state.go @@ -45,7 +45,7 @@ func (a *ActionState) Description() string { case ActionCombating: return "fighting a " + a.TargetName case ActionMoving: - return "walking in from the " + a.Direction + return "heading " + a.Direction case ActionUsing: return "using a " + a.TargetName case ActionTalking: diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go index 359e0ac..d7fb365 100644 --- a/internal/game/cmd_attack.go +++ b/internal/game/cmd_attack.go @@ -272,6 +272,12 @@ func (g *Game) mobAttack(sess *net.Session, p *player.Player, mob *world.MobInst prefix := fmt.Sprintf(" %s hits you for %s damage.", g.colorize(sess, "mob_name", attacker), g.colorize(sess, "damage", fmt.Sprint(dmg))) hpPart := fmt.Sprintf("[%s/%dhp]", g.colorize(sess, "character_hp", fmt.Sprint(p.HP)), p.MaxHP()) sess.WriteLine(fmt.Sprintf("%-*s %s", g.combatPadWidth, prefix, hpPart)) + + if p.MoveTicks > 0 { + p.ClearMoveState() + p.ActionState = &ActionState{Type: ActionCombating, TargetName: mob.Name} + sess.WriteLine("Can't escape!") + } } else { attacker := mob.Name if !mob.Unique { @@ -288,6 +294,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst if p.HP <= 0 { sess.WriteLine(g.colorize(sess, "death", "\nOh dear, you are dead!")) + p.ClearMoveState() g.dropItemsOnDeath(p) p.HP = p.MaxHP() p.RoomID = 1 @@ -476,7 +483,7 @@ func (g *Game) respawnMob(instanceID string) { if p, ok := sess.Player.(*player.Player); ok && p.OptionBool("mob_spawn") { mobLvl := mobCombatLevel(inst) levelStr := g.levelColorize(sess, p.CombatLevel(), mobLvl, fmt.Sprintf("(level %d)", mobLvl)) - sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s %s spawns in the area.", mobDisplayName(inst, false), levelStr))) + sess.WriteLine(fmt.Sprintf("\n%s %s spawns in the area.", g.colorize(sess, "mob_name", mobDisplayName(inst, false)), levelStr)) } } } diff --git a/internal/game/cmd_move.go b/internal/game/cmd_move.go index a140003..2a3e698 100644 --- a/internal/game/cmd_move.go +++ b/internal/game/cmd_move.go @@ -4,7 +4,9 @@ import ( "fmt" "thehouseoficarus/internal/combat" + "thehouseoficarus/internal/engine" "thehouseoficarus/internal/net" + "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" ) @@ -45,17 +47,74 @@ func (g *Game) doMove(sess *net.Session, dir string) { return } - if cs := combat.GetCombat(p.Name); cs != nil && cs.LockedTicks > 0 { - sess.WriteLine(fmt.Sprintf("You are locked in combat for another %d tick%s!", cs.LockedTicks, plural(cs.LockedTicks))) + inCombat := combat.GetCombat(p.Name) != nil + + if !inCombat && p.Action != nil { + g.CancelAction(p) + } + + ticks := g.moveTicks(p) + p.MoveDirection = string(exitDir) + p.MoveTarget = targetID + + if ticks <= 0 { + g.completeMove(sess, p) return } - g.stopCombat(p.Name) + p.MoveTicks = ticks + p.ActionState = &ActionState{Type: ActionMoving, Direction: string(exitDir)} - if p.Action != nil { - g.CancelAction(p) + if inCombat && p.OptionBool("run_countdown") { + if p.MoveTicks > 1 { + sess.WriteLine(fmt.Sprintf("Running in %d ticks...", p.MoveTicks)) + } else { + sess.WriteLine("Running next tick!") + } + } +} + +var gracefulItems = map[string]bool{ + "graceful_hat": true, + "graceful_torso": true, + "graceful_legs": true, + "graceful_gloves": true, + "graceful_boots": true, + "graceful_cape": true, +} + +func (g *Game) moveTicks(p *player.Player) int { + if id, ok := p.Equipment[object.SlotBack]; ok && id == "cape_of_agility" { + return 0 } + count := 0 + for _, id := range p.Equipment { + if gracefulItems[id] { + count++ + } + } + + base := 4.0 - float64(count)*0.25 + if count == 6 { + base -= 0.5 + } + + return engine.ToTicks(base) - 1 +} + +func (g *Game) completeMove(sess *net.Session, p *player.Player) { + exitDir := p.MoveDirection + targetID := p.MoveTarget + + p.ClearMoveState() + + if combat.GetCombat(p.Name) != nil { + g.stopCombat(p.Name) + } + + p.Action = nil + oldRoom := p.RoomID p.RoomID = targetID g.AccountStore.SaveCharacter(p) @@ -78,8 +137,8 @@ func (g *Game) doMove(sess *net.Session, dir string) { } } - sess.WriteLine(fmt.Sprintf("\nYou walk %s.", g.colorize(sess, "direction", string(exitDir)))) - p.ActionState = &ActionState{Type: ActionMoving, Direction: string(exitDir)} + sess.WriteLine(fmt.Sprintf("\nYou walk %s.", g.colorize(sess, "direction", exitDir))) + p.ActionState = &ActionState{Type: ActionMoving, Direction: exitDir} if p.OptionBool("description") { g.doLook(sess) } else { diff --git a/internal/game/cmd_prompt.go b/internal/game/cmd_prompt.go index 81c3409..6cc45f9 100644 --- a/internal/game/cmd_prompt.go +++ b/internal/game/cmd_prompt.go @@ -16,7 +16,7 @@ func (g *Game) doPrompt(sess *net.Session, input string) { prompt = "> " } sess.WriteLine(fmt.Sprintf("\nPrompt: %s", prompt)) - sess.WriteLine("Use 'prompt <value>' to change. Use 'prompt reset' to restore default.") + sess.WriteLine("Use 'prompt <value>' to change. Use 'prompt none' for blank. Use 'prompt reset' to restore default.") return } @@ -27,6 +27,13 @@ func (g *Game) doPrompt(sess *net.Session, input string) { return } + if input == "none" { + p.Prompt = " " + g.AccountStore.SaveCharacter(p) + sess.WriteLine("\nPrompt set to blank.") + return + } + p.Prompt = input g.AccountStore.SaveCharacter(p) sess.WriteLine(fmt.Sprintf("\nPrompt set to: %s", input)) diff --git a/internal/game/cmd_remove.go b/internal/game/cmd_remove.go index 61fdc29..8f1a413 100644 --- a/internal/game/cmd_remove.go +++ b/internal/game/cmd_remove.go @@ -13,6 +13,10 @@ func (g *Game) doRemove(sess *net.Session, input string) { p := sess.Player.(*player.Player) lower := strings.ToLower(strings.TrimSpace(input)) + if lower == "all" { + g.doRemoveAll(sess) + return + } if lower == "" { sess.WriteLine("Remove what?") return @@ -74,3 +78,47 @@ func (g *Game) doRemove(sess *net.Session, input string) { } sess.WriteLine(fmt.Sprintf("You remove %s.", g.itemColorize(sess, def, name))) } + +func (g *Game) doRemoveAll(sess *net.Session) { + p := sess.Player.(*player.Player) + + if len(p.Equipment) == 0 { + sess.WriteLine("You aren't wearing anything.") + return + } + + if len(p.Equipment) > p.FreeSlots() { + sess.WriteLine("You don't have room to remove everything!") + return + } + + g.CancelAction(p) + + for _, slot := range EquipSlots { + itemID, ok := p.Equipment[slot] + if !ok { + continue + } + + freeSlot := p.FirstFreeSlot() + delete(p.Equipment, slot) + invSlot := &player.InventorySlot{ItemID: itemID, Quantity: 1} + if q, ok := p.EquipQuality[itemID]; ok { + invSlot.Quality = q + if def, _ := g.ItemStore.Load(itemID); def != nil { + invSlot.MaxQuality = def.MaxQuality + } + delete(p.EquipQuality, itemID) + } + p.SetInvSlot(freeSlot, invSlot) + + name := itemID + def, _ := g.ItemStore.Load(itemID) + if def != nil { + name = def.Name + } + sess.WriteLine(fmt.Sprintf("You remove %s.", g.itemColorize(sess, def, name))) + } + + g.AccountStore.SaveCharacter(p) +} diff --git a/internal/game/cmd_walk.go b/internal/game/cmd_walk.go index 4ff6187..2104782 100644 --- a/internal/game/cmd_walk.go +++ b/internal/game/cmd_walk.go @@ -92,18 +92,21 @@ func (g *Game) startWalk(sess *net.Session, p *player.Player, dirs []string) { } func (g *Game) advanceWalk(sess *net.Session, p *player.Player) { + if p.MoveTicks > 0 { + return + } if len(p.WalkSequence) == 0 { return } dir := p.WalkSequence[0] - oldRoom := p.RoomID remaining := condensePath(p.WalkSequence[1:]) p.WalkSequence = p.WalkSequence[1:] + oldRoom := p.RoomID g.doMove(sess, dir) - if p.RoomID == oldRoom { + if p.MoveTicks == 0 && p.RoomID == oldRoom { p.WalkSequence = nil p.ActionState = nil return diff --git a/internal/game/color.go b/internal/game/color.go index 4fd8000..c3b3d83 100644 --- a/internal/game/color.go +++ b/internal/game/color.go @@ -46,15 +46,15 @@ func levelColorSpec(myLevel, theirLevel int) color.ColorSpec { diff := theirLevel - myLevel switch { case diff == 0: - return color.Parse("7") + return color.Parse("231") case diff > 0 && diff < 5: - return color.Parse("11") + return color.Parse("208") case diff >= 5: - return color.Parse("9") + return color.Parse("196") case diff < 0 && diff > -5: - return color.Parse("3") + return color.Parse("190") default: - return color.Parse("2") + return color.Parse("34") } } diff --git a/internal/game/game.go b/internal/game/game.go index 9d07ead..5af35be 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -448,7 +448,9 @@ func (g *Game) ProcessQueuedCommands() { case ActionGathering, ActionCombating, ActionUsing, ActionTalking, ActionToggling, ActionBurning, ActionStoking, ActionResting, ActionWalking, ActionCooking: default: - p.ActionState = nil + if as.Type != ActionMoving || p.MoveTicks <= 0 { + p.ActionState = nil + } } } @@ -483,11 +485,14 @@ func (g *Game) ProcessQueuedCommands() { if !ok || p == nil { continue } + if p.MoveTicks > 0 { + p.ClearMoveState() + } wasBusy := p.Action != nil || len(p.WalkSequence) > 0 || combat.GetCombat(p.Name) != nil if len(parts) > 0 { g.executeCommand(qc.Session, parts[0], parts[1:], qc.Command+" "+qc.Args) } - isBusy := p.Action != nil || len(p.WalkSequence) > 0 || combat.GetCombat(p.Name) != nil + isBusy := p.Action != nil || len(p.WalkSequence) > 0 || combat.GetCombat(p.Name) != nil || p.MoveTicks > 0 _, isResting := g.restTimers[p.Name] if !isResting && (wasBusy || !isBusy) { g.writePrompt(qc.Session) @@ -501,7 +506,7 @@ func (g *Game) ProcessQueuedCommands() { continue } g.advanceWalk(sess, p) - if len(p.WalkSequence) == 0 { + if len(p.WalkSequence) == 0 && p.MoveTicks == 0 { g.writePrompt(sess) } } @@ -516,6 +521,27 @@ type pendingDepletion struct { playerNames []string } -func (g *Game) TickCombat() { - combat.TickCombat() +func (g *Game) MoveTick() { + if g.Hub == nil { + return + } + for _, sess := range g.Hub.AllSessions() { + p, ok := sess.Player.(*player.Player) + if !ok || p == nil || p.MoveTicks <= 0 { + continue + } + p.MoveTicks-- + if p.MoveTicks > 0 { + if combat.GetCombat(p.Name) != nil && p.OptionBool("run_countdown") { + if p.MoveTicks > 1 { + sess.WriteLine(fmt.Sprintf("Running in %d ticks...", p.MoveTicks)) + } else { + sess.WriteLine("Running next tick!") + } + } + } else { + g.completeMove(sess, p) + g.writePrompt(sess) + } + } } diff --git a/internal/game/tick.go b/internal/game/tick.go index ab82254..8c40c96 100644 --- a/internal/game/tick.go +++ b/internal/game/tick.go @@ -129,16 +129,18 @@ func (g *Game) WanderTick() { } if p.RoomID == m.fromRoom && p.OptionBool("mob_leave") { if m.level > 0 { - sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s (level %d) leaves.", m.name, m.level))) + levelStr := g.levelColorize(sess, p.CombatLevel(), m.level, fmt.Sprintf("(level %d)", m.level)) + sess.WriteLine(fmt.Sprintf("\n%s %s leaves.", g.colorize(sess, "mob_name", m.name), levelStr)) } else { - sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s moves away.", m.name))) + sess.WriteLine(fmt.Sprintf("\n%s moves away.", g.colorize(sess, "mob_name", m.name))) } } if p.RoomID == m.toRoom && p.OptionBool("mob_enter") { if m.level > 0 { - sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s (level %d) enters.", m.name, m.level))) + levelStr := g.levelColorize(sess, p.CombatLevel(), m.level, fmt.Sprintf("(level %d)", m.level)) + sess.WriteLine(fmt.Sprintf("\n%s %s enters.", g.colorize(sess, "mob_name", m.name), levelStr)) } else { - sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s drifts in.", m.name))) + sess.WriteLine(fmt.Sprintf("\n%s drifts in.", g.colorize(sess, "mob_name", m.name))) } } } @@ -213,3 +215,31 @@ func (g *Game) ConsumeTick() { } } } + +func (g *Game) VisualTick() { + if g.Hub == nil { + return + } + for _, sess := range g.Hub.AllSessions() { + p, ok := sess.Player.(*player.Player) + if !ok || p == nil || !p.OptionBool("visual_ticks") { + continue + } + text := p.OptionString("visual_tick_text") + count := p.OptionInt("visual_tick_count") + if count <= 0 { + sess.WriteLine(g.colorize(sess, "visual_tick", text)) + } else { + p.VisualTickCurrent++ + if p.VisualTickCurrent > count { + p.VisualTickCurrent = 1 + } + msg := fmt.Sprintf("%s %d", text, p.VisualTickCurrent) + if p.VisualTickCurrent == 1 { + sess.WriteLine(g.colorize(sess, "visual_first_tick", msg)) + } else { + sess.WriteLine(g.colorize(sess, "visual_tick", msg)) + } + } + } +} |
