Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions libs/libGamedata/lua/GameDataLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,60 @@ void GameDataLoader::Include(const std::string& filepath)
}
}

void addLandscape(WorldDescription& worldDesc, const kaguya::LuaTable& data)
{
worldDesc.landscapes.add(LandscapeDesc(CheckedLuaTable(data), worldDesc));
}

void addTerrainEdge(WorldDescription& worldDesc, const kaguya::LuaTable& data)
{
worldDesc.edges.add(EdgeDesc(CheckedLuaTable(data), worldDesc));
}

void addTerrain(WorldDescription& worldDesc, const kaguya::LuaTable& data)
{
TerrainDesc terrain(CheckedLuaTable(data), worldDesc);

// Validate s2Id
if(terrain.s2Id != 0xFF)
{
// Bit 6 (0x40) is the harbour flag (libsiedler2::HARBOR_MASK) in the S2 map format.
// It is a per-node flag in the map file, not part of the terrain identity.
if(terrain.s2Id & 0x40)
{
throw GameDataLoadError(
helpers::format("Terrain '%1%' has s2Id 0x%2$x with the harbour bit (0x40) set. "
"This bit is reserved for per-node map data (libsiedler2::HARBOR_MASK) "
"and must not be used as part of the terrain ID.",
terrain.name, terrain.s2Id));
}
// Check that no other terrain with the same s2Id + landscape combination exists.
if(worldDesc.terrain.find([s2Id = terrain.s2Id, landscape = terrain.landscape](const TerrainDesc& t) {
return t.s2Id == s2Id && t.landscape == landscape;
}))
{
throw GameDataLoadError(helpers::format("Duplicate s2Id 0x%1$x for landscape '%2%' in terrain '%3%'",
terrain.s2Id, worldDesc.landscapes.get(terrain.landscape).name,
terrain.name));
}
}

worldDesc.terrain.add(std::move(terrain));
}

void GameDataLoader::AddLandscape(const kaguya::LuaTable& data)
{
worldDesc_.landscapes.add(LandscapeDesc(data, worldDesc_));
addLandscape(worldDesc_, data);
}

void GameDataLoader::AddTerrainEdge(const kaguya::LuaTable& data)
{
worldDesc_.edges.add(EdgeDesc(data, worldDesc_));
addTerrainEdge(worldDesc_, data);
}

void GameDataLoader::AddTerrain(const kaguya::LuaTable& data)
{
worldDesc_.terrain.add(TerrainDesc(data, worldDesc_));
addTerrain(worldDesc_, data);
}

void loadGameData(WorldDescription& worldDesc)
Expand Down
8 changes: 8 additions & 0 deletions libs/libGamedata/lua/GameDataLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,12 @@ class GameDataLoader : public LuaInterfaceBase
int curIncludeDepth_;
};

/// @name Shared helpers for adding world description items from Lua tables
/// These can be used by both GameDataLoader (game data init) and LuaInterfaceGame (companion scripts).
/// \{
void addTerrain(WorldDescription& worldDesc, const kaguya::LuaTable& data);
void addLandscape(WorldDescription& worldDesc, const kaguya::LuaTable& data);
void addTerrainEdge(WorldDescription& worldDesc, const kaguya::LuaTable& data);
/// \}

void loadGameData(WorldDescription& worldDesc);
5 changes: 5 additions & 0 deletions libs/rttrConfig/src/RttrConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ void RttrConfig::overridePathMapping(const std::string& id, const boost::filesys
pathMappings[id] = path;
}

void RttrConfig::addPathMapping(const std::string& id, const boost::filesystem::path& path)
{
pathMappings[id] = path;
}

bool RttrConfig::Init()
{
prefixPath_ = GetPrefixPath();
Expand Down
2 changes: 2 additions & 0 deletions libs/rttrConfig/src/RttrConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class RttrConfig : public Singleton<RttrConfig>
boost::filesystem::path ExpandPath(const std::string& path) const;
/// Overwrite a given path mapping
void overridePathMapping(const std::string& id, const boost::filesystem::path& path);
/// Register a new path mapping (used for dynamic prefixes like <RTTR_MAP>)
void addPathMapping(const std::string& id, const boost::filesystem::path& path);
};

