feat(dtmm): Rework mod template

Ditch the `.mod` file and move its data into the config file.

The `run` function was the only thing that could have been dynamic, but
the vast majority of mods in VT2 never made use of that. Infact, VMF was
probably the only mod that had a different content for that.
This commit is contained in:
Lucas Schwiderski 2023-02-22 15:48:43 +01:00
parent aa05c5bd4a
commit ebcbdaeec0
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
6 changed files with 91 additions and 75 deletions

View file

@ -14,9 +14,9 @@ use sdk::filetype::lua;
use sdk::filetype::package::Package;
use sdk::murmur::Murmur64;
use sdk::{
Bundle, BundleDatabase, BundleFile, BundleFileType, BundleFileVariant, FromBinary, ToBinary,
Bundle, BundleDatabase, BundleFile, BundleFileType, BundleFileVariant, FromBinary, ModConfig,
ToBinary,
};
use serde::Deserialize;
use tokio::io::AsyncWriteExt;
use tokio::{fs, try_join};
use tracing::Instrument;
@ -176,7 +176,10 @@ async fn build_bundles(state: Arc<State>) -> Result<()> {
bundle.add_file(file);
let src = mod_dir.join(pkg_info.get_name());
let bundle_name = Murmur64::hash(pkg_info.get_name())
.to_string()
.to_ascii_lowercase();
let src = mod_dir.join(&bundle_name);
let dest = bundle_dir.clone();
let pkg_name = pkg_info.get_name().clone();
let mod_name = mod_info.get_name().clone();
@ -200,7 +203,7 @@ async fn build_bundles(state: Arc<State>) -> Result<()> {
dest.display()
);
fs::hard_link(&src, dest.as_ref()).await.wrap_err_with(|| {
format!("failed to hard link bundle {pkg_name} for mod {mod_name}")
format!("failed to hard link bundle {pkg_name} for mod {mod_name}. src: {}, dest: {}", src.display(), dest.display())
})
}
.instrument(span);
@ -376,13 +379,6 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> {
Ok(())
}
#[derive(Debug, Default, Deserialize)]
struct ModConfig {
name: String,
#[serde(default)]
description: String,
}
#[tracing::instrument(skip(state))]
pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo> {
let data = fs::read(&info.path)
@ -462,7 +458,7 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo>
.into_iter()
.map(|(name, files)| PackageInfo::new(name, files.into_iter().collect()))
.collect();
let info = ModInfo::new(mod_cfg.name, mod_cfg.description, packages);
let info = ModInfo::new(mod_cfg, packages);
Ok(info)
}

View file

