diff --git a/mod_files/SKSE/Plugins/SkyrimOutfitSystemSE.ini b/mod_files/SKSE/Plugins/SkyrimOutfitSystemSE.ini index 4a213c1..3e7271a 100644 --- a/mod_files/SKSE/Plugins/SkyrimOutfitSystemSE.ini +++ b/mod_files/SKSE/Plugins/SkyrimOutfitSystemSE.ini @@ -16,3 +16,8 @@ ScriptHotReloading = off Enable = off Port = 3030 AllowRemoteAccess = off +; Allow refreshing actor appearance immediately when +; sending new settings from web UI. Experimental and +; could cause crashes of the game or hangs of the +; web server. Turn it off if you have web UI issues. +RefreshImmediately = on diff --git a/src/cpp/OutfitSystem.cpp b/src/cpp/OutfitSystem.cpp index d963d4e..64e92d0 100644 --- a/src/cpp/OutfitSystem.cpp +++ b/src/cpp/OutfitSystem.cpp @@ -139,6 +139,17 @@ namespace OutfitSystem { RE::AIProcessAugments::UpdateEquipment(pm, actor); } } + void refreshArmorForAllConfiguredActors() { + rust::Vec actors; + { + actors = outfit_service_get_singleton_ptr()->inner().list_actors(); + } + for (auto& actor_form : actors) { + auto actor = RE::Actor::LookupByID(actor_form); + if (!actor) continue; + refreshArmorFor(actor); + } + } void RefreshArmorFor(RE::BSScript::IVirtualMachine* registry, std::uint32_t stackId, RE::StaticFunctionTag*, @@ -151,15 +162,7 @@ namespace OutfitSystem { std::uint32_t stackId, RE::StaticFunctionTag*) { LogExit exitPrint("RefreshArmorForAllConfiguredActors"sv); - rust::Vec actors; - { - actors = outfit_service_get_singleton_ptr()->inner().list_actors(); - } - for (auto& actor_form : actors) { - auto actor = RE::Actor::LookupByID(actor_form); - if (!actor) continue; - refreshArmorFor(actor); - } + refreshArmorForAllConfiguredActors(); } std::vector ActorsNearPC(RE::BSScript::IVirtualMachine* registry, diff --git a/src/cpp/RustSymbols.cpp b/src/cpp/RustSymbols.cpp index 38738f5..a01e56c 100644 --- a/src/cpp/RustSymbols.cpp +++ b/src/cpp/RustSymbols.cpp @@ -31,8 +31,16 @@ namespace OutfitSystem { namespace ArmorFormSearchUtils { std::unique_ptr> RustSearch(rust::Str query); } + void refreshArmorForAllConfiguredActors(); } std::unique_ptr> ArmorSearch(rust::Str query) { return OutfitSystem::ArmorFormSearchUtils::RustSearch(query); +} + +void CreateRefreshAllArmorsTask() { + auto task_intfc = SKSE::GetTaskInterface(); + task_intfc->AddTask([]() { + OutfitSystem::refreshArmorForAllConfiguredActors(); + }); } \ No newline at end of file diff --git a/src/rust/sos/include/customize.h b/src/rust/sos/include/customize.h index dd51291..48a98b1 100644 --- a/src/rust/sos/include/customize.h +++ b/src/rust/sos/include/customize.h @@ -30,6 +30,7 @@ std::unique_ptr GetRuntimePath(); std::unique_ptr GetRuntimeName(); std::unique_ptr GetRuntimeDirectory(); std::unique_ptr> ArmorSearch(rust::Str query); +void CreateRefreshAllArmorsTask(); #endif diff --git a/src/rust/sos/src/interface.rs b/src/rust/sos/src/interface.rs index a050409..b0906cd 100644 --- a/src/rust/sos/src/interface.rs +++ b/src/rust/sos/src/interface.rs @@ -62,6 +62,7 @@ pub mod ffi { // fn GetRuntimePath() -> UniquePtr; // fn GetRuntimeName() -> UniquePtr; fn GetRuntimeDirectory() -> UniquePtr; + fn CreateRefreshAllArmorsTask(); fn ArmorSearch(query: &str) -> UniquePtr>; } extern "Rust" { diff --git a/src/rust/sos/src/settings.rs b/src/rust/sos/src/settings.rs index 2f6e5c5..f1b6631 100644 --- a/src/rust/sos/src/settings.rs +++ b/src/rust/sos/src/settings.rs @@ -79,6 +79,13 @@ impl Settings { .unwrap_or_else(|| false) } + pub fn server_refresh_immediately(&self) -> bool { + self.ini + .getboolcoerce("Server", "RefreshImmediately") + .unwrap_or_else(|_| None) + .unwrap_or_else(|| false) + } + pub fn golden_items(&self) -> &HashedSet { &self.golden_items diff --git a/src/rust/sos/src/web.rs b/src/rust/sos/src/web.rs index ccb8d47..2f0b9a9 100644 --- a/src/rust/sos/src/web.rs +++ b/src/rust/sos/src/web.rs @@ -10,6 +10,7 @@ pub async fn web_server() { .and_then(handlers::get_state); let set_state = warp::path!("state") .and(warp::post()) + .and(warp::query::()) .and(warp::body::bytes()) .and_then(handlers::set_state); let armor_info = warp::path!("armors") @@ -23,7 +24,11 @@ pub async fn web_server() { let mut file = PathBuf::from(GetRuntimeDirectory().to_string()); file.push("Data\\SKSE\\Plugins\\SkyrimOutfitSystemSE_Web"); - let static_assets = warp::fs::dir(file); + let mut static_file = file.clone(); + static_file.push("index.html"); + let static_assets = warp::fs::dir(file) // Handle most static paths + .or(warp::fs::file(static_file)); // Fallback to index for SPA routes + let base = warp::path("api"); let routes = get_state @@ -46,10 +51,10 @@ pub async fn web_server() { mod handlers { use crate::{ armor::{ArmorExt, ArmorLocatorExt}, - interface::ffi::ArmorSearch, + interface::ffi::{ArmorSearch, CreateRefreshAllArmorsTask}, policy::POLICY_STORE, stable_ptr::TESObjectARMOStablePtr, - OUTFIT_SERVICE_SINGLETON, + OUTFIT_SERVICE_SINGLETON, settings::SETTINGS, }; use commonlibsse::{ RE_LookupActorFormID, RE_TESDataHandler_GetSingleton, SKSE_GetSerializationInterface, @@ -66,7 +71,12 @@ mod handlers { Ok(warp::reply::with_status(json, StatusCode::OK)) } - pub async fn set_state(body: bytes::Bytes) -> Result { + #[derive(Deserialize)] + pub struct SetStateParams { + refresh_armors: Option + } + + pub async fn set_state(params: SetStateParams, body: bytes::Bytes) -> Result { let Ok(body) = String::from_utf8(body.to_vec()) else { return Ok(warp::reply::with_status("Failed", StatusCode::BAD_REQUEST)); }; @@ -74,6 +84,10 @@ mod handlers { let result = OUTFIT_SERVICE_SINGLETON .write() .replace_with_json(body.as_str(), unsafe { &*intfc }); + // Try to set the refresh all armors task... unsure if this is a good idea. + if params.refresh_armors.unwrap_or(false) || SETTINGS.server_refresh_immediately() { + CreateRefreshAllArmorsTask(); + } if result { Ok(warp::reply::with_status("OK", StatusCode::OK)) } else {