#define RTTRCONFIG RttrConfig::inst()
39 changes: 39 additions & 0 deletions libs/s25main/lua/LuaInterfaceGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include "LuaInterfaceGame.h"
#include "EventManager.h"
#include "Game.h"
#include "RttrConfig.h"
#include "WindowManager.h"
#include "ai/AIInterface.h"
#include "ai/AIPlayer.h"
#include "ingameWindows/iwMissionStatement.h"
#include "lua/GameDataLoader.h"
#include "lua/LuaHelpers.h"
#include "lua/LuaPlayer.h"
#include "lua/LuaWorld.h"
Expand Down Expand Up @@ -206,6 +208,7 @@ KAGUYA_MEMBER_FUNCTION_OVERLOADS(SetMissionGoalWrapper, LuaInterfaceGame, SetMis
void LuaInterfaceGame::Register(kaguya::State& state)
{
state["RTTRGame"].setClass(kaguya::UserdataMetatable<LuaInterfaceGame, LuaInterfaceGameBase>()
.addFunction("AddTerrain", &LuaInterfaceGame::AddTerrain)
.addFunction("ClearResources", &LuaInterfaceGame::ClearResources)
.addFunction("GetGF", &LuaInterfaceGame::GetGF)
.addFunction("FormatNumGFs", &LuaInterfaceGame::FormatNumGFs)
Expand Down Expand Up @@ -259,6 +262,42 @@ bool LuaInterfaceGame::Deserialize(Serializer& luaSaveState)
return true;
}

void LuaInterfaceGame::SetMapDir(const boost::filesystem::path& mapDir)
{
mapDir_ = mapDir;
RTTRCONFIG.addPathMapping("MAP", mapDir);
}

void LuaInterfaceGame::AddTerrain(const kaguya::LuaTable& data)
{
if(!mapDir_.empty())
{
// Resolve relative texture paths to <RTTR_MAP>/filename
kaguya::LuaRef texRef = data["texture"];
if(texRef.type() == LUA_TSTRING)
{
std::string texPath = texRef;
if(texPath.find("<RTTR_") != 0 && !texPath.empty())
{
boost::filesystem::path p(texPath);
if(p.is_absolute())
{
throw LuaExecutionError("Absolute paths not allowed in AddTerrain texture path: " + texPath);
}
// Prevent directory traversal
if(texPath.find("..") != std::string::npos)
{
throw LuaExecutionError("Invalid texture path '" + texPath + "': must not contain '..'");
}
// Use <RTTR_MAP> prefix so validatePath and ExpandPath handle it naturally
kaguya::LuaTable mutableData = data;
mutableData["texture"] = std::string("<RTTR_MAP>/") + texPath;
}
}
}
addTerrain(gw.GetDescriptionWriteable(), data);
}

void LuaInterfaceGame::ClearResources()
{
for(unsigned p = 0; p < gw.GetNumPlayers(); p++)
Expand Down
7 changes: 7 additions & 0 deletions libs/s25main/lua/LuaInterfaceGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "LuaInterfaceGameBase.h"
#include "gameTypes/MapCoordinates.h"
#include "gameTypes/PactTypes.h"
#include <boost/filesystem/path.hpp>
#include <memory>
#include <string>

Expand Down Expand Up @@ -45,7 +46,12 @@ class LuaInterfaceGame : public LuaInterfaceGameBase
// called if pact was created
void EventPactCreated(PactType pt, unsigned char suggestedByPlayerId, unsigned char targetPlayerId,
unsigned duration);
/// Set the directory from which relative texture paths in AddTerrain are resolved.
/// Registers <RTTR_MAP> so that ExpandPath and validatePath can resolve it.
void SetMapDir(const boost::filesystem::path& mapDir);

// Callable from Lua
void AddTerrain(const kaguya::LuaTable& data);
void ClearResources();
unsigned GetGF() const;
std::string FormatNumGFs(unsigned numGFs) const;
Expand All @@ -63,6 +69,7 @@ class LuaInterfaceGame : public LuaInterfaceGameBase
ILocalGameState& localGameState;
GameWorld& gw;
Game& game;
boost::filesystem::path mapDir_; ///< Base dir for resolving relative texture paths in AddTerrain
LuaPlayer GetPlayer(int playerIdx);
LuaWorld GetWorld();
};
5 changes: 3 additions & 2 deletions libs/s25main/world/MapLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ bool MapLoader::LoadLuaScript(Game& game, ILocalGameState& localgameState, const
if(!bfs::exists(luaFilePath))
return false;
auto lua = std::make_unique<LuaInterfaceGame>(game, localgameState);
lua->SetMapDir(luaFilePath.parent_path());
if(!lua->loadScript(luaFilePath) || !lua->CheckScriptVersion())
return false;
game.SetLua(std::move(lua));
Expand Down Expand Up @@ -254,8 +255,8 @@ bool MapLoader::InitNodes(const libsiedler2::ArchivItem_Map& map, Exploration ex
// Will be set later
node.harborId.reset();

node.t1 = getTerrainFromS2(t1 & 0x3F); // Only lower 6 bits
node.t2 = getTerrainFromS2(t2 & 0x3F); // Only lower 6 bits
node.t1 = getTerrainFromS2(t1 & ~0x40); // Clear harbour bit (bit 6)
node.t2 = getTerrainFromS2(t2 & ~0x40); // Clear harbour bit (bit 6)
if(!node.t1 || !node.t2)
return false;

Expand Down