diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..61260450f8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,139 @@ +## General Project Information + +- Language: **C++ (native)** with sol2-based **Lua API framework**, targeting multiple platforms (Windows, Mac, Linux). +- Domain: Real-time 3D engine and tooling for classic-era Tomb Raider–style games. +- Architecture: Grid-based, room-based world structure. Rooms are connected via portals (horizontal/vertical) strictly aligned to the grid. +- World units: 1 sector = 1024 world units = roughly 2 meters. +- Codebase is **performance-critical** and low-level. Unnecessary abstractions should be avoided. + +## General Guidelines + +- Prefer simple, explicit, low-overhead C++, with a preference for C-style patterns. +- Favor readability and predictability over abstraction. +- Minimize hidden behavior and implicit costs. +- Prefer grouping all feature-related functionality within self-contained modules. Avoid creating large code blocks over 10–15 lines in existing modules; instead, offload code to helper functions. +- Before implementing your own helper methods for common math operations, refer to existing utility modules in the `/Math` subdirectory (`Math/Geometry.cpp`, `Math/Legacy.cpp`, `Math/Random.cpp`, `Math/Solvers.cpp`, etc.) and use them instead, if applicable. +- Avoid duplicating and copy-pasting code. Implement helper methods instead whenever similar code is used in several places within a given module, class, or feature scope. +- Avoid using Windows-specific or MSVC compiler-specific code patterns, data types or functions. Prefer universal patterns that are tolerated by all platform-specific compilers (MSVC, g++, Clang). + +## Casting Rules + +- Prefer C-style casts: + + ```cpp + int value = (int)someFloat; + ``` + +- Avoid these C++-style casts unless absolutely necessary by design: + + ```cpp + static_cast(someFloat); + reinterpret_cast<...>(); + const_cast<...>(); + ``` + +## Types + +- Avoid types ending with `_t`, such as `size_t`, `uint32_t`, `int16_t`, in local code. Use these types only when referencing external library methods or functions. +- Prefer explicit types such as `char`, `unsigned char`, `short`, `unsigned short`, `int`, `unsigned int`. +- Prefer `unsigned char` and `char` over `unsigned byte` and `byte`. + +## Namespaces + +- Do not use anonymous namespaces, like in this example: + + ```cpp + namespace + { + // code... + } + ``` + +## Includes + +- Local includes must use quotes `""`. +- External library or system includes must use angle brackets `<>`. +- Refer to the `framework.h` file to determine system or external library includes that should not be explicitly added to modules. + +- **Includes should be grouped in this order**: + - Module's own `.h` include (for `.cpp` files). + - External library or system includes. + - Local project includes. + +- Every include group must be sorted in alphabetical order unless a specific order is required for a successful build. +- Every include group must be separated from another group with a blank line. + +## Formatting + +- Indentation: 4 spaces (no tabs). +- Files must use Windows line endings. +- Only standard ASCII symbols are allowed; do not use Unicode symbols, even in comments. + +- **Braces**: + - Namespace declarations, type definitions, and `if`/`while`/`for` blocks should place the opening curly brace `{` on a new line. + - Always use braces for multi-statement `if` blocks. + - Do not use braces for single-statement `if` blocks unless the statement is part of a multi-branch conditional (`else if`, etc.). + +- **Line breaks and spacing**: + - A blank line separates logically distinct groups of members (fields, constructors, public methods, static helpers, etc.). + - Use spaces around binary operators (`=`, `+`, `==`, etc.) and after commas. + - A single space follows the keywords `if`/`for`/`while` before the opening parenthesis. + - Expressions may be broken into multiple lines and aligned with the previous line's indentation level to improve readability. + +- Do not collapse early exits or single-statement conditions into a single line: + + Bad example: + + ```cpp + if (condition) return; + ``` + + Do this instead: + + ```cpp + if (condition) + return; + ``` + +## Naming + +- **PascalCase** for public types, methods, constants, properties, and events. +- **camelCase** for private fields and local variables. Private fields should start with an underscore (`_itemIndex`, `_rendererFont`). Local variables should not start with an underscore. +- Constants use ALL_CAPS. +- `enum class` members use PascalCase. Old-style `enum` members use ALL_CAPS. +- Methods and variables should use clear, descriptive names and generally avoid Hungarian notation. Avoid short, non-descriptive names such as `s2`, `rwh`, `fmp`, unless the underlying meaning is trivial (e.g., `x`, `i`). +- Do not use prefix-based method and field names, except `g_`, which indicates "global". +- Class method and field names should not repeat words from the class name itself (e.g., `InputHandler::InitializeInputHandler` is a bad name, but `InputHandler::Initialize` is a good name). +- Interfaces are prefixed with `I` and use PascalCase (`IScalable`). + +## Members and Access + +- `auto` should be preferred where possible when the right-hand type is evident from the initializer. Always use `*` and `&` with `auto` if the underlying type is a pointer or reference. +- `constexpr auto` should be preferred for constants unless it interferes with the compiler's ability to make them static. +- Explicit typing should be used only when required by logic or the compiler, or when the type name is shorter than 6 characters (e.g., `int`, `bool`, `float`). +- For floating-point numbers, always use the `f` postfix and include a decimal, even if the value is not fractional (e.g., `2.0f`). + +## Control Flow and Syntax + +- Avoid excessive condition nesting and use early exits or breaks where possible. +- Exception and error handling is done with `try`/`catch`, and caught exceptions are logged using `TENLog` where appropriate. +- Warnings or safeguards must also be logged using `TENLog` where possible if incorrect behavior is caused by user action. + +## Comments + +- Use single-line comments (`//`). Block comments (`/* ... */`) are rare. +- Comments should be sparse. Code should rely on meaningful names rather than inline documentation. +- If a module or function implements complex functionality, a brief description (2–3 lines) may be added before it, separated by a blank line from the function body. +- All descriptive comments should end with a full stop (`.`). + +## Code Grouping + +- Large methods should group related actions together, separated by blank lines. +- Constants and static helpers used multiple times should appear at the top of a class or module. +- Constants used only within a method should be declared within that method. +- One-line lambdas may be grouped together if they share similar meaning or functionality. + +## Performance + +- Prefer performant approaches and locally cache frequently used data within the function scope whenever possible. +- Use `g_Parallel` for bulk operations to maximize performance if parallelization overhead does not exceed the data size. Avoid using it in thread-unsafe contexts or when operating on inherently serial data sets. \ No newline at end of file diff --git a/Documentation/compile.bat b/Documentation/compile.bat index 503b5f8ae7..5b734335e3 100644 --- a/Documentation/compile.bat +++ b/Documentation/compile.bat @@ -32,5 +32,29 @@ if %ERRORLEVEL% neq 0 ( exit /b %ERRORLEVEL% ) +echo Copying TEN logo asset... +copy /Y "..\TEN logo.png" "%DOC_DIR%\TEN logo.png" >nul + +if %ERRORLEVEL% neq 0 ( + echo TEN logo copy failed with error code %ERRORLEVEL% + exit /b %ERRORLEVEL% +) + +echo Copying documentation search script... +copy /Y ".\docs-search.js" "%DOC_DIR%\docs-search.js" >nul + +if %ERRORLEVEL% neq 0 ( + echo Documentation search script copy failed with error code %ERRORLEVEL% + exit /b %ERRORLEVEL% +) + +echo Generating documentation search index... +powershell.exe -ExecutionPolicy Bypass -File "generate_search_index.ps1" -DocRoot "%DOC_DIR%" + +if %ERRORLEVEL% neq 0 ( + echo Documentation search index generation failed with error code %ERRORLEVEL% + exit /b %ERRORLEVEL% +) + echo Documentation build completed successfully! exit /b 0 diff --git a/Documentation/config.ld b/Documentation/config.ld index defc570992..f84f8301cf 100644 --- a/Documentation/config.ld +++ b/Documentation/config.ld @@ -13,8 +13,8 @@ new_type("luautil", "6 Lua utility modules", true) not_luadoc = true -local version = "1.11" -project = " TombEngine" +local version = "2.0" +project = "TombEngine" title = "TombEngine " .. version .. " Lua API" description = "TombEngine " .. version .. " scripting interface" full_description = [[Welcome to the TombEngine scripting API. diff --git a/Documentation/docs-search.js b/Documentation/docs-search.js new file mode 100644 index 0000000000..44d1107e8b --- /dev/null +++ b/Documentation/docs-search.js @@ -0,0 +1,351 @@ +(function () { + function onReady(callback) { + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", callback); + return; + } + + callback(); + } + + function normalizeText(value) { + return (value || "").toLowerCase().replace(/\s+/g, " ").trim(); + } + + function hasClass(element, className) { + return (" " + element.className + " ").indexOf(" " + className + " ") >= 0; + } + + function addClass(element, className) { + if (!hasClass(element, className)) { + element.className = (element.className + " " + className).replace(/^\s+|\s+$/g, ""); + } + } + + function removeClass(element, className) { + element.className = (" " + element.className + " ") + .replace(" " + className + " ", " ") + .replace(/^\s+|\s+$/g, ""); + } + + function createTextElement(tagName, className, text) { + var element = document.createElement(tagName); + element.className = className; + element.textContent = text; + return element; + } + + function escapeHtml(text) { + return (text || "") + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\"/g, """) + .replace(/'/g, "'"); + } + + function escapeRegExp(text) { + return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + + function createHighlightedElement(className, text, terms) { + var element = document.createElement("span"); + var uniqueTerms = []; + var seenTerms = {}; + + element.className = className; + + for (var termIndex = 0; termIndex < terms.length; termIndex++) { + var term = terms[termIndex]; + if (!term || seenTerms[term]) { + continue; + } + + seenTerms[term] = true; + uniqueTerms.push(term); + } + + uniqueTerms.sort(function (left, right) { + return right.length - left.length; + }); + + if (!uniqueTerms.length) { + element.textContent = text; + return element; + } + + var matchPattern = new RegExp("(" + uniqueTerms.map(escapeRegExp).join("|") + ")", "gi"); + var exactPattern = new RegExp("^(" + uniqueTerms.map(escapeRegExp).join("|") + ")$", "i"); + var fragments = String(text || "").split(matchPattern); + var html = ""; + + for (var fragmentIndex = 0; fragmentIndex < fragments.length; fragmentIndex++) { + var fragment = fragments[fragmentIndex]; + if (!fragment) { + continue; + } + + if (exactPattern.test(fragment)) { + html += '' + escapeHtml(fragment) + ""; + } else { + html += escapeHtml(fragment); + } + } + + element.innerHTML = html; + return element; + } + + onReady(function () { + var input = document.getElementById("doc-search-input"); + var popup = document.getElementById("doc-search-popup"); + var summary = document.getElementById("doc-search-summary"); + var resultsContainer = document.getElementById("doc-search-results"); + var searchRoot = window.TENDocRoot || ""; + var index = window.TENSearchIndex || []; + var activeIndex = -1; + var currentResults = []; + + if (!input || !popup || !summary || !resultsContainer) { + return; + } + + for (var itemIndex = 0; itemIndex < index.length; itemIndex++) { + var entry = index[itemIndex]; + entry.text = (entry.text || "").replace(/\s+/g, " ").trim(); + entry.searchText = normalizeText([ + entry.title, + entry.pageTitle, + entry.section, + entry.text, + entry.path + ].join(" ")); + } + + function hidePopup() { + addClass(popup, "doc-search-popup-hidden"); + activeIndex = -1; + } + + function showPopup() { + removeClass(popup, "doc-search-popup-hidden"); + } + + function updateActiveResult() { + var resultNodes = resultsContainer.getElementsByTagName("a"); + for (var resultIndex = 0; resultIndex < resultNodes.length; resultIndex++) { + if (resultIndex === activeIndex) { + addClass(resultNodes[resultIndex], "doc-search-result-active"); + } else { + removeClass(resultNodes[resultIndex], "doc-search-result-active"); + } + } + } + + function buildExcerpt(entry, query, terms) { + var text = entry.text || ""; + var lowerText = text.toLowerCase(); + var matchIndex = query ? lowerText.indexOf(query) : -1; + var hasMatch = matchIndex >= 0; + + if (matchIndex < 0) { + for (var termIndex = 0; termIndex < terms.length; termIndex++) { + matchIndex = lowerText.indexOf(terms[termIndex]); + if (matchIndex >= 0) { + hasMatch = true; + break; + } + } + } + + if (matchIndex < 0) { + matchIndex = 0; + } + + var start = Math.max(0, matchIndex - 60); + var end = Math.min(text.length, matchIndex + 160); + var excerpt = text.slice(start, end).trim(); + + if (start > 0) { + excerpt = "... " + excerpt; + } + + if (end < text.length) { + excerpt += " ..."; + } + + return { + hasMatch: hasMatch, + text: excerpt + }; + } + + function search(query) { + var normalizedQuery = normalizeText(query); + if (!normalizedQuery) { + return []; + } + + var terms = normalizedQuery.split(" "); + var matches = []; + + for (var searchIndex = 0; searchIndex < index.length; searchIndex++) { + var candidate = index[searchIndex]; + var isMatch = true; + + for (var termIndex = 0; termIndex < terms.length; termIndex++) { + if (candidate.searchText.indexOf(terms[termIndex]) < 0) { + isMatch = false; + break; + } + } + + if (!isMatch) { + continue; + } + + var score = 0; + var loweredTitle = normalizeText(candidate.title); + if (loweredTitle.indexOf(normalizedQuery) === 0) { + score += 400; + } else if (loweredTitle.indexOf(normalizedQuery) >= 0) { + score += 250; + } + + if (candidate.searchText.indexOf(normalizedQuery) >= 0) { + score += 100; + } + + for (var scoreTermIndex = 0; scoreTermIndex < terms.length; scoreTermIndex++) { + if (loweredTitle.indexOf(terms[scoreTermIndex]) === 0) { + score += 50; + } else if (loweredTitle.indexOf(terms[scoreTermIndex]) >= 0) { + score += 20; + } + } + + matches.push({ + entry: candidate, + score: score, + excerpt: buildExcerpt(candidate, normalizedQuery, terms), + terms: normalizedQuery.indexOf(" ") >= 0 ? [normalizedQuery].concat(terms) : terms + }); + } + + matches.sort(function (left, right) { + if (right.score !== left.score) { + return right.score - left.score; + } + + return left.entry.title.localeCompare(right.entry.title); + }); + + return matches; + } + + function renderResults(query) { + resultsContainer.innerHTML = ""; + currentResults = search(query); + activeIndex = currentResults.length > 0 ? 0 : -1; + + if (!query) { + summary.textContent = "Type to search all generated documentation files."; + hidePopup(); + return; + } + + showPopup(); + + if (currentResults.length === 0) { + summary.textContent = "No results found."; + return; + } + + summary.textContent = currentResults.length + (currentResults.length === 1 ? " result" : " results"); + + for (var resultIndex = 0; resultIndex < currentResults.length; resultIndex++) { + (function (indexInResults) { + var result = currentResults[indexInResults]; + var link = document.createElement("a"); + link.className = "doc-search-result"; + link.href = searchRoot + result.entry.path; + + link.appendChild(createTextElement("span", "doc-search-result-title", result.entry.title)); + link.appendChild(createTextElement("span", "doc-search-result-meta", result.entry.section + " | " + result.entry.path)); + if (result.excerpt.hasMatch) { + link.appendChild(createHighlightedElement("doc-search-result-excerpt", result.excerpt.text, result.terms)); + } else { + link.appendChild(createTextElement("span", "doc-search-result-excerpt", result.excerpt.text)); + } + + link.addEventListener("mouseenter", function () { + activeIndex = indexInResults; + updateActiveResult(); + }); + + resultsContainer.appendChild(link); + }(resultIndex)); + } + + updateActiveResult(); + } + + function openActiveResult() { + if (activeIndex < 0 || activeIndex >= currentResults.length) { + return; + } + + window.location.href = searchRoot + currentResults[activeIndex].entry.path; + } + + input.addEventListener("input", function () { + renderResults(input.value); + }); + + input.addEventListener("focus", function () { + if (input.value) { + renderResults(input.value); + } + }); + + input.addEventListener("keydown", function (event) { + if (event.key === "Escape") { + hidePopup(); + return; + } + + if (!currentResults.length) { + return; + } + + if (event.key === "ArrowDown") { + activeIndex = Math.min(currentResults.length - 1, activeIndex + 1); + updateActiveResult(); + event.preventDefault(); + return; + } + + if (event.key === "ArrowUp") { + activeIndex = Math.max(0, activeIndex - 1); + updateActiveResult(); + event.preventDefault(); + return; + } + + if (event.key === "Enter") { + openActiveResult(); + event.preventDefault(); + } + }); + + document.addEventListener("click", function (event) { + if (!document.getElementById("doc-search").contains(event.target)) { + hidePopup(); + } + }); + + if (!index.length) { + summary.textContent = "Search index unavailable."; + } + }); +}()); \ No newline at end of file diff --git a/Documentation/generate_search_index.ps1 b/Documentation/generate_search_index.ps1 new file mode 100644 index 0000000000..4b4b6c36b1 --- /dev/null +++ b/Documentation/generate_search_index.ps1 @@ -0,0 +1,238 @@ +param( + [string]$DocRoot = ".\doc", + [string]$OutputFile +) + +Add-Type -AssemblyName System.Web + +function Normalize-Whitespace { + param( + [string]$Text + ) + + if ($null -eq $Text) { + $Text = "" + } + + return ([regex]::Replace($Text, "\s+", " ")).Trim() +} + +function Get-RegexGroupValue { + param( + [string]$InputText, + [string]$Pattern + ) + + $match = [regex]::Match( + $InputText, + $Pattern, + [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Singleline) + + if (-not $match.Success) { + return "" + } + + $decodedValue = [System.Web.HttpUtility]::HtmlDecode($match.Groups[1].Value) + $withoutTags = [regex]::Replace($decodedValue, "<[^>]+>", " ") + return Normalize-Whitespace $withoutTags +} + +function Convert-HtmlToText { + param( + [string]$Html + ) + + $text = $Html + $text = [regex]::Replace($text, "(?is)", " ") + $text = [regex]::Replace($text, "(?is)", " ") + $text = [regex]::Replace($text, "(?is)", " ") + $text = [regex]::Replace($text, "(?i)", " ") + $text = [regex]::Replace($text, "(?i)", " ") + $text = [regex]::Replace($text, "<[^>]+>", " ") + $text = [System.Web.HttpUtility]::HtmlDecode($text) + return Normalize-Whitespace $text +} + +function Get-DocumentContentHtml { + param( + [string]$Html + ) + + $match = [regex]::Match( + $Html, + '
(.*?)
\s*', + [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Singleline) + + if ($match.Success) { + return $match.Groups[1].Value + } + + return $Html +} + +function Get-NearestSectionName { + param( + [System.Collections.ArrayList]$Sections, + [int]$Position + ) + + $closestSection = "" + + foreach ($section in $Sections) { + if ($section.Position -gt $Position) { + break + } + + $closestSection = $section.Name + } + + return $closestSection +} + +$resolvedDocRoot = (Resolve-Path $DocRoot).Path + +if (-not $OutputFile) { + $OutputFile = Join-Path $resolvedDocRoot "search-index.js" +} + +$regexOptions = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Singleline + +$entries = Get-ChildItem -Path $resolvedDocRoot -Filter *.html -Recurse | + Sort-Object FullName | + ForEach-Object { + $rawHtml = Get-Content -LiteralPath $_.FullName -Raw + $contentHtml = Get-DocumentContentHtml -Html $rawHtml + $relativePath = $_.FullName.Substring($resolvedDocRoot.Length).TrimStart('\') -replace '\\', '/' + $section = Split-Path $relativePath -Parent + + if ([string]::IsNullOrWhiteSpace($section)) { + $section = "Overview" + } + + $pageTitle = Get-RegexGroupValue -InputText $rawHtml -Pattern "]*>(.*?)" + $heading = Get-RegexGroupValue -InputText $contentHtml -Pattern "]*>(.*?)" + if ([string]::IsNullOrWhiteSpace($heading)) { + $heading = Get-RegexGroupValue -InputText $contentHtml -Pattern "]*>(.*?)" + } + + $documentText = Convert-HtmlToText -Html $contentHtml + + if ([string]::IsNullOrWhiteSpace($heading)) { + $heading = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) + } + + $pageEntries = New-Object System.Collections.ArrayList + $null = $pageEntries.Add([ordered]@{ + kind = "page" + path = $relativePath + section = $section + title = $heading + pageTitle = $pageTitle + text = $documentText + }) + + $sectionMatches = [regex]::Matches( + $contentHtml, + ']*class="section-header[^"]*"[^>]*>\s*(.*?)', + $regexOptions) + + $sections = New-Object System.Collections.ArrayList + foreach ($sectionMatch in $sectionMatches) { + $sectionName = Convert-HtmlToText -Html $sectionMatch.Groups[2].Value + if (-not [string]::IsNullOrWhiteSpace($sectionName)) { + $null = $sections.Add([ordered]@{ + Position = $sectionMatch.Index + Name = $sectionName + }) + } + } + + $summaryMatches = [regex]::Matches( + $contentHtml, + '

