2025-06-28 11:24:06 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2025-06-29 18:21:22 +03:00
|
|
|
func HandleSubmitInput(w *ecs.World, event ecs.Entity) (err error) {
|
2025-06-28 11:24:06 +03:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2025-07-02 20:42:25 +03:00
|
|
|
player := world.FindPlayerByConnectionId(w, eventConnId.ConnectionId)
|
2025-06-28 11:24:06 +03:00
|
|
|
|
|
|
|
if player == ecs.NilEntity() {
|
|
|
|
return createCommandParseError("Unable to find valid player with provided connection id")
|
|
|
|
}
|
|
|
|
|
2025-07-02 20:42:25 +03:00
|
|
|
if world.IsPlayerInDirectInputMode(w, player) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2025-06-28 11:24:06 +03:00
|
|
|
tokens, err := tokenize(commandString.Command)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return createCommandParseError("Error with tokenization: ", err)
|
|
|
|
}
|
|
|
|
|
2025-07-02 20:42:25 +03:00
|
|
|
if world.IsPlayerInYNAnswerMode(w, player) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2025-06-28 11:24:06 +03:00
|
|
|
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 {
|
2025-06-29 18:21:22 +03:00
|
|
|
world.CreateGameOutput(w, connectionId.ConnectionId, "Unknown command")
|
2025-06-28 11:24:06 +03:00
|
|
|
ecs.DeleteEntity(w, cmdEnt.Entity)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|