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::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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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())?;
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue