Skip to content

Commit a36ee2e

Browse files
committed
Add support for multiple mods for a single ref
- This is essentially a merge of the `multimod-wip` branch - Previously, if you wanted multiple textures on a mod, the only way to do that was to author a new override texture with all the data you want and then use that instead of the game's preferred texture - which is a pain and breaks game features like dyeing with custom colors. - You could specify multiple mods for a ref, but they would be initialized as "variants" - which you can only render one of at a time. - Now you can have multiple mods for a ref which are not variants but use one of the variants as a parent. Just like the existing parent support, these will be rendered alongside the parent mod when it is active. Since each mod can specify its own mesh and textures, you can use different data for those without having to cram everything into one set. - This is a bit tedious to work with in practice since you generally need to align the mod meshes in one blend file and then create a secondary blend file with just the child data for the secondary mod - then delete that data from the primary blend file. Export them both to different mmobj files and then create all the yaml files for the two mods. I should probably document this somewhere. - This also opens the door for future features such as shader mods on some parts of an aggregate mod, if I ever decide to do that. But maybe reshade is better for that sort of thing. - Performance: There is some additional housekeeping and some small allocations when the mod(s) are selected, but profiling with very sleepy indicates no real change in performance - after all the vast majority of the time no mod is even selected because the game is rendering something else.
1 parent c9a1026 commit a36ee2e

File tree

8 files changed

+404
-125
lines changed

8 files changed

+404
-125
lines changed

Native/hook_core/src/hook_render.rs

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use dnclr::{init_clr, reload_managed_dll};
2121
use util;
2222
use mod_load;
2323
use mod_load::AsyncLoadState;
24+
use crate::debug_spam;
2425
use crate::input_commands;
2526
use crate::mod_render;
2627
use mod_stats::mod_stats;
@@ -730,9 +731,9 @@ pub enum CheckRenderModResult {
730731
}
731732
/// Check for a mod to render, and if one is found, render it using the supplied function `F`.
732733
/// Returns `CheckRenderModResult` to indicate to result of this check.
733-
pub unsafe fn check_and_render_mod<F>(primCount:u32, NumVertices: u32, rfunc:F) -> CheckRenderModResult
734+
pub unsafe fn check_and_render_mod<F>(primCount:u32, NumVertices: u32, mut rfunc:F) -> CheckRenderModResult
734735
where
735-
F: FnOnce(&ModD3DData,&NativeModData) -> bool {
736+
F: FnMut(&ModD3DData,&NativeModData) -> bool {
736737

737738
let mut loading_mod_name = None;
738739
let res = GLOBAL_STATE.loaded_mods.as_mut()
@@ -745,36 +746,63 @@ where
745746
profile_end!(hdip, mod_select);
746747
r
747748
})
748-
.and_then(|nmod| {
749-
// early out if mod is a deletion mod
750-
if nmod.mod_data.numbers.mod_type == types::interop::ModType::Deletion as i32 {
751-
return Some(nmod.mod_data.numbers.mod_type);
752-
}
753-
// if the mod d3d data isn't loaded, can't render
754-
let d3dd = match nmod.d3d_data {
755-
native_mod::ModD3DState::Loaded(ref d3dd) => d3dd,
756-
// could observe partial if we noted it previously but the deferred load
757-
// hasn't happened yet (since it happens less often)
758-
native_mod::ModD3DState::Partial(_)
759-
| native_mod::ModD3DState::Unloaded => {
760-
// tried to render an unloaded mod, make a note that it should be loaded
761-
let load_next_hs = GLOBAL_STATE.load_on_next_frame.get_or_insert_with(
762-
|| fnv::FnvHashSet::with_capacity_and_hasher(
763-
100,
764-
Default::default(),
765-
));
766-
loading_mod_name = Some(nmod.name.to_owned());
767-
load_next_hs.insert(nmod.name.to_owned());
768-
return None;
769-
}
770-
};
749+
.and_then(|nmods| {
750+
751+
let modslice = nmods.as_slice();
752+
let nmodlen = modslice.len();
771753

772-
let rendered = rfunc(d3dd,nmod);
773-
if rendered {
774-
Some(nmod.mod_data.numbers.mod_type)
754+
debug_spam!(|| format!("select returned {} mods, first: {}", nmodlen, if nmodlen > 0 {
755+
&modslice[0].name
775756
} else {
776-
None
757+
"none"
758+
}));
759+
for nmod in modslice {
760+
// early out if mod is a deletion mod
761+
if nmod.mod_data.numbers.mod_type == types::interop::ModType::Deletion as i32 {
762+
return Some(nmod.mod_data.numbers.mod_type);
763+
}
764+
// if the mod d3d data isn't loaded, can't render
765+
match nmod.d3d_data {
766+
native_mod::ModD3DState::Loaded(_) => (),
767+
// could observe partial if we noted it previously but the deferred load
768+
// hasn't happened yet (since it happens less often)
769+
native_mod::ModD3DState::Partial(_)
770+
| native_mod::ModD3DState::Unloaded => {
771+
debug_spam!(|| format!("starting load of requested mod: {}", nmod.name));
772+
// tried to render an unloaded mod, make a note that it should be loaded
773+
let load_next_hs = GLOBAL_STATE.load_on_next_frame.get_or_insert_with(
774+
|| fnv::FnvHashSet::with_capacity_and_hasher(
775+
100,
776+
Default::default(),
777+
));
778+
loading_mod_name = Some(nmod.name.to_owned());
779+
load_next_hs.insert(nmod.name.to_owned());
780+
return None;
781+
}
782+
};
783+
}
784+
// If it returned multiple mods we currently assume they have the same type. Since all these mods must share the same
785+
// ref, it doesn't make sense to have (for instance) two mods where one is a deletion, the other a replacement,
786+
// or one a replacement other an addition; the semantics are mostly exclusive.
787+
let mut first_mod_type = None;
788+
for nmod in modslice {
789+
debug_spam!(|| format!("rend mod: {}, loadstate: {:?}, ({} total)", nmod.name, nmod.d3d_data, modslice.len()));
790+
if let native_mod::ModD3DState::Loaded(ref d3dd) = nmod.d3d_data {
791+
debug_spam!(|| format!("rendering loaded mod: {}", nmod.name));
792+
let rendered = rfunc(d3dd,nmod);
793+
if rendered {
794+
debug_spam!(|| format!("{} was rendered", nmod.name));
795+
if first_mod_type.is_none() {
796+
first_mod_type = Some(nmod.mod_data.numbers.mod_type)
797+
}
798+
}
799+
} else {
800+
debug_spam!(|| format!("cannot render mod {} because it isn't loaded", nmod.name));
801+
}
802+
debug_spam!(|| format!("finish rend: {}", nmod.name));
777803
}
804+
debug_spam!(|| format!("done rend loop for {} mods", modslice.len() ));
805+
first_mod_type
778806
});
779807

780808
match (res,loading_mod_name) {

Native/hook_core/src/input_commands.rs

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::hook_render::hook_set_texture;
1717
use crate::global_state::MAX_STAGE;
1818
use crate::hook_render::CLR_OK;
1919
use crate::input;
20+
use crate::mod_render;
2021
use mod_load::AsyncLoadState;
2122

2223
use dnclr::reload_managed_dll;
@@ -319,41 +320,7 @@ fn select_next_variant() {
319320
let lastframe = hookstate.metrics.total_frames;
320321

321322
hookstate.loaded_mods.as_mut().map(|mstate| {
322-
for (mkey, nmdv) in mstate.mods.iter() {
323-
if nmdv.len() <= 1 {
324-
// most mods have no variants
325-
continue;
326-
}
327-
328-
// don't change the selection if none have been rendered recently
329-
let foundrecent = nmdv.iter().find(|nmd| nmd.recently_rendered(lastframe));
330-
if foundrecent.is_none() {
331-
continue;
332-
}
333-
334-
// get the current variant for this mod
335-
let sel_index_entry = mstate.selected_variant.entry(*mkey).or_insert(0);
336-
let mut sel_index = *sel_index_entry;
337-
let start = sel_index;
338-
// select next, skipping over child mods. stop if we wrap to where we started
339-
sel_index += 1;
340-
loop {
341-
if sel_index >= nmdv.len() {
342-
sel_index = 0;
343-
}
344-
if sel_index == start {
345-
break;
346-
}
347-
if nmdv[sel_index].parent_mod_names.is_empty() {
348-
// found one
349-
write_log_file(&format!("selected next variant: {}", nmdv[sel_index].name));
350-
*sel_index_entry = sel_index;
351-
break;
352-
}
353-
// keep looking
354-
sel_index += 1;
355-
}
356-
}
323+
mod_render::select_next_variant(mstate, lastframe);
357324
});
358325
}
359326

Native/hook_core/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ extern crate types;
3131
#[macro_use]
3232
extern crate profiler;
3333

34+
pub const ENABLE_DEBUG_SPAM:bool = false;
35+
pub const DEBUG_SPAM_TO_STDERR:bool = false;
36+
3437
mod debugmode;
3538
mod hook_render;
3639
mod hook_render_d3d11;

0 commit comments

Comments
 (0)