Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions internal/buffs/buffs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package buffs

import (
"maps"
"slices"

"github.com/GoMudEngine/GoMud/internal/mudlog"
)

Expand Down Expand Up @@ -48,6 +51,28 @@ func New() Buffs {
}
}

func (bs Buffs) Clone() Buffs {
cloned := Buffs{
List: make([]*Buff, len(bs.List)),
buffFlags: make(map[Flag][]int, len(bs.buffFlags)),
buffIds: maps.Clone(bs.buffIds),
}

for i, buff := range bs.List {
if buff == nil {
continue
}
buffCopy := *buff
cloned.List[i] = &buffCopy
}

for flag, indexes := range bs.buffFlags {
cloned.buffFlags[flag] = slices.Clone(indexes)
}

return cloned
}

func (bs *Buffs) Validate(forceRebuild ...bool) {
if bs.buffFlags == nil {
bs.buffFlags = make(map[Flag][]int)
Expand Down
56 changes: 56 additions & 0 deletions internal/characters/character.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,62 @@ func New() *Character {
}
}

func (c *Character) Clone() *Character {
if c == nil {
return nil
}

cloned := *c
cloned.Adjectives = slices.Clone(c.Adjectives)
cloned.Shop = c.Shop.Clone()
cloned.SpellBook = maps.Clone(c.SpellBook)
cloned.CharmedMobs = slices.Clone(c.CharmedMobs)
cloned.Items = cloneItems(c.Items)
cloned.Buffs = c.Buffs.Clone()
cloned.Equipment = c.Equipment.Clone()
cloned.Skills = maps.Clone(c.Skills)
cloned.Cooldowns = c.Cooldowns.Clone()
cloned.Settings = maps.Clone(c.Settings)
cloned.QuestProgress = maps.Clone(c.QuestProgress)
cloned.KeyRing = maps.Clone(c.KeyRing)
cloned.KD = c.KD.Clone()
cloned.MiscData = maps.Clone(c.MiscData)
cloned.MobMastery = c.MobMastery.Clone()
cloned.Pet = c.Pet.Clone()
cloned.Timers = maps.Clone(c.Timers)
cloned.ZonesVisited = cloneZonesVisited(c.ZonesVisited)
cloned.roomHistory = slices.Clone(c.roomHistory)
cloned.PlayerDamage = maps.Clone(c.PlayerDamage)
cloned.permaBuffIds = slices.Clone(c.permaBuffIds)

if c.Charmed != nil {
charmed := *c.Charmed
cloned.Charmed = &charmed
}

return &cloned
}

func cloneItems(itemList []items.Item) []items.Item {
cloned := slices.Clone(itemList)
for i := range cloned {
cloned[i] = cloned[i].Clone()
}
return cloned
}

func cloneZonesVisited(zones map[string]RoomBitset) map[string]RoomBitset {
if zones == nil {
return nil
}

cloned := make(map[string]RoomBitset, len(zones))
for zone, rooms := range zones {
cloned[zone] = rooms.Clone()
}
return cloned
}

// returns description unless description is a hash
// which points to another description location.
func (c *Character) GetDescription() string {
Expand Down
56 changes: 56 additions & 0 deletions internal/characters/clone_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package characters

import (
"testing"

"github.com/GoMudEngine/GoMud/internal/items"
)

func TestCharacterCloneOwnsNestedMutableState(t *testing.T) {
original := New()
original.Shop = Shop{{ItemId: 4001, Quantity: 1}}
original.Cooldowns = Cooldowns{"cast": 3}
original.KD = KDStats{
Kills: map[int]int{101: 2},
PlayerKills: map[string]int{"1:player": 1},
PlayerDeaths: map[string]int{"2:player": 1},
}
original.MobMastery = MobMasteries{Tame: map[int]int{201: 4}}
original.ZonesVisited = map[string]RoomBitset{"start": {1: 1}}
original.Equipment.Weapon = items.Item{ItemId: 3001, Adjectives: []string{"old"}}

cloned := original.Clone()
cloned.Shop[0].Quantity = 5
cloned.Cooldowns["cast"] = 9
cloned.KD.Kills[101] = 7
cloned.KD.PlayerKills["1:player"] = 8
cloned.KD.PlayerDeaths["2:player"] = 9
cloned.MobMastery.Tame[201] = 6
cloned.ZonesVisited["start"].Set(130)
cloned.Equipment.Weapon.Adjectives[0] = "new"

if original.Shop[0].Quantity != 1 {
t.Fatalf("original shop quantity = %d, want 1", original.Shop[0].Quantity)
}
if original.Cooldowns["cast"] != 3 {
t.Fatalf("original cooldown = %d, want 3", original.Cooldowns["cast"])
}
if original.KD.Kills[101] != 2 {
t.Fatalf("original mob kills = %d, want 2", original.KD.Kills[101])
}
if original.KD.PlayerKills["1:player"] != 1 {
t.Fatalf("original player kills = %d, want 1", original.KD.PlayerKills["1:player"])
}
if original.KD.PlayerDeaths["2:player"] != 1 {
t.Fatalf("original player deaths = %d, want 1", original.KD.PlayerDeaths["2:player"])
}
if original.MobMastery.Tame[201] != 4 {
t.Fatalf("original tame mastery = %d, want 4", original.MobMastery.Tame[201])
}
if original.ZonesVisited["start"].Has(130) {
t.Fatal("original zone visit bitset changed after mutating clone")
}
if original.Equipment.Weapon.Adjectives[0] != "old" {
t.Fatalf("original weapon adjective = %q, want old", original.Equipment.Weapon.Adjectives[0])
}
}
6 changes: 6 additions & 0 deletions internal/characters/cooldowns.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package characters

import (
"maps"

"github.com/GoMudEngine/GoMud/internal/gametime"
)

type Cooldowns map[string]int

func (cd Cooldowns) Clone() Cooldowns {
return maps.Clone(cd)
}

func (cd Cooldowns) RoundTick() {
for trackingTag := range cd {
cd[trackingTag] = cd[trackingTag] - 1
Expand Down
12 changes: 11 additions & 1 deletion internal/characters/kdstats.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package characters

import "fmt"
import (
"fmt"
"maps"
)

type KDStats struct {
TotalKills int `json:"totalkills,omitempty"` // Quick tally of kills
Expand All @@ -13,6 +16,13 @@ type KDStats struct {
TotalPvpDeaths int `json:"totalpvpdeaths,omitempty"` // Quick tally of pvp deaths
}

func (kd KDStats) Clone() KDStats {
kd.Kills = maps.Clone(kd.Kills)
kd.PlayerKills = maps.Clone(kd.PlayerKills)
kd.PlayerDeaths = maps.Clone(kd.PlayerDeaths)
return kd
}

func (kd *KDStats) GetMobKDRatio() float64 {
if kd.TotalDeaths == 0 {
return float64(kd.TotalKills)
Expand Down
7 changes: 7 additions & 0 deletions internal/characters/mobmastery.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package characters

import "maps"

type MobMasteries struct {
Tame map[int]int `json:"tame,omitempty"` // mobId to proficiency
}

func (m MobMasteries) Clone() MobMasteries {
m.Tame = maps.Clone(m.Tame)
return m
}

// // // // // // // // // // // //
// Tame related
// // // // // // // // // // // //
Expand Down
5 changes: 5 additions & 0 deletions internal/characters/roombitset.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package characters

import (
"fmt"
"maps"
"math/bits"
"strconv"

Expand All @@ -18,6 +19,10 @@ import (
// in character save files.
type RoomBitset map[uint16]uint64

func (rb RoomBitset) Clone() RoomBitset {
return maps.Clone(rb)
}

// Set marks a room as visited. Room IDs must be positive; non-positive IDs
// are silently ignored because they represent special sentinel values (e.g.
// -1 for the character-creation room, 0 for StartRoomIdAlias) that are
Expand Down
12 changes: 12 additions & 0 deletions internal/characters/shop.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ type ShopItem struct {
lastRestockRound uint64 // When was the last time an item was restocked?
}

func (s Shop) Clone() Shop {
cloned := slices.Clone(s)
for i := range cloned {
cloned[i] = cloned[i].Clone()
}
return cloned
}

func (si ShopItem) Clone() ShopItem {
return si
}

func (s *Shop) Restock() bool {

if len(*s) < 1 {
Expand Down
14 changes: 14 additions & 0 deletions internal/characters/worn.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ type Worn struct {
Feet items.Item `yaml:"feet,omitempty"`
}

func (w Worn) Clone() Worn {
w.Weapon = w.Weapon.Clone()
w.Offhand = w.Offhand.Clone()
w.Head = w.Head.Clone()
w.Neck = w.Neck.Clone()
w.Body = w.Body.Clone()
w.Belt = w.Belt.Clone()
w.Gloves = w.Gloves.Clone()
w.Ring = w.Ring.Clone()
w.Legs = w.Legs.Clone()
w.Feet = w.Feet.Clone()
return w
}

func (w *Worn) StatMod(stat ...string) int {

return w.Weapon.StatMod(stat...) +
Expand Down
43 changes: 43 additions & 0 deletions internal/items/clone_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package items

import (
"testing"

"github.com/GoMudEngine/GoMud/internal/statmods"
)

func TestItemCloneOwnsOverrideSpec(t *testing.T) {
original := Item{
ItemId: 3001,
Adjectives: []string{"old"},
Spec: &ItemSpec{
BuffIds: []int{1},
WornBuffIds: []int{2},
Damage: Damage{CritBuffIds: []int{3}},
StatMods: statmods.StatMods{"strength": 4},
},
}

cloned := original.Clone()
cloned.Adjectives[0] = "new"
cloned.Spec.BuffIds[0] = 10
cloned.Spec.WornBuffIds[0] = 20
cloned.Spec.Damage.CritBuffIds[0] = 30
cloned.Spec.StatMods["strength"] = 40

if original.Adjectives[0] != "old" {
t.Fatalf("original adjective = %q, want old", original.Adjectives[0])
}
if original.Spec.BuffIds[0] != 1 {
t.Fatalf("original buff id = %d, want 1", original.Spec.BuffIds[0])
}
if original.Spec.WornBuffIds[0] != 2 {
t.Fatalf("original worn buff id = %d, want 2", original.Spec.WornBuffIds[0])
}
if original.Spec.Damage.CritBuffIds[0] != 3 {
t.Fatalf("original crit buff id = %d, want 3", original.Spec.Damage.CritBuffIds[0])
}
if original.Spec.StatMods["strength"] != 4 {
t.Fatalf("original strength stat mod = %d, want 4", original.Spec.StatMods["strength"])
}
}
14 changes: 14 additions & 0 deletions internal/items/items.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package items

import (
"fmt"
"maps"
"slices"
"strconv"
"strings"
"unicode"
Expand Down Expand Up @@ -65,6 +67,18 @@ func New(itemId int) Item {
return newItm
}

func (i Item) Clone() Item {
i.Adjectives = slices.Clone(i.Adjectives)
i.tempDataStore = maps.Clone(i.tempDataStore)

if i.Spec != nil {
spec := i.Spec.Clone()
i.Spec = &spec
}

return i
}

func (i *Item) GetScript() string {
return i.GetSpec().GetScript()
}
Expand Down
15 changes: 15 additions & 0 deletions internal/items/itemspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package items

import (
"fmt"
"maps"
"math"
"os"
"slices"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -188,6 +190,11 @@ type Damage struct {
BonusDamage int `yaml:"bonusdamage,omitempty"` // flat damage bonus, so for example 1d6+1
}

func (d Damage) Clone() Damage {
d.CritBuffIds = slices.Clone(d.CritBuffIds)
return d
}

type ItemMessage string

// Attack messages
Expand Down Expand Up @@ -220,6 +227,14 @@ type ItemSpec struct {
KeyLockId string `yaml:"keylockid,omitempty"` // Example: `778-north` - If it's a key, what lock does it open? roomid-exitname etc.
}

func (i ItemSpec) Clone() ItemSpec {
i.BuffIds = slices.Clone(i.BuffIds)
i.WornBuffIds = slices.Clone(i.WornBuffIds)
i.Damage = i.Damage.Clone()
i.StatMods = maps.Clone(i.StatMods)
return i
}

func (i Element) String() string {
return string(i)
}
Expand Down
Loading