ECS refactor
This commit is contained in:
parent
87f5c2f842
commit
b2212a279c
16 changed files with 598 additions and 200 deletions
29
internal/game/components/common.go
Normal file
29
internal/game/components/common.go
Normal file
|
@ -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
|
||||||
|
}
|
35
internal/game/components/player.go
Normal file
35
internal/game/components/player.go
Normal file
|
@ -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
|
||||||
|
}
|
18
internal/game/components/room.go
Normal file
18
internal/game/components/room.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
196
internal/game/ecs/ecs.go
Normal file
196
internal/game/ecs/ecs.go
Normal file
|
@ -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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
23
internal/game/ecs/error.go
Normal file
23
internal/game/ecs/error.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
package ecs
|
|
|
@ -1,15 +0,0 @@
|
||||||
package ecs
|
|
||||||
|
|
||||||
type World struct {
|
|
||||||
Players []*Player
|
|
||||||
Rooms []*Room
|
|
||||||
DefaultRoom *Room
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateWorld() *World {
|
|
||||||
world := &World{
|
|
||||||
Players: []*Player{},
|
|
||||||
Rooms: []*Room{},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
39
internal/game/entities.go
Normal file
39
internal/game/entities.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.haedhutner.dev/mvv/LastMUD/internal/game/command"
|
"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"
|
"code.haedhutner.dev/mvv/LastMUD/internal/logging"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
@ -23,10 +25,14 @@ func (pje *PlayerJoinEvent) Type() EventType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pje *PlayerJoinEvent) Handle(game *LastMUDGame, delta time.Duration) {
|
func (pje *PlayerJoinEvent) Handle(game *LastMUDGame, delta time.Duration) {
|
||||||
p := CreateJoiningPlayer(pje.connectionId)
|
p, err := CreatePlayer(game.world.World, pje.connectionId, components.PlayerStateJoining)
|
||||||
game.world.AddPlayerToDefaultRoom(p)
|
|
||||||
game.enqeueOutput(game.CreateOutput(p.Identity(), []byte("Welcome to LastMUD!")))
|
if err != nil {
|
||||||
game.enqeueOutput(game.CreateOutput(p.Identity(), []byte("Please enter your name:")))
|
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 {
|
type PlayerLeaveEvent struct {
|
||||||
|
@ -44,7 +50,7 @@ func (ple *PlayerLeaveEvent) Type() EventType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ple *PlayerLeaveEvent) Handle(game *LastMUDGame, delta time.Duration) {
|
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 {
|
type PlayerCommandEvent struct {
|
||||||
|
@ -72,8 +78,6 @@ func (pce *PlayerCommandEvent) Type() EventType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pce *PlayerCommandEvent) Handle(game *LastMUDGame, delta time.Duration) {
|
func (pce *PlayerCommandEvent) Handle(game *LastMUDGame, delta time.Duration) {
|
||||||
player := game.world.FindPlayerById(pce.connectionId)
|
|
||||||
|
|
||||||
if player == nil {
|
if player == nil {
|
||||||
logging.Error("Unable to handle player command from player with id", pce.connectionId, ": Player does not exist")
|
logging.Error("Unable to handle player command from player with id", pce.connectionId, ": Player does not exist")
|
||||||
return
|
return
|
||||||
|
@ -82,7 +86,7 @@ func (pce *PlayerCommandEvent) Handle(game *LastMUDGame, delta time.Duration) {
|
||||||
event := pce.parseCommandIntoEvent(game, player)
|
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() {
|
switch pce.command.Command().Definition().Name() {
|
||||||
case SayCommand:
|
case SayCommand:
|
||||||
speech, err := pce.command.Command().Parameters()[0].AsString()
|
speech, err := pce.command.Command().Parameters()[0].AsString()
|
||||||
|
|
|
@ -41,7 +41,7 @@ type LastMUDGame struct {
|
||||||
|
|
||||||
cmdRegistry *command.CommandRegistry
|
cmdRegistry *command.CommandRegistry
|
||||||
|
|
||||||
world *World
|
world *GameWorld
|
||||||
|
|
||||||
eventBus *EventBus
|
eventBus *EventBus
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ func CreateGame(ctx context.Context, wg *sync.WaitGroup) (game *LastMUDGame) {
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
eventBus: CreateEventBus(MaxEnqueuedGameEventsPerTick),
|
eventBus: CreateEventBus(MaxEnqueuedGameEventsPerTick),
|
||||||
output: make(chan GameOutput, MaxEnqueuedOutputPerTick),
|
output: make(chan GameOutput, MaxEnqueuedOutputPerTick),
|
||||||
world: CreateWorld(),
|
world: CreateGameWorld(),
|
||||||
}
|
}
|
||||||
|
|
||||||
game.cmdRegistry = game.CreateGameCommandRegistry()
|
game.cmdRegistry = game.CreateGameCommandRegistry()
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "github.com/google/uuid"
|
// import "github.com/google/uuid"
|
||||||
|
|
||||||
type Player struct {
|
// type Player struct {
|
||||||
id uuid.UUID
|
// id uuid.UUID
|
||||||
|
|
||||||
state PlayerState
|
// state PlayerState
|
||||||
|
|
||||||
currentRoom *Room
|
// currentRoom *Room
|
||||||
}
|
// }
|
||||||
|
|
||||||
func CreateJoiningPlayer(identity uuid.UUID) *Player {
|
// func CreateJoiningPlayer(identity uuid.UUID) *Player {
|
||||||
return &Player{
|
// return &Player{
|
||||||
id: identity,
|
// id: identity,
|
||||||
state: PlayerStateJoining,
|
// state: PlayerStateJoining,
|
||||||
currentRoom: nil,
|
// currentRoom: nil,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func CreatePlayer(identity uuid.UUID, state PlayerState, room *Room) *Player {
|
// func CreatePlayer(identity uuid.UUID, state PlayerState, room *Room) *Player {
|
||||||
return &Player{
|
// return &Player{
|
||||||
id: identity,
|
// id: identity,
|
||||||
state: state,
|
// state: state,
|
||||||
currentRoom: room,
|
// currentRoom: room,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *Player) Identity() uuid.UUID {
|
// func (p *Player) Identity() uuid.UUID {
|
||||||
return p.id
|
// return p.id
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *Player) SetRoom(r *Room) {
|
// func (p *Player) SetRoom(r *Room) {
|
||||||
p.currentRoom = r
|
// p.currentRoom = r
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *Player) CurrentRoom() *Room {
|
// func (p *Player) CurrentRoom() *Room {
|
||||||
return p.currentRoom
|
// return p.currentRoom
|
||||||
}
|
// }
|
||||||
|
|
|
@ -1,51 +1,51 @@
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import "github.com/google/uuid"
|
// import "github.com/google/uuid"
|
||||||
|
|
||||||
type RoomPlayer interface {
|
// type RoomPlayer interface {
|
||||||
Identity() uuid.UUID
|
// Identity() uuid.UUID
|
||||||
SetRoom(room *Room)
|
// SetRoom(room *Room)
|
||||||
}
|
// }
|
||||||
|
|
||||||
type Room struct {
|
// type Room struct {
|
||||||
world *World
|
// world *World
|
||||||
|
|
||||||
North *Room
|
// North *Room
|
||||||
South *Room
|
// South *Room
|
||||||
East *Room
|
// East *Room
|
||||||
West *Room
|
// West *Room
|
||||||
|
|
||||||
Name string
|
// Name string
|
||||||
Description string
|
// Description string
|
||||||
|
|
||||||
players map[uuid.UUID]RoomPlayer
|
// players map[uuid.UUID]RoomPlayer
|
||||||
}
|
// }
|
||||||
|
|
||||||
func CreateRoom(world *World, name, description string) *Room {
|
// func CreateRoom(world *World, name, description string) *Room {
|
||||||
return &Room{
|
// return &Room{
|
||||||
world: world,
|
// world: world,
|
||||||
Name: name,
|
// Name: name,
|
||||||
Description: description,
|
// Description: description,
|
||||||
players: map[uuid.UUID]RoomPlayer{},
|
// players: map[uuid.UUID]RoomPlayer{},
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Room) PlayerJoinRoom(player RoomPlayer) (err error) {
|
// func (r *Room) PlayerJoinRoom(player RoomPlayer) (err error) {
|
||||||
r.players[player.Identity()] = player
|
// r.players[player.Identity()] = player
|
||||||
|
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Room) PlayerLeaveRoom(player RoomPlayer) (err error) {
|
// func (r *Room) PlayerLeaveRoom(player RoomPlayer) (err error) {
|
||||||
delete(r.players, player.Identity())
|
// delete(r.players, player.Identity())
|
||||||
|
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Room) Players() map[uuid.UUID]RoomPlayer {
|
// func (r *Room) Players() map[uuid.UUID]RoomPlayer {
|
||||||
return r.players
|
// return r.players
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *Room) World() *World {
|
// func (r *Room) World() *World {
|
||||||
return r.world
|
// return r.world
|
||||||
}
|
// }
|
||||||
|
|
1
internal/game/systems.go
Normal file
1
internal/game/systems.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package game
|
|
@ -1,80 +1,199 @@
|
||||||
package game
|
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 {
|
const (
|
||||||
rooms []*Room
|
ResourceDefaultRoom ecs.Resource = "world:room:default"
|
||||||
players map[uuid.UUID]*Player
|
)
|
||||||
defaultRoom *Room
|
|
||||||
|
type GameWorld struct {
|
||||||
|
*ecs.World
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateWorld() (world *World) {
|
func CreateGameWorld() (gw *GameWorld) {
|
||||||
world = &World{
|
gw = &GameWorld{
|
||||||
players: map[uuid.UUID]*Player{},
|
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.")
|
forest := CreateRoom(
|
||||||
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.")
|
gw.World,
|
||||||
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.")
|
"Forest",
|
||||||
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.")
|
"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.",
|
||||||
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.")
|
ecs.NilEntity(),
|
||||||
|
ecs.NilEntity(),
|
||||||
|
ecs.NilEntity(),
|
||||||
|
ecs.NilEntity(),
|
||||||
|
)
|
||||||
|
|
||||||
forest.North = cabin
|
ecs.SetResource(gw.World, ResourceDefaultRoom, forest)
|
||||||
forest.South = graveyard
|
|
||||||
forest.East = lake
|
|
||||||
forest.West = chapel
|
|
||||||
|
|
||||||
cabin.South = forest
|
cabin := CreateRoom(
|
||||||
cabin.West = chapel
|
gw.World,
|
||||||
cabin.East = lake
|
"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
|
lake := CreateRoom(
|
||||||
chapel.South = graveyard
|
gw.World,
|
||||||
chapel.East = forest
|
"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
|
graveyard := CreateRoom(
|
||||||
lake.North = cabin
|
gw.World,
|
||||||
lake.South = graveyard
|
"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
|
chapel := CreateRoom(
|
||||||
graveyard.West = chapel
|
gw.World,
|
||||||
graveyard.East = lake
|
"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{
|
ecs.SetComponent(gw.World, forest, components.NeighborsComponent{
|
||||||
forest,
|
North: cabin,
|
||||||
cabin,
|
South: graveyard,
|
||||||
lake,
|
East: lake,
|
||||||
graveyard,
|
West: chapel,
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *World) AddPlayerToDefaultRoom(p *Player) {
|
// type World struct {
|
||||||
w.players[p.Identity()] = p
|
// // rooms []*Room
|
||||||
w.defaultRoom.PlayerJoinRoom(p)
|
// // players map[uuid.UUID]*Player
|
||||||
p.SetRoom(w.defaultRoom)
|
// // defaultRoom *Room
|
||||||
}
|
|
||||||
|
|
||||||
func (w *World) RemovePlayerById(id uuid.UUID) {
|
// entities []Entity
|
||||||
p, ok := w.players[id]
|
// systems []*System
|
||||||
|
// components map[ComponentType]any
|
||||||
|
// }
|
||||||
|
|
||||||
if ok {
|
// func CreateWorld() (world *World) {
|
||||||
p.currentRoom.PlayerLeaveRoom(p)
|
// world = &World{
|
||||||
delete(w.players, id)
|
// entities: []Entity{},
|
||||||
return
|
// systems: []*System{},
|
||||||
}
|
// components: map[ComponentType]any{},
|
||||||
}
|
// }
|
||||||
|
// // world = &World{
|
||||||
|
// // players: map[uuid.UUID]*Player{},
|
||||||
|
// // }
|
||||||
|
|
||||||
func (w *World) FindPlayerById(id uuid.UUID) *Player {
|
// // 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.")
|
||||||
p, ok := w.players[id]
|
// // 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 {
|
// // forest.North = cabin
|
||||||
return p
|
// // forest.South = graveyard
|
||||||
} else {
|
// // forest.East = lake
|
||||||
return nil
|
// // 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
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
Loading…
Add table
Reference in a new issue