Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: CI

on:
pull_request:
push:
branches: [ master, main, develop, fix-* ]

jobs:
build-and-test-windows:
runs-on: windows-latest
env:
BUILD_DIR: ${{ github.workspace }}\build
ARTIFACTS_DIR: ${{ github.workspace }}\artifacts
COMMONLIBSSE_NG_DIR: ${{ github.workspace }}\deps\CommonLibSSE-NG
VCPKG_ROOT: ${{ github.workspace }}\deps\vcpkg
VCPKG_DEFAULT_TRIPLET: x64-windows
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}\\vcpkg-bincache
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Check CMake version
shell: pwsh
run: cmake --version

- name: Remove existing vcpkg directory
shell: pwsh
run: |
if (Test-Path "deps/vcpkg") { Remove-Item -Recurse -Force "deps/vcpkg" }

- name: Ensure vcpkg binary cache directory exists
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path "$env:VCPKG_DEFAULT_BINARY_CACHE" | Out-Null

- name: Cache vcpkg installed packages
uses: actions/cache@v4
with:
path: |
${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}
restore-keys: |
vcpkg-${{ runner.os }}


- name: Install vcpkg (classic)
shell: pwsh
run: |
git clone --depth=1 https://github.com/microsoft/vcpkg "${{ env.VCPKG_ROOT }}"
& "${{ env.VCPKG_ROOT }}\\bootstrap-vcpkg.bat"

- name: Clone CommonLibSSE-NG
shell: pwsh
run: |
git clone --depth=1 https://github.com/CharmedBaryon/CommonLibSSE-NG "${{ env.COMMONLIBSSE_NG_DIR }}"

- name: Configure (CMake, VS 2022, x64)
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path "${{ env.BUILD_DIR }}" | Out-Null
$env:CommonLibSSEPath_NG = "${{ env.COMMONLIBSSE_NG_DIR }}"
$env:CompiledPluginsPath = "${{ env.ARTIFACTS_DIR }}"
cmake -S . -B "${{ env.BUILD_DIR }}" `
-G "Visual Studio 17 2022" -A x64 `
-DCMAKE_TOOLCHAIN_FILE="${{ env.VCPKG_ROOT }}\scripts\buildsystems\vcpkg.cmake" `
-DCMAKE_BUILD_TYPE=Release `
-DBUILD_TESTS=ON

- name: Build (Release)
shell: pwsh
run: |
$env:CommonLibSSEPath_NG = "${{ env.COMMONLIBSSE_NG_DIR }}"
$env:CompiledPluginsPath = "${{ env.ARTIFACTS_DIR }}"
cmake --build "${{ env.BUILD_DIR }}" --config Release --parallel

- name: Run tests (Release)
shell: pwsh
run: |
ctest --test-dir "${{ env.BUILD_DIR }}" -C Release --output-on-failure

- name: Verify artifacts
shell: pwsh
run: |
Write-Host "Verifying Release build artifacts..."
Get-ChildItem -Path "${{ env.ARTIFACTS_DIR }}" -Recurse -File | Select-Object FullName, Length, LastWriteTime | Format-Table -AutoSize
if (-not (Test-Path "${{ env.ARTIFACTS_DIR }}\SKSE\Plugins\TrueHUD.dll")) {
Write-Error "TrueHUD.dll not found in artifacts directory!"
exit 1
}
$dllSize = (Get-Item "${{ env.ARTIFACTS_DIR }}\SKSE\Plugins\TrueHUD.dll").Length
Write-Host "TrueHUD.dll size: $dllSize bytes"
if ($dllSize -gt 2000000) {
Write-Warning "DLL size is larger than expected for Release build (>2MB). Verify this is not a Debug build."
}

- name: Upload artifacts (DLL/PDB)
if: always()
uses: actions/upload-artifact@v4
with:
name: truehud-artifacts
path: |
${{ env.ARTIFACTS_DIR }}/**/*
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -421,5 +421,9 @@ FodyWeavers.xsd

# End of https://www.toptal.com/developers/gitignore/api/cmake,visualstudio

/build
# Project-specific ignores
/build*/
/vcpkg_installed/
/CMakeUserPresets.json
/CMakeUserPresets.json.user
*.swf
11 changes: 9 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,24 @@ macro(set_from_environment VARIABLE)
endmacro()

