Skip to content

Commit 6812c80

Browse files
committed
✨ Added asset pack support 📦
Quick 2hr draft - BUILDS, NOT TESTED. A new file type "*.assetpack" was added to modcache with all the usual treatment: - the file is recorded to the cache file (/cache/cache.json), - it's searchable and selectable via UI (`LT_AssetPack`) - can have a thumbnail (filename format '{}-mini.*'' where {} is the assetpack file name without extension) - The file itself can be empty, recognized entries are `assetpack_name` (display name), `assetpack_description` (for display in selector UI) and `assetpack_author` (same syntax as in rig-def (aka .truck) file). Assetpacks are referenced by the file name (standard approach) and can be used for both terrains and actors: - Terrains: in .terrn2 file, add section `[AssetPacks]` followed by list of filenames (with extension, each on separate line) ending with `=` token, same as in `[Objects/Scripts]` sections - this is a quirk of the game. - Actors: in the .truck file, use section `assetpacks` followed by list of filenames (with extension, each on separate line) Note that, unlike any other mod, assetpacks aren't loaded to their own resource group but rather to the resoruce group of the mod which requested them. Therefore try to name all resources in an unique way to avoid clashes.
1 parent 32c257f commit 6812c80

14 files changed

+148
-4
lines changed

source/main/Application.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ enum VisibilityMasks
261261
HIDE_MIRROR = BITMASK(3),
262262
};
263263

264-
enum LoaderType //!< Operation mode for GUI::MainSelector
264+
enum LoaderType //!< Search mode for `ModCache::Query()` & Operation mode for `GUI::MainSelector`
265265
{
266266
LT_None,
267267
LT_Terrain, // Invocable from GUI; No script alias, used in main menu
@@ -278,6 +278,7 @@ enum LoaderType //!< Operation mode for GUI::MainSelector
278278
LT_AllBeam, // Invocable from GUI; Script "all", ext: truck car boat airplane train load
279279
LT_AddonPart, // No script alias, invoked manually, ext: addonpart
280280
LT_Tuneup, // No script alias, invoked manually, ext: tuneup
281+
LT_AssetPack, // No script alias, invoked manually, ext: assetpack
281282
};
282283

283284
// ------------------------------------------------------------------------------------------------

source/main/GameContext.cpp

+30-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ bool GameContext::LoadTerrain(std::string const& filename_part)
126126
// Init resources
127127
App::GetCacheSystem()->LoadResource(terrn_entry);
128128

129-
// Load the terrain
129+
// Load the terrain def file
130130
Terrn2Def terrn2;
131131
std::string const& filename = terrn_entry->fname;
132132
try
@@ -145,6 +145,35 @@ bool GameContext::LoadTerrain(std::string const& filename_part)
145145
return false;
146146
}
147147

148+
// Load asset packs into the terrain-bundle's resource group (quick & dirty approach).
149+
// See also `ContentManager::resourceCollision()` - we always keep the original file and dump the colliding one.
150+
for (std::string const& assetpack_filename: terrn2.assetpack_files)
151+
{
152+
CacheEntryPtr assetpack_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AssetPack, /*partial=*/false, assetpack_filename);
153+
if (assetpack_entry)
154+
{
155+
try
156+
{
157+
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
158+
assetpack_entry->resource_bundle_path, // name (source)
159+
assetpack_entry->resource_bundle_type, // type (source)
160+
terrn_entry->resource_group, // resGroup (target)
161+
false, // recursive
162+
true); // readOnly
163+
}
164+
catch (std::exception const& e)
165+
{
166+
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_TERRN, Console::CONSOLE_SYSTEM_ERROR,
167+
fmt::format(_L("Failed to load asset pack '{}': {}"), assetpack_entry->fname, e.what()));
168+
}
169+
}
170+
else
171+
{
172+
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_TERRN, Console::CONSOLE_SYSTEM_WARNING,
173+
fmt::format(_L("Asset pack '{}' requested by terrain '{}' not found"), assetpack_filename, terrn2.name));
174+
}
175+
}
176+
148177
// CAUTION - the global instance must be set during init! Needed by:
149178
// * GameScript::spawnObject()
150179
// * ProceduralManager

