Compare commits
	
		
			2 Commits
		
	
	
		
			testbuild-
			...
			e45066b2a8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e45066b2a8 | |||
| bb01dccf2b | 
| @ -53,6 +53,7 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) { | ||||
| 	for _, msg := range messages { | ||||
| 		localMsg := types.ChatMessage{ | ||||
| 			PlayerID: msg.PlayerId, | ||||
| 			Username: msg.Username, | ||||
| 			Content:  msg.Content, | ||||
| 			Time:     time.Unix(0, msg.Timestamp), | ||||
| 		} | ||||
| @ -117,7 +118,7 @@ func (c *Chat) Draw(screenWidth, screenHeight int32) { | ||||
|  | ||||
| 	for i := startIdx; i < endIdx; i++ { | ||||
| 		msg := c.messages[i] | ||||
| 		text := fmt.Sprintf("[%d]: %s", msg.PlayerID, msg.Content) | ||||
| 		text := fmt.Sprintf("%s: %s", msg.Username, msg.Content) | ||||
| 		rl.DrawText(text, int32(chatX)+5, int32(messageY), 20, rl.White) | ||||
| 		messageY += messageHeight | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										60
									
								
								game/game.go
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								game/game.go
									
									
									
									
									
								
							| @ -5,6 +5,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"gitea.boner.be/bdnugget/goonscape/assets" | ||||
| 	"gitea.boner.be/bdnugget/goonscape/network" | ||||
| 	"gitea.boner.be/bdnugget/goonscape/types" | ||||
| 	pb "gitea.boner.be/bdnugget/goonserver/actions" | ||||
| 	rl "github.com/gen2brain/raylib-go/raylib" | ||||
| @ -19,19 +20,13 @@ type Game struct { | ||||
| 	Chat         *Chat | ||||
| 	MenuOpen     bool | ||||
| 	QuitChan     chan struct{} // Channel to signal shutdown | ||||
| 	loginScreen  *LoginScreen | ||||
| 	isLoggedIn   bool | ||||
| } | ||||
|  | ||||
| func New() *Game { | ||||
| 	InitWorld() | ||||
| 	game := &Game{ | ||||
| 		Player: &types.Player{ | ||||
| 			PosActual:  rl.NewVector3(5*types.TileSize, 0, 5*types.TileSize), | ||||
| 			PosTile:    GetTile(5, 5), | ||||
| 			Speed:      50.0, | ||||
| 			TargetPath: []types.Tile{}, | ||||
| 			UserData:   nil, | ||||
| 			QuitDone:   make(chan struct{}), | ||||
| 		}, | ||||
| 		OtherPlayers: make(map[int32]*types.Player), | ||||
| 		Camera: rl.Camera3D{ | ||||
| 			Position:   rl.NewVector3(0, 10, 10), | ||||
| @ -42,8 +37,8 @@ func New() *Game { | ||||
| 		}, | ||||
| 		Chat:        NewChat(), | ||||
| 		QuitChan:    make(chan struct{}), | ||||
| 		loginScreen: NewLoginScreen(), | ||||
| 	} | ||||
| 	game.Player.UserData = game | ||||
| 	game.Chat.userData = game | ||||
| 	return game | ||||
| } | ||||
| @ -64,6 +59,35 @@ func (g *Game) LoadAssets() error { | ||||
| } | ||||
|  | ||||
| func (g *Game) Update(deltaTime float32) { | ||||
| 	if !g.isLoggedIn { | ||||
| 		username, password, isRegistering, submitted := g.loginScreen.Update() | ||||
| 		if submitted { | ||||
| 			conn, playerID, err := network.ConnectToServer(username, password, isRegistering) | ||||
| 			if err != nil { | ||||
| 				g.loginScreen.SetError(err.Error()) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			// Assign model based on player ID | ||||
| 			modelIndex := int(playerID) % len(g.Models) | ||||
| 			g.Player = &types.Player{ | ||||
| 				Speed:      50.0, | ||||
| 				TargetPath: []types.Tile{}, | ||||
| 				UserData:   g, | ||||
| 				QuitDone:   make(chan struct{}), | ||||
| 				ID:         playerID, | ||||
| 				Model:      g.Models[modelIndex].Model, | ||||
| 				Texture:    g.Models[modelIndex].Texture, | ||||
| 			} | ||||
|  | ||||
| 			go network.HandleServerCommunication(conn, playerID, g.Player, g.OtherPlayers, g.QuitChan) | ||||
| 			g.isLoggedIn = true | ||||
| 			return | ||||
| 		} | ||||
| 		g.loginScreen.Draw() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Handle ESC for menu | ||||
| 	if rl.IsKeyPressed(rl.KeyEscape) { | ||||
| 		g.MenuOpen = !g.MenuOpen | ||||
| @ -174,11 +198,23 @@ func (g *Game) Render() { | ||||
| 	rl.BeginDrawing() | ||||
| 	rl.ClearBackground(rl.RayWhite) | ||||
|  | ||||
| 	if !g.isLoggedIn { | ||||
| 		g.loginScreen.Draw() | ||||
| 		rl.EndDrawing() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	rl.BeginMode3D(g.Camera) | ||||
| 	g.DrawMap() | ||||
| 	g.DrawPlayer(g.Player, g.Player.Model) | ||||
| 	for id, other := range g.OtherPlayers { | ||||
| 		g.DrawPlayer(other, g.Models[int(id)%len(g.Models)].Model) | ||||
| 		if other.Model.Meshes == nil { | ||||
| 			// Assign model based on player ID for consistency | ||||
| 			modelIndex := int(id) % len(g.Models) | ||||
| 			other.Model = g.Models[modelIndex].Model | ||||
| 			other.Texture = g.Models[modelIndex].Texture | ||||
| 		} | ||||
| 		g.DrawPlayer(other, other.Model) | ||||
| 	} | ||||
| 	rl.EndMode3D() | ||||
|  | ||||
| @ -312,3 +348,7 @@ func (g *Game) Shutdown() { | ||||
| 	rl.CloseWindow() | ||||
| 	os.Exit(0) | ||||
| } | ||||
|  | ||||
| func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) { | ||||
| 	g.Chat.HandleServerMessages(messages) | ||||
| } | ||||
|  | ||||
							
								
								
									
										185
									
								
								game/login.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								game/login.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | ||||
| package game | ||||
|  | ||||
| import ( | ||||
| 	rl "github.com/gen2brain/raylib-go/raylib" | ||||
| ) | ||||
|  | ||||
| type LoginScreen struct { | ||||
| 	username      string | ||||
| 	password      string | ||||
| 	errorMessage  string | ||||
| 	isRegistering bool | ||||
| 	focusedField  int // 0 = username, 1 = password | ||||
| } | ||||
|  | ||||
| func NewLoginScreen() *LoginScreen { | ||||
| 	return &LoginScreen{} | ||||
| } | ||||
|  | ||||
| func (l *LoginScreen) Draw() { | ||||
| 	screenWidth := float32(rl.GetScreenWidth()) | ||||
| 	screenHeight := float32(rl.GetScreenHeight()) | ||||
|  | ||||
| 	// Draw background | ||||
| 	rl.DrawRectangle(0, 0, int32(screenWidth), int32(screenHeight), rl.RayWhite) | ||||
|  | ||||
| 	// Draw title | ||||
| 	title := "GoonScape" | ||||
| 	if l.isRegistering { | ||||
| 		title += " - Register" | ||||
| 	} else { | ||||
| 		title += " - Login" | ||||
| 	} | ||||
| 	titleSize := int32(40) | ||||
| 	titleWidth := rl.MeasureText(title, titleSize) | ||||
| 	rl.DrawText(title, int32(screenWidth/2)-titleWidth/2, 100, titleSize, rl.Black) | ||||
|  | ||||
| 	// Draw input fields | ||||
| 	inputWidth := float32(200) | ||||
| 	inputHeight := float32(30) | ||||
| 	inputX := screenWidth/2 - inputWidth/2 | ||||
|  | ||||
| 	// Username field | ||||
| 	rl.DrawRectangleRec(rl.Rectangle{ | ||||
| 		X: inputX, Y: 200, | ||||
| 		Width: inputWidth, Height: inputHeight, | ||||
| 	}, rl.LightGray) | ||||
| 	rl.DrawText(l.username, int32(inputX)+5, 205, 20, rl.Black) | ||||
| 	if l.focusedField == 0 { | ||||
| 		rl.DrawRectangleLinesEx(rl.Rectangle{ | ||||
| 			X: inputX - 2, Y: 198, | ||||
| 			Width: inputWidth + 4, Height: inputHeight + 4, | ||||
| 		}, 2, rl.Blue) | ||||
| 	} | ||||
|  | ||||
| 	// Password field | ||||
| 	rl.DrawRectangleRec(rl.Rectangle{ | ||||
| 		X: inputX, Y: 250, | ||||
| 		Width: inputWidth, Height: inputHeight, | ||||
| 	}, rl.LightGray) | ||||
| 	masked := "" | ||||
| 	for range l.password { | ||||
| 		masked += "*" | ||||
| 	} | ||||
| 	rl.DrawText(masked, int32(inputX)+5, 255, 20, rl.Black) | ||||
| 	if l.focusedField == 1 { | ||||
| 		rl.DrawRectangleLinesEx(rl.Rectangle{ | ||||
| 			X: inputX - 2, Y: 248, | ||||
| 			Width: inputWidth + 4, Height: inputHeight + 4, | ||||
| 		}, 2, rl.Blue) | ||||
| 	} | ||||
|  | ||||
| 	// Draw error message | ||||
| 	if l.errorMessage != "" { | ||||
| 		msgWidth := rl.MeasureText(l.errorMessage, 20) | ||||
| 		rl.DrawText(l.errorMessage, int32(screenWidth/2)-msgWidth/2, 300, 20, rl.Red) | ||||
| 	} | ||||
|  | ||||
| 	// Draw buttons | ||||
| 	buttonWidth := float32(100) | ||||
| 	buttonHeight := float32(30) | ||||
| 	buttonY := float32(350) | ||||
|  | ||||
| 	// Login/Register button | ||||
| 	actionBtn := rl.Rectangle{ | ||||
| 		X:      screenWidth/2 - buttonWidth - 10, | ||||
| 		Y:      buttonY, | ||||
| 		Width:  buttonWidth, | ||||
| 		Height: buttonHeight, | ||||
| 	} | ||||
| 	rl.DrawRectangleRec(actionBtn, rl.Blue) | ||||
| 	actionText := "Login" | ||||
| 	if l.isRegistering { | ||||
| 		actionText = "Register" | ||||
| 	} | ||||
| 	actionWidth := rl.MeasureText(actionText, 20) | ||||
| 	rl.DrawText(actionText, | ||||
| 		int32(actionBtn.X+actionBtn.Width/2)-actionWidth/2, | ||||
| 		int32(actionBtn.Y+5), | ||||
| 		20, rl.White) | ||||
|  | ||||
| 	// Switch mode button | ||||
| 	switchBtn := rl.Rectangle{ | ||||
| 		X:      screenWidth/2 + 10, | ||||
| 		Y:      buttonY, | ||||
| 		Width:  buttonWidth, | ||||
| 		Height: buttonHeight, | ||||
| 	} | ||||
| 	rl.DrawRectangleRec(switchBtn, rl.DarkGray) | ||||
| 	switchText := "Register" | ||||
| 	if l.isRegistering { | ||||
| 		switchText = "Login" | ||||
| 	} | ||||
| 	switchWidth := rl.MeasureText(switchText, 20) | ||||
| 	rl.DrawText(switchText, | ||||
| 		int32(switchBtn.X+switchBtn.Width/2)-switchWidth/2, | ||||
| 		int32(switchBtn.Y+5), | ||||
| 		20, rl.White) | ||||
| } | ||||
|  | ||||
| func (l *LoginScreen) Update() (string, string, bool, bool) { | ||||
| 	// Handle input field focus | ||||
| 	if rl.IsMouseButtonPressed(rl.MouseLeftButton) { | ||||
| 		mousePos := rl.GetMousePosition() | ||||
| 		screenWidth := float32(rl.GetScreenWidth()) | ||||
| 		inputWidth := float32(200) | ||||
| 		inputX := screenWidth/2 - inputWidth/2 | ||||
|  | ||||
| 		// Check username field | ||||
| 		if mousePos.X >= inputX && mousePos.X <= inputX+inputWidth && | ||||
| 			mousePos.Y >= 200 && mousePos.Y <= 230 { | ||||
| 			l.focusedField = 0 | ||||
| 		} | ||||
| 		// Check password field | ||||
| 		if mousePos.X >= inputX && mousePos.X <= inputX+inputWidth && | ||||
| 			mousePos.Y >= 250 && mousePos.Y <= 280 { | ||||
| 			l.focusedField = 1 | ||||
| 		} | ||||
|  | ||||
| 		// Check buttons | ||||
| 		buttonWidth := float32(100) | ||||
| 		if mousePos.Y >= 350 && mousePos.Y <= 380 { | ||||
| 			// Action button | ||||
| 			if mousePos.X >= screenWidth/2-buttonWidth-10 && | ||||
| 				mousePos.X <= screenWidth/2-10 { | ||||
| 				return l.username, l.password, l.isRegistering, true | ||||
| 			} | ||||
| 			// Switch mode button | ||||
| 			if mousePos.X >= screenWidth/2+10 && | ||||
| 				mousePos.X <= screenWidth/2+buttonWidth+10 { | ||||
| 				l.isRegistering = !l.isRegistering | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Handle text input | ||||
| 	key := rl.GetCharPressed() | ||||
| 	for key > 0 { | ||||
| 		if l.focusedField == 0 && len(l.username) < 12 { | ||||
| 			l.username += string(key) | ||||
| 		} else if l.focusedField == 1 && len(l.password) < 20 { | ||||
| 			l.password += string(key) | ||||
| 		} | ||||
| 		key = rl.GetCharPressed() | ||||
| 	} | ||||
|  | ||||
| 	// Handle backspace | ||||
| 	if rl.IsKeyPressed(rl.KeyBackspace) { | ||||
| 		if l.focusedField == 0 && len(l.username) > 0 { | ||||
| 			l.username = l.username[:len(l.username)-1] | ||||
| 		} else if l.focusedField == 1 && len(l.password) > 0 { | ||||
| 			l.password = l.password[:len(l.password)-1] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Handle tab to switch fields | ||||
| 	if rl.IsKeyPressed(rl.KeyTab) { | ||||
| 		l.focusedField = (l.focusedField + 1) % 2 | ||||
| 	} | ||||
|  | ||||
| 	return "", "", false, false | ||||
| } | ||||
|  | ||||
| func (l *LoginScreen) SetError(msg string) { | ||||
| 	l.errorMessage = msg | ||||
| } | ||||
 Submodule goonserver updated: be32dec202...3f7205d73e
									
								
							
							
								
								
									
										37
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								main.go
									
									
									
									
									
								
							| @ -11,20 +11,21 @@ import ( | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	local := flag.Bool("local", false, "Use local server instead of remote") | ||||
| 	addr := flag.String("addr", "boner.be:6969", "Server address (hostname:port or hostname)") | ||||
| 	// Parse command line flags | ||||
| 	local := flag.Bool("local", false, "Connect to local server") | ||||
| 	addr := flag.String("addr", "", "Server address (host or host:port)") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	if *local && *addr != "boner.be:6969" { | ||||
| 		log.Fatal("Cannot use both -local and -addr flags") | ||||
| 	} | ||||
|  | ||||
| 	// Set server address based on flags | ||||
| 	if *local { | ||||
| 		if *addr != "" { | ||||
| 			log.Fatal("Cannot use -local and -addr together") | ||||
| 		} | ||||
| 		network.SetServerAddr("localhost:6969") | ||||
| 	} else if *addr != "" { | ||||
| 		// If only hostname is provided, append default port | ||||
| 		// If port is not specified, append default port | ||||
| 		if !strings.Contains(*addr, ":") { | ||||
| 			*addr = *addr + ":6969" | ||||
| 			*addr += ":6969" | ||||
| 		} | ||||
| 		network.SetServerAddr(*addr) | ||||
| 	} | ||||
| @ -32,36 +33,24 @@ func main() { | ||||
| 	rl.InitWindow(1024, 768, "GoonScape") | ||||
| 	rl.SetExitKey(0) | ||||
| 	defer rl.CloseWindow() | ||||
|  | ||||
| 	rl.InitAudioDevice() | ||||
| 	defer rl.CloseAudioDevice() | ||||
|  | ||||
| 	rl.SetTargetFPS(60) | ||||
|  | ||||
| 	game := game.New() | ||||
| 	if err := game.LoadAssets(); err != nil { | ||||
| 		log.Fatalf("Failed to load assets: %v", err) | ||||
| 	} | ||||
| 	defer game.Cleanup() | ||||
|  | ||||
| 	conn, playerID, err := network.ConnectToServer() | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to connect to server: %v", err) | ||||
| 	} | ||||
| 	defer conn.Close() | ||||
|  | ||||
| 	game.Player.ID = playerID | ||||
| 	modelIndex := int(playerID) % len(game.Models) | ||||
| 	game.Player.Model = game.Models[modelIndex].Model | ||||
| 	game.Player.Texture = game.Models[modelIndex].Texture | ||||
|  | ||||
| 	go network.HandleServerCommunication(conn, playerID, game.Player, game.OtherPlayers, game.QuitChan) | ||||
|  | ||||
| 	rl.PlayMusicStream(game.Music) | ||||
| 	rl.SetMusicVolume(game.Music, 0.5) | ||||
| 	rl.SetTargetFPS(60) | ||||
|  | ||||
| 	for !rl.WindowShouldClose() { | ||||
| 		rl.UpdateMusicStream(game.Music) | ||||
| 		deltaTime := rl.GetFrameTime() | ||||
|  | ||||
| 		rl.UpdateMusicStream(game.Music) | ||||
| 		game.Update(deltaTime) | ||||
| 		game.Render() | ||||
| 	} | ||||
|  | ||||
| @ -3,56 +3,89 @@ package network | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"encoding/binary" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"time" | ||||
|  | ||||
| 	"gitea.boner.be/bdnugget/goonscape/game" | ||||
| 	"gitea.boner.be/bdnugget/goonscape/types" | ||||
| 	pb "gitea.boner.be/bdnugget/goonserver/actions" | ||||
| 	rl "github.com/gen2brain/raylib-go/raylib" | ||||
| 	"google.golang.org/protobuf/proto" | ||||
| ) | ||||
|  | ||||
| const protoVersion = 1 | ||||
|  | ||||
| var serverAddr = "boner.be:6969" | ||||
|  | ||||
| func SetServerAddr(addr string) { | ||||
| 	serverAddr = addr | ||||
| } | ||||
|  | ||||
| func ConnectToServer() (net.Conn, int32, error) { | ||||
| func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) { | ||||
| 	conn, err := net.Dial("tcp", serverAddr) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to dial server: %v", err) | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	log.Println("Connected to server. Waiting for player ID...") | ||||
| 	reader := bufio.NewReader(conn) | ||||
| 	log.Println("Connected to server. Authenticating...") | ||||
|  | ||||
| 	// Read message length (4 bytes) | ||||
| 	// Send auth message | ||||
| 	authAction := &pb.Action{ | ||||
| 		Type:     pb.Action_LOGIN, | ||||
| 		Username: username, | ||||
| 		Password: password, | ||||
| 	} | ||||
| 	if isRegistering { | ||||
| 		authAction.Type = pb.Action_REGISTER | ||||
| 	} | ||||
|  | ||||
| 	authBatch := &pb.ActionBatch{ | ||||
| 		Actions:         []*pb.Action{authAction}, | ||||
| 		ProtocolVersion: protoVersion, | ||||
| 	} | ||||
|  | ||||
| 	if err := writeMessage(conn, authBatch); err != nil { | ||||
| 		conn.Close() | ||||
| 		return nil, 0, fmt.Errorf("failed to send auth: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Read server response | ||||
| 	reader := bufio.NewReader(conn) | ||||
| 	lengthBuf := make([]byte, 4) | ||||
| 	if _, err := io.ReadFull(reader, lengthBuf); err != nil { | ||||
| 		log.Printf("Failed to read message length: %v", err) | ||||
| 		return nil, 0, err | ||||
| 		conn.Close() | ||||
| 		return nil, 0, fmt.Errorf("failed to read auth response: %v", err) | ||||
| 	} | ||||
| 	messageLength := binary.BigEndian.Uint32(lengthBuf) | ||||
|  | ||||
| 	// Read the full message | ||||
| 	messageBuf := make([]byte, messageLength) | ||||
| 	if _, err := io.ReadFull(reader, messageBuf); err != nil { | ||||
| 		log.Printf("Failed to read message body: %v", err) | ||||
| 		return nil, 0, err | ||||
| 		conn.Close() | ||||
| 		return nil, 0, fmt.Errorf("failed to read auth response body: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	var response pb.ServerMessage | ||||
| 	if err := proto.Unmarshal(messageBuf, &response); err != nil { | ||||
| 		log.Printf("Failed to unmarshal server response: %v", err) | ||||
| 		return nil, 0, err | ||||
| 		conn.Close() | ||||
| 		return nil, 0, fmt.Errorf("failed to unmarshal auth response: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if response.ProtocolVersion > protoVersion { | ||||
| 		conn.Close() | ||||
| 		return nil, 0, fmt.Errorf("server requires newer protocol version (server: %d, client: %d)", | ||||
| 			response.ProtocolVersion, protoVersion) | ||||
| 	} | ||||
|  | ||||
| 	if !response.AuthSuccess { | ||||
| 		conn.Close() | ||||
| 		return nil, 0, fmt.Errorf(response.ErrorMessage) | ||||
| 	} | ||||
|  | ||||
| 	playerID := response.GetPlayerId() | ||||
| 	log.Printf("Successfully connected with player ID: %d", playerID) | ||||
| 	log.Printf("Successfully authenticated with player ID: %d", playerID) | ||||
| 	return conn, playerID, nil | ||||
| } | ||||
|  | ||||
| @ -162,6 +195,17 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play | ||||
|  | ||||
| 			for _, state := range serverMessage.Players { | ||||
| 				if state.PlayerId == playerID { | ||||
| 					player.Lock() | ||||
| 					// Update initial position if not set | ||||
| 					if player.PosActual.X == 0 && player.PosActual.Z == 0 { | ||||
| 						player.PosActual = rl.Vector3{ | ||||
| 							X: float32(state.X * types.TileSize), | ||||
| 							Y: 0, | ||||
| 							Z: float32(state.Y * types.TileSize), | ||||
| 						} | ||||
| 						player.PosTile = types.Tile{X: int(state.X), Y: int(state.Y)} | ||||
| 					} | ||||
| 					player.Unlock() | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| @ -172,8 +216,8 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if g, ok := player.UserData.(*game.Game); ok && len(serverMessage.ChatMessages) > 0 { | ||||
| 				g.Chat.HandleServerMessages(serverMessage.ChatMessages) | ||||
| 			if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 { | ||||
| 				handler.HandleServerMessages(serverMessage.ChatMessages) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -39,6 +39,7 @@ type ModelAsset struct { | ||||
|  | ||||
| type ChatMessage struct { | ||||
| 	PlayerID int32 | ||||
| 	Username string | ||||
| 	Content  string | ||||
| 	Time     time.Time | ||||
| } | ||||
| @ -49,6 +50,10 @@ type FloatingMessage struct { | ||||
| 	ScreenPos  rl.Vector2 // Store the screen position for 2D rendering | ||||
| } | ||||
|  | ||||
| type ChatMessageHandler interface { | ||||
| 	HandleServerMessages([]*pb.ChatMessage) | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	MapWidth   = 50 | ||||
| 	MapHeight  = 50 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user