From b04843d758a07803658f1c0220a2626072857dee Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Sat, 21 Jun 2025 19:48:16 -0700
Subject: [PATCH 1/9] initial migration code
---
Makefile | 3 +-
_datafiles/config.yaml | 1 +
.../tutorial/scripts/58-training_dummy.js | 9 +-
.../world/default/rooms/catacombs/32.yaml | 15 ++-
.../tutorial/scripts/58-training_dummy.js | 8 +-
internal/configs/config.server.go | 5 +
internal/migration/1.0.0.go | 70 ++++++++++++++
internal/migration/migration.go | 23 +++++
internal/version/version.go | 94 +++++++++++++++++++
internal/version/version_test.go | 81 ++++++++++++++++
main.go | 21 +++++
11 files changed, 313 insertions(+), 17 deletions(-)
create mode 100644 internal/migration/1.0.0.go
create mode 100644 internal/migration/migration.go
create mode 100644 internal/version/version.go
create mode 100644 internal/version/version_test.go
diff --git a/Makefile b/Makefile
index b7939b27..4dfdf8c6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
+
.DEFAULT_GOAL := build
VERSION ?= $(shell git rev-parse HEAD)
@@ -120,7 +121,7 @@ coverage:
rm -rf bin
.PHONY: js-lint
-js-lint:
+js-lint: ### Run Javascript linter
# Grep filtering it to remove errors reported by docker image around npm packages
# if "### errors" is found in the output, exits with an error code of 1
# This should allow us to use it in CI/CD
diff --git a/_datafiles/config.yaml b/_datafiles/config.yaml
index c6780fc3..d42ea917 100755
--- a/_datafiles/config.yaml
+++ b/_datafiles/config.yaml
@@ -78,6 +78,7 @@ Server:
# accidental changes that could break the game.
Locked:
- FilePaths
+ - Server.CurrentVersion
- Server.NextRoomId
- Server.Seed
- Server.OnLoginCommands
diff --git a/_datafiles/world/default/mobs/tutorial/scripts/58-training_dummy.js b/_datafiles/world/default/mobs/tutorial/scripts/58-training_dummy.js
index cbf4b9c6..536c3624 100644
--- a/_datafiles/world/default/mobs/tutorial/scripts/58-training_dummy.js
+++ b/_datafiles/world/default/mobs/tutorial/scripts/58-training_dummy.js
@@ -4,8 +4,9 @@ function onDie(mob, room, eventDetails) {
room.SendText( mob.GetCharacterName(true) + " crumbles to dust." );
- room.GetMob(teacherMobId, true);
-
- teacherMob.Command('say You did it! As you can see you gain experience points for combat victories.');
- teacherMob.Command('say Now head west to complete your training.', 2.0);
+ var teacherMob = room.GetMob(teacherMobId, true);
+ if ( teacherMob != null ) {
+ teacherMob.Command('say You did it! As you can see you gain experience points for combat victories.');
+ teacherMob.Command('say Now head west to complete your training.', 2.0);
+ }
}
diff --git a/_datafiles/world/default/rooms/catacombs/32.yaml b/_datafiles/world/default/rooms/catacombs/32.yaml
index 80678b08..0c2fe72f 100755
--- a/_datafiles/world/default/rooms/catacombs/32.yaml
+++ b/_datafiles/world/default/rooms/catacombs/32.yaml
@@ -1,15 +1,12 @@
roomid: 32
zone: Catacombs
title: Entrance to the Catacombs
-description: Beneath the Sanctuary of the Benevolent Heart lies the labyrinthine expanse
- known as the Catacombs. A network of narrow, winding tunnels and chambers, the Catacombs
- are carved from the cold, unyielding stone of Frostfang's bedrock. The air within
- is thick with age and whispers of the past, carrying a palpable weight of reverence
- and mystery. Walls are lined with alcoves, each holding the remains of the departed,
- their final resting places marked with inscriptions and symbols of their beliefs.
- Faintly glowing lanterns, placed at irregular intervals, cast eerie illuminations
- on the ancient bones and artifacts, while the distant echo of dripping water serves
- as a haunting reminder of the passage of time in this sacred underworld.
+zoneconfig:
+ roomid: 32
+ autoscale:
+ minimum: 8
+ maximum: 13
+ musicfile: static/audio/music/catacombs.mp3
mapsymbol: E
maplegend: Entrance
exits:
diff --git a/_datafiles/world/empty/mobs/tutorial/scripts/58-training_dummy.js b/_datafiles/world/empty/mobs/tutorial/scripts/58-training_dummy.js
index 5b3e2c9b..536c3624 100644
--- a/_datafiles/world/empty/mobs/tutorial/scripts/58-training_dummy.js
+++ b/_datafiles/world/empty/mobs/tutorial/scripts/58-training_dummy.js
@@ -4,7 +4,9 @@ function onDie(mob, room, eventDetails) {
room.SendText( mob.GetCharacterName(true) + " crumbles to dust." );
- teacherMob = room.GetMob(teacherMobId, true);
-
- teacherMob.Command('say You did it! Head west to complete your training.');
+ var teacherMob = room.GetMob(teacherMobId, true);
+ if ( teacherMob != null ) {
+ teacherMob.Command('say You did it! As you can see you gain experience points for combat victories.');
+ teacherMob.Command('say Now head west to complete your training.', 2.0);
+ }
}
diff --git a/internal/configs/config.server.go b/internal/configs/config.server.go
index 5314e47f..723dc1ef 100644
--- a/internal/configs/config.server.go
+++ b/internal/configs/config.server.go
@@ -2,6 +2,7 @@ package configs
type Server struct {
MudName ConfigString `yaml:"MudName"` // Name of the MUD
+ CurrentVersion ConfigString `yaml:"CurrentVersion"` // Current version this mud has been updated to
Seed ConfigSecret `yaml:"Seed"` // Seed that may be used for generating content
MaxCPUCores ConfigInt `yaml:"MaxCPUCores"` // How many cores to allow for multi-core operations
OnLoginCommands ConfigSliceString `yaml:"OnLoginCommands"` // Commands to run when a user logs in
@@ -26,6 +27,10 @@ func (s *Server) Validate() {
s.MaxCPUCores = 0 // default
}
+ if s.CurrentVersion == `` {
+ s.CurrentVersion = `0.9.0` // If no version found, failover to a known version
+ }
+
}
func GetServerConfig() Server {
diff --git a/internal/migration/1.0.0.go b/internal/migration/1.0.0.go
new file mode 100644
index 00000000..b2dc014c
--- /dev/null
+++ b/internal/migration/1.0.0.go
@@ -0,0 +1,70 @@
+package migration
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+
+ "github.com/GoMudEngine/GoMud/internal/configs"
+ "github.com/GoMudEngine/GoMud/internal/mudlog"
+ "gopkg.in/yaml.v2"
+)
+
+// Description:
+// rooms.Room.ZoneConfig was removed when Zone data was migrated to zone-config.yaml in zone folders
+// This function loads all of the yaml files in the DATAFILES/world/*/rooms/* and looks for any ZoneConfig data.
+// If found, the data is moved to a zone-config.yaml file, and the ZoneConfig data in the Room datafile is removed.
+func migrate_RoomZoneConfig() error {
+
+ c := configs.GetConfig()
+
+ worldfilesGlob := filepath.Join(string(c.FilePaths.DataFiles), "rooms", "*", "*.yaml")
+ matches, err := filepath.Glob(worldfilesGlob)
+
+ if err != nil {
+ return err
+ }
+
+ // We only care about room files, so ###.yaml (possible negative)
+ re := regexp.MustCompile(`^[\-0-9]+\.yaml$`)
+ for _, path := range matches {
+
+ filename := filepath.Base(path)
+ if !re.MatchString(filename) {
+ continue
+ }
+
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ filedata := map[string]any{}
+
+ err = yaml.Unmarshal(data, &filedata)
+ if err != nil {
+ return fmt.Errorf("failed to parse YAML: %w", err)
+ }
+
+ if filedata[`zoneconfig`] == nil {
+ continue
+ }
+
+ mudlog.Info("ZoneConfig found", "path", path)
+ //fmt.Println(filedata[`zoneconfig`])
+
+ filedata[`zoneconfig`] = nil
+
+ fdata, _ := yaml.Marshal(filedata)
+
+ info, _ := os.Stat(path)
+
+ if err := os.WriteFile(path, fdata, info.Mode().Perm()); err != nil {
+ return err
+ }
+
+ }
+
+ return nil
+}
diff --git a/internal/migration/migration.go b/internal/migration/migration.go
new file mode 100644
index 00000000..b59c8c4c
--- /dev/null
+++ b/internal/migration/migration.go
@@ -0,0 +1,23 @@
+package migration
+
+import (
+ "github.com/GoMudEngine/GoMud/internal/version"
+)
+
+func Run(binVersion version.Version) error {
+
+ //
+ // Note: Follow this pattern and keep these version upgrades in order of lowest to greatest to avoid problems
+ //
+
+ // 0.0.0 -> 1.0.0
+ if binVersion.IsOlderThan(version.New(1, 0, 0)) {
+
+ if err := migrate_RoomZoneConfig(); err != nil {
+ return err
+ }
+
+ }
+
+ return nil
+}
diff --git a/internal/version/version.go b/internal/version/version.go
new file mode 100644
index 00000000..ce2ad515
--- /dev/null
+++ b/internal/version/version.go
@@ -0,0 +1,94 @@
+package version
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+const (
+ Older = -1
+ Newer = 1
+ Equal = 0
+)
+
+type Version struct {
+ Major int
+ Minor int
+ Patch int
+}
+
+func (v Version) String() string {
+ return fmt.Sprintf(`%d.%d.%d`, v.Major, v.Minor, v.Patch)
+}
+
+func (v Version) Compare(other Version) int {
+ if v.Major != other.Major {
+ if v.Major < other.Major {
+ return Older
+ }
+ return Newer
+ }
+ if v.Minor != other.Minor {
+ if v.Minor < other.Minor {
+ return Older
+ }
+ return Newer
+ }
+ if v.Patch != other.Patch {
+ if v.Patch < other.Patch {
+ return Older
+ }
+ return Newer
+ }
+ return Equal
+}
+
+func (v Version) IsNewerThan(other Version) bool {
+ return v.Compare(other) >= Newer
+}
+
+func (v Version) IsOlderThan(other Version) bool {
+ return v.Compare(other) <= Older
+}
+
+func New(major int, minor int, patch int) Version {
+ return Version{major, minor, patch}
+}
+
+func Parse(v string) (Version, error) {
+ // lowercase it all for predicatability
+ s := strings.ToLower(v)
+
+ // Remove leading "v" if present
+ s = strings.TrimPrefix(s, "v")
+
+ parts := strings.Split(s, ".")
+ if len(parts) < 2 || len(parts) > 3 {
+ return Version{}, fmt.Errorf("invalid version format: %s", s)
+ }
+
+ major, err := strconv.Atoi(parts[0])
+ if err != nil {
+ return Version{}, fmt.Errorf("invalid major version: %v", err)
+ }
+
+ minor, err := strconv.Atoi(parts[1])
+ if err != nil {
+ return Version{}, fmt.Errorf("invalid minor version: %v", err)
+ }
+
+ patch := 0
+ if len(parts) == 3 {
+ patch, err = strconv.Atoi(parts[2])
+ if err != nil {
+ return Version{}, fmt.Errorf("invalid patch version: %v", err)
+ }
+ }
+
+ if major == 0 && minor == 0 && patch == 0 {
+ return Version{}, fmt.Errorf("invalid version: %s", v)
+ }
+
+ return Version{Major: major, Minor: minor, Patch: patch}, nil
+}
diff --git a/internal/version/version_test.go b/internal/version/version_test.go
new file mode 100644
index 00000000..3077a3f9
--- /dev/null
+++ b/internal/version/version_test.go
@@ -0,0 +1,81 @@
+package version
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParse(t *testing.T) {
+ tests := []struct {
+ input string
+ expected Version
+ hasError bool
+ }{
+ {"0.9.0", Version{0, 9, 0}, false},
+ {"v0.9.0", Version{0, 9, 0}, false},
+ {"1.2.3", Version{1, 2, 3}, false},
+ {"v1.2.3", Version{1, 2, 3}, false},
+ {"2.0", Version{2, 0, 0}, false},
+ {"v2.0", Version{2, 0, 0}, false},
+ {"10.20.30", Version{10, 20, 30}, false},
+ {"V10.20.30", Version{10, 20, 30}, false},
+
+ // Invalid cases
+ {"", Version{}, true},
+ {"v", Version{}, true},
+ {"1", Version{}, true},
+ {"v1", Version{}, true},
+ {"0.0.0", Version{0, 0, 0}, true},
+ {"v0.0.0", Version{0, 0, 0}, true},
+ {"1.2.3.4", Version{}, true},
+ {"v1.2.beta", Version{}, true},
+ {"abc", Version{}, true},
+ }
+
+ for _, tt := range tests {
+ v, err := Parse(tt.input)
+ if tt.hasError {
+ assert.Error(t, err, "expected error for input: %q", tt.input)
+ } else {
+ assert.NoError(t, err, "unexpected error for input: %q", tt.input)
+ assert.Equal(t, tt.expected, v, "parsed version mismatch for input: %q", tt.input)
+ }
+ }
+}
+
+func TestVersionCompare(t *testing.T) {
+ tests := []struct {
+ v1 Version
+ v2 Version
+ expected int // -1 = v1 older, 0 = equal, 1 = v1 newer
+ }{
+ {Version{1, 0, 0}, Version{1, 0, 0}, 0},
+ {Version{1, 2, 3}, Version{1, 2, 3}, 0},
+ {Version{2, 0, 0}, Version{1, 9, 9}, 1},
+ {Version{1, 10, 0}, Version{1, 9, 9}, 1},
+ {Version{1, 2, 5}, Version{1, 2, 3}, 1},
+ {Version{1, 0, 0}, Version{2, 0, 0}, -1},
+ {Version{1, 2, 0}, Version{1, 3, 0}, -1},
+ {Version{1, 2, 3}, Version{1, 2, 4}, -1},
+ }
+
+ for _, tt := range tests {
+ result := tt.v1.Compare(tt.v2)
+ assert.Equal(t, tt.expected, result, "Compare(%+v, %+v)", tt.v1, tt.v2)
+ }
+}
+
+func TestVersionIsNewerThan(t *testing.T) {
+ assert.True(t, Version{2, 0, 0}.IsNewerThan(Version{1, 9, 9}))
+ assert.True(t, Version{1, 2, 3}.IsNewerThan(Version{1, 2, 2}))
+ assert.False(t, Version{1, 2, 3}.IsNewerThan(Version{1, 2, 3}))
+ assert.False(t, Version{1, 0, 0}.IsNewerThan(Version{1, 1, 0}))
+}
+
+func TestVersionIsOlderThan(t *testing.T) {
+ assert.True(t, Version{1, 0, 0}.IsOlderThan(Version{1, 1, 0}))
+ assert.True(t, Version{1, 2, 2}.IsOlderThan(Version{1, 2, 3}))
+ assert.False(t, Version{1, 2, 3}.IsOlderThan(Version{1, 2, 3}))
+ assert.False(t, Version{2, 0, 0}.IsOlderThan(Version{1, 9, 9}))
+}
diff --git a/main.go b/main.go
index 538f3612..0284afae 100644
--- a/main.go
+++ b/main.go
@@ -32,7 +32,9 @@ import (
"github.com/GoMudEngine/GoMud/internal/items"
"github.com/GoMudEngine/GoMud/internal/keywords"
"github.com/GoMudEngine/GoMud/internal/language"
+ "github.com/GoMudEngine/GoMud/internal/migration"
"github.com/GoMudEngine/GoMud/internal/usercommands"
+ "github.com/GoMudEngine/GoMud/internal/version"
"github.com/gorilla/websocket"
"github.com/GoMudEngine/GoMud/internal/mapper"
@@ -56,6 +58,13 @@ import (
textLang "golang.org/x/text/language"
)
+// Version of the binary
+// Should be kept in lockstep with github releases
+// When updating this version:
+// 1. Expect to update the github release version
+// 2. Consider whether any migration code is needed for breaking changes, particularly in datafiles (see internal/migration)
+const VERSION = "1.0.0"
+
var (
sigChan = make(chan os.Signal, 1)
workerShutdownChan = make(chan bool, 1)
@@ -96,6 +105,18 @@ func main() {
configs.ReloadConfig()
c := configs.GetConfig()
+ lastKnownVersion, err := version.Parse(string(configs.GetServerConfig().CurrentVersion))
+ if err != nil {
+ mudlog.Error("Versioning", "error", err)
+ os.Exit(1)
+ }
+
+ if err = migration.Run(lastKnownVersion); err != nil {
+ mudlog.Error("migration.Run()", "error", err)
+ os.Exit(1)
+ }
+
+ return
// Default i18n localize folders
if len(c.Translation.LanguagePaths) == 0 {
c.Translation.LanguagePaths = []string{
From 4f1fe15ddcdfab0e85cbc689a79ae5fe718a72da Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Sat, 21 Jun 2025 19:51:54 -0700
Subject: [PATCH 2/9] adjustments
---
_datafiles/world/default/rooms/catacombs/32.yaml | 11 ++++++++++-
internal/migration/1.0.0.go | 8 ++++++++
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/_datafiles/world/default/rooms/catacombs/32.yaml b/_datafiles/world/default/rooms/catacombs/32.yaml
index 0c2fe72f..d50eae05 100755
--- a/_datafiles/world/default/rooms/catacombs/32.yaml
+++ b/_datafiles/world/default/rooms/catacombs/32.yaml
@@ -7,6 +7,15 @@ zoneconfig:
minimum: 8
maximum: 13
musicfile: static/audio/music/catacombs.mp3
+description: Beneath the Sanctuary of the Benevolent Heart lies the labyrinthine expanse
+ known as the Catacombs. A network of narrow, winding tunnels and chambers, the Catacombs
+ are carved from the cold, unyielding stone of Frostfang's bedrock. The air within
+ is thick with age and whispers of the past, carrying a palpable weight of reverence
+ and mystery. Walls are lined with alcoves, each holding the remains of the departed,
+ their final resting places marked with inscriptions and symbols of their beliefs.
+ Faintly glowing lanterns, placed at irregular intervals, cast eerie illuminations
+ on the ancient bones and artifacts, while the distant echo of dripping water serves
+ as a haunting reminder of the passage of time in this sacred underworld.
mapsymbol: E
maplegend: Entrance
exits:
@@ -21,4 +30,4 @@ exits:
spawninfo:
- mobid: 21
message: A dark robed figure slinks into view.
- respawnrate: 10 real minutes
+ respawnrate: 10 real minutes
\ No newline at end of file
diff --git a/internal/migration/1.0.0.go b/internal/migration/1.0.0.go
index b2dc014c..538e86fe 100644
--- a/internal/migration/1.0.0.go
+++ b/internal/migration/1.0.0.go
@@ -7,7 +7,9 @@ import (
"regexp"
"github.com/GoMudEngine/GoMud/internal/configs"
+ "github.com/GoMudEngine/GoMud/internal/fileloader"
"github.com/GoMudEngine/GoMud/internal/mudlog"
+ "github.com/GoMudEngine/GoMud/internal/rooms"
"gopkg.in/yaml.v2"
)
@@ -64,6 +66,12 @@ func migrate_RoomZoneConfig() error {
return err
}
+ roomPtr, err := fileloader.LoadFlatFile[*rooms.Room](path)
+ if err != nil {
+ return err
+ }
+
+ rooms.SaveRoomTemplate(*roomPtr)
}
return nil
From f931dce3f74e05ef31c2e023e229be897ea9ae2d Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Sat, 21 Jun 2025 21:05:30 -0700
Subject: [PATCH 3/9] Finalizing migraiton code
---
.../world/default/rooms/catacombs/32.yaml | 8 +-
internal/migration/1.0.0.go | 92 +++++++++++++++++--
internal/migration/migration.go | 10 +-
internal/rooms/rooms.go | 56 ++++++-----
internal/rooms/save_and_load.go | 22 -----
main.go | 24 +++--
6 files changed, 136 insertions(+), 76 deletions(-)
diff --git a/_datafiles/world/default/rooms/catacombs/32.yaml b/_datafiles/world/default/rooms/catacombs/32.yaml
index d50eae05..80678b08 100755
--- a/_datafiles/world/default/rooms/catacombs/32.yaml
+++ b/_datafiles/world/default/rooms/catacombs/32.yaml
@@ -1,12 +1,6 @@
roomid: 32
zone: Catacombs
title: Entrance to the Catacombs
-zoneconfig:
- roomid: 32
- autoscale:
- minimum: 8
- maximum: 13
- musicfile: static/audio/music/catacombs.mp3
description: Beneath the Sanctuary of the Benevolent Heart lies the labyrinthine expanse
known as the Catacombs. A network of narrow, winding tunnels and chambers, the Catacombs
are carved from the cold, unyielding stone of Frostfang's bedrock. The air within
@@ -30,4 +24,4 @@ exits:
spawninfo:
- mobid: 21
message: A dark robed figure slinks into view.
- respawnrate: 10 real minutes
\ No newline at end of file
+ respawnrate: 10 real minutes
diff --git a/internal/migration/1.0.0.go b/internal/migration/1.0.0.go
index 538e86fe..7b55ea15 100644
--- a/internal/migration/1.0.0.go
+++ b/internal/migration/1.0.0.go
@@ -7,7 +7,6 @@ import (
"regexp"
"github.com/GoMudEngine/GoMud/internal/configs"
- "github.com/GoMudEngine/GoMud/internal/fileloader"
"github.com/GoMudEngine/GoMud/internal/mudlog"
"github.com/GoMudEngine/GoMud/internal/rooms"
"gopkg.in/yaml.v2"
@@ -19,6 +18,27 @@ import (
// If found, the data is moved to a zone-config.yaml file, and the ZoneConfig data in the Room datafile is removed.
func migrate_RoomZoneConfig() error {
+ // This struct is how ZoneConfig looked as of 1.0.0
+ // Since we will be upgrading an older version to this format, use a copy of the struct from that period
+ // To ensure we aren't using a struct that has changed over time
+ type zoneConfig_1_0_0 struct {
+ Name string `yaml:"name,omitempty"`
+ RoomId int `yaml:"roomid,omitempty"`
+ MobAutoScale struct {
+ Minimum int `yaml:"minimum,omitempty"` // level scaling minimum
+ Maximum int `yaml:"maximum,omitempty"` // level scaling maximum
+ } `yaml:"autoscale,omitempty"` // level scaling range if any
+ Mutators []struct {
+ MutatorId string `yaml:"mutatorid,omitempty"` // Short text that will uniquely identify this modifier ("dusty")
+ SpawnedRound uint64 `yaml:"spawnedround,omitempty"` // Tracks when this mutator was created (useful for decay)
+ DespawnedRound uint64 `yaml:"despawnedround,omitempty"` // Track when it decayed to nothing.
+ } `yaml:"mutators,omitempty"`
+ IdleMessages []string `yaml:"idlemessages,omitempty"` // list of messages that can be displayed to players in the zone, assuming a room has none defined
+ MusicFile string `yaml:"musicfile,omitempty"` // background music to play when in this zone
+ DefaultBiome string `yaml:"defaultbiome,omitempty"` // city, swamp etc. see biomes.go
+ RoomIds map[int]struct{} `yaml:"-"` // Does not get written. Built dyanmically when rooms are loaded.
+ }
+
c := configs.GetConfig()
worldfilesGlob := filepath.Join(string(c.FilePaths.DataFiles), "rooms", "*", "*.yaml")
@@ -53,25 +73,81 @@ func migrate_RoomZoneConfig() error {
continue
}
- mudlog.Info("ZoneConfig found", "path", path)
- //fmt.Println(filedata[`zoneconfig`])
+ mudlog.Info("Migration 1.0.0", "file", path, "message", "migrating zoneconfig from room data file to zone-config.yaml")
+
+ //
+ // From here on out, this code migrates zoneconfig data out of room file and into zone-config.yaml
+ //
+ roomFileInfo, _ := os.Stat(path)
+
+ mudlog.Info("Migration 1.0.0", "file", path, "message", "isolating zoneconfig data")
+
+ // Isolate the zoneconfig and write it to its own zone-config.yaml file
+ zoneBytes, err := yaml.Marshal(filedata[`zoneconfig`])
+ if err != nil {
+ return err
+ }
+
+ zoneDataStruct := zoneConfig_1_0_0{}
+
+ if err = yaml.Unmarshal(zoneBytes, &zoneDataStruct); err != nil {
+ return err
+ }
+
+ if filedata[`zone`] != nil {
+ if zoneName, ok := filedata[`zone`].(string); ok {
+ zoneDataStruct.Name = zoneName
+ } else {
+ zoneDataStruct.Name = filedata[`title`].(string)
+ }
+
+ if defaultBiome, ok := filedata[`biome`].(string); ok {
+ zoneDataStruct.DefaultBiome = defaultBiome
+ }
+ }
+ zoneFileBytes, err := yaml.Marshal(zoneDataStruct)
+ if err != nil {
+ return err
+ }
+
+ zoneFilePath := filepath.Join(filepath.Dir(path), "zone-config.yaml")
+
+ mudlog.Info("Migration 1.0.0", "file", path, "message", "writing "+zoneFilePath)
+
+ if err := os.WriteFile(zoneFilePath, zoneFileBytes, roomFileInfo.Mode().Perm()); err != nil {
+ return err
+ }
+
+ // Now clear "zoneconfig" and write the room data back
filedata[`zoneconfig`] = nil
- fdata, _ := yaml.Marshal(filedata)
+ mudlog.Info("Migration 1.0.0", "file", path, "message", "writing modified room data")
- info, _ := os.Stat(path)
+ // First marshal the modified room data into bytes
+ modifiedRoomBytes, err := yaml.Marshal(filedata)
+ if err != nil {
+ return err
+ }
- if err := os.WriteFile(path, fdata, info.Mode().Perm()); err != nil {
+ // Unmarshal the bytes into the proper struct
+ modifiedRoomStruct := rooms.Room{}
+ if err = yaml.Unmarshal(modifiedRoomBytes, &modifiedRoomStruct); err != nil {
return err
}
- roomPtr, err := fileloader.LoadFlatFile[*rooms.Room](path)
+ // Marshal again, this time using the proper struct
+ modifiedRoomBytes, err = yaml.Marshal(modifiedRoomStruct)
if err != nil {
return err
}
- rooms.SaveRoomTemplate(*roomPtr)
+ if err := os.WriteFile(path, modifiedRoomBytes, roomFileInfo.Mode().Perm()); err != nil {
+ return err
+ }
+
+ mudlog.Info("Migration 1.0.0", "file", path, "message", "successfully updated")
+
}
return nil
diff --git a/internal/migration/migration.go b/internal/migration/migration.go
index b59c8c4c..722cec2b 100644
--- a/internal/migration/migration.go
+++ b/internal/migration/migration.go
@@ -1,17 +1,18 @@
package migration
import (
+ "github.com/GoMudEngine/GoMud/internal/configs"
"github.com/GoMudEngine/GoMud/internal/version"
)
-func Run(binVersion version.Version) error {
+func Run(lastConfigVersion version.Version, serverVersion version.Version) error {
//
// Note: Follow this pattern and keep these version upgrades in order of lowest to greatest to avoid problems
//
// 0.0.0 -> 1.0.0
- if binVersion.IsOlderThan(version.New(1, 0, 0)) {
+ if lastConfigVersion.IsOlderThan(version.New(1, 0, 0)) {
if err := migrate_RoomZoneConfig(); err != nil {
return err
@@ -19,5 +20,10 @@ func Run(binVersion version.Version) error {
}
+ //
+ // Finally, update to the version this migration is for
+ //
+ configs.SetVal(`Server.CurrentVersion`, serverVersion.String())
+
return nil
}
diff --git a/internal/rooms/rooms.go b/internal/rooms/rooms.go
index 32363e5d..4cd74bfe 100644
--- a/internal/rooms/rooms.go
+++ b/internal/rooms/rooms.go
@@ -30,7 +30,6 @@ var (
"*": defaultMapSymbol,
//"•": "*",
}
-
)
type FindFlag uint16
@@ -65,34 +64,33 @@ const (
type Room struct {
//mutex
- RoomId int `yaml:"roomid"` // a unique numeric index of the room. Also the filename.
- Zone string `yaml:"zone"` // zone is a way to partition rooms into groups. Also into folders.
- ZoneConfig *ZoneConfig `yaml:"zoneconfig,omitempty" instance:"skip"` // If non-null is a root room.
- MusicFile string `yaml:"musicfile,omitempty"` // background music to play when in this room
- IsBank bool `yaml:"isbank,omitempty"` // Is this a bank room? If so, players can deposit/withdraw gold here.
- IsStorage bool `yaml:"isstorage,omitempty"` // Is this a storage room? If so, players can add/remove objects here.
- IsCharacterRoom bool `yaml:"ischaracterroom,omitempty"` // Is this a room where characters can create new characters to swap between them?
- Title string `yaml:"title"` // Title shown to the user
- Description string `yaml:"description"` // Description shown to the user
- MapSymbol string `yaml:"mapsymbol,omitempty"` // The symbol to use when generating a map of the zone
- MapLegend string `yaml:"maplegend,omitempty"` // The text to display in the legend for this room. Should be one word.
- Biome string `yaml:"biome,omitempty"` // The biome of the room. Used for weather generation.
- Containers map[string]Container `yaml:"containers,omitempty"` // If this room has a chest, what is in it?
- Exits map[string]exit.RoomExit `yaml:"exits"` // Exits to other rooms
- ExitsTemp map[string]exit.TemporaryRoomExit `yaml:"-"` // Temporary exits that will be removed after a certain time. Don't bother saving on sever shutting down.
- Nouns map[string]string `yaml:"nouns,omitempty"` // Interesting nouns to highlight in the room or reveal on succesful searches.
- Items []items.Item `yaml:"items,omitempty"` // Items on the floor
- Stash []items.Item `yaml:"stash,omitempty"` // list of items in the room that are not visible to players
- Corpses []Corpse `yaml:"-"` // Any corpses laying around from recent deaths
- Gold int `yaml:"gold,omitempty"` // How much gold is on the ground?
- SpawnInfo []SpawnInfo `yaml:"spawninfo,omitempty" instance:"skip"` // key is creature ID, value is spawn chance
- SkillTraining map[string]TrainingRange `yaml:"skilltraining,omitempty"` // list of skills that can be trained in this room
- Signs []Sign `yaml:"sign,omitempty"` // list of scribbles in the room
- IdleMessages []string `yaml:"idlemessages,omitempty" ` // list of messages that can be displayed to players in the room
- LastIdleMessage uint8 `yaml:"-"` // index of the last idle message displayed
- LongTermDataStore map[string]any `yaml:"longtermdatastore,omitempty"` // Long term data store for the room
- Mutators mutators.MutatorList `yaml:"mutators,omitempty"` // mutators this room spawns with.
- Pvp bool `yaml:"pvp,omitempty"` // if config pvp is set to `limited`, uses this value
+ RoomId int `yaml:"roomid"` // a unique numeric index of the room. Also the filename.
+ Zone string `yaml:"zone"` // zone is a way to partition rooms into groups. Also into folders.
+ MusicFile string `yaml:"musicfile,omitempty"` // background music to play when in this room
+ IsBank bool `yaml:"isbank,omitempty"` // Is this a bank room? If so, players can deposit/withdraw gold here.
+ IsStorage bool `yaml:"isstorage,omitempty"` // Is this a storage room? If so, players can add/remove objects here.
+ IsCharacterRoom bool `yaml:"ischaracterroom,omitempty"` // Is this a room where characters can create new characters to swap between them?
+ Title string `yaml:"title"` // Title shown to the user
+ Description string `yaml:"description"` // Description shown to the user
+ MapSymbol string `yaml:"mapsymbol,omitempty"` // The symbol to use when generating a map of the zone
+ MapLegend string `yaml:"maplegend,omitempty"` // The text to display in the legend for this room. Should be one word.
+ Biome string `yaml:"biome,omitempty"` // The biome of the room. Used for weather generation.
+ Containers map[string]Container `yaml:"containers,omitempty"` // If this room has a chest, what is in it?
+ Exits map[string]exit.RoomExit `yaml:"exits"` // Exits to other rooms
+ ExitsTemp map[string]exit.TemporaryRoomExit `yaml:"-"` // Temporary exits that will be removed after a certain time. Don't bother saving on sever shutting down.
+ Nouns map[string]string `yaml:"nouns,omitempty"` // Interesting nouns to highlight in the room or reveal on succesful searches.
+ Items []items.Item `yaml:"items,omitempty"` // Items on the floor
+ Stash []items.Item `yaml:"stash,omitempty"` // list of items in the room that are not visible to players
+ Corpses []Corpse `yaml:"-"` // Any corpses laying around from recent deaths
+ Gold int `yaml:"gold,omitempty"` // How much gold is on the ground?
+ SpawnInfo []SpawnInfo `yaml:"spawninfo,omitempty" instance:"skip"` // key is creature ID, value is spawn chance
+ SkillTraining map[string]TrainingRange `yaml:"skilltraining,omitempty"` // list of skills that can be trained in this room
+ Signs []Sign `yaml:"sign,omitempty"` // list of scribbles in the room
+ IdleMessages []string `yaml:"idlemessages,omitempty" ` // list of messages that can be displayed to players in the room
+ LastIdleMessage uint8 `yaml:"-"` // index of the last idle message displayed
+ LongTermDataStore map[string]any `yaml:"longtermdatastore,omitempty"` // Long term data store for the room
+ Mutators mutators.MutatorList `yaml:"mutators,omitempty"` // mutators this room spawns with.
+ Pvp bool `yaml:"pvp,omitempty"` // if config pvp is set to `limited`, uses this value
// Unexported/private
players []int // list of user IDs currently in the room
mobs []int // list of mob instance IDs currently in the room. Does not get saved.
diff --git a/internal/rooms/save_and_load.go b/internal/rooms/save_and_load.go
index 180c3b3d..5bdd348f 100644
--- a/internal/rooms/save_and_load.go
+++ b/internal/rooms/save_and_load.go
@@ -400,28 +400,6 @@ func loadAllRoomZones() error {
for _, loadedRoom := range loadedRooms {
- //
- // This code migrates old format data to the new format (separate zone file)
- //
- if loadedRoom.ZoneConfig != nil {
- if loadedRoom.ZoneConfig.RoomId == loadedRoom.RoomId {
- if _, ok := roomManager.zones[loadedRoom.Zone]; !ok {
- newZone := NewZoneConfig(loadedRoom.Zone)
- newZone.DefaultBiome = loadedRoom.Biome
- newZone.IdleMessages = loadedRoom.ZoneConfig.IdleMessages
- newZone.MusicFile = loadedRoom.ZoneConfig.MusicFile
- newZone.MobAutoScale = loadedRoom.ZoneConfig.MobAutoScale
- newZone.Mutators = loadedRoom.ZoneConfig.Mutators
- newZone.RoomId = loadedRoom.ZoneConfig.RoomId
- if err := SaveZoneConfig(newZone); err != nil {
- return err
- }
- loadedRoom.ZoneConfig = nil // if successfully saved, blank out the ZoneConfig for the room
- SaveRoomTemplate(*loadedRoom)
- }
- }
- }
-
// configs.GetConfig().DeathRecoveryRoom is the death/shadow realm and gets a pass
if loadedRoom.RoomId == int(configs.GetSpecialRoomsConfig().DeathRecoveryRoom) {
continue
diff --git a/main.go b/main.go
index 0284afae..ca9dbcd2 100644
--- a/main.go
+++ b/main.go
@@ -111,12 +111,13 @@ func main() {
os.Exit(1)
}
- if err = migration.Run(lastKnownVersion); err != nil {
+ currentVersion, _ := version.Parse(VERSION)
+
+ if err = migration.Run(lastKnownVersion, currentVersion); err != nil {
mudlog.Error("migration.Run()", "error", err)
os.Exit(1)
}
- return
// Default i18n localize folders
if len(c.Translation.LanguagePaths) == 0 {
c.Translation.LanguagePaths = []string{
@@ -127,12 +128,19 @@ func main() {
mudlog.Info(`========================`)
//
- mudlog.Info(` ___ ____ _______ `)
- mudlog.Info(` | \/ | | | | _ \ `)
- mudlog.Info(` | . . | | | | | | | `)
- mudlog.Info(` | |\/| | | | | | | | `)
- mudlog.Info(` | | | | |_| | |/ / `)
- mudlog.Info(` \_| |_/\___/|___/ `)
+ mudlog.Info(` _____ `)
+ mudlog.Info(` / ____| `)
+ mudlog.Info(`| | __ ___ `)
+ mudlog.Info(`| | |_ |/ _ \ `)
+ mudlog.Info(`| |__| | (_) | `)
+ mudlog.Info(` \_____|\___/ `)
+ mudlog.Info(` __ __ _ `)
+ mudlog.Info(`| \/ | | |`)
+ mudlog.Info(`| \ / |_ _ __| |`)
+ mudlog.Info(`| |\/| | | | |/ _' |`)
+ mudlog.Info(`| | | | |_| | (_| |`)
+ mudlog.Info(`|_| |_|\__,_|\__,_|`)
+
//
mudlog.Info(`========================`)
//
From 71a78a2a6c7ccaf0727df3eeb265308084547984 Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Mon, 23 Jun 2025 12:46:40 -0700
Subject: [PATCH 4/9] Fixing older/newer comparisons to use = operator
---
internal/version/version.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/internal/version/version.go b/internal/version/version.go
index ce2ad515..cd7c0262 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -45,11 +45,11 @@ func (v Version) Compare(other Version) int {
}
func (v Version) IsNewerThan(other Version) bool {
- return v.Compare(other) >= Newer
+ return v.Compare(other) == Newer
}
func (v Version) IsOlderThan(other Version) bool {
- return v.Compare(other) <= Older
+ return v.Compare(other) == Older
}
func New(major int, minor int, patch int) Version {
From 41cd377a2d55e6cbd76682521ba08a77b6d1ec6b Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Mon, 23 Jun 2025 14:00:35 -0700
Subject: [PATCH 5/9] Better version comparisons, backup before migration and
restore on failure.
---
internal/migration/backup.go | 73 +++++++++++++++++++++++++++++++++
internal/migration/migration.go | 36 +++++++++++++---
internal/version/version.go | 4 ++
3 files changed, 108 insertions(+), 5 deletions(-)
create mode 100644 internal/migration/backup.go
diff --git a/internal/migration/backup.go b/internal/migration/backup.go
new file mode 100644
index 00000000..1d8ea063
--- /dev/null
+++ b/internal/migration/backup.go
@@ -0,0 +1,73 @@
+package migration
+
+import (
+ "errors"
+ "io"
+ "io/fs"
+ "os"
+ "path/filepath"
+
+ "github.com/GoMudEngine/GoMud/internal/configs"
+)
+
+func datafilesBackup() (string, error) {
+
+ tmpDir, err := os.MkdirTemp("", "datafiles_backup_*")
+ if err != nil {
+ return "", err
+ }
+
+ c := configs.GetConfig()
+ datafilesFolder := string(c.FilePaths.DataFiles)
+
+ err = copyDir(datafilesFolder, tmpDir)
+ if err != nil {
+ return "", err
+ }
+
+ return tmpDir, nil
+}
+
+func copyDir(src string, dst string) error {
+ return filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+
+ relPath, err := filepath.Rel(src, path)
+ if err != nil {
+ return err
+ }
+
+ destPath := filepath.Join(dst, relPath)
+
+ if d.IsDir() {
+ _, err := os.Stat(destPath)
+ if errors.Is(err, os.ErrNotExist) {
+ return os.MkdirAll(destPath, 0755)
+ }
+ return nil
+ }
+
+ // It’s a file
+ return copyFile(path, destPath)
+ })
+}
+
+// CopyFile copies a single file
+func copyFile(srcFile, dstFile string) error {
+ srcF, err := os.Open(srcFile)
+ if err != nil {
+ return err
+ }
+ defer srcF.Close()
+
+ dstF, err := os.Create(dstFile)
+ if err != nil {
+ return err
+ }
+ defer dstF.Close()
+
+ _, err = io.Copy(dstF, srcF)
+ return err
+}
diff --git a/internal/migration/migration.go b/internal/migration/migration.go
index 722cec2b..b7df0c73 100644
--- a/internal/migration/migration.go
+++ b/internal/migration/migration.go
@@ -1,29 +1,55 @@
package migration
import (
+ "fmt"
+ "os"
+
"github.com/GoMudEngine/GoMud/internal/configs"
"github.com/GoMudEngine/GoMud/internal/version"
)
-func Run(lastConfigVersion version.Version, serverVersion version.Version) error {
+func Run(lastConfigVersion version.Version, serverVersion version.Version) (err error) {
+
+ if lastConfigVersion.IsEqualTo(serverVersion) {
+ return nil
+ }
//
// Note: Follow this pattern and keep these version upgrades in order of lowest to greatest to avoid problems
+ // Note2: Take care to not shadow the err variable, since it is used in defer
//
+ var backupFolder string
+
+ // This defer checks whether an error is present before returning
+ // If so, restores backup.
+ defer func() {
+ if err != nil && backupFolder != `` {
+ fmt.Println("OOPS", err)
+ copyDir(backupFolder, string(configs.GetFilePathsConfig().DataFiles))
+ }
+ os.RemoveAll(backupFolder)
+ }()
+
+ backupFolder, err = datafilesBackup()
+ if err != nil {
+ err = fmt.Errorf(`could not backup datafiles: %w`, err)
+ return
+ }
// 0.0.0 -> 1.0.0
if lastConfigVersion.IsOlderThan(version.New(1, 0, 0)) {
- if err := migrate_RoomZoneConfig(); err != nil {
- return err
+ err = migrate_RoomZoneConfig()
+ if err != nil {
+ return
}
}
//
- // Finally, update to the version this migration is for
+ // Finally, since successful, update to the version this migration is for
//
configs.SetVal(`Server.CurrentVersion`, serverVersion.String())
- return nil
+ return
}
diff --git a/internal/version/version.go b/internal/version/version.go
index cd7c0262..a687a7ff 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -52,6 +52,10 @@ func (v Version) IsOlderThan(other Version) bool {
return v.Compare(other) == Older
}
+func (v Version) IsEqualTo(other Version) bool {
+ return v.Compare(other) == Equal
+}
+
func New(major int, minor int, patch int) Version {
return Version{major, minor, patch}
}
From a91331ed6e223138db260f73a45a6661ed6305cb Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Wed, 25 Jun 2025 13:06:26 -0700
Subject: [PATCH 6/9] Cleaned up, added comments, added check for
zone-config.yaml
---
internal/migration/1.0.0.go | 97 +++++++++++++++++++++++++++++----
internal/migration/migration.go | 61 ++++++++++++---------
2 files changed, 121 insertions(+), 37 deletions(-)
diff --git a/internal/migration/1.0.0.go b/internal/migration/1.0.0.go
index 7b55ea15..ac60a826 100644
--- a/internal/migration/1.0.0.go
+++ b/internal/migration/1.0.0.go
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"regexp"
+ "strings"
"github.com/GoMudEngine/GoMud/internal/configs"
"github.com/GoMudEngine/GoMud/internal/mudlog"
@@ -48,27 +49,85 @@ func migrate_RoomZoneConfig() error {
return err
}
+ existingZoneFiles := map[string]struct{}{}
+
// We only care about room files, so ###.yaml (possible negative)
re := regexp.MustCompile(`^[\-0-9]+\.yaml$`)
for _, path := range matches {
- filename := filepath.Base(path)
- if !re.MatchString(filename) {
+ //
+ // Must look like a room yaml file:
+ // 1.yaml
+ // 123.yaml
+ // -83.yaml
+ // etc.
+ //
+
+ if !re.MatchString(filepath.Base(path)) {
continue
}
+ //
+ // strip the filename form the room file and replace with zone-config.yaml
+ // to get the path to the zone-config.yaml
+ //
+ zoneFilePath := filepath.Join(filepath.Dir(path), "zone-config.yaml")
+
+ //
+ // The following checks whether the zone config file already exists
+ // We will leave the config data in the room data file if the zone-config.yaml is already present.
+ // It should be inert if present, since it is not unmarshalled into anything in current code.
+ //
+
+ // Check whether zone file already is tracked as existing, if found, skip.
+ if _, ok := existingZoneFiles[zoneFilePath]; ok {
+ continue
+ }
+
+ _, err = os.Stat(zoneFilePath)
+ if err == nil {
+ // Mark zone file as existing, skip further processing.
+ existingZoneFiles[zoneFilePath] = struct{}{}
+ continue
+ }
+
+ //
+ // End check for existing zone-config.yaml
+ // After this point, we will unmarshal the yaml file into a generic map structure.
+ // This allows us to examine the data in the yaml file, particularly the "zoneconfig" node
+ // since the ZoneConfig field has been removed from the rooms.Room struct
+ // We can de-populate the field, move it, and re-write the yaml back to the original room template file.
+ // The downside to this method is that being a map, the fields will be read/written in a non-deterministic manner,
+ // So the room yaml file field orders may be written in a random order.
+ // Because of this, and as a final fix, we will finally marshal/unmarshal into the proper room struct from the map data
+ // Allowing us to write the data in an expected ordered form.
+ //
+
data, err := os.ReadFile(path)
if err != nil {
return err
}
- filedata := map[string]any{}
+ //
+ // First do a simple check for the field name in the text file.
+ // We know the way the field will appear: "zoneconfig:"
+ // This avoids having to unmarshal the struct and search that way, unnecessarily.
+ //
+ if !strings.Contains(string(data), "zoneconfig:") {
+ continue
+ }
+ //
+ // Unmarshal the entire yaml file into a map
+ // This will let us further examine the data, modify it, etc.
+ //
+ filedata := map[string]any{}
err = yaml.Unmarshal(data, &filedata)
if err != nil {
return fmt.Errorf("failed to parse YAML: %w", err)
}
+ // Make sure that the zoneconfig key is present and populated
if filedata[`zoneconfig`] == nil {
continue
}
@@ -82,7 +141,13 @@ func migrate_RoomZoneConfig() error {
mudlog.Info("Migration 1.0.0", "file", path, "message", "isolating zoneconfig data")
+ //
// Isolate the zoneconfig and write it to its own zone-config.yaml file
+ // We'll marshal just the zoneconfig data, get its bytes, then unmarshal it into
+ // the desired target structure.
+ // Some fields have changed or are missing due to some slight differences in the new struct
+ // so we'll also try and reconcile some of that by pulling from the core room definition
+ //
zoneBytes, err := yaml.Marshal(filedata[`zoneconfig`])
if err != nil {
return err
@@ -106,24 +171,35 @@ func migrate_RoomZoneConfig() error {
}
}
+ mudlog.Info("Migration 1.0.0", "file", path, "message", "writing "+zoneFilePath)
+
+ //
+ // Write the zone data to the zone-config.yaml path
+ // We'll just use whatever permissions were set in the room file for this file.
+ //
zoneFileBytes, err := yaml.Marshal(zoneDataStruct)
if err != nil {
return err
}
-
- zoneFilePath := filepath.Join(filepath.Dir(path), "zone-config.yaml")
-
- mudlog.Info("Migration 1.0.0", "file", path, "message", "writing "+zoneFilePath)
-
if err := os.WriteFile(zoneFilePath, zoneFileBytes, roomFileInfo.Mode().Perm()); err != nil {
return err
}
- // Now clear "zoneconfig" and write the room data back
- filedata[`zoneconfig`] = nil
+ // Mark zone file as existing
+ existingZoneFiles[zoneFilePath] = struct{}{}
mudlog.Info("Migration 1.0.0", "file", path, "message", "writing modified room data")
+ //
+ // Now clear the "zoneconfig" node from the room data.
+ // The data will be in a random order if we just write this back to the room yaml file,
+ // so we'll take the extract step of marshalling the room data from the map into a string,
+ // and then unmarshal it into the actual target rooms.Room{} struct.
+ // This way, when writing to a file, it'll be in the typical field order according to the struct
+ // field order.
+ //
+ delete(filedata, `zoneconfig`)
+
// First marshal the modified room data into bytes
modifiedRoomBytes, err := yaml.Marshal(filedata)
if err != nil {
@@ -142,6 +218,7 @@ func migrate_RoomZoneConfig() error {
return err
}
+ // Again, we'll just use the rooms original permissions when writing.
if err := os.WriteFile(path, modifiedRoomBytes, roomFileInfo.Mode().Perm()); err != nil {
return err
}
diff --git a/internal/migration/migration.go b/internal/migration/migration.go
index b7df0c73..40b01fad 100644
--- a/internal/migration/migration.go
+++ b/internal/migration/migration.go
@@ -8,42 +8,49 @@ import (
"github.com/GoMudEngine/GoMud/internal/version"
)
-func Run(lastConfigVersion version.Version, serverVersion version.Version) (err error) {
+// Migration code goes here.
+// They should be put in the order of oldest to newest and follow the pattern as below
+func doAllMigrations(lastConfigVersion version.Version) error {
+ // 0.0.0 -> 1.0.0
+ if lastConfigVersion.IsOlderThan(version.New(1, 0, 0)) {
+
+ if err := migrate_RoomZoneConfig(); err != nil {
+ return err
+ }
+
+ }
+
+ return nil
+}
+
+// Entrypoint for migrations.
+// This is run on server start-up, after config files are loaded.
+// NOTE: This means migrations that modify config files themselves would need special consideration
+func Run(lastConfigVersion version.Version, serverVersion version.Version) error {
+
+ //
+ // If already up to speed on version, we don't really need to do anything.
+ //
if lastConfigVersion.IsEqualTo(serverVersion) {
return nil
}
//
- // Note: Follow this pattern and keep these version upgrades in order of lowest to greatest to avoid problems
- // Note2: Take care to not shadow the err variable, since it is used in defer
+ // Start by making a backup of all datafiles.
//
- var backupFolder string
-
- // This defer checks whether an error is present before returning
- // If so, restores backup.
- defer func() {
- if err != nil && backupFolder != `` {
- fmt.Println("OOPS", err)
- copyDir(backupFolder, string(configs.GetFilePathsConfig().DataFiles))
- }
- os.RemoveAll(backupFolder)
- }()
-
- backupFolder, err = datafilesBackup()
+ backupFolder, err := datafilesBackup()
if err != nil {
- err = fmt.Errorf(`could not backup datafiles: %w`, err)
- return
+ return fmt.Errorf(`could not backup datafiles: %w`, err)
}
+ defer os.RemoveAll(backupFolder)
- // 0.0.0 -> 1.0.0
- if lastConfigVersion.IsOlderThan(version.New(1, 0, 0)) {
-
- err = migrate_RoomZoneConfig()
- if err != nil {
- return
- }
-
+ //
+ // If an error occured, restore backup
+ //
+ if err := doAllMigrations(lastConfigVersion); err != nil {
+ copyDir(backupFolder, string(configs.GetFilePathsConfig().DataFiles))
+ return err
}
//
@@ -51,5 +58,5 @@ func Run(lastConfigVersion version.Version, serverVersion version.Version) (err
//
configs.SetVal(`Server.CurrentVersion`, serverVersion.String())
- return
+ return nil
}
From 923b53aa60b6d1606a9aeb2f016f6170d6794c25 Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Wed, 25 Jun 2025 15:32:29 -0700
Subject: [PATCH 7/9] adding --version flag to command line to get version
---
internal/flags/flags.go | 10 +++++++++-
main.go | 2 +-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/internal/flags/flags.go b/internal/flags/flags.go
index 357cce94..ca191ade 100644
--- a/internal/flags/flags.go
+++ b/internal/flags/flags.go
@@ -11,13 +11,21 @@ import (
"github.com/GoMudEngine/GoMud/internal/mudlog"
)
-func HandleFlags() {
+func HandleFlags(serverVersion string) {
+
var portsearch string
+ var showVersion bool
flag.StringVar(&portsearch, "port-search", "", "Search for the first 10 open ports: -port-search=30000-40000")
+ flag.BoolVar(&showVersion, "version", false, "Display the current binary version")
flag.Parse()
+ if showVersion {
+ fmt.Println(serverVersion)
+ os.Exit(0)
+ }
+
if portsearch != `` {
doPortSearch(portsearch)
os.Exit(0)
diff --git a/main.go b/main.go
index ca9dbcd2..669a6149 100644
--- a/main.go
+++ b/main.go
@@ -100,7 +100,7 @@ func main() {
os.Getenv(`LOG_NOCOLOR`) == ``,
)
- flags.HandleFlags()
+ flags.HandleFlags(VERSION)
configs.ReloadConfig()
c := configs.GetConfig()
From 522498f9d206bfc7381a6776c05f8cc3e74c59b2 Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Wed, 25 Jun 2025 15:36:24 -0700
Subject: [PATCH 8/9] Setting version to 0.9.1
---
internal/migration/migration.go | 4 ++--
main.go | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/internal/migration/migration.go b/internal/migration/migration.go
index 40b01fad..188f01c5 100644
--- a/internal/migration/migration.go
+++ b/internal/migration/migration.go
@@ -12,8 +12,8 @@ import (
// They should be put in the order of oldest to newest and follow the pattern as below
func doAllMigrations(lastConfigVersion version.Version) error {
- // 0.0.0 -> 1.0.0
- if lastConfigVersion.IsOlderThan(version.New(1, 0, 0)) {
+ // 0.0.0 -> 0.9.1
+ if lastConfigVersion.IsOlderThan(version.New(0, 9, 1)) {
if err := migrate_RoomZoneConfig(); err != nil {
return err
diff --git a/main.go b/main.go
index 669a6149..eeb241ba 100644
--- a/main.go
+++ b/main.go
@@ -63,7 +63,7 @@ import (
// When updating this version:
// 1. Expect to update the github release version
// 2. Consider whether any migration code is needed for breaking changes, particularly in datafiles (see internal/migration)
-const VERSION = "1.0.0"
+const VERSION = "0.9.1"
var (
sigChan = make(chan os.Signal, 1)
From 4a6fb52c1cfeee26393227ff0be64c5e859321e7 Mon Sep 17 00:00:00 2001
From: Volte6 <143822+Volte6@users.noreply.github.com>
Date: Wed, 25 Jun 2025 15:38:56 -0700
Subject: [PATCH 9/9] Renaming 1.0.0 to 0.9.1
---
internal/migration/{1.0.0.go => 0.9.1.go} | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
rename internal/migration/{1.0.0.go => 0.9.1.go} (95%)
diff --git a/internal/migration/1.0.0.go b/internal/migration/0.9.1.go
similarity index 95%
rename from internal/migration/1.0.0.go
rename to internal/migration/0.9.1.go
index ac60a826..0356d076 100644
--- a/internal/migration/1.0.0.go
+++ b/internal/migration/0.9.1.go
@@ -19,7 +19,7 @@ import (
// If found, the data is moved to a zone-config.yaml file, and the ZoneConfig data in the Room datafile is removed.
func migrate_RoomZoneConfig() error {
- // This struct is how ZoneConfig looked as of 1.0.0
+ // This struct is how ZoneConfig looked as of 0.9.1
// Since we will be upgrading an older version to this format, use a copy of the struct from that period
// To ensure we aren't using a struct that has changed over time
type zoneConfig_1_0_0 struct {
@@ -132,14 +132,14 @@ func migrate_RoomZoneConfig() error {
continue
}
- mudlog.Info("Migration 1.0.0", "file", path, "message", "migrating zoneconfig from room data file to zone-config.yaml")
+ mudlog.Info("Migration 0.9.1", "file", path, "message", "migrating zoneconfig from room data file to zone-config.yaml")
//
// From here on out, this code migrates zoneconfig data out of room file and into zone-config.yaml
//
roomFileInfo, _ := os.Stat(path)
- mudlog.Info("Migration 1.0.0", "file", path, "message", "isolating zoneconfig data")
+ mudlog.Info("Migration 0.9.1", "file", path, "message", "isolating zoneconfig data")
//
// Isolate the zoneconfig and write it to its own zone-config.yaml file
@@ -171,7 +171,7 @@ func migrate_RoomZoneConfig() error {
}
}
- mudlog.Info("Migration 1.0.0", "file", path, "message", "writing "+zoneFilePath)
+ mudlog.Info("Migration 0.9.1", "file", path, "message", "writing "+zoneFilePath)
//
// Write the zone data to the zone-config.yaml path
@@ -188,7 +188,7 @@ func migrate_RoomZoneConfig() error {
// Mark zone file as existing
existingZoneFiles[zoneFilePath] = struct{}{}
- mudlog.Info("Migration 1.0.0", "file", path, "message", "writing modified room data")
+ mudlog.Info("Migration 0.9.1", "file", path, "message", "writing modified room data")
//
// Now clear the "zoneconfig" node from the room data.
@@ -223,7 +223,7 @@ func migrate_RoomZoneConfig() error {
return err
}
- mudlog.Info("Migration 1.0.0", "file", path, "message", "successfully updated")
+ mudlog.Info("Migration 0.9.1", "file", path, "message", "successfully updated")
}