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:
parent
aa05c5bd4a
commit
ebcbdaeec0
6 changed files with 91 additions and 75 deletions
|
@ -14,9 +14,9 @@ use sdk::filetype::lua;
|
||||||
use sdk::filetype::package::Package;
|
use sdk::filetype::package::Package;
|
||||||
use sdk::murmur::Murmur64;
|
use sdk::murmur::Murmur64;
|
||||||
use sdk::{
|
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::io::AsyncWriteExt;
|
||||||
use tokio::{fs, try_join};
|
use tokio::{fs, try_join};
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
@ -176,7 +176,10 @@ async fn build_bundles(state: Arc<State>) -> Result<()> {
|
||||||
|
|
||||||
bundle.add_file(file);
|
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 dest = bundle_dir.clone();
|
||||||
let pkg_name = pkg_info.get_name().clone();
|
let pkg_name = pkg_info.get_name().clone();
|
||||||
let mod_name = mod_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()
|
dest.display()
|
||||||
);
|
);
|
||||||
fs::hard_link(&src, dest.as_ref()).await.wrap_err_with(|| {
|
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);
|
.instrument(span);
|
||||||
|
@ -376,13 +379,6 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
struct ModConfig {
|
|
||||||
name: String,
|
|
||||||
#[serde(default)]
|
|
||||||
description: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo> {
|
pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo> {
|
||||||
let data = fs::read(&info.path)
|
let data = fs::read(&info.path)
|
||||||
|
@ -462,7 +458,7 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo>
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, files)| PackageInfo::new(name, files.into_iter().collect()))
|
.map(|(name, files)| PackageInfo::new(name, files.into_iter().collect()))
|
||||||
.collect();
|
.collect();
|
||||||
let info = ModInfo::new(mod_cfg.name, mod_cfg.description, packages);
|
let info = ModInfo::new(mod_cfg, packages);
|
||||||
|
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use druid::{
|
||||||
AppDelegate, Command, Data, DelegateCtx, Env, FileInfo, Handled, Lens, Selector, SingleUse,
|
AppDelegate, Command, Data, DelegateCtx, Env, FileInfo, Handled, Lens, Selector, SingleUse,
|
||||||
Target,
|
Target,
|
||||||
};
|
};
|
||||||
|
use sdk::ModConfig;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use crate::Config;
|
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)]
|
#[derive(Clone, Data, Debug, Lens)]
|
||||||
pub(crate) struct ModInfo {
|
pub(crate) struct ModInfo {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -65,15 +87,22 @@ pub(crate) struct ModInfo {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
#[lens(ignore)]
|
#[lens(ignore)]
|
||||||
packages: Vector<PackageInfo>,
|
packages: Vector<PackageInfo>,
|
||||||
|
#[lens(ignore)]
|
||||||
|
resources: ModResourceInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModInfo {
|
impl ModInfo {
|
||||||
pub fn new(name: String, description: String, packages: Vector<PackageInfo>) -> Self {
|
pub fn new(cfg: ModConfig, packages: Vector<PackageInfo>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name: cfg.name,
|
||||||
description: Arc::new(description),
|
description: Arc::new(cfg.description),
|
||||||
packages,
|
|
||||||
enabled: false,
|
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 {
|
pub(crate) fn get_name(&self) -> &String {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_enabled(&self) -> bool {
|
||||||
|
self.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_resources(&self) -> &ModResourceInfo {
|
||||||
|
&self.resources
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for ModInfo {
|
impl PartialEq for ModInfo {
|
||||||
|
|
|
@ -7,8 +7,7 @@ use color_eyre::{Help, Report};
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use sdk::filetype::package::Package;
|
use sdk::filetype::package::Package;
|
||||||
use sdk::{Bundle, BundleFile};
|
use sdk::{Bundle, BundleFile, ModConfig};
|
||||||
use serde::Deserialize;
|
|
||||||
use tokio::fs::{self, File};
|
use tokio::fs::{self, File};
|
||||||
use tokio::io::AsyncReadExt;
|
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]
|
#[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 (path, mut file) = if let Some(path) = dir {
|
||||||
let file = File::open(&path.join(PROJECT_CONFIG_NAME))
|
let file = File::open(&path.join(PROJECT_CONFIG_NAME))
|
||||||
.await
|
.await
|
||||||
|
@ -83,7 +74,7 @@ async fn find_project_config(dir: Option<PathBuf>) -> Result<ProjectConfig> {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
file.read_to_string(&mut buf).await?;
|
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;
|
cfg.dir = path;
|
||||||
Ok(cfg)
|
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 config_file = {
|
||||||
let mut path = cfg.dir.join(&cfg.name);
|
let path = cfg.dir.join("dtmt.cfg");
|
||||||
path.set_extension("mod");
|
fs::read(&path)
|
||||||
fs::read(path).await?
|
.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 dest = dest.clone();
|
||||||
let name = cfg.name.clone();
|
let name = cfg.name.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let mut archive = Archive::new(name);
|
let mut archive = Archive::new(name);
|
||||||
|
|
||||||
archive.add_mod_file(mod_file);
|
|
||||||
archive.add_config(config_file);
|
archive.add_config(config_file);
|
||||||
|
|
||||||
for bundle in bundles {
|
for bundle in bundles {
|
||||||
|
|
|
@ -8,13 +8,19 @@ use futures::{StreamExt, TryStreamExt};
|
||||||
use string_template::Template;
|
use string_template::Template;
|
||||||
use tokio::fs::{self, DirBuilder};
|
use tokio::fs::{self, DirBuilder};
|
||||||
|
|
||||||
const TEMPLATES: [(&str, &str); 6] = [
|
const TEMPLATES: [(&str, &str); 5] = [
|
||||||
(
|
(
|
||||||
"dtmt.cfg",
|
"dtmt.cfg",
|
||||||
r#"name = "{{name}}"
|
r#"name = "{{name}}"
|
||||||
description = "An elaborate description of my cool game mod!"
|
description = "An elaborate description of my cool game mod!"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
resources = {
|
||||||
|
script = "scripts/mods/{{name}}/init"
|
||||||
|
data = "scripts/mods/{{name}}/data"
|
||||||
|
localization = "scripts/mods/{{name}}/locationzation"
|
||||||
|
}
|
||||||
|
|
||||||
packages = [
|
packages = [
|
||||||
"packages/{{name}}"
|
"packages/{{name}}"
|
||||||
]
|
]
|
||||||
|
@ -23,21 +29,6 @@ depends = [
|
||||||
"dmf"
|
"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",
|
"packages/{{name}}.package",
|
||||||
|
@ -47,7 +38,7 @@ depends = [
|
||||||
"#,
|
"#,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"scripts/mods/{{name}}/{{name}}.lua",
|
"scripts/mods/{{name}}/init.lua",
|
||||||
r#"local mod = get_mod("{{name}}")
|
r#"local mod = get_mod("{{name}}")
|
||||||
|
|
||||||
-- Your mod code goes here.
|
-- 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}}")
|
r#"local mod = get_mod("{{name}}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -65,7 +56,7 @@ return {
|
||||||
}"#,
|
}"#,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"scripts/mods/{{name}}/{{name}}_localization.lua",
|
"scripts/mods/{{name}}/localization.lua",
|
||||||
r#"return {
|
r#"return {
|
||||||
mod_description = {
|
mod_description = {
|
||||||
en = "An elaborate description of my cool game mod!",
|
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)?
|
promptly::prompt_default("The mod identifier name", default)?
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::debug!(root = %root.display());
|
tracing::debug!(root = %root.display(), title, name);
|
||||||
tracing::debug!(title, name);
|
|
||||||
|
|
||||||
let mut data = HashMap::new();
|
let mut data = HashMap::new();
|
||||||
data.insert("name", name.as_str());
|
data.insert("name", name.as_str());
|
||||||
|
|
|
@ -12,7 +12,6 @@ use zip::ZipWriter;
|
||||||
pub struct Archive {
|
pub struct Archive {
|
||||||
name: String,
|
name: String,
|
||||||
bundles: Vec<Bundle>,
|
bundles: Vec<Bundle>,
|
||||||
mod_file: Option<Vec<u8>>,
|
|
||||||
config_file: Option<Vec<u8>>,
|
config_file: Option<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +20,6 @@ impl Archive {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
bundles: Vec::new(),
|
bundles: Vec::new(),
|
||||||
mod_file: None,
|
|
||||||
config_file: None,
|
config_file: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +28,6 @@ impl Archive {
|
||||||
self.bundles.push(bundle)
|
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>) {
|
pub fn add_config(&mut self, content: Vec<u8>) {
|
||||||
self.config_file = Some(content);
|
self.config_file = Some(content);
|
||||||
}
|
}
|
||||||
|
@ -42,11 +36,6 @@ impl Archive {
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
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
|
let config_file = self
|
||||||
.config_file
|
.config_file
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -64,13 +53,6 @@ impl Archive {
|
||||||
|
|
||||||
let base_path = PathBuf::from(&self.name);
|
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");
|
let name = base_path.join("dtmt.cfg");
|
||||||
zip.start_file(name.to_string_lossy(), Default::default())?;
|
zip.start_file(name.to_string_lossy(), Default::default())?;
|
||||||
|
|
|
@ -9,3 +9,23 @@ pub use bundle::database::BundleDatabase;
|
||||||
pub use bundle::decompress;
|
pub use bundle::decompress;
|
||||||
pub use bundle::{Bundle, BundleFile, BundleFileType, BundleFileVariant};
|
pub use bundle::{Bundle, BundleFile, BundleFileType, BundleFileVariant};
|
||||||
pub use context::Context;
|
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>,
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue