Compare commits
No commits in common. "567ec40c3d4c87c0029d238690a46600e1bd6e1b" and "91cdbab54a098c685dee803f2a99b8c2a5571f7e" have entirely different histories.
567ec40c3d
...
91cdbab54a
194
game/chat.go
194
game/chat.go
@ -1,194 +0,0 @@
|
|||||||
package game
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/types"
|
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxMessages = 50
|
|
||||||
chatWindowWidth = 400
|
|
||||||
chatHeight = 200
|
|
||||||
messageHeight = 20
|
|
||||||
inputHeight = 30
|
|
||||||
)
|
|
||||||
|
|
||||||
type Chat struct {
|
|
||||||
messages []types.ChatMessage
|
|
||||||
inputBuffer []rune
|
|
||||||
isTyping bool
|
|
||||||
cursorPos int
|
|
||||||
scrollOffset int
|
|
||||||
userData interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewChat() *Chat {
|
|
||||||
return &Chat{
|
|
||||||
messages: make([]types.ChatMessage, 0, maxMessages),
|
|
||||||
inputBuffer: make([]rune, 0, 256),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chat) AddMessage(playerID int32, content string) {
|
|
||||||
msg := types.ChatMessage{
|
|
||||||
PlayerID: playerID,
|
|
||||||
Content: content,
|
|
||||||
Time: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.messages) >= maxMessages {
|
|
||||||
c.messages = c.messages[1:]
|
|
||||||
}
|
|
||||||
c.messages = append(c.messages, msg)
|
|
||||||
c.scrollOffset = 0 // Reset scroll position for new messages
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) {
|
|
||||||
// Convert protobuf messages to our local type
|
|
||||||
for _, msg := range messages {
|
|
||||||
localMsg := types.ChatMessage{
|
|
||||||
PlayerID: msg.PlayerId,
|
|
||||||
Content: msg.Content,
|
|
||||||
Time: time.Unix(0, msg.Timestamp),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only add if it's not already in our history
|
|
||||||
if len(c.messages) == 0 || c.messages[len(c.messages)-1].Time.UnixNano() < msg.Timestamp {
|
|
||||||
if len(c.messages) >= maxMessages {
|
|
||||||
c.messages = c.messages[1:]
|
|
||||||
}
|
|
||||||
c.messages = append(c.messages, localMsg)
|
|
||||||
|
|
||||||
// Add floating message to the player
|
|
||||||
if game, ok := c.userData.(*Game); ok {
|
|
||||||
if msg.PlayerId == game.Player.ID {
|
|
||||||
game.Player.Lock()
|
|
||||||
game.Player.FloatingMessage = &types.FloatingMessage{
|
|
||||||
Content: msg.Content,
|
|
||||||
ExpireTime: time.Now().Add(6 * time.Second),
|
|
||||||
}
|
|
||||||
game.Player.Unlock()
|
|
||||||
} else if otherPlayer, exists := game.OtherPlayers[msg.PlayerId]; exists {
|
|
||||||
otherPlayer.Lock()
|
|
||||||
otherPlayer.FloatingMessage = &types.FloatingMessage{
|
|
||||||
Content: msg.Content,
|
|
||||||
ExpireTime: time.Now().Add(6 * time.Second),
|
|
||||||
}
|
|
||||||
otherPlayer.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chat) Draw(screenWidth, screenHeight int32) {
|
|
||||||
// Draw chat window background
|
|
||||||
chatX := float32(10)
|
|
||||||
chatY := float32(screenHeight - chatHeight - 10)
|
|
||||||
rl.DrawRectangle(int32(chatX), int32(chatY), chatWindowWidth, chatHeight, rl.ColorAlpha(rl.Black, 0.5))
|
|
||||||
|
|
||||||
// Draw messages
|
|
||||||
messageY := chatY + 5
|
|
||||||
startIdx := len(c.messages) - 1 - c.scrollOffset
|
|
||||||
endIdx := max(0, startIdx-int((chatHeight-inputHeight)/messageHeight))
|
|
||||||
|
|
||||||
for i := startIdx; i >= endIdx && i >= 0; i-- {
|
|
||||||
msg := c.messages[i]
|
|
||||||
text := fmt.Sprintf("[%d]: %s", msg.PlayerID, msg.Content)
|
|
||||||
rl.DrawText(text, int32(chatX)+5, int32(messageY), 20, rl.White)
|
|
||||||
messageY += messageHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw input field
|
|
||||||
inputY := chatY + float32(chatHeight-inputHeight)
|
|
||||||
rl.DrawRectangle(int32(chatX), int32(inputY), chatWindowWidth, inputHeight, rl.ColorAlpha(rl.White, 0.3))
|
|
||||||
if c.isTyping {
|
|
||||||
inputText := string(c.inputBuffer)
|
|
||||||
rl.DrawText(inputText, int32(chatX)+5, int32(inputY)+5, 20, rl.White)
|
|
||||||
|
|
||||||
// Draw cursor
|
|
||||||
cursorX := rl.MeasureText(inputText[:c.cursorPos], 20)
|
|
||||||
rl.DrawRectangle(int32(chatX)+5+cursorX, int32(inputY)+5, 2, 20, rl.White)
|
|
||||||
} else {
|
|
||||||
rl.DrawText("Press T to chat", int32(chatX)+5, int32(inputY)+5, 20, rl.Gray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chat) Update() (string, bool) {
|
|
||||||
// Handle scrolling with mouse wheel when not typing
|
|
||||||
if !c.isTyping {
|
|
||||||
wheelMove := rl.GetMouseWheelMove()
|
|
||||||
if wheelMove != 0 {
|
|
||||||
maxScroll := max(0, len(c.messages)-int((chatHeight-inputHeight)/messageHeight))
|
|
||||||
c.scrollOffset = clamp(c.scrollOffset-int(wheelMove), 0, maxScroll)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rl.IsKeyPressed(rl.KeyT) {
|
|
||||||
c.isTyping = true
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
key := rl.GetCharPressed()
|
|
||||||
for key > 0 {
|
|
||||||
if len(c.inputBuffer) < 256 {
|
|
||||||
c.inputBuffer = append(c.inputBuffer[:c.cursorPos], append([]rune{key}, c.inputBuffer[c.cursorPos:]...)...)
|
|
||||||
c.cursorPos++
|
|
||||||
}
|
|
||||||
key = rl.GetCharPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
if rl.IsKeyPressed(rl.KeyEnter) {
|
|
||||||
if len(c.inputBuffer) > 0 {
|
|
||||||
message := string(c.inputBuffer)
|
|
||||||
c.inputBuffer = c.inputBuffer[:0]
|
|
||||||
c.cursorPos = 0
|
|
||||||
c.isTyping = false
|
|
||||||
return message, true
|
|
||||||
}
|
|
||||||
c.isTyping = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if rl.IsKeyPressed(rl.KeyEscape) {
|
|
||||||
c.inputBuffer = c.inputBuffer[:0]
|
|
||||||
c.cursorPos = 0
|
|
||||||
c.isTyping = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if rl.IsKeyPressed(rl.KeyBackspace) && c.cursorPos > 0 {
|
|
||||||
c.inputBuffer = append(c.inputBuffer[:c.cursorPos-1], c.inputBuffer[c.cursorPos:]...)
|
|
||||||
c.cursorPos--
|
|
||||||
}
|
|
||||||
|
|
||||||
if rl.IsKeyPressed(rl.KeyLeft) && c.cursorPos > 0 {
|
|
||||||
c.cursorPos--
|
|
||||||
}
|
|
||||||
if rl.IsKeyPressed(rl.KeyRight) && c.cursorPos < len(c.inputBuffer) {
|
|
||||||
c.cursorPos++
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add helper functions
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func clamp(value, min, max int) int {
|
|
||||||
if value < min {
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
if value > max {
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
65
game/game.go
65
game/game.go
@ -1,8 +1,6 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/assets"
|
"gitea.boner.be/bdnugget/goonscape/assets"
|
||||||
"gitea.boner.be/bdnugget/goonscape/types"
|
"gitea.boner.be/bdnugget/goonscape/types"
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||||
@ -15,7 +13,6 @@ type Game struct {
|
|||||||
Camera rl.Camera3D
|
Camera rl.Camera3D
|
||||||
Models []types.ModelAsset
|
Models []types.ModelAsset
|
||||||
Music rl.Music
|
Music rl.Music
|
||||||
Chat *Chat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Game {
|
func New() *Game {
|
||||||
@ -26,7 +23,6 @@ func New() *Game {
|
|||||||
PosTile: GetTile(5, 5),
|
PosTile: GetTile(5, 5),
|
||||||
Speed: 50.0,
|
Speed: 50.0,
|
||||||
TargetPath: []types.Tile{},
|
TargetPath: []types.Tile{},
|
||||||
UserData: nil,
|
|
||||||
},
|
},
|
||||||
OtherPlayers: make(map[int32]*types.Player),
|
OtherPlayers: make(map[int32]*types.Player),
|
||||||
Camera: rl.Camera3D{
|
Camera: rl.Camera3D{
|
||||||
@ -36,10 +32,7 @@ func New() *Game {
|
|||||||
Fovy: 45.0,
|
Fovy: 45.0,
|
||||||
Projection: rl.CameraPerspective,
|
Projection: rl.CameraPerspective,
|
||||||
},
|
},
|
||||||
Chat: NewChat(),
|
|
||||||
}
|
}
|
||||||
game.Player.UserData = game
|
|
||||||
game.Chat.userData = game
|
|
||||||
return game
|
return game
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,16 +52,6 @@ func (g *Game) LoadAssets() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Update(deltaTime float32) {
|
func (g *Game) Update(deltaTime float32) {
|
||||||
if message, sent := g.Chat.Update(); sent {
|
|
||||||
g.Player.Lock()
|
|
||||||
g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{
|
|
||||||
Type: pb.Action_CHAT,
|
|
||||||
ChatMessage: message,
|
|
||||||
PlayerId: g.Player.ID,
|
|
||||||
})
|
|
||||||
g.Player.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
g.HandleInput()
|
g.HandleInput()
|
||||||
|
|
||||||
if len(g.Player.TargetPath) > 0 {
|
if len(g.Player.TargetPath) > 0 {
|
||||||
@ -125,18 +108,6 @@ func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
|||||||
|
|
||||||
rl.DrawModel(model, playerPos, 16, rl.White)
|
rl.DrawModel(model, playerPos, 16, rl.White)
|
||||||
|
|
||||||
if player.FloatingMessage != nil && time.Now().Before(player.FloatingMessage.ExpireTime) {
|
|
||||||
screenPos := rl.GetWorldToScreen(rl.Vector3{
|
|
||||||
X: playerPos.X,
|
|
||||||
Y: playerPos.Y + 24.0,
|
|
||||||
Z: playerPos.Z,
|
|
||||||
}, g.Camera)
|
|
||||||
|
|
||||||
player.FloatingMessage.ScreenPos = screenPos
|
|
||||||
} else if player.FloatingMessage != nil {
|
|
||||||
player.FloatingMessage = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(player.TargetPath) > 0 {
|
if len(player.TargetPath) > 0 {
|
||||||
targetTile := player.TargetPath[len(player.TargetPath)-1]
|
targetTile := player.TargetPath[len(player.TargetPath)-1]
|
||||||
targetPos := rl.Vector3{
|
targetPos := rl.Vector3{
|
||||||
@ -159,45 +130,17 @@ func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
|||||||
func (g *Game) Render() {
|
func (g *Game) Render() {
|
||||||
rl.BeginDrawing()
|
rl.BeginDrawing()
|
||||||
rl.ClearBackground(rl.RayWhite)
|
rl.ClearBackground(rl.RayWhite)
|
||||||
|
|
||||||
rl.BeginMode3D(g.Camera)
|
rl.BeginMode3D(g.Camera)
|
||||||
|
|
||||||
g.DrawMap()
|
g.DrawMap()
|
||||||
g.DrawPlayer(g.Player, g.Player.Model)
|
g.DrawPlayer(g.Player, g.Player.Model)
|
||||||
|
|
||||||
for id, other := range g.OtherPlayers {
|
for id, other := range g.OtherPlayers {
|
||||||
g.DrawPlayer(other, g.Models[int(id)%len(g.Models)].Model)
|
g.DrawPlayer(other, g.Models[int(id)%len(g.Models)].Model)
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.EndMode3D()
|
rl.EndMode3D()
|
||||||
|
|
||||||
drawFloatingMessage := func(msg *types.FloatingMessage) {
|
|
||||||
if msg == nil || time.Now().After(msg.ExpireTime) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pos := msg.ScreenPos
|
|
||||||
text := msg.Content
|
|
||||||
textWidth := rl.MeasureText(text, 20)
|
|
||||||
|
|
||||||
for offsetX := -2; offsetX <= 2; offsetX++ {
|
|
||||||
for offsetY := -2; offsetY <= 2; offsetY++ {
|
|
||||||
rl.DrawText(text,
|
|
||||||
int32(pos.X)-textWidth/2+int32(offsetX),
|
|
||||||
int32(pos.Y)+int32(offsetY),
|
|
||||||
20,
|
|
||||||
rl.Black)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rl.DrawText(text, int32(pos.X)-textWidth/2, int32(pos.Y), 20, rl.Yellow)
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.Player.FloatingMessage != nil {
|
|
||||||
drawFloatingMessage(g.Player.FloatingMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, other := range g.OtherPlayers {
|
|
||||||
drawFloatingMessage(other.FloatingMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.DrawFPS(10, 10)
|
rl.DrawFPS(10, 10)
|
||||||
g.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight()))
|
|
||||||
rl.EndDrawing()
|
rl.EndDrawing()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +153,7 @@ func (g *Game) HandleInput() {
|
|||||||
clickedTile, clicked := g.GetTileAtMouse()
|
clickedTile, clicked := g.GetTileAtMouse()
|
||||||
if clicked {
|
if clicked {
|
||||||
path := FindPath(GetTile(g.Player.PosTile.X, g.Player.PosTile.Y), clickedTile)
|
path := FindPath(GetTile(g.Player.PosTile.X, g.Player.PosTile.Y), clickedTile)
|
||||||
if len(path) > 1 {
|
if path != nil && len(path) > 1 {
|
||||||
g.Player.Lock()
|
g.Player.Lock()
|
||||||
g.Player.TargetPath = path[1:]
|
g.Player.TargetPath = path[1:]
|
||||||
g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{
|
g.Player.ActionQueue = append(g.Player.ActionQueue, &pb.Action{
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 368fbdbc4743ad5d571789e5ebb7f76cfb964743
|
Subproject commit 4b73492ffc0824faccddd81053c08d3d7607919a
|
@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/game"
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/types"
|
"gitea.boner.be/bdnugget/goonscape/types"
|
||||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
@ -113,9 +112,5 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
|
|||||||
otherPlayers[state.PlayerId] = types.NewPlayer(state)
|
otherPlayers[state.PlayerId] = types.NewPlayer(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if g, ok := player.UserData.(*game.Game); ok && len(serverMessage.ChatMessages) > 0 {
|
|
||||||
g.Chat.HandleServerMessages(serverMessage.ChatMessages)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,6 @@ type Player struct {
|
|||||||
CurrentTick int64
|
CurrentTick int64
|
||||||
LastUpdateTime time.Time
|
LastUpdateTime time.Time
|
||||||
InterpolationProgress float32
|
InterpolationProgress float32
|
||||||
UserData interface{} // Used to store reference to game
|
|
||||||
FloatingMessage *FloatingMessage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModelAsset struct {
|
type ModelAsset struct {
|
||||||
@ -36,18 +34,6 @@ type ModelAsset struct {
|
|||||||
Texture rl.Texture2D
|
Texture rl.Texture2D
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatMessage struct {
|
|
||||||
PlayerID int32
|
|
||||||
Content string
|
|
||||||
Time time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type FloatingMessage struct {
|
|
||||||
Content string
|
|
||||||
ExpireTime time.Time
|
|
||||||
ScreenPos rl.Vector2 // Store the screen position for 2D rendering
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MapWidth = 50
|
MapWidth = 50
|
||||||
MapHeight = 50
|
MapHeight = 50
|
||||||
@ -58,6 +44,5 @@ const (
|
|||||||
ServerTickRate = 600 * time.Millisecond
|
ServerTickRate = 600 * time.Millisecond
|
||||||
ClientTickRate = 50 * time.Millisecond
|
ClientTickRate = 50 * time.Millisecond
|
||||||
MaxTickDesync = 5
|
MaxTickDesync = 5
|
||||||
// ServerAddr = "localhost:6969"
|
ServerAddr = "localhost:6969"
|
||||||
ServerAddr = "boner.be:6969"
|
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user