set_from_environment(CompiledPluginsPath)
# Provide a default deploy path if not specified via environment
if(NOT DEFINED CompiledPluginsPath)
message(FATAL_ERROR "CompiledPluginsPath is not set")
set(CompiledPluginsPath "${CMAKE_BINARY_DIR}/_deploy")
endif()

option(COPY_OUTPUT "Copy the output of build operations to the game directory" ON)
option(ENABLE_SKYRIM_SE "Enable support for Skyrim SE in the dynamic runtime feature." ON)
option(ENABLE_SKYRIM_AE "Enable support for Skyrim AE in the dynamic runtime feature." ON)
option(ENABLE_SKYRIM_VR "Enable support for Skyrim VR in the dynamic runtime feature." OFF)
set(BUILD_TESTS OFF)
option(BUILD_TESTS "Build unit tests" ON)

list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")

add_subdirectory(src)

if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

include(cmake/packaging.cmake)
12 changes: 6 additions & 6 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
"cacheVariables": {
"CMAKE_BUILD_TYPE": {
"type": "STRING",
"value": "Debug"
"value": "Release"
}
},
"errors": {
"deprecated": true
},
"hidden": true,
"name": "cmake-dev",
"name": "cmake-release",
"warnings": {
"deprecated": true,
"dev": true
Expand Down Expand Up @@ -48,17 +48,17 @@
"value": "x64"
},
"cacheVariables": {
"CMAKE_CXX_FLAGS": "/EHsc /MP /W4 /WX"
"CMAKE_CXX_FLAGS": "/EHsc /MP /W4"
},
"generator": "Visual Studio 17 2022",
"inherits": [
"cmake-dev",
"cmake-release",
"vcpkg",
"windows"
],
"name": "vs2022-windows",
"name": "vs2022-windows-release",
"toolset": "v143"
}
],
"version": 3
}
}
6 changes: 5 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ target_include_directories(
"${SOURCE_DIR}"
)

set(SKSE_SUPPORT_XBYAK ON)
# Allow callers to override Xbyak support; default to ON if not explicitly set
if(NOT DEFINED SKSE_SUPPORT_XBYAK)
set(SKSE_SUPPORT_XBYAK ON)
endif()
add_subdirectory("$ENV{CommonLibSSEPath_NG}" CommonLibSSE EXCLUDE_FROM_ALL)

find_package(xbyak REQUIRED CONFIG)
Expand Down Expand Up @@ -142,6 +145,7 @@ if("${COPY_OUTPUT}")
add_custom_command(
TARGET "${PROJECT_NAME}"
POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CompiledPluginsPath}/SKSE/Plugins"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:${PROJECT_NAME}>" "${CompiledPluginsPath}/SKSE/Plugins/"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_PDB_FILE:${PROJECT_NAME}>" "${CompiledPluginsPath}/SKSE/Plugins/"
VERBATIM
Expand Down
66 changes: 37 additions & 29 deletions src/HUDHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,13 @@ HUDHandler::EventResult HUDHandler::ProcessEvent(const RE::TESHitEvent* a_event,
}

