From c14c2e403c75a913e1a6d962fb6fe64f013adae5 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Wed, 1 Jul 2026 22:30:31 -0400 Subject: fix: combat formulas and mob attack types (mobs switch attack styles if safespotting) --- internal/world/mob.go | 268 +++++++++++++++++++++++++++------------------- internal/world/room.go | 36 +++---- internal/world/trigger.go | 8 +- internal/world/world.go | 8 +- 4 files changed, 186 insertions(+), 134 deletions(-) (limited to 'internal/world') diff --git a/internal/world/mob.go b/internal/world/mob.go index 19ec935..05650c0 100644 --- a/internal/world/mob.go +++ b/internal/world/mob.go @@ -40,47 +40,51 @@ type MobCombatBonuses struct { } type MobCombatDefenses struct { - StabDefense int `yaml:"stab_defense"` - SlashDefense int `yaml:"slash_defense"` - CrushDefense int `yaml:"crush_defense"` - ScienceDefense int `yaml:"science_defense"` - RangedDefense int `yaml:"ranged_defense"` - Weakness string `yaml:"weakness"` + StabDefense int `yaml:"stab_defense"` + SlashDefense int `yaml:"slash_defense"` + CrushDefense int `yaml:"crush_defense"` + ScienceDefense int `yaml:"science_defense"` + RangedDefense int `yaml:"ranged_defense"` + Weakness string `yaml:"weakness"` + WeaknessPercent int `yaml:"weakness_percent"` } type MobCombatStats struct { - HP int `yaml:"hp"` - Attack int `yaml:"attack"` - Strength int `yaml:"strength"` - Defense int `yaml:"defense"` - Ranged int `yaml:"ranged"` - Science int `yaml:"science"` - AttackType string `yaml:"attack_type"` - Speed float64 `yaml:"speed"` - Aggressive bool `yaml:"aggressive"` - RespawnTicks float64 `yaml:"respawn_ticks"` - Bonuses MobCombatBonuses `yaml:"bonuses"` - Defenses MobCombatDefenses `yaml:"defenses"` + HP int `yaml:"hp"` + Attack int `yaml:"attack"` + Strength int `yaml:"strength"` + Defense int `yaml:"defense"` + Ranged int `yaml:"ranged"` + Science int `yaml:"science"` + Speed float64 `yaml:"speed"` + MaxMeleeHit int `yaml:"max_melee_hit"` + MaxRangedHit int `yaml:"max_ranged_hit"` + MaxScienceHit int `yaml:"max_science_hit"` + Bonuses MobCombatBonuses `yaml:"bonuses"` + Defenses MobCombatDefenses `yaml:"defenses"` } type MobCombat struct { - Kind string `yaml:"kind"` - Stats MobCombatStats `yaml:"stats"` - AssassinLevel int `yaml:"assassin_level"` - FinishingBlow string `yaml:"finishing_blow"` - DamageWithout string `yaml:"damage_without"` - Size string `yaml:"size"` - CombatDescriptions []string `yaml:"combat_descriptions"` + Kind string `yaml:"kind"` + AttackTypes []string `yaml:"attack_types"` + Aggressive bool `yaml:"aggressive"` + RespawnTicks float64 `yaml:"respawn_ticks"` + Stats MobCombatStats `yaml:"stats"` + AssassinLevel int `yaml:"assassin_level"` + FinishingBlow string `yaml:"finishing_blow"` + DamageWithout string `yaml:"damage_without"` + Size string `yaml:"size"` + CombatDescriptions []string `yaml:"combat_descriptions"` } type MobDef struct { - ID string `yaml:"id"` - Name string `yaml:"name"` - Description string `yaml:"description"` - IdleDescriptions []string `yaml:"idle_descriptions"` - Protected bool `yaml:"protected"` - Unique bool `yaml:"unique"` - Drops MobDropTable `yaml:"drops"` + ID string `yaml:"id"` + Name string `yaml:"name"` + Description string `yaml:"description"` + IdleDescriptions []string `yaml:"idle_descriptions"` + Protected bool `yaml:"protected"` + Unique bool `yaml:"unique"` + Drops MobDropTable `yaml:"drops"` Steal *MobSteal `yaml:"steal,omitempty"` Task *MobTask `yaml:"task,omitempty"` @@ -98,35 +102,39 @@ func (d *MobDef) HasShop() bool { return d.Shop != nil } func (d *MobDef) IsTask() bool { return d.Combat != nil && d.Combat.Kind == "task" } type MobInstance struct { - InstanceID string - DefID string - Name string - Description string - HP int - MaxHP int - Attack int - Strength int - Defense int - Ranged int - Science int - Speed float64 - Aggressive bool - Protected bool - Unique bool - RespawnTicks float64 - RoomID int - HomeRoomID int - Drops MobDropTable - IdleDescription string - CombatDescriptions []string - WanderRooms []int - WanderInterval float64 - WanderTickCounter int - regenerateTick int + InstanceID string + DefID string + Name string + Description string + HP int + MaxHP int + Attack int + Strength int + Defense int + Ranged int + Science int + Speed float64 + Aggressive bool + Protected bool + Unique bool + RespawnTicks float64 + RoomID int + HomeRoomID int + Drops MobDropTable + IdleDescription string + CombatDescriptions []string + WanderRooms []int + WanderInterval float64 + WanderTickCounter int + regenerateTick int AttackBonus int StrengthBonus int - AttackType string + AttackTypes []string + + RangedBonus int + ScienceBonus int + SciencePercentBonus int StabDefense int SlashDefense int @@ -134,11 +142,16 @@ type MobInstance struct { ScienceDefense int RangedDefense int - Weakness string - StealTable string - StealLevel int - StealXP int - StealSpeed float64 + Weakness string + WeaknessPercent int + + MaxMeleeHit int + MaxRangedHit int + MaxScienceHit int + StealTable string + StealLevel int + StealXP int + StealSpeed float64 AssassinLevel int FinishingBlow string @@ -186,13 +199,20 @@ func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []in respawnTicks := 0.0 attackBonus := 0 strengthBonus := 0 - attackType := "" + rangedBonus := 0 + scienceBonus := 0 + sciencePercentBonus := 0 + var attackType []string stabDef := 0 slashDef := 0 crushDef := 0 scienceDef := 0 rangedDef := 0 weakness := "" + weaknessPercent := 0 + maxMeleeHit := 0 + maxRangedHit := 0 + maxScienceHit := 0 assassinLevel := 0 finishingBlow := "" damageWithout := "" @@ -208,6 +228,10 @@ func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []in stealSpeed := 0.0 if def.Combat != nil { + aggressive = def.Combat.Aggressive + respawnTicks = def.Combat.RespawnTicks + attackType = append([]string(nil), def.Combat.AttackTypes...) + cs := def.Combat.Stats hp = cs.HP attack = cs.Attack @@ -216,17 +240,21 @@ func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []in ranged = cs.Ranged science = cs.Science speed = cs.Speed - aggressive = cs.Aggressive - respawnTicks = cs.RespawnTicks attackBonus = cs.Bonuses.AttackBonus strengthBonus = cs.Bonuses.StrengthBonus - attackType = cs.AttackType + rangedBonus = cs.Bonuses.RangedBonus + scienceBonus = cs.Bonuses.ScienceBonus + sciencePercentBonus = cs.Bonuses.SciencePercentBonus stabDef = cs.Defenses.StabDefense slashDef = cs.Defenses.SlashDefense crushDef = cs.Defenses.CrushDefense scienceDef = cs.Defenses.ScienceDefense rangedDef = cs.Defenses.RangedDefense weakness = cs.Defenses.Weakness + weaknessPercent = cs.Defenses.WeaknessPercent + maxMeleeHit = cs.MaxMeleeHit + maxRangedHit = cs.MaxRangedHit + maxScienceHit = cs.MaxScienceHit assassinLevel = def.Combat.AssassinLevel finishingBlow = def.Combat.FinishingBlow @@ -253,50 +281,57 @@ func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []in hp = 1 } inst := &MobInstance{ - InstanceID: instanceID, - DefID: def.ID, - Name: def.Name, - Description: def.Description, - HP: hp, - MaxHP: hp, - Attack: attack, - Strength: strength, - Defense: defense, - Ranged: ranged, - Science: science, - Speed: speed, - Aggressive: aggressive, - Protected: def.Protected, - Unique: def.Unique, - RespawnTicks: respawnTicks, - RoomID: roomID, - HomeRoomID: roomID, - WanderRooms: wanderRooms, - WanderInterval: wanderInterval, - Drops: def.Drops, - AttackBonus: attackBonus, - StrengthBonus: strengthBonus, - AttackType: attackType, - StabDefense: stabDef, - SlashDefense: slashDef, - CrushDefense: crushDef, - ScienceDefense: scienceDef, - RangedDefense: rangedDef, - Weakness: weakness, - StealTable: stealTable, - StealLevel: stealLevel, - StealXP: stealXP, - StealSpeed: stealSpeed, - AssassinLevel: assassinLevel, - FinishingBlow: finishingBlow, - DamageWithout: damageWithout, - Size: size, - Kind: kind, - Verb: verb, - ProgressNoun: progressNoun, - CompleteMessage: completeMessage, - CombatDescriptions: combatDescriptions, - TalkConfig: def.Talk, + InstanceID: instanceID, + DefID: def.ID, + Name: def.Name, + Description: def.Description, + HP: hp, + MaxHP: hp, + Attack: attack, + Strength: strength, + Defense: defense, + Ranged: ranged, + Science: science, + Speed: speed, + Aggressive: aggressive, + Protected: def.Protected, + Unique: def.Unique, + RespawnTicks: respawnTicks, + RoomID: roomID, + HomeRoomID: roomID, + WanderRooms: wanderRooms, + WanderInterval: wanderInterval, + Drops: def.Drops, + AttackBonus: attackBonus, + StrengthBonus: strengthBonus, + AttackTypes: attackType, + RangedBonus: rangedBonus, + ScienceBonus: scienceBonus, + SciencePercentBonus: sciencePercentBonus, + StabDefense: stabDef, + SlashDefense: slashDef, + CrushDefense: crushDef, + ScienceDefense: scienceDef, + RangedDefense: rangedDef, + Weakness: weakness, + WeaknessPercent: weaknessPercent, + MaxMeleeHit: maxMeleeHit, + MaxRangedHit: maxRangedHit, + MaxScienceHit: maxScienceHit, + StealTable: stealTable, + StealLevel: stealLevel, + StealXP: stealXP, + StealSpeed: stealSpeed, + AssassinLevel: assassinLevel, + FinishingBlow: finishingBlow, + DamageWithout: damageWithout, + Size: size, + Kind: kind, + Verb: verb, + ProgressNoun: progressNoun, + CompleteMessage: completeMessage, + CombatDescriptions: combatDescriptions, + TalkConfig: def.Talk, } if def.Shop != nil { inst.Shop = def.Shop @@ -434,6 +469,17 @@ func (s *MobStore) AllInstances() []*MobInstance { return out } +// SetInstanceRoom updates a mob instance's room under the store lock, so it is +// safe against concurrent readers (MobsInRoom, RemoveMobsInRoom) in other +// goroutines. +func (s *MobStore) SetInstanceRoom(instanceID string, roomID int) { + s.mu.Lock() + defer s.mu.Unlock() + if inst := s.instances[instanceID]; inst != nil { + inst.RoomID = roomID + } +} + func (s *MobStore) MobsInRoom(roomID int) []*MobInstance { s.mu.Lock() defer s.mu.Unlock() diff --git a/internal/world/room.go b/internal/world/room.go index 7cc1f08..9c2e52f 100644 --- a/internal/world/room.go +++ b/internal/world/room.go @@ -202,24 +202,24 @@ func (ro *RoomObject) UnmarshalYAML(value *yaml.Node) error { func (ro RoomObject) MarshalYAML() (interface{}, error) { if ro.Local != nil { type inlineObj struct { - Name string `yaml:"name"` - Aliases []string `yaml:"aliases,omitempty"` - Color string `yaml:"color,omitempty"` - Hidden bool `yaml:"hidden,omitempty"` - InRoomDescription string `yaml:"inroom_description,omitempty"` - RemovalItem string `yaml:"removal_item,omitempty"` - Description behavior.DescList `yaml:"description,omitempty"` - UseInteractions []object.UseInteraction `yaml:"use_interactions,omitempty"` - StealTable string `yaml:"steal_table,omitempty"` - StealLevel int `yaml:"steal_level,omitempty"` - StealXP int `yaml:"steal_xp,omitempty"` - StealSpeed float64 `yaml:"steal_speed,omitempty"` - GuardMob string `yaml:"guard_mob,omitempty"` - Gather *behavior.GatherConfig `yaml:"gather,omitempty"` - Talk *behavior.TalkConfig `yaml:"talk,omitempty"` - Use *behavior.UseConfig `yaml:"use,omitempty"` - Safespot *object.SafespotConfig `yaml:"safespot,omitempty"` - OnLook *behavior.NodeAction `yaml:"on_look,omitempty"` + Name string `yaml:"name"` + Aliases []string `yaml:"aliases,omitempty"` + Color string `yaml:"color,omitempty"` + Hidden bool `yaml:"hidden,omitempty"` + InRoomDescription string `yaml:"inroom_description,omitempty"` + RemovalItem string `yaml:"removal_item,omitempty"` + Description behavior.DescList `yaml:"description,omitempty"` + UseInteractions []object.UseInteraction `yaml:"use_interactions,omitempty"` + StealTable string `yaml:"steal_table,omitempty"` + StealLevel int `yaml:"steal_level,omitempty"` + StealXP int `yaml:"steal_xp,omitempty"` + StealSpeed float64 `yaml:"steal_speed,omitempty"` + GuardMob string `yaml:"guard_mob,omitempty"` + Gather *behavior.GatherConfig `yaml:"gather,omitempty"` + Talk *behavior.TalkConfig `yaml:"talk,omitempty"` + Use *behavior.UseConfig `yaml:"use,omitempty"` + Safespot *object.SafespotConfig `yaml:"safespot,omitempty"` + OnLook *behavior.NodeAction `yaml:"on_look,omitempty"` } def := ro.Local return inlineObj{ diff --git a/internal/world/trigger.go b/internal/world/trigger.go index 7c06be4..8e74ff7 100644 --- a/internal/world/trigger.go +++ b/internal/world/trigger.go @@ -33,10 +33,10 @@ type TriggerStep struct { } type SpawnMobConfig struct { - ID string `yaml:"id"` - OwnerOnly bool `yaml:"owner_only"` - DespawnOnLeave bool `yaml:"despawn_on_leave"` - DespawnRooms []int `yaml:"despawn_rooms"` + ID string `yaml:"id"` + OwnerOnly bool `yaml:"owner_only"` + DespawnOnLeave bool `yaml:"despawn_on_leave"` + DespawnRooms []int `yaml:"despawn_rooms"` DespawnTicks float64 `yaml:"despawn_ticks"` } diff --git a/internal/world/world.go b/internal/world/world.go index 3dfa99b..b582faf 100644 --- a/internal/world/world.go +++ b/internal/world/world.go @@ -37,7 +37,9 @@ func New(dataDir string) *World { } func (w *World) LoadRoom(id int) (*Room, error) { + w.mu.Lock() path, ok := w.roomPathIndex[id] + w.mu.Unlock() if !ok { return nil, fmt.Errorf("read room %d: no such room", id) } @@ -119,7 +121,9 @@ func (w *World) SeedGroundItems(roomID int) { } func (w *World) RoomIndex() map[int]bool { - ids := make(map[int]bool) + w.mu.Lock() + defer w.mu.Unlock() + ids := make(map[int]bool, len(w.roomPathIndex)) for id := range w.roomPathIndex { ids[id] = true } @@ -139,6 +143,8 @@ func (w *World) AddRoomPath(id int, path string) { } func (w *World) GetRoomPath(id int) (string, bool) { + w.mu.Lock() + defer w.mu.Unlock() path, ok := w.roomPathIndex[id] return path, ok } -- cgit v1.2.3