source/main/physics/ActorManager.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ ActorPtr ActorManager::CreateNewActor(ActorSpawnRequest rq, RigDef::DocumentPtr
9090
ActorSpawner spawner;
9191
spawner.ConfigureSections(actor->m_section_config, def);
9292
spawner.ConfigureAddonParts(actor->m_working_tuneup_def);
93+
spawner.ConfigureAssetPacks(actor, def);
9394
spawner.ProcessNewActor(actor, rq, def);
9495

9596
if (App::diag_actor_dump->getBool())

source/main/physics/ActorSpawner.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,43 @@ void ActorSpawner::ConfigureAddonParts(TuneupDefPtr& tuneup_def)
142142
}
143143
}
144144

145+
void ActorSpawner::ConfigureAssetPacks(ActorPtr actor, RigDef::DocumentPtr def)
146+
{
147+
for (auto& module: m_selected_modules)
148+
{
149+
for (RigDef::Assetpack const& assetpack: module->assetpacks)
150+
{
151+
CacheEntryPtr assetpack_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AssetPack, /*partial=*/false, assetpack.filename);
152+
if (assetpack_entry)
153+
{
154+
// Load asset packs into the actor-bundle's resource group (quick & dirty approach).
155+
// See also `ContentManager::resourceCollision()` - we always keep the original file and dump the colliding one.
156+
try
157+
{
158+
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
159+
assetpack_entry->resource_bundle_path, // name (source)
160+
assetpack_entry->resource_bundle_type, // type (source)
161+
actor->getUsedActorEntry()->resource_group, // resGroup (target)
162+
false, // recursive
163+
true); // readOnly
164+
165+
Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup(
166+
actor->getUsedActorEntry()->resource_group);
167+
}
168+
catch (std::exception const& e)
169+
{
170+
this->AddMessage(Message::TYPE_ERROR, fmt::format(_L("Failed to load asset pack '{}': {}"), assetpack.filename, e.what()));
171+
}
172+
}
173+
else
174+
{
175+
this->AddMessage(Message::TYPE_WARNING, fmt::format(_L("Asset pack '{}' requested by actor '{}' not found"),
176+
assetpack.filename, actor->getUsedActorEntry()->fname));
177+
}
178+
}
179+
}
180+
}
181+
145182
void ActorSpawner::CalcMemoryRequirements(ActorMemoryRequirements& req, RigDef::Document::Module* module_def)
146183
{
147184
// 'nodes'

source/main/physics/ActorSpawner.h

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class ActorSpawner
9696
/// @{
9797
void ConfigureSections(Ogre::String const & sectionconfig, RigDef::DocumentPtr def);
9898
void ConfigureAddonParts(TuneupDefPtr& tuneup_def);
99+
void ConfigureAssetPacks(ActorPtr actor, RigDef::DocumentPtr def);
99100
void ProcessNewActor(ActorPtr actor, ActorSpawnRequest rq, RigDef::DocumentPtr def);
100101
static void SetupDefaultSoundSources(ActorPtr const& actor);
101102
/// @}

source/main/resources/CacheSystem.cpp

+41
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ CacheSystem::CacheSystem()
138138
m_known_extensions.push_back("skin");
139139
m_known_extensions.push_back("addonpart");
140140
m_known_extensions.push_back("tuneup");
141+
m_known_extensions.push_back("assetpack");
141142
}
142143

143144
void CacheSystem::LoadModCache(CacheValidity validity)
@@ -719,6 +720,12 @@ void CacheSystem::AddFile(String group, Ogre::FileInfo f, String ext)
719720
new_entries.push_back(entry);
720721
}
721722
}
723+
else if (ext == "assetpack")
724+
{
725+
CacheEntryPtr entry = new CacheEntry();
726+
FillAssetPackDetailInfo(entry, ds);
727+
new_entries.push_back(entry);
728+
}
722729
else
723730
{
724731
CacheEntryPtr entry = new CacheEntry();
@@ -1163,6 +1170,38 @@ void CacheSystem::FillAddonPartDetailInfo(CacheEntryPtr &entry, Ogre::DataStream
11631170
}
11641171
}
11651172

