From b2212a279c65ee654a425b67527063b1683d95a7 Mon Sep 17 00:00:00 2001 From: Miroslav Vasilev Date: Wed, 25 Jun 2025 20:14:07 +0300 Subject: [PATCH] ECS refactor --- internal/game/components/common.go | 29 ++++ internal/game/components/player.go | 35 +++++ internal/game/components/room.go | 18 +++ internal/game/ecs/components.go | 31 ---- internal/game/ecs/ecs.go | 196 ++++++++++++++++++++++++ internal/game/ecs/entity.go | 19 --- internal/game/ecs/error.go | 23 +++ internal/game/ecs/systems.go | 1 - internal/game/ecs/world.go | 15 -- internal/game/entities.go | 39 +++++ internal/game/events.go | 20 ++- internal/game/game.go | 4 +- internal/game/player.go | 58 +++---- internal/game/room.go | 74 ++++----- internal/game/systems.go | 1 + internal/game/world.go | 235 ++++++++++++++++++++++------- 16 files changed, 598 insertions(+), 200 deletions(-) create mode 100644 internal/game/components/common.go create mode 100644 internal/game/components/player.go create mode 100644 internal/game/components/room.go delete mode 100644 internal/game/ecs/components.go create mode 100644 internal/game/ecs/ecs.go delete mode 100644 internal/game/ecs/entity.go create mode 100644 internal/game/ecs/error.go delete mode 100644 internal/game/ecs/systems.go delete mode 100644 internal/game/ecs/world.go create mode 100644 internal/game/entities.go create mode 100644 internal/game/systems.go diff --git a/internal/game/components/common.go b/internal/game/components/common.go new file mode 100644 index 0000000..cad68a5 --- /dev/null +++ b/internal/game/components/common.go @@ -0,0 +1,29 @@ +package components + +import "code.haedhutner.dev/mvv/LastMUD/internal/game/ecs" + +const ( + TypeName ecs.ComponentType = iota + TypeDescription + TypePlayerState + TypeInRoom + TypeNeighbors + TypeIsRoom + TypeIsPlayer +) + +type NameComponent struct { + Name string +} + +func (c NameComponent) Type() ecs.ComponentType { + return TypeName +} + +type DescriptionComponent struct { + Description string +} + +func (c DescriptionComponent) Type() ecs.ComponentType { + return TypeDescription +} diff --git a/internal/game/components/player.go b/internal/game/components/player.go new file mode 100644 index 0000000..93d02e2 --- /dev/null +++ b/internal/game/components/player.go @@ -0,0 +1,35 @@ +package components + +import "code.haedhutner.dev/mvv/LastMUD/internal/game/ecs" + +type PlayerState = byte + +const ( + PlayerStateJoining PlayerState = iota + PlayerStateLoggingIn + PlayerStateRegistering + PlayerStatePlaying + PlayerStateLeaving +) + +type PlayerStateComponent struct { + State PlayerState +} + +func (c PlayerStateComponent) Type() ecs.ComponentType { + return TypePlayerState +} + +type InRoomComponent struct { + Room ecs.Entity +} + +func (c InRoomComponent) Type() ecs.ComponentType { + return TypeInRoom +} + +type IsPlayerComponent struct{} + +func (c IsPlayerComponent) Type() ecs.ComponentType { + return TypeIsPlayer +} diff --git a/internal/game/components/room.go b/internal/game/components/room.go new file mode 100644 index 0000000..5cab687 --- /dev/null +++ b/internal/game/components/room.go @@ -0,0 +1,18 @@ +package components + +import "code.haedhutner.dev/mvv/LastMUD/internal/game/ecs" + +type IsRoomComponent struct { +} + +func (c IsRoomComponent) Type() ecs.ComponentType { + return TypeIsRoom +} + +type NeighborsComponent struct { + North, South, East, West ecs.Entity +} + +func (c NeighborsComponent) Type() ecs.ComponentType { + return TypeNeighbors +} diff --git a/internal/game/ecs/components.go b/internal/game/ecs/components.go deleted file mode 100644 index b98d19f..0000000 --- a/internal/game/ecs/components.go +++ /dev/null @@ -1,31 +0,0 @@ -package ecs - -type PlayerState = byte - -const ( - PlayerStateJoining PlayerState = iota - PlayerStateLoggingIn - PlayerStateRegistering - PlayerStatePlaying - PlayerStateLeaving -) - -type PlayerStateComponent struct { - State PlayerState -} - -type NameComponent struct { - Name string -} - -type DescriptionComponent struct { - Description string -} - -type InRoomComponent struct { - InRoom Entity -} - -type NeighboringRoomsComponent struct { - North, South, East, West Entity -} diff --git a/internal/game/ecs/ecs.go b/internal/game/ecs/ecs.go new file mode 100644 index 0000000..e4c5fc5 --- /dev/null +++ b/internal/game/ecs/ecs.go @@ -0,0 +1,196 @@ +package ecs + +import ( + "slices" + "time" + + "code.haedhutner.dev/mvv/LastMUD/internal/logging" + "github.com/google/uuid" +) + +type Entity uuid.UUID + +func CreateEntity(uuid uuid.UUID) Entity { + return Entity(uuid) +} + +func NewEntity() Entity { + return Entity(uuid.New()) +} + +func NilEntity() Entity { + return Entity(uuid.Nil) +} + +func (e Entity) AsUUID() uuid.UUID { + return uuid.UUID(e) +} + +type ComponentType int16 + +type Resource string + +type Component interface { + Type() ComponentType +} + +type ComponentStorage[T Component] struct { + forType ComponentType + storage map[Entity]T +} + +func CreateComponentStorage[T Component](forType ComponentType) *ComponentStorage[T] { + return &ComponentStorage[T]{ + forType: forType, + storage: map[Entity]T{}, + } +} + +func (cs *ComponentStorage[T]) ComponentType() ComponentType { + return cs.forType +} + +func (cs *ComponentStorage[T]) Set(e Entity, component T) { + cs.storage[e] = component +} + +func (cs *ComponentStorage[T]) Get(e Entity) (component T, ok bool) { + component, ok = cs.storage[e] + return +} + +func (cs *ComponentStorage[T]) Delete(e Entity) { + delete(cs.storage, e) +} + +func (cs *ComponentStorage[T]) All() map[Entity]T { + return cs.storage +} + +type System struct { + name string + priority int + work func(world *World, delta time.Duration) (err error) +} + +func CreateSystem(name string, priority int, work func(world *World, delta time.Duration) (err error)) *System { + return &System{ + name: name, + priority: priority, + work: work, + } +} + +func (s *System) Priority() int { + return s.priority +} + +func (s *System) DoWork(world *World, delta time.Duration) { + err := s.work(world, delta) + + if err != nil { + logging.Error("Error in system '", s.name, "': ", err.Error()) + } +} + +type World struct { + systems []*System + componentsByType map[ComponentType]any + componentsByEntity map[Entity]map[ComponentType]any + resources map[Resource]any +} + +func CreateWorld() (world *World) { + world = &World{ + systems: []*System{}, + componentsByType: map[ComponentType]any{}, + componentsByEntity: map[Entity]map[ComponentType]any{}, // TODO: Can't figure out use-case right now + resources: map[Resource]any{}, + } + + return +} + +func (w *World) Tick(delta time.Duration) { + for _, s := range w.systems { + s.DoWork(w, delta) + } +} + +func DeleteEntity(world *World, entity Entity) { + for _, s := range world.componentsByType { + storage, ok := s.(*ComponentStorage[Component]) + + if ok { + storage.Delete(entity) + } + } +} + +func SetResource(world *World, r Resource, val any) { + world.resources[r] = val +} + +func GetResource[T any](world *World, r Resource) (res T, err error) { + val, ok := world.resources[r] + + if !ok { + err = newECSError("Resource '", r, "' not found.") + return + } + + res, ok = val.(T) + + if !ok { + err = newECSError("Incompatible type for resource '", r, "'") + } + + return +} + +func RemoveResource(world *World, r Resource) { + delete(world.resources, r) +} + +func RegisterComponent[T Component](world *World, compType ComponentType) { + world.componentsByType[compType] = CreateComponentStorage[T](compType) +} + +func SetComponent[T Component](world *World, entity Entity, component T) { + compStorage := world.componentsByType[component.Type()].(*ComponentStorage[T]) + compStorage.Set(entity, component) + + // if _, ok := world.componentsByEntity[entity]; !ok { + // world.componentsByEntity[entity] = map[ComponentType]any{} + // } + + // world.componentsByEntity[entity][component.Type()] = component +} + +func GetComponent[T Component](world *World, entity Entity) (component T, exists bool) { + storage := GetComponentStorage[T](world) + + return storage.Get(entity) +} + +func DeleteComponent[T Component](world *World, entity Entity) { + storage := GetComponentStorage[T](world) + + storage.Delete(entity) +} + +func GetComponentStorage[T Component](world *World) (compStorage *ComponentStorage[T]) { + var zero T + + return world.componentsByType[zero.Type()].(*ComponentStorage[T]) +} + +func RegisterSystem(world *World, s *System) { + world.systems = append(world.systems, s) + slices.SortFunc( + world.systems, + func(a, b *System) int { + return a.priority - b.priority + }, + ) +} diff --git a/internal/game/ecs/entity.go b/internal/game/ecs/entity.go deleted file mode 100644 index f9d8197..0000000 --- a/internal/game/ecs/entity.go +++ /dev/null @@ -1,19 +0,0 @@ -package ecs - -import "github.com/google/uuid" - -type Entity uuid.UUID - -type Room struct { - Entity - NameComponent - DescriptionComponent - NeighboringRoomsComponent -} - -type Player struct { - Entity - PlayerStateComponent - NameComponent - InRoomComponent -} diff --git a/internal/game/ecs/error.go b/internal/game/ecs/error.go new file mode 100644 index 0000000..b1b6a26 --- /dev/null +++ b/internal/game/ecs/error.go @@ -0,0 +1,23 @@ +package ecs + +import "fmt" + +type ecsError struct { + err string +} + +func newECSError(v ...any) *ecsError { + return &ecsError{ + err: fmt.Sprint(v...), + } +} + +func newFormattedECSError(format string, v ...any) *ecsError { + return &ecsError{ + err: fmt.Sprintf(format, v...), + } +} + +func (err *ecsError) Error() string { + return err.err +} diff --git a/internal/game/ecs/systems.go b/internal/game/ecs/systems.go deleted file mode 100644 index 7f32d0d..0000000 --- a/internal/game/ecs/systems.go +++ /dev/null @@ -1 +0,0 @@ -package ecs diff --git a/internal/game/ecs/world.go b/internal/game/ecs/world.go deleted file mode 100644 index 7bc3de8..0000000 --- a/internal/game/ecs/world.go +++ /dev/null @@ -1,15 +0,0 @@ -package ecs - -type World struct { - Players []*Player - Rooms []*Room - DefaultRoom *Room -} - -func CreateWorld() *World { - world := &World{ - Players: []*Player{}, - Rooms: []*Room{}, - } - -} diff --git a/internal/game/entities.go b/internal/game/entities.go new file mode 100644 index 0000000..d722b4b --- /dev/null +++ b/internal/game/entities.go @@ -0,0 +1,39 @@ +package game + +import ( + "code.haedhutner.dev/mvv/LastMUD/internal/game/components" + "code.haedhutner.dev/mvv/LastMUD/internal/game/ecs" + "github.com/google/uuid" +) + +func CreatePlayer(world *ecs.World, id uuid.UUID, state components.PlayerState) (entity ecs.Entity, err error) { + entity = ecs.CreateEntity(id) + + defaultRoom, err := ecs.GetResource[ecs.Entity](world, ResourceDefaultRoom) + + if err != nil { + return + } + + ecs.SetComponent(world, entity, components.PlayerStateComponent{State: state}) + ecs.SetComponent(world, entity, components.NameComponent{Name: id.String()}) + ecs.SetComponent(world, entity, components.InRoomComponent{Room: defaultRoom}) + ecs.SetComponent(world, entity, components.IsPlayerComponent{}) + + return +} + +func CreateRoom( + world *ecs.World, + name, description string, + north, south, east, west ecs.Entity, +) ecs.Entity { + entity := ecs.NewEntity() + + ecs.SetComponent(world, entity, components.IsRoomComponent{}) + ecs.SetComponent(world, entity, components.NameComponent{Name: name}) + ecs.SetComponent(world, entity, components.DescriptionComponent{Description: description}) + ecs.SetComponent(world, entity, components.NeighborsComponent{North: north, South: south, East: east, West: west}) + + return entity +} diff --git a/internal/game/events.go b/internal/game/events.go index bee19ea..d69db75 100644 --- a/internal/game/events.go +++ b/internal/game/events.go @@ -4,6 +4,8 @@ import ( "time" "code.haedhutner.dev/mvv/LastMUD/internal/game/command" + "code.haedhutner.dev/mvv/LastMUD/internal/game/components" + "code.haedhutner.dev/mvv/LastMUD/internal/game/ecs" "code.haedhutner.dev/mvv/LastMUD/internal/logging" "github.com/google/uuid" ) @@ -23,10 +25,14 @@ func (pje *PlayerJoinEvent) Type() EventType { } func (pje *PlayerJoinEvent) Handle(game *LastMUDGame, delta time.Duration) { - p := CreateJoiningPlayer(pje.connectionId) - game.world.AddPlayerToDefaultRoom(p) - game.enqeueOutput(game.CreateOutput(p.Identity(), []byte("Welcome to LastMUD!"))) - game.enqeueOutput(game.CreateOutput(p.Identity(), []byte("Please enter your name:"))) + p, err := CreatePlayer(game.world.World, pje.connectionId, components.PlayerStateJoining) + + if err != nil { + logging.Error("Unabled to create player: ", err) + } + + game.enqeueOutput(game.CreateOutput(p.AsUUID(), []byte("Welcome to LastMUD!"))) + game.enqeueOutput(game.CreateOutput(p.AsUUID(), []byte("Please enter your name:"))) } type PlayerLeaveEvent struct { @@ -44,7 +50,7 @@ func (ple *PlayerLeaveEvent) Type() EventType { } func (ple *PlayerLeaveEvent) Handle(game *LastMUDGame, delta time.Duration) { - game.world.RemovePlayerById(ple.connectionId) + ecs.DeleteEntity(game.world.World, ecs.CreateEntity(ple.connectionId)) } type PlayerCommandEvent struct { @@ -72,8 +78,6 @@ func (pce *PlayerCommandEvent) Type() EventType { } func (pce *PlayerCommandEvent) Handle(game *LastMUDGame, delta time.Duration) { - player := game.world.FindPlayerById(pce.connectionId) - if player == nil { logging.Error("Unable to handle player command from player with id", pce.connectionId, ": Player does not exist") return @@ -82,7 +86,7 @@ func (pce *PlayerCommandEvent) Handle(game *LastMUDGame, delta time.Duration) { event := pce.parseCommandIntoEvent(game, player) } -func (pce *PlayerCommandEvent) parseCommandIntoEvent(game *LastMUDGame, player *Player) GameEvent { +func (pce *PlayerCommandEvent) parseCommandIntoEvent(game *LastMUDGame, player ecs.Entity) GameEvent { switch pce.command.Command().Definition().Name() { case SayCommand: speech, err := pce.command.Command().Parameters()[0].AsString() diff --git a/internal/game/game.go b/internal/game/game.go index 13ccabe..7539dfb 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -41,7 +41,7 @@ type LastMUDGame struct { cmdRegistry *command.CommandRegistry - world *World + world *GameWorld eventBus *EventBus @@ -54,7 +54,7 @@ func CreateGame(ctx context.Context, wg *sync.WaitGroup) (game *LastMUDGame) { ctx: ctx, eventBus: CreateEventBus(MaxEnqueuedGameEventsPerTick), output: make(chan GameOutput, MaxEnqueuedOutputPerTick), - world: CreateWorld(), + world: CreateGameWorld(), } game.cmdRegistry = game.CreateGameCommandRegistry() diff --git a/internal/game/player.go b/internal/game/player.go index bc98392..f1aee49 100644 --- a/internal/game/player.go +++ b/internal/game/player.go @@ -1,39 +1,39 @@ package game -import "github.com/google/uuid" +// import "github.com/google/uuid" -type Player struct { - id uuid.UUID +// type Player struct { +// id uuid.UUID - state PlayerState +// state PlayerState - currentRoom *Room -} +// currentRoom *Room +// } -func CreateJoiningPlayer(identity uuid.UUID) *Player { - return &Player{ - id: identity, - state: PlayerStateJoining, - currentRoom: nil, - } -} +// func CreateJoiningPlayer(identity uuid.UUID) *Player { +// return &Player{ +// id: identity, +// state: PlayerStateJoining, +// currentRoom: nil, +// } +// } -func CreatePlayer(identity uuid.UUID, state PlayerState, room *Room) *Player { - return &Player{ - id: identity, - state: state, - currentRoom: room, - } -} +// func CreatePlayer(identity uuid.UUID, state PlayerState, room *Room) *Player { +// return &Player{ +// id: identity, +// state: state, +// currentRoom: room, +// } +// } -func (p *Player) Identity() uuid.UUID { - return p.id -} +// func (p *Player) Identity() uuid.UUID { +// return p.id +// } -func (p *Player) SetRoom(r *Room) { - p.currentRoom = r -} +// func (p *Player) SetRoom(r *Room) { +// p.currentRoom = r +// } -func (p *Player) CurrentRoom() *Room { - return p.currentRoom -} +// func (p *Player) CurrentRoom() *Room { +// return p.currentRoom +// } diff --git a/internal/game/room.go b/internal/game/room.go index d047142..db4861f 100644 --- a/internal/game/room.go +++ b/internal/game/room.go @@ -1,51 +1,51 @@ package game -import "github.com/google/uuid" +// import "github.com/google/uuid" -type RoomPlayer interface { - Identity() uuid.UUID - SetRoom(room *Room) -} +// type RoomPlayer interface { +// Identity() uuid.UUID +// SetRoom(room *Room) +// } -type Room struct { - world *World +// type Room struct { +// world *World - North *Room - South *Room - East *Room - West *Room +// North *Room +// South *Room +// East *Room +// West *Room - Name string - Description string +// Name string +// Description string - players map[uuid.UUID]RoomPlayer -} +// players map[uuid.UUID]RoomPlayer +// } -func CreateRoom(world *World, name, description string) *Room { - return &Room{ - world: world, - Name: name, - Description: description, - players: map[uuid.UUID]RoomPlayer{}, - } -} +// func CreateRoom(world *World, name, description string) *Room { +// return &Room{ +// world: world, +// Name: name, +// Description: description, +// players: map[uuid.UUID]RoomPlayer{}, +// } +// } -func (r *Room) PlayerJoinRoom(player RoomPlayer) (err error) { - r.players[player.Identity()] = player +// func (r *Room) PlayerJoinRoom(player RoomPlayer) (err error) { +// r.players[player.Identity()] = player - return -} +// return +// } -func (r *Room) PlayerLeaveRoom(player RoomPlayer) (err error) { - delete(r.players, player.Identity()) +// func (r *Room) PlayerLeaveRoom(player RoomPlayer) (err error) { +// delete(r.players, player.Identity()) - return -} +// return +// } -func (r *Room) Players() map[uuid.UUID]RoomPlayer { - return r.players -} +// func (r *Room) Players() map[uuid.UUID]RoomPlayer { +// return r.players +// } -func (r *Room) World() *World { - return r.world -} +// func (r *Room) World() *World { +// return r.world +// } diff --git a/internal/game/systems.go b/internal/game/systems.go new file mode 100644 index 0000000..cde26fe --- /dev/null +++ b/internal/game/systems.go @@ -0,0 +1 @@ +package game diff --git a/internal/game/world.go b/internal/game/world.go index ba15af0..fb89d42 100644 --- a/internal/game/world.go +++ b/internal/game/world.go @@ -1,80 +1,199 @@ package game -import "github.com/google/uuid" +import ( + "code.haedhutner.dev/mvv/LastMUD/internal/game/components" + "code.haedhutner.dev/mvv/LastMUD/internal/game/ecs" +) -type World struct { - rooms []*Room - players map[uuid.UUID]*Player - defaultRoom *Room +const ( + ResourceDefaultRoom ecs.Resource = "world:room:default" +) + +type GameWorld struct { + *ecs.World } -func CreateWorld() (world *World) { - world = &World{ - players: map[uuid.UUID]*Player{}, +func CreateGameWorld() (gw *GameWorld) { + gw = &GameWorld{ + World: ecs.CreateWorld(), } - forest := CreateRoom(world, "Forest", "A dense, misty forest stretches endlessly, its towering trees whispering secrets through rustling leaves. Sunbeams filter through the canopy, dappling the mossy ground with golden light.") - cabin := CreateRoom(world, "Wooden Cabin", "The cabin’s interior is cozy and rustic, with wooden beams overhead and a stone fireplace crackling warmly. A wool rug lies on creaky floorboards, and shelves brim with books, mugs, and old lanterns.") - lake := CreateRoom(world, "Ethermere Lake", "Ethermire Lake lies shrouded in mist, its dark, still waters reflecting a sky perpetually overcast. Whispers ride the wind, and strange lights flicker beneath the surface, never breaking it.") - graveyard := CreateRoom(world, "Graveyard", "An overgrown graveyard shrouded in fog, with cracked headstones and leaning statues. The wind sighs through dead trees, and unseen footsteps echo faintly among the mossy graves.") - chapel := CreateRoom(world, "Chapel of the Hollow Light", "This ruined chapel leans under ivy and age. Faint light filters through shattered stained glass, casting broken rainbows across dust-choked pews and a long-silent altar.") + forest := CreateRoom( + gw.World, + "Forest", + "A dense, misty forest stretches endlessly, its towering trees whispering secrets through rustling leaves. Sunbeams filter through the canopy, dappling the mossy ground with golden light.", + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ) - forest.North = cabin - forest.South = graveyard - forest.East = lake - forest.West = chapel + ecs.SetResource(gw.World, ResourceDefaultRoom, forest) - cabin.South = forest - cabin.West = chapel - cabin.East = lake + cabin := CreateRoom( + gw.World, + "Wooden Cabin", + "The cabin’s interior is cozy and rustic, with wooden beams overhead and a stone fireplace crackling warmly. A wool rug lies on creaky floorboards, and shelves brim with books, mugs, and old lanterns.", + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ) - chapel.North = cabin - chapel.South = graveyard - chapel.East = forest + lake := CreateRoom( + gw.World, + "Ethermere Lake", + "Ethermire Lake lies shrouded in mist, its dark, still waters reflecting a sky perpetually overcast. Whispers ride the wind, and strange lights flicker beneath the surface, never breaking it.", + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ) - lake.West = forest - lake.North = cabin - lake.South = graveyard + graveyard := CreateRoom( + gw.World, + "Graveyard", + "An overgrown graveyard shrouded in fog, with cracked headstones and leaning statues. The wind sighs through dead trees, and unseen footsteps echo faintly among the mossy graves.", + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ) - graveyard.North = forest - graveyard.West = chapel - graveyard.East = lake + chapel := CreateRoom( + gw.World, + "Chapel of the Hollow Light", + "This ruined chapel leans under ivy and age. Faint light filters through shattered stained glass, casting broken rainbows across dust-choked pews and a long-silent altar.", + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ecs.NilEntity(), + ) - world.rooms = []*Room{ - forest, - cabin, - lake, - graveyard, - chapel, - } + ecs.SetComponent(gw.World, forest, components.NeighborsComponent{ + North: cabin, + South: graveyard, + East: lake, + West: chapel, + }) - world.defaultRoom = forest + ecs.SetComponent(gw.World, cabin, components.NeighborsComponent{ + South: graveyard, + West: chapel, + East: lake, + }) + + ecs.SetComponent(gw.World, chapel, components.NeighborsComponent{ + North: cabin, + South: graveyard, + East: forest, + }) + + ecs.SetComponent(gw.World, lake, components.NeighborsComponent{ + West: forest, + North: cabin, + South: graveyard, + }) + + ecs.SetComponent(gw.World, graveyard, components.NeighborsComponent{ + North: forest, + West: chapel, + East: lake, + }) return } -func (w *World) AddPlayerToDefaultRoom(p *Player) { - w.players[p.Identity()] = p - w.defaultRoom.PlayerJoinRoom(p) - p.SetRoom(w.defaultRoom) -} +// type World struct { +// // rooms []*Room +// // players map[uuid.UUID]*Player +// // defaultRoom *Room -func (w *World) RemovePlayerById(id uuid.UUID) { - p, ok := w.players[id] +// entities []Entity +// systems []*System +// components map[ComponentType]any +// } - if ok { - p.currentRoom.PlayerLeaveRoom(p) - delete(w.players, id) - return - } -} +// func CreateWorld() (world *World) { +// world = &World{ +// entities: []Entity{}, +// systems: []*System{}, +// components: map[ComponentType]any{}, +// } +// // world = &World{ +// // players: map[uuid.UUID]*Player{}, +// // } -func (w *World) FindPlayerById(id uuid.UUID) *Player { - p, ok := w.players[id] +// // forest := CreateRoom(world, "Forest", "A dense, misty forest stretches endlessly, its towering trees whispering secrets through rustling leaves. Sunbeams filter through the canopy, dappling the mossy ground with golden light.") +// // cabin := CreateRoom(world, "Wooden Cabin", "The cabin’s interior is cozy and rustic, with wooden beams overhead and a stone fireplace crackling warmly. A wool rug lies on creaky floorboards, and shelves brim with books, mugs, and old lanterns.") +// // lake := CreateRoom(world, "Ethermere Lake", "Ethermire Lake lies shrouded in mist, its dark, still waters reflecting a sky perpetually overcast. Whispers ride the wind, and strange lights flicker beneath the surface, never breaking it.") +// // graveyard := CreateRoom(world, "Graveyard", "An overgrown graveyard shrouded in fog, with cracked headstones and leaning statues. The wind sighs through dead trees, and unseen footsteps echo faintly among the mossy graves.") +// // chapel := CreateRoom(world, "Chapel of the Hollow Light", "This ruined chapel leans under ivy and age. Faint light filters through shattered stained glass, casting broken rainbows across dust-choked pews and a long-silent altar.") - if ok { - return p - } else { - return nil - } -} +// // forest.North = cabin +// // forest.South = graveyard +// // forest.East = lake +// // forest.West = chapel + +// // cabin.South = forest +// // cabin.West = chapel +// // cabin.East = lake + +// // chapel.North = cabin +// // chapel.South = graveyard +// // chapel.East = forest + +// // lake.West = forest +// // lake.North = cabin +// // lake.South = graveyard + +// // graveyard.North = forest +// // graveyard.West = chapel +// // graveyard.East = lake + +// // world.rooms = []*Room{ +// // forest, +// // cabin, +// // lake, +// // graveyard, +// // chapel, +// // } + +// // world.defaultRoom = forest + +// return +// } + +// func RegisterComponentType[T any](world *World, compType ComponentType) { +// world.components[compType] = CreateComponentStorage[any](compType) +// } + +// func SetComponent[T](compType ComponentType, ent Entity, component any) { +// world.components[compType] +// } + +// func (w *World) AddPlayerToDefaultRoom(p *Player) { +// w.players[p.Identity()] = p +// w.defaultRoom.PlayerJoinRoom(p) +// p.SetRoom(w.defaultRoom) +// } + +// func (w *World) RemovePlayerById(id uuid.UUID) { +// p, ok := w.players[id] + +// if ok { +// p.currentRoom.PlayerLeaveRoom(p) +// delete(w.players, id) +// return +// } +// } + +// func (w *World) FindPlayerById(id uuid.UUID) *Player { +// p, ok := w.players[id] + +// if ok { +// return p +// } else { +// return nil +// } +// }