(.*?)

\s*(.*?)
', + $regexOptions) + + $summaryByKey = @{} + foreach ($summaryMatch in $summaryMatches) { + $summarySection = Convert-HtmlToText -Html $summaryMatch.Groups[2].Value + $rowMatches = [regex]::Matches( + $summaryMatch.Groups[3].Value, + '\s*]*>(.*?)\s*(.*?)\s*', + $regexOptions) + + foreach ($rowMatch in $rowMatches) { + $rowAnchor = $rowMatch.Groups[1].Value + $rowSummary = Convert-HtmlToText -Html $rowMatch.Groups[3].Value + $summaryByKey["$summarySection|$rowAnchor"] = $rowSummary + } + } + + $detailMatches = [regex]::Matches( + $contentHtml, + '
\s*\s*(.*?).*?
\s*
(.*?)
', + $regexOptions) + + $anchorCounts = @{} + foreach ($detailMatch in $detailMatches) { + $anchorName = $detailMatch.Groups[1].Value + if ($anchorCounts.ContainsKey($anchorName)) { + $anchorCounts[$anchorName]++ + } else { + $anchorCounts[$anchorName] = 1 + } + } + + foreach ($detailMatch in $detailMatches) { + $anchorName = $detailMatch.Groups[1].Value + if ($anchorCounts[$anchorName] -ne 1) { + continue + } + + $detailTitle = Convert-HtmlToText -Html $detailMatch.Groups[2].Value + $detailBody = Convert-HtmlToText -Html $detailMatch.Groups[3].Value + $detailSection = Get-NearestSectionName -Sections $sections -Position $detailMatch.Index + $detailSummary = "" + + if (-not [string]::IsNullOrWhiteSpace($detailSection)) { + $summaryKey = "$detailSection|$anchorName" + if ($summaryByKey.ContainsKey($summaryKey)) { + $detailSummary = $summaryByKey[$summaryKey] + } + } + + $detailText = Normalize-Whitespace "$detailSummary $detailBody" + if ([string]::IsNullOrWhiteSpace($detailText)) { + $detailText = $detailSummary + } + + if ([string]::IsNullOrWhiteSpace($detailTitle)) { + continue + } + + $detailSectionLabel = $section + if (-not [string]::IsNullOrWhiteSpace($heading)) { + $detailSectionLabel += " / $heading" + } + + if (-not [string]::IsNullOrWhiteSpace($detailSection)) { + $detailSectionLabel += " / $detailSection" + } + + $null = $pageEntries.Add([ordered]@{ + kind = "anchor" + path = "$relativePath#$anchorName" + section = $detailSectionLabel + title = $detailTitle + pageTitle = $pageTitle + text = $detailText + }) + } + + $pageEntries + } + +$json = $entries | ConvertTo-Json -Depth 4 -Compress +$output = "window.TENSearchIndex = $json;" +Set-Content -LiteralPath $OutputFile -Value $output -Encoding UTF8 + +Write-Host "Search index saved to: $OutputFile" \ No newline at end of file diff --git a/Documentation/ldoc.css b/Documentation/ldoc.css index b86524f8b8..c27489c11f 100644 --- a/Documentation/ldoc.css +++ b/Documentation/ldoc.css @@ -91,9 +91,9 @@ table.index { border: 1px #00007f; } table.index td { text-align: left; vertical-align: top; } #container { - margin-left: 1em; - margin-right: 1em; background-color: #f0f0f0; + box-sizing: border-box; + width: 100%; } #product { @@ -109,16 +109,148 @@ table.index td { text-align: left; vertical-align: top; } #main { background-color: #f0f0f0; border-left: 2px solid #cccccc; - display: flex + display: flex; + width: 100%; } #navigation { + box-sizing: border-box; + flex: 0 0 18em; width: 18em; vertical-align: top; background-color: #f0f0f0; overflow: visible; } +#navigation .project-title { + align-items: center; + display: flex; + gap: 0.5rem; + margin: 1rem 0 1rem 0.5rem; +} + +#navigation .project-title-link, +#navigation .project-title-link:link, +#navigation .project-title-link:visited { + align-items: center; + color: inherit; + display: inline-flex; + gap: 0.5rem; + text-decoration: none; +} + +#navigation .project-title-link:hover { + text-decoration: none; +} + +#navigation .project-title-logo { + display: block; + flex: 0 0 auto; + height: 2rem; + width: auto; +} + +#doc-search { + margin: 0 0.5rem 1rem 0.5rem; + position: relative; +} + +#doc-search-input { + border: 1px solid #b8b8b8; + border-radius: 4px; + box-sizing: border-box; + font-size: 0.95em; + padding: 0.45rem 0.6rem; + width: 100%; +} + +#doc-search-input:focus { + border-color: #c8751d; + box-shadow: 0 0 0 2px rgba(200, 117, 29, 0.15); + outline: none; +} + +#doc-search-popup { + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 6px; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.16); + font-family: inherit; + left: 0; + margin-top: 0.35rem; + max-width: calc(100vw - 3rem); + position: absolute; + top: 100%; + width: 36rem; + z-index: 40; +} + +#doc-search-popup.doc-search-popup-hidden { + display: none; +} + +#doc-search-summary { + border-bottom: 1px solid #ededed; + color: #606060; + font-size: 0.82em; + padding: 0.6rem 0.75rem; +} + +#doc-search-results { + max-height: 26rem; + overflow-y: auto; +} + +a.doc-search-result, +a.doc-search-result:link, +a.doc-search-result:visited { + border-bottom: 1px solid #f0f0f0; + color: #222222; + display: block; + font-weight: normal; + padding: 0.7rem 0.75rem; + text-decoration: none; +} + +a.doc-search-result:last-child { + border-bottom: 0; +} + +a.doc-search-result:hover, +a.doc-search-result.doc-search-result-active { + background-color: #fff5e9; + text-decoration: none; +} + +.doc-search-result-title { + color: #004080; + display: block; + font-size: 0.95em; + font-weight: bold; + margin-bottom: 0.15rem; +} + +.doc-search-result-meta { + color: #7a7a7a; + display: block; + font-size: 0.78em; + font-weight: normal; + margin-bottom: 0.25rem; +} + +.doc-search-result-excerpt { + color: #666666; + display: block; + font-size: 0.84em; + font-weight: normal; + line-height: 1.35; +} + +.doc-search-result-highlight { + color: #2b2b2b; + font-weight: bold; +} + #navigation h2 { background-color:#e7e7e7; font-size:1.1em; @@ -139,7 +271,7 @@ table.index td { text-align: left; vertical-align: top; } #navigation li { text-indent: -1em; display: block; - margin: 3px 0px 0px 22px; + margin: 3px 0px 0px 0px; } #navigation li li a { @@ -147,8 +279,10 @@ table.index td { text-align: left; vertical-align: top; } } #content { + box-sizing: border-box; + flex: 1 1 auto; + min-width: 0; padding: 2em; - width: 900px; border-left: 2px solid #cccccc; border-right: 2px solid #cccccc; background-color: #ffffff; @@ -173,12 +307,11 @@ table.index td { text-align: left; vertical-align: top; } } #container { - margin-left: 2%; - margin-right: 2%; background-color: #ffffff; } #content { + width: auto; padding: 1em; background-color: #ffffff; } diff --git a/Documentation/ldoc.ltp b/Documentation/ldoc.ltp index 5c94f877d5..c7c404ddf1 100644 --- a/Documentation/ldoc.ltp +++ b/Documentation/ldoc.ltp @@ -28,20 +28,23 @@ # local iter = ldoc.modules.iter # local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end # local nowrap = ldoc.wrap and '' or 'nowrap' +# local asset_root = module and '../' or '' +# local index_path = module and '../index.html' or 'index.html' +# local logo_path = module and '../TEN%20logo.png' or 'TEN%20logo.png' + + + diff --git a/TEN logo.png b/TEN logo.png index 97b7b13fa0..9c0d53c5a2 100644 Binary files a/TEN logo.png and b/TEN logo.png differ diff --git a/TombEngine/Game/Lara/Optics.cpp b/TombEngine/Game/Lara/Optics.cpp index 43e610ffa8..1fa61ff8fc 100644 --- a/TombEngine/Game/Lara/Optics.cpp +++ b/TombEngine/Game/Lara/Optics.cpp @@ -123,7 +123,7 @@ static void HandlePlayerOpticAnimations(ItemInfo& item) player.TargetEntity = nullptr; } - int objNumber = Objects[ID_LARA_BINOCULARS_MESH].loaded ? ID_LARA_BINOCULARS_MESH : ID_LARA_SKIN; + int objNumber = Objects[ID_LARA_BINOCULARS_MESH].loaded ? ID_LARA_BINOCULARS_MESH : player.Skin.Skin; item.Model.MeshIndex[LM_RHAND] = Objects[objNumber].meshIndex + LM_RHAND; player.Control.HandStatus = HandStatus::Free; diff --git a/TombEngine/Game/Lara/lara_initialise.cpp b/TombEngine/Game/Lara/lara_initialise.cpp index 10c01b78d6..b41ded4ff5 100644 --- a/TombEngine/Game/Lara/lara_initialise.cpp +++ b/TombEngine/Game/Lara/lara_initialise.cpp @@ -108,6 +108,12 @@ void InitializeLaraMeshes(ItemInfo* item) { auto& player = GetLaraInfo(*item); + //Set Skin Defaults. + Lara.Skin.Skin = ID_LARA_SKIN; + Lara.Skin.SkinJoints = ID_LARA_SKIN_JOINTS; + Lara.Skin.HairPrimary = ID_HAIR_PRIMARY; + Lara.Skin.HairSecondary = ID_HAIR_SECONDARY; + // Override base mesh and mesh indices to player skin if it exists. auto& obj = Objects[(Objects[ID_LARA_SKIN].loaded ? ID_LARA_SKIN : ID_LARA)]; diff --git a/TombEngine/Game/Lara/lara_struct.h b/TombEngine/Game/Lara/lara_struct.h index 5eb8819609..3eb1913578 100644 --- a/TombEngine/Game/Lara/lara_struct.h +++ b/TombEngine/Game/Lara/lara_struct.h @@ -1302,6 +1302,8 @@ struct PlayerEffectData std::array BubbleNodes = {}; }; + + struct PlayerInventoryData { bool IsBusy = false; @@ -1339,6 +1341,14 @@ struct PlayerInventoryData int ExaminesCombo[NUM_EXAMINES * 2] = {}; }; +struct PlayerSkinData +{ + GAME_OBJECT_ID Skin; + GAME_OBJECT_ID SkinJoints; + GAME_OBJECT_ID HairPrimary; + GAME_OBJECT_ID HairSecondary; +}; + struct LaraInfo { static constexpr auto TARGET_COUNT_MAX = 16; @@ -1348,6 +1358,7 @@ struct LaraInfo PlayerStatusData Status = {}; PlayerEffectData Effect = {}; PlayerInventoryData Inventory = {}; + PlayerSkinData Skin = {}; // TODO: Move to PlayerControlData. FlareData Flare = {}; diff --git a/TombEngine/Game/effects/hair.cpp b/TombEngine/Game/effects/hair.cpp index 3d2e40c2e3..e0b4fcc9e7 100644 --- a/TombEngine/Game/effects/hair.cpp +++ b/TombEngine/Game/effects/hair.cpp @@ -343,7 +343,7 @@ namespace TEN::Effects::Hair { auto& unit = Units[i]; - auto objectID = (i == 0) ? ID_HAIR_PRIMARY : ID_HAIR_SECONDARY; + auto objectID = (i == 0) ? Lara.Skin.HairPrimary : Lara.Skin.HairSecondary; const auto& object = Objects[objectID]; unit.IsEnabled = (object.loaded && (i == 0 || (i == 1 && isYoung))); diff --git a/TombEngine/Game/effects/tomb4fx.cpp b/TombEngine/Game/effects/tomb4fx.cpp index 3f16916677..77e917bd29 100644 --- a/TombEngine/Game/effects/tomb4fx.cpp +++ b/TombEngine/Game/effects/tomb4fx.cpp @@ -1233,8 +1233,8 @@ void ExplodingDeath(short itemNumber, short flags) ItemInfo* item = &g_Level.Items[itemNumber]; ObjectInfo* obj; - if (item->IsLara() && Objects[ID_LARA_SKIN].loaded) - obj = &Objects[ID_LARA_SKIN]; + if (item->IsLara() && Objects[Lara.Skin.Skin].loaded) + obj = &Objects[Lara.Skin.Skin]; else obj = &Objects[item->ObjectNumber]; diff --git a/TombEngine/Game/savegame.cpp b/TombEngine/Game/savegame.cpp index 250a199aab..e61bb81fd9 100644 --- a/TombEngine/Game/savegame.cpp +++ b/TombEngine/Game/savegame.cpp @@ -364,6 +364,13 @@ const std::vector SaveGame::Build() subsuitVelocity.push_back(Lara.Control.Subsuit.Velocity[1]); auto subsuitVelocityOffset = fbb.CreateVector(subsuitVelocity); + Save::PlayerSkinBuilder playerSkin{ fbb }; + playerSkin.add_skin((int)Lara.Skin.Skin); + playerSkin.add_skin_joints((int)Lara.Skin.SkinJoints); + playerSkin.add_hair_primary((int)Lara.Skin.HairPrimary); + playerSkin.add_hair_secondary((int)Lara.Skin.HairSecondary); + auto playerSkinOffset = playerSkin.Finish(); + Save::HolsterInfoBuilder holsterInfo{ fbb }; holsterInfo.add_back_holster((int)Lara.Control.Weapon.HolsterInfo.BackHolster); holsterInfo.add_left_holster((int)Lara.Control.Weapon.HolsterInfo.LeftHolster); @@ -604,6 +611,7 @@ const std::vector SaveGame::Build() lara.add_location(Lara.Location); lara.add_location_pad(Lara.LocationPad); lara.add_right_arm(rightArmOffset); + lara.add_skin(playerSkinOffset); lara.add_status(statusOffset); lara.add_collision(collisionOffset); lara.add_target_arm_orient(&FromEulerAngles(Lara.TargetArmOrient)); @@ -2300,6 +2308,10 @@ static void ParsePlayer(const Save::SaveGame* s) Lara.Status.Stamina = s->lara()->status()->stamina(); Lara.TargetEntity = (s->lara()->target_entity_number() >= 0) ? &g_Level.Items[s->lara()->target_entity_number()] : nullptr; Lara.TargetArmOrient = ToEulerAngles(s->lara()->target_arm_orient()); + Lara.Skin.Skin = static_cast(s->lara()->skin()->skin()); + Lara.Skin.SkinJoints = static_cast(s->lara()->skin()->skin_joints()); + Lara.Skin.HairPrimary = static_cast(s->lara()->skin()->hair_primary()); + Lara.Skin.HairSecondary = static_cast(s->lara()->skin()->hair_secondary()); for (int i = 0; i < s->lara()->weapons()->size(); i++) { diff --git a/TombEngine/Renderer/Renderer.h b/TombEngine/Renderer/Renderer.h index 030f91b5fb..53bd312cf4 100644 --- a/TombEngine/Renderer/Renderer.h +++ b/TombEngine/Renderer/Renderer.h @@ -495,6 +495,7 @@ namespace TEN::Renderer void PrepareSparkParticles(RenderView& view); void PrepareExplosionParticles(RenderView& view); void DrawLaraHolsters(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass); + void SwapLaraSkin(); void DrawLaraJoints(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass); void DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass); void DrawMesh(RendererItem* itemToDraw, RendererMesh* mesh, RendererObjectType type, int boneIndex, bool skinned, RenderView& view, RendererPass rendererPass); diff --git a/TombEngine/Renderer/RendererCompatibility.cpp b/TombEngine/Renderer/RendererCompatibility.cpp index 8402f6c54e..7758ae5ce7 100644 --- a/TombEngine/Renderer/RendererCompatibility.cpp +++ b/TombEngine/Renderer/RendererCompatibility.cpp @@ -8,6 +8,7 @@ #include "Game/control/control.h" #include "Game/effects/Decal.h" #include "Game/effects/Hair.h" +#include "Game/Lara/lara.h" #include "Game/Lara/lara_struct.h" #include "Game/savegame.h" #include "Game/Setup.h" @@ -616,7 +617,7 @@ namespace TEN::Renderer ); TENLog("Preparing object data...", LogLevel::Info); - + bool isSkinPresent = false; totalVertices = 0; @@ -664,8 +665,8 @@ namespace TEN::Renderer auto* mesh = GetRendererMeshFromTrMesh( &moveable, &g_Level.Meshes[obj->meshIndex + j], - j, MoveablesIds[i] == ID_LARA_SKIN_JOINTS, - MoveablesIds[i] == ID_HAIR_PRIMARY || MoveablesIds[i] == ID_HAIR_SECONDARY, &lastVertex, &lastIndex); + j, MoveablesIds[i] == Lara.Skin.SkinJoints, + MoveablesIds[i] == Lara.Skin.HairPrimary || MoveablesIds[i] == Lara.Skin.HairSecondary, &lastVertex, &lastIndex); moveable.ObjectMeshes.push_back(mesh); _meshes.push_back(mesh); @@ -774,12 +775,12 @@ namespace TEN::Renderer BuildHierarchy(&moveable); // Fix player skin joints and hair units. - if (MoveablesIds[i] == ID_LARA_SKIN_JOINTS) + if (MoveablesIds[i] == Lara.Skin.SkinJoints) { isSkinPresent = true; int bonesToCheck[2] = { 0, 0 }; - const auto& objSkin = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN); + const auto& objSkin = GetRendererObject(Lara.Skin.Skin); for (int j = 1; j < obj->nmeshes; j++) { @@ -853,11 +854,11 @@ namespace TEN::Renderer } } } - else if ((MoveablesIds[i] == ID_HAIR_PRIMARY || MoveablesIds[i] == ID_HAIR_SECONDARY) && isSkinPresent) + else if ((MoveablesIds[i] == Lara.Skin.HairPrimary || MoveablesIds[i] == Lara.Skin.HairSecondary) && isSkinPresent) { bool isYoung = (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young); - bool isSecond = isYoung && MoveablesIds[i] == ID_HAIR_SECONDARY; - const auto& skinObj = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN); + bool isSecond = isYoung && MoveablesIds[i] == Lara.Skin.HairSecondary; + const auto& skinObj = GetRendererObject(Lara.Skin.Skin); const auto& settings = g_GameFlow->GetSettings()->Hair; // Flatten skinned hairmesh vertices to be transformed correctly. diff --git a/TombEngine/Renderer/RendererHelper.cpp b/TombEngine/Renderer/RendererHelper.cpp index 4b118c4ad9..2e1a01640b 100644 --- a/TombEngine/Renderer/RendererHelper.cpp +++ b/TombEngine/Renderer/RendererHelper.cpp @@ -401,10 +401,10 @@ namespace TEN::Renderer RendererObject& Renderer::GetRendererObject(GAME_OBJECT_ID id) { - if (id == GAME_OBJECT_ID::ID_LARA || id == GAME_OBJECT_ID::ID_LARA_SKIN) + if (id == GAME_OBJECT_ID::ID_LARA || id == Lara.Skin.Skin) { - if (_moveableObjects[GAME_OBJECT_ID::ID_LARA_SKIN].has_value()) - return _moveableObjects[GAME_OBJECT_ID::ID_LARA_SKIN].value(); + if (_moveableObjects[Lara.Skin.Skin].has_value()) + return _moveableObjects[Lara.Skin.Skin].value(); else return _moveableObjects[GAME_OBJECT_ID::ID_LARA].value(); } diff --git a/TombEngine/Renderer/RendererLara.cpp b/TombEngine/Renderer/RendererLara.cpp index ea9b23ad06..76bb93653e 100644 --- a/TombEngine/Renderer/RendererLara.cpp +++ b/TombEngine/Renderer/RendererLara.cpp @@ -16,6 +16,7 @@ #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Scripting/Include/ScriptInterfaceLevel.h" #include "Specific/level.h" +#include using namespace TEN::Animation; using namespace TEN::Effects::Hair; @@ -441,10 +442,10 @@ void Renderer::DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, Render void Renderer::DrawLaraJoints(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass) { - if (!_moveableObjects[ID_LARA_SKIN_JOINTS].has_value()) + if (!_moveableObjects[Lara.Skin.SkinJoints].has_value()) return; - RendererObject& laraSkinJoints = *_moveableObjects[ID_LARA_SKIN_JOINTS]; + RendererObject& laraSkinJoints = *_moveableObjects[Lara.Skin.SkinJoints]; for (int k = 1; k < laraSkinJoints.ObjectMeshes.size(); k++) { @@ -479,4 +480,4 @@ void Renderer::DrawLaraHolsters(RendererItem* itemToDraw, RendererRoom* room, Re RendererMesh* mesh = holsterSkin.ObjectMeshes[LM_TORSO]; DrawMesh(itemToDraw, mesh, RendererObjectType::Moveable, LM_TORSO, false, view, rendererPass); } -} \ No newline at end of file +} diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp index d7aa003b27..8cd9f8717e 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp @@ -10,6 +10,7 @@ #include "Game/Lara/lara_fire.h" #include "Game/Lara/lara_helpers.h" #include "Game/Lara/lara_struct.h" +#include "Game/Setup.h" #include "Game/Lara/lara_one_gun.h" #include "Game/Lara/lara_two_guns.h" #include "Objects/Generic/Object/burning_torch.h" @@ -844,6 +845,23 @@ bool LaraObject::TestInteraction(const Moveable& mov, return (TestLaraPosition(interactionBasis, &item, _moveable)); } +/// Set Lara weapon type +// @function LaraObject:SetWeaponType +// @usage +// Lara:SetWeaponType(WeaponType.PISTOLS, false) +// @tparam Flow.WeaponType weaponType +// @tparam bool activate if `true`, also draw the weapons or set torch lit. If `false`, keep weapons holstered or leave torch unlit. +void LaraObject::SetSkin(GAME_OBJECT_ID skin, GAME_OBJECT_ID skinJoints, GAME_OBJECT_ID hair1, GAME_OBJECT_ID hair2) +{ + auto* lara = GetLaraInfo(_moveable); + + lara->Skin.Skin = skin; + lara->Skin.SkinJoints = skinJoints; + lara->Skin.HairPrimary = hair1; + lara->Skin.HairSecondary = hair2; + +} + void LaraObject::Register(sol::table& parent) { parent.new_usertype( @@ -881,6 +899,7 @@ void LaraObject::Register(sol::table& parent) ScriptReserved_SetWaterSkinStatus, & LaraObject::SetWaterSkinStatus, ScriptReserved_PlayerInteract, &LaraObject::Interact, ScriptReserved_PlayerTestInteraction, &LaraObject::TestInteraction, + "SetSkin", &LaraObject::SetSkin, // COMPATIBILITY "TorchIsLit", &LaraObject::IsTorchLit, diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.h b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.h index d6c0ea0b73..4e7bc4c409 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.h @@ -54,4 +54,5 @@ class LaraObject : public Moveable const TypeOrNil& rotConstraintMin, const TypeOrNil& rotConstraintMax) const; using Moveable::Moveable; + void SetSkin(GAME_OBJECT_ID skin, GAME_OBJECT_ID skinJoints, GAME_OBJECT_ID hair1, GAME_OBJECT_ID hair2); }; diff --git a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h index 829b79d207..99f34339e8 100644 --- a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h +++ b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h @@ -45,6 +45,10 @@ struct WeaponInfo; struct WeaponInfoBuilder; struct WeaponInfoT; +struct PlayerSkin; +struct PlayerSkinBuilder; +struct PlayerSkinT; + struct ArmInfo; struct ArmInfoBuilder; struct ArmInfoT; @@ -2484,6 +2488,97 @@ struct WeaponInfo::Traits { flatbuffers::Offset CreateWeaponInfo(flatbuffers::FlatBufferBuilder &_fbb, const WeaponInfoT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct PlayerSkinT : public flatbuffers::NativeTable { + typedef PlayerSkin TableType; + uint32_t skin = 0; + uint32_t skin_joints = 0; + uint32_t hair_primary = 0; + uint32_t hair_secondary = 0; +}; + +struct PlayerSkin FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef PlayerSkinT NativeTableType; + typedef PlayerSkinBuilder Builder; + struct Traits; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_SKIN = 4, + VT_SKIN_JOINTS = 6, + VT_HAIR_PRIMARY = 8, + VT_HAIR_SECONDARY = 10 + }; + uint32_t skin() const { + return GetField(VT_SKIN, 0); + } + uint32_t skin_joints() const { + return GetField(VT_SKIN_JOINTS, 0); + } + uint32_t hair_primary() const { + return GetField(VT_HAIR_PRIMARY, 0); + } + uint32_t hair_secondary() const { + return GetField(VT_HAIR_SECONDARY, 0); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_SKIN) && + VerifyField(verifier, VT_SKIN_JOINTS) && + VerifyField(verifier, VT_HAIR_PRIMARY) && + VerifyField(verifier, VT_HAIR_SECONDARY) && + verifier.EndTable(); + } + PlayerSkinT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(PlayerSkinT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const PlayerSkinT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct PlayerSkinBuilder { + typedef PlayerSkin Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_skin(uint32_t skin) { + fbb_.AddElement(PlayerSkin::VT_SKIN, skin, 0); + } + void add_skin_joints(uint32_t skin_joints) { + fbb_.AddElement(PlayerSkin::VT_SKIN_JOINTS, skin_joints, 0); + } + void add_hair_primary(uint32_t hair_primary) { + fbb_.AddElement(PlayerSkin::VT_HAIR_PRIMARY, hair_primary, 0); + } + void add_hair_secondary(uint32_t hair_secondary) { + fbb_.AddElement(PlayerSkin::VT_HAIR_SECONDARY, hair_secondary, 0); + } + explicit PlayerSkinBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreatePlayerSkin( + flatbuffers::FlatBufferBuilder &_fbb, + uint32_t skin = 0, + uint32_t skin_joints = 0, + uint32_t hair_primary = 0, + uint32_t hair_secondary = 0) { + PlayerSkinBuilder builder_(_fbb); + builder_.add_hair_secondary(hair_secondary); + builder_.add_hair_primary(hair_primary); + builder_.add_skin_joints(skin_joints); + builder_.add_skin(skin); + return builder_.Finish(); +} + +struct PlayerSkin::Traits { + using type = PlayerSkin; + static auto constexpr Create = CreatePlayerSkin; +}; + +flatbuffers::Offset CreatePlayerSkin(flatbuffers::FlatBufferBuilder &_fbb, const PlayerSkinT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct ArmInfoT : public flatbuffers::NativeTable { typedef ArmInfo TableType; int32_t anim_number = 0; @@ -4724,6 +4819,7 @@ struct LaraT : public flatbuffers::NativeTable { int32_t location_pad = 0; std::unique_ptr right_arm{}; std::unique_ptr status{}; + std::unique_ptr skin{}; std::unique_ptr target_arm_orient{}; int32_t target_entity_number = 0; std::unique_ptr torch{}; @@ -4752,10 +4848,11 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { VT_LOCATION_PAD = 32, VT_RIGHT_ARM = 34, VT_STATUS = 36, - VT_TARGET_ARM_ORIENT = 38, - VT_TARGET_ENTITY_NUMBER = 40, - VT_TORCH = 42, - VT_WEAPONS = 44 + VT_SKIN = 38, + VT_TARGET_ARM_ORIENT = 40, + VT_TARGET_ENTITY_NUMBER = 42, + VT_TORCH = 44, + VT_WEAPONS = 46 }; const TEN::Save::PlayerContextData *context() const { return GetPointer(VT_CONTEXT); @@ -4808,6 +4905,9 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const TEN::Save::PlayerStatusData *status() const { return GetPointer(VT_STATUS); } + const TEN::Save::PlayerSkin *skin() const { + return GetPointer(VT_SKIN); + } const TEN::Save::EulerAngles *target_arm_orient() const { return GetStruct(VT_TARGET_ARM_ORIENT); } @@ -4848,6 +4948,8 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { verifier.VerifyTable(right_arm()) && VerifyOffset(verifier, VT_STATUS) && verifier.VerifyTable(status()) && + VerifyOffset(verifier, VT_SKIN) && + verifier.VerifyTable(skin()) && VerifyField(verifier, VT_TARGET_ARM_ORIENT) && VerifyField(verifier, VT_TARGET_ENTITY_NUMBER) && VerifyOffset(verifier, VT_TORCH) && @@ -4917,6 +5019,9 @@ struct LaraBuilder { void add_status(flatbuffers::Offset status) { fbb_.AddOffset(Lara::VT_STATUS, status); } + void add_skin(flatbuffers::Offset skin) { + fbb_.AddOffset(Lara::VT_SKIN, skin); + } void add_target_arm_orient(const TEN::Save::EulerAngles *target_arm_orient) { fbb_.AddStruct(Lara::VT_TARGET_ARM_ORIENT, target_arm_orient); } @@ -4959,6 +5064,7 @@ inline flatbuffers::Offset CreateLara( int32_t location_pad = 0, flatbuffers::Offset right_arm = 0, flatbuffers::Offset status = 0, + flatbuffers::Offset skin = 0, const TEN::Save::EulerAngles *target_arm_orient = 0, int32_t target_entity_number = 0, flatbuffers::Offset torch = 0, @@ -4968,6 +5074,7 @@ inline flatbuffers::Offset CreateLara( builder_.add_torch(torch); builder_.add_target_entity_number(target_entity_number); builder_.add_target_arm_orient(target_arm_orient); + builder_.add_skin(skin); builder_.add_status(status); builder_.add_right_arm(right_arm); builder_.add_location_pad(location_pad); @@ -5012,6 +5119,7 @@ inline flatbuffers::Offset CreateLaraDirect( int32_t location_pad = 0, flatbuffers::Offset right_arm = 0, flatbuffers::Offset status = 0, + flatbuffers::Offset skin = 0, const TEN::Save::EulerAngles *target_arm_orient = 0, int32_t target_entity_number = 0, flatbuffers::Offset torch = 0, @@ -5036,6 +5144,7 @@ inline flatbuffers::Offset CreateLaraDirect( location_pad, right_arm, status, + skin, target_arm_orient, target_entity_number, torch, @@ -10158,6 +10267,41 @@ inline flatbuffers::Offset CreateWeaponInfo(flatbuffers::FlatBufferB _target_state); } +inline PlayerSkinT *PlayerSkin::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void PlayerSkin::UnPackTo(PlayerSkinT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = skin(); _o->skin = _e; } + { auto _e = skin_joints(); _o->skin_joints = _e; } + { auto _e = hair_primary(); _o->hair_primary = _e; } + { auto _e = hair_secondary(); _o->hair_secondary = _e; } +} + +inline flatbuffers::Offset PlayerSkin::Pack(flatbuffers::FlatBufferBuilder &_fbb, const PlayerSkinT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreatePlayerSkin(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreatePlayerSkin(flatbuffers::FlatBufferBuilder &_fbb, const PlayerSkinT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const PlayerSkinT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _skin = _o->skin; + auto _skin_joints = _o->skin_joints; + auto _hair_primary = _o->hair_primary; + auto _hair_secondary = _o->hair_secondary; + return TEN::Save::CreatePlayerSkin( + _fbb, + _skin, + _skin_joints, + _hair_primary, + _hair_secondary); +} + inline ArmInfoT *ArmInfo::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = std::make_unique(); UnPackTo(_o.get(), _resolver); @@ -10910,6 +11054,7 @@ inline void Lara::UnPackTo(LaraT *_o, const flatbuffers::resolver_function_t *_r { auto _e = location_pad(); _o->location_pad = _e; } { auto _e = right_arm(); if (_e) _o->right_arm = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = status(); if (_e) _o->status = std::unique_ptr(_e->UnPack(_resolver)); } + { auto _e = skin(); if (_e) _o->skin = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = target_arm_orient(); if (_e) _o->target_arm_orient = std::unique_ptr(new TEN::Save::EulerAngles(*_e)); } { auto _e = target_entity_number(); _o->target_entity_number = _e; } { auto _e = torch(); if (_e) _o->torch = std::unique_ptr(_e->UnPack(_resolver)); } @@ -10941,6 +11086,7 @@ inline flatbuffers::Offset CreateLara(flatbuffers::FlatBufferBuilder &_fbb auto _location_pad = _o->location_pad; auto _right_arm = _o->right_arm ? CreateArmInfo(_fbb, _o->right_arm.get(), _rehasher) : 0; auto _status = _o->status ? CreatePlayerStatusData(_fbb, _o->status.get(), _rehasher) : 0; + auto _skin = _o->skin ? CreatePlayerSkin(_fbb, _o->skin.get(), _rehasher) : 0; auto _target_arm_orient = _o->target_arm_orient ? _o->target_arm_orient.get() : 0; auto _target_entity_number = _o->target_entity_number; auto _torch = _o->torch ? CreateTorchData(_fbb, _o->torch.get(), _rehasher) : 0; @@ -10964,6 +11110,7 @@ inline flatbuffers::Offset CreateLara(flatbuffers::FlatBufferBuilder &_fbb _location_pad, _right_arm, _status, + _skin, _target_arm_orient, _target_entity_number, _torch, diff --git a/TombEngine/Specific/savegame/schema/ten_savegame.fbs b/TombEngine/Specific/savegame/schema/ten_savegame.fbs index e3180662b8..223c2910f3 100644 --- a/TombEngine/Specific/savegame/schema/ten_savegame.fbs +++ b/TombEngine/Specific/savegame/schema/ten_savegame.fbs @@ -155,6 +155,13 @@ table WeaponInfo { target_state: uint32; } +table PlayerSkin { + skin: uint32; + skin_joints: uint32; + hair_primary: uint32; + hair_secondary: uint32; +} + table ArmInfo { anim_number: int32; frame_number: int32; @@ -352,6 +359,7 @@ table Lara { location_pad: int32; right_arm: ArmInfo; status: PlayerStatusData; + skin: PlayerSkin; target_arm_orient: EulerAngles; target_entity_number: int32; torch: TorchData; diff --git a/TombEngine/version.h b/TombEngine/version.h index cd47a60fa1..ab21ba6a4f 100644 --- a/TombEngine/version.h +++ b/TombEngine/version.h @@ -1,9 +1,9 @@ #pragma once -#define TEN_MAJOR_VERSION 1 -#define TEN_MINOR_VERSION 11 -#define TEN_BUILD_NUMBER 1 -#define TEN_REVISION_NUMBER 1 +#define TEN_MAJOR_VERSION 2 +#define TEN_MINOR_VERSION 0 +#define TEN_BUILD_NUMBER 0 +#define TEN_REVISION_NUMBER 0 #define TEST_BUILD 0