LastMUD/internal/game/game.go

158 lines
3.2 KiB
Go
Raw Normal View History

2025-06-20 16:26:39 +03:00
package game
import (
"context"
"sync"
"time"
"code.haedhutner.dev/mvv/LastMUD/internal/ecs"
2025-06-22 22:27:56 +03:00
"code.haedhutner.dev/mvv/LastMUD/internal/game/command"
"code.haedhutner.dev/mvv/LastMUD/internal/game/data"
"code.haedhutner.dev/mvv/LastMUD/internal/game/systems"
2025-06-20 16:26:39 +03:00
"code.haedhutner.dev/mvv/LastMUD/internal/logging"
2025-06-22 17:54:07 +03:00
"github.com/google/uuid"
2025-06-20 16:26:39 +03:00
)
const TickRate = time.Duration(50 * time.Millisecond)
2025-06-24 16:37:26 +03:00
const MaxEnqueuedOutputPerTick = 100
2025-06-22 17:54:07 +03:00
type GameOutput struct {
connId uuid.UUID
contents []byte
}
2025-06-22 22:27:56 +03:00
func (game *LastMUDGame) CreateOutput(connId uuid.UUID, contents []byte) GameOutput {
2025-06-22 17:54:07 +03:00
return GameOutput{
connId: connId,
contents: contents,
}
}
func (g GameOutput) Id() uuid.UUID {
return g.connId
}
func (g GameOutput) Contents() []byte {
return g.contents
2025-06-20 16:26:39 +03:00
}
type LastMUDGame struct {
ctx context.Context
wg *sync.WaitGroup
2025-06-22 17:54:07 +03:00
2025-06-24 16:37:26 +03:00
cmdRegistry *command.CommandRegistry
world *data.GameWorld
2025-06-22 17:54:07 +03:00
output chan GameOutput
2025-06-20 16:26:39 +03:00
}
func CreateGame(ctx context.Context, wg *sync.WaitGroup) (game *LastMUDGame) {
game = &LastMUDGame{
wg: wg,
ctx: ctx,
output: make(chan GameOutput, MaxEnqueuedOutputPerTick),
world: data.CreateGameWorld(),
2025-06-20 16:26:39 +03:00
}
ecs.RegisterSystems(game.world.World, systems.CreateEventSystems()...)
2025-06-24 16:37:26 +03:00
game.cmdRegistry = game.CreateGameCommandRegistry()
2025-06-22 22:27:56 +03:00
2025-06-20 16:26:39 +03:00
wg.Add(1)
go game.start()
return
}
2025-06-24 16:37:26 +03:00
func (game *LastMUDGame) ConsumeNextOutput() *GameOutput {
select {
case output := <-game.output:
return &output
default:
return nil
}
}
func (game *LastMUDGame) ConnectPlayer(connectionId uuid.UUID) {
data.CreatePlayerConnectEvent(game.world.World, connectionId)
}
func (game *LastMUDGame) DisconnectPlayer(connectionId uuid.UUID) {
data.CreatePlayerDisconnectEvent(game.world.World, connectionId)
}
func (game *LastMUDGame) SendPlayerCommand(connectionId uuid.UUID, command string) {
data.CreatePlayerCommandEvent(game.world.World, connectionId, command)
}
2025-06-24 16:37:26 +03:00
func (game *LastMUDGame) commandRegistry() *command.CommandRegistry {
return game.cmdRegistry
}
2025-06-20 16:26:39 +03:00
func (game *LastMUDGame) start() {
defer game.wg.Done()
defer game.shutdown()
logging.Info("Starting LastMUD...")
lastTick := time.Now()
for {
now := time.Now()
if game.shouldStop() {
break
}
game.tick(now.Sub(lastTick))
// Tick at regular intervals
if time.Since(lastTick) < TickRate {
time.Sleep(TickRate - time.Since(lastTick))
}
lastTick = now
}
}
func (game *LastMUDGame) consumeOutputs() {
entities := ecs.FindEntitiesWithComponents(game.world.World, data.TypeConnectionId, data.TypeContents)
for _, entity := range entities {
connId, _ := ecs.GetComponent[data.ConnectionIdComponent](game.world.World, entity)
contents, _ := ecs.GetComponent[data.ContentsComponent](game.world.World, entity)
game.enqeueOutput(GameOutput{
connId: connId.ConnectionId,
contents: contents.Contents,
})
}
2025-06-27 09:45:55 +03:00
ecs.DeleteEntities(game.world.World, entities...)
}
2025-06-20 16:26:39 +03:00
func (game *LastMUDGame) shutdown() {
logging.Info("Stopping LastMUD...")
2025-06-22 17:54:07 +03:00
close(game.output)
2025-06-20 16:26:39 +03:00
}
func (game *LastMUDGame) shouldStop() bool {
select {
case <-game.ctx.Done():
return true
default:
return false
}
}
2025-06-22 17:54:07 +03:00
func (game *LastMUDGame) enqeueOutput(output GameOutput) {
game.output <- output
}
2025-06-20 16:26:39 +03:00
func (g *LastMUDGame) tick(delta time.Duration) {
g.world.Tick(delta)
g.consumeOutputs()
2025-06-20 16:26:39 +03:00
}