package player import "thirdcollapse/internal/action" import "thirdcollapse/internal/object" type SkillName string const ( Attack SkillName = "attack" Strength SkillName = "strength" Defense SkillName = "defense" Hitpoints SkillName = "hitpoints" Ranged SkillName = "ranged" Science SkillName = "science" Technology SkillName = "technology" Fishing SkillName = "fishing" Cooking SkillName = "cooking" Woodcutting SkillName = "woodcutting" Firemaking SkillName = "firemaking" Mining SkillName = "mining" Smithing SkillName = "smithing" Crafting SkillName = "crafting" Fletching SkillName = "fletching" Alchemy SkillName = "alchemy" Thieving SkillName = "thieving" Agility SkillName = "agility" Construction SkillName = "construction" Scavenging SkillName = "scavenging" Hacking SkillName = "hacking" Assassin SkillName = "assassin" Farming SkillName = "farming" ) var AllSkills = []SkillName{ Attack, Strength, Defense, Hitpoints, Ranged, Science, Technology, Fishing, Cooking, Woodcutting, Firemaking, Mining, Smithing, Crafting, Fletching, Alchemy, Thieving, Agility, Construction, Scavenging, Hacking, Assassin, Farming, } var SkillAbbr = map[SkillName]string{ Attack: "atk", Strength: "str", Defense: "def", Hitpoints: "hp", Ranged: "rng", Science: "sci", Technology: "tec", Fishing: "fis", Cooking: "cok", Woodcutting: "wct", Firemaking: "fmk", Mining: "min", Smithing: "smt", Crafting: "cft", Fletching: "flt", Alchemy: "alc", Thieving: "thv", Agility: "agl", Construction: "con", Scavenging: "scv", Hacking: "hck", Assassin: "asm", Farming: "frm", } type AttackStyle string const ( Accurate AttackStyle = "accurate" Aggressive AttackStyle = "aggressive" Defensive AttackStyle = "defensive" Balanced AttackStyle = "balanced" ) type Skill struct { Name SkillName XP int } func (s Skill) Level() int { return LevelForXP(s.XP) } type InventorySlot struct { ItemID string `yaml:"item_id"` Quantity int `yaml:"quantity"` Quality int `yaml:"quality,omitempty"` MaxQuality int `yaml:"max_quality,omitempty"` } type OptionType int const ( OptBool OptionType = iota OptString OptInt ) type OptionDef struct { Name string Type OptionType Default any ValidValues []string Description string } var OptionDefs = []OptionDef{ {"description", OptBool, true, nil, "Long room descriptions when moving"}, {"tiny_map", OptString, "right", []string{"off", "right", "left"}, "Mini-map display (off/right/left)"}, {"xp_drops", OptBool, true, nil, "XP drop messages"}, {"exits", OptBool, true, nil, "Long exit display in look"}, {"mob_enter", OptBool, true, nil, "Messages when mobs enter the room"}, {"mob_leave", OptBool, true, nil, "Messages when mobs leave the room"}, {"mob_spawn", OptBool, true, nil, "Messages when mobs spawn in the area"}, {"reserve", OptBool, true, nil, "Show full reserved item details"}, {"depletion", OptBool, false, nil, "Show depletion and despawn timers"}, {"despawn", OptBool, false, nil, "Show ground item despawn timers"}, {"color", OptString, "ansi", []string{"none", "ansi", "xterm256"}, "Color output mode"}, {"mapwidth", OptInt, 30, nil, "Map width for the map command"}, {"mapheight", OptInt, 20, nil, "Map height for the map command"}, {"mappadding", OptString, "none", []string{"none", "x", "y", "xy"}, "Map output padding mode"}, {"automap", OptBool, false, nil, "Show map automatically after moving"}, {"queue_silently", OptBool, true, nil, "Suppress messages for queued tick actions"}, {"room_desc_width", OptInt, 70, nil, "Maximum width for room descriptions"}, {"unicode", OptBool, true, nil, "Unicode box-drawing characters"}, {"prompt", OptString, "> ", nil, "Custom command prompt"}, } var optionByName map[string]*OptionDef func init() { optionByName = make(map[string]*OptionDef, len(OptionDefs)) for i := range OptionDefs { optionByName[OptionDefs[i].Name] = &OptionDefs[i] } } func GetOptionDef(name string) *OptionDef { return optionByName[name] } type Player struct { Name string `yaml:"name"` Skills map[SkillName]int `yaml:"skills"` Inventory map[int]*InventorySlot `yaml:"inventory"` Equipment map[object.EquipSlot]string `yaml:"equipment"` EquipQuality map[string]int `yaml:"equip_quality,omitempty"` RoomID int `yaml:"room_id"` HP int `yaml:"hp"` Credits int `yaml:"credits"` Description string `yaml:"description"` AttackStyle AttackStyle `yaml:"attack_style"` Options map[string]any `yaml:"options"` Flags map[string]any `yaml:"flags"` RegenerateTick int Action *action.Action `yaml:"-"` ActionState any `yaml:"-"` WalkSequence []string `yaml:"-"` } func (p *Player) OptionBool(name string) bool { def := GetOptionDef(name) if def == nil || def.Type != OptBool { return false } if p.Options == nil { return def.Default.(bool) } val, ok := p.Options[name] if !ok { return def.Default.(bool) } b, ok := val.(bool) if !ok { return def.Default.(bool) } return b } func (p *Player) OptionString(name string) string { def := GetOptionDef(name) if def == nil || def.Type != OptString { return "" } if p.Options == nil { return def.Default.(string) } val, ok := p.Options[name] if !ok { return def.Default.(string) } s, ok := val.(string) if !ok { return def.Default.(string) } return s } func (p *Player) OptionInt(name string) int { def := GetOptionDef(name) if def == nil || def.Type != OptInt { return 0 } if p.Options == nil { return def.Default.(int) } val, ok := p.Options[name] if !ok { return def.Default.(int) } switch v := val.(type) { case int: return v case int64: return int(v) case float64: return int(v) } return def.Default.(int) } func (p *Player) InvSlot(i int) *InventorySlot { if p.Inventory == nil { return nil } return p.Inventory[i] } func (p *Player) SetInvSlot(i int, slot *InventorySlot) { if p.Inventory == nil { p.Inventory = make(map[int]*InventorySlot) } if slot == nil { delete(p.Inventory, i) } else { p.Inventory[i] = slot } } func (p *Player) FirstFreeSlot() int { for i := 0; i < 28; i++ { if p.Inventory[i] == nil { return i } } return -1 } func (p *Player) FreeSlots() int { used := len(p.Inventory) return 28 - used } func New(name string) *Player { p := &Player{ Name: name, Skills: make(map[SkillName]int), Equipment: make(map[object.EquipSlot]string), AttackStyle: Accurate, Options: make(map[string]any), RoomID: 0, } for _, def := range OptionDefs { p.Options[def.Name] = def.Default } for _, s := range AllSkills { p.Skills[s] = 0 } p.Skills[Hitpoints] = XPForLevel(10) p.HP = p.MaxHP() return p } func (p *Player) Level(s SkillName) int { return LevelForXP(p.Skills[s]) } func (p *Player) AddXP(s SkillName, amount int) { if p.Skills == nil { p.Skills = make(map[SkillName]int) } p.Skills[s] += amount } func (p *Player) AddSkillXP(s SkillName, amount int) int { oldLevel := p.Level(s) p.AddXP(s, amount) newLevel := p.Level(s) if newLevel > oldLevel { return newLevel } return 0 } func (p *Player) CombatLevel() int { base := 0.25 * float64(p.Level(Defense)+p.Level(Hitpoints)+p.Level(Science)) att := float64(p.Level(Attack)) str := float64(p.Level(Strength)) if float64(p.Level(Ranged))*1.5 > att+str { base += 0.375 * float64(p.Level(Ranged)) } else { base += 0.25 * (att + str) } base += 0.125 * float64(p.Level(Technology)) return int(base) } func (p *Player) MaxHP() int { return p.Level(Hitpoints) } func (p *Player) StartRegen() { if p.RegenerateTick == 0 { p.RegenerateTick = 100 } } func (p *Player) HasItem(itemID string) bool { for _, slot := range p.Inventory { if slot != nil && slot.ItemID == itemID && slot.Quantity > 0 { return true } } return false } func (p *Player) RemoveItem(itemID string, qty int) bool { remaining := qty for i, slot := range p.Inventory { if slot == nil || slot.ItemID != itemID { continue } if slot.Quantity <= remaining { remaining -= slot.Quantity delete(p.Inventory, i) } else { slot.Quantity -= remaining remaining = 0 } if remaining <= 0 { return true } } return remaining <= 0 }