Skip to content

Commit

Permalink
Merge pull request #317 from robinje/deployment-fix
Browse files Browse the repository at this point in the history
Deployment Fixes
  • Loading branch information
robinje authored Dec 28, 2024
2 parents 04bfbe7 + 599c2ac commit e064db6
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 130 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,9 @@ Deploying the server involves several steps:
This script will create the necessary AWS resources using CloudFormation.
6. Once deployment is complete, build and run the server:
```
go build ./ssh_server
./ssh_server
cd ./server
go build . -o server
./server
```

## License
Expand Down
2 changes: 1 addition & 1 deletion cloudformation/cognito.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Resources:
UserPoolTags:
Environment: Production
Application: MUD
DeletionProtection: ACTIVE
DeletionProtection: INACTIVE

CognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Expand Down
39 changes: 39 additions & 0 deletions server/archtype.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"fmt"
"sort"
"strconv"
"strings"
)

Expand All @@ -15,13 +17,15 @@ type Archetype struct {

// DisplayArchetypes logs the loaded archetypes for debugging purposes.
func DisplayArchetypes(g *Game) {
Logger.Debug("Archetypes:" + fmt.Sprint(len(g.ArcheTypes)))
for key, archtype := range g.ArcheTypes {
Logger.Debug("Archetype", "name", key, "description", archtype.Description)
}
}

// LoadArchetypes retrieves all archetypes from the DynamoDB table and stores them in the Server's ArcheTypes map.
func LoadArchetypes(g *Game) error {
DisplayArchetypes(g)
var archetypes []Archetype
err := g.Database.Scan("archetypes", &archetypes)
if err != nil {
Expand Down Expand Up @@ -71,3 +75,38 @@ func StoreArchetypes(g *Game) error {

return nil
}

func selectArchetype(player *Player) (string, error) {
options := buildArchetypeOptions(player.server.game)
if len(options) == 0 {
return "", nil // No archetypes available
}

msg := "\n\rSelect a character archetype.\n\r"
for i, option := range options {
msg += fmt.Sprintf("%d: %s\n\r", i+1, option)
}
msg += "Enter the number of your choice: "

player.toPlayer <- msg
selection, ok := <-player.fromPlayer
if !ok {
return "", fmt.Errorf("player input channel closed")
}

num, err := strconv.Atoi(strings.TrimSpace(selection))
if err != nil || num < 1 || num > len(options) {
return "", fmt.Errorf("invalid archetype selection")
}

return strings.Split(options[num-1], " - ")[0], nil
}

func buildArchetypeOptions(g *Game) []string {
options := make([]string, 0, len(g.ArcheTypes))
for name, archetype := range g.ArcheTypes {
options = append(options, name+" - "+archetype.Description)
}
sort.Strings(options)
return options
}
52 changes: 13 additions & 39 deletions server/character-select.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,31 @@ func createNewCharacter(player *Player) (*Character, error) {
return nil, err
}

archetype, err := selectArchetype(player)
if err != nil {
return nil, err
// Skip archetype selection if none exist
var archetype string
if len(player.server.game.ArcheTypes) > 0 {
archetype, err = selectArchetype(player)
if err != nil {
Logger.Warn("Error selecting archetype", "error", err)
archetype = ""
}
}

room, err := getStartingRoom(player.server.game, archetype)
if err != nil {
return nil, err
Logger.Warn("Error getting starting room", "error", err)
room = player.server.game.Rooms[0] // Fallback to Room 0
}

character, err := CreateCharacter(name, player, room, archetype, player.server.game)
if err != nil {
Logger.Warn("Error creating character", "error", err)
return nil, err
}

err = WriteCharacter(character, player.server.database)
if err != nil {
Logger.Warn("Error writing character", "error", err)
return nil, err
}

Expand All @@ -86,6 +94,7 @@ func createNewCharacter(player *Player) (*Character, error) {
player.mutex.Unlock()

if err := player.WritePlayer(); err != nil {
Logger.Warn("Error writing player", "error", err)
return nil, err
}

Expand Down Expand Up @@ -269,41 +278,6 @@ func validateCharacterName(name string, g *Game) error {
return nil
}

func selectArchetype(player *Player) (string, error) {
options := buildArchetypeOptions(player.server.game)
if len(options) == 0 {
return "", fmt.Errorf("no archetypes available")
}

msg := "\n\rSelect a character archetype.\n\r"
for i, option := range options {
msg += fmt.Sprintf("%d: %s\n\r", i+1, option)
}
msg += "Enter the number of your choice: "

player.toPlayer <- msg
selection, ok := <-player.fromPlayer
if !ok {
return "", fmt.Errorf("player input channel closed")
}

num, err := strconv.Atoi(strings.TrimSpace(selection))
if err != nil || num < 1 || num > len(options) {
return "", fmt.Errorf("invalid archetype selection")
}

return strings.Split(options[num-1], " - ")[0], nil
}

func buildArchetypeOptions(g *Game) []string {
options := make([]string, 0, len(g.ArcheTypes))
for name, archetype := range g.ArcheTypes {
options = append(options, name+" - "+archetype.Description)
}
sort.Strings(options)
return options
}

func getStartingRoom(g *Game, archetype string) (*Room, error) {
g.Mutex.RLock()
defer g.Mutex.RUnlock()
Expand Down
97 changes: 64 additions & 33 deletions server/character.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"context"
"errors"
"fmt"
"sort"
Expand Down Expand Up @@ -174,11 +173,24 @@ func (c *Character) cleanupRoom() error {
}

func CreateCharacter(name string, player *Player, room *Room, archetypeName string, game *Game) (*Character, error) {

Logger.Debug("Creating character", "name", name)

// Validate character name
if game.CharacterBloomFilter.Test([]byte(name)) {
return nil, fmt.Errorf("character name '%s' already exists", name)
}

if player == nil {
return nil, fmt.Errorf("player cannot be nil")
}
if room == nil {
return nil, fmt.Errorf("room cannot be nil")
}
if game == nil {
return nil, fmt.Errorf("game cannot be nil")
}

character := NewCharacter()
character.ID = uuid.New()
character.Name = name
Expand Down Expand Up @@ -237,6 +249,9 @@ func CreateCharacter(name string, player *Player, room *Room, archetypeName stri
}

func LoadCharacter(characterID uuid.UUID, player *Player, game *Game) (*Character, error) {

Logger.Debug("Loading character", "characterID", characterID)

character := NewCharacter()
character.ID = characterID
character.Player = player
Expand Down Expand Up @@ -725,11 +740,26 @@ func moveCharacter(character *Character, direction string) error {
// InputLoop is the main loop that handles player commands.
// It reads commands from the player's input and executes them accordingly.
func (c *Character) InputLoop() error {
var lastCommand string
if c == nil || c.Player == nil {
return fmt.Errorf("invalid character or player")
}

shouldQuit := false
const commandTimeout = 5 * time.Second
const idleTimeout = 30 * time.Second

// Send initial prompt
select {
case c.Player.toPlayer <- c.prompt:
case <-c.Player.ctx.Done():
return fmt.Errorf("player context cancelled during initial prompt")
}

timer := time.NewTimer(idleTimeout)
defer timer.Stop()

for !shouldQuit {
timer.Reset(idleTimeout)

select {
case <-c.Player.ctx.Done():
return fmt.Errorf("player context cancelled")
Expand All @@ -738,43 +768,44 @@ func (c *Character) InputLoop() error {
if !ok {
return fmt.Errorf("input channel closed")
}
if lastCommand == "" {
lastCommand = strings.Replace(inputLine, "\n", "\n\r", -1)
}

case <-c.Game.ticker.C:
if lastCommand != "" {
cmdCtx, cancel := context.WithTimeout(c.Player.ctx, commandTimeout)
defer cancel()

verb, tokens, err := ValidateCommand(strings.TrimSpace(lastCommand))
if err != nil {
select {
case c.outputChan <- err.Error() + "\n\r":
case <-cmdCtx.Done():
return fmt.Errorf("command context cancelled")
}
} else {
shouldQuit = ExecuteCommand(c, verb, tokens)
Logger.Debug("Command executed",
"character", c.Name,
"command", strings.Join(tokens, " "))
verb, tokens, err := ValidateCommand(strings.TrimSpace(inputLine))
if err != nil {
if err := c.sendMessage(err.Error() + "\n\r"); err != nil {
return fmt.Errorf("failed to send error message: %w", err)
}
} else {
shouldQuit = ExecuteCommand(c, verb, tokens)
Logger.Debug("Command executed",
"character", c.Name,
"command", strings.Join(tokens, " "))
}

if !shouldQuit {
select {
case c.outputChan <- c.prompt:
case <-cmdCtx.Done():
return fmt.Errorf("prompt context cancelled")
default:
Logger.Warn("Unable to send prompt", "characterName", c.Name)
}
if !shouldQuit {
if err := c.sendMessage(c.prompt); err != nil {
return fmt.Errorf("failed to send prompt: %w", err)
}
}

lastCommand = ""
case <-c.End:
return fmt.Errorf("character end signaled")

case <-timer.C:
if c.Player == nil || c.Player.ctx.Err() != nil {
return fmt.Errorf("player connection lost")
}
}
}

return nil
return fmt.Errorf("character quit")
}

// Helper method to handle message sending with error checking
func (c *Character) sendMessage(msg string) error {
select {
case c.Player.toPlayer <- msg:
return nil
case <-c.Player.ctx.Done():
return fmt.Errorf("player context cancelled while sending message")
}
}
7 changes: 5 additions & 2 deletions server/cognito.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,14 @@ func handleCognitoError(err error, email string) error {
}

func Authenticate(username, password string, ssh_interface *Interface_SSH) bool {
secretHash := ssh_interface.server.calculateSecretHash(username)

authOutput, err := ssh_interface.server.cognito.InitiateAuth(&cognitoidentityprovider.InitiateAuthInput{
AuthFlow: aws.String("USER_PASSWORD_AUTH"),
AuthParameters: map[string]*string{
"USERNAME": aws.String(username),
"PASSWORD": aws.String(password),
"USERNAME": aws.String(username),
"PASSWORD": aws.String(password),
"SECRET_HASH": aws.String(secretHash),
},
ClientId: aws.String(ssh_interface.config.Cognito.ClientID),
})
Expand Down
9 changes: 5 additions & 4 deletions server/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type Game struct {
}

func NewGame(globalCtx context.Context, config *Configuration) (*Game, error) {
Logger.Info("Initializing game...")
Logger.Info("Initializing game engine...")

ctx, cancel := context.WithCancel(globalCtx)

Expand All @@ -52,6 +52,7 @@ func NewGame(globalCtx context.Context, config *Configuration) (*Game, error) {
Rooms: make(map[int64]*Room),
Prototypes: make(map[uuid.UUID]*Prototype),
Items: make(map[uuid.UUID]*Item),
ticker: time.NewTicker(time.Second),
}

database, err := NewKeyPair(config.Aws.Region)
Expand Down Expand Up @@ -82,7 +83,7 @@ func NewGame(globalCtx context.Context, config *Configuration) (*Game, error) {
}

func (g *Game) Run() error {
Logger.Info("Starting game...")
Logger.Info("Starting game engine...")
ticker := time.NewTicker(time.Second)
defer ticker.Stop()

Expand Down Expand Up @@ -113,12 +114,12 @@ func (g *Game) initBloomFilter() error {
return fmt.Errorf("character names load error: %w", err)
}

namesFromFile, err := loadNamesFromFile("./data/names.txt")
namesFromFile, err := loadNamesFromFile("../data/names.txt")
if err != nil {
return fmt.Errorf("names file load error: %w", err)
}

obscenities, err := loadNamesFromFile("./data/obscenity.txt")
obscenities, err := loadNamesFromFile("../data/obscenity.txt")
if err != nil {
return fmt.Errorf("obscenity file load error: %w", err)
}
Expand Down
Loading

0 comments on commit e064db6

Please sign in to comment.