Fix hard crash when terrain is missing from map

Previously, opening a map that referenced a deleted or missing terrain
caused a severe crash with multiple error dialogs, forcing users to kill
the process. This change restores the previous behavior where missing
terrains are gracefully handled: a warning is logged and the terrain is
substituted with a fallback placeholder (ErrorTexture from
TextureManager).

This significantly improves UX for developers and modders working with
outdated or broken maps, aligning behavior with expectations from
earlier versions of the editor.
This commit is contained in:
trompetin17 2025-05-29 19:31:23 -05:00
parent 9dfc638b80
commit 81dfd51eb3
No known key found for this signature in database
GPG key ID: 6C585FA3FC5DB179
4 changed files with 59 additions and 16 deletions

View file

@ -137,8 +137,8 @@ CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr properties, con
if (CRenderer::IsInitialised())
m_TerrainAlpha = g_TexMan.LoadAlphaMap(alphamap);
float texAngle = 0.f;
float texSize = 1.f;
float texAngle{0.f};
float texSize{1.f};
if (m_pProperties)
{
@ -146,19 +146,33 @@ CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr properties, con
texAngle = m_pProperties->GetTextureAngle();
texSize = m_pProperties->GetTextureSize();
}
m_TextureMatrix.SetZero();
m_TextureMatrix._11 = cosf(texAngle) / texSize;
m_TextureMatrix._13 = -sinf(texAngle) / texSize;
m_TextureMatrix._21 = -sinf(texAngle) / texSize;
m_TextureMatrix._23 = -cosf(texAngle) / texSize;
m_TextureMatrix._44 = 1.f;
GenerateTextureMatrix(texAngle, texSize);
GroupVector::iterator it=m_Groups.begin();
for (;it!=m_Groups.end();++it)
(*it)->AddTerrain(this);
}
CTerrainTextureEntry::CTerrainTextureEntry(const CStr tag):
m_pProperties(nullptr),
m_BaseColor(0),
m_BaseColorValid(false),
m_DiffuseTexturePath(""),
m_Tag(tag)
{
if (!CRenderer::IsInitialised())
return;
const VfsPath alphamap{"standard"};
const VfsPath mat{VfsPath{"art/materials"} / "terrain_norm_spec.xml"};
m_Material = g_Renderer.GetSceneRenderer().GetMaterialManager().LoadMaterial(mat);
const CTexturePtr texptr{g_Renderer.GetTextureManager().GetErrorTexture()};
m_Material.AddSampler(CMaterial::TextureSampler{str_baseTex, texptr});
m_TerrainAlpha = g_TexMan.LoadAlphaMap(alphamap);
GenerateTextureMatrix(0.0f, 1.f);
}
CTerrainTextureEntry::~CTerrainTextureEntry()
{
for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it)
@ -184,3 +198,13 @@ void CTerrainTextureEntry::BuildBaseColor()
m_BaseColorValid = true;
}
}
void CTerrainTextureEntry::GenerateTextureMatrix(const float texAngle, const float texSize)
{
m_TextureMatrix.SetZero();
m_TextureMatrix._11 = cosf(texAngle) / texSize;
m_TextureMatrix._13 = -sinf(texAngle) / texSize;
m_TextureMatrix._21 = -sinf(texAngle) / texSize;
m_TextureMatrix._23 = -cosf(texAngle) / texSize;
m_TextureMatrix._44 = 1.f;
}

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2025 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -37,6 +37,19 @@ public:
// Most of the texture's data is delay-loaded, so after the constructor has
// been called, the texture entry is ready to be used.
CTerrainTextureEntry(CTerrainPropertiesPtr props, const VfsPath& path);
/**
* @brief Constructs a terrain texture entry with the given tag.
*
* This constructor creates a placeholder (dummy) texture entry,
* allowing the system to handle missing terrain textures gracefully.
* It ensures stability by avoiding crashes when a texture file is
* not found.
*
* @param tag The identifier for the terrain texture entry.
*/
CTerrainTextureEntry(const CStr tag);
~CTerrainTextureEntry();
const CStr& GetTag() const { return m_Tag; }
@ -67,6 +80,7 @@ public:
CTerrainTextureManager::TerrainAlphaMap::iterator m_TerrainAlpha;
private:
void GenerateTextureMatrix(const float texAngle, const float texSize);
// Tag = file name stripped of path and extension (grass_dark_1)
CStr m_Tag;

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2024 Wildfire Games.
/* Copyright (C) 2025 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -67,7 +67,7 @@ void CTerrainTextureManager::UnloadTerrainTextures()
m_LastGroupIndex = 0;
}
CTerrainTextureEntry* CTerrainTextureManager::FindTexture(const CStr& tag_) const
CTerrainTextureEntry* CTerrainTextureManager::FindTexture(const CStr& tag_)
{
CStr tag = tag_.BeforeLast("."); // Strip extension
@ -75,8 +75,13 @@ CTerrainTextureEntry* CTerrainTextureManager::FindTexture(const CStr& tag_) cons
if (te->GetTag() == tag)
return te;
LOGWARNING("CTerrainTextureManager: Couldn't find terrain %s", tag.c_str());
return 0;
LOGWARNING("CTerrainTextureManager: Couldn't find terrain %s using fallback texture", tag.c_str());
// If the texture is not found, return a default texture.
// This is a fallback texture, so it should not be used in the editor.
CTerrainTextureEntry* fallback{new CTerrainTextureEntry{tag}};
m_TextureEntries.push_back(fallback);
return fallback;
}
CTerrainTextureEntry* CTerrainTextureManager::AddTexture(const CTerrainPropertiesPtr& props, const VfsPath& path)

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2023 Wildfire Games.
/* Copyright (C) 2025 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -103,7 +103,7 @@ public:
void UnloadTerrainTextures();
CTerrainTextureEntry* FindTexture(const CStr& tag) const;
CTerrainTextureEntry* FindTexture(const CStr& tag);
// Create a texture object for a new terrain texture at path, using the
// property sheet props.