diff --git a/.agents/skills/code_style b/.agents/skills/code_style new file mode 120000 index 000000000..edb444fb7 --- /dev/null +++ b/.agents/skills/code_style @@ -0,0 +1 @@ +../../.github/skills/code_style \ No newline at end of file diff --git a/.agents/skills/execute b/.agents/skills/execute new file mode 120000 index 000000000..ff61f8ce2 --- /dev/null +++ b/.agents/skills/execute @@ -0,0 +1 @@ +../../.github/skills/execute \ No newline at end of file diff --git a/.agents/skills/ghidra b/.agents/skills/ghidra new file mode 120000 index 000000000..e348149ed --- /dev/null +++ b/.agents/skills/ghidra @@ -0,0 +1 @@ +../../.github/skills/ghidra \ No newline at end of file diff --git a/.agents/skills/implement b/.agents/skills/implement new file mode 120000 index 000000000..609ac0075 --- /dev/null +++ b/.agents/skills/implement @@ -0,0 +1 @@ +../../.github/skills/implement \ No newline at end of file diff --git a/.agents/skills/line_lookup b/.agents/skills/line_lookup new file mode 120000 index 000000000..98bcd185e --- /dev/null +++ b/.agents/skills/line_lookup @@ -0,0 +1 @@ +../../.github/skills/line_lookup \ No newline at end of file diff --git a/.agents/skills/lookup b/.agents/skills/lookup new file mode 120000 index 000000000..e0466a56a --- /dev/null +++ b/.agents/skills/lookup @@ -0,0 +1 @@ +../../.github/skills/lookup \ No newline at end of file diff --git a/.agents/skills/refiner b/.agents/skills/refiner new file mode 120000 index 000000000..d61e56ea1 --- /dev/null +++ b/.agents/skills/refiner @@ -0,0 +1 @@ +../../.github/skills/refiner \ No newline at end of file diff --git a/.agents/skills/scaffold b/.agents/skills/scaffold new file mode 120000 index 000000000..5684eddc2 --- /dev/null +++ b/.agents/skills/scaffold @@ -0,0 +1 @@ +../../.github/skills/scaffold \ No newline at end of file diff --git a/.claude b/.claude new file mode 120000 index 000000000..c0ca46856 --- /dev/null +++ b/.claude @@ -0,0 +1 @@ +.agents \ No newline at end of file diff --git a/.github/skills/code_style/SKILL.md b/.github/skills/code_style/SKILL.md index 5218fb40b..28db08c3c 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -19,6 +19,15 @@ In this repo, style cleanup must preserve decomp progress. - If a style tweak changes codegen or match status, revert it. - Extend this skill only from patterns you actually verified in the repo. +### Authenticity Over Hacks + +A 100% match is the goal, but **how** we get there matters just as much. + +- Do not use "any means necessary" to force a match if it results in unreadable or unnatural code. +- Always think about what the original code probably looked like and write it that way. +- Even if a function matches 100% binary-wise, it is not "correct" if the source is unreadable or contains logic that no human developer would have written. +- If you find a stubborn mismatch, look for a more natural C++ expression or a different architectural pattern instead of resorting to opaque hacks. + ## Quick Tooling Use the repo-local helper before doing a style pass: @@ -31,7 +40,7 @@ python tools/code_style.py audit --base origin/main - `audit` also checks touched `class` / `struct` declarations against known header declarations and, when no header exists, against the PS2 visibility rule. - `audit` warns on touched local forward declarations when the repo already has a header for that type. - `audit` warns on touched type members that look like invented padding or placeholder names such as `pad`, `unk`, or `field_1234`. -- `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, and missing `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's guard region is touched. +- `audit` also checks touched style-guide rules that clang-format cannot enforce for you, such as cast spacing, `using namespace`, `NULL`, bare `#if MACRO` presence checks, recovered layout members that still use raw `unsigned char` / `unsigned short`, and missing or misordered `EA_PRAGMA_ONCE_SUPPORTED` guard blocks when a header's prologue is touched. - `audit` groups repeated findings by file so branch-wide output stays readable. - Use `audit --category safe-cpp` when you want a smaller Frontend/FEng-focused subset and `audit --category match-sensitive-cpp` when you want a conservative review queue for decomp code. - `format --check` is an opt-in wrapper around the repo's `.clang-format`, and by default it targets eligible changed C/C++ files, including match-sensitive code. @@ -95,7 +104,14 @@ Foo::Foo() - Use `nullptr` exclusively for null pointers. - Prefer `if (ptr)` / `if (!ptr)` over explicit null comparisons when the change is local and verified safe. - When a match-sensitive TU has many explicit `nullptr` checks and you decide to normalize them, prefer one mechanical full-TU pass over piecemeal cleanup. Rebuild the unit and re-check its status before keeping the rewrite. +- When a helper is doing address arithmetic, prefer `intptr_t` / `uintptr_t` or byte-pointer (`reinterpret_cast`) math over plain `int` parameters or integerized pointer subtraction. - Inline assembly is acceptable when it is needed to preserve dead-code compares, ordering, or other compiler behavior that source alone cannot reproduce. +- In low-level list / node / allocator code, prefer existing helper methods such as `AddBefore`, `AddAfter`, `Remove`, `GetPrev`, `GetNext`, or typed accessors over open-coding link rewiring once the helper exists. + +### Header prologues and preprocessor checks + +- In headers, keep the guard / `EA_PRAGMA_ONCE_SUPPORTED` block before any project `#include`; do not place includes ahead of `#pragma once`. +- Use `#ifdef MACRO` / `#ifndef MACRO` for presence checks. Reserve bare `#if MACRO` for cases where you really need the macro's numeric value. ### Forward declarations and local prototypes @@ -103,6 +119,7 @@ Foo::Foo() - If the repo already has a header declaration/definition for a type, include that header instead of redeclaring the type locally. - If the repo only has an empty or stub owner header, and line info / surrounding source clearly points at that header's subsystem, prefer populating that owner header over leaving a recovered project type declaration inside a `.cpp`. - Only keep a local forward declaration when no canonical repo header exists yet and you have verified that the ownership is still unresolved. +- Likewise for project free functions: if a declaration is shared across translation units, move it into the owning header instead of leaving ad-hoc local prototypes in `.cpp` files. - Prefer moving helper template declarations next to their real use site instead of leaving them in an unrelated file. ### Pointer style @@ -117,10 +134,13 @@ Foo::Foo() - Preserve the original `class` / `struct` kind from existing headers or Dwarf / PS2 evidence; do not treat it as a cosmetic style choice. - Treat header declarations as the repo source of truth. If the repo only has local `.cpp` partial declarations, verify the kind with the PS2 dump instead of copying them blindly. - Even forward declarations and local partial declarations should use the accurate keyword when known. +- Keep the `// total size: 0x...` comment above the recovered type declaration instead of burying it inside the body. +- When a recovered type is a `class`, keep explicit access sections and put the method/accessor block before the member layout block unless existing repo evidence shows otherwise. - Preserve the member naming style that DWARF shows. Some types use `mMember`, others use `m_member`; do not normalize them. - Preserve recovered member names, types, order, and offset comments. Do not invent placeholder members named `pad`, `unk`, `unknown`, or `field_XXXX` for game code just to make a layout compile. - If a member is genuinely unknown, stop and verify it with `find-symbol.py`, GC Dwarf, and PS2 data. If the layout is still incomplete, add a short TODO above the type instead of burying uncertainty in fake member names. - Add offset / size comments when you are writing recovered type layouts from DWARF. +- In recovered layouts, prefer explicit-width aliases such as `uint8` / `uint16` when the field width is known. Use plain `char` for text / byte buffers and `signed char` when the field is a signed 8-bit counter. - Define inline member functions in headers only when DWARF shows that they are genuinely inlined in the binary. - Use `struct` for POD-like data carriers with public fields; use `class` for behavior-heavy types only when that matches the recovered type information. - Keep tiny placeholder methods as concise inline bodies when that is already the local pattern. @@ -134,13 +154,27 @@ Foo::Foo() ### Dense local code - Expand dense one-line helper structs, declaration blocks, and function bodies in non-match-sensitive files into normal multiline formatting. +- In low-level headers, prefer normal multi-line bodies for touched inline operators and accessors instead of stacking `{ return ...; }` on one line, unless the surrounding file clearly uses intentional placeholder one-liners. - Prefer readable blocks over stacked one-line statements when behavior does not depend on exact source shape. +- In touched validation/parsing code, prefer explicit min/max or boundary checks over equivalent magic-constant arithmetic when the clearer form still compiles to the verified result. +- In parser/state-table code, prefer named enums and enum-typed state variables over anonymous integer state codes when that rewrite is verified safe. + +### Recovery markers + +- Remove stale recovery markers such as `// TODO`, `// UNSOLVED`, or `// STRIPPED` when the touched code is now implemented or understood. +- If a marker still needs to stay, give it short context such as ownership uncertainty, a Dwarf caveat, a platform/config note, or a scratch/link reference. Avoid bare marker-only comments. +- Do not leave `// TODO` hanging off a declaration or helper you just implemented; either finish the thought or remove the marker. ### Uncertain ownership - If a declaration or global clearly compiles but its original home is uncertain, add a short TODO comment instead of inventing structure you cannot justify yet. - When ownership matters, verify it with `decomp-workflow.py`, `decomp-context.py`, and `line-lookup` before moving code. +### Readable helper extraction + +- When touched recovered code repeats the same pointer/boundary arithmetic, prefer a short named helper or accessor such as `GetTop`, `GetBot`, `GetNext`, `GetPrev`, `GetStringTableStart`, or `GetStringTableEnd` if that shape is already supported by Dwarf/inlining evidence. +- Prefer call sites that use those helpers or existing container APIs over re-encoding the same arithmetic or link manipulation inline. + ## Phase 3: Things Not To "Clean Up" Blindly - Do not move an inline method out of a header just because it looks cleaner. @@ -172,3 +206,9 @@ Keep the cleanup only if the build succeeds and the relevant match status is unc - The trailing `//` initializer-list markers are an intentional repo convention, not noise to remove. - Small `if (ptr)` cleanup batches can be kept in match-sensitive code, but only after rebuilding the affected unit. - Dense frontend shim files benefit from multiline struct/prototype/function formatting. +- Header prologues should keep the `EA_PRAGMA_ONCE_SUPPORTED` block ahead of includes, not after them. +- Bare `#if MACRO` presence checks are review bait; use `#ifdef` / `#ifndef` unless you are intentionally testing a numeric config value. +- Reviewed recovered headers tend to keep total-size comments above the type, methods before fields, explicit access sections, and fixed-width aliases for width-known narrow integer members. +- Reviewed fixups also remove stale bare recovery markers or replace them with context, and prefer existing list/node helpers over hand-written pointer/link rewiring. +- Some reviewed fixups improved readability without losing match by replacing opaque range-check arithmetic with explicit bounds and by moving repeated pointer/boundary math behind short named helpers. +- Other recurring review churn came from plain-`int` address helpers, stray local `.cpp` prototypes for shared functions, and integer-coded parser states where named enums were clearer but still matched. diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index 7abf9cb3f..5ba0bd156 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -11,6 +11,9 @@ the produced C++ compiles to byte-identical object code against the original ret For each function, "done" means both objdiff and normalized DWARF are exact. +Human review is not a substitute for running `dwarf compare`. Each function should hit +its own `verify` gate before you treat it as ready to hand off, commit, or move past. + ## Overview This workflow combines several smaller workflows: @@ -152,6 +155,10 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName If it fails, follow up with `decomp-workflow.py diff` and `decomp-workflow.py dwarf` until both checks pass. +Do not queue up several "probably done" functions and leave the DWARF check for later. +Close the `verify` gate per function before moving on whenever feasible; otherwise the +reviewer ends up doing avoidable DWARF triage. + ### 3g. Periodic reassessment After every few functions, re-run the full status check: @@ -189,6 +196,8 @@ For any remaining nonmatching functions, make one final pass using the implement or refiner workflow with all context accumulated during the session. Do not report a function as complete unless its per-function `verify` check also passes. +Do not hand a function to review as "done except maybe DWARF" — either resolve the DWARF +failure yourself or explicitly call out the blocker and why it remains. ## Phase 5: Report diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index 25f56b926..cbd94da78 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -9,6 +9,11 @@ Your goal is to decompile a specific function: writing C++ source that compiles A function is not done until it is exact in both objdiff and normalized DWARF. +Reviewers should not be spending their time rediscovering DWARF mismatches. Before you +report progress, ask for review, hand the function off, or switch to another target, you +must run the per-function verification gate yourself and treat any DWARF failure as your +next task, not as review debt. + ## Phase 1: Gather Context Collect data from **all** of these sources in parallel where possible. @@ -156,6 +161,16 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName If the build fails, fix compilation errors first. +As soon as you have a compiling draft, run the combined verification gate immediately: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +Do this before you spend a long time polishing late instruction mismatches. If `verify` +already shows a DWARF failure, fix that first so you are not polishing code the reviewer +will bounce anyway. + ### Check the diff ```sh @@ -203,6 +218,17 @@ debug-line owner files for each DWARF `// Range:` block, which makes it much eas spot inlines that are coming from the wrong header or owner file. Exact line-number agreement is a useful secondary hint, but file ownership is the first thing to check. +Use this as the default loop when the function compiles but `verify` is still failing: + +1. Run `verify`. +2. If DWARF fails, run `dwarf`. +3. Fix the structural issue the DWARF diff is pointing at first: missing/extra locals, + wrong qualifiers or parameter types, wrong inline ownership, wrong helper/header owner, + or a source shape that outlined something that should be inlined. +4. Rebuild and rerun `verify`. +5. Only return to instruction-by-instruction cleanup once the remaining failures are no + longer obvious DWARF-compare issues. + Manual fallback: After writing your code, you can also run the dwarf dump on the compiled output and then query your output dump with lookup.py to compare your decompiled functions against the originals. Since the address of the function you're working on can keep changing @@ -233,6 +259,9 @@ Every mismatched instruction is a signal — don't settle for "close enough". Reaching 100% instruction matching status is not enough. Stay in the loop until `verify` passes, which means the DWARF of the function also matches after normalization. +Do not leave a function in a "review-ready" or "good enough for now" state with a known +DWARF failure unless you are explicitly blocked and you document that blocker clearly. + ## Phase 5: Report Summarize: diff --git a/.github/skills/refiner/SKILL.md b/.github/skills/refiner/SKILL.md index a6aeb2125..0054e6e18 100644 --- a/.github/skills/refiner/SKILL.md +++ b/.github/skills/refiner/SKILL.md @@ -15,9 +15,25 @@ approaches that were tried before — instead, apply systematic lateral analysis - A diff is available (`decomp-diff.py -u -d `). - The "obvious" translation from Ghidra has been attempted. - You have been given the current source code and the diff. +- You have already run the per-function `verify` gate and know whether the remaining work + is still structural DWARF cleanup or true late-stage instruction cleanup. + +Refiner is not the place to dump unresolved DWARF debt on a reviewer. If `verify` or +`dwarf` is still showing obvious structural mismatches (missing locals, wrong types, +wrong inline ownership, wrong helper/header owner), fix those first or drop back to the +implementer workflow before doing late instruction polish. ## Phase 1: Read the full diff without collapsing +Before you start a refiner pass, confirm the gate status: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +If the combined gate is failing for reasons that are still clearly visible in the DWARF +diff, address those first instead of treating them as reviewer follow-up. + Preferred shortcut: ```sh @@ -151,6 +167,9 @@ DWARF mismatches to watch for: - Wrong return type - Missing inlined function records (means an inline call was outlined) +If these mismatches are still present, you are not in pure refiner territory yet. Resolve +them before you ask a reviewer to spend time on the function. + ## Phase 4: Report Summarize: diff --git a/.github/skills/scaffold/SKILL.md b/.github/skills/scaffold/SKILL.md index a2f062f50..3fe3d9256 100644 --- a/.github/skills/scaffold/SKILL.md +++ b/.github/skills/scaffold/SKILL.md @@ -39,6 +39,9 @@ Preserve the real `class` / `struct` kind while scaffolding. Check existing head then use Dwarf plus PS2 visibility / vtable info to decide the type kind. Even temporary forward declarations should match the known original kind. +Keep the header prologue in repo order: header guard, `EA_PRAGMA_ONCE_SUPPORTED` block, +then includes. Do not drop project includes ahead of `#pragma once`. + If the repo already has a header for a type you need, include that header instead of adding a new local forward declaration. Only forward-declare when no canonical repo header exists yet and you have verified that the ownership is still unresolved. @@ -47,11 +50,26 @@ Preserve real member names, types, order, and offset comments while scaffolding. fill gaps with invented `pad`, `unk`, or `field_XXXX` members for game types; verify the layout from Dwarf / PS2 data and leave a TODO over the type if a field is still uncertain. +Keep the `// total size: 0x...` comment above the recovered type declaration. When the +recovered type is a `class`, keep explicit access sections and prefer putting methods / +accessors before the member layout block unless existing repo evidence says otherwise. + +When a recovered field width is known, prefer explicit-width aliases such as `uint8` / +`uint16` over raw `unsigned char` / `unsigned short`. Use plain `char` for string or byte +buffers and `signed char` when the field is a signed 8-bit counter. + +If a recovered type repeatedly walks neighbors, boundaries, or in-object offsets, prefer +small named helpers such as `GetTop`, `GetBot`, `GetNext`, `GetPrev`, or boundary getters +instead of repeating raw pointer arithmetic at each call site. + +When those helpers operate on addresses or byte offsets, prefer `intptr_t` / `uintptr_t` +or explicit byte-pointer arithmetic instead of plain `int` address parameters. + Only create headers if it's really necessary (the struct doesn't have inlines so you can't determine in which header file it goes and it's thematically very different from the other structs that use it), otherwise put it into the one you determined to be correct. The dwarf often has duplicated inlines, clean those up according to the order in the PS2 info. -Write a TODO comment over the struct/class if you aren't 100% sure that it belongs to the correct header. +Write a TODO comment over the struct/class if you aren't 100% sure that it belongs to the correct header, and say why (ownership uncertainty, circular dependency, dwarf caveat, etc.) instead of leaving a bare marker. ## Phase 3: Add needed files to jumbo file and compile diff --git a/.gitignore b/.gitignore index 547d049c6..4144c1240 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ vc80.pdb undefined_funcs_auto.txt undefined_syms_auto.txt .splache +.DS_Store diff --git a/AGENTS.md b/AGENTS.md index d367fc237..a54d0b789 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,8 +12,16 @@ ninja all_source # build all objects ninja # build all objects, hash check and progress report ninja baseline # generates baseline report for regression checking ninja changes # check for regressions after code changes (empty = no regressions) +python tools/build_matrix.py # sequential full `ninja` across GC/Xbox/PS2, then restore GOWE69 +python tools/build_matrix.py --all-source # sequential compile-only smoke check across GC/Xbox/PS2 ``` +Use `python tools/build_matrix.py` when you want one command that verifies the current +worktree across all supported platforms. It runs `configure.py --version ...` and the +selected ninja target sequentially, writes per-platform logs under `build//logs/`, +prints failure tails with the exact failing command, and restores the worktree to +`GOWE69` by default when it finishes. + ## Project Layout ``` @@ -31,16 +39,7 @@ objdiff.json Generated build/diff configuration ## Sub-Agent Usage -Sub-agents are allowed only for **read-only exploration** tasks such as: - -- searching the codebase for symbols, call sites, or include relationships -- inspecting decomp output, assembly, DWARF, PS2 dumps, or line mappings -- gathering context from Ghidra, `tools/decomp-workflow.py`, `lookup.py`, `decomp-diff.py`, or similar tools -- summarizing findings that help the main worker decide what to change - -Sub-agents must **not** write or edit code files, headers, configs, or other repository files. -All persistent file changes, decomp implementations, scaffolding, and follow-up fixes must be -done by the main worker after reviewing the read-only findings. +Sub-agents are **strictly prohibited**. Do not use sub-agents for any tasks (whether read-only exploration or editing). All work must be performed by the main worker directly. ## Forbidden Changes @@ -354,48 +353,40 @@ This is a **C++98** codebase compiled with ProDG GC 3.9.3 (GCC 2.95 under the ho ## Committing Progress -After each meaningful percentage-point improvement in objdiff match score, commit your changes. Check the current unit match percentage with: +After each meaningful improvement in objdiff match score or DWARF progress, commit your changes. Check the current unit match percentage with: ```sh python tools/decomp-status.py --unit main/Path/To/TU ``` -Commit whenever the match percentage increases (e.g. you matched a new function). Use this format for the commit message: +Commit whenever the match percentage increases or you achieve a milestone (e.g. you matched a new function or improved an existing one). Use this format for the commit message: ``` -n.n%: short description of what was matched or changed +n.n[n]%: [action] [Subject]::[Function] ``` +- **match+**: used when a function or object achieves 100% byte-match status AND 100% DWARF match. +- **match**: used when a function achieves 100% byte-match status but DWARF is still missing/mismatched. +- **improve**: used when the instruction match percentage increases. +- **dwarf match**: used when the normalized DWARF achieves 100% match. +- **dwarf improve**: used when DWARF issues are resolved but it's not yet 100% DWARF-matched. + Examples: -- `42.1%: match UpdateCamera` -- `78.5%: match PlayerController constructor and destructor` +- `42.1%: match+ UpdateCamera` +- `76.7%: match+ TrackStreamer::HibernateStreamingSections` +- `76.7%: match TrackStreamer::WillUnloadBlock` +- `76.5%: dwarf match TrackStreamer::HandleLoading` +- `76.5%: improve TrackStreamer::LoadSection` +- `76.5%: dwarf improve TrackStreamer::CountUserAllocations` - `100.0%: full match for zAnim` -Do not batch up multiple percentage milestones into one commit — commit as each improvement lands. - -## Parallel Sub-Agent Matching - -When working on a translation unit with multiple non-matching functions, use sub-agents selectively for **read-only exploration** around individual functions. Each sub-agent should focus on **exactly one function** — do not assign a sub-agent more than one function at a time. - -**Limit: never run more than 5 sub-agents concurrently.** Spawning too many at once causes resource contention and makes it harder to reason about progress. - -Guidelines: - -- Prefer solving difficult matching work in the main worker. Use sub-agents to inspect one function's context, diff, DWARF, or related call paths without editing files. -- Spawn a sub-agent per function only when the functions are independent (no shared edits to the same source lines). -- Sub-agents stay read-only. Let them inspect existing diff/context output rather than compiling or rebuilding. -- Do not sit idle waiting for sub-agents to finish. Continue with other independent investigation while they run. -- After a useful result lands and you make a real improvement, check the updated match percentage and commit if it improved. +Do not batch up multiple unrelated improvements into one commit — commit as each logical piece of work lands. ## Matching Philosophy You should take the Ghidra decompiler output for the initial translation step, get it to compile, make sure that the dwarf of the function matches and only then look for binary matching problems in the assembly. Be aware Ghidra usually gets the order of branches incorrect in if statements (it inverts the logic and the two bodies are swapped), this needs to be fixed to achieve bytematching status. -You may use sub-agents to gather read-only context during this process, but they must not -edit files. Treat their output as analysis input for the main worker, not as a path to -delegate source changes. - A function is only done when both objdiff and normalized DWARF are exact. Treat a 100% instruction match with a DWARF mismatch as unfinished work, not a near-complete result. diff --git a/config/GOWE69/config.yml b/config/GOWE69/config.yml index 525eaaf36..30e13cf05 100644 --- a/config/GOWE69/config.yml +++ b/config/GOWE69/config.yml @@ -34,3 +34,33 @@ block_relocations: - source: .text:0x80047c1c end: .text:0x80047c28 + +- source: .text:0x8007ecc4 + end: .text:0x8007eccc + +- source: .text:0x8007ee24 + end: .text:0x8007ee2c + +# CameraAI::Director - R_PPC_NONE on StringKey ctor inline mr instructions +- source: .text:0x80068F8C + end: .text:0x80068F90 +- source: .text:0x80068FAC + end: .text:0x80068FB0 + +# CameraAI::Director::Reset - R_PPC_NONE on StringKey ctor inline mr instructions +- source: .text:0x8006921C + end: .text:0x80069220 +- source: .text:0x80069234 + end: .text:0x80069238 + +# CameraAI::Director::Update - R_PPC_NONE on StringKey ctor inline mr instructions +- source: .text:0x80069F50 + end: .text:0x80069F54 +- source: .text:0x80069F60 + end: .text:0x80069F64 + +# LoaderICECameras / UnloaderICECameras - false relocation to _STL::find +- source: .text:0x8007F114 + end: .text:0x8007F11C +- source: .text:0x8007F19C + end: .text:0x8007F1A4 diff --git a/configure.py b/configure.py index 4a58a0646..ac17a1bb0 100755 --- a/configure.py +++ b/configure.py @@ -501,6 +501,8 @@ def MatchingFor(*versions): Object(NonMatching, "Speed/Indep/SourceLists/zAnim.cpp"), Object(NonMatching, "Speed/Indep/SourceLists/zAttribSys.cpp"), Object(NonMatching, "Speed/Indep/SourceLists/zBWare.cpp"), + # Keep zCamera on the real unity C++ source; do not point it at build/asm + # dumps to inflate progress. Object(NonMatching, "Speed/Indep/SourceLists/zCamera.cpp"), Object(NonMatching, "Speed/Indep/SourceLists/zComms.cpp"), Object(NonMatching, "Speed/Indep/SourceLists/zDebug.cpp"), diff --git a/src/Speed/Indep/Libs/Support/Utility/UCOM.h b/src/Speed/Indep/Libs/Support/Utility/UCOM.h index 511c88f54..74766ccc4 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UCOM.h +++ b/src/Speed/Indep/Libs/Support/Utility/UCOM.h @@ -54,6 +54,10 @@ class Object { return gFastMem.Alloc(size, nullptr); } + void *operator new(std::size_t size, const char *name) { + return gFastMem.Alloc(size, name); + } + Object(std::size_t icount) : _mInterfaces(icount) {} ~Object() {} @@ -163,18 +167,22 @@ template class Factory { ~Factory() {} static _PRODUCT CreateInstance(_PRODUCT_SIGNATURE sig, _BUILD_PARAMETERS params); - // TODO - // { - // for (const Prototype *f = Prototype::GetHead(); f != nullptr; f = f->GetNext()) { - // if (f->mSignature == sig) { - // return f->mConstructor(params); - // } - // } - // return nullptr; - // } }; } // namespace COM } // namespace UTL +template +typename UTL::COM::Factory::_PRODUCT +UTL::COM::Factory::CreateInstance( + typename UTL::COM::Factory::_PRODUCT_SIGNATURE sig, + typename UTL::COM::Factory::_BUILD_PARAMETERS params) { + for (const Prototype *f = Prototype::GetHead(); f != nullptr; f = f->GetNext()) { + if (f->mSignature == sig) { + return f->mConstructor(params); + } + } + return nullptr; +} + #endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UListable.h b/src/Speed/Indep/Libs/Support/Utility/UListable.h index c307298c5..fdf9eb393 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UListable.h +++ b/src/Speed/Indep/Libs/Support/Utility/UListable.h @@ -21,7 +21,7 @@ template class Listable { typedef value_type *pointer; typedef value_type const *const_pointer; - class List : public FixedVector { + class List : public _Storage { public: typedef T value_type; typedef value_type *pointer; @@ -29,7 +29,7 @@ template class Listable { // List(const List &); List(); - virtual ~List(); + virtual ~List() {} // List &operator=(List &); }; @@ -70,6 +70,9 @@ template class Listable { static List _mTable; }; +template +Listable::List::List() {} + template class ListableSet { public: typedef T value_type; @@ -103,7 +106,7 @@ template class Li _buckets[idx].push_back(t); } - void _remove(iterator t, std::size_t idx) { + void _remove(pointer t, std::size_t idx) { List &bucket = _buckets[idx]; typename List::iterator newend = std::remove(bucket.begin(), bucket.end(), t); if (newend != bucket.end()) { @@ -130,7 +133,7 @@ template class Li void UnList() { for (std::size_t i = 0; i < EnumMax; i++) { - _mLists._remove(static_cast(this), i); + _mLists._remove(static_cast(this), i); } } @@ -138,7 +141,21 @@ template class Li UnList(); } - iterator Next(Enum idx) {} + iterator Next(Enum idx) { + const List &list = GetList(idx); + const pointer *iter = std::find(list.begin(), list.end(), static_cast(this)); + + if (iter == list.end()) { + return nullptr; + } + + ++iter; + if (iter == list.end()) { + return nullptr; + } + + return *iter; + } void AddToList(Enum to) { _mLists._add(static_cast(this), to); @@ -148,6 +165,11 @@ template class Li static _ListSet _mLists; }; +template +int ListableSet::Count(Enum idx) { + return static_cast(_mLists._buckets[idx].size()); +} + template class Countable { static int _mCount; diff --git a/src/Speed/Indep/Libs/Support/Utility/UMath.h b/src/Speed/Indep/Libs/Support/Utility/UMath.h index 76c15b209..c6de994a5 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UMath.h +++ b/src/Speed/Indep/Libs/Support/Utility/UMath.h @@ -39,6 +39,7 @@ inline float Cosr(const float a) { void BuildRotate(Matrix4 &m, float r, float x, float y, float z); float Ceil(const float x); +float Mod(const float x, const float e); inline float Distance(const Vector3 &a, const Vector3 &b) { return VU0_v3distance(a, b); @@ -83,6 +84,12 @@ inline void Copy(const Vector4 &a, Vector4 &r) { VU0_v4Copy(a, r); } +inline void Copy(const Vector3 &a, Vector3 &r) { + r.x = a.x; + r.y = a.y; + r.z = a.z; +} + #ifdef EA_PLATFORM_XENON void Transpose(const Matrix4 &m, Matrix4 &r); #else @@ -249,6 +256,10 @@ inline void Subxyz(const Vector4 &a, const Vector4 &b, Vector4 &r) { VU0_v4subxyz(a, b, r); } +inline void Sub(const Vector4 &a, const Vector4 &b, Vector4 &r) { + VU0_v4sub(a, b, r); +} + inline void SetYRot(Matrix4 &r, float a) { VU0_MATRIX4setyrot(r, a); } @@ -282,6 +293,10 @@ inline void Rotate(const Vector3 &a, const Matrix4 &m, Vector3 &r) { #endif } +inline void Rotate(const Vector4 &a, const Matrix4 &m, Vector4 &r) { + VU0_MATRIX3x4_vect4mult(a, m, r); +} + inline float Dot(const Vector3 &a, const Vector3 &b) { #ifdef EA_PLATFORM_XENON return a.x * b.x + a.y * b.y + a.z * b.z; @@ -383,6 +398,10 @@ inline float Length(const Vector3 &a) { #endif } +inline float Length(const Vector4 &a) { + return VU0_v4length(a); +} + inline void Matrix4ToQuaternion(const Matrix4 &m, Vector4 &q) { VU0_m4toquat(m, q); } @@ -412,6 +431,10 @@ inline float Lerp(const float a, const float b, const float t) { return a + (b - a) * t; } +inline void Lerp(const Vector3 &a, const Vector3 &b, const float t, Vector3 &r) { + VU0_v3lerp(a, b, t, r); +} + inline void Negate(Vector3 &r) { VU0_v3negate(r); } diff --git a/src/Speed/Indep/Libs/Support/Utility/UTLVector.h b/src/Speed/Indep/Libs/Support/Utility/UTLVector.h index 861aed70f..27e4374c9 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UTLVector.h +++ b/src/Speed/Indep/Libs/Support/Utility/UTLVector.h @@ -9,7 +9,7 @@ #include namespace UTL { -template class Vector { +template class Vector { public: typedef T value_type; typedef value_type *pointer; @@ -59,6 +59,22 @@ template class Vector { return mBegin + mSize; } + const_reference operator[](unsigned int idx) const { + return mBegin[idx]; + } + + reference operator[](unsigned int idx) { + return mBegin[idx]; + } + + void push_back() { + if (size() >= capacity()) { + reserve(GetGrowSize(size() + 1)); + } + new (&mBegin[size()]) T(); + mSize++; + } + void push_back(value_type const &val) { if (size() >= capacity()) { reserve(GetGrowSize(size() + 1)); @@ -71,6 +87,60 @@ template class Vector { mSize = size() - 1; } + void resize(size_type num) { + while (size() > num) { + pop_back(); + } + while (size() < num) { + reserve(GetGrowSize(size() + 1)); + push_back(); + } + } + + bool Contains(pointer p) { + size_type index = p - mBegin; + return index < capacity(); + } + + void assign(const Vector &src) { + assign(src.begin(), src.end()); + } + + void assign(const_iterator srcBeg, const_iterator srcEnd) { + size_type minSize = srcEnd - srcBeg; + const_iterator srcIt = srcBeg; + iterator destIt = begin(); + + resize(minSize); + + if (capacity() < minSize || !Contains(destIt)) { + make_empty(); + reserve(minSize); + } + + destIt = begin(); + srcIt = srcBeg; + + iterator endIt = end(); + while (destIt != endIt && srcIt != srcEnd) { + value_type &dest = *destIt; + const value_type &src = *srcIt; + dest = src; + ++destIt; + ++srcIt; + } + + while (end() != destIt) { + pop_back(); + } + + while (srcIt != srcEnd) { + const value_type &val = *srcIt; + push_back(val); + ++srcIt; + } + } + void reserve(size_type num) { if (num > capacity()) { OnGrowRequest(num); @@ -150,12 +220,12 @@ template class Vector { virtual void FreeVectorSpace(pointer buffer, size_type num) {} virtual size_type GetGrowSize(size_type minSize) const { - return UMath::Max(minSize, mCapacity + ((mCapacity + 1) >> 1)); // TODO is this right? + size_type grow = mCapacity + ((mCapacity + 1) >> 1); + return grow < minSize ? minSize : grow; } - // Unfinished virtual size_type GetMaxCapacity() const { - return 0; + return 0x7FFFFFFF; } virtual void OnGrowRequest(size_type newSize) {} @@ -167,10 +237,20 @@ template class Vector { size_type mSize; // offset 0x8, size 0x4 }; -template class FixedVector : public Vector { +template class FixedVector : public Vector { public: FixedVector() {} + FixedVector(const FixedVector &src) : Vector() { + Vector::Init(); + *this = src; + } + + FixedVector &operator=(const FixedVector &rhs) { + Vector::assign(rhs); + return *this; + } + ~FixedVector() override { // clang is being annoying Vector::clear(); @@ -179,21 +259,18 @@ template class Fixed // TODO also put the typedefs here according to the dwarf? protected: - // Unfinished virtual std::size_t GetGrowSize(std::size_t minSize) const { - return 0; + return Size; } - // Unfinished virtual typename Vector::pointer AllocVectorSpace(std::size_t num, unsigned int alignment) { - return nullptr; + return reinterpret_cast::pointer>(mVectorSpace); } virtual void FreeVectorSpace(typename Vector::pointer buffer, std::size_t) {} - // Unfinished virtual std::size_t GetMaxCapacity() const { - return 0; + return Size; } private: diff --git a/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h b/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h index 87987e57a..c3965238c 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h +++ b/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h @@ -36,11 +36,13 @@ void VU0_v4scaleaddxyz(const UMath::Vector4 &a, const float scaleby, const UMath float VU0_v4lengthsquare(const UMath::Vector4 &a); float VU0_v4lengthsquarexyz(const UMath::Vector4 &a); void VU0_v4subxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); +void VU0_v4sub(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result); float VU0_v4dotprodxyz(const UMath::Vector4 &a, const UMath::Vector4 &b); void VU0_v4scale(const UMath::Vector4 &a, const float scaleby, UMath::Vector4 &result); void VU0_v4scalexyz(const UMath::Vector4 &a, const float scaleby, UMath::Vector4 &result); float VU0_v4distancesquarexyz(const UMath::Vector4 &p1, const UMath::Vector4 &p2); void VU0_MATRIX3x4_vect3mult(const UMath::Vector3 &v, const UMath::Matrix4 &m, UMath::Vector3 &result); +void VU0_MATRIX3x4_vect4mult(const UMath::Vector4 &v, const UMath::Matrix4 &m, UMath::Vector4 &result); void VU0_qmul(const UMath::Vector4 &b, const UMath::Vector4 &a, UMath::Vector4 &dest); void VU0_v3quatrotate(const UMath::Vector4 &q, const UMath::Vector3 &v, UMath::Vector3 &result); @@ -194,6 +196,8 @@ inline float VU0_v4lengthsquare(const UMath::Vector4 &a) {} inline float VU0_v4lengthsquarexyz(const UMath::Vector4 &a) {} +inline void VU0_v4sub(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) {} + inline void VU0_v4subxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &result) {} inline float VU0_v4dotprodxyz(const UMath::Vector4 &a, const UMath::Vector4 &b) {} @@ -225,6 +229,20 @@ inline void VU0_MATRIX3x4_vect3mult(const UMath::Vector3 &v, const UMath::Matrix : "o"(v), "r"(&m)); } +inline void VU0_MATRIX3x4_vect4mult(const UMath::Vector4 &v, const UMath::Matrix4 &m, UMath::Vector4 &result) { + asm __volatile__("lqc2 vf1, %1\n" + "lqc2 vf2, 0x0(%2)\n" + "lqc2 vf3, 0x10(%2)\n" + "lqc2 vf4, 0x20(%2)\n" + "lqc2 vf5, %0\n" + "vmulax ACC, vf2, vf1x\n" + "vmadday ACC, vf3, vf1y\n" + "vmaddz vf5, vf4, vf1z\n" + "sqc2 vf5, %0" + : "=o"(result) + : "o"(v), "r"(&m)); +} + inline void VU0_MATRIX4_vect3mult(const UMath::Vector3 &v, const UMath::Matrix4 &m, UMath::Vector3 &result) { asm __volatile__("lqc2 vf1, %1\n" "lqc2 vf2, 0x0(%2)\n" @@ -354,6 +372,12 @@ inline float VU0_v3distancexz(const UMath::Vector3 &p1, const UMath::Vector3 &p2 #endif } +inline void VU0_v3lerp(const UMath::Vector3 &v1, const UMath::Vector3 &v2, const float t, UMath::Vector3 &target) { + target.x = v1.x + (v2.x - v1.x) * t; + target.y = v1.y + (v2.y - v1.y) * t; + target.z = v1.z + (v2.z - v1.z) * t; +} + // TODO these should go into UVectorMathGC.hpp inline void VU0_v3unitcrossprod(const UMath::Vector3 &a, const UMath::Vector3 &b, UMath::Vector3 &dest) { #ifdef EA_PLATFORM_PLAYSTATION2 @@ -621,6 +645,10 @@ inline float VU0_v4lengthxyz(const UMath::Vector4 &a) { return VU0_sqrt(VU0_v4lengthsquarexyz(a)); } +inline float VU0_v4length(const UMath::Vector4 &a) { + return VU0_sqrt(VU0_v4lengthsquare(a)); +} + inline void VU0_v3unit(const UMath::Vector3 &a, UMath::Vector3 &result) { #ifdef EA_PLATFORM_PLAYSTATION2 u_long128 _t0; diff --git a/src/Speed/Indep/SourceLists/zCamera.cpp b/src/Speed/Indep/SourceLists/zCamera.cpp index e69de29bb..8b4096215 100644 --- a/src/Speed/Indep/SourceLists/zCamera.cpp +++ b/src/Speed/Indep/SourceLists/zCamera.cpp @@ -0,0 +1,57 @@ +#include "Speed/Indep/Src/Camera/Camera.cpp" + +#include "Speed/Indep/Src/Camera/CameraMover.cpp" + +#include "Speed/Indep/Src/Camera/CameraAI.cpp" + +#include "Speed/Indep/Src/Camera/ChaseCamAI.cpp" + +#include "Speed/Indep/Src/Camera/Actions/CDActionDrive.cpp" + +#include "Speed/Indep/Src/Camera/Actions/CDActionTrackCar.cpp" + +#include "Speed/Indep/Src/Camera/Actions/CDActionTrackCop.cpp" + +#include "Speed/Indep/Src/Camera/Actions/CDActionShowcase.cpp" + +#include "Speed/Indep/Src/Camera/Actions/CDActionDebug.cpp" + +#include "Speed/Indep/Src/Camera/Actions/CDActionIce.cpp" + +#include "Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.cpp" + +#include "Speed/Indep/Src/Camera/Movers/Cubic.cpp" + +#include "Speed/Indep/Src/Camera/Movers/TrackCar.cpp" + +#include "Speed/Indep/Src/Camera/Movers/TrackCop.cpp" + +#include "Speed/Indep/Src/Camera/Movers/Rearview.cpp" + +#include "Speed/Indep/Src/Camera/Movers/SelectCar.cpp" + +#include "Speed/Indep/Src/Camera/Movers/Showcase.cpp" + +#include "Speed/Indep/Src/Camera/Movers/DebugWorld.cpp" + +#include "Speed/Indep/Src/Camera/Movers/Still.cpp" + +#include "Speed/Indep/Src/Camera/Movers/CopView.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEData.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEAnchor.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEAnimScene.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEMover.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEReplay.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEPoint.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICERender.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEOverlays.cpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEManager.cpp" diff --git a/src/Speed/Indep/SourceLists/zGameplay.cpp b/src/Speed/Indep/SourceLists/zGameplay.cpp index e69de29bb..2f1202246 100644 --- a/src/Speed/Indep/SourceLists/zGameplay.cpp +++ b/src/Speed/Indep/SourceLists/zGameplay.cpp @@ -0,0 +1,151 @@ +#include "Speed/Indep/Src/Gameplay/GRuntimeInstance.cpp" +#include "Speed/Indep/Src/Gameplay/GTrigger.cpp" +#include "Speed/Indep/Src/Gameplay/GMarker.cpp" +#include "Speed/Indep/Src/Gameplay/GActivity.cpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.cpp" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.cpp" +#include "Speed/Indep/Src/Gameplay/GCharacter.cpp" +#include "Speed/Indep/Src/Gameplay/GState.cpp" +#include "Speed/Indep/Src/Gameplay/GHandler.cpp" +#include "Speed/Indep/Src/Gameplay/GManager.cpp" +#include "Speed/Indep/Src/Gameplay/GTimer.cpp" +#include "Speed/Indep/Src/Gameplay/GVault.cpp" +#include "Speed/Indep/Src/Gameplay/GObjectBlock.cpp" +#include "Speed/Indep/Src/Gameplay/GInfractionManager.cpp" +#include "Speed/Indep/Src/Gameplay/GMilestone.cpp" +#include "Speed/Indep/Src/Gameplay/GSpeedTrap.cpp" +#include "Speed/Indep/Src/Gameplay/GIcon.cpp" + +namespace _STL { +template _Rb_tree_node_base* _STLP_CALL +_Rb_global<_Dummy>::_Rebalance_for_erase(_Rb_tree_node_base* __z, + _Rb_tree_node_base*& __root, + _Rb_tree_node_base*& __leftmost, + _Rb_tree_node_base*& __rightmost) +{ + _Rb_tree_node_base* __y = __z; + _Rb_tree_node_base* __x = 0; + _Rb_tree_node_base* __x_parent = 0; + if (__y->_M_left == 0) + __x = __y->_M_right; + else + if (__y->_M_right == 0) + __x = __y->_M_left; + else { + __y = __y->_M_right; + while (__y->_M_left != 0) + __y = __y->_M_left; + __x = __y->_M_right; + } + if (__y != __z) { + __z->_M_left->_M_parent = __y; + __y->_M_left = __z->_M_left; + if (__y != __z->_M_right) { + __x_parent = __y->_M_parent; + if (__x) __x->_M_parent = __y->_M_parent; + __y->_M_parent->_M_left = __x; + __y->_M_right = __z->_M_right; + __z->_M_right->_M_parent = __y; + } + else + __x_parent = __y; + if (__root == __z) + __root = __y; + else if (__z->_M_parent->_M_left == __z) + __z->_M_parent->_M_left = __y; + else + __z->_M_parent->_M_right = __y; + __y->_M_parent = __z->_M_parent; + _STLP_STD::swap(__y->_M_color, __z->_M_color); + __y = __z; + } + else { + __x_parent = __y->_M_parent; + if (__x) __x->_M_parent = __y->_M_parent; + if (__root == __z) + __root = __x; + else + if (__z->_M_parent->_M_left == __z) + __z->_M_parent->_M_left = __x; + else + __z->_M_parent->_M_right = __x; + if (__leftmost == __z) + if (__z->_M_right == 0) + __leftmost = __z->_M_parent; + else + __leftmost = _Rb_tree_node_base::_S_minimum(__x); + if (__rightmost == __z) + if (__z->_M_left == 0) + __rightmost = __z->_M_parent; + else + __rightmost = _Rb_tree_node_base::_S_maximum(__x); + } + if (__y->_M_color != _S_rb_tree_red) { + while (__x != __root && (__x == 0 || __x->_M_color == _S_rb_tree_black)) + if (__x == __x_parent->_M_left) { + _Rb_tree_node_base* __w = __x_parent->_M_right; + if (__w->_M_color == _S_rb_tree_red) { + __w->_M_color = _S_rb_tree_black; + __x_parent->_M_color = _S_rb_tree_red; + _Rotate_left(__x_parent, __root); + __w = __x_parent->_M_right; + } + if ((__w->_M_left == 0 || + __w->_M_left->_M_color == _S_rb_tree_black) && (__w->_M_right == 0 || + __w->_M_right->_M_color == _S_rb_tree_black)) { + __w->_M_color = _S_rb_tree_red; + __x = __x_parent; + __x_parent = __x_parent->_M_parent; + } else { + if (__w->_M_right == 0 || + __w->_M_right->_M_color == _S_rb_tree_black) { + if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black; + __w->_M_color = _S_rb_tree_red; + _Rotate_right(__w, __root); + __w = __x_parent->_M_right; + } + __w->_M_color = __x_parent->_M_color; + __x_parent->_M_color = _S_rb_tree_black; + if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black; + _Rotate_left(__x_parent, __root); + break; + } + } else { + _Rb_tree_node_base* __w = __x_parent->_M_left; + if (__w->_M_color == _S_rb_tree_red) { + __w->_M_color = _S_rb_tree_black; + __x_parent->_M_color = _S_rb_tree_red; + _Rotate_right(__x_parent, __root); + __w = __x_parent->_M_left; + } + if ((__w->_M_right == 0 || + __w->_M_right->_M_color == _S_rb_tree_black) && (__w->_M_left == 0 || + __w->_M_left->_M_color == _S_rb_tree_black)) { + __w->_M_color = _S_rb_tree_red; + __x = __x_parent; + __x_parent = __x_parent->_M_parent; + } else { + if (__w->_M_left == 0 || + __w->_M_left->_M_color == _S_rb_tree_black) { + if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black; + __w->_M_color = _S_rb_tree_red; + _Rotate_left(__w, __root); + __w = __x_parent->_M_left; + } + __w->_M_color = __x_parent->_M_color; + __x_parent->_M_color = _S_rb_tree_black; + if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black; + _Rotate_right(__x_parent, __root); + break; + } + } + if (__x) __x->_M_color = _S_rb_tree_black; + } + return __y; +} + +template _Rb_tree_node_base* _Rb_global::_Rebalance_for_erase(_Rb_tree_node_base*, + _Rb_tree_node_base*&, + _Rb_tree_node_base*&, + _Rb_tree_node_base*&); +} diff --git a/src/Speed/Indep/Src/Animation/AnimDirectory.hpp b/src/Speed/Indep/Src/Animation/AnimDirectory.hpp index a201d8400..b5c6e5ebe 100644 --- a/src/Speed/Indep/Src/Animation/AnimDirectory.hpp +++ b/src/Speed/Indep/Src/Animation/AnimDirectory.hpp @@ -18,7 +18,12 @@ struct AnimFileLoadInfo { // total size: 0x8 struct AnimSceneLoadInfo { - AnimSceneLoadInfo() {} + AnimSceneLoadInfo() + : mAnimSceneHash(0) // + , mSharedFileCount(0) // + , mSharedFileStartIndex(0) // + , mSceneFileCount(0) // + , mSceneFileStartIndex(0) {} unsigned int mAnimSceneHash; // offset 0x0, size 0x4 unsigned char mSharedFileCount; // offset 0x4, size 0x1 @@ -34,15 +39,15 @@ class AnimDirectory { ~AnimDirectory() {} - unsigned int GetFileCount() {} + unsigned int GetFileCount() { return mAnimFileLoadInfo.mAnimFileCount; } - char *GetFileName(unsigned int file_slot_position) {} + char *GetFileName(unsigned int file_slot_position) { return mAnimFileLoadInfo.mAnimFileNameTable[file_slot_position]; } - unsigned int GetSceneCount() {} + unsigned int GetSceneCount() { return mAnimSceneCount; } - void GetSceneLoadInfo(unsigned int scene_slot_position, AnimSceneLoadInfo &info) {} + void GetSceneLoadInfo(unsigned int scene_slot_position, AnimSceneLoadInfo &info) { info = mAnimSceneLoadInfo[scene_slot_position]; } - AnimSceneLoadInfo *GetSceneLoadInfo(int slot) {} + AnimSceneLoadInfo *GetSceneLoadInfo(int slot) { return &mAnimSceneLoadInfo[slot]; } void GetNameOfSceneNumber(int scene_slot_position, char *buffer) {} @@ -52,9 +57,9 @@ class AnimDirectory { void SetFileName(unsigned int file_slot_position, char *file_name) {} - void SetSceneCount(unsigned int count) {} + void SetSceneCount(unsigned int count) { mAnimSceneCount = count; } - void SetSceneLoadInfo(unsigned int scene_slot_position, const AnimSceneLoadInfo &info) {} + void SetSceneLoadInfo(unsigned int scene_slot_position, const AnimSceneLoadInfo &info) { mAnimSceneLoadInfo[scene_slot_position] = info; } void EndianSwap() { bPlatEndianSwap(&mAnimSceneCount); diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionDebug.cpp b/src/Speed/Indep/Src/Camera/Actions/CDActionDebug.cpp index e69de29bb..59b7f7bc3 100644 --- a/src/Speed/Indep/Src/Camera/Actions/CDActionDebug.cpp +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionDebug.cpp @@ -0,0 +1,75 @@ +#include "Speed/Indep/Src/Camera/Actions/CDActionDebug.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" + +extern bool Tweak_EnableICEAuthoring; + +static UTL::COM::Factory::Prototype _CDActionDebug("DEBUG", CDActionDebug::Construct); + +const Attrib::StringKey &CDActionDebug::GetName() const { + static Attrib::StringKey name("DEBUG"); + return name; +} + +Attrib::StringKey CDActionDebug::GetNext() const { + if (mDone) { + return mPrev; + } + return Attrib::StringKey(""); +} + +CameraAI::Action *CDActionDebug::Construct(CameraAI::Director *director) { + return new (static_cast(0)) CDActionDebug(director); +} + +CDActionDebug::CDActionDebug(CameraAI::Director *director) + : CameraAI::Action(), // + mActionQ(1, 0x98c7a2f5, "DebugWorld", false) { + mDone = false; + bVector3 start_position(0.0f, 0.0f, 0.0f); + bVector3 start_direction(0.0f, 0.0f, 1.0f); + Action *prev_action = director->GetAction(); + + if (prev_action) { + mPrev = prev_action->GetName(); + CameraMover *prev_mover = prev_action->GetMover(); + if (prev_mover) { + start_position = *prev_mover->GetPosition(); + start_direction = *prev_mover->GetDirection(); + } + } + + int viewId = static_cast(director->GetViewID()); + mMover = new DebugWorldCameraMover(viewId, &start_position, &start_direction, static_cast(viewId)); +} + +CDActionDebug::~CDActionDebug() { + CameraMover *m = mMover; + delete m; +} + +void CDActionDebug::Update(float dT) { + while (!mActionQ.IsEmpty()) { + ActionRef aRef = mActionQ.GetAction(); + float data = aRef.Data(); + if (aRef.ID() == 0x15 && !Tweak_EnableICEAuthoring) { + mDone = true; + } + mActionQ.PopAction(); + } +} + +bool CDActionDebug::GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) { + bMatrix4 camera_to_world; + eInvertTransformationMatrix(&camera_to_world, mMover->GetCamera()->GetCameraMatrix()); + ConversionUtil::RightToLeftMatrix4(camera_to_world, matrix); + ConversionUtil::RightToLeftVector3(*mMover->GetCamera()->GetVelocityPosition(), velocity); + return true; +} + +namespace ConversionUtil { +template void Copy4(UMath::Vector4 &, const UMath::Vector4 &); +template void RightToLeftVector3(const UMath::Vector3 &, UMath::Vector3 &); +template void RightToLeftMatrix4(const UMath::Matrix4 &, UMath::Matrix4 &); +} // namespace ConversionUtil diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionDebug.hpp b/src/Speed/Indep/Src/Camera/Actions/CDActionDebug.hpp new file mode 100644 index 000000000..82644cbc8 --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionDebug.hpp @@ -0,0 +1,34 @@ +#ifndef CAMERA_ACTIONS_CDACTIONDEBUG_H +#define CAMERA_ACTIONS_CDACTIONDEBUG_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Input/ActionQueue.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ITrafficCenter.h" + +// total size: 0x2D0 +class CDActionDebug : public CameraAI::Action, public ITrafficCenter { + public: + const Attrib::StringKey &GetName() const override; + Attrib::StringKey GetNext() const override; + CameraMover *GetMover() override { return mMover; } + void SetSpecial(float val) override {} + + static CameraAI::Action *Construct(CameraAI::Director *director); + CDActionDebug(CameraAI::Director *director); + ~CDActionDebug() override; + void Reset() override {} + void Update(float dT) override; + bool GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) override; + + private: + ActionQueue mActionQ; // offset 0x20, size 0x294 + CameraMover *mMover; // offset 0x2B4, size 0x4 + Attrib::StringKey mPrev; // offset 0x2B8, size 0x10 + bool mDone; // offset 0x2C8, size 0x1 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.cpp b/src/Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.cpp index e69de29bb..9a392bcc0 100644 --- a/src/Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.cpp +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.cpp @@ -0,0 +1,116 @@ +#include "Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +extern int mToggleCar; +extern eVehicleList mToggleCarList; + +extern bool CameraDebugWatchCar; + +// TODO: Confirm the original home and linkage of these camera-debug globals. + +template <> +UTL::Collections::Listable::List + UTL::Collections::Listable::_mTable; + +static UTL::COM::Factory::Prototype _CDActionDebugWatchCar("DEBUGWATCHCAR", CDActionDebugWatchCar::Construct); + +const Attrib::StringKey &CDActionDebugWatchCar::GetName() const { + static Attrib::StringKey name("DEBUGWATCHCAR"); + return name; +} + +Attrib::StringKey CDActionDebugWatchCar::GetNext() const { + if (CameraDebugWatchCar) { + return Attrib::StringKey(); + } + return mPrev; +} + +ISimable *CDActionDebugWatchCar::GetSimable() { + return ISimable::FindInstance(mhSimable); +} + +void CDActionDebugWatchCar::ReleaseTarget() { + if (mTarget.IsValid()) { + mhSimable = 0; + mTarget.Set(0); + } +} + +void CDActionDebugWatchCar::AquireTarget() { + ISimable *isim = ISimable::FindInstance(mhSimable); + if (!isim) { + ReleaseTarget(); + } + + if (mToggleCar >= 0 && mToggleCarList <= 9 && mToggleCarList >= 0) { + int count = IVehicle::Count(mToggleCarList); + if (count != 0) { + IVehicle *ivehicle = IVehicle::GetList(mToggleCarList)[static_cast(mToggleCar % count)]; + if (ivehicle) { + if (ivehicle->GetSimable()->GetInstanceHandle() != mhSimable) { + unsigned int world_id = ivehicle->GetSimable()->GetWorldID(); + if (world_id != 0) { + ReleaseTarget(); + CameraAnchor *anchor = mAnchor; + const char *model_str = ivehicle->GetVehicleAttributes().MODEL().GetString(); + if (!model_str) { + model_str = ""; + } + anchor->SetModel(bStringHash(model_str)); + mTarget.Set(world_id); + mhSimable = ivehicle->GetSimable()->GetInstanceHandle(); + } + } + } + } + } +} + +CameraAI::Action *CDActionDebugWatchCar::Construct(CameraAI::Director *director) { + if (!director->GetAction()) { + return nullptr; + } + return new CDActionDebugWatchCar(director); +} + +CDActionDebugWatchCar::CDActionDebugWatchCar(CameraAI::Director *director) + : CameraAI::Action(), // + IDebugWatchCar(this) { + mTarget = WorldConn::Reference(0); + mhSimable = nullptr; + mPrev = director->GetAction()->GetName(); + + mAnchor = new CameraAnchor(0); + mMover = new CubicCameraMover(static_cast(director->GetViewID()), mAnchor, 0, false, false, false, true); +} + +CDActionDebugWatchCar::~CDActionDebugWatchCar() { + ReleaseTarget(); + CameraMover *m = mMover; + delete m; + delete mAnchor; +} + +void CDActionDebugWatchCar::Update(float dT) { + AquireTarget(); + if (mTarget.IsValid()) { + mAnchor->SetWorldID(mTarget.GetWorldID()); + const bVector3 *accel = mTarget.GetAcceleration(); + mAnchor->Update(dT, *mTarget.GetMatrix(), *mTarget.GetVelocity(), *accel); + } +} + +bool CDActionDebugWatchCar::GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) { + bool valid = mTarget.IsValid(); + if (valid) { + ConversionUtil::RightToLeftMatrix4(*mTarget.GetMatrix(), matrix); + ConversionUtil::RightToLeftVector3(*mTarget.GetVelocity(), velocity); + } + return valid; +} + +template class UTL::Collections::Listable::List; diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.hpp b/src/Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.hpp new file mode 100644 index 000000000..a770e7f10 --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionDebugWatchCar.hpp @@ -0,0 +1,40 @@ +#ifndef CAMERA_ACTIONS_CDACTIONDEBUGWATCHCAR_H +#define CAMERA_ACTIONS_CDACTIONDEBUGWATCHCAR_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Camera/IDebugWatchCar.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ITrafficCenter.h" +#include "Speed/Indep/Src/World/WorldConn.h" + +// total size: 0x60 +class CDActionDebugWatchCar : public CameraAI::Action, public IDebugWatchCar, public ITrafficCenter { + public: + const Attrib::StringKey &GetName() const override; + Attrib::StringKey GetNext() const override; + CameraMover *GetMover() override { return mMover; } + void SetSpecial(float val) override {} + + static CameraAI::Action *Construct(CameraAI::Director *director); + CDActionDebugWatchCar(CameraAI::Director *director); + ~CDActionDebugWatchCar() override; + void Reset() override {} + ISimable *GetSimable() override; + void ReleaseTarget(); + void AquireTarget(); + void Update(float dT) override; + bool GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) override; + + private: + CameraMover *mMover; // offset 0x2C, size 0x4 + CameraAnchor *mAnchor; // offset 0x30, size 0x4 + WorldConn::Reference mTarget; // offset 0x34, size 0x10 + Attrib::StringKey mPrev; // offset 0x48, size 0x10 + HSIMABLE mhSimable; // offset 0x58, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionDrive.cpp b/src/Speed/Indep/Src/Camera/Actions/CDActionDrive.cpp index e69de29bb..38b57eb74 100644 --- a/src/Speed/Indep/Src/Camera/Actions/CDActionDrive.cpp +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionDrive.cpp @@ -0,0 +1,602 @@ +#include "Speed/Indep/Src/Camera/Actions/CDActionDrive.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Camera/ICE/ICEManager.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Generated/Messages/MJumpCut.h" +#include "Speed/Indep/Src/Interfaces/IBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngine.h" +#include "Speed/Indep/Src/Interfaces/Simables/IExplosion.h" +#include "Speed/Indep/Src/Interfaces/Simables/IINput.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISuspension.h" +#include "Speed/Indep/Src/Interfaces/Simables/ITransmission.h" +#include "Speed/Indep/Src/Physics/Common/VehicleSystem.h" +#include "Speed/Indep/Src/Sim/SimSurface.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" + +extern bool gCinematicMomementCamera; +extern bool gGameBreakerCamera; +extern bool gCamCloseToRoadBlock; + +static float kCinematicMomementSeconds; + +class IVisualTreatment { +public: + static IVisualTreatment *Get(); + void TriggerPulse(float length); +}; + +int GetPOVTypeFromPlayerCamera(ePlayerSettingsCameras cam); +void MaybeCameraShake(int nPlayer, bVector3 *pAccel); + +static UTL::COM::Factory::Prototype _CDActionDrive("DRIVE", CDActionDrive::Construct); + +const Attrib::StringKey &CDActionDrive::GetName() const { + static Attrib::StringKey name("DRIVE"); + return name; +} + +Attrib::StringKey CDActionDrive::GetNext() const { + return Attrib::StringKey(); +} + +void CDActionDrive::SetSpecial(float val) { + if (val > 0.0f) { + kCinematicMomementSeconds = val; + mCinematicMomementTimerInc = true; + } +} + +bool CDActionDrive::Attach(UTL::COM::IUnknown *pOther) { + return mAttachments->Attach(pOther); +} + +bool CDActionDrive::Detach(UTL::COM::IUnknown *pOther) { + return mAttachments->Detach(pOther); +} + +bool CDActionDrive::IsAttached(const UTL::COM::IUnknown *pOther) const { + return mAttachments->IsAttached(pOther); +} + +const IAttachable::List *CDActionDrive::GetAttachments() const { + return &mAttachments->GetList(); +} + +CameraAI::Action *CDActionDrive::Construct(CameraAI::Director *director) { + IPlayer *player = nullptr; + int player_idx; + IPlayer *ip; + for (IPlayer *const *iter = IPlayer::GetList(PLAYER_LOCAL).begin(); iter != IPlayer::GetList(PLAYER_LOCAL).end(); ++iter) { + ip = *iter; + if (ip->GetControllerPort() == static_cast(director->GetViewID())) { + player = ip; + break; + } + } + + if (!player) { + goto null_return; + } + + if (!player->GetSettings()) { + goto null_return; + } + + { + ISimable *isimable = player->GetSimable(); + if (!isimable) { + goto null_return; + } + + unsigned int world_id = isimable->GetWorldID(); + CameraAI::Action *action = nullptr; + if (world_id != 0) { + action = new (static_cast(0)) CDActionDrive(director, player); + } + return action; + } + +null_return: + return nullptr; +} + +CDActionDrive::CDActionDrive(CameraAI::Director *director, IPlayer *player) + : CameraAI::Action(), // + IAttachable(this), // + mTarget(0) { + mPlayer = player; + mVehicle = nullptr; + mGameBreakerScale = 0.0f; + mViewID = director->GetViewID(); + mDampCollisionTime = 0.0f; + mGroundCollisionTime = 0.0f; + mObjectCollisionTime = 0.0f; + mMaxCollisionTime = 2.0f; + mPulseTimer = 0.0f; + mCinematicMomementTimer = 0.0f; + mCinematicMomementTimerInc = false; + mGear = 0; + + gCinematicMomementCamera = false; + gGameBreakerCamera = false; + if (director->IsCinematicMomement()) { + gCinematicMomementCamera = true; + mCinematicMomementTimer = 1.0f; + kCinematicMomementSeconds = mMaxCollisionTime; + mPulseTimer = 0.3f; + } + + mAttachments = new Sim::Attachments(static_cast(this)); + + mMsgJumpCut = Hermes::Handler::Create( + this, &CDActionDrive::MessageJumpCut, "Camera", 0); + + mAttachments->Attach(mPlayer); + + bool smooth = false; + CameraMover *m = director->GetMover(); + if (m && m->GetType() == CM_ICE) { + smooth = TheICEManager.IsSmoothExit(); + } + + mAnchor = new (static_cast(0), 0) CameraAnchor(0); + + if (0.0f < mCinematicMomementTimer) { + mAnchor->SetZoom(1.0f - mCinematicMomementTimer * 0.1f); + } + + AquireCar(); + + if (mTarget.IsValid()) { + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc = nullptr; + if (mVehicle && mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + } + + mAnchor->Update(0.0f, mat, *mTarget.GetVelocity(), *mTarget.GetAcceleration()); + } + + mMover = new (static_cast(0), 0) CubicCameraMover(static_cast(director->GetViewID()), mAnchor, player->GetSettings()->CurCam, smooth, false, false, true); + mRearViewMirrorMover = new (static_cast(0), 0) RearViewMirrorCameraMover(3, mAnchor); +} + +CDActionDrive::~CDActionDrive() { + if (mMsgJumpCut) { + Hermes::Handler::Destroy(mMsgJumpCut); + } + if (mPlayer) { + mAttachments->Detach(mPlayer); + } + if (mVehicle) { + mAttachments->Detach(mVehicle); + } + CameraMover *rvm = mRearViewMirrorMover; + delete rvm; + CameraMover *m = mMover; + delete m; + delete mAnchor; + delete mAttachments; + Sim::Collision::RemoveListener(static_cast(this)); +} + +void CDActionDrive::Reset() { + mGameBreakerScale = 0.0f; + mDampCollisionTime = 0.0f; + mGroundCollisionTime = 0.0f; + mObjectCollisionTime = 0.0f; + mPulseTimer = 0.0f; + mCinematicMomementTimerInc = false; + mCinematicMomementTimer = 0.0f; + mGear = 0; +} + +void CDActionDrive::OnDetached(IAttachable *pOther) { + if (ComparePtr(pOther, mPlayer)) { + mPlayer = nullptr; + } + if (ComparePtr(pOther, mVehicle)) { + OnCarDetached(); + } +} + +void CDActionDrive::OnCarDetached() { + Sim::Collision::RemoveListener(static_cast(this), mVehicle); + if (mTarget.IsValid()) { + mTarget.Set(0); + } + if (mAnchor) { + mAnchor->SetWorldID(0); + } + mVehicle = nullptr; +} + +void CDActionDrive::OnCollision(const Sim::Collision::Info &cinfo) { + float speed = UMath::Length(cinfo.closingVel); + switch (cinfo.type) { + case Sim::Collision::Info::OBJECT: { + if (speed > 20.0f) { + float objecttime = UMath::Min((speed - 20.0f) * 0.01f, 1.0f); + objecttime = UMath::Max(objecttime * mMaxCollisionTime, mObjectCollisionTime); + mObjectCollisionTime = objecttime; + } + break; + } + case Sim::Collision::Info::WORLD: { + if (speed > 5.0f) { + float damptime = UMath::Min((speed - 5.0f) * 0.025f, 1.0f); + damptime = UMath::Max(damptime * mMaxCollisionTime, mDampCollisionTime); + mDampCollisionTime = damptime; + } + break; + } + case Sim::Collision::Info::GROUND: { + if (speed > 3.0f) { + float groundtime = UMath::Min((speed - 3.0f) * 0.05f, 1.0f); + groundtime = UMath::Max(groundtime * mMaxCollisionTime, mGroundCollisionTime); + mGroundCollisionTime = groundtime; + } + break; + } + } +} + +void CDActionDrive::AquireCar() { + ISimable *isimable; + + if (!mPlayer) { + return; + } + + if (!ComparePtr(mPlayer->GetSimable(), mVehicle)) { + if (mVehicle) { + Detach(mVehicle); + mVehicle = nullptr; + } + } + if (mVehicle) { + return; + } + isimable = mPlayer->GetSimable(); + if (isimable) { + mTarget.Set(isimable->GetWorldID()); + if (mTarget.IsValid()) { + if (isimable->QueryInterface(&mVehicle)) { + Attach(mVehicle); + Sim::Collision::AddListener(static_cast(this), mVehicle, "Camera"); + CameraAnchor *anchor = mAnchor; + const char *model_str = mVehicle->GetVehicleAttributes().MODEL().GetString(); + if (!model_str) { + model_str = ""; + } + anchor->SetModel(bStringHash(model_str)); + mAnchor->SetWorldID(mTarget.GetWorldID()); + IRigidBody *body = isimable->GetRigidBody(); + UMath::Vector3 dimension = body->GetDimension(); + mAnchor->SetDimension(bVector3(dimension.x, dimension.y, dimension.z)); + ITransmission *itrans; + if (mVehicle->QueryInterface(&itrans)) { + mAnchor->SetTopSpeed(itrans->GetMaxSpeedometer()); + mGear = itrans->GetGear(); + } + } + } + } +} + +void CDActionDrive::Update(float dT) { + gCinematicMomementCamera = false; + gGameBreakerCamera = false; + + mDampCollisionTime -= dT; + if (mDampCollisionTime < 0.0f) { + mDampCollisionTime = 0.0f; + } + mGroundCollisionTime -= dT; + if (mGroundCollisionTime < 0.0f) { + mGroundCollisionTime = 0.0f; + } + mObjectCollisionTime -= dT; + if (mObjectCollisionTime < 0.0f) { + mObjectCollisionTime = 0.0f; + } + + if (!mPlayer) { + if (mVehicle) { + Detach(mVehicle); + mVehicle = nullptr; + } + return; + } + + AquireCar(); + mAnchor->SetVehicleDestroyed(false); + if (mVehicle && mVehicle->IsDestroyed()) { + mAnchor->SetVehicleDestroyed(true); + } + + if (mMover->OutsidePOV() && GRaceStatus::Exists()) { + if (GRaceStatus::Get().GetIsTimeLimited()) { + if (GRaceStatus::Get().GetRaceTimeRemaining() <= 0.0f) { + ISimable *playerSim = mPlayer->GetSimable(); + GRacerInfo *racerInfo = GRaceStatus::Get().GetRacerInfo(playerSim); + if (racerInfo && !racerInfo->IsFinishedRacing()) { + return; + } + } + } + } + + if (GRaceStatus::Exists()) { + ISimable *playerSim = mPlayer->GetSimable(); + GRacerInfo *racerInfo = GRaceStatus::Get().GetRacerInfo(playerSim); + if (racerInfo && racerInfo->GetCameraDetached()) { + return; + } + } + + bool isBeingPursued = false; + mAnchor->SetCloseToRoadBlock(isBeingPursued); + IVehicleAI *ivehicleai = mVehicle->GetAIVehiclePtr(); + if (ivehicleai) { + IPursuit *ipursuit = ivehicleai->GetPursuit(); + if (ipursuit) { + float distance = 0.0f; + IVehicle *cop = ipursuit->GetNearestCopInRoadblock(&distance); + if (cop && 0.0f < distance && distance < mAnchor->GetVelocityMagnitude() * 3.0f) { + for (IVehicle *const *iter = IVehicle::GetList(VEHICLE_AICOPS).begin(); iter != IVehicle::GetList(VEHICLE_AICOPS).end(); ++iter) { + IVehicle *p_car = *iter; + if (p_car && p_car->IsActive() && p_car->GetVehicleClass() == VehicleClass::CAR) { + UVector3 ucoppos(p_car->GetPosition()); + bVector3 coppos; + eSwizzleWorldVector(reinterpret_cast(ucoppos), coppos); + bVector3 copdir; + bSub(&copdir, &coppos, mAnchor->GetGeometryPosition()); + float copdist = bLength(&copdir); + + if (0.0f < copdist && copdist < mAnchor->GetVelocityMagnitude() * 3.0f) { + bVector3 unitcopdir; + bNormalize(&unitcopdir, &copdir); + float dot = bDot(&unitcopdir, mAnchor->GetForwardVector()); + if (-0.5f < dot) { + float s = bClamp(dot, 0.0f, 1.0f); + s = 1.0f - s * s; + float targetsize = bSqrt(s); + bVector2 target(copdir.z * targetsize, copdir.x * targetsize); + if (bLength(target) < 10.0f) { + mAnchor->SetCloseToRoadBlock(true); + gCamCloseToRoadBlock = true; + break; + } + } + } + } + } + } + } + } + + IInput *iinput; + if (mVehicle && mVehicle->QueryInterface(&iinput)) { + mMover->SetLookBack(false); + if (iinput->IsLookBackButtonPressed() && !mVehicle->IsStaging() && !isBeingPursued) { + mMover->SetLookBack(true); + } + mAnchor->SetBrakeEngaged(false); + if (0.0f < iinput->GetControls().fHandBrake || 0.0f < iinput->GetControls().fBrake) { + mAnchor->SetBrakeEngaged(true); + } + } + + if (0.0f < mPulseTimer) { + float pulseTime = mPulseTimer - dT; + mPulseTimer = pulseTime; + if (0.0f < pulseTime && pulseTime < 0.25f) { + mPulseTimer = 0.0f; + IVisualTreatment *ivt = IVisualTreatment::Get(); + if (ivt) { + ivt->TriggerPulse(0.5f); + } + } + } + + float timer; + if (mCinematicMomementTimerInc) { + timer = 1.0f; + if (mCinematicMomementTimer < 1.0f) { + timer = mCinematicMomementTimer + dT * 4.0f / kCinematicMomementSeconds; + } + } else { + timer = 0.0f; + if (0.0f < mCinematicMomementTimer) { + timer = mCinematicMomementTimer - dT / kCinematicMomementSeconds; + } + } + mCinematicMomementTimer = timer; + + if (0.001f < mCinematicMomementTimer) { + gCinematicMomementCamera = true; + } + + if (gCinematicMomementCamera) { + Camera *camera = mMover->GetCamera(); + if (camera) { + float timeScale = (1.0f - mCinematicMomementTimer) * 2.5f + 3.0f; + if (0.9f < timeScale) { + timeScale = 1.0f; + } + camera->SetSimTimeMultiplier(timeScale); + } + } + + float gbScale; + if (mPlayer->InGameBreaker()) { + gbScale = dT * 4.0f + mGameBreakerScale; + mGameBreakerScale = gbScale; + gbScale = UMath::Min(gbScale, 1.0f); + } else { + gbScale = mGameBreakerScale - dT; + mGameBreakerScale = gbScale; + gbScale = UMath::Max(gbScale, 0.0f); + } + mGameBreakerScale = gbScale; + + if (0.75f < mGameBreakerScale) { + gGameBreakerCamera = true; + } + + { + PlayerSettings *settings = mPlayer->GetSettings(); + if (settings) { + int pov_type = GetPOVTypeFromPlayerCamera(settings->CurCam); + + if (mVehicle && mVehicle->QueryInterface(&iinput)) { + static int old_pov = -1; + if (iinput->IsPullBackButtonPressed()) { + old_pov = pov_type; + mMover->SetPovType(PSC_PURSUIT); + } else { + if (iinput->IsLookBackButtonPressed() && isBeingPursued) { + old_pov = pov_type; + mMover->SetPovType(PSC_PURSUIT); + } else if (old_pov > -1) { + mMover->SetPovType(old_pov); + old_pov = -1; + } else { + mMover->SetPovType(pov_type); + } + } + } else { + mMover->SetPovType(pov_type); + } + } + } + + if (mTarget.IsValid()) { + if (mVehicle) { + mAnchor->SetDragRace(mVehicle->GetDriverStyle() == STYLE_DRAG); + } + + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc = nullptr; + mAnchor->SetSurface(SimSurface::kNull); + mAnchor->SetTouchingGround(false); + + if (mVehicle && mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + + ISuspension *isuspension; + if (mVehicle->QueryInterface(&isuspension)) { + if (isuspension->GetNumWheels() == isuspension->GetNumWheelsOnGround()) { + ISimable *isimable = mVehicle->GetSimable(); + WWorldPos &wpos = isimable->GetWPos(); + SimSurface surf(wpos.GetSurface()); + mAnchor->SetSurface(surf); + } + if (isuspension->GetNumWheelsOnGround() > 0) { + mAnchor->SetTouchingGround(true); + } + } + } + + mAnchor->SetNosEngaged(false); + mAnchor->SetOverRev(false); + if (mVehicle) { + ISimable *isimable; + if (mVehicle->QueryInterface(&isimable)) { + IEngine *iengine; + if (isimable->QueryInterface(&iengine)) { + mAnchor->SetNosEngaged(iengine->IsNOSEngaged()); + mAnchor->SetOverRev(iengine->GetRPM() > iengine->GetRedline() - 500.0f); + } + } + } + + float drift = 0.0f; + if (mAnchor->IsTouchingGround() && mVehicle) { + float slipangle = bAbs(mVehicle->GetSlipAngle()); + slipangle = ANGLE2DEG(slipangle); + drift = UMath::Ramp(slipangle, 5.0f, 45.0f); + float speedFactor = UMath::Ramp(mVehicle->GetAbsoluteSpeed() - 10.0f, 0.0f, 10.0f); + drift = drift * speedFactor; + } + mAnchor->SetDrift(drift); + + mAnchor->SetGearChanging(false); + ITransmission *itrans; + if (mVehicle->QueryInterface(&itrans)) { + int gear = itrans->GetGear(); + mAnchor->SetGearChanging(gear != mGear); + mGear = gear; + } + + mAnchor->SetCollisionDamping(mDampCollisionTime / mMaxCollisionTime); + mAnchor->SetGroundCollision(mGroundCollisionTime / mMaxCollisionTime); + mAnchor->SetObjectCollision(mObjectCollisionTime / mMaxCollisionTime); + + float zoom_input = bMax(mGameBreakerScale, mCinematicMomementTimer); + mAnchor->SetZoom(1.0f - zoom_input * 0.1f); + + mAnchor->Update(dT, mat, *mTarget.GetVelocity(), *mTarget.GetAcceleration()); + + unsigned int viewIndex = mViewID - 1; + if (viewIndex < 2) { + MaybeCameraShake(viewIndex, mAnchor->GetAcceleration()); + + for (IExplosion *const *iter = IExplosion::GetList().begin(); iter != IExplosion::GetList().end(); ++iter) { + IExplosion *explosion = *iter; + const UMath::Vector3 &pos = explosion->GetOrigin(); + bVector3 bpos; + eSwizzleWorldVector(reinterpret_cast(pos), bpos); + bVector3 dir; + bSub(&dir, &bpos, mMover->GetPosition()); + float distance = bLength(&dir); + + float radius = explosion->GetMaximumRadius(); + if (distance < radius + 20.0f && 0.0f < distance) { + bVector3 explosion_dir; + explosion_dir = bNormalize(dir); + float force = -explosion->GetExpansionSpeed() / dT; + bVector3 acc; + acc = bScale(explosion_dir, force); + MaybeCameraShake(mViewID - 1, &acc); + } + } + } + } +} + +bool CDActionDrive::GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) { + IBody *ibody; + if (!mVehicle) { + return false; + } + if (!mVehicle->QueryInterface(&ibody)) { + return false; + } + ibody->GetTransform(matrix); + ibody->GetLinearVelocity(velocity); + return true; +} + +void CDActionDrive::MessageJumpCut(const MJumpCut &message) { + if (mAnchor && message.GetAnchorWorldID() == mAnchor->GetWorldID()) { + static_cast(mMover)->SetSnapNext(); + } +} diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionDrive.hpp b/src/Speed/Indep/Src/Camera/Actions/CDActionDrive.hpp new file mode 100644 index 000000000..1a6e51a66 --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionDrive.hpp @@ -0,0 +1,64 @@ +#ifndef CAMERA_ACTIONS_CDACTIONDRIVE_H +#define CAMERA_ACTIONS_CDACTIONDRIVE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Interfaces/IAttachable.h" +#include "Speed/Indep/Src/Interfaces/IListener.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ITrafficCenter.h" +#include "Speed/Indep/Src/Misc/Hermes.h" +#include "Speed/Indep/Src/Sim/SimAttachable.h" +#include "Speed/Indep/Src/World/WorldConn.h" + +class MJumpCut; + +// total size: 0x80 +class CDActionDrive : public CameraAI::Action, public IAttachable, public Sim::Collision::IListener, public ITrafficCenter { + public: + const Attrib::StringKey &GetName() const override; + Attrib::StringKey GetNext() const override; + CameraMover *GetMover() override { return mMover; } + void SetSpecial(float val) override; + bool Attach(UTL::COM::IUnknown *pOther) override; + bool Detach(UTL::COM::IUnknown *pOther) override; + bool IsAttached(const UTL::COM::IUnknown *pOther) const override; + void OnAttached(IAttachable *pOther) override {} + const IAttachable::List *GetAttachments() const override; + + static CameraAI::Action *Construct(CameraAI::Director *director); + CDActionDrive(CameraAI::Director *director, IPlayer *player); + ~CDActionDrive() override; + void Reset() override; + void OnDetached(IAttachable *pOther) override; + void OnCarDetached(); + void OnCollision(const Sim::Collision::Info &cinfo) override; + void AquireCar(); + void Update(float dT) override; + bool GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) override; + void MessageJumpCut(const MJumpCut &message); + + private: + CameraMover *mMover; // offset 0x2C, size 0x4 + CameraMover *mRearViewMirrorMover; // offset 0x30, size 0x4 + CameraAnchor *mAnchor; // offset 0x34, size 0x4 + WorldConn::Reference mTarget; // offset 0x38, size 0x10 + IPlayer *mPlayer; // offset 0x48, size 0x4 + Sim::Attachments *mAttachments; // offset 0x4C, size 0x4 + IVehicle *mVehicle; // offset 0x50, size 0x4 + float mGameBreakerScale; // offset 0x54, size 0x4 + EVIEW_ID mViewID; // offset 0x58, size 0x4 + float mDampCollisionTime; // offset 0x5C, size 0x4 + float mGroundCollisionTime; // offset 0x60, size 0x4 + float mObjectCollisionTime; // offset 0x64, size 0x4 + float mMaxCollisionTime; // offset 0x68, size 0x4 + float mPulseTimer; // offset 0x6C, size 0x4 + float mCinematicMomementTimer; // offset 0x70, size 0x4 + bool mCinematicMomementTimerInc; // offset 0x74, size 0x1 + int mGear; // offset 0x78, size 0x4 + Hermes::HHANDLER mMsgJumpCut; // offset 0x7C, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionIce.cpp b/src/Speed/Indep/Src/Camera/Actions/CDActionIce.cpp index e69de29bb..a72798af8 100644 --- a/src/Speed/Indep/Src/Camera/Actions/CDActionIce.cpp +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionIce.cpp @@ -0,0 +1,268 @@ +#include "Speed/Indep/Src/Camera/Actions/CDActionIce.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Camera/ICE/ICEManager.hpp" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngine.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISuspension.h" + +extern ICEManager TheICEManager; +extern bool Tweak_ForceICEReplay; +extern bool Tweak_EnableICEAuthoring; +#include "Speed/Indep/Src/Camera/ICE/ICEMover.hpp" + +static UTL::COM::Factory::Prototype _CDActionIce("ICE", CDActionIce::Construct); + +const Attrib::StringKey &CDActionIce::GetName() const { + static Attrib::StringKey name("ICE"); + return name; +} + +Attrib::StringKey CDActionIce::GetNext() const { + if (mDone) { + return mPrev; + } + return Attrib::StringKey(""); +} + +CameraMover *CDActionIce::GetMover() { + return mMover; +} + +bool CDActionIce::Attach(UTL::COM::IUnknown *pOther) { + return mAttachments->Attach(pOther); +} + +bool CDActionIce::Detach(UTL::COM::IUnknown *pOther) { + return mAttachments->Detach(pOther); +} + +bool CDActionIce::IsAttached(const UTL::COM::IUnknown *pOther) const { + return mAttachments->IsAttached(pOther); +} + +const IAttachable::List *CDActionIce::GetAttachments() const { + return &mAttachments->GetList(); +} + +CameraAI::Action *CDActionIce::Construct(CameraAI::Director *director) { + IPlayer *player = nullptr; + const IPlayer::List &list = IPlayer::GetList(PLAYER_LOCAL); + for (IPlayer *const *iter = list.begin(); iter != list.end(); ++iter) { + IPlayer *ip = *iter; + if (ip->GetControllerPort() == static_cast(director->GetViewID())) { + player = ip; + break; + } + } + + if (!player) { + return nullptr; + } + + if (!player->GetSettings()) { + return nullptr; + } + + ISimable *isimable = player->GetSimable(); + if (!isimable) { + return nullptr; + } + + unsigned int world_id = isimable->GetWorldID(); + if (world_id == 0) { + return nullptr; + } + + if (TheICEManager.ChooseCameraPlaybackTrack() || Tweak_ForceICEReplay) { + return new (static_cast(0)) CDActionIce(director, player); + } + return nullptr; +} + +CDActionIce::CDActionIce(CameraAI::Director *director, IPlayer *player) + : CameraAI::Action(), // + IAttachable(this), // + mActionQ(1, 0x98c7a2f5, "ICE", false) { + mDone = false; + mPrev = Attrib::StringKey(); + mTrack = nullptr; + mTarget = WorldConn::Reference(0); + mPlayer = player; + mVehicle = nullptr; + + mAttachments = new Sim::Attachments(static_cast(this)); + mAttachments->Attach(mPlayer); + + CameraMover *m = director->GetMover(); + if (m) { + CameraAnchor *prevAnchor = m->GetAnchor(); + if (prevAnchor) { + // mPrev from prevAnchor + } + } + + mAnchor = new ICEAnchor(); + + AquireCar(); + + if (mTarget.IsValid()) { + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc = nullptr; + mVehicle->QueryInterface(&irbc); + if (irbc) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(cg)); + } + + mAnchor->Update(0.0f, *reinterpret_cast(mTarget.GetMatrix()), + *reinterpret_cast(mTarget.GetVelocity()), + *reinterpret_cast(mTarget.GetAcceleration())); + } + + mMover = new ICEMover(static_cast(director->GetViewID()), mAnchor); +} + +CDActionIce::~CDActionIce() { + TheICEManager.SetGenericCameraToPlay("", ""); + if (mPlayer) { + mAttachments->Detach(mPlayer); + } + ReleaseCar(true); + CameraMover *m = mMover; + delete m; + delete mAnchor; + delete mAttachments; +} + +void CDActionIce::OnDetached(IAttachable *pOther) { + if (ComparePtr(pOther, mPlayer)) { + mPlayer = nullptr; + } + if (ComparePtr(pOther, mVehicle)) { + ReleaseCar(false); + } +} + +void CDActionIce::ReleaseCar(bool detach) { + if (mVehicle) { + if (detach) { + Detach(mVehicle); + } + mVehicle = nullptr; + } + mTarget.Set(0); +} + +void CDActionIce::AquireCar() { + ISimable *isimable; + ITransmission *itrans; + ISuspension *isuspension; + + if (!mPlayer) { + return; + } + + if (!ComparePtr(mPlayer->GetSimable(), mVehicle)) { + ReleaseCar(true); + } + if (mVehicle) { + return; + } + + isimable = mPlayer->GetSimable(); + if (!isimable) { + return; + } + + mTarget.Set(isimable->GetWorldID()); + if (!mTarget.IsValid()) { + return; + } + + if (!isimable->QueryInterface(&mVehicle)) { + return; + } + + Attach(mVehicle); + + if (mVehicle->QueryInterface(&itrans)) { + mAnchor->SetTopSpeed(itrans->GetMaxSpeedometer()); + } + + if (mVehicle->QueryInterface(&isuspension)) { + mAnchor->SetNumWheels(isuspension->GetNumWheels() != 0); + } +} + +void CDActionIce::Update(float dT) { + while (!mActionQ.IsEmpty()) { + ActionRef aRef = mActionQ.GetAction(); + float data = aRef.Data(); + if (aRef.ID() == 0x15 && Tweak_EnableICEAuthoring) { + Tweak_EnableICEAuthoring = false; + mDone = true; + } + mActionQ.PopAction(); + } + + if (!mPlayer) { + ReleaseCar(true); + } else { + AquireCar(); + if (mTarget.IsValid()) { + bMatrix4 mat(*mTarget.GetMatrix()); + ICollisionBody *irbc; + ISimable *isimable; + float forward_slip; + ISuspension *isuspension; + + if (mVehicle && mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + } + + mAnchor->SetSlipAngle(0.0f); + if (mVehicle) { + mAnchor->SetSlipAngle(mVehicle->GetSlipAngle()); + } + + mAnchor->SetRPM(0.0f); + mAnchor->SetNosEngaged(false); + mAnchor->SetNosPercentageLeft(0.0f); + + if (mVehicle && mVehicle->QueryInterface(&isimable)) { + IEngine *iengine; + if (isimable->QueryInterface(&iengine)) { + mAnchor->SetRPM(iengine->GetRPM()); + mAnchor->SetNosEngaged(iengine->IsNOSEngaged()); + mAnchor->SetNosPercentageLeft(iengine->GetNOSCapacity()); + } + } + + forward_slip = 0.0f; + if (mVehicle && mVehicle->QueryInterface(&isuspension)) { + for (unsigned int i = 0; i < isuspension->GetNumWheels(); i++) { + if (isuspension->GetWheelSlip(i) > 0.0f) { + forward_slip += isuspension->GetWheelSlip(i); + } + } + } + + mAnchor->SetForwardSlip(forward_slip); + mAnchor->Update(dT, *reinterpret_cast(&mat), + *reinterpret_cast(mTarget.GetVelocity()), + *reinterpret_cast(mTarget.GetAcceleration())); + if (TheICEManager.IsEditorOff() && Tweak_ForceICEReplay) { + TheICEManager.ChooseReplayCamera(); + } + } + TheICEManager.Update(); + } +} diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionIce.hpp b/src/Speed/Indep/Src/Camera/Actions/CDActionIce.hpp new file mode 100644 index 000000000..7f7a3b9a9 --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionIce.hpp @@ -0,0 +1,53 @@ +#ifndef CAMERA_ACTIONS_CDACTIONICE_H +#define CAMERA_ACTIONS_CDACTIONICE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Input/ActionQueue.h" +#include "Speed/Indep/Src/Interfaces/IAttachable.h" +#include "Speed/Indep/Src/Sim/SimAttachable.h" +#include "Speed/Indep/Src/World/WorldConn.h" + +class ICEAnchor; +class ICEMover; +class ICETrack; + +// total size: 0x2F8 +class CDActionIce : public CameraAI::Action, public IAttachable { + public: + const Attrib::StringKey &GetName() const override; + Attrib::StringKey GetNext() const override; + CameraMover *GetMover() override; + void SetSpecial(float val) override {} + bool Attach(UTL::COM::IUnknown *pOther) override; + bool Detach(UTL::COM::IUnknown *pOther) override; + bool IsAttached(const UTL::COM::IUnknown *pOther) const override; + void OnAttached(IAttachable *pOther) override {} + const IAttachable::List *GetAttachments() const override; + + static CameraAI::Action *Construct(CameraAI::Director *director); + CDActionIce(CameraAI::Director *director, IPlayer *player); + ~CDActionIce() override; + void Reset() override {} + void OnDetached(IAttachable *pOther) override; + void ReleaseCar(bool detach); + void AquireCar(); + void Update(float dT) override; + + private: + ActionQueue mActionQ; // offset 0x20, size 0x294 + Attrib::StringKey mPrev; // offset 0x2B8, size 0x10 + ICEAnchor *mAnchor; // offset 0x2C8, size 0x4 + ICEMover *mMover; // offset 0x2CC, size 0x4 + WorldConn::Reference mTarget; // offset 0x2D0, size 0x10 + IPlayer *mPlayer; // offset 0x2E0, size 0x4 + Sim::Attachments *mAttachments; // offset 0x2E4, size 0x4 + IVehicle *mVehicle; // offset 0x2E8, size 0x4 + ICETrack *mTrack; // offset 0x2EC, size 0x4 + bool mDone; // offset 0x2F0, size 0x1 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionShowcase.cpp b/src/Speed/Indep/Src/Camera/Actions/CDActionShowcase.cpp index e69de29bb..9c7d8334f 100644 --- a/src/Speed/Indep/Src/Camera/Actions/CDActionShowcase.cpp +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionShowcase.cpp @@ -0,0 +1,271 @@ +#include "Speed/Indep/Src/Camera/Actions/CDActionShowcase.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Camera/Movers/Showcase.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" + +static bool IsRightSide() { + int playerRanking = 0; + int opponentRanking = 0; + UMath::Vector3 playerPos; + UMath::Vector3 opponentPos; + UMath::Vector3 playerFwd; + UMath::Vector3 opponentFwd; + + for (int onRacer = 0; onRacer < GRaceStatus::Get().GetRacerCount(); onRacer++) { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(onRacer); + ISimable *simable = racerInfo.GetSimable(); + UMath::Matrix4 matrix; + UMath::Vector3 velocity; + + simable->GetTransform(matrix); + simable->GetLinearVelocity(velocity); + + if (simable->IsPlayer()) { + playerRanking = racerInfo.GetRanking(); + if (UMath::LengthSquare(velocity) > 0.0f) { + UMath::Unit(velocity, playerFwd); + } else { + playerFwd = UMath::Vector4To3(matrix.v0); + } + playerPos = UMath::Vector4To3(matrix.v3); + } else { + opponentRanking = racerInfo.GetRanking(); + if (UMath::LengthSquare(velocity) > 0.0f) { + UMath::Unit(velocity, opponentFwd); + } else { + opponentFwd = UMath::Vector4To3(matrix.v0); + } + opponentPos = UMath::Vector4To3(matrix.v3); + } + } + + if (playerRanking == 0 || opponentRanking == 0) { + return false; + } + + UMath::Vector3 opponent2player; + UMath::Sub(opponentPos, playerPos, opponent2player); + opponent2player.y = 0.0f; + + if (UMath::LengthSquare(opponent2player) > 10000.0f) { + return false; + } + + UMath::Unit(opponent2player, opponent2player); + UMath::Vector3 up = {0.0f, 1.0f, 0.0f}; + UMath::Vector3 playerRight; + UMath::Cross(up, playerFwd, playerRight); + float dot = UMath::Dot(opponent2player, playerRight); + + bool rightSide = false; + if (playerRanking == 1) { + if (dot > 0.0f) { + rightSide = true; + } + } else if (playerRanking == 2) { + if (dot < 0.0f) { + rightSide = true; + } + } + return rightSide; +} + +static UTL::COM::Factory::Prototype _CDActionShowcase("SHOWCASE", CDActionShowcase::Construct); + +const Attrib::StringKey &CDActionShowcase::GetName() const { + static Attrib::StringKey name("SHOWCASE"); + return name; +} + +Attrib::StringKey CDActionShowcase::GetNext() const { + return Attrib::StringKey(); +} + +bool CDActionShowcase::Attach(UTL::COM::IUnknown *pOther) { + return mAttachments->Attach(pOther); +} + +bool CDActionShowcase::Detach(UTL::COM::IUnknown *pOther) { + return mAttachments->Detach(pOther); +} + +bool CDActionShowcase::IsAttached(const UTL::COM::IUnknown *pOther) const { + return mAttachments->IsAttached(pOther); +} + +const IAttachable::List *CDActionShowcase::GetAttachments() const { + return &mAttachments->GetList(); +} + +CameraAI::Action *CDActionShowcase::Construct(CameraAI::Director *director) { + IPlayer *player = nullptr; + int player_idx; + IPlayer *ip; + for (IPlayer *const *iter = IPlayer::GetList(PLAYER_LOCAL).begin(); iter != IPlayer::GetList(PLAYER_LOCAL).end(); ++iter) { + ip = *iter; + if (ip->GetControllerPort() == static_cast(director->GetViewID())) { + player = ip; + break; + } + } + + if (!player) { + goto null_return; + } + + if (!player->GetSettings()) { + goto null_return; + } + + { + ISimable *isimable = player->GetSimable(); + if (!isimable) { + goto null_return; + } + + unsigned int world_id = isimable->GetWorldID(); + CameraAI::Action *action = nullptr; + if (world_id != 0) { + action = new (static_cast(0)) CDActionShowcase(director, player); + } + return action; + } + +null_return: + return nullptr; +} + +CDActionShowcase::CDActionShowcase(CameraAI::Director *director, IPlayer *player) + : CameraAI::Action(), // + IAttachable(this), // + mTarget(0) { + mPlayer = player; + mVehicle = nullptr; + mViewID = director->GetViewID(); + + mAttachments = new Sim::Attachments(static_cast(this)); + mAttachments->Attach(mPlayer); + + mAnchor = new (static_cast(0), 0) CameraAnchor(0); + + AquireCar(); + + if (mTarget.IsValid()) { + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc = nullptr; + if (mVehicle && mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + } + + mAnchor->Update(0.0f, mat, *mTarget.GetVelocity(), *mTarget.GetAcceleration()); + } + + mMover = new (static_cast(0), 0) ShowcaseCameraMover(static_cast(director->GetViewID()), mAnchor, IsRightSide()); + mMover->ResetState(); +} + +CDActionShowcase::~CDActionShowcase() { + if (mPlayer) { + mAttachments->Detach(mPlayer); + } + if (mVehicle) { + mAttachments->Detach(mVehicle); + } + CameraMover *m = mMover; + delete m; + delete mAnchor; + delete mAttachments; +} + +void CDActionShowcase::OnDetached(IAttachable *pOther) { + if (ComparePtr(pOther, mPlayer)) { + mPlayer = nullptr; + } + if (ComparePtr(pOther, mVehicle)) { + OnCarDetached(); + } +} + +void CDActionShowcase::OnCarDetached() { + if (mTarget.IsValid()) { + mTarget.Set(0); + } + if (mAnchor) { + mAnchor->SetWorldID(0); + } + mVehicle = nullptr; +} + +void CDActionShowcase::AquireCar() { + ISimable *isimable; + + if (!mPlayer) { + return; + } + + if (!ComparePtr(mPlayer->GetSimable(), mVehicle)) { + if (mVehicle) { + Detach(mVehicle); + mVehicle = nullptr; + } + } + if (mVehicle) { + return; + } + isimable = mPlayer->GetSimable(); + if (isimable) { + mTarget.Set(isimable->GetWorldID()); + if (mTarget.IsValid()) { + if (isimable->QueryInterface(&mVehicle)) { + Attach(mVehicle); + CameraAnchor *anchor = mAnchor; + const char *model_str = mVehicle->GetVehicleAttributes().MODEL().GetString(); + if (!model_str) { + model_str = ""; + } + anchor->SetModel(bStringHash(model_str)); + mAnchor->SetWorldID(mTarget.GetWorldID()); + } + } + } +} + +void CDActionShowcase::Update(float dT) { + if (!mPlayer) { + if (mVehicle) { + Detach(mVehicle); + mVehicle = nullptr; + } + return; + } + + AquireCar(); + if (!mTarget.IsValid()) { + return; + } + + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc; + if (mVehicle) { + if (mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + cg.x = 0.0f; + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + } + } + + mAnchor->Update(dT, mat, *mTarget.GetVelocity(), *mTarget.GetAcceleration()); +} diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionShowcase.hpp b/src/Speed/Indep/Src/Camera/Actions/CDActionShowcase.hpp new file mode 100644 index 000000000..03ef57706 --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionShowcase.hpp @@ -0,0 +1,45 @@ +#ifndef CAMERA_ACTIONS_CDACTIONSHOWCASE_H +#define CAMERA_ACTIONS_CDACTIONSHOWCASE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Interfaces/IAttachable.h" +#include "Speed/Indep/Src/Sim/SimAttachable.h" +#include "Speed/Indep/Src/World/WorldConn.h" + +// total size: 0x48 +class CDActionShowcase : public CameraAI::Action, public IAttachable { + public: + const Attrib::StringKey &GetName() const override; + Attrib::StringKey GetNext() const override; + CameraMover *GetMover() override { return mMover; } + void SetSpecial(float val) override {} + bool Attach(UTL::COM::IUnknown *pOther) override; + bool Detach(UTL::COM::IUnknown *pOther) override; + bool IsAttached(const UTL::COM::IUnknown *pOther) const override; + void OnAttached(IAttachable *pOther) override {} + const IAttachable::List *GetAttachments() const override; + + static CameraAI::Action *Construct(CameraAI::Director *director); + CDActionShowcase(CameraAI::Director *director, IPlayer *player); + ~CDActionShowcase() override; + void Reset() override {} + void OnDetached(IAttachable *pOther) override; + void OnCarDetached(); + void AquireCar(); + void Update(float dT) override; + + private: + CameraMover *mMover; // offset 0x20, size 0x4 + CameraAnchor *mAnchor; // offset 0x24, size 0x4 + WorldConn::Reference mTarget; // offset 0x28, size 0x10 + IPlayer *mPlayer; // offset 0x38, size 0x4 + Sim::Attachments *mAttachments; // offset 0x3C, size 0x4 + IVehicle *mVehicle; // offset 0x40, size 0x4 + EVIEW_ID mViewID; // offset 0x44, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCar.cpp b/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCar.cpp index e69de29bb..e964a67b1 100644 --- a/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCar.cpp +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCar.cpp @@ -0,0 +1,222 @@ +#include "Speed/Indep/Src/Camera/Actions/CDActionTrackCar.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Interfaces/IBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ITransmission.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" + +static UTL::COM::Factory::Prototype _CDActionTrackCar("TRACKCAR", CDActionTrackCar::Construct); + +const Attrib::StringKey &CDActionTrackCar::GetName() const { + static Attrib::StringKey name("TRACKCAR"); + return name; +} + +Attrib::StringKey CDActionTrackCar::GetNext() const { + return Attrib::StringKey(); +} + +bool CDActionTrackCar::Attach(UTL::COM::IUnknown *pOther) { + return mAttachments->Attach(pOther); +} + +bool CDActionTrackCar::Detach(UTL::COM::IUnknown *pOther) { + return mAttachments->Detach(pOther); +} + +bool CDActionTrackCar::IsAttached(const UTL::COM::IUnknown *pOther) const { + return mAttachments->IsAttached(pOther); +} + +const IAttachable::List *CDActionTrackCar::GetAttachments() const { + return &mAttachments->GetList(); +} + +CameraAI::Action *CDActionTrackCar::Construct(CameraAI::Director *director) { + IPlayer *player = nullptr; + int player_idx; + IPlayer *ip; + for (IPlayer *const *iter = IPlayer::GetList(PLAYER_LOCAL).begin(); iter != IPlayer::GetList(PLAYER_LOCAL).end(); ++iter) { + ip = *iter; + if (ip->GetControllerPort() == static_cast(director->GetViewID())) { + player = ip; + break; + } + } + + if (!player) { + goto null_return; + } + + if (!player->GetSettings()) { + goto null_return; + } + + { + ISimable *isimable = player->GetSimable(); + if (!isimable) { + goto null_return; + } + + unsigned int world_id = isimable->GetWorldID(); + CameraAI::Action *action = nullptr; + if (world_id != 0) { + action = new (static_cast(0)) CDActionTrackCar(director, player); + } + return action; + } + +null_return: + return nullptr; +} + +CDActionTrackCar::CDActionTrackCar(CameraAI::Director *director, IPlayer *player) + : CameraAI::Action(), // + IAttachable(this), // + mTarget(0) { + mPlayer = player; + mVehicle = nullptr; + mViewID = director->GetViewID(); + + mAttachments = new Sim::Attachments(static_cast(this)); + mAttachments->Attach(mPlayer); + + mAnchor = new (static_cast(0), 0) CameraAnchor(0); + + AquireCar(); + + if (mTarget.IsValid()) { + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc = nullptr; + if (mVehicle && mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + } + + mAnchor->Update(0.0f, mat, *mTarget.GetVelocity(), *mTarget.GetAcceleration()); + } + + mMover = new (static_cast(0), 0) TrackCarCameraMover(static_cast(director->GetViewID()), mAnchor, true); + mMover->ResetState(); +} + +CDActionTrackCar::~CDActionTrackCar() { + if (mPlayer) { + mAttachments->Detach(mPlayer); + } + if (mVehicle) { + mAttachments->Detach(mVehicle); + } + CameraMover *m = mMover; + delete m; + delete mAnchor; + delete mAttachments; +} + +void CDActionTrackCar::OnDetached(IAttachable *pOther) { + if (ComparePtr(pOther, mPlayer)) { + mPlayer = nullptr; + } + if (ComparePtr(pOther, mVehicle)) { + OnCarDetached(); + } +} + +void CDActionTrackCar::OnCarDetached() { + if (mTarget.IsValid()) { + mTarget.Set(0); + } + if (mAnchor) { + mAnchor->SetWorldID(0); + } + mVehicle = nullptr; +} + +void CDActionTrackCar::AquireCar() { + ISimable *isimable; + + if (!mPlayer) { + return; + } + + if (!ComparePtr(mPlayer->GetSimable(), mVehicle)) { + if (mVehicle) { + Detach(mVehicle); + mVehicle = nullptr; + } + } + if (mVehicle) { + return; + } + isimable = mPlayer->GetSimable(); + if (isimable) { + mTarget.Set(isimable->GetWorldID()); + if (mTarget.IsValid()) { + if (isimable->QueryInterface(&mVehicle)) { + Attach(mVehicle); + CameraAnchor *anchor = mAnchor; + const char *model_str = mVehicle->GetVehicleAttributes().MODEL().GetString(); + if (!model_str) { + model_str = ""; + } + anchor->SetModel(bStringHash(model_str)); + mAnchor->SetWorldID(mTarget.GetWorldID()); + ITransmission *itrans; + if (mVehicle->QueryInterface(&itrans)) { + mAnchor->SetTopSpeed(itrans->GetMaxSpeedometer()); + } + } + } + } +} + +void CDActionTrackCar::Update(float dT) { + if (!mPlayer) { + if (mVehicle) { + Detach(mVehicle); + mVehicle = nullptr; + } + return; + } + + AquireCar(); + if (!mTarget.IsValid()) { + return; + } + + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc; + if (mVehicle) { + if (mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + cg.x = 0.0f; + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + } + } + + mAnchor->Update(dT, mat, *mTarget.GetVelocity(), *mTarget.GetAcceleration()); +} + +bool CDActionTrackCar::GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) { + IBody *ibody; + if (!mVehicle) { + return false; + } + if (!mVehicle->QueryInterface(&ibody)) { + return false; + } + ibody->GetTransform(matrix); + ibody->GetLinearVelocity(velocity); + return true; +} diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCar.hpp b/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCar.hpp new file mode 100644 index 000000000..0929bef5b --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCar.hpp @@ -0,0 +1,47 @@ +#ifndef CAMERA_ACTIONS_CDACTIONTRACKCAR_H +#define CAMERA_ACTIONS_CDACTIONTRACKCAR_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Interfaces/IAttachable.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ITrafficCenter.h" +#include "Speed/Indep/Src/Sim/SimAttachable.h" +#include "Speed/Indep/Src/World/WorldConn.h" + +// total size: 0x50 +class CDActionTrackCar : public CameraAI::Action, public IAttachable, public ITrafficCenter { + public: + const Attrib::StringKey &GetName() const override; + Attrib::StringKey GetNext() const override; + CameraMover *GetMover() override { return mMover; } + void SetSpecial(float val) override {} + bool Attach(UTL::COM::IUnknown *pOther) override; + bool Detach(UTL::COM::IUnknown *pOther) override; + bool IsAttached(const UTL::COM::IUnknown *pOther) const override; + void OnAttached(IAttachable *pOther) override {} + const IAttachable::List *GetAttachments() const override; + + static CameraAI::Action *Construct(CameraAI::Director *director); + CDActionTrackCar(CameraAI::Director *director, IPlayer *player); + ~CDActionTrackCar() override; + void Reset() override {} + void OnDetached(IAttachable *pOther) override; + void OnCarDetached(); + void AquireCar(); + void Update(float dT) override; + bool GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) override; + + private: + CameraMover *mMover; // offset 0x28, size 0x4 + CameraAnchor *mAnchor; // offset 0x2C, size 0x4 + WorldConn::Reference mTarget; // offset 0x30, size 0x10 + IPlayer *mPlayer; // offset 0x40, size 0x4 + Sim::Attachments *mAttachments; // offset 0x44, size 0x4 + IVehicle *mVehicle; // offset 0x48, size 0x4 + EVIEW_ID mViewID; // offset 0x4C, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCop.cpp b/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCop.cpp index e69de29bb..5d038130b 100644 --- a/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCop.cpp +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCop.cpp @@ -0,0 +1,230 @@ +#include "Speed/Indep/Src/Camera/Actions/CDActionTrackCop.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Interfaces/IBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ITransmission.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" + +static UTL::COM::Factory::Prototype _CDActionTrackCop("TRACKCOP", CDActionTrackCop::Construct); + +const Attrib::StringKey &CDActionTrackCop::GetName() const { + static Attrib::StringKey name("TRACKCOP"); + return name; +} + +Attrib::StringKey CDActionTrackCop::GetNext() const { + return Attrib::StringKey(); +} + +bool CDActionTrackCop::Attach(UTL::COM::IUnknown *pOther) { + return mAttachments->Attach(pOther); +} + +bool CDActionTrackCop::Detach(UTL::COM::IUnknown *pOther) { + return mAttachments->Detach(pOther); +} + +bool CDActionTrackCop::IsAttached(const UTL::COM::IUnknown *pOther) const { + return mAttachments->IsAttached(pOther); +} + +const IAttachable::List *CDActionTrackCop::GetAttachments() const { + return &mAttachments->GetList(); +} + +CameraAI::Action *CDActionTrackCop::Construct(CameraAI::Director *director) { + IPlayer *player = nullptr; + int player_idx; + IPlayer *ip; + for (IPlayer *const *iter = IPlayer::GetList(PLAYER_LOCAL).begin(); iter != IPlayer::GetList(PLAYER_LOCAL).end(); ++iter) { + ip = *iter; + if (ip->GetControllerPort() == static_cast(director->GetViewID())) { + player = ip; + break; + } + } + + if (!player) { + goto null_return; + } + + if (!player->GetSettings()) { + goto null_return; + } + + { + ISimable *isimable = player->GetSimable(); + if (!isimable) { + goto null_return; + } + + unsigned int world_id = isimable->GetWorldID(); + CameraAI::Action *action = nullptr; + if (world_id != 0) { + action = new (static_cast(0)) CDActionTrackCop(director, player); + } + return action; + } + +null_return: + return nullptr; +} + +CDActionTrackCop::CDActionTrackCop(CameraAI::Director *director, IPlayer *player) + : CameraAI::Action(), // + IAttachable(this), // + mTarget(0) { + mPlayer = player; + mVehicle = nullptr; + bool renderCarPOV = true; + mViewID = director->GetViewID(); + + mAttachments = new Sim::Attachments(static_cast(this)); + mAttachments->Attach(mPlayer); + + CameraMover *m = director->GetMover(); + if (m) { + renderCarPOV = m->RenderCarPOV(); + } + + mAnchor = new (static_cast(0), 0) CameraAnchor(0); + + AquireCar(); + + if (mTarget.IsValid()) { + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc = nullptr; + if (mVehicle && mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + } + + mAnchor->Update(0.0f, mat, *mTarget.GetVelocity(), *mTarget.GetAcceleration()); + } + + mMover = new (static_cast(0), 0) TrackCopCameraMover(static_cast(director->GetViewID()), mAnchor, false); + mMover->ResetState(); + static_cast(mMover)->SetRenderCarPOV(renderCarPOV); +} + +CDActionTrackCop::~CDActionTrackCop() { + if (mPlayer) { + mAttachments->Detach(mPlayer); + } + if (mVehicle) { + mAttachments->Detach(mVehicle); + } + CameraMover *m = mMover; + delete m; + delete mAnchor; + delete mAttachments; +} + +void CDActionTrackCop::OnDetached(IAttachable *pOther) { + if (ComparePtr(pOther, mPlayer)) { + mPlayer = nullptr; + } + if (ComparePtr(pOther, mVehicle)) { + OnCarDetached(); + } +} + +void CDActionTrackCop::OnCarDetached() { + if (mTarget.IsValid()) { + mTarget.Set(0); + } + if (mAnchor) { + mAnchor->SetWorldID(0); + } + mVehicle = nullptr; +} + +void CDActionTrackCop::AquireCar() { + ISimable *isimable; + + if (!mPlayer) { + return; + } + + if (!ComparePtr(mPlayer->GetSimable(), mVehicle)) { + if (mVehicle) { + Detach(mVehicle); + mVehicle = nullptr; + } + } + if (mVehicle) { + return; + } + isimable = mPlayer->GetSimable(); + if (isimable) { + mTarget.Set(isimable->GetWorldID()); + if (mTarget.IsValid()) { + if (isimable->QueryInterface(&mVehicle)) { + Attach(mVehicle); + CameraAnchor *anchor = mAnchor; + const char *model_str = mVehicle->GetVehicleAttributes().MODEL().GetString(); + if (!model_str) { + model_str = ""; + } + anchor->SetModel(bStringHash(model_str)); + mAnchor->SetWorldID(mTarget.GetWorldID()); + ITransmission *itrans; + if (mVehicle->QueryInterface(&itrans)) { + mAnchor->SetTopSpeed(itrans->GetMaxSpeedometer()); + } + } + } + } +} + +void CDActionTrackCop::Update(float dT) { + if (!mPlayer) { + if (mVehicle) { + Detach(mVehicle); + mVehicle = nullptr; + } + return; + } + + AquireCar(); + if (!mTarget.IsValid()) { + return; + } + + bMatrix4 mat(*mTarget.GetMatrix()); + + ICollisionBody *irbc; + if (mVehicle) { + if (mVehicle->QueryInterface(&irbc)) { + IRigidBody *irb = mVehicle->GetSimable()->GetRigidBody(); + UVector3 cg(irbc->GetCenterOfGravity()); + cg.x = 0.0f; + irb->ConvertLocalToWorld(cg, false); + cg += irb->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(cg), reinterpret_cast(mat.v3)); + } + } + + mAnchor->Update(dT, mat, *mTarget.GetVelocity(), *mTarget.GetAcceleration()); +} + +bool CDActionTrackCop::GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) { + IBody *ibody; + if (!mVehicle) { + return false; + } + if (!mVehicle->QueryInterface(&ibody)) { + return false; + } + ibody->GetTransform(matrix); + ibody->GetLinearVelocity(velocity); + return true; +} diff --git a/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCop.hpp b/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCop.hpp new file mode 100644 index 000000000..432d2b10d --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Actions/CDActionTrackCop.hpp @@ -0,0 +1,47 @@ +#ifndef CAMERA_ACTIONS_CDACTIONTRACKCOP_H +#define CAMERA_ACTIONS_CDACTIONTRACKCOP_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Interfaces/IAttachable.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ITrafficCenter.h" +#include "Speed/Indep/Src/Sim/SimAttachable.h" +#include "Speed/Indep/Src/World/WorldConn.h" + +// total size: 0x50 +class CDActionTrackCop : public CameraAI::Action, public IAttachable, public ITrafficCenter { + public: + const Attrib::StringKey &GetName() const override; + Attrib::StringKey GetNext() const override; + CameraMover *GetMover() override { return mMover; } + void SetSpecial(float val) override {} + bool Attach(UTL::COM::IUnknown *pOther) override; + bool Detach(UTL::COM::IUnknown *pOther) override; + bool IsAttached(const UTL::COM::IUnknown *pOther) const override; + void OnAttached(IAttachable *pOther) override {} + const IAttachable::List *GetAttachments() const override; + + static CameraAI::Action *Construct(CameraAI::Director *director); + CDActionTrackCop(CameraAI::Director *director, IPlayer *player); + ~CDActionTrackCop() override; + void Reset() override {} + void OnDetached(IAttachable *pOther) override; + void OnCarDetached(); + void AquireCar(); + void Update(float dT) override; + bool GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) override; + + private: + CameraMover *mMover; // offset 0x28, size 0x4 + CameraAnchor *mAnchor; // offset 0x2C, size 0x4 + WorldConn::Reference mTarget; // offset 0x30, size 0x10 + IPlayer *mPlayer; // offset 0x40, size 0x4 + Sim::Attachments *mAttachments; // offset 0x44, size 0x4 + IVehicle *mVehicle; // offset 0x48, size 0x4 + EVIEW_ID mViewID; // offset 0x4C, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Camera.cpp b/src/Speed/Indep/Src/Camera/Camera.cpp index e69de29bb..4a2e59567 100644 --- a/src/Speed/Indep/Src/Camera/Camera.cpp +++ b/src/Speed/Indep/Src/Camera/Camera.cpp @@ -0,0 +1,224 @@ +#include "Camera.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bFunk.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void UpdateCameraMovers(float dT); +void UpdateCameraShakers(float dT); + +extern int DisableCommunication; +extern int JR2ServerExists; + +int Camera::StopUpdating; +volatile JollyRancherResponsePacket Camera::JollyRancherResponse; + +static unsigned short aBaselineFovNoise = 0x2AAA; +static int cameralink; + +Camera::Camera() { + bMatrix4 m; + + m.v0 = bVector4(1.0f, 0.0f, 0.0f, 0.0f); + m.v1 = bVector4(0.0f, -1.0f, 0.0f, 0.0f); + m.v2 = bVector4(0.0f, 0.0f, -1.0f, 100.0f); + m.v3 = bVector4(0.0f, 0.0f, 1200.0f, 1.0f); + + LastUpdateTime = 0x80000000; + LastDisparateTime = RealTimeFrames; + RenderDash = 0; + CurrentKey.TargetDistance = 10.0f; + CurrentKey.NearZ = 0.5f; + bClearVelocity = false; + CurrentKey.FocalDistance = 0.0f; + CurrentKey.DepthOfField = 0.0f; + ElapsedTime = 1.0f; + CurrentKey.FarZ = 10000.0f; + CurrentKey.FieldOfView = 0x36FB; + CurrentKey.NoiseAmplitude2.w = 0.0f; + CurrentKey.LB_height = 0.0f; + CurrentKey.NoiseAmplitude1.x = 0.0f; + CurrentKey.NoiseAmplitude1.y = 0.0f; + CurrentKey.NoiseAmplitude1.z = 0.0f; + CurrentKey.NoiseAmplitude1.w = 0.0f; + CurrentKey.SimTimeMultiplier = 1.0f; + CurrentKey.NoiseFrequency1.x = 1.0f; + CurrentKey.NoiseFrequency1.y = 1.0f; + CurrentKey.NoiseFrequency1.z = 1.0f; + CurrentKey.NoiseFrequency1.w = 1.0f; + CurrentKey.NoiseFrequency2.x = 1.0f; + CurrentKey.NoiseFrequency2.y = 1.0f; + CurrentKey.NoiseFrequency2.z = 1.0f; + CurrentKey.NoiseFrequency2.w = 1.0f; + CurrentKey.NoiseAmplitude2.x = 0.0f; + CurrentKey.NoiseAmplitude2.y = 0.0f; + CurrentKey.NoiseAmplitude2.z = 0.0f; + SetCameraMatrix(m, 1.0f); + SetCameraMatrix(m, 1.0f); +} + +void Camera::UpdateAll(float dT) { + UpdateCameraMovers(dT); + UpdateCameraShakers(dT); +} + +float NoiseBase(int x) { + int n = x << 13 ^ x; + return 1.0f - static_cast((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) * 0.0000000009313226f; +} + +float NoiseInterpolated(float x) { + int a = static_cast(bFloor(x)); + float t = NoiseBase(a); + float s = NoiseBase(a + 1); + float f = x - static_cast(a); + + return f * s + (1.0f - f) * t; +} + +float Noise(float x) { + float total = 0.0f; + float amplitude = 1.0f; + float frequency = 1.0f; + + for (int i = 0; i <= 5; i++) { + total += amplitude * NoiseInterpolated(x * frequency); + frequency += frequency; + amplitude *= 0.5f; + } + + return total; +} + +unsigned short Camera::FovRelativeAngle(unsigned short angle) { + float sine = bSin(angle); + float fov_sine = bSin(static_cast(CurrentKey.FieldOfView >> 1)); + float baseline_sine = bSin(static_cast(aBaselineFovNoise >> 1)); + + return bASin((sine * fov_sine) / baseline_sine); +} + +void Camera::CommunicateWithJollyRancher(char *cameraname) { + if (DisableCommunication == 0) { + char data[96]; + void *addr = const_cast(&JollyRancherResponse); + int protocol = DisableCommunication; + + bMemCpy(&data[0], &addr, 4); + bMemCpy(&data[4], &protocol, 4); + bMatrix4 scaledmatrix(*reinterpret_cast(this)); + scaledmatrix.v3.x *= 100.0f; + scaledmatrix.v3.y *= 100.0f; + scaledmatrix.v3.z *= 100.0f; + scaledmatrix.v3.w = 1.0f; + bMemCpy(&data[8], &scaledmatrix, 0x40); + bStrCpy(&data[72], cameraname); + bFunkCallASync("JR2Server", 1, data, 0x60); + } +} + +void Camera::SetCameraMatrix(const bMatrix4 &m, float fTime) { + if (StopUpdating != 0) { + return; + } + + bMemCpy(&PreviousKey, &CurrentKey, sizeof(CameraParams)); + ElapsedTime = fTime; + + if (JollyRancherResponse.UseMatrix != 0 && DisableCommunication == 0) { + if (cameralink == 0) { + cameralink = 1; + } + + bMatrix4 scaledmatrix; + bMemCpy(&scaledmatrix, const_cast(&JollyRancherResponse.CamMatrix), sizeof(bMatrix4)); + scaledmatrix.v3.x *= 0.01f; + scaledmatrix.v3.y *= 0.01f; + scaledmatrix.v3.z *= 0.01f; + scaledmatrix.v3.w = 1.0f; + bCopy(reinterpret_cast(&CurrentKey.Matrix), reinterpret_cast(&scaledmatrix)); + } else { + if (cameralink != 0) { + cameralink = 0; + } + + bCopy(reinterpret_cast(&CurrentKey.Matrix), reinterpret_cast(&m)); + } + + bMatrix4 t; + eTransposeMatrix(&t, &CurrentKey.Matrix); + eMulVector(&CurrentKey.Position, &t, reinterpret_cast(&CurrentKey.Matrix.v3)); + CurrentKey.Position.x = -CurrentKey.Position.x; + CurrentKey.Position.y = -CurrentKey.Position.y; + CurrentKey.Position.z = -CurrentKey.Position.z; + bNormalize(&CurrentKey.Direction, reinterpret_cast(&t.v2)); + bScale(&CurrentKey.Target, &CurrentKey.Direction, CurrentKey.TargetDistance); + CurrentKey.Target += CurrentKey.Position; + + if (bClearVelocity) { + bClearVelocity = false; + bMemCpy(&PreviousKey, &CurrentKey, sizeof(CameraParams)); + ElapsedTime = 1.0f; + } + + if (ElapsedTime > 0.0f) { + float fTimeRecip = 1.0f / ElapsedTime; + + VelocityKey.Position = CurrentKey.Position - PreviousKey.Position; + VelocityKey.Position *= fTimeRecip; + + VelocityKey.Direction = CurrentKey.Direction - PreviousKey.Direction; + VelocityKey.Direction *= fTimeRecip; + + VelocityKey.Target = CurrentKey.Target - PreviousKey.Target; + VelocityKey.Target *= fTimeRecip; + + VelocityKey.FieldOfView = CurrentKey.FieldOfView - PreviousKey.FieldOfView; + VelocityKey.FieldOfView = static_cast(static_cast(fTimeRecip * static_cast(VelocityKey.FieldOfView))); + + VelocityKey.TargetDistance = (CurrentKey.TargetDistance - PreviousKey.TargetDistance) * fTimeRecip; + VelocityKey.FocalDistance = (CurrentKey.FocalDistance - PreviousKey.FocalDistance) * fTimeRecip; + VelocityKey.DepthOfField = (CurrentKey.DepthOfField - PreviousKey.DepthOfField) * fTimeRecip; + VelocityKey.NearZ = (CurrentKey.NearZ - PreviousKey.NearZ) * fTimeRecip; + VelocityKey.FarZ = (CurrentKey.FarZ - PreviousKey.FarZ) * fTimeRecip; + VelocityKey.LB_height = (CurrentKey.LB_height - PreviousKey.LB_height) * fTimeRecip; + VelocityKey.SimTimeMultiplier = (CurrentKey.SimTimeMultiplier - PreviousKey.SimTimeMultiplier) * fTimeRecip; + + VelocityKey.NoiseFrequency1 = CurrentKey.NoiseFrequency1 - PreviousKey.NoiseFrequency1; + VelocityKey.NoiseFrequency1 *= fTimeRecip; + + VelocityKey.NoiseFrequency2 = CurrentKey.NoiseFrequency2 - PreviousKey.NoiseFrequency2; + VelocityKey.NoiseFrequency2 *= fTimeRecip; + + VelocityKey.NoiseAmplitude1 = CurrentKey.NoiseAmplitude1 - PreviousKey.NoiseAmplitude1; + VelocityKey.NoiseAmplitude1 *= fTimeRecip; + + VelocityKey.NoiseAmplitude2 = CurrentKey.NoiseAmplitude2 - PreviousKey.NoiseAmplitude2; + VelocityKey.NoiseAmplitude2 *= fTimeRecip; + } +} + +void Camera::ApplyNoise(bMatrix4 *p_matrix, float time, float intensity) { + bVector4 v(CurrentKey.NoiseFrequency1); + bScale(&v, &v, time); + bVector4 v1(Noise(v.x), Noise(v.y), Noise(v.z), Noise(v.w)); + bScale(&v1, &v1, &CurrentKey.NoiseAmplitude1); + + v = CurrentKey.NoiseFrequency2; + bScale(&v, &v, time); + bVector4 v2(Noise(v.x), Noise(v.y), Noise(v.z), Noise(v.w)); + bScale(&v2, &v2, &CurrentKey.NoiseAmplitude2); + + bVector4 v_noise; + bAdd(&v_noise, &v1, &v2); + bScale(&v_noise, &v_noise, intensity); + + bMatrix4 m; + bIdentity(&m); + m.v3.x = v_noise.x; + m.v3.y = v_noise.y; + eRotateX(&m, &m, FovRelativeAngle(bDegToAng(v_noise.z))); + eRotateY(&m, &m, FovRelativeAngle(bDegToAng(v_noise.w))); + eMulMatrix(p_matrix, p_matrix, &m); +} diff --git a/src/Speed/Indep/Src/Camera/Camera.hpp b/src/Speed/Indep/Src/Camera/Camera.hpp index 835f62948..98923fe66 100644 --- a/src/Speed/Indep/Src/Camera/Camera.hpp +++ b/src/Speed/Indep/Src/Camera/Camera.hpp @@ -8,6 +8,16 @@ #include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +extern int RealTimeFrames; + +struct JollyRancherResponsePacket { + volatile int UseMatrix; // offset 0x0, size 0x4 + volatile int Pad1; // offset 0x4, size 0x4 + volatile int Pad2; // offset 0x8, size 0x4 + volatile int Pad3; // offset 0xC, size 0x4 + volatile bMatrix4 CamMatrix; // offset 0x10, size 0x40 +}; + struct CameraParams { // total size: 0xD4 bMatrix4 Matrix; // offset 0x0, size 0x40 @@ -32,8 +42,17 @@ struct CameraParams { // total size: 0x290 class Camera { public: + static int StopUpdating; + static volatile JollyRancherResponsePacket JollyRancherResponse; + + Camera(); + static void UpdateAll(float dT); + void CommunicateWithJollyRancher(char *cameraname); + void SetCameraMatrix(const bMatrix4 &m, float fTime); + void ApplyNoise(bMatrix4 *p_matrix, float time, float intensity); + bMatrix4 *GetCameraMatrix() { return &this->CurrentKey.Matrix; } @@ -47,6 +66,9 @@ class Camera { // float GetDepthOfField() {} // unsigned short GetFieldOfView() {} + unsigned short GetFieldOfView() { + return CurrentKey.FieldOfView; + } // bMatrix4 *GetWorldToCameraMatrix() {} @@ -58,9 +80,15 @@ class Camera { return &this->CurrentKey.Direction; } - // bVector3 *GetTarget() {} + bVector3 *GetTarget() { + return &CurrentKey.Target; + } - // unsigned short GetFov() {} + unsigned short GetFov() { + return CurrentKey.FieldOfView; + } + + unsigned short FovRelativeAngle(unsigned short angle); bVector3 GetPositionSimSpace() { bVector3 vec(CurrentKey.Position); @@ -77,65 +105,143 @@ class Camera { // unsigned short GetPreviousFov() {} - // bVector3 *GetVelocityPosition() {} + bVector3 *GetVelocityPosition() { + return &VelocityKey.Position; + } - // bVector3 *GetVelocityDirection() {} + bVector3 *GetVelocityDirection() { + return &VelocityKey.Direction; + } - // bVector3 *GetVelocityTarget() {} + bVector3 *GetVelocityTarget() { + return &VelocityKey.Target; + } // unsigned short GetVelocityFov() {} + unsigned short GetVelocityFov() { + return VelocityKey.FieldOfView; + } // unsigned int GetLastDisparateTime() {} - void ClearVelocity() {} + void ClearVelocity() { + bClearVelocity = true; + LastDisparateTime = RealTimeFrames; + } - void SetRenderDash(int r) {} + void SetRenderDash(int r) { + if (!StopUpdating) { + RenderDash = r; + } + } - void SetTargetDistance(float f) {} + void SetTargetDistance(float f) { + if (!StopUpdating) { + CurrentKey.TargetDistance = f; + } + } - void SetFocalDistance(float f) {} + void SetFocalDistance(float f) { + if (!StopUpdating) { + CurrentKey.FocalDistance = f; + } + } - void SetDepthOfField(float f) {} + void SetDepthOfField(float f) { + if (!StopUpdating) { + CurrentKey.DepthOfField = f; + } + } - void SetFieldOfView(unsigned short fov) {} + void SetFieldOfView(unsigned short fov) { + if (!StopUpdating) { + CurrentKey.FieldOfView = fov; + } + } - void SetNoiseFrequency1(float x, float y, float z, float w) {} + void SetNoiseFrequency1(float x, float y, float z, float w) { + CurrentKey.NoiseFrequency1.x = x; + CurrentKey.NoiseFrequency1.y = y; + CurrentKey.NoiseFrequency1.z = z; + CurrentKey.NoiseFrequency1.w = w; + } - void SetNoiseFrequency2(float x, float y, float z, float w) {} + void SetNoiseFrequency2(float x, float y, float z, float w) { + CurrentKey.NoiseFrequency2.x = x; + CurrentKey.NoiseFrequency2.y = y; + CurrentKey.NoiseFrequency2.z = z; + CurrentKey.NoiseFrequency2.w = w; + } - void SetNoiseAmplitude1(float x, float y, float z, float w) {} + void SetNoiseAmplitude1(float x, float y, float z, float w) { + CurrentKey.NoiseAmplitude1.x = x; + CurrentKey.NoiseAmplitude1.y = y; + CurrentKey.NoiseAmplitude1.z = z; + CurrentKey.NoiseAmplitude1.w = w; + } - void SetNoiseAmplitude2(float x, float y, float z, float w) {} + void SetNoiseAmplitude2(float x, float y, float z, float w) { + CurrentKey.NoiseAmplitude2.x = x; + CurrentKey.NoiseAmplitude2.y = y; + CurrentKey.NoiseAmplitude2.z = z; + CurrentKey.NoiseAmplitude2.w = w; + } - void SetNoiseFrequency1(bVector4 *p) {} + void SetNoiseFrequency1(bVector4 *p) { + CurrentKey.NoiseFrequency1 = *p; + } - void SetNoiseFrequency2(bVector4 *p) {} + void SetNoiseFrequency2(bVector4 *p) { + CurrentKey.NoiseFrequency2 = *p; + } - void SetNoiseAmplitude1(bVector4 *p) {} + void SetNoiseAmplitude1(bVector4 *p) { + CurrentKey.NoiseAmplitude1 = *p; + } - void SetNoiseAmplitude2(bVector4 *p) {} + void SetNoiseAmplitude2(bVector4 *p) { + CurrentKey.NoiseAmplitude2 = *p; + } - void SetNoiseFrequency1(float *p) {} + void SetNoiseFrequency1(float *p) { + SetNoiseFrequency1(p[0], p[1], p[2], p[3]); + } - void SetNoiseFrequency2(float *p) {} + void SetNoiseFrequency2(float *p) { + SetNoiseFrequency2(p[0], p[1], p[2], p[3]); + } - void SetNoiseAmplitude1(float *p) {} + void SetNoiseAmplitude1(float *p) { + SetNoiseAmplitude1(p[0], p[1], p[2], p[3]); + } - void SetNoiseAmplitude2(float *p) {} + void SetNoiseAmplitude2(float *p) { + SetNoiseAmplitude2(p[0], p[1], p[2], p[3]); + } - void SetNearZ(float near_z) {} + void SetNearZ(float near_z) { + CurrentKey.NearZ = near_z; + } - void SetFarZ(float far_z) {} + void SetFarZ(float far_z) { + CurrentKey.FarZ = far_z; + } - // float GetNearZ() {} + float GetNearZ() { + return CurrentKey.NearZ; + } // float GetFarZ() {} - void SetLetterBox(float LB_h) {} + void SetLetterBox(float LB_h) { + CurrentKey.LB_height = LB_h; + } // float GetLetterBox() {} - void SetSimTimeMultiplier(float multiplier) {} + void SetSimTimeMultiplier(float multiplier) { + CurrentKey.SimTimeMultiplier = multiplier; + } // float GetSimTimeMultiplier() {} @@ -153,4 +259,8 @@ class Camera { // TODO move? extern bool gCinematicMomementCamera; +float NoiseBase(int x); +float NoiseInterpolated(float x); +float Noise(float x); + #endif diff --git a/src/Speed/Indep/Src/Camera/CameraAI.cpp b/src/Speed/Indep/Src/Camera/CameraAI.cpp index e69de29bb..2acb23591 100644 --- a/src/Speed/Indep/Src/Camera/CameraAI.cpp +++ b/src/Speed/Indep/Src/Camera/CameraAI.cpp @@ -0,0 +1,657 @@ +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Generated/Messages/MGamePlayMoment.h" +#include "Speed/Indep/Src/Generated/Messages/MICECameraFinished.h" +#include "Speed/Indep/Src/Generated/Messages/MMiscSound.h" +#include "Speed/Indep/Src/Interfaces/IBody.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" + +#include +#include + +#include + +#include "Speed/Indep/Src/Camera/ICE/ICEManager.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Speech/SoundAI.h" +#include "Speed/Indep/Src/World/TrackPath.hpp" + +namespace _STL { +template IVehicle *const *find( + IVehicle *const *, + IVehicle *const *, + IVehicle const *const & +); +} + +#ifndef EA_PLATFORM_PLAYSTATION2 +extern Avoidables *TheAvoidables; +#endif +extern bool gGameBreakerCamera; + +void SetNewSndCamAction(Attrib::StringKey mode, EVIEW_ID viewID); + +static float kJumpTimeMultiplier = 2.0f; +static const float kEndJumpThreshold = 0.0f; +static const float kEndJumpValue = -1.0f; +static const float kEndPursuitThreshold = 0.0f; +static const float kEndPursuitValue = -1.0f; +static const float kJumpSpeedHigh = 100.0f; +static const float kJumpSpeedLow = 80.0f; +static const float kJumpDuration = 5.0f; + +static const float Tweak_JumpCamHighestAirTresh[2] = {2.5f, 1.8f}; +static const float Tweak_JumpCamLongestAirTresh[2] = {25.0f, 15.0f}; + +// --- Director methods --- + +CameraAI::Director::Director(EVIEW_ID viewID) + : UTL::Collections::Listable(), // + mViewID(viewID), // + mDesiredMode(""), // + mAction(nullptr), // + mInputQ(1, 0x98c7a2f5, "CAMERA", false), // + mPrepareToEnableIce(false), // + mPursuitStartTime(0.0f), // + mJumpTime(0.0f), // + mIsCinematicMomement(false), // + mCinematicSlowdownSeconds(0.0f) { +} + +CameraAI::Director::~Director() { + ReleaseAction(); +} + +void CameraAI::Director::ReleaseAction() { + if (mAction) { + delete mAction; + mAction = nullptr; + } +} + +void CameraAI::Director::Reset() { + mIsCinematicMomement = false; + mJumpTime = 0.0f; + mPursuitStartTime = 0.0f; + mCinematicSlowdownSeconds = 0.0f; + SetAction(Attrib::StringKey("DRIVE")); + if (mAction) { + mAction->Reset(); + } +} + +void CameraAI::Director::JumpStart(float time) { + mJumpTime = time * kJumpTimeMultiplier; +} + +void CameraAI::Director::EndJumping() { + if (mJumpTime >= kEndJumpThreshold) { + return; + } + mJumpTime = kEndJumpValue; +} + +void CameraAI::Director::EndPursuitStart() { + if (mPursuitStartTime >= kEndPursuitThreshold) { + return; + } + mPursuitStartTime = kEndPursuitValue; +} + +CameraMover *CameraAI::Director::GetMover() { + if (mAction) { + return mAction->GetMover(); + } + return nullptr; +} + +void CameraAI::Director::Update(float dT) { + if (!TheGameFlowManager.IsPaused() && mJumpTime > 0.0f) { + mJumpTime -= dT; + } + if (!FEManager::ShouldPauseSimulation(true) && mPursuitStartTime > 0.0f) { + mPursuitStartTime -= dT; + } + SelectAction(); + if (mAction) { + if (mAction->GetName() == Attrib::StringKey("DRIVE")) { + mAction->SetSpecial(mCinematicSlowdownSeconds); + } + if (mAction) { + mAction->Update(dT); + } + } +} + +void CameraAI::Director::SetAction(Attrib::StringKey desiredMode) { + Action *action; + mDesiredMode = desiredMode; + if (mAction) { + const Attrib::StringKey &key = mAction->GetNext(); + if (!key.IsEmpty()) { + mDesiredMode = key; + } + if (mAction) { + if (mAction->GetName() == mDesiredMode) { + return; + } + } + } + if (!mDesiredMode.IsEmpty()) { + action = Action::CreateInstance(UCrc32(mDesiredMode), this); + if (action) { + ReleaseAction(); + mAction = action; + SetNewSndCamAction(mDesiredMode, mViewID); + } + } +} + +void CameraAI::Director::SelectAction() { + if (TheICEManager.IsEditorOff()) { + + if (mJumpTime < 0.0f) { + mJumpTime = 0.0f; + mDesiredMode = Attrib::StringKey("DRIVE"); + mIsCinematicMomement = true; + } + + if (mPursuitStartTime < 0.0f) { + mPursuitStartTime = 0.0f; + mDesiredMode = Attrib::StringKey("DRIVE"); + mIsCinematicMomement = true; + } + + if (mPursuitStartTime > 0.0f && mPursuitStartTime < kJumpDuration) { + mDesiredMode = Attrib::StringKey("TRACK_COP"); + } + + if (!gGameBreakerCamera) { + eView *view = &eViews[mViewID]; + if (view) { + CameraMover *cm = view->GetCameraMover(); + if (cm && cm->GetType() == CM_DRIVE_CUBIC) { + CameraAnchor *anchor = cm->GetAnchor(); + if (anchor) { + if (AreMomentCamerasEnabled() && + (anchor->GetVelocityMagnitude() > kJumpSpeedHigh || + (anchor->GetVelocityMagnitude() > kJumpSpeedLow && anchor->IsTouchingGround())) && + anchor->IsCloseToRoadBlock()) { + mDesiredMode = Attrib::StringKey("JUMP"); + mJumpTime = kJumpDuration; + { + MGamePlayMoment msg(UMath::Vector4::kZero, UMath::Vector4::kZero, UMath::Vector4::kZero, 0, 0x2d8acb81); + msg.Send(UCrc32("MomentStrm")); + } + } + } + } + } + } + + bool isICEPlaying = false; + INIS *nis = UTL::Collections::Singleton::Get(); + if (nis) { + if (nis->IsPlaying()) { + ICEScene *scene = nis->GetScene(); + if (scene) { + isICEPlaying = scene->IsControllingCamera(); + mIsCinematicMomement = nis->IsWorldMomement(); + } + } + } + + if (isICEPlaying || TheICEManager.IsGenericCameraPlaying()) { + mDesiredMode = Attrib::StringKey("ICE"); + mJumpTime = 0.0f; + mPursuitStartTime = 0.0f; + } else { + if (mAction && mAction->GetName() == Attrib::StringKey("ICE")) { + TheICEManager.SetUseRealTime(false); + mDesiredMode = Attrib::StringKey("DRIVE"); + INIS *nis2 = UTL::Collections::Singleton::Get(); + if (nis2) { + nis2->FireEventTag("CameraFinished"); + } + MICECameraFinished().Post(UCrc32(0x20d60dbf)); + } + } + } + + if (mAction) { + const Attrib::StringKey &key = mAction->GetNext(); + if (!key.IsEmpty()) { + mDesiredMode = key; + } + if (mAction) { + if (mAction->GetName() == mDesiredMode) { + return; + } + } + } + if (!mDesiredMode.IsEmpty()) { + Action *action = Action::CreateInstance(UCrc32(mDesiredMode), this); + if (action) { + mIsCinematicMomement = false; + mCinematicSlowdownSeconds = 0.0f; + ReleaseAction(); + mAction = action; + SetNewSndCamAction(mDesiredMode, mViewID); + } + } +} + +void CameraAI::Director::TotaledStart() { + mDesiredMode = Attrib::StringKey("TOTALED"); + mJumpTime = 0.0f; + SetAction(mDesiredMode); +} + +void CameraAI::Director::PursuitStart() { + if (mPursuitStartTime <= 0.0f) { + { + MGamePlayMoment msg(UMath::Vector4::kZero, UMath::Vector4::kZero, UMath::Vector4::kZero, 0, 0x88bff834); + msg.Send(UCrc32("MomentStrm")); + } + { + MMiscSound snd(1); + snd.Send(UCrc32("play")); + } + + mPursuitStartTime = 5.0f; + mCinematicSlowdownSeconds = 3.0f; + } +} + +// --- Free functions --- + +IPlayer *FindPlayer(EVIEW_ID id) { + for (IPlayer *const *iter = IPlayer::GetList(PLAYER_LOCAL).begin(); iter != IPlayer::GetList(PLAYER_LOCAL).end(); ++iter) { + IPlayer *ip = *iter; + if (ip->GetControllerPort() == static_cast(id)) { + return ip; + } + } + return nullptr; +} + +CameraAI::Director *FindDirector(EVIEW_ID id) { + for (CameraAI::Director *const *iter = CameraAI::Director::GetList().begin(); iter != CameraAI::Director::GetList().end(); ++iter) { + CameraAI::Director *cd = *iter; + if (cd->GetViewID() == id) { + return cd; + } + } + return nullptr; +} + +CameraAI::Director *FindDirector(unsigned int id) { + for (CameraAI::Director *const *iter = CameraAI::Director::GetList().begin(); iter != CameraAI::Director::GetList().end(); ++iter) { + CameraAI::Director *cd = *iter; + IPlayer *iplayer = FindPlayer(cd->GetViewID()); + if (iplayer) { + ISimable *isimable = iplayer->GetSimable(); + if (isimable && isimable->GetWorldID() == id) { + return cd; + } + } + } + return nullptr; +} + +bool AreMomentCamerasEnabled() { + bool splitCheck = false; + if (FEDatabase->IsSplitScreenMode()) { + splitCheck = (FEDatabase->iNumPlayers == 2); + } + if (splitCheck) { + return false; + } + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + return false; + } + return FEDatabase->GetGameplaySettings()->JumpCam; +} + +// --- CameraAI namespace functions --- + +void CameraAI::Update(float dT) { + IPlayer::Count(PLAYER_LOCAL); + unsigned int player = 0; + do { + EVIEW_ID viewID = static_cast(++player); + IPlayer *iplayer = FindPlayer(viewID); + Director *cd = FindDirector(viewID); + if (cd && !iplayer) { + delete cd; + continue; + } + if (iplayer && !cd) { + cd = new (static_cast(0)) Director(viewID); + } + } while (player <= static_cast(PLAYER_LOCAL)); + for (Director *const *iter = Director::GetList().begin(); iter != Director::GetList().end(); ++iter) { + Director *cd = *iter; + cd->Update(dT); + } +} + +void CameraAI::Reset() { + for (Director *const *iter = Director::GetList().begin(); iter != Director::GetList().end(); ++iter) { + Director *cd = *iter; + cd->Reset(); + } +} + +void CameraAI::SetAction(EVIEW_ID viewID, const char *desiredMode) { + for (Director *const *iter = Director::GetList().begin(); iter != Director::GetList().end(); ++iter) { + Director *cd = *iter; + if (cd->GetViewID() == viewID) { + cd->SetAction(Attrib::StringKey(desiredMode)); + } + } +} + +void CameraAI::MaybeKillPursuitCam(unsigned int id) { + Director *cd = FindDirector(id); + if (cd) { + cd->EndPursuitStart(); + } +} + +static float AverageAir(ISimable *isimable, float fSeconds, float *pHighest, float *pLongest) { + IRigidBody *irb = isimable->GetRigidBody(); + if (!irb) return 0.0f; + + ICollisionBody *irbc; + if (!isimable->QueryInterface(&irbc)) return 0.0f; + + float fSpeed = irb->GetSpeed(); + if (fSpeed < 1.0f) return 0.0f; + + int nSteps = static_cast(fSpeed * fSeconds * 0.5f); + if (nSteps <= 0) return 0.0f; + + ISuspension *isuspension; + if (!isimable->QueryInterface(&isuspension)) return 0.0f; + + IVehicle *vehicle; + if (!isimable->QueryInterface(&vehicle)) return 0.0f; + + UMath::Vector3 p = UMath::Vector3::kZero; + + for (unsigned int j = 0; j < isuspension->GetNumWheels(); j++) { + UMath::Vector3 wp = isuspension->GetWheelPos(j); + const UMath::Vector3 &upVec = irbc->GetUpVector(); + float compression = isuspension->GetCompression(j); + UMath::ScaleAdd(upVec, -compression, wp, wp); + UMath::ScaleAdd(wp, 0.25f, p, p); + } + + UMath::Vector4 vNormal = irbc->GetGroundNormal(); + WWorldPos pTopo = isimable->GetWPos(); + pTopo.SetTolerance(5.0f); + + float fElevation = pTopo.HeightAtPoint(p); + float fAirSum = 0.0f; + float fAirMax = bMax(0.0f, p.y - fElevation); + float fStep = fSeconds / static_cast(nSteps); + float fAirTime = fStep; + float fDeparture = 0.0f; + + if (fAirMax <= 0.0f) { + fAirTime = 0.0f; + } else { + fDeparture = -fStep; + } + + Attrib::Gen::pvehicle attributes(vehicle->GetVehicleAttributes()); + Attrib::Gen::chassis chassis(attributes.chassis(0), 0, nullptr); + + float fDownForce = -Physics::Info::AerodynamicDownforce(chassis, fSpeed); + float fDownAccel = irbc->GetGravity() + fDownForce / irb->GetMass(); + + UMath::Vector3 a = UMath::Vector3Make(0.0f, fDownAccel, 0.0f); + + UMath::Vector3 v = irb->GetLinearVelocity(); + + UMath::Vector3 pNew; + UMath::ScaleAdd(v, 0.5f, p, pNew); + + const float tbarr = 1.0f; + + UMath::Vector4 seg[2]; + seg[0] = UMath::Vector4Make(p, tbarr); + seg[1] = UMath::Vector4Make(pNew, tbarr); + + WCollisionMgr::WorldCollisionInfo cInfo; + WCollisionMgr(0, 3).CheckHitWorld(seg, cInfo, 2); + + if (cInfo.HitSomething()) { + return 0.0f; + } + + int i = 1; + float fFuture = fAirTime; + + for (i = 1; i < nSteps; i++) { + fFuture = fStep * static_cast(i) - fDeparture; + UMath::ScaleAdd(a, fFuture * 0.5f, v, pNew); + UMath::ScaleAdd(pNew, fFuture, p, pNew); + + if (pTopo.Update(pNew, vNormal, true, nullptr, true)) { + if (pTopo.OnValidFace() && 0.5f <= vNormal.y) { + float fElevation = pTopo.HeightAtPoint(pNew); + float fAir = pNew.y - fElevation; + if (fAir <= 0.0f) break; + fAirMax = bMax(fAirMax, fAir); + fAirSum += fAir; + } + } + } + + if (pHighest) { + *pHighest = fAirMax; + } + if (pLongest) { + *pLongest = fFuture; + } + + return fAirSum / static_cast(i); +} + +void CameraAI::MaybeKillJumpCam(unsigned int id) { + Director *cd = FindDirector(id); + if (cd) { + cd->EndJumping(); + } +} + +void CameraAI::Init() { +#ifndef EA_PLATFORM_PLAYSTATION2 + TheAvoidables = new Avoidables(); +#endif +} + +void CameraAI::Shutdown() { +#ifndef EA_PLATFORM_PLAYSTATION2 + if (TheAvoidables) { + delete TheAvoidables; + } + TheAvoidables = nullptr; +#endif + Director::List copy(Director::GetList()); + for (Director *const *iter = copy.begin(); iter != copy.end(); ++iter) { + Director *cd = *iter; + if (cd) { + delete cd; + } + } +} + +void CameraAI::AddAvoidable(IBody *body) { +#ifndef EA_PLATFORM_PLAYSTATION2 + Avoidables::iterator iter = _STL::find(TheAvoidables->begin(), TheAvoidables->end(), body); + if (iter == TheAvoidables->end()) { + TheAvoidables->push_back(body); + } +#endif +} + +void CameraAI::RemoveAvoidable(IBody *body) { +#ifndef EA_PLATFORM_PLAYSTATION2 + Avoidables::iterator iter = _STL::find(TheAvoidables->begin(), TheAvoidables->end(), body); + if (iter != TheAvoidables->end()) { + TheAvoidables->erase(iter); + } +#endif +} + +void CameraAI::StartCinematicSlowdown(EVIEW_ID viewID, float seconds) { + for (Director *const *iter = Director::GetList().begin(); iter != Director::GetList().end(); ++iter) { + Director *cd = *iter; + if (cd->GetViewID() == viewID) { + Action *action = cd->GetAction(); + if (action && action->GetName() == Attrib::StringKey("DRIVE")) { + cd->SetCinematicSlowdown(seconds); + } + } + } +} + +void CameraAI::MaybeDoTotaledCam(IPlayer *iplayer) { + if (Sim::GetUserMode() != Sim::USER_SINGLE) { + return; + } + for (Director *const *iter = Director::GetList().begin(); iter != Director::GetList().end(); ++iter) { + Director *cd = *iter; + if (cd->GetViewID() == iplayer->GetControllerPort()) { + cd->TotaledStart(); + } + } +} + +void CameraAI::MaybeDoPursuitCam(IVehicle *ivehicle) { + if (TheICEManager.IsEditorOn()) { + return; + } + if (!AreMomentCamerasEnabled()) { + return; + } + INIS *nis = UTL::Collections::Singleton::Get(); + if (nis) { + return; + } + GRaceParameters *parms = GRaceStatus::Get().GetRaceParameters(); + if (parms) { + if (parms->GetIsPursuitRace()) { + return; + } + if (GRaceStatus::Get().GetRaceTimeElapsed() < 5.0f) { + return; + } + } + if (ivehicle->GetDriverStyle() == STYLE_DRAG) { + return; + } + ISimable *isimable = ivehicle->GetSimable(); + if (!isimable) { + return; + } + Director *cd = FindDirector(isimable->GetWorldID()); + if (!cd) { + return; + } + if (gGameBreakerCamera) { + return; + } + cd->PursuitStart(); +} + +// NON-MATCHING: dead store offset mismatch (stw r0, 0xa0(r1) vs 0x70(r1)) +// GCC internal stack slot reuse picks Vector4 temp slot instead of _GetKind return slot +void CameraAI::MaybeDoJumpCam(ISimable *isimable) { + if (TheICEManager.IsEditorOn()) { + return; + } + if (!AreMomentCamerasEnabled()) { + return; + } + if (UTL::Collections::Singleton::Get()) { + return; + } + IVehicle *ivehicle; + if (isimable->QueryInterface(&ivehicle)) { + if (ivehicle->GetDriverStyle() == STYLE_DRAG) { + return; + } + } + Director *cd = FindDirector(isimable->GetWorldID()); + if (!cd) { + return; + } + if (cd->IsJumping()) { + return; + } + if (gGameBreakerCamera) { + return; + } + UMath::Vector3 velocity; + isimable->GetLinearVelocity(velocity); + float speed = UMath::Length(velocity); + if (speed < 10.0f) { + return; + } + int set = 0; + bVector3 position; + TrackPathZone *zone = TheTrackPathManager.FindZone( + reinterpret_cast( + &bConvertFromBond(position, isimable->GetPosition())), + TRACK_PATH_ZONE_JUMP_CAM, nullptr); + if (zone) { + set = 1; + } + float highest = 0.0f; + float longest = 0.0f; + float avg = AverageAir(isimable, 3.0f, &highest, &longest); + if (avg >= 20.1f) { + return; + } + if (highest >= 20.1f) { + return; + } + if (longest >= 3.1f) { + return; + } + if (avg <= 1.0f) { + return; + } + if (highest > Tweak_JumpCamHighestAirTresh[set] && + longest * speed > Tweak_JumpCamLongestAirTresh[set]) { + SetAction(cd->GetViewID(), "CDActionTrackCar"); + cd->JumpStart(bClamp(longest + longest, 1.0f, 4.0f)); + MGamePlayMoment msg(UMath::Vector4::kZero, UMath::Vector4::kZero, UMath::Vector4::kZero, 0, 0xa3447e3f); + msg.Send(UCrc32("MomentStrm")); + } + if (avg > 1.0f) { + SoundAI *ai = UTL::Collections::Singleton::Get(); + if (ai) { + Observer *observer = ai->GetObserver(); + if (observer) { + observer->NotifyAirborne(highest, longest); + } + } + } +} + + +// Static member definitions +template <> +UTL::Collections::Listable::List + UTL::Collections::Listable::_mTable; diff --git a/src/Speed/Indep/Src/Camera/CameraAI.hpp b/src/Speed/Indep/Src/Camera/CameraAI.hpp index b4cad6624..d2ad3a808 100644 --- a/src/Speed/Indep/Src/Camera/CameraAI.hpp +++ b/src/Speed/Indep/Src/Camera/CameraAI.hpp @@ -5,15 +5,153 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Libs/Support/Utility/UListable.h" +#include "Speed/Indep/Src/Ecstasy/EcstasyData.hpp" +#include "Speed/Indep/Src/Input/ActionQueue.h" #include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h" + +class CameraMover; +class IBody; +class ISimable; namespace CameraAI { +// total size: 0x18 +struct Action : public UTL::COM::Object, public UTL::COM::Factory { + Action() : UTL::COM::Object(0) {} + + void *operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, std::size_t size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + void *operator new(std::size_t size, const char *name) { + return gFastMem.Alloc(size, name); + } + + void operator delete(void *mem, const char *name) { + if (mem) { + gFastMem.Free(mem, 0, name); + } + } + + virtual ~Action() {} + + virtual void Update(float dT) {} + virtual void Reset() {} + virtual const Attrib::StringKey &GetName() const = 0; + virtual Attrib::StringKey GetNext() const = 0; + virtual CameraMover *GetMover() = 0; + virtual void SetSpecial(float val) {} +}; + +// total size: 0x2C8 +class Director : public UTL::Collections::Listable { + public: + void *operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, std::size_t size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + void *operator new(std::size_t size, const char *name) { + return gFastMem.Alloc(size, name); + } + + void operator delete(void *mem, const char *name) { + if (mem) { + gFastMem.Free(mem, 0, name); + } + } + + void operator delete(void *mem, std::size_t size, const char *name) { + if (mem) { + gFastMem.Free(mem, size, name); + } + } + + EVIEW_ID GetViewID() const { + return mViewID; + } + + Action *GetAction() { + return mAction; + } + + bool IsJumping() { + return mJumpTime > 0.0f; + } + + bool IsCinematicMomement() { + return mIsCinematicMomement; + } + + float GetCinematicSlowdown() { + return mCinematicSlowdownSeconds; + } + + void SetCinematicSlowdown(float seconds) { + mCinematicSlowdownSeconds = seconds; + } + + Director(EVIEW_ID viewID); + virtual ~Director(); + + void ReleaseAction(); + void Reset(); + void SetAction(Attrib::StringKey desiredMode); + void SelectAction(); + void TotaledStart(); + void JumpStart(float time); + void EndJumping(); + void PursuitStart(); + void EndPursuitStart(); + void Update(float dT); + CameraMover *GetMover(); + + private: + EVIEW_ID mViewID; // offset 0x4, size 0x4 + Attrib::StringKey mDesiredMode; // offset 0x8, size 0x10 + Action *mAction; // offset 0x18, size 0x4 + ActionQueue mInputQ; // offset 0x1C, size 0x294 + bool mPrepareToEnableIce; // offset 0x2B0, size 0x1 + float mPursuitStartTime; // offset 0x2B4, size 0x4 + float mJumpTime; // offset 0x2B8, size 0x4 + bool mIsCinematicMomement; // offset 0x2BC, size 0x1 + float mCinematicSlowdownSeconds; // offset 0x2C0, size 0x4 +}; + void Update(float dT); void Reset(); +void SetAction(EVIEW_ID viewID, const char *action); +void StartCinematicSlowdown(EVIEW_ID viewID, float seconds); +void Init(); +void Shutdown(); +void AddAvoidable(IBody *body); +void RemoveAvoidable(IBody *body); void MaybeDoTotaledCam(IPlayer *iplayer); void MaybeDoPursuitCam(IVehicle *ivehicle); +void MaybeKillPursuitCam(unsigned int handle); +void MaybeKillJumpCam(unsigned int handle); +void MaybeDoJumpCam(ISimable *simable); }; // namespace CameraAI +IPlayer *FindPlayer(EVIEW_ID viewID); +CameraAI::Director *FindDirector(EVIEW_ID viewID); +CameraAI::Director *FindDirector(unsigned int handle); +bool AreMomentCamerasEnabled(); + #endif diff --git a/src/Speed/Indep/Src/Camera/CameraMover.cpp b/src/Speed/Indep/Src/Camera/CameraMover.cpp index e69de29bb..271b2f29b 100644 --- a/src/Speed/Indep/Src/Camera/CameraMover.cpp +++ b/src/Speed/Indep/Src/Camera/CameraMover.cpp @@ -0,0 +1,1637 @@ +#include "CameraMover.hpp" +#include "CameraAI.hpp" + +Attrib::Key Attrib::Gen::ecar::ClassKey() { + return 0xa5b543b7; +} + +Attrib::Key Attrib::Gen::camerainfo::ClassKey() { + return 0x93c171e4; +} +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Interfaces/IBody.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Misc/Table.hpp" +#include "Speed/Indep/Src/Physics/Common/VehicleSystem.h" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/Src/World/TrackStreamer.hpp" +#include "Speed/Indep/Src/World/WWorldMath.h" +#include "Speed/Indep/bWare/Inc/bFunk.hpp" + +int AmIinATunnel(eView *view, int CheckOverPass); + +#ifndef EA_PLATFORM_PLAYSTATION2 +DECLARE_CONTAINER_TYPE(CameraAIAvoidables); + +struct Avoidables : public _STL::list > { + void *operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, std::size_t size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } +}; + +Avoidables *TheAvoidables; +#endif + +extern int WeHaveCheckedIfJR2ServerExists; +extern int DisablePrecullerCounter; +extern int JR2ServerExists; +extern int LastUpdateTimeCaffeine; +extern int LastUpdateTimeJR2; +extern int RealTime; +extern int DisableCommunication; +static volatile const int RemoteCaffeinating = 0; +extern int bStreamingPositionFromICE; +extern Timer RealTimer; +extern bool TrackCopCameraMover_IdleSim; + +bool OutsidePovType(int pov_type); +bool RenderCarPovType(int pov_type, bool look_back); +bool RenderCarPovTypeRaw(int pov_type, int look_back) __asm__("RenderCarPovType__Fib"); +struct LongVector; +inline void espSetCameraPositionFix(const LongVector *eye, const LongVector *target) {} +inline void espCentrePlaneView(const bVector3 *pos) {} +int bFunkDoesServerExist(const char *server_name); + +static const bVector4 CameraNoiseHandheldFrequency(0.213f, 0.175f, 0.153f, 0.192f); +static const bVector4 CameraNoiseHandheldAmplitude(0.01f, 0.01f, 0.03f, 0.03f); +static const bVector4 CameraNoiseChopperFrequency(3.141f, 2.971f, 0.84234f, 0.92345f); +static const bVector4 CameraNoiseChopperAmplitude(0.01f, 0.05f, 1.1f, 2.7f); +static const bVector4 CameraNoiseSpeedFrequency(1.8f, 2.0f, 2.125f, 2.0f); +static const bVector4 CameraNoiseSpeedAmplitude(0.03f, 0.025f, 0.68f, 0.28f); +static const bVector4 CameraNoiseTerrainFrequency(3.0f, 5.0f, 7.0f, 5.5f); +static const bVector4 CameraNoiseTerrainAmplitude(0.007f, 0.01f, 0.3f, 0.4f); +static const bVector4 CameraNoiseSpeedData[5] = { + bVector4(0.0f, 0.0f, 0.6f, 1.0f), + bVector4(0.005f, 0.5f, 1.1f, 1.1f), + bVector4(0.03f, 0.8f, 1.2f, 1.2f), + bVector4(0.07f, 1.0f, 1.3f, 1.3f), + bVector4(0.02f, 1.0f, 1.0f, 1.4f), +}; +extern float CameraAccelerationCurve[5]; +extern bVector2 CameraSpeedHugData[5]; +extern Graph gDriftSpeed; +extern CarTypeInfo *GetCarTypeInfoFromHash(unsigned int car_type_hash); +extern float RVMnearz; +extern float RVMfarz; +extern bVector3 RVMOffsetInCar; +static float TrackCarIsoZoomDistance[3] = {3.3f, 3.3f, 3.3f}; +static float TrackCarLookOffsetX[3] = {1.2f, -4.0f, 1.2f}; +static float TrackCarLookOffsetY[3] = {0.0f, 0.0f, 0.0f}; +static float TrackCarLookOffsetZ[3] = {-0.2f, -0.2f, 1.0f}; +void ApplyCameraShake(int nViewID, bMatrix4 *pMatrix); +void HideEverySingleHud(); +void SplineSeek(tCubic1D *p, float time, float fDClamp, float fDDClamp) __asm__("SplineSeek__6cPointP8tCubic1Dfff"); +namespace CameraAI { +void MaybeKillJumpCam(unsigned int id); +void MaybeKillPursuitCam(unsigned int id); +} + +struct LongVector { + int x; + int y; + int z; +}; + +static inline void MakeLongVector(LongVector *dest, const bVector3 *src, float scale) { + dest->x = static_cast(src->x * scale); + dest->y = static_cast(src->y * scale); + dest->z = static_cast(src->z * scale); +} + +static float SampleFloatTable(const float *data, int count, float value, float min_value, float max_value) { + if (count <= 0) { + return 0.0f; + } + if (value <= min_value) { + return data[0]; + } + if (value >= max_value) { + return data[count - 1]; + } + + { + float scaled = (value - min_value) * (static_cast(count - 1) / (max_value - min_value)); + int index = static_cast(bFloor(scaled)); + float blend = scaled - static_cast(index); + + if (index < 0) { + index = 0; + } + if (index >= count - 1) { + index = count - 2; + } + + return data[index] + (data[index + 1] - data[index]) * blend; + } +} + +static bVector2 SampleVector2Table(const bVector2 *data, int count, float value, float min_value, float max_value) { + if (count <= 0) { + return bVector2(0.0f, 0.0f); + } + if (value <= min_value) { + return data[0]; + } + if (value >= max_value) { + return data[count - 1]; + } + + { + float scaled = (value - min_value) * (static_cast(count - 1) / (max_value - min_value)); + int index = static_cast(bFloor(scaled)); + float blend = scaled - static_cast(index); + bVector2 sample; + + if (index < 0) { + index = 0; + } + if (index >= count - 1) { + index = count - 2; + } + + sample.x = data[index].x + (data[index + 1].x - data[index].x) * blend; + sample.y = data[index].y + (data[index + 1].y - data[index].y) * blend; + return sample; + } +} + +bool OutsidePovType(int pov_type) { + return (static_cast(pov_type - 2) < 3) || pov_type == 6 || pov_type == 5; +} + +bool RenderCarPovType(int pov_type, bool look_back) { + return (static_cast(pov_type - 2) < 3) || (pov_type == 1 && !look_back) || pov_type == 6 || pov_type == 5; +} + +static inline void ResetCubic1DState(tCubic1D *cubic) { + cubic->Val = cubic->ValDesired; + cubic->dVal = cubic->dValDesired; + cubic->state = 0; +} + +static inline void ResetCubic3DState(tCubic3D *cubic) { + ResetCubic1DState(&cubic->x); + ResetCubic1DState(&cubic->y); + ResetCubic1DState(&cubic->z); +} + +template <> void tTable::Blend(float *dest, float *a, float *b, float blend_a) { + *dest = *a * blend_a + *b * (1.0f - blend_a); +} + +template <> void tTable::Blend(bVector2 *dest, bVector2 *a, bVector2 *b, float blend_a) { + float blend_b = 1.0f - blend_a; + dest->x = a->x * blend_a + b->x * blend_b; + dest->y = a->y * blend_a + b->y * blend_b; +} + +CameraAnchor::CameraAnchor(int model) + : mVelocity(0.0f, 0.0f, 0.0f), // + mVelMag(0.0f), // + mTopSpeed(0.0f), // + mGeomPos(0.0f, 0.0f, 0.0f), // + mDimension(0.0f, 0.0f, 0.0f), // + mAccel(0.0f, 0.0f, 0.0f), // + mGeomRot(), // + mModel(0), // + mWorldID(0), // + mSurface(SimSurface::kNull), // + mCollisionDamping(0.0f), // + mDrift(0.0f), // + mGroundCollision(0.0f), // + mObjectCollision(0.0f), // + mIsNosEngaged(false), // + mIsBrakeEngaged(false), // + mIsDragRace(false), // + mIsOverRev(false), // + mIsTouchingGround(false), // + mIsVehicleDestroyed(false), // + mIsGearChanging(false), // + mIsCloseToRoadBlock(false), // + mZoom(1.0f), // + mModelAttributes(Attrib::FindCollection(Attrib::Gen::ecar::ClassKey(), 0xeec2271a), 0, nullptr), // + mCameraInfoAttributes(Attrib::FindCollection(Attrib::Gen::camerainfo::ClassKey(), 0xeec2271a), 0, nullptr) { + mPOV.Type = 3; + mPOV.Angle = bDegToAng(mCameraInfoAttributes.ANGLE(0)); + mPOV.Lag = mCameraInfoAttributes.LAG(0); + mPOV.Height = mCameraInfoAttributes.HEIGHT(0); + mPOV.LatOffset = mCameraInfoAttributes.LATOFFSET(0); + mPOV.Fov = bDegToAng(mCameraInfoAttributes.FOV(0)); + mPOV.AllowTilting = mCameraInfoAttributes.TILTING(0); + mPOV.Stiffness = mCameraInfoAttributes.STIFFNESS(0); + SetModel(model); + bIdentity(&mGeomRot); +} + +CameraAnchor::~CameraAnchor() {} + +void CameraAnchor::SetModel(int model) { + if (mModel != model) { + CarTypeInfo *info = GetCarTypeInfoFromHash(model); + const char *name; + + if (info) { + name = info->CarTypeName; + } else { + name = ""; + } + + mModel = model; + mModelAttributes.ChangeWithDefault(Attrib::StringToLowerCaseKey(name)); + } +} + +POV *CameraAnchor::GetPov(int pov_type) { + const Attrib::RefSpec *refspec; + + mPOV.Type = static_cast(pov_type); + + switch (pov_type) { + case 0: + refspec = reinterpret_cast(mModelAttributes.GetAttributePointer(0x585517f3, 0)); + break; + case 1: + refspec = reinterpret_cast(mModelAttributes.GetAttributePointer(0xd74c1435, 0)); + break; + case 2: + refspec = reinterpret_cast(mModelAttributes.GetAttributePointer(0x0c2da793, 0)); + break; + case 3: + refspec = reinterpret_cast(mModelAttributes.GetAttributePointer(0xccf03cb3, 0)); + break; + case 4: + refspec = reinterpret_cast(mModelAttributes.GetAttributePointer(0x10204a90, 0)); + break; + case 5: + refspec = reinterpret_cast(mModelAttributes.GetAttributePointer(0x4b675dc8, 0)); + break; + case 6: + refspec = reinterpret_cast(mModelAttributes.GetAttributePointer(0xd76a6fad, 0)); + break; + default: + goto default_case; + } + + if (!refspec) { + refspec = reinterpret_cast(Attrib::DefaultDataArea(sizeof(Attrib::RefSpec))); + } + + mCameraInfoAttributes.ChangeWithDefault(*refspec); + goto after_camerainfo; + +default_case: + mPOV.Type = 3; + mCameraInfoAttributes.Change(Attrib::FindCollection(Attrib::Gen::camerainfo::ClassKey(), 0xeec2271a)); + +after_camerainfo: + { + float zoom = mZoom; + if (zoom < 1.0f) { + zoom = 1.0f; + } + unsigned int index = eGetCurrentViewMode() == 3; + + mPOV.Angle = bDegToAng(mCameraInfoAttributes.ANGLE(index)); + mPOV.Lag = mCameraInfoAttributes.LAG(index) / zoom; + mPOV.Height = mCameraInfoAttributes.HEIGHT(index); + mPOV.LatOffset = mCameraInfoAttributes.LATOFFSET(index); + mPOV.Fov = bDegToAng(mCameraInfoAttributes.FOV(index) * zoom); + mPOV.AllowTilting = static_cast(mCameraInfoAttributes.TILTING(index)); + mPOV.Stiffness = mCameraInfoAttributes.STIFFNESS(index); + } + + return &mPOV; +} + +void CameraAnchor::Update(float dT, const bMatrix4 &matrix, const bVector3 &velocity, const bVector3 &forward) { + float dist = bDistBetween(&mGeomPos, reinterpret_cast(&matrix.v3)); + + bCopy(reinterpret_cast(&mGeomRot), reinterpret_cast(&matrix)); + float savedVelMag = mVelMag; + mGeomRot.v3.z = 0.0f; + mGeomRot.v3.y = 0.0f; + mGeomRot.v3.x = 0.0f; + + mGeomPos.x = matrix.v3.x; + mGeomPos.y = matrix.v3.y; + mGeomPos.z = matrix.v3.z; + mVelocity = velocity; + + mVelMag = bLength(&velocity); + + if (dT > 0.0f && dist / dT < 200.0f) { + bVector3 acc((mVelMag - savedVelMag) / dT, 0.0f, 0.0f); + bMulMatrix(&mAccel, &mGeomRot, &acc); + } else { + mAccel.z = 0.0f; + mAccel.y = 0.0f; + mAccel.x = 0.0f; + } +} + +void RenderCameraMovers(eView *view) { + CameraMover *head = view->CameraMoverList.GetHead(); + int no_camera_mover = head == view->CameraMoverList.EndOfList(); + CameraMover *camera_mover = nullptr; + + if (!no_camera_mover) { + camera_mover = head; + } + if (camera_mover) { + camera_mover = nullptr; + if (!no_camera_mover) { + camera_mover = head; + } + camera_mover->Render(view); + } +} + +void CameraMoverRestartRace() { + WeHaveCheckedIfJR2ServerExists = 0; + CameraAI::Reset(); + + for (int view_id = 1; view_id < 4; view_id++) { + eView *view = eGetView(view_id, false); + + if (!view) { + continue; + } + + CameraMover *camera_mover = view->GetCameraMover(); + if (camera_mover) { + camera_mover->ResetState(); + } + } +} + +Camera *GetCurrentCamera() { + eView *view = eGetView(1, false); + if (view) { + Camera *c = view->GetCamera(); + if (c) { + return c; + } + } + return nullptr; +} + +static bool sHavePrevPosition; +static bVector3 sPrevPosition; + +void UpdateCameraMovers(float dT) { + for (int view_id = 0; view_id < 22; view_id++) { + eView *view = eGetView(view_id, false); + CameraMover *m = view->GetCameraMover(); + + if (m) { + m->Update(dT); + } + } + + if (WeHaveCheckedIfJR2ServerExists == 0) { + JR2ServerExists = bFunkDoesServerExist("JR2Server"); + WeHaveCheckedIfJR2ServerExists = 1; + } + + { + eView *view = eGetView(1, false); + Camera *camera = view->GetCamera(); + + if (JR2ServerExists != 0 && camera) { + if (bAbs(RealTime - LastUpdateTimeJR2) > 0x10) { + LastUpdateTimeJR2 = RealTime; + camera->CommunicateWithJollyRancher(const_cast("Player1Camera")); + } + } + + if (RemoteCaffeinating != 0 && DisableCommunication == 0 && camera) { + if (bAbs(RealTime - LastUpdateTimeCaffeine) > 0x10) { + LongVector fix_eye; + LongVector fix_look; + + bVector3 look = *camera->GetDirection() * 0.01f; + bVector3 eye = *camera->GetPosition() - look; + + LastUpdateTimeCaffeine = RealTime; + + MakeLongVector(&fix_eye, &eye, 100.0f); + MakeLongVector(&fix_look, camera->GetPosition(), 100.0f); + espSetCameraPositionFix(&fix_eye, &fix_look); + + if (!sHavePrevPosition) { + sPrevPosition = bVector3(0.0f, 0.0f, 0.0f); + sHavePrevPosition = true; + } + + if (bDistBetween(&sPrevPosition, camera->GetPosition()) > 0.01f) { + sPrevPosition = *camera->GetPosition(); + } + + bLength(reinterpret_cast(camera->GetPosition())); + espCentrePlaneView(camera->GetPosition()); + } + } + } + + if ((!GManager::Exists() || !GManager::Get().GetIsWarping()) && + (!GRaceStatus::Exists() || !GRaceStatus::Get().GetIsScriptWaitingForLoading())) { + bool set_any_positions = false; + + for (int view_id = 1; view_id < 3; view_id++) { + eView *view = eGetView(view_id, false); + + if (view->Active != 0) { + CameraMover *camera_mover = view->GetCameraMover(); + + if (camera_mover) { + if (!set_any_positions) { + TheTrackStreamer.ClearStreamingPositions(); + set_any_positions = true; + } + + bVector3 pos; + bVector3 vel; + bVector3 dir; + + pos = *view->GetCamera()->GetPosition(); + vel = *view->GetCamera()->GetVelocityPosition(); + dir = *view->GetCamera()->GetDirection(); + + if (bStreamingPositionFromICE != 0) { + INIS *nis = INIS::Get(); + if (nis) { + bConvertFromBond(pos, reinterpret_cast(*nis->GetStartLocation())); + } + + dir.z = vel.z; + vel.x = 0.0f; + vel.y = 0.0f; + dir.x = 0.0f; + dir.y = 0.0f; + } + + bool following_car = false; + CameraMover *mover = view->GetCameraMover(); + if (mover->GetType() == CM_DRIVE_CUBIC) { + following_car = true; + } + + int pos_num = (view_id == 2); + TheTrackStreamer.PredictStreamingPosition( + pos_num, &pos, &vel, &dir, following_car); + } + } + } + } +} + +bool DoesCameraTypeDisablePreculler(CameraMoverTypes type) { + if (type == CM_DEBUG_WORLD) { + return true; + } + return type == CM_TRACK_CAR; +} + +CameraMover::CameraMover(int view_id, CameraMoverTypes type) + : mCollider(WCollider::Create(0, WCollider::kColliderShape_Sphere, 0x1C, 0)) // + , mWPos(0.025f) { + mWPos.fFace.fPt0 = UMath::Vector3::kZero; + mWPos.fFace.fPt1 = UMath::Vector3::kZero; + mWPos.fFace.fPt2 = UMath::Vector3::kZero; + Type = type; + mWPos.fSurface = nullptr; + ViewID = view_id; + Enabled = 0; + fAccumulatedClearance = 0.0f; + fAccumulatedAdjust = 0.0f; + fSavedAdjust = 0.0f; + vSavedForward.z = 0.0f; + vSavedForward.y = 0.0f; + vSavedForward.x = 0.0f; + if (view_id == -1) { + pView = nullptr; + pCamera = nullptr; + RenderDash = 0; + } else { + pView = eGetView(view_id, false); + pCamera = pView->GetCamera(); + pCamera->SetFarZ(12000.0f); + RenderDash = pCamera->GetRenderDash(); + Enable(); + } + + if (DoesCameraTypeDisablePreculler(Type)) { + DisablePrecullerCounter++; + } +} + +CameraMover::~CameraMover() { + WCollider::Destroy(mCollider); + if (DoesCameraTypeDisablePreculler(Type)) { + DisablePrecullerCounter--; + } + Disable(); +} + +void CameraMover::Update(float dT) {} + +void CameraMover::Render(eView *view) {} + +CameraAnchor *CameraMover::GetAnchor() { + return nullptr; +} + +bool CameraMover::OutsidePOV() { + return true; +} + +bool CameraMover::RenderCarPOV() { + return true; +} + +void CameraMover::Enable() { + if (Enabled == 0) { + Enabled = 1; + pCamera->SetRenderDash(RenderDash); + pView->AttachCameraMover(this); + pCamera->SetNearZ(0.5f); + } +} + +void CameraMover::Disable() { + if (Enabled != 0) { + Enabled = 0; + RenderDash = pCamera->GetRenderDash(); + pView->UnattachCameraMover(this); + } +} + +bool CameraMover::OnWCollide(const WCollisionMgr::WorldCollisionInfo &cInfo, const UMath::Vector3 &cPoint, void *userdata) { + if (userdata) { + bVector3 vPoint; + eSwizzleWorldVector(cPoint, vPoint); + } + return true; +} + +bool CameraMover::IsSomethingInBetween(const UMath::Vector4 &p0, const UMath::Vector4 &p1) { + UMath::Vector4 seg[2]; + seg[0] = p0; + seg[1] = p1; + seg[0].y += 0.5f; + seg[1].y += 0.5f; + WCollisionMgr::WorldCollisionInfo cInfo; + WCollisionMgr collision_mgr(0, 3); + collision_mgr.CheckHitWorld(seg, cInfo, 3); + bool result = cInfo.HitSomething(); + return result; +} + +bool CameraMover::IsSomethingInBetween(const bVector3 *start, const bVector3 *end) { + UMath::Vector4 p0; + UMath::Vector4 p1; + eUnSwizzleWorldVector(*start, reinterpret_cast(p0)); + eUnSwizzleWorldVector(*end, reinterpret_cast(p1)); + return IsSomethingInBetween(p0, p1); +} + +float CameraMover::MinDistToWall() { + return 0.7f; +} + +bool CameraMover::EnforceMinGapToWalls(WCollider *collider, bVector3 *pCarPos, bVector3 *pCameraPos, bVector4 *pAdjust) { + bool bViolates = false; + bVector3 vAdjust(0.0f, 0.0f, 0.0f); + + { + float fovScale = bClamp(bAngToDeg(pCamera->GetFieldOfView()) * (1.0f / 180.0f) * 2.0f, 0.0f, 1.0f); + + bVector3 camDir = *pCarPos - *pCameraPos; + float camDist = bLength(&camDir); + + if (camDist >= 0.1f) { + bVector3 camVec = bNormalize(camDir); + bVector3 upVec(0.0f, 1.0f, 0.0f); + bVector3 leftVec; + bCross(&leftVec, &upVec, &camVec); + + const int kProbeSize = 2; + bVector3 camVecProbe[2]; + + float sideMargin = fovScale * camDist * 0.5f * 0.85f; + bScaleAdd(&camVecProbe[0], &camDir, &leftVec, sideMargin); + bScaleAdd(&camVecProbe[1], &camDir, &leftVec, -sideMargin); + + float clearance = 0.0f; + for (int i = 0; i < kProbeSize; ++i) { + bVector3 probeVec; + bNormalize(&probeVec, &camVecProbe[i]); + + bVector3 vCameraPosBackClearance; + bScaleAdd(&vCameraPosBackClearance, pCameraPos, &probeVec, -0.5f); + + UMath::Vector4 camSeg[2]; + eUnSwizzleWorldVector(*pCarPos, reinterpret_cast(camSeg[0])); + eUnSwizzleWorldVector(vCameraPosBackClearance, reinterpret_cast(camSeg[1])); + camSeg[0].w = 0.0f; + camSeg[1].w = 0.0f; + + WCollisionMgr::WorldCollisionInfo cInfo; + WCollisionMgr collision_mgr(0, 3); + + if (collision_mgr.CheckHitWorld(camSeg, cInfo, 2)) { + bViolates = true; + bVector3 collisionPt; + bVector3 collisionNorm; + eSwizzleWorldVector(reinterpret_cast(cInfo.fCollidePt), collisionPt); + eSwizzleWorldVector(reinterpret_cast(cInfo.fNormal), collisionNorm); + + float dot = camDir.x * collisionNorm.x + camDir.y * collisionNorm.y + camDir.z * collisionNorm.z; + float normalFactor = (1.0f - bAbs(dot)) * 2.0f + 1.0f; + float oldDist = bDistBetween(pCarPos, &vCameraPosBackClearance); + float newDist = bDistBetween(pCarPos, &collisionPt); + float newClearance = (oldDist - newDist) * normalFactor; + if (clearance < newClearance) { + clearance = newClearance; + } + } + } + + float fSmoothed = (fAccumulatedClearance + clearance) * 0.5f; + fAccumulatedClearance = fAccumulatedClearance + (clearance - fSmoothed); + if (0.0f < fSmoothed) { + vAdjust.x = camVec.x * fSmoothed; + vAdjust.y = camVec.y * fSmoothed; + vAdjust.z = camVec.z * fSmoothed; + } + + pAdjust->w = 0.0f; + pAdjust->x = vAdjust.x; + pAdjust->y = vAdjust.y; + pAdjust->z = vAdjust.z; + } + } + + return bViolates; +} + +unsigned short CameraMover::GetLookbackAngle() { + return 0; +} + +void CameraMover::ResetState() {} + +unsigned int CameraMover::GetAnchorID() { + CameraAnchor *anchor = GetAnchor(); + if (anchor) { + return anchor->GetWorldID(); + } + return 0; +} + +bVector3 *CameraMover::GetTarget() { + return pCamera->GetTarget(); +} + +void CameraMover::HandheldNoise(bMatrix4 *world_to_camera, float f_scale, bool useWorldTimer) { + if (f_scale <= 0.0f) { + return; + } + + bVector4 v_frequency; + bVector4 v_magnitude; + bScale(&v_frequency, &CameraNoiseHandheldFrequency, 1.0f); + bScale(&v_magnitude, &CameraNoiseHandheldAmplitude, f_scale); + + pCamera->SetNoiseFrequency1(&v_frequency); + pCamera->SetNoiseAmplitude1(&v_magnitude); + float time = useWorldTimer ? WorldTimer.GetSeconds() : RealTimer.GetSeconds(); + pCamera->ApplyNoise(world_to_camera, time, 1.0f); +} + +void CameraMover::ChopperNoise(bMatrix4 *world_to_camera, float f_scale, bool useWorldTimer) { + if (f_scale <= 0.0f) { + return; + } + + const IVehicle::List &vehicles = IVehicle::GetList(VEHICLE_AICOPS); + + for (IVehicle::List::const_iterator iter = vehicles.begin(); iter != vehicles.end(); ++iter) { + IVehicle *vehicle = *iter; + + if (!vehicle->IsActive()) continue; + if (vehicle->GetVehicleClass() != VehicleClass::CHOPPER) continue; + + const UMath::Vector3 &pos = vehicle->GetPosition(); + bVector3 bpos; + bVector3 dir; + float distance; + + eSwizzleWorldVector(pos, bpos); + bSub(&dir, &bpos, pCamera->GetPosition()); + dir.z = 0.0f; + distance = bLength(&dir); + + if (distance < 100.0f) { + float intensity = f_scale * (1.0f - distance * 0.01f); + bVector4 v_frequency; + bVector4 v_magnitude; + float time; + + bScale(&v_frequency, &CameraNoiseChopperFrequency, intensity); + bScale(&v_magnitude, &CameraNoiseChopperAmplitude, intensity); + pCamera->SetNoiseFrequency1(&v_frequency); + pCamera->SetNoiseAmplitude1(&v_magnitude); + time = useWorldTimer ? WorldTimer.GetSeconds() : RealTimer.GetSeconds(); + pCamera->ApplyNoise(world_to_camera, time, 1.0f); + } + } +} + +void CameraMover::TerrainVelocityNoise(bMatrix4 *world_to_camera, CameraAnchor *p_car, float f_speed_scale, float f_terrain_scale) { + if (!p_car) { + return; + } + + const float speed_tresh = 5.0f; + + bVector4 v_speed_terrain_freq; + tTable speed_table(const_cast(CameraNoiseSpeedData), 5, 0.0f, 80.0f); + speed_table.GetValue(&v_speed_terrain_freq, p_car->GetVelocityMagnitude()); + + float f_road_noise_amplitude = p_car->GetSurface().CAMERA_NOISE(0); + float f_road_noise_grid_spacing_inverse = p_car->GetSurface().CAMERA_NOISE(1); + + float f_speed_magnitude = f_speed_scale * v_speed_terrain_freq.x; + float f_speed_frequency = v_speed_terrain_freq.z; + + if (p_car->IsDragRace()) { + f_speed_magnitude *= 1.5f; + } + + if (p_car->GetVelocityMagnitude() > speed_tresh) { + const float accel_max = 20.0f; + float accel = bDot(p_car->GetAcceleration(), p_car->GetForwardVector()); + accel = bClamp(accel, 0.0f, accel_max); + accel *= 0.05f; + f_speed_magnitude += accel * 0.15f; + f_speed_frequency += (0.5f - f_speed_frequency) * accel; + } + + if (p_car->IsOverRev()) { + f_speed_magnitude += 0.15f; + f_speed_frequency *= 0.7f; + } + + if (p_car->IsNosEngaged() && p_car->GetVelocityMagnitude() > speed_tresh) { + f_speed_magnitude = 0.3f; + f_speed_frequency = 2.0f; + } + + if (p_car->IsBrakeEngaged() && p_car->GetVelocityMagnitude() > speed_tresh) { + f_speed_magnitude = 0.3f; + f_speed_frequency = speed_tresh; + } + + float f_terrain_magnitude = f_terrain_scale * v_speed_terrain_freq.y * f_road_noise_amplitude; + float f_terrain_frequency = v_speed_terrain_freq.w * f_road_noise_grid_spacing_inverse; + + if (!OutsidePOV()) { + f_speed_magnitude *= 0.25f; + f_terrain_magnitude *= 0.25f; + } + + bVector4 v_speed_frequency; + bVector4 v_speed_magnitude; + bScale(&v_speed_frequency, &CameraNoiseSpeedFrequency, f_speed_frequency); + bScale(&v_speed_magnitude, &CameraNoiseSpeedAmplitude, f_speed_magnitude); + pCamera->SetNoiseFrequency1(&v_speed_frequency); + pCamera->SetNoiseAmplitude1(&v_speed_magnitude); + + bVector4 v_terrain_frequency; + bVector4 v_terrain_magnitude; + bScale(&v_terrain_frequency, &CameraNoiseTerrainFrequency, f_terrain_frequency); + bScale(&v_terrain_magnitude, &CameraNoiseTerrainAmplitude, f_terrain_magnitude); + pCamera->SetNoiseFrequency2(&v_terrain_frequency); + pCamera->SetNoiseAmplitude2(&v_terrain_magnitude); + + pCamera->ApplyNoise(world_to_camera, WorldTimer.GetSeconds(), 1.0f); +} + +void CameraMover::ComputeBankedUpVector(bVector3 *up, bVector3 *eye, bVector3 *look, unsigned short bank) { + bMatrix4 axis_rotation; + bVector3 axis; + + bSub(&axis, look, eye); + bNormalize(&axis, &axis); + eCreateAxisRotationMatrix(&axis_rotation, axis, bank); + bVector3 new_up(0.0f, 0.0f, 1.0f); + eMulVector(up, &axis_rotation, &new_up); +} + +void CameraMover::IsoProjectionMatrix(bMatrix4 *pMatrix, bVector3 *pEye, bVector3 *pLook, bVector2 *pProjection) { + bVector3 vUp(-pMatrix->v0.y, -pMatrix->v1.y, -pMatrix->v2.y); + bMatrix4 mWorldToCamera; + bMatrix4 mCameraToWorld; + bVector3 vNewLookCameraSpace; + bVector3 vNewLookWorldSpace; + + eCreateLookAtMatrix(&mWorldToCamera, *pEye, *pLook, vUp); + eInvertTransformationMatrix(&mCameraToWorld, &mWorldToCamera); + eMulVector(&vNewLookCameraSpace, &mWorldToCamera, pLook); + vNewLookCameraSpace.x -= vNewLookCameraSpace.z * pProjection->x; + vNewLookCameraSpace.y -= vNewLookCameraSpace.z * pProjection->y; + eMulVector(&vNewLookWorldSpace, &mCameraToWorld, &vNewLookCameraSpace); + eCreateLookAtMatrix(pMatrix, *pEye, vNewLookWorldSpace, vUp); +} + +float CameraMover::AdjustHeightAroundCar(const bVector3 *position, bVector3 *pCarPos, bVector3 *pForward) { +#ifndef EA_PLATFORM_PLAYSTATION2 + _STL::list >::const_iterator iter; + + for (iter = TheAvoidables->begin(); iter != TheAvoidables->end(); ++iter) { + IBody *car = *iter; + UMath::Matrix4 umatrix; + bMatrix4 matrix; + + car->GetTransform(umatrix); + eSwizzleWorldMatrix(reinterpret_cast(umatrix), matrix); + + const bVector3 *car_position = reinterpret_cast(&matrix.v3); + UMath::Vector3 dim; + bVector3 box; + bVector2 eye_to_car = *reinterpret_cast(const_cast(car_position)) - *reinterpret_cast(position); + float gap_squared = bDot(&eye_to_car, &eye_to_car); + float gap_height = bAbs(car_position->z - position->z); + + car->GetDimension(dim); + bFill(&box, dim.z + 0.85f, dim.x + 0.85f, dim.y + 0.85f); + + float min_gap = bLength(reinterpret_cast(&box)); + float min_gap_squared = min_gap * min_gap; + + if (gap_squared < min_gap_squared && gap_height < box.z + box.z) { + bMatrix4 mWorldToCar; + bVector3 vCameraCarSpace; + + eInvertTransformationMatrix(&mWorldToCar, &matrix); + eMulVector(&vCameraCarSpace, &mWorldToCar, position); + + float cam_x4 = vCameraCarSpace.x * vCameraCarSpace.x; + float cam_y4 = vCameraCarSpace.y * vCameraCarSpace.y; + float box_x4 = box.x * box.x; + float box_y4 = box.y * box.y; + cam_x4 *= cam_x4; + cam_y4 *= cam_y4; + box_x4 *= box_x4; + box_y4 *= box_y4; + float m = cam_x4 / box_x4 + cam_y4 / box_y4; + + if (m < 1.0f) { + float remaining = 1.0f - m; + float sqrt_remaining = bSqrt(remaining); + float sqrt_sqrt = bSqrt(sqrt_remaining); + float new_z = sqrt_sqrt * box.z; + + if (new_z > vCameraCarSpace.z) { + vCameraCarSpace.z = new_z; + bVector3 vNewCam; + eMulVector(&vNewCam, &matrix, &vCameraCarSpace); + float zdiff = vNewCam.z - position->z; + if (zdiff > min_gap) { + zdiff = 0.0f; + } + return zdiff; + } + } + } + } +#endif + return 0.0f; +} + +bVector3 *CameraMover::DutchAroundCar(bVector3 *pCarPos, bVector3 *pCarVelocity) { + static bVector3 ret(0.0f, 0.0f, 0.0f); + + ret.x = 0.0f; + ret.z = 0.0f; + ret.y = 0.0f; + + { +#ifndef EA_PLATFORM_PLAYSTATION2 + _STL::list >::const_iterator iter; + + for (iter = TheAvoidables->begin(); iter != TheAvoidables->end(); ++iter) { + IBody *car = *iter; + UMath::Matrix4 umatrix; + bMatrix4 matrix; + + car->GetTransform(umatrix); + eSwizzleWorldMatrix(reinterpret_cast(umatrix), matrix); + + const bVector3 *car_position = reinterpret_cast(&matrix.v3); + bVector3 eye_to_car; + bSub(&eye_to_car, car_position, pCarPos); + float gap_squared = bDot(&eye_to_car, &eye_to_car); + + if (gap_squared > 100.0f && gap_squared < 10000.0f) { + bVector3 unitEyeVelocity; + bNormalize(&eye_to_car, &eye_to_car); + bNormalize(&unitEyeVelocity, pCarVelocity); + + float dot = bDot(&eye_to_car, &unitEyeVelocity); + + if (dot > 0.5f) { + float gap_factor = bClamp(1000.0f / gap_squared, 0.0f, 1.0f); + + UMath::Vector3 uvelocity; + car->GetLinearVelocity(uvelocity); + bVector3 velocity; + eSwizzleWorldVector(reinterpret_cast(uvelocity), velocity); + + float vel_diff = bDistBetween(pCarVelocity, &velocity) - 5.0f; + float vel_factor = bClamp(vel_diff * 0.1f, 0.0f, 1.0f); + + bVector3 unitVelocity; + bNormalize(&unitVelocity, &velocity); + bNormalize(&unitEyeVelocity, pCarVelocity); + + float vel_dot = bDot(&unitEyeVelocity, &unitVelocity); + float dot_factor = 0.0f; + if (vel_dot < 0.0f) { + dot_factor = -vel_dot; + } + + bScaleAdd(&ret, &ret, &eye_to_car, gap_factor * (vel_factor + dot_factor)); + } + } + } +#endif + } + + return &ret; +} + +int CameraMover::MinGapCars(bMatrix4 *pMatrix, bVector3 *pLook, bVector3 *pForward) { + bMatrix4 mCameraToWorld; + + eInvertTransformationMatrix(&mCameraToWorld, pMatrix); + bool ret = false; + bVector3 *pCameraPos = reinterpret_cast(&mCameraToWorld.v3); + float old_z = pCameraPos->z; + int i = 0; + bVector3 vCarCameraSpace; + float adjust; + + for (;;) { + adjust = AdjustHeightAroundCar(pCameraPos, pLook, pForward); + if (i > 15) break; + if (adjust <= 0.0f) break; + ret = true; + i++; + pCameraPos->z += adjust; + AdjustHeightAroundCar(pCameraPos, pLook, pForward); + } + + { + float fwd_dot = bDot(&vSavedForward, reinterpret_cast(pMatrix)); + bCopy(&vSavedForward, reinterpret_cast(pMatrix)); + + float total_adjust = pCameraPos->z - old_z; + float speed2D = bLength(reinterpret_cast(pForward)); + + if (speed2D < 1.0f && fwd_dot > 0.9f && total_adjust < fSavedAdjust) { + total_adjust = fSavedAdjust; + } + + fSavedAdjust = total_adjust; + float avg = (fAccumulatedAdjust + total_adjust) * 0.5f; + fAccumulatedAdjust += total_adjust - avg; + pCameraPos->z = old_z + avg; + } + + eMulVector(&vCarCameraSpace, pMatrix, pLook); + + if (vCarCameraSpace.z > 0.0f) { + bVector2 vProjection(vCarCameraSpace.x / vCarCameraSpace.z, vCarCameraSpace.y / vCarCameraSpace.z); + IsoProjectionMatrix(pMatrix, pCameraPos, pLook, &vProjection); + } else { + ret = false; + } + + return ret; +} + +bool CameraMover::MinGapTopology(bMatrix4 *pMatrix, bVector3 *pCarPos) { + bMatrix4 mCameraToWorld; + bVector3 vCenter; + bVector4 vAdjust; + UMath::Vector3 usCamPos; + UMath::Vector3 usCarPos; + UMath::Vector4 seg[2]; + bVector3 vCarCameraSpace; + bVector2 vProjection; + + eInvertTransformationMatrix(&mCameraToWorld, pMatrix); + bVector3 *pCameraPos = reinterpret_cast(&mCameraToWorld.v3); + + bScale(&vCenter, pCameraPos, 0.5f); + bScaleAdd(&vCenter, &vCenter, pCarPos, 0.5f); + + vAdjust.x = 0.0f; + vAdjust.y = 0.0f; + vAdjust.z = 0.0f; + vAdjust.w = 1.0f; + + float fRadius = bDistBetween(pCarPos, pCameraPos); + fRadius = (fRadius + 1.0f) * 0.5f; + if (fRadius > 50.0f) { + fRadius = 50.0f; + } + + eUnSwizzleWorldVector(reinterpret_cast(vCenter), reinterpret_cast(usCamPos)); + mCollider->Refresh(usCamPos, fRadius, true); + + eUnSwizzleWorldVector(*pCarPos, reinterpret_cast(usCarPos)); + eUnSwizzleWorldVector(*pCameraPos, reinterpret_cast(usCamPos)); + + seg[0] = UMath::Vector4Make(usCamPos, 1.0f); + seg[1] = UMath::Vector4Make(usCarPos, 1.0f); + + bool bViolates = EnforceMinGapToWalls(mCollider, pCarPos, pCameraPos, &vAdjust); + + pCameraPos->x += vAdjust.x; + pCameraPos->y += vAdjust.y; + pCameraPos->z += vAdjust.z; + + int inTunnel = AmIinATunnel(&eViews[ViewID], 1); + + eUnSwizzleWorldVector(*pCameraPos, reinterpret_cast(usCamPos)); + + if (!inTunnel && usCamPos.y < pCarPos->z + 0.5f) { + usCamPos.y = pCarPos->z + 0.5f; + } + + { + WWorldPos temp(0.5f); + temp.FindClosestFace(mCollider, usCamPos, true); + + if (temp.OnValidFace()) { + UMath::Vector3 norm; + temp.UNormal(&norm); + + if (norm.y < 0.0f) { + norm.y = -norm.y; + norm.x = -norm.x; + norm.z = -norm.z; + } + if (0.95f <= norm.y) { + norm.y = 0.95f; + } + + float height = WWorldMath::GetPlaneY(norm, UMath::Vector4To3(temp.FacePoint(0)), usCamPos); + if (pCameraPos->z < height + 1.0f) { + pCameraPos->z = height + 1.0f; + } + if (inTunnel && (height + 3.0f < pCameraPos->z)) { + pCameraPos->z = height + 3.0f; + } + } + } + + eMulVector(&vCarCameraSpace, pMatrix, pCarPos); + if (vCarCameraSpace.z <= 0.0f) { + return bViolates; + } + + vProjection = bVector2(vCarCameraSpace.x / vCarCameraSpace.z, vCarCameraSpace.y / vCarCameraSpace.z); + IsoProjectionMatrix(pMatrix, pCameraPos, pCarPos, &vProjection); + + return bViolates; +} + +void CameraMover::FovCubicInit(tCubic1D *cubic) { + float fov = static_cast(pCamera->GetFov()) + static_cast(pCamera->GetVelocityFov()) * (1.0f / 30.0f); + float fov_velocity = static_cast(pCamera->GetVelocityFov()) * cubic->duration; + + cubic->SetVal(fov); + cubic->SetdVal(fov_velocity); +} + +void CameraMover::EyeCubicInit(tCubic3D *pEye, bMatrix4 *pMatrix, bVector3 *pVelocity) { + bVector3 vEye; + bScaleAdd(&vEye, pCamera->GetPosition(), pCamera->GetVelocityPosition(), 1.0f / 30.0f); + + if (pMatrix) { + bMulMatrix(&vEye, pMatrix, &vEye); + } + + pEye->SetVal(&vEye); + + bVector3 vEyeRel(*pCamera->GetVelocityPosition()); + + if (pVelocity) { + bSub(&vEyeRel, &vEyeRel, pVelocity); + } + + bVector4 vEyeVel; + bScale(reinterpret_cast(&vEyeVel), &vEyeRel, pEye->x.duration); + vEyeVel.w = 0.0f; + + if (pMatrix) { + bMulMatrix(&vEyeVel, pMatrix, &vEyeVel); + } + + pEye->SetdVal(reinterpret_cast(&vEyeVel)); +} + +void CameraMover::LookCubicInit(tCubic3D *pLook, bMatrix4 *pMatrix, bVector3 *pVelocity) { + bVector3 vLook; + bScaleAdd(&vLook, pCamera->GetTarget(), pCamera->GetVelocityTarget(), 1.0f / 30.0f); + + if (pMatrix) { + bMulMatrix(&vLook, pMatrix, &vLook); + } + + pLook->SetVal(&vLook); + + bVector3 vLookRel(*pCamera->GetVelocityTarget()); + + if (pVelocity) { + bSub(&vLookRel, &vLookRel, pVelocity); + } + + bVector4 vLookVel; + bScale(reinterpret_cast(&vLookVel), &vLookRel, pLook->x.duration); + vLookVel.w = 0.0f; + + if (pMatrix) { + bMulMatrix(&vLookVel, pMatrix, &vLookVel); + } + + pLook->SetdVal(reinterpret_cast(&vLookVel)); +} + +void CameraMover::SetEyeLook(tCubic3D *eye, tCubic3D *look, tCubic1D *fov, bMatrix4 *matrix, bVector3 *target) { + FovCubicInit(fov); + EyeCubicInit(eye, matrix, target); + LookCubicInit(look, matrix, target); +} + +CameraAnchor *CubicCameraMover::GetAnchor() { + return pCar; +} + +void CubicCameraMover::SetLookBack(bool b) { + bLookBack = b; +} + +void CubicCameraMover::SetDisableLag(bool disable) { + bAccelLag = !disable; +} + +bool CubicCameraMover::OutsidePOV() { + return OutsidePovType(nPovTypeUsed); +} + +bool CubicCameraMover::RenderCarPOV() { + return RenderCarPovTypeRaw(nPovTypeUsed, bLookBack); +} + +float CubicCameraMover::MinDistToWall() { + return 0.7f; +} + +bool CubicCameraMover::HighliteMode() { + return false; +} + +void CubicCameraMover::SetSnapNext() { + if (fIgnoreSetSnapNextTimer > 0.0f) { + return; + } + bSnapNext = 1; +} + +unsigned short CubicCameraMover::GetLookbackAngle() { + if (bLookBack == 0) { + return 0; + } + return 0x8000; +} + +bool CubicCameraMover::IsHoodCamera() { + bool is_hood_camera = false; + + if (nPovTypeUsed == 1) { + is_hood_camera = !bLookBack; + } + return is_hood_camera; +} + +void CubicCameraMover::SetForward(POV *pov, bool bSnap) { + if (pov && OutsidePovType(pov->Type)) { + if (!bSnap && HighliteMode()) { + return; + } + + bVector3 v(*pCar->GetVelocity()); + const bVector3 *pFwd = pCar->GetForwardVector(); + float fDot = bDot(&v, pFwd); + + if (fDot < 0.0f) { + bScaleAdd(&v, &v, pFwd, fDot * -2.0f); + } + + float fDrift = gDriftSpeed.GetValue(pCar->GetVelocityMagnitude()); + bNormalize(&v, &v); + bScale(&v, &v, fDrift); + bScaleAdd(&v, &v, pFwd, 1.0f - fDrift); + + float fSeconds = (WorldTimer - tLastGrounded).GetSeconds(); + float z = bClamp(1.0f - fSeconds * 0.5f, 0.0f, 1.0f); + v.z *= z; + v.z *= 1.0f - pCar->GetForwardVector()->z * pCar->GetForwardVector()->z; + + pForward->SetValDesired(&v); + + if (!bSnap) { + return; + } + } else { + if (!pCar) { + return; + } + pForward->SetValDesired(pCar->GetForwardVector()); + } + + pForward->Snap(); +} + +void CubicCameraMover::CameraAccelCurve(bVector3 *pAccel) { + tTable accel_table(CameraAccelerationCurve, 5, -30.0f, 30.0f); + accel_table.GetValue(&pAccel->x, pCar->GetAcceleration()->x); + accel_table.GetValue(&pAccel->y, pCar->GetAcceleration()->y); + accel_table.GetValue(&pAccel->z, pCar->GetAcceleration()->z); + bScale(pAccel, pAccel, 30.0f); +} + +void CubicCameraMover::CameraSpeedHug(bVector3 *pEyeOffset) { + float f_top_speed = pCar->GetTopSpeed(); + if (f_top_speed > 0.0f) { + tTable speed_table(CameraSpeedHugData, 5, 0.0f, f_top_speed); + bVector2 v_speed_hug; + speed_table.GetValue(&v_speed_hug, pCar->GetVelocityMagnitude()); + + pEyeOffset->x *= v_speed_hug.x; + pEyeOffset->z *= v_speed_hug.y; + } +} + +void CubicCameraMover::MakeSpace(bMatrix4 *pMatrix) { + if (OutsidePOV()) { + bIdentity(pMatrix); + + bVector3 vForward(pForward->x.Val, pForward->y.Val, pForward->z.Val); + bNormalize(reinterpret_cast(&pMatrix->v0), &vForward); + bCross(reinterpret_cast(&pMatrix->v1), reinterpret_cast(&pMatrix->v2), reinterpret_cast(&pMatrix->v0)); + bCross(reinterpret_cast(&pMatrix->v2), reinterpret_cast(&pMatrix->v0), reinterpret_cast(&pMatrix->v1)); + bCopy(&pMatrix->v3, pCar->GetGeometryPosition(), 1.0f); + } else { + bCopy(pMatrix, pCar->GetGeometryOrientation(), pCar->GetGeometryPosition()); + + if (pCar->GetGeometryOrientation()->v2.z < 0.5f) { + pMatrix->v1.x = -pMatrix->v1.x; + pMatrix->v1.y = -pMatrix->v1.y; + pMatrix->v1.z = -pMatrix->v1.z; + pMatrix->v1.w = -pMatrix->v1.w; + pMatrix->v2.x = -pMatrix->v2.x; + pMatrix->v2.y = -pMatrix->v2.y; + pMatrix->v2.z = -pMatrix->v2.z; + pMatrix->v2.w = -pMatrix->v2.w; + } + } +} + +CubicCameraMover::~CubicCameraMover() { + delete pUp; + delete pFov; + delete pEye; + delete pLook; + delete pForward; + delete reinterpret_cast(pAvgAccel); +} + +void CubicCameraMover::SetPovType(int pov_type) { + if (pov_type != nPovTypeUsed) { + bool old_outside = OutsidePovType(nPovTypeUsed); + bool new_outside = OutsidePovType(pov_type); + + bSnapNext = !!(bSnapNext | (!new_outside || !old_outside)); + nPovType = pov_type; + } +} + +void CubicCameraMover::ResetState() { + ResetCubic3DState(pUp); + ResetCubic1DState(pFov); + ResetCubic3DState(pEye); + ResetCubic3DState(pLook); + ResetCubic3DState(pForward); + GetCamera()->ClearVelocity(); + vCameraImpcat.y = 0.0f; + vCameraImpcat.x = 0.0f; + vCameraImpcatTimer.y = 0.0f; + vCameraImpcatTimer.x = 0.0f; +} + +Bezier::Bezier() + : pControlPoints(nullptr) { + mBasis.v0.x = 1.0f; + mBasis.v0.y = -3.0f; + mBasis.v0.z = 3.0f; + mBasis.v0.w = -1.0f; + mBasis.v1.x = 0.0f; + mBasis.v1.y = 3.0f; + mBasis.v1.z = -6.0f; + mBasis.v1.w = 3.0f; + mBasis.v2.x = 0.0f; + mBasis.v2.y = 0.0f; + mBasis.v2.z = 3.0f; + mBasis.v2.w = -3.0f; + mBasis.v3.x = 0.0f; + mBasis.v3.y = 0.0f; + mBasis.v3.z = 0.0f; + mBasis.v3.w = 1.0f; +} + +void Bezier::GetPoint(bVector3 *pPoint, float parameter) { + if (pControlPoints) { + bVector4 basis; + bVector4 v; + float t = 1.0f - parameter; + float t2 = t * t; + + basis.x = t * t2; + basis.y = t2; + basis.z = t; + basis.w = 1.0f; + + bMulMatrix(&v, &mBasis, &basis); + bMulMatrix(reinterpret_cast(pPoint), pControlPoints, &v); + } +} + +RearViewMirrorCameraMover::RearViewMirrorCameraMover(int view_id, CameraAnchor *car) + : CameraMover(view_id, CM_REAR_VIEW_MIRROR), + pCar(car) { + if (Camera::StopUpdating == 0) { + GetCamera()->SetRenderDash(0); + } +} + +RearViewMirrorCameraMover::~RearViewMirrorCameraMover() {} + +void RearViewMirrorCameraMover::Update(float dT) { + if (FEDatabase->GetGameplaySettings()->RearviewOn) { + bMatrix4 m; + bMatrix4 tbod; + bMatrix4 CarRotMat; + bMatrix4 rvm_matrix; + + eIdentity(&m); + m.v3.x = -pCar->GetGeometryPosition()->x; + m.v3.y = -pCar->GetGeometryPosition()->y; + m.v3.z = -pCar->GetGeometryPosition()->z; + bCopy(reinterpret_cast(&CarRotMat), reinterpret_cast(pCar->GetGeometryOrientation())); + eTransposeMatrix(&tbod, &CarRotMat); + eRotateX(&tbod, &tbod, 0x4000); + eRotateY(&tbod, &tbod, 0x4000); + eRotateZ(&tbod, &tbod, 0); + eMulMatrix(&rvm_matrix, &m, &tbod); + rvm_matrix.v3.x += RVMOffsetInCar.x; + rvm_matrix.v3.y += RVMOffsetInCar.y; + rvm_matrix.v3.z += RVMOffsetInCar.z; + GetCamera()->SetFieldOfView(20000); + GetCamera()->SetNearZ(RVMnearz); + GetCamera()->SetFarZ(RVMfarz); + ApplyCameraShake(ViewID, &rvm_matrix); + GetCamera()->SetCameraMatrix(rvm_matrix, dT); + } +} + +TrackCarCameraMover::TrackCarCameraMover(int nView, CameraAnchor *pCar, bool focus_effects) + : CameraMover(nView, CM_TRACK_CAR), + CarToFollow(pCar), + FocalDistCubic(1, 1.0f), + FocusEffects(focus_effects), + CameraType(0) { + Init(); +} + +TrackCarCameraMover::~TrackCarCameraMover() { + GetCamera()->SetSimTimeMultiplier(1.0f); + GetCamera()->SetFocalDistance(0.0f); + GetCamera()->SetDepthOfField(0.0f); + GetCamera()->ClearVelocity(); +} + +void TrackCarCameraMover::Update(float dT) { + if (!TheGameFlowManager.IsPaused()) { + bVector3 up(0.0f, 0.0f, 1.0f); + unsigned short fov; + + GetCamera()->SetSimTimeMultiplier(1.0f); + + bVector3 displacement = Eye - *CarToFollow->GetGeometryPosition(); + float distance = bLength(&displacement); + if (distance < 1.0f) { + distance = 1.0f; + } + + fov = bATan(distance, TrackCarIsoZoomDistance[CameraType]); + unsigned int x = static_cast(fov & 0x7fff) << 1; + if (x) { + int fov_limit = 800; + if (static_cast(x) > 800) { + fov_limit = static_cast(x); + } + if (fov_limit > 0x332c) { + fov_limit = 0x332c; + } + GetCamera()->SetFieldOfView(static_cast(fov_limit)); + } + + Look = *CarToFollow->GetGeometryPosition(); + displacement /= distance; + bVector3 hcomp; + bCross(&hcomp, &displacement, &up); + + bVector3 look_offset; + look_offset.x = TrackCarLookOffsetX[CameraType]; + look_offset.y = TrackCarLookOffsetY[CameraType]; + look_offset.z = TrackCarLookOffsetZ[CameraType]; + float vert_comp = 0.0f; + bScale(&hcomp, &hcomp, vert_comp); + eMulVector(&look_offset, CarToFollow->GetGeometryOrientation(), &look_offset); + + Look += look_offset; + bVector3 lookdir = Look - Eye; + bNormalize(&lookdir, &lookdir); + + bMatrix4 m; + eCreateLookAtMatrix(&m, Eye, Look, up); + float focal_dist = bDistBetween(CarToFollow->GetGeometryPosition(), &Eye); + GetCamera()->SetTargetDistance(focal_dist); + + FocalDistCubic.dValDesired = FocalDistCubic.Val * 0.1f; + SplineSeek(&FocalDistCubic, dT, vert_comp, vert_comp); + focal_dist += FocalDistCubic.Val; + if (focal_dist < 2.0f) { + focal_dist = 2.0f; + } + + if (FocusEffects) { + GetCamera()->SetFocalDistance(focal_dist + FocalDistCubic.Val); + GetCamera()->SetDepthOfField(2.0f); + } + + GetCamera()->SetCameraMatrix(m, dT); + if (IsSomethingInBetween(GetCamera()->GetPosition(), CarToFollow->GetGeometryPosition())) { + CameraAI::MaybeKillJumpCam(CarToFollow->GetWorldID()); + } + } +} + +TrackCopCameraMover::TrackCopCameraMover(int nView, CameraAnchor *pCar, bool focus_effects) + : CameraMover(nView, CM_TRACK_COP), + ZoomSplineParam(0.0f), // + EyeSplineParam(0.0f), // + LookSplineParam(0.0f), // + CarToFollow(pCar), + FocalDistCubic(1, 1.0f), + FocusEffects(focus_effects), + bRenderCarPOV(true) { + Init(); +} + +TrackCopCameraMover::~TrackCopCameraMover() { + TrackCopCameraMover_IdleSim = false; + GetCamera()->SetSimTimeMultiplier(1.0f); + GetCamera()->SetFocalDistance(0.0f); + GetCamera()->SetDepthOfField(0.0f); + GetCamera()->ClearVelocity(); +} + +void TrackCopCameraMover::Update(float dT) { + if (!FEManager::Get() || !FEManager::ShouldPauseSimulation(true)) { + bVector3 world_up(0.0f, 0.0f, 1.0f); + bVector3 eye; + bVector3 look; + bVector3 offset; + bVector3 displacement; + bVector3 hcomp; + bVector3 look_offset; + bMatrix4 camera_matrix; + float focal_distance; + + EyeSplineParam += dT * 0.25f; + if (EyeSplineParam > 1.0f) { + EyeSplineParam = 1.0f; + } + EyeSpline.GetPoint(&eye, EyeSplineParam); + + LookSplineParam += dT * 0.25f; + if (LookSplineParam > 1.0f) { + LookSplineParam = 1.0f; + } + LookSpline.GetPoint(&look, LookSplineParam); + + ZoomSplineParam += dT * 0.25f; + if (ZoomSplineParam > 1.0f) { + ZoomSplineParam = 1.0f; + } + ZoomSpline.GetPoint(reinterpret_cast(&offset), ZoomSplineParam); + + GetCamera()->SetFieldOfView(static_cast(bDegToAng(offset.x))); + + displacement = eye - look; + float distance = bLength(&displacement); + if (distance < 1.0f) { + distance = 1.0f; + } + displacement /= distance; + bCross(&hcomp, &displacement, &world_up); + + float vert_comp = 0.0f; + bScale(&hcomp, &hcomp, vert_comp); + look_offset.x = vert_comp; + look_offset.y = vert_comp; + look_offset.z = vert_comp; + + eMulVector(&look_offset, CarToFollow->GetGeometryOrientation(), &look_offset); + look += look_offset; + + eCreateLookAtMatrix(&camera_matrix, eye, look, world_up); + focal_distance = bDistBetween(CarToFollow->GetGeomPos(), &eye); + GetCamera()->SetTargetDistance(focal_distance); + + FocalDistCubic.dValDesired = FocalDistCubic.Val * 0.1f; + SplineSeek(&FocalDistCubic, dT, 0.0f, 0.0f); + focal_distance += FocalDistCubic.Val; + if (focal_distance < 2.0f) { + focal_distance = 2.0f; + } + + if (FocusEffects) { + GetCamera()->SetFocalDistance(focal_distance + FocalDistCubic.Val); + GetCamera()->SetDepthOfField(2.0f); + } + + GetCamera()->SetCameraMatrix(camera_matrix, dT); + } +} + +CameraAnchor *RearViewMirrorCameraMover::GetAnchor() { + return pCar; +} + +CameraAnchor *TrackCarCameraMover::GetAnchor() { + return CarToFollow; +} + +bVector3 *TrackCarCameraMover::GetTarget() { + CameraAnchor *car = CarToFollow; + if (car) { + return car->GetGeomPos(); + } + return GetCamera()->GetTarget(); +} + +CameraAnchor *TrackCopCameraMover::GetAnchor() { + return CarToFollow; +} + +bool TrackCopCameraMover::RenderCarPOV() { + return bRenderCarPOV; +} + +bVector3 *TrackCopCameraMover::GetTarget() { + CameraAnchor *car = CarToFollow; + if (car) { + return car->GetGeomPos(); + } + return GetCamera()->GetTarget(); +} diff --git a/src/Speed/Indep/Src/Camera/CameraMover.hpp b/src/Speed/Indep/Src/Camera/CameraMover.hpp index e2ef17946..a3b680196 100644 --- a/src/Speed/Indep/Src/Camera/CameraMover.hpp +++ b/src/Speed/Indep/Src/Camera/CameraMover.hpp @@ -8,12 +8,135 @@ #include "./Camera.hpp" #include "Speed/Indep/Src/Generated/AttribSys/Classes/camerainfo.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" #include "Speed/Indep/Src/Sim/SimSurface.h" #include "Speed/Indep/Src/World/WCollisionMgr.h" #include "Speed/Indep/Src/World/WWorldPos.h" #include "Speed/Indep/bWare/Inc/bList.hpp" class eView; +class tCubic3D; +class ActionQueue; +template class tAverage; + +struct tCubic1D { + float Val; // offset 0x0, size 0x4 + float dVal; // offset 0x4, size 0x4 + float ValDesired; // offset 0x8, size 0x4 + float dValDesired; // offset 0xC, size 0x4 + float Coeff[4]; // offset 0x10, size 0x10 + float time; // offset 0x20, size 0x4 + float duration; // offset 0x24, size 0x4 + short state; // offset 0x28, size 0x2 + short flags; // offset 0x2A, size 0x2 + + tCubic1D(short type, float dur) + : Val(0.0f), // + dVal(0.0f), // + ValDesired(0.0f), // + dValDesired(0.0f), // + time(0.0f), // + duration(dur), // + state(0), // + flags(type) { + Coeff[0] = 0.0f; + Coeff[1] = 0.0f; + Coeff[2] = 0.0f; + Coeff[3] = 0.0f; + } + + void SetVal(const float v) { + Val = v; + if (v != ValDesired) { + state = 2; + } + } + + void SetdVal(float v) { + dVal = v; + if (v != dValDesired) { + state = 2; + } + } + + void SetValDesired(float v) { + ValDesired = v; + if (v != Val) { + state = 2; + } + } + + void SetDuration(const float t) { + duration = t; + } + + void SetFlags(short f) { + flags = f; + } + + void Update(float fSeconds, float fDClamp, float fDDClamp); + + void Snap() { + Val = ValDesired; + dVal = dValDesired; + state = 0; + } +}; + +struct tCubic3D { + tCubic1D x; // offset 0x0, size 0x2C + tCubic1D y; // offset 0x2C, size 0x2C + tCubic1D z; // offset 0x58, size 0x2C + + tCubic3D(short type, float dur) + : x(type, dur), // + y(type, dur), // + z(type, dur) {} + + tCubic3D(short type, bVector3 *pDuration) + : x(type, pDuration->x), // + y(type, pDuration->y), // + z(type, pDuration->z) {} + + void SetVal(const bVector3 *pV); + void SetdVal(bVector3 *pV); + void SetValDesired(bVector3 *pV); + + void GetVal(bVector3 *pV); + void GetValDesired(bVector3 *pV); + + void Snap() { + x.Snap(); + y.Snap(); + z.Snap(); + } + + void SetDuration(const float t) { + x.SetDuration(t); + y.SetDuration(t); + z.SetDuration(t); + } + + void SetDuration(const float tx, const float ty, const float tz) { + x.SetDuration(tx); + y.SetDuration(ty); + z.SetDuration(tz); + } + + void Update(float fSeconds, float fDClamp, float fDDClamp); +}; + +struct Bezier { + bMatrix4 *pControlPoints; // offset 0x0, size 0x4 + bMatrix4 mBasis; // offset 0x4, size 0x40 + + Bezier(); + void GetPoint(bVector3 *pPoint, float parameter); + + void SetControlPoints(bMatrix4 *pPoints) { + pControlPoints = pPoints; + } +}; enum CameraMoverTypes { CM_NONE_TYPE = 0, @@ -40,10 +163,134 @@ enum CameraMoverTypes { // total size: 0x124 class CameraAnchor { public: + CameraAnchor(int model); + ~CameraAnchor(); + + bVector3 *GetGeomPos() { + return &mGeomPos; + } + + bVector3 *GetGeometryPosition() { + return &mGeomPos; + } + + bVector3 *GetVelocity() { + return &mVelocity; + } + + const bVector3 *GetVelocity() const { + return &mVelocity; + } + + const bVector3 *GetAccel() const { + return &mAccel; + } + + bMatrix4 *GetMatrix() { + return &mGeomRot; + } + + bMatrix4 *GetGeometryOrientation() { + return &mGeomRot; + } + + float GetVelMag() const { + return mVelMag; + } + + float GetVelocityMagnitude() const { + return mVelMag; + } + + float GetTopSpeed() const { + return mTopSpeed; + } + + const SimSurface &GetSurface() const { + return mSurface; + } + + bool IsNosEngaged() const { + return mIsNosEngaged; + } + + bool IsBrakeEngaged() const { + return mIsBrakeEngaged; + } + + bool IsDragRace() const { + return mIsDragRace; + } + + bool IsOverRev() const { + return mIsOverRev; + } + unsigned int GetWorldID() const { return mWorldID; } + void SetWorldID(unsigned int id) { + mWorldID = id; + } + + bool IsTouchingGround() const { + return mIsTouchingGround; + } + + bool IsGearChanging() const { + return mIsGearChanging; + } + + bool IsCloseToRoadBlock() const { + return mIsCloseToRoadBlock; + } + + float GetCollisionDamping() const { + return mCollisionDamping; + } + + float GetDrift() const { + return mDrift; + } + + bVector3 *GetUpVector() { + return reinterpret_cast(&mGeomRot.v1); + } + + bVector3 *GetForwardVector() { + return reinterpret_cast(&mGeomRot.v0); + } + + bVector3 *GetLeftVector() { + return reinterpret_cast(&mGeomRot.v1); + } + + void Update(float dT, const bMatrix4 &matrix, const bVector3 &velocity, const bVector3 &forward); + void SetModel(int model); + void SetZoom(float z) { mZoom = z; } + + void SetDimension(const bVector3 &dim) { mDimension = dim; } + void SetTopSpeed(float s) { mTopSpeed = s; } + + void SetVehicleDestroyed(bool destroyed) { mIsVehicleDestroyed = destroyed; } + void SetCloseToRoadBlock(bool close) { mIsCloseToRoadBlock = close; } + void SetBrakeEngaged(bool engaged) { mIsBrakeEngaged = engaged; } + void SetDragRace(bool drag) { mIsDragRace = drag; } + void SetSurface(const SimSurface &surface) { mSurface = surface; } + void SetTouchingGround(bool touchingGround) { mIsTouchingGround = touchingGround; } + void SetNosEngaged(bool engaged) { mIsNosEngaged = engaged; } + void SetOverRev(bool overRev) { mIsOverRev = overRev; } + void SetDrift(float amount) { mDrift = amount; } + void SetGearChanging(bool changing) { mIsGearChanging = changing; } + void SetCollisionDamping(float amount) { mCollisionDamping = amount; } + void SetGroundCollision(float amount) { mGroundCollision = amount; } + void SetObjectCollision(float amount) { mObjectCollision = amount; } + + bVector3 *GetAcceleration() { return &mAccel; } + + POV *GetPov(int pov_type); + private: bVector3 mVelocity; // offset 0x0, size 0x10 float mVelMag; // offset 0x10, size 0x4 @@ -77,6 +324,7 @@ class CameraAnchor { class CameraMover : public bTNode, public WCollisionMgr::ICollisionHandler { public: CameraMover(); + CameraMover(int view_id, CameraMoverTypes type); CameraMoverTypes GetType() { return Type; @@ -86,13 +334,21 @@ class CameraMover : public bTNode, public WCollisionMgr::ICollision return pCamera->GetPosition(); } + bVector3 *GetDirection() { + return pCamera->GetDirection(); + } + + Camera *GetCamera() { + return pCamera; + } + // Virtual methods virtual ~CameraMover(); virtual void Update(float dT); virtual void Render(eView *view); - virtual CameraAnchor *GetAnchor() {} + virtual CameraAnchor *GetAnchor(); virtual void SetLookBack(bool b) {} @@ -102,25 +358,49 @@ class CameraMover : public bTNode, public WCollisionMgr::ICollision virtual void SetPovType(int pov_type) {} - virtual bool OutsidePOV() {} + virtual bool OutsidePOV(); - virtual bool RenderCarPOV() {} + virtual bool RenderCarPOV(); - virtual float MinDistToWall() {} + virtual float MinDistToWall(); - virtual unsigned short GetLookbackAngle() {} + virtual unsigned short GetLookbackAngle(); - virtual void ResetState() {} + virtual void ResetState(); // ICollisionHandler bool OnWCollide(const WCollisionMgr::WorldCollisionInfo &cInfo, const UMath::Vector3 &cPoint, void *userdata) override; virtual void Enable(); virtual void Disable(); + virtual bool IsHoodCamera() { return false; } - private: + virtual bVector3 *GetTarget(); + + bool IsSomethingInBetween(const UMath::Vector4 &start, const UMath::Vector4 &end); + bool IsSomethingInBetween(const bVector3 *start, const bVector3 *end); + void HandheldNoise(bMatrix4 *world_to_camera, float f_scale, bool useWorldTimer); + void ChopperNoise(bMatrix4 *world_to_camera, float f_scale, bool useWorldTimer); + void TerrainVelocityNoise(bMatrix4 *world_to_camera, CameraAnchor *car, float speed_scale, float terrain_scale); + static void ComputeBankedUpVector(bVector3 *up, bVector3 *eye, bVector3 *look, unsigned short bank); + void IsoProjectionMatrix(bMatrix4 *pMatrix, bVector3 *pEye, bVector3 *pLook, bVector2 *pProjection); + float AdjustHeightAroundCar(const bVector3 *car_pos, bVector3 *pEye, bVector3 *pForward); + int MinGapCars(bMatrix4 *pMatrix, bVector3 *pLook, bVector3 *pForward); + bool MinGapTopology(bMatrix4 *pMatrix, bVector3 *pCarPos); + bool EnforceMinGapToWalls(WCollider *collider, bVector3 *pCarPos, bVector3 *pCameraPos, bVector4 *pAdjust); + bVector3 *DutchAroundCar(bVector3 *pPos, bVector3 *pVelocity); + void FovCubicInit(tCubic1D *cubic); + void EyeCubicInit(tCubic3D *eye, bMatrix4 *matrix, bVector3 *target); + void LookCubicInit(tCubic3D *look, bMatrix4 *matrix, bVector3 *target); + void SetEyeLook(tCubic3D *eye, tCubic3D *look, tCubic1D *fov, bMatrix4 *matrix, bVector3 *target); + + unsigned int GetAnchorID(); + + protected: CameraMoverTypes Type; // offset 0xC, size 0x4 int ViewID; // offset 0x10, size 0x4 + + private: int Enabled; // offset 0x14, size 0x4 eView *pView; // offset 0x18, size 0x4 Camera *pCamera; // offset 0x1C, size 0x4 @@ -135,4 +415,201 @@ class CameraMover : public bTNode, public WCollisionMgr::ICollision void CameraMoverRestartRace(); +// total size: 0xB0 +struct CubicPovData { + float fEyeDuration; // offset 0x0, size 0x4 + float fLookDuration; // offset 0x4, size 0x4 + float fFovDuration; // offset 0x8, size 0x4 + float fUpDuration; // offset 0xC, size 0x4 + bVector3 vUpAccel; // offset 0x10, size 0x10 + bVector3 vUpAccelMin; // offset 0x20, size 0x10 + bVector3 vUpAccelMax; // offset 0x30, size 0x10 + bVector3 vEyeAccel; // offset 0x40, size 0x10 + bVector3 vEyeAccelMin; // offset 0x50, size 0x10 + bVector3 vEyeAccelMax; // offset 0x60, size 0x10 + bVector3 vLookAccel; // offset 0x70, size 0x10 + bVector3 vLookAccelMin; // offset 0x80, size 0x10 + bVector3 vLookAccelMax; // offset 0x90, size 0x10 + bVector3 vForwardDuration; // offset 0xA0, size 0x10 + + bVector3 *GetUpAccel() { return &vUpAccel; } + bVector3 *GetUpAccelMin() { return &vUpAccelMin; } + bVector3 *GetUpAccelMax() { return &vUpAccelMax; } + bVector3 *GetEyeAccel() { return &vEyeAccel; } + bVector3 *GetEyeAccelMin() { return &vEyeAccelMin; } + bVector3 *GetEyeAccelMax() { return &vEyeAccelMax; } + bVector3 *GetLookAccel() { return &vLookAccel; } + bVector3 *GetLookAccelMin() { return &vLookAccelMin; } + bVector3 *GetLookAccelMax() { return &vLookAccelMax; } + bVector3 *GetForwardDuration() { return &vForwardDuration; } +}; + +class CubicCameraMover : public CameraMover { + public: + CubicCameraMover(int nView, CameraAnchor *pCar, int pov_type, bool disable_lag, bool look_back, bool perfect_focus, bool snap_next); + virtual ~CubicCameraMover(); + + virtual void Update(float dT); + virtual CameraAnchor *GetAnchor(); + virtual void SetLookBack(bool b); + virtual void SetDisableLag(bool disable); + virtual void SetPovType(int pov_type); + virtual bool OutsidePOV(); + virtual bool RenderCarPOV(); + virtual float MinDistToWall(); + virtual unsigned short GetLookbackAngle(); + virtual void ResetState(); + + bool IsHoodCamera() override; + bool HighliteMode(); + void SetSnapNext(); + void SetForward(POV *pov, bool snap); + void MakeSpace(bMatrix4 *pMatrix); + void CameraAccelCurve(bVector3 *pAccel); + void CameraSpeedHug(bVector3 *pForward); + bool IsUnderVehicle(); + void SetDesired(bMatrix4 *pCarToWorld, POV *pov, CubicPovData *pov_data, bool bSnapForward); + + private: + CameraAnchor *pCar; // offset 0x80, size 0x4 + tCubic1D *pFov; // offset 0x84, size 0x4 + tCubic3D *pEye; // offset 0x88, size 0x4 + tCubic3D *pLook; // offset 0x8C, size 0x4 + tCubic3D *pForward; // offset 0x90, size 0x4 + tCubic3D *pUp; // offset 0x94, size 0x4 + int nPovType; // offset 0x98, size 0x4 + int nPovTypeUsed; // offset 0x9C, size 0x4 + int bAccelLag; // offset 0xA0, size 0x4 + int bLookBack; // offset 0xA4, size 0x4 + int bSnapNext; // offset 0xA8, size 0x4 + int bPerfectFocus; // offset 0xAC, size 0x4 + int bFirstTime; // offset 0xB0, size 0x4 + Timer tLastGrounded; // offset 0xB4, size 0x4 + Timer tLastUnderVehicle; // offset 0xB8, size 0x4 + Timer tLastGearChange; // offset 0xBC, size 0x4 + float fIgnoreSetSnapNextTimer; // offset 0xC0, size 0x4 + bVector3 vSavedEye; // offset 0xC4, size 0x10 + bVector2 vCameraImpcat; // offset 0xD4, size 0x8 + bVector2 vCameraImpcatTimer; // offset 0xDC, size 0x8 + tAverage *pAvgAccel; // offset 0xE4, size 0x4 +}; + +class RearViewMirrorCameraMover : public CameraMover { + public: + RearViewMirrorCameraMover(int view_id, CameraAnchor *car); + virtual ~RearViewMirrorCameraMover(); + + virtual void Update(float dT); + virtual CameraAnchor *GetAnchor(); + + private: + CameraAnchor *pCar; // offset 0x80, size 0x4 +}; + +class TrackCarCameraMover : public CameraMover { + public: + TrackCarCameraMover(int nView, CameraAnchor *pCar, bool focus_effects); + virtual ~TrackCarCameraMover(); + + virtual void Update(float dT); + virtual CameraAnchor *GetAnchor(); + virtual bVector3 *GetTarget(); + + private: + bVector3 Eye; // offset 0x80, size 0x10 + bVector3 Look; // offset 0x90, size 0x10 + CameraAnchor *CarToFollow; // offset 0xA0, size 0x4 + tCubic1D FocalDistCubic; // offset 0xA4, size 0x2C + int FocusEffects; // offset 0xD0, size 0x4 + int CameraType; // offset 0xD4, size 0x4 + + void Init(); +}; + +class TrackCopCameraMover : public CameraMover { + public: + TrackCopCameraMover(int nView, CameraAnchor *pCar, bool focus_effects); + virtual ~TrackCopCameraMover(); + + virtual void Update(float dT); + virtual CameraAnchor *GetAnchor(); + virtual bool RenderCarPOV(); + virtual bVector3 *GetTarget(); + void SetRenderCarPOV(bool val) { bRenderCarPOV = val; } + + private: + Bezier ZoomSpline; // offset 0x80, size 0x44 + bMatrix4 ZoomVerts; // offset 0xC4, size 0x40 + float ZoomSplineParam; // offset 0x104, size 0x4 + Bezier EyeSpline; // offset 0x108, size 0x44 + bMatrix4 EyeVerts; // offset 0x14C, size 0x40 + float EyeSplineParam; // offset 0x18C, size 0x4 + Bezier LookSpline; // offset 0x190, size 0x44 + bMatrix4 LookVerts; // offset 0x1D4, size 0x40 + float LookSplineParam; // offset 0x214, size 0x4 + CameraAnchor *CarToFollow; // offset 0x218, size 0x4 + tCubic1D FocalDistCubic; // offset 0x21C, size 0x2C + int FocusEffects; // offset 0x248, size 0x4 + public: + bool bRenderCarPOV; // offset 0x24C, size 0x4 + + bool FindPursuitVehiclePosition(bVector3 *pPosition); + void Init(); +}; + +enum JoystickPort { + JOYSTICK_PORT_NONE = -1, + JOYSTICK_PORT1 = 0, + JOYSTICK_PORT2 = 1, + JOYSTICK_PORT3 = 2, + JOYSTICK_PORT4 = 3, + JOYSTICK_PORT_ALL = 4, +}; + +class DebugWorldCameraMover : public CameraMover { + public: + DebugWorldCameraMover(int view_id, const bVector3 *start_position, const bVector3 *start_direction, JoystickPort jp); + ~DebugWorldCameraMover() override; + + void JoyHandler(); + void Update(float dT) override; + + static void SetEye(const bVector3 &eye) { + Eye = eye; + } + + static void SetLook(const bVector3 &look) { + Look = look; + } + + static const bVector3 &GetLook() { + return Look; + } + + static const bVector3 &GetEye() { + return Eye; + } + + static bVector3 Eye; + static bVector3 Look; + static bVector3 Up; + static float TurboSpeed; + static float SuperTurboSpeed; + static int TurboOn; + static int SuperTurboOn; + + private: + float HeightInc; // offset 0x80, size 0x4 + float ForwardInc; // offset 0x84, size 0x4 + float ForwardAnalogInc; // offset 0x88, size 0x4 + float StrafeInc; // offset 0x8C, size 0x4 + short TurnHInc; // offset 0x90, size 0x2 + short TurnVInc; // offset 0x92, size 0x2 + float RoadNetworkXInc; // offset 0x94, size 0x4 + float RoadNetworkYInc; // offset 0x98, size 0x4 + JoystickPort JoyPort; // offset 0x9C, size 0x4 + float PrevNearZ; // offset 0xA0, size 0x4 + ActionQueue *mActionQ; // offset 0xA4, size 0x4 +}; + #endif diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEData.cpp b/src/Speed/Indep/Src/Camera/ICE/ICEData.cpp index e69de29bb..886d6ba19 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEData.cpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEData.cpp @@ -0,0 +1,92 @@ +#include "ICEManager.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" + +bool bMirrorICEData; + +void ICEData::PlatEndianSwap() { + bPlatEndianSwap(&fParameter); + bPlatEndianSwap(&nShakeType); + + for (int i = 0; i < 2; i++) { + bPlatEndianSwap(&fTangentLength[i]); + + for (int j = 0; j < 3; j++) { + bPlatEndianSwap(&vEye[i][j]); + bPlatEndianSwap(&vLook[i][j]); + } + + bPlatEndianSwap(&fDutch[i]); + bPlatEndianSwap(&fLens[i]); + bPlatEndianSwap(&fNearClip[i]); + bPlatEndianSwap(&fFocalDistance[i]); + bPlatEndianSwap(&fNoiseAmplitude[i]); + bPlatEndianSwap(&fNoiseFrequency[i]); + } +} + +void ICEData::GetEye(int i, ICE::Vector3 *p) { + p->x = vEye[i][0]; + p->y = vEye[i][1]; + p->z = vEye[i][2]; + + if (bMirrorICEData) { + p->y = -p->y; + } + if (nSpaceEye == 3) { + TheICEManager.FixAnimElevation(p); + } +} + +void ICEData::GetLook(int i, ICE::Vector3 *p) { + p->x = vLook[i][0]; + p->y = vLook[i][1]; + p->z = vLook[i][2]; + + if (bMirrorICEData) { + p->y = -p->y; + } + if (nSpaceLook == 3) { + TheICEManager.FixAnimElevation(p); + } +} + +namespace ICE { + +bool KeysSharedSpace(ICEData *p1, int n1, ICEData *p2, int n2); + +bool KeysShared(ICEData *p1, int n1, ICEData *p2, int n2) { + if (bAbs(p1->fTangentLength[n1] - p2->fTangentLength[n2]) > 0.001f) { + return false; + } + if (bAbs(p1->fDutch[n1] - p2->fDutch[n2]) > 0.001f) { + return false; + } + if (bAbs(p1->fLens[n1] - p2->fLens[n2]) > 0.001f) { + return false; + } + return KeysSharedSpace(p1, n1, p2, n2); +} + +bool KeysSharedSpace(ICEData *p1, int n1, ICEData *p2, int n2) { + if (p1->nType != p2->nType) { + return false; + } + if (p1->nSpaceEye != p2->nSpaceEye) { + return false; + } + if (p1->nSpaceLook != p2->nSpaceLook) { + return false; + } + for (int i = 0; i < 3; i++) { + if (bAbs(p1->vEye[n1][i] - p2->vEye[n2][i]) > 0.001f) { + return false; + } + if (bAbs(p1->vLook[n1][i] - p2->vLook[n2][i]) > 0.001f) { + return false; + } + } + return true; +} + +} // namespace ICE diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEData.hpp b/src/Speed/Indep/Src/Camera/ICE/ICEData.hpp index 9504cfaeb..8db5e14df 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEData.hpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEData.hpp @@ -9,7 +9,10 @@ // total size: 0x84 struct ICEData { + void InitData(); + void PlatEndianSwap(); void GetEye(int i, ICE::Vector3 *p); + void GetLook(int i, ICE::Vector3 *p); unsigned char nType; // offset 0x0, size 0x1 unsigned char bSmooth; // offset 0x1, size 0x1 @@ -35,6 +38,7 @@ struct ICEData { float fFocalDistance[2]; // offset 0x74, size 0x8 unsigned char fAperture[2]; // offset 0x7C, size 0x2 unsigned char fLetterbox[2]; // offset 0x7E, size 0x2 + unsigned char fSimSpeed[2]; // offset 0x80, size 0x2 }; #endif diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEManager.cpp b/src/Speed/Indep/Src/Camera/ICE/ICEManager.cpp index e69de29bb..3b4e8a978 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEManager.cpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEManager.cpp @@ -0,0 +1,1102 @@ +#include "ICEManager.hpp" +#include "ICEAnimScene.hpp" +#include "ICEReplay.hpp" +#include "Speed/Indep/Src/Animation/AnimDirectory.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern Timer RealTimer; +extern AnimDirectory *TheAnimDirectory; +extern int bUseOldDutch; + +int bStrNICmp(const char *s1, const char *s2, int n); + +struct ICEAnchor; +ICEAnchor *GetICEAnchor(); + +static float GetGroundElevation(const ICE::Vector3 *position); +static void ICEGetPlayerCarTransform(ICE::Matrix4 *matrix); + +namespace ICEReplay { +ICETrack *ChooseGoodCamera(ICEAnchor *p_car, ICEGroup *p_replay_cameras, int num_replay_cameras); +} + +namespace ICE { +ICEScene *FindAnimScene(); +unsigned int GetSceneCount(); +unsigned int GetSceneHash(unsigned int slot); +void GetNameOfSceneHash(unsigned int hash, char *name); +bool KeysShared(ICEData *p1, int n1, ICEData *p2, int n2); +} + +static const char *GenericCategoryNames[2] = {"Cinematics", "Debug"}; + +ICEManager TheICEManager; + +void ICEGroup::FlushAllocatedTracks() { + ICETrack *track = TrackList.GetHead(); + + while (track != TrackList.EndOfList()) { + ICETrack *next = track->GetNext(); + + if (track->IsAllocated()) { + TrackList.Remove(track); + delete track; + NumTracks--; + } + + track = next; + } +} + +ICETrack *ICEGroup::GetTrack(int n) { + ICETrack *track = TrackList.GetNode(n); + + if (track == TrackList.EndOfList()) { + track = 0; + } + + return track; +} + +ICETrack *ICEGroup::GetTrack(char *s) { + ICETrack *track = TrackList.GetHead(); + + while (track != TrackList.EndOfList()) { + if (bStrCmp(s, track->Name) == 0) { + return track; + } + + track = track->GetNext(); + } + + return 0; +} + +void ICEShakeGroup::FlushAllocatedTracks() { + ICEShakeTrack *track = TrackList.GetHead(); + + while (track != TrackList.EndOfList()) { + ICEShakeTrack *next = track->GetNext(); + + if (track->IsAllocated()) { + TrackList.Remove(track); + delete track; + NumTracks--; + } + + track = next; + } +} + +ICEShakeTrack *ICEShakeGroup::GetTrack(int n) { + ICEShakeTrack *track = TrackList.GetNode(n); + + if (track == TrackList.EndOfList()) { + track = 0; + } + + return track; +} + +void ICETrack::PlatEndianSwap() { + bPlatEndianSwap(&Start); + bPlatEndianSwap(&Length); + bPlatEndianSwap(&NumKeys); + + for (int i = 0; i < NumKeys; i++) { + Keys[i].PlatEndianSwap(); + } +} + +int ICETrack::GetContext() { + if (Group == 0) { + return 4; + } + + return Group->Context; +} + +int ICETrack::GetKeyNumber(float f_param) { + int i = NumKeys - 1; + + if (i < 1) { + return i; + } + + float *parameters = &Keys[0].fParameter; + + if (parameters[i * 33] <= f_param) { + return i; + } + + do { + i--; + + if (i < 1) { + return i; + } + } while (parameters[i * 33] > f_param); + + return i; +} + +float ICETrack::GetParameter() { + float f_param = 0.0f; + int context = GetContext(); + + switch (context) { + case 0: { + ICEScene *scene = ICE::FindAnimScene(); + + if (scene != 0) { + f_param = (scene->GetTimeElapsed() - scene->GetTimeStart()) / (scene->GetTimeTotalLength() - scene->GetTimeStart()); + } + break; + } + case 1: + case 3: + if (0.0f < Length) { + float t = (TheICEManager.GetTimerSeconds() - Start) / Length; + f_param = bMin(1.0f, t); + } + break; + case 2: + if (Length > f_param) { + float t = (TheICEManager.GetTimerSeconds() - Start) / Length; + f_param = bMin(1.0f, t); + } + break; + } + + return f_param; +} + +ICEData *ICETrack::GetCameraData(float *p_start, float *p_end, float *p_current) { + float f_param = GetParameter(); + + if (f_param != bClamp(f_param, 0.0f, 1.0f)) { + return 0; + } + + int key = GetKeyNumber(f_param); + + if (p_current != 0) { + *p_current = f_param; + } + if (p_start != 0) { + *p_start = GetParameter(key); + } + if (p_end != 0) { + *p_end = GetParameter(key + 1); + } + + return GetKey(key); +} + +void ICEShakeData::PlatEndianSwap() { + bPlatEndianSwap(&q[0]); + bPlatEndianSwap(&q[1]); + bPlatEndianSwap(&q[2]); + bPlatEndianSwap(&p[0]); + bPlatEndianSwap(&p[1]); + bPlatEndianSwap(&p[2]); +} + +void ICEShakeTrack::PlatEndianSwap() { + bPlatEndianSwap(&NumKeys); + + for (int i = 0; i < NumKeys; i++) { + Keys[i].PlatEndianSwap(); + } +} + +ICEManager::ICEManager() { + nState = 0; + nTrack = 0; + nHandle = 0; + nOption = 0; + nSetting = 0; + nSceneHash = 0; + nExitConfirmOption = 0; + nDeleteConfirmOption = 0; + nCopyMode = 0; + nNisCameras = 0; + nFmvCameras = 0; + nReplayCameras = 0; + nGenericCameras = 0; + pNisCameras = 0; + pFmvCameras = 0; + pReplayCameras = 0; + pGenericCameras = 0; + pShakeGroup = 0; + nContext = 3; + fAnimElevation = 0.0f; + fParameterStart = 0.0f; + fParameterLength = 0.0f; + fParameterLengthBackup = 0.0f; + nPlayGenericGroupHash = bStringHash(""); + nPlayGenericTrackName[0] = 0; + pPlaybackTrack = 0; + ICEReplay::ClearRecentlyUsed(); + bSmoothExit = false; + nMarkerIndex = -1; + bUseRealTime = false; +} + +float ICEManager::GetTimerSeconds() { + if (bUseRealTime) { + return RealTimer.GetSeconds(); + } else { + return WorldTimer.GetSeconds(); + } +} + +bool ICEManager::RefreshCameraSplines() { + return false; +} + +void ICEManager::Update() {} + +ICEGroup *ICEManager::GetNisCameraGroup(unsigned int scene_hash) { + for (int i = 0; i < nNisCameras; i++) { + if (scene_hash == pNisCameras[i].Handle) { + return &pNisCameras[i]; + } + } + + return 0; +} + +ICEGroup *ICEManager::GetFmvCameraGroup(unsigned int scene_hash) { + for (int i = 0; i < nFmvCameras; i++) { + if (scene_hash == pFmvCameras[i].Handle) { + return &pFmvCameras[i]; + } + } + + return 0; +} + +ICEGroup *ICEManager::GetReplayCameraGroup(unsigned int category_hash) { + for (int i = 0; i < nReplayCameras; i++) { + if (category_hash == pReplayCameras[i].Handle) { + return &pReplayCameras[i]; + } + } + + return 0; +} + +ICEGroup *ICEManager::GetGenericCameraGroup(unsigned int name_hash) { + for (int i = 0; i < nGenericCameras; i++) { + if (name_hash == pGenericCameras[i].Handle) { + return &pGenericCameras[i]; + } + } + + return 0; +} + +ICEShakeTrack *ICEManager::GetShakeTrack(unsigned int shake_type) { + if (shake_type != 0 && pShakeGroup != 0) { + for (int i = 0; i < pShakeGroup->NumTracks; i++) { + ICEShakeTrack *track = pShakeGroup->GetTrack(i); + + if (track != 0 && bStringHash(track->Name) == shake_type) { + return track; + } + } + } + + return 0; +} + +int ICEManager::GetCameraIndex(float f_param, ICETrack *track) { + if (track) { + return track->GetKeyNumber(f_param); + } + + return 0; +} + +float ICEManager::GetParameter() { + float parameter = 0.0f; + ICEScene *scene = ICE::FindAnimScene(); + + if (scene != 0 && 0.0f < fParameterLength) { + parameter = (scene->GetTimeElapsed() - fParameterStart) / fParameterLength; + } + + return parameter; +} + +float ICEManager::GetParameter(int i, ICETrack *track) { + if (track == 0) { + return 0.0f; + } + if (i < 0) { + return 0.0f; + } + if (i < track->NumKeys) { + return track->Keys[i].fParameter; + } + + return 1.0f; +} + +float ICEManager::GetIntervalSize(ICEData *data, ICETrack *track) { + int i = 0; + + if (track != 0) { + i = data - track->Keys; + } + + return GetParameter(i + 1, track) - GetParameter(i, track); +} + +ICETrack *ICEManager::ChooseGenericCamera() { + ICETrack *track = 0; + ICEGroup *group = GetGenericCameraGroup(nPlayGenericGroupHash); + + if (group != 0) { + track = group->GetTrack(nPlayGenericTrackName); + + if (track != 0) { + track->Start = GetTimerSeconds(); + } + } + + return track; +} + +int ICEManager::GetNumSceneCameraTrack(unsigned int scene_hash) { + int available_tracks = 0; + ICEGroup *group = GetNisCameraGroup(scene_hash); + + if (group) { + available_tracks = group->NumTracks; + } + + return available_tracks; +} + +void ICEManager::ChooseReplayCamera() { + float parameter = 0.0f; + + if (pPlaybackTrack != 0) { + parameter = (GetTimerSeconds() - pPlaybackTrack->Start) / pPlaybackTrack->Length; + } + + if (0.0f <= parameter) { + ICETrack *track = ICEReplay::ChooseGoodCamera(GetICEAnchor(), pReplayCameras, nReplayCameras); + + if (track != 0) { + pPlaybackTrack = track; + pPlaybackTrack->Start = GetTimerSeconds(); + } + } +} + +float ICEManager::GetAnimElevationFixup(ICE::Vector3 *position) { + float elevation = GetGroundElevation(position); + if (0.0f < elevation) { + return elevation - fAnimElevation; + } + return 0.0f; +} + +void ICEManager::FixAnimElevation(ICE::Vector3 *position) { + ICEScene *scene = ICE::FindAnimScene(); + if (scene != 0 && scene->IsCameraFixingElevation()) { + ICE::Vector3 world_position; + ICE::MulVector(&world_position, reinterpret_cast(&scene->GetSceneTransformMatrix()), position); + if (IsEditorOff()) { + const ICE::Vector3 *scene_origin = reinterpret_cast(&scene->GetSceneTransformMatrix().v3); + fAnimElevation = GetGroundElevation(scene_origin); + } + position->z += GetAnimElevationFixup(&world_position); + } +} + +void ICEManager::SetGenericCameraToPlay(const char *group_name, const char *track_name) { + nPlayGenericGroupHash = Attrib::StringHash32(group_name); + bStrNCpy(nPlayGenericTrackName, track_name, 13); + nPlayGenericTrackName[13] = 0; +} + +ICEData *ICEManager::GetCameraData(unsigned int scene_hash, int camTrack) { + ICEGroup *group = GetNisCameraGroup(scene_hash); + if (group != 0) { + char name[14]; + bSPrintf(name, "cam%d", camTrack); + pPlaybackTrack = group->GetTrack(name); + if (pPlaybackTrack != 0) { + return pPlaybackTrack->GetCameraData(0, 0, 0); + } + } + return 0; +} + +ICEData *ICEManager::GetCameraData(ICETrack **p_track, float *p_start, float *p_end) { + if (p_track != 0) { + *p_track = pPlaybackTrack; + } + if (pPlaybackTrack != 0) { + return pPlaybackTrack->GetCameraData(p_start, p_end, 0); + } + return 0; +} + +ICEData *ICEManager::GetNeighbour(ICEData *data, int key, ICETrack *track) { + if (track == 0) { + return 0; + } + int camera = track->GetKeyNumber(data); + int offset = camera - 1; + if (key) { + offset = camera + 1; + } + return track->GetKey(offset); +} + +ICEGroup *ICEManager::GetCameraGroup(ICEContext context, unsigned int handle) { + int num_groups = 0; + ICEGroup *groups = 0; + + switch (context) { + case eDCE_NIS: + groups = pNisCameras; + num_groups = nNisCameras; + break; + case eDCE_FMV: + groups = pFmvCameras; + num_groups = nFmvCameras; + break; + case eDCE_REPLAY: + groups = pReplayCameras; + num_groups = nReplayCameras; + break; + case eDCE_GENERIC: + groups = pGenericCameras; + num_groups = nGenericCameras; + break; + default: + break; + } + + for (int i = 0; i < num_groups; i++) { + ICEGroup *group = &groups[i]; + if (handle == group->GetHandle()) { + return group; + } + } + return 0; +} + +ICEGroup *ICEManager::AddCameraGroup(ICEContext context, unsigned int handle) { + ICEGroup *group = GetCameraGroup(context, handle); + if (group != 0) { + return group; + } + + switch (context) { + case eDCE_NIS: { + int index = nNisCameras; + if (index > 0xff) { + return group; + } + group = &pNisCameras[index]; + nNisCameras = index + 1; + break; + } + case eDCE_FMV: { + int index = nFmvCameras; + if (index > 9) { + return group; + } + group = &pFmvCameras[index]; + nFmvCameras = index + 1; + break; + } + case eDCE_REPLAY: { + int index = nReplayCameras; + if (index > 0x31) { + return group; + } + group = &pReplayCameras[index]; + nReplayCameras = index + 1; + break; + } + case eDCE_GENERIC: { + int index = nGenericCameras; + if (index > 0x31) { + return group; + } + group = &pGenericCameras[index]; + nGenericCameras = index + 1; + break; + } + default: + return group; + } + + group->SetContext(context); + group->SetHandle(handle); + return group; +} + +void ICEManager::LoadCameraSet(bChunk *set_chunk) { + ICEContext context = eDCE_NOCONTEXT; + unsigned int id = set_chunk->GetID(); + + switch (id) { + case 0x8003B200: + context = eDCE_NIS; + break; + case 0x8003B201: + context = eDCE_FMV; + break; + case 0x8003B202: + context = eDCE_REPLAY; + break; + case 0x8003B203: + context = eDCE_GENERIC; + break; + } + + for (bChunk *chunk = set_chunk->GetFirstChunk(); chunk != set_chunk->GetLastChunk(); chunk = chunk->GetNext()) { + bPlatEndianSwap(reinterpret_cast(chunk->GetData())); + bPlatEndianSwap(reinterpret_cast(chunk->GetData() + 4)); + ICEGroup *group = AddCameraGroup(context, *reinterpret_cast(chunk->GetData())); + if (group != 0) { + int num_tracks = *reinterpret_cast(chunk->GetData() + 4); + ICETrack *track = reinterpret_cast(chunk->GetData() + 8); + for (int i = 0; i < num_tracks; i++) { + track->PlatEndianSwap(); + group->AddTrack(track); + track = reinterpret_cast(reinterpret_cast(track) + track->MemoryImageSize()); + } + } + } +} + +void ICEManager::UnloadCameraSet(bChunk *set_chunk) { + ICEContext context = eDCE_NOCONTEXT; + unsigned int id = set_chunk->GetID(); + + switch (id) { + case 0x8003B200: + context = eDCE_NIS; + break; + case 0x8003B201: + context = eDCE_FMV; + break; + case 0x8003B202: + context = eDCE_REPLAY; + break; + case 0x8003B203: + context = eDCE_GENERIC; + break; + } + + for (bChunk *chunk = set_chunk->GetFirstChunk(); chunk != set_chunk->GetLastChunk(); chunk = chunk->GetNext()) { + ICEGroup *group = GetCameraGroup(context, *reinterpret_cast(chunk->GetData())); + if (group != 0) { + group->NumTracks = 0; + group->FlushTracks(); + } + } + + int num_groups = 0; + ICEGroup *groups = 0; + + switch (context) { + case eDCE_NIS: + groups = pNisCameras; + num_groups = nNisCameras; + break; + case eDCE_FMV: + groups = pFmvCameras; + num_groups = nFmvCameras; + break; + case eDCE_REPLAY: + groups = pReplayCameras; + num_groups = nReplayCameras; + break; + case eDCE_GENERIC: + groups = pGenericCameras; + num_groups = nGenericCameras; + break; + } + + for (int i = 0; i < num_groups; i++) { + groups[i].FlushAllocatedTracks(); + } +} + +void ICEManager::LoadCameraShakes(bChunk *set_chunk) { + bPlatEndianSwap(reinterpret_cast(set_chunk->GetData())); + ICEShakeGroup *group = pShakeGroup; + if (group != 0) { + int num_tracks = *reinterpret_cast(set_chunk->GetData()); + ICEShakeTrack *track = reinterpret_cast(set_chunk->GetData() + 4); + for (int i = 0; i < num_tracks; i++) { + track->PlatEndianSwap(); + group->AddTrack(track); + track = reinterpret_cast(reinterpret_cast(track) + track->MemoryImageSize()); + } + } +} + +void ICEManager::UnloadCameraShakes(bChunk *set_chunk) { + ICEShakeGroup *group = pShakeGroup; + group->NumTracks = 0; + while (!group->TrackList.IsEmpty()) { + ICEShakeTrack *track = group->TrackList.RemoveHead(); + if (track->IsAllocated() && track != 0) { + delete track; + } + } + group->FlushAllocatedTracks(); +} + +void ICEManager::Init() { + pNisCameras = new ICEGroup[256]; + pFmvCameras = new ICEGroup[10]; + pReplayCameras = new ICEGroup[50]; + pGenericCameras = new ICEGroup[50]; + pShakeGroup = new ICEShakeGroup; +} + +void ICEManager::Resolve() { + unsigned int num_scenes = ICE::GetSceneCount(); + + { + for (unsigned int scene = 0; scene < num_scenes; scene++) { + unsigned int scene_hash = ICE::GetSceneHash(scene); + char scene_name[16]; + ICE::GetNameOfSceneHash(scene_hash, scene_name); + char *name_ptr = scene_name; + + if (bStrNICmp(name_ptr, "FMV", 3) != 0 && + bStrNICmp(name_ptr, "replay", 6) != 0 && + bStrNICmp(name_ptr, "clip", 4) != 0) { + if (GetNisCameraGroup(scene_hash) == 0) { + if (nNisCameras <= 0xff) { + pNisCameras[nNisCameras].Context = eDCE_NIS; + pNisCameras[nNisCameras].Handle = scene_hash; + nNisCameras++; + } + } + } + } + } + + { + for (unsigned int fmv = 0; fmv < num_scenes; fmv++) { + unsigned int scene_hash = ICE::GetSceneHash(fmv); + char scene_name[16]; + ICE::GetNameOfSceneHash(scene_hash, scene_name); + + if (bStrNICmp(scene_name, "FMV", 3) == 0) { + if (GetFmvCameraGroup(scene_hash) == 0) { + if (nFmvCameras <= 9) { + pFmvCameras[nFmvCameras].Context = eDCE_FMV; + pFmvCameras[nFmvCameras].Handle = scene_hash; + nFmvCameras++; + } + } + } + } + } + + { + int num_categories = ICE::GetReplayCategoryNumElements(); + for (int category = 0; category < num_categories; category++) { + unsigned int category_hash = ICE::GetReplayCategoryHash(category); + + if (GetReplayCameraGroup(category_hash) == 0) { + if (nReplayCameras <= 0x31) { + pReplayCameras[nReplayCameras].Context = eDCE_REPLAY; + pReplayCameras[nReplayCameras].Handle = category_hash; + nReplayCameras++; + } + } + } + } + + { + for (int name = 0; name < 2; name++) { + unsigned int name_hash = bStringHash(GenericCategoryNames[name]); + + if (GetGenericCameraGroup(name_hash) == 0) { + if (nGenericCameras <= 0x31) { + pGenericCameras[nGenericCameras].Context = eDCE_GENERIC; + pGenericCameras[nGenericCameras].Handle = name_hash; + nGenericCameras++; + } + } + } + } +} + +bool ICEManager::ChooseCameraPlaybackTrack() { + pPlaybackTrack = 0; + bUseOldDutch = 0; + ICEScene *scene = ICE::FindAnimScene(); + if (scene != 0) { + unsigned int scene_hash = scene->GetSceneHash(); + ICEGroup *group = GetNisCameraGroup(scene_hash); + if (group != 0) { + char name[14]; + bSPrintf(name, "Track %d", scene->GetCameraTrackNumber()); + pPlaybackTrack = group->GetTrack(name); + if (pPlaybackTrack == 0) { + char scene_name[16]; + ICE::GetNameOfSceneHash(scene_hash, scene_name); + } + bUseOldDutch = 1; + } + } else { + pPlaybackTrack = ChooseGenericCamera(); + if (pPlaybackTrack == 0) { + pPlaybackTrack = ICEReplay::ChooseGoodCamera(GetICEAnchor(), pReplayCameras, nReplayCameras); + if (pPlaybackTrack != 0) { + pPlaybackTrack->Start = GetTimerSeconds(); + } + } + } + return pPlaybackTrack != 0; +} + +int ICEManager::ChooseGoodSceneCameraTrackIndex(unsigned int scene_hash, const ICE::Matrix4 *scene_origin) { + bMatrix4 mCarToWorld; + int bestTrack = 0; + + ICEGetPlayerCarTransform(reinterpret_cast(&mCarToWorld)); + + for (int i = 0; i < nNisCameras; i++) { + if (scene_hash == pNisCameras[i].Handle) { + ICEGroup *group = &pNisCameras[i]; + int numTracks = group->GetNumTracks(); + + if (numTracks < 2) { + break; + } + + float bestDot = -1.0f; + + for (int k = 0; k < numTracks; k++) { + ICETrack *track = group->GetTrack(k); + + if (track->GetNumKeys() > 0) { + ICEData *key = track->GetKey(0); + int n = 0; + if (key->bSmooth != 0) { + n = 1; + } + + bVector3 v_eye; + key[n].GetEye(n, reinterpret_cast(&v_eye)); + + switch (key[n].nSpaceEye) { + case 2: + bAdd(&v_eye, &v_eye, reinterpret_cast(&mCarToWorld.v3)); + break; + case 0: + eMulVector(&v_eye, &mCarToWorld, &v_eye); + break; + case 3: + eMulVector(&v_eye, reinterpret_cast(scene_origin), &v_eye); + break; + } + + bVector3 v_look; + key[n].GetLook(n, reinterpret_cast(&v_look)); + + switch (key[n].nSpaceLook) { + case 2: + bAdd(&v_look, &v_look, reinterpret_cast(&mCarToWorld.v3)); + break; + case 0: + eMulVector(&v_look, &mCarToWorld, &v_look); + break; + case 3: + eMulVector(&v_look, reinterpret_cast(scene_origin), &v_look); + break; + } + + bVector3 vCamDir; + bSub(&vCamDir, &v_eye, &v_look); + bNormalize(&vCamDir, &vCamDir); + + bVector3 *pCarDir = reinterpret_cast(&mCarToWorld.v0); + float dot = bDot(&vCamDir, pCarDir); + + if (bestDot < dot) { + bestTrack = k; + bestDot = dot; + } + } + } + } + } + + return bestTrack; +} + +void ICEManager::GetSlope(ICE::Vector3 *eye, ICE::Vector3 *look, float *fov, float *dutch, ICEData *data, int key, ICETrack *track) { + ICE::Vector3 v_eye_slope; + ICE::Vector3 v_look_slope; + float f_dutch_slope = 0.0f; + float f_lens_slope = 0.0f; + + if (data->nType != 0) { + ICEData *p_neighbour = GetNeighbour(data, key, track); + bool shared_slope = false; + if (p_neighbour != 0 && p_neighbour->nType != 0 && + ICE::KeysShared(data, key, p_neighbour, key ^ 1)) { + ICE::Vector3 v0; + ICE::Vector3 v1; + ICE::Vector3 v_eye0; + ICE::Vector3 v_eye1; + ICE::Vector3 v_look0; + ICE::Vector3 v_look1; + + shared_slope = true; + + p_neighbour->GetEye(0, &v0); + p_neighbour->GetEye(1, &v1); + bSub(reinterpret_cast(&v_eye0), reinterpret_cast(&v1), reinterpret_cast(&v0)); + + p_neighbour->GetLook(0, &v0); + p_neighbour->GetLook(1, &v1); + bSub(reinterpret_cast(&v_look0), reinterpret_cast(&v1), reinterpret_cast(&v0)); + + float f_dutch0 = p_neighbour->fDutch[0]; + float f_lens0 = p_neighbour->fLens[0]; + float f_dutch1 = p_neighbour->fDutch[1]; + float f_lens1 = p_neighbour->fLens[1]; + + data->GetEye(0, &v0); + data->GetEye(1, &v1); + bSub(reinterpret_cast(&v_eye1), reinterpret_cast(&v1), reinterpret_cast(&v0)); + + data->GetLook(0, &v0); + data->GetLook(1, &v1); + float f_dutch_data0 = data->fDutch[0]; + float f_lens_data1 = data->fLens[1]; + bSub(reinterpret_cast(&v_look1), reinterpret_cast(&v1), reinterpret_cast(&v0)); + float f_lens_data0 = data->fLens[0]; + float f_dutch_data1 = data->fDutch[1]; + + float f_camera_size = GetIntervalSize(data, track); + float f_neighbour_size = GetIntervalSize(p_neighbour, track); + + float f_neighbour_blend = f_camera_size / (f_camera_size + f_neighbour_size); + float f_camera_blend = 1.0f - f_neighbour_blend; + + if (f_neighbour_size > 0.000001f) { + f_neighbour_blend *= f_camera_size / f_neighbour_size; + } + + float tangent_length = data->fTangentLength[key]; + bScale(reinterpret_cast(&v_eye_slope), reinterpret_cast(&v_eye0), f_neighbour_blend * tangent_length); + bScaleAdd(reinterpret_cast(&v_eye_slope), reinterpret_cast(&v_eye_slope), reinterpret_cast(&v_eye1), f_camera_blend * tangent_length); + + bScale(reinterpret_cast(&v_look_slope), reinterpret_cast(&v_look0), f_neighbour_blend * tangent_length); + bScaleAdd(reinterpret_cast(&v_look_slope), reinterpret_cast(&v_look_slope), reinterpret_cast(&v_look1), f_camera_blend * tangent_length); + + f_lens_slope = (f_dutch1 - f_lens1) * f_neighbour_blend * tangent_length + (f_dutch_data1 - f_lens_data1) * f_camera_blend * tangent_length; + f_dutch_slope = (f_lens0 - f_dutch0) * f_neighbour_blend * tangent_length + (f_lens_data0 - f_dutch_data0) * f_camera_blend * tangent_length; + } + if (!shared_slope) { + ICE::Vector3 v_eye0; + ICE::Vector3 v_eye1; + ICE::Vector3 v_look0; + ICE::Vector3 v_look1; + + data->GetEye(0, &v_eye0); + data->GetEye(1, &v_eye1); + data->GetLook(0, &v_look0); + data->GetLook(1, &v_look1); + + float tangent_length = data->fTangentLength[key]; + bSub(reinterpret_cast(&v_eye_slope), reinterpret_cast(&v_eye1), reinterpret_cast(&v_eye0)); + bScale(reinterpret_cast(&v_eye_slope), reinterpret_cast(&v_eye_slope), tangent_length); + bSub(reinterpret_cast(&v_look_slope), reinterpret_cast(&v_look1), reinterpret_cast(&v_look0)); + bScale(reinterpret_cast(&v_look_slope), reinterpret_cast(&v_look_slope), tangent_length); + f_lens_slope = tangent_length * (data->fLens[1] - data->fLens[0]); + f_dutch_slope = tangent_length * (data->fDutch[1] - data->fDutch[0]); + } + } + + *eye = v_eye_slope; + *look = v_look_slope; + *fov = f_dutch_slope; + *dutch = f_lens_slope; +} + +static float GetGroundElevation(const ICE::Vector3 *position) { + float ground_elevation = 0.0f; + if (IsGameFlowInGame()) { + UMath::Vector3 unswizzled_position; + eUnSwizzleWorldVector(reinterpret_cast(*position), reinterpret_cast(unswizzled_position)); + unswizzled_position.y += 4.0f; + WCollisionMgr collisionMgr(0, 3); + bool point_valid = collisionMgr.GetWorldHeightAtPointRigorous(unswizzled_position, ground_elevation, 0); + if (!point_valid) { + ground_elevation = position->z; + } + } + return ground_elevation; +} + +static void ICEGetPlayerCarTransform(ICE::Matrix4 *mCarToWorld) { + bIdentity(reinterpret_cast(mCarToWorld)); + IPlayer *iplayer = IPlayer::First(PLAYER_LOCAL); + if (iplayer != 0) { + IRigidBody *player_rigid_body = iplayer->GetSimable()->GetRigidBody(); + if (player_rigid_body != 0) { + UMath::Matrix4 mat; + player_rigid_body->GetMatrix4(mat); + bConvertFromBond(*reinterpret_cast(mCarToWorld), reinterpret_cast(mat)); + const UMath::Vector3 &pos = player_rigid_body->GetPosition(); + eSwizzleWorldVector(reinterpret_cast(pos), reinterpret_cast(reinterpret_cast(mCarToWorld)->v3)); + } + } +} + +int LoaderICECameras(bChunk *pChunk) { + unsigned int id = pChunk->GetID(); + if (id != 0x0003B211) { + if (id < 0x0003B211) { + return 0; + } + if (id > 0x8003B203) { + return 0; + } + if (id < 0x8003B200) { + return 0; + } + TheICEManager.LoadCameraSet(pChunk); + return 1; + } + TheICEManager.LoadCameraShakes(pChunk); + return 1; +} + +int UnloaderICECameras(bChunk *pChunk) { + unsigned int id = pChunk->GetID(); + if (id != 0x0003B211) { + if (id < 0x0003B211) { + return 0; + } + if (id > 0x8003B203) { + return 0; + } + if (id < 0x8003B200) { + return 0; + } + TheICEManager.UnloadCameraSet(pChunk); + return 1; + } + TheICEManager.UnloadCameraShakes(pChunk); + return 1; +} + +namespace ICE { + +ICEScene *FindAnimScene() { + INIS *nis = INIS::Get(); + + if (nis != 0) { + return nis->GetScene(); + } + + return 0; +} + +unsigned int GetSceneCount() { + unsigned int sceneCount = 0; + if (TheAnimDirectory) { + sceneCount = TheAnimDirectory->GetSceneCount(); + } + return sceneCount; +} + +unsigned int GetSceneHash(unsigned int slot) { + if (TheAnimDirectory != 0 && slot < TheAnimDirectory->GetSceneCount()) { + AnimSceneLoadInfo info; + TheAnimDirectory->GetSceneLoadInfo(slot, info); + return info.mAnimSceneHash; + } + return 0; +} + +void GetNameOfSceneHash(unsigned int hash, char *name) { + *name = 0; + if (TheAnimDirectory != 0) { + AnimDirectory *dir = TheAnimDirectory; + for (unsigned int i = 0; i < dir->GetSceneCount(); i++) { + if (hash == dir->GetSceneLoadInfo(i)->mAnimSceneHash) { + AnimSceneLoadInfo info; + dir->GetSceneLoadInfo(i, info); + char *filename = dir->GetFileName(info.mSceneFileStartIndex); + int pos = 0; + while (filename[pos] != '_') { + pos++; + } + pos++; + int start = pos; + while (filename[pos] != '_') { + pos++; + } + int len = pos - start - 1; + for (int j = start; len >= 0; j++, len--) { + *name = filename[j]; + name++; + } + *name = 0; + break; + } + } + } +} + +void FireEventTag(int key) { + INIS *nis = INIS::Get(); + if (nis != 0) { + char tagName[64]; + bSPrintf(tagName, "key_%d", key); + nis->FireEventTag(tagName); + } +} + +} // namespace ICE + +void ICECompleteEventTags() { + ICETrack *p_track; + float fParameter0; + float fParameter1; + TheICEManager.GetCameraData(&p_track, &fParameter0, &fParameter1); + int key = TheICEManager.GetCameraIndex((fParameter0 + fParameter1) * 0.5f, p_track) + 1; + if (p_track != 0) { + int keyCount = p_track->GetNumKeys(); + for (; key < keyCount; key++) { + ICE::FireEventTag(key); + } + } +} diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEManager.hpp b/src/Speed/Indep/Src/Camera/ICE/ICEManager.hpp index db8ce5c52..0a991b7ae 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEManager.hpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEManager.hpp @@ -6,10 +6,51 @@ #endif #include "Speed/Indep/Src/Camera/ICE/ICEData.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" #include "Speed/Indep/bWare/Inc/bList.hpp" +enum ICEContext { + eDCE_NIS = 0, + eDCE_FMV = 1, + eDCE_REPLAY = 2, + eDCE_GENERIC = 3, + eDCE_NUM_CONTEXTS = 4, + eDCE_NOCONTEXT = 4, +}; + struct ICEGroup { // total size: 0x14 + void FlushAllocatedTracks(); + struct ICETrack *GetTrack(int n); + struct ICETrack *GetTrack(char *s); + + ICEGroup(); + ~ICEGroup(); + + unsigned int GetHandle() { + return Handle; + } + + int GetContext() { + return Context; + } + + void SetHandle(unsigned int n) { + Handle = n; + } + + void SetContext(int context) { + Context = context; + } + + int GetNumTracks() { + return NumTracks; + } + + void AddTrack(struct ICETrack *track); + + void FlushTracks(); + unsigned int Handle; // offset 0x0, size 0x4 int Context; // offset 0x4, size 0x4 int NumTracks; // offset 0x8, size 0x4 @@ -18,6 +59,65 @@ struct ICEGroup { // total size: 0x19F0 struct ICETrack : public bTNode { + void PlatEndianSwap(); + int GetContext(); + int GetKeyNumber(float f_param); + int GetKeyNumber(ICEData *data) { + return static_cast(data - Keys); + } + float GetParameter(); + float GetParameter(int n) { + if (n < 0) return 0.0f; + if (n >= NumKeys) return 1.0f; + return Keys[n].fParameter; + } + struct ICEData *GetCameraData(float *p_start, float *p_end, float *p_current); + + int GetNumKeys() { + return NumKeys; + } + + ICEData *GetKey(int n) { + if (n == UMath::Clamp(n, 0, NumKeys - 1)) return &Keys[n]; + return 0; + } + + char *GetName() { + return Name; + } + + ICEGroup *GetGroup() { + return Group; + } + + float GetStart() { + return Start; + } + + float GetLength() { + return Length; + } + + bool IsAllocated() { + return Allocated != 0; + } + + void SetGroup(ICEGroup *g) { + Group = g; + } + + void SetStart(float s) { + Start = s; + } + + void SetLength(float l) { + Length = l; + } + + int MemoryImageSize() { + return static_cast(sizeof(ICETrack)) - (50 - NumKeys) * static_cast(sizeof(ICEData)); + } + ICEGroup *Group; // offset 0x8, size 0x4 float Start; // offset 0xC, size 0x4 float Length; // offset 0x10, size 0x4 @@ -27,20 +127,65 @@ struct ICETrack : public bTNode { ICEData Keys[50]; // offset 0x28, size 0x19C8 }; +inline ICEGroup::ICEGroup() + : Handle(0) // + , Context(eDCE_NOCONTEXT) // + , NumTracks(0) {} + +inline ICEGroup::~ICEGroup() {} + +inline void ICEGroup::AddTrack(ICETrack *track) { + track->SetGroup(this); + TrackList.AddTail(track); + NumTracks++; +} + +inline void ICEGroup::FlushTracks() { + while (!TrackList.IsEmpty()) { + ICETrack *track = TrackList.RemoveHead(); + if (track->IsAllocated() && track != 0) { + delete track; + } + } +} + // total size: 0xC struct ICEShakeGroup { + void FlushAllocatedTracks(); + struct ICEShakeTrack *GetTrack(int n); + void FlushTracks(); + + ICEShakeGroup(); + ~ICEShakeGroup(); + + void AddTrack(struct ICEShakeTrack *track); + int NumTracks; // offset 0x0, size 0x4 struct bTList TrackList; // offset 0x4, size 0x8 }; // total size: 0x18 struct ICEShakeData { + void InitData(); + void PlatEndianSwap(); + float q[3]; // offset 0x0, size 0xC float p[3]; // offset 0xC, size 0xC }; // total size: 0xB60 struct ICEShakeTrack : public bTNode { + void PlatEndianSwap(); + bool IsAllocated() { return Allocated != 0; } + + void SetGroup(ICEShakeGroup *g) { + Group = g; + } + + int MemoryImageSize() { + return static_cast(sizeof(ICEShakeTrack)) - (120 - NumKeys) * static_cast(sizeof(ICEShakeData)); + } + ICEShakeGroup *Group; // offset 0x8, size 0x4 short NumKeys; // offset 0xC, size 0x2 char Allocated; // offset 0xE, size 0x1 @@ -48,19 +193,104 @@ struct ICEShakeTrack : public bTNode { ICEShakeData Keys[120]; // offset 0x20, size 0xB40 }; +inline ICEShakeGroup::~ICEShakeGroup() {} + +inline ICEShakeGroup::ICEShakeGroup() : NumTracks(0) {} + +inline void ICEShakeGroup::AddTrack(ICEShakeTrack *track) { + track->SetGroup(this); + TrackList.AddTail(track); + NumTracks++; +} + // total size: 0x80 class ICEManager { public: + ICEManager(); + void Init(); void Resolve(); ICEData *GetCameraData(unsigned int scene_hash, int camTrack); + ICEShakeTrack *GetShakeTrack(unsigned int shake_type); + int GetCameraIndex(float f_param, ICETrack *track); + float GetParameter(); + float GetTimerSeconds(); + bool RefreshCameraSplines(); + void Update(); + int GetNumSceneCameraTrack(unsigned int scene_hash); + void ChooseReplayCamera(); bool IsEditorOn() { // TODO maybe negated? return nState >= 1; } + bool IsEditorOff() { + return nState <= 0; + } + + int GetState() { + return nState; + } + + ICETrack *GetPlaybackTrack() { + return pPlaybackTrack; + } + + void SetSmoothExit(bool smooth) { + bSmoothExit = smooth; + } + + bool IsSmoothExit() { + return bSmoothExit; + } + + void SetUseRealTime(bool val) { + bUseRealTime = val; + } + + float IsUsingRealTime() { + return bUseRealTime; + } + + float GetParameterLength() { + return fParameterLength; + } + + bool IsGenericCameraPlaying() { + return nPlayGenericGroupHash != bStringHash(""); + } + + float GetAnimElevationFixup(ICE::Vector3 *position); + void SetupAnimElevation(); + void FixAnimElevation(ICE::Vector3 *position); + + int ChooseGoodSceneCameraTrackIndex(unsigned int scene_hash, const ICE::Matrix4 *scene_origin); + void SetGenericCameraToPlay(const char *group_name, const char *track_name); + ICEGroup *GetCurrentGroup(); + unsigned int GetRelativeShakeType(unsigned int shake_type, int inc); + char *GetShakeTypeName(unsigned int shake_type); + ICETrack *GetCurrentTrack(); + bool ChooseCameraPlaybackTrack(); + ICEData *GetCameraData(ICETrack **p_track, float *p_start, float *p_end); + ICEData *GetNeighbour(ICEData *data, int key, ICETrack *track); + void GetSlope(ICE::Vector3 *eye, ICE::Vector3 *look, float *fov, float *dutch, ICEData *data, int key, ICETrack *track); + ICEGroup *AddCameraGroup(ICEContext context, unsigned int handle); + ICEGroup *GetCameraGroup(ICEContext context, unsigned int handle); + void LoadCameraSet(bChunk *chunk); + void UnloadCameraSet(bChunk *chunk); + void LoadCameraShakes(bChunk *chunk); + void UnloadCameraShakes(bChunk *chunk); + private: + float GetParameter(int i, ICETrack *track); + float GetIntervalSize(ICEData *data, ICETrack *track); + ICETrack *ChooseGenericCamera(); + ICEGroup *GetNisCameraGroup(unsigned int scene_hash); + ICEGroup *GetFmvCameraGroup(unsigned int scene_hash); + ICEGroup *GetReplayCameraGroup(unsigned int category_hash); + ICEGroup *GetGenericCameraGroup(unsigned int name_hash); + ICEGroup *pNisCameras; // offset 0x0, size 0x4 ICEGroup *pFmvCameras; // offset 0x4, size 0x4 ICEGroup *pReplayCameras; // offset 0x8, size 0x4 diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEMath.hpp b/src/Speed/Indep/Src/Camera/ICE/ICEMath.hpp index 4697c3808..e01a98cbd 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEMath.hpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEMath.hpp @@ -23,6 +23,12 @@ struct Vector3 { pad = 0.0f; } + Vector3(float _x, float _y, float _z) + : x(_x), // + y(_y), // + z(_z), // + pad(0.0f) {} + float x; // offset 0x0, size 0x4 float y; // offset 0x4, size 0x4 float z; // offset 0x8, size 0x4 @@ -31,6 +37,12 @@ struct Vector3 { // total size: 0x10 struct Vector4 { + Vector4() + : x(0.0f), // + y(0.0f), // + z(0.0f), // + w(0.0f) {} + float x; // offset 0x0, size 0x4 float y; // offset 0x4, size 0x4 float z; // offset 0x8, size 0x4 @@ -39,6 +51,12 @@ struct Vector4 { // total size: 0x40 struct Matrix4 { + Matrix4() + : v0(), // + v1(), // + v2(), // + v3() {} + struct Vector4 v0; // offset 0x0, size 0x10 struct Vector4 v1; // offset 0x10, size 0x10 struct Vector4 v2; // offset 0x20, size 0x10 diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEMover.cpp b/src/Speed/Indep/Src/Camera/ICE/ICEMover.cpp index e69de29bb..121039e7b 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEMover.cpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEMover.cpp @@ -0,0 +1,1205 @@ +#include "ICEMover.hpp" +#include "ICEManager.hpp" +#include "ICEAnimScene.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +namespace ICE { +void HideOverlay(); +void ShowOverlay(unsigned char overlay); +ICEScene *FindAnimScene(); +bool KeysShared(ICEData *p1, int n1, ICEData *p2, int n2); +void FireEventTag(int key); +} + +void FlushAccumulationBuffer(); +extern bool bMirrorICEData; +extern ICE::Vector3 vIceAccelLagScale; +extern ICE::Vector3 vIceAccelLagMin; +extern ICE::Vector3 vIceAccelLagMax; + +bVector3 *bCross(bVector3 *dest, const bVector3 *v1, const bVector3 *v2); +void bQuaternionToMatrix(bMatrix4 *matrix, const bQuaternion *quaternion); + +void bQuaternion::GetMatrix(bMatrix4 &mat) const { + float x2 = x + x; + float y2 = y + y; + float z2 = z + z; + float xx = x * x2; + float xy = x * y2; + float xz = x * z2; + float yy = y * y2; + float yz = y * z2; + float zz = z * z2; + float sx = w * x2; + float sy = w * y2; + float sz = w * z2; + mat.v0.x = 1.0f - yy - zz; + mat.v0.y = xy + sz; + mat.v0.z = xz - sy; + mat.v0.w = 0.0f; + mat.v1.x = xy - sz; + mat.v1.y = 1.0f - xx - zz; + mat.v1.z = yz + sx; + mat.v1.w = 0.0f; + mat.v2.x = xz + sy; + mat.v2.y = yz - sx; + mat.v2.z = 1.0f - xx - yy; + mat.v2.w = 0.0f; + mat.v3.x = 0.0f; + mat.v3.y = 0.0f; + mat.v3.z = 0.0f; + mat.v3.w = 1.0f; +} + +unsigned short ConvertLensLengthToFovAngle(float f_lens_mm) { + return (bATan(f_lens_mm, 15.96f) & 0x7FFF) << 1; +} + +float ConvertFovAngleToLensLength(unsigned short a_fov) { + unsigned short half_angle = a_fov >> 1; + float tangent = bSin(half_angle) / bCos(half_angle); + + if (tangent <= 0.0f) { + tangent = 0.0001f; + } + + return 15.96f / tangent; +} + +float ConvertLensDeltaToFovDelta(float f_lens_mm, float f_lens_slope) { + unsigned short a = bATan(f_lens_mm, 15.96f) << 1; + unsigned short b = bATan(f_lens_mm + f_lens_slope * 0.01f, 15.96f) << 1; + return (SignExtendAng(b) - SignExtendAng(a)) * 100.0f; +} + +float ConvertApertureNumberToFStop(float aperture) { + const float kFStops[] = { + 1.0f, + 1.1224620f, + 1.2599211f, + 1.4142135f, + 1.5874010f, + 1.7817974f, + 2.0f, + 2.2449241f, + 2.5198421f, + 2.8284271f, + 3.1748021f, + 3.5635948f, + 4.0f, + 4.4898481f, + 5.0396843f, + 5.6568542f, + 6.3496041f, + 7.1271896f, + 8.0f, + 8.9796963f, + 10.079369f, + 11.313708f, + 12.699208f, + 14.254379f, + 16.0f, + 17.959393f, + 20.158737f, + 22.627417f, + 25.398417f, + 28.508759f, + 32.0f, + 35.918785f, + 40.317474f, + 45.254833f, + 50.796833f, + 57.017517f, + 64.0f, + }; + int n = static_cast(aperture + 0.5f); + if (n < 0) { + n = 0; + } + if (n > 36) { + n = 36; + } + + return kFStops[n]; +} + +static void CreateLookAtMatrix(ICE::Matrix4 *mat, ICE::Vector3 &eye, ICE::Vector3 ¢er, unsigned short dutch) { + UMath::Vector3 c; + UMath::Vector3 vUp; + UMath::Vector3 b; + UMath::Vector3 a; + UMath::Matrix4 tl; + + c.x = center.x - eye.x; + c.y = center.y - eye.y; + c.z = center.z - eye.z; + bNormalize(reinterpret_cast(&c), reinterpret_cast(&c)); + + vUp.x = 0.0f; + vUp.y = 0.0f; + vUp.z = 1.0f; + + bCross(reinterpret_cast(&b), reinterpret_cast(&vUp), reinterpret_cast(&c)); + bCross(reinterpret_cast(&a), reinterpret_cast(&c), reinterpret_cast(&b)); + bNormalize(reinterpret_cast(&b), reinterpret_cast(&b)); + bNormalize(reinterpret_cast(&a), reinterpret_cast(&a)); + + bMatrix4 *m = reinterpret_cast(mat); + m->v0.x = b.x; + m->v0.y = a.x; + m->v0.z = c.x; + m->v0.w = 0.0f; + m->v1.x = b.y; + m->v1.y = a.y; + m->v1.z = c.y; + m->v1.w = 0.0f; + m->v2.x = b.z; + m->v2.y = a.z; + m->v2.z = c.z; + m->v2.w = 0.0f; + m->v3.x = 0.0f; + m->v3.y = 0.0f; + m->v3.z = 0.0f; + m->v3.w = 1.0f; + + eRotateZ(m, m, dutch); + + tl.v0.x = 1.0f; + tl.v0.y = 0.0f; + tl.v0.z = 0.0f; + tl.v0.w = 0.0f; + tl.v1.x = 0.0f; + tl.v1.y = 1.0f; + tl.v1.z = 0.0f; + tl.v1.w = 0.0f; + tl.v2.x = 0.0f; + tl.v2.y = 0.0f; + tl.v2.z = 1.0f; + tl.v2.w = 0.0f; + tl.v3.x = -eye.x; + tl.v3.y = -eye.y; + tl.v3.z = -eye.z; + tl.v3.w = 1.0f; + + bMulMatrix(m, m, reinterpret_cast(&tl)); +} + +ICEMover::ICEMover(int nView, ICEAnchor *pCar) + : CameraMover(nView, CM_ICE), // + pCar(pCar), // + mHybridToWorld(), // + vSmoothCarPos(), // + vSmoothCarFwd() { + GetCamera()->ClearVelocity(); + pEye = new (__FILE__, __LINE__) ICE::Cubic3D(0, 1.0f); + pLook = new (__FILE__, __LINE__) ICE::Cubic3D(0, 1.0f); + pDutch = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pFov = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pNearClip = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pNoiseAmplitude = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pNoiseFrequency = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pFocalDistance = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pAperture = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pLetterbox = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pSimSpeed = new (__FILE__, __LINE__) ICE::Cubic1D(0, 1.0f); + pAccelOffset = new (__FILE__, __LINE__) ICE::Cubic3D(1, 1.0f); + pICEData = 0; + nSpaceEye = 0; + nSpaceLook = 0; + fParameter0 = 0.0f; + bViolatesTopology = false; + fParameter1 = 0.0f; + ICE::HideOverlay(); + bIdentity(reinterpret_cast(&mHybridToWorld)); + bCopy(reinterpret_cast(&vSmoothCarPos), reinterpret_cast(pCar->GetGeometryPosition())); + bCopy(reinterpret_cast(&vSmoothCarFwd), reinterpret_cast(pCar->GetForwardVector())); + SetDesired(true, true); +} + +ICEAnchor::ICEAnchor() + : mGeomPos(), // + mGeomRot(), // + mVelocity(), // + mAccel(), // + mVelMag(0.0f), // + mTopSpeed(0.0f), // + mRPM(0.0f), // + mNumWheels(false), // + mForwardSlip(0.0f), // + mSlipAngle(0.0f), // + mIsTouchingGround(true), // + mIsNosEngaged(false), // + mNosPercentageLeft(0.0f) { + bIdentity(reinterpret_cast(&mGeomRot)); +} + +void ICEAnchor::Update(float dT, const ICE::Matrix4 &orientpos, const ICE::Vector3 &velocity, const ICE::Vector3 &) { + float dist = bDistBetween(reinterpret_cast(&mGeomPos), reinterpret_cast(&orientpos.v3)); + + bCopy(reinterpret_cast(&mGeomRot), reinterpret_cast(&orientpos)); + float savedVelMag = mVelMag; + mGeomRot.v3.z = 0.0f; + mGeomRot.v3.y = 0.0f; + mGeomRot.v3.x = 0.0f; + bCopy(reinterpret_cast(&mGeomPos), reinterpret_cast(&orientpos.v3)); + bCopy(reinterpret_cast(&mVelocity), reinterpret_cast(&velocity)); + + mVelMag = bLength(reinterpret_cast(&velocity)); + + if (dT > 0.0f && dist / dT < 300.0f) { + ICE::Vector3 acc((mVelMag - savedVelMag) / dT, 0.0f, 0.0f); + bMulMatrix(reinterpret_cast(&mAccel), reinterpret_cast(&mGeomRot), reinterpret_cast(&acc)); + } else { + mAccel.x = 0.0f; + mAccel.y = 0.0f; + mAccel.z = 0.0f; + } +} + +namespace ICE { + +void Cubic1D::MakeCoeffs() { + float delta = ValDesired - Val; + + Coeff[2] = dVal; + Coeff[3] = Val; + Coeff[0] = (dVal + dValDesired) - (delta + delta); + Coeff[1] = (delta * 3.0f - dValDesired) - (dVal + dVal); +} + +float Cubic1D::GetVal(float t) const { + return ((Coeff[0] * t + Coeff[1]) * t + Coeff[2]) * t + Coeff[3]; +} + +float Cubic1D::GetdVal(float t) const { + return (Coeff[0] * 3.0f * t + (Coeff[1] + Coeff[1])) * t + Coeff[2]; +} + +float Cubic1D::GetddVal(float t) const { + return Coeff[0] * (t * 6.0f) + (Coeff[1] + Coeff[1]); +} + +float Cubic1D::GetValDesired() const { + return ValDesired; +} + +float Cubic1D::GetDerivative(float t) const { + float scale = 1.0f / duration; + return GetdVal(t * scale) * scale; +} + +float Cubic1D::GetSecondDerivative(float t) const { + float scale = 1.0f / duration; + return GetddVal(t * scale) * (scale * scale); +} + +void Cubic1D::ClampDerivative(float fMag) { + float deriv = GetDerivative(duration); + float deriv_abs = bAbs(deriv); + + if (deriv_abs > fMag) { + dValDesired = (deriv_abs / deriv) * fMag * duration; + } +} + +void Cubic1D::ClampSecondDerivative(float fMag) { + float fAcc0 = GetSecondDerivative(0.0f); + float fAcc0Abs = bAbs(fAcc0); + float fAcc1 = GetSecondDerivative(duration); + float fAcc1Abs = bAbs(fAcc1); + bool bNeedFix = false; + + if (fAcc0Abs > fMag) { + float fSign = fAcc0Abs / fAcc0; + fAcc0 = fSign * fMag; + bNeedFix = true; + } + if (fAcc1Abs > fMag) { + float fSign = fAcc1Abs / fAcc1; + fAcc1 = fSign * fMag; + bNeedFix = true; + } + if (bNeedFix) { + float fDurationSquared = duration * duration; + float start = fAcc0 * fDurationSquared; + + Coeff[0] = (fAcc1 * fDurationSquared - start) * (1.0f / 6.0f); + Coeff[1] = start * 0.5f; + } +} + +void Cubic1D::Update(float fSeconds, float fDClamp, float fDDClamp) { + if (state == 1) { + goto update; + } + if (state <= 1) { + return; + } + if (state != 2) { + return; + } + + time = 0.0f; + + if (flags == 0) { + state = 1; + } + if (0.0f < fDClamp) { + ClampDerivative(fDClamp); + } + + MakeCoeffs(); + + if (0.0f < fDDClamp) { + ClampSecondDerivative(fDDClamp); + } + +update: + { + float t; + + if (0.0f < duration) { + float interval = fSeconds / duration; + time = time + interval; + } else { + time = 1.0f; + } + + if (1.0f < time) { + time = 1.0f; + Snap(); + } + + t = time; + Val = GetVal(t); + dVal = GetdVal(t); + } +} + +void Cubic3D::SetVal(const Vector3 *pV) { + SetVal(pV->x, pV->y, pV->z); +} + +void Cubic3D::SetdVal(const Vector3 *pV) { + SetdVal(pV->x, pV->y, pV->z); +} + +void Cubic3D::SetValDesired(const Vector3 *pV) { + SetValDesired(pV->x, pV->y, pV->z); +} + +void Cubic3D::SetdValDesired(const Vector3 *pV) { + SetdValDesired(pV->x, pV->y, pV->z); +} + +void Cubic3D::GetVal(Vector3 *pV) const { + pV->x = x.Val; + pV->y = y.Val; + pV->z = z.Val; +} + +void Cubic3D::GetdVal(Vector3 *pV) const { + pV->x = x.dVal; + pV->y = y.dVal; + pV->z = z.dVal; +} + +void Cubic3D::GetVal(Vector3 *pV, float t) const { + pV->x = x.GetVal(t); + pV->y = y.GetVal(t); + pV->z = z.GetVal(t); +} + +void Cubic3D::GetValDesired(Vector3 *pV) const { + pV->x = x.ValDesired; + pV->y = y.ValDesired; + pV->z = z.ValDesired; +} + +void Cubic3D::Update(float fSeconds, float fDClamp, float fDDClamp) { + x.Update(fSeconds, fDClamp, fDDClamp); + y.Update(fSeconds, fDClamp, fDDClamp); + z.Update(fSeconds, fDClamp, fDDClamp); +} + +} // namespace ICE + +void ICEMover::EyeCubicInit(ICE::Cubic3D *pEye, ICE::Matrix4 *pMatrix, ICE::Vector3 *pVelocity) { + ICE::Vector3 vEye; + ICE::Vector3 vEyeRel; + UMath::Vector4 vEyeVel; + + bScaleAdd(reinterpret_cast(&vEye), + GetCamera()->GetPosition(), + GetCamera()->GetVelocityPosition(), + 1.0f / 30.0f); + if (pMatrix != 0) { + bMulMatrix(reinterpret_cast(&vEye), reinterpret_cast(pMatrix), reinterpret_cast(&vEye)); + } + pEye->SetVal(&vEye); + + bVector3 *velPos = GetCamera()->GetVelocityPosition(); + vEyeRel.x = velPos->x; + vEyeRel.y = velPos->y; + vEyeRel.z = velPos->z; + if (pVelocity != 0) { + bSub(reinterpret_cast(&vEyeRel), reinterpret_cast(&vEyeRel), reinterpret_cast(pVelocity)); + } + float dur = pEye->x.duration; + bScale(reinterpret_cast(&vEyeVel), reinterpret_cast(&vEyeRel), dur); + vEyeVel.w = 0.0f; + if (pMatrix != 0) { + bMulMatrix(reinterpret_cast(&vEyeVel), reinterpret_cast(pMatrix), reinterpret_cast(&vEyeVel)); + } + pEye->SetdVal(reinterpret_cast(&vEyeVel)); +} + +void ICEMover::LookCubicInit(ICE::Cubic3D *pLook, ICE::Matrix4 *pMatrix, ICE::Vector3 *pVelocity) { + ICE::Vector3 vLook; + ICE::Vector3 vLookRel; + UMath::Vector4 vLookVel; + + bScaleAdd(reinterpret_cast(&vLook), + GetCamera()->GetTarget(), + GetCamera()->GetVelocityTarget(), + 1.0f / 30.0f); + if (pMatrix != 0) { + bMulMatrix(reinterpret_cast(&vLook), reinterpret_cast(pMatrix), reinterpret_cast(&vLook)); + } + pLook->SetVal(&vLook); + + bVector3 *velTgt = GetCamera()->GetVelocityTarget(); + vLookRel.x = velTgt->x; + vLookRel.y = velTgt->y; + vLookRel.z = velTgt->z; + if (pVelocity != 0) { + bSub(reinterpret_cast(&vLookRel), reinterpret_cast(&vLookRel), reinterpret_cast(pVelocity)); + } + float dur = pLook->x.duration; + bScale(reinterpret_cast(&vLookVel), reinterpret_cast(&vLookRel), dur); + vLookVel.w = 0.0f; + if (pMatrix != 0) { + bMulMatrix(reinterpret_cast(&vLookVel), reinterpret_cast(pMatrix), reinterpret_cast(&vLookVel)); + } + pLook->SetdVal(reinterpret_cast(&vLookVel)); +} + +void ICEMover::DutchCubicInit(ICE::Cubic1D *pDutch) { + pDutch->SetVal(0.0f); + pDutch->SetdVal(0.0f); +} + +void ICEMover::FovCubicInit(ICE::Cubic1D *pFov) { + float fFov = static_cast(static_cast(GetCamera()->GetFov())) + static_cast(static_cast(GetCamera()->GetVelocityFov())) * (1.0f / 30.0f); + float fFovVel = static_cast(static_cast(GetCamera()->GetVelocityFov())) * pFov->duration; + + pFov->SetVal(fFov); + pFov->SetdVal(fFovVel); +} + +void ICEMover::SetDesired(bool b_snap, bool b_refresh) { + ICEData *pOldCameraData = pICEData; + ICETrack *p_track = 0; + ICEData *pCameraData = TheICEManager.GetCameraData(&p_track, &fParameter0, &fParameter1); + + bool b_new_camera = (pICEData != pCameraData); + + if (!b_refresh && !b_new_camera) { + return; + } + + int hard_cut = 0; + if (b_new_camera && pCameraData != 0 && pOldCameraData != 0) { + hard_cut = (ICE::KeysShared(pOldCameraData, 1, pCameraData, 0) == 0); + } + + int cond1 = 0; + if (pCameraData != 0 && pICEData == 0 && !(pCameraData->bSmooth & 1)) { + cond1 = 1; + } + int flush = (hard_cut | cond1) != 0; + + int cond2 = 0; + if (pICEData != 0 && pCameraData == 0 && !(pICEData->bSmooth & 1)) { + cond2 = 1; + } + flush = (flush | cond2) != 0; + + pICEData = pCameraData; + + if (flush) { + FlushAccumulationBuffer(); + bCopy(reinterpret_cast(&vSmoothCarPos), + reinterpret_cast(pCar->GetGeometryPosition())); + bCopy(reinterpret_cast(&vSmoothCarFwd), + reinterpret_cast(pCar->GetForwardVector())); + } + + if (b_new_camera) { + int key = TheICEManager.GetCameraIndex((fParameter0 + fParameter1) * 0.5f, p_track); + ICE::FireEventTag(key); + } + + if (pCameraData == 0) { + return; + } + + nSpaceEye = pCameraData->nSpaceEye; + nSpaceLook = pCameraData->nSpaceLook; + + { + ICE::Vector3 v_eye[2]; + ICE::Vector3 v_look[2]; + ICE::Vector3 v_eye_slope[2]; + ICE::Vector3 v_look_slope[2]; + float f_dutch_slope[2]; + float f_lens_slope[2]; + + pCameraData->GetEye(0, &v_eye[0]); + pCameraData->GetEye(1, &v_eye[1]); + pCameraData->GetLook(0, &v_look[0]); + pCameraData->GetLook(1, &v_look[1]); + + TheICEManager.GetSlope(&v_eye_slope[0], &v_look_slope[0], &f_lens_slope[0], &f_dutch_slope[0], pCameraData, 0, p_track); + TheICEManager.GetSlope(&v_eye_slope[1], &v_look_slope[1], &f_lens_slope[1], &f_dutch_slope[1], pCameraData, 1, p_track); + + pEye->SetVal(&v_eye[0]); + pEye->SetdVal(&v_eye_slope[0]); + pEye->SetValDesired(&v_eye[1]); + pEye->SetdValDesired(&v_eye_slope[1]); + + pLook->SetVal(&v_look[0]); + pLook->SetdVal(&v_look_slope[0]); + pLook->SetValDesired(&v_look[1]); + pLook->SetdValDesired(&v_look_slope[1]); + + pDutch->SetVal(pCameraData->fDutch[0]); + pDutch->SetdVal(f_dutch_slope[0]); + pDutch->SetValDesired(pCameraData->fDutch[1]); + pDutch->dValDesired = f_dutch_slope[1]; + + pFov->SetVal(static_cast(ConvertLensLengthToFovAngle(pCameraData->fLens[0]))); + pFov->SetdVal(ConvertLensDeltaToFovDelta(pCameraData->fLens[0], f_lens_slope[0])); + pFov->SetValDesired(static_cast(ConvertLensLengthToFovAngle(pCameraData->fLens[1]))); + pFov->dValDesired = ConvertLensDeltaToFovDelta(pCameraData->fLens[1], f_lens_slope[1]); + + if (flush) { + bIdentity(reinterpret_cast(&mHybridToWorld)); + bCopy(reinterpret_cast(&mHybridToWorld), + reinterpret_cast(pCar->GetGeometryOrientation()), + reinterpret_cast(pCar->GetGeometryPosition())); + } + + if (b_new_camera) { + bMirrorICEData = (pCameraData->bSmooth >> 1) & 1; + + if ((pCameraData->bSmooth & 1) != 0) { + TheICEManager.SetSmoothExit(true); + { + ICE::Matrix4 mCarToWorld; + ICE::Matrix4 mWorldToCar; + + bCopy(reinterpret_cast(&mCarToWorld), + reinterpret_cast(pCar->GetGeometryOrientation()), + reinterpret_cast(pCar->GetGeometryPosition())); + + eInvertTransformationMatrix(reinterpret_cast(&mWorldToCar), + reinterpret_cast(&mCarToWorld)); + + ICE::Matrix4 mWorldToHybrid; + + eInvertTransformationMatrix(reinterpret_cast(&mWorldToHybrid), + reinterpret_cast(&mHybridToWorld)); + + ICE::Cubic3D eye(1, 1.0f); + ICE::Cubic3D look(1, 1.0f); + ICE::Cubic1D dutch(1, 1.0f); + ICE::Cubic1D fov(1, 1.0f); + + ICE::Matrix4 mWorldToScene; + ICE::Matrix4 *pWorldToScene = 0; + ICE::Matrix4 *eye_space; + ICE::Matrix4 *look_space; + + if (nSpaceEye == 3 || nSpaceLook == 3) { + ICEScene *scene = ICE::FindAnimScene(); + if (scene != 0) { + bMatrix4 &sceneMat = scene->GetSceneTransformMatrix(); + pWorldToScene = &mWorldToScene; + eInvertTransformationMatrix(reinterpret_cast(pWorldToScene), &sceneMat); + } + } + + switch (nSpaceEye) { + case 0: eye_space = &mWorldToCar; break; + case 2: eye_space = &mWorldToHybrid; break; + case 3: eye_space = pWorldToScene; break; + default: eye_space = 0; break; + } + + switch (nSpaceLook) { + case 0: look_space = &mWorldToCar; break; + case 2: look_space = &mWorldToHybrid; break; + case 3: look_space = pWorldToScene; break; + default: look_space = 0; break; + } + + ICE::Vector3 *eye_vel = (nSpaceEye == 0) ? pCar->GetVelocity() : static_cast(0); + ICE::Vector3 *look_vel = (nSpaceLook == 0) ? pCar->GetVelocity() : static_cast(0); + + EyeCubicInit(&eye, eye_space, eye_vel); + LookCubicInit(&look, look_space, look_vel); + DutchCubicInit(&dutch); + FovCubicInit(&fov); + + float f_route_param = TheICEManager.GetParameter(); + float f_to_start = bAbs(f_route_param - fParameter0); + float f_to_end = bAbs(f_route_param - fParameter1); + + if (f_to_end <= f_to_start) { + ICE::Vector3 v_eye_val; + ICE::Vector3 v_deye; + ICE::Vector3 v_look_val; + ICE::Vector3 v_dlook; + + eye.GetVal(&v_eye_val); + eye.GetdVal(&v_deye); + pEye->SetVal(&v_eye_val); + pEye->SetdVal(&v_deye); + + look.GetVal(&v_look_val); + look.GetdVal(&v_dlook); + pLook->SetVal(&v_look_val); + pLook->SetdVal(&v_dlook); + + pDutch->SetVal(dutch.GetVal()); + pDutch->SetdVal(dutch.GetdVal()); + + pFov->SetVal(fov.GetVal()); + pFov->SetdVal(fov.GetdVal()); + } else { + ICE::Vector3 v_eye_val; + ICE::Vector3 v_deye; + ICE::Vector3 v_look_val; + ICE::Vector3 v_dlook; + + eye.GetVal(&v_eye_val); + eye.GetdVal(&v_deye); + pEye->SetValDesired(&v_eye_val); + pEye->SetdValDesired(&v_deye); + + look.GetVal(&v_look_val); + look.GetdVal(&v_dlook); + pLook->SetValDesired(&v_look_val); + pLook->SetdValDesired(&v_dlook); + + pDutch->SetValDesired(dutch.GetVal()); + pDutch->dValDesired = dutch.GetdVal(); + + pFov->SetValDesired(fov.GetVal()); + pFov->dValDesired = fov.GetdVal(); + } + } + } + } + } + + pEye->MakeCoeffs(); + pLook->MakeCoeffs(); + pDutch->MakeCoeffs(); + pFov->MakeCoeffs(); + + pNearClip->SetVal(pCameraData->fNearClip[0]); + pNearClip->SetValDesired(pCameraData->fNearClip[1]); + pNearClip->MakeCoeffs(); + + pNoiseAmplitude->SetVal(pCameraData->fNoiseAmplitude[0]); + pNoiseAmplitude->SetValDesired(pCameraData->fNoiseAmplitude[1]); + pNoiseAmplitude->MakeCoeffs(); + + pNoiseFrequency->SetVal(pCameraData->fNoiseFrequency[0]); + pNoiseFrequency->SetValDesired(pCameraData->fNoiseFrequency[1]); + pNoiseFrequency->MakeCoeffs(); + + pFocalDistance->SetVal(pCameraData->fFocalDistance[0]); + pFocalDistance->SetValDesired(pCameraData->fFocalDistance[1]); + pFocalDistance->MakeCoeffs(); + + pAperture->SetVal(static_cast(pCameraData->fAperture[0])); + pAperture->SetValDesired(static_cast(pCameraData->fAperture[1])); + pAperture->MakeCoeffs(); + + pLetterbox->SetVal(static_cast(pCameraData->fLetterbox[0])); + pLetterbox->SetValDesired(static_cast(pCameraData->fLetterbox[1])); + pLetterbox->MakeCoeffs(); + + pSimSpeed->SetVal(static_cast(pCameraData->fSimSpeed[0])); + pSimSpeed->SetValDesired(static_cast(pCameraData->fSimSpeed[1])); + pSimSpeed->MakeCoeffs(); + + int state = TheICEManager.GetState(); + if ((state == 0 || state == 5 || state == 7) && + (pOldCameraData == 0 || pOldCameraData->nOverlay != pCameraData->nOverlay)) { + ICE::HideOverlay(); + ICE::ShowOverlay(pCameraData->nOverlay); + } +} + +float ICEMover::GetDutch(float f_param) { + if (pICEData == 0) { + goto blend; + } + if (pICEData->bCubicEye == 0) { + goto blend; + } + + return pDutch->GetVal(f_param); + +blend: { + float v0 = pDutch->GetVal(); + float v1 = pDutch->GetValDesired(); + return v0 * (1.0f - f_param) + v1 * f_param; +} +} + +unsigned short ICEMover::GetFOV(float f_param) { + float result; + + if (pICEData == 0) { + goto blend; + } + if (pICEData->bCubicEye == 0) { + goto blend; + } + + result = pFov->GetVal(f_param); + goto done; + +blend: + result = pFov->GetVal() * (1.0f - f_param) + pFov->GetValDesired() * f_param; + +done: + return static_cast(result); +} + +ICEMover::~ICEMover() { + ICE::HideOverlay(); + delete pEye; + delete pLook; + delete pDutch; + delete pFov; + delete pNearClip; + delete pNoiseAmplitude; + delete pNoiseFrequency; + delete pFocalDistance; + delete pAperture; + delete pLetterbox; + delete pSimSpeed; + delete pAccelOffset; + GetCamera()->SetSimTimeMultiplier(1.0f); + GetCamera()->SetLetterBox(0.0f); + GetCamera()->SetDepthOfField(0.0f); + GetCamera()->SetFocalDistance(0.0f); + GetCamera()->ClearVelocity(); +} + +void ICEMover::GetEye(ICE::Vector3 *vEye, float f_param) { + if (pICEData == 0) { + goto blend; + } + if (pICEData->bCubicEye == 0) { + goto blend; + } + + pEye->GetVal(vEye, f_param); + return; + +blend: + { + ICE::Vector3 v0; + ICE::Vector3 v1; + pEye->GetVal(&v0); + pEye->GetValDesired(&v1); + float inv = 1.0f - f_param; + bScale(reinterpret_cast(vEye), reinterpret_cast(&v0), inv); + bScaleAdd(reinterpret_cast(vEye), reinterpret_cast(vEye), + reinterpret_cast(&v1), f_param); + } +} + +void ICEMover::GetLook(ICE::Vector3 *vLook, float f_param) { + if (pICEData == 0) { + goto blend; + } + if (pICEData->bCubicLook == 0) { + goto blend; + } + + pLook->GetVal(vLook, f_param); + return; + +blend: + { + ICE::Vector3 v0; + ICE::Vector3 v1; + pLook->GetVal(&v0); + pLook->GetValDesired(&v1); + float inv = 1.0f - f_param; + bScale(reinterpret_cast(vLook), reinterpret_cast(&v0), inv); + bScaleAdd(reinterpret_cast(vLook), reinterpret_cast(vLook), + reinterpret_cast(&v1), f_param); + } +} + +ICEAnchor *GetICEAnchor() { + eView *view = eGetView(1, false); + if (view != 0) { + CameraMover *m = view->GetCameraMover(); + if (m != 0 && m->GetType() == CM_ICE) { + return reinterpret_cast(m)->GetICEAnchor(); + } + } + return 0; +} + +void ICEMover::Update(float dT) { + ICETrack *p_track = TheICEManager.GetPlaybackTrack(); + bool bLerpLag; + UMath::Matrix4 mSceneToWorld; + + float realTime = TheICEManager.IsUsingRealTime(); + if (realTime == 0.0f && TheGameFlowManager.IsPaused()) { + return; + } + + bLerpLag = false; + if (p_track) { + bLerpLag = (p_track->GetContext() == 3); + if (p_track->GetContext() != 2) { + bMirrorICEData = false; + } + } + + bViolatesTopology = false; + SetDesired(false, TheICEManager.RefreshCameraSplines()); + + bIdentity(reinterpret_cast(&mSceneToWorld)); + + if (nSpaceEye == 3 || nSpaceLook == 3) { + ICEScene *scene = ICE::FindAnimScene(); + if (scene == 0) { + return; + } + bMatrix4 &sceneMat = scene->GetSceneTransformMatrix(); + bCopy(reinterpret_cast(&mSceneToWorld), reinterpret_cast(&sceneMat)); + } + + float f_route_param; + if (p_track) { + f_route_param = p_track->GetParameter(); + } else { + f_route_param = TheICEManager.GetParameter(); + } + + float f_param = 0.0f; + if (0.0001f < bAbs(fParameter1 - fParameter0)) { + f_param = (f_route_param - fParameter0) / (fParameter1 - fParameter0); + } + + if (1.0f <= f_route_param) { + TheICEManager.SetGenericCameraToPlay("", ""); + } + + float simspeed = pSimSpeed->GetVal(f_param); + if (0.0f <= simspeed) { + if (1.0f <= simspeed) { + simspeed = 1.0f; + } + simspeed = simspeed * 30.0f; + GetCamera()->SetSimTimeMultiplier(simspeed); + } + if (simspeed < 1.0f) { + simspeed = 1.0f; + } + + float f_near_clip = pNearClip->GetVal(f_param); + if (0.0f <= f_near_clip) { + GetCamera()->SetNearZ(f_near_clip); + } + + unsigned short a_fov = GetFOV(f_param); + if (a_fov != 0) { + GetCamera()->SetFieldOfView(a_fov); + } + + ICE::Vector3 vEye; + ICE::Vector3 vLook; + + GetEye(&vEye, f_param); + GetLook(&vLook, f_param); + + UMath::Matrix4 mCarToWorld; + int n_state; + + if (pICEData == 0) { + ICE::Vector3 *carPos = pCar->GetGeometryPosition(); + mCarToWorld.v0 = *reinterpret_cast(&pCar->GetGeometryOrientation()->v0); + mCarToWorld.v1 = *reinterpret_cast(&pCar->GetGeometryOrientation()->v1); + mCarToWorld.v2 = *reinterpret_cast(&pCar->GetGeometryOrientation()->v2); + bCopy(reinterpret_cast(&mCarToWorld.v3), reinterpret_cast(carPos), 1.0f); + } else if (pICEData->bIgnoreOrientation == 0) { + if (pICEData->bCarSpaceLag == 0 || !bLerpLag) { + ICE::Vector3 *carPos = pCar->GetGeometryPosition(); + bCopy(reinterpret_cast(&mCarToWorld), + reinterpret_cast(pCar->GetGeometryOrientation()), + reinterpret_cast(carPos)); + } else { + float lerp = f_route_param * 10.0f + 0.01f; + ICE::Vector3 *carPos = pCar->GetGeometryPosition(); + + vSmoothCarPos.x = (carPos->x - vSmoothCarPos.x) * lerp + vSmoothCarPos.x; + vSmoothCarPos.y = (carPos->y - vSmoothCarPos.y) * lerp + vSmoothCarPos.y; + vSmoothCarPos.z = (carPos->z - vSmoothCarPos.z) * lerp + vSmoothCarPos.z; + + ICE::Vector3 *carFwd = pCar->GetForwardVector(); + vSmoothCarFwd.x = (carFwd->x - vSmoothCarFwd.x) * lerp + vSmoothCarFwd.x; + vSmoothCarFwd.y = (carFwd->y - vSmoothCarFwd.y) * lerp + vSmoothCarFwd.y; + vSmoothCarFwd.z = (carFwd->z - vSmoothCarFwd.z) * lerp + vSmoothCarFwd.z; + + bIdentity(reinterpret_cast(&mCarToWorld)); + bNormalize(reinterpret_cast(&vSmoothCarFwd), reinterpret_cast(&vSmoothCarFwd)); + bCopy(reinterpret_cast(&mCarToWorld.v0), reinterpret_cast(&vSmoothCarFwd), 0.0f); + bCross(reinterpret_cast(&mCarToWorld.v1), reinterpret_cast(&mCarToWorld.v2), reinterpret_cast(&mCarToWorld.v0)); + bCross(reinterpret_cast(&mCarToWorld.v2), reinterpret_cast(&mCarToWorld.v0), reinterpret_cast(&mCarToWorld.v1)); + bCopy(reinterpret_cast(&mCarToWorld.v3), reinterpret_cast(&vSmoothCarPos), 1.0f); + } + } else { + mCarToWorld.v0 = *reinterpret_cast(&mHybridToWorld.v0); + mCarToWorld.v1 = *reinterpret_cast(&mHybridToWorld.v1); + mCarToWorld.v2 = *reinterpret_cast(&mHybridToWorld.v2); + ICE::Vector3 *carPos = pCar->GetGeometryPosition(); + bCopy(reinterpret_cast(&mCarToWorld.v3), reinterpret_cast(carPos), 1.0f); + } + + if (nSpaceEye == 2) { + ICE::Vector3 *carPos = pCar->GetGeometryPosition(); + vEye.x += carPos->x; + vEye.y += carPos->y; + vEye.z += carPos->z; + } else if (nSpaceEye == 0) { + bMulMatrix(reinterpret_cast(&vEye), reinterpret_cast(&mCarToWorld), reinterpret_cast(&vEye)); + } else if (nSpaceEye == 3) { + bMulMatrix(reinterpret_cast(&vEye), reinterpret_cast(&mSceneToWorld), reinterpret_cast(&vEye)); + } + + if (nSpaceLook == 2) { + ICE::Vector3 *carPos = pCar->GetGeometryPosition(); + vLook.x += carPos->x; + vLook.y += carPos->y; + vLook.z += carPos->z; + } else if (nSpaceLook == 0) { + bMulMatrix(reinterpret_cast(&vLook), reinterpret_cast(&mCarToWorld), reinterpret_cast(&vLook)); + } else if (nSpaceLook == 3) { + bMulMatrix(reinterpret_cast(&vLook), reinterpret_cast(&mSceneToWorld), reinterpret_cast(&vLook)); + } + + if (pICEData != 0 && pICEData->bCarSpaceLag != 0) { + ICE::Vector3 accel_offset; + pAccelOffset->SetValDesired(reinterpret_cast(pCar->GetAcceleration())); + pAccelOffset->Update(dT * simspeed, 0.0f, 0.0f); + pAccelOffset->GetVal(&accel_offset); + + accel_offset.x = accel_offset.x * vIceAccelLagScale.x; + if (accel_offset.x - vIceAccelLagMin.x < 0.0f) { + accel_offset.x = vIceAccelLagMin.x; + } + if (vIceAccelLagMax.x - accel_offset.x < 0.0f) { + accel_offset.x = vIceAccelLagMax.x; + } + + accel_offset.y = accel_offset.y * vIceAccelLagScale.y; + if (accel_offset.y - vIceAccelLagMin.y < 0.0f) { + accel_offset.y = vIceAccelLagMin.y; + } + if (vIceAccelLagMax.y - accel_offset.y < 0.0f) { + accel_offset.y = vIceAccelLagMax.y; + } + + accel_offset.z = accel_offset.z * vIceAccelLagScale.z; + if (accel_offset.z - vIceAccelLagMin.z < 0.0f) { + accel_offset.z = vIceAccelLagMin.z; + } + if (vIceAccelLagMax.z - accel_offset.z < 0.0f) { + accel_offset.z = vIceAccelLagMax.z; + } + + vLook.x -= accel_offset.x; + vLook.y -= accel_offset.y; + vLook.z -= accel_offset.z; + vEye.x -= accel_offset.x; + vEye.y -= accel_offset.y; + vEye.z -= accel_offset.z; + } + + n_state = TheICEManager.GetState(); + unsigned short dutch = static_cast(static_cast(GetDutch(f_param) * 65536.0f) & 0xffff); + + UMath::Matrix4 mWorldToCamera; + CreateLookAtMatrix(reinterpret_cast(&mWorldToCamera), + *reinterpret_cast(&vEye), + *reinterpret_cast(&vLook), + dutch); + + float f_amplitude = pNoiseAmplitude->GetVal(f_param); + float f_frequency = pNoiseFrequency->GetVal(f_param); + if (f_amplitude < 0.0f) { + f_amplitude = 0.0f; + } + if (f_frequency < 0.0f) { + f_frequency = 0.0f; + } + + if (pICEData == 0 || pICEData->nShakeType == 0) { + GetCamera()->SetNoiseAmplitude1(0.0f, 0.0f, f_amplitude, f_amplitude); + GetCamera()->SetNoiseFrequency1(0.0f, 0.0f, f_amplitude, f_amplitude); + GetCamera()->SetNoiseAmplitude2(0.0f, 0.0f, 0.0f, 0.0f); + GetCamera()->SetNoiseFrequency2(0.0f, 0.0f, 0.0f, 0.0f); + + float routeLen; + if (p_track) { + routeLen = p_track->GetLength(); + } else { + routeLen = TheICEManager.GetParameterLength(); + } + + float time = f_route_param * routeLen; + GetCamera()->ApplyNoise(reinterpret_cast(&mWorldToCamera), time, 1.0f); + } else { + ICEShakeTrack *shake_track = TheICEManager.GetShakeTrack(pICEData->nShakeType); + if (shake_track != 0) { + float routeLen; + if (p_track) { + routeLen = p_track->GetLength(); + } else { + routeLen = TheICEManager.GetParameterLength(); + } + + int numKeys = static_cast(shake_track->NumKeys); + int frame = static_cast(f_route_param * routeLen * f_frequency); + + if (numKeys < 1) { + frame = 0; + } else { + while (frame < 0) { + frame += numKeys; + } + frame = frame % numKeys; + } + + int maxKey = shake_track->NumKeys - 1; + int clampedFrame = frame; + if (clampedFrame < 0) clampedFrame = 0; + if (clampedFrame > maxKey) clampedFrame = maxKey; + + ICEShakeData *shake_data; + if (frame == clampedFrame) { + shake_data = &shake_track->Keys[clampedFrame]; + } else { + shake_data = 0; + } + + if (shake_data != 0) { + UMath::Vector3 r; + r.x = shake_data->q[0] * f_amplitude; + r.y = shake_data->q[1] * f_amplitude; + r.z = shake_data->q[2] * f_amplitude; + float w = bLength(reinterpret_cast(&r)); + float halfAngle = bSqrt(1.0f - w); + + bQuaternion q(r.x, r.y, r.z, halfAngle); + UMath::Matrix4 shake_matrix; + q.GetMatrix(*reinterpret_cast(&shake_matrix)); + + UMath::Vector3 t; + t.x = shake_data->p[0] * f_amplitude; + t.y = shake_data->p[1] * f_amplitude; + t.z = shake_data->p[2] * f_amplitude; + bCopy(reinterpret_cast(&shake_matrix), + reinterpret_cast(&shake_matrix), + reinterpret_cast(&t)); + + bMulMatrix(reinterpret_cast(&mWorldToCamera), + reinterpret_cast(&shake_matrix), + reinterpret_cast(&mWorldToCamera)); + } + } + } + + if (!p_track || p_track->GetContext() != 2) { + bool constrain_to_topology = false; + if (pICEData != 0 && pICEData->bConstrainToWorld != 0) { + constrain_to_topology = (n_state < 9); + } + if (constrain_to_topology) { + MinGapTopology(reinterpret_cast(&mWorldToCamera), + reinterpret_cast(pCar->GetGeometryPosition())); + } + + bool constrain_to_cars = false; + if (pICEData != 0 && pICEData->bConstrainToCars != 0) { + constrain_to_cars = (n_state < 9); + } + if (constrain_to_cars) { + MinGapCars(reinterpret_cast(&mWorldToCamera), + reinterpret_cast(pCar->GetGeometryPosition()), + reinterpret_cast(pCar->GetVelocity())); + } + } else { + int topViolation = MinGapTopology(reinterpret_cast(&mWorldToCamera), + reinterpret_cast(pCar->GetGeometryPosition())); + bViolatesTopology = topViolation != 0; + int carViolation = MinGapCars(reinterpret_cast(&mWorldToCamera), + reinterpret_cast(pCar->GetGeometryPosition()), + reinterpret_cast(pCar->GetVelocity())); + bViolatesTopology = (bViolatesTopology || carViolation != 0); + } + + float letterbox = pLetterbox->GetVal(f_param); + if (0.0f <= letterbox) { + GetCamera()->SetLetterBox(letterbox * 0.01f); + } + + float aperture = pAperture->GetVal(f_param); + if (aperture < 0.0f || 37.0f <= aperture) { + GetCamera()->SetFocalDistance(0.0f); + } else { + float focal = pFocalDistance->GetVal(f_param); + if (focal < 1.0f) { + focal = 1.0f; + } + focal = focal * 100.0f; + + unsigned short fovAngle = GetFOV(f_param); + float lens = ConvertFovAngleToLensLength(fovAngle); + float fstop = ConvertApertureNumberToFStop(aperture); + + float hyperFocal = (lens * lens) / (fstop * 0.03f) + lens; + float dofNear = (hyperFocal - lens) * focal; + float dofFar = 10000.0f; + if (0.001f < hyperFocal - focal) { + dofFar = (dofNear / (hyperFocal - focal)) * 2.0f; + } + if (dofFar < dofNear) { + dofFar = dofNear + 1.0f; + } + GetCamera()->SetFocalDistance((dofFar + dofNear) * 0.5f); + } + GetCamera()->SetDepthOfField(0.0f); + + GetCamera()->SetTargetDistance(bDistBetween(reinterpret_cast(&vEye), reinterpret_cast(&vLook))); + + GetCamera()->SetCameraMatrix(*reinterpret_cast(&mWorldToCamera), dT * simspeed); +} diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEMover.hpp b/src/Speed/Indep/Src/Camera/ICE/ICEMover.hpp index a643a2a0b..793113210 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEMover.hpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEMover.hpp @@ -5,6 +5,367 @@ #pragma once #endif +#include "ICEMath.hpp" +#include "ICEData.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" + +namespace ICE { + +struct Cubic1D { + Cubic1D(short type = 0, float dur = 0.0f) + : Val(0.0f), // + dVal(0.0f), // + ValDesired(0.0f), // + dValDesired(0.0f), // + time(0.0f), // + duration(dur), // + state(0), // + flags(type) { + Coeff[0] = 0.0f; + Coeff[1] = 0.0f; + Coeff[2] = 0.0f; + Coeff[3] = 0.0f; + } + + void Snap() { + Val = ValDesired; + dVal = dValDesired; + state = 0; + } + + void SetVal(float v) { + Val = v; + if (v != ValDesired) { + state = 2; + } + } + + void SetdVal(float v) { + dVal = v; + if (v != dValDesired) { + state = 2; + } + } + + void SetValDesired(float v) { + ValDesired = v; + if (v != Val) { + state = 2; + } + } + + void SetdValDesired(float v) { + dValDesired = v; + if (v != dVal) { + state = 2; + } + } + + void SetDuration(float t) { + duration = t; + } + + void SetState(short s) { + state = s; + } + + void SetFlags(short f) { + flags = f; + } + + float GetVal() const { + return Val; + } + + float GetdVal() const { + return dVal; + } + + float GetddVal() const { + return GetddVal(time); + } + + int HasArrived() const { + return state == 0; + } + + void MakeCoeffs(); + float GetVal(float t) const; + float GetdVal(float t) const; + float GetddVal(float t) const; + float GetValDesired() const; + float GetDerivative(float t) const; + float GetSecondDerivative(float t) const; + void ClampDerivative(float fMag); + void ClampSecondDerivative(float fMag); + void Update(float fSeconds, float fDClamp, float fDDClamp); + + float Val; + float dVal; + float ValDesired; + float dValDesired; + float Coeff[4]; + float time; + float duration; + short state; + short flags; +}; + +struct Cubic3D { + Cubic3D(short type = 0, float dur = 0.0f) + : x(type, dur), // + y(type, dur), // + z(type, dur) {} + + Cubic3D(short type, const Vector3 *pDuration) + : x(type, pDuration->x), // + y(type, pDuration->y), // + z(type, pDuration->z) {} + + int HasArrived() const { + return x.HasArrived() && y.HasArrived() && z.HasArrived(); + } + + void Snap() { + x.Snap(); + y.Snap(); + z.Snap(); + } + + void SetDuration(float t) { + x.SetDuration(t); + y.SetDuration(t); + z.SetDuration(t); + } + + void SetState(short s) { + x.SetState(s); + y.SetState(s); + z.SetState(s); + } + + void SetFlags(short s) { + x.SetFlags(s); + y.SetFlags(s); + z.SetFlags(s); + } + + void MakeCoeffs() { + x.MakeCoeffs(); + y.MakeCoeffs(); + z.MakeCoeffs(); + } + + void SetVal(float vx, float vy, float vz) { + x.SetVal(vx); + y.SetVal(vy); + z.SetVal(vz); + } + + void SetdVal(float vx, float vy, float vz) { + x.SetdVal(vx); + y.SetdVal(vy); + z.SetdVal(vz); + } + + void SetValDesired(float vx, float vy, float vz) { + x.SetValDesired(vx); + y.SetValDesired(vy); + z.SetValDesired(vz); + } + + void SetdValDesired(float vx, float vy, float vz) { + x.dValDesired = vx; + y.dValDesired = vy; + z.dValDesired = vz; + } + + void SetVal(const Vector3 *pV); + void SetdVal(const Vector3 *pV); + void SetValDesired(const Vector3 *pV); + void SetdValDesired(const Vector3 *pV); + void GetVal(Vector3 *pV) const; + void GetdVal(Vector3 *pV) const; + void GetVal(Vector3 *pV, float t) const; + void GetValDesired(Vector3 *pV) const; + void Update(float fSeconds, float fDClamp, float fDDClamp); + + Cubic1D x; + Cubic1D y; + Cubic1D z; +}; + +} // namespace ICE + +struct ICEAnchor { + ICE::Vector3 *GetVelocity() { + return &mVelocity; + } + + float GetVelocityMagnitude() { + return mVelMag; + } + + ICE::Vector3 *GetForwardVector() { + return reinterpret_cast(&mGeomRot.v0); + } + + ICE::Vector3 *GetUpVector() { + return reinterpret_cast(&mGeomRot.v2); + } + + ICE::Vector3 *GetGeometryPosition() { + return &mGeomPos; + } + + ICE::Vector3 *GetAcceleration() { + return &mAccel; + } + + ICE::Matrix4 *GetGeometryOrientation() { + return &mGeomRot; + } + + float GetTopSpeed() const { + return mTopSpeed; + } + + void SetTopSpeed(float s) { + mTopSpeed = s; + } + + float GetRPM() const { + return mRPM; + } + + void SetRPM(float s) { + mRPM = s; + } + + bool GetNumWheels() const { + return mNumWheels; + } + + void SetNumWheels(bool w) { + mNumWheels = w; + } + + int IsTouchingGround() const { + return mIsTouchingGround; + } + + void SetTouchingGround(bool touching) { + mIsTouchingGround = touching; + } + + float GetForwardSlip() const { + return mForwardSlip; + } + + void SetForwardSlip(float slip) { + mForwardSlip = slip; + } + + float GetSlipAngle() const { + return mSlipAngle; + } + + void SetSlipAngle(float a) { + mSlipAngle = a; + } + + bool IsNosEngaged() const { + return mIsNosEngaged; + } + + void SetNosEngaged(bool engaged) { + mIsNosEngaged = engaged; + } + + float GetNosPercentageLeft() const { + return mNosPercentageLeft; + } + + void SetNosPercentageLeft(float percentage) { + mNosPercentageLeft = percentage; + } + + void Update(float dT, const ICE::Matrix4 &orientpos, const ICE::Vector3 &velocity, const ICE::Vector3 &acceleration); + ICEAnchor(); + + ICE::Vector3 mGeomPos; + ICE::Matrix4 mGeomRot; + ICE::Vector3 mVelocity; + ICE::Vector3 mAccel; + float mVelMag; + float mTopSpeed; + float mRPM; + bool mNumWheels; + float mForwardSlip; + float mSlipAngle; + bool mIsTouchingGround; + bool mIsNosEngaged; + float mNosPercentageLeft; +}; + +class ICEMover : public CameraMover { + public: + ICEMover(int nView, ICEAnchor *pCar); + ~ICEMover() override; + + void EyeCubicInit(ICE::Cubic3D *pEye, ICE::Matrix4 *pMatrix, ICE::Vector3 *pVelocity); + void LookCubicInit(ICE::Cubic3D *pLook, ICE::Matrix4 *pMatrix, ICE::Vector3 *pVelocity); + void DutchCubicInit(ICE::Cubic1D *pDutch); + void FovCubicInit(ICE::Cubic1D *pFov); + void SetDesired(bool b_snap, bool b_refresh); + void GetEye(ICE::Vector3 *vEye, float f_param); + void GetLook(ICE::Vector3 *vLook, float f_param); + float GetDutch(float f_param); + unsigned short GetFOV(float f_param); + float GetNearClip(float f_param); + float GetNoiseAmplitude(float f_param); + float GetNoiseFrequency(float f_param); + float GetFocalDistance(float f_param); + float GetAperture(float f_param); + float GetLetterbox(float f_param); + float GetSimSpeed(float f_param); + void Update(float dT) override; + + bool IsViolatingTopology() { + return bViolatesTopology; + } + + bool IsSmooth() { + return pICEData != 0 && pICEData->bSmooth != 0; + } + + ICEAnchor *GetICEAnchor() { + return pCar; + } + + ICEAnchor *pCar; + ICE::Cubic3D *pEye; + ICE::Cubic3D *pLook; + ICE::Cubic1D *pDutch; + ICE::Cubic1D *pFov; + ICE::Cubic1D *pNearClip; + ICE::Cubic1D *pNoiseAmplitude; + ICE::Cubic1D *pNoiseFrequency; + ICE::Cubic1D *pFocalDistance; + ICE::Cubic1D *pAperture; + ICE::Cubic1D *pLetterbox; + ICE::Cubic1D *pSimSpeed; + ICE::Cubic3D *pAccelOffset; + float fParameter0; + float fParameter1; + int nSpaceEye; + int nSpaceLook; + ICEData *pICEData; + bool bViolatesTopology; + ICE::Matrix4 mHybridToWorld; + ICE::Vector3 vSmoothCarPos; + ICE::Vector3 vSmoothCarFwd; +}; #endif diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEOverlays.cpp b/src/Speed/Indep/Src/Camera/ICE/ICEOverlays.cpp index e69de29bb..4d9056566 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEOverlays.cpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEOverlays.cpp @@ -0,0 +1,76 @@ +// ICE Overlay system + +#include + +class Event { + public: + void *operator new(std::size_t size); + void operator delete(void *ptr, std::size_t size); + virtual ~Event() {} + virtual const char *GetEventName(); + Event(std::size_t size) : fEventSize(size) {} + std::size_t fEventSize; +}; + +struct cFEng { + static cFEng *Get(); + static cFEng *mInstance; + void PushNoControlPackage(const char *name, int priority); + void PopNoControlPackage(const char *name); + bool IsPackagePushed(const char *name); +}; + +struct ELoadingScreenOff : public Event { + ELoadingScreenOff(); +}; + +struct OverlayEntry { + unsigned char id; + char pad[3]; + const char *name; +}; + +static const int NUM_OVERLAYS = 5; +extern OverlayEntry gIceOverlays[NUM_OVERLAYS]; +static unsigned char gOverlay; + +static int GetOverlayIndex(unsigned char overlay) { + int index = 0; + for (int i = 0; i < NUM_OVERLAYS; i++) { + if (overlay == gIceOverlays[i].id) { + index = i; + break; + } + } + return index; +} + +namespace ICE { + +char *GetOverlayName(unsigned char overlay) { + int index = GetOverlayIndex(overlay); + return const_cast(gIceOverlays[index].name); +} + +void ShowOverlay(unsigned char overlay) { + if (overlay != 0) { + if ((gOverlay & 0x7f) == 0 || (gOverlay & 0x80) != 0) { + new ELoadingScreenOff(); + gOverlay = overlay; + cFEng::mInstance->PushNoControlPackage(GetOverlayName(overlay), 0x67); + } + } +} + +void HideOverlay() { + if ((gOverlay & 0x7f) != 0) { + if (cFEng::mInstance->IsPackagePushed(GetOverlayName(gOverlay & 0x7f))) { + cFEng::mInstance->PopNoControlPackage(GetOverlayName(gOverlay & 0x7f)); + gOverlay = 0; + } else { + gOverlay = gOverlay | 0x80; + } + } +} + +} // namespace ICE \ No newline at end of file diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEReplay.cpp b/src/Speed/Indep/Src/Camera/ICE/ICEReplay.cpp index e69de29bb..81b4c165a 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEReplay.cpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEReplay.cpp @@ -0,0 +1,404 @@ +#include "ICEReplay.hpp" +#include "ICEMover.hpp" + +float PredictAverageAir(float fSeconds, float *pHighest, float *pLongest, bool b_grounded_abort); +float GetRecentCurvature(); + +static float sPredictedAir; +static float sRecentCurvature; + +static float ReplayNosScore(ICEAnchor *p_car) { + float ret = 0.0f; + + if (p_car->IsNosEngaged()) { + ret = p_car->GetNosPercentageLeft() * 0.25f + 0.5f; + } + + return ret; +} + +static bool ReplayNosMirror(ICEAnchor *) { + return false; +} + +static float ReplayJumpScore(ICEAnchor *p_car) { + float ret = p_car->GetUpVector()->z < 0.0f ? 1.0f : 0.0f; + + if (p_car->IsTouchingGround() != 0) { + float highest = 0.0f; + float longest = 0.0f; + + PredictAverageAir(3.0f, &highest, &longest, true); + if (1.0f < highest && 1.5f < longest) { + ret = 1.0f; + } + } + + return ret * 5.0f; +} + +static bool ReplayJumpMirror(ICEAnchor *) { + return false; +} + +static float ReplaySpeedScore(ICEAnchor *p_car) { + float ret = 0.0f; + + if (10.0f < p_car->GetVelocityMagnitude()) { + ret = bClamp((p_car->GetVelocityMagnitude() / p_car->GetTopSpeed()) * 0.7f + 0.3f, 0.0f, 1.0f); + } + + return ret; +} + +static bool ReplaySpeedMirror(ICEAnchor *) { + return false; +} + +static float ReplayCornerScore(ICEAnchor *p_car) { + float ret = 0.0f; + + if (5.0f < p_car->GetVelocityMagnitude()) { + ret = bClamp(bAbs(GetRecentCurvature()) * 20.0f, 0.0f, 1.0f); + } + + return ret; +} + +static bool ReplayCornerMirror(ICEAnchor *) { + return GetRecentCurvature() < 0.0f; +} + +static float ReplayBurnoutScore(ICEAnchor *p_car) { + float ret = 0.0f; + float forward_speed = p_car->GetVelocityMagnitude(); + + if (forward_speed != bClamp(forward_speed, -10.0f, 20.0f)) { + return ret; + } + + float forward_slip = p_car->GetForwardSlip(); + ret = bClamp(forward_slip * 0.1f - 1.0f, 0.0f, 1.0f); + + if (forward_speed != bClamp(forward_speed, -1.0f, 1.0f)) { + return ret; + } + if (p_car->GetRPM() <= 3000.0f) { + return ret; + } + + return ret + 10.9999895f; +} + +static bool ReplayBurnoutMirror(ICEAnchor *) { + return false; +} + +static float ReplayPowerSlideScore(ICEAnchor *p_car) { + float ret = 0.0f; + + if (p_car->GetVelocityMagnitude() <= 4.0f) { + return ret; + } + + { + float yaw = (bAbs(p_car->GetSlipAngle()) * 360.0f - 5.0f) * 0.025f; + + if (yaw <= ret) { + return ret; + } + + return bClamp(yaw + 0.1f, 0.0f, 1.0f); + } +} + +static bool ReplayPowerSlideMirror(ICEAnchor *p_car) { + return p_car->GetSlipAngle() < 0.0f; +} + +static ICEReplayCategory ReplayCategories[] = { + {"NOS", 0x40DFADC5, "ReplayNos", 0xF6F4912B, ReplayNosScore, ReplayNosMirror}, + {"Jump", 0xC9CFEAFB, "ReplayJump", 0x17911633, ReplayJumpScore, ReplayJumpMirror}, + {"Speed", 0x41862FE6, "ReplaySpeed", 0xC0E543F7, ReplaySpeedScore, ReplaySpeedMirror}, + {"Corner", 0xA38C8885, "ReplayCorner", 0xE12BD1A8, ReplayCornerScore, ReplayCornerMirror}, + {"Burnout", 0x6512F314, "ReplayBurnout", 0x87EA4B2F, ReplayBurnoutScore, ReplayBurnoutMirror}, + {"PowerSlide", 0x9FD09E0E, "ReplaySlide", 0xD7BFCC6F, ReplayPowerSlideScore, ReplayPowerSlideMirror}, +}; + +namespace ICE { + +int GetReplayCategoryNumElements() { + return 6; +} + +unsigned int GetReplayCategoryHash(int category) { + return ReplayCategories[category].nCategoryHash; +} + +ICEReplayCategory *GetReplayCategory(unsigned int category_hash) { + for (int i = 0; i < 6; i++) { + if (category_hash == ReplayCategories[i].nCategoryHash) { + return &ReplayCategories[i]; + } + } + + return 0; +} + +} // namespace ICE + +namespace ICEReplay { + +ICETrack *RecentlyUsedTracks[3]; +int nRecentlyUsedIndex; + +bool WasRecentlyUsed(ICETrack *track) { + for (int i = 0; i < 3; i++) { + if (track == RecentlyUsedTracks[i]) { + return true; + } + } + + return false; +} + +void ClearRecentlyUsed() { + nRecentlyUsedIndex = 0; + + for (int i = 0; i < 3; i++) { + RecentlyUsedTracks[i] = 0; + } +} + +} // namespace ICEReplay + +float PredictAverageAir(float fSeconds, float *pHighest, float *pLongest, bool b_grounded_abort) { + return sPredictedAir; +} + +float GetRecentCurvature() { + return sRecentCurvature; +} + +Camera *GetCurrentCamera(); +unsigned short ConvertLensLengthToFovAngle(float f_lens_mm); + +extern bool bMirrorICEData; +bool Tweak_ForceICEReplay; + +namespace ICEReplay { + +bool CameraCutIsGood(ICEData *camera, float param, ICEAnchor *p_car) { + Camera *old_cam = GetCurrentCamera(); + bool is_it_good = false; + char sStat[5] = "...."; + + if (!old_cam) { + return is_it_good; + } + + bMatrix4 mCarToWorld; + bMatrix4 mWorldToCar; + + bCopy(&mCarToWorld, reinterpret_cast(p_car->GetGeometryOrientation()), + reinterpret_cast(p_car->GetGeometryPosition())); + eInvertTransformationMatrix(&mWorldToCar, &mCarToWorld); + + bVector3 v_pos; + bVector3 v_tar; + bVector3 *p_pos = old_cam->GetPosition(); + bVector3 *p_tar = old_cam->GetTarget(); + eMulVector(&v_pos, &mWorldToCar, p_pos); + eMulVector(&v_tar, &mWorldToCar, p_tar); + + bVector3 v_old_dir; + bSub(&v_old_dir, &v_tar, &v_pos); + bNormalize(&v_old_dir, &v_old_dir); + + bVector3 *p_old_vel = old_cam->GetVelocityPosition(); + bVector3 v_old_vel; + eMulVector(&v_old_vel, &mWorldToCar, p_old_vel); + bNormalize(&v_old_vel, &v_old_vel); + + bVector3 v_eye; + bVector3 v_look; + camera->GetEye(0, reinterpret_cast(&v_eye)); + camera->GetLook(0, reinterpret_cast(&v_look)); + + bVector3 v_new_dir; + bSub(&v_new_dir, &v_look, &v_eye); + bNormalize(&v_new_dir, &v_new_dir); + + bVector3 v_eye1; + camera->GetEye(1, reinterpret_cast(&v_eye1)); + bVector3 v_new_vel; + bSub(&v_new_vel, &v_eye1, &v_eye); + bNormalize(&v_new_vel, &v_new_vel); + + bVector3 v_world_eye; + eMulVector(&v_world_eye, &mCarToWorld, &v_eye); + + { + float dir_dot = bDot(&v_old_dir, &v_new_dir); + float vel_dot = bDot(&v_old_vel, &v_new_vel); + const float fMinAngleTreshold = -0.5f; + const float fMaxAngleTreshold = 0.866f; + const float fPercentTreshold = 0.5f; + + if (v_old_dir.y * v_new_dir.y < 0.0f) { + return is_it_good; + } + + if (dir_dot <= fMinAngleTreshold) { + return is_it_good; + } + + if (fMaxAngleTreshold <= dir_dot) { + unsigned short half_angle_old = old_cam->GetFov() >> 1; + float old_tan = bTan(half_angle_old); + float old_len = bLength(&v_pos); + + unsigned short fov_angle = ConvertLensLengthToFovAngle(camera->fLens[0]); + unsigned short half_angle_new = fov_angle >> 1; + float new_tan = bTan(half_angle_new); + float new_len = bLength(&v_eye); + + float old_dist = 1.0f / (old_len * old_tan); + float new_dist = 1.0f / (new_len * new_tan); + float max_len = bMax(old_dist, new_dist); + float percent = bAbs(new_dist - old_dist) / max_len; + + if (percent <= fPercentTreshold) { + return is_it_good; + } + } else if (dir_dot <= 0.0f) { + is_it_good = true; + return is_it_good; + } + + if (vel_dot <= 0.0f) { + return is_it_good; + } + } + + is_it_good = true; + return is_it_good; +} + +ICETrack *ChooseGoodCamera(ICEAnchor *p_car, ICEGroup *p_replay_cameras, int num_replay_cameras) { + struct ReplayCandidate { + ICETrack *pTrack; + float fScore; + bool bMirror; + }; + + ICETrack *good_track = 0; + + if (p_car != 0) { + float total_score = 0.0f; + float *scores = new float[num_replay_cameras]; + + for (int group_number = 0; group_number < num_replay_cameras; group_number++) { + ICEGroup *group = &p_replay_cameras[group_number]; + ICEReplayCategory *category = ICE::GetReplayCategory(group->GetHandle()); + float score = category->GetScore(p_car); + + if (Tweak_ForceICEReplay != 0) { + score += 0.1f; + } + + if (score >= 0.1f) { + total_score += score; + scores[group_number] = score; + } else { + scores[group_number] = 0.0f; + } + } + + if (0.0f < total_score) { + int num_tracks = 0; + + for (int group_number = 0; group_number < num_replay_cameras; group_number++) { + if (0.0f < scores[group_number]) { + ICEGroup *group = &p_replay_cameras[group_number]; + num_tracks += group->GetNumTracks(); + } + } + + if (num_tracks > 0) { + int num_candidates = 0; + ReplayCandidate *candidates = new ReplayCandidate[num_tracks]; + + for (int group_number = 0; group_number < num_replay_cameras; group_number++) { + float score = scores[group_number]; + + if (0.0f < score) { + int old_num_candidates = num_candidates; + ICEGroup *group = &p_replay_cameras[group_number]; + + for (int track_number = 0; track_number < group->GetNumTracks(); track_number++) { + ICETrack *track = group->GetTrack(track_number); + ICEData *camera_data = track->GetKey(0); + + if (camera_data != 0 && camera_data->nType != 0) { + char sTrack[16]; + ICEReplayCategory *category = ICE::GetReplayCategory(group->GetHandle()); + bMirrorICEData = category->GetMirror(p_car); + + if (10.0f <= score || + (CameraCutIsGood(camera_data, 0.0f, p_car) && + !WasRecentlyUsed(track))) { + candidates[num_candidates].pTrack = track; + candidates[num_candidates].fScore = score; + candidates[num_candidates].bMirror = bMirrorICEData; + num_candidates++; + } + + bMirrorICEData = 0; + } + } + + int candidates_from_this_group = num_candidates - old_num_candidates; + if (candidates_from_this_group > 0) { + float recip = 1.0f / static_cast(candidates_from_this_group); + for (int i = old_num_candidates; i < num_candidates; i++) { + candidates[i].fScore *= recip; + } + } + } + } + + if (num_candidates > 0) { + float random_score = bRandom(total_score); + + for (int i = 0; i < num_candidates; i++) { + float score = candidates[i].fScore; + if (random_score < score) { + good_track = candidates[i].pTrack; + bMirrorICEData = candidates[i].bMirror; + break; + } + random_score -= score; + } + + if (good_track == 0) { + bMirrorICEData = candidates[0].bMirror; + good_track = candidates[0].pTrack; + } + } + + if (candidates != 0) { + delete[] candidates; + } + } + } + + if (scores != 0) { + delete[] scores; + } + } + + return good_track; +} + +} // namespace ICEReplay diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEReplay.hpp b/src/Speed/Indep/Src/Camera/ICE/ICEReplay.hpp index 3054e235d..de144ad51 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEReplay.hpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEReplay.hpp @@ -5,6 +5,50 @@ #pragma once #endif +struct ICEAnchor; +struct ICETrack; + +struct ICEReplayCategory { + float GetScore(ICEAnchor *car) { + if (ScoreFunction != 0) { + return ScoreFunction(car); + } + return 0.0f; + } + + bool GetMirror(ICEAnchor *car) { + if (MirrorFunction != 0) { + return MirrorFunction(car); + } + return false; + } + + const char *pCategoryName; + unsigned int nCategoryHash; + const char *pSceneName; + unsigned int nSceneHash; + float (*ScoreFunction)(ICEAnchor *); + bool (*MirrorFunction)(ICEAnchor *); +}; + +namespace ICE { + +int GetReplayCategoryNumElements(); +unsigned int GetReplayCategoryHash(int category); +ICEReplayCategory *GetReplayCategory(unsigned int category_hash); + +} // namespace ICE + +namespace ICEReplay { + +extern ICETrack *RecentlyUsedTracks[3]; +extern int nRecentlyUsedIndex; + +bool WasRecentlyUsed(ICETrack *track); +void ClearRecentlyUsed(); + +} // namespace ICEReplay + #endif diff --git a/src/Speed/Indep/Src/Camera/IDebugWatchCar.h b/src/Speed/Indep/Src/Camera/IDebugWatchCar.h new file mode 100644 index 000000000..71a777b40 --- /dev/null +++ b/src/Speed/Indep/Src/Camera/IDebugWatchCar.h @@ -0,0 +1,26 @@ +#ifndef CAMERA_IDEBUGWATCHCAR_H +#define CAMERA_IDEBUGWATCHCAR_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Libs/Support/Utility/UListable.h" + +class ISimable; + +class IDebugWatchCar : public UTL::COM::IUnknown, public UTL::Collections::Listable { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + IDebugWatchCar(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual ~IDebugWatchCar() {} + + virtual ISimable *GetSimable() = 0; +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Movers/Cubic.cpp b/src/Speed/Indep/Src/Camera/Movers/Cubic.cpp index e69de29bb..8818409cd 100644 --- a/src/Speed/Indep/Src/Camera/Movers/Cubic.cpp +++ b/src/Speed/Indep/Src/Camera/Movers/Cubic.cpp @@ -0,0 +1,645 @@ +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Misc/Table.hpp" +#include "Speed/Indep/Src/World/RaceParameters.hpp" +#include "Speed/Indep/Src/World/Rain.hpp" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" + +extern tTable aCubicPovTables[]; +extern float CameraImpcatCurveH[]; +extern float CameraImpcatCurveV[]; +extern float CameraGearChangingCurve[]; +extern float PovHandheldNoiseScale[]; +extern float PovHandheldChopperScale[]; +extern float PovVelocityNoiseScale[]; +extern float PovTerrainNoiseScale[]; +extern float _SmokeShowEyeOffset; +extern float _HydraulicsEyeOffset; +extern unsigned short _SmokeShowLookAngle; +extern unsigned short _HydraulicsLookAngle; +extern unsigned short _NOSFovWidening; +extern RaceParameters theRaceParameters; +extern Timer RealTimer; + +bool OutsidePovType(int pov_type); +void FlushAccumulationBuffer(); +void ApplyCameraShake(int nViewID, bMatrix4 *pMatrix); +int AmIinATunnel(eView *view, int CheckOverPass); + +bVector3* bClamp(bVector3* dest, const bVector3* pMin, const bVector3* pMax) { + float x = bClamp(dest->x, pMin->x, pMax->x); + float y = bClamp(dest->y, pMin->y, pMax->y); + float z = bClamp(dest->z, pMin->z, pMax->z); + dest->x = x; + dest->y = y; + dest->z = z; + return dest; +} + +void tTable::Blend(CubicPovData *dest, CubicPovData *a, CubicPovData *b, float blend_a) { + float blend_b = 1.0f - blend_a; + float f1 = blend_b * b->fEyeDuration; + float f2 = blend_b * b->fLookDuration; + float f3 = blend_b * b->fFovDuration; + float f4 = blend_b * b->fUpDuration; + + dest->fEyeDuration = blend_a * a->fEyeDuration + f1; + dest->fLookDuration = blend_a * a->fLookDuration + f2; + dest->fFovDuration = blend_a * a->fFovDuration + f3; + dest->fUpDuration = blend_a * a->fUpDuration + f4; + + bVector3 v0, v1, v2, v3, v4, v5, v6, v7, v8, v9; + + bScale(&v0, a->GetUpAccel(), blend_a); + bScale(&v1, a->GetUpAccelMin(), blend_a); + bScale(&v2, a->GetUpAccelMax(), blend_a); + bScale(&v3, a->GetEyeAccel(), blend_a); + bScale(&v4, a->GetEyeAccelMin(), blend_a); + bScale(&v5, a->GetEyeAccelMax(), blend_a); + bScale(&v6, a->GetLookAccel(), blend_a); + bScale(&v7, a->GetLookAccelMin(), blend_a); + bScale(&v8, a->GetLookAccelMax(), blend_a); + bScale(&v9, a->GetForwardDuration(), blend_a); + + bScaleAdd(dest->GetUpAccel(), &v0, b->GetUpAccel(), blend_b); + bScaleAdd(dest->GetUpAccelMin(), &v1, b->GetUpAccelMin(), blend_b); + bScaleAdd(dest->GetUpAccelMax(), &v2, b->GetUpAccelMax(), blend_b); + bScaleAdd(dest->GetEyeAccel(), &v3, b->GetEyeAccel(), blend_b); + bScaleAdd(dest->GetEyeAccelMin(), &v4, b->GetEyeAccelMin(), blend_b); + bScaleAdd(dest->GetEyeAccelMax(), &v5, b->GetEyeAccelMax(), blend_b); + bScaleAdd(dest->GetLookAccel(), &v6, b->GetLookAccel(), blend_b); + bScaleAdd(dest->GetLookAccelMin(), &v7, b->GetLookAccelMin(), blend_b); + bScaleAdd(dest->GetLookAccelMax(), &v8, b->GetLookAccelMax(), blend_b); + bScaleAdd(dest->GetForwardDuration(), &v9, b->GetForwardDuration(), blend_b); +} + +CubicCameraMover::CubicCameraMover(int nView, CameraAnchor *p_car, int pov_type, bool smooth, bool disable_lag, bool look_back, bool perfect_focus) + : CameraMover(nView, CM_DRIVE_CUBIC) { + bSnapNext = 0; + bAccelLag = !disable_lag; + bLookBack = look_back; + bPerfectFocus = perfect_focus; + pCar = p_car; + nPovType = pov_type; + nPovTypeUsed = pov_type; + tLastGrounded = WorldTimer - Timer(8000); + tLastUnderVehicle = WorldTimer - Timer(0x1900); + tLastGearChange = WorldTimer - Timer(6000); + fIgnoreSetSnapNextTimer = 0.0f; + bFirstTime = 1; + + POV *pov = pCar->GetPov(nPovType); + + CubicPovData pov_data; + aCubicPovTables[nPovType].GetValue(&pov_data, 0.0f); + + pFov = new (__FILE__, __LINE__) tCubic1D(1, pov_data.fFovDuration); + pEye = new (__FILE__, __LINE__) tCubic3D(1, pov_data.fEyeDuration); + pLook = new (__FILE__, __LINE__) tCubic3D(1, pov_data.fLookDuration); + pForward = new (__FILE__, __LINE__) tCubic3D(1, pov_data.GetForwardDuration()); + pUp = new (__FILE__, __LINE__) tCubic3D(1, pov_data.GetForwardDuration()); + + pAvgAccel = new tAverage(5); + + bMatrix4 mCarToWorld; + SetDesired(&mCarToWorld, pov, &pov_data, true); + + bMatrix4 mWorldToCar; + eInvertTransformationMatrix(&mWorldToCar, &mCarToWorld); + + if (bLookBack) { + mWorldToCar.v0.x = -mWorldToCar.v0.x; + mWorldToCar.v0.y = -mWorldToCar.v0.y; + mWorldToCar.v1.x = -mWorldToCar.v1.x; + mWorldToCar.v1.y = -mWorldToCar.v1.y; + mWorldToCar.v2.x = -mWorldToCar.v2.x; + mWorldToCar.v2.y = -mWorldToCar.v2.y; + mWorldToCar.v3.x = -mWorldToCar.v3.x; + mWorldToCar.v3.y = -mWorldToCar.v3.y; + } + + SetEyeLook(pEye, pLook, pFov, &mWorldToCar, pCar->GetVelocity()); + + bVector3 eye_current; + bVector3 eye_desired; + bVector3 look_current; + bVector3 look_desired; + + pEye->GetVal(&eye_current); + pLook->GetVal(&look_current); + pEye->GetValDesired(&eye_desired); + pLook->GetValDesired(&look_desired); + + bVector3 eye_movement(eye_desired - eye_current); + bVector3 direction_current(look_current - eye_current); + bVector3 direction_desired(look_desired - eye_desired); + + bNormalize(&direction_current, &direction_current); + bNormalize(&direction_desired, &direction_desired); + + vSavedEye.z = 0.0f; + vSavedEye.y = 0.0f; + vSavedEye.x = 0.0f; + vCameraImpcat.y = 0.0f; + vCameraImpcat.x = 0.0f; + vCameraImpcatTimer.y = 0.0f; + vCameraImpcatTimer.x = 0.0f; + + if (smooth && bLength(&eye_movement) <= 50.0f && bDot(&direction_current, &direction_desired) >= -0.9f) + goto set_timer; + + pUp->Snap(); + pFov->Snap(); + pEye->Snap(); + pLook->Snap(); + goto done; + +set_timer: + fIgnoreSetSnapNextTimer = 1.0f; + +done:; +} + +bool CubicCameraMover::IsUnderVehicle() { + const IVehicle::List &vehicles = IVehicle::GetList(VEHICLE_ALL); + + for (IVehicle::List::const_iterator iter = vehicles.begin(); iter != vehicles.end(); ++iter) { + IVehicle *ivehicle = *iter; + + if (!ivehicle->IsActive()) { + continue; + } + if (ivehicle->IsDestroyed()) { + continue; + } + + ISimable *isimable = ivehicle->GetSimable(); + if (!isimable) { + continue; + } + + if (isimable->GetWorldID() == pCar->GetWorldID()) { + continue; + } + + IRigidBody *irb = isimable->GetRigidBody(); + if (!irb) { + continue; + } + + if (!irb->IsSimple()) { + continue; + } + + UMath::Vector4 vehiclePos; + const UMath::Vector3 &pos = irb->GetPosition(); + vehiclePos.x = pos.x; + vehiclePos.y = pos.y; + vehiclePos.z = pos.z; + vehiclePos.w = 0.0f; + + UMath::Vector4 testPos; + testPos.x = vehiclePos.x; + testPos.y = vehiclePos.y; + testPos.z = vehiclePos.z; + testPos.w = 0.0f; + + UMath::Vector3 dim; + irb->GetDimension(dim); + + UMath::Matrix4 mat; + irb->GetMatrix4(mat); + + UMath::Matrix4 world2local; + UMath::Transpose(mat, world2local); + + UMath::Vector3 forward; + irb->GetForwardVector(forward); + + bVector3 carFwd; + eSwizzleWorldVector(reinterpret_cast(forward), carFwd); + + bVector3 *camFwd = GetCamera()->GetDirection(); + float dot = bDot(camFwd, &carFwd); + + if (bAbs(dot) >= 0.5f) { + continue; + } + + { + bVector3 predictedPos; + bVector3 *geomPos = pCar->GetGeometryPosition(); + bVector3 *vel = const_cast(pCar->GetVelocity()); + bVector3 scaled = *vel * 0.5f; + bVector3 sum = *geomPos + scaled; + eUnSwizzleWorldVector(sum, reinterpret_cast(testPos)); + testPos.w = 0.0f; + + UMath::Vector4 test2vehicle; + UMath::Sub(testPos, vehiclePos, test2vehicle); + float dist = UMath::Length(test2vehicle); + + if (dist >= irb->GetRadius()) { + continue; + } + + UMath::Vector4 test2vehicleLocal; + UMath::Rotate(test2vehicle, world2local, test2vehicleLocal); + + float absX = test2vehicleLocal.x; + if (absX < 0.0f) { + absX = -absX; + } + if (absX < dim.x) { + float absZ = test2vehicleLocal.z; + if (absZ < 0.0f) { + absZ = -absZ; + } + if (absZ < dim.z) { + return true; + } + } + } + } + return false; +} + +void CubicCameraMover::SetDesired(bMatrix4 *pCarToWorld, POV *pov, CubicPovData *pov_data, bool bSnapForward) { + bool bOutside = OutsidePovType(pov->Type); + + if (pCar->IsTouchingGround()) { + tLastGrounded = WorldTimer; + } + + if (IsUnderVehicle()) { + tLastUnderVehicle = WorldTimer; + } + + SetForward(pov, bSnapForward); + MakeSpace(pCarToWorld); + + bMatrix4 mWorldToCar; + eInvertTransformationMatrix(&mWorldToCar, pCarToWorld); + + bVector4 vAccel(0.0f, 0.0f, 0.0f, 0.0f); + if (bAccelLag) { + bVector3 *avgAccelVal = pAvgAccel->GetValue(); + vAccel.x = avgAccelVal->x; + vAccel.y = avgAccelVal->y; + vAccel.z = avgAccelVal->z; + vAccel.w = 0.0f; + bMulMatrix(reinterpret_cast(&vAccel), &mWorldToCar, &vAccel); + } + + bVector3 vEyeOffset; + unsigned short aAngle; + + bool b_snap_always = false; + if (theRaceParameters.IsBurnout()) { + b_snap_always = true; + } + + if (b_snap_always) { + vEyeOffset = bVector3(_SmokeShowEyeOffset, 0.0f, 0.0f); + aAngle = _SmokeShowLookAngle; + } else { + if (!HighliteMode()) { + vEyeOffset.x = pov->Lag; + vEyeOffset.y = pov->LatOffset; + vEyeOffset.z = pov->Height; + CameraSpeedHug(&vEyeOffset); + aAngle = pov->Angle; + } else { + vEyeOffset = bVector3(_HydraulicsEyeOffset, 0.0f, 0.0f); + aAngle = _HydraulicsLookAngle; + } + } + + float fTan = bSin(aAngle) / bCos(aAngle); + + bVector3 vLookOffset; + if (!bOutside) { + vLookOffset.x = vEyeOffset.x + 1.0f; + vLookOffset.z = vEyeOffset.z - fTan; + } else { + vLookOffset.z = fTan * vEyeOffset.x + vEyeOffset.z; + vLookOffset.x = 0.0f; + } + + bVector3 vEye; + vEye.x = pov_data->vEyeAccel.x * vAccel.x; + vEye.y = pov_data->vEyeAccel.y * vAccel.y; + vEye.z = pov_data->vEyeAccel.z * vAccel.z; + vLookOffset.y = 0.0f; + bClamp(&vEye, pov_data->GetEyeAccelMin(), pov_data->GetEyeAccelMax()); + + if (HighliteMode()) { + vEyeOffset *= 0.25f; + } + + vEye += vEyeOffset; + pEye->SetValDesired(&vEye); + + bVector3 vLook; + vLook.x = pov_data->vLookAccel.x * vAccel.x; + vLook.y = pov_data->vLookAccel.y * vAccel.y; + vLook.z = pov_data->vLookAccel.z * vAccel.z; + bClamp(&vLook, pov_data->GetLookAccelMin(), pov_data->GetLookAccelMax()); + vLook += vLookOffset; + pLook->SetValDesired(&vLook); + + bVector3 v_up(0.0f, 0.0f, 1.0f); + if (pov->AllowTilting) { + bVector3 *avgVal = pAvgAccel->GetValue(); + bVector3 scaled = bScale(*pov_data->GetUpAccel(), *avgVal); + v_up = v_up + scaled; + bClamp(&v_up, pov_data->GetUpAccelMin(), pov_data->GetUpAccelMax()); + } + + if (0.1f < pCar->GetVelMag()) { + bVector3 *dutch = DutchAroundCar(pCar->GetGeometryPosition(), const_cast(pCar->GetVelocity())); + v_up += *dutch; + } + bNormalize(&v_up, &v_up); + pUp->SetValDesired(&v_up); + + unsigned short aFov = pov->Fov; + if (bOutside && pCar->IsNosEngaged() && 0.0f < pCar->GetVelMag()) { + aFov = aFov + _NOSFovWidening; + } + pFov->SetValDesired(static_cast(aFov)); + + if (bSnapNext || !bOutside) { + pUp->Snap(); + pFov->Snap(); + pEye->Snap(); + pLook->Snap(); + } + + if (bSnapNext) { + FlushAccumulationBuffer(); + } +} + +void CubicCameraMover::Update(float dT) { + if (TheGameFlowManager.IsPaused() && !bFirstTime) { + return; + } + + bFirstTime = 0; + + if (0.0f < fIgnoreSetSnapNextTimer) { + fIgnoreSetSnapNextTimer -= dT; + } + + nPovTypeUsed = nPovType; + POV *pov = pCar->GetPov(nPovType); + + float collision_damper = pCar->GetCollisionDamping(); + float drift_damper = pCar->GetDrift(); + float stiffness = bClamp(pCar->GetVelMag() * 0.005f, 0.0f, 1.0f); + + CubicPovData pov_data; + aCubicPovTables[nPovTypeUsed].GetValue(&pov_data, stiffness); + + if (theRaceParameters.IsDriftRace() || theRaceParameters.IsBurnout()) { + pov_data.vEyeAccel = bVector3(0.0f, 0.0f, 0.0f); + } + + bVector3 vAccel(0.0f, 0.0f, 0.0f); + if (fIgnoreSetSnapNextTimer <= 0.0f) { + CameraAccelCurve(&vAccel); + } + + tAverage *avg = pAvgAccel; + if (avg->GetNumSamples() < avg->nSlots) { + avg->nSamples = avg->nSamples + 1; + } + + int slot = static_cast(avg->nCurrentSlot); + bVector3 *slotData = &avg->pData[slot]; + avg->Total -= *slotData; + avg->Total += vAccel; + *slotData = vAccel; + + bVector3 avgVal = avg->Total * (1.0f / static_cast(static_cast(avg->nSamples))); + avg->Average = avgVal; + + avg->nCurrentSlot = avg->nCurrentSlot + 1; + if (avg->nSlots <= avg->nCurrentSlot) { + avg->nCurrentSlot = 0; + } + + float fAccelH = bMax(bAbs(vAccel.x), bAbs(vAccel.y)); + float fAccelV; + + if (24.0f <= fAccelH && 0.0f < pCar->GetCollisionDamping() && + vCameraImpcat.x < pCar->GetCollisionDamping()) { + vCameraImpcat.x = pCar->GetCollisionDamping(); + vCameraImpcatTimer.x = 1.0f; + } + + fAccelV = vAccel.z; + if (fAccelV >= 24.0f) { + float impact = bClamp((fAccelV - 24.0f) * (1.0f / 6.0f), 0.0f, 1.0f); + if (impact > vCameraImpcat.y) { + vCameraImpcat.y = impact; + vCameraImpcatTimer.y = 1.0f; + } + } + + float eye_duration = pov_data.fEyeDuration; + pUp->SetDuration(pov_data.fUpDuration); + pFov->SetDuration(pov_data.fFovDuration); + pLook->SetDuration(pov_data.fLookDuration); + bVector3 *foward_duration = pov_data.GetForwardDuration(); + pForward->SetDuration(foward_duration->x + collision_damper + drift_damper, + foward_duration->y + collision_damper + drift_damper, + foward_duration->z + collision_damper + drift_damper); + + if (HighliteMode()) { + eye_duration = pov_data.fEyeDuration * (10.0f / 13.0f); + } + + pEye->SetDuration(eye_duration, eye_duration, eye_duration); + + pUp->Update(dT, 0.0f, 0.0f); + pFov->Update(dT, 0.0f, 0.0f); + pEye->Update(dT, 0.0f, 0.0f); + pLook->Update(dT, 0.0f, 0.0f); + pForward->Update(dT, 0.0f, 0.0f); + + bool bOutside = OutsidePovType(pov->Type); + + float target_dist = bDistBetween(GetCamera()->GetPosition(), pCar->GetGeometryPosition()); + GetCamera()->SetTargetDistance(target_dist); + + float dof; + if (!bPerfectFocus) { + GetCamera()->SetFocalDistance(40.0f); + dof = 100.0f; + } else { + GetCamera()->SetFocalDistance(0.0f); + } + GetCamera()->SetDepthOfField(dof); + + bMatrix4 mCarToWorld; + SetDesired(&mCarToWorld, pov, &pov_data, bSnapNext); + bSnapNext = 0; + + float fSign; + if (bLookBack) { + fSign = -1.0f; + } else { + fSign = 1.0f; + } + bVector3 vUp; + vUp.x = (*pUp).x.Val; + vUp.y = (*pUp).y.Val; + vUp.z = 1.0f; + if (!bOutside) { + vUp.x = pCar->GetUpVector()->x; + vUp.y = pCar->GetUpVector()->y; + vUp.z = pCar->GetUpVector()->z; + } + + bVector3 vEye(fSign * (*pEye).x.Val, fSign * (*pEye).y.Val, (*pEye).z.Val); + bVector3 vLook(fSign * (*pLook).x.Val, fSign * (*pLook).y.Val, (*pLook).z.Val); + + { + if (vCameraImpcat.x > 0.0f) { + if (0.0f < vCameraImpcatTimer.x) { + tTable envelope(CameraImpcatCurveH, 5, 0.0f, 1.0f); + float s; + envelope.GetValue(&s, vCameraImpcatTimer.x); + s = s * vCameraImpcat.x * 0.001f; + vEye.x = vEye.x * (1.0f - s); + vLook.x = vLook.x * (s + 1.0f); + vCameraImpcatTimer.x -= dT; + if (vCameraImpcatTimer.x <= 0.0f) { + goto clearH; + } + } else { + clearH: + vCameraImpcat.x = 0.0f; + } + } else { + if (vCameraImpcatTimer.x > 0.0f) { + vCameraImpcatTimer.x -= dT; + if (vCameraImpcatTimer.x > 0.0f) { + goto doneH; + } + } + vCameraImpcat.x = 0.0f; + doneH:; + } + } + + { + if (vCameraImpcat.y > 0.0f) { + if (0.0f < vCameraImpcatTimer.y) { + tTable envelope(CameraImpcatCurveV, 5, 0.0f, 1.0f); + float s; + envelope.GetValue(&s, vCameraImpcatTimer.y); + vEye.z = vEye.z * (1.0f - s * vCameraImpcat.y * 0.001f); + vCameraImpcatTimer.y -= dT; + if (vCameraImpcatTimer.y <= 0.0f) { + goto clearV; + } + } else { + clearV: + vCameraImpcat.y = 0.0f; + } + } else { + if (vCameraImpcatTimer.y > 0.0f) { + vCameraImpcatTimer.y -= dT; + if (vCameraImpcatTimer.y > 0.0f) { + goto doneV; + } + } + vCameraImpcat.y = 0.0f; + doneV:; + } + } + + if (!bOutside) { + float fSeconds = (Timer(WorldTimer - tLastGearChange)).GetSeconds(); + if (fSeconds >= 1.0f) { + if (0.4f < pCar->GetVelMag() && pCar->IsGearChanging()) { + tLastGearChange = WorldTimer; + } + } else { + tTable gear_changing_table(CameraGearChangingCurve, 9, 0.0f, 1.0f); + float eyeMove; + gear_changing_table.GetValue(&eyeMove, fSeconds); + float speed_attenuation = bClamp(pCar->GetVelMag() * 0.01f, 0.0f, 1.0f); + float lag_scale = bAbs(vEye.x); + vEye.x = vEye.x + eyeMove * lag_scale * 0.5f * (1.0f - speed_attenuation); + } + } + + bool isUnder = false; + if (!bOutside) { + float fSeconds = (Timer(WorldTimer - tLastUnderVehicle)).GetSeconds() * 0.001f; + float fCurve = 2.0f - bClamp(fSeconds, 0.0f, 2.0f); + if (fCurve > 1.0f) { + fCurve = 3.0f - fCurve; + } + vEye.z = fCurve * 0.05f + vEye.z; + if (0.0f < fCurve) { + isUnder = true; + } + } + + if (GRaceStatus::Exists() && pCar->IsDragRace()) { + float seconds = GRaceStatus::Get().GetRaceTimeElapsed(); + if (seconds < 0.0f) { + float t = -seconds; + bVector3 velCopy = *pCar->GetVelocity(); + vEye = (*pCar->GetUpVector()) * t + velCopy * (t * 0.5f) + vEye; + } + } + + bVector3 vDiff = vEye - vLook; + vDiff *= fSign; + vLook = vLook + vDiff; + vEye = vLook; + + unsigned short a_fov = static_cast(static_cast(pFov->Val)); + float f_tan_fov = bTan(a_fov >> 1); + unsigned short a_new_fov = bATan(1.0f, f_tan_fov) << 1; + + GetCamera()->SetFieldOfView(a_new_fov); + + bMatrix4 mWorldToCamera; + eCreateLookAtMatrix(&mWorldToCamera, vEye, vLook, vUp); + + ApplyCameraShake(ViewID, &mWorldToCamera); + HandheldNoise(&mWorldToCamera, PovHandheldNoiseScale[nPovTypeUsed], true); + + if (!AmIinATunnel(&eViews[ViewID], 1)) { + float speed_attenuation = bClamp(pCar->GetVelMag() * 0.01f, 0.0f, 1.0f); + float f_chopper_scale = PovHandheldChopperScale[nPovTypeUsed] * (1.0f - speed_attenuation * 0.5f); + ChopperNoise(&mWorldToCamera, f_chopper_scale, true); + } + + TerrainVelocityNoise(&mWorldToCamera, pCar, PovVelocityNoiseScale[nPovTypeUsed], + PovTerrainNoiseScale[nPovTypeUsed]); + + if (bOutside) { + MinGapTopology(&mWorldToCamera, pCar->GetGeometryPosition()); + if (!isUnder) { + MinGapCars(&mWorldToCamera, pCar->GetGeometryPosition(), const_cast(pCar->GetVelocity())); + } + } + + GetCamera()->SetCameraMatrix(mWorldToCamera, dT); +} + +template class tAverage; \ No newline at end of file diff --git a/src/Speed/Indep/Src/Camera/Movers/DebugWorld.cpp b/src/Speed/Indep/Src/Camera/Movers/DebugWorld.cpp index e69de29bb..94b299724 100644 --- a/src/Speed/Indep/Src/Camera/Movers/DebugWorld.cpp +++ b/src/Speed/Indep/Src/Camera/Movers/DebugWorld.cpp @@ -0,0 +1,263 @@ +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Input/ActionQueue.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Misc/Table.hpp" +#include "Speed/Indep/Src/World/Track.hpp" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/bWare/Inc/bFunk.hpp" + +extern float DebugCameraNearPlane; +extern bVector3 JumpToPosition; +extern bool gDebugCameraSetEye; +extern bool gDebugCameraSetLook; +extern bVector3 gDebugCameraTweakableEye; +extern bVector3 gDebugCameraTweakableLook; +extern tGraph gDebugCameraInputGraph; + +unsigned short bFixATan(int x); +unsigned short bFixATan(int x, int y); + +bVector3 DebugWorldCameraMover::Eye; +bVector3 DebugWorldCameraMover::Look; +bVector3 DebugWorldCameraMover::Up; +float DebugWorldCameraMover::TurboSpeed = 3.06f; +float DebugWorldCameraMover::SuperTurboSpeed = 7.16f; +int DebugWorldCameraMover::TurboOn; +int DebugWorldCameraMover::SuperTurboOn; + +DebugWorldCameraMover::DebugWorldCameraMover(int view_id, const bVector3 *start_position, + const bVector3 *start_direction, JoystickPort jp) + : CameraMover(view_id, CM_DEBUG_WORLD) { + JoyPort = jp; + TurnVInc = 0; + TurnHInc = 0; + StrafeInc = 0.0f; + HeightInc = 0.0f; + ForwardInc = 0.0f; + ForwardAnalogInc = 0.0f; + SuperTurboOn = 0; + Eye = *start_position; + TurboOn = 0; + Look = *start_direction; + PrevNearZ = GetCamera()->GetNearZ(); + GetCamera()->SetNearZ(DebugCameraNearPlane); + mActionQ = new ActionQueue(JoyPort, 0x98c7a2f5, "DebugWorld", false); +} + +DebugWorldCameraMover::~DebugWorldCameraMover() { + if (mActionQ) { + GetCamera()->SetNearZ(PrevNearZ); + if (mActionQ) { + delete mActionQ; + } + mActionQ = nullptr; + } +} + +void DebugWorldCameraMover::JoyHandler() { + if (!mActionQ) { + return; + } + + while (!mActionQ->IsEmpty()) { + ActionRef aRef = mActionQ->GetAction(); + float data = gDebugCameraInputGraph.GetValue(aRef.Data()); + + switch (aRef.ID()) { + case 0x35: + HeightInc = data * 10.0f; + break; + case 0x36: + HeightInc = data * -10.0f; + break; + case 0x37: + case 0x3b: + data = -data; + case 0x38: + case 0x3c: + StrafeInc = data * -20.0f; + break; + case 0x3a: + data = -data; + case 0x39: + ForwardAnalogInc = data * 20.0f; + break; + case 0x3e: + data = -data; + case 0x3d: + ForwardInc = -data * 20.0f; + break; + case 0x40: + case 0x44: + data = -data; + case 0x3f: + case 0x43: + TurnVInc = static_cast(static_cast(-data * 20000.0f)); + break; + case 0x41: + case 0x45: + data = -data; + case 0x42: + case 0x46: + TurnHInc = static_cast(static_cast(data * -20000.0f)); + break; + case 0x47: + if (data != 0.0f) { + TurboOn = 1; + } else { + TurboOn = 0; + } + break; + case 0x48: + if (data != 0.0f) { + SuperTurboOn = 1; + } else { + SuperTurboOn = 0; + } + break; + case 0x49: { + bVector3 simpos; + eUnSwizzleWorldVector(*GetPosition(), simpos); + { + IPlayer *player = IPlayer::First(PLAYER_LOCAL); + if (player) { + ISimable *sim = player->GetSimable(); + if (sim) { + WCollisionMgr(0, 3).GetWorldHeightAtPoint( + reinterpret_cast(simpos), simpos.y, nullptr); + simpos.y += 3.0f; + sim->GetRigidBody()->SetPosition(reinterpret_cast(simpos)); + } + } + } + break; + } + default: + break; + } + + mActionQ->PopAction(); + } +} + +void DebugWorldCameraMover::Update(float dT) { + if (JumpToPosition.y != 0.0f) { + TopologyCoordinate topology_coordinate; + topology_coordinate.SetInterestBBox(&Eye, 0.0f, &Look); + topology_coordinate.IsLoaded(); + JumpToPosition.z += 100.0f; + bVector3 dir = Eye - Look; + bNormalize(&dir, &dir, 1.0f); + bVector3 newEye = JumpToPosition + dir; + Eye = newEye; + Look = JumpToPosition; + bFill(&JumpToPosition, 0.0f, 0.0f, 0.0f); + bRefreshTweaker(); + } + + if (gDebugCameraSetEye) { + eSwizzleWorldVector(gDebugCameraTweakableEye, Eye); + gDebugCameraSetEye = false; + } + + if (gDebugCameraSetLook) { + eSwizzleWorldVector(gDebugCameraTweakableLook, Look); + gDebugCameraSetLook = false; + } + + JoyHandler(); + + bVector3 eyelook = Look - Eye; + unsigned short hAngle = bFixATan(static_cast(eyelook.x * 65536.0f), + static_cast(eyelook.y * 65536.0f)); + + if (TurnHInc != 0 || TurnVInc != 0 || HeightInc != 0.0f) { + hAngle = (hAngle + static_cast(static_cast(TurnHInc) * dT)) & 0xffff; + bVector2 *horiz = reinterpret_cast(&eyelook); + float xylen = bLength(horiz); + + unsigned short pitch = bFixATan(static_cast(xylen * 65536.0f), + static_cast(eyelook.z * 65536.0f)); + pitch = (pitch + static_cast(static_cast(TurnVInc) * dT)) & 0xffff; + + if (static_cast(pitch - 0x3ff7) < 0x4009u) { + pitch = 0x3ff6; + } + if ((static_cast(pitch - 0x8000) & 0xffffu) < 0x400au) { + pitch = 0xc00a; + } + + float fi; + if (SuperTurboOn != 0) { + fi = HeightInc * SuperTurboSpeed; + } else if (TurboOn != 0) { + fi = HeightInc * TurboSpeed; + } else { + fi = HeightInc; + } + + float hi = fi * dT; + Eye = Eye + bVector3(0.0f, 0.0f, hi); + + Look.x = bCos(pitch) * (bCos(hAngle) * 100.0f) + Eye.x; + Look.y = bCos(pitch) * (bSin(hAngle) * 100.0f) + Eye.y; + Look.z = bSin(pitch) * 100.0f + Eye.z; + } + + if (ForwardInc != 0.0f) { + float fi; + if (SuperTurboOn) { + fi = ForwardInc * SuperTurboSpeed * dT; + } else if (TurboOn) { + fi = ForwardInc * TurboSpeed * dT; + } else { + fi = ForwardInc * dT; + } + bVector3 forward = *GetDirection() * fi; + Eye += forward; + Look += forward; + } else if (ForwardAnalogInc != 0.0f) { + float fi; + if (SuperTurboOn != 0) { + fi = ForwardAnalogInc * SuperTurboSpeed; + } else if (TurboOn != 0) { + fi = ForwardAnalogInc * TurboSpeed; + } else { + fi = ForwardAnalogInc; + } + fi *= dT; + bVector3 forward = *GetDirection() * fi; + Eye += forward; + Look += forward; + } + + if (StrafeInc != 0.0f) { + float si; + if (SuperTurboOn != 0) { + si = StrafeInc * SuperTurboSpeed; + } else if (TurboOn != 0) { + si = StrafeInc * TurboSpeed; + } else { + si = StrafeInc; + } + si *= dT; + unsigned short sAngle = (hAngle + 0x4000) & 0xffff; + float cval = bCos(sAngle) * si; + float sval = bSin(sAngle) * si; + bVector3 rl(cval, sval, 0.0f); + Eye += rl; + Look += rl; + } + + bMatrix4 m; + bVector3 up; + unsigned short bank = 0; + ComputeBankedUpVector(&up, &Eye, &Look, bank); + eCreateLookAtMatrix(&m, Eye, Look, up); + + unsigned short fov = 0x32dc; + GetCamera()->SetFieldOfView(fov); + GetCamera()->SetCameraMatrix(m, dT); +} diff --git a/src/Speed/Indep/Src/Camera/Movers/SelectCar.cpp b/src/Speed/Indep/Src/Camera/Movers/SelectCar.cpp index e69de29bb..81ff016a6 100644 --- a/src/Speed/Indep/Src/Camera/Movers/SelectCar.cpp +++ b/src/Speed/Indep/Src/Camera/Movers/SelectCar.cpp @@ -0,0 +1,251 @@ +#include "Speed/Indep/Src/Camera/Movers/SelectCar.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" + +#include + +extern int CarGuysCamera; + +static float kSelectCarFrameRate = 60.0f; +static float kSelectCarUpperOrbitV = 110.0f; +static float kSelectCarLowerOrbitV = 94.5f; +static float kSelectCarRadiusSpeedScale = 0.125f; +static float kSelectCarUpperRadius = 6.65f; +static float kSelectCarLowerRadius = 4.65f; +static const float kSelectCarWrapAngle = 360.0f; + +SelectCarCameraMover::~SelectCarCameraMover() {} + +static float kSelectCarDefaultRollV = 0.0f; +static float kSelectCarDefaultFOVV = 45.0f; +static float kSelectCarDefaultLookAtZV = 0.75f; +static float kSelectCarDefaultAnimTimeV = 0.555f; + +void SelectCarCameraMover::SetVRotateSpeed(float f) { + if (ControlMode != 0) { + if (ControlMode != 1) { + StartAnimCameraData.RollAngle = CurrentCameraData.RollAngle; + StartAnimCameraData.FOV = CurrentCameraData.FOV; + StartAnimCameraData.LookAt = CurrentCameraData.LookAt; + GoalAnimCameraData.RollAngle = kSelectCarDefaultRollV; + GoalAnimCameraData.FOV = kSelectCarDefaultFOVV; + bFill(&GoalAnimCameraData.LookAt, kSelectCarDefaultRollV, kSelectCarDefaultRollV, kSelectCarDefaultLookAtZV); + GoalAnimCameraData.RollAngle = FindBestAngleGoal(CurrentCameraData.RollAngle, kSelectCarDefaultRollV); + CurrentAnimationTime = kSelectCarDefaultRollV; + TotalAnimationTime = kSelectCarDefaultAnimTimeV; + } + OrbitVSpeed = f; + ControlMode = 1; + } +} + +static float kSelectCarDefaultRollH = 0.0f; +static float kSelectCarDefaultFOVH = 45.0f; +static float kSelectCarDefaultLookAtZH = 0.75f; +static float kSelectCarDefaultAnimTimeH = 0.555f; + +void SelectCarCameraMover::SetHRotateSpeed(float f) { + if (ControlMode != 0) { + if (ControlMode != 1) { + StartAnimCameraData.RollAngle = CurrentCameraData.RollAngle; + StartAnimCameraData.FOV = CurrentCameraData.FOV; + StartAnimCameraData.LookAt = CurrentCameraData.LookAt; + GoalAnimCameraData.RollAngle = kSelectCarDefaultRollH; + GoalAnimCameraData.FOV = kSelectCarDefaultFOVH; + bFill(&GoalAnimCameraData.LookAt, kSelectCarDefaultRollH, kSelectCarDefaultRollH, kSelectCarDefaultLookAtZH); + GoalAnimCameraData.RollAngle = FindBestAngleGoal(CurrentCameraData.RollAngle, kSelectCarDefaultRollH); + CurrentAnimationTime = kSelectCarDefaultRollH; + TotalAnimationTime = kSelectCarDefaultAnimTimeH; + } + OrbitHSpeed = f; + ControlMode = 1; + } +} + +static float kSelectCarDefaultRollZ = 0.0f; +static float kSelectCarDefaultFOVZ = 45.0f; +static float kSelectCarDefaultLookAtZZ = 0.75f; +static float kSelectCarDefaultAnimTimeZ = 0.555f; + +void SelectCarCameraMover::SetZoomSpeed(float f) { + if (ControlMode != 0) { + if (ControlMode != 1) { + StartAnimCameraData.RollAngle = CurrentCameraData.RollAngle; + StartAnimCameraData.FOV = CurrentCameraData.FOV; + StartAnimCameraData.LookAt = CurrentCameraData.LookAt; + GoalAnimCameraData.RollAngle = kSelectCarDefaultRollZ; + GoalAnimCameraData.FOV = kSelectCarDefaultFOVZ; + bFill(&GoalAnimCameraData.LookAt, kSelectCarDefaultRollZ, kSelectCarDefaultRollZ, kSelectCarDefaultLookAtZZ); + GoalAnimCameraData.RollAngle = FindBestAngleGoal(CurrentCameraData.RollAngle, kSelectCarDefaultRollZ); + CurrentAnimationTime = kSelectCarDefaultRollZ; + TotalAnimationTime = kSelectCarDefaultAnimTimeZ; + } + RadiusSpeed = f; + ControlMode = 1; + } +} + +SelectCarCameraMover::SelectCarCameraMover(int view_id) : CameraMover(view_id, CM_SELECT_CAR) { + CurrentCameraData.OrbitVAngle = 0.0f; + CurrentCameraData.OrbitHAngle = 0.0f; + CurrentCameraData.Radius = 0.0f; + CurrentCameraData.RollAngle = 0.0f; + CurrentCameraData.FOV = 0.0f; + bFill(&CurrentCameraData.LookAt, 0.0f, 0.0f, 0.0f); + StartAnimCameraData = CurrentCameraData; + GoalAnimCameraData = CurrentCameraData; + RadiusSpeed = 0.0f; + OrbitVSpeed = 0.0f; + OrbitHSpeed = 0.0f; + CurrentAnimationTime = 0.0f; + ControlMode = 1; + LookingAtParts = 0; + TotalAnimationTime = 1.0f; + Periods = 2; + Damping = 5.0f; +} + +void SelectCarCameraMover::Update(float dT) { + int screen_print_x; + int screen_print_y; + SelectCarCameraData *camera_data = &CurrentCameraData; + + if (ControlMode != 2) { + float animiation_amount = 1.0f; + CurrentAnimationTime = CurrentAnimationTime + dT; + if (0.0f < TotalAnimationTime && CurrentAnimationTime < TotalAnimationTime) { + animiation_amount = CurrentAnimationTime / TotalAnimationTime; + } + float aa2 = animiation_amount * animiation_amount; + float anim = 1.0f - static_cast(expf(-Damping * aa2)) * + static_cast(cosf((static_cast(Periods) + 0.5f) * aa2 * 3.14159265f)); + if (ControlMode == 1) { + float the_frame_rate = dT * kSelectCarFrameRate; + + CurrentCameraData.OrbitHAngle = OrbitHSpeed * the_frame_rate + CurrentCameraData.OrbitHAngle; + float possibleNewOrbitV = OrbitVSpeed * the_frame_rate + CurrentCameraData.OrbitVAngle; + if (CarGuysCamera == 0) { + if (possibleNewOrbitV > kSelectCarUpperOrbitV) { + possibleNewOrbitV = kSelectCarUpperOrbitV; + } else if (possibleNewOrbitV < kSelectCarLowerOrbitV) { + possibleNewOrbitV = kSelectCarLowerOrbitV; + } + } + CurrentCameraData.OrbitVAngle = possibleNewOrbitV; + float possibleNewRadius = RadiusSpeed * (the_frame_rate * kSelectCarRadiusSpeedScale) + CurrentCameraData.Radius; + if (CarGuysCamera == 0) { + if (possibleNewRadius > kSelectCarUpperRadius) { + possibleNewRadius = kSelectCarUpperRadius; + } else if (possibleNewRadius < kSelectCarLowerRadius) { + possibleNewRadius = kSelectCarLowerRadius; + } + } + float complement = 1.0f - anim; + CurrentCameraData.Radius = possibleNewRadius; + CurrentCameraData.RollAngle = complement * StartAnimCameraData.RollAngle + anim * GoalAnimCameraData.RollAngle; + CurrentCameraData.FOV = complement * StartAnimCameraData.FOV + anim * GoalAnimCameraData.FOV; + bVector3 lookat_change = GoalAnimCameraData.LookAt - StartAnimCameraData.LookAt; + lookat_change *= anim; + CurrentCameraData.LookAt = StartAnimCameraData.LookAt + lookat_change; + if (CurrentCameraData.OrbitHAngle > kSelectCarWrapAngle) { + CurrentCameraData.OrbitHAngle = CurrentCameraData.OrbitHAngle - kSelectCarWrapAngle; + } + if (CurrentCameraData.OrbitHAngle < 0.0f) { + CurrentCameraData.OrbitHAngle = CurrentCameraData.OrbitHAngle + kSelectCarWrapAngle; + } + } else if (ControlMode == 0) { + float complement = 1.0f - anim; + CurrentCameraData.OrbitVAngle = complement * StartAnimCameraData.OrbitVAngle + anim * GoalAnimCameraData.OrbitVAngle; + CurrentCameraData.OrbitHAngle = complement * StartAnimCameraData.OrbitHAngle + anim * GoalAnimCameraData.OrbitHAngle; + CurrentCameraData.Radius = complement * StartAnimCameraData.Radius + anim * GoalAnimCameraData.Radius; + CurrentCameraData.RollAngle = complement * StartAnimCameraData.RollAngle + anim * GoalAnimCameraData.RollAngle; + CurrentCameraData.FOV = complement * StartAnimCameraData.FOV + anim * GoalAnimCameraData.FOV; + bVector3 lookat_change = GoalAnimCameraData.LookAt - StartAnimCameraData.LookAt; + lookat_change *= anim; + CurrentCameraData.LookAt = StartAnimCameraData.LookAt + lookat_change; + if (CurrentCameraData.OrbitHAngle > kSelectCarWrapAngle) { + CurrentCameraData.OrbitHAngle = CurrentCameraData.OrbitHAngle - kSelectCarWrapAngle; + } + if (CurrentCameraData.OrbitHAngle < 0.0f) { + CurrentCameraData.OrbitHAngle = CurrentCameraData.OrbitHAngle + kSelectCarWrapAngle; + } + if (CurrentCameraData.RollAngle > kSelectCarWrapAngle) { + CurrentCameraData.RollAngle = CurrentCameraData.RollAngle - kSelectCarWrapAngle; + } + if (CurrentCameraData.RollAngle < 0.0f) { + CurrentCameraData.RollAngle = CurrentCameraData.RollAngle + kSelectCarWrapAngle; + } + if (ControlMode == 0 && 1.0f <= animiation_amount) { + ControlMode = 2; + } + } + } + bMatrix4 camera_matrix; + CreateCameraMatrix(&camera_matrix, camera_data); + screen_print_x = bDegToAng(camera_data->FOV); + GetCamera()->SetFieldOfView(static_cast(screen_print_x)); + GetCamera()->SetTargetDistance(camera_data->Radius); + GetCamera()->SetCameraMatrix(camera_matrix, dT); +} + +void SelectCarCameraMover::SetCurrentOrientation(bVector3 &orbit, float roll, float fov, bVector3 &lookAt) { + CurrentCameraData.OrbitVAngle = orbit.x; + CurrentCameraData.OrbitHAngle = orbit.y; + CurrentCameraData.Radius = orbit.z; + CurrentCameraData.RollAngle = roll; + CurrentCameraData.FOV = fov; + CurrentCameraData.LookAt = lookAt; +} + +void SelectCarCameraMover::SetDesiredOrientation(bVector3 &orbit, float roll, float fov, bVector3 &lookAt, float animSpeed, float damping, + int periods) { + StartAnimCameraData = CurrentCameraData; + ControlMode = 0; + GoalAnimCameraData = CurrentCameraData; + GoalAnimCameraData.OrbitVAngle = orbit.x; + GoalAnimCameraData.OrbitHAngle = orbit.y; + GoalAnimCameraData.Radius = orbit.z; + GoalAnimCameraData.RollAngle = roll; + GoalAnimCameraData.FOV = fov; + GoalAnimCameraData.LookAt = lookAt; + Damping = damping; + Periods = periods; + GoalAnimCameraData.OrbitVAngle = FindBestAngleGoal(StartAnimCameraData.OrbitVAngle, GoalAnimCameraData.OrbitVAngle); + GoalAnimCameraData.OrbitHAngle = FindBestAngleGoal(StartAnimCameraData.OrbitHAngle, GoalAnimCameraData.OrbitHAngle); + GoalAnimCameraData.RollAngle = FindBestAngleGoal(StartAnimCameraData.RollAngle, GoalAnimCameraData.RollAngle); + CurrentAnimationTime = 0.0f; + TotalAnimationTime = animSpeed; +} + +float SelectCarCameraMover::FindBestAngleGoal(float start, float goal) { + float normal_h_diff = bAbs(start - goal); + float over_h_diff = bAbs(start - (goal + kSelectCarWrapAngle)); + float under_h_diff = bAbs(start - (goal - kSelectCarWrapAngle)); + float return_goal; + + if (over_h_diff < normal_h_diff && over_h_diff < under_h_diff) { + return_goal = goal + kSelectCarWrapAngle; + } else if (under_h_diff < normal_h_diff && under_h_diff < over_h_diff) { + return_goal = goal - kSelectCarWrapAngle; + } else { + return_goal = goal; + } + return return_goal; +} + +void SelectCarCameraMover::CreateCameraMatrix(bMatrix4 *camera_matrix, SelectCarCameraData *camera_data) { + bVector3 transpost(0.0f, 0.0f, camera_data->Radius); + bMatrix4 camera_to_world; + bVector3 eye; + bVector3 up; + + bIdentity(camera_matrix); + eRotateZ(camera_matrix, camera_matrix, bDegToAng(camera_data->OrbitHAngle)); + eRotateX(camera_matrix, camera_matrix, bDegToAng(camera_data->OrbitVAngle)); + eTranslate(camera_matrix, camera_matrix, &transpost); + eInvertTransformationMatrix(&camera_to_world, camera_matrix); + eye.x = camera_to_world.v3.x; + eye.y = camera_to_world.v3.y; + eye.z = camera_to_world.v3.z; + ComputeBankedUpVector(&up, &eye, &camera_data->LookAt, bDegToAng(camera_data->RollAngle)); + eCreateLookAtMatrix(camera_matrix, eye, camera_data->LookAt, up); +} diff --git a/src/Speed/Indep/Src/Camera/Movers/SelectCar.hpp b/src/Speed/Indep/Src/Camera/Movers/SelectCar.hpp new file mode 100644 index 000000000..4a9a4d0fd --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Movers/SelectCar.hpp @@ -0,0 +1,59 @@ +#ifndef CAMERA_MOVERS_SELECTCAR_H +#define CAMERA_MOVERS_SELECTCAR_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraMover.hpp" + +struct SelectCarCameraData { + float OrbitVAngle; // offset 0x0, size 0x4 + float OrbitHAngle; // offset 0x4, size 0x4 + float Radius; // offset 0x8, size 0x4 + float RollAngle; // offset 0xC, size 0x4 + float FOV; // offset 0x10, size 0x4 + bVector3 LookAt; // offset 0x14, size 0x10 + + SelectCarCameraData() {} +}; + +// total size: 0x114 +class SelectCarCameraMover : public CameraMover { + public: + SelectCarCameraMover(int view_id); + ~SelectCarCameraMover() override; + + void Update(float dT) override; + + static void CreateCameraMatrix(bMatrix4 *camera_matrix, SelectCarCameraData *camera_data); + void SetVRotateSpeed(float f); + void SetHRotateSpeed(float f); + void SetZoomSpeed(float f); + void SetCurrentOrientation(bVector3 &orbit, float roll, float fov, bVector3 &lookAt); + void SetDesiredOrientation(bVector3 &orbit, float roll, float fov, bVector3 &lookAt, float animSpeed, float damping, int periods); + float FindBestAngleGoal(float start, float goal); + + float GetTotalAnimationTime() { return TotalAnimationTime; } + float GetCurrentAnimationTime() { return CurrentAnimationTime; } + float GetVAngle() { return CurrentCameraData.OrbitVAngle; } + float GetHAngle() { return CurrentCameraData.OrbitHAngle; } + float GetZoom() { return CurrentCameraData.Radius; } + + private: + int ControlMode; // offset 0x80, size 0x4 + int DramaticMode; // offset 0x84, size 0x4 + int LookingAtParts; // offset 0x88, size 0x4 + SelectCarCameraData CurrentCameraData; // offset 0x8C, size 0x24 + SelectCarCameraData StartAnimCameraData; // offset 0xB0, size 0x24 + SelectCarCameraData GoalAnimCameraData; // offset 0xD4, size 0x24 + float RadiusSpeed; // offset 0xF8, size 0x4 + float OrbitVSpeed; // offset 0xFC, size 0x4 + float OrbitHSpeed; // offset 0x100, size 0x4 + float Damping; // offset 0x104, size 0x4 + int Periods; // offset 0x108, size 0x4 + float CurrentAnimationTime; // offset 0x10C, size 0x4 + float TotalAnimationTime; // offset 0x110, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Movers/Showcase.cpp b/src/Speed/Indep/Src/Camera/Movers/Showcase.cpp index e69de29bb..653bcb439 100644 --- a/src/Speed/Indep/Src/Camera/Movers/Showcase.cpp +++ b/src/Speed/Indep/Src/Camera/Movers/Showcase.cpp @@ -0,0 +1,109 @@ +#include "Speed/Indep/Src/Camera/Movers/Showcase.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" + +static float gPhoto_LatAng = 90.0f; +static float gPhoto_UpAng = 2.0f; +static float gPhoto_Dist = 5.0f; +static float gPhoto_DOF = 0.0f; +static bVector3 gPhoto_CarPosBias; + +ShowcaseCameraMover::~ShowcaseCameraMover() {} + +void ShowcaseCameraMover::ResetState() { + GetCamera()->ClearVelocity(); +} + +ShowcaseCameraMover::ShowcaseCameraMover(int nView, CameraAnchor *p_car, bool flipSide) + : CameraMover(nView, CM_SHOWCASE) { + pCar = p_car; + GetCamera()->ClearVelocity(); + SetFromTweakables(); + if (flipSide) { + mLatAng = -mLatAng; + } + BuildPhotoCameraMatrix(); +} + +void ShowcaseCameraMover::SetFromTweakables() { + mUpAng = gPhoto_UpAng; + mDist = gPhoto_Dist; + mLatAng = gPhoto_LatAng; + mCarPosBias = gPhoto_CarPosBias; + mFd = gPhoto_DOF; + mFOV = gPhoto_LatAng; + mDOF = gPhoto_DOF; +} + +void ShowcaseCameraMover::SetShowcaseCameraParams(float lat_ang, float up_ang, float dist, float carpos_bias_x, float carpos_bias_y, + float carpos_bias_z, float fov, float fd, float dof) { + mLatAng = lat_ang; + mUpAng = up_ang; + mDist = dist; + mCarPosBias.x = carpos_bias_x; + mCarPosBias.y = carpos_bias_y; + mCarPosBias.z = carpos_bias_z; + mFOV = fov; + mFd = fd; + mDOF = dof; +} + +void ShowcaseCameraMover::BuildPhotoCameraMatrix() { + bVector3 *car_position = pCar->GetGeometryPosition(); + bVector3 car_adj_position; + bVector3 car_bias; + bMatrix4 car_to_world; + + bCopy(&car_to_world, pCar->GetGeometryOrientation()); + + bVector3 ground_normal(0.0f, 0.0f, 1.0f); + bCopy(&car_to_world.v2, &ground_normal, 0.0f); + car_to_world.v0.z = 0.0f; + car_to_world.v1.z = 0.0f; + + bCross(reinterpret_cast(&car_to_world.v0), reinterpret_cast(&car_to_world.v1), reinterpret_cast(&car_to_world.v2)); + bCross(reinterpret_cast(&car_to_world.v1), reinterpret_cast(&car_to_world.v2), reinterpret_cast(&car_to_world.v0)); + + bNormalize(&car_to_world.v0, &car_to_world.v0); + bNormalize(&car_to_world.v1, &car_to_world.v1); + + bMulMatrix(&car_bias, &car_to_world, &mCarPosBias); + + bAdd(&car_adj_position, car_position, &car_bias); + + unsigned short lat_view_angle = bDegToAng(mLatAng); + float lat_sin, lat_cos; + bSinCos(&lat_sin, &lat_cos, lat_view_angle); + + if (mUpAng < 3.0f) { + mUpAng = 3.0f; + } + + unsigned short up_view_angle = bDegToAng(mUpAng); + float up_sin, up_cos; + bSinCos(&up_sin, &up_cos, up_view_angle); + + float view_dist = mDist; + bVector3 camera_position; + bVector3 yada(lat_cos * view_dist, lat_sin * view_dist, up_sin * view_dist); + bMulMatrix(&camera_position, &car_to_world, &yada); + + bAdd(&camera_position, &camera_position, &car_adj_position); + + bMatrix4 matrix; + bVector3 ref_up_vec(0.0f, 0.0f, 1.0f); + eCreateLookAtMatrix(&matrix, camera_position, car_adj_position, ref_up_vec); + + MinGapTopology(&matrix, pCar->GetGeometryPosition()); + + mCameraMatrix = matrix; +} + +void ShowcaseCameraMover::Update(float dT) { + BuildPhotoCameraMatrix(); + unsigned short fov = bDegToAng(mFOV); + GetCamera()->SetFieldOfView(fov); + GetCamera()->SetDepthOfField(mDOF); + GetCamera()->SetFocalDistance(mFd); + GetCamera()->SetCameraMatrix(mCameraMatrix, dT); +} diff --git a/src/Speed/Indep/Src/Camera/Movers/Showcase.hpp b/src/Speed/Indep/Src/Camera/Movers/Showcase.hpp new file mode 100644 index 000000000..b1e10ced9 --- /dev/null +++ b/src/Speed/Indep/Src/Camera/Movers/Showcase.hpp @@ -0,0 +1,34 @@ +#ifndef CAMERA_MOVERS_SHOWCASE_H +#define CAMERA_MOVERS_SHOWCASE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Camera/CameraMover.hpp" + +// total size: 0xEC +class ShowcaseCameraMover : public CameraMover { + public: + ShowcaseCameraMover(int nView, CameraAnchor *p_car, bool flipSide); + ~ShowcaseCameraMover() override; + + void SetFromTweakables(); + void SetShowcaseCameraParams(float lat_ang, float up_ang, float dist, float carpos_bias_x, float carpos_bias_y, float carpos_bias_z, float fov, float fd, float dof); + void BuildPhotoCameraMatrix(); + void ResetState() override; + void Update(float dT) override; + + private: + CameraAnchor *pCar; // offset 0x80, size 0x4 + float mLatAng; // offset 0x84, size 0x4 + float mUpAng; // offset 0x88, size 0x4 + float mDist; // offset 0x8C, size 0x4 + bVector3 mCarPosBias; // offset 0x90, size 0x10 + float mFOV; // offset 0xA0, size 0x4 + float mFd; // offset 0xA4, size 0x4 + float mDOF; // offset 0xA8, size 0x4 + bMatrix4 mCameraMatrix; // offset 0xAC, size 0x40 +}; + +#endif diff --git a/src/Speed/Indep/Src/Camera/Movers/TrackCar.cpp b/src/Speed/Indep/Src/Camera/Movers/TrackCar.cpp index e69de29bb..724779622 100644 --- a/src/Speed/Indep/Src/Camera/Movers/TrackCar.cpp +++ b/src/Speed/Indep/Src/Camera/Movers/TrackCar.cpp @@ -0,0 +1,279 @@ +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" +#include "Speed/Indep/Src/World/WRoadNetwork.h" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" + +extern bool gCamCloseToRoadBlock; + +static const float Tweak_JumpCamPositionSpeedMult[4] = {0.5f, 1.3f, 1.0f, 1.5f}; +static bVector3 PreviousEye; + +static float TrackCarEyeOffsetZ[4] = {0.1f, 3.0f, 3.5f, 2.0f}; + +static bool IsAnyCopNear(CameraAnchor *pCar) { + IVehicle *const *iter = IVehicle::GetList(VEHICLE_AICOPS).begin(); + + while (iter != IVehicle::GetList(VEHICLE_AICOPS).end()) { + IVehicle *p_car = *iter; + if (p_car && p_car->IsActive()) { + UVector3 ucoppos(p_car->GetPosition()); + bVector3 coppos; + bVector3 copdir; + eSwizzleWorldVector(reinterpret_cast(ucoppos), coppos); + bSub(&copdir, &coppos, pCar->GetGeometryPosition()); + float copdist = bLength(&copdir); + if (copdist < 30.0f) { + return true; + } + } + ++iter; + } + return false; +} + +static bool IsBeingPursued(int nView) { + IPerpetrator *iperp; + const IPlayer::List &playerList = IPlayer::GetList(PLAYER_LOCAL); + + IPlayer *const *iter = playerList.begin(); + + while (iter != playerList.end()) { + IPlayer *ip = *iter; + if (ip->GetControllerPort() == nView) { + ISimable *simable = ip->GetSimable(); + if (simable) { + simable->QueryInterface(&iperp); + if (iperp) { + if (!iperp->IsBeingPursued()) { + return false; + } + return true; + } + } + return false; + } + ++iter; + } + return false; +} + +static void FixWorldHeight(UMath::Vector3 *point, int type) { + if (!IsGameFlowInGame()) { + return; + } + + UMath::Vector3 test; + float ground_elevation = 0.0f; + + test = *point; + test.y += 4.0f; + + WCollisionMgr collision_mgr(0, 3); + + if (collision_mgr.GetWorldHeightAtPointRigorous(test, ground_elevation, nullptr)) { + if (type == 3) { + point->y = ground_elevation; + } + } + + if (ground_elevation > point->y) { + point->y = ground_elevation; + } + + point->y += TrackCarEyeOffsetZ[type]; +} + +void TrackCarCameraMover::Init() { + const float kMaxPositionDistance = 150.0f; + WRoadNav nav; + nav.SetCookieTrail(true); + nav.SetNavType(WRoadNav::kTypeDirection); + + if (gCamCloseToRoadBlock) { + gCamCloseToRoadBlock = false; + CameraType = 2; + } else { + if (IsBeingPursued(ViewID) && IsAnyCopNear(CarToFollow)) { + CameraType = 1; + } + } + + float positionSpeedMultiplier[2]; + positionSpeedMultiplier[0] = Tweak_JumpCamPositionSpeedMult[CameraType]; + positionSpeedMultiplier[1] = Tweak_JumpCamPositionSpeedMult[3]; + + if (CarToFollow->GetVelocityMagnitude() * positionSpeedMultiplier[0] > kMaxPositionDistance) { + positionSpeedMultiplier[0] = kMaxPositionDistance / CarToFollow->GetVelocityMagnitude(); + } + if (CarToFollow->GetVelocityMagnitude() * positionSpeedMultiplier[1] > kMaxPositionDistance) { + positionSpeedMultiplier[1] = kMaxPositionDistance / CarToFollow->GetVelocityMagnitude(); + } + + bVector3 vCarPosQuarterTime; + bScaleAdd(&vCarPosQuarterTime, CarToFollow->GetGeometryPosition(), CarToFollow->GetVelocity(), 0.1f); + + UMath::Vector4 carPosQuarterTime; + eUnSwizzleWorldVector(vCarPosQuarterTime, reinterpret_cast(carPosQuarterTime)); + + bVector3 vFuture; + bScaleAdd(&vFuture, CarToFollow->GetGeometryPosition(), CarToFollow->GetVelocity(), positionSpeedMultiplier[0]); + + bVector3 vLeft(vFuture); + bVector3 vRight(vFuture); + + bVector3 vFutureHigh; + bScaleAdd(&vFutureHigh, CarToFollow->GetGeometryPosition(), CarToFollow->GetVelocity(), positionSpeedMultiplier[1]); + + bVector3 vLeftHigh(vFutureHigh); + bVector3 vRightHigh(vFutureHigh); + + UMath::Vector4 carPos; + eUnSwizzleWorldVector(*CarToFollow->GetGeometryPosition(), reinterpret_cast(carPos)); + carPos.w = 1.0f; + + UMath::Vector4 carDir; + eUnSwizzleWorldVector(*CarToFollow->GetForwardVector(), reinterpret_cast(carDir)); + + nav.InitAtPoint(reinterpret_cast(carPos), + reinterpret_cast(carDir), true, 1.0f); + + float leftDist = 0.0f; + float rightDist = 0.0f; + float leftDistHigh = 0.0f; + float rightDistHigh = 0.0f; + + if (nav.IsValid()) { + nav.IncNavPosition(positionSpeedMultiplier[0] * CarToFollow->GetVelocityMagnitude(), + UMath::Vector3::kZero, 0.0f); + + float navDist = UMath::Distancexz(nav.GetPosition(), nav.GetLeftPosition()); + + float sideLerp = 0.9f; + if (navDist > 10.0f) { + sideLerp = (10.0f / navDist) * 0.9f; + } + + UMath::Vector3 leftPos; + UMath::Vector3 rightPos; + + int focal_error = 0; + + UMath::Lerp(nav.GetLeftPosition(), nav.GetPosition(), sideLerp, leftPos); + FixWorldHeight(&leftPos, CameraType); + + if (!IsSomethingInBetween(UMath::Vector4Make(leftPos, 1.0f), carPos) || + (!IsSomethingInBetween(UMath::Vector4Make(leftPos, 1.0f), carPosQuarterTime) && CameraType != 2)) { + focal_error = 1; + } + + if (focal_error) { + eSwizzleWorldVector(reinterpret_cast(leftPos), vLeft); + if (bDistBetween(&PreviousEye, &vLeft) > 3.0f) { + leftDist = bDistBetween(&vFuture, &vLeft); + } + } + + navDist = UMath::Distancexz(nav.GetPosition(), nav.GetRightPosition()); + + sideLerp = 0.9f; + if (navDist > 10.0f) { + sideLerp = (10.0f / navDist) * 0.9f; + } + + focal_error = 0; + + UMath::Lerp(nav.GetRightPosition(), nav.GetPosition(), sideLerp, rightPos); + FixWorldHeight(&rightPos, CameraType); + + if (!IsSomethingInBetween(UMath::Vector4Make(rightPos, 1.0f), carPos) || + (!IsSomethingInBetween(UMath::Vector4Make(rightPos, 1.0f), carPosQuarterTime) && CameraType != 2)) { + focal_error = 1; + } + + if (focal_error) { + eSwizzleWorldVector(reinterpret_cast(rightPos), vRight); + if (bDistBetween(&PreviousEye, &vRight) > 3.0f) { + rightDist = bDistBetween(&vFuture, &vRight); + } + } + } + + { + UMath::Vector3 pos; + bool visible = false; + + bScaleAdd(&vLeftHigh, &vFutureHigh, CarToFollow->GetLeftVector(), 4.0f); + eUnSwizzleWorldVector(vLeftHigh, reinterpret_cast(pos)); + FixWorldHeight(&pos, 3); + vLeftHigh.z = pos.y; + + if (!IsSomethingInBetween(UMath::Vector4Make(pos, 1.0f), carPos) || + (!IsSomethingInBetween(UMath::Vector4Make(pos, 1.0f), carPosQuarterTime) && CameraType != 2)) { + visible = true; + } + + if (visible && bDistBetween(&PreviousEye, &vLeftHigh) > 3.0f) { + leftDistHigh = bDistBetween(&vFutureHigh, &vLeftHigh) * 0.1f; + } + + visible = false; + + bScaleAdd(&vRightHigh, &vFutureHigh, CarToFollow->GetLeftVector(), -4.0f); + eUnSwizzleWorldVector(vRightHigh, reinterpret_cast(pos)); + FixWorldHeight(&pos, 3); + vRightHigh.z = pos.y; + + if (!IsSomethingInBetween(UMath::Vector4Make(pos, 1.0f), carPos) || + (!IsSomethingInBetween(UMath::Vector4Make(pos, 1.0f), carPosQuarterTime) && CameraType != 2)) { + visible = true; + } + + if (visible && bDistBetween(&PreviousEye, &vRightHigh) > 3.0f) { + rightDistHigh = bDistBetween(&vFutureHigh, &vRightHigh) * 0.1f; + } + } + + if (leftDist > 0.0f || rightDist > 0.0f || leftDistHigh > 0.0f || rightDistHigh > 0.0f) { + if (leftDist + leftDistHigh <= rightDist + rightDistHigh) { + if (rightDist > rightDistHigh) { + Eye = vRight; + } else { + Eye = vRightHigh; + } + } else { + if (leftDist > leftDistHigh) { + Eye = vLeft; + } else { + Eye = vLeftHigh; + } + } + } else { + Eye = *GetCamera()->GetPosition(); + Eye.z += 2.0f; + if (IsSomethingInBetween(GetCamera()->GetPosition(), CarToFollow->GetGeometryPosition())) { + Eye.z -= 2.0f; + } + } + + Eye.z += 0.5f; + PreviousEye = Eye; + + FocalDistCubic.SetDuration(0.42f); + FocalDistCubic.SetFlags(1); + + int dutch_raw = bRandom(0x13) + 0x1a; + if (bRandom(2) != 0) { + dutch_raw = -dutch_raw; + } + + float dutch = static_cast(dutch_raw); + FocalDistCubic.SetVal(dutch); + FocalDistCubic.SetValDesired(0.0f); + + if (FocusEffects == 0) { + GetCamera()->SetFocalDistance(0.0f); + GetCamera()->SetDepthOfField(0.0f); + } +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Camera/Movers/TrackCop.cpp b/src/Speed/Indep/Src/Camera/Movers/TrackCop.cpp index e69de29bb..8b3d06a87 100644 --- a/src/Speed/Indep/Src/Camera/Movers/TrackCop.cpp +++ b/src/Speed/Indep/Src/Camera/Movers/TrackCop.cpp @@ -0,0 +1,198 @@ +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Camera/Camera.hpp" +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/AI/AITarget.h" + +extern bool TrackCopCameraMover_IdleSim; +void HideEverySingleHud(); + +static float CrossXY(const bVector3 *v1, const bVector3 *v2) { + return v1->x * v2->y - v1->y * v2->x; +} + +bool TrackCopCameraMover::FindPursuitVehiclePosition(bVector3 *copPos) { + const float kMinDist = 300.0f; + float minDist = kMinDist; + for (IVehicle *const *iter = IVehicle::GetList(VEHICLE_AICOPS).begin(); + iter != IVehicle::GetList(VEHICLE_AICOPS).end(); ++iter) { + IVehicle *p_car = *iter; + if (!p_car) continue; + if (!p_car->IsActive()) continue; + if (!(p_car->GetVehicleClass() == VehicleClass::CAR)) continue; + + IVehicleAI *p_vehicleai; + if (!p_car->QueryInterface(&p_vehicleai)) continue; + + AITarget *p_target = p_vehicleai->GetTarget(); + if (!p_target) continue; + if (!p_target->IsValid()) continue; + + ISimable *p_targetsimable = p_target->GetSimable(); + if (!p_targetsimable) continue; + if (p_targetsimable->GetWorldID() != CarToFollow->GetWorldID()) continue; + + IPursuitAI *p_pursuitai; + if (!p_car->QueryInterface(&p_pursuitai)) continue; + if (!p_pursuitai->GetInPursuit()) continue; + if (p_pursuitai->GetTimeSinceTargetSeen() > 0.0f) continue; + + UMath::Vector3 upos = p_car->GetPosition(); + bVector3 bpos; + eSwizzleWorldVector(upos, bpos); + + if (IsSomethingInBetween(GetCamera()->GetPosition(), &bpos)) continue; + + float dist = bDistBetween(CarToFollow->GetGeomPos(), &bpos); + if (dist < minDist) { + *copPos = bpos; + minDist = dist; + } + } + + return minDist < kMinDist; +} + +void TrackCopCameraMover::Init() { + bVector3 copPos; + int focal_error; + + TrackCopCameraMover_IdleSim = 1; + HideEverySingleHud(); + + unsigned short fov; + + bCopy(&EyeVerts.v0, GetCamera()->GetPosition(), 0.0f); + bCopy(&EyeVerts.v1, GetCamera()->GetPosition(), 0.0f); + bCopy(&EyeVerts.v2, GetCamera()->GetPosition(), 0.0f); + bCopy(&EyeVerts.v3, GetCamera()->GetPosition(), 0.0f); + + bCopy(&LookVerts.v0, GetCamera()->GetTarget(), 0.0f); + bCopy(&LookVerts.v1, GetCamera()->GetTarget(), 0.0f); + bCopy(&LookVerts.v2, GetCamera()->GetTarget(), 0.0f); + bCopy(&LookVerts.v3, GetCamera()->GetTarget(), 0.0f); + + bool copFound = FindPursuitVehiclePosition(&copPos); + + if (copFound) { + bVector3 eyeDisplacement = *GetCamera()->GetPosition() - *GetCamera()->GetTarget(); + float eyeDistance = bLength(&eyeDisplacement); + + bVector3 copDisplacement = *GetCamera()->GetTarget() - copPos; + float savedZ = copDisplacement.z; + copDisplacement.z = 0.0f; + float copDistance = bLength(&copDisplacement); + + if (copDistance < 0.1f) { + copDistance = 0.1f; + } + + copDisplacement /= copDistance; + copDisplacement *= 5.0f; + + copDisplacement.z = bClamp(savedZ, eyeDisplacement.z, 2.25f) + 0.25f; + + copDistance = bLength(&copDisplacement); + + bVector3 copEyePos = *GetCamera()->GetTarget() + copDisplacement; + + float dot = bDot(&eyeDisplacement, &copDisplacement) / (eyeDistance * copDistance); + + if (dot < 0.0f) { + float scale = (1.0f - dot) * 0.5f; + float cross = CrossXY(&eyeDisplacement, &copDisplacement); + if (cross < 0.0f) { + scale = -scale; + } + + bVector3 eyeNormalDisplacement(-eyeDisplacement.y * scale, eyeDisplacement.x * scale, 0.0f); + bVector3 copNormalDisplacement(copDisplacement.y * scale, -copDisplacement.x * scale, 0.0f); + + bVector3 ctrl1 = *GetCamera()->GetPosition() + eyeNormalDisplacement; + bVector3 ctrl2 = copEyePos + copNormalDisplacement; + + if (!IsSomethingInBetween(&ctrl1, GetCamera()->GetTarget()) && + !IsSomethingInBetween(&ctrl2, GetCamera()->GetTarget())) { + bCopy(&EyeVerts.v1, &ctrl1, 0.0f); + bCopy(&EyeVerts.v2, &ctrl2, 0.0f); + bCopy(&EyeVerts.v3, &copEyePos, 0.0f); + } + } + + bCopy(&LookVerts.v3, &copPos, 0.0f); + } + + EyeSpline.SetControlPoints(&EyeVerts); + LookSpline.SetControlPoints(&LookVerts); + + bVector3 displacement = *reinterpret_cast(&EyeVerts.v3) - *reinterpret_cast(&LookVerts.v3); + float distance = bLength(&displacement); + if (distance < 1.0f) { + distance = 1.0f; + } + + fov = static_cast((bATan(distance, 3.0f) & 0x7FFF) << 1); + + if (fov != 0) { + fov = static_cast(bClamp(static_cast(fov), 1000, 13100)); + float fovDegDefault = bAngToDeg(GetCamera()->GetFov()); + float fovDeg = bAngToDeg(fov); + + ZoomVerts.v0.x = fovDegDefault; + ZoomVerts.v0.y = fovDegDefault; + ZoomVerts.v0.z = fovDegDefault; + ZoomVerts.v0.w = 0.0f; + ZoomVerts.v1.x = fovDegDefault; + ZoomVerts.v1.y = fovDegDefault; + ZoomVerts.v1.z = fovDegDefault; + ZoomVerts.v1.w = 0.0f; + ZoomVerts.v2.x = fovDegDefault; + ZoomVerts.v2.y = fovDegDefault; + ZoomVerts.v2.z = fovDegDefault; + ZoomVerts.v2.w = 0.0f; + ZoomVerts.v3.x = fovDeg; + ZoomVerts.v3.y = fovDeg; + ZoomVerts.v3.z = fovDeg; + ZoomVerts.v3.w = 0.0f; + } else { + float fovDeg = bAngToDeg(GetCamera()->GetFov()); + + ZoomVerts.v0.x = fovDeg; + ZoomVerts.v0.y = fovDeg; + ZoomVerts.v0.z = fovDeg; + ZoomVerts.v0.w = 0.0f; + ZoomVerts.v1.x = fovDeg; + ZoomVerts.v1.y = fovDeg; + ZoomVerts.v1.z = fovDeg; + ZoomVerts.v1.w = 0.0f; + ZoomVerts.v2.x = fovDeg; + ZoomVerts.v2.y = fovDeg; + ZoomVerts.v2.z = fovDeg; + ZoomVerts.v2.w = 0.0f; + ZoomVerts.v3.x = fovDeg; + ZoomVerts.v3.y = fovDeg; + ZoomVerts.v3.z = fovDeg; + ZoomVerts.v3.w = 0.0f; + } + + ZoomSpline.SetControlPoints(&ZoomVerts); + + FocalDistCubic.SetDuration(0.42f); + FocalDistCubic.SetFlags(1); + + focal_error = bRandom(19) + 26; + if (bRandom(2) != 0) { + focal_error = -focal_error; + } + + FocalDistCubic.SetVal(static_cast(focal_error)); + FocalDistCubic.SetValDesired(0.0f); + + if (FocusEffects == 0) { + GetCamera()->SetFocalDistance(0.0f); + GetCamera()->SetDepthOfField(0.0f); + } + + if (!copFound) { + CameraAI::MaybeKillPursuitCam(CarToFollow->GetWorldID()); + } +} diff --git a/src/Speed/Indep/Src/EAXSound/EAXSOund.hpp b/src/Speed/Indep/Src/EAXSound/EAXSOund.hpp index 6aa05c883..b78cdb394 100644 --- a/src/Speed/Indep/Src/EAXSound/EAXSOund.hpp +++ b/src/Speed/Indep/Src/EAXSound/EAXSOund.hpp @@ -113,6 +113,7 @@ class EAXSound : public AudioMemBase { void StartSND11(); void StopSND11(); + void StartNewGamePlay(); void QueueNISStream(unsigned int anim_id, int camera_track_number, void (*setmstimecb)(unsigned int, int)); bool IsNISStreamQueued(); diff --git a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp index fc73ad0fc..b5b6bc1aa 100644 --- a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp +++ b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp @@ -108,6 +108,10 @@ inline void eUnSwizzleWorldVector(const bVector3 &inVec, bVector3 &outVec) { bConvertToBond(outVec, inVec); } +inline void eSwizzleWorldMatrix(const bMatrix4 &inMat, bMatrix4 &outMat) { + bConvertFromBond(outMat, inMat); +} + eRenderTarget *eGetRenderTarget(int render_target); void eUpdateViewMode(void); diff --git a/src/Speed/Indep/Src/Ecstasy/EmitterSystem.h b/src/Speed/Indep/Src/Ecstasy/EmitterSystem.h index 979a05d1f..16c508322 100644 --- a/src/Speed/Indep/Src/Ecstasy/EmitterSystem.h +++ b/src/Speed/Indep/Src/Ecstasy/EmitterSystem.h @@ -572,4 +572,6 @@ class EmitterSystem { LibList mLibs; // offset 0x39C, size 0x10 }; +extern EmitterSystem gEmitterSystem; + #endif diff --git a/src/Speed/Indep/Src/Ecstasy/eMath.hpp b/src/Speed/Indep/Src/Ecstasy/eMath.hpp index 32ac5b572..5c7aadbdb 100644 --- a/src/Speed/Indep/Src/Ecstasy/eMath.hpp +++ b/src/Speed/Indep/Src/Ecstasy/eMath.hpp @@ -41,4 +41,12 @@ inline struct bMatrix4 *eGetZeroMatrix() { return &eMathZeroMatrix; } +inline bMatrix4 *eTransposeMatrix(bMatrix4 *dest, bMatrix4 *m) { + bTransposeMatrix(dest, m); + dest->v0.w = 0.0f; + dest->v1.w = 0.0f; + dest->v2.w = 0.0f; + return dest; +} + #endif diff --git a/src/Speed/Indep/Src/FEng/FEGameInterface.h b/src/Speed/Indep/Src/FEng/FEGameInterface.h index 63da4eb32..b2718ceae 100644 --- a/src/Speed/Indep/Src/FEng/FEGameInterface.h +++ b/src/Speed/Indep/Src/FEng/FEGameInterface.h @@ -5,6 +5,86 @@ #pragma once #endif +#include +#include "FEObject.h" +#include "FEMath.h" + +class FEPackage; +struct FEResourceRequest; +struct FEObjectListEntry; +struct FEMouseInfo; + +enum FEng_WarningLevel { + FEng_NonWarning = 0, + FEng_SoftWarning = 1, + FEng_HardWarning = 2, +}; + +// TODO: FEGameInterface ownership is inferred from the existing stub header and FEGameInterface.cpp line info. +struct FEGameInterface { + virtual ~FEGameInterface() {} + + virtual unsigned char *GetPackageData(const char *pPackageName, unsigned char **pBlockStart, bool &bDeleteBlock) { + bDeleteBlock = false; + return nullptr; + } + + virtual bool LoadResources(FEPackage *pPackage, int Count, FEResourceRequest *pList) { return false; } + virtual bool UnloadResources(FEPackage *pPackage, int Count, FEResourceRequest *pList) { return false; } + virtual void PackageWasLoaded(FEPackage *pPackage) {} + virtual bool PackageWillUnload(FEPackage *pPackage) { return false; } + virtual bool UnloadUnreferencedLibrary() { return false; } + virtual void NotificationMessage(unsigned long Message, FEObject *pObject, unsigned long Param1, unsigned long Param2) {} + virtual void NotifySoundMessage(unsigned long Message, FEObject *pObject, unsigned long ControlMask, unsigned long pPackagePtr) {} + virtual void BeginPackageRendering(FEPackage *pPackage) {} + virtual void EndPackageRendering(FEPackage *pPackage) {} + virtual void GenerateRenderContext(unsigned short uContext, FEObject *pObject) {} + virtual bool GetContextTransform(unsigned short uContext, FEMatrix4 &Matrix) { return false; } + virtual void RenderObjectList(FEObjectListEntry *pList, unsigned long Count) {} + virtual void RenderObject(FEObject *pObject) {} + virtual void DrawMousePointer() {} + virtual void GetViewTransformation(FEMatrix4 *pView) {} + virtual unsigned long GetJoyPadMask(unsigned char feng_pad_index) { return 0; } + virtual void GetMouseInfo(FEMouseInfo &Info) {} + virtual bool DoesPointTouchObject(float xPos, float yPos, FEObject *pButton) { return false; } + virtual bool SetCellData(unsigned long Param1, unsigned long Param2, unsigned long Param3, unsigned long Param4) { return false; } + virtual void OutputWarning(const char *pString, FEng_WarningLevel WarningLevel) {} + virtual void DebugMessageQueued(unsigned long Param1, unsigned long Param2, unsigned long Param3, unsigned long Param4) {} + virtual void DebugMessageProcessed(unsigned long Param1, unsigned long Param2, unsigned long Param3, unsigned long Param4) {} + virtual void DebugMessageBeginUpdate() {} + virtual void DebugMessageEndUpdate() {} +}; + +// TODO: cFEngGameInterface ownership is inferred from FEGameInterface.cpp and the matching FEGameInterface header stub. +class cFEngGameInterface : public FEGameInterface { + public: + cFEngGameInterface(); + virtual ~cFEngGameInterface(); + + bool LoadResources(FEPackage *pPackage, int Count, FEResourceRequest *pList) override; + bool UnloadResources(FEPackage *pPackage, int Count, FEResourceRequest *pList) override; + void NotificationMessage(unsigned long Message, FEObject *pObject, unsigned long Param1, unsigned long Param2) override; + void NotifySoundMessage(unsigned long Message, FEObject *pObject, unsigned long ControlMask, unsigned long pPackagePtr) override; + void GenerateRenderContext(unsigned short uContext, FEObject *pObject) override; + bool GetContextTransform(unsigned short uContext, FEMatrix4 &Matrix) override; + void RenderObject(FEObject *pObject) override; + void GetViewTransformation(FEMatrix4 *pView) override; + void BeginPackageRendering(FEPackage *pPackage) override; + void EndPackageRendering(FEPackage *pPackage) override; + void PackageWasLoaded(FEPackage *pPackage) override; + bool PackageWillUnload(FEPackage *pPackage) override; + unsigned char *GetPackageData(const char *pPackageName, unsigned char **pBlockStart, bool &bDeleteBlock) override; + unsigned long GetJoyPadMask(unsigned char feng_pad_index) override; + void GetMouseInfo(FEMouseInfo &Info) override; + bool DoesPointTouchObject(float xPos, float yPos, FEObject *pButton) override; + void OutputWarning(const char *pString, FEng_WarningLevel WarningLevel) override; + + static cFEngGameInterface *pInstance; + + private: + bool RenderThisPackage; + int iGameMode; +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEMath.h b/src/Speed/Indep/Src/FEng/FEMath.h index 1c696caf7..842bd7845 100644 --- a/src/Speed/Indep/Src/FEng/FEMath.h +++ b/src/Speed/Indep/Src/FEng/FEMath.h @@ -5,6 +5,25 @@ #pragma once #endif +struct FEMatrix4 { + float m11; // offset 0x0, size 0x4 + float m12; // offset 0x4, size 0x4 + float m13; // offset 0x8, size 0x4 + float m14; // offset 0xC, size 0x4 + float m21; // offset 0x10, size 0x4 + float m22; // offset 0x14, size 0x4 + float m23; // offset 0x18, size 0x4 + float m24; // offset 0x1C, size 0x4 + float m31; // offset 0x20, size 0x4 + float m32; // offset 0x24, size 0x4 + float m33; // offset 0x28, size 0x4 + float m34; // offset 0x2C, size 0x4 + float m41; // offset 0x30, size 0x4 + float m42; // offset 0x34, size 0x4 + float m43; // offset 0x38, size 0x4 + float m44; // offset 0x3C, size 0x4 + void Identify(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp b/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp index e3b8fea94..79031eab2 100644 --- a/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp +++ b/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp @@ -139,6 +139,10 @@ class AudioSettings { // total size: 0xC0 class OptionsSettings { public: + GameplaySettings *GetGameplaySettings() { + return &TheGameplaySettings; + } + eOptionsCategory CurrentCategory; // offset 0x0, size 0x4 VideoSettings TheVideoSettings; // offset 0x4, size 0x10 GameplaySettings TheGameplaySettings; // offset 0x14, size 0x20 @@ -149,6 +153,7 @@ class OptionsSettings { // total size: 0x4 struct SMSMessage { public: + bool IsVoice(); private: unsigned char Handle; // offset 0x0, size 0x1 unsigned char Flags; // offset 0x1, size 0x1 @@ -162,6 +167,17 @@ class CareerSettings { return CurrentCar; } + unsigned char GetCurrentBin() { + return CurrentBin; + } + + SMSMessage *GetSMSMessage(unsigned int index); + unsigned short GetSMSSortOrder(); + + void SetAdaptiveDifficulty(float difficulty) { + AdaptiveDifficulty = static_cast(difficulty * 32767.0f); + } + private: uint32 CurrentCar; // offset 0x0, size 0x4 uint32 SpecialFlags; // offset 0x4, size 0x4 @@ -278,6 +294,20 @@ class cFrontendDatabase { return FEGameMode & 1; } + bool IsOnlineMode() { + return FEGameMode & 0x40; + } + + bool IsLANMode() { + return FEGameMode & 8; + } + + GameplaySettings *GetGameplaySettings() { + return CurrentUserProfiles[0]->GetOptions()->GetGameplaySettings(); + } + + bool IsFinalEpicChase(); + unsigned char iNumPlayers; // offset 0x0, size 0x1 bool bComingFromBoot; // offset 0x4, size 0x1 bool bSavedProfileForMP; // offset 0x8, size 0x1 diff --git a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp index 006e3698f..dc49d1796 100644 --- a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp +++ b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp @@ -29,6 +29,10 @@ struct FECustomizationRecord { Physics::eCustomTuningType ActiveTuning; // offset 0x18C, size 0x4 int Preset; // offset 0x190, size 0x4 unsigned char Handle; // offset 0x194, size 0x1 + + bool IsPreset() const { + return Preset != 0; + } }; // total size: 0x8 diff --git a/src/Speed/Indep/Src/Frontend/FEManager.cpp b/src/Speed/Indep/Src/Frontend/FEManager.cpp index e69de29bb..871e46c7b 100644 --- a/src/Speed/Indep/Src/Frontend/FEManager.cpp +++ b/src/Speed/Indep/Src/Frontend/FEManager.cpp @@ -0,0 +1,240 @@ +#include "FEManager.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/FEGameInterface.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEPackageManager.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Camera/ICE/ICEManager.hpp" +#include "Speed/Indep/Src/Input/IOModule.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +class cFEngRender; +class EasterEggs; + +void InitFEngMemoryPool(); +void InitLoadingScreen(); +void InitLoadingTipsScreen(); +void InitLoadingControllerScreen(); +void InitChyron(); +void DismissChyron(); +void SummonChyron(char *, char *, char *); +void ChoppedMiniMapManager_Init(); +void PlayFEMusic_EAXSound(EAXSound *sound, int id); +void SteeringWheels_StopAllForces(); +void UpdateGarageCarLoaders(); +unsigned long FEngMapJoyportToJoyParam(int port); +int FadeScreen_IsOn(); +void BootFlowManager_Init(); +void BootFlowManager_Destroy(); +void EasterEggs_HandleJoy(EasterEggs *eggs); + +extern cFEngRender *cFEngRender_mInstance; +extern int DrawFEng; +extern int DoScreenPrintf; +extern int SummonChyronNow; +extern float RealTimeElapsed; +extern bool CarViewer_haveLoadedOnce; +extern EasterEggs gEasterEggs; + +void ShowCarScreen_CarViewer(); +void BuildCurrentRideForPlayer_cFrontendDatabase(cFrontendDatabase *db, int player, RideInfo *ride); +void SetRideInfo_CarViewer(RideInfo *ride, int reason, int which_car); +void ShowAllCars_CarViewer(); + +FEManager *FEManager::mInstance = nullptr; +int FEManager::mPauseRequest = 0; +const char *FEManager::mPauseReason[8] = {}; + +FEManager::FEManager() { + mFirstScreenMask = 0xff; + mFirstBoot = true; + mEATraxFirstButton = false; + bSuppressControllerError = false; + bAllowControllerError = false; + mFirstScreen = nullptr; + mFirstScreenArg = 0; + mGarageType = GARAGETYPE_NONE; + mPreviousGarageType = GARAGETYPE_NONE; + mGarageBackground = nullptr; + mEATraxDelay = 0; + for (int i = 0; i < 8; i++) { + bWantControllerError[i] = false; + } +} + +void FEManager::Init() { + if (mInstance == nullptr) { + mInstance = new FEManager(); + } + InitFEngMemoryPool(); + InitLoadingScreen(); + InitLoadingTipsScreen(); + InitLoadingControllerScreen(); + InitChyron(); + cFEngGameInterface::pInstance = new cFEngGameInterface(); + ChoppedMiniMapManager_Init(); + cFEng::Init(); + cFEngRender_mInstance = new cFEngRender(); + FEManager::Get()->SetGarageType(GARAGETYPE_MAIN_FE); + uiRepSheetRivalFlow::Init(); +} + +void FEManager::InitInput() { + cFEngJoyInput::mInstance = new cFEngJoyInput(); +} + +FEManager *FEManager::Get() { + return mInstance; +} + +eGarageType FEManager::GetGarageType() { + return mGarageType; +} + +void FEManager::SetGarageType(eGarageType pGarageType) { + eGarageType old = GetGarageType(); + mGarageType = pGarageType; + mPreviousGarageType = old; +} + +const char *FEManager::GetGarageNameFromType() { + switch (mGarageType) { + case GARAGETYPE_NONE: return ""; + case GARAGETYPE_MAIN_FE: return "FE_Garage"; + case GARAGETYPE_CAREER_SAFEHOUSE: return "Safehouse_Garage"; + case GARAGETYPE_CUSTOMIZATION_SHOP: return "CustomShop_Garage"; + case GARAGETYPE_CUSTOMIZATION_SHOP_BACKROOM: return "BackRoom_Garage"; + case GARAGETYPE_CAR_LOT: return "CarLot_Garage"; + default: return ""; + } +} + +const char *FEManager::GetGaragePrefixFromType(eGarageType pGarageType) { + switch (pGarageType) { + case GARAGETYPE_NONE: return ""; + case GARAGETYPE_MAIN_FE: return "FE"; + case GARAGETYPE_CAREER_SAFEHOUSE: return "SH"; + case GARAGETYPE_CUSTOMIZATION_SHOP: + case GARAGETYPE_CUSTOMIZATION_SHOP_BACKROOM: return "CS"; + case GARAGETYPE_CAR_LOT: return "CL"; + default: return ""; + } +} + +bool FEManager::IsOkayToRequestPauseSimulation(int playerIndex, bool useControllerErrors, bool okIfAutoSaveActive) { + if (TheGameFlowManager.GetState() != GAMEFLOW_STATE_RACING) return false; + if (cFEng::Get()->IsPackagePushed("ScreenPrintf")) return false; + if (FadeScreen_IsOn()) return false; + if (cFEng::Get()->IsPackagePushed("FePlayerPaused.fng")) return false; +} + +bool FEManager::ShouldPauseSimulation(bool useControllerErrors) { + && useControllerErrors && UTL::Collections::Singleton::Get() == nullptr + && gMoviePlayer == nullptr) return true; + return mPauseRequest != 0; +} + +void FEManager::RequestPauseSimulation(const char *reason) { + mPauseReason[mPauseRequest] = reason; + mPauseRequest++; +} + +void FEManager::RequestUnPauseSimulation(const char *reason) { + mPauseRequest--; +} + +void FEManager::WantControllerError(int port) { + if (port == -1) return; + bWantControllerError[port] = true; +} + +bool FEManager::WaitingForControllerError() { + for (int i = 0; i < 8; i++) { + if (bWantControllerError[i]) return true; + } + return false; +} + +void FEManager::StartFE() { + PlayFEMusic_EAXSound(g_pEAXSound, -1); + RideInfo ride; + ShowCarScreen_CarViewer(); + BuildCurrentRideForPlayer_cFrontendDatabase(FEDatabase, 0, &ride); + SetRideInfo_CarViewer(&ride, 2, 0); + CarViewer_haveLoadedOnce = true; + } + ShowAllCars_CarViewer(); + } else { + mFirstBoot = false; + BootFlowManager_Init(); + } + bAllowControllerError = false; + bSuppressControllerError = false; + for (int i = 0; i < 8; i++) bWantControllerError[i] = false; + mPauseRequest = 0; + cFEng::Get()->QueuePackagePush(mFirstScreen, mFirstScreenArg, mFirstScreenMask, false); +} + +void FEManager::StopFE() { + cFEngJoyInput::mInstance->JoyDisable(); + FEPackageManager::Get()->CloseAllPackages(0); + BootFlowManager_Destroy(); + mEATraxDelay = 0; +} + +void FEManager::Render() { + if (DrawFEng) cFEng::Get()->DrawForeground(); +} + +void FEManager::Update() { + if (MemoryCard::GetInstance() != nullptr) + MemoryCard::GetInstance()->Tick(static_cast(RealTimeElapsed * 1000.0f)); + SteeringWheels_StopAllForces(); + if (cFEngJoyInput::mInstance != nullptr) cFEngJoyInput::mInstance->HandleJoy(); + int port; + for (port = 0; port < 8; port++) { + if (bWantControllerError[port]) { + if (TheGameFlowManager.IsInGame() && mPauseRequest > 0) { + int p1 = FEDatabase->GetPlayersJoystickPort(0); + ClearControllerError(p1); + int p2 = FEDatabase->GetPlayersJoystickPort(1); + ClearControllerError(p2); + } + IOModule& iomod = IOModule::GetIOModule(); + for (int d = 0; d < iomod.fNumDevices; d++) { + InputDevice *dev = iomod.GetDevice(d); + if (dev != nullptr) dev->PollDevices(); + } + unsigned long jp = FEngMapJoyportToJoyParam(port); + cFEng::Get()->PushErrorPackage("ControllerUnplugged.fng", port, jp); + } + } + break; + } + } + cFEng::Get()->Service(); + FEPackageManager::Get()->Tick(); + if (TheGameFlowManager.GetState() == GAMEFLOW_STATE_IN_FRONTEND) UpdateGarageCarLoaders(); + EasterEggs_HandleJoy(&gEasterEggs); + if (gMoviePlayer != nullptr) gMoviePlayer->Update(); + if (SummonChyronNow) { SummonChyron(nullptr, nullptr, nullptr); SummonChyronNow = 0; } + else if (mEATraxDelay > -1) { mEATraxDelay--; if (mEATraxDelay == 0) SummonChyron(nullptr, nullptr, nullptr); } + } else { + FEPackageManager::Get()->ErrorTick(); + } +} + +void FEManager::SetEATraxSecondButton() { + if (gMoviePlayer != nullptr) return; + DismissChyron(); +} diff --git a/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp b/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp index e69de29bb..7f48cfd1d 100644 --- a/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp +++ b/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp @@ -0,0 +1,108 @@ +#include "FEObjectCallbacks.hpp" + +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/Misc/Config.h" + +extern int SkipMovies; + +enum eLanguages; + +int GetMovieNameEnum(const char *name); +eLanguages GetCurrentLanguage(); +void CalculateMovieFilename(char *buffer, int size, const char *name, eLanguages lang); +bool bFileExists(const char *filename); +void bStrNCpy(char *dst, const char *src, int size); + +void FEngSetInvisible(FEObject *obj); +void FEngSetVisibility(FEObject *obj, bool visible); + +bool FEngMovieStarter::Callback(FEObject *obj) { + if (obj->Type == FE_Movie) { + if (SkipMovies) { + cFEng::Get()->QueueGameMessagePkg(0xC3960EB9, pPackage); + } + const char *movie_name = reinterpret_cast(obj->Handle); + char buffer[64]; + int movieID = GetMovieNameEnum(movie_name); + eLanguages lang = GetCurrentLanguage(); + CalculateMovieFilename(buffer, 64, movie_name, lang); + if (GetCurrentLanguage() != 0 && !bFileExists(buffer)) { + CalculateMovieFilename(buffer, 64, movie_name, static_cast(0)); + } + if (!bFileExists(buffer)) { + cFEng::Get()->QueueGameMessagePkg(0xC3960EB9, pPackage); + } else { + MoviePlayer_StartUp(); + MoviePlayer::Settings settings; + settings.volume = 0; + settings.bufferSize = 0x40000; + settings.activeController = 0; + settings.type = 0; + settings.movieId = 0; + settings.preload = false; + settings.sound = IsSoundEnabled != false; + settings.loop = false; + settings.pal = false; + settings.filename[0] = '\0'; + bStrNCpy(settings.filename, buffer, 256); + settings.loop = true; + settings.type = 0; + settings.movieId = movieID; + gMoviePlayer->Init(settings); + MoviePlayer_Play(); + } + return false; + } + return true; +} + +bool FEngMovieStopper::Callback(FEObject *obj) { + if (obj->Type == FE_Movie) { + if (gMoviePlayer) { + gMoviePlayer->Stop(); + } + MoviePlayer_ShutDown(); + return false; + } + return true; +} + +bool FEngHidePCObjects::Callback(FEObject *obj) { + if (obj->Flags & 0x8) { + FEngSetInvisible(obj); + if (obj->Flags & 0x10000000) { + obj->Flags &= ~0x10000000; + } + obj->Flags |= 0x400000; + } + return true; +} + +bool FEngTransferFlagsToChildren::Callback(FEObject *obj) { + if ((obj->Flags & FlagToTransfer) && obj->Type == FE_Group) { + FEGroup *group = static_cast(obj); + FEObject *child = group->GetFirstChild(); + int num = group->GetNumChildren(); + for (int i = 0; i < num; i++) { + child->Flags |= FlagToTransfer; + Callback(child); + child = child->GetNext(); + } + } + return true; +} + +bool RenderObjectDisconnect::Callback(FEObject *pObj) { + pObj->Cached = nullptr; + return true; +} + +bool ObjectDirtySetter::Callback(FEObject *obj) { + obj->Cached = nullptr; + return true; +} + diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp index e69de29bb..0dc0c51b5 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp @@ -0,0 +1,234 @@ +#include "Speed/Indep/Src/FEng/FEGameInterface.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/FEMath.h" +#include "Speed/Indep/Src/FEng/FEList.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp" +#include "Speed/Indep/Src/Frontend/FEPackageManager.hpp" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +struct RenderContext; +class FEngine; + +class cFEngRender { + public: + static cFEngRender *mInstance; + RenderContext *GetRenderContext(unsigned short renderContext); + void GenerateRenderContext(unsigned short groupContext, FEObject *pObject); + void PrepForPackage(FEPackage *pPackage); + void PackageFinished(FEPackage *pPackage); + void AddToRenderList(FEObject *pObject); +}; + +extern FEColor gNormal; +extern FEColor gTint; +extern FEColor gRapsheet; +extern int g_discErrorOccured; +extern int FEngPleaseRenderSinglePackage; + +void GetBaseName(char *dst, const char *src); +void bToUpper(char *s); +FEPackageRenderInfo *HACK_FEPkgMgr_GetPackageRenderInfo(FEPackage *pkg); +bool FEngTestForIntersection(float x, float y, FEObject *obj); + +cFEngGameInterface *cFEngGameInterface::pInstance = nullptr; + +cFEngGameInterface::cFEngGameInterface() { + RenderThisPackage = false; + iGameMode = 0; +} + +cFEngGameInterface::~cFEngGameInterface() { +} + +bool cFEngGameInterface::LoadResources(FEPackage *pPackage, int Count, FEResourceRequest *pList) { + for (int i = 0; i < Count; i++) { + FEResourceRequest *req = &pList[i]; + char filename[256]; + GetBaseName(filename, req->pFilename); + bToUpper(filename); + unsigned long type = req->Type; + if (type == 1 || type == 2) { + req->Handle = bStringHash(filename); + req->UserParam = 0; + } else if (type == 4) { + void *mem = bMalloc(256, 0); + bStrNCpy(static_cast(mem), filename, 256); + req->Handle = reinterpret_cast(mem); + req->UserParam = 0; + } else { + req->Handle = bStringHash(filename); + req->UserParam = 0; + } + } + return true; +} + +bool cFEngGameInterface::UnloadResources(FEPackage *pPackage, int Count, FEResourceRequest *pList) { + for (int i = 0; i < Count; i++) { + if (pList[i].Type == 4) { + bFree(reinterpret_cast(pList[i].Handle)); + } + } + return true; +} + +void cFEngGameInterface::NotificationMessage( + unsigned long Message, + FEObject *pObject, + unsigned long Param1, + unsigned long Param2 +) { + if (Message != 0x5922615 && Message != 0x7E4D1288) { + FEPackageManager::Get()->NotificationMessage(Message, pObject, Param1, Param2); + } +} + +void cFEngGameInterface::NotifySoundMessage( + unsigned long Message, + FEObject *pObject, + unsigned long ControlMask, + unsigned long pPackagePtr +) { + FEPackageManager::Get()->NotifySoundMessage(Message, pObject, ControlMask, pPackagePtr); +} + +void cFEngGameInterface::GenerateRenderContext(unsigned short uContext, FEObject *pObject) { + cFEngRender::mInstance->GenerateRenderContext(uContext, pObject); +} + +bool cFEngGameInterface::GetContextTransform(unsigned short uContext, FEMatrix4 &Matrix) { + Matrix.Identify(); + if (uContext != 0) { + RenderContext *ctxt = cFEngRender::mInstance->GetRenderContext(uContext); + if (ctxt) { + Matrix = *reinterpret_cast(ctxt); + } + } + return true; +} + +void cFEngGameInterface::RenderObject(FEObject *pObject) { + bool visible = false; + if (!(pObject->Flags & 1)) { + if (RenderThisPackage) { + visible = true; + } + } + + if (pObject->Flags & 0x10) { + FEObjData *data = pObject->GetObjData(); + if (iGameMode == 0) { + data->Color = gNormal; + } else if (iGameMode == 1) { + data->Color = gTint; + } else if (iGameMode == 2) { + data->Color = gRapsheet; + } + } + + if (visible) { + cFEngRender::mInstance->AddToRenderList(pObject); + } +} + +void cFEngGameInterface::GetViewTransformation(FEMatrix4 *pView) { + pView->Identify(); +} + +void cFEngGameInterface::BeginPackageRendering(FEPackage *pPackage) { + RenderThisPackage = true; + if (g_discErrorOccured && pPackage->GetNameHash() != 0x942C98B5) { + RenderThisPackage = false; + } + if (FEngPleaseRenderSinglePackage) { + if (FEHashUpper(pPackage->GetName()) != pPackage->GetNameHash()) { + RenderThisPackage = false; + } + } + if (!FEPackageManager::Get()->GetVisibility(pPackage->GetName())) { + RenderThisPackage = false; + } + cFEngRender::mInstance->PrepForPackage(pPackage); +} + +void cFEngGameInterface::EndPackageRendering(FEPackage *pPackage) { + cFEngRender::mInstance->PackageFinished(pPackage); +} + +void cFEngGameInterface::PackageWasLoaded(FEPackage *pPackage) { + pPackage->InitializePackage(); + pPackage->bExecuting = true; + if (!pPackage->bIsLibrary) { + pPackage->Update(cFEng::Get()->mFEng, 0); + } + FEPackageManager::Get()->PackageWasLoaded(pPackage); + + { + FEngMovieStarter movie_starter(pPackage); + pPackage->ForAllObjects(movie_starter); + } + { + FEngHidePCObjects pcHideObjects; + pPackage->ForAllObjects(pcHideObjects); + } + { + FEngTransferFlagsToChildren transfer_to_children(4); + pPackage->ForAllObjects(transfer_to_children); + } + + if (GRaceStatus::Exists()) { + iGameMode = 1; + } else { + if (FEDatabase && (FEDatabase->GetFEGameMode() & eFE_GAME_MODE_RAP_SHEET)) { + iGameMode = 2; + } else { + iGameMode = 0; + } + } +} + +bool cFEngGameInterface::PackageWillUnload(FEPackage *pPackage) { + { + FEngMovieStopper movie_stop; + pPackage->ForAllObjects(movie_stop); + } + { + RenderObjectDisconnect disconnect( + HACK_FEPkgMgr_GetPackageRenderInfo(pPackage), + cFEngRender::mInstance + ); + pPackage->ForAllObjects(disconnect); + } + FEPackageManager::Get()->PackageWillBeUnloaded(pPackage); + return true; +} + +unsigned char *cFEngGameInterface::GetPackageData( + const char *pPackageName, + unsigned char **pBlockStart, + bool &bDeleteBlock +) { + bDeleteBlock = false; + return static_cast(FEPackageManager::Get()->GetPackageData(pPackageName)); +} + +unsigned long cFEngGameInterface::GetJoyPadMask(unsigned char feng_pad_index) { + return cFEngJoyInput::mInstance->GetJoyPadMask(feng_pad_index); +} + +void cFEngGameInterface::GetMouseInfo(FEMouseInfo &Info) { +} + +bool cFEngGameInterface::DoesPointTouchObject(float xPos, float yPos, FEObject *pButton) { + return FEngTestForIntersection(xPos, yPos, pButton); +} + +void cFEngGameInterface::OutputWarning(const char *pString, FEng_WarningLevel WarningLevel) { +} diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp index e69de29bb..b4be79c18 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp @@ -0,0 +1 @@ +void DisplayStatus(int i) {} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp index e24bb9430..48f0e9d73 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp @@ -5,6 +5,32 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +struct GRuntimeInstance; + +struct IMenuZoneTrigger : public UTL::COM::IUnknown { + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + protected: + IMenuZoneTrigger(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual ~IMenuZoneTrigger() {} + + public: + virtual bool ShouldSeeMenuZoneCluster(); + virtual bool IsPlayerInsideTrigger(); + virtual void EnterTriggerForAutoSave(); + virtual void ExitTriggerForAutoSave(); + virtual void EnterTrigger(GRuntimeInstance *pRaceActivity); + virtual void EnterTrigger(char *zoneType); + virtual void ExitTrigger(); + virtual void RequestEventInfoDialog(int port); + virtual void RequestZoneInfoDialog(int port); + virtual bool IsType(const char *t); + virtual void RequestDoAction(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp index e69de29bb..8592985bd 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp @@ -0,0 +1,70 @@ +#include "FEAnyMovieScreen.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +struct FEMovie; +class GarageMainScreen { + public: + static GarageMainScreen *GetInstance(); + bool IsVisable(); + void NotificationMessage(unsigned long, unsigned long, unsigned long, unsigned long); +}; +FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +void FEngSetMovieName(FEMovie *movie, const char *name); +void DismissChyron(); +void bStrNCpy(char *dst, const char *src, int size); +bool eIsWidescreen(); +char FEAnyMovieScreen::MovieFilename[64] = {}; +char FEAnyMovieScreen::ReturnToPackageName[64] = {}; +FEAnyMovieScreen::FEAnyMovieScreen(ScreenConstructorData *sd) + : MenuScreen(sd) // + , mSubtitler() // + , bHidGarage(false) // + , bAllowingControllerErrors(false) +{ + bAllowingControllerErrors = FEManager::Get()->IsAllowingControllerError(); + FEManager::Get()->AllowControllerError(false); + FEMovie *movie = static_cast(FEngFindObject(GetPackageName(), 0x348FF9F)); + FEngSetMovieName(movie, MovieFilename); + mSubtitler.BeginningMovie(MovieFilename, GetPackageName()); + DismissChyron(); + EFadeScreenOff *evt = new EFadeScreenOff(0x14035FB); + GarageMainScreen *gs = GarageMainScreen::GetInstance(); + if (gs && !gs->IsVisable()) { gs->NotificationMessage(0xAD4BBDC, 0, 0, 0); bHidGarage = true; } +} +FEAnyMovieScreen::~FEAnyMovieScreen() { + if (bHidGarage) { GarageMainScreen *gs = GarageMainScreen::GetInstance(); if (gs) gs->NotificationMessage(0x18883F75, 0, 0, 0); } + FEManager::Get()->SetEATraxSecondButton(); + FEManager::Get()->AllowControllerError(bAllowingControllerErrors); +} +MenuScreen *FEAnyMovieScreen::Create(ScreenConstructorData *sd) { return new(__FILE__, __LINE__) FEAnyMovieScreen(sd); } +void FEAnyMovieScreen::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + mSubtitler.Update(msg); + if (msg == 0xB5AF2461 || msg == 0x406415E3) { + if (FEDatabase->IsDDay() || MoviePlayer_Bypass()) { mSubtitler.Update(0xC3960EB9); DismissMovie(); } + } else if (msg == 0xC3960EB9) { DismissMovie(); } +} +void FEAnyMovieScreen::LaunchMovie(const char *return_to_pkg, const char *filename) { + bStrNCpy(ReturnToPackageName, return_to_pkg, 64); SetMovieName(filename); + cFEng::Get()->QueuePackageSwitch(GetFEngPackageName(), 0, 0, false); +} +void FEAnyMovieScreen::PlaySafehouseIntroMovie() { + SetMovieName("SafehouseIntroMovie"); bStrNCpy(ReturnToPackageName, "FeMainMenu.fng", 64); +} +void FEAnyMovieScreen::DismissMovie() { + if (ReturnToPackageName[0] != '\0') { + cFEng::Get()->QueuePackageSwitch(ReturnToPackageName, 0, 0, false); + ReturnToPackageName[0] = '\0'; + } else { + if (FEDatabase->IsPostRivalMode()) uiRepSheetRivalFlow::Get()->Next(); + else cFEng::Get()->QueuePackagePop(1); + } +} +void FEAnyMovieScreen::SetMovieName(const char *filename) { bStrNCpy(MovieFilename, filename, 64); } +const char *FEAnyMovieScreen::GetFEngPackageName() { + if (eIsWidescreen()) return "FeMovieWide.fng"; + return "FeMovie.fng"; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp index e69de29bb..d839d6f3b 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp @@ -0,0 +1,85 @@ +#include "FEAnyTutorialScreen.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +struct FEMovie; +FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +void FEngSetMovieName(FEMovie *movie, const char *name); +void DismissChyron(); +void bStrNCpy(char *dst, const char *src, int size); +int bStrCmp(const char *a, const char *b); +bool eIsWidescreen(); +unsigned int FEngHashString(const char *s); +void FEngSetLanguageHash(const char *pkg_name, unsigned int obj_hash, unsigned int lang_hash); +unsigned int bStringHash(const char *s, unsigned int hash); +static const char *FEAnyTutorialScreenName = "FeTutorial.fng"; +char FEAnyTutorialScreen::MovieFilename[64] = {}; +char FEAnyTutorialScreen::PackageFilename[64] = {}; +bool FEAnyTutorialScreen::PackageSet = false; +FEAnyTutorialScreen::FEAnyTutorialScreen(ScreenConstructorData *sd) + : MenuScreen(sd) // + , LastTime(0) // + , TimeElapsed(0.0f) // + , TextToggleTiming(0.0f) // + , mTimer() // + , mSubtitler() +{ + DismissChyron(); + FEMovie *movie = static_cast(FEngFindObject(GetPackageName(), 0x348FF9F)); + FEngSetMovieName(movie, MovieFilename); + if (eIsWidescreen()) cFEng::Get()->QueuePackageMessage(0x70D2183B, GetPackageName(), nullptr); + CareerSettings *career = FEDatabase->GetCareerSettings(); + unsigned int str_hash = 0; + bool mSkipable = true; + unsigned int label_hash; + if (bStrCmp(MovieFilename, "DragRaceTutorial") == 0) { + if (career && !career->HasDoneDragTutorial()) { mSkipable = false; career->SetHasDoneDragTutorial(); } + label_hash = FEngHashString("DragTutText"); + } else if (bStrCmp(MovieFilename, "SpeedtrapTutorial") == 0) { + if (career && !career->HasDoneSpeedTrapTutorial()) { mSkipable = false; career->SetHasDoneSpeedTrapTutorial(); } + label_hash = FEngHashString("SpeedTrapTutText"); + } else if (bStrCmp(MovieFilename, "TollBoothTutorial") == 0) { + if (career && !career->HasDoneTollBoothTutorial()) { mSkipable = false; career->SetHasDoneTollBoothTutorial(); } + label_hash = FEngHashString("TollBoothTutText"); + } else if (bStrCmp(MovieFilename, "BountyTutorial") == 0) { + if (career && !career->HasDoneBountyTutorial()) { mSkipable = false; career->SetHasDoneBountyTutorial(); } + label_hash = FEngHashString("BountyTutText"); + } else if (bStrCmp(MovieFilename, "PursuitTutorial") == 0) { + if (career && !career->HasDonePursuitTutorial()) { mSkipable = false; career->SetHasDonePursuitTutorial(); } + label_hash = FEngHashString("PursuitTutText"); + } else { + goto skip_labels; + } + str_hash = label_hash; +skip_labels: + if (mSkipable) cFEng::Get()->QueuePackageMessage(0x59291F95, GetPackageName(), nullptr); + { + unsigned int einput = bStringHash("TutText", str_hash); + FEngSetLanguageHash(GetPackageName(), 0x5A0EE0D9, einput); + FEngSetLanguageHash(GetPackageName(), 0xF414BF3E, einput); + FEngSetLanguageHash(GetPackageName(), 0x5A0EE0D8, einput); + FEngSetLanguageHash(GetPackageName(), 0x07D2EA5D, einput); + } + mSubtitler.BeginningMovie(MovieFilename, GetPackageName()); + EFadeScreenOff *evt = new EFadeScreenOff(0x14035FB); +} +MenuScreen *FEAnyTutorialScreen::Create(ScreenConstructorData *sd) { return new(__FILE__, __LINE__) FEAnyTutorialScreen(sd); } +FEAnyTutorialScreen::~FEAnyTutorialScreen() { FEManager::Get()->SetEATraxSecondButton(); } +void FEAnyTutorialScreen::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + mSubtitler.Update(msg); + if (msg == 0xB5AF2461 || msg == 0x406415E3) { DismissMovie(true); mSubtitler.Update(0xC3960EB9); } + else if (msg == 0xC3960EB9) { DismissMovie(false); } +} +void FEAnyTutorialScreen::LaunchMovie(const char *filename, const char *packageName) { + PackageSet = false; SetMovieName(filename); + if (packageName) SetPackageName(packageName); + cFEng::Get()->QueuePackagePush(FEAnyTutorialScreenName, 0, 0, false); +} +void FEAnyTutorialScreen::DismissMovie(bool send_message) { + cFEng::Get()->QueuePackagePop(1); + if (send_message) cFEng::Get()->QueueGameMessage(0xC3960EB9, PackageFilename, 0xFF); +} +void FEAnyTutorialScreen::SetMovieName(const char *filename) { bStrNCpy(MovieFilename, filename, 64); } +void FEAnyTutorialScreen::SetPackageName(const char *packageName) { PackageSet = true; bStrNCpy(PackageFilename, packageName, 64); } diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp index e69de29bb..4520d2961 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp @@ -0,0 +1 @@ +#include "uiEATraxJukebox.hpp" diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp index e69de29bb..f02fd92ad 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp @@ -0,0 +1,53 @@ +#include "uiOptionsTrailers.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" + +class GarageMainScreen { + public: + static GarageMainScreen *GetInstance(); + void CancelCameraPush(); +}; + +void FEngSetLanguageHash(const char *pkg_name, unsigned int obj_hash, unsigned int language); +int FEngGetLastButton(const char *pkg_name); + +UIOptionsTrailers::UIOptionsTrailers(ScreenConstructorData *sd) + : IconScrollerMenu(sd) { + Setup(); +} + +void UIOptionsTrailers::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, + unsigned long param2) { + if (msg != 0xC407210) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + } + + if (msg == 0x911AB364) { + StorePrevNotification(msg, pobj, param1, param2); + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), 0); + } else if (msg == 0xC407210) { + cFEng::Get()->QueuePackageMessage(0x8CB81F09, 0, 0); + Options.GetCurrentOption()->React(GetPackageName(), 0xC407210, pobj, param1, param2); + } else if (msg == 0xD05FC3A3) { + Options.GetCurrentOption()->React(GetPackageName(), 0xD05FC3A3, pobj, param1, param2); + } else if (msg == 0xE1FDE1D1) { + if (PrevButtonMessage == 0x911AB364) { + FEDatabase->ClearGameMode(eFE_GAME_TRAILERS); + FEDatabase->GetOptionsSettings()->CurrentCategory = static_cast(-1); + cFEng::Get()->QueuePackageSwitch("OptionsMain.fng", 0, 0, 0); + } + } +} + +void UIOptionsTrailers::Setup() { + int lastButton = FEngGetLastButton(GetPackageName()); + if (bFadeInIconsImmediately) { + SetInitialOption(lastButton); + } + + GarageMainScreen::GetInstance()->CancelCameraPush(); + + FEngSetLanguageHash(GetPackageName(), 0xB71B576D, 0xB65A46D8); + RefreshHeader(); +} diff --git a/src/Speed/Indep/Src/Frontend/SubTitle.cpp b/src/Speed/Indep/Src/Frontend/SubTitle.cpp index e69de29bb..07094e9ad 100644 --- a/src/Speed/Indep/Src/Frontend/SubTitle.cpp +++ b/src/Speed/Indep/Src/Frontend/SubTitle.cpp @@ -0,0 +1,196 @@ +#include "SubTitle.hpp" + +#include + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bDebug.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +class FEString; + +int GetCurrentLanguage(); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +void FEngSetLanguageHash(FEString *text, unsigned int hash); +int FEPrintf(FEString *text, const char *fmt, ...); +FEString *FEngFindString(const char *pkg_name, int name_hash); +FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +void FEngGetTopLeft(FEObject *object, float& x, float& y); +void FEngSetTopLeft(FEObject *object, float x, float y); +unsigned int FEngHashString(const char *str, ...); +bool DoesStringExist(unsigned int hash); +const char *GetLocalizedString(unsigned int hash); + +SubTitler *SubTitler::gCurrentSubtitler_; + +SubTitler::SubTitler() { + next_ = 0; + float zero = 0.0f; + data_ = nullptr; + str_ = nullptr; + str2_ = nullptr; + back_ = nullptr; + gCurrentSubtitler_ = this; + timeElapsed = zero; + mSubtitlePaused = false; + lastTime = 0; +} + +SubTitler::~SubTitler() { + Unload(); + gCurrentSubtitler_ = nullptr; +} + +bool SubTitler::ShouldShowSubTitles(const char *movie_name) { + int lang = GetCurrentLanguage(); + if (lang != 0 || mIsTutorial) { + return true; + } + return false; +} + +void SubTitler::BeginningMovie(const char *moviename, const char *packagename) { + SetIsTutorialMovie(moviename); + if (ShouldShowSubTitles(moviename)) { + Load(moviename, packagename); + } +} + +void SubTitler::Load(const char *movieName, const char *packageName) { + char filename[64]; + Unload(); + if (movieName != nullptr) { + bSNPrintf(filename, 0x40, "SUBTITLES\%s.sub", movieName); + int size; + data_ = static_cast(bGetFile(filename, &size, 0)); + if (data_ != nullptr) { + lastTime = 0; + timeElapsed = 0.0f; + next_ = 0; + for (int i = 0; data_[i].startTime != static_cast(0xFFFF); i++) { + bEndianSwap16(&data_[i].startTime); + bEndianSwap32(&data_[i].stringHash); + } + str_ = FEngFindString(packageName, 0x599B8442); + str2_ = FEngFindString(packageName, 0x2E8DA933); + back_ = FEngFindObject(packageName, 0x8BD49BCC); + FEPrintf(str_, ""); + unsigned int hideHash = FEngHashString("HIDE"); + FEngSetScript(reinterpret_cast(str_), hideHash, true); + FEPrintf(str2_, ""); + hideHash = FEngHashString("HIDE"); + FEngSetScript(reinterpret_cast(str2_), hideHash, true); + } + } +} + +void SubTitler::Unload() { + if (data_ != nullptr) { + bFree(data_); + data_ = nullptr; + } +} + +float SubTitler::GetElapsedTime() { + unsigned int timenow; + float thetime_ms; + if (mSubtitlePaused == false) { + timenow = bGetTicker(); + float diff = bGetTickerDifference(lastTime, timenow); + lastTime = timenow; + thetime_ms = timeElapsed + diff * 0.001f; + timeElapsed = thetime_ms; + } else { + timenow = bGetTicker(); + lastTime = timenow; + thetime_ms = timeElapsed; + } + return thetime_ms; +} + +void SubTitler::Update(unsigned int msg) { + if (gMoviePlayer != nullptr) { + mSubtitlePaused = gMoviePlayer->IsMoviePaused(); + if (msg == 0xC9960EBA) { + if (data_ != nullptr && lastTime != 0) { + float timenow = GetElapsedTime(); + if (IsMovieTimerPrintf != 0) { + Timer timer; + char timer_str[100]; + timer.SetTime(timenow); + timer.PrintToString(timer_str, 0); + } + unsigned short delta = static_cast( + static_cast(timenow * 1000.0f)); + if (data_[next_].startTime <= delta) { + RefreshText(); + next_++; + } + } + } else if (msg == 0xC3960EB9) { + Unload(); + } + } +} + +void SubTitler::Start() { + lastTime = bGetTicker(); +} + +void SubTitler::NotifyFirstFrame() { + if (gCurrentSubtitler_ != nullptr) { + gCurrentSubtitler_->Start(); + } +} + +void SubTitler::RefreshText() { + if (mIsTutorial == false) { + if (data_[next_].stringHash != 0x1A20BA) { + const char *str = GetLocalizedString(data_[next_].stringHash); + if (bStrCmp("", str) != 0) { + FEngSetLanguageHash(str_, data_[next_].stringHash); + float x1, y1; + FEngGetTopLeft(reinterpret_cast(str_), x1, y1); + float x2, dummy; + FEngGetTopLeft(back_, x2, dummy); + FEngSetTopLeft(back_, x2, y1); + return; + } + } + float x3, dummy2; + FEngGetTopLeft(back_, x3, dummy2); + FEngSetTopLeft(back_, x3, 1000.0f); + FEPrintf(str_, ""); + } else if (data_[next_].stringHash == 0x1A20BA) { + cFEng::Get()->QueuePackageMessage(0xDBDF2888, nullptr, nullptr); + } else { + FEngSetScript(reinterpret_cast(str_), 0x16A259, true); + FEngSetScript(reinterpret_cast(str2_), 0x16A259, true); + unsigned int text_hash = bStringHash("1_", data_[next_].stringHash); + if (DoesStringExist(text_hash)) { + FEngSetLanguageHash(str_, text_hash); + FEngSetScript(reinterpret_cast(str_), 0xBCBF0306, true); + } + text_hash = bStringHash("2_", data_[next_].stringHash); + if (DoesStringExist(text_hash)) { + FEngSetLanguageHash(str2_, text_hash); + FEngSetScript(reinterpret_cast(str2_), 0xBCBF0306, true); + } + } +} + +void SubTitler::SetIsTutorialMovie(const char *movieName) { + if (bStrCmp(movieName, "NIS_tutorial_1") == 0 || + bStrCmp(movieName, "NIS_tutorial_2") == 0 || + bStrCmp(movieName, "NIS_tutorial_3") == 0 || + bStrCmp(movieName, "NIS_tutorial_4") == 0 || + bStrCmp(movieName, "NIS_tutorial_5") == 0) { + mIsTutorial = true; + } else { + mIsTutorial = false; + } +} diff --git a/src/Speed/Indep/Src/Gameplay/GActivity.cpp b/src/Speed/Indep/Src/Gameplay/GActivity.cpp index e69de29bb..d6cbfa023 100644 --- a/src/Speed/Indep/Src/Gameplay/GActivity.cpp +++ b/src/Speed/Indep/Src/Gameplay/GActivity.cpp @@ -0,0 +1,551 @@ +#include "GActivity.h" + +#include "GManager.h" +#include "GTrigger.h" +#include "LuaMessageDeliveryInfo.h" +#include "Speed/Indep/Libs/Support/Miscellaneous/StringHash.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" +#include "Speed/Indep/Src/Generated/Events/EChangeState.hpp" +#include "Speed/Indep/Src/Lua/LuaBindery.h" +#include "Speed/Indep/Src/Lua/LuaPostOffice.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/Src/Lua/LuaRuntime.h" +#include "Speed/Indep/Src/Lua/source/lua.h" + +unsigned short SerializeTable(lua_State *state, unsigned char *buffer, bool allowUserData) + __asm__("SerializeTable__10LuaRuntimeP9lua_StatePUcb"); +void AttachMetatable(lua_State *luaState, const char *name) __asm__("AttachMetatable__10LuaBinderyP9lua_StatePCc"); +void RegisterHandler(LuaPostOffice *postOffice, unsigned int messageType, GActivity *activity) + __asm__("RegisterHandler__13LuaPostOfficeUiP9GActivity"); +void UnregisterHandler(LuaPostOffice *postOffice, unsigned int messageType, GActivity *activity) + __asm__("UnregisterHandler__13LuaPostOfficeUiP9GActivity"); +void BeginDelivery(LuaRuntime *runtime) __asm__("BeginDelivery__10LuaRuntime"); +void EndDelivery(LuaRuntime *runtime) __asm__("EndDelivery__10LuaRuntime"); +extern LuaPostOffice *fObj__13LuaPostOffice __asm__("fObj__13LuaPostOffice"); + +template struct GObjectIteratorTraits; + +template <> struct GObjectIteratorTraits { + enum { kType = kGameplayObjType_State }; +}; + +template <> struct GObjectIteratorTraits { + enum { kType = kGameplayObjType_Handler }; +}; + +template <> struct GObjectIteratorTraits { + enum { kType = kGameplayObjType_Activity }; +}; + +template class GObjectIterator { + public: + GObjectIterator(unsigned int flagMask); + + bool IsValid() const { + return mInstance != nullptr; + } + + T *GetInstance() const { + return mInstance; + } + + void Advance(); + + private: + T *mInstance; + unsigned int mFlagMask; +}; + +template +GObjectIterator::GObjectIterator(unsigned int flagMask) + : mInstance(nullptr), // + mFlagMask(flagMask) { + mInstance = static_cast(GRuntimeInstance::sRingListHead[GObjectIteratorTraits::kType]); + if (mInstance && (mInstance->GetFlag(mFlagMask) == 0)) { + Advance(); + } +} + +template +void GObjectIterator::Advance() { + if (!mInstance) { + return; + } + + do { + mInstance = static_cast(mInstance->GetNextRuntimeInstance()); + if (mInstance == GRuntimeInstance::sRingListHead[GObjectIteratorTraits::kType]) { + mInstance = nullptr; + return; + } + } while (mInstance->GetFlag(mFlagMask) == 0); +} + +GActivity::GActivity(const Attrib::Key &activityKey) + : GRuntimeInstance(activityKey, kGameplayObjType_Activity) // + , mCurrentState(nullptr) // + , mRegisteredHandlersState(nullptr) // + , mStateHandlers() // + , mRunning(false) // + , mVarsInLuaVM(false) { + GatherStatesAndHandlers(); +} + +GActivity::~GActivity() { + SerializeVars(true); + UnregisterMessageHandlers(); + mStateHandlers.clear(); +} + +LuaMessageDeliveryInfo::~LuaMessageDeliveryInfo() {} + +void LuaMessageDeliveryInfo::BuildMessageTable() { + if (!mLuaTableBuilt && mBuildTableFunc) { + mBuildTableFunc(mLuaState, mMessageBase); + mLuaTableBuilt = true; + } +} + +void GActivity::GatherStatesAndHandlers() { + if (!mStateHandlers.empty()) { + mStateHandlers.clear(); + } + + GObjectIterator iter(0xFFFFFFFF); + while (iter.IsValid()) { + GState *state = iter.GetInstance(); + + if (CollectionIsStateForActivity(state)) { + mStateHandlers[state].clear(); + } + + iter.Advance(); + } + + for (StateToHandlers::iterator iterState = mStateHandlers.begin(); iterState != mStateHandlers.end(); ++iterState) { + GState *state = iterState->first; + UTL::Std::vector &handlerVec = iterState->second; + unsigned int handlerCount = StoreHandlers(state, nullptr); + + handlerVec.reserve(handlerCount); + StoreHandlers(state, &handlerVec); + } +} + +unsigned int GActivity::StoreHandlers(GState *state, UTL::Std::vector *handlerVec) { + unsigned int count = 0; + GObjectIterator iter(0xFFFFFFFF); + + while (iter.IsValid()) { + GHandler *handler = iter.GetInstance(); + + if (CollectionIsHandlerForState(state, handler)) { + if (handlerVec) { + handlerVec->push_back(handler); + } + + count++; + } + + iter.Advance(); + } + + return count; +} + +bool GActivity::CollectionIsStateForActivity(GState *state) { + unsigned int parentKey = GetParent(); + + while (parentKey) { + Attrib::Gen::gameplay parent(Attrib::FindCollection(Attrib::Gen::gameplay::ClassKey(), parentKey), 0, nullptr); + const unsigned int *activityKey = reinterpret_cast(state->GetAttributePointer(0xA0697302, 0)); + + if (!activityKey) { + activityKey = reinterpret_cast(Attrib::DefaultDataArea(sizeof(unsigned int))); + } + + if (*activityKey == parent.GetCollection()) { + return true; + } + + parentKey = parent.GetParent(); + } + + return false; +} + +void GActivity::RegisterMessageHandlers(GState *state) { + if (mRegisteredHandlersState != state) { + if (mRegisteredHandlersState) { + UnregisterMessageHandlers(); + } + + StateToHandlers::iterator iter = mStateHandlers.find(state); + for (UTL::Std::vector::iterator handler = iter->second.begin(); + handler != iter->second.end(); ++handler) { + RegisterHandler(fObj__13LuaPostOffice, (*handler)->GetCollection(), this); + } + + mRegisteredHandlersState = state; + } +} + +void GActivity::UnregisterMessageHandlers() { + if (mRegisteredHandlersState) { + StateToHandlers::iterator iter = mStateHandlers.find(mRegisteredHandlersState); + for (UTL::Std::vector::iterator handler = iter->second.begin(); + handler != iter->second.end(); ++handler) { + UnregisterHandler(fObj__13LuaPostOffice, (*handler)->GetCollection(), this); + } + + mRegisteredHandlersState = nullptr; + } +} + +void GActivity::ActivateReferencedTriggers(bool activate, GRuntimeInstance *instance) { + for (unsigned int onConnection = 0; onConnection < instance->GetConnectionCount(); onConnection++) { + GRuntimeInstance *target = instance->GetConnectionAt(onConnection); + GTrigger *trigger = GRuntimeInstance::FindObject(target->GetCollection()); + + if (trigger) { + if (activate) { + trigger->AddActivationReference(); + } else { + trigger->RemoveActivationReference(); + } + } + } + + for (unsigned int onChild = 0; onChild < instance->Num_Children(); onChild++) { + const GCollectionKey &childSpec = instance->Children(onChild); + GRuntimeInstance *child = GManager::Get().FindInstance(childSpec.GetCollectionKey()); + + if (child) { + ActivateReferencedTriggers(activate, child); + } + } +} + +bool GActivity::CollectionIsHandlerForState(GState *state, GHandler *handler) { + const GCollectionKey &stateRef = handler->stateref(); + + if (stateRef.GetCollectionKey() != state->GetCollection()) { + return false; + } + + { + const GCollectionKey &handlerOwner = handler->handler_owner(); + + if (handlerOwner.GetCollectionKey() == GetCollection()) { + return true; + } + + Attrib::Gen::gameplay handlerOwnerObj(handlerOwner.GetCollectionKey(), 0, nullptr); + + if (bStrCmp( + *reinterpret_cast(handlerOwnerObj.GetLayoutPointer()), + *reinterpret_cast(GetLayoutPointer())) == 0) { + return true; + } + + unsigned int validOwnerKey = GetCollection(); + + while (validOwnerKey) { + if (validOwnerKey == handlerOwner.GetCollectionKey()) { + return true; + } + + { + Attrib::Gen::gameplay validOwnerObj(validOwnerKey, 0, nullptr); + validOwnerKey = validOwnerObj.GetParent(); + } + } + } + + return false; +} + +void GActivity::Run() { + if (!mRunning) { + if (mStateHandlers.empty()) { + GatherStatesAndHandlers(); + } + + mRunning = true; + ActivateReferencedTriggers(true, this); + if (!mCurrentState) { + GState *initialState = GetStateByName("initial"); + new EChangeState(GetCollection(), initialState->GetCollection()); + } else { + RegisterMessageHandlers(mCurrentState); + } + } +} + +void GActivity::Suspend() { + if (!mRunning) { + return; + } + + mRunning = false; + UnregisterMessageHandlers(); + ActivateReferencedTriggers(false, this); +} + +void GActivity::Reset() { + Suspend(); + mCurrentState = nullptr; + mRegisteredHandlersState = nullptr; +} + +GState *GActivity::GetStateByName(const char *stateName) { + StateToHandlers::iterator it = mStateHandlers.begin(); + StateToHandlers::iterator end = mStateHandlers.end(); + + while (it != end) { + GState *state = it->first; + if (state && bStrCmp(state->CollectionName(), stateName) == 0) { + return state; + } + ++it; + } + + return nullptr; +} + +void GActivity::EnterStateByName(const char *stateName) { + EnterState(GetStateByName(stateName)); +} + +void GActivity::EnterState(GState *newState) { + if (mCurrentState == newState) { + return; + } + + UnregisterMessageHandlers(); + mCurrentState = newState; + if (mRunning && mCurrentState) { + RegisterMessageHandlers(mCurrentState); + } +} + +int GActivity::ChangeStateFromScript(lua_State *luaState) { + GActivity *activity = reinterpret_cast(lua_touserdata(luaState, -10002)); + GState *state = activity->GetStateByName(lua_tostring(luaState, 1)); + new EChangeState(activity->GetCollection(), state->GetCollection()); + return 0; +} + +void GActivity::HandleLocalMessage(UCrc32 messageType) { + LuaMessageDeliveryInfo deliveryInfo(messageType, nullptr, nullptr); + + BeginDelivery(&LuaRuntime::Get()); + deliveryInfo.SetLuaState(LuaRuntime::Get().GetState()); + HandleMessage(&deliveryInfo); + if (deliveryInfo.GetLuaState()) { + lua_settop(deliveryInfo.GetLuaState(), -2); + } + EndDelivery(&LuaRuntime::Get()); +} + +void GActivity::PushActivityVars(lua_State *luaState) { + if (!mVarsInLuaVM) { + DeserializeVars(); + } + + const char *activityName = *reinterpret_cast(GetLayoutPointer()); + + lua_pushstring(luaState, activityName); + lua_gettable(luaState, LUA_REGISTRYINDEX); + if (lua_type(luaState, -1) == LUA_TNIL) { + lua_settop(luaState, -2); + lua_newtable(luaState); + lua_pushstring(luaState, activityName); + lua_pushvalue(luaState, -2); + lua_settable(luaState, LUA_REGISTRYINDEX); + } + + mVarsInLuaVM = true; +} + +int GActivity::BuildActivityTables(lua_State *luaState) { + int top = lua_gettop(luaState); + GActivity **userdata = reinterpret_cast(lua_newuserdata(luaState, sizeof(GActivity *))); + + *userdata = this; + AttachMetatable(luaState, "GRuntimeInstance"); + PushActivityVars(luaState); + lua_pushstring(luaState, "ChangeState"); + lua_pushlightuserdata(luaState, this); + lua_pushcclosure(luaState, ChangeStateFromScript, 1); + lua_settable(luaState, LUA_GLOBALSINDEX); + return top; +} + +void GActivity::HandleMessage(LuaMessageDeliveryInfo *deliveryInfo) { + lua_State *luaState = deliveryInfo->GetLuaState(); + int luaStackPreviousTop = 0; + bool activityTablesBuilt = false; + StateToHandlers::const_iterator iterState = mStateHandlers.find(mCurrentState); + const UTL::Std::vector &handlerVec = iterState->second; + + for (GHandler *const *iterHandler = handlerVec.begin(); iterHandler != handlerVec.end(); ++iterHandler) { + GHandler *handler = *iterHandler; + + if (handler->message_id() == deliveryInfo->GetMessageKind()) { + deliveryInfo->SetActivityContext(this); + deliveryInfo->SetHandlerContext(handler); + handler->IsScripted(); + if (handler->MessagePassesFilters(deliveryInfo)) { + deliveryInfo->BuildMessageTable(); + if (!activityTablesBuilt) { + luaStackPreviousTop = BuildActivityTables(luaState); + activityTablesBuilt = true; + } + handler->HandleMessage(deliveryInfo); + } + } + } + + if (activityTablesBuilt) { + lua_settop(luaState, luaStackPreviousTop); + } +} + +template GObjectIterator::GObjectIterator(unsigned int flagMask); +template void GObjectIterator::Advance(); +template GObjectIterator::GObjectIterator(unsigned int flagMask); +template void GObjectIterator::Advance(); +template GObjectIterator::GObjectIterator(unsigned int flagMask); +template void GObjectIterator::Advance(); + +void GActivity::SerializeVars(bool abandonLuaTable) { + const char *stateName; + unsigned int stateNameLen; + bool terminalState; + SerializedHeader header; + lua_State *luaState; + int prevStackTop; + unsigned int footprint; + unsigned char *buffer; + + if (!mVarsInLuaVM) { + return; + } + + stateName = ""; + if (mCurrentState) { + stateName = mCurrentState->Name(0); + } + + terminalState = false; + stateNameLen = bStrLen(stateName); + if (mCurrentState) { + if (bStrCmp(mCurrentState->Name(0), "done") == 0) { + terminalState = true; + } + } + + header.mStateNameHash = stateNameLen ? stringhash32(stateName) : 0; + header.mFlags = 0; + header.mTableBytes = 0; + + if (mRunning) { + header.mFlags = header.mFlags | 1; + } + + if (terminalState) { + header.mFlags = header.mFlags | 2; + } + + luaState = LuaRuntime::Get().GetState(); + prevStackTop = lua_gettop(luaState); + + if (!terminalState) { + lua_pushstring(luaState, *reinterpret_cast(GetLayoutPointer())); + lua_gettable(luaState, LUA_REGISTRYINDEX); + if (lua_type(luaState, -1) == LUA_TTABLE) { + header.mTableBytes = SerializeTable(luaState, nullptr, !Persistent(0)); + } + } + + footprint = header.mTableBytes + sizeof(header); + buffer = reinterpret_cast(GManager::Get().AllocObjectStateBlock(GetCollection(), footprint, Persistent(0))); + + if (buffer) { + bMemCpy(buffer, &header, sizeof(header)); + buffer += sizeof(header); + if (header.mTableBytes != 0) { + unsigned int writtenBytes = SerializeTable(luaState, buffer, !Persistent(0)); + (void)writtenBytes; + } + } + + lua_settop(luaState, prevStackTop); + + if (abandonLuaTable) { + ClearActivityVars(luaState); + } +} + +void GActivity::DeserializeVars() { + unsigned char *buffer = reinterpret_cast(GManager::Get().GetObjectStateBlock(GetCollection())); + + if (buffer) { + bool handlerListWasEmpty = mStateHandlers.empty(); + SerializedHeader header; + const char *stateName; + + if (handlerListWasEmpty) { + GatherStatesAndHandlers(); + } + + bMemCpy(&header, buffer, sizeof(header)); + if (header.mStateNameHash != 0) { + for (StateToHandlers::iterator iterState = mStateHandlers.begin(); iterState != mStateHandlers.end(); ++iterState) { + GState *state = iterState->first; + const char *const *stateNamePtr = + reinterpret_cast(state->GetAttributePointer(0x3E225EC1, 0)); + + if (!stateNamePtr) { + stateNamePtr = reinterpret_cast(Attrib::DefaultDataArea(sizeof(const char *))); + } + + stateName = *stateNamePtr; + if (header.mStateNameHash == stringhash32(stateName)) { + if (!state->GetAttributePointer(0x3E225EC1, 0)) { + Attrib::DefaultDataArea(sizeof(const char *)); + } + mCurrentState = state; + break; + } + } + } + + if (header.mTableBytes != 0) { + lua_State *luaState = LuaRuntime::Get().GetState(); + unsigned int bytesLoaded = LuaRuntime::DeserializeTable(luaState, buffer + sizeof(header), !Persistent(0)); + + if (bytesLoaded != 0) { + lua_pushstring(luaState, *reinterpret_cast(GetLayoutPointer())); + lua_pushvalue(luaState, -2); + lua_settable(luaState, LUA_REGISTRYINDEX); + lua_settop(luaState, -2); + mVarsInLuaVM = true; + } + } + + if (handlerListWasEmpty && !mStateHandlers.empty()) { + mStateHandlers.clear(); + } + } +} + +void GActivity::ClearActivityVars(lua_State *luaState) { + const char *activityName = *reinterpret_cast(GetLayoutPointer()); + + lua_pushstring(luaState, activityName); + lua_pushnil(luaState); + lua_settable(luaState, LUA_REGISTRYINDEX); + mVarsInLuaVM = false; +} diff --git a/src/Speed/Indep/Src/Gameplay/GActivity.h b/src/Speed/Indep/Src/Gameplay/GActivity.h index 2614120c4..0ee052201 100644 --- a/src/Speed/Indep/Src/Gameplay/GActivity.h +++ b/src/Speed/Indep/Src/Gameplay/GActivity.h @@ -7,6 +7,8 @@ #include "GHandler.h" #include "GState.h" +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" #include "Speed/Indep/Libs/Support/Utility/UStandard.h" DECLARE_CONTAINER_TYPE(ID_GHandlerVector); @@ -14,6 +16,9 @@ DECLARE_CONTAINER_TYPE(ID_StateToVectors); typedef UTL::Std::map, _type_ID_StateToVectors> StateToHandlers; +struct LuaMessageDeliveryInfo; +struct lua_State; + // total size: 0x48 class GActivity : public GRuntimeInstance { public: @@ -24,8 +29,74 @@ class GActivity : public GRuntimeInstance { unsigned short mTableBytes; // offset 0x6, size 0x2 }; + void *operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, std::size_t size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + GActivity(const Attrib::Key &activityKey); + ~GActivity() override; + + GameplayObjType GetType() const override { + return kGameplayObjType_Activity; + } + + const char *GetActivityName() { + return CollectionName(); + } + + bool GetIsRunning() const { + return mRunning; + } + + void GatherStatesAndHandlers(); + + unsigned int StoreHandlers(GState *state, UTL::Std::vector *handlerVec); + + bool CollectionIsStateForActivity(GState *state); + + bool CollectionIsHandlerForState(GState *state, GHandler *handler); + + void RegisterMessageHandlers(GState *state); + + void UnregisterMessageHandlers(); + + void ActivateReferencedTriggers(bool activate, GRuntimeInstance *instance); + + void Run(); + + void Suspend(); + + void Reset(); + + GState *GetStateByName(const char *stateName); + + void EnterStateByName(const char *stateName); + + void EnterState(GState *newState); + + static int ChangeStateFromScript(lua_State *luaState); + + void HandleLocalMessage(UCrc32 messageType); + + void PushActivityVars(lua_State *luaState); + + void ClearActivityVars(lua_State *luaState); + + int BuildActivityTables(lua_State *luaState); + + void HandleMessage(LuaMessageDeliveryInfo *deliveryInfo); + + void SerializeVars(bool abandonLuaTable); + + void DeserializeVars(); + private: GState *mCurrentState; // offset 0x28, size 0x4 GState *mRegisteredHandlersState; // offset 0x2C, size 0x4 diff --git a/src/Speed/Indep/Src/Gameplay/GCharacter.cpp b/src/Speed/Indep/Src/Gameplay/GCharacter.cpp index e69de29bb..a952f036b 100644 --- a/src/Speed/Indep/Src/Gameplay/GCharacter.cpp +++ b/src/Speed/Indep/Src/Gameplay/GCharacter.cpp @@ -0,0 +1,315 @@ +#include "GCharacter.h" + +#include "GManager.h" +#include "GMarker.h" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Src/AI/AITarget.h" +#include "Speed/Indep/Src/Generated/Messages/MSetTrafficSpeed.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" +#include "Speed/Indep/Src/Physics/PVehicle.h" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +extern int SkipFE; +extern int SkipFEDisableTraffic; +extern int SkipFEDisableCops; + +GCharacter::GCharacter(const Attrib::Key &triggerKey) + : GRuntimeInstance(triggerKey, kGameplayObjType_Character), // + UTL::COM::Object(1), // + IAttachable(this), // + mSpawnPos(UMath::Vector3::kZero), // + mState(kCharState_Unspawned), // + mFlags(0), // + mCreateAttemptsMade(0), // + mSpawnDir(UMath::Vector3::kZero), // + mSpawnSpeed(0.0f), // + mTargetPos(UMath::Vector3::kZero), // + mVehicle(nullptr), // + mTargetDir(UMath::Vector3::kZero), // + mAttachments(new Sim::Attachments(this)) {} + +GCharacter::~GCharacter() { + delete mAttachments; +} + +void GCharacter::OnAttached(IAttachable *pOther) { + IVehicle *vehicle; + + if (pOther->QueryInterface(&vehicle)) { + mVehicle = vehicle; + } +} + +void GCharacter::OnDetached(IAttachable *pOther) { + IVehicle *ivehicle; + + if (pOther->QueryInterface(&ivehicle)) { + ISimable *isimable; + IVehicleAI *vai; + + mVehicle->QueryInterface(&isimable); + + if (isimable->QueryInterface(&vai)) { + WRoadNav *driveTo = vai->GetDriveToNav(); + + if (driveTo) { + driveTo->CancelPathFinding(); + } + } + + if (IsFlagSet(kCharFlag_UsingStockCar)) { + mVehicle->Deactivate(); + GManager::Get().ReleaseStockCar(isimable); + ClearFlag(kCharFlag_UsingStockCar); + } + + mVehicle = nullptr; + Unspawn(); + } +} + +void GCharacter::Spawn(const UMath::Vector3 &pos, const UMath::Vector3 &dir, GMarker *targetPoint, float initialSpeed) { + Unspawn(); + mSpawnPos = pos; + mSpawnDir = dir; + mSpawnSpeed = MPH2MPS(initialSpeed); + mTargetPos = targetPoint->GetPosition(); + mTargetDir = targetPoint->GetDirection(); + mCreateAttemptsMade = 0; + + AttemptSpawn(); + GManager::Get().AttachCharacter(this); + SetFlag(kCharFlag_AttachedToManager); +} + +bool GCharacter::SpawnPending() const { + return static_cast(mState - kCharState_Spawning_WaitingForModel) < 2; +} + +bool GCharacter::AttemptSpawn() { + if (mState == kCharState_Unspawned) { + const char *carType = CarType(0); + const char *carTypeLowMem = CarTypeLowMem(0); + + if (carTypeLowMem) { + if (*carTypeLowMem) { + carType = carTypeLowMem; + } + } + + bool isCop = bStrCmp(carType, "copmidsize") == 0; + DriverClass driverClass = DRIVER_TRAFFIC; + bool spawn_ok = true; + + if (isCop) { + driverClass = DRIVER_COP; + } + + if (SkipFE) { + if (isCop) { + spawn_ok = SkipFEDisableCops == 0; + } else { + spawn_ok = SkipFEDisableTraffic == 0; + } + } + + if (spawn_ok) { + ISimable *isimable = GManager::Get().GetStockCar(carType); + + if (!isimable) { + VehicleParams params(&GManager::Get(), driverClass, Attrib::StringToKey(carType), mSpawnDir, mSpawnPos, 0, nullptr, 0); + isimable = UTL::COM::Factory::CreateInstance("PVehicle", params); + + if (!isimable) { + isimable = GManager::Get().GetRandomEmergencyStockCar(); + if (isimable) { + SetFlag(kCharFlag_UsingStockCar); + } + } + } else { + SetFlag(kCharFlag_UsingStockCar); + } + + if (isimable) { + Attach(isimable); + mVehicle->SetDriverClass(driverClass); + mState = kCharState_Spawning_WaitingForModel; + } + } + } + + if (mState == kCharState_Spawning_WaitingForModel) { + if (!mVehicle->IsLoading() || AllowInvisibleSpawn(0)) { + mState = kCharState_Spawning_WaitingForTrack; + } + } + + if (mState == kCharState_Spawning_WaitingForTrack) { + float worldHeight = 0.0f; + WCollisionMgr collisionMgr(0, 3); + + if (collisionMgr.GetWorldHeightAtPointRigorous(mSpawnPos, worldHeight, nullptr)) { + ISimable *isimable; + + if (mVehicle->QueryInterface(&isimable)) { + IVehicleAI *vehicleAI; + ITrafficAI *itv; + + if (isimable->QueryInterface(&vehicleAI)) { + AITarget *target; + WRoadNav *road_nav; + + target = vehicleAI->GetTarget(); + + if (target) { + target->Aquire(mTargetPos, mTargetDir); + } + + road_nav = vehicleAI->GetCurrentRoad(); + + if (road_nav) { + road_nav->ResetCookieTrail(); + } + + vehicleAI->SetSpawned(); + vehicleAI->ResetVehicleToRoadPos(mSpawnPos, mSpawnDir); + + if (0.0f < mSpawnSpeed) { + float speedMph; + + mVehicle->Activate(); + speedMph = MPS2MPH(mSpawnSpeed); + MSetTrafficSpeed msg(speedMph, speedMph, true); + msg.SetID(isimable->GetWorldID()); + msg.Post("AIAction"); + + if (isimable->QueryInterface(&itv)) { + itv->StartDriving(mSpawnSpeed); + } + } + } + + mState = kCharState_Spawned; + } + } + } + + return mState == kCharState_Spawned; +} + +bool GCharacter::IsSpawned() const { + return static_cast(mState - kCharState_Spawned) < 2; +} + +void GCharacter::ReleaseVehicle() { + ISimable *simableToKill; + + if (mVehicle) { + simableToKill = nullptr; + + if (IsFlagClear(kCharFlag_UsingStockCar)) { + mVehicle->QueryInterface(&simableToKill); + } + + Detach(mVehicle); + + if (simableToKill) { + simableToKill->Kill(); + } + } +} + +void GCharacter::Unspawn() { + if (IsFlagSet(kCharFlag_AttachedToManager)) { + GManager::Get().DetachCharacter(this); + ClearFlag(kCharFlag_AttachedToManager); + } + + if (mVehicle) { + ReleaseVehicle(); + } + + mState = kCharState_Unspawned; +} + +void GCharacter::UnspawnWhenOffscreen() { + switch (mState) { + case kCharState_Spawning_WaitingForModel: + case kCharState_Spawning_WaitingForTrack: + Unspawn(); + break; + case kCharState_Spawned: + case kCharState_Unspawning_WaitingUntilOffscreen: + if (mVehicle && mVehicle->GetOffscreenTime() > 0.0f) { + Unspawn(); + } + mState = kCharState_Unspawning_WaitingUntilOffscreen; + break; + } +} + +bool GCharacter::IsNoLongerUseful() const { + if (!IsSpawned()) { + return false; + } + + if (mVehicle->GetOffscreenTime() < 0.5f) { + return false; + } + + IVehicle *racerVehicle = IVehicle::First(VEHICLE_RACERS); + for (int racerIdx = 0; racerIdx < IVehicle::Count(VEHICLE_RACERS); ++racerIdx) { + ISimable *racerSimable = racerVehicle ? racerVehicle->GetSimable() : nullptr; + + if (!racerVehicle->IsDestroyed() && racerSimable) { + IRigidBody *rigidBody = racerSimable->GetRigidBody(); + + if (rigidBody) { + UMath::Vector3 charPos; + UMath::Vector3 dirToChar; + UMath::Vector3 humanPos; + UMath::Vector3 humanForward; + float distToChar; + + charPos = mVehicle->GetPosition(); + humanPos = racerVehicle->GetPosition(); + UMath::Sub(charPos, humanPos, dirToChar); + distToChar = UMath::Normalize(dirToChar); + if (distToChar < 100.0f) { + return false; + } + + rigidBody->GetForwardVector(humanForward); + if (UMath::Dot(dirToChar, humanForward) >= 0.0f) { + return false; + } + } + } + + { + const IVehicle::List &list = IVehicle::GetList(VEHICLE_RACERS); + IVehicle *const *iter = std::find(list.begin(), list.end(), racerVehicle); + + if (iter == list.end() || ++iter == list.end()) { + racerVehicle = nullptr; + } else { + racerVehicle = *iter; + } + } + } + + return true; +} + +IVehicle *GCharacter::GetSpawnedVehicle() const { + return mVehicle; +} + +unsigned int GCharacter::GetName() const { + const char *name = RacerName(0); + return name ? Attrib::StringToKey(name) : 0; +} diff --git a/src/Speed/Indep/Src/Gameplay/GCharacter.h b/src/Speed/Indep/Src/Gameplay/GCharacter.h index 330122349..738816e92 100644 --- a/src/Speed/Indep/Src/Gameplay/GCharacter.h +++ b/src/Speed/Indep/Src/Gameplay/GCharacter.h @@ -13,8 +13,88 @@ // total size: 0x80 class GCharacter : public GRuntimeInstance, public UTL::COM::Object, public IAttachable { public: + enum State { + kCharState_Invalid = 0, + kCharState_Unspawned = 1, + kCharState_Spawning_WaitingForModel = 2, + kCharState_Spawning_WaitingForTrack = 3, + kCharState_Spawned = 4, + kCharState_Unspawning_WaitingUntilOffscreen = 5, + }; + + enum Flags { + kCharFlag_UsingStockCar = 1, + kCharFlag_AttachedToManager = 2, + }; + GCharacter(const Attrib::Key &triggerKey); + ~GCharacter() override; + + GameplayObjType GetType() const override { + return kGameplayObjType_Character; + } + + bool HasStockCar() const { + return IsFlagSet(kCharFlag_UsingStockCar); + } + + bool Attach(IUnknown *pOther) override { + return mAttachments->Attach(pOther); + } + + bool Detach(IUnknown *pOther) override { + return mAttachments->Detach(pOther); + } + + bool IsAttached(const IUnknown *pOther) const override { + return mAttachments->IsAttached(pOther); + } + + const IAttachable::List *GetAttachments() const override { + return &mAttachments->GetList(); + } + + void SetFlag(unsigned short flag) { + mFlags = mFlags | flag; + } + + void ClearFlag(unsigned short flag) { + mFlags = mFlags & ~flag; + } + + bool IsFlagSet(unsigned short flag) const { + return (mFlags & flag) != 0; + } + + bool IsFlagClear(unsigned short flag) const { + return (mFlags & flag) == 0; + } + + void OnAttached(IAttachable *pOther) override; + + void OnDetached(IAttachable *pOther) override; + + void Spawn(const UMath::Vector3 &pos, const UMath::Vector3 &dir, GMarker *targetPoint, float initialSpeed); + + bool SpawnPending() const; + + bool IsSpawned() const; + + void ReleaseVehicle(); + + void Unspawn(); + + void UnspawnWhenOffscreen(); + + bool IsNoLongerUseful() const; + + bool AttemptSpawn(); + + IVehicle *GetSpawnedVehicle() const; + + unsigned int GetName() const; + private: UMath::Vector3 mSpawnPos; // offset 0x40, size 0xC unsigned char mState; // offset 0x4C, size 0x1 diff --git a/src/Speed/Indep/Src/Gameplay/GHandler.cpp b/src/Speed/Indep/Src/Gameplay/GHandler.cpp index e69de29bb..d79225ed3 100644 --- a/src/Speed/Indep/Src/Gameplay/GHandler.cpp +++ b/src/Speed/Indep/Src/Gameplay/GHandler.cpp @@ -0,0 +1,180 @@ +#include "Speed/Indep/Src/Gameplay/GHandler.h" + +#include "Speed/Indep/Src/Gameplay/LuaMessageDeliveryInfo.h" +#include "Speed/Indep/Src/Lua/source/lauxlib.h" +#include "Speed/Indep/Src/Lua/source/lua.h" +#include "Speed/Indep/Src/Lua/LuaRuntime.h" +#include "Speed/Indep/Src/Misc/Hermes.h" +#include "Speed/Indep/Src/Misc/LZCompress.hpp" + +void LZByteSwapHeader(LZHeader *header); + +typedef void (*QueryFunc)(IMessageFilterContext *, int, const void *, int, bool *); + +struct MessageFilterHeader { + unsigned char mSourceOffset; + unsigned char mDestOffset; + unsigned char mSize; + unsigned char mInitialized; + QueryFunc mQuery; + unsigned char mQueryStaticData[1]; +}; + +void ByteSwapStaticData(const void *data) __asm__("ByteSwapStaticData__5QueryPCv"); +QueryFunc LookupQueryFunc(unsigned int key) __asm__("LookupQueryFunc__5QueryUi"); + +GHandler::GHandler(const Attrib::Key &handlerKey) + : GRuntimeInstance(handlerKey, kGameplayObjType_Handler), // + mAttached(false) {} + +GHandler::~GHandler() { + Detach(LuaRuntime::Get().GetState()); +} + +void GHandler::NotifyBytecodeFlushed() { + mAttached = false; +} + +void GHandler::Attach(lua_State *luaState) { + struct BlobView { + unsigned int mSize; + const void *mData; + }; + + if (!mAttached) { + Attrib::Blob blob; + bool hasBytecode; + + reinterpret_cast(blob).mSize = 0; + reinterpret_cast(blob).mData = nullptr; + hasBytecode = false; + + if (const Attrib::Blob *blobPtr = reinterpret_cast(GetAttributePointer(0x9a4a020a, 0))) { + blob = *blobPtr; + hasBytecode = true; + } + + if (hasBytecode && reinterpret_cast(blob).mSize != 0) { + int dobuffer_status; + unsigned char *compressedBlock = + reinterpret_cast(const_cast(reinterpret_cast(blob).mData)); + LZHeader *lzHeader = reinterpret_cast(compressedBlock); + + LZByteSwapHeader(lzHeader); + if (!LZValidHeader(lzHeader)) { + dobuffer_status = + lua_dobuffer(luaState, reinterpret_cast(compressedBlock), reinterpret_cast(blob).mSize, "Handler"); + } else { + const unsigned int kStackBufferSize = 0x1000; + unsigned char stackBuffer[kStackBufferSize]; + unsigned char *decompressionBuffer = stackBuffer; + unsigned char *heapBuffer = nullptr; + + if (lzHeader->UncompressedSize > kStackBufferSize) { + heapBuffer = new unsigned char[lzHeader->UncompressedSize]; + decompressionBuffer = heapBuffer; + } + + LZDecompress(reinterpret_cast(lzHeader), decompressionBuffer); + dobuffer_status = lua_dobuffer( + luaState, + reinterpret_cast(decompressionBuffer), + lzHeader->UncompressedSize, + "Handler"); + if (heapBuffer) { + delete[] heapBuffer; + } + } + LZByteSwapHeader(lzHeader); + + if (dobuffer_status == 0) { + mAttached = true; + } + } + } +} + +void GHandler::Detach(lua_State *luaState) { + if (mAttached) { + lua_pushstring(luaState, CollectionName()); + lua_pushnil(luaState); + lua_settable(luaState, LUA_GLOBALSINDEX); + mAttached = false; + } +} + +void GHandler::HandleMessage(LuaMessageDeliveryInfo *deliveryInfo) { + IsScripted(); + ExecuteScriptedHandler(deliveryInfo); +} + +bool GHandler::MessagePassesFilters(LuaMessageDeliveryInfo *deliveryInfo) { + struct BlobView { + unsigned int mSize; + const void *mData; + }; + + for (unsigned int onFilter = 0;; onFilter++) { + if (onFilter >= Num_FilterBlocks()) { + return true; + } + + Attrib::Blob blob; + + reinterpret_cast(blob).mSize = 0; + reinterpret_cast(blob).mData = nullptr; + + if (const Attrib::Blob *blobPtr = + reinterpret_cast(GetAttributePointer(0x56e1436d, onFilter))) { + blob = *blobPtr; + } + + if (unsigned char *blockData = + reinterpret_cast(const_cast(reinterpret_cast(blob).mData))) { + MessageFilterHeader *filter = reinterpret_cast(blockData); + + if (!filter->mInitialized) { + ByteSwapStaticData(filter->mQueryStaticData); + filter->mQuery = LookupQueryFunc(*reinterpret_cast(filter->mQueryStaticData)); + filter->mInitialized = 1; + } + + { + const unsigned char *source = + reinterpret_cast(reinterpret_cast(deliveryInfo->GetMessage()) + 1) + + filter->mSourceOffset; + unsigned char *dest = blockData + filter->mDestOffset + 0xC; + + bMemCpy(dest, source, filter->mSize); + } + + bool filterPassed = true; + + if (filter->mQuery) { + filter->mQuery(deliveryInfo, 0, filter->mQueryStaticData, 1, &filterPassed); + } + + if (FilterModePassAll(0)) { + if (!filterPassed) { + return false; + } + } else { + if (filterPassed) { + return true; + } + } + } + } +} + +void GHandler::ExecuteScriptedHandler(LuaMessageDeliveryInfo *deliveryInfo) { + lua_State *luaState = deliveryInfo->GetLuaState(); + + Attach(luaState); + lua_pushstring(luaState, CollectionName()); + lua_gettable(luaState, LUA_GLOBALSINDEX); + lua_pushvalue(luaState, -3); + lua_pushvalue(luaState, -5); + lua_pushvalue(luaState, -4); + lua_call(luaState, 3, 0); +} diff --git a/src/Speed/Indep/Src/Gameplay/GHandler.h b/src/Speed/Indep/Src/Gameplay/GHandler.h index 0c3503ec5..f315161ef 100644 --- a/src/Speed/Indep/Src/Gameplay/GHandler.h +++ b/src/Speed/Indep/Src/Gameplay/GHandler.h @@ -7,11 +7,31 @@ #include "GRuntimeInstance.h" +struct LuaMessageDeliveryInfo; +struct lua_State; + // total size: 0x2C class GHandler : public GRuntimeInstance { public: GHandler(const Attrib::Key &handlerKey); + ~GHandler() override; + + GameplayObjType GetType() const override { + return kGameplayObjType_Handler; + } + + bool IsScripted() { + return mAttached; + } + + void Attach(lua_State *luaState); + void Detach(lua_State *luaState); + void NotifyBytecodeFlushed(); + bool MessagePassesFilters(LuaMessageDeliveryInfo *deliveryInfo); + void HandleMessage(LuaMessageDeliveryInfo *deliveryInfo); + void ExecuteScriptedHandler(LuaMessageDeliveryInfo *deliveryInfo); + private: bool mAttached; // offset 0x28, size 0x1 }; diff --git a/src/Speed/Indep/Src/Gameplay/GIcon.cpp b/src/Speed/Indep/Src/Gameplay/GIcon.cpp index e69de29bb..b46c88ebd 100644 --- a/src/Speed/Indep/Src/Gameplay/GIcon.cpp +++ b/src/Speed/Indep/Src/Gameplay/GIcon.cpp @@ -0,0 +1,228 @@ +#include "Speed/Indep/Src/Gameplay/GIcon.h" + +#include "Speed/Indep/Src/Ecstasy/EmitterSystem.h" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/World/VisibleSection.hpp" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WorldModel.hpp" + +extern void *gWGrid __asm__("_5WGrid.fgGrid"); + +struct WGrid { + static bool Initialized() { + return gWGrid != nullptr; + } +}; + +static int sNumSpawned; + +GIcon::EffectInfo GIcon::kEffectInfo[GIcon::kType_Count] = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, +}; + +GIcon::GIcon(Type type, const UMath::Vector3 &pos, float rotDeg) + : mType(type), // + mFlags(0), // + mSectionID(-1), // + mCombSectionID(-1), // + mModel(nullptr), // + mEmitter(nullptr), // + mPosition(pos), // + mRotation(bDegToAng(rotDeg)), + mPad(0) { + FindSection(); +} + +GIcon::~GIcon() { + Unspawn(); +} + +void GIcon::Spawn() { + EffectInfo *info = &kEffectInfo[mType]; + + if (mType == kType_HidingSpot) { + SnapToGround(); + } + + if (!mModel && info->mModelHash) { + mModel = CreateGeometry(info->mModelHash); + } + + if (!mEmitter && info->mParticleHash) { + mEmitter = CreateParticleEffect(info->mParticleHash); + } + + if (IsFlagClear(kFlag_Spawned)) { + sNumSpawned++; + } + + SetFlag(kFlag_Spawned); + if (IsFlagSet(kFlag_ShowWhenSpawned)) { + Show(); + ClearFlag(kFlag_ShowWhenSpawned); + } +} + +void GIcon::Unspawn() { + if (IsFlagSet(kFlag_Spawned)) { + ReleaseParticleEffect(); + ReleaseGeometry(); + ClearFlag(kFlag_Spawned); + sNumSpawned--; + } +} + +void GIcon::FindSection() { + bVector2 pos2D(mPosition.x, mPosition.z); + DrivableScenerySection *drivable = TheVisibleSectionManager.FindDrivableSection(&pos2D); + + if (drivable) { + mSectionID = drivable->SectionNumber; + mCombSectionID = drivable->SectionNumber; + } else { + mSectionID = -1; + mCombSectionID = -1; + } +} + +void GIcon::SnapToGround() { + float worldHeight; + bool heightValid; + + if (!WGrid::Initialized()) { + return; + } + + if (IsFlagSet(kFlag_Snapped)) { + return; + } + + if (mPosition.z != 0.0f) { + return; + } + + UMath::Vector3 posSwiz = UMath::Vector3Make(-mPosition.y, mPosition.z, mPosition.x); + worldHeight = 0.0f; + heightValid = WCollisionMgr(0, 3).GetWorldHeightAtPointRigorous(posSwiz, worldHeight, nullptr); + if (heightValid) { + SetFlag(kFlag_Snapped); + mPosition.z = worldHeight; + SetPosition(); + } +} + +void GIcon::NotifyEmitterGroupDelete(void *obj, EmitterGroup *group) { + GIcon *icon = reinterpret_cast(obj); + + if (icon && icon->mEmitter == group) { + icon->mEmitter = nullptr; + } +} + +EmitterGroup *GIcon::CreateParticleEffect(unsigned int particleHash) { + EmitterGroup *effect = gEmitterSystem.CreateEmitterGroup(static_cast(particleHash), 0x8040000); + + if (effect) { + effect->SetAutoUpdate(true); + effect->SubscribeToDeletion(this, NotifyEmitterGroupDelete); + effect->Disable(); + gEmitterSystem.AddEmitterGroup(effect); + mEmitter = effect; + SetPosition(); + } + + return effect; +} + +void GIcon::ReleaseParticleEffect() { + if (mEmitter) { + delete mEmitter; + mEmitter = nullptr; + } +} + +void GIcon::RefreshEffects() { + bool shouldBeEnabled = GetIsEnabled() && GetVisibleInWorld(); + + if (mEmitter) { + if (shouldBeEnabled) { + mEmitter->Enable(); + } else { + mEmitter->Disable(); + } + } +} + +WorldModel *GIcon::CreateGeometry(unsigned int modelHash) { + WorldModel *model = new WorldModel(modelHash, nullptr, true); + + if (model) { + model->SetEnabledFlag(false); + mModel = model; + SetPosition(); + } + + return model; +} + +void GIcon::ReleaseGeometry() { + if (mModel) { + delete mModel; + mModel = nullptr; + } +} + +void GIcon::SetPosition() { + if (IsFlagSet(kFlag_Spawned)) { + bMatrix4 mat; + + bIdentity(&mat); + eRotateZ(&mat, &mat, mRotation); + mat.v3 = bVector4(mPosition.x, mPosition.y, mPosition.z, 1.0f); + + if (mModel) { + mModel->SetMatrix(&mat); + if (IsFlagClear(kFlag_Enabled)) { + mModel->SetEnabledFlag(false); + } + } + + if (mEmitter) { + mEmitter->SetLocalWorld(&mat); + } + } +} + +void GIcon::Enable() { + if (!IsFlagSet(kFlag_Enabled)) { + if (mModel) { + mModel->SetEnabledFlag(GetVisibleInWorld()); + } + RefreshEffects(); + SetFlag(kFlag_Enabled); + } +} + +void GIcon::Disable() { + if (mModel) { + mModel->SetEnabledFlag(false); + } + RefreshEffects(); + ClearFlag(kFlag_Enabled); +} diff --git a/src/Speed/Indep/Src/Gameplay/GIcon.h b/src/Speed/Indep/Src/Gameplay/GIcon.h index a6438b96c..571f6c867 100644 --- a/src/Speed/Indep/Src/Gameplay/GIcon.h +++ b/src/Speed/Indep/Src/Gameplay/GIcon.h @@ -5,6 +5,232 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include + +struct EmitterGroup; +struct WorldModel; +struct bVector2; + +// total size: 0x20 +struct GIcon { + public: + enum Type { + kType_Invalid = 0, + kType_RaceSprint = 1, + kType_RaceCircuit = 2, + kType_RaceDrag = 3, + kType_RaceKnockout = 4, + kType_RaceTollbooth = 5, + kType_RaceSpeedtrap = 6, + kType_RaceRival = 7, + kType_GateSafehouse = 8, + kType_GateCarLot = 9, + kType_GateCustomShop = 10, + kType_HidingSpot = 11, + kType_PursuitBreaker = 12, + kType_SpeedTrap = 13, + kType_SpeedTrapInRace = 14, + kType_AreaUnlock = 15, + kType_Checkpoint = 16, + kType_Count = 17, + }; + + struct EffectInfo { + unsigned int mType; // offset 0x0, size 0x4 + unsigned int mModelHash; // offset 0x4, size 0x4 + unsigned int mParticleHash; // offset 0x8, size 0x4 + }; + + void Show(); + void Hide(); + void HideUntilRespawn(); + void ShowOnMap(); + void HideOnMap(); + void SetGPSing(); + void ClearGPSing(); + Type GetType() const; + int GetSectionID() const; + int GetCombinedSectionID() const; + bool GetVisibleInWorld() const; + bool GetVisibleOnMap() const; + bool GetIsDisposable() const; + bool GetIsSnapped() const; + bool GetIsGPSing() const; + const UMath::Vector3 &GetPosition() const; + void GetPosition2D(bVector2 &outPos); + + static void *operator new(unsigned int size); + static void operator delete(void *mem, unsigned int size); + static void *operator new(unsigned int size, const char *name); + static void operator delete(void *mem, const char *name); + static void operator delete(void *mem, unsigned int size, const char *name); + + void MarkDisposable(); + bool GetIsEnabled() const; + void SetFlag(unsigned int mask); + void ClearFlag(unsigned int mask); + bool IsFlagSet(unsigned int mask) const; + bool IsFlagClear(unsigned int mask) const; + + GIcon(Type type, const UMath::Vector3 &pos, float rotDeg); + ~GIcon(); + + void Spawn(); + void Unspawn(); + void FindSection(); + void SnapToGround(); + static void NotifyEmitterGroupDelete(void *obj, EmitterGroup *group); + EmitterGroup *CreateParticleEffect(unsigned int particleHash); + void ReleaseParticleEffect(); + void RefreshEffects(); + WorldModel *CreateGeometry(unsigned int modelHash); + void ReleaseGeometry(); + void SetPosition(); + void Enable(); + void Disable(); + + static EffectInfo kEffectInfo[]; + + private: + enum Flags { + kFlag_VisibleInWorld = 0x1, + kFlag_VisibleOnMap = 0x2, + kFlag_Spawned = 0x4, + kFlag_Enabled = 0x8, + kFlag_Disposable = 0x10, + kFlag_Snapped = 0x20, + kFlag_ShowWhenSpawned = 0x40, + kFlag_GPSing = 0x80, + }; + + unsigned short mType; // offset 0x0, size 0x2 + unsigned short mFlags; // offset 0x2, size 0x2 + short mSectionID; // offset 0x4, size 0x2 + short mCombSectionID; // offset 0x6, size 0x2 + WorldModel *mModel; // offset 0x8, size 0x4 + EmitterGroup *mEmitter; // offset 0xC, size 0x4 + UMath::Vector3 mPosition; // offset 0x10, size 0xC + unsigned short mRotation; // offset 0x1C, size 0x2 + unsigned short mPad; // offset 0x1E, size 0x2 +}; + +inline void GIcon::Show() { + SetFlag(kFlag_VisibleInWorld); +} + +inline void GIcon::Hide() { + ClearFlag(kFlag_VisibleInWorld); +} + +inline void GIcon::HideUntilRespawn() { + if (IsFlagSet(kFlag_Spawned)) { + Hide(); + } else { + SetFlag(kFlag_ShowWhenSpawned); + } +} + +inline void GIcon::ShowOnMap() { + SetFlag(kFlag_VisibleOnMap); +} + +inline void GIcon::HideOnMap() { + ClearFlag(kFlag_VisibleOnMap); +} + +inline void GIcon::SetGPSing() { + SetFlag(kFlag_GPSing); +} + +inline void GIcon::ClearGPSing() { + ClearFlag(kFlag_GPSing); +} + +inline GIcon::Type GIcon::GetType() const { + return static_cast(mType); +} + +inline int GIcon::GetSectionID() const { + return mSectionID; +} + +inline int GIcon::GetCombinedSectionID() const { + return mCombSectionID; +} + +inline bool GIcon::GetVisibleInWorld() const { + return IsFlagSet(kFlag_VisibleInWorld); +} + +inline bool GIcon::GetVisibleOnMap() const { + return IsFlagSet(kFlag_VisibleOnMap); +} + +inline bool GIcon::GetIsDisposable() const { + return IsFlagSet(kFlag_Disposable); +} + +inline bool GIcon::GetIsSnapped() const { + return IsFlagSet(kFlag_Snapped); +} + +inline bool GIcon::GetIsGPSing() const { + return IsFlagSet(kFlag_GPSing); +} + +inline const UMath::Vector3 &GIcon::GetPosition() const { + return mPosition; +} + +inline void GIcon::GetPosition2D(bVector2 &outPos) { + outPos.x = mPosition.x; + outPos.y = mPosition.z; +} + +inline void *GIcon::operator new(unsigned int size) { + return ::operator new(size); +} + +inline void GIcon::operator delete(void *mem, unsigned int) { + ::operator delete(mem); +} + +inline void *GIcon::operator new(unsigned int size, const char *) { + return ::operator new(size); +} + +inline void GIcon::operator delete(void *mem, const char *) { + ::operator delete(mem); +} + +inline void GIcon::operator delete(void *mem, unsigned int, const char *) { + ::operator delete(mem); +} + +inline void GIcon::MarkDisposable() { + SetFlag(kFlag_Disposable); +} + +inline bool GIcon::GetIsEnabled() const { + return IsFlagSet(kFlag_Enabled); +} + +inline void GIcon::SetFlag(unsigned int mask) { + mFlags = static_cast(mFlags | mask); +} + +inline void GIcon::ClearFlag(unsigned int mask) { + mFlags = static_cast(mFlags & ~mask); +} + +inline bool GIcon::IsFlagSet(unsigned int mask) const { + return (mFlags & mask) != 0; +} + +inline bool GIcon::IsFlagClear(unsigned int mask) const { + return (mFlags & mask) == 0; +} #endif diff --git a/src/Speed/Indep/Src/Gameplay/GInfractionManager.cpp b/src/Speed/Indep/Src/Gameplay/GInfractionManager.cpp index e69de29bb..44cbd9d02 100644 --- a/src/Speed/Indep/Src/Gameplay/GInfractionManager.cpp +++ b/src/Speed/Indep/Src/Gameplay/GInfractionManager.cpp @@ -0,0 +1,68 @@ +#include "Speed/Indep/Src/Gameplay/GInfractionManager.h" + +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" + +typedef GInfractionManager::InfractionType InfractionType; + +#include "Speed/Indep/Src/Generated/Events/EReportInfraction.hpp" + +GInfractionManager *GInfractionManager::mObj = nullptr; + +GInfractionManager::GInfractionManager() + : mInfractions(0), // + mNumThisPursuit(0), // + mSpeeding(false), // + mDrivingRecklessly(false), // + mRacing(false) {} + +void GInfractionManager::Init() { + mObj = new GInfractionManager; +} + +void GInfractionManager::PursuitStarted() { + mInfractions = 0; + mNumThisPursuit = 0; + mSpeeding = false; + mDrivingRecklessly = false; + mRacing = false; +} + +void GInfractionManager::ReportInfraction(InfractionType infraction) { + bool pursuitRace; + + if (!GRaceStatus::Exists()) { + return; + } + + pursuitRace = false; + if (GRaceStatus::Get().GetRaceParameters() && GRaceStatus::Get().GetRaceParameters()->GetIsPursuitRace()) { + pursuitRace = true; + } + + if (!GRaceStatus::Get().GetActivelyRacing() || pursuitRace) { + if ((mInfractions & infraction) == 0) { + mInfractions |= infraction; + new EReportInfraction(infraction); + GManager::Get().TrackValue("total_infractions", static_cast(GetNumInfractions())); + } + + mNumThisPursuit++; + } +} + +unsigned int GInfractionManager::GetNumInfractions() { + unsigned int bits; + unsigned int count; + + count = 0; + for (bits = mInfractions; bits != 0; bits >>= 1) { + count += bits & 1; + } + + return count; +} + +bool GInfractionManager::DidInfractionOccur(InfractionType infraction) { + return (mInfractions & infraction) != 0; +} diff --git a/src/Speed/Indep/Src/Gameplay/GInfractionManager.h b/src/Speed/Indep/Src/Gameplay/GInfractionManager.h index 347ed93d4..ff4ad2d1d 100644 --- a/src/Speed/Indep/Src/Gameplay/GInfractionManager.h +++ b/src/Speed/Indep/Src/Gameplay/GInfractionManager.h @@ -39,33 +39,60 @@ class GInfractionManager { return *mObj; } - // static bool Exists() {} + static bool Exists() { + return mObj != nullptr; + } - // void ClearInfractions() {} + void ClearInfractions() { + mInfractions = 0; + } - // void ReportResistingArrest() {} + void ReportResistingArrest() { + ReportInfraction(kInfraction_Resist); + } - // void ReportSpeeding(bool speeding) {} + void ReportSpeeding(bool speeding) { + mSpeeding = speeding; + if (speeding) { + ReportInfraction(kInfraction_Speeding); + } + } - // void ReportRecklessDriving(bool reckless) {} + void ReportRecklessDriving(bool reckless) { + mDrivingRecklessly = reckless; + if (reckless) { + ReportInfraction(kInfraction_Reckless); + } + } - // void ReportRacing(bool racing) {} + void ReportRacing(bool racing) { + mRacing = racing; + if (racing) { + ReportInfraction(kInfraction_Racing); + } + } - // void ReportAssaultingPoliceOfficer() {} + void ReportAssaultingPoliceOfficer() { + ReportInfraction(kInfraction_Assault); + } - // void ReportHitAndRun() {} + void ReportHitAndRun() { + ReportInfraction(kInfraction_HitAndRun); + } - // void ReportDamageToProperty() {} + void ReportDamageToProperty() { + ReportInfraction(kInfraction_Damage); + } void ReportDrivingOffRoadWay() { ReportInfraction(kInfraction_OffRoad); } - // float GetRecklessSpeedThreshold() {} + float GetRecklessSpeedThreshold(); - // float GetSpeedLimit() {} + float GetSpeedLimit(); - // float GetRacingSpeedLimit() {} + float GetRacingSpeedLimit(); unsigned int GetInfractions() { return mInfractions; diff --git a/src/Speed/Indep/Src/Gameplay/GManager.cpp b/src/Speed/Indep/Src/Gameplay/GManager.cpp index e69de29bb..2b94de517 100644 --- a/src/Speed/Indep/Src/Gameplay/GManager.cpp +++ b/src/Speed/Indep/Src/Gameplay/GManager.cpp @@ -0,0 +1,2879 @@ +#include "Speed/Indep/Src/Gameplay/GManager.h" + +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/milestonetypes.h" +#include "Speed/Indep/Src/Generated/Events/EAutoSave.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOn.hpp" +#include "Speed/Indep/Src/Generated/Events/EShowSMS.hpp" +#include "Speed/Indep/Src/Generated/Messages/MEnteringGameplay.h" +#include "Speed/Indep/Src/Generated/Messages/MNotifyMilestoneProgress.h" +#include "Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp" +#include "Speed/Indep/Src/Gameplay/GMarker.h" +#include "Speed/Indep/Src/Gameplay/GUtility.h" +#include "Speed/Indep/Src/Gameplay/GVault.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ICopMgr.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ITrafficMgr.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/IArticulatedVehicle.h" +#include "Speed/Indep/Src/Physics/PVehicle.h" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Misc/LZCompress.hpp" +#include "Speed/Indep/Src/Misc/MD5.hpp" +#include "Speed/Indep/Src/Misc/Platform.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/TrackPath.hpp" +#include "Speed/Indep/Src/World/TrackInfo.hpp" +#include "Speed/Indep/Src/World/TrackPositionMarker.hpp" +#include "Speed/Indep/Src/World/TrackStreamer.hpp" +#include "Speed/Indep/Src/World/WCollisionAssets.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribLoadAndGo.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/Common/AttribPrivate.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +#include +#include + +extern int SkipFE; +extern int TWEAK_ShowAllGameplayIcons; +extern int TWEAK_ShowGameplayMilestoneValues; +extern int gVerboseTesterOutput; + +char *bStrIStr(const char *s1, const char *s2); +void bCloseMemoryPool(int pool_num); +bool bSetMemoryPoolDebugTracing(int pool_num, bool on_off); +void ApplyTimeOfDayTickOver(); +unsigned int GameplayClassKey() __asm__("ClassKey__Q36Attrib3Gen8gameplay"); +void SetOverRideRainIntensity(float intensity); +void ForEachTrackPositionMarker(bool (*callback)(TrackPositionMarker *marker, unsigned int tag), unsigned int tag); +void LZByteSwapHeader(LZHeader *header); +void World_RestoreProps(); +void GPS_Disengage(); + +class IPursuit; + +namespace Speech { +class Manager { + public: + static int m_speechDisable; + static bool IsCopSpeechPlaying(int event_id); +}; +}; // namespace Speech + +class PhotoFinishScreen { + public: + static int mActive; +}; + +#ifndef DECLARE_GAMEPLAY_MINIMAP_CLASS +#define DECLARE_GAMEPLAY_MINIMAP_CLASS +class Minimap { + public: + static void ConvertPos(bVector2 &worldPos, bVector2 &minimapPos, TrackInfo *track); +}; +#endif + +struct GManagerRaceStatusCompat { + unsigned char _padRaceBin[0x1AB0]; + GRaceBin *mRaceBin; + unsigned char _padPursuitCooldown[0xC]; + bool mPlayerPursuitInCooldown; + unsigned char _padRefresh[0x33]; + bool mRefreshBinAfterRace; +}; + +struct GManagerSpeedTrapCompat { + unsigned char _pad[0xB8]; + unsigned int mNumSpeedTraps; + GSpeedTrap *mSpeedTraps; +}; + +class GCopMgrCompat : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + static GCopMgrCompat *Get() { + return reinterpret_cast(ICopMgr::Get()); + } + + virtual float GetLockoutTimeRemaining() const; + virtual bool VehicleSpawningEnabled(bool isdespawn); + virtual void SpawnCop(UMath::Vector3 &InitialPos, UMath::Vector3 &InitialVec, const char *VehicleName, bool InPursuit, bool RoadBlock); + virtual bool IsCopSpawnPending() const; + virtual void SetAllBustedTimersToZero(); + virtual void PursuitIsEvaded(IPursuit *ipursuit); + virtual bool IsCopRequestPending(); + virtual bool CanPursueRacers(); + virtual bool IsPlayerPursuitActive(); + virtual bool PlayerPursuitHasCop() const; + virtual void PursueAtHeatLevel(int minHeatLevel); + virtual void ResetCopsForRestart(bool release); + virtual void LockoutCops(bool lockout); + virtual void NoNewPursuitsOrCops(); + + protected: + GCopMgrCompat(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + virtual ~GCopMgrCompat() {} +}; + +GManager *GManager::mObj = nullptr; + +GManager::GManager(const char *vaultPackName) + : UTL::COM::Object(1), // + IVehicleCache((UTL::COM::Object *)this), // + mVaultPackFileName(vaultPackName), // + mVaultPackFile(nullptr), // + mVaultCount(0), // + mVaults(nullptr), // + mVaultNameStrings(nullptr), // + mLoadingPackImage(nullptr), // + mBinSlotSize(0), // + mStreamedBinSlots(nullptr), // + mRaceSlotSize(0), // + mStreamedRaceSlots(nullptr), // + mTempLoadData(nullptr), // + mTransientPoolNumber(0), // + mTransientPoolMemory(nullptr), // + mGameplayClass(nullptr), // + mMilestoneClass(nullptr), // + mAttributeKeyShiftTo24(0), // + mCollectionKeyShiftTo32(0), // + mMaxObjects(0), // + mClassTempBuffer(nullptr), // + mInstanceHashTableSize(0), // + mInstanceHashTableMask(0), // + mWorstHashCollision(0), // + mKeyToInstanceMap(nullptr), // + mActiveCharacters(), // + mStockCars(), // + mMilestoneTypeInfo(), // + mNumMilestones(0), // + mMilestones(nullptr), // + mNumSpeedTraps(0), // + mSpeedTraps(nullptr), // + mNumBountySpawnPoints(0), // + mFreeRoamStartMarker(0), // + mFreeRoamFromSafeHouseStartMarker(0), // + mStartFreeRoamFromSafeHouse(false), // + mStartFreeRoamPursuit(false), // + mQueuedPursuitMinHeat(0.0f), // + mInGameplay(false), // + mOverrideFreeRoamStartMarker(0), // + mObjectStateBuffer(nullptr), // + mObjectStateBufferFree(nullptr), // + mObjectStateBufferSize(0), // + mPersistentStateBlocks(), // + mSessionStateBlocks(), // + mPendingSMS(), // + mWarping(false), // + mWarpStartPursuit(false), // + mWarpTargetMarker(0), // + mNumIcons(0), // + mNumVisibleIcons(0), // + mIcons(nullptr), // + mPursuitBreakerIconsShown(false), // + mHidingSpotIconsShown(false), // + mEventIconsShown(false), // + mMenuGateIconsShown(false), // + mSpeedTrapIconsShown(false), // + mSpeedTrapRaceIconsShown(false), // + mAllowEngageEvents(false), // + mAllowEngageSafehouse(false), // + mAllowMenuGates(false), // + mRestartEventHash(0) { + mGameplayClass = Attrib::Database::Get().GetClass(Attrib::Gen::gameplay::ClassKey()); + mMilestoneClass = Attrib::Database::Get().GetClass(Attrib::Gen::milestonetypes::ClassKey()); + + bMemSet(mBinVaultInSlot, 0, sizeof(mBinVaultInSlot)); + bMemSet(mRaceVaultInSlot, 0, sizeof(mRaceVaultInSlot)); + bMemSet(mHidingSpotFound, 0, sizeof(mHidingSpotFound)); + bMemSet(mBountySpawnPoint, 0, sizeof(mBountySpawnPoint)); + + mActiveCharacters.reserve(0x10); + AllocateObjectStateStorage(); + ResetAllGameplayData(); +} + +GManager::~GManager() { + mActiveCharacters.clear(); + ReleaseSpeedTraps(); + ReleaseMilestones(); + ClearStockCars(); + ReleaseStreamingBuffers(); + ReleaseInstanceMap(); + ReleaseObjectStateStorage(); + DestroyVaults(); + + if (mVaultPackFile) { + bClose(mVaultPackFile); + mVaultPackFile = nullptr; + } + + mObj = nullptr; +} + +GVault *GManager::FindVault(const char *vaultName) { + int lower = 0; + int upper = mVaultCount - 1; + + while (lower <= upper) { + int middle = (lower + upper) >> 1; + int compare = bStrCmp(vaultName, mVaults[middle].GetName()); + + if (compare > 0) { + lower = middle + 1; + } else { + if (compare < 0) { + upper = middle - 1; + } else { + return &mVaults[middle]; + } + } + } + + return nullptr; +} + +GVault *GManager::FindVaultContaining(unsigned int collectionKey) { + unsigned int collectionType = Attrib::StringToTypeID("Attrib::CollectionLoadData"); + + for (int i = 0; i < mVaultCount; ++i) { + GVault *vault = &mVaults[i]; + + if (vault->IsLoaded()) { + Attrib::Vault *attribVault = vault->GetAttribVault(); + unsigned int exportCount = attribVault->CountExports(); + + for (unsigned int exportIndex = 0; exportIndex < exportCount; ++exportIndex) { + if (attribVault->GetExportType(exportIndex) == collectionType) { + Attrib::Collection *collection = reinterpret_cast(attribVault->GetExportData(exportIndex)); + + if (collection && collection->GetKey() == collectionKey) { + return vault; + } + } + } + } + } + + return nullptr; +} + +void GManager::LoadCoreVault(AttribVaultPackImage *packImage) { + FindVault("gpcore")->LoadResident(packImage); +} + +void GManager::UnloadCoreVault() { + FindVault("gpcore")->Unload(); +} + +void GManager::PreloadTransientVaults(AttribVaultPackImage *packImage) { + mTransientPoolMemory = bMalloc(0x164000, 0x47); + mTransientPoolNumber = bGetFreeMemoryPoolNum(); + bInitMemoryPool(mTransientPoolNumber, mTransientPoolMemory, 0x164000, "GManager Temp"); + bSetMemoryPoolDebugTracing(mTransientPoolNumber, false); + + for (unsigned int i = 0; i < mVaultCount; ++i) { + DVDErrorTask(nullptr, 0); + + if (!mVaults[i].IsLoaded()) { + mVaults[i].PreloadTransient(packImage, mTransientPoolNumber); + } + } +} + +void GManager::Init(const char *vaultPackName) { + mObj = new GManager(vaultPackName); + mObj->InitializeVaults(); +} + +void GManager::InitializeVaults() { + char compressedFilename[128]; + void *compressedBuf; + LZHeader *header; + AttribVaultPackImage *imageBuffer; + unsigned int tableBufferSize; + + bStrCpy(compressedFilename, mVaultPackFileName); + bStrCpy(bStrIStr(compressedFilename, ".BIN"), ".LZC"); + + compressedBuf = bGetFile(compressedFilename, nullptr, 0); + header = reinterpret_cast(compressedBuf); + LZByteSwapHeader(header); + imageBuffer = static_cast(bMalloc(header->UncompressedSize, 0x1047)); + LZDecompress(reinterpret_cast(compressedBuf), reinterpret_cast(imageBuffer)); + bFree(compressedBuf); + + mLoadingPackImage = imageBuffer; + mLoadingPackImage->EndianSwap(); + + tableBufferSize = mGameplayClass->GetTableNodeSize() << 14; + mClassTempBuffer = bMalloc(tableBufferSize, 0x40); + mGameplayClass->SetTableBuffer(mClassTempBuffer, tableBufferSize); + + BuildVaultTable(mLoadingPackImage); + LoadCoreVault(mLoadingPackImage); + PreloadTransientVaults(mLoadingPackImage); + FindKeyReductionShifts(); + AllocateIcons(); + AllocateMilestones(); + AllocateSpeedTraps(); + FindBountySpawnPoints(); + RefreshZoneIcons(); + RefreshTrackMarkerIcons(); +} + +void GManager::InitializeRaceStreaming() { + AllocateStreamingBuffers(); + AllocateInstanceMap(); + UnloadTransientVaults(); + + mGameplayClass->SetTableBuffer(nullptr, mMaxObjects * 3 * mGameplayClass->GetTableNodeSize()); + bFree(mClassTempBuffer); + mClassTempBuffer = nullptr; + bFree(mLoadingPackImage); + mLoadingPackImage = nullptr; +} + +void GManager::UnloadTransientVaults() { + for (unsigned int i = 0; i < mVaultCount; ++i) { + GVault &vault = mVaults[i]; + + if (vault.IsTransient()) { + vault.Unload(); + } + } + + bCloseMemoryPool(mTransientPoolNumber); + bFree(mTransientPoolMemory); + mTransientPoolMemory = nullptr; + mTransientPoolNumber = 0; +} + +void GManager::PreBeginGameplay() { + if (mRestartEventHash) { + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromHash(mRestartEventHash); + GRaceCustom *race = GRaceDatabase::Get().AllocCustomRace(parms); + + GRaceDatabase::Get().SetStartupRace(race, GRace::kRaceContext_Career); + GRaceDatabase::Get().FreeCustomRace(race); + mRestartEventHash = 0; + } +} + +void GManager::BeginGameplay() { + for (unsigned int onVault = 0; onVault < mVaultCount; ++onVault) { + GVault &vault = mVaults[onVault]; + + if (vault.IsLoaded()) { + vault.CreateGameplayObjects(); + } + } + + GRaceCustom *startupRace = GRaceDatabase::Get().GetStartupRace(); + if (startupRace) { + startupRace->CreateRaceActivity(); + } + + ConnectRuntimeInstances(); + StartActivities(); + WCollisionAssets::Get().AddPackLoadCallback(NotifyCollisionPackLoaded); + mPursuitBreakerIconsShown = false; + mHidingSpotIconsShown = false; + mEventIconsShown = false; + mMenuGateIconsShown = false; + mSpeedTrapIconsShown = false; + mSpeedTrapRaceIconsShown = false; + RefreshEngageTriggerIcons(); + RefreshSpeedTrapIcons(); + SpawnAllLoadedSectionIcons(); + + if (!startupRace) { + GRaceStatus::Get().EnableBinBarriers(); + } + + MEnteringGameplay().Post(UCrc32(0x20D60DBF)); + mStartFreeRoamFromSafeHouse = false; + mInGameplay = true; +} + +void GManager::Update(float dT) { + GRaceStatus::Get().Update(dT); + UnspawnUselessCharacters(); + ServicePendingCharacters(); + UpdatePursuit(); + UpdateTimers(dT); + UpdatePendingSMS(); + UpdateTriggerAvailability(); + UpdateIconVisibility(); +} + +void GManager::ClearAllSessionData() { + mSessionStateBlocks.clear(); + DefragObjectStateStorage(); +} + +void GManager::ServicePendingCharacters() { + for (GCharacterList::iterator it = mActiveCharacters.begin(); it != mActiveCharacters.end(); ++it) { + GCharacter *character = *it; + + if (character->SpawnPending() && character->AttemptSpawn()) { + return; + } + } +} + +void GManager::UnspawnUselessCharacters() { + for (GCharacterList::iterator it = mActiveCharacters.begin(); it != mActiveCharacters.end(); ++it) { + GCharacter *character = *it; + + if (character->IsNoLongerUseful()) { + character->Unspawn(); + return; + } + } +} + +void GManager::EndGameplay() { + UnspawnAllCharacters(); + ClearStockCars(); + + for (unsigned int i = 0; i < mVaultCount; ++i) { + if (mVaults[i].IsLoaded()) { + mVaults[i].DestroyGameplayObjects(); + } + } + + UnspawnAllIcons(); + ClearAllSessionData(); + WCollisionAssets::Get().RemovePackLoadCallback(NotifyCollisionPackLoaded); + GRaceDatabase::Get().SetStartupRace(nullptr, GRace::kRaceContext_Career); + mInGameplay = false; + mWorstHashCollision = 0; + mOverrideFreeRoamStartMarker = 0; + ApplyTimeOfDayTickOver(); + SetOverRideRainIntensity(0.0f); +} + +void GManager::StartWorldActivities(bool startFreeRoamOnly) { + for (unsigned int i = 0; i < mInstanceHashTableSize; ++i) { + GRuntimeInstance *instance = mKeyToInstanceMap[i].mInstance; + + if (instance && instance->GetType() == kGameplayObjType_Activity) { + GActivity *activity = static_cast(instance); + bool autoStart = activity->AutoStart(0); + + if (activity->FreeRoamOnly(0) && !startFreeRoamOnly) { + autoStart = false; + } + + if (autoStart) { + activity->Run(); + } + } + } +} + +void GManager::StartActivities() { + bool startWorld = SkipFE == 0; + GRaceCustom *startupRace = GRaceDatabase::Get().GetStartupRace(); + + if (startupRace) { + startupRace->GetRaceActivity()->Run(); + GRaceStatus::Get().SetRaceContext(GRaceDatabase::Get().GetStartupRaceContext()); + startWorld = GRaceDatabase::Get().GetStartupRaceContext() == GRace::kRaceContext_Career && startWorld; + } + + if (startWorld) { + StartWorldActivities(startupRace == nullptr); + } +} + +void GManager::StartRaceFromInGame(unsigned int raceHash) { + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming) { + GRaceParameters *race = GRaceDatabase::Get().GetRaceFromHash(raceHash); + + if (race) { + GManagerRaceStatusCompat &raceStatus = reinterpret_cast(GRaceStatus::Get()); + + if (raceStatus.mRaceBin) { + if (race->GetParentVault() != raceStatus.mRaceBin->GetChildVault()) { + SuspendAllBinActivities(); + raceStatus.mRefreshBinAfterRace = true; + } + } + + race->BlockUntilLoaded(); + race->GetActivity()->Reset(); + race->GetActivity()->Run(); + } + } +} + +void GManager::UpdateTimers(float dT) { + for (unsigned int i = 0; i < 8; ++i) { + mTimers[i].Update(dT); + } +} + +bool GManager::SetTimer(const char *name, float interval) { + unsigned int hash = bStringHash(name); + int index; + + for (index = 0; index < 8; ++index) { + GEventTimer &timer = mTimers[index]; + + if (hash == timer.GetNameHash()) { + timer.Stop(); + timer.SetInterval(interval); + timer.Start(); + return true; + } + } + + for (index = 0; index < 8; ++index) { + GEventTimer &timer = mTimers[index]; + + if (!timer.IsRunning()) { + timer.SetName(name); + timer.SetInterval(interval); + timer.Start(); + return true; + } + } + + return false; +} + +void GManager::KillTimer(const char *name) { + unsigned int hash = bStringHash(name); + + for (unsigned int i = 0; i < 8; ++i) { + if (hash == mTimers[i].GetNameHash()) { + mTimers[i].Stop(); + return; + } + } +} + +unsigned int GManager::SaveTimerInfo(SavedTimerInfo *saveInfo) { + for (unsigned int i = 0; i < 8; ++i) { + mTimers[i].Serialize(&saveInfo[i]); + } + + return 8; +} + +void GManager::LoadTimerInfo(SavedTimerInfo *saveInfo, unsigned int count) { + for (unsigned int i = 0; i < count; ++i) { + mTimers[i].Deserialize(&saveInfo[i]); + } +} + +void GManager::StartBinActivity(GRaceBin *raceBin) { + GActivity *activity; + + if (!raceBin) { + return; + } + + activity = static_cast(FindInstance(raceBin->GetCollectionKey())); + if (activity && !activity->GetIsRunning()) { + activity->Run(); + } +} + +void GManager::LoadVaultSync(GVault *vault) { + int slot; + int offset; + unsigned char *streamBuffer; + + if (!vault->IsRaceBin()) { + slot = GetAvailableRaceSlot(); + streamBuffer = mStreamedRaceSlots; + offset = slot * mRaceSlotSize; + mRaceVaultInSlot[slot] = vault; + } else { + GetAvailableRaceSlot(); + slot = GetAvailableBinSlot(); + streamBuffer = mStreamedBinSlots; + offset = slot * mBinSlotSize; + mBinVaultInSlot[slot] = vault; + } + + mVaultPackFile = bOpen(mVaultPackFileName, 1, 1); + bSeek(mVaultPackFile, vault->GetDataOffset(), 0); + bRead(mVaultPackFile, streamBuffer + offset, vault->GetDataSize()); + bSeek(mVaultPackFile, vault->GetLoadDataOffset(), 0); + bRead(mVaultPackFile, mTempLoadData, vault->GetLoadDataSize()); + bClose(mVaultPackFile); + mVaultPackFile = nullptr; + + vault->InitTransient(streamBuffer + offset, mTempLoadData); + if (mInGameplay) { + ConnectRuntimeInstances(); + } +} + +int GManager::GetAvailableBinSlot() { + if (mBinVaultInSlot[0]) { + mBinVaultInSlot[0]->Unload(); + mBinVaultInSlot[0] = nullptr; + } + + return 0; +} + +int GManager::GetAvailableRaceSlot() { + if (mRaceVaultInSlot[0]) { + mRaceVaultInSlot[0]->Unload(); + mRaceVaultInSlot[0] = nullptr; + } + + return 0; +} + +GMilestone *GManager::GetNextMilestone(GMilestone *current, bool availOnly, unsigned int binNumber) { + GMilestone *next = current + 1; + GMilestone *end = mMilestones + mNumMilestones; + + while (next < end) { + if ((!availOnly || next->GetIsAvailable() || next->GetIsDonePendingEscape()) && + (binNumber == 0 || next->GetBinNumber() == binNumber)) { + return next; + } + + next++; + } + + return nullptr; +} + +GMilestone *GManager::GetFirstMilestone(bool availOnly, unsigned int binNumber) { + return GetNextMilestone(mMilestones - 1, availOnly, binNumber); +} + +unsigned int GManager::SaveMilestones(GMilestone *dest) { + bMemCpy(dest, mMilestones, mNumMilestones * sizeof(GMilestone)); + return mNumMilestones; +} + +void GManager::LoadMilestones(GMilestone *src, unsigned int count) { + unsigned int i; + unsigned int j; + + for (i = 0; i < count; ++i) { + for (j = 0; j < mNumMilestones; ++j) { + if (mMilestones[j].GetChallengeKey() == src[i].GetChallengeKey()) { + mMilestones[j] = src[i]; + break; + } + } + } +} + +GSpeedTrap *GManager::GetNextSpeedTrap(GSpeedTrap *current, bool activeOnly, unsigned int binNumber) { + current++; + + while (current < mSpeedTraps + mNumSpeedTraps) { + if ((!activeOnly || current->GetIsActive()) && + (binNumber == 0 || current->GetBinNumber() == binNumber)) { + return current; + } + + current++; + } + + return nullptr; +} + +unsigned int GManager::SaveSpeedTraps(GSpeedTrap *dest) { + bMemCpy(dest, mSpeedTraps, mNumSpeedTraps * sizeof(GSpeedTrap)); + return mNumSpeedTraps; +} + +void GManager::LoadSpeedTraps(GSpeedTrap *src, unsigned int count) { + unsigned int i; + unsigned int j; + + for (i = 0; i < count; ++i) { + for (j = 0; j < mNumSpeedTraps; ++j) { + if (mSpeedTraps[j].GetSpeedTrapKey() == src[i].GetSpeedTrapKey()) { + mSpeedTraps[j] = src[i]; + break; + } + } + } +} + +void GManager::AllocateObjectStateStorage() { + mObjectStateBuffer = static_cast(bMalloc(0x4000, GetVirtualMemoryAllocParams())); + mObjectStateBufferFree = mObjectStateBuffer; + mObjectStateBufferSize = 0x4000; +} + +void GManager::AllocateInstanceMap() { + unsigned int largestTransientCount; + + largestTransientCount = 0; + mMaxObjects = 0; + + for (unsigned int i = 0; i < mVaultCount; ++i) { + GVault &vault = mVaults[i]; + + if (vault.IsResident()) { + mMaxObjects += vault.GetObjectCount(); + } else if (largestTransientCount < vault.GetObjectCount()) { + largestTransientCount = vault.GetObjectCount(); + } + } + + mMaxObjects += largestTransientCount * 2; + mInstanceHashTableSize = 0x100; + + { + unsigned int neededSize = mMaxObjects + (mMaxObjects >> 1); + + while (mInstanceHashTableSize < neededSize) { + mInstanceHashTableSize <<= 1; + } + } + + mInstanceHashTableMask = mInstanceHashTableSize - 1; + mKeyToInstanceMap = static_cast(bMalloc(mInstanceHashTableSize << 3, GetVirtualMemoryAllocParams())); + bMemSet(mKeyToInstanceMap, 0, mInstanceHashTableSize << 3); +} + +void GManager::AllocateStreamingBuffers() { + GVault *largestBinVault; + GVault *largestRaceVault; + + largestBinVault = nullptr; + for (unsigned int i = 0; i < GRaceDatabase::Get().GetBinCount(); ++i) { + GRaceBin *raceBin = GRaceDatabase::Get().GetBin(i); + GVault *vault = raceBin->GetChildVault(); + + if (vault && !vault->IsResident()) { + if (!largestBinVault || largestBinVault->GetFootprint() < vault->GetFootprint()) { + largestBinVault = vault; + } + } + } + + largestRaceVault = nullptr; + for (unsigned int i = 0; i < GRaceDatabase::Get().GetRaceCount(); ++i) { + GRaceParameters *raceParameters = GRaceDatabase::Get().GetRaceParameters(i); + GVault *vault = raceParameters->GetChildVault(); + + if (vault && !vault->IsResident()) { + if (!largestRaceVault || largestRaceVault->GetFootprint() < vault->GetFootprint()) { + largestRaceVault = vault; + } + } + } + + mBinSlotSize = largestBinVault ? largestBinVault->GetFootprint() : 0; + mRaceSlotSize = largestRaceVault ? largestRaceVault->GetFootprint() : 0; + + mStreamedBinSlots = new unsigned char[mBinSlotSize]; + mStreamedRaceSlots = new unsigned char[mRaceSlotSize]; + + { + unsigned int maxLoadDataSize = 0; + + for (unsigned int i = 0; i < mVaultCount; ++i) { + GVault &vault = mVaults[i]; + + if (vault.IsTransient()) { + if (maxLoadDataSize < vault.GetLoadDataSize()) { + maxLoadDataSize = vault.GetLoadDataSize(); + } + } + } + + mTempLoadData = new unsigned char[maxLoadDataSize]; + } +} + +void GManager::BuildVaultTable(AttribVaultPackImage *packImage) { + char *image; + AttribVaultPackEntry *entries; + + image = reinterpret_cast(packImage); + mVaultNameStrings = new char[*reinterpret_cast(image + 0xC)]; + bMemCpy(const_cast(mVaultNameStrings), image + *reinterpret_cast(image + 8), *reinterpret_cast(image + 0xC)); + + mVaultCount = *reinterpret_cast(image + 4); + mVaults = static_cast(bMalloc(mVaultCount << 6, GetVirtualMemoryAllocParams() | 0x1000)); + + entries = reinterpret_cast(image + 0x10); + for (unsigned int i = 0; i < mVaultCount; ++i) { + new (&mVaults[i]) GVault(&entries[i], mVaultNameStrings + entries[i].mVaultNameOffset); + } +} + +void GManager::ReleaseObjectStateStorage() { + mPersistentStateBlocks.clear(); + mSessionStateBlocks.clear(); + + if (mObjectStateBuffer) { + bFree(mObjectStateBuffer); + } + + mObjectStateBufferFree = nullptr; + mObjectStateBufferSize = 0; +} + +void GManager::ReleaseInstanceMap() { + if (mKeyToInstanceMap) { + bFree(mKeyToInstanceMap); + mKeyToInstanceMap = nullptr; + } + + mInstanceHashTableSize = 0; + mInstanceHashTableMask = 0; +} + +void GManager::ReleaseStreamingBuffers() { + if (mStreamedBinSlots) { + delete[] mStreamedBinSlots; + mStreamedBinSlots = nullptr; + } + + if (mStreamedRaceSlots) { + delete[] mStreamedRaceSlots; + mStreamedRaceSlots = nullptr; + } + + if (mTempLoadData) { + delete[] mTempLoadData; + mTempLoadData = nullptr; + } +} + +unsigned int GManager::GetStrippedNameKey(const char *name) { + int length; + const char *start; + const char *scan; + + length = bStrLen(name); + scan = name + length; + do { + start = scan; + scan = start - 1; + if (scan < name || *scan == '/') { + break; + } + } while (*scan != '\\'); + + return Attrib::StringToKey(start); +} + +void GManager::RegisterInstance(GRuntimeInstance *instance) { + unsigned int key; + unsigned int collisions; + unsigned int slot; + + key = instance->GetCollection() >> (mCollectionKeyShiftTo32 & 0x1F); + collisions = 0; + if (mInstanceHashTableSize != 0) { + slot = key & mInstanceHashTableMask; + do { + if (!mKeyToInstanceMap[slot].mInstance) { + if (mWorstHashCollision < collisions) { + mWorstHashCollision = collisions; + } + + mKeyToInstanceMap[slot].mKey32 = key; + mKeyToInstanceMap[slot].mInstance = instance; + return; + } + + collisions++; + slot = (slot + 1) & mInstanceHashTableMask; + } while (collisions < mInstanceHashTableSize); + } +} + +void GManager::UnregisterInstance(GRuntimeInstance *instance) { + unsigned int key; + unsigned int collisions; + unsigned int slot; + + key = instance->GetCollection() >> (mCollectionKeyShiftTo32 & 0x1F); + collisions = 0; + slot = key & mInstanceHashTableMask; + do { + if (mKeyToInstanceMap[slot].mKey32 == key) { + mKeyToInstanceMap[slot].mKey32 = 0; + mKeyToInstanceMap[slot].mInstance = nullptr; + return; + } + + collisions++; + slot = (slot + 1) & mInstanceHashTableMask; + } while (collisions <= mWorstHashCollision); +} + +GRuntimeInstance *GManager::FindInstance(Attrib::Key key) const { + unsigned int key32; + unsigned int index; + + key32 = Get32BitCollectionKey(key); + index = key32 & mInstanceHashTableMask; + + for (unsigned int onElem = 0; onElem <= mWorstHashCollision; onElem++) { + HashEntry *entry = mKeyToInstanceMap + index; + + if (entry->mKey32 == key32) { + return entry->mInstance; + } + + index = (index + 1) & mInstanceHashTableMask; + } + + return nullptr; +} + +unsigned int GManager::FindUniqueKeyShift(unsigned int *keys, unsigned int numKeys, unsigned int uniqueBits) { + if (numKeys > 1) { + unsigned int shift = 0x20 - uniqueBits; + unsigned int clearMask = 0xFFFFFFFF; + unsigned int compareMask = ((1 << (uniqueBits & 0x1F)) - 1) << (shift & 0x1F); + + while (static_cast(shift) > -1) { + for (unsigned int i = 0; i < numKeys; ++i) { + keys[i] &= clearMask; + } + + std::sort(keys, keys + numKeys); + + { + bool foundDuplicate = false; + + for (unsigned int i = 1; i < numKeys; ++i) { + if ((keys[i] & compareMask) == (keys[i - 1] & compareMask)) { + foundDuplicate = true; + break; + } + } + + if (!foundDuplicate) { + return shift; + } + } + + clearMask >>= 1; + compareMask >>= 1; + shift--; + } + } + + return 0; +} + +void GManager::FindKeyReductionShifts() { + unsigned int numCollections; + unsigned int numDefinitions; + unsigned int numKeys; + unsigned int *keys; + unsigned int *out; + unsigned int collectionKey; + Attrib::Key definitionKey; + + numCollections = mGameplayClass->GetNumCollections(); + numDefinitions = mGameplayClass->GetNumDefinitions(); + numKeys = numCollections; + if (numKeys < numDefinitions + numCollections) { + numKeys = numDefinitions + numCollections; + } + + keys = new unsigned int[numKeys]; + out = keys; + + for (definitionKey = mGameplayClass->GetFirstDefinition(); definitionKey != 0; definitionKey = mGameplayClass->GetNextDefinition(definitionKey)) { + *out++ = definitionKey; + } + + for (collectionKey = mGameplayClass->GetFirstCollection(); collectionKey != 0; collectionKey = mGameplayClass->GetNextCollection(collectionKey)) { + Attrib::Gen::gameplay gameplay(collectionKey, 0, nullptr); + *out++ = GetStrippedNameKey(gameplay.CollectionName()); + } + + std::sort(keys, out); + out = std::unique(keys, out); + mAttributeKeyShiftTo24 = FindUniqueKeyShift(keys, out - keys, 0x18); + delete[] keys; +} + +void GManager::ConnectInstanceReferences(GRuntimeInstance *runtimeInstance, const Attrib::Gen::gameplay &collection) { + Attrib::AttributeIterator iter = collection.Iterator(); + + while (iter.Valid()) { + unsigned int attributeKey = iter.GetKey(); + { + Attrib::Attribute attribute = collection.Get(attributeKey); + + if (attributeKey != 0x916E0E78) { + if (attribute.IsValid()) { + const Attrib::TypeDesc &typeDesc = Attrib::Database::Get().GetTypeDesc(attribute.GetType()); + const char *typeName = + *reinterpret_cast(reinterpret_cast(&typeDesc) + sizeof(unsigned int)); + + if (bStrCmp(typeName, "GCollectionKey") == 0) { + const Attrib::Definition *definition = mGameplayClass->GetDefinition(attribute.GetKey()); + int numConnections = definition->GetFlag(1) ? static_cast(attribute.GetLength()) : 1; + + for (int i = 0; i < numConnections; ++i) { + const GCollectionKey *connectedKey = + reinterpret_cast(collection.GetAttributePointer(attributeKey, i)); + + if (connectedKey) { + GRuntimeInstance *connected = FindInstance(connectedKey->GetCollectionKey()); + + if (connected) { + runtimeInstance->ConnectToInstance(attributeKey, i, connected); + } + } + } + } + } + } + } + iter.Advance(); + } +} + +void GManager::ConnectRuntimeInstances() { + for (unsigned int onEntry = 0; onEntry < mInstanceHashTableSize; ++onEntry) { + GRuntimeInstance *targetInstance = mKeyToInstanceMap[onEntry].mInstance; + unsigned int collectionKey; + + if (targetInstance) { + targetInstance->ResetConnections(); + ConnectInstanceReferences(targetInstance, *targetInstance); + + collectionKey = targetInstance->GetParent(); + while (collectionKey) { + Attrib::Gen::gameplay collection(collectionKey, 0, nullptr); + + ConnectInstanceReferences(targetInstance, collection); + collectionKey = collection.GetParent(); + } + + ConnectChildren(targetInstance); + targetInstance->LockConnections(); + } + } +} + +void GManager::ConnectChildren(GRuntimeInstance *runtimeInstance) { + for (unsigned int i = 0; i < runtimeInstance->Num_Children(); ++i) { + GRuntimeInstance *child = FindInstance(runtimeInstance->Children(i).GetCollectionKey()); + + if (child) { + runtimeInstance->ConnectToInstance(GetStrippedNameKey(*reinterpret_cast(child->GetLayoutPointer())), 0, child); + } + } +} + +void GManager::TrackValue(const char *valueName, float value) { + unsigned int key; + MilestoneInfoMap::iterator iterMile; + bool setBestValue; + + key = Attrib::StringToKey(valueName); + iterMile = mMilestoneTypeInfo.find(key); + MilestoneTypeInfo &info = iterMile->second; + info.mLastKnownValue = value; + setBestValue = true; + + if (info.mBestValue != -1.0f) { + if ((info.mFlags & GMilestone::kFlag_BiggerIsBetter) != 0) { + setBestValue = info.mBestValue < value; + } else { + setBestValue = value < info.mBestValue; + } + } + + if (setBestValue) { + info.mBestValue = value; + + MNotifyMilestoneProgress message(valueName, value); + message.Post(UCrc32(0x20D60DBF)); + + if (GRaceStatus::Exists() && GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming) { + for (GMilestone *availMile = GetFirstMilestone(true, 0); availMile; + availMile = GetNextMilestone(availMile, true, 0)) { + if (availMile->GetTypeKey() == info.mTypeKey) { + availMile->NotifyProgress(value); + } + } + } + } +} + +void GManager::IncValue(const char *valueName) { + unsigned int key = Attrib::StringToKey(valueName); + MilestoneInfoMap::iterator iterMile = mMilestoneTypeInfo.find(key); + if (iterMile->second.mLastKnownValue == -1.0f) { + TrackValue(valueName, 1.0f); + } else { + TrackValue(valueName, iterMile->second.mLastKnownValue + 1.0f); + } +} + +float GManager::GetValue(const char *valueName) { + return GetValue(Attrib::StringToKey(valueName)); +} + +float GManager::GetValue(unsigned int valueKey) { + MilestoneInfoMap::iterator it; + + it = mMilestoneTypeInfo.find(valueKey); + if (it == mMilestoneTypeInfo.end()) { + return -1.0f; + } + + return it->second.mLastKnownValue; +} + +float GManager::GetBestValue(unsigned int valueKey) { + return mMilestoneTypeInfo.find(valueKey)->second.mBestValue; +} + +bool GManager::GetIsBiggerValueBetter(unsigned int valueKey) { + MilestoneInfoMap::iterator it; + + it = mMilestoneTypeInfo.find(valueKey); + if (it == mMilestoneTypeInfo.end()) { + return false; + } + + return (it->second.mFlags & GMilestone::kFlag_BiggerIsBetter) != 0; +} + +void GManager::DestroyVaults() { + if (mVaults) { + for (unsigned int i = 0; i < mVaultCount; ++i) { + mVaults[i].~GVault(); + } + + mVaultCount = 0; + bFree(mVaults); + mVaults = nullptr; + } + + if (mVaultNameStrings) { + delete[] const_cast(mVaultNameStrings); + mVaultNameStrings = nullptr; + } +} + +void GManager::ReleaseMilestones() { + if (mMilestones) { + delete[] mMilestones; + } + + mMilestones = nullptr; + mNumMilestones = 0; +} + +void GManager::AllocateMilestones() { + Attrib::Gen::gameplay milestoneRoot(0x1D975142, 0, nullptr); + AttribKeyList keys; + GMilestone *milestone; + + GatherInstanceKeys(milestoneRoot, keys, 0xA3D34781); + + mNumMilestones = keys.size(); + mMilestones = new GMilestone[mNumMilestones]; + milestone = mMilestones; + + for (AttribKeyList::iterator iter = keys.begin(); iter != keys.end(); ++iter) { + milestone->Init(*iter); + ++milestone; + } +} + +void GManager::ResetMilestoneTrackingInfo() { + unsigned int collectionKey; + + mMilestoneTypeInfo.clear(); + collectionKey = mMilestoneClass->GetFirstCollection(); + while (collectionKey) { + Attrib::Gen::milestonetypes milestoneType( + Attrib::FindCollection(Attrib::Gen::milestonetypes::ClassKey(), collectionKey), 0, nullptr); + MilestoneTypeInfo info; + + info.mTypeKey = collectionKey; + info.mLastKnownValue = -1.0f; + info.mBestValue = -1.0f; + info.mFlags = milestoneType.MilestoneType() != 2 ? GMilestone::kFlag_BiggerIsBetter : 0; + if (milestoneType.ResetWhenPursuitStarts()) { + info.mFlags |= GMilestone::kFlag_CompletionFaked; + } + + mMilestoneTypeInfo[collectionKey] = info; + collectionKey = mMilestoneClass->GetNextCollection(collectionKey); + } +} + +void GManager::LoadMilestoneInfo(MilestoneTypeInfo *savedInfo, unsigned int count) { + unsigned int onMilestone; + + ResetMilestoneTrackingInfo(); + for (onMilestone = 0; onMilestone < count; ++onMilestone) { + MilestoneTypeInfo &saved = savedInfo[onMilestone]; + MilestoneInfoMap::iterator iterCurrent; + + iterCurrent = mMilestoneTypeInfo.find(saved.mTypeKey); + if (iterCurrent != mMilestoneTypeInfo.end()) { + iterCurrent->second.mBestValue = saved.mBestValue; + iterCurrent->second.mLastKnownValue = saved.mLastKnownValue; + } + } +} + +ObjectStateBlockHeader *GManager::AllocObjectStateBlock(unsigned int key, unsigned int size, bool persistent) { + ObjectStateMap &blocks = persistent ? mPersistentStateBlocks : mSessionStateBlocks; + unsigned int shiftedKey = key >> mCollectionKeyShiftTo32; + ObjectStateMap::iterator it = blocks.find(shiftedKey); + unsigned int blockSize; + unsigned int tryCount; + ObjectStateBlockHeader *block; + ObjectStateBlockHeader *userData; + + if (it != blocks.end()) { + block = it->second; + if (size <= block->mSize) { + block->mSize = size; + return block + 1; + } + + ClearObjectStateBlock(key); + } + + blockSize = (size + 0x17U) & ~0xFU; + for (tryCount = 0; tryCount < 2; ++tryCount) { + if (mObjectStateBufferSize >= static_cast(mObjectStateBufferFree - mObjectStateBuffer) + blockSize) { + break; + } + + if (tryCount != 0) { + return nullptr; + } + + DefragObjectStateStorage(); + } + + block = reinterpret_cast(mObjectStateBufferFree); + mObjectStateBufferFree += blockSize; + if (persistent) { + bMemSet(block, 0, blockSize); + } + + block->mKey = key; + block->mSize = size; + userData = block + 1; + blocks[shiftedKey] = block; + return userData; +} + +void *GManager::GetObjectStateBlock(unsigned int key) { + unsigned int persistent = 0; + unsigned int shiftedKey = key >> mCollectionKeyShiftTo32; + + do { + ObjectStateMap &stateMap = persistent ? mPersistentStateBlocks : mSessionStateBlocks; + ObjectStateMap::iterator existing = stateMap.find(shiftedKey); + + if (existing != stateMap.end()) { + return existing->second + 1; + } + + persistent++; + } while (persistent <= 1); + + return nullptr; +} + +void GManager::ClearObjectStateBlock(unsigned int collectionKey) { + unsigned int key32 = collectionKey >> mCollectionKeyShiftTo32; + unsigned int persistent = 0; + + do { + ObjectStateMap &stateMap = persistent ? mPersistentStateBlocks : mSessionStateBlocks; + ObjectStateMap::iterator existing = stateMap.find(key32); + + if (existing != stateMap.end()) { + stateMap.erase(existing); + } + + persistent++; + } while (persistent <= 1); +} + +unsigned int GManager::SaveMilestoneInfo(MilestoneTypeInfo *dest) { + MilestoneInfoMap::iterator it; + + for (it = mMilestoneTypeInfo.begin(); it != mMilestoneTypeInfo.end(); ++it, ++dest) { + *dest = it->second; + } + + return mMilestoneTypeInfo.size(); +} + +void GManager::ResetMilestones() { + if (!mNumMilestones) { + return; + } + + Attrib::Gen::gameplay milestoneRoot(0x1D975142, 0, nullptr); + AttribKeyList keys; + GMilestone *milestone; + + GatherInstanceKeys(milestoneRoot, keys, 0xA3D34781); + + milestone = mMilestones; + for (AttribKeyList::iterator iter = keys.begin(); iter != keys.end(); ++iter) { + milestone->Init(*iter); + milestone++; + } +} + +void GManager::ReleaseSpeedTraps() { + if (mSpeedTraps) { + delete[] mSpeedTraps; + } + + mSpeedTraps = nullptr; + mNumSpeedTraps = 0; +} + +void GManager::AllocateIcons() { + mNumIcons = 0; + mNumVisibleIcons = 0; + mIcons = new GIcon *[200]; +} + +void GManager::ReleaseIcons() { + if (mIcons) { + delete[] mIcons; + } + + mNumIcons = 0; + mNumVisibleIcons = 0; + mIcons = nullptr; +} + +void GManager::AllocateSpeedTraps() { + Attrib::Gen::gameplay speedTrapRoot(0x49511906, 0, nullptr); + AttribKeyList keys; + GSpeedTrap *speedTrap; + + GatherInstanceKeys(speedTrapRoot, keys, 0xB05871D3); + + mNumSpeedTraps = keys.size(); + mSpeedTraps = new GSpeedTrap[mNumSpeedTraps]; + speedTrap = mSpeedTraps; + + for (AttribKeyList::iterator iter = keys.begin(); iter != keys.end(); ++iter) { + speedTrap->Init(*iter); + ++speedTrap; + } +} + +void GManager::ResetSpeedTraps() { + if (!mNumSpeedTraps) { + return; + } + + Attrib::Gen::gameplay speedTrapRoot(0x49511906, 0, nullptr); + AttribKeyList keys; + GSpeedTrap *speedTrap; + + GatherInstanceKeys(speedTrapRoot, keys, 0xB05871D3); + + speedTrap = mSpeedTraps; + for (AttribKeyList::iterator iter = keys.begin(); iter != keys.end(); ++iter) { + speedTrap->Init(*iter); + ++speedTrap; + } +} + +GSpeedTrap *GManager::GetFirstSpeedTrap(bool activeOnly, unsigned int binNumber) { + return GetNextSpeedTrap(mSpeedTraps - 1, activeOnly, binNumber); +} + +void GManager::EnableBinSpeedTraps(unsigned int binNumber) { + for (GSpeedTrap *speedTrap = GetFirstSpeedTrap(false, binNumber); speedTrap; + speedTrap = GetNextSpeedTrap(speedTrap, false, binNumber)) { + speedTrap->Unlock(); + speedTrap->Activate(); + } +} + +void GManager::EnableBinMilestones(unsigned int binNumber) { + for (GMilestone *milestone = GetFirstMilestone(false, binNumber); milestone; + milestone = GetNextMilestone(milestone, false, binNumber)) { + milestone->Unlock(); + } +} + +void GManager::NotifyPursuitStarted() { + for (MilestoneInfoMap::iterator iterMile = mMilestoneTypeInfo.begin(); iterMile != mMilestoneTypeInfo.end(); ++iterMile) { + MilestoneTypeInfo &info = iterMile->second; + + if ((info.mFlags & GMilestone::kFlag_CompletionFaked) != 0) { + info.mBestValue = -1.0f; + info.mLastKnownValue = -1.0f; + } + } +} + +void GManager::NotifyCollisionPackLoaded(int sectionID, bool loaded) { + if (mObj) { + if (loaded) { + mObj->SpawnSectionIcons(sectionID); + } else { + mObj->UnspawnSectionIcons(sectionID); + } + } +} + +void GManager::SpawnSectionIcons(int section) { + for (unsigned int i = 0; i < mNumIcons; ++i) { + GIcon *icon = mIcons[i]; + + if (icon->GetSectionID() < 0) { + icon->FindSection(); + } + + if (icon->GetSectionID() == section || icon->GetCombinedSectionID() == section) { + icon->Spawn(); + } + } +} + +void GManager::UnspawnSectionIcons(int section) { + for (unsigned int i = 0; i < mNumIcons; ++i) { + GIcon *icon = mIcons[i]; + + if (icon->GetSectionID() < 0) { + icon->FindSection(); + } + + if (icon->GetSectionID() == section || icon->GetCombinedSectionID() == section) { + icon->Unspawn(); + } + } +} + +void GManager::UnspawnAllIcons() { + for (unsigned int i = 0; i < mNumIcons; ++i) { + mIcons[i]->Unspawn(); + } +} + +bool GManager::GetIsIconVisible(GIcon *icon) { + for (unsigned int i = 0; i < mNumVisibleIcons; ++i) { + if (mIcons[i] == icon) { + return true; + } + } + + return false; +} + +void GManager::SpawnAllLoadedSectionIcons() { + for (unsigned int i = 0; i < mNumIcons; ++i) { + GIcon *icon = mIcons[i]; + + icon->FindSection(); + if (icon->GetSectionID() >= 0) { + if (TheTrackStreamer.IsSectionVisible(icon->GetSectionID()) || + TheTrackStreamer.IsSectionVisible(icon->GetCombinedSectionID())) { + icon->Spawn(); + } + } + } +} + +void GManager::RestorePursuitBreakerIcons(int sectionID) { + for (unsigned int i = 0; i < mNumIcons; ++i) { + GIcon *icon = mIcons[i]; + + if (icon->GetType() == GIcon::kType_PursuitBreaker && + (sectionID == -1 || sectionID == icon->GetSectionID() || sectionID == icon->GetCombinedSectionID())) { + icon->SetFlag(1); + } + } +} + +void GManager::HidePursuitBreakerIcon(const UMath::Vector3 &pos, float radius) { + UMath::Vector3 swizzledPos = UMath::Vector3Make(pos.z, -pos.x, pos.y); + + for (unsigned int i = 0; i < mNumIcons; ++i) { + GIcon *icon = mIcons[i]; + + if (icon->GetType() == GIcon::kType_PursuitBreaker && + UMath::DistanceSquare(swizzledPos, icon->GetPosition()) <= radius * radius) { + icon->ClearFlag(1); + } + } +} + +void GManager::FreeDisposableIcons(GIcon::Type iconType) { + for (unsigned int i = 0; i < mNumIcons;) { + GIcon *icon = mIcons[i]; + + if (icon->GetIsDisposable() && icon->GetType() == iconType) { + FreeIconAt(i); + } else { + ++i; + } + } +} + +void GManager::FreeIcon(GIcon *icon) { + for (unsigned int i = 0; i < mNumIcons; ++i) { + if (mIcons[i] == icon) { + FreeIconAt(i); + return; + } + } +} + +void GManager::FreeAllIcons() { + while (mNumIcons != 0) { + FreeIconAt(0); + } +} + +unsigned int GManager::GetBountySpawnMarker(unsigned int index) const { + if (index >= mNumBountySpawnPoints) { + return 0; + } + + return mBountySpawnPoint[index]; +} + +int GManager::GetBountySpawnMarkerTag(unsigned int index) const { + Attrib::Gen::gameplay gameplay(Attrib::FindCollection(Attrib::Gen::gameplay::ClassKey(), GetBountySpawnMarker(index)), 0, nullptr); + + return gameplay.LocalizationTag(0); +} + +void GManager::RefreshZoneIcons() { + FreeDisposableIcons(GIcon::kType_HidingSpot); + + for (TrackPathZone *zone = TheTrackPathManager.FindZone(nullptr, TRACK_PATH_ZONE_HIDDEN, nullptr); zone; + zone = TheTrackPathManager.FindZone(nullptr, TRACK_PATH_ZONE_HIDDEN, zone)) { + UMath::Vector3 pos; + + pos.x = zone->Position.x; + pos.y = zone->Position.y; + pos.z = zone->Elevation; + + GIcon *icon = AllocIcon(GIcon::kType_HidingSpot, pos, 0.0f, true); + if (icon) { + icon->Show(); + icon->ShowOnMap(); + } + } + + if (GetInGameplay()) { + SpawnAllLoadedSectionIcons(); + } +} + +void GManager::RefreshWorldParticleEffects() { + for (unsigned int i = 0; i < mInstanceHashTableSize; ++i) { + GRuntimeInstance *instance = mKeyToInstanceMap[i].mInstance; + + if (instance && instance->GetType() == kGameplayObjType_Trigger) { + static_cast(instance)->RefreshParticleEffects(); + } + } + + for (unsigned int i = 0; i < mNumIcons; ++i) { + mIcons[i]->RefreshEffects(); + } +} + +void GManager::RefreshEngageTriggerIcons() { + GObjectIterator iter(0x200); + + while (iter.IsValid()) { + GTrigger *trigger = iter.GetInstance(); + GActivity *activity = trigger->GetTargetActivity(); + + if (activity) { + GRaceParameters *raceParms = GRaceDatabase::Get().GetRaceFromActivity(activity); + bool complete = GRaceDatabase::Get().IsCareerRaceComplete(raceParms->GetEventHash()); + bool unlocked = GRaceDatabase::Get().IsCareerRaceUnlocked(raceParms->GetEventHash()); + + if (unlocked && !complete) { + trigger->ShowIcon(); + } else { + trigger->HideIcon(); + } + } + + iter.Advance(); + } +} + +GIcon *GManager::AllocIcon(GIcon::Type iconType, const UMath::Vector3 &pos, float rotDeg, bool disposable) { + GIcon *icon = nullptr; + + if (mNumIcons < 200) { + icon = new GIcon(iconType, pos, rotDeg); + if (icon) { + mIcons[mNumIcons++] = icon; + if (disposable) { + icon->MarkDisposable(); + } + } + } + + return icon; +} + +void GManager::FreeIconAt(unsigned int index) { + if (mNumIcons == 0) { + return; + } + + GIcon *icon = mIcons[index]; + if (icon) { + icon->ClearGPSing(); + delete icon; + mIcons[index] = nullptr; + } + + if (index + 1 < mNumIcons) { + if (index + 1 < mNumVisibleIcons) { + mIcons[index] = mIcons[mNumVisibleIcons - 1]; + if (mNumVisibleIcons < mNumIcons) { + mIcons[mNumVisibleIcons - 1] = mIcons[mNumIcons - 1]; + } + } else { + mIcons[index] = mIcons[mNumIcons - 1]; + } + } + + if (index < mNumVisibleIcons) { + mNumVisibleIcons--; + } + + mNumIcons--; +} + +int GManager::GatherVisibleIcons(GIcon **iconArray, IPlayer *player) { + struct IconSort { + GIcon *mIcon; + int mDist; + + static int Compare(const void *lhs, const void *rhs) { + return reinterpret_cast(lhs)->mDist - reinterpret_cast(rhs)->mDist; + } + }; + + UMath::Vector3 playerPos = UMath::Vector3::kZero; + IconSort iconSort[200]; + ISimable *simable = nullptr; + + if (player) { + simable = player->GetSimable(); + } + + if (simable) { + const UMath::Vector3 &pos = simable->GetPosition(); + + playerPos.x = pos.z; + playerPos.y = -pos.x; + playerPos.z = pos.y; + } + + int count = 0; + for (unsigned int i = 0; i < mNumVisibleIcons; i++) { + GIcon *icon = mIcons[i]; + bool show = false; + + if (icon->IsFlagSet(1)) { + if (icon->IsFlagSet(2)) { + show = true; + } + } + + if (show) { + iconSort[count].mIcon = icon; + if (!simable) { + iconSort[count].mDist = 0; + } else { + iconSort[count].mDist = static_cast(VU0_v3distancesquarexz(icon->GetPosition(), playerPos)); + } + count++; + } + } + + if (simable) { + qsort(iconSort, count, sizeof(IconSort), IconSort::Compare); + } + + for (int i = 0; i < count; i++) { + iconArray[i] = iconSort[i].mIcon; + } + + return count; +} + +bool GManager::AddIconForTrackMarker(TrackPositionMarker *marker, unsigned int tag) { + if (marker->NameHash == tag) { + UMath::Vector3 pos; + + pos.x = marker->Position.x; + pos.y = marker->Position.y; + pos.z = marker->Position.z; + + GIcon *icon = GManager::Get().AllocIcon(GIcon::kType_PursuitBreaker, pos, 0.0f, true); + if (icon) { + icon->Show(); + icon->ShowOnMap(); + } + } + + return true; +} + +void GManager::RefreshTrackMarkerIcons() { + FreeDisposableIcons(GIcon::kType_PursuitBreaker); + ForEachTrackPositionMarker(AddIconForTrackMarker, bStringHash("IconMarker")); + + if (GetInGameplay()) { + SpawnAllLoadedSectionIcons(); + } +} + +void GManager::FindBountySpawnPoints() { + Attrib::Gen::gameplay jumpRoot(0x3D48E303, 0, nullptr); + AttribKeyList keys; + + GatherInstanceKeys(jumpRoot, keys, 0xA7BCCF63); + mNumBountySpawnPoints = 0; + + for (AttribKeyList::iterator iter = keys.begin(); iter != keys.end(); ++iter) { + mBountySpawnPoint[mNumBountySpawnPoints++] = *iter; + if (mNumBountySpawnPoints > 0x13) { + break; + } + } +} + +void GManager::RefreshSpeedTrapIcons() { + for (GSpeedTrap *speedTrap = GetFirstSpeedTrap(false, 0); speedTrap; + speedTrap = GetNextSpeedTrap(speedTrap, false, 0)) { + GTrigger *trigger = speedTrap->GetTrapTrigger(); + + if (trigger) { + if (!speedTrap->IsFlagSet(GSpeedTrap::kFlag_Active) || speedTrap->IsFlagSet(GSpeedTrap::kFlag_Completed)) { + trigger->HideIcon(); + } else { + trigger->ShowIcon(); + } + } + } +} + +void NotifyGameZonesChanged() { + if (GManager::Exists()) { + GManager::Get().RefreshZoneIcons(); + } +} + +void NotifyTrackMarkersChanged() { + if (GManager::Exists()) { + GManager::Get().RefreshTrackMarkerIcons(); + } +} + +void GManager::NotifyPursuitEnded(bool evaded) { + for (GMilestone *availMile = GetFirstMilestone(true, 0); availMile; + availMile = GetNextMilestone(availMile, true, 0)) { + availMile->NotifyPursuitOver(evaded); + } + + if (evaded && GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming) { + new EAutoSave(); + } +} + +void GManager::GatherInstanceKeys(Attrib::Gen::gameplay &collection, AttribKeyList &list, unsigned int templateKey) { + Attrib::Key parentKey; + unsigned int i; + + parentKey = collection.GetParent(); + while (parentKey != 0) { + if (parentKey == templateKey) { + list.push_back(collection.GetCollection()); + break; + } + + { + Attrib::Gen::gameplay parent(Attrib::FindCollection(Attrib::Gen::gameplay::ClassKey(), parentKey), 0, nullptr); + parentKey = parent.GetParent(); + } + } + + for (i = 0; i < collection.Num_Children(); ++i) { + Attrib::Gen::gameplay child(Attrib::FindCollection(Attrib::Gen::gameplay::ClassKey(), collection.Children(i).GetCollectionKey()), 0, nullptr); + GatherInstanceKeys(child, list, templateKey); + } +} + +void GManager::RecursivePreloadCharacterCars(GRuntimeInstance *instance, bool forcePreload) { + if (instance->GetType() == kGameplayObjType_Character) { + const char *const *carNamePtr = reinterpret_cast(instance->GetAttributePointer(0xF833C06F, 0)); + const char *const *stockCarNamePtr; + const int *preloadPtr; + const char *carName; + const char *stockCarName; + + if (!carNamePtr) { + carNamePtr = reinterpret_cast(Attrib::DefaultDataArea(sizeof(const char *))); + } + + carName = *carNamePtr; + stockCarNamePtr = reinterpret_cast(instance->GetAttributePointer(0xFD3CF790, 0)); + if (!stockCarNamePtr) { + stockCarNamePtr = reinterpret_cast(Attrib::DefaultDataArea(sizeof(const char *))); + } + + stockCarName = *stockCarNamePtr; + if (stockCarName && *stockCarName) { + carName = stockCarName; + } + + preloadPtr = reinterpret_cast(instance->GetAttributePointer(0x9652AF0F, 0)); + if (!preloadPtr) { + preloadPtr = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + if (*preloadPtr != 0 || forcePreload) { + ReserveStockCar(carName); + } + } + + unsigned int i = 0; + + while (true) { + Attrib::Attribute children = instance->Get(0x916E0E78); + unsigned int numChildren = children.GetLength(); + + children.~Attribute(); + if (numChildren <= i) { + break; + } + + const unsigned int *childKey = reinterpret_cast(instance->GetAttributePointer(0x916E0E78, i)); + + if (!childKey) { + childKey = reinterpret_cast(Attrib::DefaultDataArea(sizeof(unsigned int))); + } + + GRuntimeInstance *child = FindInstance(*childKey); + if (child) { + RecursivePreloadCharacterCars(child, forcePreload); + } + + i += 1; + } +} + +void GManager::PreloadStockCarsForActivity(GActivity *activity) { + GRaceParameters *raceParms; + bool forcePreload; + + ClearStockCars(); + if (activity) { + forcePreload = false; + raceParms = GRaceDatabase::Get().GetRaceFromActivity(activity); + + if (raceParms) { + forcePreload = raceParms->GetRaceType() == GRace::kRaceType_Drag; + } + + if (gVerboseTesterOutput && raceParms) { + raceParms->GetEventID(); + } + + RecursivePreloadCharacterCars(activity, forcePreload); + } +} + +void GManager::ClearStockCars() { + for (StockCarMap::iterator iter = mStockCars.begin(); iter != mStockCars.end(); ++iter) { + ISimable *simable = iter->second; + + simable->Kill(); + } + + mStockCars.clear(); +} + +void GManager::ReserveStockCar(const char *carName) { + unsigned int carHash; + StockCarMap::iterator iterExisting; + + if (!carName || !*carName) { + return; + } + + carHash = Attrib::StringHash32(carName); + iterExisting = mStockCars.find(carHash); + if (iterExisting == mStockCars.end()) { + UMath::Vector3 pos = UMath::Vector3::kZero; + UMath::Vector3 dir = UMath::Vector3::kZero; + IVehicleCache *cache = static_cast(this); + VehicleParams params(cache, DRIVER_TRAFFIC, Attrib::StringToKey(carName), dir, pos, 0, nullptr, nullptr); + ISimable *simable = UTL::COM::Factory::CreateInstance("PVehicle", params); + + if (simable) { + IVehicle *vehicle; + + if (simable->QueryInterface(&vehicle)) { + vehicle->Deactivate(); + } + + mStockCars[carHash] = simable; + } + } +} + +bool GManager::StockCarsLoaded() { + { + StockCarMap::iterator iter = mStockCars.begin(); + + for (; iter != mStockCars.end(); ++iter) { + ISimable *simable = iter->second; + IVehicle *vehicle; + + if (simable->QueryInterface(&vehicle) && vehicle->IsLoading()) { + return false; + } + } + } + + return true; +} + +ISimable *GManager::GetStockCar(const char *carName) { + StockCarMap::iterator it; + ISimable *stockCar; + + it = mStockCars.find(Attrib::StringHash32(carName)); + if (it == mStockCars.end()) { + return nullptr; + } + + stockCar = it->second; + mStockCars.erase(it); + return stockCar; +} + +ISimable *GManager::GetRandomEmergencyStockCar() { + typedef UTL::Std::vector CandidateCars; + + GRaceParameters *parms = GRaceStatus::Get().GetRaceParameters(); + + if (!parms || parms->GetRaceType() == 2) { + CandidateCars validCars; + int numValid; + + validCars.reserve(mStockCars.size()); + for (StockCarMap::iterator it = mStockCars.begin(); it != mStockCars.end(); ++it) { + if (!UTL::COM::QueryInterface(it->second)) { + validCars.push_back(it); + } + } + + numValid = static_cast(validCars.size()); + if (numValid > 0) { + int index = bRandom(numValid); + ISimable *simable = validCars[index]->second; + + mStockCars.erase(validCars[index]); + return simable; + } + } + + return nullptr; +} + +void GManager::ReleaseStockCar(ISimable *stockCar) { + const Attrib::Instance &carAttrib = stockCar->GetAttributes(); + unsigned int carHash; + + carHash = carAttrib.GetCollection(); + StockCarMap::iterator iterExisting = mStockCars.find(carHash); + mStockCars[carHash] = stockCar; +} + +void GManager::AttachCharacter(GCharacter *character) { + if (std::find(mActiveCharacters.begin(), mActiveCharacters.end(), character) == mActiveCharacters.end()) { + mActiveCharacters.push_back(character); + } +} + +void GManager::DetachCharacter(GCharacter *character) { + GCharacterList::iterator it = std::find(mActiveCharacters.begin(), mActiveCharacters.end(), character); + + if (it != mActiveCharacters.end()) { + mActiveCharacters.erase(it); + } +} + +void GManager::UnspawnAllCharacters() { + while (!mActiveCharacters.empty()) { + mActiveCharacters.front()->Unspawn(); + } +} + +bool GManager::GetHasPendingSMS() const { + return !mPendingSMS.empty(); +} + +unsigned int GManager::SaveSMSInfo(int *saveInfo) { + PendingSMSList::const_iterator it; + unsigned int count; + + count = 0; + for (it = mPendingSMS.begin(); it != mPendingSMS.end(); ++it) { + *saveInfo++ = *it; + count++; + } + + return count; +} + +void GManager::LoadSMSInfo(int *loadInfo, unsigned int count) { + unsigned int i; + + mPendingSMS.clear(); + for (i = 0; i < count; ++i) { + mPendingSMS.push_back(loadInfo[i]); + } +} + +bool GManager::CanPlaySMS() const { + IPlayer *player; + + if (Speech::Manager::m_speechDisable != 0 || TheGameFlowManager.GetState() != GAMEFLOW_STATE_RACING || + UTL::Collections::Singleton::Get() != nullptr || PhotoFinishScreen::mActive != 0 || + !GRaceStatus::Exists() || GRaceStatus::Get().GetPlayMode() != GRaceStatus::kPlayMode_Roaming) { + return false; + } + + if (!FEManager::IsOkayToRequestPauseSimulation(0, true, true)) { + return false; + } + + if (Speech::Manager::IsCopSpeechPlaying(0xCC)) { + return false; + } + + player = IPlayer::First(PLAYER_LOCAL); + if (player) { + ISimable *simable = player->GetSimable(); + if (simable) { + IHud *ihud = player->GetHud(); + if (ihud && ihud->IsHudVisible()) { + IVehicle *ivehicle; + + if (simable->QueryInterface(&ivehicle) && ivehicle) { + if (!ivehicle->IsAnimating() && !ivehicle->IsStaging() && !ivehicle->IsLoading()) { + IVehicleAI *ivehicleai = ivehicle->GetAIVehiclePtr(); + if (ivehicleai) { + return !ivehicleai->GetPursuit(); + } + } + } + } + } + } + + return false; +} + +void GManager::DispatchSMSMessage(int smsID) { + mPendingSMS.push_back(smsID); +} + +void GManager::AddSMS(int smsID) { + PendingSMSList::iterator it; + + if ((smsID - 10U < 5) || smsID == 0x5F || smsID == 0x60) { + DispatchSMSMessage(smsID); + return; + } + + for (it = mPendingSMS.begin(); it != mPendingSMS.end(); ++it) { + if (*it == smsID) { + return; + } + } + + mPendingSMS.push_back(smsID); +} + +int GManager::PushSMSToInbox() { + CareerSettings *careerSettings; + PendingSMSList::iterator it; + int smsID; + + smsID = -1; + careerSettings = FEDatabase->GetCareerSettings(); + for (it = mPendingSMS.begin(); it != mPendingSMS.end(); ++it) { + SMSMessage *smsMessage = careerSettings->GetSMSMessage(*it); + + if (smsMessage) { + if (smsMessage->IsVoice() && smsID == -1) { + smsID = *it; + } + reinterpret_cast(smsMessage)[1] = 2; + *reinterpret_cast(reinterpret_cast(smsMessage) + 2) = careerSettings->GetSMSSortOrder(); + } + } + + if (smsID == -1) { + int numSMS = 0; + + for (it = mPendingSMS.begin(); it != mPendingSMS.end(); ++it) { + ++numSMS; + } + if (numSMS != 0) { + smsID = mPendingSMS.front(); + } + } + + mPendingSMS.clear(); + return smsID; +} + +void GManager::UpdatePendingSMS() { + if (mPendingSMS.size() && CanPlaySMS()) { + int smsToShow = PushSMSToInbox(); + if (smsToShow != -1) { + new EShowSMS(smsToShow); + } + + IPlayer *player = IPlayer::First(PLAYER_LOCAL); + if (player) { + IHud *ihud = player->GetHud(); + if (ihud) { + IMenuZoneTrigger *izone; + if (ihud->QueryInterface(&izone)) { + if (izone) { + izone->ExitTrigger(); + } + } + } + } + } +} + +bool GManager::SaveGameplayData(unsigned char *dest, unsigned int maxSize) { + unsigned char *destStart = dest; + SavedGameplayDataHeader *gameplayHeader; + unsigned int spotBytes; + unsigned int respawnMarker; + unsigned int respawnSafeHouseMarker; + unsigned char *startChecksum; + unsigned int bytesToChecksum; + + bMemSet(destStart, 0, maxSize); + + { + GObjectIterator iter(0xFFFFFFFF); + + while (iter.IsValid()) { + GActivity *activity = iter.GetInstance(); + + activity->SerializeVars(false); + iter.Advance(); + } + } + + if (maxSize < 0x80) { + return false; + } + + gameplayHeader = reinterpret_cast(destStart); + gameplayHeader->mVersion = 8; + gameplayHeader->mMagic = 0x656D6147; + gameplayHeader->mNumPersistent = mPersistentStateBlocks.size(); + + startChecksum = destStart + 0x80; + { + ObjectStateMap::iterator iter = mPersistentStateBlocks.begin(); + + while (iter != mPersistentStateBlocks.end()) { + ObjectStateBlockHeader *header = iter->second; + unsigned int allocSize = (header->mSize + 0x17U) & ~0xFU; + + if (startChecksum + allocSize > destStart + maxSize) { + return false; + } + + bMemCpy(startChecksum, header, allocSize); + startChecksum += allocSize; + ++iter; + } + } + + AlignPointer(startChecksum, 0x10); + gameplayHeader->mNumSavedTimers = SaveTimerInfo(reinterpret_cast(startChecksum)); + startChecksum += gameplayHeader->mNumSavedTimers * 0x20; + + gameplayHeader->mNumMilestoneTypes = SaveMilestoneInfo(reinterpret_cast(startChecksum)); + startChecksum += gameplayHeader->mNumMilestoneTypes * 0x10; + + gameplayHeader->mNumMilestoneRecords = SaveMilestones(reinterpret_cast(startChecksum)); + startChecksum += gameplayHeader->mNumMilestoneRecords * 0x14; + AlignPointer(startChecksum, 0x10); + + gameplayHeader->mNumSpeedTrapRecords = SaveSpeedTraps(reinterpret_cast(startChecksum)); + startChecksum += gameplayHeader->mNumSpeedTrapRecords * 0x14; + AlignPointer(startChecksum, 0x10); + + gameplayHeader->mNumHidingSpotFlags = 0x200; + spotBytes = sizeof(mHidingSpotFound); + bMemCpy(startChecksum, mHidingSpotFound, spotBytes); + startChecksum += spotBytes; + AlignPointer(startChecksum, 0x10); + + gameplayHeader->mNumBytesBinStats = GRaceDatabase::Get().SerializeBins(startChecksum); + startChecksum += gameplayHeader->mNumBytesBinStats; + AlignPointer(startChecksum, 0x10); + + gameplayHeader->mNumPendingSMS = SaveSMSInfo(reinterpret_cast(startChecksum)); + startChecksum += gameplayHeader->mNumPendingSMS * sizeof(int); + AlignPointer(startChecksum, 0x10); + + respawnMarker = GManager::Get().GetFreeRoamStartMarker(); + bMemCpy(startChecksum, &respawnMarker, sizeof(respawnMarker)); + startChecksum += sizeof(respawnMarker); + AlignPointer(startChecksum, 0x10); + respawnSafeHouseMarker = GManager::Get().GetFreeRoamFromSafeHouseStartMarker(); + bMemCpy(startChecksum, &respawnSafeHouseMarker, sizeof(respawnSafeHouseMarker)); + + MD5 md5; + + startChecksum = destStart + 0x10; + bytesToChecksum = maxSize - static_cast(startChecksum - destStart); + md5.Reset(); + md5.Update(startChecksum, bytesToChecksum); + md5.GetRaw(); + bMemCpy(destStart, md5.GetRaw(), md5.GetRawLength()); + return true; +} + +bool GManager::LoadGameplayData(unsigned char *src, unsigned int maxSize) { + unsigned char *srcStart; + SavedGameplayDataHeader *gameplayHeader; + unsigned char *startChecksum; + unsigned int bytesToChecksum; + unsigned int spotBytes; + unsigned int binBytesRead; + unsigned int respawnMarker; + + if (maxSize < 0x80) { + return false; + } + + gameplayHeader = reinterpret_cast(src); + bytesToChecksum = maxSize - 0x10; + srcStart = reinterpret_cast(gameplayHeader); + startChecksum = srcStart + 0x10; + MD5 md5; + + md5.Reset(); + md5.Update(startChecksum, bytesToChecksum); + md5.GetRaw(); + if (bMemCmp(srcStart, md5.GetRaw(), md5.GetRawLength()) != 0 || gameplayHeader->mMagic != 0x656D6147 || + gameplayHeader->mVersion < 8) { + return false; + } + + ResetAllGameplayData(); + src += 0x80; + for (unsigned int onBlock = 0; onBlock < gameplayHeader->mNumPersistent; ++onBlock) { + ObjectStateBlockHeader *header = reinterpret_cast(src); + unsigned int allocSize = header->mSize; + unsigned int blockBytes = (allocSize + 0x17U) & ~0xFU; + unsigned char *newBlock = reinterpret_cast(AllocObjectStateBlock(header->mKey, allocSize, true)); + + if (newBlock) { + unsigned char *data = reinterpret_cast(header + 1); + bMemCpy(newBlock, data, header->mSize); + } + + src += blockBytes; + } + + src = reinterpret_cast((reinterpret_cast(src) + 0xFU) & ~0xFU); + LoadTimerInfo(reinterpret_cast(src), gameplayHeader->mNumSavedTimers); + src += gameplayHeader->mNumSavedTimers * 0x20; + + LoadMilestoneInfo(reinterpret_cast(src), gameplayHeader->mNumMilestoneTypes); + src += gameplayHeader->mNumMilestoneTypes * 0x10; + AlignPointer(src, 0x10); + + LoadMilestones(reinterpret_cast(src), gameplayHeader->mNumMilestoneRecords); + src += gameplayHeader->mNumMilestoneRecords * 0x14; + src = reinterpret_cast((reinterpret_cast(src) + 0xFU) & ~0xFU); + + LoadSpeedTraps(reinterpret_cast(src), gameplayHeader->mNumSpeedTrapRecords); + src += gameplayHeader->mNumSpeedTrapRecords * 0x14; + src = reinterpret_cast((reinterpret_cast(src) + 0xFU) & ~0xFU); + + spotBytes = (gameplayHeader->mNumHidingSpotFlags + 7U) >> 3; + bMemCpy(mHidingSpotFound, src, spotBytes); + src += spotBytes; + src = reinterpret_cast((reinterpret_cast(src) + 0xFU) & ~0xFU); + + binBytesRead = GRaceDatabase::Get().DeserializeBins(src); + src += binBytesRead; + src = reinterpret_cast((reinterpret_cast(src) + 0xFU) & ~0xFU); + + LoadSMSInfo(reinterpret_cast(src), gameplayHeader->mNumPendingSMS); + src += gameplayHeader->mNumPendingSMS * sizeof(int); + src = reinterpret_cast((reinterpret_cast(src) + 0xFU) & ~0xFU); + + bMemCpy(&respawnMarker, src, sizeof(respawnMarker)); + src += sizeof(respawnMarker); + AlignPointer(src, 0x10); + Attrib::Gen::gameplay testInstance(respawnMarker, 0, nullptr); + + if (testInstance.IsValid()) { + Get().SetFreeRoamStartMarker(respawnMarker); + } + + bMemCpy(&respawnMarker, src, sizeof(respawnMarker)); + Attrib::Gen::gameplay testInstance2(respawnMarker, 0, nullptr); + + if (testInstance2.IsValid()) { + Get().SetFreeRoamFromSafeHouseStartMarker(respawnMarker); + } + + return true; +} + +void GManager::DefragObjectStateStorage() { + unsigned int count; + unsigned int index; + ObjectStateBlockHeader *stackBlocks[0x100]; + ObjectStateBlockHeader **blocks; + unsigned char *freePtr; + + blocks = stackBlocks; + count = mPersistentStateBlocks.size() + mSessionStateBlocks.size(); + if (count > 0x100) { + blocks = new ObjectStateBlockHeader *[count]; + } + + index = 0; + for (ObjectStateMap::iterator iter = mPersistentStateBlocks.begin(); iter != mPersistentStateBlocks.end(); ++iter) { + blocks[index] = iter->second; + index++; + } + for (ObjectStateMap::iterator iter = mSessionStateBlocks.begin(); iter != mSessionStateBlocks.end(); ++iter) { + blocks[index] = iter->second; + index++; + } + + std::sort(blocks, blocks + count); + freePtr = mObjectStateBuffer; + for (index = 0; index < count; ++index) { + ObjectStateBlockHeader *block = blocks[index]; + unsigned int blockSize = (block->mSize + 0x17U) & ~0xFU; + + if (reinterpret_cast(block) != freePtr) { + unsigned int shiftedKey = block->mKey >> mCollectionKeyShiftTo32; + unsigned int persistent = 0; + + bOverlappedMemCpy(freePtr, block, blockSize); + + do { + ObjectStateMap &stateMap = persistent ? mPersistentStateBlocks : mSessionStateBlocks; + + if (stateMap.find(shiftedKey) != stateMap.end()) { + stateMap[shiftedKey] = reinterpret_cast(freePtr); + } + + persistent++; + } while (persistent <= 1); + } + + freePtr += blockSize; + } + + if (blocks != stackBlocks) { + delete[] blocks; + } + + mObjectStateBufferFree = freePtr; +} + +void GManager::GetPlayerPursuitInterfaces(IPursuit *&pursuit, IPerpetrator *&perpetrator) { + IPlayer *player = IPlayer::First(PLAYER_LOCAL); + ISimable *simable = player ? player->GetSimable() : nullptr; + IVehicleAI *vehicleAI = nullptr; + + if (simable) { + simable->QueryInterface(&vehicleAI); + } + + if (!vehicleAI) { + pursuit = nullptr; + } else { + pursuit = vehicleAI->GetPursuit(); + } + + perpetrator = nullptr; + if (simable) { + simable->QueryInterface(&perpetrator); + } +} + +void GManager::OnRemovedVehicleCache(IVehicle *ivehicle) { + for (StockCarMap::iterator it = mStockCars.begin(); it != mStockCars.end(); ++it) { + if (UTL::COM::ComparePtr(it->second, ivehicle)) { + mStockCars.erase(it); + return; + } + } +} + +eVehicleCacheResult GManager::OnQueryVehicleCache(const IVehicle *removethis, const IVehicleCache *whosasking) const { + for (StockCarMap::iterator it = const_cast(this)->mStockCars.begin(); + it != const_cast(this)->mStockCars.end(); + ++it) { + IVehicle *stockCar = nullptr; + + if (it->second->QueryInterface(&stockCar) && stockCar == removethis) { + if (UTL::COM::ComparePtr(whosasking, INIS::Get())) { + const_cast(this)->mStockCars.erase(it); + return VCR_DONTCARE; + } + + return VCR_WANT; + } + } + + for (GCharacterList::const_iterator it = mActiveCharacters.begin(); it != mActiveCharacters.end(); ++it) { + GCharacter *character = *it; + IVehicle *vehicle = character->GetSpawnedVehicle(); + + if (vehicle != removethis) { + continue; + } + + bool hasStockCar = false; + if (vehicle) { + if (character->HasStockCar()) { + hasStockCar = true; + } + } + + if (hasStockCar) { + return VCR_DONTCARE; + } + + if (!UTL::COM::ComparePtr(whosasking, ITrafficMgr::Get()) && vehicle->GetOffscreenTime() != 0.0f) { + GCharacter *firstCharacter = nullptr; + if ((mActiveCharacters.end() - mActiveCharacters.begin()) != 0) { + firstCharacter = *mActiveCharacters.begin(); + } + + return character == firstCharacter ? VCR_DONTCARE : VCR_WANT; + } + + return VCR_DONTCARE; + } + + return VCR_DONTCARE; +} + +void GManager::SuspendAllBinActivities() { + for (unsigned int i = 0; i < GRaceDatabase::Get().GetBinCount(); ++i) { + GRaceBin *bin = GRaceDatabase::Get().GetBin(i); + + if (bin) { + GActivity *activity = static_cast(FindInstance(bin->GetCollectionKey())); + + if (activity && activity->GetIsRunning()) { + activity->SerializeVars(false); + activity->Suspend(); + } + } + } +} + +void GManager::UpdatePursuit() { + IPursuit *pursuit; + bool roaming; + IPerpetrator *perp; + bool inPursuit; + bool inCooldown; + GRaceParameters *raceParms; + bool challengeRace; + + roaming = !GRaceStatus::Get().GetPlayMode(); + pursuit = nullptr; + perp = nullptr; + inPursuit = false; + GetPlayerPursuitInterfaces(pursuit, perp); + + inCooldown = false; + if (pursuit) { + inPursuit = true; + mObj->TrackValue("cost_to_state_in_pursuit", static_cast(pursuit->CalcTotalCostToState())); + inCooldown = pursuit->GetPursuitStatus() == 2; + } + GRaceStatus::Get().PlayerPursuitInCoolDown(inCooldown); + + if (perp) { + mObj->TrackValue("cost_to_state", static_cast(perp->GetCostToState())); + mObj->TrackValue("bounty_in_pursuit", + static_cast(perp->GetPendingRepPointsNormal() + + perp->GetPendingRepPointsFromCopDestruction())); + } + + raceParms = GRaceStatus::Get().GetRaceParameters(); + challengeRace = false; + if (raceParms && raceParms->GetIsChallengeSeriesRace()) { + challengeRace = true; + } + + mHidingSpotIconsShown = (roaming || challengeRace) && inPursuit && inCooldown; + + bool pursuitBreakerIconsShown = false; + + if (!roaming && !challengeRace) { + if (GRaceStatus::IsFinalEpicPursuit() && inPursuit && !inCooldown) { + pursuitBreakerIconsShown = true; + } + } else if (inPursuit && !inCooldown) { + pursuitBreakerIconsShown = true; + } + mPursuitBreakerIconsShown = pursuitBreakerIconsShown; + + if (TWEAK_ShowGameplayMilestoneValues != 0) { + static volatile int xLeft = -130; + static volatile int yTop = -230; + static volatile int tab = 220; + static volatile int line = 16; + static const char *milestoneNames[] = { + "cops_damaged", + "cops_destroyed_in_pursuit", + "cost_to_state_in_pursuit", + "pursuit_evasion_time", + "pursuit_length", + "roadblocks_dodged", + "tire_spikes_dodged", + "total_infractions", + "bounty_in_pursuit", + }; + int x = xLeft; + int y = yTop; + + for (int printMilestone = 0; printMilestone <= 8; ++printMilestone) { + const char *name = milestoneNames[printMilestone]; + float val = GetValue(name); + + y = line; + } + } +} + +void GManager::UpdateTriggerAvailability() { + IPursuit *pursuit; + IPerpetrator *perpetrator; + bool roaming; + bool cooldown; + bool inPursuit; + + pursuit = nullptr; + perpetrator = nullptr; + roaming = GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming; + GetPlayerPursuitInterfaces(pursuit, perpetrator); + + cooldown = false; + inPursuit = pursuit != nullptr; + if (inPursuit) { + cooldown = pursuit->GetPursuitStatus() == 2; + } + + mAllowMenuGates = roaming && !inPursuit; + mAllowEngageEvents = roaming && !inPursuit; + mAllowEngageSafehouse = roaming && (!inPursuit || cooldown); +} + +void GManager::UpdateIconVisibility() { + bool eventIconsShown; + bool menuGateIconsShown; + bool speedTrapIconsShown; + bool speedTrapRaceIconsShown; + bool hideAll; + bool showAll; + bool iconTypeVisible[GIcon::kType_Count]; + unsigned int onFlag; + unsigned int onIcon; + + eventIconsShown = mAllowEngageEvents; + menuGateIconsShown = mAllowMenuGates; + speedTrapIconsShown = mAllowEngageEvents; + mSpeedTrapIconsShown = speedTrapIconsShown; + mMenuGateIconsShown = menuGateIconsShown; + mEventIconsShown = eventIconsShown; + speedTrapRaceIconsShown = + GRaceStatus::Exists() && + GRaceStatus::Get().GetRaceType() == GRace::kRaceType_SpeedTrap; + mSpeedTrapRaceIconsShown = speedTrapRaceIconsShown; + hideAll = false; + if (UTL::Collections::Singleton::Exists()) { + hideAll = true; + } + showAll = false; + if (TWEAK_ShowAllGameplayIcons != 0) { + showAll = true; + } + + for (onFlag = 0; onFlag < GIcon::kType_Count; ++onFlag) { + iconTypeVisible[onFlag] = showAll; + } + + if (!showAll && !hideAll) { + iconTypeVisible[GIcon::kType_RaceSprint] = mEventIconsShown; + iconTypeVisible[GIcon::kType_RaceCircuit] = mEventIconsShown; + iconTypeVisible[GIcon::kType_RaceDrag] = mEventIconsShown; + iconTypeVisible[GIcon::kType_RaceKnockout] = mEventIconsShown; + iconTypeVisible[GIcon::kType_RaceTollbooth] = mEventIconsShown; + iconTypeVisible[GIcon::kType_RaceSpeedtrap] = mEventIconsShown; + iconTypeVisible[GIcon::kType_RaceRival] = mEventIconsShown; + iconTypeVisible[GIcon::kType_GateSafehouse] = mAllowEngageSafehouse; + iconTypeVisible[GIcon::kType_GateCarLot] = mMenuGateIconsShown; + iconTypeVisible[GIcon::kType_GateCustomShop] = mMenuGateIconsShown; + iconTypeVisible[GIcon::kType_HidingSpot] = mHidingSpotIconsShown; + iconTypeVisible[GIcon::kType_PursuitBreaker] = mPursuitBreakerIconsShown; + iconTypeVisible[GIcon::kType_SpeedTrap] = mSpeedTrapIconsShown; + iconTypeVisible[GIcon::kType_SpeedTrapInRace] = mSpeedTrapRaceIconsShown; + iconTypeVisible[GIcon::kType_AreaUnlock] = true; + } + + mNumVisibleIcons = 0; + for (onIcon = 0; onIcon < mNumIcons; ++onIcon) { + GIcon *icon = mIcons[onIcon]; + bool enabled = icon->GetIsEnabled(); + bool iconVisible = icon->GetVisibleInWorld(); + bool typeVisible = iconTypeVisible[icon->GetType()]; + bool shouldBeVisible = iconVisible && typeVisible; + + if (!shouldBeVisible) { + if (enabled) { + icon->Disable(); + } + } else { + if (!enabled) { + icon->Enable(); + } + + GIcon *visibleIcon = mIcons[mNumVisibleIcons]; + mIcons[onIcon] = visibleIcon; + mIcons[mNumVisibleIcons] = icon; + mNumVisibleIcons++; + } + } +} + +bool GManager::CalcMapCoordsForMarker(unsigned int markerKey, bVector2 &outPos, float &outRotDeg) { + Attrib::Gen::gameplay marker(markerKey, 0, nullptr); + + if (!marker.IsValid()) { + return false; + } + + const UMath::Vector3 &pos = marker.Position(); + UMath::Matrix4 rotMat; + UMath::Vector3 forwardVec; + TrackInfo *trackInfo; + + trackInfo = TrackInfo::GetTrackInfo(2000); + bVector2 worldPos(pos.x, pos.y); + Minimap::ConvertPos(worldPos, outPos, trackInfo); + + rotMat = UMath::Matrix4::kIdentity; + forwardVec.x = 0.0f; + forwardVec.y = 0.0f; + forwardVec.z = 1.0f; + MATRIX4_multyrot(&rotMat, -marker.Rotation() * 0.0027777778f, &rotMat); + VU0_MATRIX3x4_vect3mult(forwardVec, rotMat, forwardVec); + outRotDeg = bAngToDeg(bATan(-forwardVec.x, forwardVec.z)); + return true; +} + +void GManager::ResetTimers() { + bMemSet(mTimers, 0, sizeof(mTimers)); +} + +void GManager::ResetAllGameplayData() { + mPersistentStateBlocks.clear(); + mSessionStateBlocks.clear(); + mObjectStateBufferFree = mObjectStateBuffer; + ResetMilestoneTrackingInfo(); + ResetMilestones(); + ResetSpeedTraps(); + bMemSet(mHidingSpotFound, 0, sizeof(mHidingSpotFound)); + ResetTimers(); + mFreeRoamStartMarker = 0; + mOverrideFreeRoamStartMarker = 0; + mFreeRoamFromSafeHouseStartMarker = 0; + mStartFreeRoamFromSafeHouse = false; + mPendingSMS.clear(); +} + +void GManager::NotifyWorldService() { + if (!mWarping) { + return; + } + + if (TheTrackStreamer.IsLoadingInProgress() == 1) { + return; + } + + if (mWarpTargetMarker != 0) { + GMarker *marker = static_cast(FindInstance(mWarpTargetMarker)); + const UMath::Vector3 &position = marker->GetPosition(); + const UMath::Vector3 &direction = marker->GetDirection(); + IVehicle *vehicle = nullptr; + + if (IPlayer::First(PLAYER_LOCAL)->GetSimable()->QueryInterface(&vehicle)) { + vehicle->SetVehicleOnGround(position, direction); + vehicle->Activate(); + } + + mWarpTargetMarker = 0; + if (mWarpStartPursuit) { + World_RestoreProps(); + GPS_Disengage(); + ITrafficMgr::Get()->FlushAllTraffic(true); + GCopMgrCompat::Get()->ResetCopsForRestart(true); + GCopMgrCompat::Get()->LockoutCops(false); + GCopMgrCompat::Get()->PursueAtHeatLevel(1); + } + } + + if (mWarpStartPursuit) { + if (GCopMgrCompat::Get()->IsCopSpawnPending()) { + mWarpStartPursuit = false; + } else { + GCopMgrCompat::Get()->PursueAtHeatLevel(1); + goto done; + } + } + + mWarping = false; + +done: + if (!mWarping) { + new EFadeScreenOff(0x161A918); + } +} + +bool GManager::WarpToMarker(unsigned int markerKey, bool startPursuit) { + if (!GRaceStatus::Exists() || GRaceStatus::Get().GetPlayMode() != GRaceStatus::kPlayMode_Roaming) { + return false; + } + + if (mWarping) { + return false; + } + + GMarker *marker = static_cast(FindInstance(markerKey)); + if (!marker) { + return false; + } + + const UMath::Vector3 &markerPos = marker->GetPosition(); + const UMath::Vector3 &markerDir = marker->GetDirection(); + + new EFadeScreenOn(false); + TheTrackStreamer.EnableZoneSwitching(); + Sim::SetStream(const_cast(markerPos), false); + + ISimable *player = IPlayer::First(PLAYER_LOCAL)->GetSimable(); + IVehicle *vehicle = nullptr; + + if (player->QueryInterface(&vehicle)) { + vehicle->SetVehicleOnGround(markerPos, markerDir); + vehicle->Deactivate(); + } + + mWarping = true; + mWarpTargetMarker = markerKey; + mWarpStartPursuit = startPursuit; + return true; +} + +unsigned int GManager::GetRespawnMarker() { + unsigned int markerKey = mOverrideFreeRoamStartMarker; + Attrib::Gen::gameplay marker(markerKey, 0, nullptr); + + if (!marker.IsValid()) { + if (mStartFreeRoamFromSafeHouse) { + markerKey = mFreeRoamFromSafeHouseStartMarker; + } else { + markerKey = mFreeRoamStartMarker; + } + + Attrib::Gen::gameplay defaultMarker(markerKey, 0, nullptr); + + if (!defaultMarker.IsValid()) { + char markerName[32]; + + bSPrintf(markerName, "career_start_%s", LoadedTrackInfo->GetLoadedTrackInfo()); + markerKey = Attrib::StringToLowerCaseKey(markerName); + mFreeRoamStartMarker = markerKey; + } + } + + return markerKey; +} + +void GManager::GetRespawnLocation(UMath::Vector3 &startLoc, UMath::Vector3 &initialVec) { + unsigned int markerKey = GetRespawnMarker(); + Attrib::Gen::gameplay marker(markerKey, 0, nullptr); + UMath::Matrix4 rotMat; + const UMath::Vector3 &pos = marker.Position(); + + startLoc = UMath::Vector3Make(-pos.y, pos.z, pos.x); + + rotMat = UMath::Matrix4::kIdentity; + + initialVec = UMath::Vector3Make(0.0f, 0.0f, 1.0f); + + MATRIX4_multyrot(&rotMat, -marker.Rotation() * 0.0027777778f, &rotMat); + VU0_MATRIX3x4_vect3mult(initialVec, rotMat, initialVec); +} diff --git a/src/Speed/Indep/Src/Gameplay/GManager.h b/src/Speed/Indep/Src/Gameplay/GManager.h index f1a37ee98..f7d5fb516 100644 --- a/src/Speed/Indep/Src/Gameplay/GManager.h +++ b/src/Speed/Indep/Src/Gameplay/GManager.h @@ -7,6 +7,7 @@ #include "GActivity.h" #include "GCharacter.h" +#include "GIcon.h" #include "GMilestone.h" #include "GRaceDatabase.h" #include "GSpeedTrap.h" @@ -36,6 +37,10 @@ typedef UTL::Std::map PendingSMSList; typedef UTL::Std::list AttribKeyList; +class GVault; +class IPursuit; +class IPerpetrator; + // total size: 0x308 class GManager : public UTL::COM::Object, public IVehicleCache { public: @@ -62,8 +67,20 @@ class GManager : public UTL::COM::Object, public IVehicleCache { }; static void Init(const char *vaultPackName); + ~GManager() override; + + const char *GetCacheName() const override { return "GManager"; } + void OnRemovedVehicleCache(IVehicle *ivehicle) override; + eVehicleCacheResult OnQueryVehicleCache(const IVehicle *removethis, const IVehicleCache *whosasking) const override; + void InitializeVaults(); void InitializeRaceStreaming(); + GVault *FindVault(const char *vaultName); + GVault *FindVaultContaining(unsigned int collectionKey); + void LoadCoreVault(struct AttribVaultPackImage *packImage); + void PreloadTransientVaults(struct AttribVaultPackImage *packImage); + void UnloadCoreVault(); + void UnloadTransientVaults(); void PreBeginGameplay(); void BeginGameplay(); void EndGameplay(); @@ -76,6 +93,9 @@ class GManager : public UTL::COM::Object, public IVehicleCache { void TrackValue(const char *valueName, float value); void IncValue(const char *valueName); float GetValue(const char *valueName); + float GetValue(unsigned int valueKey); + float GetBestValue(unsigned int valueKey); + bool GetIsBiggerValueBetter(unsigned int valueKey); void RegisterInstance(GRuntimeInstance *instance); void UnregisterInstance(GRuntimeInstance *instance); @@ -104,9 +124,10 @@ class GManager : public UTL::COM::Object, public IVehicleCache { void RefreshSpeedTrapIcons(); void GatherInstanceKeys(Attrib::Gen::gameplay &collection, AttribKeyList &list, unsigned int templateKey); void FindBountySpawnPoints(); - unsigned int GetNumBountySpawnMarkers() const; + unsigned int GetNumBountySpawnMarkers() const { return mNumBountySpawnPoints; } unsigned int GetBountySpawnMarker(unsigned int index) const; void ServicePendingCharacters(); + void UnspawnAllCharacters(); void UnspawnUselessCharacters(); unsigned int GetRespawnMarker(); int GetBountySpawnMarkerTag(unsigned int index) const; @@ -116,8 +137,10 @@ class GManager : public UTL::COM::Object, public IVehicleCache { void RefreshEngageTriggerIcons(); void HidePursuitBreakerIcon(const UMath::Vector3 &pos, float radius); - // struct GIcon *AllocIcon(enum Type iconType, const UMath::Vector3 &pos, float rotDeg, bool disposable); - // void FreeDisposableIcons(enum Type iconType); + void AllocateIcons(); + void ReleaseIcons(); + GIcon *AllocIcon(GIcon::Type iconType, const UMath::Vector3 &pos, float rotDeg, bool disposable); + void FreeDisposableIcons(GIcon::Type iconType); void FreeIcon(struct GIcon *icon); void FreeIconAt(unsigned int index); int GatherVisibleIcons(struct GIcon **iconArray, IPlayer *player); @@ -128,8 +151,8 @@ class GManager : public UTL::COM::Object, public IVehicleCache { void UnspawnAllIcons(); void FreeAllIcons(); - unsigned int GetNumMilestones(); - GMilestone *GetMilestone(unsigned int index); + unsigned int GetNumMilestones() { return mNumMilestones; } + GMilestone *GetMilestone(unsigned int index) { return &mMilestones[index]; } GMilestone *GetFirstMilestone(bool availOnly, unsigned int binNumber); GMilestone *GetNextMilestone(GMilestone *current, bool availOnly, unsigned int binNumber); void AllocateMilestones(); @@ -142,8 +165,8 @@ class GManager : public UTL::COM::Object, public IVehicleCache { unsigned int SaveMilestones(GMilestone *dest); void LoadMilestones(GMilestone *src, unsigned int count); - unsigned int GetNumSpeedTraps(); - GSpeedTrap *GetSpeedTrap(unsigned int index); + unsigned int GetNumSpeedTraps() { return mNumSpeedTraps; } + GSpeedTrap *GetSpeedTrap(unsigned int index) { return &mSpeedTraps[index]; } GSpeedTrap *GetFirstSpeedTrap(bool activeOnly, unsigned int binNumber); GSpeedTrap *GetNextSpeedTrap(GSpeedTrap *current, bool activeOnly, unsigned int binNumber); void AllocateSpeedTraps(); @@ -151,6 +174,8 @@ class GManager : public UTL::COM::Object, public IVehicleCache { void ResetSpeedTraps(); unsigned int SaveSpeedTraps(GSpeedTrap *dest); void LoadSpeedTraps(GSpeedTrap *src, unsigned int count); + void AttachCharacter(GCharacter *character); + void DetachCharacter(GCharacter *character); void RecursivePreloadCharacterCars(GRuntimeInstance *instance, bool forcePreload); void PreloadStockCarsForActivity(GActivity *activity); @@ -167,20 +192,32 @@ class GManager : public UTL::COM::Object, public IVehicleCache { void UpdateTimers(float dT); unsigned int SaveTimerInfo(struct SavedTimerInfo *saveInfo); void LoadTimerInfo(struct SavedTimerInfo *saveInfo, unsigned int count); + bool SaveGameplayData(unsigned char *dest, unsigned int maxSize); + bool LoadGameplayData(unsigned char *src, unsigned int maxSize); + void *GetObjectStateBlock(unsigned int key); + void ClearObjectStateBlock(unsigned int key); unsigned int SaveSMSInfo(int *saveInfo); void LoadSMSInfo(int *loadInfo, unsigned int count); bool GetHasPendingSMS() const; + bool GetHasPendingRestartEvent() const { + return mRestartEventHash != 0; + } bool CanPlaySMS() const; void AddSMS(int smsID); void DispatchSMSMessage(int smsID); void UpdatePendingSMS(); + void UpdateTriggerAvailability(); + void UpdateIconVisibility(); int PushSMSToInbox(); + void ClearAllSessionData(); static GManager &Get() { return *mObj; } + static void NotifyCollisionPackLoaded(int sectionID, bool loaded); + static bool Exists() { return mObj != nullptr; } @@ -189,17 +226,68 @@ class GManager : public UTL::COM::Object, public IVehicleCache { return mWarping; } + bool GetInGameplay() const { + return mInGameplay; + } + bool GetStartFreeRoamPursuit() { return mStartFreeRoamPursuit; } + unsigned int Get24BitAttributeKey(unsigned int attribKey) const { + return attribKey >> mAttributeKeyShiftTo24; + } + + unsigned int Get32BitCollectionKey(unsigned int collectionKey) const { + return collectionKey >> mCollectionKeyShiftTo32; + } + + void SetFreeRoamStartMarker(unsigned int markerKey) { + mFreeRoamStartMarker = markerKey; + } + + void SetFreeRoamFromSafeHouseStartMarker(unsigned int markerKey) { + mFreeRoamFromSafeHouseStartMarker = markerKey; + } + + unsigned int GetFreeRoamStartMarker() const { + return mFreeRoamStartMarker; + } + + unsigned int GetFreeRoamFromSafeHouseStartMarker() const { + return mFreeRoamFromSafeHouseStartMarker; + } + void TrackValue(const char *valueName, int value) { TrackValue(valueName, static_cast(value)); } private: + friend class GVault; + friend class GActivity; + GManager(const char *vaultPackName); + void AllocateObjectStateStorage(); + void AllocateInstanceMap(); + void AllocateStreamingBuffers(); + void BuildVaultTable(struct AttribVaultPackImage *packImage); + void FindKeyReductionShifts(); + unsigned int FindUniqueKeyShift(unsigned int *keys, unsigned int numKeys, unsigned int uniqueBits); + void ReleaseObjectStateStorage(); + void ReleaseInstanceMap(); + void ReleaseStreamingBuffers(); + void DestroyVaults(); + void ResetAllGameplayData(); + ObjectStateBlockHeader *AllocObjectStateBlock(unsigned int key, unsigned int size, bool persistent); + void DefragObjectStateStorage(); + void UpdatePursuit(); + void GetPlayerPursuitInterfaces(IPursuit *&pursuit, IPerpetrator *&perpetrator); + + int GetAvailableBinSlot(); + int GetAvailableRaceSlot(); + void LoadVaultSync(GVault *vault); + static GManager *mObj; const char *mVaultPackFileName; // offset 0x1C, size 0x4 diff --git a/src/Speed/Indep/Src/Gameplay/GMarker.cpp b/src/Speed/Indep/Src/Gameplay/GMarker.cpp index e69de29bb..051dd3a76 100644 --- a/src/Speed/Indep/Src/Gameplay/GMarker.cpp +++ b/src/Speed/Indep/Src/Gameplay/GMarker.cpp @@ -0,0 +1,18 @@ +#include "Speed/Indep/Src/Gameplay/GMarker.h" + +#include "Speed/Indep/Libs/Support/Utility/UMath.h" + +GMarker::GMarker(const Attrib::Key &markerKey) + : GRuntimeInstance(markerKey, kGameplayObjType_Marker) { + const UMath::Vector3 &pos = Position(0); + UMath::Matrix4 rotMat = UMath::Matrix4::kIdentity; + UMath::Vector3 initialVec = UMath::Vector3Make(0.0f, 0.0f, 1.0f); + + MATRIX4_multyrot(&rotMat, -Rotation(0) * 0.0027777778f, &rotMat); + VU0_MATRIX3x4_vect3mult(initialVec, rotMat, initialVec); + + mPosition = UMath::Vector3Make(-pos.y, pos.z, pos.x); + mDirection = initialVec; +} + +GMarker::~GMarker() {} diff --git a/src/Speed/Indep/Src/Gameplay/GMarker.h b/src/Speed/Indep/Src/Gameplay/GMarker.h index 793b7f037..93d541bd0 100644 --- a/src/Speed/Indep/Src/Gameplay/GMarker.h +++ b/src/Speed/Indep/Src/Gameplay/GMarker.h @@ -5,6 +5,32 @@ #pragma once #endif +#include "GRuntimeInstance.h" +// total size: 0x40 +class GMarker : public GRuntimeInstance { + public: + GMarker(const Attrib::Key &markerKey); + + ~GMarker() override; + + GameplayObjType GetType() const override { + return kGameplayObjType_Marker; + } + + const UMath::Vector3 &GetPosition() const { + return mPosition; + } + + const UMath::Vector3 &GetDirection() const { + return mDirection; + } + + void CalcTransform(UMath::Matrix4 &mat) const; + + private: + UMath::Vector3 mPosition; // offset 0x28, size 0xC + UMath::Vector3 mDirection; // offset 0x34, size 0xC +}; #endif diff --git a/src/Speed/Indep/Src/Gameplay/GMilestone.cpp b/src/Speed/Indep/Src/Gameplay/GMilestone.cpp index e69de29bb..485703a1d 100644 --- a/src/Speed/Indep/Src/Gameplay/GMilestone.cpp +++ b/src/Speed/Indep/Src/Gameplay/GMilestone.cpp @@ -0,0 +1,141 @@ +#include "Speed/Indep/Src/Gameplay/GMilestone.h" + +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/milestonetypes.h" +#include "Speed/Indep/Src/Generated/Events/EReportMilestoneAtStake.hpp" +#include "Speed/Indep/Src/Generated/Messages/MNotifyMilestoneReached.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" + +void Game_AwardPlayerBounty(int bounty); +void Game_ChallengeCompleted(); + +GMilestone::GMilestone() + : mTypeKey(0), // + mChallengeKey(0), // + mState(0), // + mFlags(0), // + mBinNumber(0), // + mRequiredValue(0.0f), // + mRecordedValue(0.0f) {} + +float GMilestone::GetCurrentValue() const { + return GManager::Get().GetValue(mTypeKey); +} + +float GMilestone::GetBounty() const { + Attrib::Gen::gameplay gameplayObj(mChallengeKey, 0, nullptr); + + if (!gameplayObj.IsValid()) { + return 0.0f; + } + + return static_cast(gameplayObj.Bounty(0)); +} + +int GMilestone::GetLocalizationTag() const { + Attrib::Gen::milestonetypes milestoneType(mTypeKey, 0, nullptr); + + if (!milestoneType.IsValid()) { + return 0; + } + + return milestoneType.LocalizationTag(); +} + +unsigned int GMilestone::GetJumpMarkerKey() const { + Attrib::Gen::gameplay gameplayObj(mChallengeKey, 0, nullptr); + + return gameplayObj.SpawnPoint(0).GetCollectionKey(); +} + +void GMilestone::DebugForceComplete() { + mFlags |= kFlag_CompletionFaked; + mState = kState_DonePendingEscape; +} + +void GMilestone::Init(unsigned int challengeKey) { + mChallengeKey = challengeKey; + Reset(); +} + +void GMilestone::Reset() { + Attrib::Gen::gameplay gameplayObj(mChallengeKey, 0, nullptr); + + mTypeKey = Attrib::StringToKey(gameplayObj.MilestoneName(0)); + mState = kState_Locked; + mBinNumber = static_cast(gameplayObj.BinIndex(0)); + mRequiredValue = gameplayObj.GoalEasy(0); + mRecordedValue = 0.0f; + + if (GManager::Get().GetIsBiggerValueBetter(mTypeKey)) { + mFlags |= kFlag_BiggerIsBetter; + } +} + +void GMilestone::Unlock() { + if (mState == kState_Locked) { + mState = kState_Available; + } +} + +bool GMilestone::ValueMeetsGoal(float value) { + float delta; + + delta = mRequiredValue - value; + if (((mFlags ^ kFlag_BiggerIsBetter) & kFlag_BiggerIsBetter) != 0) { + delta = -delta; + } + + return delta <= 0.0f; +} + +void GMilestone::NotifyProgress(float value) { + if (mState == kState_Available && ValueMeetsGoal(value)) { + new EReportMilestoneAtStake(this); + mState = kState_DonePendingEscape; + } +} + +void GMilestone::NotifyPursuitOver(bool escaped) { + if (mState == kState_DonePendingEscape) { + if (escaped) { + float currentValue = GetCurrentValue(); + + if ((mFlags & kFlag_CompletionFaked) != 0) { + currentValue = mRequiredValue; + } + + mRecordedValue = currentValue; + mState = kState_Awarded; + + { + Attrib::Gen::milestonetypes milestoneType(mTypeKey, 0, nullptr); + MNotifyMilestoneReached message(milestoneType.CollectionName(), currentValue); + + message.Post(UCrc32(0x20D60DBF)); + } + + { + Attrib::Gen::gameplay gameplayObj(mChallengeKey, 0, nullptr); + GRaceBin *bin = GRaceDatabase::Get().GetBinNumber(mBinNumber); + + if (bin) { + bin->RefreshProgress(); + } + + const int *bounty = reinterpret_cast(gameplayObj.GetAttributePointer(0x8E1904C7, 0)); + + if (!bounty) { + bounty = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + Game_AwardPlayerBounty(*bounty); + Game_ChallengeCompleted(); + } + } else { + mState = kState_Available; + } + } +} diff --git a/src/Speed/Indep/Src/Gameplay/GMilestone.h b/src/Speed/Indep/Src/Gameplay/GMilestone.h index 4de0c87cf..9753105e4 100644 --- a/src/Speed/Indep/Src/Gameplay/GMilestone.h +++ b/src/Speed/Indep/Src/Gameplay/GMilestone.h @@ -14,6 +14,76 @@ struct MilestoneTypeInfo { // total size: 0x14 class GMilestone { + public: + enum State { + kState_Invalid = 0, + kState_Locked = 1, + kState_Available = 2, + kState_DonePendingEscape = 3, + kState_Awarded = 4, + }; + + enum Flags { + kFlag_BiggerIsBetter = 1, + kFlag_CompletionFaked = 2, + }; + + bool GetIsLocked() const { + return mState == kState_Locked; + } + + bool GetIsAvailable() const { + return mState == kState_Available; + } + + bool GetIsDonePendingEscape() const { + return mState == kState_DonePendingEscape; + } + + bool GetIsAwarded() const { + return mState == kState_Awarded; + } + + unsigned int GetTypeKey() const { + return mTypeKey; + } + + unsigned int GetChallengeKey() const { + return mChallengeKey; + } + + unsigned int GetBinNumber() const { + return mBinNumber; + } + + float GetRequiredValue() const { + return mRequiredValue; + } + + float GetRecordedPassValue() const { + return mRecordedValue; + } + + bool operator<(const GMilestone &rhs) const { + return mChallengeKey < rhs.mChallengeKey; + } + + GMilestone(); + float GetCurrentValue() const; + float GetBounty() const; + int GetLocalizationTag() const; + unsigned int GetJumpMarkerKey() const; + void DebugForceComplete(); + void Init(unsigned int challengeKey); + void Reset(); + void Unlock(); + void SetGoal(float required) { + mRequiredValue = required; + } + bool ValueMeetsGoal(float value); + void NotifyProgress(float value); + void NotifyPursuitOver(bool escaped); + private: unsigned int mTypeKey; // offset 0x0, size 0x4 unsigned int mChallengeKey; // offset 0x4, size 0x4 diff --git a/src/Speed/Indep/Src/Gameplay/GObjectBlock.cpp b/src/Speed/Indep/Src/Gameplay/GObjectBlock.cpp index e69de29bb..c2eee6f53 100644 --- a/src/Speed/Indep/Src/Gameplay/GObjectBlock.cpp +++ b/src/Speed/Indep/Src/Gameplay/GObjectBlock.cpp @@ -0,0 +1,290 @@ +#include "Speed/Indep/Src/Gameplay/GObjectBlock.h" + +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/Gameplay/GActivity.h" +#include "Speed/Indep/Src/Gameplay/GCharacter.h" +#include "Speed/Indep/Src/Gameplay/GHandler.h" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GMarker.h" +#include "Speed/Indep/Src/Gameplay/GState.h" +#include "Speed/Indep/Src/Gameplay/GTrigger.h" +#include "Speed/Indep/Src/Gameplay/GVault.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +template +static unsigned int GetPaddedObjectSize() { + return (sizeof(T) + 15) & ~15; +} + +template +static unsigned int FindInstances(GVault *vault, AttribKeyList *attribKeyList, unsigned int *outObjCount, unsigned int *outConnCount) { + static unsigned int kObjectTemplateKey = 0x7FCB7ABA; + Attrib::Gen::gameplay objectTemplate(kObjectTemplateKey, 0, nullptr); + Attrib::Vault *attribVault = vault->GetAttribVault(); + int collectionType = Attrib::StringToTypeID("Attrib::CollectionLoadData"); + unsigned int exportCount = attribVault->CountExports(); + int numObjects = 0; + int numConnections = 0; + unsigned int exportIndex = 0; + if (exportCount != 0) { + do { + if (attribVault->GetExportType(exportIndex) == collectionType) { + unsigned int collectionKey = Attrib::GetCollectionKey(reinterpret_cast(attribVault->GetExportData(exportIndex))); + Attrib::Gen::gameplay instanceObj(collectionKey, 0, nullptr); + + if (GObjectBlock::CollectionIsInstanceOfTemplate(instanceObj, objectTemplate)) { + if (attribKeyList) { + attribKeyList->push_back(collectionKey); + } + + numObjects = numObjects + 1; + numConnections = numConnections + GObjectBlock::CalcNumConnections(collectionKey); + } + } + + exportIndex = exportIndex + 1; + } while (exportIndex < exportCount); + } + + if (outObjCount) { + *outObjCount = *outObjCount + numObjects; + } + + if (outConnCount) { + *outConnCount = *outConnCount + numConnections; + } + + return (numObjects * GetPaddedObjectSize() + numConnections * 8 + 0xFU) & ~0xFU; +} + +template +struct GObjectBlockTypeTraits; + +template <> +struct GObjectBlockTypeTraits { + enum { kType = kGameplayObjType_Activity }; +}; + +template <> +struct GObjectBlockTypeTraits { + enum { kType = kGameplayObjType_Character }; +}; + +template <> +struct GObjectBlockTypeTraits { + enum { kType = kGameplayObjType_Handler }; +}; + +template <> +struct GObjectBlockTypeTraits { + enum { kType = kGameplayObjType_Marker }; +}; + +template <> +struct GObjectBlockTypeTraits { + enum { kType = kGameplayObjType_State }; +}; + +template <> +struct GObjectBlockTypeTraits { + enum { kType = kGameplayObjType_Trigger }; +}; + +template +static inline unsigned int GetGameplayType() { + return GObjectBlockTypeTraits::kType; +} + +template +void GObjectBlock::DeleteObjects() { + const unsigned int type = GetGameplayType(); + unsigned char *buffer = reinterpret_cast(mObjectList[type]); + unsigned int objSize = GetPaddedObjectSize(); + + for (unsigned int onObj = 0; onObj < mObjectCount[type]; ++onObj) { + T *obj = reinterpret_cast(buffer + onObj * objSize); + obj->~T(); + } + + mObjectCount[type] = 0; + mObjectSize[type] = 0; + mObjectList[type] = nullptr; +} + +template +unsigned int GObjectBlock::CreateObjects(GVault *vault, unsigned char *buffer) { + const GameplayObjType type = static_cast(GetGameplayType()); + AttribKeyList keys; + unsigned int objSize; + unsigned int objCount; + GRuntimeInstance::ConnectedInstance *connectionBase; + GRuntimeInstance::ConnectedInstance *connectionDest; + unsigned int spaceUsed; + + FindInstances(vault, &keys, nullptr, nullptr); + + objCount = 0; + objSize = ::GetPaddedObjectSize(); + connectionBase = reinterpret_cast(buffer + objSize * keys.size()); + connectionDest = connectionBase; + + for (AttribKeyList::const_iterator iterObj = keys.begin(); iterObj != keys.end(); ++iterObj) { + unsigned int collectionKey = *iterObj; + T *pMem = ::new (buffer + objCount * objSize) T(collectionKey); + T *newObj = pMem; + unsigned int numConnections = CalcNumConnections(collectionKey); + + newObj->SetConnectionBuffer(connectionDest, numConnections); + connectionDest += numConnections; + objCount += 1; + } + + mObjectCount[type] = objCount; + mObjectSize[type] = objSize; + mObjectList[type] = reinterpret_cast(buffer); + + spaceUsed = reinterpret_cast(connectionDest) - buffer; + return (spaceUsed + 0xFU) & ~0xFU; +} + +GObjectBlock::GObjectBlock(GVault *vault, unsigned char *buffer) + : mVault(vault), // + mObjectBuffer(buffer) { + unsigned int onType = 0; + + do { + mObjectCount[onType] = 0; + mObjectSize[onType] = 0; + mObjectList[onType] = nullptr; + onType += 1; + } while (onType < 6); +} + +GObjectBlock::~GObjectBlock() { + DeleteObjects(); + DeleteObjects(); + DeleteObjects(); + DeleteObjects(); + DeleteObjects(); + DeleteObjects(); + + mVault = nullptr; + mObjectBuffer = nullptr; + + if (WTriggerManager::Exists()) { + WTriggerManager::Get().ClearAllFireOnExit(); + } +} + +void GObjectBlock::Initialize(unsigned int bufferSize) { + unsigned char *buffer = mObjectBuffer; + + buffer += CreateObjects(mVault, buffer); + buffer += CreateObjects(mVault, buffer); + buffer += CreateObjects(mVault, buffer); + buffer += CreateObjects(mVault, buffer); + buffer += CreateObjects(mVault, buffer); + CreateObjects(mVault, buffer); +} + +unsigned int GObjectBlock::CalcSpaceRequired(GVault *vault, unsigned int *outObjCount) { + unsigned int bytesUsed = 0; + unsigned int objCount = bytesUsed; + + bytesUsed = FindInstances(vault, nullptr, &objCount, nullptr); + + bytesUsed += FindInstances(vault, nullptr, &objCount, nullptr); + bytesUsed += FindInstances(vault, nullptr, &objCount, nullptr); + bytesUsed += FindInstances(vault, nullptr, &objCount, nullptr); + bytesUsed += FindInstances(vault, nullptr, &objCount, nullptr); + bytesUsed += FindInstances(vault, nullptr, &objCount, nullptr); + + if (outObjCount) { + *outObjCount = objCount; + } + + return bytesUsed; +} + +bool GObjectBlock::CollectionIsInstanceOfTemplate(Attrib::Gen::gameplay &instanceObj, Attrib::Gen::gameplay &templateObj) { + unsigned int parentKey; + + if (instanceObj.Template(0)) { + return false; + } + + parentKey = instanceObj.GetParent(); + while (parentKey != 0) { + Attrib::Gen::gameplay parentObj(parentKey, 0, nullptr); + + if (parentObj.GetCollection() == templateObj.GetCollection()) { + return true; + } + + parentKey = parentObj.GetParent(); + } + + return false; +} + +unsigned int GObjectBlock::CalcNumConnections(unsigned int collectionKey) { + Attrib::Class *gameplayClass = Attrib::Database::Get().GetClass(Attrib::Gen::gameplay::ClassKey()); + unsigned int numConnections; + Attrib::Gen::gameplay collection(collectionKey, 0, nullptr); + + numConnections = collection.Num_Children(); + if (collectionKey != 0) { + do { + Attrib::Gen::gameplay collection(collectionKey, 0, nullptr); + Attrib::AttributeIterator iter = collection.Iterator(); + + while (iter.Valid()) { + unsigned int attribKey = iter.GetKey(); + + { + Attrib::Attribute attrib = collection.Get(attribKey); + + if (attribKey != 0x916E0E78) { + if (attrib.IsValid()) { + const Attrib::TypeDesc &typeDesc = Attrib::Database::Get().GetTypeDesc(attrib.GetType()); + const char *typeName = + *reinterpret_cast(reinterpret_cast(&typeDesc) + sizeof(unsigned int)); + + if (bStrCmp(typeName, "GCollectionKey") == 0) { + const Attrib::Definition *definition = gameplayClass->GetDefinition(attrib.GetKey()); + int count = 1; + + if (definition->GetFlag(1)) { + count = attrib.GetLength(); + } + + numConnections += count; + } + } + } + } + + iter.Advance(); + } + + collectionKey = collection.GetParent(); + } while (collectionKey != 0); + } + + return numConnections; +} + +template void GObjectBlock::DeleteObjects(); +template void GObjectBlock::DeleteObjects(); +template void GObjectBlock::DeleteObjects(); +template void GObjectBlock::DeleteObjects(); +template void GObjectBlock::DeleteObjects(); +template void GObjectBlock::DeleteObjects(); + +template unsigned int GObjectBlock::CreateObjects(GVault *vault, unsigned char *buffer); +template unsigned int GObjectBlock::CreateObjects(GVault *vault, unsigned char *buffer); +template unsigned int GObjectBlock::CreateObjects(GVault *vault, unsigned char *buffer); +template unsigned int GObjectBlock::CreateObjects(GVault *vault, unsigned char *buffer); +template unsigned int GObjectBlock::CreateObjects(GVault *vault, unsigned char *buffer); +template unsigned int GObjectBlock::CreateObjects(GVault *vault, unsigned char *buffer); diff --git a/src/Speed/Indep/Src/Gameplay/GObjectBlock.h b/src/Speed/Indep/Src/Gameplay/GObjectBlock.h new file mode 100644 index 000000000..5a3a21619 --- /dev/null +++ b/src/Speed/Indep/Src/Gameplay/GObjectBlock.h @@ -0,0 +1,41 @@ +#ifndef GAMEPLAY_GOBJECTBLOCK_H +#define GAMEPLAY_GOBJECTBLOCK_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h" + +class GVault; +class GRuntimeInstance; + +// total size: 0x50 +struct GObjectBlock { + GObjectBlock(GVault *vault, unsigned char *buffer); + ~GObjectBlock(); + + void Initialize(unsigned int bufferSize); + static unsigned int CalcSpaceRequired(GVault *vault, unsigned int *outObjCount); + static bool CollectionIsInstanceOfTemplate(Attrib::Gen::gameplay &instanceObj, Attrib::Gen::gameplay &templateObj); + static unsigned int CalcNumConnections(unsigned int collectionKey); + + template + void DeleteObjects(); + + template + unsigned int CreateObjects(GVault *vault, unsigned char *buffer); + + template + static unsigned int GetPaddedObjectSize() { + return (sizeof(T) + 15) & ~15; + } + + GVault *mVault; // offset 0x0, size 0x4 + unsigned char *mObjectBuffer; // offset 0x4, size 0x4 + unsigned int mObjectCount[6]; // offset 0x8, size 0x18 + unsigned int mObjectSize[6]; // offset 0x20, size 0x18 + GRuntimeInstance *mObjectList[6]; // offset 0x38, size 0x18 +}; + +#endif diff --git a/src/Speed/Indep/Src/Gameplay/GRaceDatabase.cpp b/src/Speed/Indep/Src/Gameplay/GRaceDatabase.cpp index e69de29bb..e92735b5e 100644 --- a/src/Speed/Indep/Src/Gameplay/GRaceDatabase.cpp +++ b/src/Speed/Indep/Src/Gameplay/GRaceDatabase.cpp @@ -0,0 +1,859 @@ +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" + +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Gameplay/GVault.h" +#include "Speed/Indep/Src/World/WCollisionAssets.h" +#include "Speed/Indep/Src/World/WRoadNetwork.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +#include + +void EnableBarrierSceneryGroup(const char *name, bool flipped); +void RedoTopologyAndSceneryGroups(); + +GRaceDatabase *GRaceDatabase::mObj = nullptr; +static const char *kDDayRaces[5] = { + "16.1.0", + "16.2.2", + "16.2.3", + "16.1.1", + "16.2.1", +}; + +GRaceDatabase::GRaceDatabase() + : mRaceCountStatic(0), // + mRaceCountDynamic(0), // + mRaceIndex(nullptr), // + mRaceParameters(nullptr), // + mBinCount(0), // + mBins(nullptr), // + mGameplayClass(Attrib::Database::Get().GetClass(0x5CEA9D46)), // + mStartupRace(nullptr), // + mStartupRaceContext(GRace::kRaceContext_QuickRace), // + mNumInitialUnlocks(0), // + mInitialUnlockHash(nullptr), // + mRaceScoreInfo(nullptr) { + unsigned int i; + + for (i = 0; i < 4; i++) { + mRaceCustom[i] = nullptr; + } + + BuildBinList(); + BuildRaceList(); + BuildScoreList(); +} + +GRaceBin::GRaceBin(unsigned int collectionKey) + : mBinRecord(collectionKey, 0, nullptr), // + mChildVault(nullptr) { + bMemSet(&mStats, 0, sizeof(mStats)); +} + +unsigned int GRaceBin::GetCollectionKey() const { + return mBinRecord.GetCollection(); +} + +const Attrib::Gen::gameplay *GRaceBin::GetGameplayObj() const { + return &mBinRecord; +} + +GVault *GRaceBin::GetChildVault() const { + return mChildVault; +} + +int GRaceBin::GetBinNumber() const { + return *reinterpret_cast(mBinRecord.GetAttributePointer(0x6CE23062, 0) ? + mBinRecord.GetAttributePointer(0x6CE23062, 0) : + Attrib::DefaultDataArea(sizeof(int))); +} + +int GRaceBin::GetBossReputation() const { + return mBinRecord.BossReputation(0); +} + +float GRaceBin::GetBaseOpenWorldHeat() const { + return *reinterpret_cast(mBinRecord.GetAttributePointer(0x8F186AC4, 0) ? + mBinRecord.GetAttributePointer(0x8F186AC4, 0) : + Attrib::DefaultDataArea(sizeof(float))); +} + +float GRaceBin::GetMaxOpenWorldHeat() const { + return *reinterpret_cast(mBinRecord.GetAttributePointer(0xE8C24416, 0) ? + mBinRecord.GetAttributePointer(0xE8C24416, 0) : + Attrib::DefaultDataArea(sizeof(float))); +} + +float GRaceBin::GetScaleOpenWorldHeat() const { + return *reinterpret_cast(mBinRecord.GetAttributePointer(0x1823B89E, 0) ? + mBinRecord.GetAttributePointer(0x1823B89E, 0) : + Attrib::DefaultDataArea(sizeof(float))); +} + +unsigned int GRaceBin::GetBossRaceCount() const { + Attrib::Attribute bossRaces; + + bossRaces = mBinRecord.Get(0xD5A174AA); + return bossRaces.GetLength(); +} + +unsigned int GRaceBin::GetBossRaceHash(unsigned int index) const { + GRaceParameters *race; + const unsigned int *key; + + key = reinterpret_cast(mBinRecord.GetAttributePointer(0xD5A174AA, index)); + if (!key) { + key = reinterpret_cast(Attrib::DefaultDataArea(sizeof(unsigned int))); + } + + race = GRaceDatabase::Get().GetRaceFromKey(*key); + return race ? race->GetEventHash() : 0; +} + +unsigned int GRaceBin::GetWorldRaceCount() const { + Attrib::Attribute worldRaces; + + worldRaces = mBinRecord.Get(0xA7EF40EF); + return worldRaces.GetLength(); +} + +unsigned int GRaceBin::GetWorldRaceHash(unsigned int index) const { + GRaceParameters *race; + const unsigned int *key; + + key = reinterpret_cast(mBinRecord.GetAttributePointer(0xA7EF40EF, index)); + if (!key) { + key = reinterpret_cast(Attrib::DefaultDataArea(sizeof(unsigned int))); + } + + race = GRaceDatabase::Get().GetRaceFromKey(*key); + return race ? race->GetEventHash() : 0; +} + +unsigned int GRaceBin::GetBaselineUnlockCount() const { + Attrib::Attribute unlocks; + + unlocks = mBinRecord.Get(0xBAF89280); + return unlocks.GetLength(); +} + +unsigned int GRaceBin::GetBaselineUnlock(unsigned int index) const { + const unsigned int *key; + + key = reinterpret_cast(mBinRecord.GetAttributePointer(0xBAF89280, index)); + if (!key) { + key = reinterpret_cast(Attrib::DefaultDataArea(sizeof(unsigned int))); + } + + return *key; +} + +unsigned int GRaceBin::GetBarrierCount() const { + return mBinRecord.Num_Barriers(); +} + +void GRaceBin::EnableBarriers() { + unsigned int i; + + for (i = 0; i < GetBarrierCount(); i++) { + EnableBarrierSceneryGroup(GetBarrierName(i), GetBarrierIsFlipped(i)); + } + + WCollisionAssets::Get().SetExclusionFlags(); + WRoadNetwork::Get().ResolveBarriers(); +} + +void GRaceBin::DisableBarriers() { + RedoTopologyAndSceneryGroups(); + WRoadNetwork::Get().ResetBarriers(); + WRoadNetwork::Get().ResetRaceSegments(); +} + +unsigned int GRaceBin::Serialize(unsigned char *dest) { + *reinterpret_cast(dest) = mStats; + return sizeof(mStats); +} + +unsigned int GRaceBin::Deserialize(unsigned char *src) { + mStats = *reinterpret_cast(src); + return sizeof(mStats); +} + +const char *GRaceBin::GetBarrierName(unsigned int index) const { + const char *barrierName; + const EA::Reflection::Text *barrierText; + + barrierText = reinterpret_cast(mBinRecord.GetAttributePointer(0xE244F26B, index)); + if (!barrierText) { + barrierText = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + barrierName = *barrierText; + if (barrierName && *barrierName == '*') { + return barrierName + 1; + } + + return barrierName; +} + +unsigned int GRaceBin::GetBarrierHash(unsigned int index) const { + const char *barrierName; + + barrierName = GetBarrierName(index); + return barrierName ? Attrib::StringToKey(barrierName) : 0; +} + +bool GRaceBin::GetBarrierIsFlipped(unsigned int index) const { + const char *barrierName; + + barrierName = mBinRecord.Barriers(index); + if (barrierName) { + return *barrierName == '*'; + } + + return false; +} + +int GRaceBin::GetRequiredBounty() const { + return *reinterpret_cast(mBinRecord.GetAttributePointer(0xD3657D92, 0) ? + mBinRecord.GetAttributePointer(0xD3657D92, 0) : + Attrib::DefaultDataArea(sizeof(int))); +} + +int GRaceBin::GetRequiredChallenges() const { + return *reinterpret_cast(mBinRecord.GetAttributePointer(0x6DD4B98B, 0) ? + mBinRecord.GetAttributePointer(0x6DD4B98B, 0) : + Attrib::DefaultDataArea(sizeof(int))); +} + +int GRaceBin::GetRequiredRaceWins() const { + return *reinterpret_cast(mBinRecord.GetAttributePointer(0xD617FEDC, 0) ? + mBinRecord.GetAttributePointer(0xD617FEDC, 0) : + Attrib::DefaultDataArea(sizeof(int))); +} + +int GRaceBin::GetCompletedChallenges() const { + return mStats.mChallengesCompleted; +} + +int GRaceBin::GetAwardedRaceWins() const { + return mStats.mRacesWon; +} + +void GRaceBin::SetCompletedChallenges(int numChallenges) { + mStats.mChallengesCompleted = numChallenges; +} + +void GRaceBin::SetRacesWon(int numRaces) { + mStats.mRacesWon = numRaces; +} + +void GRaceBin::RefreshProgress() { + int numRaces; + int numChallenges; + GMilestone *binMilestone; + GSpeedTrap *binSpeedTrap; + + numRaces = 0; + for (unsigned int onRace = 0; onRace < GetWorldRaceCount(); onRace++) { + unsigned int raceHash = GetWorldRaceHash(onRace); + + if (GRaceDatabase::Get().IsCareerRaceComplete(raceHash)) { + numRaces++; + } + } + + numChallenges = 0; + binMilestone = GManager::Get().GetFirstMilestone(false, GetBinNumber()); + while (binMilestone) { + if (binMilestone->GetIsAwarded()) { + numChallenges++; + } + binMilestone = GManager::Get().GetNextMilestone(binMilestone, false, GetBinNumber()); + } + + binSpeedTrap = GManager::Get().GetFirstSpeedTrap(false, GetBinNumber()); + while (binSpeedTrap) { + if (binSpeedTrap->GetIsCompleted()) { + numChallenges++; + } + binSpeedTrap = GManager::Get().GetNextSpeedTrap(binSpeedTrap, false, GetBinNumber()); + } + + SetRacesWon(numRaces); + SetCompletedChallenges(numChallenges); +} + +void GRaceDatabase::Init() { + mObj = new GRaceDatabase; +} + +GRaceCustom *GRaceDatabase::GetStartupRace() { + return mStartupRace; +} + +GRace::Context GRaceDatabase::GetStartupRaceContext() { + return mStartupRaceContext; +} + +unsigned int GRaceDatabase::GetBinCount() { + return mBinCount; +} + +unsigned int GRaceDatabase::GetRaceCount() { + return mRaceCountStatic + mRaceCountDynamic; +} + +void GRaceDatabase::SimulateDDayComplete() {} + +GRaceBin *GRaceDatabase::GetBin(unsigned int index) { + return &mBins[index]; +} + +bool GRaceDatabase::CollectionIsRaceActivity(Attrib::Gen::gameplay &collection) { + Attrib::Gen::gameplay activity(0xD1E66B67, 0, nullptr); + const int *isObject = reinterpret_cast(collection.GetAttributePointer(0x3E9156CA, 0)); + + if (!isObject) { + isObject = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + if (*isObject != 0) { + return false; + } + + Attrib::Key parentKey = collection.GetParent(); + + while (parentKey != 0) { + Attrib::Gen::gameplay parent(parentKey, 0, nullptr); + + if (parent.GetCollection() == activity.GetCollection()) { + return true; + } + + parentKey = parent.GetParent(); + } + + return false; +} + +bool GRaceDatabase::CollectionIsRaceBin(Attrib::Gen::gameplay &collection) { + Attrib::Key parentKey = collection.GetParent(); + Attrib::Gen::gameplay bin(0x022EB0EE, 0, nullptr); + Attrib::Gen::gameplay parent(parentKey, 0, nullptr); + bool isRaceBin = parent.GetCollection() == bin.GetCollection(); + + return isRaceBin; +} + +unsigned int GRaceDatabase::StoreBinList(GRaceBin *dest) { + unsigned int count; + unsigned int collectionKey; + GRaceBin *current; + + count = 0; + current = dest; + for (collectionKey = mGameplayClass->GetFirstCollection(); collectionKey != 0; + collectionKey = mGameplayClass->GetNextCollection(collectionKey)) { + Attrib::Gen::gameplay gameplay(collectionKey, 0, nullptr); + + if (CollectionIsRaceBin(gameplay)) { + if (dest && current) { + new (current) GRaceBin(collectionKey); + current = reinterpret_cast(reinterpret_cast(current) + 0x1C); + } + count++; + } + } + + return count; +} + +void GRaceDatabase::BuildBinList() { + mBinCount = StoreBinList(nullptr); + mBins = reinterpret_cast(new unsigned char[mBinCount * 0x1C]); + StoreBinList(mBins); +} + +unsigned int GRaceDatabase::StoreRaceList(GRaceParameters *dest) { + unsigned int count; + unsigned int collectionKey; + GRaceParameters *current; + unsigned char *indexData; + + count = 0; + current = dest; + indexData = reinterpret_cast(mRaceIndex); + for (collectionKey = mGameplayClass->GetFirstCollection(); collectionKey != 0; + collectionKey = mGameplayClass->GetNextCollection(collectionKey)) { + Attrib::Gen::gameplay gameplay(collectionKey, 0, nullptr); + + if (CollectionIsRaceActivity(gameplay)) { + if (dest && current) { + new (current) GRaceParameters(collectionKey, reinterpret_cast(indexData)); + current = reinterpret_cast(reinterpret_cast(current) + 0x14); + } + indexData += 0x30; + count++; + } + } + + return count; +} + +void GRaceDatabase::BuildRaceList() { + unsigned int i; + + mRaceCountStatic = StoreRaceList(nullptr); + mRaceCountDynamic = 0; + mRaceIndex = reinterpret_cast(bMalloc(mRaceCountStatic * 0x30, 0)); + mRaceParameters = static_cast(bMalloc(mRaceCountStatic * 0x14, 0)); + for (i = 0; i < 4; i++) { + mRaceCustom[i] = nullptr; + } + StoreRaceList(mRaceParameters); +} + +void GRaceDatabase::RefreshBinProgress() { + unsigned int i; + + for (i = 0; i < mBinCount; i++) { + mBins[i].RefreshProgress(); + } +} + +GRaceParameters *GRaceDatabase::GetRaceParameters(unsigned int index) { + if (mRaceCountStatic <= index) { + return mRaceCustom[index - mRaceCountStatic]; + } + + return &mRaceParameters[index]; +} + +GRaceParameters *GRaceDatabase::GetRaceFromKey(unsigned int key) { + unsigned int i; + + for (i = 0; i < GetRaceCount(); i++) { + GRaceParameters *race; + + race = GetRaceParameters(i); + if (race && race->GetCollectionKey() == key) { + return race; + } + } + + return nullptr; +} + +GRaceParameters *GRaceDatabase::GetRaceFromHash(unsigned int hash) { + unsigned int i; + + for (i = 0; i < GetRaceCount(); i++) { + GRaceParameters *race; + + race = GetRaceParameters(i); + if (race && race->GetEventHash() == hash) { + return race; + } + } + + return nullptr; +} + +void GRaceDatabase::DestroyCustomRace(GRaceCustom *custom) { + unsigned int i; + + if (custom) { + delete custom; + } + + for (i = 0; i < 4; i++) { + if (mRaceCustom[i] == custom) { + unsigned int lastIndex; + + lastIndex = mRaceCountDynamic - 1; + if (i < lastIndex) { + mRaceCustom[i] = mRaceCustom[lastIndex]; + } + mRaceCountDynamic = lastIndex; + mRaceCustom[lastIndex] = nullptr; + return; + } + } +} + +void GRaceDatabase::ClearStartupRace() { + if (mStartupRace && mStartupRace->GetFreedByOwner()) { + DestroyCustomRace(mStartupRace); + } + + mStartupRace = nullptr; + mStartupRaceContext = GRace::kRaceContext_QuickRace; +} + +void GRaceDatabase::SetStartupRace(GRaceCustom *custom, GRace::Context context) { + if (mStartupRace) { + ClearStartupRace(); + } + + mStartupRace = custom; + mStartupRaceContext = context; + + if (custom && mStartupRaceContext == GRace::kRaceContext_Career) { + custom->SetupTimeOfDay(); + } +} + +GRaceBin *GRaceDatabase::GetBinNumber(int number) { + unsigned int i; + + for (i = 0; i < GetBinCount(); i++) { + GRaceBin *bin; + + bin = &mBins[i]; + if (bin->GetBinNumber() == number) { + return bin; + } + } + + return nullptr; +} + +void GRaceDatabase::NotifyVaultLoaded(GVault *vault) { + unsigned int i; + + for (i = 0; i < GetRaceCount(); i++) { + GRaceParameters *race; + + race = GetRaceParameters(i); + if (race->GetParentVault() == vault) { + race->NotifyParentVaultLoaded(); + } + } +} + +void GRaceDatabase::NotifyVaultUnloading(GVault *vault) { + unsigned int i; + + for (i = 0; i < GetRaceCount(); i++) { + GRaceParameters *race; + + race = GetRaceParameters(i); + if (race->GetParentVault() == vault) { + race->NotifyParentVaultUnloading(); + } + } + + if (mStartupRace && vault == mStartupRace->GetParentVault()) { + ClearStartupRace(); + } +} + +unsigned int GRaceDatabase::SerializeBins(unsigned char *dest) { + unsigned int i; + unsigned char *cursor; + + *reinterpret_cast(dest) = mBinCount; + cursor = dest + 4; + for (i = 0; i < mBinCount; i++) { + unsigned short bytes; + + *reinterpret_cast(cursor) = static_cast(mBins[i].GetBinNumber()); + bytes = static_cast(mBins[i].Serialize(cursor + 4)); + *reinterpret_cast(cursor + 2) = bytes; + cursor += 4 + bytes; + } + + return cursor - dest; +} + +unsigned int GRaceDatabase::DeserializeBins(unsigned char *src) { + unsigned int count; + unsigned int i; + unsigned char *cursor; + + count = *reinterpret_cast(src); + cursor = src + 4; + for (i = 0; i < count; i++) { + GRaceBin *bin; + unsigned short bytes; + + bytes = *reinterpret_cast(cursor + 2); + bin = GetBinNumber(*reinterpret_cast(cursor)); + if (bin) { + bin->Deserialize(cursor + 4); + } + cursor += 4 + bytes; + } + + return cursor - src; +} + +GRaceParameters *GRaceDatabase::GetRaceFromActivity(GActivity *activity) { + return GetRaceFromKey(activity->GetCollection()); +} + +void GRaceDatabase::FreeCustomRace(GRaceCustom *custom) { + if (!custom) { + return; + } + + if (custom == mStartupRace) { + custom->SetFreedByOwner(); + } else { + DestroyCustomRace(custom); + } +} + +GRaceCustom *GRaceDatabase::AllocCustomRace(GRaceParameters *parms) { + GRaceCustom *custom; + + custom = nullptr; + if (parms) { + parms->BlockUntilLoaded(); + custom = new GRaceCustom(*parms); + mRaceCustom[mRaceCountDynamic++] = custom; + } + + return custom; +} + +void GRaceDatabase::BuildScoreList() { + unsigned int i; + + mRaceScoreInfo = static_cast(bMalloc(mRaceCountStatic << 4, 0x800)); + if (!mRaceScoreInfo) { + return; + } + + bMemSet(mRaceScoreInfo, 0, mRaceCountStatic << 4); + + for (i = 0; i < mRaceCountStatic; i++) { + GRaceParameters *race; + + race = &mRaceParameters[i]; + if (!race->GetIsDDayRace()) { + bool unlockedQuick; + bool unlockedOnline; + bool unlockedChallenge; + + unlockedQuick = race->GetInitiallyUnlockedQuickRace(); + unlockedOnline = race->GetInitiallyUnlockedOnline(); + unlockedChallenge = race->GetInitiallyUnlockedChallenge(); + + if (race->GetIsBossRace()) { + unlockedQuick = false; + unlockedOnline = false; + unlockedChallenge = false; + } + + if (unlockedQuick || unlockedOnline || unlockedChallenge) { + unsigned int *flags; + + flags = &reinterpret_cast(GetScoreInfo(race->GetEventHash()))[1]; + if (unlockedQuick || unlockedChallenge) { + *flags |= kUnlocked_QuickRace; + } + if (unlockedOnline) { + *flags |= kUnlocked_Online; + } + } + } + } +} + +void GRaceDatabase::ClearRaceScores() { + if (!mRaceScoreInfo) { + return; + } + + bMemSet(mRaceScoreInfo, 0, mRaceCountStatic << 4); + for (unsigned int onRace = 0; onRace < mRaceCountStatic; onRace++) { + GRaceParameters *parms; + bool unlockedQR; + bool unlockedOnline; + bool unlockedChallenge; + + parms = &mRaceParameters[onRace]; + if (!parms->GetIsDDayRace()) { + unlockedQR = parms->GetInitiallyUnlockedQuickRace(); + unlockedOnline = parms->GetInitiallyUnlockedOnline(); + unlockedChallenge = parms->GetInitiallyUnlockedChallenge(); + + if (parms->GetIsBossRace()) { + unlockedChallenge = false; + } + + if (unlockedQR || unlockedOnline || unlockedChallenge) { + GRaceSaveInfo *info; + + info = GetScoreInfo(parms->GetEventHash()); + if (unlockedQR || unlockedChallenge) { + reinterpret_cast(info)[1] |= kUnlocked_QuickRace; + } + if (unlockedOnline) { + reinterpret_cast(info)[1] |= kUnlocked_Online; + } + } + } + } +} + +GRaceSaveInfo *GRaceDatabase::GetScoreInfo(unsigned int hash) { + unsigned int i; + int *scoreInfo; + + i = 0; + scoreInfo = reinterpret_cast(mRaceScoreInfo); + while (i < mRaceCountStatic && *scoreInfo) { + if (static_cast(*scoreInfo) == hash) { + return reinterpret_cast(scoreInfo); + } + i++; + scoreInfo += 4; + } + + *scoreInfo = static_cast(hash); + return reinterpret_cast(scoreInfo); +} + +bool GRaceDatabase::CheckRaceScoreFlags(unsigned int hash, ScoreFlags flags) { + GRaceSaveInfo *scoreInfo; + + scoreInfo = GetScoreInfo(hash); + return scoreInfo && (reinterpret_cast(scoreInfo)[1] & flags) != 0; +} + +void GRaceDatabase::ResetCareerCompleteFlag(unsigned int hash) { + unsigned int *flags; + + flags = &reinterpret_cast(GetScoreInfo(hash))[1]; + *flags &= ~kCompleted_ContextCareer; +} + +void GRaceDatabase::LoadBestScores(GRaceSaveInfo *scores, unsigned int count) { + int *scoreWords; + int *dest; + unsigned int loaded; + unsigned int i; + + loaded = 0; + i = 0; + scoreWords = reinterpret_cast(scores); + dest = reinterpret_cast(mRaceScoreInfo); + bMemSet(mRaceScoreInfo, 0, mRaceCountStatic << 4); + while (i < count && loaded != mRaceCountStatic) { + if (scoreWords[0] && GetRaceFromHash(scoreWords[0])) { + loaded++; + bMemCpy(dest, scoreWords, 0x10); + dest += 4; + } + scoreWords += 4; + i++; + } +} + +const char *GRaceDatabase::GetNextDDayRace() { + int i; + + for (i = 0; i < 5; i++) { + GRaceParameters *race; + + race = GetRaceFromHash(Attrib::StringHash32(kDDayRaces[i])); + if (!CheckRaceScoreFlags(race->GetEventHash(), kCompleted_ContextCareer)) { + return kDDayRaces[i]; + } + } + + return nullptr; +} + +void GRaceDatabase::UpdateRaceScore(bool raceCompleted) { + GRaceParameters *parms; + unsigned int eventHash; + GRacerInfo *racerInfo; + GRaceSaveInfo *saveInfo; + float recordValue; + float value; + + parms = GRaceStatus::Get().GetRaceParameters(); + eventHash = parms->GetEventHash(); + racerInfo = GRaceStatus::Get().GetWinningPlayerInfo(); + if (!racerInfo) { + return; + } + + saveInfo = GetScoreInfo(eventHash); + + recordValue = racerInfo->GetTopSpeed(); + value = static_cast(*reinterpret_cast(reinterpret_cast(saveInfo) + 0x0C)) / + FixedPoint::GetScale(); + recordValue = UMath::Max(recordValue, value); + *reinterpret_cast(reinterpret_cast(saveInfo) + 0x0C) = + FixedPoint(recordValue).mValue; + + recordValue = racerInfo->CalcAverageSpeed(); + value = static_cast(*reinterpret_cast(reinterpret_cast(saveInfo) + 0x0E)) / + FixedPoint::GetScale(); + recordValue = UMath::Max(recordValue, value); + *reinterpret_cast(reinterpret_cast(saveInfo) + 0x0E) = + FixedPoint(recordValue).mValue; + + switch (parms->GetRaceType()) { + case GRace::kRaceType_Circuit: + case GRace::kRaceType_Knockout: + if ((reinterpret_cast(saveInfo)[1] & 3) == 0 || + GRaceStatus::Get().GetBestLapTime(racerInfo->GetIndex()) < *reinterpret_cast(reinterpret_cast(saveInfo) + 2)) { + *reinterpret_cast(reinterpret_cast(saveInfo) + 2) = GRaceStatus::Get().GetBestLapTime(racerInfo->GetIndex()); + } + break; + + case GRace::kRaceType_P2P: + case GRace::kRaceType_Drag: + case GRace::kRaceType_Tollbooth: + case GRace::kRaceType_JumpToSpeedTrap: + case GRace::kRaceType_JumpToMilestone: + if ((reinterpret_cast(saveInfo)[1] & 3) == 0 || + racerInfo->GetRaceTime() < *reinterpret_cast(reinterpret_cast(saveInfo) + 2)) { + *reinterpret_cast(reinterpret_cast(saveInfo) + 2) = racerInfo->GetRaceTime(); + } + break; + + case GRace::kRaceType_Checkpoint: + { + unsigned int score; + + score = static_cast(racerInfo->GetPointTotal()); + if ((reinterpret_cast(saveInfo)[1] & 3) != 0 && + score <= reinterpret_cast(saveInfo)[2]) { + break; + } + reinterpret_cast(saveInfo)[2] = score; + } + break; + + case GRace::kRaceType_SpeedTrap: + case GRace::kRaceType_CashGrab: + value = racerInfo->GetPointTotal(); + if ((reinterpret_cast(saveInfo)[1] & 3) != 0 && + value <= *reinterpret_cast(reinterpret_cast(saveInfo) + 2)) { + break; + } + *reinterpret_cast(reinterpret_cast(saveInfo) + 2) = value; + break; + + default: + break; + } + + if (raceCompleted) { + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + reinterpret_cast(saveInfo)[1] |= kCompleted_ContextCareer; + } else { + reinterpret_cast(saveInfo)[1] |= kCompleted_ContextQuickRace; + } + RefreshBinProgress(); + } +} diff --git a/src/Speed/Indep/Src/Gameplay/GRaceDatabase.h b/src/Speed/Indep/Src/Gameplay/GRaceDatabase.h index 0cc743d17..dce088992 100644 --- a/src/Speed/Indep/Src/Gameplay/GRaceDatabase.h +++ b/src/Speed/Indep/Src/Gameplay/GRaceDatabase.h @@ -5,6 +5,7 @@ #pragma once #endif +#include "GRace.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h" #include "Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h" @@ -16,12 +17,15 @@ enum Context { }; class GVault; +class GActivity; class GRaceCustom; class GRaceParameters; // total size: 0x1C class GRaceBin { public: + friend class GRaceDatabase; + // total size: 0x4 struct BinStats { uint16 mChallengesCompleted; // offset 0x0, size 0x2 @@ -107,6 +111,9 @@ class GRaceBin { // total size: 0x40 class GRaceDatabase { public: + friend class GRaceBin; + friend class GRaceParameters; + enum ScoreFlags { kCompleted_ContextQuickRace = 1 << 0, kCompleted_ContextCareer = 1 << 1, @@ -116,14 +123,46 @@ class GRaceDatabase { kUnlocked_Online = 1 << 4, }; + GRaceDatabase(); + static void Init(); GRaceCustom *GetStartupRace(); - void SetStartupRace(GRaceCustom *custom, Context context); + GRace::Context GetStartupRaceContext(); + void SetStartupRace(GRaceCustom *custom, GRace::Context context); void FreeCustomRace(GRaceCustom *custom); + void DestroyCustomRace(GRaceCustom *custom); GRaceParameters *GetRaceFromHash(unsigned int hash); + GRaceParameters *GetRaceFromKey(unsigned int key); + GRaceParameters *GetRaceParameters(unsigned int index); GRaceCustom *AllocCustomRace(GRaceParameters *parms); + unsigned int GetBinCount(); + GRaceBin *GetBin(unsigned int index); + GRaceBin *GetBinNumber(int number); + unsigned int GetRaceCount(); + + void SimulateDDayComplete(); + + void NotifyVaultLoaded(GVault *vault); + void NotifyVaultUnloading(GVault *vault); + unsigned int SerializeBins(unsigned char *dest); + unsigned int DeserializeBins(unsigned char *src); + GRaceParameters *GetRaceFromActivity(GActivity *activity); + const char *GetDDayEndRace() const { + return "16.2.1"; + } + const char *GetNextDDayRace(); + void UpdateRaceScore(bool raceCompleted); + + bool IsCareerRaceComplete(unsigned int eventHash) { + return CheckRaceScoreFlags(eventHash, kCompleted_ContextCareer); + } + + bool IsCareerRaceUnlocked(unsigned int eventHash) { + return CheckRaceScoreFlags(eventHash, kUnlocked_Career); + } + static GRaceDatabase &Get() { return *mObj; } @@ -136,7 +175,23 @@ class GRaceDatabase { return GetRaceFromHash(Attrib::StringHash32(name)); } + void ResetCareerCompleteFlag(unsigned int hash); + private: + void BuildBinList(); + unsigned int StoreBinList(GRaceBin *dest); + void RefreshBinProgress(); + void BuildRaceList(); + unsigned int StoreRaceList(GRaceParameters *dest); + bool CollectionIsRaceActivity(Attrib::Gen::gameplay &collection); + bool CollectionIsRaceBin(Attrib::Gen::gameplay &collection); + void BuildScoreList(); + void ClearRaceScores(); + void LoadBestScores(struct GRaceSaveInfo *scores, unsigned int count); + struct GRaceSaveInfo *GetScoreInfo(unsigned int hash); + bool CheckRaceScoreFlags(unsigned int hash, ScoreFlags flags); + void ClearStartupRace(); + unsigned int mRaceCountStatic; // offset 0x0, size 0x4 unsigned int mRaceCountDynamic; // offset 0x4, size 0x4 struct GRaceIndexData *mRaceIndex; // offset 0x8, size 0x4 @@ -146,7 +201,7 @@ class GRaceDatabase { GRaceBin *mBins; // offset 0x24, size 0x4 Attrib::Class *mGameplayClass; // offset 0x28, size 0x4 struct GRaceCustom *mStartupRace; // offset 0x2C, size 0x4 - Context mStartupRaceContext; // offset 0x30, size 0x4 + GRace::Context mStartupRaceContext; // offset 0x30, size 0x4 unsigned int mNumInitialUnlocks; // offset 0x34, size 0x4 unsigned int *mInitialUnlockHash; // offset 0x38, size 0x4 struct GRaceSaveInfo *mRaceScoreInfo; // offset 0x3C, size 0x4 diff --git a/src/Speed/Indep/Src/Gameplay/GRaceStatus.cpp b/src/Speed/Indep/Src/Gameplay/GRaceStatus.cpp index e69de29bb..52afa2d8a 100644 --- a/src/Speed/Indep/Src/Gameplay/GRaceStatus.cpp +++ b/src/Speed/Indep/Src/Gameplay/GRaceStatus.cpp @@ -0,0 +1,4470 @@ +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" + +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GMarker.h" +#include "Speed/Indep/Src/Gameplay/GObjectBlock.h" +#include "Speed/Indep/Src/Gameplay/GVault.h" +#include "Speed/Indep/Src/Generated/Messages/MLoadingComplete.h" +#include "Speed/Indep/Src/Generated/Messages/MNotifyRaceTime.h" +#include "Speed/Indep/Src/Generated/Messages/MNotifyRaceTimeExpired.h" +#include "Speed/Indep/Src/Generated/Messages/MNotifyRaceTimeSecTick.h" +#include "Speed/Indep/Src/Generated/Events/EAutoSave.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOn.hpp" +#include "Speed/Indep/Src/Generated/Events/EReloadHud.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Interfaces/SimActivities/ICopMgr.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ITrafficMgr.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAudible.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngine.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRenderable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Main/AttribSupport.h" +#include "Speed/Indep/Src/Physics/PVehicle.h" +#include "Speed/Indep/Src/Misc/Table.hpp" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/WCollisionAssets.h" +#include "Speed/Indep/Src/World/WRoadNetwork.h" +#include "Speed/Indep/Src/World/TrackInfo.hpp" +#include "Speed/Indep/Src/World/WorldModel.hpp" +#include "Speed/Indep/Src/World/TrackStreamer.hpp" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +#include + +void SetCurrentTimeOfDay(float value); +void SetOverRideRainIntensity(float intensity); +bool DoesStringExist(unsigned int hash); +const char *GetLocalizedString(unsigned int hash); +extern int UnlockAllThings; + +void EnableBarrierSceneryGroup(const char *name, bool flipped); +void RedoTopologyAndSceneryGroups(); +extern int SkipFE; +extern const char *SkipFEOpponentPresetRide; +unsigned int bStringHashUpper(const char *text); + +struct PresetCar; +struct CarPartDatabase; + +HINTERFACE IAudible::_IHandle() { + return (HINTERFACE)_IHandle; +} + +extern CarPartDatabase CarPartDB; + +FECustomizationRecord *FECustomizationRecordCtor(FECustomizationRecord *self) __asm__("__21FECustomizationRecord"); +PresetCar *FindFEPresetCar(unsigned int key) __asm__("FindFEPresetCar__FUi"); +void FECustomizationRecordBecomePreset(FECustomizationRecord *self, PresetCar *preset) + __asm__("BecomePreset__21FECustomizationRecordP9PresetCar"); +void FECustomizationRecordWriteRideIntoRecord(FECustomizationRecord *self, const RideInfo *ride) + __asm__("WriteRideIntoRecord__21FECustomizationRecordPC8RideInfo"); +void RideInfoSetRandomParts(RideInfo *self) __asm__("SetRandomParts__8RideInfo"); +CarType CarPartDatabaseGetCarType(CarPartDatabase *self, unsigned int key) __asm__("GetCarType__15CarPartDatabaseUi"); + +#ifndef DECLARE_GAMEPLAY_MINIMAP_CLASS +#define DECLARE_GAMEPLAY_MINIMAP_CLASS +class Minimap { + public: + static void ConvertPos(bVector2 &worldPos, bVector2 &minimapPos, TrackInfo *track); +}; +#endif + +struct GRaceStatusCompat { + unsigned char _pad[0x1AB0]; + GRaceBin *mRaceBin; +}; + +struct GManagerRestartCompat { + unsigned char _pad[0x304]; + unsigned int mRestartEventHash; +}; + +template <> struct GObjectIteratorTraits { + enum { kType = kGameplayObjType_Trigger }; +}; + +template <> +const GCollectionKey &Attrib::TAttrib::Get(unsigned int index) const { + const GCollectionKey *resultptr = reinterpret_cast(GetElementPointer(index)); + + if (!resultptr) { + resultptr = reinterpret_cast(Attrib::DefaultDataArea(sizeof(GCollectionKey))); + } + + return *resultptr; +} + +GRaceStatus *GRaceStatus::fObj = nullptr; + +DECLARE_CONTAINER_TYPE(ID_ROAD_SET); +DECLARE_CONTAINER_TYPE(ID_PATH_SET); + +struct PathSegment { + bool operator<(const PathSegment &rhs) const { + return Length < rhs.Length; + } + + float Length; + UTL::Std::set Roads; +}; + +template struct FixedPoint { + FixedPoint(float val) + : mValue(static_cast(static_cast(val * static_cast(GetScale())))) {} + + static int GetScale(); + + T mValue; +}; + +template +int FixedPoint::GetScale() { + return 1 << FractionBits; +} + +template +struct FloatingPoint { + FloatingPoint(float val) { + bool neg = false; + int man = 0; + int exp = 0; + + if (val < 0.0f) { + neg = true; + val = -val; + } + + if (0.0f < val) { + while (val < static_cast(GetNormalizedLower())) { + exp--; + val *= 10.0f; + } + + man = static_cast(val); + while (man >= GetNormalizedUpper()) { + exp++; + man /= 10; + } + } + + if (neg) { + man = -man; + } + + mValue = static_cast(((exp & 0x1F) << MantissaBits) | (man & ((1 << MantissaBits) - 1))); + } + + static int GetNormalizedLower(); + + static int GetNormalizedUpper(); + + T mValue; +}; + +template +int FloatingPoint::GetNormalizedLower() { + return 1 << (MantissaBits - 1); +} + +template +int FloatingPoint::GetNormalizedUpper() { + return 1 << MantissaBits; +} + +static const float Tweak_GlueSpreadData_Low[5] = { + 1000.0f, + 900.0f, + 750.0f, + 600.0f, + 800.0f, +}; + +static const float Tweak_GlueSpreadData_High[5] = { + 300.0f, + 275.0f, + 250.0f, + 150.0f, + 250.0f, +}; + +static const float Tweak_GlueStrengthData_Low[5] = { + 0.75f, + 0.75f, + 0.75f, + 0.5f, + 0.25f, +}; + +static const float Tweak_GlueStrengthData_High[5] = { + 0.75f, + 0.75f, + 0.75f, + 0.75f, + 0.25f, +}; + +static const float Tweak_QuickRaceGlue[3] = { + 0.0f, + 0.5f, + 1.0f, +}; + +Table Tweak_GlueSpreadTable_Low(Tweak_GlueSpreadData_Low, 5, 0.0f, 100.0f); +Table Tweak_GlueSpreadTable_High(Tweak_GlueSpreadData_High, 5, 0.0f, 100.0f); +Table Tweak_GlueStrengthTable_Low(Tweak_GlueStrengthData_Low, 5, 0.0f, 100.0f); +Table Tweak_GlueStrengthTable_High(Tweak_GlueStrengthData_High, 5, 0.0f, 100.0f); + +int NotNumeric(char c) { + if (c == '-' || c == '.') { + return 0; + } + + int numeric = '0'; + if (c > '0') { + numeric = c; + } + if (numeric > '9') { + numeric = '9'; + } + + return c != numeric; +} + +int SplitChars(char *in, char ***array, int (*func)(char)) { + while (*in != '\0' && func(*in) != 0) { + in += 1; + } + + int count = 0; + char *cursor = in; + + while (*cursor != '\0') { + if (func(*cursor) == 0) { + count += 1; + while (*cursor != '\0' && func(*cursor) == 0) { + cursor += 1; + } + } else { + cursor += 1; + } + } + + *array = new char *[count]; + if (count > 0) { + int i = 0; + + do { + char *end = in; + + (*array)[i] = in; + i += 1; + while (*end != '\0' && func(*end) == 0) { + end += 1; + } + + in = end; + while (*in != '\0' && func(*in) != 0) { + in += 1; + } + + *end = '\0'; + } while (i < count); + } + + return count; +} + +float ParseFloat(char *word) { + float whole = 0.0f; + unsigned int index = static_cast(*word == '-'); + bool pastDecimal = false; + float scale = 1.0f; + + for (char c = word[index]; c != '\0'; c = word[++index]) { + if (c == '.') { + pastDecimal = true; + } else { + if (pastDecimal) { + scale *= 0.1f; + } else { + whole *= 10.0f; + } + + int digit = c - '0'; + + if (digit < 0) { + digit = 0; + } + if (digit > 9) { + digit = 9; + } + + whole = scale * static_cast(digit) + whole; + } + } + + return whole; +} + +bool GRacerInfo::GetIsHuman() const { + ISimable *simable = GetSimable(); + return simable && simable->IsPlayer(); +} + +void GRacerInfo::SetName(const char *name) { + mName = name; +} + +void GRacerInfo::SetIndex(int index) { + mIndex = index; +} + +void GRacerInfo::SetRanking(int ranking) { + mRanking = ranking; +} + +void GRacerInfo::SetSimable(ISimable *isim) { + mhSimable = isim ? isim->GetInstanceHandle() : nullptr; +} + +IVehicle *GRacerInfo::CreateVehicle(unsigned int default_key) { + GCharacter *racerChar = GetGameCharacter(); + const char *carName; + const char *carNameLowMem; + const char *presetRide; + FECustomizationRecord customizations; + unsigned int vehicle_key; + + if (!racerChar) { + return nullptr; + } + + carName = racerChar->CarType(0); + carNameLowMem = racerChar->CarTypeLowMem(0); + presetRide = racerChar->PresetRide(0); + FECustomizationRecordCtor(&customizations); + + if (SkipFE && bStrLen(SkipFEOpponentPresetRide) > 0) { + presetRide = SkipFEOpponentPresetRide; + } + + vehicle_key = 0; + if (presetRide) { + PresetCar *preset = FindFEPresetCar(bStringHashUpper(presetRide)); + + if (preset) { + FECustomizationRecordBecomePreset(&customizations, preset); + vehicle_key = *reinterpret_cast(reinterpret_cast(preset) + 0x54); + } + } + + if (vehicle_key == 0) { + if (carName && *carName) { + vehicle_key = Attrib::StringKey(carName); + } + + if (vehicle_key == 0) { + vehicle_key = default_key; + } + } + + Attrib::Gen::pvehicle attributes(vehicle_key, 0, nullptr); + + if (!attributes.IsValid()) { + return nullptr; + } + + if (!customizations.IsPreset()) { + RideInfo ride; + const char *modelName = attributes.MODEL().GetString(); + + if (!modelName) { + modelName = ""; + } + + ride.Init(CarPartDatabaseGetCarType(&CarPartDB, bStringHashUpper(modelName)), CarRenderUsage_AIRacer, 0, 0); + RideInfoSetRandomParts(&ride); + FECustomizationRecordWriteRideIntoRecord(&customizations, &ride); + } + + Physics::Info::Performance ai_performance(1.0f, 1.0f, 1.0f); + IVehicleCache *cache = nullptr; + if (GRaceStatus::Exists()) { + GRaceStatus *raceStatus = &GRaceStatus::Get(); + + cache = reinterpret_cast(reinterpret_cast(raceStatus) + 0x10); + } + + UMath::Vector3 direction = {0.0f, 0.0f, 1.0f}; + VehicleParams params(cache, DRIVER_RACER, vehicle_key, direction, UMath::Vector3::kZero, 0, &customizations, &ai_performance); + ISimable *result = UTL::COM::Factory::CreateInstance("PVehicle", params); + if (result) { + IVehicle *vehicle; + + if (result->QueryInterface(&vehicle)) { + SetSimable(result); + return vehicle; + } + } + + return nullptr; +} + +void GRacerInfo::KnockOut() { + if (mFinishedRacing) { + return; + } + + mKnockedOut = true; + mFinishedRacing = true; + GRaceStatus::Get().CalculateRankings(); +} + +void GRacerInfo::TotalVehicle() { + if (mFinishedRacing) { + return; + } + + mTotalled = true; + mFinishedRacing = true; + GRaceStatus::Get().CalculateRankings(); +} + +void GRacerInfo::Busted() { + if (mFinishedRacing) { + return; + } + + mBusted = true; + mFinishedRacing = true; + GRaceStatus::Get().CalculateRankings(); +} + +void GRacerInfo::ChallengeComplete() { + mChallengeComplete = true; +} + +void GRacerInfo::ForceStop() { + ISimable *simable = GetSimable(); + mRaceTimer.Stop(); + mLapTimer.Stop(); + mCheckTimer.Stop(); + mFinishedRacing = true; + mFinishingSpeed = 0.0f; + + if (simable) { + IVehicle *vehicle; + + if (simable->QueryInterface(&vehicle)) { + vehicle->SetSpeed(0.0f); + } + } +} + +void GRacerInfo::BlowEngine() { + if (mFinishedRacing) { + return; + } + + mEngineBlown = true; + mFinishedRacing = true; + GRaceStatus::Get().CalculateRankings(); +} + +void GRacerInfo::AddToPointTotal(float points) { + mPointTotal += points; + mPointTotal = UMath::Max(0.0f, mPointTotal); +} + +float GRacerInfo::CalcAverageSpeed() const { + float time = mRaceTimer.GetTime(); + + if (time <= 0.0f) { + return 0.0f; + } + + return mDistanceDriven / time; +} + +void GRacerInfo::ChooseRandomName() { + char nameBuffer[32]; + const char *name; + + if (!DoesStringExist(Attrib::StringHash32("RACERNAME_000"))) { + mName = "UNKNOWN"; + return; + } + + do { + bool duplicate = false; + int i; + const int racerCount = GRaceStatus::Get().GetRacerCount(); + + bSPrintf(nameBuffer, "RACERNAME_%03d", bRandom(0x96)); + name = GetLocalizedString(bStringHash(nameBuffer)); + + for (i = 0; i < racerCount; ++i) { + GRacerInfo &info = GRaceStatus::Get().GetRacerInfo(i); + + if (info.mName && bStrCmp(name, info.mName) == 0) { + duplicate = true; + break; + } + } + + if (!duplicate) { + mName = name; + return; + } + } while (true); +} + +bool GRacerInfo::ChooseRacerName() { + unsigned int nameHash; + + if (GRaceStatus::Exists()) { + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + if ((nameHash = mGameCharacter->GetName()) != 0) { + mName = GetLocalizedString(nameHash); + return true; + } + } + } + + return false; +} + +bool GRacerInfo::ChooseBossName() { + char stringBuffer[32]; + GRaceBin *raceBin; + + if (GRaceStatus::Exists()) { + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + GRaceParameters *raceParameters = GRaceStatus::Get().GetRaceParameters(); + + if (raceParameters) { + if (raceParameters->GetIsBossRace()) { + raceBin = reinterpret_cast(&GRaceStatus::Get())->mRaceBin; + bSNPrintf(stringBuffer, 0x20, "BLACKLIST_RIVAL_%02d_LEADERBOARD", raceBin->GetBinNumber()); + mName = GetLocalizedString(bStringHash(stringBuffer)); + return true; + } + } + } + } + + return false; +} + +float GRacerInfo::GetHudPctRaceComplete() const { + float startPercent = 0.0f; + + if (GRaceStatus::Get().GetRaceParameters()) { + startPercent = GRaceStatus::Get().GetRaceParameters()->GetStartPercent(); + } + + if (mFinishedRacing) { + return 1.0f; + } + + return bClamp(startPercent + ((1.0f - startPercent) * mPctRaceComplete), 0.0f, 1.0f); +} + +void GRacerInfo::StartRace() { + GRaceParameters *raceParameters = GRaceStatus::Get().GetRaceParameters(); + float startTime = raceParameters ? raceParameters->GetStartTime() : 0.0f; + + mRaceTimer.Reset(startTime); + mRaceTimer.Start(); + StartLap(1); +} + +void GRacerInfo::StartLap(int lapIndexOneBased) { + mLapsCompleted = lapIndexOneBased - 1; + mCheckpointsHitThisLap = 0; + mLapTimer.Reset(0.0f); + mLapTimer.Start(); + StartCheckpoint(0); +} + +void GRacerInfo::StartCheckpoint(int checkIndex) { + if (checkIndex == mCheckpointsHitThisLap + 1) { + mCheckpointsHitThisLap = checkIndex; + } + + mCheckTimer.Reset(0.0f); + mCheckTimer.Start(); + mDistToNextCheckpoint = 0.0f; + mTimeCrossedLastCheck = mRaceTimer.GetTime(); +} + +void GRacerInfo::NotifySpeedTrapTriggered(float speed) { + if (mSpeedTrapsCrossed < 16) { + mSpeedTrapSpeed[mSpeedTrapsCrossed] = speed; + mSpeedTrapPosition[mSpeedTrapsCrossed] = mRanking; + } + + ++mSpeedTrapsCrossed; +} + +void GRacerInfo::FinishRace() { + ISimable *simable; + UMath::Vector3 linearVelocity; + + mRaceTimer.Stop(); + mFinishedRacing = true; + + simable = GetSimable(); + if (simable) { + simable->GetLinearVelocity(linearVelocity); + mFinishingSpeed = UMath::Length(linearVelocity); + } +} + +bool GRacerInfo::IsBehind(const GRacerInfo &rhs) const { + if (IsFinishedRacing()) { + if (rhs.IsFinishedRacing()) { + return GetRaceTime() > rhs.GetRaceTime(); + } + return false; + } + + if (rhs.IsFinishedRacing()) { + return true; + } + + if (GetIsKnockedOut() && rhs.GetIsKnockedOut()) { + return GetRaceTime() < rhs.GetRaceTime(); + } + + if (GetIsEngineBlown() && rhs.GetIsEngineBlown()) { + return GetRaceTime() < rhs.GetRaceTime(); + } + + if (GetIsTotalled() && rhs.GetIsTotalled()) { + return GetRaceTime() < rhs.GetRaceTime(); + } + +#ifndef EA_BUILD_A124 + if (GetDNF()) { + if (!rhs.GetDNF()) { + return GetPctRaceComplete() > rhs.GetPctRaceComplete(); + } + } +#endif + + if (GetIsKnockedOut() || GetIsEngineBlown() || GetIsTotalled()) { + return true; + } + + if (rhs.GetIsKnockedOut() || rhs.GetIsEngineBlown() || rhs.GetIsTotalled()) { + return false; + } + + if (mLapsCompleted != rhs.mLapsCompleted) { + return mLapsCompleted < rhs.mLapsCompleted; + } + + return GetPctRaceComplete() < rhs.GetPctRaceComplete(); +} + +bool GRacerInfo::AreStatsReady() const { + if (GRaceStatus::Get().GetRaceContext() != GRace::kRaceContext_TimeTrial) { + return true; + } + + return mFinishedRacing || mEngineBlown || mTotalled; +} + +void GRacerInfo::SaveStartPosition() { + mSavedHeatLevel = 0.0f; + mSavedSpeed = 0.0f; + mSavedPosition = UMath::Vector3::kZero; + mSavedDirection = UMath::Vector3::kZero; + + ISimable *simable = GetSimable(); + + if (!simable) { + return; + } + + { + IRigidBody *rigidBody = simable->GetRigidBody(); + + if (rigidBody) { + mSavedPosition = rigidBody->GetPosition(); + mSavedSpeed = rigidBody->GetSpeed(); + rigidBody->GetForwardVector(mSavedDirection); + if (mSavedSpeed == 0.0f) { + mSavedSpeed = GRaceStatus::Get().GetRaceParameters()->GetInitialPlayerSpeed(); + } + } + IPerpetrator *perp; + + if (simable->QueryInterface(&perp)) { + mSavedHeatLevel = perp->GetHeat(); + } + } +} + +void GRacerInfo::RestoreStartPosition() { + ISimable *simable = GetSimable(); + IRacer *racer; + + if (!simable || !simable->QueryInterface(&racer)) { + return; + } + + RacePreparationInfo rpi; + rpi.Position = mSavedPosition; + rpi.Speed = mSavedSpeed; + rpi.Direction = mSavedDirection; + rpi.HeatLevel = mSavedHeatLevel; + rpi.Flags = RacePreparationInfo::RESET_DAMAGE; + racer->PrepareForRace(rpi); +} + +void GRacerInfo::ForceStartPosition(const UMath::Vector3 &pos, const UMath::Vector3 &dir) { + mSavedPosition = pos; + mSavedDirection = dir; + mSavedSpeed = 0.0f; + RestoreStartPosition(); +} + +void GRacerInfo::Update(float dT) { + ISimable *simable; + IVehicleAI *vehicleAI; + IEngine *engine; + IPlayer *player; + UMath::Vector3 linearVelocity; + float speed; + float distance; + float raceLength; + + if (IsFinishedRacing() || GetIsEngineBlown() || GetIsTotalled() || GetIsKnockedOut()) { + return; + } + + simable = GetSimable(); + if (!simable) { + return; + } + + GRaceStatus &raceStatus = GRaceStatus::Get(); + + engine = nullptr; + if (simable->QueryInterface(&engine) && engine->IsNOSEngaged()) { + mPoundsNOSUsed += dT * engine->GetNOSFlowRate(); + } + + player = simable->GetPlayer(); + if (player && player->InGameBreaker()) { + mSpeedBreakerTime += dT; + } + + simable->GetLinearVelocity(linearVelocity); + speed = UMath::Length(linearVelocity); + if (speed > mTopSpeed) { + mTopSpeed = speed; + } + + distance = mDistanceDriven; + distance += speed * dT; + mTotalUpdateTime += dT; + mDistanceDriven = distance; + + #ifndef EA_BUILD_A124 + if (mQuarterMileTime == 0.0f) { + static float quarterMileInMeters = MILE2METERS(0.25f); + + if (distance >= quarterMileInMeters) { + mQuarterMileTime = GetRaceTime(); + } + } + + if (mZeroToSixtyTime == 0.0f) { + static float sixtyMphInMetersPerSec = MPH2MPS(60.0f); + + if (mTopSpeed >= sixtyMphInMetersPerSec) { + mZeroToSixtyTime = GetRaceTime(); + } + } +#endif + + raceLength = raceStatus.GetRaceLength(); + vehicleAI = nullptr; + if (simable->QueryInterface(&vehicleAI)) { + distance = vehicleAI->GetPathDistanceRemaining(); + } else { + distance = 0.0f; + } + mDistToNextCheckpoint = distance; + + if (raceLength > 0.0f) { + float raceDistanceCompleted = 0.0f; + int lapsCompleted = GetLapsCompleted(); + float lapDistanceCompleted; + int checkpointsCompleted; + float distanceToNextCheckpoint; + float currentSegmentLength; + + if (lapsCompleted > 0) { + raceDistanceCompleted = raceStatus.GetFirstLapLength(); + } + + if (lapsCompleted > 1) { + raceDistanceCompleted += raceStatus.GetSubsequentLapLength() * static_cast(lapsCompleted - 1); + } + + checkpointsCompleted = GetChecksHitThisLap(); + lapDistanceCompleted = 0.0f; + for (int i = 0; i < checkpointsCompleted; ++i) { + lapDistanceCompleted += raceStatus.GetSegmentLength(i, lapsCompleted); + } + + distanceToNextCheckpoint = GetDistToNextCheck(); + currentSegmentLength = raceStatus.GetSegmentLength(checkpointsCompleted, lapsCompleted); + if (distanceToNextCheckpoint != 0.0f) { + float currentDistanceCompleted; + float lapLength; + + lapDistanceCompleted += currentSegmentLength - distanceToNextCheckpoint; + raceDistanceCompleted += lapDistanceCompleted; + currentDistanceCompleted = raceDistanceCompleted; + lapLength = raceStatus.GetLapLength(lapsCompleted); + if (lapLength > 0.0f) { + mPctLapComplete = bClamp(lapDistanceCompleted / lapLength, 0.0f, 1.0f); + } else { + mPctLapComplete = 1.0f; + } + + mPctRaceComplete = bClamp(raceDistanceCompleted / raceLength, 0.0f, 1.0f); + } + } +} + +void GRacerInfo::UpdateSplits() { +#ifndef EA_BUILD_A124 + int split = -1; + + if (mPctRaceComplete >= 1.0f && mSplitTimes[3] == 0.0f) { + split = 3; + } else if (mPctRaceComplete >= 0.75f && mSplitTimes[2] == 0.0f) { + split = 2; + } else if (mPctRaceComplete >= 0.5f && mSplitTimes[1] == 0.0f) { + split = 1; + } else if (mPctRaceComplete >= 0.25f && mSplitTimes[0] == 0.0f) { + split = 0; + } + + if (split != -1) { + mSplitTimes[split] = mRaceTimer.GetTime(); + mSplitRankings[split] = mRanking; + } +#endif +} + +void GRacerInfo::FinalizeRaceStats() { + float currentTime; + float time_now; + float pctComplete; + + if (mFinishedRacing) { + return; + } + + currentTime = GetRaceTime(); + time_now = currentTime; + pctComplete = GetPctRaceComplete(); + if (0.1f < pctComplete) { + currentTime = currentTime / (pctComplete * 0.01f); + } + + if (GRaceStatus::Get().GetRaceType() == GRace::kRaceType_Drag && + (GetIsTotalled() || GetIsEngineBlown() || pctComplete < 1.0f)) { + currentTime = 0.0f; +#ifndef EA_BUILD_A124 + mDNF = true; +#endif + } + + if (GRaceStatus::Get().GetRaceType() == GRace::kRaceType_SpeedTrap && mGameCharacter) { + GRaceParameters *parameters = GRaceStatus::Get().GetRaceParameters(); + float penalty; + float point_loss; + + if (parameters) { + penalty = static_cast(parameters->GetGameplayObj()->OvertimePenaltyPerSec(0)); + point_loss = currentTime - time_now; + + if (0.0f < point_loss) { + point_loss *= penalty; + AddToPointTotal(-point_loss); + } + } + } + + { + GRaceParameters *parameters = GRaceStatus::Get().GetRaceParameters(); + + if (parameters->GetIsLoopingRace()) { + if (mGameCharacter) { + int totalLaps = GRaceStatus::Get().GetRaceParameters()->GetNumLaps(); + int completedLaps = GetLapsCompleted(); + float elapsedTime = 0.0f; + int onLap = 0; + + if (onLap < totalLaps) { + do { + float lapTime = GRaceStatus::Get().GetLapTime(onLap, GetIndex(), false); + + if (lapTime == 0.0f) { + completedLaps = onLap; + break; + } + + onLap++; + elapsedTime += lapTime; + } while (onLap < totalLaps); + } + + GRaceStatus::Get().SetLapTime(completedLaps, GetIndex(), currentTime - elapsedTime); + } + } + } + +#ifndef EA_BUILD_A124 + if (mGameCharacter) { + float effectivePct = mDNF ? pctComplete : 1.0f; + float pct[4] = {0.25f, 0.5f, 0.75f, 1.0f}; + int onSplit; + + for (onSplit = 0; onSplit <= 3; ++onSplit) { + bool wasAliveAtPct = effectivePct >= pct[onSplit]; + + if (mSplitTimes[onSplit] == 0.0f) { + if (wasAliveAtPct) { + mSplitTimes[onSplit] = currentTime * pct[onSplit]; + } else { + mSplitTimes[onSplit] = 0.0f; + } + } + + if (mSplitRankings[onSplit] == 0) { + mSplitRankings[onSplit] = mRanking; + } + } + } + + if (!mDNF) { + mRaceTimer.SetTime(currentTime); + FinishRace(); + } +#else + mRaceTimer.SetTime(currentTime); + FinishRace(); +#endif +} + +GRaceParameters::GRaceParameters(unsigned int collectionKey, GRaceIndexData *index) + : mIndex(nullptr), // + mRaceRecord(new Attrib::Gen::gameplay(collectionKey, 0, nullptr)), // + mParentVault(nullptr), // + mChildVault(nullptr) { + const char *childVaultName; + + GenerateIndex(index); + mIndex = index; + + childVaultName = mRaceRecord->gameplayvault(0); + if (childVaultName) { + mChildVault = GManager::Get().FindVault(childVaultName); + mRaceRecord->Num_Children(); + } + + mParentVault = GManager::Get().FindVaultContaining(mRaceRecord->GetCollection()); + if (mParentVault) { + mParentVault->IsTransient(); + } +} + +GRaceParameters::~GRaceParameters() { + delete mRaceRecord; + mRaceRecord = nullptr; + mIndex = nullptr; +} + +void GRaceParameters::GenerateIndex(GRaceIndexData *index) { + unsigned int flags; + unsigned int *indexWords; + char *indexBytes; + UMath::Vector2 topLeft; + UMath::Vector2 botRight; + float timeOfDay; + + if (!index) { + return; + } + + indexWords = reinterpret_cast(index); + indexBytes = reinterpret_cast(index); + + indexWords[0] = GetCollectionKey(); + indexWords[5] = GetEventHash(); + *reinterpret_cast(indexBytes + 0x20) = GetLocalizationTag(); + *reinterpret_cast(indexBytes + 0x1C) = GetRaceLengthMeters(); + *reinterpret_cast(indexBytes + 0x26) = + FixedPoint(GetRivalBestTime()).mValue; + *reinterpret_cast(indexBytes + 0x24) = + FloatingPoint(static_cast(GetReputation())).mValue; + *reinterpret_cast(indexBytes + 0x22) = + FloatingPoint(GetCashValue()).mValue; + indexWords[4] = GetChallengeType(); + *reinterpret_cast(indexBytes + 0x0E) = + FloatingPoint(GetChallengeGoal()).mValue; + indexBytes[0x28] = static_cast(GetNumLaps()); + indexBytes[0x2A] = static_cast(GetCopDensity()); + indexBytes[0x2B] = static_cast(GetRaceType()); + indexBytes[0x29] = static_cast(GetRegion()); + + flags = 0; + indexWords[6] = 0; + if (GetInitiallyUnlockedQuickRace()) { + flags |= 1 << 0; + } + indexWords[6] = flags; + if (GetInitiallyUnlockedOnline()) { + flags |= 1 << 1; + } + indexWords[6] = flags; + if (GetInitiallyUnlockedChallenge()) { + flags |= 1 << 2; + } + indexWords[6] = flags; + if (GetCanBeReversed()) { + flags |= 1 << 3; + } + indexWords[6] = flags; + if (GetIsDDayRace()) { + flags |= 1 << 4; + } + indexWords[6] = flags; + if (GetIsBossRace()) { + flags |= 1 << 5; + } + indexWords[6] = flags; + if (GetIsMarkerRace()) { + flags |= 1 << 6; + } + indexWords[6] = flags; + if (GetIsPursuitRace()) { + flags |= 1 << 7; + } + indexWords[6] = flags; + if (GetIsLoopingRace()) { + flags |= 1 << 8; + } + indexWords[6] = flags; + if (GetRankPlayersByPoints()) { + flags |= 1 << 9; + } + indexWords[6] = flags; + if (GetRankPlayersByDistance()) { + flags |= 1 << 10; + } + indexWords[6] = flags; + if (GetCopsEnabled()) { + flags |= 1 << 11; + } + indexWords[6] = flags; + if (GetScriptedCopsInRace()) { + flags |= 1 << 12; + } + indexWords[6] = flags; + timeOfDay = GetTimeOfDay(); + if (0.8f < timeOfDay) { + flags |= 1 << 13; + } + indexWords[6] = flags; + if (GetNeverInQuickRace()) { + flags |= 1 << 14; + } + indexWords[6] = flags; + if (GetIsChallengeSeriesRace()) { + flags |= 1 << 15; + } + indexWords[6] = flags; + if (GetIsCollectorsEditionRace()) { + flags |= 1 << 16; + } + indexWords[6] = flags; + timeOfDay = GetTimeOfDay(); + if (timeOfDay < 0.8f) { + timeOfDay = GetTimeOfDay(); + if (0.0f <= timeOfDay) { + flags = indexWords[6] | (1 << 17); + indexWords[6] = flags; + } + } else { + flags = indexWords[6] | (1 << 13); + indexWords[6] = flags; + } + + bMemSet(indexWords + 1, 0, 10); + bSafeStrCpy(reinterpret_cast(indexWords + 1), GetEventID(), 10); + + GetBoundingBox(topLeft, botRight); + indexBytes[0x2C] = static_cast(topLeft.x * 255.0f); + indexBytes[0x2D] = static_cast(topLeft.y * 255.0f); + indexBytes[0x2E] = static_cast(botRight.x * 255.0f); + indexBytes[0x2F] = static_cast(botRight.y * 255.0f); +} + +void GRaceParameters::EnsureLoaded() const { + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } +} + +void GRaceParameters::BlockUntilLoaded() { + EnsureLoaded(); +} + +bool GRaceParameters::GetIsLoaded() const { + if (mParentVault && !mParentVault->IsLoaded()) { + return false; + } + + if (mChildVault && !mChildVault->IsLoaded()) { + return false; + } + + return true; +} + +void GRaceParameters::NotifyParentVaultUnloading() { + delete mRaceRecord; + mRaceRecord = nullptr; +} + +void GRaceParameters::NotifyParentVaultLoaded() { + if (!mRaceRecord) { + mRaceRecord = new Attrib::Gen::gameplay(reinterpret_cast(mIndex)[0], 0, nullptr); + } +} + +const Attrib::Gen::gameplay *GRaceParameters::GetGameplayObj() const { + return mRaceRecord; +} + +GVault *GRaceParameters::GetChildVault() const { + return mChildVault; +} + +GVault *GRaceParameters::GetParentVault() const { + return mParentVault; +} + +GActivity *GRaceParameters::GetActivity() const { + return GRuntimeInstance::FindObject(GetCollectionKey()); +} + +unsigned int GRaceParameters::GetCollectionKey() const { + unsigned int collectionKey; + + if (!mIndex) { + EnsureLoaded(); + collectionKey = mRaceRecord->GetCollection(); + } else { + collectionKey = reinterpret_cast(mIndex)[0]; + } + + return collectionKey; +} + +void GRaceParameters::GetBoundingBox(UMath::Vector2 &topLeft, UMath::Vector2 &botRight) const { + UMath::Vector3 pos; + float x1; + float x2; + float y1; + float y2; + TrackInfo *trackInfo; + + if (mIndex) { + const unsigned char *indexBytes; + + indexBytes = reinterpret_cast(mIndex); + topLeft.x = indexBytes[0x2C] * (1.0f / 255.0f); + topLeft.y = indexBytes[0x2D] * (1.0f / 255.0f); + botRight.x = indexBytes[0x2E] * (1.0f / 255.0f); + botRight.y = indexBytes[0x2F] * (1.0f / 255.0f); + return; + } + + EnsureLoaded(); + GetStartPosition(pos); + x1 = pos.z; + x2 = pos.z; + y1 = -pos.x; + y2 = y1; + + if (HasFinishLine()) { + GetFinishPosition(pos); + x1 = std::min(x1, pos.z); + x2 = std::max(x2, pos.z); + y1 = std::min(y1, -pos.x); + y2 = std::max(y2, -pos.x); + } + + for (unsigned int onCheck = 0; onCheck < GetNumCheckpoints(); ++onCheck) { + GetCheckpointPosition(onCheck, pos); + x1 = std::min(x1, pos.z); + x2 = std::max(x2, pos.z); + y1 = std::min(y1, -pos.x); + y2 = std::max(y2, -pos.x); + } + + trackInfo = TrackInfo::GetTrackInfo(2000); + bVector2 topLeftWorld(x1, y1); + bVector2 botRightWorld(x2, y2); + bVector2 topLeftMap; + bVector2 botRightMap; + Minimap::ConvertPos(topLeftWorld, topLeftMap, trackInfo); + Minimap::ConvertPos(botRightWorld, botRightMap, trackInfo); + topLeft.x = topLeftMap.x; + topLeft.y = topLeftMap.y; + botRight.x = botRightMap.x; + botRight.y = botRightMap.y; +} + +float GRaceParameters::GetRaceLengthMeters() const { + if (mIndex) { + return *reinterpret_cast(reinterpret_cast(mIndex) + 0x1C); + } + + const float *raceLength; + + EnsureLoaded(); + raceLength = reinterpret_cast(mRaceRecord->GetAttributePointer(0x7C11C52E, 0)); + if (!raceLength) { + raceLength = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + return *raceLength; +} + +int GRaceParameters::GetReputation() const { + if (mIndex) { + unsigned short value = *reinterpret_cast(reinterpret_cast(mIndex) + 0x24); + int exponent = static_cast(static_cast(value)) >> 11; + unsigned int scale = 1; + + if (exponent < 0) { + exponent = -exponent; + } + + while (true) { + bool done = exponent < 1; + + exponent = exponent - 1; + if (done) { + break; + } + + scale = scale * 10; + } + + if ((value & 0x8000) != 0) { + return static_cast(static_cast(static_cast(value << 5) >> 5) / + static_cast(scale)); + } + + return static_cast(static_cast(static_cast(value << 5) >> 5) * + static_cast(scale)); + } + + const int *reputation; + + EnsureLoaded(); + reputation = reinterpret_cast(mRaceRecord->GetAttributePointer(0x477EC5AA, 0)); + if (!reputation) { + reputation = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + return *reputation; +} + +float GRaceParameters::GetCashValue() const { + if (mIndex) { + unsigned short value = *reinterpret_cast(reinterpret_cast(mIndex) + 0x22); + int exponent = static_cast(static_cast(value)) >> 11; + unsigned int scale = 1; + + if (exponent < 0) { + exponent = -exponent; + } + + while (true) { + bool done = exponent < 1; + + exponent = exponent - 1; + if (done) { + break; + } + + scale = scale * 10; + } + + if ((value & 0x8000) != 0) { + return static_cast(static_cast(value << 5) >> 5) / static_cast(scale); + } + + return static_cast(static_cast(value << 5) >> 5) * static_cast(scale); + } + + const float *cashValue; + + EnsureLoaded(); + cashValue = reinterpret_cast(mRaceRecord->GetAttributePointer(0xD8BAA07B, 0)); + if (!cashValue) { + cashValue = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + return *cashValue; +} + +int GRaceParameters::GetLocalizationTag() const { + if (mIndex) { + return *reinterpret_cast(reinterpret_cast(mIndex) + 0x20); + } + + const int *localizationTag; + + EnsureLoaded(); + localizationTag = reinterpret_cast(mRaceRecord->GetAttributePointer(0xDB89AB5C, 0)); + if (!localizationTag) { + localizationTag = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + return *localizationTag; +} + +int GRaceParameters::GetNumLaps() const { + if (mIndex) { + return reinterpret_cast(mIndex)[0x28]; + } + + const int *numLaps; + + EnsureLoaded(); + numLaps = reinterpret_cast(mRaceRecord->GetAttributePointer(0x0EBDC165, 0)); + if (!numLaps) { + numLaps = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + return *numLaps; +} + +const char *GRaceParameters::GetEventID() const { + if (mIndex) { + return reinterpret_cast(mIndex) + 4; + } + + const EA::Reflection::Text *eventID; + + EnsureLoaded(); + eventID = reinterpret_cast(mRaceRecord->GetAttributePointer(0xA78403EC, 0)); + if (!eventID) { + eventID = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + return *eventID; +} + +float GRaceParameters::GetRivalBestTime() const { + const float *rivalBestTime; + + if (mIndex) { + const unsigned short value = *reinterpret_cast(reinterpret_cast(mIndex) + 0x26); + const int scale = FixedPoint::GetScale(); + + return static_cast(value) / static_cast(scale); + } + + EnsureLoaded(); + rivalBestTime = reinterpret_cast(mRaceRecord->GetAttributePointer(0xF9120D73, 0)); + if (!rivalBestTime) { + rivalBestTime = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + return *rivalBestTime; +} + +float GRaceParameters::GetChallengeGoal() const { + const float *challengeGoal; + + if (mIndex) { + short recordValue = *reinterpret_cast(reinterpret_cast(mIndex) + 0x0E); + int exponent = recordValue >> 11; + int multiplier = 1; + int mantissa; + + if (exponent < 0) { + exponent = -exponent; + } + + while (exponent > 0) { + exponent--; + multiplier *= 10; + } + + mantissa = recordValue << 21 >> 21; + if (recordValue & 0x8000) { + return static_cast(mantissa) / static_cast(multiplier); + } + + return static_cast(mantissa) * static_cast(multiplier); + } + + EnsureLoaded(); + challengeGoal = reinterpret_cast(mRaceRecord->GetAttributePointer(0x4E90219D, 0)); + if (!challengeGoal) { + challengeGoal = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + return *challengeGoal; +} + +bool GRaceParameters::GetIsPursuitRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 7)) != 0; + } + + const bool *isPursuitRace; + + EnsureLoaded(); + isPursuitRace = reinterpret_cast(mRaceRecord->GetAttributePointer(0x2B1F54F6, 0)); + if (!isPursuitRace) { + isPursuitRace = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *isPursuitRace; +} + +bool GRaceParameters::GetIsLoopingRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 8)) != 0; + } + + EnsureLoaded(); + return mRaceRecord->IsLoopingRace(0); +} + +bool GRaceParameters::GetInitiallyUnlockedQuickRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 0)) != 0; + } + + const bool *initiallyUnlockedQuickRace; + + EnsureLoaded(); + initiallyUnlockedQuickRace = reinterpret_cast(mRaceRecord->GetAttributePointer(0xB39ED8C3, 0)); + if (!initiallyUnlockedQuickRace) { + initiallyUnlockedQuickRace = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *initiallyUnlockedQuickRace; +} + +bool GRaceParameters::GetInitiallyUnlockedOnline() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 1)) != 0; + } + + const bool *initiallyUnlockedOnline; + + EnsureLoaded(); + initiallyUnlockedOnline = reinterpret_cast(mRaceRecord->GetAttributePointer(0x39509746, 0)); + if (!initiallyUnlockedOnline) { + initiallyUnlockedOnline = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *initiallyUnlockedOnline; +} + +bool GRaceParameters::GetInitiallyUnlockedChallenge() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 2)) != 0; + } + + const bool *initiallyUnlockedChallenge; + + EnsureLoaded(); + initiallyUnlockedChallenge = reinterpret_cast(mRaceRecord->GetAttributePointer(0xEA855EAF, 0)); + if (!initiallyUnlockedChallenge) { + initiallyUnlockedChallenge = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *initiallyUnlockedChallenge; +} + +bool GRaceParameters::GetIsDDayRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 4)) != 0; + } + + const bool *isDDayRace; + + EnsureLoaded(); + isDDayRace = reinterpret_cast(mRaceRecord->GetAttributePointer(0x8CB01ABF, 0)); + if (!isDDayRace) { + isDDayRace = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *isDDayRace; +} + +bool GRaceParameters::GetIsBossRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 5)) != 0; + } + + const bool *isBossRace; + + EnsureLoaded(); + isBossRace = reinterpret_cast(mRaceRecord->GetAttributePointer(0xFF5EE5D6, 0)); + if (!isBossRace) { + isBossRace = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *isBossRace; +} + +bool GRaceParameters::GetIsMarkerRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 6)) != 0; + } + + const bool *isMarkerRace; + + EnsureLoaded(); + isMarkerRace = reinterpret_cast(mRaceRecord->GetAttributePointer(0xF2FE50D7, 0)); + if (!isMarkerRace) { + isMarkerRace = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *isMarkerRace; +} + +bool GRaceParameters::GetRankPlayersByPoints() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 9)) != 0; + } + + EnsureLoaded(); + return mRaceRecord->RankPlayersByPoints(0); +} + +bool GRaceParameters::GetRankPlayersByDistance() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 10)) != 0; + } + + EnsureLoaded(); + return mRaceRecord->RankPlayersByDistance(0); +} + +bool GRaceParameters::GetScriptedCopsInRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 12)) != 0; + } + + EnsureLoaded(); + return mRaceRecord->ScriptedCopsInRace(0); +} + +bool GRaceParameters::GetCopsEnabled() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 11)) != 0; + } + + const bool *copsEnabled; + + EnsureLoaded(); + copsEnabled = reinterpret_cast(mRaceRecord->GetAttributePointer(0x3918E889, 0)); + if (!copsEnabled) { + copsEnabled = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *copsEnabled; +} + +bool GRaceParameters::GetNeverInQuickRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 14)) != 0; + } + + EnsureLoaded(); + return mRaceRecord->NeverInQuickRace(0); +} + +bool GRaceParameters::GetIsChallengeSeriesRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 15)) != 0; + } + + const bool *isChallengeSeriesRace; + + EnsureLoaded(); + isChallengeSeriesRace = reinterpret_cast(mRaceRecord->GetAttributePointer(0x1C650104, 0)); + if (!isChallengeSeriesRace) { + isChallengeSeriesRace = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *isChallengeSeriesRace; +} + +bool GRaceParameters::GetIsCollectorsEditionRace() const { + if (mIndex) { + return (reinterpret_cast(mIndex)[6] & (1 << 16)) != 0; + } + + const bool *isCollectorsEditionRace; + + EnsureLoaded(); + isCollectorsEditionRace = reinterpret_cast(mRaceRecord->GetAttributePointer(0x637584FE, 0)); + if (!isCollectorsEditionRace) { + isCollectorsEditionRace = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *isCollectorsEditionRace; +} + +float GRaceParameters::GetTimeLimit() const { + EnsureLoaded(); + return mRaceRecord->TimeLimit(0); +} + +float GRaceParameters::GetMaxHeatLevel() const { + EnsureLoaded(); + return mRaceRecord->MaxHeatLevel(0); +} + +bool GRaceParameters::GetNoPostRaceScreen() const { + EnsureLoaded(); + return mRaceRecord->NoPostRaceScreen(0); +} + +bool GRaceParameters::GetUseWorldHeatInRace() const { + const bool *useWorldHeatInRace; + + EnsureLoaded(); + useWorldHeatInRace = reinterpret_cast(mRaceRecord->GetAttributePointer(0x45F2AD6C, 0)); + if (!useWorldHeatInRace) { + useWorldHeatInRace = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *useWorldHeatInRace; +} + +float GRaceParameters::GetForceHeatLevel() const { + EnsureLoaded(); + return static_cast(mRaceRecord->ForceHeatLevel(0)); +} + +float GRaceParameters::GetMaxRaceHeatLevel() const { + const float *maxRaceHeatLevel; + + EnsureLoaded(); + maxRaceHeatLevel = reinterpret_cast(mRaceRecord->GetAttributePointer(0xF5A03629, 0)); + if (!maxRaceHeatLevel) { + maxRaceHeatLevel = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + return *maxRaceHeatLevel; +} + +float GRaceParameters::GetInitialPlayerSpeed() const { + EnsureLoaded(); + return mRaceRecord->InitialPlayerSpeed(0); +} + +bool GRaceParameters::GetIsRollingStart() const { + const bool *isRollingStart; + + EnsureLoaded(); + isRollingStart = reinterpret_cast(mRaceRecord->GetAttributePointer(0xB809D19C, 0)); + if (!isRollingStart) { + isRollingStart = reinterpret_cast(Attrib::DefaultDataArea(sizeof(bool))); + } + + return *isRollingStart; +} + +bool GRaceParameters::GetIsEpicPursuitRace() const { + EnsureLoaded(); + return mRaceRecord->IsEpicPursuitRace(0); +} + +const char *GRaceParameters::GetPlayerCarType() const { + EnsureLoaded(); + return mRaceRecord->PlayerCarType(0); +} + +float GRaceParameters::GetPlayerCarPerformance() const { + EnsureLoaded(); + return mRaceRecord->PlayerCarPerformance(0); +} + +int GRaceParameters::GetBustedLives() const { + EnsureLoaded(); + return mRaceRecord->BustedLives(0); +} + +int GRaceParameters::GetKnockoutsPerLap() const { + EnsureLoaded(); + return mRaceRecord->KnockoutsPerLap(0); +} + +bool GRaceParameters::GetCatchUp() const { + EnsureLoaded(); + return mRaceRecord->CatchUp(0); +} + +bool GRaceParameters::GetCatchUpOverride() const { + EnsureLoaded(); + return mRaceRecord->CatchUpOverride(0); +} + +const char *GRaceParameters::GetCatchUpSkill() const { + EnsureLoaded(); + return mRaceRecord->CatchUpSkill(0); +} + +const char *GRaceParameters::GetCatchUpSpread() const { + EnsureLoaded(); + return mRaceRecord->CatchUpSpread(0); +} + +float GRaceParameters::GetCatchUpIntegral() const { + EnsureLoaded(); + return mRaceRecord->CatchUpIntegral(0); +} + +float GRaceParameters::GetCatchUpDerivative() const { + EnsureLoaded(); + return mRaceRecord->CatchUpDerivative(0); +} + +bool GRaceParameters::GetPhotofinish() const { + EnsureLoaded(); + return mRaceRecord->DoPhotofinish(0); +} + +unsigned int GRaceParameters::GetNumCheckpoints() const { + Attrib::Attribute checkpointList; + + EnsureLoaded(); + checkpointList = mRaceRecord->Get(0x34AAE3FC); + return checkpointList.GetLength(); +} + +bool GRaceParameters::GetCheckpointsVisible() const { + EnsureLoaded(); + return mRaceRecord->CheckpointsVisible(0); +} + +unsigned int GRaceParameters::GetNumShortcuts() const { + EnsureLoaded(); + return mRaceRecord->Num_Shortcuts(); +} + +unsigned int GRaceParameters::GetNumBarrierExemptions() const { + EnsureLoaded(); + return mRaceRecord->Num_BarrierExemptions(); +} + +unsigned int GRaceParameters::GetBarrierCount() const { + EnsureLoaded(); + return mRaceRecord->Num_Barriers(); +} + +const char *GRaceParameters::GetPhotoFinishCamera() const { + const EA::Reflection::Text *photoFinishCamera; + + EnsureLoaded(); + photoFinishCamera = reinterpret_cast(mRaceRecord->GetAttributePointer(0x62DFC259, 0)); + if (!photoFinishCamera) { + photoFinishCamera = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + return *photoFinishCamera; +} + +const char *GRaceParameters::GetPhotoFinishTexture() const { + const EA::Reflection::Text *photoFinishTexture; + + EnsureLoaded(); + photoFinishTexture = reinterpret_cast(mRaceRecord->GetAttributePointer(0x038A3B53, 0)); + if (!photoFinishTexture) { + photoFinishTexture = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + return *photoFinishTexture; +} + +const char *GRaceParameters::GetTrafficPattern() const { + const EA::Reflection::Text *trafficPattern; + + EnsureLoaded(); + trafficPattern = reinterpret_cast(mRaceRecord->GetAttributePointer(0x6319B692, 0)); + if (!trafficPattern) { + trafficPattern = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + return *trafficPattern; +} + +float GRaceParameters::GetTimeOfDay() const { + const float *timeOfDay; + + EnsureLoaded(); + timeOfDay = reinterpret_cast(mRaceRecord->GetAttributePointer(0x9DFF3C3D, 0)); + if (!timeOfDay) { + timeOfDay = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + return *timeOfDay; +} + +unsigned int GRaceParameters::GetChallengeType() const { + const EA::Reflection::Text *challengeType; + + if (mIndex) { + return *reinterpret_cast(reinterpret_cast(mIndex) + 0x10); + } + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + challengeType = reinterpret_cast(mRaceRecord->GetAttributePointer(0x704F72E8, 0)); + if (!challengeType) { + challengeType = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + if (!*challengeType || !**challengeType) { + return 0; + } + + return Attrib::StringHash32(*challengeType); +} + +GRace::Type GRaceParameters::GetRaceType() const { + static const struct { + const char *mTypeName; + GRace::Type mType; + } typeTable[11] = { + {"circuit", GRace::kRaceType_Circuit}, + {"p2p", GRace::kRaceType_P2P}, + {"drag", GRace::kRaceType_Drag}, + {"knockout", GRace::kRaceType_Knockout}, + {"tollbooth", GRace::kRaceType_Tollbooth}, + {"speedtrap", GRace::kRaceType_SpeedTrap}, + {"cashgrab", GRace::kRaceType_CashGrab}, + {"checkpointrace", GRace::kRaceType_Checkpoint}, + {"challenge", GRace::kRaceType_Challenge}, + {"speedtrapjump", GRace::kRaceType_JumpToSpeedTrap}, + {"milestonejump", GRace::kRaceType_JumpToMilestone}, + }; + const char *eventType; + + if (mIndex) { + return static_cast(static_cast(reinterpret_cast(mIndex)[0x2B])); + } + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + { + const EA::Reflection::Text *eventTypePtr = + reinterpret_cast(mRaceRecord->GetAttributePointer(0x0F6BCDE1, 0)); + + if (!eventTypePtr) { + eventTypePtr = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + eventType = *eventTypePtr; + } + + { + int onType; + + for (onType = 0; onType < 11; ++onType) { + if (bStrCmp(eventType, typeTable[onType].mTypeName) == 0) { + return typeTable[onType].mType; + } + } + } + + return GRace::kRaceType_None; +} + +unsigned int GRaceParameters::GetRegion() const { + static const struct { + const char *mName; + unsigned int mRegion; + } kRaceRegions[3] = { + {"city", 0}, + {"coastal", 1}, + {"college_town", 2}, + }; + unsigned int i; + const EA::Reflection::Text *regionName; + + if (mIndex) { + return reinterpret_cast(mIndex)[0x29]; + } + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + regionName = reinterpret_cast(mRaceRecord->GetAttributePointer(0xCB01E454, 0)); + if (!regionName) { + regionName = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + for (i = 0; i < 3; i++) { + if (bStrCmp(*regionName, kRaceRegions[i].mName) == 0) { + return kRaceRegions[i].mRegion; + } + } + + return static_cast(-1); +} + +void GRaceParameters::ExtractPosition(Attrib::Gen::gameplay &collection, UMath::Vector3 &pos) const { + const UMath::Vector3 &collectionPosition = collection.Position(0); + + pos.x = -collectionPosition.y; + pos.y = collectionPosition.z; + pos.z = collectionPosition.x; +} + +void GRaceParameters::ExtractDirection(Attrib::Gen::gameplay &collection, UMath::Vector3 &dir, float rotate) const { + UMath::Matrix4 rotMat = UMath::Matrix4::kIdentity; + UMath::Vector3 initialVec = UMath::Vector3Make(0.0f, 0.0f, 1.0f); + const float *rotation = reinterpret_cast(collection.GetAttributePointer(0x5A6A57C6, 0)); + + if (!rotation) { + rotation = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + MATRIX4_multyrot(&rotMat, -(*rotation + rotate) * 0.0027777778f, &rotMat); + VU0_MATRIX3x4_vect3mult(initialVec, rotMat, initialVec); + + dir = initialVec; +} + +unsigned int GRaceParameters::GetEventHash() const { + const EA::Reflection::Text *eventID; + + if (mIndex) { + return *reinterpret_cast(reinterpret_cast(mIndex) + 0x14); + } + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + eventID = reinterpret_cast(mRaceRecord->GetAttributePointer(0xA78403EC, 0)); + if (!eventID) { + eventID = reinterpret_cast(Attrib::DefaultDataArea(sizeof(EA::Reflection::Text))); + } + + return Attrib::StringHash32(*eventID); +} + +bool GRaceParameters::GetIsAvailable(GRace::Context context) const { + if (UnlockAllThings) { + return true; + } + + switch (context) { + case GRace::kRaceContext_TimeTrial: + return GRaceDatabase::Get().CheckRaceScoreFlags(GetEventHash(), GRaceDatabase::kUnlocked_Online); + + case GRace::kRaceContext_QuickRace: + if (GetNeverInQuickRace()) { + return false; + } + + return GRaceDatabase::Get().CheckRaceScoreFlags(GetEventHash(), GRaceDatabase::kUnlocked_QuickRace); + + case GRace::kRaceContext_Career: + if (!GRaceDatabase::Get().CheckRaceScoreFlags(GetEventHash(), GRaceDatabase::kUnlocked_Career)) { + return false; + } + + return !GRaceDatabase::Get().CheckRaceScoreFlags(GetEventHash(), GRaceDatabase::kCompleted_ContextCareer); + + default: + return false; + } +} + +int GRaceParameters::GetTrafficDensity() const { + const int *trafficDensity; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + trafficDensity = reinterpret_cast(mRaceRecord->GetAttributePointer(0xC64BC341, 0)); + if (!trafficDensity) { + trafficDensity = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + return *trafficDensity; +} + +bool GRaceParameters::GetIsSunsetRace() const { + if (mIndex) { + return (*reinterpret_cast(reinterpret_cast(mIndex) + 0x18) >> 13) & 1; + } + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + return GetTimeOfDay() >= 0.8f; +} + +bool GRaceParameters::GetIsMiddayRace() const { + if (mIndex) { + return (*reinterpret_cast(reinterpret_cast(mIndex) + 0x18) >> 17) & 1; + } + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + return GetTimeOfDay() >= 0.0f && GetTimeOfDay() < 0.8f; +} + +void GRaceParameters::SetupTimeOfDay() { + if (GetIsSunsetRace()) { + SetCurrentTimeOfDay(0.9f); + } else if (GetIsMiddayRace()) { + SetCurrentTimeOfDay(0.0f); + } +} + +GRace::Difficulty GRaceParameters::GetDifficulty() const { + const int *difficulty; + GRace::Difficulty raceDifficulty; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + difficulty = reinterpret_cast(mRaceRecord->GetAttributePointer(0x88A7E3BE, 0)); + if (!difficulty) { + difficulty = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + if (*difficulty < 0x22) { + return GRace::kRaceDifficulty_Easy; + } + + raceDifficulty = GRace::kRaceDifficulty_Medium; + if (*difficulty > 0x42) { + raceDifficulty = GRace::kRaceDifficulty_Hard; + } + + return raceDifficulty; +} + +int GRaceParameters::GetCopDensity() const { + const int *copDensity; + int raceCopDensity; + int density; + + if (mIndex) { + return static_cast(reinterpret_cast(mIndex)[0x2A]); + } + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + copDensity = reinterpret_cast(mRaceRecord->GetAttributePointer(0xDBC08D32, 0)); + if (!copDensity) { + copDensity = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + density = *copDensity; + if (density == 0 && !GetCopsEnabled()) { + return 0; + } + if (density < 0x22) { + return 1; + } + + raceCopDensity = 2; + if (density > 0x42) { + raceCopDensity = 3; + } + + return raceCopDensity; +} + +bool GRaceParameters::GetCanBeReversed() const { + if (mIndex) { + return (*reinterpret_cast(reinterpret_cast(mIndex) + 0x18) >> 3) & 1; + } + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + return false; +} + +GCharacter *GRaceParameters::GetOpponentChar(unsigned int index) const { + unsigned int characterKey; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + if (GetIsBossRace() && GRaceStatus::Get().GetRaceContext() != GRace::kRaceContext_Career) { + characterKey = Attrib::StringToLowerCaseKey("character_smart"); + } else { + characterKey = mRaceRecord->Opponents(index).GetCollectionKey(); + } + + return GRuntimeInstance::FindObject(characterKey); +} + +int GRaceParameters::GetNumOpponents() const { + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + return mRaceRecord->Num_Opponents(); +} + +void GRaceParameters::GetStartPosition(UMath::Vector3 &pos) const { + const GCollectionKey *raceStartSpec; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + raceStartSpec = reinterpret_cast(mRaceRecord->GetAttributePointer(0xE43B2CCC, 0)); + if (!raceStartSpec) { + raceStartSpec = reinterpret_cast(Attrib::DefaultDataArea(sizeof(GCollectionKey))); + } + + if (raceStartSpec->GetCollectionKey()) { + Attrib::Gen::gameplay raceStart(raceStartSpec->GetCollectionKey(), 0, nullptr); + + ExtractPosition(raceStart, pos); + } +} + +void GRaceParameters::GetStartDirection(UMath::Vector3 &dir) const { + const GCollectionKey *raceStartSpec; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + dir = UMath::Vector3::kZero; + + raceStartSpec = reinterpret_cast(mRaceRecord->GetAttributePointer(0xE43B2CCC, 0)); + if (!raceStartSpec) { + raceStartSpec = reinterpret_cast(Attrib::DefaultDataArea(sizeof(GCollectionKey))); + } + + if (raceStartSpec->GetCollectionKey()) { + Attrib::Gen::gameplay raceStart(raceStartSpec->GetCollectionKey(), 0, nullptr); + + ExtractDirection(raceStart, dir, 0.0f); + } +} + +bool GRaceParameters::HasFinishLine() const { + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + return mRaceRecord->racefinish(0).GetCollectionKey() != 0; +} + +void GRaceParameters::GetFinishPosition(UMath::Vector3 &pos) const { + const GCollectionKey *raceFinishSpec; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + pos = UMath::Vector3::kZero; + + raceFinishSpec = reinterpret_cast(mRaceRecord->GetAttributePointer(0xB0A24ADC, 0)); + if (!raceFinishSpec) { + raceFinishSpec = reinterpret_cast(Attrib::DefaultDataArea(sizeof(GCollectionKey))); + } + + if (raceFinishSpec->GetCollectionKey()) { + Attrib::Gen::gameplay raceFinish(raceFinishSpec->GetCollectionKey(), 0, nullptr); + + ExtractPosition(raceFinish, pos); + } +} + +void GRaceParameters::GetFinishDirection(UMath::Vector3 &dir) const { + const GCollectionKey *raceFinishSpec; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + dir = UMath::Vector3::kZero; + + raceFinishSpec = reinterpret_cast(mRaceRecord->GetAttributePointer(0xB0A24ADC, 0)); + if (!raceFinishSpec) { + raceFinishSpec = reinterpret_cast(Attrib::DefaultDataArea(sizeof(GCollectionKey))); + } + + if (raceFinishSpec->GetCollectionKey()) { + Attrib::Gen::gameplay raceFinish(raceFinishSpec->GetCollectionKey(), 0, nullptr); + + ExtractDirection(raceFinish, dir, 0.0f); + } +} + +void GRaceParameters::GetCheckpointPosition(unsigned int index, UMath::Vector3 &pos) const { + const GCollectionKey *raceCheckSpec; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + pos = UMath::Vector3::kZero; + + raceCheckSpec = reinterpret_cast(mRaceRecord->GetAttributePointer(0x34AAE3FC, index)); + if (!raceCheckSpec) { + raceCheckSpec = reinterpret_cast(Attrib::DefaultDataArea(sizeof(GCollectionKey))); + } + + if (raceCheckSpec->GetCollectionKey()) { + Attrib::Gen::gameplay checkpoint(raceCheckSpec->GetCollectionKey(), 0, nullptr); + + ExtractPosition(checkpoint, pos); + } +} + +void GRaceParameters::GetCheckpointDirection(unsigned int index, UMath::Vector3 &dir) const { + const GCollectionKey *raceCheckSpec; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + dir = UMath::Vector3::kZero; + + raceCheckSpec = reinterpret_cast(mRaceRecord->GetAttributePointer(0x34AAE3FC, index)); + if (!raceCheckSpec) { + raceCheckSpec = reinterpret_cast(Attrib::DefaultDataArea(sizeof(GCollectionKey))); + } + + if (raceCheckSpec->GetCollectionKey()) { + Attrib::Gen::gameplay checkpoint(raceCheckSpec->GetCollectionKey(), 0, nullptr); + + ExtractDirection(checkpoint, dir, 0.0f); + } +} + +GMarker *GRaceParameters::GetShortcut(unsigned int index) const { + unsigned int collectionKey; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + collectionKey = mRaceRecord->Shortcuts(index).GetCollectionKey(); + return GRuntimeInstance::FindObject(collectionKey); +} + +GMarker *GRaceParameters::GetBarrierExemption(unsigned int index) const { + unsigned int collectionKey; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + collectionKey = mRaceRecord->BarrierExemptions(index).GetCollectionKey(); + return GRuntimeInstance::FindObject(collectionKey); +} + +const char *GRaceParameters::GetBarrierName(unsigned int index) const { + const char *barrierName; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + barrierName = mRaceRecord->Barriers(index); + if (barrierName && *barrierName == '*') { + return barrierName + 1; + } + + return barrierName; +} + +bool GRaceParameters::GetBarrierIsFlipped(unsigned int index) const { + const char *barrierName; + + if (mParentVault && !mParentVault->IsLoaded()) { + mParentVault->LoadSyncTransient(); + } + + if (mChildVault && !mChildVault->IsLoaded()) { + mChildVault->LoadSyncTransient(); + } + + barrierName = mRaceRecord->Barriers(index); + if (barrierName) { + return *barrierName == '*'; + } + + return false; +} + +float GRaceParameters::GetStartTime() const { + EnsureLoaded(); + return mRaceRecord->StartTime(0); +} + +float GRaceParameters::GetStartPercent() const { + EnsureLoaded(); + return mRaceRecord->StartPercent(0); +} + +const char *GRaceParameters::GetSpeedTrapCamera() const { + EnsureLoaded(); + return mRaceRecord->SpeedTrapCamera(0); +} + +int ParseArray(const char *string, float *data, int max) { + int len = bStrLen(string); + char *copy = new (__FILE__, __LINE__) char[len + 1]; + char **words; + int num; + float *temp = nullptr; + int i; + + bMemCpy(copy, string, len + 1); + num = SplitChars(copy, &words, NotNumeric); + if (num > max) { + temp = new (__FILE__, __LINE__) float[num]; + i = 0; + if (num > 0) { + do { + temp[i] = ParseFloat(words[i]); + i += 1; + } while (i < num); + } + + { + Table table(temp, num, 0.0f, static_cast(max - 1)); + + i = 0; + if (max > 0) { + do { + data[i] = table.GetValue(static_cast(i)); + i += 1; + } while (i < max); + } + } + } else { + i = 0; + if (num > 0) { + do { + data[i] = ParseFloat(words[i]); + i += 1; + } while (i < num); + } + + max = num; + if (num == 1) { + max = 2; + data[1] = *data; + } + } + + if (temp) { + delete[] temp; + } + if (words) { + delete[] words; + } + if (copy) { + delete[] copy; + } + + return max; +} + +GRaceCustom::GRaceCustom(const GRaceParameters &other) + : GRaceParameters(other.GetCollectionKey(), nullptr), // + mRaceActivity(nullptr), // + mNumOpponents(0), // + mReversed(false), // + mFreedByOwner(false), // + mHeatLevel(-1) { + unsigned int customKey; + + customKey = mRaceRecord->GenerateUniqueKey("GRaceCustom", true); + mRaceRecord->Modify(customKey, 0); + mRaceRecord->SetParent(other.GetGameplayObj()->GetCollection()); + mNumOpponents = mRaceRecord->Num_Opponents(); +} + +GRaceCustom::~GRaceCustom() { + if (mRaceActivity) { + delete mRaceActivity; + mRaceActivity = nullptr; + } +} + +void GRaceCustom::CreateRaceActivity() { + int existingOpponents; + bool bossINQuickRace; + + if (mReversed) { + if (GetCanBeReversed()) { + GCollectionKey startReverse = mRaceRecord->racestartReverse(); + GCollectionKey finishReverse = mRaceRecord->racefinishReverse(); + + mRaceRecord->Set_racestart(startReverse); + mRaceRecord->Set_racefinish(finishReverse); + } else { + mReversed = false; + } + } + + existingOpponents = GetNumOpponents(); + bossINQuickRace = false; + if (GRaceStatus::Get().GetRaceContext() != GRace::kRaceContext_Career && GetIsBossRace()) { + bossINQuickRace = true; + existingOpponents = 0; + } + + if (mNumOpponents != static_cast(existingOpponents)) { + unsigned int opponentKey[16]; + unsigned int currOpponents = 0; + + if (!bossINQuickRace) { + currOpponents = mRaceRecord->Num_Opponents(); + unsigned int onOpp = 0; + + for (; onOpp < currOpponents; onOpp++) { + opponentKey[onOpp] = mRaceRecord->Opponents(onOpp).GetCollectionKey(); + } + } + + if (mNumOpponents > currOpponents) { + if (currOpponents == 0) { + for (unsigned int onSet = 0; onSet < mNumOpponents; onSet++) { + opponentKey[onSet] = 0x9F3B88C5; + } + } else { + for (unsigned int onCopy = currOpponents; onCopy < mNumOpponents; onCopy++) { + opponentKey[onCopy] = opponentKey[onCopy % currOpponents]; + } + } + } + + unsigned int onSet = 0; + mRaceRecord->Add(0x5839FA1A, mNumOpponents); + + { + Attrib::Attribute attribute(mRaceRecord->Get(0x5839FA1A)); + + if (onSet < mNumOpponents) { + do { + GCollectionKey opponent(opponentKey[onSet]); + + attribute.Set(onSet, opponent); + onSet++; + } while (onSet < mNumOpponents); + } + } + } + + if (mHeatLevel != -1) { + if (mHeatLevel == 0) { + SetCopsEnabled(false); + } else { + SetForceHeatLevel(mHeatLevel); + } + } + + mRaceActivity = new GActivity(mRaceRecord->GetCollection()); + mRaceActivity->AllocateConnectionBuffer(GObjectBlock::CalcNumConnections(mRaceRecord->GetCollection())); +} + +GActivity *GRaceCustom::GetRaceActivity() const { + return mRaceActivity; +} + +void GRaceCustom::GetCheckpointPosition(unsigned int index, UMath::Vector3 &pos) const { + if (mReversed) { + index = GetNumCheckpoints() - 1 - index; + } + + GRaceParameters::GetCheckpointPosition(index, pos); +} + +void GRaceCustom::GetCheckpointDirection(unsigned int index, UMath::Vector3 &dir) const { + UMath::Vector3 checkDir; + + if (mReversed) { + index = GetNumCheckpoints() - 1 - index; + } + + checkDir = UMath::Vector3::kZero; + { + const GCollectionKey &raceCheckSpec = mRaceRecord->Checkpoint(index); + + if (raceCheckSpec.GetCollectionKey()) { + Attrib::Gen::gameplay checkpoint(raceCheckSpec.GetCollectionKey(), 0, nullptr); + + if (mReversed) { + ExtractDirection(checkpoint, checkDir, 180.0f); + } else { + ExtractDirection(checkpoint, checkDir, 0.0f); + } + } + } + dir = checkDir; +} + +void GRaceCustom::SetReversed(bool isReverseDir) { + mReversed = isReverseDir; +} + +void GRaceCustom::SetNumLaps(int numLaps) { + SetAttribute(0x0EBDC165, numLaps, 0); +} + +void GRaceCustom::SetTrafficDensity(int density) { + if (density < 0) { + density = 0; + } else if (density > 100) { + density = 100; + } + + SetAttribute(0xC64BC341, density, 0); +} + +void GRaceCustom::SetNumOpponents(int numOpponents) { + mNumOpponents = numOpponents; +} + +void GRaceCustom::SetDifficulty(GRace::Difficulty difficulty) { + int value; + + if (difficulty == GRace::kRaceDifficulty_Easy) { + value = 0x21; + } else if (difficulty == GRace::kRaceDifficulty_Medium) { + value = 0x42; + } else if (difficulty == GRace::kRaceDifficulty_Hard) { + value = 100; + } else { + value = 0; + } + + SetAttribute(0x88A7E3BE, value, 0); +} + +void GRaceCustom::SetCatchUp(bool catchUpEnabled) { + SetAttribute(0x10DB04E6, catchUpEnabled, 0); +} + +void GRaceCustom::SetCopsEnabled(bool copsEnabled) { + SetAttribute(0x3918E889, copsEnabled, 0); +} + +void GRaceCustom::SetForceHeatLevel(int level) { + SetAttribute(0xE4211F4F, level, 0); +} + +template void GRaceCustom::SetAttribute(unsigned int key, const T &value, unsigned int index) { + Attrib::Attribute attribute; + T *dest; + + attribute = mRaceRecord->Get(key); + if (!attribute.IsValid()) { + mRaceRecord->Add(key, 1); + } + + dest = reinterpret_cast(const_cast(mRaceRecord->GetAttributePointer(key, index))); + if (dest) { + *dest = value; + } +} + +GRaceStatus::GRaceStatus() + : UTL::COM::Object(1), // + IVehicleCache((UTL::COM::Object *)this) { + unsigned char currentBin; + + for (int i = 0; i < 16; ++i) { + mRacerInfo[i].ClearAll(); + } + + currentBin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + + mNextCheckpoint = nullptr; + mCheckpointModel = nullptr; + mCheckpointEmitter = nullptr; + mIsLoading = false; + mActivelyRacing = false; + mRaceParms = nullptr; + mRacerCount = 0; + nSpeedTraps = 0; + mRaceContext = GRace::kRaceContext_Career; + mRaceBin = GRaceDatabase::Get().GetBinNumber(currentBin); + mCaluclatedAdaptiveGain = false; + mQueueBinChange = false; + mNumTollbooths = 0; + mVehicleCacheLocked = false; + bRaceRouteError = false; + mTrafficDensity = 0; + mTrafficPattern = 0; + mScriptWaitingForLoad = false; + mHasBeenWon = false; +#ifndef EA_BUILD_A124 + mRefreshBinAfterRace = false; + mWarpWhenInFreeRoam = 0; +#endif + fSubsequentLapLength = 0.0f; + mPlayMode = kPlayMode_Racing; + fRaceLength = 0.0f; + fFirstLapLength = 0.0f; + fObj = this; + + ClearTimes(); + SyncronizeAdaptiveBonus(); + MakeDefaultCatchUpData(); + + if (!GRaceDatabase::Get().GetStartupRace()) { + EnterBin(currentBin); + SetRoaming(); + } +} + +GRaceStatus::~GRaceStatus() { + if (mCheckpointModel) { + delete mCheckpointModel; + mCheckpointModel = nullptr; + } + + if (mCheckpointEmitter) { + mCheckpointEmitter->UnSubscribe(); + delete mCheckpointEmitter; + mCheckpointEmitter = nullptr; + } +} + +void GRaceStatus::Init() { + if (!fObj) { + new GRaceStatus(); + } +} + +void GRaceStatus::OnRemovedVehicleCache(IVehicle *ivehicle) {} + +const char *GRaceStatus::GetCacheName() const { + return "GRaceStatus"; +} + +bool GRaceStatus::CanUnspawnRoamer(const IVehicle *roamer) const { + const GRacerInfo *info; + + if (!roamer->IsActive()) { + return true; + } + + info = nullptr; + for (int onRacer = 0; onRacer < GetRacerCount(); ++onRacer) { + const GRacerInfo &racerInfo = mRacerInfo[onRacer]; + + if (UTL::COM::ComparePtr(racerInfo.GetSimable(), roamer)) { + info = &racerInfo; + break; + } + } + + if (!info) { + return true; + } + + if (roamer->GetOffscreenTime() < 4.0f) { + return false; + } + + return Sim::DistanceToCamera(roamer->GetPosition()) >= 100.0f; +} + +eVehicleCacheResult GRaceStatus::OnQueryVehicleCache(const IVehicle *removethis, const IVehicleCache *whosasking) const { + if (GetPlayMode() != kPlayMode_Racing && !mVehicleCacheLocked) { + if (UTL::COM::ComparePtr(whosasking, ICopMgr::Get()) || UTL::COM::ComparePtr(whosasking, ITrafficMgr::Get())) { + if (!CanUnspawnRoamer(removethis)) { + return VCR_WANT; + } + + return VCR_DONTCARE; + } + + return VCR_DONTCARE; + } else { + for (int onRacer = 0; onRacer < GetRacerCount(); ++onRacer) { + const GRacerInfo &racerInfo = mRacerInfo[onRacer]; + + if (UTL::COM::ComparePtr(racerInfo.GetSimable(), removethis)) { + return VCR_WANT; + } + } + } + + return VCR_DONTCARE; +} + +void GRaceStatus::SetRaceContext(GRace::Context context) { + mRaceContext = context; +} + +int GRaceStatus::GetRacerCount() const { + return mRacerCount; +} + +void GRaceStatus::SetActivelyRacing(bool racing) { + mActivelyRacing = racing; +} + +void GRaceStatus::SetHasBeenWon(bool won) { + mHasBeenWon = won; +} + +void GRaceStatus::SetIsLoading(bool loading) { + mIsLoading = loading; +} + +void GRaceStatus::SetTaskTime(float time) { + mTaskTime = time; +} + +void GRaceStatus::Shutdown() { + if (fObj) { + delete fObj; + } + + fObj = nullptr; +} + +void GRaceStatus::EnableBinBarriers() { + if (mRaceBin) { + mRaceBin->EnableBarriers(); + } +} + +void GRaceStatus::RefreshBinWhileInGame() { + mQueueBinChange = true; +} + +void GRaceStatus::EnterBin(unsigned int binNumber) { + if (mRaceBin) { + DisableBarriers(); + } + + mRaceBin = GRaceDatabase::Get().GetBinNumber(binNumber); + if (mRaceBin) { + EnableBarriers(); + if (mRaceBin->GetChildVault() && !mRaceBin->GetChildVault()->IsLoaded()) { + mRaceBin->GetChildVault()->LoadSyncTransient(); + } + } + + if (GManager::Get().GetInGameplay()) { + GManager::Get().StartWorldActivities(true); + GManager::Get().StartBinActivity(mRaceBin); + GManager::Get().RefreshEngageTriggerIcons(); + GManager::Get().RefreshSpeedTrapIcons(); + } +} + +void GRaceStatus::SetRacing() { + IPlayer *player; + + mPlayMode = kPlayMode_Racing; + ClearTimes(); + + player = IPlayer::First(PLAYER_ALL); + while (player) { + if (player->InGameBreaker()) { + player->ToggleGameBreaker(); + } + + { + const IPlayer *currentPlayer = player; + IPlayer::List::const_iterator iter = + std::find(IPlayer::GetList(PLAYER_ALL).begin(), IPlayer::GetList(PLAYER_ALL).end(), currentPlayer); + IPlayer::List::const_iterator end = IPlayer::GetList(PLAYER_ALL).end(); + + if (iter == end) { + player = nullptr; + } else { + ++iter; + player = iter == end ? nullptr : *iter; + } + } + } + + mNumTollbooths = 0; + + { + GObjectIterator iter(0x800); + + while (iter.IsValid()) { + if (iter.GetInstance()->GetTriggerEnabled()) { + mNumTollbooths++; + } + + iter.Advance(); + } + } + + if (!mRaceParms || !mRaceParms->GetIsDDayRace() || bStrCmp(mRaceParms->GetEventID(), "16.1.0") == 0) { + if (!FEDatabase->IsFinalEpicChase()) { + goto skipAutoSave; + } + } + + { + new EAutoSave(); + } + +skipAutoSave: + new EReloadHud(); + +#ifndef EA_BUILD_A124 + mCaluclatedAdaptiveGain = false; +#endif +} + +void GRaceStatus::NotifyScriptWhenLoaded() { + bool racersLoading = IsLoading(); + bool trackLoading = TheTrackStreamer.IsLoadingInProgress(); + bool copsSpawning = false; + + if (ICopMgr::Get() && ICopMgr::Get()->IsCopSpawnPending()) { + copsSpawning = true; + } + + if (trackLoading) { + if (!racersLoading) { + if (!copsSpawning) { + if (!TheTrackStreamer.IsFarLoadingInProgress()) { + trackLoading = false; + } + } + } + } + + if (racersLoading || trackLoading || copsSpawning) { + new EFadeScreenOn(false); + } + + mScriptWaitingForLoad = true; +} + +void GRaceStatus::SetRoaming() { + bool lastDDay = false; + IPlayer *player; + + if (mRaceParms) { + lastDDay = bStrCmp(mRaceParms->GetEventID(), GRaceDatabase::Get().GetDDayEndRace()) == 0; + if (!mRaceParms->GetGameplayObj()->PostRaceActivity().GetCollectionKey()) { + g_pEAXSound->StartNewGamePlay(); + } + } else { + g_pEAXSound->StartNewGamePlay(); + } + + mPlayMode = kPlayMode_Roaming; + SetRaceContext(GRace::kRaceContext_Career); + mIsLoading = false; + mRaceParms = nullptr; + WRoadNetwork::Get().ResetShortcuts(); + + player = IPlayer::First(PLAYER_ALL); + while (player) { + ISimable *simable; + IVehicle *vehicle; + IVehicleAI *vehicleAI; + + if (player->InGameBreaker()) { + player->ResetGameBreaker(true); + } + + simable = player->GetSimable(); + if (simable && simable->QueryInterface(&vehicle)) { + vehicle->ForceStopOff(vehicle->GetForceStop()); + + if (vehicle->QueryInterface(&vehicleAI) && !vehicleAI->GetPursuit()) { + ICopMgr *copMgr = ICopMgr::Get(); + + if (copMgr) { + copMgr->ResetCopsForRestart(true); + } + } + } + + player = player->Next(PLAYER_ALL); + } + + if (!lastDDay && !GManager::Get().GetHasPendingRestartEvent()) { + new EReloadHud(); + } + + new EAutoSave(); + bool dDay = false; + if (mRaceParms) { + if (mRaceParms->GetIsDDayRace()) { + dDay = true; + } + } + + if (!dDay) { + SetOverRideRainIntensity(0.0f); + } + + GManager::Get().SpawnAllLoadedSectionIcons(); + ICopMgr::mDisableCops = 0; +} + +void GRaceStatus::CalculateRankings() { + GRacerInfo *sortedRacers[16]; + bool sortByPoints; + int players_encountered = 0; + + for (int idx = 0; idx < mRacerCount; ++idx) { + sortedRacers[idx] = &mRacerInfo[idx]; + } + + sortByPoints = mRaceParms->GetRankPlayersByPoints(); + for (int sort1 = mRacerCount - 1; sort1 > 0; --sort1) { + for (int sort2 = 0; sort2 < sort1; ++sort2) { + bool swap; + + if (sortByPoints) { + float p1 = sortedRacers[sort1]->GetPointTotal(); + float p2 = sortedRacers[sort2]->GetPointTotal(); + + if (p1 + p2 <= 0.0f) { + swap = sortedRacers[sort2]->IsBehind(*sortedRacers[sort1]); + } else { + swap = p2 < p1; + } + } else { + swap = sortedRacers[sort2]->IsBehind(*sortedRacers[sort1]); + } + + if (swap) { + GRacerInfo *temp = sortedRacers[sort1]; + + sortedRacers[sort1] = sortedRacers[sort2]; + sortedRacers[sort2] = temp; + } + } + } + + for (int onSetRank = 0; onSetRank < mRacerCount; ++onSetRank) { + int ranking = onSetRank + 1; + GRacerInfo *info = sortedRacers[onSetRank]; + + info->SetRanking(ranking); + if (!info->GetIsHuman()) { + info->mAiRanking = 0; + ++players_encountered; + } else { + info->mAiRanking = ranking - players_encountered; + } + } +} + +void GRaceStatus::SortCheckPointRankings() { + GRacerInfo *rankings[16]; + + for (int checkpoint = 0; checkpoint < 16; ++checkpoint) { + for (int i = 0; i < mRacerCount; ++i) { + rankings[i] = &mRacerInfo[i]; + } + + for (int i = mRacerCount - 1; i > 0; --i) { + for (int j = 0; j < i; ++j) { + GRacerInfo *info = rankings[i]; + + if (rankings[j]->GetSpeedTrapSpeed(checkpoint) < info->GetSpeedTrapSpeed(checkpoint)) { + rankings[i] = rankings[j]; + rankings[j] = info; + } + } + } + + for (int i = 0; i < mRacerCount; ++i) { + rankings[i]->mSpeedTrapPosition[checkpoint] = i + 1; + } + } +} + +void GRaceStatus::Update(float dT) { +#ifndef EA_BUILD_A124 + if (GetPlayMode() == kPlayMode_Racing && mRefreshBinAfterRace) { + RefreshBinWhileInGame(); + mRefreshBinAfterRace = false; + } +#endif + + if (GetPlayMode() == kPlayMode_Roaming) { + if (mQueueBinChange) { + EnterBin(FEDatabase->GetCareerSettings()->GetCurrentBin()); + mQueueBinChange = false; + } + +#ifndef EA_BUILD_A124 + if (GetPlayMode() == kPlayMode_Roaming && mWarpWhenInFreeRoam && GManager::Get().WarpToMarker(mWarpWhenInFreeRoam, false)) { + mWarpWhenInFreeRoam = 0; + } +#endif + } + + { + int num_racers = mRacerCount; + + if (GetPlayMode() == kPlayMode_Racing && num_racers > 0) { + int num_ai_racers = 0; + float float_racers = 0.0f; + float percent_complete = 0.0f; + float elapsed; + int elapsedSec; + + for (int idx = 0; idx < num_racers; ++idx) { + GRacerInfo &info = GetRacerInfo(idx); + + if (info.mGameCharacter) { + ++num_ai_racers; + } + } + + for (int idx = 0; idx < num_racers; ++idx) { + GRacerInfo &info = GetRacerInfo(idx); + + info.Update(dT); + + if (info.mhSimable) { + float weight = 1.0f; + + if (!info.mGameCharacter) { + weight = bMax(1.0f, static_cast(num_ai_racers)); + } + + float_racers += weight; + percent_complete += weight * info.mPctRaceComplete; + } + } + + fAveragePercentComplete = percent_complete / bMax(1.0f, float_racers); + CalculateRankings(); + +#ifndef EA_BUILD_A124 + for (int idx = 0; idx < num_racers; ++idx) { + GetRacerInfo(idx).UpdateSplits(); + } +#endif + + UCrc32 gameplayMessage(0x20D60DBF); + elapsed = GetRaceTimeElapsed(); + bool isTimeLimited; + + if (mRaceParms) { + isTimeLimited = mRaceParms->GetTimeLimit() > 0.0f; + } else { + isTimeLimited = false; + } + float timeRemaining = GetRaceTimeRemaining(); + + MNotifyRaceTime(elapsed, isTimeLimited, timeRemaining).Post(gameplayMessage); + + elapsed = GetRaceTimeElapsed(); + elapsedSec = static_cast(elapsed); + if (elapsedSec > mLastSecondTickSent) { + mLastSecondTickSent = elapsedSec; + MNotifyRaceTimeSecTick(elapsed).Post(gameplayMessage); + } + + if (GetIsTimeLimited()) { + if (IsChallengeRace()) { +#ifndef EA_BUILD_A124 + if (mPlayerPursuitInCooldown == 1) { + if (mRaceMasterTimer.IsRunning()) { + mRaceMasterTimer.Stop(); + } + } else if (!mRaceMasterTimer.IsRunning()) { + mRaceMasterTimer.Start(); + } +#endif + } + + if (!mTimeExpiredMsgSent && GetRaceTimeRemaining() <= 0.0f) { + MNotifyRaceTimeExpired().Post(gameplayMessage); + mTimeExpiredMsgSent = true; + + for (int idx = 0; idx < num_racers; ++idx) { + GRacerInfo &info = mRacerInfo[idx]; + + if (!info.GetGameCharacter() && !info.IsFinishedRacing()) { + info.ForceStop(); + } + } + } + } + } + } + + if (mScriptWaitingForLoad) { + bool racersLoading; + bool trackLoading; + bool copsSpawning; + ICopMgr *copMgr; + + racersLoading = IsLoading(); + trackLoading = true; + if (!TheTrackStreamer.IsLoadingInProgress()) { + trackLoading = false; + } + copsSpawning = false; + copMgr = ICopMgr::Get(); + + if (copMgr && copMgr->IsCopSpawnPending()) { + copsSpawning = true; + } + + if (!trackLoading && !racersLoading && !copsSpawning) { + MLoadingComplete().Post(UCrc32(0x20D60DBF)); + mScriptWaitingForLoad = false; + } + } +} + +GRacerInfo &GRaceStatus::GetRacerInfo(int index) { + return mRacerInfo[index]; +} + +GRacerInfo *GRaceStatus::GetRacerInfo(ISimable *isim) { + { + int idx = 0; + + for (; idx < mRacerCount; ++idx) { + GRacerInfo &info = mRacerInfo[idx]; + + if (info.GetSimable() == isim) { + return &info; + } + } + } + + return nullptr; +} + +GRacerInfo *GRaceStatus::GetWinningPlayerInfo() { + IPlayer *player; + + player = IPlayer::First(PLAYER_ALL); + while (player) { + ISimable *sim; + GRacerInfo *info; + + sim = player->GetSimable(); + info = GetRacerInfo(sim); + if (info && info->IsFinishedRacing()) { + if (info->GetRanking() == 1) { + return info; + } + } + + { + const IPlayer *currentPlayer = player; + IPlayer::List::const_iterator iter = + std::find(IPlayer::GetList(PLAYER_ALL).begin(), IPlayer::GetList(PLAYER_ALL).end(), currentPlayer); + IPlayer::List::const_iterator end = IPlayer::GetList(PLAYER_ALL).end(); + + if (iter == end) { + player = nullptr; + } else { + ++iter; + player = iter == end ? nullptr : *iter; + } + } + } + + return nullptr; +} + +void GRaceStatus::StartMasterTimer() { + GRaceParameters *race_parameters; + float start_time; + + race_parameters = GetRaceParameters(); + if (race_parameters) { + start_time = race_parameters->GetStartTime(); + } else { + start_time = 0.0f; + } + + mRaceMasterTimer.Reset(start_time); + mRaceMasterTimer.Start(); +} + +void GRaceStatus::StopMasterTimer() { + mRaceMasterTimer.Stop(); +} + +float GRaceStatus::GetRaceTimeElapsed() const { + if (!mRaceParms) { + return 0.0f; + } + + return mRaceMasterTimer.GetTime(); +} + +float GRaceStatus::GetRaceTimeRemaining() const { + float totalTime = mRaceParms ? mRaceParms->GetTimeLimit() : 0.0f; + + totalTime += mBonusTime; + if (totalTime <= 0.0f) { + return 0.0f; + } + + { + float timeRemaining = totalTime - mRaceMasterTimer.GetTime(); + if (timeRemaining < 0.0f) { + return 0.0f; + } + + return timeRemaining; + } +} + +void GRaceStatus::ClearRacers() { + const IVehicle::List &list = IVehicle::GetList(VEHICLE_RACERS); + + for (IVehicle *const *iter = list.begin(); iter != list.end(); ++iter) { + IVehicle *vehicle = *iter; + ISimable *simable = vehicle->GetSimable(); + + if (simable) { + if (!simable->IsPlayer()) { + simable->Kill(); + } + } + } + + mRacerCount = 0; +} + +GRacerInfo &GRaceStatus::AddSimablePlayer(ISimable *isim) { + GRacerInfo &info = mRacerInfo[mRacerCount]; + const char *name; + + ++mRacerCount; + + info.mhSimable = nullptr; + info.mGameCharacter = nullptr; + info.mName = nullptr; + info.mIndex = -1; + info.mSavedHeatLevel = 0.0f; + info.mSavedSpeed = 0.0f; + info.mSavedPosition = UMath::Vector3::kZero; + info.mSavedDirection = UMath::Vector3::kZero; + info.mRanking = 0; + info.mAiRanking = 0; + info.mKnockedOut = false; + info.mTotalled = false; + info.mEngineBlown = false; + info.mBusted = false; + info.mChallengeComplete = false; + info.mFinishedRacing = false; + info.mCameraDetached = false; + info.mLapsCompleted = 0; + info.mPctRaceComplete = 0.0f; + info.mPctLapComplete = 0.0f; + info.mCheckpointsHitThisLap = 0; + info.mDistToNextCheckpoint = 0.0f; + info.mDistanceDriven = 0.0f; + info.mTopSpeed = 0.0f; + info.mFinishingSpeed = 0.0f; + info.mPoundsNOSUsed = 0.0f; + info.mTimeCrossedLastCheck = 0.0f; + info.mTotalUpdateTime = 0.0f; + info.mPointTotal = 0.0f; + info.mZeroToSixtyTime = 0.0f; + info.mQuarterMileTime = 0.0f; + info.mSpeedBreakerTime = 0.0f; +#ifndef EA_BUILD_A124 + info.mDNF = false; +#endif + info.mTollboothsCrossed = 0; + info.mSpeedTrapsCrossed = 0; + info.mNumPerfectShifts = 0; + info.mNumTrafficCarsHit = 0; + + info.mRaceTimer.Stop(); + info.mRaceTimer.Reset(0.0f); + info.mLapTimer.Stop(); + info.mLapTimer.Reset(0.0f); + info.mCheckTimer.Stop(); + info.mCheckTimer.Reset(0.0f); + + for (int i = 0; i < 16; ++i) { + info.mTimeRemainingToBooth[i] = 0.0f; + } + + for (int i = 0; i < 16; ++i) { + info.mSpeedTrapSpeed[i] = 0.0f; + info.mSpeedTrapPosition[i] = -1; + } + +#ifndef EA_BUILD_A124 + for (int i = 0; i < 4; ++i) { + info.mSplitTimes[i] = 0.0f; + info.mSplitRankings[i] = 0; + } +#endif + + info.SetSimable(isim); + info.SetIndex(mRacerCount - 1); + + if (Sim::GetUserMode() == 1) { + if (mRacerCount == 1) { + name = GetLocalizedString(0x7B070984); + } else { + name = GetLocalizedString(0x7B070985); + } + } else { + UserProfile *userProfile = FEDatabase->CurrentUserProfiles[isim->GetPlayer()->GetSettingsIndex()]; + + if (!userProfile) { + name = GetLocalizedString(0xF760EABE); + info.SetName(name); + return info; + } + + name = userProfile->GetProfileName(); + } + + info.SetName(name); + return info; +} + +inline void GRacerInfo::ClearAll() { + ClearRaceStats(); +} + +void GRaceStatus::AddRacer(GRuntimeInstance *racer) { + GRacerInfo &info = mRacerInfo[mRacerCount]; + + ++mRacerCount; + info.ClearAll(); + + info.mGameCharacter = static_cast(racer); + info.SetIndex(mRacerCount - 1); + + if (!info.ChooseBossName() && !info.ChooseRacerName()) { + info.ChooseRandomName(); + } +} + +void GRaceStatus::SetRaceActivity(GActivity *activity) { + mRaceParms = activity ? GRaceDatabase::Get().GetRaceFromActivity(activity) : nullptr; + DetermineRaceLength(); +} + +void GRaceStatus::EnableBarriers() { + if (mRaceParms) { + for (unsigned int onBarrier = 0; onBarrier < mRaceParms->GetBarrierCount(); onBarrier++) { + { + const char *barrierName = mRaceParms->GetBarrierName(onBarrier); + bool flipFlag = mRaceParms->GetBarrierIsFlipped(onBarrier); + + EnableBarrierSceneryGroup(barrierName, flipFlag); + } + } + + WCollisionAssets::Get().SetExclusionFlags(); + WRoadNetwork::Get().ResolveBarriers(); + } +} + +void GRaceStatus::DisableBarriers() { + RedoTopologyAndSceneryGroups(); + WRoadNetwork::Get().ResetBarriers(); + WRoadNetwork::Get().ResetRaceSegments(); +} + +void GRaceStatus::AddAvailableEventToMap(GRuntimeInstance *trigger, GRuntimeInstance *activity) {} + +void GRaceStatus::AddSpeedTrapToMap(GRuntimeInstance *trigger) {} + +void GRaceStatus::AwardBonusTime(float seconds) { + for (int i = 0; i < mRacerCount; ++i) { + GRacerInfo &info = mRacerInfo[i]; + + info.mTimeRemainingToBooth[info.mTollboothsCrossed] = GRaceStatus::Get().GetRaceTimeRemaining(); + info.mTollboothsCrossed++; + } + + mBonusTime += seconds; +} + +void GRaceStatus::ClearCheckpoints() { + mCheckpoints.clear(); + mNextCheckpoint = nullptr; +} + +void GRaceStatus::AddCheckpoint(GRuntimeInstance *trigger) { + GTrigger *gtrigger = static_cast(trigger); + + if (!gtrigger) { + return; + } + + mCheckpoints.push_back(gtrigger); + if (!mNextCheckpoint) { + mNextCheckpoint = gtrigger; + } +} + +void GRaceStatus::SetNextCheckpointPos(GRuntimeInstance *trigger) { + GTrigger *gtrigger = static_cast(trigger); + bool checkpointVisible = false; + + mNextCheckpoint = gtrigger; + + if (mRaceParms) { + checkpointVisible = mRaceParms->GetCheckpointsVisible(); + } + + if (gtrigger && checkpointVisible) { + UMath::Vector3 triggerPos; + bVector3 triggerPosSwiz; + bMatrix4 mat; + + if (!mCheckpointModel) { + mCheckpointModel = new WorldModel(0x738B1F9B, nullptr, false); + } + + gtrigger->GetPosition(triggerPos); + eSwizzleWorldVector(reinterpret_cast(triggerPos), triggerPosSwiz); + bIdentity(&mat); + bCopy(&mat.v3, &triggerPosSwiz, 1.0f); + mCheckpointModel->SetMatrix(&mat); + + if (mCheckpointEmitter) { + mCheckpointEmitter->SetLocalWorld(&mat); + } + } else { + if (mCheckpointModel) { + delete mCheckpointModel; + } + mCheckpointModel = nullptr; + + if (mCheckpointEmitter) { + mCheckpointEmitter->UnSubscribe(); + delete mCheckpointEmitter; + mCheckpointEmitter = nullptr; + } + } +} + +float GRaceStatus::GetAdaptiveDifficutly() const { + if (GetRaceContext() == GRace::kRaceContext_Career) { + return fCatchUpAdaptiveBonus; + } + + return 0.0f; +} + +void GRaceStatus::SyncronizeAdaptiveBonus() { + if (fCatchUpAdaptiveBonus < 0.0f) { + fCatchUpAdaptiveBonus = 0.0f; + } +} + +void GRaceStatus::UpdateAdaptiveDifficulty(eAdaptiveGainReason reason, ISimable *who) { + bool update; + GRacerInfo *winning_player; + GRacerInfo *eliminated_player; + GRacerInfo *winning_ai; + float difficulty; + + if (mCaluclatedAdaptiveGain || GetRaceContext() != GRace::kRaceContext_Career || GetRacerCount() <= 1 || Sim::GetUserMode() != 0 || GetPlayMode() != kPlayMode_Racing || + (mRaceParms && mRaceParms->GetNoPostRaceScreen()) || GetRaceLength() <= 0.0f) { + return; + } + + difficulty = fCatchUpAdaptiveBonus; + const int num_racers = GetRacerCount(); + update = false; + winning_player = nullptr; + eliminated_player = nullptr; + winning_ai = nullptr; + + for (int i = 0; i < num_racers; ++i) { + GRacerInfo &info = GetRacerInfo(i); + + if (!info.GetGameCharacter()) { + if (info.GetIsBusted()) { + return; + } + + if (info.IsFinishedRacing() && info.GetRanking() == 1) { + winning_player = &info; + } else if (GetRaceType() == GRace::kRaceType_Knockout && info.GetIsKnockedOut()) { + eliminated_player = &info; + } + } else if (info.IsFinishedRacing() && info.GetRanking() == 1) { + winning_ai = &info; + } + } + + if (static_cast(reason) < 2) { + if (!who || !who->IsPlayer()) { + return; + } + + float percent_ai_complete = 0.0f; + float percent_human_complete = 0.0f; + + for (int i = 0; i < num_racers; ++i) { + GRacerInfo &info = GetRacerInfo(i); + + if (!info.GetIsKnockedOut()) { + if (!info.GetGameCharacter()) { + percent_human_complete = UMath::Max(percent_human_complete, info.GetPctRaceComplete()); + } else { + percent_ai_complete = UMath::Max(percent_ai_complete, info.GetPctRaceComplete()); + } + } + } + + if (percent_ai_complete > percent_human_complete && percent_human_complete > 0.0f) { + float lose_margin = ((percent_ai_complete - percent_human_complete) * (GetRaceLength() * 0.01f)) / 300.0f; + float t = UMath::Ramp(lose_margin, 0.0f, 1.0f); + float bonus = UMath::Lerp(0.0f, -0.4f, t); + + difficulty = bClamp(difficulty + ((bonus * percent_human_complete) * 0.01f), -1.0f, 1.0f); + update = true; + } + } else if (reason == kAdaptiveGainReason_5) { + float percent_ai_complete = 0.0f; + float percent_human_complete = 0.0f; + + for (int i = 0; i < num_racers; ++i) { + GRacerInfo &info = GetRacerInfo(i); + + if (!info.GetIsKnockedOut() && !info.GetIsTotalled() && !info.GetIsEngineBlown()) { + if (!info.GetGameCharacter()) { + percent_human_complete = UMath::Max(percent_human_complete, info.GetPctRaceComplete()); + } else { + percent_ai_complete = UMath::Max(percent_ai_complete, info.GetPctRaceComplete()); + } + } + } + + if (percent_ai_complete > percent_human_complete && percent_human_complete > 0.0f) { + float lose_margin = ((percent_ai_complete - percent_human_complete) * (GetRaceLength() * 0.01f)) / 300.0f; + float t = UMath::Ramp(lose_margin, 0.0f, 1.0f); + float bonus = UMath::Lerp(0.0f, -0.4f, t); + + difficulty = bClamp(difficulty + ((bonus * percent_human_complete) * 0.01f), -1.0f, 1.0f); + update = true; + } + } else if (GetRaceType() == GRace::kRaceType_SpeedTrap) { + float player_points = 0.0f; + float ai_points = 0.0f; + float total_points; + + for (int i = 0; i < num_racers; ++i) { + if (!GetRacerInfo(i).IsFinishedRacing()) { + return; + } + + if (GetRacerInfo(i).GetGameCharacter()) { + ai_points = UMath::Max(ai_points, GetRacerInfo(i).GetPointTotal()); + } else { + player_points = GetRacerInfo(i).GetPointTotal(); + } + } + + total_points = UMath::Max(player_points, ai_points); + if (total_points > 0.0f && player_points > 0.0f && ai_points > 0.0f) { + float point_spread_ratio = (player_points - ai_points) / total_points; + + if (point_spread_ratio > 0.0f) { + float lose_margin = point_spread_ratio; + float t = UMath::Ramp(lose_margin, 0.05f, 0.2f); + float bonus = UMath::Lerp(0.0f, 0.2f, t); + + difficulty = bClamp(difficulty + bonus, -1.0f, 1.0f); + } else { + float win_margin = -point_spread_ratio; + float t = UMath::Ramp(win_margin, 0.0f, 0.2f); + float bonus = UMath::Lerp(0.0f, -0.2f, t); + + difficulty = bClamp(difficulty + bonus, -1.0f, 1.0f); + } + + update = true; + } + } else if (winning_ai) { + float max_pct_complete = 0.0f; + float win_margin; + + for (int i = 0; i < num_racers; ++i) { + GRacerInfo *info = &GetRacerInfo(i); + + if (info != winning_ai && !info->IsFinishedRacing() && !info->GetIsTotalled() && !info->GetIsEngineBlown()) { + max_pct_complete = bMax(max_pct_complete, info->GetPctRaceComplete()); + } + } + + win_margin = 100.0f - max_pct_complete; + win_margin *= 0.01f; + win_margin *= GetRaceLength(); + + if (win_margin > 0.0f && max_pct_complete > 0.0f) { + float t = UMath::Ramp(win_margin, 200.0f, 750.0f); + float bonus = UMath::Lerp(0.0f, 0.4f, t); + + difficulty = bClamp(difficulty + bonus, -1.0f, 1.0f); + update = true; + } + } else if (winning_player) { + for (int i = 0; i < num_racers; ++i) { + GRacerInfo *info = &GetRacerInfo(i); + + if (!info->GetGameCharacter() && !info->IsFinishedRacing()) { + float percent_human_complete = info->GetPctRaceComplete(); + float lose_margin = GetRaceLength() * ((100.0f - percent_human_complete) * 0.01f); + + if (lose_margin > 0.0f) { + float t = UMath::Ramp(lose_margin, 0.0f, 300.0f); + float bonus = UMath::Lerp(-0.1f, -0.4f, t); + + difficulty = bClamp(difficulty + ((bonus * percent_human_complete) * 0.01f), -1.0f, 1.0f); + update = true; + } + } + } + } else if (eliminated_player) { + float num_laps; + float race_lose_margin; + float lose_margin; + + int laps = GetRaceParameters()->GetNumLaps(); + + if (laps <= 1) { + laps = 1; + } + + num_laps = static_cast(laps); + + lose_margin = GetRaceLength() * ((100.0f - eliminated_player->GetPctRaceComplete()) * 0.01f); + race_lose_margin = GetRaceLength() / num_laps; + lose_margin = UMath::Mod(lose_margin, race_lose_margin); + + if (lose_margin > 0.0f) { + float t = UMath::Ramp(lose_margin / 300.0f, 0.0f, 1.0f); + float bonus = UMath::Lerp(-0.1f, -0.4f, t); + + difficulty = bClamp(difficulty + ((bonus * eliminated_player->GetPctRaceComplete()) * 0.01f), -1.0f, 1.0f); + update = true; + } + } + + if (update) { + mCaluclatedAdaptiveGain = true; + difficulty = bClamp(difficulty, -1.0f, 1.0f); + + FEDatabase->GetCareerSettings()->SetAdaptiveDifficulty(difficulty); + + fCatchUpAdaptiveBonus = difficulty; + } +} + +bool GRaceStatus::ComputeCatchUpSkill(GRacerInfo *racer_info, PidError *pid, float *output, float *skill, bool off_world) { + float glue_level = 0.5f; + bool is_boss = false; + bool use_race_override = false; + float percent_complete; + float glue_skill; + float glue_spread; + float glue_integral; + float glue_derivative; + float glue_p; + float glue_error; + float glue_output; + + if (off_world) { + glue_level = 1.0f; + } else { + if (GetRaceContext() != GRace::kRaceContext_QuickRace) { + if (GetRaceContext() != GRace::kRaceContext_Career) { + return false; + } + + glue_level = UMath::Clamp((GetAdaptiveDifficutly() + 1.0f) * 0.5f, 0.0f, 1.0f); + if (mRaceParms) { + if (mRaceParms->GetCatchUpOverride()) { + use_race_override = true; + } + + if (mRaceParms->GetIsBossRace()) { + is_boss = true; + glue_level = UMath::Lerp(glue_level, 1.0f, 0.5f); + } + } + } else { + if (mRaceParms && mRaceParms->GetCatchUp()) { + glue_level = Tweak_QuickRaceGlue[mRaceParms->GetDifficulty()]; + } else { + return false; + } + } + } + + percent_complete = racer_info->GetPctRaceComplete(); + glue_p = pid->GetErrorIntegral(); + glue_error = pid->GetErrorDerivative(); + + if (!use_race_override) { + glue_spread = UMath::Lerp(Tweak_GlueSpreadTable_Low.GetValue(percent_complete), Tweak_GlueSpreadTable_High.GetValue(percent_complete), glue_level); + glue_skill = UMath::Lerp(Tweak_GlueStrengthTable_Low.GetValue(percent_complete), Tweak_GlueStrengthTable_High.GetValue(percent_complete), glue_level); + glue_integral = 5.0e-5f; + glue_derivative = 0.01f; + glue_p *= glue_integral; + glue_error *= glue_derivative; + } else { + { + Table glue_table(aCatchUpSkillData, nCatchUpSkillEntries, 0.0f, 100.0f); + + glue_skill = glue_table.GetValue(percent_complete); + } + { + Table glue_table(aCatchUpSpreadData, nCatchUpSpreadEntries, 0.0f, 100.0f); + + glue_spread = glue_table.GetValue(percent_complete); + } + glue_derivative = fCatchUpDerivative; + glue_integral = fCatchUpIntegral; + glue_p *= glue_integral; + glue_error *= glue_derivative; + } + + glue_spread = bMax(100.0f, glue_spread); + glue_output = bClamp((2.0f / glue_spread) * pid->GetError() + glue_p + glue_error, -1.0f, 1.0f); + *skill = glue_skill * glue_output; + + if (is_boss) { + glue_output = 1.0f; + } + + *output = glue_output; + return true; +} + +void GRaceStatus::MakeDefaultCatchUpData() { + nCatchUpSkillEntries = 0; + nCatchUpSpreadEntries = 0; + fCatchUpIntegral = 0.0f; + fCatchUpDerivative = 0.0f; + bMemSet(aCatchUpSkillData, 0, sizeof(aCatchUpSkillData)); + bMemSet(aCatchUpSpreadData, 0, sizeof(aCatchUpSpreadData)); +} + +void GRaceStatus::MakeCatchUpData() { + MakeDefaultCatchUpData(); + if (mRaceParms) { + ParseCatchUpData(mRaceParms->GetCatchUpSkill(), mRaceParms->GetCatchUpSpread()); + fCatchUpIntegral = mRaceParms->GetCatchUpIntegral(); + fCatchUpDerivative = mRaceParms->GetCatchUpDerivative(); + } +} + +void GRaceStatus::ParseCatchUpData(const char *skill, const char *spread) {} + +int GRaceStatus::GetCheckpointCount() { + return mCheckpoints.size(); +} + +GTrigger *GRaceStatus::GetCheckpoint(int index) { + return mCheckpoints[index]; +} + +GTrigger *GRaceStatus::GetNextCheckpoint() { + return mNextCheckpoint; +} + +void GRaceStatus::ClearTimes() { + GRaceParameters *race_parameters; + float start_time; + + bMemSet(mLapTimes, 0, sizeof(mLapTimes)); + bMemSet(mCheckTimes, 0, sizeof(mCheckTimes)); + + race_parameters = GetRaceParameters(); + if (race_parameters) { + start_time = race_parameters->GetStartTime(); + } else { + start_time = 0.0f; + } + + mRaceMasterTimer.Stop(); + mRaceMasterTimer.Reset(start_time); + mBonusTime = 0.0f; + mTaskTime = 0.0f; + mTimeExpiredMsgSent = false; + mSuddenDeathMode = false; + mLastSecondTickSent = 0; +} + +void GRaceStatus::SetLapTime(int lapIndex, int racerIndex, float time) { + mLapTimes[lapIndex][racerIndex] = time; +} + +float GRaceStatus::GetLapTime(int lapIndex, int racerIndex, bool bCumulativeTimeAtLap) { + if (bCumulativeTimeAtLap) { + float totalTime = 0.0f; + + { + int i = 0; + + if (i > lapIndex) { + return totalTime; + } + + do { + float time = mLapTimes[i][racerIndex]; + + if (time <= 0.0f) { + return 0.0f; + } + + totalTime += time; + ++i; + } while (i <= lapIndex); + } + + return totalTime; + } + + return mLapTimes[lapIndex][racerIndex]; +} + +void GRaceStatus::SetCheckpointTime(int lapIndex, int checkIndex, int racerIndex, float time) { + mCheckTimes[lapIndex][checkIndex][racerIndex] = time; +} + +float GRaceStatus::GetCheckpointTime(int lapIndex, int checkIndex, int racerIndex, bool bCumulative) { + float time = mCheckTimes[lapIndex][checkIndex][racerIndex]; + + if (bCumulative) { + for (int i = 0; i < checkIndex; ++i) { + time += mCheckTimes[lapIndex][i][racerIndex]; + } + } + + return time; +} + +int GRaceStatus::GetLapPosition(int lapIndex, int racerIndex, bool bOverallPosition) { + float lapTime = GetLapTime(lapIndex, racerIndex, bOverallPosition); + int numFaster = 0; + + for (int onRacer = 0; onRacer < mRacerCount; ++onRacer) { + if (onRacer == racerIndex) { + continue; + } + + float onRacerLapTime = GetLapTime(lapIndex, onRacer, bOverallPosition); + if (onRacerLapTime < lapTime && onRacerLapTime > 0.0f) { + ++numFaster; + } + } + + return numFaster + 1; +} + +float GRaceStatus::GetBestLapTime(int racerIndex) { + float bestLapTime; + + { + int onLap; + + bestLapTime = GetLapTime(0, racerIndex, false); + onLap = 1; + while (onLap < mRaceParms->GetNumLaps()) { + { + float lapTime = GetLapTime(onLap, racerIndex, false); + + if (lapTime > 0.0f && lapTime < bestLapTime) { + bestLapTime = lapTime; + } + } + + onLap++; + } + } + + return bestLapTime; +} + +float GRaceStatus::GetWorstLapTime(int racerIndex) { + float worstLapTime; + + { + int onLap; + + worstLapTime = GetLapTime(0, racerIndex, false); + onLap = 1; + while (onLap < mRaceParms->GetNumLaps()) { + { + float lapTime = GetLapTime(onLap, racerIndex, false); + + if (lapTime > 0.0f && lapTime > worstLapTime) { + worstLapTime = lapTime; + } + } + + onLap++; + } + } + + return worstLapTime; +} + +float GRaceStatus::GetFinishTimeBehind(int racerIndex) { + GRacerInfo &info = mRacerInfo[racerIndex]; + float finishTime = info.GetRaceTime(); + float bestFinish = finishTime; + + for (int i = 0; i < mRacerCount; ++i) { + if (i == racerIndex) { + continue; + } + + GRacerInfo &opponent = mRacerInfo[i]; + if (opponent.GetRaceTime() < bestFinish) { + bestFinish = opponent.GetRaceTime(); + } + } + + return finishTime - bestFinish; +} + +int GRaceStatus::GetLapsLed(int racerIndex) { + int numLed = 0; + + for (int i = 0; i < 10; ++i) { + if (GetLapPosition(i, racerIndex, true) == 1) { + ++numLed; + } + } + + return numLed; +} + +float GRaceStatus::GetRaceSpeedTrapSpeed(int trapIndex, int racerIndex) { + GRacerInfo &info = GetRacerInfo(racerIndex); + return info.mSpeedTrapSpeed[trapIndex]; +} + +int GRaceStatus::GetRaceSpeedTrapPosition(int trapIndex, int racerIndex) { + GRacerInfo &info = GetRacerInfo(racerIndex); + return info.mSpeedTrapPosition[trapIndex]; +} + +float GRaceStatus::GetBestSpeedTrapSpeed(int racerIndex) { + float bestSpeedtrapSpeed = GetRaceSpeedTrapSpeed(0, racerIndex); + + for (int onSpeedtrap = 1; onSpeedtrap < GetNumRaceSpeedTraps(); ++onSpeedtrap) { + float speedtrapSpeed = GetRaceSpeedTrapSpeed(onSpeedtrap, racerIndex); + + if (speedtrapSpeed > 0.0f && speedtrapSpeed < bestSpeedtrapSpeed) { + bestSpeedtrapSpeed = speedtrapSpeed; + } + } + + return bestSpeedtrapSpeed; +} + +float GRaceStatus::GetWorstSpeedTrapSpeed(int racerIndex) { + float worstSpeedtrapSpeed = GetRaceSpeedTrapSpeed(0, racerIndex); + + for (int onSpeedtrap = 1; onSpeedtrap < GetNumRaceSpeedTraps(); ++onSpeedtrap) { + float speedtrapSpeed = GetRaceSpeedTrapSpeed(onSpeedtrap, racerIndex); + + if (speedtrapSpeed > 0.0f && speedtrapSpeed > worstSpeedtrapSpeed) { + worstSpeedtrapSpeed = speedtrapSpeed; + } + } + + return worstSpeedtrapSpeed; +} + +float GRaceStatus::GetRaceTollboothTime(int boothIndex, int racerIndex) { + GRacerInfo &info = GetRacerInfo(racerIndex); + return info.mTimeRemainingToBooth[boothIndex]; +} + +void GRaceStatus::RaceAbandoned() { + if (GetRaceContext() != GRace::kRaceContext_Career) { + return; + } + + if (mRaceBin) { + GRaceParameters *parms = GetRaceParameters(); + + if (parms && parms->GetIsBossRace() && !parms->GetIsEpicPursuitRace()) { + unsigned int numBossRaces = mRaceBin->GetBossRaceCount(); + + for (unsigned int index = 0; index < numBossRaces; ++index) { + unsigned int raceHash = mRaceBin->GetBossRaceHash(index); + + GRaceDatabase::Get().ResetCareerCompleteFlag(raceHash); + } + + GManager::Get().RefreshEngageTriggerIcons(); + } + } +} + +void GRaceStatus::EndStopAll() { + for (int i = 0; i < mRacerCount; ++i) { + mRacerInfo[i].ForceStop(); + } +} + +void GRaceStatus::FinalizeRaceStats() { + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + for (int i = 0; i < mRacerCount; ++i) { + mRacerInfo[i].FinalizeRaceStats(); + } + } +} + +bool GRaceStatus::IsAudioLoading() { + for (int i = 0; i < GetRacerCount(); ++i) { + ISimable *simable = GetRacerInfo(i).GetSimable(); + + if (simable) { + IAudible *iaudible; + + if (simable->QueryInterface(&iaudible) && !iaudible->IsAudible()) { + return true; + } + } + } + + return false; +} + +bool GRaceStatus::IsModelsLoading() { + for (int i = 0; i < GetRacerCount(); ++i) { + ISimable *simable = GetRacerInfo(i).GetSimable(); + + if (simable) { + IRenderable *irenderable; + + if (simable->QueryInterface(&irenderable) && !irenderable->IsRenderable()) { + return true; + } + } + } + + return false; +} + +bool GRaceStatus::IsLoading() { + return mIsLoading || IsAudioLoading() || IsModelsLoading(); +} + +float GRaceStatus::GetSegmentLength(int segment, int lap) { + if (segment < 0 || segment >= 18) { + return 0.0f; + } + + return mSegmentLengths[segment]; +} + +void GRaceStatus::EnterSuddenDeath() { + mSuddenDeathMode = true; +} + +float GRaceStatus::DetermineRaceSegmentLength(const UMath::Vector4 *positions, const UMath::Vector4 *directions, int start, int end) { + WRoadNav nav; + float pathDistance; + float segmentDistance; + + nav.SetNavType(WRoadNav::kTypeDirection); + nav.SetDecisionFilter(true); + nav.SetPathType(WRoadNav::kPathChopper); + + mRaceParms->GetNumCheckpoints(); + { + UMath::Vector3 delta; + + VU0_v3sub(UMath::Vector4To3(positions[start]), UMath::Vector4To3(positions[end]), delta); + pathDistance = VU0_sqrt(VU0_v3lengthsquare(delta)); + } + + nav.InitAtPoint(UMath::Vector4To3(positions[start]), UMath::Vector4To3(directions[start]), true, 1.0f); + if (nav.IsValid()) { + segmentDistance = 0.0f; + + if (start == end) { + short segment = nav.GetSegmentInd(); + float segLenScale = static_cast(nav.GetSegment()->nLength) * 0.015259022f; + + do { + float lengthDelta = UMath::Max(segLenScale * (1.0f - nav.GetSegTime()), 0.01f); + + segmentDistance += lengthDelta; + nav.IncNavPosition(lengthDelta, UMath::Vector4To3(directions[end]), 0.0f); + } while (segment == nav.GetSegmentInd()); + } + + UTL::Std::set pathSegments; + char shortcutAllowed[32]; + bool noShortcuts = true; + WRoadNetwork *roadNetwork = &WRoadNetwork::Get(); + + bool foundPath; + + bMemSet(shortcutAllowed, 1, sizeof(shortcutAllowed)); + do { + nav.SetNavType(WRoadNav::kTypeDirection); + nav.FindPathNow(&UMath::Vector4To3(positions[end]), &UMath::Vector4To3(directions[end]), shortcutAllowed); + + foundPath = nav.GetNavType() == WRoadNav::kTypePath; + if (foundPath) { + roadNetwork->AddRaceSegments(&nav); + pathDistance = nav.GetPathDistanceRemaining(); + + unsigned char shortcut = nav.FirstShortcutInPath(); + + noShortcuts = shortcut == 0xFF; + if (!noShortcuts) { + shortcutAllowed[shortcut] = 0; + } + PathSegment pathSegment; + + pathSegment.Length = pathDistance; + int numSegments = nav.GetNumPathSegments(); + + for (int i = 0; i < numSegments; ++i) { + const WRoadSegment *roadSegment = roadNetwork->GetSegment(nav.GetPathSegment(i)); + + if ((roadSegment->fFlags & 1) == 0) { + pathSegment.Roads.insert(roadSegment->fRoadID); + } + } + pathSegments.insert(pathSegment); + } + } while (foundPath && !noShortcuts); + + int numPaths = 0; + + if (noShortcuts) { + numPaths = pathSegments.size(); + } + + pathDistance += segmentDistance; + + if (numPaths == 0) { + bRaceRouteError = true; + } + + if (numPaths > 1) { + PathSegment *sortedPaths[32]; + int count = 0; + + for (UTL::Std::set::iterator it = pathSegments.begin(); it != pathSegments.end(); ++it) { + sortedPaths[count++] = const_cast(&*it); + } + + for (int i = 1; i < count; ++i) { + PathSegment *longer_path = sortedPaths[i]; + PathSegment *shorter_path = sortedPaths[i - 1]; + UTL::Std::set unique_roads; + + std::set_difference( + shorter_path->Roads.begin(), + shorter_path->Roads.end(), + longer_path->Roads.begin(), + longer_path->Roads.end(), + std::insert_iterator >(unique_roads, unique_roads.begin())); + float unique_length = 0.0f; + float longer_by = longer_path->Length - shorter_path->Length; + + for (UTL::Std::set::iterator it = unique_roads.begin(); it != unique_roads.end(); ++it) { + unique_length += static_cast(roadNetwork->GetRoad(*it)->nLength) * 0.061036088f; + } + + if (unique_length > 0.0f) { + float road_scale = (longer_by + unique_length) / unique_length; + + for (UTL::Std::set::iterator it = unique_roads.begin(); it != unique_roads.end(); ++it) { + WRoad *road = const_cast(roadNetwork->GetRoad(*it)); + int scale = static_cast(road_scale * 65536.0f); + + road->nScale = static_cast(scale >> 8); + } + } + } + } + } else { + bRaceRouteError = true; + mRaceParms->GetNumCheckpoints(); + } + + return pathDistance; +} + +void GRaceStatus::DetermineRaceLength() { + nSpeedTraps = 0; + fSubsequentLapLength = 0.0f; + fRaceLength = 0.0f; + fFirstLapLength = 0.0f; + bMemSet(mSegmentLengths, 0, sizeof(mSegmentLengths)); + bRaceRouteError = false; + + WRoadNetwork &rn = WRoadNetwork::Get(); + GRaceParameters *race_parameters = GetRaceParameters(); + rn.ResolveShortcuts(); + + if (!race_parameters || !race_parameters->HasFinishLine()) { + return; + } + + { + int numCheckpoints; + int numPathPoints; + UMath::Vector4 positions[18]; + UMath::Vector4 directions[18]; + float totalDistance; + bool raceLoops; + int numSegmentLengths; + int j; + const bool forceCentreLane = true; + const float directionWeight = 1.0f; + + rn.SetRaceFilterValid(true); + numCheckpoints = race_parameters->GetNumCheckpoints(); + numPathPoints = numCheckpoints + 2; + race_parameters->GetStartPosition(UMath::Vector4To3(positions[0])); + positions[0].w = 0.0f; + race_parameters->GetStartDirection(UMath::Vector4To3(directions[0])); + directions[0].w = 0.0f; + race_parameters->GetFinishPosition(UMath::Vector4To3(positions[numPathPoints - 1])); + positions[numPathPoints - 1].w = 0.0f; + race_parameters->GetFinishDirection(UMath::Vector4To3(directions[numPathPoints - 1])); + directions[numPathPoints - 1].w = 0.0f; + + for (int i = 0; i < numCheckpoints; ++i) { + race_parameters->GetCheckpointPosition(i, UMath::Vector4To3(positions[i + 1])); + positions[i + 1].w = 0.0f; + race_parameters->GetCheckpointDirection(i, UMath::Vector4To3(directions[i + 1])); + directions[i + 1].w = 0.0f; + } + + totalDistance = 0.0f; + raceLoops = race_parameters->GetIsLoopingRace(); + numSegmentLengths = numPathPoints - 1; + if (raceLoops) { + numSegmentLengths = numPathPoints; + } + + for (j = 0; j < numSegmentLengths; ++j) { + mSegmentLengths[j] = DetermineRaceSegmentLength(positions, directions, j, (j % (numPathPoints - 1)) + 1); + totalDistance += mSegmentLengths[j]; + } + + if (!raceLoops) { + fRaceLength = totalDistance; + fFirstLapLength = totalDistance; + fSubsequentLapLength = totalDistance; + } else { + fSubsequentLapLength = totalDistance - mSegmentLengths[0]; + fFirstLapLength = totalDistance - mSegmentLengths[numPathPoints - 1]; + fRaceLength = fSubsequentLapLength * static_cast(race_parameters->GetNumLaps() - 1) + fFirstLapLength; + } + + WRoadNav nav; + + nav.SetDecisionFilter(true); + nav.SetNavType(WRoadNav::kTypeDirection); + nav.SetPathType(WRoadNav::kPathChopper); + nav.InitAtPoint(UMath::Vector4To3(positions[numPathPoints - 1]), UMath::Vector4To3(directions[numPathPoints - 1]), forceCentreLane, + directionWeight); + if (nav.IsValid()) { + for (int i = 0; i < 100; ++i) { + int segmentNumber; + WRoadSegment *segment; + + nav.IncNavPosition(1.0f, UMath::Vector3::kZero, 0.0f); + segmentNumber = nav.GetSegmentInd(); + segment = rn.GetSegmentNonConst(segmentNumber); + segment->SetInRace(true); + segment->SetRaceRouteForward(nav.GetNodeInd() == 1); + } + } + + GObjectIterator iter(0x100); + int numSpeedTraps = 0; + + while (iter.IsValid()) { + GTrigger *trigger = iter.GetInstance(); + + if (!trigger->OpenWorldSpeedTrap(0)) { + aSpeedTraps[numSpeedTraps] = trigger; + ++numSpeedTraps; + } + + iter.Advance(); + } + + nSpeedTraps = numSpeedTraps; + } +} + +template void GRaceCustom::SetAttribute(unsigned int key, const int &value, unsigned int index); +template void GRaceCustom::SetAttribute(unsigned int key, const bool &value, unsigned int index); diff --git a/src/Speed/Indep/Src/Gameplay/GRaceStatus.h b/src/Speed/Indep/Src/Gameplay/GRaceStatus.h index efbb40541..c933e3327 100644 --- a/src/Speed/Indep/Src/Gameplay/GRaceStatus.h +++ b/src/Speed/Indep/Src/Gameplay/GRaceStatus.h @@ -21,6 +21,8 @@ // total size: 0x1A8 struct GRacerInfo { public: + friend class GRaceStatus; + GCharacter *GetGameCharacter() const { return mGameCharacter; } @@ -29,7 +31,176 @@ struct GRacerInfo { return mPctRaceComplete; } + ISimable *GetSimable() const { + return ISimable::FindInstance(mhSimable); + } + + int GetRanking() const { + return mRanking; + } + + bool IsFinishedRacing() const { + return mFinishedRacing; + } + + bool GetCameraDetached() const { + return mCameraDetached; + } + + bool GetIsHuman() const; + const char *GetName() const { return mName; } + int GetIndex() const { return mIndex; } + int GetAiRanking() const { return mAiRanking; } + int GetSpeedTrapsCrossed() const { return mSpeedTrapsCrossed; } + float GetSpeedTrapSpeed(int index) const { return mSpeedTrapSpeed[index]; } + int GetSpeedTrapPosition(int index) const { return mSpeedTrapPosition[index]; } + float GetTollboothTime(int index) const { return mTimeRemainingToBooth[index]; } + bool GetIsKnockedOut() const { return mKnockedOut; } + bool GetIsTotalled() const { return mTotalled; } + bool GetIsEngineBlown() const { return mEngineBlown; } + bool GetIsBusted() const { return mBusted; } + bool GetChallengeComplete() const { return mChallengeComplete; } + float GetPctLapComplete() const { return mPctLapComplete; } + int GetLapsCompleted() const { return mLapsCompleted; } + int GetChecksHitThisLap() const { return mCheckpointsHitThisLap; } + int GetTollboothsCrossed() const { return mTollboothsCrossed; } + float GetDistToNextCheck() const { return mDistToNextCheckpoint; } + float GetDistDriven() const { return mDistanceDriven; } + float GetTopSpeed() const { return mTopSpeed; } + float GetFinishingSpeed() const { return mFinishingSpeed; } + float GetPoundsNOSUsed() const { return mPoundsNOSUsed; } + float GetRaceTime() const { return mRaceTimer.GetTime(); } + float GetLapTime() const { return mLapTimer.GetTime(); } + float GetCheckTime() const { return mCheckTimer.GetTime(); } + int GetPerfectShifts() const { return mNumPerfectShifts; } + int GetTrafficCarsHit() const { return mNumTrafficCarsHit; } + float GetSpeedBreakerTime() const { return mSpeedBreakerTime; } + float GetPointTotal() const { return mPointTotal; } + float GetZeroToSixtyTime() const { return mZeroToSixtyTime; } + float GetQuarterMileTime() const { return mQuarterMileTime; } +#ifndef EA_BUILD_A124 + bool GetDNF() const { return mDNF; } +#endif + + void SetName(const char *name); + void SetIndex(int n); + void SetRanking(int n); + void SetRaceTime(float f) { mRaceTimer.SetTime(f); } + void SetLapsCompleted(int n) { mLapsCompleted = n; } + void SetPctRaceComplete(float f) { mPctRaceComplete = f; } + void SetDistDriven(float f) { mDistanceDriven = f; } + void SetTopSpeed(float f) { mTopSpeed = f; } + void SetPoundsNOSUsed(float f) { mPoundsNOSUsed = f; } + void SetSimable(ISimable *isim); + + void Reset(); + IVehicle *CreateVehicle(unsigned int default_key); + void Update(float dT); + void UpdateSplits(); + void SaveExistingRaceStats(); + void RestoreExistingRaceStats(); + bool IsBehind(const GRacerInfo &rhs) const; + float CalcAverageSpeed() const; + float GetHudPctRaceComplete() const; + bool AreStatsReady() const; + void ChooseRandomName(); + bool ChooseBossName(); + bool ChooseRacerName(); + void FinalizeRaceStats(); + void KnockOut(); + void TotalVehicle(); + void Busted(); + void ForceStop(); + void BlowEngine(); + void AddToPointTotal(float points); + void StartRace(); + void StartLap(int lap); + void StartCheckpoint(int checkpoint); + void NotifySpeedTrapTriggered(float speed); + void FinishRace(); + void SaveStartPosition(); + void RestoreStartPosition(); + void ForceStartPosition(const UMath::Vector3 &pos, const UMath::Vector3 &dir); + void ChallengeComplete(); + void ClearAll(); + private: + inline void ClearRaceStats() { + mhSimable = nullptr; + mGameCharacter = nullptr; + mName = nullptr; + mIndex = -1; + mSavedHeatLevel = 0.0f; + mSavedSpeed = 0.0f; + mSavedPosition = UMath::Vector3::kZero; + mSavedDirection = UMath::Vector3::kZero; + mRanking = 0; + mAiRanking = 0; + mPctRaceComplete = 0.0f; + mKnockedOut = false; + mTotalled = false; + mEngineBlown = false; + mBusted = false; + mChallengeComplete = false; + mFinishedRacing = false; + mCameraDetached = false; + mPctLapComplete = 0.0f; + mLapsCompleted = 0; + mCheckpointsHitThisLap = 0; + mTollboothsCrossed = 0; + mSpeedTrapsCrossed = 0; + mDistToNextCheckpoint = 0.0f; + mDistanceDriven = 0.0f; + mTopSpeed = 0.0f; + mFinishingSpeed = 0.0f; + mPoundsNOSUsed = 0.0f; + mTimeCrossedLastCheck = 0.0f; + mTotalUpdateTime = 0.0f; + mNumPerfectShifts = 0; + mNumTrafficCarsHit = 0; + mPointTotal = 0.0f; + mZeroToSixtyTime = 0.0f; + mQuarterMileTime = 0.0f; + mSpeedBreakerTime = 0.0f; +#ifndef EA_BUILD_A124 + mDNF = false; +#endif + mRaceTimer.Stop(); + mRaceTimer.Reset(0.0f); + mLapTimer.Stop(); + mLapTimer.Reset(0.0f); + mCheckTimer.Stop(); + mCheckTimer.Reset(0.0f); + + { + int i; + + for (i = 0; i < 16; ++i) { + mTimeRemainingToBooth[i] = 0.0f; + } + } + + { + int i; + + for (i = 0; i < 16; ++i) { + mSpeedTrapSpeed[i] = 0.0f; + mSpeedTrapPosition[i] = -1; + } + } + +#ifndef EA_BUILD_A124 + { + int i; + + for (i = 0; i < 4; ++i) { + mSplitTimes[i] = 0.0f; + mSplitRankings[i] = 0; + } + } +#endif + } + HSIMABLE mhSimable; // offset 0x0, size 0x4 GCharacter *mGameCharacter; // offset 0x4, size 0x4 const char *mName; // offset 0x8, size 0x4 @@ -200,7 +371,7 @@ class GRaceParameters { const char *GetSpeedTrapCamera() const; - inline void EnsureLoaded() const {} + void EnsureLoaded() const; void BlockUntilLoaded(); @@ -224,13 +395,13 @@ class GRaceParameters { struct GVault *GetParentVault() const; - void GetBoundingBox(struct Vector2 &topLeft, struct Vector2 &botRight) const; + void GetBoundingBox(UMath::Vector2 &topLeft, UMath::Vector2 &botRight) const; unsigned int GetChallengeType() const; GRace::Type GetRaceType() const; - // enum Region GetRegion() const; + unsigned int GetRegion() const; void ExtractPosition(Attrib::Gen::gameplay &collection, UMath::Vector3 &pos) const; @@ -238,7 +409,7 @@ class GRaceParameters { unsigned int GetEventHash() const; - // bool GetIsAvailable(enum Context context) const; + bool GetIsAvailable(GRace::Context context) const; bool GetIsSunsetRace() const; @@ -248,9 +419,9 @@ class GRaceParameters { int GetTrafficDensity() const; - // enum Difficulty GetDifficulty() const; + GRace::Difficulty GetDifficulty() const; - // enum CopDensity GetCopDensity() const; + int GetCopDensity() const; bool GetCanBeReversed() const; @@ -294,6 +465,15 @@ class GRaceParameters { // total size: 0x46AC class GRaceStatus : public UTL::COM::Object, public IVehicleCache { public: + enum eAdaptiveGainReason { + kAdaptiveGainReason_0 = 0, + kAdaptiveGainReason_1 = 1, + kAdaptiveGainReason_2 = 2, + kAdaptiveGainReason_3 = 3, + kAdaptiveGainReason_4 = 4, + kAdaptiveGainReason_5 = 5, + }; + enum PlayMode { kPlayMode_Roaming = 0, kPlayMode_Racing = 1, @@ -326,6 +506,9 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { // Overrides: IVehicleCache void OnRemovedVehicleCache(IVehicle *ivehicle) override; + // Overrides: IVehicleCache + const char *GetCacheName() const override; + void SetRaceContext(GRace::Context context); GRacerInfo &GetRacerInfo(int index); @@ -336,6 +519,11 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { int GetRacerCount() const; + void SetActivelyRacing(bool racing); + void SetHasBeenWon(bool won); + void SetIsLoading(bool loading); + void SetTaskTime(float time); + void StartMasterTimer(); void StopMasterTimer(); @@ -390,7 +578,7 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { void SyncronizeAdaptiveBonus(); - // void UpdateAdaptiveDifficulty(enum eAdaptiveGainReason reason, struct ISimable *who); + void UpdateAdaptiveDifficulty(eAdaptiveGainReason reason, ISimable *who); bool ComputeCatchUpSkill(GRacerInfo *racer_info, PidError *pid, float *output, float *skill, bool off_world); @@ -430,6 +618,10 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { int GetRaceSpeedTrapPosition(int trapIndex, int racerIndex); + int GetNumRaceSpeedTraps() { + return nSpeedTraps; + } + float GetBestSpeedTrapSpeed(int racerIndex); float GetWorstSpeedTrapSpeed(int racerIndex); @@ -451,6 +643,7 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { float GetSegmentLength(int segment, int lap); GRaceStatus(); + ~GRaceStatus() override; GRaceParameters *GetRaceParameters() const { return mRaceParms; @@ -464,15 +657,39 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { return fObj != nullptr; } + bool GetIsScriptWaitingForLoading() const { + return mScriptWaitingForLoad; + } + GRace::Type GetRaceType() const { return mRaceParms ? mRaceParms->GetRaceType() : GRace::kRaceType_None; } + bool GetIsTimeLimited() const { + return mRaceParms != nullptr && mRaceParms->GetTimeLimit() > 0.0f; + } + + float GetRaceLength() { + return fRaceLength; + } + + float GetFirstLapLength() { + return fFirstLapLength; + } + + float GetSubsequentLapLength() { + return fSubsequentLapLength; + } + + float GetLapLength(int lap) { + return lap == 0 ? GetFirstLapLength() : GetSubsequentLapLength(); + } + static bool IsChallengeRace() { return Exists() && Get().GetRaceType() == GRace::kRaceType_Challenge; } - PlayMode GetPlayMode() { + PlayMode GetPlayMode() const { return mPlayMode; } @@ -492,6 +709,10 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { return GRaceStatus::Exists() && GRaceStatus::Get().GetRaceParameters() && GRaceStatus::Get().GetRaceParameters()->GetIsEpicPursuitRace(); } + void PlayerPursuitInCoolDown(bool c) { + mPlayerPursuitInCooldown = c; + } + float GetBinBaseHeat() const { return mRaceBin->GetBaseOpenWorldHeat(); } @@ -504,6 +725,8 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { return mRaceBin->GetScaleOpenWorldHeat(); } + void EnterSuddenDeath(); + private: static struct GRaceStatus *fObj; @@ -617,11 +840,7 @@ class GRaceCustom : public GRaceParameters { void SetForceHeatLevel(int level); - void SetAttribute(unsigned int key, const int &value, unsigned int index); - - void SetAttribute(unsigned int key, const float &value, unsigned int index); - - void SetAttribute(unsigned int key, const bool &value, unsigned int index); + template void SetAttribute(unsigned int key, const T &value, unsigned int index); void SetHeatLevel(int level) { mHeatLevel = level; diff --git a/src/Speed/Indep/Src/Gameplay/GReflected.h b/src/Speed/Indep/Src/Gameplay/GReflected.h index c1ab3ad19..510e8c012 100644 --- a/src/Speed/Indep/Src/Gameplay/GReflected.h +++ b/src/Speed/Indep/Src/Gameplay/GReflected.h @@ -5,8 +5,24 @@ #pragma once #endif +class GRuntimeInstance; + // total size: 0x4 struct GCollectionKey { + GCollectionKey(unsigned int key) : mCollectionKey(key) {} + + GCollectionKey(GRuntimeInstance *inst); + + unsigned int GetCollectionKey() const { + return mCollectionKey; + } + + operator unsigned int() { + return mCollectionKey; + } + + operator GRuntimeInstance *() const; + unsigned int mCollectionKey; // offset 0x0, size 0x4 }; diff --git a/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.cpp b/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.cpp index e69de29bb..0f7ff1bb7 100644 --- a/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.cpp +++ b/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.cpp @@ -0,0 +1,258 @@ +#include "GRuntimeInstance.h" + +#include "GActivity.h" +#include "GCharacter.h" +#include "GManager.h" +#include "GMarker.h" +#include "GTrigger.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" + +#include + +GRuntimeInstance *GRuntimeInstance::sRingListHead[6] = {0}; + +GRuntimeInstance::GRuntimeInstance(const Attrib::Key &key, GameplayObjType type) + : Attrib::Gen::gameplay(key, 0, nullptr) // +{ + mFlags = 0; + mNumConnected = 0; + mConnected = nullptr; + mPrev = nullptr; + mNext = nullptr; + AddToTypeList(type); + GManager::Get().RegisterInstance(this); + SetFlag(1); +} + +GRuntimeInstance::~GRuntimeInstance() { + GManager::Get().UnregisterInstance(this); + DisconnectInstances(); + RemoveFromTypeList(); + mFlags = mFlags & ~1; +} + +void GRuntimeInstance::SetConnectionBuffer(ConnectedInstance *destBuffer, unsigned int numEntries) { + mConnected = destBuffer; + mNumConnected = 0; +} + +void GRuntimeInstance::AllocateConnectionBuffer(unsigned int numEntries) { + ConnectedInstance *buffer = new ConnectedInstance[numEntries]; + mConnected = buffer; + mNumConnected = 0; + SetFlag(4); +} + +void GRuntimeInstance::ConnectToInstance(const Attrib::Key &key, int index, GRuntimeInstance *instance) { + unsigned int packedKey = MakePackedKey(key, index); + unsigned short connectedIndex = mNumConnected; + ConnectedInstance *connected = &mConnected[connectedIndex]; + + mNumConnected = connectedIndex + 1; + connected->mIndexedKey = packedKey; + connected->mInstance = instance; +} + +void GRuntimeInstance::LockConnections() { + std::sort(mConnected, mConnected + mNumConnected); + SetFlag(2); +} + +GRuntimeInstance *GRuntimeInstance::GetConnectedInstance(const Attrib::Key &key, int index) const { + unsigned int targetKey = MakePackedKey(key, index); + int lower = 0; + int upper = mNumConnected - 1; + + while (lower <= upper) { + int middle = (lower + upper) >> 1; + ConnectedInstance &connected = mConnected[middle]; + + if (targetKey > connected.mIndexedKey) { + lower = middle + 1; + } else if (targetKey < connected.mIndexedKey) { + upper = middle - 1; + } else { + return connected.mInstance; + } + } + + return nullptr; +} + +void GRuntimeInstance::ResetConnections() { + mNumConnected = 0; + mFlags = mFlags & ~2; +} + +void GRuntimeInstance::DisconnectInstances() { + if ((mFlags & 4) != 0) { + if (mConnected) { + delete[] mConnected; + } + mFlags &= ~4; + } + mNumConnected = 0; + mConnected = nullptr; + mFlags &= ~2; +} + +unsigned int GRuntimeInstance::MakePackedKey(unsigned int key, int index) const { + unsigned int key24 = GManager::Get().Get24BitAttributeKey(key); + unsigned char index8 = static_cast(index); + return (key24 << 8) | index8; +} + +void GRuntimeInstance::AddToTypeList(GameplayObjType type) { + GRuntimeInstance *&head = sRingListHead[type]; + if (!head) { + mNext = this; + mPrev = this; + head = this; + } else { + mNext = head; + mPrev = head->mPrev; + mPrev->mNext = this; + mNext->mPrev = this; + } +} + +void GRuntimeInstance::RemoveFromTypeList() { + mNext->mPrev = mPrev; + mPrev->mNext = mNext; + + for (unsigned int onType = 0; onType < kGameplayObjType_Count; onType++) { + if (sRingListHead[onType] == this) { + GRuntimeInstance *newHead = nullptr; + + if (mNext != this) { + newHead = mNext; + } + + sRingListHead[onType] = newHead; + mPrev = nullptr; + mNext = nullptr; + return; + } + } + + mPrev = nullptr; + mNext = nullptr; +} + +unsigned int GRuntimeInstance::GetConnectionCount() const { + return mNumConnected; +} + +GRuntimeInstance *GRuntimeInstance::GetConnectionAt(unsigned int index) const { + return mConnected[index].mInstance; +} + +bool GRuntimeInstance::IsDerivedFromTemplate(unsigned int templateKey) const { + unsigned int parentKey = GetParent(); + while (parentKey) { + if (parentKey == templateKey) { + return true; + } + { + Attrib::Gen::gameplay parentObj(parentKey, 0, nullptr); + parentKey = parentObj.GetParent(); + } + } + return false; +} + +bool GRuntimeInstance::GetPosition(UMath::Vector3 &pos) { + if (GetType() == kGameplayObjType_Marker) { + GMarker *marker = static_cast(this); + pos = marker->GetPosition(); + return true; + } else if (GetType() == kGameplayObjType_Trigger) { + GTrigger *trigger = static_cast(this); + trigger->GetPosition(pos); + return true; + } else { + return false; + } +} + +bool GRuntimeInstance::GetDirection(UMath::Vector3 &dir) { + if (GetType() == kGameplayObjType_Marker) { + GMarker *marker = static_cast(this); + dir = marker->GetDirection(); + return true; + } else if (GetType() == kGameplayObjType_Trigger) { + GTrigger *trigger = static_cast(this); + dir = trigger->GetDirection(); + return true; + } else { + return false; + } +} + +template +struct GRuntimeInstanceFindObjectTraits; + +template <> +struct GRuntimeInstanceFindObjectTraits { + enum { kType = kGameplayObjType_Activity }; +}; + +template <> +struct GRuntimeInstanceFindObjectTraits { + enum { kType = kGameplayObjType_Character }; +}; + +template <> +struct GRuntimeInstanceFindObjectTraits { + enum { kType = kGameplayObjType_Marker }; +}; + +template <> +struct GRuntimeInstanceFindObjectTraits { + enum { kType = kGameplayObjType_Trigger }; +}; + +template +T *GRuntimeInstance::FindObject(unsigned int key) { + T *instance = static_cast(sRingListHead[GRuntimeInstanceFindObjectTraits::kType]); + + while (instance) { + if (instance->GetCollection() == key) { + return instance; + } + + instance = static_cast(instance->GetNextRuntimeInstance()); + if (instance == sRingListHead[GRuntimeInstanceFindObjectTraits::kType]) { + return nullptr; + } + } + + return nullptr; +} + +template GActivity *GRuntimeInstance::FindObject(unsigned int key); +template GCharacter *GRuntimeInstance::FindObject(unsigned int key); +template GMarker *GRuntimeInstance::FindObject(unsigned int key); +template GTrigger *GRuntimeInstance::FindObject(unsigned int key); + +template <> const GCollectionKey &Attrib::Attribute::Get(unsigned int index) const { + const GCollectionKey *resultptr = reinterpret_cast(GetElementPointer(index)); + + if (!resultptr) { + resultptr = reinterpret_cast(Attrib::DefaultDataArea(sizeof(GCollectionKey))); + } + + return *resultptr; +} + +GCollectionKey::GCollectionKey(GRuntimeInstance *inst) { + if (inst) { + mCollectionKey = inst->GetCollection(); + } else { + mCollectionKey = 0; + } +} + +GCollectionKey::operator GRuntimeInstance *() const { + return GManager::Get().FindInstance(mCollectionKey); +} diff --git a/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.h b/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.h index cae688d12..497fa1062 100644 --- a/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.h +++ b/src/Speed/Indep/Src/Gameplay/GRuntimeInstance.h @@ -5,19 +5,94 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h" +class GMarker; + // total size: 0x28 class GRuntimeInstance : public Attrib::Gen::gameplay { public: + // total size: 0x8 + struct ConnectedInstance { + bool operator<(const ConnectedInstance &other) const { + return mIndexedKey < other.mIndexedKey; + } + + unsigned int mIndexedKey; // offset 0x0, size 0x4 + GRuntimeInstance *mInstance; // offset 0x4, size 0x4 + }; + + typedef unsigned int PackedIndexedKey; + GRuntimeInstance(const Attrib::Key &key, GameplayObjType type); + virtual ~GRuntimeInstance(); + + static void operator delete(void *mem, unsigned int size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + + void SetConnectionBuffer(ConnectedInstance *destBuffer, unsigned int numEntries); + + void AllocateConnectionBuffer(unsigned int numEntries); + + void ConnectToInstance(const Attrib::Key &key, int index, GRuntimeInstance *instance); + + void LockConnections(); + + GRuntimeInstance *GetConnectedInstance(const Attrib::Key &key, int index) const; + + void ResetConnections(); + + void DisconnectInstances(); + + unsigned int MakePackedKey(unsigned int key, int index) const; + + void AddToTypeList(GameplayObjType type); + + void RemoveFromTypeList(); + + unsigned int GetConnectionCount() const; + + GRuntimeInstance *GetConnectionAt(unsigned int index) const; + + bool IsDerivedFromTemplate(unsigned int templateKey) const; + + bool GetPosition(UMath::Vector3 &pos); + + bool GetDirection(UMath::Vector3 &dir); + + template + static T *FindObject(unsigned int key); + + virtual GameplayObjType GetType() const { + return kGameplayObjType_Invalid; + } + + bool GetFlag(unsigned int flag) const { + return (mFlags & flag) != 0; + } + + void SetFlag(unsigned int flag) { + mFlags = mFlags | static_cast(flag); + } + + GRuntimeInstance *GetNextRuntimeInstance() const { + return mNext; + } + + static GRuntimeInstance *sRingListHead[6]; + private: - unsigned short mFlags; // offset 0x14, size 0x2 - unsigned short mNumConnected; // offset 0x16, size 0x2 - struct ConnectedInstance *mConnected; // offset 0x18, size 0x4 - GRuntimeInstance *mPrev; // offset 0x1C, size 0x4 - GRuntimeInstance *mNext; // offset 0x20, size 0x4 + unsigned short mFlags; // offset 0x14, size 0x2 + unsigned short mNumConnected; // offset 0x16, size 0x2 + ConnectedInstance *mConnected; // offset 0x18, size 0x4 + GRuntimeInstance *mPrev; // offset 0x1C, size 0x4 + GRuntimeInstance *mNext; // offset 0x20, size 0x4 }; #endif diff --git a/src/Speed/Indep/Src/Gameplay/GSpeedTrap.cpp b/src/Speed/Indep/Src/Gameplay/GSpeedTrap.cpp index e69de29bb..8bc33d724 100644 --- a/src/Speed/Indep/Src/Gameplay/GSpeedTrap.cpp +++ b/src/Speed/Indep/Src/Gameplay/GSpeedTrap.cpp @@ -0,0 +1,75 @@ +#include "Speed/Indep/Src/Gameplay/GSpeedTrap.h" + +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GTrigger.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h" + +GSpeedTrap::GSpeedTrap() + : mFlags(0), // + mBinNumber(0), // + mSpeedTrapKey(0), // + mCameraMarkerKey(0), // + mRequiredValue(0.0f), // + mRecordedValue(0.0f) {} + +float GSpeedTrap::GetBounty() const { + Attrib::Gen::gameplay gameplayObj(mSpeedTrapKey, 0, nullptr); + + if (!gameplayObj.IsValid()) { + return 0.0f; + } + + return static_cast(gameplayObj.Bounty(0)); +} + +GTrigger *GSpeedTrap::GetTrapTrigger() const { + return static_cast(GManager::Get().FindInstance(mSpeedTrapKey)); +} + +unsigned int GSpeedTrap::GetJumpMarkerKey() const { + Attrib::Gen::gameplay gameplayObj(mSpeedTrapKey, 0, nullptr); + + return gameplayObj.SpawnPoint(0).GetCollectionKey(); +} + +void GSpeedTrap::DebugForceComplete() { + SetFlag(kFlag_Unlocked | kFlag_Active | kFlag_Completed); + mRecordedValue = mRequiredValue; +} + +void GSpeedTrap::Init(unsigned int trapKey) { + mSpeedTrapKey = trapKey; + Reset(); +} + +void GSpeedTrap::Reset() { + Attrib::Gen::gameplay gameplayObj(mSpeedTrapKey, 0, nullptr); + + mFlags = 0; + mCameraMarkerKey = gameplayObj.CameraModelMarker(0).GetCollectionKey(); + mBinNumber = static_cast(gameplayObj.BinIndex(0)); + mRequiredValue = gameplayObj.ThreshholdSpeed(0) * 0.277779996f; + mRecordedValue = 0.0f; +} + +void GSpeedTrap::Unlock() { + mFlags = static_cast(mFlags | kFlag_Unlocked); +} + +void GSpeedTrap::Activate() { + mFlags = static_cast(mFlags | kFlag_Active); +} + +void GSpeedTrap::NotifyTriggered(float value) { + if (IsFlagSet(kFlag_Unlocked) && IsFlagSet(kFlag_Active)) { + mFlags = static_cast((mFlags & ~kFlag_Active) | kFlag_Completed); + mRecordedValue = value; + + if (GRaceDatabase::Get().GetBinNumber(mBinNumber)) { + GRaceDatabase::Get().GetBinNumber(mBinNumber)->RefreshProgress(); + } + + GManager::Get().RefreshSpeedTrapIcons(); + } +} diff --git a/src/Speed/Indep/Src/Gameplay/GSpeedTrap.h b/src/Speed/Indep/Src/Gameplay/GSpeedTrap.h index 77dc76771..18cba847e 100644 --- a/src/Speed/Indep/Src/Gameplay/GSpeedTrap.h +++ b/src/Speed/Indep/Src/Gameplay/GSpeedTrap.h @@ -7,6 +7,85 @@ // total size: 0x14 class GSpeedTrap { + public: + enum Flags { + kFlag_Unlocked = 1, + kFlag_Active = 2, + kFlag_Completed = 4, + kFlag_KnockedOver = 8, + }; + + bool GetIsLocked() const { + return IsFlagClear(kFlag_Unlocked); + } + + bool GetIsUnlocked() const { + return IsFlagSet(kFlag_Unlocked); + } + + bool GetIsCompleted() const { + return IsFlagSet(kFlag_Completed); + } + + bool GetIsKnockedOver() const { + return IsFlagSet(kFlag_KnockedOver); + } + + bool GetIsActive() const { + return IsFlagSet(kFlag_Active); + } + + unsigned int GetSpeedTrapKey() const { + return mSpeedTrapKey; + } + + unsigned int GetCameraMarkerKey() const { + return mCameraMarkerKey; + } + + unsigned int GetBinNumber() const { + return mBinNumber; + } + + float GetTriggerSpeed() const { + return mRequiredValue; + } + + float GetRecordedPassSpeed() const { + return mRecordedValue; + } + + bool operator<(const GSpeedTrap &rhs) const { + return mSpeedTrapKey < rhs.mSpeedTrapKey; + } + + void SetFlag(unsigned int mask) { + mFlags = static_cast(mFlags | mask); + } + + void ClearFlag(unsigned int mask) { + mFlags = static_cast(mFlags & ~mask); + } + + bool IsFlagSet(unsigned int mask) const { + return (mFlags & mask) != 0; + } + + bool IsFlagClear(unsigned int mask) const { + return (mFlags & mask) == 0; + } + + GSpeedTrap(); + float GetBounty() const; + class GTrigger *GetTrapTrigger() const; + unsigned int GetJumpMarkerKey() const; + void DebugForceComplete(); + void Init(unsigned int trapKey); + void Reset(); + void Unlock(); + void Activate(); + void NotifyTriggered(float value); + private: unsigned short mFlags; // offset 0x0, size 0x2 unsigned short mBinNumber; // offset 0x2, size 0x2 diff --git a/src/Speed/Indep/Src/Gameplay/GState.cpp b/src/Speed/Indep/Src/Gameplay/GState.cpp index e69de29bb..344ff68b9 100644 --- a/src/Speed/Indep/Src/Gameplay/GState.cpp +++ b/src/Speed/Indep/Src/Gameplay/GState.cpp @@ -0,0 +1,20 @@ +#include "Speed/Indep/Src/Gameplay/GState.h" + +#include "Speed/Indep/Src/Generated/Messages/MStateEnter.h" +#include "Speed/Indep/Src/Generated/Messages/MStateExit.h" + +UCrc32 MStateEnter::_GetKind() { + static UCrc32 k("MStateEnter"); + + return k; +} + +UCrc32 MStateExit::_GetKind() { + static UCrc32 k("MStateExit"); + + return k; +} + +GState::GState(const Attrib::Key &stateKey) : GRuntimeInstance(stateKey, kGameplayObjType_State) {} + +GState::~GState() {} diff --git a/src/Speed/Indep/Src/Gameplay/GState.h b/src/Speed/Indep/Src/Gameplay/GState.h index 722c11708..9f6b4de7e 100644 --- a/src/Speed/Indep/Src/Gameplay/GState.h +++ b/src/Speed/Indep/Src/Gameplay/GState.h @@ -11,6 +11,14 @@ class GState : public GRuntimeInstance { public: GState(const Attrib::Key &stateKey); + + ~GState() override; + + GameplayObjType GetType() const override { + return kGameplayObjType_State; + } + + bool IsTerminalState() const; }; #endif diff --git a/src/Speed/Indep/Src/Gameplay/GTimer.cpp b/src/Speed/Indep/Src/Gameplay/GTimer.cpp index e69de29bb..4951904cc 100644 --- a/src/Speed/Indep/Src/Gameplay/GTimer.cpp +++ b/src/Speed/Indep/Src/Gameplay/GTimer.cpp @@ -0,0 +1,98 @@ +#include "Speed/Indep/Src/Gameplay/GTimer.h" + +#include "Speed/Indep/Src/Generated/Messages/MNotifyTimer.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +GTimer::GTimer() { + Reset(0.0f); +} + +GTimer::~GTimer() {} + +void GTimer::Start() { + mTotalTime = GetTime(); + mStartTime = Sim::GetTime(); + mRunning = true; +} + +void GTimer::Stop() { + mTotalTime = GetTime(); + mRunning = false; +} + +void GTimer::Reset(float value) { + mTotalTime = value; + mStartTime = Sim::GetTime(); +} + +float GTimer::GetTime() const { + if (!mRunning) { + return mTotalTime; + } + + return mTotalTime + (Sim::GetTime() - mStartTime); +} + +void GTimer::SetTime(float time) { + mTotalTime = time; + mRunning = false; +} + +GEventTimer::GEventTimer() { + Reset(); +} + +GEventTimer::~GEventTimer() {} + +void GEventTimer::Reset() { + mInterval = 1.0f; + mRunning = false; + mElapsed = 0.0f; + mNameHash = 0; +} + +void GEventTimer::Start() { + mRunning = true; + mElapsed = 0.0f; +} + +void GEventTimer::Stop() { + mRunning = false; + mElapsed = 0.0f; +} + +void GEventTimer::SetInterval(float value) { + mInterval = value; + mElapsed = 0.0f; +} + +void GEventTimer::SetName(const char *name) { + bSafeStrCpy(mName, name, sizeof(mName) - 1); + mNameHash = bStringHash(mName); +} + +void GEventTimer::Update(float dT) { + if (mRunning != 0) { + mElapsed += dT; + if (mElapsed >= mInterval) { + MNotifyTimer(mName).Post(UCrc32(0x20D60DBF)); + mElapsed -= mInterval; + } + } +} + +void GEventTimer::Serialize(SavedTimerInfo *saveInfo) { + saveInfo->mElapsed = mElapsed; + saveInfo->mInterval = mInterval; + saveInfo->mRunning = mRunning; + bSafeStrCpy(saveInfo->mName, mName, sizeof(saveInfo->mName) - 1); +} + +void GEventTimer::Deserialize(SavedTimerInfo *saveInfo) { + mElapsed = saveInfo->mElapsed; + mInterval = saveInfo->mInterval; + mRunning = saveInfo->mRunning; + bSafeStrCpy(mName, saveInfo->mName, sizeof(mName) - 1); + mNameHash = bStringHash(mName); +} diff --git a/src/Speed/Indep/Src/Gameplay/GTimer.h b/src/Speed/Indep/Src/Gameplay/GTimer.h index 90169c489..45ddcc70c 100644 --- a/src/Speed/Indep/Src/Gameplay/GTimer.h +++ b/src/Speed/Indep/Src/Gameplay/GTimer.h @@ -8,24 +8,50 @@ // total size: 0xC class GTimer { public: + GTimer(); + ~GTimer(); + + bool IsRunning() { return mRunning; } + void Start(); + void Stop(); + void Reset(float value); + float GetTime() const; + void SetTime(float time); + private: float mStartTime; // offset 0x0, size 0x4 float mTotalTime; // offset 0x4, size 0x4 bool mRunning; // offset 0x8, size 0x1 }; +// total size: 0x20 +struct SavedTimerInfo { + float mInterval; // offset 0x0, size 0x4 + bool mRunning; // offset 0x4, size 0x1 + float mElapsed; // offset 0x8, size 0x4 + char mName[20]; // offset 0xC, size 0x14 +}; + // total size: 0x24 class GEventTimer { public: - // bool IsRunning() const {} - - // float GetInterval() const {} - - // unsigned int GetNameHash() const {} + bool IsRunning() const { return mRunning; } + float GetInterval() const { return mInterval; } + unsigned int GetNameHash() const { return mNameHash; } + const char *GetName() const { return mName; } + float GetElapsed() const { return mElapsed; } - // const char *GetName() const {} + GEventTimer(); + ~GEventTimer(); - // float GetElapsed() const {} + void Reset(); + void Start(); + void Stop(); + void SetName(const char *name); + void SetInterval(float value); + void Update(float dT); + void Serialize(struct SavedTimerInfo *saveInfo); + void Deserialize(struct SavedTimerInfo *saveInfo); private: float mInterval; // offset 0x0, size 0x4 diff --git a/src/Speed/Indep/Src/Gameplay/GTrigger.cpp b/src/Speed/Indep/Src/Gameplay/GTrigger.cpp index e69de29bb..6c52a6307 100644 --- a/src/Speed/Indep/Src/Gameplay/GTrigger.cpp +++ b/src/Speed/Indep/Src/Gameplay/GTrigger.cpp @@ -0,0 +1,465 @@ +#include "Speed/Indep/Src/Gameplay/GTrigger.h" + +#include "Speed/Indep/Src/Ecstasy/EmitterSystem.h" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/eMath.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Generated/Messages/MTriggerEnter.h" +#include "Speed/Indep/Src/Generated/Messages/MTriggerExit.h" +#include "Speed/Indep/Src/Generated/Messages/MTriggerInside.h" +#include "Speed/Indep/Src/Gameplay/GIcon.h" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/World/WCollisionAssets.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" + +#include + +GTrigger::GTrigger(const Attrib::Key &triggerKey) + : GRuntimeInstance(triggerKey, kGameplayObjType_Trigger), // + mWorldTrigger() { + UMath::Matrix4 directionMat; + UMath::Matrix4 mat; + UMath::Vector3 initialVec; + const float *rotation; + const UMath::Vector3 *position; + const UMath::Vector3 *dimensions; + UMath::Vector3 posSwizzled; + UMath::Vector3 dimSwizzled; + float triggerRadius; + const int *oneShot; + + mTriggerEnabled = 0; + mIcon = nullptr; + mEnabled = false; + mActivationReferences = 0; + bMemSet(mParticleEffect, 0, 8); + + UMath::Copy(UMath::Matrix4::kIdentity, directionMat); + initialVec.x = 0.0f; + initialVec.y = 0.0f; + initialVec.z = 1.0f; + + rotation = reinterpret_cast(GetAttributePointer(0x5A6A57C6, 0)); + if (!rotation) { + rotation = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + MATRIX4_multyrot(&directionMat, -*rotation * 0.0027777778f, &directionMat); + VU0_MATRIX3x4_vect3mult(initialVec, directionMat, initialVec); + mDirection = initialVec; + mSimObjInside.reserve(8); + position = reinterpret_cast(GetAttributePointer(0x9F743A0E, 0)); + if (!position) { + position = reinterpret_cast(Attrib::DefaultDataArea(sizeof(UMath::Vector3))); + } + + dimensions = reinterpret_cast(GetAttributePointer(0x6D9E21AD, 0)); + posSwizzled.x = -position->y; + posSwizzled.y = position->z; + posSwizzled.z = position->x; + dimSwizzled.x = 0.0f; + dimSwizzled.y = 0.0f; + dimSwizzled.z = 0.0f; + bool hasDimensions = false; + triggerRadius = 0.0f; + + if (dimensions) { + dimSwizzled.x = dimensions->y; + dimSwizzled.y = dimensions->z; + dimSwizzled.z = dimensions->x; + hasDimensions = true; + } + + UMath::Copy(UMath::Matrix4::kIdentity, mat); + + if (Radius(triggerRadius)) { + dimSwizzled.x = triggerRadius + triggerRadius; + dimSwizzled.y = dimSwizzled.x; + dimSwizzled.z = dimSwizzled.x; + mWorldTrigger.fShape = 3; + } else if (hasDimensions) { + mWorldTrigger.fShape = 1; + triggerRadius = UMath::Sqrt(dimSwizzled.x * dimSwizzled.x * 0.25f + dimSwizzled.z * dimSwizzled.z * 0.25f); + } else { + const float *width = reinterpret_cast(GetAttributePointer(0x5816C1FC, 0)); + + if (!width) { + width = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + dimSwizzled.x = *width; + dimSwizzled.y = 50.0f; + dimSwizzled.z = 1.0f; + mWorldTrigger.fShape = 1; + triggerRadius = UMath::Sqrt(dimSwizzled.x * dimSwizzled.x * 0.25f + 0.25f); + } + + rotation = reinterpret_cast(GetAttributePointer(0x5A6A57C6, 0)); + if (!rotation) { + rotation = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + + MATRIX4_multyrot(&mat, -*rotation * 0.0027777778f, &mat); + mWorldTrigger.fType = 1; + mWorldTrigger.fEvents = &mEventList; + mWorldTrigger.fIterStamp = 0; + mWorldTrigger.fFingerprint = 0; + mWorldTrigger.fMatRow0Width.x = mat[0][0]; + mWorldTrigger.fMatRow0Width.y = mat[0][1]; + mWorldTrigger.fMatRow0Width.z = mat[0][2]; + mWorldTrigger.fMatRow0Width.w = dimSwizzled.x; + mWorldTrigger.fHeight = dimSwizzled.y + dimSwizzled.y; + mWorldTrigger.fMatRow2Length.x = mat[2][0]; + mWorldTrigger.fMatRow2Length.y = mat[2][1]; + mWorldTrigger.fMatRow2Length.z = mat[2][2]; + mWorldTrigger.fMatRow2Length.w = dimSwizzled.z; + mWorldTrigger.fPosRadius.x = posSwizzled.x; + mWorldTrigger.fPosRadius.y = posSwizzled.y; + mWorldTrigger.fPosRadius.z = posSwizzled.z; + mWorldTrigger.fPosRadius.w = triggerRadius; + mWorldTrigger.fFlags = 0x4810D; + + oneShot = reinterpret_cast(GetAttributePointer(0xCE4261AC, 0)); + if (!oneShot) { + oneShot = reinterpret_cast(Attrib::DefaultDataArea(sizeof(int))); + } + + if (*oneShot) { + mWorldTrigger.fFlags |= 2; + } + + mEventList.fNumEvents = 1; + mEventList.fPad[0] = 0; + mEventList.fPad[1] = 0; + mEventList.fPad[2] = 0; + EventStaticData *pTriggerData = &mEventStaticData; + pTriggerData->fEventID = 0xC34649C0u; + pTriggerData->fEventSize = 8; + pTriggerData->fDataOffset = 0x10; + pTriggerData->fPad = 0; + bMemSet(mTriggerEventData, 0, sizeof(mTriggerEventData)); + reinterpret_cast(mTriggerEventData)[1] = GetCollection(); + + bool showIconBasedOnBin = true; + if (IsDerivedFromTemplate(0xF05931AB)) { + SetFlag(0x200); + const GCollectionKey &targetActivityKey = TargetActivity(); + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromKey(targetActivityKey.GetCollectionKey()); + if (parms) { + GIcon::Type iconType = GIcon::kType_Invalid; + + if (parms->GetIsBossRace()) { + iconType = GIcon::kType_RaceRival; + } else { + switch (parms->GetRaceType()) { + case GRace::kRaceType_P2P: + iconType = GIcon::kType_RaceSprint; + break; + case GRace::kRaceType_Circuit: + iconType = GIcon::kType_RaceCircuit; + break; + case GRace::kRaceType_Drag: + iconType = GIcon::kType_RaceDrag; + break; + case GRace::kRaceType_Knockout: + iconType = GIcon::kType_RaceKnockout; + break; + case GRace::kRaceType_Tollbooth: + iconType = GIcon::kType_RaceTollbooth; + break; + case GRace::kRaceType_SpeedTrap: + iconType = GIcon::kType_RaceSpeedtrap; + break; + default: + break; + } + } + + if (iconType != GIcon::kType_Invalid) { + mIcon = GManager::Get().AllocIcon(iconType, posSwizzled, 0.0f, false); + } + showIconBasedOnBin = false; + } + } else if (IsDerivedFromTemplate(0x73049919)) { + SetFlag(0x2000); + mIcon = GManager::Get().AllocIcon(GIcon::kType_GateCarLot, posSwizzled, 0.0f, false); + } else if (IsDerivedFromTemplate(0x4698966B)) { + SetFlag(0x4000); + mIcon = GManager::Get().AllocIcon(GIcon::kType_GateCustomShop, posSwizzled, 0.0f, false); + } else if (IsDerivedFromTemplate(0x326427BB)) { + SetFlag(0x8000); + mIcon = GManager::Get().AllocIcon(GIcon::kType_GateSafehouse, posSwizzled, 0.0f, false); + } else if (IsDerivedFromTemplate(0xB05871D3)) { + SetFlag(0x100); + if (OpenWorldSpeedTrap()) { + mIcon = GManager::Get().AllocIcon(GIcon::kType_SpeedTrap, posSwizzled, 0.0f, false); + } else { + showIconBasedOnBin = false; + mIcon = GManager::Get().AllocIcon(GIcon::kType_SpeedTrapInRace, posSwizzled, 0.0f, false); + } + } else if (IsDerivedFromTemplate(0x45E28759)) { + SetFlag(0x400); + } else if (IsDerivedFromTemplate(0x76720F56)) { + SetFlag(0x800); + } else if (IsDerivedFromTemplate(0xA3E939E9)) { + SetFlag(0x1000); + } else if (IsDerivedFromTemplate(0x98217DBF)) { + mIcon = GManager::Get().AllocIcon(GIcon::kType_AreaUnlock, posSwizzled, 0.0f, false); + } + + if (showIconBasedOnBin && mIcon) { + int binIndex = BinIndex(); + + if (binIndex > 0 && FEDatabase && FEDatabase->GetCareerSettings()->GetCurrentBin() <= BinIndex()) { + mIcon->Show(); + mIcon->ShowOnMap(); + } + } +} + +GTrigger::~GTrigger() { + Disable(); + + for (unsigned int onEffect = 0; onEffect < 2; onEffect++) { + if (mParticleEffect[onEffect]) { + mParticleEffect[onEffect]->UnSubscribe(); + if (mParticleEffect[onEffect]) { + delete mParticleEffect[onEffect]; + } + } + } + + if (mIcon) { + GManager::Get().FreeIcon(mIcon); + mIcon = nullptr; + } +} + +GActivity *GTrigger::GetTargetActivity() { + unsigned int targetActivityKey = 0x277566F3; + return reinterpret_cast(GetConnectedInstance(targetActivityKey, 0)); +} + +void GTrigger::AddActivationReference() { + mActivationReferences++; + if (!mEnabled) { + Enable(true); + } +} + +void GTrigger::RemoveActivationReference() { + if (mActivationReferences > 0) { + mActivationReferences--; + } + if (mActivationReferences <= 0) { + Enable(false); + } +} + +EmitterGroup *GTrigger::CreateParticleEffect(const char *effectName, UMath::Vector3 &pos) { + EmitterGroup *effect = gEmitterSystem.CreateEmitterGroup(Attrib::StringKey(effectName), 0x8040000); + + if (effect) { + bMatrix4 mat; + bVector3 posSwizzled; + + eSwizzleWorldVector(reinterpret_cast(pos), posSwizzled); + eCreateTranslationMatrix(&mat, posSwizzled); + effect->SetLocalWorld(&mat); + effect->SetAutoUpdate(true); + effect->SubscribeToDeletion(this, NotifyEmitterGroupDelete); + effect->Disable(); + gEmitterSystem.AddEmitterGroup(effect); + } + + return effect; +} + +void GTrigger::CreateAllParticleEffects() { + const char *effectName = ParticleEffect(0); + + if (effectName && *effectName) { + UMath::Vector3 pos; + float flareSpacing; + + GetPosition(pos); + flareSpacing = FlareSpacing(0); + if (flareSpacing > 0.0f) { + const UMath::Vector3 upVec = {0.0f, 1.0f, 0.0f}; + UMath::Vector3 lateralVec; + UMath::Vector3 posLeft; + UMath::Vector3 posRight; + + bCross(reinterpret_cast(&lateralVec), reinterpret_cast(&upVec), reinterpret_cast(&mDirection)); + bScale(reinterpret_cast(&lateralVec), reinterpret_cast(&lateralVec), flareSpacing); + bScaleAdd(reinterpret_cast(&posLeft), reinterpret_cast(&pos), reinterpret_cast(&lateralVec), -1.0f); + bScaleAdd(reinterpret_cast(&posRight), reinterpret_cast(&pos), reinterpret_cast(&lateralVec), 1.0f); + mParticleEffect[0] = CreateParticleEffect(effectName, posLeft); + mParticleEffect[1] = CreateParticleEffect(effectName, posRight); + } else { + mParticleEffect[0] = CreateParticleEffect(effectName, pos); + } + } +} + +void GTrigger::ClearParticleEffects() { + for (unsigned int onEffect = 0; onEffect < 2; onEffect++) { + if (mParticleEffect[onEffect]) { + mParticleEffect[onEffect]->UnSubscribe(); + if (mParticleEffect[onEffect]) { + delete mParticleEffect[onEffect]; + } + } + } + + bMemSet(mParticleEffect, 0, sizeof(mParticleEffect)); +} + +void GTrigger::EnableParticleEffects(bool enabled) { + for (unsigned int onEffect = 0; onEffect < 2; onEffect++) { + if (mParticleEffect[onEffect]) { + if (enabled) { + mParticleEffect[onEffect]->Enable(); + } else { + mParticleEffect[onEffect]->Disable(); + } + } + } +} + +void GTrigger::RefreshParticleEffects() { + ClearParticleEffects(); + CreateAllParticleEffects(); + EnableParticleEffects(mEnabled); +} + +void GTrigger::NotifyEmitterGroupDelete(void *obj, EmitterGroup *group) { + GTrigger *trigger = reinterpret_cast(obj); + unsigned int i = 0; + + do { + if (trigger->mParticleEffect[i] == group) { + trigger->mParticleEffect[i] = nullptr; + } + i++; + } while (i <= 1); +} + +void GTrigger::Enable(bool setEnabled) { + if (!mTriggerEnabled) { + if (setEnabled) { + WCollisionAssets::Get().AddTrigger(&mWorldTrigger); + mTriggerEnabled = 1; + } + } else if (!setEnabled) { + WCollisionAssets::Get().RemoveTrigger(&mWorldTrigger); + mTriggerEnabled = 0; + } + + { + WTrigger *trigger = &mWorldTrigger; + unsigned char *triggerBytes = reinterpret_cast(trigger); + unsigned int word = *reinterpret_cast(triggerBytes + 0x10); + unsigned int flags; + + if (setEnabled) { + flags = static_cast(triggerBytes[0x13]) | + (static_cast(triggerBytes[0x12]) << 8) | + (static_cast(triggerBytes[0x11]) << 16) | 1; + } else { + flags = (static_cast(triggerBytes[0x13]) & 0xFFFFFE) | + (static_cast(triggerBytes[0x12]) << 8) | + (static_cast(triggerBytes[0x11]) << 16); + } + + *reinterpret_cast(triggerBytes + 0x10) = (word & 0xFF000000) | flags; + } + + if (setEnabled) { + CreateAllParticleEffects(); + } else { + ClearParticleEffects(); + } + + mEnabled = setEnabled; +} + +void GTrigger::GetPosition(UMath::Vector3 &pos) { + pos = *reinterpret_cast(&mWorldTrigger.fPosRadius); +} + +void GTrigger::NotifySimableTrigger(ISimable *isim, int triggerStimulus) { + if (!isim) { + return; + } + + bool triggerExited = triggerStimulus == 2; + bool triggerInside = triggerStimulus == 1; + bool wasInside = IsInside(isim); + + if (triggerExited) { + MarkAsOutside(isim); + if (FireOnExit()) { + UCrc32 triggerMessage(0x20D60DBF); + HSIMABLE handle = isim->GetInstanceHandle(); + GCollectionKey sender(this); + MTriggerExit msg(sender, handle); + msg.Post(triggerMessage); + } + } + + if (triggerInside) { + if (!wasInside) { + MarkAsInside(isim); + UCrc32 triggerMessage(0x20D60DBF); + HSIMABLE handle = isim->GetInstanceHandle(); + GCollectionKey sender(this); + MTriggerEnter enterMsg(sender, handle); + enterMsg.Post(triggerMessage); + } + + UCrc32 triggerMessage(0x20D60DBF); + HSIMABLE handle = isim->GetInstanceHandle(); + GCollectionKey sender(this); + MTriggerInside insideMsg(sender, handle); + insideMsg.Post(triggerMessage); + } +} + +void GTrigger::Reset() { + mSimObjInside.clear(); +} + +void GTrigger::ShowIcon() { + if (mIcon) { + mIcon->Show(); + mIcon->ShowOnMap(); + } +} + +void GTrigger::HideIcon() { + if (mIcon) { + mIcon->Hide(); + mIcon->HideOnMap(); + } +} + +void GTrigger::MarkAsInside(ISimable *simable) { + if (std::find(mSimObjInside.begin(), mSimObjInside.end(), simable) == mSimObjInside.end()) { + mSimObjInside.push_back(simable); + } +} + +void GTrigger::MarkAsOutside(ISimable *simable) { + UTL::Std::vector::iterator it = std::find(mSimObjInside.begin(), mSimObjInside.end(), simable); + + if (it != mSimObjInside.end()) { + mSimObjInside.erase(it); + } +} + +bool GTrigger::IsInside(ISimable *simable) { + return std::find(mSimObjInside.begin(), mSimObjInside.end(), simable) != mSimObjInside.end(); +} diff --git a/src/Speed/Indep/Src/Gameplay/GTrigger.h b/src/Speed/Indep/Src/Gameplay/GTrigger.h index 0efe0b8b0..13988b279 100644 --- a/src/Speed/Indep/Src/Gameplay/GTrigger.h +++ b/src/Speed/Indep/Src/Gameplay/GTrigger.h @@ -10,21 +10,109 @@ #include "Speed/Indep/Libs/Support/Utility/UTypes.h" #include "Speed/Indep/Src/Ecstasy/EmitterSystem.h" #include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" +#include "Speed/Indep/Src/World/WTrigger.h" -// // total size: 0xCC -// class GTrigger : public GRuntimeInstance { -// private: -// WTrigger mWorldTrigger; // offset 0x28, size 0x40 -// UMath::Vector3 mDirection; // offset 0x68, size 0xC -// unsigned int mTriggerEnabled; // offset 0x74, size 0x4 -// UTL::Std::vector mSimObjInside; // offset 0x78, size 0x10 -// struct EventList mEventList; // offset 0x88, size 0x10 -// struct EventStaticData mEventStaticData; // offset 0x98, size 0x10 -// unsigned char mTriggerEventData[16]; // offset 0xA8, size 0x10 -// EmitterGroup *mParticleEffect[2]; // offset 0xB8, size 0x8 -// struct GIcon *mIcon; // offset 0xC0, size 0x4 -// bool mEnabled; // offset 0xC4, size 0x1 -// int mActivationReferences; // offset 0xC8, size 0x4 -// }; +struct GIcon; + +DECLARE_CONTAINER_TYPE(ID_SimObjList); + +// total size: 0x10 +struct EventList { + unsigned int fNumEvents; // offset 0x0, size 0x4 + unsigned int fPad[3]; // offset 0x4, size 0xC +}; + +// total size: 0x10 +struct EventStaticData { + unsigned int fEventID; // offset 0x0, size 0x4 + unsigned int fEventSize; // offset 0x4, size 0x4 + unsigned int fDataOffset; // offset 0x8, size 0x4 + unsigned int fPad; // offset 0xC, size 0x4 +}; + +// total size: 0xCC +class GTrigger : public GRuntimeInstance { + public: + GTrigger(const Attrib::Key &triggerKey); + + ~GTrigger() override; + + GameplayObjType GetType() const override { + return kGameplayObjType_Trigger; + } + + void Disable() { + Enable(false); + } + + const UMath::Vector3 &GetDirection() const { + return mDirection; + } + + bool IsEnabled() const { + return mEnabled; + } + + bool GetTriggerEnabled() const { + return mTriggerEnabled != 0; + } + + GIcon *GetIcon() const { + return mIcon; + } + + GActivity *GetTargetActivity(); + + void AddActivationReference(); + + void RemoveActivationReference(); + + EmitterGroup *CreateParticleEffect(const char *effectName, UMath::Vector3 &pos); + + void CreateAllParticleEffects(); + + void ClearParticleEffects(); + + void EnableParticleEffects(bool enabled); + + void RefreshParticleEffects(); + + static void NotifyEmitterGroupDelete(void *obj, EmitterGroup *group); + + void Enable(bool setEnabled); + + void GetPosition(UMath::Vector3 &pos); + + float GetRadius(); + + void NotifySimableTrigger(ISimable *isim, int triggerStimulus); + + void Reset(); + + void ShowIcon(); + + void HideIcon(); + + void MarkAsInside(ISimable *simable); + + void MarkAsOutside(ISimable *simable); + + bool IsInside(ISimable *simable); + + void Update(float dT); + + private: + WTrigger mWorldTrigger; // offset 0x28, size 0x40 + UMath::Vector3 mDirection; // offset 0x68, size 0xC + unsigned int mTriggerEnabled; // offset 0x74, size 0x4 + UTL::Std::vector mSimObjInside; // offset 0x78, size 0x10 + EventList mEventList; // offset 0x88, size 0x10 + EventStaticData mEventStaticData; // offset 0x98, size 0x10 + unsigned char mTriggerEventData[16]; // offset 0xA8, size 0x10 + EmitterGroup *mParticleEffect[2]; // offset 0xB8, size 0x8 + GIcon *mIcon; // offset 0xC0, size 0x4 + bool mEnabled; // offset 0xC4, size 0x1 + int mActivationReferences; // offset 0xC8, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/Gameplay/GUtility.h b/src/Speed/Indep/Src/Gameplay/GUtility.h index 1abc1294c..90b1ab027 100644 --- a/src/Speed/Indep/Src/Gameplay/GUtility.h +++ b/src/Speed/Indep/Src/Gameplay/GUtility.h @@ -5,6 +5,8 @@ #pragma once #endif - +static inline void AlignPointer(unsigned char *&ptr, unsigned int bound) { + ptr = reinterpret_cast((reinterpret_cast(ptr) + (bound - 1)) & ~(bound - 1)); +} #endif diff --git a/src/Speed/Indep/Src/Gameplay/GVault.cpp b/src/Speed/Indep/Src/Gameplay/GVault.cpp index e69de29bb..e7018d729 100644 --- a/src/Speed/Indep/Src/Gameplay/GVault.cpp +++ b/src/Speed/Indep/Src/Gameplay/GVault.cpp @@ -0,0 +1,349 @@ +#include "Speed/Indep/Src/Gameplay/GVault.h" + +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GObjectBlock.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Misc/AttribAlloc.h" +#include "Speed/Indep/Src/Misc/AttribAsset.h" + +struct LoggingAttribAllocatorLayout { + void *vfptr; + unsigned int mChecksum; + unsigned int mAllocCount; + unsigned int mAllocBytes; + unsigned int mFreeCount; + unsigned int mFreeBytes; +}; + +struct PreloadingAttribAllocatorLayout : LoggingAttribAllocatorLayout { + int mPoolNum; +}; + +extern void *PreloadingAttribAllocator_vtable __asm__("_vt.25PreloadingAttribAllocator"); + +void *LoggingAttribAllocator::operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); +} + +void LoggingAttribAllocator::operator delete(void *mem, std::size_t size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } +} + +LoggingAttribAllocator::LoggingAttribAllocator() + : mChecksum(0xEA0FF1CE), // + mAllocCount(0), // + mAllocBytes(0), // + mFreeCount(0), // + mFreeBytes(0) {} + +LoggingAttribAllocator::~LoggingAttribAllocator() {} + +void *LoggingAttribAllocator::Allocate(std::size_t bytes, const char *name) { + void *memory = gFastMem.Alloc(bytes, name); + + if (memory) { + LogAlloc(static_cast(bytes), name); + } + + return memory; +} + +void LoggingAttribAllocator::Free(void *ptr, std::size_t bytes, const char *name) { + if (ptr) { + LogFree(static_cast(bytes), name); + gFastMem.Free(ptr, bytes, name); + } +} + +unsigned int LoggingAttribAllocator::GetChecksum() const { + return mChecksum; +} + +unsigned int LoggingAttribAllocator::GetByteCount() const { + return mAllocBytes; +} + +void LoggingAttribAllocator::LogAlloc(unsigned int bytes, const char *name) { + mAllocCount++; + mAllocBytes += bytes; +} + +void LoggingAttribAllocator::LogFree(unsigned int bytes, const char *name) { + mFreeCount++; + mFreeBytes += bytes; +} + +PreloadingAttribAllocator::PreloadingAttribAllocator(int pool_num) : LoggingAttribAllocator(), mPoolNum(pool_num) {} + +void *PreloadingAttribAllocator::Allocate(std::size_t bytes, const char *name) { + return LoggingAttribAllocator::Allocate(bytes, name); +} + +void PreloadingAttribAllocator::Free(void *ptr, std::size_t bytes, const char *name) { + LoggingAttribAllocator::Free(ptr, bytes, name); +} + +BlockLoadingAttribAllocator::BlockLoadingAttribAllocator(unsigned char *buffer, unsigned int heapSize, unsigned int targetChecksum) + : LoggingAttribAllocator(), // + mAllocPtr(buffer), // + mAvailBytes(heapSize), // + mTargetChecksum(targetChecksum) {} + +void *BlockLoadingAttribAllocator::Allocate(std::size_t bytes, const char *name) { + const unsigned int alignedBytes = static_cast((bytes + 15U) & ~15U); + + if (alignedBytes > mAvailBytes) { + return nullptr; + } + + void *memory = mAllocPtr; + mAllocPtr += alignedBytes; + mAvailBytes -= alignedBytes; + LogAlloc(alignedBytes, name); + return memory; +} + +void BlockLoadingAttribAllocator::Free(void *ptr, std::size_t bytes, const char *name) { + if (ptr) { + LogFree(static_cast(bytes), name); + } +} + +void BlockLoadingAttribAllocator::VerifyAllocations() {} + +GVault::GVault(AttribVaultPackEntry *packEntry, const char *vaultName) + : mVault(nullptr), // + mVaultName(vaultName), // + mFlags(0), // + mBinResidentData(nullptr), // + mBinOffset(packEntry->mBinOffset), // + mBinSize((packEntry->mBinSize + 15U) & ~15U), // + mVltOffset(packEntry->mVltOffset), // + mVltSize((packEntry->mVltSize + 15U) & ~15U), // + mAttribAllocator(nullptr), // + mAttribObjSize(0), // + mAttribAllocChecksum(0), // + mAttribTransientData(nullptr), // + mGameObjSize(0), // + mGameObjCount(0), // + mGameObjBlock(nullptr), // + mGameObjData(nullptr) {} + +GVault::~GVault() { + if (IsLoaded()) { + Unload(); + } +} + +void GVault::LoadResident(AttribVaultPackImage *packImage) { + char binName[128]; + char vltName[128]; + char allocName[128]; + + bSPrintf(binName, "%s.bin", GetName()); + bSPrintf(vltName, "%s.vlt", GetName()); + bSPrintf(allocName, "Gameplay resident data: %s", binName); + + mBinResidentData = static_cast(bMalloc(mBinSize, allocName, 0, GetVirtualMemoryAllocParams())); + bMemCpy(mBinResidentData, reinterpret_cast(packImage) + mBinOffset, mBinSize); + AddDepFile(binName, mBinResidentData, mBinSize); + mVault = AddVault(vltName, reinterpret_cast(packImage) + mVltOffset, mVltSize); + + mGameObjSize = (GObjectBlock::CalcSpaceRequired(this, &mGameObjCount) + 0x5FU) & ~15U; + mGameObjData = static_cast(bMalloc(mGameObjSize, "Gameplay object data", 0, 0x400)); + mFlags |= 1; +} + +void GVault::PreloadTransient(AttribVaultPackImage *packImage, int pool_num) { + unsigned char *binBlock; + unsigned char *vltBlock = reinterpret_cast(packImage); + char binFileName[128]; + char vltFileName[128]; + PreloadingAttribAllocator *preloadingAllocator; + IAttribAllocator *oldAllocator; + + binBlock = vltBlock + mBinOffset; + vltBlock += mVltOffset; + + bSPrintf(binFileName, "%s.bin", GetName()); + bSPrintf(vltFileName, "%s.vlt", GetName()); + + preloadingAllocator = static_cast(gFastMem.Alloc(sizeof(PreloadingAttribAllocator), nullptr)); + { + PreloadingAttribAllocatorLayout *allocator = reinterpret_cast(preloadingAllocator); + void *vfptr = &PreloadingAttribAllocator_vtable; + unsigned int checksum = 0xEA0FF1CE; + + allocator->mPoolNum = pool_num; + allocator->vfptr = vfptr; + allocator->mFreeBytes = 0; + allocator->mChecksum = checksum; + allocator->mAllocCount = 0; + allocator->mAllocBytes = 0; + allocator->mFreeCount = 0; + } + oldAllocator = AttribAlloc::OverrideAllocator(preloadingAllocator); + + AddDepFile(binFileName, binBlock, mBinSize); + mVault = AddVault(vltFileName, vltBlock, mVltSize); + mAttribAllocator = preloadingAllocator; + mAttribAllocChecksum = reinterpret_cast(preloadingAllocator)->mChecksum; + mAttribObjSize = reinterpret_cast(preloadingAllocator)->mAllocBytes; + + AttribAlloc::OverrideAllocator(oldAllocator); + mGameObjSize = (GObjectBlock::CalcSpaceRequired(this, &mGameObjCount) + 0x5FU) & ~15U; +} + +unsigned int GVault::InitTransient(unsigned char *binBlock, unsigned char *vltBlock) { + char binName[128]; + char vltName[128]; + BlockLoadingAttribAllocator *allocator; + IAttribAllocator *prevAllocator; + + bSPrintf(binName, "%s.bin", GetName()); + bSPrintf(vltName, "%s.vlt", GetName()); + + mAttribTransientData = binBlock + mBinSize; + mGameObjData = binBlock + mBinSize + mAttribObjSize; + + allocator = new BlockLoadingAttribAllocator(mAttribTransientData, mAttribObjSize, mAttribAllocChecksum); + prevAllocator = AttribAlloc::OverrideAllocator(allocator); + + AddDepFile(binName, binBlock, mBinSize); + mVault = AddVault(vltName, vltBlock, mVltSize); + mAttribAllocator = allocator; + + AttribAlloc::OverrideAllocator(prevAllocator); + + if (GManager::Get().GetInGameplay()) { + CreateGameplayObjects(); + } + + GRaceDatabase::Get().NotifyVaultLoaded(this); + return GetFootprint(); +} + +void GVault::CreateGameplayObjects() { + if (mGameObjData) { + new (mGameObjData) GObjectBlock(this, mGameObjData + sizeof(GObjectBlock)); + } + + mGameObjBlock = reinterpret_cast(mGameObjData); + mGameObjBlock->Initialize(mGameObjSize); +} + +void GVault::DestroyGameplayObjects() { + delete mGameObjBlock; + mGameObjBlock = nullptr; +} + +void GVault::LoadSyncTransient() { + GManager::Get().LoadVaultSync(this); +} + +void GVault::Unload() { + IAttribAllocator *prevAllocator; + char binName[128]; + char vltName[128]; + + prevAllocator = nullptr; + GRaceDatabase::Get().NotifyVaultUnloading(this); + + if (IsTransient()) { + prevAllocator = AttribAlloc::OverrideAllocator(mAttribAllocator); + } + + if (mGameObjBlock) { + DestroyGameplayObjects(); + } + + if (IsResident()) { + bFree(mGameObjData); + } + mGameObjData = nullptr; + + mVault->Deinitialize(); + mVault->Release(); + mVault = nullptr; + + bSPrintf(binName, "%s.bin", GetName()); + RemoveDepFile(binName); + + bSPrintf(vltName, "%s.vlt", GetName()); + RemoveVault(vltName); + + if (prevAllocator) { + AttribAlloc::OverrideAllocator(prevAllocator); + } + + if (IsTransient()) { + delete mAttribAllocator; + mAttribAllocator = nullptr; + } + + if (IsResident()) { + if (mBinResidentData) { + bFree(mBinResidentData); + } + + mBinResidentData = nullptr; + mFlags &= ~1U; + } +} + +const char *GVault::GetName() const { + return mVaultName; +} + +Attrib::Vault *GVault::GetAttribVault() const { + return mVault; +} + +unsigned int GVault::GetObjectCount() const { + return mGameObjCount; +} + +unsigned int GVault::GetFootprint() const { + return mBinSize + mAttribObjSize + mGameObjSize; +} + +unsigned int GVault::GetDataOffset() const { + return mBinOffset; +} + +unsigned int GVault::GetDataSize() const { + return mBinSize; +} + +unsigned int GVault::GetLoadDataOffset() const { + return mVltOffset; +} + +unsigned int GVault::GetLoadDataSize() const { + return mVltSize; +} + +bool GVault::IsLoaded() const { + return mVault != nullptr; +} + +bool GVault::IsResident() const { + return (mFlags & 1) != 0; +} + +bool GVault::IsTransient() const { + return (mFlags ^ 1) & 1; +} + +bool GVault::IsRaceBin() const { + return (mFlags & 2) != 0; +} + +void GVault::SetRaceBin() { + mFlags |= 2; +} diff --git a/src/Speed/Indep/Src/Gameplay/GVault.h b/src/Speed/Indep/Src/Gameplay/GVault.h new file mode 100644 index 000000000..bf6c31c18 --- /dev/null +++ b/src/Speed/Indep/Src/Gameplay/GVault.h @@ -0,0 +1,180 @@ +#ifndef GAMEPLAY_GVAULT_H +#define GAMEPLAY_GVAULT_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Misc/AttribAlloc.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +// total size: 0x14 +struct AttribVaultPackEntry { + unsigned int mVaultNameOffset; // offset 0x0, size 0x4 + unsigned int mBinSize; // offset 0x4, size 0x4 + unsigned int mVltSize; // offset 0x8, size 0x4 + unsigned int mBinOffset; // offset 0xC, size 0x4 + unsigned int mVltOffset; // offset 0x10, size 0x4 +}; + +// total size: 0x10 +struct AttribVaultPackHeader { + char mMagic[4]; // offset 0x0, size 0x4 + unsigned int mNumEntries; // offset 0x4, size 0x4 + unsigned int mStringBlockOffset; // offset 0x8, size 0x4 + unsigned int mStringBlockSize; // offset 0xC, size 0x4 +}; + +// total size: 0x24 +struct AttribVaultPackImage { + const char *GetVaultName(int index) { + return reinterpret_cast(this) + mHeader.mStringBlockOffset + GetEntry(index).mVaultNameOffset; + } + + int GetVaultIndex(const char *name) { + for (unsigned int i = 0; i < mHeader.mNumEntries; ++i) { + if (bStrCmp(GetVaultName(i), name) == 0) { + return static_cast(i); + } + } + + return -1; + } + + AttribVaultPackEntry &GetEntry(int index) { + return mEntry[index]; + } + + unsigned char *GetData(unsigned int offset) { + return reinterpret_cast(this) + offset; + } + + void EndianSwap() { + bPlatEndianSwap(&mHeader.mNumEntries); + bPlatEndianSwap(&mHeader.mStringBlockOffset); + bPlatEndianSwap(&mHeader.mStringBlockSize); + + for (int onEntry = 0; onEntry < static_cast(mHeader.mNumEntries); ++onEntry) { + AttribVaultPackEntry &entry = GetEntry(onEntry); + + bPlatEndianSwap(&entry.mVaultNameOffset); + bPlatEndianSwap(&entry.mBinSize); + bPlatEndianSwap(&entry.mVltSize); + bPlatEndianSwap(&entry.mBinOffset); + bPlatEndianSwap(&entry.mVltOffset); + } + } + + AttribVaultPackHeader mHeader; // offset 0x0, size 0x10 + AttribVaultPackEntry mEntry[1]; // offset 0x10, size 0x14 +}; + +struct GObjectBlock; + +// total size: 0x18 +class LoggingAttribAllocator : public IAttribAllocator { + public: + static void *operator new(std::size_t size); + static void operator delete(void *mem, std::size_t size); + + LoggingAttribAllocator(); + virtual ~LoggingAttribAllocator(); + + virtual void *Allocate(std::size_t bytes, const char *name); + virtual void Free(void *ptr, std::size_t bytes, const char *name); + + unsigned int GetChecksum() const; + unsigned int GetByteCount() const; + + protected: + void LogAlloc(unsigned int bytes, const char *name); + void LogFree(unsigned int bytes, const char *name); + + unsigned int mChecksum; // offset 0x4, size 0x4 + unsigned int mAllocCount; // offset 0x8, size 0x4 + unsigned int mAllocBytes; // offset 0xC, size 0x4 + unsigned int mFreeCount; // offset 0x10, size 0x4 + unsigned int mFreeBytes; // offset 0x14, size 0x4 +}; + +// total size: 0x1C +class PreloadingAttribAllocator : public LoggingAttribAllocator { + public: + PreloadingAttribAllocator(int pool_num); + + void *Allocate(std::size_t bytes, const char *name) override; + void Free(void *ptr, std::size_t bytes, const char *name) override; + + private: + int mPoolNum; // offset 0x18, size 0x4 +}; + +// total size: 0x24 +class BlockLoadingAttribAllocator : public LoggingAttribAllocator { + public: + BlockLoadingAttribAllocator(unsigned char *buffer, unsigned int heapSize, unsigned int targetChecksum); + + void *Allocate(std::size_t bytes, const char *name) override; + void Free(void *ptr, std::size_t bytes, const char *name) override; + void VerifyAllocations(); + + private: + unsigned char *mAllocPtr; // offset 0x18, size 0x4 + unsigned int mAvailBytes; // offset 0x1C, size 0x4 + unsigned int mTargetChecksum; // offset 0x20, size 0x4 +}; + +namespace Attrib { +class Vault; +} + +// total size: 0x40 +class GVault { + public: + GVault(AttribVaultPackEntry *packEntry, const char *vaultName); + ~GVault(); + + void LoadResident(AttribVaultPackImage *packImage); + void PreloadTransient(AttribVaultPackImage *packImage, int pool_num); + unsigned int InitTransient(unsigned char *binBlock, unsigned char *vltBlock); + void CreateGameplayObjects(); + void DestroyGameplayObjects(); + void LoadSyncTransient(); + void LoadAsyncTransient(); + void Unload(); + + const char *GetName() const; + Attrib::Vault *GetAttribVault() const; + GObjectBlock *GetObjectBlock() const { return mGameObjBlock; } + unsigned int GetObjectCount() const; + unsigned int GetFootprint() const; + unsigned int GetDataOffset() const; + unsigned int GetDataSize() const; + unsigned int GetLoadDataOffset() const; + unsigned int GetLoadDataSize() const; + bool IsLoaded() const; + bool IsResident() const; + bool IsTransient() const; + bool IsRaceBin() const; + void SetRaceBin(); + + private: + Attrib::Vault *mVault; // offset 0x0, size 0x4 + const char *mVaultName; // offset 0x4, size 0x4 + unsigned int mFlags; // offset 0x8, size 0x4 + unsigned char *mBinResidentData; // offset 0xC, size 0x4 + unsigned int mBinOffset; // offset 0x10, size 0x4 + unsigned int mBinSize; // offset 0x14, size 0x4 + unsigned int mVltOffset; // offset 0x18, size 0x4 + unsigned int mVltSize; // offset 0x1C, size 0x4 + LoggingAttribAllocator *mAttribAllocator; // offset 0x20, size 0x4 + unsigned int mAttribObjSize; // offset 0x24, size 0x4 + unsigned int mAttribAllocChecksum; // offset 0x28, size 0x4 + unsigned char *mAttribTransientData; // offset 0x2C, size 0x4 + unsigned int mGameObjSize; // offset 0x30, size 0x4 + unsigned int mGameObjCount; // offset 0x34, size 0x4 + GObjectBlock *mGameObjBlock; // offset 0x38, size 0x4 + unsigned char *mGameObjData; // offset 0x3C, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Gameplay/LuaMessageDeliveryInfo.h b/src/Speed/Indep/Src/Gameplay/LuaMessageDeliveryInfo.h new file mode 100644 index 000000000..8e0b9a074 --- /dev/null +++ b/src/Speed/Indep/Src/Gameplay/LuaMessageDeliveryInfo.h @@ -0,0 +1,71 @@ +#ifndef GAMEPLAY_LUAMESSAGEDELIVERYINFO_H +#define GAMEPLAY_LUAMESSAGEDELIVERYINFO_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" + +struct GActivity; +struct GHandler; +struct lua_State; +struct Message; + +struct IMessageFilterContext : public UTL::COM::IUnknown { + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + IMessageFilterContext(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual ~IMessageFilterContext() {} + + virtual lua_State *GetLuaState() const = 0; + virtual GActivity *GetActivity() const = 0; + virtual GHandler *GetHandler() const = 0; + virtual const Message *GetMessage() const = 0; +}; + +// total size: 0x34 +struct LuaMessageDeliveryInfo : public UTL::COM::Object, public IMessageFilterContext { + LuaMessageDeliveryInfo(UCrc32 messageKind, const Message *messageBase, + void (*buildTableFunc)(lua_State *, const Message *)) + : UTL::COM::Object(1) // + , IMessageFilterContext(this) // + , mMessageKind(messageKind) // + , mMessageBase(messageBase) // + , mBuildTableFunc(buildTableFunc) // + , mLuaTableBuilt(false) // + , mLuaState(nullptr) // + , mActivityContext(nullptr) // + , mHandlerContext(nullptr) // + { + } + + ~LuaMessageDeliveryInfo() override; + + unsigned int GetMessageKind() const { return mMessageKind.GetValue(); } + void BuildMessageTable(); + + void SetLuaState(lua_State *luaState) { mLuaState = luaState; } + void SetActivityContext(GActivity *activity) { mActivityContext = activity; } + void SetHandlerContext(GHandler *handler) { mHandlerContext = handler; } + + lua_State *GetLuaState() const override { return mLuaState; } + GActivity *GetActivity() const override { return mActivityContext; } + GHandler *GetHandler() const override { return mHandlerContext; } + const Message *GetMessage() const override { return mMessageBase; } + + private: + UCrc32 mMessageKind; // offset 0x18, size 0x4 + const Message *mMessageBase; // offset 0x1C, size 0x4 + void (*mBuildTableFunc)(lua_State *, const Message *); // offset 0x20, size 0x4 + bool mLuaTableBuilt; // offset 0x24, size 0x1 + lua_State *mLuaState; // offset 0x28, size 0x4 + GActivity *mActivityContext; // offset 0x2C, size 0x4 + GHandler *mHandlerContext; // offset 0x30, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/camerainfo.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/camerainfo.h index 3cc83a249..26baa2e17 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/camerainfo.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/camerainfo.h @@ -72,9 +72,7 @@ struct camerainfo : Instance { Change(FindCollection(ClassKey(), collectionkey)); } - static Key ClassKey() { - return 0x93c171e4; - } + static Key ClassKey(); const float &STIFFNESS(unsigned int index) const { const _LayoutStruct *lp = reinterpret_cast<_LayoutStruct *>(this->GetLayoutPointer()); diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h index 6094b012b..bcb718fd2 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h @@ -73,10 +73,12 @@ struct ecar : Instance { Change(FindCollection(ClassKey(), collectionkey)); } - static Key ClassKey() { - return 0xa5b543b7; + void ChangeWithDefault(unsigned int collectionkey) { + Change(FindCollectionWithDefault(ClassKey(), collectionkey)); } + static Key ClassKey(); + const RefSpec &CameraInfo_Close(unsigned int index) const { const RefSpec *resultptr = reinterpret_cast(this->GetAttributePointer(0x0c2da793, index)); if (!resultptr) { diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h index 8f0044386..047245d86 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h @@ -27,14 +27,22 @@ enum GameplayObjType { }; namespace Attrib { + +template <> +const GCollectionKey &TAttrib::Get(unsigned int index) const; + namespace Gen { struct gameplay : Instance { struct _LayoutStruct { - char CollectionName[4]; // offset 0x0, size 0x4 + const char *CollectionName; // offset 0x0, size 0x4 unsigned int message_id; // offset 0x4, size 0x4 }; + void *operator new(size_t bytes) { + return Attrib::Alloc(bytes, "gameplay"); + } + void operator delete(void *ptr, size_t bytes) { Attrib::Free(ptr, bytes, "gameplay"); } @@ -58,6 +66,14 @@ struct gameplay : Instance { Change(FindCollection(ClassKey(), collectionkey)); } + bool Modify(Key dynamicCollectionKey, unsigned int spaceForAdditionalAttributes) { + return ModifyInternal(ClassKey(), dynamicCollectionKey, LocalAttribCount() + spaceForAdditionalAttributes); + } + + Key GenerateUniqueKey(const char *name, bool registerName) const { + return GUKeyInternal(ClassKey(), name, registerName); + } + static Key ClassKey() { return 0x5cea9d46; } @@ -278,6 +294,14 @@ struct gameplay : Instance { return *resultptr; } + const bool &OpenWorldSpeedTrap() const { + const bool *resultptr = reinterpret_cast(this->GetAttributePointer(0x1bb16f14, 0)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(bool))); + } + return *resultptr; + } + const bool &OpenWorldSpeedTrap(unsigned int index) const { const bool *resultptr = reinterpret_cast(this->GetAttributePointer(0x1bb16f14, index)); if (!resultptr) { @@ -338,6 +362,14 @@ struct gameplay : Instance { return *resultptr; } + const GCollectionKey &TargetActivity() const { + const GCollectionKey *resultptr = reinterpret_cast(this->GetAttributePointer(0x277566f3, 0)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(GCollectionKey))); + } + return *resultptr; + } + const GCollectionKey &TargetActivity(unsigned int index) const { const GCollectionKey *resultptr = reinterpret_cast(this->GetAttributePointer(0x277566f3, index)); if (!resultptr) { @@ -422,6 +454,15 @@ struct gameplay : Instance { return *resultptr; } + bool Radius(float &result) const { + const float *resultptr = reinterpret_cast(this->GetAttributePointer(0x39bf8002, 0)); + if (!resultptr) { + return false; + } + result = *resultptr; + return true; + } + const float &Radius(unsigned int index) const { const float *resultptr = reinterpret_cast(this->GetAttributePointer(0x39bf8002, index)); if (!resultptr) { @@ -670,6 +711,14 @@ struct gameplay : Instance { return this->Get(0x56e1436d).GetLength(); } + const float &Width() const { + const float *resultptr = reinterpret_cast(this->GetAttributePointer(0x5816c1fc, 0)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(float))); + } + return *resultptr; + } + const float &Width(unsigned int index) const { const float *resultptr = reinterpret_cast(this->GetAttributePointer(0x5816c1fc, index)); if (!resultptr) { @@ -710,6 +759,14 @@ struct gameplay : Instance { return *resultptr; } + const float &Rotation() const { + const float *resultptr = reinterpret_cast(this->GetAttributePointer(0x5a6a57c6, 0)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(float))); + } + return *resultptr; + } + const float &Rotation(unsigned int index) const { const float *resultptr = reinterpret_cast(this->GetAttributePointer(0x5a6a57c6, index)); if (!resultptr) { @@ -782,7 +839,7 @@ struct gameplay : Instance { return *resultptr; } - const GCollectionKey &PostRaceActivity(unsigned int index) const { + const GCollectionKey &PostRaceActivity(unsigned int index = 0) const { const GCollectionKey *resultptr = reinterpret_cast(this->GetAttributePointer(0x64273c71, index)); if (!resultptr) { resultptr = reinterpret_cast(DefaultDataArea(sizeof(GCollectionKey))); @@ -842,6 +899,14 @@ struct gameplay : Instance { return *resultptr; } + const int &BinIndex() const { + const int *resultptr = reinterpret_cast(this->GetAttributePointer(0x6ce23062, 0)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(int))); + } + return *resultptr; + } + const int &BinIndex(unsigned int index) const { const int *resultptr = reinterpret_cast(this->GetAttributePointer(0x6ce23062, index)); if (!resultptr) { @@ -862,6 +927,16 @@ struct gameplay : Instance { return this->Get(0x6d7e73c9).GetLength(); } + bool Dimensions(UMath::Vector3 &result) const { + const UMath::Vector3 *resultptr = reinterpret_cast(this->GetAttributePointer(0x6d9e21ad, 0)); + bool hasResult = resultptr != nullptr; + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(UMath::Vector3))); + } + result = *resultptr; + return hasResult; + } + const UMath::Vector3 &Dimensions(unsigned int index) const { const UMath::Vector3 *resultptr = reinterpret_cast(this->GetAttributePointer(0x6d9e21ad, index)); if (!resultptr) { @@ -978,7 +1053,7 @@ struct gameplay : Instance { return *resultptr; } - const GCollectionKey &racefinishReverse(unsigned int index) const { + const GCollectionKey &racefinishReverse(unsigned int index = 0) const { const GCollectionKey *resultptr = reinterpret_cast(this->GetAttributePointer(0x7c7cf20f, index)); if (!resultptr) { resultptr = reinterpret_cast(DefaultDataArea(sizeof(GCollectionKey))); @@ -1022,7 +1097,7 @@ struct gameplay : Instance { return *resultptr; } - const GCollectionKey &handler_owner(unsigned int index) const { + const GCollectionKey &handler_owner(unsigned int index = 0) const { const GCollectionKey *resultptr = reinterpret_cast(this->GetAttributePointer(0x857fe432, index)); if (!resultptr) { resultptr = reinterpret_cast(DefaultDataArea(sizeof(GCollectionKey))); @@ -1098,7 +1173,7 @@ struct gameplay : Instance { return this->Get(0x916e0e78).GetLength(); } - const GCollectionKey &stateref(unsigned int index) const { + const GCollectionKey &stateref(unsigned int index = 0) const { const GCollectionKey *resultptr = reinterpret_cast(this->GetAttributePointer(0x918c796e, index)); if (!resultptr) { resultptr = reinterpret_cast(DefaultDataArea(sizeof(GCollectionKey))); @@ -1142,7 +1217,7 @@ struct gameplay : Instance { return this->Get(0x9c19e56f).GetLength(); } - const char *CollectionName() const { + const char * const &CollectionName() const { return reinterpret_cast<_LayoutStruct *>(this->GetLayoutPointer())->CollectionName; } @@ -1170,6 +1245,14 @@ struct gameplay : Instance { return *resultptr; } + const UMath::Vector3 &Position() const { + const UMath::Vector3 *resultptr = reinterpret_cast(this->GetAttributePointer(0x9f743a0e, 0)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(UMath::Vector3))); + } + return *resultptr; + } + const UMath::Vector3 &Position(unsigned int index) const { const UMath::Vector3 *resultptr = reinterpret_cast(this->GetAttributePointer(0x9f743a0e, index)); if (!resultptr) { @@ -1326,6 +1409,37 @@ struct gameplay : Instance { return *resultptr; } + TAttrib GetOrClone(unsigned int attributeKey) { + TAttrib attr(this->Get(attributeKey)); + + if (attr.IsValid() && attr.GetCollection() != this->GetConstCollection()) { + unsigned int len = attr.GetLength(); + + if (!this->Add(attributeKey, len)) { + return attr; + } + + { + TAttrib localattr(this->Get(attributeKey)); + localattr.GetCollection(); + + for (unsigned int i = 0; i < len; i++) { + localattr.Set(i, attr.Get(i)); + } + + return localattr; + } + } + + return attr; + } + + bool Set_racefinish(const GCollectionKey &input) { + TAttrib attr = GetOrClone(0xb0a24adc); + + return attr.Set(0, input); + } + const float &MinimumAIPerformance() const { const float *resultptr = reinterpret_cast(this->GetAttributePointer(0xb1ece070, 0)); if (!resultptr) { @@ -1342,6 +1456,10 @@ struct gameplay : Instance { return *resultptr; } + const bool &FireOnExit() const { + return FireOnExit(0); + } + const bool &AvailableQR(unsigned int index) const { const bool *resultptr = reinterpret_cast(this->GetAttributePointer(0xb39ed8c3, index)); if (!resultptr) { @@ -1534,6 +1652,14 @@ struct gameplay : Instance { return *resultptr; } + const bool &OneShot() const { + const bool *resultptr = reinterpret_cast(this->GetAttributePointer(0xce4261ac, 0)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(bool))); + } + return *resultptr; + } + const bool &OneShot(unsigned int index) const { const bool *resultptr = reinterpret_cast(this->GetAttributePointer(0xce4261ac, index)); if (!resultptr) { @@ -1710,6 +1836,12 @@ struct gameplay : Instance { return *resultptr; } + bool Set_racestart(const GCollectionKey &input) { + TAttrib attr = GetOrClone(0xe43b2ccc); + + return attr.Set(0, input); + } + const bool &Persistent(unsigned int index) const { const bool *resultptr = reinterpret_cast(this->GetAttributePointer(0xe4542e9b, index)); if (!resultptr) { @@ -1882,7 +2014,7 @@ struct gameplay : Instance { return *resultptr; } - const GCollectionKey &racestartReverse(unsigned int index) const { + const GCollectionKey &racestartReverse(unsigned int index = 0) const { const GCollectionKey *resultptr = reinterpret_cast(this->GetAttributePointer(0xfd945479, index)); if (!resultptr) { resultptr = reinterpret_cast(DefaultDataArea(sizeof(GCollectionKey))); diff --git a/src/Speed/Indep/Src/Generated/Messages/MGamePlayMoment.h b/src/Speed/Indep/Src/Generated/Messages/MGamePlayMoment.h index beb532aea..8388f5c8b 100644 --- a/src/Speed/Indep/Src/Generated/Messages/MGamePlayMoment.h +++ b/src/Speed/Indep/Src/Generated/Messages/MGamePlayMoment.h @@ -11,7 +11,7 @@ // total size: 0x48 class MGamePlayMoment : public Hermes::Message { public: - static std::size_t _GetSize() { + static unsigned int _GetSize() { return sizeof(MGamePlayMoment); } diff --git a/src/Speed/Indep/Src/Generated/Messages/MStateEnter.h b/src/Speed/Indep/Src/Generated/Messages/MStateEnter.h index b41cf70b8..766765c8f 100644 --- a/src/Speed/Indep/Src/Generated/Messages/MStateEnter.h +++ b/src/Speed/Indep/Src/Generated/Messages/MStateEnter.h @@ -14,11 +14,7 @@ class MStateEnter : public Hermes::Message { return sizeof(MStateEnter); } - static UCrc32 _GetKind() { - static UCrc32 k("MStateEnter"); - - return k; - } + static UCrc32 _GetKind(); MStateEnter() : Hermes::Message(_GetKind(), _GetSize(), 0) {} diff --git a/src/Speed/Indep/Src/Generated/Messages/MStateExit.h b/src/Speed/Indep/Src/Generated/Messages/MStateExit.h index 6ed9ae779..aebf9b977 100644 --- a/src/Speed/Indep/Src/Generated/Messages/MStateExit.h +++ b/src/Speed/Indep/Src/Generated/Messages/MStateExit.h @@ -14,11 +14,7 @@ class MStateExit : public Hermes::Message { return sizeof(MStateExit); } - static UCrc32 _GetKind() { - static UCrc32 k("MStateExit"); - - return k; - } + static UCrc32 _GetKind(); MStateExit() : Hermes::Message(_GetKind(), _GetSize(), 0) {} diff --git a/src/Speed/Indep/Src/Interfaces/IFengHud.h b/src/Speed/Indep/Src/Interfaces/IFengHud.h index 8611948d3..b2c1211df 100644 --- a/src/Speed/Indep/Src/Interfaces/IFengHud.h +++ b/src/Speed/Indep/Src/Interfaces/IFengHud.h @@ -21,6 +21,7 @@ class IHud : public UTL::COM::IUnknown, public UTL::Collections::Listable { public: ITrafficCenter() {} - virtual bool GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity); + virtual bool GetTrafficBasis(UMath::Matrix4 &matrix, UMath::Vector3 &velocity) = 0; virtual ~ITrafficCenter() {} }; diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IAudible.h b/src/Speed/Indep/Src/Interfaces/Simables/IAudible.h index 9c1dc8476..19bc8168d 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IAudible.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IAudible.h @@ -9,9 +9,7 @@ class IAudible : public UTL::COM::IUnknown { public: - static HINTERFACE _IHandle() { - return (HINTERFACE)_IHandle; - } + static HINTERFACE _IHandle(); IAudible(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} diff --git a/src/Speed/Indep/Src/Lua/LuaRuntime.h b/src/Speed/Indep/Src/Lua/LuaRuntime.h index dd77e68f6..64f0d47c1 100644 --- a/src/Speed/Indep/Src/Lua/LuaRuntime.h +++ b/src/Speed/Indep/Src/Lua/LuaRuntime.h @@ -29,6 +29,8 @@ class LuaRuntime { void TakeResetSnapshot(); + static unsigned int DeserializeTable(lua_State *state, unsigned char *buffer, bool allowUserData); + static LuaRuntime &Get() { return *mObj; } diff --git a/src/Speed/Indep/Src/Misc/AttribAsset.h b/src/Speed/Indep/Src/Misc/AttribAsset.h index 7fa1d1c67..8c1acc776 100644 --- a/src/Speed/Indep/Src/Misc/AttribAsset.h +++ b/src/Speed/Indep/Src/Misc/AttribAsset.h @@ -45,4 +45,9 @@ class FileMap : public std::map { FileMap(); }; +bool AddDepFile(const char *filename, void *data, size_t bytes); +bool RemoveDepFile(const char *filename); +Attrib::Vault *AddVault(const char *filename, void *data, size_t bytes); +void RemoveVault(const char *filename); + #endif diff --git a/src/Speed/Indep/Src/Misc/GameFlow.hpp b/src/Speed/Indep/Src/Misc/GameFlow.hpp index a1034b17e..2573e38b3 100644 --- a/src/Speed/Indep/Src/Misc/GameFlow.hpp +++ b/src/Speed/Indep/Src/Misc/GameFlow.hpp @@ -45,6 +45,8 @@ class GameFlowManager { return GetState() == GAMEFLOW_STATE_LOADING_REGION || GetState() == GAMEFLOW_STATE_LOADING_TRACK; } + bool IsPaused(); + private: void (*pSingleFunction)(int); // offset 0x0, size 0x4 int SingleFunctionParam; // offset 0x4, size 0x4 diff --git a/src/Speed/Indep/Src/Misc/Hermes.h b/src/Speed/Indep/Src/Misc/Hermes.h index 34452c4a6..18cf9db0d 100644 --- a/src/Speed/Indep/Src/Misc/Hermes.h +++ b/src/Speed/Indep/Src/Misc/Hermes.h @@ -23,7 +23,7 @@ class Message { public: Message() {} - Message(UCrc32 kind, std::size_t size, unsigned int id) : mKind(kind), mSize(size), mID(id) {} + Message(UCrc32 kind, unsigned int size, unsigned int id) : mKind(kind), mSize(size), mID(id) {} ~Message() {} @@ -35,7 +35,7 @@ class Message { Deliver(); } - std::size_t GetSize() const { + unsigned int GetSize() const { return mSize; } @@ -55,7 +55,7 @@ class Message { private: UCrc32 mKind; // offset 0x0, size 0x4 UCrc32 mPort; // offset 0x4, size 0x4 - std::size_t mSize; // offset 0x8, size 0x4 + unsigned int mSize; // offset 0x8, size 0x4 unsigned int mID; // offset 0xC, size 0x4 }; diff --git a/src/Speed/Indep/Src/Misc/LZCompress.hpp b/src/Speed/Indep/Src/Misc/LZCompress.hpp index d1436fa4c..093643542 100644 --- a/src/Speed/Indep/Src/Misc/LZCompress.hpp +++ b/src/Speed/Indep/Src/Misc/LZCompress.hpp @@ -1,3 +1,10 @@ +#ifndef MISC_LZCOMPRESS_HPP +#define MISC_LZCOMPRESS_HPP + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + #include "types.h" // total size: 0x10 @@ -13,3 +20,5 @@ class LZHeader { int32 LZDecompress(uint8 *pSrc, uint8 *pDst); int LZValidHeader(LZHeader *header); + +#endif diff --git a/src/Speed/Indep/Src/Misc/MD5.hpp b/src/Speed/Indep/Src/Misc/MD5.hpp index dcb20e28b..dc67aa0f9 100644 --- a/src/Speed/Indep/Src/Misc/MD5.hpp +++ b/src/Speed/Indep/Src/Misc/MD5.hpp @@ -5,6 +5,28 @@ #pragma once #endif +// total size: 0x90 +class MD5 { + public: + MD5() {} + virtual ~MD5() {} + int GetRawLength() { + return computed ? 16 : 0; + } + + void Reset(); + void Update(const void *buffer, int length); + void *GetRaw(); + const char *GetString(); + + private: + unsigned int uCount; // offset 0x0, size 0x4 + unsigned int uRegs[4]; // offset 0x4, size 0x10 + unsigned char strData[64]; // offset 0x14, size 0x40 + bool computed; // offset 0x54, size 0x1 + unsigned char rawMD5[16]; // offset 0x58, size 0x10 + unsigned char strMD5[33]; // offset 0x68, size 0x21 +}; #endif diff --git a/src/Speed/Indep/Src/Misc/Table.hpp b/src/Speed/Indep/Src/Misc/Table.hpp index 6672953f1..ee6e16d7c 100644 --- a/src/Speed/Indep/Src/Misc/Table.hpp +++ b/src/Speed/Indep/Src/Misc/Table.hpp @@ -29,6 +29,14 @@ class TableBase { IndexMultiplier = (NumEntries - 1) / (MaxArg - MinArg); } + int GetNumEntries() const { + return NumEntries; + } + + float GetIndex(float f) const { + return IndexMultiplier * (f - MinArg); + } + protected: int NumEntries; // offset 0x0, size 0x4 float MinArg; // offset 0x4, size 0x4 @@ -56,6 +64,23 @@ template class tTable : public TableBase { T *pTable; public: + tTable(T *table, int num, float min, float max) : TableBase(num, min, max), pTable(table) {} + + void Blend(T *dest, T *a, T *b, float blend_a); + + void GetValue(T *p, float arg) { + int entries = GetNumEntries(); + float normarg = GetIndex(arg); + int index = static_cast(normarg); + if (index < 0) { + bMemCpy(p, &pTable[0], sizeof(T)); + } else if (index >= entries - 1) { + bMemCpy(p, &pTable[entries - 1], sizeof(T)); + } else { + float blend = normarg - bFloor(normarg); + Blend(p, &pTable[index + 1], &pTable[index], blend); + } + } }; class AverageBase { @@ -94,13 +119,50 @@ class AverageBase { virtual void Recalculate() {} - protected: unsigned char nSize; unsigned char nSlots; unsigned char nSamples; unsigned char nCurrentSlot; }; +template class tAverage : public AverageBase { + public: + tAverage(int nSlots) : AverageBase(sizeof(T), nSlots) { + pData = new (__FILE__, __LINE__) T[nSlots]; + bMemSet(pData, 0, sizeof(T) * nSlots); + Average = pData[0]; + Total = pData[0]; + } + + virtual ~tAverage() { + if (pData) { + delete[] pData; + } + } + + T *GetValue() { + return &Average; + } + + void Record(T *pValue); + + virtual void Recalculate() { + Total *= 0.0f; + for (int i = 0; i < nSamples; i++) { + Total += pData[i]; + } + int n = static_cast(nSamples); + if (n == 0) n = 1; + float fRecip = 1.0f / static_cast(n); + bVector3 result = Total * fRecip; + Average = result; + } + + T *pData; // offset 0x8, size 0x4 + T Total; // offset 0xC + T Average; // offset varies +}; + class Average : public AverageBase { public: Average(); @@ -194,7 +256,7 @@ template class tGraph { } else { for (int i = 0; i < NumEntries - 1; ++i) { if (x >= GraphData[i].x && x < GraphData[i + 1].x) { - const T blend = (x - GraphData[i].x) / (GraphData[i + 1].x - GraphData[i].x); + T blend = (x - GraphData[i].x) / (GraphData[i + 1].x - GraphData[i].x); Blend(pValue, &GraphData[i + 1].y, &GraphData[i].y, blend); return; } diff --git a/src/Speed/Indep/Src/Misc/Timer.hpp b/src/Speed/Indep/Src/Misc/Timer.hpp index 634d10422..621ce9360 100644 --- a/src/Speed/Indep/Src/Misc/Timer.hpp +++ b/src/Speed/Indep/Src/Misc/Timer.hpp @@ -49,6 +49,10 @@ class Timer { Timer operator*(const Timer &t) const {} + Timer operator-(const Timer &t) const { + return Timer(PackedTime - t.PackedTime); + } + Timer &operator+=(const Timer &t) {} Timer &operator-=(const Timer &t) {} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.h b/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.h index 360072b1b..b2dc54975 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.h @@ -308,7 +308,7 @@ class RigidBody : public Behavior, bool IsImmobile() const override; // ICollisionHandler - override bool OnWCollide(const WCollisionMgr::WorldCollisionInfo &cInfo, const UMath::Vector3 &cPoint, void *userdata); + bool OnWCollide(const WCollisionMgr::WorldCollisionInfo &cInfo, const UMath::Vector3 &cPoint, void *userdata) override; // Virtual methods virtual void OnDebugDraw(); diff --git a/src/Speed/Indep/Src/Speech/Observer.h b/src/Speed/Indep/Src/Speech/Observer.h index c9385142d..8b0b9413b 100644 --- a/src/Speed/Indep/Src/Speech/Observer.h +++ b/src/Speed/Indep/Src/Speech/Observer.h @@ -5,6 +5,8 @@ #pragma once #endif - +struct Observer { + void NotifyAirborne(float alt, float t); +}; #endif diff --git a/src/Speed/Indep/Src/Speech/SoundAI.h b/src/Speed/Indep/Src/Speech/SoundAI.h index a35ac00c0..571e68b1d 100644 --- a/src/Speed/Indep/Src/Speech/SoundAI.h +++ b/src/Speed/Indep/Src/Speech/SoundAI.h @@ -7,17 +7,26 @@ #include "EAXAirSupport.h" #include "Speed/Indep/Src/EAXSound/EAXSoundTypes.h" +#include "Speed/Indep/Src/Speech/Observer.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/speechtune.h" #include "Speed/Indep/Src/Generated/Messages/MUnspawnCop.h" #include "Speed/Indep/Src/Interfaces/IListener.h" #include "Speed/Indep/Src/Interfaces/Simables/IAI.h" #include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" #include "Speed/Indep/Src/Misc/Hermes.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" #include "Speed/Indep/Src/Sim/Collision.h" #include "Speed/Indep/Src/Sim/SimActivity.h" DECLARE_CONTAINER_TYPE(IVehiclePtrs); +// total size: 0xC +struct BlowByRecord { + float distance; // offset 0x0, size 0x4 + float speed; // offset 0x4, size 0x4 + Timer timestamp; // offset 0x8, size 0x4 +}; + // total size: 0x260 class SoundAI : public Sim::Activity, public Sim::Collision::IListener, public UTL::Collections::Singleton { public: @@ -234,6 +243,9 @@ class SoundAI : public Sim::Activity, public Sim::Collision::IListener, public U // void SetFocus(enum MachineState s) {} // struct Observer *GetObserver() {} + struct Observer *GetObserver() { + return mObserver; + } // struct RoadblockFlow *GetRBFlow() {} @@ -346,7 +358,7 @@ class SoundAI : public Sim::Activity, public Sim::Collision::IListener, public U CarHeading mAILastKnown; // offset 0x1D4, size 0x8 PursuitState mPursuitState; // offset 0x1DC, size 0x4 QuadrantState mQuadrantState; // offset 0x1E0, size 0x4 - // BlowByRecord mRecentBlowby; // offset 0x1E4, size 0xC + BlowByRecord mRecentBlowby; // offset 0x1E4, size 0xC int mInfraction; // offset 0x1F0, size 0x4 int mNumCopsInWave; // offset 0x1F4, size 0x4 int mNumActiveCopCars; // offset 0x1F8, size 0x4 diff --git a/src/Speed/Indep/Src/World/RaceParameters.hpp b/src/Speed/Indep/Src/World/RaceParameters.hpp index f3484afd2..b7a40981c 100644 --- a/src/Speed/Indep/Src/World/RaceParameters.hpp +++ b/src/Speed/Indep/Src/World/RaceParameters.hpp @@ -76,6 +76,10 @@ struct RaceParameters { return ((this->bDriftRaceFlag) || (g_tweakIsDriftRace)); } + inline bool IsBurnout() { + return ((this->bBurnoutFlag) || (g_tweakIsBurnout)); + } + int TrackNumber; // offset 0x0, size 0x4 eTrackDirection TrackDirection; // offset 0x4, size 0x4 eTrafficDensity TrafficDensity; // offset 0x8, size 0x4 diff --git a/src/Speed/Indep/Src/World/Track.hpp b/src/Speed/Indep/Src/World/Track.hpp index 31877c32d..79a64f357 100644 --- a/src/Speed/Indep/Src/World/Track.hpp +++ b/src/Speed/Indep/Src/World/Track.hpp @@ -5,6 +5,20 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +// total size: 0x8 +struct TopologyCoordinate : public bTNode { + TopologyCoordinate() {} + + ~TopologyCoordinate() {} + + void SetInterestBBox(const bVector3 *position, float radius, const bVector3 *velocity) {} + + bool IsLoaded() { return false; } +}; + void EstablishRemoteCaffeineConnection(); #endif diff --git a/src/Speed/Indep/Src/World/TrackInfo.hpp b/src/Speed/Indep/Src/World/TrackInfo.hpp index f2cd58ff0..0f46ed9ea 100644 --- a/src/Speed/Indep/Src/World/TrackInfo.hpp +++ b/src/Speed/Indep/Src/World/TrackInfo.hpp @@ -77,6 +77,8 @@ class TrackInfo { float TrackMapZoomWidth; // offset 0x118, size 0x4 char TrackMapStartZoomed; // offset 0x11C, size 0x1 + static TrackInfo *GetTrackInfo(int trackNumber); + const char *GetLoadedTrackInfo() { return this->RegionName; } diff --git a/src/Speed/Indep/Src/World/TrackStreamer.hpp b/src/Speed/Indep/Src/World/TrackStreamer.hpp index aa1028f1b..dfef41328 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.hpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.hpp @@ -149,8 +149,13 @@ class TrackStreamer { void ServiceGameState(); void ServiceNonGameState(); void SetStreamingPosition(int position_number, const bVector3 *position); + void PredictStreamingPosition(int position_number, const bVector3 *position, const bVector3 *velocity, const bVector3 *direction, bool following_car); void ClearStreamingPositions(); void BlockUntilLoadingComplete(); + int IsLoadingInProgress(); + bool IsFarLoadingInProgress() { + return CurrentZoneFarLoad; + } void *AllocateUserMemory(int size, const char *debug_name, int offset); void FreeUserMemory(void *mem); @@ -158,6 +163,10 @@ class TrackStreamer { ZoneSwitchingDisabled = true; } + void EnableZoneSwitching() { + ZoneSwitchingDisabled = false; + } + int IsSectionVisible(int section_number) { return CurrentVisibleSectionTable.IsSet(section_number); } diff --git a/src/Speed/Indep/Src/World/WCollider.h b/src/Speed/Indep/Src/World/WCollider.h index fcafc2d6f..0252a9ea4 100644 --- a/src/Speed/Indep/Src/World/WCollider.h +++ b/src/Speed/Indep/Src/World/WCollider.h @@ -31,6 +31,7 @@ class WCollider : public UTL::Collections::Listable { }; static void Destroy(WCollider *col); + static WCollider *Create(unsigned int wuid, eColliderShape shape, unsigned int typeCheckMask, unsigned int exclusionMask); void Clear(); bool IsEmpty() const; diff --git a/src/Speed/Indep/Src/World/WCollision.h b/src/Speed/Indep/Src/World/WCollision.h index cf8e7cef8..a59b4ba9d 100644 --- a/src/Speed/Indep/Src/World/WCollision.h +++ b/src/Speed/Indep/Src/World/WCollision.h @@ -13,6 +13,7 @@ struct WSurface : public CollisionSurface {}; + struct WCollisionArticle { // total size: 0x10 unsigned short fNumStrips; // offset 0x0, size 0x2 diff --git a/src/Speed/Indep/Src/World/WCollisionMgr.h b/src/Speed/Indep/Src/World/WCollisionMgr.h index 9f44211f1..2b0d4af33 100644 --- a/src/Speed/Indep/Src/World/WCollisionMgr.h +++ b/src/Speed/Indep/Src/World/WCollisionMgr.h @@ -34,14 +34,13 @@ class WCollisionMgr { fPad(0), // fCInst(nullptr) {} - bool HitSomething() const {} + bool HitSomething() const { return fType; } }; class ICollisionHandler { public: ICollisionHandler() {} - - virtual bool OnWCollide(const WorldCollisionInfo &cInfo, const bVector3 &cPoint, void *userdata); + virtual bool OnWCollide(const WorldCollisionInfo &cInfo, const UMath::Vector3 &cPoint, void *userdata); }; typedef UTL::Vector NodeIndexList; diff --git a/src/Speed/Indep/Src/World/WCollisionTri.h b/src/Speed/Indep/Src/World/WCollisionTri.h index b88fd9b2b..e9b61ebcf 100644 --- a/src/Speed/Indep/Src/World/WCollisionTri.h +++ b/src/Speed/Indep/Src/World/WCollisionTri.h @@ -8,8 +8,34 @@ #include "./WCollision.h" #include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" +#include "Speed/Indep/Libs/Support/Utility/UVectorMath.h" struct WCollisionTri { + WCollisionTri() {} + + void GetNormal(UMath::Vector3 *norm) const { + UMath::Vector3 vecX; + UMath::Vector3 vecZ; + UMath::Vector3 normal; + + vecZ.x = fPt1.x - fPt0.x; + vecZ.y = fPt1.y - fPt0.y; + vecZ.z = fPt1.z - fPt0.z; + + vecX.x = fPt0.x - fPt2.x; + vecX.y = fPt0.y - fPt2.y; + vecX.z = fPt0.z - fPt2.z; + + VU0_v3crossprod(vecZ, vecX, normal); + + if (normal.x == 0.0f && normal.y == 0.0f && normal.z == 0.0f) { + norm->x = 0.0f; + norm->y = 1.0f; + norm->z = 0.0f; + } else { + VU0_v3unit(normal, *norm); + } + } // total size: 0x30 UMath::Vector3 fPt0; // offset 0x0, size 0xC const struct SimSurface *fSurfaceRef; // offset 0xC, size 0x4 diff --git a/src/Speed/Indep/Src/World/WPathFinder.h b/src/Speed/Indep/Src/World/WPathFinder.h new file mode 100644 index 000000000..d6183f264 --- /dev/null +++ b/src/Speed/Indep/Src/World/WPathFinder.h @@ -0,0 +1,96 @@ +#ifndef _WPATHFINDER +#define _WPATHFINDER + +#include "Speed/Indep/Src/Sim/SimActivity.h" +#include "Speed/Indep/Src/Sim/SimTypes.h" +#include "Speed/Indep/Src/World/WRoadNetwork.h" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +struct HSIMTASK__; + +extern float ASTAR_METRIC_SCALE; + +struct AStarNode : public bTNode { + static void *operator new(unsigned int size); + static void operator delete(void *ptr); + + const WRoadNode *GetRoadNode() { return WRoadNetwork::Get().GetNode(nRoadNode); } + int GetSegmentIndex() { return nSegmentIndex; } + float GetActualCost() { return static_cast(fActualCost) * ASTAR_METRIC_SCALE; } + float GetEstimatedCost() { return static_cast(fEstimatedCost) * ASTAR_METRIC_SCALE; } + float GetTotalCost() { return GetActualCost() + GetEstimatedCost(); } + + short nParentSlot; + short nSegmentIndex; + short nRoadNode; + unsigned short fActualCost; + unsigned short fEstimatedCost; +}; + +enum AStarSearchState {}; + +extern SlotPool *AStarSearchSlotPool; +extern SlotPool *AStarNodeSlotPool; + +struct AStarSearch : public bTNode { + static void *operator new(unsigned int size) { return bMalloc(AStarSearchSlotPool); } + static void operator delete(void *ptr) { bFree(AStarSearchSlotPool, ptr); } + + AStarSearch(WRoadNav *road_nav, const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, const char *shortcut_allowed); + virtual ~AStarSearch(); + bool IsGoal(AStarNode *node); + AStarNode *FindOpenNode(const WRoadNode *road_node, int segment_number); + AStarNode *FindClosedNode(const WRoadNode *road_node, int segment_number); + static int AStarCheckFlip(AStarNode *before, AStarNode *after); + float Service(float time); + bool Admissible(const WRoadSegment *segment, bool forward, WRoadNav::EPathType path_type); + bool IsFinished() { return nState > 0; } + WRoadNav *GetRoadNav() { return pRoadNav; } + + AStarSearchState nState; + AStarNode *pSolution; + int nServices; + int nSteps; + float fSearchTime; + unsigned int nShortcutCached; + unsigned int nShortcutAllowed; + const char *pShortcutAllowed; + WRoadNav *pRoadNav; + const WRoadNode *pGoalNode; + int nGoalSegment; + UMath::Vector3 vGoalPosition; + bTList lOpen; + bTList lClosed; +}; + +class PathFinder : public Sim::Activity { + public: + PathFinder(); + ~PathFinder() override; + + static PathFinder *Get() { + return pInstance; + } + + static void Set(PathFinder *instance) { + pInstance = instance; + } + + static Sim::IActivity *Construct(Sim::Param params); + void Service(float time_limit_ms); + void ServiceAll(); + bool OnTask(HSIMTASK__ *htask, float elapsed_seconds) override; + void Cancel(WRoadNav *road_nav); + AStarSearch *Pending(WRoadNav *road_nav); + AStarSearch *Submit(WRoadNav *road_nav, const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, const char *shortcut_allowed); + + private: + static PathFinder *pInstance; + + HSIMTASK__ *mSimTask; + bTList lSearches; +}; + +#endif diff --git a/src/Speed/Indep/Src/World/WRoadElem.h b/src/Speed/Indep/Src/World/WRoadElem.h index 3cdbd826f..ba8d15551 100644 --- a/src/Speed/Indep/Src/World/WRoadElem.h +++ b/src/Speed/Indep/Src/World/WRoadElem.h @@ -176,7 +176,13 @@ struct WRoadSegment { // bool RaceRouteForward() const {} - // void SetRaceRouteForward(bool forward) {} + void SetRaceRouteForward(bool forward) { + if (forward) { + fFlags = fFlags | 4; + } else { + fFlags = fFlags & static_cast(~4); + } + } // bool ShouldChopperStayLow() const {} @@ -198,7 +204,13 @@ struct WRoadSegment { return fFlags & (1 << 15); } - // void SetInRace(bool in_race) {} + void SetInRace(bool in_race) { + if (in_race) { + fFlags = fFlags | static_cast(1 << 15); + } else { + fFlags = fFlags & static_cast(~(1 << 15)); + } + } // bool IsShortcut() const {} diff --git a/src/Speed/Indep/Src/World/WRoadNetwork.h b/src/Speed/Indep/Src/World/WRoadNetwork.h index e6a3b125a..d912f8363 100644 --- a/src/Speed/Indep/Src/World/WRoadNetwork.h +++ b/src/Speed/Indep/Src/World/WRoadNetwork.h @@ -15,6 +15,7 @@ #include "types.h" extern class WRoadNetwork *fgRoadNetwork; +class WRoadNav; // total size: 0x1 class WRoadNetwork : public Debugable { @@ -43,7 +44,9 @@ class WRoadNetwork : public Debugable { ~WRoadNetwork() {} - // void SetRaceFilterValid(bool b) {} + inline void SetRaceFilterValid(bool b) { + fValidRaceFilter = b; + } bool IsRaceFilterValid() { return fValidRaceFilter; @@ -55,7 +58,9 @@ class WRoadNetwork : public Debugable { // const WRoadNode *GetNode(int index) {} - // const WRoad *GetRoad(int index) {} + const WRoad *GetRoad(int index) { + return &fRoads[index]; + } // const WRoadProfile *GetProfile(int index) {} @@ -67,7 +72,18 @@ class WRoadNetwork : public Debugable { // WRoad *GetRoadNonConst(int index) {} - // WRoadSegment *GetSegmentNonConst(int index) {} + WRoadSegment *GetSegmentNonConst(int index) { + return &fSegments[index]; + } + + void ResolveBarriers(); + void ResolveShortcuts(); + + void ResetBarriers(); + void ResetShortcuts(); + + void ResetRaceSegments(); + void AddRaceSegments(class WRoadNav *road_nav); // unsigned int GetNumRoads() {} @@ -191,6 +207,7 @@ class WRoadNav { bool FindPath(const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, char *shortcut_allowed); bool FindPathNow(const UMath::Vector3 *goal_position, const UMath::Vector3 *goal_direction, char *shortcut_allowed); bool FindingPath(); + unsigned char FirstShortcutInPath(); float GetPathDistanceRemaining(); bool IsPointInCookieTrail(const UMath::Vector3 &position_3d, float margin); bool IsSegmentInCookieTrail(int segment_number, bool use_whole_path); @@ -296,6 +313,30 @@ class WRoadNav { return fSegmentInd; } + char GetNodeInd() const { + return fNodeInd; + } + + unsigned short GetPathSegment(int n) { + return pPathSegments[n]; + } + + unsigned short *GetPathSegments() { + return pPathSegments; + } + + void SetNumPathSegments(int n) { + nPathSegments = n; + } + + int GetNumPathSegments() { + return nPathSegments; + } + + float GetSegTime() const { + return fSegTime; + } + char HitDeadEnd() const { return fDeadEnd; } diff --git a/src/Speed/Indep/Src/World/WTrigger.h b/src/Speed/Indep/Src/World/WTrigger.h index 2bc77fe21..5af649e86 100644 --- a/src/Speed/Indep/Src/World/WTrigger.h +++ b/src/Speed/Indep/Src/World/WTrigger.h @@ -5,12 +5,65 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UMath.h" #include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" +struct EventList; +struct EventStaticData; + +// total size: 0x40 +struct Trigger { + UMath::Vector4 fMatRow0Width; // offset 0x0, size 0x10 + unsigned int fType : 4; // offset 0x10 + unsigned int fShape : 4; // offset 0x10 + unsigned int fFlags : 24; // offset 0x10 + float fHeight; // offset 0x14, size 0x4 + EventList *fEvents; // offset 0x18, size 0x4 + unsigned short fIterStamp; // offset 0x1C, size 0x2 + unsigned short fFingerprint; // offset 0x1E, size 0x2 + UMath::Vector4 fMatRow2Length; // offset 0x20, size 0x10 + UMath::Vector4 fPosRadius; // offset 0x30, size 0x10 +}; + +// total size: 0x40 +class WTrigger : public Trigger { + public: + WTrigger(); + WTrigger(const UMath::Matrix4 &mat, const UMath::Vector3 &dimensions, EventList *eventList, unsigned int flags); + WTrigger(const UMath::Matrix4 &mat, float radius, float height, EventList *eventList, unsigned int flags); + ~WTrigger(); + + void FireEvents(HSIMABLE hSimable); + bool HasEvent(unsigned int eventID, const EventStaticData **foundEvent) const; + bool TestDirection(const UMath::Vector3 &vec) const; + bool TestDirection(const UMath::Vector4 *seg) const; + void UpdateBox(const UMath::Matrix4 &mat, const UMath::Vector3 &dimension); + void UpdateCylinder(const UMath::Vector3 &position, const UMath::Matrix4 &newRot, const UMath::Vector3 &dim); + bool UpdatePos(const UMath::Vector3 &newPos, unsigned int triggerInd); + + void Enable() { + fFlags |= 1; + } + + void Disable() { + fFlags &= ~1; + } + + bool IsEnabled(bool allowSilencables) const { + return (fFlags & 1) != 0; + } + + void GetCenter(UMath::Vector3 ¢er) const { + center.x = fPosRadius.x; + center.y = fPosRadius.y; + center.z = fPosRadius.z; + } +}; + // total size: 0x8 struct FireOnExitRec { - class WTrigger &mTrigger; // offset 0x0, size 0x4 - HSIMABLE mhSimable; // offset 0x4, size 0x4 + WTrigger &mTrigger; // offset 0x0, size 0x4 + HSIMABLE mhSimable; // offset 0x4, size 0x4 }; // total size: 0x10 @@ -20,6 +73,11 @@ class FireOnExitList : public std::set {}; class WTriggerManager { public: void Update(float dT); + void ClearAllFireOnExit(); + + static bool Exists() { + return fgTriggerManager != nullptr; + } static WTriggerManager &Get() { return *fgTriggerManager; diff --git a/src/Speed/Indep/Src/World/WWorldMath.h b/src/Speed/Indep/Src/World/WWorldMath.h index aa0211bc1..91417e485 100644 --- a/src/Speed/Indep/Src/World/WWorldMath.h +++ b/src/Speed/Indep/Src/World/WWorldMath.h @@ -8,6 +8,7 @@ namespace WWorldMath { bool IntersectCircle(float x1, float y1, float x2, float y2, float cx, float cy, float r, float &u1, float &u2); +float GetPlaneY(const UMath::Vector3 &normal, const UMath::Vector3 &pointOnPlane, const UMath::Vector3 &testPoint); }; diff --git a/src/Speed/Indep/Src/World/WWorldPos.h b/src/Speed/Indep/Src/World/WWorldPos.h index b1c11652d..6126a97e5 100644 --- a/src/Speed/Indep/Src/World/WWorldPos.h +++ b/src/Speed/Indep/Src/World/WWorldPos.h @@ -9,8 +9,7 @@ #include "Speed/Indep/Src/World/WCollisionTri.h" // total size: 0x3C -class WWorldPos { - public: +struct WWorldPos { void MakeFaceAtPoint(const UMath::Vector3 &inPoint); bool FindClosestFace(const WCollider *collider, const UMath::Vector3 &ptRaw, bool quitIfOnSameFace); bool FindClosestFace(const UMath::Vector3 &ptRaw, bool quitIfOnSameFace); @@ -33,18 +32,18 @@ class WWorldPos { } WWorldPos(float yOffset) { - this->fFaceValid = 0; - this->fMissCount = 0; - this->fUsageCount = 0; - this->fYOffset = yOffset; - this->fSurface = nullptr; + fFaceValid = 0; + fMissCount = 0; + fUsageCount = 0; + fYOffset = yOffset; + fSurface = nullptr; } ~WWorldPos() {} // bool OffEdge() const {} - // bool OnValidFace() const {} + bool OnValidFace() const { return fFaceValid; } void ForceFaceValidity() {} @@ -54,11 +53,15 @@ class WWorldPos { fYOffset = liftAmount; } - void UNormal(UMath::Vector3 *norm) const {} + void UNormal(UMath::Vector3 *norm) const { + fFace.GetNormal(norm); + } void UNormal(UMath::Vector4 *norm) const {} - // const UMath::Vector4 &FacePoint(int ptInd) const {} + const UMath::Vector4 &FacePoint(int ptInd) const { + return reinterpret_cast(&fFace)[ptInd]; + } const Attrib::Collection *GetSurface() const { return fSurface; diff --git a/src/Speed/Indep/Src/World/World.hpp b/src/Speed/Indep/Src/World/World.hpp index 0f0115f73..4569b789f 100644 --- a/src/Speed/Indep/Src/World/World.hpp +++ b/src/Speed/Indep/Src/World/World.hpp @@ -57,6 +57,7 @@ enum eTimeOfDay { }; extern int g_tweakIsDriftRace; +extern int g_tweakIsBurnout; void ServiceSpaceNodes(); void ResetWorldTime(); diff --git a/src/Speed/Indep/Src/World/WorldConn.h b/src/Speed/Indep/Src/World/WorldConn.h index f0301ef9c..df6f4e839 100644 --- a/src/Speed/Indep/Src/World/WorldConn.h +++ b/src/Speed/Indep/Src/World/WorldConn.h @@ -18,6 +18,7 @@ namespace WorldConn { // total size: 0x10 class Reference { public: + Reference() : mWorldID(0), mMatrix(nullptr), mVelocity(nullptr), mAcceleration(nullptr) {} Reference(unsigned int); ~Reference(); void Set(unsigned int); @@ -29,6 +30,18 @@ class Reference { return mMatrix; } + const bVector3 *GetVelocity() const { + return mVelocity; + } + + const bVector3 *GetAcceleration() const { + return mAcceleration; + } + + unsigned int GetWorldID() const { + return mWorldID; + } + private: unsigned int mWorldID; // offset 0x0, size 0x4 const bMatrix4 *mMatrix; // offset 0x4, size 0x4 diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h index 6633f4de8..9ab69bda9 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h @@ -26,6 +26,19 @@ class StringKey { mString = str; } + StringKey(const StringKey &src) { + mHash64 = src.mHash64; + mHash32 = src.mHash32; + mString = src.mString; + } + + const StringKey &operator=(const StringKey &rhs) { + mString = rhs.mString; + mHash64 = rhs.mHash64; + mHash32 = rhs.mHash32; + return *this; + } + bool operator==(const StringKey &rhs) const { return mHash64 == rhs.mHash64; } @@ -46,8 +59,18 @@ class StringKey { return mString != nullptr; } + bool IsEmpty() const { + if (mString != nullptr) { + return *mString == '\0'; + } + return true; + } + + const char *GetString() const { + return mString; + } + private: - // total size: 0x10 unsigned long long mHash64; // offset 0x0, size 0x8 unsigned int mHash32; // offset 0x8, size 0x4 const char *mString; // offset 0xC, size 0x4 diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h index f458308a4..0ce008b66 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h @@ -666,7 +666,18 @@ class Attribute { bool SetLength(unsigned int); void SendChangeMsg() const; // TODO + template const T &Get(unsigned int index) const; template const T &Get(unsigned int index, T &result) const; + template bool Set(unsigned int index, const T &input) { + T *resultptr = reinterpret_cast(GetElementPointer(index)); + + if (resultptr) { + *resultptr = input; + return true; + } + + return false; + } void operator delete(void *ptr, std::size_t bytes) { Free(ptr, bytes, "Attrib::Attribute"); @@ -911,7 +922,19 @@ template class TAttrib : public Attribute { TAttrib(const Attribute &src) : Attribute(src) {} ~TAttrib() {} - bool &Get(unsigned int index) const; + const t &Get(unsigned int index) const { + const t *resultptr = reinterpret_cast(GetElementPointer(index)); + + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(t))); + } + + return *resultptr; + } + + bool Set(unsigned int index, const t &data) { + return Attribute::Set(index, data); + } }; } // namespace Attrib diff --git a/src/Speed/Indep/Tools/Inc/ConversionUtil.hpp b/src/Speed/Indep/Tools/Inc/ConversionUtil.hpp index 780e5a3c0..281290078 100644 --- a/src/Speed/Indep/Tools/Inc/ConversionUtil.hpp +++ b/src/Speed/Indep/Tools/Inc/ConversionUtil.hpp @@ -67,6 +67,10 @@ inline float INCH2METERS(const float _inches_) { return _inches_ * 0.0254f; } +inline float MILE2METERS(const float _mi_) { + return _mi_ * 1609.34f; +} + inline Rpm RPS2RPM(const float _rps_) { return _rps_ * 9.549296f; // TODO problems on PS2 } @@ -99,4 +103,66 @@ inline Mps KPH2MPS(Kph x) { return x / 3.6f; } +namespace ConversionUtil { + +template +void Copy4(T2 &out, const T1 &in) { + out.x = in.x; + out.y = in.y; + out.z = in.z; + out.w = in.w; +} + +template +void Scale3(T &v, float s) { + v.x *= s; + v.y *= s; + v.z *= s; +} + +template +T Make4(float x, float y, float z, float w) { + T v; + v.x = x; + v.y = y; + v.z = z; + v.w = w; + return v; +} + +template +T Make3(float x, float y, float z) { + T v; + v.x = x; + v.y = y; + v.z = z; + return v; +} + +template +void RightToLeftVector4(const T1 &in, T2 &out) { + out = Make4(-in.y, in.z, in.x, in.w); +} + +template +void RightToLeftVector3(const T1 &in, T2 &out) { + out = Make3(-in.y, in.z, in.x); +} + +template +void RightToLeftMatrix4(const T1 &in, T2 &out) { + T2 tmp; + Copy4(tmp[0], in[1]); + Copy4(tmp[1], in[2]); + Copy4(tmp[2], in[0]); + Copy4(tmp[3], in[3]); + Scale3(tmp[0], -1.0f); + RightToLeftVector4(tmp[0], out[0]); + RightToLeftVector4(tmp[1], out[1]); + RightToLeftVector4(tmp[2], out[2]); + RightToLeftVector4(tmp[3], out[3]); +} + +} // namespace ConversionUtil + #endif diff --git a/src/Speed/Indep/bWare/Inc/bMath.hpp b/src/Speed/Indep/bWare/Inc/bMath.hpp index da7b7cc80..9c1af2866 100644 --- a/src/Speed/Indep/bWare/Inc/bMath.hpp +++ b/src/Speed/Indep/bWare/Inc/bMath.hpp @@ -194,6 +194,10 @@ inline float bAngToRad(unsigned short angle) { return ((float)angle) * 0.0000958738f; } +inline float SignExtendAng(unsigned short angle) { + return static_cast(static_cast(angle)); +} + inline float bDegToRad(float degrees) { return degrees * 0.017453292f; } @@ -292,6 +296,10 @@ inline float bLength(const bVector2 *v) { return bSqrt(x * x + y * y); } +inline float bLength(const bVector2 &v) { + return bLength(&v); +} + inline bVector2 bNormalize(const bVector2 &v) { bVector2 dest; bNormalize(&dest, &v); @@ -330,14 +338,16 @@ struct ALIGN_16 bVector3 { bVector3 operator-() {} - bVector3 operator*(float f) {} + bVector3 operator*(float f) const; - bVector3 &operator-=(const bVector3 &v) {} + bVector3 &operator-=(const bVector3 &v); }; bVector3 *bNormalize(bVector3 *dest, const bVector3 *v); bVector3 *bNormalize(bVector3 *dest, const bVector3 *v, float length); bVector3 *bScaleAdd(bVector3 *dest, const bVector3 *v1, const bVector3 *v2, float scale); +bVector3 *bCross(bVector3 *dest, const bVector3 *v1, const bVector3 *v2); +inline bVector3 bScale(const bVector3 &v, float scale); inline bVector3 *bFill(bVector3 *dest, float x, float y, float z) { dest->x = x; @@ -424,6 +434,11 @@ inline bVector3 &bVector3::operator+=(const bVector3 &v) { return *this; } +inline bVector3 &bVector3::operator-=(const bVector3 &v) { + bSub(this, this, &v); + return *this; +} + inline bVector3 bVector3::operator+(const bVector3 &v) const { return bAdd(*this, v); } @@ -491,10 +506,18 @@ inline float bDistBetween(const bVector3 &v1, const bVector3 &v2) { inline bVector3 bScale(const bVector3 &v, float scale) { bVector3 dest; + bScale(&dest, &v, scale); + return dest; +} + +inline bVector3 bVector3::operator*(float f) const { + return bScale(*this, f); } inline bVector3 bScale(const bVector3 &v1, const bVector3 &v2) { bVector3 dest; + bScale(&dest, &v1, &v2); + return dest; } inline bVector3 bScaleAdd(const bVector3 &v1, const bVector3 &v2, float scale) { @@ -503,10 +526,14 @@ inline bVector3 bScaleAdd(const bVector3 &v1, const bVector3 &v2, float scale) { inline bVector3 bNormalize(const bVector3 &v) { bVector3 dest; + bNormalize(&dest, &v); + return dest; } inline bVector3 bNormalize(const bVector3 &v, float length) { bVector3 dest; + bNormalize(&dest, &v, length); + return dest; } inline bVector3 bMin(const bVector3 &v1, const bVector3 &v2) { @@ -685,10 +712,15 @@ inline bVector4 *bScale(bVector4 *dest, const bVector4 *v, float scale) { } inline bVector4 *bScale(bVector4 *dest, const bVector4 *v1, const bVector4 *v2) { - float x; - float y; - float z; - float w; + float x = v1->x; + float y = v1->y; + float z = v1->z; + float w = v1->w; + dest->x = x * v2->x; + dest->y = y * v2->y; + dest->z = z * v2->z; + dest->w = w * v2->w; + return dest; } inline bVector4 *bMin(bVector4 *dest, const bVector4 *v1, const bVector4 *v2) {} @@ -892,13 +924,21 @@ inline bMatrix4 &bMatrix4::operator=(const bMatrix4 &m) { // UNUSED inline bMatrix4 *bCopy(bMatrix4 *dest, const bMatrix4 *v, const struct bVector4 *position) {} -inline bMatrix4 *bCopy(bMatrix4 *dest, const bMatrix4 *v, const struct bVector3 *position) {} +inline bMatrix4 *bCopy(bMatrix4 *dest, const bMatrix4 *v, const struct bVector3 *position) { + dest->v0 = v->v0; + dest->v1 = v->v1; + dest->v2 = v->v2; + bCopy(&dest->v3, position, 1.0f); + return dest; +} void bMulMatrix(bMatrix4 *dest, const bMatrix4 *a, const bMatrix4 *b); +void bMulMatrix(bVector4 *dest, const bMatrix4 *a, const bVector4 *b); void bMulMatrix(bVector3 *dest, const bMatrix4 *a, const bVector3 *b); bMatrix4 *bTransposeMatrix(bMatrix4 *dest, const bMatrix4 *m); void bInvertMatrix(bMatrix4 *dest, const bMatrix4 *src); +void bConvertFromBond(bMatrix4 &dest, const bMatrix4 &m); struct bQuaternion { // total size: 0x10 @@ -917,6 +957,8 @@ struct bQuaternion { } bQuaternion &Slerp(bQuaternion &r, const bQuaternion &target, float t) const; + + void GetMatrix(bMatrix4 &mat) const; }; class bBitTable { diff --git a/tools/build_matrix.py b/tools/build_matrix.py new file mode 100644 index 000000000..135bce18c --- /dev/null +++ b/tools/build_matrix.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 + +""" +Run sequential build checks across supported platforms. + +Examples: + python tools/build_matrix.py + python tools/build_matrix.py --version GOWE69 --version SLES-53558-A124 + python tools/build_matrix.py --all-source +""" + +import argparse +import os +import subprocess +import sys +import time +from dataclasses import dataclass +from typing import List, Optional, Sequence + + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +ROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..")) +DEFAULT_RESTORE_VERSION = "GOWE69" + + +@dataclass(frozen=True) +class PlatformCheck: + version: str + label: str + required_assets: Sequence[str] + + +@dataclass +class StepResult: + name: str + command: List[str] + returncode: int + elapsed: float + log_path: str + output: str + + @property + def ok(self) -> bool: + return self.returncode == 0 + + +@dataclass +class PlatformResult: + platform: PlatformCheck + configure: Optional[StepResult] = None + build: Optional[StepResult] = None + preflight_error: Optional[str] = None + + @property + def ok(self) -> bool: + return ( + self.preflight_error is None + and self.configure is not None + and self.configure.ok + and self.build is not None + and self.build.ok + ) + + +PLATFORMS = ( + PlatformCheck( + version="GOWE69", + label="GameCube", + required_assets=("orig/GOWE69/NFSMWRELEASE.ELF",), + ), + PlatformCheck( + version="EUROPEGERMILESTONE", + label="Xbox 360", + required_assets=("orig/EUROPEGERMILESTONE/NfsMWEuropeGerMilestone.xex",), + ), + PlatformCheck( + version="SLES-53558-A124", + label="PS2", + required_assets=("orig/SLES-53558-A124/NFS.ELF",), + ), +) + +PLATFORM_BY_VERSION = {platform.version: platform for platform in PLATFORMS} + + +def print_section(title: str) -> None: + print(f"\n== {title} ==", flush=True) + + +def tail_lines(text: str, count: int) -> str: + lines = text.rstrip().splitlines() + if len(lines) <= count: + return "\n".join(lines) + return "\n".join(lines[-count:]) + + +def run_logged(command: List[str], log_path: str) -> StepResult: + start = time.monotonic() + try: + completed = subprocess.run( + command, + cwd=ROOT_DIR, + capture_output=True, + text=True, + errors="replace", + ) + output = completed.stdout + if completed.stderr: + if output and not output.endswith("\n"): + output += "\n" + output += completed.stderr + returncode = completed.returncode + except OSError as exc: + output = str(exc) + returncode = 127 + elapsed = time.monotonic() - start + + os.makedirs(os.path.dirname(log_path), exist_ok=True) + with open(log_path, "w", encoding="utf-8") as log_file: + log_file.write(output) + + return StepResult( + name=os.path.basename(log_path), + command=command, + returncode=returncode, + elapsed=elapsed, + log_path=log_path, + output=output, + ) + + +def missing_assets(platform: PlatformCheck) -> List[str]: + missing = [] + for rel_path in platform.required_assets: + abs_path = os.path.join(ROOT_DIR, rel_path) + if not os.path.exists(abs_path): + missing.append(rel_path) + return missing + + +def describe_failure(step: StepResult, tail_count: int) -> None: + print(f"FAIL {step.name}: exit {step.returncode} in {step.elapsed:.2f}s") + print(f"Command: {' '.join(step.command)}") + print(f"Log: {step.log_path}") + if step.output.strip(): + print("--- output tail ---") + print(tail_lines(step.output, tail_count)) + + +def run_platform( + platform: PlatformCheck, build_target: Optional[str], jobs: int, tail_count: int +) -> PlatformResult: + result = PlatformResult(platform=platform) + logs_dir = os.path.join(ROOT_DIR, "build", platform.version, "logs") + + print_section(f"{platform.label} ({platform.version})") + + missing = missing_assets(platform) + if missing: + result.preflight_error = ( + "Missing required assets: " + + ", ".join(missing) + + " (hint: seed shared assets or run worktree bootstrap first)" + ) + print(f"FAIL preflight: {result.preflight_error}") + return result + + configure_cmd = [sys.executable, "configure.py", "--version", platform.version] + configure_log = os.path.join(logs_dir, "build-matrix-configure.log") + print(f"RUN configure: {' '.join(configure_cmd)}") + result.configure = run_logged(configure_cmd, configure_log) + if result.configure.ok: + print(f"OK configure: {result.configure.elapsed:.2f}s ({configure_log})") + else: + describe_failure(result.configure, tail_count) + return result + + build_cmd = ["ninja", "-j", str(jobs)] + if build_target is not None: + build_cmd.append(build_target) + build_name = build_target or "default" + build_log = os.path.join(logs_dir, f"build-matrix-{build_name}.log") + print(f"RUN build: {' '.join(build_cmd)}") + result.build = run_logged(build_cmd, build_log) + if result.build.ok: + print(f"OK build: {result.build.elapsed:.2f}s ({build_log})") + else: + describe_failure(result.build, tail_count) + + return result + + +def restore_version(version: str, tail_count: int) -> bool: + print_section(f"Restore {version}") + log_path = os.path.join(ROOT_DIR, "build", version, "logs", "build-matrix-restore.log") + step = run_logged([sys.executable, "configure.py", "--version", version], log_path) + if step.ok: + print(f"OK restore: {step.elapsed:.2f}s ({log_path})") + return True + + describe_failure(step, tail_count) + return False + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Check sequential builds across all supported platforms." + ) + parser.add_argument( + "--version", + dest="versions", + action="append", + choices=sorted(PLATFORM_BY_VERSION.keys()), + help="Limit the run to one or more versions (default: all platforms).", + ) + parser.add_argument( + "--all-source", + action="store_true", + help="Run `ninja all_source` instead of the default full `ninja`.", + ) + parser.add_argument( + "--jobs", + type=int, + default=1, + help="Parallelism passed to ninja (default: 1).", + ) + parser.add_argument( + "--tail", + type=int, + default=40, + help="How many output lines to print when a step fails (default: 40).", + ) + parser.add_argument( + "--restore-version", + default=DEFAULT_RESTORE_VERSION, + choices=sorted(PLATFORM_BY_VERSION.keys()), + help=f"Version to restore at the end (default: {DEFAULT_RESTORE_VERSION}).", + ) + parser.add_argument( + "--no-restore", + action="store_true", + help="Leave the worktree configured for the last checked version.", + ) + return parser.parse_args() + + +def print_summary( + results: Sequence[PlatformResult], restore_version_name: str, restore_ok: Optional[bool] +) -> None: + print_section("Summary") + for result in results: + if result.preflight_error is not None: + print(f"FAIL {result.platform.version}: {result.preflight_error}") + continue + if result.configure is None or not result.configure.ok: + assert result.configure is not None + print( + f"FAIL {result.platform.version}: configure exit {result.configure.returncode} " + f"({result.configure.elapsed:.2f}s)" + ) + continue + if result.build is None or not result.build.ok: + assert result.build is not None + print( + f"FAIL {result.platform.version}: build exit {result.build.returncode} " + f"({result.build.elapsed:.2f}s)" + ) + continue + total = result.configure.elapsed + result.build.elapsed + print(f"OK {result.platform.version}: {total:.2f}s") + + if restore_ok is not None: + status = "OK" if restore_ok else "FAIL" + print(f"{status:4} restore: {restore_version_name}") + + +args = parse_args() + + +def main() -> int: + selected_versions = args.versions or [platform.version for platform in PLATFORMS] + platforms = [PLATFORM_BY_VERSION[version] for version in selected_versions] + build_target = "all_source" if args.all_source else None + results: List[PlatformResult] = [] + restore_ok: Optional[bool] = None + + print(f"Root: {ROOT_DIR}") + print(f"Build target: {build_target or 'ninja default'}") + + try: + for platform in platforms: + results.append(run_platform(platform, build_target, args.jobs, args.tail)) + finally: + if not args.no_restore: + restore_ok = restore_version(args.restore_version, args.tail) + + print_summary(results, args.restore_version, restore_ok) + + if restore_ok is False: + return 1 + if any(not result.ok for result in results): + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/code_style.py b/tools/code_style.py index ecb85f713..61c3f2ee3 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -84,6 +84,7 @@ class Finding: ) USING_NAMESPACE_PATTERN = re.compile(r"^\s*using\s+namespace\b") NULL_PATTERN = re.compile(r"\bNULL\b") +BARE_PRESENCE_IF_PATTERN = re.compile(r"^\s*#if\s+([A-Za-z_][A-Za-z0-9_]*)\s*$") HEADER_GUARD_IFNDEF_PATTERN = re.compile(r"^\s*#ifndef\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) HEADER_GUARD_DEFINE_PATTERN = re.compile(r"^\s*#define\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) EA_PRAGMA_BLOCK_PATTERN = re.compile( @@ -92,6 +93,16 @@ class Finding: r".*?^\s*#endif\s*$", re.MULTILINE | re.DOTALL, ) +EA_PRAGMA_IFDEF_PATTERN = re.compile( + r"^\s*#ifdef\s+EA_PRAGMA_ONCE_SUPPORTED\s*$", re.MULTILINE +) +RECOVERED_LAYOUT_COMMENT_PATTERN = re.compile( + r"//\s*offset 0x[0-9A-Fa-f]+,\s*size 0x[0-9A-Fa-f]+" +) +RECOVERED_NARROW_UNSIGNED_PATTERN = re.compile(r"\bunsigned\s+(char|short)\b") +BARE_RECOVERY_MARKER_PATTERN = re.compile( + r"//\s*(TODO|UNSOLVED|STRIPPED)\b(?:\s*[.:,-]*)?\s*$" +) SUSPICIOUS_MEMBER_PATTERN = re.compile( r"^(?:" r"_?pad(?:ding)?[0-9A-Fa-f_]*" @@ -441,6 +452,16 @@ def audit_style_guide_rules( if touched_lines is not None and idx not in touched_lines: continue stripped = line.strip() + bare_recovery_marker_match = BARE_RECOVERY_MARKER_PATTERN.search(line) + if bare_recovery_marker_match is not None: + findings.append( + Finding( + path, + idx, + "INFO", + f"`// {bare_recovery_marker_match.group(1)}` has no context; add a short reason or remove the stale recovery marker", + ) + ) if stripped.startswith("//"): continue @@ -471,9 +492,35 @@ def audit_style_guide_rules( "use `nullptr` instead of `NULL`", ) ) + bare_presence_if_match = BARE_PRESENCE_IF_PATTERN.match(line) + if bare_presence_if_match is not None: + findings.append( + Finding( + path, + idx, + "WARN", + f"bare `#if {bare_presence_if_match.group(1)}` looks like a presence check; prefer `#ifdef {bare_presence_if_match.group(1)}` unless a numeric test is intentional", + ) + ) + narrow_type_match = RECOVERED_NARROW_UNSIGNED_PATTERN.search(line) + if ( + narrow_type_match is not None + and RECOVERED_LAYOUT_COMMENT_PATTERN.search(line) is not None + ): + preferred = "uint8" if narrow_type_match.group(1) == "char" else "uint16" + findings.append( + Finding( + path, + idx, + "INFO", + f"recovered layout member uses `{narrow_type_match.group(0)}`; prefer explicit-width `{preferred}` when the field width is known", + ) + ) if ext in HEADER_EXTS: - should_check_guard = touched_lines is None or any(line_no <= 8 for line_no in touched_lines) + should_check_guard = touched_lines is None or any( + line_no <= 12 for line_no in touched_lines + ) if should_check_guard: has_ifndef = HEADER_GUARD_IFNDEF_PATTERN.search(text) is not None has_define = HEADER_GUARD_DEFINE_PATTERN.search(text) is not None @@ -487,6 +534,20 @@ def audit_style_guide_rules( "header guard should use `#ifndef` / `#define` plus the `EA_PRAGMA_ONCE_SUPPORTED` `#pragma once` block", ) ) + pragma_ifdef_match = EA_PRAGMA_IFDEF_PATTERN.search(text) + if pragma_ifdef_match is not None: + pragma_ifdef_line = text[: pragma_ifdef_match.start()].count("\n") + 1 + for idx, line in enumerate(text.splitlines(), 1): + if line.strip().startswith("#include ") and idx < pragma_ifdef_line: + findings.append( + Finding( + path, + idx, + "WARN", + "header include appears before the `EA_PRAGMA_ONCE_SUPPORTED` block; keep the guard / pragma block ahead of includes", + ) + ) + break return findings diff --git a/tools/decomp-diff.py b/tools/decomp-diff.py index 5ad03c640..2fc7bbb96 100644 --- a/tools/decomp-diff.py +++ b/tools/decomp-diff.py @@ -19,6 +19,7 @@ import sys from typing import Any, Dict, List, Optional, Tuple from _common import ( + RELOC_DIFF_CHOICES, ROOT_DIR, RELOC_DIFF_CHOICES, ToolError, diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index 84f2f83d5..78e7fee04 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -16,12 +16,15 @@ python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --no-source python tools/decomp-workflow.py diff -u main/Speed/Indep/SourceLists/zCamera -d UpdateAll --reloc-diffs all python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll + python tools/decomp-workflow.py dwarf-scan -u main/Speed/Indep/SourceLists/zCamera + python tools/decomp-workflow.py dwarf-scan -u main/Speed/Indep/SourceLists/zCamera --objdiff-status match python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zAttribSys -f 'Attrib::Class::RemoveCollection(Attrib::Collection *)' --full-diff python tools/decomp-workflow.py verify -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll python tools/decomp-workflow.py unit -u main/Speed/Indep/SourceLists/zCamera """ import argparse +import difflib import json import re import os @@ -45,6 +48,8 @@ make_abs, run_objdiff_json, ) +from lookup import _candidate_func_names, _sig_contains_name, read_text, split_functions +from split_dwarf_info import apply_umath_fixups SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) @@ -56,24 +61,51 @@ PS2_SYMBOLS = os.path.join(ROOT_DIR, "config", "SLES-53558-A124", "symbols.txt") GC_DWARF = os.path.join(ROOT_DIR, "symbols", "Dwarf") DEBUG_LINES = os.path.join(ROOT_DIR, "symbols", "debug_lines.txt") +X360_COMPILER_DIR = os.path.join(ROOT_DIR, "build", "compilers", "X360", "14.00.2110") +PS2_COMPILER_DIR = os.path.join(ROOT_DIR, "build", "compilers", "PS2", "ee-gcc2.9-991111") +MIPS_BINUTILS_DIR = os.path.join(ROOT_DIR, "build", "mips_binutils") DEFAULT_SMOKE_UNIT = "main/Speed/Indep/SourceLists/zCamera" DEBUG_SYMBOL_PROBE_MANGLED = "UpdateAll__6Cameraf" DEBUG_SYMBOL_PROBE_DEMANGLED = "Camera::UpdateAll(float)" DEBUG_SYMBOL_PROBE_GC_ADDR = "0x80065A84" REBUILT_DEBUG_LINE_RE = re.compile(r"^\s*([0-9A-Fa-f]+)\s*:") +DWARF_HEX_RE = re.compile(r"0x[0-9A-Fa-f]+") LOW_MATCH_PRIORITY_THRESHOLD = 60.0 VERY_LOW_MATCH_PRIORITY_THRESHOLD = 40.0 HIGH_MATCH_CLEANUP_THRESHOLD = 85.0 VERY_HIGH_MATCH_CLEANUP_THRESHOLD = 95.0 +FunctionBlock = Tuple[str, str, str, str] SHARED_ASSET_REQUIREMENTS = [ (os.path.join("build", "tools"), "downloaded tooling"), (os.path.join("orig", "GOWE69", "NFSMWRELEASE.ELF"), "GameCube original ELF"), + ( + os.path.join("orig", "EUROPEGERMILESTONE", "NfsMWEuropeGerMilestone.xex"), + "Xbox original XEX", + ), (os.path.join("orig", "SLES-53558-A124", "NFS.ELF"), "PS2 original ELF"), (os.path.join("symbols", "Dwarf"), "DWARF dump"), ] +PLATFORM_BUILD_REQUIREMENTS = [ + ( + "x360-compiler", + X360_COMPILER_DIR, + "missing (seed build/compilers in this worktree for Xbox builds)", + ), + ( + "ps2-compiler", + PS2_COMPILER_DIR, + "missing (seed build/compilers in this worktree for PS2 builds)", + ), + ( + "ps2-binutils", + MIPS_BINUTILS_DIR, + "missing (seed build/mips_binutils in this worktree for PS2 builds)", + ), +] + class WorkflowError(RuntimeError): pass @@ -292,6 +324,14 @@ def choose_objdiff_row(unit_name: str, function_name: str, reloc_diffs: str = "n return matches[0] +def resolve_exact_function_name( + unit_name: str, function_name: str, reloc_diffs: str = "none" +) -> str: + return str( + choose_objdiff_row(unit_name, function_name, reloc_diffs=reloc_diffs)["name"] + ) + + def load_dwarf_report( unit_name: str, function_name: str, @@ -307,6 +347,355 @@ def load_dwarf_report( raise WorkflowError(f"dwarf-compare.py returned invalid JSON: {e}") +def load_dwarf_blocks( + path: str, folder_mode: bool, apply_split_fixups_in_ram: bool = False +) -> List[FunctionBlock]: + if folder_mode: + text = read_text(os.path.join(path, "functions.nothpp")) + else: + text = read_text(path) + if apply_split_fixups_in_ram: + text = apply_umath_fixups(text) + return split_functions(text) + + +def find_dwarf_function_blocks( + funcs: Sequence[FunctionBlock], query: str +) -> List[FunctionBlock]: + candidates = _candidate_func_names(query) + exact_matches: List[FunctionBlock] = [] + fuzzy_matches: List[FunctionBlock] = [] + + for func in funcs: + sig_line = func[2] + if sig_line == query: + exact_matches.append(func) + elif any(_sig_contains_name(sig_line, candidate) for candidate in candidates): + fuzzy_matches.append(func) + + if exact_matches: + return exact_matches + return fuzzy_matches + + +def choose_dwarf_function_block( + funcs: Sequence[FunctionBlock], query: str, label: str +) -> FunctionBlock: + matches = find_dwarf_function_blocks(funcs, query) + if not matches: + raise WorkflowError(f"{label}: function '{query}' not found.") + if len(matches) > 1: + preview = "\n".join(f" - {match[2]}" for match in matches[:8]) + extra = "" + if len(matches) > 8: + extra = f"\n ... {len(matches) - 8} more" + raise WorkflowError( + f"{label}: function query '{query}' matched multiple DWARF blocks.\n" + f"Use a more specific function name.\n{preview}{extra}" + ) + return matches[0] + + +def normalize_dwarf_line(line: str) -> str: + stripped = line.rstrip("\n").rstrip() + if stripped.startswith("// Range:"): + return "// Range: " + return DWARF_HEX_RE.sub("0xADDR", stripped) + + +def normalize_dwarf_block(block: str) -> List[str]: + return [normalize_dwarf_line(line) for line in block.splitlines()] + + +def count_dwarf_opcodes( + opcodes: Sequence[Tuple[str, int, int, int, int]] +) -> Dict[str, int]: + matching = 0 + original_only = 0 + rebuilt_only = 0 + changed_groups = 0 + for tag, i1, i2, j1, j2 in opcodes: + if tag == "equal": + matching += i2 - i1 + continue + changed_groups += 1 + if tag in ("replace", "delete"): + original_only += i2 - i1 + if tag in ("replace", "insert"): + rebuilt_only += j2 - j1 + return { + "matching_lines": matching, + "original_only_lines": original_only, + "rebuilt_only_lines": rebuilt_only, + "changed_groups": changed_groups, + } + + +def build_dwarf_scan_row( + row: Dict[str, Any], + original_funcs: Sequence[FunctionBlock], + rebuilt_funcs: Sequence[FunctionBlock], +) -> Dict[str, Any]: + function_name = str(row["name"]) + result: Dict[str, Any] = { + "function": function_name, + "symbol_name": row["symbol_name"], + "objdiff_status": row["status"], + "objdiff_match_percent": row["match_percent"], + "unmatched_bytes_est": row["unmatched_bytes_est"], + "size": row["size"], + } + + try: + original_block = choose_dwarf_function_block( + original_funcs, function_name, "original DWARF" + ) + rebuilt_block = choose_dwarf_function_block( + rebuilt_funcs, function_name, "rebuilt DWARF" + ) + original_lines = normalize_dwarf_block(original_block[3]) + rebuilt_lines = normalize_dwarf_block(rebuilt_block[3]) + matcher = difflib.SequenceMatcher(a=original_lines, b=rebuilt_lines) + counts = count_dwarf_opcodes(matcher.get_opcodes()) + total_lines = max(len(original_lines), len(rebuilt_lines), 1) + result.update( + { + "dwarf_status": "exact" + if original_lines == rebuilt_lines + else "mismatch", + "dwarf_match_percent": 100.0 * counts["matching_lines"] / total_lines, + "changed_groups": counts["changed_groups"], + "matching_lines": counts["matching_lines"], + "total_lines": total_lines, + "original_line_count": len(original_lines), + "rebuilt_line_count": len(rebuilt_lines), + "signature_match": normalize_dwarf_line(original_block[2]) + == normalize_dwarf_line(rebuilt_block[2]), + } + ) + except WorkflowError as e: + result.update( + { + "dwarf_status": "error", + "dwarf_match_percent": None, + "changed_groups": None, + "matching_lines": None, + "total_lines": None, + "original_line_count": None, + "rebuilt_line_count": None, + "signature_match": None, + "error": str(e), + } + ) + return result + + +def filter_dwarf_scan_rows( + rows: Sequence[Dict[str, Any]], dwarf_status: str +) -> List[Dict[str, Any]]: + if dwarf_status == "all": + return list(rows) + if dwarf_status == "problem": + return [row for row in rows if row["dwarf_status"] in ("mismatch", "error")] + return [row for row in rows if row["dwarf_status"] == dwarf_status] + + +def filter_dwarf_signature_rows( + rows: Sequence[Dict[str, Any]], signature_status: str +) -> List[Dict[str, Any]]: + if signature_status == "all": + return list(rows) + want_match = signature_status == "match" + return [ + row + for row in rows + if row.get("signature_match") is not None + and bool(row["signature_match"]) == want_match + ] + + +def sort_dwarf_scan_rows(rows: List[Dict[str, Any]]) -> None: + status_rank = {"error": 0, "mismatch": 1, "exact": 2} + rows.sort( + key=lambda row: ( + status_rank.get(str(row["dwarf_status"]), 3), + row["dwarf_match_percent"] + if row["dwarf_match_percent"] is not None + else -1.0, + 0 + if row.get("signature_match") is True + else 1 + if row.get("signature_match") is False + else 2, + -(row["changed_groups"] or 0), + -(row["unmatched_bytes_est"] or 0), + row["objdiff_match_percent"] + if row["objdiff_match_percent"] is not None + else -1.0, + row["function"].lower(), + ) + ) + + +def command_dwarf_scan(args: argparse.Namespace) -> None: + ensure_decomp_prereqs() + if not args.json: + print_section(f"DWARF Scan: {args.unit}") + ensure_shared_unit_output(args.unit) + + rebuilt_dwarf_path = ( + os.path.abspath(args.rebuilt_dwarf_file) if args.rebuilt_dwarf_file else None + ) + cleanup_rebuilt_dwarf = False + try: + if not rebuilt_dwarf_path: + rebuilt_dwarf_path = dtk_dwarf_dump(get_unit_build_output(args.unit)) + cleanup_rebuilt_dwarf = True + + data = run_objdiff_json( + OBJDIFF_CLI, + args.unit, + reloc_diffs=args.reloc_diffs, + root_dir=ROOT_DIR, + ) + rows = [ + row + for row in build_objdiff_symbol_rows(data) + if row["type"] == "function" and row["side"] == "left" + ] + if args.objdiff_status != "all": + rows = [row for row in rows if row["status"] == args.objdiff_status] + if args.search: + rows = [ + row + for row in rows + if fuzzy_match(args.search, row["name"]) + or fuzzy_match(args.search, row["symbol_name"]) + ] + if not rows: + raise WorkflowError("No functions match the given filters.") + + original_funcs = load_dwarf_blocks(GC_DWARF, folder_mode=True) + rebuilt_funcs = load_dwarf_blocks( + rebuilt_dwarf_path, folder_mode=False, apply_split_fixups_in_ram=True + ) + scan_rows = [ + build_dwarf_scan_row(row, original_funcs, rebuilt_funcs) for row in rows + ] + + summary = { + "scanned_functions": len(scan_rows), + "exact_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "exact" + ), + "mismatch_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "mismatch" + ), + "error_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "error" + ), + "byte_matched_dwarf_problems": sum( + 1 + for row in scan_rows + if row["objdiff_status"] == "match" + and row["dwarf_status"] in ("mismatch", "error") + ), + "signature_mismatch_functions": sum( + 1 for row in scan_rows if row.get("signature_match") is False + ), + } + + filtered_rows = filter_dwarf_scan_rows(scan_rows, args.dwarf_status) + filtered_rows = filter_dwarf_signature_rows( + filtered_rows, args.signature_status + ) + sort_dwarf_scan_rows(filtered_rows) + if args.limit is not None: + filtered_rows = filtered_rows[: args.limit] + + if args.json: + print( + json.dumps( + { + "unit": args.unit, + "summary": summary, + "rows": filtered_rows, + }, + indent=2, + ) + ) + return + + print( + f"Scanned {summary['scanned_functions']} function(s): " + f"{summary['exact_functions']} exact, " + f"{summary['mismatch_functions']} mismatched, " + f"{summary['error_functions']} errors." + ) + print( + "Byte-matched but DWARF-problem functions: " + f"{summary['byte_matched_dwarf_problems']}" + ) + print( + "Signature-mismatch functions: " + f"{summary['signature_mismatch_functions']}" + ) + + if not filtered_rows: + print("No functions match the given filters.") + return + + print() + print( + f"{'DSTAT':<8} {'DWARF':>7} {'SIG':>3} {'CHG':>4} {'OBJ':>7} {'OSTAT':<10} {'UNM':>6} FUNCTION" + ) + print("-" * 120) + for row in filtered_rows: + dwarf_percent = ( + f"{row['dwarf_match_percent']:.1f}%" + if row["dwarf_match_percent"] is not None + else "ERR" + ) + objdiff_percent = ( + f"{row['objdiff_match_percent']:.1f}%" + if row["objdiff_match_percent"] is not None + else "-" + ) + changed_groups = ( + str(row["changed_groups"]) if row["changed_groups"] is not None else "-" + ) + signature_state = ( + "yes" + if row.get("signature_match") is True + else "no" + if row.get("signature_match") is False + else "-" + ) + print( + f"{row['dwarf_status']:<8} {dwarf_percent:>7} {signature_state:>3} {changed_groups:>4} " + f"{objdiff_percent:>7} {row['objdiff_status']:<10} " + f"{row['unmatched_bytes_est']:>5}B {row['function']}" + ) + if args.show_errors and row.get("error"): + first_line = str(row["error"]).splitlines()[0] + print(f" error: {first_line}") + + print() + print( + "Tip: focus matched-byte functions first with " + "`python tools/decomp-workflow.py dwarf-scan " + f"-u {shlex.quote(args.unit)} --objdiff-status match`" + ) + if summary["signature_mismatch_functions"]: + print( + "Tip: add `--signature-status match` to focus body/local DWARF mismatches " + "instead of signature-only trouble." + ) + finally: + if cleanup_rebuilt_dwarf: + maybe_remove(rebuilt_dwarf_path) + + def lookup_symbol_address(symbols_file: str, mangled_name: str) -> Optional[str]: if not os.path.exists(symbols_file): return None @@ -395,6 +784,14 @@ def build_shared_unit_cached(unit: str) -> str: except WorkflowError as e: report(False, "ghidra", str(e)) + print_section("Platform Build Inputs") + for label, abs_path, missing_detail in PLATFORM_BUILD_REQUIREMENTS: + report( + os.path.exists(abs_path), + label, + describe_path(abs_path) if os.path.exists(abs_path) else missing_detail, + ) + print_section("Debug Symbol Checks") try: gc_addr = lookup_symbol_address(GC_SYMBOLS, DEBUG_SYMBOL_PROBE_MANGLED) @@ -642,6 +1039,9 @@ def command_function(args: argparse.Namespace) -> None: ensure_decomp_prereqs() print_section(f"Function Workflow: {args.function}") ensure_shared_unit_output(args.unit) + resolved_function_name = resolve_exact_function_name( + args.unit, args.function, reloc_diffs=args.reloc_diffs + ) cmd = python_tool("decomp-context.py", "-u", args.unit, "-f", args.function) if args.no_source: cmd.append("--no-source") @@ -661,9 +1061,14 @@ def command_function(args: argparse.Namespace) -> None: print(flush=True) print( "Required completion check: python tools/decomp-workflow.py verify " - f"-u {shlex.quote(args.unit)} -f {shlex.quote(args.function)}", + f"-u {shlex.quote(args.unit)} -f {shlex.quote(resolved_function_name)}", flush=True, ) + if resolved_function_name != args.function: + print( + f"(Resolved exact function name for DWARF-safe follow-up: {resolved_function_name})", + flush=True, + ) def command_unit(args: argparse.Namespace) -> None: @@ -810,8 +1215,11 @@ def command_dwarf(args: argparse.Namespace) -> None: print_section(f"DWARF Workflow: {args.unit} / {args.function}") if not args.rebuilt_dwarf_file: ensure_shared_unit_output(args.unit) + resolved_function_name = resolve_exact_function_name(args.unit, args.function) - cmd: List[str] = python_tool("dwarf-compare.py", "-u", args.unit, "-f", args.function) + cmd: List[str] = python_tool( + "dwarf-compare.py", "-u", args.unit, "-f", resolved_function_name + ) if args.summary: cmd.append("--summary") if args.json: @@ -833,18 +1241,24 @@ def command_verify(args: argparse.Namespace) -> None: ensure_shared_unit_output(args.unit) objdiff_row = choose_objdiff_row(args.unit, args.function, reloc_diffs=args.reloc_diffs) - dwarf_report = load_dwarf_report( - args.unit, - args.function, - rebuilt_dwarf_file=args.rebuilt_dwarf_file, - ) + resolved_function_name = str(objdiff_row["name"]) + dwarf_load_error: Optional[str] = None + dwarf_report: Optional[Dict[str, Any]] = None + try: + dwarf_report = load_dwarf_report( + args.unit, + resolved_function_name, + rebuilt_dwarf_file=args.rebuilt_dwarf_file, + ) + except WorkflowError as e: + dwarf_load_error = str(e) objdiff_exact = ( objdiff_row["status"] == "match" and objdiff_row["match_percent"] is not None and float(objdiff_row["match_percent"]) >= 100.0 ) - dwarf_exact = bool(dwarf_report["normalized_exact_match"]) + dwarf_exact = bool(dwarf_report["normalized_exact_match"]) if dwarf_report else False overall_ok = objdiff_exact and dwarf_exact objdiff_percent = ( @@ -852,34 +1266,56 @@ def command_verify(args: argparse.Namespace) -> None: if objdiff_row["match_percent"] is not None else "-" ) - dwarf_percent = f"{float(dwarf_report['match_percent']):.1f}%" + dwarf_percent = ( + f"{float(dwarf_report['match_percent']):.1f}%" if dwarf_report else "-" + ) print( f"objdiff: {'PASS' if objdiff_exact else 'FAIL'} | " f"{objdiff_percent} | status={objdiff_row['status']} | " f"unmatched~{objdiff_row['unmatched_bytes_est']}B" ) - print( - f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " - f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " - f"change groups={dwarf_report['changed_groups']}" - ) + if dwarf_report: + print( + f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " + f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " + f"change groups={dwarf_report['changed_groups']}" + ) + else: + print("DWARF: FAIL | unable to compare rebuilt vs original DWARF", flush=True) + if resolved_function_name != args.function: + print(f"Resolved DWARF symbol: {resolved_function_name}") print(f"Overall: {'PASS' if overall_ok else 'FAIL'}") print("Done means both objdiff and normalized DWARF are exact for the function.") if overall_ok: return + if dwarf_load_error: + print(flush=True) + print("DWARF compare could not complete:", flush=True) + print(dwarf_load_error, flush=True) + if ( + objdiff_row["status"] == "missing" + and "rebuilt DWARF: function" in dwarf_load_error + and "not found" in dwarf_load_error + ): + print( + "Hint: the rebuilt object does not contain this function yet. " + "Implement the function or fix its ownership/signature first, then rerun verify.", + flush=True, + ) + print(flush=True) print("Follow-up commands:", flush=True) print( f" python tools/decomp-workflow.py diff -u {shlex.quote(args.unit)} " - f"-d {shlex.quote(args.function)}", + f"-d {shlex.quote(resolved_function_name)}", flush=True, ) print( f" python tools/decomp-workflow.py dwarf -u {shlex.quote(args.unit)} " - f"-f {shlex.quote(args.function)}", + f"-f {shlex.quote(resolved_function_name)}", flush=True, ) raise WorkflowError( @@ -897,7 +1333,7 @@ def build_parser() -> argparse.ArgumentParser: health = subparsers.add_parser( "health", - help="Check whether the current worktree is ready for GC and PS2 decomp work", + help="Check whether the current worktree is ready for GC, Xbox, and PS2 work", ) health.add_argument( "--full", @@ -941,7 +1377,12 @@ def build_parser() -> argparse.ArgumentParser: help="Run decomp-context.py for one function", ) function.add_argument("-u", "--unit", required=True, help="Translation unit name") - function.add_argument("-f", "--function", required=True, help="Function name to inspect") + function.add_argument( + "-f", + "--function", + required=True, + help="Function name to inspect (full name or a unique substring)", + ) function.add_argument( "--no-source", action="store_true", @@ -1086,7 +1527,12 @@ def build_parser() -> argparse.ArgumentParser: help="Compare original vs rebuilt DWARF for one function", ) dwarf.add_argument("-u", "--unit", required=True, help="Translation unit name") - dwarf.add_argument("-f", "--function", required=True, help="Function name to compare") + dwarf.add_argument( + "-f", + "--function", + required=True, + help="Function name to compare (full name or a unique substring)", + ) dwarf.add_argument( "--summary", action="store_true", @@ -1122,12 +1568,72 @@ def build_parser() -> argparse.ArgumentParser: ) dwarf.set_defaults(func=command_dwarf) + dwarf_scan = subparsers.add_parser( + "dwarf-scan", + help="Scan one translation unit and rank per-function DWARF problem areas", + ) + dwarf_scan.add_argument("-u", "--unit", required=True, help="Translation unit name") + dwarf_scan.add_argument( + "--search", + help="Only include functions whose name or symbol contains this text", + ) + dwarf_scan.add_argument( + "--objdiff-status", + choices=["all", "match", "nonmatching", "missing"], + default="all", + help="Filter functions by objdiff status before scanning (default: all)", + ) + dwarf_scan.add_argument( + "--dwarf-status", + choices=["all", "problem", "exact", "mismatch", "error"], + default="problem", + help="Filter scan results by DWARF outcome after scanning (default: problem)", + ) + dwarf_scan.add_argument( + "--signature-status", + choices=["all", "match", "mismatch"], + default="all", + help="Filter scan results by whether the DWARF signature already matches (default: all)", + ) + dwarf_scan.add_argument( + "--limit", + type=int, + default=20, + help="Maximum rows to print after sorting (default: 20)", + ) + dwarf_scan.add_argument( + "--json", + action="store_true", + help="Print the scan summary and rows as JSON", + ) + dwarf_scan.add_argument( + "--show-errors", + action="store_true", + help="Print one-line error details under rows that could not be compared", + ) + dwarf_scan.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help="Pass through objdiff relocation diff mode when loading unit symbols", + ) + dwarf_scan.add_argument( + "--rebuilt-dwarf-file", + help="Use an existing rebuilt DWARF dump instead of dumping the unit object", + ) + dwarf_scan.set_defaults(func=command_dwarf_scan) + verify = subparsers.add_parser( "verify", help="Fail unless one function matches in both objdiff and DWARF", ) verify.add_argument("-u", "--unit", required=True, help="Translation unit name") - verify.add_argument("-f", "--function", required=True, help="Function name to verify") + verify.add_argument( + "-f", + "--function", + required=True, + help="Function name to verify (full name or a unique substring)", + ) verify.add_argument( "--reloc-diffs", choices=RELOC_DIFF_CHOICES, diff --git a/tools/project.py b/tools/project.py index 793200414..295a7ec56 100644 --- a/tools/project.py +++ b/tools/project.py @@ -1786,6 +1786,10 @@ def generate_objdiff_config( ], "units": [], "progress_categories": [], + "options": { + "functionRelocDiffs": "none", + "ppc.calculatePoolRelocations": False, + }, } # decomp.me compiler name mapping diff --git a/tools/share_worktree_assets.py b/tools/share_worktree_assets.py index 374d168e5..4bfdcf055 100644 --- a/tools/share_worktree_assets.py +++ b/tools/share_worktree_assets.py @@ -13,6 +13,8 @@ python tools/share_worktree_assets.py status --all python tools/share_worktree_assets.py link --all python tools/share_worktree_assets.py bootstrap + python tools/share_worktree_assets.py bootstrap --version EUROPEGERMILESTONE --xbox-xex /path/to/NfsMWEuropeGerMilestone.xex + python tools/share_worktree_assets.py bootstrap --version SLES-53558-A124 --ps2-toolchain-zip /path/to/PS2.zip """ import argparse @@ -21,6 +23,7 @@ import shutil import subprocess import sys +import zipfile from dataclasses import dataclass from typing import Dict, Iterable, List, Optional, Set @@ -39,11 +42,16 @@ class AssetSpec: FIXED_ASSETS = ( AssetSpec(os.path.join("orig", "GOWE69", "NFSMWRELEASE.ELF"), "file"), + AssetSpec( + os.path.join("orig", "EUROPEGERMILESTONE", "NfsMWEuropeGerMilestone.xex"), + "file", + ), AssetSpec(os.path.join("orig", "SLES-53558-A124", "NFS.ELF"), "file"), AssetSpec(os.path.join("orig", "SLES-53558-A124", "NFS.MAP"), "file"), AssetSpec(os.path.join("build", "tools"), "dir"), AssetSpec(os.path.join("build", "compilers"), "dir"), AssetSpec(os.path.join("build", "ppc_binutils"), "dir"), + AssetSpec(os.path.join("build", "mips_binutils"), "dir"), ) @@ -120,6 +128,85 @@ def ensure_parent(path: str) -> None: os.makedirs(parent, exist_ok=True) +def seed_shared_file(shared_path: str, source_path: str, description: str) -> None: + ensure_parent(shared_path) + if os.path.isfile(shared_path) and not os.path.islink(shared_path): + if not filecmp.cmp(shared_path, source_path, shallow=False): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + return + if os.path.islink(shared_path): + if not same_symlink(shared_path, source_path): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + os.unlink(shared_path) + elif lexists(shared_path): + raise RuntimeError( + f"Refusing to replace existing shared {description}: {shared_path}" + ) + shutil.copy2(source_path, shared_path) + + +def extract_zip_into(zip_path: str, output_dir: str) -> None: + os.makedirs(output_dir, exist_ok=True) + with zipfile.ZipFile(zip_path) as archive: + for member in archive.infolist(): + member_name = member.filename.rstrip("/") + if not member_name: + continue + + output_path = os.path.join(output_dir, *member_name.split("/")) + if member.is_dir(): + os.makedirs(output_path, exist_ok=True) + continue + + ensure_parent(output_path) + if os.path.exists(output_path): + continue + + with archive.open(member) as src, open(output_path, "wb") as dst: + shutil.copyfileobj(src, dst) + os.chmod(output_path, 0o755) + + +def seed_bootstrap_assets( + shared_root: str, xbox_xex: Optional[str], ps2_toolchain_zip: Optional[str] +) -> None: + if xbox_xex: + if not os.path.isfile(xbox_xex): + raise RuntimeError(f"Xbox XEX not found: {xbox_xex}") + seed_shared_file( + os.path.join( + shared_root, + "orig", + "EUROPEGERMILESTONE", + "NfsMWEuropeGerMilestone.xex", + ), + xbox_xex, + "Xbox XEX", + ) + + if ps2_toolchain_zip: + if not os.path.isfile(ps2_toolchain_zip): + raise RuntimeError(f"PS2 toolchain zip not found: {ps2_toolchain_zip}") + extract_zip_into(ps2_toolchain_zip, os.path.join(shared_root, "build", "compilers")) + expected_ee_gcc = os.path.join( + shared_root, + "build", + "compilers", + "PS2", + "ee-gcc2.9-991111", + "bin", + "ee-gcc.exe", + ) + if not os.path.isfile(expected_ee_gcc): + raise RuntimeError( + "PS2 toolchain zip did not produce build/compilers/PS2/ee-gcc2.9-991111/bin/ee-gcc.exe" + ) + + def merge_file(src: str, dst: str, relpath: str) -> None: ensure_parent(dst) if not os.path.exists(dst): @@ -342,18 +429,23 @@ def bootstrap_generated_files(worktree: str, version: str) -> None: objdiff_json = os.path.join(worktree, "objdiff.json") compile_commands = os.path.join(worktree, "compile_commands.json") config_target = os.path.join("build", version, "config.json") + configure_cmd = [sys.executable, "configure.py", "--version", version] - print(f"{worktree}: running configure.py") - run_command([sys.executable, "configure.py"], worktree, "configure.py") + print(f"{worktree}: running {' '.join(configure_cmd)}") + run_command(configure_cmd, worktree, "configure.py") if not os.path.isfile(build_ninja): raise RuntimeError(f"{worktree}: configure.py did not create build.ninja") - if not os.path.isfile(objdiff_json) or not os.path.isfile(compile_commands): + if ( + not os.path.isfile(config_target) + or not os.path.isfile(objdiff_json) + or not os.path.isfile(compile_commands) + ): print(f"{worktree}: generating {config_target} for local objdiff metadata") run_command(["ninja", config_target], worktree, f"ninja {config_target}") - print(f"{worktree}: rerunning configure.py") - run_command([sys.executable, "configure.py"], worktree, "configure.py") + print(f"{worktree}: rerunning {' '.join(configure_cmd)}") + run_command(configure_cmd, worktree, "configure.py") missing = [] if not os.path.isfile(objdiff_json): @@ -373,7 +465,10 @@ def bootstrap_worktrees( version: str, run_health: bool, smoke_build: Optional[str], + xbox_xex: Optional[str], + ps2_toolchain_zip: Optional[str], ) -> int: + seed_bootstrap_assets(shared_root, xbox_xex, ps2_toolchain_zip) link_assets(target_worktrees, seed_worktrees, shared_root) for worktree in target_worktrees: bootstrap_generated_files(worktree, version) @@ -418,6 +513,16 @@ def main() -> int: metavar="UNIT", help="Also run `decomp-workflow.py health --smoke-build UNIT` after bootstrap.", ) + parser.add_argument( + "--xbox-xex", + metavar="PATH", + help="Seed the shared Xbox XEX from a local file before linking/bootstrap.", + ) + parser.add_argument( + "--ps2-toolchain-zip", + metavar="PATH", + help="Extract a local PS2 EE-GCC zip into shared build/compilers before linking/bootstrap.", + ) args = parser.parse_args() common_dir = git_common_dir(root_dir) @@ -437,6 +542,8 @@ def main() -> int: args.version, args.health, args.smoke_build, + args.xbox_xex, + args.ps2_toolchain_zip, ) except RuntimeError as e: print(f"Error: {e}", file=sys.stderr)