if (causeActorHandle && targetActorHandle) {
auto causeActor = causeActorHandle.get()->As<RE::Actor>();
auto targetActor = targetActorHandle.get()->As<RE::Actor>();
auto causeRef = causeActorHandle.get();
auto targetRef = targetActorHandle.get();
if (!causeRef || !targetRef) {
return EventResult::kContinue;
}
auto causeActor = causeRef->As<RE::Actor>();
auto targetActor = targetRef->As<RE::Actor>();

if (causeActor && targetActor) {
if (causeActor->IsDead() || targetActor->IsDead()) {
Expand Down Expand Up @@ -236,25 +241,21 @@ HUDHandler::EventResult HUDHandler::ProcessEvent(const RE::MenuOpenCloseEvent* a
}

// Hide the widgets when a menu is open
if (const auto controlMap = RE::ControlMap::GetSingleton()) {
const auto& priorityStack = controlMap->GetRuntimeData().contextPriorityStack;
if (priorityStack.empty()) {
HUDHandler::GetSingleton()->SetMenuVisibilityMode(MenuVisibilityMode::kHidden);
} else if (priorityStack.back() == ContextID::kGameplay ||
priorityStack.back() == ContextID::kFavorites ||
priorityStack.back() == ContextID::kConsole) {
HUDHandler::GetSingleton()->SetMenuVisibilityMode(MenuVisibilityMode::kVisible);
} else if ((priorityStack.back() == ContextID::kCursor ||
priorityStack.back() == ContextID::kItemMenu ||
priorityStack.back() == ContextID::kMenuMode ||
priorityStack.back() == ContextID::kInventory) &&
(RE::UI::GetSingleton()->IsMenuOpen(RE::DialogueMenu::MENU_NAME) ||
!Settings::bRecentLootHideInCraftingMenus && RE::UI::GetSingleton()->IsMenuOpen(RE::CraftingMenu::MENU_NAME) ||
!Settings::bRecentLootHideInInventoryMenus && (RE::UI::GetSingleton()->IsMenuOpen(RE::BarterMenu::MENU_NAME) ||
RE::UI::GetSingleton()->IsMenuOpen(RE::ContainerMenu::MENU_NAME) ||
RE::UI::GetSingleton()->IsMenuOpen(RE::GiftMenu::MENU_NAME) ||
RE::UI::GetSingleton()->IsMenuOpen(RE::InventoryMenu::MENU_NAME)))) {
// NOTE: Avoid using ControlMap::contextPriorityStack due to reported crashes on some setups
if (const auto ui = RE::UI::GetSingleton()) {
const bool dialogueOpen = ui->IsMenuOpen(RE::DialogueMenu::MENU_NAME);
const bool craftingOpen = !Settings::bRecentLootHideInCraftingMenus && ui->IsMenuOpen(RE::CraftingMenu::MENU_NAME);
const bool inventoryOpen = !Settings::bRecentLootHideInInventoryMenus && (
ui->IsMenuOpen(RE::BarterMenu::MENU_NAME) ||
ui->IsMenuOpen(RE::ContainerMenu::MENU_NAME) ||
ui->IsMenuOpen(RE::GiftMenu::MENU_NAME) ||
ui->IsMenuOpen(RE::InventoryMenu::MENU_NAME));

if (dialogueOpen || craftingOpen || inventoryOpen) {
HUDHandler::GetSingleton()->SetMenuVisibilityMode(MenuVisibilityMode::kPartial);
} else if (ui->IsMenuOpen(RE::HUDMenu::MENU_NAME)) {
// Treat gameplay/favorites/console cases as visible; HUD is still present in these
HUDHandler::GetSingleton()->SetMenuVisibilityMode(MenuVisibilityMode::kVisible);
} else {
HUDHandler::GetSingleton()->SetMenuVisibilityMode(MenuVisibilityMode::kHidden);
}
Expand Down Expand Up @@ -700,8 +701,13 @@ bool HUDHandler::CheckActorForBoss(RE::ObjectRefHandle a_refHandle)
return false;
}

auto ref = a_refHandle.get();
if (!ref) {
return false;
}

auto playerCharacter = RE::PlayerCharacter::GetSingleton();
auto actor = a_refHandle.get()->As<RE::Actor>();
auto actor = ref->As<RE::Actor>();
if (actor && playerCharacter && (actor != playerCharacter)) {
// Check whether the target is even alive or hostile first
if (actor->IsDead() || (actor->AsActorState()->IsBleedingOut() && actor->IsEssential()) || !actor->IsHostileToActor(playerCharacter)) {
Expand Down Expand Up @@ -733,8 +739,8 @@ bool HUDHandler::CheckActorForBoss(RE::ObjectRefHandle a_refHandle)

// Check current loc refs
if (auto currentLocation = playerCharacter->GetPlayerRuntimeData().currentLocation) {
for (auto& ref : currentLocation->specialRefs) {
if (ref.type && Settings::bossLocRefTypes.contains(ref.type) && ref.refData.refID == actor->formID) {
for (auto& locRef : currentLocation->specialRefs) {
if (locRef.type && Settings::bossLocRefTypes.contains(locRef.type) && locRef.refData.refID == actor->formID) {
return true;
}
}
Expand Down Expand Up @@ -766,13 +772,15 @@ void HUDHandler::Initialize()

}

void HUDHandler::Process(TrueHUDMenu& a_menu, float a_deltaTime)
void HUDHandler::Process(TrueHUDMenu& a_menu, float a_deltaTime)
{
while (!_taskQueue.empty()) {
auto& task = _taskQueue.front();
task(a_menu);
_taskQueue.pop();
}
DrainAndProcessTasks([&](HUDTask& task) {
if (task) {
task(a_menu);
} else {
logger::warn("Skipping empty HUD task (drained)");
}
});

for (auto it = _stackingDamage.begin(), next_it = it; it != _stackingDamage.end(); it = next_it) {
++next_it;
Expand Down
Loading