LastMUD/internal/game/logic/event/command.go

165 lines
3.9 KiB
Go

package event
import (
"code.haedhutner.dev/mvv/LastMUD/internal/ecs"
"code.haedhutner.dev/mvv/LastMUD/internal/game/data"
"code.haedhutner.dev/mvv/LastMUD/internal/game/logic/command"
"code.haedhutner.dev/mvv/LastMUD/internal/game/logic/world"
"fmt"
"regexp"
)
type commandParseError struct {
err string
}
func createCommandParseError(v ...any) *commandParseError {
return &commandParseError{
err: fmt.Sprint("Error parsing command: ", v),
}
}
func (e *commandParseError) Error() string {
return e.err
}
func HandlePlayerCommand(w *ecs.World, event ecs.Entity) (err error) {
commandString, ok := ecs.GetComponent[data.CommandStringComponent](w, event)
if !ok {
return createCommandParseError("No command string found for event")
}
eventConnId, ok := ecs.GetComponent[data.ConnectionIdComponent](w, event)
if !ok {
return createCommandParseError("No connection id found for event")
}
player := ecs.NilEntity()
for p := range ecs.IterateEntitiesWithComponent[data.IsPlayerComponent](w) {
playerConnId, ok := ecs.GetComponent[data.ConnectionIdComponent](w, p)
if ok && playerConnId.ConnectionId == eventConnId.ConnectionId {
player = p
break
}
}
if player == ecs.NilEntity() {
return createCommandParseError("Unable to find valid player with provided connection id")
}
tokens, err := tokenize(commandString.Command)
if err != nil {
return createCommandParseError("Error with tokenization: ", err)
}
cmd := world.CreateTokenizedCommand(w, player, commandString.Command, tokens)
world.CreateParseCommandEvent(w, cmd)
return
}
func tokenize(commandString string) (tokens []data.Token, err error) {
tokens = []data.Token{}
pos := 0
inputLen := len(commandString)
// Continue iterating until we reach the end of the input
for pos < inputLen {
matched := false
remaining := commandString[pos:]
// Iterate through each token type and test its pattern
for tokenType, pattern := range data.TokenPatterns {
// If the token pattern doesn't compile, panic ( why do we have invalid patterns?! )
tokenPattern := regexp.MustCompile(pattern)
// If the location of the match isn't nil, that means we've found a match
if loc := tokenPattern.FindStringIndex(remaining); loc != nil {
lexeme := remaining[loc[0]:loc[1]]
pos += loc[1]
matched = true
// Skip whitespace
if tokenType == data.TokenWhitespace {
break
}
tokens = append(
tokens,
data.Token{
Type: tokenType,
Lexeme: lexeme,
Index: pos,
},
)
break
}
}
// Unknown tokens are still added
if !matched {
tokens = append(
tokens,
data.Token{
Type: data.TokenUnknown,
Lexeme: commandString[pos : pos+1],
Index: pos,
},
)
pos++
}
}
// Mark the end of the tokens
tokens = append(tokens, data.Token{Type: data.TokenEOF, Lexeme: "", Index: pos})
return
}
func HandleParseCommand(w *ecs.World, event ecs.Entity) (err error) {
cmdEnt, ok := ecs.GetComponent[data.EntityComponent](w, event)
if !ok {
return createCommandParseError("No command entity provided in event")
}
tokens, ok := ecs.GetComponent[data.TokensComponent](w, cmdEnt.Entity)
if !ok {
return createCommandParseError("No tokens provided in command entity")
}
var foundMatch bool
for cmd, parser := range command.Parsers {
match, args := parser(tokens.Tokens)
if !match {
continue
}
ecs.SetComponent(w, cmdEnt.Entity, data.ArgsComponent{Args: args})
ecs.SetComponent(w, cmdEnt.Entity, data.CommandComponent{Cmd: cmd})
ecs.SetComponent(w, cmdEnt.Entity, data.CommandStateComponent{State: data.CommandStateParsed})
foundMatch = true
}
player, _ := ecs.GetComponent[data.PlayerComponent](w, cmdEnt.Entity)
connectionId, _ := ecs.GetComponent[data.ConnectionIdComponent](w, player.Player)
if !foundMatch {
world.CreateGameOutput(w, connectionId.ConnectionId, []byte("Unknown command"))
ecs.DeleteEntity(w, cmdEnt.Entity)
}
return
}