@ -7,6 +7,7 @@ use druid::{
AppDelegate, Command, Data, DelegateCtx, Env, FileInfo, Handled, Lens, Selector, SingleUse,
Target,
};
use sdk::ModConfig;
use tokio::sync::mpsc::UnboundedSender;
use crate::Config;
@ -58,6 +59,27 @@ impl PackageInfo {
}
}
#[derive(Clone, Data, Debug)]
pub(crate) struct ModResourceInfo {
init: String,
data: String,
localization: String,
}
impl ModResourceInfo {
pub(crate) fn get_init(&self) -> &String {
&self.init
}
pub(crate) fn get_data(&self) -> &String {
&self.data
}
pub(crate) fn get_localization(&self) -> &String {
&self.localization
}
}
#[derive(Clone, Data, Debug, Lens)]
pub(crate) struct ModInfo {
name: String,
@ -65,15 +87,22 @@ pub(crate) struct ModInfo {
enabled: bool,
#[lens(ignore)]
packages: Vector<PackageInfo>,
#[lens(ignore)]
resources: ModResourceInfo,
}
impl ModInfo {
pub fn new(name: String, description: String, packages: Vector<PackageInfo>) -> Self {
pub fn new(cfg: ModConfig, packages: Vector<PackageInfo>) -> Self {
Self {
name,
description: Arc::new(description),
packages,
name: cfg.name,
description: Arc::new(cfg.description),
enabled: false,
packages,
resources: ModResourceInfo {
init: cfg.resources.init,
data: cfg.resources.data,
localization: cfg.resources.localization,
},
}
}
@ -84,6 +113,14 @@ impl ModInfo {
pub(crate) fn get_name(&self) -> &String {
&self.name
}
pub(crate) fn get_enabled(&self) -> bool {
self.enabled
}
pub(crate) fn get_resources(&self) -> &ModResourceInfo {
&self.resources
}
}
impl PartialEq for ModInfo {

View file

@ -7,8 +7,7 @@ use color_eyre::{Help, Report};
use futures::future::try_join_all;
use futures::StreamExt;
use sdk::filetype::package::Package;
use sdk::{Bundle, BundleFile};
use serde::Deserialize;
use sdk::{Bundle, BundleFile, ModConfig};
use tokio::fs::{self, File};
use tokio::io::AsyncReadExt;
@ -36,16 +35,8 @@ pub(crate) fn command_definition() -> Command {
))
}
#[derive(Debug, Default, Deserialize)]
struct ProjectConfig {
#[serde(skip)]
dir: PathBuf,
name: String,
packages: Vec<PathBuf>,
}
#[tracing::instrument]
async fn find_project_config(dir: Option<PathBuf>) -> Result<ProjectConfig> {
async fn find_project_config(dir: Option<PathBuf>) -> Result<ModConfig> {
let (path, mut file) = if let Some(path) = dir {
let file = File::open(&path.join(PROJECT_CONFIG_NAME))
.await
@ -83,7 +74,7 @@ async fn find_project_config(dir: Option<PathBuf>) -> Result<ProjectConfig> {
let mut buf = String::new();
file.read_to_string(&mut buf).await?;
let mut cfg: ProjectConfig = serde_sjson::from_str(&buf)?;
let mut cfg: ModConfig = serde_sjson::from_str(&buf)?;
cfg.dir = path;
Ok(cfg)
}
@ -210,23 +201,23 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
})
});
let bundles = try_join_all(tasks).await?;
let bundles = try_join_all(tasks)
.await
.wrap_err("failed to build mod bundles")?;
let mod_file = {
let mut path = cfg.dir.join(&cfg.name);
path.set_extension("mod");
fs::read(path).await?
let config_file = {
let path = cfg.dir.join("dtmt.cfg");
fs::read(&path)
.await
.wrap_err_with(|| format!("failed to read mod config at {}", path.display()))?
};
let config_file = fs::read(cfg.dir.join("dtmt.cfg")).await?;
{
let dest = dest.clone();
let name = cfg.name.clone();
tokio::task::spawn_blocking(move || {
let mut archive = Archive::new(name);
archive.add_mod_file(mod_file);
archive.add_config(config_file);
for bundle in bundles {

View file

@ -8,13 +8,19 @@ use futures::{StreamExt, TryStreamExt};
use string_template::Template;
use tokio::fs::{self, DirBuilder};
const TEMPLATES: [(&str, &str); 6] = [
const TEMPLATES: [(&str, &str); 5] = [
(
"dtmt.cfg",
r#"name = "{{name}}"
description = "An elaborate description of my cool game mod!"
version = "0.1.0"
resources = {
script = "scripts/mods/{{name}}/init"
data = "scripts/mods/{{name}}/data"
localization = "scripts/mods/{{name}}/locationzation"
}
packages = [
"packages/{{name}}"
]
@ -23,21 +29,6 @@ depends = [
"dmf"
]
"#,
),
(
"{{name}}.mod",
r#"return {
run = function()
fassert(rawget(_G, "new_mod"), "`{{title}}` encountered an error loading the Darktide Mod Framework.")
new_mod("{{name}}", {
mod_script = "scripts/mods/{{name}}/{{name}}",
mod_data = "scripts/mods/{{name}}/{{name}}_data",
mod_localization = "scripts/mods/{{name}}/{{name}}_localization",
})
end,
packages = {},
}"#,
),
(
"packages/{{name}}.package",
@ -47,7 +38,7 @@ depends = [
"#,
),
(
"scripts/mods/{{name}}/{{name}}.lua",
"scripts/mods/{{name}}/init.lua",
r#"local mod = get_mod("{{name}}")
-- Your mod code goes here.
@ -55,7 +46,7 @@ depends = [
"#,
),
(
"scripts/mods/{{name}}/{{name}}_data.lua",
"scripts/mods/{{name}}/data.lua",
r#"local mod = get_mod("{{name}}")
return {
@ -65,7 +56,7 @@ return {
}"#,
),
(
"scripts/mods/{{name}}/{{name}}_localization.lua",
"scripts/mods/{{name}}/localization.lua",
r#"return {
mod_description = {
en = "An elaborate description of my cool game mod!",
@ -127,8 +118,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()>
promptly::prompt_default("The mod identifier name", default)?
};
tracing::debug!(root = %root.display());
tracing::debug!(title, name);
tracing::debug!(root = %root.display(), title, name);
let mut data = HashMap::new();
data.insert("name", name.as_str());

View file

@ -12,7 +12,6 @@ use zip::ZipWriter;
pub struct Archive {
name: String,
bundles: Vec<Bundle>,
mod_file: Option<Vec<u8>>,
config_file: Option<Vec<u8>>,
}
@ -21,7 +20,6 @@ impl Archive {
Self {
name,
bundles: Vec::new(),
mod_file: None,
config_file: None,
}
}
@ -30,10 +28,6 @@ impl Archive {
self.bundles.push(bundle)
}
pub fn add_mod_file(&mut self, content: Vec<u8>) {
self.mod_file = Some(content);
}
pub fn add_config(&mut self, content: Vec<u8>) {
self.config_file = Some(content);
}
@ -42,11 +36,6 @@ impl Archive {
where
P: AsRef<Path>,
{
let mod_file = self
.mod_file
.as_ref()
.ok_or_else(|| eyre::eyre!("Mod file is missing in mod archive"))?;
let config_file = self
.config_file
.as_ref()
@ -64,13 +53,6 @@ impl Archive {
let base_path = PathBuf::from(&self.name);
{
let mut name = base_path.join(&self.name);
name.set_extension("mod");
zip.start_file(name.to_string_lossy(), Default::default())?;
zip.write_all(mod_file)?;
}
{
let name = base_path.join("dtmt.cfg");
zip.start_file(name.to_string_lossy(), Default::default())?;

View file

@ -9,3 +9,23 @@ pub use bundle::database::BundleDatabase;
pub use bundle::decompress;
pub use bundle::{Bundle, BundleFile, BundleFileType, BundleFileVariant};
pub use context::Context;
#[derive(Clone, Debug, Default, serde::Deserialize)]
pub struct ModConfigResources {
pub init: String,
pub data: String,
pub localization: String,
}
#[derive(Clone, Debug, Default, serde::Deserialize)]
pub struct ModConfig {
#[serde(skip)]
pub dir: std::path::PathBuf,
pub name: String,
pub description: String,
pub version: String,
pub packages: Vec<std::path::PathBuf>,
pub resources: ModConfigResources,
#[serde(default)]
pub depends: Vec<String>,
}