package game import ( "fmt" "sort" "strconv" "strings" "thehouseoficarus/internal/action" "thehouseoficarus/internal/combat" "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) var verbAliases = map[string]string{ "mine": "gather", "chop": "gather", "fish": "gather", "cut": "gather", "shear": "gather", "talk": "talk", "speak": "talk", "ask": "talk", "steal": "steal", "thieve": "steal", "hide": "safespot", } var verbSkill = map[string]string{ "mine": "mining", "chop": "woodcutting", "cut": "woodcutting", "fish": "fishing", "shear": "crafting", "steal": "thieving", "thieve": "thieving", } func normalizeVerb(v string) string { if a, ok := verbAliases[v]; ok { return a } return v } func (g *Game) startAction(sess *net.Session, verb, target string) { p := sess.Player verb = normalizeVerb(verb) if combat.GetCombat(p.Name) != nil { sess.WriteLine("You can't do that during combat!") return } if p.Action != nil { g.cancelAction(p) } g.cancelBgAction(p) lower := strings.ToLower(target) instanceIdx := -1 if dotPos := strings.Index(lower, "."); dotPos > 0 { if n, err := strconv.Atoi(lower[:dotPos]); err == nil && n > 0 { instanceIdx = n - 1 lower = lower[dotPos+1:] } } instances := g.World.FindObjInstances(p.RoomID, lower) sort.Slice(instances, func(i, j int) bool { return instances[i].Index < instances[j].Index }) var obj *object.ObjectDef var mob *world.MobInstance var chosen *world.ObjState if len(instances) > 0 { uniqueDefs := make(map[string]bool) for _, ist := range instances { uniqueDefs[ist.DefID] = true } if len(uniqueDefs) > 1 { sess.WriteLine("That's ambiguous, which one?") return } if instanceIdx >= 0 && instanceIdx < len(instances) { chosen = &instances[instanceIdx] } else { for i := range instances { if !instances[i].Depleted { chosen = &instances[i] break } } if chosen == nil { chosen = &instances[0] } } if chosen == nil { sess.WriteLine(fmt.Sprintf("There's nothing here to %s.", verb)) return } var err error obj, err = g.ObjectStore.Load(chosen.DefID) if err != nil || !obj.IsInteractable() { sess.WriteLine(fmt.Sprintf("You can't %s the %s.", verb, obj.Name)) return } } else { mobs := g.MobStore.MobsInRoom(p.RoomID) var candidates []*world.MobInstance for _, m := range mobs { if !m.IsTalkable() { continue } if q := m.MatchQuality(lower); q >= world.MatchPrefix { candidates = append(candidates, m) } } if len(candidates) == 0 { sess.WriteLine(fmt.Sprintf("There's nothing here to %s.", verb)) return } sort.Slice(candidates, func(i, j int) bool { return candidates[i].InstanceID < candidates[j].InstanceID }) if instanceIdx >= 0 && instanceIdx < len(candidates) { mob = candidates[instanceIdx] } else if len(candidates) == 1 { mob = candidates[0] } else { sess.WriteLine("Which one?") return } } if obj != nil && obj.ID == "estate_directory" { g.startEstateDirectory(sess, verb) return } bhType := "" if obj != nil { bhType = obj.BehaviorType() } else if mob != nil { if mob.IsTalkable() { bhType = "talk" } } if bhType != verb { if obj != nil { sess.WriteLine(fmt.Sprintf("You can't %s the %s.", verb, obj.Name)) } else { sess.WriteLine(fmt.Sprintf("You can't %s that.", verb)) } return } switch bhType { case "gather": if obj == nil || chosen == nil { sess.WriteLine(fmt.Sprintf("You can't %s that.", verb)) return } g.startGather(sess, p, obj, chosen) case "talk": if mob != nil { g.startMobTalk(sess, p, mob) } else { g.startTalk(sess, p, obj) } case "use": g.startUse(sess, p, obj) default: sess.WriteLine(fmt.Sprintf("You can't %s that.", verb)) } } func (g *Game) cancelAction(p *player.Player) { p.Action = nil p.ActionState = nil p.WalkSequence = nil p.ClearMoveState() } func (g *Game) cancelBgAction(p *player.Player) { p.BackgroundAction = nil p.BackgroundActionState = nil } func (g *Game) AdvanceActions() { if g.Hub == nil { return } for _, sess := range g.Hub.AllSessions() { p := sess.Player if p == nil || p.Action == nil { continue } if !p.Action.Advance() { continue } switch p.Action.Type { case "gather": g.advanceGather(sess, p) case "use": g.advanceUse(sess, p) case "burn": g.advanceBurn(sess, p) case "stoke": g.advanceStoke(sess, p) case "search": g.advanceSearch(sess, p) case "identify": g.advanceIdentify(sess, p) case "steal": g.advanceSteal(sess, p) case "plant": g.advancePlant(sess, p) case "harvest": g.advanceHarvest(sess, p) case "rake": g.advanceRake(sess, p) case "water": g.advanceWater(sess, p) case "cure": g.advanceCure(sess, p) case "obstacle": g.advanceObstacle(sess, p) default: if productionActionTypes[p.Action.Type] { g.advanceProduction(sess, p) } } if p.Action == nil { g.writePrompt(sess) } } for _, sess := range g.Hub.AllSessions() { p := sess.Player if p == nil || p.BackgroundAction == nil { continue } if !p.BackgroundAction.Advance() { continue } switch p.BackgroundAction.Type { case "fletch": g.advanceFletch(sess, p) case "clean": g.advanceClean(sess, p) } if p.BackgroundAction == nil { g.writePrompt(sess) } } } func (g *Game) checkCondition(sess *net.Session, c *action.Condition) bool { p := sess.Player if c.AllOf != nil { for _, sub := range c.AllOf { if !g.checkCondition(sess, &sub) { return false } } return true } if c.AnyOf != nil { for _, sub := range c.AnyOf { if g.checkCondition(sess, &sub) { return true } } return false } if c.PlayerFlag != "" { if p == nil || p.Flags == nil { return c.Not } val, ok := p.Flags[c.PlayerFlag] if c.Not { return !ok || val != c.Value } return ok && val == c.Value } if c.Flag != "" { val, ok := g.WorldFlags[c.Flag] if c.Not { return !ok || val != c.Value } return ok && val == c.Value } if c.HasItem != "" { has := p != nil && p.HasItem(c.HasItem) if c.Not { return !has } return has } if c.MinCredits > 0 { has := p != nil && p.Credits >= c.MinCredits if c.Not { return !has } return has } return true }