refactor/less-complexity #5
247
assets/assets.go
247
assets/assets.go
@ -1,6 +1,9 @@
|
|||||||
package assets
|
package assets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/types"
|
"gitea.boner.be/bdnugget/goonscape/types"
|
||||||
rl "github.com/gen2brain/raylib-go/raylib"
|
rl "github.com/gen2brain/raylib-go/raylib"
|
||||||
)
|
)
|
||||||
@ -9,6 +12,11 @@ import (
|
|||||||
func loadModelAnimations(animPaths map[string]string) (types.AnimationSet, error) {
|
func loadModelAnimations(animPaths map[string]string) (types.AnimationSet, error) {
|
||||||
var animSet types.AnimationSet
|
var animSet types.AnimationSet
|
||||||
|
|
||||||
|
// Only try to load animations if environment variable isn't set
|
||||||
|
if os.Getenv("GOONSCAPE_DISABLE_ANIMATIONS") == "1" {
|
||||||
|
return animSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Load idle animations if specified
|
// Load idle animations if specified
|
||||||
if idlePath, ok := animPaths["idle"]; ok {
|
if idlePath, ok := animPaths["idle"]; ok {
|
||||||
idleAnims := rl.LoadModelAnimations(idlePath)
|
idleAnims := rl.LoadModelAnimations(idlePath)
|
||||||
@ -32,10 +40,129 @@ func loadModelAnimations(animPaths map[string]string) (types.AnimationSet, error
|
|||||||
return animSet, nil
|
return animSet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateModel checks if a model is valid and properly loaded
|
||||||
|
func ValidateModel(model rl.Model) error {
|
||||||
|
if model.Meshes == nil {
|
||||||
|
return fmt.Errorf("model has nil meshes")
|
||||||
|
}
|
||||||
|
if model.Meshes.VertexCount <= 0 {
|
||||||
|
return fmt.Errorf("model has invalid vertex count")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompletelyAvoidExternalModels determines if we should avoid loading external models
|
||||||
|
func CompletelyAvoidExternalModels() bool {
|
||||||
|
return os.Getenv("GOONSCAPE_SAFE_MODE") == "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeLoadModel attempts to load a model, returning a placeholder if it fails
|
||||||
|
func SafeLoadModel(fileName string, fallbackShape int, fallbackColor rl.Color) (rl.Model, bool, rl.Color) {
|
||||||
|
// Don't even try to load external models in safe mode
|
||||||
|
if CompletelyAvoidExternalModels() {
|
||||||
|
rl.TraceLog(rl.LogInfo, "Safe mode enabled, using primitive shape instead of %s", fileName)
|
||||||
|
return createPrimitiveShape(fallbackShape), false, fallbackColor
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// Recover from any panics during model loading
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
rl.TraceLog(rl.LogError, "Panic in SafeLoadModel: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Try to load the model
|
||||||
|
model := rl.LoadModel(fileName)
|
||||||
|
|
||||||
|
// Check if the model is valid
|
||||||
|
if model.Meshes == nil || model.Meshes.VertexCount <= 0 {
|
||||||
|
rl.TraceLog(rl.LogWarning, "Failed to load model %s, using placeholder", fileName)
|
||||||
|
return createPrimitiveShape(fallbackShape), false, fallbackColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// For real models, return zero color since we don't need it
|
||||||
|
return model, true, rl.Color{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createPrimitiveShape creates a simple shape without loading external models
|
||||||
|
func createPrimitiveShape(shapeType int) rl.Model {
|
||||||
|
var mesh rl.Mesh
|
||||||
|
|
||||||
|
switch shapeType {
|
||||||
|
case 0: // Cube
|
||||||
|
mesh = rl.GenMeshCube(1.0, 2.0, 1.0)
|
||||||
|
case 1: // Sphere
|
||||||
|
mesh = rl.GenMeshSphere(1.0, 8, 8)
|
||||||
|
case 2: // Cylinder
|
||||||
|
mesh = rl.GenMeshCylinder(0.8, 2.0, 8)
|
||||||
|
case 3: // Cone
|
||||||
|
mesh = rl.GenMeshCone(1.0, 2.0, 8)
|
||||||
|
default: // Default to cube
|
||||||
|
mesh = rl.GenMeshCube(1.0, 2.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
model := rl.LoadModelFromMesh(mesh)
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
func LoadModels() ([]types.ModelAsset, error) {
|
func LoadModels() ([]types.ModelAsset, error) {
|
||||||
// Goonion model and animations
|
// Force safe mode for now until we fix the segfault
|
||||||
goonerModel := rl.LoadModel("resources/models/gooner/walk_no_y_transform.glb")
|
os.Setenv("GOONSCAPE_SAFE_MODE", "1")
|
||||||
goonerAnims, _ := loadModelAnimations(map[string]string{"idle": "resources/models/gooner/idle_no_y_transform.glb", "walk": "resources/models/gooner/walk_no_y_transform.glb"})
|
|
||||||
|
models := make([]types.ModelAsset, 0, 3)
|
||||||
|
|
||||||
|
// Use environment variable to completely disable model loading
|
||||||
|
safeMode := CompletelyAvoidExternalModels()
|
||||||
|
|
||||||
|
// Colors for the different models
|
||||||
|
goonerColor := rl.Color{R: 255, G: 200, B: 200, A: 255} // Pinkish
|
||||||
|
coomerColor := rl.Color{R: 200, G: 230, B: 255, A: 255} // Light blue
|
||||||
|
shrekeColor := rl.Color{R: 180, G: 255, B: 180, A: 255} // Light green
|
||||||
|
|
||||||
|
// If in safe mode, create all models directly without loading
|
||||||
|
if safeMode {
|
||||||
|
// Gooner model (cube)
|
||||||
|
cube := createPrimitiveShape(0)
|
||||||
|
models = append(models, types.ModelAsset{
|
||||||
|
Model: cube,
|
||||||
|
YOffset: 0.0,
|
||||||
|
PlaceholderColor: goonerColor,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Coomer model (sphere)
|
||||||
|
sphere := createPrimitiveShape(1)
|
||||||
|
models = append(models, types.ModelAsset{
|
||||||
|
Model: sphere,
|
||||||
|
YOffset: -4.0,
|
||||||
|
PlaceholderColor: coomerColor,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Shreke model (cylinder)
|
||||||
|
cylinder := createPrimitiveShape(2)
|
||||||
|
models = append(models, types.ModelAsset{
|
||||||
|
Model: cylinder,
|
||||||
|
YOffset: 0.0,
|
||||||
|
PlaceholderColor: shrekeColor,
|
||||||
|
})
|
||||||
|
|
||||||
|
return models, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest of the function with normal model loading
|
||||||
|
// Load Goonion model with error handling
|
||||||
|
var goonerModel rl.Model
|
||||||
|
var success bool
|
||||||
|
var modelColor rl.Color
|
||||||
|
|
||||||
|
goonerModel, success, modelColor = SafeLoadModel("resources/models/gooner/walk_no_y_transform.glb", 0, goonerColor)
|
||||||
|
|
||||||
|
// Create animations only if model was loaded successfully
|
||||||
|
var goonerAnims types.AnimationSet
|
||||||
|
if success {
|
||||||
|
goonerAnims, _ = loadModelAnimations(map[string]string{
|
||||||
|
"idle": "resources/models/gooner/idle_no_y_transform.glb",
|
||||||
|
"walk": "resources/models/gooner/walk_no_y_transform.glb",
|
||||||
|
})
|
||||||
|
|
||||||
// Apply transformations
|
// Apply transformations
|
||||||
transform := rl.MatrixIdentity()
|
transform := rl.MatrixIdentity()
|
||||||
@ -43,45 +170,105 @@ func LoadModels() ([]types.ModelAsset, error) {
|
|||||||
transform = rl.MatrixMultiply(transform, rl.MatrixRotateX(-90*rl.Deg2rad))
|
transform = rl.MatrixMultiply(transform, rl.MatrixRotateX(-90*rl.Deg2rad))
|
||||||
transform = rl.MatrixMultiply(transform, rl.MatrixScale(1.0, 1.0, 1.0))
|
transform = rl.MatrixMultiply(transform, rl.MatrixScale(1.0, 1.0, 1.0))
|
||||||
goonerModel.Transform = transform
|
goonerModel.Transform = transform
|
||||||
|
}
|
||||||
|
|
||||||
// Coomer model (ready for animations)
|
// Always add a model (real or placeholder)
|
||||||
coomerModel := rl.LoadModel("resources/models/coomer/idle_notransy.glb")
|
models = append(models, types.ModelAsset{
|
||||||
// coomerTexture := rl.LoadTexture("resources/models/coomer.png")
|
|
||||||
// rl.SetMaterialTexture(coomerModel.Materials, rl.MapDiffuse, coomerTexture)
|
|
||||||
// When you have animations, add them like:
|
|
||||||
coomerAnims, _ := loadModelAnimations(map[string]string{"idle": "resources/models/coomer/idle_notransy.glb", "walk": "resources/models/coomer/unsteadywalk_notransy.glb"})
|
|
||||||
coomerModel.Transform = transform
|
|
||||||
|
|
||||||
// Shreke model (ready for animations)
|
|
||||||
shrekeModel := rl.LoadModel("resources/models/shreke.obj")
|
|
||||||
shrekeTexture := rl.LoadTexture("resources/models/shreke.png")
|
|
||||||
rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture)
|
|
||||||
// When you have animations, add them like:
|
|
||||||
// shrekeAnims, _ := loadModelAnimations("resources/models/shreke.glb",
|
|
||||||
// map[string]string{
|
|
||||||
// "idle": "resources/models/shreke_idle.glb",
|
|
||||||
// "walk": "resources/models/shreke_walk.glb",
|
|
||||||
// })
|
|
||||||
|
|
||||||
return []types.ModelAsset{
|
|
||||||
{
|
|
||||||
Model: goonerModel,
|
Model: goonerModel,
|
||||||
Animation: append(goonerAnims.Idle, goonerAnims.Walk...),
|
Animation: append(goonerAnims.Idle, goonerAnims.Walk...),
|
||||||
AnimFrames: int32(len(goonerAnims.Idle) + len(goonerAnims.Walk)),
|
AnimFrames: int32(len(goonerAnims.Idle) + len(goonerAnims.Walk)),
|
||||||
Animations: goonerAnims,
|
Animations: goonerAnims,
|
||||||
YOffset: 0.0,
|
YOffset: 0.0,
|
||||||
},
|
PlaceholderColor: modelColor,
|
||||||
{
|
})
|
||||||
|
|
||||||
|
// Coomer model with safe loading - using a sphere shape
|
||||||
|
var coomerModel rl.Model
|
||||||
|
coomerModel, success, modelColor = SafeLoadModel("resources/models/coomer/idle_notransy.glb", 1, coomerColor)
|
||||||
|
|
||||||
|
if success {
|
||||||
|
// Only load animations if the model loaded successfully
|
||||||
|
coomerAnims, _ := loadModelAnimations(map[string]string{
|
||||||
|
"idle": "resources/models/coomer/idle_notransy.glb",
|
||||||
|
"walk": "resources/models/coomer/unsteadywalk_notransy.glb",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Apply transformations
|
||||||
|
transform := rl.MatrixIdentity()
|
||||||
|
transform = rl.MatrixMultiply(transform, rl.MatrixRotateY(180*rl.Deg2rad))
|
||||||
|
transform = rl.MatrixMultiply(transform, rl.MatrixRotateX(-90*rl.Deg2rad))
|
||||||
|
transform = rl.MatrixMultiply(transform, rl.MatrixScale(1.0, 1.0, 1.0))
|
||||||
|
coomerModel.Transform = transform
|
||||||
|
|
||||||
|
models = append(models, types.ModelAsset{
|
||||||
Model: coomerModel,
|
Model: coomerModel,
|
||||||
Animation: append(coomerAnims.Idle, coomerAnims.Walk...),
|
Animation: append(coomerAnims.Idle, coomerAnims.Walk...),
|
||||||
AnimFrames: int32(len(coomerAnims.Idle) + len(coomerAnims.Walk)),
|
AnimFrames: int32(len(coomerAnims.Idle) + len(coomerAnims.Walk)),
|
||||||
Animations: coomerAnims,
|
Animations: coomerAnims,
|
||||||
YOffset: -4.0,
|
YOffset: -4.0,
|
||||||
},
|
PlaceholderColor: rl.Color{}, // Not a placeholder
|
||||||
{Model: shrekeModel, Texture: shrekeTexture},
|
})
|
||||||
}, nil
|
} else {
|
||||||
|
// Add a placeholder with different shape/color
|
||||||
|
models = append(models, types.ModelAsset{
|
||||||
|
Model: coomerModel,
|
||||||
|
YOffset: -4.0,
|
||||||
|
PlaceholderColor: modelColor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shreke model with safe loading - using a cylinder shape
|
||||||
|
var shrekeModel rl.Model
|
||||||
|
shrekeModel, success, modelColor = SafeLoadModel("resources/models/shreke.obj", 2, shrekeColor)
|
||||||
|
|
||||||
|
if success {
|
||||||
|
// Only proceed with texture if model loaded
|
||||||
|
shrekeTexture := rl.LoadTexture("resources/models/shreke.png")
|
||||||
|
if shrekeTexture.ID <= 0 {
|
||||||
|
rl.TraceLog(rl.LogWarning, "Failed to load shreke texture")
|
||||||
|
} else {
|
||||||
|
rl.SetMaterialTexture(shrekeModel.Materials, rl.MapDiffuse, shrekeTexture)
|
||||||
|
|
||||||
|
models = append(models, types.ModelAsset{
|
||||||
|
Model: shrekeModel,
|
||||||
|
Texture: shrekeTexture,
|
||||||
|
YOffset: 0.0,
|
||||||
|
PlaceholderColor: rl.Color{}, // Not a placeholder
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add another placeholder with different shape/color
|
||||||
|
models = append(models, types.ModelAsset{
|
||||||
|
Model: shrekeModel,
|
||||||
|
YOffset: 0.0,
|
||||||
|
PlaceholderColor: modelColor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(models) == 0 {
|
||||||
|
return nil, fmt.Errorf("failed to load any models")
|
||||||
|
}
|
||||||
|
|
||||||
|
return models, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadMusic(filename string) (rl.Music, error) {
|
func LoadMusic(filename string) (rl.Music, error) {
|
||||||
return rl.LoadMusicStream(filename), nil
|
defer func() {
|
||||||
|
// Recover from any panics during music loading
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
rl.TraceLog(rl.LogError, "Panic in LoadMusic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Skip loading music if environment variable is set
|
||||||
|
if os.Getenv("GOONSCAPE_DISABLE_AUDIO") == "1" {
|
||||||
|
rl.TraceLog(rl.LogInfo, "Audio disabled, skipping music loading")
|
||||||
|
return rl.Music{}, fmt.Errorf("audio disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
music := rl.LoadMusicStream(filename)
|
||||||
|
if music.Stream.Buffer == nil {
|
||||||
|
return music, fmt.Errorf("failed to load music: %s", filename)
|
||||||
|
}
|
||||||
|
return music, nil
|
||||||
}
|
}
|
||||||
|
76
game/game.go
76
game/game.go
@ -170,8 +170,18 @@ func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
|||||||
player.Lock()
|
player.Lock()
|
||||||
defer player.Unlock()
|
defer player.Unlock()
|
||||||
|
|
||||||
|
// Check for invalid model
|
||||||
|
if model.Meshes == nil || model.Meshes.VertexCount <= 0 {
|
||||||
|
// Don't try to draw invalid models
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
grid := GetMapGrid()
|
grid := GetMapGrid()
|
||||||
modelIndex := int(player.ID) % len(g.Models)
|
modelIndex := int(player.ID) % len(g.Models)
|
||||||
|
if modelIndex < 0 || modelIndex >= len(g.Models) {
|
||||||
|
// Prevent out of bounds access
|
||||||
|
modelIndex = 0
|
||||||
|
}
|
||||||
modelAsset := g.Models[modelIndex]
|
modelAsset := g.Models[modelIndex]
|
||||||
|
|
||||||
const defaultHeight = 8.0 // Default height above tile, fine tune per model in types.ModelAsset
|
const defaultHeight = 8.0 // Default height above tile, fine tune per model in types.ModelAsset
|
||||||
@ -185,16 +195,25 @@ func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
|||||||
if modelAsset.Animations.Idle != nil || modelAsset.Animations.Walk != nil {
|
if modelAsset.Animations.Idle != nil || modelAsset.Animations.Walk != nil {
|
||||||
if player.IsMoving && len(modelAsset.Animations.Walk) > 0 {
|
if player.IsMoving && len(modelAsset.Animations.Walk) > 0 {
|
||||||
anim := modelAsset.Animations.Walk[0] // Use first walk animation
|
anim := modelAsset.Animations.Walk[0] // Use first walk animation
|
||||||
|
if anim.FrameCount > 0 {
|
||||||
player.AnimationFrame = player.AnimationFrame % anim.FrameCount
|
player.AnimationFrame = player.AnimationFrame % anim.FrameCount
|
||||||
rl.UpdateModelAnimation(model, anim, player.AnimationFrame)
|
rl.UpdateModelAnimation(model, anim, player.AnimationFrame)
|
||||||
|
}
|
||||||
} else if len(modelAsset.Animations.Idle) > 0 {
|
} else if len(modelAsset.Animations.Idle) > 0 {
|
||||||
anim := modelAsset.Animations.Idle[0] // Use first idle animation
|
anim := modelAsset.Animations.Idle[0] // Use first idle animation
|
||||||
|
if anim.FrameCount > 0 {
|
||||||
player.AnimationFrame = player.AnimationFrame % anim.FrameCount
|
player.AnimationFrame = player.AnimationFrame % anim.FrameCount
|
||||||
rl.UpdateModelAnimation(model, anim, player.AnimationFrame)
|
rl.UpdateModelAnimation(model, anim, player.AnimationFrame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rl.DrawModel(model, playerPos, 16, rl.White)
|
// Use placeholder color if it's set, otherwise use white
|
||||||
|
var drawColor rl.Color = rl.White
|
||||||
|
if player.PlaceholderColor.A > 0 {
|
||||||
|
drawColor = player.PlaceholderColor
|
||||||
|
}
|
||||||
|
rl.DrawModel(model, playerPos, 16, drawColor)
|
||||||
|
|
||||||
// Draw floating messages and path indicators
|
// Draw floating messages and path indicators
|
||||||
if player.FloatingMessage != nil {
|
if player.FloatingMessage != nil {
|
||||||
@ -228,20 +247,43 @@ func (g *Game) DrawPlayer(player *types.Player, model rl.Model) {
|
|||||||
|
|
||||||
func (g *Game) Render() {
|
func (g *Game) Render() {
|
||||||
rl.BeginDrawing()
|
rl.BeginDrawing()
|
||||||
|
defer func() {
|
||||||
|
// This defer will catch any panics that might occur during rendering
|
||||||
|
// and ensure EndDrawing gets called to maintain proper graphics state
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
rl.TraceLog(rl.LogError, "Panic during rendering: %v", r)
|
||||||
|
}
|
||||||
|
rl.EndDrawing()
|
||||||
|
}()
|
||||||
|
|
||||||
rl.ClearBackground(rl.RayWhite)
|
rl.ClearBackground(rl.RayWhite)
|
||||||
|
|
||||||
if !g.isLoggedIn {
|
if !g.isLoggedIn {
|
||||||
g.loginScreen.Draw()
|
g.loginScreen.Draw()
|
||||||
rl.EndDrawing()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.BeginMode3D(g.Camera)
|
rl.BeginMode3D(g.Camera)
|
||||||
g.DrawMap()
|
g.DrawMap()
|
||||||
|
|
||||||
|
// Draw player only if valid
|
||||||
|
if g.Player != nil && g.Player.Model.Meshes != nil {
|
||||||
g.DrawPlayer(g.Player, g.Player.Model)
|
g.DrawPlayer(g.Player, g.Player.Model)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw other players with defensive checks
|
||||||
for _, other := range g.OtherPlayers {
|
for _, other := range g.OtherPlayers {
|
||||||
|
if other == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure model is assigned
|
||||||
if other.Model.Meshes == nil {
|
if other.Model.Meshes == nil {
|
||||||
g.AssignModelToPlayer(other)
|
g.AssignModelToPlayer(other)
|
||||||
|
// Skip this frame if assignment failed
|
||||||
|
if other.Model.Meshes == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g.DrawPlayer(other, other.Model)
|
g.DrawPlayer(other, other.Model)
|
||||||
}
|
}
|
||||||
@ -268,13 +310,15 @@ func (g *Game) Render() {
|
|||||||
rl.DrawText(text, int32(pos.X)-textWidth/2, int32(pos.Y), 20, rl.Yellow)
|
rl.DrawText(text, int32(pos.X)-textWidth/2, int32(pos.Y), 20, rl.Yellow)
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.Player.FloatingMessage != nil {
|
if g.Player != nil && g.Player.FloatingMessage != nil {
|
||||||
drawFloatingMessage(g.Player.FloatingMessage)
|
drawFloatingMessage(g.Player.FloatingMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, other := range g.OtherPlayers {
|
for _, other := range g.OtherPlayers {
|
||||||
|
if other != nil && other.FloatingMessage != nil {
|
||||||
drawFloatingMessage(other.FloatingMessage)
|
drawFloatingMessage(other.FloatingMessage)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw menu if open
|
// Draw menu if open
|
||||||
if g.MenuOpen {
|
if g.MenuOpen {
|
||||||
@ -282,12 +326,11 @@ func (g *Game) Render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only draw chat if menu is not open
|
// Only draw chat if menu is not open
|
||||||
if !g.MenuOpen {
|
if !g.MenuOpen && g.Chat != nil {
|
||||||
g.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight()))
|
g.Chat.Draw(int32(rl.GetScreenWidth()), int32(rl.GetScreenHeight()))
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.DrawFPS(10, 10)
|
rl.DrawFPS(10, 10)
|
||||||
rl.EndDrawing()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Cleanup() {
|
func (g *Game) Cleanup() {
|
||||||
@ -392,12 +435,33 @@ func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) AssignModelToPlayer(player *types.Player) {
|
func (g *Game) AssignModelToPlayer(player *types.Player) {
|
||||||
|
if player == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defensive check for empty models array
|
||||||
|
if len(g.Models) == 0 {
|
||||||
|
rl.TraceLog(rl.LogWarning, "No models available to assign to player")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
modelIndex := int(player.ID) % len(g.Models)
|
modelIndex := int(player.ID) % len(g.Models)
|
||||||
|
if modelIndex < 0 || modelIndex >= len(g.Models) {
|
||||||
|
// Prevent out of bounds access
|
||||||
|
modelIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
modelAsset := g.Models[modelIndex]
|
modelAsset := g.Models[modelIndex]
|
||||||
|
|
||||||
// Just use the original model - don't try to copy it
|
// Validate model before assigning
|
||||||
|
if modelAsset.Model.Meshes == nil {
|
||||||
|
rl.TraceLog(rl.LogWarning, "Trying to assign invalid model to player %d", player.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
player.Model = modelAsset.Model
|
player.Model = modelAsset.Model
|
||||||
player.Texture = modelAsset.Texture
|
player.Texture = modelAsset.Texture
|
||||||
|
player.PlaceholderColor = modelAsset.PlaceholderColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) QuitChan() <-chan struct{} {
|
func (g *Game) QuitChan() <-chan struct{} {
|
||||||
|
57
main.go
57
main.go
@ -7,6 +7,7 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.boner.be/bdnugget/goonscape/game"
|
"gitea.boner.be/bdnugget/goonscape/game"
|
||||||
"gitea.boner.be/bdnugget/goonscape/network"
|
"gitea.boner.be/bdnugget/goonscape/network"
|
||||||
@ -14,9 +15,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Set up panic recovery at the top level
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
log.Printf("Recovered from panic in main: %v", r)
|
log.Printf("Recovered from fatal panic in main: %v", r)
|
||||||
|
// Give the user a chance to see the error
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -46,26 +50,61 @@ func main() {
|
|||||||
network.SetServerAddr(*addr)
|
network.SetServerAddr(*addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize window with error handling
|
||||||
|
rl.SetConfigFlags(rl.FlagMsaa4xHint) // Enable MSAA for smoother rendering
|
||||||
rl.InitWindow(1024, 768, "GoonScape")
|
rl.InitWindow(1024, 768, "GoonScape")
|
||||||
rl.SetExitKey(0)
|
|
||||||
rl.InitAudioDevice()
|
|
||||||
|
|
||||||
gameInstance := game.New()
|
rl.SetExitKey(0)
|
||||||
if err := gameInstance.LoadAssets(); err != nil {
|
|
||||||
log.Printf("Failed to load assets: %v", err)
|
// Initialize audio with error handling
|
||||||
return
|
if !rl.IsAudioDeviceReady() {
|
||||||
|
rl.InitAudioDevice()
|
||||||
|
if !rl.IsAudioDeviceReady() {
|
||||||
|
log.Println("Warning: Failed to initialize audio device, continuing without audio")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a maximum of 3 attempts to load assets
|
||||||
|
var gameInstance *game.Game
|
||||||
|
var loadErr error
|
||||||
|
maxAttempts := 3
|
||||||
|
|
||||||
|
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||||
|
gameInstance = game.New()
|
||||||
|
loadErr = gameInstance.LoadAssets()
|
||||||
|
if loadErr == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Attempt %d/%d: Failed to load assets: %v", attempt, maxAttempts, loadErr)
|
||||||
|
if attempt < maxAttempts {
|
||||||
|
log.Println("Retrying...")
|
||||||
|
gameInstance.Cleanup() // Cleanup before retrying
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if loadErr != nil {
|
||||||
|
log.Printf("Failed to load assets after %d attempts. Starting with default assets.", maxAttempts)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
if gameInstance != nil {
|
||||||
gameInstance.Cleanup()
|
gameInstance.Cleanup()
|
||||||
|
}
|
||||||
rl.CloseWindow()
|
rl.CloseWindow()
|
||||||
|
if rl.IsAudioDeviceReady() {
|
||||||
rl.CloseAudioDevice()
|
rl.CloseAudioDevice()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rl.SetTargetFPS(60)
|
rl.SetTargetFPS(60)
|
||||||
|
|
||||||
|
// Play music if available
|
||||||
|
if gameInstance.Music.Stream.Buffer != nil {
|
||||||
rl.PlayMusicStream(gameInstance.Music)
|
rl.PlayMusicStream(gameInstance.Music)
|
||||||
rl.SetMusicVolume(gameInstance.Music, 0.5)
|
rl.SetMusicVolume(gameInstance.Music, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle OS signals for clean shutdown
|
// Handle OS signals for clean shutdown
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
@ -80,7 +119,11 @@ func main() {
|
|||||||
// Keep game loop in main thread for Raylib
|
// Keep game loop in main thread for Raylib
|
||||||
for !rl.WindowShouldClose() {
|
for !rl.WindowShouldClose() {
|
||||||
deltaTime := rl.GetFrameTime()
|
deltaTime := rl.GetFrameTime()
|
||||||
|
|
||||||
|
// Update music if available
|
||||||
|
if gameInstance.Music.Stream.Buffer != nil {
|
||||||
rl.UpdateMusicStream(gameInstance.Music)
|
rl.UpdateMusicStream(gameInstance.Music)
|
||||||
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -27,6 +27,7 @@ type Player struct {
|
|||||||
LastAnimUpdate time.Time
|
LastAnimUpdate time.Time
|
||||||
LastUpdateTime time.Time
|
LastUpdateTime time.Time
|
||||||
InterpolationProgress float32
|
InterpolationProgress float32
|
||||||
|
PlaceholderColor rl.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
func (p *Player) MoveTowards(target Tile, deltaTime float32, mapGrid [][]Tile) {
|
||||||
|
@ -28,6 +28,7 @@ type ModelAsset struct {
|
|||||||
AnimFrames int32 // Keep this for compatibility
|
AnimFrames int32 // Keep this for compatibility
|
||||||
Animations AnimationSet // New field for organized animations
|
Animations AnimationSet // New field for organized animations
|
||||||
YOffset float32 // Additional height offset (added to default 8.0)
|
YOffset float32 // Additional height offset (added to default 8.0)
|
||||||
|
PlaceholderColor rl.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatMessage struct {
|
type ChatMessage struct {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user