1173+
void CacheSystem::FillAssetPackDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds)
1174+
{
1175+
GenericDocumentPtr doc = new GenericDocument();
1176+
BitMask_t options = GenericDocument::OPTION_ALLOW_SLASH_COMMENTS | GenericDocument::OPTION_ALLOW_NAKED_STRINGS;
1177+
doc->loadFromDataStream(ds, options);
1178+
1179+
GenericDocContextPtr ctx = new GenericDocContext(doc);
1180+
while (!ctx->endOfFile())
1181+
{
1182+
if (ctx->isTokKeyword() && ctx->getTokKeyword() == "assetpack_name")
1183+
{
1184+
entry->dname = ctx->getTokString(1);
1185+
}
1186+
else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "assetpack_description")
1187+
{
1188+
entry->description = ctx->getTokString(1);
1189+
}
1190+
else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "assetpack_author")
1191+
{
1192+
int n = ctx->countLineArgs();
1193+
AuthorInfo author;
1194+
if (n > 1) { author.type = ctx->getTokString(1); }
1195+
if (n > 2) { author.id = (int)ctx->getTokFloat(2); }
1196+
if (n > 3) { author.name = ctx->getTokString(3); }
1197+
if (n > 4) { author.email = ctx->getTokString(4); }
1198+
entry->authors.push_back(author);
1199+
}
1200+
1201+
ctx->seekNextLine();
1202+
}
1203+
}
1204+
11661205
void CacheSystem::FillTuneupDetailInfo(CacheEntryPtr &entry, TuneupDefPtr& tuneup_def)
11671206
{
11681207
if (!tuneup_def->author_name.empty())
@@ -1863,6 +1902,8 @@ size_t CacheSystem::Query(CacheQuery& query)
18631902
add = (query.cqy_filter_type == LT_AddonPart);
18641903
else if (entry->fext == "tuneup")
18651904
add = (query.cqy_filter_type == LT_Tuneup);
1905+
else if (entry->fext == "assetpack")
1906+
add = (query.cqy_filter_type == LT_AssetPack);
18661907
else if (entry->fext == "truck")
18671908
add = (query.cqy_filter_type == LT_AllBeam || query.cqy_filter_type == LT_Vehicle || query.cqy_filter_type == LT_Truck);
18681909
else if (entry->fext == "car")

source/main/resources/CacheSystem.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ namespace RoR {
4646

4747
struct AuthorInfo
4848
{
49-
int id;
49+
int id = -1;
5050
Ogre::String type;
5151
Ogre::String name;
5252
Ogre::String email;
@@ -350,6 +350,7 @@ class CacheSystem
350350
void FillSkinDetailInfo(CacheEntryPtr &entry, std::shared_ptr<SkinDef>& skin_def);
351351
void FillAddonPartDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds);
352352
void FillTuneupDetailInfo(CacheEntryPtr &entry, TuneupDefPtr& tuneup_def);
353+
void FillAssetPackDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds);
353354
/// @}
354355

355356
void GenerateHashFromFilenames(); //!< For quick detection of added/removed content

source/main/resources/ContentManager.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,9 @@ bool ContentManager::resourceCollision(Ogre::Resource* resource, Ogre::ResourceM
330330
{
331331
// RoR loads each resource bundle (see CacheSystem.h for info)
332332
// into dedicated resource group outside the global pool [see CacheSystem::LoadResource()]
333-
// This means resource collision is entirely content creator's fault.
333+
// This means resource collision is pretty much content creator's fault, with 2 exceptions:
334+
// * asset packs (introduced 2024) are mixed into the requesting mod's resource group.
335+
// * bundled resources (e.g. beamobjects.zip) are also mixed into the mod's resource group.
334336
RoR::LogFormat("[RoR|ContentManager] Skipping resource with duplicate name: '%s' (origin: '%s')",
335337
resource->getName().c_str(), resource->getOrigin().c_str());
336338
return false; // Instruct OGRE to drop the new resource and keep the original.

source/main/resources/rig_def_fileformat/RigDef_File.h

+7
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ enum class Keyword
7070
AIRBRAKES,
7171
ANIMATORS,
7272
ANTILOCKBRAKES,
73+
ASSETPACKS,
7374
AUTHOR,
7475
AXLES,
7576
BACKMESH,
@@ -399,6 +400,11 @@ struct AeroAnimator // used by Animator
399400
unsigned int engine_idx = 0u;
400401
};
401402

403+
struct Assetpack
404+
{
405+
std::string filename;
406+
};
407+
402408
struct BaseWheel
403409
{
404410
float width = 0.f;
@@ -1473,6 +1479,7 @@ struct Document
14731479
std::vector<Airbrake> airbrakes;
14741480
std::vector<Animator> animators;
14751481
std::vector<AntiLockBrakes> antilockbrakes;
1482+
std::vector<Assetpack> assetpacks;
14761483
std::vector<Author> author;
14771484
std::vector<Axle> axles;
14781485
std::vector<Beam> beams;

source/main/resources/rig_def_fileformat/RigDef_Parser.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ void Parser::ProcessCurrentLine()
235235
{
236236
case Keyword::AIRBRAKES: this->ParseAirbrakes(); return;
237237
case Keyword::ANIMATORS: this->ParseAnimator(); return;
238+
case Keyword::ASSETPACKS: this->ParseAssetpacks(); return;
238239
case Keyword::AXLES: this->ParseAxles(); return;
239240
case Keyword::BEAMS: this->ParseBeams(); return;
240241
case Keyword::BRAKES: this->ParseBrakes(); return;
@@ -1633,6 +1634,17 @@ void Parser::ParseAirbrakes()
16331634
m_current_module->airbrakes.push_back(airbrake);
16341635
}
16351636

1637+
void Parser::ParseAssetpacks()
1638+
{
1639+
if (! this->CheckNumArguments(1)) { return; }
1640+
1641+
Assetpack assetpack;
1642+
1643+
assetpack.filename = this->GetArgStr(0);
1644+
1645+
m_current_module->assetpacks.push_back(assetpack);
1646+
}
1647+
16361648
void Parser::ParseVideoCamera()
16371649
{
16381650
if (! this->CheckNumArguments(19)) { return; }

source/main/resources/rig_def_fileformat/RigDef_Parser.h

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class Parser
116116
void ParseAirbrakes();
117117
void ParseAnimator();
118118
void ParseAntiLockBrakes();
119+
void ParseAssetpacks();
119120
void ParseAuthor();
120121
void ParseAxles();
121122
void ParseBeams();

source/main/resources/rig_def_fileformat/RigDef_Regexes.h

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ namespace Regexes
122122
E_KEYWORD_BLOCK("airbrakes") /* Position 2 */ \
123123
E_KEYWORD_BLOCK("animators") /* Position 3 etc... */ \
124124
E_KEYWORD_INLINE("AntiLockBrakes") \
125+
E_KEYWORD_BLOCK("assetpacks") \
125126
E_KEYWORD_INLINE("author") \
126127
E_KEYWORD_BLOCK("axles") \
127128
E_KEYWORD_BLOCK("backmesh") \

source/main/resources/terrn2_fileformat/Terrn2FileFormat.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,15 @@ bool Terrn2Parser::LoadTerrn2(Terrn2Def& def, Ogre::DataStreamPtr &ds)
112112
}
113113
}
114114

115+
if (file.HasSection("AssetPacks"))
116+
{
117+
for (auto& assetpack: file.getSettings("AssetPacks"))
118+
{
119+
Ogre::String assetpack_filename = SanitizeUtf8String(assetpack.first);
120+
def.as_files.push_back(TrimStr(assetpack_filename));
121+
}
122+
}
123+
115124
this->ProcessTeleport(def, &file);
116125

117126
return true;

source/main/resources/terrn2_fileformat/Terrn2FileFormat.h

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ struct Terrn2Def
6262
std::list<Terrn2Author> authors;
6363
std::list<std::string> tobj_files;
6464
std::list<std::string> as_files;
65+
std::list<std::string> assetpack_files;
6566
std::list<Terrn2Telepoint> telepoints;
6667
std::string caelum_config;
6768
int caelum_fog_start;

0 commit comments

Comments
 (0)