package game import ( "fmt" "math/rand" "thehouseoficarus/internal/combat" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) func (g *Game) DisconnectTick() { if g.Hub == nil { return } for _, sess := range g.Hub.AllSessions() { if !sess.Disconnecting { continue } p, ok := sess.Player.(*player.Player) if !ok { g.Hub.Remove(sess) continue } if combat.GetCombat(p.Name) != nil { continue } sess.DisconnectTicks-- if sess.DisconnectTicks <= 0 { g.AccountStore.SaveCharacter(p) g.Hub.HardRemove(sess) } } } func (g *Game) RegenTick() { if g.Hub == nil { return } for _, sess := range g.Hub.AllSessions() { p, ok := sess.Player.(*player.Player) if !ok { continue } if p.HP <= 0 || p.HP >= p.MaxHP() { p.RegenerateTick = 0 continue } p.RegenerateTick-- if p.RegenerateTick <= 0 { p.HP++ if p.HP >= p.MaxHP() { p.HP = p.MaxHP() p.RegenerateTick = 0 } else { p.RegenerateTick = 100 } } } } func (g *Game) WanderTick() { if g.Hub == nil { return } type moveEvent struct { name string fromRoom int toRoom int level int } var moves []moveEvent // Mob wandering — exit-based for _, inst := range g.MobStore.AllInstances() { if inst.HP <= 0 || inst.WanderInterval <= 0 { continue } if combat.IsMobInCombat(inst.InstanceID) { continue } inst.WanderTickCounter++ if float64(inst.WanderTickCounter) < inst.WanderInterval { continue } inst.WanderTickCounter = 0 legal := g.legalMobExits(inst) if len(legal) == 0 { continue } toRoom := legal[rand.Intn(len(legal))] moves = append(moves, moveEvent{ name: mobDisplayName(inst, false), fromRoom: inst.RoomID, toRoom: toRoom, level: mobCombatLevel(inst), }) inst.RoomID = toRoom } // Object wandering (fishing spots, etc.) g.World.TickObjWander() objMoves := g.World.FlushObjMoves() for _, om := range objMoves { for _, sess := range g.Hub.PlayersInRoom(om.FromRoom) { if p, ok := sess.Player.(*player.Player); ok && p.Action != nil { if p.Action.TargetID == om.DefID { g.CancelAction(p) g.writePrompt(sess) } } } moves = append(moves, moveEvent{ name: "a " + om.Name, fromRoom: om.FromRoom, toRoom: om.ToRoom, level: 0, }) } for _, m := range moves { for _, sess := range g.Hub.AllSessions() { p, ok := sess.Player.(*player.Player) if !ok { continue } if p.RoomID == m.fromRoom && p.OptionBool("mob_leave") { if m.level > 0 { 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(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 { 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(fmt.Sprintf("\n%s drifts in.", g.colorize(sess, "mob_name", m.name))) } } } } } func (g *Game) SharedDepletionTick() { activeKeys := make(map[string]bool) for _, sess := range g.Hub.AllSessions() { p, ok := sess.Player.(*player.Player) if !ok || p == nil || p.Action == nil || p.Action.Type != "gather" { continue } if key, ok := p.Action.Data["instance_key"].(string); ok { activeKeys[key] = true } } all := g.World.AllSharedObjStates() for _, st := range all { if st.Depleted { continue } key := g.World.ObjStateKey(st.RoomID, st.DefID, st.Index) if activeKeys[key] { if st.SharedTimer > 0 { st.SharedTimer-- } } else { if st.SharedTimer < int(st.SharedMax) { st.SharedTimer++ } } } } func (g *Game) legalMobExits(inst *world.MobInstance) []int { room, err := g.World.LoadRoom(inst.RoomID) if err != nil || len(room.Exits) == 0 { return nil } allowed := make(map[int]bool) if len(inst.WanderRooms) > 0 { for _, r := range inst.WanderRooms { allowed[r] = true } } var legal []int for _, exitDef := range room.Exits { if exitDef.Condition != nil { continue } if len(allowed) > 0 && !allowed[exitDef.Room] { continue } legal = append(legal, exitDef.Room) } return legal } func (g *Game) ConsumeTick() { if g.Hub == nil { return } for _, sess := range g.Hub.AllSessions() { p, ok := sess.Player.(*player.Player) if !ok || p == nil { continue } if p.ConsumeCooldown > 0 { p.ConsumeCooldown-- } } } 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)) } } } }