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..ba212eb9e 100644 --- a/.github/skills/code_style/SKILL.md +++ b/.github/skills/code_style/SKILL.md @@ -31,7 +31,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,14 +95,32 @@ 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 - Prefer including the owning repo header over adding a local forward declaration for a project type. - If the repo already has a header declaration/definition for a type, include that header instead of redeclaring the type locally. +- Before replacing a forward declaration with the owner header, check both directions for an include cycle. If the owner header already includes the current header (for example `Smackable.h` vs `HeirarchyModel.h`), keep the forward declaration until the ownership/header split is cleaned up more deliberately instead of blindly introducing the cycle. +- Treat owner-header audit warnings as hints, not proof. Verify the namespace and the actual owner before acting: `SoundHeli.cpp` forward-declares `SoundConn::Pkt_Car_Service`, but `DrawVehicle.h` only owns `RenderConn::Pkt_Car_Service`, and the `SoundConn` packet body actually lives as a `.cpp`-local class in `SoundCar.cpp`. - 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`. +- Do not create a duplicate micro-header that re-declares one type already owned by a broader subsystem header just to dodge that include. Include the real owner header and rewrite the caller back toward the original API shape instead of inventing one-off constructors or wrappers in the duplicate header. - 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. +- For COM-style interfaces that declare `static HINTERFACE _IHandle();`, preserve the original ownership. If the interface wants an out-of-line handle, define the real `Type::_IHandle()` member in the owning TU (or use the header's outline hook macro) instead of inventing a free `extern "C"` shim with an `asm("...")` alias. +- In constructors for out-of-line `_IHandle` owners, check local precedent before calling `_IHandle()`. In this repo, several matching interfaces want `(HINTERFACE)_IHandle` passed straight to `UTL::COM::IUnknown`, and using the call form can perturb both codegen and DWARF ownership. +- When `QueryInterface(&iface)` still emits the wrong handle path for an out-of-line `_IHandle` owner, look for local precedent before rewriting it as a direct COM-table lookup through `_mInterfaces.Find((HINTERFACE)Iface::_IHandle)`. Also preserve the source-level branch shape around the result: `PVehicle::DoStaging` only matched after the direct `Find` plus an explicit `bool hasRaceEngine = raceEngine != nullptr;` test. +- If the dumps show that a shared project type had a real out-of-line constructor, declare that constructor in the owner header instead of relying on an implicit default constructor. Missing ctor declarations can make GCC synthesize member initialization inside each caller; `PVehicle::Construct` only matched the local-static setup once `FECustomizationRecord()` was declared in `FECustomizationRecord.h`. +- When a recovered constructor still needs a block of trivial member stores to happen before a later non-trivial member ctor, prefer moving those declaration-ordered pointer/scalar initializers into the initializer list instead of leaving them as body assignments. `PVehicle::PVehicle` only got close once the pre-`mBehaviorOverrides` members moved into the list, which let GCC place that setup ahead of the map ctor like the original. +- Keep owner-header parameter names synced with the matched out-of-line definition / DWARF names when neighboring parameters share the same type. The mangled signature may still compile, but stale swapped names make future polish work much easier to get wrong. +- If DWARF says a tiny virtual destructor was inline, do not assume "move it into the header" is automatically safe. In jumbo TUs that can improve one destructor callsite while also making objdiff report the standalone deleting-dtor symbol as missing from the owner TU, so always rebuild and check both the caller and the destructor symbol before keeping that rewrite. - Prefer moving helper template declarations next to their real use site instead of leaving them in an unrelated file. ### Pointer style @@ -114,13 +132,20 @@ Foo::Foo() - Use the repo's header guard form when writing headers: `#ifndef` / `#define` plus the `#ifdef EA_PRAGMA_ONCE_SUPPORTED` / `#pragma once` block. - Keep member layout comments aligned and intact in decomp headers. +- When writing a recovered layout, start from a pasted GC DWARF dump instead of hand-reconstructing a cleaner version. Treat the dump as source-of-truth data entry, then make only small verified fixes from PS2 or existing headers. - 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. +- If Dwarf and PS2 disagree on `struct` vs `class`, treat PS2 visibility sections as the tie-breaker for the real owner kind and then rebuild the affected TU to confirm the shape is byte-stable. +- 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. +- If DWARF says a member is a concrete generated Attrib wrapper such as `Attrib::Gen::effects`, keep the owner-header member typed as that wrapper and use the wrapper's original ctor overload in callers. Do not flatten it back to `Attrib::Instance` plus local `FindCollection` / `SetDefaultLayout` glue unless the dumps prove the generic form was real. - 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. +- Preserve the dumped declaration order too. Do not regroup methods, helpers, enums, or fields for readability unless an existing repo header or PS2 evidence proves the original order differs. - 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 +159,34 @@ 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. +- In match-sensitive gameplay code, if DWARF shows float-derived locals as `int`, keep them signed in source and prefer signed clamps such as `UMath::Min` / `UMath::Max` over `unsigned int` temporaries. On PPC, unsigned float-to-int conversion often pulls in the wrong `lfd` / `xoris` sequence. +- If DWARF points a tiny inline helper back at the current `.cpp` instead of a shared utility header, prefer recreating that file-local helper with the same return type / signedness before reusing a generic helper. Helper ownership alone can change normalized DWARF even when objdiff stays flat. +- Watch COM `QueryInterface` on interfaces that only declare `static HINTERFACE _IHandle();` in the header and define it out-of-line elsewhere. The current `UCOM.h` template calls `T::_IHandle()`, which can compile as a helper call instead of the original direct `(HINTERFACE)T::_IHandle` constant. If objdiff shows the direct handle form, use a tiny file-local `_mInterfaces.Find((HINTERFACE)T::_IHandle)` helper and preserve any explicit `bool hasX` local the original kept. +- If a touched shared utility header still has placeholder virtual/helper bodies, treat clusters of tiny 8-byte helper diffs as evidence that the header itself is unfinished. Recover the real shared body once in the header (for example, `UTL::FixedVector` returning the template size and inline buffer) instead of polishing each emitted instantiation separately. +- If DWARF puts most of a function's live locals inside one anonymous block, prefer preserving that with one explicit inner scope in the recovered source. Scope-only rewrites like this can improve normalized DWARF a lot without changing objdiff. +- In factory/build helpers, if DWARF only names the final real local and shows copy-ctor / parameter-bundle setup as inline ranges, prefer passing those temporaries directly at the call site instead of naming extra locals for them. This often shrinks the frame and matches the original ownership better. + +### 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. +- If a touched caller is using a local `*Access` shim with placeholder padding just to peek at another type's private members, prefer a narrow inline accessor on the real type over keeping the fake layout struct in the caller. + ## 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 +218,10 @@ 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. +- Recent `zMisc` review cleanup also showed that hand-reconstructed structs and reordered declarations create avoidable churn; copy recovered layouts from DWARF into the owner header first and keep the dumped order unless PS2/header evidence proves a correction. +- 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..ea6fef41b 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: @@ -88,6 +91,10 @@ definition does not yet exist in the project, follow the scaffold workflow in `.github/skills/scaffold/SKILL.md` to create the needed header/source definitions before moving on. +Treat recovered types here as copied reference data, not as hand-designed headers. Copy +the GC DWARF type body into the canonical owner header first and preserve its declaration +order unless PS2 or existing repo-header evidence proves a specific correction. + ## Phase 3: Implement Functions ### 3a. Get the updated function list @@ -113,6 +120,9 @@ For each missing or nonmatching function, follow the implementation workflow in - **One at a time.** Keep the tree in a coherent state as you work through the list. - **Balance new vs fixing.** Don't get stuck on one stubborn function — sometimes implementing the next function reveals patterns that make the previous one click. +- **Recovered types are not freeform.** If a function forces you to add or fix a type, + copy the DWARF layout into the owner header first. Do not sketch structs/classes from + use sites or reorder declarations just to make the header look nicer. - **Mismatch triage:** - `@stringBase0` offset mismatches often resolve as more string literals are added - If you need to inspect the original string or rodata at a virtual address, use `python tools/elf_lookup.py 0xADDR` @@ -152,6 +162,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 +203,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..8915ac246 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. @@ -85,6 +90,8 @@ Reference the skill for the usage. It gives info based on the virtual address of - If a repo header already exists for the type, include that header instead of introducing a local forward declaration. - Preserve the original `class` vs `struct` kind. If the existing header is missing or incomplete, verify the type kind from GC Dwarf and PS2 info before writing a local declaration. - Preserve real member names and field types too. Do not introduce `pad`, `unk`, or `field_XXXX` members as placeholders for guessed layout; verify the member list from GC Dwarf / PS2 data and leave a TODO when something is still uncertain. +- When a type is missing or incomplete, dump the full class/struct body from GC DWARF and paste that as the starting point. Do not reconstruct the layout from one function's field accesses or from guessed semantics. +- Preserve the dumped declaration order as well as the member order. Do not re-sort methods, group fields by guessed meaning, or otherwise "clean up" the layout unless an existing repo header or PS2 evidence proves a specific correction. ### 1e. Assembly reference @@ -125,6 +132,8 @@ and assembly: Utilize the dwarf information that you get from the lookup skill heavily. +For any recovered type you touch while implementing the function, treat the DWARF body as source material to copy, not prose to paraphrase. Start from the dumped layout in the canonical owner header, then make only the minimal verified fixes. + Don't add explanatory comments during implementation unless you need to document a remaining DWARF mismatch. Don't use any temporary local variables that don't exist in the dwarf. @@ -156,6 +165,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 +222,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 +263,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..52cdf0e9a 100644 --- a/.github/skills/scaffold/SKILL.md +++ b/.github/skills/scaffold/SKILL.md @@ -29,7 +29,14 @@ Collect data from **all** of these sources in parallel where possible: ## Phase 2: Setup class -Copy and cleanup the header that you got from running the `lookup` skill using the `symbols/Dwarf` folder. Fix visibility, function order and vtable related things based on using `lookup` on the PS2 types. +Copy the header/type body that you got from running the `lookup` skill using the +`symbols/Dwarf` folder into the canonical owner header first. Do not retype or +reconstruct the layout from memory, from scattered callsites, or from guessed +semantics. + +Then do the minimum cleanup backed by evidence: fix visibility, function order and +vtable related things based on using `lookup` on the PS2 types, and clean up duplicated +inline copies when the DWARF emitted both versions. For formatting and local cleanup while writing the header, consult `.github/skills/code_style/SKILL.md`. Use it for member-comment alignment, declaration @@ -39,6 +46,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 +57,31 @@ 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. +Preserve the declaration order from the dumped type body as well, not just the member +order. Do not regroup methods, fields, enums, or helper declarations for readability +unless an existing repo header or PS2 evidence proves the original owner header used a +different order. + +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..1e78f634d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,12 @@ .idea/ .vs/ +# macOS +.DS_Store +.AppleDouble +.LSOverride +._* + # Caches __pycache__ .mypy_cache diff --git a/AGENTS.md b/AGENTS.md index d367fc237..6d7faed21 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 @@ -343,8 +342,24 @@ This is a **C++98** codebase compiled with ProDG GC 3.9.3 (GCC 2.95 under the ho - Inline assembly is acceptable when needed to reproduce dead code or compiler scheduling that source alone cannot express cleanly - Preserve the original `class` vs `struct` kind. Check existing headers first, then Dwarf / PS2 info when needed. Even forward declarations and local partial declarations should use the accurate keyword when known. - Prefer including the real repo header over introducing a local forward declaration for a project type. If a type already has a header in `src/`, include it instead of redeclaring it locally. -- If a subsystem already has a stub owner header and the debug line info points back at that subsystem, fill the owner header instead of keeping a recovered project type declaration in a `.cpp`. +- If a subsystem already has a stub or umbrella owner header and the debug line info points back at that subsystem, fill the owner header instead of keeping a recovered project type declaration in a `.cpp` or spinning up a one-off micro-header just for that type. +- Do not spin up a duplicate micro-header that re-declares a type already owned by a real subsystem or umbrella header just to avoid a heavier include. Include the owner header and adapt the call site back to the original API shape (for example, use the existing default-constructor-plus-`Init` flow instead of inventing a convenience constructor in a one-off header). +- Apply the same owner-header rule to shared enums, globals, callback typedefs, and free functions. If multiple TUs need the declaration, put it in the canonical owner header once and include that header instead of duplicating enum bodies or `extern` blocks across `.cpp`s. +- For COM-style interfaces that declare `static HINTERFACE _IHandle();`, preserve the original ownership: if the handle is meant to be out-of-line, define the real `Type::_IHandle()` member in the owning TU (or via the header's outline hook macro) instead of inventing a free `extern "C"` shim with an `asm("...")` alias. Those fake shims can generate malformed local labels and even make `ngcas` crash. +- In constructors for out-of-line `_IHandle` owners, prefer passing `(HINTERFACE)_IHandle` to the `UTL::COM::IUnknown` base when existing repo examples in that subsystem already do so. Calling `_IHandle()` instead often changes codegen and DWARF ownership in match-sensitive TUs. +- When `QueryInterface(&iface)` on an out-of-line `_IHandle` owner emits the wrong handle-call shape, check for existing subsystem precedent to read the COM table directly with `(*reinterpret_cast(owner))->_mInterfaces.Find((HINTERFACE)Iface::_IHandle)`. For some functions (for example `PVehicle::DoStaging` with `IRaceEngine`), GCC only matched after that direct `Find` path and an explicit `bool hasIface = iface != nullptr;` branch shape. +- If DWARF / PS2 show that a shared project type had a real out-of-line constructor, declare that constructor in the owner header instead of relying on an implicit default constructor. Leaving it implicit can make GCC synthesize member-by-member initialization in each caller; `PVehicle::Construct` only took the original local-static path after `FECustomizationRecord()` was declared in `FECustomizationRecord.h`. +- When a recovered constructor still emits a big zeroing block before a later non-trivial member ctor (for example `UTL::Std::map` or another container), try moving the declaration-ordered pointer/scalar setup into the initializer list instead of zeroing those members in the body. `PVehicle::PVehicle` only jumped into the 90% range once the pre-`mBehaviorOverrides` members were initialized through the list, letting GCC schedule those stores before the map ctor like the original. +- Keep owner-header parameter names aligned with the matched out-of-line definition and DWARF names when adjacent parameters share the same type. Swapped names like `initialPos` / `initialVec` in a header do not change mangling, but they mislead later cleanup and make it easier to "fix" a function away from the original source shape. +- When DWARF marks a tiny virtual destructor as inline, do not blindly move the body into the header. In jumbo TUs that can fix one caller's inlining while also making objdiff report the standalone deleting-destructor symbol as missing from its original owner TU; always rebuild the owner TU and check both the callsite and the destructor symbol itself before keeping that change. - Preserve original member names, types, order, and proven layout comments. Do not invent `pad`, `unk`, or `field_XXXX` members just to satisfy a guessed size or offset; verify the real members with `find-symbol.py`, GC Dwarf, and PS2 data, and leave a short TODO if a layout detail is still uncertain. +- When recovering a type, start by copying the GC DWARF struct/class body into the canonical owner header. Treat that dump as the source of truth for declaration order too; only apply targeted fixes that are backed by existing repo headers or PS2 data, such as visibility, virtual/function order, duplicate-inline cleanup, or owner-header placement. +- Do not hand-reconstruct recovered layouts from scattered field accesses, guessed semantics, or a "cleaned up" reordering. If you do not have enough evidence to paste the type confidently, stop and gather more DWARF / PS2 info first. +- Preserve the original scope and nesting of recovered declarations too. Keep class-owned enums/types nested when the original did, and move subsystem/global enums into their real owner header instead of flattening or duplicating them near one caller. +- Use the narrowest correct home for recovered declarations: shared project-facing types in headers, TU-private helper structs/classes and allocator metadata in the `.cpp`. Do not dump implementation-only helpers into public headers just because they were convenient to write there. +- Prefer real subsystem or vendor headers over ad-hoc local typedef/prototype blocks. If an external API is shared and the project is missing the proper header, add that header in the correct subtree instead of stashing declarations in an unrelated gameplay file. +- When a touched function is reaching through an object's private layout via a local `*Access` struct plus guessed padding, stop and look for a cleaner owner-backed shape first. If the real type already lives in the repo, prefer adding a tiny inline accessor on that type over keeping a fake layout shim with `pad` members in the caller. +- Do not leave repeated `// TODO move`, `// TODO where should this go`, or "I just made this up" markers around declarations. Either move the declaration to its owner now or leave one short targeted TODO above the owner declaration if ownership is still genuinely unresolved. - Follow DWARF member naming exactly (`mMember` vs `m_member`) instead of normalizing names - Omit the `this` pointer. - Use `nullptr` and `override`. If they are missing, you need to include `types.h`. @@ -363,44 +378,26 @@ 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: ``` -n.n%: short description of what was matched or changed +n.n[n]%: short description of what was matched or changed ``` Examples: - `42.1%: match UpdateCamera` -- `78.5%: match PlayerController constructor and destructor` +- `78.56%: match PlayerController constructor and destructor` - `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. - ## 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. -The dwarf of your structs doesn't have to neccessarily match the original due to various reasons, just make sure that you copied everything correctly. +The DWARF of your structs does not always compare cleanly in every detail, but the recovery process still starts by copying the dumped layout correctly. Do not freehand-reconstruct a struct from call sites or guessed semantics; paste the DWARF body into the real owner header first, then make only the minimal PS2/header-backed fixes such as visibility, function order, vtable order, or duplicate-inline cleanup. Never dismiss a diff as "close enough" or "just register allocation." Every mismatched instruction is a signal that the source doesn't perfectly represent the original. Even @@ -436,6 +433,10 @@ Virtual table layout is also missing from the dwarf but there on PS2. Be aware t The inline information in the dwarf is incredibly useful. When you encounter one, you should look up its body in the project. If it doesn't exist yet, deduce how the code should look like and add it to the correct header (you can use your address lookup skill or if that doesn't succeed and the inline is a member function, just find the corresponding class in the project). +For recovered structs and classes, treat DWARF as copied source material rather than a loose sketch. Paste the dumped type into the owner header first and keep its declaration/member order unless PS2 or an existing repo header proves a specific correction. + +Apply the same rule to generated Attrib wrappers. If DWARF names a member as `Attrib::Gen::foo` (or another concrete generated wrapper), keep the member typed as that wrapper in the owner header and mirror the original wrapper ctor overload instead of demoting it to `Attrib::Instance` plus a local `FindCollection` / `SetDefaultLayout` helper chain. + It's very important that you use math inlines from bMath and UMath as shown in the dwarf. UVector inlines use temporaries that the compiler couldn't optimize out. You can see in the dwarf on which stack address they are and deduce final destination they are copied to. ### Store instruction order hints @@ -481,6 +482,48 @@ register assignments but does NOT affect integer register assignments (and vice Every local that is NOT in the DWARF is a spurious temporary — remove it. - Every local that IS in the DWARF must exist in the source, even if you don't use the name. Name it exactly as the DWARF shows. +- If DWARF shows a float-derived local as `int`, keep it signed in source and clamp it with + a signed helper such as `UMath::Min`/`UMath::Max` or an equivalent signed branch. Rewriting + it as `unsigned int` often forces the PPC unsigned float-to-int conversion path (`lfd` / + `xoris`) and can badly perturb both objdiff and DWARF. +- When DWARF attributes a tiny inline helper to the current `.cpp` instead of a shared header + utility, prefer recreating that file-local helper with the same signedness / ownership rather + than calling the generic helper. In `DamageVehicle::OnImpact`, a file-local `int Min(int, int)` + preserved the helper's owner file and improved DWARF without changing objdiff. +- Be careful with COM `QueryInterface` on interfaces whose headers only declare + `static HINTERFACE _IHandle();` out-of-line while their constructors already use the function-address + form `(HINTERFACE)_IHandle`. The template in `UCOM.h` calls `T::_IHandle()`, which can compile as a + helper-call shape the original code did not use. When objdiff wants the direct handle constant, + prefer a tiny file-local helper that calls `_mInterfaces.Find((HINTERFACE)T::_IHandle)` and preserve + any explicit `bool hasX = ptr != nullptr;` local if the assembly/DWARF shows one. +- When a shared utility header still has obvious placeholder virtual/helper bodies, do not dismiss the + resulting 8-byte helper diffs as harmless noise. In `UTL::FixedVector`, restoring the real header-level + bodies (`GetGrowSize` / `GetMaxCapacity` returning the template size and `AllocVectorSpace` returning the + inline buffer) matched whole clusters of instantiations at once and improved the owning TU without any + per-instantiation edits. +- When objdiff is already exact but a local only differs by lexical scope, try an equivalent + loop form that keeps the temporary inside the same block as the original DWARF. In practice, + changing a `for (...; ...; x = next)` into a `while (...) { T *next = ...; ...; x = next; }` + can fix DWARF-only scope mismatches without changing codegen. +- If DWARF groups most of a function's working locals inside one anonymous block, prefer keeping + that recovered logic inside one explicit inner scope instead of leaving the locals at function + scope. Matching the lexical block alone can dramatically improve normalized DWARF even when + objdiff is unchanged. +- In factory/build helpers, if DWARF only names the final real local but shows copy-ctor / parameter-bundle + construction as inline ranges, prefer passing those temporaries directly at the call site instead of + materializing named locals for them. In `PhysicsObject::LoadBehavior`, collapsing a copied `UCrc32` and + `BehaviorParams` into `BuildElement(behavior, BehaviorParams(...))` removed the extra stack slot and matched + the original frame. + +### Slot-pooled delete paths + +- If a recovered local/project type participates in `delete` paths or container/list teardown, + check whether the original type exposed inline `operator new` / `operator delete`. Missing + slot-pool-backed operators often makes GCC emit `__builtin_delete` instead of the original + allocator/free path and can also move destructor/delete DWARF ownership out of the TU. +- This applies even when the TU mostly allocates the type manually through `bOMalloc` or a + pool helper. Restoring the inline operators can still be necessary so `delete` expressions + and synthesized cleanup paths match the original code and DWARF. ### Virtual vs direct calls @@ -520,6 +563,31 @@ TU: | Function: +### AttributeGetStringKeyFallback + +TU: zPhysics | Function: PVehicle::LookupBehaviorSignature +When a generated Attrib wrapper fallback refuses to match, do not "simplify" it to `GetAttributePointer` plus a raw `const char *` hash. In `PVehicle::LookupBehaviorSignature`, ProDG matched much better when the code kept a local `Attrib::StringKey`, fetched through `Attrib::Attribute value = mAttributes.Get(...)`, copied it with `value.Get(0, behaviourKey)`, and even preserved an otherwise-unused local `Attrib::Instance(nullptr, 0, nullptr)` ahead of that path to reproduce the hidden ctor/dtor pair the original emitted. + +### BoolStringKeyAttributeGet + +TU: zPhysics | Function: PVehicle::LookupBehaviorSignature +If DWARF shows `Attribute::Get(unsigned int, StringKey&)` as a bool-returning inline instead of the generic template path, recover that shared overload in `AttribSys.h` rather than open-coding `Get(...); GetInternal()` in each caller. `PVehicle::LookupBehaviorSignature` improved and its DWARF got closer to the original once `Attrib::Attribute` exposed a dedicated `bool Get(unsigned int, StringKey&) const` helper and the caller used `if (atr.Get(0, behaviourKey))`. + +### ExplicitStringKeyAssignmentOrder + +TU: zPhysics | Function: PVehicle::LookupBehaviorSignature +If an Attrib/StringKey copy path still differs after recovering the bool `Attribute::Get` helper, check whether `StringKey` is missing an explicit inline assignment operator. `PVehicle::LookupBehaviorSignature` only moved the copied `behaviourKey` words into the original store order once `AttribHash.h` exposed `const StringKey &operator=(const StringKey &rhs)` and assigned `mString` before the hashed fields instead of relying on the compiler-generated memberwise assignment. + +### AILoopReturnSignatureSplit + +TU: zPhysics | Function: PVehicle::LookupBehaviorSignature +When a small lookup loop still reloads the same field twice in objdiff, do not assume the clean `while (sig != kNull) { ... sig = ab->field; }` form is original. `PVehicle::LookupBehaviorSignature` matched much better once the AI block kept `sig` as the return value, compared `ab->signature` directly in the loop condition, and only assigned `sig = ab->signature` on the winning match before breaking. + +### EmptyIUnknownWrapperDtorTrap + +TU: zPhysics / zPhysicsBehaviors | Function: Smackable::~Smackable / EngineRacer::~EngineRacer / PVehicle::~PVehicle +When a small interface derives from `UTL::COM::IUnknown` and its out-of-line destructor body is empty, do not assume callers should simply invoke that empty wrapper dtor. In both `Smackable::~Smackable` and `EngineRacer::~EngineRacer`, the remaining diff wanted the inlined `_mCOMObject->_mInterfaces.Remove(this)` path from `IUnknown::~IUnknown`, not a direct call to the empty derived wrapper. Naively moving those empty dtors inline in the caller TU can improve one destructor but also erase the standalone interface-dtor symbol and regress the unit, so only inline them when you can preserve the owner symbol elsewhere. + ### ExplicitInlineSpecialMembersForSTLElements TU: zAttribSys | Function: \_STL::\_Rb_tree::\_M_insert diff --git a/AGENTS_REVIEW.md b/AGENTS_REVIEW.md new file mode 100644 index 000000000..fc4e49a16 --- /dev/null +++ b/AGENTS_REVIEW.md @@ -0,0 +1,17 @@ +# Need for Speed Most Wanted Decompilation + +Matching decompilation of Need for Speed Most Wanted 2005 (GameCube) targeting the USA Release build (`GOWE69`). +The goal is to produce C++ source that compiles to byte-identical and dwarf-identical object code against the +original retail binary using the ProDG GC 3.9.3 compiler. + +You should heavily rely on your skills. An AI agent recently decompiled the translation unit zPhysics, +but the result is not as clean as human written code. And there was a fatal error, the agent used the PS2 +structs, because the GameCube ones were missing. + +You should do the following tasks: + +1. Make sure the dol builds using `ninja`. +2. Get rid of `using` directives and qualify the full names instead. +3. Apply STYLE_GUIDE.md to zPhysics. +4. Look at all the functions and struct/classes that were created in all the latest Copilot commits and make sure that the dwarf matches the original (dtk dump + lookup skill). Ignore the fact that some inlines are duplicated inside struct bodies in the original dwarf. Keep in mind that the dwarf only ever shows "struct", so don't change classes to structs according to that. +5. Look at all the structs/classes that were created in all the latest Copilot commits and depending on the line info decide whether they are defined in the right place (lookup skill + line_lookup skill). Move them if they aren't. diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md new file mode 100644 index 000000000..a4587fb53 --- /dev/null +++ b/STYLE_GUIDE.md @@ -0,0 +1,124 @@ +# Code Style Guide for zAnim Decompilation + +This guide describes the coding style to follow when writing decompiled code for this project. +It is derived by comparing **human-written** zAI source files against **AI-written** zAnim source files. + +## 1. Cast Spacing + +Use C++ casts **without** spaces inside the angle brackets. + +```cpp +// CORRECT (human style) +static_cast(value) +reinterpret_cast(ptr) +static_cast(door) + +// WRONG (AI style) +static_cast< float >(value) +reinterpret_cast< char * >(ptr) +static_cast< unsigned int >(door) +``` + +## 2. Inline Assembly + +Inline assembly (`asm(...)`, `__asm__ volatile(...)`) is **acceptable** when it is required to reproduce +dead code sequences that the compiler cannot generate from source alone. This is a valid decompilation +technique, not a style violation. + +```cpp +// Acceptable — matches compiler-generated dead compare +asm("cmpw 7, %0, %1" : : "r"(mThisInstanceNameHash), "r"(info->mParentInstanceNameHash) : "cr7"); + +// Acceptable — memory barriers to control instruction scheduling +__asm__ volatile("" : : : "memory"); +``` + +## 3. Null Pointer + +Use `nullptr` exclusively. Never use `NULL` or `0` as a null pointer. +Write `if (ptr)` instead of `if (ptr != nullptr)`. + +## 4. Include Guards + +Every header must use both the `#ifndef` guard **and** the `EA_PRAGMA_ONCE_SUPPORTED` block: + +```cpp +#ifndef PATH_FILENAME_H +#define PATH_FILENAME_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif +``` + +The guard name follows `SUBPATH_FILENAME_H` (all uppercase), e.g. `ANIMATION_ANIMSCENE_H`, +`AI_ACTIVITIES_AICOPMANAGER_H`. + +## 5. Constructor Initializer Lists + +Each initializer goes on its own line with a leading `, ` and a trailing `//` comment (empty) to +prevent clang-format from collapsing them to one line: + +```cpp +CBasicCharacterAnimEntity::CBasicCharacterAnimEntity() + : mDrawShadow(true), // + mTypeID(0), // + mThisInstanceNameHash(0), // + mSpaceNode(nullptr), // + mWorldModel(nullptr) {} +``` + +## 6. Struct vs Class + +Follow the `struct vs class` rule from the lookup skill: if the PS2 dump shows **no visibility +modifiers** → use `struct`; if any `public:`/`private:`/`protected:` appear → use `class`. + +## 7. Member Variable Naming + +Member variable naming reflects what the original DWARF says. Some classes use `mVariable` +(no underscore), others use `m_variable` (with underscore). Follow the DWARF dump. Do not +invent naming prefixes. + +## 8. `using` directives + +Never add `using` directives. Qualify all names fully. + +## 9. Namespace-qualified Types + +All types must be fully qualified at point of use: + +```cpp +// CORRECT +UMath::Vector3 pos; +Sim::IActivity *act; + +// WRONG +using namespace UMath; +Vector3 pos; +``` + +## 10. DECLARE_CONTAINER_TYPE + +When using `UTL::Std::list` or `UTL::Std::vector` type aliases, pair them with +`DECLARE_CONTAINER_TYPE`: + +```cpp +DECLARE_CONTAINER_TYPE(AICopManagerSpawnRequests); +typedef UTL::Std::list SpawnList; +``` + +## 11. Inline Functions in Headers + +Define inline member functions in the header only when they are genuinely inlined in the binary +(confirmed by DWARF). Keep non-inlined functions as declarations with implementations in `.cpp`. + +## 12. Offset Comments + +When writing struct definitions, add offset and size comments matching the DWARF: + +```cpp +UMath::Vector3 InitialPos; // offset 0x0, size 0xC +UMath::Vector3 InitialVec; // offset 0xC, size 0xC +char VehicleName[32]; // offset 0x18, size 0x20 +bool InPursuit; // offset 0x38, size 0x1 +``` diff --git a/config/GOWE69/config.yml b/config/GOWE69/config.yml index 525eaaf36..76c32a9b0 100644 --- a/config/GOWE69/config.yml +++ b/config/GOWE69/config.yml @@ -34,3 +34,21 @@ block_relocations: - source: .text:0x80047c1c end: .text:0x80047c28 + +- source: .text:0x8021EAA8 + end: .text:0x8021EAAC +- source: .text:0x8021EAB0 + end: .text:0x8021EAB4 + +- source: .text:0x8021EB34 + end: .text:0x8021EB38 +- source: .text:0x8021EB3C + end: .text:0x8021EB40 + +- source: .text:0x8021FE78 + end: .text:0x8021FE7C +- source: .text:0x8021FE80 + end: .text:0x8021FE84 + +- source: .text:0x8021eaa8 + end: .text:0x8021eab0 diff --git a/src/Speed/Indep/Libs/Support/Utility/UBitArray.h b/src/Speed/Indep/Libs/Support/Utility/UBitArray.h new file mode 100644 index 000000000..7ede92f61 --- /dev/null +++ b/src/Speed/Indep/Libs/Support/Utility/UBitArray.h @@ -0,0 +1,63 @@ +#ifndef SUPPORT_UTILITY_UBITARRAY_H +#define SUPPORT_UTILITY_UBITARRAY_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +template < typename T, int N > struct BitArray { + // Number of bits per word + enum { BITS_PER_WORD = sizeof(T) * 8 }; + // Number of words needed + enum { NUM_WORDS = (N + BITS_PER_WORD - 1) / BITS_PER_WORD }; + + T Words[NUM_WORDS]; + + BitArray() { + for (unsigned int i = 0; i < NUM_WORDS; i++) { + Words[i] = 0; + } + } + + BitArray(const BitArray &src) { + for (unsigned int i = 0; i < NUM_WORDS; i++) { + Words[i] = src.Words[i]; + } + } + + const BitArray &operator=(const BitArray &src) { + for (unsigned int i = 0; i < NUM_WORDS; i++) { + Words[i] = src.Words[i]; + } + return *this; + } + + void Set(unsigned int index) { + Words[index >> 5] |= (1 << (index & 0x1f)); + } + + void Clear(unsigned int index) { + Words[index >> 5] &= ~(1 << (index & 0x1f)); + } + + bool Test(unsigned int index) const { + return (Words[index >> 5] >> (index & 0x1f)) & 1; + } + + bool Test() const { + for (int i = 0; i < static_cast< int >(NUM_WORDS); i++) { + if (Words[i]) { + return true; + } + } + return false; + } + + void Clear() { + for (unsigned int i = 0; i < NUM_WORDS; i++) { + Words[i] = 0; + } + } +}; + +#endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UCOM.h b/src/Speed/Indep/Libs/Support/Utility/UCOM.h index 511c88f54..d8c364d3b 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UCOM.h +++ b/src/Speed/Indep/Libs/Support/Utility/UCOM.h @@ -139,6 +139,7 @@ template class Factory { Prototype(const _PRODUCT_SIGNATURE &classsig, _CONSTRUCTOR constructor) { mSignature = classsig; mConstructor = constructor; + mTail = mHead; mHead = this; } @@ -162,18 +163,19 @@ 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; - // } + static _PRODUCT CreateInstance(_PRODUCT_SIGNATURE sig, _BUILD_PARAMETERS params) { + for (const Prototype *f = Prototype::GetHead(); f != nullptr; f = f->GetNext()) { + if (f->mSignature == sig) { + return f->mConstructor(params); + } + } + return nullptr; + } }; +template +typename Factory::Prototype *Factory::Prototype::mHead; + } // namespace COM } // namespace UTL diff --git a/src/Speed/Indep/Libs/Support/Utility/UCollections.h b/src/Speed/Indep/Libs/Support/Utility/UCollections.h index 9c912da3d..4740b5ed9 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UCollections.h +++ b/src/Speed/Indep/Libs/Support/Utility/UCollections.h @@ -5,6 +5,7 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" #include "Speed/Indep/Libs/Support/Utility/UTLVector.h" #include "UStandard.h" @@ -233,14 +234,48 @@ template class GarbageNode { static Collector _mCollector; }; +template +uintptr_t Instanceable::_mHNext; + +template +typename Instanceable::_List Instanceable::_mList; + +template +typename GarbageNode::Collector GarbageNode::_mCollector; + template class Container { public: - class Elements { + class Elements : public UTL::Std::list { public: - Elements(); - ~Elements(); + Elements() {} + ~Elements() {} }; + void AddElement(T *e) { + _mElements.push_back(e); + } + + template + T *BuildElement(UCrc32 sig, const P &parms) { + T *e = T::CreateInstance(sig, parms); + if (e != nullptr) { + _mElements.push_back(e); + } + return e; + } + + bool DestroyElement(T &el) { + typename Elements::iterator last = _mElements.end(); + for (typename Elements::iterator first = _mElements.begin(); first != last; first++) { + if (*first == &el) { + _mElements.erase(first); + T::Destroy(&el); + return true; + } + } + return false; + } + private: Elements _mElements; }; diff --git a/src/Speed/Indep/Libs/Support/Utility/UCrc.h b/src/Speed/Indep/Libs/Support/Utility/UCrc.h index c717d6e00..988254b58 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UCrc.h +++ b/src/Speed/Indep/Libs/Support/Utility/UCrc.h @@ -14,8 +14,8 @@ class bHash32 { public: bHash32() {} bHash32(const char *name) {} - bHash32(const bHash32 &from) {} - bHash32(unsigned int crc) {} + bHash32(const bHash32 &from) : mCRC(from.mCRC) {} + bHash32(unsigned int crc) : mCRC(crc) {} bHash32 &operator=(bHash32 &from) { this->mCRC = from.mCRC; diff --git a/src/Speed/Indep/Libs/Support/Utility/UListable.h b/src/Speed/Indep/Libs/Support/Utility/UListable.h index c307298c5..a7f47482e 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UListable.h +++ b/src/Speed/Indep/Libs/Support/Utility/UListable.h @@ -35,7 +35,7 @@ template class Listable { }; typedef void (*ForEachFunc)(pointer); - typedef bool (*ComparisonFunc)(pointer, pointer); + typedef bool (*ComparisonFunc)(const_pointer, const_pointer); protected: Listable() { @@ -66,10 +66,23 @@ template class Listable { std::sort(_mTable.begin(), _mTable.end(), pred); } + static int Count() { + return _mTable.size(); + } + private: static List _mTable; }; +template +typename Listable::List Listable::_mTable; + +template +Listable::List::List() {} + +template +Listable::List::~List() {} + template class ListableSet { public: typedef T value_type; @@ -81,8 +94,8 @@ template class Li class List : public _Storage { public: // List(const List &); - List() {} - ~List() override {} + List(); + ~List() override; // List &operator=(List &); }; @@ -134,6 +147,10 @@ template class Li } } + void UnList(Enum from) { + _mLists._remove(static_cast(this), from); + } + ~ListableSet() { UnList(); } @@ -148,6 +165,18 @@ template class Li static _ListSet _mLists; }; +template +ListableSet::List::List() {} + +template +ListableSet::List::~List() {} + +template +ListableSet::_ListSet::_ListSet() {} + +template +ListableSet::_ListSet::~_ListSet() {} + template class Countable { static int _mCount; @@ -166,6 +195,12 @@ template class Countable { } }; +template +int Countable::_mCount; + +template +typename ListableSet::_ListSet ListableSet::_mLists; + }; // namespace Collections }; // namespace UTL diff --git a/src/Speed/Indep/Libs/Support/Utility/UMath.h b/src/Speed/Indep/Libs/Support/Utility/UMath.h index 76c15b209..f4394beaa 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UMath.h +++ b/src/Speed/Indep/Libs/Support/Utility/UMath.h @@ -75,6 +75,10 @@ inline void Copy(const Matrix4 &a, Matrix4 &r) { VU0_MATRIX4Copy(a, r); } +inline void Copy(const Vector3 &a, Vector3 &r) { + __builtin_memcpy(&r, &a, sizeof(Vector3)); +} + inline void Set(Matrix4 &m, unsigned int row, const Vector4 &a) { VU0_v4Copy(a, m[row]); } @@ -99,6 +103,10 @@ inline const Vector3 &ExtractAxis(const Matrix4 &m, unsigned int row) { return *reinterpret_cast(&m[row]); } +inline Vector3 &ExtractAxis(Matrix4 &m, unsigned int row) { + return *reinterpret_cast(&m[row]); +} + inline void ExtractXAxis(const Vector4 &q, Vector3 &r) { VU0_ExtractXAxis3FromQuat(q, r); } @@ -147,6 +155,10 @@ inline void Unitxyz(const Vector4 &a, Vector4 &r) { VU0_v4unitxyz(a, r); } +inline void Unitxyz(Vector4 &a) { + VU0_v4unitxyz(a, a); +} + // UEALibs not working??? void MATRIX4_multyrot(const Matrix4 *m4, float ybangle, Matrix4 *resultm); inline void MultYRot(const Matrix4 &m, float a, Matrix4 &r) { @@ -172,6 +184,10 @@ inline void Add(const Vector3 &a, const Vector3 &b, Vector3 &r) { #endif } +inline void Add(const Vector4 &a, const Vector4 &b, Vector4 &r) { + VU0_v4add(a, b, r); +} + inline void Scale(const Vector3 &a, const Vector3 &b, Vector3 &r) { #ifdef EA_PLATFORM_XENON r.x = a.x * b.x; @@ -228,6 +244,10 @@ inline void ScaleAddxyz(const Vector4 &a, const float s, const Vector4 &b, Vecto VU0_v4scaleaddxyz(a, s, b, r); } +inline void Addxyz(const Vector4 &a, const Vector4 &b, Vector4 &r) { + VU0_v4addxyz(a, b, r); +} + inline void AddScale(const Vector3 &a, const Vector3 &b, const float s, Vector3 &r) { #ifdef EA_PLATFORM_XENON #else @@ -235,6 +255,10 @@ inline void AddScale(const Vector3 &a, const Vector3 &b, const float s, Vector3 #endif } +inline void AddScale(const Vector4 &a, const Vector4 &b, const float s, Vector4 &r) { + VU0_v4addscale(a, b, s, r); +} + inline void Sub(const Vector3 &a, const Vector3 &b, Vector3 &r) { #ifdef EA_PLATFORM_XENON r.x = a.x - b.x; @@ -282,6 +306,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; @@ -464,4 +492,12 @@ inline float Limit(const float a, const float l) { } // namespace UMath +inline void AddScalexyz(const UMath::Vector4 &a, const UMath::Vector4 &b, const float s, UMath::Vector4 &r) { + VU0_v4addscalexyz(a, b, s, r); +} + +inline void UnitCrossxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &dest) { + VU0_v4unitcrossprodxyz(a, b, dest); +} + #endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UQueue.h b/src/Speed/Indep/Libs/Support/Utility/UQueue.h index 101bfc07d..e954d3316 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UQueue.h +++ b/src/Speed/Indep/Libs/Support/Utility/UQueue.h @@ -10,26 +10,60 @@ template class UCircularQueue { int Head; // offset 0x4, size 0x4 int Tail; // offset 0x8, size 0x4 int MaxSize; // offset 0xC, size 0x4 - T Elements[U]; // offset 0x10, size 0x258 + T Elements[U]; // offset 0x10, size varies public: - // UCircularQueue() {} + UCircularQueue() : Size(0), Head(-1), Tail(0), MaxSize(U) {} - // void enqueue(const T &insert) {} + void enqueue(const T &insert) { + Head = Head + 1; + if (Head > MaxSize - 1) { + Head = 0; + } + Size = Size + 1; + if (Size > MaxSize) { + Tail = Tail + 1; + if (Tail > MaxSize - 1) { + Tail = 0; + } + Size = MaxSize; + } + Elements[Head] = insert; + } - // void dequeue() {} + void dequeue() { + Tail = Tail + 1; + if (Tail > MaxSize - 1) { + Tail = 0; + } + Size = Size - 1; + } - // T &tail() {} + T &tail() { + return Elements[Tail]; + } - // T &head() {} + T &head() { + return Elements[Head]; + } - // T &operator[](int i) { - // int newindex; - // } + T &operator[](int i) { + int newindex = Head - i; + if (newindex < 0) { + newindex = newindex + MaxSize; + } + return Elements[newindex]; + } - // void reset() {} + void reset() { + Size = 0; + Head = -1; + Tail = 0; + } - // int size() const {} + int size() const { + return Size; + } }; #endif diff --git a/src/Speed/Indep/Libs/Support/Utility/UTLVector.h b/src/Speed/Indep/Libs/Support/Utility/UTLVector.h index 861aed70f..b8108dae2 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; @@ -117,6 +117,9 @@ template class Vector { } iterator erase(iterator begIt, iterator endIt) { + if (begIt == mBegin + mSize) { + return mBegin + mSize; + } size_type iPos = indexof(begIt); size_type num = endIt - begIt; for (iterator it = begIt; it != endIt; ++it) { @@ -124,13 +127,13 @@ template class Vector { obj.~T(); } - for (size_type ii = 0; ii < size() - (iPos + num); ++ii) { + for (size_type ii = 0; ii < size() - (iPos + num); ii++) { size_type src = iPos + num + ii; size_type dest = iPos + ii; - new (&mBegin[dest]) T(mBegin[src]); } - mSize = size() - num; + mSize -= num; + (void)size(); return end(); } @@ -150,12 +153,15 @@ 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 growSize = mCapacity + ((mCapacity + 1) >> 1); + if (growSize < minSize) { + growSize = minSize; + } + return growSize; } - // Unfinished virtual size_type GetMaxCapacity() const { - return 0; + return 0x7FFFFFFF; } virtual void OnGrowRequest(size_type newSize) {} @@ -167,7 +173,7 @@ template class Vector { size_type mSize; // offset 0x8, size 0x4 }; -template class FixedVector : public Vector { +template class FixedVector : public Vector { public: FixedVector() {} @@ -179,21 +185,21 @@ 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; + (void)minSize; + return Size; } - // Unfinished virtual typename Vector::pointer AllocVectorSpace(std::size_t num, unsigned int alignment) { - return nullptr; + (void)num; + (void)alignment; + 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..8b2357b67 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h +++ b/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h @@ -41,6 +41,11 @@ void VU0_v4scale(const UMath::Vector4 &a, const float scaleby, UMath::Vector4 &r 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_v4addxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &r); +void VU0_v4add(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &r); +void VU0_v4addscale(const UMath::Vector4 &a, const UMath::Vector4 &b, const float scaleby, UMath::Vector4 &result); +void VU0_v4addscalexyz(const UMath::Vector4 &a, const UMath::Vector4 &b, const float scaleby, UMath::Vector4 &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); @@ -209,6 +214,14 @@ inline void VU0_v4scale(const UMath::Vector4 &a, const float scaleby, UMath::Vec inline void VU0_v4scalexyz(const UMath::Vector4 &a, const float scaleby, UMath::Vector4 &result) {} +inline void VU0_v4add(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &r) {} + +inline void VU0_v4addscale(const UMath::Vector4 &a, const UMath::Vector4 &b, const float scaleby, UMath::Vector4 &result) {} + +inline void VU0_v4addscalexyz(const UMath::Vector4 &a, const UMath::Vector4 &b, const float scaleby, UMath::Vector4 &result) {} + +inline void VU0_MATRIX3x4_vect4mult(const UMath::Vector4 &v, const UMath::Matrix4 &m, UMath::Vector4 &result) {} + inline float VU0_v4distancesquarexyz(const UMath::Vector4 &p1, const UMath::Vector4 &p2) {} inline void VU0_MATRIX3x4_vect3mult(const UMath::Vector3 &v, const UMath::Matrix4 &m, UMath::Vector3 &result) { @@ -381,6 +394,15 @@ inline void VU0_v3unitcrossprod(const UMath::Vector3 &a, const UMath::Vector3 &b #endif } +inline void VU0_v4unitcrossprodxyz(const UMath::Vector4 &a, const UMath::Vector4 &b, UMath::Vector4 &dest) { +#ifdef EA_PLATFORM_PLAYSTATION2 + // PS2 asm not needed for GC target +#else + VU0_v3crossprod(UMath::Vector4To3(a), UMath::Vector4To3(b), UMath::Vector4To3(dest)); + VU0_v3unit(UMath::Vector4To3(dest), UMath::Vector4To3(dest)); +#endif +} + inline void VU0_ExtractXAxis3FromQuat(const UMath::Vector4 &quat, UMath::Vector3 &result) { const float scale = 2.0f; float yy = scale * (quat.y * quat.y); diff --git a/src/Speed/Indep/SourceLists/zPhysics.cpp b/src/Speed/Indep/SourceLists/zPhysics.cpp index b4698703b..849126b6b 100644 --- a/src/Speed/Indep/SourceLists/zPhysics.cpp +++ b/src/Speed/Indep/SourceLists/zPhysics.cpp @@ -1,5 +1,27 @@ +#include "Speed/Indep/Src/Physics/SmackableTrigger.cpp" + +#include "Speed/Indep/Src/Physics/Common/Explosion.cpp" + +#include "Speed/Indep/Src/Physics/Common/PVehicle.cpp" + +#include "Speed/Indep/Src/Physics/Common/PhysicsObject.cpp" + +#include "Speed/Indep/Src/Physics/Common/Smackable.cpp" + #include "Speed/Indep/Src/Physics/Wheel.cpp" +#include "Speed/Indep/Src/Physics/Common/VehicleBehaviors.cpp" + #include "Speed/Indep/Src/Physics/Common/VehicleSystem.cpp" +#include "Speed/Indep/Src/Physics/Common/Behavior.cpp" + +#include "Speed/Indep/Src/Physics/Common/Bounds.cpp" + +#include "Speed/Indep/Src/Physics/Common/SmokeableInfo.cpp" + +#include "Speed/Indep/Src/Physics/PhysicsUpgrades.cpp" + #include "Speed/Indep/Src/Physics/PhysicsInfo.cpp" + +#include "Speed/Indep/Src/Physics/PhysicsTunings.cpp" diff --git a/src/Speed/Indep/SourceLists/zPhysicsBehaviors.cpp b/src/Speed/Indep/SourceLists/zPhysicsBehaviors.cpp index cc5075779..2c028d45a 100644 --- a/src/Speed/Indep/SourceLists/zPhysicsBehaviors.cpp +++ b/src/Speed/Indep/SourceLists/zPhysicsBehaviors.cpp @@ -40,4 +40,18 @@ #include "Speed/Indep/Src/Physics/Behaviors/EngineTraffic.cpp" +#include "Speed/Indep/Src/Physics/Behaviors/SimpleChopper.cpp" + +#include "Speed/Indep/Src/Physics/Behaviors/DrawVehicle.cpp" + +#include "Speed/Indep/Src/Physics/Behaviors/DrawHeli.cpp" + +#include "Speed/Indep/Src/Physics/Behaviors/DrawCar.cpp" + +#include "Speed/Indep/Src/Physics/Behaviors/ResetCar.cpp" + +#include "Speed/Indep/Src/Physics/Behaviors/SpikeStrip.cpp" + #include "Speed/Indep/Src/Physics/Behaviors/SoundHeli.cpp" + +#include "Speed/Indep/Src/Physics/Behaviors/SoundCar.cpp" diff --git a/src/Speed/Indep/Src/AI/AIAvoidable.h b/src/Speed/Indep/Src/AI/AIAvoidable.h index cacb55cbd..5d39433c2 100644 --- a/src/Speed/Indep/Src/AI/AIAvoidable.h +++ b/src/Speed/Indep/Src/AI/AIAvoidable.h @@ -62,7 +62,7 @@ class ALIGN_16 AIAvoidable { return mNeighbors; } - // void SetAvoidableObject(UTL::COM::IUnknown *pUnk) {} + void SetAvoidableObject(UTL::COM::IUnknown *pUnk) { mUnk = pUnk; } template bool QueryInterface(T **out) { if (mUnk) { diff --git a/src/Speed/Indep/Src/AI/Activities/AITrafficManager.cpp b/src/Speed/Indep/Src/AI/Activities/AITrafficManager.cpp index 976864b65..a8f25d1b2 100644 --- a/src/Speed/Indep/Src/AI/Activities/AITrafficManager.cpp +++ b/src/Speed/Indep/Src/AI/Activities/AITrafficManager.cpp @@ -365,7 +365,8 @@ bool AITrafficManager::CheckRace(const WRoadNav &nav) const { bool AITrafficManager::FindSpawnPoint(WRoadNav &nav) const { RandomSortTCDir = !RandomSortTCDir; - ITrafficCenter::Sort(RandomSortTC); + // TODO clanker + // ITrafficCenter::Sort(RandomSortTC); nav.Reset(); const ITrafficCenter::List &traffic_centers = ITrafficCenter::GetList(); diff --git a/src/Speed/Indep/Src/AI/Common/AIVehiclePursuit.cpp b/src/Speed/Indep/Src/AI/Common/AIVehiclePursuit.cpp index b22641a87..4b8dd3650 100644 --- a/src/Speed/Indep/Src/AI/Common/AIVehiclePursuit.cpp +++ b/src/Speed/Indep/Src/AI/Common/AIVehiclePursuit.cpp @@ -64,7 +64,7 @@ void AIVehiclePursuit::StartPatrol() { void AIVehiclePursuit::StartFlee() { IVehicle *ivehicle; - GetVehicle()->GlareOff(LIGHT_COPS); + GetVehicle()->GlareOff(VehicleFX::LIGHT_COPS); UCrc32 goal("AIGoalFleePursuit"); if (GetSimable()->QueryInterface(&ivehicle) && ivehicle->GetVehicleClass() == VehicleClass::CHOPPER) { @@ -78,7 +78,7 @@ void AIVehiclePursuit::StartFlee() { void AIVehiclePursuit::StartRoadBlock() { IVehicle *ivehicle; - GetVehicle()->GlareOn(LIGHT_COPS); + GetVehicle()->GlareOn(VehicleFX::LIGHT_COPS); SetInPursuit(true); GetTarget()->Clear(); if (GetVehicle()->GetVehicleClass() == VehicleClass::CHOPPER) { @@ -89,7 +89,7 @@ void AIVehiclePursuit::StartRoadBlock() { } void AIVehiclePursuit::StartPursuit(AITarget *target, ISimable *itargetSimable) { - GetVehicle()->GlareOn(LIGHT_COPS); + GetVehicle()->GlareOn(VehicleFX::LIGHT_COPS); if (target) { GetTarget()->Aquire(target); } else if (itargetSimable) { @@ -110,7 +110,7 @@ void AIVehiclePursuit::DoInPositionGoal() { void AIVehiclePursuit::EndPursuit() { SetInPursuit(false); - GetVehicle()->GlareOff(LIGHT_COPS); + GetVehicle()->GlareOff(VehicleFX::LIGHT_COPS); } bool AIVehiclePursuit::StartSupportGoal() { diff --git a/src/Speed/Indep/Src/Camera/CameraAI.hpp b/src/Speed/Indep/Src/Camera/CameraAI.hpp index b4cad6624..8613a72c9 100644 --- a/src/Speed/Indep/Src/Camera/CameraAI.hpp +++ b/src/Speed/Indep/Src/Camera/CameraAI.hpp @@ -7,12 +7,16 @@ #include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +class IBody; + namespace CameraAI { void Update(float dT); void Reset(); void MaybeDoTotaledCam(IPlayer *iplayer); void MaybeDoPursuitCam(IVehicle *ivehicle); +void AddAvoidable(IBody *body); +void RemoveAvoidable(IBody *body); }; // namespace CameraAI diff --git a/src/Speed/Indep/Src/FE/FECustomizationRecord.h b/src/Speed/Indep/Src/FE/FECustomizationRecord.h new file mode 100644 index 000000000..052ed99fd --- /dev/null +++ b/src/Speed/Indep/Src/FE/FECustomizationRecord.h @@ -0,0 +1,56 @@ +#ifndef FE_FECUSTOMIZATIONRECORD_H +#define FE_FECUSTOMIZATIONRECORD_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Physics/PhysicsTunings.h" +#include "Speed/Indep/Src/Physics/PhysicsUpgrades.hpp" + +namespace Attrib { +namespace Gen { +struct pvehicle; +} +} // namespace Attrib +struct PresetCar; + +struct FECustomizationRecord { + short InstalledPartIndices[139]; // offset 0x000, size 0x116 + Physics::Upgrades::Package InstalledPhysics; // offset 0x118, size 0x20 + Physics::Tunings Tunings[Physics::NUM_CUSTOM_TUNINGS]; // offset 0x138, size 0x54 + Physics::eCustomTuningType ActiveTuning; // offset 0x18C, size 0x4 + int Preset; // offset 0x190, size 0x4 + unsigned char Handle; // offset 0x194, size 0x1 + + FECustomizationRecord(); + + static void operator delete(void *mem, unsigned int size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + void SetTuning(Physics::Tunings::Path id, float value) { + Tunings[ActiveTuning].Value[id] = value; + } + + float GetTuning(Physics::Tunings::Path id) const { + return Tunings[ActiveTuning].Value[id]; + } + + const Physics::Tunings *GetTunings() const { + return &Tunings[ActiveTuning]; + } + + Physics::Tunings *GetTunings() { + return &Tunings[ActiveTuning]; + } + + void Default(); + void BecomePreset(PresetCar *preset); + bool WriteRecordIntoPhysics(Attrib::Gen::pvehicle &vehicle) const; + bool WritePhysicsIntoRecord(const Attrib::Gen::pvehicle &vehicle); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp b/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp index dd14bbd32..f2213a91d 100644 --- a/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp +++ b/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp @@ -264,6 +264,10 @@ class cFrontendDatabase { return &CurrentUserProfiles[0]->GetOptions()->ThePlayerSettings[player]; } + GameplaySettings *GetGameplaySettings() { + return &CurrentUserProfiles[0]->GetOptions()->TheGameplaySettings; + } + FEPlayerCarDB *GetPlayerCarStable(int player) { return &CurrentUserProfiles[player]->PlayersCarStable; } diff --git a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp index 006e3698f..554c8db61 100644 --- a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp +++ b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp @@ -5,6 +5,7 @@ #pragma once #endif +#include "Speed/Indep/Src/FE/FECustomizationRecord.h" #include "Speed/Indep/Src/Gameplay/GInfractionManager.h" #include "Speed/Indep/Src/Physics/PhysicsTunings.h" #include "Speed/Indep/Src/Physics/PhysicsUpgrades.hpp" @@ -21,16 +22,6 @@ struct FECarRecord { unsigned short Padd; // offset 0x12, size 0x2 }; -// total size: 0x198 -struct FECustomizationRecord { - short InstalledPartIndices[139]; // offset 0x0, size 0x116 - Physics::Upgrades::Package InstalledPhysics; // offset 0x118, size 0x20 - Physics::Tunings Tunings[3]; // offset 0x138, size 0x54 - Physics::eCustomTuningType ActiveTuning; // offset 0x18C, size 0x4 - int Preset; // offset 0x190, size 0x4 - unsigned char Handle; // offset 0x194, size 0x1 -}; - // total size: 0x8 struct FEImpoundData { enum eImpoundReasons { diff --git a/src/Speed/Indep/Src/Gameplay/GRaceStatus.h b/src/Speed/Indep/Src/Gameplay/GRaceStatus.h index efbb40541..54cf48c2b 100644 --- a/src/Speed/Indep/Src/Gameplay/GRaceStatus.h +++ b/src/Speed/Indep/Src/Gameplay/GRaceStatus.h @@ -472,7 +472,7 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { return Exists() && Get().GetRaceType() == GRace::kRaceType_Challenge; } - PlayMode GetPlayMode() { + PlayMode GetPlayMode() const { return mPlayMode; } diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/chopperspecs.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/chopperspecs.h index fc1d517c0..55b94dc1f 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/chopperspecs.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/chopperspecs.h @@ -82,9 +82,7 @@ struct chopperspecs : Instance { Instance::Change(refspec); } - static Key ClassKey() { - return 0x5d898ee7; - } + static Key ClassKey(); const UMath::Vector4 &AIR_RESISTANCE_SCALE() const { return reinterpret_cast<_LayoutStruct *>(GetLayoutPointer())->AIR_RESISTANCE_SCALE; diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/collisionreactions.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/collisionreactions.h index 91f377d9a..46f54be4d 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/collisionreactions.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/collisionreactions.h @@ -51,6 +51,10 @@ struct collisionreactions : Instance { return *this; } +#ifdef COLLISIONREACTIONS_INSTANCE_ASSIGN_OWNER + const collisionreactions &operator=(const Instance &rhs); +#endif + void Change(const Collection *c) { Instance::Change(c); } diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/effects.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/effects.h index 95325db9b..9d573f139 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/effects.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/effects.h @@ -60,9 +60,7 @@ struct effects : Instance { Instance::Change(refspec); } - static Key ClassKey() { - return 0xebcee74c; - } + static Key ClassKey(); const float &InheritVelocity() const { const float *resultptr = reinterpret_cast(GetAttributePointer(0x0099cb26, 0)); diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/engine.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/engine.h index 74336534a..503612e69 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/engine.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/engine.h @@ -36,6 +36,10 @@ struct engine : Instance { this->SetDefaultLayout(sizeof(_LayoutStruct)); } + engine(const RefSpec &refspec, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(refspec, msgPort, owner) { + this->SetDefaultLayout(sizeof(_LayoutStruct)); + } + engine(const Collection *collection, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(collection, msgPort, owner) { this->SetDefaultLayout(sizeof(_LayoutStruct)); } diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/induction.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/induction.h index 0001dd5b5..0f7e82fc6 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/induction.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/induction.h @@ -33,6 +33,10 @@ struct induction : Instance { this->SetDefaultLayout(sizeof(_LayoutStruct)); } + induction(const RefSpec &refspec, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(refspec, msgPort, owner) { + this->SetDefaultLayout(sizeof(_LayoutStruct)); + } + induction(const Collection *collection, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(collection, msgPort, owner) { this->SetDefaultLayout(sizeof(_LayoutStruct)); } diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h index 3e03054bf..6220a1e4d 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h @@ -59,8 +59,8 @@ struct pvehicle : Instance { struct _LayoutStruct { UMath::Vector4 TENSOR_SCALE; // offset 0x0, size 0x10 Attrib::StringKey MODEL; // offset 0x10, size 0x10 - char DefaultPresetRide[4]; // offset 0x20, size 0x4 - char CollectionName[4]; // offset 0x24, size 0x4 + const char *DefaultPresetRide; // offset 0x20, size 0x4 + const char *CollectionName; // offset 0x24, size 0x4 int engine_upgrades; // offset 0x28, size 0x4 int transmission_upgrades; // offset 0x2c, size 0x4 int nos_upgrades; // offset 0x30, size 0x4 @@ -99,6 +99,11 @@ struct pvehicle : Instance { this->SetDefaultLayout(sizeof(_LayoutStruct)); } + pvehicle(const RefSpec &refspec, unsigned int msgPort, UTL::COM::IUnknown *owner) + : Instance(refspec, msgPort, owner) { + this->SetDefaultLayout(sizeof(_LayoutStruct)); + } + ~pvehicle() {} void Change(const Collection *c) { @@ -113,6 +118,25 @@ struct pvehicle : Instance { return 0x4a97ec8f; } + Instance &GetBase() { + return *this; + } + + const Instance &GetBase() const { + return *this; + } + + const pvehicle &operator=(const pvehicle &rhs) { + operator=(rhs.GetBase()); + return *this; + } + + const pvehicle &operator=(const Instance &rhs); + + void Modify(Key dynamicCollectionKey, unsigned int spaceForAdditionalAttributes) { + Instance::Modify(dynamicCollectionKey, spaceForAdditionalAttributes); + } + const RefSpec &transmission(unsigned int index) const { const RefSpec *resultptr = reinterpret_cast(GetAttributePointer(0x07a7a3e5, index)); if (!resultptr) { @@ -609,11 +633,11 @@ struct pvehicle : Instance { return reinterpret_cast<_LayoutStruct *>(GetLayoutPointer())->MODEL; } - const char *DefaultPresetRide() const { + const char *const &DefaultPresetRide() const { return reinterpret_cast<_LayoutStruct *>(GetLayoutPointer())->DefaultPresetRide; } - const char *CollectionName() const { + const char *const &CollectionName() const { return reinterpret_cast<_LayoutStruct *>(GetLayoutPointer())->CollectionName; } @@ -663,6 +687,7 @@ struct pvehicle : Instance { }; } // namespace Gen + } // namespace Attrib #endif diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/smackable.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/smackable.h index eeacc25db..9c15d6d02 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/smackable.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/smackable.h @@ -13,6 +13,25 @@ #include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" #include "Speed/Indep/Tools/AttribSys/Runtime/Common/AttribPrivate.h" +enum eDRIVE_BY_TYPE { + DRIVE_BY_UNKNOWN = 0, + DRIVE_BY_TREE = 1, + DRIVE_BY_LAMPPOST = 2, + DRIVE_BY_SMOKABLE = 3, + DRIVE_BY_TUNNEL_IN = 4, + DRIVE_BY_TUNNEL_OUT = 5, + DRIVE_BY_OVERPASS_IN = 6, + DRIVE_BY_OVERPASS_OUT = 7, + DRIVE_BY_AI_CAR = 8, + DRIVE_BY_TRAFFIC = 9, + DRIVE_BY_BRIDGE = 10, + DRIVE_BY_PRE_COL = 11, + DRIVE_BY_CAMERA_BY = 12, + MAX_DRIVE_BY_TYPES = 13, +}; + +struct EffectLinkageRecord; + namespace Attrib { namespace Gen { @@ -36,19 +55,23 @@ struct smackable : Instance { smackable(Key collectionKey, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(FindCollection(ClassKey(), collectionKey), msgPort, owner) { - SetDefaultLayout(sizeof(_LayoutStruct)); + SetDefaultLayout(0x28); } smackable(const Collection *collection, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(collection, msgPort, owner) { - SetDefaultLayout(sizeof(_LayoutStruct)); + SetDefaultLayout(0x28); } smackable(const smackable &src) : Instance(src) { - SetDefaultLayout(sizeof(_LayoutStruct)); + SetDefaultLayout(0x28); + } + + smackable(const Instance &src) : Instance(src) { + SetDefaultLayout(0x28); } smackable(const RefSpec &refspec, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(refspec, msgPort, owner) { - SetDefaultLayout(sizeof(_LayoutStruct)); + SetDefaultLayout(0x28); } ~smackable() {} @@ -65,8 +88,14 @@ struct smackable : Instance { Instance::Change(refspec); } - static Key ClassKey() { - return 0xce70d7db; + static Key ClassKey(); + + Instance &GetBase() { + return *this; + } + + const Instance &GetBase() const { + return *this; } const EffectLinkageRecord &OnHitObject(unsigned int index) const { @@ -82,7 +111,8 @@ struct smackable : Instance { } const float &ExplosionEffect() const { - const float *resultptr = reinterpret_cast(GetAttributePointer(0x360552da, 0)); + const float *resultptr; + resultptr = reinterpret_cast(GetAttributePointer(0x360552da, 0)); if (!resultptr) { resultptr = reinterpret_cast(DefaultDataArea(sizeof(float))); } @@ -301,6 +331,16 @@ struct smackable : Instance { return *resultptr; } + bool MOMENT(UMath::Vector3 &result) const { + const UMath::Vector3 *resultptr = + reinterpret_cast(GetAttributePointer(0xfb19212f, 0)); + if (resultptr) { + result = *resultptr; + return true; + } + return false; + } + const char *CollectionName() const { return reinterpret_cast<_LayoutStruct *>(GetLayoutPointer())->CollectionName; } diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/transmission.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/transmission.h index ce2657579..45dd23430 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/transmission.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/transmission.h @@ -39,6 +39,10 @@ struct transmission : Instance { this->SetDefaultLayout(sizeof(_LayoutStruct)); } + transmission(const RefSpec &refspec, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(refspec, msgPort, owner) { + this->SetDefaultLayout(sizeof(_LayoutStruct)); + } + transmission(const Collection *collection, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(collection, msgPort, owner) { this->SetDefaultLayout(sizeof(_LayoutStruct)); } diff --git a/src/Speed/Indep/Src/Generated/AttribSys/GenericAccessor.h b/src/Speed/Indep/Src/Generated/AttribSys/GenericAccessor.h index 47f6f9aa5..f924264b8 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/GenericAccessor.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/GenericAccessor.h @@ -13,6 +13,17 @@ namespace Gen { class GenericAccessor : private Attrib::Instance { public: + const Attrib::StringKey &BEHAVIOR_ORDER(unsigned int index) const { + const Attrib::StringKey *resultptr = + reinterpret_cast(GetAttributePointer(0x104e9d16, index)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(Attrib::StringKey))); + } + return *resultptr; + } + + unsigned int Num_BEHAVIOR_ORDER() const { return Get(0x104e9d16).GetLength(); } + bool NO_CAR_EFFECT(bool &val, unsigned int index) const { // NO_CAR_EFFECT Attrib::TAttrib attr = Attrib::TAttrib(Get(0x1f989f01)); diff --git a/src/Speed/Indep/Src/Interfaces/IBody.h b/src/Speed/Indep/Src/Interfaces/IBody.h index 0b9704a59..992b77dd8 100644 --- a/src/Speed/Indep/Src/Interfaces/IBody.h +++ b/src/Speed/Indep/Src/Interfaces/IBody.h @@ -11,13 +11,11 @@ class IBody : public UTL::COM::IUnknown { public: - static HINTERFACE _IHandle() { - return (HINTERFACE)_IHandle; - } + static HINTERFACE _IHandle(); - IBody(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + IBody(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, (HINTERFACE)_IHandle) {} - virtual ~IBody() {} + virtual ~IBody(); virtual void GetTransform(UMath::Matrix4 &) const; virtual void GetLinearVelocity(UMath::Vector3 &) const; diff --git a/src/Speed/Indep/Src/Interfaces/SimActivities/IVehicleCache.h b/src/Speed/Indep/Src/Interfaces/SimActivities/IVehicleCache.h index 27223cbe4..f578205d1 100644 --- a/src/Speed/Indep/Src/Interfaces/SimActivities/IVehicleCache.h +++ b/src/Speed/Indep/Src/Interfaces/SimActivities/IVehicleCache.h @@ -27,9 +27,9 @@ class IVehicleCache : public UTL::COM::IUnknown, public UTL::Collections::Listab virtual ~IVehicleCache() {} public: - virtual void OnRemovedVehicleCache(IVehicle *ivehicle); - virtual eVehicleCacheResult OnQueryVehicleCache(const IVehicle *removethis, const IVehicleCache *whosasking) const; virtual const char *GetCacheName() const; + virtual eVehicleCacheResult OnQueryVehicleCache(const IVehicle *removethis, const IVehicleCache *whosasking) const; + virtual void OnRemovedVehicleCache(IVehicle *ivehicle); }; #endif diff --git a/src/Speed/Indep/Src/Interfaces/SimEntities/IEntity.h b/src/Speed/Indep/Src/Interfaces/SimEntities/IEntity.h index 20e35f434..e313c4075 100644 --- a/src/Speed/Indep/Src/Interfaces/SimEntities/IEntity.h +++ b/src/Speed/Indep/Src/Interfaces/SimEntities/IEntity.h @@ -25,9 +25,13 @@ class IEntity : public UTL::COM::IUnknown, public UTL::Collections::ListableSet, public UTL::COM::Factory { public: +#ifdef ZPHYSICS_OUTLINE_IENTITY_HANDLE + static HINTERFACE _IHandle(); +#else static HINTERFACE _IHandle() { return (HINTERFACE)_IHandle; } +#endif IEntity(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} diff --git a/src/Speed/Indep/Src/Interfaces/SimModels/IModel.h b/src/Speed/Indep/Src/Interfaces/SimModels/IModel.h index 5ae504b8f..97f1dfdf8 100644 --- a/src/Speed/Indep/Src/Interfaces/SimModels/IModel.h +++ b/src/Speed/Indep/Src/Interfaces/SimModels/IModel.h @@ -27,13 +27,11 @@ class IModel : public UTL::COM::IUnknown, public UTL::Collections::Instanceable< virtual bool OnModel(IModel *model); }; - static HINTERFACE _IHandle() { - return (HINTERFACE)_IHandle; - } + static HINTERFACE _IHandle(); - IModel(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + IModel(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, (HINTERFACE)_IHandle) {} - virtual ~IModel() {} + virtual ~IModel(); virtual void OnProcessFrame(float dT); virtual UCrc32 GetPartName() const; @@ -45,8 +43,8 @@ class IModel : public UTL::COM::IUnknown, public UTL::Collections::Instanceable< virtual ISimable *GetSimable() const; virtual bool IsHidden() const; virtual void HideModel(); - virtual bool InView() const; - virtual float DistanceToView() const; + virtual bool InView() const = 0; + virtual float DistanceToView() const = 0; virtual void GetLinearVelocity(UMath::Vector3 &velocity) const; virtual void GetAngularVelocity(UMath::Vector3 &velocity) const; virtual void GetTransform(UMath::Matrix4 &matrix) const; @@ -60,9 +58,9 @@ class IModel : public UTL::COM::IUnknown, public UTL::Collections::Instanceable< virtual IModel *SpawnModel(UCrc32 rendernode, UCrc32 collisionnode, UCrc32 attributes); virtual void ReleaseModel(); virtual void ReleaseChildModels(); - virtual void HidePart(const UCrc32 &nodename); - virtual void ShowPart(const UCrc32 &nodename); - virtual bool IsPartVisible(const UCrc32 &nodename) const; + virtual void HidePart(const UCrc32 &nodename) = 0; + virtual void ShowPart(const UCrc32 &nodename) = 0; + virtual bool IsPartVisible(const UCrc32 &nodename) const = 0; virtual void PlayEffect(UCrc32 identifire, const Attrib::Collection *effect, const UMath::Vector3 &position, const UMath::Vector3 &magnitude, bool tracking); virtual void StopEffect(UCrc32 identifire); diff --git a/src/Speed/Indep/Src/Interfaces/SimModels/IPlaceableScenery.h b/src/Speed/Indep/Src/Interfaces/SimModels/IPlaceableScenery.h index e8a77a3ba..f9b4fd28b 100644 --- a/src/Speed/Indep/Src/Interfaces/SimModels/IPlaceableScenery.h +++ b/src/Speed/Indep/Src/Interfaces/SimModels/IPlaceableScenery.h @@ -11,17 +11,17 @@ // total size: 0x8 class IPlaceableScenery : public UTL::COM::IUnknown, public UTL::Collections::Countable { public: - static HINTERFACE _IHandle() { - return (HINTERFACE)_IHandle; - } + static HINTERFACE _IHandle(); - IPlaceableScenery(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + IPlaceableScenery(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, (HINTERFACE)_IHandle) {} - virtual ~IPlaceableScenery() {} + virtual ~IPlaceableScenery(); + virtual void Destroy(); virtual void PickUp(); virtual bool Place(const UMath::Matrix4 &transform, bool snap_to_ground); - virtual void Destroy(); + + static IPlaceableScenery *CreateInstance(const char *name, unsigned int attributes); }; #endif diff --git a/src/Speed/Indep/Src/Interfaces/SimModels/ISceneryModel.h b/src/Speed/Indep/Src/Interfaces/SimModels/ISceneryModel.h index afc2c5701..611f8a906 100644 --- a/src/Speed/Indep/Src/Interfaces/SimModels/ISceneryModel.h +++ b/src/Speed/Indep/Src/Interfaces/SimModels/ISceneryModel.h @@ -19,11 +19,11 @@ class ISceneryModel : public UTL::COM::IUnknown { virtual ~ISceneryModel() {} - virtual unsigned int GetSpawnerID() {} + virtual bool GetSceneryTransform(UMath::Matrix4 &matrix) const; virtual void RestoreScene(); - virtual bool GetSceneryTransform(UMath::Matrix4) const; + virtual unsigned int GetSpawnerID() const {} virtual void WakeUp(); - virtual bool IsExcluded(unsigned int scenery_exclusion_flag) {} + virtual bool IsExcluded(unsigned int scenery_exclusion_flag) const {} }; #endif diff --git a/src/Speed/Indep/Src/Interfaces/SimModels/ITriggerableModel.h b/src/Speed/Indep/Src/Interfaces/SimModels/ITriggerableModel.h index c37a328fb..16d3bd138 100644 --- a/src/Speed/Indep/Src/Interfaces/SimModels/ITriggerableModel.h +++ b/src/Speed/Indep/Src/Interfaces/SimModels/ITriggerableModel.h @@ -5,6 +5,20 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +class ITriggerableModel : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + ITriggerableModel(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual ~ITriggerableModel() {} + + virtual void PlaceTrigger(const UMath::Matrix4 &mat, bool retrigger) = 0; +}; #endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IAI.h b/src/Speed/Indep/Src/Interfaces/Simables/IAI.h index 0ecefb290..b316353c0 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IAI.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IAI.h @@ -15,8 +15,18 @@ #include "Speed/Indep/Src/Interfaces/SimModels/IPlaceableScenery.h" #include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" #include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Src/Sim/SimTypes.h" #include "Speed/Indep/Src/World/WRoadNetwork.h" +struct AIParams : public Sim::Param { + AIParams() : Sim::Param(TypeName(), static_cast(nullptr)) {} + + static UCrc32 TypeName() { + static UCrc32 value = "AIParams"; + return value; + } +}; + // TODO move? enum eLaneSelection { SELECT_CENTER_LANE = 0, diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IArticulatedVehicle.h b/src/Speed/Indep/Src/Interfaces/Simables/IArticulatedVehicle.h index c2220b80f..b81d2fc40 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IArticulatedVehicle.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IArticulatedVehicle.h @@ -16,7 +16,7 @@ class IArticulatedVehicle : public UTL::COM::IUnknown { IArticulatedVehicle(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~IArticulatedVehicle() {} + virtual ~IArticulatedVehicle(); virtual IVehicle *GetTrailer() const; virtual void SetHitch(bool hitched); diff --git a/src/Speed/Indep/Src/Interfaces/Simables/ICarAudio.h b/src/Speed/Indep/Src/Interfaces/Simables/ICarAudio.h index 38627aac3..58a932053 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/ICarAudio.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/ICarAudio.h @@ -5,6 +5,19 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +// total size: 0x8 +class ICarAudio : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle(); + + ICarAudio(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, (HINTERFACE)_IHandle) {} + + virtual ~ICarAudio() {} + + virtual Rpm GetRPM() const = 0; +}; #endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/ICause.h b/src/Speed/Indep/Src/Interfaces/Simables/ICause.h index b8a3567ff..20478ef23 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/ICause.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/ICause.h @@ -9,6 +9,13 @@ #include "Speed/Indep/Libs/Support/Utility/UCOM.h" #include "Speed/Indep/Libs/Support/Utility/UCollections.h" +namespace Sim { +namespace Collision { +struct Info; +} +} +typedef Sim::Collision::Info COLLISION_INFO; + struct HCAUSE__ { // total size: 0x4 int unused; // offset 0x0, size 0x4 @@ -29,6 +36,7 @@ class ICause : public UTL::COM::IUnknown, public UTL::Collections::Instanceable< virtual ~ICause() {} // virtual void OnCausedCollision(const COLLISION_INFO &cinfo, ISimable *from, ISimable *to); + virtual void OnCausedCollision(const COLLISION_INFO &cinfo, ISimable *from, ISimable *to) = 0; virtual void OnCausedExplosion(IExplosion *explosion, ISimable *to); }; diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IDamageable.h b/src/Speed/Indep/Src/Interfaces/Simables/IDamageable.h index 2db7ec983..703bee3fe 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IDamageable.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IDamageable.h @@ -17,7 +17,7 @@ class IDamageable : public UTL::COM::IUnknown { IDamageable(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~IDamageable() {} + virtual ~IDamageable(); virtual void SetInShock(float scale); virtual void SetShockForce(float f); @@ -37,7 +37,7 @@ class IDamageableVehicle : public UTL::COM::IUnknown { IDamageableVehicle(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~IDamageableVehicle() {} + virtual ~IDamageableVehicle(); virtual bool IsLightDamaged(VehicleFX::ID idx) const; virtual void DamageLight(VehicleFX::ID idx, bool b); diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IDisposable.h b/src/Speed/Indep/Src/Interfaces/Simables/IDisposable.h index 1a41dcb1b..34a4aa968 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IDisposable.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IDisposable.h @@ -16,7 +16,7 @@ class IDisposable : public UTL::COM::IUnknown, public UTL::Collections::Listable IDisposable(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~IDisposable() {} + virtual ~IDisposable(); public: virtual bool IsRequired() const; diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IEffects.h b/src/Speed/Indep/Src/Interfaces/Simables/IEffects.h index 5664585fb..1dfe3431a 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IEffects.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IEffects.h @@ -5,6 +5,25 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +class IEffects : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + IEffects(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual ~IEffects() {} + + virtual void HitGround(); + virtual void HitWorld(); + virtual void HitObject(); + virtual void ScrapeObject(); + virtual void ScrapeGround(); + virtual void ScrapeWorld(); + virtual void Purge(); +}; #endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IEngine.h b/src/Speed/Indep/Src/Interfaces/Simables/IEngine.h index db3c2ede5..d1655756a 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IEngine.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IEngine.h @@ -6,6 +6,7 @@ #endif #include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Src/Sim/SimTypes.h" #include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" // Credits: Brawltendo @@ -20,21 +21,32 @@ class IEngine : public UTL::COM::IUnknown { virtual ~IEngine() {} public: - virtual float GetRPM() const; - virtual float GetRedline() const; - virtual float GetMaxRPM() const; - virtual float GetMinRPM() const; - virtual float GetPeakTorqueRPM() const; - virtual void MatchSpeed(float speed); - virtual float GetNOSCapacity() const; - virtual bool IsNOSEngaged() const; - virtual float GetNOSFlowRate() const; - virtual float GetNOSBoost() const; - virtual bool HasNOS() const; - virtual void ChargeNOS(float charge); - virtual float GetMaxHorsePower() const; - virtual Hp GetMinHorsePower() const; - virtual float GetHorsePower() const; + virtual float GetRPM() const = 0; + virtual float GetRedline() const = 0; + virtual float GetMaxRPM() const = 0; + virtual float GetMinRPM() const = 0; + virtual float GetPeakTorqueRPM() const = 0; + virtual void MatchSpeed(float speed) = 0; + virtual float GetNOSCapacity() const = 0; + virtual bool IsNOSEngaged() const = 0; + virtual float GetNOSFlowRate() const = 0; + virtual float GetNOSBoost() const = 0; + virtual bool HasNOS() const = 0; + virtual void ChargeNOS(float charge) = 0; + virtual float GetMaxHorsePower() const = 0; + virtual Hp GetMinHorsePower() const = 0; + virtual float GetHorsePower() const = 0; +}; + +struct EngineParams : public Sim::Param { + EngineParams(const EngineParams &_ctor_arg) : Sim::Param(_ctor_arg) {} + + EngineParams() : Sim::Param(TypeName(), static_cast(nullptr)) {} + + static UCrc32 TypeName() { + static UCrc32 value = "EngineParams"; + return value; + } }; class IDragEngine : public UTL::COM::IUnknown { @@ -48,22 +60,20 @@ class IDragEngine : public UTL::COM::IUnknown { virtual ~IDragEngine() {} public: - virtual float GetOverRev() const; - virtual float GetHeat() const; + virtual float GetOverRev() const = 0; + virtual float GetHeat() const = 0; }; class IRaceEngine : public UTL::COM::IUnknown { public: - static HINTERFACE _IHandle() { - return (HINTERFACE)_IHandle; - } + static HINTERFACE _IHandle(); - IRaceEngine(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + IRaceEngine(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, (HINTERFACE)_IHandle) {} virtual ~IRaceEngine() {} public: - virtual float GetPerfectLaunchRange(float &range); + virtual float GetPerfectLaunchRange(float &range) = 0; }; #endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IExplodeable.h b/src/Speed/Indep/Src/Interfaces/Simables/IExplodeable.h index 655921b64..6b36020c5 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IExplodeable.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IExplodeable.h @@ -5,6 +5,22 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Libs/Support/Utility/UTypes.h" +class IExplosion; + +class IExplodeable : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + IExplodeable(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual ~IExplodeable() {} + + virtual bool OnExplosion(const UMath::Vector3 &normal, const UMath::Vector3 &position, float dT, IExplosion *explosion) = 0; +}; #endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IExplosion.h b/src/Speed/Indep/Src/Interfaces/Simables/IExplosion.h index 5ae527620..3b6f89bf0 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IExplosion.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IExplosion.h @@ -9,6 +9,12 @@ #include "Speed/Indep/Libs/Support/Utility/UListable.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" +struct HCAUSE__; +typedef HCAUSE__ *HCAUSE; + +struct HMODEL__; +typedef HMODEL__ *HMODEL; + class IExplosion : public UTL::COM::IUnknown, public UTL::Collections::Listable { public: static HINTERFACE _IHandle() { @@ -19,11 +25,15 @@ class IExplosion : public UTL::COM::IUnknown, public UTL::Collections::Listable< virtual ~IExplosion() {} - virtual const UMath::Vector3 &GetOrigin() const; - virtual float GetExpansionSpeed() const; - virtual float GetMaximumRadius() const; - virtual float GetRadius() const; - // TODO rest, why isn't GetCausality in the dwarf? + virtual const UMath::Vector3 &GetOrigin() const = 0; + virtual float GetExpansionSpeed() const = 0; + virtual float GetMaximumRadius() const = 0; + virtual float GetRadius() const = 0; + virtual HCAUSE GetCausality() const = 0; + virtual float GetCausalityTime() const = 0; + virtual bool HasDamage() const = 0; + virtual unsigned int GetTargets() const = 0; + virtual HMODEL GetSource() const = 0; }; #endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IINput.h b/src/Speed/Indep/Src/Interfaces/Simables/IINput.h index dc66f59cd..9c89f0cbd 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IINput.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IINput.h @@ -48,7 +48,7 @@ class IInput : public UTL::COM::IUnknown { virtual void ClearInput(); virtual InputControls &GetControls() const; - virtual float GetControlHandBrake() const; + virtual bool GetControlHandBrake() const; virtual bool GetControlActionButton() const; virtual void SetControlSteering(float steer); virtual void SetControlGas(float gas); @@ -72,7 +72,7 @@ class IInputPlayer : public UTL::COM::IUnknown, public UTL::Collections::Listabl IInputPlayer(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~IInputPlayer() {} + virtual ~IInputPlayer(); virtual void BlockInput(bool block); virtual bool IsBlocked() const; diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IInductable.h b/src/Speed/Indep/Src/Interfaces/Simables/IInductable.h index a293bb7e2..a07c354af 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IInductable.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IInductable.h @@ -17,7 +17,7 @@ class IInductable : public UTL::COM::IUnknown { IInductable(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~IInductable() {} + virtual ~IInductable(); public: virtual Physics::Info::eInductionType InductionType() const; diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IRecordablePlayer.h b/src/Speed/Indep/Src/Interfaces/Simables/IRecordablePlayer.h new file mode 100644 index 000000000..d31921601 --- /dev/null +++ b/src/Speed/Indep/Src/Interfaces/Simables/IRecordablePlayer.h @@ -0,0 +1,31 @@ +#ifndef _IRecordablePlayer +#define _IRecordablePlayer + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Libs/Support/Utility/UListable.h" + +struct IRecordablePlayer : public UTL::COM::IUnknown, + public UTL::Collections::Listable { + static HINTERFACE _IHandle() { return (HINTERFACE)_IHandle; } + + IRecordablePlayer(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual ~IRecordablePlayer() {} + + virtual void StartRecording() = 0; + virtual void StopRecording() = 0; + virtual void StartPlayback() = 0; + virtual void Pause() = 0; + virtual void UnPause() = 0; + virtual float GetTimeTotalLength() = 0; + virtual float GetTimeElapsed() = 0; + virtual void SetTime() = 0; + virtual void StopPlayback() = 0; + virtual unsigned int GetPacketSize() = 0; +}; + +#endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IRenderable.h b/src/Speed/Indep/Src/Interfaces/Simables/IRenderable.h index 0d9344ae0..0a6690c39 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IRenderable.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IRenderable.h @@ -23,7 +23,7 @@ class IRenderable : public UTL::COM::IUnknown { virtual HMODEL GetModelHandle() const = 0; virtual const IModel *GetModel() const = 0; virtual IModel *GetModel() = 0; - virtual float DistanceToView() = 0; + virtual float DistanceToView() const = 0; }; #endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h b/src/Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h index 761156d5f..caf5ca078 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h @@ -11,13 +11,11 @@ class ISimpleBody : public UTL::COM::IUnknown, public UTL::Collections::Listable { public: - static HINTERFACE _IHandle() { - return (HINTERFACE)_IHandle; - } + static HINTERFACE _IHandle(); - ISimpleBody(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + ISimpleBody(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, (HINTERFACE)_IHandle) {} - virtual ~ISimpleBody() {} + virtual ~ISimpleBody(); public: virtual void ModifyFlags(unsigned int uRemove, unsigned int uAdd); diff --git a/src/Speed/Indep/Src/Interfaces/Simables/ISpikeable.h b/src/Speed/Indep/Src/Interfaces/Simables/ISpikeable.h index 1a8a49c46..19ff66514 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/ISpikeable.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/ISpikeable.h @@ -17,7 +17,7 @@ class ISpikeable : public UTL::COM::IUnknown, public UTL::Collections::Listable< ISpikeable(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~ISpikeable() {} + virtual ~ISpikeable(); public: virtual eTireDamage GetTireDamage(unsigned int wheelId) const; diff --git a/src/Speed/Indep/Src/Interfaces/Simables/ITiptronic.h b/src/Speed/Indep/Src/Interfaces/Simables/ITiptronic.h index 6d28c3d43..6417b5758 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/ITiptronic.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/ITiptronic.h @@ -17,7 +17,7 @@ class ITiptronic : public UTL::COM::IUnknown { ITiptronic(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~ITiptronic() {} + virtual ~ITiptronic(); public: virtual bool SportShift(GearID gear); diff --git a/src/Speed/Indep/Src/Interfaces/Simables/ITransmission.h b/src/Speed/Indep/Src/Interfaces/Simables/ITransmission.h index 8a78d1759..7c627fb36 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/ITransmission.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/ITransmission.h @@ -20,31 +20,29 @@ class ITransmission : public UTL::COM::IUnknown { virtual ~ITransmission() {} public: - virtual GearID GetGear() const; - virtual GearID GetTopGear() const; - virtual bool Shift(GearID to_gear); - virtual bool IsGearChanging() const; - virtual bool IsReversing() const; - virtual float GetSpeedometer() const; - virtual float GetMaxSpeedometer() const; - virtual float GetDriveTorque() const; - virtual float GetShiftPoint(GearID from_gear, GearID to_gear) const; - virtual ShiftStatus GetShiftStatus() const; - virtual ShiftPotential GetShiftPotential() const; + virtual GearID GetGear() const = 0; + virtual GearID GetTopGear() const = 0; + virtual bool Shift(GearID to_gear) = 0; + virtual bool IsGearChanging() const = 0; + virtual bool IsReversing() const = 0; + virtual float GetSpeedometer() const = 0; + virtual float GetMaxSpeedometer() const = 0; + virtual float GetDriveTorque() const = 0; + virtual float GetShiftPoint(GearID from_gear, GearID to_gear) const = 0; + virtual ShiftStatus GetShiftStatus() const = 0; + virtual ShiftPotential GetShiftPotential() const = 0; }; class IDragTransmission : public UTL::COM::IUnknown { public: - static HINTERFACE _IHandle() { - return (HINTERFACE)_IHandle; - } + static HINTERFACE _IHandle(); - IDragTransmission(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + IDragTransmission(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, (HINTERFACE)_IHandle) {} virtual ~IDragTransmission() {} public: - virtual float GetShiftBoost() const; + virtual float GetShiftBoost() const = 0; }; #endif diff --git a/src/Speed/Indep/Src/Interfaces/Simables/IVehicle.h b/src/Speed/Indep/Src/Interfaces/Simables/IVehicle.h index c5e5cb51a..f1bc49761 100644 --- a/src/Speed/Indep/Src/Interfaces/Simables/IVehicle.h +++ b/src/Speed/Indep/Src/Interfaces/Simables/IVehicle.h @@ -127,31 +127,6 @@ enum DriverClass { DRIVER_REMOTE = 6, }; -// TODO move? -enum ID { - LIGHT_NONE = 0, - LIGHT_LHEAD = 1, - LIGHT_RHEAD = 2, - LIGHT_CHEAD = 4, - LIGHT_LBRAKE = 8, - LIGHT_RBRAKE = 16, - LIGHT_CBRAKE = 32, - LIGHT_LREVERSE = 64, - LIGHT_RREVERSE = 128, - LIGHT_LRSIGNAL = 256, - LIGHT_RRSIGNAL = 512, - LIGHT_LFSIGNAL = 1024, - LIGHT_RFSIGNAL = 2048, - LIGHT_COPRED = 4096, - LIGHT_COPBLUE = 8192, - LIGHT_COPWHITE = 16384, - LIGHT_COPS = 28672, - LIGHT_LSIGNAL = 1280, - LIGHT_RSIGNAL = 2560, - LIGHT_HEADLIGHTS = 7, - LIGHT_REVERSE = 192, - LIGHT_BRAKELIGHTS = 56, -}; class IVehicle : public UTL::COM::IUnknown, public UTL::Collections::ListableSet { public: @@ -164,7 +139,7 @@ class IVehicle : public UTL::COM::IUnknown, public UTL::Collections::ListableSet virtual ~IVehicle() {} virtual const ISimable *GetSimable() const = 0; virtual ISimable *GetSimable() = 0; - virtual UMath::Vector3 &GetPosition() const = 0; + virtual const UMath::Vector3 &GetPosition() const = 0; virtual void SetBehaviorOverride(UCrc32 mechanic, UCrc32 behavior) = 0; virtual void RemoveBehaviorOverride(UCrc32 mechanic) = 0; virtual void CommitBehaviorOverrides() = 0; @@ -179,7 +154,7 @@ class IVehicle : public UTL::COM::IUnknown, public UTL::Collections::ListableSet virtual CarType GetModelType() const = 0; virtual bool IsSpooled() const = 0; virtual const UCrc32 &GetVehicleClass() const = 0; - virtual Attrib::Gen::pvehicle &GetVehicleAttributes() const = 0; + virtual const Attrib::Gen::pvehicle &GetVehicleAttributes() const = 0; virtual const char *GetVehicleName() const = 0; virtual unsigned int GetVehicleKey() const = 0; virtual void SetDriverClass(DriverClass dc) = 0; @@ -200,13 +175,13 @@ class IVehicle : public UTL::COM::IUnknown, public UTL::Collections::ListableSet virtual float GetSpeed() const = 0; virtual void SetSpeed(float speed) = 0; virtual float GetAbsoluteSpeed() const = 0; - virtual bool IsGlareOn(ID glare) const = 0; - virtual void GlareOn(ID glare) = 0; - virtual void GlareOff(ID glare) = 0; + virtual bool IsGlareOn(VehicleFX::ID glare) = 0; + virtual void GlareOn(VehicleFX::ID glare) = 0; + virtual void GlareOff(VehicleFX::ID glare) = 0; virtual bool IsCollidingWithSoftBarrier() = 0; virtual class IVehicleAI *GetAIVehiclePtr() const = 0; virtual float GetSlipAngle() const = 0; - virtual UMath::Vector3 &GetLocalVelocity() const = 0; + virtual const UMath::Vector3 &GetLocalVelocity() const = 0; virtual void ComputeHeading(UMath::Vector3 *v) = 0; virtual bool IsAnimating() const = 0; virtual void SetAnimating(bool animate) = 0; diff --git a/src/Speed/Indep/Src/Main/Event.h b/src/Speed/Indep/Src/Main/Event.h index 16461c9b3..8502b08b0 100644 --- a/src/Speed/Indep/Src/Main/Event.h +++ b/src/Speed/Indep/Src/Main/Event.h @@ -31,6 +31,20 @@ class Event { std::size_t fEventSize; }; +// 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: 0x10 +struct EventList { + unsigned int fNumEvents; // offset 0x0, size 0x4 + unsigned int fPad[3]; // offset 0x4, size 0xC +}; + // total size: 0x1 class EventManager { public: diff --git a/src/Speed/Indep/Src/Main/EventSequencer.h b/src/Speed/Indep/Src/Main/EventSequencer.h index 56bc5950c..f0ca953d2 100644 --- a/src/Speed/Indep/Src/Main/EventSequencer.h +++ b/src/Speed/Indep/Src/Main/EventSequencer.h @@ -34,13 +34,11 @@ class System; class IContext : public UTL::COM::IUnknown { public: - static HINTERFACE _IHandle() { - return (HINTERFACE)_IHandle; - } + static HINTERFACE _IHandle(); - IContext(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + IContext(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, (HINTERFACE)_IHandle) {} - virtual ~IContext() {} + virtual ~IContext(); virtual bool SetDynamicData(const System *system, EventDynamicData *data); }; @@ -55,6 +53,7 @@ typedef HENGINE__ *HENGINE; enum QueueMode { QUEUE_DISABLE = 0, QUEUE_SHALLOW = 1, + QUEUE = 2, QUEUE_ALLOW = 2, QUEUE_FLUSH = 3, QUEUE_ABORT = 4, @@ -101,6 +100,7 @@ struct System { kQueueLength = 4, }; unsigned int ID() const; + bool ProcessStimulus(unsigned int stimulus, float externalTime, IContext *context, QueueMode mode); // TODO it's EventSequencer::Engine struct Engine *mEngine; // offset 0x0, size 0x4 diff --git a/src/Speed/Indep/Src/Main/stubs.h b/src/Speed/Indep/Src/Main/stubs.h index 26bdb87cb..58765e360 100644 --- a/src/Speed/Indep/Src/Main/stubs.h +++ b/src/Speed/Indep/Src/Main/stubs.h @@ -5,6 +5,10 @@ #pragma once #endif +inline void dbattrib(float, float, unsigned int, float, float, unsigned int); +inline void dbattrib(int, int, unsigned int, int, int, unsigned int); +inline void dbattrib(float min1, float max1, unsigned int attr1, float min2, float max2, unsigned int attr2) {} +inline void dbattrib(int min1, int max1, unsigned int attr1, int min2, int max2, unsigned int attr2) {} #endif diff --git a/src/Speed/Indep/Src/Misc/CookieTrail.h b/src/Speed/Indep/Src/Misc/CookieTrail.h index 918da5f2a..e8eb9c461 100644 --- a/src/Speed/Indep/Src/Misc/CookieTrail.h +++ b/src/Speed/Indep/Src/Misc/CookieTrail.h @@ -19,6 +19,36 @@ template class CookieTrail { public: CookieTrail() : mCount(0), mLast(-1), mCapacity(U) {} + + int Count() const { + return mCount; + } + + T &Oldest() { + int next; + if (mCount < mCapacity) { + return mData[0]; + } + next = mLast + 1; + return mData[next - (next / mCapacity) * mCapacity]; + } + + const T &Newest() const { + return mData[mLast]; + } + + void Clear() { + mCount = 0; + mLast = -1; + } + + void AddNew(const T &t) { + mLast = (mLast + 1) % mCapacity; + if (mCount < mCapacity) { + mCount++; + } + mData[mLast] = t; + } }; // TODO move? diff --git a/src/Speed/Indep/Src/Physics/Behavior.h b/src/Speed/Indep/Src/Physics/Behavior.h index f5f8fb0f2..32e148862 100644 --- a/src/Speed/Indep/Src/Physics/Behavior.h +++ b/src/Speed/Indep/Src/Physics/Behavior.h @@ -20,6 +20,13 @@ extern Attrib::StringKey BEHAVIOR_MECHANIC_SUSPENSION; // total size: 0x10 struct BehaviorParams { + BehaviorParams(const Sim::Param ¶ms, struct PhysicsObject *owner, + const UCrc32 &mechanic, const UCrc32 &signature) + : fparams(params) // + , fowner(owner) // + , fSig(signature) // + , fMechanic(mechanic) {} + const Sim::Param &fparams; // offset 0x0, size 0x4 struct PhysicsObject *fowner; // offset 0x4, size 0x4 const UCrc32 &fSig; // offset 0x8, size 0x4 @@ -28,6 +35,8 @@ struct BehaviorParams { // total size: 0x4C class Behavior : public Sim::Object, public UTL::COM::Factory { + friend class PhysicsObject; + public: void *operator new(std::size_t size) { return gFastMem.Alloc(size, nullptr); @@ -41,15 +50,21 @@ class Behavior : public Sim::Object, public UTL::COM::Factory class BehaviorSpecsPtr : public AttributeStructPtr { public: BehaviorSpecsPtr(Behavior *behavior, int index) : AttributeStructPtr(LookupKey(behavior->GetOwner(), index)) {} - BehaviorSpecsPtr(ISimable *owner, int index) : AttributeStructPtr(0) { - // TODO - } + BehaviorSpecsPtr(ISimable *owner, int index) : AttributeStructPtr(LookupKey(owner, index)) {} ~BehaviorSpecsPtr(); @@ -126,4 +147,6 @@ template class BehaviorSpecsPtr : public AttributeStructPtr { } }; +template BehaviorSpecsPtr::~BehaviorSpecsPtr() {} + #endif diff --git a/src/Speed/Indep/Src/Physics/Behaviors/Chassis.cpp b/src/Speed/Indep/Src/Physics/Behaviors/Chassis.cpp index 711f92002..e3125bf0a 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/Chassis.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/Chassis.cpp @@ -20,7 +20,8 @@ Chassis::Chassis(const BehaviorParams &bp) GetOwner()->QueryInterface(&mInput); GetOwner()->QueryInterface(&mEngine); GetOwner()->QueryInterface(&mTransmission); - GetOwner()->QueryInterface(&mDragTrany); + mDragTrany = + (IDragTransmission *)(*reinterpret_cast(GetOwner()))->_mInterfaces.Find((HINTERFACE)IDragTransmission::_IHandle); GetOwner()->QueryInterface(&mEngineDamage); GetOwner()->QueryInterface(&mSpikeDamage); } @@ -182,7 +183,8 @@ void Chassis::OnBehaviorChange(const UCrc32 &mechanic) { if (mechanic == BEHAVIOR_MECHANIC_ENGINE) { GetOwner()->QueryInterface(&mTransmission); GetOwner()->QueryInterface(&mEngine); - GetOwner()->QueryInterface(&mDragTrany); + mDragTrany = + (IDragTransmission *)(*reinterpret_cast(GetOwner()))->_mInterfaces.Find((HINTERFACE)IDragTransmission::_IHandle); GetOwner()->QueryInterface(&mEngineDamage); } else if (mechanic == BEHAVIOR_MECHANIC_INPUT) { GetOwner()->QueryInterface(&mInput); diff --git a/src/Speed/Indep/Src/Physics/Behaviors/DamageDragster.cpp b/src/Speed/Indep/Src/Physics/Behaviors/DamageDragster.cpp index f053a0c20..79b4b057b 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/DamageDragster.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/DamageDragster.cpp @@ -11,6 +11,8 @@ Behavior *DamageDragster::Construct(const BehaviorParams ¶ms) { DamageDragster::DamageDragster(const BehaviorParams &bp, const DamageParams &dp) : DamageRacer(bp, dp) {} +DamageDragster::~DamageDragster() {} + void DamageDragster::CheckTotaling(const COLLISION_INFO &cinfo) { if ((cinfo.type == Sim::Collision::Info::GROUND) || IsDestroyed()) { return; diff --git a/src/Speed/Indep/Src/Physics/Behaviors/DamageRacer.cpp b/src/Speed/Indep/Src/Physics/Behaviors/DamageRacer.cpp index 817521b58..143c15454 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/DamageRacer.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/DamageRacer.cpp @@ -1,6 +1,7 @@ #include "DamageRacer.h" #include "Speed/Indep/Src/Generated/Events/ETireBlown.hpp" #include "Speed/Indep/Src/Generated/Events/ETirePunctured.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" #include "Speed/Indep/Src/Interfaces/Simables/IDamageable.h" #include "Speed/Indep/Src/Interfaces/Simables/ISpikeable.h" #include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" @@ -10,11 +11,21 @@ #include "Speed/Indep/Src/Physics/PhysicsTypes.h" #include "Speed/Indep/bWare/Inc/bWare.hpp" +ISpikeable::~ISpikeable() {} + Behavior *DamageRacer::Construct(const BehaviorParams ¶ms) { const DamageParams dp(params.fparams.Fetch(UCrc32(0xa6b47fac))); return new DamageRacer(params, dp); } +DamageRacer::DamageRacer(const BehaviorParams &bp, const DamageParams &dp) + : DamageVehicle(bp, dp), // + ISpikeable(bp.fowner) { + GetOwner()->QueryInterface(&mSuspension); + bMemSet(mBlowOutTimes, 0, sizeof(mBlowOutTimes)); + bMemSet(mDamage, 0, sizeof(mDamage)); +} + DamageRacer::~DamageRacer() { // TODO } @@ -54,9 +65,6 @@ bool DamageRacer::IsLightDamaged(VehicleFX::ID idx) const { if (!CanDamageVisuals()) { return false; } - if (IsDestroyed()) { - return true; - } return DamageVehicle::IsLightDamaged(idx); } @@ -68,8 +76,12 @@ DamageZone::Info DamageRacer::GetZoneDamage() const { } bool DamageRacer::CanDamageVisuals() const { - // TODO - return true; + if (FEDatabase) { + if (!FEDatabase->GetGameplaySettings()->Damage) { + return false; + } + } + return DamageVehicle::CanDamageVisuals(); } void DamageRacer::OnTaskSimulate(float dT) { diff --git a/src/Speed/Indep/Src/Physics/Behaviors/DamageVehicle.cpp b/src/Speed/Indep/Src/Physics/Behaviors/DamageVehicle.cpp index f6370dcbb..1a1963d74 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/DamageVehicle.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/DamageVehicle.cpp @@ -1,17 +1,69 @@ #include "DamageVehicle.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/smackable.h" #include "Speed/Indep/Src/Generated/Events/EVehicleDestroyed.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/GenericAccessor.h" #include "Speed/Indep/Src/Interfaces/Simables/IArticulatedVehicle.h" #include "Speed/Indep/Src/Interfaces/Simables/IDamageable.h" #include "Speed/Indep/Src/Interfaces/Simables/IEngineDamage.h" +#include "Speed/Indep/Src/Interfaces/SimModels/IModel.h" #include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" #include "Speed/Indep/Src/Sim/Simulation.h" #include "Speed/Indep/Src/World/Damagezones.h" +namespace DamageZone { +UCrc32 GetSystemName(DamageZone::ID zone); +UCrc32 GetDamageStimulus(unsigned int level); +UCrc32 GetImpactStimulus(unsigned int level); +} // namespace DamageZone + +static inline int Min(const int a, const int b) { + return a > b ? b : a; +} + +HINTERFACE IModel::_IHandle() { + return (HINTERFACE)_IHandle; +} + +IModel::~IModel() {} + +IDamageable::~IDamageable() {} + +IDamageableVehicle::~IDamageableVehicle() {} + +IArticulatedVehicle::~IArticulatedVehicle() {} + +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); + Behavior *DamageVehicle::Construct(const BehaviorParams ¶ms) { const DamageParams dp(params.fparams.Fetch(UCrc32(0xa6b47fac))); return new DamageVehicle(params, dp); } +DamageVehicle::DamageVehicle(const BehaviorParams &bp, const DamageParams &sp) + : VehicleBehavior(bp, 2), // + IDamageable(bp.fowner), // + Sim::Collision::IListener(), // + IDamageableVehicle(bp.fowner), // + EventSequencer::IContext(this), // + mShockTimer(0.0f), // + mSpecs(this, 0), // + mDamageTotal(0.0f), // + mZoneDamage(), // + mLastImpactSpeed(UMath::Vector3::kZero), // + mBrokenParts(), // + mLightDamage(0) { + GetOwner()->QueryInterface(&mIRBComplex); + GetOwner()->QueryInterface(&mRB); + GetOwner()->QueryInterface(&mRenderable); + Sim::Collision::AddListener(this, GetOwner(), "DamageVehicle"); +} + +DamageVehicle::~DamageVehicle() { + Sim::Collision::RemoveListener(this); + ResetParts(); +} + void DamageVehicle::ResetParts() { IRenderable *irenderable; if (GetOwner()->QueryInterface(&irenderable)) { @@ -32,7 +84,39 @@ void DamageVehicle::OnBehaviorChange(const UCrc32 &mechanic) { } } -void DamageVehicle::OnCollision(const COLLISION_INFO &cinfo) {} +void DamageVehicle::OnCollision(const COLLISION_INFO &cinfo) { + float closing_speed = UMath::Length(cinfo.closingVel); + if (closing_speed < 1.0f) { + return; + } + + IRigidBody *body = GetOwner()->GetRigidBody(); + float force = cinfo.impulseA * body->GetMass(); + + if (cinfo.Type() == COLLISION_INFO::OBJECT) { + SetShockForce(cinfo.force); + if (cinfo.objB == GetOwner()->GetInstanceHandle()) { + force = cinfo.impulseB * body->GetMass(); + } + force += force; + } + + HSIMABLE my_handle = GetOwner()->GetInstanceHandle(); + if (cinfo.objA == my_handle) { + UMath::Vector3 normal = cinfo.normal; + mLastImpactSpeed = cinfo.objAVel; + SimSurface surface(cinfo.objAsurface); + ISimable *other = cinfo.objB ? ISimable::FindInstance(cinfo.objB) : nullptr; + OnImpact(cinfo.armA, normal, force, UMath::Length(cinfo.objAVel), surface, other); + } else if (cinfo.objB == my_handle) { + UMath::Vector3 normal; + UMath::Scale(cinfo.normal, -1.0f, normal); + mLastImpactSpeed = cinfo.objBVel; + SimSurface surface(cinfo.objBsurface); + ISimable *other = cinfo.objA ? ISimable::FindInstance(cinfo.objA) : nullptr; + OnImpact(cinfo.armB, normal, force, UMath::Length(cinfo.objBVel), surface, other); + } +} bool DamageVehicle::SetDynamicData(const EventSequencer::System *system, EventDynamicData *data) { data->fVelocity = UMath::Vector4Make(mLastImpactSpeed, 1.0f); @@ -136,3 +220,131 @@ void DamageVehicle::SetInShock(float scale) { } mShockTimer = UMath::Min(UMath::Max(mShockTimer, scale), 1.0f); } + +void DamageVehicle::OnImpact(const UMath::Vector3 &arm, const UMath::Vector3 &normal, float force, float speed, const SimSurface &mysurface, + ISimable *iother) { + float suppress_distance = mSpecs.SUPPRESS_DIST(); + if (0.0f < suppress_distance) { + float viewdist = Sim::DistanceToCamera(mRB->GetPosition()); + if (viewdist > suppress_distance) { + return; + } + } + + if (iother && iother->GetSimableType() == SIMABLE_SMACKABLE) { + Attrib::Gen::smackable smackable(iother->GetAttributes()); + if (smackable.NO_CAR_EFFECT()) { + return; + } + } + + float material_strength = mSpecs.FORCE() * 1000.0f; + if (!(0.0f < material_strength)) { + return; + } + + { + UMath::Vector3 dim = mRB->GetDimension(); + + UMath::Vector3 relative_normal = normal; + mRB->ConvertWorldToLocal(relative_normal, false); + + DamageZone::ID zone; + const float kDirectionThreshold = 0.8f; + const float kZoneThreshold = 0.5f; + if (arm.z > dim.z * kZoneThreshold) { + zone = DamageZone::DZ_RFRONT; + if (arm.x <= dim.x * kZoneThreshold || -kDirectionThreshold <= relative_normal.x) { + zone = DamageZone::DZ_FRONT; + if (arm.x < -dim.x * kZoneThreshold && kDirectionThreshold < relative_normal.x) { + zone = DamageZone::DZ_LFRONT; + } + } + } else if (arm.z < -dim.z * kZoneThreshold) { + zone = DamageZone::DZ_RREAR; + if (arm.x <= dim.x * kZoneThreshold || -kDirectionThreshold <= relative_normal.x) { + zone = DamageZone::DZ_REAR; + if (arm.x < -dim.x * kZoneThreshold && kDirectionThreshold < relative_normal.x) { + zone = DamageZone::DZ_LREAR; + } + } + } else if (arm.y > dim.y * kZoneThreshold && relative_normal.y < -kDirectionThreshold) { + zone = DamageZone::DZ_TOP; + } else { + if (arm.y < -dim.y * kZoneThreshold && kDirectionThreshold < relative_normal.y) { + zone = DamageZone::DZ_BOTTOM; + } else { + zone = DamageZone::DZ_LEFT; + if (0.0f < arm.x) { + zone = DamageZone::DZ_RIGHT; + } + } + } + + const DamageScaleRecord &record = GetDamageRecord(zone); + float visual_scale = record.VisualScale; + float hitpoint_scale = record.HitPointScale; + float ratio = force / material_strength; + int newimpact_level = Min(static_cast(ratio * visual_scale), 6); + int newdamage_level = Min(static_cast(ratio * visual_scale), 2); + + float total_hit_points = mSpecs.HIT_POINTS(); + if (0.0f < total_hit_points && mDamageTotal < 1.0f) { + float damage_points = (ratio * hitpoint_scale) / total_hit_points; + float threshold = mSpecs.HP_THRESHOLD() / total_hit_points; + if (threshold < damage_points) { + mDamageTotal += damage_points; + if (1.0f < mDamageTotal) { + mDamageTotal = 1.0f; + new EVehicleDestroyed(GetOwner()->GetInstanceHandle()); + } + } + } + + float time; + EventSequencer::IEngine *sequencer = GetOwner()->GetEventSequencer(); + EventSequencer::System *system = nullptr; + if (sequencer) { + system = sequencer->FindSystem(DamageZone::GetSystemName(zone).GetValue()); + } + + if (system) { + time = Sim::GetTime(); + system->ProcessStimulus(0x1ea131b8, time, this, EventSequencer::QUEUE_ALLOW); + + IVehicle *iv = nullptr; + if (iother && iother->QueryInterface(&iv)) { + system->ProcessStimulus(0x1e3a3751, time, this, EventSequencer::QUEUE_ALLOW); + if (iv->GetDriverClass() == DRIVER_COP) { + system->ProcessStimulus(0xe8d20c6b, time, this, EventSequencer::QUEUE_ALLOW); + } + if (iother->IsPlayer()) { + system->ProcessStimulus(0xb13e6c7e, time, this, EventSequencer::QUEUE_ALLOW); + } + } + + for (int j = 0; j < newimpact_level; ++j) { + system->ProcessStimulus(DamageZone::GetImpactStimulus(j).GetValue(), time, this, EventSequencer::QUEUE_ALLOW); + } + } + + if (0 < newdamage_level && CanDamageVisuals()) { + if (system) { + int damage_level = mZoneDamage.Get(zone); + if (damage_level < 6 && damage_level < newdamage_level) { + while (damage_level < newdamage_level) { + system->ProcessStimulus(DamageZone::GetDamageStimulus(damage_level).GetValue(), time, this, + EventSequencer::QUEUE_ALLOW); + ++damage_level; + } + mZoneDamage.Set(zone, newdamage_level); + } + } else { + int damage_level = mZoneDamage.Get(zone); + if (damage_level < newdamage_level) { + mZoneDamage.Set(zone, newdamage_level); + } + } + } + } +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/DamageVehicle.h b/src/Speed/Indep/Src/Physics/Behaviors/DamageVehicle.h index 816271715..35c3588ec 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/DamageVehicle.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/DamageVehicle.h @@ -36,7 +36,7 @@ class DamageVehicle : public VehicleBehavior, public: static Behavior *Construct(const BehaviorParams ¶ms); - DamageVehicle(const BehaviorParams &bp, const DamageParams &dp); + DamageVehicle(const BehaviorParams &bp, const DamageParams &sp); void ResetParts(); const DamageScaleRecord &GetDamageRecord(DamageZone::ID zone) const; @@ -60,6 +60,17 @@ class DamageVehicle : public VehicleBehavior, void SetShockForce(float f) override; void SetInShock(float scale) override; void ResetDamage() override; + float InShock() const override { + return mShockTimer; + } + + float GetHealth() const override { + return UMath::Clamp(1.0f - mDamageTotal, 0.0f, 1.0f); + } + + bool IsDestroyed() const override { + return mDamageTotal >= 1.0f; + } // Virtual methods virtual void OnImpact(const UMath::Vector3 &arm, const UMath::Vector3 &normal, float force, float speed, const SimSurface &mysurface, @@ -70,9 +81,22 @@ class DamageVehicle : public VehicleBehavior, } virtual bool IsLightDamaged(VehicleFX::ID idx) const { + if (IsDestroyed()) { + return true; + } return (mLightDamage & idx) != VehicleFX::LIGHT_NONE; } + void DamageLight(VehicleFX::ID idx, bool b) override { + if (CanDamageVisuals()) { + if (b) { + mLightDamage |= idx; + } else { + mLightDamage &= ~idx; + } + } + } + // Inline overrides DamageZone::Info GetZoneDamage() const override { return mZoneDamage; diff --git a/src/Speed/Indep/Src/Physics/Behaviors/DrawCar.cpp b/src/Speed/Indep/Src/Physics/Behaviors/DrawCar.cpp index e69de29bb..603627022 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/DrawCar.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/DrawCar.cpp @@ -0,0 +1,556 @@ +#include "DrawVehicle.h" + +#include "Speed/Indep/Src/FE/FECustomizationRecord.h" +#include "Speed/Indep/Src/Physics/Dynamics.h" +#include "Speed/Indep/Src/Sim/SimConn.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h" + +namespace RenderConn { + +class Pkt_Car_Open : public Sim::Packet { + public: + Pkt_Car_Open(const char *modelname, WUID worldid, CarRenderUsage usage, const FECustomizationRecord *customizations, unsigned int physicskey, + bool spool_load) + : mModelHash(bStringHash(modelname)), // + mWorldID(worldid), // + mUsage(usage), // + mCustomizations(customizations), // + mPhysicsKey(physicskey), // + mSpoolLoad(spool_load) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash = "CarRenderConn"; + return hash; + } + + unsigned int Size() override { + return sizeof(*this); + } + + unsigned int Type() override { + return SType(); + } + + static unsigned int SType() { + static UCrc32 hash = "Pkt_Car_Open"; + return hash.GetValue(); + } + + private: + unsigned int mModelHash; + WUID mWorldID; + CarRenderUsage mUsage; + const FECustomizationRecord *mCustomizations; + unsigned int mPhysicsKey; + bool mSpoolLoad; +}; + +class Pkt_Car_Service : public Sim::Packet { + public: + void HidePart(const UCrc32 &partname); + + bool InView() const { + return mInView; + } + + float DistanceToView() const { + return mDistanceToView; + } + + void SetFlashing(bool b) { + mFlashing = b; + } + + void SetBrokenLightState(VehicleFX::ID idx, bool on) { + if (on) { + mBrokenLights |= idx; + } else { + mBrokenLights &= ~idx; + } + } + + void SetLightState(VehicleFX::ID idx, bool on) { + if (on) { + mLights |= idx; + } else { + mLights &= ~idx; + } + } + + void SetExtraBodyRoll(float amount) { + mExtraBodyRoll = amount; + } + + void SetExtraBodyPitch(float amount) { + mExtraBodyPitch = amount; + } + + void SetDamage(const DamageZone::Info &dmg) { + mDamageInfo = dmg; + } + + void SetHealth(float amount) { + mHealth = amount; + } + + void SetSteering(eTireIdx idx, float angle) { + mSteering[idx] = angle; + } + + void SetTireBlowOut(eTireIdx idx) { + mBlowOuts |= 1 << idx; + } + + void SetWheelSlip(eTireIdx idx, float slip) { + mTireSlip[idx] = slip; + } + + void SetWheelSpeed(eTireIdx idx, float speed) { + mWheelSpeed[idx] = speed; + } + + void SetWheelCompression(eTireIdx idx, float compression) { + mCompressions[idx] = compression; + } + + void SetWheelSkid(eTireIdx idx, float skid) { + mTireSkid[idx] = skid; + } + + void SetWheelOnGround(eTireIdx idx, bool b) { + if (b) { + mGroundState |= 1 << idx; + } else { + mGroundState &= ~(1 << idx); + } + } + + void SetAnimatedPitch(float amount) { + mAnimatedCarPitch = amount; + } + + void SetAnimatedRoll(float amount) { + mAnimatedCarRoll = amount; + } + + void SetAnimatedShake(float amount) { + mAnimatedCarShake = amount; + } + + float mCompressions[4]; + float mWheelSpeed[4]; + float mTireSkid[4]; + float mTireSlip[4]; + float mSteering[2]; + int mGroundState; + DamageZone::Info mDamageInfo; + unsigned int mPartState[3]; + unsigned int mLights; + unsigned int mBrokenLights; + bool mInView; + float mDistanceToView; + bool mFlashing; + bool mNos; + bool mEngineBlown; + ShiftStatus mShift; + GearID mGear; + float mEnginePower; + float mEngineSpeed; + float mExtraBodyRoll; + float mExtraBodyPitch; + int mBlowOuts; + float mHealth; + float mAnimatedCarPitch; + float mAnimatedCarRoll; + float mAnimatedCarShake; +}; + +} // namespace RenderConn + +namespace VehicleFX { + +struct Maps { + ID id; + Attrib::StringKey name; + unsigned int marker; +}; + +const Maps *GetMaps(); + +} // namespace VehicleFX + +namespace { + +static inline bool IsRight(unsigned int i) { + return (i & 1) != 0; +} + +} // namespace + +Behavior *DrawTraffic::Construct(const BehaviorParams ¶ms) { + return new DrawTraffic(params); +} + +DrawTraffic::DrawTraffic(const BehaviorParams ¶ms) + : DrawCar(params, CarRenderUsage_AITraffic) {} + +DrawPerformanceCar::DrawPerformanceCar(const BehaviorParams ¶ms, CarRenderUsage usage) + : DrawCar(params, usage) { + GetOwner()->QueryInterface(&mTransmission); + GetOwner()->QueryInterface(&mEngine); +} + +DrawCar::DrawCar(const BehaviorParams ¶ms, CarRenderUsage usage) + : DrawVehicle(params), // + mRenderService(nullptr), // + mSuspension(nullptr), // + mDamage(nullptr), // + mVehicle(nullptr), // + mVehicleDamage(nullptr), // + mRBVehicle(nullptr), // + mNIS(nullptr), // + mSpikeDamage(nullptr), // + mParts(), // + mInView(false), // + mDistanceToView(0.0f), // + mHidden(false) { + GetOwner()->QueryInterface(&mVehicle); + GetOwner()->QueryInterface(&mSuspension); + GetOwner()->QueryInterface(&mDamage); + GetOwner()->QueryInterface(&mSpikeDamage); + GetOwner()->QueryInterface(&mVehicleDamage); + GetOwner()->QueryInterface(&mRBVehicle); + GetOwner()->QueryInterface(&mNIS); + + if (mSuspension && mVehicle) { + const char *modelName = mVehicle->GetVehicleName(); + if (!modelName) { + modelName = ""; + } + + RenderConn::Pkt_Car_Open pkt(modelName, + GetOwner()->GetWorldID(), + usage, + mVehicle->GetCustomizations(), + mVehicle->GetVehicleKey(), + mVehicle->IsSpooled()); + mRenderService = OpenService(UCrc32(0x804c146e), &pkt); + } +} + +void DrawPerformanceCar::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == BEHAVIOR_MECHANIC_ENGINE) { + GetOwner()->QueryInterface(&mTransmission); + GetOwner()->QueryInterface(&mEngine); + } + DrawCar::OnBehaviorChange(mechanic); +} + +void DrawPerformanceCar::OnService(RenderConn::Pkt_Car_Service &pkt) { + DrawCar::OnService(pkt); + if (mEngine && mTransmission) { + *reinterpret_cast(reinterpret_cast(&pkt) + 0x80) = mTransmission->GetGear(); + *reinterpret_cast(reinterpret_cast(&pkt) + 0x7C) = mTransmission->GetShiftStatus(); + *reinterpret_cast(reinterpret_cast(&pkt) + 0x74) = mEngine->IsNOSEngaged(); + + if (mTransmission->GetGear() < 2 || GetVehicle()->GetDriverClass() != DRIVER_HUMAN) { + float rpm_ratio = 0.0f; + float rev = mEngine->GetRPM(); + float amin = mEngine->GetMinRPM(); + float amax = mEngine->GetMaxRPM(); + float arange = amax - amin; + if (arange > 1e-06f) { + float result = (rev - amin) / arange; + rpm_ratio = 1.0f; + if (result < 1.0f) { + rpm_ratio = result; + } + if (rpm_ratio < 0.0f) { + rpm_ratio = 0.0f; + } + } + + float vib = 0.0f; + { + float rev = mEngine->GetHorsePower(); + Hp min_power = mEngine->GetMinHorsePower(); + Hp max_power = mEngine->GetMaxHorsePower(); + float arange = max_power - min_power; + if (arange > 1e-06f) { + float result = (rev - min_power) / arange; + vib = 1.0f; + if (result < 1.0f) { + vib = result; + } + if (vib < 0.0f) { + vib = 0.0f; + } + } + } + + *reinterpret_cast(reinterpret_cast(&pkt) + 0x88) = rpm_ratio; + *reinterpret_cast(reinterpret_cast(&pkt) + 0x84) = vib; + } + } +} + +Behavior *DrawNISCar::Construct(const BehaviorParams ¶ms) { + return new DrawNISCar(params); +} + +DrawNISCar::DrawNISCar(const BehaviorParams ¶ms) + : DrawPerformanceCar(params, carRenderUsage_NISCar) {} + +Behavior *DrawCopCar::Construct(const BehaviorParams ¶ms) { + return new DrawCopCar(params); +} + +DrawCopCar::DrawCopCar(const BehaviorParams ¶ms) + : DrawPerformanceCar(params, CarRenderUsage_AICop) {} + +Behavior *DrawRaceCar::Construct(const BehaviorParams ¶ms) { + IVehicle *vehicle = nullptr; + static_cast(params.fowner)->QueryInterface(&vehicle); + if (!vehicle) { + return nullptr; + } + + DriverClass driverClass = vehicle->GetDriverClass(); + CarRenderUsage usage; + if (driverClass != DRIVER_RACER) { + if (driverClass < DRIVER_NONE) { + if (driverClass == DRIVER_HUMAN) { + usage = CarRenderUsage_Player; + } else { + usage = CarRenderUsage_AIRacer; + } + } else if (driverClass == DRIVER_REMOTE) { + usage = CarRenderUsage_RemotePlayer; + } else { + usage = CarRenderUsage_AIRacer; + } + } else { + usage = CarRenderUsage_AIRacer; + } + + return new DrawRaceCar(params, usage); +} + +DrawRaceCar::DrawRaceCar(const BehaviorParams ¶ms, CarRenderUsage usage) + : DrawPerformanceCar(params, usage) { + GetOwner()->QueryInterface(&mEngineDamage); +} + +void DrawRaceCar::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == BEHAVIOR_MECHANIC_ENGINE) { + GetOwner()->QueryInterface(&mEngineDamage); + } + DrawPerformanceCar::OnBehaviorChange(mechanic); +} + +void DrawRaceCar::OnService(RenderConn::Pkt_Car_Service &pkt) { + DrawPerformanceCar::OnService(pkt); + if (mEngineDamage) { + *reinterpret_cast(reinterpret_cast(&pkt) + 0x78) = mEngineDamage->IsBlown(); + } +} + +bool DrawCar::OnService(HSIMSERVICE hCon, Sim::Packet *pkt) { + if (hCon == mRenderService) { + if (!static_cast(this)->IsHidden()) { + OnService(*static_cast(pkt)); + return true; + } + mInView = false; + } + return false; +} + +void DrawCar::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == BEHAVIOR_MECHANIC_SUSPENSION) { + GetOwner()->QueryInterface(&mSuspension); + GetOwner()->QueryInterface(&mNIS); + } + if (mechanic == BEHAVIOR_MECHANIC_RIGIDBODY) { + GetOwner()->QueryInterface(&mRBVehicle); + } + if (mechanic == BEHAVIOR_MECHANIC_DAMAGE) { + GetOwner()->QueryInterface(&mDamage); + GetOwner()->QueryInterface(&mVehicleDamage); + GetOwner()->QueryInterface(&mSpikeDamage); + } +} + +bool DrawCar::IsHidden() const { + return mHidden || DrawVehicle::IsHidden() || IsPaused(); +} + +void DrawCar::HideModel() { + DrawVehicle::HideModel(); + mHidden = true; +} + +void DrawCar::ReleaseModel() { + DrawVehicle::ReleaseModel(); + if (mRenderService) { + CloseService(mRenderService); + mRenderService = 0; + } +} + +void DrawCar::ReleaseChildModels() { + DrawVehicle::ReleaseChildModels(); + if (!mParts.empty()) { + mParts.clear(); + } +} + +bool DrawCar::IsPartVisible(const UCrc32 &name) const { + return mParts.find(name) == mParts.end(); +} + +void DrawCar::HidePart(const UCrc32 &name) { + Parts::iterator iter = mParts.find(name); + if (iter == mParts.end()) { + mParts[name] = 1; + } else { + iter->second = iter->second + 1; + } +} + +void DrawCar::ShowPart(const UCrc32 &name) { + Parts::iterator iter = mParts.find(name); + if (iter != mParts.end()) { + iter->second = iter->second - 1; + if (iter->second < 1) { + mParts.erase(iter); + } + } +} + +bool DrawCar::InView() const { + return mInView; +} + +bool DrawCar::IsRenderable() const { + return CheckService(mRenderService) == Sim::CONNSTATUS_READY; +} + +float DrawCar::DistanceToView() const { + return mDistanceToView; +} + +void DrawCar::Reset() {} + +void DrawCar::OnTaskSimulate(float dT) {} + +DrawCar::~DrawCar() { + if (mRenderService) { + CloseService(mRenderService); + mRenderService = 0; + } +} + +void DrawCar::OnService(RenderConn::Pkt_Car_Service &pkt) { + mInView = pkt.InView(); + mDistanceToView = pkt.DistanceToView(); + + if (mRBVehicle) { + eInvulnerablitiy invulnerability = mRBVehicle->GetInvulnerability(); + if (invulnerability > INVULNERABLE_NONE && invulnerability < INVULNERABLE_FROM_CONTROL_SWITCH) { + pkt.SetFlashing(true); + } + } + + const VehicleFX::Maps *fx = VehicleFX::GetMaps(); + if (fx->id != VehicleFX::LIGHT_NONE) { + do { + if (fx->marker != 0) { + if (mVehicleDamage && mVehicleDamage->IsLightDamaged(fx->id)) { + pkt.SetBrokenLightState(fx->id, true); + } else { + pkt.SetLightState(fx->id, mVehicle->IsGlareOn(fx->id)); + } + } + ++fx; + } while (fx->id != VehicleFX::LIGHT_NONE); + } + + IDynamicsEntity *dynamicsEntity = nullptr; + if (GetOwner()->QueryInterface(&dynamicsEntity) && Dynamics::Articulation::IsJoined(dynamicsEntity)) { + pkt.SetExtraBodyRoll(0.0f); + pkt.SetExtraBodyPitch(0.0f); + } else if (mSuspension) { + const float motion = mSuspension->GetRenderMotion(); + pkt.SetExtraBodyPitch(motion); + pkt.SetExtraBodyRoll(motion); + } + + if (mDamage) { + pkt.SetDamage(mDamage->GetZoneDamage()); + pkt.SetHealth(mDamage->GetHealth()); + } + + for (Parts::const_iterator iter = mParts.begin(); iter != mParts.end(); iter++) { + pkt.HidePart((*iter).first); + } + + pkt.mGroundState = 0; + pkt.mBlowOuts = 0; + if (mSuspension) { + IRigidBody *irb = GetOwner()->GetRigidBody(); + (void)irb; + + for (unsigned int i = 0; i < mSuspension->GetNumWheels(); ++i) { + const UMath::Vector3 &vel = mSuspension->GetWheelVelocity(i); + float pos_x = mSuspension->GetWheelLocalPos(i).x; + float steer = UMath::Clamp(ANGLE2RAD(mSuspension->GetWheelSteer(i)), -3.1415927f, 3.1415927f); + float compression = mSuspension->GetCompression(i) - mSuspension->GetRideHeight(i); + float av = mSuspension->GetWheelAngularVelocity(i); + float radius = mSuspension->GetWheelRadius(i); + float slip = 0.0f; + float skid = 0.0f; + bool onground = false; + eTireIdx idx; + + (void)vel; + (void)pos_x; + + compression += radius; + if (mSuspension->IsWheelOnGround(i)) { + skid = UMath::Abs(mSuspension->GetWheelSkid(i)); + slip = UMath::Abs(mSuspension->GetWheelSlip(i)); + float tolerance = mSuspension->GetToleratedSlip(i); + if (tolerance < slip) { + slip -= tolerance; + } else { + slip = 0.0f; + } + onground = true; + } + + if (IsFront(i)) { + idx = IsRight(i) ? TIRE_FR : TIRE_FL; + pkt.SetSteering(idx, -steer); + } else { + idx = IsRight(i) ? TIRE_RR : TIRE_RL; + } + + if (mSpikeDamage && mSpikeDamage->GetTireDamage(i) == TIRE_DAMAGE_BLOWN) { + pkt.SetTireBlowOut(idx); + } + + pkt.SetWheelSlip(idx, slip); + pkt.SetWheelSpeed(idx, av * radius); + pkt.SetWheelCompression(idx, compression); + pkt.SetWheelSkid(idx, skid); + pkt.SetWheelOnGround(idx, onground); + } + } + + if (mNIS) { + pkt.SetAnimatedPitch(mNIS->GetAnimPitch()); + pkt.SetAnimatedRoll(mNIS->GetAnimRoll()); + pkt.SetAnimatedShake(mNIS->GetAnimShake()); + } +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/DrawHeli.cpp b/src/Speed/Indep/Src/Physics/Behaviors/DrawHeli.cpp index e69de29bb..35c3c479b 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/DrawHeli.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/DrawHeli.cpp @@ -0,0 +1,161 @@ +#include "DrawVehicle.h" + +#include "Speed/Indep/Src/Sim/SimConn.h" +#include "Speed/Indep/Src/Sim/Simulation.h" + +namespace RenderConn { + +class Pkt_Heli_Open : public Sim::Packet { + public: + Pkt_Heli_Open(const char *modelname, WUID worldid, bool spool_load) + : mModelHash(bStringHash(modelname)), // + mWorldID(worldid), // + mSpoolLoad(spool_load) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash = "HeliRenderConn"; + return hash; + } + + unsigned int Size() override { + return sizeof(*this); + } + + unsigned int Type() override { + return SType(); + } + + static unsigned int SType() { + static UCrc32 hash = "Pkt_Heli_Open"; + return hash.GetValue(); + } + + private: + unsigned int mModelHash; // offset 0x4, size 0x4 + WUID mWorldID; // offset 0x8, size 0x4 + bool mSpoolLoad; // offset 0xC, size 0x1 +}; + +class Pkt_Heli_Service : public Sim::Packet { + public: + bool mInView; // offset 0x4, size 0x1 + float mDistanceToView; // offset 0x8, size 0x4 + float mShadowScale; // offset 0xC, size 0x4 + float mDustStorm; // offset 0x10, size 0x4 +}; + +} // namespace RenderConn + +Behavior *DrawHeli::Construct(const BehaviorParams ¶ms) { + return new DrawHeli(params); +} + +DrawHeli::DrawHeli(const BehaviorParams ¶ms) + : DrawVehicle(params), // + mRenderService(nullptr), // + mEffect(0, nullptr), // + mWash(Attrib::StringToKey("heliwash"), 0, nullptr) { + mInView = false; + mDistanceToView = 0.0f; + + const char *model = GetVehicle()->GetVehicleAttributes().MODEL().GetString(); + if (!model) { + model = ""; + } + + RenderConn::Pkt_Heli_Open pkt(model, GetOwner()->GetWorldID(), GetVehicle()->IsSpooled()); + mWashTask = AddTask(UCrc32(stringhash32("Physics")), 1.0f, 0.0f, Sim::TASK_FRAME_VARIABLE); + Sim::ProfileTask(mWashTask, "HeliWash"); + mRenderService = OpenService(UCrc32(0x804c146e), &pkt); + + GetOwner()->QueryInterface(&mIVehicle); + GetOwner()->QueryInterface(&mCollisionBody); +} + +void DrawHeli::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == BEHAVIOR_MECHANIC_RIGIDBODY) { + GetOwner()->QueryInterface(&mCollisionBody); + } +} + +DrawHeli::~DrawHeli() { + RemoveTask(mWashTask); + CloseService(mRenderService); +} + +void DrawHeli::OnService(RenderConn::Pkt_Heli_Service &pkt) { + mInView = pkt.mInView; + mDistanceToView = pkt.mDistanceToView; + if (mIVehicle) { + if (mIVehicle->QueryInterface(&mIAIHelicopter)) { + pkt.mShadowScale = mIAIHelicopter->GetShadowScale(); + pkt.mDustStorm = mIAIHelicopter->GetDustStormIntensity(); + } + } +} + +bool DrawHeli::OnService(HSIMSERVICE hCon, Sim::Packet *pkt) { + if (hCon == mRenderService) { + if (!static_cast(this)->IsHidden()) { + OnService(*static_cast(pkt)); + return true; + } + mInView = false; + return false; + } + return false; +} + +void DrawHeli::HidePart(const UCrc32 &name) {} + +void DrawHeli::ShowPart(const UCrc32 &name) {} + +bool DrawHeli::IsPartVisible(const UCrc32 &name) const { + return true; +} + +bool DrawHeli::InView() const { + return mInView; +} + +bool DrawHeli::IsRenderable() const { + return CheckService(mRenderService) == Sim::CONNSTATUS_READY; +} + +float DrawHeli::DistanceToView() const { + return mDistanceToView; +} + +void DrawHeli::Reset() {} + +void DrawHeli::OnTaskSimulate(float dT) {} + +bool DrawHeli::OnTask(HSIMTASK hTask, float dT) { + if (hTask == mWashTask) { + if (!static_cast(this)->IsHidden() && mCollisionBody) { + WWorldPos wpos = GetOwner()->GetWPos(); + if (wpos.OnValidFace()) { + UMath::Vector3 position = GetVehicle()->GetPosition(); + float altitude = position.y; + position.y = wpos.HeightAtPoint(position); + altitude -= position.y; + + UMath::Vector3 normal = UMath::Vector4To3(mCollisionBody->GetGroundNormal()); + float intensity = 1.0f - UMath::Ramp(altitude, 5.0f, 30.0f); + if (intensity > 0.0f) { + UMath::Scale(normal, intensity); + mEffect.Pause(false); + mEffect.Set(mWash.GetConstCollection(), position, normal, nullptr, false, 0); + return true; + } + } + + mEffect.Pause(true); + } else { + mEffect.Stop(); + } + + return true; + } + return false; +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/DrawVehicle.cpp b/src/Speed/Indep/Src/Physics/Behaviors/DrawVehicle.cpp index e69de29bb..382e27bb9 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/DrawVehicle.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/DrawVehicle.cpp @@ -0,0 +1,412 @@ +#include "DrawVehicle.h" +#include "Speed/Indep/Src/Physics/SmokeableInfo.hpp" +#include "Speed/Indep/Src/Physics/SmackableTrigger.h" + +namespace RenderConn { +class Pkt_VehicleFragment_Open : public Sim::Packet { + public: + Pkt_VehicleFragment_Open(WUID worldid_part, WUID worldid_vehicle, UCrc32 partname, UCrc32 collision_node) + : mVehicleWorldID(worldid_part), // + mWorldID(worldid_vehicle), // + mPartName(partname), // + mColName(collision_node) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash = "VehicleFragmentConn"; + return hash; + } + + unsigned int Size() override { + return sizeof(*this); + } + + unsigned int Type() override { + return SType(); + } + + static unsigned int SType() { + static UCrc32 hash = "VehicleFragment_Open"; + return hash.GetValue(); + } + + private: + WUID mVehicleWorldID; + WUID mWorldID; + UCrc32 mPartName; + UCrc32 mColName; +}; + +class Pkt_VehicleFragment_Service : public Sim::Packet { + private: + int mInView; + float mDistanceToView; + + friend class DrawVehicle::Part; +}; +} + +static int Vehicle_Part_Count; + +DrawVehicle::DrawVehicle(const BehaviorParams &bp) + : VehicleBehavior(bp, 2), // + IRenderable(bp.fowner), // + IModel(bp.fowner), // + IAttachable(bp.fowner), // + mGeometry(nullptr), // + mAttachments(nullptr), // + mCausality(nullptr), // + mCausalityTime(0.0f) { + mAttachments = new Sim::Attachments(static_cast(this)); + + CollisionGeometry::IBoundable *boundable = nullptr; + if (GetOwner()->QueryInterface(&boundable)) { + mGeometry = boundable->GetGeometryNode(); + } +} + +DrawVehicle::~DrawVehicle() { + if (mAttachments) { + delete mAttachments; + } + for (EffectList::iterator iter = mEffects.begin(); iter != mEffects.end(); iter++) { + delete *iter; + } +} + +HMODEL DrawVehicle::GetModelHandle() const { + return static_cast(this)->GetInstanceHandle(); +} + +const IModel *DrawVehicle::GetModel() const { + return this; +} + +IModel *DrawVehicle::GetModel() { + return this; +} + +void DrawVehicle::OnProcessFrame(float dT) {} + +void DrawVehicle::GetLinearVelocity(UMath::Vector3 &velocity) const { + GetOwner()->GetLinearVelocity(velocity); +} + +void DrawVehicle::GetAngularVelocity(UMath::Vector3 &velocity) const { + GetOwner()->GetAngularVelocity(velocity); +} + +void DrawVehicle::GetTransform(UMath::Matrix4 &transform) const { + GetOwner()->GetTransform(transform); +} + +UCrc32 DrawVehicle::GetPartName() const { + return UCrc32::kNull; +} + +WUID DrawVehicle::GetWorldID() const { + return GetOwner()->GetWorldID(); +} + +const CollisionGeometry::Bounds *DrawVehicle::GetCollisionGeometry() const { + return mGeometry; +} + +const Attrib::Instance &DrawVehicle::GetAttributes() const { + return GetOwner()->GetAttributes(); +} + +ISimable *DrawVehicle::GetSimable() const { + return GetOwner(); +} + +IModel *DrawVehicle::GetRootModel() const { + return const_cast(this); +} + +IModel *DrawVehicle::GetParentModel() const { + return nullptr; +} + +bool DrawVehicle::IsRootModel() const { + return true; +} + +EventSequencer::IEngine *DrawVehicle::GetEventSequencer() { + return GetOwner()->GetEventSequencer(); +} + +float DrawVehicle::GetCausalityTime() const { + return mCausalityTime; +} + +bool DrawVehicle::Attach(UTL::COM::IUnknown *pOther) { + return mAttachments->Attach(pOther); +} + +bool DrawVehicle::Detach(UTL::COM::IUnknown *pOther) { + return mAttachments->Detach(pOther); +} + +bool DrawVehicle::IsAttached(const UTL::COM::IUnknown *pOther) const { + return mAttachments->IsAttached(pOther); +} + +void DrawVehicle::OnAttached(IAttachable *pOther) {} + +void DrawVehicle::OnDetached(IAttachable *pOther) {} + +const IAttachable::List *DrawVehicle::GetAttachments() const { + return reinterpret_cast(mAttachments); +} + +DrawVehicle::Effect::~Effect() {} + +DrawVehicle::Part::Part(IModel *parent, WUID vehicleID, const CollisionGeometry::Bounds *geoms, const Attrib::Collection *spec, UCrc32 partname) + : Sim::Model(parent, geoms, partname, 2), // + ITriggerableModel(this), // + mTrigger(nullptr), // + mAttributes(spec, 0, nullptr) { + mOffScreenTask = false; + mOffScreenTime = 0.0f; + mVehicleID = vehicleID; + Vehicle_Part_Count = Vehicle_Part_Count + 1; +} + +DrawVehicle::Part::~Part() { + RemoveTrigger(); + if (mOffScreenTask) { + mOffScreenTask = false; + mOffScreenTime = 0.0f; + } + Vehicle_Part_Count = Vehicle_Part_Count - 1; +} + +const Attrib::Instance &DrawVehicle::Part::GetAttributes() const { + return mAttributes; +} + +void DrawVehicle::Part::GetTransform(UMath::Matrix4 &transform) const { + ISimable *isimable = static_cast(this)->GetSimable(); + if (isimable) { + isimable->GetTransform(transform); + } else if (mTrigger != nullptr) { + mTrigger->GetObjectMatrix(transform); + } else { + transform = UMath::Matrix4::kIdentity; + } +} + +void DrawVehicle::Part::RemoveTrigger() { + if (mTrigger) { + delete mTrigger; + mTrigger = nullptr; + } +} + +bool DrawVehicle::Part::OnDraw(Sim::Packet *service) { + RenderConn::Pkt_VehicleFragment_Service *pkt = static_cast(service); + int inView = pkt->mInView; + *reinterpret_cast(reinterpret_cast(this) + 0x68) = pkt->mDistanceToView; + *reinterpret_cast(reinterpret_cast(this) + 0x6C) = inView; + return true; +} + +void DrawVehicle::Part::OnBeginDraw() {} + +void DrawVehicle::Part::OnEndDraw() { + RemoveTrigger(); + if (mOffScreenTask) { + mOffScreenTask = false; + } + ReleaseSequencer(); + static_cast(this)->StopEffects(); +} + +void DrawVehicle::Part::PlaceTrigger(const UMath::Matrix4 &matrix, bool enable) { + CreateTrigger(matrix); + if (!enable && mTrigger != nullptr) { + mTrigger->Disable(); + } +} + +void DrawVehicle::Part::CreateTrigger(const UMath::Matrix4 &matrix) { + UMath::Vector3 dim; + GetCollisionGeometry()->GetHalfDimensions(dim); + if (VU0_v3lengthsquare(dim) <= 0.0f) { + RemoveTrigger(); + } else if (mTrigger == nullptr) { + mTrigger = new SmackableTrigger(GetInstanceHandle(), false, matrix, dim, 0); + } else { + mTrigger->Move(matrix, dim, false); + mTrigger->Enable(); + } +} + +void DrawVehicle::Part::OnProcessFrame(float dT) { + if (mOffScreenTask) { + const float *offscreen_allow_ptr = reinterpret_cast(mAttributes.GetAttributePointer(0xc141f7f8, 0)); + if (!offscreen_allow_ptr) { + offscreen_allow_ptr = reinterpret_cast(Attrib::DefaultDataArea(sizeof(float))); + } + float offscreen_allow = *offscreen_allow_ptr; + if (0.0f < offscreen_allow) { + if (!static_cast(this)->InView()) { + mOffScreenTime = mOffScreenTime + dT; + } else { + mOffScreenTime = 0.0f; + } + if (mOffScreenTime > offscreen_allow) { + static_cast(this)->ReleaseModel(); + } + } + } +} + +void DrawVehicle::Part::OnBeginSimulation() { + if (mTrigger != nullptr) { + mTrigger->Disable(); + } + + RenderConn::Pkt_VehicleFragment_Open pkt(mVehicleID, + static_cast(this)->GetWorldID(), + static_cast(this)->GetPartName(), + static_cast(this)->GetCollisionGeometry()->fNameHash); + BeginDraw(UCrc32(0x804c146e), &pkt); + + if (!mOffScreenTask) { + float offscreen_allow; + offscreen_allow = mAttributes.KILL_OFF_SCREEN(); + if (0.0f < offscreen_allow) { + mOffScreenTime = 0.0f; + mOffScreenTask = true; + } + } + + StartSequencer(UCrc32(mAttributes.EventSequencer())); +} + +void DrawVehicle::SetCausality(HCAUSE from, float time) { + mCausality = from; + mCausalityTime = time; +} + +HCAUSE DrawVehicle::GetCausality() const { + return mCausality; +} + +IModel::Enumerator *DrawVehicle::EnumerateChildren(Enumerator *enumerator) const { + const IAttachable::List *attachments = static_cast(this)->GetAttachments(); + if (attachments) { + IModel *model = static_cast(const_cast(this)); + for (IAttachable::List::const_iterator iter = attachments->begin(); iter != attachments->end(); ++iter) { + IModel *imodel = + static_cast((*reinterpret_cast(*iter))->_mInterfaces.Find((HINTERFACE)IModel::_IHandle)); + if (imodel && UTL::COM::ComparePtr(imodel->GetParentModel(), model)) { + if (!enumerator->OnModel(imodel)) { + return enumerator; + } + } + } + } + return enumerator; +} + +IModel *DrawVehicle::GetChildModel(UCrc32 name) const { + if (name == UCrc32::kNull) { + return nullptr; + } + + const IAttachable::List *attachments = static_cast(this)->GetAttachments(); + if (attachments) { + for (IAttachable::List::const_iterator iter = attachments->begin(); iter != attachments->end(); ++iter) { + IModel *imodel = + static_cast((*reinterpret_cast(*iter))->_mInterfaces.Find((HINTERFACE)IModel::_IHandle)); + if (imodel && imodel->GetPartName() == name) { + return imodel; + } + } + } + return nullptr; +} + +IModel *DrawVehicle::SpawnModel(UCrc32 name, UCrc32 collisionnode, UCrc32 attributes) { + const CollisionGeometry::Bounds *bounds; + const Attrib::Collection *spec; + + if (Vehicle_Part_Count >= 61U || UTL::Collections::Listable::Count() >= 435U || !mGeometry) { + return nullptr; + } + bounds = mGeometry->GetChild(collisionnode); + if (!bounds) { + return nullptr; + } + spec = SmokeableSpawner::FindAttributes(attributes); + if (!spec) { + return nullptr; + } + return new Part(static_cast(this), GetOwner()->GetWorldID(), bounds, spec, name); +} + +bool DrawVehicle::IsHidden() const { + return !GetVehicle()->IsActive(); +} + +void DrawVehicle::HideModel() { + GetVehicle()->Deactivate(); +} + +void DrawVehicle::ReleaseModel() { + static_cast(this)->ReleaseChildModels(); +} + +void DrawVehicle::ReleaseChildModels() { + if (mAttachments) { + delete mAttachments; + } + mAttachments = new Sim::Attachments(static_cast(this)); +} + +UCrc32 DrawVehicle::GetName() const { + return GetVehicle()->GetVehicleAttributes().MODEL(); +} + +void DrawVehicle::StopEffects() { + for (EffectList::iterator iter = mEffects.begin(); iter != mEffects.end(); iter++) { + Effect *effect = *iter; + delete effect; + } + mEffects.clear(); +} + +void DrawVehicle::PlayEffect(UCrc32 identifire, const Attrib::Collection *effect, const UMath::Vector3 &position, const UMath::Vector3 &magnitude, + bool tracking) { + if (identifire == UCrc32::kNull) { + return; + } + + Effect *drawEffect = nullptr; + for (EffectList::iterator iter = mEffects.begin(); iter != mEffects.end(); iter++) { + if ((*iter)->Identifire == identifire) { + drawEffect = *iter; + break; + } + } + + if (!drawEffect) { + drawEffect = ::new ("DrawVehicle", 0) Effect(identifire, static_cast(this)->GetWorldID(), GetAttributes().GetConstCollection()); + mEffects.push_back(drawEffect); + } + + drawEffect->Set(effect, position, magnitude, nullptr, tracking, 0); +} + +void DrawVehicle::StopEffect(UCrc32 identifire) { + for (EffectList::iterator iter = mEffects.begin(); iter != mEffects.end(); iter++) { + Effect *effect = *iter; + if (effect->Identifire == identifire) { + delete effect; + mEffects.erase(iter); + return; + } + } +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/DrawVehicle.h b/src/Speed/Indep/Src/Physics/Behaviors/DrawVehicle.h index d324d7b14..4d94fba34 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/DrawVehicle.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/DrawVehicle.h @@ -5,6 +5,253 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/effects.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/smackable.h" +#include "Speed/Indep/Src/Interfaces/SimModels/ITriggerableModel.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IDamageable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngine.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngineDamage.h" +#include "Speed/Indep/Src/Interfaces/Simables/INISCarControl.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRBVehicle.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRenderable.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISpikeable.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISuspension.h" +#include "Speed/Indep/Src/Physics/SmackableTrigger.h" +#include "Speed/Indep/Src/Physics/VehicleBehaviors.h" +#include "Speed/Indep/Src/Sim/SimModel.h" +#include "Speed/Indep/Src/World/CarInfo.hpp" +namespace RenderConn { +class Pkt_Car_Service; +class Pkt_Heli_Service; +} + +// total size: 0x88 +class DrawVehicle : public VehicleBehavior, public IRenderable, public IModel, public IAttachable { + public: + // total size: 0x60 + class Effect : public Sim::Effect { + public: + Effect(UCrc32 id, unsigned int owner, const Attrib::Collection *parent) + : Sim::Effect(owner, parent), // + Identifire(id) {} + + ~Effect() override; + + const UCrc32 Identifire; // offset 0x5C, size 0x4 + }; + + // total size: 0xCC + class Part : public Sim::Model, public ITriggerableModel { + public: + enum State { + S_NONE = 0, + S_DRAW = 1, + S_HIDE = 2, + S_TRIGGER = 3 + }; + + Part(IModel *parent, WUID vehicleID, const CollisionGeometry::Bounds *geoms, const Attrib::Collection *spec, UCrc32 partname); + ~Part() override; + void GetTransform(UMath::Matrix4 &transform) const override; + void OnProcessFrame(float dT) override; + void PlaceTrigger(const UMath::Matrix4 &mat, bool retrigger) override; + const Attrib::Instance &GetAttributes() const override; + + protected: + void OnBeginSimulation() override; + bool OnDraw(Sim::Packet *service) override; + void OnBeginDraw() override; + void OnEndDraw() override; + + private: + void CreateTrigger(const UMath::Matrix4 &matrix); + void RemoveTrigger(); + + State mState; // offset 0xA4, size 0x4 + SmackableTrigger *mTrigger; // offset 0xA8, size 0x4 + Attrib::Gen::smackable mAttributes; // offset 0xAC, size 0x14 + bool mOffScreenTask; // offset 0xC0, size 0x1 + float mOffScreenTime; // offset 0xC4, size 0x4 + WUID mVehicleID; // offset 0xC8, size 0x4 + }; + + typedef UTL::Std::list EffectList; + + HMODEL GetModelHandle() const override; + const IModel *GetModel() const override; + IModel *GetModel() override; + void OnProcessFrame(float dT) override; + void GetLinearVelocity(UMath::Vector3 &velocity) const override; + void GetAngularVelocity(UMath::Vector3 &velocity) const override; + void GetTransform(UMath::Matrix4 &transform) const override; + UCrc32 GetPartName() const override; + WUID GetWorldID() const override; + const CollisionGeometry::Bounds *GetCollisionGeometry() const override; + const Attrib::Instance &GetAttributes() const override; + ISimable *GetSimable() const override; + IModel *GetRootModel() const override; + IModel *GetParentModel() const override; + bool IsRootModel() const override; + EventSequencer::IEngine *GetEventSequencer() override; + float GetCausalityTime() const 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; + void OnDetached(IAttachable *pOther) override; + const IAttachable::List *GetAttachments() const override; + + DrawVehicle(const BehaviorParams &bp); + ~DrawVehicle() override; + void SetCausality(HCAUSE from, float time) override; + HCAUSE GetCausality() const override; + Enumerator *EnumerateChildren(Enumerator *enumerator) const override; + IModel *GetChildModel(UCrc32 name) const override; + IModel *SpawnModel(UCrc32 name, UCrc32 collisionnode, UCrc32 attributes) override; + void ReleaseModel() override; + void ReleaseChildModels() override; + virtual UCrc32 GetName() const; + bool IsHidden() const override; + void HideModel() override; + void StopEffects() override; + void PlayEffect(UCrc32 identifire, const Attrib::Collection *effect, const UMath::Vector3 &position, const UMath::Vector3 &magnitude, + bool tracking) override; + void StopEffect(UCrc32 identifire) override; + + protected: + const CollisionGeometry::Bounds *mGeometry; // offset 0x70, size 0x4 + Sim::Attachments *mAttachments; // offset 0x74, size 0x4 + EffectList mEffects; // offset 0x78, size 0x8 + HCAUSE mCausality; // offset 0x80, size 0x4 + float mCausalityTime; // offset 0x84, size 0x4 +}; + +// total size: 0x114 +class DrawHeli : public DrawVehicle { + public: + typedef DrawVehicle Base; + + void HidePart(const UCrc32 &name) override; + void ShowPart(const UCrc32 &name) override; + bool IsPartVisible(const UCrc32 &name) const override; + bool InView() const override; + bool IsRenderable() const override; + float DistanceToView() const override; + void Reset() override; + void OnTaskSimulate(float dT) override; + + static Behavior *Construct(const BehaviorParams ¶ms); + + DrawHeli(const BehaviorParams ¶ms); + void OnBehaviorChange(const UCrc32 &mechanic) override; + ~DrawHeli() override; + bool OnTask(HSIMTASK hTask, float dT) override; + void OnService(RenderConn::Pkt_Heli_Service &pkt); + bool OnService(HSIMSERVICE hCon, Sim::Packet *pkt) override; + + private: + HSIMSERVICE mRenderService; // offset 0x88, size 0x4 + Sim::Effect mEffect; // offset 0x8C, size 0x5C + Attrib::Gen::effects mWash; // offset 0xE8, size 0x14 + HSIMTASK mWashTask; // offset 0xFC, size 0x4 + bool mInView; // offset 0x100, size 0x1 + float mDistanceToView; // offset 0x104, size 0x4 + IAIHelicopter *mIAIHelicopter; // offset 0x108, size 0x4 + IVehicle *mIVehicle; // offset 0x10C, size 0x4 + ICollisionBody *mCollisionBody; // offset 0x110, size 0x4 +}; + +// total size: 0xC4 +class DrawCar : public DrawVehicle { + public: + typedef DrawVehicle Base; + typedef UTL::Std::map Parts; + + bool InView() const override; + bool IsRenderable() const override; + float DistanceToView() const override; + void Reset() override; + void OnTaskSimulate(float dT) override; + + DrawCar(const BehaviorParams ¶ms, CarRenderUsage usage); + bool IsHidden() const override; + void HideModel() override; + void ReleaseModel() override; + void ReleaseChildModels() override; + bool IsPartVisible(const UCrc32 &name) const override; + void HidePart(const UCrc32 &name) override; + void ShowPart(const UCrc32 &name) override; + void OnBehaviorChange(const UCrc32 &mechanic) override; + ~DrawCar() override; + virtual void OnService(RenderConn::Pkt_Car_Service &pkt); + bool OnService(HSIMSERVICE hCon, Sim::Packet *pkt) override; + + private: + HSIMSERVICE mRenderService; // offset 0x88, size 0x4 + ISuspension *mSuspension; // offset 0x8C, size 0x4 + IDamageable *mDamage; // offset 0x90, size 0x4 + IVehicle *mVehicle; // offset 0x94, size 0x4 + IDamageableVehicle *mVehicleDamage; // offset 0x98, size 0x4 + IRBVehicle *mRBVehicle; // offset 0x9C, size 0x4 + INISCarControl *mNIS; // offset 0xA0, size 0x4 + ISpikeable *mSpikeDamage; // offset 0xA4, size 0x4 + Parts mParts; // offset 0xA8, size 0x10 + bool mInView; // offset 0xB8, size 0x1 + float mDistanceToView; // offset 0xBC, size 0x4 + bool mHidden; // offset 0xC0, size 0x1 +}; + +// total size: 0xC4 +class DrawTraffic : public DrawCar { + public: + static Behavior *Construct(const BehaviorParams ¶ms); + + DrawTraffic(const BehaviorParams ¶ms); +}; + +// total size: 0xCC +class DrawPerformanceCar : public DrawCar { + public: + DrawPerformanceCar(const BehaviorParams ¶ms, CarRenderUsage usage); + void OnBehaviorChange(const UCrc32 &mechanic) override; + void OnService(RenderConn::Pkt_Car_Service &pkt) override; + + private: + ITransmission *mTransmission; // offset 0xC4, size 0x4 + IEngine *mEngine; // offset 0xC8, size 0x4 +}; + +// total size: 0xCC +class DrawNISCar : public DrawPerformanceCar { + public: + static Behavior *Construct(const BehaviorParams ¶ms); + + DrawNISCar(const BehaviorParams ¶ms); +}; + +// total size: 0xCC +class DrawCopCar : public DrawPerformanceCar { + public: + static Behavior *Construct(const BehaviorParams ¶ms); + + DrawCopCar(const BehaviorParams ¶ms); +}; + +// total size: 0xD0 +class DrawRaceCar : public DrawPerformanceCar { + public: + static Behavior *Construct(const BehaviorParams ¶ms); + + DrawRaceCar(const BehaviorParams ¶ms, CarRenderUsage usage); + void OnBehaviorChange(const UCrc32 &mechanic) override; + void OnService(RenderConn::Pkt_Car_Service &pkt) override; + + private: + IEngineDamage *mEngineDamage; // offset 0xCC, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/Physics/Behaviors/Effects.cpp b/src/Speed/Indep/Src/Physics/Behaviors/Effects.cpp index eb557fa66..6b9059a3d 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/Effects.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/Effects.cpp @@ -1,5 +1,6 @@ #include "Effects.h" #include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/effects.h" #include "Speed/Indep/Src/Interfaces/IListener.h" #include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" #include "Speed/Indep/Src/Physics/Behavior.h" @@ -7,8 +8,59 @@ #include "Speed/Indep/Src/Sim/Collision.h" #include "Speed/Indep/Src/Sim/SimSurface.h" -Effects::Effects(const struct BehaviorParams &bp) : Behavior(bp, 0) { - // TODO +Attrib::Key Attrib::Gen::effects::ClassKey() { + return 0xebcee74c; +} + +template <> const EffectLinkageRecord &Attrib::Attribute::Get(unsigned int index) const { + const EffectLinkageRecord *resultptr = reinterpret_cast(GetElementPointer(index)); + if (!resultptr) { + resultptr = reinterpret_cast(DefaultDataArea(sizeof(EffectLinkageRecord))); + } + return *resultptr; +} + +const EffectLinkageRecord *EffectLookup::Find(unsigned int surfacekey, const Attrib::Attribute &list) const { + unsigned int i = 0; + while (i < list.GetLength()) { + const EffectLinkageRecord &record = list.Get(i); + if (record.mSurface.GetCollectionKey() == surfacekey) { + return &record; + } + ++i; + } + return nullptr; +} + +Behavior *EffectsVehicle::Construct(const BehaviorParams ¶ms) { + return new EffectsVehicle(params); +} + +Behavior *EffectsCar::Construct(const BehaviorParams ¶ms) { + return new EffectsCar(params); +} + +Behavior *EffectsPlayer::Construct(const BehaviorParams ¶ms) { + return new EffectsPlayer(params); +} + +Behavior *EffectsSmackable::Construct(const BehaviorParams ¶ms) { + return new EffectsSmackable(params); +} + +Behavior *EffectsFragment::Construct(const BehaviorParams ¶ms) { + return new EffectsFragment(params); +} + +Effects::Effects(const struct BehaviorParams &bp) + : Behavior(bp, 0), // + Sim::Collision::IListener(), // + mIRBComplex(nullptr), // + mHitEffects(), // + mScrapeEffects(), // + mScrape(bp.fowner->GetWorldID(), bp.fowner->GetAttributes().GetConstCollection()) { + mScrapeTimeOut = 0.0f; + Sim::Collision::AddListener(this, GetOwner(), "Effects"); } Effects::~Effects() { @@ -142,7 +194,40 @@ void Effects::OnHitGround(const SimSurface &othersurface, float impulse, const U } void Effects::OnCollision(const COLLISION_INFO &cinfo) { - // TODO annoying virtuals + if (IsPaused()) { + return; + } + + if (cinfo.Type() == COLLISION_INFO::WORLD) { + SimSurface othersurface(cinfo.objBsurface); + OnHitWorld(othersurface, cinfo.impulseA, cinfo.position, cinfo.closingVel, cinfo.normal); + OnScrapeWorld(othersurface, cinfo.position, cinfo.slidingVel, cinfo.normal); + return; + } + + if (cinfo.Type() == COLLISION_INFO::OBJECT) { + UMath::Vector3 normal = cinfo.normal; + SimSurface othersurface(cinfo.objBsurface); + HSIMABLE hother = cinfo.objB; + float impulse = cinfo.impulseA; + + if (cinfo.objB == GetOwner()->GetInstanceHandle()) { + UMath::Scale(normal, -1.0f, normal); + othersurface.Change(cinfo.objAsurface); + impulse = cinfo.impulseB; + hother = cinfo.objA; + } + + OnHitObject(othersurface, impulse, cinfo.position, cinfo.closingVel, normal, hother); + OnScrapeObject(othersurface, cinfo.position, cinfo.slidingVel, normal, hother); + return; + } + + if (cinfo.Type() == COLLISION_INFO::GROUND) { + SimSurface othersurface(cinfo.objBsurface); + OnHitGround(othersurface, cinfo.impulseA, cinfo.position, cinfo.closingVel, cinfo.normal); + OnScrapeGround(othersurface, cinfo.position, cinfo.slidingVel, cinfo.normal); + } } EffectsVehicle::EffectsVehicle(const BehaviorParams &bp) : Effects(bp) {} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/Effects.h b/src/Speed/Indep/Src/Physics/Behaviors/Effects.h index 9ffe3fbe0..1d757b9f8 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/Effects.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/Effects.h @@ -24,6 +24,14 @@ struct EffectLinkageRecord { // TODO here or in SimEffect.h? class EffectLookup { public: + EffectLookup() + : mEffect(), // + mSourceKey(0), // + mDataKey(0), // + mSurfaceKey(0), // + mMinSpeed(0.0f), // + mMaxSpeed(0.0f) {} + const Attrib::Collection *GetEffect() { return mEffect.GetCollection(); } @@ -62,10 +70,7 @@ class EffectLookup { } } - const EffectLinkageRecord *Find(unsigned int surfacekey, const Attrib::Attribute &list) { - // TODO - return nullptr; - } + const EffectLinkageRecord *Find(unsigned int surfacekey, const Attrib::Attribute &list) const; float GetMinSpeed() { return mMinSpeed; @@ -132,12 +137,14 @@ class Effects : public Behavior, public Sim::Collision::IListener { // total size: 0xF4 class EffectsVehicle : public Effects { public: + static Behavior *Construct(const BehaviorParams ¶ms); EffectsVehicle(const BehaviorParams &bp); }; // total size: 0xF8 class EffectsCar : public EffectsVehicle { public: + static Behavior *Construct(const BehaviorParams ¶ms); EffectsCar(const BehaviorParams &bp); // Overrides @@ -157,12 +164,14 @@ class EffectsCar : public EffectsVehicle { // total size: 0xF8 class EffectsPlayer : public EffectsCar { public: + static Behavior *Construct(const BehaviorParams ¶ms); EffectsPlayer(const BehaviorParams &bp); }; // total size: 0xF4 class EffectsSmackable : public Effects { public: + static Behavior *Construct(const BehaviorParams ¶ms); EffectsSmackable(const BehaviorParams &bp); // Overrides @@ -173,6 +182,7 @@ class EffectsSmackable : public Effects { // total size: 0xF4 class EffectsFragment : public Effects { public: + static Behavior *Construct(const BehaviorParams ¶ms); EffectsFragment(const BehaviorParams &bp); }; diff --git a/src/Speed/Indep/Src/Physics/Behaviors/EngineRacer.cpp b/src/Speed/Indep/Src/Physics/Behaviors/EngineRacer.cpp index e20a0e17f..2361f417d 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/EngineRacer.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/EngineRacer.cpp @@ -1,7 +1,9 @@ #include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/chopperspecs.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/engine.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/induction.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/nos.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/tires.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/transmission.h" #include "Speed/Indep/Src/Generated/Events/EEngineBlown.hpp" @@ -29,6 +31,16 @@ #include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +IInductable::~IInductable() {} +ITiptronic::~ITiptronic() {} +HINTERFACE IDragTransmission::_IHandle() { + return (HINTERFACE)_IHandle; +} + +Attrib::Key Attrib::Gen::chopperspecs::ClassKey() { + return 0x5d898ee7; +} + // total size: 0x1B0 class EngineRacer : protected VehicleBehavior, protected ITransmission, @@ -399,6 +411,12 @@ EngineRacer::~EngineRacer() { IAttributeable::UnRegister(this); } +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); + float EngineRacer::GetHorsePower() const { float engine_torque = GetEngineTorque(mRPM); return NM2HP(engine_torque * mThrottle, mRPM); diff --git a/src/Speed/Indep/Src/Physics/Behaviors/PInput.cpp b/src/Speed/Indep/Src/Physics/Behaviors/PInput.cpp index e767ea74d..8573fddbc 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/PInput.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/PInput.cpp @@ -17,6 +17,8 @@ #include "Speed/Indep/Src/Physics/PhysicsObject.h" #include "Speed/Indep/Src/Physics/PhysicsTypes.h" +IInputPlayer::~IInputPlayer() {} + Behavior *PInput::Construct(const BehaviorParams ¶ms) { return new PInput(params); } @@ -35,6 +37,49 @@ void PInput::ClearInput() { mControls = InputControls(); } +InputControls &PInput::GetControls() const { + return const_cast(mControls); +} + +bool PInput::GetControlHandBrake() const { + return mControls.fHandBrake != 0.0f; +} + +bool PInput::GetControlActionButton() const { + if (!mControls.fActionButton) { + return false; + } + return true; +} + +void PInput::SetControlSteering(float steer) { + mControls.fSteering = steer; +} + +void PInput::SetControlGas(float gas) { + mControls.fGas = gas; +} + +void PInput::SetControlBrake(float brake) { + mControls.fBrake = brake; +} + +void PInput::SetControlNOS(bool nos_on) { + mControls.fNOS = nos_on; +} + +void PInput::SetControlHandBrake(float hbrake) { + mControls.fHandBrake = hbrake; +} + +void PInput::SetControlActionButton(bool hAction) { + mControls.fActionButton = hAction; +} + +void PInput::SetControlSteeringVertical(float steer) { + mControls.fSteeringVertical = steer; +} + Behavior *InputPlayer::Construct(const BehaviorParams ¶ms) { return new InputPlayer(params); } @@ -107,6 +152,18 @@ void InputPlayer::BlockInput(bool block) { } } +bool InputPlayer::IsBlocked() const { + return mBlocked; +} + +bool InputPlayer::IsLookBackButtonPressed() const { + return mLookBackButton > 0.0f; +} + +bool InputPlayer::IsPullBackButtonPressed() const { + return mPullBackButton > 0.0f; +} + void InputPlayer::FetchInput() { if (mBlocked) { FlushInput(); @@ -378,3 +435,7 @@ bool InputPlayerDrag::OnAction(const ActionRef &a) { } InputNIS::InputNIS(const BehaviorParams &bp) : PInput(bp) {} + +Behavior *InputNIS::Construct(const BehaviorParams ¶ms) { + return new InputNIS(params); +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/PInput.h b/src/Speed/Indep/Src/Physics/Behaviors/PInput.h index 88dc1d1a5..ab711433e 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/PInput.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/PInput.h @@ -28,6 +28,16 @@ class PInput : public Behavior, public IInput { // IInput void ClearInput() override; + InputControls &GetControls() const override; + bool GetControlHandBrake() const override; + bool GetControlActionButton() const override; + void SetControlSteering(float steer) override; + void SetControlGas(float gas) override; + void SetControlBrake(float brake) override; + void SetControlNOS(bool nos_on) override; + void SetControlHandBrake(float hbrake) override; + void SetControlActionButton(bool hAction) override; + void SetControlSteeringVertical(float steer) override; // Virtual methods virtual void SetControlStrafeVertical(float steer) { @@ -84,11 +94,14 @@ class InputPlayer : public PInput, public IInputPlayer { // IInput void ClearInput() override; bool IsAutomaticShift() const override; + bool IsLookBackButtonPressed() const override; + bool IsPullBackButtonPressed() const override; // IInputPlayer void FlushInput() override; void BlockInput(bool block) override; void FetchInput() override; + bool IsBlocked() const override; // Virtual methods virtual bool OnAction(const ActionRef &a) { @@ -131,6 +144,7 @@ class InputPlayerDrag : public InputPlayer { // total size: 0x7C class InputNIS : public PInput { + public: static Behavior *Construct(const BehaviorParams ¶ms); InputNIS(const BehaviorParams &bp); diff --git a/src/Speed/Indep/Src/Physics/Behaviors/RBTractor.cpp b/src/Speed/Indep/Src/Physics/Behaviors/RBTractor.cpp index 3a443add3..cae8a6110 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/RBTractor.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/RBTractor.cpp @@ -3,6 +3,7 @@ #include "Speed/Indep/Src/Interfaces/ITaskable.h" #include "Speed/Indep/Src/Interfaces/SimActivities/IVehicleCache.h" #include "Speed/Indep/Src/Physics/Bounds.h" +#include "Speed/Indep/Src/Physics/PVehicle.h" #include "Speed/Indep/Src/Physics/Dynamics.h" #include "rbvehicle.h" @@ -11,6 +12,28 @@ Behavior *RBTractor::Construct(const struct BehaviorParams ¶ms) { return new RBTractor(params, rp); } +RBTractor::~RBTractor() { + if (mHitched) { + Dynamics::Articulation::Release(this); + mHitched = false; + } + if (mTrailerTask) { + RemoveTask(mTrailerTask); + mTrailerTask = nullptr; + } +} + +void RBTractor::SetInvulnerability(eInvulnerablitiy state, float time) { + if (mTrailer && IsHitched()) { + IRBVehicle *irbv = nullptr; + mTrailer->QueryInterface(&irbv); + if (irbv) { + irbv->SetInvulnerability(state, time); + } + } + RBVehicle::SetInvulnerability(state, time); +} + void RBTractor::SetHitch(bool hitched) { if (hitched) { float YAW_LIMIT; @@ -18,8 +41,9 @@ void RBTractor::SetHitch(bool hitched) { CollisionGeometry::IBoundable *ibounds_trailer; if (mTrailer && !mHitched && mTrailer->QueryInterface(&ibounds_trailer)) { UCrc32 name5thwheel("5THWHEEL"); - const Attrib::Collection *tractor_col = CollisionGeometry::Lookup(UCrc32(GetVehicle()->GetVehicleAttributes().MODEL())); - const Attrib::Collection *trailer_col = CollisionGeometry::Lookup(UCrc32(mTrailer->GetVehicleAttributes().MODEL())); + // TODO clanker + // const Attrib::Collection *tractor_col = CollisionGeometry::Lookup(UCrc32(GetVehicle()->GetVehicleAttributes().MODEL())); + // const Attrib::Collection *trailer_col = CollisionGeometry::Lookup(UCrc32(mTrailer->GetVehicleAttributes().MODEL())); mHitched = CollisionGeometry::CreateJoint(this, name5thwheel, ibounds_trailer, name5thwheel, &m5thWheel, &mTrailer5thWheel, 0); if (mHitched) { @@ -152,7 +176,38 @@ void RBTractor::PlaceObject(const UMath::Matrix4 &orientMat, const UMath::Vector } } -// RBTractor::RBTractor(const BehaviorParams &bp, const RBComplexParams ¶ms) {} +RBTractor::RBTractor(const BehaviorParams &bp, const RBComplexParams ¶ms) + : RBVehicle(bp, params), // + IArticulatedVehicle(bp.fowner), // + IVehicleCache(this), // + mTrailer(nullptr), // + mIInput(nullptr), // + mTrailerTask(nullptr), // + mHitched(false), // + m5thWheel(UMath::Vector3::kZero), // + mTrailer5thWheel(UMath::Vector3::kZero), // + mDetachTimer(0.0f) { + GetOwner()->QueryInterface(&mIInput); + + const Attrib::RefSpec &trailerRef = GetVehicle()->GetVehicleAttributes().Trailer(); + if (trailerRef.GetCollectionKey() != 0) { + UMath::Vector3 initialVec; + UMath::Vector3 initialPos; + GetForwardVector(initialVec); + initialPos = GetPosition(); + + ISimable *isimable = UTL::COM::Factory::CreateInstance( + UCrc32("PVehicle"), VehicleParams(this, DRIVER_NONE, trailerRef.GetCollectionKey(), initialVec, initialPos, 4, nullptr, nullptr)); + if (isimable) { + if (isimable->QueryInterface(&mTrailer)) { + GetOwner()->Attach(isimable); + SetHitch(true); + } else { + delete isimable; + } + } + } +} bool RBTractor::Pose() { if (!mTrailer || !mHitched) { diff --git a/src/Speed/Indep/Src/Physics/Behaviors/RBTractor.h b/src/Speed/Indep/Src/Physics/Behaviors/RBTractor.h index c14e5edc3..349426490 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/RBTractor.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/RBTractor.h @@ -26,6 +26,7 @@ class RBTractor : public RBVehicle, public IArticulatedVehicle, public IVehicleC // IRigidBody void PlaceObject(const UMath::Matrix4 &orientMat, const UMath::Vector3 &initPos) override; + void SetInvulnerability(eInvulnerablitiy state, float time) override; // RigidBody void ModifyCollision(const RigidBody &other, const Dynamics::Collision::Plane &plane, Dynamics::Collision::Moment &myMoment) override; diff --git a/src/Speed/Indep/Src/Physics/Behaviors/RBVehicle.cpp b/src/Speed/Indep/Src/Physics/Behaviors/RBVehicle.cpp index 9fe47b3ac..595cf0311 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/RBVehicle.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/RBVehicle.cpp @@ -1,4 +1,6 @@ +#define COLLISIONREACTIONS_INSTANCE_ASSIGN_OWNER #include "RBVehicle.h" +#undef COLLISIONREACTIONS_INSTANCE_ASSIGN_OWNER #include "RigidBody.h" #include "Speed/Indep/Libs/Support/Utility/UCOM.h" #include "Speed/Indep/Libs/Support/Utility/UMath.h" @@ -15,6 +17,11 @@ #include +const Attrib::Gen::collisionreactions &Attrib::Gen::collisionreactions::operator=(const Attrib::Instance &rhs) { + Instance::operator=(rhs); + return *this; +} + Behavior *RBVehicle::Construct(const BehaviorParams ¶ms) { const RBComplexParams rp(params.fparams.Fetch(UCrc32(0xa6b47fac))); return new RBVehicle(params, rp); @@ -30,6 +37,8 @@ RBVehicle::RBVehicle(const BehaviorParams &bp, const RBComplexParams ¶ms) mGeoms = params.fgeoms ? params.fgeoms->fCollection : nullptr; } +RBVehicle::~RBVehicle() {} + unsigned int RBVehicle::GetNumContactPoints() const { unsigned int numpoints = RigidBody::GetNumContactPoints(); if (mSuspension) { diff --git a/src/Speed/Indep/Src/Physics/Behaviors/RBVehicle.h b/src/Speed/Indep/Src/Physics/Behaviors/RBVehicle.h index 629563464..64999f16c 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/RBVehicle.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/RBVehicle.h @@ -68,8 +68,8 @@ class RBVehicle : public RigidBody, public IRBVehicle { } void SetInvulnerability(eInvulnerablitiy state, float time) override { - mInvulnerableTimer = time; mInvulnerableState = state; + mInvulnerableTimer = time; } eInvulnerablitiy GetInvulnerability() const override { diff --git a/src/Speed/Indep/Src/Physics/Behaviors/ResetCar.cpp b/src/Speed/Indep/Src/Physics/Behaviors/ResetCar.cpp index e69de29bb..632e6e0ef 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/ResetCar.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/ResetCar.cpp @@ -0,0 +1,336 @@ +#include "ResetCar.h" + +#include "Speed/Indep/Src/Generated/Events/EVehicleReset.hpp" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Sim/SimSurface.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WRoadNetwork.h" + +bool DoLinesIntersect(const bVector2 &a, const bVector2 &b, const bVector2 &c, const bVector2 &d); + +struct TrackPathBarrier { + bVector2 Points[2]; + char Enabled; + char Pad; + char PlayerBarrier; + char LeftHanded; + unsigned int GroupHash; +}; + +static inline int GetNumBarriers() { + return *reinterpret_cast(reinterpret_cast(&TheTrackPathManager) + 0x484); +} + +static inline TrackPathBarrier *GetBarrier(int n) { + TrackPathBarrier **barriers = reinterpret_cast(reinterpret_cast(&TheTrackPathManager) + 0x488); + return barriers[0] + n; +} + +static inline bool IsBarrierEnabled(TrackPathBarrier *barrier) { + return barrier->Enabled != 0; +} + +static inline bool BarrierIntersects(TrackPathBarrier *barrier, const bVector2 *pointa, const bVector2 *pointb) { + return DoLinesIntersect(barrier->Points[0], barrier->Points[1], *pointa, *pointb); +} + +static inline char GetNodeInd(const WRoadNav &nav) { + return *reinterpret_cast(reinterpret_cast(&nav) + 0x98); +} + +static inline char RaceRouteForward(const WRoadSegment &seg) { + if (seg.fFlags & (1 << 2)) { + return 1; + } + return 0; +} + +Behavior *ResetCar::Construct(const BehaviorParams ¶ms) { + return new ResetCar(params); +} + +ResetCar::ResetCar(const BehaviorParams ¶ms) + : VehicleBehavior(params, 0), // + IResetable(params.fowner), // + mFlippedOver(0.0f), // + mCookies() { + mCheckTask = AddTask("Physics", 1.0f, 0.0f, Sim::TASK_FRAME_VARIABLE); + Sim::ProfileTask(mCheckTask, "ResetCar"); + + GetOwner()->QueryInterface(&mCollisionBody); + GetOwner()->QueryInterface(&mSuspension); + GetOwner()->QueryInterface(&mVehicleBody); +} + +ResetCar::~ResetCar() { + RemoveTask(mCheckTask); + + if (mVehicleBody) { + mVehicleBody->SetInvulnerability(INVULNERABLE_NONE, 0.0f); + } +} + +void ResetCar::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == BEHAVIOR_MECHANIC_SUSPENSION) { + GetOwner()->QueryInterface(&mSuspension); + } + if (mechanic == BEHAVIOR_MECHANIC_RIGIDBODY) { + GetOwner()->QueryInterface(&mCollisionBody); + GetOwner()->QueryInterface(&mVehicleBody); + } + Behavior::OnBehaviorChange(mechanic); +} + +bool ResetCar::ValidTerrain(unsigned int checktires) const { + if (mSuspension) { + SimSurface owner_surface(GetOwner()->GetWPos().GetSurface()); + + if (owner_surface == SimSurface::kNull) { + unsigned int invalid_tires = 0; + + for (unsigned int i = 0; i < mSuspension->GetNumWheels(); i++) { + if (mSuspension->GetWheelRoadSurface(i) == SimSurface::kNull) { + invalid_tires++; + } + } + + if (!(invalid_tires < checktires)) { + return false; + } + } + } + + return true; +} + +bool ResetCar::CanRecord() const { + if (mCookies.Count() != 0) { + const ResetCookie &cookie = mCookies.Newest(); + if (UMath::DistanceSquare(cookie.position, GetOwner()->GetPosition()) < 4.0f) { + return false; + } + } + + if (!ValidTerrain(0)) { + return false; + } + + if (mSuspension) { + if (mSuspension->GetNumWheelsOnGround() != mSuspension->GetNumWheels()) { + return false; + } + } + + return true; +} + +bool ResetCar::ShouldReset() const { + if (mCookies.Count() == 0) { + return false; + } + + if (GetVehicle()->GetDriverClass() == DRIVER_HUMAN && !ValidTerrain(2)) { + return true; + } + + return 4.0f < mFlippedOver; +} + +void ResetCar::TrackState(float dT) { + bool flippedover = false; + + if (mSuspension) { + if (mSuspension->GetNumWheelsOnGround() != mSuspension->GetNumWheels() && mCollisionBody && mCollisionBody->IsModeling()) { + { + const UMath::Vector3 &vup = mCollisionBody->GetUpVector(); + float ground_dot = UMath::Dot(vup, UMath::Vector4To3(mCollisionBody->GetGroundNormal())); + + if (ground_dot < 0.5f) { + flippedover = vup.y < 0.5f; + } + } + } + } + + if (flippedover) { + mFlippedOver += dT; + } else { + mFlippedOver = 0.0f; + } +} + +bool ResetCar::CheckZone(eTrackPathZoneType type) { + bVector2 position2d(mCollisionBody->GetPosition().z, -mCollisionBody->GetPosition().x); + TrackPathZone *zone = TheTrackPathManager.FindZone(&position2d, type, nullptr); + + if (zone) { + bVector2 *zone_position = zone->GetPosition(); + bVector2 *zone_direction = zone->GetDirection(); + UMath::Vector3 reset_position = UMath::Vector3Make(-zone_position->y, 0.0f, zone_position->x); + UMath::Vector3 reset_dir = UMath::Vector3Make(-zone_direction->y, 0.0f, zone_direction->x); + + if (1e-06f < UMath::Normalize(reset_dir)) { + return ResetTo(reset_position, reset_dir, false); + } + } + + return false; +} + +void ResetCar::Check(float dT) { + { + ResetCookie cookie; + UMath::Matrix4 mat; + + TrackState(dT); + if (!CheckZone(TRACK_PATH_ZONE_RESET_TO_POINT)) { + if (CanRecord()) { + GetOwner()->GetTransform(mat); + cookie.time = Sim::GetTime(); + cookie.position = UMath::Vector4To3(mat.v3); + cookie.direction = UMath::Vector4To3(mat.v2); + cookie.flags = 1; + mCookies.AddNew(cookie); + } else if (ShouldReset()) { + static_cast(this)->ResetVehicle(false); + } + } + } +} + +void ResetCar::Reset() { + if (mVehicleBody) { + mVehicleBody->SetInvulnerability(INVULNERABLE_NONE, 0.0f); + } + mCookies.Clear(); + mFlippedOver = 0.0f; +} + +bool ResetCar::HasResetPosition() { + return mCookies.Count() != 0; +} + +inline void ResetCar::SetResetPosition(const UMath::Vector3 &position, const UMath::Vector3 &direction) { + ResetCookie cookie; + + mCookies.Clear(); + cookie.position = position; + cookie.direction = direction; + cookie.time = Sim::GetTime(); + cookie.flags = 0; + mCookies.AddNew(cookie); +} + +void ResetCar::ClearResetPosition() { + mCookies.Clear(); +} + +bool ResetCar::ResetVehicle(bool manual) { + ResetCookie cookie; + + if (manual && CheckZone(TRACK_PATH_ZONE_GUIDED_RESET)) { + return true; + } + + if (HasResetPosition() && (!manual || !mVehicleBody || !mVehicleBody->GetInvulnerability())) { + cookie = mCookies.Oldest(); + if (cookie.flags & 1) { + FindNearestRoad(&cookie); + } + if (ResetTo(cookie.position, cookie.direction, manual)) { + mCookies.Clear(); + mCookies.AddNew(cookie); + return true; + } + } + + return false; +} + +bool ResetCar::OnTask(HSIMTASK htask, float dT) { + if (htask == mCheckTask) { + if (!IsPaused()) { + Check(dT); + } + return true; + } + return Object::OnTask(htask, dT); +} + +bool ResetCar::FindNearestRoad(ResetCookie *cookie) const { + IVehicleAI *iai = nullptr; + if (GetOwner()->QueryInterface(&iai)) { + WRoadNav nav; + UMath::Vector3 drive_dir; + + nav.SetNavType(WRoadNav::kTypeDirection); + nav.SetLaneType(WRoadNav::kLaneReset); + + bool actively_racing = false; + if (GRaceStatus::Exists() && GRaceStatus::Get().GetActivelyRacing()) { + actively_racing = true; + } + nav.SetRaceFilter(actively_racing); + nav.SetDecisionFilter(true); + drive_dir = cookie->direction; + nav.InitAtPoint(cookie->position, drive_dir, false, 1.0f); + nav.SnapToSelectableLane(); + if (nav.IsValid()) { + float elevation_difference = bClamp(nav.GetPosition().y - cookie->position.y, -6.0f, 6.0f); + if (nav.GetPosition().y - cookie->position.y == elevation_difference) { + bool crosses_barrier = false; + bVector2 a(cookie->position.z, -cookie->position.x); + bVector2 b(nav.GetPosition().z, -nav.GetPosition().x); + int num_barriers = GetNumBarriers(); + + for (int barrier_number = 0; barrier_number < num_barriers; barrier_number++) { + TrackPathBarrier *barrier = GetBarrier(barrier_number); + if (IsBarrierEnabled(barrier) && BarrierIntersects(barrier, &a, &b)) { + crosses_barrier = true; + break; + } + } + + if (!crosses_barrier) { + float worldHeight; + if (WCollisionMgr(0, 3).GetWorldHeightAtPointRigorous(nav.GetPosition(), worldHeight, nullptr)) { + if (UMath::Abs(cookie->position.y - worldHeight) < 2.5f) { + cookie->position = nav.GetPosition(); + UMath::Unit(nav.GetForwardVector(), cookie->direction); + + const WRoadSegment *seg = nav.GetSegment(); + if (seg && seg->IsInRace()) { + char raceind = RaceRouteForward(*seg); + if (GetNodeInd(nav) != raceind) { + UMath::Negate(cookie->direction); + } + } + + return true; + } + } + } + } + } + } + return false; +} + +bool ResetCar::ResetTo(const UMath::Vector3 &position, const UMath::Vector3 &direction, bool manual) { + UMath::Vector3 from_positon = GetOwner()->GetPosition(); + + if (GetVehicle()->SetVehicleOnGround(position, direction)) { + new EVehicleReset(reinterpret_cast(GetOwner()->GetInstanceHandle()), from_positon, position); + mCookies.Clear(); + mFlippedOver = 0.0f; + + if (mVehicleBody) { + mVehicleBody->SetInvulnerability(manual ? INVULNERABLE_FROM_MANUAL_RESET : INVULNERABLE_FROM_RESET, 2.0f); + } + + return true; + } + + return false; +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/ResetCar.h b/src/Speed/Indep/Src/Physics/Behaviors/ResetCar.h new file mode 100644 index 000000000..ad393c007 --- /dev/null +++ b/src/Speed/Indep/Src/Physics/Behaviors/ResetCar.h @@ -0,0 +1,52 @@ +#ifndef PHYSICS_BEHAVIORS_RESETCAR_H +#define PHYSICS_BEHAVIORS_RESETCAR_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRBVehicle.h" +#include "Speed/Indep/Src/Interfaces/Simables/IResetable.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISuspension.h" +#include "Speed/Indep/Src/Misc/CookieTrail.h" +#include "Speed/Indep/Src/Physics/VehicleBehaviors.h" +#include "Speed/Indep/Src/World/TrackPath.hpp" + +// total size: 0xFC +class ResetCar : protected VehicleBehavior, protected IResetable { + public: + typedef VehicleBehavior Base; + + static Behavior *Construct(const BehaviorParams ¶ms); + + ResetCar(const BehaviorParams ¶ms); + ~ResetCar() override; + + void OnBehaviorChange(const UCrc32 &mechanic) override; + + bool ValidTerrain(unsigned int checktires) const; + bool CanRecord() const; + bool ShouldReset() const; + void TrackState(float dT); + bool CheckZone(eTrackPathZoneType type); + void Check(float dT); + bool OnTask(HSIMTASK htask, float dT) override; + void Reset() override; + bool ResetTo(const UMath::Vector3 &position, const UMath::Vector3 &direction, bool manual); + bool FindNearestRoad(ResetCookie *cookie) const; + bool ResetVehicle(bool manual) override; + bool HasResetPosition() override; + void SetResetPosition(const UMath::Vector3 &position, const UMath::Vector3 &direction) override; + void ClearResetPosition() override; + + protected: + float mFlippedOver; // offset 0x58, size 0x4 + CookieTrail mCookies; // offset 0x5C, size 0x90 + ICollisionBody *mCollisionBody; // offset 0xEC, size 0x4 + ISuspension *mSuspension; // offset 0xF0, size 0x4 + IRBVehicle *mVehicleBody; // offset 0xF4, size 0x4 + HSIMTASK mCheckTask; // offset 0xF8, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.cpp b/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.cpp index e8b60f2ea..bfed69735 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.cpp @@ -8,17 +8,177 @@ #include "Speed/Indep/Src/Main/EventSequencer.h" #include "Speed/Indep/Src/Physics/Dynamics.h" #include "Speed/Indep/Src/Physics/Dynamics/Collision.h" +#include "Speed/Indep/Src/Physics/PhysicsObject.h" #include "Speed/Indep/Src/Sim/Collision.h" #include "Speed/Indep/Src/Sim/Simulation.h" #include "Speed/Indep/Src/World/WCollisionTri.h" #include "Speed/Indep/bWare/Inc/bWare.hpp" +#include #include +namespace Dynamics { +namespace Articulation { + +void Resolve(); + +} // namespace Articulation +} // namespace Dynamics + static char RBGrid_Memory[6912]; // size: 0x1B00, address: 0x8048A1A8 +template <> +void *ScratchPtr::mWorkSpace = nullptr; + +template <> +RigidBody::Volatile *ScratchPtr::mPointer[64] = { nullptr }; + +template <> +RigidBody::Volatile ScratchPtr::mRAMBuffer[64]; + +template <> +SAP::Grid::Node *SAP::Grid::mRootX = nullptr; + +template <> +SAP::Grid::Node *SAP::Grid::mRootZ = nullptr; + bTList TheRigidBodies; +inline bool operator<(const CollisionPacket &a, const CollisionPacket &b) { + return a.penetration > b.penetration; +} + +template <> ScratchPtr::~ScratchPtr() { + *mRef = nullptr; +} + +IDynamicsEntity::~IDynamicsEntity() {} + +struct SAPNodeAccess { + SAPNodeAccess *mHead; + SAPNodeAccess *mTail; + float mPosition; + float mSort; + void *mAxis; + SAPNodeAccess **mRoot; +}; + +struct SAPAxisAccess { + SAPNodeAccess mMin; + SAPNodeAccess mMax; + void *mGrid; +}; + +struct RBGridAccess { + SAPAxisAccess mX; + SAPAxisAccess mZ; + RigidBody *mOwner; +}; + +static inline void InitGridNode(SAPNodeAccess &node, void *axis, SAPNodeAccess **root, float position) { + node.mHead = nullptr; + node.mTail = nullptr; + node.mPosition = position; + node.mSort = position; + node.mAxis = axis; + node.mRoot = root; +} + +static inline void InsertGridNode(SAPNodeAccess *&root, SAPNodeAccess *node) { + SAPNodeAccess *tail = root; + SAPNodeAccess *head = nullptr; + + while (tail && tail->mPosition < node->mPosition) { + head = tail; + tail = tail->mTail; + } + + node->mHead = head; + node->mTail = tail; + if (head) { + head->mTail = node; + } else { + root = node; + } + if (tail) { + tail->mHead = node; + } +} + +template +SAP::Grid::~Grid() { + struct NodeAccess { + NodeAccess *mHead; + NodeAccess *mTail; + float mPosition; + float mSort; + void *mAxis; + NodeAccess **mRoot; + }; + + struct AxisAccess { + NodeAccess mMin; + NodeAccess mMax; + void *mGrid; + }; + + struct GridAccess { + AxisAccess mX; + AxisAccess mZ; + void *mOwner; + }; + + GridAccess &grid = *reinterpret_cast(this); + + if (*grid.mZ.mMax.mRoot == &grid.mZ.mMax) { + *grid.mZ.mMax.mRoot = grid.mZ.mMax.mTail; + } + if (grid.mZ.mMax.mHead) { + grid.mZ.mMax.mHead->mTail = grid.mZ.mMax.mTail; + } + if (grid.mZ.mMax.mTail) { + grid.mZ.mMax.mTail->mHead = grid.mZ.mMax.mHead; + } + grid.mZ.mMax.mTail = nullptr; + grid.mZ.mMax.mHead = nullptr; + + if (*grid.mZ.mMin.mRoot == &grid.mZ.mMin) { + *grid.mZ.mMin.mRoot = grid.mZ.mMin.mTail; + } + if (grid.mZ.mMin.mHead) { + grid.mZ.mMin.mHead->mTail = grid.mZ.mMin.mTail; + } + if (grid.mZ.mMin.mTail) { + grid.mZ.mMin.mTail->mHead = grid.mZ.mMin.mHead; + } + grid.mZ.mMin.mTail = nullptr; + grid.mZ.mMin.mHead = nullptr; + + if (*grid.mX.mMax.mRoot == &grid.mX.mMax) { + *grid.mX.mMax.mRoot = grid.mX.mMax.mTail; + } + if (grid.mX.mMax.mHead) { + grid.mX.mMax.mHead->mTail = grid.mX.mMax.mTail; + } + if (grid.mX.mMax.mTail) { + grid.mX.mMax.mTail->mHead = grid.mX.mMax.mHead; + } + grid.mX.mMax.mTail = nullptr; + grid.mX.mMax.mHead = nullptr; + + if (*grid.mX.mMin.mRoot == &grid.mX.mMin) { + *grid.mX.mMin.mRoot = grid.mX.mMin.mTail; + } + if (grid.mX.mMin.mHead) { + grid.mX.mMin.mHead->mTail = grid.mX.mMin.mTail; + } + if (grid.mX.mMin.mTail) { + grid.mX.mMin.mTail->mHead = grid.mX.mMin.mHead; + } + grid.mX.mMin.mTail = nullptr; + grid.mX.mMin.mHead = nullptr; +} + // TODO clear the magic numbers in Volatile::state and status Behavior *RigidBody::Construct(const BehaviorParams ¶ms) { @@ -30,11 +190,11 @@ RigidBody::Volatile::Volatile() {} // UNSOLVED but functionally matching RigidBody::Mesh::Mesh(const SimSurface &material, const UMath::Vector4 *verts, unsigned int count, UCrc32 name, bool persistant) - : mVerts(nullptr), // - mNumVertices(count), // - mFlags(0), // - mMaterial(material.GetConstCollection()), // - mName(name) { + : mNumVertices(count), // + mFlags(0) { + mVerts = nullptr; + mMaterial = material.GetConstCollection(); + mName = name; if (persistant) { mVerts = const_cast(verts); } else { @@ -121,12 +281,370 @@ void RigidBody::InitRigidBodySystem() {} void RigidBody::ShutdownRigidBodySystem() {} +RBGrid *RBGrid::Add(unsigned int index, RigidBody &owner, const UMath::Vector3 &position, float radius) { + if (index > 0x3F) { + return nullptr; + } + + RBGridAccess *grid = reinterpret_cast(RBGrid_Memory + index * sizeof(RBGrid)); + if (!grid) { + return nullptr; + } + + const float minX = position.x - radius; + const float maxX = position.x + radius; + const float maxZ = position.z + radius; + const float minZ = position.z - radius; + + SAPNodeAccess *&rootX = reinterpret_cast(SAP::Grid::mRootX); + SAPNodeAccess *&rootZ = reinterpret_cast(SAP::Grid::mRootZ); + + grid->mX.mMin.mHead = nullptr; + grid->mX.mMin.mTail = nullptr; + grid->mX.mMin.mPosition = minX; + grid->mX.mMin.mSort = minX; + grid->mX.mMin.mAxis = &grid->mX; + grid->mX.mMin.mRoot = &rootX; + + SAPNodeAccess *tail = rootX; + if (!tail) { + rootX = &grid->mX.mMin; + } else { + bool before = minX < tail->mPosition; + bool equal = tail->mPosition == minX; + SAPNodeAccess *head = nullptr; + SAPNodeAccess *node = rootX; + while (!(before || equal) && (tail = node->mTail, head = node, tail)) { + before = grid->mX.mMin.mPosition < tail->mPosition; + equal = tail->mPosition == grid->mX.mMin.mPosition; + node = tail; + } + if (!head) { + SAPNodeAccess *oldHead = rootX; + if (rootX == &grid->mX.mMin) { + rootX = grid->mX.mMin.mTail; + } + if (grid->mX.mMin.mHead) { + grid->mX.mMin.mHead->mTail = grid->mX.mMin.mTail; + } + if (grid->mX.mMin.mTail) { + grid->mX.mMin.mTail->mHead = grid->mX.mMin.mHead; + } + grid->mX.mMin.mHead = oldHead; + grid->mX.mMin.mTail = nullptr; + if (oldHead) { + oldHead->mTail = &grid->mX.mMin; + } + } else { + SAPNodeAccess *newTail = head->mTail; + if (rootX == &grid->mX.mMin) { + rootX = grid->mX.mMin.mTail; + } + if (grid->mX.mMin.mHead) { + grid->mX.mMin.mHead->mTail = grid->mX.mMin.mTail; + } + if (grid->mX.mMin.mTail) { + grid->mX.mMin.mTail->mHead = grid->mX.mMin.mHead; + } + grid->mX.mMin.mHead = head; + grid->mX.mMin.mTail = nullptr; + head->mTail = &grid->mX.mMin; + if (newTail) { + grid->mX.mMin.mTail = newTail; + newTail->mHead = &grid->mX.mMin; + if (grid->mX.mMin.mTail == rootX) { + rootX = &grid->mX.mMin; + } + } + } + } + + grid->mX.mMax.mHead = nullptr; + grid->mX.mMax.mTail = nullptr; + grid->mX.mMax.mPosition = maxX; + grid->mX.mMax.mSort = maxX; + grid->mX.mMax.mAxis = &grid->mX; + grid->mX.mMax.mRoot = &rootX; + grid->mX.mGrid = grid; + + tail = rootX; + if (!tail) { + rootX = &grid->mX.mMax; + } else { + bool before = maxX < tail->mPosition; + bool equal = tail->mPosition == maxX; + SAPNodeAccess *head = nullptr; + SAPNodeAccess *node = rootX; + while (!(before || equal) && (tail = node->mTail, head = node, tail)) { + before = grid->mX.mMax.mPosition < tail->mPosition; + equal = tail->mPosition == grid->mX.mMax.mPosition; + node = tail; + } + if (!head) { + SAPNodeAccess *oldHead = rootX; + if (rootX == &grid->mX.mMax) { + rootX = grid->mX.mMax.mTail; + } + if (grid->mX.mMax.mHead) { + grid->mX.mMax.mHead->mTail = grid->mX.mMax.mTail; + } + if (grid->mX.mMax.mTail) { + grid->mX.mMax.mTail->mHead = grid->mX.mMax.mHead; + } + grid->mX.mMax.mHead = oldHead; + grid->mX.mMax.mTail = nullptr; + if (oldHead) { + oldHead->mTail = &grid->mX.mMax; + } + } else { + SAPNodeAccess *newTail = head->mTail; + if (rootX == &grid->mX.mMax) { + rootX = grid->mX.mMax.mTail; + } + if (grid->mX.mMax.mHead) { + grid->mX.mMax.mHead->mTail = grid->mX.mMax.mTail; + } + if (grid->mX.mMax.mTail) { + grid->mX.mMax.mTail->mHead = grid->mX.mMax.mHead; + } + grid->mX.mMax.mHead = head; + grid->mX.mMax.mTail = nullptr; + head->mTail = &grid->mX.mMax; + if (newTail) { + grid->mX.mMax.mTail = newTail; + newTail->mHead = &grid->mX.mMax; + if (grid->mX.mMax.mTail == rootX) { + rootX = &grid->mX.mMax; + } + } + } + } + + grid->mZ.mMin.mHead = nullptr; + grid->mZ.mMin.mTail = nullptr; + grid->mZ.mMin.mPosition = minZ; + grid->mZ.mMin.mSort = minZ; + grid->mZ.mMin.mAxis = &grid->mZ; + grid->mZ.mMin.mRoot = &rootZ; + + tail = rootZ; + if (!tail) { + rootZ = &grid->mZ.mMin; + } else { + bool before = minZ < tail->mPosition; + bool equal = tail->mPosition == minZ; + SAPNodeAccess *head = nullptr; + SAPNodeAccess *node = rootZ; + while (!(before || equal) && (tail = node->mTail, head = node, tail)) { + before = grid->mZ.mMin.mPosition < tail->mPosition; + equal = tail->mPosition == grid->mZ.mMin.mPosition; + node = tail; + } + if (!head) { + SAPNodeAccess *oldHead = rootZ; + if (rootZ == &grid->mZ.mMin) { + rootZ = grid->mZ.mMin.mTail; + } + if (grid->mZ.mMin.mHead) { + grid->mZ.mMin.mHead->mTail = grid->mZ.mMin.mTail; + } + if (grid->mZ.mMin.mTail) { + grid->mZ.mMin.mTail->mHead = grid->mZ.mMin.mHead; + } + grid->mZ.mMin.mHead = oldHead; + grid->mZ.mMin.mTail = nullptr; + if (oldHead) { + oldHead->mTail = &grid->mZ.mMin; + } + } else { + SAPNodeAccess *newTail = head->mTail; + if (rootZ == &grid->mZ.mMin) { + rootZ = grid->mZ.mMin.mTail; + } + if (grid->mZ.mMin.mHead) { + grid->mZ.mMin.mHead->mTail = grid->mZ.mMin.mTail; + } + if (grid->mZ.mMin.mTail) { + grid->mZ.mMin.mTail->mHead = grid->mZ.mMin.mHead; + } + grid->mZ.mMin.mHead = head; + grid->mZ.mMin.mTail = nullptr; + head->mTail = &grid->mZ.mMin; + if (newTail) { + grid->mZ.mMin.mTail = newTail; + newTail->mHead = &grid->mZ.mMin; + if (grid->mZ.mMin.mTail == rootZ) { + rootZ = &grid->mZ.mMin; + } + } + } + } + + grid->mZ.mMax.mHead = nullptr; + grid->mZ.mMax.mTail = nullptr; + grid->mZ.mMax.mPosition = maxZ; + grid->mZ.mMax.mSort = maxZ; + grid->mZ.mMax.mAxis = &grid->mZ; + grid->mZ.mMax.mRoot = &rootZ; + grid->mZ.mGrid = grid; + + tail = rootZ; + if (!tail) { + rootZ = &grid->mZ.mMax; + } else { + bool before = maxZ < tail->mPosition; + bool equal = tail->mPosition == maxZ; + SAPNodeAccess *head = nullptr; + SAPNodeAccess *node = rootZ; + while (!(before || equal) && (tail = node->mTail, head = node, tail)) { + before = grid->mZ.mMax.mPosition < tail->mPosition; + equal = tail->mPosition == grid->mZ.mMax.mPosition; + node = tail; + } + if (!head) { + SAPNodeAccess *oldHead = rootZ; + if (rootZ == &grid->mZ.mMax) { + rootZ = grid->mZ.mMax.mTail; + } + if (grid->mZ.mMax.mHead) { + grid->mZ.mMax.mHead->mTail = grid->mZ.mMax.mTail; + } + if (grid->mZ.mMax.mTail) { + grid->mZ.mMax.mTail->mHead = grid->mZ.mMax.mHead; + } + grid->mZ.mMax.mHead = oldHead; + grid->mZ.mMax.mTail = nullptr; + if (oldHead) { + oldHead->mTail = &grid->mZ.mMax; + } + } else { + SAPNodeAccess *newTail = head->mTail; + if (rootZ == &grid->mZ.mMax) { + rootZ = grid->mZ.mMax.mTail; + } + if (grid->mZ.mMax.mHead) { + grid->mZ.mMax.mHead->mTail = grid->mZ.mMax.mTail; + } + if (grid->mZ.mMax.mTail) { + grid->mZ.mMax.mTail->mHead = grid->mZ.mMax.mHead; + } + grid->mZ.mMax.mHead = head; + grid->mZ.mMax.mTail = nullptr; + head->mTail = &grid->mZ.mMax; + if (newTail) { + grid->mZ.mMax.mTail = newTail; + newTail->mHead = &grid->mZ.mMax; + if (grid->mZ.mMax.mTail == rootZ) { + rootZ = &grid->mZ.mMax; + } + } + } + } + + grid->mOwner = &owner; + return reinterpret_cast(grid); +} + void RBGrid::Remove(RBGrid *grid) { if (grid) { grid->~RBGrid(); } } +RigidBody::RigidBody(const BehaviorParams &bp, const RBComplexParams ¶ms) + : Behavior(bp, 0), // + IRigidBody(bp.fowner), // + ICollisionBody(bp.fowner), // + IDynamicsEntity(bp.fowner), // + IBoundable(bp.fowner), // + mData(), // + mSpecs(this, 0), // + mWCollider(nullptr), // + mCOG(UMath::Vector3::kZero), // + mGeoms(params.fgeoms), // + mGrid(nullptr), // + mCollisionMask(params.fCollisionMask), // + mSimableType(GetOwner()->GetSimableType()), // + mDetachForce(0.0f) { + unsigned int world_id; + + UMath::Copy(UMath::Matrix4::kIdentity, mInvWorldTensor); + mGroundNormal.x = 0.0f; + mGroundNormal.y = 1.0f; + mGroundNormal.z = 0.0f; + mGroundNormal.w = 0.0f; + { + float dimension = params.fdimension.x; + if (dimension < 0.1f) { + dimension = 0.1f; + } + mDimension.x = dimension; + } + { + float dimension = params.fdimension.y; + if (dimension < 0.1f) { + dimension = 0.1f; + } + mDimension.y = dimension; + } + { + float dimension = params.fdimension.z; + if (dimension < 0.1f) { + dimension = 0.1f; + } + mDimension.z = dimension; + } + + TheRigidBodies.AddTail(this); + MakeDebugable(DBG_RIGIDBODY); + + mData->force = UMath::Vector3::kZero; + mData->torque = UMath::Vector3::kZero; + mData->index = AssignSlot(); + mData->mass = params.finitMass; + mData->status = 0; + mData->statusPrev = 0; + mData->position = params.finitPos; + mData->linearVel = params.factive ? params.finitVel : UMath::Vector3::kZero; + mData->angularVel = params.factive ? params.finitAngVel : UMath::Vector3::kZero; + UMath::Copy(UMath::Matrix4::kIdentity, mData->bodyMatrix); + mData->inertiaTensor = params.finitMoment; + mData->leversInContact = 0; + mData->oom = 1.0f / mData->mass; + mData->state = 0; + mData->radius = UMath::Length(params.fdimension); + SetOrientation(params.finitMat); + mGrid = RBGrid::Add(mData->index, *this, params.finitPos, UMath::Length(params.fdimension)); + + mCOG = UMath::Vector4To3(mSpecs->CG()); + if (!params.factive) { + mData->state = 1; + } + + if (UMath::LengthSquare(UMath::Vector4To3(mSpecs->DRAG())) > 0.0f) { + mData->status |= 0x20; + } else { + mData->status &= 0xFFDF; + } + + if (UMath::LengthSquare(UMath::Vector4To3(mSpecs->DRAG_ANGULAR())) > 0.0f) { + mData->status |= 0x800; + } else { + mData->status &= 0xF7FF; + } + + if (mSimableType == SIMABLE_VEHICLE) { + mData->status |= 0x40; + } + + world_id = GetOwner()->GetWorldID(); + mWCollider = WCollider::Create(world_id, WCollider::kColliderShape_Cylinder, 0x1C, mCollisionMask); + CreateGeometries(); + mCount++; + mMaps[mData->index] = this; + mData->status |= 0x200; +} + RigidBody::~RigidBody() { if (mGrid) { RBGrid::Remove(mGrid); @@ -239,6 +757,63 @@ void RigidBody::SetInertiaTensor(const UMath::Vector3 &moment) { mData->inertiaTensor = moment; } +void RigidBody::DoIntegration(const float dT) { + Volatile &data = *mData; + + if (data.state == STATE_AWAKE) { + UMath::Vector3 cg0; + UMath::Rotate(mCOG, data.bodyMatrix, cg0); + + UMath::ScaleAdd(data.force, dT * data.oom, data.linearVel, data.linearVel); + UMath::Scale(data.torque, dT, data.torque); + UMath::ScaleAdd(data.linearVel, dT, data.position, data.position); + UMath::Rotate(data.torque, mInvWorldTensor, data.torque); + UMath::Add(data.angularVel, data.torque, data.angularVel); + + data.linearVel.x = UMath::Clamp(data.linearVel.x, -300.0f, 300.0f); + data.linearVel.y = UMath::Clamp(data.linearVel.y, -300.0f, 300.0f); + data.linearVel.z = UMath::Clamp(data.linearVel.z, -300.0f, 300.0f); + data.angularVel.x = UMath::Clamp(data.angularVel.x, -30.0f, 30.0f); + data.angularVel.y = UMath::Clamp(data.angularVel.y, -30.0f, 30.0f); + data.angularVel.z = UMath::Clamp(data.angularVel.z, -30.0f, 30.0f); + + if (UMath::LengthSquare(data.angularVel) != 0.0f) { + UMath::Vector3 angularStep; + UMath::Vector4 deltaOrientation; + + UMath::Scale(data.angularVel, dT, angularStep); + deltaOrientation.x = + angularStep.x * data.orientation.w + (angularStep.y * data.orientation.z - angularStep.z * data.orientation.y); + deltaOrientation.y = + angularStep.y * data.orientation.w + (angularStep.z * data.orientation.x - angularStep.x * data.orientation.z); + deltaOrientation.z = + angularStep.z * data.orientation.w + (angularStep.x * data.orientation.y - angularStep.y * data.orientation.x); + deltaOrientation.w = + -(angularStep.x * data.orientation.x + angularStep.y * data.orientation.y + angularStep.z * data.orientation.z); + + UMath::ScaleAdd(deltaOrientation, 0.5f, data.orientation, data.orientation); + + float orientationLength = + UMath::Sqrt(UMath::Dotxyz(data.orientation, data.orientation) + data.orientation.w * data.orientation.w); + if (orientationLength != 0.0f) { + UMath::Scale(data.orientation, 1.0f / orientationLength, data.orientation); + } + + UMath::QuaternionToMatrix4(data.orientation, data.bodyMatrix); + data.inertiaTensor.GetInverseWorldTensor(data.bodyMatrix, mInvWorldTensor); + + UMath::Vector3 cg1; + UMath::Vector3 dcg; + UMath::Rotate(mCOG, data.bodyMatrix, cg1); + UMath::Sub(cg0, cg1, dcg); + UMath::Add(data.position, dcg, data.position); + } + } else { + UMath::Clear(data.linearVel); + UMath::Clear(data.angularVel); + } +} + void RigidBody::SetMass(float newMass) { Volatile &data = *mData; float ratio = data.mass; @@ -1024,7 +1599,75 @@ void RigidBody::DoWorldCollisions(const float dT) { } } -void RigidBody::DoBarrierCollision(float dT) {} +void RigidBody::DoObbCollision(float dT) { + if (mWCollider->fObbList.empty()) { + return; + } + + Volatile &data = *mData; + WCollisionMgr mgr(mCollisionMask, 3); + Dynamics::Collision::Geometry thisGeom; + Dynamics::Collision::Geometry otherGeom; + + const WCollisionObject *const *obb = mWCollider->fObbList.begin(); + const WCollisionObject *const *obbEnd = mWCollider->fObbList.end(); + while (obb != obbEnd) { + UMath::Vector3 otherVelocity = UMath::Vector3::kZero; + WSurface otherSurface; + const WCollisionObject *object = *obb++; + + mgr.BuildGeomFromWorldObb(*object, dT, otherGeom, otherVelocity, otherSurface); + for (Primitive *collider = mPrimitives.GetHead(); collider != mPrimitives.EndOfList(); collider = collider->GetNext()) { + if (!(collider->GetFlags() & Primitive::VSWORLD) || (collider->GetFlags() & Primitive::DISABLED) || + !collider->SetCollision(data, thisGeom) || !Dynamics::Collision::Geometry::FindIntersection(&thisGeom, &otherGeom, &thisGeom)) { + continue; + } + + COLLISION_INFO collisionInfo; + SimSurface bodySurface(collider->GetMaterial()); + + data.SetStatus(Volatile::HAS_HAD_OBJECT_COLLISION); + UMath::ScaleAdd(thisGeom.GetCollisionNormal(), -thisGeom.GetOverlap(), data.position, data.position); + if (ResolveWorldOBBCollision(thisGeom.GetCollisionNormal(), thisGeom.GetCollisionPoint(), &collisionInfo, &otherGeom, otherVelocity, + bodySurface, SimSurface::kNull)) { + data.SetStatus(Volatile::HAS_HAD_WORLD_COLLISION); + new ECollision(collisionInfo); + } + } + } +} + +void RigidBody::DoBarrierCollision(float dT) { + if (mWCollider->fBarrierList.empty()) { + return; + } + + const Volatile &data = *mData; + const WCollisionBarrierList &barriers = mWCollider->fBarrierList; + Dynamics::Collision::Geometry thisGeom; + bool test_primitives = true; + + if (mPrimitives.Size() > 1) { + UMath::Vector3 velocity; + UMath::Vector3 dim; + float radius = mPrimitives.GetRadius(); + + UMath::Scale(data.linearVel, dT, velocity); + dim.x = radius; + dim.y = radius; + dim.z = radius; + thisGeom.Set(data.bodyMatrix, data.position, dim, Dynamics::Collision::Geometry::SPHERE, velocity); + test_primitives = WCollisionMgr(mCollisionMask, 3).Collide(&thisGeom, &barriers, this, nullptr, false); + } + + if (test_primitives) { + for (Primitive *collider = mPrimitives.GetHead(); collider != mPrimitives.EndOfList(); collider = collider->GetNext()) { + if ((((collider->GetFlags() ^ 1) & 1) == 0) && !(collider->GetFlags() & 8) && collider->SetCollision(data, thisGeom)) { + WCollisionMgr(mCollisionMask, 3).Collide(&thisGeom, &barriers, this, collider, (collider->GetFlags() & Primitive::ONESIDED) != 0); + } + } + } +} void RigidBody::DoInstanceCollision(float dT) { if (!mSpecs.INSTANCE_COLLISIONS_3D()) { @@ -1034,6 +1677,145 @@ void RigidBody::DoInstanceCollision(float dT) { } } +void RigidBody::DoInstanceCollision2d(float dT) { + Volatile &data = *mData; + CollisionPacket packets[16]; + unsigned int packetCount = 0; + UMath::Vector4 deepestPenetration = UMath::Vector4::kZero; + UMath::Vector3 worldCG; + WWorldPos worldPos = GetOwner()->GetWPos(); + UMath::Vector3 upExtent; + + data.leversInContact = 0; + UMath::Rotate(mCOG, data.bodyMatrix, worldCG); + + upExtent.x = data.bodyMatrix.v0.y * mDimension.x; + upExtent.y = data.bodyMatrix.v1.y * mDimension.y; + upExtent.z = data.bodyMatrix.v2.y * mDimension.z; + + const float speedXZ = UMath::Lengthxz(data.linearVel); + const float projectedHeight = UMath::Length(upExtent); + const float maxBodyY = data.position.y + projectedHeight; + + for (Primitive *collider = mPrimitives.GetHead(); collider != mPrimitives.EndOfList() && packetCount < 16; collider = collider->GetNext()) { + if (!(collider->GetFlags() & Primitive::VSGROUND) || (collider->GetFlags() & Primitive::DISABLED) || + collider->GetShape() != Dynamics::Collision::Geometry::BOX) { + continue; + } + + Dynamics::Collision::Geometry boxGeom; + if (!collider->SetCollision(data, boxGeom)) { + continue; + } + + const UMath::Vector3 &dim = collider->GetDimension(); + for (int corner = 0; corner < 8 && packetCount < 16; ++corner) { + UMath::Vector3 localCorner = UMath::Vector3::kZero; + UMath::Vector3 lever; + UMath::Vector3 worldPoint; + UMath::Vector3 arm; + UMath::Vector4 groundNormal = mGroundNormal; + + localCorner.x = (corner & 1) ? dim.x : -dim.x; + localCorner.y = (corner & 2) ? dim.y : -dim.y; + localCorner.z = (corner & 4) ? dim.z : -dim.z; + + UMath::Rotate(localCorner, boxGeom.GetOrientation(), lever); + UMath::Add(lever, boxGeom.GetPosition(), worldPoint); + UMath::Sub(worldPoint, data.position, lever); + + float fallDistance = + (data.angularVel.x * (lever.z - worldCG.z) - (data.angularVel.z * (lever.x - worldCG.x) + data.linearVel.y)) * dT; + fallDistance = UMath::Max(fallDistance, 0.0f); + + float tolerance = maxBodyY - worldPoint.y; + const float maxTolerance = projectedHeight + speedXZ * dT + fallDistance; + if (maxTolerance < tolerance) { + tolerance = maxTolerance; + } + if (tolerance < 0.25f) { + tolerance = 0.25f; + } + + worldPos.SetTolerance(tolerance); + if (worldPos.Update(worldPoint, groundNormal, true, mWCollider, true) && groundNormal.w > 1.0e-6f) { + if (deepestPenetration.w < groundNormal.w) { + deepestPenetration = groundNormal; + } + + arm = lever; + ConvertWorldToLocal(arm, false); + + CollisionPacket &packet = packets[packetCount++]; + packet.lever = lever; + packet.bodysurface = collider->GetMaterial(); + packet.normal = UMath::Vector4To3(groundNormal); + packet.penetration = groundNormal.w; + packet.arm = arm; + packet.surface = worldPos.GetSurface(); + } + } + } + + for (Mesh *mesh = mMeshes.GetHead(); mesh != mMeshes.EndOfList() && packetCount < 16; mesh = mesh->GetNext()) { + if (mesh->GetFlags() & Mesh::DISABLED) { + continue; + } + + const UMath::Vector4 *vertices = mesh->GetVerts(); + const unsigned int vertexCount = mesh->GetNumVertices(); + for (unsigned int i = 0; i < vertexCount && packetCount < 16; ++i) { + UMath::Vector3 arm = UMath::Vector4To3(vertices[i]); + UMath::Vector3 lever; + UMath::Vector3 worldPoint; + UMath::Vector4 groundNormal = mGroundNormal; + + UMath::Rotate(arm, data.bodyMatrix, lever); + UMath::Add(lever, data.position, worldPoint); + + float fallDistance = + (data.angularVel.x * (lever.z - worldCG.z) - (data.angularVel.z * (lever.x - worldCG.x) + data.linearVel.y)) * dT; + fallDistance = UMath::Max(fallDistance, 0.0f); + + float tolerance = maxBodyY - worldPoint.y; + const float maxTolerance = projectedHeight + speedXZ * dT + fallDistance; + if (maxTolerance < tolerance) { + tolerance = maxTolerance; + } + if (tolerance < 0.25f) { + tolerance = 0.25f; + } + + worldPos.SetTolerance(tolerance); + if (worldPos.Update(worldPoint, groundNormal, true, mWCollider, true) && groundNormal.w > 1.0e-6f) { + if (deepestPenetration.w < groundNormal.w) { + deepestPenetration = groundNormal; + } + + CollisionPacket &packet = packets[packetCount++]; + packet.lever = lever; + packet.bodysurface = mesh->GetMaterial(); + packet.normal = UMath::Vector4To3(groundNormal); + packet.penetration = groundNormal.w; + packet.arm = arm; + packet.surface = worldPos.GetSurface(); + } + } + } + + if (packetCount != 0) { + data.leversInContact = static_cast(packetCount); + if (packetCount > 1) { + std::sort(packets, packets + packetCount); + } + + ResolveGroundCollision(packets, packetCount); + if (deepestPenetration.w > 0.0f) { + UMath::ScaleAdd(UMath::Vector4To3(deepestPenetration), deepestPenetration.w, data.position, data.position); + } + } +} + void RigidBody::DoInstanceCollision3d(float dT) { Volatile &data = *mData; WCollisionMgr::WorldCollisionInfo cInfo; @@ -1106,6 +1888,178 @@ void RigidBody::PopSP() { ScratchPtr::Pop(); } +void RigidBody::Update(const float dT) { + int overlapX = 0; + int overlapZ = 0; + + for (RigidBody *body = TheRigidBodies.GetHead(); body != TheRigidBodies.EndOfList(); body = body->GetNext()) { + body->OnBeginFrame(dT); + body->UpdateGrid(overlapX, overlapZ); + } + + for (SAPNodeAccess *nodeX = reinterpret_cast(SAP::Grid::mRootX); nodeX;) { + SAPNodeAccess *next = nodeX->mTail; + const float position = nodeX->mPosition; + nodeX->mSort = position; + + SAPNodeAccess *head = nullptr; + for (SAPNodeAccess *node = nodeX->mHead; node && position < node->mSort; node = node->mHead) { + head = node; + } + + if (head) { + SAPNodeAccess *newHead = head->mHead; + if (*nodeX->mRoot == nodeX) { + *nodeX->mRoot = nodeX->mTail; + } + if (nodeX->mHead) { + nodeX->mHead->mTail = nodeX->mTail; + } + if (nodeX->mTail) { + nodeX->mTail->mHead = nodeX->mHead; + } + nodeX->mTail = head; + nodeX->mHead = newHead; + if (newHead) { + newHead->mTail = nodeX; + } + head->mHead = nodeX; + if (nodeX->mTail == *nodeX->mRoot) { + *nodeX->mRoot = nodeX; + } + nodeX = next; + continue; + } + + SAPNodeAccess *tail = nullptr; + for (SAPNodeAccess *node = nodeX->mTail; node && node->mSort < position; node = node->mTail) { + tail = node; + } + + if (tail) { + SAPNodeAccess *newTail = tail->mTail; + if (*nodeX->mRoot == nodeX) { + *nodeX->mRoot = nodeX->mTail; + } + if (nodeX->mHead) { + nodeX->mHead->mTail = nodeX->mTail; + } + if (nodeX->mTail) { + nodeX->mTail->mHead = nodeX->mHead; + } + nodeX->mHead = tail; + tail->mTail = nodeX; + if (newTail) { + nodeX->mTail = newTail; + newTail->mHead = nodeX; + if (nodeX->mTail == *nodeX->mRoot) { + *nodeX->mRoot = nodeX; + } + } else { + nodeX->mTail = nullptr; + } + } + nodeX = next; + } + + for (SAPNodeAccess *nodeZ = reinterpret_cast(SAP::Grid::mRootZ); nodeZ;) { + SAPNodeAccess *next = nodeZ->mTail; + const float position = nodeZ->mPosition; + nodeZ->mSort = position; + + SAPNodeAccess *head = nullptr; + for (SAPNodeAccess *node = nodeZ->mHead; node && position < node->mSort; node = node->mHead) { + head = node; + } + + if (head) { + SAPNodeAccess *newHead = head->mHead; + if (*nodeZ->mRoot == nodeZ) { + *nodeZ->mRoot = nodeZ->mTail; + } + if (nodeZ->mHead) { + nodeZ->mHead->mTail = nodeZ->mTail; + } + if (nodeZ->mTail) { + nodeZ->mTail->mHead = nodeZ->mHead; + } + nodeZ->mTail = head; + nodeZ->mHead = newHead; + if (newHead) { + newHead->mTail = nodeZ; + } + head->mHead = nodeZ; + if (nodeZ->mTail == *nodeZ->mRoot) { + *nodeZ->mRoot = nodeZ; + } + nodeZ = next; + continue; + } + + SAPNodeAccess *tail = nullptr; + for (SAPNodeAccess *node = nodeZ->mTail; node && node->mSort < position; node = node->mTail) { + tail = node; + } + + if (tail) { + SAPNodeAccess *newTail = tail->mTail; + if (*nodeZ->mRoot == nodeZ) { + *nodeZ->mRoot = nodeZ->mTail; + } + if (nodeZ->mHead) { + nodeZ->mHead->mTail = nodeZ->mTail; + } + if (nodeZ->mTail) { + nodeZ->mTail->mHead = nodeZ->mHead; + } + nodeZ->mHead = tail; + tail->mTail = nodeZ; + if (newTail) { + nodeZ->mTail = newTail; + newTail->mHead = nodeZ; + if (nodeZ->mTail == *nodeZ->mRoot) { + *nodeZ->mRoot = nodeZ; + } + } else { + nodeZ->mTail = nullptr; + } + } + nodeZ = next; + } + + const bool useZ = overlapZ <= overlapX; + SAPNodeAccess *root = reinterpret_cast(useZ ? SAP::Grid::mRootZ : SAP::Grid::mRootX); + for (SAPNodeAccess *node = root; node; node = node->mTail) { + if (node == reinterpret_cast(node->mAxis)) { + SAPAxisAccess *axis = reinterpret_cast(node->mAxis); + RBGridAccess *grid = reinterpret_cast(axis->mGrid); + SAPAxisAccess *otherAxis = useZ ? &grid->mX : &grid->mZ; + const float max = otherAxis->mMax.mPosition; + const float min = otherAxis->mMin.mPosition; + + for (SAPNodeAccess *test = node->mTail; test != &axis->mMax; test = test->mTail) { + if (test == reinterpret_cast(test->mAxis)) { + SAPAxisAccess *testAxis = reinterpret_cast(test->mAxis); + RBGridAccess *testGrid = reinterpret_cast(testAxis->mGrid); + SAPAxisAccess *testOtherAxis = useZ ? &testGrid->mX : &testGrid->mZ; + const float testMin = testOtherAxis->mMin.mPosition; + const float testMax = testOtherAxis->mMax.mPosition; + + if (((min <= testMin) && (testMin <= max)) || ((min <= testMax) && (testMax <= max)) || + ((testMin < min) && (max < testMax))) { + OnObjectOverlap(*grid->mOwner, *testGrid->mOwner, dT); + } + } + } + } + } + + for (RigidBody *body = TheRigidBodies.GetHead(); body != TheRigidBodies.EndOfList(); body = body->GetNext()) { + body->OnEndFrame(dT); + } + Dynamics::Articulation::Resolve(); +} + void RigidBody::Damp(float amount) { Volatile &data = *mData; float scale = 1.0f - amount; diff --git a/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.h b/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.h index 360072b1b..dcda40d58 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/RigidBody.h @@ -93,6 +93,22 @@ class RigidBody : public Behavior, return mName; } + const UMath::Vector4 *GetVerts() const { + return mVerts; + } + + unsigned int GetNumVertices() const { + return mNumVertices; + } + + unsigned short GetFlags() const { + return mFlags; + } + + const Attrib::Collection *GetMaterial() const { + return mMaterial; + } + private: // total size: 0x18 UMath::Vector4 *mVerts; // offset 0x8, size 0x4 diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SimpleChopper.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SimpleChopper.cpp index e69de29bb..4577fa500 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SimpleChopper.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SimpleChopper.cpp @@ -0,0 +1,313 @@ +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/chopperspecs.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/rigidbodyspecs.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IDamageable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngine.h" +#include "Speed/Indep/Src/Interfaces/Simables/IHelicopter.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Physics/Behavior.h" +#include "Speed/Indep/Src/Physics/VehicleBehaviors.h" + +// total size: 0xD8 +class SimpleChopper : protected VehicleBehavior, protected ISimpleChopper { + public: + virtual float GetPower() const; + virtual float GetRPM() const; + virtual float GetMaxRPM() const; + virtual float GetRedline() const; + virtual float GetMinRPM() const; + virtual float GetMinGearRPM(int gear) const; + virtual void MatchSpeed(float speed); + virtual float GetNOSCapacity() const; + virtual bool IsNOSEngaged() const; + virtual bool HasNOS() const; + + void SetDesiredVelocity(const UMath::Vector3 &vel) override; + void GetDesiredVelocity(UMath::Vector3 &vel) override; + void MaxDeceleration(bool t) override; + void SetDesiredFacingVector(const UMath::Vector3 &facingDir) override; + void GetDesiredFacingVector(UMath::Vector3 &facingDir) override; + + static Behavior *Construct(const BehaviorParams ¶ms); + + SimpleChopper(const BehaviorParams &bp, const EngineParams &ep); + ~SimpleChopper() override; + void Reset() override; + void OnBehaviorChange(const UCrc32 &mechanic) override; + void SetTorqueToMatchPitchAndRoll(UMath::Vector3 &localXZAccel, UMath::Vector3 &angVelOut); + void OnTaskSimulate(float dT) override; + + private: + UMath::Vector3 mLastBodyOffset; // offset 0x58, size 0xC + UMath::Vector3 mLastAngVelocity; // offset 0x64, size 0xC + UMath::Vector3 mLastAccelVector; // offset 0x70, size 0xC + UMath::Vector3 mDesiredVelocity; // offset 0x7C, size 0xC + UMath::Vector3 mPreviousVelocity; // offset 0x88, size 0xC + UMath::Vector3 mDesiredFacingVector; // offset 0x94, size 0xC + BehaviorSpecsPtr mChopperSpecs; // offset 0xA0, size 0x14 + BehaviorSpecsPtr mVehicleSpecs; // offset 0xB4, size 0x14 + bool mMaxDecelFlag; // offset 0xC8, size 0x1 + IRigidBody *mIrigidBody; // offset 0xCC, size 0x4 + ICollisionBody *mIrbComplex; // offset 0xD0, size 0x4 + IDamageable *mIdamage; // offset 0xD4, size 0x4 +}; + +float SimpleChopper::GetPower() const { + return 1.0f; +} + +float SimpleChopper::GetRPM() const { + return 1000.0f; +} + +float SimpleChopper::GetMaxRPM() const { + return 4500.0f; +} + +float SimpleChopper::GetRedline() const { + return 3500.0f; +} + +float SimpleChopper::GetMinRPM() const { + return 0.0f; +} + +float SimpleChopper::GetMinGearRPM(int gear) const { + return 0.0f; +} + +void SimpleChopper::MatchSpeed(float speed) {} + +float SimpleChopper::GetNOSCapacity() const { + return 0.0f; +} + +bool SimpleChopper::IsNOSEngaged() const { + return false; +} + +bool SimpleChopper::HasNOS() const { + return false; +} + +void SimpleChopper::SetDesiredVelocity(const UMath::Vector3 &vel) { + mDesiredVelocity.x = vel.x; + mDesiredVelocity.z = vel.z; + mDesiredVelocity.y = vel.y; +} + +void SimpleChopper::GetDesiredVelocity(UMath::Vector3 &vel) { + vel.x = mDesiredVelocity.x; + vel.z = mDesiredVelocity.z; + vel.y = mDesiredVelocity.y; +} + +void SimpleChopper::MaxDeceleration(bool t) { + mMaxDecelFlag = t; +} + +void SimpleChopper::SetDesiredFacingVector(const UMath::Vector3 &facingDir) { + mDesiredFacingVector.x = facingDir.x; + mDesiredFacingVector.z = facingDir.z; + mDesiredFacingVector.y = facingDir.y; +} + +void SimpleChopper::GetDesiredFacingVector(UMath::Vector3 &facingDir) { + facingDir.x = mDesiredFacingVector.x; + facingDir.z = mDesiredFacingVector.z; + facingDir.y = mDesiredFacingVector.y; +} + +Behavior *SimpleChopper::Construct(const BehaviorParams ¶ms) { + const EngineParams ep(params.fparams.Fetch(UCrc32(0xa6b47fac))); + return new SimpleChopper(params, ep); +} + +SimpleChopper::SimpleChopper(const BehaviorParams &bp, const EngineParams &ep) + : VehicleBehavior(bp, 0), // + ISimpleChopper(bp.fowner), // + mChopperSpecs(this, 0), // + mVehicleSpecs(this, 0) { + (void)ep; + + mIrigidBody = nullptr; + mIrbComplex = nullptr; + mIdamage = nullptr; + *reinterpret_cast(reinterpret_cast(this) + 0x48) = 0; + + GetOwner()->QueryInterface(&mIrigidBody); + GetOwner()->QueryInterface(&mIrbComplex); + GetOwner()->QueryInterface(&mIdamage); + + *reinterpret_cast(&mDesiredFacingVector.z) = 0; + UMath::Clear(mLastBodyOffset); + UMath::Clear(mLastAngVelocity); + UMath::Clear(mLastAccelVector); + UMath::Clear(mDesiredVelocity); + UMath::Clear(mPreviousVelocity); + *reinterpret_cast(&mDesiredFacingVector.x) = 0; + *reinterpret_cast(&mDesiredFacingVector.y) = 0; +} + +void SimpleChopper::Reset() {} + +void SimpleChopper::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == BEHAVIOR_MECHANIC_RIGIDBODY) { + GetOwner()->QueryInterface(&mIrigidBody); + GetOwner()->QueryInterface(&mIrbComplex); + } + if (mechanic == BEHAVIOR_MECHANIC_DAMAGE) { + GetOwner()->QueryInterface(&mIdamage); + } +} + +SimpleChopper::~SimpleChopper() {} + +extern float Max_Chopper_Accel; +extern float Min_Chopper_Accel; +extern float Chopper_Ratio; + +void SimpleChopper::SetTorqueToMatchPitchAndRoll(UMath::Vector3 &localXZAccel, UMath::Vector3 &angVelOut) { + float adjustedForwardAccel; + float pitchAng; + UMath::Vector3 idealForwardV; + UMath::Vector3 actualF; + UMath::Vector3 totalTorque; + UMath::Vector3 angVel; + float adjustedSideAccel; + float rollAng; + UMath::Vector3 rightVec; + UMath::Vector3 idealRightV; + + mIrigidBody->ConvertWorldToLocal(localXZAccel, false); + + UMath::Scale(mLastAccelVector, 4.0f, mLastAccelVector); + UMath::AddScale(localXZAccel, mLastAccelVector, 0.2f, localXZAccel); + + adjustedForwardAccel = -(localXZAccel.z * (9.0f / Max_Chopper_Accel)) * mChopperSpecs->PITCH_ANG(); + if (0.0f < localXZAccel.y && 15.0f < mIrigidBody->GetSpeed() && 0.0f < mIrigidBody->GetLinearVelocity().y) { + adjustedForwardAccel += localXZAccel.y * 0.035f; + } + + pitchAng = UMath::Clamp(adjustedForwardAccel, -0.1f, 0.1f); + mIrigidBody->GetForwardVector(idealForwardV); + actualF = idealForwardV; + idealForwardV.y = UMath::Sina(pitchAng); + UMath::Unit(idealForwardV, idealForwardV); + UMath::Cross(idealForwardV, actualF, totalTorque); + + angVel = mIrigidBody->GetAngularVelocity(); + mIrigidBody->ConvertWorldToLocal(angVel, false); + angVel.y = 0.0f; + angVel.z = 0.0f; + mIrigidBody->ConvertWorldToLocal(totalTorque, false); + angVel.x = UMath::Clamp(-totalTorque.x * mChopperSpecs->PITCH_ALIGN_SCALE(), -1.0f, 1.0f); + + adjustedSideAccel = -(localXZAccel.x * (12.0f / Max_Chopper_Accel)) * mChopperSpecs->ROLL_ANG(); + rollAng = UMath::Clamp(adjustedSideAccel, -0.1f, 0.1f); + mIrigidBody->GetRightVector(rightVec); + idealRightV = rightVec; + idealRightV.y = UMath::Sina(rollAng); + UMath::Unit(idealRightV, idealRightV); + UMath::Cross(idealRightV, rightVec, totalTorque); + mIrigidBody->ConvertWorldToLocal(totalTorque, false); + angVel.z = UMath::Clamp(-totalTorque.z * mChopperSpecs->ROLL_ALIGN_SCALE(), -1.0f, 1.0f); + + mIrigidBody->ConvertLocalToWorld(angVel, false); + angVelOut = angVel; + mIrigidBody->ResolveTorque(UMath::Vector3::kZero); +} + +void SimpleChopper::OnTaskSimulate(float dT) { + UMath::Vector3 v; + float desiredSpeed; + float maxSpeed; + float currentLinearSpeed; + float maxAccel; + UMath::Vector3 deltaVel; + UMath::Vector3 accelVector; + float fAccel; + UMath::Vector3 totalForce; + float gravity; + float RotorUp; + UMath::Vector3 workingAngVel; + UMath::Vector3 idealForwardV; + UMath::Vector3 actualF; + UMath::Vector3 totalTorque; + UMath::Vector3 angVel; + + mIrigidBody->ModifyXPos(-mLastBodyOffset.x); + mIrigidBody->ModifyYPos(-mLastBodyOffset.y); + mIrigidBody->ModifyZPos(-mLastBodyOffset.z); + + UMath::Clear(v); + mIrigidBody->GetDimension(v); + v.x = 0.0f; + v.y = -v.y; + v.z = 0.0f; + UMath::Rotate(v, mIrbComplex->GetMatrix4(), mLastBodyOffset); + + mIrigidBody->ModifyXPos(mLastBodyOffset.x); + mIrigidBody->ModifyYPos(mLastBodyOffset.y); + mIrigidBody->ModifyZPos(mLastBodyOffset.z); + + desiredSpeed = UMath::Length(mDesiredVelocity); + maxSpeed = mChopperSpecs->MAX_SPEED_MPS(); + if (desiredSpeed > maxSpeed) { + UMath::Scale(mDesiredVelocity, maxSpeed / desiredSpeed, mDesiredVelocity); + } + + currentLinearSpeed = mIrigidBody->GetSpeed(); + maxAccel = currentLinearSpeed * 0.6f + Min_Chopper_Accel; + if (maxAccel > Max_Chopper_Accel) { + maxAccel = Max_Chopper_Accel; + } + if (mMaxDecelFlag) { + maxAccel = Max_Chopper_Accel; + } + + UMath::Sub(mDesiredVelocity, mIrigidBody->GetLinearVelocity(), deltaVel); + fAccel = UMath::Length(deltaVel) * Chopper_Ratio; + if (fAccel > 1.0f && (fAccel > maxAccel || mMaxDecelFlag)) { + UMath::Scale(deltaVel, maxAccel / fAccel, accelVector); + } else { + UMath::Scale(deltaVel, Chopper_Ratio, accelVector); + } + + UMath::Scale(accelVector, mIrigidBody->GetMass(), totalForce); + Attrib::Gen::pvehicle vehicleSpecs(*mVehicleSpecs); + Attrib::Gen::rigidbodyspecs rigidBodySpecs(vehicleSpecs.rigidbodyspecs(), 0, nullptr); + gravity = rigidBodySpecs.GRAVITY(); + totalForce.y -= mIrigidBody->GetMass() * gravity; + mIrigidBody->ResolveForce(totalForce); + + workingAngVel = UMath::Vector3::kZero; + if (dT > 0.005f) { + UMath::Vector3 calculatedAccel; + + UMath::Sub(mIrigidBody->GetLinearVelocity(), mPreviousVelocity, calculatedAccel); + UMath::Scale(calculatedAccel, 1.0f / dT, calculatedAccel); + SetTorqueToMatchPitchAndRoll(calculatedAccel, workingAngVel); + } + + idealForwardV = mDesiredFacingVector; + mIrigidBody->GetForwardVector(actualF); + idealForwardV.y = 0.0f; + actualF.y = 0.0f; + UMath::Unit(idealForwardV, idealForwardV); + UMath::Cross(idealForwardV, actualF, totalTorque); + + angVel.x = workingAngVel.x; + angVel.z = workingAngVel.z; + angVel.y = UMath::Clamp(-totalTorque.y * 8.0f, -1.3f, 1.3f); + + UMath::Scale(mLastAngVelocity, 7.0f, mLastAngVelocity); + UMath::AddScale(angVel, mLastAngVelocity, 1.0f, angVel); + UMath::Scale(angVel, 0.125f, angVel); + + mIrigidBody->SetAngularVelocity(angVel); + mLastAngVelocity = angVel; + mPreviousVelocity = mIrigidBody->GetLinearVelocity(); +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.cpp index 703bac431..0f06e48ba 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.cpp @@ -1,13 +1,45 @@ #include "SimpleRigidBody.h" +#include "RigidBody.h" #include "Speed/Indep/Libs/Support/Utility/UMath.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" +#include "Speed/Indep/Libs/Support/Utility/UVector.h" #include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" #include "Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h" #include "Speed/Indep/Src/Main/ScratchPtr.h" #include "Speed/Indep/Src/Physics/Behavior.h" +#include "Speed/Indep/Src/Physics/Dynamics/Collision.h" #include "Speed/Indep/Src/Physics/PhysicsObject.h" +#include "Speed/Indep/Src/Sim/OBB.h" #include "Speed/Indep/Src/Sim/Simulation.h" +namespace { + +struct CollisionMapAccess { + unsigned long long fBitMap[3]; +}; + +static void SetCollisionMapBit(SimCollisionMap &map, unsigned int index) { + CollisionMapAccess &access = reinterpret_cast(map); + access.fBitMap[index / 64] |= 1ULL << (index % 64); +} + +} // namespace + +ISimpleBody::~ISimpleBody() {} + +template <> +void *ScratchPtr::mWorkSpace = nullptr; + +template <> +SimpleRigidBody::Volatile *ScratchPtr::mPointer[64] = { nullptr }; + +template <> +SimpleRigidBody::Volatile ScratchPtr::mRAMBuffer[64]; + +template <> ScratchPtr::~ScratchPtr() { + *mRef = nullptr; +} + SimpleRigidBody::SimpleRigidBody(const BehaviorParams &bp, const RBSimpleParams ¶ms) : Behavior(bp, 0), IRigidBody(bp.fowner), ISimpleBody(bp.fowner) { TheSimpleBodies.AddTail(this); @@ -156,13 +188,166 @@ void SimpleRigidBody::ResolveForce(const UMath::Vector3 &force, const UMath::Vec void SimpleRigidBody::ResolveTorque(const UMath::Vector3 &torque) {} -// TODO unsigned int SimpleRigidBody::GetTriggerFlags() const { - return 0; + if (!CanHitTrigger()) { + return 0; + } + + ISimable *owner = GetOwner(); + unsigned int flags = 0x40; + + if (owner->GetSimableType() == SIMABLE_EXPLOSION) { + flags = 0x50; + } + + if (owner->GetSimableType() == SIMABLE_HUMAN) { + flags |= 0x10000; + if (!owner->IsPlayer()) { + flags |= 8; + } + } + + if (owner->IsPlayer()) { + flags |= 4; + } + + return flags; +} + +void SimpleRigidBody::RecalcOrientMat(UMath::Matrix4 &resultMat4) const { + UMath::QuaternionToMatrix4(mData->orientation, resultMat4); +} + +void SimpleRigidBody::DoRBCollisions(const float dT) { + IRigidBody *thisBody = this; + SimCollisionMap &cmap = mCollisionMap[thisBody->GetIndex()]; + Volatile &data = *mData; + Dynamics::Collision::Geometry geomSRB; + const float thisRadius = UMath::Max(thisBody->GetRadius(), 0.25f); + UVector3 dimSRB(thisRadius, thisRadius, thisRadius); + + if (data.flags & 0x8000) { + geomSRB.Set(UMath::Matrix4::kIdentity, thisBody->GetPosition(), dimSRB, Dynamics::Collision::Geometry::SPHERE, + UVector3(thisBody->GetLinearVelocity()) * dT); + } else if (data.flags & 0x40) { + UMath::Matrix4 orientSimple; + UMath::QuaternionToMatrix4(data.orientation, orientSimple); + geomSRB.Set(orientSimple, thisBody->GetPosition(), dimSRB, Dynamics::Collision::Geometry::BOX, + UVector3(thisBody->GetLinearVelocity()) * dT); + } + + for (RigidBody *body = TheRigidBodies.GetHead(); body != TheRigidBodies.EndOfList(); body = body->GetNext()) { + if (!body->CanCollideWithObjects()) { + continue; + } + + const bool useObb = (data.flags & 0x8040) != 0; + float vdist = 0.0f; + if (useObb) { + vdist = UMath::Distance(thisBody->GetLinearVelocity(), body->GetLinearVelocity()) * dT; + } + + const float distSquared = UMath::DistanceSquare(thisBody->GetPosition(), body->GetPosition()); + const float testRadius = thisRadius + body->GetRadius() + vdist; + if (distSquared >= testRadius * testRadius) { + continue; + } + + if (useObb) { + UMath::Vector3 bodyDimension; + body->GetDimension(bodyDimension); + Dynamics::Collision::Geometry geomRB(body->GetMatrix4(), body->GetPosition(), bodyDimension, Dynamics::Collision::Geometry::BOX, + UVector3(body->GetLinearVelocity()) * dT); + if (!Dynamics::Collision::Geometry::FindIntersection(&geomRB, &geomSRB, &geomSRB)) { + continue; + } + } + + SetCollisionMapBit(cmap, body->GetIndex()); + } +} + +void SimpleRigidBody::DoSRBCollisions(SimpleRigidBody *other) { + Volatile &data = *mData; + Volatile &otherData = *other->mData; + + if ((data.flags | otherData.flags) & 0x4000) { + ISimable *ownerOther = other->GetOwner(); + ISimable *ownerThis = GetOwner(); + if (ownerOther == ownerThis) { + return; + } + } + + IRigidBody &thisBody = *this; + IRigidBody &otherBody = *other; + SimCollisionMap &cmap = mCollisionMap[thisBody.GetIndex()]; + SimCollisionMap &cmapother = mCollisionMap[otherBody.GetIndex()]; + UMath::Vector3 posSRB = otherBody.GetPosition(); + const float radiusSRB = UMath::Max(otherBody.GetRadius(), 0.25f); + const float thisRadius = UMath::Max(thisBody.GetRadius(), 0.25f); + UMath::Vector3 vec; + UMath::Sub(thisBody.GetPosition(), posSRB, vec); + const float distSquared = UMath::LengthSquare(vec); + const float testRadius = thisRadius + radiusSRB; + if (distSquared >= testRadius * testRadius) { + return; + } + + if (((data.flags & 0x80) == 0) || ((otherData.flags & 0x80) == 0)) { + SetCollisionMapBit(cmap, RIGID_BODY_MAX + otherBody.GetIndex()); + SetCollisionMapBit(cmapother, RIGID_BODY_MAX + thisBody.GetIndex()); + return; + } + + OBB obbThis; + OBB obbOther; + UMath::Matrix4 orientThis; + UMath::Matrix4 orientOther; + UMath::Vector3 dimThis; + UMath::Vector3 dimOther; + UMath::QuaternionToMatrix4(thisBody.GetOrientation(), orientThis); + UMath::QuaternionToMatrix4(otherBody.GetOrientation(), orientOther); + dimThis.x = thisRadius; + dimThis.y = thisRadius; + dimThis.z = thisRadius * GetScalarVelocity(); + dimOther.x = radiusSRB; + dimOther.y = radiusSRB; + dimOther.z = radiusSRB * other->GetScalarVelocity(); + obbThis.Reset(orientThis, thisBody.GetPosition(), dimThis); + obbOther.Reset(orientOther, posSRB, dimOther); + if (obbThis.CheckOBBOverlap(&obbOther)) { + SetCollisionMapBit(cmap, RIGID_BODY_MAX + otherBody.GetIndex()); + SetCollisionMapBit(cmapother, RIGID_BODY_MAX + thisBody.GetIndex()); + } } void SimpleRigidBody::Update(const float dT, void *workspace) { - // TODO really ugly output + ScratchPtr::Push(workspace); + + for (SimpleRigidBody *body = TheSimpleBodies.GetHead(); body != TheSimpleBodies.EndOfList(); body = body->GetNext()) { + body->DoIntegration(dT); + } + + for (unsigned int i = 0; i < SIMPLE_RIGID_BODY_MAX; ++i) { + mCollisionMap[i].Clear(); + } + + for (SimpleRigidBody *body = TheSimpleBodies.GetHead(); body != TheSimpleBodies.EndOfList(); body = body->GetNext()) { + if (body->CanCollideWithRB()) { + body->DoRBCollisions(dT); + } + + if (body->CanCollideWithSRB()) { + for (SimpleRigidBody *other = body->GetNext(); other != TheSimpleBodies.EndOfList(); other = other->GetNext()) { + if (other->CanCollideWithSRB()) { + body->DoSRBCollisions(other); + } + } + } + } + + ScratchPtr::Pop(); } IRigidBody *SimpleRigidBody::Get(unsigned int index) { diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.h b/src/Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.h index 757a1364a..0b36b8fee 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.h +++ b/src/Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.h @@ -13,33 +13,10 @@ #include "Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h" #include "Speed/Indep/Src/Main/ScratchPtr.h" #include "Speed/Indep/Src/Physics/Behavior.h" +#include "Speed/Indep/Src/Physics/VehicleBehaviors.h" #define SIMPLE_RIGID_BODY_MAX (96) -// total size: 0x28 -struct RBSimpleParams : public Sim::Param { - RBSimpleParams(const RBSimpleParams &_ctor_arg) - : Sim::Param(_ctor_arg), finitPos(_ctor_arg.finitPos), finitVel(_ctor_arg.finitVel), finitAngVel(_ctor_arg.finitAngVel), - finitMat(_ctor_arg.finitMat), finitRadius(_ctor_arg.finitRadius), finitMass(_ctor_arg.finitMass) {} - - RBSimpleParams(const UMath::Vector3 &initPos, const UMath::Vector3 &initVel, const UMath::Vector3 &initAngVel, const UMath::Matrix4 &initMat, - float initRadius, float initMass) - : Sim::Param(TypeName(), static_cast(nullptr)), finitPos(initPos), finitVel(initVel), finitAngVel(initAngVel), - finitMat(initMat), finitRadius(initRadius), finitMass(initMass) {} - - static UCrc32 TypeName() { - static UCrc32 value = "RBSimpleParams"; - return value; - } - - const UMath::Vector3 &finitPos; // offset 0x10, size 0x4 - const UMath::Vector3 &finitVel; // offset 0x14, size 0x4 - const UMath::Vector3 &finitAngVel; // offset 0x18, size 0x4 - const UMath::Matrix4 &finitMat; // offset 0x1C, size 0x4 - float finitRadius; // offset 0x20, size 0x4 - float finitMass; // offset 0x24, size 0x4 -}; - // total size: 0x74 class SimpleRigidBody : public Behavior, public IRigidBody, public ISimpleBody, public bTNode, public Debugable { public: @@ -69,6 +46,8 @@ class SimpleRigidBody : public Behavior, public IRigidBody, public ISimpleBody, float GetScalarVelocity() const; void ApplyFriction(); void DoIntegration(const float dT); + void DoRBCollisions(const float dT); + void DoSRBCollisions(SimpleRigidBody *other); void RecalcOrientMat(UMath::Matrix4 &resultMat4) const; // Virtual methods diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SoundCar.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SoundCar.cpp index e69de29bb..6b106a3ea 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SoundCar.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SoundCar.cpp @@ -0,0 +1,572 @@ +#include "Speed/Indep/Src/EAXSound/SoundConn.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/engine.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAudible.h" +#include "Speed/Indep/Src/Interfaces/Simables/IArticulatedVehicle.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICarAudio.h" +#include "Speed/Indep/Src/Interfaces/Simables/IDamageable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngine.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEngineDamage.h" +#include "Speed/Indep/Src/Interfaces/Simables/IInput.h" +#include "Speed/Indep/Src/Interfaces/Simables/INISCarControl.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISpikeable.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISuspension.h" +#include "Speed/Indep/Src/Interfaces/Simables/ITransmission.h" +#include "Speed/Indep/Src/Physics/VehicleBehaviors.h" +#include "Speed/Indep/Src/Sim/SimServer.h" +#include "Speed/Indep/Src/Sim/SimSurface.h" + +namespace Sound { + +enum Context { + CONTEXT_PLAYER = 0, + CONTEXT_AIRACER = 1, + CONTEXT_COP = 2, + CONTEXT_TRAFFIC = 3, + CONTEXT_ONLINE = 4, + CONTEXT_TRACTOR = 5, + CONTEXT_TRAILER = 6, + CONTEXT_MAX = 7, +}; + +enum WheelConfig { + EAX4WD_FL = 0, + EAX4WD_FR = 1, + EAX4WD_RR = 2, + EAX4WD_RL = 3, +}; + +}; // namespace Sound + +HINTERFACE ICarAudio::_IHandle() { + return (HINTERFACE)_IHandle; +} + +enum ControlSource { + CONTROL_NONE = 0, + CONTROL_HUMAN = 1, + CONTROL_AI = 2, + CONTROL_NIS = 3, + CONTROL_ONLINE = 4, +}; + +extern "C" UCrc32 VehicleClass_TRAILER asm("_12VehicleClass.TRAILER"); +extern "C" UCrc32 VehicleClass_TRACTOR asm("_12VehicleClass.TRACTOR"); + +namespace SoundConn { + +// total size: 0x18 +class Pkt_Car_Open : public Sim::Packet { + public: + Pkt_Car_Open(const Attrib::Collection *spec, WUID worldid, Sound::Context ctx, bool spool_load, HSIMABLE handle) + : m_VehicleSpec(spec), // + mWorldID(worldid), // + mCarContext(ctx), // + mSpoolLoad(spool_load), // + mHandle(handle) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash = "CarSoundConn"; + return hash; + } + + unsigned int Size() override { + return sizeof(*this); + } + + unsigned int Type() override { + return SType(); + } + + static unsigned int SType() { + static UCrc32 hash = "Pkt_Car_Open"; + return hash.GetValue(); + } + + private: + const Attrib::Collection *m_VehicleSpec; // offset 0x4, size 0x4 + WUID mWorldID; // offset 0x8, size 0x4 + Sound::Context mCarContext; // offset 0xC, size 0x4 + bool mSpoolLoad; // offset 0x10, size 0x1 + HSIMABLE mHandle; // offset 0x14, size 0x4 +}; + +// total size: 0x108 +class Pkt_Car_Service : public Sim::Packet { + public: + void SetSlipAngle(float sa) { + mSlipAngle = sa; + } + + void SetTraction(Sound::WheelConfig id, float pct) { + mTractionPct[id] = pct; + } + + void SetWheelTerrain(int idx, SimSurface s, bool onground) { + mWheelTerrain[idx] = s; + mWheelOnGround[idx] = onground; + } + + void SetOversteer(float factor) { + mOversteer = factor; + } + + void SetUndersteer(float factor) { + mUndersteer = factor; + } + + void SetWheelSlip(int idx, float fwd, float lateral) { + mWheelSlip[idx].x = fwd; + mWheelSlip[idx].y = lateral; + } + + void SetWheelLoad(Sound::WheelConfig id, float load) { + mWheelLoad[id] = load; + } + + void SetWheelZForce(Sound::WheelConfig id, float load) { + mWheelCompression[id] = load; + } + + void SetSiren(SirenState s) { + mSirenState = s; + } + + void SetTimeSinceSeen(float seconds) { + mTimeSinceSeen = seconds; + } + + void SetHotPursuit(bool h) { + mHotPursuit = h; + } + + void SetEngineBlown(int b) { + mEngineBlown = b; + } + + void SetTrailer(WUID trailer) { + mTrailer = trailer; + } + + void SetTireBlown(Sound::WheelConfig id, eTireDamage state) { + mBlownTires[id] = state; + } + + float mRPMPercent; // offset 0x4, size 0x4 + float mThrottlePercent; // offset 0x8, size 0x4 + float mBrakePercent; // offset 0xC, size 0x4 + float mEBrakePercent; // offset 0x10, size 0x4 + float mSteering; // offset 0x14, size 0x4 + int mGear; // offset 0x18, size 0x4 + float mTractionPct[4]; // offset 0x1C, size 0x10 + SirenState mSirenState; // offset 0x2C, size 0x4 + bool mHotPursuit; // offset 0x30, size 0x1 + float mOversteer; // offset 0x34, size 0x4 + float mUndersteer; // offset 0x38, size 0x4 + float mSlipAngle; // offset 0x3C, size 0x4 + float mHealth; // offset 0x40, size 0x4 + SimSurface mWheelTerrain[4]; // offset 0x44, size 0x50 + float mAudibleRPMPct; // offset 0x94, size 0x4 + bVector2 mWheelSlip[4]; // offset 0x98, size 0x20 + float mWheelLoad[4]; // offset 0xB8, size 0x10 + float mWheelCompression[4]; // offset 0xC8, size 0x10 + bool mWheelOnGround[4]; // offset 0xD8, size 0x4 + int mEngineBlown; // offset 0xE8, size 0x4 + bool mNOSFlag; // offset 0xEC, size 0x1 + float mNOSCapacity; // offset 0xF0, size 0x4 + WUID mTrailer; // offset 0xF4, size 0x4 + unsigned char mBlownTires[4]; // offset 0xF8, size 0x4 + float mTimeSinceSeen; // offset 0xFC, size 0x4 + float mDesiredSpeed; // offset 0x100, size 0x4 + ControlSource mControlSource; // offset 0x104, size 0x4 +}; + +}; // namespace SoundConn + +// total size: 0x9C +class SoundCar : public VehicleBehavior, public ICarAudio, public IAudible { + public: + SoundCar(const BehaviorParams ¶ms, Sound::Context ctx); + ~SoundCar() override; + + void Reset() override; + void OnTaskSimulate(float dT) override; + bool IsAudible() const override; + Rpm GetRPM() const override; + void OnBehaviorChange(const UCrc32 &mechanic) override; + bool OnService(HSIMSERVICE hCon, Sim::Packet *pkt) override; + + virtual void OnService(SoundConn::Pkt_Car_Service &svc); + virtual void OnServiceTire(SoundConn::Pkt_Car_Service &pkt, unsigned int wheelid, Sound::WheelConfig sndId); + + private: + HSIMSERVICE mSoundService; // offset 0x60, size 0x4 + IEngine *mEngine; // offset 0x64, size 0x4 + IInput *mInput; // offset 0x68, size 0x4 + ITransmission *mTransmission; // offset 0x6C, size 0x4 + ISuspension *mSuspension; // offset 0x70, size 0x4 + IVehicleAI *mAI; // offset 0x74, size 0x4 + IHumanAI *mHumanAI; // offset 0x78, size 0x4 + IDamageable *mDamage; // offset 0x7C, size 0x4 + Rpm mSoundRPM; // offset 0x80, size 0x4 + INISCarControl *mNIS; // offset 0x84, size 0x4 + BehaviorSpecsPtr mEngineInfo; // offset 0x88, size 0x14 +}; + +// total size: 0xA0 +class SoundTraffic : public SoundCar { + public: + SoundTraffic(const BehaviorParams ¶ms, Sound::Context ctx); + ~SoundTraffic() override {} + + static Behavior *Construct(const BehaviorParams ¶ms); + + void LocateTrailer(); + void OnBehaviorChange(const UCrc32 &mechanic) override; + void OnService(SoundConn::Pkt_Car_Service &svc) override; + + private: + WUID mTrailer; // offset 0x9C, size 0x4 +}; + +// total size: 0xA4 +class SoundCop : public SoundCar { + public: + SoundCop(const BehaviorParams ¶ms); + ~SoundCop() override {} + + static Behavior *Construct(const BehaviorParams ¶ms); + + void OnBehaviorChange(const UCrc32 &mechanic) override; + void OnService(SoundConn::Pkt_Car_Service &svc) override; + + private: + IPursuitAI *mPursuitAI; // offset 0x9C, size 0x4 + IDamageableVehicle *mVehicleDamage; // offset 0xA0, size 0x4 +}; + +// total size: 0xA4 +class SoundRacer : public SoundCar { + public: + SoundRacer(const BehaviorParams ¶ms, Sound::Context ctx); + ~SoundRacer() override {} + + static Behavior *Construct(const BehaviorParams ¶ms); + + void OnServiceTire(SoundConn::Pkt_Car_Service &pkt, unsigned int wheelid, Sound::WheelConfig sndId) override; + void OnService(SoundConn::Pkt_Car_Service &svc) override; + void OnBehaviorChange(const UCrc32 &mechanic) override; + + private: + IEngineDamage *mEngineDamage; // offset 0x9C, size 0x4 + ISpikeable *mSpikeDamage; // offset 0xA0, size 0x4 +}; + +SoundCar::SoundCar(const BehaviorParams ¶ms, Sound::Context ctx) + : VehicleBehavior(params, 0), // + ICarAudio(params.fowner), // + IAudible(params.fowner), // + mSoundService(nullptr), // + mSoundRPM(0.0f), // + mEngineInfo(GetOwner(), 0) { + GetOwner()->QueryInterface(&mEngine); + GetOwner()->QueryInterface(&mInput); + GetOwner()->QueryInterface(&mTransmission); + GetOwner()->QueryInterface(&mSuspension); + GetOwner()->QueryInterface(&mDamage); + GetOwner()->QueryInterface(&mAI); + GetOwner()->QueryInterface(&mHumanAI); + GetOwner()->QueryInterface(&mNIS); + + mSoundRPM = mEngineInfo->IDLE(); + + SoundConn::Pkt_Car_Open pkt(GetOwner()->GetAttributes().GetConstCollection(), + GetOwner()->GetWorldID(), + ctx, + GetVehicle()->IsSpooled(), + GetOwner()->GetInstanceHandle()); + mSoundService = OpenService(UCrc32(0xa3f44e2e), &pkt); +} + +SoundCar::~SoundCar() { + if (mSoundService) { + CloseService(mSoundService); + } +} + +void SoundCar::Reset() {} + +void SoundCar::OnTaskSimulate(float dT) {} + +bool SoundCar::IsAudible() const { + return CheckService(mSoundService) == Sim::CONNSTATUS_READY; +} + +Rpm SoundCar::GetRPM() const { + return mSoundRPM; +} + +void SoundCar::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == BEHAVIOR_MECHANIC_ENGINE) { + GetOwner()->QueryInterface(&mEngine); + GetOwner()->QueryInterface(&mTransmission); + } else if (mechanic == BEHAVIOR_MECHANIC_INPUT) { + GetOwner()->QueryInterface(&mInput); + } else if (mechanic == BEHAVIOR_MECHANIC_SUSPENSION) { + GetOwner()->QueryInterface(&mSuspension); + GetOwner()->QueryInterface(&mNIS); + } else if (mechanic == BEHAVIOR_MECHANIC_DAMAGE) { + GetOwner()->QueryInterface(&mDamage); + } else if (mechanic == BEHAVIOR_MECHANIC_AI) { + GetOwner()->QueryInterface(&mAI); + GetOwner()->QueryInterface(&mHumanAI); + } +} + +bool SoundCar::OnService(HSIMSERVICE hCon, Sim::Packet *pkt) { + if (!INIS::Exists() && hCon == mSoundService && !IsPaused()) { + OnService(*static_cast(pkt)); + return true; + } + return false; +} + +void SoundCar::OnService(SoundConn::Pkt_Car_Service &svc) { + if (mEngine && mTransmission) { + float rpm = mEngine->GetRPM(); + float idle = mEngineInfo->IDLE(); + float redLineRange = mEngineInfo->RED_LINE() - idle; + float rpmPercent = 0.0f; + + if (redLineRange > 0.0f) { + rpmPercent = UMath::Min((rpm - idle) / redLineRange, 1.0f); + rpmPercent = UMath::Max(0.0f, rpmPercent); + } + + mSoundRPM = svc.mAudibleRPMPct * redLineRange + idle; + svc.mRPMPercent = rpmPercent; + svc.mNOSFlag = mEngine->IsNOSEngaged(); + svc.mNOSCapacity = mEngine->GetNOSCapacity(); + svc.mGear = static_cast(mTransmission->GetGear()); + } + + if (mInput) { + float throttle = UMath::Min(mInput->GetControls().fGas, 1.0f); + throttle = UMath::Max(0.0f, throttle); + + float brake = UMath::Min(mInput->GetControls().fBrake, 1.0f); + brake = UMath::Max(0.0f, brake); + + float eBrake = UMath::Min(mInput->GetControls().fHandBrake, 1.0f); + eBrake = UMath::Max(0.0f, eBrake); + + float steering = UMath::Min(mInput->GetControls().fSteering, 1.0f); + steering = UMath::Max(-1.0f, steering); + + svc.mThrottlePercent = throttle; + svc.mBrakePercent = brake; + svc.mEBrakePercent = eBrake; + svc.mSteering = steering; + + if (mNIS) { + svc.mControlSource = CONTROL_NIS; + } else if (GetVehicle()->GetDriverClass() == DRIVER_REMOTE) { + svc.mControlSource = CONTROL_ONLINE; + } else if (mHumanAI && !mHumanAI->GetAiControl()) { + svc.mControlSource = CONTROL_HUMAN; + } else if (mAI) { + svc.mControlSource = CONTROL_AI; + svc.mDesiredSpeed = mAI->GetDriveSpeed(); + } + } + + if (mDamage) { + if (mDamage->IsDestroyed()) { + svc.mHealth = 0.0f; + } else { + svc.mHealth = mDamage->GetHealth(); + } + } + + svc.SetSlipAngle(GetVehicle()->GetSlipAngle() * 6.2831855f); + + if (mSuspension) { + for (unsigned int wheelid = 0; wheelid < mSuspension->GetNumWheels(); ++wheelid) { + Sound::WheelConfig packetWheel; + const UMath::Vector3 &vel = mSuspension->GetWheelVelocity(wheelid); + float pos_x = mSuspension->GetWheelLocalPos(wheelid).x; + float pct = mSuspension->GetWheelTraction(wheelid); + float slip; + float toleratedSlip; + + if (wheelid < 2) { + packetWheel = Sound::EAX4WD_FL; + if ((wheelid & 1) != 0) { + packetWheel = Sound::EAX4WD_FR; + } + } else if ((wheelid & 1) != 0) { + packetWheel = Sound::EAX4WD_RR; + } else { + packetWheel = Sound::EAX4WD_RL; + } + + slip = mSuspension->GetWheelSlip(wheelid); + toleratedSlip = mSuspension->GetToleratedSlip(wheelid) * 0.2f; + if (slip > toleratedSlip) { + slip -= toleratedSlip; + } else if (slip < -toleratedSlip) { + slip += toleratedSlip; + } else { + slip = 0.0f; + } + + SimSurface roadsurf = mSuspension->GetWheelRoadSurface(wheelid); + svc.SetWheelTerrain(packetWheel, roadsurf, mSuspension->IsWheelOnGround(wheelid)); + svc.SetTraction(packetWheel, pct); + svc.SetOversteer(mSuspension->CalculateOversteerFactor()); + svc.SetUndersteer(mSuspension->CalculateUndersteerFactor()); + svc.SetWheelSlip(packetWheel, slip, -mSuspension->GetWheelSkid(wheelid)); + svc.SetWheelLoad(packetWheel, mSuspension->GetWheelLoad(wheelid)); + svc.SetWheelZForce(packetWheel, mSuspension->GetCompression(wheelid)); + + OnServiceTire(svc, wheelid, packetWheel); + } + } +} + +void SoundCar::OnServiceTire(SoundConn::Pkt_Car_Service &pkt, unsigned int wheelid, Sound::WheelConfig sndId) {} + +SoundTraffic::SoundTraffic(const BehaviorParams ¶ms, Sound::Context ctx) + : SoundCar(params, ctx), // + mTrailer(0) { + LocateTrailer(); +} + +Behavior *SoundTraffic::Construct(const BehaviorParams ¶ms) { + IVehicle *vehicle = nullptr; + static_cast(params.fowner)->QueryInterface(&vehicle); + bool haveVehicle = vehicle; + if (!haveVehicle) { + return nullptr; + } + + UCrc32 vehicleClass = vehicle->GetVehicleClass(); + Sound::Context ctx = Sound::CONTEXT_TRAFFIC; + if (vehicleClass == VehicleClass_TRACTOR) { + ctx = Sound::CONTEXT_TRACTOR; + } else if (vehicleClass == VehicleClass_TRAILER) { + ctx = Sound::CONTEXT_TRAILER; + } + + return new SoundTraffic(params, ctx); +} + +void SoundTraffic::LocateTrailer() { + IArticulatedVehicle *iarticulation = nullptr; + + mTrailer = 0; + bool haveArticulation = GetOwner()->QueryInterface(&iarticulation); + if (haveArticulation) { + IVehicle *trailer = iarticulation->GetTrailer(); + if (trailer) { + mTrailer = trailer->GetSimable()->GetWorldID(); + } + } +} + +void SoundTraffic::OnBehaviorChange(const UCrc32 &mechanic) { + SoundCar::OnBehaviorChange(mechanic); + if (mechanic == BEHAVIOR_MECHANIC_RIGIDBODY) { + LocateTrailer(); + } +} + +void SoundTraffic::OnService(SoundConn::Pkt_Car_Service &svc) { + SoundCar::OnService(svc); + svc.SetTrailer(mTrailer); +} + +SoundCop::SoundCop(const BehaviorParams ¶ms) + : SoundCar(params, Sound::CONTEXT_COP) { + GetOwner()->QueryInterface(&mPursuitAI); + GetOwner()->QueryInterface(&mVehicleDamage); +} + +Behavior *SoundCop::Construct(const BehaviorParams ¶ms) { + return new SoundCop(params); +} + +void SoundCop::OnBehaviorChange(const UCrc32 &mechanic) { + SoundCar::OnBehaviorChange(mechanic); + if (mechanic == BEHAVIOR_MECHANIC_AI) { + GetOwner()->QueryInterface(&mPursuitAI); + } else if (mechanic == BEHAVIOR_MECHANIC_DAMAGE) { + GetOwner()->QueryInterface(&mVehicleDamage); + } +} + +void SoundCop::OnService(SoundConn::Pkt_Car_Service &svc) { + SoundCar::OnService(svc); + + if (mPursuitAI) { + svc.SetSiren(mPursuitAI->GetSirenState()); + if (mPursuitAI->GetInFormation()) { + svc.SetHotPursuit(true); + } + svc.SetTimeSinceSeen(mPursuitAI->GetTimeSinceTargetSeen()); + } + + if (mVehicleDamage && mVehicleDamage->IsLightDamaged(VehicleFX::LIGHT_COPS)) { + svc.SetSiren(SIREN_DIE); + } +} + +SoundRacer::SoundRacer(const BehaviorParams ¶ms, Sound::Context ctx) + : SoundCar(params, ctx) { + GetOwner()->QueryInterface(&mEngineDamage); + GetOwner()->QueryInterface(&mSpikeDamage); +} + +Behavior *SoundRacer::Construct(const BehaviorParams ¶ms) { + IVehicle *vehicle = nullptr; + static_cast(params.fowner)->QueryInterface(&vehicle); + bool haveVehicle = vehicle; + if (!haveVehicle) { + return nullptr; + } + + Sound::Context ctx = Sound::CONTEXT_AIRACER; + if (vehicle->GetDriverClass() == DRIVER_HUMAN) { + ctx = Sound::CONTEXT_PLAYER; + } + return new SoundRacer(params, ctx); +} + +void SoundRacer::OnServiceTire(SoundConn::Pkt_Car_Service &pkt, unsigned int wheelid, Sound::WheelConfig sndId) { + if (mSpikeDamage) { + pkt.SetTireBlown(sndId, mSpikeDamage->GetTireDamage(wheelid)); + } +} + +void SoundRacer::OnService(SoundConn::Pkt_Car_Service &svc) { + SoundCar::OnService(svc); + + if (mEngineDamage) { + if (mEngineDamage->IsBlown()) { + svc.SetEngineBlown(1); + } + if (mEngineDamage->IsSabotaged()) { + svc.SetEngineBlown(2); + } + } +} + +void SoundRacer::OnBehaviorChange(const UCrc32 &mechanic) { + SoundCar::OnBehaviorChange(mechanic); + if (mechanic == BEHAVIOR_MECHANIC_ENGINE) { + GetOwner()->QueryInterface(&mEngineDamage); + } else if (mechanic == BEHAVIOR_MECHANIC_DAMAGE) { + GetOwner()->QueryInterface(&mSpikeDamage); + } +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SoundHeli.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SoundHeli.cpp index 9ee9fdb85..e20980ff0 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SoundHeli.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SoundHeli.cpp @@ -4,12 +4,16 @@ #include "Speed/Indep/Src/Physics/VehicleBehaviors.h" #include "Speed/Indep/Src/Sim/SimServer.h" +namespace SoundConn { +class Pkt_Car_Service; +} + // total size: 0x64 class SoundHeli : public VehicleBehavior, public IAudible { static Behavior *Construct(const BehaviorParams ¶ms); SoundHeli(const BehaviorParams ¶ms); - // void OnService(Pkt_Car_Service &svc); + void OnService(SoundConn::Pkt_Car_Service &svc); // Virtual overrides // IUnknown @@ -42,3 +46,25 @@ SoundHeli::SoundHeli(const BehaviorParams ¶ms) : VehicleBehavior(params, 0), SoundConn::Pkt_Heli_Open pkt(GetOwner()->GetAttributes().GetConstCollection(), GetOwner()->GetWorldID()); mSoundService = OpenService(0xa3f44e2e, &pkt); } + +SoundHeli::~SoundHeli() { + if (mSoundService) { + CloseService(mSoundService); + } +} + +Behavior *SoundHeli::Construct(const BehaviorParams ¶ms) { + return new SoundHeli(params); +} + +void SoundHeli::OnService(SoundConn::Pkt_Car_Service &svc) {} + +void SoundHeli::OnBehaviorChange(const struct UCrc32 &mechanic) {} + +bool SoundHeli::OnService(HSIMSERVICE hCon, Sim::Packet *pkt) { + if (hCon == mSoundService && !IsPaused()) { + OnService(*reinterpret_cast(pkt)); + return true; + } + return false; +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SpikeStrip.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SpikeStrip.cpp index e69de29bb..8a5460dd4 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SpikeStrip.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SpikeStrip.cpp @@ -0,0 +1,160 @@ +#include "Speed/Indep/Src/Physics/Behaviors/SpikeStrip.h" + +#include "Speed/Indep/Src/Interfaces/SimModels/ITriggerableModel.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISpikeable.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISuspension.h" + +static inline ISimpleBody *FindSimpleBody(ISimable *owner) { + return reinterpret_cast( + (*reinterpret_cast(owner))->_mInterfaces.Find((HINTERFACE)ISimpleBody::_IHandle) + ); +} + +Behavior *SpikeStrip::Construct(const BehaviorParams ¶ms) { + return new SpikeStrip(params); +} + +SpikeStrip::SpikeStrip(const BehaviorParams ¶ms) + : Behavior(params, 0) { + bool hasbody; + + mBody = FindSimpleBody(GetOwner()); + hasbody = mBody != nullptr; + + if (hasbody) { + SetupBody(); + } +} + +SpikeStrip::~SpikeStrip() {} + +void SpikeStrip::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY)) { + bool hasbody; + + mBody = FindSimpleBody(GetOwner()); + hasbody = mBody != nullptr; + + if (hasbody) { + SetupBody(); + } + } + + Behavior::OnBehaviorChange(mechanic); +} + +void SpikeStrip::SetupBody() { + mBody->ModifyFlags(0x20B, 0x8001); +} + +void SpikeStrip::OnCollide(const Dynamics::Collision::Geometry &mygeometry, IRigidBody *irb, float dT) { + ISpikeable *ispikeable; + ISuspension *isuspension; + unsigned int num_tires; + + if (!irb->QueryInterface(&ispikeable)) { + return; + } + + if (!irb->QueryInterface(&isuspension)) { + return; + } + + num_tires = isuspension->GetNumWheels(); + + for (unsigned int i = 0; i < num_tires; i++) { + if (isuspension->IsWheelOnGround(i) && ispikeable->GetTireDamage(i) == TIRE_DAMAGE_NONE) { + UMath::Vector3 dP; + UMath::Matrix4 matrix; + + UMath::Scale(irb->GetLinearVelocity(), dT, dP); + irb->GetMatrix4(matrix); + + UMath::Vector3 position = isuspension->GetWheelCenterPos(i); + UMath::Vector3 dim; + + dim.y = isuspension->GetWheelRadius(i); + dim.z = dim.y; + dim.x = 0.15f; + + Dynamics::Collision::Geometry tirebox( + matrix, // + position, // + dim, // + Dynamics::Collision::Geometry::BOX, // + dP + ); + if (Dynamics::Collision::Geometry::FindIntersection(&mygeometry, &tirebox, &tirebox)) { + ispikeable->Puncture(i); + } + } + } +} + +void SpikeStrip::OnTaskSimulate(float dT) { + IModel *model; + const CollisionGeometry::Bounds *geometry; + + if (!mBody) { + return; + } + + model = GetOwner()->GetModel(); + if (!model) { + return; + } + + geometry = model->GetCollisionGeometry(); + if (!geometry) { + return; + } + + if (!mBody) { + return; + } + + { + SimCollisionMap *cmap = mBody->GetCollisionMap(); + + if (cmap && cmap->CollisionWithAny()) { + UMath::Matrix4 matrix; + UMath::Vector3 position; + UMath::Vector3 dim; + + model->GetTransform(matrix); + position = UMath::Vector4To3(matrix.v3); + matrix.v3 = UMath::Vector4::kIdentity; + geometry->GetHalfDimensions(dim); + + Dynamics::Collision::Geometry mygeometry( + matrix, // + position, // + dim, // + Dynamics::Collision::Geometry::BOX, // + UMath::Vector3::kZero + ); + + for (int i = 0; static_cast(i) < 0xA0; i++) { + if (cmap->CollisionWithOrderedBody(i)) { + IRigidBody *irb = cmap->GetOrderedBody(i); + + if (irb) { + OnCollide(mygeometry, irb, dT); + } + } + } + } else { + ITriggerableModel *itrigger; + + if (model->QueryInterface(&itrigger)) { + UMath::Matrix4 mat; + + GetOwner()->GetTransform(mat); + itrigger->PlaceTrigger(mat, true); + GetOwner()->Kill(); + } + } + } +} + +void SpikeStrip::Reset() {} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SpikeStrip.h b/src/Speed/Indep/Src/Physics/Behaviors/SpikeStrip.h new file mode 100644 index 000000000..3eebe51bf --- /dev/null +++ b/src/Speed/Indep/Src/Physics/Behaviors/SpikeStrip.h @@ -0,0 +1,32 @@ +#ifndef PHYSICS_BEHAVIORS_SPIKESTRIP_H +#define PHYSICS_BEHAVIORS_SPIKESTRIP_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h" +#include "Speed/Indep/Src/Physics/Behavior.h" +#include "Speed/Indep/Src/Physics/Dynamics/Collision.h" + +// total size: 0x50 +struct SpikeStrip : public Behavior { + public: + static Behavior *Construct(const BehaviorParams ¶ms); + + SpikeStrip(const BehaviorParams ¶ms); + ~SpikeStrip() override; + + void OnBehaviorChange(const UCrc32 &mechanic) override; + void Reset() override; + void OnTaskSimulate(float dT) override; + + void SetupBody(); + void OnCollide(const Dynamics::Collision::Geometry &mygeometry, IRigidBody *irb, float dT); + + private: + ISimpleBody *mBody; // offset 0x4C, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionRacer.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionRacer.cpp index 9fda428c0..63ba3c0c3 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionRacer.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionRacer.cpp @@ -23,6 +23,11 @@ #include "Speed/Indep/Src/Sim/Simulation.h" #include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); + // Credits: Brawltendo class SuspensionRacer : public Chassis, public Sim::Collision::IListener, public IAttributeable, Debugable { public: @@ -452,6 +457,8 @@ float EightDegreeTable[] = {0.0f, 1.83f, 3.6f, 5.0f, 5.96f, 6.4f}; float TenDegreeTable[] = {0.0f, 1.86f, 3.7f, 5.1f, 6.13f, 6.7f}; float TwelveDegreeTable[] = {0.0f, 1.9f, 3.8f, 5.2f, 6.3f, 7.1f}; +Wheel::~Wheel() {} + Table ZeroDegree = Table(ZeroDegreeTable, 6, 0.0f, 10.0f); Table TwoDegree = Table(TwoDegreeTable, 6, 0.0f, 10.0f); Table FourDegree = Table(FourDegreeTable, 6, 0.0f, 10.0f); diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionSimple.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionSimple.cpp index c7d952302..d9135735e 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionSimple.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionSimple.cpp @@ -13,6 +13,7 @@ #include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" #include "Speed/Indep/Src/Interfaces/Simables/IINput.h" #include "Speed/Indep/Src/Physics/Behavior.h" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" #include "Speed/Indep/Src/Physics/Wheel.h" #include "Speed/Indep/Src/Sim/Collision.h" #include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" @@ -33,6 +34,7 @@ class SuspensionSimple : public Chassis, public Sim::Collision::IListener, publi void BeginFrame(float max_slip, float grip_scale, float traction_boost); void EndFrame(float dT); void UpdateFree(float dT); + float UpdateLoaded(float lat_vel, float fwd_vel, float body_speed, float load, float dT, float drag_reduction); void MatchSpeed(float speed) { mAV = speed / mRadius; @@ -71,6 +73,50 @@ class SuspensionSimple : public Chassis, public Sim::Collision::IListener, publi return mRadius; } + void SetBrake(float brake) { + mBrake = brake; + } + + void SetEBrake(float ebrake) { + mEBrake = ebrake; + } + + float GetAngularVelocity() const { + return mAV; + } + + float GetLoad() const { + return mLoad; + } + + float GetTraction() const { + return mTraction; + } + + float GetSlip() const { + return mSlip; + } + + float GetLateralSpeed() const { + return mLateralSpeed; + } + + float GetSlipAngle() const { + return mSlipAngle; + } + + float GetToleratedSlip() const { + return mMaxSlip; + } + + void SetAngularVelocity(float av) { + mAV = av; + } + + float GetLongitudeForce() const { + return mLongitudeForce; + } + void ApplyTorque(float torque) { if (!mBrakeLocked) { mAppliedTorque += torque; @@ -81,10 +127,21 @@ class SuspensionSimple : public Chassis, public Sim::Collision::IListener, publi mTractionBoost *= scale; } + void SetTractionBoost(float boost) { + mTractionBoost = boost; + } + void AllowSlip(bool b) { mAllowSlip = b; } + void Stop() { + mAV = 0.0f; + mSlip = 0.0f; + mRoadSpeed = 0.0f; + mSlipAngle = 0.0f; + } + private: const float mRadius; // offset 0xC4, size 0x4 float mBrake; // offset 0xC8, size 0x4 @@ -115,10 +172,12 @@ class SuspensionSimple : public Chassis, public Sim::Collision::IListener, publi static Behavior *Construct(const BehaviorParams ¶ms); SuspensionSimple(const struct BehaviorParams &bp, const SuspensionParams &sp); + void CreateTires(); void DoAerobatics(State &state); void DoSteering(State &state, UMath::Vector3 &right, UMath::Vector3 &left); void DoWallSteer(State &state); void DoDriveForces(State &state); + void DoWheelForces(State &state); // Overrides // IUnknown @@ -128,10 +187,79 @@ class SuspensionSimple : public Chassis, public Sim::Collision::IListener, publi void OnAttributeChange(const Attrib::Collection *aspec, unsigned int attribkey) override; // ISuspension - void MatchSpeed(float speed) override; + float GetWheelTraction(unsigned int index) const override { + return mTires[index]->GetTraction(); + } + unsigned int GetNumWheels() const override { + return 4; + } + const UMath::Vector3 &GetWheelPos(unsigned int i) const override { + return mTires[i]->GetPosition(); + } + const UMath::Vector3 &GetWheelLocalPos(unsigned int i) const override { + return mTires[i]->GetLocalArm(); + } UMath::Vector3 GetWheelCenterPos(unsigned int i) const override; + float GetWheelLoad(unsigned int i) const override { + return mTires[i]->GetLoad(); + } + void ApplyVehicleEntryForces(bool enteringVehicle, const UMath::Vector3 &pos, bool calledfromEvent) override {} + const float GetWheelRoadHeight(unsigned int i) const override { + return mTires[i]->GetNormal().w; + } + bool IsWheelOnGround(unsigned int i) const override { + return mTires[i]->IsOnGround(); + } + float GetCompression(unsigned int i) const override { + return mTires[i]->GetCompression(); + } + float GetWheelSlip(unsigned int idx) const override { + return mTires[idx]->GetSlip(); + } + float GetToleratedSlip(unsigned int idx) const override { + return mTires[idx]->GetToleratedSlip(); + } + float GetWheelSkid(unsigned int idx) const override { + return mTires[idx]->GetLateralSpeed(); + } + float GetWheelSlipAngle(unsigned int idx) const override { + return mTires[idx]->GetSlipAngle(); + } + const UMath::Vector4 &GetWheelRoadNormal(unsigned int i) const override { + return mTires[i]->GetNormal(); + } + const SimSurface &GetWheelRoadSurface(unsigned int i) const override { + return mTires[i]->GetSurface(); + } + const UMath::Vector3 &GetWheelVelocity(unsigned int i) const override { + return mTires[i]->GetVelocity(); + } + int GetNumWheelsOnGround() const override { + return mNumWheelsOnGround; + } + Radians GetWheelAngularVelocity(int index) const override { + return mTires[index]->GetAngularVelocity(); + } + void SetWheelAngularVelocity(int wheel, float w) override { + mTires[wheel]->SetAngularVelocity(w); + } + float GetWheelSteer(unsigned int wheel) const override { + if (wheel < 2) { + return RAD2ANGLE(mWheelSteer[wheel]); + } + return 0.0f; + } + float GetWheelRadius(unsigned int index) const override { + return mTires[index]->GetRadius(); + } + float GetMaxSteering() const override { + return DEG2ANGLE(mMaxSteering); + } + void MatchSpeed(float speed) override; // Behavior + void OnBehaviorChange(const UCrc32 &mechanic) override; + void OnTaskSimulate(float dT) override; void Reset() override; // IListener @@ -165,6 +293,32 @@ class SuspensionSimple : public Chassis, public Sim::Collision::IListener, publi Tire *mTires[4]; // offset 0x134, size 0x10 }; +SuspensionSimple::Tire::Tire(float radius, int index, const Attrib::Gen::tires *specs, const Attrib::Gen::brakes *brakes) + : Wheel(1), // + mRadius(UMath::Max(radius, 0.1f)), // + mBrake(0.0f), // + mEBrake(0.0f), // + mAV(0.0f), // + mLoad(0.0f), // + mLateralForce(0.0f), // + mLongitudeForce(0.0f), // + mAppliedTorque(0.0f), // + mTractionBoost(1.0f), // + mSlip(0.0f), // + mLateralSpeed(0.0f), // + mWheelIndex(index), // + mRoadSpeed(0.0f), // + mSpecs(specs), // + mBrakes(brakes), // + mSlipAngle(0.0f), // + mAxleIndex(index >> 1), // + mBrakeLocked(false), // + mAllowSlip(false), // + mLastTorque(0.0f), // + mAngularAcc(0.0f), // + mMaxSlip(0.5f), // + mGripBoost(1.0f) {} + void SuspensionSimple::Tire::BeginFrame(float max_slip, float grip_scale, float traction_boost) { mMaxSlip = max_slip; mAppliedTorque = 0.0f; @@ -200,14 +354,248 @@ void SuspensionSimple::Tire::UpdateFree(float dT) { mAngularAcc = 0.0f; } +float SuspensionSimple::Tire::UpdateLoaded(float lat_vel, float fwd_vel, float body_speed, float load, float dT, float drag_reduction) { + if (mLoad <= 0.0f && !mBrakeLocked) { + mAV = fwd_vel / mRadius; + } + + const float fwd_acc = (fwd_vel - mRoadSpeed) / dT; + mRoadSpeed = fwd_vel; + mLateralSpeed = lat_vel; + mLoad = UMath::Max(load, 0.0f); + mTraction = 1.0f; + mBrakeLocked = mEBrake > 0.0f && mAxleIndex == 1; + + const bool noBrake = mBrake + mEBrake <= 0.0f; + if (!noBrake) { + const float abs_fwd = UMath::Abs(fwd_vel); + if (abs_fwd < 1.0f) { + mAppliedTorque = mAppliedTorque + ((-mBrake * load) * fwd_vel) / mRadius + ((-mEBrake * load) * fwd_vel) / mRadius; + } else { + float brake_torque = mBrake * FTLB2NM(mBrakes->BRAKES().At(mAxleIndex)) * 10.0f + + mEBrake * FTLB2NM(mBrakes->EBRAKE()) * 10.0f; + if (mAV <= 0.0f) { + brake_torque = mAppliedTorque + brake_torque; + } else { + brake_torque = mAppliedTorque - brake_torque; + } + mAppliedTorque = brake_torque; + } + } + + mSlipAngle = UMath::Atan2a(lat_vel, UMath::Abs(fwd_vel)); + if (!mBrakeLocked) { + if (!mAllowSlip) { + mSlip = 0.0f; + } else { + mSlip = mAV * mRadius - fwd_vel; + } + } else { + mSlip = mAV * mRadius - fwd_vel; + } + + const float abs_slip = UMath::Abs(mSlip); + bool clippedSlip = false; + float slipRatio = 1.0f; + if (mAllowSlip && mMaxSlip < abs_slip) { + slipRatio = mMaxSlip / abs_slip; + clippedSlip = true; + mTraction = mTraction * slipRatio; + } + + const float skid_speed = UMath::Sqrt(mSlip * mSlip + lat_vel * lat_vel); + float slip_scale = 1.0f; + const float slip_angle_scale = UMath::Abs(mSlipAngle) / (1.54f * 1.5f); + if (slip_angle_scale < slip_scale) { + slip_scale = slip_angle_scale; + } + if (slip_scale < 0.0f) { + slip_scale = 0.0f; + } + + float lateralForce = ((mGripBoost * slip_scale) * mLoad) * 0.85f * mSpecs->GRIP_SCALE().At(mAxleIndex) * mTractionBoost; + mLateralForce = lateralForce; + if (mSlipAngle > 0.0f) { + mLateralForce = -lateralForce; + } + + if (mBrakeLocked && skid_speed > 1.0f) { + const float dynamicGrip = mSpecs->DYNAMIC_GRIP().At(mAxleIndex); + mLongitudeForce = ((mSlip * mLoad) * mTractionBoost * dynamicGrip) / (skid_speed * 1.5f); + mLateralForce = (((-lat_vel * mLoad) * mTractionBoost) * dynamicGrip * 1.5f) / skid_speed; + } else if (!clippedSlip) { + mLongitudeForce = mAppliedTorque / mRadius; + } else { + float longitudeForce = mAppliedTorque / mRadius; + float speedRatio = 1.0f; + const float bodyRatio = (body_speed - 30.0f * 0.44703f) / (50.0f * 0.44703f - 30.0f * 0.44703f); + if (bodyRatio < speedRatio) { + speedRatio = bodyRatio; + } + if (speedRatio < 0.0f) { + speedRatio = 0.0f; + } + + float dynamicGrip = + ((((mLoad * mSpecs->DYNAMIC_GRIP().At(mAxleIndex)) * mTractionBoost) * (speedRatio * 0.5f + 0.5f)) / skid_speed) * mSlip; + mLongitudeForce = dynamicGrip; + if (dynamicGrip * longitudeForce <= 0.0f) { + longitudeForce = dynamicGrip; + } else if (0.0f < dynamicGrip) { + if (dynamicGrip < longitudeForce) { + longitudeForce = dynamicGrip; + } + } else if (longitudeForce < dynamicGrip) { + longitudeForce = dynamicGrip; + } + mLongitudeForce = longitudeForce; + } + + if (mAllowSlip && !clippedSlip && !mBrakeLocked) { + mLongitudeForce = mLongitudeForce + (((mAngularAcc * mRadius - fwd_acc) * 10.0f) / mRadius); + } + + const float max_force = + ((mLoad * mSpecs->STATIC_GRIP().At(mAxleIndex)) * mTractionBoost) * (mBrake * 0.5f + 1.0f); + const float force_sq = mLongitudeForce * mLongitudeForce + mLateralForce * mLateralForce; + if (max_force * max_force < force_sq && 1.0e-6f < force_sq) { + const float forceScale = max_force / UMath::Sqrt(force_sq); + mTraction = mTraction * forceScale; + mLateralForce = mLateralForce * forceScale; + mLongitudeForce = mLongitudeForce * forceScale; + slipRatio = slipRatio * forceScale; + slipRatio = slipRatio * forceScale; + } + + if (UMath::Abs(fwd_vel) > 1.0f) { + const float gripScale = mSpecs->GRIP_SCALE().At(mAxleIndex); + mLongitudeForce = mLongitudeForce - ((sinf(mSlipAngle * 6.2831855f) * mLateralForce) * drag_reduction) / gripScale; + } else { + float lateralScale = 1.0f; + const float abs_lat = UMath::Abs(lat_vel); + if (abs_lat < 1.0f) { + lateralScale = abs_lat; + } + mLateralForce = mLateralForce * lateralScale; + } + + if (noBrake && mWheelIndex > 1) { + mLongitudeForce = mLongitudeForce * mSurface.DRIVE_GRIP(); + mLateralForce = mLateralForce * mSurface.LATERAL_GRIP(); + } + + if (mBrakeLocked) { + mAV = 0.0f; + mAngularAcc = 0.0f; + } else if (!mAllowSlip || slipRatio >= 1.0f) { + mAV = mRoadSpeed / mRadius; + } else { + const float radius = mRadius; + float lastTorque = ((mAppliedTorque - mLongitudeForce * radius) + mLastTorque) * 0.5f; + mLastTorque = lastTorque; + float angularAcc = ((lastTorque - (mAV * 2.5f) * mSurface.ROLLING_RESISTANCE()) * 50.0f) - (slipRatio * mSlip) / (radius * dT); + angularAcc = ((fwd_acc / radius) - angularAcc) * slipRatio + angularAcc; + mAngularAcc = angularAcc; + mAV = angularAcc * dT + mAV; + } + + return mLateralForce; +} + Behavior *SuspensionSimple::Construct(const BehaviorParams ¶ms) { // "BASE" SuspensionParams sp(params.fparams.Fetch(UCrc32(0xa6b47fac))); return new SuspensionSimple(params, sp); } +SuspensionSimple::SuspensionSimple(const BehaviorParams &bp, const SuspensionParams &sp) + : Chassis(bp), // + mBrakeInfo(this, 0), // + mTireInfo(this, 0), // + mSuspensionInfo(this, 0), // + mDrivetrainInfo(this, 0), // + mRB(0), // + mRBComplex(0), // + mInput(0), // + mTrany(0), // + mCheater(0), // + mFrictionBoost(1.0f), // + mDraft(1.0f), // + mPowerSliding(false), // + mYawControlMultiplier(1.0f), // + mNumWheelsOnGround(0), // + mAgainstWall(0.0f), // + mMaxSteering(45.0f), // + mTimeInAir(0.0f), // + mSleepTime(0.0f), // + mDriftPhysics(false) { + GetOwner()->QueryInterface(&mRB); + GetOwner()->QueryInterface(&mRBComplex); + GetOwner()->QueryInterface(&mInput); + GetOwner()->QueryInterface(&mTrany); + GetOwner()->QueryInterface(&mCheater); + Sim::Collision::AddListener(this, GetOwner(), "SuspensionSimple"); + + mWheelSteer[0] = 0.0f; + mWheelSteer[1] = 0.0f; + + for (int i = 0; i < 4; ++i) { + mTires[i] = 0; + } + + CreateTires(); +} + +SuspensionSimple::~SuspensionSimple() { + for (int i = 0; i < 4; ++i) { + delete mTires[i]; + } + Sim::Collision::RemoveListener(this); +} + void SuspensionSimple::OnAttributeChange(const Attrib::Collection *aspec, unsigned int attribkey) {} +void SuspensionSimple::OnBehaviorChange(const UCrc32 &mechanic) { + Chassis::OnBehaviorChange(mechanic); + + if (mechanic == BEHAVIOR_MECHANIC_INPUT) { + GetOwner()->QueryInterface(&mInput); + } else if (mechanic == BEHAVIOR_MECHANIC_RIGIDBODY) { + GetOwner()->QueryInterface(&mRBComplex); + GetOwner()->QueryInterface(&mRB); + } else if (mechanic == BEHAVIOR_MECHANIC_ENGINE) { + GetOwner()->QueryInterface(&mTrany); + } else if (mechanic == BEHAVIOR_MECHANIC_AI) { + GetOwner()->QueryInterface(&mCheater); + } +} + +void SuspensionSimple::CreateTires() { + for (int i = 0; i < 4; ++i) { + delete mTires[i]; + float diameter = Physics::Info::WheelDiameter(mTireInfo, i < 2); + mTires[i] = new Tire(diameter * 0.5f, i, mTireInfo, mBrakeInfo); + } + + UMath::Vector3 dimension; + GetOwner()->GetRigidBody()->GetDimension(dimension); + + float wheelbase = mSuspensionInfo.WHEEL_BASE(); + float axle_width_f = mSuspensionInfo.TRACK_WIDTH().At(0) - mTireInfo.SECTION_WIDTH().At(0) * 0.001f; + float axle_width_r = mSuspensionInfo.TRACK_WIDTH().At(1) - mTireInfo.SECTION_WIDTH().At(1) * 0.001f; + float front_axle = mSuspensionInfo.FRONT_AXLE(); + + UVector3 fl(-axle_width_f * 0.5f, -dimension.y, front_axle); + UVector3 fr(axle_width_f * 0.5f, -dimension.y, front_axle); + UVector3 rl(-axle_width_r * 0.5f, -dimension.y, front_axle - wheelbase); + UVector3 rr(axle_width_r * 0.5f, -dimension.y, front_axle - wheelbase); + + GetWheel(0).SetLocalArm(fl); + GetWheel(1).SetLocalArm(fr); + GetWheel(2).SetLocalArm(rl); + GetWheel(3).SetLocalArm(rr); +} + void SuspensionSimple::MatchSpeed(float speed) { for (int i = 0; i < 4; ++i) { mTires[i]->MatchSpeed(speed); @@ -357,3 +745,283 @@ void SuspensionSimple::DoDriveForces(State &state) { } } } + +void SuspensionSimple::DoWheelForces(State &state) { + const float dT = state.time; + + UMath::Vector3 steerR; + UMath::Vector3 steerL; + DoSteering(state, steerR, steerL); + + for (unsigned int i = 2; i < 4; ++i) { + mTires[i]->SetEBrake(state.ebrake_input); + } + + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->SetBrake(state.brake_input); + } + + float catchup_cheat = 0.5f; + if (state.driver_style == 3 && state.driver_class != 1) { + if (!mCheater) { + catchup_cheat = 1.0f; + } else { + catchup_cheat = mCheater->GetCatchupCheat(); + } + catchup_cheat = catchup_cheat * (0.5f - 0.23f) + 0.23f; + } + + float shock_specs[2]; + float shock_ext_specs[2]; + float shock_valving[2]; + float shock_digression[2]; + float spring_specs[2]; + float sway_specs[2]; + float travel_specs[2]; + float rideheight_specs[2]; + float progression[2]; + + for (unsigned int i = 0; i < 2; ++i) { + shock_specs[i] = LBIN2NM(mSuspensionInfo.SHOCK_STIFFNESS().At(i)); + shock_ext_specs[i] = LBIN2NM(mSuspensionInfo.SHOCK_EXT_STIFFNESS().At(i)); + shock_valving[i] = INCH2METERS(mSuspensionInfo.SHOCK_VALVING().At(i)); + shock_digression[i] = 1.0f - mSuspensionInfo.SHOCK_DIGRESSION().At(i); + spring_specs[i] = LBIN2NM(mSuspensionInfo.SPRING_STIFFNESS().At(i)); + sway_specs[i] = LBIN2NM(mSuspensionInfo.SWAYBAR_STIFFNESS().At(i)); + travel_specs[i] = INCH2METERS(mSuspensionInfo.TRAVEL().At(i)); + rideheight_specs[i] = INCH2METERS(mSuspensionInfo.RIDE_HEIGHT().At(i)); + progression[i] = mSuspensionInfo.SPRING_PROGRESSION().At(i); + } + + float sway_stiffness[4]; + sway_stiffness[0] = (mTires[0]->GetCompression() - mTires[1]->GetCompression()) * sway_specs[0]; + sway_stiffness[1] = -sway_stiffness[0]; + sway_stiffness[2] = (mTires[2]->GetCompression() - mTires[3]->GetCompression()) * sway_specs[1]; + sway_stiffness[3] = -sway_stiffness[2]; + + UMath::Vector3 steering_normals[4]; + steering_normals[0] = steerL; + steering_normals[1] = steerR; + steering_normals[2] = state.GetForwardVector(); + steering_normals[3] = state.GetForwardVector(); + + unsigned int wheelsOnGround = 0; + float maxDelta = 0.0f; + bool resolve = false; + + const UMath::Vector3 &vUp = state.GetUpVector(); + const float maxShockForce = mSuspensionInfo.SHOCK_BLOWOUT() * state.mass * 0.25f * 9.81f; + + for (unsigned int i = 0; i < 4; ++i) { + const unsigned int axle = i / 2; + Tire &wheel = GetWheel(i); + wheel.UpdatePosition(state.angular_vel, state.linear_vel, state.matrix, state.world_cog, state.time, wheel.GetRadius(), true, state.collider, + state.dimension.y * 2.0f); + + const UMath::Vector3 &groundNormal = UMath::Vector4To3(wheel.GetNormal()); + const UMath::Vector3 &forwardNormal = steering_normals[i]; + UMath::Vector3 lateralNormal; + UMath::UnitCross(groundNormal, forwardNormal, lateralNormal); + + const float upness = UMath::Clamp(UMath::Dot(groundNormal, vUp), 0.0f, 1.0f); + const float oldCompression = wheel.GetCompression(); + float newCompression = rideheight_specs[axle] * upness + wheel.GetNormal().w; + const float maxCompression = travel_specs[axle]; + + if (oldCompression == 0.0f) { + maxDelta = UMath::Max(maxDelta, newCompression - maxCompression); + } + + newCompression = UMath::Max(newCompression, 0.0f); + if (maxCompression < newCompression) { + maxDelta = UMath::Max(maxDelta, newCompression - maxCompression); + newCompression = maxCompression; + } + + if (newCompression <= FLOAT_EPSILON || upness <= VehicleSystem::ENABLE_ROLL_STOPS_THRESHOLD) { + wheel.SetForce(UMath::Vector3::kZero); + wheel.UpdateFree(dT); + } else { + ++wheelsOnGround; + + float rise = (newCompression - oldCompression) / dT; + if (FLOAT_EPSILON < shock_valving[axle] && shock_digression[axle] < 1.0f) { + const float abs_rise = UMath::Abs(rise); + if (shock_valving[axle] < abs_rise) { + float digressed = shock_valving[axle] * UMath::Pow(abs_rise / shock_valving[axle], shock_digression[axle]); + rise = rise < 0.0f ? -digressed : digressed; + } + } + + float damp = rise * (rise <= 0.0f ? shock_ext_specs[axle] : shock_specs[axle]); + if (maxShockForce < damp) { + damp = 0.0f; + } + + float verticalLoad = damp + (newCompression * spring_specs[axle]) * (newCompression * progression[axle] + 1.0f) + sway_stiffness[i]; + verticalLoad = UMath::Max(verticalLoad, 0.0f); + + const UMath::Vector3 &pointVelocity = wheel.GetVelocity(); + const float xspeed = UMath::Dot(pointVelocity, lateralNormal); + const float zspeed = UMath::Dot(pointVelocity, forwardNormal); + float traction_force = wheel.UpdateLoaded(xspeed, state.local_vel.z, state.local_vel.z, verticalLoad, dT, catchup_cheat); + + float max_traction = (xspeed / dT) * 9.81f * (state.mass * 0.25f); + if (max_traction < 0.0f) { + max_traction = -max_traction; + } + traction_force = UMath::Clamp(traction_force, -max_traction, max_traction); + + UMath::Vector3 lateralForce; + UMath::Scale(lateralNormal, traction_force, lateralForce); + + UMath::Vector3 driveForce; + UMath::UnitCross(lateralNormal, groundNormal, driveForce); + UMath::Scale(driveForce, wheel.GetLongitudeForce(), driveForce); + + UMath::Vector3 verticalForce; + UMath::Scale(vUp, verticalLoad, verticalForce); + + UMath::Vector3 force; + UMath::Add(lateralForce, driveForce, force); + UMath::Add(force, verticalForce, force); + wheel.SetForce(force); + resolve = true; + } + + if (newCompression == 0.0f) { + wheel.IncAirTime(dT); + } else { + wheel.SetAirTime(0.0f); + } + wheel.SetCompression(newCompression); + } + + if (resolve) { + UMath::Vector3 total_force = UMath::Vector3::kZero; + UMath::Vector3 total_torque = UMath::Vector3::kZero; + + for (unsigned int i = 0; i < 4; ++i) { + Tire &wheel = GetWheel(i); + UMath::Vector3 p = wheel.GetLocalArm(); + p.y += wheel.GetCompression(); + p.y -= rideheight_specs[i / 2]; + UMath::RotateTranslate(p, state.matrix, p); + wheel.SetPosition(p); + + UMath::Vector3 r; + UMath::Sub(p, state.world_cog, r); + UMath::Sub(r, state.GetPosition(), r); + + UMath::Vector3 torque; + UMath::Cross(r, wheel.GetForce(), torque); + UMath::Add(total_force, wheel.GetForce(), total_force); + UMath::Add(total_torque, torque, total_torque); + } + + const float yaw = UMath::Dot(vUp, total_torque); + UMath::ScaleAdd(vUp, yaw * mYawControlMultiplier - yaw, total_torque, total_torque); + mRB->Resolve(total_force, total_torque); + } + + if (0.0f < maxDelta) { + for (unsigned int i = 0; i < 4; ++i) { + Tire &wheel = GetWheel(i); + wheel.SetY(wheel.GetPosition().y + maxDelta); + } + mRB->ModifyYPos(maxDelta); + } + + if (wheelsOnGround != 0 && !mNumWheelsOnGround) { + state.local_angular_vel.y *= 0.5f; + UMath::Rotate(state.local_angular_vel, state.matrix, state.angular_vel); + mRB->SetAngularVelocity(state.angular_vel); + } + + mNumWheelsOnGround = wheelsOnGround; +} + +void SuspensionSimple::OnTaskSimulate(float dT) { + if (!mInput || !mRBComplex || !mRB) { + return; + } + if (!mRBComplex->IsModeling()) { + return; + } + + float ride_extra = 0.0f; + if (mNumWheelsOnGround > 1) { + ride_extra = 2.0f; + } else if (mNumWheelsOnGround == 0) { + ride_extra = -10.0f; + } + SetCOG(0.0f, ride_extra); + + State state; + ComputeState(dT, state); + + if (mSleepTime > 3.0f && DoSleep(state) == SS_ALL) { + return; + } + + float max_slip = ComputeMaxSlip(state); + float grip_scale = ComputeLateralGripScale(state); + float traction_scale = ComputeTractionScale(state); + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->BeginFrame(max_slip, grip_scale, traction_scale); + } + + float traction_boost = UMath::Abs(GetVehicle()->GetSlipAngle() - mRB->GetDimension().y * 0.25f * RAD2ANGLE(1.0f)); + traction_boost = UMath::Clamp(traction_boost / DEG2ANGLE(35.0f), 0.0f, 1.0f); + + float speed_scale = UMath::Clamp(state.speed / 20.0f, 0.0f, 1.0f); + traction_boost = traction_boost * speed_scale * 0.15f; + if (state.gear == 6) { + traction_boost = 0.0f; + } + mTires[2]->SetTractionBoost(traction_boost + 1.0f); + mTires[3]->SetTractionBoost(traction_boost + 1.0f); + + float drag_pct = 1.0f; + float aero_pct = 1.0f; + if (mCheater) { + float catchup = mCheater->GetCatchupCheat(); + drag_pct = 1.0f - catchup; + aero_pct = catchup * (1.5f - aero_pct) + aero_pct; + } + if (state.driver_class == 1) { + aero_pct = UMath::Max(1.5f, aero_pct); + } + + DoAerodynamics(state, drag_pct, aero_pct, mTires[0]->GetLocalArm().z, mTires[2]->GetLocalArm().z, 0); + DoDriveForces(state); + DoWheelForces(state); + + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->UpdateTime(dT); + } + + mAgainstWall = 0.0f; + + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->EndFrame(dT); + } + + if (GetNumWheelsOnGround() != 0) { + mTimeInAir = 0.0f; + } else { + mTimeInAir += dT; + } + + DoAerobatics(state); + if (DoSleep(state) == SS_ALL) { + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->Stop(); + } + mSleepTime += dT; + } else { + mSleepTime = 0.0f; + } + + Chassis::OnTaskSimulate(dT); +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionSpline.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionSpline.cpp index f423e6aa3..111d176d7 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionSpline.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionSpline.cpp @@ -1,5 +1,6 @@ #include "Chassis.h" +#include "Speed/Indep/Libs/Support/Utility/UEALibs.hpp" #include "Speed/Indep/Libs/Support/Utility/UMath.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/brakes.h" @@ -10,16 +11,21 @@ #include "Speed/Indep/Src/Interfaces/Simables/IINput.h" #include "Speed/Indep/Src/Interfaces/Simables/INISCarControl.h" #include "Speed/Indep/Src/Physics/Behavior.h" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" #include "Speed/Indep/Src/Physics/Wheel.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" #include +extern float gNIS_AeroDynamics; +void OrthoInverse(UMath::Matrix4 &m); + // total size: 0x198 class SuspensionSpline : public Chassis, public INISCarControl { public: // total size: 0x124 class Tire : public Wheel { public: - Tire(float radius, int index, const Attrib::Gen::tires *specs, const Attrib::Gen::brakes *brakes); + Tire(float radius); void BeginFrame(); void EndFrame(float dT); void UpdateFree(float dT); @@ -38,20 +44,45 @@ class SuspensionSpline : public Chassis, public INISCarControl { return mRadius; } - // TODO do these exist? - // void ApplyTorque(float torque) { - // if (!mBrakeLocked) { - // mAppliedTorque += torque; - // } - // } + float GetAngularVelocity() const { + return mAV; + } + + float GetCurrentSlip() const { + return mSlip; + } + + float GetRoadSpeed() const { + return mRoadSpeed; + } + + float GetLateralSpeed() const { + return mLateralSpeed; + } + + float GetLoad() const { + return mLoad; + } + + float GetSlipAngle() const { + return mSlipAngle; + } + + float GetTraction() const { + return mLocked ? 0.0f : 1.0f; + } + + void SetAngularVelocity(float av) { + mAV = av; + } - // void ScaleTractionBoost(float scale) { - // mTractionBoost *= scale; - // } + void SetLocked(bool locked) { + mLocked = locked; + } - // void AllowSlip(bool b) { - // mAllowSlip = b; - // } + void SetBurnout(float speed) { + mBurnout = speed; + } private: bool mLocked; // offset 0xC4, size 0x1 @@ -70,21 +101,58 @@ class SuspensionSpline : public Chassis, public INISCarControl { SuspensionSpline(const struct BehaviorParams &bp, const SuspensionParams &sp); void DoSteering(State &state, UMath::Vector3 &right, UMath::Vector3 &left); void GetWheelBase(float *width, float *length); + void DoWheelForces(State &state); + void NISCarTweaks(float dT); // Overrides // IUnknown ~SuspensionSpline() override; // ISuspension + float GetWheelTraction(unsigned int index) const override; + unsigned int GetNumWheels() const override; + const UMath::Vector3 &GetWheelPos(unsigned int i) const override; + const UMath::Vector3 &GetWheelLocalPos(unsigned int i) const override; void MatchSpeed(float speed) override; UMath::Vector3 GetWheelCenterPos(unsigned int i) const override; + float GetWheelLoad(unsigned int i) const override; + void ApplyVehicleEntryForces(bool enteringVehicle, const UMath::Vector3 &pos, bool calledfromEvent) override; + const float GetWheelRoadHeight(unsigned int i) const override; + bool IsWheelOnGround(unsigned int i) const override; + float GetCompression(unsigned int i) const override; + float GetWheelSlip(unsigned int idx) const override; + float GetToleratedSlip(unsigned int idx) const override; + float GetWheelSkid(unsigned int idx) const override; + float GetWheelSlipAngle(unsigned int idx) const override; + const UMath::Vector4 &GetWheelRoadNormal(unsigned int i) const override; + const SimSurface &GetWheelRoadSurface(unsigned int i) const override; + const UMath::Vector3 &GetWheelVelocity(unsigned int i) const override; + int GetNumWheelsOnGround() const override; + Radians GetWheelAngularVelocity(int index) const override; + void SetWheelAngularVelocity(int wheel, float w) override; + float GetWheelSteer(unsigned int wheel) const override; + float GetWheelRadius(unsigned int index) const override; + float GetMaxSteering() const override; // Behavior + void OnTaskSimulate(float dT) override; void Reset() override; void OnBehaviorChange(const UCrc32 &mechanic) override; // INISCarControl + float GetBurnout() const override; + void SetBurnout(float speed) override; + void SetBrakeLock(bool front, bool rear) override; + void SetConstraintAngle(float angle) override; + void SetSteering(float angle, float weight) override; + bool SetNISPosition(const UMath::Matrix4 &position, bool initial, float dT) override; void RestoreState() override; + void SetAnimPitch(float dip, float time) override; + void SetAnimRoll(float dip, float time) override; + void SetAnimShake(float dip, float cycleRate, float cycleRamp, float time) override; + float GetAnimPitch() const override; + float GetAnimRoll() const override; + float GetAnimShake() const override; Tire &GetWheel(unsigned int i) { return *mTires[i]; @@ -126,6 +194,18 @@ class SuspensionSpline : public Chassis, public INISCarControl { Tire *mTires[4]; // offset 0x188, size 0x10 }; +SuspensionSpline::Tire::Tire(float radius) + : Wheel(0), // + mLocked(false), // + mBurnout(0.0f), // + mRadius(UMath::Max(radius, 0.1f)), // + mAV(0.0f), // + mSlip(0.0f), // + mRoadSpeed(0.0f), // + mSlipAngle(0.0f), // + mLateralSpeed(0.0f), // + mLoad(0.0f) {} + void SuspensionSpline::Tire::BeginFrame() { SetForce(UMath::Vector3::kZero); mLoad = 0.0f; @@ -147,6 +227,77 @@ Behavior *SuspensionSpline::Construct(const BehaviorParams ¶ms) { return new SuspensionSpline(params, sp); } +SuspensionSpline::SuspensionSpline(const BehaviorParams &bp, const SuspensionParams &sp) + : Chassis(bp), // + INISCarControl(this), // + mTireInfo(this, 0), // + mSuspensionInfo(this, 0), // + mDrivetrainInfo(this, 0), // + mRB(0), // + mCollisionBody(0), // + mInput(0), // + mIEngine(0), // + mNumWheelsOnGround(0), // + mMaxSteering(0.125f), // + mSteering(0.0f), // + mNISSteering(0.0f), // + mNISSteeringWeight(0.0f), // + mTimeInAir(0.0f), // + mBurnout(0.0f), // + mBrakeLockFront(false), // + mBrakeLockRear(false), // + mConstraint(60.0f), // + mAnimatedPitchLength(0.0f), // + mAnimatedPitch(0.0f), // + mAnimatedPitchDelta(0.0f), // + mAnimatedPitchTime(0.0f), // + mAnimatedRollLength(0.0f), // + mAnimatedRoll(0.0f), // + mAnimatedRollDelta(0.0f), // + mAnimatedRollTime(0.0f), // + mAnimatedShakeLength(0.0f), // + mAnimatedShakeCycleRate(0.0f), // + mAnimatedShakeRamp(0.0f), // + mAnimatedShake(0.0f), // + mAnimatedShakeDelta(0.0f), // + mAnimatedShakeTime(0.0f) { + mNISPosition = UMath::Matrix4::kIdentity; + + GetOwner()->QueryInterface(&mRB); + GetOwner()->QueryInterface(&mCollisionBody); + GetOwner()->QueryInterface(&mInput); + GetOwner()->QueryInterface(&mIEngine); + + for (int i = 0; i < 4; ++i) { + float diameter = Physics::Info::WheelDiameter(mTireInfo, i < 2); + mTires[i] = new Tire(diameter * 0.5f); + } + + UMath::Vector3 dimension; + GetOwner()->GetRigidBody()->GetDimension(dimension); + + float wheelbase = mSuspensionInfo.WHEEL_BASE(); + float axle_width_f = mSuspensionInfo.TRACK_WIDTH().At(0) - mTireInfo.SECTION_WIDTH().At(0) * 0.001f; + float axle_width_r = mSuspensionInfo.TRACK_WIDTH().At(1) - mTireInfo.SECTION_WIDTH().At(1) * 0.001f; + float front_axle = mSuspensionInfo.FRONT_AXLE(); + + UVector3 fl(-axle_width_f * 0.5f, -dimension.y, front_axle); + UVector3 fr(axle_width_f * 0.5f, -dimension.y, front_axle); + UVector3 rl(-axle_width_r * 0.5f, -dimension.y, front_axle - wheelbase); + UVector3 rr(axle_width_r * 0.5f, -dimension.y, front_axle - wheelbase); + + GetWheel(0).SetLocalArm(fl); + GetWheel(1).SetLocalArm(fr); + GetWheel(2).SetLocalArm(rl); + GetWheel(3).SetLocalArm(rr); +} + +SuspensionSpline::~SuspensionSpline() { + for (int i = 0; i < 4; ++i) { + delete mTires[i]; + } +} + void SuspensionSpline::Tire::UpdateLoaded(float lat_vel, float fwd_vel, float load, float dT) { mLoad = load; mRoadSpeed = fwd_vel; @@ -172,6 +323,253 @@ void SuspensionSpline::OnBehaviorChange(const UCrc32 &mechanic) { } } +void SuspensionSpline::DoWheelForces(State &state) { + UMath::Vector3 steerR; + UMath::Vector3 steerL; + DoSteering(state, steerR, steerL); + + float shock_specs[2]; + float shock_ext_specs[2]; + float shock_valving[2]; + float shock_digression[2]; + float spring_specs[2]; + float sway_specs[2]; + float travel_specs[2]; + float rideheight_specs[2]; + float progression[2]; + + for (unsigned int i = 0; i < 2; ++i) { + shock_specs[i] = LBIN2NM(mSuspensionInfo->SHOCK_STIFFNESS().At(i)); + shock_ext_specs[i] = LBIN2NM(mSuspensionInfo->SHOCK_EXT_STIFFNESS().At(i)); + shock_valving[i] = INCH2METERS(mSuspensionInfo->SHOCK_VALVING().At(i)); + shock_digression[i] = 1.0f - mSuspensionInfo->SHOCK_DIGRESSION().At(i); + spring_specs[i] = LBIN2NM(mSuspensionInfo->SPRING_STIFFNESS().At(i)); + sway_specs[i] = LBIN2NM(mSuspensionInfo->SWAYBAR_STIFFNESS().At(i)); + travel_specs[i] = INCH2METERS(mSuspensionInfo->TRAVEL().At(i)); + rideheight_specs[i] = INCH2METERS(mSuspensionInfo->RIDE_HEIGHT().At(i)); + progression[i] = mSuspensionInfo->SPRING_PROGRESSION().At(i); + } + + float sway_stiffness[4]; + sway_stiffness[0] = (mTires[0]->GetCompression() - mTires[1]->GetCompression()) * sway_specs[0]; + sway_stiffness[1] = -sway_stiffness[0]; + sway_stiffness[2] = (mTires[2]->GetCompression() - mTires[3]->GetCompression()) * sway_specs[1]; + sway_stiffness[3] = -sway_stiffness[2]; + + UMath::Vector3 steering_normals[4]; + steering_normals[0] = steerL; + steering_normals[1] = steerR; + steering_normals[2] = state.GetForwardVector(); + steering_normals[3] = state.GetForwardVector(); + + UMath::Vector3 world_cog; + UMath::Rotate(state.cog, state.matrix, world_cog); + + const UMath::Vector3 &vUp = state.GetUpVector(); + const float max_shock_force = mSuspensionInfo->SHOCK_BLOWOUT() * state.mass * 9.81f; + + unsigned int wheels_on_ground = 0; + float max_delta = 0.0f; + bool resolve = false; + UMath::Vector3 total_force = UMath::Vector3::kZero; + UMath::Vector3 total_torque = UMath::Vector3::kZero; + + for (unsigned int i = 0; i < 4; ++i) { + const unsigned int axle = i / 2; + Tire &wheel = GetWheel(i); + wheel.UpdatePosition(state.angular_vel, state.linear_vel, state.matrix, world_cog, state.time, wheel.GetRadius(), true, state.collider, + state.dimension.y * 2.0f); + + const UMath::Vector3 &ground_normal = UMath::Vector4To3(wheel.GetNormal()); + const UMath::Vector3 &forward_normal = steering_normals[i]; + UMath::Vector3 lateral_normal; + UMath::UnitCross(ground_normal, forward_normal, lateral_normal); + + const float upness = UMath::Clamp(UMath::Dot(ground_normal, vUp), 0.0f, 1.0f); + const float old_compression = wheel.GetCompression(); + float new_compression = rideheight_specs[axle] * upness + wheel.GetNormal().w; + const float max_compression = travel_specs[axle]; + + if (old_compression == 0.0f) { + max_delta = UMath::Max(max_delta, new_compression - max_compression); + } + + new_compression = UMath::Max(new_compression, 0.0f); + if (max_compression < new_compression) { + max_delta = UMath::Max(max_delta, new_compression - max_compression); + new_compression = max_compression; + } + + if (new_compression <= 0.0f || upness <= VehicleSystem::ENABLE_ROLL_STOPS_THRESHOLD) { + wheel.SetForce(UMath::Vector3::kZero); + wheel.UpdateFree(state.time); + } else { + ++wheels_on_ground; + + float rise = (new_compression - old_compression) / state.time; + if (0.001f < shock_valving[axle] && shock_digression[axle] < 1.0f) { + float abs_rise = UMath::Abs(rise); + if (shock_valving[axle] < abs_rise) { + float digressed = shock_valving[axle] * UMath::Pow(abs_rise / shock_valving[axle], shock_digression[axle]); + rise = rise <= 0.0f ? -digressed : digressed; + } + } + + float damp = rise * (rise <= 0.0f ? shock_ext_specs[axle] : shock_specs[axle]); + if (max_shock_force < damp) { + damp = 0.0f; + } + + float vertical_load = + damp + (new_compression * spring_specs[axle]) * (new_compression * progression[axle] + 1.0f) + sway_stiffness[i]; + vertical_load = UMath::Max(vertical_load, 0.0f); + + UMath::Vector3 vertical_force; + UMath::Scale(vUp, vertical_load, vertical_force); + + UMath::Vector3 up_cross_forward; + UMath::Cross(forward_normal, ground_normal, up_cross_forward); + UMath::Vector3 projected_forward; + UMath::Cross(up_cross_forward, forward_normal, projected_forward); + + const float load_scale = UMath::Max(0.25f, UMath::Dot(projected_forward, ground_normal) * 4.0f - 3.0f); + const float scaled_load = load_scale * vertical_load; + + const UMath::Vector3 &point_velocity = wheel.GetVelocity(); + const float lat_speed = UMath::Dot(point_velocity, lateral_normal); + const float fwd_speed = UMath::Dot(point_velocity, forward_normal); + + UMath::Vector3 tire_accel; + tire_accel.x = (lat_speed - wheel.GetLateralSpeed()) / state.time; + tire_accel.y = 0.0f; + tire_accel.z = (fwd_speed - wheel.GetRoadSpeed()) / state.time; + + wheel.UpdateLoaded(lat_speed, fwd_speed, scaled_load, state.time); + + UMath::Vector3 force; + UMath::Scale(lateral_normal, -(tire_accel.x * (state.mass * 0.25f)), force); + UMath::ScaleAdd(forward_normal, tire_accel.z * (state.mass * 0.25f), force, force); + + const float force_mag = UMath::Length(force); + if (mTireInfo->STATIC_GRIP().At(axle) * scaled_load < force_mag) { + UMath::Scale(force, (mTireInfo->DYNAMIC_GRIP().At(axle) * scaled_load) / force_mag, force); + } + + UMath::Vector3 wheel_force; + UMath::Add(force, vertical_force, wheel_force); + + UMath::Vector3 p = wheel.GetLocalArm(); + p.y = (p.y + new_compression) - rideheight_specs[axle]; + UMath::RotateTranslate(p, state.matrix, p); + wheel.SetPosition(p); + + UMath::Vector3 r; + UMath::Sub(p, world_cog, r); + + UMath::Vector3 torque; + UMath::Cross(r, wheel_force, torque); + UMath::Add(total_torque, torque, total_torque); + UMath::Add(total_force, wheel_force, total_force); + + wheel.SetForce(wheel_force); + resolve = true; + } + + if (new_compression == 0.0f) { + wheel.IncAirTime(state.time); + } else { + wheel.SetAirTime(0.0f); + } + wheel.SetCompression(new_compression); + } + + if (resolve) { + mRB->Resolve(total_force, total_torque); + } + + if (0.0f < max_delta) { + for (unsigned int i = 0; i < 4; ++i) { + Tire &wheel = GetWheel(i); + wheel.SetY(wheel.GetPosition().y + max_delta); + } + mRB->ModifyYPos(max_delta); + } + + mNumWheelsOnGround = wheels_on_ground; +} + +void SuspensionSpline::OnTaskSimulate(float dT) { + if (!mInput || !mCollisionBody || !mRB) { + return; + } + + SetCOG(0.0f, 0.0f); + + State state; + ComputeState(dT, state); + + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->BeginFrame(); + if (i > 1) { + mTires[i]->SetLocked(mBrakeLockRear); + if (mDrivetrainInfo->TORQUE_SPLIT() != 1.0f) { + mTires[i]->SetBurnout(mBurnout); + } + } else { + mTires[i]->SetLocked(mBrakeLockFront); + if (mDrivetrainInfo->TORQUE_SPLIT() != 0.0f) { + mTires[i]->SetBurnout(mBurnout); + } + } + } + + if (mBurnout != 0.0f) { + mInput->SetControlGas(1.0f); + } else { + mInput->SetControlGas(0.5f); + } + + if (mBrakeLockFront) { + if (mBrakeLockRear) { + mInput->SetControlBrake(1.0f); + mInput->SetControlHandBrake(0.0f); + } else { + mInput->SetControlBrake(0.0f); + mInput->SetControlHandBrake(0.0f); + } + } else { + if (mBrakeLockRear) { + mInput->SetControlHandBrake(1.0f); + mInput->SetControlBrake(0.0f); + } else { + mInput->SetControlBrake(0.0f); + mInput->SetControlHandBrake(0.0f); + } + } + + DoWheelForces(state); + + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->UpdateTime(dT); + } + + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->EndFrame(dT); + } + + if (GetNumWheelsOnGround() != 0) { + mTimeInAir = 0.0f; + } else { + mTimeInAir += dT; + } + + if (gNIS_AeroDynamics > 0.0f) { + DoAerodynamics(state, 0.0f, gNIS_AeroDynamics, GetWheel(0).GetLocalArm().z, GetWheel(2).GetLocalArm().z, 0); + } + + Chassis::OnTaskSimulate(dT); +} + void SuspensionSpline::RestoreState() { mInput->ClearInput(); @@ -188,6 +586,225 @@ void SuspensionSpline::RestoreState() { } } +float SuspensionSpline::GetBurnout() const { + return mBurnout; +} + +void SuspensionSpline::SetBurnout(float speed) { + mBurnout = speed; +} + +void SuspensionSpline::SetBrakeLock(bool front, bool rear) { + mBrakeLockFront = front; + mBrakeLockRear = rear; +} + +void SuspensionSpline::SetConstraintAngle(float angle) { + mConstraint = UMath::Clamp(angle, 0.0f, 80.0f); +} + +void SuspensionSpline::SetSteering(float angle, float weight) { + mNISSteering = UMath::Clamp(angle, -mMaxSteering, mMaxSteering); + mNISSteeringWeight = UMath::Clamp(weight, 0.0f, 1.0f); +} + +bool SuspensionSpline::SetNISPosition(const UMath::Matrix4 &position, bool initial, float dT) { + bool success = true; + + GetVehicle()->Activate(); + + if (initial) { + success = GetVehicle()->SetVehicleOnGround(UMath::ExtractAxis(position, 3), UMath::ExtractAxis(position, 2)); + GetVehicle()->SetSpeed(0.0f); + mNISPosition = position; + mRB->SetLinearVelocity(UMath::Vector3::kZero); + mRB->SetAngularVelocity(UMath::Vector3::kZero); + mInput->ClearInput(); + } else { + UMath::Vector3 linear_velocity_xz = UMath::Vector3::kZero; + float angular_velocity_y = 0.0f; + + if (dT > 1.0e-6f) { + UMath::Sub(UMath::ExtractAxis(position, 3), UMath::ExtractAxis(mNISPosition, 3), linear_velocity_xz); + UMath::Scale(linear_velocity_xz, 1.0f / dT, linear_velocity_xz); + + const float last_rotation = UMath::Atan2r(UMath::ExtractAxis(mNISPosition, 2).z, -UMath::ExtractAxis(mNISPosition, 2).x); + const float new_rotation = UMath::Atan2r(UMath::ExtractAxis(position, 2).z, -UMath::ExtractAxis(position, 2).x); + angular_velocity_y = (new_rotation - last_rotation) / dT; + } + + mNISPosition = position; + + UMath::Vector3 pos = UMath::ExtractAxis(position, 3); + pos.y = mRB->GetPosition().y; + mRB->SetPosition(pos); + + UMath::Vector3 vel = mRB->GetLinearVelocity(); + vel.x = linear_velocity_xz.x; + vel.z = linear_velocity_xz.z; + mRB->SetLinearVelocity(vel); + + UMath::Vector3 avel = mRB->GetAngularVelocity(); + avel.y = angular_velocity_y; + mRB->SetAngularVelocity(avel); + + UMath::Matrix4 matrix; + mRB->GetMatrix4(matrix); + + UMath::Vector3 cross; + UMath::Cross(UMath::ExtractAxis(matrix, 1), UMath::ExtractAxis(mNISPosition, 1), cross); + + const float pitch = asinf(UMath::Clamp(cross.x, -1.0f, 1.0f)) * 0.15915494f; + const float roll = asinf(UMath::Clamp(cross.z, -1.0f, 1.0f)) * 0.15915494f; + const float max_angle = mConstraint * 0.5f * 0.0027777778f; + const float newroll = UMath::Clamp(roll, -max_angle, max_angle); + const float newpitch = UMath::Clamp(pitch, -max_angle, max_angle); + + if (newroll != roll) { + MATRIX4_multzrot(&matrix, roll - newroll, &matrix); + } + + if (newpitch != pitch) { + MATRIX4_multxrot(&matrix, pitch - newpitch, &matrix); + } + + UMath::Matrix4 invorient; + UMath::Matrix4 localmatrix; + invorient = matrix; + OrthoInverse(invorient); + UMath::Mult(invorient, mNISPosition, localmatrix); + + const float newdelta = UMath::Atan2a(UMath::ExtractAxis(localmatrix, 2).z, -UMath::ExtractAxis(localmatrix, 2).x); + UMath::SetYRot(localmatrix, newdelta); + UMath::Mult(localmatrix, matrix, matrix); + mRB->SetOrientation(matrix); + } + + return success; +} + +void SuspensionSpline::SetAnimPitch(float dip, float time) { + mAnimatedPitchLength = time; + mAnimatedPitchTime = 0.0f; + mAnimatedPitch = 0.0f; + + float length; + GetWheelBase(0, &length); + if (dip < 0.0f) { + mAnimatedPitchDelta = -(static_cast(bATan(length * 0.5f, -dip)) * 3.0517578e-05f); + } else { + mAnimatedPitchDelta = static_cast(bATan(length * 0.5f, dip)) * 3.0517578e-05f; + } +} + +float SuspensionSpline::GetAnimPitch() const { + return mAnimatedPitch; +} + +void SuspensionSpline::SetAnimRoll(float dip, float time) { + mAnimatedRollLength = time; + mAnimatedRollTime = 0.0f; + mAnimatedRoll = 0.0f; + + float width; + GetWheelBase(&width, 0); + if (dip < 0.0f) { + mAnimatedRollDelta = -(static_cast(bATan(width * 0.5f, -dip)) * 3.0517578e-05f); + } else { + mAnimatedRollDelta = static_cast(bATan(width * 0.5f, dip)) * 3.0517578e-05f; + } +} + +float SuspensionSpline::GetAnimRoll() const { + return mAnimatedRoll; +} + +void SuspensionSpline::SetAnimShake(float dip, float cycleRate, float cycleRamp, float time) { + mAnimatedShakeLength = time; + mAnimatedShakeTime = 0.0f; + mAnimatedShake = 0.0f; + mAnimatedShakeCycleRate = cycleRate; + mAnimatedShakeRamp = cycleRamp; + + float width; + GetWheelBase(&width, 0); + if (dip < 0.0f) { + mAnimatedShakeDelta = -(static_cast(bATan(width * 0.5f, -dip)) * 3.0517578e-05f); + } else { + mAnimatedShakeDelta = static_cast(bATan(width * 0.5f, dip)) * 3.0517578e-05f; + } +} + +float SuspensionSpline::GetAnimShake() const { + return mAnimatedShake; +} + +void SuspensionSpline::NISCarTweaks(float dT) { + if (mAnimatedPitchLength != 0.0f) { + float time = mAnimatedPitchTime + dT; + mAnimatedPitchTime = time; + if (mAnimatedPitchLength <= time) { + mAnimatedPitchLength = 0.0f; + mAnimatedPitch = 0.0f; + } else { + float halfLength = mAnimatedPitchLength * 0.5f; + if (halfLength < time) { + time = halfLength - time * 0.5f; + } else { + time *= 0.5f; + } + mAnimatedPitch = (time / halfLength) * mAnimatedPitchDelta; + } + } + + if (mAnimatedRollLength != 0.0f) { + float time = mAnimatedRollTime + dT; + mAnimatedRollTime = time; + if (mAnimatedRollLength <= time) { + mAnimatedRollLength = 0.0f; + mAnimatedRoll = 0.0f; + } else { + float halfLength = mAnimatedRollLength * 0.5f; + if (halfLength < time) { + time = halfLength - time * 0.5f; + } else { + time *= 0.5f; + } + mAnimatedRoll = (time / halfLength) * mAnimatedRollDelta; + } + } + + if (mAnimatedShakeLength != 0.0f && mAnimatedShakeCycleRate != 0.0f) { + float time = mAnimatedShakeTime + dT; + mAnimatedShakeTime = time; + if (mAnimatedShakeLength <= time) { + mAnimatedShakeLength = 0.0f; + mAnimatedShake = 0.0f; + mAnimatedShakeDelta = 0.0f; + mAnimatedShakeRamp = 0.0f; + } else { + float totalCycles = static_cast(static_cast(mAnimatedShakeLength / mAnimatedShakeCycleRate)); + float cycle = static_cast(static_cast(time / mAnimatedShakeCycleRate)); + float ramp = static_cast(static_cast(mAnimatedShakeRamp / mAnimatedShakeCycleRate + 0.99f)); + float scale = 1.0f; + + if (ramp > 0.0f) { + if (ramp <= cycle) { + if (totalCycles - ramp < cycle) { + scale = (totalCycles - cycle) / ramp; + } + } else { + scale = cycle / ramp; + } + } + + unsigned short angle = + static_cast(static_cast(((time - mAnimatedShakeCycleRate * cycle) / mAnimatedShakeCycleRate) * 360.0f * 65536.0f) / 0x168); + mAnimatedShake = mAnimatedShakeDelta * bCos(angle) * scale; + } + } +} + void SuspensionSpline::GetWheelBase(float *width, float *length) { UMath::Vector3 dimension; GetOwner()->GetRigidBody()->GetDimension(dimension); @@ -209,12 +826,97 @@ UMath::Vector3 SuspensionSpline::GetWheelCenterPos(unsigned int i) const { } } +float SuspensionSpline::GetWheelTraction(unsigned int index) const { + return mTires[index]->GetTraction(); +} + +unsigned int SuspensionSpline::GetNumWheels() const { + return 4; +} + +const UMath::Vector3 &SuspensionSpline::GetWheelPos(unsigned int i) const { + return mTires[i]->GetPosition(); +} + +const UMath::Vector3 &SuspensionSpline::GetWheelLocalPos(unsigned int i) const { + return mTires[i]->GetLocalArm(); +} + +void SuspensionSpline::ApplyVehicleEntryForces(bool enteringVehicle, const UMath::Vector3 &pos, bool calledfromEvent) {} + +float SuspensionSpline::GetWheelLoad(unsigned int i) const { + return mTires[i]->GetLoad(); +} + +const float SuspensionSpline::GetWheelRoadHeight(unsigned int i) const { + return mTires[i]->GetNormal().w; +} + +float SuspensionSpline::GetCompression(unsigned int i) const { + return mTires[i]->GetCompression(); +} + +bool SuspensionSpline::IsWheelOnGround(unsigned int i) const { + return mTires[i]->IsOnGround(); +} + void SuspensionSpline::MatchSpeed(float speed) { for (int i = 0; i < 4; ++i) { mTires[i]->MatchSpeed(speed); } } +float SuspensionSpline::GetWheelSlip(unsigned int idx) const { + return mTires[idx]->GetCurrentSlip(); +} + +float SuspensionSpline::GetToleratedSlip(unsigned int idx) const { + return 0.0f; +} + +float SuspensionSpline::GetWheelSkid(unsigned int idx) const { + return mTires[idx]->GetLateralSpeed(); +} + +float SuspensionSpline::GetWheelSlipAngle(unsigned int idx) const { + return mTires[idx]->GetSlipAngle(); +} + +const UMath::Vector4 &SuspensionSpline::GetWheelRoadNormal(unsigned int i) const { + return mTires[i]->GetNormal(); +} + +const SimSurface &SuspensionSpline::GetWheelRoadSurface(unsigned int i) const { + return mTires[i]->GetSurface(); +} + +const UMath::Vector3 &SuspensionSpline::GetWheelVelocity(unsigned int i) const { + return mTires[i]->GetVelocity(); +} + +int SuspensionSpline::GetNumWheelsOnGround() const { + return mNumWheelsOnGround; +} + +Radians SuspensionSpline::GetWheelAngularVelocity(int index) const { + return mTires[index]->GetAngularVelocity(); +} + +void SuspensionSpline::SetWheelAngularVelocity(int wheel, float w) { +} + +float SuspensionSpline::GetWheelSteer(unsigned int wheel) const { + return wheel < 2 ? mSteering : 0.0f; +} + +float SuspensionSpline::GetWheelRadius(unsigned int index) const { + return mTires[index]->GetRadius(); +} + +float SuspensionSpline::GetMaxSteering() const { + return mMaxSteering; +} + void SuspensionSpline::Reset() { ISimable *owner = GetOwner(); IRigidBody &rigidBody = *owner->GetRigidBody(); diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionTraffic.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionTraffic.cpp index 3d3042913..0ef029cd0 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionTraffic.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionTraffic.cpp @@ -3,6 +3,7 @@ #include "Speed/Indep/Src/Generated/AttribSys/Classes/brakes.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/tires.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/transmission.h" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" #include "Speed/Indep/Src/Physics/Wheel.h" #include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" @@ -22,6 +23,7 @@ class SuspensionTraffic : public Chassis { void BeginFrame(); void EndFrame(float dT); void UpdateFree(float dT); + void UpdateLoaded(float lat_vel, float fwd_vel, float load, float dT); void Stop() { mAV = 0.0f; @@ -34,10 +36,50 @@ class SuspensionTraffic : public Chassis { return mRadius; } + float GetAngularVelocity() const { + return mAV; + } + + float GetLoad() const { + return mLoad; + } + + float GetSlip() const { + return mSlip; + } + + float GetLateralSpeed() const { + return mLateralSpeed; + } + + float GetSlipAngle() const { + return mSlipAngle; + } + + float GetTraction() const { + return mEBrake > 0.0f ? 0.0f : 1.0f; + } + void SetAngularVelocity(float av) { mAV = av; } + void SetBrake(float brake) { + mBrake = brake; + } + + void SetEBrake(float ebrake) { + mEBrake = ebrake; + } + + float GetLongitudeForce() const { + return mLongitudeForce; + } + + float GetLateralForce() const { + return mLateralForce; + } + void ApplyTorque(float torque) { mAppliedTorque += torque; } @@ -82,7 +124,65 @@ class SuspensionTraffic : public Chassis { void Reset() override; // ISuspension + float GetWheelTraction(unsigned int index) const override { + return mTires[index]->GetTraction(); + } + float GetWheelSlip(unsigned int idx) const override { + return mTires[idx]->GetSlip(); + } + float GetToleratedSlip(unsigned int idx) const override { + return 1.0f; + } + float GetWheelSkid(unsigned int idx) const override { + return mTires[idx]->GetLateralSpeed(); + } + float GetWheelLoad(unsigned int i) const override { + return mTires[i]->GetLoad(); + } + void SetWheelAngularVelocity(int wheel, float w) override {} + unsigned int GetNumWheels() const override { + return 4; + } + const UMath::Vector3 &GetWheelPos(unsigned int i) const override { + return mTires[i]->GetPosition(); + } + const UMath::Vector3 &GetWheelLocalPos(unsigned int i) const override { + return mTires[i]->GetLocalArm(); + } UMath::Vector3 GetWheelCenterPos(unsigned int i) const override; + void ApplyVehicleEntryForces(bool enteringVehicle, const UMath::Vector3 &pos, bool calledfromEvent) override {} + const float GetWheelRoadHeight(unsigned int i) const override { + return mTires[i]->GetNormal().w; + } + float GetCompression(unsigned int i) const override { + return mTires[i]->GetCompression(); + } + const UMath::Vector4 &GetWheelRoadNormal(unsigned int i) const override { + return mTires[i]->GetNormal(); + } + bool IsWheelOnGround(unsigned int i) const override { + return mTires[i]->IsOnGround(); + } + const SimSurface &GetWheelRoadSurface(unsigned int i) const override { + return mTires[i]->GetSurface(); + } + const UMath::Vector3 &GetWheelVelocity(unsigned int i) const override { + return mTires[i]->GetVelocity(); + } + int GetNumWheelsOnGround() const override { + return mNumWheelsOnGround; + } + Radians GetWheelAngularVelocity(int index) const override; + float GetWheelSteer(unsigned int wheel) const override { + return RAD2ANGLE(mLastSteer); + } + float GetWheelRadius(unsigned int index) const override; + float GetMaxSteering() const override { + return DEG2ANGLE(mMaxSteering); + } + float GetWheelSlipAngle(unsigned int idx) const override { + return mTires[idx]->GetSlipAngle(); + } void MatchSpeed(float speed) override; Tire &GetWheel(unsigned int i) { @@ -146,12 +246,127 @@ void SuspensionTraffic::Tire::UpdateFree(float dT) { mLongitudeForce = 0.0f; } +void SuspensionTraffic::Tire::UpdateLoaded(float lat_vel, float fwd_vel, float load, float dT) { + const Attrib::Gen::brakes::_LayoutStruct *brakes = + reinterpret_cast(mBrakes->GetLayoutPointer()); + const Attrib::Gen::tires::_LayoutStruct *tires = + reinterpret_cast(mSpecs->GetLayoutPointer()); + + if (mLoad <= 0.0f) { + mAV = fwd_vel / mRadius; + } + + mRoadSpeed = fwd_vel; + mLateralSpeed = lat_vel; + mLoad = UMath::Max(load, 0.0f); + + if (0.0f < mBrake) { + float brake = mBrake * (FTLB2NM(brakes->BRAKES.At(mAxleIndex)) * BrakingTorque); + if (0.0f < mAV) { + brake = -brake; + } + mAppliedTorque += brake; + } + + if (0.0f < mEBrake) { + float ebrake = mEBrake * FTLB2NM(brakes->EBRAKE) * EBrakingTorque; + if (0.0f < mAV) { + ebrake = -ebrake; + } + mAppliedTorque += ebrake; + } + + const float slip_angle = UMath::Atan2a(lat_vel, UMath::Abs(fwd_vel)); + const float slip = mAV * mRadius - fwd_vel; + mSlipAngle = slip_angle; + + if (0.0f < mEBrake && 1.0f < UMath::Abs(fwd_vel)) { + mSlip = slip; + } else { + mSlip = 0.0f; + } + + const float slip_mag = UMath::Sqrt(slip * slip + lat_vel * lat_vel); + if (mEBrake <= 0.5f || slip_mag <= 1.0f) { + mLongitudeForce = mAppliedTorque / mRadius; + } else { + mSlipping = true; + mLongitudeForce = ((-fwd_vel + -fwd_vel) * mLoad * tires->GRIP_SCALE.At(mAxleIndex)) / slip_mag; + } + + mLateralForce = (-lat_vel + -lat_vel) * mLoad * tires->GRIP_SCALE.At(mAxleIndex); + if (1.0f < slip_mag) { + mLateralForce /= slip_mag; + } + + if (UMath::Abs(fwd_vel) <= 1.0f) { + float speed_scale = 1.0f; + if (UMath::Abs(lat_vel) < 1.0f) { + speed_scale = UMath::Abs(lat_vel); + } + mLateralForce *= speed_scale; + } else { + mLongitudeForce -= UMath::Sina(mSlipAngle * 6.2831855f) * mLateralForce * 0.5f; + } + + mAV = ((1.0f - mEBrake) * fwd_vel) / mRadius; +} + Behavior *SuspensionTraffic::Construct(const BehaviorParams ¶ms) { // "BASE" SuspensionParams sp(params.fparams.Fetch(UCrc32(0xa6b47fac))); return new SuspensionTraffic(params, sp); } +SuspensionTraffic::SuspensionTraffic(const BehaviorParams &bp, const SuspensionParams &sp) + : Chassis(bp), // + mTireInfo(this, 0), // + mBrakeInfo(this, 0), // + mSuspensionInfo(this, 0), // + mDrivetrainInfo(this, 0), // + mLastSteer(0.0f), // + mMaxSteering(45.0f) { + mRB = nullptr; + mRBComplex = nullptr; + mInput = nullptr; + mNumWheelsOnGround = 0; + *reinterpret_cast(reinterpret_cast(this) + 0x48) = 0; + + GetOwner()->QueryInterface(&mRB); + GetOwner()->QueryInterface(&mRBComplex); + GetOwner()->QueryInterface(&mInput); + GetOwner()->QueryInterface(&mTransmission); + + for (int i = 0; i < 4; ++i) { + float diameter = Physics::Info::WheelDiameter(mTireInfo, i < 2); + mTires[i] = new Tire(diameter * 0.5f, i, mTireInfo, mBrakeInfo); + } + + UMath::Vector3 dimension; + GetOwner()->GetRigidBody()->GetDimension(dimension); + + float wheelbase = mSuspensionInfo.WHEEL_BASE(); + float axle_width_f = mSuspensionInfo.TRACK_WIDTH().At(0) - mTireInfo.SECTION_WIDTH().At(0) * 0.001f; + float axle_width_r = mSuspensionInfo.TRACK_WIDTH().At(1) - mTireInfo.SECTION_WIDTH().At(1) * 0.001f; + float front_axle = mSuspensionInfo.FRONT_AXLE(); + + UVector3 fl(-axle_width_f * 0.5f, -dimension.y, front_axle); + UVector3 fr(axle_width_f * 0.5f, -dimension.y, front_axle); + UVector3 rl(-axle_width_r * 0.5f, -dimension.y, front_axle - wheelbase); + UVector3 rr(axle_width_r * 0.5f, -dimension.y, front_axle - wheelbase); + + GetWheel(0).SetLocalArm(fl); + GetWheel(1).SetLocalArm(fr); + GetWheel(2).SetLocalArm(rl); + GetWheel(3).SetLocalArm(rr); +} + +SuspensionTraffic::~SuspensionTraffic() { + for (int i = 0; i < 4; ++i) { + delete mTires[i]; + } +} + void SuspensionTraffic::OnBehaviorChange(const UCrc32 &mechanic) { Chassis::OnBehaviorChange(mechanic); @@ -205,6 +420,14 @@ UMath::Vector3 SuspensionTraffic::GetWheelCenterPos(unsigned int i) const { return pos; } +Radians SuspensionTraffic::GetWheelAngularVelocity(int index) const { + return mTires[index]->GetAngularVelocity(); +} + +float SuspensionTraffic::GetWheelRadius(unsigned int index) const { + return mTires[index]->GetRadius(); +} + void SuspensionTraffic::MatchSpeed(float speed) { for (int i = 0; i < 4; ++i) { float w = mTires[i]->GetRadius(); @@ -284,3 +507,154 @@ void SuspensionTraffic::DoDriveForces(State &state) { } } } + +void SuspensionTraffic::DoWheelForces(State &state) { + UMath::Vector3 steerR; + UMath::Vector3 steerL; + DoSteering(state, steerR, steerL); + + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->SetBrake(state.brake_input); + if (i > 1) { + mTires[i]->SetEBrake(state.ebrake_input); + } + } + + UMath::Vector3 world_cog; + UMath::Rotate(state.cog, state.matrix, world_cog); + + float shock_specs[2]; + float spring_specs[2]; + float sway_specs[2]; + float travel_specs[2]; + float rideheight_specs[2]; + float progression[2]; + + for (unsigned int i = 0; i < 2; ++i) { + shock_specs[i] = LBIN2NM(mSuspensionInfo.SHOCK_STIFFNESS().At(i)); + spring_specs[i] = LBIN2NM(mSuspensionInfo.SPRING_STIFFNESS().At(i)); + sway_specs[i] = LBIN2NM(mSuspensionInfo.SWAYBAR_STIFFNESS().At(i)); + travel_specs[i] = INCH2METERS(mSuspensionInfo.TRAVEL().At(i)); + rideheight_specs[i] = INCH2METERS(mSuspensionInfo.RIDE_HEIGHT().At(i)); + progression[i] = mSuspensionInfo.SPRING_PROGRESSION().At(i); + } + + float sway_stiffness[4]; + sway_stiffness[0] = (mTires[0]->GetCompression() - mTires[1]->GetCompression()) * sway_specs[0]; + sway_stiffness[1] = -sway_stiffness[0]; + sway_stiffness[2] = (mTires[2]->GetCompression() - mTires[3]->GetCompression()) * sway_specs[1]; + sway_stiffness[3] = -sway_stiffness[2]; + + UMath::Vector3 steering_normals[4]; + steering_normals[0] = steerL; + steering_normals[1] = steerR; + steering_normals[2] = state.GetForwardVector(); + steering_normals[3] = state.GetForwardVector(); + + const UMath::Vector3 &vUp = state.GetUpVector(); + unsigned int wheels_on_ground = 0; + float max_delta = 0.0f; + bool resolve = false; + + for (unsigned int i = 0; i < 4; ++i) { + const unsigned int axle = i / 2; + Tire &wheel = GetWheel(i); + wheel.UpdatePosition(state.angular_vel, state.linear_vel, state.matrix, world_cog, state.time, wheel.GetRadius(), true, state.collider, + state.dimension.y * 2.0f); + + const UMath::Vector3 &ground_normal = UMath::Vector4To3(wheel.GetNormal()); + const UMath::Vector3 &forward_normal = steering_normals[i]; + UMath::Vector3 lateral_normal; + UMath::UnitCross(ground_normal, forward_normal, lateral_normal); + + const float upness = UMath::Clamp(UMath::Dot(vUp, ground_normal), 0.0f, 1.0f); + const float old_compression = wheel.GetCompression(); + float new_compression = rideheight_specs[axle] * upness + wheel.GetNormal().w; + const float max_compression = travel_specs[axle]; + + if (old_compression == 0.0f) { + max_delta = UMath::Max(max_delta, new_compression - max_compression); + } + + new_compression = UMath::Max(new_compression, 0.0f); + if (max_compression < new_compression) { + max_delta = UMath::Max(max_delta, new_compression - max_compression); + new_compression = max_compression; + } + + if (new_compression <= 0.0f || upness <= VehicleSystem::ENABLE_ROLL_STOPS_THRESHOLD) { + wheel.SetForce(UMath::Vector3::kZero); + wheel.UpdateFree(state.time); + } else { + ++wheels_on_ground; + + float damp = ((new_compression - old_compression) / state.time) * shock_specs[axle]; + if (mSuspensionInfo.SHOCK_BLOWOUT() * state.mass * 9.81f < damp) { + damp = 0.0f; + } + + float vertical_load = + damp + (new_compression * spring_specs[axle]) * (new_compression * progression[axle] + 1.0f) + sway_stiffness[i]; + vertical_load = UMath::Max(vertical_load, 0.0f); + + const float load_scale = UMath::Max(0.3f, upness * 4.0f - 3.0f); + const UMath::Vector3 &point_velocity = wheel.GetVelocity(); + const float lat_speed = UMath::Dot(point_velocity, lateral_normal); + const float fwd_speed = UMath::Dot(point_velocity, forward_normal); + wheel.UpdateLoaded(lat_speed, fwd_speed, load_scale * vertical_load, state.time); + + float max_lateral = (lat_speed / state.time) * (0.25f * state.mass); + if (max_lateral < 0.0f) { + max_lateral = -max_lateral; + } + + const float lateral_force = UMath::Clamp(wheel.GetLateralForce(), -max_lateral, max_lateral); + + UMath::Vector3 force; + UMath::Scale(lateral_normal, lateral_force, force); + + UMath::Vector3 drive_force; + UMath::UnitCross(lateral_normal, ground_normal, drive_force); + UMath::ScaleAdd(drive_force, wheel.GetLongitudeForce(), force, force); + UMath::ScaleAdd(vUp, vertical_load, force, force); + + wheel.SetForce(force); + resolve = true; + } + + if (new_compression == 0.0f) { + wheel.IncAirTime(state.time); + } else { + wheel.SetAirTime(0.0f); + } + wheel.SetCompression(new_compression); + } + + if (resolve) { + for (unsigned int i = 0; i < 4; ++i) { + Tire &wheel = GetWheel(i); + UMath::Vector3 p = wheel.GetLocalArm(); + p.y = (p.y + wheel.GetCompression()) - rideheight_specs[i / 2]; + UMath::RotateTranslate(p, state.matrix, p); + wheel.SetPosition(p); + + UMath::Vector3 r; + UMath::Sub(p, world_cog, r); + r.y *= 0.5f; + + UMath::Vector3 torque; + UMath::Cross(r, wheel.GetForce(), torque); + mRB->Resolve(wheel.GetForce(), torque); + } + } + + if (0.0f < max_delta) { + for (unsigned int i = 0; i < 4; ++i) { + Tire &wheel = GetWheel(i); + wheel.SetY(wheel.GetPosition().y + max_delta); + } + mRB->ModifyYPos(max_delta); + } + + mNumWheelsOnGround = wheels_on_ground; +} diff --git a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionTrailer.cpp b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionTrailer.cpp index be72a2e0d..dd97f7a80 100644 --- a/src/Speed/Indep/Src/Physics/Behaviors/SuspensionTrailer.cpp +++ b/src/Speed/Indep/Src/Physics/Behaviors/SuspensionTrailer.cpp @@ -7,11 +7,15 @@ #include "Speed/Indep/Src/Generated/AttribSys/Classes/tires.h" #include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" #include "Speed/Indep/Src/Interfaces/Simables/IDamageable.h" +#include "Speed/Indep/Src/Misc/Table.hpp" #include "Speed/Indep/Src/Physics/Behavior.h" #include "Speed/Indep/Src/Physics/Dynamics.h" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" #include "Speed/Indep/Src/Physics/Wheel.h" #include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +extern Table TrailerCorneringForce; + // total size: 0x108 class SuspensionTrailer : public Chassis { public: @@ -34,10 +38,50 @@ class SuspensionTrailer : public Chassis { return mRadius; } + float GetAngularVelocity() const { + return mAV; + } + + float GetLoad() const { + return mLoad; + } + + float GetSlip() const { + return mSlip; + } + + float GetLateralSpeed() const { + return mLateralSpeed; + } + + float GetRoadSpeed() const { + return mRoadSpeed; + } + + float GetTraction() const { + return mEBrake > 0.0f ? 0.0f : 1.0f; + } + void SetAngularVelocity(float av) { mAV = av; } + void SetBrake(float brake) { + mBrake = brake; + } + + void SetEBrake(float ebrake) { + mEBrake = ebrake; + } + + float GetLongitudeForce() const { + return mLongitudeForce; + } + + float GetLateralForce() const { + return mLateralForce; + } + private: const float mRadius; // offset 0xC4, size 0x4 float mBrake; // offset 0xC8, size 0x4 @@ -51,8 +95,8 @@ class SuspensionTrailer : public Chassis { float mLastTorque; // offset 0xE8, size 0x4 const int mWheelIndex; // offset 0xEC, size 0x4 float mRoadSpeed; // offset 0xF0, size 0x4 - const struct tires *mSpecs; // offset 0xF4, size 0x4 - const struct brakes *mBrakes; // offset 0xF8, size 0x4 + const Attrib::Gen::tires *mSpecs; // offset 0xF4, size 0x4 + const Attrib::Gen::brakes *mBrakes; // offset 0xF8, size 0x4 const int mAxleIndex; // offset 0xFC, size 0x4 bool mSlipping; // offset 0x100, size 0x1 float mLateralSpeed; // offset 0x104, size 0x4 @@ -76,8 +120,70 @@ class SuspensionTrailer : public Chassis { void Reset() override; // ISuspension - void MatchSpeed(float speed) override; + float GetWheelTraction(unsigned int index) const override { + return mTires[index]->GetTraction(); + } + unsigned int GetNumWheels() const override { + return 4; + } + const UMath::Vector3 &GetWheelPos(unsigned int i) const override { + return mTires[i]->GetPosition(); + } + const UMath::Vector3 &GetWheelLocalPos(unsigned int i) const override { + return mTires[i]->GetLocalArm(); + } UMath::Vector3 GetWheelCenterPos(unsigned int i) const override; + float GetWheelLoad(unsigned int i) const override { + return mTires[i]->GetLoad(); + } + void ApplyVehicleEntryForces(bool enteringVehicle, const UMath::Vector3 &pos, bool calledfromEvent) override {} + const float GetWheelRoadHeight(unsigned int i) const override { + return mTires[i]->GetNormal().w; + } + bool IsWheelOnGround(unsigned int i) const override { + return mTires[i]->IsOnGround(); + } + float GetCompression(unsigned int i) const override { + return mTires[i]->GetCompression(); + } + float GetWheelSlip(unsigned int idx) const override { + return mTires[idx]->GetSlip(); + } + float GetToleratedSlip(unsigned int idx) const override { + return 1.0f; + } + float GetWheelSkid(unsigned int idx) const override { + return mTires[idx]->GetLateralSpeed(); + } + float GetWheelSlipAngle(unsigned int idx) const override { + return GetVehicle()->GetSlipAngle(); + } + const UMath::Vector4 &GetWheelRoadNormal(unsigned int i) const override { + return mTires[i]->GetNormal(); + } + const SimSurface &GetWheelRoadSurface(unsigned int i) const override { + return mTires[i]->GetSurface(); + } + const UMath::Vector3 &GetWheelVelocity(unsigned int i) const override { + return mTires[i]->GetVelocity(); + } + int GetNumWheelsOnGround() const override { + return mNumWheelsOnGround; + } + Radians GetWheelAngularVelocity(int index) const override { + return mTires[index]->GetAngularVelocity(); + } + void SetWheelAngularVelocity(int wheel, float w) override {} + float GetWheelSteer(unsigned int wheel) const override { + return 0.0f; + } + float GetWheelRadius(unsigned int index) const override { + return mTires[index]->GetRadius(); + } + float GetMaxSteering() const override { + return 0.0f; + } + void MatchSpeed(float speed) override; Tire &GetWheel(unsigned int i) { return *mTires[i]; @@ -100,6 +206,27 @@ class SuspensionTrailer : public Chassis { Tire *mTires[4]; // offset 0xF8, size 0x10 }; +SuspensionTrailer::Tire::Tire(float radius, int index, const Attrib::Gen::tires *specs, const Attrib::Gen::brakes *brakes) + : Wheel(0), // + mRadius(UMath::Max(radius, 0.1f)), // + mBrake(0.0f), // + mEBrake(0.0f), // + mAV(0.0f), // + mLoad(0.0f), // + mLateralForce(0.0f), // + mLongitudeForce(0.0f), // + mAppliedTorque(0.0f), // + mSlip(0.0f), // + mLastTorque(0.0f), // + mWheelIndex(index), // + mRoadSpeed(0.0f), // + mSpecs(specs), // + mBrakes(brakes), // + mAxleIndex(index >> 1), // + mSlipping(false), // + mLateralSpeed(0.0f), // + mLastSign(WAS_ZERO) {} + void SuspensionTrailer::Tire::BeginFrame() { mAppliedTorque = 0.0f; SetForce(UMath::Vector3::kZero); @@ -120,12 +247,135 @@ void SuspensionTrailer::Tire::UpdateFree(float dT) { mLongitudeForce = 0.0f; } +void SuspensionTrailer::Tire::UpdateLoaded(float lat_vel, float fwd_vel, float load, float dT) { + const Attrib::Gen::brakes::_LayoutStruct *brakes = + reinterpret_cast(mBrakes->GetLayoutPointer()); + const Attrib::Gen::tires::_LayoutStruct *tires = + reinterpret_cast(mSpecs->GetLayoutPointer()); + + const float brake_torque = brakes->BRAKES.At(mAxleIndex) * FTLB2NM(4.0f); + const float ebrake_torque = brakes->EBRAKE * FTLB2NM(10.0f); + const float dynamic_grip = tires->DYNAMIC_GRIP.At(mAxleIndex); + const float static_grip = tires->STATIC_GRIP.At(mAxleIndex); + const float grip_scale = tires->GRIP_SCALE.At(mAxleIndex); + + mRoadSpeed = fwd_vel; + mLateralSpeed = lat_vel; + mLoad = UMath::Max(load, 0.0f); + + float brake = mBrake * brake_torque; + float ebrake = mEBrake * ebrake_torque; + if (0.0f < fwd_vel) { + brake = -brake; + ebrake = -ebrake; + } + + mAppliedTorque += brake; + mAppliedTorque += ebrake; + + const float slip_angle = UMath::Atan2a(lat_vel, UMath::Abs(fwd_vel)); + mLongitudeForce = mAppliedTorque / mRadius; + mSlip = mAV * mRadius - fwd_vel; + + float lateral_force = (mLoad * grip_scale) * TrailerCorneringForce.GetValue(UMath::Abs(slip_angle)); + if (0.0f < lat_vel) { + lateral_force = -lateral_force; + } + mLateralForce = lateral_force; + + if (mEBrake <= 0.5f) { + if (mAxleIndex == 1) { + mLateralForce *= 1.5f; + } + mAV = fwd_vel / mRadius; + } else { + if (mAxleIndex == 1) { + mLateralForce *= 0.75f; + } + mAV = 0.0f; + mSlipping = true; + } + + const float grip_mag = UMath::Sqrt(mLateralForce * mLateralForce + mLongitudeForce * mLongitudeForce); + if (mLoad * static_grip < grip_mag && 0.001f < grip_mag) { + const float grip_ratio = (mLoad * dynamic_grip) / grip_mag; + mLateralForce *= grip_ratio; + mLongitudeForce *= grip_ratio; + } + + if (UMath::Abs(fwd_vel) <= 1.0f) { + float speed_scale = 1.0f; + if (UMath::Abs(lat_vel) < 1.0f) { + speed_scale = UMath::Abs(lat_vel); + } + mLateralForce *= speed_scale; + } else { + mLongitudeForce -= UMath::Sina(slip_angle * 6.2831855f) * mLateralForce * 0.5f; + } + + if (0.0f < mEBrake) { + mAV = 0.0f; + mSlip = -fwd_vel; + } +} + Behavior *SuspensionTrailer::Construct(const BehaviorParams ¶ms) { // "BASE" SuspensionParams sp(params.fparams.Fetch(UCrc32(0xa6b47fac))); return new SuspensionTrailer(params, sp); } +SuspensionTrailer::SuspensionTrailer(const BehaviorParams &bp, const SuspensionParams &sp) + : Chassis(bp), // + mTireInfo(this, 0), // + mBrakeInfo(this, 0), // + mSuspensionInfo(this, 0), // + mRB(0), // + mIDynamicsEntity(0), // + mRBComplex(0), // + mPowerSliding(false), // + mNumWheelsOnGround(0), // + mMotionDampingFactor(0.0f), // + mSteeringControl(1.1f), // + mTimeInAir(0.0f) { + mDriftPhysics = false; + *reinterpret_cast(reinterpret_cast(this) + 0x48) = 0; + + GetOwner()->QueryInterface(&mRB); + GetOwner()->QueryInterface(&mRBComplex); + GetOwner()->QueryInterface(&mIDynamicsEntity); + GetOwner()->QueryInterface(&mIDamage); + + for (int i = 0; i < 4; ++i) { + float diameter = Physics::Info::WheelDiameter(mTireInfo, i < 2); + mTires[i] = new Tire(diameter * 0.5f, i, mTireInfo, mBrakeInfo); + } + + UMath::Vector3 dimension; + GetOwner()->GetRigidBody()->GetDimension(dimension); + + float axle_width_f = mSuspensionInfo.TRACK_WIDTH().At(0) - INCH2METERS(mTireInfo.SECTION_WIDTH().At(0)); + float axle_width_r = mSuspensionInfo.TRACK_WIDTH().At(1) - INCH2METERS(mTireInfo.SECTION_WIDTH().At(1)); + float front_axle = mSuspensionInfo.FRONT_AXLE(); + float rear_axle = front_axle - mSuspensionInfo.WHEEL_BASE(); + + UVector3 fl(-axle_width_f * 0.5f, -dimension.y, front_axle); + UVector3 fr(axle_width_f * 0.5f, -dimension.y, front_axle); + UVector3 rl(-axle_width_r * 0.5f, -dimension.y, rear_axle); + UVector3 rr(axle_width_r * 0.5f, -dimension.y, rear_axle); + + GetWheel(0).SetLocalArm(fl); + GetWheel(1).SetLocalArm(fr); + GetWheel(2).SetLocalArm(rl); + GetWheel(3).SetLocalArm(rr); +} + +SuspensionTrailer::~SuspensionTrailer() { + for (int i = 0; i < 4; ++i) { + delete mTires[i]; + } +} + void SuspensionTrailer::OnBehaviorChange(const UCrc32 &mechanic) { Chassis::OnBehaviorChange(mechanic); if (mechanic == BEHAVIOR_MECHANIC_RIGIDBODY) { @@ -228,3 +478,141 @@ void SuspensionTrailer::DoSimpleAero(State &state) { drag_vector *= drag; mRB->ResolveForce(drag_vector); } + +void SuspensionTrailer::DoWheelForces(State &state) { + for (unsigned int i = 0; i < 4; ++i) { + mTires[i]->SetBrake(state.brake_input); + mTires[i]->SetEBrake(state.ebrake_input); + } + + UMath::Vector3 world_cog; + UMath::Rotate(state.cog, state.matrix, world_cog); + + float shock_specs[2]; + float spring_specs[2]; + float sway_specs[2]; + float travel_specs[2]; + float rideheight_specs[2]; + float progression[2]; + + for (unsigned int i = 0; i < 2; ++i) { + shock_specs[i] = LBIN2NM(mSuspensionInfo.SHOCK_STIFFNESS().At(i)); + spring_specs[i] = LBIN2NM(mSuspensionInfo.SPRING_STIFFNESS().At(i)); + sway_specs[i] = LBIN2NM(mSuspensionInfo.SWAYBAR_STIFFNESS().At(i)); + travel_specs[i] = INCH2METERS(mSuspensionInfo.TRAVEL().At(i)); + rideheight_specs[i] = INCH2METERS(mSuspensionInfo.RIDE_HEIGHT().At(i)); + progression[i] = mSuspensionInfo.SPRING_PROGRESSION().At(i); + } + + float sway_stiffness[4]; + sway_stiffness[0] = (mTires[0]->GetCompression() - mTires[1]->GetCompression()) * sway_specs[0]; + sway_stiffness[1] = -sway_stiffness[0]; + sway_stiffness[2] = (mTires[2]->GetCompression() - mTires[3]->GetCompression()) * sway_specs[1]; + sway_stiffness[3] = -sway_stiffness[2]; + + const UMath::Vector3 &vUp = state.GetUpVector(); + const UMath::Vector3 &forward = state.GetForwardVector(); + unsigned int wheels_on_ground = 0; + float max_delta = 0.0f; + bool resolve = false; + + for (unsigned int i = 0; i < 4; ++i) { + const unsigned int axle = i / 2; + Tire &wheel = GetWheel(i); + wheel.UpdatePosition(state.angular_vel, state.linear_vel, state.matrix, world_cog, state.time, wheel.GetRadius(), true, state.collider, + state.dimension.y * 2.0f); + + const UMath::Vector3 &ground_normal = UMath::Vector4To3(wheel.GetNormal()); + UMath::Vector3 lateral_normal; + UMath::UnitCross(ground_normal, forward, lateral_normal); + + const float upness = UMath::Clamp(UMath::Dot(vUp, ground_normal), 0.0f, 1.0f); + const float old_compression = wheel.GetCompression(); + float new_compression = rideheight_specs[axle] * upness + wheel.GetNormal().w; + const float max_compression = travel_specs[axle]; + + if (old_compression == 0.0f) { + max_delta = UMath::Max(max_delta, new_compression - max_compression); + } + + new_compression = UMath::Max(new_compression, 0.0f); + if (max_compression < new_compression) { + max_delta = UMath::Max(max_delta, new_compression - max_compression); + new_compression = max_compression; + } + + if (new_compression <= 0.0f || upness <= VehicleSystem::ENABLE_ROLL_STOPS_THRESHOLD) { + wheel.SetForce(UMath::Vector3::kZero); + wheel.UpdateFree(state.time); + } else { + ++wheels_on_ground; + + float damp = ((new_compression - old_compression) / state.time) * shock_specs[axle]; + if (mSuspensionInfo.SHOCK_BLOWOUT() * state.mass * 9.81f < damp) { + damp = 0.0f; + } + + float vertical_load = + damp + (new_compression * spring_specs[axle]) * (new_compression * progression[axle] + 1.0f) + sway_stiffness[i]; + vertical_load = UMath::Max(vertical_load, 0.0f); + + const float load_scale = UMath::Max(0.3f, upness * 4.0f - 3.0f); + const UMath::Vector3 &point_velocity = wheel.GetVelocity(); + const float lat_speed = UMath::Dot(point_velocity, lateral_normal); + const float fwd_speed = UMath::Dot(point_velocity, forward); + wheel.UpdateLoaded(lat_speed, fwd_speed, load_scale * vertical_load, state.time); + + float max_lateral = (lat_speed / state.time) * (0.25f * state.mass); + if (max_lateral < 0.0f) { + max_lateral = -max_lateral; + } + + const float lateral_force = UMath::Clamp(wheel.GetLateralForce(), -max_lateral, max_lateral); + + UMath::Vector3 force; + UMath::Scale(lateral_normal, lateral_force, force); + + UMath::Vector3 drive_force; + UMath::UnitCross(lateral_normal, ground_normal, drive_force); + UMath::ScaleAdd(drive_force, wheel.GetLongitudeForce(), force, force); + UMath::ScaleAdd(vUp, vertical_load, force, force); + + wheel.SetForce(force); + resolve = true; + } + + if (new_compression == 0.0f) { + wheel.IncAirTime(state.time); + } else { + wheel.SetAirTime(0.0f); + } + wheel.SetCompression(new_compression); + } + + if (resolve) { + for (unsigned int i = 0; i < 4; ++i) { + Tire &wheel = GetWheel(i); + UMath::Vector3 p = wheel.GetLocalArm(); + p.y = (p.y + wheel.GetCompression()) - rideheight_specs[i / 2]; + UMath::RotateTranslate(p, state.matrix, p); + wheel.SetPosition(p); + + UMath::Vector3 r; + UMath::Sub(p, world_cog, r); + + UMath::Vector3 torque; + UMath::Cross(r, wheel.GetForce(), torque); + mRB->Resolve(wheel.GetForce(), torque); + } + } + + if (0.0f < max_delta) { + for (unsigned int i = 0; i < 4; ++i) { + Tire &wheel = GetWheel(i); + wheel.SetY(wheel.GetPosition().y + max_delta); + } + mRB->ModifyYPos(max_delta); + } + + mNumWheelsOnGround = wheels_on_ground; +} diff --git a/src/Speed/Indep/Src/Physics/Bounds.h b/src/Speed/Indep/Src/Physics/Bounds.h index 191aee250..c9006e0dc 100644 --- a/src/Speed/Indep/Src/Physics/Bounds.h +++ b/src/Speed/Indep/Src/Physics/Bounds.h @@ -7,8 +7,14 @@ #include "Speed/Indep/Libs/Support/Utility/UCOM.h" #include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" #include "Speed/Indep/Src/Sim/SimSurface.h" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +DECLARE_CONTAINER_TYPE(CollisionBoundsTable); namespace CollisionGeometry { @@ -30,53 +36,158 @@ enum BoundFlags { }; struct _V3c { - // total size: 0x6 - short x; // offset 0x0, size 0x2 - short y; // offset 0x2, size 0x2 - short z; // offset 0x4, size 0x2 + short x; + short y; + short z; + + void Decompress(UMath::Vector3 &to) const { + to.x = static_cast< float >(x) * (1.0f / 128.0f); + to.y = static_cast< float >(y) * (1.0f / 128.0f); + to.z = static_cast< float >(z) * (1.0f / 128.0f); + } }; struct _Q4c { - // total size: 0x8 - short x; // offset 0x0, size 0x2 - short y; // offset 0x2, size 0x2 - short z; // offset 0x4, size 0x2 - short w; // offset 0x6, size 0x2 + short x; + short y; + short z; + short w; + + void Decompress(UMath::Vector4 &to) const { + to.x = static_cast< float >(x) * (1.0f / 16384.0f); + to.y = static_cast< float >(y) * (1.0f / 16384.0f); + to.z = static_cast< float >(z) * (1.0f / 16384.0f); + to.w = static_cast< float >(w) * (1.0f / 16384.0f); + } +}; + +struct PCloudHeader { + int fNumPClouds; + int fPad[3]; +}; + +struct PCloud { + int fNumVerts; + int fPad1; + int fPad2; + UMath::Vector4 *fPList; }; struct BoundsHeader { - // total size: 0x10 - UCrc32 fNameHash; // offset 0x0, size 0x4 - int fNumBounds; // offset 0x4, size 0x4 - int fIsResolved; // offset 0x8, size 0x4 - int fPad; // offset 0xC, size 0x4 + UCrc32 fNameHash; + int fNumBounds; + int fIsResolved; + int fPad; }; struct Bounds; class IBoundable; struct Collection : public BoundsHeader { - // total size: 0x10 - + Bounds *GetBounds() { return reinterpret_cast< Bounds * >(this + 1); } + const Bounds *GetBounds() const { return reinterpret_cast< const Bounds * >(this + 1); } + PCloudHeader *GetPCHeader(); + const PCloudHeader *GetPCHeader() const; + PCloud *GetPCloud(); + const PCloud *GetPCloud() const; + + const Bounds *GetRoot() const; + const Bounds *GetChild(const Bounds *parent, UCrc32 name) const; + const Bounds *GetChild(const Bounds *parent, unsigned int idx) const; + const PCloud *GetPointCloud(const Bounds *parent) const; + const Bounds *GetBounds(UCrc32 hash_name) const; + void Init(); bool AddTo(IBoundable *irbc, const Bounds *root, const SimSurface &defsurface, bool parsechildren) const; + bool AddNode(IBoundable *iboundable, const Bounds *geom, const SimSurface &defsurface, bool ischild) const; }; -// total size: 0x30 struct Bounds { - _Q4c fOrientation; // offset 0x0, size 0x8 - _V3c fPosition; // offset 0x8, size 0x6 - unsigned short fFlags; // offset 0xE, size 0x2 - _V3c fHalfDimensions; // offset 0x10, size 0x6 - unsigned char fNumChildren; // offset 0x16, size 0x1 - char fPCloudIndex; // offset 0x17, size 0x1 - _V3c fPivot; // offset 0x18, size 0x6 - short fChildIndex; // offset 0x1E, size 0x2 - float fRadius; // offset 0x20, size 0x4 - UCrc32 fSurface; // offset 0x24, size 0x4 - UCrc32 fNameHash; // offset 0x28, size 0x4 - Collection *fCollection; // offset 0x2C, size 0x4 - - void GetPivot(UMath::Vector3 &to) const {} + _Q4c fOrientation; + _V3c fPosition; + unsigned short fFlags; + _V3c fHalfDimensions; + unsigned char fNumChildren; + char fPCloudIndex; + _V3c fPivot; + short fChildIndex; + float fRadius; + UCrc32 fSurface; + UCrc32 fNameHash; + Collection *fCollection; + + void GetPivot(UMath::Vector3 &to) const { fPivot.Decompress(to); } + void GetPosition(UMath::Vector3 &to) const { fPosition.Decompress(to); } + void GetHalfDimensions(UMath::Vector3 &to) const { fHalfDimensions.Decompress(to); } + void GetOrientation(UMath::Vector4 &to) const { fOrientation.Decompress(to); } + + const Bounds *GetChild(unsigned int idx) const { + return fCollection->GetChild(this, idx); + } + const Bounds *GetChild(UCrc32 namehash) const { + return fCollection->GetChild(this, namehash); + } +}; + +inline PCloudHeader *Collection::GetPCHeader() { + return reinterpret_cast< PCloudHeader * >(&GetBounds()[fNumBounds]); +} +inline const PCloudHeader *Collection::GetPCHeader() const { + return reinterpret_cast< const PCloudHeader * >(&GetBounds()[fNumBounds]); +} +inline PCloud *Collection::GetPCloud() { + return reinterpret_cast< PCloud * >(GetPCHeader() + 1); +} +inline const PCloud *Collection::GetPCloud() const { + return reinterpret_cast< const PCloud * >(GetPCHeader() + 1); +} + +class BoundsPack : public bTNode< BoundsPack > { + struct Pair { + UCrc32 Name; + Collection *Collection; + + Pair(UCrc32 name, struct Collection *collection) : Name(name), Collection(collection) {} + + bool operator<(const Pair &rhs) const { + return Name < rhs.Name; + } + }; + + class Table : public _STL::vector< Pair, UTL::Std::Allocator< Pair, _type_CollisionBoundsTable > > { + public: + void Add(Collection *collection) { + Pair pair(collection->fNameHash, collection); + iterator pos = _STL::upper_bound(begin(), end(), pair); + insert(pos, pair); + } + Collection *Find(UCrc32 name); + }; + + const bChunk *mChunk; + Table mTable; + + public: + void *operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + void operator delete(void *mem, std::size_t size) { + gFastMem.Free(mem, size, nullptr); + } + + BoundsPack(bChunk *pack); + + const bChunk *GetHeader() const { return mChunk; } + + const Collection *Find(UCrc32 name) { + return mTable.Find(UCrc32(name)); + } +}; + +struct Collections : public bTList< BoundsPack > { + ~Collections() {} + BoundsPack *Find(const bChunk *header); + const Collection *Find(UCrc32 name); }; class IBoundable : public UTL::COM::IUnknown { @@ -96,8 +207,8 @@ class IBoundable : public UTL::COM::IUnknown { CollisionGeometry::BoundFlags flags, bool persistant); }; -const Attrib::Collection *Lookup(UCrc32 object_name_hash); -bool CreateJoint(IBoundable *ifemale, struct UCrc32 femalenode_name, IBoundable *imale, UCrc32 malenode_name, UMath::Vector3 *out_female, +const Collection *Lookup(UCrc32 object_name_hash); +bool CreateJoint(IBoundable *ifemale, UCrc32 femalenode_name, IBoundable *imale, UCrc32 malenode_name, UMath::Vector3 *out_female, UMath::Vector3 *out_male, unsigned int joint_flags); }; // namespace CollisionGeometry diff --git a/src/Speed/Indep/Src/Physics/Common/Behavior.cpp b/src/Speed/Indep/Src/Physics/Common/Behavior.cpp index e69de29bb..65eba6d2e 100644 --- a/src/Speed/Indep/Src/Physics/Common/Behavior.cpp +++ b/src/Speed/Indep/Src/Physics/Common/Behavior.cpp @@ -0,0 +1,38 @@ +#include "Speed/Indep/Src/Physics/Behavior.h" + +#include "Speed/Indep/Src/Generated/AttribSys/GenericAccessor.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" +#include "Speed/Indep/Src/Physics/PhysicsObject.h" + +Behavior::Behavior(const BehaviorParams ¶ms, unsigned int num_interfaces) + : Sim::Object(num_interfaces + 1) + , mPaused(false) + , mOwner(params.fowner) + , mIOwner(static_cast(params.fowner)) + , mMechanic(params.fMechanic) + , mSignature(params.fSig) + , mPriority(0) + , mProfile(nullptr) { + const Attrib::Instance &attribs = params.fowner->GetAttributes(); + unsigned int count = + reinterpret_cast(&attribs)->Num_BEHAVIOR_ORDER(); + while (mPriority < count) { + UCrc32 order( + reinterpret_cast(&attribs)->BEHAVIOR_ORDER(mPriority)); + if (order == mMechanic) { + break; + } + mPriority = mPriority + 1; + } +} + +void Behavior::Pause(bool pause) { + if (mPaused != pause) { + mPaused = pause; + if (pause) { + OnPause(); + } else { + OnUnPause(); + } + } +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Physics/Common/Bounds.cpp b/src/Speed/Indep/Src/Physics/Common/Bounds.cpp index e69de29bb..a68cf049a 100644 --- a/src/Speed/Indep/Src/Physics/Common/Bounds.cpp +++ b/src/Speed/Indep/Src/Physics/Common/Bounds.cpp @@ -0,0 +1,451 @@ +#include "Speed/Indep/Src/Physics/Bounds.h" +#include "Speed/Indep/Src/Physics/Dynamics.h" + +#include "Speed/Indep/Libs/Support/Utility/UMath.h" + +inline void bPlatEndianSwap(UCrc32 *c) { + unsigned int val = c->GetValue(); + ::bPlatEndianSwap(&val); + *c = UCrc32(val); +} + +inline void bPlatEndianSwap(UMath::Vector4 *v) { + ::bPlatEndianSwap(&v->x); + ::bPlatEndianSwap(&v->y); + ::bPlatEndianSwap(&v->z); + ::bPlatEndianSwap(&v->w); +} + +void OrthoInverse(UMath::Matrix4 &m); + +namespace CollisionGeometry { + +static Collections TheCollections; +static UMath::Matrix4 fix; + +BoundsPack::BoundsPack(bChunk *pack) : mChunk(pack) { + bChunk *last_chunk = reinterpret_cast< bChunk * >(reinterpret_cast< char * >(pack) + pack->Size + 8); + int count = 0; + for (bChunk *chunk = pack->GetFirstChunk(); chunk < last_chunk; chunk = chunk->GetNext()) { + count = count + 1; + } + mTable.reserve(count); + + for (bChunk *chunk = pack->GetFirstChunk(); chunk < last_chunk; chunk = chunk->GetNext()) { + BoundsHeader *pheader = reinterpret_cast< BoundsHeader * >(chunk->GetAlignedData(16)); + UCrc32 name(pheader->fNameHash); + ::bPlatEndianSwap(&name); + + Collection *collection = mTable.Find(UCrc32(name)); + if (collection == nullptr) { + collection = reinterpret_cast< Collection * >(pheader); + collection->Init(); + mTable.Add(collection); + } + } +} + +inline Collection *BoundsPack::Table::Find(UCrc32 name) { + Pair *iter = _STL::lower_bound(begin(), end(), Pair(name, nullptr)); + if (iter != end() && iter->Name == name) { + return iter->Collection; + } + return nullptr; +} + +BoundsPack *Collections::Find(const bChunk *header) { + for (BoundsPack *pack = GetHead(); pack != EndOfList(); pack = pack->GetNext()) { + if (pack->GetHeader() == header) { + return pack; + } + } + return nullptr; +} + +const Collection *Collections::Find(UCrc32 name) { + for (BoundsPack *pack = GetHead(); pack != EndOfList(); pack = pack->GetNext()) { + const Collection *collection = pack->Find(UCrc32(name)); + if (collection != nullptr) { + return collection; + } + } + return nullptr; +} + +const Bounds *Collection::GetRoot() const { + if (fNumBounds > 0) { + return GetBounds(); + } + return nullptr; +} + +const Bounds *Collection::GetChild(const Bounds *parent, UCrc32 name) const { + if (parent->fChildIndex >= 0) { + for (int idx = 0; idx < static_cast< int >(parent->fNumChildren); idx++) { + const Bounds *bnds = &GetBounds()[parent->fChildIndex + idx]; + if (bnds->fNameHash == name) { + return bnds; + } + } + } + return nullptr; +} + +const Bounds *Collection::GetChild(const Bounds *parent, unsigned int idx) const { + if (idx < parent->fNumChildren && parent->fChildIndex >= 0) { + return &GetBounds()[parent->fChildIndex + idx]; + } + return nullptr; +} + +const PCloud *Collection::GetPointCloud(const Bounds *parent) const { + const PCloudHeader *pcheader = GetPCHeader(); + int numPClouds = pcheader->fNumPClouds; + if (numPClouds > 0 && parent->fPCloudIndex >= 0) { + const PCloud *pcloud = GetPCloud(); + for (int i = 0; i < numPClouds; i++) { + if (i == static_cast< int >(parent->fPCloudIndex)) { + return pcloud; + } + pcloud = reinterpret_cast< const PCloud * >(pcloud->fPList + pcloud->fNumVerts); + } + } + return nullptr; +} + +const Bounds *Collection::GetBounds(UCrc32 hash_name) const { + if (hash_name != UCrc32::kNull) { + for (int i = 0; i < fNumBounds; i++) { + if (GetBounds()[i].fNameHash == hash_name) { + return &GetBounds()[i]; + } + } + } + return nullptr; +} + +void Collection::Init() { + if (fIsResolved == 0) { + ::bPlatEndianSwap(&fNameHash); + ::bPlatEndianSwap(&fNumBounds); + if (fIsResolved == 0) { + int i; + PCloud *pcloud; + for (i = 0; i < fNumBounds; i++) { + Bounds &bounds = GetBounds()[i]; + ::bPlatEndianSwap(&bounds.fPosition.x); + ::bPlatEndianSwap(&bounds.fPosition.y); + ::bPlatEndianSwap(&bounds.fPosition.z); + ::bPlatEndianSwap(&bounds.fHalfDimensions.x); + ::bPlatEndianSwap(&bounds.fHalfDimensions.y); + ::bPlatEndianSwap(&bounds.fHalfDimensions.z); + ::bPlatEndianSwap(&bounds.fOrientation.x); + ::bPlatEndianSwap(&bounds.fOrientation.y); + ::bPlatEndianSwap(&bounds.fOrientation.z); + ::bPlatEndianSwap(&bounds.fOrientation.w); + ::bPlatEndianSwap(&bounds.fPivot.x); + ::bPlatEndianSwap(&bounds.fPivot.y); + ::bPlatEndianSwap(&bounds.fPivot.z); + ::bPlatEndianSwap(&bounds.fChildIndex); + ::bPlatEndianSwap(&bounds.fRadius); + ::bPlatEndianSwap(&bounds.fFlags); + ::bPlatEndianSwap(&bounds.fNameHash); + ::bPlatEndianSwap(&bounds.fSurface); + } + ::bPlatEndianSwap(&GetPCHeader()->fNumPClouds); + pcloud = GetPCloud(); + i = 0; + while (i < GetPCHeader()->fNumPClouds) { + ::bPlatEndianSwap(&pcloud->fNumVerts); + i = i + 1; + pcloud->fPList = reinterpret_cast< UMath::Vector4 * >(pcloud + 1); + for (int j = 0; j < pcloud->fNumVerts; j++) { + ::bPlatEndianSwap(&pcloud->fPList[j]); + } + pcloud = reinterpret_cast< PCloud * >(pcloud->fPList + pcloud->fNumVerts); + } + fIsResolved = 1; + } + } else { + PCloud *pcloud = GetPCloud(); + for (int i = 0; i < GetPCHeader()->fNumPClouds; i++) { + pcloud->fPList = reinterpret_cast< UMath::Vector4 * >(pcloud + 1); + pcloud = reinterpret_cast< PCloud * >(pcloud->fPList + pcloud->fNumVerts); + } + } + + for (int i = 0; i < fNumBounds; i++) { + GetBounds()[i].fCollection = this; + } +} + +bool Collection::AddTo(IBoundable *irbc, const Bounds *root, const SimSurface &defsurface, bool parsechildren) const { + bool added = false; + if (root != nullptr) { + if (parsechildren && root->fNumChildren != 0) { + for (unsigned int i = 0; i < root->fNumChildren; i++) { + const Bounds *geom = GetChild(root, i); + if (AddNode(irbc, geom, defsurface, true)) { + added = true; + } + } + } + if (!added) { + if (AddNode(irbc, root, defsurface, false)) { + added = true; + } + } + } + return added; +} + +bool Collection::AddNode(IBoundable *iboundable, const Bounds *geom, const SimSurface &defsurface, bool ischild) const { + bool result = false; + UMath::Vector3 offset; + UMath::Vector3 dim; + UMath::Vector4 orientation; + UMath::Matrix4 invmat; + + geom->GetHalfDimensions(dim); + geom->GetPosition(offset); + geom->GetOrientation(orientation); + + invmat = UMath::Matrix4::kIdentity; + SimSurface surface(defsurface); + unsigned short flags = geom->fFlags; + + if (geom->fSurface.GetValue() != 0) { + const Attrib::Collection *surfcol = SimSurface::Lookup(geom->fSurface); + SimSurface found(surfcol); + surface.Change(found.GetConstCollection()); + if (surface.GetConstCollection() == SimSurface::kNull.GetConstCollection()) { + surface.Change(defsurface.GetConstCollection()); + } + } + + if (ischild && (flags & kBounds_Internal)) { + result = false; + } else { + if (!ischild) { + UMath::QuaternionToMatrix4(orientation, invmat); + invmat[3].x = offset.x; + invmat[3].y = offset.y; + invmat[3].z = offset.z; + invmat[3].w = 1.0f; + OrthoInverse(invmat); + offset = UMath::Vector3::kZero; + orientation = UMath::Vector4::kIdentity; + } + + if (flags & (kBounds_PrimVsWorld | kBounds_PrimVsObjects | kBounds_PrimVsGround)) { + if (iboundable->AddCollisionPrimitive( + geom->fNameHash, dim, geom->fRadius, offset, surface, orientation, + static_cast< BoundFlags >(flags))) { + result = true; + } + } + + const PCloud *pcloud; + if ((flags & kBounds_MeshVsGround) && + (pcloud = GetPointCloud(geom)) != nullptr && + pcloud->fNumVerts > 0) { + if (ischild) { + iboundable->AddCollisionMesh( + geom->fNameHash, pcloud->fPList, pcloud->fNumVerts, surface, + static_cast< BoundFlags >(flags), true); + } else { + UMath::Vector4 tmp[16]; + int i = 0; + do { + UMath::Vector4 in = pcloud->fPList[i]; + UMath::RotateTranslate(in, invmat, tmp[i]); + i++; + } while (i < pcloud->fNumVerts); + iboundable->AddCollisionMesh( + geom->fNameHash, tmp, pcloud->fNumVerts, surface, + static_cast< BoundFlags >(flags), false); + } + result = true; + } + } + + return result; +} + +const Collection *Lookup(UCrc32 object_name_hash) { + return TheCollections.Find(UCrc32(object_name_hash)); +} + +bool CreateJoint(IBoundable *ifemale, UCrc32 femalenode_name, IBoundable *imale, UCrc32 malenode_name, + UMath::Vector3 *out_female, UMath::Vector3 *out_male, unsigned int joint_flags) { + const Bounds *female_node = ifemale->GetGeometryNode(); + const Bounds *male_node = imale->GetGeometryNode(); + + if (female_node == nullptr || male_node == nullptr) { + return false; + } + + IDynamicsEntity *ide_female; + if (!ifemale->QueryInterface(&ide_female)) { + return false; + } + + IDynamicsEntity *ide_male; + if (!imale->QueryInterface(&ide_male)) { + return false; + } + + const Bounds *male_connector = nullptr; + for (unsigned int i = 0; i < male_node->fNumChildren; i++) { + const Bounds *child = male_node->GetChild(i); + if (child->fNameHash == malenode_name && (child->fFlags & kBounds_Joint_Male)) { + male_connector = child; + break; + } + male_connector = nullptr; + } + + if (male_connector == nullptr) { + return false; + } + + const Bounds *female_connector = nullptr; + for (unsigned int i = 0; i < female_node->fNumChildren; i++) { + const Bounds *child = female_node->GetChild(i); + if (child->fNameHash == femalenode_name && (child->fFlags & kBounds_Joint_Female)) { + female_connector = child; + break; + } + female_connector = nullptr; + } + + if (female_connector == nullptr) { + return false; + } + + UMath::Vector3 lever_male; + UMath::Vector3 lever_female; + male_connector->GetPosition(lever_male); + female_connector->GetPosition(lever_female); + + if (out_female != nullptr) { + *out_female = lever_female; + } + if (out_male != nullptr) { + *out_male = lever_male; + } + + Dynamics::Articulation::HJOINT__ *hjoint = Dynamics::Articulation::Create( + ide_female, lever_female, ide_male, lever_male, + static_cast< Dynamics::Articulation::eJointFlags >(joint_flags)); + + if (hjoint == nullptr) { + return false; + } + + fix[0][0] = 1.0f; + fix[0][1] = 0.0f; + fix[0][2] = 0.0f; + fix[0][3] = 0.0f; + fix[1][0] = 0.0f; + fix[1][1] = -1.0f; + fix[1][2] = 0.0f; + fix[1][3] = 0.0f; + fix[2][0] = 0.0f; + fix[2][1] = 0.0f; + fix[2][2] = 1.0f; + fix[2][3] = 0.0f; + fix[3][0] = 0.0f; + fix[3][1] = 0.0f; + fix[3][2] = 0.0f; + fix[3][3] = 1.0f; + + for (unsigned int i = 0; i < female_connector->fNumChildren; i++) { + const Bounds *constraint = female_connector->GetChild(i); + if ((constraint->fFlags & (kBounds_Constraint_Conical | kBounds_Constraint_Prismatic)) == 0) { + continue; + } + + const Bounds *post = nullptr; + for (unsigned int j = 0; j < male_connector->fNumChildren; j++) { + post = male_connector->GetChild(j); + if (!(post->fFlags & kBounds_Male_Post)) { + continue; + } + if (post->fNameHash == constraint->fNameHash) { + break; + } + } + + if (post == nullptr) { + continue; + } + + UMath::Vector3 constraint_dim; + UMath::Vector4 q; + UMath::Matrix4 constraint_mat; + UMath::Vector4 constraint_orientation; + UMath::Vector4 female_connector_orientation; + + constraint->GetHalfDimensions(constraint_dim); + constraint->GetOrientation(constraint_orientation); + female_connector->GetOrientation(female_connector_orientation); + UMath::Mult(female_connector_orientation, constraint_orientation, q); + UMath::QuaternionToMatrix4(q, constraint_mat); + UMath::Mult(fix, constraint_mat, constraint_mat); + + UMath::Matrix4 post_mat; + UMath::Vector3 post_dim; + UMath::Vector4 post_orientation; + UMath::Vector4 male_connector_orientation; + + post->GetHalfDimensions(post_dim); + post->GetOrientation(post_orientation); + male_connector->GetOrientation(male_connector_orientation); + UMath::Mult(male_connector_orientation, post_orientation, q); + UMath::QuaternionToMatrix4(q, post_mat); + UMath::Mult(fix, post_mat, post_mat); + + if (constraint->fFlags & kBounds_Constraint_Conical) { + UMath::Vector3 post_vec; + post_vec.x = post_mat[2][0]; + post_vec.y = post_mat[2][1]; + post_vec.z = post_mat[2][2]; + UMath::Scale(post_vec, post_dim.y + post_dim.y); + float theta = UMath::Atan2d(constraint_dim.x, constraint_dim.y + constraint_dim.y); + Dynamics::Articulation::Constrain(hjoint, ide_female, constraint_mat, theta * 360.0f, theta * 360.0f, post_vec, Dynamics::Articulation::CONICAL); + } else if (constraint->fFlags & kBounds_Constraint_Prismatic) { + UMath::Vector3 post_vec; + post_vec.x = post_mat[2][0]; + post_vec.y = post_mat[2][1]; + post_vec.z = post_mat[2][2]; + UMath::Scale(post_vec, post_dim.y + post_dim.y); + float theta = UMath::Atan2d(constraint_dim.x, constraint_dim.y + constraint_dim.y); + Dynamics::Articulation::Constrain(hjoint, ide_female, constraint_mat, theta * 360.0f, theta * 360.0f, post_vec, Dynamics::Articulation::PRISMATIC); + } + } + + return true; +} + +}; // namespace CollisionGeometry + +int LoaderBounds(bChunk *chunk) { + if (chunk->GetID() != 0x8003b900) { + return 0; + } + CollisionGeometry::TheCollections.AddHead(new CollisionGeometry::BoundsPack(chunk)); + return 1; +} + +int UnloaderBounds(bChunk *chunk) { + if (chunk->GetID() != 0x8003b900) { + return 0; + } + CollisionGeometry::BoundsPack *pack = CollisionGeometry::TheCollections.Find(chunk); + if (pack != nullptr) { + CollisionGeometry::TheCollections.Remove(pack); + delete pack; + } + return 1; +} diff --git a/src/Speed/Indep/Src/Physics/Common/Explosion.cpp b/src/Speed/Indep/Src/Physics/Common/Explosion.cpp index e69de29bb..db8361dde 100644 --- a/src/Speed/Indep/Src/Physics/Common/Explosion.cpp +++ b/src/Speed/Indep/Src/Physics/Common/Explosion.cpp @@ -0,0 +1,161 @@ +#include "Speed/Indep/Src/Physics/Explosion.h" + +#include "Speed/Indep/Src/Interfaces/Simables/IExplodeable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRenderable.h" +#include "Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.h" +#include "Speed/Indep/Src/Physics/Dynamics/Collision.h" + +namespace Sim { +bool CanSpawnSimpleRigidBody(const UMath::Vector3 &position, bool highPriority); +} + +static inline ISimpleBody *FindSimpleBody(ISimable *owner) { + return reinterpret_cast( + (*reinterpret_cast(owner))->_mInterfaces.Find((HINTERFACE)ISimpleBody::_IHandle) + ); +} + +UTL::COM::Factory::Prototype _Explosion("Explosion", Explosion::Construct); + +ISimable *Explosion::Construct(Sim::Param params) { + const ExplosionParams ep(params.Fetch(UCrc32(0xa6b47fac))); + if (Sim::CanSpawnSimpleRigidBody(ep.fPosition, true)) { + return new Explosion(ep, params); + } + return nullptr; +} + +Explosion::Explosion(const ExplosionParams ¶ms, Sim::Param sp) + : PhysicsObject("explosion", "default", SIMABLE_EXPLOSION, nullptr, 0) // + , IExplosion(this) // + , mExpansionSpeed(params.fExpansionSpeed) // + , mExpansionRadius(params.fRadius) // + , mSource(params.fSource) // + , mDamages(params.fDamage) // + , mTargets(params.fTargets) +{ + mIRBSimple = nullptr; + mCausality = nullptr; + mCauseTime = 0.0f; + mEffectSource = params.fEffectSource; + + float start_radius = UMath::Max(0.0f, params.fStartRadius); + LoadBehavior( + UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), // + UCrc32("SimpleRigidBody"), // + RBSimpleParams( + params.fPosition, // + UMath::Vector3::kZero, // + UMath::Vector3::kZero, // + UMath::Matrix4::kIdentity, // + start_radius, // + 1.0f + ) + ); + + IModel *model = IModel::FindInstance(mSource); + if (model) { + mCausality = model->GetCausality(); + mCauseTime = model->GetCausalityTime(); + } +} + +Explosion::~Explosion() {} + +void Explosion::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY)) { + bool hasbody; + + mIRBSimple = FindSimpleBody(static_cast(this)); + hasbody = mIRBSimple != nullptr; + + if (hasbody) { + mIRBSimple->ModifyFlags(0, 0xC100); + } + } + PhysicsObject::OnBehaviorChange(mechanic); +} + +void Explosion::OnCollide(IRigidBody *other, float dT, float radius, + const Dynamics::Collision::Geometry &explosion_sphere) { + IExplodeable *iexplodeable; + if (!other->QueryInterface(&iexplodeable)) { + return; + } + + float total_radius = radius + other->GetRadius(); + if (UMath::DistanceSquare(other->GetPosition(), explosion_sphere.GetPosition()) < + total_radius * total_radius) { + float targetspeed; + UMath::Vector3 dim; + UMath::Matrix4 matrix; + other->GetDimension(dim); + other->GetMatrix4(matrix); + Dynamics::Collision::Geometry box(matrix, other->GetPosition(), dim, + Dynamics::Collision::Geometry::BOX, UMath::Vector3::kZero); + if (Dynamics::Collision::Geometry::FindIntersection(&box, &explosion_sphere, &box)) { + iexplodeable->OnExplosion(box.GetCollisionNormal(), box.GetCollisionPoint(), dT, + static_cast(this)); + } + } +} + +float Explosion::GetRadius() const { + const IRigidBody *irb = GetRigidBody(); + if (irb) { + return irb->GetRadius(); + } + return 0.0f; +} + +void Explosion::TestCollisions(float dT) { + if (!mIRBSimple) { + return; + } + { + SimCollisionMap *cmap = mIRBSimple->GetCollisionMap(); + if (!cmap) { + return; + } + if (!cmap->CollisionWithAny()) { + return; + } + + { + IRigidBody *mybody = GetRigidBody(); + float radius = mybody->GetRadius(); + UVector3 mydim(radius, radius, radius); + Dynamics::Collision::Geometry explosion_sphere(UMath::Matrix4::kIdentity, mybody->GetPosition(), + mydim, Dynamics::Collision::Geometry::SPHERE, + UMath::Vector3::kZero); + + for (unsigned int i = 0; i < 0xA0; i++) { + if (!cmap->CollisionWithOrderedBody(i)) { + continue; + } + IRigidBody *irb = cmap->GetOrderedBody(i); + if (!irb) { + continue; + } + if (!mEffectSource) { + IRenderable *irender; + if (irb->QueryInterface(&irender) && irender->GetModelHandle() == mSource) { + continue; + } + } + OnCollide(irb, dT, radius, explosion_sphere); + } + } + } +} + +void Explosion::OnTaskSimulate(float dT) { + IRigidBody *irb = GetRigidBody(); + TestCollisions(dT); + float radius = irb->GetRadius(); + if (mExpansionSpeed * dT + radius > mExpansionRadius) { + Kill(); + } else { + irb->SetRadius(mExpansionSpeed * dT + radius); + } +} diff --git a/src/Speed/Indep/Src/Physics/Common/PVehicle.cpp b/src/Speed/Indep/Src/Physics/Common/PVehicle.cpp index e69de29bb..c55205d2c 100644 --- a/src/Speed/Indep/Src/Physics/Common/PVehicle.cpp +++ b/src/Speed/Indep/Src/Physics/Common/PVehicle.cpp @@ -0,0 +1,1563 @@ +#include "Speed/Indep/Src/Physics/PVehicle.h" + +#include + +#include "Speed/Indep/Src/AI/AITarget.h" +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/FE/FECustomizationRecord.h" +#include "Speed/Indep/Src/Generated/Events/EPerfectLaunch.hpp" +#include "Speed/Indep/Src/Generated/Events/EPlayerAirborne.hpp" +#include "Speed/Indep/Src/Generated/Messages/MJumpCut.h" +#include "Speed/Indep/Src/Interfaces/ITaskable.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/ITrafficCenter.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/IArticulatedVehicle.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IDamageable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IEffects.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/IRecordablePlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRenderable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISpikeable.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISuspension.h" +#include "Speed/Indep/Src/Interfaces/Simables/ITransmission.h" +#include "Speed/Indep/Src/Misc/Config.h" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/Physics/Behavior.h" +#include "Speed/Indep/Src/Physics/Behaviors/DamageVehicle.h" +#include "Speed/Indep/Src/Physics/Behaviors/RigidBody.h" +#include "Speed/Indep/Src/Physics/Bounds.h" +#include "Speed/Indep/Src/Physics/Common/VehicleSystem.h" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" +#include "Speed/Indep/Src/Sim/SimSurface.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/Sim/Util.h" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WWorldPos.h" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +HINTERFACE IRaceEngine::_IHandle() { + return (HINTERFACE)_IHandle; +} + +class OnlineRacer; + +class IOnlinePlayer : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { return (HINTERFACE)_IHandle; } + IOnlinePlayer(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + virtual ~IOnlinePlayer() {} + virtual void SetOnlineRacer(); + virtual OnlineRacer *GetOnlineRacer(); + virtual void SendSetVehicleOnGround(); +}; + +struct CarPartDatabase { + CarType GetCarType(unsigned int hash); +}; +extern CarPartDatabase CarPartDB; +bool CarInfo_IsSkinned(CarType type); +unsigned int CarInfo_GetResourceCost(CarType type, bool is_player, bool split_screen); +unsigned int CarInfo_GetResourcePool(bool needs_compositing); +bool IsSplitScreen(); +PresetCar *FindFEPresetCar(unsigned int hash); +int GetMikeMannBuild(); +bool CanSpawnRigidBody(const UMath::Vector3 &position, bool highPriority); + +namespace Physics { namespace Upgrades { +void RemoveJunkman(Attrib::Gen::pvehicle &vehicle, Type type); +void RemovePart(Attrib::Gen::pvehicle &vehicle, Type type); +int GetLevel(const Attrib::Gen::pvehicle &vehicle, Type type); +int GetMaxLevel(const Attrib::Gen::pvehicle &vehicle, Type type); +bool SetLevel(Attrib::Gen::pvehicle &vehicle, Type type, int level); +bool MatchPerformance(Attrib::Gen::pvehicle &vehicle, const Physics::Info::Performance &matched_performance); +}; }; + +extern Attrib::StringKey BEHAVIOR_MECHANIC_AI; +extern Attrib::StringKey BEHAVIOR_MECHANIC_AUDIO; +extern Attrib::StringKey BEHAVIOR_MECHANIC_DAMAGE; +extern Attrib::StringKey BEHAVIOR_MECHANIC_DRAW; +extern Attrib::StringKey BEHAVIOR_MECHANIC_EFFECTS; +extern Attrib::StringKey BEHAVIOR_MECHANIC_ENGINE; +extern Attrib::StringKey BEHAVIOR_MECHANIC_INPUT; +extern Attrib::StringKey BEHAVIOR_MECHANIC_RESET; +extern Attrib::StringKey BEHAVIOR_MECHANIC_RIGIDBODY; +extern Attrib::StringKey BEHAVIOR_MECHANIC_SUSPENSION; + +extern bool Tweak_UseTweakerTunings; +extern float Tweak_TuningAero; + +namespace CameraAI { +void AddAvoidable(IBody *body); +void RemoveAvoidable(IBody *body); +} // namespace CameraAI + +bool CanInstancesShareResourceCost(CarType type); + +struct AIBehaviors { + DriverClass dclass; + UCrc32 vclass; + UCrc32 signature; +}; +extern AIBehaviors ai_behaviors[]; + +inline PVehicle::LaunchState::LaunchState() + : Time(0.0f), // + Amount(0.0f) {} + +inline void PVehicle::LaunchState::Clear() { + Time = 0.0f; + Amount = 0.0f; +} + +inline bool PVehicle::LaunchState::IsSet() const { + return Time > 0.0f; +} + +inline void PVehicle::LaunchState::Set(float time) { + Time = time; +} + +inline void PVehicle::LaunchState::Tick(float dT) { + Time -= dT; +} + +const ISimable *PVehicle::GetSimable() const { return static_cast(this); } + +ISimable *PVehicle::GetSimable() { return static_cast(this); } + +float PVehicle::GetSpeed() const { return mSpeed; } + +void PVehicle::GlareOn(VehicleFX::ID glare) { mGlareState |= glare; } + +void PVehicle::GlareOff(VehicleFX::ID glare) { mGlareState &= ~glare; } + +void PVehicle::DebugObject() { PhysicsObject::DebugObject(); } + +void PVehicle::OnAttributeChange(const Attrib::Collection *collection, unsigned int attribkey) {} + +void PVehicle::OnEnableModeling() {} + +void PVehicle::DoDebug(float dT) {} + +void PVehicle::OnDebugDraw() {} + +void PVehicle::CommitBehaviorOverrides() { + if (mOverrideDirty) { + mOverrideDirty = false; + ReloadBehaviors(); + } +} + +void PVehicle::Reset() { + mBehaviors.Reset(); + mTimeInAir = 0.0f; + mWheelsOnGround = 0; + mBrakeTime = 0.0f; +} + +void PVehicle::SetDriverClass(DriverClass cclass) { + if (mDriverClass != cclass) { + mDriverClass = cclass; + UpdateListing(); + ReloadBehaviors(); + } +} + +void PVehicle::Activate() { + if (mPhysicsMode == PHYSICS_MODE_INACTIVE) { + SetPhysicsMode(PHYSICS_MODE_SIMULATED); + } +} + +void PVehicle::Deactivate() { SetPhysicsMode(PHYSICS_MODE_INACTIVE); } + +void PVehicle::Kill() { + PhysicsObject::Kill(); + ReleaseBehavior(UCrc32(BEHAVIOR_MECHANIC_DRAW)); + ReleaseBehavior(UCrc32(BEHAVIOR_MECHANIC_AUDIO)); + mResources.Invalidate(); +} + +void PVehicle::ForceStopOn(char forceStopBits) { + mForceStop |= forceStopBits; + if (mInput != nullptr) { + mInput->ClearInput(); + } +} + +void PVehicle::ForceStopOff(char forceStopBits) { + mForceStop &= ~forceStopBits; + if (mInput != nullptr) { + mInput->ClearInput(); + } +} + +void PVehicle::OnDisableModeling() { + IEffects *ieff; + if (static_cast(this)->QueryInterface(&ieff)) { + ieff->Purge(); + } +} + +IModel *PVehicle::GetModel() { + if (mRenderable != nullptr) { + return mRenderable->GetModel(); + } else { + return nullptr; + } +} + +const IModel *PVehicle::GetModel() const { + if (mRenderable == nullptr) { + return nullptr; + } + return mRenderable->GetModel(); +} + +void PVehicle::SetPhysicsMode(PhysicsMode mode) { + if (mode != mPhysicsMode) { + OnEndMode(mPhysicsMode); + mPhysicsMode = mode; + OnBeginMode(mode); + } + if (mArticulation != nullptr) { + IVehicle *itrailer = mArticulation->GetTrailer(); + if (itrailer != nullptr) { + itrailer->SetPhysicsMode(mode); + } + } +} + +void PVehicle::SetTunings(const Physics::Tunings &tunings) { + if (mCustomization == nullptr) { + return; + } + for (unsigned int i = 0; i < Physics::Tunings::MAX_TUNINGS; i++) { + Physics::Tunings::Path path = static_cast(i); + mCustomization->SetTuning(path, tunings.Value[i]); + } +} + +bool PVehicle::OnTask(HSIMTASK htask, float dT) { + ProfileNode profile_node("OnTask", 0); + if (mTaskFX == htask) { + OnTaskFX(dT); + return true; + } + return PhysicsObject::OnTask(htask, dT); +} + +void PVehicle::SetStaging(bool staging) { + if (static_cast(staging) != static_cast(mStaging)) { + mStaging = staging; + if (staging) { + SetSpeed(0.0f); + } + } +} + +void PVehicle::SetDriverStyle(DriverStyle style) { + if (mDriverStyle != style) { + mDriverStyle = style; + ReloadBehaviors(); + if (mDriverStyle == STYLE_RACING && mEngine != nullptr) { + mEngine->MatchSpeed(mLocalVel.z); + } + } +} + +void PVehicle::Launch() { + if (mEngine == nullptr) { + return; + } + if (mPerfectLaunch.IsSet()) { + return; + } + if (mDriverClass == DRIVER_HUMAN) { + if (mPerfectLaunch.Amount > 0.0f) { + mPerfectLaunch.Set(10.0f); + new EPerfectLaunch(ISimable::GetInstanceHandle(), mPerfectLaunch.Amount); + } + } else { + mPerfectLaunch.Clear(); + } +} + +float PVehicle::GetPerfectLaunch() const { + if (mPerfectLaunch.IsSet()) {} + if (!IsStaging() && 0.5f < mPerfectLaunch.Time) { + return mPerfectLaunch.Amount; + } + return 0.5f; +} + +bool PVehicle::IsLoading() const { + if (mRenderable != nullptr && !mRenderable->IsRenderable()) { + return true; + } + if (mAudible != nullptr && !mAudible->IsAudible()) { + return true; + } + if (mArticulation != nullptr) { + IVehicle *trailer = mArticulation->GetTrailer(); + if (trailer != nullptr) { + return trailer->IsLoading(); + } + } + return false; +} + +void PVehicle::ReloadBehaviors() { + UMath::Vector3 pos; + UMath::Matrix4 mat; + pos = static_cast(this)->GetRigidBody()->GetPosition(); + static_cast(this)->GetRigidBody()->GetMatrix4(mat); + LoadBehaviors(pos, mat); +} + +void PVehicle::SetAnimating(bool animate) { + if (static_cast(animate) != static_cast(mAnimating)) { + mBehaviorOverrides.clear(); + mAnimating = animate; + ReloadBehaviors(); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_AI), animate); + if (mCollisionBody != nullptr) { + mCollisionBody->SetAnimating(mAnimating); + } + } +} + +void PVehicle::SetBehaviorOverride(UCrc32 mechanic, UCrc32 behavior) { + Behavior *beh = FindBehavior(mechanic); + if (beh == nullptr || beh->GetSignature() != behavior) { + mBehaviorOverrides[mechanic] = behavior; + mOverrideDirty = true; + } +} + +void PVehicle::RemoveBehaviorOverride(UCrc32 mechanic) { + UTL::Std::map::iterator iter = mBehaviorOverrides.find(mechanic); + if (iter != mBehaviorOverrides.end()) { + mBehaviorOverrides.erase(iter); + mOverrideDirty = true; + } +} + +void PVehicle::OnBehaviorChange(const UCrc32 &mechanic) { + PhysicsObject::OnBehaviorChange(mechanic); + if (mechanic == UCrc32(BEHAVIOR_MECHANIC_AI)) { + static_cast(this)->QueryInterface(&mAI); + } else if (mechanic == UCrc32(BEHAVIOR_MECHANIC_INPUT)) { + static_cast(this)->QueryInterface(&mInput); + } else if (mechanic == UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY)) { + if (static_cast(this)->QueryInterface(&mCollisionBody)) { + mCollisionBody->SetAnimating(mAnimating); + } + static_cast(this)->QueryInterface(&mArticulation); + } else if (mechanic == UCrc32(BEHAVIOR_MECHANIC_DRAW)) { + static_cast(this)->QueryInterface(&mRenderable); + } else if (mechanic == UCrc32(BEHAVIOR_MECHANIC_AUDIO)) { + static_cast(this)->QueryInterface(&mAudible); + } else if (mechanic == UCrc32(BEHAVIOR_MECHANIC_SUSPENSION)) { + if (!static_cast(this)->QueryInterface(&mSuspension)) { + return; + } + if (mCollisionBody == nullptr) { + return; + } + { + float speed = UMath::Dot(mCollisionBody->GetForwardVector(), + static_cast(this)->GetRigidBody()->GetLinearVelocity()); + mSuspension->MatchSpeed(speed); + } + } else if (mechanic == UCrc32(BEHAVIOR_MECHANIC_ENGINE)) { + static_cast(this)->QueryInterface(&mTranny); + if (!static_cast(this)->QueryInterface(&mEngine)) { + return; + } + mEngine->ChargeNOS(mStartingNOS - mEngine->GetNOSCapacity()); + if (mCollisionBody == nullptr) { + return; + } + { + float speed = UMath::Dot(mCollisionBody->GetForwardVector(), + static_cast(this)->GetRigidBody()->GetLinearVelocity()); + mEngine->MatchSpeed(speed); + } + } else if (mechanic == UCrc32(BEHAVIOR_MECHANIC_DAMAGE)) { + static_cast(this)->QueryInterface(&mDamage); + } +} + +void PVehicle::SetSpeed(float speed) { + mSpeed = speed; + mPerfectLaunch.Clear(); + if (mCollisionBody != nullptr) { + UMath::Vector3 vel; + UMath::Scale(mCollisionBody->GetForwardVector(), speed, vel); + static_cast(this)->GetRigidBody()->SetLinearVelocity(vel); + if (mSuspension != nullptr) { + mSuspension->MatchSpeed(speed); + } + if (mEngine != nullptr) { + mEngine->MatchSpeed(speed); + } + UpdateLocalVelocities(); + if (mArticulation != nullptr && mArticulation->GetTrailer() != nullptr) { + mArticulation->GetTrailer()->SetSpeed(speed); + } + } +} + +void PVehicle::UpdateLocalVelocities() { + IRigidBody *rigidbody = static_cast(this)->GetRigidBody(); + if (rigidbody == nullptr || mCollisionBody == nullptr) { + UMath::Clear(mLocalVel); + mSlipAngle = 0.0f; + mSpeed = 0.0f; + mAbsSpeed = 0.0f; + } else { + mLocalVel = rigidbody->GetLinearVelocity(); + rigidbody->ConvertWorldToLocal(mLocalVel, false); + mSlipAngle = UMath::Atan2a(mLocalVel.x, UMath::Abs(mLocalVel.z)); + mSpeed = rigidbody->GetSpeed(); + if (UMath::Dot(mCollisionBody->GetForwardVector(), rigidbody->GetLinearVelocity()) < 0.0f) { + mSpeed = -mSpeed; + } + mAbsSpeed = UMath::Abs(mSpeed); + } +} + +void PVehicle::DoStaging(float dT) { + if (!mPerfectLaunch.IsSet()) { + mPerfectLaunch.Amount = 0.0f; + if (mEngine != nullptr) { + IRaceEngine *raceEngine = reinterpret_cast( + (*reinterpret_cast(mEngine))->_mInterfaces.Find((HINTERFACE)IRaceEngine::_IHandle) + ); + bool hasRaceEngine = raceEngine != nullptr; + if (hasRaceEngine) { + float range = 0.0f; + float peak_rpm = raceEngine->GetPerfectLaunchRange(range); + if (range > 0.0f && peak_rpm > 0.0f) { + float rpm = mEngine->GetRPM(); + float dist = rpm - peak_rpm; + if (dist < range && dist > 0.0f) { + mPerfectLaunch.Amount = (dist / range) * 0.5f + 0.5f; + } + } + } + } + } +} + +void PVehicle::ComputeHeading(UMath::Vector3 *v) { + UMath::Vector3 forward; + UMath::Vector3 velocity; + IRigidBody *rigid_body = static_cast(this)->GetRigidBody(); + float speed = rigid_body->GetSpeed(); + rigid_body->GetForwardVector(forward); + velocity = rigid_body->GetLinearVelocity(); + if (speed > 0.01f) { + UMath::Unit(velocity, velocity); + } + float velocity_blend = UMath::Clamp(speed, 0.0f, 1.0f); + float forward_blend = 1.0f - velocity_blend; + UMath::Scale(forward, forward_blend, forward); + UMath::ScaleAdd(velocity, velocity_blend, forward, forward); + UMath::Unit(forward, *v); +} + +void PVehicle::CheckOffWorld() { + UCrc32 susp(BEHAVIOR_MECHANIC_SUSPENSION); + if (IsBehaviorActive(susp)) { + if (static_cast(this)->GetWPos().GetSurface() != + SimSurface::kNull.GetConstCollection()) { + goto set_false; + } + if (mSuspension == nullptr) { + goto set_true; + } + { + unsigned int invalid_tires = 0; + for (unsigned int i = 0; i < mSuspension->GetNumWheels(); i++) { + if (mSuspension->GetWheelRoadSurface(i).GetConstCollection() == + SimSurface::kNull.GetConstCollection()) { + invalid_tires++; + } + } + mOffWorld = !(invalid_tires < 2); + } + goto done; + set_true: + mOffWorld = true; + goto done; + set_false: + mOffWorld = false; + } else { + float worldHeight = 0.0f; + WCollisionMgr mgr(0, 3); + const UMath::Vector3 &pos = static_cast(this)->GetPosition(); + mOffWorld = !mgr.GetWorldHeightAtPointRigorous(pos, worldHeight, nullptr); + } +done:; +} + +void PVehicle::OnTaskSimulate(float dT) { + if (IsBehaviorActive(UCrc32(BEHAVIOR_MECHANIC_DRAW)) && mRenderable != nullptr && mRenderable->IsRenderable()) { + if (!mRenderable->InView()) { + mOffScreenTime = mOffScreenTime + dT; + mOnScreenTime = 0.0f; + } else { + mOffScreenTime = 0.0f; + mOnScreenTime = mOnScreenTime + dT; + } + } else { + mOffScreenTime = 0.0f; + mOnScreenTime = 0.0f; + } + CommitBehaviorOverrides(); + DoDebug(dT); + if (mCollisionBody != nullptr) { + if (mCollisionBody->IsModeling() != mIsModeling) { + mIsModeling = mCollisionBody->IsModeling(); + if (mIsModeling) { + OnEnableModeling(); + } else { + OnDisableModeling(); + } + } else { + mIsModeling = mCollisionBody->IsModeling(); + } + } + if (mPhysicsMode != PHYSICS_MODE_INACTIVE) { + UpdateLocalVelocities(); + CheckOffWorld(); + } + if (mPhysicsMode == PHYSICS_MODE_SIMULATED) { + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_SUSPENSION), + mCollisionBody->IsSleeping() && IsDestroyed()); + if (mTranny != nullptr) { + if (!mTranny->IsGearChanging()) { + mSpeedometer = mTranny->GetSpeedometer(); + } + } else { + mSpeedometer = mAbsSpeed; + } + unsigned int num_onground = 0; + if (mSuspension != nullptr) { + num_onground = mSuspension->GetNumWheelsOnGround(); + if (num_onground == 0 && mWheelsOnGround != 0 && mDriverClass == DRIVER_HUMAN) { + new EPlayerAirborne(ISimable::GetInstanceHandle()); + } + mWheelsOnGround = num_onground; + } + if (mSuspension == nullptr || num_onground == 0) { + mTimeInAir = mTimeInAir + dT; + } else { + mTimeInAir = 0.0f; + } + if (IsStaging()) { + DoStaging(dT); + } else { + if (mPerfectLaunch.IsSet()) { + if (mTranny != nullptr) { + if (!mTranny->IsGearChanging()) { + mPerfectLaunch.Time -= dT; + if (mPerfectLaunch.Time <= 0.0f) { + mPerfectLaunch.Amount = 0.0f; + mPerfectLaunch.Time = 0.0f; + } + } else { + if (mDriverStyle == STYLE_DRAG) { + mPerfectLaunch.Time = 0.0f; + mPerfectLaunch.Amount = 0.0f; + } + } + if (GetSpeed() > MPH2MPS(60.0f)) { + mPerfectLaunch.Clear(); + } + } + } + } + } else if (mPhysicsMode == PHYSICS_MODE_EMULATED) { + mTimeInAir = 0.0f; + if (mSuspension != nullptr) { + mWheelsOnGround = mSuspension->GetNumWheels(); + } else { + mWheelsOnGround = 0; + } + mSpeedometer = mAbsSpeed; + } else { + mWheelsOnGround = 0; + mTimeInAir = 0.0f; + mSpeedometer = 0.0f; + } +} + +bool CanInstancesShareResourceCost(CarType type) { + CarUsageType usage_type = GetCarTypeInfo(type)->GetCarUsageType(); + if (usage_type == CAR_USAGE_TYPE_COP) { + return true; + } + return usage_type == CAR_USAGE_TYPE_TRAFFIC; +} + +void PVehicle::CleanResources() { + for (PVehicle *dirty = mInstances.GetHead(); dirty != mInstances.EndOfList();) { + PVehicle *next = dirty->GetNext(); + if (dirty != nullptr && dirty->IsDirty()) { + delete dirty; + } + dirty = next; + } +} + +const Physics::Tunings *PVehicle::GetTunings() const { + if (GetDriverClass()) { + return nullptr; + } + if (Tweak_UseTweakerTunings) { + static Physics::Tunings tunings; + tunings.Value[Physics::Tunings::STEERING] = 0.0f; + tunings.Value[Physics::Tunings::HANDLING] = 0.0f; + tunings.Value[Physics::Tunings::BRAKES] = 0.0f; + tunings.Value[Physics::Tunings::RIDEHEIGHT] = 0.0f; + tunings.Value[Physics::Tunings::AERODYNAMICS] = Tweak_TuningAero; + tunings.Value[Physics::Tunings::NOS] = 0.0f; + tunings.Value[Physics::Tunings::INDUCTION] = 0.0f; + return &tunings; + } + if (mCustomization != nullptr) { + return static_cast(mCustomization)->GetTunings(); + } + return nullptr; +} + +unsigned int PVehicle::CountResources() { + unsigned int total_resources = 0; + UTL::Std::list resource_list; + for (PVehicle *vehicle = mInstances.GetHead(); vehicle != mInstances.EndOfList(); + vehicle = vehicle->GetNext()) { + bool found = false; + for (UTL::Std::list::const_iterator iter = resource_list.begin(); + iter != resource_list.end(); iter++) { + const Resource &resource = *iter; + if (resource.Type == vehicle->mResources.Type) { + found = true; + break; + } + } + int cost = 0; + if (!(found && CanInstancesShareResourceCost(vehicle->mResources.Type))) { + resource_list.push_back(vehicle->mResources); + cost = vehicle->mResources.Cost; + } + total_resources += static_cast(cost); + } + return total_resources; +} + +void PVehicle::OnTaskFX(float dT) { + IRigidBody &rigidBody = *static_cast(this)->GetRigidBody(); + if (&rigidBody) {} + { + if (INIS::Get() == nullptr) { + GlareOn(static_cast(7)); + } + bool do_brake = false; + if (mInput != nullptr) { + do_brake = mInput->GetControls().fBrake > 0.0f; + } + if (!static_cast(this)->IsPlayer()) { + if (do_brake) { + mBrakeTime = mBrakeTime + dT; + } else { + mBrakeTime = 0.0f; + } + do_brake = mBrakeTime > 0.5f; + } + if (do_brake) { + GlareOn(static_cast(0x38)); + } else { + GlareOff(static_cast(0x38)); + } + } + if (mTranny != nullptr && mTranny->IsReversing()) { + GlareOn(static_cast(0xc0)); + } else { + GlareOff(static_cast(0xc0)); + } +} + +void PVehicle::OnBeginMode(const PhysicsMode mode) { + if (mode == PHYSICS_MODE_INACTIVE) { + IVehicle::AddToList(VEHICLE_INACTIVE); + if (mCollisionBody != nullptr) { + mCollisionBody->DisableTriggering(); + } + Reset(); + } else if (mode == PHYSICS_MODE_EMULATED) { + } else if (mode == PHYSICS_MODE_SIMULATED) { + if (mCollisionBody != nullptr) { + mCollisionBody->EnableModeling(); + } + CameraAI::AddAvoidable(static_cast(this)); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_DRAW), false); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_SUSPENSION), false); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_ENGINE), false); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), false); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_EFFECTS), false); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_RESET), false); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_AUDIO), false); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_DAMAGE), false); + } +} + +void PVehicle::OnEndMode(const PhysicsMode mode) { + if (mode == PHYSICS_MODE_INACTIVE) { + if (mCollisionBody != nullptr) { + mCollisionBody->EnableTriggering(); + } + Reset(); + IVehicle::UnList(VEHICLE_INACTIVE); + } else if (mode == PHYSICS_MODE_EMULATED) { + } else if (mode == PHYSICS_MODE_SIMULATED) { + if (mCollisionBody != nullptr) { + mCollisionBody->DisableModeling(); + } + CameraAI::RemoveAvoidable(static_cast(this)); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_DRAW), true); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_SUSPENSION), true); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_ENGINE), true); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), true); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_EFFECTS), true); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_RESET), true); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_AUDIO), true); + PauseBehavior(UCrc32(BEHAVIOR_MECHANIC_DAMAGE), true); + } +} + + +bool PVehicle::SetDynamicData(const EventSequencer::System *system, EventDynamicData *data) { + if (IsDirty()) { + return false; + } + data->fhSimable = reinterpret_cast(ISimable::GetInstanceHandle()); + data->fWorldID = static_cast(this)->GetWorldID(); + IRigidBody *body = static_cast(this)->GetRigidBody(); + if (body != nullptr) { + const UMath::Vector3 &pos = body->GetPosition(); + data->fPosition = UMath::Vector4Make(pos, 0.0f); + UMath::Vector3 dir; + body->GetForwardVector(dir); + data->fVector = UMath::Vector4Make(dir, 0.0f); + const UMath::Vector3 &vel = body->GetLinearVelocity(); + data->fVelocity = UMath::Vector4Make(vel, 0.0f); + const UMath::Vector3 &angvel = body->GetAngularVelocity(); + data->fAngularVelocity = UMath::Vector4Make(angvel, 0.0f); + } + if (mRenderable != nullptr) { + data->fhModel = reinterpret_cast(mRenderable->GetModelHandle()); + } + return true; +} + +bool PVehicle::OnExplosion(const UMath::Vector3 &normal, const UMath::Vector3 &position, float dT, IExplosion *explosion) { + unsigned int targets = explosion->GetTargets(); + if ((targets & 2) == 0) { + return false; + } + if (static_cast(this)->IsPlayer()) { + if ((targets & 4) == 0) { + return false; + } + } + if (static_cast(this)->GetCausality() == nullptr && explosion->GetCausality() != nullptr) { + ICause *cause = ICause::FindInstance(explosion->GetCausality()); + if (cause != nullptr) { + cause->OnCausedExplosion(explosion, static_cast(this)); + } + } + IRigidBody *irb = static_cast(this)->GetRigidBody(); + float factor; + float targetspeed = 1.0f / irb->GetMass(); + factor = explosion->GetExpansionSpeed() * targetspeed; + targetspeed = factor; + UMath::Vector3 point_velocity; + irb->GetPointVelocity(position, point_velocity); + float speed = UMath::Dot(point_velocity, normal); + if (speed < targetspeed) { + float deltaspeed = targetspeed - speed; + UMath::Vector3 impactvel; + UMath::Scale(normal, deltaspeed, impactvel); + UMath::Vector3 force; + UMath::Scale(impactvel, irb->GetMass() / dT, force); + irb->ResolveForce(force, position); + } + if (mSequencer != nullptr) { + mSequencer->ProcessStimulus(0xab556d39, Sim::GetTime(), nullptr, EventSequencer::QUEUE_ALLOW); + if (explosion->HasDamage()) { + mSequencer->ProcessStimulus(0xffcd8a63, Sim::GetTime(), nullptr, EventSequencer::QUEUE_ALLOW); + } + } + return true; +} + +void PVehicle::UpdateListing() { + for (unsigned int i = 1; i < VEHICLE_MAX; i++) { + IVehicle::UnList(static_cast(i)); + } + switch (mDriverClass) { + case DRIVER_HUMAN: + IVehicle::AddToList(VEHICLE_PLAYERS); + IVehicle::AddToList(VEHICLE_RACERS); + break; + case DRIVER_REMOTE: + IVehicle::AddToList(VEHICLE_PLAYERS); + IVehicle::AddToList(VEHICLE_RACERS); + IVehicle::AddToList(VEHICLE_REMOTE); + break; + case DRIVER_COP: + IVehicle::AddToList(VEHICLE_AI); + IVehicle::AddToList(VEHICLE_AICOPS); + break; + case DRIVER_RACER: + IVehicle::AddToList(VEHICLE_AI); + IVehicle::AddToList(VEHICLE_AIRACERS); + IVehicle::AddToList(VEHICLE_RACERS); + break; + case DRIVER_TRAFFIC: + IVehicle::AddToList(VEHICLE_AI); + IVehicle::AddToList(VEHICLE_AITRAFFIC); + break; + default: + break; + } + if (!IsActive()) { + IVehicle::AddToList(VEHICLE_INACTIVE); + } + if (mClass == VehicleClass::TRAILER) { + IVehicle::AddToList(VEHICLE_TRAILERS); + } +} + +PVehicle::Resource::Resource(const Attrib::Gen::pvehicle &pvehicle, bool spool, bool is_player) { + Flags = 0; + CarPartDatabase &db = CarPartDB; + const char *text = pvehicle.MODEL().GetString(); + if (text == nullptr) { + text = ""; + } + CarType type = db.GetCarType(bStringHash(text)); + Type = type; + if (type != CARTYPE_NONE && type < NUM_CARTYPES) { + if (CarInfo_IsSkinned(type)) { + Flags |= 4; + } + bool split_screen = Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN; + Cost = CarInfo_GetResourceCost(Type, is_player, split_screen); + if (spool) { + Flags |= 2; + } + if (Cost != 0) { + Flags |= 1; + } + } +} + +PVehicle::PVehicle(DriverClass dc, const Attrib::Gen::pvehicle &attribs, const UMath::Vector3 &initialVec, + const UMath::Vector3 &initialPos, const CollisionGeometry::Bounds *bounds, + const FECustomizationRecord *customization, const Resource &resource, + const Physics::Info::Performance *performance, const char *cache_name) + : PhysicsObject(attribs.GetBase(), SIMABLE_VEHICLE, 0, 0x18) // + , bTNode() // + , IVehicle(this) // + , Debugable() // + , EventSequencer::IContext(this) // + , IExplodeable(this) // + , IAttributeable() // + , mAttributes(attribs) // + , mCustomization(nullptr) // + , mInput(nullptr) // + , mCollisionBody(nullptr) // + , mSuspension(nullptr) // + , mEngine(nullptr) // + , mDamage(nullptr) // + , mTranny(nullptr) // + , mAI(nullptr) // + , mArticulation(nullptr) // + , mRenderable(nullptr) // + , mAudible(nullptr) // + , mSequencer(nullptr) // + , mTaskFX(nullptr) // + , mClass() // + , mSpeed(0.0f) // + , mAbsSpeed(0.0f) // + , mSpeedometer(0.0f) // + , mTimeInAir(0.0f) // + , mSlipAngle(0.0f) // + , mWheelsOnGround(0) // + , mLocalVel(UMath::Vector3::kZero) // + , mDriverClass(dc) // + , mDriverStyle(STYLE_RACING) // + , mGlareState(0) // + , mStartingNOS(0.0f) // + , mBrakeTime(0.0f) // + , mForceStop(0) // + , mPhysicsMode(PHYSICS_MODE_SIMULATED) // + , mAnimating(false) // + , mStaging(false) // + , mPerfectLaunch() // + , mBehaviorOverrides() // + , mResources() // +{ + mBounds = bounds; + mOnScreenTime = 0.0f; + mOverrideDirty = false; + mIsModeling = true; + mOffScreenTime = 0.0f; + mOffWorld = false; + mHasDyno = false; + mResources = resource; + mPerformance.Default(); + mPerformanceValid = false; + mCacheName = cache_name; + if (performance != nullptr) { + mPerformance = *performance; + mPerformanceValid = true; + } + mInstances.AddTail(this); + AITarget::Register(static_cast(this)); + if (customization != nullptr) { + FECustomizationRecord *pFVar = static_cast(operator new(0x198)); + memcpy(pFVar, customization, 0x198); + mCustomization = pFVar; + } + mClass = mAttributes.CLASS(); + IVehicle::AddToList(VEHICLE_ALL); + UpdateListing(); + switch (mDriverClass) { + case DRIVER_HUMAN: + mTaskFX = AddTask("FX", 0.0f, 0.0f, Sim::TASK_FRAME_FIXED); + break; + case DRIVER_TRAFFIC: + mTaskFX = AddTask("FX", 0.05f, 0.0f, Sim::TASK_FRAME_FIXED); + break; + default: + mTaskFX = AddTask("FX", 0.02f, 0.0f, Sim::TASK_FRAME_FIXED); + break; + } + Debugable::MakeDebugable(DBG_PHYSICS_RACERS); + Reset(); + if (mDamage != nullptr) { + mDamage->ResetDamage(); + } + mGlareState = 0; + UMath::Matrix4 initMat; + initMat = Util_GenerateMatrix(initialVec, nullptr); + LoadBehaviors(initialPos, initMat); + SetOwnerObject(this); + const Attrib::StringKey seq(mAttributes.EventSequencer()); + if (seq.IsNotEmpty()) { + mSequencer = EventSequencer::Create(this, static_cast(this), + UCrc32(seq.GetString()), Sim::GetTime(), 0.0f); + } + OnBeginMode(PHYSICS_MODE_SIMULATED); +} + +PVehicle::~PVehicle() { + PhysicsObject::DetachAll(); + AITarget::UnRegister(static_cast(this)); + IAttributeable::UnRegister(this); + if (mCustomization != nullptr) { + delete static_cast(mCustomization); + mCustomization = nullptr; + } + ReleaseBehaviors(); + if (mTaskFX != nullptr) { + RemoveTask(mTaskFX); + mTaskFX = nullptr; + } + if (mSequencer != nullptr) { + mSequencer->Release(); + mSequencer = nullptr; + } + mInstances.Remove(this); + CameraAI::RemoveAvoidable(static_cast(this)); +} + +UCrc32 PVehicle::LookupBehaviorSignature(const Attrib::StringKey &mechanic) const { + if (mechanic == BEHAVIOR_MECHANIC_AUDIO) { + if (!IsSoundEnabled) { + return UCrc32::kNull; + } + } + UTL::Std::map::const_iterator iter = + mBehaviorOverrides.find(UCrc32(mechanic)); + if (iter != mBehaviorOverrides.end()) { + return (*iter).second; + } + if (mAnimating) { + if (mClass != VehicleClass::CHOPPER) { + if (mechanic == BEHAVIOR_MECHANIC_SUSPENSION) { + return UCrc32("SuspensionSpline"); + } + if (mechanic == BEHAVIOR_MECHANIC_ENGINE) { + return UCrc32("EngineSpline"); + } + } + if (mechanic == BEHAVIOR_MECHANIC_RESET) { + return UCrc32::kNull; + } + if (mechanic == BEHAVIOR_MECHANIC_INPUT) { + return UCrc32("InputNIS"); + } + } + if (mechanic == BEHAVIOR_MECHANIC_RIGIDBODY && mDriverClass == DRIVER_REMOTE) { + return UCrc32("RBRemote"); + } + if (mechanic == BEHAVIOR_MECHANIC_INPUT && mDriverClass == DRIVER_HUMAN) { + if (mDriverStyle == STYLE_DRAG) { + return UCrc32("InputPlayerDrag"); + } + return UCrc32("InputPlayer"); + } + if (mechanic == BEHAVIOR_MECHANIC_DAMAGE && mDriverStyle == STYLE_DRAG) { + return UCrc32("DamageDragster"); + } + if (mechanic == BEHAVIOR_MECHANIC_ENGINE && mClass == VehicleClass::CAR && + mDriverStyle == STYLE_DRAG) { + return UCrc32("EngineDragster"); + } + if (mechanic == BEHAVIOR_MECHANIC_SUSPENSION && mClass == VehicleClass::CAR) { + switch (mDriverClass) { + case DRIVER_RACER: + case DRIVER_NONE: + case DRIVER_REMOTE: + return UCrc32("SuspensionSimple"); + default: + break; + } + } + if (mechanic == BEHAVIOR_MECHANIC_AI) { + const AIBehaviors *aibehavior = ai_behaviors; + UCrc32 signature = aibehavior->signature; + while (aibehavior->signature != UCrc32::kNull) { + if (mDriverClass == aibehavior->dclass) { + if (mClass == aibehavior->vclass || aibehavior->vclass == UCrc32::kNull) { + signature = aibehavior->signature; + break; + } + } + aibehavior++; + } + return signature; + } + if (mechanic == BEHAVIOR_MECHANIC_EFFECTS) { + if (mDriverClass == DRIVER_HUMAN || + static_cast(this)->IsPlayer()) { + return UCrc32("EffectsPlayer"); + } + } + Attrib::Instance instance(nullptr, 0, nullptr); + Attrib::StringKey behaviourKey; + Attrib::Attribute atr = mAttributes.Get(static_cast(mechanic)); + if (atr.Get(0, behaviourKey)) { + } else { + return UCrc32::kNull; + } + return UCrc32(behaviourKey); +} + +void PVehicle::LoadBehaviors(const UMath::Vector3 &initialPos, const UMath::Matrix4 &initMat) { + if (IsDirty()) { + return; + } + ProfileNode profile_node("LoadBehaviors", 0); + if (mEngine != nullptr) { + mStartingNOS = mEngine->GetNOSCapacity(); + } + UMath::Vector3 linearVel; + memset(&linearVel, 0, sizeof(UMath::Vector3)); + UMath::Vector3 Dimension; + mBounds->GetHalfDimensions(Dimension); + unsigned int collision_mask = 0; + switch (mDriverClass) { + case DRIVER_HUMAN: + case DRIVER_RACER: + case DRIVER_REMOTE: + break; + case DRIVER_COP: + collision_mask = 0x80; + break; + default: + collision_mask |= 0x40; + break; + } + float mass = mAttributes.MASS(); + const UMath::Vector3 &tensorScale = UMath::Vector4To3(mAttributes.TENSOR_SCALE()); + UMath::Vector3 initMoment = Util_GenerateCarTensor(mass, Dimension.x, Dimension.y, Dimension.z, tensorScale); + + UCrc32 rbclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_RIGIDBODY); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), rbclass, + RBComplexParams(initialPos, linearVel, UMath::Vector3::kZero, initMat, mass, initMoment, Dimension, mBounds, true, collision_mask)); + + UCrc32 iclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_INPUT); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_INPUT), iclass, Sim::Param()); + + UCrc32 engine = LookupBehaviorSignature(BEHAVIOR_MECHANIC_ENGINE); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_ENGINE), engine, EngineParams()); + + UCrc32 suspclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_SUSPENSION); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_SUSPENSION), suspclass, SuspensionParams()); + + UCrc32 damageclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_DAMAGE); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_DAMAGE), damageclass, DamageParams()); + + UCrc32 drawclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_DRAW); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_DRAW), drawclass, Sim::Param()); + + UCrc32 audioclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_AUDIO); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_AUDIO), audioclass, Sim::Param()); + + UCrc32 aiclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_AI); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_AI), aiclass, AIParams()); + + UCrc32 effectsclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_EFFECTS); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_EFFECTS), effectsclass, Sim::Param()); + + UCrc32 resetclass = LookupBehaviorSignature(BEHAVIOR_MECHANIC_RESET); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RESET), resetclass, Sim::Param()); + + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY)); + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_INPUT)); + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_SUSPENSION)); + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_ENGINE)); + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_DAMAGE)); + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_EFFECTS)); +} + +bool PVehicle::SetVehicleOnGround(const UMath::Vector3 &resetPos, const UMath::Vector3 &initialVec) { + IRigidBody *rigid_body = static_cast(this)->GetRigidBody(); + if (rigid_body == nullptr || mCollisionBody == nullptr) { + return false; + } + bool success = true; + MJumpCut msg(static_cast(this)->GetWorldID()); + msg.Send(UCrc32("CameraMessagePort")); + const UMath::Vector3 dim = rigid_body->GetDimension(); + UMath::Vector3 position = resetPos; + position.y += dim.y; + UMath::Matrix4 orientMat = Util_GenerateMatrix(initialVec, nullptr); + rigid_body->SetLinearVelocity(UMath::Vector3::kZero); + rigid_body->SetAngularVelocity(UMath::Vector3::kZero); + float load = mCollisionBody->GetGravity() * rigid_body->GetMass(); + float worldHeight; + if (!WCollisionMgr(0, 3).GetWorldHeightAtPointRigorous(position, worldHeight, nullptr)) { + position = resetPos; + success = false; + } else if (mSuspension == nullptr) { + position = resetPos; + position.y = worldHeight + dim.y; + } else { + WWorldPos wpos(dim.y); + wpos.SetTolerance(1.0f); + UMath::Vector4 plane[4]; + UMath::Vector4 axle_center = UMath::Vector4::kZero; + position.y = worldHeight; + UMath::Vector4 p4 = UMath::Vector4Make(position, 1.0f); + for (unsigned int i = 0; i < 4; i++) { + UMath::Vector4 &this_corner = plane[i]; + const UMath::Vector3 &wheelPos = mSuspension->GetWheelLocalPos(i); + this_corner = UMath::Vector4Make(wheelPos, 0.0f); + this_corner.y = 0.0f; + UMath::ScaleAdd(this_corner, 0.25f, axle_center, axle_center); + UMath::Rotate(this_corner, orientMat, this_corner); + UMath::Add(this_corner, p4, this_corner); + wpos.FindClosestFace(UMath::Vector4To3(this_corner), true); + if (wpos.OnValidFace()) { + float compression = mSuspension->GuessCompression(i, load); + float ride = mSuspension->GetRideHeight(i); + this_corner.y = dim.y + wpos.HeightAtPoint(UMath::Vector4To3(this_corner)) + (ride - compression); + } else { + success = false; + } + } + if (!success) { + position = resetPos; + } else { + UMath::Vector4 front; + UMath::Vector4 rear; + UMath::Vector4 right; + UMath::Vector4 left; + UMath::Vector4 world_axle; + UMath::Vector4 p0; + UMath::Vector4 p1; + UMath::AddScale(plane[0], plane[1], 0.5f, front); + UMath::AddScale(plane[2], plane[3], 0.5f, rear); + UMath::AddScale(plane[0], plane[2], 0.5f, left); + UMath::AddScale(plane[1], plane[3], 0.5f, right); + UMath::Subxyz(front, rear, orientMat.v2); + UMath::Unitxyz(orientMat.v2); + UMath::Subxyz(right, left, orientMat.v0); + UMath::Unitxyz(orientMat.v0); + UnitCrossxyz(orientMat.v2, orientMat.v0, orientMat.v1); + UnitCrossxyz(orientMat.v1, orientMat.v2, orientMat.v0); + UMath::Rotate(axle_center, orientMat, world_axle); + AddScalexyz(front, rear, 0.5f, p0); + AddScalexyz(right, left, 0.5f, p1); + AddScalexyz(p0, p1, 0.5f, p4); + UMath::Subxyz(p4, world_axle, p4); + position = UMath::Vector4To3(p4); + } + } + rigid_body->PlaceObject(orientMat, position); + IPlayer *player = static_cast(this)->GetPlayer(); + if (player != nullptr) { + IOnlinePlayer *online = nullptr; + if (player->QueryInterface(&online)) { + online->SendSetVehicleOnGround(); + } + } + if (mArticulation != nullptr && !mArticulation->Pose()) { + success = false; + } + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_SUSPENSION)); + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_ENGINE)); + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_AI)); + ResetBehavior(UCrc32(BEHAVIOR_MECHANIC_RESET)); + static_cast(this)->SetSpeed(0.0f); + mOffWorld = !success; + AITarget::Track(static_cast(this)); + return success; +} + +ISimable *PVehicle::Construct(Sim::Param params) { + const VehicleParams vp = params.Fetch< VehicleParams >(UCrc32(0xa6b47fac)); + Attrib::Gen::pvehicle attributes(vp.carType, 0, nullptr); + if (!attributes.IsValid()) { + return nullptr; + } + const char *vehicle_name; + const FECustomizationRecord *customizations = vp.customization; + if (customizations == nullptr) { + vehicle_name = attributes.DefaultPresetRide(); + if (vehicle_name != nullptr) { + PresetCar *preset = FindFEPresetCar(bStringHashUpper(vehicle_name)); + if (preset != nullptr) { + static FECustomizationRecord temp_record; + temp_record.Default(); + temp_record.BecomePreset(preset); + customizations = &temp_record; + } + } + if (customizations == nullptr) { + return nullptr; + } + } + if (!customizations->WriteRecordIntoPhysics(attributes)) { + return nullptr; + } + if (vp.matched != nullptr + && !Physics::Upgrades::MatchPerformance(attributes, *vp.matched)) { + return nullptr; + } + if ((vp.Flags & 4) != 0) { + Physics::Upgrades::RemoveJunkman(attributes, Physics::Upgrades::PUT_NOS); + Physics::Upgrades::RemovePart(attributes, Physics::Upgrades::PUT_NOS); + } + if (GetMikeMannBuild() != 0) { + int new_nos = Physics::Upgrades::GetMaxLevel(attributes, Physics::Upgrades::PUT_NOS); + Physics::Upgrades::SetLevel(attributes, Physics::Upgrades::PUT_NOS, new_nos); + } + if ((vp.Flags & 0x10) != 0) { + if (Physics::Upgrades::GetLevel(attributes, Physics::Upgrades::PUT_NOS) == 0) { + if (Physics::Upgrades::GetMaxLevel(attributes, Physics::Upgrades::PUT_NOS) > 0) { + Physics::Upgrades::SetLevel(attributes, Physics::Upgrades::PUT_NOS, 1); + } + } + } + const CollisionGeometry::Collection *geoms = + CollisionGeometry::Lookup(UCrc32(attributes.MODEL())); + if (geoms == nullptr) { + return nullptr; + } + if (geoms->GetRoot() == nullptr) { + return nullptr; + } + bool spooling_resources = (vp.Flags & 1) != 0; + bool is_player = vp.carClass == DRIVER_HUMAN; + Resource resource(attributes, spooling_resources, is_player); + if (!resource.IsValid()) { + return nullptr; + } + UTL::Std::list< Resource, _type_list > resources; + resources.push_back(resource); + Attrib::RefSpec trailer_ref = attributes.Trailer(); + Physics::Info::Performance perf; + if (trailer_ref.GetCollectionKey() != 0) { + Attrib::Gen::pvehicle trailerAttribs(trailer_ref, 0, nullptr); + resources.push_back(Resource(trailerAttribs, spooling_resources, false)); + } + if (!MakeRoom(vp.VehicleCache, resources)) { + return nullptr; + } + resources.clear(); + perf.Default(); + const Physics::Info::Performance *performance = nullptr; + if (vp.matched != nullptr) { + performance = vp.matched; + } else if ((vp.Flags & 8) != 0) { + if (Physics::Info::ComputePerformance(attributes, perf)) { + performance = &perf; + } + } + if (CanSpawnRigidBody(vp.initialPos, true)) { + const char *cache_name; + PVehicle *vehicle; + if (vp.VehicleCache != nullptr) { + cache_name = vp.VehicleCache->GetCacheName(); + } else { + cache_name = nullptr; + } + vehicle = new PVehicle(vp.carClass, attributes, vp.initialVec, vp.initialPos, + geoms->GetRoot(), + customizations, resource, performance, cache_name); + if ((vp.Flags & 2) != 0) { + vehicle->SetVehicleOnGround(vp.initialPos, vp.initialVec); + } + if (vehicle != nullptr) { + return static_cast(vehicle); + } + } + return nullptr; +} + +bool PVehicle::MakeRoom(IVehicleCache *whosasking, const UTL::Std::list &resources) { + CleanResources(); + + unsigned int newinstances = resources.size(); + + unsigned int newresources = 0; + bool needs_compositing = false; + for (UTL::Std::list::const_iterator res_iter = resources.begin(); + res_iter != resources.end(); res_iter++) { + const Resource &resource = *res_iter; + if (resource.NeedsCompositing()) { + needs_compositing = true; + } + unsigned int cost = resource.Cost; + for (PVehicle *vehicle = mInstances.GetHead(); vehicle != mInstances.EndOfList(); + vehicle = vehicle->GetNext()) { + if (vehicle->mResources.Type == resource.Type) { + if (CanInstancesShareResourceCost(resource.Type)) { + cost = 0; + } + break; + } + } + newresources += cost; + } + + unsigned int resource_limit = CarInfo_GetResourcePool(needs_compositing); + unsigned int current_resources = CountResources(); + unsigned int current_instances = IVehicle::Count(VEHICLE_ALL); + unsigned int needed_resources = 0; + unsigned int needed_instances = 0; + if (current_resources + newresources > resource_limit) { + needed_resources = (current_resources + newresources) - resource_limit; + } + if (current_instances + newinstances > 10) { + needed_instances = (current_instances + newinstances) - 10; + } + + if (needed_resources == 0 && needed_instances == 0) { + return true; + } + if (whosasking == nullptr) { + return false; + } + + ManagementList vehicle_list; + vehicle_list.reserve(10); + + for (PVehicle *vehicle = mInstances.GetHead(); vehicle != mInstances.EndOfList(); + vehicle = vehicle->GetNext()) { + ManageNode node; + node.result = VCR_DONTCARE; + node.resource.Flags = 0; + node.instancecount = 0; + node.vehicle = vehicle; + node.resource = vehicle->mResources; + vehicle_list.push_back(node); + } + + for (ManageNode *node_iter = vehicle_list.begin(); + node_iter != vehicle_list.end(); ++node_iter) { + ManageNode &node = *node_iter; + if (node.result != VCR_WANT) { + node.result = whosasking->OnQueryVehicleCache(node.vehicle, whosasking); + } + } + + for (ManageNode *node_iter = vehicle_list.begin(); + node_iter != vehicle_list.end(); ++node_iter) { + ManageNode &node = *node_iter; + if (node.result != VCR_WANT) { + for (IVehicleCache *const *cache_iter = + UTL::Collections::Listable::GetList().begin(); + cache_iter != + UTL::Collections::Listable::GetList().end(); + ++cache_iter) { + IVehicleCache *cache = *cache_iter; + if (!UTL::COM::ComparePtr(cache, whosasking)) { + if (cache->OnQueryVehicleCache(node.vehicle, whosasking) == VCR_WANT) { + node.result = VCR_WANT; + break; + } + } + } + } + } + + std::sort(vehicle_list.begin(), vehicle_list.end(), ManageNode::sort_by_keep); + vehicle_list.print(); + + for (UTL::Std::list::const_iterator res_iter = resources.begin(); + res_iter != resources.end(); res_iter++) { + const Resource &resource = *res_iter; + for (ManageNode *node_iter = vehicle_list.begin(); + node_iter != vehicle_list.end(); ++node_iter) { + ManageNode &node = *node_iter; + if (resource.Type == node.resource.Type) { + node.result = VCR_WANT; + break; + } + } + } + + vehicle_list.print(); + + if (needed_resources != 0) { + CarType type = CARTYPE_NONE; + eVehicleCacheResult pushresult = VCR_DONTCARE; + for (ManageNode *node_iter = vehicle_list.begin(); + node_iter != vehicle_list.end(); ++node_iter) { + ManageNode &node = *node_iter; + CarType t = node.resource.Type; + if (t != type) { + pushresult = node.result; + type = t; + } + if (pushresult == VCR_WANT) { + node.result = VCR_WANT; + } + } + } + + ManageNode *end_iter = std::remove_if(vehicle_list.begin(), vehicle_list.end(), + ManageNode::is_kept); + vehicle_list.erase(end_iter, vehicle_list.end()); + + if (vehicle_list.size() == 0) { + return false; + } + + UTL::Std::map type_map; + for (ManageNode *node_iter = vehicle_list.begin(); + node_iter != vehicle_list.end(); ++node_iter) { + type_map[node_iter->resource.Type]++; + } + + for (ManageNode *node_iter = vehicle_list.begin(); + node_iter != vehicle_list.end(); ++node_iter) { + node_iter->instancecount = type_map[node_iter->resource.Type]; + } + + unsigned int found_instances = 0; + ManageNode *node_iter = vehicle_list.begin(); + if (needed_resources != 0) { + std::sort(vehicle_list.begin(), vehicle_list.end(), + ManageNode::sort_remove_resources); + vehicle_list.print(); + + unsigned int found_resources = 0; + CarType type = CARTYPE_NONE; + unsigned int type_cost = 0; + for (node_iter = vehicle_list.begin(); node_iter != vehicle_list.end(); + ++node_iter) { + CarType t = node_iter->resource.Type; + if (t != type) { + found_resources += type_cost; + type = t; + } + if (found_resources >= needed_resources) { + type_cost = 0; + break; + } + type_cost = node_iter->resource.Cost; + found_instances++; + } + + if (found_resources + type_cost < needed_resources) { + return false; + } + } + + if (found_instances < needed_instances) { + std::sort(node_iter, vehicle_list.end(), + ManageNode::sort_remove_instances); + vehicle_list.print(); + + for (; node_iter != vehicle_list.end(); ++node_iter) { + if (found_instances >= needed_instances) { + break; + } + found_instances++; + } + + if (found_instances < needed_instances) { + return false; + } + } + + vehicle_list.erase(node_iter, vehicle_list.end()); + + vehicle_list.print(); + + for (ManageNode *node_iter = vehicle_list.begin(); + node_iter != vehicle_list.end(); ++node_iter) { + ManageNode &node = *node_iter; + PVehicle *killit = node.vehicle; + for (IVehicleCache *const *citer = + UTL::Collections::Listable::GetList().begin(); + citer != + UTL::Collections::Listable::GetList().end(); + ++citer) { + IVehicleCache *cache = *citer; + cache->OnRemovedVehicleCache(killit); + } + if (killit != nullptr) { + delete killit; + } + } + + return true; +} + +template void UTL::Vector::push_back(ICollisionBody *const &); +template void UTL::Vector::push_back(IInputPlayer *const &); +template void UTL::Vector::push_back(IRecordablePlayer *const &); +template void UTL::Vector::push_back(ISpikeable *const &); +template UTL::Collections::Listable::List::~List(); +template UTL::Collections::Listable::List::~List(); +template Behavior *UTL::COM::Factory::CreateInstance(UCrc32, const BehaviorParams &); diff --git a/src/Speed/Indep/Src/Physics/Common/PhysicsObject.cpp b/src/Speed/Indep/Src/Physics/Common/PhysicsObject.cpp index e69de29bb..4b12a93aa 100644 --- a/src/Speed/Indep/Src/Physics/Common/PhysicsObject.cpp +++ b/src/Speed/Indep/Src/Physics/Common/PhysicsObject.cpp @@ -0,0 +1,489 @@ +#define ZPHYSICS_OUTLINE_IENTITY_HANDLE +#include "Speed/Indep/Src/Physics/PhysicsObject.h" + +#include "Speed/Indep/Src/Interfaces/SimEntities/IEntity.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/SimModels/IModel.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Main/AttribSupport.h" +#include "Speed/Indep/Src/Sim/Collision.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/WorldConn.h" + +#include + +HINTERFACE Sim::IEntity::_IHandle() { + return (HINTERFACE)_IHandle; +} + +HINTERFACE IBody::_IHandle() { + return (HINTERFACE)_IHandle; +} + +IBody::~IBody() {} + +unsigned int WorldConn::Pkt_Body_Open::Size() { + return sizeof(*this); +} + +unsigned int WorldConn::Pkt_Body_Open::Type() { + return SType(); +} + +unsigned int WorldConn::Pkt_Body_Open::SType() { + static UCrc32 hash = "Pkt_Body_Open"; + return hash.GetValue(); +} + +void PhysicsObject::Behaviors::Add(Behavior *beh) { + int pri = beh->GetPriority(); + iterator iter; + for (iter = begin(); iter != end(); ++iter) { + if ((*iter)->GetPriority() > pri) { + break; + } + } + insert(iter, beh); +} + +void PhysicsObject::Behaviors::Remove(Behavior *beh) { + erase(_STL::remove(begin(), end(), beh)); +} + +PhysicsObject::PhysicsObject(const Attrib::Instance &attribs, SimableType objType, WUID wuid, + unsigned int num_interfaces) + : Sim::Object(num_interfaces + 3) // + , ISimable(this) // + , IBody(this) // + , IAttachable(this) // + , mAttributes(attribs.GetConstCollection(), 0, nullptr) // +{ + mWPos = new WWorldPos(0.025f); + mObjType = objType; + mOwner = nullptr; + mRigidBody = nullptr; + mEntity = nullptr; + mPlayer = nullptr; + mBodyService = nullptr; + mWorldID = reinterpret_cast(GetInstanceHandle()) | 0x1000000; + if (wuid != 0) { + mWorldID = wuid; + } + mAttachments = new Sim::Attachments(static_cast(this)); + mSimulateTask = AddTask(UCrc32(stringhash32("Physics")), 1.0f, 0.0f, Sim::TASK_FRAME_FIXED); + Sim::ProfileTask(mSimulateTask, "Physics"); + Sim::Collision::AddParticipant(GetInstanceHandle()); +} + +PhysicsObject::PhysicsObject(const char *attributeClass, const char *attribName, SimableType objType, + HSIMABLE owner, WUID wuid) + : Sim::Object(13) // + , ISimable(this) // + , IBody(this) // + , IAttachable(this) // + , mAttributes(Attrib::FindCollectionWithDefault(Attrib::StringToKey(attributeClass), + Attrib::StringToKey(attribName)), + 0, nullptr) // +{ + mWPos = new WWorldPos(0.025f); + mObjType = objType; + mOwner = owner; + mRigidBody = nullptr; + mEntity = nullptr; + mPlayer = nullptr; + mBodyService = nullptr; + mWorldID = reinterpret_cast(GetInstanceHandle()) | 0x1000000; + if (wuid != 0) { + mWorldID = wuid; + } + mAttachments = new Sim::Attachments(static_cast(this)); + mSimulateTask = AddTask(UCrc32(stringhash32("Physics")), 1.0f, 0.0f, Sim::TASK_FRAME_FIXED); + Sim::ProfileTask(mSimulateTask, "Physics"); + Sim::Collision::AddParticipant(GetInstanceHandle()); +} + +PhysicsObject::~PhysicsObject() { + ReleaseBehaviors(); + RemoveTask(mSimulateTask); + DetachEntity(); + if (mAttachments) { + delete mAttachments; + mAttachments = nullptr; + } + if (mBodyService) { + CloseService(mBodyService); + mBodyService = nullptr; + } + if (mWPos) { + delete mWPos; + } + Sim::Collision::RemoveParticipant(GetInstanceHandle()); +} + +void PhysicsObject::GetTransform(UMath::Matrix4 &matrix) const { + if (mRigidBody != nullptr) { + mRigidBody->GetMatrix4(matrix); + matrix.v3 = UMath::Vector4Make(mRigidBody->GetPosition(), 1.0f); + } else { + UMath::Copy(UMath::Matrix4::kIdentity, matrix); + } +} + +void PhysicsObject::GetLinearVelocity(UMath::Vector3 &velocity) const { + if (mRigidBody != nullptr) { + UMath::Copy(mRigidBody->GetLinearVelocity(), velocity); + } else { + UMath::Copy(UMath::Vector3::kZero, velocity); + } +} + +void PhysicsObject::GetAngularVelocity(UMath::Vector3 &velocity) const { + if (mRigidBody != nullptr) { + UMath::Copy(mRigidBody->GetAngularVelocity(), velocity); + } else { + UMath::Copy(UMath::Vector3::kZero, velocity); + } +} + +void PhysicsObject::GetDimension(UMath::Vector3 &dim) const { + if (mRigidBody != nullptr) { + mRigidBody->GetDimension(dim); + } else { + UMath::Copy(UMath::Vector3::kZero, dim); + } +} + +float PhysicsObject::GetCausalityTime() const { + const IModel *model = GetModel(); + if (model != nullptr) { + return model->GetCausalityTime(); + } + return 0.0f; +} + +void PhysicsObject::SetCausality(HCAUSE from, float time) { + IModel *model = GetModel(); + if (model != nullptr) { + model->SetCausality(from, time); + } +} + +HCAUSE PhysicsObject::GetCausality() const { + const IModel *model = GetModel(); + if (model != nullptr) { + return model->GetCausality(); + } + return nullptr; +} + +void PhysicsObject::OnAttached(IAttachable *pOther) { + mBehaviors.OnAttach(pOther); +} + +void PhysicsObject::OnDetached(IAttachable *pOther) { + mBehaviors.OnDetach(pOther); +} + +bool PhysicsObject::OnService(HSIMSERVICE hConn, Sim::Packet *pkt) { + if (hConn == mBodyService) { + WorldConn::Pkt_Body_Service *pbs = static_cast(pkt); + UMath::Matrix4 mat; + mRigidBody->GetMatrix4(mat); + UMath::Copy(mRigidBody->GetPosition(), UMath::ExtractAxis(mat, 3)); + pbs->SetMatrix(mat); + pbs->SetVelocity(mRigidBody->GetLinearVelocity()); + return true; + } + return Sim::Object::OnService(hConn, pkt); +} + +bool PhysicsObject::OnTask(HSIMTASK htask, float dT) { + if (htask == mSimulateTask) { + if (!IsDirty()) { + OnTaskSimulate(dT); + mBehaviors.Simulate(dT); + } + return true; + } + return Sim::Object::OnTask(htask, dT); +} + +void PhysicsObject::Kill() { + ReleaseGC(); + if (mBodyService) { + CloseService(mBodyService); + mBodyService = nullptr; + } +} + +void PhysicsObject::SetOwnerObject(ISimable *pOwner) { + if (pOwner != nullptr) { + mOwner = pOwner->GetInstanceHandle(); + } else { + mOwner = nullptr; + } +} + +bool PhysicsObject::IsOwnedByPlayer() const { + ISimable *owner = ISimable::FindInstance(mOwner); + if (owner != nullptr) { + do { + if (owner->IsPlayer()) { + return true; + } + if (owner->GetOwnerHandle() == nullptr) { + return false; + } + if (owner->GetOwnerHandle() == owner->GetInstanceHandle()) { + return false; + } + ISimable *next = ISimable::FindInstance(owner->GetOwnerHandle()); + if (owner == next) { + return false; + } + owner = next; + } while (owner != nullptr); + } + return false; +} + +bool PhysicsObject::IsOwnedBy(ISimable *queriedOwner) const { + if (queriedOwner != nullptr) { + HSIMABLE qSig = queriedOwner->GetInstanceHandle(); + ISimable *potentialOwner = ISimable::FindInstance(mOwner); + if (potentialOwner != nullptr) { + do { + if (potentialOwner->GetInstanceHandle() == qSig) { + return true; + } + HSIMABLE ownerHandle = potentialOwner->GetOwnerHandle(); + if (ownerHandle == nullptr) { + return false; + } + ISimable *newOwner = ISimable::FindInstance(ownerHandle); + if (potentialOwner == newOwner) { + return false; + } + potentialOwner = newOwner; + } while (potentialOwner != nullptr); + } + } + return false; +} + +void PhysicsObject::DebugObject() { + GetRigidBody()->Debug(); +} + +void PhysicsObject::OnBehaviorChange(const UCrc32 &mechanic) { + if (mechanic == UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY)) { + IRigidBody *irb = nullptr; + static_cast(this)->QueryInterface(&irb); + mRigidBody = irb; + if (mBodyService == nullptr && irb != nullptr) { + UMath::Matrix4 mat; + mRigidBody->GetMatrix4(mat); + UMath::Copy(mRigidBody->GetPosition(), UMath::ExtractAxis(mat, 3)); + WorldConn::Pkt_Body_Open pkt(mWorldID, mat); + mBodyService = OpenService(UCrc32(0x998c21c0), &pkt); + } + } + mBehaviors.Changed(mechanic); +} + +bool PhysicsObject::IsBehaviorActive(const UCrc32 &mechanic) const { + unsigned int key = mechanic.GetValue(); + Mechanics::const_iterator iter = mMechanics.find(key); + if (iter == mMechanics.end()) { + return false; + } + Behavior *beh = (*iter).second; + if (beh == nullptr) { + return false; + } + return !beh->IsPaused(); +} + +void PhysicsObject::PauseBehavior(const UCrc32 &mechanic, bool pause) { + unsigned int key = mechanic.GetValue(); + if (mMechanics.find(key) == mMechanics.end()) { + return; + } + Behavior *beh = mMechanics[key]; + if (beh != nullptr) { + beh->Pause(pause); + } +} + +bool PhysicsObject::ResetBehavior(const UCrc32 &mechanic) { + unsigned int key = mechanic.GetValue(); + Behavior *beh; + if (mMechanics.find(key) == mMechanics.end()) { + } else { + beh = mMechanics[key]; + if (beh != nullptr) { + beh->Reset(); + return true; + } + } + return false; +} + +void PhysicsObject::ReleaseBehaviors() { + for (Mechanics::iterator iter = mMechanics.begin(); iter != mMechanics.end(); iter++) { + Behavior *beh = (*iter).second; + if (beh != nullptr) { + UCrc32 mechanic((*iter).first); + mBehaviors.Remove(beh); + DestroyElement(*beh); + (*iter).second = nullptr; + OnBehaviorChange(mechanic); + } + } +} + +void PhysicsObject::ReleaseBehavior(const UCrc32 &mechanic) { + unsigned int key = mechanic.GetValue(); + if (mMechanics.find(key) == mMechanics.end()) { + return; + } + Behavior *beh = mMechanics[key]; + if (beh != nullptr) { + mBehaviors.Remove(beh); + DestroyElement(*beh); + mMechanics[key] = nullptr; + OnBehaviorChange(mechanic); + } +} + +Behavior *PhysicsObject::FindBehavior(const UCrc32 &mechanic) { + unsigned int key = mechanic.GetValue(); + if (mMechanics.find(key) != mMechanics.end()) { + Behavior *beh = mMechanics[key]; + if (beh != nullptr) { + return beh; + } + } + return nullptr; +} + +Behavior *PhysicsObject::LoadBehavior(const UCrc32 &mechanic, const UCrc32 &behavior, + Sim::Param params) { + if (IsDirty()) { + return nullptr; + } + Behavior *beh = FindBehavior(mechanic); + if (beh != nullptr && beh->GetSignature() == behavior) { + return beh; + } + ReleaseBehavior(mechanic); + + if (behavior == UCrc32::kNull) { + return nullptr; + } + + unsigned int key = mechanic.GetValue(); + beh = BuildElement(behavior, BehaviorParams(params, this, mechanic, behavior)); + if (beh != nullptr) { + mMechanics[key] = beh; + mBehaviors.Add(beh); + OnBehaviorChange(mechanic); + return beh; + } + return nullptr; +} + +bool PhysicsObject::Attach(UTL::COM::IUnknown *object) { + if (mAttachments == nullptr) { + return false; + } + UTL::COM::ValidatePtr(object); + if (UTL::COM::ComparePtr(mEntity, object)) { + return false; + } + Sim::IEntity *ientity = reinterpret_cast( + (*reinterpret_cast(object))->_mInterfaces.Find((HINTERFACE)Sim::IEntity::_IHandle) + ); + bool hasIEntity = ientity != nullptr; + if (hasIEntity) { + if (mEntity != nullptr) { + Detach(mEntity); + mEntity = nullptr; + mPlayer = nullptr; + } + mEntity = ientity; + ientity->QueryInterface(&mPlayer); + } + return mAttachments->Attach(object); +} + +void PhysicsObject::DetachAll() { + DetachEntity(); + if (mAttachments) { + delete mAttachments; + mAttachments = nullptr; + } +} + +bool PhysicsObject::Detach(UTL::COM::IUnknown *object) { + if (!mAttachments) { + return false; + } + UTL::COM::ValidatePtr(object); + if (UTL::COM::ComparePtr(mEntity, object)) { + mEntity = nullptr; + mPlayer = nullptr; + } + return mAttachments->Detach(object); +} + +void PhysicsObject::DetachEntity() { + if (mEntity != nullptr) { + Detach(mEntity); + mPlayer = nullptr; + mEntity = nullptr; + } +} + +void PhysicsObject::AttachEntity(Sim::IEntity *e) { + if (e == nullptr) { + DetachEntity(); + } else { + Attach(e); + } +} + +void PhysicsObject::ProcessStimulus(unsigned int stimulus) { + if (GetEventSequencer() != nullptr) { + GetEventSequencer()->ProcessStimulus(stimulus, Sim::GetTime(), nullptr, EventSequencer::QUEUE_ALLOW); + } +} + +void PhysicsObject::Reset() { + mBehaviors.Reset(); +} + +WWorldPos &PhysicsObject::GetWPos() { + return *mWPos; +} + +const WWorldPos &PhysicsObject::GetWPos() const { + return *mWPos; +} + +IRigidBody *PhysicsObject::GetRigidBody() { + return mRigidBody; +} + + +void PhysicsObject::Behaviors::Reset() { + for (const_iterator iter = begin(); iter != end(); ++iter) { + (*iter)->Reset(); + } +} + +template void UTL::Vector::reserve(UTL::Vector::size_type); +template void UTL::Vector::reserve(UTL::Vector::size_type); +template void UTL::Vector::push_back(IModel *const &); +template void UTL::Vector::push_back(IRigidBody *const &); +template const Attrib::RefSpec &Attrib::Attribute::Get(unsigned int) const; diff --git a/src/Speed/Indep/Src/Physics/Common/Smackable.cpp b/src/Speed/Indep/Src/Physics/Common/Smackable.cpp index e69de29bb..774f82533 100644 --- a/src/Speed/Indep/Src/Physics/Common/Smackable.cpp +++ b/src/Speed/Indep/Src/Physics/Common/Smackable.cpp @@ -0,0 +1,1116 @@ +#include "Speed/Indep/Src/Physics/Smackable.h" + +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "Speed/Indep/Src/Camera/CameraAI.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Interfaces/SimModels/IModel.h" +#include "Speed/Indep/Src/Interfaces/SimModels/IPlaceableScenery.h" +#include "Speed/Indep/Src/Interfaces/SimModels/ISceneryModel.h" +#include "Speed/Indep/Src/Interfaces/SimModels/ITriggerableModel.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICause.h" +#include "Speed/Indep/Src/Interfaces/Simables/IExplosion.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h" +#include "Speed/Indep/Src/Main/AttribSupport.h" +#include "Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.h" +#include "Speed/Indep/Src/Physics/Behaviors/RigidBody.h" +#include "Speed/Indep/Src/Physics/Bounds.h" +#include "Speed/Indep/Src/Physics/Dynamics/Inertia.h" +#include "Speed/Indep/Src/Physics/HeirarchyModel.h" +#include "Speed/Indep/Src/Physics/PlaceableScenery.h" +#include "Speed/Indep/Src/Physics/SmackableTrigger.h" +#include "Speed/Indep/Src/Physics/SmokeableInfo.hpp" +#include "Speed/Indep/Src/Render/RenderConn.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/DamageZones.h" +#include "Speed/Indep/Src/World/WCollisionMgr.h" +#include "Speed/Indep/Src/World/WWorldPos.h" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" + +namespace CameraAI { +void AddAvoidable(IBody *body); +void RemoveAvoidable(IBody *body); +} // namespace CameraAI + +const ModelHeirarchy *FindSceneryHeirarchyByName(unsigned int name); + +namespace Sim { +bool CanSpawnRigidBody(const UMath::Vector3 &position, bool highPriority); +bool CanSpawnSimpleRigidBody(const UMath::Vector3 &position, bool highPriority); +} // namespace Sim + +namespace DamageZone { +UCrc32 GetImpactStimulus(unsigned int level); +} // namespace DamageZone + +Attrib::StringKey BEHAVIOR_MECHANIC_EFFECTS; + +UTL::COM::Factory::Prototype _Smackable("Smackable", + Smackable::Construct); +UTL::COM::Factory::Prototype __RBSmackable( + "RBSmackable", RBSmackable::Construct); + +unsigned int Smackable_RigidCount; +Attrib::StringKey Smackable::CYLINDER; +Attrib::StringKey Smackable::TUBE; +Attrib::StringKey Smackable::CONE; +Attrib::StringKey Smackable::SPHERE; +static float Smackable_ManagementRate = 0.125f; + +IDisposable::~IDisposable() {} + +Attrib::Key Attrib::Gen::smackable::ClassKey() { + return 0xce70d7db; +} + +HINTERFACE ISimpleBody::_IHandle() { + return (HINTERFACE)_IHandle; +} + +HINTERFACE IPlaceableScenery::_IHandle() { + return (HINTERFACE)_IHandle; +} + +IPlaceableScenery::~IPlaceableScenery() {} + +HINTERFACE EventSequencer::IContext::_IHandle() { + return (HINTERFACE)_IHandle; +} + +EventSequencer::IContext::~IContext() {} + +static float GetDropTimer(const Attrib::Gen::smackable &attributes) { + float result; + result = attributes.DROPOUT(0); + if (!(result > 0.0f)) { + return 0.0f; + } + if (attributes.DROPOUT(1) > 0.0f) { + return result; + } + return 0.0f; +} + +bool Smackable::Simplify() { + if (mCollisionBody != nullptr && !mPersistant) { + const UMath::Vector3 &pos = static_cast(this)->GetPosition(); + if (Sim::CanSpawnSimpleRigidBody(pos, true)) { + IRigidBody *irb = GetRigidBody(); + UMath::Vector3 position = irb->GetPosition(); + UMath::Vector3 velocity = irb->GetLinearVelocity(); + UMath::Vector3 angular = irb->GetAngularVelocity(); + float radius = irb->GetRadius(); + float mass = irb->GetMass(); + UMath::Matrix4 matrix = mCollisionBody->GetMatrix4(); + RBSimpleParams rbp(position, velocity, angular, matrix, radius, mass); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), UCrc32("SimpleRigidBody"), rbp); + return true; + } + } + return false; +} + +bool Smackable::TrySimplify() { + for (Smackable *const *iter = UTL::Collections::Listable< Smackable, 160 >::GetList().begin(); + iter != UTL::Collections::Listable< Smackable, 160 >::GetList().end(); ++iter) { + Smackable *smack = *iter; + if (smack->Simplify()) { + return true; + } + } + return false; +} + +ISimable *Smackable::Construct(Sim::Param params) { + const SmackableParams sp = params.Fetch< SmackableParams >(UCrc32(0xa6b47fac)); + if (sp.fScenery == nullptr) { + return nullptr; + } + Attrib::Gen::smackable attributes(sp.fScenery->GetAttributes()); + if (!attributes.IsValid()) { + return nullptr; + } + IPlaceableScenery *placeable = nullptr; + bool is_persistant = false; + if (sp.fScenery->QueryInterface(&placeable)) { + is_persistant = true; + } + bool simple_physics = attributes.SimplePhysics() || sp.fSimplePhysics; + if (!simple_physics && !is_persistant && Smackable_RigidCount >= 0x14 && !TrySimplify()) { + return nullptr; + } + sp.fScenery->GetWorldID(); + const CollisionGeometry::Bounds *geoms = sp.fScenery->GetCollisionGeometry(); + if (geoms == nullptr) { + return nullptr; + } + if (!(attributes.MASS() > 0.0f)) { + return nullptr; + } + UMath::Matrix4 matrix = sp.fMatrix; + if (simple_physics) { + if (!Sim::CanSpawnSimpleRigidBody(UMath::Vector4To3(matrix.v3), false)) { + return nullptr; + } + } else { + if (!Sim::CanSpawnRigidBody(UMath::Vector4To3(matrix.v3), false)) { + return nullptr; + } + } + if (Manager::Get() == nullptr) { + if (new Manager(Smackable_ManagementRate) == nullptr) { + return nullptr; + } + } + return new Smackable(matrix, attributes, geoms, sp.fVirginSpawn, sp.fScenery, simple_physics, + is_persistant); +} + +Smackable::Smackable(const UMath::Matrix4 &matrix, const Attrib::Gen::smackable &attributes, + const CollisionGeometry::Bounds *geoms, bool virginspawn, IModel *scenery, + bool simple_physics, bool is_persistant) + : PhysicsObject(attributes, SIMABLE_SMACKABLE, scenery->GetWorldID(), 10) // + , IDisposable(this) // + , IRenderable(this) // + , IExplodeable(this) // + , EventSequencer::IContext(this) // + , mAttributes(attributes) // + , mSimplifyWeight(0.0f) // + , mAge(0.0f) // + , mLife(Smackable_ManagementRate) // + , mDropTimer(0.0f) // + , mDropOutTimerMax(GetDropTimer(attributes)) // + , mOffWorldTimer(0.0f) // + , mAutoSimplify(attributes.AUTO_SIMPLIFY()) // + , mVirgin(virginspawn) // + , mModel(scenery) // + , mGeometry(geoms) // + , mManageTask(nullptr) // + , mDroppingOut(false) // + , mPersistant(is_persistant) // + , mCollisionBody(nullptr) // + , mSimpleBody(nullptr) // + , mLastImpactSpeed(UMath::Vector3::kZero) // + , mRBSpecs(static_cast(this), 0) // + , mLastCollisionPosition(UMath::Vector4::kZero) // +{ + UMath::Vector3 dimension; + geoms->GetHalfDimensions(dimension); + dimension.x = UMath::Max(dimension.x, 0.025f); + dimension.y = UMath::Max(dimension.y, 0.025f); + dimension.z = UMath::Max(dimension.z, 0.025f); + float radius = UMath::Length(dimension); + float mass = attributes.MASS(); + Dynamics::Inertia::Box inertia(mass, dimension.x, dimension.y, dimension.z); + UMath::Scale(inertia, 2.0f, inertia); + UMath::Vector3 moment; + if (attributes.MOMENT(moment)) { + if (moment.x > 0.0f) { + inertia.x *= moment.x; + } + if (moment.y > 0.0f) { + inertia.y *= moment.y; + } + if (moment.z > 0.0f) { + inertia.z *= moment.z; + } + } + bool active = !virginspawn || mPersistant; + UCrc32 smack_class; + if (simple_physics) { + RBSimpleParams rbp(UMath::Vector4To3(matrix.v3), UMath::Vector3::kZero, + UMath::Vector3::kZero, matrix, radius, mass); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), UCrc32("SimpleRigidBody"), rbp); + } else { + RBComplexParams rbparams(UMath::Vector4To3(matrix.v3), UMath::Vector3::kZero, + UMath::Vector3::kZero, matrix, mass, inertia, dimension, geoms, + active, 0); + if (mPersistant) { + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), UCrc32("RigidBody"), rbparams); + } else { + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), UCrc32("RBSmackable"), rbparams); + } + } + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_EFFECTS), UCrc32("EffectsSmackable"), Sim::Param()); + for (unsigned int i = 0; i < mAttributes.Num_BEHAVIORS(); ++i) { + const Attrib::StringKey &key = mAttributes.BEHAVIORS(i); + if (key.IsNotEmpty()) { + LoadBehavior(UCrc32(key), UCrc32(key), Sim::Param()); + } + } + Attach(scenery); + mManageTask = AddTask("Physics", 0.1f, 0.0f, Sim::TASK_FRAME_FIXED); + CalcSimplificationWeight(); + if (mAttributes.EventSequencer().IsNotEmpty()) { + Sim::Collision::AddListener(static_cast(this), + GetInstanceHandle(), "Smackable"); + } +} + +Smackable::~Smackable() { + DetachAll(); + if (mManageTask != nullptr) { + RemoveTask(mManageTask); + mManageTask = nullptr; + } + if (mModel != nullptr) { + Detach(mModel); + mModel = nullptr; + } + ReleaseBehavior(UCrc32(BEHAVIOR_MECHANIC_EFFECTS)); + ReleaseBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY)); + Sim::Collision::RemoveListener(static_cast(this)); +} + +bool Smackable::SetDynamicData(const EventSequencer::System *system, EventDynamicData *data) { + data->fPosition = mLastCollisionPosition; + return true; +} + +template void UTL::Vector::push_back(ISimpleBody *const &); +template BehaviorSpecsPtr::~BehaviorSpecsPtr(); + +unsigned int RenderConn::Pkt_Smackable_Open::Size() { + return sizeof(*this); +} + +unsigned int RenderConn::Pkt_Smackable_Open::Type() { + return SType(); +} + +unsigned int RenderConn::Pkt_Smackable_Open::SType() { + static UCrc32 hash = "Pkt_Smackable_Open"; + return hash.GetValue(); +} + +bool Smackable::OnExplosion(const UMath::Vector3 &normal, const UMath::Vector3 &position, + float dT, IExplosion *explosion) { + unsigned int targets = explosion->GetTargets(); + if ((targets & 1) == 0) { + return false; + } + IRigidBody *irb = GetRigidBody(); + float factor = mAttributes.ExplosionEffect(); + if (!(0.0f < factor)) { + return false; + } + float targetspeed = explosion->GetExpansionSpeed() * factor; + UMath::Vector3 point_velocity; + irb->GetPointVelocity(position, point_velocity); + float speed = UMath::Dot(point_velocity, normal); + if (speed < targetspeed) { + float deltaspeed = targetspeed - speed; + UMath::Vector3 impactvel; + UMath::Scale(normal, deltaspeed, impactvel); + UMath::Vector3 force; + UMath::Scale(impactvel, irb->GetMass() / dT, force); + irb->ResolveForce(force, position); + } + EventSequencer::IEngine *sequencer = static_cast(this)->GetEventSequencer(); + if (sequencer != nullptr) { + sequencer->ProcessStimulus(0xab556d39, Sim::GetTime(), nullptr, + EventSequencer::QUEUE_ALLOW); + if (explosion->HasDamage()) { + sequencer->ProcessStimulus(0xffcd8a63, Sim::GetTime(), nullptr, + EventSequencer::QUEUE_ALLOW); + } + } + if (!static_cast(this)->GetCausality() && explosion->GetCausality()) { + ICause *cause = ICause::FindInstance(explosion->GetCausality()); + if (cause != nullptr) { + cause->OnCausedExplosion(explosion, static_cast(this)); + } + } + return true; +} + +void Smackable::OnBehaviorChange(const UCrc32 &mechanic) { + PhysicsObject::OnBehaviorChange(mechanic); + if (mechanic == UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY)) { + if (static_cast(this)->QueryInterface(&mCollisionBody)) { + float detach = mAttributes.DETACH_FORCE(); + if (mVirgin && detach != 0.0f) { + mCollisionBody->AttachedToWorld(true, UMath::Max(0.0f, detach)); + } + const CollisionGeometry::Bounds *cog = + mGeometry->GetChild(UCrc32(0x28b0bb8d)); + if (cog != nullptr) { + UMath::Vector3 cog_position; + cog->GetPosition(cog_position); + mCollisionBody->SetCenterOfGravity(cog_position); + } else { + mCollisionBody->DistributeMass(); + } + } + if (static_cast(this)->QueryInterface(&mSimpleBody)) { + mSimpleBody->ModifyFlags(0, 0x20b); + ReleaseBehavior(UCrc32(BEHAVIOR_MECHANIC_EFFECTS)); + } + } +} + +void Smackable::DoImpactStimulus(unsigned int systemid, float intensity) { + float time; + EventSequencer::IEngine *iev; + EventSequencer::System *system; + unsigned int level; + time = Sim::GetTime(); + iev = static_cast(this)->GetEventSequencer(); + if (iev != nullptr) { + system = iev->FindSystem(systemid); + if (system != nullptr) { + intensity = UMath::Clamp(intensity, 0.0f, 1.0f); + level = static_cast(intensity * 6.0f); + for (unsigned int i = 0; i < level + 1; i++) { + system->ProcessStimulus(DamageZone::GetImpactStimulus(i).GetValue(), time, + static_cast(this), + EventSequencer::QUEUE_ALLOW); + } + } + } +} + +void Smackable::OnImpact(float acceleration, float speed, + Sim::Collision::Info::CollisionType type, ISimable *iother) { + float time; + EventSequencer::IEngine *iev; + EventSequencer::System *system; + float intensity; + time = Sim::GetTime(); + iev = static_cast(this)->GetEventSequencer(); + if (iev != nullptr) { + switch (type) { + case Sim::Collision::Info::OBJECT: + if (iother != nullptr) { + float intensity = acceleration / MPH2MPS(100.0f); + DoImpactStimulus(0xd59062c8, intensity); + if (iother->IsPlayer()) { + DoImpactStimulus(0x2f698829, intensity); + } + if (iother->GetSimableType() == SIMABLE_VEHICLE) { + DoImpactStimulus(0x80b88c1d, intensity); + } + } + break; + case Sim::Collision::Info::GROUND: + DoImpactStimulus(0x2bf74e61, speed * 0.1f); + break; + case Sim::Collision::Info::WORLD: + DoImpactStimulus(0x7ebe81c0, speed / MPH2MPS(100.0f)); + break; + default: + break; + } + } +} + +void Smackable::OnCollision(const Sim::Collision::Info &cinfo) { + EventSequencer::IEngine *iev = static_cast(this)->GetEventSequencer(); + if (iev == nullptr) { + return; + } + float speed = UMath::Length(cinfo.closingVel); + if (speed < 0.0f) { + return; + } + mLastCollisionPosition = UMath::Vector4Make(cinfo.position, 0.0f); + if (cinfo.objA == static_cast(this)->GetInstanceHandle()) { + UMath::Vector3 normal = cinfo.normal; + mLastImpactSpeed = cinfo.objAVel; + float impact_acceleration = cinfo.impulseA; + OnImpact(impact_acceleration, speed, cinfo.Type(), ISimable::FindInstance(cinfo.objB)); + } else if (cinfo.objB == static_cast(this)->GetInstanceHandle()) { + UMath::Vector3 normal; + UMath::Scale(cinfo.normal, -1.0f, normal); + mLastImpactSpeed = cinfo.objBVel; + float impact_acceleration = cinfo.impulseB; + OnImpact(impact_acceleration, speed, cinfo.Type(), ISimable::FindInstance(cinfo.objA)); + } +} + +bool Smackable::InView() const { + if (mModel != nullptr) { + return mModel->InView(); + } + return false; +} + +bool Smackable::IsRenderable() const { return mModel != nullptr; } + +const IModel *Smackable::GetModel() const { return mModel; } +IModel *Smackable::GetModel() { return mModel; } + +float Smackable::DistanceToView() const { + if (mModel != nullptr) { + return mModel->DistanceToView(); + } + return 0.0f; +} + +void Smackable::Kill() { + if (mManageTask != nullptr) { + RemoveTask(mManageTask); + mManageTask = nullptr; + } + if (mModel != nullptr && !mPersistant) { + mModel->ReleaseModel(); + mModel = nullptr; + } + if (mCollisionBody != nullptr) { + mCollisionBody->DisableModeling(); + mCollisionBody->DisableTriggering(); + } + if (mSimpleBody != nullptr) { + mSimpleBody->ModifyFlags(0x308, 0); + } + PhysicsObject::Kill(); +} + +bool Smackable::Dropout() { + if (mCollisionBody == nullptr) { + return false; + } + if (mDropOutTimerMax <= 0.0f) { + return false; + } + if (!mCollisionBody->IsAttachedToWorld()) { + mCollisionBody->DisableModeling(); + mCollisionBody->DisableTriggering(); + mDropTimer = mDropOutTimerMax; + return true; + } + return false; +} + +bool Smackable::ValidateWorld() { + const UMath::Vector3 &position = static_cast(this)->GetPosition(); + WWorldPos &wpos = static_cast(this)->GetWPos(); + wpos.FindClosestFace(position, true); + if (!wpos.OnValidFace()) { + goto fail; + } + { + float height = wpos.HeightAtPoint(position); + IRigidBody *irb = GetRigidBody(); + float radius = irb->GetRadius(); + if (height <= position.y + radius) { + goto success; + } + } +fail: + return false; +success: + return true; +} + +bool Smackable::ShouldDie() { + if (mDroppingOut) { + return false; + } + if (mCollisionBody == nullptr) { + return false; + } + if (mCollisionBody->IsSleeping()) { + if (!mCollisionBody->HasHadObjectCollision()) { + return true; + } + } + if (mCollisionBody == nullptr) { + goto shouldnt_die; + } + if (mCollisionBody->IsModeling()) { + goto shouldnt_die; + } + return true; +shouldnt_die: + return false; +} + +bool Smackable::CanRetrigger() const { + if (mCollisionBody == nullptr) { + return false; + } + if (!GetWPos().OnValidFace()) { + return false; + } + if (mCollisionBody->IsSleeping()) { + return true; + } + return false; +} + +void Smackable::ProcessDeath(float dT) { + if (ShouldDie()) { + if (mLife > 0.0f) { + mLife -= dT; + return; + } + if (Dropout()) { + return; + } + if (mModel != nullptr) { + if (CanRetrigger()) { + if (mVirgin && mCollisionBody != nullptr && + mCollisionBody->IsAttachedToWorld()) { + ISceneryModel *iscenery = nullptr; + if (mModel->QueryInterface(&iscenery)) { + iscenery->RestoreScene(); + mModel = nullptr; + } + } else { + ITriggerableModel *itrigger = nullptr; + if (mModel->QueryInterface(&itrigger)) { + UMath::Matrix4 mat; + static_cast(this)->GetTransform(mat); + itrigger->PlaceTrigger(mat, true); + static_cast(this)->Detach(mModel); + mModel = nullptr; + } + } + } else if (mModel != nullptr && !mPersistant) { + mModel->ReleaseModel(); + mModel = nullptr; + } + } + static_cast(this)->Kill(); + return; + } + mLife = 0.125f; +} + +bool Smackable::ProcessDropout(float dT) { + if (mDropOutTimerMax > 0.0f && mDropTimer > 0.0f) { + IRigidBody &rb = *GetRigidBody(); + mDropTimer -= dT; + const float dropspeed = mAttributes.DROPOUT(1); + rb.ModifyYPos(-dropspeed * dT); + if (mDropTimer <= 0.0f) { + static_cast(this)->Kill(); + mDropTimer = 0.0f; + } + return true; + } + return false; +} + +void Smackable::ProcessOffWorld(float dT) { + if (mAttributes.ALLOW_OFF_WORLD()) { + return; + } + if (!ValidateWorld()) { + mOffWorldTimer += dT; + if (mOffWorldTimer >= 1.0f) { + if (mModel != nullptr && !mPersistant) { + mModel->ReleaseModel(); + mModel = nullptr; + } + static_cast(this)->Kill(); + } + } else { + mOffWorldTimer = 0.0f; + } +} + +bool Smackable::OnTask(HSIMTASK htask, float dT) { + if (htask == mManageTask) { + Manage(dT); + return true; + } + return PhysicsObject::OnTask(htask, dT); +} + +void Smackable::OnDetached(IAttachable *pOther) { + if (UTL::COM::ComparePtr(pOther, mModel)) { + mModel = nullptr; + if (!UTL::Collections::GarbageNode< PhysicsObject, 160 >::IsDirty()) { + static_cast(this)->Kill(); + } + } + PhysicsObject::OnDetached(pOther); +} + +void Smackable::CalcSimplificationWeight() { + if (mCollisionBody == nullptr || mPersistant) { + mSimplifyWeight = -1.0f; + } + float dist = Sim::DistanceToCamera(static_cast(this)->GetPosition()); + float radius = GetRigidBody()->GetRadius(); + float baseweight = static_cast(mAttributes.CAN_SIMPLIFY()); + if (!static_cast(this)->InView()) { + baseweight += baseweight; + } + mSimplifyWeight = (dist + baseweight) / radius; +} + +void Smackable::Manage(float dT) { + ProcessDeath(dT); + ProcessOffWorld(dT); + CalcSimplificationWeight(); +} + +void Smackable::OnTaskSimulate(float dT) { + mAge += dT; + if (mCollisionBody != nullptr && mAutoSimplify > 0.0f && mAge > mAutoSimplify) { + Simplify(); + } + mDroppingOut = ProcessDropout(dT); + IRigidBody *irb = GetRigidBody(); + if (mSimpleBody != nullptr) { + UMath::Vector3 gravity = UMath::Vector3Make(0.0f, mRBSpecs.GRAVITY(), 0.0f); + UMath::Scale(gravity, irb->GetMass()); + irb->ResolveForce(gravity); + } +} + +Smackable::Manager::Manager(float rate) : Sim::Activity(0) { + mManageTask = AddTask("Smackable", rate, 0.0f, Sim::TASK_FRAME_FIXED); +} + +Smackable::Manager::~Manager() { + RemoveTask(mManageTask); +} + +bool Smackable::Manager::OnTask(HSIMTASK htask, float dT) { + if (htask == mManageTask) { + UTL::Collections::Listable< Smackable, 160 >::Sort(Smackable::SimplifySort); + if (Smackable_RigidCount > 0xa) { + TrySimplify(); + } + return true; + } + return Sim::Object::OnTask(htask, dT); +} + +Behavior *RBSmackable::Construct(const BehaviorParams &parms) { + const RBComplexParams rp = parms.fparams.Fetch< RBComplexParams >(UCrc32(0xa6b47fac)); + return new RBSmackable(parms, rp); +} + +RBSmackable::RBSmackable(const BehaviorParams &parms, const RBComplexParams &rp) + : RigidBody(parms, rp) // + , mSpecs(this, 0) // +{ + mFrame = 0; + Smackable_RigidCount++; +} + +RBSmackable::~RBSmackable() { Smackable_RigidCount--; } + +bool RBSmackable::ShouldSleep() const { + if (Dynamics::Articulation::IsJoined(this)) { + return false; + } + return RigidBody::ShouldSleep(); +} + +void RBSmackable::OnTaskSimulate(float dT) { + RigidBody::OnTaskSimulate(dT); + mFrame++; +} + +bool RBSmackable::CanCollideWith(const RigidBody &other) const { + if (Dynamics::Articulation::IsJoined(this, &other)) { + return false; + } + return RigidBody::CanCollideWith(other); +} + +bool RBSmackable::CanCollideWithGround() const { + if (IsAttachedToWorld()) { + return false; + } + return RigidBody::CanCollideWithGround(); +} + +bool RBSmackable::CanCollideWithWorld() const { + if (IsAttachedToWorld()) { + return false; + } + if (!HasHadCollision()) { + float velSquare = UMath::LengthSquare(GetLinearVelocity()); + if (velSquare < 4.0f) { + if ((mFrame & 3) != 0) { + return false; + } + } else if (velSquare < 225.0f) { + if ((mFrame & 1) != 0) { + return false; + } + } + } + return RigidBody::CanCollideWithWorld(); +} + +HeirarchyModel::HeirarchyModel(bHash32 rendermesh, const CollisionGeometry::Bounds *geometry, + UCrc32 nodename, HeirarchyModel *parent, + const Attrib::Collection *attribs, const ModelHeirarchy *heirarchy, + unsigned int child_index, bool visible) + : Sim::Model(parent != nullptr ? static_cast(parent) : nullptr, geometry, + nodename, 6) // + , IBody(this) // + , ITriggerableModel(this) // + , Attrib::Gen::smackable(attribs, 0, nullptr) // +{ + mTriggerAvoid = UMath::Vector4::kZero; + mHeirarchy = const_cast(heirarchy); + mHeirarchyNode = static_cast(child_index); + mRenderMesh = rendermesh; + mOffScreenTimer = 10.0f; + mChildVisibility = 0xFFFFFFFF; + mAvoidable = nullptr; + mTrigger = nullptr; + mFlags = 0; + Attrib::Gen::smackable smackable(attribs, 0, nullptr); + if (visible) { + RenderConn::Pkt_Smackable_Open pkt(mRenderMesh, GetWorldID(), GetCollisionGeometry(), + mHeirarchy, mHeirarchyNode); + BeginDraw(UCrc32(0x804c146e), &pkt); + } + if (smackable.AI_AVOIDABLE()) { + if (mAvoidable == nullptr) { + mAvoidable = new SmackableAvoidable(this); + } + } + if (smackable.CAMERA_AVOIDABLE()) { + SetCameraAvoidable(true); + } +} + +void HeirarchyModel::SetCameraAvoidable(bool b) { + bool is_avoidable = mFlags & 1; + if (static_cast(b) != is_avoidable) { + if (b) { + if (!CAMERA_AVOIDABLE()) { + return; + } + CameraAI::AddAvoidable(static_cast(this)); + mFlags = mFlags | 1; + } else { + CameraAI::RemoveAvoidable(static_cast(this)); + mFlags = mFlags & 0xFFFE; + } + } +} + +bool HeirarchyModel::OnRemoveOffScreen(float dT) { + if (0.0f < mOffScreenTimer) { + if (!static_cast(this)->InView()) { + mOffScreenTimer = mOffScreenTimer - dT; + if (mOffScreenTimer < 0.0f) { + mOffScreenTimer = 0.0f; + return true; + } + } else { + mOffScreenTimer = KILL_OFF_SCREEN(); + } + } + return false; +} + +void HeirarchyModel::OnProcessFrame(float dT) { + if (OnRemoveOffScreen(dT)) { + ReleaseModel(); + } +} + +void HeirarchyModel::HidePart(const UCrc32 &nodename) { + int index = FindHeirarchyChild(nodename); + if (index > -1) { + mChildVisibility = mChildVisibility & ~(1 << index); + } +} + +void HeirarchyModel::ShowPart(const UCrc32 &nodename) { + int index = FindHeirarchyChild(nodename); + if (index > -1) { + mChildVisibility = mChildVisibility | (1 << index); + } +} + +bool HeirarchyModel::IsPartVisible(const UCrc32 &nodename) const { + int index = FindHeirarchyChild(nodename); + if (index < 0) { + return false; + } + if ((mChildVisibility & (1 << index)) == 0) { + return false; + } + return true; +} + +int HeirarchyModel::FindHeirarchyChild(const UCrc32 &nodename) const { + if (mHeirarchy == nullptr) { + return -1; + } + const ModelHeirarchy::Node *nodes = mHeirarchy->GetNodes(); + const ModelHeirarchy::Node &node = nodes[mHeirarchyNode]; + unsigned int numChildren = node.mNumChildren; + int childindex = -1; + for (unsigned int i = 0; i < numChildren; ++i) { + int idx = node.mFirstChild + i; + if (nodes[idx].mNameHash == nodename.GetValue()) { + childindex = idx; + break; + } + } + return childindex; +} + +IModel *HeirarchyModel::SpawnModel(UCrc32 rendernode, UCrc32 collisionnode, UCrc32 attributes) { + if (mHeirarchy != nullptr && !IsDirty()) { + if (UTL::Collections::Listable< IModel, 434 >::Count() > 434u) { + return nullptr; + } + int childindex = FindHeirarchyChild(rendernode); + if (childindex > -1) { + const CollisionGeometry::Bounds *geom = GetCollisionGeometry(); + const CollisionGeometry::Bounds *bounds = + geom->GetChild(collisionnode); + if (bounds != nullptr) { + const Attrib::Collection *attribs = + SmokeableSpawner::FindAttributes(attributes); + if (attribs != nullptr) { + const ModelHeirarchy::Node *nodes = mHeirarchy->GetNodes(); + eModel *emodel = + reinterpret_cast(nodes[childindex].mModel); + if (emodel != nullptr) { + bHash32 meshname(emodel->GetNameHash()); + HeirarchyModel *child = new HeirarchyModel( + meshname, bounds, rendernode, this, attribs, mHeirarchy, + childindex, true); + IModel *result = nullptr; + if (child != nullptr) { + result = static_cast(child); + } + return result; + } + } + } + } + } + return nullptr; +} + +HeirarchyModel::~HeirarchyModel() { + EndSimulation(); + RemoveTrigger(); + ReleaseChildModels(); + if (mAvoidable != nullptr) { + delete mAvoidable; + mAvoidable = nullptr; + } + SetCameraAvoidable(false); +} + +void HeirarchyModel::OnBeginDraw() { + StartSequencer(UCrc32(EventSequencer())); + mOffScreenTimer = KILL_OFF_SCREEN(); +} + +void HeirarchyModel::OnEndDraw() { + mOffScreenTimer = 0.0f; + if ((mFlags & 1) != 0) { + CameraAI::RemoveAvoidable(static_cast(this)); + mFlags = mFlags & 0xFFFE; + } +} + +void HeirarchyModel::GetTransform(UMath::Matrix4 &matrix) const { + if (static_cast(this)->GetSimable()) { + static_cast(this)->GetSimable()->GetTransform(matrix); + } else if (mTrigger != nullptr) { + mTrigger->GetObjectMatrix(matrix); + } else { + matrix = UMath::Matrix4::kIdentity; + } +} + +void HeirarchyModel::GetAngularVelocity(UMath::Vector3 &velocity) const { + if (static_cast(this)->GetSimable()) { + velocity = static_cast(this)->GetSimable()->GetRigidBody()->GetAngularVelocity(); + } else { + velocity = UMath::Vector3::kZero; + } +} + +void HeirarchyModel::RemoveTrigger() { + if (mTrigger != nullptr) { + delete mTrigger; + mTrigger = nullptr; + } +} + +void HeirarchyModel::DisableTrigger() { + if (mTrigger != nullptr) { + mTrigger->Disable(); + } +} + +void HeirarchyModel::SetTrigger(const UMath::Matrix4 &matrix, bool virgin) { + UMath::Vector3 dim; + GetCollisionGeometry()->GetHalfDimensions(dim); + if (mTrigger == nullptr) { + mTrigger = new SmackableTrigger(GetInstanceHandle(), virgin, matrix, dim, 0); + } else { + mTrigger->Move(matrix, dim, virgin); + mTrigger->Enable(); + } + mTriggerAvoid = matrix.v3; + float zz = dim.y * matrix.v1.z; + float zx = dim.y * matrix.v1.x; + zz += dim.x * matrix.v0.z; + zx += dim.x * matrix.v0.x; + zz += dim.z * matrix.v2.z; + zx += dim.z * matrix.v2.x; + mTriggerAvoid.w = UMath::Sqrt(zx * zx + zz * zz); +} + +void HeirarchyModel::PlaceTrigger(const UMath::Matrix4 &matrix, bool enable) { + SetTrigger(matrix, false); + if (!enable) { + DisableTrigger(); + } +} + +void HeirarchyModel::OnEndSimulation() { + if (mAvoidable != nullptr) { + mAvoidable->SetRefrence(static_cast(this)); + } +} + +void HeirarchyModel::OnBeginSimulation() { + DisableTrigger(); + if (mAvoidable != nullptr) { + mAvoidable->SetRefrence(static_cast(this)->GetSimable()); + } + RenderConn::Pkt_Smackable_Open pkt(mRenderMesh, + static_cast(this)->GetWorldID(), + static_cast(this)->GetCollisionGeometry(), + mHeirarchy, + mHeirarchyNode); + BeginDraw(UCrc32(0x804c146e), &pkt); +} + +bool HeirarchyModel::OnDraw(Sim::Packet *service) { + RenderConn::Pkt_Smackable_Service *pss = + static_cast(service); + UpdateVisibility(pss->IsVisible(), pss->DistanceToView()); + pss->SetChildVisibility(mChildVisibility); + return true; +} + +PlaceableScenery::PlaceableScenery(bHash32 rendermesh, + const CollisionGeometry::Bounds *geometry, + const Attrib::Collection *attribs, + const ModelHeirarchy *heirarchy) + : HeirarchyModel(rendermesh, geometry, UCrc32(0x9756df79), nullptr, attribs, heirarchy, 0, + false) // + , IPlaceableScenery(this) // +{ +} + +void PlaceableScenery::ReleaseModel() { + static_cast(this)->Destroy(); +} + +PlaceableScenery *PlaceableScenery::Construct(const char *name, unsigned int attributes) { + if (static_cast(UTL::Collections::Listable< IModel, 434 >::Count()) > 434u) { + return nullptr; + } + if (static_cast(UTL::Collections::Countable< IPlaceableScenery >::Count()) > 12u) { + return nullptr; + } + bHash32 render_name(bStringHashUpper(name)); + UCrc32 collision_name(name); + bHash32 heirarchy_name(render_name); + const ModelHeirarchy *heirarchy = FindSceneryHeirarchyByName(render_name.GetValue()); + const CollisionGeometry::Collection *collection = CollisionGeometry::Lookup(collision_name); + if (collection == nullptr) { + return nullptr; + } + const CollisionGeometry::Bounds *bounds = collection->GetRoot(); + if (bounds != nullptr) { + eModel model; + model.Init(render_name.GetValue()); + if (model.GetSolid() == nullptr) { + bHash32 fallback(0xc7395a8); + render_name = fallback; + heirarchy_name = fallback; + } + const Attrib::Collection *attribs = + Attrib::FindCollection(Attrib::Gen::smackable::ClassKey(), attributes); + Attrib::Gen::smackable smk_attribs(attribs, 0, nullptr); + PlaceableScenery *result = new PlaceableScenery(heirarchy_name, bounds, + smk_attribs.GetConstCollection(), + heirarchy); + return result; + } + return nullptr; +} + +void PlaceableScenery::PickUp() { + ReleaseChildModels(); + StopEffects(); + EndDraw(); + EndSimulation(); + ReleaseSequencer(); + DisableTrigger(); +} + +bool PlaceableScenery::Place(const UMath::Matrix4 &transform, bool snap_to_ground) { + static_cast(this)->Destroy(); + UMath::Matrix4 mat; + UMath::Copy(transform, mat); + if (snap_to_ground) { + WCollisionMgr wcm(0, 3); + float worldHeight = 1000.0f; + if (!wcm.GetWorldHeightAtPointRigorous(UMath::Vector4To3(mat.v3), worldHeight, nullptr)) { + return false; + } + UMath::Vector3 dim; + GetDimension(dim); + mat.v3.y = worldHeight + dim.y; + } + PlaceTrigger(mat, false); + SmackableParams sp(mat, true, static_cast(this), false); + ISimable *physics = UTL::COM::Factory< Sim::Param, ISimable, UCrc32 >::CreateInstance( + UCrc32("Smackable"), sp); + if (physics == nullptr) { + static_cast(this)->Destroy(); + return false; + } + return true; +} + +SmackableAvoidable::SmackableAvoidable(HeirarchyModel *model) + : AIAvoidable( + mModel != nullptr + ? static_cast(static_cast(mModel)) + : nullptr) // + , mModel(model) // +{ + SetAvoidableObject(mModel != nullptr + ? static_cast(static_cast(mModel)) + : nullptr); +} + +bool SmackableAvoidable::OnUpdateAvoidable(UMath::Vector3 &pos, float &sweep) { + return mModel->OnUpdateAvoidable(pos, sweep); +} + +inline bool Smackable::SimplifySort(const Smackable *lhs, const Smackable *rhs) { + if (lhs->mSimplifyWeight > rhs->mSimplifyWeight) { + return true; + } + if (lhs->mSimplifyWeight < rhs->mSimplifyWeight) { + return false; + } + return lhs->GetInstanceHandle() < rhs->GetInstanceHandle(); +} + +IPlaceableScenery *IPlaceableScenery::CreateInstance(const char *name, unsigned int attributes) { + return PlaceableScenery::Construct(name, attributes); +} + +PlaceableScenery::~PlaceableScenery() {} diff --git a/src/Speed/Indep/Src/Physics/Common/SmokeableInfo.cpp b/src/Speed/Indep/Src/Physics/Common/SmokeableInfo.cpp index e69de29bb..c8e26e167 100644 --- a/src/Speed/Indep/Src/Physics/Common/SmokeableInfo.cpp +++ b/src/Speed/Indep/Src/Physics/Common/SmokeableInfo.cpp @@ -0,0 +1,470 @@ +#include "Speed/Indep/Src/Physics/SceneryModel.h" +#include "Speed/Indep/Src/Physics/SmokeableInfo.hpp" + +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Misc/ResourceLoader.hpp" +#include "Speed/Indep/Src/Sim/SimSubSystem.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/Scenery.hpp" + +void ResetPropTimers(); + +SmokeableSectionQ TheSmokeableSections; + +static void SceneryModel_InitSystem() { + SceneryModel::InitSystem(); +} + +static void SceneryModel_RestoreSystem() { + SceneryModel::RestoreSystem(); +} + +static Sim::SubSystem _Physics_System_SceneryModel("SceneryModel", SceneryModel_InitSystem, SceneryModel_RestoreSystem); +int SceneryModel::mSceneryCount = 0; + +SmokeableSection *SmokeableSectionQ::FindOrAdd(int section_id) { + SmokeableSection *section = nullptr; + for (int i = 0; i < mQueue.size(); i++) { + SmokeableSection &this_section = mQueue[i]; + if (this_section.SectionID == section_id) { + SmokeableSection new_section(this_section); + mQueue.dequeue(); + mQueue.enqueue(new_section); + section = &mQueue.head(); + break; + } + } + if (section == nullptr) { + SmokeableSection new_section(section_id); + mQueue.enqueue(new_section); + section = &mQueue.head(); + } + return section; +} + +SmokeableSection *SmokeableSectionQ::Find(int section_id) { + for (int i = 0; i < mQueue.size(); i++) { + SmokeableSection &this_section = mQueue[i]; + if (this_section.SectionID == section_id) { + return &this_section; + } + } + return nullptr; +} + +SceneryModel::SceneryModel(SmokeableSpawner *spawner, const CollisionGeometry::Bounds *geometry, + const Attrib::Collection *attribs, bool hidden) + : HeirarchyModel(spawner->GetRenderMesh(), geometry, UCrc32(0x9756DF79u), nullptr, attribs, // + spawner->GetRenderHeirarchy(), 0, false) // + , ISceneryModel(this) { + mInstanceVisible = true; + mSpawner = spawner; + if (!hidden) { + InitScene(); + } else { + StartOverride(); + } + mSceneryCount++; +} + +SceneryModel::~SceneryModel() { + if (mSpawner != nullptr) { + mSpawner->mSimModel = nullptr; + ShowInstance(true); + } + mSceneryCount--; +} + +void SceneryModel::ShowInstance(bool show) { + if (mSpawner != nullptr && show != mInstanceVisible) { + if (show) { + mSpawner->ShowInstance(); + } else { + mSpawner->HideInstance(); + } + mInstanceVisible = show; + } +} + +void SceneryModel::OnBeginDraw() { + HeirarchyModel::OnBeginDraw(); + StartOverride(); +} + +void SceneryModel::HideModel() { + Sim::Model::HideModel(); + StartOverride(); +} + +void SceneryModel::EndOverride() { + ShowInstance(true); +} + +void SceneryModel::StartOverride() { + ShowInstance(false); +} + +void SceneryModel::GetTransform(UMath::Matrix4 &matrix) const { + if (mInstanceVisible) { + if (!GetSceneryTransform(matrix)) { + matrix = UMath::Matrix4::kIdentity; + } + } else { + HeirarchyModel::GetTransform(matrix); + } +} + +void SceneryModel::InitScene() { + UMath::Matrix4 scenery_matrix; + + ReleaseChildModels(); + StopEffects(); + Sim::Model::EndDraw(); + Sim::Model::EndSimulation(); + Sim::Model::ReleaseSequencer(); + EndOverride(); + SetCausality(0, 0.0f); + + if (GetSceneryTransform(scenery_matrix)) { + SetTrigger(scenery_matrix, true); + } else { + RemoveTrigger(); + } + + bool enable_sequencer = start_sequencer(); + + if (no_trigger()) { + SmackableTrigger *trigger = GetTrigger(); + if (trigger != nullptr) { + trigger->Disable(); + } + enable_sequencer = true; + } + + if (enable_sequencer) { + Sim::Model::StartSequencer(UCrc32(EventSequencer())); + } + + SetCameraAvoidable(true); +} + +bool SceneryModel::GetSceneryTransform(UMath::Matrix4 &matrix) const { + const CollisionGeometry::Bounds *bounds = GetCollisionGeometry(); + if (bounds != nullptr) { + UMath::Vector3 pivot; + bounds->GetPivot(pivot); + UMath::QuaternionToMatrix4(mSpawner->GetOrientation(), matrix); + UMath::Rotate(pivot, matrix, UMath::Vector4To3(matrix.v3)); + UMath::Addxyz(matrix.v3, mSpawner->GetPosition(), matrix.v3); + return true; + } + return false; +} + +void SceneryModel::OnEndSimulation() { + HeirarchyModel::OnEndSimulation(); +} + +SceneryModel *SceneryModel::Construct(SmokeableSpawner *data, const Attrib::Collection *attributes, bool hidden) { + if (static_cast(mSceneryCount) < 256) { + const CollisionGeometry::Collection *col = CollisionGeometry::Lookup(data->GetCollisionName()); + if (col != nullptr) { + const CollisionGeometry::Bounds *bounds = col->GetRoot(); + if (bounds != nullptr) { + if (attributes == nullptr) { + return nullptr; + } + bounds = col->GetRoot(); + if (bounds != nullptr) { + UMath::Vector3 dimension; + bounds->GetHalfDimensions(dimension); + if (UMath::LengthSquare(dimension) > 0.0f) { + return new SceneryModel(data, bounds, attributes, hidden); + } + } + } + } + } + return nullptr; +} + +void SceneryModel::WakeUp() { + SmackableTrigger *trigger = GetTrigger(); + if (trigger != nullptr && !IsRendering() && !IsSimulating()) { + trigger->Fire(); + trigger->Disable(); + } +} + +void SceneryModel::RestoreScene() { + InitScene(); +} + +void SceneryModel::ReleaseModel() { + Sim::Model::EndDraw(); + Sim::Model::EndSimulation(); + StopEffects(); + HeirarchyModel::DisableTrigger(); +} + +void SceneryModel::InitSystem() { + ResetPropTimers(); +} + +void SceneryModel::RestoreSystem() { + ResetPropTimers(); +} + +void ResetPropTimers() { + TheSmokeableSections.Reset(); +} + +static const Attrib::Class *TheSmackableClass; + +void SmokeableSpawnerPack::OnUnload() { + SmokeableSection *section = TheSmokeableSections.FindOrAdd(static_cast(ScenerySectionNumber)); + section->LastLoadTime = Sim::GetTime(); + + for (int n = 0; n < NumSmokeableSpawners; n++) { + { + SmokeableSpawner *spawner = &SmokeableSpawners[n]; + if (static_cast(n) < 256) { + if (!spawner->IsInstanceVisible()) { + section->Rebuilds.Set(static_cast(n)); + } else { + section->Rebuilds.Clear(static_cast(n)); + } + } + spawner->OnUnload(); + } + } +} + +void SmokeableSpawnerPack::EndianSwap() { + if (EndianSwapped) { + return; + } + EndianSwapped = 1; + bPlatEndianSwap(&ScenerySectionNumber); + bPlatEndianSwap(&FirstSmokeableSpawnerID); + bPlatEndianSwap(&NumSmokeableSpawners); + for (int n = 0; n < NumSmokeableSpawners; n++) { + SmokeableSpawners[n].EndianSwap(); + } +} + +void SmokeableSpawnerPack::OnMoved() { + for (int n = 0; n < NumSmokeableSpawners; n++) { + SmokeableSpawners[n].OnMoved(); + } +} + +void SmokeableSpawnerPack::OnLoad(unsigned int exclude_flags) { + SmokeableSection *section = TheSmokeableSections.Find(static_cast(ScenerySectionNumber)); + + if (!GRaceStatus::Exists() || !GRaceStatus::Get().GetActivelyRacing()) { + if (section != nullptr) { + if (section->LastLoadTime + 180.0f < Sim::GetTime()) { + section->Rebuilds.Clear(); + section = nullptr; + } + } + } + + if (section == nullptr || !section->Rebuilds.Test()) { + GManager::Get().RestorePursuitBreakerIcons(static_cast(ScenerySectionNumber)); + } + + for (int n = 0; n < NumSmokeableSpawners; n++) { + bool ignore = false; + if (section != nullptr && static_cast(n) < 256 && section->Rebuilds.Test(static_cast(n))) { + ignore = true; + } + SmokeableSpawners[n].OnLoad(exclude_flags, ignore); + } +} + +const Attrib::Collection *SmokeableSpawner::FindAttributes(UCrc32 name) { + return TheSmackableClass->GetCollection(name.GetValue()); +} + +void SmokeableSpawner::Init() { + TheSmackableClass = Attrib::Database::Get().GetClass(Attrib::Gen::smackable::ClassKey()); +} + +void SmokeableSpawner::EndianSwap() { + bPlatEndianSwap(&mOrientation); + bPlatEndianSwap(&mPosition); + bPlatEndianSwap(&mModel); + bPlatEndianSwap(&mCollisionName); + bPlatEndianSwap(&mAttributes); + bPlatEndianSwap(&mSceneryOverrideInfoNumber); + bPlatEndianSwap(&mUniqueID); + bPlatEndianSwap(&mExcludeFlags); +} + +void SmokeableSpawner::OnUnload() { + if (mSimModel != nullptr) { + delete mSimModel; + mSimModel = nullptr; + } +} + +const ModelHeirarchy *SmokeableSpawner::GetRenderHeirarchy() const { + ModelHeirarchy *heirarchy; + SceneryOverrideInfo *info = GetSceneryOverrideInfo(mSceneryOverrideInfoNumber); + if (info != nullptr) { + ScenerySectionHeader *section_header = GetScenerySectionHeader(static_cast(info->SectionNumber)); + if (section_header != nullptr) { + SceneryInstance *scenery_instance = section_header->GetSceneryInstance(static_cast(info->InstanceNumber)); + if (scenery_instance != nullptr) { + SceneryInfo *sinfo = section_header->GetSceneryInfo(scenery_instance); + if (sinfo != nullptr) { + heirarchy = sinfo->mHeirarchy; + return heirarchy; + } + } + } + } + return nullptr; +} + +bHash32 SmokeableSpawner::GetRenderMesh() const { + SceneryOverrideInfo *soi = GetSceneryOverrideInfo(mSceneryOverrideInfoNumber); + if (soi != nullptr) { + ScenerySectionHeader *section_header = GetScenerySectionHeader(static_cast(soi->SectionNumber)); + if (section_header != nullptr) { + SceneryInstance *scenery_instance = section_header->GetSceneryInstance(static_cast(soi->InstanceNumber)); + if (scenery_instance != nullptr) { + SceneryInfo *scenery_info = section_header->GetSceneryInfo(scenery_instance); + if (scenery_info != nullptr) { + for (int i = 0; i < 4; i++) { + if (scenery_info->NameHash[i] != 0) { + return bHash32(scenery_info->NameHash[i]); + } + } + } + } + } + } + return bHash32(0x0C7395A8u); +} + +void SmokeableSpawner::ShowInstance() const { + SceneryOverrideInfo *info = GetSceneryOverrideInfo(mSceneryOverrideInfoNumber); + if (info != nullptr) { + info->EnableRendering(); + info->AssignOverrides(); + } +} + +bool SmokeableSpawner::IsInstanceVisible() const { + SceneryOverrideInfo *info = GetSceneryOverrideInfo(mSceneryOverrideInfoNumber); + if (info != nullptr) { + return (info->ExcludeFlags & 0x10) == 0; + } + return false; +} + +void SmokeableSpawner::HideInstance() const { + SceneryOverrideInfo *info = GetSceneryOverrideInfo(mSceneryOverrideInfoNumber); + if (info != nullptr) { + info->DisableRendering(); + info->AssignOverrides(); + } +} + +void SmokeableSpawner::OnMoved() { + if (mSimModel != nullptr) { + mSimModel->mSpawner = this; + } +} + +void SmokeableSpawner::OnLoad(unsigned int exclude_flags, bool hidden) { + if ((exclude_flags & mExcludeFlags) == 0) { + ShowInstance(); + const Attrib::Collection *collection = FindAttributes(UCrc32(mAttributes)); + if (collection != nullptr) { + mSimModel = SceneryModel::Construct(this, collection, hidden); + } else { + mSimModel = nullptr; + } + } + if (mSimModel == nullptr) { + HideInstance(); + } +} + +int SmokeableSpawnerPack::Loader(bChunk *chunk) { + if (chunk->GetID() == 0x34027) { + SmokeableSpawnerPack *spawner_pack = reinterpret_cast(chunk->GetAlignedData(16)); + if (AreChunksBeingMoved()) { + spawner_pack->OnMoved(); + } else { + spawner_pack->EndianSwap(); + if (Sim::Exists()) { + unsigned int exclude_flags = static_cast(Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN); + if (GRaceStatus::Exists() && GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Racing) { + exclude_flags |= 4; + } + spawner_pack->OnLoad(exclude_flags); + } + } + return 1; + } + return 0; +} + +int SmokeableSpawnerPack::Unloader(bChunk *chunk) { + if (chunk->GetID() == 0x34027) { + if (!AreChunksBeingMoved() && Sim::Exists()) { + SmokeableSpawnerPack *pack = reinterpret_cast(chunk->GetAlignedData(16)); + pack->OnUnload(); + } + return 1; + } + return 0; +} + +bChunkLoader SmokeableSpawnerPack::mLoader(0x34027, SmokeableSpawnerPack::Loader, SmokeableSpawnerPack::Unloader); + +inline bool SceneryModel::IsHidden() const { + bool visible = mInstanceVisible || !Sim::Model::IsHidden(); + return !visible; +} + +inline bool SceneryModel::IsExcluded(unsigned int scenery_exclusion_flag) const { + unsigned int my_flags = 0; + if (mSpawner != nullptr) { + my_flags = mSpawner->GetExcludeFlags(); + } + return (my_flags & scenery_exclusion_flag) != 0; +} + +inline unsigned int SceneryModel::GetSpawnerID() const { + if (mSpawner != nullptr) { + return mSpawner->GetUniqueID(); + } + return 0xFFFFFFFF; +} + +bool HeirarchyModel::OnUpdateAvoidable(UMath::Vector3 &pos, float &sweep) { + if (mTrigger != nullptr && mTrigger->IsEnabled()) { + if (0.0f < mTriggerAvoid.w) { + pos = UMath::Vector4To3(mTriggerAvoid); + sweep = mTriggerAvoid.w; + return true; + } + } else { + if (GetSimable() != nullptr) { + IRigidBody *irb = GetSimable()->GetRigidBody(); + if (irb != nullptr) { + sweep = irb->GetRadius(); + pos = irb->GetPosition(); + return true; + } + } + } + return false; +} diff --git a/src/Speed/Indep/Src/Physics/Common/VehicleBehaviors.cpp b/src/Speed/Indep/Src/Physics/Common/VehicleBehaviors.cpp index e69de29bb..754024dd1 100644 --- a/src/Speed/Indep/Src/Physics/Common/VehicleBehaviors.cpp +++ b/src/Speed/Indep/Src/Physics/Common/VehicleBehaviors.cpp @@ -0,0 +1,77 @@ +#include "Speed/Indep/Src/Physics/VehicleBehaviors.h" + +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" + +class RBVehicle { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class RBTractor { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class RBTrailer { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class RBCop { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DamageRacer { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DamageDragster { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DamageHeli { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DamageCopCar { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class PInput { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class InputPlayer { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class InputPlayerDrag { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class InputNIS { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; + +UTL::COM::Factory::Prototype __RBVehicle("RBVehicle", RBVehicle::Construct); +UTL::COM::Factory::Prototype __RBTractor("RBTractor", RBTractor::Construct); +UTL::COM::Factory::Prototype __RBTrailer("RBTrailer", RBTrailer::Construct); +UTL::COM::Factory::Prototype __RBCop("RBCop", RBCop::Construct); +UTL::COM::Factory::Prototype __EffectsVehicle("EffectsVehicle", EffectsVehicle::Construct); +UTL::COM::Factory::Prototype __EffectsCar("EffectsCar", EffectsCar::Construct); +UTL::COM::Factory::Prototype __EffectsPlayer("EffectsPlayer", EffectsPlayer::Construct); +UTL::COM::Factory::Prototype __EffectsSmackable("EffectsSmackable", EffectsSmackable::Construct); +UTL::COM::Factory::Prototype __EffectsFragment("EffectsFragment", EffectsFragment::Construct); +UTL::COM::Factory::Prototype __DamageVehicle("DamageVehicle", DamageVehicle::Construct); +UTL::COM::Factory::Prototype __DamageRacer("DamageRacer", DamageRacer::Construct); +UTL::COM::Factory::Prototype __DamageDragster("DamageDragster", DamageDragster::Construct); +UTL::COM::Factory::Prototype __DamageHeli("DamageHeli", DamageHeli::Construct); +UTL::COM::Factory::Prototype __DamageCopCar("DamageCopCar", DamageCopCar::Construct); +UTL::COM::Factory::Prototype __PInput("PInput", PInput::Construct); +UTL::COM::Factory::Prototype __InputPlayer("InputPlayer", InputPlayer::Construct); +UTL::COM::Factory::Prototype __InputPlayerDrag("InputPlayerDrag", InputPlayerDrag::Construct); +UTL::COM::Factory::Prototype __InputNIS("InputNIS", InputNIS::Construct); + +VehicleBehavior::VehicleBehavior(const BehaviorParams &bp, unsigned int num_interfaces) + : Behavior(bp, num_interfaces) + , mVehicle(nullptr) { + GetOwner()->QueryInterface(&mVehicle); +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Physics/Common/VehicleSystem.cpp b/src/Speed/Indep/Src/Physics/Common/VehicleSystem.cpp index 88c61d046..cb940d20e 100644 --- a/src/Speed/Indep/Src/Physics/Common/VehicleSystem.cpp +++ b/src/Speed/Indep/Src/Physics/Common/VehicleSystem.cpp @@ -1,7 +1,140 @@ #include "VehicleSystem.h" +#include "Speed/Indep/Src/Main/stubs.h" +#include "Speed/Indep/Src/Physics/Behaviors/ResetCar.h" +#include "Speed/Indep/Src/Physics/Behaviors/SimpleRigidBody.h" +#include "Speed/Indep/Src/Physics/Behaviors/SpikeStrip.h" +#include "Speed/Indep/Src/Sim/SimSubSystem.h" + +class SuspensionRacer { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SuspensionTraffic { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SuspensionSimple { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SuspensionTrailer { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SuspensionSpline { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class EngineRacer { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class EngineDragster { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class EngineSpline { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SimpleChopper { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class EngineTraffic { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DrawHeli { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DrawTraffic { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DrawNISCar { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DrawCopCar { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class DrawRaceCar { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SoundTraffic { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SoundCop { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SoundRacer { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; +class SoundHeli { + public: + static Behavior *Construct(const BehaviorParams ¶ms); +}; namespace VehicleSystem { float ENABLE_ROLL_STOPS_THRESHOLD = 0.2f; +float PAD_DEAD_ZONE = 0.05f; -}; +static void InitializeVehicleGlobals() { + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0, 0, 0, 0, 0, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0, 0, 0, 0, 0, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0, 0, 0, 0, 0, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); + dbattrib(0.0f, 0.0f, 0, 0.0f, 0.0f, 0); +} +static void InitializeGlobals() {} + +static void Init() { + InitializeGlobals(); + InitializeVehicleGlobals(); +} + +static void Shutdown() {} + +}; // namespace VehicleSystem + +static Sim::SubSystem _Physics_System_VehicleSystem("VehicleSystem", VehicleSystem::Init, VehicleSystem::Shutdown); + +UTL::COM::Factory::Prototype __SuspensionRacer("SuspensionRacer", SuspensionRacer::Construct); +UTL::COM::Factory::Prototype __SuspensionTraffic("SuspensionTraffic", SuspensionTraffic::Construct); +UTL::COM::Factory::Prototype __SuspensionSimple("SuspensionSimple", SuspensionSimple::Construct); +UTL::COM::Factory::Prototype __SuspensionTrailer("SuspensionTrailer", SuspensionTrailer::Construct); +UTL::COM::Factory::Prototype __SuspensionSpline("SuspensionSpline", SuspensionSpline::Construct); +UTL::COM::Factory::Prototype __EngineRacer("EngineRacer", EngineRacer::Construct); +UTL::COM::Factory::Prototype __EngineDragster("EngineDragster", EngineDragster::Construct); +UTL::COM::Factory::Prototype __EngineSpline("EngineSpline", EngineSpline::Construct); +UTL::COM::Factory::Prototype __SimpleChopper("SimpleChopper", SimpleChopper::Construct); +UTL::COM::Factory::Prototype __EngineTraffic("EngineTraffic", EngineTraffic::Construct); +UTL::COM::Factory::Prototype __DrawHeli("DrawHeli", DrawHeli::Construct); +UTL::COM::Factory::Prototype __DrawTraffic("DrawTraffic", DrawTraffic::Construct); +UTL::COM::Factory::Prototype __DrawNISCar("DrawNISCar", DrawNISCar::Construct); +UTL::COM::Factory::Prototype __DrawCopCar("DrawCopCar", DrawCopCar::Construct); +UTL::COM::Factory::Prototype __DrawRaceCar("DrawRaceCar", DrawRaceCar::Construct); +UTL::COM::Factory::Prototype __SoundTraffic("SoundTraffic", SoundTraffic::Construct); +UTL::COM::Factory::Prototype __SoundCop("SoundCop", SoundCop::Construct); +UTL::COM::Factory::Prototype __SoundRacer("SoundRacer", SoundRacer::Construct); +UTL::COM::Factory::Prototype __ResetCar("ResetCar", ResetCar::Construct); +UTL::COM::Factory::Prototype __SoundHeli("SoundHeli", SoundHeli::Construct); +UTL::COM::Factory::Prototype __SpikeStrip("SpikeStrip", SpikeStrip::Construct); diff --git a/src/Speed/Indep/Src/Physics/Dynamics.h b/src/Speed/Indep/Src/Physics/Dynamics.h index a55e117a9..bf875b896 100644 --- a/src/Speed/Indep/Src/Physics/Dynamics.h +++ b/src/Speed/Indep/Src/Physics/Dynamics.h @@ -32,8 +32,25 @@ class IEntity { namespace Articulation { +struct HJOINT__; + +enum eJointFlags { + JF_IMMOBILE_FEMALE = 2, + JF_IMMOBILE_MALE = 1, + JF_NONE = 0, +}; + +enum eConstraint { + CONICAL = 2, + HYPERBOLIC = 1, + PRISMATIC = 0, +}; + +HJOINT__ *Create(IEntity *female, const UMath::Vector3 &female_lever, IEntity *male, const UMath::Vector3 &male_lever, eJointFlags flags); +void Constrain(HJOINT__ *hjoint, IEntity *entity, const UMath::Matrix4 &mat, float theta1, float theta2, const UMath::Vector3 &post_vec, eConstraint type); void Release(IEntity *rb0); bool IsJoined(const IEntity *rb); +bool IsJoined(const IEntity *rb0, const IEntity *rb1); }; // namespace Articulation @@ -47,7 +64,7 @@ class IDynamicsEntity : public UTL::COM::IUnknown, public Dynamics::IEntity { IDynamicsEntity(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} - virtual ~IDynamicsEntity() {} + virtual ~IDynamicsEntity(); }; #endif diff --git a/src/Speed/Indep/Src/Physics/Dynamics/Collision.h b/src/Speed/Indep/Src/Physics/Dynamics/Collision.h index aa03a5c4b..680709aeb 100644 --- a/src/Speed/Indep/Src/Physics/Dynamics/Collision.h +++ b/src/Speed/Indep/Src/Physics/Dynamics/Collision.h @@ -70,6 +70,7 @@ class Geometry { static bool FindIntersection(const Geometry *A, const Geometry *B, Geometry *result); Geometry(); + Geometry(const UMath::Matrix4 &orient, const UMath::Vector3 &position, const UMath::Vector3 &dimension, Shape shape, const UMath::Vector3 &delta); void Set(const UMath::Matrix4 &orient, const UMath::Vector3 &position, const UMath::Vector3 &dimension, Shape shape, const UMath::Vector3 &delta); const UMath::Vector3 &GetPosition() const { diff --git a/src/Speed/Indep/Src/Physics/Dynamics/Inertia.h b/src/Speed/Indep/Src/Physics/Dynamics/Inertia.h index 3efa97436..17574c5e3 100644 --- a/src/Speed/Indep/Src/Physics/Dynamics/Inertia.h +++ b/src/Speed/Indep/Src/Physics/Dynamics/Inertia.h @@ -37,14 +37,15 @@ class Box : public Tensor { public: Box(float mass, float width, float height, float length) { - float x2 = width * width; - float y2 = height * height; - float z2 = length * length; - - x = (y2 + z2) / 1.2f; - y = (x2 + z2) / 1.2f; - z = (x2 + y2) / 1.2f; - + float x2 = width + width; + float y2 = height + height; + float z2 = length + length; + x2 *= x2; + y2 *= y2; + z2 *= z2; + x = (y2 + z2) / 12.0f; + y = (x2 + z2) / 12.0f; + z = (x2 + y2) / 12.0f; *this *= mass; } }; diff --git a/src/Speed/Indep/Src/Physics/Explosion.h b/src/Speed/Indep/Src/Physics/Explosion.h index d18e1b88b..ea67cadb2 100644 --- a/src/Speed/Indep/Src/Physics/Explosion.h +++ b/src/Speed/Indep/Src/Physics/Explosion.h @@ -5,6 +5,107 @@ #pragma once #endif +#include "Speed/Indep/Src/Interfaces/Simables/IExplosion.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h" +#include "Speed/Indep/Src/Interfaces/SimModels/IModel.h" +#include "Speed/Indep/Src/Physics/PhysicsObject.h" +struct ExplosionParams : public Sim::Param { + const UMath::Vector3 &fPosition; + float fExpansionSpeed; + float fRadius; + float fStartRadius; + HMODEL fSource; + bool fEffectSource; + bool fDamage; + unsigned int fTargets; + + static UCrc32 TypeName() { + static UCrc32 value("ExplosionParams"); + return value; + } +}; + +class Explosion : public PhysicsObject, public IExplosion { + public: + static ISimable *Construct(Sim::Param params); + 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); + } + } + + virtual const UMath::Vector3 &GetOrigin() const override { + return PhysicsObject::GetPosition(); + } + + virtual float GetExpansionSpeed() const override { + return mExpansionSpeed; + } + + virtual float GetMaximumRadius() const override { + return mExpansionRadius; + } + + virtual float GetRadius() const override; + + virtual HCAUSE GetCausality() const override { + return mCausality; + } + + virtual float GetCausalityTime() const override { + return mCauseTime; + } + + virtual bool HasDamage() const override { + return mDamages; + } + + virtual unsigned int GetTargets() const override { + return mTargets; + } + + virtual HMODEL GetSource() const override { + return mSource; + } + + virtual void SetCausality(HCAUSE from, float time) override { + mCausality = from; + mCauseTime = time; + } + + virtual IModel *GetModel() override { + return nullptr; + } + + virtual const IModel *GetModel() const override { + return nullptr; + } + + protected: + Explosion(const ExplosionParams ¶ms, Sim::Param sp); + virtual ~Explosion(); + virtual void OnTaskSimulate(float dT) override; + virtual void OnBehaviorChange(const UCrc32 &mechanic) override; + + private: + void TestCollisions(float dT); + void OnCollide(IRigidBody *other, float dT, float radius, const Dynamics::Collision::Geometry &explosion_sphere); + + const float mExpansionSpeed; + const float mExpansionRadius; + const HMODEL mSource; + ISimpleBody *mIRBSimple; + bool mEffectSource; + HCAUSE mCausality; + float mCauseTime; + const bool mDamages; + const unsigned int mTargets; +}; #endif diff --git a/src/Speed/Indep/Src/Physics/HeirarchyModel.h b/src/Speed/Indep/Src/Physics/HeirarchyModel.h new file mode 100644 index 000000000..b34748c8a --- /dev/null +++ b/src/Speed/Indep/Src/Physics/HeirarchyModel.h @@ -0,0 +1,85 @@ +#ifndef PHYSICS_HEIRARCHYMODEL_H +#define PHYSICS_HEIRARCHYMODEL_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/smackable.h" +#include "Speed/Indep/Src/Interfaces/IBody.h" +#include "Speed/Indep/Src/Interfaces/SimModels/ITriggerableModel.h" +#include "Speed/Indep/Src/Physics/Smackable.h" +#include "Speed/Indep/Src/Physics/SmackableTrigger.h" +#include "Speed/Indep/Src/Sim/SimModel.h" +#include "Speed/Indep/Src/World/ModelHeirarchy.hpp" + +class HeirarchyModel : public Sim::Model, public IBody, public ITriggerableModel, public Attrib::Gen::smackable { + 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); } + } + + HeirarchyModel(bHash32 rendermesh, const CollisionGeometry::Bounds *geometry, UCrc32 nodename, + HeirarchyModel *parent, const Attrib::Collection *attribs, const ModelHeirarchy *heirarchy, + unsigned int child_index, bool visible); + + ~HeirarchyModel() override; + + void OnBeginDraw() override; + void OnEndDraw() override; + bool OnDraw(Sim::Packet *service) override; + void OnBeginSimulation() override; + void OnEndSimulation() override; + + void GetTransform(UMath::Matrix4 &matrix) const override; + void GetLinearVelocity(UMath::Vector3 &velocity) const override { + Sim::Model::GetLinearVelocity(velocity); + } + void GetAngularVelocity(UMath::Vector3 &velocity) const override; + void GetDimension(UMath::Vector3 &dim) const override { + GetCollisionGeometry()->GetHalfDimensions(dim); + } + const Attrib::Instance &GetAttributes() const override { + return static_cast(this)->GetBase(); + } + unsigned int GetWorldID() const override { + return Sim::Model::GetWorldID(); + } + + IModel *SpawnModel(UCrc32 rendernode, UCrc32 collisionnode, UCrc32 attributes) override; + void HidePart(const UCrc32 &nodename) override; + void ShowPart(const UCrc32 &nodename) override; + bool IsPartVisible(const UCrc32 &nodename) const override; + void ReleaseModel() override; + + void PlaceTrigger(const UMath::Matrix4 &mat, bool retrigger) override; + + void OnProcessFrame(float dT) override; + virtual bool OnUpdateAvoidable(UMath::Vector3 &pos, float &sweep); + virtual bool OnRemoveOffScreen(float time); + + int FindHeirarchyChild(const UCrc32 &nodename) const; + void DisableTrigger(); + void SetTrigger(const UMath::Matrix4 &matrix, bool enable); + void RemoveTrigger(); + void SetCameraAvoidable(bool enable); + + SmackableTrigger *GetTrigger() { + return mTrigger; + } + + protected: + UMath::Vector4 mTriggerAvoid; + ModelHeirarchy *mHeirarchy; + bHash32 mRenderMesh; + SmackableTrigger *mTrigger; + float mOffScreenTimer; + unsigned short mHeirarchyNode; + unsigned short mFlags; + unsigned int mChildVisibility; + SmackableAvoidable *mAvoidable; +}; + +#endif diff --git a/src/Speed/Indep/Src/Physics/PVehicle.h b/src/Speed/Indep/Src/Physics/PVehicle.h index 53e0783ae..0f779b2c8 100644 --- a/src/Speed/Indep/Src/Physics/PVehicle.h +++ b/src/Speed/Indep/Src/Physics/PVehicle.h @@ -6,20 +6,41 @@ #endif #include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "Speed/Indep/Src/Debug/Debugable.h" +#include "Speed/Indep/Src/Interfaces/IAttributeable.h" #include "Speed/Indep/Src/Interfaces/SimActivities/IVehicleCache.h" +#include "Speed/Indep/Src/Interfaces/Simables/IExplodeable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IDamageable.h" #include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Src/Main/EventSequencer.h" +#include "Speed/Indep/Src/Physics/PhysicsObject.h" #include "Speed/Indep/Src/Sim/SimTypes.h" +#include "Speed/Indep/bWare/Inc/bList.hpp" + +DECLARE_CONTAINER_TYPE(ID_PVehicleChangeReq); + +struct FECustomizationRecord; +class IInput; +class ICollisionBody; +class ISuspension; +class IEngine; +class IDamageable; +class ITransmission; +class IVehicleAI; +class IArticulatedVehicle; +class IRenderable; +class IAudible; -// total size: 0x30 struct VehicleParams : public Sim::Param { - DriverClass carClass; // offset 0x10, size 0x4 - unsigned int carType; // offset 0x14, size 0x4 - const UMath::Vector3 &initialVec; // offset 0x18, size 0x4 - const UMath::Vector3 &initialPos; // offset 0x1C, size 0x4 - const FECustomizationRecord *customization; // offset 0x20, size 0x4 - IVehicleCache *VehicleCache; // offset 0x24, size 0x4 - const Physics::Info::Performance *matched; // offset 0x28, size 0x4 - unsigned int Flags; // offset 0x2C, size 0x4 + DriverClass carClass; + unsigned int carType; + const UMath::Vector3 &initialVec; + const UMath::Vector3 &initialPos; + const FECustomizationRecord *customization; + IVehicleCache *VehicleCache; + const Physics::Info::Performance *matched; + unsigned int Flags; VehicleParams(IVehicleCache *cache, DriverClass driver, unsigned int type, const UMath::Vector3 &dir, const UMath::Vector3 &pos, unsigned int flags, const FECustomizationRecord *upgrades, const Physics::Info::Performance *match) @@ -39,4 +60,317 @@ struct VehicleParams : public Sim::Param { } }; +class PVehicle : public PhysicsObject, + public bTNode, + public IVehicle, + public Debugable, + public EventSequencer::IContext, + public IExplodeable, + public IAttributeable { + public: + static void *operator new(unsigned int size) { + return gFastMem.Alloc(size, nullptr); + } + + static void operator delete(void *mem, unsigned int size) { + if (mem) { + gFastMem.Free(mem, size, nullptr); + } + } + + struct LaunchState { + LaunchState(); + void Clear(); + bool IsSet() const; + void Set(float time); + void Tick(float dT); + + float Time; // offset 0x0, size 0x4 + float Amount; // offset 0x4, size 0x4 + }; + + struct Resource { + enum eFlags { + VALID = 1, + SPOOL = 2, + NEEDS_COMPOSITING = 4, + }; + + Resource() {} + Resource(const Attrib::Gen::pvehicle &pvehicle, bool spool, bool is_player); + bool NeedsCompositing() const { return (Flags & NEEDS_COMPOSITING) != 0; } + bool IsValid() const { return (Flags & VALID) != 0; } + bool IsSpooled() const { return (Flags & SPOOL) != 0; } + void Invalidate() { Flags &= ~VALID; } + + CarType Type; // offset 0x0, size 0x4 + unsigned int Cost; // offset 0x4, size 0x4 + unsigned int Flags; // offset 0x8, size 0x4 + }; + + struct ManageNode { + ManageNode() {} + static void print(const ManageNode &n) {} + static bool sort_remove_resources(const ManageNode &lhs, const ManageNode &rhs) { + if (rhs.resource.Type != lhs.resource.Type) { + if (lhs.instancecount < rhs.instancecount) { + return true; + } + if (lhs.resource.Cost > rhs.resource.Cost) { + return true; + } + } + return lhs.resource.Type < rhs.resource.Type; + } + static bool sort_remove_instances(const ManageNode &lhs, const ManageNode &rhs) { + if (rhs.resource.Type != lhs.resource.Type) { + if (lhs.instancecount > rhs.instancecount) { + return true; + } + if (lhs.resource.Cost > rhs.resource.Cost) { + return true; + } + } + return lhs.resource.Type < rhs.resource.Type; + } + static bool sort_by_keep(const ManageNode &lhs, const ManageNode &rhs) { + if (rhs.resource.Type == lhs.resource.Type) { + if (lhs.result == VCR_WANT) { + if (rhs.result == VCR_DONTCARE) { + return true; + } + } + return false; + } + return lhs.resource.Type < rhs.resource.Type; + } + static bool is_kept(const ManageNode &h) { + return h.result == VCR_WANT; + } + + PVehicle *vehicle; // offset 0x0, size 0x4 + Resource resource; // offset 0x4, size 0xC + eVehicleCacheResult result; // offset 0x10, size 0x4 + unsigned int instancecount; // offset 0x14, size 0x4 + }; + + struct ManagementList : public UTL::FixedVector { + void print() const {} + }; + + typedef UTL::Std::list ResourceList; + typedef UTL::Std::map ChangeRequest; + + + PVehicle(DriverClass dc, const Attrib::Gen::pvehicle &attribs, const UMath::Vector3 &initialVec, + const UMath::Vector3 &initialPos, const CollisionGeometry::Bounds *bounds, + const FECustomizationRecord *customization, const Resource &resource, + const Physics::Info::Performance *performance, const char *cache_name); + virtual ~PVehicle(); + + static ISimable *Construct(Sim::Param params); + static bool MakeRoom(IVehicleCache *cache, const UTL::Std::list &resources); + static void CleanResources(); + static unsigned int CountResources(); + + virtual void Kill() override; + virtual void Reset() override; + virtual void DebugObject() override; + virtual bool OnTask(HSIMTASK htask, float dT) override; + virtual void OnTaskSimulate(float dT) override; + virtual void OnBehaviorChange(const UCrc32 &mechanic) override; + virtual EventSequencer::IEngine *GetEventSequencer() override { + return mSequencer; + } + virtual IModel *GetModel() override; + virtual const IModel *GetModel() const override; + virtual const UMath::Vector3 &GetPosition() const override { + return PhysicsObject::GetPosition(); + } + virtual void OnDebugDraw(); + virtual const ISimable *GetSimable() const override; + virtual ISimable *GetSimable() override; + virtual const Attrib::Gen::pvehicle &GetVehicleAttributes() const override { + return mAttributes; + } + virtual const UCrc32 &GetVehicleClass() const override { + return mClass; + } + virtual const char *GetVehicleName() const override { + return mAttributes.CollectionName(); + } + virtual unsigned int GetVehicleKey() const override { + return mAttributes.GetCollection(); + } + virtual void SetDriverClass(DriverClass cclass) override; + virtual DriverClass GetDriverClass() const override { + return mDriverClass; + } + virtual void SetDriverStyle(DriverStyle style) override; + virtual DriverStyle GetDriverStyle() const override { + return mDriverStyle; + } + virtual void SetBehaviorOverride(UCrc32 mechanic, UCrc32 behavior) override; + virtual void RemoveBehaviorOverride(UCrc32 mechanic) override; + virtual void CommitBehaviorOverrides() override; + virtual bool SetVehicleOnGround(const UMath::Vector3 &resetPos, const UMath::Vector3 &initialVec) override; + virtual char GetForceStop() override { + return mForceStop; + } + virtual void ForceStopOn(char forceStopBits) override; + virtual void ForceStopOff(char forceStopBits) override; + virtual bool IsOffWorld() const override { + return mOffWorld; + } + virtual CarType GetModelType() const override { + return mResources.Type; + } + virtual bool IsSpooled() const override { + return mResources.IsSpooled(); + } + virtual void SetStaging(bool staging) override; + virtual bool IsStaging() const override { + return mStaging; + } + virtual void Launch() override; + virtual float GetPerfectLaunch() const override; + virtual bool IsLoading() const override; + virtual float GetOffscreenTime() const override { + return mOffScreenTime; + } + virtual float GetOnScreenTime() const override { + return mOnScreenTime; + } + virtual bool InShock() const override { + if (mDamage == nullptr) { + return false; + } + return mDamage->InShock() > 0.0f; + } + virtual bool IsDestroyed() const override { + if (mDamage != nullptr) { + return mDamage->IsDestroyed(); + } else { + return false; + } + } + virtual float GetAbsoluteSpeed() const override { + return mAbsSpeed; + } + virtual float GetSpeedometer() const override { + return mSpeedometer; + } + virtual float GetSpeed() const override; + virtual void SetSpeed(float speed) override; + virtual void GlareOn(VehicleFX::ID glare) override; + virtual void GlareOff(VehicleFX::ID glare) override; + virtual bool IsGlareOn(VehicleFX::ID glare) override { + return (mGlareState & glare) != 0; + } + virtual bool IsCollidingWithSoftBarrier() override { + return false; + } + virtual bool IsAnimating() const override { + return mAnimating; + } + virtual void SetAnimating(bool animate) override; + virtual void Activate() override; + virtual void Deactivate() override; + virtual bool IsActive() const override { + return mPhysicsMode != PHYSICS_MODE_INACTIVE; + } + virtual void SetPhysicsMode(PhysicsMode mode) override; + virtual PhysicsMode GetPhysicsMode() const override { + return mPhysicsMode; + } + virtual IVehicleAI *GetAIVehiclePtr() const override { + return mAI; + } + virtual float GetSlipAngle() const override { + return mSlipAngle; + } + virtual const UMath::Vector3 &GetLocalVelocity() const override { + return mLocalVel; + } + virtual const FECustomizationRecord *GetCustomizations() const override { + return mCustomization; + } + virtual const Physics::Tunings *GetTunings() const override; + virtual void SetTunings(const Physics::Tunings &tunings) override; + virtual void ComputeHeading(UMath::Vector3 *v) override; + virtual bool GetPerformance(Physics::Info::Performance &performance) const override { + performance = mPerformance; + return mPerformanceValid; + } + virtual const char *GetCacheName() const override { + return mCacheName; + } + virtual bool OnExplosion(const UMath::Vector3 &normal, const UMath::Vector3 &position, float dT, IExplosion *explosion) override; + virtual bool SetDynamicData(const EventSequencer::System *system, EventDynamicData *data) override; + virtual void OnAttributeChange(const Attrib::Collection *collection, unsigned int attribkey) override; + + static bTList mInstances; + + private: + void OnBeginMode(const PhysicsMode mode); + void OnEndMode(const PhysicsMode mode); + void DoDebug(float dT); + void DoStaging(float dT); + void CheckOffWorld(); + void LoadBehaviors(const UMath::Vector3 &pos, const UMath::Matrix4 &matrix); + UCrc32 LookupBehaviorSignature(const Attrib::StringKey &mechanic) const; + void ReloadBehaviors(); + void UpdateLocalVelocities(); + void OnDisableModeling(); + void OnEnableModeling(); + void UpdateListing(); + void OnTaskFX(float dT); + + Attrib::Gen::pvehicle mAttributes; + FECustomizationRecord *mCustomization; + IInput *mInput; + ICollisionBody *mCollisionBody; + ISuspension *mSuspension; + IEngine *mEngine; + IDamageable *mDamage; + ITransmission *mTranny; + IVehicleAI *mAI; + IArticulatedVehicle *mArticulation; + IRenderable *mRenderable; + IAudible *mAudible; + EventSequencer::IEngine *mSequencer; + HSIMTASK mTaskFX; + UCrc32 mClass; + float mSpeed; + float mAbsSpeed; + float mSpeedometer; + float mTimeInAir; + float mSlipAngle; + unsigned int mWheelsOnGround; + UMath::Vector3 mLocalVel; + DriverClass mDriverClass; + DriverStyle mDriverStyle; + unsigned int mGlareState; + float mStartingNOS; + float mBrakeTime; + char mForceStop; + PhysicsMode mPhysicsMode; + bool mAnimating; + bool mStaging; + LaunchState mPerfectLaunch; + UTL::Std::map mBehaviorOverrides; + bool mOverrideDirty; + const CollisionGeometry::Bounds *mBounds; + bool mIsModeling; + float mOffScreenTime; + float mOnScreenTime; + bool mOffWorld; + static bool mRunDyno; + bool mHasDyno; + Resource mResources; + Physics::Info::Performance mPerformance; + bool mPerformanceValid; + const char *mCacheName; +}; + #endif diff --git a/src/Speed/Indep/Src/Physics/PhysicsInfo.cpp b/src/Speed/Indep/Src/Physics/PhysicsInfo.cpp index 8cec2422c..bfca73f03 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsInfo.cpp +++ b/src/Speed/Indep/Src/Physics/PhysicsInfo.cpp @@ -1,18 +1,32 @@ #include "PhysicsInfo.hpp" #include "PhysicsTunings.h" +#include "PhysicsUpgrades.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" #include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/brakes.h" +#include "Speed/Indep/Src/Misc/Table.hpp" #include "Speed/Indep/Src/Sim/UTil.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" #include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" -using namespace Attrib::Gen; -// Credits: Brawltendo -float Physics::Info::AerodynamicDownforce(const chassis &chassis, const float speed) { +static PerfStats top_stats; +static PerfStats bottom_stats; +static PerformanceMaps TheStockCars; +static int Physics_Info_initialized; +Physics::Info::Performance Physics::Info::PerformanceWeights[7]; + +void Physics::Info::Performance::Default() { + TopSpeed = 0.0f; + Handling = 0.0f; + Acceleration = 0.0f; +} + +float Physics::Info::AerodynamicDownforce(const Attrib::Gen::chassis &chassis, float speed) { return speed * 2 * chassis.AERO_COEFFICIENT() * 1000.0f; } -// Credits: Brawltendo -float Physics::Info::EngineInertia(const engine &engine, const bool loaded) { +float Physics::Info::EngineInertia(const Attrib::Gen::engine &engine, bool loaded) { float scale; if (loaded) scale = 1.f; @@ -21,8 +35,12 @@ float Physics::Info::EngineInertia(const engine &engine, const bool loaded) { return scale * (engine.FLYWHEEL_MASS() * 0.025f + 0.25f); } -// Credits: Brawltendo -Physics::Info::eInductionType Physics::Info::InductionType(const induction &induction) { +Physics::Info::eInductionType Physics::Info::InductionType(const Attrib::Gen::pvehicle &pvehicle) { + const Attrib::Gen::induction ind(pvehicle.induction(0), 0, nullptr); + return InductionType(ind); +} + +Physics::Info::eInductionType Physics::Info::InductionType(const Attrib::Gen::induction &induction) { if (induction.HIGH_BOOST() > 0.0f || induction.LOW_BOOST() > 0.0f) { // turbochargers don't produce significant boost until above the boost threshold (the lowest engine RPM at which it will spool up) // meanwhile superchargers apply boost proportionally to the engine RPM, so this param isn't needed there @@ -36,8 +54,16 @@ Physics::Info::eInductionType Physics::Info::InductionType(const induction &indu } } -// Credits: Brawltendo -float Physics::Info::NosBoost(const nos &nos, const Tunings *tunings) { +bool Physics::Info::HasNos(const Attrib::Gen::pvehicle &pvehicle) { + const Attrib::Gen::nos nos(pvehicle.nos(0), 0, nullptr); + return nos.TORQUE_BOOST() > 0.0f && nos.NOS_CAPACITY() > 0.0f; +} + +bool Physics::Info::HasRunflatTires(const Attrib::Gen::pvehicle &pvehicle) { + return false; +} + +float Physics::Info::NosBoost(const Attrib::Gen::nos &nos, const Tunings *tunings) { float torque_scale = 1.0f; float boost = nos.TORQUE_BOOST(); if (tunings) { @@ -46,8 +72,7 @@ float Physics::Info::NosBoost(const nos &nos, const Tunings *tunings) { return boost + torque_scale; } -// Credits: Brawltendo -float Physics::Info::NosCapacity(const nos &nos, const Tunings *tunings) { +float Physics::Info::NosCapacity(const Attrib::Gen::nos &nos, const Tunings *tunings) { float capacity = nos.NOS_CAPACITY(); if (tunings) { capacity -= capacity * tunings->Value[Physics::Tunings::NOS] * 0.25f; @@ -55,8 +80,7 @@ float Physics::Info::NosCapacity(const nos &nos, const Tunings *tunings) { return capacity; } -// Credits: Brawltendo -float Physics::Info::InductionRPM(const engine &engine, const induction &induction, const Tunings *tunings) { +float Physics::Info::InductionRPM(const Attrib::Gen::engine &engine, const Attrib::Gen::induction &induction, const Tunings *tunings) { float spool = induction.SPOOL(); // tune the (normalized) RPM at which forced induction kicks in @@ -75,8 +99,7 @@ float Physics::Info::InductionRPM(const engine &engine, const induction &inducti return spool * (engine.RED_LINE() - engine.IDLE()) + engine.IDLE(); } -// Credits: Brawltendo -float Physics::Info::InductionBoost(const engine &engine, const induction &induction, float rpm, float spool, const Tunings *tunings, float *psi) { +float Physics::Info::InductionBoost(const Attrib::Gen::engine &engine, const Attrib::Gen::induction &induction, float rpm, float spool, const Tunings *tunings, float *psi) { if (psi) { *psi = 0.0f; } @@ -118,7 +141,6 @@ float Physics::Info::InductionBoost(const engine &engine, const induction &induc return induction_boost * spool; } -// Credits: Brawltendo float Physics::Info::Torque(const Attrib::Gen::engine &engine, float rpm) { float rpm_min = engine.IDLE(); float rpm_max = engine.MAX_RPM(); @@ -129,28 +151,27 @@ float Physics::Info::Torque(const Attrib::Gen::engine &engine, float rpm) { unsigned int index = UTIL_InterprolateIndex(numpts - 1, rpm, rpm_min, rpm_max, ratio); float power = engine.TORQUE(index); unsigned int secondIndex = UMath::Min(numpts - 1, index + 1); - return UMath::Lerp(power, engine.TORQUE(secondIndex), ratio); + float step = engine.TORQUE(secondIndex) - power; + float torque = power + step * ratio; + return torque; } return 0.0f; } -// Credits: Brawltendo -float Physics::Info::WheelDiameter(const tires &tires, bool front) { +float Physics::Info::WheelDiameter(const Attrib::Gen::tires &tires, bool front) { int axle = front ? 0 : 1; float diameter = INCH2METERS(tires.RIM_SIZE().At(axle)); return diameter + tires.SECTION_WIDTH().At(axle) * 0.001f * 2.0f * (tires.ASPECT_RATIO().At(axle) * 0.01f); } -// float Physics::Info::MaxInductedTorque(const Attrib::Gen::pvehicle &pvehicle, float &atrpm, const Tunings *tunings) { -// Attrib::Gen::engine engine(pvehicle.engine(), 0, NULL); -// Attrib::Gen::induction induction(pvehicle.induction()); -// return MaxInductedTorque(engine, induction, atrpm, tunings); -// } +float Physics::Info::WheelDiameter(const Attrib::Gen::pvehicle &pvehicle, bool front) { + const Attrib::Gen::tires t(pvehicle.tires(0), 0, nullptr); + return WheelDiameter(t, front); +} -// Credits: Brawltendo -// TODO not matching on GC yet -bool Physics::Info::ShiftPoints(const transmission &transmission, const engine &engine, const induction &induction, float *shift_up, +// NON_MATCHING: register allocation swap f31/f30 for rpm/converter_ratio +bool Physics::Info::ShiftPoints(const Attrib::Gen::transmission &transmission, const Attrib::Gen::engine &engine, const Attrib::Gen::induction &induction, float *shift_up, float *shift_down, unsigned int numpts) { for (int i = 0; i < numpts; ++i) { shift_up[i] = 0.0f; @@ -167,38 +188,31 @@ bool Physics::Info::ShiftPoints(const transmission &transmission, const engine & for (j = G_FIRST; j < topgear; ++j) { float g1 = transmission.GEAR_RATIO(j); float g2 = transmission.GEAR_RATIO(j + 1); - float rpm = (redline + engine.IDLE()) * 0.5f; + float rpm = (engine.IDLE() + redline) * 0.5f; float max = rpm; - int flag = 0; + int flag = 1; if (rpm < redline) { - // find the upshift RPM for this gear using predicted engine torque - while (!flag) { - // seems like the rpm and spool params are swapped in both instances - // so either it's a mistake that was copy-pasted or it was a deliberate choice + while (flag) { float currenttorque = Torque(engine, max) * (InductionBoost(engine, induction, 1.0f, max, NULL, NULL) + 1.0f); float shiftuptorque; if (UMath::Abs(g1) > 0.00001f) { float ratio = g2 / g1; float next_rpm = ratio * max; - shiftuptorque = Torque(engine, next_rpm) * (InductionBoost(engine, induction, 1.0f, next_rpm, NULL, NULL) + 1.0f) * g2 / g1; + shiftuptorque = Torque(engine, next_rpm) * g2 / g1 * (InductionBoost(engine, induction, 1.0f, next_rpm, NULL, NULL) + 1.0f); } else { shiftuptorque = 0.0f; } - // set the upshift RPM to the current max - if (shiftuptorque > currenttorque) + if (shiftuptorque > currenttorque) { + shift_up[j] = max; + flag = 0; break; + } max += 50.0f; - // set the upshift RPM to the redline RPM - flag = !(max < redline); - } - if (!flag) { - shift_up[j] = max; + if (max >= redline) break; } - } else { - flag = 1; } if (flag) { shift_up[j] = redline - 100.0f; @@ -206,7 +220,7 @@ bool Physics::Info::ShiftPoints(const transmission &transmission, const engine & // calculate downshift RPM for the next gear if (UMath::Abs(g1) > 0.00001f) { - shift_down[j + 1] = (g2 / g1) * shift_up[j]; + shift_down[j + 1] = shift_up[j] * g2 / g1; } else { shift_down[j + 1] = 0.0f; } @@ -216,7 +230,6 @@ bool Physics::Info::ShiftPoints(const transmission &transmission, const engine & return true; } -// Credits: Brawltendo Mps Physics::Info::Speedometer(const Attrib::Gen::transmission &transmission, const Attrib::Gen::engine &engine, const Attrib::Gen::tires &tires, Rpm rpm, GearID gear, const Tunings *tunings) { float speed = 0.0f; @@ -238,3 +251,667 @@ Mps Physics::Info::Speedometer(const Attrib::Gen::transmission &transmission, co return speed; } + +float Physics::Info::MaxInductedPower(const Attrib::Gen::pvehicle &pvehicle, const Tunings *tunings) { + Attrib::Gen::engine engine(pvehicle.engine(0), 0, nullptr); + Attrib::Gen::induction induction(pvehicle.induction(0), 0, nullptr); + unsigned int num_torque = engine.Num_TORQUE(); + + if (num_torque < 2) { + return 0.0f; + } + + float result = 0.0f; + float rpm = engine.IDLE(); + float delta_rpm = (engine.MAX_RPM() - engine.IDLE()) / static_cast(engine.Num_TORQUE() - 1); + + for (unsigned int i = 0; i < engine.Num_TORQUE(); i++) { + float pt_torque = engine.TORQUE(i) * (InductionBoost(engine, induction, rpm, 1.0f, tunings, nullptr) + 1.0f); + float hp = FTLB2HP(pt_torque, rpm); + if (hp > result) { + result = hp; + } + rpm += delta_rpm; + } + + return result; +} + +float Physics::Info::AvgInductedTorque(const Attrib::Gen::engine &engine, const Attrib::Gen::induction &induction, const Attrib::Gen::transmission &transmission, bool from_peak, const Tunings *tunings) { + unsigned int num_torque = engine.Num_TORQUE(); + if (num_torque < 2) { + return 0.0f; + } + + float peak_torque_rpm; + float peak_torque = MaxInductedTorque(engine, induction, peak_torque_rpm, tunings); + if (!(peak_torque > 0.0f)) { + return 0.0f; + } + + float torque_converter = transmission.TORQUE_CONVERTER(); + float torque; + float rpm = engine.IDLE(); + float total_torque = 0.0f; + float count = 0.0f; + float delta_rpm = (engine.MAX_RPM() - engine.IDLE()) / static_cast(engine.Num_TORQUE() - 1); + + for (unsigned int i = 0; i < engine.Num_TORQUE(); i++) { + if (!from_peak || rpm >= peak_torque_rpm) { + float converter_ratio = 1.0f + torque_converter * (1.0f - UMath::Ramp(rpm, engine.IDLE(), peak_torque_rpm)); + torque = engine.TORQUE(i) * (InductionBoost(engine, induction, rpm, 1.0f, tunings, nullptr) + 1.0f); + float torque_pt = torque * converter_ratio; + total_torque += torque_pt; + count += 1.0f; + } + rpm += delta_rpm; + if (rpm >= engine.RED_LINE()) break; + } + + if (count > 0.0f) { + return total_torque / count; + } + return 0.0f; +} + +float Physics::Info::MaxInductedTorque(const Attrib::Gen::engine &eng, const Attrib::Gen::induction &ind, float &atrpm, const Tunings *tunings) { + if (eng.Num_TORQUE() < 2) { + atrpm = eng.IDLE(); + return 0.0f; + } + + float torque = 0.0f; + atrpm = eng.IDLE(); + float rpm = eng.IDLE(); + float delta_rpm = (eng.MAX_RPM() - eng.IDLE()) / static_cast(eng.Num_TORQUE() - 1); + + for (unsigned int i = 0; i < eng.Num_TORQUE(); i++) { + float pt_torque = eng.TORQUE(i) * (InductionBoost(eng, ind, rpm, 1.0f, tunings, nullptr) + 1.0f); + if (pt_torque > torque) { + atrpm = rpm; + torque = pt_torque; + } + rpm += delta_rpm; + } + + atrpm = UMath::Clamp(atrpm, eng.IDLE(), eng.RED_LINE()); + return torque; +} + +float Physics::Info::MaxInductedTorque(const Attrib::Gen::pvehicle &pvehicle, float &atrpm, const Tunings *tunings) { + const Attrib::Gen::engine eng(pvehicle.engine(0), 0, nullptr); + const Attrib::Gen::induction ind(pvehicle.induction(0), 0, nullptr); + return MaxInductedTorque(eng, ind, atrpm, tunings); +} + +float Physics::Info::MaxTorque(const Attrib::Gen::engine &eng, float &atrpm) { + float torque = 0.0f; + int max_pt = 0; + unsigned int num_torque = eng.Num_TORQUE(); + + if (num_torque == 0) { + atrpm = torque; + } else { + for (unsigned int i = 0; i < eng.Num_TORQUE(); i++) { + float pt_torque = eng.TORQUE(i); + if (pt_torque > torque) { + max_pt = i; + torque = pt_torque; + } + } + + atrpm = eng.IDLE(); + if (num_torque > 1) { + float rpm_ratio = static_cast(max_pt) / static_cast(num_torque - 1); + atrpm = rpm_ratio * (eng.MAX_RPM() - eng.IDLE()) + atrpm; + } + + atrpm = UMath::Clamp(atrpm, eng.IDLE(), eng.RED_LINE()); + } + return torque; +} + +float Physics::Info::Redline(const Attrib::Gen::engine &engine) { + return engine.RED_LINE(); +} + +float Physics::Info::Redline(const Attrib::Gen::pvehicle &pvehicle) { + const Attrib::Gen::engine eng(pvehicle.engine(0), 0, nullptr); + return Redline(eng); +} + +unsigned int Physics::Info::NumFowardGears(const Attrib::Gen::transmission &transmission) { + unsigned int num_ratios = transmission.Num_GEAR_RATIO(); + if (num_ratios > 2) { + return num_ratios - 2; + } + return 0; +} + +unsigned int Physics::Info::NumFowardGears(const Attrib::Gen::pvehicle &pvehicle) { + const Attrib::Gen::transmission trans(pvehicle.transmission(0), 0, nullptr); + return NumFowardGears(trans); +} + +bool Physics::Info::HasPerformanceRatings(const Attrib::Gen::pvehicle &pvehicle) { + float base_handling = pvehicle.HandlingRating(0); + float top_handling = pvehicle.HandlingRating(1); + return base_handling < top_handling && 0.0f < top_handling; +} + +bool PerfStats::Fetch(const Attrib::Gen::pvehicle &vehicle, bVector2 *graph_data, int *num_data) { + Time0To100 = 0.0f; + TopSpeed = 0.0f; + HandlingRating = 0.0f; + + Attrib::Gen::engine eng(vehicle.engine(0), 0, nullptr); + Attrib::Gen::induction ind(vehicle.induction(0), 0, nullptr); + Attrib::Gen::transmission trans(vehicle.transmission(0), 0, nullptr); + Attrib::Gen::chassis chas(vehicle.chassis(0), 0, nullptr); + Attrib::Gen::tires tir(vehicle.tires(0), 0, nullptr); + Attrib::Gen::brakes bra(vehicle.brakes(0).GetCollection(), 0, nullptr); + Attrib::Gen::nos n(vehicle.nos(0), 0, nullptr); + + float max_torque_rpm; + Physics::Info::MaxTorque(eng, max_torque_rpm); + float wheel_diameter = Physics::Info::WheelDiameter(vehicle, false); + float idle_rpm = eng.IDLE(); + float redline_rpm = eng.RED_LINE(); + float min_w = RPM2RPS(idle_rpm); + float max_w = RPM2RPS(redline_rpm); + float wheel_radius = wheel_diameter * 0.5f; + float final_gear = trans.FINAL_GEAR(); + float speed_limiter = MPH2MPS(eng.SPEED_LIMITER(0)); + + float shift_up[12]; + float shift_down[12]; + + if (!Physics::Info::ShiftPoints(trans, eng, ind, shift_up, shift_down, 12) || + wheel_radius <= 0.0f || final_gear <= 0.0f) { + return false; + } + + float speed = 0.0f; + unsigned int gear = 0; + float time = 0.0f; + float mass = vehicle.MASS(); + float dT = 0.125f; + int data_index = 0; + if (graph_data != nullptr) { + dT = 1.0f; + } + int max_data_index = 0; + if (num_data != nullptr) { + max_data_index = *num_data; + } + float power_range = max_w - min_w; + unsigned int num_gears = Physics::Info::NumFowardGears(vehicle); + unsigned int last_gear = num_gears - 1; + + do { + float total_gear_ratio = trans.GEAR_RATIO(gear + G_FIRST) * final_gear; + float differential_rpm = RPS2RPM(min_w + (speed / wheel_radius) * total_gear_ratio * (power_range / max_w)); + float rpm = UMath::Min(differential_rpm, redline_rpm); + rpm = UMath::Max(rpm, idle_rpm); + + if (gear == 0) { + rpm = UMath::Max(rpm, max_torque_rpm); + } + + float torque = Physics::Info::Torque(eng, rpm) * FTLB2NM(1.0f); + float boost = Physics::Info::InductionBoost(eng, ind, rpm, 1.0f, nullptr, nullptr); + float nos_capacity = Physics::Info::NosCapacity(n, nullptr); + float force = torque * (boost + 1.0f) * total_gear_ratio; + if (time < nos_capacity && speed > 5.0f) { + float nos_boost = Physics::Info::NosBoost(n, nullptr); + force *= nos_boost; + } + float accel = (force / wheel_radius) / mass; + + if (graph_data != nullptr) { + int idx = data_index; + if (max_data_index < idx) { + idx = max_data_index; + } + graph_data[idx].y = accel; + graph_data[idx].x = speed; + data_index++; + } + + float drag = UMath::Abs(speed * speed * chas.DRAG_COEFFICIENT()) / mass; + speed = (speed + accel * dT) - drag * dT; + + if (speed >= MPH2MPS(100.0f) && Time0To100 <= 0.0f) { + Time0To100 = time; + dT = 1.0f; + } + + if (speed_limiter <= 0.0f || speed < speed_limiter) { + if (gear == last_gear && + (accel < drag || redline_rpm <= rpm) && + TopSpeed <= 0.0f) { + TopSpeed = speed; + } + } else { + TopSpeed = speed_limiter; + } + + time += dT; + + if (TopSpeed > 0.0f) { + if (Time0To100 > 0.0f) break; + } + + if (rpm >= shift_up[gear + G_FIRST]) { + unsigned int next_gear = gear + 1; + gear = last_gear; + if (next_gear < last_gear) { + gear = next_gear; + } + } + } while (time < 120.0f); + + if (gear == last_gear || TopSpeed <= 0.0f) { + TopSpeed = speed; + } + + float base_handling = vehicle.HandlingRating(0); + float top_handling = vehicle.HandlingRating(1); + float handling_sum = 0.0f; + float diff = top_handling - base_handling; + float *pw = &Physics::Info::PerformanceWeights[0].Handling; + float weight_sum = 0.0f; + Physics::Upgrades::Type type = Physics::Upgrades::PUT_TIRES; + do { + Physics::Upgrades::Type next = static_cast(type + Physics::Upgrades::PUT_BRAKES); + weight_sum += *pw; + float pct = Physics::Upgrades::GetPercent(vehicle, type); + float w = *pw; + pw += 3; + handling_sum += pct * w; + type = next; + } while (static_cast(type) < 7); + if (weight_sum > 1e-6f) { + handling_sum /= weight_sum; + } + HandlingRating = UMath::Lerp(base_handling, top_handling, handling_sum); + + if (num_data != nullptr) { + *num_data = data_index; + } + + bool success = false; + if (TopSpeed > 0.0f) { + success = Time0To100 > 0.0f; + } + return success; +} + +void PerfLevel::Print(const char *) { +} + +void PerfLevel::Rate() { + Stock.Handling = UMath::Ramp(Stats.HandlingRating, bottom_stats.HandlingRating, top_stats.HandlingRating); + Stock.Acceleration = 1.0f - UMath::Ramp(Stats.Time0To100, bottom_stats.Time0To100, top_stats.Time0To100); + Stock.TopSpeed = UMath::Ramp(Stats.TopSpeed, bottom_stats.TopSpeed, top_stats.TopSpeed); +} + +bool PerfLevel::Analyze(const Attrib::Gen::pvehicle &pvehicle) { + Analyzed = false; + if (!Stats.Fetch(pvehicle, nullptr, nullptr)) { + return false; + } + Analyzed = true; + return true; +} + +void PerformanceMaps::FindLimits(float direction, PerfStats &out) const { + PerfStats temp; + bMemSet(&temp, 0, sizeof(PerfStats)); + out = temp; + + for (const_iterator iter = begin(); iter != end(); iter++) { + const PerfLevel &p = *iter; + if (iter == begin()) { + out = p.Stats; + } else { + if (p.Stats.HandlingRating * direction > out.HandlingRating * direction) { + out.HandlingRating = p.Stats.HandlingRating; + } + if (p.Stats.Time0To100 * direction > out.Time0To100 * direction) { + out.Time0To100 = p.Stats.Time0To100; + } + if (p.Stats.TopSpeed * direction > out.TopSpeed * direction) { + out.TopSpeed = p.Stats.TopSpeed; + } + } + } +} + +void Physics::Info::Init() { + const Attrib::Class *aclass = Attrib::Database::Get().GetClass(Attrib::Gen::pvehicle::ClassKey()); + unsigned int key = aclass->GetFirstCollection(); + + PerformanceMaps all_cars; + PerformanceMaps upgraded_cars; + + while (key != 0) { + Attrib::Gen::pvehicle vehicle(key, 0, nullptr); + if (vehicle.MODEL().GetHash32() != UCrc32::kNull.GetValue() && !vehicle.IsDynamic()) { + if (HasPerformanceRatings(vehicle)) { + PerfLevel performance(key); + if (performance.Analyze(vehicle)) { + TheStockCars.push_back(performance); + all_cars.push_back(performance); + } + } + } + Physics::Upgrades::Flush(); + key = aclass->GetNextCollection(key); + } + + for (PerformanceMaps::iterator iter = TheStockCars.begin(); iter != TheStockCars.end(); iter++) { + PerfLevel &p = *iter; + Attrib::Gen::pvehicle vehicle(p.Key, 0, nullptr); + if (Physics::Upgrades::SetMaximum(vehicle)) { + PerfLevel performance(p.Key); + if (performance.Analyze(vehicle)) { + upgraded_cars.push_back(performance); + all_cars.push_back(performance); + } + } + Physics::Upgrades::Flush(); + } + + int count = 0; + for (PerformanceMaps::iterator iter = TheStockCars.begin(); iter != TheStockCars.end(); iter++) { + count++; + } + + if (count == 0) { + return; + } + + all_cars.FindLimits(-1.0f, bottom_stats); + all_cars.FindLimits(1.0f, top_stats); + + for (PerformanceMaps::iterator iter = TheStockCars.begin(); iter != TheStockCars.end(); iter++) { + PerfLevel &p = *iter; + p.Rate(); + p.Print("Stock Performance"); + } + + for (PerformanceMaps::iterator iter = upgraded_cars.begin(); iter != upgraded_cars.end(); iter++) { + PerfLevel &p = *iter; + p.Rate(); + p.Print("Upgraded Performance"); + } + + for (PerformanceMaps::iterator iter = TheStockCars.begin(); iter != TheStockCars.end(); iter++) { + PerfLevel &p = *iter; + p.Upgraded = p.Stock; + for (PerformanceMaps::iterator iter2 = upgraded_cars.begin(); iter2 != upgraded_cars.end(); iter2++) { + if (p.Key == (*iter2).Key) { + p.Upgraded = (*iter2).Stock; + break; + } + } + } + + Physics_Info_initialized = 1; +} + +bool Physics::Info::ComputeAccelerationTable(const Attrib::Gen::pvehicle &vehicle, float &top_speed, float *table, int num_entries) { + Attrib::Gen::transmission trans(vehicle.transmission(0), 0, nullptr); + Attrib::Gen::tires tir(vehicle.tires(0), 0, nullptr); + Attrib::Gen::chassis chas(vehicle.chassis(0), 0, nullptr); + Attrib::Gen::engine eng(vehicle.engine(0), 0, nullptr); + Attrib::Gen::induction ind(vehicle.induction(0), 0, nullptr); + + float avg_torque = AvgInductedTorque(eng, ind, trans, true, nullptr); + avg_torque = avg_torque * FTLB2NM(1.0f); + + if (avg_torque <= 0.0f || num_entries < 2 || table == nullptr) { + return false; + } + + bVector2 graph_data[10]; + + unsigned int num_gears = NumFowardGears(trans); + if (num_gears == 0) { + return false; + } + + float mass = vehicle.MASS(); + float final_gear = trans.FINAL_GEAR(); + float wheel_radius = WheelDiameter(tir, false) * 0.5f; + + if (wheel_radius <= 0.001f) { + return false; + } + + top_speed = 0.0f; + int graph_max = 0; + float prev_accel = 0.0f; + float prev_speed = 0.0f; + + for (unsigned int foward_gear = 0; foward_gear < num_gears; foward_gear++) { + float gear_ratio = trans.GEAR_RATIO(foward_gear + G_FIRST) * final_gear; + float gear_eff = trans.GEAR_EFFICIENCY(foward_gear + G_FIRST); + + if (gear_ratio <= 0.0f) break; + + float speed = (eng.RED_LINE() * RPM2RPS(1.0f) * wheel_radius) / gear_ratio; + float force = (avg_torque * gear_ratio * gear_eff) / wheel_radius; + float drag = speed * speed * chas.DRAG_COEFFICIENT(); + float accel = (force - drag) / mass; + + if (accel <= 0.0f) { + if (prev_accel <= 0.0f) break; + speed = UMath::Lerp(prev_speed, speed, prev_accel / (prev_accel - accel)); + accel = 0.0f; + } + + top_speed = UMath::Max(top_speed, speed); + + graph_data[graph_max].x = speed; + graph_data[graph_max].y = accel; + graph_max++; + prev_accel = accel; + prev_speed = speed; + } + + if (graph_max == 0) { + return false; + } + + Graph accel_graph(graph_data, graph_max); + float max_speed = top_speed; + + if (max_speed <= 0.0f) { + return false; + } + + float inc = max_speed / static_cast(num_entries - 1); + for (int i = 0; i < num_entries; i++) { + table[i] = accel_graph.GetValue(inc * static_cast(i)); + } + + return true; +} + +bool Physics::Info::EstimatePerformance(const Attrib::Gen::pvehicle &vehicle, Performance &perf) { + Performance stock; + Performance upgraded; + + bool success = GetStockPerformance(vehicle, stock); + bool result = false; + if (success) { + success = GetMaximumPerformance(vehicle, upgraded); + if (success) { + perf.TopSpeed = 0.0f; + perf.Acceleration = 0.0f; + perf.Handling = 0.0f; + + Performance weights; + Performance junk; + Performance junk_weights; + + Physics::Info::Performance *pw = PerformanceWeights; + Physics::Upgrades::Type type = Physics::Upgrades::PUT_TIRES; + do { + float value = Physics::Upgrades::GetPercent(vehicle, type); + + junk_weights.Handling += pw->Handling; + junk_weights.Acceleration += pw->Acceleration; + junk_weights.TopSpeed += pw->TopSpeed; + + if (Physics::Upgrades::GetJunkman(vehicle, type)) { + junk.Handling += pw->Handling; + junk.Acceleration += pw->Acceleration; + junk.TopSpeed += pw->TopSpeed; + } + + float hw = pw->Handling; + type = static_cast(type + Physics::Upgrades::PUT_BRAKES); + float handling = hw * value + perf.Handling; + weights.Handling += hw; + perf.Handling = handling; + weights.Acceleration += pw->Acceleration; + perf.Acceleration += pw->Acceleration * value; + weights.TopSpeed += pw->TopSpeed; + perf.TopSpeed += pw->TopSpeed * value; + pw++; + } while (static_cast(type) < 7); + + if (weights.Handling > 1e-6f) { + perf.Handling = perf.Handling / weights.Handling; + } + if (weights.Acceleration > 1e-6f) { + perf.Acceleration = perf.Acceleration / weights.Acceleration; + } + if (weights.TopSpeed > 1e-6f) { + perf.TopSpeed = perf.TopSpeed / weights.TopSpeed; + } + + if (junk_weights.Handling > 1e-6f) { + float junk_ratio = junk.Handling / junk_weights.Handling; + upgraded.Handling = UMath::Lerp(upgraded.Handling, 1.0f, junk_ratio); + float bonus = junk_ratio * 0.33f; + float h = perf.Handling * (bonus + 1.0f); + perf.Handling = h; + stock.Handling = UMath::Lerp(stock.Handling, upgraded.Handling, bonus); + perf.Handling = UMath::Min(h, 1.0f); + } + + if (junk_weights.Acceleration > 1e-6f) { + float junk_ratio = junk.Acceleration / junk_weights.Acceleration; + upgraded.Acceleration = UMath::Lerp(upgraded.Acceleration, 1.0f, junk_ratio); + float bonus = junk_ratio * 0.33f; + float a = perf.Acceleration * (bonus + 1.0f); + perf.Acceleration = a; + stock.Acceleration = UMath::Lerp(stock.Acceleration, upgraded.Acceleration, bonus); + perf.Acceleration = UMath::Min(a, 1.0f); + } + + if (junk_weights.TopSpeed > 1e-6f) { + float junk_ratio = junk.TopSpeed / junk_weights.TopSpeed; + upgraded.TopSpeed = UMath::Lerp(upgraded.TopSpeed, 1.0f, junk_ratio); + float bonus = junk_ratio * 0.33f; + float t = perf.TopSpeed * (bonus + 1.0f); + perf.TopSpeed = t; + stock.TopSpeed = UMath::Lerp(stock.TopSpeed, upgraded.TopSpeed, bonus); + perf.TopSpeed = UMath::Min(t, 1.0f); + } + + result = true; + perf.Handling = UMath::Lerp(stock.Handling, upgraded.Handling, perf.Handling); + perf.Acceleration = UMath::Lerp(stock.Acceleration, upgraded.Acceleration, perf.Acceleration); + perf.TopSpeed = UMath::Lerp(stock.TopSpeed, upgraded.TopSpeed, perf.TopSpeed); + } else { + result = false; + } + } + return result; +} + +bool Physics::Info::ComputePerformance(const Attrib::Gen::pvehicle &vehicle, Performance &perf) { + if (!HasPerformanceRatings(vehicle)) { + return false; + } + + for (PerformanceMaps::iterator iter = TheStockCars.begin(); iter != TheStockCars.end(); iter++) { + if ((*iter).Key == vehicle.GetCollection()) { + perf = (*iter).Stock; + return true; + } + } + + unsigned int coll_key = vehicle.GetCollection(); + PerfLevel perf_level(coll_key); + if (!perf_level.Analyze(vehicle)) { + return false; + } + perf_level.Rate(); + perf = perf_level.Stock; + return true; +} + +bool Physics::Info::GetStockPerformance(const Attrib::Gen::pvehicle &pvehicle, Performance &perf) { + if (!HasPerformanceRatings(pvehicle)) { + return false; + } + + unsigned int key = pvehicle.GetCollection(); + if (pvehicle.IsDynamic()) { + key = pvehicle.GetParent(); + } + + for (PerformanceMaps::const_iterator iter = TheStockCars.begin(); iter != TheStockCars.end(); iter++) { + const PerfLevel &p = *iter; + if (p.Key == key) { + perf = p.Stock; + return true; + } + } + + return false; +} + +bool Physics::Info::GetMaximumPerformance(const Attrib::Gen::pvehicle &pvehicle, Performance &perf) { + if (!HasPerformanceRatings(pvehicle)) { + return false; + } + + unsigned int key = pvehicle.GetCollection(); + if (pvehicle.IsDynamic()) { + key = pvehicle.GetParent(); + } + + for (PerformanceMaps::const_iterator iter = TheStockCars.begin(); iter != TheStockCars.end(); iter++) { + const PerfLevel &p = *iter; + if (p.Key == key) { + perf = p.Upgraded; + return true; + } + } + + return false; +} + +void Physics::Info::FindPerformanceCandidates(const Performance &minimum_perf, const Performance &maximum_perf, + UTL::Std::list &vlist) { + vlist.clear(); + + for (PerformanceMaps::const_iterator iter = TheStockCars.begin(); iter != TheStockCars.end(); iter++) { + const PerfLevel &p = *iter; + if (p.Stock.TopSpeed <= maximum_perf.TopSpeed && + p.Stock.Acceleration <= maximum_perf.Acceleration && + p.Stock.Handling <= maximum_perf.Handling && + p.Upgraded.TopSpeed >= minimum_perf.TopSpeed && + p.Upgraded.Acceleration >= minimum_perf.Acceleration && + p.Upgraded.Handling >= minimum_perf.Handling) { + vlist.push_back(p.Key); + } + } +} diff --git a/src/Speed/Indep/Src/Physics/PhysicsInfo.hpp b/src/Speed/Indep/Src/Physics/PhysicsInfo.hpp index de214a52b..374b4dc0f 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsInfo.hpp +++ b/src/Speed/Indep/Src/Physics/PhysicsInfo.hpp @@ -8,6 +8,7 @@ #include "PhysicsTunings.h" #include "PhysicsTypes.h" #include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/chassis.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/engine.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/induction.h" @@ -16,6 +17,10 @@ #include "Speed/Indep/Src/Generated/AttribSys/Classes/tires.h" #include "Speed/Indep/Src/Generated/AttribSys/Classes/transmission.h" #include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +class _type_list; +DECLARE_CONTAINER_TYPE(PerformanceMaps); namespace Physics { namespace Info { @@ -27,21 +32,14 @@ enum eInductionType { }; struct Performance { - Performance() { - Default(); - } + Performance() {} - Performance(float topspeed, float handling, float accel) { - TopSpeed = topspeed; - Handling = handling; - Acceleration = accel; - } + Performance(float topspeed, float handling, float accel) + : TopSpeed(topspeed) // + , Handling(handling) // + , Acceleration(accel) {} - void Default() { - TopSpeed = 0.0f; - Handling = 0.0f; - Acceleration = 0.0f; - } + void Default(); void Maximize(const Performance &other) { TopSpeed = UMath::Max(TopSpeed, other.TopSpeed); @@ -54,10 +52,47 @@ struct Performance { float Acceleration; }; +} // namespace Info +} // namespace Physics + +struct PerfStats { + bool Fetch(const Attrib::Gen::pvehicle &pvehicle, bVector2 *graph_data, int *num_data); + + float Time0To100; + float TopSpeed; + float HandlingRating; +}; + +struct PerfLevel { + PerfLevel(unsigned int key) // + : Stats() // + , Stock() // + , Upgraded() // + , Key(key) // + , Analyzed(false) {} + + bool Analyze(const Attrib::Gen::pvehicle &pvehicle); + void Rate(); + void Print(const char * = nullptr); + + PerfStats Stats; + Physics::Info::Performance Stock; + Physics::Info::Performance Upgraded; + unsigned int Key; + bool Analyzed; +}; + +struct PerformanceMaps : public UTL::Std::list { + void FindLimits(float direction, PerfStats &out) const; +}; + +namespace Physics { +namespace Info { + void Init(); -float AerodynamicDownforce(const Attrib::Gen::chassis &chassis, const float speed); -float EngineInertia(const Attrib::Gen::engine &engine, const bool loaded); +float AerodynamicDownforce(const Attrib::Gen::chassis &chassis, float speed); +float EngineInertia(const Attrib::Gen::engine &engine, bool loaded); eInductionType InductionType(const Attrib::Gen::induction &induction); bool HasNos(const Attrib::Gen::pvehicle &pvehicle); bool HasRunflatTires(const Attrib::Gen::pvehicle &pvehicle); @@ -79,6 +114,27 @@ bool ShiftPoints(const Attrib::Gen::transmission &transmission, const Attrib::Ge Mps Speedometer(const Attrib::Gen::transmission &transmission, const Attrib::Gen::engine &engine, const Attrib::Gen::tires &tires, Rpm rpm, GearID gear, const Tunings *tunings); bool EstimatePerformance(Performance &perf); +eInductionType InductionType(const Attrib::Gen::pvehicle &pvehicle); +float WheelDiameter(const Attrib::Gen::pvehicle &pvehicle, bool front); +float Redline(const Attrib::Gen::engine &engine); +float Redline(const Attrib::Gen::pvehicle &pvehicle); +unsigned int NumFowardGears(const Attrib::Gen::transmission &transmission); +unsigned int NumFowardGears(const Attrib::Gen::pvehicle &pvehicle); +float MaxInductedPower(const Attrib::Gen::pvehicle &pvehicle, const Tunings *tunings); +float AvgInductedTorque(const Attrib::Gen::engine &engine, const Attrib::Gen::induction &induction, + const Attrib::Gen::transmission &transmission, bool loaded, const Tunings *tunings); +float MaxInductedTorque(const Attrib::Gen::engine &engine, const Attrib::Gen::induction &induction, + float &atrpm, const Tunings *tunings); +float MaxInductedTorque(const Attrib::Gen::pvehicle &pvehicle, float &atrpm, const Tunings *tunings); +float MaxTorque(const Attrib::Gen::engine &engine, float &atrpm); +void Init(); +void FindPerformanceCandidates(const Performance &minimum_perf, const Performance &maximum_perf, + UTL::Std::list &candidates); +bool HasPerformanceRatings(const Attrib::Gen::pvehicle &pvehicle); +bool EstimatePerformance(const Attrib::Gen::pvehicle &pvehicle, Performance &perf); +bool ComputePerformance(const Attrib::Gen::pvehicle &pvehicle, Performance &perf); +bool GetStockPerformance(const Attrib::Gen::pvehicle &pvehicle, Performance &perf); +bool GetMaximumPerformance(const Attrib::Gen::pvehicle &pvehicle, Performance &perf); bool ComputeAccelerationTable(const Attrib::Gen::pvehicle &pvehicle, float &top_speed, float *table, int num_entries); extern Performance PerformanceWeights[7]; diff --git a/src/Speed/Indep/Src/Physics/PhysicsObject.h b/src/Speed/Indep/Src/Physics/PhysicsObject.h index 05ddb27f8..407988f32 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsObject.h +++ b/src/Speed/Indep/Src/Physics/PhysicsObject.h @@ -6,6 +6,7 @@ #endif #include "Speed/Indep/Src/Interfaces/IBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRigidBody.h" #include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" #include "Speed/Indep/Src/Physics/Behavior.h" #include "Speed/Indep/Src/Sim/SimAttachable.h" @@ -24,7 +25,42 @@ class PhysicsObject : public Sim::Object, typedef UTL::Std::map Mechanics; struct Behaviors : protected UTL::Std::list { + typedef UTL::Std::list _Base; + using _Base::begin; + using _Base::end; + using _Base::const_iterator; + using _Base::iterator; + // total size: 0x8 + void Add(Behavior *beh); + void Remove(Behavior *beh); + void Reset(); + + void OnAttach(IAttachable *iother) { + for (const_iterator iter = begin(); iter != end(); ++iter) { + (*iter)->OnOwnerAttached(iother); + } + } + + void OnDetach(IAttachable *iother) { + for (const_iterator iter = begin(); iter != end(); ++iter) { + (*iter)->OnOwnerDetached(iother); + } + } + + void Simulate(float dT) { + for (const_iterator iter = begin(); iter != end(); ++iter) { + if (!(*iter)->IsPaused()) { + (*iter)->DoSimulate(dT); + } + } + } + + void Changed(const UCrc32 &mechanic) { + for (const_iterator iter = begin(); iter != end(); ++iter) { + (*iter)->BehaviorChanged(mechanic); + } + } }; PhysicsObject(const Attrib::Instance &attribs, SimableType objType, WUID wuid, unsigned int num_interfaces); @@ -33,7 +69,108 @@ class PhysicsObject : public Sim::Object, // Overrides virtual ~PhysicsObject(); - private: + // ISimable overrides + virtual SimableType GetSimableType() const override { + return mObjType; + } + virtual void Kill() override; + virtual bool Attach(UTL::COM::IUnknown *object) override; + virtual bool Detach(UTL::COM::IUnknown *object) override; + virtual const UTL::Std::list *GetAttachments() const override { + if (mAttachments == nullptr) { + return nullptr; + } + return &mAttachments->GetList(); + } + virtual void AttachEntity(Sim::IEntity *e) override; + virtual void DetachEntity() override; + virtual struct IPlayer *GetPlayer() const override { + return mPlayer; + } + virtual bool IsPlayer() const override { + return mPlayer != nullptr; + } + virtual bool IsOwnedByPlayer() const override; + virtual Sim::IEntity *GetEntity() const override { + return mEntity; + } + virtual void DebugObject() override; + virtual HSIMABLE GetOwnerHandle() const override { + return mOwner; + } + virtual ISimable *GetOwner() const override { + return ISimable::FindInstance(mOwner); + } + virtual bool IsOwnedBy(ISimable *queriedOwner) const override; + virtual void SetOwnerObject(ISimable *pOwner) override; + virtual const Attrib::Instance &GetAttributes() const override { + return mAttributes; + } + virtual WWorldPos &GetWPos() override; + virtual const WWorldPos &GetWPos() const override; + virtual class IRigidBody *GetRigidBody() override; + virtual const class IRigidBody *GetRigidBody() const override { return mRigidBody; } + virtual bool IsRigidBodySimple() const override { + if (mRigidBody) { + return mRigidBody->IsSimple(); + } + return false; + } + virtual bool IsRigidBodyComplex() const override { + return !IsRigidBodySimple(); + } + virtual const UMath::Vector3 &GetPosition() const override { + return mRigidBody != nullptr ? mRigidBody->GetPosition() : UMath::Vector3::kZero; + } + virtual void GetTransform(UMath::Matrix4 &matrix) const override; + virtual void GetLinearVelocity(UMath::Vector3 &velocity) const override; + virtual void GetAngularVelocity(UMath::Vector3 &velocity) const override; + virtual unsigned int GetWorldID() const override { + return mWorldID; + } + virtual EventSequencer::IEngine *GetEventSequencer() override { + return nullptr; + } + virtual void ProcessStimulus(unsigned int stimulus) override; + virtual IModel *GetModel() override; + virtual const IModel *GetModel() const override; + virtual void SetCausality(HCAUSE from, float time) override; + virtual HCAUSE GetCausality() const override; + virtual float GetCausalityTime() const override; + + // IAttachable overrides + virtual void OnAttached(IAttachable *pOther) override; + virtual void OnDetached(IAttachable *pOther) override; + virtual bool IsAttached(const UTL::COM::IUnknown *pOther) const override { + if (mAttachments) { + return mAttachments->IsAttached(pOther); + } + return false; + } + + // IBody overrides + virtual void GetDimension(UMath::Vector3 &dim) const override; + + // Sim::Object overrides + virtual bool OnService(HSIMSERVICE hCon, Sim::Packet *pkt) override; + virtual bool OnTask(HSIMTASK htask, float dT) override; + + // Other public methods + bool IsBehaviorActive(const UCrc32 &mechanic) const; + void PauseBehavior(const UCrc32 &mechanic, bool pause); + bool ResetBehavior(const UCrc32 &mechanic); + Behavior *FindBehavior(const UCrc32 &mechanic); + void DetachAll(); + void ReleaseBehaviors(); + virtual void Reset(); + + protected: + Behavior *LoadBehavior(const UCrc32 &mechanic, const UCrc32 &behavior, Sim::Param params); + void ReleaseBehavior(const UCrc32 &mechanic); + virtual void OnTaskSimulate(float dT); + virtual void OnBehaviorChange(const UCrc32 &mechanic); + + protected: WWorldPos *mWPos; // offset 0x58, size 0x4 SimableType mObjType; // offset 0x5C, size 0x4 HSIMABLE mOwner; // offset 0x60, size 0x4 diff --git a/src/Speed/Indep/Src/Physics/PhysicsTunings.cpp b/src/Speed/Indep/Src/Physics/PhysicsTunings.cpp index e69de29bb..0d8c693b8 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsTunings.cpp +++ b/src/Speed/Indep/Src/Physics/PhysicsTunings.cpp @@ -0,0 +1,25 @@ +#include "PhysicsTunings.h" + +namespace Physics { + +static const float TuningLimits[Tunings::MAX_TUNINGS][2] = { + {-1.0f, 1.0f}, + {-1.0f, 1.0f}, + {-1.0f, 1.0f}, + {-1.0f, 1.0f}, + {-1.0f, 1.0f}, + {-1.0f, 1.0f}, + {-1.0f, 1.0f}, +}; + +void Tunings::Default() { + for (int i = 0; i < MAX_TUNINGS; i++) { + Value[i] = 0.0f; + } +} + +float Tunings::LowerLimit(Path path) { return TuningLimits[path][0]; } + +float Tunings::UpperLimit(Path path) { return TuningLimits[path][1]; } + +} // namespace Physics \ No newline at end of file diff --git a/src/Speed/Indep/Src/Physics/PhysicsTunings.h b/src/Speed/Indep/Src/Physics/PhysicsTunings.h index 2dec61ee5..67f54de2c 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsTunings.h +++ b/src/Speed/Indep/Src/Physics/PhysicsTunings.h @@ -27,6 +27,12 @@ struct Tunings { }; float Value[7]; // offset 0x0, size 0x1C + Tunings() { + Default(); + } + + void Default(); + static float LowerLimit(Path path); static float UpperLimit(Path path); }; diff --git a/src/Speed/Indep/Src/Physics/PhysicsTypes.h b/src/Speed/Indep/Src/Physics/PhysicsTypes.h index c30d1ca9f..31056a824 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsTypes.h +++ b/src/Speed/Indep/Src/Physics/PhysicsTypes.h @@ -38,6 +38,8 @@ struct AxlePair { float Front; // offset 0x0, size 0x4 float Rear; // offset 0x4, size 0x4 + AxlePair() : Front(0.0f), Rear(0.0f) {} + float At(int index) const { return (&Front)[index]; } diff --git a/src/Speed/Indep/Src/Physics/PhysicsUpgrades.cpp b/src/Speed/Indep/Src/Physics/PhysicsUpgrades.cpp index e69de29bb..37d7f38ee 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsUpgrades.cpp +++ b/src/Speed/Indep/Src/Physics/PhysicsUpgrades.cpp @@ -0,0 +1,795 @@ +#include "PhysicsUpgrades.hpp" +#include "PhysicsInfo.hpp" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/bWare/Inc/bDebug.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/junkman.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/presetride.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Physics/PhysicsTypes.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" + +using Attrib::Attribute; +using Attrib::AttributeIterator; +using Attrib::Database; +using Attrib::Key; +using Attrib::RefSpec; + +struct PUJunkNode : Attrib::Instance { + PUJunkNode(const RefSpec &collection, const Attrib::Gen::junkman &junkman, unsigned int junkkey); + + void operator delete(void *ptr, std::size_t bytes) { + Attrib::Free(ptr, bytes, "Attrib::Instance"); + } +}; + +struct PUPartNode : Attrib::Instance { + PUPartNode(const RefSpec &collection0, const RefSpec &collection1, float weight); + + void operator delete(void *ptr, std::size_t bytes) { + Attrib::Free(ptr, bytes, "Attrib::Instance"); + } +}; + +namespace Physics { +namespace Upgrades { + +struct tPartMap { + Type type; + const char *name; + Key key; + Key countkey; + Key currentkey; + Key junkkey; + Key presetkey; +}; + +} // namespace Upgrades +} // namespace Physics + + +static Physics::Upgrades::tPartMap put_maps[] = { + {Physics::Upgrades::PUT_TIRES, "tires", 0x570E7E24, 0x3F16D2B1, 0x65E52BDE, 0xC5860F58, 0x5F4A69DB}, + {Physics::Upgrades::PUT_BRAKES, "brakes", 0x2DD1F36A, 0xA2AEA57C, 0xC316DCD1, 0x56C63B6F, 0xE6C23DE6}, + {Physics::Upgrades::PUT_CHASSIS, "chassis", 0xF4E2FAD0, 0x20F6C9D5, 0x58CCF9F1, 0xB6495C9E, 0x21DD95EB}, + {Physics::Upgrades::PUT_TRANSMISSION, "transmission", 0x07A7A3E5, 0x0CB3C7E5, 0x170D5554, 0x25AE629A, 0x3DF99C50}, + {Physics::Upgrades::PUT_ENGINE, "engine", 0x5B862C53, 0x0CA4AD56, 0x5F8AFDDB, 0x9206EFD2, 0x3A93C8C7}, + {Physics::Upgrades::PUT_INDUCTION, "induction", 0xC92A0142, 0x99FCA5D3, 0x4DFC0A63, 0x7546359E, 0xFC61F3A5}, + {Physics::Upgrades::PUT_NOS, "nos", 0xD7B2D8F2, 0x2C48F8EC, 0x1D79C9A4, 0x452D2634, 0xEABB55C5}, + {(Physics::Upgrades::Type)0, nullptr, 0, 0, 0, 0, 0}, +}; + +const Physics::Upgrades::tPartMap *FindPartMap(Physics::Upgrades::Type type) { + Physics::Upgrades::tPartMap *t; + const Physics::Upgrades::tPartMap *type_map = put_maps; + if (type_map->key == 0) { + goto not_found; + } + if (type_map->type == type) { + return type_map; + } + do { + type_map++; + if (type_map->key == 0) { + goto not_found; + } + } while (type_map->type != type); + return type_map; +not_found: + return nullptr; +} + +static void DownGradeInternal(Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + if (!vehicle.IsDynamic()) { + return; + } + + const Physics::Upgrades::tPartMap *t = FindPartMap(type); + if (t == nullptr) { + return; + } + + vehicle.Remove(t->key); + vehicle.Remove(t->currentkey); + + int mask = 1 << type; + int junkman_current = vehicle.junkman_current(); + + if (junkman_current & mask) { + vehicle.Remove(0xcdc136e8); + junkman_current = junkman_current & ~mask; + vehicle.GetBase().AddAndSet(0xcdc136e8, &junkman_current, 1); + } +} + +template +void BlendParts(const Attribute &start_attribute, const Attribute &end_attribute, unsigned int index, float weight, Attribute &new_attrib) { + T start_data = T(); + T end_data = T(); + + start_attribute.Get(index, start_data); + end_attribute.Get(index, end_data); + + T new_data; + + float *start_ptr = reinterpret_cast(&start_data); + float *end_ptr = reinterpret_cast(&end_data); + float *new_ptr = reinterpret_cast(&new_data); + + new_ptr[0] = start_ptr[0] * (1.0f - weight) + end_ptr[0] * weight; + if (sizeof(T) > sizeof(float)) { + new_ptr[1] = start_ptr[1] * (1.0f - weight) + end_ptr[1] * weight; + } + + new_attrib.Set(index, new_data); +} + +template <> +void BlendParts(const Attribute &start_attribute, const Attribute &end_attribute, unsigned int index, float weight, Attribute &new_attrib) { + int start_data = 0; + int end_data = 0; + + start_attribute.Get(index, start_data); + end_attribute.Get(index, end_data); + + int new_data = static_cast(static_cast(start_data) * (1.0f - weight) + static_cast(end_data) * weight); + + new_attrib.Set(index, new_data); +} + +template +void ScalePart(Attribute &attrib, unsigned int index, float scale) { + T start_data = T(); + T new_data; + + attrib.Get(index, start_data); + + float *start_ptr = reinterpret_cast(&start_data); + float *new_ptr = reinterpret_cast(&new_data); + + new_ptr[0] = start_ptr[0] * scale; + if (sizeof(T) > sizeof(float)) { + new_ptr[1] = start_ptr[1] * scale; + } + + attrib.Set(index, new_data); +} + +template <> +void ScalePart(Attribute &attrib, unsigned int index, float scale) { + int start_data = 0; + + attrib.Get(index, start_data); + + int new_data = static_cast(static_cast(start_data) * scale); + + attrib.Set(index, new_data); +} + +float Physics::Upgrades::GetPercent(const Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + int max_level = GetMaxLevel(vehicle, type); + if (max_level == 0) { + return 0.0f; + } + int cur_level = GetLevel(vehicle, type); + if (cur_level == max_level) { + return 1.0f; + } + return static_cast(cur_level) / static_cast(max_level); +} + +int Physics::Upgrades::GetLevel(const Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + Attribute attrib; + const Physics::Upgrades::tPartMap *t = FindPartMap(type); + if (t == nullptr || !vehicle.Lookup(t->currentkey, attrib)) { + return 0; + } + return attrib.Get< int >(0u); +} + +void Physics::Upgrades::GetPackage(const Attrib::Gen::pvehicle &vehicle, Package &package) { + package.Default(); + + for (int i = 0; i < Physics::Upgrades::PUT_MAX; i++) { + Physics::Upgrades::Type type = static_cast(i); + package.Part[i] = GetLevel(vehicle, type); + if (GetJunkman(vehicle, type)) { + package.Junkman |= (1 << type); + } + } +} + +bool Physics::Upgrades::SetPackage(Attrib::Gen::pvehicle &vehicle, const Package &package) { + Attrib::Gen::pvehicle newvehicle(vehicle); + Clear(newvehicle); + + if (!Validate(newvehicle)) { + return false; + } + + for (int i = 0; i < Physics::Upgrades::PUT_MAX; i++) { + Physics::Upgrades::Type type = static_cast(i); + int mask = 1 << type; + + if (!SetLevel(newvehicle, type, package.Part[i])) { + return false; + } + + if ((package.Junkman & mask) == 0) { + RemoveJunkman(newvehicle, type); + } else { + if (!SetJunkman(newvehicle, type)) { + return false; + } + } + } + + if (!Validate(newvehicle)) { + return false; + } + + vehicle = newvehicle; + return true; +} + +bool Physics::Upgrades::GetJunkman(const Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + int junkman_current = vehicle.junkman_current(); + bool result = true; + if (((junkman_current >> type) & 1) == 0) { + result = false; + } + return result; +} + +bool Physics::Upgrades::CanInstallJunkman(const Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + const Physics::Upgrades::tPartMap *p = FindPartMap(type); + if (p == nullptr) { + return false; + } + if (p->junkkey == 0) { + return false; + } + + if (type == Physics::Upgrades::PUT_INDUCTION) { + if (Physics::Info::InductionType(vehicle) == Physics::Info::INDUCTION_NONE) { + return false; + } + } else if (type == Physics::Upgrades::PUT_NOS) { + if (!Physics::Info::HasNos(vehicle)) { + return false; + } + } + + Attrib::Gen::junkman junkman(vehicle.junkman(), 0, nullptr); + Attribute junk_attribute; + bool found = junkman.Lookup(p->junkkey, junk_attribute); + if (found && junk_attribute.GetType() == 0x51ead18d) { + unsigned int len = junk_attribute.GetLength(); + return len != 0; + } + + return false; +} + +bool Physics::Upgrades::SetJunkman(Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + Attrib::Gen::pvehicle newvehicle(vehicle); + + if (GetJunkman(vehicle, type)) { + return true; + } + + if (!CanInstallJunkman(vehicle, type)) { + return false; + } + + const Physics::Upgrades::tPartMap *p = FindPartMap(type); + if (p == nullptr) { + return false; + } + + unsigned int part_key = p->key; + unsigned int junk_key = p->junkkey; + + if (part_key == 0 || junk_key == 0) { + return false; + } + + int junkman_current = vehicle.junkman_current(); + + if (newvehicle.IsDynamic()) { + newvehicle.Remove(0xcdc136e8); + } + + Attribute part_attribute; + if (!newvehicle.Lookup(part_key, part_attribute) || part_attribute.GetType() != 0x2b936eb7) { + return false; + } + + RefSpec basepart(part_attribute.Get< RefSpec >(0)); + + Attrib::Gen::junkman junkman_inst(newvehicle.junkman(), 0, nullptr); + PUJunkNode node(basepart, junkman_inst, junk_key); + + if (!node.IsValid()) { + return false; + } + + RefSpec newref; + newref.SetCollection(node.GetConstCollection()); + + if (newref != basepart) { + if (!newvehicle.IsDynamic()) { + const char *name = newvehicle.CollectionName(); + Key uniqueKey = newvehicle.GenerateUniqueKey(name, false); + newvehicle.Modify(uniqueKey, newvehicle.LocalAttribCount()); + } else { + newvehicle.Remove(part_key); + } + + if (!newvehicle.GetBase().AddAndSet(part_key, &newref, 1)) { + return false; + } + } + + junkman_current |= (1 << type); + if (!newvehicle.GetBase().AddAndSet(0xcdc136e8, &junkman_current, 1)) { + return false; + } + + vehicle = static_cast(newvehicle); + return true; +} + +bool Physics::Upgrades::ApplyPreset(Attrib::Gen::pvehicle &vehicle, const Attrib::Gen::presetride &presetride) { + if (!presetride.IsValid()) { + return false; + } + if (!vehicle.IsValid()) { + return false; + } + + Attrib::Gen::pvehicle newvehicle(vehicle); + Clear(newvehicle); + + for (int i = 0; i < Physics::Upgrades::PUT_MAX; i++) { + Physics::Upgrades::Type type = static_cast(i); + const Physics::Upgrades::tPartMap *part = FindPartMap(type); + if (part == nullptr) { + continue; + } + + int max_level = GetMaxLevel(vehicle, type); + if (max_level <= 0) { + continue; + } + + Attribute attrib; + if (!presetride.Lookup(part->presetkey, attrib) || !attrib.IsValid()) { + continue; + } + + int level = UMath::Min(attrib.Get< int >(0u), max_level); + + if (!SetLevel(newvehicle, type, level)) { + return false; + } + } + + vehicle = static_cast(newvehicle); + return true; +} + +void Physics::Upgrades::RemovePart(Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + if (!vehicle.IsDynamic()) { + return; + } + + if (GetLevel(vehicle, type) == 0) { + return; + } + + const Physics::Upgrades::tPartMap *t = FindPartMap(type); + if (t == nullptr || t->key == 0) { + return; + } + + bool had_junkman = GetJunkman(vehicle, type); + DownGradeInternal(vehicle, type); + if (had_junkman) { + SetJunkman(vehicle, type); + } +} + +void Physics::Upgrades::RemoveJunkman(Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + if (!vehicle.IsDynamic()) { + return; + } + + const Physics::Upgrades::tPartMap *t = FindPartMap(type); + if (t == nullptr || t->key == 0) { + return; + } + + if (!GetJunkman(vehicle, type)) { + return; + } + + int old_upgrade_level = GetLevel(vehicle, type); + DownGradeInternal(vehicle, type); + if (old_upgrade_level > 0) { + SetLevel(vehicle, type, old_upgrade_level); + } +} + +bool Physics::Upgrades::Validate(const Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + if (GetJunkman(vehicle, type)) { + if (!CanInstallJunkman(vehicle, type)) { + return false; + } + } + + int current = GetLevel(vehicle, type); + int max_level = GetMaxLevel(vehicle, type); + + if (current == 0 && max_level != 0) { + const Physics::Upgrades::tPartMap *t = FindPartMap(type); + if (t == nullptr) { + return false; + } + + Attrib::Gen::pvehicle base(vehicle); + Clear(base); + Attribute attrib; + if (!base.Lookup(t->key, attrib) || attrib.GetLength() < 2) { + return false; + } + } + + return current <= max_level; +} + +bool Physics::Upgrades::Validate(const Attrib::Gen::pvehicle &vehicle) { + for (int i = 0; i < Physics::Upgrades::PUT_MAX; i++) { + if (!Validate(vehicle, static_cast(i))) { + return false; + } + } + return true; +} + +int Physics::Upgrades::GetMaxLevel(const Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type) { + Attribute attrib; + const Physics::Upgrades::tPartMap *t = FindPartMap(type); + if (t == nullptr || !vehicle.Lookup(t->countkey, attrib)) { + return 0; + } + return attrib.Get< int >(0u); +} + +bool Physics::Upgrades::SetMaximum(Attrib::Gen::pvehicle &pvehicle) { + Package package; + package.Default(); + + for (int i = 0; i < Physics::Upgrades::PUT_MAX; i++) { + package.Part[i] = GetMaxLevel(pvehicle, static_cast(i)); + } + + return SetPackage(pvehicle, package); +} + +static bool UpgradeInternal(Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type, int level, float weight) { + Attrib::Gen::pvehicle newvehicle(vehicle); + + if (weight <= 0.0f) { + RemovePart(vehicle, type); + return true; + } + + int max_level = GetMaxLevel(newvehicle, type); + if (max_level <= 0 || weight > 1.0f) { + return false; + } + + const Physics::Upgrades::tPartMap *p = FindPartMap(type); + if (p == nullptr) { + return false; + } + + unsigned int part_key = p->key; + if (part_key == 0) { + return false; + } + + bool had_junkman = GetJunkman(vehicle, type); + DownGradeInternal(newvehicle, type); + + Attribute part_attribute; + if (!newvehicle.Lookup(part_key, part_attribute)) { + return false; + } + + if (part_attribute.GetType() != 0x2b936eb7 || part_attribute.GetLength() < 2) { + return false; + } + + unsigned int base_index = (part_attribute.GetLength() == 3) ? 1 : 0; + unsigned int top_index = (part_attribute.GetLength() == 3) ? 2 : 1; + + RefSpec basepart; + part_attribute.Get(base_index, basepart); + + RefSpec endpart; + part_attribute.Get(top_index, endpart); + + if (basepart.GetClassKey() == 0 || endpart.GetClassKey() == 0 || + basepart.GetCollectionKey() == 0 || endpart.GetCollectionKey() == 0) { + return false; + } + + PUPartNode node(basepart, endpart, weight); + if (!node.IsValid()) { + return false; + } + + RefSpec newref; + newref.SetCollection(node.GetConstCollection()); + + if (newref == basepart) { + return true; + } + + if (!newvehicle.IsDynamic()) { + const char *name = newvehicle.CollectionName(); + Key uniqueKey = newvehicle.GenerateUniqueKey(name, false); + newvehicle.Modify(uniqueKey, newvehicle.LocalAttribCount()); + } + + if (!newvehicle.GetBase().AddAndSet(part_key, &newref, 1)) { + return false; + } + + if (!newvehicle.GetBase().AddAndSet(p->currentkey, &level, 1)) { + return false; + } + + if (had_junkman && !SetJunkman(newvehicle, type)) { + return false; + } + + vehicle = newvehicle; + return true; +} + +bool Physics::Upgrades::SetLevel(Attrib::Gen::pvehicle &vehicle, Physics::Upgrades::Type type, int level) { + Attrib::Gen::pvehicle newvehicle(vehicle); + + int cur_level = GetLevel(vehicle, type); + if (cur_level == level) { + return true; + } + + if (level < 1) { + RemovePart(vehicle, type); + } else { + int max_level = GetMaxLevel(newvehicle, type); + if (max_level < 1 || level > max_level) { + return false; + } + + float weight = static_cast(level) / static_cast(max_level); + if (!UpgradeInternal(newvehicle, type, level, weight)) { + return false; + } + + vehicle = static_cast(newvehicle); + } + + return true; +} + +void Physics::Upgrades::Clear(Attrib::Gen::pvehicle &vehicle) { + if (vehicle.IsDynamic()) { + vehicle.Unmodify(); + } +} + +bool Physics::Upgrades::MatchPerformance(Attrib::Gen::pvehicle &vehicle, const Physics::Info::Performance &matched_performance) { + Attrib::Gen::pvehicle newvehicle(vehicle); + + Clear(newvehicle); + + Physics::Info::Performance stock_performance; + Physics::Info::Performance upgraded_performance; + + if (!Physics::Info::GetStockPerformance(newvehicle, stock_performance)) { + return false; + } + + if (!Physics::Info::GetMaximumPerformance(newvehicle, upgraded_performance)) { + return false; + } + + Physics::Info::Performance match_line; + + float acc_range = upgraded_performance.Acceleration - stock_performance.Acceleration; + if (acc_range > 1e-06f) { + match_line.Acceleration = UMath::Ramp( + (matched_performance.Acceleration - stock_performance.Acceleration) / acc_range, + 0.0f, 1.0f); + } else { + match_line.Acceleration = 0.0f; + } + + float top_range = upgraded_performance.TopSpeed - stock_performance.TopSpeed; + if (top_range > 1e-06f) { + match_line.TopSpeed = UMath::Ramp( + (matched_performance.TopSpeed - stock_performance.TopSpeed) / top_range, + 0.0f, 1.0f); + } else { + match_line.TopSpeed = 0.0f; + } + + float hand_range = upgraded_performance.Handling - stock_performance.Handling; + if (hand_range > 1e-06f) { + match_line.Handling = UMath::Ramp( + (matched_performance.Handling - stock_performance.Handling) / hand_range, + 0.0f, 1.0f); + } else { + match_line.Handling = 0.0f; + } + + for (int i = 0; i < Physics::Upgrades::PUT_MAX; i++) { + Physics::Upgrades::Type type = static_cast(i); + float weight; + + switch (type) { + case Physics::Upgrades::PUT_TIRES: + weight = match_line.Handling; + break; + case Physics::Upgrades::PUT_BRAKES: + weight = (match_line.Handling + match_line.TopSpeed) * 0.5f; + break; + case Physics::Upgrades::PUT_CHASSIS: + weight = match_line.Handling; + break; + case Physics::Upgrades::PUT_TRANSMISSION: + weight = (match_line.Acceleration + match_line.TopSpeed) * 0.5f; + break; + case Physics::Upgrades::PUT_ENGINE: + weight = (match_line.Acceleration + match_line.TopSpeed) * 0.5f; + break; + case Physics::Upgrades::PUT_INDUCTION: + weight = match_line.Acceleration; + break; + case Physics::Upgrades::PUT_NOS: + weight = match_line.Acceleration; + break; + default: + weight = (match_line.Acceleration + match_line.TopSpeed + match_line.Handling) / 3.0f; + break; + } + + weight = UMath::Clamp(weight, 0.0f, 1.0f); + + if (weight > 0.0f) { + int max_level = GetMaxLevel(vehicle, type); + if (max_level > 0) { + float f_index = UMath::Ceil(weight * static_cast(max_level)); + int level = static_cast(f_index); + if (!UpgradeInternal(newvehicle, type, level, weight)) { + return false; + } + } + } + } + + vehicle = newvehicle; + return true; +} + +void Physics::Upgrades::Flush() { + Database::Get().CollectGarbage(); +} + +PUJunkNode::PUJunkNode(const RefSpec &collection, const Attrib::Gen::junkman &junkman, unsigned int junkkey) + : Instance(collection, 0, nullptr) { + Attribute junk_attribute; + if (junkman.Lookup(junkkey, junk_attribute)) { + Key uniqueKey = GenerateUniqueKey("junk_upgrade", false); + Modify(uniqueKey, 0); + unsigned int len = junk_attribute.GetLength(); + for (unsigned int index = 0; index < len; index++) { + JunkmanMod modifire; + junk_attribute.Get(index, modifire); + if (&modifire != nullptr) { + Attribute start_attribute = Get(modifire.DefinitionKey); + unsigned int count = start_attribute.GetLength(); + if (count != 0) { + Add(modifire.DefinitionKey, count); + Attribute attribute = Get(modifire.DefinitionKey); + unsigned int type = attribute.GetType(); + for (unsigned int i = 0; i < count; i++) { + if (type == 0x4cb36381) { + ScalePart(attribute, i, modifire.Scale); + } else if (type < 0x4cb36382) { + if (type == 0x3c16ec5e) { + ScalePart(attribute, i, modifire.Scale); + } else { + bBreak(); + } + } else { + if (type != 0x5763da41) { + bBreak(); + continue; + } + ScalePart(attribute, i, modifire.Scale); + } + } + } + } + } + } +} + +PUPartNode::PUPartNode(const RefSpec &collection0, const RefSpec &collection1, float weight) + : Instance(collection0, 0, nullptr) { + if (weight >= 1.0f) { + ChangeWithDefault(collection1); + } else if (weight > 0.0f) { + ChangeWithDefault(collection0); + Key uniqueKey = GenerateUniqueKey("part_upgrade", false); + Modify(uniqueKey, 0); + Instance end_instance(collection1, 0, nullptr); + Instance start_instance(collection0, 0, nullptr); + AttributeIterator iter = end_instance.Iterator(); + while (iter.Valid()) { + Key key = iter.GetKey(); + Attribute end_attribute = end_instance.Get(key); + Attribute start_attribute = start_instance.Get(key); + unsigned int end_count = end_attribute.GetLength(); + unsigned int start_count = start_attribute.GetLength(); + unsigned int count = UMath::Max(end_count, start_count); + unsigned int end_type = end_attribute.GetType(); + unsigned int start_type = start_attribute.GetType(); + if (end_type == start_type) { + if (count != 0) { + Add(key, count); + Attribute new_attrib = Get(key); + unsigned int type = start_attribute.GetType(); + for (unsigned int i = 0; i < count; i++) { + if (type == 0x4cb36381) { + BlendParts(start_attribute, end_attribute, i, weight, new_attrib); + } else if (type < 0x4cb36382) { + if (type == 0x3c16ec5e) { + BlendParts(start_attribute, end_attribute, i, weight, new_attrib); + } else { + bBreak(); + } + } else { + if (type != 0x5763da41) { + bBreak(); + continue; + } + BlendParts(start_attribute, end_attribute, i, weight, new_attrib); + } + } + } + } + iter.Advance(); + } + } +} + +// Explicit template instantiations +template void BlendParts(const Attribute &, const Attribute &, unsigned int, float, Attribute &); +template void BlendParts(const Attribute &, const Attribute &, unsigned int, float, Attribute &); +template void ScalePart(Attribute &, unsigned int, float); +template void ScalePart(Attribute &, unsigned int, float); + +namespace Attrib { +namespace Gen { +const Attrib::Gen::pvehicle &pvehicle::operator=(const Instance &rhs) { + Instance::operator=(rhs); + return *this; +} +} // namespace Gen +} // namespace Attrib diff --git a/src/Speed/Indep/Src/Physics/PhysicsUpgrades.hpp b/src/Speed/Indep/Src/Physics/PhysicsUpgrades.hpp index 11a6db840..7ee6a95cb 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsUpgrades.hpp +++ b/src/Speed/Indep/Src/Physics/PhysicsUpgrades.hpp @@ -5,16 +5,66 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +namespace Attrib { +namespace Gen { +struct pvehicle; +struct presetride; +} // namespace Gen +} // namespace Attrib + namespace Physics { +namespace Info { +struct Performance; +} // namespace Info + namespace Upgrades { +enum Type { + PUT_TIRES = 0, + PUT_BRAKES = 1, + PUT_CHASSIS = 2, + PUT_TRANSMISSION = 3, + PUT_ENGINE = 4, + PUT_INDUCTION = 5, + PUT_NOS = 6, + PUT_MAX = 7, +}; + // total size: 0x20 struct Package { int Part[7]; // offset 0x0, size 0x1C int Junkman; // offset 0x1C, size 0x4 + + void Default() { + bMemSet(this, 0, sizeof(*this)); + Junkman = 0; + } + + Package() {} }; +float GetPercent(const Attrib::Gen::pvehicle &vehicle, Type type); +int GetLevel(const Attrib::Gen::pvehicle &vehicle, Type type); +void GetPackage(const Attrib::Gen::pvehicle &vehicle, Package &package); +bool SetPackage(Attrib::Gen::pvehicle &vehicle, const Package &package); +bool GetJunkman(const Attrib::Gen::pvehicle &vehicle, Type type); +bool CanInstallJunkman(const Attrib::Gen::pvehicle &vehicle, Type type); +bool SetJunkman(Attrib::Gen::pvehicle &vehicle, Type type); +bool ApplyPreset(Attrib::Gen::pvehicle &vehicle, const Attrib::Gen::presetride &presetride); +void RemovePart(Attrib::Gen::pvehicle &vehicle, Type type); +void RemoveJunkman(Attrib::Gen::pvehicle &vehicle, Type type); +bool Validate(const Attrib::Gen::pvehicle &vehicle, Type type); +bool Validate(const Attrib::Gen::pvehicle &vehicle); +int GetMaxLevel(const Attrib::Gen::pvehicle &vehicle, Type type); +bool SetMaximum(Attrib::Gen::pvehicle &pvehicle); +bool SetLevel(Attrib::Gen::pvehicle &vehicle, Type type, int level); +void Clear(Attrib::Gen::pvehicle &vehicle); +bool MatchPerformance(Attrib::Gen::pvehicle &vehicle, const Physics::Info::Performance &matched_performance); +void Flush(); + }; // namespace Upgrades }; // namespace Physics diff --git a/src/Speed/Indep/Src/Physics/PlaceableScenery.h b/src/Speed/Indep/Src/Physics/PlaceableScenery.h new file mode 100644 index 000000000..351c65861 --- /dev/null +++ b/src/Speed/Indep/Src/Physics/PlaceableScenery.h @@ -0,0 +1,37 @@ +#ifndef PHYSICS_PLACEABLESCENERY_H +#define PHYSICS_PLACEABLESCENERY_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/Interfaces/SimModels/IPlaceableScenery.h" +#include "Speed/Indep/Src/Physics/HeirarchyModel.h" + +struct PlaceableScenery : public HeirarchyModel, public IPlaceableScenery { + PlaceableScenery(bHash32 rendermesh, const CollisionGeometry::Bounds *geometry, + const Attrib::Collection *attribs, const ModelHeirarchy *heirarchy); + ~PlaceableScenery() override; + + void *operator new(std::size_t size) { + return gFastMem.Alloc(size, nullptr); + } + + static PlaceableScenery *Construct(const char *name, unsigned int node); + + // IPlaceableScenery + void PickUp() override; + bool Place(const UMath::Matrix4 &transform, bool snap_to_ground) override; + void Destroy() override { + Sim::Model::ReleaseModel(); + } + + // IModel + void ReleaseModel() override; + bool OnRemoveOffScreen(float dT) override { + return false; + } +}; + +#endif diff --git a/src/Speed/Indep/Src/Physics/SceneryModel.h b/src/Speed/Indep/Src/Physics/SceneryModel.h new file mode 100644 index 000000000..85703d21a --- /dev/null +++ b/src/Speed/Indep/Src/Physics/SceneryModel.h @@ -0,0 +1,65 @@ +#ifndef PHYSICS_SCENERYMODEL_H +#define PHYSICS_SCENERYMODEL_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/Interfaces/SimModels/ISceneryModel.h" +#include "Speed/Indep/Src/Physics/HeirarchyModel.h" +#include "Speed/Indep/Src/Physics/SmackableTrigger.h" + +class SmokeableSpawner; + +class SceneryModel : public HeirarchyModel, public ISceneryModel { + public: + SceneryModel(SmokeableSpawner *spawner, const CollisionGeometry::Bounds *geometry, + const Attrib::Collection *attribs, bool hidden); + ~SceneryModel() override; + + 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); } + } + + static SceneryModel *Construct(SmokeableSpawner *data, const Attrib::Collection *attributes, bool hidden); + + static int SceneryCount() { + return mSceneryCount; + } + + unsigned int GetSpawnerID() const; + void RestoreScene(); + bool GetSceneryTransform(UMath::Matrix4 &matrix) const; + void WakeUp(); + bool IsExcluded(unsigned int scenery_exclusion_flag) const; + + void GetTransform(UMath::Matrix4 &matrix) const override; + void ReleaseModel() override; + bool IsHidden() const override; + void HideModel() override; + void OnBeginDraw() override; + void OnEndSimulation() override; + + static void InitSystem(); + static void RestoreSystem(); + + private: + void InitScene(); + void StartOverride(); + void EndOverride(); + void ShowInstance(bool show); + + bool mInstanceVisible; + SmokeableSpawner *mSpawner; + + static int mSceneryCount; + + friend class SmokeableSpawner; +}; + +#endif diff --git a/src/Speed/Indep/Src/Physics/Smackable.h b/src/Speed/Indep/Src/Physics/Smackable.h index 996f1954b..9f01929f0 100644 --- a/src/Speed/Indep/Src/Physics/Smackable.h +++ b/src/Speed/Indep/Src/Physics/Smackable.h @@ -5,6 +5,197 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Libs/Support/Utility/UCollections.h" +#include "Speed/Indep/Libs/Support/Utility/UListable.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Src/AI/AIAvoidable.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/rigidbodyspecs.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/smackable.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Interfaces/Simables/IDisposable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IExplodeable.h" +#include "Speed/Indep/Src/Interfaces/Simables/IRenderable.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimpleBody.h" +#include "Speed/Indep/Src/Main/EventSequencer.h" +#include "Speed/Indep/Src/Physics/Behaviors/RigidBody.h" +#include "Speed/Indep/Src/Physics/PhysicsObject.h" +#include "Speed/Indep/Src/Sim/Collision.h" +#include "Speed/Indep/Src/Sim/SimActivity.h" +class HeirarchyModel; + +struct SmackableParams : public Sim::Param { + SmackableParams(const SmackableParams &_ctor_arg) + : Sim::Param(_ctor_arg) // + , fMatrix(_ctor_arg.fMatrix) // + , fVirginSpawn(_ctor_arg.fVirginSpawn) // + , fScenery(_ctor_arg.fScenery) // + , fSimplePhysics(_ctor_arg.fSimplePhysics) // + {} + + SmackableParams(const UMath::Matrix4 &matrix, bool virginspawn, IModel *scenery, + bool simple_physics) + : Sim::Param(UCrc32(0xa6b47fac), this) // + , fMatrix(matrix) // + , fVirginSpawn(virginspawn) // + , fScenery(scenery) // + , fSimplePhysics(simple_physics) // + {} + + static UCrc32 TypeName() { + static UCrc32 value = "SmackableParams"; + return value; + } + + UMath::Matrix4 fMatrix; + bool fVirginSpawn; + IModel *fScenery; + bool fSimplePhysics; +}; + +class Smackable : public PhysicsObject, + public IDisposable, + public IRenderable, + public Sim::Collision::IListener, + public UTL::Collections::Listable, + public IExplodeable, + public EventSequencer::IContext { + public: + class Manager : public Sim::Activity, public UTL::Collections::Singleton { + 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); } + } + Manager(float rate); + virtual ~Manager(); + bool OnTask(HSIMTASK htask, float dT) override; + + private: + HSIMTASK mManageTask; // offset 0x50, size 0x4 + }; + + 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); } + } + + static ISimable *Construct(Sim::Param params); + static bool SimplifySort(const Smackable *lhs, const Smackable *rhs); + static bool TrySimplify(); + static Attrib::StringKey CYLINDER; + static Attrib::StringKey TUBE; + static Attrib::StringKey CONE; + static Attrib::StringKey SPHERE; + + Smackable(const UMath::Matrix4 &matrix, const Attrib::Gen::smackable &attributes, + const CollisionGeometry::Bounds *geoms, bool virginspawn, IModel *scenery, + bool simple_physics, bool is_persistant); + virtual ~Smackable(); + + bool IsRequired() const override { return false; } + bool InView() const override; + bool IsRenderable() const override; + HMODEL GetModelHandle() const override { + HMODEL result = nullptr; + if (mModel != nullptr) { + result = mModel->GetInstanceHandle(); + } + return result; + } + const IModel *GetModel() const override; + IModel *GetModel() override; + float DistanceToView() const override; + void OnCollision(const Sim::Collision::Info &cinfo) override; + bool OnExplosion(const UMath::Vector3 &normal, const UMath::Vector3 &position, float dT, IExplosion *explosion) override; + bool SetDynamicData(const EventSequencer::System *system, EventDynamicData *data) override; + void Kill() override; + bool OnTask(HSIMTASK htask, float dT) override; + void OnDetached(IAttachable *pOther) override; + EventSequencer::IEngine *GetEventSequencer() override { + if (mModel != nullptr) { + return mModel->GetEventSequencer(); + } + return nullptr; + } + + virtual void HidePart(const UCrc32 &name) {} + virtual void ShowPart(const UCrc32 &name) {} + virtual bool IsPartVisible(const UCrc32 &name) const { return true; } + + protected: + bool Simplify(); + void OnTaskSimulate(float dT) override; + void OnBehaviorChange(const UCrc32 &mechanic) override; + + private: + bool Dropout(); + bool ProcessDropout(float dT); + void ProcessDeath(float dT); + void ProcessOffWorld(float dT); + void Manage(float dT); + bool ValidateWorld(); + bool ShouldDie(); + bool CanRetrigger() const; + void OnImpact(float acceleration, float speed, Sim::Collision::Info::CollisionType type, ISimable *iother); + void DoImpactStimulus(unsigned int systemid, float intensity); + void CalcSimplificationWeight(); + + Attrib::Gen::smackable mAttributes; + float mSimplifyWeight; + float mAge; + float mLife; + float mDropTimer; + const float mDropOutTimerMax; + float mOffWorldTimer; + const float mAutoSimplify; + const bool mVirgin; + IModel *mModel; + const CollisionGeometry::Bounds *mGeometry; + HSIMTASK mManageTask; + bool mDroppingOut; + bool mPersistant; + ICollisionBody *mCollisionBody; + ISimpleBody *mSimpleBody; + UMath::Vector3 mLastImpactSpeed; + BehaviorSpecsPtr mRBSpecs; + UMath::Vector4 mLastCollisionPosition; +}; + +class RBSmackable : public RigidBody { + public: + static Behavior *Construct(const BehaviorParams &parms); + RBSmackable(const BehaviorParams &parms, const RBComplexParams &rp); + virtual ~RBSmackable(); + bool ShouldSleep() const override; + void OnTaskSimulate(float dT) override; + bool CanCollideWith(const RigidBody &other) const override; + bool CanCollideWithGround() const override; + bool CanCollideWithWorld() const override; + + private: + BehaviorSpecsPtr mSpecs; + unsigned int mFrame; +}; + +class SmackableAvoidable : public AIAvoidable { + 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); } + } + SmackableAvoidable(HeirarchyModel *model); + void SetRefrence(UTL::COM::IUnknown *pUnk) { + SetAvoidableObject(pUnk); + } + bool OnUpdateAvoidable(UMath::Vector3 &pos, float &sweep) override; + + private: + HeirarchyModel *mModel; +}; + +extern Attrib::StringKey BEHAVIOR_MECHANIC_EFFECTS; +extern unsigned int Smackable_RigidCount; #endif diff --git a/src/Speed/Indep/Src/Physics/SmackableTrigger.cpp b/src/Speed/Indep/Src/Physics/SmackableTrigger.cpp index e69de29bb..c8bbbec0e 100644 --- a/src/Speed/Indep/Src/Physics/SmackableTrigger.cpp +++ b/src/Speed/Indep/Src/Physics/SmackableTrigger.cpp @@ -0,0 +1,72 @@ +#include "Speed/Indep/Src/Physics/SmackableTrigger.h" + +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Libs/Support/Utility/UTypes.h" +#include "Speed/Indep/Src/World/WCollisionAssets.h" + +SmackableTrigger::SmackableTrigger(HMODEL__ *hmodel, bool virgin, const UMath::Matrix4 &objectmatrix, + const UMath::Vector3 &dim, unsigned int extra_flags) { + unsigned int flags = extra_flags | 0x40143; + void *eventMem = gFastMem.Alloc(0x48, "SmackTrigger"); + EventList *el = static_cast(eventMem); + mTrigger = new WTrigger(&objectmatrix, &dim, el, flags); + el->fNumEvents = 1; + mEventData = reinterpret_cast(el + 2); + EventStaticData *es = reinterpret_cast(el + 1); + es->fDataOffset = 0x10; + es->fEventID = 0xEB626F77; + es->fPad = 0; + es->fEventSize = 0x38; + WCollisionAssets::Get().AddTrigger(mTrigger); + if (!virgin) { + mTrigger->UpdateBox(&objectmatrix, &dim); + } + mEventData[4].fEventID = reinterpret_cast(hmodel); + mEventData[9].fEventID = static_cast(virgin); + UMath::Matrix4ToQuaternion(objectmatrix, + *reinterpret_cast(&mEventData[5])); + *reinterpret_cast(&mEventData[1]) = Vector4To3(objectmatrix.v3); + mTrigger->Enable(); +} + +void SmackableTrigger::Fire() { + mTrigger->FireEvents(nullptr); +} + +void SmackableTrigger::Disable() { + mTrigger->Disable(); +} + +void SmackableTrigger::Enable() { + mTrigger->Enable(); +} + +bool SmackableTrigger::IsEnabled() const { + if (mTrigger->IsEnabled()) {} + if (mTrigger->fFlags & 1) + return true; + return false; +} + +void SmackableTrigger::GetObjectMatrix(UMath::Matrix4 &matrix) const { + UMath::Vector4 q = *reinterpret_cast(&mEventData[5]); + UMath::QuaternionToMatrix4(q, matrix); + matrix.v3 = UMath::Vector4Make(*reinterpret_cast(&mEventData[1]), 1.0f); +} + +void SmackableTrigger::Move(const UMath::Matrix4 &matrix, const UMath::Vector3 &dim, bool virgin) { + mTrigger->UpdateBox(&matrix, &dim); + mEventData[9].fEventID = static_cast(virgin); + UMath::Matrix4ToQuaternion(matrix, + *reinterpret_cast(&mEventData[5])); + *reinterpret_cast(&mEventData[1]) = Vector4To3(matrix.v3); +} + +SmackableTrigger::~SmackableTrigger() { + gFastMem.Free(mTrigger->fEvents, 0x48, "SmackTrigger"); + mTrigger->fEvents = nullptr; + WCollisionAssets::Get().RemoveTrigger(mTrigger); + delete mTrigger; + mTrigger = nullptr; + mEventData = nullptr; +} diff --git a/src/Speed/Indep/Src/Physics/SmackableTrigger.h b/src/Speed/Indep/Src/Physics/SmackableTrigger.h index 34cb5a34a..c47a4f437 100644 --- a/src/Speed/Indep/Src/Physics/SmackableTrigger.h +++ b/src/Speed/Indep/Src/Physics/SmackableTrigger.h @@ -5,6 +5,34 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +#include "Speed/Indep/Src/Main/Event.h" +#include "Speed/Indep/Src/World/WTrigger.h" +struct HMODEL__; + +struct SmackableTrigger { + SmackableTrigger(HMODEL__ *hmodel, bool virgin, const UMath::Matrix4 &objectmatrix, + const UMath::Vector3 &dim, unsigned int extra_flags); + ~SmackableTrigger(); + void Fire(); + void Disable(); + void Enable(); + bool IsEnabled() const; + void GetObjectMatrix(UMath::Matrix4 &matrix) const; + void Move(const UMath::Matrix4 &matrix, const UMath::Vector3 &dim, bool virgin); + + 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); + } + } + + WTrigger *mTrigger; + Event::StaticData *mEventData; +}; #endif diff --git a/src/Speed/Indep/Src/Physics/SmokeableInfo.hpp b/src/Speed/Indep/Src/Physics/SmokeableInfo.hpp index 850ba598c..81ede70a1 100644 --- a/src/Speed/Indep/Src/Physics/SmokeableInfo.hpp +++ b/src/Speed/Indep/Src/Physics/SmokeableInfo.hpp @@ -5,58 +5,126 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UBitArray.h" #include "Speed/Indep/Libs/Support/Utility/UCrc.h" +#include "Speed/Indep/Libs/Support/Utility/UQueue.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" #include "Speed/Indep/Tools/AttribSys/Runtime/Common/AttribPrivate.h" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" + +struct SceneryModel; + +struct SmokeableSection { + float LastLoadTime; + int SectionID; + BitArray< unsigned int, 256 > Rebuilds; + + SmokeableSection() {} + + SmokeableSection(int section_id) + : LastLoadTime(0.0f) // + , SectionID(section_id) + , Rebuilds() {} + + SmokeableSection(const SmokeableSection &_ctor_arg) + : LastLoadTime(_ctor_arg.LastLoadTime) // + , SectionID(_ctor_arg.SectionID) + , Rebuilds(_ctor_arg.Rebuilds) {} + + SmokeableSection &operator=(const SmokeableSection &_ctor_arg) { + LastLoadTime = _ctor_arg.LastLoadTime; + SectionID = _ctor_arg.SectionID; + Rebuilds = _ctor_arg.Rebuilds; + return *this; + } +}; + +class SmokeableSectionQ { + public: + SmokeableSection *FindOrAdd(int section_id); + SmokeableSection *Find(int section_id); + + void Reset() { + mQueue.reset(); + } + + private: + UCircularQueue< SmokeableSection, 96 > mQueue; +}; -// total size: 0x40 class SmokeableSpawner { public: static const Attrib::Collection *FindAttributes(UCrc32 name); - static void Init(); void EndianSwap(); - void OnUnload(); - const struct ModelHeirarchy *GetRenderHeirarchy() const; - struct bHash32 GetRenderMesh() const; - void ShowInstance() const; - bool IsInstanceVisible() const; - void HideInstance() const; - void OnMoved(); - void OnLoad(unsigned int exclude_flags, bool hidden); - // UCrc32 GetModelName() const {} + UCrc32 GetModelName() const { + return mModel; + } - // UCrc32 GetCollisionName() const {} + UCrc32 GetCollisionName() const { + return mCollisionName; + } - // const UMath::Vector4 &GetOrientation() const {} + const UMath::Vector4 &GetOrientation() const { + return mOrientation; + } - // const UMath::Vector4 &GetPosition() const {} + const UMath::Vector4 &GetPosition() const { + return mPosition; + } - // unsigned int GetUniqueID() const {} + unsigned int GetUniqueID() const { + return mUniqueID; + } - // unsigned int GetExcludeFlags() const {} + unsigned int GetExcludeFlags() const { + return mExcludeFlags; + } private: - UMath::Quaternion mOrientation; // offset 0x0, size 0x10 - UMath::Vector4 mPosition; // offset 0x10, size 0x10 - UCrc32 mModel; // offset 0x20, size 0x4 - UCrc32 mCollisionName; // offset 0x24, size 0x4 - UCrc32 mAttributes; // offset 0x28, size 0x4 - uint32 mSceneryOverrideInfoNumber; // offset 0x2C, size 0x4 - uint32 mUniqueID; // offset 0x30, size 0x4 - uint32 mExcludeFlags; // offset 0x34, size 0x4 - struct SceneryModel *mSimModel; // offset 0x38, size 0x4 - uint32 pad; // offset 0x3C, size 0x4 + UMath::Quaternion mOrientation; + UMath::Vector4 mPosition; + UCrc32 mModel; + UCrc32 mCollisionName; + UCrc32 mAttributes; + uint32 mSceneryOverrideInfoNumber; + uint32 mUniqueID; + uint32 mExcludeFlags; + SceneryModel *mSimModel; + uint32 pad; + + friend class SceneryModel; +}; + +struct SmokeableSpawnerPack : public bTNode< SmokeableSpawnerPack > { + short ScenerySectionNumber; + short FirstSmokeableSpawnerID; + short NumSmokeableSpawners; + char EndianSwapped; + char Pad; + SmokeableSpawner SmokeableSpawners[512]; + + void OnLoad(unsigned int exclude_flags); + void OnUnload(); + void OnMoved(); + void EndianSwap(); + + static int Loader(bChunk *chunk); + static int Unloader(bChunk *chunk); + static bChunkLoader mLoader; }; +extern SmokeableSectionQ TheSmokeableSections; + #endif diff --git a/src/Speed/Indep/Src/Physics/VehicleBehaviors.h b/src/Speed/Indep/Src/Physics/VehicleBehaviors.h index 139d5bc9d..6fb1bc8ac 100644 --- a/src/Speed/Indep/Src/Physics/VehicleBehaviors.h +++ b/src/Speed/Indep/Src/Physics/VehicleBehaviors.h @@ -22,4 +22,35 @@ class VehicleBehavior : public Behavior { IVehicle *mVehicle; // offset 0x4C, size 0x4 }; +// total size: 0x28 +struct RBSimpleParams : public Sim::Param { + RBSimpleParams(const struct UMath::Vector3 &initPos, const struct UMath::Vector3 &initVel, + const struct UMath::Vector3 &initAngVel, const struct UMath::Matrix4 &initMat, + float initRadius, float initMass); + + RBSimpleParams(const RBSimpleParams &_ctor_arg); + + static UCrc32 TypeName() { + static UCrc32 value = "RBSimpleParams"; + return value; + } + + const struct UMath::Vector3 &finitPos; // offset 0x10, size 0x4 + const struct UMath::Vector3 &finitVel; // offset 0x14, size 0x4 + const struct UMath::Vector3 &finitAngVel; // offset 0x18, size 0x4 + const struct UMath::Matrix4 &finitMat; // offset 0x1C, size 0x4 + float finitRadius; // offset 0x20, size 0x4 + float finitMass; // offset 0x24, size 0x4 +}; + +inline RBSimpleParams::RBSimpleParams(const struct UMath::Vector3 &initPos, const struct UMath::Vector3 &initVel, + const struct UMath::Vector3 &initAngVel, const struct UMath::Matrix4 &initMat, + float initRadius, float initMass) + : Sim::Param(UCrc32(0xa6b47fac), static_cast(nullptr)), finitPos(initPos), finitVel(initVel), finitAngVel(initAngVel), + finitMat(initMat), finitRadius(initRadius), finitMass(initMass) {} + +inline RBSimpleParams::RBSimpleParams(const RBSimpleParams &_ctor_arg) + : Sim::Param(_ctor_arg), finitPos(_ctor_arg.finitPos), finitVel(_ctor_arg.finitVel), finitAngVel(_ctor_arg.finitAngVel), + finitMat(_ctor_arg.finitMat), finitRadius(_ctor_arg.finitRadius), finitMass(_ctor_arg.finitMass) {} + #endif diff --git a/src/Speed/Indep/Src/Physics/Wheel.cpp b/src/Speed/Indep/Src/Physics/Wheel.cpp index 5a3350adc..0b2913f8d 100644 --- a/src/Speed/Indep/Src/Physics/Wheel.cpp +++ b/src/Speed/Indep/Src/Physics/Wheel.cpp @@ -1,6 +1,66 @@ #include "Wheel.h" #include "Speed/Indep/Libs/Support/Utility/UTypes.h" +Wheel::Wheel(unsigned int flags) : mWorldPos(0.025f) // + , mNormal(UMath::Vector4::kZero) // + , mPosition(UMath::Vector3::kZero) // + , mFlags(flags) // + , mForce(UMath::Vector3::kZero) // + , mAirTime(0.0f) // + , mLocalArm(UMath::Vector3::kZero) // + , mCompression(0.0f) // + , mWorldArm(UMath::Vector3::kZero) // + , mVelocity(UMath::Vector3::kZero) // + , mSurface(SimSurface::kNull) // + , mSurfaceStick(0.0f) // + , mIntegral(UMath::Vector4::kZero) { +} + +void Wheel::UpdateTime(float dT) { + if (mSurfaceStick > 0.0f && dT < mSurfaceStick) { + mSurfaceStick = mSurfaceStick - dT; + return; + } + mSurfaceStick = 0.0f; +} + +void Wheel::UpdateSurface(const SimSurface &surface) { + if (mSurfaceStick <= 0.0f) { + mSurface = surface; + surface.STICK(); + } else { + if (!(surface == mSurface)) { + return; + } + } + mSurfaceStick = surface.STICK(); + mSurface.DebugOverride(); +} + +void Wheel::Reset() { + mIntegral = UMath::Vector4::kZero; + mSurfaceStick = 0.0f; + mAirTime = 0.0f; + mVelocity = UMath::Vector3::kZero; + mCompression = 0.0f; + mNormal = UMath::Vector4::kZero; + mForce = UMath::Vector3::kZero; + mSurface = SimSurface::kNull; + mWorldPos = WWorldPos(0.025f); +} + +bool Wheel::InitPosition(const IRigidBody &rb, float maxcompression) { + UMath::Matrix4 matrix; + rb.GetMatrix4(matrix); + const UMath::Vector3 &pos = rb.GetPosition(); + matrix.v3 = UMath::Vector4Make(pos, 1.0f); + const UMath::Vector3 &body_av = rb.GetAngularVelocity(); + const UMath::Vector3 &body_lv = rb.GetLinearVelocity(); + const WCollider *collider = rb.GetWCollider(); + UMath::Vector3 dim = rb.GetDimension(); + return UpdatePosition(body_av, body_lv, matrix, UMath::Vector3::kZero, 0.0f, maxcompression, false, collider, dim.y + dim.y); +} + bool Wheel::UpdatePosition(const UMath::Vector3 &body_av, const UMath::Vector3 &body_lv, const UMath::Matrix4 &body_matrix, const UMath::Vector3 &cog, float dT, float wheel_radius, bool usecache, const WCollider *collider, float vehicle_height) { diff --git a/src/Speed/Indep/Src/Physics/Wheel.h b/src/Speed/Indep/Src/Physics/Wheel.h index daaacb06c..83642d35d 100644 --- a/src/Speed/Indep/Src/Physics/Wheel.h +++ b/src/Speed/Indep/Src/Physics/Wheel.h @@ -17,7 +17,9 @@ class Wheel { } void operator delete(void *mem, std::size_t size) { - gFastMem.Free(mem, size, nullptr); + if (mem) { + gFastMem.Free(mem, size, nullptr); + } } Wheel() : mWorldPos(0.0f) { diff --git a/src/Speed/Indep/Src/Render/RenderConn.h b/src/Speed/Indep/Src/Render/RenderConn.h index df2cd16c9..e1262af53 100644 --- a/src/Speed/Indep/Src/Render/RenderConn.h +++ b/src/Speed/Indep/Src/Render/RenderConn.h @@ -14,6 +14,26 @@ namespace RenderConn { class Pkt_Smackable_Open : public Sim::Packet { public: + Pkt_Smackable_Open(bHash32 rendermesh, unsigned int objectworldid, + const CollisionGeometry::Bounds *collisionNode, + const ModelHeirarchy *heirarchy, unsigned int rendernode) + : mModelHash(rendermesh) // + , mObjectWUID(objectworldid) // + , mCollisionNode(collisionNode) // + , mHeirarchy(heirarchy) // + , mRenderNode(rendernode) {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash = "SmackableRenderConn"; + return hash; + } + + unsigned int Size() override; + + unsigned int Type() override; + + static unsigned int SType(); + // total size: 0x18 bHash32 mModelHash; // offset 0x4, size 0x4 unsigned int mObjectWUID; // offset 0x8, size 0x4 @@ -30,6 +50,18 @@ class Pkt_Smackable_Service : public Sim::Packet { this->mChildVisibility = 0xFFFFFF; } + bool IsVisible() const { + return mVisible; + } + + float DistanceToView() const { + return mDistanceToView; + } + + void SetChildVisibility(unsigned int visiblility) { + mChildVisibility = visiblility; + } + // total size: 0x10 bool mVisible; // offset 0x4, size 0x1 float mDistanceToView; // offset 0x8, size 0x4 diff --git a/src/Speed/Indep/Src/Sim/Collision.h b/src/Speed/Indep/Src/Sim/Collision.h index b0beb7623..52e01a403 100644 --- a/src/Speed/Indep/Src/Sim/Collision.h +++ b/src/Speed/Indep/Src/Sim/Collision.h @@ -49,6 +49,10 @@ struct Info { void Clear() {} + CollisionType Type() const { + return static_cast(type); + } + UMath::Vector3 position; // offset 0x0, size 0xC const Attrib::Collection *objAsurface; // offset 0xC, size 0x4 UMath::Vector3 normal; // offset 0x10, size 0xC @@ -77,6 +81,10 @@ void AddListener(IListener *listener, HSIMABLE participant, const char *who); void AddListener(IListener *listener, class UTL::COM::IUnknown *participant, const char *who); void RemoveListener(IListener *listener, const UTL::COM::IUnknown *participant); void RemoveListener(IListener *listener); +void AddParticipant(HSIMABLE participant); +void RemoveParticipant(HSIMABLE participant); +void AddParticipant(HSIMABLE participant); +void RemoveParticipant(HSIMABLE participant); }; // namespace Collision diff --git a/src/Speed/Indep/Src/Sim/SimModel.h b/src/Speed/Indep/Src/Sim/SimModel.h index ad5133109..e6eba2f49 100644 --- a/src/Speed/Indep/Src/Sim/SimModel.h +++ b/src/Speed/Indep/Src/Sim/SimModel.h @@ -91,7 +91,7 @@ class Model : public Sim::Object, } WUID GetWorldID() const override; - const CollisionGeometry::Bounds *GetCollisionGeometry() const override; + const CollisionGeometry::Bounds *GetCollisionGeometry() const override { return mGeometry; } void ReleaseModel() override; ISimable *GetSimable() const override { @@ -143,11 +143,14 @@ class Model : public Sim::Object, void EndDraw(); void EndSimulation(); - void UpdateVisibility(bool visible) {} + void UpdateVisibility(bool visible, float distance) { + mInView = visible; + mDistanceToView = distance; + } - bool IsRendering() const {} + bool IsRendering() const { return mService != nullptr; } - bool IsSimulating() const {} + bool IsSimulating() const { return mSimable != nullptr; } virtual void OnBeginSimulation() {} diff --git a/src/Speed/Indep/Src/Sim/SimSurface.h b/src/Speed/Indep/Src/Sim/SimSurface.h index 0f3d93483..1c5507693 100644 --- a/src/Speed/Indep/Src/Sim/SimSurface.h +++ b/src/Speed/Indep/Src/Sim/SimSurface.h @@ -54,4 +54,8 @@ inline bool operator!=(const SimSurface &surfaceA, const SimSurface &surfaceB) { return surfaceA.GetConstCollection() != surfaceB.GetConstCollection(); } +inline bool operator==(const SimSurface &surfaceA, const SimSurface &surfaceB) { + return surfaceA.GetConstCollection() == surfaceB.GetConstCollection(); +} + #endif diff --git a/src/Speed/Indep/Src/Sim/SimTypes.h b/src/Speed/Indep/Src/Sim/SimTypes.h index 832f77dac..6fb5421a0 100644 --- a/src/Speed/Indep/Src/Sim/SimTypes.h +++ b/src/Speed/Indep/Src/Sim/SimTypes.h @@ -64,6 +64,23 @@ class SimCollisionMap { class IRigidBody *GetSRB(int srbIndex) const; class IRigidBody *GetOrderedBody(int index) const; + bool TestBit(unsigned int index) const { + return (fBitMap[index / 64] >> (index % 64)) & 1; + } + + bool CollisionWithOrderedBody(int obIndex) const { + return TestBit(obIndex); + } + + bool CollisionWithAny() const { + for (int i = 0; i < 3; ++i) { + if (fBitMap[i] != 0) { + return true; + } + } + return false; + } + void Clear() { for (unsigned int i = 0; i < 3; ++i) { fBitMap[i] = 0; diff --git a/src/Speed/Indep/Src/World/CarInfo.hpp b/src/Speed/Indep/Src/World/CarInfo.hpp index 28e8872fd..a8bbc2aee 100644 --- a/src/Speed/Indep/Src/World/CarInfo.hpp +++ b/src/Speed/Indep/Src/World/CarInfo.hpp @@ -137,6 +137,12 @@ struct CarTypeInfo { char Skinnable; // offset 0xC7, size 0x1 int Padding; // offset 0xC8, size 0x4 int DefaultBasePaint; // offset 0xCC, size 0x4 + + CarUsageType GetCarUsageType() { return UsageType; } }; +extern CarTypeInfo *CarTypeInfoArray; + +inline CarTypeInfo *GetCarTypeInfo(CarType car_type) { return &CarTypeInfoArray[car_type]; } + #endif diff --git a/src/Speed/Indep/Src/World/ModelHeirarchy.hpp b/src/Speed/Indep/Src/World/ModelHeirarchy.hpp new file mode 100644 index 000000000..e2a51dcc2 --- /dev/null +++ b/src/Speed/Indep/Src/World/ModelHeirarchy.hpp @@ -0,0 +1,30 @@ +#ifndef WORLD_MODELHEIRARCHY_H +#define WORLD_MODELHEIRARCHY_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" + +struct ModelHeirarchy { + struct Node { + unsigned int mNameHash; + unsigned int mPad0; + unsigned int mModel; + unsigned short mPad1; + unsigned char mNumChildren; + unsigned char mFirstChild; + }; + + unsigned int mNameHash; + unsigned char mNumNodes; + unsigned char mFlags; + unsigned short pad; + + const Node *GetNodes() const { + return reinterpret_cast< const Node * >(this + 1); + } +}; + +#endif diff --git a/src/Speed/Indep/Src/World/Scenery.hpp b/src/Speed/Indep/Src/World/Scenery.hpp index 6083173f1..631a5103b 100644 --- a/src/Speed/Indep/Src/World/Scenery.hpp +++ b/src/Speed/Indep/Src/World/Scenery.hpp @@ -57,6 +57,69 @@ struct SceneryCullInfo { int PrecullerSectionNumber; // offset 0xB8, size 0x4 }; +struct ModelHeirarchy; +struct SceneryTreeNode; +struct tPrecullerInfo; + +struct SceneryInfo { + char DebugName[24]; + unsigned int NameHash[4]; + eModel *pModel[4]; + float Radius; + unsigned int MeshChecksum; + unsigned int mHeirarchyNameHash; + ModelHeirarchy *mHeirarchy; +}; + +struct ScenerySectionHeader : public bTNode< ScenerySectionHeader > { + int ChunksLoaded; + int SectionNumber; + int NumPolygonsInMemory; + int NumPolygonsInWorld; + SceneryInfo *pSceneryInfo; + int NumSceneryInfo; + SceneryInstance *pSceneryInstance; + int NumSceneryInstances; + SceneryTreeNode *SceneryTreeNodeTable; + int NumSceneryTreeNodes; + tPrecullerInfo *PrecullerInfoTable; + int NumPrecullerInfos; + int ViewsVisibleThisFrame; + + SceneryInstance *GetSceneryInstance(int scenery_instance_number) { + return &pSceneryInstance[scenery_instance_number]; + } + + SceneryInfo *GetSceneryInfo(SceneryInstance *scenery_instance) { + return &pSceneryInfo[scenery_instance->SceneryInfoNumber]; + } +}; + +struct SceneryOverrideInfo { + short SectionNumber; + short InstanceNumber; + unsigned short ExcludeFlags; + + void SetExcludeFlags(unsigned short exclude_flag_mask, unsigned short exclude_flag_override) { + ExcludeFlags = (ExcludeFlags & ~exclude_flag_mask) | exclude_flag_override; + } + + void EnableRendering() { + ExcludeFlags &= ~0x10; + SetExcludeFlags(0, 0); + } + + void DisableRendering() { + SetExcludeFlags(0x10, 0x10); + } + + void AssignOverrides(); + void AssignOverrides(ScenerySectionHeader *section_header); +}; + +SceneryOverrideInfo *GetSceneryOverrideInfo(int index); +ScenerySectionHeader *GetScenerySectionHeader(int section_number); + // total size: 0x8014 struct SceneryGroup : public bTNode { // SceneryGroup(unsigned int name_hash) {} diff --git a/src/Speed/Indep/Src/World/TrackPath.hpp b/src/Speed/Indep/Src/World/TrackPath.hpp index 93f225503..7545e9137 100644 --- a/src/Speed/Indep/Src/World/TrackPath.hpp +++ b/src/Speed/Indep/Src/World/TrackPath.hpp @@ -52,6 +52,14 @@ class TrackPathZone { int GetData(int index) { return Data[index]; } + + bVector2 *GetPosition() { + return &Position; + } + + bVector2 *GetDirection() { + return &Direction; + } }; // total size: 0x48C diff --git a/src/Speed/Indep/Src/World/WCollider.h b/src/Speed/Indep/Src/World/WCollider.h index fcafc2d6f..7f46a235a 100644 --- a/src/Speed/Indep/Src/World/WCollider.h +++ b/src/Speed/Indep/Src/World/WCollider.h @@ -30,6 +30,7 @@ class WCollider : public UTL::Collections::Listable { // total size: 0x10 }; + static WCollider *Create(unsigned int wuid, eColliderShape shape, unsigned int typeCheckMask, unsigned int exclusionMask); static void Destroy(WCollider *col); void Clear(); diff --git a/src/Speed/Indep/Src/World/WCollision.h b/src/Speed/Indep/Src/World/WCollision.h index cf8e7cef8..e5a9b1a10 100644 --- a/src/Speed/Indep/Src/World/WCollision.h +++ b/src/Speed/Indep/Src/World/WCollision.h @@ -11,7 +11,9 @@ #include "Speed/Indep/Src/Physics/Dynamics/Collision.h" #include "Speed/Indep/bWare/Inc/bMath.hpp" -struct WSurface : public CollisionSurface {}; +struct WSurface : public CollisionSurface { + WSurface() {} +}; struct WCollisionArticle { // total size: 0x10 diff --git a/src/Speed/Indep/Src/World/WCollisionTri.h b/src/Speed/Indep/Src/World/WCollisionTri.h index b88fd9b2b..e3ed0d595 100644 --- a/src/Speed/Indep/Src/World/WCollisionTri.h +++ b/src/Speed/Indep/Src/World/WCollisionTri.h @@ -10,6 +10,8 @@ #include "Speed/Indep/Libs/Support/Utility/UTypes.h" struct WCollisionTri { + WCollisionTri() {} + // total size: 0x30 UMath::Vector3 fPt0; // offset 0x0, size 0xC const struct SimSurface *fSurfaceRef; // offset 0xC, size 0x4 @@ -18,6 +20,7 @@ struct WCollisionTri { UMath::Vector3 fPt2; // offset 0x20, size 0xC WSurface fSurface; // offset 0x2C, size 0x2 unsigned short PAD; // offset 0x2E, size 0x2 + }; DECLARE_CONTAINER_TYPE(WCollisionWarnVector); diff --git a/src/Speed/Indep/Src/World/WTrigger.h b/src/Speed/Indep/Src/World/WTrigger.h index 2bc77fe21..023bfc1ec 100644 --- a/src/Speed/Indep/Src/World/WTrigger.h +++ b/src/Speed/Indep/Src/World/WTrigger.h @@ -5,8 +5,59 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Libs/Support/Utility/UMath.h" #include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" +struct EventList; + +// total size: 0x40 +struct __attribute__((packed)) Trigger { + UMath::Vector4 fMatRow0Width; // offset 0x00, size 0x10 + unsigned int fType : 4; // offset 0x10:0 + unsigned int fShape : 4; // offset 0x10:4 + unsigned int fFlags : 24; // offset 0x11:0 + 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: + 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); + } + } + + WTrigger(const UMath::Matrix4 *objectmatrix, const UMath::Vector3 *dim, EventList *eventList, + unsigned int flags); + ~WTrigger(); + void FireEvents(HSIMABLE__ *simable); + void UpdateBox(const UMath::Matrix4 *objectmatrix, const UMath::Vector3 *dim); + void UpdateCylinder(const UMath::Matrix4 *objectmatrix); + bool UpdatePos(const UMath::Matrix4 *objectmatrix); + void MakeMatrix(UMath::Matrix4 &matrix) const; + void GetCenter(UMath::Vector3 ¢er) const; + void GetBase(UMath::Vector3 &base) const; + bool HasEvent(unsigned int eventID) const; + + inline void Enable() { fFlags |= 1; } + + inline void Disable() { fFlags &= ~1; } + + inline bool IsEnabled(bool allowSilencables = true) const { + if (!(fFlags & 1)) + return false; + return true; + } +}; + // total size: 0x8 struct FireOnExitRec { class WTrigger &mTrigger; // offset 0x0, size 0x4 diff --git a/src/Speed/Indep/Src/World/WWorldPos.h b/src/Speed/Indep/Src/World/WWorldPos.h index b1c11652d..93aad1300 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,10 +32,15 @@ class WWorldPos { } WWorldPos(float yOffset) { + fFace.fSurface.fSurface = 0; + fFace.fSurface.fFlags = 0; + this->fYOffset = yOffset; this->fFaceValid = 0; this->fMissCount = 0; this->fUsageCount = 0; - this->fYOffset = yOffset; + fFace.fPt0 = UMath::Vector3::kZero; + fFace.fPt1 = UMath::Vector3::kZero; + fFace.fPt2 = UMath::Vector3::kZero; this->fSurface = nullptr; } @@ -44,7 +48,7 @@ class WWorldPos { // bool OffEdge() const {} - // bool OnValidFace() const {} + bool OnValidFace() const { return fFaceValid != 0; } void ForceFaceValidity() {} diff --git a/src/Speed/Indep/Src/World/WorldConn.h b/src/Speed/Indep/Src/World/WorldConn.h index 0021f2b36..e43308e27 100644 --- a/src/Speed/Indep/Src/World/WorldConn.h +++ b/src/Speed/Indep/Src/World/WorldConn.h @@ -112,6 +112,44 @@ void InitServices(); void UpdateServices(float dT); void RestoreServices(); +// total size: 0x60 +class Pkt_Body_Open : public Sim::Packet { + public: + Pkt_Body_Open(unsigned int id, const UMath::Matrix4 &matrix) : mMatrix(matrix), mID(id) {} + ~Pkt_Body_Open() override {} + + UCrc32 ConnectionClass() override { + static UCrc32 hash = "WorldBodyConn"; + return hash; + } + + unsigned int Size() override; + + unsigned int Type() override; + + static unsigned int SType(); + + UMath::Matrix4 mMatrix; // offset 0x4, size 0x40 + WUID mID; // offset 0x44, size 0x4 +}; + +// total size: 0x60 +class Pkt_Body_Service : public Sim::Packet { + public: + ~Pkt_Body_Service() override {} + + void SetMatrix(const UMath::Matrix4 &matrix) { + mMatrix = matrix; + } + + void SetVelocity(const UMath::Vector3 &velocity) { + mVelocity = velocity; + } + + UMath::Matrix4 mMatrix; // offset 0x4, size 0x40 + UMath::Vector3 mVelocity; // offset 0x44, size 0xC +}; + } // namespace WorldConn #endif diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h index 6633f4de8..ab43e5ae2 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h @@ -26,6 +26,13 @@ class StringKey { mString = str; } + 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,6 +53,14 @@ class StringKey { return mString != nullptr; } + bool IsNotEmpty() const { + return mString != nullptr && mString[0] != '\0'; + } + + const char *GetString() const { + return mString; + } + private: // total size: 0x10 unsigned long long mHash64; // offset 0x0, size 0x8 diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h index f458308a4..e2b42707e 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h @@ -5,6 +5,7 @@ #pragma once #endif +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h" #include "Speed/Indep/Libs/Support/Utility/UCOM.h" #include "Speed/Indep/Src/Misc/AttribAlloc.h" @@ -610,6 +611,14 @@ class RefSpec { RefSpec &operator=(const RefSpec &rhs); void Clean() const; + bool operator==(const RefSpec &rhs) const { + return mClassKey == rhs.mClassKey && mCollectionKey == rhs.mCollectionKey; + } + + bool operator!=(const RefSpec &rhs) const { + return !(*this == rhs); + } + void operator delete(void *ptr, std::size_t bytes) { Free(ptr, bytes, "RefSpec"); } @@ -666,7 +675,35 @@ class Attribute { bool SetLength(unsigned int); void SendChangeMsg() const; // TODO - template const T &Get(unsigned int index, T &result) const; + template const T &Get(unsigned int index, T &result) const { + const T *resultptr = reinterpret_cast(GetElementPointer(index)); + if (resultptr) { + result = *resultptr; + } + return result; + } + + bool Get(unsigned int index, StringKey &result) const { + const StringKey *resultptr = reinterpret_cast(GetElementPointer(index)); + if (resultptr) { + result = *resultptr; + return true; + } + return false; + } + + template const T &Get(unsigned int index) const { + return *reinterpret_cast(GetElementPointer(index)); + } + + 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"); @@ -684,7 +721,7 @@ class Attribute { return mInternal; } - bool Get(unsigned int index, RefSpec &result) { + bool Get(unsigned int index, RefSpec &result) const { const RefSpec *resultptr = reinterpret_cast(GetElementPointer(index)); if (resultptr) { @@ -748,6 +785,17 @@ class Instance { unsigned int LocalAttribCount() const; bool Add(Key attributeKey, unsigned int count); bool Remove(Key attributeKey); + + template bool AddAndSet(Key attributeKey, const T *data, unsigned int count) { + if (Add(attributeKey, count) || Contains(attributeKey)) { + Attribute newattrib = Get(attributeKey); + for (unsigned int i = 0; i < count; i++) { + newattrib.Set(i, data[i]); + } + return true; + } + return false; + } bool Modify(Key dynamicCollectionKey, unsigned int spaceForAdditionalAttributes); bool ModifyInternal(Key classKey, Key dynamicCollectionKey, unsigned int reserve); void Unmodify(); @@ -773,9 +821,9 @@ class Instance { return mCollection; } - void SetDefaultLayout(unsigned int bytes) { + void SetDefaultLayout(unsigned int bytes) const { if (mLayoutPtr == nullptr) { - mLayoutPtr = const_cast(DefaultDataArea(bytes)); + const_cast(this)->mLayoutPtr = const_cast(DefaultDataArea(bytes)); } } diff --git a/src/Speed/Indep/bWare/Inc/Strings.hpp b/src/Speed/Indep/bWare/Inc/Strings.hpp index 4a0387ed7..cfcca0cec 100644 --- a/src/Speed/Indep/bWare/Inc/Strings.hpp +++ b/src/Speed/Indep/bWare/Inc/Strings.hpp @@ -83,6 +83,7 @@ const char *bAllocateSharedString(const char *s); void bFreeSharedString(const char *s); unsigned int bStringHash(const char *text); unsigned int bStringHash(const char *text, int prefix_hash); +unsigned int bStringHashUpper(const char *text); int bStrToLong(const char *s); float bStrToFloat(const char *s); int bStrLen(const char *s); diff --git a/src/Speed/Indep/bWare/Inc/bList.hpp b/src/Speed/Indep/bWare/Inc/bList.hpp index 70079e26d..c72a66a62 100644 --- a/src/Speed/Indep/bWare/Inc/bList.hpp +++ b/src/Speed/Indep/bWare/Inc/bList.hpp @@ -36,8 +36,8 @@ class bNode { } bNode *AddAfter(bNode *insert_point) { - bNode *new_next = insert_point->Next; bNode *new_prev = this->Prev; // unused + bNode *new_next = insert_point->Next; insert_point->Next = this; new_next->Prev = this; this->Prev = insert_point; 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..56c02ac45 100644 --- a/tools/decomp-diff.py +++ b/tools/decomp-diff.py @@ -25,6 +25,7 @@ build_objdiff_symbol_rows, fail, run_objdiff_json, + RELOC_DIFF_CHOICES ) root_dir = ROOT_DIR 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/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) diff --git a/tools/write_smackable.py b/tools/write_smackable.py new file mode 100644 index 000000000..0c29151ba --- /dev/null +++ b/tools/write_smackable.py @@ -0,0 +1,533 @@ +#!/usr/bin/env python3 +"""Append function bodies to Smackable.cpp""" +filepath = 'src/Speed/Indep/Src/Physics/Common/Smackable.cpp' +functions = r''' +static float GetDropTimer(const Attrib::Gen::smackable &attributes) { + float result = 0.0f; + if (0.0f < attributes.DROPOUT(0)) { + if (0.0f < attributes.DROPOUT(1)) { + result = attributes.DROPOUT(0); + } + } + return result; +} + +bool Smackable::Simplify() { + if (mCollisionBody != nullptr && !mPersistant) { + const UMath::Vector3 &pos = static_cast< ISimable * >(this)->GetPosition(); + if (Sim::CanSpawnSimpleRigidBody(pos, true)) { + IRigidBody *irb = GetRigidBody(); + UMath::Vector3 position = irb->GetPosition(); + UMath::Vector3 velocity = irb->GetLinearVelocity(); + UMath::Vector3 angular = irb->GetAngularVelocity(); + float radius = irb->GetRadius(); + float mass = irb->GetMass(); + UMath::Matrix4 matrix = mCollisionBody->GetMatrix4(); + RBSimpleParams rbp(position, velocity, angular, matrix, radius, mass); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), UCrc32("SimpleRigidBody"), rbp); + return true; + } + } + return false; +} + +bool Smackable::TrySimplify() { + typedef UTL::Collections::Listable< Smackable, 160 > SmackList; + for (Smackable *const *iter = SmackList::GetList().begin(); + iter != SmackList::GetList().end(); ++iter) { + if ((*iter)->Simplify()) { + return true; + } + } + return false; +} + +ISimable *Smackable::Construct(Sim::Param params) { + const SmackableParams &sp = params.Fetch< SmackableParams >(UCrc32(0xa6b47fac)); + IModel *scenery = sp.fScenery; + if (scenery == nullptr) { + return nullptr; + } + Attrib::Gen::smackable attributes(scenery->GetAttributes()); + if (attributes.GetCollection() == nullptr) { + return nullptr; + } + IPlaceableScenery *placeable = nullptr; + bool is_persistant = scenery->QueryInterface(&placeable); + bool simple_physics = attributes.SimplePhysics() || sp.fSimplePhysics; + if (!simple_physics && !is_persistant && Smackable_RigidCount >= 0x14 && !TrySimplify()) { + return nullptr; + } + scenery->GetWorldID(); + const CollisionGeometry::Bounds *geoms = scenery->GetCollisionGeometry(); + if (geoms == nullptr || !(attributes.MASS() > 0.0f)) { + return nullptr; + } + UMath::Matrix4 matrix = sp.fMatrix; + bool canSpawn; + if (!simple_physics) { + canSpawn = Sim::CanSpawnRigidBody(Vector4To3(matrix.v3), false); + } else { + canSpawn = Sim::CanSpawnSimpleRigidBody(Vector4To3(matrix.v3), false); + } + if (!canSpawn) { + return nullptr; + } + if (!Manager::Exists()) { + new Manager(Smackable_ManagementRate); + } + return new Smackable(matrix, attributes, geoms, sp.fVirginSpawn, scenery, simple_physics, + is_persistant); +} + +Smackable::Smackable(const UMath::Matrix4 &matrix, const Attrib::Gen::smackable &attributes, + const CollisionGeometry::Bounds *geoms, bool virginspawn, IModel *scenery, + bool simple_physics, bool is_persistant) + : PhysicsObject(attributes.GetBase(), SIMABLE_SMACKABLE, scenery->GetWorldID(), 10) // + , IDisposable(this) // + , IRenderable(this) // + , IExplodeable(this) // + , EventSequencer::IContext(this) // + , mAttributes(attributes) // + , mRBSpecs(static_cast< ISimable * >(this), 0) // +{ + mSimplifyWeight = 0.0f; + mAge = 0.0f; + mDropTimer = 0.0f; + mLife = 0.125f; + mDropOutTimerMax = GetDropTimer(attributes); + mOffWorldTimer = 0.0f; + mAutoSimplify = attributes.AUTO_SIMPLIFY(); + mVirgin = virginspawn; + mPersistant = is_persistant; + mLastImpactSpeed = UMath::Vector3::kZero; + mModel = scenery; + mGeometry = geoms; + mManageTask = nullptr; + mDroppingOut = false; + mCollisionBody = nullptr; + mSimpleBody = nullptr; + UMath::Vector3 dimension; + geoms->GetHalfDimensions(dimension); + UMath::Scale(dimension, 2.0f, dimension); + float mass = attributes.MASS(); + UMath::Vector3 moment = attributes.MOMENT(); + bool active = !virginspawn; + if (simple_physics) { + float radius = UMath::Max(dimension.x, UMath::Max(dimension.y, dimension.z)); + RBSimpleParams rbp(Vector4To3(matrix.v3), UMath::Vector3::kZero, UMath::Vector3::kZero, + matrix, radius, mass); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), UCrc32("SimpleRigidBody"), rbp); + } else { + RBComplexParams rbparams(Vector4To3(matrix.v3), UMath::Vector3::kZero, + UMath::Vector3::kZero, matrix, mass, moment, dimension, geoms, + active, 0); + LoadBehavior(UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY), UCrc32("RBSmackable"), rbparams); + } + for (unsigned int i = 0; i < attributes.Num_BEHAVIORS(); ++i) { + const Attrib::StringKey &key = attributes.BEHAVIORS(i); + if (key.IsNotEmpty()) { + LoadBehavior(UCrc32(key), UCrc32(key), Sim::Param()); + } + } +} + +Smackable::~Smackable() {} + +bool Smackable::SetDynamicData(const EventSequencer::System *system, EventDynamicData *data) { + data->fPosition = mLastCollisionPosition; + return true; +} + +bool Smackable::OnExplosion(const UMath::Vector3 &normal, const UMath::Vector3 &position, + float dT, IExplosion *explosion) { + if (!(explosion->GetTargets() & 1)) { + return false; + } + IRigidBody *irb = GetRigidBody(); + float factor = mAttributes.ExplosionEffect(); + if (!(0.0f < factor)) { + return false; + } + float targetspeed = explosion->GetExpansionSpeed() * factor; + UMath::Vector3 point_velocity; + irb->GetPointVelocity(position, point_velocity); + float speed = UMath::Dot(point_velocity, normal); + if (speed < targetspeed) { + UMath::Vector3 impactvel; + UMath::Scale(normal, targetspeed - speed, impactvel); + UMath::Vector3 force; + UMath::Scale(impactvel, irb->GetMass() / dT, force); + irb->ResolveForce(force, position); + } + EventSequencer::IEngine *sequencer = static_cast< ISimable * >(this)->GetEventSequencer(); + if (sequencer != nullptr) { + float time = Sim::GetTime(); + sequencer->ProcessStimulus(0xab556d39, time, nullptr, EventSequencer::QUEUE_ALLOW); + if (explosion->GetCausality() != nullptr) { + sequencer->ProcessStimulus(0xffcd8a63, time, nullptr, EventSequencer::QUEUE_ALLOW); + } + } + if (!static_cast< ISimable * >(this)->GetCausality() && explosion->GetCausality()) { + ICause *cause = ICause::FindInstance(explosion->GetCausality()); + if (cause != nullptr) { + cause->OnCausedExplosion(explosion, static_cast< ISimable * >(this)); + } + } + return true; +} + +void Smackable::OnBehaviorChange(const UCrc32 &mechanic) { + PhysicsObject::OnBehaviorChange(mechanic); + if (mechanic == UCrc32(BEHAVIOR_MECHANIC_RIGIDBODY)) { + static_cast< ISimable * >(this)->QueryInterface(&mCollisionBody); + if (mCollisionBody != nullptr) { + float detach = mAttributes.DETACH_FORCE(); + if (mVirgin && detach != 0.0f) { + mCollisionBody->AttachedToWorld(UMath::Max(0.0f, detach), true); + } + const CollisionGeometry::Bounds *cog = + mGeometry->fCollection->GetChild(mGeometry, UCrc32(0x28b0bb8d)); + if (cog == nullptr) { + mCollisionBody->DistributeMass(); + } else { + UMath::Vector3 cog_position; + cog->GetPosition(cog_position); + mCollisionBody->SetCenterOfGravity(cog_position); + } + } + static_cast< ISimable * >(this)->QueryInterface(&mSimpleBody); + if (mSimpleBody != nullptr) { + mSimpleBody->ModifyFlags(0, 0x20b); + ReleaseBehavior(UCrc32(BEHAVIOR_MECHANIC_EFFECTS)); + } + } +} + +void Smackable::DoImpactStimulus(unsigned int systemid, float intensity) { + float externalTime = Sim::GetTime(); + EventSequencer::IEngine *iev = static_cast< ISimable * >(this)->GetEventSequencer(); + if (iev != nullptr) { + EventSequencer::System *system = iev->FindSystem(systemid); + if (system != nullptr) { + float clamped = UMath::Min(intensity, 1.0f); + if (clamped < 0.0f) { + clamped = 0.0f; + } + unsigned int level = static_cast< unsigned int >(clamped * 6.0f); + for (unsigned int i = 0; i <= level; ++i) { + unsigned int stimulus; + DamageZone::GetImpactStimulus(stimulus); + system->ProcessStimulus(stimulus, externalTime, + static_cast< EventSequencer::IContext * >(this), + EventSequencer::QUEUE_ALLOW); + } + } + } +} + +void Smackable::OnImpact(float acceleration, float speed, + Sim::Collision::Info::CollisionType type, ISimable *iother) { + Sim::GetTime(); + EventSequencer::IEngine *iev = static_cast< ISimable * >(this)->GetEventSequencer(); + if (iev != nullptr) { + if (type == Sim::Collision::Info::WORLD) { + DoImpactStimulus(0x7ebe81c0, speed / MPH2MPS(100.0f)); + } else if (static_cast< int >(type) < 3) { + if (type == Sim::Collision::Info::OBJECT && iother != nullptr) { + float intensity = acceleration / MPH2MPS(100.0f); + DoImpactStimulus(0xd59062c8, intensity); + if (iother->IsPlayer()) { + DoImpactStimulus(0x2f698829, intensity); + } + if (iother->GetSimableType() == SIMABLE_VEHICLE) { + DoImpactStimulus(0x80b88c1d, intensity); + } + } + } else if (type == Sim::Collision::Info::GROUND) { + DoImpactStimulus(0x2bf74e61, speed * 0.1f); + } + } +} + +void Smackable::OnCollision(const Sim::Collision::Info &cinfo) { + EventSequencer::IEngine *iev = static_cast< ISimable * >(this)->GetEventSequencer(); + if (iev == nullptr) { + return; + } + float speed = UMath::Length(cinfo.closingVel); + if (speed < 0.0f) { + return; + } + mLastCollisionPosition = UMath::Vector4Make(cinfo.position, 0.0f); + HSIMABLE myHandle = static_cast< ISimable * >(this)->GetInstanceHandle(); + if (cinfo.objA == myHandle) { + UMath::Vector3 normal = cinfo.normal; + mLastImpactSpeed = cinfo.objAVel; + ISimable *other = ISimable::FindInstance(cinfo.objB); + OnImpact(cinfo.impulseA, speed, + static_cast< Sim::Collision::Info::CollisionType >(cinfo.type), other); + } else if (cinfo.objB == myHandle) { + UMath::Vector3 normal; + UMath::Scale(cinfo.normal, -1.0f, normal); + mLastImpactSpeed = cinfo.objBVel; + ISimable *other = ISimable::FindInstance(cinfo.objA); + OnImpact(cinfo.impulseB, speed, + static_cast< Sim::Collision::Info::CollisionType >(cinfo.type), other); + } +} + +bool Smackable::InView() const { + if (mModel == nullptr) { return false; } + return mModel->InView(); +} + +bool Smackable::IsRenderable() const { return mModel != nullptr; } + +HMODEL Smackable::GetModelHandle() const { + if (mModel == nullptr) { return nullptr; } + return mModel->GetInstanceHandle(); +} + +const IModel *Smackable::GetModel() const { return mModel; } +IModel *Smackable::GetModel() { return mModel; } + +float Smackable::DistanceToView() { + if (mModel == nullptr) { return 0.0f; } + return mModel->DistanceToView(); +} + +void Smackable::Kill() { + if (mManageTask != nullptr) { RemoveTask(mManageTask); mManageTask = nullptr; } + if (mModel != nullptr && !mPersistant) { mModel->ReleaseModel(); mModel = nullptr; } + if (mCollisionBody != nullptr) { + mCollisionBody->DisableModeling(); + mCollisionBody->DisableTriggering(); + } + if (mSimpleBody != nullptr) { mSimpleBody->ModifyFlags(0x308, 0); } + PhysicsObject::Kill(); +} + +bool Smackable::Dropout() { + if (mCollisionBody == nullptr) { return false; } + if (mDropOutTimerMax <= 0.0f) { return false; } + if (mCollisionBody->IsAttachedToWorld()) { return false; } + mCollisionBody->DisableModeling(); + mCollisionBody->DisableTriggering(); + mDropTimer = mDropOutTimerMax; + return true; +} + +bool Smackable::ValidateWorld() { + const UMath::Vector3 &pos = static_cast< ISimable * >(this)->GetPosition(); + WWorldPos &wpos = static_cast< ISimable * >(this)->GetWPos(); + wpos.FindClosestFace(pos, true); + if (!wpos.OnValidFace()) { return false; } + float height = wpos.HeightAtPoint(pos); + IRigidBody *irb = GetRigidBody(); + float radius = irb->GetRadius(); + return height <= pos.y + radius; +} + +bool Smackable::ShouldDie() { + if (mDroppingOut) { return false; } + if (mCollisionBody == nullptr) { return false; } + if (mCollisionBody->IsSleeping()) { + if (!mCollisionBody->HasHadObjectCollision()) { return true; } + } + if (mCollisionBody == nullptr) { return false; } + return !mCollisionBody->IsModeling(); +} + +bool Smackable::CanRetrigger() const { + if (mCollisionBody == nullptr) { return false; } + const WWorldPos &wpos = const_cast< Smackable * >(this)->GetWPos(); + if (!wpos.OnValidFace()) { return false; } + return mCollisionBody->IsSleeping(); +} + +void Smackable::ProcessDeath(float dT) { + if (!ShouldDie()) { mLife = 0.125f; return; } + if (mLife > 0.0f) { mLife -= dT; return; } + if (Dropout()) { return; } + if (mModel != nullptr) { + if (CanRetrigger()) { + if (!mVirgin || mCollisionBody == nullptr || !mCollisionBody->IsAttachedToWorld()) { + ITriggerableModel *itrigger = nullptr; + if (mModel->QueryInterface(&itrigger)) { + UMath::Matrix4 mat; + static_cast< ISimable * >(this)->GetTransform(mat); + itrigger->PlaceTrigger(mat, true); + static_cast< ISimable * >(this)->Detach(mModel); + mModel = nullptr; + } + } else { + ISceneryModel *iscenery = nullptr; + if (mModel->QueryInterface(&iscenery)) { iscenery->RestoreScene(); mModel = nullptr; } + } + } else { + if (mModel != nullptr && !mPersistant) { mModel->ReleaseModel(); mModel = nullptr; } + } + } + static_cast< ISimable * >(this)->Kill(); +} + +bool Smackable::ProcessDropout(float dT) { + if (mDropOutTimerMax <= 0.0f || mDropTimer <= 0.0f) { return false; } + IRigidBody *irb = GetRigidBody(); + mDropTimer -= dT; + float dropspeed = mAttributes.DROPOUT(1); + irb->ModifyYPos(-dropspeed * dT); + if (mDropTimer <= 0.0f) { + static_cast< ISimable * >(this)->Kill(); + mDropTimer = 0.0f; + } + return true; +} + +void Smackable::ProcessOffWorld(float dT) { + if (mAttributes.ALLOW_OFF_WORLD()) { return; } + if (ValidateWorld()) { mOffWorldTimer = 0.0f; } + else { + mOffWorldTimer += dT; + if (mOffWorldTimer >= 1.0f) { + if (mModel != nullptr && !mPersistant) { mModel->ReleaseModel(); mModel = nullptr; } + static_cast< ISimable * >(this)->Kill(); + } + } +} + +bool Smackable::OnTask(HSIMTASK htask, float dT) { + if (htask == mManageTask) { Manage(dT); return true; } + return PhysicsObject::OnTask(htask, dT); +} + +void Smackable::OnDetached(IAttachable *pOther) { + if (UTL::COM::ComparePtr(pOther, mModel)) { + mModel = nullptr; + if (!UTL::Collections::GarbageNode< PhysicsObject, 160 >::IsDirty()) { + static_cast< ISimable * >(this)->Kill(); + } + } + PhysicsObject::OnDetached(pOther); +} + +void Smackable::CalcSimplificationWeight() { + if (mCollisionBody == nullptr || mPersistant) { mSimplifyWeight = -1.0f; } + const UMath::Vector3 &pos = static_cast< ISimable * >(this)->GetPosition(); + float dist = Sim::DistanceToCamera(pos); + IRigidBody *irb = GetRigidBody(); + float radius = irb->GetRadius(); + float baseweight = static_cast< float >(mAttributes.CAN_SIMPLIFY()); + if (!static_cast< IRenderable * >(this)->IsRenderable()) { baseweight += baseweight; } + mSimplifyWeight = (dist + baseweight) / radius; +} + +void Smackable::Manage(float dT) { + ProcessDeath(dT); + ProcessOffWorld(dT); + CalcSimplificationWeight(); +} + +void Smackable::OnTaskSimulate(float dT) { + mAge += dT; + if (mCollisionBody != nullptr && mAutoSimplify > 0.0f && mAutoSimplify < mAge) { + Simplify(); + } + mDroppingOut = ProcessDropout(dT); + IRigidBody *irb = GetRigidBody(); + if (mSimpleBody != nullptr) { + UMath::Vector3 gravity; + gravity.x = 0.0f; + gravity.y = mRBSpecs.GRAVITY(); + gravity.z = 0.0f; + UMath::Scale(gravity, irb->GetMass(), gravity); + irb->ResolveForce(gravity); + } +} + +EventSequencer::IEngine *Smackable::GetEventSequencer() { + return PhysicsObject::GetEventSequencer(); +} + +Smackable::Manager::Manager(float rate) : Sim::Object(0) { + AddTask("Smackable", rate, 0.0f, Sim::TASK_FRAME_FIXED); +} + +Smackable::Manager::~Manager() {} + +bool Smackable::Manager::OnTask(HSIMTASK htask, float dT) { + UTL::Collections::Listable< Smackable, 160 >::Sort(Smackable::SimplifySort); + return Sim::Object::OnTask(htask, dT); +} + +Behavior *RBSmackable::Construct(const BehaviorParams &parms) { + const RBComplexParams &rp = parms.fparams.Fetch< RBComplexParams >(UCrc32(0xa6b47fac)); + return new RBSmackable(parms, rp); +} + +RBSmackable::RBSmackable(const BehaviorParams &parms, const RBComplexParams &rp) + : RigidBody(parms, rp) // + , mSpecs(parms.fowner, 0) // +{ + mFrame = 0; + Smackable_RigidCount++; +} + +RBSmackable::~RBSmackable() { Smackable_RigidCount--; } + +bool RBSmackable::ShouldSleep() const { + if (Dynamics::Articulation::IsJoined(this)) { return false; } + return RigidBody::ShouldSleep(); +} + +void RBSmackable::OnTaskSimulate(float dT) { + RigidBody::OnTaskSimulate(dT); + mFrame++; +} + +bool RBSmackable::CanCollideWith(const RigidBody &other) const { + if (Dynamics::Articulation::IsJoined(this, &other)) { return false; } + return RigidBody::CanCollideWith(other); +} + +bool RBSmackable::CanCollideWithGround() const { + if (IsAttachedToWorld()) { return false; } + return RigidBody::CanCollideWithGround(); +} + +bool RBSmackable::CanCollideWithWorld() const { + if (IsAttachedToWorld()) { return false; } + if (!HasHadCollision()) { + float velSquare = UMath::LengthSquare(GetLinearVelocity()); + if (velSquare < 4.0f) { + if ((mFrame & 3) != 0) { return false; } + } else if (velSquare < 225.0f) { + if ((mFrame & 1) != 0) { return false; } + } + } + return RigidBody::CanCollideWithWorld(); +} + +SmackableAvoidable::SmackableAvoidable(HeirarchyModel *model) + : AIAvoidable(model != nullptr ? static_cast< UTL::COM::IUnknown * >(static_cast< ITriggerableModel * >(model)) : nullptr) // + , mModel(model) // +{ +} + +bool SmackableAvoidable::OnUpdateAvoidable(UMath::Vector3 &pos, float &sweep) { + return mModel->OnUpdateAvoidable(pos, sweep); +} + +bool Smackable::SimplifySort(Smackable *lhs, Smackable *rhs) { + return lhs->mSimplifyWeight < rhs->mSimplifyWeight; +} + +bool Smackable::IsRequired() const { return false; } +void Smackable::HidePart(const UCrc32 &name) {} +void Smackable::ShowPart(const UCrc32 &name) {} +bool Smackable::IsPartVisible(const UCrc32 &name) const { return false; } +''' +with open(filepath, 'a') as f: + f.write(functions) +print("Done")