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..d5b2cbbb3 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,7 +95,14 @@ Foo::Foo() - Use `nullptr` exclusively for null pointers. - Prefer `if (ptr)` / `if (!ptr)` over explicit null comparisons when the change is local and verified safe. - When a match-sensitive TU has many explicit `nullptr` checks and you decide to normalize them, prefer one mechanical full-TU pass over piecemeal cleanup. Rebuild the unit and re-check its status before keeping the rewrite. +- When a helper is doing address arithmetic, prefer `intptr_t` / `uintptr_t` or byte-pointer (`reinterpret_cast`) math over plain `int` parameters or integerized pointer subtraction. - Inline assembly is acceptable when it is needed to preserve dead-code compares, ordering, or other compiler behavior that source alone cannot reproduce. +- In low-level list / node / allocator code, prefer existing helper methods such as `AddBefore`, `AddAfter`, `Remove`, `GetPrev`, `GetNext`, or typed accessors over open-coding link rewiring once the helper exists. + +### Header prologues and preprocessor checks + +- In headers, keep the guard / `EA_PRAGMA_ONCE_SUPPORTED` block before any project `#include`; do not place includes ahead of `#pragma once`. +- Use `#ifdef MACRO` / `#ifndef MACRO` for presence checks. Reserve bare `#if MACRO` for cases where you really need the macro's numeric value. ### Forward declarations and local prototypes @@ -103,6 +110,7 @@ Foo::Foo() - If the repo already has a header declaration/definition for a type, include that header instead of redeclaring the type locally. - If the repo only has an empty or stub owner header, and line info / surrounding source clearly points at that header's subsystem, prefer populating that owner header over leaving a recovered project type declaration inside a `.cpp`. - Only keep a local forward declaration when no canonical repo header exists yet and you have verified that the ownership is still unresolved. +- Likewise for project free functions: if a declaration is shared across translation units, move it into the owning header instead of leaving ad-hoc local prototypes in `.cpp` files. - Prefer moving helper template declarations next to their real use site instead of leaving them in an unrelated file. ### Pointer style @@ -117,10 +125,13 @@ Foo::Foo() - Preserve the original `class` / `struct` kind from existing headers or Dwarf / PS2 evidence; do not treat it as a cosmetic style choice. - Treat header declarations as the repo source of truth. If the repo only has local `.cpp` partial declarations, verify the kind with the PS2 dump instead of copying them blindly. - Even forward declarations and local partial declarations should use the accurate keyword when known. +- Keep the `// total size: 0x...` comment above the recovered type declaration instead of burying it inside the body. +- When a recovered type is a `class`, keep explicit access sections and put the method/accessor block before the member layout block unless existing repo evidence shows otherwise. - Preserve the member naming style that DWARF shows. Some types use `mMember`, others use `m_member`; do not normalize them. - Preserve recovered member names, types, order, and offset comments. Do not invent placeholder members named `pad`, `unk`, `unknown`, or `field_XXXX` for game code just to make a layout compile. - If a member is genuinely unknown, stop and verify it with `find-symbol.py`, GC Dwarf, and PS2 data. If the layout is still incomplete, add a short TODO above the type instead of burying uncertainty in fake member names. - Add offset / size comments when you are writing recovered type layouts from DWARF. +- In recovered layouts, prefer explicit-width aliases such as `uint8` / `uint16` when the field width is known. Use plain `char` for text / byte buffers and `signed char` when the field is a signed 8-bit counter. - Define inline member functions in headers only when DWARF shows that they are genuinely inlined in the binary. - Use `struct` for POD-like data carriers with public fields; use `class` for behavior-heavy types only when that matches the recovered type information. - Keep tiny placeholder methods as concise inline bodies when that is already the local pattern. @@ -134,13 +145,27 @@ Foo::Foo() ### Dense local code - Expand dense one-line helper structs, declaration blocks, and function bodies in non-match-sensitive files into normal multiline formatting. +- In low-level headers, prefer normal multi-line bodies for touched inline operators and accessors instead of stacking `{ return ...; }` on one line, unless the surrounding file clearly uses intentional placeholder one-liners. - Prefer readable blocks over stacked one-line statements when behavior does not depend on exact source shape. +- In touched validation/parsing code, prefer explicit min/max or boundary checks over equivalent magic-constant arithmetic when the clearer form still compiles to the verified result. +- In parser/state-table code, prefer named enums and enum-typed state variables over anonymous integer state codes when that rewrite is verified safe. + +### Recovery markers + +- Remove stale recovery markers such as `// TODO`, `// UNSOLVED`, or `// STRIPPED` when the touched code is now implemented or understood. +- If a marker still needs to stay, give it short context such as ownership uncertainty, a Dwarf caveat, a platform/config note, or a scratch/link reference. Avoid bare marker-only comments. +- Do not leave `// TODO` hanging off a declaration or helper you just implemented; either finish the thought or remove the marker. ### Uncertain ownership - If a declaration or global clearly compiles but its original home is uncertain, add a short TODO comment instead of inventing structure you cannot justify yet. - When ownership matters, verify it with `decomp-workflow.py`, `decomp-context.py`, and `line-lookup` before moving code. +### Readable helper extraction + +- When touched recovered code repeats the same pointer/boundary arithmetic, prefer a short named helper or accessor such as `GetTop`, `GetBot`, `GetNext`, `GetPrev`, `GetStringTableStart`, or `GetStringTableEnd` if that shape is already supported by Dwarf/inlining evidence. +- Prefer call sites that use those helpers or existing container APIs over re-encoding the same arithmetic or link manipulation inline. + ## Phase 3: Things Not To "Clean Up" Blindly - Do not move an inline method out of a header just because it looks cleaner. @@ -172,3 +197,9 @@ Keep the cleanup only if the build succeeds and the relevant match status is unc - The trailing `//` initializer-list markers are an intentional repo convention, not noise to remove. - Small `if (ptr)` cleanup batches can be kept in match-sensitive code, but only after rebuilding the affected unit. - Dense frontend shim files benefit from multiline struct/prototype/function formatting. +- Header prologues should keep the `EA_PRAGMA_ONCE_SUPPORTED` block ahead of includes, not after them. +- Bare `#if MACRO` presence checks are review bait; use `#ifdef` / `#ifndef` unless you are intentionally testing a numeric config value. +- Reviewed recovered headers tend to keep total-size comments above the type, methods before fields, explicit access sections, and fixed-width aliases for width-known narrow integer members. +- Reviewed fixups also remove stale bare recovery markers or replace them with context, and prefer existing list/node helpers over hand-written pointer/link rewiring. +- Some reviewed fixups improved readability without losing match by replacing opaque range-check arithmetic with explicit bounds and by moving repeated pointer/boundary math behind short named helpers. +- Other recurring review churn came from plain-`int` address helpers, stray local `.cpp` prototypes for shared functions, and integer-coded parser states where named enums were clearer but still matched. diff --git a/.github/skills/execute/SKILL.md b/.github/skills/execute/SKILL.md index 7abf9cb3f..5ba0bd156 100644 --- a/.github/skills/execute/SKILL.md +++ b/.github/skills/execute/SKILL.md @@ -11,6 +11,9 @@ the produced C++ compiles to byte-identical object code against the original ret For each function, "done" means both objdiff and normalized DWARF are exact. +Human review is not a substitute for running `dwarf compare`. Each function should hit +its own `verify` gate before you treat it as ready to hand off, commit, or move past. + ## Overview This workflow combines several smaller workflows: @@ -152,6 +155,10 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName If it fails, follow up with `decomp-workflow.py diff` and `decomp-workflow.py dwarf` until both checks pass. +Do not queue up several "probably done" functions and leave the DWARF check for later. +Close the `verify` gate per function before moving on whenever feasible; otherwise the +reviewer ends up doing avoidable DWARF triage. + ### 3g. Periodic reassessment After every few functions, re-run the full status check: @@ -189,6 +196,8 @@ For any remaining nonmatching functions, make one final pass using the implement or refiner workflow with all context accumulated during the session. Do not report a function as complete unless its per-function `verify` check also passes. +Do not hand a function to review as "done except maybe DWARF" — either resolve the DWARF +failure yourself or explicitly call out the blocker and why it remains. ## Phase 5: Report diff --git a/.github/skills/implement/SKILL.md b/.github/skills/implement/SKILL.md index 25f56b926..cbd94da78 100644 --- a/.github/skills/implement/SKILL.md +++ b/.github/skills/implement/SKILL.md @@ -9,6 +9,11 @@ Your goal is to decompile a specific function: writing C++ source that compiles A function is not done until it is exact in both objdiff and normalized DWARF. +Reviewers should not be spending their time rediscovering DWARF mismatches. Before you +report progress, ask for review, hand the function off, or switch to another target, you +must run the per-function verification gate yourself and treat any DWARF failure as your +next task, not as review debt. + ## Phase 1: Gather Context Collect data from **all** of these sources in parallel where possible. @@ -156,6 +161,16 @@ python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName If the build fails, fix compilation errors first. +As soon as you have a compiling draft, run the combined verification gate immediately: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +Do this before you spend a long time polishing late instruction mismatches. If `verify` +already shows a DWARF failure, fix that first so you are not polishing code the reviewer +will bounce anyway. + ### Check the diff ```sh @@ -203,6 +218,17 @@ debug-line owner files for each DWARF `// Range:` block, which makes it much eas spot inlines that are coming from the wrong header or owner file. Exact line-number agreement is a useful secondary hint, but file ownership is the first thing to check. +Use this as the default loop when the function compiles but `verify` is still failing: + +1. Run `verify`. +2. If DWARF fails, run `dwarf`. +3. Fix the structural issue the DWARF diff is pointing at first: missing/extra locals, + wrong qualifiers or parameter types, wrong inline ownership, wrong helper/header owner, + or a source shape that outlined something that should be inlined. +4. Rebuild and rerun `verify`. +5. Only return to instruction-by-instruction cleanup once the remaining failures are no + longer obvious DWARF-compare issues. + Manual fallback: After writing your code, you can also run the dwarf dump on the compiled output and then query your output dump with lookup.py to compare your decompiled functions against the originals. Since the address of the function you're working on can keep changing @@ -233,6 +259,9 @@ Every mismatched instruction is a signal — don't settle for "close enough". Reaching 100% instruction matching status is not enough. Stay in the loop until `verify` passes, which means the DWARF of the function also matches after normalization. +Do not leave a function in a "review-ready" or "good enough for now" state with a known +DWARF failure unless you are explicitly blocked and you document that blocker clearly. + ## Phase 5: Report Summarize: diff --git a/.github/skills/refiner/SKILL.md b/.github/skills/refiner/SKILL.md index a6aeb2125..0054e6e18 100644 --- a/.github/skills/refiner/SKILL.md +++ b/.github/skills/refiner/SKILL.md @@ -15,9 +15,25 @@ approaches that were tried before — instead, apply systematic lateral analysis - A diff is available (`decomp-diff.py -u -d `). - The "obvious" translation from Ghidra has been attempted. - You have been given the current source code and the diff. +- You have already run the per-function `verify` gate and know whether the remaining work + is still structural DWARF cleanup or true late-stage instruction cleanup. + +Refiner is not the place to dump unresolved DWARF debt on a reviewer. If `verify` or +`dwarf` is still showing obvious structural mismatches (missing locals, wrong types, +wrong inline ownership, wrong helper/header owner), fix those first or drop back to the +implementer workflow before doing late instruction polish. ## Phase 1: Read the full diff without collapsing +Before you start a refiner pass, confirm the gate status: + +```sh +python tools/decomp-workflow.py verify -u main/Path/To/TU -f FunctionName +``` + +If the combined gate is failing for reasons that are still clearly visible in the DWARF +diff, address those first instead of treating them as reviewer follow-up. + Preferred shortcut: ```sh @@ -151,6 +167,9 @@ DWARF mismatches to watch for: - Wrong return type - Missing inlined function records (means an inline call was outlined) +If these mismatches are still present, you are not in pure refiner territory yet. Resolve +them before you ask a reviewer to spend time on the function. + ## Phase 4: Report Summarize: diff --git a/.github/skills/scaffold/SKILL.md b/.github/skills/scaffold/SKILL.md index a2f062f50..3fe3d9256 100644 --- a/.github/skills/scaffold/SKILL.md +++ b/.github/skills/scaffold/SKILL.md @@ -39,6 +39,9 @@ Preserve the real `class` / `struct` kind while scaffolding. Check existing head then use Dwarf plus PS2 visibility / vtable info to decide the type kind. Even temporary forward declarations should match the known original kind. +Keep the header prologue in repo order: header guard, `EA_PRAGMA_ONCE_SUPPORTED` block, +then includes. Do not drop project includes ahead of `#pragma once`. + If the repo already has a header for a type you need, include that header instead of adding a new local forward declaration. Only forward-declare when no canonical repo header exists yet and you have verified that the ownership is still unresolved. @@ -47,11 +50,26 @@ Preserve real member names, types, order, and offset comments while scaffolding. fill gaps with invented `pad`, `unk`, or `field_XXXX` members for game types; verify the layout from Dwarf / PS2 data and leave a TODO over the type if a field is still uncertain. +Keep the `// total size: 0x...` comment above the recovered type declaration. When the +recovered type is a `class`, keep explicit access sections and prefer putting methods / +accessors before the member layout block unless existing repo evidence says otherwise. + +When a recovered field width is known, prefer explicit-width aliases such as `uint8` / +`uint16` over raw `unsigned char` / `unsigned short`. Use plain `char` for string or byte +buffers and `signed char` when the field is a signed 8-bit counter. + +If a recovered type repeatedly walks neighbors, boundaries, or in-object offsets, prefer +small named helpers such as `GetTop`, `GetBot`, `GetNext`, `GetPrev`, or boundary getters +instead of repeating raw pointer arithmetic at each call site. + +When those helpers operate on addresses or byte offsets, prefer `intptr_t` / `uintptr_t` +or explicit byte-pointer arithmetic instead of plain `int` address parameters. + Only create headers if it's really necessary (the struct doesn't have inlines so you can't determine in which header file it goes and it's thematically very different from the other structs that use it), otherwise put it into the one you determined to be correct. The dwarf often has duplicated inlines, clean those up according to the order in the PS2 info. -Write a TODO comment over the struct/class if you aren't 100% sure that it belongs to the correct header. +Write a TODO comment over the struct/class if you aren't 100% sure that it belongs to the correct header, and say why (ownership uncertainty, circular dependency, dwarf caveat, etc.) instead of leaving a bare marker. ## Phase 3: Add needed files to jumbo file and compile diff --git a/.gitignore b/.gitignore index 547d049c6..23fbca775 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ vc80.pdb undefined_funcs_auto.txt undefined_syms_auto.txt .splache +.com/ diff --git a/AGENTS.md b/AGENTS.md index d367fc237..c55e27cb4 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 @@ -363,39 +362,21 @@ 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. diff --git a/configure.py b/configure.py index 4a58a0646..c0dd7f61c 100755 --- a/configure.py +++ b/configure.py @@ -529,8 +529,22 @@ def MatchingFor(*versions): Object(NonMatching, "Speed/Indep/SourceLists/zTrack.cpp"), Object(NonMatching, "Speed/Indep/SourceLists/zWorld.cpp"), Object(NonMatching, "Speed/Indep/SourceLists/zWorld2.cpp"), - Object(NonMatching, "Speed/Indep/SourceLists/zOnline.cpp"), - Object(NonMatching, "Speed/Indep/SourceLists/zFeOverlay.cpp"), + Object( + NonMatching, + "Speed/Indep/SourceLists/zOnline.cpp", + section_renames=( + (".text", ".over"), + (".rela.text", ".rela.over"), + ), + ), + Object( + NonMatching, + "Speed/Indep/SourceLists/zFeOverlay.cpp", + section_renames=( + (".text", ".over"), + (".rela.text", ".rela.over"), + ), + ), ], }, { diff --git a/src/Speed/Indep/Libs/Support/Utility/UTLVector.h b/src/Speed/Indep/Libs/Support/Utility/UTLVector.h index 861aed70f..1b3085d24 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UTLVector.h +++ b/src/Speed/Indep/Libs/Support/Utility/UTLVector.h @@ -9,7 +9,7 @@ #include namespace UTL { -template class Vector { +template class Vector { public: typedef T value_type; typedef value_type *pointer; @@ -59,6 +59,14 @@ template class Vector { return mBegin + mSize; } + reference operator[](size_type idx) { + return *(mBegin + idx); + } + + const_reference operator[](size_type idx) const { + return *(mBegin + idx); + } + void push_back(value_type const &val) { if (size() >= capacity()) { reserve(GetGrowSize(size() + 1)); diff --git a/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h b/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h index 87987e57a..a1e1858c3 100644 --- a/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h +++ b/src/Speed/Indep/Libs/Support/Utility/UVectorMath.h @@ -522,7 +522,7 @@ inline void VU0_MATRIX4_mult(const UMath::Matrix4 &m1, const UMath::Matrix4 &m2, UMath::Matrix4 temp; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { - result[i][j] = m1[i][0] * m2[0][j] + m1[i][1] * m2[1][j] + m1[i][2] * m2[2][j] + m1[i][3] * m2[3][j]; + temp[i][j] = m1[i][0] * m2[0][j] + m1[i][1] * m2[1][j] + m1[i][2] * m2[2][j] + m1[i][3] * m2[3][j]; } } @@ -654,6 +654,7 @@ inline void VU0_v4unitxyz(const UMath::Vector4 &a, UMath::Vector4 &result) { VU0_v4scalexyz(a, rlen, result); } +#ifndef FENG_FETYPES_H inline float IntAsFloat(const int &i) { return *reinterpret_cast(&i); } @@ -669,5 +670,6 @@ inline float V3DistanceSquared(const UMath::Vector3 &a, const UMath::Vector3 &b) // TODO where to put these? TODO only one of them uses IntAsFloat actually static const float kFloatScaleUp = IntAsFloat(0x00800000); static const float kFloatScaleDown = 1.0f / kFloatScaleUp; +#endif #endif diff --git a/src/Speed/Indep/Libs/realcore/include/common/realcore/system.h b/src/Speed/Indep/Libs/realcore/include/common/realcore/system.h index 705091037..643a74586 100644 --- a/src/Speed/Indep/Libs/realcore/include/common/realcore/system.h +++ b/src/Speed/Indep/Libs/realcore/include/common/realcore/system.h @@ -1,6 +1,8 @@ #ifndef REALCORE_COMMON_SYSTEM_H #define REALCORE_COMMON_SYSTEM_H +extern "C" void bMemSet(void *dest, unsigned char pattern, unsigned int size); + // TODO move away namespace RealSystem { @@ -23,6 +25,7 @@ struct MUTEX { }; inline bool MUTEX_create(MUTEX *m) { + bMemSet(m, sizeof(MUTEX), 0); reinterpret_cast(m)->Create(); } diff --git a/src/Speed/Indep/SourceLists/zFEng.cpp b/src/Speed/Indep/SourceLists/zFEng.cpp index e69de29bb..d81f5892e 100644 --- a/src/Speed/Indep/SourceLists/zFEng.cpp +++ b/src/Speed/Indep/SourceLists/zFEng.cpp @@ -0,0 +1,32 @@ +#include "Speed/Indep/Src/FEng/FEButtonMap.cpp" +#include "Speed/Indep/Src/FEng/FECodeListBox.cpp" +#include "Speed/Indep/Src/FEng/FEEvent.cpp" +#include "Speed/Indep/Src/FEng/FEGroup.cpp" +#include "Speed/Indep/Src/FEng/FEJoyPad.cpp" +#include "Speed/Indep/Src/FEng/FEKeyInterp.cpp" +#include "Speed/Indep/Src/FEng/FEKeyInterpLinear.cpp" +#include "Speed/Indep/Src/FEng/FEKeyInterpNone.cpp" +#include "Speed/Indep/Src/FEng/FEKeyTrack.cpp" +#include "Speed/Indep/Src/FEng/FEList.cpp" +#include "Speed/Indep/Src/FEng/FEListBox.cpp" +#include "Speed/Indep/Src/FEng/FELocalizer.cpp" +#include "Speed/Indep/Src/FEng/FEMath.cpp" +#include "Speed/Indep/Src/FEng/FEMemStream.cpp" +#include "Speed/Indep/Src/FEng/FEMessageResponse.cpp" +#include "Speed/Indep/Src/FEng/FEMouse.cpp" +#include "Speed/Indep/Src/FEng/FEMsgTargetList.cpp" +#include "Speed/Indep/Src/FEng/FEngine.cpp" +#include "Speed/Indep/Src/FEng/FEngStandard.cpp" +#include "Speed/Indep/Src/FEng/FEObject.cpp" +#include "Speed/Indep/Src/FEng/FEPackage.cpp" +#include "Speed/Indep/Src/FEng/FEPackageList.cpp" +#include "Speed/Indep/Src/FEng/FEPackageReader.cpp" +#include "Speed/Indep/Src/FEng/FERefList.cpp" +#include "Speed/Indep/Src/FEng/FEScript.cpp" +#include "Speed/Indep/Src/FEng/FESlotPool.cpp" +#include "Speed/Indep/Src/FEng/FEString.cpp" +#include "Speed/Indep/Src/FEng/FETypeLib.cpp" +#include "Speed/Indep/Src/FEng/FETypeNode.cpp" +#include "Speed/Indep/Src/FEng/FETypes.cpp" +#include "Speed/Indep/Src/FEng/FEWideString.cpp" +#include "Speed/Indep/Src/FEng/FEMultiImage.cpp" diff --git a/src/Speed/Indep/SourceLists/zFe.cpp b/src/Speed/Indep/SourceLists/zFe.cpp index e69de29bb..0ef5eb87d 100644 --- a/src/Speed/Indep/SourceLists/zFe.cpp +++ b/src/Speed/Indep/SourceLists/zFe.cpp @@ -0,0 +1,117 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiSafehouseRegionUnlock.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMSMessage.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.cpp" + +#include "Speed/Indep/Src/Frontend/UnicodeFile.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/ControllerUnplugged.cpp" + +#include "Speed/Indep/Src/Frontend/FEJoyInput.cpp" + +#include "Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEObjects.cpp" + +#include "Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEImages.cpp" + +#include "Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEStrings.cpp" + +#include "Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEMovies.cpp" + +#include "Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEButtons.cpp" + +#include "Speed/Indep/Src/FEng/FEMath.cpp" + +#include "Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp" + +#include "Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp" + +#include "Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp" + +#include "Speed/Indep/Src/Frontend/FEManager.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.cpp" + +#include "Speed/Indep/Src/Frontend/SubTitle.cpp" + +#include "Speed/Indep/Src/Frontend/Database/uiProfileManager.cpp" + +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.cpp" + +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCardCallbacks.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.cpp" diff --git a/src/Speed/Indep/SourceLists/zFe2.cpp b/src/Speed/Indep/SourceLists/zFe2.cpp index e69de29bb..1df1f004e 100644 --- a/src/Speed/Indep/SourceLists/zFe2.cpp +++ b/src/Speed/Indep/SourceLists/zFe2.cpp @@ -0,0 +1,145 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeRadarDetector.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeHeatMeter.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeCostToState.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeReputation.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeMinimap.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeCountdown.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeGenericMessage.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FESpeedBreakerMeter.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeSpeedometer.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeTachometer.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeRaceInformation.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FePursuitBoard.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeTimeExtension.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeBustedMeter.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeTurboMeter.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeDragTachometer.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeInfractions.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiInfractions.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FeFadeScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FEpkg_MU_Keyboard.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPKg_PostRace.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSixDaysLater.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FESplashScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FEMovieScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Career/FEPkg_EngageEventDialog.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameMovieScreen.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameTutorialScreen.cpp" + +#include "Speed/Indep/Src/Frontend/Localization/Localize.cpp" + +#include "Speed/Indep/Src/Frontend/Localization/WideCharHistogram.cpp" + +#include "Speed/Indep/Src/Frontend/RaceStarter.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feDialogBox.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.cpp" + +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.cpp" + +#include "Speed/Indep/Src/Frontend/Database/RaceDB.cpp" + +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.cpp" + +#include "Speed/Indep/Src/Frontend/FERenderObject.cpp" + +#include "Speed/Indep/Src/Frontend/FEngRender.cpp" + +#include "Speed/Indep/Src/Frontend/FEPackageData.cpp" + +#include "Speed/Indep/Src/Frontend/FEPackageManager.cpp" + +#include "Speed/Indep/Src/Frontend/FEngFont.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.cpp" + +#include "Speed/Indep/Src/Frontend/FEngFrontend.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feIconScrollerMenu.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feUIWidgetMenu.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/FECustomize.cpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.cpp" + +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.cpp" + +#include "Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.cpp" diff --git a/src/Speed/Indep/SourceLists/zFeOverlay.cpp b/src/Speed/Indep/SourceLists/zFeOverlay.cpp index e69de29bb..7a8b3c92d 100644 --- a/src/Speed/Indep/SourceLists/zFeOverlay.cpp +++ b/src/Speed/Indep/SourceLists/zFeOverlay.cpp @@ -0,0 +1,17 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/FECustomize.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.cpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.cpp" diff --git a/src/Speed/Indep/Src/Animation/AnimWorldTypes.hpp b/src/Speed/Indep/Src/Animation/AnimWorldTypes.hpp index 45c84effd..76d45e525 100644 --- a/src/Speed/Indep/Src/Animation/AnimWorldTypes.hpp +++ b/src/Speed/Indep/Src/Animation/AnimWorldTypes.hpp @@ -7,6 +7,16 @@ #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +static unsigned int WAM_START_TRIGGER = bStringHash("WAM_START_TRIGGER"); +static unsigned int WAM_STOP_TRIGGER = bStringHash("WAM_STOP_TRIGGER"); +static unsigned int WAM_FIRST_FRAME = bStringHash("WAM_FIRST_FRAME"); +static unsigned int WAM_LAST_FRAME = bStringHash("WAM_LAST_FRAME"); +static unsigned int WAM_SOUND_TRIGGER_START = bStringHash("WAM_SOUND_TRIGGER_START"); +static unsigned int WAM_SOUND_TRIGGER_STOP = bStringHash("WAM_SOUND_TRIGGER_STOP"); +static unsigned int WAM_NIS_GENERIC_CONTROL_MSG = bStringHash("WAM_NIS_GENERIC_CONTROL_MSG"); +static unsigned int WAM_FWD_REV_TRACK_CONTROL_MSG = bStringHash("WAM_FWD_REV_TRACK_CONTROL_MSG"); enum eControlScenarioType { eCST_ERROR = -1, diff --git a/src/Speed/Indep/Src/Camera/ICE/ICEManager.hpp b/src/Speed/Indep/Src/Camera/ICE/ICEManager.hpp index db8ce5c52..5457ebc39 100644 --- a/src/Speed/Indep/Src/Camera/ICE/ICEManager.hpp +++ b/src/Speed/Indep/Src/Camera/ICE/ICEManager.hpp @@ -54,6 +54,7 @@ class ICEManager { void Init(); void Resolve(); ICEData *GetCameraData(unsigned int scene_hash, int camTrack); + void SetGenericCameraToPlay(const char *group_name, const char *track_name); bool IsEditorOn() { // TODO maybe negated? diff --git a/src/Speed/Indep/Src/EAXSound/EAXSOund.hpp b/src/Speed/Indep/Src/EAXSound/EAXSOund.hpp index 6aa05c883..a10defc52 100644 --- a/src/Speed/Indep/Src/EAXSound/EAXSOund.hpp +++ b/src/Speed/Indep/Src/EAXSound/EAXSOund.hpp @@ -8,10 +8,13 @@ #include "Speed/Indep/Src/EAXSound/AudioMemBase.hpp" #include "Speed/Indep/Src/EAXSound/SFX_base.hpp" #include "Speed/Indep/Src/EAXSound/STICH_Playback.h" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" #include "Speed/Indep/Src/Generated/AttribSys/Classes/audiosystem.h" #include "Speed/Indep/Src/Main/Event.h" #include "Speed/Indep/Src/Misc/Hermes.h" +class AudioSettings; + // yes that is the correct name for the file enum eSNDPAUSE_REASON { @@ -99,6 +102,8 @@ enum eSNDCTLSTATE { SNDSTATE_OFF = 0, }; +struct EAXFrontEnd; + // total size: 0xBC class EAXSound : public AudioMemBase { public: @@ -107,6 +112,8 @@ class EAXSound : public AudioMemBase { void Update(float t); + void PlayFEMusic(int nIndex); + void START_321Countdown(); SFX_Base *GetSFXBase_Object(int nID); @@ -114,10 +121,23 @@ class EAXSound : public AudioMemBase { void StartSND11(); void StopSND11(); + void StopUISoundFX(eMenuSoundTriggers trigger); + void PlayUISoundFX(eMenuSoundTriggers trigger); + void QueueNISStream(unsigned int anim_id, int camera_track_number, void (*setmstimecb)(unsigned int, int)); bool IsNISStreamQueued(); void NISFinished(); + void UpdateVolumes(AudioSettings *paudiosettings, float NewValue); + + int GetDefaultPlatformAudioMode(); + + bool AreResourceLoadsPending(); + + static void ChangeLanguage(int new_language) {} + + EAXFrontEnd *GetFrontEnd() { return m_pFESnd; } + private: int ncompiletest; // offset 0x4, size 0x4 int m_nCopAIStateParam; // offset 0x8, size 0x4 diff --git a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp index fc73ad0fc..a21cd436a 100644 --- a/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp +++ b/src/Speed/Indep/Src/Ecstasy/Ecstasy.hpp @@ -168,17 +168,19 @@ struct ePoly { unsigned char flags; // offset 0x90, size 0x1 unsigned char Flailer; // offset 0x91, size 0x1 + ePoly(); + void *operator new(size_t size) {} void operator delete(void *ptr) {} - void SetFlags(unsigned char i) {} + void SetFlags(unsigned char i) { flags = i; } - void SetFlailer(unsigned char i) {} + void SetFlailer(unsigned char i) { Flailer = i; } - unsigned char GetFlags() {} + unsigned char GetFlags() { return flags; } - unsigned char GetFlailer() {} + unsigned char GetFlailer() { return Flailer; } }; struct OnScreenRain { diff --git a/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp b/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp index 0422076ab..767f1a441 100644 --- a/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp +++ b/src/Speed/Indep/Src/Ecstasy/EcstasyData.hpp @@ -9,6 +9,8 @@ #include "Speed/Indep/bWare/Inc/bMath.hpp" #include "Speed/Indep/bWare/Inc/bWare.hpp" +class eModel; + enum EVIEW_ID { NUM_RVM_VIEWS = 1, NUM_PLAYER_VIEWS = 3, @@ -212,6 +214,7 @@ class eViewPlatInterface { static eViewPlatInfo *GimmeMyViewPlatInfo(int view_id); eVisibleState GetVisibleStateGB(const bVector3 *aabb_min, const bVector3 *aabb_max, bMatrix4 *local_world); eVisibleState GetVisibleStateSB(const bVector3 *aabb_min, const bVector3 *aabb_max, bMatrix4 *local_world); + void Render(eModel *model, bMatrix4 *local_to_world, struct eLightContext *light_context, unsigned int flags, bMatrix4 *blending_matricies); }; #endif diff --git a/src/Speed/Indep/Src/Ecstasy/eSolid.hpp b/src/Speed/Indep/Src/Ecstasy/eSolid.hpp index bcf9c7c28..cd264a3d1 100644 --- a/src/Speed/Indep/Src/Ecstasy/eSolid.hpp +++ b/src/Speed/Indep/Src/Ecstasy/eSolid.hpp @@ -52,6 +52,8 @@ struct eSolid : public eSolidPlatInterface, public bTNode { void ReplaceLightMaterial(unsigned int old_name_hash, eLightMaterial *new_light_material); ePositionMarker *GetPostionMarker(ePositionMarker *prev_marker); ePositionMarker *GetPostionMarker(unsigned int namehash); + + const char *GetName() { return Name; } }; struct eLoadedSolidStats { diff --git a/src/Speed/Indep/Src/FEng/FEAnimImage.h b/src/Speed/Indep/Src/FEng/FEAnimImage.h index 9108218e6..620c393b4 100644 --- a/src/Speed/Indep/Src/FEng/FEAnimImage.h +++ b/src/Speed/Indep/Src/FEng/FEAnimImage.h @@ -5,6 +5,30 @@ #pragma once #endif +#include "feimage.h" +struct FEAnimImageData; + +// total size: 0x60 +struct FEAnimImage : public FEImage { + inline FEAnimImage() {} + inline FEAnimImage(const FEAnimImage& Object, bool bReference) + : FEImage(Object, bReference) {} + ~FEAnimImage() override; + + inline FEAnimImageData* GetImageData(); + + FEObject* Clone(bool bReference) override; + + inline void SetTopLeft(const FEVector2& topleft, bool bRelative) { + SetTrackValue(FETrack_UpperLeft, topleft, bRelative); + Flags |= 0x400000; + } + + inline void SetBottomRight(const FEVector2& bottomright, bool bRelative) { + SetTrackValue(FETrack_LowerRight, bottomright, bRelative); + Flags |= 0x400000; + } +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEButtonMap.cpp b/src/Speed/Indep/Src/FEng/FEButtonMap.cpp index e69de29bb..474add835 100644 --- a/src/Speed/Indep/Src/FEng/FEButtonMap.cpp +++ b/src/Speed/Indep/Src/FEng/FEButtonMap.cpp @@ -0,0 +1,108 @@ +#include + +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" +#include "Speed/Indep/Src/FEng/FEGameInterface.h" + +static unsigned long PassWrapMode[5] = { 3, 1, 1, 2, 2 }; +static FEVector2 DirectionVectors[8] = { + FEVector2(0.0f, -1.0f), + FEVector2(0.707110f, -0.707110f), + FEVector2(1.0f, 0.0f), + FEVector2(0.707110f, 0.707110f), + FEVector2(0.0f, 1.0f), + FEVector2(-0.707110f, 0.707110f), + FEVector2(-1.0f, 0.0f), + FEVector2(-0.707110f, -0.707110f), +}; +static FEVector2 PassOffsets[5] = { + FEVector2(0.0f, 0.0f), + FEVector2(-640.0f, 0.0f), + FEVector2(640.0f, 0.0f), + FEVector2(0.0f, -480.0f), + FEVector2(0.0f, 480.0f), +}; + +void FEButtonMap::SetCount(unsigned long NewCount) { + if (pList) { + delete[] pList; + } + pList = nullptr; + if (NewCount != 0) { + pList = reinterpret_cast(FENG_NEW char[NewCount * sizeof(FEObject*)]); + } + Count = NewCount; +} + +void FEButtonMap::ComputeButtonLocation(FEObject* pButton, FEGameInterface* pInterface, FEVector2& Dest) { + if (!pInterface || pButton->RenderContext == 0) { + FEObjData* pData = pButton->GetObjData(); + Dest.x = pData->Pos.x; + Dest.y = pData->Pos.y; + } else { + FEMatrix4 Matrix; + if (!pInterface->GetContextTransform(pButton->RenderContext, Matrix)) { + FEObjData* pData = pButton->GetObjData(); + Dest.x = pData->Pos.x; + Dest.y = pData->Pos.y; + } else { + FEVector3 Temp; + FEMultMatrix(&Temp, &Matrix, &pButton->GetObjData()->Pos); + Dest.x = Temp.x; + Dest.y = Temp.y; + } + } +} + +FEObject* FEButtonMap::GetButtonFrom(FEObject* pButton, long Direction, FEGameInterface* pInterface, FEButtonWrapMode WrapMode) { + FEVector2 VectOrig; + FEVector2 VectFrom; + FEVector2 VectTo; + float BestScore = 1e30f; + unsigned long BestIndex = 0; + + ComputeButtonLocation(pButton, pInterface, VectOrig); + + unsigned long Pass = 0; + do { + if (Pass == 0 || (PassWrapMode[Pass] & WrapMode) != 0) { + VectFrom = VectOrig + PassOffsets[Pass]; + unsigned long i = 0; + if (i < Count) { + do { + FEObject* pObj = pList[i]; + if ((pObj->Flags & 0x4000000) == 0 && pButton != pObj) { + FEVector2 Delta; + ComputeButtonLocation(pList[i], pInterface, VectTo); + Delta = VectTo - VectFrom; + float Distance = Delta.Length(); + if (Distance >= 0.0001f) { + Delta *= 1.0f / Distance; + float Angle = Delta.Dot(DirectionVectors[Direction]); + if (Angle >= 0.0f) { + Angle = Angle * Angle; + } + float Score; + if (Angle >= 0.25f) { + Score = (1.0f - Angle) * 200.0f + Distance; + } else { + Score = 1500.0f; + } + if (Score < BestScore) { + BestScore = Score; + BestIndex = i; + } + } + } + i++; + } while (i < Count); + } + } + Pass++; + } while (Pass <= 4); + + if (BestScore < 1500.0f) { + return pList[BestIndex]; + } + return nullptr; +} diff --git a/src/Speed/Indep/Src/FEng/FEChunk.h b/src/Speed/Indep/Src/FEng/FEChunk.h index 379f91b2a..b9a8e2a24 100644 --- a/src/Speed/Indep/Src/FEng/FEChunk.h +++ b/src/Speed/Indep/Src/FEng/FEChunk.h @@ -5,6 +5,58 @@ #pragma once #endif +inline unsigned long FEngGetu32(unsigned long Val) { + return (Val >> 24) | (Val << 24) | ((Val & 0xFF00) << 8) | ((Val >> 8) & 0xFF00); +} +inline unsigned short FEngGetu16(unsigned short Val) { + return static_cast((Val >> 8) | (Val << 8)); +} + +inline float FEngGetf32(float& Val) { + unsigned long Temp = FEngGetu32(*reinterpret_cast(&Val)); + return *reinterpret_cast(&Temp); +} + +// total size: 0x4 +struct FETag { + unsigned short ID; // offset 0x0, size 0x2 + unsigned short Size; // offset 0x2, size 0x2 + + inline unsigned short GetID() { return FEngGetu16(ID); } + inline unsigned short GetSize() { return FEngGetu16(Size); } + inline unsigned char* Data() { return reinterpret_cast(this) + 4; } + inline unsigned long Getu32(unsigned long Index) { return FEngGetu32(reinterpret_cast(Data())[Index]); } + inline int Geti32(unsigned long Index) { return reinterpret_cast(Data())[Index]; } + inline unsigned short Getu16(unsigned long Index) { return reinterpret_cast(Data())[Index]; } + inline short Geti16(unsigned long Index) { return reinterpret_cast(Data())[Index]; } + inline float Getf32(unsigned long Index) { return FEngGetf32(reinterpret_cast(Data())[Index]); } + inline FETag* Next() { return reinterpret_cast(Data() + GetSize()); } +}; + +// total size: 0x8 +struct FEChunk { + unsigned long ID; // offset 0x0, size 0x4 + unsigned long Size; // offset 0x4, size 0x4 + + inline unsigned long GetID() { return FEngGetu32(ID); } + inline unsigned long GetSize() { return Size; } + inline bool IsNestedChunk() { return (ID & 0x10000) != 0; } + inline bool IsDataChunk() { return (ID & 0x10000) == 0; } + inline char* GetData() { return reinterpret_cast(this) + 8; } + inline FEChunk* GetFirstChunk() { return reinterpret_cast(reinterpret_cast(this) + 8); } + inline FEChunk* GetLastChunk() { return reinterpret_cast(reinterpret_cast(this) + FEngGetu32(Size) + 8); } + inline FEChunk* GetNext() { return GetLastChunk(); } + + inline unsigned long CountChildren() { + unsigned long count = 0; + FEChunk* pChild = GetFirstChunk(); + while (pChild < GetLastChunk()) { + count++; + pChild = pChild->GetNext(); + } + return count; + } +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FECodeListBox.cpp b/src/Speed/Indep/Src/FEng/FECodeListBox.cpp index e69de29bb..dc240509e 100644 --- a/src/Speed/Indep/Src/FEng/FECodeListBox.cpp +++ b/src/Speed/Indep/Src/FEng/FECodeListBox.cpp @@ -0,0 +1,645 @@ +#include + +#include "Speed/Indep/Src/FEng/FECodeListBox.h" +#include "Speed/Indep/Src/FEng/FEListBox.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" +#include "Speed/Indep/Src/FEng/FEGameInterface.h" + +template void CopyString(short* pDst, const T* pSrc); +template void CopyString(short* pDst, const T* pSrc, unsigned long ulMaxLength); + +static inline int GetValidIndexListBox(int lIndex, int lRange) { + if (lIndex >= 0) { + int rem = lIndex - (lIndex / lRange) * lRange; + return rem; + } + + int posIndex = -lIndex; + int rem = posIndex - (posIndex / lRange) * lRange; + if (lRange <= 1) { + return 0; + } + return lRange - rem; +} + +void (*FECodeListBox::mpDefaultCallback)(FECodeListBox*) = FECodeListBox::DefaultSelectCallback; + +FECodeListBox::FECodeListBox() + : mpobRenderer(nullptr) // + , mulNumVisibleColumns(0) // + , mulNumVisibleRows(0) // + , mulFlags(2) // + , mulNumTotalColumns(0) // + , mulNumTotalRows(0) // + , mulCurrentVirtualColumn(0) // + , mulCurrentVirtualRow(0) // + , mulTargetColumn(0) // + , mulTargetRow(0) // + , mstViewDimensions(0.0f, 0.0f) // + , mpstCells(nullptr) // + , mulNumStrings(0) // + , mulStringSize(0) // + , mulCurrentString(0) // + , mppsStringData(nullptr) // + , mpsStrings(nullptr) // + , mfCurrentAlpha(1.0f) // + , mfAlphaDelta(-0.000694f) // + , mstSelectionColor(0xFFFFFFFF) // + , mpSelectionCallback(mpDefaultCallback) // + , mpSetCellCallback(nullptr) // + , mpvCallbackData(nullptr) { + Type = FE_CodeList; +} + +FECodeListBox::FECodeListBox(const FECodeListBox& Object, bool bReference) + : FEObject(Object, bReference) // + , mpobRenderer(Object.mpobRenderer) // + , mulNumVisibleColumns(0) // + , mulNumVisibleRows(0) // + , mulFlags(0) // + , mulNumTotalColumns(Object.mulNumTotalColumns) // + , mulNumTotalRows(Object.mulNumTotalRows) // + , mulCurrentVirtualColumn(0) // + , mulCurrentVirtualRow(0) // + , mulTargetColumn(0) // + , mulTargetRow(0) // + , mstViewDimensions(Object.mstViewDimensions) // + , mpstCells(nullptr) // + , mulNumStrings(0) // + , mulStringSize(0) // + , mulCurrentString(0) // + , mppsStringData(nullptr) // + , mpsStrings(nullptr) // + , mfCurrentAlpha(1.0f) // + , mfAlphaDelta(-0.001389f) // + , mstSelectionColor(0xFFFFFFFF) // + , mpSelectionCallback(Object.mpSelectionCallback) // + , mpSetCellCallback(Object.mpSetCellCallback) // + , mpvCallbackData(Object.mpvCallbackData) { + CopyProperties(Object); +} + +FECodeListBox::~FECodeListBox() { + if (mpstCells) { + delete[] mpstCells; + } + if (mpsStrings) { + delete[] mpsStrings; + } + if (mppsStringData) { + delete[] mppsStringData; + } +} + +void FECodeListBox::CopyProperties(const FECodeListBox& Object) { + unsigned long ulNumCells; + mulFlags |= Object.mulFlags & 0xE; + mstViewDimensions = Object.mstViewDimensions; + mulNumTotalColumns = Object.mulNumTotalColumns; + mulNumTotalRows = Object.mulNumTotalRows; + Initialize(Object.mulNumVisibleColumns, Object.mulNumVisibleRows); + if (mpsStrings) { + delete[] mpsStrings; + mpsStrings = nullptr; + } + if (mppsStringData) { + delete[] mppsStringData; + mppsStringData = nullptr; + } + mulNumStrings = 0; + mulCurrentString = 0; + mulStringSize = 0; + AllocateStrings(Object.mulNumStrings, Object.mulStringSize); + ulNumCells = mulNumVisibleColumns * mulNumVisibleRows; + for (unsigned long i = 0; i < ulNumCells; i++) { + mpstCells[i].ulColor = Object.mpstCells[i].ulColor; + mpstCells[i].ulType = Object.mpstCells[i].ulType; + mpstCells[i].stScale = Object.mpstCells[i].stScale; + mpstCells[i].stResource.Handle = Object.mpstCells[i].stResource.Handle; + mpstCells[i].stResource.ResourceIndex = Object.mpstCells[i].stResource.ResourceIndex; + mpstCells[i].stResource.UserParam = Object.mpstCells[i].stResource.UserParam; + mpstCells[i].ulJustification = Object.mpstCells[i].ulJustification; + if (mpstCells[i].ulType == 2) { + short* psString = Object.mpstCells[i].u.string.pStr; + if (!psString) { + return; + } + mpstCells[i].u.string.pStr = AllocateString(); + CopyString(mpstCells[i].u.string.pStr, psString); + } + if (mpstCells[i].ulType == 1) { + mpstCells[i].SetUV() = Object.mpstCells[i].GetUV(); + } + } +} + +void FECodeListBox::Initialize(unsigned long ulNumVisCols, unsigned long ulNumVisRows) { + FEListBoxCell* pstOldCells = mpstCells; + unsigned long ulOldNumVisibleColumns = mulNumVisibleColumns; + unsigned long ulOldNumVisibleRows = mulNumVisibleRows; + mulNumVisibleColumns = ulNumVisCols; + mulNumVisibleRows = ulNumVisRows; + long ulNumCells = ulNumVisRows * ulNumVisCols; + mpstCells = FENG_NEW FEListBoxCell[ulNumCells]; + FEListBox::InitializeCell(mpstCells, mulNumVisibleRows * mulNumVisibleColumns); + SetTotalNumColumns(mulNumVisibleColumns); + SetTotalNumRows(mulNumVisibleRows); + if (mulFlags & 1) { + unsigned long ulNumColumns = ulOldNumVisibleColumns; + if (ulOldNumVisibleColumns > mulNumVisibleColumns) { + ulNumColumns = mulNumVisibleColumns; + } + unsigned long ulNumRows = ulOldNumVisibleRows; + if (ulOldNumVisibleRows > mulNumVisibleRows) { + ulNumRows = mulNumVisibleRows; + } + if (pstOldCells) { + for (unsigned long i = 0; i < ulNumRows; i++) { + FEngMemCpy(mpstCells + i * mulNumVisibleColumns, pstOldCells + i * ulOldNumVisibleColumns, mulNumVisibleColumns * sizeof(FEListBoxCell)); + for (unsigned long j = ulNumColumns; j < ulOldNumVisibleColumns; j++) { + FEListBoxCell* pOldCell = &pstOldCells[i * ulOldNumVisibleColumns + j]; + if (pOldCell->ulType == 2) { + DeallocateString(pOldCell->u.string.pStr); + } + } + } + for (unsigned long i = ulNumRows; i < ulOldNumVisibleRows; i++) { + for (unsigned long j = 0; j < ulOldNumVisibleColumns; j++) { + FEListBoxCell* pOldCell = &pstOldCells[i * ulOldNumVisibleColumns + j]; + if (pOldCell->ulType == 2) { + DeallocateString(pOldCell->u.string.pStr); + } + } + } + ulNumRows = ulOldNumVisibleRows; + if (pstOldCells) { + delete[] pstOldCells; + } + } + } + mulFlags |= 1; +} + +void FECodeListBox::FillAllCells() { + if (!mulNumTotalColumns || !mulNumTotalRows || !mulNumVisibleRows || !mulNumVisibleColumns) { + return; + } + unsigned long ulNumVisRows = mulNumVisibleRows; + int lStartColumn = mulCurrentVirtualRow; + int lRow = mulCurrentVirtualColumn; + if (ulNumVisRows > mulNumTotalRows) { + ulNumVisRows = mulNumTotalRows; + } + unsigned long ulNumVisCols = mulNumVisibleColumns; + if (ulNumVisCols > mulNumTotalColumns) { + ulNumVisCols = mulNumTotalColumns; + } + if (mpSetCellCallback) { + unsigned long i = 0; + if (i < ulNumVisRows) { + do { + int lColumn = lRow; + unsigned long j = 0; + if (j < ulNumVisCols) { + do { + mpSetCellCallback(mpvCallbackData, this, lColumn, lStartColumn); + lColumn = GetValidIndexListBox(lColumn + 1, mulNumTotalColumns); + j++; + } while (j < ulNumVisCols); + } + lStartColumn = GetValidIndexListBox(lStartColumn + 1, mulNumTotalRows); + i++; + } while (i < ulNumVisRows); + } + } else { + if (mpobRenderer) { + unsigned long i = 0; + if (i < ulNumVisRows) { + do { + int lColumn = lRow; + unsigned long j = 0; + if (j < ulNumVisCols) { + do { + mpobRenderer->SetCellData(this, lColumn, lStartColumn); + lColumn = GetValidIndexListBox(lColumn + 1, mulNumTotalColumns); + j++; + } while (j < ulNumVisCols); + } + lStartColumn = GetValidIndexListBox(lStartColumn + 1, mulNumTotalRows); + i++; + } while (i < ulNumVisRows); + } + } + } +} + +void FECodeListBox::AllocateStrings(unsigned long ulNumStrings, unsigned long ulStringSize) { + short* psOldStrings = mpsStrings; + short** ppsOldStringData = mppsStringData; + unsigned long i = 0; + unsigned long j = 0; + mulNumStrings = 0; + mulCurrentString = 0; + mulStringSize = 0; + mpsStrings = nullptr; + mppsStringData = nullptr; + if (ulNumStrings == 0 || ulStringSize == 0) { + if (i < mulNumVisibleRows) { + do { + j = 0; + i++; + if (j < mulNumVisibleColumns) { + do { + j++; + } while (j < mulNumVisibleColumns); + } + } while (i < mulNumVisibleRows); + } + } else { + mpsStrings = static_cast(FEngMalloc((ulNumStrings * ulStringSize) << 1, 0, 0)); + mppsStringData = static_cast(FEngMalloc(ulNumStrings * 4, 0, 0)); + FEngMemSet(mpsStrings, 0, ulNumStrings * (ulStringSize + ulStringSize)); + for (i = 0; i < ulNumStrings; i++) { + mppsStringData[i] = mpsStrings + i * ulStringSize; + } + mulNumStrings = ulNumStrings; + mulStringSize = ulStringSize; + if (!psOldStrings) { + goto cleanup_ptrs; + } + if (ppsOldStringData) { + i = 0; + if (i < mulNumVisibleRows) { + do { + j = 0; + if (j < mulNumVisibleColumns) { + do { + FEListBoxCell* pstCell = GetCellData(j, i); + if (pstCell->ulType == 2) { + short* psString = pstCell->u.string.pStr; + pstCell->u.string.pStr = AllocateString(); + CopyString(pstCell->u.string.pStr, psString, ulStringSize); + } + j++; + } while (j < mulNumVisibleColumns); + } + i++; + } while (i < mulNumVisibleRows); + } + } + } + if (psOldStrings) { + delete[] psOldStrings; + } +cleanup_ptrs: + if (ppsOldStringData) { + delete[] ppsOldStringData; + } +} + +FEObject* FECodeListBox::Clone(bool bReference) { + return FENG_NEW FECodeListBox(*this, bReference); +} + +void FECodeListBox::SetTotalNumColumns(unsigned long ulNumColumns) { + mulNumTotalColumns = ulNumColumns; + mulCurrentVirtualColumn = CalculateCurrentFromTarget(mulTargetColumn, ulNumColumns, mulNumVisibleColumns); +} + +void FECodeListBox::SetTotalNumRows(unsigned long ulNumRows) { + mulNumTotalRows = ulNumRows; + mulCurrentVirtualRow = CalculateCurrentFromTarget(mulTargetRow, ulNumRows, mulNumVisibleRows); +} + +bool FECodeListBox::ScrollSelection(long lNumMove, unsigned long& ulCurrentVirtual, unsigned long& ulTarget, unsigned long ulNumTotal, unsigned long ulNumVis, bool bColumn) { + if (lNumMove == 0) return false; + if (!CheckMovement(lNumMove, ulCurrentVirtual, ulTarget, ulNumTotal, ulNumVis)) return false; + if (!MakeMove(lNumMove, ulCurrentVirtual, ulTarget, ulNumTotal, ulNumVis)) return false; + + if (ulNumTotal != 0) { + if (bColumn) { + if (0 < lNumMove) { + unsigned long NumColumns = mulNumVisibleColumns; + unsigned long ulFillCell = + GetValidIndex(static_cast(mulCurrentVirtualColumn) + static_cast(NumColumns) - 1, static_cast(mulNumTotalColumns)); + NumColumns--; + if (mpSetCellCallback != nullptr) { + unsigned long r = 0; + while (r < mulNumVisibleRows) { + unsigned long c = 0; + short* psString = mpstCells[r * mulNumVisibleColumns].u.string.pStr; + while (c < NumColumns) { + unsigned long Index = r * mulNumVisibleColumns + c; + c++; + FEngMemCpy(&mpstCells[Index], &mpstCells[Index + 1], sizeof(FEListBoxCell)); + } + mpstCells[r * mulNumVisibleColumns + c].u.string.pStr = psString; + mpSetCellCallback( + mpvCallbackData, + this, + ulFillCell, + GetValidIndex(static_cast(mulCurrentVirtualRow) + static_cast(r), static_cast(mulNumTotalRows))); + r++; + } + } else if (mpobRenderer) { + unsigned long r = 0; + while (r < mulNumVisibleRows) { + unsigned long c = 0; + short* psString = mpstCells[r * mulNumVisibleColumns].u.string.pStr; + while (c < NumColumns) { + unsigned long Index = r * mulNumVisibleColumns + c; + c++; + FEngMemCpy(&mpstCells[Index], &mpstCells[Index + 1], sizeof(FEListBoxCell)); + } + mpstCells[r * mulNumVisibleColumns + c].u.string.pStr = psString; + mpobRenderer->SetCellData( + this, + ulFillCell, + GetValidIndex(static_cast(mulCurrentVirtualRow) + static_cast(r), static_cast(mulNumTotalRows))); + r++; + } + } + } else if (mpSetCellCallback != nullptr) { + unsigned long r = 0; + while (r < mulNumVisibleRows) { + unsigned long NumColumns = mulNumVisibleColumns; + long c = NumColumns; + short* psString = mpstCells[NumColumns + r * NumColumns - 1].u.string.pStr; + while (--c != 0) { + unsigned long Index = r * mulNumVisibleColumns + c; + FEngMemCpy(&mpstCells[Index], &mpstCells[Index - 1], sizeof(FEListBoxCell)); + } + mpstCells[r * mulNumVisibleColumns].u.string.pStr = psString; + mpSetCellCallback(mpvCallbackData, this, mulCurrentVirtualColumn, GetValidIndex(static_cast(mulCurrentVirtualRow) + static_cast(r), static_cast(mulNumTotalRows))); + r++; + } + } else if (mpobRenderer) { + unsigned long r = 0; + while (r < mulNumVisibleRows) { + unsigned long NumColumns = mulNumVisibleColumns; + long c = NumColumns; + short* psString = mpstCells[NumColumns + r * NumColumns - 1].u.string.pStr; + while (--c != 0) { + unsigned long Index = r * mulNumVisibleColumns + c; + FEngMemCpy(&mpstCells[Index], &mpstCells[Index - 1], sizeof(FEListBoxCell)); + } + mpstCells[r * mulNumVisibleColumns].u.string.pStr = psString; + mpobRenderer->SetCellData(this, mulCurrentVirtualColumn, GetValidIndex(static_cast(mulCurrentVirtualRow) + static_cast(r), static_cast(mulNumTotalRows))); + r++; + } + } + } else if (0 < lNumMove) { + unsigned long NumRows = mulNumVisibleRows; + unsigned long ulFillCell = + GetValidIndex(static_cast(mulCurrentVirtualRow) + static_cast(NumRows) - 1, static_cast(mulNumTotalRows)); + NumRows--; + if (mpSetCellCallback != nullptr) { + unsigned long c = 0; + while (c < mulNumVisibleColumns) { + unsigned long r = 0; + short* psString = mpstCells[c].u.string.pStr; + while (r < NumRows) { + unsigned long Index = r * mulNumVisibleColumns + c; + r++; + FEngMemCpy(&mpstCells[Index], &mpstCells[Index + mulNumVisibleColumns], sizeof(FEListBoxCell)); + } + mpstCells[r * mulNumVisibleColumns + c].u.string.pStr = psString; + mpSetCellCallback( + mpvCallbackData, + this, + GetValidIndex(static_cast(mulCurrentVirtualColumn) + static_cast(c), static_cast(mulNumTotalColumns)), + ulFillCell); + c++; + } + } else if (mpobRenderer) { + unsigned long c = 0; + while (c < mulNumVisibleColumns) { + unsigned long r = 0; + short* psString = mpstCells[c].u.string.pStr; + while (r < NumRows) { + unsigned long Index = r * mulNumVisibleColumns + c; + r++; + FEngMemCpy(&mpstCells[Index], &mpstCells[Index + mulNumVisibleColumns], sizeof(FEListBoxCell)); + } + mpstCells[r * mulNumVisibleColumns + c].u.string.pStr = psString; + mpobRenderer->SetCellData( + this, + GetValidIndex(static_cast(mulCurrentVirtualColumn) + static_cast(c), static_cast(mulNumTotalColumns)), + ulFillCell); + c++; + } + } + } else if (mpSetCellCallback != nullptr) { + unsigned long NumColumns = mulNumVisibleColumns; + unsigned long c = 0; + while (c < NumColumns) { + long r = mulNumVisibleRows - 1; + short* psString = mpstCells[r * NumColumns + c].u.string.pStr; + while (r != 0) { + unsigned long Index = r * mulNumVisibleColumns + c; + FEngMemCpy(&mpstCells[Index], &mpstCells[Index - mulNumVisibleColumns], sizeof(FEListBoxCell)); + r--; + } + mpstCells[c].u.string.pStr = psString; + mpSetCellCallback(mpvCallbackData, this, mulCurrentVirtualColumn, GetValidIndex(static_cast(mulCurrentVirtualRow), static_cast(mulNumTotalRows))); + NumColumns = mulNumVisibleColumns; + c++; + } + } else if (mpobRenderer) { + unsigned long NumColumns = mulNumVisibleColumns; + unsigned long c = 0; + while (c < NumColumns) { + long r = mulNumVisibleRows - 1; + short* psString = mpstCells[r * NumColumns + c].u.string.pStr; + while (r != 0) { + unsigned long Index = r * mulNumVisibleColumns + c; + FEngMemCpy(&mpstCells[Index], &mpstCells[Index - mulNumVisibleColumns], sizeof(FEListBoxCell)); + r--; + } + mpstCells[c].u.string.pStr = psString; + mpobRenderer->SetCellData(this, mulCurrentVirtualColumn, GetValidIndex(static_cast(mulCurrentVirtualRow), static_cast(mulNumTotalRows))); + NumColumns = mulNumVisibleColumns; + c++; + } + } + } + return true; +} + +void FECodeListBox::ScrollSelection(long lColumnNum, long lRowNum) { + ScrollSelection(lColumnNum, mulCurrentVirtualColumn, mulTargetColumn, mulNumTotalColumns, mulNumVisibleColumns, true); + ScrollSelection(lRowNum, mulCurrentVirtualRow, mulTargetRow, mulNumTotalRows, mulNumVisibleRows, false); +} + +short* FECodeListBox::AllocateString() { + short* psRet = mppsStringData[mulCurrentString++]; + *psRet = 0; + return psRet; +} + +void FECodeListBox::DeallocateString(short* psString) { + mulCurrentString--; + mppsStringData[mulCurrentString] = psString; +} + +void FECodeListBox::DefaultSelectCallback(FECodeListBox* pList) { + FEColor stColor = pList->GetSelectionColor(); + stColor.a = static_cast(pList->GetAlphaHilite() * 255.0f); + pList->SetSelectionColor(stColor); +} + +long FECodeListBox::GetRealColumn(long lColumn) const { + return GetRealValue(lColumn, mulNumTotalColumns, mulCurrentVirtualColumn, mulNumVisibleColumns); +} + +long FECodeListBox::GetRealRow(long lRow) const { + return GetRealValue(lRow, mulNumTotalRows, mulCurrentVirtualRow, mulNumVisibleRows); +} + +unsigned long FECodeListBox::CalculateCurrentFromTarget(unsigned long ulTarget, unsigned long ulNumTotal, unsigned long ulNumVisible) { + if (ulTarget >= ulNumTotal) { + ulTarget = 0; + if (ulNumTotal != 0) { + ulTarget = ulNumTotal - 1; + } + } + int lRet = static_cast(ulTarget); + if (mulFlags & 8) { + lRet = GetValidIndex(lRet - static_cast(ulNumVisible >> 1), static_cast(ulNumTotal)); + } + return static_cast(lRet); +} + +void FECodeListBox::Update(float fNumTicks) { + if (mpSelectionCallback) { + mpSelectionCallback(this); + } + float fAlpha = mfCurrentAlpha + mfAlphaDelta * fNumTicks; + mfCurrentAlpha = fAlpha; + if (fAlpha < 0.0f) { + mfCurrentAlpha = 0.0f; + mfAlphaDelta = -mfAlphaDelta; + } else if (fAlpha > 1.0f) { + mfCurrentAlpha = 1.0f; + mfAlphaDelta = -mfAlphaDelta; + } +} + +void FECodeListBox::SetCellColor(unsigned long ulStartColumn, unsigned long ulStartRow, unsigned long ulColor, unsigned long ulNumColumns, unsigned long ulNumRows) { + unsigned long endRow = ulNumRows + ulStartRow; + unsigned long i = ulStartRow; + unsigned long endColumn = ulNumColumns + ulStartColumn; + while (i < endRow) { + unsigned long j = ulStartColumn; + while (j < endColumn) { + long lCIndex = GetRealColumn(j); + long lRIndex = GetRealRow(i); + FEListBoxCell* pstCell = &mpstCells[lRIndex * mulNumVisibleColumns + lCIndex]; + pstCell->ulColor = ulColor; + j++; + } + i++; + } +} + +void FECodeListBox::SetCellScale(unsigned long ulStartColumn, unsigned long ulStartRow, const FEPoint& stScale, unsigned long ulNumColumns, unsigned long ulNumRows) { + unsigned long endRow = ulNumRows + ulStartRow; + unsigned long i = ulStartRow; + unsigned long endColumn = ulNumColumns + ulStartColumn; + while (i < endRow) { + unsigned long j = ulStartColumn; + while (j < endColumn) { + long lCIndex = GetRealColumn(j); + long lRIndex = GetRealRow(i); + FEListBoxCell* pstCell = &mpstCells[lRIndex * mulNumVisibleColumns + lCIndex]; + pstCell->stScale = stScale; + j++; + } + i++; + } +} + +void FECodeListBox::SetCellJustification(unsigned long ulStartColumn, unsigned long ulStartRow, unsigned long ulJustification, unsigned long ulNumColumns, unsigned long ulNumRows) { + unsigned long endRow = ulNumRows + ulStartRow; + unsigned long i = ulStartRow; + unsigned long endColumn = ulNumColumns + ulStartColumn; + while (i < endRow) { + unsigned long j = ulStartColumn; + while (j < endColumn) { + long lCIndex = GetRealColumn(j); + long lRIndex = GetRealRow(i); + FEListBoxCell* pstCell = &mpstCells[lRIndex * mulNumVisibleColumns + lCIndex]; + pstCell->ulJustification = ulJustification; + j++; + } + i++; + } +} + +bool FECodeListBox::CheckMovement(long lNumMove, long lCurrentVirtual, long lTarget, long lNumTotal, long lNumVis) { + if ((mulFlags & 4) && lNumTotal <= lNumVis) { + mpobRenderer->NotificationMessage(FEHashUpper("ListBound"), this, 0xFF, 0); + mpobRenderer->NotificationMessage(FEHashUpper("ListEnd"), this, 0xFF, 0); + return false; + } + if (!(mulFlags & 2)) { + goto success; + } + if (mulFlags & 4) { + if (lCurrentVirtual + lNumMove < 0) { + mpobRenderer->NotificationMessage(FEHashUpper("ListBound"), this, 0xFF, 0); + return false; + } + if (lCurrentVirtual + lNumMove < lNumTotal - lNumVis) { + goto success; + } + } else { + if (lNumMove + lTarget < 0) { + mpobRenderer->NotificationMessage(FEHashUpper("ListBound"), this, 0xFF, 0); + return false; + } + if (lNumMove + lTarget < lNumTotal) { + goto success; + } + } + mpobRenderer->NotificationMessage(FEHashUpper("ListEnd"), this, 0xFF, 0); + return false; + +success: + return true; +} + +bool FECodeListBox::MakeMove(long lNumMove, unsigned long& ulCurrentVirtual, unsigned long& ulTarget, unsigned long ulNumTotal, unsigned long ulNumVis) { + if (mulFlags & 8) { + ulCurrentVirtual = GetValidIndexListBox(static_cast(ulCurrentVirtual) + lNumMove, ulNumTotal); + ulTarget = GetValidIndexListBox(static_cast(ulTarget) + lNumMove, ulNumTotal); + } else if ((mulFlags & 6) == 6) { + ulCurrentVirtual = GetValidIndexListBox(static_cast(ulCurrentVirtual) + lNumMove, ulNumTotal); + ulTarget = ulCurrentVirtual; + } else { + unsigned long ulOldTarget = ulTarget; + ulTarget = GetValidIndexListBox(static_cast(ulOldTarget) + lNumMove, ulNumTotal); + if (lNumMove < 0) { + if (ulCurrentVirtual != ulOldTarget) { + return false; + } + ulCurrentVirtual = ulTarget; + } else { + unsigned long ulDifference; + if (ulCurrentVirtual == ulOldTarget) { + return false; + } + if (ulCurrentVirtual < ulTarget) { + ulDifference = ulTarget - ulCurrentVirtual; + } else { + ulDifference = ulTarget + ulNumTotal - ulCurrentVirtual; + } + if (ulDifference < ulNumVis) { + return false; + } + ulDifference = GetValidIndexListBox(static_cast(ulCurrentVirtual) + lNumMove, ulNumTotal); + ulCurrentVirtual = ulDifference; + } + } + return true; +} diff --git a/src/Speed/Indep/Src/FEng/FECodeListBox.h b/src/Speed/Indep/Src/FEng/FECodeListBox.h index a82182545..7a16e8314 100644 --- a/src/Speed/Indep/Src/FEng/FECodeListBox.h +++ b/src/Speed/Indep/Src/FEng/FECodeListBox.h @@ -5,6 +5,127 @@ #pragma once #endif +#include "FEObject.h" +#include "FEListBox.h" +#include "FETypes.h" +struct FEGameInterface; +struct FEPoint; + +inline int GetValidIndex(int lIndex, int lRange) { + if (lIndex >= 0) { + int rem = lIndex - (lIndex / lRange) * lRange; + return rem; + } + + int posIndex = -lIndex; + int ret = 0; + int rem = posIndex - (posIndex / lRange) * lRange; + if (lRange > 1) { + ret = lRange - rem; + } + return ret; +} + +inline int GetRealValue(int i, int lNumTotal, int lCurrentVirtual, int lNumVisible) { + int lRet; + + if (lNumTotal == 0) { + return -1; + } + + if (i >= lNumTotal) { + i = i % lNumTotal; + } + + lRet = i - lCurrentVirtual; + if (lRet < 0) { + lRet += lNumTotal; + } + + if (lRet >= 0) { + int rem = lRet - (lRet / lNumVisible) * lNumVisible; + return rem; + } + + int posIndex = -lRet; + int ret = 0; + int rem = posIndex - (posIndex / lNumVisible) * lNumVisible; + if (lNumVisible > 1) { + ret = lNumVisible - rem; + } + return ret; +} + +// total size: 0xC8 +struct FECodeListBox : public FEObject { + static void (*mpDefaultCallback)(FECodeListBox*); + + FEGameInterface* mpobRenderer; // offset 0x5C, size 0x4 + unsigned long mulNumVisibleColumns; // offset 0x60, size 0x4 + unsigned long mulNumVisibleRows; // offset 0x64, size 0x4 + unsigned long mulFlags; // offset 0x68, size 0x4 + unsigned long mulNumTotalColumns; // offset 0x6C, size 0x4 + unsigned long mulNumTotalRows; // offset 0x70, size 0x4 + unsigned long mulCurrentVirtualColumn; // offset 0x74, size 0x4 + unsigned long mulCurrentVirtualRow; // offset 0x78, size 0x4 + unsigned long mulTargetColumn; // offset 0x7C, size 0x4 + unsigned long mulTargetRow; // offset 0x80, size 0x4 + FEPoint mstViewDimensions; // offset 0x84, size 0x8 + FEListBoxCell* mpstCells; // offset 0x8C, size 0x4 + unsigned long mulNumStrings; // offset 0x90, size 0x4 + unsigned long mulStringSize; // offset 0x94, size 0x4 + unsigned long mulCurrentString; // offset 0x98, size 0x4 + short** mppsStringData; // offset 0x9C, size 0x4 + short* mpsStrings; // offset 0xA0, size 0x4 + float mfCurrentAlpha; // offset 0xA4, size 0x4 + float mfAlphaDelta; // offset 0xA8, size 0x4 + FEColor mstSelectionColor; // offset 0xAC, size 0x10 + void (*mpSelectionCallback)(FECodeListBox*); // offset 0xBC, size 0x4 + void (*mpSetCellCallback)(void*, FECodeListBox*, unsigned long, unsigned long); // offset 0xC0, size 0x4 + void* mpvCallbackData; // offset 0xC4, size 0x4 + + FECodeListBox(); + FECodeListBox(const FECodeListBox& Src, bool bReference); + ~FECodeListBox() override; + + void CopyProperties(const FECodeListBox& Src); + void Initialize(unsigned long ulNumColumns, unsigned long ulNumRows); + FEObject* Clone(bool bReference); + void FillAllCells(); + void SetTotalNumColumns(unsigned long ulNumTotalColumns); + void SetTotalNumRows(unsigned long ulNumTotalRows); + void AllocateStrings(unsigned long ulNumStrings, unsigned long ulStringSize); + void ScrollSelection(long lColumnNum, long lRowNum); + void Update(float fNumTicks); + static void DefaultSelectCallback(FECodeListBox* pList); + short* AllocateString(); + void DeallocateString(short* psString); + long GetRealColumn(long lColumn) const; + long GetRealRow(long lRow) const; + bool CheckMovement(long lTargetColumn, long lTargetRow, long lOldColumn, long lOldRow, long lFlags); + bool MakeMove(long lDirection, unsigned long& ulVirtual, unsigned long& ulTarget, unsigned long ulTotal, unsigned long ulVisible); + bool ScrollSelection(long lDirection, unsigned long& ulVirtual, unsigned long& ulTarget, unsigned long ulTotal, unsigned long ulVisible, bool bIsColumn); + unsigned long CalculateCurrentFromTarget(unsigned long ulTarget, unsigned long ulTotal, unsigned long ulVisible); + void SetCellColor(unsigned long ulColumn, unsigned long ulRow, unsigned long ulColor, unsigned long ulNumColumns, unsigned long ulNumRows); + void SetCellScale(unsigned long ulColumn, unsigned long ulRow, const FEPoint& stScale, unsigned long ulNumColumns, unsigned long ulNumRows); + void SetCellJustification(unsigned long ulColumn, unsigned long ulRow, unsigned long ulJustification, unsigned long ulNumColumns, unsigned long ulNumRows); + + inline unsigned long GetNumVisibleColumns() const { return mulNumVisibleColumns; } + inline unsigned long GetNumVisibleRows() const { return mulNumVisibleRows; } + inline unsigned long GetNumTotalColumns() const { return mulNumTotalColumns; } + inline unsigned long GetNumTotalRows() const { return mulNumTotalRows; } + inline unsigned long GetCurrentVirtualColumn() const { return mulCurrentVirtualColumn; } + inline unsigned long GetCurrentVirtualRow() const { return mulCurrentVirtualRow; } + inline FEListBoxCell* GetCellData(unsigned long ulColumn, unsigned long ulRow) { return &mpstCells[ulRow * mulNumVisibleColumns + ulColumn]; } + inline FEListBoxCell* GetRealCellData(long lColumnIndex, long lRowIndex) { return &mpstCells[GetRealRow(lRowIndex) * mulNumVisibleColumns + GetRealColumn(lColumnIndex)]; } + inline void SetSelectionCallback(void (*pCallback)(FECodeListBox*)) { mpSelectionCallback = pCallback; } + inline void SetSetCellCallback(void (*pCallback)(void*, FECodeListBox*, unsigned long, unsigned long), void* pData) { mpSetCellCallback = pCallback; mpvCallbackData = pData; } + inline const FEColor& GetSelectionColor() const { return mstSelectionColor; } + inline void SetSelectionColor(const FEColor& stColor) { mstSelectionColor = stColor; } + inline float GetAlphaHilite() const { return mfCurrentAlpha; } + inline unsigned long GetVisualSelectionColumn() { return mulCurrentVirtualColumn % mulNumVisibleColumns; } + inline unsigned long GetVisualSelectionRow() { return mulCurrentVirtualRow % mulNumVisibleRows; } +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEColoredImage.h b/src/Speed/Indep/Src/FEng/FEColoredImage.h index a7ac62864..180bfd5b4 100644 --- a/src/Speed/Indep/Src/FEng/FEColoredImage.h +++ b/src/Speed/Indep/Src/FEng/FEColoredImage.h @@ -5,6 +5,17 @@ #pragma once #endif +#include "feimage.h" +// total size: 0x60 +struct FEColoredImage : public FEImage { + inline FEColoredImage() {} + inline FEColoredImage(const FEColoredImage& Object, bool bReference) + : FEImage(Object, bReference) {} + ~FEColoredImage() override; + FEObject* Clone(bool bReference) override; + + inline void SetVertexColor(const FEColor& color, unsigned long vertexIndex, bool bRelative); +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEEvent.cpp b/src/Speed/Indep/Src/FEng/FEEvent.cpp index e69de29bb..a84b686f4 100644 --- a/src/Speed/Indep/Src/FEng/FEEvent.cpp +++ b/src/Speed/Indep/Src/FEng/FEEvent.cpp @@ -0,0 +1,34 @@ +#include "Speed/Indep/Src/FEng/FEEvent.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" +#include "types.h" + +void FEEventList::operator=(FEEventList& Src) { + SetCount(Src.GetCount()); + FEngMemCpy(pEvent, Src.pEvent, Count * sizeof(FEEvent)); +} + +void FEEventList::SetCount(long NewCount) { + if (NewCount == Count) { + return; + } + if (NewCount == 0) { + if (pEvent) { + delete pEvent; + } + pEvent = nullptr; + Count = 0; + } else { + FEEvent* pNewList = static_cast(FEngMalloc(NewCount * sizeof(FEEvent), nullptr, 0)); + if (NewCount < Count) { + FEngMemCpy(pNewList, pEvent, NewCount * sizeof(FEEvent)); + } else { + FEngMemCpy(pNewList, pEvent, Count * sizeof(FEEvent)); + FEngMemSet(reinterpret_cast(pNewList) + Count * sizeof(FEEvent), 0, (NewCount - Count) * sizeof(FEEvent)); + } + if (pEvent) { + delete[] pEvent; + } + pEvent = pNewList; + Count = NewCount; + } +} diff --git a/src/Speed/Indep/Src/FEng/FEEvent.h b/src/Speed/Indep/Src/FEng/FEEvent.h index 2a5c3215a..958f3c42a 100644 --- a/src/Speed/Indep/Src/FEng/FEEvent.h +++ b/src/Speed/Indep/Src/FEng/FEEvent.h @@ -5,11 +5,24 @@ #pragma once #endif +// total size: 0xC +struct FEEvent { + unsigned long EventID; // offset 0x0, size 0x4 + unsigned long Target; // offset 0x4, size 0x4 + unsigned long tTime; // offset 0x8, size 0x4 +}; + // total size: 0x8 class FEEventList { - private: + public: int Count; // offset 0x0, size 0x4 - struct FEEvent *pEvent; // offset 0x4, size 0x4 + FEEvent* pEvent; // offset 0x4, size 0x4 + + inline FEEventList() : Count(0), pEvent(nullptr) {} + void operator=(FEEventList& rhs); + void SetCount(long NewCount); + inline unsigned long GetCount() { return Count; } + inline FEEvent& operator[](int Index) { return pEvent[Index]; } }; #endif diff --git a/src/Speed/Indep/Src/FEng/FEGameInterface.h b/src/Speed/Indep/Src/FEng/FEGameInterface.h index 63da4eb32..6a158422d 100644 --- a/src/Speed/Indep/Src/FEng/FEGameInterface.h +++ b/src/Speed/Indep/Src/FEng/FEGameInterface.h @@ -5,6 +5,87 @@ #pragma once #endif +#include +#include "FECodeListBox.h" +struct FEPackage; +struct FEObject; +// total size: 0x8 +struct FEObjectListEntry { + FEObject *pObject; // offset 0x0 + unsigned long ulKey; // offset 0x4 +}; +struct FEMouse; +struct FEMouseInfo; +struct FEResourceRequest; +struct FEMatrix4; +enum FEng_WarningLevel { + FEng_NonWarning = 0, + FEng_SoftWarning = 1, + FEng_HardWarning = 2, +}; + +// total size: 0x4 +struct FEGameInterface { + + // vtable order must match PS2 dump (GCC 2.95 uses declaration order) + virtual unsigned char* GetPackageData(const char*, unsigned char**, bool&) = 0; // [1] + virtual bool LoadResources(FEPackage*, long, FEResourceRequest*) = 0; // [2] + virtual bool UnloadResources(FEPackage*, long, FEResourceRequest*) = 0; // [3] + virtual void PackageWasLoaded(FEPackage*) = 0; // [4] + virtual bool PackageWillUnload(FEPackage*) = 0; // [5] + virtual bool UnloadUnreferencedLibrary(FEPackage*); // [6] + virtual void NotificationMessage(unsigned long, FEObject*, unsigned long, unsigned long) = 0; // [7] + virtual void NotifySoundMessage(unsigned long, FEObject*, unsigned long, unsigned long) = 0; // [8] + virtual void BeginPackageRendering(FEPackage*) = 0; // [9] + virtual void EndPackageRendering(FEPackage*) = 0; // [10] + virtual void GenerateRenderContext(unsigned short, FEObject*) = 0; // [11] + virtual bool GetContextTransform(unsigned short, FEMatrix4&) = 0; // [12] + virtual void RenderObjectList(FEObjectListEntry* pList, unsigned long Count); // [13] + virtual void RenderObject(FEObject*) = 0; // [14] + virtual void DrawMousePointer(FEMouse&) {} // [15] + virtual void GetViewTransformation(FEMatrix4*) = 0; // [16] + virtual unsigned long GetJoyPadMask(unsigned char) = 0; // [17] + virtual void GetMouseInfo(FEMouseInfo&) = 0; // [18] + virtual bool DoesPointTouchObject(float, float, FEObject*) = 0; // [19] + virtual bool SetCellData(FECodeListBox*, unsigned long, unsigned long); // [20] + virtual void OutputWarning(const char* pString, FEng_WarningLevel) {} // [21] + virtual void DebugMessageQueued(unsigned long, FEObject*, FEPackage*, FEObject*, unsigned long) {} // [22] + virtual void DebugMessageProcessed(unsigned long, FEObject*, FEObject*, FEPackage*, unsigned long) {} // [23] + virtual void DebugMessageBeginUpdate() {} // [24] + virtual void DebugMessageEndUpdate() {} // [25] +}; + +// total size: 0xC +struct cFEngGameInterface : public FEGameInterface { + bool RenderThisPackage; // offset 0x4 + int iGameMode; // offset 0x8 + + static cFEngGameInterface* pInstance; + + cFEngGameInterface(); + ~cFEngGameInterface() override; + + bool LoadResources(FEPackage*, long, FEResourceRequest*) override; + bool UnloadResources(FEPackage*, long, FEResourceRequest*) override; + void NotificationMessage(unsigned long, FEObject*, unsigned long, unsigned long) override; + void NotifySoundMessage(unsigned long, FEObject*, unsigned long, unsigned long) override; + void GenerateRenderContext(unsigned short, FEObject*) override; + bool GetContextTransform(unsigned short, FEMatrix4&) override; + void RenderObject(FEObject*) override; + void GetViewTransformation(FEMatrix4*) override; + void BeginPackageRendering(FEPackage*) override; + void EndPackageRendering(FEPackage*) override; + void PackageWasLoaded(FEPackage*) override; + bool PackageWillUnload(FEPackage*) override; + unsigned char* GetPackageData(const char*, unsigned char**, bool&) override; + unsigned long GetJoyPadMask(unsigned char) override; + void GetMouseInfo(FEMouseInfo&) override; + bool DoesPointTouchObject(float, float, FEObject*) override; + void OutputWarning(const char*, FEng_WarningLevel) override; + bool UnloadUnreferencedLibrary(FEPackage*) override; + void RenderObjectList(FEObjectListEntry* pList, unsigned long Count) override; + bool SetCellData(FECodeListBox*, unsigned long, unsigned long) override; +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEGenericVal.h b/src/Speed/Indep/Src/FEng/FEGenericVal.h index b7edba55d..9576d5c32 100644 --- a/src/Speed/Indep/Src/FEng/FEGenericVal.h +++ b/src/Speed/Indep/Src/FEng/FEGenericVal.h @@ -5,9 +5,34 @@ #pragma once #endif +#include "FETypes.h" + // total size: 0x10 class FEGenericVal { public: + inline operator unsigned char*() const { return reinterpret_cast(const_cast(Data)); } + inline operator long*() const { return reinterpret_cast(const_cast(Data)); } + inline operator FEQuaternion*() const { return reinterpret_cast(const_cast(Data)); } + inline FEGenericVal& operator=(const FEGenericVal& Val) { + Data[0] = Val.Data[0]; Data[1] = Val.Data[1]; Data[2] = Val.Data[2]; Data[3] = Val.Data[3]; + return *this; + } + inline FEGenericVal& operator=(const FEVector3& Val) { + *reinterpret_cast(Data) = Val; + return *this; + } + inline FEGenericVal& operator=(const FEVector2& Val) { + *reinterpret_cast(Data) = Val; + return *this; + } + inline FEGenericVal& operator=(const FEQuaternion& Val) { + *reinterpret_cast(Data) = Val; + return *this; + } + inline FEGenericVal& operator=(const FEColor& Val) { + *reinterpret_cast(Data) = Val; + return *this; + } private: unsigned long Data[4]; // offset 0x0, size 0x10 }; diff --git a/src/Speed/Indep/Src/FEng/FEGroup.cpp b/src/Speed/Indep/Src/FEng/FEGroup.cpp index e69de29bb..b0aee25c4 100644 --- a/src/Speed/Indep/Src/FEng/FEGroup.cpp +++ b/src/Speed/Indep/Src/FEng/FEGroup.cpp @@ -0,0 +1,37 @@ +#include + +#include "FEGroup.h" +#include "FEngStandard.h" + +FEGroup::FEGroup(const FEGroup& Object, bool bCloneChildren, bool bReference) + : FEObject(Object, bReference) +{ + if (bCloneChildren) { + for (FEObject* pChild = static_cast(Object.Children.GetHead()); pChild; pChild = pChild->GetNext()) { + FEObject* pClone = pChild->Clone(bReference); + Children.AddNode(Children.GetTail(), pClone); + } + } +} + +FEObject* FEGroup::FindChildRecursive(unsigned long NameHash) const { + FEObject* pChild = static_cast(Children.GetHead()); + while (pChild) { + if (pChild->NameHash == NameHash) { + return pChild; + } + FEObject* pFound = nullptr; + if (pChild->Type == FE_Group) { + pFound = static_cast(pChild)->FindChildRecursive(NameHash); + } + if (pFound) { + return pFound; + } + pChild = pChild->GetNext(); + } + return nullptr; +} + +FEObject* FEGroup::Clone(bool bReference) { + return FENG_NEW FEGroup(*this, true, bReference); +} diff --git a/src/Speed/Indep/Src/FEng/FEGroup.h b/src/Speed/Indep/Src/FEng/FEGroup.h index 7343bf5dd..2ef64f019 100644 --- a/src/Speed/Indep/Src/FEng/FEGroup.h +++ b/src/Speed/Indep/Src/FEng/FEGroup.h @@ -1,10 +1,29 @@ #ifndef FENG_FEGROUP_H #define FENG_FEGROUP_H -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include "FEObject.h" + +// total size: 0x6C +struct FEGroup : public FEObject { + FEMinList Children; // offset 0x5C, size 0x10 + + inline FEGroup() : FEObject(), Children() {} + FEGroup(const FEGroup& Object, bool bCloneChildren, bool bReference); + + inline void AddObject(FEObject* pObj) { Children.AddTail(pObj); } + inline void AddObjectAfter(FEObject* pObj, FEObject* pAddAfter) { Children.AddNode(pAddAfter, pObj); } + inline void RemoveObject(FEObject* pObj) { Children.RemNode(pObj); } + + inline unsigned long GetNumChildren() const { return Children.GetNumElements(); } + inline FEObject* GetFirstChild() const { return static_cast(Children.GetHead()); } + inline FEObject* GetLastChild() const { return static_cast(Children.GetTail()); } + FEObject* FindChild(unsigned long NameHash) const; + FEObject* FindChild(const char* pName) const; + FEObject* FindChildRecursive(unsigned long NameHash) const; + FEObject* FindChildRecursive(const char* pName) const; + FEObject* Clone(bool bReference) override; +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEJoyPad.cpp b/src/Speed/Indep/Src/FEng/FEJoyPad.cpp index e69de29bb..f38192a68 100644 --- a/src/Speed/Indep/Src/FEng/FEJoyPad.cpp +++ b/src/Speed/Indep/Src/FEng/FEJoyPad.cpp @@ -0,0 +1,66 @@ +#include "FEJoyPad.h" +#include + +FEJoyPad::FEJoyPad() { + Reset(); +} + +void FEJoyPad::Reset() { + CurMask = 0; + LastMask = 0; + for (int i = 0; i < 32; i++) { + HeldCount[i] = 0; + } +} + +void FEJoyPad::Update(unsigned long NewMask, unsigned long tDelta) { + LastMask = CurMask; + CurMask = NewMask; + for (int i = 0; i < 32; i++) { + if (CurMask & (1 << i)) { + if (LastMask & (1 << i)) { + HeldCount[i] += tDelta; + } else { + HeldCount[i] = 0; + } + } + } +} + +bool FEJoyPad::WasPressed(unsigned long Mask) { + return (CurMask & Mask) == Mask && (LastMask & Mask) != Mask; +} + +bool FEJoyPad::WasHeld(unsigned long Mask) { + return (CurMask & Mask) == Mask && (LastMask & Mask) == Mask; +} + +unsigned long FEJoyPad::HeldFor(unsigned long Mask) { + unsigned long result = 0xFFFFFFFF; + for (int i = 0; i < 32; i++) { + if (Mask & (1 << i)) { + unsigned long v = HeldCount[i]; + if (v > result) { + v = result; + } + result = v; + } + } + return result; +} + +bool FEJoyPad::WasReleased(unsigned long Mask) { + return (CurMask & Mask) != Mask && (LastMask & Mask) == Mask; +} + +void FEJoyPad::DecrementHold(unsigned long Mask, unsigned long Amount) { + for (int i = 0; i < 32; i++) { + if (Mask & (1 << i)) { + if (HeldCount[i] > Amount) { + HeldCount[i] -= Amount; + } else { + HeldCount[i] = 0; + } + } + } +} diff --git a/src/Speed/Indep/Src/FEng/FEJoyPad.h b/src/Speed/Indep/Src/FEng/FEJoyPad.h index 3154991f5..38069525b 100644 --- a/src/Speed/Indep/Src/FEng/FEJoyPad.h +++ b/src/Speed/Indep/Src/FEng/FEJoyPad.h @@ -5,6 +5,25 @@ #pragma once #endif +#include "types.h" +// total size: 0x88 +struct FEJoyPad { + unsigned long LastMask; // offset 0x0, size 0x4 + unsigned long CurMask; // offset 0x4, size 0x4 + unsigned long HeldCount[32]; // offset 0x8, size 0x80 + + FEJoyPad(); + + void Reset(); + void Update(unsigned long NewMask, unsigned long tDelta); + bool WasPressed(unsigned long Mask); + bool WasHeld(unsigned long Mask); + unsigned long HeldFor(unsigned long Mask); + bool WasReleased(unsigned long Mask); + void DecrementHold(unsigned long Mask, unsigned long Amount); + + inline bool WasActive() const { return (LastMask | CurMask) != 0; } +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEKeyInterp.cpp b/src/Speed/Indep/Src/FEng/FEKeyInterp.cpp index e69de29bb..4c6e4a96d 100644 --- a/src/Speed/Indep/Src/FEng/FEKeyInterp.cpp +++ b/src/Speed/Indep/Src/FEng/FEKeyInterp.cpp @@ -0,0 +1,61 @@ +#include "Speed/Indep/Src/FEng/FEScript.h" + +struct FEObject; + +void FEInterpLinear(FEScript* pScript, unsigned char TrackNum, long tTime, void* pOutData); +void FEInterpNone(FEScript* pScript, unsigned char TrackNum, long tTime, void* pOutData); + +void FEKeyInterp(FEScript* pScript, unsigned char TrackNum, long tTime, FEObject* pOutObj) { + FEKeyTrack* pTrack = pScript->pTracks + TrackNum; + + switch (pTrack->InterpType) { + case 0: + FEInterpNone(pScript, TrackNum, tTime, pOutObj->pData); + break; + case 1: + case 3: + FEInterpLinear(pScript, TrackNum, tTime, pOutObj->pData); + break; + default: + break; + } +} + +void FEInterpLinear(FEKeyTrack* pTrack, long tTime, void* pOutData); +void FEInterpNone(FEKeyTrack* pTrack, long tTime, void* pOutData); + +void FEKeyInterp(FEKeyTrack* pTrack, long tTime, void* pOutData) { + switch (pTrack->InterpType) { + case 0: + FEInterpNone(pTrack, tTime, pOutData); + break; + case 1: + case 3: + FEInterpLinear(pTrack, tTime, pOutData); + break; + default: + break; + } +} + +void FEKeyInterpFast(FEKeyTrack* pTrack, long tTime, void* pOutData) { + if (pTrack->InterpAction & 0x80) { + return; + } + + switch (pTrack->InterpType) { + case 0: + FEInterpNone(pTrack, tTime, pOutData); + break; + case 1: + case 3: + FEInterpLinear(pTrack, tTime, pOutData); + break; + default: + break; + } + + if (pTrack->DeltaKeys.GetNumElements() == 0) { + pTrack->InterpAction |= 0x80; + } +} diff --git a/src/Speed/Indep/Src/FEng/FEKeyInterpLinear.cpp b/src/Speed/Indep/Src/FEng/FEKeyInterpLinear.cpp index e69de29bb..207617cb9 100644 --- a/src/Speed/Indep/Src/FEng/FEKeyInterpLinear.cpp +++ b/src/Speed/Indep/Src/FEng/FEKeyInterpLinear.cpp @@ -0,0 +1,253 @@ +#include "Speed/Indep/Src/FEng/FEScript.h" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +void FEInterpLinear(FEKeyTrack* pTrack, long tTime, void* pOutData); + +void FEInterpLinear(FEScript* pScript, unsigned char TrackNum, long tTime, void* pOutData) { + FEKeyTrack* pTrack = pScript->pTracks + TrackNum; + char* pData = reinterpret_cast(pOutData); + + FEInterpLinear(pTrack, tTime, pData + *(reinterpret_cast(pTrack) + 7) * 4); +} + +void FELerpInteger(long n1, long n2, float t, long* pOffset, long* pDest) { + *pDest = *pOffset + n1 + static_cast(static_cast(n2 - n1) * t + 0.5f); +} + +void FELerpFloat(float n1, float n2, float t, float* pOffset, float* pDest) { + *pDest = *pOffset + n1 + (n2 - n1) * t; +} + +void FELerpVector2(FEVector2& v1, FEVector2& v2, float t, FEVector2* pOffset, FEVector2* pDest) { + pDest->x = pOffset->x + v1.x + (v2.x - v1.x) * t; + pDest->y = pOffset->y + v1.y + (v2.y - v1.y) * t; +} + +void FELerpVector3(FEVector3& v1, FEVector3& v2, float t, FEVector3* pOffset, FEVector3* pDest) { + pDest->x = pOffset->x + v1.x + (v2.x - v1.x) * t; + pDest->y = pOffset->y + v1.y + (v2.y - v1.y) * t; + pDest->z = pOffset->z + v1.z + (v2.z - v1.z) * t; +} + +void FELerpQuaternion(FEQuaternion& q1, FEQuaternion& q2, float t, FEQuaternion* pOffset, FEQuaternion* pDest) { + FEQuaternion q = q2; + float Dot = QuaternionDot(q1, q); + + if (Dot < 0.0f) { + q.x = -q.x; + q.y = -q.y; + q.z = -q.z; + q.w = -q.w; + Dot = -Dot; + } + + if (Dot < 0.999f) { + float Angle = FEngACos(Dot); + float SinA = FEngSin(Angle); + float SinAT = FEngSin(Angle * t); + float SinAInvT = FEngSin(Angle * (1.0f - t)); + FEQuaternion temp1 = operator*(q1, SinAInvT); + FEQuaternion temp2 = operator*(q, SinAT); + FEQuaternion r = operator+(temp1, temp2); + q = operator*(r, 1.0f / SinA); + } else { + FEQuaternion r = operator+(q1, operator*(operator-(q, q1), t)); + NormalizeQuaternion(r); + q = r; + } + + FEQuaternion qRet = *pOffset * q; + *pDest = qRet; +} + +void FELerpColor(FEColor& c1, FEColor& c2, float t, FEColor* pOffset, FEColor* pDest) { + pDest->b = pOffset->b + c1.b + static_cast(static_cast(c2.b - c1.b) * t + 0.5f); + pDest->g = pOffset->g + c1.g + static_cast(static_cast(c2.g - c1.g) * t + 0.5f); + pDest->r = pOffset->r + c1.r + static_cast(static_cast(c2.r - c1.r) * t + 0.5f); + pDest->a = pOffset->a + c1.a + static_cast(static_cast(c2.a - c1.a) * t + 0.5f); +} + +bool Close(float x, float y, float epsilon) { + bool result = false; + if (x + epsilon >= y) { + result = x - epsilon <= y; + } + return result; +} + +bool Close(long x, long y, long epsilon) { + return x + epsilon >= y && x - epsilon <= y; +} + +void FEInterpLinear(FEKeyTrack* pTrack, long tTime, void* pOutDataPtr) { + float t = 0.0f; + FEKeyNode* pKey = nullptr; + FEKeyNode* pPrevKey = nullptr; + FEKeyNode* pBaseKey = pTrack->GetBaseKey(); + unsigned char* pBaseValue = *pBaseKey->GetKeyData(); + + if (pTrack->DeltaKeys.GetNumElements() != 0) { + switch (pTrack->InterpAction & 0x7f) { + case 0: { + // Standard (clamp) + pKey = pTrack->GetDeltaKeyAt(tTime); + if (!pKey) goto write_base; + pPrevKey = pKey->GetPrev(); + if (pPrevKey && tTime < pKey->tTime) { + float div = static_cast(pKey->tTime - pPrevKey->tTime); + if (div > 0.00001f) { + t = static_cast(tTime - pPrevKey->tTime) / div; + } + } else { + t = 1.0f; + } + break; + } + case 1: { + // Loop + pKey = pTrack->GetDeltaKeyAt(tTime); + if (!pKey) goto write_base; + pPrevKey = pKey; + if (pKey->tTime < tTime) { + FEKeyNode* pFirstKey = pTrack->GetFirstDeltaKey(); + float div = static_cast((pTrack->Length - pKey->tTime) + pFirstKey->tTime); + if (div > 0.00001f) { + t = static_cast(tTime - pKey->tTime) / div; + } + pKey = pFirstKey; + } else if (pKey->tTime != tTime) { + pPrevKey = pKey->GetPrev(); + if (!pPrevKey) { + pPrevKey = pTrack->GetKeyAt(pTrack->Length); + float div = static_cast((pTrack->Length - pPrevKey->tTime) + pKey->tTime); + if (div > 0.00001f) { + t = static_cast((tTime + pTrack->Length) - pPrevKey->tTime) / div; + } + } else { + float div = static_cast(pKey->tTime - pPrevKey->tTime); + if (div > 0.00001f) { + t = static_cast(tTime - pPrevKey->tTime) / div; + } + } + } + break; + } + case 2: { + // Ping-pong + if (tTime > pTrack->Length) { + tTime = pTrack->Length * 2 - tTime; + } + pKey = pTrack->GetDeltaKeyAt(tTime); + break; + } + } + } + + write_base: + if (!pKey) { + switch (pTrack->ParamType) { + case 1: + case 2: { + long* pSrc = reinterpret_cast(pBaseKey->GetKeyData()); + *reinterpret_cast(pOutDataPtr) = pSrc[0]; + break; + } + case 3: { + long* pSrc = reinterpret_cast(pBaseKey->GetKeyData()); + long val0 = pSrc[0]; + long val1 = pSrc[1]; + reinterpret_cast(pOutDataPtr)[0] = val0; + reinterpret_cast(pOutDataPtr)[1] = val1; + break; + } + case 4: { + long* pSrc = reinterpret_cast(pBaseKey->GetKeyData()); + long val0 = pSrc[0]; + long val1 = pSrc[1]; + long val2 = pSrc[2]; + reinterpret_cast(pOutDataPtr)[0] = val0; + reinterpret_cast(pOutDataPtr)[1] = val1; + reinterpret_cast(pOutDataPtr)[2] = val2; + break; + } + case 5: + case 6: { + long* pSrc = reinterpret_cast(pBaseValue); + long val0 = pSrc[0]; + long val1 = pSrc[1]; + long val2 = pSrc[2]; + long val3 = pSrc[3]; + reinterpret_cast(pOutDataPtr)[0] = val0; + reinterpret_cast(pOutDataPtr)[1] = val1; + reinterpret_cast(pOutDataPtr)[2] = val2; + reinterpret_cast(pOutDataPtr)[3] = val3; + break; + } + } + return; + } + + if (t == 0.0f || t == 1.0f) { + void* pValPtr = pKey->GetKeyData(); + if (t == 0.0f) { + pValPtr = pPrevKey->GetKeyData(); + } + switch (pTrack->ParamType) { + case 1: { + *reinterpret_cast(pOutDataPtr) = *reinterpret_cast(pBaseValue) + *reinterpret_cast(pValPtr); + break; + } + case 2: { + *reinterpret_cast(pOutDataPtr) = *reinterpret_cast(pBaseValue) + *reinterpret_cast(pValPtr); + break; + } + case 3: { + reinterpret_cast(pOutDataPtr)[0] = reinterpret_cast(pBaseValue)[0] + reinterpret_cast(pValPtr)[0]; + reinterpret_cast(pOutDataPtr)[1] = reinterpret_cast(pBaseValue)[1] + reinterpret_cast(pValPtr)[1]; + break; + } + case 4: { + reinterpret_cast(pOutDataPtr)[0] = reinterpret_cast(pBaseValue)[0] + reinterpret_cast(pValPtr)[0]; + reinterpret_cast(pOutDataPtr)[1] = reinterpret_cast(pBaseValue)[1] + reinterpret_cast(pValPtr)[1]; + reinterpret_cast(pOutDataPtr)[2] = reinterpret_cast(pBaseValue)[2] + reinterpret_cast(pValPtr)[2]; + break; + } + case 5: { + FEQuaternion* pBaseQuat = reinterpret_cast(pBaseValue); + FEQuaternion* pKeyQuat = reinterpret_cast(pValPtr); + FEQuaternion* pDestQuat = reinterpret_cast(pOutDataPtr); + *pDestQuat = *pBaseQuat * *pKeyQuat; + break; + } + case 6: { + reinterpret_cast(pOutDataPtr)[2] = reinterpret_cast(pBaseValue)[2] + reinterpret_cast(pValPtr)[2]; + reinterpret_cast(pOutDataPtr)[1] = reinterpret_cast(pBaseValue)[1] + reinterpret_cast(pValPtr)[1]; + reinterpret_cast(pOutDataPtr)[0] = reinterpret_cast(pBaseValue)[0] + reinterpret_cast(pValPtr)[0]; + reinterpret_cast(pOutDataPtr)[3] = reinterpret_cast(pBaseValue)[3] + reinterpret_cast(pValPtr)[3]; + break; + } + } + } else { + switch (pTrack->ParamType) { + case 1: + FELerpInteger(*reinterpret_cast(pPrevKey->GetKeyData()), *reinterpret_cast(pKey->GetKeyData()), t, reinterpret_cast(pBaseValue), reinterpret_cast(pOutDataPtr)); + break; + case 2: + FELerpFloat(*reinterpret_cast(pPrevKey->GetKeyData()), *reinterpret_cast(pKey->GetKeyData()), t, reinterpret_cast(pBaseValue), reinterpret_cast(pOutDataPtr)); + break; + case 3: + FELerpVector2(*reinterpret_cast(pPrevKey->GetKeyData()), *reinterpret_cast(pKey->GetKeyData()), t, reinterpret_cast(pBaseValue), reinterpret_cast(pOutDataPtr)); + break; + case 4: + FELerpVector3(*reinterpret_cast(pPrevKey->GetKeyData()), *reinterpret_cast(pKey->GetKeyData()), t, reinterpret_cast(pBaseValue), reinterpret_cast(pOutDataPtr)); + break; + case 5: + FELerpQuaternion(*reinterpret_cast(pPrevKey->GetKeyData()), *reinterpret_cast(pKey->GetKeyData()), t, reinterpret_cast(pBaseValue), reinterpret_cast(pOutDataPtr)); + break; + case 6: + FELerpColor(*reinterpret_cast(pPrevKey->GetKeyData()), *reinterpret_cast(pKey->GetKeyData()), t, reinterpret_cast(pBaseValue), reinterpret_cast(pOutDataPtr)); + break; + } + } +} diff --git a/src/Speed/Indep/Src/FEng/FEKeyInterpNone.cpp b/src/Speed/Indep/Src/FEng/FEKeyInterpNone.cpp index e69de29bb..e44300cd6 100644 --- a/src/Speed/Indep/Src/FEng/FEKeyInterpNone.cpp +++ b/src/Speed/Indep/Src/FEng/FEKeyInterpNone.cpp @@ -0,0 +1,34 @@ +#include "Speed/Indep/Src/FEng/FEScript.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +void FEInterpNone(FEKeyTrack* pTrack, long tTime, void* pOutDataPtr) { + unsigned long KeySize = pTrack->ParamSize + 4; + FEKeyNode* pKey; + FEKeyNode* pPrevKey; + + if (tTime <= pTrack->Length) { + pKey = pTrack->GetKeyAt(tTime); + pPrevKey = static_cast(pKey->GetPrev()); + if (pPrevKey && pKey->tTime > tTime) { + FEngMemCpy(pOutDataPtr, &pPrevKey->Val, KeySize - 4); + } else { + FEngMemCpy(pOutDataPtr, &pKey->Val, KeySize - 4); + } + return; + } + + pKey = pTrack->GetKeyAt(pTrack->Length - tTime); + pPrevKey = static_cast(pKey->GetNext()); + if (pPrevKey && pKey->tTime < tTime) { + FEngMemCpy(pOutDataPtr, &pPrevKey->Val, KeySize - 4); + } else { + FEngMemCpy(pOutDataPtr, &pKey->Val, KeySize - 4); + } +} + +void FEInterpNone(FEScript* pScript, unsigned char TrackNum, long tTime, void* pOutData) { + FEKeyTrack* pTrack = pScript->pTracks + TrackNum; + char* pData = reinterpret_cast(pOutData); + + FEInterpNone(pTrack, tTime, pData + *(reinterpret_cast(pTrack) + 7) * 4); +} diff --git a/src/Speed/Indep/Src/FEng/FEKeyTrack.cpp b/src/Speed/Indep/Src/FEng/FEKeyTrack.cpp index e69de29bb..5c14d07ee 100644 --- a/src/Speed/Indep/Src/FEng/FEKeyTrack.cpp +++ b/src/Speed/Indep/Src/FEng/FEKeyTrack.cpp @@ -0,0 +1,83 @@ +#include "Speed/Indep/Src/FEng/FEKeyTrack.h" +#include "Speed/Indep/Src/FEng/ObjectPool.h" + +ObjectPool FEKeyNode::NodePool; + +void* FEKeyNode::operator new(unsigned int) { + FEKeyNode* pNode = NodePool.AllocSingle(); + return pNode; +} + +void FEKeyNode::operator delete(void* pNode) { + FEKeyNode* pDeleteNode = static_cast(pNode); + pDeleteNode->~FEKeyNode(); + NodePool.FreeSingleNoDestroy(pDeleteNode); +} + +FEKeyNode* FEKeyTrack::GetKeyAt(long tTime) { + if (tTime < 0) { + return GetBaseKey(); + } + FEKeyNode* pPrev = GetFirstDeltaKey(); + if (!pPrev) { + return GetBaseKey(); + } + for (;;) { + FEKeyNode* pKey = pPrev->GetNext(); + if (!pKey) { + break; + } + if (pPrev->tTime >= tTime) { + break; + } + pPrev = pKey; + } + return pPrev; +} + +FEKeyNode* FEKeyTrack::GetDeltaKeyAt(long tTime) { + FEKeyNode* pPrev = GetFirstDeltaKey(); + if (!pPrev) { + return nullptr; + } + for (;;) { + FEKeyNode* pKey = pPrev->GetNext(); + if (!pKey) { + break; + } + if (pPrev->tTime >= tTime) { + break; + } + pPrev = pKey; + } + return pPrev; +} + +void FEKeyTrack::operator=(FEKeyTrack& Src) { + FEKeyNode* pKey; + while ((pKey = static_cast(DeltaKeys.RemHead())) != nullptr) { + delete pKey; + } + + ParamType = Src.ParamType; + ParamSize = Src.ParamSize; + InterpType = Src.InterpType; + InterpAction = Src.InterpAction; + Length = Src.Length; + LongOffset = Src.LongOffset; + BaseKey.tTime = Src.BaseKey.tTime; + BaseKey.Val = Src.BaseKey.Val; + + if (Src.IsReference()) { + DeltaKeys.ReferenceList(Src.DeltaKeys.GetRefSource()); + } else { + FEKeyNode* pSrcKey = Src.GetFirstDeltaKey(); + while (pSrcKey) { + pKey = new FEKeyNode(); + pKey->tTime = pSrcKey->tTime; + pKey->Val = pSrcKey->Val; + DeltaKeys.AddTail(pKey); + pSrcKey = pSrcKey->GetNext(); + } + } +} diff --git a/src/Speed/Indep/Src/FEng/FEKeyTrack.h b/src/Speed/Indep/Src/FEng/FEKeyTrack.h index f4dd83aa2..2548e7fc2 100644 --- a/src/Speed/Indep/Src/FEng/FEKeyTrack.h +++ b/src/Speed/Indep/Src/FEng/FEKeyTrack.h @@ -7,15 +7,33 @@ #include "FEGenericVal.h" #include "FERefList.h" +#include "FEngStandard.h" + +template struct ObjectPool; // total size: 0x20 -class FEKeyNode : public FEMinNode { +struct FEKeyNode : public FEMinNode { int tTime; // offset 0xC, size 0x4 FEGenericVal Val; // offset 0x10, size 0x10 + + inline void Init() { + next = reinterpret_cast(0xABADCAFE); + prev = reinterpret_cast(0xABADCAFE); + } + inline FEKeyNode() {} + + static void* operator new(unsigned int); + static void operator delete(void* pNode); + + inline FEKeyNode* GetNext() const { return static_cast(FEMinNode::GetNext()); } + inline FEKeyNode* GetPrev() const { return static_cast(FEMinNode::GetPrev()); } + inline FEGenericVal* GetKeyData() { return &Val; } + + static ObjectPool NodePool; }; // total size: 0x38 -class FEKeyTrack { +struct FEKeyTrack { unsigned char ParamType; // offset 0x0, size 0x1 unsigned char ParamSize; // offset 0x1, size 0x1 unsigned char InterpType; // offset 0x2, size 0x1 @@ -24,6 +42,23 @@ class FEKeyTrack { int LongOffset : 8; // offset 0x4, size 0x4 FEKeyNode BaseKey; // offset 0x8, size 0x20 FERefList DeltaKeys; // offset 0x28, size 0x10 + + inline FEKeyNode* GetBaseKey() { return &BaseKey; } + inline FEKeyNode* GetFirstDeltaKey() { return static_cast(DeltaKeys.GetHead()); } + inline bool IsReference() const { return DeltaKeys.IsReference(); } + + inline FEKeyTrack() + : ParamType(0) // + , ParamSize(0) // + , InterpType(0) // + , InterpAction(0) // + , Length(0) + , LongOffset(0) { + } + + FEKeyNode* GetKeyAt(long tTime); + FEKeyNode* GetDeltaKeyAt(long tTime); + void operator=(FEKeyTrack& Src); }; #endif diff --git a/src/Speed/Indep/Src/FEng/FEList.cpp b/src/Speed/Indep/Src/FEng/FEList.cpp index e69de29bb..41cbacec7 100644 --- a/src/Speed/Indep/Src/FEng/FEList.cpp +++ b/src/Speed/Indep/Src/FEng/FEList.cpp @@ -0,0 +1,168 @@ +#include "Speed/Indep/Src/FEng/FEList.h" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +char FEUpperCase(char val) { + return static_cast(val - 'a') > 25 ? val : static_cast(val - 0x20); +} + +unsigned long FEHash(const char* String) { + unsigned long Hash = 0xFFFFFFFF; + + if (String) { + while (*String) { + Hash += Hash << 5; + Hash += *reinterpret_cast(String); + String++; + } + } + + return Hash; +} + +unsigned long FEHashUpper(const char* String) { + unsigned long Hash = 0xFFFFFFFF; + + if (String) { + while (*String) { + Hash = (Hash << 5) + Hash; + Hash += FEUpperCase(*String) & 0xFF; + String++; + } + } + + return Hash; +} + +int FEStricmp(const char* s1, const char* s2) { + int c1, c2; + do { + c1 = FEUpperCase(*s1); + s1++; + c2 = FEUpperCase(*s2); + s2++; + if (c1 == 0) { + break; + } + } while (c1 == c2); + return c1 - c2; +} + +FENode::FENode() + : name(nullptr), // + nameHash(0) { +} + +FENode::~FENode() { + if (name) { + delete[] name; + } +} + +bool FENode::SetName(const char* theName) { + bool retval = false; + int Len; + + if (name) { + delete[] name; + name = nullptr; + } + if (theName) { + Len = FEngStrLen(theName); + + name = FENG_NEW char[Len + 1]; + if (name) { + retval = true; + FEngStrCpy(name, theName); + } + } + nameHash = FEHashUpper(name); + return retval; +} + +void FEMinList::AddNode(FEMinNode* insertpoint, FEMinNode* node) { + if (!node) { + return; + } + if (insertpoint) { + node->next = insertpoint->next; + if (node->next) { + node->next->prev = node; + } + node->prev = insertpoint; + insertpoint->next = node; + } else { + node->next = head; + if (node->next) { + node->next->prev = node; + } + node->prev = nullptr; + head = node; + } + if (tail == insertpoint) { + tail = node; + } + numElements++; +} + +FEMinNode* FEMinList::RemNode(FEMinNode* node) { + if (!node) { + return nullptr; + } + if (node == head) { + head = node->next; + } + if (node == tail) { + tail = node->prev; + } + if (node->prev) { + node->prev->next = node->next; + } + if (node->next) { + node->next->prev = node->prev; + } + node->next = reinterpret_cast(0xABADCAFE); + node->prev = reinterpret_cast(0xABADCAFE); + numElements--; + return node; +} + +FEMinNode* FEMinList::RemHead() { + FEMinNode* node = head; + if (node) { + RemNode(node); + } + return node; +} + +FEMinNode* FEMinList::FindNode(unsigned long ordinalnumber) const { + unsigned long i = 0; + FEMinNode* n = GetHead(); + while (n && i != ordinalnumber) { + n = n->GetNext(); + i++; + } + return n; +} + +FENode* FEList::FindNode(const char* pName, FENode* node) const { + FENode* result = nullptr; + unsigned long hash = FEHashUpper(pName); + while (node) { + if (node->name) { + if (hash == node->nameHash && FEStricmp(node->name, pName) == 0) { + result = node; + break; + } + } else if (!pName) { + result = node; + break; + } + node = node->GetNext(); + } + return result; +} + +FENode* FEList::FindNode(const char* pName) const { + return FindNode(pName, static_cast(head)); +} diff --git a/src/Speed/Indep/Src/FEng/FEList.h b/src/Speed/Indep/Src/FEng/FEList.h index bb39123b7..c2b2e9624 100644 --- a/src/Speed/Indep/Src/FEng/FEList.h +++ b/src/Speed/Indep/Src/FEng/FEList.h @@ -1,28 +1,76 @@ -#ifndef FENG_FELIST_H -#define FENG_FELIST_H +#ifndef _FELIST +#define _FELIST -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include "types.h" // total size: 0xC -class FEMinNode { - public: - protected: - FEMinNode *next; // offset 0x0, size 0x4 - FEMinNode *prev; // offset 0x4, size 0x4 +struct FEMinNode { + friend class FERefList; + friend class FEMinList; + friend class FEngine; + friend class FESlotPool; + friend class FEMultiPool; + friend struct FEPackage; + +protected: + FEMinNode* next; // offset 0x0, size 0x4 + FEMinNode* prev; // offset 0x4, size 0x4 + +public: + inline FEMinNode() + : next(reinterpret_cast(0xABADCAFE)), // + prev(reinterpret_cast(0xABADCAFE)) { + } + virtual ~FEMinNode() {} + + inline FEMinNode* GetNext() const { return next; } + inline FEMinNode* GetPrev() const { return prev; } }; // total size: 0x10 -class FEMinList { - public: - private: +struct FEMinList { + typedef bool (*CheckFlipFunc)(FEMinNode*, FEMinNode*); + +private: unsigned int numElements; // offset 0x0, size 0x4 - protected: - FEMinNode *head; // offset 0x4, size 0x4 - FEMinNode *tail; // offset 0x8, size 0x4 + +protected: + FEMinNode* head; // offset 0x4, size 0x4 + FEMinNode* tail; // offset 0x8, size 0x4 + +public: + inline FEMinList() { + head = nullptr; + tail = nullptr; + numElements = 0; + } + virtual ~FEMinList() { Purge(); } + + inline FEMinNode* GetHead() const { return head; } + inline FEMinNode* GetTail() const { return tail; } + inline void AddHead(FEMinNode* n) { AddNode(nullptr, n); } + inline void AddTail(FEMinNode* n) { AddNode(tail, n); } + inline void Purge() { + FEMinNode* cmn = RemHead(); + while (cmn) { + delete cmn; + cmn = RemHead(); + } + } + inline bool IsListEmpty() const { return numElements == 0; } + inline unsigned long GetNumElements() const { return numElements; } + + void AddNode(FEMinNode* insertpoint, FEMinNode* node); + bool IsInList(FEMinNode* node) const; + int ElementNumber(FEMinNode* node); + FEMinNode* RemNode(FEMinNode* node); + FEMinNode* RemHead(); + FEMinNode* RemTail(); + FEMinNode* FindNode(unsigned long ordinalnumber) const; + void Swap(FEMinNode* n, FEMinNode* m); + void Sort(bool (*CheckFlip)(FEMinNode*, FEMinNode*)); }; -unsigned long FEHashUpper(const char *String); +unsigned long FEHashUpper(const char* String); #endif diff --git a/src/Speed/Indep/Src/FEng/FEListBox.cpp b/src/Speed/Indep/Src/FEng/FEListBox.cpp index e69de29bb..fd54031e0 100644 --- a/src/Speed/Indep/Src/FEng/FEListBox.cpp +++ b/src/Speed/Indep/Src/FEng/FEListBox.cpp @@ -0,0 +1,451 @@ +#include "Speed/Indep/Src/FEng/FEListBox.h" +#include "Speed/Indep/Src/FEng/FEWideString.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +unsigned long GetStringLength(const short* pString); + +FEListBox::FEListBox() + : mulFlags(4) // + , mulNumColumns(0) // + , mulNumRows(0) // + , mstViewDimensions(0.0f, 0.0f) // + , mstCurrentLocation(0.0f, 0.0f) // + , mpstColumnData(nullptr) // + , mpstRowData(nullptr) // + , mstSelectionSpeed(1.0f, 1.0f) // + , mulCurrentColumn(0) // + , mulCurrentRow(0) // + , mpstCells(nullptr) // + , mstTargetLocation(0.0f, 0.0f) // + , mstDirection(0.0f, 0.0f) // + , mfCurrentAlpha(1.0f) // + , mfAlphaDelta(-1.0f / 720.0f) { + Type = FE_List; +} + +FEListBox::~FEListBox() { + Terminate(); +} + +void FEListBox::Terminate() { + if (!(mulFlags & 1)) { + return; + } + mulFlags &= ~1; + CleanupColumns(); + CleanupRows(); + CleanupCells(); +} + +void FEListBox::SetAutoWrap(bool bStopWrap) { + if (bStopWrap) { + mulFlags &= ~4; + } else { + mulFlags |= 4; + } +} + +void FEListBox::InitializeListEntry(FEListEntryData* pstEntries, unsigned long ulNumEntries) { + FEngMemSet(pstEntries, 0, ulNumEntries * sizeof(FEListEntryData)); +} + +void FEListBox::InitializeCell(FEListBoxCell* pstCells, unsigned long ulNumCells) { + for (unsigned long i = 0; i < ulNumCells; i++) { + pstCells[i].ulColor = 0xFFFFFFFF; + pstCells[i].stScale = FEPoint(1.0f, 1.0f); + pstCells[i].ulJustification = 0; + pstCells[i].stResource.Handle = 0; + pstCells[i].stResource.UserParam = 0; + pstCells[i].stResource.ResourceIndex = 0; + pstCells[i].ulType = 0; + pstCells[i].u.rect.uv_left = 0.0f; + pstCells[i].u.rect.uv_top = 0.0f; + pstCells[i].u.rect.uv_right = 1.0f; + pstCells[i].u.rect.uv_bottom = 1.0f; + } +} + +void FEListBox::IncrementCellByColumn() { + mulCurrentColumn++; + if (mulCurrentColumn >= mulNumColumns) { + mulCurrentColumn = 0; + mulCurrentRow++; + if (mulCurrentRow >= mulNumRows) { + mulCurrentRow = 0; + } + } +} + +void FEListBox::CleanupColumns() { + if (mulNumColumns != 0) { + if (mpstColumnData) { + delete[] mpstColumnData; + } + mpstColumnData = nullptr; + mulNumColumns = 0; + } +} + +void FEListBox::CleanupRows() { + if (mulNumRows != 0) { + if (mpstRowData) { + delete[] mpstRowData; + } + mpstRowData = nullptr; + mulNumRows = 0; + } +} + +void FEListBox::CleanupCells() { + unsigned long numCells = mulNumRows * mulNumColumns; + if (numCells != 0) { + for (unsigned long i = 0; i < numCells; i++) { + if (mpstCells[i].ulType == 2 && mpstCells[i].u.string.pStr) { + delete[] mpstCells[i].u.string.pStr; + mpstCells[i].u.string.pStr = nullptr; + } + } + if (mpstCells) { + delete[] mpstCells; + } + mpstCells = nullptr; + } +} + +void FEListBox::RecalculateCummulative() { + unsigned long i; + float cumulative; + + i = 0; + FEListEntryData* pCol = mpstColumnData; + cumulative = 0.0f; + while (i < mulNumColumns) { + pCol->fCummulativeValue = cumulative; + i++; + float val = pCol->fValue; + pCol += 1; + cumulative = cumulative + val; + } + + i = 0; + FEListEntryData* pRow = mpstRowData; + cumulative = 0.0f; + while (i < mulNumRows) { + pRow->fCummulativeValue = cumulative; + i++; + float val = pRow->fValue; + pRow += 1; + cumulative = cumulative + val; + } +} + +void FEListBox::CompleteScroll() { + mstCurrentLocation = mstTargetLocation; + mulFlags &= ~0x62; + if (mulCurrentColumn == 0) { + mulFlags &= ~0x08; + mstCurrentLocation.h = 0.0f; + } + if (mulCurrentRow == 0) { + mulFlags &= ~0x10; + mstCurrentLocation.v = 0.0f; + } +} + +void FEListBox::SetCellType(unsigned long ulType) { + FEListBoxCell* pCell = &mpstCells[mulCurrentRow * mulNumColumns + mulCurrentColumn]; + if (pCell->ulType != ulType) { + if (pCell->ulType == 2 && pCell->u.string.pStr) { + delete[] pCell->u.string.pStr; + pCell->u.string.pStr = nullptr; + } + pCell->ulType = ulType; + } +} + +void FEListBox::SetCellString(const short* psString) { + FEListBoxCell* pCell = &mpstCells[mulCurrentRow * mulNumColumns + mulCurrentColumn]; + if (pCell->u.string.pStr) { + delete[] pCell->u.string.pStr; + pCell->u.string.pStr = nullptr; + } + if (psString) { + int len = (GetStringLength(psString) + 1) * 2; + pCell->u.string.pStr = static_cast(FEngMalloc(len, 0, 0)); + FEngMemCpy(pCell->u.string.pStr, psString, len); + } +} + +void FEListBox::SetNumColumns(unsigned long ulNumColumns) { + if (ulNumColumns == 0) { + CleanupColumns(); + CleanupCells(); + } else { + unsigned long ulNumCopy = 0; + FEListEntryData* pstNewColumns = static_cast(FEngMalloc(ulNumColumns * sizeof(FEListEntryData), 0, 0)); + if (mulNumColumns != 0) { + ulNumCopy = ulNumColumns; + if (ulNumColumns > mulNumColumns) { + ulNumCopy = mulNumColumns; + } + FEngMemCpy(pstNewColumns, mpstColumnData, ulNumCopy * sizeof(FEListEntryData)); + if (mpstColumnData) { + delete[] mpstColumnData; + } + } + InitializeListEntry(pstNewColumns + ulNumCopy, ulNumColumns - ulNumCopy); + + unsigned long ulNumCells = ulNumColumns * mulNumRows; + FEListBoxCell* pstCells = nullptr; + if (ulNumCells != 0) { + pstCells = FENG_NEW FEListBoxCell[ulNumCells]; + if (mpstCells) { + for (unsigned long c = 0; c < mulNumRows; c++) { + unsigned long dstOff = c * ulNumColumns; + unsigned long srcOff = c * mulNumColumns; + FEngMemCpy(pstCells + dstOff, mpstCells + srcOff, ulNumCopy); + InitializeCell(pstCells + dstOff + ulNumCopy, ulNumColumns - ulNumCopy); + } + if (mpstCells) { + delete[] mpstCells; + } + } else { + InitializeCell(pstCells, ulNumCells); + } + } + mulNumColumns = ulNumColumns; + mpstColumnData = pstNewColumns; + mpstCells = pstCells; + } +} + +void FEListBox::SetNumRows(unsigned long ulNumRows) { + if (ulNumRows == 0) { + CleanupRows(); + CleanupCells(); + } else { + unsigned long ulNumCopy = 0; + FEListEntryData* pstNewRows = static_cast(FEngMalloc(ulNumRows * sizeof(FEListEntryData), 0, 0)); + if (mulNumRows != 0) { + ulNumCopy = ulNumRows; + if (ulNumRows > mulNumRows) { + ulNumCopy = mulNumRows; + } + FEngMemCpy(pstNewRows, mpstRowData, ulNumCopy * sizeof(FEListEntryData)); + if (mpstRowData) { + delete[] mpstRowData; + } + } + InitializeListEntry(pstNewRows + ulNumCopy, ulNumRows - ulNumCopy); + + unsigned long ulNumCells = mulNumColumns * ulNumRows; + FEListBoxCell* pstCells = nullptr; + if (ulNumCells != 0) { + pstCells = FENG_NEW FEListBoxCell[ulNumCells]; + if (mpstCells) { + FEngMemCpy(pstCells, mpstCells, ulNumCopy * mulNumColumns); + InitializeCell(pstCells + ulNumCopy * mulNumColumns, (ulNumRows - ulNumCopy) * mulNumColumns); + if (mpstCells) { + delete[] mpstCells; + } + } else { + InitializeCell(pstCells, ulNumCells); + } + } + mulNumRows = ulNumRows; + mpstRowData = pstNewRows; + mpstCells = pstCells; + } +} + +void FEListBox::ScrollSelection(long lColumnNum, long lRowNum) { + unsigned long flags = mulFlags; + if ((flags & 0x60) == 0x60) { + return; + } + unsigned long colDisabled = flags & 0x20; + if (colDisabled) { + lColumnNum = 0; + } + if ((flags & 0x40) != 0) { + lRowNum = 0; + } + if (lColumnNum == 0) { + if (lRowNum == 0) { + return; + } + } else if (!colDisabled) { + unsigned long ulCurrentColumn = mulCurrentColumn; + unsigned long ulNewColumn = ulCurrentColumn + lColumnNum; + if (flags & 4) { + if (static_cast(ulNewColumn) >= static_cast(mulNumColumns)) { + ulNewColumn = mulNumColumns - 1; + } + if (static_cast(ulNewColumn) < 0) { + ulNewColumn = 0; + } + goto set_column; + } else { + if (static_cast(ulNewColumn) < 0) { + unsigned long numCols = mulNumColumns; + unsigned long i = numCols + ulNewColumn; + float fCummulativeValue = mpstColumnData[i].fCummulativeValue; + mstTargetLocation.h = fCummulativeValue; + mstCurrentLocation.h = fCummulativeValue; + do { + mstCurrentLocation.h = mstCurrentLocation.h + mpstColumnData[i].fValue; + unsigned long next = i + 1; + i = next - (next / numCols) * numCols; + } while (i != ulCurrentColumn); + } else { + unsigned long numCols = mulNumColumns; + if (static_cast(ulNewColumn) < static_cast(numCols)) { + goto set_column; + } + mstTargetLocation.h = mstCurrentLocation.h; + do { + long idx = ulCurrentColumn - (ulCurrentColumn / numCols) * numCols; + ulCurrentColumn = ulCurrentColumn + 1; + mstTargetLocation.h = mstTargetLocation.h + mpstColumnData[idx].fValue; + } while (ulCurrentColumn != ulNewColumn); + } + mulFlags = mulFlags | 8; + mulCurrentColumn = ulNewColumn - (ulNewColumn / mulNumColumns) * mulNumColumns; + goto end_column; + } + set_column: + mulCurrentColumn = ulNewColumn; + mstTargetLocation.h = mpstColumnData[ulNewColumn].fCummulativeValue; + end_column: + + unsigned long i = mulCurrentColumn; + float fNewWidth = 0.0f; + if (i < mulNumColumns) { + do { + long idx = i * 0xC; + i = i + 1; + fNewWidth = fNewWidth + *reinterpret_cast(reinterpret_cast(mpstColumnData) + idx); + } while (i < mulNumColumns); + } + + if ((mulFlags & 4) != 0) { + if (fNewWidth < mstViewDimensions.h) { + mstTargetLocation.h = mstTargetLocation.h - (mstViewDimensions.h - fNewWidth); + } + } else { + if (fNewWidth < mstViewDimensions.h) { + mulFlags = mulFlags | 8; + } + } + mulFlags = mulFlags | 0x20; + } + + if (lRowNum == 0 || (mulFlags & 0x40) != 0) { + goto compute_direction; + } + + { + unsigned long ulCurrentRow = mulCurrentRow; + unsigned long ulNewRow = ulCurrentRow + lRowNum; + if (mulFlags & 4) { + if (static_cast(ulNewRow) >= static_cast(mulNumRows)) { + ulNewRow = mulNumRows - 1; + } + if (static_cast(ulNewRow) < 0) { + ulNewRow = 0; + } + goto set_row; + } else { + if (static_cast(ulNewRow) < 0) { + unsigned long numRows = mulNumRows; + unsigned long i = numRows + ulNewRow; + float fCummulativeValue = mpstRowData[i].fCummulativeValue; + mstTargetLocation.v = fCummulativeValue; + mstCurrentLocation.v = fCummulativeValue; + do { + mstCurrentLocation.v = mstCurrentLocation.v + mpstRowData[i].fValue; + unsigned long next = i + 1; + i = next - (next / numRows) * numRows; + } while (i != ulCurrentRow); + } else { + unsigned long numRows = mulNumRows; + if (static_cast(ulNewRow) < static_cast(numRows)) { + goto set_row; + } + mstTargetLocation.v = mstCurrentLocation.v; + do { + long idx = ulCurrentRow - (ulCurrentRow / numRows) * numRows; + ulCurrentRow = ulCurrentRow + 1; + mstTargetLocation.v = mstTargetLocation.v + mpstRowData[idx].fValue; + } while (ulCurrentRow != ulNewRow); + } + mulFlags = mulFlags | 8; + mulCurrentRow = ulNewRow - (ulNewRow / mulNumRows) * mulNumRows; + goto end_row; + } + set_row: + mulCurrentRow = ulNewRow; + mstTargetLocation.v = mpstRowData[ulNewRow].fCummulativeValue; + end_row: + + unsigned long i = mulCurrentRow; + float fNewHeight = 0.0f; + if (i < mulNumRows) { + do { + long idx = i * 0xC; + i = i + 1; + fNewHeight = fNewHeight + *reinterpret_cast(reinterpret_cast(mpstRowData) + idx); + } while (i < mulNumRows); + } + + if ((mulFlags & 4) != 0) { + if (fNewHeight < mstViewDimensions.v) { + mstTargetLocation.v = mstTargetLocation.v - (mstViewDimensions.v - fNewHeight); + } + } else { + if (fNewHeight < mstViewDimensions.v) { + mulFlags = mulFlags | 0x10; + } + } + mulFlags = mulFlags | 0x40; + } + +compute_direction: + FEVector2& obDirection = reinterpret_cast(mstDirection); + obDirection = reinterpret_cast(mstTargetLocation) - reinterpret_cast(mstCurrentLocation); + mulFlags = mulFlags | 2; + float fLength = obDirection.Length(); + if (fLength < 0.1f) { + CompleteScroll(); + } else { + obDirection *= 1.0f / obDirection.Length(); + } +} + +void FEListBox::Update(float fNumTicks) { + mfCurrentAlpha = mfCurrentAlpha + mfAlphaDelta * fNumTicks; + if (mfCurrentAlpha < 0.0f) { + mfCurrentAlpha = 0.0f; + mfAlphaDelta = -mfAlphaDelta; + } else if (mfCurrentAlpha > 1.0f) { + mfCurrentAlpha = 1.0f; + mfAlphaDelta = -mfAlphaDelta; + } + if (mulFlags & 2) { + FEVector2 obDirection(reinterpret_cast(mstDirection)); + FEVector2 obVelocity(obDirection); + FEVector2& obSpeed = reinterpret_cast(mstSelectionSpeed); + float fDot = obDirection.Dot(obSpeed); + obVelocity *= FEngAbs(fDot) * fNumTicks; + FEVector2& obCurrentLocation = reinterpret_cast(mstCurrentLocation); + obCurrentLocation += obVelocity; + FEVector2& obTargetLocation = reinterpret_cast(mstTargetLocation); + if (obDirection.Dot(obTargetLocation) - obDirection.Dot(obCurrentLocation) < 0.0f) { + CompleteScroll(); + } + } +} + +FEListBoxCell* FEListBox::GetPCellData(unsigned long ulColumn, unsigned long ulRow) { + return &mpstCells[ulRow * mulNumColumns + ulColumn]; +} diff --git a/src/Speed/Indep/Src/FEng/FEListBox.h b/src/Speed/Indep/Src/FEng/FEListBox.h index 1f4a1a666..6ece7d596 100644 --- a/src/Speed/Indep/Src/FEng/FEListBox.h +++ b/src/Speed/Indep/Src/FEng/FEListBox.h @@ -5,6 +5,156 @@ #pragma once #endif +#include "FEObject.h" +#include "FETypes.h" +inline unsigned long ClampIndex(unsigned long val, unsigned long range) { + if (range == 0) return 0; + if (val < range) return val; + return range - 1; +} + +// total size: 0xC +struct FEListEntryData { + float fValue; // offset 0x0, size 0x4 + float fCummulativeValue; // offset 0x4, size 0x4 + unsigned long ulJustification; // offset 0x8, size 0x4 +}; + +// total size: 0xC +struct ListBoxResource { + unsigned long Handle; // offset 0x0, size 0x4 + unsigned long UserParam; // offset 0x4, size 0x4 + unsigned long ResourceIndex; // offset 0x8, size 0x4 + + inline ListBoxResource() {} +}; + +// total size: 0x30 +struct FEListBoxCell { + union _u { + struct { + float uv_left; // offset 0x0 + float uv_top; // offset 0x4 + float uv_right; // offset 0x8 + float uv_bottom; // offset 0xC + } rect; + struct { + short* pStr; // offset 0x0, size 0x4 + unsigned long Label; // offset 0x4, size 0x4 + } string; + }; + + unsigned long ulColor; // offset 0x0, size 0x4 + FEPoint stScale; // offset 0x4, size 0x8 + ListBoxResource stResource; // offset 0xC, size 0xC + unsigned long ulType; // offset 0x18, size 0x4 + unsigned long ulJustification; // offset 0x1C, size 0x4 + _u u; // offset 0x20, size 0x10 + + inline FEListBoxCell() : ulColor(0), stScale(1.0f, 1.0f) { + stResource.Handle = 0; + stResource.UserParam = 0; + stResource.ResourceIndex = 0; + ulType = 0; + u.string.pStr = nullptr; + u.string.Label = 0xFFFFFFFF; + } + + inline unsigned long GetLabelHash() const { return u.string.Label; } + inline const short* GetStringPtr() const { return u.string.pStr; } + inline const FERect& GetUV() const { return *reinterpret_cast(&u.rect); } + inline FERect& SetUV() { return *reinterpret_cast(&u.rect); } +}; + +// total size: 0xAC +struct FEListBox : public FEObject { + unsigned long mulFlags; // offset 0x5C, size 0x4 + unsigned long mulNumColumns; // offset 0x60, size 0x4 + unsigned long mulNumRows; // offset 0x64, size 0x4 + FEPoint mstViewDimensions; // offset 0x68, size 0x8 + FEPoint mstCurrentLocation; // offset 0x70, size 0x8 + FEListEntryData* mpstColumnData; // offset 0x78, size 0x4 + FEListEntryData* mpstRowData; // offset 0x7C, size 0x4 + FEPoint mstSelectionSpeed; // offset 0x80, size 0x8 + unsigned long mulCurrentColumn; // offset 0x88, size 0x4 + unsigned long mulCurrentRow; // offset 0x8C, size 0x4 + FEListBoxCell* mpstCells; // offset 0x90, size 0x4 + FEPoint mstTargetLocation; // offset 0x94, size 0x8 + FEPoint mstDirection; // offset 0x9C, size 0x8 + float mfCurrentAlpha; // offset 0xA4, size 0x4 + float mfAlphaDelta; // offset 0xA8, size 0x4 + + FEListBox(); + FEListBox(const FEListBox& Object); + ~FEListBox() override; + + void Initialize(unsigned long ulNumColumns, unsigned long ulNumRows); + void Terminate(); + void SetNumColumns(unsigned long ulNumColumns); + void SetNumRows(unsigned long ulNumRows); + void SetColumnWidth(float fWidth); + void SetRowHeight(float fHeight); + void SetCellType(unsigned long ulType); + void SetCellString(const short* psString); + void IncrementCellByRow(); + void IncrementCellByColumn(); + unsigned long GetFirstVisibleColumn() const; + unsigned long GetFirstVisibleRow() const; + unsigned long GetLastVisibleColumn() const; + unsigned long GetLastVisibleRow() const; + bool GetCellInfo(unsigned long ulColumn, unsigned long ulRow, FERect& stCellRect, FERect& stClippedCellRect, FEListBoxCell& stCellInfo, unsigned long& ulJustification) const; + void ScrollSelection(long lColumnNum, long lRowNum); + void Update(float fNumTicks); + void SetAutoWrap(bool bStopWrap); + static void InitializeListEntry(FEListEntryData* pstEntries, unsigned long ulNumEntries); + static void InitializeCell(FEListBoxCell* pstCells, unsigned long ulNumCells); + void CleanupColumns(); + void CleanupRows(); + void CleanupCells(); + void RecalculateCummulative(); + void CompleteScroll(); + + inline FEObject* Clone(bool bReference) { + FEListBox* pNew = new (0) FEListBox(*this); + return pNew; + } + + inline void SetViewDimensions(const FEPoint& stViewDimensions) { mstViewDimensions = stViewDimensions; } + inline void SetCurrentLocation(const FEPoint& stCurrentLocation) { mstCurrentLocation = stCurrentLocation; } + inline void SetSelectionSpeed(const FEPoint& stSelectionSpeed) { mstSelectionSpeed = stSelectionSpeed; } + inline void SetCurrentColumn(unsigned long ulCurrentColumn) { mulCurrentColumn = ClampIndex(ulCurrentColumn, mulNumColumns); } + inline void SetCurrentRow(unsigned long ulCurrentRow) { mulCurrentRow = ClampIndex(ulCurrentRow, mulNumRows); } + inline void SetColumnJustification(unsigned long ulJustification) { mpstColumnData[mulCurrentColumn].ulJustification = ulJustification; } + inline void SetRowJustification(unsigned long ulJustification) { mpstRowData[mulCurrentRow].ulJustification = ulJustification; } + inline void SetCellColor(const FEColor& stColor) { mpstCells[mulCurrentRow * mulNumColumns + mulCurrentColumn].ulColor = static_cast(stColor); } + inline void SetCellScale(const FEPoint& stScale) { mpstCells[mulCurrentRow * mulNumColumns + mulCurrentColumn].stScale = stScale; } + inline void SetCellResource(unsigned long ulResHandle, unsigned long ulResParam, unsigned long ulResIndex) { + FEListBoxCell* pCell = &mpstCells[mulCurrentRow * mulNumColumns + mulCurrentColumn]; + pCell->stResource.Handle = ulResHandle; + pCell->stResource.UserParam = ulResParam; + pCell->stResource.ResourceIndex = ulResIndex; + } + inline void SetCellUV(const FERect& stUV) { *reinterpret_cast(&mpstCells[mulCurrentRow * mulNumColumns + mulCurrentColumn].u.rect) = stUV; } + inline unsigned long GetNumColumns() const { return mulNumColumns; } + inline unsigned long GetNumRows() const { return mulNumRows; } + inline const FEPoint& GetViewDimensions() const { return mstViewDimensions; } + inline const FEPoint& GetCurrentLocation() const { return mstCurrentLocation; } + inline const FEListEntryData* GetColumnData(unsigned long ulColumn) const { return &mpstColumnData[ulColumn]; } + inline const FEListEntryData* GetRowData(unsigned long ulRow) const { return &mpstRowData[ulRow]; } + inline const FEPoint& GetSelectionSpeed() const { return mstSelectionSpeed; } + inline unsigned long GetCurrentColumn() const { return mulCurrentColumn; } + inline unsigned long GetCurrentRow() const { return mulCurrentRow; } + inline const FEListBoxCell* GetCellData(unsigned long ulColumn, unsigned long ulRow) const { return &mpstCells[ulRow * mulNumColumns + ulColumn]; } + inline const FEListBoxCell* GetCurrentCellData() const { return &mpstCells[mulCurrentRow * mulNumColumns + mulCurrentColumn]; } + inline const FEListEntryData* GetCurrentColumnData() const { return &mpstColumnData[mulCurrentColumn]; } + inline const FEListEntryData* GetCurrentRowData() const { return &mpstRowData[mulCurrentRow]; } + inline float GetAlphaHilite() const { return mfCurrentAlpha; } + inline bool IsCurrent(unsigned long ulColumn, unsigned long ulRow) const { return ulColumn == mulCurrentColumn && ulRow == mulCurrentRow; } + inline bool IsAutoWrap() const { return (mulFlags & 1) != 0; } + FEListBoxCell* GetPCellData(unsigned long ulColumn, unsigned long ulRow); + inline FEListEntryData* GetPColumnData(unsigned long ulColumn) { return &mpstColumnData[ulColumn]; } + inline FEListEntryData* GetPRowData(unsigned long ulRow) { return &mpstRowData[ulRow]; } +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEMath.cpp b/src/Speed/Indep/Src/FEng/FEMath.cpp index e69de29bb..2bc8684a3 100644 --- a/src/Speed/Indep/Src/FEng/FEMath.cpp +++ b/src/Speed/Indep/Src/FEng/FEMath.cpp @@ -0,0 +1,39 @@ +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +void FEMatrix4::Identify() { + m11 = 1.0f; m12 = 0.0f; m13 = 0.0f; m14 = 0.0f; + m21 = 0.0f; m22 = 1.0f; m23 = 0.0f; m24 = 0.0f; + m31 = 0.0f; m32 = 0.0f; m33 = 1.0f; m34 = 0.0f; + m41 = 0.0f; m42 = 0.0f; m43 = 0.0f; m44 = 1.0f; +} + +void FEMultMatrix(FEMatrix4* dest, const FEMatrix4* a, const FEMatrix4* b) { + FEMatrix4 t; + t.m11 = b->m11 * a->m11 + b->m12 * a->m21 + b->m13 * a->m31 + b->m14 * a->m41; + t.m12 = b->m11 * a->m12 + b->m12 * a->m22 + b->m13 * a->m32 + b->m14 * a->m42; + t.m13 = b->m11 * a->m13 + b->m12 * a->m23 + b->m13 * a->m33 + b->m14 * a->m43; + t.m14 = b->m11 * a->m14 + b->m12 * a->m24 + b->m13 * a->m34 + b->m14 * a->m44; + t.m21 = b->m21 * a->m11 + b->m22 * a->m21 + b->m23 * a->m31 + b->m24 * a->m41; + t.m22 = b->m21 * a->m12 + b->m22 * a->m22 + b->m23 * a->m32 + b->m24 * a->m42; + t.m23 = b->m21 * a->m13 + b->m22 * a->m23 + b->m23 * a->m33 + b->m24 * a->m43; + t.m24 = b->m21 * a->m14 + b->m22 * a->m24 + b->m23 * a->m34 + b->m24 * a->m44; + t.m31 = b->m31 * a->m11 + b->m32 * a->m21 + b->m33 * a->m31 + b->m34 * a->m41; + t.m32 = b->m31 * a->m12 + b->m32 * a->m22 + b->m33 * a->m32 + b->m34 * a->m42; + t.m33 = b->m31 * a->m13 + b->m32 * a->m23 + b->m33 * a->m33 + b->m34 * a->m43; + t.m34 = b->m31 * a->m14 + b->m32 * a->m24 + b->m33 * a->m34 + b->m34 * a->m44; + t.m41 = b->m41 * a->m11 + b->m42 * a->m21 + b->m43 * a->m31 + b->m44 * a->m41; + t.m42 = b->m41 * a->m12 + b->m42 * a->m22 + b->m43 * a->m32 + b->m44 * a->m42; + t.m43 = b->m41 * a->m13 + b->m42 * a->m23 + b->m43 * a->m33 + b->m44 * a->m43; + t.m44 = b->m41 * a->m14 + b->m42 * a->m24 + b->m43 * a->m34 + b->m44 * a->m44; + FEngMemCpy(dest, &t, sizeof(FEMatrix4)); +} + +void FEMultMatrix(FEVector3* dest, const FEMatrix4* m, const FEVector3* v) { + float x = m->m11 * v->x + m->m21 * v->y + m->m31 * v->z + m->m41; + float y = m->m12 * v->x + m->m22 * v->y + m->m32 * v->z + m->m42; + float z = m->m13 * v->x + m->m23 * v->y + m->m33 * v->z + m->m43; + dest->z = z; + dest->x = x; + dest->y = y; +} diff --git a/src/Speed/Indep/Src/FEng/FEMath.h b/src/Speed/Indep/Src/FEng/FEMath.h index 1c696caf7..719d6bc85 100644 --- a/src/Speed/Indep/Src/FEng/FEMath.h +++ b/src/Speed/Indep/Src/FEng/FEMath.h @@ -5,6 +5,8 @@ #pragma once #endif - +inline float RAD2DEG(float a) { + return a * 57.295776f; +} #endif diff --git a/src/Speed/Indep/Src/FEng/FEMessageResponse.cpp b/src/Speed/Indep/Src/FEng/FEMessageResponse.cpp index e69de29bb..f16a4f6d3 100644 --- a/src/Speed/Indep/Src/FEng/FEMessageResponse.cpp +++ b/src/Speed/Indep/Src/FEng/FEMessageResponse.cpp @@ -0,0 +1,123 @@ +#include "FEMessageResponse.h" +#include "FESlotPool.h" +#include "FEngStandard.h" +#include "ObjectPool.h" + +ObjectPool FEMessageResponse::NodePool; + +void* FEMessageResponse::operator new(unsigned int) { + FEMessageResponse* pNode = NodePool.AllocSingle(); + pNode->Init(); + return pNode; +} + +void FEMessageResponse::operator delete(void* pNode) { + FEMessageResponse* pDeleteNode = static_cast(pNode); + pDeleteNode->~FEMessageResponse(); + NodePool.FreeSingleNoDestroy(pDeleteNode); +} + +FEResponse::~FEResponse() { + ReleaseParam(); +} + +FEResponse& FEResponse::operator=(FEResponse& rhs) { + ReleaseParam(); + unsigned long id = rhs.ResponseID; + ResponseID = id; + if (FEResponse::HasString(id)) { + SetParam(reinterpret_cast(rhs.ResponseParam)); + } else { + ResponseParam = rhs.ResponseParam; + } + ResponseTarget = rhs.ResponseTarget; + return *this; +} + +void FEResponse::SetParam(const char* pString) { + ReleaseParam(); + if (pString) { + unsigned long Len = FEngStrLen(pString); + char* pPathCopy = FENG_NEW char[Len + 1]; + FEngStrCpy(pPathCopy, pString); + ResponseParam = reinterpret_cast(pPathCopy); + } +} + +void FEResponse::ReleaseParam() { + if (HasString() && ResponseParam) { + delete[] reinterpret_cast(ResponseParam); + } + ResponseParam = 0; +} + +FEMessageResponse::~FEMessageResponse() { + PurgeResponses(); +} + +void FEMessageResponse::PurgeResponses() { + delete[] pResponseList; + pResponseList = nullptr; + Count = 0; +} + +void FEMessageResponse::SetCount(unsigned long NewCount) { + if (NewCount != Count) { + if (NewCount == 0) { + PurgeResponses(); + } else { + FEResponse* pNew = new FEResponse[NewCount]; + unsigned long copyCount = Count; + if (copyCount > NewCount) { + copyCount = NewCount; + } + unsigned long i = 0; + while (i < copyCount) { + pNew[i] = pResponseList[i]; + i++; + } + delete[] pResponseList; + pResponseList = pNew; + Count = NewCount; + } + } +} + +unsigned long FEMessageResponse::FindResponse(unsigned long ResponseID) const { + for (unsigned long i = 0; i < Count; i++) { + if (pResponseList[i].ResponseID == ResponseID) { + return i; + } + } + return 0xFFFFFFFF; +} + +unsigned long FEMessageResponse::FindConditionBranchTarget(unsigned long Index) const { + if (Index == Count - 1) { + return Count; + } + unsigned long Nest = 1; + goto body; + do { + if (Nest == 0) + break; +body: + Index++; + unsigned long id = pResponseList[Index].ResponseID; + switch (id) { + case 0x300: + case 0x301: + Nest++; + break; + case 0x500: + if (Nest == 1) { + Nest = 0; + } + break; + case 0x501: + Nest--; + break; + } + } while (Index < Count); + return Index; +} diff --git a/src/Speed/Indep/Src/FEng/FEMessageResponse.h b/src/Speed/Indep/Src/FEng/FEMessageResponse.h index 683257fc2..57fce0887 100644 --- a/src/Speed/Indep/Src/FEng/FEMessageResponse.h +++ b/src/Speed/Indep/Src/FEng/FEMessageResponse.h @@ -5,6 +5,68 @@ #pragma once #endif +#include "FEList.h" +#include "FEngStandard.h" +template struct ObjectPool; + +// total size: 0xC +struct FEResponse { + unsigned long ResponseID; // offset 0x0, size 0x4 + unsigned long ResponseParam; // offset 0x4, size 0x4 + unsigned long ResponseTarget; // offset 0x8, size 0x4 + + inline FEResponse() : ResponseID(0), ResponseParam(0), ResponseTarget(0) {} + + static inline void* operator new[](unsigned int size) { + return FEngMalloc(size, nullptr, 0); + } + + static inline bool HasString(unsigned long ResponseID) { + return (ResponseID - 0x200u < 5) && (ResponseID != 0x203); + } + + inline bool HasString() const { return HasString(ResponseID); } + + inline void SetID(unsigned long ID) { ResponseID = ID; } + inline void SetParam(unsigned long Value) { ResponseParam = Value; } + + ~FEResponse(); + FEResponse& operator=(FEResponse& rhs); + void SetParam(const char* pString); + void ReleaseParam(); +}; + +// total size: 0x18 +struct FEMessageResponse : public FEMinNode { + unsigned long MsgID; // offset 0xC, size 0x4 + unsigned long Count; // offset 0x10, size 0x4 + FEResponse* pResponseList; // offset 0x14, size 0x4 + + inline void Init() { + MsgID = 0; + Count = 0; + pResponseList = nullptr; + } + inline FEMessageResponse() : MsgID(0), Count(0), pResponseList(nullptr) {} + ~FEMessageResponse() override; + + static void* operator new(unsigned int); + static void operator delete(void* pNode); + + void PurgeResponses(); + void SetCount(unsigned long NewCount); + unsigned long FindResponse(unsigned long CommandID) const; + unsigned long FindConditionBranchTarget(unsigned long Index) const; + + inline unsigned long GetMsgID() const { return MsgID; } + inline void SetMsgID(unsigned long NewID) { MsgID = NewID; } + inline unsigned long GetCount() const { return Count; } + inline FEResponse* GetResponse(int Index) const { return &pResponseList[Index]; } + inline FEMessageResponse* GetNext() { return static_cast(FEMinNode::GetNext()); } + inline FEMessageResponse* GetPrev() { return static_cast(FEMinNode::GetPrev()); } + + static ObjectPool NodePool; +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEMouse.cpp b/src/Speed/Indep/Src/FEng/FEMouse.cpp index e69de29bb..bc7997ff1 100644 --- a/src/Speed/Indep/Src/FEng/FEMouse.cpp +++ b/src/Speed/Indep/Src/FEng/FEMouse.cpp @@ -0,0 +1,44 @@ +#include "FEMouse.h" + +FEMouse::FEMouse() { + Reset(); +} + +void FEMouse::Reset() { + WheelDelta = 0; + YPos = 0; + XPos = 0; + CurMask = 0; + LastMask = 0; + HeldCount[2] = 0; + HeldCount[1] = 0; + HeldCount[0] = 0; + bDragging = false; + bMoved = false; +} + +void FEMouse::Update(FEMouseInfo& Info, unsigned long tDelta) { + bool moved = false; + if (Info.XPos != XPos || Info.YPos != YPos) { + moved = true; + } + bMoved = moved; + XPos = Info.XPos; + YPos = Info.YPos; + WheelDelta = Info.WheelDelta; + LastMask = CurMask; + CurMask = Info.ButtonMask; + for (int i = 0; i < 3; i++) { + if (CurMask & (1 << i)) { + if (LastMask & (1 << i)) { + HeldCount[i] += tDelta; + } else { + HeldCount[i] = 0; + } + } + } +} + +bool FEMouse::IsDown(unsigned short Mask) { + return (CurMask & Mask) == Mask; +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/FEng/FEMouse.h b/src/Speed/Indep/Src/FEng/FEMouse.h index 9267dec76..860d3d2c6 100644 --- a/src/Speed/Indep/Src/FEng/FEMouse.h +++ b/src/Speed/Indep/Src/FEng/FEMouse.h @@ -5,6 +5,41 @@ #pragma once #endif +#include "types.h" +// total size: 0x8 +struct FEMouseInfo { + short XPos; // offset 0x0, size 0x2 + short YPos; // offset 0x2, size 0x2 + short WheelDelta; // offset 0x4, size 0x2 + unsigned short ButtonMask; // offset 0x6, size 0x2 +}; + +// total size: 0x24 +struct FEMouse { + int XPos; // offset 0x0, size 0x4 + int YPos; // offset 0x4, size 0x4 + int WheelDelta; // offset 0x8, size 0x4 + unsigned short LastMask; // offset 0xC, size 0x2 + unsigned short CurMask; // offset 0xE, size 0x2 + unsigned long HeldCount[3]; // offset 0x10, size 0xC + bool bDragging; // offset 0x1C, size 0x1 + bool bMoved; // offset 0x20, size 0x1 + + FEMouse(); + + void Reset(); + void Update(FEMouseInfo& Info, unsigned long tDelta); + bool WasPressed(unsigned short Mask); + bool WasHeld(unsigned short Mask); + bool IsDown(unsigned short Mask); + unsigned long HeldFor(unsigned short Mask); + bool WasReleased(unsigned short Mask); + void DecrementHold(unsigned short Mask, unsigned long Amount); + + inline int GetXPos() const { return XPos; } + inline int GetYPos() const { return YPos; } + inline bool MouseMoved() const { return bMoved; } +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEMovie.h b/src/Speed/Indep/Src/FEng/FEMovie.h index fd6b5f7e4..8eeef9295 100644 --- a/src/Speed/Indep/Src/FEng/FEMovie.h +++ b/src/Speed/Indep/Src/FEng/FEMovie.h @@ -1,10 +1,19 @@ -#ifndef FENG_FEMOVIE_H -#define FENG_FEMOVIE_H +#ifndef _FEMOVIE +#define _FEMOVIE -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include "FEObject.h" + +// total size: 0x60 +struct FEMovie : public FEObject { + unsigned long CurTime; // offset 0x5C, size 0x4 + + inline FEMovie() : FEObject(), CurTime(0) {} + inline FEMovie(const FEMovie& Object, bool bReference); + ~FEMovie() override; + FEObject* Clone(bool bReference) override; + inline void Update(unsigned long tDelta) { CurTime += tDelta; } +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEMsgTargetList.cpp b/src/Speed/Indep/Src/FEng/FEMsgTargetList.cpp index e69de29bb..99bce50f0 100644 --- a/src/Speed/Indep/Src/FEng/FEMsgTargetList.cpp +++ b/src/Speed/Indep/Src/FEng/FEMsgTargetList.cpp @@ -0,0 +1,32 @@ +#include "Speed/Indep/Src/FEng/FEMsgTargetList.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +void FEMsgTargetList::Allocate(unsigned long NewAlloc) { + if (NewAlloc == 0) { + if (pTargets) { + delete[] reinterpret_cast(pTargets); + } + pTargets = nullptr; + Count = 0; + Alloc = 0; + } else if (NewAlloc != Alloc) { + FEObject** pNewTargets = static_cast(FEngMalloc(NewAlloc * sizeof(FEObject*), nullptr, 0)); + if (NewAlloc < Alloc) { + FEngMemCpy(pNewTargets, pTargets, NewAlloc * sizeof(FEObject*)); + } else { + FEngMemCpy(pNewTargets, pTargets, Alloc * sizeof(FEObject*)); + } + if (pTargets) { + delete[] reinterpret_cast(pTargets); + } + pTargets = pNewTargets; + } + Alloc = NewAlloc; +} + +void FEMsgTargetList::AppendTarget(FEObject* pObject) { + if (Count == Alloc) { + Allocate(Count + 1); + } + pTargets[Count++] = pObject; +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/FEng/FEMsgTargetList.h b/src/Speed/Indep/Src/FEng/FEMsgTargetList.h index e5f94d962..6134cb071 100644 --- a/src/Speed/Indep/Src/FEng/FEMsgTargetList.h +++ b/src/Speed/Indep/Src/FEng/FEMsgTargetList.h @@ -5,6 +5,31 @@ #pragma once #endif +struct FEObject; +// total size: 0x10 +struct FEMsgTargetList { + unsigned long MsgID; // offset 0x0, size 0x4 + unsigned long Alloc; // offset 0x4, size 0x4 + unsigned long Count; // offset 0x8, size 0x4 + FEObject** pTargets; // offset 0xC, size 0x4 + + inline FEMsgTargetList() : MsgID(0), Alloc(0), Count(0), pTargets(nullptr) {} + inline FEMsgTargetList(unsigned long NewID) : MsgID(NewID), Alloc(0), Count(0), pTargets(nullptr) {} + inline ~FEMsgTargetList() { + if (pTargets) { + delete[] pTargets; + } + } + + inline void SetMsgID(unsigned long NewID) { MsgID = NewID; } + inline unsigned long GetMsgID() const { return MsgID; } + inline unsigned long GetCount() const { return Count; } + inline FEObject* GetTarget(unsigned long Index) { return pTargets[Index]; } + inline const FEObject* GetTarget(unsigned long Index) const { return pTargets[Index]; } + + void Allocate(unsigned long NewAlloc); + void AppendTarget(FEObject* pObject); +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEMultiImage.cpp b/src/Speed/Indep/Src/FEng/FEMultiImage.cpp index e69de29bb..8ca02278f 100644 --- a/src/Speed/Indep/Src/FEng/FEMultiImage.cpp +++ b/src/Speed/Indep/Src/FEng/FEMultiImage.cpp @@ -0,0 +1,75 @@ +#include + +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/FEng/FEMovie.h" +#include "Speed/Indep/Src/FEng/FEAnimImage.h" +#include "Speed/Indep/Src/FEng/FEColoredImage.h" +#include "Speed/Indep/Src/FEng/FESimpleImage.h" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +FEImage::~FEImage() {} + +FEObject* FEImage::Clone(bool bReference) { + return FENG_NEW FEImage(*this, bReference); +} + +FEMultiImage::FEMultiImage(const FEMultiImage& Object, bool bReference) + : FEImage(Object, bReference) {} + +FEMultiImage::~FEMultiImage() {} + +FEObject* FEMultiImage::Clone(bool bReference) { + return FENG_NEW FEMultiImage(*this, bReference); +} + +FEMovie::FEMovie(const FEMovie& Object, bool bReference) + : FEObject(Object, bReference), CurTime(Object.CurTime) {} + +FEMovie::~FEMovie() {} + +FEObject* FEMovie::Clone(bool bReference) { + return FENG_NEW FEMovie(*this, bReference); +} + +unsigned long FEMultiImage::GetTexture(unsigned long tex_num) { + if (tex_num > 2) { + return 0xFFFFFFFF; + } + + return hTexture[tex_num]; +} + +void FEMultiImage::SetUVs(unsigned long tex_num, FEVector2 top_left, FEVector2 bottom_right) { + if (tex_num > 2) return; + reinterpret_cast(pData)->TopLeftUV[tex_num].x = top_left.x; + reinterpret_cast(pData)->TopLeftUV[tex_num].y = top_left.y; + reinterpret_cast(pData)->BottomRightUV[tex_num].x = bottom_right.x; + reinterpret_cast(pData)->BottomRightUV[tex_num].y = bottom_right.y; +} + +void FEMultiImage::GetUVs(unsigned long tex_num, FEVector2& top_left, FEVector2& bottom_right) { + if (tex_num > 2) return; + top_left.x = reinterpret_cast(pData)->TopLeftUV[tex_num].x; + top_left.y = reinterpret_cast(pData)->TopLeftUV[tex_num].y; + bottom_right.x = reinterpret_cast(pData)->BottomRightUV[tex_num].x; + bottom_right.y = reinterpret_cast(pData)->BottomRightUV[tex_num].y; +} + +FEAnimImage::~FEAnimImage() {} + +FEObject* FEAnimImage::Clone(bool bReference) { + return FENG_NEW FEAnimImage(*this, bReference); +} + +FEColoredImage::~FEColoredImage() {} + +FEObject* FEColoredImage::Clone(bool bReference) { + return FENG_NEW FEColoredImage(*this, bReference); +} + +FESimpleImage::~FESimpleImage() {} + +FEObject* FESimpleImage::Clone(bool bReference) { + return FENG_NEW FESimpleImage(*this, bReference); +} diff --git a/src/Speed/Indep/Src/FEng/FEMultiImage.h b/src/Speed/Indep/Src/FEng/FEMultiImage.h index bfa7bd050..6d7e13b7b 100644 --- a/src/Speed/Indep/Src/FEng/FEMultiImage.h +++ b/src/Speed/Indep/Src/FEng/FEMultiImage.h @@ -1,10 +1,34 @@ -#ifndef FENG_FEMULTIIMAGE_H -#define FENG_FEMULTIIMAGE_H +#ifndef _FEMULTIIMAGE +#define _FEMULTIIMAGE -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include "feimage.h" + +struct FEMultiImageData; + +// total size: 0x78 +struct FEMultiImage : public FEImage { + unsigned long hTexture[3]; // offset 0x60, size 0xC + unsigned long TextureFlags[3]; // offset 0x6C, size 0xC + + inline FEMultiImage() : FEImage() { + for (int i = 0; i <= 2; i++) { + hTexture[i] = 0; + TextureFlags[i] = 1; + } + } + inline FEMultiImage(const FEMultiImage& Object, bool bReference); + ~FEMultiImage() override; + + inline FEMultiImageData* GetMultiImageData(); + FEObject* Clone(bool bReference) override; + unsigned long GetTexture(unsigned long tex_num); + void SetTexture(unsigned long tex_num, unsigned long handle); + unsigned long GetTextureFlags(unsigned long tex_num); + void SetTextureFlag(unsigned long tex_num, unsigned long flag, bool on); + void SetUVs(unsigned long tex_num, FEVector2 top_left, FEVector2 bottom_right); + void GetUVs(unsigned long tex_num, FEVector2& top_left, FEVector2& bottom_right); +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEObject.cpp b/src/Speed/Indep/Src/FEng/FEObject.cpp index e69de29bb..361fb8a09 100644 --- a/src/Speed/Indep/Src/FEng/FEObject.cpp +++ b/src/Speed/Indep/Src/FEng/FEObject.cpp @@ -0,0 +1,389 @@ +#include + +#include "FEObject.h" +#include "FETypes.h" +#include "FEScript.h" +#include "FESlotPool.h" +#include "FEngStandard.h" +#include "FEMessageResponse.h" +#include "fengine.h" + +extern FEMultiPool ObjDataPool; + +inline bool CloseEnoughPosition(const FEVector3& vector1, const FEVector3& vector2) { + if (!Close(vector1.x, vector2.x, 0.001f)) return false; + if (!Close(vector1.y, vector2.y, 0.001f)) return false; + return Close(vector1.z, vector2.z, 0.001f); +} + +inline bool CloseEnoughColor(const FEColor& color1, const FEColor& color2) { + if (!Close(static_cast(color1.r), static_cast(color2.r), 1L)) return false; + if (!Close(static_cast(color1.g), static_cast(color2.g), 1L)) return false; + if (!Close(static_cast(color1.b), static_cast(color2.b), 1L)) return false; + return Close(static_cast(color1.a), static_cast(color2.a), 1L); +} + +bool Close(float a, float b, float epsilon); +bool Close(long a, long b, long epsilon); + +FEObjectDestructorCallback* FEObject::pDestructorCallback; + +FEObject::FEObject() + : NameHash(0) // + , pName(nullptr) // + , Flags(0) // + , RenderContext(0) // + , Handle(0) // + , UserParam(0) // + , pData(nullptr) // + , DataSize(0) // + , Cached(nullptr) // +{ + GUID = FEngine::GetNextGUID(); +} + +FEObject::FEObject(const FEObject& Object, bool bReference) + : NameHash(0) // + , pName(nullptr) // + , Flags(0) // + , Handle(0) // + , UserParam(0) // + , pData(nullptr) // +{ + GUID = FEngine::SysGUID++; + SetDataSize(Object.DataSize); + FEngMemSet(pData, 0, DataSize); + Type = Object.Type; + Flags = Object.Flags; + RenderContext = Object.RenderContext; + ResourceIndex = Object.ResourceIndex; + Handle = Object.Handle; + SetName(Object.pName); + + FEMessageResponse* pSrcResp = static_cast(Object.Responses.GetHead()); + while (pSrcResp) { + FEMessageResponse* pNewResp = new FEMessageResponse(); + unsigned long count = pSrcResp->GetCount(); + pNewResp->SetCount(count); + pNewResp->MsgID = pSrcResp->MsgID; + for (unsigned long i = 0; i < count; i++) { + pNewResp->pResponseList[i] = pSrcResp->pResponseList[i]; + } + Responses.AddNode(Responses.GetTail(), pNewResp); + pSrcResp = static_cast(pSrcResp->GetNext()); + } + + for (FEScript* pSrcScript = static_cast(Object.Scripts.GetHead()); pSrcScript; pSrcScript = static_cast(pSrcScript->GetNext())) { + FEScript* pNewScript = new FEScript(*pSrcScript, bReference); + Scripts.AddNode(Scripts.GetTail(), pNewScript); + } + + FEScript* pFoundScript = FindScript(Object.pCurrentScript->ID); + SetCurrentScript(pFoundScript); + + FEScript* pMyScript = static_cast(Scripts.GetHead()); + for (FEScript* pSrcScript = static_cast(Object.Scripts.GetHead()); pSrcScript; pSrcScript = static_cast(pSrcScript->GetNext())) { + if (pSrcScript->pChainTo) { + pMyScript->pChainTo = FindScript(pSrcScript->pChainTo->ID); + } + pMyScript = static_cast(pMyScript->GetNext()); + } +} + +FEObject::~FEObject() { + if (pDestructorCallback) { + pDestructorCallback->OnDestroy(this); + } + ObjDataPool.Free(pData); + if (pName) { + delete[] pName; + } +} + +void FEObject::SetDataSize(unsigned long Size) { + ObjDataPool.Free(pData); + pData = nullptr; + pData = ObjDataPool.Alloc(Size); + DataSize = Size; +} + +void FEObject::SetName(const char* pNewName) { + if (pName) { + delete[] pName; + pName = nullptr; + } + NameHash = -1; + if (pNewName) { + int Len = FEngStrLen(pNewName); + + pName = FENG_NEW char[Len + 1]; + FEngStrCpy(pName, pNewName); + NameHash = FEHashUpper(pName); + } +} + +FEScript* FEObject::FindScript(unsigned long ID) const { + FEScript* pScript = static_cast(Scripts.GetHead()); + if (pScript) { + while (pScript->ID != ID) { + pScript = pScript->GetNext(); + if (!pScript) break; + } + } + return pScript; +} + +void FEObject::SetCurrentScript(FEScript* pScript) { + pCurrentScript = pScript; + if (pScript) { + SetupMoveToTracks(); + } +} + +void FEObject::SetupMoveToTracks() { + unsigned long NumTracks = pCurrentScript->TrackCount; + FEKeyTrack* pTrack = pCurrentScript->pTracks; + + for (unsigned long i = 0; i < NumTracks; i++) { + pTrack[i].InterpAction &= 0x7F; + + if (static_cast(pTrack[i].InterpType - 3) < 2) { + float* pfData = reinterpret_cast(pData + pTrack[i].LongOffset * 4); + FEKeyNode* pBase = pTrack[i].GetBaseKey(); + FEKeyNode* pKey = pTrack[i].GetFirstDeltaKey(); + + if (pKey) { + switch (pTrack[i].ParamType) { + case 1: { + *static_cast(pKey->Val) = *reinterpret_cast(pfData) - *reinterpret_cast(static_cast(pBase->Val)); + break; + } + case 2: { + *reinterpret_cast(static_cast(pKey->Val)) = *reinterpret_cast(pfData) - *reinterpret_cast(static_cast(pBase->Val)); + break; + } + case 3: { + FEVector2 diff = *reinterpret_cast(pfData) - *reinterpret_cast(static_cast(pBase->Val)); + pKey->Val = diff; + break; + } + case 4: { + FEVector3 diff3 = *reinterpret_cast(pfData) - *reinterpret_cast(static_cast(pBase->Val)); + pKey->Val = diff3; + break; + } + case 5: { + FEQuaternion BaseQuat = *static_cast(pBase->Val); + BaseQuat.Conjugate(); + FEQuaternion qRet; + qRet.x = pfData[1] * BaseQuat.z - pfData[2] * BaseQuat.y; + qRet.y = pfData[2] * BaseQuat.x - pfData[0] * BaseQuat.z; + qRet.z = pfData[0] * BaseQuat.y - pfData[1] * BaseQuat.x; + qRet.x += pfData[0] * BaseQuat.w + BaseQuat.x * pfData[3]; + qRet.y += pfData[1] * BaseQuat.w + BaseQuat.y * pfData[3]; + qRet.z += pfData[2] * BaseQuat.w + BaseQuat.z * pfData[3]; + qRet.w = pfData[3] * BaseQuat.w - (pfData[0] * BaseQuat.x + pfData[1] * BaseQuat.y + pfData[2] * BaseQuat.z); + pKey->Val = qRet; + break; + } + case 6: { + FEColor colorDiff = *reinterpret_cast(pfData) - *reinterpret_cast(static_cast(pBase->Val)); + pKey->Val = colorDiff; + break; + } + } + } + } + } +} + +FEMessageResponse* FEObject::FindResponse(unsigned long MsgID) const { + FEMessageResponse* pNode = GetFirstResponse(); + while (pNode) { + if (pNode->GetMsgID() == MsgID) { + return pNode; + } + pNode = pNode->GetNext(); + } + return pNode; +} + +void FEObject::SetScript(unsigned long ID, bool bForce) { + FEScript* pScript = FindScript(ID); + SetScript(pScript, bForce); +} + +void FEObject::SetScript(FEScript* pScript, bool bForce) { + if (!bForce && pScript == pCurrentScript) { + return; + } + SetCurrentScript(pScript); + pCurrentScript->CurTime = 0; +} + +unsigned long FEObject::GetDataOffset(FEKeyTrack_Indices track) { + switch (track) { + case FETrack_Color: + return 0; + case FETrack_Pivot: + return 0x10; + case FETrack_Position: + return 0x1C; + case FETrack_Rotation: + return 0x28; + case FETrack_Size: + return 0x38; + case FETrack_UpperLeft: + return 0x44; + case FETrack_LowerRight: + return 0x4C; + case FETrack_Color1: + return 0x54; + case FETrack_Color2: + return 0x64; + case FETrack_Color3: + return 0x74; + case FETrack_Color4: + return 0x84; + default: + return 0; + } +} + +FEObject* FEObject::Clone(bool bReference) { + return FENG_NEW FEObject(*this, bReference); +} + +void FEObject::SetTrackValue(FEKeyTrack_Indices track, const FEVector3& value, bool bRelative) { + FEScript* pScript = static_cast(Scripts.GetHead()); + while (pScript) { + FEKeyTrack* pTrack = pScript->FindTrack(track); + if (pTrack) { + FEKeyNode* pKey = pTrack->GetBaseKey(); + if (bRelative) { + reinterpret_cast(static_cast(*pKey->GetKeyData()))->operator+=(value); + } else { + *pKey->GetKeyData() = value; + } + pTrack->InterpAction &= 0x7F; + } + pScript = pScript->GetNext(); + } + if (bRelative) { + unsigned long offset = GetDataOffset(track); + reinterpret_cast(pData + offset)->operator+=(value); + } else { + unsigned long offset = GetDataOffset(track); + *reinterpret_cast(pData + offset) = value; + } +} + +void FEObject::SetTrackValue(FEKeyTrack_Indices track, const FEVector2& value, bool bRelative) { + FEScript* pScript = static_cast(Scripts.GetHead()); + while (pScript) { + FEKeyTrack* pTrack = pScript->FindTrack(track); + if (pTrack) { + FEKeyNode* pKey = pTrack->GetBaseKey(); + if (bRelative) { + reinterpret_cast(static_cast(*pKey->GetKeyData()))->operator+=(value); + } else { + *pKey->GetKeyData() = value; + } + pTrack->InterpAction &= 0x7F; + } + pScript = pScript->GetNext(); + } + if (bRelative) { + unsigned long offset = GetDataOffset(track); + reinterpret_cast(pData + offset)->operator+=(value); + } else { + unsigned long offset = GetDataOffset(track); + *reinterpret_cast(pData + offset) = value; + } +} + +void FEObject::SetTrackValue(FEKeyTrack_Indices track, const FEColor& value, bool bRelative) { + FEScript* pScript = static_cast(Scripts.GetHead()); + while (pScript) { + FEKeyTrack* pTrack = pScript->FindTrack(track); + if (pTrack) { + FEKeyNode* pKey = pTrack->GetBaseKey(); + if (bRelative) { + *reinterpret_cast(static_cast(*pKey->GetKeyData())) += value; + } else { + *pKey->GetKeyData() = value; + } + pTrack->InterpAction &= 0x7F; + } + pScript = pScript->GetNext(); + } + if (bRelative) { + unsigned long offset = GetDataOffset(track); + *reinterpret_cast(pData + offset) += value; + } else { + unsigned long offset = GetDataOffset(track); + *reinterpret_cast(pData + offset) = value; + } +} + +void FEObject::SetPosition(const FEVector3& position, bool bRelative) { + if (Type > 0xFF) { + return; + } + if (bRelative) { + FEVector3 zero(0.0f, 0.0f, 0.0f); + if (!CloseEnoughPosition(position, zero)) { + Flags |= 0x400000; + } + } else { + FEObjData* pData = GetObjData(); + if (!CloseEnoughPosition(position, pData->Pos)) { + Flags |= 0x400000; + } + } + SetTrackValue(FETrack_Position, position, bRelative); +} + +void FEObject::SetRotation(const FEQuaternion& rotation, bool bRelative) { + if (Type > 0xFF) { + return; + } + Flags |= 0x400000; + FEScript* pScript = static_cast(Scripts.GetHead()); + while (pScript) { + FEKeyTrack* pTrack = pScript->FindTrack(FETrack_Rotation); + if (pTrack) { + FEKeyNode* pKey = pTrack->GetBaseKey(); + if (bRelative) { + reinterpret_cast(static_cast(*pKey->GetKeyData()))->operator*=(rotation); + } else { + *pKey->GetKeyData() = rotation; + } + pTrack->InterpAction &= 0x7F; + } + pScript = pScript->GetNext(); + } + if (bRelative) { + GetObjData()->Rot *= rotation; + } else { + GetObjData()->Rot = rotation; + } +} + +void FEObject::SetColor(const FEColor& color, bool bRelative) { + if (Type > 0xFF) { + return; + } + bool bClose; + if (bRelative) { + FEColor zero(0); + bClose = CloseEnoughColor(color, zero); + } else { + FEObjData* pData = GetObjData(); + bClose = CloseEnoughColor(color, pData->Col); + } + if (!bClose) { + Flags |= 0x400000; + } + SetTrackValue(FETrack_Color, color, bRelative); +} diff --git a/src/Speed/Indep/Src/FEng/FEObject.h b/src/Speed/Indep/Src/FEng/FEObject.h index 3bcca442a..9793d6fad 100644 --- a/src/Speed/Indep/Src/FEng/FEObject.h +++ b/src/Speed/Indep/Src/FEng/FEObject.h @@ -1,12 +1,45 @@ -#ifndef FENG_FEOBJECT_H -#define FENG_FEOBJECT_H - -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +// !! BUILD BREAK (zFe2 agent) !! +// Commit ab675cc1 broke the zFe2 TU. NgcAs fails with "Unrecognised opcode" +// at feWidget.cpp(21). Bisected: 7c2ee32b builds, ab675cc1 does not. +// The header scaffolding added in ab675cc1 pushes NgcAs past an internal +// limit. The assembler error is non-deterministic (.4b, .l, .string, etc.) +// which points to memory corruption inside NgcAs/wibo. +// Please trim headers or split scaffolding to fix. I'm blocked on zFe2. +// -- zFe2 agent +#ifndef _FEOBJECT +#define _FEOBJECT +#include "types.h" #include "FEList.h" -#include "FEScript.h" + +struct FEScript; +struct FERenderObject; +struct FEObjData; +struct FEMessageResponse; +struct FEObject; + +struct FEObjectDestructorCallback { + virtual void OnDestroy(FEObject* pObject) = 0; +}; +struct FEVector2; +struct FEVector3; +struct FEColor; +struct FEQuaternion; +enum FEKeyTrack_Indices { + FETrack_Color = 0, + FETrack_Pivot = 1, + FETrack_Position = 2, + FETrack_Rotation = 3, + FETrack_Size = 4, + FETrack_UpperLeft = 5, + FETrack_LowerRight = 6, + FETrack_FrameNumber = 7, + FETrack_Color1 = 7, + FETrack_Color2 = 8, + FETrack_Color3 = 9, + FETrack_Color4 = 10, + Num_BaseFETracks = 11, +}; enum FEObjType { FE_None = 0, @@ -26,24 +59,62 @@ enum FEObjType { }; // total size: 0x5C -class FEObject : public FEMinNode { - public: - private: +struct FEObject : public FEMinNode { + static FEObjectDestructorCallback* pDestructorCallback; + unsigned long GUID; // offset 0xC, size 0x4 unsigned long NameHash; // offset 0x10, size 0x4 - char *pName; // offset 0x14, size 0x4 + char* pName; // offset 0x14, size 0x4 FEObjType Type; // offset 0x18, size 0x4 unsigned long Flags; // offset 0x1C, size 0x4 unsigned short RenderContext; // offset 0x20, size 0x2 unsigned short ResourceIndex; // offset 0x22, size 0x2 unsigned long Handle; // offset 0x24, size 0x4 unsigned long UserParam; // offset 0x28, size 0x4 - unsigned char *pData; // offset 0x2C, size 0x4 + unsigned char* pData; // offset 0x2C, size 0x4 unsigned long DataSize; // offset 0x30, size 0x4 FEMinList Responses; // offset 0x34, size 0x10 FEMinList Scripts; // offset 0x44, size 0x10 - FEScript *pCurrentScript; // offset 0x54, size 0x4 - class FERenderObject *Cached; // offset 0x58, size 0x4 + FEScript* pCurrentScript; // offset 0x54, size 0x4 + FERenderObject* Cached; // offset 0x58, size 0x4 + + inline FEObjData* GetObjData() const { return reinterpret_cast(pData); } + inline FEScript* GetFirstScript() const { return reinterpret_cast(Scripts.GetHead()); } + inline unsigned long GetNumScripts() const; + inline FEScript* GetScript(unsigned long Index) const; + inline FEMessageResponse* GetFirstResponse() const { return reinterpret_cast(Responses.GetHead()); } + inline unsigned long GetNumResponses() const; + inline FEMessageResponse* GetResponse(unsigned long Index) const; + inline void SetNameHash(const unsigned long nameHash); + inline FEObject* GetNext() const { return static_cast(FEMinNode::GetNext()); } + inline FEObject* GetPrev() const { return static_cast(FEMinNode::GetPrev()); } + + FEObject(); + FEObject(const FEObject& Object, bool bReference); + ~FEObject() override; + + void SetDataSize(unsigned long Size); + void SetName(const char* pNewName); + FEScript* FindScript(unsigned long ID) const; + void SetupMoveToTracks(); + void SetCurrentScript(FEScript* pScript); + FEMessageResponse* FindResponse(unsigned long MsgID) const; + + void SetPivot(const FEVector3& pivot, bool bRelative); + void SetPosition(const FEVector3& position, bool bRelative); + void SetRotation(const FEQuaternion& rotation, bool bRelative); + void SetSize(const FEVector3& size, bool bRelative); + void SetColor(const FEColor& color, bool bRelative); + void SetScript(unsigned long ID, bool bForce); + void SetScript(FEScript* pScript, bool bForce); + unsigned long GetDataOffset(FEKeyTrack_Indices track); + + virtual FEObject* Clone(bool bReference); + +protected: + void SetTrackValue(FEKeyTrack_Indices track, const FEVector3& value, bool bRelative); + void SetTrackValue(FEKeyTrack_Indices track, const FEVector2& value, bool bRelative); + void SetTrackValue(FEKeyTrack_Indices track, const FEColor& value, bool bRelative); }; #endif diff --git a/src/Speed/Indep/Src/FEng/FEObjectCallback.h b/src/Speed/Indep/Src/FEng/FEObjectCallback.h index cd1b381b3..6c684f841 100644 --- a/src/Speed/Indep/Src/FEng/FEObjectCallback.h +++ b/src/Speed/Indep/Src/FEng/FEObjectCallback.h @@ -5,6 +5,15 @@ #pragma once #endif +#include "types.h" +struct FEObject; + +// total size: 0x4 +struct FEObjectCallback { + inline FEObjectCallback() {} + inline virtual ~FEObjectCallback() {} + virtual bool Callback(FEObject* obj) = 0; +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEObjectSorter.h b/src/Speed/Indep/Src/FEng/FEObjectSorter.h index 5b47d81e2..4ff366685 100644 --- a/src/Speed/Indep/Src/FEng/FEObjectSorter.h +++ b/src/Speed/Indep/Src/FEng/FEObjectSorter.h @@ -5,6 +5,45 @@ #pragma once #endif +#include "Speed/Indep/Src/FEng/FEngStandard.h" +template +void FEObjectSorter::SortObjects() { + int lNumBytes = mulNumObjects << 3; + SFERadixKey* pstDestList = mastScratchList; + SFERadixKey* pstSrcList = mastFinalList; + int b = 3; + do { + long alElemCount[256]; + FEngMemSet(alElemCount, 0, sizeof(alElemCount)); + int byteOffset = b + 4; + int nextB = b - 1; + unsigned char* pucByte = reinterpret_cast(pstSrcList) + byteOffset; + int i = 0; + if (i < lNumBytes) { + do { + unsigned char ucIndex = pucByte[i]; + i += 8; + alElemCount[ucIndex]++; + } while (i < lNumBytes); + } + long alElemIndex[256]; + alElemIndex[0] = 0; + for (int j = 0; j < 255; j++) { + alElemIndex[j + 1] = alElemIndex[j] + alElemCount[j]; + } + for (int k = 0; k < static_cast(mulNumObjects); k++) { + unsigned char ucIndex = pucByte[k * 8]; + SFERadixKey* pOut = pstDestList + alElemIndex[ucIndex]; + pOut->pobObject = pstSrcList[k].pobObject; + pOut->ulKey = pstSrcList[k].ulKey; + alElemIndex[ucIndex]++; + } + SFERadixKey* pstTemp = pstSrcList; + pstSrcList = pstDestList; + pstDestList = pstTemp; + b = nextB; + } while (b >= 0); +} #endif diff --git a/src/Speed/Indep/Src/FEng/FEPackage.cpp b/src/Speed/Indep/Src/FEng/FEPackage.cpp index e69de29bb..e91b153f7 100644 --- a/src/Speed/Indep/Src/FEng/FEPackage.cpp +++ b/src/Speed/Indep/Src/FEng/FEPackage.cpp @@ -0,0 +1,758 @@ +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/fengine.h" +#include "Speed/Indep/Src/FEng/FEResourceRequest.h" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" +#include "Speed/Indep/Src/FEng/FEListBox.h" +#include "Speed/Indep/Src/FEng/FECodeListBox.h" +#include "Speed/Indep/Src/FEng/FEGameInterface.h" +#include "Speed/Indep/Src/FEng/FEMovie.h" + +// Forward declarations for types only needed locally as pointer members. +// Their struct definitions come from FEngine.cpp earlier in the jumbo build. + +// total size: 0xC +struct FELibraryRef { + unsigned long ObjGUID; // offset 0x0, size 0x4 + unsigned long PackNameHash; // offset 0x4, size 0x4 + unsigned long LibGUID; // offset 0x8, size 0x4 + + FELibraryRef() + : ObjGUID(0) // + , PackNameHash(0xFFFFFFFF) // + , LibGUID(0) {} +}; + +// total size: 0x14 +struct FEObjectComment : public FEMinNode { + unsigned long ObjectGUID; // offset 0xC, size 0x4 + char* pStr; // offset 0x10, size 0x4 + + inline ~FEObjectComment() override {} +}; + +// FEMsgTargetList defined in FEPackage.h + +unsigned long FEPackage::uHoldDirtyFlags; + +FEPackage::FEPackage() + : bExecuting(false) // + , bUseIdleList(false) // + , bIsLibrary(false) // + , bStartEqualsAccept(false) // + , bErrorScreen(false) // + , Priority(0) // + , Controllers(0xff) // + , OldControllers(0xff) // + , bInputEnabled(false) // + , pFilename(nullptr) // + , pParentPackage(nullptr) // + , UserParam(0) // + , NumRequests(0) // + , pRequests(nullptr) // + , NumMsgTargets(0) // + , pMsgTargets(nullptr) // + , NumLibRefs(0) // + , pLibRefs(nullptr) // + , pCurrentButton(nullptr) // + , pResourceNames(nullptr) // + , MouseObjectStates(nullptr) // + , NumMouseObjects(0) +{ +} + +FEPackage::~FEPackage() { + if (pFilename) { + delete[] pFilename; + } + if (pRequests) { + delete[] pRequests; + } + if (pMsgTargets) { + delete[] pMsgTargets; + } + if (pResourceNames) { + delete[] pResourceNames; + } + if (MouseObjectStates) { + delete[] MouseObjectStates; + } + FEObjectComment* pComment; + while ((pComment = static_cast(Comments.RemHead())) != nullptr) { + if (pComment->pStr) { + delete[] pComment->pStr; + } + delete pComment; + } + if (pLibRefs) { + delete[] pLibRefs; + } +} + +bool FEPackage::InitializePackage() { + PackageInitStateCB cb; + return ForAllObjects(cb); +} + +FEObject* FEPackage::FindObjectByHash(unsigned long NameHash) { + FEFindByHash finder; + finder.Hash = NameHash; + finder.pFound = nullptr; + ForAllObjects(finder); + return finder.pFound; +} + +FEObject* FEPackage::FindObjectByGUID(unsigned long GUID) { + FEFindByGUID finder; + finder.GUID = GUID; + finder.pFound = nullptr; + ForAllObjects(finder); + return finder.pFound; +} + +bool FEPackage::ForAllChildren(FEGroup* pGroup, FEObjectCallback& Callback) { + FEObject* pObj = static_cast(pGroup->Children.GetHead()); + while (pObj) { + if (!Callback.Callback(pObj)) { + return false; + } + if (pObj->Type == 5 && !ForAllChildren(static_cast(pObj), Callback)) { + return false; + } + pObj = static_cast(pObj->GetNext()); + } + return true; +} + +bool FEPackage::ForAllObjects(FEObjectCallback& Callback) { + FEObject* pObj = static_cast(Objects.GetHead()); + while (pObj) { + if (!Callback.Callback(pObj)) { + return false; + } + if (pObj->Type == 5 && !ForAllChildren(static_cast(pObj), Callback)) { + return false; + } + pObj = static_cast(pObj->GetNext()); + } + return true; +} + +void FEPackage::SetCurrentButton(FEObject* pNewButton, bool bSendMsgs) { + if (bSendMsgs) { + if (pCurrentButton) { + pEnginePtr->QueueMessage(0x55d1e635, nullptr, this, pCurrentButton, 0); + pEnginePtr->QueueMessage(0x55d1e635, pCurrentButton, this, reinterpret_cast(0xfffffffb), 0); + } + if (pNewButton) { + pEnginePtr->QueueMessage(0xabc08912, nullptr, this, pNewButton, 0); + pEnginePtr->QueueMessage(0xabc08912, pNewButton, this, reinterpret_cast(0xfffffffb), 0); + } + } + pCurrentButton = pNewButton; +} + +bool PackageInitStateCB::Callback(FEObject* pObj) { + pObj->SetCurrentScript(pObj->FindScript(0x1744b3)); + pObj->pCurrentScript->CurTime = 0; + pObj->Flags |= 0x3c00000; + return true; +} + +bool FEFindByHash::Callback(FEObject* pObj) { + if (pObj->NameHash == Hash) { + pFound = pObj; + return false; + } + return true; +} + +bool FEFindByGUID::Callback(FEObject* pObj) { + if (pObj->GUID == GUID) { + pFound = pObj; + return false; + } + return true; +} + +bool MouseStateObjectCounter::Callback(FEObject* pObj) { + if (pObj->Flags & 0x20000) { + NumMouseObjects++; + } + return true; +} + +bool MouseStateArrayBuilder::Callback(FEObject* pObj) { + if (pObj->Flags & 0x20000) { + pPack->AddMouseObjectState(pObj); + } + return true; +} + +bool MouseStateArrayOffsetUpdater::Callback(FEObject* pObj) { + if (pObj->Flags & 0x20000) { + pPack->UpdateMouseObjectOffsets(pObj); + } + return true; +} + +extern unsigned int eFrameCounter; +extern unsigned int eFrameCounterOLD; +extern unsigned int objCount; + +void FEKeyInterp(FEKeyTrack* pTrack, long tTime, void* pOutData); +void FEKeyInterpFast(FEKeyTrack* pTrack, long tTime, void* pOutData); + +void FEPackage::UpdateGroup(FEGroup* pGroup, long tDeltaTicks) { + FEObject* pChild = pGroup->GetFirstChild(); + while (pChild) { + UpdateObject(pChild, tDeltaTicks); + pChild->Flags |= pGroup->Flags & 0x3C00000; + pChild = static_cast(pChild->next); + } +} + +void FEPackage::UpdateObjectTracks(FEObject* pObj, FEScript* pScript) { + unsigned char* pData = pObj->pData; + int CurTime = pScript->CurTime; + FEKeyTrack* pTracks = pScript->pTracks; + + if (bExecuting) { + if (pTracks && pTracks->LongOffset == 0) { + if (pTracks->InterpAction & 0x80) { + pObj->Flags &= FEPackage::uHoldDirtyFlags | 0xFF7FFFFF; + } else { + pObj->Flags |= 0x800000; + } + FEKeyInterpFast(pTracks, CurTime, pData); + } else { + pObj->Flags &= FEPackage::uHoldDirtyFlags | 0xFF7FFFFF; + } + unsigned char bDone = 0x80; + if (*reinterpret_cast(pObj->pData + 0xC)) { + unsigned char TrackCount = static_cast(pScript->TrackCount); + for (unsigned char i = 0; i < TrackCount; i++, pTracks++) { + bDone = pTracks->InterpAction & bDone; + FEKeyInterpFast(pTracks, CurTime, + pData + pTracks->LongOffset * 4); + } + } + if (bDone) { + pObj->Flags &= FEPackage::uHoldDirtyFlags | 0xFEFFFFFF; + } else { + pObj->Flags |= 0x1000000; + } + } else { + if (pTracks && pTracks->LongOffset == 0) { + FEKeyInterp(pTracks, CurTime, pData); + } + if (*reinterpret_cast(pObj->pData + 0xC)) { + unsigned char TrackCount = static_cast(pScript->TrackCount); + for (unsigned char i = 0; i < TrackCount; i++, pTracks++) { + FEKeyInterp(pTracks, CurTime, + pData + pTracks->LongOffset * 4); + } + } + } + + unsigned long Flags = pObj->Flags; + if (Flags & 0x1C00000) { + pObj->Flags = Flags | 0x2000000; + } +} + +void FEPackage::IssueScriptMessages(FEngine* pEngine, FEObject* pObj, + FEScript* pScript, long tOldTime, long tNewTime) { + FEEvent* pEvents = pScript->Events.pEvent; + int Count = pScript->Events.Count; + + if (tNewTime < tOldTime) { + return; + } + + if (tNewTime == pScript->Length) { + tNewTime++; + } + + int i = 0; + while (i < Count) { + if (pEvents[i].tTime >= static_cast(tOldTime)) { + break; + } + i++; + } + + if (i < Count && pEvents[i].tTime < static_cast(tNewTime)) { + for (;;) { + switch (pEvents[i].Target) { + case 0: + if (pEvents[i].EventID == 0x1B3909AA) { + FEObject* pButton = FindObjectByGUID(0); + SetCurrentButton(pButton, true); + } else { + pEngine->QueueMessage(pEvents[i].EventID, pObj, this, + reinterpret_cast(0), 0); + } + break; + case 0xFFFFFFFF: + pEngine->SendMessageToGame(pEvents[i].EventID, pObj, this, 0); + break; + case 0xFFFFFFFC: + pEngine->QueueMessage(pEvents[i].EventID, pObj, this, + reinterpret_cast(0xFFFFFFFC), 0); + break; + case 0xFFFFFFFB: + pEngine->QueueMessage(pEvents[i].EventID, pObj, this, + reinterpret_cast(0xFFFFFFFB), 0); + break; + case 0xFFFFFFFA: + pEngine->QueueMessage(pEvents[i].EventID, pObj, this, + reinterpret_cast(0xFFFFFFFA), 0); + break; + default: { + FEObject* pTarget = FindObjectByGUID(pEvents[i].Target); + if (pEvents[i].EventID == 0x1B3909AA) { + FEObject* pButton = FindObjectByGUID(pEvents[i].Target); + SetCurrentButton(pButton, true); + break; + } + if (pObj) { + pEngine->QueueMessage(pEvents[i].EventID, pObj, this, pTarget, 0); + } + break; + } + } + i++; + if (i >= Count) { + return; + } + if (pEvents[i].tTime >= static_cast(tNewTime)) { + return; + } + } + } +} + +void FEPackage::UpdateObject(FEObject* pObj, long tDeltaTicks) { + if (eFrameCounterOLD == eFrameCounter) { + objCount++; + } else { + objCount = 0; + eFrameCounterOLD = eFrameCounter; + } + + unsigned long Flags = pObj->Flags; + if (Flags & 0x1C00000) { + pObj->Flags = Flags | 0x2000000; + } else { + pObj->Flags = Flags & (FEPackage::uHoldDirtyFlags | 0xFDFFFFFF); + } + + FEScript* pScript = pObj->pCurrentScript; + int tPrevTime = pScript->CurTime; + int ScrLength = pScript->Length; + pScript->CurTime = tPrevTime + iTickIncrement; + if (pScript->CurTime < 0) { + pScript->CurTime = 0; + } + + unsigned long PlayAction; + if (pScript->CurTime >= ScrLength) { + if (bExecuting) { + if (pScript->pChainTo) { + UpdateObjectTracks(pObj, pScript); + int tOverTime = pScript->CurTime - ScrLength; + pScript->CurTime = 0; + if (pScript->Events.Count) { + IssueScriptMessages(pEnginePtr, pObj, pScript, tPrevTime, ScrLength); + } + pScript = pScript->pChainTo; + pObj->SetCurrentScript(pScript); + pScript->CurTime = tOverTime; + if (pScript->Events.Count) { + goto issueFrom0; + } + } else { + PlayAction = pScript->Flags & 3; + switch (PlayAction) { + case 0: + if (pScript->Events.Count) { + IssueScriptMessages(pEnginePtr, pObj, pScript, tPrevTime, ScrLength); + } + pScript->CurTime = pScript->Length + 1; + break; + case 1: + if (pScript->Length > 0) { + if (pScript->Events.Count) { + IssueScriptMessages(pEnginePtr, pObj, pScript, tPrevTime, ScrLength); + } + pScript->CurTime = pScript->CurTime - + (pScript->CurTime / pScript->Length) * pScript->Length; + if (pScript->Events.Count) { + IssueScriptMessages(pEnginePtr, pObj, pScript, 0, pScript->CurTime); + } + pObj->SetupMoveToTracks(); + } else { + pScript->CurTime = 0; + } + break; + case 2: + if (pScript->Length > 0) { + int doubleLen = pScript->Length * 2; + pScript->CurTime = pScript->CurTime - (pScript->CurTime / doubleLen) * doubleLen; + } else { + pScript->CurTime = 0; + } + break; + } + } + if (bExecuting && tPrevTime == pScript->CurTime && + tPrevTime == pScript->Length + 1 && !(pObj->Flags & 0x400000)) { + goto finalize; + } + } + } else { + if (bExecuting) { + if (pScript->Events.Count != 0) { + PlayAction = pScript->Flags & 3; + switch (PlayAction) { + case 0: + IssueScriptMessages(pEnginePtr, pObj, pScript, tPrevTime, pScript->CurTime); + break; + case 1: + if (pScript->CurTime < tPrevTime) { + IssueScriptMessages(pEnginePtr, pObj, pScript, tPrevTime, ScrLength); + issueFrom0: + IssueScriptMessages(pEnginePtr, pObj, pScript, 0, pScript->CurTime); + break; + } + IssueScriptMessages(pEnginePtr, pObj, pScript, tPrevTime, pScript->CurTime); + break; + case 2: + if (tPrevTime < ScrLength) { + IssueScriptMessages(pEnginePtr, pObj, pScript, tPrevTime, pScript->CurTime); + } else { + IssueScriptMessages(pEnginePtr, pObj, pScript, tPrevTime - ScrLength, 0); + IssueScriptMessages(pEnginePtr, pObj, pScript, 0, pScript->CurTime); + } + break; + } + } + if (bExecuting && tPrevTime == pScript->CurTime && + tPrevTime == pScript->Length + 1 && !(pObj->Flags & 0x400000)) { + goto finalize; + } + } + } + + UpdateObjectTracks(pObj, pScript); + +finalize: + switch (pObj->Type) { + case FE_Group: + UpdateGroup(static_cast(pObj), tDeltaTicks); + break; + case FE_List: + static_cast(pObj)->Update(static_cast(tDeltaTicks)); + break; + case FE_CodeList: + static_cast(pObj)->Update(static_cast(tDeltaTicks)); + break; + case FE_Movie: + if (bExecuting) { + static_cast(pObj)->Update(tDeltaTicks); + } + break; + } + + if (bExecuting == true && tPrevTime == pScript->CurTime && + tPrevTime == pScript->Length + 1 && !(pObj->Flags & 0x400000)) { + pObj->Flags &= FEPackage::uHoldDirtyFlags | 0xFE7FFFFF; + } + + Flags = pObj->Flags & (FEPackage::uHoldDirtyFlags | 0xFFBFFFFF); + pObj->Flags = Flags; + if (Flags & 0x1C00000) { + pObj->Flags = Flags | 0x2000000; + } +} + +void FEPackage::Update(FEngine* pEngine, long tDeltaTicks) { + FEObject* pObject = static_cast(Objects.GetHead()); + pEnginePtr = pEngine; + iTickIncrement = tDeltaTicks; + while (pObject) { + UpdateObject(pObject, tDeltaTicks); + pObject = pObject->GetNext(); + } + if (NumMouseObjects > 0) { + NumMouseObjectsCounter = 0; + MouseStateArrayOffsetUpdater the_udater; + the_udater.pPack = this; + ForAllObjects(the_udater); + } +} + +void FEPackage::SetFilename(const char* pName) { + if (pFilename) { + delete[] pFilename; + } + pFilename = nullptr; + if (pName) { + int Len = FEngStrLen(pName); + + pFilename = FENG_NEW char[Len + 1]; + FEngStrCpy(pFilename, pName); + } +} + +bool FEPackage::Startup(FEGameInterface* pGameInterface) { + bool bResult = true; + if (!pGameInterface->LoadResources(this, NumRequests, pRequests)) { + bResult = false; + } + ConnectObjectResources(); + BuildMouseObjectStateList(); + return bResult; +} + +void FEPackage::Shutdown(FEGameInterface* pGameInterface) { + if (pGameInterface) { + pGameInterface->UnloadResources(this, NumRequests, pRequests); + } +} + +FEMessageResponse* FEPackage::FindResponse(unsigned long MsgID) { + FEMessageResponse* pNode = GetFirstResponse(); + while (pNode) { + if (pNode->GetMsgID() == MsgID) { + return pNode; + } + pNode = pNode->GetNext(); + } + return pNode; +} + +bool ResourceConnector::Callback(FEObject* pObj) { + if (pObj->Type == FE_List) + goto connect; + if (pObj->Type < FE_List) + goto check_resource; + if (pObj->Type > FE_CodeList) + goto check_resource; + goto done; +connect: + ConnectListBoxResources(static_cast(pObj)); + goto done; +check_resource: + if (pObj->ResourceIndex != 0xFFFF) { + unsigned long idx = static_cast(pObj->ResourceIndex); + pObj->UserParam = (*pReqList)[idx].UserParam; + pObj->Handle = (*pReqList)[idx].Handle; + } +done: + return true; +} + +void ResourceConnector::ConnectListBoxResources(FEListBox* pList) { + { + unsigned long* pCurrentColumn = &pList->mulCurrentColumn; + unsigned long col; + if (pList->mulNumColumns == 0) { + col = 0; + } else { + col = 0; + if (col >= pList->mulNumColumns) { + col = pList->mulNumColumns - 1; + } + } + *pCurrentColumn = col; + } + { + unsigned long* pCurrentRow = &pList->mulCurrentRow; + unsigned long row; + if (pList->mulNumRows == 0) { + row = 0; + } else { + row = 0; + if (row >= pList->mulNumRows) { + row = pList->mulNumRows - 1; + } + } + *pCurrentRow = row; + } + unsigned long Rows = pList->GetNumRows(); + unsigned long Cols = pList->GetNumColumns(); + for (unsigned long j = 0; j < Rows; j++) { + unsigned long i = 0; + while (i < Cols) { + const FEListBoxCell* pCellData = pList->GetCurrentCellData(); + unsigned long resIdx = pCellData->stResource.ResourceIndex; + if (resIdx != 0xFFFFFFFF) { + FEResourceRequest* pReq = &(*pReqList)[resIdx]; + unsigned long handle = pReq->Handle; + unsigned long userParam = pReq->UserParam; + FEListBoxCell* pCell = pList->GetPCellData(pList->mulCurrentColumn, pList->mulCurrentRow); + pCell->stResource.Handle = handle; + pCell->stResource.UserParam = userParam; + pCell->stResource.ResourceIndex = resIdx; + } else { + FEListBoxCell* pCell = pList->GetPCellData(pList->mulCurrentColumn, pList->mulCurrentRow); + pCell->stResource.Handle = 0; + pCell->stResource.UserParam = 0; + pCell->stResource.ResourceIndex = 0xFFFFFFFF; + } + i++; + pList->IncrementCellByColumn(); + } + } +} + +void FEPackage::ConnectObjectResources() { + ResourceConnector resConnector; + resConnector.pPack = this; + resConnector.pReqList = &pRequests; + ForAllObjects(resConnector); +} + +void FEPackage::SetNumLibraryRefs(unsigned long NewCount) { + if (NewCount == 0) { + if (pLibRefs) { + delete[] reinterpret_cast(pLibRefs); + } + pLibRefs = nullptr; + } else { + FELibraryRef* pNewList = FENG_NEW FELibraryRef[NewCount]; + unsigned long CopyCount = NewCount; + if (NewCount > NumLibRefs) { + CopyCount = NumLibRefs; + } + if (CopyCount != 0) { + FEngMemCpy(pNewList, pLibRefs, CopyCount * sizeof(FELibraryRef)); + } + if (pLibRefs) { + delete[] reinterpret_cast(pLibRefs); + } + pLibRefs = pNewList; + NumLibRefs = NewCount; + } +} + +FELibraryRef* FEPackage::FindLibraryReference(unsigned long ObjGUID) const { + for (unsigned long i = 0; i < NumLibRefs; i++) { + if (pLibRefs[i].ObjGUID == ObjGUID) { + return &pLibRefs[i]; + } + } + return nullptr; +} + +void FEPackage::BuildMouseObjectStateList() { + if (MouseObjectStates) { + delete[] MouseObjectStates; + MouseObjectStates = nullptr; + NumMouseObjects = 0; + } + MouseStateObjectCounter the_counter; + the_counter.NumMouseObjects = 0; + ForAllObjects(the_counter); + if (the_counter.NumMouseObjects > 0) { + MouseObjectStates = new FEObjectMouseState[the_counter.NumMouseObjects]; + MouseStateArrayBuilder the_builder; + the_builder.pPack = this; + ForAllObjects(the_builder); + } +} + +FEObjectMouseState::FEObjectMouseState() { + pObject = nullptr; + Offset.h = 0.0f; + Offset.v = 0.0f; + Flags = 0; +} + +FEObjectMouseState::~FEObjectMouseState() {} + +FEMsgTargetList* FEPackage::GetMessageTargets(unsigned long MsgID) { + for (unsigned long i = 0; i < NumMsgTargets; i++) { + if (pMsgTargets[i].GetMsgID() == MsgID) { + return &pMsgTargets[i]; + } + } + return nullptr; +} + +bool OffsetCalculatron(unsigned long NameHash, FEObject* pObj, FEPoint& Offset) { + if (NameHash == pObj->NameHash) { + FEObjData* pData = pObj->GetObjData(); + Offset.h += pData->Pos.x; + Offset.v += pData->Pos.y; + return true; + } else if (pObj->Type == FE_Group && static_cast(pObj)->FindChildRecursive(NameHash)) { + FEObjData* pData = pObj->GetObjData(); + Offset.h += pData->Pos.x; + Offset.v += pData->Pos.y; + FEObject* pChild = static_cast(pObj)->GetFirstChild(); + while (pChild) { + OffsetCalculatron(NameHash, pChild, Offset); + pChild = static_cast(pChild->GetNext()); + } + return true; + } + return false; +} + +void FEPackage::AddMouseObjectState(FEObject* obj) { + if (!obj) { + return; + } + FEObject* pObj = GetFirstObject(); + unsigned long mouseable = obj->NameHash; + while (pObj) { + if (pObj->Type == FE_Group) { + if (static_cast(pObj)->FindChildRecursive(mouseable) || mouseable == pObj->NameHash) { + FEPoint p; + if (OffsetCalculatron(mouseable, pObj, p)) { + MouseObjectStates[NumMouseObjects].Offset = p; + break; + } + } + } else if (mouseable == pObj->NameHash) { + FEPoint p; + if (OffsetCalculatron(mouseable, pObj, p)) { + MouseObjectStates[NumMouseObjects].Offset = p; + break; + } + } + pObj = static_cast(pObj->GetNext()); + } + MouseObjectStates[NumMouseObjects].pObject = obj; + NumMouseObjects++; +} + +void FEPackage::UpdateMouseObjectOffsets(FEObject* obj) { + if (!obj) { + return; + } + FEObject* pObj = GetFirstObject(); + unsigned long mouseable = obj->NameHash; + while (pObj) { + if (pObj->Type == FE_Group) { + if (static_cast(pObj)->FindChildRecursive(mouseable) || mouseable == pObj->NameHash) { + FEPoint p; + if (OffsetCalculatron(mouseable, pObj, p)) { + MouseObjectStates[NumMouseObjectsCounter++].Offset = p; + break; + } + } + } else if (mouseable == pObj->NameHash) { + FEPoint p; + if (OffsetCalculatron(mouseable, pObj, p)) { + MouseObjectStates[NumMouseObjectsCounter++].Offset = p; + break; + } + } + pObj = static_cast(pObj->GetNext()); + } +} diff --git a/src/Speed/Indep/Src/FEng/FEPackage.h b/src/Speed/Indep/Src/FEng/FEPackage.h index 17d31dc5f..4801d327c 100644 --- a/src/Speed/Indep/Src/FEng/FEPackage.h +++ b/src/Speed/Indep/Src/FEng/FEPackage.h @@ -1,10 +1,199 @@ #ifndef FENG_FEPACKAGE_H #define FENG_FEPACKAGE_H -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include "FEObject.h" +#include "FEMsgTargetList.h" +#include "FEngStandard.h" + +struct FEObjectCallback; +struct FEGroup; +struct FEngine; +struct FEGameInterface; +struct FEResourceRequest; +struct FELibraryRef; +struct FEMessageResponse; +struct FEPackageRenderInfo; +struct FEListBox; +#include "FETypes.h" + +enum FEButtonWrapMode { + Wrap_None = 0, + Wrap_Horizontal = 1, + Wrap_Vertical = 2, + Wrap_Both = 3, +}; + +struct FEObjectMouseState { + FEObject* pObject; // offset 0x0, size 0x4 + FEPoint Offset; // offset 0x4, size 0x8 + unsigned long Flags; // offset 0xC, size 0x4 + + FEObjectMouseState(); + ~FEObjectMouseState(); + + static inline void* operator new[](unsigned int size) { + return FEngMalloc(size, nullptr, 0); + } + + inline bool GetBit(unsigned long bit) { return (Flags & bit) != 0; } + inline void SetBit(unsigned long bit, bool state) { + if (state) { + Flags |= bit; + } else { + Flags &= ~bit; + } + } +}; + +// total size: 0x14 +struct FENode : public FEMinNode { + char* name; // offset 0xC, size 0x4 + unsigned int nameHash; // offset 0x10, size 0x4 + + FENode(); + ~FENode() override; + bool SetName(const char* theName); + + inline const char* GetName() const { return name; } + inline const unsigned int GetNameHash() const { return nameHash; } + inline FENode* GetNext() const { return static_cast(FEMinNode::GetNext()); } + inline FENode* GetPrev() const { return static_cast(FEMinNode::GetPrev()); } +}; + +// total size: 0x10 +struct FEList : public FEMinList { + inline FENode* GetHead() const { return static_cast(FEMinList::GetHead()); } + inline FENode* RemNode(FEMinNode* n) { return static_cast(FEMinList::RemNode(n)); } + FENode* FindNode(const char* pName, FENode* node) const; + FENode* FindNode(const char* pName) const; +}; + +// total size: 0x8 +struct FEButtonMap { + FEObject** pList; // offset 0x0, size 0x4 + unsigned long Count; // offset 0x4, size 0x4 + + inline FEButtonMap() : pList(nullptr), Count(0) {} + inline ~FEButtonMap() { + if (pList) { + delete[] pList; + } + } + inline unsigned long GetCount() { return Count; } + inline void SetButton(unsigned long Index, FEObject* pButton) { pList[Index] = pButton; } + inline FEObject* GetButton(unsigned long Index) { return pList[Index]; } + + void SetCount(unsigned long NewCount); + FEObject* GetButtonFrom(FEObject* pButton, long Direction, FEGameInterface* pInterface, FEButtonWrapMode WrapMode); + void ComputeButtonLocation(FEObject* pObj, FEGameInterface* pInterface, FEVector2& Loc); +}; + +// total size: 0xC4 +struct FEPackage : public FENode { + static unsigned long uHoldDirtyFlags; + + bool bExecuting; // offset 0x14, size 0x1 + bool bUseIdleList; // offset 0x18, size 0x1 + bool bIsLibrary; // offset 0x1C, size 0x1 + bool bStartEqualsAccept; // offset 0x20, size 0x1 + bool bErrorScreen; // offset 0x24, size 0x1 + int Priority; // offset 0x28, size 0x4 + unsigned long Controllers; // offset 0x2C, size 0x4 + unsigned long OldControllers; // offset 0x30, size 0x4 + bool bInputEnabled; // offset 0x34, size 0x1 + unsigned long VersionNumber; // offset 0x38, size 0x4 + FEngine* pEnginePtr; // offset 0x3C, size 0x4 + int iTickIncrement; // offset 0x40, size 0x4 + char* pFilename; // offset 0x44, size 0x4 + FEPackage* pParentPackage; // offset 0x48, size 0x4 + unsigned long UserParam; // offset 0x4C, size 0x4 + FEMinList Objects; // offset 0x50, size 0x10 + FEMinList Responses; // offset 0x60, size 0x10 + unsigned long NumRequests; // offset 0x70, size 0x4 + FEResourceRequest* pRequests; // offset 0x74, size 0x4 + unsigned long NumMsgTargets; // offset 0x78, size 0x4 + FEMsgTargetList* pMsgTargets; // offset 0x7C, size 0x4 + FEList LibrariesUsed; // offset 0x80, size 0x10 + inline FEList& GetLibraryList() { return LibrariesUsed; } + unsigned long NumLibRefs; // offset 0x90, size 0x4 + FELibraryRef* pLibRefs; // offset 0x94, size 0x4 + FEObject* pCurrentButton; // offset 0x98, size 0x4 + FEButtonMap ButtonMap; // offset 0x9C, size 0x8 + char* pResourceNames; // offset 0xA4, size 0x4 + FEMinList Comments; // offset 0xA8, size 0x10 + FEObjectMouseState* MouseObjectStates; // offset 0xB8, size 0x4 + int NumMouseObjects; // offset 0xBC, size 0x4 + int NumMouseObjectsCounter; // offset 0xC0, size 0x4 + + FEPackage(); + ~FEPackage() override; + + bool InitializePackage(); + void Shutdown(FEGameInterface* pGameInterface); + void Update(FEngine* pEngine, long tDeltaTicks); + + inline FEObject* GetCurrentButton() { return pCurrentButton; } + inline FEButtonMap* GetButtonMap() { return &ButtonMap; } + inline FEObject* GetFirstObject() { return static_cast(Objects.GetHead()); } + inline FEMessageResponse* GetFirstResponse() { return reinterpret_cast(Responses.GetHead()); } + inline FEPackage* GetNext() { return static_cast(FENode::GetNext()); } + inline FEPackage* GetPrev() { return static_cast(FENode::GetPrev()); } + inline unsigned long GetControlMask() const { return Controllers; } + inline void SetControlMask(unsigned long ControlMask) { Controllers = ControlMask; } + inline unsigned long GetOldControlMask() const { return OldControllers; } + inline void SetOldControlMask(unsigned long ControlMask) { OldControllers = ControlMask; } + inline void SetErrorScreen(bool b) { bErrorScreen = b; } + inline bool IsInputEnabled() const { return bInputEnabled; } + inline void SetInputEnabled(bool b) { bInputEnabled = b; } + inline bool IsErrorScreen() const { return bErrorScreen; } + inline int GetPriority() const { return Priority; } + inline void SetPriority(int NewPri) { Priority = NewPri; } + inline bool IsLibrary() const { return bIsLibrary; } + inline void SetStartEqualsAccept(bool bVal) { bStartEqualsAccept = bVal; } + inline bool StartEqualsAccept() const { return bStartEqualsAccept; } + inline unsigned long GetUserParam() const { return UserParam; } + inline void SetUserParam(unsigned long NewParam) { UserParam = NewParam; } + inline void SetParentPackage(FEPackage* pPack) { pParentPackage = pPack; } + inline FEPackage* GetParentPackage() { return pParentPackage; } + inline unsigned long GetVersion() const { return VersionNumber; } + inline char* GetFilename() { return pFilename; } + inline unsigned long GetNumParentObjects() { return Objects.GetNumElements(); } + inline void SetTickIncrement(int tDeltaTicks) { iTickIncrement = tDeltaTicks; } + inline void SetExecute(bool bExec) { bExecuting = bExec; } + inline void SetUseIdleList(bool bUseIdle) { bUseIdleList = bUseIdle; } + inline bool UsesIdleList() { return bUseIdleList; } + inline FEObject* GetLastObject() { return static_cast(Objects.GetTail()); } + inline void AddObject(FEObject* pObject) { Objects.AddTail(static_cast(pObject)); } + inline void AddObjectAfter(FEObject* pObject, FEObject* pAddAfter) { Objects.AddNode(static_cast(pAddAfter), static_cast(pObject)); } + inline void RemoveObject(FEObject* pObject) { Objects.RemNode(static_cast(pObject)); } + inline unsigned long GetNumResponses() { return Responses.GetNumElements(); } + inline void AddResponse(FEMessageResponse* pResp) { Responses.AddTail(reinterpret_cast(pResp)); } + inline void PurgeResponses() { Responses.Purge(); } + inline void RemoveResponse(FEMessageResponse* pResp) { Responses.RemNode(reinterpret_cast(pResp)); } + inline FEMessageResponse* GetResponse(unsigned long Index) { return reinterpret_cast(Responses.FindNode(Index)); } + inline const FEMsgTargetList* GetMessageTargetList(unsigned long Index) const { return &pMsgTargets[Index]; } + + FEMsgTargetList* GetMessageTargets(unsigned long MsgID); + FEObject* FindObjectByHash(unsigned long NameHash); + FEObject* FindObjectByGUID(unsigned long GUID); + FEMessageResponse* FindResponse(unsigned long MsgID); + void SetCurrentButton(FEObject* pNewButton, bool bSendMsgs); + bool ForAllChildren(FEGroup* pGroup, FEObjectCallback& Callback); + bool ForAllObjects(FEObjectCallback& Callback); + void SetFilename(const char* pName); + void SetNumLibraryRefs(unsigned long NewCount); + FELibraryRef* FindLibraryReference(unsigned long ObjGUID) const; + void ConnectObjectResources(); + void BuildMouseObjectStateList(); + bool Startup(FEGameInterface* pGameInterface); + void IssueScriptMessages(FEngine* pEngine, FEObject* pObj, FEScript* pScript, long tOldTime, long tNewTime); + void UpdateObject(FEObject* pObj, long tDeltaTicks); + void UpdateObjectTracks(FEObject* pObj, FEScript* pScript); + void UpdateGroup(FEGroup* pGroup, long tDeltaTicks); + void AddMouseObjectState(FEObject* pObj); + void UpdateMouseObjectOffsets(FEObject* pObj); +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEPackageList.cpp b/src/Speed/Indep/Src/FEng/FEPackageList.cpp index e69de29bb..baf114384 100644 --- a/src/Speed/Indep/Src/FEng/FEPackageList.cpp +++ b/src/Speed/Indep/Src/FEng/FEPackageList.cpp @@ -0,0 +1,37 @@ +#include "Speed/Indep/Src/FEng/fengine_full.h" + +void FEPackageList::AddPackage(FEPackage* pPack) { + FEPackage* pNode = GetLastPackage(); + for (;;) { + if (!pNode) { + break; + } + if (pNode->GetPriority() <= pPack->GetPriority()) { + break; + } + pNode = pNode->GetPrev(); + } + Packages.AddNode(static_cast(static_cast(pNode)), static_cast(static_cast(pPack))); +} + +bool FEPackageList::RemovePackage(FEPackage* pPack) { + FEPackage* pNode = GetFirstPackage(); + while (pNode) { + if (pNode == pPack) { + Packages.RemNode(static_cast(static_cast(pPack))); + return true; + } + pNode = pNode->GetNext(); + } + return false; +} + +void FEPackageList::ReplaceParentLinks(const FEPackage* pParent, const FEPackage* pReplacement) { + FEPackage* pNode = GetFirstPackage(); + while (pNode) { + if (pNode->GetParentPackage() == pParent) { + pNode->SetParentPackage(const_cast(pReplacement)); + } + pNode = pNode->GetNext(); + } +} diff --git a/src/Speed/Indep/Src/FEng/FEPackageReader.cpp b/src/Speed/Indep/Src/FEng/FEPackageReader.cpp index e69de29bb..314981b3d 100644 --- a/src/Speed/Indep/Src/FEng/FEPackageReader.cpp +++ b/src/Speed/Indep/Src/FEng/FEPackageReader.cpp @@ -0,0 +1,1105 @@ +#include + +#include "FEPackageReader.h" +#include "FEChunk.h" +#include "FEPackage.h" +#include "FEObject.h" +#include "FEScript.h" +#include "FEString.h" +#include "feimage.h" +#include "FEMultiImage.h" +#include "FEMovie.h" +#include "FEListBox.h" +#include "FECodeListBox.h" +#include "FEGroup.h" +#include "FESimpleImage.h" +#include "FEAnimImage.h" +#include "FEColoredImage.h" +#include "FETypes.h" +#include "FEKeyTrack.h" +#include "FEngStandard.h" +#include "fengine.h" +#include "fengine_full.h" +#include "FEMsgTargetList.h" +#include "FEWideString.h" + +void FEKeyInterp(FEScript* pScript, unsigned char TrackNum, long tTime, FEObject* pOutObj); + +inline unsigned long BSwap32(unsigned long v) { + return (v >> 24) | (v << 24) | ((v & 0xFF00) << 8) | ((v >> 8) & 0xFF00); +} + +inline unsigned short BSwap16(unsigned short v) { + return static_cast((v >> 8) | (v << 8)); +} + +FEPackageReader::FEPackageReader() { + Reset(); +} + +FEPackageReader::~FEPackageReader() { +} + +void FEPackageReader::Reset() { + pChunk = nullptr; + pPack = nullptr; + pObj = nullptr; + pParent = nullptr; + pLastParent = nullptr; + TypeSizeCount = 0; + TypeSizeList = nullptr; + ObjectCount = 0; + ResourceCount = 0; + CurButton = 0; + ButtonCount = 0; +} + +FEChunk* FEPackageReader::FindChild(FEChunk* pChunk, unsigned long ID) { + FEChunk* pLast = pChunk->GetLastChunk(); + FEChunk* pCur = pChunk->GetFirstChunk(); + while (pCur->GetID() != ID && pCur != pLast) { + pCur = pCur->GetNext(); + } + if (pCur->GetID() == ID) { + return pCur; + } + return nullptr; +} + +unsigned long FEPackageReader::GetTypeSize(unsigned long TypeID) { + for (unsigned long i = 0; i < TypeSizeCount; i++) { + if (BSwap32(TypeSizeList[i].ID) == TypeID) { + return BSwap32(TypeSizeList[i].Size); + } + } + return 0; +} + +bool FEPackageReader::ReadTypeSizes() { + FEChunk* pChild = FindChild(pChunk, 0x53707954); + if (!pChild) { + return true; + } + unsigned long Size = pChild->GetSize(); + TypeSizeList = reinterpret_cast(pChild->GetData()); + TypeSizeCount = BSwap32(Size) >> 3; + return true; +} + +bool FEPackageReader::ReadHeaderChunk() { + if (pChunk->GetID() != 0xE76E4546) { + return false; + } + FEChunk* pHeadChunk = pChunk->GetFirstChunk(); + if (pHeadChunk->GetID() != 0x64486B50) { + return false; + } + unsigned long* pData = reinterpret_cast(pHeadChunk->GetData()); + if (BSwap32(pData[0]) <= 0x1FFFF) { + return false; + } + char* pShortName = reinterpret_cast(pChunk) + 0x28; + FEPackage* pNewPack = FENG_NEW FEPackage(); + pPack = pNewPack; + pNewPack->pCurrentButton = nullptr; + ResourceCount = BSwap32(pData[2]); + ObjectCount = BSwap32(pData[3]); + unsigned long NameLen = BSwap32(pData[4]); + char* pFileName = pShortName + NameLen; + pPack->SetName(pShortName); + pPack->SetFilename(pFileName); + return true; +} + +bool FEPackageReader::ReadPackageResponseChunk() { + FEChunk* pChild = FindChild(pChunk, 0x52676B50); + if (pChild) { + ReadMessageResponseTags(reinterpret_cast(pChild->GetData()), BSwap32(pChild->GetSize()), true); + } + return true; +} + +FEPackage* FEPackageReader::Load(const void* pDataPtr, FEGameInterface* pInt, FEngine* pEng, + bool bLoadObjNames, bool bLoadScrNames, bool bLibrary) { + FEPackage* pResult = nullptr; + Reset(); + bIsLibrary = bLibrary; + bLoadObjectNames = bLoadObjNames; + bLoadScriptNames = bLoadScrNames; + pChunk = reinterpret_cast(const_cast(pDataPtr)); + pInterface = pInt; + pEngine = pEng; + if (!ReadHeaderChunk() || + !ReadTypeSizes() || + !ReadReferencedPackagesChunk() || + !ReadLibraryRefsChunk() || + !ReadResourceChunk() || + !ReadObjectChunk() || + !ReadPackageResponseChunk() || + !ReadMessageTargetListChunk()) { + goto Error; + } + pPack->bIsLibrary = bIsLibrary; + if (pPack->Startup(pInterface)) { + pResult = pPack; + pPack = nullptr; + } +Error: + if (pPack) { + delete pPack; + } + pPack = nullptr; + return pResult; +} + +FEObject* FEPackageReader::CreateObject(unsigned long ObjectType) { + FEObject* pObject; + switch (ObjectType) { + case FE_String: + pObject = FENG_NEW FEString(); + pObject->Type = static_cast(ObjectType); + break; + case FE_List: + pObject = FENG_NEW FEListBox(); + break; + case FE_CodeList: + pObject = FENG_NEW FECodeListBox(); + static_cast(pObject)->mpobRenderer = pInterface; + break; + case FE_Group: + pObject = FENG_NEW FEGroup(); + pObject->Type = static_cast(ObjectType); + break; + case FE_Image: + pObject = FENG_NEW FEImage(); + pObject->Type = static_cast(ObjectType); + break; + case FE_Movie: + pObject = FENG_NEW FEMovie(); + pObject->Type = static_cast(ObjectType); + break; + case FE_ColoredImage: + pObject = FENG_NEW FEColoredImage(); + pObject->Type = static_cast(ObjectType); + break; + case FE_AnimImage: + pObject = FENG_NEW FEAnimImage(); + pObject->Type = static_cast(ObjectType); + break; + case FE_SimpleImage: + pObject = FENG_NEW FESimpleImage(); + pObject->Type = static_cast(ObjectType); + break; + case FE_MultiImage: + pObject = FENG_NEW FEMultiImage(); + pObject->Type = static_cast(ObjectType); + break; + case FE_None: + return nullptr; + default: + pObject = FENG_NEW FEObject(); + break; + } + pObject->Type = static_cast(ObjectType); + unsigned long Size = GetTypeSize(ObjectType); + pObject->SetDataSize(Size); + return pObject; +} + +void FEPackageReader::ProcessImageTag(FETag* pTag) { + FEImage* pImage = static_cast(pObj); + if (pTag->GetID() != 0x6649) { + return; + } + pImage->ImageFlags = pTag->Getu32(0); +} + +bool FEPackageReader::FindReferencedObject(unsigned long ObjGUID, FEObject** ppRefObj, FEPackage** ppRefPack) { + *ppRefObj = nullptr; + *ppRefPack = nullptr; + FELibraryRef* pRef = pPack->FindLibraryReference(ObjGUID); + if (!pRef) { + return false; + } + *ppRefPack = pEngine->FindLibraryPackage(pRef->PackNameHash); + if (!*ppRefPack) { + return false; + } + *ppRefObj = (*ppRefPack)->FindObjectByGUID(pRef->LibGUID); + return *ppRefObj != nullptr; +} + +bool FEPackageReader::ReadReferencedPackagesChunk() { + FEChunk* pRefChunk = FindChild(pChunk, 0x4C62694C); + if (!pRefChunk) { + return false; + } + + char* pStrings = pRefChunk->GetData(); + unsigned long* pData = reinterpret_cast(pStrings); + unsigned long NumRefs = BSwap32(pData[0]); + FEList& LibList = pPack->LibrariesUsed; + unsigned long* pRefs = pData + 1; + for (unsigned long i = 0; i < NumRefs; i++) { + FENode* pNode = new FENode(); + unsigned long Offset = BSwap32(pRefs[i]); + pNode->SetName(pStrings + Offset); + LibList.AddNode(LibList.GetTail(), pNode); + } + FENode* pLibNode = static_cast(LibList.GetHead()); + while (pLibNode) { + FEPackage* pLibPack = pEngine->FindLibraryPackage(pLibNode->GetNameHash()); + if (!pLibPack) { + bool bDeleteBlock; + unsigned char* pBlockStart; + unsigned char* pPackData = pInterface->GetPackageData(pLibNode->GetName(), &pBlockStart, bDeleteBlock); + if (!pPackData) { + return false; + } + pLibPack = pEngine->LoadPackage(pPackData, true); + if (bDeleteBlock && pBlockStart) { + delete[] pBlockStart; + } + if (!pLibPack) { + return false; + } + pInterface->PackageWasLoaded(pLibPack); + pLibPack->SetPriority(1); + pEngine->AddToLibraryList(pLibPack); + } else { + pLibPack->SetPriority(pLibPack->GetPriority() + 1); + } + pLibNode = pLibNode->GetNext(); + } + return true; +} + +bool FEPackageReader::ReadLibraryRefsChunk() { + FEChunk* pChild = FindChild(pChunk, 0x5262694C); + if (!pChild) { + return pPack->LibrariesUsed.GetNumElements() == 0; + } + unsigned long ChunkSize = BSwap32(pChild->GetSize()); + unsigned long Count = ChunkSize / sizeof(FELibraryRef); + if (ChunkSize != Count * sizeof(FELibraryRef)) { + return false; + } + unsigned long* pData = reinterpret_cast(pChild->GetData()); + pPack->SetNumLibraryRefs(Count); + unsigned long i = 0; + FELibraryRef* pRef = pPack->pLibRefs; + for (; i < Count; i++) { + pRef[i].ObjGUID = BSwap32(pData[0]); + pRef[i].PackNameHash = BSwap32(pData[1]); + pRef[i].LibGUID = BSwap32(pData[2]); + pData += 3; + } + return true; +} + +bool FEPackageReader::ReadResourceChunk() { + FEChunk* pChild = FindChild(pChunk, 0xcc736552); + if (!pChild) { + return false; + } + FEChunk* pNameChunk = pChild->GetFirstChunk(); + if (pNameChunk->GetID() != 0x6d4e7352) { + return false; + } + FEChunk* pResReqChunk = pNameChunk->GetNext(); + if (pResReqChunk->GetID() != 0x71527352) { + return false; + } + unsigned long* pData = reinterpret_cast(pResReqChunk->GetData()) + 1; + unsigned long NumRequests = BSwap32(*reinterpret_cast(pResReqChunk->GetData())); + pPack->NumRequests = NumRequests; + if (NumRequests != 0) { + pPack->pRequests = static_cast(FEngMalloc(NumRequests * sizeof(FEResourceRequest), nullptr, 0)); + pPack->pResourceNames = static_cast(FEngMalloc(BSwap32(pNameChunk->GetSize()), nullptr, 0)); + char* nameData = pNameChunk->GetData(); + for (unsigned long Index = 0; Index < NumRequests; Index++) { + pPack->pRequests[Index].ID = BSwap32(pData[0]); + pPack->pRequests[Index].pFilename = reinterpret_cast(BSwap32(pData[1])); + pPack->pRequests[Index].Type = BSwap32(pData[2]); + pPack->pRequests[Index].Flags = BSwap32(pData[3]); + pPack->pRequests[Index].Handle = BSwap32(pData[4]); + pPack->pRequests[Index].UserParam = BSwap32(pData[5]); + pData += 6; + } + FEngMemCpy(pPack->pResourceNames, nameData, BSwap32(pNameChunk->GetSize())); + for (unsigned long i = 0; i < NumRequests; i++) { + pPack->pRequests[i].pFilename = pPack->pRequests[i].pFilename + reinterpret_cast(pPack->pResourceNames); + } + } + return true; +} + +void FEPackageReader::ProcessMultiImageTag(FETag* pTag) { + FEMultiImage* pImage = static_cast(pObj); + unsigned short tagID = pTag->GetID(); + switch (tagID) { + case 0x314d: + pImage->hTexture[0] = pTag->Getu32(0); + break; + case 0x324d: + pImage->hTexture[1] = pTag->Getu32(0); + break; + case 0x334d: + pImage->hTexture[2] = pTag->Getu32(0); + break; + case 0x614d: + pImage->TextureFlags[0] = pTag->Getu32(0); + break; + case 0x624d: + pImage->TextureFlags[1] = pTag->Getu32(0); + break; + case 0x634d: + pImage->TextureFlags[2] = pTag->Getu32(0); + break; + } +} + +void FEPackageReader::ProcessStringTag(FETag* pTag) { + FEString* pString = static_cast(pObj); + unsigned short tagID = pTag->GetID(); + switch (tagID) { + case 0x6253: + pString->string.SetLength(pTag->Getu32(0)); + break; + case 0x7453: + pString->string = reinterpret_cast(pTag->Data()); + { + short* ptr = pString->string.mpsString; + while (*ptr) { + short s = *ptr; + *ptr = static_cast(((s >> 8) & 0xFF) | (s << 8)); + ptr++; + } + } + break; + case 0x6a53: + pString->Format = pTag->Getu32(0); + break; + case 0x6c53: + pString->Leading = pTag->Getu32(0); + break; + case 0x7753: + pString->MaxWidth = pTag->Getu32(0); + break; + case 0x4c53: + if (bLoadObjectNames) { + pString->SetLabel(reinterpret_cast(pTag->Data())); + } + break; + case 0x4853: + pString->SetLabelHash(pTag->Getu32(0)); + break; + } +} + +#ifndef EA_BUILD_A124 +static void FEPackageReaderInitializeCodeList(FECodeListBox* pList, FETag* pTag) { + pList->Initialize(pTag->Getu32(0), pTag->Getu32(1)); +} + +static void FEPackageReaderSetCodeListViewDimensions(FECodeListBox* pList, FETag* pTag) { + FEPoint pt; + pt.h = pTag->Getf32(0); + pt.v = pTag->Getf32(1); + pList->mstViewDimensions.h = pt.h; + pList->mstViewDimensions.v = pt.v; +} + +static void FEPackageReaderAllocateCodeListStrings(FECodeListBox* pList, FETag* pTag) { + pList->AllocateStrings(pTag->Getu32(0), pTag->Getu32(1)); +} + +static void FEPackageReaderSetCodeListFlags(FECodeListBox* pList, FETag* pTag) { + pList->mulFlags &= 1; + pList->mulFlags |= pTag->Getu32(0) & 0xFFFFFFFE; +} + +static void FEPackageReaderSetCodeListJustification(FECodeListBox* pList, FETag* pTag) { + unsigned long justification = pTag->Getu32(0); + unsigned long num_visible_columns = pList->mulNumVisibleColumns; + unsigned long num_visible_rows = pList->mulNumVisibleRows; + pList->SetCellJustification(0, 0, justification, num_visible_columns, num_visible_rows); +} + +static void FEPackageReaderSetCodeListColor(FECodeListBox* pList, FETag* pTag) { + unsigned long color = pTag->Getu32(0); + unsigned long num_visible_columns = pList->mulNumVisibleColumns; + unsigned long num_visible_rows = pList->mulNumVisibleRows; + pList->SetCellColor(0, 0, color, num_visible_columns, num_visible_rows); +} + +static void FEPackageReaderSetCodeListScale(FECodeListBox* pList, FETag* pTag) { + FEPoint scale; + unsigned long num_visible_columns = pList->mulNumVisibleColumns; + unsigned long num_visible_rows = pList->mulNumVisibleRows; + scale.h = pTag->Getf32(0); + scale.v = pTag->Getf32(1); + pList->SetCellScale(0, 0, scale, num_visible_columns, num_visible_rows); +} +#endif + +void FEPackageReader::ProcessCodeListBoxTag(FETag* pTag) { +#ifdef EA_BUILD_A124 + (void)pTag; + return; +#else + FECodeListBox* pList = static_cast(pObj); + unsigned short tagID = pTag->GetID(); + if (tagID == 0x444c) { + FEPackageReaderInitializeCodeList(pList, pTag); + return; + } + + if (tagID == 0x764c) { + FEPackageReaderSetCodeListViewDimensions(pList, pTag); + return; + } + + if (tagID == 0x4953) { + FEPackageReaderAllocateCodeListStrings(pList, pTag); + return; + } + + if (tagID == 0x744c) { + FEPackageReaderSetCodeListFlags(pList, pTag); + return; + } + + if (tagID == 0x6a4c) { + FEPackageReaderSetCodeListJustification(pList, pTag); + return; + } + + if (tagID == 0x6343) { + FEPackageReaderSetCodeListColor(pList, pTag); + return; + } + + if (tagID == 0x7343) { + FEPackageReaderSetCodeListScale(pList, pTag); + return; + } +#endif +} + +bool FEPackageReader::ReadMessageResponseTags(FETag* pTag, unsigned long Length, bool bPackage) { + FETag* pEnd = reinterpret_cast(reinterpret_cast(pTag) + Length); + FEMessageResponse* pMsgResp = nullptr; + FEResponse* pResp = nullptr; + int CurResponse = -1; + while (pTag < pEnd) { + unsigned short tagID = pTag->GetID(); + switch (tagID) { + case 0x694d: { + unsigned long MsgID = pTag->Getu32(0); + pMsgResp = nullptr; + if (!bPackage && bIsReference) { + pMsgResp = pObj->FindResponse(MsgID); + } + if (!pMsgResp) { + pMsgResp = new FEMessageResponse(); + pMsgResp->SetMsgID(MsgID); + if (bPackage) { + pPack->Responses.AddNode(pPack->Responses.GetTail(), pMsgResp); + } else { + pObj->Responses.AddNode(pObj->Responses.GetTail(), pMsgResp); + } + } else { + pMsgResp->PurgeResponses(); + } + CurResponse = -1; + break; + } + case 0x434d: + pMsgResp->SetCount(pTag->Getu32(0)); + break; + case 0x6952: + CurResponse++; + pResp = pMsgResp->GetResponse(CurResponse); + pResp->SetID(pTag->Getu32(0)); + break; + case 0x7552: + pResp->ResponseParam = pTag->Getu32(0); + break; + case 0x7352: + pResp->SetParam(reinterpret_cast(pTag->Data())); + break; + case 0x7452: + pResp->ResponseTarget = pTag->Getu32(0); + break; + } + pTag = reinterpret_cast(reinterpret_cast(pTag) + 4 + pTag->GetSize()); + } + return true; +} + +bool FEPackageReader::ReadMessageTargetListChunk() { + FEChunk* pTargetsChunk = FindChild(pChunk, 0x67726154); + if (pTargetsChunk) { + FETag* pTag = reinterpret_cast(pTargetsChunk->GetData()); + FETag* pLast = reinterpret_cast(reinterpret_cast(pTag) + BSwap32(pTargetsChunk->GetSize())); + unsigned long CurMsgTarg = 0; + while (pTag < pLast) { + unsigned short tagID = pTag->GetID(); + switch (tagID) { + case 0x6354: { + unsigned long NumMsgs = pTag->Getu32(0); + pPack->NumMsgTargets = NumMsgs; + pPack->pMsgTargets = FENG_NEW FEMsgTargetList[NumMsgs]; + break; + } + case 0x744d: { + pPack->pMsgTargets[CurMsgTarg].MsgID = pTag->Getu32(0); + unsigned long NumTargets = (pTag->GetSize() >> 2) - 1; + pPack->pMsgTargets[CurMsgTarg].Allocate(NumTargets); + unsigned long* pData = reinterpret_cast(reinterpret_cast(pTag) + 8); + for (unsigned long i = 0; i < NumTargets; i++) { + FEObject* pTarg = pPack->FindObjectByGUID(BSwap32(pData[i])); + pPack->pMsgTargets[CurMsgTarg].AppendTarget(pTarg); + } + CurMsgTarg++; + break; + } + } + pTag = reinterpret_cast(reinterpret_cast(pTag) + (pTag->GetSize() + 4)); + } + } + return true; +} + +void FEPackageReader::ProcessListBoxTag(FETag* pTag) { +#ifdef EA_BUILD_A124 + (void)pTag; + return; +#else + FEListBox* pList = static_cast(pObj); + int idx; + unsigned short tagID = pTag->GetID(); + switch (tagID) { + case 0x644c: + pList->SetNumColumns(pTag->Getu32(0)); + pList->SetNumRows(pTag->Getu32(1)); + CurListCell = 0xFFFFFFFF; + CurListRow = 0xFFFFFFFF; + CurListCol = 0xFFFFFFFF; + { + unsigned long* pCurrentColumn = &pList->mulCurrentColumn; + *pCurrentColumn = ClampIndex(0, pList->mulNumColumns); + } + { + unsigned long* pCurrentRow = &pList->mulCurrentRow; + unsigned long row = ClampIndex(0, pList->mulNumRows); + *pCurrentRow = row; + } + return; + case 0x774c: + pList->SetAutoWrap(pTag->Getu32(0) != 0); + return; + case 0x764c: { + FEPoint pt; + pt.h = pTag->Getf32(0); + pt.v = pTag->Getf32(1); + pList->mstViewDimensions = pt; + return; + } + case 0x734c: { + FEPoint pt; + pt.h = pTag->Getf32(0); + pt.v = pTag->Getf32(1); + pList->mstSelectionSpeed = pt; + return; + } + case 0x634c: + CurListCol++; + { + FEListEntryData* pRowColData = pList->GetPColumnData(CurListCol); + *reinterpret_cast(&pRowColData->fValue) = pTag->Getu32(0); + pRowColData->ulJustification = pTag->Getu32(1); + } + return; + case 0x724c: + CurListRow++; + { + FEListEntryData* pRowColData = pList->GetPRowData(CurListRow); + *reinterpret_cast(&pRowColData->fValue) = pTag->Getu32(0); + pRowColData->ulJustification = pTag->Getu32(1); + } + return; + case 0x6343: { + CurListCell++; + if (CurListCell != 0) { + pList->IncrementCellByColumn(); + } + FEColor color(pTag->Getu32(0)); + FEColor* pColor = &color; + FEListBoxCell* pCell = pList->GetPCellData(pList->mulCurrentColumn, pList->mulCurrentRow); + pCell->ulColor = static_cast(*pColor); + return; + } + case 0x7343: { + FEPoint pt; + pt.h = pTag->Getf32(0); + pt.v = pTag->Getf32(1); + FEListBoxCell* pCell = pList->GetPCellData(pList->mulCurrentColumn, pList->mulCurrentRow); + pCell->stScale = pt; + return; + } + case 0x7243: { + unsigned long zero = 0; + unsigned long rawVal = pTag->Getu32(0); + FEListBoxCell* pCell = pList->GetPCellData(pList->mulCurrentColumn, pList->mulCurrentRow); + pCell->stResource.Handle = zero; + pCell->stResource.UserParam = zero; + pCell->stResource.ResourceIndex = rawVal; + return; + } + case 0x5443: + pList->SetCellType(pTag->Getu32(0)); + return; + case 0x7443: + pList->SetCellString(reinterpret_cast(pTag->Data())); + return; + case 0x6943: { + unsigned long c0 = pTag->Getu32(0); + unsigned long c1 = pTag->Getu32(1); + unsigned long c2 = pTag->Getu32(2); + unsigned long c3 = pTag->Getu32(3); + FERect rect; + *reinterpret_cast(&rect.left) = c0; + *reinterpret_cast(&rect.top) = c1; + *reinterpret_cast(&rect.right) = c2; + *reinterpret_cast(&rect.bottom) = c3; + FEListBoxCell* pCell = pList->GetPCellData(pList->mulCurrentColumn, pList->mulCurrentRow); + pCell->SetUV() = rect; + return; + } + default: + return; + } +#endif +} + +bool FEPackageReader::ReadObjectChunk() { + FEChunk* pObjList = FindChild(pChunk, 0xcc6a624f); + if (!pObjList) { + return true; + } + + FEChunk* pLast = pObjList->GetLastChunk(); + FEChunk* pObjChunk = pObjList->GetFirstChunk(); + + if (!pObjChunk || !pLast) { + return true; + } + + while (true) { + unsigned long chunkID = pObjChunk->GetID(); + if (chunkID != 0xea624f46) { + if (pObjChunk >= pLast) { + return true; + } + if (chunkID == 0x6e747542) { + unsigned long count = BSwap32(*reinterpret_cast(pObjChunk->GetData())); + ButtonCount = count; + pPack->ButtonMap.SetCount(count); + } + pObjChunk = pObjChunk->GetNext(); + continue; + } + if (pObjChunk >= pLast) { + return true; + } + do { + + FEChunk* pLastSub = pObjChunk->GetLastChunk(); + FEChunk* pSubChunk = pObjChunk->GetFirstChunk(); + pObj = nullptr; + pParent = nullptr; + + while (pSubChunk < pLastSub) { + unsigned long subID = pSubChunk->GetID(); + switch (subID) { + case 0x446a624f: + if (!ReadObjectTags(reinterpret_cast(pSubChunk->GetData()), BSwap32(pSubChunk->GetSize()))) { + return false; + } + break; + case 0x70726353: + ReadScriptTags(reinterpret_cast(pSubChunk->GetData()), BSwap32(pSubChunk->GetSize())); + break; + case 0x5267734d: + ReadMessageResponseTags(reinterpret_cast(pSubChunk->GetData()), BSwap32(pSubChunk->GetSize()), false); + break; + } + pSubChunk = pSubChunk->GetNext(); + } + + if (pObj) { + if (pObj->Type == FE_List) { + static_cast(pObj)->RecalculateCummulative(); + } else if (pObj->Type == FE_CodeList) { + static_cast(pObj)->FillAllCells(); + } + + FEScript* pScript = pObj->GetFirstScript(); + while (pScript) { + if (pScript->pChainTo) { + pScript->pChainTo = pObj->FindScript(reinterpret_cast(pScript->pChainTo)); + } + pScript = pScript->GetNext(); + } + + FEScript* pDefaultScript = pObj->FindScript(0x1744b3); + pObj->SetCurrentScript(pDefaultScript); + pDefaultScript->CurTime = 0; + + if (!bIsLibrary) { + for (unsigned char i = 0; i < pDefaultScript->TrackCount; i++) { + FEKeyInterp(pDefaultScript, i, 0, pObj); + } + } + + if (pParent) { + static_cast(pParent)->AddObject(pObj); + } else { + pPack->AddObject(pObj); + } + } + + pObjChunk = pObjChunk->GetNext(); + } while (pObjChunk < pLast); + return true; + } +} + +bool FEPackageReader::ReadObjectTags(FETag* pTag, unsigned long Length) { + FETag* pEnd = reinterpret_cast(reinterpret_cast(pTag) + Length); + bIsReference = false; + + while (pTag < pEnd) { + unsigned short tagID = pTag->GetID(); + switch (tagID) { + case 0x744f: + pObj = CreateObject(pTag->Getu32(0)); + break; + case 0x6e4f: + if (bLoadObjectNames) { + pObj->SetName(reinterpret_cast(pTag->Data())); + } + break; + case 0x684f: + pObj->NameHash = pTag->Getu32(0); + break; + case 0x504f: { + pObj->GUID = pTag->Getu32(0); + pObj->NameHash = pTag->Getu32(1); + pObj->Flags = pTag->Getu32(2); + pObj->ResourceIndex = static_cast(pTag->Getu32(3)); + + if (pObj->Flags & 0x100000) { + if (!FindReferencedObject(pObj->GUID, &pRefObj, &pRefPack)) { + if (pObj) { + delete pObj; + } + pObj = nullptr; + return false; + } + bIsReference = true; + pObj->Flags |= pRefObj->Flags; + + if (pObj->ResourceIndex == 0xFFFF) { + pObj->Handle = pRefObj->Handle; + pObj->UserParam = pRefObj->UserParam; + } + + FEObject* pClone; + if (pRefObj->Type != FE_Group) { + pClone = pRefObj->Clone(true); + } else { + pClone = new FEGroup(static_cast(*pRefObj), false, true); + } + + pClone->GUID = pObj->GUID; + pClone->NameHash = pObj->NameHash; + pClone->Flags = pObj->Flags; + pClone->ResourceIndex = pObj->ResourceIndex; + pClone->Handle = pObj->Handle; + pClone->UserParam = pObj->UserParam; + + if (pObj) { + delete pObj; + } + pObj = pClone; + + FEScript* pScript = pObj->GetFirstScript(); + while (pScript) { + if (pScript->pChainTo) { + pScript->pChainTo = reinterpret_cast(pScript->pChainTo->ID); + } + pScript = pScript->GetNext(); + } + } + + if (pObj->Flags & 0x10000000) { + pPack->ButtonMap.pList[CurButton] = pObj; + CurButton++; + } + break; + } + case 0x4150: + if (!pLastParent || pLastParent->GUID != pTag->Getu32(0)) { + pLastParent = static_cast(pPack->FindObjectByGUID(pTag->Getu32(0))); + } + pParent = pLastParent; + break; + case 0x4153: { + unsigned long count = pTag->GetSize() >> 2; + for (unsigned long i = 0; i < count; i++) { + reinterpret_cast(pObj->pData)[i] = pTag->Getu32(i); + } + break; + } + default: + if (pObj) { + switch (pObj->Type) { + case FE_String: + ProcessStringTag(pTag); + break; + case FE_List: + ProcessListBoxTag(pTag); + break; + case FE_CodeList: + ProcessCodeListBoxTag(pTag); + break; + case FE_Image: + case FE_ColoredImage: + case FE_AnimImage: + ProcessImageTag(pTag); + break; + case FE_MultiImage: + ProcessImageTag(pTag); + ProcessMultiImageTag(pTag); + break; + } + } + break; + } + unsigned long nextSize = pTag->GetSize() + 4; + pTag = reinterpret_cast(reinterpret_cast(pTag) + nextSize); + } + return true; +} + +bool FEPackageReader::ReadScriptTags(FETag* pTag, unsigned long Length) { + FETag* pEnd = reinterpret_cast(reinterpret_cast(pTag) + Length); + unsigned long CurTrack = static_cast(-1); + FEScript* pScript = nullptr; + FEKeyTrack* pTrack = nullptr; + int RunningTrackOffset = 0; + + while (pTag < pEnd) { + unsigned short tagID = pTag->GetID(); + switch (tagID) { + case 0x6e53: { + pScript = new FEScript(); + pScript->CurTime = 0; + if (bLoadScriptNames) { + pScript->SetName(reinterpret_cast(pTag->Data())); + } + CurTrack = static_cast(-1); + RunningTrackOffset = 0; + break; + } + case 0x6853: { + if (!pScript) { + pScript = new FEScript(); + pScript->CurTime = 0; + CurTrack = static_cast(-1); + RunningTrackOffset = 0; + } + pScript->ID = pTag->Getu32(0); + pScript->Length = static_cast(pTag->Getu32(1)); + pScript->Flags = pTag->Getu32(2); + pScript->SetTrackCount(static_cast(pTag->Getu32(3))); + break; + } + case 0x6353: { + pScript->pChainTo = reinterpret_cast(pTag->Getu32(0)); + break; + } + case 0x4953: { + pTrack = nullptr; + pScript = pObj->FindScript(pTag->Getu32(0)); + break; + } + case 0x6c53: { + pScript->Length = static_cast(pTag->Getu32(0)); + break; + } + case 0x6653: { + if (pScript) { + pScript->Flags = pTag->Getu32(0); + } + break; + } + case 0x4946: { + CurTrack++; + pTrack = &pScript->pTracks[CurTrack]; + pTrack->ParamType = pTag->Data()[0]; + pTrack->ParamSize = pTag->Data()[1]; + pTrack->InterpType = pTag->Data()[2]; + pTrack->InterpAction = pTag->Data()[3]; + pTrack->Length = static_cast(pTag->Getu32(1)); + pTrack->LongOffset = RunningTrackOffset; + RunningTrackOffset += pTrack->ParamSize >> 2; + break; + } + case 0x6f54: { + pTrack->LongOffset = pTag->Getu16(0) >> 8; + break; + } + case 0x6954: { + if (pScript) { + unsigned short Index = BSwap16(pTag->Getu16(0)); + pTrack = pScript->FindTrack(static_cast(Index)); + if (!pTrack) { + unsigned long trackCount = pScript->TrackCount; + FEKeyTrack* pNewArray = new FEKeyTrack[trackCount + 1]; + FETypeNode* pTypeNode = pEngine->GetTypeLib().FindType(pObj->Type); + FEFieldNode* pField = pTypeNode->GetField(static_cast(Index)); + unsigned long SrcIndex = 0; + FEKeyTrack* pSrcTrack = pScript->pTracks; + for (unsigned long DestIndex = 0; DestIndex <= pScript->TrackCount; DestIndex++) { + if (pSrcTrack && SrcIndex < pScript->TrackCount) { + if (pField) { + int srcLongOffset = pSrcTrack[SrcIndex].LongOffset; + int fieldOffset = static_cast(pField->GetOffset()); + if (fieldOffset < 0) { + fieldOffset += 3; + } + if (srcLongOffset >= (fieldOffset >> 2)) { + goto insert_track; + } + } + pNewArray[DestIndex] = pSrcTrack[SrcIndex]; + SrcIndex++; + continue; + } + insert_track: + pNewArray[DestIndex].ParamType = static_cast(pField->GetType()); + pNewArray[DestIndex].InterpType = 1; + pNewArray[DestIndex].InterpAction = 0; + pNewArray[DestIndex].ParamSize = static_cast(pField->GetSize()); + pTrack = &pNewArray[DestIndex]; + pTrack->Length = pScript->Length; + pTrack->LongOffset = static_cast(pField->GetOffset() >> 2); + pField = nullptr; + } + delete[] pScript->pTracks; + pScript->pTracks = pNewArray; + pScript->TrackCount++; + } + } + break; + } + case 0x7454: { + if (pScript) { + pTrack->InterpType = static_cast(pTag->Getu16(0) >> 8); + } + break; + } + case 0x6154: { + if (pScript) { + pTrack->InterpAction = static_cast(pTag->Getu16(0) >> 8); + } + break; + } + case 0x6254: { + { + unsigned long KeyLongs = pTrack->ParamSize >> 2; + pTrack->BaseKey.tTime = static_cast(pTag->Getu32(0)); + { + unsigned long i = 0; + if (KeyLongs != 0) { + do { + reinterpret_cast(&pTrack->BaseKey.Val)[i] = pTag->Getu32(i + 1); + i++; + } while (i < KeyLongs); + } + } + } + break; + } + case 0x644b: { + { + unsigned long CurKey = 0; + unsigned long KeySize = pTrack->ParamSize + 4; + unsigned long Count = (KeySize >> 2) - 1; + unsigned long NumKeys = pTag->GetSize() / KeySize; + unsigned char* pKeyData = pTag->Data(); + FEKeyNode* pKey; + unsigned long* pSrc; + unsigned long Index; + + if (pTrack->IsReference()) { + pTrack->DeltaKeys.ReferenceList(nullptr); + } + + do { + if (CurKey != 0) { + pKey = new FEKeyNode(); + } else { + pKey = &pTrack->BaseKey; + } + pSrc = reinterpret_cast(pKeyData); + pKey->tTime = static_cast(BSwap32(*pSrc)); + Index = 0; + if (Count != 0) { + do { + reinterpret_cast(&pKey->Val)[Index] = BSwap32(pSrc[Index + 1]); + Index++; + } while (Index < Count); + } + if (CurKey != 0) { + pTrack->DeltaKeys.AddTail(pKey); + } + CurKey++; + pKeyData += KeySize; + } while (CurKey < NumKeys); + } + break; + } + case 0x5645: { + { + unsigned long NumEvents = pTag->GetSize() / sizeof(FEEvent); + pScript->Events.SetCount(static_cast(NumEvents)); + FEEvent* pEvent = &pScript->Events[0]; + unsigned long* pData = reinterpret_cast(pTag->Data()); + do { + NumEvents--; + pEvent->EventID = BSwap32(*pData); + pEvent->Target = BSwap32(pData[1]); + pEvent->tTime = BSwap32(pData[2]); + pData += 3; + pEvent++; + } while (NumEvents != 0); + } + break; + } + } + pTag = reinterpret_cast(reinterpret_cast(pTag) + (pTag->GetSize() + 4)); + } + + if (!bIsReference) { + pObj->Scripts.AddTail(pScript); + } + if (pScript->ID == 0x1744b3) { + pObj->pCurrentScript = pScript; + } + return true; +} diff --git a/src/Speed/Indep/Src/FEng/FEPackageReader.h b/src/Speed/Indep/Src/FEng/FEPackageReader.h index fa48d2982..92d12e097 100644 --- a/src/Speed/Indep/Src/FEng/FEPackageReader.h +++ b/src/Speed/Indep/Src/FEng/FEPackageReader.h @@ -5,6 +5,72 @@ #pragma once #endif +struct FEPackage; +struct FEChunk; +struct FETag; +struct FEObject; +struct FEGroup; +struct FEGameInterface; +struct FEngine; +struct FEScript; +// total size: 0x8 +struct FETypeSize { + unsigned long ID; // offset 0x0, size 0x4 + unsigned long Size; // offset 0x4, size 0x4 +}; + +// total size: 0x58 +struct FEPackageReader { + FEPackage* pPack; // offset 0x0, size 0x4 + FEChunk* pChunk; // offset 0x4, size 0x4 + bool bIsLibrary; // offset 0x8, size 0x1 + bool bLoadObjectNames; // offset 0xC, size 0x1 + bool bLoadScriptNames; // offset 0x10, size 0x1 + FEObject* pObj; // offset 0x14, size 0x4 + FEGroup* pLastParent; // offset 0x18, size 0x4 + FEGroup* pParent; // offset 0x1C, size 0x4 + bool bIsReference; // offset 0x20, size 0x1 + FEObject* pRefObj; // offset 0x24, size 0x4 + FEPackage* pRefPack; // offset 0x28, size 0x4 + unsigned long CurListCol; // offset 0x2C, size 0x4 + unsigned long CurListRow; // offset 0x30, size 0x4 + unsigned long CurListCell; // offset 0x34, size 0x4 + unsigned long TypeSizeCount; // offset 0x38, size 0x4 + FETypeSize* TypeSizeList; // offset 0x3C, size 0x4 + FEGameInterface* pInterface; // offset 0x40, size 0x4 + FEngine* pEngine; // offset 0x44, size 0x4 + unsigned long ResourceCount; // offset 0x48, size 0x4 + unsigned long ObjectCount; // offset 0x4C, size 0x4 + unsigned long ButtonCount; // offset 0x50, size 0x4 + unsigned long CurButton; // offset 0x54, size 0x4 + + FEPackageReader(); + ~FEPackageReader(); + void Reset(); + FEPackage* Load(const void* pDataPtr, FEGameInterface* pInt, FEngine* pEng, bool bLoadObjNames, bool bLoadScrNames, bool bLibrary); + FEChunk* FindChild(FEChunk* pChunk, unsigned long ID); + unsigned long GetTypeSize(unsigned long TypeID); + bool ReadTypeSizes(); + bool ReadHeaderChunk(); + bool ReadReferencedPackagesChunk(); + bool ReadLibraryRefsChunk(); + bool ReadResourceChunk(); + bool ReadPackageResponseChunk(); + bool ReadObjectChunk(); + FEObject* CreateObject(unsigned long ObjectType); + bool ReadObjectTags(FETag* pTag, unsigned long TagSize); + void ProcessStringTag(FETag* pTag); + void ProcessImageTag(FETag* pTag); + void ProcessMultiImageTag(FETag* pTag); + void ProcessListBoxTag(FETag* pTag); + void ProcessCodeListBoxTag(FETag* pTag); + bool ReadScriptTags(FETag* pTag, unsigned long TagSize); + bool ReadMessageResponseTags(FETag* pTag, unsigned long Length, bool bPackage); + bool ReadMessageTargetListChunk(); + bool FindReferencedObject(unsigned long ObjGUID, FEObject** pRefObj, FEPackage** pRefPack); + unsigned long GetTypeSizeFromID(unsigned long TypeID); + void SetupListBoxResource(FETag* pTag, unsigned long ulResHandle, unsigned long ulResParam, unsigned long ulResIndex); +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FERefList.cpp b/src/Speed/Indep/Src/FEng/FERefList.cpp index e69de29bb..d044a9059 100644 --- a/src/Speed/Indep/Src/FEng/FERefList.cpp +++ b/src/Speed/Indep/Src/FEng/FERefList.cpp @@ -0,0 +1,105 @@ +#include "Speed/Indep/Src/FEng/FERefList.h" + +static FEMinNode* const kRemovedNode = reinterpret_cast(0xABADCAFE); + +void FERefList::ReferenceList(FERefList* pList) { + FEMinNode* n; + + if (pList) { + if (!bIsReference) { + while ((n = RemHead()) != nullptr) { + delete n; + } + } + + pRef = pList; + bIsReference = true; + } else { + *reinterpret_cast(reinterpret_cast(this) + 0x4) = pList; + *reinterpret_cast(reinterpret_cast(this) + 0x8) = nullptr; + *reinterpret_cast(this) = reinterpret_cast(pList); + } +} + +void FERefList::AddNode(FEMinNode* insertpoint, FEMinNode* node) { + FEMinNode* n; + + if (!node) { + return; + } + + if (insertpoint) { + n = insertpoint->next; + node->next = n; + if (n) { + n->prev = node; + } + node->prev = insertpoint; + insertpoint->next = node; + } else { + n = head; + node->next = n; + if (n) { + n->prev = node; + } + node->prev = nullptr; + head = node; + } + + if (tail == insertpoint) { + tail = node; + } +} + +FEMinNode* FERefList::RemNode(FEMinNode* node) { + FERefList* pList = this; + FEMinNode* ret = node; + + if (!ret) { + return ret; + } + + if (ret == pList->head) { + pList->head = ret->next; + } + + if (ret == pList->tail) { + pList->tail = ret->prev; + } + + if (ret->prev) { + ret->prev->next = ret->next; + } + + if (ret->next) { + ret->next->prev = ret->prev; + } + + ret->next = kRemovedNode; + ret->prev = kRemovedNode; + return ret; +} + +FEMinNode* FERefList::RemHead() { + FEMinNode* n = head; + + if (n) { + RemNode(n); + } + + return n; +} + +unsigned long FERefList::GetNumElements() { + unsigned long Count = 0; + FEMinNode* pNode = GetHead(); + + while (pNode) { + pNode = pNode->GetNext(); + Count++; + } + + return Count; +} + +// destructor moved to header for inlining diff --git a/src/Speed/Indep/Src/FEng/FERefList.h b/src/Speed/Indep/Src/FEng/FERefList.h index caccbeef1..d8c881e44 100644 --- a/src/Speed/Indep/Src/FEng/FERefList.h +++ b/src/Speed/Indep/Src/FEng/FERefList.h @@ -10,6 +10,41 @@ // total size: 0x10 class FERefList { public: + FERefList() { + head = nullptr; + tail = nullptr; + bIsReference = false; + } + virtual ~FERefList() { + if (!bIsReference) { + Purge(); + } + } + + inline bool IsReference() const { return bIsReference; } + inline FERefList* GetRefSource() { return pRef; } + inline FEMinNode* GetHead() const { return bIsReference ? pRef->GetHead() : head; } + inline FEMinNode* GetTail() const { return bIsReference ? pRef->GetTail() : tail; } + inline bool IsListEmpty() const { return GetHead() == nullptr; } + + void ReferenceList(FERefList* pList); + inline void AddTail(FEMinNode* n) { AddNode(tail, n); } + void AddNode(FEMinNode* insertpoint, FEMinNode* node); + bool IsInList(FEMinNode* node) const; + int ElementNumber(FEMinNode* node); + FEMinNode* RemNode(FEMinNode* node); + FEMinNode* RemHead(); + FEMinNode* RemTail(); + FEMinNode* FindNode(unsigned long ordinalnumber) const; + inline void Purge() { + FEMinNode* cmn = RemHead(); + while (cmn) { + delete cmn; + cmn = RemHead(); + } + } + unsigned long GetNumElements(); + private: bool bIsReference; // offset 0x0, size 0x1 protected: diff --git a/src/Speed/Indep/Src/FEng/FEResourceRequest.h b/src/Speed/Indep/Src/FEng/FEResourceRequest.h new file mode 100644 index 000000000..51787bd94 --- /dev/null +++ b/src/Speed/Indep/Src/FEng/FEResourceRequest.h @@ -0,0 +1,18 @@ +#ifndef FENG_FERESOURCEREQUEST_H +#define FENG_FERESOURCEREQUEST_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +// total size: 0x18 +struct FEResourceRequest { + unsigned long ID; // offset 0x0, size 0x4 + const char* pFilename; // offset 0x4, size 0x4 + unsigned long Type; // offset 0x8, size 0x4 + unsigned long Flags; // offset 0xC, size 0x4 + unsigned long Handle; // offset 0x10, size 0x4 + unsigned long UserParam; // offset 0x14, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/FEng/FEScript.cpp b/src/Speed/Indep/Src/FEng/FEScript.cpp index e69de29bb..b1e39d11a 100644 --- a/src/Speed/Indep/Src/FEng/FEScript.cpp +++ b/src/Speed/Indep/Src/FEng/FEScript.cpp @@ -0,0 +1,127 @@ +#include "Speed/Indep/Src/FEng/FEScript.h" +#include "Speed/Indep/Src/FEng/FESlotPool.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" +#include "Speed/Indep/Src/FEng/ObjectPool.h" + +ObjectPool FEScript::NodePool; + +void* FEScript::operator new(unsigned int) { + FEScript* pNode = NodePool.AllocSingle(); + pNode->Init(); + return pNode; +} + +void FEScript::operator delete(void* pNode) { + FEScript* pDeleteNode = static_cast(pNode); + pDeleteNode->~FEScript(); + NodePool.FreeSingleNoDestroy(pDeleteNode); +} + +extern const unsigned long FETrackOffsets[11] = { + 0x00000000, + 0x00000004, + 0x00000007, + 0x0000000A, + 0x0000000E, + 0x00000011, + 0x00000013, + 0x00000015, + 0x00000019, + 0x0000001D, + 0x00000021, +}; + +void FEScript::Init() { + *reinterpret_cast(reinterpret_cast(this) + 0x2C) = nullptr; + *reinterpret_cast(reinterpret_cast(this) + 0x30) = 0; + *reinterpret_cast(reinterpret_cast(this) + 0x0C) = 0; + *reinterpret_cast(reinterpret_cast(this) + 0x14) = 0; + *reinterpret_cast(reinterpret_cast(this) + 0x18) = nullptr; + *reinterpret_cast(reinterpret_cast(this) + 0x1C) = 0; + *reinterpret_cast(reinterpret_cast(this) + 0x20) = nullptr; +} + +FEKeyTrack* FEScript::FindTrack(FEKeyTrack_Indices TrackIndex) const { + unsigned long i = 0; + unsigned long count = TrackCount; + int offset = static_cast(FETrackOffsets[TrackIndex]); + + if (i < count) { + FEKeyTrack* pBase = pTracks; + do { + FEKeyTrack* pTrack = pBase + i; + + if (pTrack->LongOffset == offset) { + return pTrack; + } + + i++; + } while (i < count); + } + + return nullptr; +} + +void FEScript::SetName(const char* pNewName) { + if (pName) { + delete[] pName; + pName = nullptr; + } + + ID = 0xFFFFFFFF; + if (pNewName) { + int Len = FEngStrLen(pNewName) + 1; + + pName = FENG_NEW char[Len]; + FEngStrCpy(pName, pNewName); + ID = FEHashUpper(pName); + } +} + +FEScript::~FEScript() { + delete[] pTracks; + pTracks = nullptr; + delete[] pName; + pName = nullptr; + delete[] Events.pEvent; + Events.pEvent = nullptr; +} + +void FEScript::SetTrackCount(long Count) { + delete[] pTracks; + TrackCount = Count; + pTracks = nullptr; + if (Count != 0) { + pTracks = FENG_NEW FEKeyTrack[Count]; + } +} + +FEScript::FEScript(FEScript& Src, bool bReference) { + Init(); + SetName(Src.pName); + ID = Src.ID; + Length = Src.Length; + CurTime = Src.CurTime; + Flags = Src.Flags; + SetTrackCount(Src.TrackCount); + if (bReference) { + for (unsigned long i = 0; i < TrackCount; i++) { + pTracks[i].ParamType = Src.pTracks[i].ParamType; + pTracks[i].ParamSize = Src.pTracks[i].ParamSize; + pTracks[i].InterpType = Src.pTracks[i].InterpType; + pTracks[i].InterpAction = Src.pTracks[i].InterpAction; + pTracks[i].Length = Src.pTracks[i].Length; + *reinterpret_cast(reinterpret_cast(&pTracks[i]) + 7) = + *reinterpret_cast(reinterpret_cast(&Src.pTracks[i]) + 7); + pTracks[i].BaseKey = Src.pTracks[i].BaseKey; + pTracks[i].DeltaKeys.ReferenceList(&Src.pTracks[i].DeltaKeys); + } + } else { + for (unsigned long i = 0; i < TrackCount; i++) { + pTracks[i] = Src.pTracks[i]; + } + } + Events = Src.Events; +} + +// Pool removed - using ObjectPool template diff --git a/src/Speed/Indep/Src/FEng/FEScript.h b/src/Speed/Indep/Src/FEng/FEScript.h index 7ebe4d943..0de7f64aa 100644 --- a/src/Speed/Indep/Src/FEng/FEScript.h +++ b/src/Speed/Indep/Src/FEng/FEScript.h @@ -5,10 +5,13 @@ #pragma once #endif +#include "FEObject.h" #include "FEEvent.h" #include "FEKeyTrack.h" #include "FEList.h" +template struct ObjectPool; + // total size: 0x34 class FEScript : public FEMinNode { public: @@ -21,6 +24,23 @@ class FEScript : public FEMinNode { FEEventList Events; // offset 0x24, size 0x8 char *pName; // offset 0x2C, size 0x4 unsigned long ID; // offset 0x30, size 0x4 + + inline const char* GetName() const { return pName; } + inline FEScript* GetNext() const { return static_cast(FEMinNode::GetNext()); } + inline FEScript* GetPrev() const { return static_cast(FEMinNode::GetPrev()); } + + inline FEScript() { Init(); } + static void* operator new(unsigned int); + static void operator delete(void* pNode); + + void Init(); + ~FEScript() override; + FEScript(FEScript& Src, bool bReference); + void SetTrackCount(long Count); + FEKeyTrack* FindTrack(FEKeyTrack_Indices TrackIndex) const; + void SetName(const char* pNewName); + + static ObjectPool NodePool; }; #endif diff --git a/src/Speed/Indep/Src/FEng/FESimpleImage.h b/src/Speed/Indep/Src/FEng/FESimpleImage.h index a590bbcc4..fd86642d9 100644 --- a/src/Speed/Indep/Src/FEng/FESimpleImage.h +++ b/src/Speed/Indep/Src/FEng/FESimpleImage.h @@ -5,6 +5,15 @@ #pragma once #endif +#include "FEObject.h" +// total size: 0x5C +struct FESimpleImage : public FEObject { + inline FESimpleImage() {} + inline FESimpleImage(const FESimpleImage& Object, bool bReference) + : FEObject(Object, bReference) {} + ~FESimpleImage() override; + FEObject* Clone(bool bReference) override; +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FESlotPool.cpp b/src/Speed/Indep/Src/FEng/FESlotPool.cpp index e69de29bb..3e1baf4b3 100644 --- a/src/Speed/Indep/Src/FEng/FESlotPool.cpp +++ b/src/Speed/Indep/Src/FEng/FESlotPool.cpp @@ -0,0 +1,101 @@ +#include "Speed/Indep/Src/FEng/FESlotPool.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +unsigned char* FESlotNode::AllocBlock() { + if (SlotsUsed == 0x20) { + return nullptr; + } + unsigned long Index = 0; + while (SlotMask[Index] == 0xFF) { + Index++; + } + Index <<= 3; + if (SlotMask[Index >> 3] & 1) { + do { + Index++; + } while ((SlotMask[Index >> 3] >> (Index & 7)) & 1); + } + SlotsUsed++; + SlotMask[Index >> 3] |= static_cast(1 << (Index & 7)); + return pData + SlotSize * Index; +} + +void FESlotNode::FreeBlock(unsigned char* pSlot) { + unsigned long idx = static_cast(pSlot - pData) / static_cast(SlotSize); + SlotsUsed--; + unsigned long byteIdx = idx >> 3; + unsigned long bitIdx = idx & 7; + SlotMask[byteIdx] &= ~static_cast(1 << bitIdx); +} + +unsigned char* FESlotPool::Alloc() { + FESlotNode* pNode = static_cast(Slots.GetHead()); + while (pNode && pNode->IsFull()) { + pNode = pNode->GetNext(); + } + if (!pNode) { + pNode = FENG_NEW FESlotNode(static_cast(SlotSize)); + pNode->pData = static_cast(FEngMalloc(static_cast(pNode->SlotSize) << 5, nullptr, 0)); + FEngMemSet(pNode->SlotMask, 0, 4); + Slots.AddHead(pNode); + } + return pNode->AllocBlock(); +} + +bool FESlotPool::Free(unsigned char* pSlot) { + FESlotNode* pNode = static_cast(Slots.GetHead()); + while (pNode) { + bool contains = false; + if (reinterpret_cast(pSlot) >= reinterpret_cast(pNode->pData)) { + contains = reinterpret_cast(pSlot) < reinterpret_cast(pNode->pData) + static_cast(pNode->SlotSize) * 0x20; + } + if (contains) { + break; + } + pNode = pNode->GetNext(); + } + if (!pNode) { + return false; + } + pNode->FreeBlock(pSlot); + if (pNode->SlotsUsed == 0) { + Slots.RemNode(pNode); + delete pNode; + } + return true; +} + +FESlotNode::~FESlotNode() { + if (pData) { + delete[] pData; + } +} + +unsigned char* FEMultiPool::Alloc(unsigned long Size) { + if (Size == 0) { + return nullptr; + } + FESlotPool* pPool = static_cast(Pools.GetHead()); + while (pPool && pPool->SlotSize != Size) { + pPool = pPool->GetNext(); + } + if (!pPool) { + pPool = FENG_NEW FESlotPool(Size); + Pools.AddHead(pPool); + } + return pPool->Alloc(); +} + +void FEMultiPool::Free(unsigned char* pSlot) { + if (!pSlot) { + return; + } + FESlotPool* pPool = static_cast(Pools.GetHead()); + while (pPool && !pPool->Free(pSlot)) { + pPool = pPool->GetNext(); + } + if (pPool->IsEmpty()) { + Pools.RemNode(pPool); + delete pPool; + } +} diff --git a/src/Speed/Indep/Src/FEng/FESlotPool.h b/src/Speed/Indep/Src/FEng/FESlotPool.h index 23ca8cde3..b50c06f5f 100644 --- a/src/Speed/Indep/Src/FEng/FESlotPool.h +++ b/src/Speed/Indep/Src/FEng/FESlotPool.h @@ -5,6 +5,49 @@ #pragma once #endif +#include "FEList.h" +// total size: 0x18 +struct FESlotNode : public FEMinNode { + unsigned short SlotSize; // offset 0xC, size 0x2 + unsigned short SlotsUsed; // offset 0xE, size 0x2 + unsigned char SlotMask[4]; // offset 0x10, size 0x4 + unsigned char* pData; // offset 0x14, size 0x4 + + inline FESlotNode(unsigned short Size) + : SlotSize(Size) // + , SlotsUsed(0) // + , pData(nullptr) { + } + + inline bool IsEmpty() { return SlotsUsed == 0; } + inline bool IsFull() { return SlotsUsed == 0x20; } + inline FESlotNode* GetNext() { return static_cast(FEMinNode::GetNext()); } + + ~FESlotNode() override; + unsigned char* AllocBlock(); + void FreeBlock(unsigned char* pSlot); +}; + +// total size: 0x20 +struct FESlotPool : public FEMinNode { + FEMinList Slots; // offset 0xC, size 0x10 + unsigned long SlotSize; // offset 0x1C, size 0x4 + + inline FESlotPool(unsigned long Size) : SlotSize(Size) {} + inline bool IsEmpty() { return Slots.GetNumElements() == 0; } + inline FESlotPool* GetNext() { return static_cast(FEMinNode::GetNext()); } + + unsigned char* Alloc(); + bool Free(unsigned char* pSlot); +}; + +// total size: 0x10 +struct FEMultiPool { + FEMinList Pools; // offset 0x0, size 0x10 + + unsigned char* Alloc(unsigned long Size); + void Free(unsigned char* pSlot); +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEString.cpp b/src/Speed/Indep/Src/FEng/FEString.cpp index e69de29bb..4b3d3d947 100644 --- a/src/Speed/Indep/Src/FEng/FEString.cpp +++ b/src/Speed/Indep/Src/FEng/FEString.cpp @@ -0,0 +1,43 @@ +#include + +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +FELabelCallback* FEString::pLabelCallback; + +FEString::FEString(const FEString& String, bool bReference) + : FEObject(String, bReference), pLabelName(0), string(String.string), Format(String.Format), Leading(String.Leading), + MaxWidth(String.MaxWidth) { + SetLabel(String.pLabelName); + string.SetLength(String.string.mulBufferLength); +} + +FEString::~FEString() { + if (pLabelName) { + delete[] pLabelName; + } +} + +FEObject* FEString::Clone(bool bReference) { + return FENG_NEW FEString(*this, bReference); +} + +void FEString::SetLabel(const char* pString) { + if (pLabelName) { + delete[] pLabelName; + } + + pLabelName = 0; + if (pString) { + unsigned long Len = FEngStrLen(pString) + 1; + + pLabelName = FENG_NEW char[Len]; + FEngStrCpy(pLabelName, pString); + } + + LabelHash = FEHashUpper(pLabelName); + Flags |= 0x400000; + if (pLabelCallback) { + pLabelCallback->OnLabelChanged(this); + } +} diff --git a/src/Speed/Indep/Src/FEng/FEString.h b/src/Speed/Indep/Src/FEng/FEString.h index 16514b028..b3da1c2cc 100644 --- a/src/Speed/Indep/Src/FEng/FEString.h +++ b/src/Speed/Indep/Src/FEng/FEString.h @@ -1,10 +1,72 @@ -#ifndef FENG_FESTRING_H -#define FENG_FESTRING_H +#ifndef _FESTRING +#define _FESTRING -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include "FEObject.h" +#include "FEWideString.h" + +struct FELabelCallback; + +// total size: 0x78 +struct FEString : public FEObject { + static FELabelCallback* pLabelCallback; + +private: + char* pLabelName; // offset 0x5C, size 0x4 + unsigned long LabelHash; // offset 0x60, size 0x4 + +public: + FEWideString string; // offset 0x64, size 0x8 + unsigned long Format; // offset 0x6C, size 0x4 + int Leading; // offset 0x70, size 0x4 + unsigned long MaxWidth; // offset 0x74, size 0x4 + + inline FEString() + : FEObject() // + , pLabelName(nullptr) // + , LabelHash(0xFFFFFFFF) // + , string() // + , Format(0) // + , Leading(0) // + , MaxWidth(0) + { + } + FEString(const FEString& String, bool bReference); + ~FEString() override; + + inline short* GetString(); + inline void SetString(short* pNewText); + inline void SetString(const char* pcString); + inline void SetStringFromUTF8(const char* pUTF8String) {} + inline const char* GetLabel() const; + inline unsigned long GetLabelHash(); + inline void SetLabelHash(unsigned long Hash); + inline FEWideString& GetWideString(); + + void SetLabel(const char* pString); + + FEObject* Clone(bool bReference) override; + + static inline void SetLabelCallback(FELabelCallback* pCallback); +}; + +struct FELabelCallback { + virtual void OnLabelChanged(FEString* text) = 0; +}; + +inline void FEString::SetLabelHash(unsigned long Hash) { + Flags |= 0x400000; + LabelHash = Hash; + if (pLabelCallback) { + pLabelCallback->OnLabelChanged(this); + } +} +inline unsigned long FEString::GetLabelHash() { + return LabelHash; +} +inline short *FEString::GetString() { + return string.mpsString; +} #endif diff --git a/src/Speed/Indep/Src/FEng/FETypeLib.cpp b/src/Speed/Indep/Src/FEng/FETypeLib.cpp index e69de29bb..0c4a671da 100644 --- a/src/Speed/Indep/Src/FEng/FETypeLib.cpp +++ b/src/Speed/Indep/Src/FEng/FETypeLib.cpp @@ -0,0 +1,189 @@ +#include "Speed/Indep/Src/FEng/FETypeLib.h" +#include "Speed/Indep/Src/FEng/FETypeNode.h" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +extern int bSPrintf(char* buf, const char* fmt, ...); + +static const char* FEColor1Name = "Top Left"; +static const char* FEColor2Name = "Top Right"; +static const char* FEColor3Name = "Bottom Right"; +static const char* FEColor4Name = "Bottom Left"; +static const char* FEFrameNumName = "Frame Number"; + +FETypeNode* FETypeLib::CreateBaseObjectType(const char* pName) { + FEVector3 ZeroVect; + FEVector3 SizeVect; + FEQuaternion ZeroQuat; + FEColor White; + + FETypeNode* pType = FENG_NEW FETypeNode(); + pType->SetName(pName); + + pType->AddField("Color", 6); + pType->AddField("Pivot", 4); + pType->AddField("Position", 4); + pType->AddField("Rotation", 5); + pType->AddField("Size", 4); + + ZeroVect.x = 0.0f; + ZeroVect.y = 0.0f; + ZeroVect.z = 0.0f; + + SizeVect.x = 1.0f; + SizeVect.y = 1.0f; + SizeVect.z = 1.0f; + + ZeroQuat.x = 0.0f; + ZeroQuat.y = 0.0f; + ZeroQuat.z = 0.0f; + ZeroQuat.w = 1.0f; + + White = FEColor(0xFFFFFFFF); + FEFieldNode* pField = pType->GetFirstField(); + pField->SetDefault(&White); + pField = pField->GetNext(); + pField->SetDefault(&ZeroVect); + pField = pField->GetNext(); + pField->SetDefault(&ZeroVect); + pField = pField->GetNext(); + pField->SetDefault(&ZeroQuat); + pField = pField->GetNext(); + pField->SetDefault(&SizeVect); + + return pType; +} + +FETypeNode* FETypeLib::CreateImageObjectType(const char* pName) { + FEVector2 ZeroVect; + FEVector2 OneVect; + + FETypeNode* pType = CreateBaseObjectType(pName); + + pType->AddField("Upper Left", 3); + pType->AddField("Lower Right", 3); + + ZeroVect = FEVector2(0.0f, 0.0f); + FEFieldNode* pField = pType->GetField("Upper Left"); + pField->SetDefault(&ZeroVect); + OneVect = FEVector2(1.0f, 1.0f); + pField->GetNext()->SetDefault(&OneVect); + + return pType; +} + +FETypeNode* FETypeLib::CreateMultiImageObjectType(const char* pName) { + FETypeNode* pType = CreateImageObjectType(pName); + char sztemp[32]; + FEVector3 pivot_rot(0.0f, 0.0f, 0.0f); + FEVector2 top_left(0.0f, 0.0f); + FEVector2 bottom_right(1.0f, 1.0f); + + for (int i = 1; i < 4; i++) { + bSPrintf(sztemp, "Tex %d: Top Left", i); + pType->AddField(sztemp, 3); + FEFieldNode* pField = pType->GetField(sztemp); + pField->SetDefault(&top_left); + } + for (int i = 1; i < 4; i++) { + bSPrintf(sztemp, "Tex %d: Bottom Right", i); + pType->AddField(sztemp, 3); + FEFieldNode* pField = pType->GetField(sztemp); + pField->SetDefault(&bottom_right); + } + + bSPrintf(sztemp, "Pivot Rot (Z)"); + pType->AddField(sztemp, 4); + FEFieldNode* pField = pType->GetField(sztemp); + pField->SetDefault(&pivot_rot); + + return pType; +} + +bool FETypeLib::Startup() { + FETypeNode* pType; + FEColor White; + int DefaultFrame; + + pType = CreateImageObjectType("Image"); + pType->SetID(1); + AddType(pType); + + pType = CreateImageObjectType("CBV Image"); + pType->AddField(FEColor1Name, 6); + pType->AddField(FEColor2Name, 6); + pType->AddField(FEColor3Name, 6); + pType->AddField(FEColor4Name, 6); + White = FEColor(0xFFFFFFFF); + FEFieldNode* pField; + pField = pType->GetField(FEColor1Name); + pField->SetDefault(&White); + pField = pType->GetField(FEColor2Name); + pField->SetDefault(&White); + pField = pType->GetField(FEColor3Name); + pField->SetDefault(&White); + pField = pType->GetField(FEColor4Name); + pField->SetDefault(&White); + pType->SetID(9); + AddType(pType); + + pType = CreateImageObjectType("Anim Image"); + pType->AddField(FEFrameNumName, 1); + DefaultFrame = 0; + pField = pType->GetField(FEFrameNumName); + pField->SetDefault(&DefaultFrame); + pType->SetID(10); + AddType(pType); + + pType = CreateBaseObjectType("Simple Image"); + pType->SetID(11); + AddType(pType); + + pType = CreateMultiImageObjectType("MultiImage"); + pType->SetID(12); + AddType(pType); + + pType = CreateBaseObjectType("String"); + pType->SetID(2); + AddType(pType); + + pType = CreateBaseObjectType("Model"); + pType->SetID(3); + AddType(pType); + + pType = CreateBaseObjectType("Movie"); + pType->SetID(7); + AddType(pType); + + pType = CreateBaseObjectType("Effect"); + pType->SetID(8); + AddType(pType); + + pType = CreateBaseObjectType("List"); + pType->SetID(4); + AddType(pType); + + pType = CreateBaseObjectType("Group"); + pType->SetID(5); + AddType(pType); + + pType = CreateBaseObjectType("Code List"); + pType->SetID(6); + AddType(pType); + + return true; +} + +FETypeNode* FETypeLib::FindType(unsigned long TypeID) { + FETypeNode* pNode = GetFirstType(); + for (;;) { + if (!pNode) { + break; + } + if (pNode->GetID() == TypeID) { + return pNode; + } + pNode = pNode->GetNext(); + } + return pNode; +} diff --git a/src/Speed/Indep/Src/FEng/FETypeLib.h b/src/Speed/Indep/Src/FEng/FETypeLib.h index 8bfa37a30..6afb7eda3 100644 --- a/src/Speed/Indep/Src/FEng/FETypeLib.h +++ b/src/Speed/Indep/Src/FEng/FETypeLib.h @@ -5,6 +5,6 @@ #pragma once #endif - +struct FETypeLib; #endif diff --git a/src/Speed/Indep/Src/FEng/FETypeNode.cpp b/src/Speed/Indep/Src/FEng/FETypeNode.cpp index e69de29bb..01ca4e66f 100644 --- a/src/Speed/Indep/Src/FEng/FETypeNode.cpp +++ b/src/Speed/Indep/Src/FEng/FETypeNode.cpp @@ -0,0 +1,68 @@ +#include "Speed/Indep/Src/FEng/FETypeNode.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +extern const unsigned long FEKeyTypeSize[]; + +FEFieldNode::~FEFieldNode() { + if (pDefault) { + delete[] pDefault; + } +} + +void FEFieldNode::SetDefault(void* pSrc) { + if (pDefault) { + delete[] pDefault; + } + pDefault = nullptr; + if (Size != 0) { + pDefault = reinterpret_cast(FENG_NEW char[Size]); + FEngMemCpy(pDefault, pSrc, Size); + } +} + +void FEFieldNode::GetDefault(void* pDest) { + if (pDefault) { + FEngMemCpy(pDest, pDefault, Size); + } +} + +void FETypeNode::AddField(const char* pName, long iType) { + FEFieldNode* pField; + pField = FENG_NEW FEFieldNode(); + pField->SetName(pName); + pField->SetType(iType); + pField->SetSize(FEKeyTypeSize[iType]); + Fields.AddNode(static_cast(Fields.GetTail()), pField); + UpdateOffsets(); +} + +void FETypeNode::UpdateOffsets() { + unsigned long Offset = 0; + FEFieldNode* pField = GetFirstField(); + while (pField) { + pField->SetOffset(Offset); + Offset += pField->GetSize(); + pField = pField->GetNext(); + } +} + +unsigned long FETypeNode::GetTypeSize() { + unsigned long Size = 0; + FEFieldNode* pField = GetFirstField(); + while (pField) { + Size += pField->GetSize(); + pField = pField->GetNext(); + } + return Size; +} + +FEFieldNode* FETypeNode::GetField(const char* pName) { + FEFieldNode* pNode = GetFirstField(); + while (pNode) { + if (FEngStrICmp(pNode->GetName(), pName) == 0) { + break; + } + pNode = pNode->GetNext(); + } + return pNode; +} diff --git a/src/Speed/Indep/Src/FEng/FETypeNode.h b/src/Speed/Indep/Src/FEng/FETypeNode.h index 8894fd0ae..a3ac131ea 100644 --- a/src/Speed/Indep/Src/FEng/FETypeNode.h +++ b/src/Speed/Indep/Src/FEng/FETypeNode.h @@ -5,6 +5,7 @@ #pragma once #endif - +struct FEFieldNode; +struct FETypeNode; #endif diff --git a/src/Speed/Indep/Src/FEng/FETypes.cpp b/src/Speed/Indep/Src/FEng/FETypes.cpp index e69de29bb..353e8fad9 100644 --- a/src/Speed/Indep/Src/FEng/FETypes.cpp +++ b/src/Speed/Indep/Src/FEng/FETypes.cpp @@ -0,0 +1,109 @@ +#include "Speed/Indep/Src/FEng/FETypes.h" + +FEImageData::FEImageData() { + Rot.w = 1.0f; +} + +FEColor::FEColor(unsigned long Col) { + a = Col >> 24; + r = (Col >> 16) & 0xFF; + g = (Col >> 8) & 0xFF; + b = Col & 0xFF; +} + +FEColor::operator unsigned long() const { + unsigned char rv, gv, bv, av; + + if (r >= 0) { + if (r > 255) { + rv = 255; + } else { + rv = r; + } + } else { + rv = 0; + } + + if (g >= 0) { + if (g > 255) { + gv = 255; + } else { + gv = g; + } + } else { + gv = 0; + } + + if (b >= 0) { + if (b > 255) { + bv = 255; + } else { + bv = b; + } + } else { + bv = 0; + } + + if (a >= 0) { + av = 255; + if (a < 256) { + av = a; + } + } else { + av = 0; + } + + return (av << 24) | (rv << 16) | (gv << 8) | bv; +} +FEColor& FEColor::operator=(const FEColor& rhs) { + a = rhs.a; + r = rhs.r; + g = rhs.g; + b = rhs.b; + return *this; +} +FEColor& FEColor::operator+=(const FEColor& rhs) { + r += rhs.r; + g += rhs.g; + b += rhs.b; + a += rhs.a; + return *this; +} + +FEColor FEColor::operator-(const FEColor& rhs) const { + FEColor c; + c.r = r - rhs.r; + c.g = g - rhs.g; + c.b = b - rhs.b; + c.a = a - rhs.a; + return c; +} + +void FEQuaternion::GetMatrix(FEMatrix4* pMatrix) { + float xx2 = x * (x + x); + float yy2 = y * (y + y); + float zz2 = z * (z + z); + float xy2 = x * (y + y); + float xz2 = x * (z + z); + float yz2 = y * (z + z); + float wx2 = w * (x + x); + float wy2 = w * (y + y); + float wz2 = w * (z + z); + + pMatrix->m11 = 1.0f - (yy2 + zz2); + pMatrix->m12 = xy2 + wz2; + pMatrix->m13 = xz2 - wy2; + pMatrix->m14 = 0.0f; + pMatrix->m21 = xy2 - wz2; + pMatrix->m22 = 1.0f - (xx2 + zz2); + pMatrix->m23 = yz2 + wx2; + pMatrix->m24 = 0.0f; + pMatrix->m31 = xz2 + wy2; + pMatrix->m32 = yz2 - wx2; + pMatrix->m33 = 1.0f - (xx2 + yy2); + pMatrix->m34 = 0.0f; + pMatrix->m41 = 0.0f; + pMatrix->m42 = 0.0f; + pMatrix->m43 = 0.0f; + pMatrix->m44 = 1.0f; +} diff --git a/src/Speed/Indep/Src/FEng/FETypes.h b/src/Speed/Indep/Src/FEng/FETypes.h index 665f0ab62..c9f8efb21 100644 --- a/src/Speed/Indep/Src/FEng/FETypes.h +++ b/src/Speed/Indep/Src/FEng/FETypes.h @@ -5,6 +5,249 @@ #pragma once #endif +float FEngSqrt(float x); +// total size: 0x8 +struct FEVector2 { + float x; // offset 0x0, size 0x4 + float y; // offset 0x4, size 0x4 + + inline FEVector2() : x(0.0f), y(0.0f) {} + inline FEVector2(float v) : x(v), y(v) {} + inline FEVector2(float vx, float vy) : x(vx), y(vy) {} + inline FEVector2(float* pf) : x(pf[0]), y(pf[1]) {} + inline FEVector2(const FEVector2& v) { *this = v; } + + inline FEVector2 operator+(const FEVector2& v) const { return FEVector2(x + v.x, y + v.y); } + inline FEVector2 operator-(const FEVector2& v) const { return FEVector2(x - v.x, y - v.y); } + inline FEVector2 operator*(float f) const { return FEVector2(x * f, y * f); } + inline FEVector2 operator/(float f) const { return FEVector2(x / f, y / f); } + inline FEVector2& operator=(const FEVector2& v) { x = v.x; y = v.y; return *this; } + inline FEVector2& operator+=(FEVector2 v) { x += v.x; y += v.y; return *this; } + inline FEVector2& operator-=(FEVector2 v) { x -= v.x; y -= v.y; return *this; } + inline FEVector2& operator*=(float f) { x *= f; y *= f; return *this; } + + inline float Dot(const FEVector2& v) const { return x * v.x + y * v.y; } + inline float Length() const { return FEngSqrt(Dot(*this)); } + inline float Normalize() { + float ret = Length(); + if (ret >= 0.1f) { + float oof = 1.0f / ret; + x *= oof; + y *= oof; + } + return ret; + } +}; + +// total size: 0xC +struct FEVector3 { + float x; // offset 0x0, size 0x4 + float y; // offset 0x4, size 0x4 + float z; // offset 0x8, size 0x4 + + inline FEVector3() : x(0.0f), y(0.0f), z(0.0f) {} + inline FEVector3(float v) : x(v), y(v), z(v) {} + inline FEVector3(float vx, float vy, float vz) : x(vx), y(vy), z(vz) {} + inline FEVector3(float* pf) : x(pf[0]), y(pf[1]), z(pf[2]) {} + inline FEVector3(const FEVector3& v) { *this = v; } + + inline FEVector3& operator=(const FEVector3& v) { x = v.x; y = v.y; z = v.z; return *this; } + inline FEVector3 operator-(const FEVector3& v) const { return FEVector3(x - v.x, y - v.y, z - v.z); } + inline FEVector3& operator*=(float f) { x *= f; y *= f; z *= f; return *this; } + inline FEVector3& operator+=(const FEVector3& v) { x += v.x; y += v.y; z += v.z; return *this; } +}; + +// total size: 0x10 +struct FEColor { + int b; // offset 0x0, size 0x4 + int g; // offset 0x4, size 0x4 + int r; // offset 0x8, size 0x4 + int a; // offset 0xC, size 0x4 + + inline FEColor() {} + FEColor(unsigned long Col); + operator unsigned long() const; + FEColor& operator=(const FEColor& rhs); + FEColor& operator+=(const FEColor& rhs); + FEColor operator-(const FEColor& rhs) const; +}; + +struct FEMatrix4; + +// total size: 0x10 +struct FEQuaternion { + float x; // offset 0x0, size 0x4 + float y; // offset 0x4, size 0x4 + float z; // offset 0x8, size 0x4 + float w; // offset 0xC, size 0x4 + + inline FEQuaternion() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {} + inline FEQuaternion(float X, float Y, float Z, float W) : x(X), y(Y), z(Z), w(W) {} + inline FEQuaternion& operator=(const FEQuaternion& q) { x = q.x; y = q.y; z = q.z; w = q.w; return *this; } + inline void Conjugate() { x = -x; y = -y; z = -z; } + inline FEQuaternion& operator*=(const FEQuaternion& q) { *this = *this * q; return *this; } + inline FEQuaternion operator*(const FEQuaternion& q1) { + FEQuaternion qRet; + qRet.x = (y * q1.z - z * q1.y) + (q1.w * x + q1.x * w); + qRet.y = (z * q1.x - x * q1.z) + (q1.w * y + q1.y * w); + qRet.z = (x * q1.y - y * q1.x) + (q1.w * z + q1.z * w); + qRet.w = q1.w * w - (q1.x * x + q1.y * y + q1.z * z); + return qRet; + } + void GetMatrix(FEMatrix4* pMatrix); +}; + +inline FEQuaternion operator+(const FEQuaternion& q0, const FEQuaternion& q1) { + FEQuaternion q; + q.x = q0.x + q1.x; + q.y = q0.y + q1.y; + q.z = q0.z + q1.z; + q.w = q0.w + q1.w; + return q; +} + +inline FEQuaternion operator-(const FEQuaternion& q0, const FEQuaternion& q1) { + FEQuaternion q; + q.x = q0.x - q1.x; + q.y = q0.y - q1.y; + q.z = q0.z - q1.z; + q.w = q0.w - q1.w; + return q; +} + +inline FEQuaternion operator*(const FEQuaternion& q, float fScaler) { + FEQuaternion r; + r.x = q.x * fScaler; + r.y = q.y * fScaler; + r.z = q.z * fScaler; + r.w = q.w * fScaler; + return r; +} + +inline float QuaternionDot(const FEQuaternion& q0, const FEQuaternion& q1) { + return q0.x * q1.x + q0.y * q1.y + q0.z * q1.z + q0.w * q1.w; +} + +inline float QuaternionNorm(const FEQuaternion& q) { + return q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w; +} + +float FEngSqrt(float x); + +inline float QuaternionMagnitude(const FEQuaternion& q) { + return FEngSqrt(QuaternionNorm(q)); +} + +inline void NormalizeQuaternion(FEQuaternion& q) { + float fMagnitude = 1.0f / QuaternionMagnitude(q); + if (fMagnitude > 0.00001f) { + q.x *= fMagnitude; + q.y *= fMagnitude; + q.z *= fMagnitude; + q.w *= fMagnitude; + } else { + q.x = 0.0f; + q.y = 0.0f; + q.z = 0.0f; + q.w = 1.0f; + } +} + +// total size: 0x10 +struct FERect { + float left; // offset 0x0, size 0x4 + float top; // offset 0x4, size 0x4 + float right; // offset 0x8, size 0x4 + float bottom; // offset 0xC, size 0x4 + + inline FERect() : left(0.0f), top(0.0f), right(0.0f), bottom(0.0f) {} + inline FERect(float Value) : left(Value), top(Value), right(Value), bottom(Value) {} + inline FERect(float l, float t, float r, float b) : left(l), top(t), right(r), bottom(b) {} + inline FERect& operator=(const FERect& r) { left = r.left; top = r.top; right = r.right; bottom = r.bottom; return *this; } +}; + +// total size: 0x44 +struct FEObjData { + inline FEObjData() {} + FEColor Col; // offset 0x0, size 0x10 + FEVector3 Pivot; // offset 0x10, size 0xC + FEVector3 Pos; // offset 0x1C, size 0xC + FEQuaternion Rot; // offset 0x28, size 0x10 + FEVector3 Size; // offset 0x38, size 0xC +}; + +// total size: 0x54 +struct FEImageData : public FEObjData { + FEVector2 UpperLeft; // offset 0x44, size 0x8 + FEVector2 LowerRight; // offset 0x4C, size 0x8 + + FEImageData(); +}; + +// total size: 0x94 +struct FEColoredImageData : public FEImageData { + FEColor VertexColors[4]; // offset 0x54, size 0x40 +}; + +// total size: 0x90 +struct FEMultiImageData : public FEImageData { + FEVector2 TopLeftUV[3]; // offset 0x54, size 0x18 + FEVector2 BottomRightUV[3]; // offset 0x6C, size 0x18 + FEVector3 PivotRot; // offset 0x84, size 0xC +}; + +// total size: 0x8 +struct FEPoint { + float h; // offset 0x0, size 0x4 + float v; // offset 0x4, size 0x4 + + inline FEPoint() : h(0.0f), v(0.0f) {} + inline FEPoint(float Value) {} + inline FEPoint(float H, float V) : h(H), v(V) {} + inline FEPoint& operator=(const FEPoint& p) { h = p.h; v = p.v; return *this; } +}; + +// total size: 0x40 +struct FEMatrix4 { + float m11; // offset 0x0 + float m12; // offset 0x4 + float m13; // offset 0x8 + float m14; // offset 0xC + float m21; // offset 0x10 + float m22; // offset 0x14 + float m23; // offset 0x18 + float m24; // offset 0x1C + float m31; // offset 0x20 + float m32; // offset 0x24 + float m33; // offset 0x28 + float m34; // offset 0x2C + float m41; // offset 0x30 + float m42; // offset 0x34 + float m43; // offset 0x38 + float m44; // offset 0x3C + + inline FEMatrix4& operator=(const FEMatrix4& m) { + m11 = m.m11; m12 = m.m12; m13 = m.m13; m14 = m.m14; + m21 = m.m21; m22 = m.m22; m23 = m.m23; m24 = m.m24; + m31 = m.m31; m32 = m.m32; m33 = m.m33; m34 = m.m34; + m41 = m.m41; m42 = m.m42; m43 = m.m43; m44 = m.m44; + return *this; + } + + void Identify(); +}; + +void FEMultMatrix(FEMatrix4* dest, const FEMatrix4* a, const FEMatrix4* b); +void FEMultMatrix(FEVector3* dest, const FEMatrix4* m, const FEVector3* v); + +#ifndef SUPPORT_UTILITY_UVECTOR_MATH_H +inline float IntAsFloat(const int& i) { + return *reinterpret_cast(&i); +} + +static const float kFloatScaleUp = IntAsFloat(0x00800000); +static const float kFloatScaleDown = 1.0f / kFloatScaleUp; +#endif #endif diff --git a/src/Speed/Indep/Src/FEng/FEWideString.cpp b/src/Speed/Indep/Src/FEng/FEWideString.cpp index e69de29bb..c6ccafd94 100644 --- a/src/Speed/Indep/Src/FEng/FEWideString.cpp +++ b/src/Speed/Indep/Src/FEng/FEWideString.cpp @@ -0,0 +1,140 @@ +#include "Speed/Indep/Src/FEng/FEWideString.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" + +unsigned long GetStringLength(const short* pString) { + if (!pString) { + return 0; + } + + unsigned long length = 0; + + if (*pString == 0) { + return 0; + } + + do { + length++; + } while (pString[length] != 0); + + return length; +} + +template void CopyString(short* pDst, const T* pSrc) { + if (!pDst) { + return; + } + + if (pSrc) { + unsigned short value = *pSrc; + + while (value != 0) { + *pDst = value; + pSrc++; + pDst++; + value = *pSrc; + } + } + + *pDst = 0; +} + +template void CopyString(short* pDst, const T* pSrc, unsigned long ulMaxLength) { + if (!pDst) { + return; + } + + if (ulMaxLength == 0) { + return; + } + + if (pSrc) { + T ch = *pSrc; + unsigned long length = 0; + ulMaxLength--; + if (ch != 0 && ulMaxLength != 0) { + do { + length++; + *pDst = *pSrc; + pDst++; + pSrc++; + if (*pSrc == 0) { + break; + } + } while (ulMaxLength != length); + } + } + + *pDst = 0; +} + +FEWideString::FEWideString() { + mpsString = 0; + mulBufferLength = 0; + mulBufferLength = Length(); +} + +FEWideString::FEWideString(const FEWideString& string) { + mpsString = 0; + mulBufferLength = 0; + *this = string; + mulBufferLength = Length(); +} + +FEWideString::~FEWideString() { + if (mpsString) { + delete[] mpsString; + } +} + +FEWideString& FEWideString::operator=(const FEWideString& string) { + if (string.mpsString) { + short* pString = AllocateString(GetStringLength(string.mpsString) + 1); + + mpsString = pString; + CopyString(pString, string.mpsString); + } + + return *this; +} + +FEWideString& FEWideString::operator=(const short* psString) { + if (!psString) { + return *this; + } + + short* pString = AllocateString(GetStringLength(psString) + 1); + + mpsString = pString; + CopyString(pString, psString); + + return *this; +} + +unsigned long FEWideString::Length() const { return GetStringLength(mpsString); } + +void FEWideString::SetLength(const unsigned long newLength) { + unsigned long length = Length(); + + if (newLength > length) { + short* pString; + + mulBufferLength = newLength; + pString = static_cast(FEngMalloc((newLength + 1) * 2, 0, 0)); + CopyString(pString, mpsString); + if (mpsString) { + delete[] mpsString; + } + mpsString = pString; + } +} + +short* FEWideString::AllocateString(const unsigned long newLength) { + if (newLength > mulBufferLength) { + SetLength(newLength); + } + + return mpsString; +} + +template void CopyString(short* pDst, const short* pSrc); +template void CopyString(short* pDst, const short* pSrc, unsigned long ulMaxLength); diff --git a/src/Speed/Indep/Src/FEng/FEWideString.h b/src/Speed/Indep/Src/FEng/FEWideString.h index 8e9982ba7..02af00f6c 100644 --- a/src/Speed/Indep/Src/FEng/FEWideString.h +++ b/src/Speed/Indep/Src/FEng/FEWideString.h @@ -1,10 +1,44 @@ -#ifndef FENG_FEWIDESTRING_H -#define FENG_FEWIDESTRING_H +#ifndef _FEWIDESTRING +#define _FEWIDESTRING -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +// total size: 0x8 +struct FEWideString { + short* mpsString; // offset 0x0, size 0x4 + unsigned long mulBufferLength; // offset 0x4, size 0x4 + + FEWideString(); + FEWideString(const short* psString); + FEWideString(const char* pcString); + FEWideString(const FEWideString& string); + ~FEWideString(); + + bool operator==(const FEWideString& string); + FEWideString& operator=(const FEWideString& string); + FEWideString& operator=(const short* psString); + FEWideString& operator=(const char* pcString); + FEWideString& operator+=(const FEWideString& string); + FEWideString& operator+=(const short* pString); + FEWideString& operator+=(const char* pcString); + + inline void Associate(short* pString); + inline short* Disaccociate(); + inline operator short*() { return mpsString; } + inline operator const short*() const { return mpsString; } + inline const short operator[](int i) const; + inline short& operator[](int i); + unsigned long Length() const; + void SetLength(const unsigned long newLength); + char* Convert(char* pString) const; + short* AllocateString(const unsigned long newLength); + inline void ConvertToSpecial(const FEWideString& string); + void ConvertToSpecial(const char* pcString); + void ConvertToSpecial(const unsigned char* pcString); + void ConvertToSpecial(const short* psString); + void ConvertFromSpecial(char* pcString); + void ConvertFromSpecial(short* psString); + void ConvertFromSpecial(FEWideString& string); +}; #endif diff --git a/src/Speed/Indep/Src/FEng/FEngStandard.cpp b/src/Speed/Indep/Src/FEng/FEngStandard.cpp index e69de29bb..0711ca045 100644 --- a/src/Speed/Indep/Src/FEng/FEngStandard.cpp +++ b/src/Speed/Indep/Src/FEng/FEngStandard.cpp @@ -0,0 +1,63 @@ +#include + +#include "Speed/Indep/Src/FEng/FEngStandard.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bMemory.hpp" + +int bStrICmp(const char* s1, const char* s2); +bool bSetMemoryPoolDebugTracing(int pool_num, bool on_off); + +void FEngMemCpy(void* pDest, const void* pSrc, int Len) { memcpy(pDest, pSrc, Len); } + +void FEngMemSet(void* pDest, int Value, int Len) { memset(pDest, Value, Len); } + +void FEngStrCpy(char* pDest, const char* pSrc) { strcpy(pDest, pSrc); } + +int FEngStrLen(const char* pSrc) { return strlen(pSrc); } + +int FEngStrICmp(const char* pStr1, const char* pStr2) { return bStrICmp(pStr1, pStr2); } + +static int FEngMemoryPoolNumber = -1; +static void* pFEngMemoryPoolMemory = nullptr; +static int FEngMemoryPoolSize = 0; +static int FEngMemoryPoolTracingEnabled = 0; + +void InitFEngMemoryPool() { + if (FEngMemoryPoolNumber != 0) { + FEngMemoryPoolNumber = bGetFreeMemoryPoolNum(); + if (!pFEngMemoryPoolMemory) { + pFEngMemoryPoolMemory = bMalloc(FEngMemoryPoolSize, __FILE__, 0, 0); + } + bInitMemoryPool(FEngMemoryPoolNumber, pFEngMemoryPoolMemory, FEngMemoryPoolSize, __FILE__); + bSetMemoryPoolDebugTracing(FEngMemoryPoolNumber, FEngMemoryPoolTracingEnabled); + } +} + +void* FEngMalloc(unsigned int size, const char* pFilename, int Line) { + int pool_num = 0; + if (FEngMemoryPoolNumber != -1) { + int largest = bLargestMalloc(FEngMemoryPoolNumber); + if (largest > static_cast(size) + 0x40) { + pool_num = FEngMemoryPoolNumber; + } + } + void* ptr = bMalloc(size, pFilename, Line, (pool_num & 0xf) | 0x100); + return ptr; +} + +float FEngAbs(float x) { + return bAbs(x); +} + +float FEngSqrt(float x) { + return bSqrt(x); +} + +float FEngSin(float x) { + return bSin(x); +} + +float FEngACos(float x) { + return bAngToRad(bACos(x)); +} diff --git a/src/Speed/Indep/Src/FEng/FEngStandard.h b/src/Speed/Indep/Src/FEng/FEngStandard.h index ba80df886..adb9e151d 100644 --- a/src/Speed/Indep/Src/FEng/FEngStandard.h +++ b/src/Speed/Indep/Src/FEng/FEngStandard.h @@ -5,6 +5,28 @@ #pragma once #endif +void* FEngMalloc(unsigned int size, const char* pFilename, int Line); +void FEngMemCpy(void* pDest, const void* pSrc, int Length); +void FEngMemSet(void* pDest, int Value, int Length); +void FEngStrCpy(char* pDest, const char* pSrc); +int FEngStrLen(const char* pSrc); +int FEngStrICmp(const char* pStr1, const char* pStr2); +void InitFEngMemoryPool(); +float FEngAbs(float x); +float FEngSqrt(float x); +float FEngSin(float x); +float FEngACos(float x); +struct DummyFEngNewType {}; + +inline void* operator new(unsigned int size, const char* file, int line, DummyFEngNewType* dummy) { + return FEngMalloc(size, file, line); +} + +inline void* operator new[](unsigned int size, const char* file, int line, DummyFEngNewType* dummy) { + return FEngMalloc(size, file, line); +} + +#define FENG_NEW new(nullptr, 0, static_cast(nullptr)) #endif diff --git a/src/Speed/Indep/Src/FEng/FEngine.cpp b/src/Speed/Indep/Src/FEng/FEngine.cpp index e69de29bb..95a79f9cd 100644 --- a/src/Speed/Indep/Src/FEng/FEngine.cpp +++ b/src/Speed/Indep/Src/FEng/FEngine.cpp @@ -0,0 +1,1473 @@ +#include "Speed/Indep/Src/FEng/fengine_full.h" +#include "Speed/Indep/Src/FEng/FEJoyPad.h" +#include "Speed/Indep/Src/FEng/FEngStandard.h" +#include "Speed/Indep/Src/FEng/FEPackageReader.h" +#include "Speed/Indep/Src/FEng/FEGameInterface.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/FEListBox.h" +#include "Speed/Indep/Src/FEng/FECodeListBox.h" +#include "Speed/Indep/Src/FEng/cFEng.h" + +void FEngGetCenter(FEObject* pObj, float& cx, float& cy); + +extern "C" int printf(const char*, ...); + +// Callback structs used by both FEngine and FEPackage. +// Defined here because FEngine.cpp comes before FEPackage.cpp in the jumbo build. + +// total size: 0x4 +struct PackageInitStateCB : public FEObjectCallback { + bool Callback(FEObject* pObj) override; +}; + +// total size: 0xC +struct FEFindByHash : public FEObjectCallback { + unsigned long Hash; // offset 0x4, size 0x4 + FEObject* pFound; // offset 0x8, size 0x4 + + bool Callback(FEObject* pObj) override; +}; + +// total size: 0xC +struct FEFindByGUID : public FEObjectCallback { + unsigned long GUID; // offset 0x4, size 0x4 + FEObject* pFound; // offset 0x8, size 0x4 + + bool Callback(FEObject* pObj) override; +}; + +// total size: 0x8 +struct MouseStateObjectCounter : public FEObjectCallback { + int NumMouseObjects; // offset 0x4, size 0x4 + + bool Callback(FEObject* pObj) override; +}; + +// total size: 0x8 +struct MouseStateArrayBuilder : public FEObjectCallback { + FEPackage* pPack; // offset 0x4, size 0x4 + + bool Callback(FEObject* pObj) override; +}; + +// total size: 0x8 +struct MouseStateArrayOffsetUpdater : public FEObjectCallback { + FEPackage* pPack; // offset 0x4, size 0x4 + + bool Callback(FEObject* pObj) override; +}; + +// total size: 0xC +struct ResourceConnector : public FEObjectCallback { + FEPackage* pPack; // offset 0x4, size 0x4 + FEResourceRequest** pReqList; // offset 0x8, size 0x4 + + bool Callback(FEObject* pObj) override; + void ConnectListBoxResources(FEListBox* pList); +}; + +unsigned long FEngine::SysGUID; + +FEMultiPool ObjDataPool; +FEColoredImageData MaximumObjData; + +FEngine::FEngine() +{ + bExecuting = true; + bRenderedRecently = false; + NumJoyPads = 0; + pJoyPad = nullptr; + FastRepCache = 0; + FastRep = 0; + WrapMode = Wrap_None; + bMouseActive = false; + bErrorScreenMode = false; + bDebugMessages = false; + bLoadObjectNames = true; + bLoadScriptNames = true; + FEngMemSet(HeldButtons, 0, sizeof(HeldButtons)); + CurrentPackageRecordIndex = 0; + FEngMemSet(RecordedPackageNames, 0, sizeof(RecordedPackageNames)); + NextButtonRecordIndex = 0; + FEngMemSet(RecordedPackageButtons, 0, sizeof(RecordedPackageButtons)); + TypeLib.Startup(); +} + +void FEngine::SetNumJoyPads(unsigned char Count) { + FEJoyPad** ppJoyPad = &pJoyPad; + if (*ppJoyPad) { + delete[] *ppJoyPad; + } + if (Count) { + FEJoyPad* pPads = static_cast(FEngMalloc(Count * sizeof(FEJoyPad), nullptr, 0)); + long i = Count - 1; + if (Count != 0) { + FEJoyPad* pCur = pPads; + do { + new (pCur) FEJoyPad(); + pCur++; + } while (i-- != 0); + } + *ppJoyPad = pPads; + } + NumJoyPads = Count; + FEngMemSet(HoldDecrement, 0, sizeof(HoldDecrement)); +} + +void FEngine::SetExecution(bool bProcessEverything) { + FEPackage* pPack = PackList.GetFirstPackage(); + bExecuting = bProcessEverything; + while (pPack) { + pPack->bExecuting = bExecuting; + pPack = pPack->GetNext(); + } +} + +void FEngine::SetInitialState() { + PackageInitStateCB cb; + ForAllObjects(cb); + unsigned char i = 0; + while (i < NumJoyPads) { + pJoyPad[i].Reset(); + i++; + } + if (bMouseActive) { + Mouse.Reset(); + } +} + +FEPackage* FEngine::FindIdlePackage(const char* pName) const { + return static_cast(IdleList.FindNode(pName)); +} + +FEPackage* FEngine::FindPackageWithControl() { + FEPackage* pPack = PackList.GetLastPackage(); + while (pPack) { + if (pPack->Controllers) { + return pPack; + } + pPack = pPack->GetPrev(); + } + return nullptr; +} + +bool FEngine::ForAllObjects(FEObjectCallback& Callback) { + FEPackage* pPack = PackList.GetFirstPackage(); + while (pPack) { + if (!pPack->ForAllObjects(Callback)) { + return false; + } + pPack = pPack->GetNext(); + } + return true; +} + +// total size: 0x20 +struct FEMessageNode : public FEMinNode { + FEObject* pMsgFrom; // offset 0xC, size 0x4 + FEObject* pMsgTarget; // offset 0x10, size 0x4 + FEPackage* pFromPackage; // offset 0x14, size 0x4 + unsigned long MsgID; // offset 0x18, size 0x4 + unsigned long ControlMask; // offset 0x1C, size 0x4 + + ~FEMessageNode() override; +}; + +// total size: 0x20 +struct FEPackageCommand : public FENode { + int iCommand; // offset 0x14, size 0x4 + unsigned long uControlMask; // offset 0x18, size 0x4 + FEPackage* pPackage; // offset 0x1C, size 0x4 +}; + +FEMessageNode::~FEMessageNode() {} + +void FEngine::SetProcessInput(FEPackage* pkg, bool bProcess) { + if (!pkg) { + return; + } + pkg->SetInputEnabled(bProcess); +} + +FEPackage* FEngine::GetFirstLibrary() const { + return static_cast(LibraryList.GetHead()); +} + +void FEngine::AddToIdleList(FEPackage* pPack) { + IdleList.AddNode(IdleList.GetTail(), pPack); +} + +void FEngine::AddToLibraryList(FEPackage* pPack) { + LibraryList.AddNode(LibraryList.GetTail(), pPack); +} + +void FEngine::RemoveFromLibraryList(FEPackage* pPack) { + LibraryList.RemNode(pPack); +} + +FEPackage* FEngine::FindLibraryPackage(unsigned long NameHash) const { + FEPackage* pPack = GetFirstLibrary(); + while (pPack) { + if (FEHashUpper(pPack->pFilename + 2) == NameHash) { + return pPack; + } + pPack = pPack->GetNext(); + } + return nullptr; +} + +void FEngine::QueueMessage(unsigned long MsgID, FEObject* pFrom, FEPackage* pFromPackage, FEObject* pTo, unsigned long ControlMask) { + FEMessageNode* pNode = FENG_NEW FEMessageNode(); + pNode->MsgID = MsgID; + pNode->pMsgFrom = pFrom; + pNode->pFromPackage = pFromPackage; + pNode->pMsgTarget = pTo; + pNode->ControlMask = ControlMask; + if (bDebugMessages) { + int iVar2 = *reinterpret_cast(pInterface); + typedef void (*DebugFn)(void*, unsigned long, FEPackage*, FEObject*, FEObject*, unsigned long); + (*reinterpret_cast(iVar2 + 0xB4))( + reinterpret_cast(reinterpret_cast(pInterface) + *reinterpret_cast(iVar2 + 0xB0)), + MsgID, + pFromPackage, + pTo, + pFrom, + ControlMask); + } + MsgQ.AddTail(pNode); +} + +void FEngine::SendMessageToGame(unsigned long MsgID, FEObject* pFrom, FEPackage* pFromPackage, unsigned long uControlMask) { + int iVar1 = *reinterpret_cast(pInterface); + typedef void (*GameMsgFn)(void*, unsigned long, FEObject*, unsigned long, FEPackage*); + GameMsgFn fn = *reinterpret_cast(iVar1 + 0x3C); + void* adjusted = reinterpret_cast(reinterpret_cast(pInterface) + *reinterpret_cast(iVar1 + 0x38)); + fn(adjusted, MsgID, pFrom, uControlMask, pFromPackage); +} + +void FEngine::QueuePackageSwitch(const char* pPackageName, unsigned long ControlMask) { + QueuePackageCommand(3, ControlMask, pPackageName); +} + +void FEngine::QueuePackagePush(const char* pPackageName, unsigned long ControlMask) { + QueuePackageCommand(2, ControlMask, pPackageName); +} + +void FEngine::QueuePackagePop() { + QueuePackageCommand(1, 0, nullptr); +} + +FEPackageCommand* FEngine::FindQueuedNodeWithControl() { + FEPackageCommand* pCmd = static_cast(PackageCommands.GetTail()); + while (pCmd) { + if (pCmd->iCommand & 2) { + return pCmd; + } + pCmd = static_cast(pCmd->GetPrev()); + } + return nullptr; +} + +void FEngine::RecordLastPackageButton(unsigned long PackHash, unsigned long ButtonGUID) { + int i = 0; + do { + if (RecordedPackageButtons[i].PackageHash == PackHash) { + RecordedPackageButtons[i].PackageHash = 0; + } + i++; + } while (i < 32); + RecordedPackageButtons[NextButtonRecordIndex].PackageHash = PackHash; + RecordedPackageButtons[NextButtonRecordIndex].ButtonGUID = ButtonGUID; + NextButtonRecordIndex = (NextButtonRecordIndex + 1) % 32; +} + +unsigned long FEngine::RecallLastPackageButton(unsigned long PackHash) { + for (int i = 0; i < 32; i++) { + if (RecordedPackageButtons[i].PackageHash == PackHash) { + return RecordedPackageButtons[i].ButtonGUID; + } + } + return 0; +} + +bool FEngine::RecordPackageMarker(const char* pName) { + int idx = CurrentPackageRecordIndex; + if (idx == 16) { + return false; + } + CurrentPackageRecordIndex = idx + 1; + FEngStrCpy(RecordedPackageNames[idx], pName); + return true; +} + +const char* FEngine::RecallPackageMarker() { + if (CurrentPackageRecordIndex == 0) { + return nullptr; + } + return RecordedPackageNames[--CurrentPackageRecordIndex]; +} + +void FEngine::ClearPackageMarkers() { + { + unsigned long i = 0; + do { + RecordedPackageNames[i][0] = '\0'; + i++; + } while (i < 16); + } + CurrentPackageRecordIndex = 0; +} + +FEPackage* FEngine::LoadPackage(const void* pPackageData, bool bLoadAsLibrary) { + FEPackageReader reader; + FEPackage* pPack = reader.Load(pPackageData, pInterface, this, bLoadObjectNames, bLoadScriptNames, bLoadAsLibrary); + if (!pPack) { + return nullptr; + } + return pPack; +} + +bool FEngine::UnloadPackage(FEPackage* pPackage) { + FEPackage* pPack = PackList.GetFirstPackage(); + while (pPack) { + if (pPackage == pPack) { + bool bDelete; + if (pInterface) { + bDelete = pInterface->PackageWillUnload(pPack); + } else { + bDelete = true; + } + PackList.RemovePackage(pPackage); + FEPackageCommand* pTempNode = static_cast(PackageCommands.GetHead()); + while (pTempNode) { + FEPackageCommand* pNextNode = static_cast(pTempNode->GetNext()); + if (pTempNode->pPackage == pPackage) { + PackageCommands.RemNode(pTempNode); + if (pTempNode) { + delete pTempNode; + } + } + pTempNode = pNextNode; + } + if (pPack->UsesIdleList()) { + AddToIdleList(pPackage); + } else { + FENode* pLibName = static_cast(pPack->GetLibraryList().GetHead()); + while (pLibName) { + FEPackage* pLibPack = FindLibraryPackage(pLibName->GetNameHash()); + if (pLibPack) { + int Pri = pLibPack->GetPriority() - 1; + if (Pri < 1) { + UnloadLibraryPackage(pLibPack); + } else { + pLibPack->SetPriority(Pri); + } + } + pLibName = pLibName->GetNext(); + } + pPack->Shutdown(pInterface); + if (bDelete) { + if (pPack) { + delete pPack; + } + } + } + return true; + } + pPack = pPack->GetNext(); + } + return false; +} + +FEPackage* FEngine::PushPackage(const char* pPackageName, const unsigned char Level, const unsigned long ControlMask) { + FEPackage* pPack = FindIdlePackage(pPackageName); + if (!pPack) { + char len = static_cast(FEngStrLen(pPackageName)); + const char* pBaseName = pPackageName + len - 1; + char c = *pBaseName; + while (c != '/' && c != '\\' && len > 0) { + c = *--pBaseName; + len--; + } + if (len != 0) { + pBaseName++; + } + pPack = FindIdlePackage(pBaseName); + } + if (pPack) { + PackageInitStateCB cb; + pPack->bUseIdleList = true; + pPack->ForAllObjects(cb); + IdleList.RemNode(pPack); + } else { + unsigned char* pBlockStart; + bool bDeleteBlock; + unsigned char* pPackData = pInterface->GetPackageData(pPackageName, &pBlockStart, bDeleteBlock); + if (!pPackData) { + return nullptr; + } + pPack = LoadPackage(pPackData, false); + if (bDeleteBlock && pBlockStart) { + delete[] pBlockStart; + } + if (!pPack) { + return nullptr; + } + } + pPack->Controllers = ControlMask; + pPack->Priority = Level; + pPack->bExecuting = bExecuting; + if (pInterface) { + pInterface->PackageWasLoaded(pPack); + } + PackList.AddPackage(pPack); + return pPack; +} + +void FEngine::QueuePackageUserTransfer(FEPackage* pFrom, bool bToChild, unsigned long ControlMask) { + printf("If you get this, come see Gary or Lolley!\n"); + FEPackageCommand* pCom = FENG_NEW FEPackageCommand(); + pCom->iCommand = 0; + pCom->uControlMask = 0; + pCom->pPackage = pFrom; + pCom->uControlMask = pFrom->GetControlMask() & ControlMask; + pCom->iCommand = bToChild ? 8 : 4; + PackageCommands.AddTail(pCom); +} + +int FEngine::GetNumPackagesBelowPriority(unsigned char priority) { + int count = 0; + FEPackage* package = PackList.GetFirstPackage(); + while (package) { + if (package->GetPriority() < priority) { + count++; + } + package = package->GetNext(); + } + FEPackageCommand* pNode = static_cast(PackageCommands.GetHead()); + while (pNode) { + if (count == 0 && (pNode->iCommand & 3)) { + count = 1; + } else if (pNode->iCommand & 2) { + count++; + } else if (pNode->iCommand & 1) { + count--; + } + pNode = static_cast(pNode->GetNext()); + } + return count; +} + +void FEngine::ProcessObjectMessage(FEObject* pObj, FEPackage* pPack, unsigned long MsgID, unsigned long uControlMask) { + if (pObj->Type == FE_List) { + if (ProcessListBoxResponses(pObj, pPack, MsgID)) { + return; + } + } + if (pObj->Type == FE_CodeList) { + if (ProcessCodeListBoxResponses(pObj, pPack, MsgID)) { + return; + } + } + FEMessageResponse* pResp = pObj->FindResponse(MsgID); + if (pResp) { + ProcessResponses(pResp, pObj, pPack, uControlMask); + } +} + +void FEngine::ProcessGlobalMessage(FEPackage* pPack, unsigned long MsgID, unsigned long uControlMask) { + FEMessageResponse* pResp = pPack->FindResponse(MsgID); + if (pResp) { + ProcessResponses(pResp, nullptr, pPack, uControlMask); + } +} + +bool FEngine::ProcessListBoxResponses(FEObject* pObj, FEPackage* pPack, unsigned long MsgID) { + FEListBox* pList = static_cast(pObj); + switch (MsgID) { + case 0xe10c4af9: pList->ScrollSelection(-1, 0); return true; + case 0x030471ac: pList->ScrollSelection(1, 0); return true; + case 0xfb814f13: pList->ScrollSelection(0, -1); return true; + case 0xe10814a6: pList->ScrollSelection(0, 1); return true; + } + return false; +} + +bool FEngine::ProcessCodeListBoxResponses(FEObject* pObj, FEPackage* pPack, unsigned long MsgID) { + FECodeListBox* pList = static_cast(pObj); + switch (MsgID) { + case 0xe10c4af9: pList->ScrollSelection(-1, 0); return true; + case 0x030471ac: pList->ScrollSelection(1, 0); return true; + case 0xfb814f13: pList->ScrollSelection(0, -1); return true; + case 0xe10814a6: pList->ScrollSelection(0, 1); return true; + } + return false; +} + +void FEngine::UnloadLibraryPackage(FEPackage* pLibPack) { + bool bDelete = pInterface->UnloadUnreferencedLibrary(pLibPack); + if (bDelete) { + RemoveFromLibraryList(pLibPack); + bool bOwnsMemory; + if (pInterface) { + bOwnsMemory = pInterface->PackageWillUnload(pLibPack); + } else { + bOwnsMemory = true; + } + pLibPack->Shutdown(pInterface); + if (bOwnsMemory && pLibPack) { + delete pLibPack; + } + } +} + +void FEngine::Render() { + FEMatrix4 mView; + FEMatrix4 mIdentity; + mIdentity.Identify(); + pInterface->GetViewTransformation(&mView); + FEPackage* aPackages[32]; + int numPackages = 0; + for (FEPackage* pPack = PackList.GetFirstPackage(); pPack; pPack = pPack->GetNext()) { + aPackages[numPackages] = pPack; + numPackages++; + } + int i; + for (i = 0; i < numPackages; i++) { + PackList.RemovePackage(aPackages[i]); + } + for (i = 0; i < numPackages; i++) { + PackList.AddPackage(aPackages[i]); + } + FEPackage* pPack = PackList.GetFirstPackage(); + uGroupContext = 0; + while (pPack) { + pInterface->BeginPackageRendering(pPack); + Sorter.Zero(); + FEObject* pObj = pPack->GetFirstObject(); + while (pObj) { + if (pObj->Type == FE_Group) { + RenderGroup(static_cast(pObj), mIdentity, mView, 0); + } else { + RenderObject(pObj, mView, 0); + } + pObj = pObj->GetNext(); + } + Sorter.SortObjects(); + pInterface->RenderObjectList(reinterpret_cast(Sorter.GetListPtr()), Sorter.GetNumObjects()); + pInterface->EndPackageRendering(pPack); + pPack = pPack->GetNext(); + } + bRenderedRecently = bExecuting; +} + +void FEngine::RenderGroup(FEGroup* pGroup, FEMatrix4& mParent, FEMatrix4& mAccum, unsigned short RenderContext) { + FEObjData* pData = pGroup->GetObjData(); + FEMatrix4 stTemp; + FEMatrix4 stContext; + FEMatrix4 stContextView; + FEVector3 stOffset(0.0f); + FEVector3 stPivot(0.0f); + if (pData->Col.a != 0) { + if (bExecuting || static_cast(pGroup->Flags) >= 0) { + pData->Rot.GetMatrix(&stTemp); + stPivot = pData->Pivot; + stPivot *= -1.0f; + FEMultMatrix(&stOffset, &stTemp, &stPivot); + stTemp.m41 = stOffset.x + pData->Pivot.x + pData->Pos.x; + stTemp.m42 = stOffset.y + pData->Pivot.y + pData->Pos.y; + stTemp.m43 = stOffset.z + pData->Pivot.z + pData->Pos.z; + FEMultMatrix(&stContext, &stTemp, &mParent); + FEMultMatrix(&stContextView, &stContext, &mAccum); + unsigned short ctx = uGroupContext + 1; + uGroupContext = ctx; + pGroup->RenderContext = RenderContext; + pInterface->GenerateRenderContext(ctx, pGroup); + FEObject* pObj = pGroup->GetFirstChild(); + while (pObj) { + if (pObj->Type == FE_Group) { + RenderGroup(static_cast(pObj), stContext, mAccum, ctx); + } else { + RenderObject(pObj, stContextView, ctx); + } + pObj = pObj->GetNext(); + } + } + } +} + +void FEngine::RenderObject(FEObject* pObj, FEMatrix4& mParent, unsigned short RenderContext) { + FEObjData* pData = pObj->GetObjData(); + if (pData->Col.a != 0) { + FEVector3 pos(pData->Pivot); + FEVector3 result; + result.z = 0.0f; + result.y = 0.0f; + result.x = 0.0f; + pos.x = pos.x + pData->Pos.x; + pos.y = pos.y + pData->Pos.y; + pos.z = pData->Pos.z + pos.z; + FEMultMatrix(&result, &mParent, &pos); + pObj->RenderContext = RenderContext; + if (result.z > 0.0f) { + Sorter.AddObject(pObj, result.z); + } + } +} + +void FEngine::QueuePackageCommand(long command, unsigned long ControlMask, const char* pPackageName) { + FEPackageCommand* pCom = nullptr; + FEPackage* pPackageWithControl = FindPackageWithControl(); + FEPackageCommand* Node = FENG_NEW FEPackageCommand(); + Node->iCommand = 0; + Node->uControlMask = 0; + Node->pPackage = pPackageWithControl; + if (pPackageWithControl) { + if (ControlMask == 0) { + Node->uControlMask = pPackageWithControl->GetControlMask(); + } else { + Node->uControlMask = ControlMask; + } + pPackageWithControl->SetOldControlMask(pPackageWithControl->GetControlMask()); + pPackageWithControl->SetControlMask(0); + } else { + pCom = FindQueuedNodeWithControl(); + if (pCom) { + if (ControlMask == 0) { + Node->uControlMask = pCom->uControlMask; + } else { + Node->uControlMask = ControlMask; + } + } else { + if (ControlMask == 0) { + Node->uControlMask = 0xFF; + } else { + Node->uControlMask = ControlMask; + } + } + } + Node->iCommand = command; + Node->SetName(pPackageName); + PackageCommands.AddTail(Node); +} + +void FEngine::Update(const long tDeltaTicks, unsigned int lock) { + FEPackage* pPackage; + if (bDebugMessages) { + pInterface->DebugMessageBeginUpdate(); + } + if (bExecuting) { + PadHoldRegistered = 0; + if (bMouseActive) { + FEMouseInfo Info; + pInterface->GetMouseInfo(Info); + Mouse.Update(Info, tDeltaTicks); + } + for (unsigned char PadIndex = 0; PadIndex < NumJoyPads; PadIndex++) { + pJoyPad[PadIndex].Update(pInterface->GetJoyPadMask(PadIndex), tDeltaTicks); + } + for (pPackage = PackList.GetFirstPackage(); pPackage; pPackage = pPackage->GetNext()) { + if (pPackage->IsInputEnabled() && + (!bErrorScreenMode || pPackage->IsErrorScreen())) { + ProcessPadsForPackage(pPackage); + if (bMouseActive) { + ProcessMouseForPackage(pPackage); + } + } + } + unsigned long i = 0; + unsigned long MaskBit = 1; + do { + if ((PadHoldRegistered & MaskBit) != 0) { + for (unsigned char PadIdx = 0; PadIdx < NumJoyPads; PadIdx++) { + pJoyPad[PadIdx].DecrementHold(MaskBit, HoldDecrement[i]); + } + } + HoldDecrement[i] = 0; + i++; + MaskBit <<= 1; + } while (i < 19); + FastRep = FastRepCache; + } + if (bExecuting) { + int iTicksRemaining = tDeltaTicks; + do { + int iIterationTicks; + if (!bRenderedRecently) { + FEPackage::uHoldDirtyFlags = 0xFFFFFFFF; + } else { + FEPackage::uHoldDirtyFlags = 0; + } + pPackage = PackList.GetFirstPackage(); + iIterationTicks = 0; + while (pPackage) { + FEPackage* pCachedNext = pPackage->GetNext(); + if (!bErrorScreenMode || pPackage->IsErrorScreen()) { + pPackage->Update(this, iTicksRemaining); + } + pPackage = pCachedNext; + } + ProcessMessageQueue(); + if (!bErrorScreenMode) { + ProcessPackageCommands(); + } + if (MsgQ.GetHead()) { + ProcessMessageQueue(); + } + bRenderedRecently = false; + iTicksRemaining = iIterationTicks; + } while (iTicksRemaining); + } else { + for (pPackage = PackList.GetFirstPackage(); pPackage; pPackage = pPackage->GetNext()) { + if (!bErrorScreenMode || pPackage->IsErrorScreen()) { + pPackage->Update(this, tDeltaTicks); + } + } + } + if (bDebugMessages) { + pInterface->DebugMessageEndUpdate(); + } +} + +struct ImpulseDirEntry { + unsigned char dir0; + unsigned char dir1; + unsigned short directionIndex; +}; + +static unsigned long PadButtonHash[19] = { + 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, + 0x406415E3u, 0x911AB364u, 0xB5AF2461u, 0x5073EF13u, + 0xD9FEEC59u, 0xC519BFBFu, 0xC519BFC0u, 0xC519BFC1u, + 0xC519BFC2u, 0xC519BFC3u, 0xC519BFC4u, 0xC519BFC5u, + 0xC519BFC6u, 0xC519BFC7u, 0xC519BFC8u, +}; + +static unsigned long PadButtonHeldHash[2] = { + 0x447315AFu, 0x20AD4EB5u, +}; + +static unsigned long PadReleasedHash[19] = { + 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, + 0xC12E9E27u, 0xC2F8FCC8u, 0xEBFCDA65u, 0x091DCD57u, + 0x7A39195Du, 0xD4671F83u, 0xD871B0A4u, 0xDC7C41C5u, + 0xE086D2E6u, 0xE4916407u, 0xE89BF528u, 0xECA68649u, + 0xF0B1176Au, 0xF4BBA88Bu, 0xF8C639ACu, +}; + +unsigned long FEDirection_Message[8] = { + 0x72619778u, 0x6FD81B16u, 0xB5971BF1u, 0xAB1A49C9u, + 0x911C0A4Bu, 0x79891376u, 0x9120409Eu, 0x6FFB6F23u, +}; + +static ImpulseDirEntry ImpulseDir[8] = { + {0x00, 0x02, 0x0007}, {0x00, 0x03, 0x0001}, {0x01, 0x02, 0x0005}, {0x01, 0x03, 0x0003}, + {0x00, 0xFF, 0x0000}, {0x02, 0xFF, 0x0006}, {0x01, 0xFF, 0x0004}, {0x03, 0xFF, 0x0002}, +}; + +inline int FEFramesToTicks(int Frames) { return Frames * 16; } + +void FEngine::ProcessPadsForPackage(FEPackage* pPackage) { + unsigned long Pressed; + unsigned long Released; + unsigned long Held; + unsigned long Mask; + unsigned long HeldFor[19]; + unsigned char FromPadHeld[19]; + unsigned char FromPadPressed[19]; + unsigned char FromPadReleased[19]; + unsigned char PadIndex; + unsigned long i; + unsigned long JoyMask; + bool bSomethingActive; + + JoyMask = pPackage->GetControlMask(); + if (JoyMask == 0) { + return; + } + + bSomethingActive = false; + for (PadIndex = 0; PadIndex < NumJoyPads; PadIndex++) { + if ((JoyMask & (1 << PadIndex)) != 0) { + bSomethingActive = bSomethingActive | pJoyPad[PadIndex].WasActive(); + } + } + if (!bSomethingActive) { + return; + } + + FEngMemSet(HeldFor, 0, sizeof(HeldFor)); + FEngMemSet(FromPadHeld, 0, 19); + FEngMemSet(FromPadPressed, 0, 19); + FEngMemSet(FromPadReleased, 0, 19); + + i = 4; + while (i < 19 && pPackage->IsInputEnabled()) { + FEObject* pCurButton; + JoyMask = pPackage->GetControlMask(); + Mask = 1 << i; + Pressed = 0; + Released = 0; + Held = 0; + + { + for (unsigned char PadIndex = 0; PadIndex < NumJoyPads; PadIndex++) { + if ((JoyMask & (1 << PadIndex)) != 0) { + if (pJoyPad[PadIndex].WasPressed(Mask)) { + Pressed = Pressed | Mask; + FromPadPressed[i] = FromPadPressed[i] | static_cast(1 << PadIndex); + } + if (pJoyPad[PadIndex].WasReleased(Mask)) { + Released = Released | Mask; + FromPadReleased[i] = FromPadReleased[i] | static_cast(1 << PadIndex); + } + if (pJoyPad[PadIndex].WasHeld(Mask)) { + Held = Held | Mask; + HeldFor[i] = HeldFor[i] > pJoyPad[PadIndex].HeldFor(Mask) + ? HeldFor[i] + : pJoyPad[PadIndex].HeldFor(Mask); + FromPadHeld[i] = FromPadHeld[i] | static_cast(1 << PadIndex); + } + } + } + } + + if (i == 4 && pPackage->StartEqualsAccept()) { + for (unsigned char PadIndex = 0; PadIndex < NumJoyPads; PadIndex++) { + if ((JoyMask & (1 << PadIndex)) != 0) { + if (pJoyPad[PadIndex].WasPressed(0x40)) { + Pressed = Pressed | Mask; + FromPadPressed[i] = FromPadPressed[i] | static_cast(1 << PadIndex); + } + if (pJoyPad[PadIndex].WasReleased(0x40)) { + Released = Released | Mask; + FromPadReleased[i] = FromPadReleased[i] | static_cast(1 << PadIndex); + } + if (pJoyPad[PadIndex].WasHeld(0x40)) { + Held = Held | Mask; + HeldFor[i] = HeldFor[i] > pJoyPad[PadIndex].HeldFor(0x40) + ? HeldFor[i] + : pJoyPad[PadIndex].HeldFor(0x40); + FromPadHeld[i] = FromPadHeld[i] | static_cast(1 << PadIndex); + } + } + } + } + + if ((Held | Released | Pressed) != 0) { + pCurButton = pPackage->GetCurrentButton(); + + if (i > 8) goto outer_range; + if (i >= 7) goto held_handler; + if (i == 4) goto accept_handler; + if (i < 4) goto check_released; + goto default_press; + + outer_range: + if (i > 18) goto check_released; + goto default_press; + + accept_handler: + if ((Pressed & 0x10) == 0) goto check_released; + HeldButtons[4] = pCurButton; + if (pCurButton && pCurButton->FindResponse(0x0C407210u) != nullptr) { + QueueMessage(0x0C407210u, nullptr, pPackage, pPackage->GetCurrentButton(), FromPadPressed[4]); + QueueMessage( + 0x0C407210u, + pPackage->GetCurrentButton(), + pPackage, + reinterpret_cast(0xFFFFFFFB), + FromPadPressed[4]); + } else if (pPackage->FindResponse(0x406415E3u) != nullptr) { + QueueMessage(0x406415E3u, nullptr, pPackage, reinterpret_cast(0xFFFFFFFD), FromPadPressed[4]); + QueueMessage(0x406415E3u, nullptr, pPackage, reinterpret_cast(0xFFFFFFFB), FromPadPressed[4]); + } + goto check_released; + + held_handler: + if ((Held & Mask) != 0) { + unsigned long PadMask = FromPadPressed[i]; + unsigned long MsgID = PadButtonHeldHash[i - 7]; + if (pCurButton && pCurButton->FindResponse(MsgID) != nullptr) { + QueueMessage(MsgID, nullptr, pPackage, pCurButton, PadMask); + QueueMessage(MsgID, pCurButton, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + } else if (pPackage->FindResponse(MsgID) != nullptr) { + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFD), PadMask); + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + } + } + + default_press: + if ((Pressed & Mask) != 0) { + unsigned long PadMask = FromPadPressed[i]; + HeldButtons[i] = pCurButton; + unsigned long MsgID = PadButtonHash[i]; + if (pCurButton && pCurButton->FindResponse(MsgID) != nullptr) { + QueueMessage(MsgID, nullptr, pPackage, pCurButton, PadMask); + QueueMessage(MsgID, pCurButton, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + } else if (pPackage->FindResponse(MsgID) != nullptr) { + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFD), PadMask); + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + } + } + + check_released: + if ((Released & Mask) != 0) { + unsigned long PadMask = FromPadReleased[i]; + unsigned long MsgID = PadReleasedHash[i]; + if (HeldButtons[i] == pCurButton && pCurButton != nullptr) { + HeldButtons[i] = nullptr; + if (i == 4) { + MsgID = 0x936A6A7Fu; + } + if (pCurButton->FindResponse(MsgID) != nullptr) { + QueueMessage(MsgID, nullptr, pPackage, pCurButton, PadMask); + QueueMessage(MsgID, pCurButton, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + } + } + PadMask = FromPadReleased[i]; + if (pPackage->FindResponse(MsgID) != nullptr) { + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFD), PadMask); + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + } + } + + if (MsgQ.GetNumElements() != 0) { + ProcessMessageQueue(); + } + } + i = i + 1; + } + + // Direction pad processing + JoyMask = pPackage->GetControlMask(); + Pressed = 0; + i = 0; + while (i < 4 && pPackage->IsInputEnabled()) { + Mask = 1 << i; + { + for (unsigned char PadIndex = 0; PadIndex < NumJoyPads; PadIndex++) { + if ((JoyMask & (1 << PadIndex)) != 0) { + if (pJoyPad[PadIndex].WasPressed(Mask)) { + Pressed = Pressed | Mask; + FromPadPressed[i] = FromPadPressed[i] | static_cast(1 << PadIndex); + } + pJoyPad[PadIndex].WasReleased(Mask); + if (pJoyPad[PadIndex].WasHeld(Mask)) { + HeldFor[i] = HeldFor[i] > pJoyPad[PadIndex].HeldFor(Mask) + ? HeldFor[i] + : pJoyPad[PadIndex].HeldFor(Mask); + FromPadHeld[i] = FromPadHeld[i] | static_cast(1 << PadIndex); + } + } + } + } + i = i + 1; + } + + i = 0; + { + unsigned long Result; + unsigned long Compare; + unsigned long JustPressed; + unsigned long PadMask; + FEObject* pCurButton; + while (true) { + if (i > 7) { + return; + } + if (!pPackage->IsInputEnabled()) { + return; + } + + pCurButton = pPackage->GetCurrentButton(); + if (ImpulseDir[i].dir1 != 0xFF) { + Result = HeldFor[ImpulseDir[i].dir1]; + if (HeldFor[ImpulseDir[i].dir0] < HeldFor[ImpulseDir[i].dir1]) { + Result = HeldFor[ImpulseDir[i].dir0]; + } + JustPressed = + (Pressed >> ImpulseDir[i].dir0) & (Pressed >> ImpulseDir[i].dir1); + PadMask = + (FromPadPressed[ImpulseDir[i].dir0] & FromPadPressed[ImpulseDir[i].dir1]) | + (FromPadHeld[ImpulseDir[i].dir0] & FromPadHeld[ImpulseDir[i].dir1]); + } else { + JustPressed = Pressed >> ImpulseDir[i].dir0; + Result = HeldFor[ImpulseDir[i].dir0]; + PadMask = FromPadPressed[ImpulseDir[i].dir0] | FromPadHeld[ImpulseDir[i].dir0]; + } + + Compare = FEFramesToTicks(20); + if ((FastRep & (1 << i)) != 0) { + Compare = 0x78; + } + if (Compare <= Result) { + break; + } + if ((JustPressed & 1) == 0) { + if (Result == 0) { + FastRepCache = FastRepCache & ~(1 << i); + } + } else if (Result == 0) { + break; + } + if (MsgQ.GetNumElements() != 0) { + ProcessMessageQueue(); + } + i = i + 1; + } + + if (Result != 0) { + FastRepCache = FastRepCache | (1 << i); + } + HoldDecrement[ImpulseDir[i].dir0] = Compare; + if (ImpulseDir[i].dir1 != 0xFF) { + HoldDecrement[ImpulseDir[i].dir1] = Compare; + HeldFor[ImpulseDir[i].dir0] = 0; + HeldFor[ImpulseDir[i].dir1] = 0; + PadHoldRegistered = + PadHoldRegistered | + (1 << ImpulseDir[i].dir0) | + (1 << ImpulseDir[i].dir1); + goto fire_direction; + } + { + HeldFor[ImpulseDir[i].dir0] = 0; + PadHoldRegistered = PadHoldRegistered | (1 << ImpulseDir[i].dir0); + } + + fire_direction: + if (pCurButton) { + FEObject* pNewButton = nullptr; + unsigned long MsgID = FEDirection_Message[ImpulseDir[i].directionIndex]; + FEMessageResponse* pResponse = pCurButton->FindResponse(MsgID); + if (pResponse) { + QueueMessage(MsgID, nullptr, pPackage, pCurButton, PadMask); + if ((pCurButton->Flags & 0x80000) == 0) { + if (pResponse->FindResponse(0x104) == -1) { + pNewButton = pPackage->GetButtonMap()->GetButtonFrom(pCurButton, ImpulseDir[i].directionIndex, pInterface, WrapMode); + } + } + } else { + if ((pCurButton->Flags & 0x80000) == 0) { + pNewButton = pPackage->GetButtonMap()->GetButtonFrom(pCurButton, ImpulseDir[i].directionIndex, pInterface, WrapMode); + } + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFD), PadMask); + } + QueueMessage(MsgID, pCurButton, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + if (pNewButton != nullptr) { + for (unsigned long j = 4; j < 19; j++) { + if (HeldButtons[j] != nullptr && pCurButton != nullptr) { + unsigned long PadMask; + unsigned long MsgID; + HeldButtons[j] = nullptr; + PadMask = FromPadReleased[j]; + MsgID = PadReleasedHash[j]; + if (j == 4) { + MsgID = 0x936A6A7Fu; + } + if (pCurButton->FindResponse(MsgID) != nullptr) { + QueueMessage(MsgID, nullptr, pPackage, pCurButton, PadMask); + QueueMessage(MsgID, pCurButton, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + } + } + } + pPackage->SetCurrentButton(pNewButton, true); + } + } else { + unsigned long MsgID = FEDirection_Message[ImpulseDir[i].directionIndex]; + if (pPackage->FindResponse(MsgID) != nullptr) { + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFD), PadMask); + QueueMessage(MsgID, nullptr, pPackage, reinterpret_cast(0xFFFFFFFB), PadMask); + } + } + } +} + +void FEngine::ProcessMouseForPackage(FEPackage* pPackage) { + unsigned long JoyMask = pPackage->GetControlMask(); + if (JoyMask != 0 && ((JoyMask ^ 1) & 1) == 0 && pPackage->IsInputEnabled()) { + float mx = static_cast(Mouse.XPos); + float my = static_cast(Mouse.YPos); + int NumMO = pPackage->NumMouseObjects; + FEObjectMouseState* pStates = pPackage->MouseObjectStates; + for (int i = 0; i < NumMO; i++) { + UpdateMouseState(pPackage, pStates + i, mx, my); + } + } +} + +void FEngine::UpdateMouseState(FEPackage* pkg, FEObjectMouseState* state, float mx, float my) { + FEObject* obj = state->pObject; + if (obj && (obj->Flags & 0x14000000) == 0x14000000) { + return; + } + float objX, objY; + FEngGetCenter(obj, objX, objY); + bool is_mouse_over = pInterface->DoesPointTouchObject( + mx - (state->Offset.h - objX), + my - (state->Offset.v - objY), + obj); + bool is_left_down = Mouse.IsDown(1); + bool is_right_down = Mouse.IsDown(2); + bool was_mouse_over = state->GetBit(1); + bool was_mouse_left_down = state->GetBit(2); + bool was_mouse_right_down = state->GetBit(4); + + if (is_mouse_over) { + unsigned int msg = 0x13f4bd45; + if (was_mouse_over) { + msg = 0xb30d0683; + } + cFEng::mInstance->QueuePackageMessage(msg, pkg->name, obj); + } else { + if (was_mouse_over) { + cFEng::mInstance->QueuePackageMessage(0xb30793c1, pkg->name, obj); + } + } + + if (is_left_down) { + if (was_mouse_left_down) { + cFEng::mInstance->QueuePackageMessage(0x1e646b2e, pkg->name, obj); + } else { + if (!is_mouse_over) { + goto skip_left; + } + cFEng::mInstance->QueuePackageMessage(0xf459b307, pkg->name, obj); + } + } else { + if (was_mouse_left_down && is_mouse_over) { + cFEng::mInstance->QueuePackageMessage(0x7eabca56, pkg->name, obj); + } + } +skip_left: + + if (is_right_down) { + if (was_mouse_right_down) { + cFEng::mInstance->QueuePackageMessage(0x0da2f4e1, pkg->name, obj); + } else if (is_mouse_over) { + cFEng::mInstance->QueuePackageMessage(0xce59c3da, pkg->name, obj); + } else { + goto set_bits; + } + if (!is_mouse_over) { + goto set_bits; + } + } else { + if (was_mouse_right_down) { + if (!is_mouse_over) { + goto set_bits; + } + cFEng::mInstance->QueuePackageMessage(0x98adf589, pkg->name, obj); + } + if (!is_mouse_over) { + goto set_bits; + } + } +set_bits: + state->SetBit(1, is_mouse_over); + state->SetBit(2, is_left_down); + state->SetBit(4, is_right_down); +} + +void FEngine::ProcessMessageQueue() { + FEMessageNode* pNode = static_cast(MsgQ.RemHead()); + FEPackage* pPack; + while (pNode) { + if (bDebugMessages) { + pInterface->DebugMessageProcessed(pNode->MsgID, pNode->pMsgTarget, pNode->pMsgFrom, pNode->pFromPackage, pNode->ControlMask); + } + switch (reinterpret_cast(pNode->pMsgTarget)) { + case 0: { + pPack = PackList.GetFirstPackage(); + while (pPack) { + ProcessGlobalMessage(pPack, pNode->MsgID, pNode->ControlMask); + FEMsgTargetList* pTargList = pPack->GetMessageTargets(pNode->MsgID); + if (pTargList) { + unsigned long Count = pTargList->GetCount(); + unsigned long i = 0; + unsigned long MsgID = pNode->MsgID; + while (i < Count) { + ProcessObjectMessage(pTargList->GetTarget(i), pPack, MsgID, pNode->ControlMask); + i++; + } + } + pPack = pPack->GetNext(); + } + break; + } + case 0xFFFFFFFF: + pInterface->NotificationMessage(pNode->MsgID, pNode->pMsgFrom, pNode->ControlMask, reinterpret_cast(pNode->pFromPackage)); + break; + case 0xFFFFFFFE: + pPack = PackList.GetFirstPackage(); + while (pPack) { + ProcessGlobalMessage(pPack, pNode->MsgID, pNode->ControlMask); + pPack = pPack->GetNext(); + } + break; + case 0xFFFFFFFD: + ProcessGlobalMessage(pNode->pFromPackage, pNode->MsgID, pNode->ControlMask); + break; + case 0xFFFFFFFC: { + pPack = PackList.GetFirstPackage(); + while (pPack && pPack != pNode->pFromPackage) { + pPack = pPack->GetNext(); + } + if (pPack) { + ProcessGlobalMessage(pPack, pNode->MsgID, pNode->ControlMask); + FEMsgTargetList* pTargList = pPack->GetMessageTargets(pNode->MsgID); + if (pTargList) { + unsigned long Count = pTargList->GetCount(); + unsigned long i = 0; + unsigned long MsgID = pNode->MsgID; + while (i < Count) { + ProcessObjectMessage(pTargList->GetTarget(i), pPack, MsgID, pNode->ControlMask); + i++; + } + } + } + break; + } + case 0xFFFFFFFB: + pInterface->NotifySoundMessage(pNode->MsgID, pNode->pMsgFrom, pNode->ControlMask, reinterpret_cast(pNode->pFromPackage)); + break; + case 0xFFFFFFFA: { + switch (pNode->MsgID) { + case 0x5d4ce32d: + SetProcessInput(pNode->pFromPackage, false); + break; + case 0x59bed120: + SetProcessInput(pNode->pFromPackage, true); + break; + } + break; + } + default: + ProcessObjectMessage(pNode->pMsgTarget, pNode->pFromPackage, pNode->MsgID, pNode->ControlMask); + break; + } + delete pNode; + pNode = static_cast(MsgQ.RemHead()); + } +} + +void FEngine::ProcessResponses(FEMessageResponse* pRespList, FEObject* pObj, FEPackage* pPack, unsigned long ControlMask) { + unsigned long Count = pRespList->GetCount(); + for (unsigned long i = 0; i < Count; i++) { + FEResponse* pResp = pRespList->GetResponse(i); + switch (pResp->ResponseID) { + case 0: + if (pObj) { + FEScript* pScript = pObj->FindScript(pResp->ResponseParam); + if (pScript) { + pObj->SetCurrentScript(pScript); + pScript->CurTime = 0; + } + } + break; + case 1: { + FEObject* pTo = reinterpret_cast(pResp->ResponseTarget); + if (reinterpret_cast(pTo) != 0xFFFFFFFC && reinterpret_cast(pTo) != 0xFFFFFFFF) { + pTo = pPack->FindObjectByGUID(pResp->ResponseTarget); + } + QueueMessage(pResp->ResponseParam, pObj, pPack, pTo, ControlMask); + break; + } + case 2: + QueueMessage(pResp->ResponseParam, pObj, pPack, reinterpret_cast(0xFFFFFFFF), ControlMask); + break; + case 3: + QueueMessage(pResp->ResponseParam, pObj, pPack, reinterpret_cast(0xFFFFFFFB), ControlMask); + break; + case 0x100: { + FEObject* pButton; + if (pResp->ResponseParam != 0) { + pButton = pPack->FindObjectByGUID(pResp->ResponseParam); + } else { + pButton = nullptr; + } + if (!pButton && pResp->ResponseParam != 0) { + break; + } + pPack->SetCurrentButton(pButton, pButton != nullptr); + break; + } + case 0x101: + SetProcessInput(pPack, pResp->ResponseParam == 1); + break; + case 0x102: + if (pPack->GetCurrentButton()) { + RecordLastPackageButton(pPack->GetNameHash(), pPack->GetCurrentButton()->GUID); + } else { + RecordLastPackageButton(pPack->GetNameHash(), 0); + } + break; + case 0x103: { + FEObject* pButton = nullptr; + unsigned long recalled = RecallLastPackageButton(pPack->GetNameHash()); + if (recalled != 0) { + pButton = pPack->FindObjectByGUID(recalled); + } + if (!pButton) { + if (pResp->ResponseParam != 0) { + pButton = pPack->FindObjectByGUID(pResp->ResponseParam); + } + if (!pButton && pResp->ResponseParam != 0) { + break; + } + } + pPack->SetCurrentButton(pButton, pButton != nullptr); + break; + } + case 0x105: + QueuePackageUserTransfer(pPack, true, ControlMask); + break; + case 0x106: + QueuePackageUserTransfer(pPack, true, 0xFF); + break; + case 0x107: + QueuePackageUserTransfer(pPack, false, ControlMask); + break; + case 0x108: + QueuePackageUserTransfer(pPack, false, 0xFF); + break; + case 0x200: + QueuePackageSwitch(reinterpret_cast(pResp->ResponseParam), pPack->GetControlMask()); + break; + case 0x201: + QueuePackagePush(reinterpret_cast(pResp->ResponseParam), pPack->GetControlMask()); + break; + case 0x202: { + unsigned long pad = 0; + do { + if (ControlMask & (1 << pad)) { + QueuePackagePush(reinterpret_cast(pResp->ResponseParam), ControlMask); + } + pad++; + } while (pad < 8); + break; + } + case 0x204: + QueuePackagePush(reinterpret_cast(pResp->ResponseParam), 0); + break; + case 0x203: + QueuePackagePop(); + break; + case 0x2c0: + RecordPackageMarker(pPack->GetName()); + break; + case 0x2c1: { + const char* pMarker = RecallPackageMarker(); + if (pMarker) { + QueuePackageSwitch(pMarker, pPack->GetControlMask()); + } + break; + } + case 0x2c2: + ClearPackageMarkers(); + break; + case 0x300: + if (pObj->pCurrentScript->ID != pResp->ResponseParam) { + i = pRespList->FindConditionBranchTarget(i); + } + break; + case 0x301: + if (pObj->pCurrentScript->ID == pResp->ResponseParam) { + i = pRespList->FindConditionBranchTarget(i); + } + break; + case 0x500: + i = pRespList->FindConditionBranchTarget(i); + break; + } + } +} + +void FEngine::ProcessPackageCommands() { + FEPackage* pFixParentLink = nullptr; + FEPackage* pNewParentLink = nullptr; + + do { + FEPackageCommand* pNode = static_cast(PackageCommands.RemHead()); + if (!pNode) { + return; + } + + int Level; + if (pNode->pPackage) { + Level = pNode->pPackage->Priority; + } else { + FEPackage* pPack = FindPackageWithControl(); + pNode->pPackage = pPack; + if (pPack) { + Level = pPack->Priority; + pPack->OldControllers = pPack->Controllers; + pNode->pPackage->Controllers = 0; + } else { + Level = -1; + } + } + + if (pNode->iCommand & 1) { + if (Level >= 0) { + if (!(pNode->iCommand & 2)) { + PackList.ReplaceParentLinks(pNode->pPackage, pNode->pPackage->pParentPackage); + } else { + pFixParentLink = pNode->pPackage; + pNewParentLink = pNode->pPackage->pParentPackage; + } + FEPackage* pParent = pNode->pPackage->pParentPackage; + if (pParent) { + pParent->Controllers = pParent->OldControllers; + } + UnloadPackage(pNode->pPackage); + Level--; + } + } + + if (pNode->iCommand & 2) { + FEPackage* pPushed = PushPackage(pNode->GetName(), + static_cast(Level + 1), pNode->uControlMask); + if (pPushed && (pNode->iCommand & 1) == 0 && Level >= 0) { + pPushed->pParentPackage = pNode->pPackage; + } else if (pNode->iCommand & 1) { + pPushed->pParentPackage = pNewParentLink; + PackList.ReplaceParentLinks(pFixParentLink, pPushed); + } + } + + if (pNode->iCommand & 4) { + FEPackage* pPack = pNode->pPackage; + FEPackage* pParent = pPack->pParentPackage; + if (pParent) { + unsigned long PassedMask = pPack->Controllers & pNode->uControlMask; + pPack->Controllers &= ~PassedMask; + pParent->Controllers |= PassedMask; + QueueMessage(0x334c5493u, nullptr, pParent, + reinterpret_cast(0xFFFFFFFCu), pNode->uControlMask); + } + } + + if (pNode->iCommand & 8) { + FEPackage* pChild = PackList.GetFirstPackage(); + while (true) { + if (!pChild) { + break; + } + if (pChild->pParentPackage == pNode->pPackage) + break; + pChild = pChild->GetNext(); + } + if (pChild) { + unsigned long PassedMask = pNode->pPackage->Controllers & pNode->uControlMask; + pNode->pPackage->Controllers &= ~PassedMask; + pChild->Controllers |= PassedMask; + QueueMessage(0x334c5493u, nullptr, pChild, + reinterpret_cast(0xFFFFFFFCu), pNode->uControlMask); + } + } + + delete pNode; + } while (true); +} diff --git a/src/Speed/Indep/Src/FEng/ObjectPool.h b/src/Speed/Indep/Src/FEng/ObjectPool.h index bf5082204..c0200db91 100644 --- a/src/Speed/Indep/Src/FEng/ObjectPool.h +++ b/src/Speed/Indep/Src/FEng/ObjectPool.h @@ -5,6 +5,83 @@ #pragma once #endif +#include +#include "FEList.h" +#include "FEngStandard.h" +void FEngFree(void* ptr); + +template +struct FEPoolNode : public FEMinNode { + T Pool[N]; + FEMinList Free; + int Used; + + inline FEPoolNode() : Used(0) { + for (int i = 0; i < N; i++) { + Free.AddTail(&Pool[i]); + } + } + + ~FEPoolNode() override; + + inline FEPoolNode* GetNext() { return static_cast(FEMinNode::GetNext()); } +}; + +template +FEPoolNode::~FEPoolNode() { + while (Free.GetNumElements() != 0) { + Free.RemHead(); + } +} + +template +struct ObjectPool { + FEMinList Pools; + + inline T* AllocSingle() { + FEPoolNode* pPool = static_cast*>(Pools.GetHead()); + while (pPool) { + if (pPool->Free.GetNumElements() != 0) { + break; + } + pPool = pPool->GetNext(); + } + if (!pPool) { + pPool = FENG_NEW FEPoolNode(); + Pools.AddHead(pPool); + } + T* pNode = static_cast(pPool->Free.RemHead()); + pPool->Used++; + return pNode; + } + + inline void FreeSingleNoDestroy(T* pNode) { + FEPoolNode* pPool = static_cast*>(Pools.GetHead()); + while (pPool) { + bool bInPool = false; + if (pNode >= &pPool->Pool[0]) { + bInPool = pNode < &pPool->Pool[N]; + } + if (bInPool) { + pPool->Free.AddNode(pPool->Free.GetTail(), pNode); + pPool->Used--; + if (pPool->Used == 0) { + Pools.RemNode(pPool); + if (pPool) { + delete pPool; + } + } + return; + } + pPool = pPool->GetNext(); + } + } + + inline void FreeSingle(T* pNode) { + pNode->~T(); + FreeSingleNoDestroy(pNode); + } +}; #endif diff --git a/src/Speed/Indep/Src/FEng/cFEng.h b/src/Speed/Indep/Src/FEng/cFEng.h new file mode 100644 index 000000000..3d3e3b9e4 --- /dev/null +++ b/src/Speed/Indep/Src/FEng/cFEng.h @@ -0,0 +1,62 @@ +#ifndef _CFENG +#define _CFENG + +#include "FEPackage.h" +#include "FEngine.h" + +enum FE_PACKAGE_PRIORITY { + FE_PACKAGE_PRIORITY_FIFTH_CLOSEST = 100, + FE_PACKAGE_PRIORITY_FOURTH_CLOSEST = 101, + FE_PACKAGE_PRIORITY_THIRD_CLOSEST = 102, + FE_PACKAGE_PRIORITY_SECOND_CLOSEST = 103, + FE_PACKAGE_PRIORITY_CLOSEST = 104, + FE_PACKAGE_PRIORITY_ERROR = 105, +}; + +// total size: 0x8 +struct cFEng { + static cFEng* mInstance; + + FEngine* mFEng; // offset 0x0, size 0x4 + bool bWasPaused; // offset 0x4, size 0x1 + + static void Init(); + static inline cFEng* Get() { return mInstance; } + + cFEng(); + bool IsErrorState() { return mFEng->bErrorScreenMode; } + + FEPackage* FindPackage(const char* pPackageName); + FEPackage* FindPackageWithControl(); + + void QueueGameMessagePkg(unsigned int pMessage, FEPackage* topkg); + void QueueGameMessage(unsigned int pMessage, const char* pPackageName, unsigned int controlMask); + bool IsPackagePushed(const char* packageName); + void PushNoControlPackage(const char* pPackageName, FE_PACKAGE_PRIORITY pPriority); + void PopNoControlPackage(const char* pPackageName); + + void QueuePackageMessage(unsigned int msg, const char* pkg_name, FEObject* obj); + + void QueuePackageSwitch(const char* pkg_name, int arg, unsigned long param, bool b); + void QueuePackagePush(const char* pPackageName, int pArg, unsigned long ControlMask, bool pSuppressSimPause); + void QueuePackagePop(int numPackagesToPop); + void QueueSoundMessage(unsigned int pMessage, const char* pPackageName); + + void DrawForeground(); + void PushErrorPackage(const char* pPackageName, int pArg, unsigned long ControlMask); + void PopErrorPackage(); + void PopErrorPackage(int); + void PauseAllSystems(); + void ResumeAllSystems(bool); + void Service(); + void ServiceFengOnly(); + FEPackage* FindPackageAtBase(); + FEPackage* FindPackageActive(const char* pPackageName); + FEPackage* FindPackageIdle(const char* pPackageName); + void PrintLoadedPackages(); + void QueueMessage(unsigned int msg, const char* pkg_name, FEObject* obj, unsigned int controlMask); + bool IsPackageInControl(const char* packageName); + void MakeLoadedPackagesDirty(); +}; + +#endif diff --git a/src/Speed/Indep/Src/FEng/feimage.h b/src/Speed/Indep/Src/FEng/feimage.h index c4d1c2bf6..51a2bbbb6 100644 --- a/src/Speed/Indep/Src/FEng/feimage.h +++ b/src/Speed/Indep/Src/FEng/feimage.h @@ -1,10 +1,35 @@ -#ifndef FENG_FEIMAGE_H -#define FENG_FEIMAGE_H +#ifndef _FEIMAGE +#define _FEIMAGE -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include "FEObject.h" + +struct FEImageData; + +// total size: 0x60 +struct FEImage : public FEObject { + unsigned long ImageFlags; // offset 0x5C, size 0x4 + + inline FEImage() : FEObject(), ImageFlags(0) {} + inline FEImage(const FEImage& Object, bool bReference) + : FEObject(Object, bReference), ImageFlags(Object.ImageFlags) {} + ~FEImage() override; + + inline FEImageData* GetImageData(); + + FEObject* Clone(bool bReference) override; + + inline void SetTopLeft(const FEVector2& topleft, bool bRelative); + inline void SetBottomRight(const FEVector2& bottomright, bool bRelative); +}; +inline void FEImage::SetTopLeft(const FEVector2& topleft, bool bRelative) { + SetTrackValue(FETrack_UpperLeft, topleft, bRelative); + Flags |= 0x400000; +} +inline void FEImage::SetBottomRight(const FEVector2& bottomright, bool bRelative) { + SetTrackValue(FETrack_LowerRight, bottomright, bRelative); + Flags |= 0x400000; +} #endif diff --git a/src/Speed/Indep/Src/FEng/fengine.h b/src/Speed/Indep/Src/FEng/fengine.h index 0ddf60af8..fce7370ff 100644 --- a/src/Speed/Indep/Src/FEng/fengine.h +++ b/src/Speed/Indep/Src/FEng/fengine.h @@ -5,6 +5,112 @@ #pragma once #endif +#include "Speed/Indep/Src/FEng/FEPackage.h" +struct FEJoyPad; +struct FEGameInterface; +struct FEObjectMouseState; +struct FEMessageResponse; +struct FEMatrix4; +struct FEPackageCommand; + +struct FEPackageList { + FEList Packages; // offset 0x0, size 0x10 + + inline FEPackage* GetFirstPackage() const { + return static_cast(Packages.GetHead()); + } + inline FEPackage* GetLastPackage() const { + return static_cast(Packages.GetTail()); + } + inline FEPackage* FindPackage(const char* pName) const { + return static_cast(Packages.FindNode(pName)); + } + inline unsigned long GetCount() const { return Packages.GetNumElements(); } +}; + +// total size: 0x5268 +struct FEngine { + static unsigned long SysGUID; + + bool bExecuting; // offset 0x0 + bool bMouseActive; // offset 0x4 + bool bLoadObjectNames; // offset 0x8 + bool bLoadScriptNames; // offset 0xC + FEJoyPad* pJoyPad; // offset 0x10 + char _padMouse[0xD4]; // offset 0x14 to 0xE8 + FEPackageList PackList; // offset 0xE8, size 0x10 + FEList IdleList; // offset 0xF8, size 0x10 + FEList LibraryList; // offset 0x108, size 0x10 + FEGameInterface* pInterface; // offset 0x118 + char _padRest[0x5140]; // offset 0x11C to 0x525C + bool bErrorScreenMode; // offset 0x525C + bool bRenderedRecently; // offset 0x5260 + bool bDebugMessages; // offset 0x5264 + + inline FEPackageList* GetPackageList() { + FEPackageList* p = &PackList; + return p; + } + + inline void SetInterface(FEGameInterface* pNewInterface) { pInterface = pNewInterface; } + inline void ToggleErrorScreenMode(bool b) { bErrorScreenMode = b; } + inline bool IsErrorScreenMode() { return bErrorScreenMode; } + + FEngine(); + ~FEngine(); + void ReleaseAll(); + void SetNumJoyPads(unsigned char Count); + void SetExecution(bool bProcessEverything); + void SetProcessInput(FEPackage* pkg, bool bProcess); + void SetInitialState(); + FEPackage* LoadPackage(const void* pPackageData, bool bLoadAsLibrary); + bool UnloadPackage(FEPackage* pPackage); + void UnloadLibraryPackage(FEPackage* pLibPack); + FEPackage* PushPackage(const char* pPackageName, const unsigned char Level, const unsigned long ControlMask); + FEPackage* GetFirstIdle() const; + void AddToIdleList(FEPackage* pPack); + void RemoveFromIdleList(FEPackage* pPack); + FEPackage* FindIdlePackage(const char* pName) const; + FEPackage* GetFirstLibrary() const; + void AddToLibraryList(FEPackage* pPack); + void RemoveFromLibraryList(FEPackage* pPack); + FEPackage* FindPackageWithControl(); + void QueuePackagePush(const char* pPackageName, unsigned long ControlMask); + void QueuePackageSwitch(const char* pPackageName, unsigned long ControlMask); + void QueuePackagePop(); + void QueuePackageCommand(long Command, unsigned long ControlMask, const char* pPackageName); + void QueuePackageUserTransfer(FEPackage* pPack, bool bPush, unsigned long ControlMask); + void QueueMessage(unsigned long MsgID, FEObject* pFrom, FEPackage* pFromPackage, FEObject* pTo, unsigned long ControlMask); + void SendMessageToGame(unsigned long MsgID, FEObject* pFrom, FEPackage* pFromPackage, unsigned long uControlMask); + int GetNumPackagesBelowPriority(unsigned char priority); + void MakeLoadedPackagesDirty(); + void Render(); + void RenderGroup(FEGroup* pGroup, FEMatrix4& mParent, FEMatrix4& mAccum, unsigned short RenderContext); + void RenderObject(FEObject* pObj, FEMatrix4& mParent, unsigned short RenderContext); + void Update(long, unsigned int); + bool ForAllObjects(FEObjectCallback& Callback); + void ProcessResponses(FEMessageResponse* pResp, FEObject* pObj, FEPackage* pPack, unsigned long uControlMask); + void ProcessPadsForPackage(FEPackage* pPackage); + void ProcessMouseForPackage(FEPackage* pPackage); + void UpdateMouseState(FEPackage* pPack, FEObjectMouseState* pState, float mx, float my); + void ProcessMessageQueue(); + void ProcessPackageCommands(); + bool ProcessListBoxResponses(FEObject* pObj, unsigned long MsgID); + bool ProcessListBoxResponses(FEObject* pObj, FEPackage* pPack, unsigned long MsgID); + bool ProcessCodeListBoxResponses(FEObject* pObj, unsigned long MsgID); + bool ProcessCodeListBoxResponses(FEObject* pObj, FEPackage* pPack, unsigned long MsgID); + void ProcessObjectMessage(FEObject* pObj, FEPackage* pPack, unsigned long MsgID, unsigned long uControlMask); + void ProcessGlobalMessage(FEPackage* pPack, unsigned long MsgID, unsigned long uControlMask); + FEPackage* FindLibraryPackage(unsigned long NameHash) const; + FEPackageCommand* FindQueuedNodeWithControl(); + void PopPackage(); + void RemovePackage(FEPackage* pPack); + void RecordLastPackageButton(unsigned long PackHash, unsigned long ButtonGUID); + unsigned long RecallLastPackageButton(unsigned long PackHash); + bool RecordPackageMarker(const char* pName); + const char* RecallPackageMarker(); + void ClearPackageMarkers(); +}; #endif diff --git a/src/Speed/Indep/Src/FEng/fengine_full.h b/src/Speed/Indep/Src/FEng/fengine_full.h new file mode 100644 index 000000000..d368a60ee --- /dev/null +++ b/src/Speed/Indep/Src/FEng/fengine_full.h @@ -0,0 +1,270 @@ +#ifndef FENG_FENGINE_H +#define FENG_FENGINE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/FEMouse.h" +#include "Speed/Indep/Src/FEng/FEObjectCallback.h" + +struct FEJoyPad; +struct FEGameInterface; +struct FEObjectMouseState; +struct FEMessageResponse; +struct FEScript; +struct FEPackageCommand; +struct FEFieldNode; + +// total size: 0x28 +struct FETypeNode : public FENode { + FEMinList Fields; // offset 0x14, size 0x10 + unsigned long TypeID; // offset 0x24, size 0x4 + + inline FETypeNode* GetNext() { return static_cast(FENode::GetNext()); } + inline FETypeNode* GetPrev() { return static_cast(FENode::GetPrev()); } + inline FEFieldNode* GetFirstField(); + inline unsigned long GetID() { return TypeID; } + inline void SetID(unsigned long ID) { TypeID = ID; } + + void AddField(const char* pName, long Type); + void UpdateOffsets(); + unsigned long GetTypeSize(); + FEFieldNode* GetField(const char* pName); + inline FEFieldNode* GetField(int Index); +}; + +// total size: 0x24 +struct FEFieldNode : public FENode { + int Type; // offset 0x14, size 0x4 + unsigned long Size; // offset 0x18, size 0x4 + unsigned long Offset; // offset 0x1C, size 0x4 + unsigned char* pDefault; // offset 0x20, size 0x4 + + inline FEFieldNode() : Size(0), Offset(0), pDefault(nullptr) {} + ~FEFieldNode() override; + + inline int GetType() const { return Type; } + inline void SetType(int NewType) { Type = NewType; } + inline unsigned long GetSize() const { return Size; } + inline void SetSize(unsigned long Val) { Size = Val; } + inline unsigned long GetOffset() const { return Offset; } + inline void SetOffset(unsigned long Val) { Offset = Val; } + inline const void* GetDefaultPtr() { return pDefault; } + inline FEFieldNode* GetNext() const { return static_cast(FENode::GetNext()); } + inline FEFieldNode* GetPrev() const { return static_cast(FENode::GetPrev()); } + + void SetDefault(void* pSrc); + void GetDefault(void* pDest); +}; + +inline FEFieldNode* FETypeNode::GetFirstField() { return static_cast(Fields.GetHead()); } + +inline FEFieldNode* FETypeNode::GetField(int Index) { + return static_cast(Fields.FindNode(static_cast(Index))); +} + +// total size: 0x8 +struct SFERadixKey { + FEObject* pobObject; // offset 0x0 + unsigned long ulKey; // offset 0x4 +}; + +// total size: 0x4004 +template +struct FEObjectSorter { + unsigned long mulNumObjects; // offset 0x0, size 0x4 + SFERadixKey mastFinalList[N]; // offset 0x4, size 0x2000 + SFERadixKey mastScratchList[N]; // offset 0x2004, size 0x2000 + + inline void Zero() { mulNumObjects = 0; } + inline FEObjectSorter() { Zero(); } + inline SFERadixKey* GetListPtr() { return mastFinalList; } + inline unsigned long GetNumObjects() { return mulNumObjects; } + inline void AddObject(FEObject* pobObject, float fZValue) { + mastFinalList[mulNumObjects].pobObject = pobObject; + mastFinalList[mulNumObjects].ulKey = *reinterpret_cast(&fZValue); + mulNumObjects++; + } + void SortObjects(); +}; + +#include "Speed/Indep/Src/FEng/FEObjectSorter.h" + +// total size: 0x14 +struct FETypeLib { + FEList List; // offset 0x0, size 0x10 + bool bAutoCreateHideScripts; // offset 0x10, size 0x1 + + inline FETypeLib() : bAutoCreateHideScripts(false) {} + inline ~FETypeLib() {} + inline void Shutdown() {} + inline void SetAutoCreateHide(bool bValue) { bAutoCreateHideScripts = bValue; } + inline bool GetAutoCreateHide() const { return bAutoCreateHideScripts; } + inline FETypeNode* FindType(const char* pName) { return static_cast(List.FindNode(pName)); } + inline void AddType(FETypeNode* pNode) { List.AddTail(pNode); } + inline void RemoveType(FETypeNode* pNode) { List.RemNode(pNode); } + inline FETypeNode* GetFirstType() { return static_cast(List.GetHead()); } + inline const FEList* GetList() { return &List; } + + bool Startup(); + FETypeNode* FindType(unsigned long TypeID); + FEObject* CreateFEObject(FETypeNode* pType, bool bInitScript); + FEObject* CreateFEObject(unsigned long TypeID, bool bInitScript); + FEScript* CreateObjectScript(FETypeNode* pType); + FEScript* CreateObjectScript(unsigned long TypeID); + FETypeNode* CreateBaseObjectType(const char* pName); + FETypeNode* CreateImageObjectType(const char* pName); + FETypeNode* CreateMultiImageObjectType(const char* pName); +}; + +// total size: 0x8 +struct FEPackageButtonRec { + unsigned long PackageHash; // offset 0x0, size 0x4 + unsigned long ButtonGUID; // offset 0x4, size 0x4 +}; + +// FEButtonWrapMode defined in FEPackage.h + +struct FEPackageList { + FEList Packages; // offset 0x0, size 0x10 + + inline FEPackage* GetFirstPackage() const { + return static_cast(Packages.GetHead()); + } + inline FEPackage* GetLastPackage() const { + return static_cast(Packages.GetTail()); + } + inline FEPackage* FindPackage(const char* pName) const { + return static_cast(Packages.FindNode(pName)); + } + inline unsigned long GetCount() const { return Packages.GetNumElements(); } + + void AddPackage(FEPackage* pPack); + void AddPackageAfter(FEPackage* pPack, FEPackage* pAfter); + FEPackage* FindPackage(const char* pName, unsigned char ControllerIndex) const; + bool RemovePackage(FEPackage* pPack); + void ReplaceParentLinks(const FEPackage* pParent, const FEPackage* pReplacement); +}; + +// total size: 0x40 +// FEMatrix4 is now defined in FETypes.h + +// total size: 0x5268 +struct FEngine { + static unsigned long SysGUID; + + bool bExecuting; // offset 0x0, size 0x1 + bool bMouseActive; // offset 0x4, size 0x1 + bool bLoadObjectNames; // offset 0x8, size 0x1 + bool bLoadScriptNames; // offset 0xC, size 0x1 + FEJoyPad* pJoyPad; // offset 0x10, size 0x4 + FEMouse Mouse; // offset 0x14, size 0x24 + unsigned long FastRep; // offset 0x38, size 0x4 + unsigned long FastRepCache; // offset 0x3C, size 0x4 + unsigned long PadHoldRegistered; // offset 0x40, size 0x4 + unsigned long HoldDecrement[19]; // offset 0x44, size 0x4C + FEObject* HeldButtons[19]; // offset 0x90, size 0x4C + FEButtonWrapMode WrapMode; // offset 0xDC, size 0x4 + unsigned long NumJoyPads; // offset 0xE0, size 0x4 + unsigned short uGroupContext; // offset 0xE4, size 0x2 + FEPackageList PackList; // offset 0xE8, size 0x10 + FEList IdleList; // offset 0xF8, size 0x10 + FEList LibraryList; // offset 0x108, size 0x10 + FEGameInterface* pInterface; // offset 0x118, size 0x4 + FEObjectSorter<1024> Sorter; // offset 0x11C, size 0x4004 + FEMinList MsgQ; // offset 0x4120, size 0x10 + FEList PackageCommands; // offset 0x4130, size 0x10 + FETypeLib TypeLib; // offset 0x4140, size 0x14 + int CurrentPackageRecordIndex; // offset 0x4154, size 0x4 + char RecordedPackageNames[16][256]; // offset 0x4158, size 0x1000 + int NextButtonRecordIndex; // offset 0x5158, size 0x4 + FEPackageButtonRec RecordedPackageButtons[32]; // offset 0x515C, size 0x100 + bool bErrorScreenMode; // offset 0x525C, size 0x1 + bool bRenderedRecently; // offset 0x5260, size 0x1 + bool bDebugMessages; // offset 0x5264, size 0x1 + + inline FEPackageList* GetPackageList() { + FEPackageList* p = &PackList; + return p; + } + + inline void SetInterface(FEGameInterface* pNewInterface) { pInterface = pNewInterface; } + inline void ToggleErrorScreenMode(bool b) { bErrorScreenMode = b; } + inline bool IsErrorScreenMode() { return bErrorScreenMode; } + inline FEObjectSorter<1024>& GetSorter() { return Sorter; } + inline FEJoyPad* GetJoyPad(unsigned char Index) { return &pJoyPad[Index]; } + inline void SetUseMouse(bool bUseMouse) { bMouseActive = bUseMouse; } + inline FEMouse* GetMouse() { return &Mouse; } + inline FETypeLib& GetTypeLib() { return TypeLib; } + inline void SetLoadObjectNames(bool bLoadNames) { bLoadObjectNames = bLoadNames; } + inline bool GetLoadObjectNames() const { return bLoadObjectNames; } + inline void SetLoadScriptNames(bool bLoadNames) { bLoadScriptNames = bLoadNames; } + inline bool GetLoadScriptNames() const { return bLoadScriptNames; } + inline void SetWrapMode(FEButtonWrapMode NewMode) { WrapMode = NewMode; } + inline FEButtonWrapMode GetWrapMode() { return WrapMode; } + static inline unsigned long GetNextGUID() { return SysGUID++; } + static inline unsigned long GetSysGUID() { return SysGUID; } + static inline void SetSysGUID(unsigned long NewGUID) { SysGUID = NewGUID; } + inline int GetNumPackageMarkers() const { return CurrentPackageRecordIndex; } + inline const char* GetPackageMarker(unsigned long Index) const { return RecordedPackageNames[Index]; } + inline void DebugMessages(bool bDebug) { bDebugMessages = bDebug; } + + FEngine(); + ~FEngine(); + void ReleaseAll(); + void SetNumJoyPads(unsigned char Count); + void SetExecution(bool bProcessEverything); + void SetProcessInput(FEPackage* pkg, bool bProcess); + void SetInitialState(); + FEPackage* LoadPackage(const void* pPackageData, bool bLoadAsLibrary); + bool UnloadPackage(FEPackage* pPackage); + void UnloadLibraryPackage(FEPackage* pLibPack); + FEPackage* PushPackage(const char* pPackageName, const unsigned char Level, const unsigned long ControlMask); + FEPackage* GetFirstIdle() const; + void AddToIdleList(FEPackage* pPack); + void RemoveFromIdleList(FEPackage* pPack); + FEPackage* FindIdlePackage(const char* pName) const; + FEPackage* GetFirstLibrary() const; + void AddToLibraryList(FEPackage* pPack); + void RemoveFromLibraryList(FEPackage* pPack); + FEPackage* FindPackageWithControl(); + void QueuePackagePush(const char* pPackageName, unsigned long ControlMask); + void QueuePackageSwitch(const char* pPackageName, unsigned long ControlMask); + void QueuePackagePop(); + void QueuePackageCommand(long Command, unsigned long ControlMask, const char* pPackageName); + void QueuePackageUserTransfer(FEPackage* pPack, bool bPush, unsigned long ControlMask); + void QueueMessage(unsigned long MsgID, FEObject* pFrom, FEPackage* pFromPackage, FEObject* pTo, unsigned long ControlMask); + void SendMessageToGame(unsigned long MsgID, FEObject* pFrom, FEPackage* pFromPackage, unsigned long uControlMask); + int GetNumPackagesBelowPriority(unsigned char priority); + void MakeLoadedPackagesDirty(); + void Render(); + void RenderGroup(FEGroup* pGroup, FEMatrix4& mParent, FEMatrix4& mAccum, unsigned short RenderContext); + void RenderObject(FEObject* pObj, FEMatrix4& mParent, unsigned short RenderContext); + void Update(long, unsigned int); + bool ForAllObjects(FEObjectCallback& Callback); + void ProcessResponses(FEMessageResponse* pResp, FEObject* pObj, FEPackage* pPack, unsigned long uControlMask); + void ProcessPadsForPackage(FEPackage* pPackage); + void ProcessMouseForPackage(FEPackage* pPackage); + void UpdateMouseState(FEPackage* pPack, FEObjectMouseState* pState, float mx, float my); + void ProcessMessageQueue(); + void ProcessPackageCommands(); + bool ProcessListBoxResponses(FEObject* pObj, unsigned long MsgID); + bool ProcessListBoxResponses(FEObject* pObj, FEPackage* pPack, unsigned long MsgID); + bool ProcessCodeListBoxResponses(FEObject* pObj, unsigned long MsgID); + bool ProcessCodeListBoxResponses(FEObject* pObj, FEPackage* pPack, unsigned long MsgID); + void ProcessObjectMessage(FEObject* pObj, FEPackage* pPack, unsigned long MsgID, unsigned long uControlMask); + void ProcessGlobalMessage(FEPackage* pPack, unsigned long MsgID, unsigned long uControlMask); + FEPackage* FindLibraryPackage(unsigned long NameHash) const; + FEPackageCommand* FindQueuedNodeWithControl(); + void PopPackage(); + void RemovePackage(FEPackage* pPack); + void RecordLastPackageButton(unsigned long PackHash, unsigned long ButtonGUID); + unsigned long RecallLastPackageButton(unsigned long PackHash); + bool RecordPackageMarker(const char* pName); + const char* RecallPackageMarker(); + void ClearPackageMarkers(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/Careers/UnlockSystem.cpp b/src/Speed/Indep/Src/Frontend/Careers/UnlockSystem.cpp index e69de29bb..2a2667ae9 100644 --- a/src/Speed/Indep/Src/Frontend/Careers/UnlockSystem.cpp +++ b/src/Speed/Indep/Src/Frontend/Careers/UnlockSystem.cpp @@ -0,0 +1,1576 @@ +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Misc/EasterEggs.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/gameplay.h" +#include "Speed/Indep/Src/Misc/MD5.hpp" + +extern int UnlockAllThings; +extern char TheUnlockData[0x1c8]; +extern char gMaxPartLevels[NUM_UNLOCKABLES]; +extern EasterEggs gEasterEggs; +extern bool gVerboseTesterOutput; +extern int SkipFE; +extern int SkipFESplitScreen; + +void GetLocalizedString(char *buf, unsigned int maxlen, unsigned int hash); +unsigned int FEngHashString(const char *fmt, ...); + +// GRaceDatabase inline methods (can't add bodies to header - DWARF crash) +inline GRaceSaveInfo *GRaceDatabase::GetScoreInfo() { + return mRaceScoreInfo; +} + +inline unsigned int GRaceDatabase::GetScoreInfoCount() { + return mRaceCountStatic; +} + +// total size: 0x10 +#ifndef GRACESAVEINFO_DEFINED +#define GRACESAVEINFO_DEFINED +struct GRaceSaveInfo { + unsigned int mRaceHash; // offset 0x0, size 0x4 + unsigned int mFlags; // offset 0x4, size 0x4 + float mHighScores; // offset 0x8, size 0x4 + unsigned short mTopSpeed; // offset 0xC, size 0x2 + unsigned short mAverageSpeed; // offset 0xE, size 0x2 +}; +#endif + +#include "Speed/Indep/Src/World/CarPart.hpp" + +bool GetIsCollectorsEdition(); +eUnlockableEntity MapCarPartToUnlockable(int carslot, CarPart *part); +eUnlockableEntity MapPerfPkgToUnlockable(Physics::Upgrades::Type type); +const FECarPartInfo *LookupFEPartInfo(eUnlockableEntity unlockable, int level); +eUnlockableEntity ConvertBigBangUpgradeAward(const char *partname); +char *SaveSomeData(void *save_to, void *save_from, int bytes, void *maxptr); +char *LoadSomeData(void *load_to, void *load_from, int bytes, void *maxptr); + +// ============================================================ +// Free functions +// ============================================================ + +void DefaultUnlockData() { + bMemSet(&TheUnlockData, 0, 0x1c8); + bMemSet(&gMaxPartLevels, 0, NUM_UNLOCKABLES); + gMaxPartLevels[4] = 3; // PUT_TIRES + gMaxPartLevels[5] = 4; // PUT_BRAKES + gMaxPartLevels[6] = 3; // PUT_CHASSIS + gMaxPartLevels[7] = 4; // PUT_TRANSMISSION + gMaxPartLevels[8] = 4; // PUT_ENGINE + gMaxPartLevels[9] = 3; // PUT_INDUCTION + gMaxPartLevels[10] = 3; // PUT_NOS + gMaxPartLevels[11] = 4; // BODY_KIT + gMaxPartLevels[12] = 5; // SPOILERS + gMaxPartLevels[13] = 6; // RIM_BRANDS + gMaxPartLevels[14] = 6; // HOODS + gMaxPartLevels[15] = 6; // ROOF_SCOOPS + gMaxPartLevels[17] = 4; // CUSTOM_HUD + gMaxPartLevels[18] = 4; // WINDOW_TINT + gMaxPartLevels[23] = 3; // PAINTABLE_BODY + gMaxPartLevels[40] = 6; // VINYLS_GROUP_BODY +} + +void UnlockUnlockableThing(eUnlockableEntity entity, unsigned int filter, int level, const char *part_name) { + int unlock_index; + if (level < 0) { + level = 0; + } + if (filter & 1) { + char *secondary_data = TheUnlockData; + char *primary_data = TheUnlockData; + secondary_data += 4; + primary_data += 5; + unlock_index = static_cast(entity) * 8; + primary_data[unlock_index] = level; + secondary_data[unlock_index] = level; + return; + } + if ((filter & 2) == 0) { + return; + } + char *primary_data = TheUnlockData; + char *secondary_data = TheUnlockData; + char *tertiary_data = TheUnlockData; + primary_data += 5; + secondary_data += 1; + tertiary_data += 4; + unlock_index = static_cast(entity) * 8; + primary_data[unlock_index] = level; + secondary_data[unlock_index] = level; + tertiary_data[unlock_index] = level; + TheUnlockData[unlock_index] = level; +} + +// ============================================================ +// QuickRaceUnlocker +// ============================================================ + +bool QuickRaceUnlocker::IsBackroomAvailable(eUnlockFilters filter, eUnlockableEntity ent, int level, int player) { + bool answer = false; + return answer; +} + +int QuickRaceUnlocker::IsUnlockableUnlocked(eUnlockFilters filter, eUnlockableEntity ent, int level, int player, bool backroom) { + return level <= TheUnlockData[static_cast(ent) * 8 + 4] + || UnlockAllThings != 0 + || FEDatabase->GetUserProfile(0)->GetCareer()->HasBeatenCareer() + || FEDatabase->GetUserProfile(0)->CareerModeHasBeenCompletedAtLeastOnce; +} + +int QuickRaceUnlocker::IsCarPartUnlocked(eUnlockFilters filter, int carslot, CarPart *part, int player, bool backroom) { + bool answer = UnlockAllThings != 0; + unsigned char group_and_level = part->GroupNumber_UpgradeLevel; + eUnlockableEntity unlockable = MapCarPartToUnlockable(carslot, part); + int unlocked = QuickRaceUnlocker::IsUnlockableUnlocked(filter, unlockable, part->GroupNumber_UpgradeLevel >> 5, player, false); + return (group_and_level >> 5 == 0 || answer) || unlocked != 0; +} + +int QuickRaceUnlocker::IsPerfPackageUnlocked(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, int player, bool backroom) { + bool answer = UnlockAllThings != 0; + eUnlockableEntity unlockable = MapPerfPkgToUnlockable(pkg_type); + int unlocked = QuickRaceUnlocker::IsUnlockableUnlocked(filter, unlockable, level, player, false); + return answer | unlocked; +} + +bool QuickRaceUnlocker::IsTrackUnlocked(eUnlockFilters filter, int event_hash, int player) { + bool answer = UnlockAllThings != 0; + int raceUnlocked = GRaceDatabase::Get().CheckRaceScoreFlags(event_hash, GRaceDatabase::kUnlocked_QuickRace); + answer = answer || raceUnlocked != 0; + int tutorialHash = Attrib::StringHash32("19.8.31"); + if (event_hash == tutorialHash) { + return true; + } + return answer; +} + +// ============================================================ +// OnlineUnlocker +// ============================================================ + +bool OnlineUnlocker::IsUnlockableUnlocked(eUnlockFilters filter, eUnlockableEntity ent, int level, bool backroom) { + bool answer; + answer = QuickRaceUnlocker::IsUnlockableUnlocked(filter, ent, level, backroom, false); + return answer; +} + +bool OnlineUnlocker::IsCarPartUnlocked(eUnlockFilters filter, int carslot, CarPart *part, bool backroom) { + bool answer; + answer = QuickRaceUnlocker::IsCarPartUnlocked(filter, carslot, part, backroom, false); + return answer; +} + +bool OnlineUnlocker::IsPerfPackageUnlocked(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, bool backroom) { + bool answer; + answer = QuickRaceUnlocker::IsPerfPackageUnlocked(filter, pkg_type, level, backroom, false); + return answer; +} + +bool OnlineUnlocker::IsTrackUnlocked(eUnlockFilters filter, int event_hash) { + bool answer = true; + if (UnlockAllThings == 0) { + answer = false; + } + answer = answer | GRaceDatabase::Get().CheckRaceScoreFlags(event_hash, GRaceDatabase::kUnlocked_QuickRace); + answer = answer | GRaceDatabase::Get().CheckRaceScoreFlags(event_hash, GRaceDatabase::kUnlocked_Online); + return answer; +} + +bool OnlineUnlocker::IsCarUnlocked(eUnlockFilters filter, unsigned int car) { + return QuickRaceUnlocker::IsCarUnlocked(filter, car, 0); +} + +bool OnlineUnlocker::IsBackroomAvailable(eUnlockFilters filter, eUnlockableEntity ent, int level) { + bool answer = QuickRaceUnlocker::IsBackroomAvailable(filter, ent, level, 0); + return answer; +} + +// ============================================================ +// CareerUnlocker +// ============================================================ + +bool CareerUnlocker::IsUnlockableUnlocked(eUnlockFilters filter, eUnlockableEntity ent, int level, bool backroom) { + bool answer = level <= TheUnlockData[static_cast(ent) * 8] + | UnlockAllThings + | FEDatabase->GetUserProfile(0)->GetCareer()->HasBeatenCareer() + | FEDatabase->GetUserProfile(0)->CareerModeHasBeenCompletedAtLeastOnce; + + if (!backroom) return answer; + + answer = level <= TheUnlockData[static_cast(ent) * 8] + 1 | answer; + + if (TheUnlockData[static_cast(ent) * 8] != gMaxPartLevels[static_cast(ent)]) + return answer; + + return level <= 7 | answer; +} + +bool CareerUnlocker::IsPerfPackageUnlocked(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, bool backroom) { + bool answer = UnlockAllThings != 0; + eUnlockableEntity unlockable = MapPerfPkgToUnlockable(pkg_type); + return static_cast(answer | CareerUnlocker::IsUnlockableUnlocked(filter, unlockable, level, backroom)); +} + +bool CareerUnlocker::IsTrackUnlocked(eUnlockFilters filter, int event_hash) { + bool answer = true; + if (UnlockAllThings == 0) { + answer = false; + } + bool raceUnlocked = GRaceDatabase::Get().CheckRaceScoreFlags(event_hash, GRaceDatabase::kUnlocked_Career); + return static_cast(answer | raceUnlocked); +} + +// ============================================================ +// UnlockSystem dispatchers +// ============================================================ + +bool UnlockSystem::IsUnlockableUnlocked(eUnlockFilters filter, eUnlockableEntity thing, int level, int player, bool backroom) { + if (UnlockAllThings) return true; + bool answer = false; + if (filter & 1) { + answer = QuickRaceUnlocker::IsUnlockableUnlocked(filter, thing, level, player, backroom) != 0; + } + if (filter & 2) { + answer = static_cast(answer | CareerUnlocker::IsUnlockableUnlocked(filter, thing, level, backroom)); + } + if (filter & 4) { + answer = static_cast(answer | OnlineUnlocker::IsUnlockableUnlocked(filter, thing, level, backroom)); + } + return answer; +} + +bool UnlockSystem::IsCarPartUnlocked(eUnlockFilters filter, int carslot, CarPart *part, int player, bool backroom) { + if (UnlockAllThings) return true; + bool answer = false; + if (filter & 1) { + answer = QuickRaceUnlocker::IsCarPartUnlocked(filter, carslot, part, player, backroom) != 0; + } + if (filter & 2) { + answer = static_cast(answer | CareerUnlocker::IsCarPartUnlocked(filter, carslot, part, backroom)); + } + if (filter & 4) { + answer = static_cast(answer | OnlineUnlocker::IsCarPartUnlocked(filter, carslot, part, backroom)); + } + return answer; +} + +bool UnlockSystem::IsPerfPackageUnlocked(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, int player, bool backroom) { + if (UnlockAllThings) return true; + bool answer = false; + if (filter & 1) { + answer = QuickRaceUnlocker::IsPerfPackageUnlocked(filter, pkg_type, level, player, backroom) != 0; + } + if (filter & 2) { + answer = static_cast(answer | CareerUnlocker::IsPerfPackageUnlocked(filter, pkg_type, level, backroom)); + } + if (filter & 4) { + answer = static_cast(answer | OnlineUnlocker::IsPerfPackageUnlocked(filter, pkg_type, level, backroom)); + } + return answer; +} + +bool UnlockSystem::IsTrackUnlocked(eUnlockFilters filter, int event_hash, int player) { + if (UnlockAllThings) return true; + bool answer = false; + if (filter & 1) { + answer = QuickRaceUnlocker::IsTrackUnlocked(filter, event_hash, player) != 0; + } + if (filter & 2) { + answer = static_cast(answer | CareerUnlocker::IsTrackUnlocked(filter, event_hash)); + } + if (filter & 4) { + answer = static_cast(answer | OnlineUnlocker::IsTrackUnlocked(filter, event_hash)); + } + return answer; +} + +bool UnlockSystem::IsCarUnlocked(eUnlockFilters filter, unsigned int handle, int player) { + if (UnlockAllThings) return true; + bool answer = false; + if (filter & 1) { + answer = QuickRaceUnlocker::IsCarUnlocked(filter, handle, player); + } + if (filter & 2) { + answer = static_cast(answer | CareerUnlocker::IsCarUnlocked(filter, handle)); + } + if (filter & 4) { + answer = static_cast(answer | OnlineUnlocker::IsCarUnlocked(filter, handle)); + } + if (GetIsCollectorsEdition() && UnlockSystem::IsBonusCarCEOnly(handle)) { + answer = true; + } + return answer; +} + +bool UnlockSystem::IsBackroomAvailable(eUnlockFilters filter, eUnlockableEntity ent, int level) { + bool answer = false; + if (filter & 1) { + answer = QuickRaceUnlocker::IsBackroomAvailable(filter, ent, level, 0); + } + if (filter & 2) { + answer = static_cast(answer | CareerUnlocker::IsBackroomAvailable(filter, ent, level)); + } + if (filter & 4) { + answer = static_cast(answer | OnlineUnlocker::IsBackroomAvailable(filter, ent, level)); + } + return answer; +} + +bool UnlockSystem::IsUnlockableNew(eUnlockFilters filter, eUnlockableEntity ent, int level) { + if (level == -2) { + if (filter & 1) { + char *new_flag = TheUnlockData; + new_flag += 5; + int unlock_index = static_cast(ent) * 8; + return new_flag[unlock_index] != -1; + } + if (filter & 2) { + char *new_flag = TheUnlockData; + new_flag += 1; + int unlock_index = static_cast(ent) * 8; + return new_flag[unlock_index] != -1; + } + char *new_flag = TheUnlockData; + new_flag += 5; + int unlock_index = static_cast(ent) * 8; + return new_flag[unlock_index] != -1; + } + if (filter & 2) { + char *new_flag = TheUnlockData; + new_flag += 1; + int unlock_index = static_cast(ent) * 8; + return new_flag[unlock_index] == level; + } + char *new_flag = TheUnlockData; + new_flag += 5; + int unlock_index = static_cast(ent) * 8; + return new_flag[unlock_index] == level; +} + +void UnlockSystem::ClearNewUnlock(eUnlockableEntity ent, unsigned int filter) { + if (filter & 1) { + char *unlock_data = TheUnlockData; + unlock_data += 5; + int unlock_index = static_cast(ent) * 8; + unlock_data[unlock_index] = -1; + } + if ((filter & 2) == 0) { + return; + } + char *unlock_data = TheUnlockData; + unlock_data += 1; + int unlock_index = static_cast(ent) * 8; + unlock_data[unlock_index] = -1; +} + +// ============================================================ +// FEMarkerManager +// ============================================================ + +FEMarkerManager TheFEMarkerManager; + +FEMarkerManager::FEMarkerManager() { + Default(); +} + +void FEMarkerManager::Default() { + for (int i = 0; i < 63; i++) { + OwnedMarkers[i].Marker = MARKER_NONE; + OwnedMarkers[i].Param = 0; + OwnedMarkers[i].State = MARKER_STATE_NOT_OWNED; + } + ClearMarkersForLaterSelection(); +} + +void FEMarkerManager::GetMarkerForLaterSelection(int index, ePossibleMarker &marker, int ¶m) { + marker = TempSelectionMarkers[index].Marker; + param = TempSelectionMarkers[index].Param; +} + +void FEMarkerManager::AddMarkerForLaterSelection(ePossibleMarker marker, int param) { + for (int i = 0; i < 6; i++) { + if (TempSelectionMarkers[i].Marker == MARKER_NONE) { + TempSelectionMarkers[i].Marker = marker; + TempSelectionMarkers[i].Param = param; + iNumTempMarkers++; + return; + } + } +} + +void FEMarkerManager::ClearMarkersForLaterSelection() { + for (int i = 0; i < 6; i++) { + TempSelectionMarkers[i].Marker = MARKER_NONE; + TempSelectionMarkers[i].Param = 0; + TempSelectionMarkers[i].State = MARKER_STATE_NOT_OWNED; + } + iNumTempMarkers = 0; +} + +void FEMarkerManager::AddMarkerToInventory(ePossibleMarker marker, int param) { + for (int i = 0; i < 63; i++) { + if (OwnedMarkers[i].Marker == MARKER_NONE) { + OwnedMarkers[i].Marker = marker; + OwnedMarkers[i].Param = param; + OwnedMarkers[i].State = MARKER_STATE_OWNED; + return; + } + } +} + +void FEMarkerManager::UtilizeMarker(ePossibleMarker marker, int param) { + for (int i = 0; i < 63; i++) { + if (OwnedMarkers[i].Marker == marker && OwnedMarkers[i].Param == param && OwnedMarkers[i].State == MARKER_STATE_OWNED) { + OwnedMarkers[i].Marker = MARKER_NONE; + OwnedMarkers[i].State = MARKER_STATE_NOT_OWNED; + return; + } + } +} + +void FEMarkerManager::UtilizeMarker(unsigned int slot_id) { + ePossibleMarker marker = MARKER_NONE; + switch (slot_id) { + case 0x17: marker = MARKER_BODY; break; + case 0x3f: marker = MARKER_HOOD; break; + case 0x2c: marker = MARKER_SPOILER; break; + case 0x42: marker = MARKER_RIMS; break; + case 0x3e: marker = MARKER_ROOF_SCOOP; break; + case 0x84: marker = MARKER_CUSTOM_HUD; break; + case 0x53: + case 0x5b: + case 99: + case 100: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x73: + case 0x7b: marker = MARKER_DECAL; break; + case 0x4d: marker = MARKER_VINYL; break; + case 0x4c: marker = MARKER_PAINT; break; + } + UtilizeMarker(marker, 0); +} + +void FEMarkerManager::UtilizeMarker(Physics::Upgrades::Type type) { + ePossibleMarker marker = MARKER_NONE; + switch (type) { + case Physics::Upgrades::kType_Engine: marker = MARKER_TIRES; break; + case Physics::Upgrades::kType_Transmission: marker = MARKER_BRAKES; break; + case Physics::Upgrades::kType_Chassis: marker = MARKER_CHASSIS; break; + case Physics::Upgrades::kType_Nitrous: marker = MARKER_TRANSMISSION; break; + case Physics::Upgrades::kType_Tires: marker = MARKER_ENGINE; break; + case Physics::Upgrades::kType_Brakes: marker = MARKER_INDUCTION; break; + case Physics::Upgrades::kType_Induction: marker = MARKER_NOS; break; + } + UtilizeMarker(marker, 0); +} + +bool FEMarkerManager::IsMarkerAvailable(ePossibleMarker marker, int param) { + for (int i = 0; i < 63; i++) { + if (OwnedMarkers[i].Marker == marker) { + if (OwnedMarkers[i].Param == param || (param == 0 && marker == MARKER_VINYL)) { + if (OwnedMarkers[i].State == MARKER_STATE_OWNED) { + return true; + } + } + } + } + return false; +} + +int FEMarkerManager::GetNumCustomizeMarkers() { + int total = 0; + for (int i = static_cast(MARKER_CUSTOMIZE_FIRST); i < static_cast(MARKER_CUSTOMIZE_LAST); i++) { + total += GetNumMarkers(static_cast(i), 0); + } + return total; +} + +int FEMarkerManager::GetNumMarkers(ePossibleMarker marker, int param) { + int count = 0; + for (int i = 0; i < 63; i++) { + if (OwnedMarkers[i].Marker == marker) { + if (OwnedMarkers[i].Param == param || (param == 0 && marker == MARKER_VINYL)) { + if (OwnedMarkers[i].State == MARKER_STATE_OWNED) { + count++; + } + } + } + } + return count; +} + +char *FEMarkerManager::SaveToBuffer(char *buffer) { + int nSize = GetSaveBufferSize(); + bMemCpy(buffer, this, nSize); + return buffer + nSize; +} + +char *FEMarkerManager::LoadFromBuffer(char *buffer) { + int nSize = GetSaveBufferSize(); + bMemCpy(this, buffer, nSize); + return buffer + nSize; +} + +// ============================================================ +// CareerSettings Save/Load unlock data +// ============================================================ + +char *CareerSettings::SaveUnlockData(void *save_to, void *maxptr) { + char *buf = static_cast(save_to); + for (unsigned int i = 0; i < 0x39; i++) { + buf = SaveSomeData(buf, &TheUnlockData[i * 8], 8, maxptr); + } + return buf; +} + +char *CareerSettings::LoadUnlockData(void *load_from, void *maxptr) { + char *buf = static_cast(load_from); + for (unsigned int i = 0; i < 0x39; i++) { + buf = LoadSomeData(&TheUnlockData[i * 8], buf, 8, maxptr); + } + return buf; +} + +// ============================================================ +// More free functions +// ============================================================ + +void MarkUnlockableThingSeen(eUnlockableEntity entity, unsigned int filter) { + char count; + if (filter & 1) { + int unlock_index = static_cast(entity) * 8; + count = TheUnlockData[unlock_index + 6]; + TheUnlockData[unlock_index + 6] = count + 1; + if (static_cast(count + 1) < 4) { + return; + } + TheUnlockData[unlock_index + 6] = 0; + TheUnlockData[unlock_index + 5] = -1; + return; + } + if ((filter & 2) == 0) { + return; + } + int unlock_index = static_cast(entity) * 8; + count = TheUnlockData[unlock_index + 2]; + TheUnlockData[unlock_index + 2] = count + 1; + if (static_cast(count + 1) < 4) { + return; + } + TheUnlockData[unlock_index + 1] = -1; + TheUnlockData[unlock_index + 2] = 0; +} + +eUnlockableEntity MapPerfPkgToUnlockable(Physics::Upgrades::Type pkg_type) { + switch (pkg_type) { + case Physics::Upgrades::kType_Tires: return UNLOCKABLE_THING_PUT_TIRES; + case Physics::Upgrades::kType_Brakes: return UNLOCKABLE_THING_PUT_BRAKES; + case Physics::Upgrades::kType_Chassis: return UNLOCKABLE_THING_PUT_CHASSIS; + case Physics::Upgrades::kType_Transmission: return UNLOCKABLE_THING_PUT_TRANSMISSION; + case Physics::Upgrades::kType_Engine: return UNLOCKABLE_THING_PUT_ENGINE; + case Physics::Upgrades::kType_Induction: return UNLOCKABLE_THING_PUT_INDUCTION; + case Physics::Upgrades::kType_Nitrous: return UNLOCKABLE_THING_PUT_NOS; + } + return UNLOCKABLE_THING_UNKNOWN; +} + +eUnlockableEntity MapCarPartToUnlockable(int carslot, CarPart *part) { + switch (carslot) { + case 0x17: return UNLOCKABLE_THING_BODY_KIT; + case 0x2c: return UNLOCKABLE_THING_SPOILERS; + case 0x3e: return UNLOCKABLE_THING_ROOF_SCOOPS; + case 0x3f: return UNLOCKABLE_THING_HOODS; + case 0x42: return UNLOCKABLE_THING_RIM_BRANDS; + case 0x43: return UNLOCKABLE_THING_RIM_BRANDS; + case 0x45: return UNLOCKABLE_THING_LICENSE_PLATE; + case 0x4c: return UNLOCKABLE_THING_PAINTABLE_BODY; + case 0x4d: return UNLOCKABLE_VINYLS_GROUP_BODY; + case 0x4e: return UNLOCKABLE_THING_PAINTABLE_BODY; + case 0x53: return UNLOCKABLE_DECAL_REAR_WINDOW; + case 0x5b: return UNLOCKABLE_DECAL_REAR_WINDOW; + case 99: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 100: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x65: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x66: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x67: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x68: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x69: return UNLOCKABLE_DECAL_NUMBERS; + case 0x6b: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x6c: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x6d: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x6e: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x6f: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x70: return UNLOCKABLE_DECAL_LEFT_DOOR; + case 0x71: return UNLOCKABLE_DECAL_NUMBERS; + case 0x73: return UNLOCKABLE_DECAL_LEFT_QP; + case 0x7b: return UNLOCKABLE_DECAL_LEFT_QP; + case 0x83: return UNLOCKABLE_THING_WINDOW_TINT; + case 0x84: return UNLOCKABLE_THING_CUSTOM_HUD; + default: return UNLOCKABLE_THING_UNKNOWN; + } +} + +const FECarPartInfo *LookupFEPartInfo(eUnlockableEntity unlockable, int upgrade_level) { + const Attrib::Class *feclass = Attrib::Database::Get().GetClass(0x85885722); + unsigned int key = feclass->GetFirstCollection(); + Attrib::Gen::frontend carparts(Attrib::StringToKey("carparts"), 0, nullptr); + do { + if (!key) { + return nullptr; + } + Attrib::Gen::frontend part(key, 0, nullptr); + if (part.GetParent() == Attrib::StringToKey("carparts")) { + if (static_cast(part.feCarPartName()) == static_cast(unlockable)) { + for (unsigned int i = 0; i < part.Num_feCarPartInfo(); i++) { + if (static_cast(part.feCarPartInfo(i).Level) == upgrade_level) { + return &part.feCarPartInfo(i); + } + } + } + } + key = feclass->GetNextCollection(key); + } while (true); +} + +int UnlockSystem::GetPerfPackageCost(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, int player) { + eUnlockableEntity unlockable = MapPerfPkgToUnlockable(pkg_type); + float price = 0.0f; + if (unlockable != UNLOCKABLE_THING_UNKNOWN) { + const FECarPartInfo *info = LookupFEPartInfo(unlockable, level); + if (info) { + price = info->Cost; + } + } + return static_cast(price); +} + +int UnlockSystem::GetCarPartCost(eUnlockFilters filter, int carslot, CarPart *part, int player) { + eUnlockableEntity unlockable = MapCarPartToUnlockable(carslot, part); + float price = 0.0f; + if (unlockable != UNLOCKABLE_THING_UNKNOWN) { + const FECarPartInfo *info = LookupFEPartInfo(unlockable, part->GetUpgradeLevel()); + if (info) { + price = info->Cost; + } + } + return static_cast(price); +} + +bool UnlockSystem::IsEventAvailable(unsigned int event_hash) { + if (event_hash == Attrib::StringHash32("99.1.1")) { + return false; + } + if (event_hash == Attrib::StringHash32("21.1.1") + || event_hash == Attrib::StringHash32("21.2.1") + || event_hash == Attrib::StringHash32("21.2.2") + || event_hash == Attrib::StringHash32("19.9.70")) { + if (GetIsCollectorsEdition()) { + return true; + } + } else { + if (event_hash != Attrib::StringHash32("19.8.31")) { + return true; + } + if (gEasterEggs.IsEasterEggUnlocked(EASTER_EGG_BURGER_KING) + && !FEDatabase->GetCareerSettings()->HasBeenAwardedBKReward()) { + return true; + } + } + return false; +} + +bool UnlockSystem::IsBonusCarCEOnly(unsigned int name_hash) { + switch (name_hash) { + case 0x02d642b8: + case 0x03d3401a: + case 0x03d8a6d1: + case 0x363a1fea: + case 0x54653c71: + case 0x54655133: + case 0x582f21d9: + case 0x634d1bd2: + case 0xe1075862: + case 0xe115ead0: + return true; + default: + return false; + } +} + +bool UnlockSystem::IsUnlockableAvailable(unsigned int part_name_hash) { + if (part_name_hash <= 0x13D0CA) { + unsigned int collectors_edition_start = 0x13D0C8; + if (part_name_hash < collectors_edition_start) { + return true; + } + return GetIsCollectorsEdition() != 0; + } + return true; +} + +// ============================================================ +// CareerUnlocker remaining +// ============================================================ + +bool CareerUnlocker::IsCarPartUnlocked(eUnlockFilters filter, int carslot, CarPart *part, bool backroom) { + bool answer = UnlockAllThings != 0; + unsigned char group_and_level = part->GroupNumber_UpgradeLevel; + eUnlockableEntity unlockable = MapCarPartToUnlockable(carslot, part); + int unlocked = CareerUnlocker::IsUnlockableUnlocked(filter, unlockable, part->GroupNumber_UpgradeLevel >> 5, backroom); + return (group_and_level >> 5 == 0 || answer) || unlocked != 0; +} + +bool CareerUnlocker::IsCarUnlocked(eUnlockFilters filter, unsigned int car) { + bool answer = UnlockAllThings != 0; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *fe_car = stable->GetCarRecordByHandle(car); + Attrib::Gen::frontend CarAttribs(fe_car->FEKey, 0, nullptr); + unsigned char unlockedAt = CarAttribs.UnlockedAt(); + unsigned char currentBin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + return currentBin >= unlockedAt | answer; +} + +bool CareerUnlocker::IsBackroomAvailable(eUnlockFilters filter, eUnlockableEntity ent, int level) { + bool answer = false; + FEMarkerManager::ePossibleMarker marker = FEMarkerManager::MARKER_NONE; + if (ent != UNLOCKABLE_THING_SPOILERS) { + if (ent > UNLOCKABLE_THING_SPOILERS) { + if (ent < UNLOCKABLE_THING_PAINTABLE_RIMS) { + if (ent > UNLOCKABLE_THING_WINDOW_TINT) { + marker = FEMarkerManager::MARKER_PAINT; + goto marker_check; + } + if (ent == UNLOCKABLE_THING_HOODS) { + marker = FEMarkerManager::MARKER_HOOD; + goto marker_check; + } + if (ent > UNLOCKABLE_THING_RIM_BRANDS) { + if (ent == UNLOCKABLE_THING_ROOF_SCOOPS) { + marker = FEMarkerManager::MARKER_ROOF_SCOOP; + } else { + if (ent != UNLOCKABLE_THING_CUSTOM_HUD) { + return false; + } + marker = FEMarkerManager::MARKER_CUSTOM_HUD; + } + goto marker_check; + } + } else { + if (ent > UNLOCKABLE_VINYLS_GROUP_CONTEST) { + if (ent > UNLOCKABLE_DECAL_SLOT_6 || ent < UNLOCKABLE_DECAL_WINDSHIELD) { + return false; + } + marker = FEMarkerManager::MARKER_DECAL; + goto marker_check; + } + if (ent > UNLOCKABLE_THING_RIM_BRAND_ROJA) { + marker = FEMarkerManager::MARKER_VINYL; + goto marker_check; + } + if (ent < UNLOCKABLE_THING_RIM_BRAND_5_ZIGEN) { + return false; + } + } + marker = FEMarkerManager::MARKER_RIMS; + } else { + if (ent != UNLOCKABLE_THING_PUT_CHASSIS) { + if (ent > UNLOCKABLE_THING_PUT_CHASSIS) { + if (ent != UNLOCKABLE_THING_PUT_INDUCTION) { + if (ent < UNLOCKABLE_THING_PUT_INDUCTION) { + if (ent == UNLOCKABLE_THING_PUT_TRANSMISSION) { + marker = FEMarkerManager::MARKER_TRANSMISSION; + } else { + if (ent != UNLOCKABLE_THING_PUT_ENGINE) { + return false; + } + marker = FEMarkerManager::MARKER_ENGINE; + } + goto marker_check; + } + if (ent != UNLOCKABLE_THING_PUT_NOS) { + if (ent != UNLOCKABLE_THING_BODY_KIT) { + return false; + } + marker = FEMarkerManager::MARKER_BODY; + goto marker_check; + } + marker = FEMarkerManager::MARKER_NOS; + goto marker_check; + } + marker = FEMarkerManager::MARKER_INDUCTION; + goto marker_check; + } + if (ent < UNLOCKABLE_THING_PUT_TRANSMISSION) { + if (ent != UNLOCKABLE_THING_CUSTOMIZE_VISUAL) { + if (ent > UNLOCKABLE_THING_CUSTOMIZE_VISUAL) { + if (ent == UNLOCKABLE_THING_PUT_TIRES) { + marker = FEMarkerManager::MARKER_TIRES; + } else { + if (ent != UNLOCKABLE_THING_PUT_BRAKES) { + return false; + } + marker = FEMarkerManager::MARKER_BRAKES; + } + goto marker_check; + } + if (ent == UNLOCKABLE_THING_CUSTOMIZE_PARTS) { + answer = CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_BODY_KIT, level); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_SPOILERS, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_RIM_BRANDS, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_HOODS, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_ROOF_SCOOPS, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_CUSTOM_HUD, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_RIM_BRAND_5_ZIGEN, level)); + } else { + if (ent != UNLOCKABLE_THING_CUSTOMIZE_PERFORMANCE) { + return false; + } + answer = CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_PUT_TIRES, level); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_PUT_BRAKES, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_PUT_CHASSIS, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_PUT_TRANSMISSION, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_PUT_ENGINE, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_PUT_INDUCTION, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_PUT_NOS, level)); + } + } else { + answer = CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_THING_PAINT_METALLIC, level); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_VINYLS_GROUP_FLAME, level)); + answer = static_cast(answer) | static_cast(CareerUnlocker::IsBackroomAvailable(filter, UNLOCKABLE_DECAL_WINDSHIELD, level)); + } + } + } else { + marker = FEMarkerManager::MARKER_CHASSIS; + goto marker_check; + } + } + } else { + marker = FEMarkerManager::MARKER_SPOILER; + goto marker_check; + } + +marker_check: + return static_cast(answer) | static_cast(TheFEMarkerManager.IsMarkerAvailable(marker, 0)); +} + +// ============================================================ +// QuickRaceUnlocker::IsCarUnlocked +// ============================================================ + +bool QuickRaceUnlocker::IsCarUnlocked(eUnlockFilters filter, unsigned int car, int player) { + bool answer = UnlockAllThings != 0; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(player); + FECarRecord *fe_car = stable->GetCarRecordByHandle(car); + { + Attrib::Gen::frontend CarAttribs(fe_car->FEKey, 0, nullptr); + bool unlockedCheck = FEDatabase->GetCareerSettings()->GetCurrentBin() <= CarAttribs.UnlockedAt(); + answer = static_cast(unlockedCheck | answer); + } + if (fe_car->MatchesFilter(0xF0007)) { + int type = fe_car->GetType(); + if (type < 0x19) { + if (type >= 0x17 || type <= 4 || type == 5 || type == 6 || type == 8) { + answer = true; + } + } else if (type < 0x45) { + if (type > 0x42 || type == 0x2F || type == 0x3E) { + answer = true; + } + } else if (type == 0x4A) { + answer = true; + } + return answer; + } + if (!fe_car->MatchesFilter(0xF0008)) { + return answer; + } + + unsigned char currentBin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + unsigned int handle = fe_car->Handle; + if (handle == 0x2D642B8) { + return GetIsCollectorsEdition(); + } + if (handle < 0x2D642B9) { + if (handle != 0x9665) { + if (handle > 0x9665) { + if (handle == 0x136250) { + return currentBin < 0xC; + } + if (handle < 0x136251) { + if (handle == 0x13624E) { + return currentBin < 10; + } + if (handle < 0x13624F) { + if (handle == 0x9666) { + return currentBin < 9; + } + } else { + return currentBin < 0xB; + } + } else if (handle == 0x136252) { + return currentBin < 0xE; + } else if (handle < 0x136252) { + return currentBin < 0xD; + } else if (handle == 0x136253) { + return currentBin < 0xF; + } + return false; + } + if (handle == 0x9661) { + return currentBin < 4; + } + if (handle > 0x9661) { + if (handle == 0x9663) { + return currentBin < 6; + } + if (handle < 0x9664) { + return currentBin < 5; + } + return currentBin < 7; + } + if (handle == 0x965F) { + return currentBin < 2; + } + if (handle != 0x9660 || currentBin > 2) { + return false; + } + return true; + } + return currentBin < 8; + } + if (handle == 0x363A1FEA) { + return GetIsCollectorsEdition(); + } + if (handle > 0x363A1FEA) { + if (handle != 0x634D1BD2) { + if (handle < 0x634D1BD3) { + if (handle != 0x54655133) { + if (handle < 0x54655134) { + if (handle != 0x54653C71) { + return false; + } + } else if (handle != 0x582F21D9) { + return false; + } + } + } else if (handle != 0xE1075862) { + if (handle < 0xE1075863) { + if (handle != 0xCB6AAF2F) { + return false; + } + return (FEDatabase->GetCareerSettings()->SpecialFlags & 0x8000) != 0; + } + if (handle != 0xE115EAD0) { + return false; + } + } + } + return GetIsCollectorsEdition(); + } + if (handle == 0x3D8A6D1) { + return GetIsCollectorsEdition(); + } + if (handle < 0x3D8A6D2) { + if (handle == 0x3A94520) { + return FEDatabase->GetCareerSettings()->HasBeatenCareer(); + } + if (handle != 0x3D3401A) { + return false; + } + return GetIsCollectorsEdition(); + } + if (handle == 0x2CF385B2) { + return (FEDatabase->GetCareerSettings()->SpecialFlags & 1) != 0; + } + if (handle < 0x2CF385B3) { + if (handle != 0x2CF370F0) { + return false; + } + return FEDatabase->GetCareerSettings()->HasBeatenCareer(); + } + if (handle != 0x34498EB2) { + return false; + } + return (FEDatabase->GetCareerSettings()->SpecialFlags & 0x40000) != 0; +} + +void ClearAllNewStatus() { + for (int i = 0; i < 0x39; i++) { + UnlockSystem::ClearNewUnlock(static_cast(i), 2); + UnlockSystem::ClearNewUnlock(static_cast(i), 1); + } +} + +bool DoesCategoryHaveNewUnlock(eUnlockableEntity ent) { + int category = static_cast(ent); + bool answer = false; + bool bVar1; + bool bVar2; + bool bVar3; + char cVar4; + if (category == 2) { + if (TheUnlockData[static_cast(UNLOCKABLE_THING_PUT_BRAKES) * 8 + 1] == -1) { + bVar1 = true; + if (TheUnlockData[static_cast(UNLOCKABLE_THING_PUT_TIRES) * 8 + 1] == -1) { + bVar1 = false; + } + } else { + bVar1 = true; + } + if (TheUnlockData[static_cast(UNLOCKABLE_THING_PUT_CHASSIS) * 8 + 1] == -1) { + bVar2 = true; + if (!bVar1) { + bVar2 = false; + } + } else { + bVar2 = true; + } + if (TheUnlockData[static_cast(UNLOCKABLE_THING_PUT_TRANSMISSION) * 8 + 1] == -1) { + bVar1 = true; + if (!bVar2) { + bVar1 = false; + } + } else { + bVar1 = true; + } + if (TheUnlockData[static_cast(UNLOCKABLE_THING_PUT_ENGINE) * 8 + 1] == -1) { + bVar2 = true; + if (!bVar1) { + bVar2 = false; + } + } else { + bVar2 = true; + } + cVar4 = TheUnlockData[static_cast(UNLOCKABLE_THING_PUT_NOS) * 8 + 1]; + if (TheUnlockData[static_cast(UNLOCKABLE_THING_PUT_INDUCTION) * 8 + 1] == -1) { + bVar1 = true; + if (!bVar2) { + bVar1 = false; + } + } else { + bVar1 = true; + } + } else if (category < 3) { + if (category != 1) { + return answer; + } + if (TheUnlockData[static_cast(UNLOCKABLE_THING_SPOILERS) * 8 + 1] == -1) { + bVar1 = true; + if (TheUnlockData[static_cast(UNLOCKABLE_THING_BODY_KIT) * 8 + 1] == -1) { + bVar1 = false; + } + } else { + bVar1 = true; + } + if (TheUnlockData[static_cast(UNLOCKABLE_THING_RIM_BRANDS) * 8 + 1] == -1) { + bVar2 = true; + if (!bVar1) { + bVar2 = false; + } + } else { + bVar2 = true; + } + cVar4 = TheUnlockData[static_cast(UNLOCKABLE_THING_ROOF_SCOOPS) * 8 + 1]; + if (TheUnlockData[static_cast(UNLOCKABLE_THING_HOODS) * 8 + 1] == -1) { + bVar1 = true; + if (!bVar2) { + bVar1 = false; + } + } else { + bVar1 = true; + } + } else { + if (category != 3) { + return answer; + } + if (TheUnlockData[static_cast(UNLOCKABLE_THING_CUSTOM_HUD) * 8 + 1] == -1) { + bVar1 = true; + if (TheUnlockData[static_cast(static_cast(50)) * 8 + 1] == -1) { + bVar1 = false; + } + } else { + bVar1 = true; + } + if (TheUnlockData[static_cast(static_cast(43)) * 8 + 1] == -1) { + bVar2 = true; + if (!bVar1) { + bVar2 = false; + } + } else { + bVar2 = true; + } + if (TheUnlockData[static_cast(UNLOCKABLE_THING_PAINTABLE_BODY) * 8 + 1] == -1) { + bVar3 = true; + if (!bVar2) { + bVar3 = false; + } + } else { + bVar3 = true; + } + cVar4 = TheUnlockData[static_cast(UNLOCKABLE_VINYLS_GROUP_BODY) * 8 + 1]; + if (TheUnlockData[static_cast(UNLOCKABLE_THING_WINDOW_TINT) * 8 + 1] == -1) { + bVar1 = true; + if (!bVar3) { + bVar1 = false; + } + } else { + bVar1 = true; + } + } + if (cVar4 == -1) { + if (!bVar1) { + return answer; + } + return true; + } + return true; +} + +eUnlockableEntity ConvertBigBangUpgradeAward(const char *partname) { + static struct { + const char *mPartName; + eUnlockableEntity mUnlockable; + } unlockType[18] = { + { "brakes", UNLOCKABLE_THING_PUT_BRAKES }, + { "chassis", UNLOCKABLE_THING_PUT_CHASSIS }, + { "engine", UNLOCKABLE_THING_PUT_ENGINE }, + { "induction", UNLOCKABLE_THING_PUT_INDUCTION }, + { "nos", UNLOCKABLE_THING_PUT_NOS }, + { "tires", UNLOCKABLE_THING_PUT_TIRES }, + { "transmission", UNLOCKABLE_THING_PUT_TRANSMISSION }, + { "bodykit", UNLOCKABLE_THING_BODY_KIT }, + { "decals", static_cast< eUnlockableEntity >(50) }, + { "hood", UNLOCKABLE_THING_HOODS }, + { "hud", UNLOCKABLE_THING_CUSTOM_HUD }, + { "numbers", static_cast< eUnlockableEntity >(43) }, + { "paint", UNLOCKABLE_THING_PAINTABLE_BODY }, + { "rims", UNLOCKABLE_THING_RIM_BRANDS }, + { "roofscoop", UNLOCKABLE_THING_ROOF_SCOOPS }, + { "spoiler", UNLOCKABLE_THING_SPOILERS }, + { "tint", UNLOCKABLE_THING_WINDOW_TINT }, + { "vinyls", static_cast< eUnlockableEntity >(40) }, + }; + + for (unsigned int onPart = 0; onPart < 18; onPart++) { + if (bStrCmp(partname, unlockType[onPart].mPartName) == 0) { + return unlockType[onPart].mUnlockable; + } + } + return UNLOCKABLE_THING_UNKNOWN; +} + +void AwardUnlockUpgrade(Attrib::Gen::gameplay &inst) { + const char *upgradePartName = inst.UpgradePartName(0); + const char *upgradePartID = inst.UpgradePartID(0); + int upgradeLevel = inst.UpgradeLevel(0); + eUnlockableEntity entity = ConvertBigBangUpgradeAward(upgradePartID); + if (entity != UNLOCKABLE_THING_UNKNOWN) { + if (entity == static_cast(0x32)) { + if (upgradeLevel == 2) { + entity = static_cast(0x2e); + } else if (upgradeLevel == 1) { + entity = static_cast(0x2c); + } else if (upgradeLevel == 3) { + entity = static_cast(0x30); + } + } + UnlockUnlockableThing(entity, 2, upgradeLevel, ""); + } +} + +struct MarkerUnlockTypeEntry { + const char *mMarkerName; + const char *mPartName; + FEMarkerManager::ePossibleMarker mMarker; +}; + +static MarkerUnlockTypeEntry markerUnlockType[21] = { + { "backroom", "brakes", FEMarkerManager::MARKER_BRAKES }, + { "backroom", "chassis", FEMarkerManager::MARKER_CHASSIS }, + { "backroom", "engine", FEMarkerManager::MARKER_ENGINE }, + { "backroom", "induction", FEMarkerManager::MARKER_INDUCTION }, + { "backroom", "nos", FEMarkerManager::MARKER_NOS }, + { "backroom", "tires", FEMarkerManager::MARKER_TIRES }, + { "backroom", "transmission", FEMarkerManager::MARKER_TRANSMISSION }, + { "backroom", "bodykit", FEMarkerManager::MARKER_BODY }, + { "backroom", "decals", FEMarkerManager::MARKER_DECAL }, + { "backroom", "hood", FEMarkerManager::MARKER_HOOD }, + { "backroom", "hud", FEMarkerManager::MARKER_CUSTOM_HUD }, + { "backroom", "paint", FEMarkerManager::MARKER_PAINT }, + { "backroom", "rims", FEMarkerManager::MARKER_RIMS }, + { "backroom", "roofscoop", FEMarkerManager::MARKER_ROOF_SCOOP }, + { "backroom", "spoiler", FEMarkerManager::MARKER_SPOILER }, + { "backroom", "vinyls", FEMarkerManager::MARKER_VINYL }, + { "add_impound_box", nullptr, FEMarkerManager::MARKER_ADD_IMPOUND_BOX }, + { "cash_bonus", nullptr, FEMarkerManager::MARKER_CASH }, + { "out_of_jail_free", nullptr, FEMarkerManager::MARKER_GET_OUT_OF_JAIL }, + { "pink_slip", nullptr, FEMarkerManager::MARKER_PINK_SLIP }, + { "release_car_from_impound", nullptr, FEMarkerManager::MARKER_IMPOUND_RELEASE }, +}; + +FEMarkerManager::ePossibleMarker FEMarkerManager::ConvertBigBangMarkerAward(const char *marker_name, const char *partid) { + const char *const *partName = reinterpret_cast(&markerUnlockType[0].mPartName); + const ePossibleMarker *marker = reinterpret_cast(&markerUnlockType[0].mMarker); + + for (int onMarker = 0; onMarker < 21; onMarker++) { + if (bStrICmp(marker_name, markerUnlockType[onMarker].mMarkerName) == 0 + && (!partName[onMarker * 3] || bStrICmp(partid, partName[onMarker * 3]) == 0)) { + return marker[onMarker * 3]; + } + } + return MARKER_NONE; +} + +void FEMarkerManager::AwardMarker(Attrib::Gen::gameplay &inst, bool immediate_reward) { + ePossibleMarker marker = ConvertBigBangMarkerAward(inst.RewardMarkerType(0), inst.UpgradePartID(0)); + if (marker != MARKER_NONE) { + int param = 0; + if (immediate_reward) { + if (marker == MARKER_PINK_SLIP) { + goto award_pink_slip; + } + if (marker != MARKER_CASH) { + goto add_inventory; + } + param = static_cast(inst.CashReward(0)); + FEDatabase->GetCareerSettings()->CurrentCash = FEDatabase->GetCareerSettings()->CurrentCash + param; + goto award_done; + + award_pink_slip: + param = FEngHashString("BL%d", FEDatabase->GetCareerSettings()->GetCurrentBin()); + FEDatabase->GetPlayerCarStable(0)->AwardRivalCar(param); + goto award_done; + + add_inventory: + AddMarkerToInventory(marker, 0); + + award_done: + ; + } else { + if (marker != MARKER_PINK_SLIP) { + if (marker == MARKER_CASH) { + param = static_cast(inst.CashReward(0)); + } + } else { + param = FEngHashString("BL%d", FEDatabase->GetCareerSettings()->GetCurrentBin(), 0); + } + AddMarkerForLaterSelection(marker, param); + } + } +} + +inline bool CareerSettings::HasBeenAwardedDemoMarker() { + return SpecialFlags & 0x20000; +} + +inline void CareerSettings::SetAwardedDemoMarker() { + SpecialFlags |= 0x20000; +} + +void CareerSettings::TryAwardDemoMarker() { + if (!HasBeenAwardedDemoMarker() && gEasterEggs.IsEasterEggUnlocked(static_cast(5))) { + TheFEMarkerManager.AddMarkerToInventory(FEMarkerManager::ePossibleMarker(2), 0); + SetAwardedDemoMarker(); + } +} + +char *CareerSettings::SaveToBuffer(void *buffer, void *maxbuf) { + char *buf = SaveGameplayData(buffer, maxbuf); + buf = SaveSomeData(buf, &CurrentCar, 4, maxbuf); + buf = SaveSomeData(buf, &CurrentBin, 1, maxbuf); + buf = SaveSomeData(buf, &CurrentCash, 4, maxbuf); + buf = SaveSomeData(buf, &AdaptiveDifficulty, 2, maxbuf); + buf = SaveSomeData(buf, &SpecialFlags, 4, maxbuf); + buf = SaveSomeData(buf, SMSMessages, 600, maxbuf); + buf = SaveSomeData(buf, &SMSSortOrder, 2, maxbuf); + buf = SaveSomeData(buf, CaseFileName, 16, maxbuf); + buf = SaveRaceData(buf, maxbuf); + buf = SaveUnlockData(buf, maxbuf); + TheFEMarkerManager.SaveToBuffer(buf); + return buf; +} + +char *CareerSettings::LoadFromBuffer(void *buffer, void *maxbuf) { + char *buf = LoadGameplayData(buffer, maxbuf); + buf = LoadSomeData(&CurrentCar, buf, 4, maxbuf); + buf = LoadSomeData(&CurrentBin, buf, 1, maxbuf); + buf = LoadSomeData(&CurrentCash, buf, 4, maxbuf); + buf = LoadSomeData(&AdaptiveDifficulty, buf, 2, maxbuf); + buf = LoadSomeData(&SpecialFlags, buf, 4, maxbuf); + buf = LoadSomeData(SMSMessages, buf, 600, maxbuf); + buf = LoadSomeData(&SMSSortOrder, buf, 2, maxbuf); + buf = LoadSomeData(CaseFileName, buf, 16, maxbuf); + buf = LoadRaceData(buf, maxbuf); + buf = LoadUnlockData(buf, maxbuf); + TheFEMarkerManager.LoadFromBuffer(buf); + return buf; +} + +char *CareerSettings::SaveGameplayData(void *save_to, void *maxptr) { + char *buf = static_cast(save_to); + if (!GManager::Exists()) { + bMemSet(buf, 0, 0x4000); + } else { + GManager::Get().SaveGameplayData(reinterpret_cast(buf), 0x4000); + } + return buf + 0x4000; +} + +char *CareerSettings::LoadGameplayData(void *load_from_here, void *maxptr) { + char *buf = static_cast(load_from_here); + if (GManager::Exists()) { + GManager::Get().LoadGameplayData(reinterpret_cast(buf), 0x4000); + } + return buf + 0x4000; +} + +char *CareerSettings::SaveRaceData(void *save_to, void *maxptr) { + char *buf = static_cast(save_to); + if (GRaceDatabase::Exists()) { + unsigned int nEntries = GRaceDatabase::Get().GetScoreInfoCount(); + nEntries = bMin(static_cast(nEntries), 300); + buf = SaveSomeData(buf, &nEntries, 4, maxptr); + GRaceSaveInfo *current = GRaceDatabase::Get().GetScoreInfo(); + for (unsigned int index = 0; index < nEntries; index++) { + if (gVerboseTesterOutput && current->mRaceHash != 0 && (current->mFlags & 2)) { + GRaceDatabase::Get().GetRaceFromHash(current->mRaceHash); + } + buf = SaveSomeData(buf, current, 0x10, maxptr); + current++; + } + } + return static_cast(save_to) + 0x12C8; +} + +char *CareerSettings::LoadRaceData(void *load_from_here, void *maxptr) { + char *buf = static_cast(load_from_here); + if (GRaceDatabase::Exists()) { + unsigned int nEntries = 0; + buf = LoadSomeData(&nEntries, buf, 4, maxptr); + nEntries = bMin(static_cast(nEntries), 300); + GRaceSaveInfo saveInfoEntries[300]; + GRaceSaveInfo *current = saveInfoEntries; + for (unsigned int index = 0; index < nEntries; index++) { + buf = LoadSomeData(current, buf, 0x10, maxptr); + if (gVerboseTesterOutput && current->mRaceHash != 0 && (current->mFlags & 2)) { + GRaceDatabase::Get().GetRaceFromHash(current->mRaceHash); + } + current++; + } + GRaceDatabase::Get().LoadBestScores(saveInfoEntries, nEntries); + } + return static_cast(load_from_here) + 0x12C8; +} + +// ==================== cFrontendDatabase implementations ==================== + +void cFrontendDatabase::DefaultRaceSettings() { + unsigned int default_car = GetDefaultCar(); + for (unsigned int i = 0; i < 11; i++) { + RaceSettings &settings = TheQuickRaceSettings[i]; + settings.Default(); + settings.SetSelectedCar(default_car, 0); + settings.SetSelectedCar(default_car, 1); + } + TheQuickRaceSettings[0].NumLaps = 1; + TheQuickRaceSettings[2].NumLaps = 1; + TheQuickRaceSettings[5].NumLaps = 1; + TheQuickRaceSettings[4].NumOpponents = 0; + TheQuickRaceSettings[3].NumLaps = TheQuickRaceSettings[3].NumOpponents; + TheQuickRaceSettings[4].NumLaps = 1; +} + + +void cFrontendDatabase::RestoreFromBackupDB() { + if (m_pDBBackup) { + LoadUserProfileFromBuffer(m_pDBBackup, GetUserProfileSaveSize(false), 0); + DeallocBackupDB(); + } +} + +void cFrontendDatabase::CreateMultiplayerProfile(int player) { + if (!CurrentUserProfiles[player]) { + CurrentUserProfiles[player] = new(__FILE__, __LINE__) UserProfile; + CurrentUserProfiles[player]->Default(player, true); + } +} + +void cFrontendDatabase::DeleteMultiplayerProfile(int player) { + if (player == 1 && CurrentUserProfiles[1]) { + RaceSettings *settings = GetQuickRaceSettings(static_cast(11)); + FEPlayerCarDB *stable = GetPlayerCarStable(1); + FECarRecord *record = stable->GetCarRecordByHandle(settings->GetSelectedCar(1)); + FECustomizationRecord *customization = stable->GetCustomizationRecordByHandle(record->Customization); + bStrCpy(SplitScreenCarType, record->GetDebugName()); + if (!customization) { + SplitScreenCustomization = nullptr; + } else { + SplitScreenCustomization = static_cast(bMalloc(sizeof(FECustomizationRecord), 0x47)); + bMemCpy(SplitScreenCustomization, customization, sizeof(FECustomizationRecord)); + } + if (CurrentUserProfiles[1]) { + delete CurrentUserProfiles[1]; + } + CurrentUserProfiles[1] = nullptr; + } +} + +void cFrontendDatabase::DefaultProfile() { + CurrentUserProfiles[0]->Default(0, true); + bAutoSaveOverwriteConfirmed = false; + DefaultRaceSettings(); + unsigned int default_car = GetDefaultCar(); + GetCareerSettings()->SetCurrentCar(default_car); + bIsOptionsDirty = false; + GetPlayerCarStable(0)->Default(); + MemoryCard::GetInstance()->SetCardRemovedWithAutoSaveEnabled(false); + DefaultUnlockData(); + TheFEMarkerManager.Default(); + if (GRaceDatabase::Exists()) { + GRaceDatabase::Get().ClearRaceScores(); + } + if (GManager::Exists()) { + GManager::Get().ResetAllGameplayData(); + } +} + +bool cFrontendDatabase::LoadUserProfileFromBuffer(void *buffer, int bufsize, int player) { + if (player == 0) { + return CurrentUserProfiles[0]->LoadFromBuffer(buffer, bufsize, true, 0); + } else { + bool result = CurrentUserProfiles[player]->LoadFromBuffer(buffer, bufsize, false, player); + bMemCpy(&CurrentUserProfiles[0]->GetOptions()->ThePlayerSettings[1], + &CurrentUserProfiles[1]->GetOptions()->ThePlayerSettings[0], + sizeof(PlayerSettings)); + return result; + } +} + +void cFrontendDatabase::RefreshCurrentRide() { + RideInfo ride; + FEPlayerCarDB *stable = GetPlayerCarStable(0); + if (IsCareerMode() || IsSafehouseMode() || IsCareerManagerMode()) { + BuildCurrentRideForPlayer(0, &ride); + } else { + RaceSettings *settings = GetQuickRaceSettings(static_cast(11)); + unsigned int handle = settings->GetSelectedCar(0); + stable->BuildRideForPlayer(handle, 0, &ride); + } + CarViewer::SetRideInfo(&ride, static_cast(2), static_cast(0)); +} + +// ==================== UserProfile implementations ==================== + +void UserProfile::SetProfileName(const char *pName, bool isP1) { + bool named = false; + if (pName && bStrLen(pName) > 0) { + named = true; + } + bMemSet(m_aProfileName, 0, 0x20); + if (named) { + bStrNCpy(m_aProfileName, pName, 0x20); + m_bNamed = true; + } else { + char defaultName[32]; + if (isP1) { + GetLocalizedString(defaultName, 0x20, 0x7b070984); + } else { + GetLocalizedString(defaultName, 0x20, 0x7b070985); + } + bStrNCpy(m_aProfileName, defaultName, 0x20); + m_bNamed = false; + } +} + +void UserProfile::WriteProfileHash(void *bufferToHash, void *bufferToWrite, int bytes, void *maxptr) { + MD5 md5; + md5.Reset(); + md5.Update(bufferToHash, bytes); + md5.GetRaw(); + SaveSomeData(bufferToWrite, md5.GetRaw(), 0x10, maxptr); +} + +bool UserProfile::VerifyProfileHash(void *bufferToHash, void *bufferHash, int bytes) { + MD5 md5; + md5.Reset(); + md5.Update(bufferToHash, bytes); + md5.GetRaw(); + return bMemCmp(md5.GetRaw(), bufferHash, 0x10) == 0; +} + +void UserProfile::SaveToBuffer(void *buffer, int size) { + char *buf = static_cast(buffer); + char *maxbuf = buf + size; + bMemSet(buf, 0, size); + char aVersion[16]; + bMemSet(aVersion, 0, 0x10); + buf = SaveSomeData(buf, aVersion, 0x10, maxbuf); + buf = TheCareerSettings.SaveToBuffer(buf, maxbuf); + buf = SaveSomeData(buf, &FEDatabase->iDefaultStableHash, 4, maxbuf); + buf = SaveSomeData(buf, m_aProfileName, 0x20, maxbuf); + buf = SaveSomeData(buf, Playlist, 0xF0, maxbuf); + buf = SaveSomeData(buf, &TheOptionsSettings, 0xC0, maxbuf); + int stableSize = PlayersCarStable.GetSaveBufferSize(); + buf = PlayersCarStable.SaveToBuffer(buf, stableSize); + buf = SaveSomeData(buf, &CareerModeHasBeenCompletedAtLeastOnce, 4, maxbuf); + buf = SaveSomeData(buf, &HighScores, 0xBD8, maxbuf); + for (int i = 0; i < 11; i++) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(i)); + unsigned int h = settings->GetSelectedCar(0); + buf = SaveSomeData(buf, &h, 4, maxbuf); + } + WriteProfileHash(static_cast(buffer) + 0x10, buf, size - 0x20, maxbuf); +} + +bool UserProfile::LoadFromBuffer(void *buffer, int size, bool commit_changes, int player_id) { + char *buf = static_cast(buffer); + char *maxbuf = buf + size; + char aVersion[16]; + buf = LoadSomeData(aVersion, buf, 0x10, maxbuf); + if (!player_id) { + buf = TheCareerSettings.LoadFromBuffer(buf, maxbuf); + TheCareerSettings.TryAwardDemoMarker(); + } else { + int careerSize = TheCareerSettings.GetSaveBufferSize(false); + buf = buf + careerSize; + } + unsigned int version; + buf = LoadSomeData(&version, buf, 4, maxbuf); + if (version != FEDatabase->iDefaultStableHash) { + return false; + } + buf = LoadSomeData(m_aProfileName, buf, 0x20, maxbuf); + if (!player_id) { + buf = LoadSomeData(Playlist, buf, 0xF0, maxbuf); + } else { + buf = buf + 0xF0; + } + buf = LoadSomeData(&TheOptionsSettings, buf, 0xC0, maxbuf); + int stableSize = PlayersCarStable.GetSaveBufferSize(); + buf = PlayersCarStable.LoadFromBuffer(buf, stableSize); + PlayersCarStable.AwardBonusCars(); + buf = LoadSomeData(&CareerModeHasBeenCompletedAtLeastOnce, buf, 4, maxbuf); + buf = LoadSomeData(&HighScores, buf, 0xBD8, maxbuf); + for (int i = 0; i < 11; i++) { + unsigned int h; + buf = LoadSomeData(&h, buf, 4, maxbuf); + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(i)); + settings->SelectedCar[player_id] = h; + } + if (!VerifyProfileHash(static_cast(buffer) + 0x10, buf, size - 0x20)) { + return false; + } + m_bNamed = true; + return true; +} diff --git a/src/Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp b/src/Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp index b28e8d43e..0fc8e6a3f 100644 --- a/src/Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp +++ b/src/Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp @@ -5,4 +5,125 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp" +#include "Speed/Indep/Src/Physics/PhysicsUpgrades.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" + +struct CarPart; + +struct UnlockSystem { + static bool IsUnlockableUnlocked(eUnlockFilters filter, eUnlockableEntity thing, int level, int player, bool backroom); + static bool IsCarPartUnlocked(eUnlockFilters filter, int carslot, CarPart *part, int player, bool backroom); + static bool IsPerfPackageUnlocked(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, int player, bool backroom); + static bool IsTrackUnlocked(eUnlockFilters filter, int event_hash, int player); + static bool IsCarUnlocked(eUnlockFilters filter, unsigned int handle, int player); + static bool IsBackroomAvailable(eUnlockFilters filter, eUnlockableEntity ent, int level); + static bool IsUnlockableNew(eUnlockFilters filter, eUnlockableEntity ent, int level); + static void ClearNewUnlock(eUnlockableEntity ent, unsigned int filter); + static int GetPerfPackageCost(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, int player); + static int GetCarPartCost(eUnlockFilters filter, int carslot, CarPart *part, int player); + static bool IsEventAvailable(unsigned int event_hash); + static bool IsBonusCarCEOnly(unsigned int name_hash); + static bool IsUnlockableAvailable(unsigned int part_name_hash); +}; + +struct CareerUnlocker { + static bool IsUnlockableUnlocked(eUnlockFilters filter, eUnlockableEntity ent, int level, bool backroom); + static bool IsCarPartUnlocked(eUnlockFilters filter, int carslot, CarPart *part, bool backroom); + static bool IsPerfPackageUnlocked(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, bool backroom); + static bool IsTrackUnlocked(eUnlockFilters filter, int event_hash); + static bool IsCarUnlocked(eUnlockFilters filter, unsigned int car); + static bool IsBackroomAvailable(eUnlockFilters filter, eUnlockableEntity ent, int level); +}; + +struct QuickRaceUnlocker { + static int IsUnlockableUnlocked(eUnlockFilters filter, eUnlockableEntity ent, int level, int player, bool backroom); + static int IsCarPartUnlocked(eUnlockFilters filter, int carslot, CarPart *part, int player, bool backroom); + static int IsPerfPackageUnlocked(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, int player, bool backroom); + static bool IsTrackUnlocked(eUnlockFilters filter, int event_hash, int player); + static bool IsCarUnlocked(eUnlockFilters filter, unsigned int car, int player); + static bool IsBackroomAvailable(eUnlockFilters filter, eUnlockableEntity ent, int level, int player); +}; + +struct OnlineUnlocker { + static bool IsUnlockableUnlocked(eUnlockFilters filter, eUnlockableEntity ent, int level, bool backroom); + static bool IsCarPartUnlocked(eUnlockFilters filter, int carslot, CarPart *part, bool backroom); + static bool IsPerfPackageUnlocked(eUnlockFilters filter, Physics::Upgrades::Type pkg_type, int level, bool backroom); + static bool IsTrackUnlocked(eUnlockFilters filter, int event_hash); + static bool IsCarUnlocked(eUnlockFilters filter, unsigned int car); + static bool IsBackroomAvailable(eUnlockFilters filter, eUnlockableEntity ent, int level); +}; + +struct FEMarkerManager { + enum ePossibleMarker { + MARKER_NONE = 0, + MARKER_BRAKES = 1, + MARKER_ENGINE = 2, + MARKER_NOS = 3, + MARKER_INDUCTION = 4, + MARKER_CHASSIS = 5, + MARKER_TIRES = 6, + MARKER_TRANSMISSION = 7, + MARKER_BODY = 8, + MARKER_HOOD = 9, + MARKER_SPOILER = 10, + MARKER_RIMS = 11, + MARKER_ROOF_SCOOP = 12, + MARKER_CUSTOM_HUD = 13, + MARKER_VINYL = 14, + MARKER_DECAL = 15, + MARKER_PAINT = 16, + MARKER_CUSTOMIZE_FIRST = 1, + MARKER_CUSTOMIZE_LAST = 16, + MARKER_GET_OUT_OF_JAIL = 17, + MARKER_PINK_SLIP = 18, + MARKER_CASH = 19, + MARKER_ADD_IMPOUND_BOX = 20, + MARKER_IMPOUND_RELEASE = 21, + MARKER_FIRST = 1, + MARKER_LAST = 21, + }; + + enum eMarkerStates { + MARKER_STATE_NOT_OWNED = 0, + MARKER_STATE_OWNED = 1, + MARKER_STATE_USED = 2, + }; + + struct OwnedMarker { + ePossibleMarker Marker; // offset 0x0 + int Param; // offset 0x4 + eMarkerStates State; // offset 0x8 + }; + + FEMarkerManager(); + void Default(); + void GetMarkerForLaterSelection(int index, ePossibleMarker &marker, int ¶m); + void AddMarkerForLaterSelection(ePossibleMarker marker, int param); + void ClearMarkersForLaterSelection(); + void AddMarkerToInventory(ePossibleMarker marker, int param); + void UtilizeMarker(ePossibleMarker marker, int param); + void UtilizeMarker(unsigned int slot_id); + void UtilizeMarker(Physics::Upgrades::Type type); + bool IsMarkerAvailable(ePossibleMarker marker, int param); + int GetNumCustomizeMarkers(); + int GetNumMarkers(ePossibleMarker marker, int param); + ePossibleMarker ConvertBigBangMarkerAward(const char *marker_name, const char *partid); + void AwardMarker(Attrib::Gen::gameplay &inst, bool immediate_reward); + char *SaveToBuffer(char *buffer); + char *LoadFromBuffer(char *buffer); + int GetSaveBufferSize() { return 0x2F4; } + + inline int GetNumTempMarkers() { return iNumTempMarkers; } + inline bool HasMarker(ePossibleMarker marker, int param) { + return GetNumMarkers(marker, param) > 0; + } + + OwnedMarker OwnedMarkers[63]; // offset 0x0 + OwnedMarker TempSelectionMarkers[6]; // offset 0x2F4 + int iNumTempMarkers; // offset 0x33C +}; + +extern FEMarkerManager TheFEMarkerManager; + #endif diff --git a/src/Speed/Indep/Src/Frontend/Database/FEDatabase.cpp b/src/Speed/Indep/Src/Frontend/Database/FEDatabase.cpp index e69de29bb..b969c3dd9 100644 --- a/src/Speed/Indep/Src/Frontend/Database/FEDatabase.cpp +++ b/src/Speed/Indep/Src/Frontend/Database/FEDatabase.cpp @@ -0,0 +1,1130 @@ +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GMilestone.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/audiosystem.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" +typedef int PathEventEnum; +#include "Speed/Indep/Src/Generated/AttribSys/Classes/music.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Libs/Support/Utility/UStandard.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern unsigned int FEngHashString(const char *, ...); +extern eLanguages GetCurrentLanguage(); +extern EAXSound *g_pEAXSound; +extern unsigned int bCalculateCrc32(const void *data, int size, unsigned int prev_crc32); +extern int SkipFE; +extern int SkipFESplitScreen; +extern int g_MaxSongs; +extern void InitializeEATrax(bool breset); + +namespace Sound { + +struct stSongInfo { + char *SongName; + char *Artist; + char *Album; + char *DefPlay; + int PathEvent; +}; + +} // namespace Sound + +typedef std::vector SongInfoList; + +extern SongInfoList Songs; + +const char* UserProfile::GetProfileName() {} + +UserProfile::UserProfile() { + TheOptionsSettings.TheVideoSettings.Default(); + TheOptionsSettings.TheGameplaySettings.Default(); + TheOptionsSettings.TheAudioSettings.Default(); + for (int i = 0; i < 2; i++) { + TheOptionsSettings.ThePlayerSettings[i].Default(); + } + TheOptionsSettings.Default(); + for (int i = 0; i < 150; i++) { + TheCareerSettings.SMSMessages[i].Handle = 0xFF; + TheCareerSettings.SMSMessages[i].Flags = 0; + TheCareerSettings.SMSMessages[i].SortOrder = 0; + } + for (int i = 0; i < 5; i++) { + bMemSet(&HighScores.TopEvadedPursuitScores[i], 0, 0x38); + } + bMemSet(&HighScores.CareerPursuitDetails, 0, 0x20); + for (int i = 0; i < 10; i++) { + bMemSet(&HighScores.BestPursuitRankings[i], 0, 8); + } + bMemSet(&HighScores.CostToStateDetails, 0, 0x20); +} + +UserProfile::~UserProfile() { +} + +bool UserProfile::IsProfileNamed() { + return m_bNamed; +} + +void UserProfile::Default(int player_number, bool commit_default) { + static bool song_info_loaded = false; + + if (!commit_default) { + SetProfileName(nullptr, true); + } else { + SetProfileName(nullptr, false); + } + + PlayersCarStable.Default(); + + if (!commit_default) { + TheOptionsSettings.Default(); + TheCareerSettings.Default(); + HighScores.Default(); + CareerModeHasBeenCompletedAtLeastOnce = false; + + if (!song_info_loaded) { + song_info_loaded = true; + + Attrib::Gen::audiosystem *playlist_atrs = new Attrib::Gen::audiosystem(0x7E4B0ED2, 0, nullptr); + if (playlist_atrs->IsValid()) { + Attrib::Gen::audiosystem licensed_music(playlist_atrs->LicensedMusic(0), 0, nullptr); + g_MaxSongs = licensed_music.Num_PFMapping(); + + for (int i = 0; i < static_cast(Songs.size()); i++) { + delete Songs[i]; + } + Songs.clear(); + + if (static_cast(Songs.capacity()) < g_MaxSongs) { + Songs.reserve(g_MaxSongs); + } + + for (int i = 0; i < g_MaxSongs; i++) { + Sound::stSongInfo *newsong = new (__FILE__, __LINE__) Sound::stSongInfo; + Attrib::Gen::music currsong(static_cast(nullptr), 0, nullptr); + const char *tmpSongName; + + currsong.ChangeWithDefault(licensed_music.PFMapping(i)); + + tmpSongName = currsong.SongName().GetString(); + newsong->SongName = const_cast(tmpSongName ? tmpSongName : ""); + tmpSongName = currsong.Album().GetString(); + newsong->Album = const_cast(tmpSongName ? tmpSongName : ""); + tmpSongName = currsong.Artist().GetString(); + newsong->Artist = const_cast(tmpSongName ? tmpSongName : ""); + tmpSongName = currsong.DefPlay().GetString(); + newsong->DefPlay = const_cast(tmpSongName ? tmpSongName : ""); + newsong->PathEvent = currsong.PathEvent(); + Songs.push_back(newsong); + } + } + } + + for (int i = 0; i < g_MaxSongs; i++) { + Playlist[i].SongIndex = i; + + unsigned char playability = 0; + Sound::stSongInfo *song = Songs[i]; + if (song) { + if (bStrCmp(song->DefPlay, "FE") == 0) { + playability = 1; + } else if (bStrCmp(song->DefPlay, "IG") == 0) { + playability = 2; + } else if (bStrCmp(song->DefPlay, "AL") == 0) { + playability = 3; + } + } + Playlist[i].PlayabilityField = playability; + } + + InitializeEATrax(true); + } + + PlayersCarStable.AwardBonusCars(); + TheCareerSettings.TryAwardDemoMarker(); +} + +SMSMessage *CareerSettings::GetSMSMessage(unsigned int index) { + if (index < 0x96) { + return &SMSMessages[index]; + } + return nullptr; +} + +bool SMSMessage::IsVoice() { + switch (Handle) { + case 0x6E: + case 0x6F: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + case 0x80: + case 0x81: + case 0x82: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + return false; + default: + return true; + } +} + +unsigned short CareerSettings::GetSMSSortOrder() { + SMSSortOrder = SMSSortOrder + 1; + return SMSSortOrder; +} + +void CareerSettings::SpendCash(int amount) { + if (static_cast(amount) > CurrentCash) { + CurrentCash = 0; + return; + } + CurrentCash = CurrentCash - amount; +} + +void CareerSettings::AwardOneTimeCashBonus(bool bOldSaveExists) { + SpecialFlags = SpecialFlags | 2; + if (!bOldSaveExists) { + return; + } + CurrentCash = CurrentCash + 10000; +} + +void CareerSettings::SetPlayerHasBeatenTheGame() { + SetHasBeatenCareer(); +} + +int CareerSettings::GetSaveBufferSize(bool bExcludeGameplay) { + int size = TheFEMarkerManager.GetSaveBufferSize() + 0x441; + if (!bExcludeGameplay) { + size += 0x52C4; + } + return size; +} + +bool GameplaySettings::IsMapItemEnabled(eWorldMapItemType type) { + if ((MapItems & type) != 0) { + return true; + } + return false; +} + +void GameplaySettings::SetMapItem(eWorldMapItemType type, bool enabled) { + if (enabled) { + MapItems = MapItems | type; + return; + } + MapItems = MapItems & ~type; +} + +void VideoSettings::Default() { + FEScale = 1.0f; + ScreenOffsetX = 0.0f; + ScreenOffsetY = 0.0f; + WideScreen = 0; +} + +void cFrontendDatabase::FillCustomRace(GRaceCustom *parms, RaceSettings *race) { + if (!race) { + return; + } + if (!parms) { + return; + } + parms->SetCatchUp(race->CatchUp); + parms->SetCopsEnabled(race->CopsOn); + if (race->CopsOn) { + parms->SetHeatLevel(race->CopDensity); + } + parms->SetDifficulty(static_cast(race->AISkill)); + parms->SetNumLaps(race->NumLaps); + parms->SetNumOpponents(race->NumOpponents); + switch (race->TrafficDensity) { + case 1: + parms->SetTrafficDensity(10); + break; + case 2: + parms->SetTrafficDensity(30); + break; + case 3: + parms->SetTrafficDensity(50); + break; + default: + parms->SetTrafficDensity(0); + break; + } + parms->SetReversed(race->TrackDirection == 1); +} + +RaceSettings *cFrontendDatabase::GetQuickRaceSettings(GRace::Type type) { + if (static_cast(type) > 10) { + return &TheQuickRaceSettings[RaceMode]; + } + return &TheQuickRaceSettings[type]; +} + +unsigned int cFrontendDatabase::GetUserProfileSaveSize(bool bExcludeGameplay) { + return CurrentUserProfiles[0]->GetSaveBufferSize(bExcludeGameplay); +} + +void cFrontendDatabase::SaveUserProfileToBuffer(void *buffer, int size) { + CurrentUserProfiles[0]->SaveToBuffer(buffer, size); +} + +unsigned int cFrontendDatabase::GetChallengeHeaderHash(unsigned int index) { + return FEngHashString("CHALLENGE_HEADER_%d", index); +} + +unsigned int cFrontendDatabase::GetChallengeDescHash(unsigned int index) { + return FEngHashString("CHALLENGE_DESCRIPTION_%d", index); +} + +unsigned int cFrontendDatabase::GetBountyIconHash(unsigned int index) { + if (index < 5) { + return 0x8A21B882; + } + if (index < 9) { + return 0x895EC0AE; + } + return 0x9129E7FB; +} + +unsigned int cFrontendDatabase::GetBountyHeaderHash(unsigned int index) { + return FEngHashString("BOUNTY_HEADER_%d", index); +} + +unsigned int cFrontendDatabase::GetBountyDescHash(unsigned int index) { + return FEngHashString("BOUNTY_DESCRIPTION_%d", index); +} + +void RaceSettings::Default() { + NumOpponents = 3; + CatchUp = true; + TrafficDensity = 1; + NumLaps = 2; + IsLapKO = false; + AISkill = 1; + CopDensity = 1; + CopsOn = false; + TrackDirection = 0; + for (int i = 0; i < 2; i++) { + SelectedCar[i] = 0; + } + RegionFilterBits = 3; +} + +void cFrontendDatabase::SetPlayersJoystickPort(int player, signed char port) { + if (port == -1 && PlayerJoyports[player] != -1) { + cFEngJoyInput::mInstance->SetRequiredJoy(static_cast(PlayerJoyports[player]), false); + } + PlayerJoyports[player] = port; +} + +static int MikeMannBuild; + +int GetMikeMannBuild() { + return MikeMannBuild; +} + +static bool IsCollectorsEdition; + +bool GetIsCollectorsEdition() { + return IsCollectorsEdition; +} + +void PlayerSettings::Default() { + Handling = 1; + CurCam = PSC_DEFAULT; + Transmission = 0; + GaugesOn = 1; + PositionOn = 1; + LapInfoOn = 1; + ScoreOn = 1; + LeaderboardOn = 1; + TransmissionPromptOn = 1; + Rumble = 1; + DriveWithAnalog = 1; + Config = CC_CONFIG_1; + SplitTimeType = 0; +} + +bool PlayerSettings::operator==(const PlayerSettings& rhs) const { + return bMemCmp(this, &rhs, 0x2C) == 0; +} + +void PlayerSettings::DefaultFromOptionsScreen() { + int savedDriveWithAnalog = DriveWithAnalog; + eControllerConfig savedConfig = Config; + int savedRumble = Rumble; + Default(); + DriveWithAnalog = savedDriveWithAnalog; + Config = savedConfig; + Rumble = savedRumble; +} + +POVTypes GetPOVTypeFromPlayerCamera(ePlayerSettingsCameras cam); +bool IsPlayerCameraSelectable(POVTypes pov); + +void PlayerSettings::ScrollDriveCam(int dir) { + int cam = CurCam; + if (dir == 1) { + do { + cam++; + if (cam > 6) { + cam = 0; + } + } while (!IsPlayerCameraSelectable( + GetPOVTypeFromPlayerCamera(static_cast< ePlayerSettingsCameras >(cam)))); + CurCam = static_cast< ePlayerSettingsCameras >(cam); + } else if (dir == -1) { + do { + cam--; + if (cam < 0) { + cam = 6; + } + } while (!IsPlayerCameraSelectable( + GetPOVTypeFromPlayerCamera(static_cast< ePlayerSettingsCameras >(cam)))); + CurCam = static_cast< ePlayerSettingsCameras >(cam); + } else { + CurCam = static_cast< ePlayerSettingsCameras >(cam); + } +} + +extern unsigned int HudConfigs[]; +extern unsigned int DriveConfigs[]; + +unsigned int PlayerSettings::GetControllerAttribs(eControllerAttribs type, bool wheel_connected) const { + int analog = DriveWithAnalog != 0; + int config = Config; + if (wheel_connected) { + config = 0; + analog = 1; + } + if (type != CA_HUD) { + if (type == CA_DRIVING) { + return DriveConfigs[analog + config * 2]; + } + return 0; + } + return HudConfigs[analog + config * 2]; +} + +void GameplaySettings::Default() { + AutoSaveOn = 1; + RearviewOn = 1; + Damage = 1; + RacingMiniMapMode = 1; + LastMapZoom = 1; + JumpCam = 1; + MapItems = static_cast(-1); + LastPursuitMapZoom = 2; + LastMapView = 0; + HighlightCam = 127.5f; + ExploringMiniMapMode = 0; + eLanguages lang = GetCurrentLanguage(); + if (lang) { + SpeedoUnits = 1; + } else { + SpeedoUnits = 0; + } +} + +bool GameplaySettings::operator==(const GameplaySettings& rhs) const { + return bMemCmp(this, &rhs, 0x20) == 0; +} + +bool VideoSettings::operator==(const VideoSettings& rhs) const { + return bMemCmp(this, &rhs, 0x10) == 0; +} + +bool AudioSettings::operator==(const AudioSettings& rhs) const { + if (MasterVol != rhs.MasterVol) return false; + if (SpeechVol != rhs.SpeechVol) return false; + if (FEMusicVol != rhs.FEMusicVol) return false; + if (IGMusicVol != rhs.IGMusicVol) return false; + if (SoundEffectsVol != rhs.SoundEffectsVol) return false; + if (EngineVol != rhs.EngineVol) return false; + if (CarVol != rhs.CarVol) return false; + if (AmbientVol != rhs.AmbientVol) return false; + if (SpeedVol != rhs.SpeedVol) return false; + if (InteractiveMusicMode != rhs.InteractiveMusicMode) return false; + if (EATraxMode != rhs.EATraxMode) return false; + if (PlayState != rhs.PlayState) return false; + return AudioMode == rhs.AudioMode; +} + +void AudioSettings::Default() { + AudioMode = 2; + AmbientVol = 1.0f; + CarVol = 1.0f; + EngineVol = 1.0f; + SoundEffectsVol = 1.0f; + FEMusicVol = 0.8f; + SpeechVol = 1.0f; + MasterVol = 1.0f; + SpeedVol = 1.0f; + IGMusicVol = 0.8f; + AudioMode = g_pEAXSound->GetDefaultPlatformAudioMode(); + PlayState = 0; + EATraxMode = 1; + InteractiveMusicMode = 1; +} + +void OptionsSettings::Default() { + CurrentCategory = static_cast(0); + TheVideoSettings.Default(); + TheAudioSettings.Default(); + TheGameplaySettings.Default(); + ThePlayerSettings[0].Default(); + ThePlayerSettings[1].Default(); +} + +char *SaveSomeData(void *save_to, void *save_from, int bytes, void *maxptr) { + if (reinterpret_cast(save_to) + bytes <= reinterpret_cast(maxptr)) { + bMemCpy(save_to, save_from, bytes); + save_to = static_cast(save_to) + bytes; + } + return static_cast(save_to); +} + +char *LoadSomeData(void *load_to, void *load_from, int bytes, void *maxptr) { + if (reinterpret_cast(load_from) + bytes <= reinterpret_cast(maxptr)) { + bMemCpy(load_to, load_from, bytes); + } + return static_cast(load_from) + bytes; +} + +FEKeyboardSettings::FEKeyboardSettings() { + AcceptCallbackHash = 0xAE83B9DB; + DeclineCallbackHash = 0x6A97B51F; + MaxTextLength = 64; + Buffer[0] = 0; + DefaultTextHash = 0; + Mode = 0; +} + +unsigned int cFrontendDatabase::GetMilestoneHeaderHash(unsigned int tag) { + return FEngHashString("BLACKLIST_PURSUIT_MILESTONES_%02d_SHORT", tag); +} + +unsigned int cFrontendDatabase::GetMilestoneDescHash(unsigned int tag) { + return FEngHashString("BLACKLIST_PURSUIT_MILESTONES_%02d", tag); +} + +void cFrontendDatabase::SetMilestoneDescriptionString(char *outputStr, int milestoneType, float currVal, float goalVal, bool showCurrVal) const { + if (showCurrVal && currVal > goalVal) { + currVal = goalVal; + } + switch (milestoneType) { + case 0x33fa23a: { + char currValTimeToPrint[16]; + char goalValTimeToPrint[16]; + Timer currValTimer(currVal); + currValTimer.PrintToString(currValTimeToPrint, 4); + Timer goalValTimer(goalVal); + goalValTimer.PrintToString(goalValTimeToPrint, 4); + if (showCurrVal) { + bSPrintf(outputStr, "%s/%s", currValTimeToPrint, goalValTimeToPrint); + } else { + bSPrintf(outputStr, "%s", goalValTimeToPrint); + } + break; + } + case 0x5392e4fd: { + float printTime = currVal; + char currValTimeToPrint[16]; + char goalValTimeToPrint[16]; + if (currVal == 0.0f) { + IPlayer *player = IPlayer::First(PLAYER_LOCAL); + if (player) { + ISimable *simable = player->GetSimable(); + if (simable) { + IVehicle *vehicle; + if (simable->QueryInterface(&vehicle)) { + IVehicleAI *vehicleai = vehicle->GetAIVehiclePtr(); + if (vehicleai) { + IPursuit *ipursuit = vehicleai->GetPursuit(); + if (ipursuit) { + float pursuitElapsedTime = ipursuit->GetPursuitDuration(); + float timeRemaining = goalVal - pursuitElapsedTime; + printTime = UMath::Max(timeRemaining, 0.0f); + } + } + } + } + } + } + Timer currValTimer(printTime); + currValTimer.PrintToString(currValTimeToPrint, 4); + Timer goalValTimer(goalVal); + goalValTimer.PrintToString(goalValTimeToPrint, 4); + if (showCurrVal) { + bSPrintf(outputStr, "%s/%s", currValTimeToPrint, goalValTimeToPrint); + } else { + bSPrintf(outputStr, "%s", goalValTimeToPrint); + } + break; + } + default: + if (showCurrVal) { + bSPrintf(outputStr, "%$0.0f/%$0.0f", currVal, goalVal); + } else { + bSPrintf(outputStr, "%$0.0f", goalVal); + } + break; + } +} + +bool cFrontendDatabase::IsMilestoneTimeFormat(int typeKey) const { + if (typeKey == 0x33fa23a || typeKey == 0x5392e4fd) { + return true; + } + return false; +} + +GameCompletionStats::GameCompletionStats() + : m_nOverall(0) // + , m_nCareer(0) // + , m_nRapSheetRankings(0) // + , m_nChallenge(0) // + , m_nTotalChallengeRaces(0) // + , m_nCompletedChallengeRaces(0) +{ +} + +GameCompletionStats cFrontendDatabase::GetGameCompletionStats() { + GameCompletionStats stats; + float nTotalCareerRaces = 0.0f; + float nCompletedCareerRaces = 0.0f; + float nTotalMilestones = static_cast(GManager::Get().GetNumMilestones()); + float nMilestonesAwarded = 0.0f; + float nTotalRapSheetRankings = 140.0f; + float nRapSheetRankings = 0.0f; + + for (unsigned int i = 1; i < 17; i++) { + GRaceBin *pBin = GRaceDatabase::Get().GetBinNumber(i); + unsigned int nBossRaces = pBin->GetBossRaceCount(); + for (unsigned int j = 0; j < nBossRaces; j++) { + if (GRaceDatabase::Get().IsCareerRaceComplete(pBin->GetBossRaceHash(j))) { + nCompletedCareerRaces += 1.0f; + } + } + unsigned int nWorldRaces = pBin->GetWorldRaceCount(); + for (unsigned int j = 0; j < nWorldRaces; j++) { + if (GRaceDatabase::Get().IsCareerRaceComplete(pBin->GetWorldRaceHash(j))) { + nCompletedCareerRaces += 1.0f; + } + } + nTotalCareerRaces += static_cast(nBossRaces + nWorldRaces); + } + + for (unsigned int i = 0; i < GRaceDatabase::Get().GetRaceCount(); i++) { + GRaceParameters *pParams = GRaceDatabase::Get().GetRaceParameters(i); + if (bStrCmp(pParams->GetEventID(), GRaceDatabase::Get().GetBurgerKingRace()) != 0) { + if (GetIsCollectorsEdition() || !pParams->GetIsCollectorsEditionRace()) { + if (pParams->GetIsChallengeSeriesRace()) { + stats.m_nTotalChallengeRaces++; + if (GRaceDatabase::Get().IsQuickRaceComplete(pParams->GetEventHash())) { + stats.m_nCompletedChallengeRaces++; + } + } + } + } + } + + for (unsigned int i = 0; static_cast(i) < nTotalMilestones; i++) { + if (GManager::Get().GetMilestone(i)->GetIsAwarded()) { + nMilestonesAwarded += 1.0f; + } + } + + for (unsigned int i = 0; i < 10; i++) { + int rankMovement = FEDatabase->GetMultiplayerProfile(0)->GetHighScores()->CalcPursuitRank( + static_cast(i), true); + if (15 - rankMovement >= 0) { + nRapSheetRankings += static_cast(15 - rankMovement); + } + } + + stats.m_nRapSheetRankings = static_cast( + nRapSheetRankings / nTotalRapSheetRankings * 100.0f); + stats.m_nCareer = static_cast( + (nCompletedCareerRaces + nMilestonesAwarded) / (nTotalCareerRaces + nTotalMilestones) * 100.0f); + stats.m_nChallenge = static_cast( + static_cast(stats.m_nCompletedChallengeRaces) / + static_cast(stats.m_nTotalChallengeRaces) * 100.0f); + stats.m_nOverall = static_cast( + static_cast(static_cast(stats.m_nCareer)) * 0.7f + + static_cast(static_cast(stats.m_nChallenge)) * 0.2f + + static_cast(static_cast(stats.m_nRapSheetRankings)) * 0.1f); + + return stats; +} + +void cFrontendDatabase::NotifyExitRaceToFrontend(eExitRacePlaces from_where) { + { + int is_split = IsSplitScreenMode(); + } + PostRaceOptionChosen = static_cast(1); + if (from_where == EXIT_RACE_FROM_PAUSE) { + CurrentUserProfiles[0]->CommitHighScoresPauseQuit(); + } +} + +void cFrontendDatabase::DeallocBackupDB() { + if (m_pDBBackup) { + bFree(m_pDBBackup); + m_pDBBackup = nullptr; + } +} + +int UserProfile::GetSaveBufferSize(bool bExcludeGameplay) { + int size = TheCareerSettings.GetSaveBufferSize(bExcludeGameplay) + 0x1e4; + return size + PlayersCarStable.GetSaveBufferSize() + 0xc18; +} + +unsigned int GetFECarNameHashFromFEKey(unsigned int feKey) { + if (!feKey) { + return 0; + } + FECarRecord rec; + rec.FEKey = feKey; + return rec.GetNameHash(); +} + +void cFrontendDatabase::AllocBackupDB(bool bForce) { + if (!m_pDBBackup && bForce) { + m_pDBBackup = static_cast(bMalloc(GetUserProfileSaveSize(false), nullptr, 0, 0x40)); + SaveUserProfileToBuffer(m_pDBBackup, GetUserProfileSaveSize(false)); + } +} + +void cFrontendDatabase::BackupCarStable() { + if (!m_pCarStableBackup) { + m_pCarStableBackup = static_cast(bMalloc(GetPlayerCarStable(0)->GetSaveBufferSize(), 0)); + bMemCpy(m_pCarStableBackup, GetPlayerCarStable(0), GetPlayerCarStable(0)->GetSaveBufferSize()); + } +} + +bool cFrontendDatabase::IsCarStableDirty() { + if (!m_pCarStableBackup) { + return false; + } + bool result = bMemCmp(m_pCarStableBackup, GetPlayerCarStable(0), GetPlayerCarStable(0)->GetSaveBufferSize()) != 0; + bFree(m_pCarStableBackup); + m_pCarStableBackup = nullptr; + return result; +} + +extern unsigned int FEngHashString(const char *, unsigned char); +extern bool DoesStringExist(unsigned int); +extern bool gVerboseTesterOutput; +extern void bToUpper(char *); + +void CareerSettings::Default() { + CurrentCash = 0; + CurrentBin = 0x10; + CurrentCar = 0; + SpecialFlags = 0; + AdaptiveDifficulty = 0; + for (int i = 0; i < 150; i++) { + SMSMessages[i].SetHandle(static_cast(i)); + if (!DoesStringExist(FEngHashString("", SMSMessages[i].GetHandle()))) { + SMSMessages[i].SetHandle(0xFF); + } + SMSMessages[i].ClearFlags(); + } + SMSSortOrder = 0; +} + +extern bool SkipDDayRaces; +extern bool SkipCareerIntro; + +void CareerSettings::StartNewCareer(bool bEnterGameplay) { + Default(); + CurrentCar = FEDatabase->GetDefaultCar(); + GenerateCaseFileName(); + SpecialFlags |= 1; + + if (SkipCareerIntro && SkipDDayRaces) { + CurrentBin = 0xF; + GRaceDatabase::Get().SimulateDDayComplete(); + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *rec = stable->CreateNewCareerCar(0x2CF385B2); + CurrentCar = rec->Handle; + rec = stable->CreateNewCareerCar(0x03A94520); + CurrentCar = rec->Handle; + } + + TryAwardDemoMarker(); + + if (!bEnterGameplay) { + return; + } + + FEDatabase->ResetGameMode(); + FEDatabase->SetGameMode(eFE_GAME_MODE_CAREER); + + if (SkipDDayRaces) { + FEManager::Get()->SetGarageType(GARAGETYPE_CAREER_SAFEHOUSE); + FEDatabase->ClearGameMode(eFE_GAME_MODE_CAREER_MANAGER); + if (SkipCareerIntro) { + CurrentBin = 0xF; + } + } else { + unsigned int hash = FEHashUpper("M3GTRCAREERSTART"); + FEDatabase->GetCareerSettings()->SetCurrentCar(hash); + FEDatabase->GetQuickRaceSettings(GRace::kRaceType_NumTypes)->SetSelectedCar(hash, 0); + gMemcardSetup.mPreviousCommand = 0; + gMemcardSetup.mPreviousPrompt = 0; + gMemcardSetup.mOp = 0; + gMemcardSetup.mMemScreen = nullptr; + gMemcardSetup.mToScreen = nullptr; + gMemcardSetup.mFromScreen = nullptr; + gMemcardSetup.mTermFunc = nullptr; + gMemcardSetup.mTermFuncParam = nullptr; + gMemcardSetup.mLastMessage = 0; + gMemcardSetup.mSuccessMsg = 0; + gMemcardSetup.mFailedMsg = 0; + gMemcardSetup.mInBootFlow = false; + const char *firstDDayRace = GRaceDatabase::Get().GetDDayStartRace(); + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromName(firstDDayRace); + GRaceCustom *race = GRaceDatabase::Get().AllocCustomRace(parms); + GRaceDatabase::Get().SetStartupRace(race, kRaceContext_Career); + GRaceDatabase::Get().FreeCustomRace(race); + RaceStarter::StartCareerFreeRoam(); + } +} + +void CareerSettings::ResumeCareer() { + bool bDDayCompleted = false; + if (SkipDDayRaces || + GRaceDatabase::Get().CheckRaceScoreFlags( + GRaceDatabase::Get() + .GetRaceFromHash(Attrib::StringHash32("16.2.1")) + ->GetEventHash(), + GRaceDatabase::kCompleted_ContextCareer)) { + bDDayCompleted = true; + } + + bool bTutorialCompleted = false; + if (!(SpecialFlags & 0x4000)) { + if (GRaceDatabase::Get().CheckRaceScoreFlags( + GRaceDatabase::Get() + .GetRaceFromHash(Attrib::StringHash32("1.2.3")) + ->GetEventHash(), + GRaceDatabase::kCompleted_ContextCareer)) { + bTutorialCompleted = true; + } + } + + if (CurrentBin == 0x10) { + if (!bDDayCompleted) { + unsigned int carHash = FEHashUpper("M3GTRCAREERSTART"); + FEDatabase->GetCareerSettings()->CurrentCar = carHash; + FEDatabase->GetQuickRaceSettings(GRace::kRaceType_NumTypes)->SelectedCar[0] = carHash; + const char *nextRace = GRaceDatabase::Get().GetNextDDayRace(); + GRaceParameters *parms = + GRaceDatabase::Get().GetRaceFromHash(Attrib::StringHash32(nextRace)); + GRaceCustom *custom = GRaceDatabase::Get().AllocCustomRace(parms); + GRaceDatabase::Get().SetStartupRace(custom, kRaceContext_Career); + GRaceDatabase::Get().FreeCustomRace(custom); + if (bStrCmp(nextRace, "16.1.0") != 0) { + MemoryCard::s_pThis->m_bCancelNextAutoSave = true; + } + } + RaceStarter::StartCareerFreeRoam(); + } else if (bTutorialCompleted) { + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromHash(Attrib::StringHash32("1.8.1")); + GRaceCustom *custom = GRaceDatabase::Get().AllocCustomRace(parms); + GRaceDatabase::Get().SetStartupRace(custom, kRaceContext_Career); + GRaceDatabase::Get().FreeCustomRace(custom); + RaceStarter::StartCareerFreeRoam(); + MemoryCard::s_pThis->m_bCancelNextAutoSave = true; + } else { + FEManager::Get()->SetGarageType(static_cast< eGarageType >(2)); + FEDatabase->ClearGameMode(eFE_GAME_MODE_CAREER_MANAGER); + } + FEDatabase->SetGameMode(eFE_GAME_MODE_CAREER); +} + +void CareerSettings::GenerateCaseFileName() { + const int SCOTTS_RAND_CASE_FILE_NUMBER_RANGE = 0x19B3; + const int SCOTTS_RAND_CASE_FILE_NUMBER_START = 0x42D; + unsigned int num = bRandom(SCOTTS_RAND_CASE_FILE_NUMBER_RANGE) + SCOTTS_RAND_CASE_FILE_NUMBER_START; + const char *profile_name = FEDatabase->GetUserProfile(0)->GetProfileName(); + bSNPrintf(CaseFileName, 13, "%d%s", num, profile_name); + bToUpper(CaseFileName); +} + +extern int FEngSNPrintf(char *, int, const char *, ...); +extern unsigned long FEHashUpper(const char *); +extern void FixDot(char *str, int len); + +cFrontendDatabase::cFrontendDatabase() + : iDefaultStableHash(0) // + , m_pCarStableBackup(nullptr) // + , m_pDBBackup(nullptr) // + , SplitScreenCustomization(nullptr) +{ + for (int i = 0; i < 2; i++) { + CurrentUserProfiles[i] = nullptr; + } + CurrentUserProfiles[0] = new UserProfile(); +} + +unsigned int CalcLanguageHash(const char *prefix, GRaceParameters *pRaceParams) { + char buffer[64]; + FEngSNPrintf(buffer, 0x40, "%s%s", prefix, pRaceParams->GetEventID()); + FixDot(buffer, 0x40); + return FEHashUpper(buffer); +} + +void InitFrontendDatabase() { + unsigned int alloc_params = GetVirtualMemoryAllocParams(); + FEDatabase = new(alloc_params) cFrontendDatabase(); + FEDatabase->Default(); +} + +bool cFrontendDatabase::IsFinalEpicChase() { + if (!GRaceStatus::Exists()) { + return false; + } + if (!GRaceStatus::Get().GetRaceParameters()) { + return false; + } + unsigned int event_hash = GRaceStatus::Get().GetRaceParameters()->GetEventHash(); + unsigned int final_hash = Attrib::StringHash32("1.8.1"); + return event_hash == final_hash; +} + +unsigned int cFrontendDatabase::GetRaceNameHash(GRace::Type type) { + switch (type) { + case GRace::kRaceType_P2P: + return 0xb94fd70e; + case GRace::kRaceType_Circuit: + return 0x034fa2c1; + case GRace::kRaceType_Drag: + return 0x6f547e4c; + case GRace::kRaceType_Knockout: + return 0x4930f5fc; + case GRace::kRaceType_Tollbooth: + return 0xa15e4505; + case GRace::kRaceType_SpeedTrap: + return 0xee1edc76; + case GRace::kRaceType_Challenge: + return 0x213cc8d1; + default: + return 0x7818f85e; + } +} + +unsigned int cFrontendDatabase::GetRaceIconHash(GRace::Type type) { + switch (type) { + case GRace::kRaceType_P2P: + return 0x2521e5eb; + case GRace::kRaceType_Circuit: + return 0xe9638d3e; + case GRace::kRaceType_Drag: + return 0xaaab31e9; + case GRace::kRaceType_Knockout: + return 0x3a015595; + case GRace::kRaceType_Tollbooth: + return 0x1a091045; + case GRace::kRaceType_SpeedTrap: + case GRace::kRaceType_JumpToSpeedTrap: + return 0x66c9a7b6; + case GRace::kRaceType_JumpToMilestone: + return 0x1a091045; + default: + break; + } + return 0; +} + +unsigned int cFrontendDatabase::GetSafehouseIconHash(const char *name) { + unsigned int result = 0; + if (bStrICmp(name, "carlot") == 0) { + result = 0x4eaee18b; + } else if (bStrICmp(name, "safehouse") == 0) { + result = 0x0ed39f69; + } else if (bStrICmp(name, "customshop") == 0) { + result = 0x0cf07089; + } + return result; +} + +void cFrontendDatabase::GetRandomRaceOptions(RaceSettings *race, GRace::Type type) { + race->CatchUp = true; + race->CopDensity = static_cast< uint8 >(bRandom(4)); + race->AISkill = 1; + race->NumOpponents = static_cast< uint8 >(bRandom(3) + 1); + if (type == GRace::kRaceType_Circuit) { + race->NumLaps = static_cast< uint8 >(bRandom(5) + 1); + } else if (type == GRace::kRaceType_Knockout) { + race->NumLaps = static_cast< uint8 >(bRandom(3) + 1); + } else { + race->NumLaps = 1; + } + race->TrafficDensity = static_cast< uint8 >(bRandom(4)); + race->TrackDirection = static_cast< uint8 >(bRandom(1)); +} + +void cFrontendDatabase::BuildCurrentRideForPlayer(int player, RideInfo *ride) { + FEPlayerCarDB *stable; + if (static_cast< unsigned int >(player) < 2) { + stable = &GetUserProfile(player)->PlayersCarStable; + } else { + stable = nullptr; + } + unsigned int car; + unsigned int mode = FEGameMode; + if ((mode & 4) != 0 || (mode & 0x40) != 0 || (mode & 8) != 0) { + RaceSettings *settings = GetQuickRaceSettings(GRace::kRaceType_NumTypes); + car = settings->SelectedCar[player]; + } else { + car = GetUserProfile(0)->GetCareer()->GetCurrentCar(); + } + stable->BuildRideForPlayer(car, player, ride); +} + +void cFrontendDatabase::Default() { + bProfileLoaded = false; + bIsOptionsDirty = false; + bAutoSaveOverwriteConfirmed = false; + iNumPlayers = 1; + bComingFromBoot = true; + GetUserProfile(0)->Default(0, true); + FEGameMode = 0; + iCurPauseSubOptionType = 0; + iCurPauseOptionType = 0; + if (SkipFE && SkipFESplitScreen) { + FEGameMode = 4; + iNumPlayers = 2; + } + PlayerJoyports[0] = 0; + PlayerJoyports[1] = -1; + RaceMode = static_cast< GRace::Type >(1); + unsigned int default_car = GetDefaultCar(); + DefaultRaceSettings(); + GetUserProfile(0)->GetCareer()->SetCurrentCar(default_car); + if (!iDefaultStableHash) { + FEPlayerCarDB *stable = &GetUserProfile(0)->PlayersCarStable; + int buf_size = stable->GetSaveBufferSize(); + char *buf = static_cast< char * >(bMalloc(buf_size, 0x40)); + int save_size = stable->GetSaveBufferSize(); + stable->SaveToBuffer(buf, save_size); + int crc_size = stable->GetSaveBufferSize(); + iDefaultStableHash = bCalculateCrc32(buf, crc_size, 0xFFFFFFFF); + bFree(buf); + } +} + +void cFrontendDatabase::NotifyDeleteCar(unsigned int handle) { + unsigned int default_car = GetDefaultCar(); + for (unsigned int i = 0; i < 11; i++) { + RaceSettings &settings = TheQuickRaceSettings[i]; + if (settings.SelectedCar[0] == handle) { + settings.SelectedCar[0] = default_car; + } + if (settings.SelectedCar[1] == handle) { + settings.SelectedCar[0] = default_car; + } + } +} + +unsigned int cFrontendDatabase::GetMilestoneIconHash(unsigned int type, bool isMilestone) { + unsigned int hash = 0; + switch (type) { + case 0x850A64BC: + hash = 0x88E8DE9E; + break; + case 0x3FD1884D: + case 0x4FC942CA: + hash = 0x0FE608E6; + break; + case 0xFD989A3A: + hash = 0x87807869; + break; + case 0x7457EED4: + case 0x23B1BF0E: + case 0x15E88693: + case 0x20F1AEF3: + case 0x411B084E: + case 0x2CB7CAF4: + case 0x755F7845: + case 0x8ED622AD: + case 0xC8993341: + return 0; + case 0x1334DAE6: + case 0x1BF724E1: + case 0x254230F5: + case 0x4D9777F1: + case 0x9201E1F4: + case 0x9F8E56CE: + case 0xABDF316E: + case 0xCA9AFDF0: + case 0xE9A4423C: + return 0; + case 0x5392E4FD: + hash = 0x831B7EBE; + break; + case 0x033FA23A: + hash = 0x8C76CD0F; + if (isMilestone) { + hash = 0x950FCEBC; + } + break; + case 0xEB45F99D: + hash = 0xC43959D2; + break; + case 0x9E3EBB78: + hash = 0x3FFE9EC9; + break; + case 0xCDF36FC2: + hash = 0xE621B2EF; + break; + case 0xA61CAC24: + hash = 0x6784A80E; + break; + case 0x2377E50D: + hash = 0xB4E6456B; + break; + } + return hash; +} + +unsigned int cFrontendDatabase::GetDefaultCar() { + Attrib::Gen::frontend TheFrontend(Attrib::FindCollection(Attrib::Gen::frontend::ClassKey(), 0xeec2271a), 0, nullptr); + Attrib::RefSpec refSpec; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + refSpec = TheFrontend.default_car(); + Attrib::Gen::pvehicle vehicle(refSpec, 0, nullptr); + unsigned int default_car = 0; + unsigned int key = vehicle.GetCollection(); + for (int i = 0; i <= 199; i++) { + FECarRecord *car = stable->GetCarByIndex(i); + if (car->IsValid() && car->VehicleKey == key) { + default_car = car->Handle; + break; + } + } + return default_car; +} + +cFrontendDatabase *FEDatabase; diff --git a/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp b/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp index e3b8fea94..bced9e64b 100644 --- a/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp +++ b/src/Speed/Indep/Src/Frontend/Database/FEDatabase.hpp @@ -13,10 +13,33 @@ #include +class GRaceCustom; + #if ONLINE_SUPPORT #include "Speed/Indep/Src/Online/OnlineCfg.hpp" #endif +enum eFEGameModes { + eFE_GAME_MODE_NONE = 0, + eFE_GAME_MODE_CAREER = 1, + eFE_GAME_MODE_CHALLENGE = 2, + eFE_GAME_MODE_QUICK_RACE = 4, + eFE_GAME_MODE_ONLINE = 8, + eFE_GAME_MODE_OPTIONS = 16, + eFE_GAME_MODE_CUSTOMIZE = 32, + eFE_GAME_MODE_LAN = 64, + eFE_GAME_MODE_PROFILE_MANAGER = 128, + eFE_GAME_MODE_CAREER_MANAGER = 256, + eFE_GAME_MODE_RAP_SHEET = 512, + eFE_GAME_MODE_MODE_SELECT = 1024, + eFE_GAME_TRAILERS = 2048, + eFE_GAME_MODE_CAR_LOT = 32768, + eFE_GAME_MODE_SAFEHOUSE = 65536, + eFE_GAME_MODE_POST_RIVAL = 131072, + eFE_GAME_MODE_BEAT_GAME = 262144, + eFE_GAME_MODE_ALL = -1, +}; + enum eControllerConfig { CC_CONFIG_1, CC_CONFIG_2, @@ -71,12 +94,57 @@ enum eLoadSaveGame { eLOADSAVE_SAVE = 1, }; +enum eExitRacePlaces { + EXIT_RACE_FROM_PAUSE = 0, + EXIT_RACE_FROM_POSTRACE = 1, +}; + +#ifndef FRONTEND_DATABASE_EWORLDMAPITEMTYPE_DEFINED +#define FRONTEND_DATABASE_EWORLDMAPITEMTYPE_DEFINED +enum eWorldMapItemType { + WMIT_NONE = 0, + WMIT_PLAYER_CAR = 1, + WMIT_AI_RACE_CAR = 2, + WMIT_COP_CAR = 4, + WMIT_COP_HELI = 8, + WMIT_TRAFFIC_CAR = 16, + WMIT_ROADBLOCK = 32, + WMIT_CHECKPOINT = 64, + WMIT_CIRCUIT_RACE = 128, + WMIT_SPRINT_RACE = 256, + WMIT_LAP_KO_RACE = 512, + WMIT_DRAG_RACE = 1024, + WMIT_SPEED_TRAP_RACE = 2048, + WMIT_TOLLBOOTH_RACE = 4096, + WMIT_MULTIPOINT_RACE = 8192, + WMIT_CELL_PHONE_RACE = 16384, + WMIT_RIVAL_RACE = 32768, + WMIT_CASH_GRAB_RACE = 65536, + WMIT_CASH_GRAB_SMALL = 131072, + WMIT_CASH_GRAB_MED = 262144, + WMIT_CASH_GRAB_LARGE = 524288, + WMIT_CASH_GRAB_ALL = 917504, + WMIT_SPEED_TRAP = 1048576, + WMIT_SAFEHOUSE = 2097152, + WMIT_SHOP = 4194304, + WMIT_CAR_LOT = 8388608, + WMIT_TOKEN = 16777216, + WMIT_HIDING_SPOT = 33554432, + WMIT_PURSUIT_BREAKER = 67108864, +}; +#endif + // total size: 0x20 class GameplaySettings { public: - bool AutoSaveOn; // offset 0x0, size 0x1 - bool RearviewOn; // offset 0x4, size 0x1 - bool Damage; // offset 0x8, size 0x1 + void Default(); + bool operator==(const GameplaySettings& rhs) const; + bool IsMapItemEnabled(eWorldMapItemType type); + void SetMapItem(eWorldMapItemType type, bool enabled); + + int AutoSaveOn; // offset 0x0, size 0x1 + int RearviewOn; // offset 0x4, size 0x1 + int Damage; // offset 0x8, size 0x1 unsigned char SpeedoUnits; // offset 0xC, size 0x1 unsigned char RacingMiniMapMode; // offset 0xD, size 0x1 unsigned char ExploringMiniMapMode; // offset 0xE, size 0x1 @@ -84,24 +152,27 @@ class GameplaySettings { unsigned char LastMapZoom; // offset 0x14, size 0x1 unsigned char LastPursuitMapZoom; // offset 0x15, size 0x1 unsigned char LastMapView; // offset 0x16, size 0x1 - bool JumpCam; // offset 0x18, size 0x1 + int JumpCam; // offset 0x18, size 0x1 float HighlightCam; // offset 0x1C, size 0x4 }; // total size: 0x2C class PlayerSettings { public: + void Default(); + void DefaultFromOptionsScreen(); + bool operator==(const PlayerSettings& rhs) const; unsigned int GetControllerAttribs(eControllerAttribs type, bool wheel_connected) const; void ScrollDriveCam(int dir); - bool GaugesOn; - bool PositionOn; - bool LapInfoOn; - bool ScoreOn; - bool Rumble; - bool LeaderboardOn; - bool TransmissionPromptOn; - bool DriveWithAnalog; + int GaugesOn; + int PositionOn; + int LapInfoOn; + int ScoreOn; + int Rumble; + int LeaderboardOn; + int TransmissionPromptOn; + int DriveWithAnalog; eControllerConfig Config; ePlayerSettingsCameras CurCam; unsigned char SplitTimeType; @@ -112,15 +183,21 @@ class PlayerSettings { // total size: 0x10 class VideoSettings { public: + void Default(); + bool operator==(const VideoSettings& rhs) const; + float FEScale; // offset 0x0, size 0x4 float ScreenOffsetX; // offset 0x4, size 0x4 float ScreenOffsetY; // offset 0x8, size 0x4 - bool WideScreen; // offset 0xC, size 0x1 + int WideScreen; // offset 0xC, size 0x1 }; // total size: 0x34 class AudioSettings { public: + void Default(); + bool operator==(const AudioSettings& rhs) const; + float MasterVol; // offset 0x0, size 0x4 float SpeechVol; // offset 0x4, size 0x4 float FEMusicVol; // offset 0x8, size 0x4 @@ -139,6 +216,8 @@ class AudioSettings { // total size: 0xC0 class OptionsSettings { public: + void Default(); + eOptionsCategory CurrentCategory; // offset 0x0, size 0x4 VideoSettings TheVideoSettings; // offset 0x4, size 0x10 GameplaySettings TheGameplaySettings; // offset 0x14, size 0x20 @@ -149,7 +228,23 @@ class OptionsSettings { // total size: 0x4 struct SMSMessage { public: - private: + unsigned char GetHandle() { return Handle; } + void SetHandle(unsigned char handle) { Handle = handle; } + unsigned int GetFlags() { return Flags; } + void SetFlag(unsigned int flag) { Flags |= flag; } + void ClearFlags() { Flags = 0; } + unsigned short GetSortOrder() const { return SortOrder; } + void SetSortOrder(unsigned short order) { SortOrder = order; } + bool IsValid() { return Handle != 0xFF; } + bool IsRead() { return (Flags & 4) != 0; } + bool IsUnRead() { return (Flags & 2) != 0; } + bool IsVoice(); + + unsigned int GetMsgHash(); + unsigned int GetFromHash(); + unsigned int GetSubjectHash(); + unsigned int GetVoiceHash(); + unsigned char Handle; // offset 0x0, size 0x1 unsigned char Flags; // offset 0x1, size 0x1 unsigned short SortOrder; // offset 0x2, size 0x2 @@ -161,8 +256,114 @@ class CareerSettings { uint32 GetCurrentCar() { return CurrentCar; } + uint8 GetCurrentBin() const { + return CurrentBin; + } + void AwardOneTimeCashBonus(bool bOldSaveExists); + const char *GetCaseFileName() { return CaseFileName; } - private: + bool HasCareerStarted() { + return SpecialFlags & 1; + } + bool IsGameOver() { + return SpecialFlags & 0x800; + } + void SetGameOver() { + SpecialFlags |= 0x800; + } + bool HasRapSheet() { + return SpecialFlags & 0x10; + } + void SetHasRapSheet() { + SpecialFlags |= 0x10; + } + void SetHasDoneCareerIntro() { + SpecialFlags |= 0x20; + } + bool HasDoneCareerIntro() { + return SpecialFlags & 0x20; + } + bool HasDoneDragTutorial() { + return SpecialFlags & 0x40; + } + void SetHasDoneDragTutorial() { + SpecialFlags |= 0x40; + } + bool HasDoneSpeedTrapTutorial() { + return SpecialFlags & 0x80; + } + void SetHasDoneSpeedTrapTutorial() { + SpecialFlags |= 0x80; + } + bool HasDoneTollBoothTutorial() { + return SpecialFlags & 0x100; + } + void SetHasDoneTollBoothTutorial() { + SpecialFlags |= 0x100; + } + bool HasDonePursuitTutorial() { + return SpecialFlags & 0x200; + } + void SetHasDonePursuitTutorial() { + SpecialFlags |= 0x200; + } + bool HasDoneBountyTutorial() { + return SpecialFlags & 0x400; + } + void SetHasDoneBountyTutorial() { + SpecialFlags |= 0x400; + } + bool HasDoneMapLoadigTip() { + return SpecialFlags & 0x80000; + } + void SetHasDoneMapLoadigTip() { + SpecialFlags |= 0x80000; + } + bool HasBeatenCareer() { + return SpecialFlags & 0x4000; + } + void SetHasBeatenCareer() { + SpecialFlags |= 0x4000; + } + bool HasBeenBustedOnce() { + return SpecialFlags & 0x1000; + } + void SetBeenBustedOnce() { + SpecialFlags |= 0x1000; + } + int GetCash() { + return CurrentCash; + } + void AddCash(int amount) { + CurrentCash += amount; + } + SMSMessage *GetSMSMessage(unsigned int index); + unsigned short GetSMSSortOrder(); + void SpendCash(int amount); + void SetPlayerHasBeatenTheGame(); + int GetSaveBufferSize(bool bExcludeGameplay); + void ResumeCareer(); + void StartNewCareer(bool bEnterGameplay); + char *SaveUnlockData(void *save_to, void *maxptr); + char *LoadUnlockData(void *load_from, void *maxptr); + void Default(); + void TryAwardDemoMarker(); + void GenerateCaseFileName(); + char *SaveToBuffer(void *buffer, void *maxbuf); + char *LoadFromBuffer(void *buffer, void *maxbuf); + char *SaveRaceData(void *save_to, void *maxptr); + char *SaveGameplayData(void *save_to, void *maxptr); + char *LoadRaceData(void *load_from, void *maxptr); + char *LoadGameplayData(void *load_from, void *maxptr); + void SetCurrentCar(unsigned int car) { CurrentCar = car; } + bool HasBeenAwardedDemoMarker(); + void SetAwardedDemoMarker(); + + bool HasBeenAwardedBKReward() { + return SpecialFlags & 0x2000; + } + + public: uint32 CurrentCar; // offset 0x0, size 0x4 uint32 SpecialFlags; // offset 0x4, size 0x4 uint8 CurrentBin; // offset 0x8, size 0x1 @@ -182,6 +383,8 @@ struct JukeboxEntry { // total size: 0x9CF4 class UserProfile { public: + UserProfile(); + ~UserProfile(); void SetProfileName(const char *pName, bool isP1); const char *GetProfileName(); bool IsProfileNamed(); @@ -206,6 +409,7 @@ class UserProfile { CareerSettings *GetCareer() { return &TheCareerSettings; } + HighScoresDatabase *GetHighScores() { return &HighScores; } private: char m_aProfileName[32]; // offset 0x0, size 0x20 @@ -221,10 +425,17 @@ class UserProfile { // total size: 0x24 struct RaceSettings { + RaceSettings() { EventHash = 0; Default(); } + void Default(); + unsigned int GetSelectedCar(int player_num) { return SelectedCar[player_num]; } + void SetSelectedCar(unsigned int car, int player_num) { + SelectedCar[player_num] = car; + } + uint32 EventHash; // offset 0x0, size 0x4 uint8 NumLaps; // offset 0x4, size 0x1 uint8 TrackDirection; // offset 0x5, size 0x1 @@ -244,6 +455,8 @@ struct RaceSettings { // total size: 0x14C struct FEKeyboardSettings { + FEKeyboardSettings(); + int AcceptCallbackHash; // offset 0x0, size 0x4 int DeclineCallbackHash; // offset 0x4, size 0x4 int DefaultTextHash; // offset 0x8, size 0x4 @@ -253,23 +466,43 @@ struct FEKeyboardSettings { char Title[156]; // offset 0xB0, size 0x9C }; +// total size: 0x6 +struct GameCompletionStats { + GameCompletionStats(); + + unsigned char m_nOverall; // offset 0x0, size 0x1 + unsigned char m_nCareer; // offset 0x1, size 0x1 + unsigned char m_nRapSheetRankings; // offset 0x2, size 0x1 + unsigned char m_nChallenge; // offset 0x3, size 0x1 + unsigned char m_nTotalChallengeRaces; // offset 0x4, size 0x1 + unsigned char m_nCompletedChallengeRaces;// offset 0x5, size 0x1 +}; + // total size: 0xA28 class cFrontendDatabase { public: RaceSettings *GetQuickRaceSettings(GRace::Type type); + void DefaultRaceSettings(); + void FillCustomRace(GRaceCustom *parms, RaceSettings *race); PlayerSettings *GetPlayerSettings(int player) { return &CurrentUserProfiles[0]->GetOptions()->ThePlayerSettings[player]; } FEPlayerCarDB *GetPlayerCarStable(int player) { - return &CurrentUserProfiles[player]->PlayersCarStable; + if (static_cast(player) <= 1) + return &CurrentUserProfiles[player]->PlayersCarStable; + return nullptr; } CareerSettings *GetCareerSettings() { return CurrentUserProfiles[0]->GetCareer(); } + bool IsDDay() { + return GetCareerSettings()->GetCurrentBin() >= 16; + } + bool IsSplitScreenMode() { return FEGameMode & 4; } @@ -278,14 +511,171 @@ class cFrontendDatabase { return FEGameMode & 1; } + bool IsChallengeMode() { + return FEGameMode & 2; + } + + bool IsQuickRaceMode() { + return FEGameMode & 4; + } + + bool IsOnlineMode() { + return FEGameMode & 8; + } + + bool IsOptionsMode() { + return FEGameMode & 16; + } + + bool IsCustomizeMode() { + return FEGameMode & 32; + } + + bool IsLANMode() { + return FEGameMode & 64; + } + + bool IsProfileManagerMode() { + return FEGameMode & 128; + } + + bool IsCareerManagerMode() { + return FEGameMode & 256; + } + + bool IsRapSheetMode() { + return FEGameMode & 512; + } + + bool IsModeSelectMode() { + return FEGameMode & 1024; + } + + bool IsCarLotMode() { + return FEGameMode & 32768; + } + + bool IsSafehouseMode() { + return FEGameMode & 65536; + } + + bool IsPostRivalMode() { + return FEGameMode & 131072; + } + + bool IsBeatGameMode() { + return FEGameMode & 262144; + } + + void SetGameMode(eFEGameModes mode) { + FEGameMode = FEGameMode | static_cast(mode); + } + + void ClearGameMode(eFEGameModes mode) { + FEGameMode = FEGameMode & ~static_cast(mode); + } + + void ResetGameMode() { + FEGameMode = 0; + } + + unsigned int GetGameMode() { + return FEGameMode; + } + + bool IsOptionsDirty() { + return bIsOptionsDirty; + } + + void SetOptionsDirty(bool dirty) { + bIsOptionsDirty = dirty; + } + + void SetPlayersJoystickPort(int player, signed char port); + + signed char GetPlayersJoystickPort(int player) { + return PlayerJoyports[player]; + } + + UserProfile* GetMultiplayerProfile(int player) { + return CurrentUserProfiles[player]; + } + UserProfile* GetUserProfile(int player) { return CurrentUserProfiles[player]; } + void CreateMultiplayerProfile(int player); + void DeleteMultiplayerProfile(int player); + + OptionsSettings* GetOptionsSettings() { + return CurrentUserProfiles[0]->GetOptions(); + } + + AudioSettings* GetAudioSettings() { + return &CurrentUserProfiles[0]->GetOptions()->TheAudioSettings; + } + + VideoSettings* GetVideoSettings() { + return &CurrentUserProfiles[0]->GetOptions()->TheVideoSettings; + } + + GameplaySettings* GetGameplaySettings() { + return &CurrentUserProfiles[0]->GetOptions()->TheGameplaySettings; + } + + GameCompletionStats GetGameCompletionStats(); + + unsigned int GetChallengeHeaderHash(unsigned int index); + unsigned int GetChallengeDescHash(unsigned int index); + unsigned int GetBountyIconHash(unsigned int index); + unsigned int GetBountyHeaderHash(unsigned int index); + unsigned int GetBountyDescHash(unsigned int index); + unsigned int GetMilestoneDescHash(unsigned int tag); + unsigned int GetMilestoneHeaderHash(unsigned int tag); + unsigned int GetMilestoneIconHash(unsigned int typeKey, bool active); + void SetMilestoneDescriptionString(char *buf, int value, float number, float goal, bool isTime) const; + bool IsMilestoneTimeFormat(int typeKey) const; + unsigned int GetRaceIconHash(GRace::Type type); + unsigned int GetRaceNameHash(GRace::Type type); + + void BuildCurrentRideForPlayer(int player, class RideInfo* ride); + + bool IsFinalEpicChase(); + unsigned int GetUserProfileSaveSize(bool bExcludeGameplay); + void SaveUserProfileToBuffer(void* buffer, int size); + void NotifyExitRaceToFrontend(eExitRacePlaces from_where); + void AllocBackupDB(bool b); + void DefaultProfile(); + unsigned int GetDefaultCar(); + bool LoadUserProfileFromBuffer(void* buffer, int size, int player); + void RestoreFromBackupDB(); + void DeallocBackupDB(); + void RefreshCurrentRide(); + void NotifyDeleteCar(unsigned int handle); + void BackupCarStable(); + bool IsCarStableDirty(); + bool IsDirty(); + + bool MatchesGameMode(unsigned int mode) { + return FEGameMode & mode; + } + + static void *operator new(unsigned int size, unsigned int alloc_params) { +#ifdef MILESTONE_OPT + return bMalloc(size, __FILE__, __LINE__, alloc_params); +#else + return bMalloc(size, alloc_params); +#endif + } + + cFrontendDatabase(); + void Default(); + void GetRandomRaceOptions(RaceSettings *race, GRace::Type type); + unsigned int GetSafehouseIconHash(const char *name); + unsigned char iNumPlayers; // offset 0x0, size 0x1 bool bComingFromBoot; // offset 0x4, size 0x1 bool bSavedProfileForMP; // offset 0x8, size 0x1 bool bProfileLoaded; // offset 0xC, size 0x1 bool bIsOptionsDirty; // offset 0x10, size 0x1 -#ifndef EA_BUILD_A124 bool bAutoSaveOverwriteConfirmed; // offset 0x14, size 0x1 -#endif unsigned int iDefaultStableHash; // offset 0x18, size 0x4 signed char PlayerJoyports[2]; // offset 0x1C, size 0x2 UserProfile *CurrentUserProfiles[2]; // offset 0x20, size 0x8 diff --git a/src/Speed/Indep/Src/Frontend/Database/RaceDB.cpp b/src/Speed/Indep/Src/Frontend/Database/RaceDB.cpp index e69de29bb..680ae3b4d 100644 --- a/src/Speed/Indep/Src/Frontend/Database/RaceDB.cpp +++ b/src/Speed/Indep/Src/Frontend/Database/RaceDB.cpp @@ -0,0 +1,383 @@ +#include "Speed/Indep/Src/Frontend/Database/RaceDB.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +#include "types.h" + +#include + +void FixDot(char *buf, int size) { + for (int i = 0; i < size; i++) { + if (buf[i] == '.') { + buf[i] = '_'; + } + } +} + +unsigned int GetFECarNameHashFromFEKey(unsigned int fekey); + +int CareerPursuitScores::GetValue(ePursuitDetailTypes type) const { + int val; + switch (static_cast(type)) { + case 8: { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + val = stable->GetTotalNumInfractions(true) + stable->GetTotalNumInfractions(false); + break; + } + case 9: { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + val = stable->GetTotalBounty(); + break; + } + default: + val = Value[type]; + break; + } + return val; +} + +void CareerPursuitScores::IncValue(ePursuitDetailTypes type, int amount) { + if (type == 0) { + Value[0] += amount; + } else { + Value[type] += amount; + } +} + +void TopEvadedPursuitDetail::GeneratePursuitID() { + char *c = PursuitName + 3; + + PursuitName[0] = 'M'; + PursuitName[1] = 'W'; + PursuitName[2] = '-'; + + { + int i = 0; + + do { + if ((i & 1) != 0) { + int r = bRandom(0x1A); + *c = static_cast< char >(r + 'A'); + } else { + int r = bRandom(10); + *c = static_cast< char >(r + '0'); + } + i++; + c++; + } while (i <= 10); + } + + PursuitName[11] = '\0'; +} + +void HighScoresDatabase::Default() { + memset(this, 0, sizeof(*this)); +} + +int HighScoresDatabase::CalcPursuitRank(ePursuitDetailTypes type, bool career_rank) { + const char *attrib_name; + Attrib::Key key; + int player_value; + int rank; + + if (type == static_cast< ePursuitDetailTypes >(4)) { + if (!career_rank) { + attrib_name = "tire_spikes_dodged_in_pursuit"; + } else { + attrib_name = "tire_spikes_dodged"; + } + } else if (type < static_cast< ePursuitDetailTypes >(5)) { + if (type == static_cast< ePursuitDetailTypes >(1)) { + if (!career_rank) { + attrib_name = "cops_involved_in_pursuit"; + } else { + attrib_name = "cops_involved"; + } + } else if (type < static_cast< ePursuitDetailTypes >(2)) { + if (type != static_cast< ePursuitDetailTypes >(0)) { + key = 0; + goto GotAttribKey; + } + if (!career_rank) { + attrib_name = "pursuit_length_in_pursuit"; + } else { + attrib_name = "pursuit_length"; + } + } else if (type == static_cast< ePursuitDetailTypes >(2)) { + if (!career_rank) { + attrib_name = "cops_damaged_in_pursuit"; + } else { + attrib_name = "cops_damaged"; + } + } else { + if (type != static_cast< ePursuitDetailTypes >(3)) { + key = 0; + goto GotAttribKey; + } + if (!career_rank) { + attrib_name = "cops_destroyed_in_pursuit"; + } else { + attrib_name = "cops_destroyed"; + } + } + } else if (type == static_cast< ePursuitDetailTypes >(7)) { + if (!career_rank) { + attrib_name = "cost_to_state_in_pursuit"; + } else { + attrib_name = "cost_to_state"; + } + } else if (type < static_cast< ePursuitDetailTypes >(8)) { + if (type == static_cast< ePursuitDetailTypes >(5)) { + if (!career_rank) { + attrib_name = "roadblocks_dodged_in_pursuit"; + } else { + attrib_name = "roadblocks_dodged"; + } + } else { + if (type != static_cast< ePursuitDetailTypes >(6)) { + key = 0; + goto GotAttribKey; + } + if (!career_rank) { + attrib_name = "helis_involved_in_pursuit"; + } else { + attrib_name = "helis_involved"; + } + } + } else if (type == static_cast< ePursuitDetailTypes >(8)) { + if (!career_rank) { + attrib_name = "total_infractions_in_pursuit"; + } else { + attrib_name = "total_infractions"; + } + } else { + if (type != static_cast< ePursuitDetailTypes >(9)) { + key = 0; + goto GotAttribKey; + } + if (!career_rank) { + attrib_name = "bounty_in_pursuit"; + } else { + attrib_name = "bounty"; + } + } + + key = Attrib::StringToKey(attrib_name); + +GotAttribKey: + Attrib::Gen::frontend rankingsData(Attrib::FindCollection(Attrib::Gen::frontend::ClassKey(), key), 0, nullptr); + rank = 0x10; + + if (rankingsData.IsValid()) { + if (rankingsData.Num_RapSheetRanks() == 15) { + if (career_rank) { + player_value = CareerPursuitDetails.GetValue(type); + } else { + player_value = BestPursuitRankings[type].Value; + } + + for (int i = 0; i < static_cast< int >(rankingsData.Num_RapSheetRanks()); i++) { + int rank_value; + + if (type == 0) { + Timer t; + float rank_time = rankingsData.RapSheetRanks(static_cast< unsigned int >(i)); + t.SetTime(rank_time); + rank_value = t.GetPackedTime(); + } else { + rank_value = static_cast< int >(rankingsData.RapSheetRanks(static_cast< unsigned int >(i))); + } + + if (player_value >= rank_value) { + rank = i + 1; + break; + } + } + } + } + + return rank; +} + +unsigned int HighScoresDatabase::GetPreviouslyPursuedCarNameHash() const { + return GetFECarNameHashFromFEKey(PreviouslyPursuedCarFEKey); +} + +void HighScoresDatabase::GetCareerCST(RAP_CTS_ITEM item, int &quantity, unsigned int &value) const { + switch (item) { + case RAP_CTS_HELI_SPAWN: + quantity = GetCareerPursuitScore(static_cast< ePursuitDetailTypes >(6)); + value = quantity * 2000; + return; + case RAP_CTS_SUPPORT_VEHICLE_DEPLOYED: + quantity = CostToStateDetails.mNumSupportVehiclesDeployed; + value = quantity * 0x1C2; + return; + case RAP_CTS_COP_CAR_DEPLOYED: + quantity = CostToStateDetails.mNumCopCarsDeployed; + value = quantity * 0xFA; + return; + case RAP_CTS_COP_DESTROYED: + quantity = GetCareerPursuitScore(static_cast< ePursuitDetailTypes >(3)); + value = quantity * 5000; + return; + case RAP_CTS_COP_DAMAGED: + quantity = GetCareerPursuitScore(static_cast< ePursuitDetailTypes >(2)); + value = quantity * 0xFA; + return; + case RAP_CTS_ROADBLOCK_DEPLOYED: + quantity = CostToStateDetails.mNumRoadblocksDeployed; + value = quantity * 500; + return; + case RAP_CTS_SPIKE_STRIP_DEPLOYED: + quantity = CostToStateDetails.mNumSpikeStripsDeployed; + value = quantity * 0xFA; + return; + case RAP_CTS_HELI_SPIKE_STRIP_DEPLOYED: + quantity = CostToStateDetails.mNumHeliSpikeStripsDeployed; + value = quantity * 0xE1; + return; + case RAP_CTS_TRAFFIC_CAR_HIT: + quantity = CostToStateDetails.mNumTrafficCarsHit; + value = quantity * 500; + return; + case RAP_CTS_PROPERTY_DAMAGE: + quantity = CostToStateDetails.mNumPropertiesDamaged; + value = CostToStateDetails.mPropertyDamageValue; + return; + } +} + +void HighScoresDatabase::CommitHighScoresPauseQuit() { + ++TotalLosses; +} + +void HighScoresDatabase::CommitPursuitInfo(IPursuit *iPursuit, unsigned int car_FEKey, int bounty, unsigned int num_infractions) { + int cost_to_state = iPursuit->CalcTotalCostToState(); + + PreviouslyPursuedCarFEKey = car_FEKey; + + if (iPursuit->IsPerpBusted()) { + return; + } + + CostToStateDetails.mNumCopCarsDeployed += iPursuit->GetNumCopCarsDeployed(); + CostToStateDetails.mNumHeliSpikeStripsDeployed += iPursuit->GetNumHeliSpikeStripDeployed(); + CostToStateDetails.mNumPropertiesDamaged += iPursuit->GetNumPropertyDamaged(); + CostToStateDetails.mNumRoadblocksDeployed += iPursuit->GetNumRoadblocksDeployed(); + CostToStateDetails.mNumSpikeStripsDeployed += iPursuit->GetNumSpikeStripsDeployed(); + CostToStateDetails.mNumSupportVehiclesDeployed += iPursuit->GetNumSupportVehiclesDeployed(); + CostToStateDetails.mNumTrafficCarsHit += iPursuit->GetNumTrafficCarsHit(); + CostToStateDetails.mPropertyDamageValue += iPursuit->GetValueOfPropertyDamaged(); + + Timer pursuit_length; + pursuit_length.SetTime(iPursuit->GetPursuitDuration()); + CareerPursuitDetails.IncValue(static_cast< ePursuitDetailTypes >(0), pursuit_length.GetPackedTime()); + CareerPursuitDetails.IncValue(static_cast< ePursuitDetailTypes >(1), iPursuit->GetTotalNumCopsInvolved()); + CareerPursuitDetails.IncValue(static_cast< ePursuitDetailTypes >(2), iPursuit->GetNumCopsDamaged()); + CareerPursuitDetails.IncValue(static_cast< ePursuitDetailTypes >(3), iPursuit->GetNumCopsDestroyed()); + CareerPursuitDetails.IncValue(static_cast< ePursuitDetailTypes >(4), iPursuit->GetNumSpikeStripsDodged()); + CareerPursuitDetails.IncValue(static_cast< ePursuitDetailTypes >(5), iPursuit->GetNumRoadblocksDodged()); + CareerPursuitDetails.IncValue(static_cast< ePursuitDetailTypes >(6), iPursuit->GetNumHeliSpawns()); + CareerPursuitDetails.IncValue(static_cast< ePursuitDetailTypes >(7), cost_to_state); + + if (BestPursuitRankings[0].Value < pursuit_length.GetPackedTime()) { + Timer best_pursuit_length; + best_pursuit_length.SetTime(iPursuit->GetPursuitDuration()); + BestPursuitRankings[0].CarFEKey = car_FEKey; + BestPursuitRankings[0].Value = best_pursuit_length.GetPackedTime(); + } + + if (BestPursuitRankings[1].Value < iPursuit->GetTotalNumCopsInvolved()) { + BestPursuitRankings[1].Value = iPursuit->GetTotalNumCopsInvolved(); + BestPursuitRankings[1].CarFEKey = car_FEKey; + } + + if (BestPursuitRankings[2].Value < iPursuit->GetNumCopsDamaged()) { + BestPursuitRankings[2].Value = iPursuit->GetNumCopsDamaged(); + BestPursuitRankings[2].CarFEKey = car_FEKey; + } + + if (BestPursuitRankings[3].Value < iPursuit->GetNumCopsDestroyed()) { + BestPursuitRankings[3].Value = iPursuit->GetNumCopsDestroyed(); + BestPursuitRankings[3].CarFEKey = car_FEKey; + } + + if (BestPursuitRankings[4].Value < iPursuit->GetNumSpikeStripsDodged()) { + BestPursuitRankings[4].Value = iPursuit->GetNumSpikeStripsDodged(); + BestPursuitRankings[4].CarFEKey = car_FEKey; + } + + if (BestPursuitRankings[5].Value < iPursuit->GetNumRoadblocksDodged()) { + BestPursuitRankings[5].Value = iPursuit->GetNumRoadblocksDodged(); + BestPursuitRankings[5].CarFEKey = car_FEKey; + } + + if (BestPursuitRankings[6].Value < iPursuit->GetNumHeliSpawns()) { + BestPursuitRankings[6].Value = iPursuit->GetNumHeliSpawns(); + BestPursuitRankings[6].CarFEKey = car_FEKey; + } + + if (BestPursuitRankings[8].Value < static_cast< int >(num_infractions)) { + BestPursuitRankings[8].Value = num_infractions; + BestPursuitRankings[8].CarFEKey = car_FEKey; + } + + if (BestPursuitRankings[7].Value < cost_to_state) { + BestPursuitRankings[7].Value = cost_to_state; + BestPursuitRankings[7].CarFEKey = car_FEKey; + } + + if (BestPursuitRankings[9].Value < bounty) { + BestPursuitRankings[9].Value = bounty; + BestPursuitRankings[9].CarFEKey = car_FEKey; + } + + int pos = 5; + if (TopEvadedPursuitScores[0].Bounty < bounty) { + pos = 0; + } else { + for (int i = 1; i < 5; i++) { + if (bounty > TopEvadedPursuitScores[i].Bounty) { + pos = i; + break; + } + } + } + + if (pos < 4) { + for (int i = 4; i > pos; i--) { + TopEvadedPursuitScores[i] = TopEvadedPursuitScores[i - 1]; + } + } + + if (pos != 5) { + TopEvadedPursuitDetail &detail = TopEvadedPursuitScores[pos]; + Timer detail_length; + + detail.GeneratePursuitID(); + detail.CarFEKey = car_FEKey; + detail.Bounty = bounty; + detail_length.SetTime(iPursuit->GetPursuitDuration()); + detail.Length = detail_length.GetPackedTime(); + detail.NumCops = iPursuit->GetTotalNumCopsInvolved(); + detail.NumCopsDamaged = iPursuit->GetNumCopsDamaged(); + detail.NumCopsDestroyed = iPursuit->GetNumCopsDestroyed(); + detail.NumRoadblocksDodged = iPursuit->GetNumRoadblocksDodged(); + detail.NumSpikeStripsDodged = iPursuit->GetNumSpikeStripsDodged(); + detail.TotalCostToState = cost_to_state; + detail.NumInfractions = num_infractions; + detail.NumHelicopters = iPursuit->GetNumHeliSpawns(); + } +} + +void UserProfile::CommitHighScoresPauseQuit() { + HighScores.CommitHighScoresPauseQuit(); +} + +void UserProfile::CommitPursuitInfo(IPursuit *iPursuit, unsigned int car_FEKey, unsigned int bounty, unsigned int num_infractions) { + HighScores.CommitPursuitInfo(iPursuit, car_FEKey, bounty, num_infractions); +} diff --git a/src/Speed/Indep/Src/Frontend/Database/RaceDB.hpp b/src/Speed/Indep/Src/Frontend/Database/RaceDB.hpp index 745fa686d..814ca5ac9 100644 --- a/src/Speed/Indep/Src/Frontend/Database/RaceDB.hpp +++ b/src/Speed/Indep/Src/Frontend/Database/RaceDB.hpp @@ -5,8 +5,17 @@ #pragma once #endif +enum ePursuitDetailTypes { + ePDT_CostToState = 0, + ePDT_Bounty = 1, + ePDT_Infractions = 2, + ePDT_SpeedingTotalFine = 3, +}; + #include "Speed/Indep/Src/Misc/Timer.hpp" +class IPursuit; + // total size: 0x8 struct TrackHighScore { short TrackNumber; // offset 0x0, size 0x2 @@ -23,6 +32,8 @@ struct RaceTypeHighScores { // total size: 0x38 struct TopEvadedPursuitDetail { + void GeneratePursuitID(); + char PursuitName[12]; // offset 0x0, size 0xC unsigned int CarFEKey; // offset 0xC, size 0x4 int Bounty; // offset 0x10, size 0x4 @@ -39,6 +50,9 @@ struct TopEvadedPursuitDetail { // total size: 0x20 struct CareerPursuitScores { + int GetValue(ePursuitDetailTypes type) const; + void IncValue(ePursuitDetailTypes type, int amount); + int Value[8]; // offset 0x0, size 0x20 }; @@ -60,9 +74,19 @@ struct CostToStateScores { int mNumPropertiesDamaged; // offset 0x1C, size 0x4 }; +enum RAP_CTS_ITEM { RAP_CTS_HELI_SPAWN=0,RAP_CTS_SUPPORT_VEHICLE_DEPLOYED=1,RAP_CTS_COP_CAR_DEPLOYED=2,RAP_CTS_COP_DESTROYED=3,RAP_CTS_COP_DAMAGED=4,RAP_CTS_ROADBLOCK_DEPLOYED=5,RAP_CTS_SPIKE_STRIP_DEPLOYED=6,RAP_CTS_HELI_SPIKE_STRIP_DEPLOYED=7,RAP_CTS_TRAFFIC_CAR_HIT=8,RAP_CTS_PROPERTY_DAMAGE=9 }; // total size: 0xBD8 class HighScoresDatabase { public: + void Default(); + int GetCareerPursuitScore(ePursuitDetailTypes type) const { return CareerPursuitDetails.GetValue(type); } + const TopEvadedPursuitDetail &GetTopEvadedPursuitScores(unsigned short index) const { return TopEvadedPursuitScores[index]; } + const PursuitScore &GetBestPursuitScore(ePursuitDetailTypes type) const { return BestPursuitRankings[type]; } + int CalcPursuitRank(ePursuitDetailTypes type, bool career_rank); + unsigned int GetPreviouslyPursuedCarNameHash() const; + void GetCareerCST(RAP_CTS_ITEM item, int &quantity, unsigned int &value) const; + void CommitHighScoresPauseQuit(); + void CommitPursuitInfo(IPursuit *iPursuit, unsigned int car_FEKey, int bounty, unsigned int num_infractions); TrackHighScore TrackHighScoreTable[320]; // offset 0x0, size 0xA00 float TotalOdometer; // offset 0xA00, size 0x4 int TotalStarts; // offset 0xA04, size 0x4 @@ -80,6 +104,19 @@ class HighScoresDatabase { // total size: 0xC0 struct FinishedRaceStatsEntry { + FinishedRaceStatsEntry() { + RaceTime.ResetLow(); + BestLapTime.ResetLow(); + for (int i = 0; i < 11; i++) { + LapTimes[i].ResetLow(); + } + for (int i = 0; i < 11; i++) { + LapRunningTimes[i].ResetLow(); + } + ZeroToSixtyTime.ResetLow(); + QuarterMileTime.ResetLow(); + } + int FinishPosition; // offset 0x0, size 0x4 int DriverNumber; // offset 0x4, size 0x4 int FinishReason; // offset 0x8, size 0x4 @@ -105,8 +142,11 @@ struct FinishedRaceStatsEntry { // total size: 0x604 struct cFinishedRaceStats { + inline cFinishedRaceStats() {} + FinishedRaceStatsEntry RaceStats[8]; // offset 0x0, size 0x600 int NumStats; // offset 0x600, size 0x4 }; + #endif diff --git a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.cpp b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.cpp index e69de29bb..cb6df294d 100644 --- a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.cpp +++ b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.cpp @@ -0,0 +1,1438 @@ +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/FEng/FEList.h" +#include "Speed/Indep/Src/Main/AttribSupport.h" +#include "Speed/Indep/Src/Misc/EasterEggs.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/fecooling.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/infractions.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/presetride.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pursuitlevels.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/ecar.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/camerainfo.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Src/Ecstasy/EcstasyE.hpp" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/Src/Gameplay/GManager.h" + +#include "types.h" + +#include + +extern int g_MaximumMaximumTimesBusted; +extern float g_fImpoundPercentageOfOriginalCost; +extern TextureInfo *GetTextureInfo(unsigned int hash, int, int); +extern bool DoesStringExist(unsigned int hash); + +struct PresetCar { + unsigned int Pad0[2]; + char CarTypeName[32]; + char PresetName[32]; + unsigned long long FEKey; + unsigned long long VehicleKey; + unsigned int FilterBits; + int PhysicsLevel; + int PartNameHashes[139]; +}; + +class CarPartDatabase { + public: + CarType GetCarType(unsigned int key); + struct CarPart *GetCarPartByIndex(int index); + int GetPartIndex(struct CarPart *part); + struct CarPart *NewGetCarPart(CarType cartype, int slot, unsigned int part_name_hash, struct CarPart *fallback, int index); + struct CarPart *NewGetFirstCarPart(CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upg_level); + struct CarPart *NewGetNextCarPart(struct CarPart *car_part, CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upg_level); +}; + +extern CarPartDatabase CarPartDB; +PresetCar *FindFEPresetCar(unsigned int key); +int GetNumPresetCars(); +PresetCar *GetPresetCarAt(int index); +extern bool ShowAllCarsInFE; +extern bool ShowAllPresetsInFE; +unsigned int bStringHashUpper(const char *text); + +namespace Physics { + +namespace Upgrades { + +bool ApplyPreset(Attrib::Gen::pvehicle &vehicle, const Attrib::Gen::presetride &preset); +void Clear(Attrib::Gen::pvehicle &vehicle); +void Flush(); +bool SetPackage(Attrib::Gen::pvehicle &vehicle, const Package &package); +void GetPackage(const Attrib::Gen::pvehicle &vehicle, Package &package); + +} // namespace Upgrades + +} // namespace Physics + +namespace { + +unsigned short GetInfractionValue(const FEInfractionsData &data, GInfractionManager::InfractionType type) { + return data.GetValue(type); +} + +unsigned int GetInfractionCount(const FEInfractionsData &data) { + return data.NumInfractions(); +} + +void ClearInfractions(FEInfractionsData &data) { + bMemSet(&data, 0, sizeof(data)); +} + +void AddInfractions(FEInfractionsData &dst, const FEInfractionsData &src) { + dst += src; +} + +const unsigned int kHeatAdjustCollectionKey = 0xEEC2271A; + +} // namespace + +FEPlayerCarDB::FEPlayerCarDB() { + Default(); +} + +FEPlayerCarDB::~FEPlayerCarDB() {} + +FECarRecord::FECarRecord() { + Handle = 0xFFFFFFFF; + CareerHandle = 0xFF; + FilterBits = 0; + FEKey = 0; + VehicleKey = 0; + Customization = 0xFF; +} + +FECarRecord &FECarRecord::operator=(const FECarRecord &other_record) { + FEKey = other_record.FEKey; + VehicleKey = other_record.VehicleKey; + FilterBits = other_record.FilterBits; + return *this; +} + +void FECarRecord::Default() { + Customization = 0xFF; + FilterBits = 0; + CareerHandle = 0xFF; + if (Handle == 0xFFFFFFFF) { + return; + } + + Attrib::Gen::pvehicle vehicle(VehicleKey, 0, 0); + Attrib::Gen::frontend frontend(FEKey, 0, 0); + const unsigned char *frontendLayout = reinterpret_cast< const unsigned char * >(frontend.GetLayoutPointer()); + int region = *reinterpret_cast< const int * >(frontendLayout + 0x38); + int isCustomizable = *reinterpret_cast< const int * >(frontendLayout + 0x58); + + if (region == 1) { + FilterBits |= 0x20000; + } + else if (region == 0) { + FilterBits |= 0x10000; + } + else if (region == 2) { + FilterBits |= 0x40000; + } + else if (region == 3) { + FilterBits |= 0x80000; + } + + if (region == 3 || isCustomizable == 0) { + FilterBits |= 0x20; + } + else { + FilterBits |= 1; + } +} + +bool FECarRecord::MatchesFilter(int theFilter) { + int theList = theFilter & 0xFFFF; + bool regionCompare = true; + int myList = FilterBits & 0xFFFF; + int myRegion = FilterBits & theFilter; + if ((myRegion & static_cast(0xFFFF0000)) == 0) { + regionCompare = false; + } + bool listCompare = true; + if ((theList & myList) == 0) { + listCompare = false; + } + if (!regionCompare) return false; + if (!listCompare) return false; + return true; +} + +unsigned int FECarRecord::GetCost() { + Attrib::Gen::frontend frontend(FEKey, 0, 0); + + return frontend.Cost(); +} + +const char *FECarRecord::GetDebugName() { + Attrib::Gen::pvehicle vehicle(VehicleKey, 0, 0); + const unsigned char *vehicleLayout = reinterpret_cast< const unsigned char * >(vehicle.GetLayoutPointer()); + + return *reinterpret_cast< const char * const * >(vehicleLayout + 0x24); +} + +unsigned int FECarRecord::GetNameHash() { + const char *manu = GetManufacturerName(); + if (bStrCmp(manu, "")) { + char buf[128]; + Attrib::Gen::frontend frontend(FEKey, 0, nullptr); + FEngSNPrintf(buf, 0x80, "CARNAME_%s_%s", manu, frontend.CollectionName()); + unsigned int hash = FEHashUpper(buf); + if (DoesStringExist(hash)) { + return hash; + } + } + return 0x9BB9CCC3; +} + +unsigned int FECarRecord::GetReleaseFromImpoundCost() { + return static_cast< unsigned int >(static_cast< float >(GetCost()) * g_fImpoundPercentageOfOriginalCost); +} + +CarType FECarRecord::GetType() { + Attrib::Gen::pvehicle vehicle(VehicleKey, 0, 0); + + return CarPartDB.GetCarType(vehicle.MODEL().GetHash32()); +} + +unsigned int FECarRecord::GetLogoHash() { + const char *manu = GetManufacturerName(); + if (bStrCmp(manu, "")) { + char buf[128]; + Attrib::Gen::frontend frontend(FEKey, 0, 0); + FEngSNPrintf(buf, 0x80, "SECONDARY_LOGO_%s", frontend.CollectionName()); + unsigned int texHash = FEHashUpper(buf); + if (GetTextureInfo(texHash, 0, 0)) { + return texHash; + } + } + return FEHashUpper("GENERIC_LOGO_256"); +} + +unsigned int FECarRecord::GetManuLogoHash() { + const char *manu = GetManufacturerName(); + if (bStrCmp(manu, "")) { + char buf[128]; + FEngSNPrintf(buf, 0x80, "CARSELECT_MANUFACTURER_%s", manu); + unsigned int texHash = FEHashUpper(buf); + if (GetTextureInfo(texHash, 0, 0)) { + return texHash; + } + } + return FEHashUpper("GENERIC_LOGO_128"); +} + +const char *FECarRecord::GetManufacturerName() { + Attrib::Gen::frontend fe(FEKey, 0, 0); + unsigned char Manufacturer = static_cast(fe.manufacturer()); + switch (Manufacturer) { + case 0: + return ""; + case 1: + return "BMW"; + case 2: + return "FORD"; + case 3: + return "SUBARU"; + case 4: + return "PORSCHE"; + case 5: + return "AUDI"; + case 6: + return "MAZDA"; + case 7: + return "GENERAL_MOTORS"; + case 8: + return "DODGE"; + case 9: + return "TOYOTA"; + case 10: + return "MITSUBISHI"; + case 11: + return "MCLAREN"; + case 12: + return "MERCEDES"; + case 13: + return "NISSAN"; + case 14: + return "LOTUS"; + case 15: + return "LAMBORGHINI"; + case 16: + return "RENAULT"; + case 17: + return "LEXUS"; + case 18: + return "PONTIAC"; + case 19: + return "CHEVROLET"; + case 20: + return "VAUXHALL"; + case 21: + return "ASTONMARTIN"; + case 22: + return "VOLKSWAGEN"; + case 23: + return "FIAT"; + case 24: + return "CADILLAC"; + case 25: + return "CORVETTE"; + default: + return ""; + } +} + +void FECustomizationRecord::Default() { + for (int i = 0; i < 139; i++) { + InstalledPartIndices[i] = -1; + } + + bMemSet(&InstalledPhysics, 0, 0x20); + for (int i = 0; i < 3; i++) { + bMemSet(&Tunings[i], 0, sizeof(Tunings[i])); + } + Preset = 0; + ActiveTuning = static_cast< Physics::eCustomTuningType >(0); +} + +FECustomizationRecord::FECustomizationRecord() { + bMemSet(&InstalledPhysics, 0, sizeof(InstalledPhysics)); + ActiveTuning = static_cast< Physics::eCustomTuningType >(0); + for (int i = 0; i < 3; i++) { + bMemSet(&Tunings[i], 0, sizeof(Tunings[i])); + } + Handle = 0xFF; + Default(); +} + +bool FECustomizationRecord::WriteRecordIntoPhysics(Attrib::Gen::pvehicle &attributes) const { + return Physics::Upgrades::SetPackage(attributes, InstalledPhysics); +} + +void FECustomizationRecord::WritePhysicsIntoRecord(const Attrib::Gen::pvehicle &attributes) { + Physics::Upgrades::GetPackage(attributes, InstalledPhysics); +} + +struct CarPart *FECustomizationRecord::GetInstalledPart(CarType cartype, int carslotid) const { + return CarPartDB.GetCarPartByIndex(InstalledPartIndices[carslotid]); +} + +void FECustomizationRecord::SetInstalledPart(int carslotid, struct CarPart *part) { + if (part != nullptr) { + InstalledPartIndices[carslotid] = static_cast< short >(CarPartDB.GetPartIndex(part)); + } else { + InstalledPartIndices[carslotid] = -1; + } +} + +void FECustomizationRecord::WriteRecordIntoRide(RideInfo *ride) const { + for (int i = 0; i <= 0x8A; i++) { + ride->SetPart(i, GetInstalledPart(ride->Type, i), true); + } +} + +void FECustomizationRecord::WriteRideIntoRecord(const RideInfo *ride) { + for (int i = 0; i <= 0x8A; i++) { + SetInstalledPart(i, ride->GetPart(i)); + } +} + +void FECustomizationRecord::BecomePreset(PresetCar *preset) { + Default(); + + CarType cartype = CarPartDB.GetCarType(bStringHash(preset->CarTypeName)); + for (int i = 0; i <= 0x8A; i++) { + unsigned int part_name_hash = preset->PartNameHashes[i]; + if (part_name_hash > 1) { + struct CarPart *part = CarPartDB.NewGetCarPart(cartype, i, part_name_hash, nullptr, -1); + InstalledPartIndices[i] = static_cast< short >(CarPartDB.GetPartIndex(part)); + } + } + + Preset = bStringHashUpper(preset->PresetName); +} + +void FEImpoundData::Default() { + Pad1 = 0; + MaxBusted = 3; + EvadeCount = 0; + TimesBusted = 0; + ImpoundedState = 0; + DaysBeforeRelease = 0; + Pad2 = 0; +} + +void FEImpoundData::BecomeImpounded(eImpoundReasons reason) { + ImpoundedState = reason; + TimesBusted = MaxBusted; + DaysBeforeRelease = 5; +} + +void FEImpoundData::NotifyPlayerPaidToRelease() { + TimesBusted = 0; + ImpoundedState = 0; + DaysBeforeRelease = 0; +} + +void FEImpoundData::NotifyPlayerUsedMarkerToRelease() { + NotifyPlayerPaidToRelease(); +} + +bool FEImpoundData::NotifyWin() { + bool impounded = ImpoundedState != 0; + if (impounded && ((DaysBeforeRelease == 0 || --DaysBeforeRelease == 0) && ImpoundedState != IMPOUND_RELEASED)) { + ImpoundedState = IMPOUND_RELEASED; + return true; + } + return false; +} + +bool FEImpoundData::NotifyBusted() { + char timesBusted = TimesBusted; + + EvadeCount = 0; + TimesBusted = timesBusted + 1; + return MaxBusted <= static_cast< unsigned char >(timesBusted + 1); +} + +bool FEImpoundData::NotifyEvade() { + bool impounded = ImpoundedState != 0; + + if (!impounded) { + EvadeCount = EvadeCount + 1; + if (EvadeCount > 2) { + EvadeCount = 0; + TimesBusted--; + } + if (TimesBusted < 0) { + TimesBusted = 0; + } + } + + return NotifyWin(); +} + +bool FEImpoundData::CanAddMaxBusted() { + if (static_cast< int >(MaxBusted) < g_MaximumMaximumTimesBusted && ImpoundedState == 0) { + return true; + } + return false; +} + +void FEImpoundData::AddMaxBusted() { + MaxBusted++; + if (static_cast< int >(MaxBusted) > g_MaximumMaximumTimesBusted) { + MaxBusted = static_cast< unsigned char >(g_MaximumMaximumTimesBusted); + } +} + +FEInfractionsData::FEInfractionsData(unsigned int infractions) { + bMemSet(this, 0, sizeof(*this)); + if ((infractions & 8) != 0) { + Assault++; + } + if ((infractions & 0x20) != 0) { + Damage++; + } + if ((infractions & 0x10) != 0) { + HitAndRun++; + } + if ((infractions & 0x80) != 0) { + OffRoad++; + } + if ((infractions & 2) != 0) { + Racing++; + } + if ((infractions & 4) != 0) { + Reckless++; + } + if ((infractions & 0x40) != 0) { + Resist++; + } + if ((infractions & 1) != 0) { + Speeding++; + } +} + +void FEInfractionsData::operator+=(const FEInfractionsData &rhs) { + Speeding += rhs.Speeding; + Racing += rhs.Racing; + Reckless += rhs.Reckless; + Assault += rhs.Assault; + HitAndRun += rhs.HitAndRun; + Damage += rhs.Damage; + Resist += rhs.Resist; + OffRoad += rhs.OffRoad; +} + +unsigned short FEInfractionsData::GetValue(GInfractionManager::InfractionType type) const { + if (type == GInfractionManager::kInfraction_Assault) { + return Assault; + } + if (type < GInfractionManager::kInfraction_HitAndRun) { + if (type == GInfractionManager::kInfraction_Racing) { + return Racing; + } + if (type < GInfractionManager::kInfraction_Reckless) { + if (type == GInfractionManager::kInfraction_Speeding) { + return Speeding; + } + } + else if (type == GInfractionManager::kInfraction_Reckless) { + return Reckless; + } + } + else { + if (type == GInfractionManager::kInfraction_Damage) { + return Damage; + } + if (type < GInfractionManager::kInfraction_Resist) { + if (type == GInfractionManager::kInfraction_HitAndRun) { + return HitAndRun; + } + } + else { + if (type == GInfractionManager::kInfraction_Resist) { + return Resist; + } + if (type == GInfractionManager::kInfraction_OffRoad) { + return OffRoad; + } + } + } + return 0; +} + +unsigned short FEInfractionsData::NumInfractions() const { + return Speeding + Racing + Reckless + Assault + HitAndRun + Damage + Resist + OffRoad; +} + +void FECareerRecord::Default() { + Handle = 0xFF; + TheImpoundData.Default(); + VehicleHeat = 1.0f; + Bounty = 0; + NumEvadedPursuits = 0; + NumBustedPursuits = 0; + FEInfractionsData unserved(0); + FEInfractionsData served(0); + + UnservedInfractions = unserved; + ServedInfractions = served; +} + +void FECareerRecord::SetVehicleHeat(float h) { + VehicleHeat = h; +} + +float FECareerRecord::GetVehicleHeat() { + return VehicleHeat; +} + +void FECareerRecord::AdjustHeatOnEventWin() { + Attrib::Gen::pursuitlevels pursuitLevels(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat = VehicleHeat * pursuitLevels.EventWinHeatAdjust(); +} + +void FECareerRecord::AdjustHeatOnEvadePursuit() { + Attrib::Gen::pursuitlevels pursuitLevels(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat = VehicleHeat * pursuitLevels.EvadeSuccessHeatAdjust(); +} + +void FECareerRecord::AdjustHeatOnDecalApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewDecal() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnPaintApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewPaint() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnVinylApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewVinyl() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnBodyKitApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewBodyKit() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnHoodApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewHood() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnRimApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewRim() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnRimPaintApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewRimPaint() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnRoofScoopApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewRoofScoop() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnSpoilerApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewSpoiler() * extraAdjust; +} + +void FECareerRecord::AdjustHeatOnWindowTintApplied(float extraAdjust) { + Attrib::Gen::fecooling cooling(kHeatAdjustCollectionKey, 0, 0); + + VehicleHeat *= cooling.NewWindowTint() * extraAdjust; +} + +void FECareerRecord::CommitPursuitCarData(unsigned int infractions, unsigned int accumulated_bounty, bool pursuit_evaded) { + FEInfractionsData infractionData(infractions); + + UnservedInfractions += infractionData; + if (pursuit_evaded) { + Bounty += accumulated_bounty; + NumEvadedPursuits++; + } + else { + NumBustedPursuits++; + } +} + +void FECareerRecord::WaiveIncractions(unsigned int infractions) { + if ((infractions & GInfractionManager::kInfraction_Assault) != 0) { + UnservedInfractions.Assault--; + } + if ((infractions & GInfractionManager::kInfraction_Damage) != 0) { + UnservedInfractions.Damage--; + } + if ((infractions & GInfractionManager::kInfraction_HitAndRun) != 0) { + UnservedInfractions.HitAndRun--; + } + if ((infractions & GInfractionManager::kInfraction_OffRoad) != 0) { + UnservedInfractions.OffRoad--; + } + if ((infractions & GInfractionManager::kInfraction_Racing) != 0) { + UnservedInfractions.Racing--; + } + if ((infractions & GInfractionManager::kInfraction_Reckless) != 0) { + UnservedInfractions.Reckless--; + } + if ((infractions & GInfractionManager::kInfraction_Resist) != 0) { + UnservedInfractions.Resist--; + } + if ((infractions & GInfractionManager::kInfraction_Speeding) != 0) { + UnservedInfractions.Speeding--; + } + NumBustedPursuits--; +} + +void FECareerRecord::ServeAllIncractions() { + FEInfractionsData cleared(0); + + ServedInfractions += UnservedInfractions; + UnservedInfractions = cleared; +} + +unsigned int FECareerRecord::GetNumInfraction(GInfractionManager::InfractionType type, bool get_unserved) const { + return GetInfractions(get_unserved).GetValue(type); +} + +FECarRecord *FEPlayerCarDB::GetCarRecordByHandle(unsigned int handle) { + for (int i = 0; i < 200; i++) { + if (CarTable[i].Handle == handle) { + return &CarTable[i]; + } + } + return nullptr; +} + +FECustomizationRecord *FEPlayerCarDB::GetCustomizationRecordByHandle(unsigned char handle) { + if (handle > 75) { + return nullptr; + } + return &Customizations[handle]; +} + +FECarRecord *FEPlayerCarDB::GetCarByIndex(int index) { + if (index > 199) { + return nullptr; + } + return &CarTable[index]; +} + +bool FEPlayerCarDB::CanCreateNewCarRecord() { + for (int i = 0; i < 200; i++) { + if (!CarTable[i].IsValid()) { + return true; + } + } + return false; +} + +bool FEPlayerCarDB::CanCreateNewCustomizationRecord() { + for (int i = 0; i < 75; i++) { + if (Customizations[i].Handle == 0xFF) { + return true; + } + } + return false; +} + +FECarRecord *FEPlayerCarDB::CreateNewCarRecord() { + for (int i = 0; i < 200; i++) { + if (CarTable[i].Handle == 0xFFFFFFFF) { + FECarRecord *record = &CarTable[i]; + + record->Default(); + record->Handle = i; + return record; + } + } + return nullptr; +} + +FECustomizationRecord *FEPlayerCarDB::CreateNewCustomizationRecord() { + for (int i = 0; i < 75; i++) { + if (Customizations[i].Handle == 0xFF) { + FECustomizationRecord *record = &Customizations[i]; + + record->Default(); + Customizations[i].Handle = i; + return record; + } + } + return nullptr; +} + +FECareerRecord *FEPlayerCarDB::CreateNewCareerRecord() { + for (int i = 0; i < 25; i++) { + if (CareerRecords[i].Handle == 0xFF) { + CareerRecords[i].Default(); + CareerRecords[i].Handle = i; + return &CareerRecords[i]; + } + } + return nullptr; +} + +unsigned short FEPlayerCarDB::GetNumInfraction(GInfractionManager::InfractionType type, bool get_unserved) { + struct NumInfraction : public MyCallback { + virtual unsigned int Callback(const FECareerRecord &record) const { + return record.GetNumInfraction(type, get_unserved); + } + + GInfractionManager::InfractionType type; + bool get_unserved; + }; + + unsigned short total = + static_cast< unsigned short >((get_unserved ? SoldHistoryUnservedInfractions : SoldHistoryServedInfractions).GetValue(type)); + NumInfraction callback; + + callback.type = type; + callback.get_unserved = get_unserved; + total += static_cast< unsigned short >(ForAllCareerRecordsSum(callback)); + return total; +} + +unsigned int FEPlayerCarDB::GetTotalNumInfractions(bool get_unserved) { + struct TotalNumInfractions : public MyCallback { + virtual unsigned int Callback(const FECareerRecord &record) const { + return record.GetInfractions(get_unserved).NumInfractions(); + } + + bool get_unserved; + }; + + unsigned int total = (get_unserved ? SoldHistoryUnservedInfractions : SoldHistoryServedInfractions).NumInfractions(); + TotalNumInfractions callback; + + callback.get_unserved = get_unserved; + total += ForAllCareerRecordsSum(callback); + return total; +} + +unsigned short FEPlayerCarDB::GetNumInfractionsOnCar(unsigned int car_handle, bool get_unserved) { + FECarRecord *fe_car = GetCarRecordByHandle(car_handle); + FECareerRecord *record = GetCareerRecordByHandle(fe_car->CareerHandle); + if (record != nullptr) { + return static_cast< unsigned short >(record->GetInfractions(get_unserved).NumInfractions()); + } + + return 0; +} + +unsigned int FEPlayerCarDB::GetTotalBounty() { + struct Bounty : public MyCallback { + virtual unsigned int Callback(const FECareerRecord &record) const { + return record.GetBounty(); + } + }; + + Bounty callback; + return ForAllCareerRecordsSum(callback) + SoldHistoryBounty; +} + +unsigned int FEPlayerCarDB::GetTotalEvadedPursuits() { + struct EvadedPursuits : public MyCallback { + virtual unsigned int Callback(const FECareerRecord &record) const { + return record.GetNumEvadedPursuits(); + } + }; + + EvadedPursuits callback; + return ForAllCareerRecordsSum(callback) + SoldHistoryNumEvadedPursuits; +} + +unsigned int FEPlayerCarDB::GetTotalBustedPursuits() { + struct BustedPursuits : public MyCallback { + virtual unsigned int Callback(const FECareerRecord &record) const { + return record.GetNumBustedPursuits(); + } + }; + + BustedPursuits callback; + return ForAllCareerRecordsSum(callback) + SoldHistoryNumBustedPursuits; +} + +unsigned int FEPlayerCarDB::GetNumImpoundedCars() { + struct IsImpounded : public MyCallback { + virtual unsigned int Callback(const FECareerRecord &record) const { + return record.TheImpoundData.IsImpounded(); + } + }; + + IsImpounded callback; + return ForAllCareerRecordsSum(callback); +} + +unsigned int FEPlayerCarDB::GetTotalFines(bool get_unserved) { + struct Fines : public MyCallback { + virtual unsigned int Callback(const FECareerRecord &record) const { + return record.GetInfractions(get_unserved).GetFineValue(); + } + + bool get_unserved; + }; + + Fines callback; + callback.get_unserved = get_unserved; + return ForAllCareerRecordsSum(callback); +} + +unsigned int FEPlayerCarDB::GetNumCareerCarsWithARecord() { + struct NumCars : public MyCallback { + virtual unsigned int Callback(const FECareerRecord &record) const { + return 1; + } + }; + + NumCars callback; + return ForAllCareerRecordsSum(callback); +} + +unsigned int FEPlayerCarDB::ForAllCareerRecordsSum(const MyCallback &callback) { + unsigned int val = 0; + + for (int i = 0; i < 200; i++) { + FECarRecord *fe_car = GetCarByIndex(i); + if (fe_car->IsValid() && fe_car->MatchesFilter(0xF0002)) { + const FECareerRecord *record = GetCareerRecordByHandle(fe_car->CareerHandle); + if (record != nullptr) { + val += callback.Callback(*record); + } + } + } + + return val; +} + +void FEPlayerCarDB::BackupSoldCarHistory(unsigned char sold_car) { + FECareerRecord *careerRecord = GetCareerRecordByHandle(sold_car); + if (careerRecord == nullptr) { + return; + } + + SoldHistoryBounty += careerRecord->GetBounty(); + SoldHistoryNumEvadedPursuits += careerRecord->GetNumEvadedPursuits(); + SoldHistoryNumBustedPursuits += careerRecord->GetNumBustedPursuits(); + AddInfractions(SoldHistoryUnservedInfractions, careerRecord->GetInfractions(true)); + AddInfractions(SoldHistoryServedInfractions, careerRecord->GetInfractions(false)); +} + +unsigned int FEPlayerCarDB::GetPreferedCarName() { + unsigned int max_pursuits = 0; + unsigned int name = 0; + + for (int i = 0; i < 200; i++) { + FECarRecord *fe_car = GetCarByIndex(i); + if (fe_car->IsValid() && fe_car->MatchesFilter(0xF0002)) { + FECareerRecord *career_record = GetCareerRecordByHandle(fe_car->CareerHandle); + if (career_record != nullptr) { + unsigned int pursuits = career_record->GetNumEvadedPursuits() + career_record->GetNumBustedPursuits(); + if (pursuits > max_pursuits) { + max_pursuits = pursuits; + name = fe_car->GetNameHash(); + } + } + } + } + + return name; +} + +int FEPlayerCarDB::GetNumCars(unsigned int filter) { + int total = 0; + + for (int i = 0; i < 200; i++) { + if (CarTable[i].IsValid() && CarTable[i].MatchesFilter(filter)) { + total++; + } + } + + return total; +} + +int FEPlayerCarDB::GetNumPurchasedCars() { + int total = 0; + + for (int i = 0; i < 200; i++) { + FECarRecord *record = &CarTable[i]; + if (record->IsValid() && record->MatchesFilter(0xF0002) && (record->FilterBits & 0x40) == 0) { + total++; + } + } + + return total; +} + +int FEPlayerCarDB::GetNumAvailableCareerCars() { + int total = 0; + + for (int i = 0; i < 200; i++) { + FECarRecord *record = &CarTable[i]; + if (record->IsValid() && record->CareerHandle != 0xFF) { + FECareerRecord *career_record = GetCareerRecordByHandle(record->CareerHandle); + if (!career_record->TheImpoundData.IsImpounded()) { + total++; + } + } + } + + return total; +} + +void FEPlayerCarDB::DeleteAllCars() { + for (int i = 0; i < 200; i++) { + CarTable[i].Handle = 0xFFFFFFFF; + } +} + +void FEPlayerCarDB::DeleteAllCustomizations() { + for (int i = 0; i < 75; i++) { + Customizations[i].Handle = 0xFF; + } +} + +void FEPlayerCarDB::DeleteAllCareerRecords() { + for (int i = 0; i < 25; i++) { + CareerRecords[i].Handle = 0xFF; + } +} + +void FEPlayerCarDB::Default() { + DeleteAllCars(); + DeleteAllCustomizations(); + DeleteAllCareerRecords(); + + FECarRecord *careerStart = CreateNewPresetCar("M3GTRCAREERSTART"); + careerStart->Handle = 0x12345678; + careerStart->FilterBits = 0xF0020; + + for (int i = 0; i < GetNumPresetCars(); i++) { + PresetCar *preset = GetPresetCarAt(i); + const char *preset_name = preset->PresetName; + unsigned int preset_hash = FEHashUpper(preset_name); + + if (UnlockSystem::IsBonusCarCEOnly(preset_hash) || IsBonusCar(preset_name)) { + FECarRecord *bonusCar = CreateNewPresetCar(preset_name); + if (bonusCar) { + bonusCar->FilterBits = 0xF0008; + } + } else if (!bStrICmp(preset_name, "M3GTRCAREERSTART") || ShowAllPresetsInFE) { + CreateNewPresetCar(preset_name); + } + Physics::Upgrades::Flush(); + } + + const Attrib::Class *carClass = Attrib::Database::Get().GetClass(Attrib::Gen::pvehicle::ClassKey()); + unsigned int key = carClass->GetFirstCollection(); + while (key != 0) { + Attrib::Gen::pvehicle vehicle(key, 0, nullptr); + Attrib::Gen::frontend frontendData(vehicle.frontend(), 0, nullptr); + if (!frontendData.IsDynamic()) { + if (vehicle.PlayerUsable() || ShowAllCarsInFE) { + const char *collection_name = vehicle.CollectionName(); + if (collection_name && *collection_name) { + FECarRecord *fe_car = CreateNewCarRecord(); + if (fe_car) { + fe_car->FEKey = frontendData.GetCollection(); + fe_car->VehicleKey = vehicle.GetCollection(); + fe_car->Default(); + } + } + } + Physics::Upgrades::Flush(); + } + key = carClass->GetNextCollection(key); + } + + SoldHistoryBounty = 0; + SoldHistoryNumEvadedPursuits = 0; + SoldHistoryNumBustedPursuits = 0; + ClearInfractions(SoldHistoryUnservedInfractions); + ClearInfractions(SoldHistoryServedInfractions); +} + +bool FEPlayerCarDB::IsBonusCar(const char *preset_name) { + unsigned int hash = FEHashUpper(preset_name); + + if (hash == 0x03A94520) { + return true; + } + + if (hash >= 0x0000965F && hash <= 0x00009666) { + return true; + } + + if (hash >= 0x0013624E && hash <= 0x00136253) { + return true; + } + + return hash == 0x2CF385B2 || hash == 0x2CF370F0 || hash == 0x34498EB2 || hash == 0xCB6AAF2F; +} + +FECareerRecord *FEPlayerCarDB::GetCareerRecordByHandle(unsigned char handle) { + if (handle >= 26) { + return nullptr; + } + if (CareerRecords[handle].Handle == 0xFF) { + return nullptr; + } + return &CareerRecords[handle]; +} + +FECarRecord *FEPlayerCarDB::CreateNewCustomCar(unsigned int fromCar) { + if (GetNumQuickRaceCars() < 20) { + return CreateCar(fromCar, 0xF0004); + } + return nullptr; +} + +FECarRecord *FEPlayerCarDB::AwardRivalCar(unsigned int preset) { + if (preset == 0x03A94520) { + FEDatabase->GetCareerSettings()->SpecialFlags |= 0x100000; + } + + PresetCar *presetCar = FindFEPresetCar(preset); + FECarRecord *presetRecord = GetCarRecordByHandle(preset); + if (presetRecord == nullptr) { + presetRecord = CreateNewPresetCar(presetCar->PresetName); + } + + FECarRecord *car = CreateNewCareerCar(presetRecord->Handle); + FECustomizationRecord *customization = GetCustomizationRecordByHandle(car->Customization); + RideInfo ride; + + car->FilterBits |= 0x40; + ride.Init(static_cast< CarType >(-1), CarRenderUsage_Player, 0, 0); + ride.FillWithPreset(FEHashUpper(presetCar->PresetName)); + customization->WriteRideIntoRecord(&ride); + + Attrib::Gen::presetride ridePreset(Attrib::StringToLowerCaseKey(presetCar->PresetName), 0, nullptr); + if (ridePreset.IsValid() && customization != nullptr) { + Attrib::Gen::pvehicle vehicle(car->VehicleKey, 0, nullptr); + if (Physics::Upgrades::ApplyPreset(vehicle, ridePreset)) { + customization->WritePhysicsIntoRecord(vehicle); + } + } + + return car; +} + +FECarRecord *FEPlayerCarDB::CreateNewCareerCar(unsigned int fromCar) { + FECarRecord *car = nullptr; + + if (GetNumCareerCars() < 0x19) { + car = CreateCar(fromCar, 0xF0002); + if (car != nullptr) { + FECareerRecord *careerRecord = CreateNewCareerRecord(); + if (careerRecord == nullptr) { + GetCustomizationRecordByHandle(car->Customization)->Handle = 0xFF; + car->Handle = 0xFFFFFFFF; + car = nullptr; + } else { + car->CareerHandle = careerRecord->Handle; + } + } + } + + return car; +} + +FECarRecord *FEPlayerCarDB::CreateNewPresetCar(const char *preset_name) { + unsigned int presetHash = FEHashUpper(preset_name); + PresetCar *preset = FindFEPresetCar(presetHash); + Attrib::Gen::pvehicle vehicle(static_cast< unsigned int >(preset->VehicleKey), 0, nullptr); + + if (!vehicle.IsValid() || preset == nullptr) { + return nullptr; + } + + FECarRecord *car = CreateNewCarRecord(); + if (car == nullptr) { + return nullptr; + } + + car->Handle = presetHash; + FECustomizationRecord *customization = CreateNewCustomizationRecord(); + if (customization == nullptr) { + car->Handle = 0xFFFFFFFF; + return nullptr; + } + + car->Customization = customization->Handle; + SetCarToPreset(car->Handle, preset); + + Attrib::Gen::presetride ridePreset(Attrib::StringToLowerCaseKey(preset->PresetName), 0, nullptr); + if (ridePreset.IsValid()) { + Attrib::Gen::pvehicle vehicleWithPreset(vehicle); + if (Physics::Upgrades::ApplyPreset(vehicleWithPreset, ridePreset)) { + customization->WritePhysicsIntoRecord(vehicleWithPreset); + } + Physics::Upgrades::Clear(vehicleWithPreset); + } + + car->FilterBits = 0xF0010; + return car; +} + +FECarRecord *FEPlayerCarDB::CreateCar(unsigned int fromCar, int FilterBits) { + FECarRecord *source = GetCarRecordByHandle(fromCar); + if (source == nullptr) { + return nullptr; + } + + FECarRecord *car = CreateNewCarRecord(); + if (car == nullptr) { + return nullptr; + } + + *car = *source; + FECustomizationRecord *customization = CreateNewCustomizationRecord(); + if (customization == nullptr) { + car->Handle = 0xFFFFFFFF; + return nullptr; + } + + car->Customization = customization->Handle; + car->FilterBits = (car->FilterBits & 0xFFFF0000) | static_cast< unsigned int >(FilterBits); + + RideInfo ride; + ride.Init(static_cast< CarType >(-1), CarRenderUsage_Player, 0, 0); + ride.Init(car->GetType(), CarRenderUsage_Player, 0, 0); + ride.SetRandomPaint(); + ride.SetStockParts(); + customization->WriteRideIntoRecord(&ride); + return car; +} + +void FEPlayerCarDB::DeleteCustomCar(unsigned int handle) { + DeleteCar(handle, 4, false); +} + +void FEPlayerCarDB::DeleteCareerCar(unsigned int handle, bool was_sold) { + DeleteCar(handle, 2, was_sold); +} + +bool FEPlayerCarDB::DeleteCar(unsigned int handle, unsigned int filter, bool was_sold) { + FECarRecord *car = GetCarRecordByHandle(handle); + if (car == nullptr || car->Handle == 0xFFFFFFFF || (car->FilterBits & filter) == 0) { + return false; + } + + if (was_sold) { + BackupSoldCarHistory(car->CareerHandle); + } + + car->Handle = 0xFFFFFFFF; + + if (car->Customization != 0xFF) { + GetCustomizationRecordByHandle(car->Customization)->Handle = 0xFF; + } + + if (car->CareerHandle != 0xFF) { + GetCareerRecordByHandle(car->CareerHandle)->Handle = 0xFF; + } + + return true; +} + +int FEPlayerCarDB::GetNumQuickRaceCars() { + return GetNumCars(0xF0004); +} + +int FEPlayerCarDB::GetNumCareerCars() { + return GetNumCars(0xF0002); +} + +char *FEPlayerCarDB::SaveToBuffer(char *buffer, int bufsize) { + bMemCpy(buffer, this, 0x8CC8); + return buffer + 0x8CC8; +} + +char *FEPlayerCarDB::LoadFromBuffer(char *buffer, int bufsize) { + bMemCpy(this, buffer, 0x8CC8); + return buffer + 0x8CC8; +} + +int FEPlayerCarDB::GetSaveBufferSize() { + return 0x8CC8; +} + +void FEPlayerCarDB::AwardBonusCars() { + if (gEasterEggs.IsEasterEggUnlocked(EASTER_EGG_CASTROL)) { + unsigned int flags = FEDatabase->GetCareerSettings()->SpecialFlags; + if ((flags & 0x00040000) == 0) { + FEDatabase->GetCareerSettings()->SpecialFlags = flags | 0x00040000; + } + } +} + +void FEPlayerCarDB::SetCarToPreset(unsigned int car, PresetCar *preset) { + FECarRecord *record = GetCarRecordByHandle(car); + + record->FEKey = static_cast< unsigned int >(preset->FEKey); + record->VehicleKey = static_cast< unsigned int >(preset->VehicleKey); + record->FilterBits = preset->FilterBits; + + FECustomizationRecord *customization = GetCustomizationRecordByHandle(record->Customization); + if (customization != nullptr) { + customization->BecomePreset(preset); + } +} + +void FEPlayerCarDB::BuildRideForPlayer(unsigned int car, int player, RideInfo *ride) { + FECarRecord *record = GetCarRecordByHandle(car); + + ride->Init(record->GetType(), CarRenderUsage_Player, 0, 0); + FECustomizationRecord *customization = GetCustomizationRecordByHandle(record->Customization); + if (customization != nullptr) { + customization->WriteRecordIntoRide(ride); + } else { + ride->SetRandomPaint(); + ride->SetStockParts(); + } +} + +bool FEPlayerCarDB::WriteRecordIntoPhysics(unsigned int car, Attrib::Gen::pvehicle &attributes) { + FECarRecord *record = GetCarRecordByHandle(car); + if (record != nullptr) { + FECustomizationRecord *customization = GetCustomizationRecordByHandle(record->Customization); + if (customization != nullptr) { + return customization->WriteRecordIntoPhysics(attributes); + } + } + return false; +} + +POVTypes GetPOVTypeFromPlayerCamera(ePlayerSettingsCameras cam) { + switch (cam) { + case 0: return static_cast(0); + case 1: return static_cast(1); + case 2: return static_cast(2); + case 3: return static_cast(3); + case 4: return static_cast(4); + case 5: return static_cast(5); + case 6: return static_cast(6); + default: return static_cast(2); + } +} + +bool IsPlayerCameraSelectable(POVTypes pov_type) { + Attrib::Gen::camerainfo camera_info(Attrib::FindCollection(Attrib::Gen::camerainfo::ClassKey(), 0xeec2271a), 0, nullptr); + unsigned int model_name_key = 0; + IPlayer *player = IPlayer::First(PLAYER_LOCAL); + if (player) { + ISimable *simable = player->GetSimable(); + if (simable) { + IVehicle *vehicle = nullptr; + if (simable->QueryInterface(&vehicle)) { + const char *vehicle_name = vehicle->GetVehicleName(); + if (vehicle_name) { + model_name_key = Attrib::StringToLowerCaseKey(vehicle_name); + } + } + } + } + + Attrib::Gen::ecar car_info(Attrib::FindCollectionWithDefault(Attrib::Gen::ecar::ClassKey(), model_name_key), 0, nullptr); + + const Attrib::RefSpec *ref_spec = nullptr; + switch (pov_type) { + case 0: + ref_spec = &car_info.CameraInfo_Bumper(0); + break; + case 1: + ref_spec = &car_info.CameraInfo_Hood(0); + break; + case 2: + ref_spec = &car_info.CameraInfo_Close(0); + break; + case 3: + ref_spec = &car_info.CameraInfo_Far(0); + break; + case 4: + ref_spec = &car_info.CameraInfo_SuperFar(0); + break; + case 5: + ref_spec = &car_info.CameraInfo_Drift(0); + break; + case 6: + ref_spec = &car_info.CameraInfo_Pursuit(0); + break; + } + + if (!ref_spec) { + camera_info.Change(0xeec2271a); + return camera_info.SELECTABLE(eGetCurrentViewMode() == EVIEWMODE_TWOH); + } + + static_cast(camera_info).Change(*ref_spec); + return camera_info.SELECTABLE(eGetCurrentViewMode() == EVIEWMODE_TWOH); +} + +ePlayerSettingsCameras GetPlayerCameraFromPOVType(POVTypes pov) { + switch (pov) { + case 0: return static_cast(0); + case 1: return static_cast(1); + case 2: return static_cast(2); + case 3: return static_cast(3); + case 4: return static_cast(4); + case 5: return static_cast(5); + case 6: return static_cast(6); + default: return static_cast(2); + } +} + +void AdjustStableHeat_EvadePursuit(int playerNum) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(playerNum); + for (int i = 0; i <= 0xC7; i++) { + FECarRecord *fe_car = stable->GetCarByIndex(i); + FECareerRecord *fe_career = stable->GetCareerRecordByHandle(fe_car->CareerHandle); + if (fe_career) { + fe_career->AdjustHeatOnEvadePursuit(); + } + } +} + +void AdjustStableHeat_EventWin(int playerNum) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(playerNum); + for (int i = 0; i <= 0xC7; i++) { + FECarRecord *fe_car = stable->GetCarByIndex(i); + FECareerRecord *fe_career = stable->GetCareerRecordByHandle(fe_car->CareerHandle); + if (fe_career) { + fe_career->AdjustHeatOnEventWin(); + } + } +} + +void AdjustStableImpound_EventWin(int playerNum) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(playerNum); + for (int i = 0; i <= 0xC7; i++) { + FECarRecord *fe_car = stable->GetCarByIndex(i); + FECareerRecord *fe_career = stable->GetCareerRecordByHandle(fe_car->CareerHandle); + if (fe_career) { + if (fe_career->TheImpoundData.NotifyWin()) { + GManager::Get().AddSMS(0x78); + } + } + } +} + +void AdjustStableImpound_EvadePursuit(int playerNum) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(playerNum); + for (int i = 0; i <= 0xC7; i++) { + FECarRecord *fe_car = stable->GetCarByIndex(i); + FECareerRecord *fe_career = stable->GetCareerRecordByHandle(fe_car->CareerHandle); + if (fe_career) { + fe_career->TheImpoundData.NotifyEvade(); + } + } +} + +unsigned int FEInfractionsData::GetFineValue() const { + if (NumInfractions() == 0) { + return 0; + } + unsigned int fines = 0; + Attrib::Gen::infractions AssaultFine(Attrib::StringToKey("assault"), 0, nullptr); + if (AssaultFine.IsValid()) { + fines = static_cast(Assault) * AssaultFine.amount(); + } + Attrib::Gen::infractions DamageFine(Attrib::StringToKey("damage"), 0, nullptr); + if (DamageFine.IsValid()) { + fines += static_cast(Damage) * DamageFine.amount(); + } + Attrib::Gen::infractions HitAndRunFine(Attrib::StringToKey("hit_and_run"), 0, nullptr); + if (HitAndRunFine.IsValid()) { + fines += static_cast(HitAndRun) * HitAndRunFine.amount(); + } + Attrib::Gen::infractions OffRoadFine(Attrib::StringToKey("off_road"), 0, nullptr); + if (OffRoadFine.IsValid()) { + fines += static_cast(OffRoad) * OffRoadFine.amount(); + } + Attrib::Gen::infractions RacingFine(Attrib::StringToKey("racing"), 0, nullptr); + if (RacingFine.IsValid()) { + fines += static_cast(Racing) * RacingFine.amount(); + } + Attrib::Gen::infractions RecklessFine(Attrib::StringToKey("reckless_driving"), 0, nullptr); + if (RecklessFine.IsValid()) { + fines += static_cast(Reckless) * RecklessFine.amount(); + } + Attrib::Gen::infractions ResistFine(Attrib::StringToKey("resisting_arrest"), 0, nullptr); + if (ResistFine.IsValid()) { + fines += static_cast(Resist) * ResistFine.amount(); + } + Attrib::Gen::infractions SpeedingFine(Attrib::StringToKey("speeding"), 0, nullptr); + if (SpeedingFine.IsValid()) { + fines += static_cast(Speeding) * SpeedingFine.amount(); + } + return fines; +} diff --git a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp index 006e3698f..2c93773e4 100644 --- a/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp +++ b/src/Speed/Indep/Src/Frontend/Database/VehicleDB.hpp @@ -12,6 +12,7 @@ // total size: 0x14 struct FECarRecord { + FECarRecord(); unsigned int Handle; // offset 0x0, size 0x4 unsigned int FEKey; // offset 0x4, size 0x4 unsigned int VehicleKey; // offset 0x8, size 0x4 @@ -19,16 +20,41 @@ struct FECarRecord { unsigned char Customization; // offset 0x10, size 0x1 unsigned char CareerHandle; // offset 0x11, size 0x1 unsigned short Padd; // offset 0x12, size 0x2 + bool IsValid() { return Handle != 0xFFFFFFFF; } + bool IsCustomized() { return Customization != 0xFF; } + bool IsCareer() { return CareerHandle != 0xFF; } + FECarRecord &operator=(const FECarRecord &other_record); + void Default(); + bool MatchesFilter(int theFilter); + unsigned int GetCost(); + const char *GetDebugName(); + unsigned int GetNameHash(); + const char *GetManufacturerName(); + unsigned int GetLogoHash(); + unsigned int GetManuLogoHash(); + unsigned int GetReleaseFromImpoundCost(); + CarType GetType(); }; +struct PresetCar; + // total size: 0x198 struct FECustomizationRecord { + 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 + void BecomePreset(PresetCar *preset); + void Default(); + bool WriteRecordIntoPhysics(Attrib::Gen::pvehicle &attributes) const; + void WriteRideIntoRecord(const RideInfo *ride); + struct CarPart *GetInstalledPart(CarType cartype, int carslotid) const; + void SetInstalledPart(int carslotid, struct CarPart *part); + void WriteRecordIntoRide(RideInfo *ride) const; + void WritePhysicsIntoRecord(const Attrib::Gen::pvehicle &attributes); }; // total size: 0x8 @@ -47,6 +73,16 @@ struct FEImpoundData { char EvadeCount; // offset 0x4, size 0x1 char Pad1; // offset 0x5, size 0x1 short Pad2; // offset 0x6, size 0x2 + void Default(); + void BecomeImpounded(eImpoundReasons reason); + void NotifyPlayerPaidToRelease(); + void NotifyPlayerUsedMarkerToRelease(); + bool NotifyWin(); + bool NotifyBusted(); + bool NotifyEvade(); + bool CanAddMaxBusted(); + void AddMaxBusted(); + bool IsImpounded() const { return ImpoundedState != 0; } }; // total size: 0x10 @@ -59,6 +95,13 @@ struct FEInfractionsData { unsigned short Damage; // offset 0xA, size 0x2 unsigned short Resist; // offset 0xC, size 0x2 unsigned short OffRoad; // offset 0xE, size 0x2 + FEInfractionsData() { bMemSet(this, 0, sizeof(FEInfractionsData)); } + FEInfractionsData(unsigned int infractions); + void operator+=(const FEInfractionsData &rhs); + unsigned short GetValue(GInfractionManager::InfractionType type) const; + unsigned short NumInfractions() const; + unsigned int GetFineValue() const; + unsigned short GetTotalInfractions() const { return NumInfractions(); } }; // total size: 0x38 @@ -68,13 +111,24 @@ class FECareerRecord { return Bounty; } - // unsigned int GetNumEvadedPursuits() const {} + unsigned int GetNumEvadedPursuits() const { + return NumEvadedPursuits; + } - // unsigned int GetNumBustedPursuits() const {} + unsigned int GetNumBustedPursuits() const { + return NumBustedPursuits; + } + + int GetTimesBusted() { return TheImpoundData.TimesBusted; } - // int GetTimesBusted() {} + int GetMaxBusted() { return TheImpoundData.MaxBusted; } - // const FEInfractionsData &GetInfractions(bool get_unserved) const {} + const FEInfractionsData& GetInfractions(bool get_unserved) const { + if (get_unserved) { + return UnservedInfractions; + } + return ServedInfractions; + } // void TweakBounty(unsigned int bounty) {} @@ -100,7 +154,7 @@ class FECareerRecord { void CommitPursuitCarData(unsigned int infractions, unsigned int accumulated_bounty, bool pursuit_evaded); void WaiveIncractions(unsigned int infractions); void ServeAllIncractions(); - // unsigned int GetNumInfraction(InfractionType type, bool get_unserved) const; + unsigned int GetNumInfraction(GInfractionManager::InfractionType type, bool get_unserved) const; unsigned char Handle; // offset 0x0, size 0x1 FEImpoundData TheImpoundData; // offset 0x2, size 0x8 @@ -118,8 +172,14 @@ class FECareerRecord { class FEPlayerCarDB { public: // total size: 0x4 - class MyCallback {}; + class MyCallback { + public: + virtual ~MyCallback() {} + virtual unsigned int Callback(const FECareerRecord &record) const = 0; + }; + FEPlayerCarDB(); + ~FEPlayerCarDB(); void BuildRideForPlayer(unsigned int car, int player, RideInfo *ride); FECarRecord *GetCarRecordByHandle(unsigned int handle); FECustomizationRecord *GetCustomizationRecordByHandle(unsigned char handle); diff --git a/src/Speed/Indep/Src/Frontend/Database/uiProfileManager.cpp b/src/Speed/Indep/Src/Frontend/Database/uiProfileManager.cpp index e69de29bb..49fb7cf78 100644 --- a/src/Speed/Indep/Src/Frontend/Database/uiProfileManager.cpp +++ b/src/Speed/Indep/Src/Frontend/Database/uiProfileManager.cpp @@ -0,0 +1,181 @@ +#include "uiProfileManager.hpp" + +#include "Speed/Indep/Src/FEng/FEList.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" + +FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetColor(FEObject* obj, unsigned int color); +unsigned char FEngGetLastButton(const char* pkg_name); +const char* GetLocalizedString(unsigned int hash); +void MemcardEnter(const char* from, const char* to, unsigned int op, void (*pTermFunc)(void*), + void* pTermFuncParam, unsigned int msgSuccess, unsigned int msgFailed); + +MenuScreen* CreateUIProfileManager(ScreenConstructorData* sd) { + return new UIProfileManager(sd); +} + +UIProfileManager::UIProfileManager(ScreenConstructorData* sd) + : IconScrollerMenu(sd) { + Setup(); + FEPrintf(GetPackageName(), 0x42ADB44C, GetLocalizedString(0xBCB18F38)); +} + +UIProfileManager::~UIProfileManager() {} + +void UIProfileManager::Refresh() { + if (FEDatabase->bProfileLoaded) { + mpSave->IsGreyOut = false; + } else { + mpSave->IsGreyOut = true; + } + + if (FEDatabase->bProfileLoaded) { + FEngSetVisible(FEngFindObject(GetPackageName(), FEHashUpper("PROFILE_NAME_GROUP"))); + FEPrintf(GetPackageName(), 0xEB406FEC, + FEDatabase->GetUserProfile(0)->GetProfileName()); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), FEHashUpper("PROFILE_NAME_GROUP"))); + } + + FEngSetColor(mpSave->FEngObject, mpSave->OriginalColor); + RefreshHeader(); +} + +void UIProfileManager::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, obj, param1, param2); + + switch (msg) { + case 0x911AB364: + cFEng::Get()->QueuePackageSwitch("MainMenu.fng", 0, 0, false); + break; + case 0x35F8620B: + Refresh(); + break; + case 0x7E998E5E: + FEDatabase->RefreshCurrentRide(); + Refresh(); + break; + } +} + +void UIProfileManager::Setup() { + mpSave = new PMSave(0x228B7E32, 0x1C8ACE, 0); + mpSave->SetReactImmediately(true); + + PMPopDelete* popDelete = new PMPopDelete(0x43798644, 0x55423473, 0); + popDelete->SetReactImmediately(true); + AddOption(popDelete); + + PMLoad* load = new PMLoad(0x2287E063, 0x18ECFF, 0); + load->SetReactImmediately(true); + AddOption(load); + + AddOption(mpSave); + + PMDelete* del = new PMDelete(0x0D9035CE, 0x56B00632, 0); + del->SetReactImmediately(true); + AddOption(del); + + int lastButton = FEngGetLastButton(GetPackageName()); + if (bFadeInIconsImmediately) { + Options.bDelayUpdate = false; + Options.bFadingOut = false; + Options.StartFadeIn(); + } + Options.SetInitialPos(lastButton); + + Refresh(); +} + +MenuScreen* CreateUIDeleteProfile(ScreenConstructorData* sd) { + return new UIDeleteProfile(sd); +} + +UIDeleteProfile::UIDeleteProfile(ScreenConstructorData* sd) + : IconScrollerMenu(sd) { + Setup(); + FEPrintf(GetPackageName(), 0x42ADB44C, GetLocalizedString(0xE6F55DF0)); +} + +UIDeleteProfile::~UIDeleteProfile() {} + +void UIDeleteProfile::Setup() { + PMCreateNew* createNew = new PMCreateNew(0x43798644, 0x55423473, 0); + createNew->SetReactImmediately(true); + AddOption(createNew); + + PMDelete* del = new PMDelete(0x0D9035CE, 0x9F014666, 0); + del->SetReactImmediately(true); + AddOption(del); + + int lastButton = FEngGetLastButton(GetPackageName()); + SetInitialOption(lastButton); + + Refresh(); +} + +void UIDeleteProfile::Refresh() { + if (FEDatabase->bProfileLoaded) { + FEngSetVisible(FEngFindObject(GetPackageName(), FEHashUpper("PROFILE_NAME_GROUP"))); + FEPrintf(GetPackageName(), 0xEB406FEC, + FEDatabase->GetUserProfile(0)->GetProfileName()); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), FEHashUpper("PROFILE_NAME_GROUP"))); + } + + RefreshHeader(); + FEDatabase->RefreshCurrentRide(); +} + +void UIDeleteProfile::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, obj, param1, param2); + + switch (msg) { + case 0x911AB364: + cFEng::Get()->QueuePackageSwitch("ProfileManager.fng", 0, 0, false); + break; + case 0x7E998E5E: + Refresh(); + break; + } +} + +void PMSave::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + MemcardEnter(pkg_name, pkg_name, 0x2251, 0, 0, 0, 0); + } +} + +void PMLoad::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + MemcardEnter(pkg_name, pkg_name, 0x411, 0, 0, 0x3A2BE557, 0x8867412D); + } +} + +void PMDelete::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + MemcardEnter(pkg_name, pkg_name, 0x31, 0, 0, 0, 0); + } +} + +void PMCreateNew::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + MemcardEnter(pkg_name, pkg_name, 0x61, 0, 0, 0, 0); + } +} + +void PMPopDelete::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + MemcardEnter(pkg_name, pkg_name, 0x61, 0, 0, 0, 0); + } +} diff --git a/src/Speed/Indep/Src/Frontend/Database/uiProfileManager.hpp b/src/Speed/Indep/Src/Frontend/Database/uiProfileManager.hpp new file mode 100644 index 000000000..5b7624eac --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/Database/uiProfileManager.hpp @@ -0,0 +1,69 @@ +#ifndef FRONTEND_DATABASE_UIPROFILEMANAGER_H +#define FRONTEND_DATABASE_UIPROFILEMANAGER_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" + +struct PMSave : public IconOption { + PMSave(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~PMSave() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct PMLoad : public IconOption { + PMLoad(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~PMLoad() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct PMDelete : public IconOption { + PMDelete(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~PMDelete() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct PMCreateNew : public IconOption { + PMCreateNew(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~PMCreateNew() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct PMPopDelete : public IconOption { + PMPopDelete(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~PMPopDelete() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +// total size: 0x16C +struct UIDeleteProfile : public IconScrollerMenu { + UIDeleteProfile(ScreenConstructorData* sd); + ~UIDeleteProfile() override; + void Setup() override; + void Refresh(); + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; +}; + +// total size: 0x170 +struct UIProfileManager : public IconScrollerMenu { + PMSave* mpSave; // offset 0x16C + + UIProfileManager(ScreenConstructorData* sd); + ~UIProfileManager() override; + void Refresh(); + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; + void Setup() override; +}; + +MenuScreen* CreateUIProfileManager(ScreenConstructorData* sd); + +#endif diff --git a/src/Speed/Indep/Src/Frontend/FECarLoader.hpp b/src/Speed/Indep/Src/Frontend/FECarLoader.hpp index 59183bfe8..6d89ff739 100644 --- a/src/Speed/Indep/Src/Frontend/FECarLoader.hpp +++ b/src/Speed/Indep/Src/Frontend/FECarLoader.hpp @@ -1,10 +1,32 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY #ifndef FRONTEND_FECARLOADER_H #define FRONTEND_FECARLOADER_H - #ifdef EA_PRAGMA_ONCE_SUPPORTED #pragma once #endif - - - +#include +#include "Speed/Indep/Src/World/CarInfo.hpp" +struct GarageCarLoader { + bool IsThereALoadingRideInfo() { return IsLoadingRide; } + bool IsThereACurrentRideInfo() { return IsCurrentRide; } + bool HasSwitched() { return IsDifferent; } + GarageCarLoader(); + ~GarageCarLoader(); + void Init(); + void CleanUp(); + void CancelCarLoad(); + void LoadRideInfo(RideInfo *ride_info); + RideInfo *GetLoadingRideInfo(); + RideInfo *GetCurrentRideInfo(); + void Switch(); + void Update(); + char _pad_ride0[0x310]; // offset 0x0 (RideInfo placeholder) + char _pad_ride1[0x310]; // offset 0x310 (RideInfo placeholder) + bool IsLoadingRide; // offset 0x620 + bool IsCurrentRide; // offset 0x624 + int LoadingCar; // offset 0x628 + int CurrentCar; // offset 0x62C + bool IsDifferent; // offset 0x630 + bool UseFirstDummyTexturesForNextLoad; // offset 0x634 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/FEJoyInput.cpp b/src/Speed/Indep/Src/Frontend/FEJoyInput.cpp index e69de29bb..d9c76bae3 100644 --- a/src/Speed/Indep/Src/Frontend/FEJoyInput.cpp +++ b/src/Speed/Indep/Src/Frontend/FEJoyInput.cpp @@ -0,0 +1,356 @@ +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Input/ActionQueue.h" +#include "Speed/Indep/Src/Input/ActionRef.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Sim/SimTypes.h" + +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" + +namespace Sim { +Sim::eUserMode GetUserMode(); +} + +struct FEngTextInputObject; + +struct KeyboardEditString { + char InitialString[256]; + unsigned short EditStringUCS2[256]; + int CursorPosUCS2; + char EditStringPacked[256]; + unsigned int ModeFlags; + int KeysProcessed; + int MaxTextLength; + bool mEnabled; + FEngTextInputObject* TextInputObject; + + bool IsCapturing() { + return mEnabled && TextInputObject != nullptr; + } +}; + +extern KeyboardEditString gKeyboardManager; + +struct FEJoyMapping { + int eventID; + unsigned long padMask; + const char* name; + int state[4]; +}; + +static FEJoyMapping MapJoyEventToFEPad[16] = { + {FRONTENDACTION_UP, 0x00000001, "FEPad_Up", {0, 0, 0, 0}}, + {FRONTENDACTION_DOWN, 0x00000002, "FEPad_Down", {0, 0, 0, 0}}, + {FRONTENDACTION_LEFT, 0x00000004, "FEPad_Left", {0, 0, 0, 0}}, + {FRONTENDACTION_RIGHT, 0x00000008, "FEPad_Right", {0, 0, 0, 0}}, + {FRONTENDACTION_ACCEPT, 0x00000010, "FEPad_Accept", {0, 0, 0, 0}}, + {FRONTENDACTION_CANCEL, 0x00000020, "FEPad_Back", {0, 0, 0, 0}}, + {FRONTENDACTION_CANCEL_ALT, 0x00000020, "FEPad_Back", {0, 0, 0, 0}}, + {FRONTENDACTION_START, 0x00000040, "FEPad_Start", {0, 0, 0, 0}}, + {FRONTENDACTION_BUTTON0, 0x00000200, "FEPad_Button0", {0, 0, 0, 0}}, + {FRONTENDACTION_BUTTON1, 0x00000400, "FEPad_Button1", {0, 0, 0, 0}}, + {FRONTENDACTION_BUTTON2, 0x00000800, "FEPad_Button2", {0, 0, 0, 0}}, + {FRONTENDACTION_BUTTON3, 0x00001000, "FEPad_Button3", {0, 0, 0, 0}}, + {FRONTENDACTION_BUTTON4, 0x00002000, "FEPad_Button4", {0, 0, 0, 0}}, + {FRONTENDACTION_BUTTON5, 0x00004000, "FEPad_Button5", {0, 0, 0, 0}}, + {FRONTENDACTION_LTRIGGER, 0x00000080, "FEPad_LeftTrigger", {0, 0, 0, 0}}, + {FRONTENDACTION_RTRIGGER, 0x00000100, "FEPad_RightTrigger", {0, 0, 0, 0}}, +}; + +cFEngJoyInput* cFEngJoyInput::mInstance; + +cFEngJoyInput::cFEngJoyInput() { + for (int i = 0; i < 2; i++) { + mActionQ[i] = new ActionQueue(i, 0x82d21520, "FEng", false); + mActionQ[i]->Enable(true); + mActionQ[i]->IsConnected(); + } +} + +void cFEngJoyInput::FlushActions() { + for (int port = 0; port < 2; port++) { + if (mActionQ[port] != nullptr) { + mActionQ[port]->Flush(); + } + for (int i = 0; i < 16; i++) { + MapJoyEventToFEPad[i].state[port] = 0; + } + } +} + +void cFEngJoyInput::JoyDisable(JoystickPort port, bool do_flush) { + if (port == kJP_NumPorts) { + for (int i = 0; i < 2; i++) { + mActionQ[i]->Enable(false); + if (do_flush) { + mActionQ[i]->Flush(); + } + } + } else { + mActionQ[port]->Enable(false); + if (do_flush) { + mActionQ[port]->Flush(); + } + } +} + +bool cFEngJoyInput::IsJoyPluggedIn(JoystickPort port) { + return mActionQ[port]->IsConnected(); +} + +void cFEngJoyInput::JoyEnable(JoystickPort port, bool do_flush) { + if (port == kJP_NumPorts) { + for (int i = 0; i < 2; i++) { + if (!mActionQ[i]->IsEnabled()) { + mActionQ[i]->Enable(true); + if (do_flush) { + mActionQ[i]->Flush(); + } + } + } + } else if (port != static_cast(-1)) { + if (!mActionQ[port]->IsEnabled()) { + mActionQ[port]->Enable(true); + if (do_flush) { + mActionQ[port]->Flush(); + } + } + } +} + +bool cFEngJoyInput::IsJoyEnabled(JoystickPort port) { + if (port == kJP_NumPorts) { + for (int i = 0; i < 2; i++) { + if (!mActionQ[i]->IsEnabled()) { + return false; + } + } + } else { + if (!mActionQ[port]->IsEnabled()) { + return false; + } + } + return true; +} + +void cFEngJoyInput::SetRequiredJoy(JoystickPort port, bool required) { + if (port == kJP_NumPorts) { + for (int i = 0; i < 2; i++) { + mActionQ[i]->SetRequired(required); + } + return; + } + mActionQ[port]->SetRequired(required); +} + +bool cFEngJoyInput::CheckUnplugged() { + bool unplugged = false; + if (!TheGameFlowManager.IsInGame() && !FEManager::Get()->IsAllowingControllerError()) { + SetRequiredJoy(kJP_NumPorts, false); + } else { + int is_splitscreen = false; + if (FEDatabase->IsSplitScreenMode()) { + is_splitscreen = FEDatabase->iNumPlayers == 2; + } + bool bIsSplit; + if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + bIsSplit = true; + } else { + bIsSplit = is_splitscreen ? true : false; + } + JoystickPort player_port2 = static_cast(-1); + JoystickPort player_port1 = static_cast(FEDatabase->GetPlayersJoystickPort(0)); + if (player_port1 == static_cast(-1)) { + return false; + } + if (bIsSplit) { + player_port2 = static_cast(FEDatabase->GetPlayersJoystickPort(1)); + } + SetRequiredJoy(player_port1, true); + if (player_port2 != static_cast(-1)) { + SetRequiredJoy(player_port2, true); + } + FEManager* feManager = FEManager::Get(); + if (!IsJoyPluggedIn(player_port1)) { + feManager->WantControllerError(player_port1); + unplugged = true; + } else if (!bIsSplit && !cFEng::Get()->IsPackagePushed("ControllerUnplugged.fng")) { + feManager->ClearControllerError(player_port1); + } + if (player_port2 != static_cast(-1) && !IsJoyPluggedIn(player_port2)) { + feManager->WantControllerError(player_port2); + unplugged = true; + } + } + return unplugged; +} + +void cFEngJoyInput::HandleJoy() { + for (int port = 0; port < 2; port++) { + if (mActionQ[port] != nullptr) { + while (!mActionQ[port]->IsEmpty()) { + ActionRef aRef = mActionQ[port]->GetAction(); + if (aRef.ID() == ACTION_PLUGGED) { + bool is_splitscreen = false; + if (FEDatabase->IsSplitScreenMode()) { + is_splitscreen = FEDatabase->iNumPlayers == 2; + } + bool bIsSplit; + if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + bIsSplit = true; + } else if (!is_splitscreen) { + bIsSplit = false; + } else { + bIsSplit = true; + } + JoystickPort player_port2 = static_cast(-1); + JoystickPort player_port1 = static_cast(FEDatabase->GetPlayersJoystickPort(0)); + if (bIsSplit) { + player_port2 = static_cast(FEDatabase->GetPlayersJoystickPort(1)); + } + if (port == player_port1) { + if (bIsSplit && player_port2 != static_cast(-1)) { + JoyEnable(player_port2, false); + } + } else if (port == player_port2 && bIsSplit && player_port1 != static_cast(-1)) { + JoyEnable(player_port1, false); + } + JoyEnable(static_cast(port), false); + mActionQ[port]->PopAction(); + } else { + mActionQ[port]->IsEnabled(); + for (int j = 0; j < 16; j++) { + if (mActionQ[port]->IsConnected()) { + if (MapJoyEventToFEPad[j].eventID == aRef.ID()) { + MapJoyEventToFEPad[j].state[port] = static_cast(aRef.Data() + 0.5f); + if (!gKeyboardManager.IsCapturing()) { + if (aRef.ID() == FRONTENDACTION_BUTTON2) { + if (aRef.Data() == 1.0f) { + FEManager::Get()->SetEATraxSecondButton(); + } + } else if (aRef.ID() == FRONTENDACTION_BUTTON3) { + if (aRef.Data() == 1.0f) { + FEManager::Get()->SetEATraxFirstButton(true); + } else { + FEManager::Get()->SetEATraxFirstButton(false); + } + } + } + break; + } + } else { + MapJoyEventToFEPad[j].state[port] = 0; + } + } + mActionQ[port]->PopAction(); + } + } + } + } + CheckUnplugged(); +} + +unsigned long cFEngJoyInput::GetJoyPadMask(unsigned char pPadIndex) { + unsigned int buttons = 0; + for (int i = 0; i < 16; i++) { + if (MapJoyEventToFEPad[i].state[pPadIndex] != 0) { + buttons |= MapJoyEventToFEPad[i].padMask; + } + } + return buttons; +} + +void MUTEX_lock(MUTEX* m); +void MUTEX_unlock(MUTEX* m); + +void MyMutex::Lock() { + MUTEX_lock(&mMutex); +} + +void MyMutex::Unlock() { + MUTEX_unlock(&mMutex); +} + +int MyMutex::AddRef() { + return ++mRefcount; +} + +int MyMutex::Release() { + int ref = --mRefcount; + if (ref > 0) { + return ref; + } + if (this != nullptr) { + delete this; + } + return 0; +} + +IMutex* MyMutex::CreateInstance() { + return new MyMutex(); +} + +int MyThread::AddRef() { + return ++mRefcount; +} + +int MyThread::Release() { + int ref = --mRefcount; + if (ref > 0) { + return ref; + } + if (this != nullptr) { + delete this; + } + return 0; +} + +IThread* MyThread::CreateInstance() { + return new MyThread(); +} + +void THREAD_sleep(int ticks); +void THREAD_create(THREAD* thread, int (*func)(void*), void* param, void* stack, int stackSize, int priority); +void THREAD_waitexit(THREAD* thread, int status); +void THREAD_setpriority(THREAD* thread, int priority); +void THREAD_yield(int ticks); + +void MyThread::Sleep(int ticks) { + THREAD_sleep(ticks); +} + +void MyThread::Begin(int (*func)(void*)) { + mEntryFunc = func; + mStackBuffer = new char[mStackSize]; + THREAD_create(&mThreadData, EntryProc, this, mStackBuffer, mStackSize, mPriority); + mActive = true; +} + +void MyThread::WaitForEnd(int) { + THREAD_waitexit(&mThreadData, 0); + if (mStackBuffer != nullptr) { + delete[] static_cast< char* >(mStackBuffer); + } + mActive = false; +} + +void MyThread::SetPriority(int priority) { + mPriority = 0; + THREAD_setpriority(&mThreadData, 0); +} + +bool MyThread::IsActive() { return mActive; } + +int (*MyThread::GetEntryFunc())(void*) { return mEntryFunc; } + +int MyThread::EntryProc(void* pContext) { + MyThread* pThread = static_cast< MyThread* >(pContext); + while (!pThread->MyThread::IsActive()) { + THREAD_yield(1); + } + pThread->MyThread::GetEntryFunc()(pContext); + return 0; +} diff --git a/src/Speed/Indep/Src/Frontend/FEJoyInput.hpp b/src/Speed/Indep/Src/Frontend/FEJoyInput.hpp index a3e3d57ba..d962d596b 100644 --- a/src/Speed/Indep/Src/Frontend/FEJoyInput.hpp +++ b/src/Speed/Indep/Src/Frontend/FEJoyInput.hpp @@ -5,6 +5,35 @@ #pragma once #endif +#include +struct ActionQueue; + +enum JoystickPort { + kJP_Port0 = 0, + kJP_Port1 = 1, + kJP_Port2 = 2, + kJP_Port3 = 3, + kJP_NumPorts = 4 +}; + +// total size: 0x8 +struct cFEngJoyInput { + ActionQueue* mActionQ[2]; // offset 0x0 + + static cFEngJoyInput* mInstance; + + static cFEngJoyInput* Get() { return mInstance; } + cFEngJoyInput(); + void FlushActions(); + void JoyDisable(JoystickPort port, bool do_flush); + bool IsJoyPluggedIn(JoystickPort port); + void JoyEnable(JoystickPort port, bool do_flush); + bool IsJoyEnabled(JoystickPort port); + void SetRequiredJoy(JoystickPort port, bool required); + bool CheckUnplugged(); + void HandleJoy(); + unsigned long GetJoyPadMask(unsigned char pPadIndex); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/FEManager.cpp b/src/Speed/Indep/Src/Frontend/FEManager.cpp index e69de29bb..f3e0fadbd 100644 --- a/src/Speed/Indep/Src/Frontend/FEManager.cpp +++ b/src/Speed/Indep/Src/Frontend/FEManager.cpp @@ -0,0 +1,464 @@ +#include "FEManager.hpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEManager.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/FEGameInterface.h" +#include "Speed/Indep/Src/FEng/FEngine.h" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/Src/Frontend/FEPackageManager.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/Frontend/cFEngRender.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Generated/Messages/MControlPathfinder.h" +#include "Speed/Indep/Src/Input/IOModule.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Misc/EasterEggs.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/CarInfo.hpp" + +struct ChoppedMiniMapManager { + static void Init(); +}; + +struct FadeScreen { + static bool IsFadeScreenOn(); +}; + +extern bool DrawFEng; +extern int SummonChyronNow; +extern int DoScreenPrintf; +extern float RealTimeElapsed; + +void InitFEngMemoryPool(); +void InitChyron(); +void SummonChyron(char *, char *, char *); +void UpdateGarageCarLoaders(); +unsigned long FEngMapJoyportToJoyParam(int); + +struct LGWheels { + bool IsConnected(int channel); + void StopConstantForce(int channel); + void StopSurfaceEffect(int channel); + void StopDamperForce(int channel); + void StopCarAirborne(int channel); + void StopSlipperyRoadEffect(int channel); + void PlaySpringForce(int channel, char offset, unsigned char saturation, short coefficient); +}; + +struct SteeringWheelDevice { + static LGWheels *lgwheels; +}; + +void SteeringWheels_StopAllForces(); + +FEManager::FEManager() + : bSuppressControllerError(false) // + , bAllowControllerError(false) // + , mFirstScreen(nullptr) // + , mFirstScreenArg(0) // + , mFirstScreenMask(0xFF) // + , mGarageType(GARAGETYPE_NONE) // + , mPreviousGarageType(GARAGETYPE_NONE) // + , mGarageBackground(nullptr) // + , mFirstBoot(true) // + , mEATraxDelay(0) // + , mEATraxFirstButton(false) { + for (int port = 0; port < 8; port++) { + bWantControllerError[port] = false; + } +} + +void FEManager::Init() { + if (!mInstance) { + mInstance = new FEManager; + } + InitFEngMemoryPool(); + LoadingScreen::InitLoadingScreen(); + LoadingTips::InitLoadingTipsScreen(); + LoadingControllerScreen::InitLoadingControllerScreen(); + InitChyron(); + cFEngGameInterface::pInstance = new cFEngGameInterface; + ChoppedMiniMapManager::Init(); + cFEng::Init(); + cFEngRender::mInstance = new cFEngRender; + FEManager::Get()->SetGarageType(GARAGETYPE_MAIN_FE); + uiRepSheetRivalFlow::Init(); +} + +void FEManager::InitInput() { + cFEngJoyInput::mInstance = new cFEngJoyInput(); +} + +void FEManager::Destroy() { +} + +FEManager *FEManager::Get() { + return mInstance; +} + +eGarageType FEManager::GetGarageType() { + return mGarageType; +} + +void FEManager::SetGarageType(eGarageType pGarageType) { + mPreviousGarageType = GetGarageType(); + mGarageType = pGarageType; +} + +const char *FEManager::GetGarageNameFromType() { + eGarageType garageTypeToUse = mGarageType; + switch (garageTypeToUse) { + case GARAGETYPE_NONE: + return ""; + case GARAGETYPE_MAIN_FE: + return "FRONTEND\\PLATFORMS\\PLATFORMCRIB.BIN"; + case GARAGETYPE_CAREER_SAFEHOUSE: + return "FRONTEND\\PLATFORMS\\CAREER_SAFEHOUSE.BIN"; + case GARAGETYPE_CUSTOMIZATION_SHOP: + return "FRONTEND\\PLATFORMS\\CUSTOMIZATION_SHOP.BIN"; + case GARAGETYPE_CUSTOMIZATION_SHOP_BACKROOM: + return "FRONTEND\\PLATFORMS\\CUSTOMIZATION_SHOP_BACKROOM.BIN"; + case GARAGETYPE_CAR_LOT: + return "FRONTEND\\PLATFORMS\\CAR_LOT.BIN"; + default: + return ""; + } +} + +const char *FEManager::GetGaragePrefixFromType(eGarageType pGarageType) { + switch (pGarageType) { + case GARAGETYPE_NONE: + return ""; + case GARAGETYPE_MAIN_FE: + return "QRACE"; + case GARAGETYPE_CAREER_SAFEHOUSE: + return "CAREER"; + case GARAGETYPE_CUSTOMIZATION_SHOP: + case GARAGETYPE_CUSTOMIZATION_SHOP_BACKROOM: + return "CSHOP"; + case GARAGETYPE_CAR_LOT: + return "CARLOT"; + default: + return ""; + } +} + +bool FEManager::IsOkayToRequestPauseSimulation(int playerIndex, bool useControllerErrors, + bool okIfAutoSaveActive) { + if (TheGameFlowManager.GetState() != GAMEFLOW_STATE_RACING) { + return false; + } + + if (cFEng::Get()->IsPackagePushed("InGamePhotoMaster.fng")) { + return false; + } + + if (MemoryCard::GetInstance()->IsAutoSaving() && !okIfAutoSaveActive) { + return false; + } + + if (FadeScreen::IsFadeScreenOn()) { + return false; + } + + if (cFEng::Get()->IsPackagePushed("FadeScreenNoLoadingBar.fng")) { + return false; + } + + if (GRaceStatus::Exists()) { + ISimable *simable = + IPlayer::GetList(PLAYER_LOCAL)[static_cast< unsigned int >(playerIndex)]->GetSimable(); + GRacerInfo *racerInfo; + if (simable) { + racerInfo = GRaceStatus::Get().GetRacerInfo(simable); + } else { + racerInfo = nullptr; + } + + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Racing) { + if (!GRaceStatus::Get().GetIsTimeLimited() || + GRaceStatus::Get().GetRaceTimeRemaining() > 0.0f) { + if (!racerInfo || + (!racerInfo->GetIsEngineBlown() && !racerInfo->GetIsTotalled() && + !racerInfo->GetIsKnockedOut() && !racerInfo->IsFinishedRacing())) { + goto done; + } + + if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + ISimable *other_simable = + IPlayer::GetList(PLAYER_LOCAL)[static_cast< unsigned int >(playerIndex != 1)] + ->GetSimable(); + GRacerInfo *other_racerInfo; + if (other_simable) { + other_racerInfo = GRaceStatus::Get().GetRacerInfo(other_simable); + } else { + other_racerInfo = nullptr; + } + if (!other_racerInfo || + (!other_racerInfo->GetIsEngineBlown() && + !other_racerInfo->GetIsTotalled() && + !other_racerInfo->GetIsKnockedOut() && + !other_racerInfo->IsFinishedRacing())) { + goto done; + } + } + } + return false; + } + + if (simable) { + IVehicle *vehicle; + if (simable->QueryInterface(&vehicle)) { + IVehicleAI *vehicleai = vehicle->GetAIVehiclePtr(); + if (vehicleai) { + IPursuit *ipursuit = vehicleai->GetPursuit(); + if (ipursuit && ipursuit->ShouldEnd()) { + return false; + } + } + } + } + } + +done: + return !ShouldPauseSimulation(useControllerErrors); +} + +bool FEManager::ShouldPauseSimulation(bool useControllerErrors) { + if (!mInstance->bSuppressControllerError && mInstance->WaitingForControllerError() && + useControllerErrors && !UTL::Collections::Singleton::Get() && !gMoviePlayer) { + return true; + } + return mPauseRequest != 0; +} + +void FEManager::RequestPauseSimulation(const char *reason) { + mPauseReason[mPauseRequest++] = reason; +} + +void FEManager::RequestUnPauseSimulation(const char *reason) { + mPauseRequest--; +} + +void FEManager::WantControllerError(int port) { + if (port == -1) { + return; + } + + if (TheGameFlowManager.IsInGame() && + (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode())) { + ISimable *simable = IPlayer::First(PLAYER_LOCAL)->GetSimable(); + GRacerInfo *racerInfo; + if (simable) { + racerInfo = GRaceStatus::Get().GetRacerInfo(simable); + } else { + racerInfo = nullptr; + } + if (racerInfo) { + IPlayer *player = racerInfo->GetSimable()->GetPlayer(); + if (player->GetHud()) { + ICountdown *icountdown; + if (player->GetHud()->QueryInterface(&icountdown) && icountdown->IsActive()) { + return; + } + } + } + } + + bWantControllerError[port] = true; +} + +bool FEManager::WaitingForControllerError() { + for (int port = 0; port < 8; port++) { + if (bWantControllerError[port]) { + return true; + } + } + return false; +} + +void FEManager::StartFE() { + if (mFirstBoot) { + mFirstBoot = false; + BootFlowManager::Init(); + } else { + g_pEAXSound->PlayFEMusic(-1); + if (!CarViewer::haveLoadedOnce) { + RideInfo ride; + CarViewer::ShowCarScreen(); + FEDatabase->BuildCurrentRideForPlayer(0, &ride); + CarViewer::SetRideInfo(&ride, SET_RIDE_INFO_REASON_CATCHALL, eCARVIEWER_PLAYER1_CAR); + CarViewer::haveLoadedOnce = true; + } + CarViewer::ShowAllCars(); + } + + bSuppressControllerError = false; + bAllowControllerError = false; + for (int port = 0; port < 8; port++) { + bWantControllerError[port] = false; + } + mPauseRequest = 0; + cFEng::Get()->QueuePackagePush(mFirstScreen, mFirstScreenArg, mFirstScreenMask, false); +} + +void FEManager::StopFE() { + cFEngJoyInput::mInstance->JoyDisable(kJP_NumPorts, true); + FEPackageManager::Get()->CloseAllPackages(0); + BootFlowManager::Destroy(); + mEATraxDelay = 0; +} + +void FEManager::Render() { + if (DrawFEng) { + cFEng::Get()->DrawForeground(); + } +} + +void SteeringWheels_StopAllForces() { + if (SteeringWheelDevice::lgwheels != nullptr) { + for (int mDeviceIndex = 0; mDeviceIndex < 2; mDeviceIndex++) { + if (SteeringWheelDevice::lgwheels->IsConnected(mDeviceIndex)) { + SteeringWheelDevice::lgwheels->StopConstantForce(mDeviceIndex); + SteeringWheelDevice::lgwheels->StopSurfaceEffect(mDeviceIndex); + SteeringWheelDevice::lgwheels->StopDamperForce(mDeviceIndex); + SteeringWheelDevice::lgwheels->StopCarAirborne(mDeviceIndex); + SteeringWheelDevice::lgwheels->StopSlipperyRoadEffect(mDeviceIndex); + SteeringWheelDevice::lgwheels->PlaySpringForce(mDeviceIndex, 0, 200, 200); + } + } + } +} + +int GetPortsPlayer(int port) { + if (FEDatabase->GetPlayersJoystickPort(0) != -1 && + FEDatabase->GetPlayersJoystickPort(0) == port) { + return 0; + } + if (FEDatabase->GetPlayersJoystickPort(1) != -1 && + FEDatabase->GetPlayersJoystickPort(1) == port) { + return 1; + } + return -1; +} + +void FEManager::UpdateJoyInput() { + if (cFEngJoyInput::mInstance) { + cFEngJoyInput::mInstance->CheckUnplugged(); + } +} + +void FEManager::Update() { + if (MemoryCard::GetInstance()) { + MemoryCard::GetInstance()->Tick(static_cast< int >(RealTimeElapsed * 1000.0f)); + } + + if (!Sim::Exists() || (Sim::Exists() && Sim::GetState() != Sim::STATE_ACTIVE) || + UTL::Collections::Singleton::Get()) { + SteeringWheels_StopAllForces(); + } + + if (cFEngJoyInput::mInstance) { + cFEngJoyInput::mInstance->HandleJoy(); + } + + int port; + for (port = 0; port < 8; port++) { + if (bWantControllerError[port]) { + if ((!UTL::Collections::Singleton::Get() && !gMoviePlayer) || + bAllowControllerError) { + if (!bSuppressControllerError) { + if (TheGameFlowManager.IsInGame() && FEManager::IsPaused()) { + FEManager *feManager = FEManager::Get(); + JoystickPort player_port1 = + static_cast< JoystickPort >(FEDatabase->GetPlayersJoystickPort(0)); + feManager->ClearControllerError(static_cast< int >(player_port1)); + JoystickPort player_port2 = + static_cast< JoystickPort >(FEDatabase->GetPlayersJoystickPort(1)); + feManager->ClearControllerError(static_cast< int >(player_port2)); + } + + int maxPort = IOModule::GetIOModule().GetNumDevices(); + for (int p = 0; p < maxPort; p++) { + InputDevice *device = IOModule::GetIOModule().GetDevice(p); + if (device) { + device->PollDevice(); + } + } + + if (!cFEng::Get()->IsPackagePushed("ControllerUnplugged.fng")) { + cFEng *eng = cFEng::Get(); + unsigned long joyParam = FEngMapJoyportToJoyParam(port); + eng->PushErrorPackage("ControllerUnplugged.fng", port, joyParam); + } + } + } + break; + } + } + + cFEng::Get()->Service(); + + if (cFEng::Get()->IsErrorState()) { + FEPackageManager::Get()->ErrorTick(); + } else { + FEPackageManager::Get()->Tick(); + + if (TheGameFlowManager.IsInFrontend()) { + UpdateGarageCarLoaders(); + } + + if (DoScreenPrintf && !TheICEManager.IsEditorOn()) { + FEPackage *pCurrentPkgWithControl = cFEng::Get()->FindPackageWithControl(); + if (pCurrentPkgWithControl) { + pCurrentPkgWithControl->GetName(); + } + } + + gEasterEggs.HandleJoy(); + + if (gMoviePlayer) { + gMoviePlayer->Update(); + } + + if (SummonChyronNow) { + SummonChyron(0, 0, 0); + SummonChyronNow = 0; + } else { + if (mEATraxDelay >= 0) { + mEATraxDelay--; + if (mEATraxDelay == 0) { + SummonChyron(0, 0, 0); + } + } + } + } +} + +void FEManager::SetEATraxSecondButton() { + if (gMoviePlayer && static_cast< unsigned int >(gMoviePlayer->GetStatus() - 3) < 3) { + return; + } + + if (!cFEng::Get()->IsPackagePushed("EA_Trax_Jukebox.fng") && + TheGameFlowManager.IsInFrontend()) { + MControlPathfinder msg(true, 0xffffffff, 0, 0); + msg.Send(UCrc32("Pathfinder5")); + } +} + +void FEManager::ExitOnlineGameplayBasedOnConnection() { +} diff --git a/src/Speed/Indep/Src/Frontend/FEManager.hpp b/src/Speed/Indep/Src/Frontend/FEManager.hpp index 958744d60..b8f0e6dff 100644 --- a/src/Speed/Indep/Src/Frontend/FEManager.hpp +++ b/src/Speed/Indep/Src/Frontend/FEManager.hpp @@ -64,33 +64,46 @@ class FEManager { void ExitOnlineGameplayBasedOnConnection(); - // void SetFirstScreen(const char *pPackageName, int arg, unsigned int controlMask) {} + void SetFirstScreen(const char *pPackageName, int arg, unsigned int controlMask) { + mFirstScreen = pPackageName; + mFirstScreenArg = arg; + mFirstScreenMask = controlMask; + } - // void RequestBootFlow() {} + void RequestBootFlow() { mFirstBoot = true; } - // eGarageType GetPreviousGarageType() {} + eGarageType GetPreviousGarageType() { return mPreviousGarageType; } - // ResourceFile *GetGarageBackground() {} + ResourceFile *GetGarageBackground() { return mGarageBackground; } - // void SetGarageBackground(ResourceFile *pBackground) {} + void SetGarageBackground(ResourceFile *pBackground) { mGarageBackground = pBackground; } - // void SetEATraxFirstButton(bool onOff) {} + void SetEATraxFirstButton(bool onOff) { mEATraxFirstButton = onOff; } - // static bool IsPaused() {} + static inline bool IsPaused() { return mInstance->mPauseRequest > 0; } - // static int GetNumPauseRequests() {} + static int GetNumPauseRequests() { return mPauseRequest; } // static const char *GetPauseReason(int idx) {} - // void ClearControllerError(int port) {} + void ClearControllerError(int port) { + if (port == -1) return; + if (port == 4) { + for (int i = 0; i <= 7; i++) { + bWantControllerError[i] = false; + } + } else { + bWantControllerError[port] = false; + } + } - // void SuppressControllerError(bool b) {} + void SuppressControllerError(bool b) { bSuppressControllerError = b; } - // void AllowControllerError(bool b) {} + void AllowControllerError(bool b) { bAllowControllerError = b; } - // bool IsAllowingControllerError() {} + bool IsAllowingControllerError() { return bAllowControllerError; } - // bool IsFirstBoot() {} + bool IsFirstBoot() { return mFirstBoot; } // ~FEManager() {} @@ -113,4 +126,31 @@ class FEManager { bool mEATraxFirstButton; // offset 0x48, size 0x1 }; +struct RideInfo; + +enum eSetRideInfoReasons { + SET_RIDE_INFO_REASON_VINYL = 0, + SET_RIDE_INFO_REASON_LOAD_CAR = 1, + SET_RIDE_INFO_REASON_CATCHALL = 2, +}; + +enum eCarViewerWhichCar { + eCARVIEWER_PLAYER1_CAR = 0, + eCARVIEWER_PLAYER2_CAR = 1, +}; + +struct GarageMainScreen; + +struct CarViewer { + static GarageMainScreen *FindWhichScreenToUpdate(eCarViewerWhichCar which_car); + static void SetRideInfo(RideInfo *ride, eSetRideInfoReasons reason, eCarViewerWhichCar which_car); + static void CancelCarLoad(eCarViewerWhichCar which_car); + static RideInfo *GetRideInfo(eCarViewerWhichCar which_car); + static void HideAllCars(); + static void ShowAllCars(); + static void ShowCarScreen(); + static void UnshowCarScreen(); + static bool haveLoadedOnce; +}; + #endif diff --git a/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp b/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp index e69de29bb..c9c7856c8 100644 --- a/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp +++ b/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.cpp @@ -0,0 +1,196 @@ +#include "Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp" + +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/Frontend/cFEngRender.hpp" +#include "Speed/Indep/Src/Misc/BuildRegion.hpp" + +namespace BuildRegion { +bool IsPal(); +} + +extern int FEngStrICmp(const char*, const char*); +extern int FEngSNPrintf(char*, int, const char*, ...); +extern int bSPrintf(char*, const char*, ...); +extern const char* GetLanguageName(eLanguages language); +extern int SkipMovies; +extern bool IsSoundEnabled; +extern int GetCurrentLanguage(); +extern int bFileExists(const char* f); +extern char* bStrNCpy(char* to, const char* from, int m); +extern void FEngSetVisible(FEObject* obj); +extern void FEngSetInvisible(FEObject* obj); + +struct MovieNameMap { + const char* movieName; + int movieId; +}; + +static MovieNameMap sMovieNameMap[42]; + +static int GetMovieNameEnum(const char* movieName) { + for (int i = 0; i < 42; i++) { + if (FEngStrICmp(movieName, sMovieNameMap[i].movieName) == 0) { + return sMovieNameMap[i].movieId; + } + } + return -1; +} + +static void CalculateMovieFilename(char* buffer, int bufsize, const char* basename, + eLanguages cur_language) { + const char* extension; + const char* prefix = ""; + char language[64]; + const char* pal_or_ntsc; + + if (!BuildRegion::IsPal()) { + pal_or_ntsc = "_ntsc"; + } else { + pal_or_ntsc = "_pal"; + } + + bSPrintf(language, "_%s", GetLanguageName(cur_language)); + extension = ".vp6"; + FEngSNPrintf(buffer, bufsize, "%sMOVIES\\%s%s%s%s", prefix, basename, language, pal_or_ntsc, + extension); +} + + +bool FEngMovieStopper::Callback(FEObject* obj) { + if (obj->Type == 7) { + if (gMoviePlayer != nullptr) { + gMoviePlayer->Stop(); + } + MoviePlayer_ShutDown(); + return false; + } + return true; +} + +bool FEngHidePCObjects::Callback(FEObject* obj) { + if (obj->Flags & 0x8) { + FEngSetInvisible(obj); + if (obj->Flags & 0x10000000) { + obj->Flags &= ~0x10000000; + } + obj->Flags |= 0x00400000; + } + return true; +} + +bool RenderObjectDisconnect::Callback(FEObject* pObj) { + pFEngRenderer->RemoveCachedRender(pObj, PkgRenderInfo); + return true; +} + +bool ObjectDirtySetter::Callback(FEObject* obj) { + obj->Flags |= 0x00400000; + cFEngRender::mInstance->RemoveCachedRender(obj, pRenderInfo); + return true; +} + +bool ObjectVisibilitySetter::Callback(FEObject* obj) { + if (Visible) { + FEngSetVisible(obj); + } else { + FEngSetInvisible(obj); + } + return true; +} + +bool FEngMovieStarter::Callback(FEObject* obj) { + if (obj->Type == FE_Movie) { + if (SkipMovies) { + cFEng::Get()->QueueGameMessagePkg(0xc3960eb9, pPackage); + } + + const char* movie_name = reinterpret_cast(obj->Handle); + char buffer[64]; + int movieID = GetMovieNameEnum(movie_name); + eLanguages lang = static_cast(GetCurrentLanguage()); + CalculateMovieFilename(buffer, 0x40, movie_name, lang); + + if (GetCurrentLanguage() != 0 && !bFileExists(buffer)) { + CalculateMovieFilename(buffer, 0x40, movie_name, eLANGUAGE_ENGLISH); + } + + if (bFileExists(buffer)) { + MoviePlayer_StartUp(); + { + MoviePlayer::Settings settings; + settings.bufferSize = 0x40000; + settings.activeController = 0; + settings.preload = false; + settings.volume = 0; + settings.filename[0] = '\0'; + settings.sound = IsSoundEnabled != 0; + settings.loop = false; + settings.pal = false; + settings.type = 0; + settings.movieId = 0; + bStrNCpy(settings.filename, buffer, 0x100); + settings.loop = true; + settings.movieId = movieID; + settings.type = 0; + gMoviePlayer->Init(settings); + } + MoviePlayer_Play(); + } else { + cFEng::Get()->QueueGameMessagePkg(0xc3960eb9, pPackage); + } + + return false; + } + return true; +} + +bool FEngTransferFlagsToChildren::Callback(FEObject* obj) { + if ((obj->Flags & FlagToTransfer) && obj->Type == FE_Group) { + FEGroup* group = static_cast(obj); + FEObject* child = group->GetFirstChild(); + int num = group->GetNumChildren(); + for (int i = 0; i < num; i++) { + child->Flags |= FlagToTransfer; + Callback(child); + child = child->GetNext(); + } + } + return true; +} + +static char* GetBaseName(char* dest, const char* filename) { + long x = 0; + long first = 0; + long last; + + while (filename[x] != '\0') { + int c = filename[x]; + x++; + if (c == '\\' || c == '/') { + first = x; + } + } + + last = x; + if (x != 0) { + if (filename[x] != '.') { + while (--x != 0) { + if (filename[x] == '.') { + last = x; + break; + } + } + } + } + + long y = 0; + for (x = first; x < last; x++) { + dest[y] = filename[x]; + y++; + } + dest[y] = '\0'; + return dest; +} diff --git a/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp b/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp index c10667a7d..3a9325c19 100644 --- a/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp +++ b/src/Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp @@ -5,6 +5,74 @@ #pragma once #endif +#include "Speed/Indep/Src/FEng/FEObjectCallback.h" +struct FEPackage; +struct FEPackageRenderInfo; +struct cFEngRender; + +// total size: 0x8 +struct FEngMovieStarter : public FEObjectCallback { + FEPackage* pPackage; // offset 0x4 + + inline FEngMovieStarter(FEPackage* pkg) : pPackage(pkg) {} + bool Callback(FEObject* obj) override; + ~FEngMovieStarter() override {} +}; + +// total size: 0x4 +struct FEngMovieStopper : public FEObjectCallback { + inline FEngMovieStopper() {} + bool Callback(FEObject* obj) override; + ~FEngMovieStopper() override {} +}; + +// total size: 0x4 +struct FEngHidePCObjects : public FEObjectCallback { + inline FEngHidePCObjects() {} + bool Callback(FEObject* obj) override; + ~FEngHidePCObjects() override {} +}; + +// total size: 0x8 +struct FEngTransferFlagsToChildren : public FEObjectCallback { + int FlagToTransfer; // offset 0x4 + + inline FEngTransferFlagsToChildren(int flag) : FlagToTransfer(flag) {} + bool Callback(FEObject* obj) override; + ~FEngTransferFlagsToChildren() override {} +}; + +// total size: 0xC +struct RenderObjectDisconnect : public FEObjectCallback { + FEPackageRenderInfo* PkgRenderInfo; // offset 0x4 + cFEngRender* pFEngRenderer; // offset 0x8 + + inline RenderObjectDisconnect() {} + inline RenderObjectDisconnect(FEPackageRenderInfo* ri, cFEngRender* r) + : PkgRenderInfo(ri) // + , pFEngRenderer(r) {} + bool Callback(FEObject* obj) override; + ~RenderObjectDisconnect() override {} +}; + +// total size: 0x8 +struct ObjectDirtySetter : public FEObjectCallback { + FEPackageRenderInfo* pRenderInfo; // offset 0x4 + + inline ObjectDirtySetter() {} + inline ObjectDirtySetter(FEPackageRenderInfo* ri) : pRenderInfo(ri) {} + bool Callback(FEObject* obj) override; + ~ObjectDirtySetter() override {} +}; + +// total size: 0x8 +struct ObjectVisibilitySetter : public FEObjectCallback { + bool Visible; // offset 0x4 + + inline ObjectVisibilitySetter(bool vis) : Visible(vis) {} + bool Callback(FEObject* obj) override; + ~ObjectVisibilitySetter() override {} +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/FEPackageData.cpp b/src/Speed/Indep/Src/Frontend/FEPackageData.cpp index e69de29bb..ab95653b0 100644 --- a/src/Speed/Indep/Src/Frontend/FEPackageData.cpp +++ b/src/Speed/Indep/Src/Frontend/FEPackageData.cpp @@ -0,0 +1,997 @@ +#include "Speed/Indep/Src/Frontend/FEPackageData.hpp" +#include "Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp" +#include "Speed/Indep/Src/Frontend/cFEngRender.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.hpp" +#include "Speed/Indep/Src/Input/ActionQueue.h" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/fengine_full.h" +#include "Speed/Indep/Src/Misc/LZCompress.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/bMemory.hpp" +#include "Speed/Indep/Src/Generated/Events/ESndGameState.hpp" +#include "Speed/Indep/Src/Generated/Messages/MAcceptEnterCareerEvent.h" +#include "Speed/Indep/Src/Generated/Messages/MDeclineEnterCareerEvent.h" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp" +#include "Speed/Indep/Src/Frontend/SubTitle.hpp" +#include "Speed/Indep/Src/Misc/EasterEggs.hpp" +#include "Speed/Indep/Src/Generated/Messages/MControlPathfinder.h" + +extern void SetSoundControlState(bool set, eSNDCTLSTATE state, const char *name); +extern int GetVideoMode(); +static int IsDebugPlayMovie; + +struct FEMovie; +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +extern void FEngSetVisible(FEObject *pObject); +extern void FEngSetMovieName(FEMovie *movie, const char *name); +extern int bStrICmp(const char *a, const char *b); + +struct MovieEntry { + const char *package_name; + const char *movie_name; + unsigned int fng_obj_hash; + int sound_state; + int has_subtitle; +}; + +static MovieEntry MovieData[10] = { + {"LS_EALogo.fng", "ealogo", 0x58BCF5B6, 0, 0}, + {"LS_EA_hidef.fng", "eahd_bumper", 0x58BCF5B6, 0, 0}, + {"LS_PSA.fng", "psa", 0x58BCF5B6, 1, 1}, + {"MW_LS_IntroFMV.fng", "intro_movie", 0x72CF9F38, 1, 0}, + {"MW_LS_AttractFMV.fng", "attract_movie", 0x72CF9F38, 1, 0}, + {"WS_LS_EALogo.fng", "ealogo", 0x58BCF5B6, 0, 0}, + {"WS_LS_EA_hidef.fng", "eahd_bumper", 0x58BCF5B6, 0, 0}, + {"WS_LS_PSA.fng", "psa", 0x58BCF5B6, 1, 1}, + {"WS_LS_IntroFMV.fng", "intro_movie", 0x58BCF5B6, 1, 0}, + {"WS_MW_LS_AttractFMV.fng", "attract_movie", 0x72CF9F38, 1, 0}, +}; + +static const char* gLoadinScreenPackageName; + +void SetLoadingScreenPackageName(const char* name) { + gLoadinScreenPackageName = name; +} + +const char* GetLoadingScreenPackageName() { + return gLoadinScreenPackageName; +} + +struct ScreenButtonDatum { + unsigned int ScreenHash; // offset 0x0, size 0x4 + unsigned char LastButton; // offset 0x4, size 0x1 + unsigned int GameMode; // offset 0x8, size 0x4 +}; + +extern unsigned long FEHashUpper(const char *str); +extern int bStrICmp(const char *, const char *); +extern void HackClearCache(FEPackage *pkg); +extern FEPackageRenderInfo *HACK_FEPkgMgr_GetPackageRenderInfo(FEPackage *pkg); +extern cFEng *cFEng_mInstance; + +struct ScreenFactoryDatum; +static ScreenFactoryDatum *FindScreenCreateData(unsigned int hash); +static MenuScreen *CreateCustomTuningScreen(ScreenConstructorData *sd); + +static ScreenButtonDatum ScreenButtonData[0x32]; + +static ScreenButtonDatum *FindAvailableButtonDatum() { + for (int i = 0; i <= 0x31; i++) { + if (ScreenButtonData[i].ScreenHash == 0) { + return &ScreenButtonData[i]; + } + } + return nullptr; +} + +ScreenButtonDatum *FindScreenButtonDatum(unsigned int screen_filename_hash) { + cFrontendDatabase *db = FEDatabase; + for (int i = 0; i <= 0x31; i++) { + if (screen_filename_hash == ScreenButtonData[i].ScreenHash) { + if (ScreenButtonData[i].GameMode == 0xFFFFFFFF || + db->MatchesGameMode(ScreenButtonData[i].GameMode)) { + return &ScreenButtonData[i]; + } + } + } + return nullptr; +} + +unsigned char FEngGetLastButton(const char *pkg_name) { + ScreenButtonDatum *sd = FindScreenButtonDatum(FEHashUpper(pkg_name)); + if (!sd) { + return 0; + } + return sd->LastButton; +} + +void FEngSetLastButton(const char *pkg_name, unsigned char button_hash) { + unsigned int hash = FEHashUpper(pkg_name); + ScreenButtonDatum *sd = FindScreenButtonDatum(hash); + if (sd) { + sd->LastButton = button_hash; + return; + } + ScreenButtonDatum *avail = FindAvailableButtonDatum(); + if (avail) { + avail->ScreenHash = FEHashUpper(pkg_name); + avail->LastButton = button_hash; + avail->GameMode = FEDatabase->GetGameMode(); + } +} + +struct UIMain : MenuScreen { UIMain(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x144]; }; +struct UIOptionsScreen : MenuScreen { UIOptionsScreen(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x130]; }; +struct UIQRBrief : MenuScreen { UIQRBrief(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x10C]; }; +struct UIQRTrackSelect : MenuScreen { UIQRTrackSelect(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xF4]; }; +struct UIQRTrackOptions : MenuScreen { UIQRTrackOptions(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x128]; }; +struct UIQRCarSelect : MenuScreen { UIQRCarSelect(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x18C]; }; +struct uiQRPressStart : MenuScreen { uiQRPressStart(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x8]; }; +struct UIQRChallengeSeries : MenuScreen { UIQRChallengeSeries(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1A4]; }; +struct Showcase : MenuScreen { Showcase(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x44]; }; +struct WorldMap : MenuScreen { WorldMap(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x170]; }; +struct uiSMS : MenuScreen { uiSMS(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xD4]; }; +struct uiSMSMessage : MenuScreen { uiSMSMessage(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xBC]; }; +struct ControllerUnplugged : MenuScreen { ControllerUnplugged(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x4]; }; +struct UISafehouseRaceSheet : MenuScreen { UISafehouseRaceSheet(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1A8]; }; +struct uiRapSheetLogin : MenuScreen { uiRapSheetLogin(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x8]; }; +struct uiRapSheetMain : MenuScreen { uiRapSheetMain(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x110]; }; +struct uiRapSheetRS : MenuScreen { uiRapSheetRS(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} }; +struct uiRapSheetUS : MenuScreen { uiRapSheetUS(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xC0]; }; +struct uiRapSheetVD : MenuScreen { uiRapSheetVD(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xBC]; }; +struct uiRapSheetCTS : MenuScreen { uiRapSheetCTS(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xBC]; }; +struct uiRapSheetTEP : MenuScreen { uiRapSheetTEP(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x114]; }; +struct uiRapSheetPD : MenuScreen { uiRapSheetPD(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x4]; }; +struct uiRapSheetRankings : MenuScreen { uiRapSheetRankings(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x8]; }; +struct uiRapSheetRankingsDetail : MenuScreen { uiRapSheetRankingsDetail(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xC4]; }; +struct uiRepSheetMain : MenuScreen { uiRepSheetMain(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x194]; }; +struct uiRepSheetRival : MenuScreen { uiRepSheetRival(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x60]; }; +struct uiRepSheetRivalBio : MenuScreen { uiRepSheetRivalBio(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x4C]; }; +struct uiRepSheetMilestones : MenuScreen { uiRepSheetMilestones(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xC8]; }; +struct uiSafehouseRegionUnlock : MenuScreen { uiSafehouseRegionUnlock(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x48]; }; +struct uiRepSheetBounty : MenuScreen { uiRepSheetBounty(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x14C]; }; +struct FEMarkerSelection : MenuScreen { FEMarkerSelection(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x94]; }; +struct FEGameWonScreen : MenuScreen { FEGameWonScreen(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} }; +struct DebugCarCustomizeScreen : MenuScreen { DebugCarCustomizeScreen(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x54]; }; +struct MyCarsManager : MenuScreen { MyCarsManager(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x194]; }; +struct CustomizeMain : MenuScreen { CustomizeMain(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1A8]; }; +struct CustomizeSub : MenuScreen { CustomizeSub(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1AC]; }; +struct CustomizeShoppingCart : MenuScreen { CustomizeShoppingCart(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x15C]; }; +struct CustomizeParts : MenuScreen { CustomizeParts(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1C8]; }; +struct CustomizeHUDColor : MenuScreen { CustomizeHUDColor(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1CC]; }; +struct CustomizeDecals : MenuScreen { CustomizeDecals(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1BC]; }; +struct CustomizeNumbers : MenuScreen { CustomizeNumbers(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x8C]; }; +struct CustomizePaint : MenuScreen { CustomizePaint(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x2F8]; }; +struct CustomizeRims : MenuScreen { CustomizeRims(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1C4]; }; +struct CustomizeSpoiler : MenuScreen { CustomizeSpoiler(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1CC]; }; +struct CustomizePerformance : MenuScreen { CustomizePerformance(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x29C]; }; +struct uiCredits : MenuScreen { uiCredits(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x1C]; }; +struct UIEATraxScreen : MenuScreen { UIEATraxScreen(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0xF4]; }; +struct UIOptionsController : MenuScreen { UIOptionsController(ScreenConstructorData *); void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override {} char _pad[0x128]; }; +// nsEngageEventDialog::EngageEventDialog defined in FEPkg_EngageEventDialog.cpp (jumbo line 89) +struct MovieScreen : MenuScreen { + MovieScreen(ScreenConstructorData *); + ~MovieScreen() override; + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + int mSoundState; // offset 0x2C + SubTitler mSubtitler; // offset 0x30 +}; +struct SplashScreen : MenuScreen { + SplashScreen(ScreenConstructorData *); + ~SplashScreen() override; + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + Timer CalculateLastJoyEventTime(); + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override { + if (bAllowContinue) { + return maybe; + } + return static_cast(-1); + } + bool bAllowContinue; + Timer CopyrightNotice; + Timer SplashStartedTimer; +}; + +static MenuScreen *CreateMainMenu(ScreenConstructorData *sd) { + return new ("", 0) UIMain(sd); +} + +static MenuScreen *CreateSubMenu(ScreenConstructorData *sd) { + if (FEDatabase->IsOptionsMode()) { + if (FEDatabase->GetOptionsSettings()->CurrentCategory == OC_TRAILERS) { + return new ("", 0) UIOptionsTrailers(sd); + } + return new ("", 0) UIOptionsMain(sd); + } + if (FEDatabase->IsCareerMode()) { + return new ("", 0) uiCareerCrib(sd); + } + if (FEDatabase->IsCareerManagerMode()) { + return new ("", 0) uiCareerManager(sd); + } + if (FEDatabase->IsModeSelectMode()) { + return new ("", 0) UIQRModeSelect(sd); + } + if (FEDatabase->IsQuickRaceMode()) { + return new ("", 0) UIQRMainMenu(sd); + } + return new ("", 0) UIOptionsMain(sd); +} + +static MenuScreen *CreateCommonPauseMenu(ScreenConstructorData *sd) { + if (FEDatabase->IsOptionsMode()) { + return new ("", 0) UIOptionsMain(sd); + } + return new ("", 0) PauseMenu(sd); +} + +static MenuScreen *CreateOptionsScreen(ScreenConstructorData *sd) { + return new ("", 0) UIOptionsScreen(sd); +} + +static MenuScreen *CreateQRBrief(ScreenConstructorData *sd) { + return new ("", 0) UIQRBrief(sd); +} + +static MenuScreen *CreateQRTrackSelect(ScreenConstructorData *sd) { + return new ("", 0) UIQRTrackSelect(sd); +} + +static MenuScreen *CreateQRTrackOptions(ScreenConstructorData *sd) { + return new ("", 0) UIQRTrackOptions(sd); +} + +static MenuScreen *CreateQRCarSelect(ScreenConstructorData *sd) { + return new ("", 0) UIQRCarSelect(sd); +} + +static MenuScreen *CreateQRPressStart(ScreenConstructorData *sd) { + return new ("", 0) uiQRPressStart(sd); +} + +static MenuScreen *CreateQRChallengeSeries(ScreenConstructorData *sd) { + return new ("", 0) UIQRChallengeSeries(sd); +} + +static MenuScreen *CreateShowcase(ScreenConstructorData *sd) { + return new ("", 0) Showcase(sd); +} + +static MenuScreen *CreateFadeScreen(ScreenConstructorData *sd) { + return new ("", 0) FadeScreen(sd); +} + +static MenuScreen *CreateWorldMap(ScreenConstructorData *sd) { + return new ("", 0) WorldMap(sd); +} + +static MenuScreen *CreateSMS(ScreenConstructorData *sd) { + return new ("", 0) uiSMS(sd); +} + +static MenuScreen *CreateSMSMessage(ScreenConstructorData *sd) { + return new ("", 0) uiSMSMessage(sd); +} + +static MenuScreen *CreateControllerUnplugged(ScreenConstructorData *sd) { + return new ("", 0) ControllerUnplugged(sd); +} + +static MenuScreen *CreateMovieScreen(ScreenConstructorData *sd) { + return new ("", 0) MovieScreen(sd); +} + +static MenuScreen *CreateSplashScreen(ScreenConstructorData *sd) { + return new ("", 0) SplashScreen(sd); +} + +static MenuScreen *CreateLanguageSelectScreen(ScreenConstructorData *sd) { + return new ("", 0) LanguageSelectScreen(sd); +} + +static MenuScreen *CreateSixDaysLaterScreen(ScreenConstructorData *sd) { + return new ("", 0) SixDaysLater(sd); +} + +static MenuScreen *CreateEngageEventDialog(ScreenConstructorData *sd) { + return new ("", 0) nsEngageEventDialog::EngageEventDialog(sd); +} + +static MenuScreen *CreateUISafeHouseRaceSheet(ScreenConstructorData *sd) { + return new ("", 0) UISafehouseRaceSheet(sd); +} + +static MenuScreen *CreateUIRapSheetLogin(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetLogin(sd); +} + +static MenuScreen *CreateUIRapSheetMain(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetMain(sd); +} + +static MenuScreen *CreateUIRapSheetRS(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetRS(sd); +} + +static MenuScreen *CreateUIRapSheetUS(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetUS(sd); +} + +static MenuScreen *CreateUIRapSheetVD(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetVD(sd); +} + +static MenuScreen *CreateUIRapSheetCTS(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetCTS(sd); +} + +static MenuScreen *CreateUIRapSheetTEP(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetTEP(sd); +} + +static MenuScreen *CreateUIRapSheetPD(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetPD(sd); +} + +static MenuScreen *CreateUIRapSheetRankings(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetRankings(sd); +} + +static MenuScreen *CreateUIRapSheetRankingsDetail(ScreenConstructorData *sd) { + return new ("", 0) uiRapSheetRankingsDetail(sd); +} + +static MenuScreen *CreateUISafeHouseRepSheetMain(ScreenConstructorData *sd) { + return new ("", 0) uiRepSheetMain(sd); +} + +static MenuScreen *CreateUISafeHouseRivalChallenge(ScreenConstructorData *sd) { + return new ("", 0) uiRepSheetRival(sd); +} + +static MenuScreen *CreateUISafeHouseRivalBio(ScreenConstructorData *sd) { + return new ("", 0) uiRepSheetRivalBio(sd); +} + +static MenuScreen *CreateUISafeHouseMilestones(ScreenConstructorData *sd) { + return new ("", 0) uiRepSheetMilestones(sd); +} + +static MenuScreen *CreateUISafeHouseRegionUnlock(ScreenConstructorData *sd) { + return new ("", 0) uiSafehouseRegionUnlock(sd); +} + +static MenuScreen *CreateUISafeHouseBounty(ScreenConstructorData *sd) { + return new ("", 0) uiRepSheetBounty(sd); +} + +static MenuScreen *CreateUISafeHouseMarkers(ScreenConstructorData *sd) { + return new ("", 0) FEMarkerSelection(sd); +} + +static MenuScreen *CreateGameWonScreen(ScreenConstructorData *sd) { + return new ("", 0) FEGameWonScreen(sd); +} + +static MenuScreen *CreateDebugCarCustomize(ScreenConstructorData *sd) { + return new ("", 0) DebugCarCustomizeScreen(sd); +} + +static MenuScreen *CreateMyCarsManager(ScreenConstructorData *sd) { + return new ("", 0) MyCarsManager(sd); +} + +static MenuScreen *CreateCustomizeMainScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeMain(sd); +} + +static MenuScreen *CreateCustomizeSubScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeSub(sd); +} + +static MenuScreen *CreateCustomizeShoppingCartScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeShoppingCart(sd); +} + +static MenuScreen *CreateCustomizePartsScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeParts(sd); +} + +static MenuScreen *CreateCustomHUDColorScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeHUDColor(sd); +} + +static MenuScreen *CreateDecalsScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeDecals(sd); +} + +static MenuScreen *CreateNumbersScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeNumbers(sd); +} + +static MenuScreen *CreatePaintScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizePaint(sd); +} + +static MenuScreen *CreateRimmingScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeRims(sd); +} + +static MenuScreen *CreateSpoilersScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizeSpoiler(sd); +} + +static MenuScreen *CreateCustomizePerformanceScreen(ScreenConstructorData *sd) { + return new ("", 0) CustomizePerformance(sd); +} + +static MenuScreen *CreateBustedOverlayScreen(ScreenConstructorData *sd) { + return new ("", 0) BustedOverlayScreen(sd); +} + +static MenuScreen *CreatePostRacePursuitScreen(ScreenConstructorData *sd) { + return new ("", 0) PostRacePursuitScreen(sd); +} + +static MenuScreen *CreatePostRaceMilestonesScreen(ScreenConstructorData *sd) { + return new ("", 0) PostRaceMilestonesScreen(sd); +} + +static MenuScreen *CreateCreditsScreen(ScreenConstructorData *sd) { + return new ("", 0) uiCredits(sd); +} + +static MenuScreen *CreateUIEATraxScreen(ScreenConstructorData *sd) { + return new ("", 0) UIEATraxScreen(sd); +} + +static MenuScreen *CreateOptionsControllerScreen(ScreenConstructorData *sd) { + return new ("", 0) UIOptionsController(sd); +} + +typedef MenuScreen *(*ScreenCreateFunc)(ScreenConstructorData *); + +struct ScreenFactoryDatum { + const char *Name; + ScreenCreateFunc CreateFunc; +}; + +static ScreenFactoryDatum ScreenFactoryData[] = { + {"MainMenu.fng", CreateMainMenu}, + {"MainMenu_Sub.fng", CreateSubMenu}, + {"Options.fng", CreateOptionsScreen}, + {"OptionsPCDisplay.fng", CreateOptionsScreen}, + {"Quick_Race_Brief.fng", CreateQRBrief}, + {"Track_Select.fng", CreateQRTrackSelect}, + {"Track_Options.fng", CreateQRTrackOptions}, + {"Car_Select.fng", CreateQRCarSelect}, + {"PressStart.fng", CreateQRPressStart}, + {"ChallengeSeries.fng", CreateQRChallengeSeries}, + {"Showcase.fng", CreateShowcase}, + {"Pause_Main.fng", CreateCommonPauseMenu}, + {"Pause_Performance_Tuning.fng", CreateCustomTuningScreen}, + {"FadeScreen.fng", CreateFadeScreen}, + {"WorldMapMain.fng", CreateWorldMap}, + {"Pause_Options.fng", CreateOptionsScreen}, + {"HUD_SingleRace.fng", nullptr}, + {"HUD_Drag.fng", nullptr}, + {"InGameAnyMovie.fng", nullptr}, + {"WS_InGameAnyMovie.fng", nullptr}, + {"InGameAnyTutorial.fng", nullptr}, + {"EngageEventDialog.fng", CreateEngageEventDialog}, + {"SafehouseRaceSheet.fng", CreateUISafeHouseRaceSheet}, + {"OPM_SafehouseRaceSheet.fng", CreateUISafeHouseRaceSheet}, + {"SafehouseReputationOverview.fng", CreateUISafeHouseRepSheetMain}, + {"RapSheetLogin.fng", CreateUIRapSheetLogin}, + {"RapSheetLogin2.fng", CreateUIRapSheetLogin}, + {"RapSheetMain.fng", CreateUIRapSheetMain}, + {"RapSheetRS.fng", CreateUIRapSheetRS}, + {"RapSheetUS.fng", CreateUIRapSheetUS}, + {"RapSheetVD.fng", CreateUIRapSheetVD}, + {"RapSheetCTS.fng", CreateUIRapSheetCTS}, + {"RapSheetTEP.fng", CreateUIRapSheetTEP}, + {"RapSheetPD.fng", CreateUIRapSheetPD}, + {"RapSheetRankings.fng", CreateUIRapSheetRankings}, + {"RapSheetRankingsDetail.fng", CreateUIRapSheetRankingsDetail}, + {"SafehouseRivalChallenge.fng", CreateUISafeHouseRivalChallenge}, + {"SafehouseRivalBio.fng", CreateUISafeHouseRivalBio}, + {"SafehouseMilestones.fng", CreateUISafeHouseMilestones}, + {"SafehouseRegionUnlock.fng", CreateUISafeHouseRegionUnlock}, + {"SafehouseBounty.fng", CreateUISafeHouseBounty}, + {"SafehouseMarkers.fng", CreateUISafeHouseMarkers}, + {"SixDaysLater.fng", CreateSixDaysLaterScreen}, + {"InGameRaceSheet.fng", CreateUISafeHouseRaceSheet}, + {"InGameReputationOverview.fng", CreateUISafeHouseRepSheetMain}, + {"InGameMilestones.fng", CreateUISafeHouseMilestones}, + {"InGameRivalChallenge.fng", CreateUISafeHouseRivalChallenge}, + {"InGameRivalBio.fng", CreateUISafeHouseRivalBio}, + {"InGameBounty.fng", CreateUISafeHouseBounty}, + {"SMS_Mailboxes.fng", CreateSMS}, + {"SMS_Message.fng", CreateSMSMessage}, + {"GameWon.fng", CreateGameWonScreen}, + {"RapSheetLogin_ENDGAME.fng", CreateGameWonScreen}, + {"RapSheetLogin2_ENDGAME.fng", CreateGameWonScreen}, + {"RapSheetMain_ENDGAME.fng", CreateGameWonScreen}, + {"ControllerUnplugged.fng", CreateControllerUnplugged}, + {"UI_DebugCarCustomize.fng", CreateDebugCarCustomize}, + {"MyCarsManager.fng", CreateMyCarsManager}, + {"CustomizeMain.fng", CreateCustomizeMainScreen}, + {"CustomizeCategory.fng", CreateCustomizeSubScreen}, + {"CustomizeCategory_BACKROOM.fng", CreateCustomizeSubScreen}, + {"CustomizeGenericTop.fng", CreateCustomizeSubScreen}, + {"CustomizeGenericTop_BACKROOM.fng", CreateCustomizeSubScreen}, + {"ShoppingCart.fng", CreateCustomizeShoppingCartScreen}, + {"ShoppingCart_QR.fng", CreateCustomizeShoppingCartScreen}, + {"ShoppingCart_BACKROOM.fng", CreateCustomizeShoppingCartScreen}, + {"CustomizeParts.fng", CreateCustomizePartsScreen}, + {"CustomizeParts_BACKROOM.fng", CreateCustomizePartsScreen}, + {"CustomHUD.fng", CreateCustomizePartsScreen}, + {"CustomHUD_BACKROOM.fng", CreateCustomizePartsScreen}, + {"CustomHUDColor.fng", CreateCustomHUDColorScreen}, + {"CustomHUDColor_BACKROOM.fng", CreateCustomHUDColorScreen}, + {"Decals.fng", CreateDecalsScreen}, + {"Decals_BACKROOM.fng", CreateDecalsScreen}, + {"Numbers.fng", CreateNumbersScreen}, + {"Paint.fng", CreatePaintScreen}, + {"Paint_BACKROOM.fng", CreatePaintScreen}, + {"Rims.fng", CreateRimmingScreen}, + {"Rims_BACKROOM.fng", CreateRimmingScreen}, + {"Spoilers.fng", CreateSpoilersScreen}, + {"Spoilers_BACKROOM.fng", CreateSpoilersScreen}, + {"CustomizePerformance.fng", CreateCustomizePerformanceScreen}, + {"CustomizePerformance_BACKROOM.fng", CreateCustomizePerformanceScreen}, + {"GarageMain.fng", nullptr}, + {"DiscError.fng", nullptr}, + {"Dialog.fng", nullptr}, + {"GenericDialog_ThreeButton.fng", nullptr}, + {"GameOver.fng", nullptr}, + {"EA_Trax_Jukebox.fng", CreateUIEATraxScreen}, + {"EA_Trax.fng", nullptr}, + {"Chyron_IG.fng", nullptr}, + {"InGameDialog.fng", nullptr}, + {"Keyboard.fng", nullptr}, + {"Keyboard_GC.fng", nullptr}, + {"ScreenPrintf.fng", nullptr}, + {"Credits.fng", CreateCreditsScreen}, + {"FEAnyMovie.fng", nullptr}, + {"WS_FEAnyMovie.fng", nullptr}, + {"FEAnyTutorial.fng", nullptr}, + {"LS_EALogo.fng", CreateSplashScreen}, + {"LS_EA_hidef.fng", CreateSplashScreen}, + {"LS_PSA.fng", CreateSplashScreen}, + {"LS_THXMovie.fng", CreateSplashScreen}, + {"MW_LS_IntroFMV.fng", CreateSplashScreen}, + {"MW_LS_AttractFMV.fng", CreateSplashScreen}, + {"MW_LS_Splash.fng", CreateMovieScreen}, + {"WS_LS_EALogo.fng", CreateSplashScreen}, + {"WS_LS_EA_hidef.fng", CreateSplashScreen}, + {"WS_LS_PSA.fng", CreateSplashScreen}, + {"WS_LS_IntroFMV.fng", CreateSplashScreen}, + {"WS_MW_LS_AttractFMV.fng", CreateSplashScreen}, + {"WS_MW_LS_Splash.fng", CreateMovieScreen}, + {"Loading_Tips.fng", CreateLoadingTipsScreen}, + {"loading_boot.fng", nullptr}, + {"LS_LangSelect.fng", CreateLanguageSelectScreen}, + {"Loading.fng", CreateLoadingScreen}, + {"WS_Loading.fng", CreateLoadingScreen}, + {"Loading_Controller.fng", CreateLoadingControllerScreen}, + {"WS_Loading_Controller.fng", CreateLoadingControllerScreen}, + {"UI_OptionsController.fng", CreateOptionsControllerScreen}, + {"Pause_Controller.fng", CreateOptionsControllerScreen}, + {"PostRace_Results.fng", CreatePostRaceResultsScreen}, + {"BUSTED_OVERLAY.fng", CreateBustedOverlayScreen}, + {"PostBusted.fng", CreatePostRacePursuitScreen}, + {"Infractions.fng", nullptr}, + {"InGamePhotoMaster.fng", nullptr}, + {"PostRace_Pursuit.fng", CreatePostRacePursuitScreen}, + {"PostRace_MilestoneRewards.fng", CreatePostRaceMilestonesScreen}, + {"MC_ProfileManager.fng", nullptr}, + {"MC_Deleteprofile.fng", nullptr}, + {"MC_Bootup.fng", nullptr}, + {"MC_Bootup_GC.fng", nullptr}, + {"MC_List.fng", nullptr}, + {"InGame_MC_Main.fng", nullptr}, + {"InGame_MC_Main_GC.fng", nullptr}, + {"MC_Main.fng", nullptr}, + {"MC_Main_GC.fng", nullptr}, +}; + +static const int kScreenFactoryDataCount = sizeof(ScreenFactoryData) / sizeof(ScreenFactoryData[0]); + +static ScreenFactoryDatum *FindScreenCreateData(unsigned int hash) { + for (int i = 0; i < kScreenFactoryDataCount; i++) { + unsigned int nameHash = FEHashUpper(ScreenFactoryData[i].Name); + if (hash == nameHash) { + return &ScreenFactoryData[i]; + } + } + return nullptr; +} + +void FEngSetCreateCallback(const char *abstract_pkg_name, MenuScreen *(*function)(ScreenConstructorData *)) { + for (int i = 0; i < kScreenFactoryDataCount; i++) { + ScreenFactoryDatum &sfd = ScreenFactoryData[i]; + if (bStrICmp(abstract_pkg_name, sfd.Name) == 0) { + sfd.CreateFunc = function; + return; + } + } +} + +static MenuScreen *ScreenFactory(unsigned int hash, FEPackage *pkg, int arg) { + for (int i = 0; i < kScreenFactoryDataCount; i++) { + unsigned int nameHash = FEHashUpper(ScreenFactoryData[i].Name); + if (hash == nameHash && ScreenFactoryData[i].CreateFunc) { + ScreenConstructorData sd; + sd.PackageFilename = ScreenFactoryData[i].Name; + sd.pPackage = pkg; + sd.Arg = arg; + return ScreenFactoryData[i].CreateFunc(&sd); + } + } + return nullptr; +} + +void FEPackageData::Activate(FEPackage *pkg, int arg) { + pPackage = pkg; + pkg->SetUserParam(reinterpret_cast(this)); + mInScreenConstructor++; + pScreen = ScreenFactory(GetNameHash(), pkg, arg); + LastKnownControlMask = pkg->GetControlMask(); + mInScreenConstructor--; +} + +void FEPackageData::NotificationMessage(unsigned long Message, FEObject *pObject, unsigned long Param1, unsigned long Param2) { + if (pScreen) { + pScreen->BaseNotify(Message, pObject, Param1, Param2); + } +} + +void FEPackageData::NotifySoundMessage(unsigned long msg, FEObject *obj, unsigned long control_mask, unsigned long pkg_ptr) { + if (pScreen) { + pScreen->BaseNotifySound(msg, obj, control_mask, pkg_ptr); + } +} + +FEPackageData::FEPackageData(bChunk *chunk) { + IsVisible = 1; + MyChunk = chunk; + pScreen = nullptr; + pPackage = nullptr; + LastKnownControlMask = 0; + bWasSetupForHotchunk = 0; + IsPermanent = 0; + CreateData = nullptr; + RenderInfo.EpolySlotPool = nullptr; + RenderInfo.AllowOverflows = false; + mArg = 0; + + if (chunk->GetID() == 0x30203) { + DataChunk = reinterpret_cast(chunk) + 2; + } else if (chunk->GetID() == 0x30210) { + DataChunk = nullptr; + } + + CreateData = FindScreenCreateData(GetNameHash()); +} + +FEPackageData::~FEPackageData() { + if (MyChunk->GetID() != 0x30203 && MyChunk->GetID() == 0x30210) { + bFree(DataChunk); + } + DataChunk = nullptr; +} + +void FEPackageData::UnActivate() { + if (pScreen) { + delete pScreen; + } + pScreen = nullptr; + if (pPackage) { + pPackage->SetUserParam(0); + } + pPackage = nullptr; + if (MyChunk->GetID() == 0x30210) { + bFree(DataChunk); + DataChunk = nullptr; + } +} + +void FEPackageData::Close() { + if (pPackage) { + HackClearCache(pPackage); + RenderObjectDisconnect cb(HACK_FEPkgMgr_GetPackageRenderInfo(pPackage), cFEngRender::mInstance); + pPackage->ForAllObjects(cb); + cFEng_mInstance->mFEng->UnloadPackage(pPackage); + UnActivate(); + } +} + +void *FEPackageData::GetDataChunk() { + if (MyChunk->GetID() == 0x30203) { + return DataChunk; + } + if (MyChunk->GetID() == 0x30210) { + int decompressedSize = reinterpret_cast(MyChunk)[5]; + DataChunk = bMalloc(decompressedSize, GetVirtualMemoryAllocParams()); + LZDecompress(reinterpret_cast(reinterpret_cast(MyChunk) + 3), + static_cast(DataChunk)); + return DataChunk; + } + return nullptr; +} + +unsigned int FEPackageData::GetNameHash() { + if (MyChunk->GetID() == 0x30203) { + unsigned int *data = static_cast(GetDataChunk()); + unsigned int magic = data[0]; + unsigned int swappedMagic = (magic >> 24) | (magic << 24) | ((magic & 0xFF00) << 8) | ((magic >> 8) & 0xFF00); + if (swappedMagic == 0xE76E4546) { + unsigned int pkHd = data[2]; + unsigned int swappedPkHd = (pkHd >> 24) | (pkHd << 24) | ((pkHd & 0xFF00) << 8) | ((pkHd >> 8) & 0xFF00); + if (swappedPkHd == 0x64486B50) { + unsigned int version = data[4]; + unsigned int swappedVersion = (version >> 24) | (version << 24) | ((version & 0xFF00) << 8) | ((version >> 8) & 0xFF00); + if (swappedVersion > 0x1FFFF) { + return FEHashUpper(reinterpret_cast(data + 10)); + } + } + } + } else if (MyChunk->GetID() == 0x30210) { + return reinterpret_cast(MyChunk)[2]; + } + return 0; +} + +// EngageEventDialog implementations +nsEngageEventDialog::EngageEventDialog::~EngageEventDialog() { + if (MapStreamer) { + delete MapStreamer; + MapStreamer = nullptr; + } +} + +void nsEngageEventDialog::EngageEventDialog::NotifyTheGameAcceptEvent() { + UCrc32 port(0x20d60dbf); + MAcceptEnterCareerEvent msg; + msg.Post(port); +} + +void nsEngageEventDialog::EngageEventDialog::NotifyTheGameDeclineEvent() { + UCrc32 port(0x20d60dbf); + MDeclineEnterCareerEvent msg; + msg.Post(port); +} + +void nsEngageEventDialog::EngageEventDialog::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x911ab364: + NotifyTheGameDeclineEvent(); + cFEng::Get()->QueuePackagePop(1); + break; + case 0xc98356ba: + if (MapStreamer) { + MapStreamer->UpdateAnimation(); + } + break; + case 0x0c407210: { + unsigned int objHash = obj->NameHash; + if (objHash == 0x694b896e) { + NotifyTheGameDeclineEvent(); + cFEng::Get()->QueuePackagePop(1); + } else if (objHash == 0xd72f002a) { + NotifyTheGameAcceptEvent(); + cFEng::Get()->QueuePackagePop(1); + } + break; + } + } +} + +// MovieScreen destructor +MovieScreen::~MovieScreen() { +} + +// MovieScreen constructor +MovieScreen::MovieScreen(ScreenConstructorData *sd) : MenuScreen(sd), mSoundState(0), mSubtitler() { + new ESndGameState(1, true); + SetSoundControlState(true, static_cast(0xb), "MovieScreen"); + for (int i = 0; i < 10; i++) { + if (bStrICmp(GetPackageName(), MovieData[i].package_name) == 0) { + mSoundState = MovieData[i].sound_state; + FEMovie *movie = reinterpret_cast(FEngFindObject(GetPackageName(), MovieData[i].fng_obj_hash)); + FEngSetMovieName(movie, MovieData[i].movie_name); + if (MovieData[i].has_subtitle) { + mSubtitler.BeginningMovie(MovieData[i].movie_name, GetPackageName()); + } + } + } +} + +// MovieScreen NotificationMessage +void MovieScreen::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + mSubtitler.Update(msg); + bool movie_is_finished = false; + if (msg != 0xb5af2461) { + if (msg > 0xb5af2461) { + if (msg == 0xc3960eb9) { + new ESndGameState(1, false); + SetSoundControlState(false, static_cast(0xb), "MovieScreen"); + if (!IsDebugPlayMovie) { + BootFlowManager::Get()->ChangeToNextBootFlowScreen(0xff); + movie_is_finished = true; + } else { + cFEng::Get()->QueuePackagePop(1); + IsDebugPlayMovie = 0; + movie_is_finished = true; + } + } + goto end; + } + if (msg != 0x406415e3) goto end; + } + if (mSoundState != 0) { + new ESndGameState(1, false); + SetSoundControlState(false, static_cast(0xb), "MovieScreen"); + if (!IsDebugPlayMovie) { + BootFlowManager::Get()->ChangeToNextBootFlowScreen(0xff); + movie_is_finished = true; + } else { + movie_is_finished = true; + cFEng::Get()->QueuePackagePop(1); + IsDebugPlayMovie = 0; + } + } +end: + if (movie_is_finished) { + mSubtitler.Update(0xc3960eb9); + } +} + +SplashScreen::SplashScreen(ScreenConstructorData *sd) : MenuScreen(sd), // + bAllowContinue(false), // + CopyrightNotice(0), // + SplashStartedTimer(0) { + const unsigned long FEObj_HDGROUP = 0x534cc377; + const unsigned long FEObj_startclick = 0x13cf446d; + const unsigned long FEObj_mouseclick = 0x8c0bd743; + const unsigned long FEObj_mousebutton = 0x4b98c4b9; + FEObject *pObject; + const unsigned long FEObj_LicenseBlurb = 0xc4df3ff2; + + if (eIsWidescreen()) { + cFEng::Get()->QueuePackageMessage(bStringHash("CURRENT_GEN_WIDESCREEN"), GetPackageName(), nullptr); + } + + FEngSetInvisible(GetPackageName(), FEObj_HDGROUP); + + if (GetVideoMode() == 0) { + const unsigned long FEObj_ESRBicon = 0x43d41f73; + FEngSetInvisible(GetPackageName(), FEObj_ESRBicon); + } + + FEngSetVisible(FEngFindObject(GetPackageName(), FEObj_LicenseBlurb)); + FEngSetInvisible(GetPackageName(), FEObj_startclick); + FEngSetInvisible(GetPackageName(), FEObj_mouseclick); + + pObject = FEngFindObject(GetPackageName(), FEObj_mouseclick); + + FEngSetInvisible(GetPackageName(), FEObj_mousebutton); + FEngSetVisible(FEngFindObject(GetPackageName(), FEObj_mousebutton)); + + FEngSetLanguageHash(GetPackageName(), FEObj_mousebutton, 0x9ba134fc); + FEngSetLanguageHash(GetPackageName(), FEObj_LicenseBlurb, 0x9b580a55); + + if (pObject) { + if ((pObject->Flags & 0x10000000) != 0) { + pObject->Flags &= ~0x10000000; + } + pObject->Flags |= 0x400000; + } + + SplashStartedTimer = RealTimer; + CopyrightNotice = RealTimer; + + { + MControlPathfinder msg(false, 16, 0, 0); + msg.Send("Event"); + } + + gEasterEggs.Activate(); + + if (!CarViewer::haveLoadedOnce) { + RideInfo ride; + FEDatabase->BuildCurrentRideForPlayer(0, &ride); + CarViewer::SetRideInfo(&ride, SET_RIDE_INFO_REASON_CATCHALL, eCARVIEWER_PLAYER1_CAR); + CarViewer::ShowCarScreen(); + CarViewer::haveLoadedOnce = true; + } +} + +SplashScreen::~SplashScreen() { + gEasterEggs.UnActivate(); + MControlPathfinder msg(false, 9, 0, 0); + msg.Send("Event"); +} + +extern int bStrICmp(const char *s1, const char *s2); + +Timer SplashScreen::CalculateLastJoyEventTime() { + Timer lowesttimer; + lowesttimer.ResetLow(); + for (ActionQueue *const *iter = UTL::Collections::Listable::GetList().begin(); + iter != UTL::Collections::Listable::GetList().end(); ++iter) { + ActionQueue *q = *iter; + if (q->IsConnected() && q->IsEnabled() && bStrICmp(q->GetName(), "FEng") == 0) { + if (!lowesttimer.IsSet() || q->LastActionTime() > lowesttimer) { + lowesttimer = q->LastActionTime(); + } + } + } + if (SplashStartedTimer > lowesttimer) { + lowesttimer = SplashStartedTimer; + } + return lowesttimer; +} + +extern float SplashScreenMovieTimeout; +extern float SplashScreenTotalTimeout; + +void SplashScreen::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, + unsigned long param2) { + switch (msg) { + case 0x98257537: + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), + 0x417b2601, 0x1fab5998, 0x53f13fd1); + break; + case 0x6521e5c2: + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), + 0x417b2601, 0x1fab5998, 0x6521e5c2); + break; + case 0xa6813b08: + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), + 0x417b2601, 0x1fab5998, 0xa1161aaf); + break; + case 0xc98356ba: { + Timer lastJoyTime = CalculateLastJoyEventTime(); + Timer elapsed = RealTimer - lastJoyTime; + int timed_out = elapsed.GetSeconds() > SplashScreenMovieTimeout || + (SplashScreenTotalTimeout != 0.0f && + (RealTimer - SplashStartedTimer).GetSeconds() > SplashScreenTotalTimeout); + int final_timed_out = timed_out; + if (TheTrackStreamer.IsPermFileLoading()) { + final_timed_out = 0; + } + if (final_timed_out != 0) { + if (!BootFlowManager::Get()->DoAttract()) { + SplashStartedTimer.ResetHigh(); + } + } + break; + } + case 0x406415e3: + case 0xb5af2461: + if (bAllowContinue) { + BootFlowManager::Get()->ChangeToNextBootFlowScreen(0xff); + } + break; + case 0x35f8620b: + bAllowContinue = true; + break; + } +} + +float SplashScreenMovieTimeout = 8.0f; +float SplashScreenTotalTimeout = 0.0f; diff --git a/src/Speed/Indep/Src/Frontend/FEPackageData.hpp b/src/Speed/Indep/Src/Frontend/FEPackageData.hpp index da447bb1c..ef7753d20 100644 --- a/src/Speed/Indep/Src/Frontend/FEPackageData.hpp +++ b/src/Speed/Indep/Src/Frontend/FEPackageData.hpp @@ -8,40 +8,61 @@ #include "Speed/Indep/bWare/Inc/bChunk.hpp" #include "Speed/Indep/bWare/Inc/bList.hpp" +struct SlotPool; + +// total size: 0x8 +struct FEPackageRenderInfo { + SlotPool *EpolySlotPool; // offset 0x0, size 0x4 + bool AllowOverflows; // offset 0x4, size 0x1 +}; + // total size: 0x38 class FEPackageData : public bTNode { public: - // static int IsInScreenConstructor() {} + static int IsInScreenConstructor() { return mInScreenConstructor > 0; } + + bChunk *GetChunk() { return MyChunk; } + + struct FEPackage *GetPackage() { return pPackage; } + + bool IsCompressedChunk() { return DataChunk != nullptr; } + + void *GetDataChunk(); - // bChunk *GetChunk() {} + unsigned int GetNameHash(); - // struct FEPackage *GetPackage() {} + bool IsActive() { return pScreen != nullptr; } - // bool IsCompressedChunk() {} + FEPackageData(bChunk *chunk); + virtual ~FEPackageData(); + void Activate(struct FEPackage *pkg, int arg); + void Close(); - // bool IsActive() {} + void SetPermanent(int flag) { IsPermanent = flag; } - // void SetPermanent(int flag) {} + int GetPermanent() { return IsPermanent; } - // int GetPermanent() {} + void SetArgument(int pArg) { mArg = pArg; } - // void SetArgument(int pArg) {} + int GetArgument() { return mArg; } - // int GetArgument() {} + bool GetVisibility() { return IsVisible; } - // bool GetVisibility() {} + void SetVisibility(bool visible) { IsVisible = visible; } - // void SetVisibility(bool visible) {} + int GetLastKnownControlMask() { return LastKnownControlMask; } - // int GetLastKnownControlMask() {} + bool WasSetupForHotchunk() { return bWasSetupForHotchunk; } - // bool WasSetupForHotchunk() {} + void SetupForHotchunk() { bWasSetupForHotchunk = true; } - // void SetupForHotchunk() {} + void ClearHotchunk() { bWasSetupForHotchunk = false; } - // void ClearHotchunk() {} + struct MenuScreen *GetScreen() { return pScreen; } - // struct MenuScreen *GetScreen() {} + void NotificationMessage(unsigned long Message, struct FEObject *pObject, unsigned long Param1, unsigned long Param2); + void NotifySoundMessage(unsigned long msg, struct FEObject *obj, unsigned long control_mask, unsigned long pkg_ptr); + void UnActivate(); // struct FEPackageRenderInfo *GetRenderInfo() {} @@ -58,8 +79,7 @@ class FEPackageData : public bTNode { int16 IsPermanent; // offset 0x20, size 0x2 int16 IsVisible; // offset 0x22, size 0x2 struct ScreenFactoryDatum *CreateData; // offset 0x24, size 0x4 - // TODO - // struct FEPackageRenderInfo RenderInfo; // offset 0x28, size 0x8 + struct FEPackageRenderInfo RenderInfo; // offset 0x28, size 0x8 int mArg; // offset 0x30, size 0x4 }; diff --git a/src/Speed/Indep/Src/Frontend/FEPackageManager.cpp b/src/Speed/Indep/Src/Frontend/FEPackageManager.cpp index e69de29bb..9791fc617 100644 --- a/src/Speed/Indep/Src/Frontend/FEPackageManager.cpp +++ b/src/Speed/Indep/Src/Frontend/FEPackageManager.cpp @@ -0,0 +1,249 @@ +#include "Speed/Indep/Src/Frontend/FEPackageManager.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +extern unsigned long FEHashUpper(const char *str); +extern unsigned int bStringHash(const char *str); +extern cFEng *cFEng_mInstance; +extern void HackClearCache(FEPackage *pkg); +extern bool IsCurrentlyHotChunking(); + +FEPackageManager *FEPackageManager::mInstance; +int FEPackageData::mInScreenConstructor; + +FEPackageManager *FEPackageManager::Get() { + return mInstance; +} + +void FEPackageManager::ErrorTick() { + BroadcastMessage(0xD0678849); +} + +void FEPackageManager::Tick() { + BroadcastMessage(0xC98356BA); +} + +unsigned int FEngGetActiveScreensChecksum() { + return FEPackageManager::Get()->GetActiveScreensChecksum(); +} + +FEPackage *FEPackageManager::FindPackage(const char *pkg_name) { + FEPackageData *data = FindFEPackageData(pkg_name); + if (!data) { + return nullptr; + } + return data->GetPackage(); +} + +void *FEPackageManager::GetPackageData(const char *pkg_name) { + FEPackageData *data = FindFEPackageData(pkg_name); + if (!data) { + return nullptr; + } + return data->GetDataChunk(); +} + +MenuScreen *FEPackageManager::FindScreen(const char *pkg_name) { + FEPackageData *data = FindFEPackageData(pkg_name); + if (!data) { + return nullptr; + } + return data->GetScreen(); +} + +MenuScreen *FEngFindScreen(const char *package_name) { + return FEPackageManager::Get()->FindScreen(package_name); +} + +FEPackageRenderInfo *HACK_FEPkgMgr_GetPackageRenderInfo(FEPackage *pkg) { + unsigned long userParam = pkg->GetUserParam(); + if (userParam == 0) { + return nullptr; + } + return reinterpret_cast(userParam + 0x28); +} + +FEPackageData *FEPackageManager::FindFEPackageData(bChunk *chunk) { + for (FEPackageData *f = ScreenList.GetHead(); f != ScreenList.EndOfList(); f = f->GetNext()) { + if (f->GetChunk() == chunk) { + return f; + } + } + return nullptr; +} + +void FEPackageManager::PackageWillBeUnloaded(FEPackage *pkg) { + FEPackageData *data = FindFEPackageData(pkg->GetName()); + if (data) { + data->UnActivate(); + } +} + +void FEPackageManager::Init() { + if (!mInstance) { + mInstance = new ("", 0) FEPackageManager(); + } +} + +void FEPackageManager::BroadcastMessage(u32 msg) { + FEPackageData *active[32]; + int count = 0; + + for (FEPackageData *f = ScreenList.GetHead(); f != ScreenList.EndOfList(); f = f->GetNext()) { + if (f->GetPackage() && count < 32) { + if (msg != 0x9803F6E2 || f->GetPackage()->IsInputEnabled()) { + active[count] = f; + count++; + } + } + } + + for (int i = 0; i < count; i++) { + if (active[i]->GetPackage()) { + active[i]->NotificationMessage(msg, nullptr, 0, 0); + } + } +} + +unsigned long FEPackageManager::GetActiveScreensChecksum() { + unsigned long checksum = 0; + for (FEPackageData *f = ScreenList.GetHead(); f != ScreenList.EndOfList(); f = f->GetNext()) { + if (f->GetPackage()) { + if (bStrICmp(f->GetPackage()->GetName(), "EA_TRAX.fng") != 0) { + checksum += bStringHash(f->GetPackage()->GetName()); + } + } + } + return checksum; +} + +void FEPackageManager::NotifySoundMessage(u32 Message, FEObject *obj, u32 controller_mask, u32 pkg_ptr) { + for (FEPackageData *f = ScreenList.GetHead(); f != ScreenList.EndOfList(); f = f->GetNext()) { + if (f->GetPackage() && pkg_ptr == reinterpret_cast(f->GetPackage())) { + f->NotifySoundMessage(Message, obj, controller_mask, pkg_ptr); + } + } +} + +void FEPackageManager::NotificationMessage(u32 Message, FEObject *pObject, u32 Param1, u32 Param2) { + for (FEPackageData *f = ScreenList.GetHead(); f != ScreenList.EndOfList(); f = f->GetNext()) { + if (f->GetPackage() && Param2 == reinterpret_cast(f->GetPackage())) { + f->NotificationMessage(Message, pObject, Param1, Param2); + } + } +} + +const char *FEPackageManager::GetBasePkgName(const char *pkg_name) { + int len = bStrLen(pkg_name); + const char *ptr = pkg_name + len; + if (ptr != pkg_name) { + char c = pkg_name[len]; + while (c != '\\') { + ptr--; + if (ptr == pkg_name) { + return ptr; + } + c = *ptr; + } + ptr++; + } + return ptr; +} + +void FEPackageManager::CloseAllPackages(int close_permanent) { + for (FEPackageData *f = ScreenList.GetHead(); f != ScreenList.EndOfList(); f = f->GetNext()) { + if (!f->GetPermanent() || close_permanent) { + f->Close(); + } else if (f->GetPackage()) { + HackClearCache(f->GetPackage()); + } + } +} + +FEPackageData *FEPackageManager::FindFEPackageData(const char *pkg_name) { + const char *baseName = GetBasePkgName(pkg_name); + unsigned int hash = FEHashUpper(baseName); + FEPackageData *found = nullptr; + + for (FEPackageData *f = ScreenList.GetHead(); f != ScreenList.EndOfList(); f = f->GetNext()) { + found = f; + if (f->GetNameHash() == hash) { + break; + } + } + + if (!found) { + return nullptr; + } + + found->Remove(); + ScreenList.AddHead(found); + return found; +} + +bool FEPackageManager::SetPackageDataArg(const char *pPackageName, const int pArg) { + FEPackageData *data = mInstance->FindFEPackageData(pPackageName); + if (data) { + data->SetArgument(pArg); + } + return data != nullptr; +} + +void FEPackageManager::PackageWasLoaded(FEPackage *pkg) { + FEPackageData *data = FindFEPackageData(pkg->GetName()); + if (data) { + data->Activate(pkg, data->GetArgument()); + } +} + +void FEPackageManager::Loader(bChunk *chunk, bool hotchunk_flag) { + FEPackageData *data = new ("", 0) FEPackageData(chunk); + if (chunk->GetID() == 0x30210) { + bEndianSwap32(reinterpret_cast(chunk) + 8); + bEndianSwap32(reinterpret_cast(chunk) + 12); + bEndianSwap16(reinterpret_cast(chunk) + 18); + bEndianSwap32(reinterpret_cast(chunk) + 20); + bEndianSwap32(reinterpret_cast(chunk) + 24); + } + FEPackageManager::Get()->ScreenList.AddTail(data); +} + +void FEPackageManager::UnLoader(bChunk *chunk, bool hotchunk_flag) { + cFEng_mInstance->ServiceFengOnly(); + FEPackageData *data = FindFEPackageData(chunk); + if (data) { + data->ClearHotchunk(); + data->Close(); + FEPackageManager::Get(); + data->Remove(); + delete data; + } +} + +FEPackageManager::~FEPackageManager() { +} + +bool FEPackageManager::GetVisibility(const char *pkg_name) { + FEPackageData *data = FindFEPackageData(pkg_name); + if (!data) { + return false; + } + return data->GetVisibility(); +} + +int LoaderFEngPackage(bChunk *chunk) { + if (chunk->GetID() == 0x30203 || chunk->GetID() == 0x30210) { + FEPackageManager::Get()->Loader(chunk, IsCurrentlyHotChunking()); + return 1; + } + return 0; +} + +int UnloaderFEngPackage(bChunk *chunk) { + if (chunk->GetID() == 0x30203 || chunk->GetID() == 0x30210) { + FEPackageManager::Get()->UnLoader(chunk, IsCurrentlyHotChunking()); + return 1; + } + return 0; +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/FEPackageManager.hpp b/src/Speed/Indep/Src/Frontend/FEPackageManager.hpp index aef2eb271..05559959c 100644 --- a/src/Speed/Indep/Src/Frontend/FEPackageManager.hpp +++ b/src/Speed/Indep/Src/Frontend/FEPackageManager.hpp @@ -65,7 +65,7 @@ class FEPackageManager { FEPackageManager() {} - virtual ~FEPackageManager() {} + virtual ~FEPackageManager(); // FEPackageData *Add(FEPackageData *screen) {} diff --git a/src/Speed/Indep/Src/Frontend/FERenderObject.cpp b/src/Speed/Indep/Src/Frontend/FERenderObject.cpp index e69de29bb..e042c6a88 100644 --- a/src/Speed/Indep/Src/Frontend/FERenderObject.cpp +++ b/src/Speed/Indep/Src/Frontend/FERenderObject.cpp @@ -0,0 +1,718 @@ +#include "Speed/Indep/Src/Frontend/FERenderObject.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" + +extern SlotPool *FERenderEPolySlotPool; +extern SlotPool *FERenderEPolySlotPoolOverflow; +extern void PSMTX44Identity(float *mtx); +extern void FEBeginBatchRender(eView *view, int polyCount); +extern void FEEndBatchRender(eView *view); +extern void FERender(eView *view, ePoly *poly, TextureInfo *tex, TextureInfo *mask, int param); +extern void FERender(eView *view, ePoly *poly, TextureInfo *tex, int param); + +extern unsigned int ClipTop(bVector3 *pDst, bVector2 *pDstUVs, bVector4 *pDstColors, + bVector3 *pSrc, bVector2 *pSrcUVs, bVector4 *pSrcColors, + unsigned int num_verts, float value); +extern unsigned int ClipBottom(bVector3 *pDst, bVector2 *pDstUVs, bVector4 *pDstColors, + bVector3 *pSrc, bVector2 *pSrcUVs, bVector4 *pSrcColors, + unsigned int num_verts, float value); +extern unsigned int ClipLeft(bVector3 *pDst, bVector2 *pDstUVs, bVector4 *pDstColors, + bVector3 *pSrc, bVector2 *pSrcUVs, bVector4 *pSrcColors, + unsigned int num_verts, float value); +extern unsigned int ClipRight(bVector3 *pDst, bVector2 *pDstUVs, bVector4 *pDstColors, + bVector3 *pSrc, bVector2 *pSrcUVs, bVector4 *pSrcColors, + unsigned int num_verts, float value); + +void FERenderObject::Initialize() { + mpobFERenderObjectSlotPool = bNewSlotPool(0x64, 0x180, "FERenderObjectSlotPool", 0); + FERenderEPolySlotPool = bNewSlotPool(0xA4, 0x348, "FERenderEPolySlotPool", 0); +} + +FERenderObject::FERenderObject(FEObject *obj, TextureInfo *tex) { + mpobOwner = obj; + mpobTexture = tex; + mobPolyList.InitList(); + mulNumTimesRendered = 0; + mulFlags = 0; + mPolyCount = 0; + PSMTX44Identity(reinterpret_cast(&mstTransform)); +} + +FERenderObject::~FERenderObject() { + while (mobPolyList.GetHead() != mobPolyList.EndOfList()) { + FERenderEPoly *poly = mobPolyList.GetHead(); + poly->Remove(); + delete poly; + } +} + +void *FERenderEPoly::operator new(unsigned int) { + if (FERenderEPolySlotPool->NumAllocatedSlots != FERenderEPolySlotPool->TotalNumSlots) { + return FERenderEPolySlotPool->FastMalloc(); + } + if (!FERenderEPolySlotPoolOverflow) { + FERenderEPolySlotPoolOverflow = bNewSlotPool(0xA4, 0x200, "FERenderEPolySlotPoolOverflow", 0); + FERenderEPolySlotPoolOverflow->ClearFlag(SLOTPOOL_FLAG_WARN_IF_OVERFLOW); + } + return FERenderEPolySlotPoolOverflow->Malloc(); +} + +void FERenderEPoly::operator delete(void *p) { + if (FERenderEPolySlotPool->GetSlotNumber(p) >= 0) { + FERenderEPolySlotPool->Free(p); + } else { + FERenderEPolySlotPoolOverflow->Free(p); + if (FERenderEPolySlotPoolOverflow->NumAllocatedSlots == 0) { + bDeleteSlotPool(FERenderEPolySlotPoolOverflow); + FERenderEPolySlotPoolOverflow = nullptr; + } + } +} + +bVector4 V4Mult(const bVector4 &v, float d) { + return bVector4(v.x * d, v.y * d, v.z * d, v.w * d); +} + +void FERenderObject::SetTransform(bMatrix4 *pMatrix) { + bMemCpy(&mstTransform, pMatrix, sizeof(bMatrix4)); +} + +void FERenderObject::Render() { + eView *view = &eViews[0]; + FEBeginBatchRender(view, mPolyCount); + FERenderEPoly *render = mobPolyList.GetHead(); + while (render != mobPolyList.EndOfList()) { + TextureInfo *texture = render->pTexture; + if (!texture) { + texture = mpobTexture; + } + if (render->EPoly.GetFlags() & 0x4) { + FERender(view, &render->EPoly, texture, render->pTextureMask, 0); + } else { + FERender(view, &render->EPoly, texture, 0); + } + render = render->GetNext(); + } + FEEndBatchRender(view); + ReadyToRender(); +} + +void FERenderObject::Clear(FEPackageRenderInfo *pkg_render_info) { + while (mobPolyList.GetHead() != mobPolyList.EndOfList()) { + FERenderEPoly *render = mobPolyList.GetHead(); + render->Remove(); + delete render; + } + mPolyCount = 0; + mulFlags &= ~2; + mulNumTimesRendered = 0; +} + +unsigned int ClipLeft(bVector3 *pDst, bVector2 *pDstUVs, bVector4 *pDstColors, + bVector3 *pSrc, bVector2 *pSrcUVs, bVector4 *pSrcColors, + unsigned int num_verts, float value) { + unsigned int new_num_verts = 0; + bool bFlag; + unsigned long last_vert; + + last_vert = num_verts - 1; + + if (pSrc[last_vert].x >= value) { + pDst[0] = pSrc[last_vert]; + pDstUVs[0] = pSrcUVs[last_vert]; + pDstColors[0] = pSrcColors[last_vert]; + new_num_verts = 1; + bFlag = true; + } else { + bFlag = false; + } + + if (num_verts != 0) { + for (unsigned long k = 0; k < num_verts; k++) { + if (pSrc[k].x >= value) { + if (!bFlag) { + float t; + pDst[new_num_verts] = pSrc[k] - pSrc[last_vert]; + t = (value - pSrc[last_vert].x) / pDst[new_num_verts].x; + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = pSrcUVs[k] - pSrcUVs[last_vert]; + pDstUVs[new_num_verts] *= t; + pDstUVs[new_num_verts].x += pSrcUVs[last_vert].x; + pDstUVs[new_num_verts].y += pSrcUVs[last_vert].y; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t); + pDstColors[new_num_verts] += pSrcColors[last_vert]; + new_num_verts++; + bFlag = true; + } + pDst[new_num_verts] = pSrc[k]; + pDstUVs[new_num_verts] = pSrcUVs[k]; + pDstColors[new_num_verts] = pSrcColors[k]; + new_num_verts++; + } else { + if (bFlag) { + float t; + pDst[new_num_verts] = pSrc[k] - pSrc[last_vert]; + t = (value - pSrc[last_vert].x) / pDst[new_num_verts].x; + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = pSrcUVs[k] - pSrcUVs[last_vert]; + pDstUVs[new_num_verts] *= t; + pDstUVs[new_num_verts].x += pSrcUVs[last_vert].x; + pDstUVs[new_num_verts].y += pSrcUVs[last_vert].y; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t); + pDstColors[new_num_verts] += pSrcColors[last_vert]; + new_num_verts++; + bFlag = false; + } + } + last_vert = k; + } + } + + return new_num_verts; +} + +unsigned int ClipTop(bVector3 *pDst, bVector2 *pDstUVs, bVector4 *pDstColors, + bVector3 *pSrc, bVector2 *pSrcUVs, bVector4 *pSrcColors, + unsigned int num_verts, float value) { + unsigned int new_num_verts = 0; + bool bFlag; + unsigned long last_vert; + + last_vert = num_verts - 1; + + if (pSrc[last_vert].y >= value) { + pDst[0] = pSrc[last_vert]; + pDstUVs[0] = pSrcUVs[last_vert]; + pDstColors[0] = pSrcColors[last_vert]; + new_num_verts = 1; + bFlag = true; + } else { + bFlag = false; + } + + if (num_verts != 0) { + for (unsigned long k = 0; k < num_verts; k++) { + if (pSrc[k].y >= value) { + if (!bFlag) { + float t; + pDst[new_num_verts] = pSrc[k] - pSrc[last_vert]; + t = (value - pSrc[last_vert].y) / pDst[new_num_verts].y; + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = pSrcUVs[k] - pSrcUVs[last_vert]; + pDstUVs[new_num_verts] *= t; + pDstUVs[new_num_verts].x += pSrcUVs[last_vert].x; + pDstUVs[new_num_verts].y += pSrcUVs[last_vert].y; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t); + pDstColors[new_num_verts] += pSrcColors[last_vert]; + new_num_verts++; + bFlag = true; + } + pDst[new_num_verts] = pSrc[k]; + pDstUVs[new_num_verts] = pSrcUVs[k]; + pDstColors[new_num_verts] = pSrcColors[k]; + new_num_verts++; + } else { + if (bFlag) { + float t; + pDst[new_num_verts] = pSrc[k] - pSrc[last_vert]; + t = (value - pSrc[last_vert].y) / pDst[new_num_verts].y; + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = pSrcUVs[k] - pSrcUVs[last_vert]; + pDstUVs[new_num_verts] *= t; + pDstUVs[new_num_verts].x += pSrcUVs[last_vert].x; + pDstUVs[new_num_verts].y += pSrcUVs[last_vert].y; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t); + pDstColors[new_num_verts] += pSrcColors[last_vert]; + new_num_verts++; + bFlag = false; + } + } + last_vert = k; + } + } + + return new_num_verts; +} + +unsigned int ClipRight(bVector3 *pDst, bVector2 *pDstUVs, bVector4 *pDstColors, + bVector3 *pSrc, bVector2 *pSrcUVs, bVector4 *pSrcColors, + unsigned int num_verts, float value) { + unsigned int new_num_verts = 0; + bool bFlag; + unsigned long last_vert; + + last_vert = num_verts - 1; + + if (pSrc[last_vert].x <= value) { + pDst[0] = pSrc[last_vert]; + pDstUVs[0] = pSrcUVs[last_vert]; + pDstColors[0] = pSrcColors[last_vert]; + new_num_verts = 1; + bFlag = true; + } else { + bFlag = false; + } + + if (num_verts != 0) { + for (unsigned long k = 0; k < num_verts; k++) { + if (pSrc[k].x <= value) { + if (!bFlag) { + float t; + pDst[new_num_verts] = pSrc[k] - pSrc[last_vert]; + t = (value - pSrc[last_vert].x) / pDst[new_num_verts].x; + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = pSrcUVs[k] - pSrcUVs[last_vert]; + pDstUVs[new_num_verts] *= t; + pDstUVs[new_num_verts].x += pSrcUVs[last_vert].x; + pDstUVs[new_num_verts].y += pSrcUVs[last_vert].y; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t); + pDstColors[new_num_verts] += pSrcColors[last_vert]; + new_num_verts++; + bFlag = true; + } + pDst[new_num_verts] = pSrc[k]; + pDstUVs[new_num_verts] = pSrcUVs[k]; + pDstColors[new_num_verts] = pSrcColors[k]; + new_num_verts++; + } else { + if (bFlag) { + float t; + pDst[new_num_verts] = pSrc[k] - pSrc[last_vert]; + t = (value - pSrc[last_vert].x) / pDst[new_num_verts].x; + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = pSrcUVs[k] - pSrcUVs[last_vert]; + pDstUVs[new_num_verts] *= t; + pDstUVs[new_num_verts].x += pSrcUVs[last_vert].x; + pDstUVs[new_num_verts].y += pSrcUVs[last_vert].y; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t); + pDstColors[new_num_verts] += pSrcColors[last_vert]; + new_num_verts++; + bFlag = false; + } + } + last_vert = k; + } + } + + return new_num_verts; +} + +unsigned int ClipBottom(bVector3 *pDst, bVector2 *pDstUVs, bVector4 *pDstColors, + bVector3 *pSrc, bVector2 *pSrcUVs, bVector4 *pSrcColors, + unsigned int num_verts, float value) { + unsigned int new_num_verts = 0; + bool bFlag; + unsigned long last_vert; + + last_vert = num_verts - 1; + + if (pSrc[last_vert].y <= value) { + pDst[0] = pSrc[last_vert]; + pDstUVs[0] = pSrcUVs[last_vert]; + pDstColors[0] = pSrcColors[last_vert]; + new_num_verts = 1; + bFlag = true; + } else { + bFlag = false; + } + + if (num_verts != 0) { + for (unsigned long k = 0; k < num_verts; k++) { + if (pSrc[k].y <= value) { + if (!bFlag) { + float t; + pDst[new_num_verts] = pSrc[k] - pSrc[last_vert]; + t = (value - pSrc[last_vert].y) / pDst[new_num_verts].y; + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = pSrcUVs[k] - pSrcUVs[last_vert]; + pDstUVs[new_num_verts] *= t; + pDstUVs[new_num_verts].x += pSrcUVs[last_vert].x; + pDstUVs[new_num_verts].y += pSrcUVs[last_vert].y; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t); + pDstColors[new_num_verts] += pSrcColors[last_vert]; + new_num_verts++; + bFlag = true; + } + pDst[new_num_verts] = pSrc[k]; + pDstUVs[new_num_verts] = pSrcUVs[k]; + pDstColors[new_num_verts] = pSrcColors[k]; + new_num_verts++; + } else { + if (bFlag) { + float t; + pDst[new_num_verts] = pSrc[k] - pSrc[last_vert]; + t = (value - pSrc[last_vert].y) / pDst[new_num_verts].y; + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = pSrcUVs[k] - pSrcUVs[last_vert]; + pDstUVs[new_num_verts] *= t; + pDstUVs[new_num_verts].x += pSrcUVs[last_vert].x; + pDstUVs[new_num_verts].y += pSrcUVs[last_vert].y; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t); + pDstColors[new_num_verts] += pSrcColors[last_vert]; + new_num_verts++; + bFlag = false; + } + } + last_vert = k; + } + } + + return new_num_verts; +} + +unsigned int FERenderObject::ClipAligned(FEClipInfo *pClipInfo, bVector3 *v, bVector2 *uv, + bVector4 *colors, bVector3 *nv, bVector2 *nuv, + bVector4 *ncolors) { + bVector3 *pDst = nv; + bVector2 *pDstUVs = nuv; + bVector4 *pDstColors = ncolors; + bVector3 *pSrc = v; + bVector2 *pSrcUVs = uv; + bVector4 *pSrcColors = colors; + unsigned long num_verts; + + num_verts = ClipLeft(pDst, pDstUVs, pDstColors, pSrc, pSrcUVs, pSrcColors, 4, pClipInfo->constants[3]); + if (!num_verts) return 0; + num_verts = ClipTop(pSrc, pSrcUVs, pSrcColors, pDst, pDstUVs, pDstColors, num_verts, pClipInfo->constants[0]); + if (!num_verts) return 0; + num_verts = ClipRight(pDst, pDstUVs, pDstColors, pSrc, pSrcUVs, pSrcColors, num_verts, pClipInfo->constants[1]); + if (!num_verts) return 0; + num_verts = ClipBottom(pSrc, pSrcUVs, pSrcColors, pDst, pDstUVs, pDstColors, num_verts, pClipInfo->constants[2]); + if (!num_verts) return 0; + return num_verts; +} + +unsigned int FERenderObject::ClipGeneral(FEClipInfo *pClipInfo, bVector3 *v, bVector2 *uv, + bVector4 *colors, bVector3 *nv, bVector2 *nuv, + bVector4 *ncolors) { + bVector3 *pSrc = v; + bVector2 *pSrcUVs = uv; + bVector4 *pSrcColors = colors; + bVector3 *pDst = nv; + bVector2 *pDstUVs = nuv; + bVector4 *pDstColors = ncolors; + unsigned long num_verts = 4; + + for (int i = 0; i < 4; i++) { + bVector3 normal(pClipInfo->normals[i]); + float constant = pClipInfo->constants[i]; + bool bFlag; + unsigned long last_vert; + unsigned long new_num_verts; + + last_vert = num_verts - 1; + + if (bDot(&normal, &pSrc[last_vert]) + constant > -0.5f) { + pDst[0] = pSrc[last_vert]; + pDstUVs[0] = pSrcUVs[last_vert]; + pDstColors[0] = pSrcColors[last_vert]; + new_num_verts = 1; + bFlag = true; + } else { + new_num_verts = 0; + bFlag = false; + } + + if (num_verts != 0) { + for (unsigned long k = 0; k < num_verts; k++) { + if (bDot(&normal, &pSrc[k]) + constant > -0.5f) { + if (!bFlag) { + bVector3 diff = pSrc[k] - pSrc[last_vert]; + pDst[new_num_verts] = diff; + float t = -(bDot(&normal, &pSrc[last_vert]) + constant) / bDot(&normal, &diff); + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = (pSrcUVs[k] - pSrcUVs[last_vert]) * t + pSrcUVs[last_vert]; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t) + pSrcColors[last_vert]; + new_num_verts++; + bFlag = true; + } + pDst[new_num_verts] = pSrc[k]; + pDstUVs[new_num_verts] = pSrcUVs[k]; + pDstColors[new_num_verts] = pSrcColors[k]; + new_num_verts++; + } else { + if (bFlag) { + bVector3 diff = pSrc[k] - pSrc[last_vert]; + pDst[new_num_verts] = diff; + float t = -(bDot(&normal, &pSrc[last_vert]) + constant) / bDot(&normal, &diff); + pDst[new_num_verts] *= t; + pDst[new_num_verts] += pSrc[last_vert]; + pDstUVs[new_num_verts] = (pSrcUVs[k] - pSrcUVs[last_vert]) * t + pSrcUVs[last_vert]; + pDstColors[new_num_verts] = V4Mult(pSrcColors[k] - pSrcColors[last_vert], t) + pSrcColors[last_vert]; + new_num_verts++; + bFlag = false; + } + } + last_vert = k; + } + } + + void *pTmp; + pTmp = pSrc; pSrc = pDst; pDst = static_cast(pTmp); + pTmp = pSrcUVs; pSrcUVs = pDstUVs; pDstUVs = static_cast(pTmp); + pTmp = pSrcColors; pSrcColors = pDstColors; pDstColors = static_cast(pTmp); + + num_verts = new_num_verts; + if (!num_verts) return 0; + } + + return num_verts; +} + +FERenderEPoly *FERenderObject::AddPoly(float x0, float y0, float x1, float y1, float z, + float s0, float t0, float s1, float t1, + unsigned int *colors, FEPackageRenderInfo *pkg_render_info) { + FERenderEPoly *render = new FERenderEPoly(); + ePoly *pPoly = &render->EPoly; + render->pTextureMask = nullptr; + render->pTexture = nullptr; + mobPolyList.AddTail(render); + mPolyCount++; + + pPoly->Vertices[0].x = x0; + pPoly->Vertices[0].y = y0; + pPoly->Vertices[0].z = z; + pPoly->Vertices[1].x = x1; + pPoly->Vertices[1].y = y0; + pPoly->Vertices[1].z = z; + pPoly->Vertices[2].x = x1; + pPoly->Vertices[2].y = y1; + pPoly->Vertices[2].z = z; + pPoly->Vertices[3].x = x0; + pPoly->Vertices[3].y = y1; + pPoly->Vertices[3].z = z; + + bMulMatrix(&pPoly->Vertices[0], &mstTransform, &pPoly->Vertices[0]); + bMulMatrix(&pPoly->Vertices[1], &mstTransform, &pPoly->Vertices[1]); + bMulMatrix(&pPoly->Vertices[2], &mstTransform, &pPoly->Vertices[2]); + bMulMatrix(&pPoly->Vertices[3], &mstTransform, &pPoly->Vertices[3]); + + pPoly->Vertices[0].z = z; + pPoly->Vertices[1].z = z; + pPoly->Vertices[2].z = z; + pPoly->Vertices[3].z = z; + + pPoly->UVs[0][0] = s0; + pPoly->UVs[0][1] = t0; + pPoly->UVs[0][2] = s1; + pPoly->UVs[0][3] = t0; + pPoly->UVs[1][0] = s1; + pPoly->UVs[1][1] = t1; + pPoly->UVs[1][2] = s0; + pPoly->UVs[1][3] = t1; + + reinterpret_cast(pPoly->Colours)[0] = colors[0]; + reinterpret_cast(pPoly->Colours)[1] = colors[1]; + reinterpret_cast(pPoly->Colours)[2] = colors[2]; + reinterpret_cast(pPoly->Colours)[3] = colors[3]; + + pPoly->SetFlailer(1); + pPoly->SetFlags(1); + + return render; +} + +void FERenderObject::AddPoly(float x0, float y0, float x1, float y1, float z, + float s0, float t0, float s1, float t1, + unsigned int *colors, TextureInfo *texture, + FEPackageRenderInfo *pkg_render_info) { + FERenderEPoly *render = AddPoly(x0, y0, x1, y1, z, s0, t0, s1, t1, colors, pkg_render_info); + if (render) { + render->pTexture = texture; + } +} + +void FERenderObject::AddPoly(float x0, float y0, float x1, float y1, float z, + float s0, float t0, float s1, float t1, + unsigned int *in_colors, FEClipInfo *pClipInfo, + FEPackageRenderInfo *pkg_render_info) { + if (!pClipInfo) { + AddPoly(x0, y0, x1, y1, z, s0, t0, s1, t1, in_colors, pkg_render_info); + return; + } + + bVector3 v[8]; + bVector2 uv[8]; + bVector4 colors[8]; + bVector3 nv[8]; + bVector2 nuv[8]; + bVector4 ncolors[8]; + unsigned int packed_colors[8]; + unsigned char *color_bytes = reinterpret_cast(in_colors); + + v[0].x = x0; + v[0].y = y0; + v[0].z = z; + v[1].x = x1; + v[1].y = y0; + v[1].z = z; + v[2].x = x1; + v[2].y = y1; + v[2].z = z; + v[3].x = x0; + v[3].y = y1; + v[3].z = z; + + uv[0].x = s0; + uv[0].y = t0; + uv[1].x = s1; + uv[1].y = t0; + uv[2].x = s1; + uv[2].y = t1; + uv[3].x = s0; + uv[3].y = t1; + + for (unsigned int i = 0; i < 4; i++) { + colors[i].x = color_bytes[i * 4 + 0] * (1.0f / 255.0f); + colors[i].y = color_bytes[i * 4 + 1] * (1.0f / 255.0f); + colors[i].z = color_bytes[i * 4 + 2] * (1.0f / 255.0f); + colors[i].w = color_bytes[i * 4 + 3] * (1.0f / 255.0f); + } + + bMulMatrix(&v[0], &mstTransform, &v[0]); + bMulMatrix(&v[1], &mstTransform, &v[1]); + bMulMatrix(&v[2], &mstTransform, &v[2]); + bMulMatrix(&v[3], &mstTransform, &v[3]); + + unsigned int num_verts = (pClipInfo->flags & 1) ? ClipAligned(pClipInfo, v, uv, colors, nv, nuv, ncolors) + : ClipGeneral(pClipInfo, v, uv, colors, nv, nuv, ncolors); + if (!num_verts || num_verts == 2) { + return; + } + + for (unsigned int i = 0; i < num_verts; i++) { + unsigned char *packed_bytes = reinterpret_cast(&packed_colors[i]); + packed_bytes[0] = static_cast(colors[i].x * 255.0f); + packed_bytes[1] = static_cast(colors[i].y * 255.0f); + packed_bytes[2] = static_cast(colors[i].z * 255.0f); + packed_bytes[3] = static_cast(colors[i].w * 255.0f); + } + + for (unsigned int i = 0; i < num_verts - 2; i++) { + FERenderEPoly *render = new FERenderEPoly(); + ePoly *pPoly = &render->EPoly; + render->pTextureMask = nullptr; + render->pTexture = nullptr; + mobPolyList.AddTail(render); + mPolyCount++; + + pPoly->Vertices[0] = v[0]; + pPoly->Vertices[1] = v[i + 1]; + pPoly->Vertices[2] = v[i + 2]; + pPoly->Vertices[3] = v[i + 2]; + + pPoly->UVs[0][0] = uv[0].x; + pPoly->UVs[0][1] = uv[0].y; + pPoly->UVs[0][2] = uv[i + 1].x; + pPoly->UVs[0][3] = uv[i + 1].y; + pPoly->UVs[1][0] = uv[i + 2].x; + pPoly->UVs[1][1] = uv[i + 2].y; + pPoly->UVs[1][2] = uv[i + 2].x; + pPoly->UVs[1][3] = uv[i + 2].y; + + reinterpret_cast(pPoly->Colours)[0] = packed_colors[0]; + reinterpret_cast(pPoly->Colours)[1] = packed_colors[i + 1]; + reinterpret_cast(pPoly->Colours)[2] = packed_colors[i + 2]; + reinterpret_cast(pPoly->Colours)[3] = packed_colors[i + 2]; + + pPoly->SetFlailer(1); + pPoly->SetFlags(1); + } +} + +void FERenderObject::AddPolyWithRotatedMask(float x0, float y0, float x1, float y1, float z, + float s0, float t0, float s1, float t1, + float ms0, float mt0, float ms1, float mt1, + float ms2, float mt2, float ms3, float mt3, + unsigned int *colors, TextureInfo *texture, + TextureInfo *textureMask) { + FERenderEPoly *render = new FERenderEPoly(); + ePoly *pPoly = &render->EPoly; + render->pTexture = texture; + render->pTextureMask = textureMask; + mobPolyList.AddTail(render); + mPolyCount++; + + pPoly->Vertices[0].x = x0; + pPoly->Vertices[0].y = y0; + pPoly->Vertices[0].z = z; + pPoly->Vertices[1].x = x1; + pPoly->Vertices[1].y = y0; + pPoly->Vertices[1].z = z; + pPoly->Vertices[2].x = x1; + pPoly->Vertices[2].y = y1; + pPoly->Vertices[2].z = z; + pPoly->Vertices[3].x = x0; + pPoly->Vertices[3].y = y1; + pPoly->Vertices[3].z = z; + + bMulMatrix(&pPoly->Vertices[0], &mstTransform, &pPoly->Vertices[0]); + bMulMatrix(&pPoly->Vertices[1], &mstTransform, &pPoly->Vertices[1]); + bMulMatrix(&pPoly->Vertices[2], &mstTransform, &pPoly->Vertices[2]); + bMulMatrix(&pPoly->Vertices[3], &mstTransform, &pPoly->Vertices[3]); + + pPoly->SetFlailer(5); + + pPoly->Vertices[0].z = z; + pPoly->Vertices[1].z = z; + pPoly->Vertices[2].z = z; + pPoly->Vertices[3].z = z; + + pPoly->UVs[0][0] = s0; + pPoly->UVs[0][1] = t0; + pPoly->UVs[0][2] = s1; + pPoly->UVs[0][3] = t0; + pPoly->UVs[1][0] = s1; + pPoly->UVs[1][1] = t1; + pPoly->UVs[1][2] = s0; + pPoly->UVs[1][3] = t1; + + pPoly->UVs[2][0] = ms0; + pPoly->UVs[2][1] = mt0; + pPoly->UVs[2][2] = ms1; + pPoly->UVs[2][3] = mt1; + pPoly->UVs[3][0] = ms2; + pPoly->UVs[3][1] = mt2; + pPoly->UVs[3][2] = ms3; + pPoly->UVs[3][3] = mt3; + + reinterpret_cast(pPoly->Colours)[0] = colors[0]; + reinterpret_cast(pPoly->Colours)[1] = colors[1]; + reinterpret_cast(pPoly->Colours)[2] = colors[2]; + reinterpret_cast(pPoly->Colours)[3] = colors[3]; + + pPoly->SetFlags(1); +} + +extern void *bOMalloc(SlotPool *pool); +extern void bMemSet(void *dst, int val, unsigned int size); + +cFEngRender::cFEngRender() { + Highwater = 0; + FERenderObject::Initialize(); + bMemSet(RContexts, 0, sizeof(RContexts)); +} + +FERenderObject *cFEngRender::CreateCachedRender(FEObject *object, TextureInfo *texture_info) { + void *mem = bOMalloc(mpobFERenderObjectSlotPool); + FERenderObject *ret = new (mem) FERenderObject(object, texture_info); + object->Cached = ret; + return ret; +} + +void cFEngRender::RemoveCachedRender(FEObject *object, FEPackageRenderInfo *sp) { + FERenderObject *cached = FindCachedRender(object); + if (cached) { + object->Cached = nullptr; + cached->Clear(sp); + delete cached; + } +} diff --git a/src/Speed/Indep/Src/Frontend/FERenderObject.hpp b/src/Speed/Indep/Src/Frontend/FERenderObject.hpp index ce86fcb06..3dfea0f7e 100644 --- a/src/Speed/Indep/Src/Frontend/FERenderObject.hpp +++ b/src/Speed/Indep/Src/Frontend/FERenderObject.hpp @@ -8,12 +8,22 @@ #include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" #include "Speed/Indep/Src/Ecstasy/Texture.hpp" #include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/Frontend/cFEngRender.hpp" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bSlotPool.hpp" + +struct FEClipInfo; +struct FEPackageRenderInfo; + +extern SlotPool *mpobFERenderObjectSlotPool; // total size: 0xA4 class FERenderEPoly : public bTNode { - private: + public: + void *operator new(unsigned int size); + void operator delete(void *p); + ePoly EPoly; // offset 0x8, size 0x94 TextureInfo *pTexture; // offset 0x9C, size 0x4 TextureInfo *pTextureMask; // offset 0xA0, size 0x4 @@ -21,7 +31,48 @@ class FERenderEPoly : public bTNode { // total size: 0x64 class FERenderObject : public bTNode { - private: + public: + FERenderObject(FEObject *obj, TextureInfo *tex); + ~FERenderObject(); + void operator delete(void *p) { + bFree(mpobFERenderObjectSlotPool, p); + } + void SetTransform(bMatrix4 *pMatrix); + void Render(); + void Clear(FEPackageRenderInfo *pkg_render_info); + FERenderEPoly *AddPoly(float x0, float y0, float x1, float y1, float z, + float s0, float t0, float s1, float t1, + unsigned int *in_colors, FEPackageRenderInfo *pkg_render_info); + void AddPoly(float x0, float y0, float x1, float y1, float z, + float s0, float t0, float s1, float t1, + unsigned int *in_colors, TextureInfo *texture, FEPackageRenderInfo *pkg_render_info); + void AddPoly(float x0, float y0, float x1, float y1, float z, + float s0, float t0, float s1, float t1, + unsigned int *in_colors, FEClipInfo *pClipInfo, FEPackageRenderInfo *pkg_render_info); + void AddPolyWithRotatedMask(float x0, float y0, float x1, float y1, float z, + float s0, float t0, float s1, float t1, + float ms0, float mt0, float ms1, float mt1, + float ms2, float mt2, float ms3, float mt3, + unsigned int *in_colors, TextureInfo *texture, TextureInfo *mask); + unsigned int ClipGeneral(FEClipInfo *pClipInfo, bVector3 *v, bVector2 *uv, bVector4 *colors, + bVector3 *nv, bVector2 *nuv, bVector4 *ncolors); + unsigned int ClipAligned(FEClipInfo *pClipInfo, bVector3 *v, bVector2 *uv, bVector4 *colors, + bVector3 *nv, bVector2 *nuv, bVector4 *ncolors); + static void Initialize(); + + void ReadyToRender() { + mulNumTimesRendered++; + mulFlags |= 2; + } + + bool IsReadyToRender() { + return (mulFlags & 2) != 0; + } + + void SetTexture(TextureInfo *texture) { + mpobTexture = texture; + } + FEObject *mpobOwner; // offset 0x8, size 0x4 unsigned int mulFlags; // offset 0xC, size 0x4 unsigned int mulNumTimesRendered; // offset 0x10, size 0x4 diff --git a/src/Speed/Indep/Src/Frontend/FEngFont.cpp b/src/Speed/Indep/Src/Frontend/FEngFont.cpp index e69de29bb..d4f4952c7 100644 --- a/src/Speed/Indep/Src/Frontend/FEngFont.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngFont.cpp @@ -0,0 +1,597 @@ +#include "Speed/Indep/Src/Frontend/FEngFont.hpp" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/Frontend/FERenderObject.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include + +extern unsigned long FEHashUpper(const char *str); +extern int bStrLen(const char *str); +extern int bStrICmp(const char *s1, const char *s2); +extern const char *GetLanguageName(int language_id); +extern TextureInfo *GetTextureInfo(unsigned int hash, int param2, int param3); +extern void bMemSet(void *dst, int val, unsigned int size); +extern void WideToCharString(char *dst, unsigned int dstSize, const short *src); +extern int bStrCmp(const char *s1, const char *s2); +extern unsigned int bStringHashUpper(const char *str); +extern unsigned int FEngColorToEpolyColor(FEColor c); + +TextureInfo *FixupTextureInfoNull(TextureInfo *info, unsigned int hash, TexturePack *pack, bool loading); + +struct ExtraFontData { + unsigned int FontHash; + float BaselineOffset; + float LeadingScale; +}; + +static ExtraFontData ExtraFontDataTable[] = { + {0xDCA5485A, 18.0f, 2.0f}, + {0x833A8678, 22.0f, 2.0f}, + {0xF88A75F9, 18.0f, 1.0f}, + {0x71C777D7, 23.0f, 1.0f}, +}; + +bTList FEngFonts; +extern unsigned int FontReplacementTable[]; +extern int NumFontReplacements; + +ExtraFontData *FindExtraFontData(unsigned int font_hash) { + for (int i = 0; i < 4; i++) { + if (font_hash == ExtraFontDataTable[i].FontHash) { + return &ExtraFontDataTable[i]; + } + } + return nullptr; +} + +FEngFont *FindFont(unsigned int font_hash) { + { + FEngFont *f = FEngFonts.GetHead(); + while (f != FEngFonts.EndOfList()) { + if (f->GetHashID() == font_hash) { + return f; + } + f = f->GetNext(); + } + } + { + for (int i = 0; i < NumFontReplacements; i++) { + unsigned int match_font = FontReplacementTable[i * 2]; + unsigned int replace_font = FontReplacementTable[i * 2 + 1]; + if (font_hash == match_font) { + FEngFont *f = FEngFonts.GetHead(); + while (f != FEngFonts.EndOfList()) { + if (f->GetHashID() == replace_font) { + return f; + } + f = f->GetNext(); + } + } + } + } + return nullptr; +} + +void *FEngMalloc(unsigned int size, const char *file, int line); +void FEngFree(void *ptr, const char *file, int line); + +int LoaderFEngFont(bChunk *chunk) { + if (chunk->GetID() != 0x30201) { + return 0; + } + void *mem = FEngMalloc(sizeof(FEngFont), nullptr, 0); + FEngFont *font = new(mem) FEngFont(chunk); + FEngFonts.AddHead(font); + ExtraFontData *extra = FindExtraFontData(chunk->GetID()); + if (extra) { + font->fBaselineOffset = extra->BaselineOffset; + font->fLeadingScale = extra->LeadingScale; + } + return 1; +} + +int UnloaderFEngFont(bChunk *chunk) { + if (chunk->GetID() != 0x30201) { + return 0; + } + unsigned int hashID = FEHashUpper(static_cast(chunk->GetData())); + FEngFont *font = FindFont(hashID); + if (font) { + FEngFonts.Remove(font); + delete font; + } + return 1; +} + +void FEngFontNotifyTextureLoading(TexturePack *texture_pack, bool loading) { + { + FEngFont *font = FEngFonts.GetHead(); + while (font != FEngFonts.EndOfList()) { + font->NotifyTextureLoading(texture_pack, loading); + font = font->GetNext(); + } + } +} + +float FEngFont::GetHeight() { + return Height; +} + +FEngFont::FEngFont(bChunk *chunk) + : pTextureInfo(nullptr) // + , pFont(nullptr) // + , mfZValue(0.0f) // + , FontHash(0) // + , TextureHash(0) // + , pFontName(static_cast(chunk->GetData()) + 0) // + , pTextureName(static_cast(chunk->GetData()) + 0x100) // + , Height(0.0f) // + , fBaselineOffset(0.0f) // + , fLeadingScale(0.0f) +{ + unsigned int raw_font_hash = FEHashUpper(pFontName); + + int n = 0; + do { + int len = bStrLen(pFontName); + if (len <= n) { + pFont = RealFontOld::Font::Create(pTextureName + 0x100); + FontHash = FEHashUpper(pFontName); + TextureHash = FEHashUpper(pTextureName); + Height = static_cast(pFont->GetHeight()); + pTextureInfo = ::GetTextureInfo(TextureHash, 0, 0); + ExtraFontData *efd = FindExtraFontData(raw_font_hash); + if (!efd) { + fBaselineOffset = 0.0f; + } else { + fBaselineOffset = efd->BaselineOffset; + fLeadingScale = efd->LeadingScale; + } + if (!efd) { + fLeadingScale = 1.0f; + } + return; + } + for (int language_id = 0; n = n + 1, language_id < 0x10; language_id++) { + const char *lang_name = GetLanguageName(language_id); + if (bStrICmp(pFontName + n - 1, lang_name) == 0 && (n - 1) > 0 && pFontName[n - 2] == '_') { + pFontName[n - 2] = '\0'; + break; + } + } + } while (true); +} + +FEngFont::~FEngFont() { + RealFontOld::Font::Destroy(pFont); +} + +unsigned short FEngFont::ConvertCharacter(unsigned short c) { + if (c > 0xFF7F) { + c = c & 0xFF; + } + if (c == 0x99) { + return 0x2122; + } + if (c == 0x9C) { + return 0x153; + } + if (c != 0xA0) { + return c; + } + return 0x20; +} + +float FEngFont::CalculateXOffset(unsigned int ulJustification, float fTextWidth) { + if (ulJustification & 1) { + return fTextWidth * -0.5f; + } + if (ulJustification & 2) { + return -fTextWidth; + } + return 0.0f; +} + +float FEngFont::CalculateYOffset(unsigned int ulJustification, float fTextHeight) { + if (ulJustification & 4) { + return fTextHeight * -0.5f; + } + if (ulJustification & 8) { + return -fTextHeight; + } + return 0.0f; +} + +void FEngFont::NotifyTextureLoading(TexturePack *texture_pack, bool loading) { + TextureInfo *info = FixupTextureInfoNull(pTextureInfo, TextureHash, texture_pack, loading); + if (info != pTextureInfo) { + pTextureInfo = info; + } +} + +bool FEngFont::IsJoyEventTexture(const short *pInputString, unsigned long Flags) { + int count = 0; + if (!pInputString || (Flags & 0x820)) { + return false; + } + if (*pInputString != '$') { + return false; + } + pInputString++; + short c = *pInputString; + while (c != 0 && c != '$') { + pInputString++; + count++; + c = *pInputString; + } + return count != 0; +} + +const short *FEngFont::SkipJoyEventTexture(const short *pInputString, unsigned long Flags) { + if (!pInputString) { + return pInputString; + } + if (Flags & 0x820) { + return pInputString; + } + if (*pInputString != '$') { + return pInputString; + } + pInputString++; + if (*pInputString == '$') { + return pInputString; + } + while (*pInputString != 0 && *pInputString != '$') { + pInputString++; + } + return pInputString + 1; +} + +float FEngFont::GetJoyEventTextureWidth(const short *pInputString) { + float result = 0.0f; + const TextureInfo *info = GetJoyEventTextureInfo(pInputString); + if (info) { + result = static_cast(*(reinterpret_cast(reinterpret_cast(info) + 0x44))); + } + return result; +} + +const TextureInfo *FEngFont::GetJoyEventTextureInfo(const short *pInputString) { + unsigned int texture_hash; + if (*pInputString == '$') { + short data[64]; + short *ptr_to_data = data; + bMemSet(ptr_to_data, 0, 0x80); + const short *ptr = pInputString + 1; + unsigned int bytes_copied = 0; + if (ptr[0] != '$' && ptr[0] != 0) { + while (true) { + short c = *ptr; + bytes_copied += 2; + ptr++; + short next = *ptr; + *ptr_to_data = c; + ptr_to_data++; + if (next == '$') break; + if (next == 0 || bytes_copied > 0x7F) break; + } + } + char buffer[128]; + WideToCharString(buffer, 0x80, data); + bStrCmp(buffer, "ICON_SPACER"); + } + return ::GetTextureInfo(0, 1, 0); +} + +const short *FEngFont::HandleJoyEventTexture(const short *input, float fX, float fY, unsigned int *render_colors, FERenderObject *cached, float &advance, FEPackageRenderInfo *pkg_render_info) { + const short *ptr = input; + short data[64]; + short *ptr_to_data = data; + bMemSet(ptr_to_data, 0, 0x80); + short c = *input; + unsigned int bytes_copied = 0; + if (c != '$' && c != 0) { + while (true) { + *ptr_to_data = c; + bytes_copied += 2; + ptr++; + c = *ptr; + ptr_to_data++; + if (c == '$') break; + if (c == 0 || bytes_copied > 0x7F) break; + } + } + char buffer[128]; + WideToCharString(buffer, 0x80, data); + unsigned int hash = bStringHashUpper(buffer); + TextureInfo *pTexInfo = ::GetTextureInfo(hash, 1, 0); + float width = static_cast(*reinterpret_cast(reinterpret_cast(pTexInfo) + 0x44)); + float height = static_cast(*reinterpret_cast(reinterpret_cast(pTexInfo) + 0x46)); + float y0 = -(height * 0.5f); + cached->AddPoly(fX, y0, fX + width, y0 + height, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, render_colors, pTexInfo, pkg_render_info); + advance = width; + return ptr + 1; +} + +float FEngFont::GetNextWordWidth(const short *pcString, unsigned long flags) { + float next_word_size = 0.0f; + const short *prev_char = pcString - 1; + const short *next_char = pcString; + while ((flags & 0x200) == 0) { + next_word_size += GetCharacterWidth(*next_char, *prev_char, flags); + short next = next_char[1]; + if (next == ' ' || next == 0) break; + if (IsNewlineChar(next)) break; + prev_char = next_char; + next_char++; + } + return next_word_size; +} + +float FEngFont::GetCharacterWidth(short Char, short PrevChar, unsigned long Flags) { + float Width = 0.0f; + if ((Flags & 0x20) == 0) { + if (IsNewlineChar(Char)) { + return Width; + } + } + if (Char == '\r') { + return Width; + } + unsigned short converted = ConvertCharacter(static_cast(Char)); + unsigned int unicode = converted; + if ((Flags & 0x80) && unicode == 0xA0) { + PrevChar = 0; + unicode = 0x20; + } + const RealFontOld::Glyph *pGlyph = pFont->GetGlyph(static_cast(unicode)); + if (!pGlyph) { + pGlyph = RealFontOld::BSearch(static_cast(unicode), + reinterpret_cast(reinterpret_cast(pFont) + pFont->mGlyphTbl), + pFont->mNum); + } + if (pGlyph) { + if (PrevChar != 0) { + Width += static_cast(pFont->GetKern(pGlyph, PrevChar)); + } + Width += static_cast(pGlyph->mAdvanceX); + } + return Width; +} + +float FEngFont::GetLineWidth(const short *pcString, unsigned long flags, unsigned long maxWidth, bool word_wrap) { + float lastSpaceWidth = 0.0f; + float width = 0.0f; + if (!pcString) { + return width; + } + short c = *pcString; + pcString++; + unsigned long k = 0; + if (c != 0) { + do { + if (IsNewlineChar(c)) break; + if (c == ' ') { + lastSpaceWidth = width; + } + short prev; + if (k == 0) { + prev = 0; + } else { + prev = *(pcString - 2); + } + width += GetCharacterWidth(c, prev, flags); + if (maxWidth != 0 && static_cast(maxWidth) < width && word_wrap) { + if (0.0f < lastSpaceWidth) { + width = lastSpaceWidth; + } + break; + } + c = *pcString; + k++; + pcString++; + } while (c != 0); + } + return width; +} + +float FEngFont::GetTextWidth(const short *pcString, unsigned long flags) { + float width = GetLineWidth(pcString, 0, 0, false); + short c = *pcString; + pcString++; + if (c != 0) { + do { + if ((flags & 0x20) == 0) { + if (IsNewlineChar(c)) { + goto next; + } + } + { + float newWidth = GetLineWidth(pcString, 0, 0, false); + if (newWidth > width) { + width = newWidth; + } + } + next: + c = *pcString; + pcString++; + } while (c != 0); + } + return width; +} + +float FEngFont::GetTextHeight(const short *pcString, int ilLeading, unsigned long flags, unsigned long maxWidth, bool word_wrap) { + float height = 0.0f; + if (!pcString) { + return height; + } + bool lastCharNotReturn = true; + bool newLine = false; + float curLineWidth = 0.0f; + short prev = 0; + short c = *pcString; + const short *next = pcString + 1; + if (c != 0) { + do { + if (IsNewlineChar(c)) { + newLine = true; + if (newLine) { + newLine = false; + lastCharNotReturn = false; + curLineWidth = 0.0f; + height = static_cast(ilLeading) + height + Height; + } + } else if (c != '\r') { + unsigned int unicode = static_cast(c) & 0xFF; + const RealFontOld::Glyph *pGlyph = pFont->GetGlyph(static_cast(unicode)); + if (!pGlyph) { + pGlyph = RealFontOld::BSearch(static_cast(unicode), + reinterpret_cast(reinterpret_cast(pFont) + pFont->mGlyphTbl), + pFont->mNum); + } + if (pGlyph) { + lastCharNotReturn = true; + } + if (word_wrap && maxWidth != 0) { + if (c == ' ') { + float next_word_size = GetNextWordWidth(next - 1, flags); + if (static_cast(maxWidth) < curLineWidth + next_word_size) { + newLine = true; + } + } + curLineWidth += GetCharacterWidth(c, prev, flags); + } + if (newLine) { + newLine = false; + lastCharNotReturn = false; + curLineWidth = 0.0f; + height = static_cast(ilLeading) + height + Height; + } + } + short s = *next; + next++; + prev = c; + c = s; + } while (c != 0); + } + if (lastCharNotReturn) { + height += Height; + } + return height; +} + +void FEngFont::RenderString(const FEColor &Color, const short *pcString, FEString *obj, bMatrix4 *matrix, FERenderObject *cached, FEPackageRenderInfo *pkg_render_info) { + unsigned long flags = obj->Flags; + unsigned long format = obj->Format; + bool word_wrap = (format & 0x10) != 0; + int leading = static_cast(static_cast(obj->Leading) * fLeadingScale); + unsigned int render_color = FEngColorToEpolyColor(Color); + unsigned int render_colors[4]; + render_colors[0] = render_color; + render_colors[1] = render_color; + render_colors[2] = render_color; + render_colors[3] = render_color; + + float line_width = GetLineWidth(pcString, flags, obj->MaxWidth, word_wrap); + float current_y = CalculateYOffset(format, GetTextHeight(pcString, leading, flags, obj->MaxWidth, word_wrap)); + float current_x = CalculateXOffset(format, line_width); + + if (pTextureInfo) { + cached->SetTransform(matrix); + + float line_start_x = current_x; + short current = *pcString; + const short *next = pcString + 1; + int character_index = 0; + bool allow_joy_event_texture = true; + + while (current != 0) { + if (current != ' ' || current_x != line_start_x || !word_wrap) { + if ((flags & 0x20) == 0 && IsNewlineChar(current)) { + if (*next == 0) { + break; + } + + current_x = CalculateXOffset(format, GetLineWidth(next, flags, obj->MaxWidth, word_wrap)); + current_y += Height + static_cast(leading); + line_start_x = current_x; + } else { + if (obj->MaxWidth != 0 && current == ' ' && word_wrap) { + float next_word_width = GetNextWordWidth(next - 1, flags); + if ((current_x - line_start_x) + next_word_width > static_cast(obj->MaxWidth)) { + current_x = CalculateXOffset(format, GetLineWidth(next, flags, obj->MaxWidth, word_wrap)); + current_y += Height + static_cast(leading); + line_start_x = current_x; + } + } + + if ((flags & 0x820) == 0 && current == '$') { + if (*next == '$') { + current = *next; + character_index++; + allow_joy_event_texture = false; + next++; + } else if (allow_joy_event_texture) { + float advance = 0.0f; + next = HandleJoyEventTexture(next, current_x, current_y, render_colors, cached, advance, pkg_render_info); + current_x += advance; + goto next_character; + } + } + + unsigned short unicode = ConvertCharacter(static_cast(current)); + int glyph_stride = (pFont->mFlags & 0x40000) ? 0x10 : 0x0C; + const RealFontOld::Glyph *glyph = pFont->GetGlyph(static_cast(unicode)); + if (!glyph) { + glyph = RealFontOld::BSearch(static_cast(unicode), + reinterpret_cast(reinterpret_cast(pFont) + pFont->mGlyphTbl), + pFont->mNum, + glyph_stride); + } + + if (glyph) { + float kern = 0.0f; + if (character_index != 0 && next[-2] != 0) { + kern = static_cast(pFont->GetKern(glyph, next[-2])); + } + + float texture_width = static_cast(pTextureInfo->Width); + float texture_height = static_cast(pTextureInfo->Height); + float glyph_width = static_cast(glyph->mWidth); + if (glyph_width < 4.0f) { + glyph_width = 4.0f; + } + + float x0 = current_x + kern + static_cast(glyph->mOffsetX); + float y0 = current_y + static_cast(glyph->mOffsetY) + fBaselineOffset; + cached->AddPoly(x0, + y0, + x0 + glyph_width, + y0 + static_cast(glyph->mHeight), + 1.0f, + static_cast(glyph->mU) / texture_width, + static_cast(glyph->mV) / texture_height, + static_cast(glyph->mU + glyph->mWidth + 1) / texture_width, + static_cast(glyph->mV + glyph->mHeight) / texture_height, + render_colors, + pkg_render_info); + + short prev_char = 0; + if (character_index != 0) { + prev_char = next[-2]; + } + current_x += GetCharacterWidth(static_cast(unicode), prev_char, format); + } + } + } + + next_character: + current = *next; + character_index++; + next++; + } + + cached->Render(); + } +} diff --git a/src/Speed/Indep/Src/Frontend/FEngFont.hpp b/src/Speed/Indep/Src/Frontend/FEngFont.hpp index 179a5f17b..a428234f6 100644 --- a/src/Speed/Indep/Src/Frontend/FEngFont.hpp +++ b/src/Speed/Indep/Src/Frontend/FEngFont.hpp @@ -5,6 +5,61 @@ #pragma once #endif -void FEngFontNotifyTextureLoading(struct TexturePack *texture_pack /* r29 */, bool loading /* r30 */); +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/Src/Frontend/RealFontOld.hpp" + +struct bChunk; +struct FEColor; +struct FEString; +struct FERenderObject; +struct FEPackageRenderInfo; +struct TextureInfo; +struct TexturePack; +struct bMatrix4; + +// total size: 0x30 +struct FEngFont : public bTNode { + float GetHeight(); + float GetTextWidth(const short *pcString, unsigned long flags); + float GetTextHeight(const short *pcString, int ilLeading, unsigned long flags, unsigned long maxWidth, bool word_wrap); + float CalculateXOffset(unsigned int ulJustification, float fTextWidth); + float CalculateYOffset(unsigned int ulJustification, float fTextHeight); + + FEngFont(bChunk *chunk); + ~FEngFont(); + + void NotifyTextureLoading(TexturePack *texture_pack, bool loading); + + static bool IsJoyEventTexture(const short *pInputString, unsigned long Flags); + static const short *SkipJoyEventTexture(const short *pInputString, unsigned long Flags); + float GetJoyEventTextureWidth(const short *pInputString); + const TextureInfo *GetJoyEventTextureInfo(const short *pInputString); + const short *HandleJoyEventTexture(const short *input, float fX, float fY, unsigned int *render_colors, FERenderObject *cached, float &advance, FEPackageRenderInfo *pkg_render_info); + + void RenderString(const FEColor &Color, const short *pcString, FEString *obj, bMatrix4 *matrix, FERenderObject *cached, FEPackageRenderInfo *pkg_render_info); + + float GetNextWordWidth(const short *pcString, unsigned long flags); + float GetCharacterWidth(short Char, short PrevChar, unsigned long Flags); + float GetLineWidth(const short *pcString, unsigned long flags, unsigned long maxWidth, bool word_wrap); + + unsigned short ConvertCharacter(unsigned short c); + + TextureInfo *GetTextureInfo() { return pTextureInfo; } + unsigned int GetHashID() { return FontHash; } + + TextureInfo *pTextureInfo; // offset 0x8, size 0x4 + RealFontOld::Font *pFont; // offset 0xC, size 0x4 + float mfZValue; // offset 0x10, size 0x4 + unsigned int FontHash; // offset 0x14, size 0x4 + unsigned int TextureHash; // offset 0x18, size 0x4 + char *pFontName; // offset 0x1C, size 0x4 + char *pTextureName; // offset 0x20, size 0x4 + float Height; // offset 0x24, size 0x4 + float fBaselineOffset; // offset 0x28, size 0x4 + float fLeadingScale; // offset 0x2C, size 0x4 +}; + +void FEngFontNotifyTextureLoading(TexturePack *texture_pack, bool loading); +FEngFont *FindFont(unsigned int handle); #endif diff --git a/src/Speed/Indep/Src/Frontend/FEngFrontend.cpp b/src/Speed/Indep/Src/Frontend/FEngFrontend.cpp index e69de29bb..1b2e5331a 100644 --- a/src/Speed/Indep/Src/Frontend/FEngFrontend.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngFrontend.cpp @@ -0,0 +1,88 @@ +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" + +#include + +extern int bVSPrintf(char *buf, const char *fmt, va_list args); +extern unsigned int bStringHash(const char *str); +extern bool IsJoystickTypeWheel(JoystickPort port); +extern unsigned int Button_Action_Hashes_GAMECUBE[][5]; +extern unsigned int Button_Action_Hashes_GAMECUBE_Wheel[][5]; + +unsigned int FindButtonNameHashForFEString(int config, int string_number, JoystickPort player) { + unsigned int (*hashes)[5]; + if (IsJoystickTypeWheel(player)) { + hashes = Button_Action_Hashes_GAMECUBE_Wheel; + } else { + hashes = Button_Action_Hashes_GAMECUBE; + } + return hashes[string_number][config]; +} + +extern int bStrLen(const unsigned short *s); + +void FEngSNMakeHidden(char *outBuffer, int out_buf_size, const char *strInput) { + int nLen = bStrLen(strInput); + int i = 0; + if (i < nLen && i != out_buf_size - 1) { + do { + outBuffer[i] = '*'; + i++; + if (i >= nLen) break; + } while (i != out_buf_size - 1); + } + outBuffer[i] = '\0'; +} + +void FEngSNMakeHidden(char *outBuffer, int out_buf_size, unsigned short *strInput) { + int nLen = bStrLen(strInput); + int i = 0; + if (i < nLen && i != out_buf_size - 1) { + do { + outBuffer[i] = '*'; + i++; + if (i >= nLen) break; + } while (i != out_buf_size - 1); + } + outBuffer[i] = '\0'; +} + +int FEngMapJoyParamToJoyport(int feng_param) { + if (feng_param & 1) return 0; + if (feng_param & 2) return 1; + if (feng_param & 4) return 2; + if (feng_param & 8) return 3; + return -1; +} + +int FEngMapJoyportToJoyParam(int joyport) { + if (joyport == 0) return 1; + if (joyport == 1) return 2; + if (joyport == 2) return 4; + if (joyport == 3) return 8; + return 0; +} + +void FEngTickSinglePackage(const char *pkg_name, unsigned int ticks) { + FEPackage *single_package = cFEng::Get()->FindPackage(pkg_name); + if (single_package) { + single_package->SetTickIncrement(ticks); + FEObject *pObject = single_package->GetFirstObject(); + while (pObject) { + single_package->UpdateObject(pObject, ticks); + pObject = pObject->GetNext(); + } + } +} + +unsigned int FEngHashString(const char *fmt, ...) { + unsigned int result; + char buffer[256]; + va_list args; + va_start(args, fmt); + bVSPrintf(buffer, fmt, args); + va_end(args); + result = bStringHash(buffer); + return result; +} diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp index e69de29bb..64538addb 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEGameInterface.cpp @@ -0,0 +1,216 @@ +#include "Speed/Indep/Src/FEng/FEGameInterface.h" +#include "Speed/Indep/Src/FEng/FEResourceRequest.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEList.h" +#include "Speed/Indep/Src/Frontend/cFEngRender.hpp" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/Src/Frontend/FEPackageManager.hpp" +#include "Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +bool FEngTestForIntersection(float xPos, float yPos, FEObject* obj); +char* GetBaseName(char* dest, const char* path); +void bToUpper(char* str); +FEPackageRenderInfo* HACK_FEPkgMgr_GetPackageRenderInfo(FEPackage* pkg); + +extern int g_discErrorOccured; +extern char* FEngPleaseRenderSinglePackage; + +static FEColor gNormal(0xFFE6E6C8u); +static FEColor gTint(0xFFFFAF41u); +static FEColor gRapsheet(0xFFAAE646u); + +cFEngGameInterface* cFEngGameInterface::pInstance; + +bool FEGameInterface::UnloadUnreferencedLibrary(FEPackage*) { + return false; +} + +void FEGameInterface::RenderObjectList(FEObjectListEntry *pList, unsigned long Count) { + while (Count) { + Count--; + RenderObject(pList[Count].pObject); + } +} + +bool FEGameInterface::SetCellData(FECodeListBox*, unsigned long, unsigned long) { + return false; +} + +cFEngGameInterface::cFEngGameInterface() { + RenderThisPackage = true; + iGameMode = 0; +} + +cFEngGameInterface::~cFEngGameInterface() { +} + +bool cFEngGameInterface::LoadResources(FEPackage* pPackage, long Count, FEResourceRequest* pList) { + for (int i = 0; i < Count; i++) { + char filename[256]; + GetBaseName(filename, pList[i].pFilename); + bToUpper(filename); + unsigned int length = pList[i].Type; + switch (length) { + case 1: + case 2: + pList[i].Handle = bStringHash(filename); + pList[i].UserParam = 0; + break; + case 4: { + void* mem = bMalloc(256, "TODO", __LINE__, 0); + bStrNCpy(static_cast(mem), filename, 256); + pList[i].Handle = reinterpret_cast(mem); + pList[i].UserParam = 0; + break; + } + case 3: + default: + pList[i].Handle = bStringHash(filename); + pList[i].UserParam = 0; + break; + } + } + return true; +} + +bool cFEngGameInterface::UnloadResources(FEPackage* pPackage, long Count, FEResourceRequest* pList) { + for (int i = 0; i < Count; i++) { + if (pList[i].Type == 4) { + bFree(reinterpret_cast(pList[i].Handle)); + } + } + return true; +} + +void cFEngGameInterface::NotificationMessage(unsigned long Message, FEObject* pObject, unsigned long Param1, unsigned long Param2) { + if (Message != 0x5922615 && Message != 0x7e4d1288) { + FEPackageManager::Get()->NotificationMessage(Message, pObject, Param1, Param2); + } +} + +void cFEngGameInterface::NotifySoundMessage(unsigned long Message, FEObject* pObject, unsigned long ControlMask, unsigned long pPackagePtr) { + FEPackageManager::Get()->NotifySoundMessage(Message, pObject, ControlMask, pPackagePtr); +} + +void cFEngGameInterface::GenerateRenderContext(unsigned short uContext, FEObject* pObject) { + cFEngRender::mInstance->GenerateRenderContext(uContext, pObject); +} + +bool cFEngGameInterface::GetContextTransform(unsigned short uContext, FEMatrix4& Matrix) { + Matrix.Identify(); + if (uContext != 0) { + RenderContext* ctxt = cFEngRender::mInstance->GetRenderContext(uContext); + if (ctxt != nullptr) { + Matrix = *reinterpret_cast(ctxt); + } + } + return true; +} + +void cFEngGameInterface::RenderObject(FEObject* pObject) { + bool visible = false; + if (!(pObject->Flags & 1) && RenderThisPackage) { + visible = true; + } + if (pObject->Flags & 0x10) { + if (iGameMode == 0) { + pObject->GetObjData()->Col = gNormal; + } else if (iGameMode == 1) { + pObject->GetObjData()->Col = gTint; + } else if (iGameMode == 2) { + pObject->GetObjData()->Col = gRapsheet; + } + } + if (visible) { + cFEngRender::mInstance->AddToRenderList(pObject); + } +} + +void cFEngGameInterface::GetViewTransformation(FEMatrix4* pView) { + pView->Identify(); +} + +void cFEngGameInterface::BeginPackageRendering(FEPackage* pPackage) { + RenderThisPackage = true; + if (g_discErrorOccured != 0 && pPackage->GetNameHash() != 0x942C98B5u) { + RenderThisPackage = false; + } + if (FEngPleaseRenderSinglePackage != nullptr) { + if (FEHashUpper(FEngPleaseRenderSinglePackage) != pPackage->GetNameHash()) { + RenderThisPackage = false; + } + } + if (!FEPackageManager::Get()->GetVisibility(pPackage->GetName())) { + RenderThisPackage = false; + } + cFEngRender::mInstance->PrepForPackage(pPackage); +} + +void cFEngGameInterface::EndPackageRendering(FEPackage* pPackage) { + cFEngRender::mInstance->PackageFinished(pPackage); +} + +void cFEngGameInterface::PackageWasLoaded(FEPackage* pPackage) { + pPackage->InitializePackage(); + pPackage->bExecuting = true; + if (!pPackage->bIsLibrary) { + pPackage->Update(cFEng::Get()->mFEng, 0); + } + FEPackageManager::Get()->PackageWasLoaded(pPackage); + { + FEngMovieStarter movie_starter(pPackage); + pPackage->ForAllObjects(movie_starter); + FEngHidePCObjects pcHideObjects; + pPackage->ForAllObjects(pcHideObjects); + FEngTransferFlagsToChildren transfer_to_children(4); + pPackage->ForAllObjects(transfer_to_children); + } + if (GRaceStatus::Exists()) { + iGameMode = 1; + } else { + if (FEDatabase != nullptr && FEDatabase->IsRapSheetMode()) { + iGameMode = 2; + } else { + iGameMode = 0; + } + } +} + +bool cFEngGameInterface::PackageWillUnload(FEPackage* pPackage) { + FEngMovieStopper movie_stop; + pPackage->ForAllObjects(movie_stop); + RenderObjectDisconnect disconnect; + disconnect.pFEngRenderer = cFEngRender::mInstance; + disconnect.PkgRenderInfo = HACK_FEPkgMgr_GetPackageRenderInfo(pPackage); + pPackage->ForAllObjects(disconnect); + FEPackageManager::Get()->PackageWillBeUnloaded(pPackage); + return true; +} + +void HackClearCache(FEPackage* pkg) { + RenderObjectDisconnect disconnect; + disconnect.pFEngRenderer = cFEngRender::mInstance; + disconnect.PkgRenderInfo = HACK_FEPkgMgr_GetPackageRenderInfo(pkg); + pkg->ForAllObjects(disconnect); +} + +unsigned char* cFEngGameInterface::GetPackageData(const char* pPackageName, unsigned char** pBlockStart, bool& bDeleteBlock) { + bDeleteBlock = false; + return static_cast(FEPackageManager::Get()->GetPackageData(pPackageName)); +} + +unsigned long cFEngGameInterface::GetJoyPadMask(unsigned char feng_pad_index) { + return cFEngJoyInput::mInstance->GetJoyPadMask(feng_pad_index); +} + +void cFEngGameInterface::GetMouseInfo(FEMouseInfo&) {} + +void cFEngGameInterface::OutputWarning(const char*, FEng_WarningLevel) {} + +bool cFEngGameInterface::DoesPointTouchObject(float xPos, float yPos, FEObject* pButton) { + return FEngTestForIntersection(xPos, yPos, pButton); +} diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp index e69de29bb..27c8cfcfd 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterface.cpp @@ -0,0 +1,314 @@ +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEGameInterface.h" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp" +#include "Speed/Indep/Src/Frontend/FEPackageManager.hpp" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" + +#include + +void SoundPause(bool pause, eSNDPAUSE_REASON reason); +void SetSoundControlState(bool set, eSNDCTLSTATE state, const char* name); +void HideEverySingleHud(); +int bStrCmp(const char* s1, const char* s2); +FEPackageRenderInfo* HACK_FEPkgMgr_GetPackageRenderInfo(FEPackage* pkg); + +Timer MessengerCreationTimer(0); +extern float RealTimeElapsed; + +cFEng* cFEng::mInstance; + +void cFEng::Init() { + if (mInstance == nullptr) { + mInstance = new cFEng(); + MessengerCreationTimer.ResetLow(); + } +} + +cFEng::cFEng() { + bWasPaused = false; + mFEng = new FEngine(); + mFEng->SetInterface(cFEngGameInterface::pInstance); + mFEng->SetNumJoyPads(2); + mFEng->SetInitialState(); + mFEng->SetExecution(true); +} + +void cFEng::PushErrorPackage(const char* pPackageName, int pArg, unsigned long ControlMask) { + if (FEDatabase == nullptr) { + if (cFEng::Get()->IsPackagePushed(pPackageName)) { + return; + } + FEPackageManager::Get()->SetPackageDataArg(pPackageName, pArg); + FEPackage* pPackage = mFEng->PushPackage(pPackageName, FE_PACKAGE_PRIORITY_ERROR, ControlMask); + pPackage->SetErrorScreen(true); + mFEng->ToggleErrorScreenMode(true); + if (!FEManager::IsPaused() || bWasPaused) { + bWasPaused = true; + FEManager::RequestPauseSimulation(pPackageName); + PauseAllSystems(); + } + } else { + FEPackageManager::Get()->SetPackageDataArg(pPackageName, pArg); + FEPackage* pPackage = mFEng->PushPackage(pPackageName, FE_PACKAGE_PRIORITY_ERROR, ControlMask); + pPackage->SetErrorScreen(true); + mFEng->ToggleErrorScreenMode(true); + if (!FEManager::IsPaused() || bWasPaused) { + bWasPaused = true; + FEManager::RequestPauseSimulation(pPackageName); + PauseAllSystems(); + } + } +} + +void cFEng::PopErrorPackage() { + if (FEDatabase == nullptr) { + mFEng->QueuePackagePop(); + mFEng->ToggleErrorScreenMode(false); + ServiceFengOnly(); + if (bWasPaused) { + ResumeAllSystems(false); + FEManager::RequestUnPauseSimulation(nullptr); + } + } else { + mFEng->QueuePackagePop(); + mFEng->ToggleErrorScreenMode(false); + ServiceFengOnly(); + if (bWasPaused) { + ResumeAllSystems(true); + if (FEManager::IsPaused()) { + FEManager::RequestUnPauseSimulation(nullptr); + } + } + } +} + +void cFEng::PopErrorPackage(int port) { + mFEng->QueuePackagePop(); + mFEng->ToggleErrorScreenMode(false); + FEManager* feManager = FEManager::Get(); + if (port != -1) { + feManager->ClearControllerError(port); + } + if (bWasPaused) { + if (!feManager->WaitingForControllerError()) { + ResumeAllSystems(true); + } + FEManager::RequestUnPauseSimulation(nullptr); + } +} + +void cFEng::PauseAllSystems() { + if (UTL::Collections::Singleton::Get()) { + UTL::Collections::Singleton::Get()->Pause(); + } + SoundPause(true, static_cast(-1)); + SetSoundControlState(true, SNDSTATE_ERROR, "PauseAllSystems"); +} + +void cFEng::ResumeAllSystems(bool flushActions) { + SoundPause(false, static_cast(-1)); + SetSoundControlState(false, SNDSTATE_ERROR, "PauseAllSystems"); + if (UTL::Collections::Singleton::Get()) { + UTL::Collections::Singleton::Get()->UnPause(); + } + if (flushActions) { + cFEngJoyInput::Get()->FlushActions(); + } +} + +void cFEng::QueuePackagePush(const char* pPackageName, int pArg, unsigned long ControlMask, bool pSuppressSimPause) { + PrintLoadedPackages(); + if (TheGameFlowManager.IsInGame() && !pSuppressSimPause && !FEManager::IsPaused()) { + FEManager::RequestPauseSimulation(pPackageName); + HideEverySingleHud(); + bool push_bkg = IsPackagePushed("InGameBackground.fng"); + if (!push_bkg) { + mFEng->QueuePackagePush("InGameBackground.fng", 0); + } + } + FEPackageManager::Get()->SetPackageDataArg(pPackageName, pArg); + mFEng->QueuePackagePush(pPackageName, ControlMask); + if (!cFEngJoyInput::Get()->IsJoyEnabled(kJP_NumPorts)) { + cFEngJoyInput::Get()->JoyEnable(kJP_NumPorts, true); + } +} + +void cFEng::QueuePackagePop(int numPackagesToPop) { + const int numPackagesPushed = mFEng->GetNumPackagesBelowPriority(FE_PACKAGE_PRIORITY_FIFTH_CLOSEST); + if (numPackagesToPop < 1 || numPackagesToPop > numPackagesPushed) { + numPackagesToPop = numPackagesPushed; + } + PrintLoadedPackages(); + for (int i = 0; i < numPackagesToPop; i++) { + mFEng->QueuePackagePop(); + } + if (mFEng->GetNumPackagesBelowPriority(FE_PACKAGE_PRIORITY_FIFTH_CLOSEST) < 1) { + if (TheGameFlowManager.IsInGame() && FEManager::IsPaused()) { + FEManager::RequestUnPauseSimulation(nullptr); + } + if (cFEngJoyInput::Get()->IsJoyEnabled(kJP_NumPorts)) { + cFEngJoyInput::Get()->JoyDisable(kJP_NumPorts, true); + } + } +} + +void cFEng::QueuePackageSwitch(const char* pPackageName, int pArg, unsigned long ControlMask, bool pSuppressSimPause) { + PrintLoadedPackages(); + if (TheGameFlowManager.IsInGame() && !pSuppressSimPause && !FEManager::IsPaused()) { + FEManager::RequestPauseSimulation(pPackageName); + HideEverySingleHud(); + bool push_bkg = IsPackagePushed("InGameBackground.fng"); + if (!push_bkg) { + mFEng->QueuePackagePush("InGameBackground.fng", 0); + } + } + FEPackageManager::Get()->SetPackageDataArg(pPackageName, pArg); + mFEng->QueuePackageSwitch(pPackageName, ControlMask); + if (cFEngJoyInput::Get() && !cFEngJoyInput::Get()->IsJoyEnabled(kJP_NumPorts)) { + cFEngJoyInput::Get()->JoyEnable(kJP_NumPorts, true); + } +} + +void cFEng::PushNoControlPackage(const char* pPackageName, FE_PACKAGE_PRIORITY pPriority) { + mFEng->PushPackage(pPackageName, pPriority, 0); +} + +void cFEng::PopNoControlPackage(const char* pPackageName) { + FEPackage* packageToPop = FEPackageManager::Get()->FindPackage(pPackageName); + mFEng->UnloadPackage(packageToPop); +} + +void cFEng::Service() { + mFEng->Update(static_cast(RealTimeElapsed * 1000.0f), + IsGameFlowInGame() && WorldTimeElapsed > 0.0f); +} + +void cFEng::ServiceFengOnly() { + mFEng->Update(0, 0); +} + +void cFEng::DrawForeground() { + mFEng->Render(); +} + +FEPackage* cFEng::FindPackageWithControl() { + FEPackageList* packageList = mFEng->GetPackageList(); + if (packageList != nullptr) { + for (FEPackage* package = packageList->GetLastPackage(); package != nullptr; package = package->GetPrev()) { + if (package->GetControlMask() != 0) { + return package; + } + } + } + return nullptr; +} + +FEPackage* cFEng::FindPackageAtBase() { + FEPackageList* packageList = mFEng->GetPackageList(); + if (packageList != nullptr) { + return packageList->GetFirstPackage(); + } + return nullptr; +} + +FEPackage* cFEng::FindPackageActive(const char* pPackageName) { + FEPackageList* packageList = mFEng->GetPackageList(); + return packageList->FindPackage(pPackageName); +} + +FEPackage* cFEng::FindPackageIdle(const char* pPackageName) { + return mFEng->FindIdlePackage(pPackageName); +} + +FEPackage* cFEng::FindPackage(const char* pPackageName) { + if (pPackageName != nullptr && strlen(pPackageName) != 0) { + if (!FEPackageData::IsInScreenConstructor()) { + FEPackage* package = FindPackageActive(pPackageName); + if (package != nullptr) { + return package; + } + package = FindPackageIdle(pPackageName); + if (package != nullptr) { + return package; + } + } else { + return FEPackageManager::Get()->FindPackage(pPackageName); + } + } + return nullptr; +} + +bool cFEng::IsPackagePushed(const char* pPackageName) { + FEPackage* package; + if (FEPackageData::IsInScreenConstructor()) { + package = FEPackageManager::Get()->FindPackage(pPackageName); + } else { + package = FindPackageActive(pPackageName); + } + return package != nullptr; +} + +bool cFEng::IsPackageInControl(const char* pPackageName) { + FEPackage* packageWithCtrl = FindPackageWithControl(); + if (packageWithCtrl != nullptr) { + return bStrCmp(pPackageName, packageWithCtrl->GetName()) == 0; + } + return false; +} + +void cFEng::PrintLoadedPackages() {} + +void cFEng::QueueMessage(unsigned int pMessage, const char* pPackageName, FEObject* to, unsigned int controlMask) { + if (pPackageName != nullptr) { + FEPackage* package = FindPackage(pPackageName); + if (package != nullptr) { + mFEng->QueueMessage(pMessage, nullptr, package, to, controlMask); + } + } else { + FEPackageList* pkg_list = mFEng->GetPackageList(); + if (pkg_list != nullptr) { + for (FEPackage* pkg = pkg_list->GetFirstPackage(); pkg != nullptr; pkg = pkg->GetNext()) { + mFEng->QueueMessage(pMessage, nullptr, pkg, to, 0); + } + } + } +} + +void cFEng::QueueGameMessage(unsigned int pMessage, const char* pPackageName, unsigned int controlMask) { + QueueMessage(pMessage, pPackageName, reinterpret_cast(-1), controlMask); +} + +void cFEng::QueueGameMessagePkg(unsigned int pMessage, FEPackage* topkg) { + mFEng->QueueMessage(pMessage, nullptr, topkg, reinterpret_cast(-1), 0); +} + +void cFEng::QueuePackageMessage(unsigned int pMessage, const char* pPackageName, FEObject* obj) { + if (obj != nullptr) { + QueueMessage(pMessage, pPackageName, obj, 0xFF); + } else { + QueueMessage(pMessage, pPackageName, reinterpret_cast(-4), 0xFF); + } +} + +void cFEng::QueueSoundMessage(unsigned int pMessage, const char* pPackageName) { + FEPackage* package = FindPackage(pPackageName); + if (package != nullptr) { + mFEng->QueueMessage(pMessage, nullptr, package, reinterpret_cast(-5), 0); + } +} + +void cFEng::MakeLoadedPackagesDirty() { + FEPackageList* pkg_list = mFEng->GetPackageList(); + if (pkg_list != nullptr) { + ObjectDirtySetter dirt; + for (FEPackage* pkg = pkg_list->GetFirstPackage(); pkg != nullptr; pkg = pkg->GetNext()) { + dirt.pRenderInfo = HACK_FEPkgMgr_GetPackageRenderInfo(pkg); + pkg->ForAllObjects(dirt); + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEButtons.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEButtons.cpp index e69de29bb..05e4e2ce0 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEButtons.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEButtons.cpp @@ -0,0 +1,48 @@ +#include "Speed/Indep/Src/Frontend/FEPackageManager.hpp" + +void FEngSetCurrentButton(const char* pkg_name, unsigned int hash) { + FEPackage* pkg = FEPackageManager::Get()->FindPackage(pkg_name); + if (pkg != nullptr) { + FEButtonMap* map = pkg->GetButtonMap(); + FEObject* button = nullptr; + for (unsigned long i = 0; i < map->GetCount(); i++) { + FEObject* btn = map->GetButton(i); + if (btn->NameHash == hash) { + button = btn; + } + } + if (button != nullptr) { + pkg->SetCurrentButton(button, true); + } + } +} + +FEObject* FEngGetCurrentButton(const char* pkg_name) { + FEPackage* pkg = FEPackageManager::Get()->FindPackage(pkg_name); + FEObject* obj = nullptr; + if (pkg != nullptr) { + obj = pkg->GetCurrentButton(); + } + return obj; +} + +void FEngSetButtonState(const char* pkg_name, unsigned int button_hash, bool enabled) { + FEPackage* pkg = FEPackageManager::Get()->FindPackage(pkg_name); + if (pkg != nullptr) { + FEButtonMap* map = pkg->GetButtonMap(); + FEObject* button = nullptr; + for (unsigned long i = 0; i < map->GetCount(); i++) { + FEObject* btn = map->GetButton(i); + if (btn->NameHash == button_hash) { + button = btn; + } + } + if (button != nullptr) { + if (enabled == true) { + button->Flags &= ~0x4000000; + } else { + button->Flags |= 0x4000000; + } + } + } +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEImages.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEImages.cpp index e69de29bb..fadbcc6e4 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEImages.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEImages.cpp @@ -0,0 +1,31 @@ +FEImage* FEngFindImage(const char* pkg_name, int name_hash) { + FEObject* obj = FEngFindObject(pkg_name, name_hash); + if (obj == nullptr || obj->Type != FE_Image) { + return nullptr; + } + return static_cast(obj); +} + +unsigned int FEngGetTextureHash(FEImage* image) { + if (image != nullptr) { + return image->Handle; + } + return 0; +} + +void FEngSetTextureHash(FEImage* image, unsigned int texture_hash) { + if (image == nullptr) { + return; + } + if (image->Handle == texture_hash) { + return; + } + image->Handle = texture_hash; + image->Flags |= 0x2400000; +} + +void FEngSetButtonTexture(FEImage* img, unsigned int tex_hash) { + if (img != nullptr) { + FEngSetTextureHash(img, tex_hash); + } +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEMovies.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEMovies.cpp index e69de29bb..5859e20b9 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEMovies.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEMovies.cpp @@ -0,0 +1,9 @@ +#include "Speed/Indep/Src/FEng/FEMovie.h" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +void FEngSetMovieName(FEMovie* movie, const char* name) { + if (movie != nullptr) { + bStrNCpy(reinterpret_cast(movie->Handle), name, 0x100); + movie->Flags |= 0x400000; + } +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEObjects.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEObjects.cpp index e69de29bb..bdf1c0ac9 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEObjects.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEObjects.cpp @@ -0,0 +1,752 @@ +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/FEng/FEScript.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/feimage.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/FEng/FEObjectCallback.h" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/FEngFont.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/Src/Ecstasy/Texture.hpp" +int bStrLen(const unsigned short* s); +int GetLocalizedWideString(short* buffer, int bufSize, unsigned int hash); +TextureInfo* GetTextureInfo(unsigned int handle, int param2, int param3); +void bMatrixToQuaternion(bQuaternion& quat, const bMatrix4& m); + +#include "Speed/Indep/Src/Frontend/FEObjectCallbacks.hpp" + +FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash) { + if (pkg_name != nullptr) { + FEPackage* pkg = cFEng::Get()->FindPackage(pkg_name); + if (pkg != nullptr) { + FEObject* obj = pkg->FindObjectByHash(obj_hash); + if (obj != nullptr) { + return obj; + } + } + } + return nullptr; +} + +FEObject* FEngFindGroup(const char* pkg_name, unsigned int obj_hash) { + FEObject* obj = FEngFindObject(pkg_name, obj_hash); + if (obj == nullptr || obj->Type != FE_Group) { + return nullptr; + } + return obj; +} + +void FEngSetInvisible(FEObject* obj) { + if (obj != nullptr) { + obj->Flags |= 1; + obj->Flags |= 0x2400000; + if (obj->Type == FE_Group) { + FEGroup* group = static_cast(obj); + FEObject* child = group->GetFirstChild(); + int num = group->GetNumChildren(); + for (int i = 0; i < num; i++) { + FEngSetInvisible(child); + child = child->GetNext(); + } + } + } +} + +void FEngSetVisible(FEObject* obj) { + if (obj != nullptr && !(obj->Flags & 8)) { + obj->Flags = (obj->Flags & ~1) | 0x2400000; + if (obj->Type == FE_Group) { + FEGroup* group = static_cast(obj); + FEObject* child = group->GetFirstChild(); + int num = group->GetNumChildren(); + for (int i = 0; i < num; i++) { + FEngSetVisible(child); + child = child->GetNext(); + } + } + } +} + +void FEngSetAllObjectsInPackageVisibility(const char* pkg_name, bool visible) { + FEPackage* pkg = cFEng::Get()->FindPackage(pkg_name); + if (pkg != nullptr) { + ObjectVisibilitySetter vis(visible); + pkg->ForAllObjects(vis); + } +} + +void FEngSetScript(FEObject* object, unsigned int script_hash, bool start_at_beginning) { + if (object != nullptr) { + FEScript* script = object->FindScript(script_hash); + if (script != nullptr) { + object->SetScript(script, false); + if (start_at_beginning) { + script->CurTime = 0; + } + } + } +} + +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning) { + FEObject* object = FEngFindObject(pkg_name, obj_hash); + FEngSetScript(object, script_hash, start_at_beginning); +} + +FEScript* FEngGetScript(FEObject* object, unsigned int script_hash) { + if (object == nullptr) { + return nullptr; + } + return object->FindScript(script_hash); +} + +FEScript* FEngGetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash) { + FEObject* obj = FEngFindObject(pkg_name, obj_hash); + return FEngGetScript(obj, script_hash); +} + +bool FEngIsScriptSet(FEObject* obj, unsigned int script_hash); +bool FEngIsScriptRunning(FEObject* object, unsigned int script_hash); + +bool FEngIsScriptSet(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash) { + FEObject* object = FEngFindObject(pkg_name, obj_hash); + return FEngIsScriptSet(object, script_hash); +} + +bool FEngIsScriptSet(FEObject* obj, unsigned int script_hash) { + if (obj == nullptr) return false; + FEScript* script = obj->pCurrentScript; + if (script == nullptr) return false; + if (script->ID != script_hash) return false; + return true; +} + +bool FEngIsScriptRunning(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash) { + FEObject* object = FEngFindObject(pkg_name, obj_hash); + return FEngIsScriptRunning(object, script_hash); +} + +bool FEngIsScriptRunning(FEObject* object, unsigned int script_hash) { + if (object != nullptr) { + FEScript* cur = object->pCurrentScript; + if (cur != nullptr && cur->ID == script_hash && cur->CurTime <= cur->Length) { + return true; + } + } + return false; +} + +void FEngSetRotationZ(FEObject* obj, float angle_degrees) { + if (obj != nullptr) { + float rad_angle = bDegToRad(angle_degrees); + bMatrix4 b; + bIdentity(&b); + float cosVal = bCos(rad_angle); + b.v1.y = cosVal; + b.v0.x = cosVal; + float sinVal = bSin(rad_angle); + b.v0.y = sinVal; + b.v1.x = -sinVal; + bQuaternion q; + bMatrixToQuaternion(q, b); + FEQuaternion feq; + feq.x = q.x; + feq.y = q.y; + feq.z = q.z; + feq.w = q.w; + obj->SetRotation(feq, false); + } +} + +void FEngSetMultiImageRot(FEMultiImage* image, float angle_degrees) { + if (image == nullptr) { + return; + } + FEMultiImageData* image_data = static_cast(image->GetObjData()); + image_data->PivotRot.z = angle_degrees; + image->Flags |= 0x400000; +} + +void FEngSetMultiImageBottomRightUVs(FEMultiImage* image, FEVector2& bottomRightUVs, int textureNumber) { + if (image != nullptr) { + FEVector2 currTopLeftUVs; + FEVector2 currBottomRightUVs; + image->GetUVs(textureNumber, currTopLeftUVs, currBottomRightUVs); + image->SetUVs(textureNumber, currTopLeftUVs, bottomRightUVs); + image->Flags |= 0x400000; + } +} + +void FEngGetTopLeft(FEObject* object, float& x, float& y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + FEVector3& pos = data->Pos; + FEVector3& size = data->Size; + + switch (object->Type) { + case FE_Group: + break; + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: + x = pos.x - size.x * 0.5f; + y = pos.y - size.y * 0.5f; + break; + case FE_String: { + FEngFont* pFont = FindFont(object->Handle); + if (pFont != nullptr) { + FEString* pStr = static_cast(object); + const short* characters = nullptr; + int label_hash = pStr->GetLabelHash(); + if (!(object->Flags & 2)) { + short localized_string_buffer[1024]; + if (GetLocalizedWideString(localized_string_buffer, 0x800, label_hash)) { + characters = localized_string_buffer; + } + } + if (characters == nullptr) { + characters = pStr->GetString(); + } + float width = size.x * pFont->GetTextWidth(characters, object->Flags); + float height = size.y * pFont->GetTextHeight(characters, pStr->Leading, object->Flags, pStr->MaxWidth, (pStr->Format >> 4) & 1); + x = pos.x + pFont->CalculateXOffset(pStr->Format, width); + y = pos.y + pFont->CalculateYOffset(pStr->Format, height); + } + break; + } + case FE_Model: + case FE_List: + case FE_CodeList: + default: + x = pos.x; + y = pos.y; + break; + } +} + +void FEngSetTopLeft(FEObject* object, float x, float y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + FEVector3 pos(data->Pos); + FEVector3 size(data->Size); + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: + pos.x = x + size.x * 0.5f; + pos.y = y + size.y * 0.5f; + break; + case FE_String: { + FEngFont* pFont = FindFont(object->Handle); + if (pFont != nullptr) { + FEString* pStr = static_cast(object); + float width = size.x * pFont->GetTextWidth(pStr->GetString(), 0); + float height = size.y * pFont->GetTextHeight(pStr->GetString(), pStr->Leading, 0, 0, false); + pos.x = x - pFont->CalculateXOffset(pStr->Format, width); + pos.y = y - pFont->CalculateYOffset(pStr->Format, height); + } + break; + } + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + pos.x = x; + pos.y = y; + break; + } + + object->SetPosition(pos, false); +} + +void FEngGetBottomRight(FEObject* object, float& x, float& y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + FEVector3& pos = data->Pos; + FEVector3& size = data->Size; + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: + x = size.x * 0.5f + pos.x; + y = size.y * 0.5f + pos.y; + break; + case FE_String: { + FEngFont* pFont = FindFont(object->Handle); + if (pFont != nullptr) { + FEString* pStr = static_cast(object); + const short* characters = nullptr; + int label_hash = pStr->GetLabelHash(); + if (!(object->Flags & 2)) { + short localized_string_buffer[1024]; + if (GetLocalizedWideString(localized_string_buffer, 0x800, label_hash)) { + characters = localized_string_buffer; + } + } + if (characters == nullptr) { + characters = pStr->GetString(); + } + float width = size.x * pFont->GetTextWidth(characters, 0); + float height = size.y * pFont->GetTextHeight(characters, pStr->Leading, 0, 0, false); + x = pos.x + pFont->CalculateXOffset(pStr->Format, width) + width; + y = pos.y + pFont->CalculateYOffset(pStr->Format, height) + height; + } + break; + } + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + x = pos.x; + y = pos.y; + break; + } +} + +void FEngSetBottomRight(FEObject* object, float x, float y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + FEVector3 pos(data->Pos); + FEVector3 size(data->Size); + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: + pos.x = x - size.x * 0.5f; + pos.y = y - size.y * 0.5f; + break; + case FE_String: + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + pos.x = x; + pos.y = y; + break; + } + + object->SetPosition(pos, false); +} + +void FEngGetCenter(FEObject* object, float& x, float& y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + FEVector3& pos = data->Pos; + FEVector3 size(data->Size); + + switch (object->Type) { + case FE_String: { + FEngFont* pFont = FindFont(object->Handle); + if (pFont != nullptr) { + FEString* pStr = static_cast(object); + const short* characters = nullptr; + int label_hash = pStr->GetLabelHash(); + if (!(object->Flags & 2)) { + short localized_string_buffer[1024]; + if (GetLocalizedWideString(localized_string_buffer, 0x800, label_hash)) { + characters = localized_string_buffer; + } + } + if (characters == nullptr) { + characters = pStr->GetString(); + } + float width = size.x * pFont->GetTextWidth(characters, 0); + float height = size.y * pFont->GetTextHeight(characters, pStr->Leading, 0, 0, false); + x = pos.x + pFont->CalculateXOffset(pStr->Format, width) + width * 0.5f; + y = pos.y + pFont->CalculateYOffset(pStr->Format, height) + height * 0.5f; + } + break; + } + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: + x = pos.x; + y = pos.y; + break; + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + x = pos.x; + y = pos.y; + break; + } +} + +void FEngSetCenter(FEObject* object, float x, float y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + FEVector3 pos(data->Pos); + + switch (object->Type) { + case FE_String: { + FEngFont* pFont = FindFont(object->Handle); + if (pFont != nullptr) { + FEString* pStr = static_cast(object); + FEVector3& size = data->Size; + float width = size.x * pFont->GetTextWidth(pStr->GetString(), 0); + float height = size.y * pFont->GetTextHeight(pStr->GetString(), pStr->Leading, 0, 0, false); + pos.x = x - (pFont->CalculateXOffset(pStr->Format, width) + width * 0.5f); + pos.y = y - (pFont->CalculateYOffset(pStr->Format, height) + height * 0.5f); + } + break; + } + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: + pos.x = x; + pos.y = y; + break; + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + pos.x = x; + pos.y = y; + break; + } + + object->SetPosition(pos, false); +} + +float FEngGetScaleX(FEObject* object) { + FEObjData* data = object->GetObjData(); + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: { + TextureInfo* pTex = GetTextureInfo(object->Handle, 1, 0); + return data->Size.x / static_cast(pTex->Width); + } + case FE_String: + return data->Size.x; + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + return 1.0f; + } +} + +float FEngGetScaleY(FEObject* object) { + FEObjData* data = object->GetObjData(); + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: { + TextureInfo* pTex = GetTextureInfo(object->Handle, 1, 0); + return data->Size.y / static_cast(pTex->Height); + } + case FE_String: + return data->Size.y; + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + return 1.0f; + } +} + +void FEngSetScaleX(FEObject* object, float x) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + float size = data->Size.x; + float scale = x; + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: { + TextureInfo* pTex = GetTextureInfo(object->Handle, 1, 0); + scale = x * static_cast(pTex->Width); + } + case FE_String: + case FE_Group: + data->Size.x = scale; + break; + } + + const float SizeEpsilon = 0.001f; + if (scale + SizeEpsilon < size || scale - SizeEpsilon > size) { + object->Flags |= 0x400000; + } +} + +void FEngSetScaleY(FEObject* object, float y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + float size = data->Size.y; + float scale = y; + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: { + TextureInfo* pTex = GetTextureInfo(object->Handle, 1, 0); + scale = y * static_cast(pTex->Height); + } + case FE_String: + case FE_Group: + data->Size.y = scale; + break; + } + + const float SizeEpsilon = 0.001f; + if (scale + SizeEpsilon < size || scale - SizeEpsilon > size) { + object->Flags |= 0x400000; + } +} + +void FEngGetSize(FEObject* object, float& x, float& y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: + x = data->Size.x; + y = data->Size.y; + return; + case FE_String: { + FEngFont* pFont = FindFont(object->Handle); + if (pFont == nullptr) { + return; + } + FEString* pStr = static_cast(object); + FEVector3& size = data->Size; + x = size.x * pFont->GetTextWidth(pStr->GetString(), 0); + y = size.y * pFont->GetTextHeight(pStr->GetString(), pStr->Leading, 0, 0, false); + break; + } + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + x = 0.0f; + y = 0.0f; + break; + } +} + +void FEngSetSize(FEObject* object, float x, float y) { + if (object == nullptr) { + return; + } + + FEObjData* data = object->GetObjData(); + + switch (object->Type) { + case FE_Image: + case FE_Movie: + case FE_ColoredImage: + case FE_MultiImage: + data->Size.x = x; + data->Size.y = y; + break; + case FE_String: + case FE_Model: + case FE_List: + case FE_Group: + case FE_CodeList: + default: + break; + } + + data->Size.x = x; + data->Size.y = y; + object->Flags |= 0x400000; +} + +void FEngGetBottomRightUV(FEImage* img, float& u, float& v) { + if (img != nullptr) { + TextureInfo* pTex = GetTextureInfo(img->Handle, 1, 0); + u = img->GetImageData()->LowerRight.x * static_cast(pTex->Width); + v = img->GetImageData()->LowerRight.y * static_cast(pTex->Height); + } +} + +void FEngSetBottomRightUV(FEImage* img, float u, float v) { + if (img != nullptr) { + TextureInfo* pTex = GetTextureInfo(img->Handle, 1, 0); + img->GetImageData()->LowerRight.x = u / static_cast(pTex->Width); + img->GetImageData()->LowerRight.y = v / static_cast(pTex->Height); + img->Flags |= 0x400000; + } +} + +void FEngSetColor(FEObject* object, unsigned int color) { + if (object != nullptr) { + FEColor col(color); + object->SetColor(col, false); + } +} + +FEColor FEngGetObjectColor(FEObject* obj) { + if (obj != nullptr) { + FEObjData* data = obj->GetObjData(); + return data->Col; + } + return FEColor(0); +} + +void FixInvertedRect(FERect& rect) { + if (rect.left > rect.right) { + float tmp = rect.left; + rect.left = rect.right; + rect.right = tmp; + } + if (rect.top > rect.bottom) { + float tmp = rect.top; + rect.top = rect.bottom; + rect.bottom = tmp; + } +} + +inline float FEngGetCenterX(FEObject* obj) { + float x; + float y; + FEngGetCenter(obj, x, y); + return x; +} + +inline float FEngGetCenterY(FEObject* obj) { + float x; + float y; + FEngGetCenter(obj, x, y); + return y; +} + +bool FEngGet2DExtentsForMouse(FEObject* pObject, FERect& Rect, FEVector2 offset); + +bool FEngTestForIntersection(float xPos, float yPos, FEObject* obj) { + if (obj != nullptr) { + FERect Rect(-10000.0f, -10000.0f, 10000.0f, 10000.0f); + FEngGet2DExtentsForMouse(obj, Rect, FEVector2()); + FixInvertedRect(Rect); + if (xPos >= Rect.left && xPos <= Rect.right && yPos >= Rect.top && yPos <= Rect.bottom) { + return true; + } + } + return false; +} + +bool FEngGet2DExtentsForMouse(FEObject* pObject, FERect& Rect, FEVector2 offset) { + if (pObject == nullptr) { + return false; + } + + switch (pObject->Type) { + case FE_Group: { + FEGroup* grp = static_cast(pObject); + FEObject* pChild = grp->GetFirstChild(); + if (pChild != nullptr) { + do { + FERect ChildRect(-10000.0f, -10000.0f, 10000.0f, 10000.0f); + float cx = FEngGetCenterX(pObject); + float cy = FEngGetCenterY(pObject); + FEVector2 childOffset = offset + FEVector2(cx, cy); + if (FEngGet2DExtentsForMouse(pChild, ChildRect, childOffset)) { + if (ChildRect.left < Rect.left) { + Rect.left = ChildRect.left; + } + if (ChildRect.right > Rect.right) { + Rect.right = ChildRect.right; + } + if (ChildRect.top < Rect.top) { + Rect.top = ChildRect.top; + } + if (ChildRect.bottom > Rect.bottom) { + Rect.bottom = ChildRect.bottom; + } + } + pChild = pChild->GetNext(); + } while (pChild != nullptr); + } + break; + } + case FE_Image: + case FE_String: + case FE_Movie: + case FE_ColoredImage: + case FE_AnimImage: + case FE_SimpleImage: + case FE_MultiImage: + FEngGetTopLeft(pObject, Rect.left, Rect.top); + FEngGetBottomRight(pObject, Rect.right, Rect.bottom); + + Rect.left += offset.x; + Rect.right += offset.x; + Rect.top += offset.y; + Rect.bottom += offset.y; + break; + case FE_Model: + case FE_List: + case FE_CodeList: + FEngGetTopLeft(pObject, Rect.left, Rect.top); + FEngGetBottomRight(pObject, Rect.right, Rect.bottom); + + Rect.left += offset.x; + Rect.right += offset.x; + Rect.top += offset.y; + Rect.bottom += offset.y; + break; + default: + return false; + } + + return true; +} diff --git a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEStrings.cpp b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEStrings.cpp index e69de29bb..edb9ee537 100644 --- a/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEStrings.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngInterfaces/FEngInterfaceFEStrings.cpp @@ -0,0 +1,166 @@ +#include + +inline void FEString::SetString(short* pNewText) { + string = pNewText; +} + +inline void FEString::SetString(const char* pcString) { + string = pcString; +} + +int bVSPrintf(char* dest, const char* fmt, va_list args); + +static char FEPrintf_Buffer[1024]; + +void FEngSetLanguageHash(FEString* text, unsigned int hash); +static int DoFEngPrintf(FEString* text, char* string, int len); + +struct FEngSetGroupLanguageHash : public FEObjectCallback { + unsigned long Hash; // offset 0x4, size 0x4 + + inline FEngSetGroupLanguageHash() : Hash(0) {} + inline ~FEngSetGroupLanguageHash() override {} + inline bool Callback(FEObject* pObj) override { + if (pObj->Type == FE_String) { + FEngSetLanguageHash(static_cast(pObj), Hash); + } + return true; + } +}; + +struct FEngGroupFEPrintf : public FEObjectCallback { + char* string; // offset 0x4, size 0x4 + + inline FEngGroupFEPrintf() : string(nullptr) {} + inline ~FEngGroupFEPrintf() override {} + inline bool Callback(FEObject* pObj) override { + if (pObj->Type == FE_String) { + DoFEngPrintf(static_cast(pObj), string, 0); + } + return true; + } +}; + +FEString* FEngFindString(const char* pkg_name, int name_hash) { + FEObject* obj = FEngFindObject(pkg_name, name_hash); + if (obj == nullptr || obj->Type != FE_String) { + return nullptr; + } + return static_cast(obj); +} + +void FEngSetLanguageHash(FEString* text, unsigned int hash) { + if (text != nullptr) { + text->SetLabelHash(hash); + } +} + +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int language) { + FEObject* obj = FEngFindObject(pkg_name, obj_hash); + if (obj != nullptr) { + if (obj->Type == FE_Group) { + FEngSetGroupLanguageHash SetHash; + SetHash.Hash = language; + FEPackage* pkg = cFEng::Get()->FindPackage(pkg_name); + pkg->ForAllChildren(static_cast(obj), SetHash); + } else if (obj->Type == FE_String) { + FEngSetLanguageHash(static_cast(obj), language); + } + } +} + +void FESetString(FEString* text, const short* string) { + if (string != nullptr && text != nullptr) { + text->SetString(const_cast(string)); + text->Flags |= 0x400002; + } +} + +static int DoFEngPrintf(FEString* text, char* string, int len) { + if (text != nullptr) { + int length = text->string.mulBufferLength; + if (static_cast(length) < 0x400) { + string[length - 1] = 0; + } + text->SetString(string); + text->Flags |= 0x400002; + return len; + } + return 0; +} + +static int DoFEngPrintf(FEString* text, const char* fmt, va_list argList) { + if (text != nullptr) { + int nchars = bVSPrintf(FEPrintf_Buffer, fmt, argList); + return DoFEngPrintf(text, FEPrintf_Buffer, nchars); + } + return 0; +} + +int FEPrintf(FEString* text, const char* fmt, ...) { + va_list argList; + va_start(argList, fmt); + if (text != nullptr) { + if (text->string != nullptr) { + return DoFEngPrintf(text, fmt, argList); + } + } + return 0; +} + +int FEPrintf(const char* pkg_name, int object_hash, const char* fmt, ...) { + va_list argList; + va_start(argList, fmt); + + FEObject* obj = FEngFindObject(pkg_name, object_hash); + if (obj == nullptr) { + return -1; + } + + if (obj->Type == FE_Group) { + va_list arg_list; + va_start(arg_list, fmt); + bVSPrintf(FEPrintf_Buffer, fmt, arg_list); + va_end(arg_list); + FEngGroupFEPrintf DoPrintf; + DoPrintf.string = FEPrintf_Buffer; + FEPackage* pkg = cFEng::Get()->FindPackage(pkg_name); + pkg->ForAllChildren(static_cast(obj), DoPrintf); + } else if (obj->Type == FE_String) { + DoFEngPrintf(static_cast(obj), fmt, argList); + } + + return 0; +} + +int FEPrintf(const char* pkg_name, FEObject* obj, const char* fmt, ...) { + va_list argList; + va_start(argList, fmt); + + if (obj == nullptr) { + return -1; + } + + if (obj->Type == FE_Group) { + va_list arg_list; + va_start(arg_list, fmt); + bVSPrintf(FEPrintf_Buffer, fmt, arg_list); + va_end(arg_list); + FEngGroupFEPrintf DoPrintf; + DoPrintf.string = FEPrintf_Buffer; + FEPackage* pkg = cFEng::Get()->FindPackage(pkg_name); + pkg->ForAllChildren(static_cast(obj), DoPrintf); + } else if (obj->Type == FE_String) { + DoFEngPrintf(static_cast(obj), fmt, argList); + } + + return 0; +} + +int FEngSNPrintf(char* buffer, int buf_size, const char* fmt, ...) { + va_list arg_list; + va_start(arg_list, fmt); + int nchars = bVSPrintf(buffer, fmt, arg_list); + va_end(arg_list); + return nchars; +} diff --git a/src/Speed/Indep/Src/Frontend/FEngRender.cpp b/src/Speed/Indep/Src/Frontend/FEngRender.cpp index e69de29bb..dde1964c9 100644 --- a/src/Speed/Indep/Src/Frontend/FEngRender.cpp +++ b/src/Speed/Indep/Src/Frontend/FEngRender.cpp @@ -0,0 +1,562 @@ +#include "Speed/Indep/Src/Frontend/cFEngRender.hpp" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/FEng/feimage.h" +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/FEng/FEColoredImage.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/Frontend/FERenderObject.hpp" +#include "Speed/Indep/Src/Frontend/FEngFont.hpp" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/Ecstasy/Texture.hpp" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +extern float ObjectSortLastZ; +extern FEPackage *ObjectSortRenderingPackage; +extern void GCDrawMovie(FEObject *obj, FERenderObject *renderObj); +extern void FinishedRenderingFEngLayer(); +extern FEPackageRenderInfo *HACK_FEPkgMgr_GetPackageRenderInfo(FEPackage *pkg); +extern TextureInfo *GetTextureInfo(unsigned int hash, int, int); + + +unsigned int FEngColorToEpolyColor(FEColor c) { + return (c.a / 2) | ((c.b / 2) << 8) | ((c.g / 2) << 16) | ((c.r / 2) << 24); +} + +unsigned int next_power_of_2(unsigned int number) { + if (!number) { + return 0; + } + number--; + unsigned int shift = 2; + while (number >>= 1) { + shift <<= 1; + } + return shift; +} + +void cFEngRender::RenderMovie(FEMovie *movie, FERenderObject *cached, FEPackageRenderInfo *pkg_render_info) { + if (gMoviePlayer) { + if (static_cast(gMoviePlayer->GetStatus() - 3) < 3) { + GCDrawMovie(reinterpret_cast(movie), cached); + } + } +} + +void cFEngRender::RenderModel(FEModel* model, FERenderObject* renderObj) {} + +void cFEngRender::RenderImage(FEImage *image, FERenderObject *cached, FEPackageRenderInfo *pkg_render_info) { + FEImageData *image_data = reinterpret_cast(image->pData); + + bMatrix4 screen; + bIdentity(&screen); + screen.v3.x = 320.0f; + screen.v3.y = 240.0f; + screen.v3.z = 0.0f; + + bMatrix4 trans; + FEColor fe_color; + + FEClipInfo *clip_info = MakeRenderMatrix( + reinterpret_cast(image_data), &trans, fe_color, + image->RenderContext, 1.0f); + + bMulMatrix(&trans, &screen, &trans); + + TextureInfo *texture_info = GetTextureInfo(image->Handle, 1, 0); + + if (!cached) { + cached = CreateCachedRender(reinterpret_cast(image), texture_info); + } else { + cached->Clear(pkg_render_info); + } + + unsigned int tw = texture_info->Width; + unsigned int th = texture_info->Height; + float ftw = static_cast(tw); + float fth = static_cast(th); + + unsigned int t2w = next_power_of_2(tw); + unsigned int t2h = next_power_of_2(th); + float ft2w = static_cast(t2w); + float ft2h = static_cast(t2h); + + float convertu = ftw / ft2w; + float convertv = fth / ft2h; + + unsigned int color = FEngColorToEpolyColor(fe_color); + unsigned int Colours[4]; + Colours[0] = color; + Colours[1] = color; + Colours[2] = color; + Colours[3] = color; + + float s0 = image_data->UpperLeft.x * convertu; + float s1 = image_data->LowerRight.x * convertu; + float t0 = image_data->UpperLeft.y * convertv; + float t1 = image_data->LowerRight.y * convertv; + + cached->SetTransform(&trans); + cached->AddPoly(-0.5f, -0.5f, 0.5f, 0.5f, 1.0f, + s0, t0, s1, t1, + Colours, clip_info, pkg_render_info); + cached->SetTexture(texture_info); + cached->Render(); +} + +FERenderObject *cFEngRender::FindCachedRender(FEObject *object) { + return object->Cached; +} + +RenderContext *cFEngRender::GetRenderContext(unsigned short ctx) { + return &RContexts[ctx]; +} + +void cFEngRender::PrepForPackage(FEPackage *pPackage) { + ObjectSortLastZ = -999999.0f; + ObjectSortRenderingPackage = pPackage; +} + +void cFEngRender::PackageFinished(FEPackage* pkg) {} + +static void rotate_uvs(bVector2 *uvs, float angle_radians, float px, float py) { + float half_width = (uvs[2].x - uvs[0].x) * 0.5f; + float half_height = (uvs[2].y - uvs[0].y) * 0.5f; + + for (int i = 0; i < 4; i++) { + uvs[i].x -= half_width; + uvs[i].y -= half_height; + uvs[i].x -= px; + uvs[i].y -= py; + } + + float sin_angle = bSin(bRadToAng(angle_radians)); + float cos_angle = bCos(bRadToAng(angle_radians)); + + const float s2r = uvs[0].x; + const float t2r = uvs[0].y; + const float s3r = uvs[1].x; + const float t3r = uvs[1].y; + const float s4r = uvs[2].x; + const float t4r = uvs[2].y; + const float s5r = uvs[3].x; + const float t5r = uvs[3].y; + + uvs[0].x = s2r * cos_angle + t2r * sin_angle + px + half_width; + uvs[0].y = t2r * cos_angle - s2r * sin_angle + py + half_height; + uvs[1].x = s3r * cos_angle + t3r * sin_angle + px + half_width; + uvs[1].y = t3r * cos_angle - s3r * sin_angle + py + half_height; + uvs[2].x = s4r * cos_angle + t4r * sin_angle + px + half_width; + uvs[2].y = t4r * cos_angle - s4r * sin_angle + py + half_height; + uvs[3].x = s5r * cos_angle + t5r * sin_angle + px + half_width; + uvs[3].y = t5r * cos_angle - s5r * sin_angle + py + half_height; +} + +void cFEngRender::RenderMultiImage(FEMultiImage *image, FERenderObject *cached, FEPackageRenderInfo *pkg_render_info) { + FEMultiImageData *image_data = reinterpret_cast(image->pData); + + bMatrix4 screen; + bIdentity(&screen); + screen.v3.x = 320.0f; + screen.v3.y = 240.0f; + screen.v3.z = 0.0f; + + bMatrix4 trans; + FEColor fe_color; + + FEClipInfo *clip_info = MakeRenderMatrix( + reinterpret_cast(image_data), &trans, fe_color, + image->RenderContext, 1.0f); + + bMulMatrix(&trans, &screen, &trans); + + TextureInfo *texture_info = GetTextureInfo(image->Handle, 1, 0); + TextureInfo *texture_info_mask = GetTextureInfo(image->GetTexture(0), 1, 0); + + if (!cached) { + cached = CreateCachedRender(reinterpret_cast(image), texture_info); + } else { + cached->Clear(pkg_render_info); + } + + unsigned int tw = texture_info->Width; + unsigned int th = texture_info->Height; + float ftw = static_cast(tw); + float fth = static_cast(th); + + unsigned int t2w = next_power_of_2(tw); + unsigned int t2h = next_power_of_2(th); + float ft2w = static_cast(t2w); + float ft2h = static_cast(t2h); + + float convertu = ftw / ft2w; + float convertv = fth / ft2h; + + unsigned int color = FEngColorToEpolyColor(fe_color); + unsigned int Colours[4]; + Colours[0] = color; + Colours[1] = color; + Colours[2] = color; + Colours[3] = color; + + float s0 = image_data->UpperLeft.x * convertu; + float t0 = image_data->UpperLeft.y * convertv; + float s1 = image_data->LowerRight.x * convertu; + float t1 = image_data->LowerRight.y * convertv; + + unsigned int tw_m = texture_info->Width; + unsigned int th_m = texture_info->Height; + float ftw_m = static_cast(tw_m); + float fth_m = static_cast(th_m); + + unsigned int t2w_m = next_power_of_2(tw_m); + unsigned int t2h_m = next_power_of_2(th_m); + float ft2w_m = static_cast(t2w_m); + float ft2h_m = static_cast(t2h_m); + + float convertu_m = ftw_m / ft2w_m; + float convertv_m = fth_m / ft2h_m; + + float ss2 = image_data->TopLeftUV[0].x * convertu_m; + float sst2 = image_data->TopLeftUV[0].y * convertv_m; + float ss3 = image_data->BottomRightUV[0].x * convertu_m; + float sst3 = image_data->BottomRightUV[0].y * convertv_m; + + bVector2 uvs[4]; + uvs[0].x = ss2 * ftw_m; + uvs[0].y = sst2 * fth_m; + uvs[1].x = ss3 * ftw_m; + uvs[1].y = sst2 * fth_m; + uvs[2].x = ss3 * ftw_m; + uvs[2].y = sst3 * fth_m; + uvs[3].x = ss2 * ftw_m; + uvs[3].y = sst3 * fth_m; + + rotate_uvs(uvs, + bDegToRad(image_data->PivotRot.z), + image_data->PivotRot.x * ftw_m - ftw_m * 0.5f, + image_data->PivotRot.y * fth_m - fth_m * 0.5f); + + uvs[0].x /= ftw_m; + uvs[0].y /= fth_m; + uvs[1].x /= ftw_m; + uvs[1].y /= fth_m; + uvs[2].x /= ftw_m; + uvs[2].y /= fth_m; + uvs[3].x /= ftw_m; + uvs[3].y /= fth_m; + + cached->SetTransform(&trans); + cached->AddPolyWithRotatedMask( + -0.5f, -0.5f, 0.5f, 0.5f, 1.0f, + s0, t0, s1, t1, + uvs[0].x, uvs[0].y, + uvs[1].x, uvs[1].y, + uvs[2].x, uvs[2].y, + uvs[3].x, uvs[3].y, + Colours, texture_info, texture_info_mask); + cached->SetTexture(texture_info); + cached->Render(); +} + +void cFEngRender::RenderCBVImage(FEColoredImage *image, FERenderObject *cached, FEPackageRenderInfo *pkg_render_info) { + FEColoredImageData *image_data = reinterpret_cast(image->pData); + + bMatrix4 screen; + bIdentity(&screen); + screen.v3.x = 320.0f; + screen.v3.y = 240.0f; + screen.v3.z = 0.0f; + + bMatrix4 trans; + FEColor fe_color; + + FEClipInfo *clip_info = MakeRenderMatrix( + reinterpret_cast(image_data), &trans, fe_color, + image->RenderContext, 1.0f); + + bMulMatrix(&trans, &screen, &trans); + + TextureInfo *texture_info = GetTextureInfo(image->Handle, 1, 0); + + if (!cached) { + cached = CreateCachedRender(reinterpret_cast(image), texture_info); + } else { + cached->Clear(pkg_render_info); + } + + unsigned int tw = texture_info->Width; + unsigned int th = texture_info->Height; + float ftw = static_cast(tw); + float fth = static_cast(th); + + unsigned int t2w = next_power_of_2(tw); + unsigned int t2h = next_power_of_2(th); + float ft2w = static_cast(t2w); + float ft2h = static_cast(t2h); + + float convertu = ftw / ft2w; + float convertv = fth / ft2h; + + unsigned int Colours[4]; + Colours[0] = FEngColorToEpolyColor(image_data->VertexColors[0]); + Colours[1] = FEngColorToEpolyColor(image_data->VertexColors[1]); + Colours[2] = FEngColorToEpolyColor(image_data->VertexColors[2]); + Colours[3] = FEngColorToEpolyColor(image_data->VertexColors[3]); + + float s0 = image_data->UpperLeft.x * convertu; + float s1 = image_data->LowerRight.x * convertu; + float t0 = image_data->UpperLeft.y * convertv; + float t1 = image_data->LowerRight.y * convertv; + + cached->SetTransform(&trans); + cached->AddPoly(-0.5f, -0.5f, 0.5f, 0.5f, 1.0f, + s0, t0, s1, t1, + Colours, clip_info, pkg_render_info); + cached->SetTexture(texture_info); + cached->Render(); +} + +void cFEngRender::RenderString(FEString *string, FERenderObject *cached, FEPackageRenderInfo *pkg_render_info) { + FEngFont *font = FindFont(string->Handle); + if (!font) { + return; + } + TextureInfo *texture_info = font->GetTextureInfo(); + if (!texture_info) { + return; + } + + if (!cached) { + cached = CreateCachedRender(reinterpret_cast(string), texture_info); + } else { + cached->Clear(pkg_render_info); + } + + float extra_scale = 1.0f; + int lang = GetCurrentLanguage(); + if ((lang == 8 || GetCurrentLanguage() == 9) && string->Handle == 0x9583AA1A) { + extra_scale = 2.0f; + } + + const short *characters = nullptr; + FEObjData *obj_data = reinterpret_cast(string->pData); + short localized_string_buffer[1024]; + unsigned int labelHash = string->GetLabelHash(); + + if (!(string->Flags & 2)) { + if (GetLocalizedWideString(localized_string_buffer, 0x800, labelHash)) { + characters = localized_string_buffer; + } + } + if (!characters) { + characters = string->GetString(); + } + + bMatrix4 screen; + bIdentity(&screen); + screen.v3.x = 320.0f; + screen.v3.y = 240.0f; + screen.v3.z = 0.0f; + + bMatrix4 trans; + FEColor fe_color; + + MakeRenderMatrix(obj_data, &trans, fe_color, string->RenderContext, extra_scale); + bMulMatrix(&trans, &screen, &trans); + + float fMaxWidth = static_cast(string->MaxWidth); + if (fMaxWidth == 0.0f) { + fMaxWidth = 3.4028235e+38f; + } + + float LineWidth = font->GetLineWidth(characters, 0, 0, false); + + if (string->MaxWidth != 0 && LineWidth > fMaxWidth && !(string->Format & 0x10)) { + float fLineScale = fMaxWidth / LineWidth; + bMatrix4 scale; + bIdentity(&scale); + scale.v0.x = fLineScale; + bMulMatrix(&trans, &trans, &scale); + } + + font->RenderString(fe_color, characters, string, &trans, cached, pkg_render_info); +} + +void cFEngRender::AddToRenderList(FEObject *obj) { + float z = reinterpret_cast(obj->pData)->Pos.z; + if (obj->RenderContext != 0) { + RenderContext *pctx = &RContexts[obj->RenderContext]; + z += pctx->matrix.v3.z; + } + bool visible = obj && !(obj->Flags & 1); + if (visible) { + if (z != ObjectSortLastZ) { + ObjectSortLastZ = z; + FinishedRenderingFEngLayer(); + } + FEPackageRenderInfo *info = HACK_FEPkgMgr_GetPackageRenderInfo(ObjectSortRenderingPackage); + RenderObject(obj, info); + } +} + +FEClipInfo *cFEngRender::MakeRenderMatrix(FEObjData *pData, bMatrix4 *trans, FEColor &color, + int GroupIndex, float extra_scale) { + int do_pivot = 0; + if (pData->Pivot.x != 0.0f || pData->Pivot.y != 0.0f || pData->Pivot.z != 0.0f) { + do_pivot = 1; + } + + int do_scale = 0; + if (pData->Size.x != 1.0f || pData->Size.y != 1.0f || pData->Size.z != 1.0f || + extra_scale != 1.0f) { + do_scale = 1; + } + + int do_rotate = 0; + if (pData->Rot.x != 0.0f || pData->Rot.y != 0.0f || pData->Rot.z != 0.0f || + pData->Rot.w != 1.0f) { + do_rotate = 1; + } + + bMatrix4 feng_to_epoly; + bIdentity(&feng_to_epoly); + feng_to_epoly.v3.x = pData->Pos.x; + feng_to_epoly.v3.y = pData->Pos.y; + feng_to_epoly.v3.z = pData->Pos.z; + + bMatrix4 pivot; + bMatrix4 pivotm1; + if (do_pivot) { + bIdentity(&pivot); + bIdentity(&pivotm1); + pivotm1.v3.x = -pData->Pivot.x; + pivotm1.v3.y = -pData->Pivot.y; + pivotm1.v3.z = -pData->Pivot.z; + pivot.v3.x = pData->Pivot.x; + pivot.v3.y = pData->Pivot.y; + pivot.v3.z = pData->Pivot.z; + } + + bMatrix4 scale; + if (do_scale) { + bIdentity(&scale); + scale.v0.x = pData->Size.x * extra_scale; + scale.v1.y = pData->Size.y * extra_scale; + scale.v2.z = pData->Size.z * extra_scale; + } + + bMatrix4 rotate; + if (do_rotate) { + bIdentity(&rotate); + bQuaternion quat; + quat.x = pData->Rot.x; + quat.y = pData->Rot.y; + quat.z = pData->Rot.z; + quat.w = pData->Rot.w; + quat.GetMatrix(&rotate); + } + + bIdentity(trans); + bMulMatrix(trans, trans, &feng_to_epoly); + if (do_pivot) { + bMulMatrix(trans, trans, &pivot); + } + if (do_rotate) { + bMulMatrix(trans, trans, &rotate); + } + if (do_pivot) { + bMulMatrix(trans, trans, &pivotm1); + } + if (do_scale) { + bMulMatrix(trans, trans, &scale); + } + + FEClipInfo *clip_info = nullptr; + if (GroupIndex != 0) { + RenderContext &Con = RContexts[GroupIndex]; + color.r = (static_cast(Con.r) * pData->Col.r + 0x80) >> 8; + color.g = (static_cast(Con.g) * pData->Col.g + 0x80) >> 8; + color.b = (static_cast(Con.b) * pData->Col.b + 0x80) >> 8; + color.a = (static_cast(Con.a) * pData->Col.a + 0x80) >> 8; + bMulMatrix(trans, &Con.matrix, trans); + if (Con.clipObject) { + clip_info = &Con.clipInfo; + } + } else { + color = pData->Col; + } + + return clip_info; +} + +void cFEngRender::GenerateRenderContext(unsigned short ctx, FEObject *obj) { + if (Highwater < ctx) { + Highwater = ctx; + } + RenderContext *rc = &RContexts[ctx]; + FEColor color; + MakeRenderMatrix(reinterpret_cast(obj->pData), &rc->matrix, color, + obj->RenderContext, 1.0f); + int val; + val = 0; + if (color.b > 0) val = color.b; + if (val > 0xff) val = 0xff; + rc->b = static_cast(val); + val = 0; + if (color.g > 0) val = color.g; + if (val > 0xff) val = 0xff; + rc->g = static_cast(val); + val = 0; + if (color.r > 0) val = color.r; + if (val > 0xff) val = 0xff; + rc->r = static_cast(val); + val = 0; + if (color.a > 0) val = color.a; + if (val > 0xff) val = 0xff; + rc->a = static_cast(val); + if (rc->group != reinterpret_cast(obj)) { + rc->group = reinterpret_cast(obj); + rc->clipObject = nullptr; + FEMinNode *child = + *reinterpret_cast(reinterpret_cast(obj) + 0x60); + for (; child; child = child->GetNext()) { + } + } +} + +void cFEngRender::RenderObject(FEObject *object, FEPackageRenderInfo *pkg_render_info) { + if (object->Flags & 8) { + return; + } + if (object->Type == 7) { + object->Flags |= 0x2000000; + } + ProfileNode profile_node("TODO", 0); + FERenderObject *cached = FindCachedRender(object); + if (cached && cached->IsReadyToRender() && !(object->Flags & 0x2000000)) { + cached->Render(); + } else { + switch (object->Type) { + case 1: + RenderImage(reinterpret_cast(object), cached, pkg_render_info); + break; + case 9: + RenderCBVImage(reinterpret_cast(object), cached, pkg_render_info); + break; + case 2: + RenderString(reinterpret_cast(object), cached, pkg_render_info); + break; + case 3: + RenderModel(reinterpret_cast(object), cached); + break; + case 7: + RenderMovie(reinterpret_cast(object), cached, pkg_render_info); + break; + case 12: + RenderMultiImage(reinterpret_cast(object), cached, pkg_render_info); + break; + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.cpp b/src/Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.cpp index e69de29bb..92f408a64 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.cpp @@ -0,0 +1,1317 @@ +#include "Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeBustedMeter.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeCostToState.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeCountdown.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeDragTachometer.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeGenericMessage.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeHeatMeter.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeInfractions.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeMinimap.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FePursuitBoard.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeRaceInformation.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeRadarDetector.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeReputation.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeSpeedBreakerMeter.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeSpeedometer.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeTachometer.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeTimeExtension.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeTurboMeter.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEList.h" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Misc/Profiler.hpp" +#include "Speed/Indep/Src/World/OnlineManager.hpp" + +#include "Speed/Indep/Src/World/TrackStreamer.hpp" +#include "Speed/Indep/Src/Misc/bFile.hpp" + +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Camera/ICE/ICEManager.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/engine.h" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Generated/Events/EPause.hpp" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOn.hpp" +#include "Speed/Indep/Src/Generated/Events/EShowResults.hpp" +#include "Speed/Indep/Src/Generated/Events/EShowSMS.hpp" +#include "Speed/Indep/Src/Generated/Events/EWorldMapOn.hpp" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/TrackInfo.hpp" + +struct FadeScreen : MenuScreen { + static bool IsFadeScreenOn(); + FadeScreen(ScreenConstructorData *); + ~FadeScreen() override; + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; +}; + +extern bool bIsRestartingRace; +extern int SkipFE; +extern const char *SkipFEPlayerCar; + +extern FEString *FEngFindString(const char *, int); +extern unsigned int bStringHash(const char *str); +extern unsigned int FEngHashString(const char *, ...); +extern int bSPrintf(char *, const char *, ...); +extern int bSNPrintf(char *buf, int max_len, const char *format, ...); +extern void FixDot(char *buf, int size); +extern void bToUpper(char *); +extern int bFileExists(const char *f); +extern int bStrCmp(const char *, const char *); + +extern void eWaitUntilRenderingDone(); +extern void eLoadStreamingTexture(unsigned int *textures, int count, + void (*callback)(void *), void *param, int pool); +extern void eLoadStreamingTexturePack(const char *filename, + void (*callback)(void *), void *param, int pool); +extern void eUnloadStreamingTexture(unsigned int *textures, int count); +extern void eUnloadAllStreamingTextures(const char *); +extern void eWaitForStreamingTexturePackLoading(const char *); +extern void eUnloadStreamingTexturePack(const char *); +extern void SetSoundControlState(bool bON, int esndstate, const char *Reason); + +extern FEObject *FEngFindObject(const char *, unsigned int); +extern FEImage *FEngFindImage(const char *, int); +extern void FEngSetTextureHash(FEImage *, unsigned int); +extern void FEngSetColor(FEObject *, unsigned int); +extern void FEngSetMultiImageRot(FEMultiImage *, float); + +inline void eLoadStreamingTexture(unsigned int *textures, int count, + void (*callback)(unsigned int), unsigned int param0, + int pool) { + eLoadStreamingTexture(textures, count, + reinterpret_cast(callback), + reinterpret_cast(param0), pool); +} + +inline void eLoadStreamingTexture(unsigned int name_hash, void (*callback)(unsigned int), + unsigned int param0, int pool) { + eLoadStreamingTexture(&name_hash, 1, callback, param0, pool); +} + +inline void eUnloadStreamingTexture(unsigned int name_hash) { + eUnloadStreamingTexture(&name_hash, 1); +} + +inline void FEngSetTextureHash(const char *pkg_name, unsigned int obj_hash, + unsigned int texture_hash) { + FEngSetTextureHash(FEngFindImage(pkg_name, obj_hash), texture_hash); +} + +inline void FEngSetColor(const char *pkg_name, unsigned int obj_hash, unsigned int color) { + FEngSetColor(FEngFindObject(pkg_name, obj_hash), color); +} + +IHud::~IHud() {} + +template void UTL::Vector::push_back(IHud *const &); + +HudResourceManager TheHudResourceManager; + +int HudResourceManager::mCustIndex; +int HudResourceManager::mPhase; +int HudResourceManager::mTachLinesHash; +ResourceFile *HudResourceManager::pMiniMapTexture; +const char *HudResourceManager::mPackageName; +char HudResourceManager::mCustHudTexPackName[32]; +unsigned int HudResourceManager::mCustomizeHUDTexTextureResources[5]; + +extern const char *HudSingleRaceTexturePackFilename; +extern const char *HudDragTexturePackFilename; +extern const char *HudSplitScreenTexturePackFilename; +extern const char *HudDragSplitScreenTexturePackFilename; + +const char *HudResourceManager::GetHudTexPackFilename(ePlayerHudType ht) { + if (ht == PHT_DRAG) { + return HudDragTexturePackFilename; + } + if (static_cast(ht - 3) < 2) { + return HudSplitScreenTexturePackFilename; + } + if (static_cast(ht - 5) >= 2) { + return HudSingleRaceTexturePackFilename; + } + return HudDragSplitScreenTexturePackFilename; +} + +const char *HudResourceManager::GetHudFengName(ePlayerHudType ht) { + switch (ht) { + case PHT_DRAG: + return "HUD_Drag.fng"; + case PHT_SPLIT1: + return "HUD_Player1.fng"; + case PHT_SPLIT2: + return "HUD_Player2.fng"; + case PHT_DRAG_SPLIT1: + return "HUD_Drag_Player1.fng"; + case PHT_DRAG_SPLIT2: + return "HUD_Drag_Player2.fng"; + default: + return "HUD_SingleRace.fng"; + } +} + +bool HudResourceManager::AreResourcesLoaded(ePlayerHudType ht) { + if (mHudResourcesState == HRM_LOADED) { + if (ht == PHT_SPLIT2) { + return LoadingResourcesForHudType == PHT_SPLIT1; + } + if (ht == PHT_DRAG_SPLIT2) { + return LoadingResourcesForHudType == PHT_DRAG_SPLIT1; + } + if (LoadingResourcesForHudType == ht) { + return true; + } + } + return false; +} + +CarPart *HudResourceManager::GetCarPart(ePlayerHudType ht, CAR_SLOT_ID carSlotId) { + FECarRecord *car = nullptr; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + car = stable->GetCarRecordByHandle(FEDatabase->GetCareerSettings()->GetCurrentCar()); + } else { + GRaceParameters *raceParams = GRaceStatus::Get().GetRaceParameters(); + if (raceParams && !raceParams->GetIsPursuitRace()) { + car = stable->GetCarRecordByHandle( + FEDatabase->GetQuickRaceSettings(GRace::kRaceType_NumTypes)->GetSelectedCar(0)); + } + } + + if (car) { + FECustomizationRecord *record = stable->GetCustomizationRecordByHandle(car->Customization); + if (record) { + return record->GetInstalledPart(car->GetType(), carSlotId); + } + } + return nullptr; +} + +int HudResourceManager::GetCustomHudColour(ePlayerHudType ht, CAR_SLOT_ID carSlotId) { + int colour = 0; + + if (ht == PHT_STANDARD) { + CarPart *part = GetCarPart(PHT_STANDARD, carSlotId); + if (part) { + unsigned char r = part->GetAppliedAttributeIParam(bStringHash("RED"), 0); + unsigned char g = part->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + unsigned char b = part->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + colour = 0xFF000000 | (r << 16) | (g << 8) | b; + } + } + + return colour; +} + +bool HudResourceManager::GetCustomHudTexPackFilename(ePlayerHudType ht, char *hudTexturePackName) { + mCustIndex = 0; + + if (ht == PHT_STANDARD) { + CarPart *part = GetCarPart(PHT_STANDARD, static_cast(0x84)); + if (part) { + mCustIndex = part->GetAppliedAttributeIParam(FEngHashString("HUDINDEX"), 0); + } + bSPrintf(hudTexturePackName, "GLOBAL\\HUDS_Custom_%2.2d.bin", mCustIndex); + return true; + } + + bSPrintf(hudTexturePackName, ""); + return false; +} + +bool HudResourceManager::ChooseMinimapTextureName(ePlayerHudType hudType, char *texture_name, + unsigned int texture_name_size, + char *minimap_texture_name, + unsigned int minimap_texture_name_size) { + if (hudType != PHT_DRAG) { + if (GRaceStatus::Exists()) { + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming) { + unsigned char bin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + if (bin >= 13) { + bSNPrintf(texture_name, texture_name_size, "MINI_MAP_UNLOCK_1"); + } else if (bin >= 9) { + bSNPrintf(texture_name, texture_name_size, "MINI_MAP_UNLOCK_2"); + } else { + bSNPrintf(texture_name, texture_name_size, "MINI_MAP"); + } + } else { + GRaceParameters *raceParams = GRaceStatus::Get().GetRaceParameters(); + if (raceParams) { + if (raceParams->GetIsPursuitRace()) { + if (raceParams->GetRegion() == kRaceRegion_College) { + bSNPrintf(texture_name, texture_name_size, "MINI_MAP_UNLOCK_1"); + } else if (raceParams->GetRegion() == kRaceRegion_Coastal) { + bSNPrintf(texture_name, texture_name_size, "MINI_MAP_UNLOCK_2"); + } else { + bSNPrintf(texture_name, texture_name_size, "MINI_MAP"); + } + } else { + bSNPrintf(texture_name, texture_name_size, "MINI_MAP_%s", raceParams->GetEventID()); + } + } + } + FixDot(texture_name, texture_name_size); + bToUpper(texture_name); + bSNPrintf(minimap_texture_name, minimap_texture_name_size, "TRACKS\\L2RA\\%s.BIN", texture_name); + } + + if (bFileExists(minimap_texture_name)) { + return true; + } + } + + return false; +} + +void HudResourceManager::ChooseLoadableTextures(ePlayerHudType hudType, int &textureHash, + float &redlineRotation) { + unsigned int vehicleKey; + + if (SkipFE) { + vehicleKey = Attrib::StringToKey(SkipFEPlayerCar); + } else { + unsigned int vehicleHandle; + FEPlayerCarDB *stable; + + if (GRaceStatus::Exists() && + GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + vehicleHandle = FEDatabase->GetCareerSettings()->GetCurrentCar(); + } else { + vehicleHandle = + FEDatabase->GetQuickRaceSettings(GRace::kRaceType_NumTypes)->GetSelectedCar(0); + } + + stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *car = stable->GetCarRecordByHandle(vehicleHandle); + vehicleKey = car->VehicleKey; + } + + Attrib::Gen::pvehicle atr(vehicleKey, 0, nullptr); + const Attrib::RefSpec &engineRef = atr.engine(0); + Attrib::Gen::engine atr_engine(engineRef, 0, nullptr); + + float MaxRPM = atr_engine.MAX_RPM(); + float RedLineRPM = atr_engine.RED_LINE(); + bool isDrag = (hudType == PHT_DRAG); + + float maxRpmTextureNum = FEngHud::ChooseMaxRpmTextureNumber(MaxRPM); + + char textureHashString[32]; + if (isDrag) { + bSPrintf(textureHashString, "DRAG_RPM_%d_LINES", static_cast(maxRpmTextureNum)); + } else { + bSPrintf(textureHashString, "%d_LINES_%2.2d", static_cast(maxRpmTextureNum), + mCustIndex); + } + textureHash = bStringHash(textureHashString); + + if (MaxRPM < 7000.0f) { + if (RedLineRPM >= 6500.0f) { + redlineRotation = isDrag ? 39.25f : 164.5f; + } else if (RedLineRPM >= 6000.0f) { + redlineRotation = isDrag ? 32.25f : 149.5f; + } else if (RedLineRPM >= 5500.0f) { + redlineRotation = isDrag ? 26.0f : 131.5f; + } else { + redlineRotation = isDrag ? 19.25f : 113.5f; + } + } else if (MaxRPM < 8000.0f) { + if (RedLineRPM >= 7500.0f) { + redlineRotation = isDrag ? 40.0f : 165.0f; + } else if (RedLineRPM >= 7000.0f) { + redlineRotation = isDrag ? 34.0f : 152.0f; + } else if (RedLineRPM >= 6500.0f) { + redlineRotation = isDrag ? 28.25f : 138.0f; + } else { + if (RedLineRPM >= 6000.0f) { + redlineRotation = isDrag ? 22.5f : 123.0f; + } else { + redlineRotation = isDrag ? 17.25f : 110.0f; + } + } + } else if (MaxRPM < 9000.0f) { + if (RedLineRPM >= 8500.0f) { + redlineRotation = isDrag ? 42.0f : 166.0f; + } else if (RedLineRPM >= 8000.0f) { + redlineRotation = isDrag ? 37.0f : 154.0f; + } else if (RedLineRPM >= 7500.0f) { + redlineRotation = isDrag ? 32.25f : 140.5f; + } else if (RedLineRPM >= 7000.0f) { + redlineRotation = isDrag ? 27.0f : 127.0f; + } else { + redlineRotation = isDrag ? 22.0f : 115.0f; + } + } else { + if (RedLineRPM >= 9500.0f) { + redlineRotation = isDrag ? 41.5f : 167.0f; + } else if (RedLineRPM >= 9000.0f) { + redlineRotation = isDrag ? 37.0f : 156.0f; + } else if (RedLineRPM >= 8500.0f) { + redlineRotation = isDrag ? 31.5f : 145.0f; + } else if (RedLineRPM >= 8000.0f) { + redlineRotation = isDrag ? 27.0f : 134.0f; + } else { + redlineRotation = isDrag ? 22.75f : 123.0f; + } + } +} + +void HudResourceManager::LoadRequiredResources(ePlayerHudType ht, const char *pkg_name) { + mPhase = 0; + const char *hud_tex_file = GetHudTexPackFilename(ht); + int allocation_params = 0x2000; + eWaitUntilRenderingDone(); + if (ht == PHT_DRAG) { + allocation_params = 0x2047; + TheTrackStreamer.MakeSpaceInPool(bFileSize(hud_tex_file), true); + } + pHudTextures = CreateResourceFile(hud_tex_file, RESOURCE_FILE_INGAME, 0, 0, 0); + pHudTextures->SetAllocationParams(allocation_params, hud_tex_file); + pHudTextures->BeginLoading(LoadingCompleteCallbackBridge, reinterpret_cast(this)); + mHudResourcesState = HRM_LOADING_IN_PROGRESS; + LoadingResourcesForHudType = ht; + mPackageName = pkg_name; +} + +void HudResourceManager::LoadingCompleteCallback() { + mPhase++; + if (mPhase == 1) { + char minimap_texture_name[64]; + char texture_name[32]; + bSPrintf(minimap_texture_name, ""); + bSPrintf(texture_name, ""); + if (ChooseMinimapTextureName(LoadingResourcesForHudType, texture_name, 0x20, + minimap_texture_name, 0x40)) { + gChoppedMiniMapManager->SetMapHeader(texture_name); + pMiniMapTexture = LoadResourceFile(minimap_texture_name, RESOURCE_FILE_TRACK, 0); + unsigned int textures_to_load[16]; + textures_to_load[0] = bStringHash(texture_name); + eLoadStreamingTexture(textures_to_load, 1, LoadingCompleteCallbackBridge, + reinterpret_cast(this), 0); + } else { + LoadingCompleteCallback(); + } + } else if (mPhase == 2) { + if (GetCustomHudTexPackFilename(LoadingResourcesForHudType, mCustHudTexPackName)) { + float redlineRotation; + ChooseLoadableTextures(LoadingResourcesForHudType, mTachLinesHash, redlineRotation); + FEngSetMultiImageRot(static_cast(FEngFindObject(mPackageName, 0xcdfce1b0)), + redlineRotation); + eLoadStreamingTexturePack(mCustHudTexPackName, + reinterpret_cast(LoadedCustomHudTexturePackCallbackBridge), + reinterpret_cast(this), 0); + } else { + float redlineRotation; + ChooseLoadableTextures(LoadingResourcesForHudType, mTachLinesHash, redlineRotation); + FEngSetMultiImageRot(static_cast(FEngFindObject(mPackageName, 0xcdfce1b0)), + redlineRotation); + FEngSetTextureHash(mPackageName, 0x309878bc, + static_cast(mTachLinesHash)); + eLoadStreamingTexture(static_cast(mTachLinesHash), + LoadingCompleteCallbackBridge, + reinterpret_cast(this), 0); + } + } else if (mPhase == 3) { + TheHudResourceManager.mHudResourcesState = HRM_LOADED; + cFEng::Get()->MakeLoadedPackagesDirty(); + SetSoundControlState(false, 0xc, "HUDLoaded"); + } +} + +void HudResourceManager::LoadedCustomHudTexturePackCallback() { + int hud_num = mCustIndex; + mCustomizeHUDTexTextureResources[0] = FEngHashString("CUSTOMHUD_TACH_%2.2d", hud_num); + mCustomizeHUDTexTextureResources[1] = mTachLinesHash; + mCustomizeHUDTexTextureResources[2] = FEngHashString("CUSTOMHUD_SPEEDO_%2.2d", hud_num); + mCustomizeHUDTexTextureResources[3] = FEngHashString("CUSTOMHUD_RPMNEEDLE_%2.2d", hud_num); + mCustomizeHUDTexTextureResources[4] = FEngHashString("CUSTOMHUD_SPEEDNEEDLE_%2.2d", hud_num); + eLoadStreamingTexture(mCustomizeHUDTexTextureResources, 5, + LoadedCustomHudTexturesCallbackBridge, + reinterpret_cast(this), 0); +} + +void HudResourceManager::LoadedCustomHudTexturesCallback() { + int mPhaseCust = 0; + do { + unsigned int fengObjHash = 0; + CAR_SLOT_ID carSlotIdForColour = static_cast(0); + switch (mPhaseCust) { + case 0: + carSlotIdForColour = static_cast(0x85); + fengObjHash = 0x05d19f25; + break; + case 1: + carSlotIdForColour = static_cast(0x87); + fengObjHash = 0x309878bc; + break; + case 2: + carSlotIdForColour = static_cast(0x87); + fengObjHash = 0xc62ad685; + break; + case 3: + carSlotIdForColour = static_cast(0x86); + fengObjHash = 0xf0250dac; + break; + case 4: + carSlotIdForColour = static_cast(0x86); + fengObjHash = 0x6d5ece44; + break; + } + int custColour = GetCustomHudColour(LoadingResourcesForHudType, carSlotIdForColour); + if (custColour) { + FEngSetColor(mPackageName, fengObjHash, custColour); + } + FEngSetTextureHash(mPackageName, fengObjHash, mCustomizeHUDTexTextureResources[mPhaseCust]); + mPhaseCust++; + } while (static_cast(mPhaseCust) <= 4); + int custColour = GetCustomHudColour(LoadingResourcesForHudType, static_cast(0x87)); + if (custColour) { + FEngSetColor(mPackageName, 0xc3383b63, custColour); + } + LoadingCompleteCallback(); +} + +void HudResourceManager::UnloadRequiredResources(ePlayerHudType ht) { + eWaitForStreamingTexturePackLoading(nullptr); + mHudResourcesState = HRM_UNLOADING_IN_PROGRESS; + eWaitUntilRenderingDone(); + cFEng::Get()->MakeLoadedPackagesDirty(); + + eUnloadAllStreamingTextures(HudDragTexturePackFilename); + eUnloadAllStreamingTextures(HudSingleRaceTexturePackFilename); + eUnloadAllStreamingTextures(HudSplitScreenTexturePackFilename); + eUnloadAllStreamingTextures(HudDragSplitScreenTexturePackFilename); + + if (pHudTextures) { + UnloadResourceFile(pHudTextures); + pHudTextures = nullptr; + } + if (pMiniMapTexture) { + UnloadResourceFile(pMiniMapTexture); + pMiniMapTexture = nullptr; + } + + if (gChoppedMiniMapManager) { + gChoppedMiniMapManager->RemoveUncompressedMaps(); + } + + if (!bStrCmp(mCustHudTexPackName, "")) { + if (mTachLinesHash) { + eUnloadStreamingTexture(static_cast(mTachLinesHash)); + mTachLinesHash = 0; + } + } else { + eUnloadStreamingTexture(mCustomizeHUDTexTextureResources, 5); + for (unsigned int i = 0; i <= 4; i++) { + mCustomizeHUDTexTextureResources[i] = 0; + } + eUnloadStreamingTexturePack(mCustHudTexPackName); + bSPrintf(mCustHudTexPackName, ""); + } + + eWaitUntilRenderingDone(); + pHudTextures = nullptr; + mHudResourcesState = HRM_NOT_LOADED; + mPackageName = nullptr; +} + +bool FEngHud::ShouldRearViewMirrorBeVisible(EVIEW_ID viewId) { + eView *view = eGetView(viewId, false); + IPlayer *player = IPlayer::First(PLAYER_LOCAL); + + if (player) { + IHud *hud = player->GetHud(); + if (hud && !player->GetHud()->IsHudVisible()) { + return false; + } + } + + CameraMover *camMover = nullptr; + if (view) { + camMover = view->GetCameraMover(); + } + + if (camMover && camMover->GetType() == CM_DRIVE_CUBIC) { + if (camMover->GetLookbackAngle()) { + return false; + } + } + + if (FEManager::ShouldPauseSimulation(true)) { + return false; + } + + if (!FEDatabase) { + return false; + } + + if (!FEDatabase->GetGameplaySettings()->RearviewOn) { + return false; + } + + ePlayerSettingsCameras playerCam = FEDatabase->GetPlayerSettings(viewId - 1)->CurCam; + if (playerCam >= PSC_CLOSE && playerCam <= PSC_PURSUIT) { + return false; + } + + if (!cFEng::Get()->IsPackagePushed("HUD_SingleRace.fng")) { + return false; + } + return true; +} + +float FEngHud::ChooseMaxRpmTextureNumber(float rpm) { + if (rpm < 7000.0f) { + return 7000.0f; + } + if (rpm < 8000.0f) { + return 8000.0f; + } + if (rpm < 9000.0f) { + return 9000.0f; + } + return 10000.0f; +} + +FEngHud::FEngHud(ePlayerHudType ht, const char *pkg_name, IPlayer *player, int player_number) + : UTL::COM::Object(0x14) // + , IHud(this) // + , mPlayerHudType(ht) // + , PlayerNumber(player_number) // + , mActionQ(true) // + , mCurrentWidescreenSetting(false) +{ + pPlayer = player; + mInPursuit = false; + mHasTurbo = false; + pSpeedometer = nullptr; + pTachometer = nullptr; + pTachometerDrag = nullptr; + pShiftUpdater = nullptr; + pCostToState = nullptr; + pReputation = nullptr; + pHeatMeter = nullptr; + pTurboMeter = nullptr; + pEngineTemp = nullptr; + pNitrous = nullptr; + pSpeedBreakerMeter = nullptr; + pRaceOverMessage = nullptr; + pGenericMessage = nullptr; + pRaceInformation = nullptr; + pLeaderBoard = nullptr; + pPursuitBoard = nullptr; + pMilestoneBoard = nullptr; + pBustedMeter = nullptr; + pTimeExtension = nullptr; + pWrongWIndi = nullptr; + pOnlineSupport = nullptr; + p321Go = nullptr; + pRadarDetector = nullptr; + pMinimap = nullptr; + pGetAwayMeter = nullptr; + pMenuZoneTrigger = nullptr; + pInfractions = nullptr; + pPackageName = pkg_name; + + if (mPlayerHudType != PHT_SPLIT2 && mPlayerHudType != PHT_DRAG_SPLIT2) { + TheHudResourceManager.LoadRequiredResources(mPlayerHudType, pkg_name); + } + + cFEng::mInstance->PushNoControlPackage(pkg_name, static_cast< FE_PACKAGE_PRIORITY >(0x66)); + FEngSetAllObjectsInPackageVisibility(pkg_name, false); + + pSpeedometer = new Speedometer(this, pPackageName, player_number); + pRaceInformation = new RaceInformation(this, pkg_name, player_number); + pLeaderBoard = new LeaderBoard(this, pkg_name, player_number); + pNitrous = new NitrousGauge(this, pkg_name, player_number); + pRaceOverMessage = new RaceOverMessage(this, pkg_name, player_number); + pGenericMessage = new GenericMessage(this, pkg_name, player_number); + pTurboMeter = new TurboMeter(this, pkg_name, player_number); + pWrongWIndi = new WrongWIndi(this, pkg_name, player_number); + p321Go = new Countdown(this, pkg_name, player_number); + + if (mPlayerHudType == PHT_DRAG || mPlayerHudType == PHT_DRAG_SPLIT1 || mPlayerHudType == PHT_DRAG_SPLIT2) { + pEngineTemp = new EngineTempGauge(this, pkg_name, player_number); + pTachometerDrag = new DragTachometer(this, pPackageName, player_number); + pShiftUpdater = new ShiftUpdater(this, pPackageName, player_number); + } else { + pTimeExtension = new TimeExtension(this, pkg_name, player_number); + pTachometer = new Tachometer(this, pPackageName, player_number); + + if (mPlayerHudType == PHT_STANDARD) { + if (GRaceStatus::Exists() && GRaceStatus::Get().GetPlayMode() == 2) { + pReputation = new Reputation(this, pkg_name, player_number); + } + pHeatMeter = new HeatMeter(this, pkg_name, player_number); + pCostToState = new CostToState(this, pkg_name, player_number); + pPursuitBoard = new PursuitBoard(this, pkg_name, player_number); + pMilestoneBoard = new MilestoneBoard(this, pkg_name, player_number); + pBustedMeter = new BustedMeter(this, pkg_name, player_number); + pMenuZoneTrigger = new MenuZoneTrigger(this, pkg_name, player_number); + pInfractions = new Infractions(this, pkg_name, player_number); + pRadarDetector = new RadarDetector(this, pkg_name, player_number); + } + + if (mPlayerHudType == PHT_STANDARD || mPlayerHudType == PHT_SPLIT1) { + pMinimap = new Minimap(pkg_name, player_number); + } + } + + if (mPlayerHudType == PHT_STANDARD || mPlayerHudType == PHT_DRAG) { + pSpeedBreakerMeter = new SpeedBreakerMeter(this, pkg_name, player_number); + pGetAwayMeter = new GetAwayMeter(this, pkg_name, player_number); + } + + if (TheOnlineManager.IsOnlineRace()) { + pOnlineSupport = new OnlineHUDSupport(pkg_name); + } else { + FEngSetInvisible(static_cast< FEObject * >(FEngFindString(pkg_name, static_cast< int >(0xC18C12FD)))); + FEngSetInvisible(static_cast< FEObject * >(FEngFindString(pkg_name, static_cast< int >(0xC18C12FE)))); + } + + CurrentHudFeatures = 0; + SetHudFeatures(0xFFFFFFFF); + SetHudFeatures(0); + JoyEnable(); +} + +FEngHud::~FEngHud() { + delete pSpeedometer; + pSpeedometer = nullptr; + delete pTachometer; + pTachometer = nullptr; + delete pTachometerDrag; + pTachometerDrag = nullptr; + delete pShiftUpdater; + pShiftUpdater = nullptr; + delete pTurboMeter; + pTurboMeter = nullptr; + delete pEngineTemp; + pEngineTemp = nullptr; + delete pNitrous; + pNitrous = nullptr; + delete pSpeedBreakerMeter; + pSpeedBreakerMeter = nullptr; + delete pRaceOverMessage; + pRaceOverMessage = nullptr; + delete pGenericMessage; + pGenericMessage = nullptr; + delete pRaceInformation; + pRaceInformation = nullptr; + delete pLeaderBoard; + pLeaderBoard = nullptr; + delete pPursuitBoard; + pPursuitBoard = nullptr; + delete pMilestoneBoard; + pMilestoneBoard = nullptr; + delete pBustedMeter; + pBustedMeter = nullptr; + delete pTimeExtension; + pTimeExtension = nullptr; + delete pCostToState; + pCostToState = nullptr; + delete pReputation; + pReputation = nullptr; + delete pHeatMeter; + pHeatMeter = nullptr; + delete pWrongWIndi; + pWrongWIndi = nullptr; + delete pOnlineSupport; + pOnlineSupport = nullptr; + delete p321Go; + p321Go = nullptr; + delete pRadarDetector; + pRadarDetector = nullptr; + delete pMinimap; + pMinimap = nullptr; + delete pGetAwayMeter; + pGetAwayMeter = nullptr; + delete pMenuZoneTrigger; + pMenuZoneTrigger = nullptr; + delete pInfractions; + pInfractions = nullptr; + + cFEng::mInstance->PopNoControlPackage(pPackageName); + + if (mPlayerHudType != PHT_SPLIT2 && mPlayerHudType != PHT_DRAG_SPLIT2) { + TheHudResourceManager.UnloadRequiredResources(mPlayerHudType); + } +} + +void FEngHud::Update(IPlayer *player, float dT) { + ProfileNode profile_node("FEngHud::Update", 0); + + unsigned long long hudFeatures = DetermineHudFeatures(player); + if (hudFeatures != CurrentHudFeatures) { + SetHudFeatures(hudFeatures); + } + + if (mActionQ.IsEnabled()) { + bool loading = TheGameFlowManager.IsLoading(); + if (!loading && !bIsRestartingRace && !UTL::Collections::Singleton< INIS >::Get() + && FadeScreen::IsFadeScreenOn()) { + new EFadeScreenOff(0x14035fb); + } + } + + SetWideScreenMode(); + + if (hudFeatures != 0) { + if (pSpeedometer && pSpeedometer->IsElementVisible()) { + pSpeedometer->Update(player); + } + if (pTachometer && pTachometer->IsElementVisible()) { + pTachometer->Update(player); + } + if (pTachometerDrag && pTachometerDrag->IsElementVisible()) { + pTachometerDrag->Update(player); + } + if (pShiftUpdater && pShiftUpdater->IsElementVisible()) { + pShiftUpdater->Update(player); + } + if (pMinimap && pMinimap->IsElementVisible()) { + pMinimap->Update(player); + } + if (pRaceInformation && pRaceInformation->IsElementVisible()) { + pRaceInformation->Update(player); + } + if (pLeaderBoard && pLeaderBoard->IsElementVisible()) { + pLeaderBoard->Update(player); + } + if (pPursuitBoard && pPursuitBoard->IsElementVisible()) { + pPursuitBoard->Update(player); + } + if (pMilestoneBoard && pMilestoneBoard->IsElementVisible()) { + pMilestoneBoard->Update(player); + } + if (pBustedMeter && pBustedMeter->IsElementVisible()) { + pBustedMeter->Update(player); + } + if (pTimeExtension && pTimeExtension->IsElementVisible()) { + pTimeExtension->Update(player); + } + if (pCostToState && pCostToState->IsElementVisible()) { + pCostToState->Update(player); + } + if (pReputation && pReputation->IsElementVisible()) { + pReputation->Update(player); + } + if (pHeatMeter && pHeatMeter->IsElementVisible()) { + pHeatMeter->Update(player); + } + if (pNitrous && pNitrous->IsElementVisible()) { + pNitrous->Update(player); + } + if (pSpeedBreakerMeter && pSpeedBreakerMeter->IsElementVisible()) { + pSpeedBreakerMeter->Update(player); + } + if (pGetAwayMeter && pGetAwayMeter->IsElementVisible()) { + pGetAwayMeter->Update(player); + } + if (pRaceOverMessage && pRaceOverMessage->IsElementVisible()) { + pRaceOverMessage->Update(player); + } + if (pGenericMessage && pGenericMessage->IsElementVisible()) { + pGenericMessage->Update(player); + } + if (pTurboMeter && pTurboMeter->IsElementVisible()) { + pTurboMeter->Update(player); + } + if (pEngineTemp && pEngineTemp->IsElementVisible()) { + pEngineTemp->Update(player); + } + if (p321Go && p321Go->IsElementVisible()) { + p321Go->Update(player); + } + if (pRadarDetector && pRadarDetector->IsElementVisible()) { + pRadarDetector->Update(player); + } + if (pMenuZoneTrigger && pMenuZoneTrigger->IsElementVisible()) { + pMenuZoneTrigger->Update(player); + } + if (pWrongWIndi && pWrongWIndi->IsElementVisible()) { + pWrongWIndi->Update(player); + } + if (pOnlineSupport) { + pOnlineSupport->Update(player); + } + if (pInfractions && pInfractions->IsElementVisible()) { + pInfractions->Update(player); + } + + if (MemoryCard::s_pThis->m_bAutoSaveRequested) { + MemoryCard::s_pThis->m_bHUDLoaded = true; + } + } + + JoyHandle(player); +} + +void FEngHud::SetInPursuit(bool inPursuit) { + if (mInPursuit != inPursuit) { + mInPursuit = inPursuit; + } +} + +void FEngHud::JoyHandle(IPlayer *player) { + if (!player || !player->GetSettings()) { + mActionQ.SetPort(-1); + mActionQ.SetConfig(0, "FEngHud"); + return; + } + + { + bool wheel_connected = false; + if (player->GetSteeringDevice()) { + if (player->GetSteeringDevice()->IsConnected()) { + wheel_connected = true; + } + } + + mActionQ.SetPort(player->GetControllerPort()); + mActionQ.SetConfig(player->GetSettings()->GetControllerAttribs(CA_HUD, wheel_connected), "FEngHud"); + + if (mActionQ.IsEmpty()) goto drain; + if (MemoryCard::GetInstance()->IsAutoSaving()) goto drain; + if (MemoryCard::GetInstance()->AutoSaveRequested()) goto drain; + + { + ActionRef aRef = mActionQ.GetAction(); + + if (!CurrentHudFeatures) goto drain; + + switch (aRef.ID()) { + case HUDACTION_PAUSEREQUEST: + new EPause(player->GetSettingsIndex(), 0, 0); + break; + + case HUDACTION_ENGAGE_EVENT: + if (!FEDatabase->IsLANMode() && !FEDatabase->IsOnlineMode()) { + ISimable *isimable = player->GetSimable(); + IVehicleAI *vehicleai; + IMenuZoneTrigger *izone; + ePursuitStatus pursuitStatus; + IPursuit *ipursuit; + + if (isimable->QueryInterface(&vehicleai)) { + ipursuit = vehicleai->GetPursuit(); + if (ipursuit) { + pursuitStatus = ipursuit->GetPursuitStatus(); + if (pursuitStatus == PS_COOL_DOWN) { + if (QueryInterface(&izone)) { + if (izone->IsPlayerInsideTrigger()) { + if (izone->IsType("safehouse")) { + ipursuit->EndPursuitEnteringSafehouse(); + break; + } + } + } + } + } + } + + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Racing && + !GRaceStatus::Get().GetRaceParameters()->GetIsPursuitRace()) { + new EShowResults(FERESULTTYPE_RACE, true); + } else if (mInPursuit) { + new EShowResults(FERESULTTYPE_PURSUIT, true); + } else { + if (QueryInterface(&izone)) { + if (izone->IsPlayerInsideTrigger()) { + izone->ExitTrigger(); + izone->RequestEventInfoDialog(mActionQ.GetPort()); + } + } + } + } + break; + + case HUDACTION_PAD_LEFT: + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming) { + new EWorldMapOn(); + } + break; + + case HUDACTION_PAD_DOWN: + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming) { + if (!FEDatabase->IsDDay()) { + new ERaceSheetOn(0); + } + } + break; + + case HUDACTION_PAD_RIGHT: + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming) { + new EShowSMS(-1); + } + break; + } + } + + drain: + while (!mActionQ.IsEmpty()) { + mActionQ.PopAction(); + } + } +} + +void FEngHud::SetHasTurbo(bool hasTurbo) { + mHasTurbo = hasTurbo; +} + +bool FEngHud::IsHudVisible() { + return CurrentHudFeatures != 0; +} + +void FEngHud::HideAll() { + SetHudFeatures(0); +} + +void FEngHud::Release() { + delete this; +} + +unsigned long long FEngHud::DetermineHudFeatures(IPlayer *player) { + unsigned long long hud_features = 0; + + eView *view = eGetView(player->GetRenderPort(), false); + CameraMover *cammover = nullptr; + if (view) { + cammover = view->GetCameraMover(); + } + + if (!cammover) { + return 0; + } + if (cammover->GetType() != CM_DRIVE_CUBIC) { + return 0; + } + if (cammover->GetLookbackAngle()) { + return 0; + } + + if (GRaceStatus::Exists()) { + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Racing && bIsRestartingRace) { + return 0; + } + } + + if (FEManager::IsPaused()) { + return 0; + } + if (Sim::GetState() != Sim::STATE_ACTIVE) { + return 0; + } + if (cFEng::Get()->IsPackagePushed("Pause_Main.fng")) { + return 0; + } + if (cFEng::Get()->IsPackagePushed("MC_Main_GC.fng")) { + return 0; + } + if (cFEng::Get()->IsPackagePushed("PostRace_Results.fng")) { + return 0; + } + if (!TheHudResourceManager.AreResourcesLoaded(mPlayerHudType)) { + return 0; + } + if (UTL::Collections::Singleton< INIS >::Get()) { + return 0; + } + if (mCurrentWidescreenSetting != FEDatabase->GetVideoSettings()->WideScreen) { + return 0; + } + if (TheICEManager.IsEditorOn()) { + return 0; + } + if (TheGameFlowManager.IsLoading()) { + return 0; + } + + bool EnableMinimap = (LoadedTrackInfo->TrackNumber == 2000); + if (!GRaceStatus::Get().GetRaceParameters()) { + if (FEDatabase->GetGameplaySettings()->ExploringMiniMapMode == 2) { + EnableMinimap = false; + } + } else { + if (FEDatabase->GetGameplaySettings()->RacingMiniMapMode == 2) { + EnableMinimap = false; + } + } + + if (EnableMinimap) { + hud_features |= 0x10000; + hud_features |= 0x4000; + } + + if (GRaceStatus::IsDragRace()) { + if (FEDatabase->GetPlayerSettings(PlayerNumber)->PositionOn) { + hud_features |= 0x8000000; + } + } else { + if (FEDatabase->GetPlayerSettings(PlayerNumber)->GaugesOn) { + hud_features |= 0x8000000; + } + } + + if (FEDatabase->GetPlayerSettings(PlayerNumber)->GaugesOn) { + hud_features |= 0x2; + + if (mHasTurbo) { + hud_features |= 0x20000; + } + + if (GRaceStatus::IsDragRace()) { + hud_features |= 0x40; + } + + hud_features |= 0x800; + hud_features |= 0x40000; + } + + bool pursuitRace = false; + if (GRaceStatus::Get().GetRaceParameters()) { + pursuitRace = GRaceStatus::Get().GetRaceParameters()->GetIsPursuitRace(); + } + + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Racing && !pursuitRace) { + if (FEDatabase->GetPlayerSettings(PlayerNumber)->LeaderboardOn) { + hud_features |= 0x8; + } + if (FEDatabase->GetPlayerSettings(PlayerNumber)->PositionOn) { + hud_features |= 0x4000000; + } + } else { + hud_features |= 0x100000; + hud_features |= 0x400000000ULL; + } + + if (FEDatabase->GetPlayerSettings(PlayerNumber)->SplitTimeType != 4) { + hud_features |= 0x10; + } + + if (GRaceStatus::IsTollboothRace()) { + hud_features |= 0x2000000; + } + + if (player->GetSettings()->ScoreOn) { + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career + || (GRaceStatus::Get().GetRaceParameters() + && GRaceStatus::Get().GetRaceParameters()->GetIsPursuitRace())) { + hud_features |= 0x1000; + } + } + + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Racing) { + hud_features |= 0x20; + } + + IRaceOverMessage *iraceover; + if (QueryInterface(&iraceover)) { + if (iraceover->ShouldShowRaceOverMessage()) { + hud_features |= 0x4; + } + } + + hud_features |= 0x1000000; + hud_features |= 0x10000000; + hud_features |= 0x100000000ULL; + hud_features |= 0x80000; + hud_features |= 0x400; + hud_features |= 0x20000000; + hud_features |= 0x400000; + hud_features |= 0x200000000ULL; + hud_features |= 0x800000; + hud_features |= 0x200000; + + return hud_features; +} + +bool FEngHud::AreResourcesLoaded() { + return TheHudResourceManager.AreResourcesLoaded(mPlayerHudType); +} + +void FEngHud::RefreshMiniMapItems() { + if (pMinimap) { + static_cast< Minimap * >(pMinimap)->RefreshMapItems(); + } +} + +OnlineHUDSupport *FEngHud::GetOnlineHUDSupport() { + return static_cast< OnlineHUDSupport * >(pOnlineSupport); +} + +extern const char lbl_803E4E0C[]; +extern const char lbl_803E572C[]; + +void FEngHud::FadeAll(bool fadeIn) { + if (fadeIn) { + { + const unsigned long FEObj_FADEIN = 0xBCC00F05; + cFEng::Get()->QueuePackageMessage(FEObj_FADEIN, pPackageName, nullptr); + cFEng::Get()->QueuePackageMessage(FEHashUpper(lbl_803E572C), pPackageName, nullptr); + } + } else { + { + const unsigned long FEObj_FADEOUT = 0x54C20A66; + cFEng::Get()->QueuePackageMessage(FEObj_FADEOUT, pPackageName, nullptr); + cFEng::Get()->QueuePackageMessage(FEHashUpper(lbl_803E4E0C), pPackageName, nullptr); + } + } +} + +void FEngHud::SetHudFeatures(unsigned long long hud_features) { + unsigned long long diff = CurrentHudFeatures ^ hud_features; + if (pSpeedometer != nullptr && (diff & 0x8000000)) { + pSpeedometer->Toggle(hud_features); + } + if (pTachometer != nullptr && (diff & 0x2)) { + pTachometer->Toggle(hud_features); + } + if (pTachometerDrag != nullptr && (diff & 0x2)) { + pTachometerDrag->Toggle(hud_features); + } + if (pShiftUpdater != nullptr && (diff & 0x20000000)) { + pShiftUpdater->Toggle(hud_features); + } + if (pTurboMeter != nullptr && (diff & 0x20000)) { + pTurboMeter->Toggle(hud_features); + } + if (pEngineTemp != nullptr && (diff & 0x40)) { + pEngineTemp->Toggle(hud_features); + } + if (pNitrous != nullptr && (diff & 0x800)) { + pNitrous->Toggle(hud_features); + } + if (pSpeedBreakerMeter != nullptr && (diff & 0x40000)) { + pSpeedBreakerMeter->Toggle(hud_features); + } + if (pHeatMeter != nullptr && (diff & 0x4000)) { + pHeatMeter->Toggle(hud_features); + } + if (pMinimap != nullptr && (diff & 0x10000)) { + pMinimap->Toggle(hud_features); + } + if (pGetAwayMeter != nullptr && (diff & 0x200)) { + pGetAwayMeter->Toggle(hud_features); + } + if (pMenuZoneTrigger != nullptr && (diff & 0x400000)) { + pMenuZoneTrigger->Toggle(hud_features); + } + if (pRaceInformation != nullptr && (diff & 0x4000000)) { + pRaceInformation->Toggle(hud_features); + } + if (pLeaderBoard != nullptr) { + if ((diff & 0x8) || (diff & 0x10)) { + pLeaderBoard->Toggle(hud_features); + } + } + if (pPursuitBoard != nullptr && (diff & 0x100000)) { + pPursuitBoard->Toggle(hud_features); + } + if (pMilestoneBoard != nullptr && (diff & 0x400000000ULL)) { + pMilestoneBoard->Toggle(hud_features); + } + if (pBustedMeter != nullptr && (diff & 0x800000)) { + pBustedMeter->Toggle(hud_features); + } + if (pTimeExtension != nullptr && (diff & 0x2000000)) { + pTimeExtension->Toggle(hud_features); + } + if (pCostToState != nullptr && (diff & 0x1000)) { + pCostToState->Toggle(hud_features); + } + if (pReputation != nullptr && (diff & 0x1000)) { + pReputation->Toggle(hud_features); + } + if (pWrongWIndi != nullptr && (diff & 0x20)) { + pWrongWIndi->Toggle(hud_features); + } + if (pRaceOverMessage != nullptr && (diff & 0x4)) { + pRaceOverMessage->Toggle(hud_features); + } + if (pGenericMessage != nullptr && (diff & 0x1000000)) { + pGenericMessage->Toggle(hud_features); + } + if (pRadarDetector != nullptr && (diff & 0x200000)) { + pRadarDetector->Toggle(hud_features); + } + if (p321Go != nullptr && (diff & 0x400)) { + p321Go->Toggle(hud_features); + } + if (pInfractions != nullptr && (diff & 0x200000000ULL)) { + pInfractions->Toggle(hud_features); + } + CurrentHudFeatures = hud_features; +} + +void FEngHud::JoyEnable() { + int port = pPlayer->GetControllerPort(); + if (!mActionQ.IsEnabled()) { + mActionQ.SetPort(port); + mActionQ.Enable(true); + mActionQ.Flush(); + } +} + +void FEngHud::JoyDisable() { + JoystickPort port = static_cast(pPlayer->GetControllerPort()); + if (mActionQ.IsEnabled()) { + mActionQ.Enable(false); + mActionQ.Flush(); + } +} + +void FEngHud::SetWideScreenMode() { + if (mCurrentWidescreenSetting != FEDatabase->GetVideoSettings()->WideScreen) { + mCurrentWidescreenSetting = FEDatabase->GetVideoSettings()->WideScreen; + if (mCurrentWidescreenSetting != 0) { + { + const unsigned long FEObj_WIDESCREENMODE = 0x62ED04EC; + + cFEng::Get()->QueuePackageMessage(FEObj_WIDESCREENMODE, pPackageName, nullptr); + } + if (pMinimap) { + static_cast< Minimap * >(pMinimap)->AdjustForWidescreen(true); + } + } else { + { + const unsigned long FEObj_NORMAL_MODE = 0x53EC068C; + + cFEng::Get()->QueuePackageMessage(FEObj_NORMAL_MODE, pPackageName, nullptr); + } + if (pMinimap) { + static_cast< Minimap * >(pMinimap)->AdjustForWidescreen(false); + } + } + } +} + +void HideEverySingleHud() { + const UTL::Collections::Listable::List &list = UTL::Collections::Listable::GetList(); + IHud *const *end = list.end(); + for (IHud *const *iter = list.begin(); iter != end; iter++) { + (*iter)->HideAll(); + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.hpp b/src/Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.hpp index ca2f94034..ff90cfcfd 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.hpp @@ -21,12 +21,47 @@ enum ePlayerHudType { PHT_DRAG_SPLIT2 = 6, }; +enum CAR_SLOT_ID { + CARSLOTID_BASE = 0, +}; + +#include "Speed/Indep/Src/World/CarPart.hpp" + +class Minimap; +class OnlineHUDSupport; +class AutoSaveIcon; + +void FEngSetAllObjectsInPackageVisibility(const char *pPackageName, bool visible); +void FEngSetInvisible(FEObject *pObject); + // total size: 0x348 class FEngHud : public UTL::COM::Object, public IHud { public: FEngHud(ePlayerHudType ht, const char *pkg_name, IPlayer *player, int player_number); + ~FEngHud(); + void Release() override; + void Update(IPlayer *player, float dt) override; + void JoyEnable(); + void JoyDisable(); + void JoyHandle(IPlayer *player); + bool AreResourcesLoaded(); + bool IsHudVisible(); + void HideAll(); + void FadeAll(bool fadeIn); + void SetInPursuit(bool inPursuit); + bool IsInPursuit(); + void SetHasTurbo(bool hasTurbo); + bool DoesHaveTurbo(); + bool IsSplitScreen(); + void RefreshMiniMapItems(); + OnlineHUDSupport *GetOnlineHUDSupport(); + static float ChooseMaxRpmTextureNumber(float rpm); + static bool ShouldRearViewMirrorBeVisible(EVIEW_ID viewId); private: + void SetHudFeatures(unsigned long long features); + unsigned long long DetermineHudFeatures(IPlayer *player); + void SetWideScreenMode(); unsigned long long CurrentHudFeatures; // offset 0x20, size 0x8 ePlayerHudType mPlayerHudType; // offset 0x28, size 0x4 const char *pPackageName; // offset 0x2C, size 0x4 @@ -63,7 +98,7 @@ class FEngHud : public UTL::COM::Object, public IHud { HudElement *pGetAwayMeter; // offset 0x338, size 0x4 HudElement *pMenuZoneTrigger; // offset 0x33C, size 0x4 HudElement *pInfractions; // offset 0x340, size 0x4 - bool mCurrentWidescreenSetting; // offset 0x344, size 0x1 + int mCurrentWidescreenSetting; // offset 0x344, size 0x4 }; // total size: 0xC @@ -76,11 +111,62 @@ class HudResourceManager { HRM_UNLOADING_IN_PROGRESS = 3, }; + HudResourceManager(); + virtual ~HudResourceManager() {} + + const char *GetHudTexPackFilename(ePlayerHudType ht); static const char *GetHudFengName(ePlayerHudType ht); + void LoadRequiredResources(ePlayerHudType ht, const char *pkg_name); + void UnloadRequiredResources(ePlayerHudType ht); + bool AreResourcesLoaded(ePlayerHudType ht); + + void LoadingCompleteCallback(); + void LoadedCustomHudTexturePackCallback(); + void LoadedCustomHudTexturesCallback(); + + static void LoadingCompleteCallbackBridge(int param); + static void LoadingCompleteCallbackBridge(unsigned int param); + static void LoadedCustomHudTexturePackCallbackBridge(unsigned int param); + static void LoadedCustomHudTexturesCallbackBridge(unsigned int param); + + static CarPart *GetCarPart(ePlayerHudType ht, CAR_SLOT_ID carSlotId); + static int GetCustomHudColour(ePlayerHudType ht, CAR_SLOT_ID carSlotId); + static bool GetCustomHudTexPackFilename(ePlayerHudType ht, char *hudTexturePackName); + static bool ChooseMinimapTextureName(ePlayerHudType hudType, char *texture_name, + unsigned int texture_name_size, + char *minimap_texture_name, + unsigned int minimap_texture_name_size); + + static void ChooseLoadableTextures(ePlayerHudType hudType, int &textureHash, + float &redlineRotation); + + static ePlayerHudType LoadingResourcesForHudType; + static int mCustIndex; + static int mPhase; + static int mTachLinesHash; + static ResourceFile *pMiniMapTexture; + static const char *mPackageName; + static char mCustHudTexPackName[32]; + static unsigned int mCustomizeHUDTexTextureResources[5]; private: HudResourceLoadStates mHudResourcesState; // offset 0x0, size 0x4 ResourceFile *pHudTextures; // offset 0x4, size 0x4 }; +extern HudResourceManager TheHudResourceManager; + +inline void HudResourceManager::LoadingCompleteCallbackBridge(int param) { + reinterpret_cast(param)->LoadingCompleteCallback(); +} +inline void HudResourceManager::LoadingCompleteCallbackBridge(unsigned int param) { + reinterpret_cast(param)->LoadingCompleteCallback(); +} +inline void HudResourceManager::LoadedCustomHudTexturePackCallbackBridge(unsigned int param) { + reinterpret_cast(param)->LoadedCustomHudTexturePackCallback(); +} +inline void HudResourceManager::LoadedCustomHudTexturesCallbackBridge(unsigned int param) { + reinterpret_cast(param)->LoadedCustomHudTexturesCallback(); +} + #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FESpeedBreakerMeter.cpp b/src/Speed/Indep/Src/Frontend/HUD/FESpeedBreakerMeter.cpp index e69de29bb..2222e362a 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FESpeedBreakerMeter.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FESpeedBreakerMeter.cpp @@ -0,0 +1,79 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeSpeedBreakerMeter.hpp" + +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/Sim/Simulation.h" + +void FEngSetMultiImageRot(FEMultiImage *image, float angle_degrees); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +unsigned long FEHashUpper(const char *name); +int bStrICmp(const char *s1, const char *s2); +void FEngGetTopLeft(FEObject *object, float &x, float &y); +void FEngGetSize(FEObject *object, float &w, float &h); +void FEngSetSize(FEObject *object, float w, float h); + +extern const char lbl_803E4D20[]; +extern const char lbl_803E4D40[]; +extern const char lbl_803E4D5C[]; +extern const char lbl_803E4D7C[]; +extern const float lbl_803E4D9C; +extern const float lbl_803E4DA0; +extern const float lbl_803E4DA4; + +SpeedBreakerMeter::SpeedBreakerMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x40000) // + , ISpeedBreakerMeter(pOutter) // + , mPursuitLevelChanged(true) // + , mSpeedBreakerBarOriginalWidth(lbl_803E4D9C) // + , mPursuitLevel(lbl_803E4D9C) // +{ + RegisterGroup(FEHashUpper(lbl_803E4D40)); + mpSpeedBreakerMeterIcon = FEngFindObject(GetPackageName(), FEHashUpper(lbl_803E4D5C)); + mpSpeedBreakerMeterBar = RegisterMultiImage(FEHashUpper(lbl_803E4D7C)); + mpSpeedBreakerGroup = RegisterGroup(0x82D60021); + mpSpeedBreakerBar = FEngFindObject(GetPackageName(), 0x1FDAF669); + if (mpSpeedBreakerBar) { + mSpeedBreakerBarOriginalWidth = mpSpeedBreakerBar->GetObjData()->Size.x; + } +} + +void SpeedBreakerMeter::Update(IPlayer *player) { + if (!mPursuitLevelChanged) { + return; + } + mPursuitLevelChanged = false; + + if (mpSpeedBreakerMeterBar != nullptr && mpSpeedBreakerBar != nullptr) { + float maxAngle = lbl_803E4DA0; + if (bStrICmp(pPackageName, lbl_803E4D20) == 0) { + maxAngle = lbl_803E4DA4; + } + FEngSetMultiImageRot(mpSpeedBreakerMeterBar, mPursuitLevel * -maxAngle + maxAngle); + + float topLeftX, topLeftY; + FEngGetTopLeft(mpSpeedBreakerBar, topLeftX, topLeftY); + float sizeW, sizeH; + FEngGetSize(mpSpeedBreakerBar, sizeW, sizeH); + FEngSetSize(mpSpeedBreakerBar, mPursuitLevel * mSpeedBreakerBarOriginalWidth, sizeH); + } + + if (mPursuitLevel <= lbl_803E4D9C) { + if (!FEngIsScriptSet(mpSpeedBreakerMeterIcon, 0x1744B3)) { + FEngSetScript(mpSpeedBreakerMeterIcon, 0x1744B3, true); + } + } else { + if (!FEngIsScriptSet(mpSpeedBreakerMeterIcon, 0x61D30442)) { + FEngSetScript(mpSpeedBreakerMeterIcon, 0x61D30442, true); + } + } +} + +void SpeedBreakerMeter::SetPursuitLevel(float level) { + if (mPursuitLevel == level) { + return; + } + mPursuitLevel = level; + mPursuitLevelChanged = true; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.cpp new file mode 100644 index 000000000..c0e19f15f --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.cpp @@ -0,0 +1,10 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.hpp" + +AutoSaveIcon::AutoSaveIcon(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0) // + , IAutoSaveIcon(pOutter) +{ +} + +void AutoSaveIcon::Update(IPlayer *player) { +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.hpp new file mode 100644 index 000000000..76284e169 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/HUD/FeAutoSaveIcon.hpp @@ -0,0 +1,22 @@ +#ifndef FRONTEND_HUD_FEAUTOSAVEICON_H +#define FRONTEND_HUD_FEAUTOSAVEICON_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" + +class AutoSaveIcon : public HudElement, public IAutoSaveIcon { + public: + AutoSaveIcon(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + + private: + FEObject *mpIcon; + bool mShowingIcon; + bool mIconRequested; +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeBustedMeter.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeBustedMeter.cpp index e69de29bb..16ce85a25 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeBustedMeter.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeBustedMeter.cpp @@ -0,0 +1,35 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeBustedMeter.hpp" + +BustedMeter::BustedMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x800000) // + , IBustedMeter(pOutter) // + , mInPursuit(false) // + , mTimeUntilBusted(0.0f) // + , mIsBusted(false) // + , mBustedFlasherShown(false) +{ +} + +void BustedMeter::Update(IPlayer *player) { +} + +void BustedMeter::SetInPursuit(bool inPursuit) { + mInPursuit = inPursuit; +} + +void BustedMeter::SetIsHiding(bool isHiding) { + mIsHiding = isHiding; +} + +void BustedMeter::SetTimeUntilBusted(float time) { + mTimeUntilBusted = time; +} + +void BustedMeter::SetIsBusted(bool isBusted) { + mIsBusted = isBusted; + if (!isBusted) { + if (mBustedFlasherShown) { + mBustedFlasherShown = false; + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeBustedMeter.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeBustedMeter.hpp index c04b1422f..aee11f701 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeBustedMeter.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeBustedMeter.hpp @@ -5,6 +5,27 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +class BustedMeter : public HudElement, public IBustedMeter { + public: + BustedMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetInPursuit(bool inPursuit) override; + void SetIsHiding(bool isHiding) override; + void SetTimeUntilBusted(float time) override; + void SetIsBusted(bool isBusted) override; + + private: + bool mInPursuit; + float mTimeUntilBusted; + bool mIsHiding; + FEObject * mpDataBustedCountdownGroup; + FEObject * mpDataBustedCountdownBar; + float mBustedCountdownBarOriginalWidth; + bool mIsBusted; + bool mBustedFlasherShown; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeCostToState.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeCostToState.cpp index e69de29bb..4a4bfb2e6 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeCostToState.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeCostToState.cpp @@ -0,0 +1,69 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeCostToState.hpp" + +#include "Speed/Indep/Src/FEng/FEString.h" + +unsigned long FEHashUpper(const char *name); +void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning); +void FEngSetLanguageHash(FEString *text, unsigned int hash); +int FEPrintf(FEString *text, const char *fmt, ...); +FEString *FEngFindString(const char *pkg_name, int name_hash); + +extern const char lbl_803E48B4[]; +extern const char lbl_803E48C0[]; +extern const char lbl_803E48C8[]; +extern const char lbl_803E48CC[]; +extern const char lbl_803E48D4[]; + +CostToState::CostToState(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x1000) // + , ICostToState(pOutter) // + , mCostToStateOn(false) // + , mCostToState(0) // + , mInPursuit(false) // + , mNumFramesLeftToShow(0) // +{ + RegisterGroup(FEHashUpper(lbl_803E48B4)); + FEngSetScript(GetPackageName(), FEHashUpper(lbl_803E48B4), FEHashUpper(lbl_803E48C0), true); + mDataCostToState = FEngFindString(GetPackageName(), 0x3FF5F33C); + mDataTitle = FEngFindString(GetPackageName(), 0x64247241); +} + +void CostToState::Update(IPlayer *player) { + if (mDataCostToState == nullptr) { + return; + } + + if (mNumFramesLeftToShow >= 1) { + mNumFramesLeftToShow = mNumFramesLeftToShow - 1; + FEngSetLanguageHash(mDataTitle, 0x3DD874C5); + FEPrintf(mDataCostToState, lbl_803E48C8, mCostToState); + if (!mCostToStateOn) { + mCostToStateOn = true; + FEngSetScript(GetPackageName(), FEHashUpper(lbl_803E48B4), FEHashUpper(lbl_803E48CC), true); + } + } else { + if (mCostToStateOn) { + mCostToStateOn = false; + FEngSetScript(GetPackageName(), FEHashUpper(lbl_803E48B4), FEHashUpper(lbl_803E48D4), true); + } + } +} + +void CostToState::SetCostToState(int cost) { + if (!mInPursuit) { + return; + } + if (cost > mCostToState) { + mCostToState = cost; + mNumFramesLeftToShow = 0x78; + return; + } + if (cost != 0) { + return; + } + mCostToState = 0; +} + +void CostToState::SetInPursuit(bool inPursuit) { + mInPursuit = inPursuit; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeCostToState.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeCostToState.hpp index 10e0a1bfe..d2a8983cd 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeCostToState.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeCostToState.hpp @@ -5,6 +5,40 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +class ICostToState : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + ICostToState(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + protected: + virtual ~ICostToState() {} + + public: + virtual void SetCostToState(int cost); + virtual void SetInPursuit(bool inPursuit); +}; + +// total size: 0x48 +class CostToState : public HudElement, public ICostToState { + public: + CostToState(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetCostToState(int cost) override; + void SetInPursuit(bool inPursuit) override; + + private: + bool mCostToStateOn; // offset 0x30 + int mCostToState; // offset 0x34 + bool mInPursuit; // offset 0x38 + int mNumFramesLeftToShow; // offset 0x3C + FEString *mDataCostToState; // offset 0x40 + FEString *mDataTitle; // offset 0x44 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeCountdown.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeCountdown.cpp index e69de29bb..be750dbec 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeCountdown.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeCountdown.cpp @@ -0,0 +1,115 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeCountdown.hpp" +#include "Speed/Indep/Src/Animation/AnimWorldTypes.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/Generated/Events/ENISWorldAnimTrigger.hpp" +#include "Speed/Indep/Src/Generated/Messages/MCountdownDone.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" + +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +void FEngSetLanguageHash(FEString *text, unsigned int hash); +int FEPrintf(FEString *text, const char *fmt, ...); + +Countdown::Countdown(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x400) // + , ICountdown(pOutter) // + , mCountdown(RACE_COUNTDOWN_NUMBER_NONE) // + , mSecondTimer(0) +{ + pMessageGroup = RegisterGroup(FEHashUpper("321_GO_GROUP")); + pMessage = static_cast(FEngFindObject(pkg_name, FEHashUpper("321_GO"))); + pMessageShadow = static_cast(FEngFindObject(pkg_name, FEHashUpper("321_GO_SHADOW"))); +} + +void Countdown::Update(IPlayer *player) { + if (!pMessageGroup) return; + if (!pMessage) return; + if (!pMessageShadow) return; + + if (WorldTimer.IsSet()) { + int countdown = mCountdown; + bool shouldStep = false; + + if (countdown == RACE_COUNTDOWN_NUMBER_4) { + shouldStep = true; + } else if (countdown > 0) { + if ((WorldTimer - mSecondTimer).GetSeconds() >= 1.0f) { + shouldStep = true; + } + } else if (!countdown) { + if ((WorldTimer - mSecondTimer).GetSeconds() >= 6.0f) { + shouldStep = true; + } + } + + if (!shouldStep) return; + + mCountdown = static_cast(countdown - 1); + + if (mCountdown == RACE_COUNTDOWN_NUMBER_NONE) { + FEngSetScript(pMessageGroup, 0x16a259, true); + mSecondTimer.UnSet(); + return; + } + + if (mCountdown == RACE_COUNTDOWN_NUMBER_GO) { + FEngSetScript(pMessageGroup, 0x535, true); + FEngSetLanguageHash(pMessage, 0x61223ab5); + FEngSetLanguageHash(pMessageShadow, 0x61223ab5); + + { + MCountdownDone msg; + msg.Post(UCrc32(0x20d60dbf)); + mSecondTimer = WorldTimer; + } + + new ENISWorldAnimTrigger("EN_TollBoothArm_01", 0.0f, 0, 0); + new ENISWorldAnimTrigger("EN_TollBoothArm_02", 0.0f, 0, 0); + new ENISWorldAnimTrigger("EN_TollBoothArm_03", 0.0f, 0, 0); + new ENISWorldAnimTrigger("EN_TollBoothArm_04", 0.0f, 0, 0); + + SetSoundControlState(false, SNDSTATE_NIS_321, "NIS 321"); + return; + } + + if (mCountdown == RACE_COUNTDOWN_NUMBER_3) { + FEngSetScript(pMessageGroup, 0x3c3c2f7, true); + + new ENISWorldAnimTrigger("EN_TollBoothArm_01", 0.0f, 1, 0); + new ENISWorldAnimTrigger("EN_TollBoothArm_02", 0.0f, 1, 0); + new ENISWorldAnimTrigger("EN_TollBoothArm_03", 0.0f, 1, 0); + new ENISWorldAnimTrigger("EN_TollBoothArm_04", 0.0f, 1, 0); + + g_pEAXSound->START_321Countdown(); + } else if (mCountdown == RACE_COUNTDOWN_NUMBER_2) { + FEngSetScript(pMessageGroup, 0xe479, true); + } else if (mCountdown == RACE_COUNTDOWN_NUMBER_1) { + FEngSetScript(pMessageGroup, 0xce01, true); + } + + FEPrintf(pMessage, "%d", mCountdown); + FEPrintf(pMessageShadow, "%d", mCountdown); + mSecondTimer = WorldTimer; + } else { + FEngSetScript(pMessageGroup, 0x16a259, true); + mSecondTimer.UnSet(); + } +} + +void Countdown::BeginCountdown() { + mCountdown = RACE_COUNTDOWN_NUMBER_4; + SetSoundControlState(true, SNDSTATE_NIS_321, "BeginCountdown"); +} + +bool Countdown::IsActive() { + return mCountdown > 0; +} + +float Countdown::GetSecondsBeforeRaceStart() { + if (mCountdown == RACE_COUNTDOWN_NUMBER_4) { + return 3.0f; + } + if (mCountdown > 0) { + return static_cast(mCountdown) - (WorldTimer - mSecondTimer).GetSeconds(); + } + return 0.0f; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeCountdown.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeCountdown.hpp index d4f60f14f..399887abc 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeCountdown.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeCountdown.hpp @@ -5,6 +5,26 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +class Countdown : public HudElement, public ICountdown { + public: + Countdown(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void BeginCountdown() override; + bool IsActive() override; + float GetSecondsBeforeRaceStart() override; + + private: + FEGroup * pMessageGroup; + FEString * pMessage; + FEString * pMessageShadow; + eRaceCountdownNumber mCountdown; + Timer mSecondTimer; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeDragTachometer.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeDragTachometer.cpp new file mode 100644 index 000000000..d930db04a --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/HUD/FeDragTachometer.cpp @@ -0,0 +1,121 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeDragTachometer.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeTachometer.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/FEng/FETypes.h" + +extern void FEngGetSize(FEObject *obj, float &x, float &y); +extern void FEngSetSize(FEObject *obj, float x, float y); +extern void FEngGetTopLeft(FEObject *obj, float &x, float &y); +extern void FEngSetTopLeft(FEObject *obj, float x, float y); +extern void FEngSetColor(FEObject *obj, unsigned int color); +extern FEColor FEngGetObjectColor(FEObject *obj); +extern void FEngSetScript(FEObject *obj, unsigned int script_hash, bool start); +extern int FEPrintf(FEString *text, const char *fmt, ...); + +extern const float lbl_803E5868; +extern const float lbl_803E586C; +extern const float lbl_803E5870; +extern const float lbl_803E5874; + +float DragTachometer::CalcAngleForRPMDrag(float rpm, float redline) { + float factor = rpm / ChooseMaxRpmTextureNumber(redline); + if (factor < lbl_803E5868) { + factor = lbl_803E5868; + } + if (factor > lbl_803E586C) { + factor = lbl_803E586C; + } + float min_angle = lbl_803E5870; + float max_angle = lbl_803E5874; + float fRange = max_angle - min_angle; + return factor * fRange + min_angle; +} + +DragTachometer::DragTachometer(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x2) // + , ITachometer(pOutter) // + , ITachometerDrag(pOutter) +{ + mMaxRpm = 0.0f; + mGear = static_cast(1); + mNeedleColourSetToPerfectLaunch = false; + mRpm = 0.0f; + mRedline = 0.0f; + mGearShifting = false; + RegisterImage(FEHashUpper("RPM_fill")); + RegisterImage(FEHashUpper("Drag_Turbo_Backing")); + RegisterImage(FEHashUpper("Drag_Turbo_Lines")); + RegisterImage(FEHashUpper("3rdPersonSpeedUnits")); + RegisterImage(FEHashUpper("SPEED_BACKING")); + RegisterGroup(FEHashUpper("RPM")); + pTachLines = RegisterImage(FEHashUpper("TAC_Lines_7500")); + pRedline = RegisterMultiImage(FEHashUpper("RPM_Redline")); + TachNeedle = RegisterImage(FEHashUpper("3rdPersonNeedle")); + pGearString = RegisterString(FEHashUpper("3rdPersonGear")); + mOriginalNeedleWidth = TachNeedle->GetObjData()->Size.x; + float x, y; + FEngGetTopLeft(TachNeedle, x, y); + mOriginalNeedleLeftX = x; +} + +void DragTachometer::Update(IPlayer *player) { + if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + float normalizedRev = mRpm / mMaxRpm; + float w, h; + FEngGetSize(TachNeedle, w, h); + FEngSetSize(TachNeedle, normalizedRev * mOriginalNeedleWidth, h); + float x, y; + FEngGetTopLeft(TachNeedle, x, y); + FEngSetTopLeft(TachNeedle, mOriginalNeedleLeftX, y); + if (mRpm < mRedline) { + FEngSetScript(TachNeedle, 0x1744B3, true); + } else { + FEngSetScript(TachNeedle, 0x61D30442, true); + } + } else { + FEngSetRotationZ(TachNeedle, CalcAngleForRPMDrag(mRpm, mMaxRpm)); + } + + if (pGearString) { + const FEColor colourGearNormal(0xFFFFFFFF); + const FEColor colourGearChanging(0x88FFFFFF); + FEPrintf(pGearString, "%c", Tachometer::GetLetterForGear(mGear)); + if (!mGearShifting) { + FEngSetColor(pGearString, static_cast(colourGearNormal)); + } else { + FEngSetColor(pGearString, static_cast(colourGearChanging)); + } + } + + if (!mInPerfectLaunchRange) { + if (mNeedleColourSetToPerfectLaunch) { + mNeedleColourSetToPerfectLaunch = false; + FEColor originalNeedleColour = FEngGetObjectColor(TachNeedle); + unsigned int oppositeOriginal = static_cast(originalNeedleColour); + FEngSetColor(TachNeedle, ~oppositeOriginal | 0xFF000000); + } + } else if (!mNeedleColourSetToPerfectLaunch) { + mNeedleColourSetToPerfectLaunch = true; + FEColor originalNeedleColour = FEngGetObjectColor(TachNeedle); + unsigned int oppositeOriginal = static_cast(originalNeedleColour); + FEngSetColor(TachNeedle, ~oppositeOriginal | 0xFF000000); + } +} + +void DragTachometer::SetRpm(float rpm) { + mRpm = rpm; +} + +void DragTachometer::SetInPerfectLaunchRange(bool inRange) { + mInPerfectLaunchRange = inRange; +} + +void DragTachometer::SetShifting(bool shifting) { + mGearShifting = shifting; +} + +void DragTachometer::SetGear(GearID gear, ShiftPotential potential, bool hasGoodEnoughTraction) { + if (gear != mGear) { + mGear = gear; + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeDragTachometer.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeDragTachometer.hpp new file mode 100644 index 000000000..5166c1c7e --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/HUD/FeDragTachometer.hpp @@ -0,0 +1,46 @@ +#ifndef FRONTEND_HUD_FEDRAGTACHOMETER_H +#define FRONTEND_HUD_FEDRAGTACHOMETER_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/Physics/PhysicsTypes.h" + +struct FEMultiImage; + +class DragTachometer : public HudElement, public ITachometer, public ITachometerDrag { + public: + DragTachometer(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetRpm(float rpm) override; + void SetRevLimiter(float redline, float maxRpm) override { + mRedline = redline; + mMaxRpm = maxRpm; + } + void SetInPerfectLaunchRange(bool inRange) override; + void SetShifting(bool shifting) override; + void SetGear(GearID gear, ShiftPotential potential, bool hasGoodEnoughTraction); + float CalcAngleForRPMDrag(float rpm, float redline); + + private: + FEImage * TachNeedle; + FEImage * Needle; + FEMultiImage * pRedline; + FEImage * pTachLines; + FEString * pGearString; + float mRpm; + float mRedline; + float mMaxRpm; + GearID mGear; + bool mGearShifting; + bool mInPerfectLaunchRange; + bool mNeedleColourSetToPerfectLaunch; + float mOriginalNeedleWidth; + float mOriginalNeedleLeftX; +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.cpp index e69de29bb..49a9f61c0 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.cpp @@ -0,0 +1,75 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.hpp" + +#include "Speed/Indep/Src/FEng/FEMultiImage.h" + +void FEngSetMultiImageRot(FEMultiImage *image, float angle_degrees); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +unsigned long FEHashUpper(const char *name); + +extern const char lbl_803E4DB0[]; +extern const char lbl_803E4DC8[]; +extern const char lbl_803E4DE0[]; +extern const float lbl_803E4DF0; +extern float warningPulseMinRpm; + +EngineTempGauge::EngineTempGauge(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x40) // + , IEngineTempGauge(pOutter) // + , mEngineTemp(lbl_803E4DF0) // + , mEngineTempChanged(true) // +{ + RegisterGroup(FEHashUpper(lbl_803E4DB0)); + mpWarningLight = FEngFindObject(GetPackageName(), FEHashUpper(lbl_803E4DC8)); + mpEngineTempGaugeBar = RegisterMultiImage(FEHashUpper(lbl_803E4DE0)); +} + +void EngineTempGauge::Update(IPlayer *player) { + if (!mEngineTempChanged) { + return; + } + mEngineTempChanged = false; + + if (mpEngineTempGaugeBar) { + const float min_angle = -26.5f; + const float max_angle = 26.5f; + FEngSetMultiImageRot(mpEngineTempGaugeBar, mEngineTemp * max_angle + min_angle); + + if (mEngineTemp > warningPulseMinRpm) { + if (!FEngIsScriptSet(mpEngineTempGaugeBar, FEHashUpper("OVERHEAT_PULSE"))) { + FEngSetScript(mpEngineTempGaugeBar, FEHashUpper("OVERHEAT_PULSE"), true); + } + } else { + if (!FEngIsScriptSet(mpEngineTempGaugeBar, FEHashUpper("INIT"))) { + FEngSetScript(mpEngineTempGaugeBar, FEHashUpper("INIT"), true); + } + } + } + + if (mpWarningLight) { + if (mEngineTemp > warningPulseMinRpm) { + if (!FEngIsScriptSet(mpWarningLight, FEHashUpper("OVERHEAT_PULSE"))) { + FEngSetScript(mpWarningLight, FEHashUpper("OVERHEAT_PULSE"), true); + } + } else if (mEngineTemp > 0.1f) { + if (!FEngIsScriptSet(mpWarningLight, FEHashUpper("ACTIVATE"))) { + FEngSetScript(mpWarningLight, FEHashUpper("ACTIVATE"), true); + } + } else { + if (FEngIsScriptSet(mpWarningLight, FEHashUpper("INIT"))) { + return; + } + FEngSetScript(mpWarningLight, FEHashUpper("INIT"), true); + return; + } + } +} + +void EngineTempGauge::SetEngineTemp(float temp) { + if (mEngineTemp == temp) { + return; + } + mEngineTemp = temp; + mEngineTempChanged = true; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.hpp index 4269a91ff..38cedecaf 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeEngineTempGauge.hpp @@ -5,6 +5,38 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +struct FEMultiImage; + +class IEngineTempGauge : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + IEngineTempGauge(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + protected: + virtual ~IEngineTempGauge() {} + + public: + virtual void SetEngineTemp(float temp); +}; + +// total size: 0x40 +class EngineTempGauge : public HudElement, public IEngineTempGauge { + public: + EngineTempGauge(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetEngineTemp(float temp) override; + + private: + float mEngineTemp; // offset 0x30 + bool mEngineTempChanged; // offset 0x34 + FEObject *mpWarningLight; // offset 0x38 + FEMultiImage *mpEngineTempGaugeBar; // offset 0x3C +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeGenericMessage.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeGenericMessage.cpp index e69de29bb..16ddf0e0e 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeGenericMessage.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeGenericMessage.cpp @@ -0,0 +1,99 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeGenericMessage.hpp" + +extern bool FEngIsScriptRunning(FEObject *object, unsigned int script_hash); +extern bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +extern void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +extern bool FEngIsScriptSet(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash); +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning); +extern char *bStrCpy(char *dst, const char *src); +extern char *bSafeStrCpy(char *dst, const char *src, int maxlen); +extern int FEPrintf(const char *pkg_name, int obj_hash, const char *fmt, ...); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); + +GenericMessage::GenericMessage(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x01000000ULL) // + , IGenericMessage(pOutter) +{ + mPriority = GenericMessage_Priority_None; + mNumFramesPlayed = 0; + mFengHash = 0; + mPlayOneFrame = false; + bStrCpy(mStringBuffer, ""); + mpMessageFirstLine = RegisterGroup(0x32a7a521); + mpIcon = RegisterObject(0x6dd754ec); + RegisterObject(0xcaec9d04); +} + +void GenericMessage::Update(IPlayer *player) { + if (mPriority > GenericMessage_Priority_None) { + if (!FEngIsScriptRunning(mpMessageFirstLine, mFengHash) || + (mPlayOneFrame && mNumFramesPlayed != 0)) { + if (!FEngIsScriptSet(mpMessageFirstLine, 0x16a259)) { + FEngSetScript(mpMessageFirstLine, 0x16a259, true); + } + mPriority = GenericMessage_Priority_None; + bStrCpy(mStringBuffer, ""); + if (!FEngIsScriptSet(mpIcon, 0x16a259)) { + FEngSetScript(mpIcon, 0x16a259, true); + } + } + mNumFramesPlayed++; + } +} + +GenericMessage_Priority GenericMessage::GetCurrentGenericMessagePriority() { + return mPriority; +} + +void GenericMessage::RequestGenericMessageZoomOut(unsigned int fengHash) { + if (!FEngIsScriptSet(GetPackageName(), 0xe0ba07ec, 0xe1c034fc)) { + FEngSetScript(GetPackageName(), 0xe0ba07ec, 0xe1c034fc, true); + } +} + +bool GenericMessage::RequestGenericMessage(const char *string, bool singleFrame, + unsigned int fengHash, unsigned int iconTextureHash, unsigned int iconFengHash, + GenericMessage_Priority priority) { + if (priority < mPriority) { + return false; + } + if (!FEngIsScriptSet(pPackageName, 0xe0ba07ec, 0x1744b3)) { + FEngSetScript(pPackageName, 0xe0ba07ec, 0x1744b3, true); + } + mPriority = priority; + mNumFramesPlayed = 0; + mPlayOneFrame = singleFrame; + mFengHash = fengHash; + if (string) { + bSafeStrCpy(mStringBuffer, string, 0x40); + if (fengHash) { + if (!mPlayOneFrame) { + FEngSetScript(mpMessageFirstLine, fengHash, true); + } else { + if (!FEngIsScriptSet(mpMessageFirstLine, fengHash)) { + FEngSetScript(mpMessageFirstLine, fengHash, true); + } + } + } + FEPrintf(pPackageName, 0x32a7a521, "%s", mStringBuffer); + } + if (iconFengHash == 0 || iconTextureHash == 0) { + if (!FEngIsScriptSet(mpIcon, 0x16a259)) { + FEngSetScript(mpIcon, 0x16a259, true); + } + } else { + if (!mPlayOneFrame) { + FEngSetScript(mpIcon, iconFengHash, true); + } else { + if (!FEngIsScriptSet(mpIcon, iconFengHash)) { + FEngSetScript(mpIcon, iconFengHash, true); + } + } + FEngSetTextureHash(static_cast(mpIcon), iconTextureHash); + } + return true; +} + +bool GenericMessage::IsGenericMessageShowing() { + return mPriority > 0; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeGenericMessage.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeGenericMessage.hpp index 90aa6f6fe..72ec2bb99 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeGenericMessage.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeGenericMessage.hpp @@ -5,6 +5,27 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +class GenericMessage : public HudElement, public IGenericMessage { + public: + GenericMessage(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + GenericMessage_Priority GetCurrentGenericMessagePriority() override; + bool RequestGenericMessage(const char *string, bool singleFrame, unsigned int fengHash, unsigned int iconTextureHash, unsigned int iconFengHash, GenericMessage_Priority priority) override; + void RequestGenericMessageZoomOut(unsigned int fengHash) override; + bool IsGenericMessageShowing() override; + + private: + FEObject * mpMessageFirstLine; + FEObject * mpMessageSecondLine; + FEObject * mpIcon; + GenericMessage_Priority mPriority; + unsigned int mNumFramesPlayed; + char mStringBuffer[64]; + unsigned int mFengHash; + bool mPlayOneFrame; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.cpp index e69de29bb..b9dc08816 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.cpp @@ -0,0 +1,27 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.hpp" + +#include "Speed/Indep/Src/FEng/FEString.h" + +unsigned long FEHashUpper(const char *name); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); + +extern const char lbl_803E46AC[]; +extern const char lbl_803E46C4[]; +extern const char lbl_803E46DC[]; +extern const float lbl_803E46EC; + +GetAwayMeter::GetAwayMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x200) // + , IGetAwayMeter(pOutter) // + , mGetawayDistance(lbl_803E46EC) // +{ + RegisterGroup(FEHashUpper(lbl_803E46AC)); + mpDataDistanceBar = FEngFindObject(pkg_name, FEHashUpper(lbl_803E46C4)); + mpDataDistanceString = static_cast< FEString * >(FEngFindObject(pkg_name, FEHashUpper(lbl_803E46DC))); +} + +void GetAwayMeter::Update(IPlayer *player) {} + +void GetAwayMeter::SetGetAwayDistance(float distance) { + mGetawayDistance = distance; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.hpp index 1b5443f62..5c10ed2de 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeGetawayMeter.hpp @@ -5,6 +5,34 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +class IGetAwayMeter : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + IGetAwayMeter(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual void SetGetAwayDistance(float distance); + + protected: + virtual ~IGetAwayMeter() {} +}; + +// total size: 0x40 +class GetAwayMeter : public HudElement, public IGetAwayMeter { + public: + GetAwayMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetGetAwayDistance(float distance) override; + + private: + float mGetawayDistance; // offset 0x30 + FEObject *mpDataDistanceBar; // offset 0x34 + FEString *mpDataDistanceString; // offset 0x38 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeHeatMeter.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeHeatMeter.cpp index e69de29bb..54e47016f 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeHeatMeter.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeHeatMeter.cpp @@ -0,0 +1,108 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeHeatMeter.hpp" + +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/FEng/FEString.h" + +void FEngSetMultiImageRot(FEMultiImage *image, float angle_degrees); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +void FEngSetVisible(FEObject *obj); +void FEngSetInvisible(FEObject *obj); +int FEPrintf(const char *pkg_name, int obj_hash, const char *fmt, ...); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); + +extern const float lbl_803E4888; +extern const char lbl_803E488C[]; +extern const float lbl_803E4890; +extern const float lbl_803E4898; +extern const float lbl_803E48A0; +extern const float lbl_803E48A4; +extern const float lbl_803E48A8; +extern const float lbl_803E48AC; +extern const float lbl_803E48B0; + +HeatMeter::HeatMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x40004000ULL) // + , IHeatMeter(pOutter) // + , mHeatChanged(true) // + , mPursuitHeat(lbl_803E4888) // + , mVehicleHeat(lbl_803E4888) // +{ + RegisterGroup(0xC46A80A9); + mpDataHeatMultiplier = FEngFindObject(GetPackageName(), 0x7F91DA62); + mpDataHeatMeterIcon = FEngFindObject(GetPackageName(), 0x6F85ED55); + mpHeatMeterBar = RegisterMultiImage(0x862824C9); + mpHeatMeterBar2 = RegisterMultiImage(0x4B2CBE1B); +} + +void HeatMeter::Update(IPlayer *player) { + mHeatChanged = false; + float heatToUse = mVehicleHeat; + if (mPursuitHeat > lbl_803E4890) { + heatToUse = mPursuitHeat; + } + + const int heatIntegerPart = static_cast(heatToUse); + const float heatDecimalPart = heatToUse - static_cast(heatIntegerPart); + + float heatDecimalPartToUse = heatDecimalPart; + if (heatDecimalPart > lbl_803E48A0) { + heatDecimalPartToUse = lbl_803E48A0; + } + FEngSetMultiImageRot(mpHeatMeterBar, (heatDecimalPartToUse + heatDecimalPartToUse) * lbl_803E48A8 + lbl_803E48A4); + + if (heatDecimalPartToUse < heatDecimalPart) { + heatDecimalPartToUse = heatDecimalPart - heatDecimalPartToUse; + } else { + heatDecimalPartToUse = lbl_803E4890; + } + FEngSetMultiImageRot(mpHeatMeterBar2, (heatDecimalPartToUse + heatDecimalPartToUse) * lbl_803E48A8 + lbl_803E48A4); + + if (heatToUse >= lbl_803E48AC) { + if (heatDecimalPart < lbl_803E48A0) { + if (!FEngIsScriptSet(mpDataHeatMultiplier, 0x41E1FEDC)) { + FEngSetScript(mpDataHeatMultiplier, 0x41E1FEDC, true); + } + } else { + if (!FEngIsScriptSet(mpDataHeatMultiplier, 0x1744B3)) { + FEngSetScript(mpDataHeatMultiplier, 0x1744B3, true); + } + } + FEPrintf(pPackageName, 0x7F91DA62, lbl_803E488C, heatIntegerPart); + FEngSetVisible(mpDataHeatMultiplier); + } else { + FEngSetInvisible(mpDataHeatMultiplier); + } + + if (heatToUse > lbl_803E4890) { + if (heatDecimalPart > lbl_803E48B0) { + if (!FEngIsScriptSet(mpDataHeatMeterIcon, 0xDA600155)) { + FEngSetScript(mpDataHeatMeterIcon, 0xDA600155, true); + } + } else { + if (!FEngIsScriptSet(mpDataHeatMeterIcon, 0x77031C70)) { + FEngSetScript(mpDataHeatMeterIcon, 0x77031C70, true); + } + } + } else { + if (!FEngIsScriptSet(mpDataHeatMeterIcon, 0x1744B3)) { + FEngSetScript(mpDataHeatMeterIcon, 0x1744B3, true); + } + } +} + +void HeatMeter::SetVehicleHeat(float heat) { + if (mVehicleHeat == heat) { + return; + } + mVehicleHeat = heat; + mHeatChanged = true; +} + +void HeatMeter::SetPursuitHeat(float heat) { + if (mPursuitHeat == heat) { + return; + } + mPursuitHeat = heat; + mHeatChanged = true; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeHeatMeter.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeHeatMeter.hpp index 1631d5128..07c82c40f 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeHeatMeter.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeHeatMeter.hpp @@ -5,6 +5,44 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +struct FEMultiImage; + +class IHeatMeter : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + IHeatMeter(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + protected: + virtual ~IHeatMeter() {} + + public: + virtual void SetVehicleHeat(float heat); + virtual void SetPursuitHeat(float heat); +}; + +// total size: 0x50 +class HeatMeter : public HudElement, public IHeatMeter { + public: + HeatMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetVehicleHeat(float heat) override; + void SetPursuitHeat(float heat) override; + + private: + bool mHeatChanged; // offset 0x30 + float mPursuitHeat; // offset 0x34 + float mVehicleHeat; // offset 0x38 + FEObject *mpDataHeatMultiplier; // offset 0x3C + FEObject *mpDataHeatMeterIcon; // offset 0x40 + FEObject *mpDataHeatMeterIconWarning; // offset 0x44 + FEMultiImage *mpHeatMeterBar; // offset 0x48 + FEMultiImage *mpHeatMeterBar2; // offset 0x4C +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeHudElement.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeHudElement.cpp index e69de29bb..b1716af2c 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeHudElement.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeHudElement.cpp @@ -0,0 +1,100 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" + +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/feimage.h" + +FEString *FEngFindString(const char *pkg_name, int name_hash); +FEImage *FEngFindImage(const char *pkg_name, int name_hash); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +FEObject *FEngFindGroup(const char *pkg_name, unsigned int obj_hash); +void FEngSetVisible(FEObject *obj); +void FEngSetInvisible(FEObject *obj); + +HudElement::HudElement(const char *pkg_name, unsigned long long mask) + : pPackageName(pkg_name) // + , Mask(mask) // + , CurrentHudFeatures(0) // + , mCurrentlySetVisible(false) {} + +void HudElement::Update(IPlayer *player) {} + +FEString *HudElement::RegisterString(unsigned int hash) { + FEString *string = FEngFindString(pPackageName, hash); + + if (string != nullptr) { + Objects.AddTail(string); + } + + return string; +} + +FEImage *HudElement::RegisterImage(unsigned int hash) { + FEImage *image = FEngFindImage(pPackageName, hash); + + if (image != nullptr) { + Objects.AddTail(image); + } + + return image; +} + +FEMultiImage *HudElement::RegisterMultiImage(unsigned int hash) { + FEMultiImage *image = static_cast< FEMultiImage * >(FEngFindObject(pPackageName, hash)); + + if (image != nullptr) { + Objects.AddTail(image); + } + + return image; +} + +FEObject *HudElement::RegisterObject(unsigned int hash) { + FEObject *object = FEngFindObject(pPackageName, hash); + + if (object != nullptr) { + Objects.AddTail(object); + } + + return object; +} + +FEGroup *HudElement::RegisterGroup(unsigned int hash) { + FEGroup *group = static_cast< FEGroup * >(FEngFindGroup(pPackageName, hash)); + + if (group != nullptr) { + for (FEObject *object = group->GetFirstChild(); object != nullptr; + object = static_cast< FEObject * >(object->FEMinNode::GetNext())) { + if (object->Type == FE_Group) { + RegisterGroup(object->NameHash); + } else { + Objects.AddTail(object); + } + } + } + + return group; +} + +void HudElement::Toggle(unsigned long long hud_features) { + unsigned long long mask = Mask; + int is_visible = 0; + + CurrentHudFeatures = hud_features; + + if ((hud_features & mask) != 0) { + is_visible = 1; + } + + for (bPNode *node = static_cast< bPNode * >(Objects.GetHead()); node != Objects.EndOfList(); + node = static_cast< bPNode * >(node->GetNext())) { + FEObject *object = static_cast< FEObject * >(node->GetpObject()); + + if (is_visible != 0) { + FEngSetVisible(object); + } else { + FEngSetInvisible(object); + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp index ebf10ce94..f7eba7f32 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp @@ -8,12 +8,50 @@ #include "Speed/Indep/Src/FEng/FEObject.h" #include "Speed/Indep/bWare/Inc/bList.hpp" +struct FEGroup; +struct FEImage; +struct FEMultiImage; +struct FEString; +struct Car; +class Player; +class IPlayer; + // total size: 0x28 class HudElement { public: + HudElement(const char *pkg_name, unsigned long long mask); + virtual ~HudElement() {} + virtual void Update(IPlayer *player); + + FEString *RegisterString(const char *name); + FEImage *RegisterImage(const char *name); + FEObject *RegisterObject(const char *name); + const char *GetPackageName() { return pPackageName; } + bool IsElementVisible() { return (CurrentHudFeatures & Mask) != 0; } + + Car *GetHudCar(Player *player); + + FEString *RegisterString(unsigned int hash); + FEImage *RegisterImage(unsigned int hash); + FEMultiImage *RegisterMultiImage(unsigned int hash); + FEObject *RegisterObject(unsigned int hash); + FEGroup *RegisterGroup(unsigned int hash); + + void Toggle(unsigned long long hud_features); + + protected: + void SetVerticalBarValue(FEImage *vertical_bar, float current_value, float max_value, float texture_offset_bottom, + float texture_offset_top, bool top_down); + void SetHorizontalBarValue(FEImage *bar, float current_value, float max_value, float offset_left, + float offset_right, bool left_to_right); + private: bPList Objects; // offset 0x0, size 0x8 + + protected: const char *pPackageName; // offset 0x8, size 0x4 + + private: unsigned long long Mask; // offset 0x10, size 0x8 unsigned long long CurrentHudFeatures; // offset 0x18, size 0x8 bool mCurrentlySetVisible; // offset 0x20, size 0x1 diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeInfractions.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeInfractions.cpp index e69de29bb..5846b103c 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeInfractions.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeInfractions.cpp @@ -0,0 +1,49 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeInfractions.hpp" +#include "Speed/Indep/Src/Gameplay/GInfractionManager.h" + +extern bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +extern void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +extern int FEPrintf(const char *pkg_name, FEObject *obj, const char *fmt, ...); +extern int FEPrintf(FEString *text, const char *fmt, ...); + +Infractions::Infractions(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x200000000ULL) // + , IInfractions(pOutter) +{ + RegisterGroup(FEHashUpper("INFRACTIONS_HEADER_GROUP")); + mpDataGenericIcon = RegisterObject(FEHashUpper("GENERIC_ICONS")); + mpDataTotalInfractions = RegisterString(FEHashUpper("TOTAL_INFRACTIONS_TEXT")); + for (int i = 0; i < 4; i++) { + char buf[32]; + bSPrintf(buf, "INFRACTION_GROUP_%d", i + 1); + mpDataInfractionStrings[i] = RegisterGroup(FEHashUpper(buf)); + } +} + +void Infractions::Update(IPlayer *player) { + bool infractionStringShowing = false; + for (int i = 0; i <= 3; i++) { + if (!FEngIsScriptSet(mpDataInfractionStrings[i], 0x16a259)) { + infractionStringShowing = true; + break; + } + } + if (!infractionStringShowing) { + if (FEngIsScriptSet(mpDataGenericIcon, 0x5079c8f8) || + FEngIsScriptSet(mpDataGenericIcon, 0x3826a28)) { + FEngSetScript(mpDataGenericIcon, 0x33113ac, true); + } + } +} + +void Infractions::RequestInfraction(const char *infractionString) { + for (int i = 0; i < 4; i++) { + if (FEngIsScriptSet(mpDataInfractionStrings[i], 0x16a259)) { + FEngSetScript(mpDataInfractionStrings[i], 0x5079c8f8, true); + FEPrintf(GetPackageName(), mpDataInfractionStrings[i], "%s", infractionString); + break; + } + } + FEngSetScript(mpDataGenericIcon, 0x5079c8f8, true); + FEPrintf(mpDataTotalInfractions, "%d", GInfractionManager::Get().GetNumInfractions()); +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeInfractions.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeInfractions.hpp index b9fcc3307..d2a845c20 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeInfractions.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeInfractions.hpp @@ -5,6 +5,21 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +class Infractions : public HudElement, public IInfractions { + public: + Infractions(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void RequestInfraction(const char *infractionString) override; + + private: + FEObject * mpDataGenericIcon; + FEGroup * mpDataInfractionStrings[4]; + FEString * mpDataTotalInfractions; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.cpp index e69de29bb..565b1419c 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.cpp @@ -0,0 +1,353 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FEPkg_Hud.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Gameplay/GRace.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" + +extern char *GetTranslatedString(int hash); +extern const char *GetLocalizedString(unsigned int hash); +int bSNPrintf(char *dest, int maxlen, const char *fmt, ...); +int FEngSNPrintf(char *dest, int size, const char *fmt, ...); +unsigned long FEHashUpper(const char *String); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +unsigned int bStringHash(const char *str); +int FEPrintf(FEString *str, const char *fmt, ...); +bool FEngIsScriptSet(FEObject *obj, unsigned int hash); +bool FEngIsScriptRunning(FEObject *obj, unsigned int hash); +void FEngSetScript(FEObject *obj, unsigned int hash, bool set); +void FEngSetInvisible(FEObject *obj); + +LeaderBoard::LeaderBoard(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x18) // + , ILeaderBoard(pOutter) +{ + mNumRacers = -1; + mPlayerIndex = -1; + mSplitTimeQueued = false; + mNumFramesBeforeTogglingPlayerTimes = 90; + mShowingRacerTimes = false; + mDataLeaderboardGroup = static_cast(RegisterGroup(FEHashUpper("LeaderBoardGroup"))); + + char sztemp[32]; + for (int i = 0; i < 4; i++) { + FEngSNPrintf(sztemp, 32, "LBData_%d", i + 1); + mDataRacerText[i] = static_cast(FEngFindObject(pkg_name, FEHashUpper(sztemp))); + FEngSNPrintf(sztemp, 32, "LeaderText_%d", i + 1); + mDataRacerNum[i] = static_cast(FEngFindObject(pkg_name, FEHashUpper(sztemp))); + FEngSNPrintf(sztemp, 32, "OL_ICON_%d", i + 2); + mDataRacerIcon[i] = static_cast(FEngFindObject(pkg_name, FEHashUpper(sztemp))); + FEngSNPrintf(sztemp, 32, "LBBacking_%d", i + 1); + mDataRacerTextBackings[i] = static_cast(FEngFindObject(pkg_name, FEHashUpper(sztemp))); + } + + for (int i = 0; i < 4; i++) { + mTopRacers[i].mHasHeadset = false; + mTopRacers[i].mIsBusted = false; + mTopRacers[i].mIsKoed = false; + mTopRacers[i].mNumLapsCompleted = 0; + mTopRacers[i].mPercentComplete = 0.0f; + mTopRacers[i].mRacerNum = 0; + mTopRacers[i].mRaceTimeOfLastLap = 0.0f; + mTopRacers[i].mTotalPoints = 0.0f; + bStrCpy(mTopRacers[i].mRacerName, ""); + for (int j = 0; j < 20; j++) { + mTopRacers[i].mRaceTimeOfSegment[j] = 0.0f; + } + } +} + +extern const char lbl_803E4CF0[]; +extern const char lbl_803E48C8[]; +extern const char lbl_803E4FF8[]; +extern const char lbl_803E5004[]; +extern const char lbl_803E500C[]; +extern const char lbl_803E5018[]; + +void LeaderBoard::Update(IPlayer *player) { + if (player->GetSettings()->LeaderboardOn) { + if (!FEngIsScriptSet(mDataLeaderboardGroup, 0x001744B3)) { + FEngSetScript(mDataLeaderboardGroup, 0x001744B3, true); + } + + --mNumFramesBeforeTogglingPlayerTimes; + if (mNumFramesBeforeTogglingPlayerTimes <= 0) { + bool toggleRacerTimesNow = false; + for (int i = 0; i < mNumRacers && i < 4; i++) { + if (mNumFramesBeforeTogglingPlayerTimes == 0) { + FEngSetScript(mDataRacerText[i], 0x033113AC, true); + } else { + if (FEngIsScriptSet(mDataRacerText[i], 0x033113AC) && + !FEngIsScriptRunning(mDataRacerText[i], 0x033113AC) && + !FEngIsScriptSet(mDataRacerText[i], 0x5079C8F8)) { + FEngSetScript(mDataRacerText[i], 0x5079C8F8, true); + toggleRacerTimesNow = true; + } + } + } + if (toggleRacerTimesNow) { + mShowingRacerTimes ^= 1; + mNumFramesBeforeTogglingPlayerTimes = 90; + } + } + + int numRacerNumToClearFrom = mNumRacers; + if (mNumRacers > 1) { + for (int i = 0; i < mNumRacers && i < 4; i++) { + if (mShowingRacerTimes) { + if (mTopRacers[i].mIsBusted) { + FEPrintf(mDataRacerText[i], lbl_803E4CF0, GetTranslatedString(0x532b5186)); + } else if (mTopRacers[i].mIsKoed) { + FEPrintf(mDataRacerText[i], lbl_803E4CF0, GetTranslatedString(0x5d82dba2)); + } else if (GRaceStatus::Get().GetRaceType() == GRace::kRaceType_SpeedTrap) { + float val = mTopRacers[i].mTotalPoints; + unsigned int unit = 0x8569a25f; + if (!FEDatabase->GetGameplaySettings()->SpeedoUnits) { + val = MPS2MPH(KPH2MPS(val)); + unit = 0x8569ab44; + } + FEPrintf(mDataRacerText[i], lbl_803E4FF8, val, GetLocalizedString(unit)); + } else if (i == mPlayerIndex) { + FEPrintf(mDataRacerText[i], lbl_803E5004); + } else { + int unit = 0xe2078322; + float totalRaceLenMetres = GRaceStatus::Get().GetRaceLength(); + if (!FEDatabase->GetGameplaySettings()->SpeedoUnits) { + totalRaceLenMetres = METERS2FT(totalRaceLenMetres); + unit = 0x2e8496b1; + } + if (mTopRacers[i].mPercentComplete >= mTopRacers[mPlayerIndex].mPercentComplete) { + float pctDiff = (mTopRacers[i].mPercentComplete - mTopRacers[mPlayerIndex].mPercentComplete) * 0.01f; + pctDiff = pctDiff * totalRaceLenMetres; + FEPrintf(mDataRacerText[i], lbl_803E500C, pctDiff, GetTranslatedString(unit)); + } else { + float pctDiff = (mTopRacers[mPlayerIndex].mPercentComplete - mTopRacers[i].mPercentComplete) * 0.01f; + pctDiff = pctDiff * totalRaceLenMetres; + FEPrintf(mDataRacerText[i], lbl_803E5018, pctDiff, GetTranslatedString(unit)); + } + } + } else { + FEPrintf(mDataRacerText[i], lbl_803E4CF0, mTopRacers[i].mRacerName); + FEPrintf(mDataRacerNum[i], lbl_803E48C8, mTopRacers[i].mRacerNum); + } + } + if (numRacerNumToClearFrom <= 1) { + numRacerNumToClearFrom = 0; + } + } else { + numRacerNumToClearFrom = 0; + } + + for (int i = numRacerNumToClearFrom; i <= 3; i++) { + FEngSetInvisible(mDataRacerText[i]); + FEngSetInvisible(mDataRacerNum[i]); + FEngSetInvisible(mDataRacerIcon[i]); + FEngSetInvisible(mDataRacerTextBackings[i]); + } + + for (int i = 0; i <= 3; i++) { + if (i == mPlayerIndex) { + if (!FEngIsScriptSet(mDataRacerTextBackings[i], 0x249db7b7)) { + FEngSetScript(mDataRacerTextBackings[i], 0x249db7b7, true); + } + } else { + if (!FEngIsScriptSet(mDataRacerTextBackings[i], 0x001744B3)) { + FEngSetScript(mDataRacerTextBackings[i], 0x001744B3, true); + } + } + } + } else { + if (!FEngIsScriptSet(mDataLeaderboardGroup, 0x0016A259)) { + FEngSetScript(mDataLeaderboardGroup, 0x0016A259, true); + } + } + + if (mSplitTimeQueued && ShowSplitTime(player)) { + mSplitTimeQueued = false; + } +} + +void LeaderBoard::SetNumRacers(int numRacers) { + mNumRacers = numRacers; +} + +void LeaderBoard::SetNumLaps(int numLaps) { + mNumLaps = numLaps; +} + +void LeaderBoard::SetPlayerIndex(int index) { + mPlayerIndex = index; +} + +void LeaderBoard::SetRacerNum(int pos, int num) { + if (pos > 3) return; + mTopRacers[pos].mRacerNum = num; +} + +void LeaderBoard::SetRacerTotalPoints(int pos, float points) { + if (pos > 3) return; + mTopRacers[pos].mTotalPoints = points; +} + +void LeaderBoard::SetRacerHasHeadset(int pos, bool racerHasHeadset) { + if (pos > 3) return; + mTopRacers[pos].mHasHeadset = racerHasHeadset; +} + +void LeaderBoard::SetRacerNumLapsCompleted(int pos, int numLaps, float time, IPlayer *player) { + if (pos > 3) return; + if (numLaps > 0 && numLaps < mNumLaps && + numLaps > mTopRacers[pos].mNumLapsCompleted && pos == mPlayerIndex) { + ShowLapTime(player); + } + mTopRacers[pos].mRaceTimeOfLastLap = time; + mTopRacers[pos].mNumLapsCompleted = numLaps; +} + +void LeaderBoard::SetRacerName(int pos, const char *name) { + if (pos > 3) return; + bStrCpy(mTopRacers[pos].mRacerName, name); +} + +void LeaderBoard::SetRacerIsBusted(int pos, bool busted) { + mTopRacers[pos].mIsBusted = busted; +} + +void LeaderBoard::SetRacerIsKoed(int pos, bool koed) { + mTopRacers[pos].mIsKoed = koed; +} + +void LeaderBoard::SetRacerPercentComplete(int pos, float percent, float time, IPlayer *player) { + if (pos > 3) return; + if (percent > 0.0f && percent < 100.0f) { + int ipercent = static_cast(percent * static_cast(mNumLaps)); + if (ipercent > static_cast(mTopRacers[pos].mPercentComplete * static_cast(mNumLaps))) { + bool showSplitTime = false; + int index = 0; + GRace::Type raceType = GRaceStatus::Get().GetRaceType(); + switch (raceType) { + case GRace::kRaceType_Circuit: + case GRace::kRaceType_Knockout: { + int q = ipercent / 50; + if (ipercent == q * 50) { + showSplitTime = true; + index = q; + } + break; + } + case GRace::kRaceType_P2P: + case GRace::kRaceType_Tollbooth: { + int q = ipercent / 25; + if (ipercent == q * 25) { + showSplitTime = true; + index = q; + } + break; + } + default: + break; + } + if (showSplitTime) { + if (mTopRacers[pos].mRaceTimeOfSegment[index] == 0.0f) { + mTopRacers[pos].mRaceTimeOfSegment[index] = time; + if (mPlayerIndex == 0) { + if (pos == 1) { + mSplitTimeQueued = true; + } + } else if (pos == mPlayerIndex) { + mSplitTimeQueued = true; + } + } + } + } + } + mTopRacers[pos].mPercentComplete = percent; +} + +extern const char lbl_803E4CF4[]; + +bool LeaderBoard::ShowLapTime(IPlayer *player) const { + IHud *hud = player->GetHud(); + if (!hud) return false; + IGenericMessage *igenericmessage; + if (!hud->QueryInterface(&igenericmessage)) return false; + if (igenericmessage->IsGenericMessageShowing()) return false; + + Timer timer(mTopRacers[mPlayerIndex].mRaceTimeOfLastLap); + char timeToPrint[16]; + char messageString[32]; + timer.PrintToString(timeToPrint, 4); + const char *fmt = lbl_803E4CF4; + char *translated = GetTranslatedString(0x7A6F9F0A); + bSNPrintf(messageString, 32, fmt, translated, timeToPrint); + igenericmessage->RequestGenericMessage( + messageString, false, 0x8AB83EDB, bStringHash("TIMER_ICON"), 0x609F6B15, + GenericMessage_Priority_3); + return true; +} + +extern const char lbl_803E5048[]; +extern const char lbl_803E5050[]; + +bool LeaderBoard::ShowSplitTime(IPlayer *player) { + if (player->GetSettings()->SplitTimeType == 4) { + mSplitTimeQueued = false; + return false; + } + + IGenericMessage *igenericmessage; + IHud *hud = player->GetHud(); + if (!hud) { + return false; + } + + if (!hud->QueryInterface(&igenericmessage)) { + return false; + } + + if (igenericmessage->IsGenericMessageShowing()) { + return false; + } + + Timer timer; + int index; + int divisor = 50; + if (GRaceStatus::Get().GetRaceType() == GRace::kRaceType_P2P || + GRaceStatus::Get().GetRaceType() == GRace::kRaceType_Tollbooth) { + divisor = 25; + } + + if (mPlayerIndex == 0) { + int ipercent = static_cast(mTopRacers[1].mPercentComplete * static_cast(mNumLaps)); + index = ipercent / divisor; + timer = Timer(mTopRacers[1].mRaceTimeOfSegment[index] - mTopRacers[0].mRaceTimeOfSegment[index]); + } else { + int ipercent = static_cast(mTopRacers[mPlayerIndex].mPercentComplete * static_cast(mNumLaps)); + index = ipercent / divisor; + timer = Timer(mTopRacers[mPlayerIndex].mRaceTimeOfSegment[index] - mTopRacers[0].mRaceTimeOfSegment[index]); + } + + char timeToPrint[16]; + char messageString[32]; + timer.PrintToString(timeToPrint, 4); + + int hash; + if (mPlayerIndex == 0) { + bSNPrintf(messageString, 32, lbl_803E5048, GetTranslatedString(0x7771a159), timeToPrint); + hash = 0xa19bb14c; + } else { + bSNPrintf(messageString, 32, lbl_803E5050, GetTranslatedString(0x7771a159), timeToPrint); + hash = 0x5230faf6; + } + + igenericmessage->RequestGenericMessage(messageString, false, hash, 0, 0, GenericMessage_Priority_3); + return true; +} + +HudResourceManager::HudResourceManager() { + mHudResourcesState = HRM_NOT_LOADED; + pHudTextures = nullptr; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.hpp index 470d1fa63..ecee8f95a 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeLeaderBoard.hpp @@ -5,6 +5,56 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/FEng/FEImage.h" +struct LeaderBoardRacerData { + char mRacerName[16]; + float mPercentComplete; + float mRaceTimeOfSegment[20]; + float mRaceTimeOfLastLap; + int mRacerNum; + float mTotalPoints; + int mNumLapsCompleted; + bool mHasHeadset; + bool mIsBusted; + bool mIsKoed; +}; + +class LeaderBoard : public HudElement, public ILeaderBoard { + public: + LeaderBoard(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetNumRacers(int numRacers) override; + void SetNumLaps(int numLaps) override; + void SetPlayerIndex(int index) override; + void SetRacerIsBusted(int pos, bool busted) override; + void SetRacerIsKoed(int pos, bool koed) override; + void SetRacerName(int pos, const char *name) override; + void SetRacerNum(int pos, int num) override; + void SetRacerTotalPoints(int pos, float points) override; + void SetRacerNumLapsCompleted(int pos, int numLaps, float time, IPlayer *player) override; + void SetRacerPercentComplete(int pos, float percent, float time, IPlayer *player) override; + void SetRacerHasHeadset(int pos, bool racerHasHeadset) override; + bool ShowLapTime(IPlayer *player) const; + bool ShowSplitTime(IPlayer *player); + + private: + int mNumRacers; // offset 0x30 + int mNumLaps; // offset 0x34 + int mPlayerIndex; // offset 0x38 + bool mSplitTimeQueued; // offset 0x3C + LeaderBoardRacerData mTopRacers[4]; // offset 0x40 + int mNumFramesBeforeTogglingPlayerTimes; // offset 0x240 + int mShowingRacerTimes; // offset 0x244 + FEGroup *mDataLeaderboardGroup; // offset 0x248 + FEString *mDataRacerText[4]; // offset 0x24C + FEString *mDataRacerNum[4]; // offset 0x25C + FEImage *mDataRacerIcon[4]; // offset 0x26C + FEImage *mDataRacerTextBackings[4]; // offset 0x27C +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.cpp index e69de29bb..13d32996f 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.cpp @@ -0,0 +1,188 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Gameplay/GActivity.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Generated/Events/EQuitToFE.hpp" +#include "Speed/Indep/Src/Generated/Events/ERequestEventInfoDialog.hpp" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOn.hpp" +#include "Speed/Indep/Src/Generated/Messages/MEnterSafeHouse.h" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +extern cFrontendDatabase *FEDatabase; + +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +void FEngSetScript(FEObject *obj, unsigned int script_hash, bool play); +void FEngSetTextureHash(FEImage *img, unsigned int hash); +void BeginCarCustomize(eCustomizeEntryPoint entry, FECarRecord *record); + +MenuZoneTrigger::MenuZoneTrigger(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x400000) // + , IMenuZoneTrigger(pOutter) +{ + mCingularTimer = 0; + mbInsideTrigger = false; + mbCingularQueued = false; + mpRaceActivity = nullptr; + mZoneType = nullptr; + mEngageMechanic = RegisterGroup(FEHashUpper("Engage_Mechanic")); + mEventIcon = RegisterImage(FEHashUpper("EventIcon")); + mCingularIcon = RegisterGroup(0xDA8141D4); +} + +void MenuZoneTrigger::Update(IPlayer *player) { + if (mbCingularQueued) { + return; + } + if (mCingularTimer.IsSet()) { + Timer diff = WorldTimer - mCingularTimer; + if (diff.GetSeconds() >= 6.0f) { + mCingularTimer.UnSet(); + if (!mbInsideTrigger) { + HideDPadButton(); + } else { + PulseDPadButton(ENGAGE_DPAD_ELEMENT_UP, mEventIcon); + } + } + } +} + +bool MenuZoneTrigger::IsPlayerInsideTrigger() { + return FEngIsScriptSet(mEventIcon, 0x280164f); +} + +void MenuZoneTrigger::ExitTrigger() { + mpRaceActivity = nullptr; + mbInsideTrigger = false; + mZoneType = nullptr; + HideDPadButton(); +} + +void MenuZoneTrigger::EnterTrigger(GRuntimeInstance *pRaceActivity) { + mpRaceActivity = pRaceActivity; + mbInsideTrigger = true; + PulseDPadButton(ENGAGE_DPAD_ELEMENT_UP, mEventIcon); + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromActivity(static_cast(mpRaceActivity)); + FEngSetTextureHash(mEventIcon, FEDatabase->GetRaceIconHash(parms->GetRaceType())); +} + +void MenuZoneTrigger::EnterTrigger(const char *zoneType) { + mZoneType = zoneType; + mbInsideTrigger = true; + PulseDPadButton(ENGAGE_DPAD_ELEMENT_UP, mEventIcon); + FEngSetTextureHash(mEventIcon, FEDatabase->GetSafehouseIconHash(zoneType)); +} + +void MenuZoneTrigger::RequestEventInfoDialog(int port) { + if (mpRaceActivity) { + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromActivity(static_cast(mpRaceActivity)); + if (!parms || !parms->GetIsBossRace()) { + new ERequestEventInfoDialog(port, mpRaceActivity); + } else { + new ERaceSheetOn(3); + } + } +} + +void MenuZoneTrigger::RequestZoneInfoDialog(int port) { + if (bStrCmp(mZoneType, "safehouse") == 0 || + bStrCmp(mZoneType, "carlot") == 0 || + bStrCmp(mZoneType, "customshop") == 0) { + MEnterSafeHouse msg(mZoneType); + msg.Post(0x20D60DBF); + } +} + +void MenuZoneTrigger::RequestDoAction() { + if (bStrCmp(mZoneType, "safehouse") == 0) { + new EQuitToFE(static_cast(2), "MainMenu_Sub.fng"); + } else if (bStrCmp(mZoneType, "carlot") == 0) { + new EQuitToFE(static_cast(5), "Car_Select.fng"); + } else if (bStrCmp(mZoneType, "customshop") == 0) { + FECarRecord *rec = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(FEDatabase->GetCareerSettings()->GetCurrentCar()); + if (rec->IsCustomized()) { + BeginCarCustomize(static_cast(0), rec); + new EQuitToFE(static_cast(3), "CustomizeMain.fng"); + } + } +} + +void MenuZoneTrigger::HideDPadButton() { + FEObject *objectPtr; + if (!FEngIsScriptSet(mEventIcon, 0x33113AC) && !FEngIsScriptSet(mEventIcon, 0x16A259)) { + FEngSetScript(mEventIcon, 0x33113AC, true); + } + if (!FEngIsScriptSet(mCingularIcon, 0x33113AC)) { + if (!FEngIsScriptSet(mCingularIcon, 0x16A259)) { + FEngSetScript(mCingularIcon, 0x33113AC, true); + } + } + objectPtr = FEngFindObject(GetPackageName(), 0xA729B1B); + if (objectPtr) { + if (!FEngIsScriptSet(objectPtr, 0x33113AC) && !FEngIsScriptSet(objectPtr, 0x1744B3)) { + FEngSetScript(objectPtr, 0x33113AC, true); + } + } + objectPtr = FEngFindObject(GetPackageName(), 0x717C82AE); + if (objectPtr) { + if (!FEngIsScriptSet(objectPtr, 0x33113AC) && !FEngIsScriptSet(objectPtr, 0x1744B3)) { + FEngSetScript(objectPtr, 0x33113AC, true); + } + } + objectPtr = FEngFindObject(GetPackageName(), 0xA206A0B4); + if (objectPtr) { + if (!FEngIsScriptSet(objectPtr, 0x33113AC) && !FEngIsScriptSet(objectPtr, 0x1744B3)) { + FEngSetScript(objectPtr, 0x33113AC, true); + } + } + objectPtr = FEngFindObject(GetPackageName(), 0x7180B901); + if (objectPtr) { + if (!FEngIsScriptSet(objectPtr, 0x33113AC) && !FEngIsScriptSet(objectPtr, 0x1744B3)) { + FEngSetScript(objectPtr, 0x33113AC, true); + } + } + if (FEngIsScriptSet(mEngageMechanic, 0x5079C8F8)) { + FEngSetScript(mEngageMechanic, 0x33113AC, true); + } +} + +void MenuZoneTrigger::PulseDPadButton(ENGAGE_DPAD_ELEMENT_DIRECTION direction, FEObject *iconToShow) { + HideDPadButton(); + if (iconToShow && !FEngIsScriptSet(iconToShow, 0x5079C8F8) && !FEngIsScriptSet(iconToShow, 0x280164F)) { + FEngSetScript(iconToShow, 0x5079C8F8, true); + } + if (direction == ENGAGE_DPAD_ELEMENT_NONE) { + if (!FEngIsScriptSet(mEngageMechanic, 0x5079C8F8)) { + FEngSetScript(mEngageMechanic, 0x5079C8F8, true); + g_pEAXSound->PlayUISoundFX(static_cast(0x13)); + } + } else { + unsigned int hash = 0; + if (direction == ENGAGE_DPAD_ELEMENT_DOWN) { + hash = 0x717C82AE; + } else if (direction == ENGAGE_DPAD_ELEMENT_UP) { + hash = 0xA729B1B; + } + if (hash) { + FEObject *btn = FEngFindObject(pPackageName, hash); + if (btn) { + if (!FEngIsScriptSet(btn, 0x5079C8F8) && !FEngIsScriptSet(btn, 0x280164F)) { + FEngSetScript(btn, 0x5079C8F8, true); + g_pEAXSound->PlayUISoundFX(static_cast(0x13)); + } + } + } + } +} + +bool MenuZoneTrigger::IsType(const char *t) { + return bStrCmp(mZoneType, t) == 0; +} + +bool MenuZoneTrigger::ShouldSeeMenuZoneCluster() { + return *reinterpret_cast(reinterpret_cast(&GRaceStatus::Get()) + 0x1AA4) == 0; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp index e24bb9430..c648900db 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeMenuZoneTrigger.hpp @@ -5,6 +5,50 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +struct GRuntimeInstance; + +class MenuZoneTrigger : public HudElement, public IMenuZoneTrigger { + public: + enum ENGAGE_DPAD_ELEMENT_DIRECTION { + ENGAGE_DPAD_ELEMENT_NONE = 0, + ENGAGE_DPAD_ELEMENT_UP = 1, + ENGAGE_DPAD_ELEMENT_DOWN = 2, + ENGAGE_DPAD_ELEMENT_LEFT = 3, + ENGAGE_DPAD_ELEMENT_RIGHT = 4, + ENGAGE_DPAD_ELEMENT_NUM = 5, + }; + + MenuZoneTrigger(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void RequestCingularLogo() override { + mbCingularQueued = true; + } + bool ShouldSeeMenuZoneCluster() override; + bool IsPlayerInsideTrigger() override; + void EnterTrigger(GRuntimeInstance *pRaceActivity) override; + void EnterTrigger(const char *zoneType) override; + void ExitTrigger() override; + void RequestEventInfoDialog(int port) override; + void RequestZoneInfoDialog(int port) override; + bool IsType(const char *t) override; + void RequestDoAction() override; + void HideDPadButton(); + void PulseDPadButton(ENGAGE_DPAD_ELEMENT_DIRECTION direction, FEObject *iconToShow); + + private: + FEGroup * mEngageMechanic; + FEImage * mEventIcon; + FEGroup * mCingularIcon; + const char * mZoneType; + GRuntimeInstance * mpRaceActivity; + bool mbCingularQueued; + bool mbInsideTrigger; + Timer mCingularTimer; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.cpp index e69de29bb..8e5764374 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.cpp @@ -0,0 +1,254 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Src/FEng/feimage.h" + +void FEngSetScript(FEObject *obj, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +bool FEngIsScriptRunning(FEObject *obj, unsigned int script_hash); +void FEngSetTextureHash(FEImage *image, unsigned int hash); +FEColor FEngGetObjectColor(FEObject *obj); +void FEngSetColor(FEObject *obj, unsigned int color); +int FEPrintf(FEString *str, const char *fmt, ...); + +inline unsigned int FEngGetColor(FEObject *obj) { + return FEngGetObjectColor(obj); +} + +MilestoneBoard::MilestoneBoard(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x400000000ULL) // + , IMilestoneBoard(pOutter) +{ + mInPursuit = false; + mChallengeSeries = false; + mPlayerBinNumber = FEDatabase->GetCareerSettings()->GetCurrentBin(); + mScrollTimer = Timer(0); + mNumMilestones = 0; + mMilestoneSetVisible = 0; + + { + MilestoneBoard_Milestone *p = mMilestones; + int m = 3; + do { + p->mMilestoneIconHash = 0; + p->mType = 0; + p->mGoal = 0.0f; + p->mCurrVal = 0.0f; + p->mHeaderHash = 0; + p->mComplete = false; + p++; + } while (m--); + } + + mpDataMilestoneInfoGroup = RegisterGroup(FEHashUpper("MILESTONE_INFO_GROUP")); + mpDataMilestoneIconGroup = RegisterGroup(FEHashUpper("MILESTONE_ICON_GROUP")); + mpDataMilestonesTotal = FEngFindString(GetPackageName(), 0x894662c5); + + for (int i = 0; i < 4; i++) { + char buf[32]; + bSPrintf(buf, "MILSTONE_ICON_%d", i + 1); + mpDataIcons[i] = FEngFindObject(GetPackageName(), FEHashUpper(buf)); + bSPrintf(buf, "MILESTONE_ICON_BACKING_%d", i + 1); + mpDataIconBackings[i] = FEngFindObject(GetPackageName(), FEHashUpper(buf)); + } + + mpDataDetailsBacking = FEngFindObject(GetPackageName(), 0x5c697702); + mpDataDetailsGroup = FEngFindObject(GetPackageName(), 0xf4405ec0); + mpDataMilestoneGoal = FEngFindString(GetPackageName(), 0xc3e48fbf); +} + +void MilestoneBoard::Update(IPlayer *player) { + int numIncompleteMilestones; + + if (mInPursuit) { + if (mNumMilestones >= 1) { + numIncompleteMilestones = GetNumIncompleteMilestones(); + if (numIncompleteMilestones > 0) { + if (FEngIsScriptSet(mpDataDetailsBacking, 0x16a259)) { + FEngSetScript(mpDataDetailsBacking, 0x1ca7c0, true); + } + if (FEngIsScriptSet(mpDataDetailsGroup, 0x16a259)) { + FEngSetScript(mpDataDetailsGroup, 0x1ca7c0, true); + } + } else { + if (!FEngIsScriptSet(mpDataDetailsBacking, 0x16a259)) { + FEngSetScript(mpDataDetailsBacking, 0x16a259, true); + } + if (!FEngIsScriptSet(mpDataDetailsGroup, 0x16a259)) { + FEngSetScript(mpDataDetailsGroup, 0x16a259, true); + } + } + + if (!FEngIsScriptSet(mpDataMilestoneInfoGroup, 0x5079c8f8)) { + FEngSetScript(mpDataMilestoneInfoGroup, 0x5079c8f8, true); + } + if (!FEngIsScriptSet(mpDataMilestoneIconGroup, 0x5079c8f8)) { + FEngSetScript(mpDataMilestoneIconGroup, 0x5079c8f8, true); + } + + FEPrintf(mpDataMilestonesTotal, "%d", GetNumCompleteMilestones()); + + if (numIncompleteMilestones > 1) { + if (!mScrollTimer.IsSet()) { + mScrollTimer = WorldTimer; + mMilestoneSetVisible = GetFirstIncompleteMilestone(); + } else { + Timer elapsed = WorldTimer - mScrollTimer; + if (elapsed.GetSeconds() >= 5.0f) { + if (FEngIsScriptSet(mpDataDetailsGroup, 0x1ca7c0)) { + FEngSetScript(mpDataDetailsGroup, 0xaff37f61, true); + } else if (FEngIsScriptSet(mpDataDetailsGroup, 0xaff37f61) && + !FEngIsScriptRunning(mpDataDetailsGroup, 0xaff37f61)) { + FEngSetScript(mpDataDetailsGroup, 0xd6c950a0, true); + mScrollTimer = WorldTimer; + mMilestoneSetVisible = GetNextVisibleMilestone(); + } + } + } + } else if (numIncompleteMilestones == 1) { + mMilestoneSetVisible = GetFirstIncompleteMilestone(); + } else { + mMilestoneSetVisible = -1; + } + + for (int i = 0; i < 4; i++) { + if (i < mNumMilestones) { + if (i == mMilestoneSetVisible) { + if (!FEngIsScriptSet(mpDataIconBackings[i], 0x249db7b7) && + !FEngIsScriptRunning(mpDataIconBackings[i], 0x3826a28)) { + FEngSetScript(mpDataIconBackings[i], 0x249db7b7, true); + } + } else { + if (!FEngIsScriptSet(mpDataIconBackings[i], 0x1744b3)) { + FEngSetScript(mpDataIconBackings[i], 0x1744b3, true); + } + } + } else { + if (!FEngIsScriptSet(mpDataIconBackings[i], 0x1744b3)) { + FEngSetScript(mpDataIconBackings[i], 0x1744b3, true); + } + } + } + + for (int i = 0; i < 4; i++) { + if (i < mNumMilestones) { + if (!FEngIsScriptSet(mpDataIcons[i], 0x1ca7c0)) { + FEngSetScript(mpDataIcons[i], 0x1ca7c0, true); + } + FEngSetTextureHash(static_cast(mpDataIcons[i]), mMilestones[i].mMilestoneIconHash); + float alpha = 0.5f; + if (GetIsMilestoneComplete(i)) { + alpha = 1.0f; + } + unsigned int colour = FEngGetColor(mpDataIcons[i]); + FEngSetColor(mpDataIcons[i], (colour & 0x00FFFFFF) | (static_cast(alpha * 255.0f) << 24)); + } else { + if (!FEngIsScriptSet(mpDataIcons[i], 0x16a259)) { + FEngSetScript(mpDataIcons[i], 0x16a259, true); + } + } + } + + if (mMilestoneSetVisible >= 0) { + char outputStr[32]; + int idx = mMilestoneSetVisible; + FEDatabase->SetMilestoneDescriptionString(outputStr, mMilestones[idx].mType, + mMilestones[idx].mCurrVal, mMilestones[idx].mGoal, true); + FEPrintf(mpDataMilestoneGoal, "%s", outputStr); + } + return; + } + } + mScrollTimer.UnSet(); + if (FEngIsScriptSet(mpDataMilestoneInfoGroup, 0x5079c8f8)) { + FEngSetScript(mpDataMilestoneInfoGroup, 0x33113ac, true); + } + if (FEngIsScriptSet(mpDataMilestoneIconGroup, 0x5079c8f8)) { + FEngSetScript(mpDataMilestoneIconGroup, 0x33113ac, true); + } +} + +void MilestoneBoard::SetInPursuit(bool inPursuit) { + mInPursuit = inPursuit; +} + +void MilestoneBoard::SetChallengeSeries(bool challenge) { + mChallengeSeries = challenge; +} + +void MilestoneBoard::SetNumberOfMilestones(int num) { + mNumMilestones = num; +} + +void MilestoneBoard::SetMilestoneComplete(int milestoneNum, bool complete) { + mMilestones[milestoneNum].mComplete = complete; +} + +bool MilestoneBoard::GetIsMilestoneComplete(int index) const { + if (index >= mNumMilestones) return true; + return mMilestones[index].mComplete; +} + +int MilestoneBoard::GetNumIncompleteMilestones() const { + int count = 0; + for (int i = 0; i < mNumMilestones; i++) { + if (!mMilestones[i].mComplete) { + count++; + } + } + return count; +} + +int MilestoneBoard::GetNumCompleteMilestones() const { + int count = 0; + for (int i = 0; i < mNumMilestones; i++) { + if (mMilestones[i].mComplete) { + count++; + } + } + return count; +} + +int MilestoneBoard::GetFirstIncompleteMilestone() const { + for (int i = 0; i < mNumMilestones; i++) { + if (!mMilestones[i].mComplete) { + return i; + } + } + return 0; +} + +int MilestoneBoard::GetNextVisibleMilestone() const { + if (GetNumIncompleteMilestones() > 1) { + int next = mMilestoneSetVisible + 1; + if (next >= mNumMilestones) { + next = 0; + } + while (mMilestones[next].mComplete) { + next++; + if (next >= mNumMilestones) { + next = 0; + } + } + return next; + } + return mMilestoneSetVisible; +} + +void MilestoneBoard::SetMilestoneCurrValue(int milestoneNum, float currVal) { + if (currVal < 0.0f) { + currVal = 0.0f; + } + if (currVal != mMilestones[milestoneNum].mCurrVal) { + mMilestones[milestoneNum].mCurrVal = currVal; + if (!mMilestones[milestoneNum].mComplete) { + if (!FEDatabase->IsMilestoneTimeFormat(mMilestones[milestoneNum].mType)) { + mMilestoneSetVisible = milestoneNum; + FEngSetScript(mpDataIconBackings[milestoneNum], 0x3826a28, true); + FEngSetScript(mpDataDetailsBacking, 0x3826a28, true); + FEngSetScript(mpDataDetailsGroup, 0xD6C950A0, true); + mScrollTimer = WorldTimer; + } + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.hpp index 1a3093769..9084b445d 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeMilestoneBoard.hpp @@ -5,6 +5,63 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +struct MilestoneBoard_Milestone { + unsigned int mMilestoneIconHash; + unsigned int mType; + float mGoal; + float mCurrVal; + int mHeaderHash; + bool mComplete; +}; + +class MilestoneBoard : public HudElement, public IMilestoneBoard { + public: + MilestoneBoard(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetInPursuit(bool inPursuit) override; + void SetChallengeSeries(bool challenge) override; + void SetNumberOfMilestones(int num) override; + void SetMilestoneIconHash(int milestoneNum, int hash) override { + mMilestones[milestoneNum].mMilestoneIconHash = hash; + } + void SetMilestoneType(int milestoneNum, unsigned int type) override { + mMilestones[milestoneNum].mType = type; + } + void SetMilestoneGoal(int milestoneNum, float goal) override { + mMilestones[milestoneNum].mGoal = goal; + } + void SetMilestoneHeaderHash(int milestoneNum, int hash) override { + mMilestones[milestoneNum].mHeaderHash = hash; + } + void SetMilestoneComplete(int milestoneNum, bool complete) override; + void SetMilestoneCurrValue(int milestoneNum, float currVal) override; + bool GetIsMilestoneComplete(int index) const; + int GetNumIncompleteMilestones() const; + int GetNumCompleteMilestones() const; + int GetNextVisibleMilestone() const; + int GetFirstIncompleteMilestone() const; + + private: + bool mInPursuit; + bool mChallengeSeries; + int mPlayerBinNumber; + int mNumMilestones; + int mMilestoneSetVisible; + Timer mScrollTimer; + MilestoneBoard_Milestone mMilestones[4]; + FEObject * mpDataMilestoneInfoGroup; + FEObject * mpDataMilestoneIconGroup; + FEString * mpDataMilestonesTotal; + FEObject * mpDataIcons[4]; + FEObject * mpDataIconBackings[4]; + FEObject * mpDataDetailsBacking; + FEObject * mpDataDetailsGroup; + FEString * mpDataMilestoneGoal; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeMinimap.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeMinimap.cpp index e69de29bb..77b6b631b 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeMinimap.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeMinimap.cpp @@ -0,0 +1,635 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeMinimap.hpp" +#include "Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/Src/Gameplay/GIcon.h" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Src/Sim/Simulation.h" +#include "Speed/Indep/Src/World/TrackInfo.hpp" +#include "Speed/Indep/Src/World/OnlineManager.hpp" +#include "Speed/Indep/Src/World/RaceParameters.hpp" + +#include "Speed/Indep/Src/AI/AITarget.h" +#include "Speed/Indep/Src/Interfaces/Simables/ICollisionBody.h" +#include "Speed/Indep/Src/Misc/MD5.hpp" +#include "Speed/Indep/Src/Physics/Common/VehicleSystem.h" +#include "Speed/Indep/Src/Physics/PhysicsTunings.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +extern void FEngSetRotationZ(FEObject *obj, float rot); +extern void FEngSetVisible(FEObject *obj); +extern void FEngSetInvisible(FEObject *obj); +extern void FEngSetCenter(FEObject *obj, float x, float y); +extern void FEngSetColor(FEObject *obj, unsigned int color); +extern FEColor FEngGetObjectColor(FEObject *obj); +extern bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +extern void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +extern unsigned long FEHashUpper(const char *str); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +extern float MinimapPivotX; +extern float MinimapPivotY; +extern float MinimapDispX; +extern float MinimapMaxSpeed; +extern bool MinimapShowNonPursuitCops; +extern bool MinimapShowPursuitCops; +extern RaceParameters TheRaceParameters; + +void GetVehicleVectors(bVector2 *pos, bVector2 *dir, ISimable *isimable) { + UMath::Vector3 position = isimable->GetPosition(); + pos->y = -position.x; + pos->x = position.z; + ICollisionBody *irigidbody; + if (isimable->QueryInterface(&irigidbody)) { + UMath::Vector3 forwardVec = irigidbody->GetForwardVector(); + dir->y = -forwardVec.x; + dir->x = forwardVec.z; + } +} + +int LoaderMiniMap(bChunk *chunk) { + return gChoppedMiniMapManager->Loader(chunk); +} + +int UnloaderMiniMap(bChunk *chunk) { + return gChoppedMiniMapManager->Unloader(chunk); +} + +static bChunkLoader bChunkLoaderMiniMap(0x3A100, LoaderMiniMap, UnloaderMiniMap); + +extern unsigned int FEngHashString(const char *, ...); +extern void FEngGetCenter(FEObject *obj, float &x, float &y); +extern char *bStrStr(const char *, const char *); + +bool GIcon::IsFlagSet(unsigned int mask) const { + return (mFlags & mask) != 0; +} + +void Physics::Tunings::Default() { + bMemSet(this, 0, 0x1C); +} + +void MD5::Reset() { + uCount = 0; + uRegs[0] = 0x67452301; + uRegs[1] = 0xEFCDAB89; + uRegs[2] = 0x98BADCFE; + uRegs[3] = 0x10325476; + computed = false; +} + +namespace UTL { +namespace Collections { + +template <> +IPlayer *ListableSet::Last(ePlayerList idx) { + ListableSet::List &l = _mLists._buckets[idx]; + if (l.size() != 0) { + return l[l.size() - 1]; + } + return nullptr; +} + +} // namespace Collections +} // namespace UTL + +Minimap::Minimap(const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x40010000) +{ + for (int i = 3; i >= 0; i--) { + for (int j = 1; j >= 0; j--) { + TrackmapArtUVs[i][j].y = 0.0f; + TrackmapArtUVs[i][j].x = 0.0f; + } + } + + mCopFlashCounter = -1; + mMapDefaultPos.z = 0.0f; + mMapDefaultPos.y = 0.0f; + mMapDefaultPos.x = 0.0f; + mSpeedZoomScale = 0.0f; + mPolyRotation = 0.0f; + MinimapPivotX = 0.0f; + mTrackMapCentre.y = 0.0f; + mTrackTargetNormalized.x = 0.0f; + mTrackTargetNormalized.y = 0.0f; + mTrackMapCentre.x = 0.0f; + + for (unsigned int i = 0; i < 4; i++) { + TrackmapArt[i] = static_cast(RegisterMultiImage(FEngHashString("TRACK_MAP%d", i + 1))); + if (TrackmapArt[i]) { + TrackmapArt[i]->GetUVs(0, TrackmapArtUVs[i][0], TrackmapArtUVs[i][1]); + } + } + + TrackmapLayout = RegisterObject(FEngHashString("TRACK_MAP")); + TrackmapNorth = RegisterImage(FEngHashString("MINIMAP_NORTH_INDICATOR")); + mPlayerCarIndicator = RegisterImage(FEngHashString("PLAYERCARINDICATOR")); + mPlayerCarIndicator2 = RegisterImage(FEngHashString("PLAYERCARINDICATOR2")); + RegisterObject(FEngHashString("TRACKMAPTARGETRING")); + RegisterObject(FEngHashString("MAP_COLOR_TINT")); + + bMemSet(mGameplayIcons, 0, sizeof(mGameplayIcons)); + + mHeliElementArt = RegisterGroup(FEngHashString("HELICOPTER_ICON_GROUP")); + mHeliLineOfSiteArt = RegisterImage(FEngHashString("HELICOPTER_LINE_OF_SIGHT")); + + for (unsigned int i = 0; i < 8; i++) { + mCopElementArt[i] = RegisterImage(FEngHashString("MMICON_COPCAR_%d", i)); + mRacerElementArt[i] = RegisterImage(FEngHashString("MMICON_AIRACER_%d", i)); + for (int onType = 0; onType < 17; onType++) { + if (kGameplayIconInfo[onType].mItemType != 0) { + if (i == 0 || bStrStr(kGameplayIconInfo[onType].mElementString, "%d")) { + mGameplayIcons[onType][i] = RegisterImage(FEngHashString(kGameplayIconInfo[onType].mElementString, i)); + if (mGameplayIcons[onType][i]) { + FEngSetInvisible(mGameplayIcons[onType][i]); + } + } else { + mGameplayIcons[onType][i] = nullptr; + } + } + } + } + + mCheckpointElementArt = RegisterImage(FEngHashString("MMICON_CHECKPOINT")); + mGPSSelectionElementArt = RegisterImage(0xE8741681); + + if (TrackmapLayout) { + mMapDefaultPos = TrackmapLayout->GetObjData()->Pos; + float x, y; + FEngGetCenter(TrackmapLayout, x, y); + mTrackMapCentre.x = x; + mTrackMapCentre.y = y; + } + + InitStaticMiniMapItems(); +} + +Minimap::~Minimap() { + gChoppedMiniMapManager->UncompressMaps(nullptr, 0); +} + +void Minimap::Update(IPlayer *player) { + if (!IsElementVisible() || !player) { + return; + } + + ISimable *isimable = player->GetSimable(); + if (!isimable) { + return; + } + + MinimapRotateWithPlayer = 1; + if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + MinimapRotateWithPlayer = 0; + } else { + unsigned char rotate_with_player = + GRaceStatus::Get().GetRaceParameters() == nullptr ? FEDatabase->GetGameplaySettings()->ExploringMiniMapMode + : FEDatabase->GetGameplaySettings()->RacingMiniMapMode; + if (!rotate_with_player) { + MinimapRotateWithPlayer = 0; + } + } + + SetupMinimap(player); + + IVehicle *ivehicle = nullptr; + float speed = 0.0f; + bVector2 target_pos; + bVector2 target_dir; + bVector2 *target_pos_to_use = &target_pos; + bVector2 *target_dir_to_use = &target_dir; + isimable = player->GetSimable(); + GetVehicleVectors(target_pos_to_use, target_dir_to_use, isimable); + + if (isimable->QueryInterface(&ivehicle)) { + speed = bAbs(ivehicle->GetSpeed()); + } + + mPolyRotation = bAngToDeg(bATan(target_dir_to_use->y, target_dir_to_use->x)); + ConvertPos(*target_pos_to_use, mTrackTargetNormalized, CurrentTrack); + + if (speed > MinimapMaxSpeed) { + speed = MinimapMaxSpeed; + } else if (speed < 0.0f) { + speed = 0.0f; + } + + mSpeedZoomScale = 2.0f - speed / MinimapMaxSpeed; + if (mSpeedZoomScale < 1.0f) { + mSpeedZoomScale = 1.0f; + } + + UpdateTrackMapArt(); + if (!MinimapRotateWithPlayer) { + mPolyRotation = 0.0f; + } + + UpdateCopElements(ivehicle); + UpdateAiRacerElements(); + UpdatePlayer2Element(); + UpdateRaceElements(); + UpdateGameplayIcons(player); +} + +void Minimap::SetupMinimap(IPlayer *player) { + const int num_chops = 8; + char texture_name[128]; + bVector2 map_pos; + bVector2 target_pos; + bVector2 target_dir; + + CurrentTrack = TrackInfo::GetTrackInfo(TheRaceParameters.TrackNumber); + short chop_nums[4] = {0}; + + ISimable *isimable = player->GetSimable(); + GetVehicleVectors(&target_pos, &target_dir, isimable); + ConvertPos(target_pos, map_pos, CurrentTrack); + + map_pos.x *= static_cast(num_chops); + map_pos.y *= static_cast(num_chops); + + int XSection = static_cast(map_pos.x); + int YSection = static_cast(map_pos.y); + float XSection_decimal = map_pos.x - static_cast(XSection); + float YSection_decimal = map_pos.y - static_cast(YSection); + + if (XSection_decimal < 0.5f) { + if (YSection_decimal < 0.5f) { + chop_nums[0] = (YSection - 1) * 8 + XSection - 1; + chop_nums[1] = (YSection - 1) * 8 + XSection; + chop_nums[2] = YSection * 8 + XSection - 1; + chop_nums[3] = YSection * 8 + XSection; + } else { + chop_nums[0] = YSection * 8 + XSection - 1; + chop_nums[1] = YSection * 8 + XSection; + chop_nums[2] = (YSection + 1) * 8 + XSection - 1; + chop_nums[3] = (YSection + 1) * 8 + XSection; + YSection_decimal -= 1.0f; + } + } else { + if (YSection_decimal < 0.5f) { + chop_nums[0] = (YSection - 1) * 8 + XSection; + chop_nums[1] = (YSection - 1) * 8 + XSection + 1; + chop_nums[2] = YSection * 8 + XSection; + chop_nums[3] = YSection * 8 + XSection + 1; + XSection_decimal -= 1.0f; + } else { + chop_nums[0] = YSection * 8 + XSection; + chop_nums[1] = YSection * 8 + XSection + 1; + chop_nums[2] = (YSection + 1) * 8 + XSection; + chop_nums[3] = (YSection + 1) * 8 + XSection + 1; + YSection_decimal -= 1.0f; + XSection_decimal -= 1.0f; + } + } + + gChoppedMiniMapManager->UncompressMaps(chop_nums, 4); + + for (unsigned int i = 0; i < 4; i++) { + gChoppedMiniMapManager->GetTextureName(texture_name, 0x80, chop_nums[i]); + unsigned int hash = FEngHashString(texture_name); + FEngSetTextureHash(TrackmapArt[i], hash); + } + + float SectionSize = mSpeedZoomScale; + float uvScale = SectionSize - 1.0f; + + TrackmapArt[0]->SetTopLeft(FEVector2(uvScale, uvScale), false); + TrackmapArt[0]->SetBottomRight(FEVector2(1.0f, 1.0f), false); + + TrackmapArt[1]->SetTopLeft(FEVector2(0.0f, uvScale), false); + TrackmapArt[1]->SetBottomRight(FEVector2(1.0f - uvScale, 1.0f), false); + + TrackmapArt[2]->SetTopLeft(FEVector2(uvScale, 0.0f), false); + TrackmapArt[2]->SetBottomRight(FEVector2(1.0f, 1.0f - uvScale), false); + + TrackmapArt[3]->SetTopLeft(FEVector2(0.0f, 0.0f), false); + TrackmapArt[3]->SetBottomRight(FEVector2(1.0f - uvScale, 1.0f - uvScale), false); + + float xDisp = -(XSection_decimal * SectionSize); + float yDisp = -(YSection_decimal * SectionSize); + + TrackmapArt[0]->SetUVs( + 0, FEVector2(TrackmapArtUVs[0][0].x + xDisp, TrackmapArtUVs[0][0].y + yDisp), + FEVector2(TrackmapArtUVs[0][1].x + xDisp, TrackmapArtUVs[0][1].y + yDisp)); + + TrackmapArt[1]->SetUVs( + 0, FEVector2(TrackmapArtUVs[1][0].x + xDisp, TrackmapArtUVs[1][0].y + yDisp), + FEVector2(TrackmapArtUVs[1][1].x + xDisp, TrackmapArtUVs[1][1].y + yDisp)); + + TrackmapArt[2]->SetUVs( + 0, FEVector2(TrackmapArtUVs[2][0].x + xDisp, TrackmapArtUVs[2][0].y + yDisp), + FEVector2(TrackmapArtUVs[2][1].x + xDisp, TrackmapArtUVs[2][1].y + yDisp)); + + TrackmapArt[3]->SetUVs( + 0, FEVector2(TrackmapArtUVs[3][0].x + xDisp, TrackmapArtUVs[3][0].y + yDisp), + FEVector2(TrackmapArtUVs[3][1].x + xDisp, TrackmapArtUVs[3][1].y + yDisp)); + + FEObjData *data = TrackmapLayout->GetObjData(); + xDisp *= -128.0f; + yDisp *= -128.0f; + data->Pos.x = mMapDefaultPos.x - xDisp; + data->Pos.y = mMapDefaultPos.y - yDisp; + data->Pos.z = mMapDefaultPos.z; + data = TrackmapLayout->GetObjData(); + data->Pivot.x = xDisp + MinimapPivotX; + data->Pivot.y = yDisp + MinimapPivotY; + data->Pivot.z = 0.0f; +} + +void Minimap::ConvertPos(bVector2 &worldPos, bVector2 &minimapPos, TrackInfo *track) { + minimapPos.x = (worldPos.x - *reinterpret_cast(reinterpret_cast(track) + 0xAC)) / + *reinterpret_cast(reinterpret_cast(track) + 0xB4); + minimapPos.y = (*reinterpret_cast(reinterpret_cast(track) + 0xB0) - worldPos.y) / + *reinterpret_cast(reinterpret_cast(track) + 0xB4) + 1.0f; +} + +void Minimap::UpdateRaceElements() { + UpdateMiniMapItems(); +} + +void Minimap::UpdateTrackMapArt() { + if (MinimapRotateWithPlayer == 0) { + FEngSetRotationZ(mPlayerCarIndicator, mPolyRotation); + FEngSetRotationZ(TrackmapLayout, 0.0f); + FEngSetRotationZ(TrackmapNorth, 0.0f); + } else { + FEngSetRotationZ(mPlayerCarIndicator, 0.0f); + FEngSetRotationZ(TrackmapLayout, -mPolyRotation); + FEngSetRotationZ(TrackmapNorth, -mPolyRotation); + } +} + +void Minimap::AdjustForWidescreen(bool moveOutwards) { + float offset; + if (moveOutwards) { + offset = -120.0f; + MinimapPivotX = offset; + MinimapDispX = -0.9375f; + } else { + offset = 120.0f; + MinimapPivotX = 0.0f; + MinimapDispX = 0.9375f; + } + mTrackMapCentre.x += offset; + for (unsigned int i = 0; i < 4; i++) { + reinterpret_cast(TrackmapArt[i]->pData)->Pos.x += offset; + } + reinterpret_cast(mPlayerCarIndicator->pData)->Pos.x += offset; + reinterpret_cast(mPlayerCarIndicator->pData)->Pos.y = mTrackMapCentre.y; +} + +void Minimap::RefreshMapItems() { + MiniMapItem *item = StaticMiniMapItems.GetHead(); + while (item != StaticMiniMapItems.EndOfList()) { + FEngSetInvisible(item->mpIcon); + item = item->GetNext(); + } + StaticMiniMapItems.DeleteAllElements(); + InitStaticMiniMapItems(); +} + +extern bool GPS_IsEngaged(); + +void Minimap::UpdateIconElement(FEImage *image, GIcon *icon) { + bVector2 pos2D; + bVector2 dir2D; + icon->GetPosition2D(pos2D); + dir2D.x = 1.0f; + dir2D.y = 0.0f; + if (icon->GetType() != GIcon::kType_AreaUnlock && !GPS_IsEngaged() && icon->GetIsGPSing()) { + icon->ClearGPSing(); + } + bool pulse = icon->GetIsGPSing(); + UpdateElementArt(&pos2D, &dir2D, image, pulse); + FEngSetRotationZ(image, 0.0f); +} + +void Minimap::UpdateMiniMapItems() { + bVector2 defaultDir; + for (MiniMapItem *item = static_cast(StaticMiniMapItems.GetHead()); + item != StaticMiniMapItems.EndOfList(); + item = static_cast(item->GetNext())) { + if (item->mHidden) { + FEngSetInvisible(item->mpIcon); + } else { + FEngSetVisible(item->mpIcon); + defaultDir.x = 0.0f; + defaultDir.y = 1.0f; + UpdateElementArt(&item->mPos, &defaultDir, item->mpIcon, false); + FEngSetRotationZ(item->mpIcon, 0.0f); + } + } +} + +void Minimap::InitStaticMiniMapItems() {} + +void Minimap::UpdatePlayer2Element() { + if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + IPlayer *player2 = IPlayer::Last(PLAYER_LOCAL); + ISimable *isimable = player2->GetSimable(); + bVector2 target_pos; + bVector2 target_dir; + bVector2 *pPos = &target_pos; + bVector2 *pDir = &target_dir; + GetVehicleVectors(pPos, pDir, isimable); + UpdateElementArt(pPos, pDir, mPlayerCarIndicator2, false); + } +} + +void Minimap::UpdateCopElements(IVehicle *ivehicle) { + unsigned int artIter = 0; + bool helicopterFound = false; + eVehicleList list_id = VEHICLE_AICOPS; + IPursuit *ipursuit = nullptr; + + mCopFlashCounter++; + if (mCopFlashCounter > 7) { + mCopFlashCounter = 0; + } + + IVehicleAI *ivehicleAI = ivehicle->GetAIVehiclePtr(); + ipursuit = ivehicleAI->GetPursuit(); + + if (MinimapShowNonPursuitCops || (ipursuit && !ipursuit->IsPursuitBailed())) { + const IVehicle::List &vehicles = IVehicle::GetList(list_id); + for (IVehicle *const *iter = vehicles.begin(); iter != vehicles.end(); ++iter) { + IVehicle *copVehicle = *iter; + if (!copVehicle->IsActive()) { + continue; + } + if (artIter > 7) { + break; + } + + bVector2 target_pos; + bVector2 target_dir; + bVector2 *target_pos_to_use = &target_pos; + bVector2 *target_dir_to_use = &target_dir; + ISimable *isimable = copVehicle->GetSimable(); + GetVehicleVectors(target_pos_to_use, target_dir_to_use, isimable); + + IPursuitAI *ipursuitai = nullptr; + copVehicle->QueryInterface(&ipursuitai); + FEObject *copArtToUse; + + if (copVehicle->GetVehicleClass() == VehicleClass::CHOPPER) { + copArtToUse = mHeliElementArt; + if (MinimapShowNonPursuitCops || (ipursuitai && ipursuitai->WasWithinEngagementRadius())) { + AITarget *target = ipursuitai->GetPursuitTarget(); + if (!target || target->GetSpeed() > 0.25f) { + if (!FEngIsScriptSet(mHeliLineOfSiteArt, FEHashUpper("TRACKING"))) { + FEngSetScript(mHeliLineOfSiteArt, FEHashUpper("TRACKING"), true); + } + } else { + if (!FEngIsScriptSet(mHeliLineOfSiteArt, 0x1744B3)) { + FEngSetScript(mHeliLineOfSiteArt, 0x1744B3, true); + } + } + } + helicopterFound = true; + UpdateElementArt(target_pos_to_use, target_dir_to_use, copArtToUse, false); + UpdateElementArt(target_pos_to_use, target_dir_to_use, mHeliLineOfSiteArt, false); + } else { + if (MinimapShowNonPursuitCops || + (ipursuitai && ipursuitai->WasWithinEngagementRadius() && MinimapShowPursuitCops)) { + copArtToUse = mCopElementArt[artIter]; + UpdateElementArt(target_pos_to_use, target_dir_to_use, copArtToUse, false); + } else { + FEngSetInvisible(mCopElementArt[artIter]); + } + } + + if (copVehicle->GetVehicleClass() != VehicleClass::CHOPPER) { + unsigned int copFlasherColour = 0xFFCCCCCC; + if (ipursuitai && ipursuitai->GetInPursuit()) { + if (mCopFlashCounter < 3) { + copFlasherColour = 0xFF0000FF; + } else if (mCopFlashCounter - 4U < 3) { + copFlasherColour = 0xFFA00000; + } + } + FEngSetColor(mCopElementArt[artIter], copFlasherColour); + artIter++; + } + } + } + + for (unsigned int i = artIter; i < 8; i++) { + FEngSetInvisible(mCopElementArt[i]); + } + if (!helicopterFound) { + FEngSetInvisible(mHeliElementArt); + FEngSetInvisible(mHeliLineOfSiteArt); + } +} + +void Minimap::UpdateElementArt(bVector2 *elementPos, bVector2 *elementDir, FEObject *elementArt, bool pulse) { + bVector2 mapPos; + ConvertPos(*elementPos, mapPos, CurrentTrack); + + float epoly_x = (mapPos.x - mTrackTargetNormalized.x) * mSpeedZoomScale; + float epoly_y = (mapPos.y - mTrackTargetNormalized.y) * mSpeedZoomScale; + const float sa = bSin(bDegToRad(mPolyRotation)); + const float ca = bCos(bDegToRad(mPolyRotation)); + float rot_epoly_x = epoly_y * ca - epoly_x * sa; + float rot_epoly_y = epoly_x * ca + epoly_y * sa; + float distance = bSqrt(rot_epoly_y * rot_epoly_y + rot_epoly_x * rot_epoly_x); + float alpha = 1.0f; + + if (distance > 0.0f) { + if (distance > 0.06f) { + rot_epoly_x *= 0.06f / distance; + rot_epoly_y *= 0.06f / distance; + + if (distance > 0.125f) { + alpha = 1.0f - (distance - 0.125f) * 9.523809f; + } + if (distance > 0.23f) { + alpha = 0.0f; + } + distance = 0.06f; + + if (pulse) { + alpha = 1.0f; + } + } + } + + if (distance <= 0.06f) { + float screen_x = mTrackMapCentre.x + rot_epoly_y * 1024.0f; + float screen_y = mTrackMapCentre.y + rot_epoly_x * 1024.0f; + FEngSetCenter(elementArt, screen_x, screen_y); + FEngSetVisible(elementArt); + FEngSetRotationZ(elementArt, bAngToDeg(bATan(elementDir->y, elementDir->x)) - mPolyRotation); + + unsigned int color = static_cast(FEngGetObjectColor(elementArt)); + int alphaInt = static_cast(alpha * 255.0f); + FEngSetColor(elementArt, color & 0x00FFFFFF | alphaInt << 24); + + if (pulse) { + FEngSetVisible(mGPSSelectionElementArt); + FEngSetCenter(mGPSSelectionElementArt, screen_x, screen_y); + } + } else { + FEngSetInvisible(elementArt); + } +} + +void Minimap::UpdateGameplayIcons(IPlayer *player) { + int iconsPlaced[GIcon::kType_Count]; + GIcon *sortedIcons[200]; + + FEngSetInvisible(mGPSSelectionElementArt); + bMemSet(iconsPlaced, 0, sizeof(iconsPlaced)); + + int numIcons = GManager::Get().GatherVisibleIcons(sortedIcons, player); + for (int onIcon = 0; onIcon < numIcons; onIcon++) { + GIcon *icon = sortedIcons[onIcon]; + GIcon::Type iconType = icon->GetType(); + GameplayIconInfo &iconInfo = kGameplayIconInfo[iconType]; + FEImage *image; + + if (iconInfo.mItemType != 0 && static_cast< unsigned int >(iconsPlaced[iconType]) < 8) { + if (FEDatabase->GetGameplaySettings()->IsMapItemEnabled(static_cast(iconInfo.mItemType))) { + image = mGameplayIcons[iconType][static_cast< unsigned int >(iconsPlaced[iconType])]; + iconsPlaced[iconType]++; + if (image) { + UpdateIconElement(image, icon); + } + } + } + } + + for (int onType = 0; onType < GIcon::kType_Count; onType++) { + for (int onHideIcon = iconsPlaced[onType]; static_cast< unsigned int >(onHideIcon) < 8; onHideIcon++) { + if (mGameplayIcons[onType][onHideIcon]) { + FEngSetInvisible(mGameplayIcons[onType][onHideIcon]); + } + } + } +} + +void Minimap::UpdateAiRacerElements() { + unsigned int artIter = 0; + eVehicleList listid = TheOnlineManager.IsOnlineRace() ? VEHICLE_REMOTE : VEHICLE_AIRACERS; + const IVehicle::List &vehicles = IVehicle::GetList(listid); + + for (IVehicle *const *iter = vehicles.begin(); iter != vehicles.end(); ++iter) { + IVehicle *ivehicle = *iter; + if (ivehicle->IsActive()) { + bVector2 target_pos; + bVector2 target_dir; + GetVehicleVectors(&target_pos, &target_dir, ivehicle->GetSimable()); + UpdateElementArt(&target_pos, &target_dir, mRacerElementArt[artIter], false); + artIter++; + } + } + + for (unsigned int i = artIter; i < 8; i++) { + FEngSetInvisible(mRacerElementArt[i]); + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.cpp index e69de29bb..a6b9adee5 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.cpp @@ -0,0 +1,106 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern void bEndianSwap32(void *data); +extern void bEndianSwap16(void *data); +extern int bSNPrintf(char *buf, int size, const char *fmt, ...); +extern int LZDecompress(unsigned char *pSrc, unsigned char *pDst); +extern void UnloadChunks(bChunk *chunks, int sizeof_chunks, const char *debug_name); +extern void LoadEmbeddedChunks(bChunk *chunk, int sizeof_chunks, const char *debug_name); + +ChoppedMiniMapManager *gChoppedMiniMapManager; + +void ChoppedMiniMapManager::Init() { + if (gChoppedMiniMapManager == nullptr) { + gChoppedMiniMapManager = new ChoppedMiniMapManager(9); + } +} + +ChoppedMiniMapManager::ChoppedMiniMapManager(int numSections) { + LoadingChopNum = 0; + NumSections = numSections; + for (int i = 0; i <= 63; i++) { + CompressedMiniMaps[i] = nullptr; + } +} + +int ChoppedMiniMapManager::Loader(bChunk *chunk) { + if (chunk->GetID() == 0x3A100) { + LZHeader *header = reinterpret_cast(chunk->GetData()); + bPlatEndianSwap(reinterpret_cast(header)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(chunk) + 0xE)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(header) + 8)); + bPlatEndianSwap(reinterpret_cast(reinterpret_cast(chunk) + 0x14)); + CompressedMiniMaps[LoadingChopNum++] = header; + return 1; + } + return 0; +} + +int ChoppedMiniMapManager::Unloader(bChunk *chunk) { + if (chunk->GetID() == 0x3A100) { + LoadingChopNum = LoadingChopNum - 1; + CompressedMiniMaps[LoadingChopNum] = nullptr; + if (LoadingChopNum == 0) { + RemoveUncompressedMaps(); + } + return 1; + } + return 0; +} + +void ChoppedMiniMapManager::SetMapHeader(char *header) { + bSNPrintf(map_header, 0x40, header); +} + +void ChoppedMiniMapManager::GetTextureName(char *buffer, int buffer_size, int chop_num) { + bSNPrintf(buffer, buffer_size, "%s_%d", map_header, chop_num); +} +void ChoppedMiniMapManager::UncompressMaps(short *chop_nums, int num_chops) { + for (int n = 0; n < NumSections; n++) { + UncompressedMiniMap *map = &UncompressedMiniMaps[n]; + if (map->Chunks) { + int keep_map = -1; + for (int i = 0; i < num_chops; i++) { + if (chop_nums[i] == static_cast(map->ChopNum)) { + keep_map = i; + break; + } + } + if (keep_map < 0) { + UnloadChunks(map->Chunks, map->SizeofChunks, "MiniMap Chop"); + bFree(map->Chunks); + map->Chunks = nullptr; + map->SizeofChunks = 0; + } + } + } + + for (int i = 0; i < num_chops; i++) { + int chop_num = chop_nums[i]; + int n = 0; + UncompressedMiniMap *free_map = nullptr; + + for (; n < NumSections; n++) { + UncompressedMiniMap *map = &UncompressedMiniMaps[n]; + if (!map->Chunks) { + free_map = map; + } else if (map->ChopNum == chop_num) { + break; + } + } + + if (n == NumSections && chop_num >= 0) { + void *lz_header = CompressedMiniMaps[chop_num]; + if (lz_header) { + free_map->ChopNum = chop_num; + int size = *reinterpret_cast(reinterpret_cast(lz_header) + 8); + free_map->SizeofChunks = size; + free_map->Chunks = static_cast(bMalloc(size, 0x2000)); + LZDecompress(reinterpret_cast(lz_header), + reinterpret_cast(free_map->Chunks)); + LoadEmbeddedChunks(free_map->Chunks, free_map->SizeofChunks, "MiniMap Chop embedded"); + } + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.hpp index a513931cd..31cff3694 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeMinimapStreamer.hpp @@ -5,6 +5,51 @@ #pragma once #endif +struct bChunk; +struct LZHeader; +// total size: 0xC +struct UncompressedMiniMap { + int ChopNum; // offset 0x0 + bChunk *Chunks; // offset 0x4 + int SizeofChunks; // offset 0x8 + + UncompressedMiniMap() : ChopNum(0) // + , Chunks(nullptr) // + , SizeofChunks(0) {} +}; + +// total size: 0x1B4 +struct ChoppedMiniMapManager { + static void Init(); + static void Close(); + + ChoppedMiniMapManager(int numSections); + ~ChoppedMiniMapManager(); + + int Loader(bChunk *chunk); + int Unloader(bChunk *chunk); + void SetMapHeader(char *header); + void GetTextureName(char *buffer, int buffer_size, int chop_num); + int CountAllocated(); + void UncompressMaps(short *chop_nums, int num_chops); + void Resize(int newNumSections); + + bool IsLoaded() { + return LoadingChopNum > 0; + } + + void RemoveUncompressedMaps() { + UncompressMaps(nullptr, 0); + } + + int LoadingChopNum; // offset 0x0 + int NumSections; // offset 0x4 + char map_header[64]; // offset 0x8 + LZHeader *CompressedMiniMaps[64]; // offset 0x48 + UncompressedMiniMap UncompressedMiniMaps[9]; // offset 0x148 +}; + +extern ChoppedMiniMapManager *gChoppedMiniMapManager; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.cpp index e69de29bb..c4321e340 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.cpp @@ -0,0 +1,61 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.hpp" + +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/Sim/Simulation.h" + +void FEngSetMultiImageRot(FEMultiImage *image, float angle_degrees); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +int bStrICmp(const char *s1, const char *s2); + +extern const char lbl_803E4D20[]; +extern const float lbl_803E4D1C; +extern const float lbl_803E4D30; +extern const float lbl_803E4D34; +extern const float lbl_803E4D38; +extern const float lbl_803E4D3C; + +NitrousGauge::NitrousGauge(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x80000800ULL) // + , INos(pOutter) // + , mNos(lbl_803E4D1C) // +{ + RegisterGroup(0x87C38E97); + mpDataNosMeterIcon = FEngFindObject(GetPackageName(), 0x27DDF583); + mpNosMeterBar = RegisterMultiImage(0xEDFB6D37); +} + +void NitrousGauge::Update(IPlayer *player) { + if (mpNosMeterBar != nullptr) { + float min_angle = lbl_803E4D30; + if (bStrICmp(GetPackageName(), lbl_803E4D20) == 0) { + min_angle = lbl_803E4D34; + } else if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + min_angle = lbl_803E4D38; + } + const float max_angle = min_angle; + const float min_nos = min_angle; + const float max_nos = min_angle; + const float frac = mNos; + const float angle = frac * -max_angle + max_angle; + FEngSetMultiImageRot(mpNosMeterBar, angle); + } +} + +void NitrousGauge::SetNos(float nos) { + if (nos <= lbl_803E4D3C) { + if (!FEngIsScriptSet(mpDataNosMeterIcon, 0x1744B3)) { + FEngSetScript(mpDataNosMeterIcon, 0x1744B3, true); + } + } else if (nos < mNos) { + if (!FEngIsScriptSet(mpDataNosMeterIcon, 0x77031C70)) { + FEngSetScript(mpDataNosMeterIcon, 0x77031C70, true); + } + } else { + if (!FEngIsScriptSet(mpDataNosMeterIcon, 0x03826A28)) { + FEngSetScript(mpDataNosMeterIcon, 0x03826A28, true); + } + } + mNos = nos; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.hpp index c495bfdf9..a458b3972 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeNitrousGauge.hpp @@ -5,6 +5,37 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +struct FEMultiImage; + +class INos : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + INos(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + protected: + virtual ~INos() {} + + public: + virtual void SetNos(float nos); +}; + +// total size: 0x40 +class NitrousGauge : public HudElement, public INos { + public: + NitrousGauge(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetNos(float nos) override; + + private: + float mNos; // offset 0x30 + FEObject *mpDataNosMeterIcon; // offset 0x34 + FEMultiImage *mpNosMeterBar; // offset 0x38 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.cpp index e69de29bb..96555371a 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.cpp @@ -0,0 +1,9 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.hpp" + +OnlineHUDSupport::OnlineHUDSupport(const char *pkg_name) + : HudElement(pkg_name, 0) +{ +} + +void OnlineHUDSupport::Update(IPlayer *player) { +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.hpp new file mode 100644 index 000000000..ead8e0842 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/HUD/FeOnlineHudSupport.hpp @@ -0,0 +1,22 @@ +#ifndef FRONTEND_HUD_FEONLINEHUDSUPPORT_H +#define FRONTEND_HUD_FEONLINEHUDSUPPORT_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" + +class OnlineHUDSupport : public HudElement { + public: + OnlineHUDSupport(const char *pkg_name); + void Update(IPlayer *player) override; + void DisplayGenericMessage(); + + private: + char *pPackageName; + IGenericMessage *mIGenericMessage; +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FePursuitBoard.cpp b/src/Speed/Indep/Src/Frontend/HUD/FePursuitBoard.cpp index e69de29bb..6a359c6ba 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FePursuitBoard.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FePursuitBoard.cpp @@ -0,0 +1,456 @@ +#include "Speed/Indep/Src/Frontend/HUD/FePursuitBoard.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/Frontend/Localization/Localize.hpp" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +bool FEngIsScriptSet(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash); +bool FEngIsScriptRunning(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash); +bool FEngIsScriptRunning(FEObject *object, unsigned int script_hash); +int FEPrintf(FEString *text, const char *fmt, ...); +void FEngGetBottomRight(FEObject *object, float &x, float &y); +void FEngSetBottomRight(FEObject *object, float x, float y); +void FEngGetTopLeft(FEObject *object, float &x, float &y); +void FEngSetTopLeft(FEObject *object, float x, float y); +void FEngGetSize(FEObject *object, float &x, float &y); +void FEngSetSize(FEObject *object, float x, float y); + +inline float FEngGetTopLeftX(FEObject *obj) { + float x, y; + FEngGetTopLeft(obj, x, y); + return x; +} + +inline float FEngGetTopLeftY(FEObject *obj) { + float x, y; + FEngGetTopLeft(obj, x, y); + return y; +} + +inline float FEngGetBottomRightX(FEObject *obj) { + float x, y; + FEngGetBottomRight(obj, x, y); + return x; +} + +inline float FEngGetBottomRightY(FEObject *obj) { + float x, y; + FEngGetBottomRight(obj, x, y); + return y; +} + +inline float FEngGetSizeY(FEObject *obj) { + float x, y; + FEngGetSize(obj, x, y); + return y; +} + +inline void FEngSetSizeX(FEObject *obj, float x) { + float y = FEngGetSizeY(obj); + FEngSetSize(obj, x, y); +} + +inline void FEngSetTopLeftX(FEObject *obj, float x) { + float y = FEngGetTopLeftY(obj); + FEngSetTopLeft(obj, x, y); +} + +inline void FEngSetBottomRightX(FEObject *obj, float x) { + float y = FEngGetBottomRightY(obj); + FEngSetBottomRight(obj, x, y); +} + +PursuitBoard::PursuitBoard(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x100000) // + , IPursuitBoard(pOutter) // + , mInPursuit(false) // + , mIsHiding(false) // + , mTimeUntilHidden(0.0f) // + , mTimeUntilBusted(0.0f) // + , mTimeUntilBackup(0.0f) // + , mPursuitDuration(0.0f) // + , mCooldownTimeRemaining(0.0f) // + , mCooldownTimeRequired(60.0f) // + , mNumCopsFullyEngaged(0) // + , mNumCopsDestroyed(0) // + , mNumCopsDamaged(0) // + , mTotalNumCopsInvolved(0) // + , mHeliInvolved(false) // + , mPursuitRep(0) +{ + mpDataPursuitBoardGroup = RegisterGroup(0xde89e070); + mpDataPursuitMeterGroup = RegisterGroup(0x7b422ba3); + mpDataPursuitIconsGroup = RegisterGroup(0xe0b1430b); + mpDataPursuitSummaryGroup = RegisterGroup(0x8674e6d4); + mpDataPursuitCooldownMeterGroup = RegisterGroup(0x84a226ec); + mpDataBackupTimerTextGroup = RegisterGroup(0x6a144066); + RegisterString(0x3c165f39); + RegisterString(0xbee44775); + mpDataPursuitTimer = static_cast(FEngFindObject(GetPackageName(), 0xfc39cb0a)); + mpDataBackupTimer = static_cast(FEngFindObject(GetPackageName(), 0xbee44775)); + mpDataPursuitCopsNumbers = static_cast(FEngFindObject(GetPackageName(), 0x814918ca)); + mpDataCopsTakenOut = static_cast(FEngFindObject(GetPackageName(), 0xa16f9f1e)); + mpDataCopsDamaged = static_cast(FEngFindObject(GetPackageName(), 0x5fa70d4c)); + mpDataPursuitSummaryTotal = static_cast(FEngFindObject(GetPackageName(), 0x875e92eb)); + mpDataBustedBar0 = FEngFindObject(GetPackageName(), 0xe0e0169b); + mpDataBustedBar1 = FEngFindObject(GetPackageName(), 0x8eeebd33); + mpDataBustedBar2 = FEngFindObject(GetPackageName(), 0x47a4e2a9); + mpDataBustedBar3 = FEngFindObject(GetPackageName(), 0x8e3653a6); + mpDataBustedBar4 = FEngFindObject(GetPackageName(), 0x30f39a6f); + mpDataCooldownBar = FEngFindObject(GetPackageName(), 0xcf817638); + mpDataBackupBacking = FEngFindObject(GetPackageName(), 0x8c20a763); + mpDataHidingBacking = FEngFindObject(GetPackageName(), 0x5c2a4f20); + mBustedBarOriginalWidth0 = mpDataBustedBar0->GetObjData()->Size.x; + mBustedBarOriginalWidth1 = mpDataBustedBar1->GetObjData()->Size.x; + mBustedBarOriginalWidth2 = mpDataBustedBar2->GetObjData()->Size.x; + mBustedBarOriginalWidth3 = mpDataBustedBar3->GetObjData()->Size.x; + mBustedBarOriginalWidth4 = mpDataBustedBar4->GetObjData()->Size.x; + mCooldownBarOriginalWidth = mpDataCooldownBar->GetObjData()->Size.x; +} + +void PursuitBoard::Update(IPlayer *player) { + if (mInPursuit) { + Timer timer; + char timeToPrint[16]; + + if (!FEngIsScriptSet(mpDataPursuitBoardGroup, 0x5079c8f8)) { + FEngSetScript(mpDataPursuitBoardGroup, 0x5079c8f8, true); + } + + timer = Timer(mPursuitDuration); + timer.PrintToString(timeToPrint, 4); + FEPrintf(mpDataPursuitTimer, "%s", timeToPrint); + FEPrintf(mpDataPursuitSummaryTotal, "%$d", mPursuitRep); + + if (!FEngIsScriptSet(mpDataPursuitSummaryGroup, 0x5079c8f8)) { + FEngSetScript(mpDataPursuitSummaryGroup, 0x5079c8f8, true); + } + + if (mTimeUntilBusted <= -1.0f) { + float originalLeftX; + + if (!FEngIsScriptSet(mpDataPursuitCooldownMeterGroup, 0x13f51124)) { + FEngSetScript(mpDataPursuitMeterGroup, 0x92975065, true); + g_pEAXSound->PlayUISoundFX(static_cast(0xc)); + } + + originalLeftX = FEngGetTopLeftX(mpDataCooldownBar); + FEngSetSizeX(mpDataCooldownBar, mCooldownBarOriginalWidth * (1.0f - mCooldownTimeRemaining / mCooldownTimeRequired)); + FEngSetTopLeftX(mpDataCooldownBar, originalLeftX); + + if (mTimeUntilHidden > 0.0f) { + if (!FEngIsScriptSet(mpDataHidingBacking, 0x5079c8f8)) { + FEngSetScript(mpDataHidingBacking, 0x5079c8f8, true); + } + } else { + if (FEngIsScriptSet(mpDataHidingBacking, 0x5079c8f8)) { + FEngSetScript(mpDataHidingBacking, 0x33113ac, true); + } + } + } else { + int numCopsToReport; + float originalLeftX; + float originalRightX; + float bustedBarTime; + + if (FEngIsScriptSet(mpDataPursuitCooldownMeterGroup, 0x13f51124)) { + FEngSetScript(mpDataPursuitCooldownMeterGroup, 0x92975065, true); + g_pEAXSound->PlayUISoundFX(static_cast(0xc)); + } else { + if (FEngIsScriptSet(mpDataPursuitMeterGroup, 0x16a259) || + FEngIsScriptSet(mpDataPursuitMeterGroup, 0x33113ac)) { + FEngSetScript(mpDataPursuitMeterGroup, 0x5079c8f8, true); + } + } + + if (FEngIsScriptSet(mpDataHidingBacking, 0x5079c8f8)) { + FEngSetScript(mpDataHidingBacking, 0x33113ac, true); + } + + numCopsToReport = mNumCopsFullyEngaged; + if (mHeliInvolved) { + numCopsToReport = numCopsToReport - 1; + } + FEPrintf(mpDataPursuitCopsNumbers, "%$d", numCopsToReport); + FEPrintf(mpDataCopsTakenOut, "%$d", mNumCopsDestroyed); + FEPrintf(mpDataCopsDamaged, "%$d", mNumCopsDamaged); + + if (mTimeUntilBackup > 0.0f) { + if (mTimeUntilBackup > 10.0f) { + if (!FEngIsScriptSet(mpDataBackupBacking, 0x5079c8f8)) { + FEngSetScript(mpDataBackupBacking, 0x5079c8f8, true); + } + } else { + if (!FEngIsScriptSet(mpDataBackupBacking, 0x26ded57)) { + FEngSetScript(mpDataBackupBacking, 0x26ded57, true); + } + } + timer = Timer(mTimeUntilBackup); + timer.PrintToString(timeToPrint, 4); + FEPrintf(mpDataBackupTimer, "%s", timeToPrint); + } else { + if (!FEngIsScriptSet(mpDataBackupBacking, 0x33113ac)) { + FEngSetScript(mpDataBackupBacking, 0x33113ac, true); + } + } + + originalLeftX = 0.0f; + originalRightX = FEngGetBottomRightX(mpDataBustedBar0); + if (mTimeUntilBusted > 0.5f) { + bustedBarTime = (mTimeUntilBusted - 0.5f) * 2.0f; + } else { + bustedBarTime = originalLeftX; + } + FEngSetSizeX(mpDataBustedBar0, bustedBarTime * mBustedBarOriginalWidth0); + FEngSetBottomRightX(mpDataBustedBar0, originalRightX); + + if (mTimeUntilBusted > 0.5f) { + if (!FEngIsScriptSet(mpDataBustedBar0, 0x26ded57)) { + FEngSetScript(mpDataBustedBar0, 0x26ded57, true); + } + } else { + if (!FEngIsScriptSet(mpDataBustedBar0, 0x1744b3)) { + FEngSetScript(mpDataBustedBar0, 0x1744b3, true); + } + } + + originalRightX = FEngGetBottomRightX(mpDataBustedBar1); + if (mTimeUntilBusted > 0.5f) { + bustedBarTime = 0.9f; + } else if (mTimeUntilBusted > 0.1f) { + bustedBarTime = mTimeUntilBusted * 2.0f - 0.1f; + } else { + bustedBarTime = 0.0f; + } + FEngSetSizeX(mpDataBustedBar1, bustedBarTime * mBustedBarOriginalWidth1); + FEngSetBottomRightX(mpDataBustedBar1, originalRightX); + + if (mTimeUntilBusted < 0.1f && mTimeUntilBusted > -0.1f) { + if (!FEngIsScriptSet(mpDataBustedBar2, 0x3826a28)) { + FEngSetScript(mpDataBustedBar2, 0x3826a28, true); + } + } else { + if (!FEngIsScriptSet(mpDataBustedBar2, 0x1ca7c0)) { + FEngSetScript(mpDataBustedBar2, 0x1ca7c0, true); + } + } + + originalLeftX = FEngGetTopLeftX(mpDataBustedBar3); + if (mTimeUntilBusted < -0.5f) { + bustedBarTime = 1.0f; + } else if (mTimeUntilBusted < -0.1f) { + bustedBarTime = (-mTimeUntilBusted * 2.0f) - 0.1f; + } else { + bustedBarTime = 0.0f; + } + FEngSetSizeX(mpDataBustedBar3, bustedBarTime * mBustedBarOriginalWidth3); + FEngSetTopLeftX(mpDataBustedBar3, originalLeftX); + + originalLeftX = FEngGetTopLeftX(mpDataBustedBar4); + if (mTimeUntilBusted < -0.5f) { + bustedBarTime = -(mTimeUntilBusted + 0.5f) * 2.0f; + } else { + bustedBarTime = 0.0f; + } + FEngSetSizeX(mpDataBustedBar4, bustedBarTime * mBustedBarOriginalWidth4); + FEngSetTopLeftX(mpDataBustedBar4, originalLeftX); + + if (mTimeUntilBusted < -0.5f) { + if (!FEngIsScriptSet(mpDataBustedBar4, 0x26ded57)) { + FEngSetScript(mpDataBustedBar4, 0x26ded57, true); + } + } else { + if (!FEngIsScriptSet(mpDataBustedBar4, 0x1744b3)) { + FEngSetScript(mpDataBustedBar4, 0x1744b3, true); + } + } + } + } else { + if (FEngIsScriptSet(mpDataPursuitBoardGroup, 0x5079c8f8)) { + FEngSetScript(mpDataPursuitBoardGroup, 0x33113ac, true); + } + if (!FEngIsScriptSet(mpDataPursuitMeterGroup, 0x33113ac) && + !FEngIsScriptSet(mpDataPursuitMeterGroup, 0x16a259)) { + FEngSetScript(mpDataPursuitMeterGroup, 0x33113ac, true); + } + if (FEngIsScriptSet(mpDataPursuitCooldownMeterGroup, 0x5079c8f8) || + FEngIsScriptSet(mpDataPursuitCooldownMeterGroup, 0x13f51124)) { + FEngSetScript(mpDataPursuitCooldownMeterGroup, 0x33113ac, true); + } + if (FEngIsScriptSet(mpDataPursuitIconsGroup, 0x5079c8f8)) { + FEngSetScript(mpDataPursuitIconsGroup, 0x33113ac, true); + } + } +} + +void PursuitBoard::SetCooldownTimeRequired(float time) { + mCooldownTimeRequired = time; +} + +void PursuitBoard::SetInPursuit(bool inPursuit) { + if (mInPursuit != inPursuit) { + mInPursuit = inPursuit; + } +} + +void PursuitBoard::SetIsHiding(bool isHiding) { + if (mIsHiding != isHiding) { + mIsHiding = isHiding; + } +} + +void PursuitBoard::SetTimeUntilHidden(float time) { + if (mTimeUntilHidden != time) { + mTimeUntilHidden = time; + } +} + +void PursuitBoard::SetTimeUntilBusted(float time, bool bIsBusted) { + if (bIsBusted) { + time = 1.0f; + } else { + if (time > 0.99f) { + time = 0.99f; + } + } + if (mTimeUntilBusted != time) { + mTimeUntilBusted = time; + if (time >= 1.0f) { + IGenericMessage *igenericmessage; + if (IPlayer::First(PLAYER_LOCAL)->GetSimable()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + GetTranslatedString(0x532b5186), false, 0x9d73bc15, 0, 0, GenericMessage_Priority_1); + } + } + } +} + +void PursuitBoard::SetTimeUntilBackup(float time) { + if (mTimeUntilBackup != time) { + mTimeUntilBackup = time; + } +} + +void PursuitBoard::SetIsInView(bool isInView) { + if (mIsInView != isInView) { + mIsInView = isInView; + } +} + +void PursuitBoard::SetCooldownTimeRemaining(float time) { + if (mCooldownTimeRemaining != time) { + mCooldownTimeRemaining = time; + } +} + +void PursuitBoard::SetTotalNumCopsInvolved(int numCops) { + if (mTotalNumCopsInvolved != numCops) { + mTotalNumCopsInvolved = numCops; + } +} + +void PursuitBoard::SetHeliInvolvedInPursuit(bool heliInvolved) { + if (mHeliInvolved != heliInvolved) { + mHeliInvolved = heliInvolved; + } +} + +void PursuitBoard::SetPursuitRep(int rep) { + if (mPursuitRep != rep) { + mPursuitRep = rep; + } +} + +void PursuitBoard::SetPursuitDuration(float time) { + if (mPursuitDuration != time) { + if (time >= 0.0f) { + mPursuitDuration = time; + } else { + mPursuitDuration = 0.0f; + } + } +} + +void PursuitBoard::SetNumCopsDamaged(int numCops) { + if (mNumCopsDamaged != numCops) { + if (numCops > mNumCopsDamaged) { + if (!FEngIsScriptSet(mpDataCopsDamaged, 0x4f90cf9b)) { + FEngSetScript(mpDataCopsDamaged, 0x4f90cf9b, true); + } + } + mNumCopsDamaged = numCops; + } +} + +void PursuitBoard::SetNumCopsInPursuit(int numCops) { + if (mNumCopsFullyEngaged != numCops) { + if (numCops > mNumCopsFullyEngaged) { + if (!FEngIsScriptRunning(GetPackageName(), 0x3787231c, 0x4f90cf9b)) { + FEngSetScript(GetPackageName(), 0x3787231c, 0x4f90cf9b, true); + } + } else { + if (!FEngIsScriptSet(GetPackageName(), 0x3787231c, 0xfb12d252)) { + FEngSetScript(GetPackageName(), 0x3787231c, 0xfb12d252, true); + } + if (numCops < mNumCopsFullyEngaged) { + if (!FEngIsScriptSet(GetPackageName(), 0x3b9919a8, 0x579dbc92)) { + FEngSetScript(GetPackageName(), 0x3b9919a8, 0x579dbc92, true); + } + } + } + mNumCopsFullyEngaged = numCops; + } +} + +void PursuitBoard::SetNumCopsDestroyed(int numCops, UCrc32 lastCopDestroyedType, + int lastCopDestroyedMultiplier, + int lastCopDestroyedRep) { + if (mNumCopsDestroyed == numCops) { + return; + } + if (numCops > mNumCopsDestroyed) { + const char *pCopString = nullptr; + if (lastCopDestroyedType == UCrc32("copcross")) { + pCopString = GetLocalizedString(0x8fe02b9f); + } else if (lastCopDestroyedType == UCrc32("copsport")) { + pCopString = GetLocalizedString(0x8fe02b9f); + } else if (lastCopDestroyedType == UCrc32("copmidsize")) { + pCopString = GetLocalizedString(0xf49a550a); + } else if (lastCopDestroyedType == UCrc32("copghost")) { + pCopString = GetLocalizedString(0x902311da); + } else if (lastCopDestroyedType == UCrc32("copgto")) { + pCopString = GetLocalizedString(0x9bfd379f); + } else if (lastCopDestroyedType == UCrc32("copgtoghost")) { + pCopString = GetLocalizedString(0x65e97524); + } else if (lastCopDestroyedType == UCrc32("copsporthench")) { + pCopString = GetLocalizedString(0x4ee07213); + } else if (lastCopDestroyedType == UCrc32("copsportghost")) { + pCopString = GetLocalizedString(0x4ed00512); + } else if (lastCopDestroyedType == UCrc32("copsuv")) { + pCopString = GetLocalizedString(0x9bfd6ad3); + } else if (lastCopDestroyedType == UCrc32("copsuvpatrol")) { + pCopString = GetLocalizedString(0x9bfd6ad3); + } else if (lastCopDestroyedType == UCrc32("copsuvl")) { + pCopString = GetLocalizedString(0x1baac57f); + } + if (pCopString) { + char copCarString[64]; + bSNPrintf(copCarString, 64, pCopString, lastCopDestroyedRep * lastCopDestroyedMultiplier); + IGenericMessage *igenericmessage; + if (IPlayer::First(PLAYER_LOCAL)->GetSimable()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + copCarString, false, 0x8ab83edb, + bStringHash("COPS_TAKENOUT_ICON"), 0x13ff94, + GenericMessage_Priority_4); + } + } + } + mNumCopsDestroyed = numCops; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FePursuitBoard.hpp b/src/Speed/Indep/Src/Frontend/HUD/FePursuitBoard.hpp index 0dfb775c1..28b6f7918 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FePursuitBoard.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FePursuitBoard.hpp @@ -5,6 +5,74 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" +class PursuitBoard : public HudElement, public IPursuitBoard { + public: + PursuitBoard(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetInPursuit(bool inPursuit) override; + void SetIsHiding(bool isHiding) override; + void SetTimeUntilHidden(float time) override; + void SetTimeUntilBusted(float time, bool bIsBusted) override; + void SetTimeUntilBackup(float time) override; + void SetIsInView(bool isInView) override; + void SetPursuitDuration(float time) override; + void SetCooldownTimeRemaining(float time) override; + void SetCooldownTimeRequired(float time) override; + void SetNumCopsInPursuit(int numCops) override; + void SetNumCopsDestroyed(int numCops, UCrc32 lastCopDestroyedType, int lastCopDestroyedMultiplier, int lastCopDestroyedRep) override; + void SetNumCopsDamaged(int numCops) override; + void SetTotalNumCopsInvolved(int numCops) override; + void SetHeliInvolvedInPursuit(bool heliInvolved) override; + void SetPursuitRep(int rep) override; + + private: + bool mInPursuit; + bool mIsHiding; + float mTimeUntilHidden; + float mTimeUntilBusted; + float mTimeUntilBackup; + bool mIsInView; + float mPursuitDuration; + float mCooldownTimeRemaining; + float mCooldownTimeRequired; + int mNumCopsFullyEngaged; + int mNumCopsDestroyed; + int mNumCopsDamaged; + int mTotalNumCopsInvolved; + bool mHeliInvolved; + int mPursuitRep; + FEGroup * mpDataPursuitBoardGroup; + FEGroup * mpDataPursuitMeterGroup; + FEGroup * mpDataPursuitIconsGroup; + FEGroup * mpDataPursuitSummaryGroup; + FEGroup * mpDataPursuitCooldownMeterGroup; + FEGroup * mpDataBackupTimerTextGroup; + FEString * mpDataPursuitTimer; + FEString * mpDataBackupTimer; + FEString * mpDataPursuitCopsNumbers; + FEString * mpDataCopsTakenOut; + FEString * mpDataCopsDamaged; + FEString * mpDataPursuitSummaryTotal; + FEObject * mpDataBustedBar0; + FEObject * mpDataBustedBar1; + FEObject * mpDataBustedBar2; + FEObject * mpDataBustedBar3; + FEObject * mpDataBustedBar4; + FEObject * mpDataCooldownBar; + FEObject * mpDataBackupBacking; + FEObject * mpDataHidingBacking; + float mBustedBarOriginalWidth0; + float mBustedBarOriginalWidth1; + float mBustedBarOriginalWidth2; + float mBustedBarOriginalWidth3; + float mBustedBarOriginalWidth4; + float mCooldownBarOriginalWidth; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeRaceInformation.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeRaceInformation.cpp index e69de29bb..9ecdc8181 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeRaceInformation.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeRaceInformation.cpp @@ -0,0 +1,137 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeRaceInformation.hpp" + +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" + +unsigned long FEHashUpper(const char *name); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +int FEPrintf(FEString *text, const char *fmt, ...); +int FEPrintf(const char *pkg_name, FEObject *obj, const char *fmt, ...); +void FEngSetLanguageHash(FEString *text, unsigned int hash); + +extern const char lbl_803E4CB4[]; +extern const char lbl_803E4CF0[]; +extern const char lbl_803E4F38[]; +extern const char lbl_803E4F50[]; +extern const char lbl_803E4F60[]; +extern const char lbl_803E4F70[]; +extern const char lbl_803E4F80[]; +extern const float lbl_803E4F94; +extern const char lbl_803E4F98[]; +extern const char lbl_803E4FA0[]; + +RaceInformation::RaceInformation(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x4000000) // + , IRaceInformation(pOutter) // + , mNumRacers(-1) // + , mNumLaps(-1) // + , mPlayerPosition(-1) // + , mPlayerLapNumber(-1) // + , mPlayerLapTime(lbl_803E4F94) // + , mSuddenDeath(false) // + , mPlayerPercentComplete(lbl_803E4F94) // + , mPlayerTollboothNumber(0) // + , mNumTollbooths(0) // +{ + RegisterGroup(FEHashUpper(lbl_803E4F38)); + mDataCurrentLapTime = static_cast(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4F50))); + mDataCompleteText = static_cast(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4F60))); + mDataPositionGroup = static_cast(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4F70))); + mDataIconTollbooth = static_cast(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4F80))); + mpDataTollboothNumTop = FEngFindObject(pkg_name, 0x1CDDD8D0); + mpDataTollboothNumBot = FEngFindObject(pkg_name, 0x18A7ACD2); + mpDataRacePosNum = FEngFindObject(pkg_name, 0x9C183BF8); + mpDataRacerCount = FEngFindObject(pkg_name, 0x3BBD6268); + mpDataPercentComplete = FEngFindObject(pkg_name, 0x9CB5C95D); +} + +void RaceInformation::Update(IPlayer *player) { + bool isTollbooth = false; + if (GRaceStatus::Exists()) { + GRace::Type raceType = GRaceStatus::Get().GetRaceType(); + if (raceType == GRace::kRaceType_Tollbooth) { + isTollbooth = true; + } + } + + if (isTollbooth) { + if (!FEngIsScriptSet(mDataPositionGroup, 0x16A259)) { + FEngSetScript(mDataPositionGroup, 0x16A259, true); + } + if (!FEngIsScriptSet(mDataIconTollbooth, 0x5079C8F8)) { + FEngSetScript(mDataIconTollbooth, 0x5079C8F8, true); + } + FEPrintf(GetPackageName(), mpDataTollboothNumTop, lbl_803E4CB4, mPlayerTollboothNumber); + FEPrintf(GetPackageName(), mpDataTollboothNumBot, lbl_803E4CB4, mNumTollbooths); + } else { + if (!FEngIsScriptSet(mDataPositionGroup, 0x1744B3)) { + FEngSetScript(mDataPositionGroup, 0x1744B3, true); + } + if (!FEngIsScriptSet(mDataIconTollbooth, 0x16A259)) { + FEngSetScript(mDataIconTollbooth, 0x16A259, true); + } + FEPrintf(GetPackageName(), mpDataRacePosNum, lbl_803E4CB4, mPlayerPosition); + FEPrintf(GetPackageName(), mpDataRacerCount, lbl_803E4CB4, mNumRacers); + } + + GRace::Type raceType2 = GRaceStatus::Get().GetRaceType(); + + if (raceType2 == GRace::kRaceType_P2P || raceType2 == GRace::kRaceType_Tollbooth || raceType2 == GRace::kRaceType_SpeedTrap) { + FEngSetLanguageHash(mDataCompleteText, 0x59BB1918); + FEPrintf(GetPackageName(), mpDataPercentComplete, lbl_803E4F98, static_cast(mPlayerPercentComplete)); + } else { + FEngSetLanguageHash(mDataCompleteText, 0xBF9C); + FEPrintf(GetPackageName(), mpDataPercentComplete, lbl_803E4FA0, mPlayerLapNumber, mNumLaps); + } + + if (!mSuddenDeath) { + Timer t; + t.SetTime(mPlayerLapTime); + char buf[16]; + t.PrintToString(buf, 4); + FEPrintf(mDataCurrentLapTime, lbl_803E4CF0, buf); + } else { + FEngSetLanguageHash(mDataCurrentLapTime, 0x733C8147); + } +} + +void RaceInformation::SetNumRacers(int numRacers) { + mNumRacers = numRacers; +} + +void RaceInformation::SetNumLaps(int numLaps) { + mNumLaps = numLaps; +} + +void RaceInformation::SetPlayerPosition(int position) { + mPlayerPosition = position; +} + +void RaceInformation::SetPlayerLapTime(float lapTime) { + mPlayerLapTime = lapTime; +} + +void RaceInformation::SetSuddenDeathMode(bool suddenDeath) { + mSuddenDeath = suddenDeath; +} + +void RaceInformation::SetPlayerPercentComplete(float percent) { + mPlayerPercentComplete = percent; +} + +void RaceInformation::SetPlayerTollboothsCrossed(int num) { + mPlayerTollboothNumber = num; +} + +void RaceInformation::SetNumTollbooths(int num) { + mNumTollbooths = num; +} + +void RaceInformation::SetPlayerLapNumber(int lap) { + if (lap > mNumLaps) { + lap = mNumLaps; + } + mPlayerLapNumber = lap; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeRaceInformation.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeRaceInformation.hpp index e2d3e4cbc..54bcf6b6a 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeRaceInformation.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeRaceInformation.hpp @@ -5,6 +5,44 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +class RaceInformation : public HudElement, public IRaceInformation { + public: + RaceInformation(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetNumRacers(int numRacers) override; + void SetNumLaps(int numLaps) override; + void SetPlayerPosition(int position) override; + void SetPlayerLapTime(float lapTime) override; + void SetSuddenDeathMode(bool suddenDeath) override; + void SetPlayerPercentComplete(float percent) override; + void SetPlayerTollboothsCrossed(int num) override; + void SetNumTollbooths(int num) override; + void SetPlayerLapNumber(int lap) override; + + private: + int mNumRacers; + int mNumLaps; + int mPlayerPosition; + int mPlayerLapNumber; + float mPlayerLapTime; + bool mSuddenDeath; + float mPlayerPercentComplete; + int mPlayerTollboothNumber; + int mNumTollbooths; + FEString * mDataCurrentLapTime; + FEString * mDataCompleteText; + FEGroup * mDataPositionGroup; + FEImage * mDataIconTollbooth; + FEObject * mpDataTollboothNumTop; + FEObject * mpDataTollboothNumBot; + FEObject * mpDataRacePosNum; + FEObject * mpDataRacerCount; + FEObject * mpDataPercentComplete; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.cpp index e69de29bb..266bd12d9 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.cpp @@ -0,0 +1,182 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.hpp" + +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Localization/Localize.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" + +const char *GetLocalizedString(unsigned int hash); +int bSPrintf(char *dest, const char *fmt, ...); +int bSNPrintf(char *dest, int maxlen, const char *fmt, ...); + +extern const char lbl_803E4CF4[]; +extern const char lbl_803E4CFC[]; + +static const unsigned int RaceOverFinishStrings[8] = { + 0xE815BC6D, + 0xC9A64EFB, + 0x443FAB5A, + 0xAC122037, + 0x4342D430, + 0x442E5F4F, + 0xFFC2867C, + 0xEAC720D3, +}; + +RaceOverMessage::RaceOverMessage(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 4) // + , IRaceOverMessage(pOutter) // + , bShowMessage(false) // + , bShowTotalledMessage(false) {} + +void RaceOverMessage::Update(IPlayer *player) { + if (bShowTotalledMessage != 0) { + eView *view = eGetView(player->GetRenderPort(), false); + CameraMover *cammover = nullptr; + + if (view != nullptr) { + cammover = view->GetCameraMover(); + } + + if (cammover != nullptr && cammover->GetType() == CM_DRIVE_CUBIC) { + bShowTotalledMessage = 0; + + IGenericMessage *igenericmessage; + if (player->GetHud()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + GetTranslatedString(0x4BA0D22F), false, 0x8AB83EDB, 0, 0, GenericMessage_Priority_1); + } + } + } +} + +void RaceOverMessage::RequestRaceOverMessage(IPlayer *player) { +#define RACEOVER_FIELD(type, base, offset) (*reinterpret_cast(reinterpret_cast(base) + (offset))) + + bShowMessage = 1; + + GRacerInfo *info; + { + ISimable *player_simable = player->GetSimable(); + info = GRaceStatus::Get().GetRacerInfo(player_simable); + } + float racer_time = info->GetRaceTimer().GetTime(); + float race_time_limit = GRaceStatus::Get().GetRaceParameters()->GetTimeLimit(); + float rank_by_points = GRaceStatus::Get().GetRaceParameters()->GetRankPlayersByPoints(); + int was_vehicle_totalled = 0; + IVehicle *ivehicle; + char race_over_message[64]; + + if (RACEOVER_FIELD(int, info, 0x0) != 0) { + ISimable *simable = ISimable::FindInstance(reinterpret_cast< HSIMABLE >(RACEOVER_FIELD(int, info, 0x0))); + if (simable != nullptr && simable->QueryInterface(&ivehicle)) { + was_vehicle_totalled = ivehicle->IsDestroyed(); + } + } + + int is_challenge_race = GRaceStatus::IsChallengeRace(); + int is_completed_challenge_race = 0; + + if (is_challenge_race != 0 && RACEOVER_FIELD(int, info, 0x2C) != 0) { + is_completed_challenge_race = 1; + } + + if (was_vehicle_totalled != 0) { + bShowTotalledMessage = 1; + } else if (RACEOVER_FIELD(int, info, 0x24) != 0) { + IGenericMessage *igenericmessage; + + if (player->GetHud()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + GetTranslatedString(0x7449D26D), false, 0x8AB83EDB, 0, 0, GenericMessage_Priority_1); + } + } else if (RACEOVER_FIELD(int, info, 0x20) != 0) { + IGenericMessage *igenericmessage; + + if (player->GetHud()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + GetTranslatedString(0x4BA0D22F), false, 0x8AB83EDB, 0, 0, GenericMessage_Priority_1); + } + } else if (RACEOVER_FIELD(int, info, 0x1C) != 0) { + IGenericMessage *igenericmessage; + + bSNPrintf(race_over_message, 64, lbl_803E4CF4, GetTranslatedString(0x82E4DAFD), + GetTranslatedString(0x1B59940C)); + + if (player->GetHud()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + race_over_message, false, 0x8AB83EDB, 0, 0, GenericMessage_Priority_1); + } + } else if (0.0f < race_time_limit && racer_time > race_time_limit && rank_by_points == 0.0f && + is_completed_challenge_race == 0) { + Timer time_limit; + char race_time_str[16]; + IGenericMessage *igenericmessage; + + time_limit.SetTime(race_time_limit); + time_limit.PrintToString(race_time_str, 4); + + if (player->GetHud()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + GetTranslatedString(0x556ED1B2), false, 0x9D73BC15, 0, 0, GenericMessage_Priority_1); + } + } else if (GRaceStatus::IsChallengeRace() != 0 && RACEOVER_FIELD(int, info, 0x2C) == 0) { + IGenericMessage *igenericmessage; + + bSNPrintf( + race_over_message, 64, lbl_803E4CF4, GetTranslatedString(0x82E4DAFD), + GetTranslatedString(0xF8ED7926)); + + if (player->GetHud()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + race_over_message, false, 0x8AB83EDB, 0, 0, GenericMessage_Priority_1); + } + } else { + Timer race_time; + char race_time_str[16]; + char bot_message_string[48]; + char message_string[64]; + unsigned int finish_hash; + IGenericMessage *igenericmessage; + + race_time.SetTime(racer_time); + race_time.PrintToString(race_time_str, 4); + + int racer_count = GRaceStatus::Get().GetRacerCount(); + int rank = RACEOVER_FIELD(int, info, 0x10); + + if (rank > 1 && rank == racer_count) { + finish_hash = 0xEAC720D3; + } else { + finish_hash = RaceOverFinishStrings[rank - 1]; + } + + bSPrintf(bot_message_string, GetLocalizedString(0xC2878EBC), race_time_str); + bSPrintf(message_string, lbl_803E4CF4, GetTranslatedString(finish_hash), bot_message_string); + + if (player->GetHud()->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + message_string, false, 0x8AB83EDB, 0, 0, GenericMessage_Priority_1); + } + } + + if (cFEng::Get()->IsPackagePushed(lbl_803E4CFC)) { + new EUnPause(); + } + +#undef RACEOVER_FIELD +} + +void RaceOverMessage::DismissRaceOverMessage() { + bShowMessage = 0; + bShowTotalledMessage = 0; +} + +int RaceOverMessage::ShouldShowRaceOverMessage() { + return bShowMessage; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.hpp index 912214fba..1f3270131 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeRaceOverMessage.hpp @@ -5,6 +5,20 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +class RaceOverMessage : public HudElement, public IRaceOverMessage { + public: + RaceOverMessage(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void RequestRaceOverMessage(IPlayer *player) override; + void DismissRaceOverMessage() override; + int ShouldShowRaceOverMessage() override; + + private: + int bShowMessage; + int bShowTotalledMessage; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeRadarDetector.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeRadarDetector.cpp index e69de29bb..b2731b73e 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeRadarDetector.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeRadarDetector.cpp @@ -0,0 +1,184 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeRadarDetector.hpp" +#include "Speed/Indep/Src/Ecstasy/EcstasyE.hpp" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Generated/Messages/MMiscSound.h" +#include "Speed/Indep/Libs/Support/Miscellaneous/StringHash.h" + +extern bool FEngIsScriptSet(FEObject *, unsigned int); +extern void FEngSetScript(FEObject *, unsigned int, bool); +extern void FEngSetMultiImageBottomRightUVs(FEMultiImage *, FEVector2 &, int); +extern void FEngSetRotationZ(FEObject *, float); +extern float TWK_RadarDetectorMinThreshold; + +float RadarDetector::mStaticRange; + +RadarDetector::RadarDetector(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x200000) // + , IRadarDetector(pOutter) // + , mRange(0.0f) // + , mDirection(0.0f) // + , mTargetType(RADAR_TARGET_NONE) // + , mCurrLedAmountShowing(0.3f) // + , mInPursuit(false) // + , mIsCoolingDown(false) // + , mTimeCycleStarted(0) +{ + mpDataRadarDetectorGroup = RegisterGroup(0x062743f5); + mpDataRadarDetectorLightsLeft = FEngFindObject(pkg_name, 0x69aa01e7); + mpDataRadarDetectorLightsRight = FEngFindObject(pkg_name, 0x9f59065a); + mpDataRadarDetectorArrow = FEngFindObject(pkg_name, FEHashUpper("Radar_DirectionArrow")); + mpDataRadarIcon = FEngFindObject(pkg_name, FEHashUpper("Radar_Icon")); + mpDataRadarDetectorBacking = RegisterObject(0x839e7d77); + mpDataRadarDetectorBackingWithMirror = RegisterObject(0x9ee06631); +} + +void RadarDetector::Update(IPlayer *player) { + if (eGetCurrentViewMode() == EVIEWMODE_ONE_RVM && FEDatabase->GetGameplaySettings()->RearviewOn) { + if (!FEngIsScriptSet(mpDataRadarDetectorBacking, 0x16a259)) { + FEngSetScript(mpDataRadarDetectorBacking, 0x16a259, true); + } + + if (!mInPursuit || mIsCoolingDown) { + if (!FEngIsScriptSet(mpDataRadarDetectorGroup, 0x1744b3)) { + FEngSetScript(mpDataRadarDetectorGroup, 0x1744b3, true); + } + } else { + if (!FEngIsScriptSet(mpDataRadarDetectorGroup, 0x16a259)) { + FEngSetScript(mpDataRadarDetectorGroup, 0x16a259, true); + } + } + + if (!FEngIsScriptSet(mpDataRadarDetectorBackingWithMirror, 0x5079c8f8)) { + FEngSetScript(mpDataRadarDetectorBackingWithMirror, 0x5079c8f8, true); + } + } else { + if (!FEngIsScriptSet(mpDataRadarDetectorBackingWithMirror, 0x16a259)) { + FEngSetScript(mpDataRadarDetectorBackingWithMirror, 0x16a259, true); + } + + if (!mInPursuit || mIsCoolingDown) { + if (!FEngIsScriptSet(mpDataRadarDetectorBacking, 0x5079c8f8)) { + FEngSetScript(mpDataRadarDetectorBacking, 0x5079c8f8, true); + } + if (!FEngIsScriptSet(mpDataRadarDetectorGroup, 0x5079c8f8)) { + FEngSetScript(mpDataRadarDetectorGroup, 0x5079c8f8, true); + } + } else { + if (FEngIsScriptSet(mpDataRadarDetectorBacking, 0x1744b3)) { + FEngSetScript(mpDataRadarDetectorBacking, 0x16a259, true); + } else if (!FEngIsScriptSet(mpDataRadarDetectorBacking, 0x033113ac)) { + if (!FEngIsScriptSet(mpDataRadarDetectorBacking, 0x16a259)) { + FEngSetScript(mpDataRadarDetectorBacking, 0x033113ac, true); + } + } + + if (FEngIsScriptSet(mpDataRadarDetectorGroup, 0x1744b3)) { + FEngSetScript(mpDataRadarDetectorGroup, 0x16a259, true); + } else if (!FEngIsScriptSet(mpDataRadarDetectorGroup, 0x033113ac)) { + if (!FEngIsScriptSet(mpDataRadarDetectorBacking, 0x16a259)) { + FEngSetScript(mpDataRadarDetectorGroup, 0x033113ac, true); + } + } + } + } + + if (mRange > 0.0f) { + if (mInPursuit && !mIsCoolingDown) { + goto low_range; + } + + const float max_range = TWK_RadarDetectorMinThreshold; + float range; + if (mRange > TWK_RadarDetectorMinThreshold) { + range = mRange; + } else { + range = TWK_RadarDetectorMinThreshold; + } + + if (!mTimeCycleStarted.IsSet()) { + mTimeCycleStarted = WorldTimer; + } + + mCurrLedAmountShowing += 0.1f; + if (mCurrLedAmountShowing > 1.0f) { + mCurrLedAmountShowing = 1.0f; + } + + if ((WorldTimer - mTimeCycleStarted).GetSeconds() > range * 1.5f) { + mTimeCycleStarted = WorldTimer; + mCurrLedAmountShowing = 0.3f; + MMiscSound sound(0); + sound.Send(UCrc32("Snd")); + } + + FEVector2 ledUVs(mCurrLedAmountShowing, 1.0f); + FEngSetMultiImageBottomRightUVs(static_cast(mpDataRadarDetectorLightsLeft), ledUVs, 0); + FEngSetMultiImageBottomRightUVs(static_cast(mpDataRadarDetectorLightsRight), ledUVs, 0); + + FEngSetRotationZ(mpDataRadarDetectorArrow, RAD2DEG(mDirection)); + + if (mTargetType == RADAR_TARGET_CAMERA) { + if (!FEngIsScriptSet(mpDataRadarDetectorArrow, 0xfa44e387)) { + FEngSetScript(mpDataRadarDetectorArrow, 0xfa44e387, true); + } + if (!FEngIsScriptSet(mpDataRadarIcon, 0xfa44e387)) { + FEngSetScript(mpDataRadarIcon, 0xfa44e387, true); + } + if (!FEngIsScriptSet(mpDataRadarDetectorLightsLeft, 0xfa44e387)) { + FEngSetScript(mpDataRadarDetectorLightsLeft, 0xfa44e387, true); + } + if (FEngIsScriptSet(mpDataRadarDetectorLightsRight, 0xfa44e387)) { + return; + } + FEngSetScript(mpDataRadarDetectorLightsRight, 0xfa44e387, true); + } else { + if (!FEngIsScriptSet(mpDataRadarDetectorArrow, 0x1744b3)) { + FEngSetScript(mpDataRadarDetectorArrow, 0x1744b3, true); + } + if (!FEngIsScriptSet(mpDataRadarIcon, 0x1744b3)) { + FEngSetScript(mpDataRadarIcon, 0x1744b3, true); + } + if (!FEngIsScriptSet(mpDataRadarDetectorLightsLeft, 0x1744b3)) { + FEngSetScript(mpDataRadarDetectorLightsLeft, 0x1744b3, true); + } + if (FEngIsScriptSet(mpDataRadarDetectorLightsRight, 0x1744b3)) { + return; + } + FEngSetScript(mpDataRadarDetectorLightsRight, 0x1744b3, true); + } + return; + } + +low_range: + { + if (mTimeCycleStarted.IsSet()) { + mTimeCycleStarted.UnSet(); + } + FEVector2 ledUVs(0.0f, 1.0f); + FEngSetMultiImageBottomRightUVs(static_cast(mpDataRadarDetectorLightsLeft), ledUVs, 0); + FEngSetMultiImageBottomRightUVs(static_cast(mpDataRadarDetectorLightsRight), ledUVs, 0); + } + + if (!FEngIsScriptSet(mpDataRadarDetectorArrow, 0x16a259)) { + FEngSetScript(mpDataRadarDetectorArrow, 0x16a259, true); + } + if (!FEngIsScriptSet(mpDataRadarIcon, 0x1744b3)) { + FEngSetScript(mpDataRadarIcon, 0x1744b3, true); + } +} + +void RadarDetector::SetInPursuit(bool inPursuit) { + mInPursuit = inPursuit; +} + +void RadarDetector::SetIsCoolingDown(bool coolingDown) { + mIsCoolingDown = coolingDown; +} + +void RadarDetector::SetTarget(RadarTarget targetType, float range, float direction) { + mTargetType = targetType; + mDirection = direction; + mRange = range; + mStaticRange = range; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeRadarDetector.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeRadarDetector.hpp index af1531427..4228bbc3d 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeRadarDetector.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeRadarDetector.hpp @@ -5,6 +5,36 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +class RadarDetector : public HudElement, public IRadarDetector { + public: + RadarDetector(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetInPursuit(bool inPursuit) override; + void SetIsCoolingDown(bool coolingDown) override; + void SetTarget(RadarTarget targetType, float range, float direction) override; + + private: + FEGroup * mpDataRadarDetectorGroup; + FEObject * mpDataRadarDetectorLightsLeft; + FEObject * mpDataRadarDetectorLightsRight; + FEObject * mpDataRadarDetectorArrow; + FEObject * mpDataRadarIcon; + FEObject * mpDataRadarDetectorBacking; + FEObject * mpDataRadarDetectorBackingWithMirror; + float mRange; + float mDirection; + RadarTarget mTargetType; + float mCurrLedAmountShowing; + bool mInPursuit; + bool mIsCoolingDown; + Timer mTimeCycleStarted; + + static float mStaticRange; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeReputation.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeReputation.cpp index e69de29bb..47a3f96ff 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeReputation.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeReputation.cpp @@ -0,0 +1,52 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeReputation.hpp" + +#include "Speed/Indep/Src/FEng/FEString.h" + +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +void FEngSetLanguageHash(FEString *text, unsigned int hash); +int FEPrintf(FEString *text, const char *fmt, ...); +FEString *FEngFindString(const char *pkg_name, int name_hash); + +extern const char lbl_803E48C8[]; + +Reputation::Reputation(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x1000) // + , IReputation(pOutter) // + , mReputationCareer(0) // + , mNumFramesLeftToShow(0) // +{ + mDataReputationGrp = RegisterGroup(0xEA903012); + FEngSetScript(mDataReputationGrp, 0x16A259, true); + mDataReputationCareer = FEngFindString(GetPackageName(), 0x9B0AC8CA); + mDataTitle = FEngFindString(GetPackageName(), 0x41A55ECF); +} + +void Reputation::Update(IPlayer *player) { + if (mDataReputationCareer == nullptr) { + return; + } + + if (mNumFramesLeftToShow >= 1) { + mNumFramesLeftToShow = mNumFramesLeftToShow - 1; + FEngSetLanguageHash(mDataTitle, 0x7D0171E4); + FEPrintf(mDataReputationCareer, lbl_803E48C8, mReputationCareer); + if (!FEngIsScriptSet(mDataReputationGrp, 0x5079C8F8)) { + FEngSetScript(mDataReputationGrp, 0x5079C8F8, true); + } + } else { + if (FEngIsScriptSet(mDataReputationGrp, 0x5079C8F8)) { + FEngSetScript(mDataReputationGrp, 0x33113AC, true); + } + } +} + +void Reputation::SetReputationCareer(int rep) { + if (mReputationCareer == rep) { + return; + } + mReputationCareer = rep; + mNumFramesLeftToShow = 0x78; +} + +void Reputation::SetReputationPursuit(int rep) {} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeReputation.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeReputation.hpp index 5cc49c2cf..3d922ab04 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeReputation.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeReputation.hpp @@ -5,6 +5,7 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" #include "Speed/Indep/Libs/Support/Utility/UCOM.h" class IReputation : public UTL::COM::IUnknown { @@ -23,4 +24,20 @@ class IReputation : public UTL::COM::IUnknown { virtual void SetReputationPursuit(int rep); }; +// total size: 0x48 +class Reputation : public HudElement, public IReputation { + public: + Reputation(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetReputationCareer(int rep) override; + void SetReputationPursuit(int rep) override; + + private: + int mReputationCareer; // offset 0x30 + int mNumFramesLeftToShow; // offset 0x34 + FEString *mDataReputationCareer; // offset 0x38 + FEString *mDataTitle; // offset 0x3C + FEObject *mDataReputationGrp; // offset 0x40 +}; + #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.cpp index e69de29bb..954ab3d92 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.cpp @@ -0,0 +1,136 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.hpp" + +extern void FEngSetVisible(FEObject *pObject); +extern void FEngSetInvisible(FEObject *pObject); +extern void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +extern bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +extern void FEngSetLanguageHash(FEString *obj, unsigned int hash); +extern unsigned long FEHashUpper(const char *String); +extern float warningPulseMinRpm; + +ShiftUpdater::ShiftUpdater(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x20000000) // + , IShiftUpdater(pOutter) // + , mGear(G_NEUTRAL) // + , mShiftPotential(SHIFT_POTENTIAL_NONE) // + , mGearChanged(0) // + , mLastShiftStatus(SHIFT_STATUS_NONE) // + , mIsEngineBlown(false) // + , mEngineTemp(0.0f) +{ + pShiftIndicator = RegisterImage(FEHashUpper("Shift_light")); + pShiftIndicatorOverheatGroup = RegisterGroup(FEHashUpper("ENGINE_HEAT_SHIFTLIGHT_GROUP")); + pShiftMsgGroup = RegisterGroup(FEHashUpper("SHIFT_GROUP")); + pShiftMsg = static_cast(FEngFindObject(pkg_name, FEHashUpper("ShiftMessage"))); + pShiftMsgShadow = static_cast(FEngFindObject(pkg_name, FEHashUpper("ShiftMessage_Shadow"))); +} + +void ShiftUpdater::Update(IPlayer *player) { + ShiftPotential potential = static_cast(mShiftPotential); + if (potential < SHIFT_POTENTIAL_GOOD || mIsEngineBlown) { + FEngSetInvisible(pShiftIndicator); + FEngSetInvisible(pShiftIndicatorOverheatGroup); + } else if (potential == SHIFT_POTENTIAL_GOOD) { + const char *scriptName = "PulseBlue"; + FEObject *obj = pShiftIndicator; + unsigned long hash = FEHashUpper(scriptName); + if (!FEngIsScriptSet(obj, hash)) { + FEngSetScript(pShiftIndicator, FEHashUpper(scriptName), true); + } + FEngSetVisible(pShiftIndicator); + FEngSetInvisible(pShiftIndicatorOverheatGroup); + } else if (potential == SHIFT_POTENTIAL_PERFECT) { + const char *scriptName = "PulseGreen"; + FEObject *obj = pShiftIndicator; + unsigned long hash = FEHashUpper(scriptName); + if (!FEngIsScriptSet(obj, hash)) { + FEngSetScript(pShiftIndicator, FEHashUpper(scriptName), true); + } + FEngSetVisible(pShiftIndicator); + FEngSetInvisible(pShiftIndicatorOverheatGroup); + } else if (potential == SHIFT_POTENTIAL_MISS) { + if (mEngineTemp <= warningPulseMinRpm) { + const char *scriptName = "PulseRed"; + FEObject *obj = pShiftIndicator; + unsigned long hash = FEHashUpper(scriptName); + if (!FEngIsScriptSet(obj, hash)) { + FEngSetScript(pShiftIndicator, FEHashUpper(scriptName), true); + } + FEngSetVisible(pShiftIndicator); + FEngSetInvisible(pShiftIndicatorOverheatGroup); + } else { + unsigned long hash = FEHashUpper("OVERHEAT_PULSE"); + if (!FEngIsScriptSet(pShiftIndicatorOverheatGroup, hash)) { + FEngSetScript(pShiftIndicatorOverheatGroup, FEHashUpper("OVERHEAT_PULSE"), true); + } + FEngSetInvisible(pShiftIndicator); + FEngSetVisible(pShiftIndicatorOverheatGroup); + } + } + + if (mGear < G_SECOND) { + return; + } + if (mGearChanged < 1) { + return; + } + ShiftStatus status = static_cast(mLastShiftStatus); + if (status < SHIFT_STATUS_NORMAL) { + return; + } + if (mIsEngineBlown) { + return; + } + + if (status == SHIFT_STATUS_NORMAL) { + FEngSetLanguageHash(pShiftMsg, 0x2202b5b9); + FEngSetLanguageHash(pShiftMsgShadow, 0x2202b5b9); + FEngSetScript(pShiftMsgGroup, FEHashUpper("EARLY"), true); + } else if (status == SHIFT_STATUS_GOOD) { + FEngSetLanguageHash(pShiftMsg, 0x27d2dd45); + FEngSetLanguageHash(pShiftMsgShadow, 0x27d2dd45); + FEngSetScript(pShiftMsgGroup, FEHashUpper("GOOD"), true); + } else if (status == SHIFT_STATUS_PERFECT) { + FEngSetLanguageHash(pShiftMsg, 0x598b065); + FEngSetLanguageHash(pShiftMsgShadow, 0x598b065); + FEngSetScript(pShiftMsgGroup, FEHashUpper("PERFECT"), true); + } else if (status == SHIFT_STATUS_MISSED) { + FEngSetLanguageHash(pShiftMsg, 0xdf61b3e5); + FEngSetLanguageHash(pShiftMsgShadow, 0xdf61b3e5); + FEngSetScript(pShiftMsgGroup, FEHashUpper("OVERREV"), true); + } + + FEngSetInvisible(pShiftIndicator); + mGearChanged = 0; +} + +void ShiftUpdater::SetGear(GearID gear, ShiftStatus status, ShiftPotential potential, bool hasGoodEnoughTraction) { + if (gear != mGear) { + int dir = -1; + if (gear > mGear) { + dir = 1; + } + mGear = gear; + mGearChanged = dir; + mShiftPotential = SHIFT_POTENTIAL_NONE; + if (hasGoodEnoughTraction) { + mLastShiftStatus = status; + } else { + mLastShiftStatus = SHIFT_STATUS_NORMAL; + } + return; + } + if (hasGoodEnoughTraction) { + mShiftPotential = potential; + } else { + mShiftPotential = SHIFT_POTENTIAL_NONE; + } +} + +void ShiftUpdater::SetEngineBlown(bool blown) { + mIsEngineBlown = blown; +} + +void ShiftUpdater::SetEngineTemp(float temp) { + mEngineTemp = temp; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.hpp index bbbc26ee4..44b0c56f5 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeShiftUpdater.hpp @@ -5,6 +5,31 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +class ShiftUpdater : public HudElement, public IShiftUpdater { + public: + ShiftUpdater(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetGear(GearID gear, ShiftStatus status, ShiftPotential potential, bool hasGoodEnoughTraction) override; + void SetEngineBlown(bool blown) override; + void SetEngineTemp(float temp) override; + + private: + FEImage * pShiftIndicator; + FEGroup * pShiftIndicatorOverheatGroup; + FEGroup * pShiftMsgGroup; + FEString * pShiftMsg; + FEString * pShiftMsgShadow; + GearID mGear; + ShiftPotential mShiftPotential; + int mGearChanged; + ShiftStatus mLastShiftStatus; + bool mIsEngineBlown; + float mEngineTemp; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeSpeedBreakerMeter.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeSpeedBreakerMeter.hpp index 1b8bebcde..2bd8dbbff 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeSpeedBreakerMeter.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeSpeedBreakerMeter.hpp @@ -5,6 +5,42 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +struct FEGroup; +struct FEMultiImage; + +class ISpeedBreakerMeter : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + ISpeedBreakerMeter(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + protected: + virtual ~ISpeedBreakerMeter() {} + + public: + virtual void SetPursuitLevel(float level); +}; + +// total size: 0x50 +class SpeedBreakerMeter : public HudElement, public ISpeedBreakerMeter { + public: + SpeedBreakerMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetPursuitLevel(float level) override; + + private: + float mPursuitLevel; // offset 0x30 + bool mPursuitLevelChanged; // offset 0x34 + FEObject *mpSpeedBreakerMeterIcon; // offset 0x38 + FEMultiImage *mpSpeedBreakerMeterBar; // offset 0x3C + FEGroup *mpSpeedBreakerGroup; // offset 0x40 + FEObject *mpSpeedBreakerBar; // offset 0x44 + float mSpeedBreakerBarOriginalWidth; // offset 0x48 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeSpeedometer.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeSpeedometer.cpp index e69de29bb..7a2eb1a7f 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeSpeedometer.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeSpeedometer.cpp @@ -0,0 +1,72 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeSpeedometer.hpp" + +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +unsigned long FEHashUpper(const char *name); +int FEPrintf(FEString *text, const char *fmt, ...); +void FEngSetVisible(FEObject *obj); +void FEngSetInvisible(FEObject *obj); + +extern cFrontendDatabase *FEDatabase; +extern const char *GetTranslatedString(unsigned int hash); + +extern const char lbl_803E4D20[]; +extern const char lbl_803E4E24[]; +extern const char lbl_803E4E38[]; +extern const char lbl_803E4E48[]; +extern const char lbl_803E4E58[]; +extern const char lbl_803E4E68[]; + +Speedometer::Speedometer(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x8000000) // + , ISpeedometer(pOutter) // + , mSpeed(0.0f) // +{ + RegisterGroup(FEHashUpper(lbl_803E4E24)); + mpSpeedDigit1 = static_cast(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4E38))); + mpSpeedDigit2 = static_cast(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4E48))); + mpSpeedDigit3 = static_cast(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4E58))); + SpeedUnits = static_cast(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4E68))); +} + +void Speedometer::Update(IPlayer *player) { + float speed = bAbs(mSpeed); + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + speed = MPS2KPH(speed); + FEPrintf(SpeedUnits, GetTranslatedString(0x8569a25f)); + } else { + speed = MPS2MPH(speed); + FEPrintf(SpeedUnits, GetTranslatedString(0x8569ab44)); + } + + int digit3 = static_cast(speed * 0.01f); + int digit2 = static_cast((speed - static_cast(digit3) * 100.0f) * 0.1f); + int digit1 = static_cast(speed - static_cast(digit3) * 100.0f - static_cast(digit2) * 10.0f); + + FEPrintf(mpSpeedDigit3, "%d", digit3); + FEPrintf(mpSpeedDigit2, "%d", digit2); + FEPrintf(mpSpeedDigit1, "%d", digit1); + + if (digit3 > 0) { + FEngSetVisible(mpSpeedDigit3); + FEngSetVisible(mpSpeedDigit2); + FEngSetVisible(mpSpeedDigit1); + } else if (digit2 > 0) { + FEngSetInvisible(mpSpeedDigit3); + FEngSetVisible(mpSpeedDigit2); + FEngSetVisible(mpSpeedDigit1); + } else { + FEngSetInvisible(mpSpeedDigit3); + FEngSetInvisible(mpSpeedDigit2); + FEngSetVisible(mpSpeedDigit1); + } +} + +void Speedometer::SetSpeed(float speed) { + mSpeed = speed; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeSpeedometer.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeSpeedometer.hpp index bda07e604..6c5601e47 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeSpeedometer.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeSpeedometer.hpp @@ -5,6 +5,36 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +class ISpeedometer : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + ISpeedometer(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual void SetSpeed(float speed); + + protected: + virtual ~ISpeedometer() {} +}; + +// total size: 0x48 +class Speedometer : public HudElement, public ISpeedometer { + public: + Speedometer(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetSpeed(float speed) override; + + private: + FEString *mpSpeedDigit1; // offset 0x30 + FEString *mpSpeedDigit2; // offset 0x34 + FEString *mpSpeedDigit3; // offset 0x38 + FEString *SpeedUnits; // offset 0x3C + float mSpeed; // offset 0x40 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeTachometer.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeTachometer.cpp index e69de29bb..cee56c958 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeTachometer.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeTachometer.cpp @@ -0,0 +1,205 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeTachometer.hpp" + +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/Sim/Simulation.h" + +void FEngSetMultiImageRot(FEMultiImage *image, float angle_degrees); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +unsigned long FEHashUpper(const char *name); +int FEPrintf(FEString *text, const char *fmt, ...); +void FEngSetVisible(FEObject *obj); +void FEngSetInvisible(FEObject *obj); +void FEngSetRotationZ(FEObject *obj, float angle); +void FEngGetTopLeft(FEObject *obj, float &x, float &y); +void FEngGetSize(FEObject *obj, float &w, float &h); +void FEngSetSize(FEObject *obj, float w, float h); +void FEngSetTopLeft(FEObject *obj, float x, float y); +void FEngSetColor(FEObject *obj, unsigned int color); +FEColor FEngGetObjectColor(FEObject *obj); +int bStrICmp(const char *s1, const char *s2); + +extern const char lbl_803E4D20[]; +extern const char lbl_803E4EB8[]; +extern const char lbl_803E4EC8[]; +extern const char lbl_803E4ED8[]; +extern const char lbl_803E4EE4[]; +extern const char lbl_803E4EF0[]; +extern const char lbl_803E4F00[]; +extern const float lbl_803E4F10; +extern const char lbl_803E4F14[]; + +float ChooseMaxRpmTextureNumber(float redline); + +extern const float lbl_803E4EA4; +extern const float lbl_803E4EA8; +extern const float lbl_803E4EAC; +extern const float lbl_803E4EB0; +extern const float lbl_803E4EB4; + +static float CalcAngleForRPM(float rpm, float redline) { + float factor = rpm / ChooseMaxRpmTextureNumber(redline); + if (factor < lbl_803E4EA4) { + factor = lbl_803E4EA4; + } + if (factor > lbl_803E4EA8) { + factor = lbl_803E4EA8; + } + float min_angle = lbl_803E4EAC; + float fRange = lbl_803E4EB0 - lbl_803E4EAC; + float max_angle = lbl_803E4EB4; + float angle = factor * fRange + min_angle; + if (angle > max_angle) { + angle = angle - max_angle; + } + if (angle < lbl_803E4EA4) { + angle = max_angle - angle; + } + return angle; +} + +Tachometer::Tachometer(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 2) // + , ITachometer(pOutter) // + , mRpm(lbl_803E4F10) // + , mRedline(lbl_803E4F10) // + , mMaxRpm(lbl_803E4F10) // + , mGear(G_NEUTRAL) // + , mIsShifting(false) // + , mInPerfectLaunchRange(false) // + , mShiftPotential(SHIFT_POTENTIAL_NONE) // + , mNeedleColourSetToPerfectLaunch(false) // +{ + RegisterGroup(FEHashUpper(lbl_803E4EB8)); + TachNeedle = FEngFindObject(pkg_name, FEHashUpper(lbl_803E4EC8)); + pRedline = FEngFindObject(pkg_name, FEHashUpper(lbl_803E4ED8)); + pShiftIndicator = FEngFindObject(pkg_name, FEHashUpper(lbl_803E4EE4)); + pRPM_bar = FEngFindObject(pkg_name, FEHashUpper(lbl_803E4EF0)); + pGearString = static_cast< FEString * >(FEngFindObject(pkg_name, FEHashUpper(lbl_803E4F00))); + RegisterGroup(0x045E9562); + PerfectShiftDetectedTimer.ResetLow(); + MissedShiftTimer.ResetLow(); + mOriginalNeedleWidth = TachNeedle->GetObjData()->Size.x; +} + +ITachometer::~ITachometer() {} + +void Tachometer::Update(IPlayer *player) { + if (Sim::GetUserMode() == 1) { + float topX, topY; + FEngGetTopLeft(TachNeedle, topX, topY); + float needleWidth = mRpm / mMaxRpm; + float sizeW, sizeH; + needleWidth = needleWidth * mOriginalNeedleWidth; + FEngGetSize(TachNeedle, sizeW, sizeH); + FEngSetSize(TachNeedle, needleWidth, sizeH); + float topX2, topY2; + FEngGetTopLeft(TachNeedle, topX2, topY2); + FEngSetTopLeft(TachNeedle, topX, topY2); + if (mRpm >= mRedline) { + FEngSetScript(TachNeedle, 0x61D30442, true); + } else { + FEngSetScript(TachNeedle, 0x001744B3, true); + } + } else { + FEngSetRotationZ(TachNeedle, CalcAngleForRPM(mRpm, mMaxRpm)); + } + + if (pGearString) { + FEPrintf(pGearString, lbl_803E4F14, GetLetterForGear(mGear)); + + if (Sim::GetUserMode() != 1) { + FEColor normalColor(0xFF000000); + FEColor redColor(0x88000000); + + if (mIsShifting) { + FEngSetColor(pGearString, static_cast(redColor)); + } else { + FEngSetColor(pGearString, static_cast(normalColor)); + } + } + } + + if (mShiftPotential > 1) { + if (!FEngIsScriptSet(pShiftIndicator, 0x02DDC8F0)) { + FEngSetScript(pShiftIndicator, 0x02DDC8F0, true); + } + } else { + if (!FEngIsScriptSet(pShiftIndicator, 0x001744B3)) { + FEngSetScript(pShiftIndicator, 0x001744B3, true); + } + } + + if (mInPerfectLaunchRange) { + if (!mNeedleColourSetToPerfectLaunch) { + mNeedleColourSetToPerfectLaunch = true; + FEColor col = FEngGetObjectColor(TachNeedle); + FEngSetColor(TachNeedle, (~static_cast(col)) | 0xFF000000); + } + } else { + if (mNeedleColourSetToPerfectLaunch) { + mNeedleColourSetToPerfectLaunch = false; + FEColor col = FEngGetObjectColor(TachNeedle); + FEngSetColor(TachNeedle, (~static_cast(col)) | 0xFF000000); + } + } +} + +void Tachometer::SetRpm(float rpm) { + mRpm = rpm; +} + +void Tachometer::SetShifting(bool shifting) { + mIsShifting = shifting; +} + +void Tachometer::SetInPerfectLaunchRange(bool inRange) { + mInPerfectLaunchRange = inRange; +} + +char Tachometer::GetLetterForGear(GearID gear) { + if (gear == G_FIRST) { + return '1'; + } + if (gear == G_SECOND) { + return '2'; + } + if (gear == G_THIRD) { + return '3'; + } + if (gear == G_FOURTH) { + return '4'; + } + if (gear == G_FIFTH) { + return '5'; + } + if (gear == G_SIXTH) { + return '6'; + } + if (gear == G_SEVENTH) { + return '7'; + } + if (gear == G_EIGHTH) { + return '8'; + } + if (gear == G_REVERSE) { + return 'R'; + } + return 'N'; +} + +void Tachometer::SetGear(GearID gear, ShiftPotential potential, bool hasGoodEnoughTraction) { + if (gear != mGear) { + mGear = gear; + mShiftPotential = static_cast(0); + return; + } + if (hasGoodEnoughTraction) { + mShiftPotential = potential; + return; + } + mShiftPotential = static_cast(0); +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeTachometer.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeTachometer.hpp index cf76c5284..2e779837e 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeTachometer.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeTachometer.hpp @@ -5,6 +5,64 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Src/Physics/PhysicsTypes.h" +struct FEMultiImage; + +class ITachometer : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + ITachometer(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + virtual void SetRpm(float rpm); + virtual void SetRevLimiter(float redline, float maxRpm); + virtual void SetGear(GearID gear, ShiftPotential potential, bool hasGoodEnoughTraction); + virtual void SetShifting(bool shifting); + virtual void SetInPerfectLaunchRange(bool inRange); + + protected: + virtual ~ITachometer(); +}; + +// total size: 0x70 +class Tachometer : public HudElement, public ITachometer { + public: + Tachometer(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetRpm(float rpm) override; + void SetRevLimiter(float redline, float maxRpm) override { + mRedline = redline; + mMaxRpm = maxRpm; + } + void SetShifting(bool shifting) override; + void SetInPerfectLaunchRange(bool inRange) override; + void SetGear(GearID gear, ShiftPotential potential, bool hasGoodEnoughTraction) override; + + static char GetLetterForGear(GearID gear); + + private: + Timer PerfectShiftDetectedTimer; // offset 0x30 + Timer MissedShiftTimer; // offset 0x34 + FEObject *TachNeedle; // offset 0x38 + FEObject *pRPM_bar; // offset 0x3C + FEString *pGearString; // offset 0x40 + FEObject *pShiftIndicator; // offset 0x44 + FEObject *pRedline; // offset 0x48 + float mRpm; // offset 0x4C + float mRedline; // offset 0x50 + float mMaxRpm; // offset 0x54 + GearID mGear; // offset 0x58 + bool mIsShifting; // offset 0x5C + bool mInPerfectLaunchRange; // offset 0x60 + ShiftPotential mShiftPotential; // offset 0x64 + bool mNeedleColourSetToPerfectLaunch; // offset 0x68 + float mOriginalNeedleWidth; // offset 0x6C +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeTimeExtension.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeTimeExtension.cpp index e69de29bb..3e30141d8 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeTimeExtension.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeTimeExtension.cpp @@ -0,0 +1,129 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeTimeExtension.hpp" + +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" + +int bSPrintf(char *dest, const char *fmt, ...); +extern char *GetTranslatedString(int hash); +unsigned int bStringHash(const char *str); + +extern const char lbl_803E5048[]; +extern const char lbl_803E48E0[]; +extern const char lbl_803E4CF4[]; +extern const char lbl_803E5060[]; + +TimeExtension::TimeExtension(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x2000000) // + , ITimeExtension(pOutter) // + , mPlayerLapTime(0.0f) // + , mTimeToShow(0.0f) // + , mScriptHash(0) +{ + mTimerTimeExtension.ResetLow(); + mTimerNextTollbooth.ResetLow(); +} + +void TimeExtension::Update(IPlayer *player) { + if (GRaceStatus::Get().GetRaceType() != 4) + return; + + if (mTimeToShow > 0.0f) { + IHud *hud = player->GetHud(); + IGenericMessage *igenericmessage; + if (hud->QueryInterface(&igenericmessage) && !igenericmessage->IsGenericMessageShowing()) { + mTimerTimeExtension = WorldTimer; + char messageString[32]; + char timeToPrint[16]; + Timer timer(GRaceStatus::Get().GetRaceTimeRemaining() - mTimeToShow); + timer.PrintToString(timeToPrint, 4); + bSPrintf(messageString, lbl_803E5048, GetTranslatedString(0x1c074e14), timeToPrint); + igenericmessage->RequestGenericMessage( + messageString, false, 0x8ab83edb, bStringHash(lbl_803E5060), 0x609f6b15, + GenericMessage_Priority_3); + mTimeToShow = 0.0f; + } + return; + } + + if (mTimerTimeExtension.IsSet()) { + if ((WorldTimer - mTimerTimeExtension).GetSeconds() >= 2.0f) { + mTimerTimeExtension.UnSet(); + mTimerNextTollbooth = WorldTimer; + return; + } + } + + if (mPlayerLapTime <= 0.0f) + return; + + if (mPlayerLapTime > 10.0f) { + if (mScriptHash == 0x821e6378 || mScriptHash == 0x4f79cba2) { + mScriptHash = 0; + } + } + + if (mPlayerLapTime <= 10.0f) { + char messageString[32]; + char timeToPrint[16]; + Timer timer(mPlayerLapTime); + timer.PrintToString(timeToPrint, 4); + bSPrintf(messageString, lbl_803E48E0, GetTranslatedString(0x862a0519), timeToPrint); + + if (mPlayerLapTime <= 5.0f) { + if (mScriptHash == 0 || mScriptHash == 0x821e6378) { + mScriptHash = 0x4f79cba2; + } + IHud *hud = player->GetHud(); + IGenericMessage *igenericmessage; + if (hud->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + messageString, true, mScriptHash, 0, 0, GenericMessage_Priority_5); + } + } else if (mPlayerLapTime <= 10.0f) { + if (!mScriptHash) { + mScriptHash = 0x821e6378; + } + IHud *hud = player->GetHud(); + IGenericMessage *igenericmessage; + if (hud->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + messageString, true, mScriptHash, 0, 0, GenericMessage_Priority_5); + } + } + } else { + if (mTimerNextTollbooth.IsSet()) { + if ((WorldTimer - mTimerNextTollbooth).GetSeconds() >= 2.0f) { + mScriptHash = 0; + mTimerNextTollbooth.UnSet(); + return; + } + mScriptHash = 0x8ab83edb; + char messageString[32]; + char timeToPrint[16]; + Timer timer(GRaceStatus::Get().GetRaceTimeRemaining()); + timer.PrintToString(timeToPrint, 4); + bSPrintf(messageString, lbl_803E4CF4, GetTranslatedString(0x171471b4), timeToPrint); + IHud *hud = player->GetHud(); + IGenericMessage *igenericmessage; + if (hud->QueryInterface(&igenericmessage)) { + igenericmessage->RequestGenericMessage( + messageString, true, mScriptHash, bStringHash(lbl_803E5060), 0x609f6b15, + GenericMessage_Priority_3); + } + } + mScriptHash = 0; + } +} + +void TimeExtension::SetPlayerLapTime(float lapTime) { + mPlayerLapTime = lapTime; +} + +void TimeExtension::RequestTimeExtensionMessage(IPlayer *iplayer, float timeToShow) { + IGenericMessage *igenericmessage; + if (iplayer->GetHud()->QueryInterface(&igenericmessage) && igenericmessage->IsGenericMessageShowing()) { + igenericmessage->RequestGenericMessageZoomOut(0xE1C034FC); + } + mTimeToShow = timeToShow; + mScriptHash = 0; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeTimeExtension.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeTimeExtension.hpp index d7a296dff..a6e0ac8c7 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeTimeExtension.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeTimeExtension.hpp @@ -5,6 +5,24 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +class TimeExtension : public HudElement, public ITimeExtension { + public: + TimeExtension(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetPlayerLapTime(float lapTime) override; + void RequestTimeExtensionMessage(IPlayer *iplayer, float timeToShow) override; + + private: + bool mShowingCountdown; + float mPlayerLapTime; + float mTimeToShow; + int mScriptHash; + Timer mTimerTimeExtension; + Timer mTimerNextTollbooth; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeTurboMeter.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeTurboMeter.cpp index e69de29bb..9e357d9eb 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeTurboMeter.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeTurboMeter.cpp @@ -0,0 +1,44 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeTurboMeter.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" + +extern void FEngSetRotationZ(FEObject *object, float angle); + +TurboMeter::TurboMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x80020000) // + , ITurbometer(pOutter) // + , mUpdated(true) // + , mInductionPsi(0.0f) +{ + pTurboGroup = RegisterGroup(FEHashUpper("TURBO_GROUP")); + pTurboNeedle = FEngFindObject(GetPackageName(), FEHashUpper("3rdperson_TurboDial")); + pTurboDialLines = FEngFindObject(GetPackageName(), FEHashUpper("TURBO_LINES")); +} + +void TurboMeter::Update(IPlayer *player) { + if (!mUpdated) { + return; + } + mUpdated = false; + float angle; + if (GRaceStatus::Exists() && GRaceStatus::Get().GetRaceType() == GRace::kRaceType_Drag) { + angle = CalcNeedleAngle(mInductionPsi, 200.0f, 270.0f); + } else { + angle = CalcNeedleAngle(mInductionPsi, -45.0f, 45.0f); + } + FEngSetRotationZ(pTurboNeedle, angle); +} + +float TurboMeter::CalcNeedleAngle(float psi, float min_angle, float max_angle) { + const float cMinPsi = -20.0f; + const float cMaxPsi = 20.0f; + float boost = (psi - cMinPsi) / (cMaxPsi - cMinPsi); + float angle = -(min_angle + boost * (max_angle - min_angle)); + return angle; +} + +void TurboMeter::SetInductionPsi(float psi) { + if (mInductionPsi != psi) { + mInductionPsi = psi; + mUpdated = true; + } +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeTurboMeter.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeTurboMeter.hpp index 0cc5d7161..f2d2b4dc7 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeTurboMeter.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeTurboMeter.hpp @@ -5,6 +5,23 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/Interfaces/IFengHud.h" +#include "Speed/Indep/Src/FEng/FEGroup.h" +class TurboMeter : public HudElement, public ITurbometer { + public: + TurboMeter(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetInductionPsi(float psi) override; + float CalcNeedleAngle(float psi, float min_angle, float max_angle); + + private: + bool mUpdated; + float mInductionPsi; + FEGroup * pTurboGroup; + FEObject * pTurboDialLines; + FEObject * pTurboNeedle; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.cpp b/src/Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.cpp index e69de29bb..db37f80cd 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.cpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.cpp @@ -0,0 +1,67 @@ +#include "Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.hpp" + +#include "Speed/Indep/Src/FEng/FEImage.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" + +void FEngSetVisible(FEObject *obj); +void FEngSetInvisible(FEObject *obj); +unsigned long FEHashUpper(const char *name); +void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); + +extern const char lbl_803E4F18[]; +extern const float lbl_803E4F28; + +WrongWIndi::WrongWIndi(UTL::COM::Object *pOutter, const char *pkg_name, int player_number) + : HudElement(pkg_name, 0x20) // + , IWrongWay(pOutter) // + , mIsWrongWay(false) // + , mTimeBeforeDisplaying(0) // + , mTimeBeforeClosing(0) // +{ + mpWrongWayImage = RegisterImage(FEHashUpper(lbl_803E4F18)); +} + +void WrongWIndi::Update(IPlayer *player) { + if (mIsWrongWay) { + if (mTimeBeforeDisplaying.IsSet()) { + Timer diff = WorldTimer - mTimeBeforeDisplaying; + if (diff.GetSeconds() >= 2.0f) { + mTimeBeforeDisplaying.UnSet(); + if (FEDatabase->GetVideoSettings()->WideScreen) { + if (!FEngIsScriptSet(mpWrongWayImage, 0x908e787e)) { + FEngSetScript(mpWrongWayImage, 0x908e787e, true); + } + } else { + if (!FEngIsScriptSet(mpWrongWayImage, 0x47510b1e)) { + FEngSetScript(mpWrongWayImage, 0x47510b1e, true); + } + } + } + } + } else { + if (mTimeBeforeClosing.IsSet()) { + Timer diff = WorldTimer - mTimeBeforeClosing; + if (diff.GetSeconds() >= 2.0f) { + mTimeBeforeClosing.UnSet(); + if (!FEngIsScriptSet(mpWrongWayImage, 0x16a259)) { + FEngSetScript(mpWrongWayImage, 0x16a259, true); + } + } + } + } +} + +void WrongWIndi::SetWrongWay(bool isWrongWay) { + if (mIsWrongWay == isWrongWay) { + return; + } + if (isWrongWay) { + mTimeBeforeDisplaying = WorldTimer; + mTimeBeforeClosing.UnSet(); + } else { + mTimeBeforeClosing = WorldTimer; + mTimeBeforeDisplaying.UnSet(); + } + mIsWrongWay = isWrongWay; +} diff --git a/src/Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.hpp b/src/Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.hpp index 6d060a4d2..1703a64dc 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/FeWrongWIndi.hpp @@ -5,6 +5,37 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +class IWrongWay : public UTL::COM::IUnknown { + public: + static HINTERFACE _IHandle() { + return (HINTERFACE)_IHandle; + } + + IWrongWay(UTL::COM::Object *owner) : UTL::COM::IUnknown(owner, _IHandle()) {} + + protected: + virtual ~IWrongWay() {} + + public: + virtual void SetWrongWay(bool isWrongWay); +}; + +// total size: 0x40 +class WrongWIndi : public HudElement, public IWrongWay { + public: + WrongWIndi(UTL::COM::Object *pOutter, const char *pkg_name, int player_number); + void Update(IPlayer *player) override; + void SetWrongWay(bool isWrongWay) override; + + private: + FEImage *mpWrongWayImage; // offset 0x30 + bool mIsWrongWay; // offset 0x34 + Timer mTimeBeforeDisplaying; // offset 0x38 + Timer mTimeBeforeClosing; // offset 0x3C +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/HUD/feMinimap.hpp b/src/Speed/Indep/Src/Frontend/HUD/feMinimap.hpp index daf1f3e4f..3c2deae8b 100644 --- a/src/Speed/Indep/Src/Frontend/HUD/feMinimap.hpp +++ b/src/Speed/Indep/Src/Frontend/HUD/feMinimap.hpp @@ -5,6 +5,85 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/HUD/FeHudElement.hpp" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +struct TrackInfo; +struct IVehicle; +struct GIcon; + +struct COORD2 { + float x; + float y; +}; + +struct MiniMapItem : public bTNode { + FEImage *mpIcon; + bVector2 mPos; + unsigned int mItemType; + bool mHidden; + + static void operator delete(void *mem, unsigned int size) { + gFastMem.Free(mem, size, nullptr); + } +}; + +class Minimap : public HudElement { + public: + Minimap(const char *pkg_name, int player_number); + ~Minimap(); + void Update(IPlayer *player) override; + void SetupMinimap(IPlayer *player); + void RefreshMapItems(); + static void ConvertPos(bVector2 &worldPos, bVector2 &minimapPos, TrackInfo *track); + void UpdateTrackMapArt(); + void UpdateElementArt(bVector2 *pos, bVector2 *dir, FEObject *element, bool visible); + void UpdateCopElements(IVehicle *vehicle); + void UpdateAiRacerElements(); + void UpdatePlayer2Element(); + void UpdateIconElement(FEImage *icon, GIcon *gicon); + void UpdateRaceElements(); + void UpdateMiniMapItems(); + void UpdateGameplayIcons(IPlayer *player); + void AdjustForWidescreen(bool widescreen); + void InitStaticMiniMapItems(); + + struct GameplayIconInfo { + int mIconType; + int mItemType; + const char *mElementString; + unsigned int mWorldMapTitle; + unsigned int mworldIconTexHash; + }; + static GameplayIconInfo kGameplayIconInfo[]; + static GameplayIconInfo &GetGameplayIconInfo(int iconType); + static GameplayIconInfo &GetGameplayIconInfoByItemType(int itemType); + + private: + bTList StaticMiniMapItems; + FEObject *TrackmapLayout; + FEMultiImage *TrackmapArt[4]; + FEVector2 TrackmapArtUVs[4][2]; + FEImage *TrackmapNorth; + FEImage *mPlayerCarIndicator; + FEImage *mPlayerCarIndicator2; + TrackInfo *CurrentTrack; + FEVector3 mMapDefaultPos; + float mSpeedZoomScale; + float mPolyRotation; + bVector2 mTrackTargetNormalized; + COORD2 mTrackMapCentre; + int mCopFlashCounter; + int MinimapRotateWithPlayer; + FEObject *mHeliElementArt; + FEObject *mHeliLineOfSiteArt; + FEImage *mCopElementArt[8]; + FEImage *mRacerElementArt[8]; + FEImage *mCheckpointElementArt; + FEImage *mGPSSelectionElementArt; + FEImage *mGameplayIcons[17][8]; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/Localization/Localize.cpp b/src/Speed/Indep/Src/Frontend/Localization/Localize.cpp index e69de29bb..85bf2d8ff 100644 --- a/src/Speed/Indep/Src/Frontend/Localization/Localize.cpp +++ b/src/Speed/Indep/Src/Frontend/Localization/Localize.cpp @@ -0,0 +1,408 @@ +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/Src/FEng/FEWideString.h" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bChunk.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" + +extern void GC_GetOSLanguage(); +extern const char *GetLocalizedString(unsigned int id); +extern void bPrintfSetLocaleInfo(char decimal, char group, char group_len); +extern void LoadLanguageResources(bool load_global, bool load_frontend, bool load_ingame, bool blocking); + +struct FontNameInfo { + unsigned int GlobalFonts[8]; // offset 0x0 + unsigned int InGameFonts[8]; // offset 0x20 + unsigned int FrontendFonts[8]; // offset 0x40 + int GlobalFontsLoaded; // offset 0x60 + int InGameFontsLoaded; // offset 0x64 + int FrontendFontsLoaded; // offset 0x68 +}; +struct LanguageChunkHeader { + int HistogramTablePos; // offset 0x0, size 0x4 + int NumStringRecords; // offset 0x4, size 0x4 + int StringRecordTablePos; // offset 0x8, size 0x4 + int StringTablePos; // offset 0xC, size 0x4 + + void PlatEndianSwap() { + bPlatEndianSwap(&HistogramTablePos); + bPlatEndianSwap(&NumStringRecords); + bPlatEndianSwap(&StringRecordTablePos); + bPlatEndianSwap(&StringTablePos); + } +}; + +struct WideCharHistogram { + void PackString(char *packed, int size, const unsigned short *wide); + void UnpackString(unsigned short *wide, int size, const char *packed); + void PlatEndianSwap(); + + protected: + int NumEntries; // offset 0x0, size 0x4 + unsigned short EntryTable[3072]; // offset 0x4, size 0x1800 +}; + +extern WideCharHistogram *pWideCharHistogram; +extern void bStrCpy(unsigned short *dst, const char *src); + +struct LanguageInfo { + eLanguages Language; // offset 0x0, size 0x4 + char *Name; // offset 0x4, size 0x4 + char *Filename; // offset 0x8, size 0x4 + char *FilenameTextOnly; // offset 0xC, size 0x4 + FontNameInfo *pFontNameInfo; // offset 0x10, size 0x4 + bPrintfLocaleInfo *pbPrintfLocaleInfo; // offset 0x14, size 0x4 +}; + +extern LanguageInfo LanguageInfoTable[]; + +struct StringRecord { + unsigned int Hash; // offset 0x0, size 0x4 + unsigned char *PackedString; // offset 0x4, size 0x4 +}; + +static unsigned int NumStringRecords; +static unsigned char *PackedStringTable; +static StringRecord *RecordTable; + +extern cFrontendDatabase *FEDatabase; +eLanguages GetCurrentLanguage(); + +void LanguageHasChanged(eLanguages new_language) { + EAXSound::ChangeLanguage(new_language); + if (FEDatabase) { + eLanguages lang = GetCurrentLanguage(); + if (lang != eLANGUAGE_ENGLISH) { + FEDatabase->GetGameplaySettings()->SpeedoUnits = 1; + } else { + FEDatabase->GetGameplaySettings()->SpeedoUnits = 0; + } + } + cFEng::Get()->MakeLoadedPackagesDirty(); +} + +LanguageInfo *GetLanguageInfo(eLanguages language) { + for (int i = 0; i <= 9; i++) { + if (LanguageInfoTable[i].Language == language) { + return &LanguageInfoTable[i]; + } + } + return nullptr; +} + +const char *GetLanguageName(eLanguages language) { + LanguageInfo *info = GetLanguageInfo(language); + if (!info) { + return "UNKNOWN"; + } + return info->Name; +} + +static eLanguages CurrentLanguage; + +void SetCurrentLanguage(eLanguages new_language); + +void LoadCurrentLanguage() { + GC_GetOSLanguage(); + SetCurrentLanguage(CurrentLanguage); +} + +void SetCurrentLanguage(eLanguages new_language) { + if (new_language != CurrentLanguage) { + if (CurrentLanguage != static_cast(-1)) { + LoadLanguageResources(false, false, false, false); + } + CurrentLanguage = new_language; + if (new_language != static_cast(-1)) { + LoadLanguageResources(true, TheGameFlowManager.IsInFrontend(), TheGameFlowManager.IsInGame(), true); + LanguageInfo *langInfo = GetLanguageInfo(CurrentLanguage); + if (langInfo) { + bPrintfLocaleInfo locInfo = *langInfo->pbPrintfLocaleInfo; + bPrintfSetLocaleInfo(locInfo.decimal_char, locInfo.group_char, langInfo->pbPrintfLocaleInfo->group_len); + } + LanguageHasChanged(CurrentLanguage); + } + if (FEDatabase && !FEDatabase->GetUserProfile(0)->IsProfileNamed()) { + FEDatabase->GetUserProfile(0)->SetProfileName(nullptr, true); + } + MemoryCard::LoadLocale(CurrentLanguage); + } +} + +eLanguages GetCurrentLanguage() { + return CurrentLanguage; +} + +struct WideCharHistogram; + +void WideToCharString(char *dest, unsigned int destlen, const short *src) { + if (!dest) { + return; + } + if (!src) { + return; + } + unsigned int bytes = 0; + unsigned short ch = *reinterpret_cast(src); + if (ch != 0) { + destlen = destlen - 1; + if (bytes < destlen) { + do { + if (ch < 0x100) { + bytes++; + *dest = reinterpret_cast(src)[1]; + src++; + dest++; + } else { + src++; + } + ch = *reinterpret_cast(src); + } while (ch != 0 && bytes < destlen); + } + } + *dest = 0; +} + +static const char *SearchForString(unsigned int string_label) { + if (!RecordTable) { + return nullptr; + } + unsigned int top = NumStringRecords - 1; + unsigned int bot = 0; + while (true) { + unsigned int mid = (bot + top) >> 1; + unsigned int hash = RecordTable[mid].Hash; + if (hash == string_label) { + return reinterpret_cast(RecordTable[mid].PackedString); + } + if (top - bot < 3) { + if (RecordTable[bot].Hash == string_label) { + return reinterpret_cast(RecordTable[bot].PackedString); + } + if (RecordTable[top].Hash == string_label) { + return reinterpret_cast(RecordTable[top].PackedString); + } + break; + } + if (mid == bot) { + return nullptr; + } + if (hash > string_label) { + top = mid; + } + if (hash < string_label) { + bot = mid; + } + } + return nullptr; +} + +int UnloaderLanguage(bChunk *chunk) { + if (chunk->GetID() == 0x39000) { + RecordTable = nullptr; + PackedStringTable = nullptr; + pWideCharHistogram = nullptr; + NumStringRecords = 0; + return 1; + } + return 0; +} + +int LoaderLanguage(bChunk *chunk) { + if (chunk->GetID() == 0x39000) { + LanguageChunkHeader *header = reinterpret_cast(chunk->GetData()); + header->PlatEndianSwap(); + RecordTable = reinterpret_cast(reinterpret_cast(header) + header->StringRecordTablePos); + PackedStringTable = reinterpret_cast(header) + header->StringTablePos; + pWideCharHistogram = reinterpret_cast(reinterpret_cast(header) + header->HistogramTablePos); + NumStringRecords = header->NumStringRecords; + pWideCharHistogram->PlatEndianSwap(); + for (unsigned int i = 0; i < NumStringRecords; i++) { + bPlatEndianSwap(reinterpret_cast(&RecordTable[i])); + unsigned int offset = reinterpret_cast(&RecordTable[i])[1]; + bPlatEndianSwap(&offset); + reinterpret_cast(&RecordTable[i])[1] = reinterpret_cast(PackedStringTable) + offset; + } + return 1; + } + return 0; +} + +void PackedStringToWideString(unsigned short *wide_string, int wide_string_buffer_size, const char *packed_string) { + if (!pWideCharHistogram) { + bStrCpy(wide_string, packed_string); + } else { + pWideCharHistogram->UnpackString(wide_string, wide_string_buffer_size, packed_string); + } +} + +void WideStringToPackedString(char *packed_string, int packed_string_buffer_size, const unsigned short *wide_string) { + pWideCharHistogram->PackString(packed_string, packed_string_buffer_size, wide_string); +} + +FEWideString &FEWideString::operator=(const char *pcString) { + short wide_string[1024]; + PackedStringToWideString(reinterpret_cast(wide_string), 0x800, pcString); + *this = wide_string; + return *this; +} + +bool DoesStringExist(unsigned int label) { + if (!SearchForString(label)) { + return false; + } + return true; +} + +const char *GetLocalizedString(unsigned int id) { + const char *str = SearchForString(id); + if (!str) { + str = SearchForString(0x9bb9ccc3); + } + return str; +} + +char *GetTranslatedString(int id) { + return const_cast(GetLocalizedString(static_cast(id))); +} + +void FormatMessage(char *buf, int size, const char *fmt, va_list *args) { + bVSPrintf(buf, fmt, args); +} + +void GetLocalizedString(char *buffer, unsigned int bufsize, unsigned int string_label) { + char *str = const_cast(GetLocalizedString(string_label)); + bStrNCpy(buffer, str, static_cast(bufsize)); +} + +bool GetLocalizedWideString(short *wide_string, int wide_string_buffer_size, unsigned int string_label) { + const char *str = SearchForString(string_label); + if (str) { + PackedStringToWideString(reinterpret_cast(wide_string), wide_string_buffer_size, str); + return true; + } + return false; +} + +const char *GetLocalizedPercentSign() { + const char *szPercentUnit = "%"; + eLanguages currLang = GetCurrentLanguage(); + if (currLang == eLANGUAGE_DANISH || currLang == eLANGUAGE_FINNISH || currLang == eLANGUAGE_FRENCH || currLang == eLANGUAGE_GERMAN || currLang == eLANGUAGE_SWEDISH) { + szPercentUnit = " %"; + } + return szPercentUnit; +} + +struct FontSizeInfo { + unsigned int Hash; // offset 0x0 + int Size; // offset 0x4 +}; + +extern FontSizeInfo FontSizeInfoTable[9]; +extern int LanguageMemoryPoolNumber; +extern void *pLanguageMemoryPoolMemory; +extern int LanguageMemoryPoolSize; +extern bool IsKorea(); +extern int bGetFreeMemoryPoolNum(); +extern void bInitMemoryPool(int pool, void *mem, int size, const char *name); +extern void eLoadStreamingTexturePack(const char *filename, void (*callback)(void *), void *param, int flags); +extern void eWaitForStreamingTexturePackLoading(const char *name); + +struct VMFile; +extern ResourceFile *pLanguageResourceFile; +extern VMFile *pLanguageResourceFile_VM; +extern void UnloadFileFromVirtualMemory(VMFile *vm_file); +extern VMFile *LoadFileIntoVirtualMemory(const char *filename, bool compressed, bool use_trackstreampool_as_temp); +extern void WaitForResourceLoadingComplete(); + +void InitLocalization() { + LanguageInfo *info; + if (IsKorea()) { + info = GetLanguageInfo(eLANGUAGE_KOREAN); + } else { + info = GetLanguageInfo(eLANGUAGE_ENGLISH); + } + unsigned int *fonts = info->pFontNameInfo->GlobalFonts; + int total_font_size = 0; + int n = 0; + while (fonts[n] != 0) { + int font_size = -1; + for (int i = 0; i < 9; i++) { + if (FontSizeInfoTable[i].Hash == fonts[n]) { + font_size = FontSizeInfoTable[i].Size; + break; + } + } + if (font_size >= 0) { + total_font_size = total_font_size + font_size; + } + n++; + } + LanguageMemoryPoolSize = (total_font_size + 0x4080) & 0xFFFFFFC0; + if (LanguageMemoryPoolNumber != 0) { + LanguageMemoryPoolNumber = bGetFreeMemoryPoolNum(); + pLanguageMemoryPoolMemory = bMalloc(LanguageMemoryPoolSize, 0); + bInitMemoryPool(LanguageMemoryPoolNumber, pLanguageMemoryPoolMemory, + LanguageMemoryPoolSize, "LanguageMemoryPool"); + } + eLoadStreamingTexturePack("LANGUAGES\\LANGUAGETEXTURES.BIN", nullptr, nullptr, 0); + eWaitForStreamingTexturePackLoading("LANGUAGES\\LANGUAGETEXTURES.BIN"); +} + +void LoadLanguageResources(bool load_global, bool load_frontend, bool load_ingame, bool blocking) { + LanguageInfo *info = GetLanguageInfo(CurrentLanguage); + if (!load_global) { + UnloadResourceFile(pLanguageResourceFile); + pLanguageResourceFile = nullptr; + UnloadFileFromVirtualMemory(pLanguageResourceFile_VM); + pLanguageResourceFile_VM = nullptr; + if (info->pFontNameInfo->GlobalFontsLoaded) { + eUnloadStreamingTexture(info->pFontNameInfo->GlobalFonts, 8); + info->pFontNameInfo->GlobalFontsLoaded = 0; + } + } + if (!load_frontend && info->pFontNameInfo->FrontendFontsLoaded) { + eUnloadStreamingTexture(info->pFontNameInfo->FrontendFonts, 8); + info->pFontNameInfo->FrontendFontsLoaded = 0; + } + if (!load_ingame && info->pFontNameInfo->InGameFontsLoaded) { + eUnloadStreamingTexture(info->pFontNameInfo->InGameFonts, 8); + info->pFontNameInfo->InGameFontsLoaded = 0; + } + if (load_global) { + if (!pLanguageResourceFile) { + pLanguageResourceFile_VM = LoadFileIntoVirtualMemory(info->FilenameTextOnly, false, false); + {} // empty anonymous block (DWARF) + int pool = 0; + pLanguageResourceFile = CreateResourceFile(info->Filename, static_cast(7), 0, 0, 0); + int file_size = bFileSize(info->Filename); + if (bLargestMalloc(LanguageMemoryPoolNumber) >= file_size + 0x80) { + pool = LanguageMemoryPoolNumber; + } + pLanguageResourceFile->SetAllocationParams((pool & 0xF) | 0x2000, info->Filename); + pLanguageResourceFile->BeginLoading(); + if (blocking) { + WaitForResourceLoadingComplete(); + } + } + if (load_global && !info->pFontNameInfo->GlobalFontsLoaded) { + eLoadStreamingTexture(info->pFontNameInfo->GlobalFonts, 8, static_cast(nullptr), static_cast(nullptr), LanguageMemoryPoolNumber); + info->pFontNameInfo->GlobalFontsLoaded = 1; + } + } + if (load_frontend && !info->pFontNameInfo->FrontendFontsLoaded) { + eLoadStreamingTexture(info->pFontNameInfo->FrontendFonts, 8, static_cast(nullptr), static_cast(nullptr), LanguageMemoryPoolNumber); + info->pFontNameInfo->FrontendFontsLoaded = 1; + } + if (load_ingame && !info->pFontNameInfo->InGameFontsLoaded) { + eLoadStreamingTexture(info->pFontNameInfo->InGameFonts, 8, static_cast(nullptr), static_cast(nullptr), LanguageMemoryPoolNumber); + info->pFontNameInfo->InGameFontsLoaded = 1; + } + if (blocking) { + eWaitForStreamingTexturePackLoading("LANGUAGES\\LANGUAGETEXTURES.BIN"); + } +} diff --git a/src/Speed/Indep/Src/Frontend/Localization/Localize.hpp b/src/Speed/Indep/Src/Frontend/Localization/Localize.hpp index a35a2dfa0..d1d47b02e 100644 --- a/src/Speed/Indep/Src/Frontend/Localization/Localize.hpp +++ b/src/Speed/Indep/Src/Frontend/Localization/Localize.hpp @@ -5,6 +5,7 @@ #pragma once #endif +const char *GetLocalizedString(unsigned int string_label); char *GetTranslatedString(int label_hash); #endif diff --git a/src/Speed/Indep/Src/Frontend/Localization/WideCharHistogram.cpp b/src/Speed/Indep/Src/Frontend/Localization/WideCharHistogram.cpp index e69de29bb..b7b9a6107 100644 --- a/src/Speed/Indep/Src/Frontend/Localization/WideCharHistogram.cpp +++ b/src/Speed/Indep/Src/Frontend/Localization/WideCharHistogram.cpp @@ -0,0 +1,99 @@ +extern int DisableWideStringHistogram; + +void WideCharHistogram::PlatEndianSwap() { + bEndianSwap32(&NumEntries); + for (int i = 0; i < NumEntries; i++) { + bEndianSwap16(&EntryTable[i]); + } +} + +void WideCharHistogram::UnpackString(unsigned short *wide, int size, const char *packed) { + unsigned int error = 0; + int out = 0; + if (size > 0) { + bool histEnabled = DisableWideStringHistogram == 0; + int in = 0; + unsigned int result; + int nextIn; + do { + unsigned int ch = static_cast(static_cast(packed[in])); + nextIn = in + 1; + result = ch; + if ((static_cast(packed[in]) & 0x80) != 0 && histEnabled) { + unsigned int lookup = static_cast(EntryTable[ch]); + result = lookup; + if (lookup < 0x80 && lookup != 0) { + unsigned char nextByte = static_cast(packed[nextIn]); + nextIn = in + 2; + result = 0; + if ((nextByte & 0x80) != 0) { + result = static_cast(EntryTable[lookup * 0x80 + static_cast(nextByte) - 0x80]); + } + } + if (result == 0) { + error = 1; + result = 0x5f; + } + } + wide[out] = static_cast(result); + out++; + } while (result != 0 && (in = nextIn, out < size)); + } +} + +void WideCharHistogram::PackString(char *packed, int size, const unsigned short *wide) { + unsigned int error = 0; + int out = 0; + int in = 0; + unsigned short ch; + if (size > 0) { + do { + ch = wide[in]; + in++; + if (ch > 0xFF7F) { + ch = ch + 0x100; + } + if (ch < 0x80) { + packed[out] = static_cast(ch); + out++; + } else if (DisableWideStringHistogram == 0) { + int numEntries = NumEntries; + int idx = 0x80; + if (numEntries > 0x80) { + unsigned short entry = EntryTable[0x80]; + while (entry != ch && (idx++, idx < numEntries)) { + entry = EntryTable[idx]; + } + } + if (idx == numEntries) { + error = 1; + } else if (idx < 0x100) { + packed[out] = static_cast(idx); + out++; + } else { + int j = 0x80; + do { + unsigned short entry = EntryTable[j]; + int idxVal = idx; + if (idx < 0) { + idxVal = idx + 0x7f; + } + if (static_cast(entry) == static_cast(idxVal >> 7)) { + packed[out] = static_cast(j); + packed[out + 1] = static_cast(idx) + static_cast(entry) * -0x80 + -0x80; + out += 2; + break; + } + j++; + } while (j < 0x100); + if (j == 0x100) { + error = 1; + } + } + } else if (ch < 0x100) { + packed[out] = static_cast(ch); + out++; + } + } while (ch != 0 && out < size); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.cpp b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.cpp index e69de29bb..313a1e526 100644 --- a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.cpp +++ b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.cpp @@ -0,0 +1,702 @@ +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Misc/Joylog.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Misc/Config.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Misc/bFile.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern unsigned short gSaveType0[]; +extern unsigned short gSaveType1[]; +extern unsigned short gSaveType2[]; +extern IAllocator* gMemoryAllocator; +extern MemcardCallbacks gMemcardCallbacks; + +void bStrCpy(unsigned short* to, const char* from); +void bStrCpy(unsigned short* to, const unsigned short* from); +void bStrNCpy(unsigned short* to, const char* from, int n); +char* bStrCat(char* dest, const char* s1, const char* s2); + +const char* GetLanguageName(eLanguages lang); +const char* GetLocalizedString(unsigned int hash); +void LOCALE_create(void* data, int param); +void LOCALE_setstate(void* data, int state, int param); +const char* LOCALE_getstrA(void* data, int strID); + +bool FEngIsScriptSet(const char* pkg_name, unsigned long obj_hash, unsigned long script_hash); + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +extern RealmcIface::GameInfo* RealmcIfaceGameInfoCtorUnsignedShort(RealmcIface::GameInfo* self, + const unsigned short* gameTitle, + unsigned int titleId, + bool multipleSaveTypesUsed, + bool multitapSupported) + asm("__Q211RealmcIface8GameInfoPCUwUibT3"); +extern void RealmcIfaceMemcardInterfaceLoadUnsignedShort(RealmcIface::MemcardInterface* self, + const char* entryName, + char* header, + char* body, + const unsigned short* contentName, + const RealmcIface::TitleInfo* titleInfo) + asm("Load__Q211RealmcIface16MemcardInterfacePCcPcT2PCUwPCQ211RealmcIface9TitleInfoT4"); +#endif + +void CaptureJoyOp(MemoryCardJoyLoggableEvents op) { + Joylog::AddData(static_cast< int >(op), 8, JOYLOG_CHANNEL_MEMORY_CARD); +} + +int ReplayJoyOp() { + MemoryCardJoyLoggableEvents l_Op = + static_cast< MemoryCardJoyLoggableEvents >(Joylog::GetData(8, JOYLOG_CHANNEL_MEMORY_CARD)); + IJoyHelper::EmulateMemoryCardLibrary(l_Op); + return l_Op; +} + +void Realmc::SystemInterface::Clear() { + mAllocator = nullptr; + mThread = nullptr; + mMutex = nullptr; + mGetStrCallback = nullptr; +} + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +RealmcIface::GameInfo::GameInfo(const wchar_t* gameTitle, unsigned int titleId, + bool multipleSaveTypesUsed, bool multitapSupported) { + RealmcIfaceGameInfoCtorUnsignedShort(this, reinterpret_cast< const unsigned short * >(gameTitle), + titleId, multipleSaveTypesUsed, multitapSupported); +} + +void RealmcIface::MemcardInterface::Load(const char* entryName, char* header, char* body, + const wchar_t* contentName, + const RealmcIface::TitleInfo* titleInfo) { + RealmcIfaceMemcardInterfaceLoadUnsignedShort( + this, entryName, header, body, reinterpret_cast< const unsigned short * >(contentName), + titleInfo); +} + +void RealmcIface::MemcardInterface::Delete(const char* entryName, const wchar_t* contentName) { + Delete(entryName, reinterpret_cast< const unsigned short * >(contentName)); +} +#endif + +void IJoyHelper::EmulateMemoryCardLibrary(int aJoyOp) { + char* pBuf = new char[0x400]; + char* pBuf1 = pBuf + 1; + const wchar_t* pOptions[4]; + pOptions[0] = reinterpret_cast< const wchar_t* >(pBuf + 0x338); + pOptions[1] = reinterpret_cast< const wchar_t* >(pBuf + 0x36a); + pOptions[2] = reinterpret_cast< const wchar_t* >(pBuf + 0x39c); + pOptions[3] = reinterpret_cast< const wchar_t* >(pBuf + 0x3ce); + RealmcIface::CardInfo lCardInfo; + RealmcIface::EntryInfo lEntryInfo; + lEntryInfo.mName = pBuf; + switch (aJoyOp) { + case 1: + gMemcardCallbacks.ShowMessage(reinterpret_cast< const wchar_t* >(pBuf), 0, pOptions); + break; + case 2: + gMemcardCallbacks.ClearMessage(); + break; + case 3: { + RealmcIface::BootupCheckResults lBootRes; + lBootRes.Clear(); + gMemcardCallbacks.BootupCheckDone(static_cast< RealmcIface::CardStatus >(0), lBootRes); + break; + } + case 4: + gMemcardCallbacks.SaveCheckDone(static_cast< RealmcIface::TaskResult >(0), + static_cast< RealmcIface::CardStatus >(0)); + break; + case 5: + gMemcardCallbacks.SaveDone(pBuf); + break; + case 6: + gMemcardCallbacks.CheckLoadedData(pBuf); + break; + case 7: + gMemcardCallbacks.LoadDone(pBuf); + break; + case 8: + gMemcardCallbacks.DeleteDone(pBuf); + break; + case 9: + gMemcardCallbacks.ClearEntries(); + break; + case 10: + gMemcardCallbacks.FoundEntry(&lEntryInfo); + break; + case 0xb: + gMemcardCallbacks.FindEntriesDone(static_cast< RealmcIface::CardStatus >(0)); + break; + case 0xc: + gMemcardCallbacks.Retry(static_cast< RealmcIface::CardStatus >(0)); + break; + case 0xd: + gMemcardCallbacks.Failed(static_cast< RealmcIface::TaskResult >(0), + static_cast< RealmcIface::CardStatus >(0)); + break; + case 0xe: + gMemcardCallbacks.CardChecked(&lCardInfo); + break; + case 0xf: + gMemcardCallbacks.CardRemoved(); + break; + case 0x10: + gMemcardCallbacks.SetAutosaveDone(static_cast< RealmcIface::TaskResult >(0), + static_cast< RealmcIface::CardStatus >(0), + static_cast< RealmcIface::AutosaveState >(0)); + break; + case 0x11: + gMemcardCallbacks.LoadReady(pBuf, 0, 0, pBuf1, pBuf1); + break; + case 0x12: + gMemcardCallbacks.SetMonitorDone(static_cast< RealmcIface::CardStatus >(0), + static_cast< RealmcIface::MonitorState >(1)); + break; + } + if (pBuf != nullptr) { + delete[] pBuf; + } +} + +void InitMemoryCard() { + MemoryCard::s_pThis = new MemoryCard(); + bStrCpy(gSaveType0, ""); + bStrCpy(gSaveType1, ""); + bStrCpy(gSaveType2, ""); + bStrCpy(MemoryCardImp::gContentName, ""); + MemoryCard::s_pThis->Init(); +} + +MemoryCardMessage::MemoryCardMessage(const wchar_t* msg, unsigned int nOptions, + const wchar_t** options) { + bStrCpy(reinterpret_cast< unsigned short* >(mMsg), + reinterpret_cast< const unsigned short* >(msg)); + mnOptions = nOptions; + for (unsigned int i = 0; i < nOptions; i++) { + bStrCpy(reinterpret_cast< unsigned short* >(mOptions[i]), + reinterpret_cast< const unsigned short* >(options[i])); + } +} + +MemoryCard::MemoryCard() { + m_MemOp = 0; + m_bWaitingForResponse = false; + m_pIMemcard = nullptr; + m_PendingMessage = nullptr; + m_BootupParams.Clear(); + m_Type = ST_PROFILE; + m_bBootFoundFile = false; + m_bAutoSave = false; + m_bInAutoSave = false; + m_bCheckingCardForAutoSave = false; + m_bFoundAutoSaveFile = false; + m_bCheckingCardForOverwrite = false; + m_bAutoSaveRequested = false; + m_bAutoSaveCardPulled = false; + m_ReqOp = 0; + m_bInBootSequence = true; + m_bRetryBootCheck = false; + m_bManualSave = false; + m_bAutoSaveCardPulledDuringSave = false; + m_bOldSaveFileExists = false; + m_bListingOldSaveFiles = false; + m_bMemcardScreenShowing = false; + m_bCardRemoved = false; + m_bRetryAutoSave = false; + m_bInitialized = false; + m_bDisablingAutoSaveForSave = false; + m_bAutoLoading = false; + m_bListingForCreate = false; + m_bHUDLoaded = false; + m_bCancelNextAutoSave = false; + m_bMonitorOn = false; + m_bAutoSaveIconShowing = false; + m_bNeedToAllowControllerErrors = false; + m_bNonSilentAutoSave = false; + m_bAutoLoadDone = false; + m_bMemcardScreenExiting = false; + m_nPlayer = 0; + char* pIcon = static_cast< char* >(bGetFile("GCSaveIcon.tpl", nullptr, 0)); + char* pBanner = static_cast< char* >(bGetFile("GCSaveBanner.tpl", nullptr, 0)); + GCIconDataInfo* pIconData = new GCIconDataInfo(); + m_pRMIcon = pIconData; + pIconData->numIconFrames = 0; + pIconData->imageData = nullptr; + GCBannerDataInfo* pBannerData = new GCBannerDataInfo(); + m_pRMBanner = pBannerData; + pBannerData->imageData = nullptr; + pIconData->numIconFrames = 1; + pIconData->imageData = pIcon; + pIconData->animationLoop = static_cast< GCAnimationImageLoop >(0); + pBannerData->imageData = pBanner; + pBannerData->imageFormat = static_cast< GCImageFormat >(0); +} + +bool MemoryCard::IsCardAvailable() { + if (GetInstance()) { + if (GetInstance()->m_LastError == 0 || GetInstance()->m_LastError == 11) + return true; + return false; + } + return false; +} + +void MemoryCard::SetExtraParam(SaveType t, const char* filename, void* buf, unsigned int size) { + if (GetInstance() == nullptr) return; + GetInstance()->m_ReqFilename = filename; + GetInstance()->m_Type = t; + GetInstance()->m_pBuffer = static_cast< char* >(buf); + GetInstance()->m_DataSize = size; +} + +void MemoryCard::InitCommand(int op) { + m_ReqOp = 0; + m_bWaitingForResponse = false; + m_LastError = 0; + m_MemOp = op; +} + +void MemoryCard::RequestTask(int op, const char* name) { + m_ReqFilename = name; + m_ReqOp = op; +} + +void MemoryCard::ProcessTask() { + if (GetScreen() == nullptr) { + m_ReqOp = 0; + return; + } + switch (m_ReqOp) { + case MO_Delete: + Delete(m_ReqFilename); + break; + case MO_Load: + Load(m_ReqFilename); + break; + case MO_List: + List(nullptr, nullptr); + break; + } + m_ReqOp = 0; +} + +bool MemoryCard::IsCardBusy() { + if (GetInstance() != nullptr + && (!GetInstance()->m_pIMemcard->IsResettable() + || GetInstance()->IsAutoSaveIconVisible() + || ((((void)GetInstance()->IsAutoSaving()), GetInstance()->IsAutoSaving()) + && !GetInstance()->IsWaitingForResponse()))) + return true; + return false; +} + +void MemoryCard::Init() { + static Realmc::SystemInterface iSystem; + static int bSystemCleared; + static Realmc::SystemInterface* pSystem; + if (!bSystemCleared) { + iSystem.Clear(); + bSystemCleared = 1; + } + static MemoryCardImp sMemcardImp; + if (pSystem == nullptr) { + iSystem.mAllocator = gMemoryAllocator; + iSystem.mThread = new (__FILE__, __LINE__) MyThread(); + MyMutex* pMutex = new (__FILE__, __LINE__) MyMutex(); + pSystem = &iSystem; + iSystem.mMutex = pMutex; + iSystem.mGetStrCallback = GetLocaleString; + } + m_pImp = &sMemcardImp; + bStrCpy(reinterpret_cast< unsigned short* >(m_GameTitle), "Need for Speed™ Most Wanted"); + GameInfo* pGameInfo = + new (__FILE__, __LINE__) GameInfo(reinterpret_cast< unsigned short* >(m_GameTitle), 0, false, false); + m_pGameInfo = pGameInfo; + m_pIMemcard = RealmcIface::MemcardInterface::CreateInstance(&iSystem, &gMemcardCallbacks, pGameInfo); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_SHOW, 1); + m_TimeOffsetSec = 0; + m_pLocaleFileHandler = nullptr; +} + +void MemoryCard::StartBootSequence() { + m_bInBootSequence = true; + gMemcardSetup.mOp = 0x20; + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x4000); +} + +void MemoryCard::EndBootSequence() { + m_bInBootSequence = false; + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_SHOW, 0x4000); +} + +void MemoryCard::LoadLocale(eLanguages eLang) { + if (s_pThis == nullptr) return; + char sPath[64]; + bStrCpy(sPath, "FRONTEND/MC_"); + if (eLang <= eLANGUAGE_LABELS) { + if (eLang < eLANGUAGE_LARGEST) { + goto lang_code; + } + bStrCat(sPath, sPath, "English.bin"); + } else { + lang_code: + const char* langName = GetLanguageName(eLang); + bStrCat(sPath, sPath, langName); + bStrCat(sPath, sPath, ".bin"); + } + MemoryCard* pThis = s_pThis; + if (pThis->m_pLocaleFileHandler == nullptr) + pThis->m_pLocaleFileHandler = bMalloc(0x2000, 0); + unsigned int currentsize = bFileSize(sPath); + bFile* file = bOpen(sPath, 1, 1); + bRead(file, s_pThis->m_pLocaleFileHandler, currentsize); + bClose(file); + LOCALE_create(s_pThis->m_pLocaleFileHandler, 1); + LOCALE_setstate(s_pThis->m_pLocaleFileHandler, 0, 0); + unsigned short* dest = gSaveType0; + const char* str = GetLocalizedString(0xe6f55df0); + bStrCpy(dest, str); +} + +int MemoryCard::GetPrefixLength() { return bStrLen(m_pImp->GetPrefix()); } +const char* MemoryCard::GetPrefix() { return m_pImp->GetPrefix(); } +const char* MemoryCard::GetLocaleString(int strID) { return LOCALE_getstrA(GetInstance()->m_pLocaleFileHandler, strID); } + +void MemoryCard::SetMessageMode(unsigned int msg, bool flag) { + if (GetInstance() != nullptr) + GetInstance()->m_pIMemcard->SetMessage(flag ? RealmcIface::MESSAGE_SHOW : RealmcIface::MESSAGE_HIDE, msg); +} + +void MemoryCard::Tick(int TickCount) { + if (m_MemOp == 0 && m_ReqOp != 0) ProcessTask(); + if (m_bAutoSaveRequested && m_bHUDLoaded && GManager::Exists() && !GManager::Get().GetHasPendingSMS()) { + m_bHUDLoaded = false; + m_bAutoSaveRequested = false; + StartAutoSave(false); + } + if (Joylog::IsReplaying()) { + MemoryCardJoyLoggableEvents l_JoyOp; + do { l_JoyOp = static_cast< MemoryCardJoyLoggableEvents >(ReplayJoyOp()); } while (l_JoyOp != 0); + } else { + m_pIMemcard->Update(TickCount); + if (Joylog::IsCapturing()) CaptureJoyOp(MJ_None); + } + if (FEDatabase == nullptr) return; + if (FEDatabase->IsOptionsMode()) return; + if (cFEng::Get()->IsPackagePushed("ScreenPrintf") + || cFEng::Get()->IsPackagePushed("MemoryCard.fng") + || IsAutoSaveIconVisible()) { + if (!FEManager::Get()->IsAllowingControllerError() && !TheGameFlowManager.IsInGame()) return; + if (cFEng::Get()->IsPackagePushed("IG_Pause.fng") || cFEng::Get()->IsPackagePushed("AutoSaveIcon.fng")) + m_bNonSilentAutoSave = true; + m_bNeedToAllowControllerErrors = true; + FEManager::Get()->AllowControllerError(false); + FEManager::Get()->SuppressControllerError(true); + } else { + if (!m_bNeedToAllowControllerErrors) return; + m_bNeedToAllowControllerErrors = false; + if (FEManager::Get()->IsAllowingControllerError()) return; + if (m_bNonSilentAutoSave) { m_bNonSilentAutoSave = false; return; } + FEManager::Get()->AllowControllerError(true); + FEManager::Get()->SuppressControllerError(false); + } +} + +void MemoryCard::MessageDone(RealmcIface::MessageChoices nInput) { + if (m_bWaitingForResponse) { + m_pIMemcard->MessageDone(nInput); + m_bWaitingForResponse = false; + } +} + +void MemoryCard::BootupCheck(const char* entry) { + bStrCpy(m_BootupFilename, ""); + m_pImp->ConstructSaveInfo(ST_PROFILE, "", FEDatabase->GetUserProfileSaveSize(false)); + m_BootupParams.mEntryNamePattern = m_BootupFilename; + m_BootupParams.mSaveReqs = reinterpret_cast< RealmcIface::SaveReq** >(m_pImp->GetSaveReqArray()); + m_BootupParams.mNumSaveTypes = 1; + m_BootupParams.mValidCardIds = 1; + InitCommand(MO_BootUp); + if (!Joylog::IsReplaying()) + m_pIMemcard->BootupCheck(&m_BootupParams, 0, static_cast< const char** >(nullptr), static_cast< wchar_t* >(nullptr)); +} + +bool MemoryCard::ShouldDoAutoSave(bool bForce) { + if (bForce) return true; + if (m_bCancelNextAutoSave) { m_bCancelNextAutoSave = false; return false; } + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) return false; + if (!IsMemcardEnabled || !IsAutoSaveEnabled) return false; + if (FEDatabase->GetGameplaySettings()->AutoSaveOn || m_bCardRemoved) { + if (!FEDatabase->IsFinalEpicChase() && GRaceStatus::Exists() + && GRaceStatus::Get().GetRaceParameters() != nullptr + && GRaceStatus::Get().GetRaceParameters()->GetIsBossRace()) + return false; + return true; + } + return false; +} + +void MemoryCard::StartAutoSave(bool bForce) { + if (!ShouldDoAutoSave(bForce)) return; + if (!FEDatabase->bProfileLoaded) return; + if ((((void)gMemcardSetup.GetCommand()), gMemcardSetup.mOp & 0xf0) != 0xb0) { + ShowAutoSaveIcon(); + gMemcardSetup.mOp = 0; + } + if (m_bCardRemoved) { HandleAutoSaveError(); } + else { + m_bInAutoSave = true; + m_bCheckingCardForAutoSave = true; + FEManager::Get()->SuppressControllerError(true); + ShowMessages(false); + CheckCard(0); + } +} + +void MemoryCard::DoAutoSave() { + m_bCheckingCardForAutoSave = false; + if ((((void)gMemcardSetup.GetCommand()), gMemcardSetup.mOp & 0xf0) == 0xb0) { + ShowMessages(true); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x100); + } else { ShowOnlyAutoSaveMessages(); } + Save(FEDatabase->GetUserProfile(0)->GetProfileName()); +} + +void MemoryCard::EndAutoSave() { + if (!m_bRetryAutoSave) m_MemOp = 0; + m_bCheckingCardForAutoSave = false; + m_bCheckingCardForOverwrite = false; + m_bInAutoSave = false; + FEManager::Get()->SuppressControllerError(false); + ShowMessages(true); + HideAutoSaveIcon(); +} + +void MemoryCard::StartListingOldSaveFiles() { m_bListingOldSaveFiles = true; ListOldSaveFilesNGC(); } + +void MemoryCard::EndListingOldSaveFiles() { + m_bListingOldSaveFiles = false; + if (m_bOldSaveFileExists) { + cFEng::Get()->QueueGameMessage(0x7e998e5e, nullptr, 0xff); + DialogInterface::ShowOneButton("", "", static_cast< eDialogTitle >(2), 0x417b2601, 0x34dc1bec, 0xc5e2beac); + } + FEDatabase->GetCareerSettings()->AwardOneTimeCashBonus(m_bOldSaveFileExists); +} + +void MemoryCard::SetMonitor(bool bEnabled) { + InitCommand(MO_SetMonitor); + if (!Joylog::IsReplaying()) + m_pIMemcard->SetMonitor(bEnabled ? RealmcIface::MONITOR_ON : RealmcIface::MONITOR_OFF); + if (!bEnabled && Joylog::IsReplaying()) ReplayJoyOp(); +} + +void MemoryCard::SetAutoSaveEnabled(bool bEnabled) { + char entryname[16]; + bStrCpy(entryname, FEDatabase->GetMultiplayerProfile(0)->GetProfileName()); + SetExtraParam(ST_PROFILE, entryname, nullptr, FEDatabase->GetUserProfileSaveSize(false)); + bStrCat(m_Filename, m_pImp->GetPrefix(), entryname); + bStrNCpy(MemoryCardImp::gContentName, entryname, 16); + if (GetScreen() && gMemcardSetup.GetCommand() == 0xa0) { + GetScreen()->SetStringCheckingCard(); + ShowMessages(true); + } else { + ShowMessages(false); + } + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_SHOW, 1); + if (bEnabled) { + gMemcardSetup.mPreviousCommand = gMemcardSetup.mOp & 0xf0; + gMemcardSetup.ClearCommand(); + gMemcardSetup.SetCommand(0xa0); + } else { + m_bDisablingAutoSaveForSave = true; + } + InitCommand(MO_AutoSave); + if (!Joylog::IsReplaying()) + m_pIMemcard->SetAutosave(bEnabled ? RealmcIface::AUTOSAVE_ENABLE : RealmcIface::AUTOSAVE_DISABLE, 0, nullptr, entryname, RealmcIface::CARD_UNKNOWN); + if (!bEnabled && Joylog::IsReplaying()) ReplayJoyOp(); +} + +void MemoryCard::ShowOnlyAutoSaveMessages() { + m_bManualSave = false; + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_SHOW, 2); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_SHOW, 4); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_SHOW, 0x800); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 1); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x100); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x200); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x400); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x1000); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x2000); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x4000); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x8000); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x10000); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x20000); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x40000); + m_pIMemcard->SetMessage(RealmcIface::MESSAGE_HIDE, 0x80000); +} + +void MemoryCard::ShowMessages(bool bShow) { + m_bManualSave = bShow; + m_pIMemcard->SetMessage(bShow ? RealmcIface::MESSAGE_SHOW : RealmcIface::MESSAGE_HIDE, 0xffffffff); +} + +void MemoryCard::CheckCard(int iSlot) { + RealmcIface::CardId id; + id = RealmcIface::CARD_UNKNOWN; + InitCommand(MO_CheckCard); + if (!Joylog::IsReplaying()) m_pIMemcard->CheckCard(id); +} + +void MemoryCard::Save(const char* entryName) { + SetExtraParam(ST_PROFILE, entryName, nullptr, FEDatabase->GetUserProfileSaveSize(false)); + if (m_pImp->GetSaveInfo() == nullptr) { + m_pImp->ConstructSaveInfo(ST_PROFILE, entryName, GetSize()); + bStrCat(m_Filename, m_pImp->GetPrefix(), entryName); + } + bStrNCpy(MemoryCardImp::gContentName, entryName, 16); + m_pBuffer = static_cast< char* >(bMalloc(GetSize(), nullptr, 0, 0x40)); + FEDatabase->SaveUserProfileToBuffer(GetData(), GetSize()); + m_Header[0] = 0x10d; + m_Header[1] = GetSize(); + InitCommand(MO_Save); + if (!Joylog::IsReplaying()) + m_pIMemcard->Save(m_Filename, GetHeader(), GetData(), + reinterpret_cast< const RealmcIface::SaveInfo* >(m_pImp->GetSaveInfo()), + static_cast< const RealmcIface::TitleInfo* >(nullptr)); +} + +void MemoryCard::List(const char* filter, RealmcIface::TitleInfo* titleInfo) { + SetExtraParam(ST_PROFILE, nullptr, nullptr, 0); + m_EntryCount = 0; + const char* prefix = m_pImp->GetPrefix(); + bStrCat(m_Filename, prefix, "*"); + InitCommand(MO_List); + if (!Joylog::IsReplaying()) { + m_pIMemcard->FindEntries(filter != nullptr ? filter : m_Filename, titleInfo); + } else { ReplayJoyOp(); } +} + +void MemoryCard::Load(const char* filename) { + SetExtraParam(ST_PROFILE, filename, nullptr, FEDatabase->GetUserProfileSaveSize(false)); + FEDatabase->AllocBackupDB(true); + m_pBuffer = static_cast< char* >(bMalloc(m_DataSize, nullptr, 0, 0x40)); + if (filename != nullptr) { + bStrNCpy(MemoryCardImp::gContentName, filename, 16); + bStrCat(m_Filename, m_pImp->GetPrefix(), filename); + } + InitCommand(MO_Load); + if (!Joylog::IsReplaying()) { + if (InBootSequence()) { + m_bAutoLoading = true; + BootupCheck(filename); + } else { + m_pIMemcard->Load(m_Filename, static_cast< char* >(nullptr), static_cast< char* >(nullptr), + reinterpret_cast< const wchar_t* >(MemoryCardImp::gContentName), + static_cast< const RealmcIface::TitleInfo* >(nullptr)); + } + } +} + +void MemoryCard::Delete(const char* filename) { + InitCommand(MO_Delete); + if (filename != nullptr) { + bStrNCpy(MemoryCardImp::gContentName, filename, 16); + bStrCat(m_Filename, m_pImp->GetPrefix(), filename); + } + if (!Joylog::IsReplaying()) + m_pIMemcard->Delete(m_Filename, reinterpret_cast< const wchar_t* >(MemoryCardImp::gContentName)); +} + +void MemoryCard::ListOldSaveFilesNGC() { + RealmcIface::TitleInfo titleInfo; + titleInfo.Init( + static_cast< RealmcIface::TitleType >(1), + 0, + static_cast< RealmcIface::NameType >(0), + static_cast< RealmcIface::DataFormat >(0)); + GetInstance()->ShowMessages(false); + List("NFSMW*", &titleInfo); +} + +void MemoryCard::ReleasePendingMessage() { + if (m_PendingMessage != nullptr) { delete m_PendingMessage; m_PendingMessage = nullptr; } +} + +void MemoryCard::HandleAutoSaveError() { + UIMemcardBase* pScreen = GetScreen(); + if ((((void)gMemcardSetup.GetCommand()), gMemcardSetup.mOp & 0xf0) == 0xb0 || pScreen != nullptr) + pScreen->HandleAutoSaveError(); + else + MemcardEnter(nullptr, nullptr, 0x91, nullptr, nullptr, 0, 0); +} + +void MemoryCard::HandleAutoSaveOverwriteMessage() { + UIMemcardBase* pScreen = GetScreen(); + if ((((void)gMemcardSetup.GetCommand()), gMemcardSetup.mOp & 0xf0) == 0xb0 || pScreen != nullptr) + pScreen->HandleAutoSaveOverwriteMessage(); + else + MemcardEnter(nullptr, nullptr, 0xd1, nullptr, nullptr, 0, 0); +} + +void MemoryCard::ShowAutoSaveIcon() { + if (m_bAutoSaveIconShowing) return; + m_bAutoSaveIconShowing = true; + if (!cFEng::Get()->IsPackagePushed("AutoSaveIcon.fng")) + cFEng::Get()->PushNoControlPackage("AutoSaveIcon.fng", static_cast< FE_PACKAGE_PRIORITY >(0x68)); + cFEng* feng = cFEng::Get(); + unsigned int msg = FEHashUpper("FadeIn"); + feng->QueuePackageMessage(msg, "AutoSaveIcon.fng", nullptr); + bool bWidescreen = FEDatabase->GetVideoSettings()->WideScreen; + if (GRaceStatus::Exists() && GRaceStatus::Get().GetRaceParameters() != nullptr + && GRaceStatus::Get().GetRaceParameters()->GetIsDDayRace()) { + const char* script; + if (bWidescreen) script = "SAVE_DDAY_16_9"; + else script = "SAVE_DDAY_4_3"; + msg = FEHashUpper(script); + } else { + if (cFEng::Get()->IsPackagePushed("SMS_HUD.fng") || GManager::Get().GetHasPendingSMS()) { + unsigned int hideMsg = FEHashUpper("HideSMSIcon"); + cFEng::Get()->QueuePackageMessage(hideMsg, nullptr, nullptr); + goto queue; + } + const char* script; + if (bWidescreen) script = "SAVE_REG_16_9"; + else script = "SAVE_REG_4_3"; + msg = FEHashUpper(script); + } +queue: + cFEng::Get()->QueuePackageMessage(msg, "AutoSaveIcon.fng", nullptr); +} + +void MemoryCard::HideAutoSaveIcon() { + if (m_bAutoSaveIconShowing) { + m_bAutoSaveIconShowing = false; + cFEng::Get()->QueuePackageMessage(FEHashUpper("FadeOut"), "AutoSaveIcon.fng", nullptr); + cFEng::Get()->QueuePackageMessage(FEHashUpper("ShowSMSIcon"), nullptr, nullptr); + } +} + +bool MemoryCard::IsAutoSaveIconVisible() { + if (!m_bAutoSaveIconShowing) { + const char *pkg = "AutoSaveIcon.fng"; + const char *iconName = "AUTOSAVE_ICON"; + unsigned int obj = FEHashUpper(iconName); + unsigned int script1 = FEHashUpper("FadeIn"); + if (!FEngIsScriptSet(pkg, obj, script1)) { + obj = FEHashUpper(iconName); + unsigned int script2 = FEHashUpper("Idle"); + if (!FEngIsScriptSet(pkg, obj, script2)) + return false; + } + } + return true; +} diff --git a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp index 377c828e1..fe01cf417 100644 --- a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp +++ b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp @@ -5,6 +5,277 @@ #pragma once #endif +#include + +#include "MemoryCardHelper.hpp" +#include "RealmcIface.hpp" + +struct MemoryCardImp; + +enum GCImageFormat { + GC_IMAGE_FORMAT_CI8 = 0, +}; + +enum GCAnimationImageLoop { + GC_ANIMATION_LOOP_NONE = 0, +}; + +struct GCIconDataInfo { + int numIconFrames; + char *imageData; + GCAnimationImageLoop animationLoop; +}; + +struct GCBannerDataInfo { + char *imageData; + GCImageFormat imageFormat; +}; + +struct UIMemcardBase; + +enum eLanguages { + eLANGUAGE_NONE = -1, + eLANGUAGE_FIRST = 0, + eLANGUAGE_ENGLISH = 0, + eLANGUAGE_FRENCH = 1, + eLANGUAGE_GERMAN = 2, + eLANGUAGE_ITALIAN = 3, + eLANGUAGE_SPANISH = 4, + eLANGUAGE_DUTCH = 5, + eLANGUAGE_SWEDISH = 6, + eLANGUAGE_DANISH = 7, + eLANGUAGE_KOREAN = 8, + eLANGUAGE_CHINESE = 9, + eLANGUAGE_JAPANESE = 10, + eLANGUAGE_THAI = 11, + eLANGUAGE_POLISH = 12, + eLANGUAGE_FINNISH = 13, + eLANGUAGE_LARGEST = 14, + eLANGUAGE_LABELS = 15, + eLANGUAGE_MAX = 16, +}; + +using RealmcIface::BootupCheckParams; + +// total size: 0x1804 +enum MessageChoices { + CHOICE_NONE = 0, + CHOICE_OPTION1 = 1, + CHOICE_OPTION2 = 2, + CHOICE_OPTION3 = 3, + CHOICE_OPTION4 = 4, +}; + +struct MemoryCardMessage { + int mMsg[1024]; // offset 0x0, size 0x1000 + unsigned int mnOptions; // offset 0x1000, size 0x4 + int mOptions[4][128]; // offset 0x1004, size 0x800 + + MemoryCardMessage(const wchar_t *msg, unsigned int nOptions, const wchar_t **options); +}; + +// total size: 0x198 +struct MemoryCard { + enum SaveType { + ST_PROFILE = 0, + ST_THUMBNAIL = 1, + ST_IMAGE = 2, + ST_MAX = 3, + }; + + enum _MemOp { + MO_NONE = 0, + MO_BootUp = 1, + MO_CheckCard = 2, + MO_Save = 3, + MO_AutoSave = 4, + MO_Load = 5, + MO_Delete = 6, + MO_List = 7, + MO_FakeLoad = 8, + MO_LoadYNCF = 9, + MO_SetMonitor = 10, + }; + + // Members + BootupCheckParams m_BootupParams; // offset 0x0, size 0x10 + GCIconDataInfo *m_pRMIcon; // offset 0x10, size 0x4 + GCBannerDataInfo *m_pRMBanner; // offset 0x14, size 0x4 + void *m_pLocaleFileHandler; // offset 0x18, size 0x4 + bool m_bWaitingForResponse; // offset 0x1C, size 0x1 + bool m_bBootFoundFile; // offset 0x20, size 0x1 + bool m_bAutoSave; // offset 0x24, size 0x1 + bool m_bAutoSaveCardPulled; // offset 0x28, size 0x1 + bool m_bInBootSequence; // offset 0x2C, size 0x1 + bool m_bRetryBootCheck; // offset 0x30, size 0x1 + bool m_bManualSave; // offset 0x34, size 0x1 + bool m_bAutoSaveCardPulledDuringSave; // offset 0x38, size 0x1 + bool m_bOldSaveFileExists; // offset 0x3C, size 0x1 + bool m_bListingOldSaveFiles; // offset 0x40, size 0x1 + bool m_bInAutoSave; // offset 0x44, size 0x1 + bool m_bAutoSaveRequested; // offset 0x48, size 0x1 + bool m_bCheckingCardForAutoSave; // offset 0x4C, size 0x1 + bool m_bFoundAutoSaveFile; // offset 0x50, size 0x1 + bool m_bCheckingCardForOverwrite; // offset 0x54, size 0x1 + bool m_bMemcardScreenShowing; // offset 0x58, size 0x1 + bool m_bCardRemoved; // offset 0x5C, size 0x1 + bool m_bRetryAutoSave; // offset 0x60, size 0x1 + bool m_bInitialized; // offset 0x64, size 0x1 + bool m_bDisablingAutoSaveForSave; // offset 0x68, size 0x1 + bool m_bAutoLoading; // offset 0x6C, size 0x1 + bool m_bListingForCreate; // offset 0x70, size 0x1 + bool m_bHUDLoaded; // offset 0x74, size 0x1 + bool m_bCancelNextAutoSave; // offset 0x78, size 0x1 + bool m_bMonitorOn; // offset 0x7C, size 0x1 + bool m_bAutoSaveIconShowing; // offset 0x80, size 0x1 + bool m_bNeedToAllowControllerErrors; // offset 0x84, size 0x1 + bool m_bNonSilentAutoSave; // offset 0x88, size 0x1 + bool m_bAutoLoadDone; // offset 0x8C, size 0x1 + bool m_bMemcardScreenExiting; // offset 0x90, size 0x1 + MemoryCardMessage *m_PendingMessage; // offset 0x94, size 0x4 + GameInfo *m_pGameInfo; // offset 0x98, size 0x4 + int m_ReqOp; // offset 0x9C, size 0x4 + const char *m_ReqFilename; // offset 0xA0, size 0x4 + int m_MemOp; // offset 0xA4, size 0x4 + char *m_pBuffer; // offset 0xA8, size 0x4 + unsigned short m_LastError; // offset 0xAC, size 0x2 + unsigned short m_SpecialError; // offset 0xAE, size 0x2 + int m_EntryCount; // offset 0xB0, size 0x4 + int m_nPlayer; // offset 0xB4, size 0x4 + unsigned int m_Header[2]; // offset 0xB8, size 0x8 + char m_Filename[32]; // offset 0xC0, size 0x20 + char m_BootupFilename[32]; // offset 0xE0, size 0x20 + int m_GameTitle[32]; // offset 0x100, size 0x80 + SaveType m_Type; // offset 0x180, size 0x4 + unsigned int m_DataSize; // offset 0x184, size 0x4 + int m_TimeOffsetSec; // offset 0x188, size 0x4 + RealmcIface::MemcardInterface *m_pIMemcard; // offset 0x18C, size 0x4 + UIMemcardBase *m_pFEScreen; // offset 0x190, size 0x4 + MemoryCardImp *m_pImp; // offset 0x194, size 0x4 + + // Static members + static MemoryCard *s_pThis; + + // Inline functions + static inline MemoryCard *GetInstance() { return s_pThis; } + inline void FEngLinkObjects(UIMemcardBase *pMenuScreen) { m_pFEScreen = pMenuScreen; } + inline bool IsTypeProfile() { return m_Type == ST_PROFILE; } + inline bool IsAutoSave() { return m_bAutoSave; } + inline int GetOp() { return m_MemOp; } + inline unsigned int GetSize() { return m_DataSize; } + inline bool InBootSequence() { return m_bInBootSequence; } + inline void SetPlayerNum(int player) { m_nPlayer = player; } + inline int GetPlayerNum() { return m_nPlayer; } + inline bool PromptForAutoSave() { return !m_bAutoSave; } + inline void CancelNextAutoSave() { m_bCancelNextAutoSave = true; } + inline void RequestAutoSave() { m_bAutoSaveRequested = true; } + inline bool AutoSaveRequested() { return m_bAutoSaveRequested; } + inline bool IsAutoSaving() { return m_bInAutoSave; } + inline bool IsCheckingCardForAutoSave() { return m_bCheckingCardForAutoSave; } + inline bool IsCheckingCardForOverwrite() { return m_bCheckingCardForOverwrite; } + inline bool IsRetryingAutoSave() { return m_bRetryAutoSave; } + inline void SetRetryAutoSave(bool bRetry) { m_bRetryAutoSave = bRetry; } + inline void SetHUDLoaded() { m_bHUDLoaded = true; } + inline bool IsMonitorOn() { return m_bMonitorOn; } + inline bool IsAutoLoading() { return m_bAutoLoading; } + inline bool IsMemcardScreenShowing() { return m_bMemcardScreenShowing; } + inline void SetMemcardScreenShowing(bool bShowing) { m_bMemcardScreenShowing = bShowing; } + inline bool IsMemcardScreenInitialized() { return m_bInitialized; } + inline void SetMemcardScreenInitialized(bool bInit) { m_bInitialized = bInit; } + inline bool IsListingForCreate() { return m_bListingForCreate; } + inline void SetListingForCreate(bool bListing) { m_bListingForCreate = bListing; } + inline void ResetAutoSaveCardPulled() { m_bAutoSaveCardPulled = false; } + inline bool CardPulledInAutoSave() { return m_bAutoSaveCardPulled; } + inline bool WasCardRemovedWithAutoSaveEnabled() { return m_bCardRemoved; } + inline void SetCardRemovedWithAutoSaveEnabled(bool bRemoved) { m_bCardRemoved = bRemoved; } + inline bool IsManualSave() { return m_bManualSave; } + inline bool IsListingOldSaveFiles() { return m_bListingOldSaveFiles; } + inline bool IsMemcardScreenExiting() { return m_bMemcardScreenExiting; } + inline void SetMemcardScreenExiting(bool bExiting) { m_bMemcardScreenExiting = bExiting; } + inline bool IsAutoLoadDone() { return m_bAutoLoadDone; } + inline void SetAutoLoadDone(bool bDone) { m_bAutoLoadDone = bDone; } + inline MemoryCardMessage *GetPendingMessage() { return m_PendingMessage; } + inline void SetWaitingForResponse(bool bWaiting) { m_bWaitingForResponse = bWaiting; } + inline bool IsWaitingForResponse() { return m_bWaitingForResponse; } + inline GCIconDataInfo *GetSaveIcon() { return m_pRMIcon; } + inline GCBannerDataInfo *GetSaveBanner() { return m_pRMBanner; } + static inline int GetLastError() { return s_pThis->m_LastError; } + static inline int GetSpecialError() { return s_pThis->m_SpecialError; } + static inline bool IsProfile(const char *name); + inline void SetBootFound(bool b) { m_bBootFoundFile = b; } + inline UIMemcardBase *GetScreen() { return m_pFEScreen; } + inline char *GetHeader() { return reinterpret_cast< char * >(m_Header); } + inline char *GetData() { return m_pBuffer; } + + // Non-inline functions + MemoryCard(); + ~MemoryCard(); + static bool IsCardAvailable(); + static void SetExtraParam(SaveType t, const char *filename, void *buf, unsigned int size); + static int GetEntryCount(); + void InitCommand(int op); + static bool FoundInBoot(); + void RequestTask(int op, const char *name); + void ProcessTask(); + static int GetImageIndex(const char *name); + static int GetImageFileProfileNameLength(const char *name); + void BuildImageDisplayName(); + static bool IsCardBusy(); + void Init(); + void Stop(); + void StartBootSequence(); + void EndBootSequence(); + bool CheckForOldSaveFile(); + static void LoadLocale(eLanguages eLang); + int GetPrefixLength(); + const char *GetPrefix(); + static const char *GetLocaleString(int strID); + static const unsigned short *GetCheckCardString(); + void RefreshActiveCard(); + void SetActiveCard(RealmcIface::CardId cardId); + static void SetMessageMode(unsigned int msg, bool flag); + static void TickCardRemoval(); + void Tick(int TickCount); + void MessageDone(RealmcIface::MessageChoices nInput); + void BootupCheck(const char *entry); + bool ShouldDoAutoSave(bool bForce); + void StartAutoSave(bool bForce); + void DoAutoSave(); + void EndAutoSave(); + void StartListingOldSaveFiles(); + void EndListingOldSaveFiles(); + void SetMonitor(bool bEnabled); + void SetAutoSaveEnabled(bool bEnabled); + void ShowOnlyAutoSaveMessages(); + void ShowOnlyCreateListMessages(); + void ShowMessages(bool bShow); + void CheckCard(int iSlot); + void FakeLoad(int iSlot); + void LoadYNCF(int iSlot); + void Save(const char *entryName); + void List(const char *filter, RealmcIface::TitleInfo *titleInfo); + void Load(const char *filename); + void Delete(const char *filename); + void ListOldSaveFilesNGC(); + void ListOldSaveFilesPC(); + void ListOldSaveFilesPS2(); + void ListOldSaveFilesXbox(); + void ReleasePendingMessage(); + void HandleAutoSaveError(); + void HandleAutoSaveOverwriteMessage(); + void ShowAutoSaveIcon(); + void HideAutoSaveIcon(); + bool IsAutoSaveIconVisible(); +}; + void InitMemoryCard(); +void CaptureJoyOp(MemoryCardJoyLoggableEvents op); +void DisplayStatus(int status); + +#include "MemoryCardImp.hpp" + +inline bool MemoryCard::IsProfile(const char *name) { + return name[s_pThis->m_pImp->GetPrefix() - name] == 'P'; +} #endif diff --git a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardCallbacks.cpp b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardCallbacks.cpp index e69de29bb..61afe04d7 100644 --- a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardCallbacks.cpp +++ b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardCallbacks.cpp @@ -0,0 +1,702 @@ +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" + +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Misc/Joylog.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void DisplayUnicode(const wchar_t* str) { + const short* pWChar = reinterpret_cast< const short* >(str); + if (*pWChar == 0) { + return; + } + do { + pWChar++; + } while (*pWChar != 0); +} + +void DisplayMessage(const wchar_t* msg, unsigned int count, const wchar_t** str) { + DisplayUnicode(msg); + if (count != 0) { + for (unsigned int i = 0; i < count; i++) { + DisplayUnicode(str[i]); + } + } +} + +extern char g_GC_Disk_GameName[]; + +void DisplayStatus(int i) {} + +MemcardCallbacks gMemcardCallbacks; + +MemoryCard* MemcardCallbacks::GetMemcard() { return MemoryCard::GetInstance(); } +UIMemcardBase* MemcardCallbacks::GetScreen() { return MemoryCard::GetInstance()->GetScreen(); } + +void MemcardCallbacks::ShowMessage(const wchar_t* msg, unsigned int nOptions, + const wchar_t** options) { + if (GetMemcard()->IsMemcardScreenExiting()) { + return; + } + JLog(MJ_ShowMesssage); + Joylog::AddOrGetData( + reinterpret_cast(const_cast(msg)), + JOYLOG_CHANNEL_MEMORY_CARD); + unsigned int loggedOptions = Joylog::AddOrGetData(nOptions, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + for (unsigned int i = 0; i < loggedOptions; i++) { + Joylog::AddOrGetData( + reinterpret_cast(const_cast(options[i])), + JOYLOG_CHANNEL_MEMORY_CARD); + } + DisplayMessage(msg, loggedOptions, options); + GetMemcard()->SetWaitingForResponse(true); + if (GetMemcard()->IsAutoSaving() && gMemcardSetup.GetMethod() != 0xb0) { + if (loggedOptions == 0) { + GetMemcard()->SetWaitingForResponse(false); + } else { + GetMemcard()->m_PendingMessage = + new (__FILE__, __LINE__) MemoryCardMessage(msg, loggedOptions, options); + GetMemcard()->HandleAutoSaveError(); + } + } else { + int op = GetMemcard()->GetOp(); + switch (op) { + case MemoryCard::MO_FakeLoad: + case MemoryCard::MO_LoadYNCF: + if (loggedOptions == 0) { + break; + } + // fallthrough + default: { + UIMemcardBase* pScreen = GetScreen(); + if (pScreen != nullptr) { + if (pScreen->IsInButtonAnimation()) { + if (GetMemcard()->GetPendingMessage() != nullptr) { + GetMemcard()->ReleasePendingMessage(); + } + GetMemcard()->m_PendingMessage = + new (__FILE__, __LINE__) + MemoryCardMessage(msg, loggedOptions, options); + } else { + GetScreen()->ShowMessage(msg, loggedOptions, options[0], + options[1], options[2]); + } + } + break; + } + } + } +} + +void MemcardCallbacks::ClearMessage() { + if (!GetMemcard()->IsAutoSaving()) { + JLog(MJ_ClearMessage); + int op = GetMemcard()->GetOp(); + switch (op) { + case MemoryCard::MO_FakeLoad: + case MemoryCard::MO_LoadYNCF: + break; + default: { + UIMemcardBase* pScreen = GetScreen(); + if (pScreen != nullptr) { + GetMemcard(); + } + break; + } + } + } +} + +void MemcardCallbacks::BootupCheckDone(RealmcIface::CardStatus status, + RealmcIface::BootupCheckResults res) { + JLog(MJ_BootupCheckDone); + status = static_cast( + Joylog::AddOrGetData(static_cast(status), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD)); + res.mEntryFound = + Joylog::AddOrGetData(static_cast(res.mEntryFound), 1, + JOYLOG_CHANNEL_MEMORY_CARD) != 0; + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + GetMemcard()->m_pImp->DestructSaveInfo(); + GetMemcard()->m_LastError = static_cast(status); + GetMemcard()->m_SpecialError = static_cast(status); + if ((status != RealmcIface::STATUS_OK && + GetMemcard()->GetPendingMessage() != nullptr) || + status == RealmcIface::STATUS_UNKNOWN) { + GetMemcard()->ReleasePendingMessage(); + MemoryCard* mc = GetMemcard(); + const char* entry; + if (GetMemcard()->IsAutoLoading() && !FEDatabase->bProfileLoaded) { + entry = GetScreen()->m_FileName; + } else { + entry = nullptr; + } + mc->BootupCheck(entry); + return; + } + GetMemcard()->m_pImp->BootupCheckDone(status, &res); + GetMemcard()->SetBootFound(res.mEntryFound); + if (GetMemcard()->m_bRetryBootCheck) { + GetScreen()->SetStringCheckingCard(); + } else { + cFEng* feng = cFEng::Get(); + UIMemcardBase* scr = GetScreen(); + feng->QueueGameMessage(0x461a18ee, scr->GetPackageName(), 0xff); + } +} + +void MemcardCallbacks::SaveCheckDone(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) { + JLog(MJ_SaveCheckDone); +} + +void MemcardCallbacks::SaveDone(const char* filename) { + JLog(MJ_SaveDone); + Joylog::AddOrGetData(const_cast(filename), JOYLOG_CHANNEL_MEMORY_CARD); + if (GetMemcard()->IsTypeProfile()) { + bFree(GetMemcard()->m_pBuffer); + } + GetMemcard()->m_pImp->DestructSaveInfo(); + GetMemcard()->m_pBuffer = nullptr; + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + FEDatabase->bProfileLoaded = true; + FEDatabase->bIsOptionsDirty = false; + GetMemcard()->m_bCardRemoved = false; + if (GetMemcard()->IsManualSave() && gMemcardSetup.GetMethod() != 0xb0) { + if (FEDatabase->GetGameplaySettings()->AutoSaveOn) { + GetMemcard()->m_bRetryAutoSave = false; + GetMemcard()->SetAutoSaveEnabled(true); + } else { + cFEng::Get()->QueueGameMessage(0x461a18ee, nullptr, 0xff); + } + } else if (GetMemcard()->IsAutoSaving() || gMemcardSetup.GetMethod() == 0xb0) { + GetMemcard()->m_bAutoSaveCardPulled = false; + if (GetMemcard()->m_bFoundAutoSaveFile) { + FEDatabase->bAutoSaveOverwriteConfirmed = true; + } + if (GetMemcard()->m_bRetryAutoSave) { + GetMemcard()->ShowMessages(false); + GetMemcard()->m_bRetryAutoSave = false; + GetMemcard()->SetAutoSaveEnabled(true); + } + GetMemcard()->EndAutoSave(); + if (gMemcardSetup.GetMethod() == 0xb0) { + cFEng::Get()->QueueGameMessage(0x461a18ee, nullptr, 0xff); + } + } +} + +RealmcIface::DataStatus MemcardCallbacks::CheckLoadedData(const char* data) { + JLog(MJ_CheckLoadedData); + return RealmcIface::DATA_OK; +} + +void MemcardCallbacks::LoadDone(const char* filename) { + JLog(MJ_LoadDone); + Joylog::AddOrGetData(const_cast(filename), JOYLOG_CHANNEL_MEMORY_CARD); + char* header = GetMemcard()->GetHeader(); + if (Joylog::IsReplaying()) { + Joylog::GetData(header, 8, JOYLOG_CHANNEL_MEMORY_CARD); + } + if (Joylog::IsCapturing()) { + Joylog::AddData(header, 8, JOYLOG_CHANNEL_MEMORY_CARD); + } + char* data = GetMemcard()->GetData(); + unsigned int size = GetMemcard()->GetSize(); + if (Joylog::IsReplaying()) { + Joylog::GetData(data, size, JOYLOG_CHANNEL_MEMORY_CARD); + } + if (Joylog::IsCapturing()) { + Joylog::AddData(data, size, JOYLOG_CHANNEL_MEMORY_CARD); + } + unsigned int* pHeader = + reinterpret_cast(GetMemcard()->GetHeader()); + unsigned int iStoredVersion = pHeader[0]; + unsigned int iStoredSize = pHeader[1]; + MemoryCard::GetInstance()->m_MemOp = MemoryCard::MO_NONE; + if (iStoredVersion == 0x10d && iStoredSize == GetMemcard()->GetSize() && + GetMemcard()->IsTypeProfile()) { + bool isProfileValid = FEDatabase->LoadUserProfileFromBuffer( + GetMemcard()->GetData(), GetMemcard()->GetSize(), + GetMemcard()->GetPlayerNum()); + if (isProfileValid) { + FEDatabase->DeallocBackupDB(); + if (GetMemcard()->GetPlayerNum() != 0) { + if (GetMemcard()->m_pBuffer != nullptr) { + bFree(GetMemcard()->m_pBuffer); + GetMemcard()->m_pBuffer = nullptr; + } + cFEng::Get()->QueueGameMessage(0x461a18ee, nullptr, 0xff); + return; + } + FEDatabase->bProfileLoaded = true; + GetMemcard()->m_bCardRemoved = false; + if (FEDatabase->GetGameplaySettings()->AutoSaveOn) { + if (GetMemcard()->m_pBuffer != nullptr) { + bFree(GetMemcard()->m_pBuffer); + GetMemcard()->m_pBuffer = nullptr; + } + GetMemcard()->SetAutoSaveEnabled(true); + } else { + cFEng* feng = cFEng::Get(); + unsigned int msg = 0x461a18ee; + if (gMemcardSetup.GetMethod() == 0x20) { + msg = 0xa4bb7ae1; + } + feng->QueueGameMessage(msg, nullptr, 0xff); + } + } else { + GetMemcard()->ShowMessages(false); + FEDatabase->RestoreFromBackupDB(); + cFEng::Get()->QueueGameMessage(0xf35d144e, nullptr, 0xff); + } + } else { + FEDatabase->RestoreFromBackupDB(); + cFEng::Get()->QueueGameMessage(0xf35d144e, nullptr, 0xff); + } + if (GetMemcard()->m_pBuffer != nullptr) { + bFree(GetMemcard()->m_pBuffer); + GetMemcard()->m_pBuffer = nullptr; + } + FEDatabase->DeallocBackupDB(); +} + +void MemcardCallbacks::DeleteDone(const char* filename) { + JLog(MJ_DeleteDone); + Joylog::AddOrGetData(const_cast(filename), JOYLOG_CHANNEL_MEMORY_CARD); + int idx = GetMemcard()->GetPrefixLength(); + if (bStrCmp(filename + idx, + FEDatabase->GetUserProfile(0)->GetProfileName()) == 0) { + FEDatabase->DefaultProfile(); + FEDatabase->bProfileLoaded = false; + } + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + cFEng::Get()->QueueGameMessage(0x461a18ee, GetScreen()->GetPackageName(), + 0xff); +} + +void MemcardCallbacks::ClearEntries() { + JLog(MJ_ClearEntries); +} + +void MemcardCallbacks::FoundEntry(const RealmcIface::EntryInfo* info) { + JLog(MJ_FoundEntry); + Joylog::AddOrGetData(const_cast(info->mName), JOYLOG_CHANNEL_MEMORY_CARD); + const_cast(info)->mStatus = + static_cast(Joylog::AddOrGetData( + static_cast(info->mStatus), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD)); + const_cast(info)->mEntryBlocks = + Joylog::AddOrGetData(info->mEntryBlocks, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + const_cast(info)->mUserDataSize = + Joylog::AddOrGetData(info->mUserDataSize, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + const_cast(info)->mTime.mCreated = + Joylog::AddOrGetData(info->mTime.mCreated, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + const_cast(info)->mTime.mLastAccessed = + Joylog::AddOrGetData(info->mTime.mLastAccessed, 0x20, + JOYLOG_CHANNEL_MEMORY_CARD); + const_cast(info)->mTime.mLastModified = + Joylog::AddOrGetData(info->mTime.mLastModified, 0x20, + JOYLOG_CHANNEL_MEMORY_CARD); + Joylog::AddOrGetData(const_cast(info->mCompanyCode), + JOYLOG_CHANNEL_MEMORY_CARD); + Joylog::AddOrGetData(const_cast(info->mGameCode), + JOYLOG_CHANNEL_MEMORY_CARD); + if (GetMemcard()->IsListingOldSaveFiles()) { + GetMemcard()->m_bOldSaveFileExists = true; + } else if (GetMemcard()->IsCheckingCardForOverwrite()) { + GetMemcard()->m_bFoundAutoSaveFile = true; + } else { + if (bStrNCmp(g_GC_Disk_GameName, info->mGameCode, 4) == 0) { + unsigned int fDefault = 0; + unsigned int iSize = GetMemcard()->GetSize(); + int iGuessSize = info->mUserDataSize; + if (info->mStatus != RealmcIface::STATUS_OK) { + fDefault = 2; + } + if (GetMemcard()->IsTypeProfile()) { + unsigned int sec = GetMemcard()->GetSize(); + GetScreen()->AddItem(info->mName, "", iGuessSize, fDefault); + } else { + if (info->mStatus != RealmcIface::STATUS_OK) { + return; + } + int idx = GetMemcard()->m_EntryCount; + bStrNCpy(GetMemcard()->m_pBuffer + idx * 0x10, info->mName, + 0x10); + } + GetMemcard()->m_EntryCount++; + } + } +} + +void MemcardCallbacks::FindEntriesDone(RealmcIface::CardStatus status) { + JLog(MJ_FindEntriesDone); + Joylog::AddOrGetData(static_cast(status), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD); + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + GetMemcard()->m_bListingForCreate = false; + if (GetMemcard()->IsListingOldSaveFiles()) { + GetMemcard()->EndListingOldSaveFiles(); + } else if (GetMemcard()->IsCheckingCardForOverwrite()) { + GetMemcard()->m_bCheckingCardForOverwrite = false; + if (GetMemcard()->m_bFoundAutoSaveFile) { + GetMemcard()->HandleAutoSaveOverwriteMessage(); + } else { + GetMemcard()->DoAutoSave(); + } + } else { + cFEng::Get()->QueueGameMessage(0x5a051729, + GetScreen()->GetPackageName(), 0xff); + GetMemcard()->SetBootFound(GetMemcard()->m_EntryCount > 0); + } +} + +void MemcardCallbacks::Retry(RealmcIface::CardStatus status) { + JLog(MJ_Retry); + Joylog::AddOrGetData(static_cast(status), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD); + if (GetScreen() != nullptr) { + GetScreen()->SetStringCheckingCard(); + if (GetMemcard()->GetOp() == MemoryCard::MO_List) { + GetScreen()->EmptyFileList(); + } + } +} + +void MemcardCallbacks::Failed(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) { + JLog(MJ_Failed); + JLog(status); + JLog(result); + if (GetMemcard()->IsWaitingForResponse() && + (GetMemcard()->GetOp() == MemoryCard::MO_Delete || + GetMemcard()->GetOp() == MemoryCard::MO_Load)) { + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + if (GetMemcard()->GetOp() == MemoryCard::MO_Delete) { + GetMemcard()->Delete(nullptr); + } else { + GetMemcard()->Load(nullptr); + } + return; + } + unsigned int msg = 0x8867412d; + if (GetMemcard()->m_pBuffer != nullptr) { + bFree(GetMemcard()->m_pBuffer); + GetMemcard()->m_pBuffer = nullptr; + } + if (GetMemcard()->m_pImp->GetSaveInfo() != nullptr) { + GetMemcard()->m_pImp->DestructSaveInfo(); + } + if (GetMemcard()->IsAutoSaving() || + GetMemcard()->IsCheckingCardForAutoSave()) { + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + GetMemcard()->EndAutoSave(); + if (gMemcardSetup.GetMethod() == 0xb0) { + cFEng::Get()->QueueGameMessage(0x8867412d, nullptr, 0xff); + } + FEDatabase->GetGameplaySettings()->AutoSaveOn = false; + return; + } + if (GetMemcard()->IsListingOldSaveFiles()) { + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + GetMemcard()->EndListingOldSaveFiles(); + return; + } + if (GetMemcard()->IsRetryingAutoSave()) { + GetMemcard()->SetRetryAutoSave(false); + FEDatabase->GetGameplaySettings()->AutoSaveOn = false; + if (result == RealmcIface::RESULT_CANCELLED || + status == RealmcIface::STATUS_CARD_DAMAGED) { + msg = 0xfe202e3b; + } + } + if (gMemcardSetup.GetMethod() == 0x60 && + GetMemcard()->GetOp() == MemoryCard::MO_List) { + GetMemcard()->SetListingForCreate(false); + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + cFEng::Get()->QueueGameMessage(0x5a051729, + GetScreen()->GetPackageName(), 0xff); + return; + } + int op = GetMemcard()->GetOp(); + switch (op) { + case MemoryCard::MO_AutoSave: + break; + + case MemoryCard::MO_Save: + if (status == RealmcIface::STATUS_NO_CARD) + goto failed_check_autosave; + if (static_cast(status) >= + static_cast(RealmcIface::STATUS_NO_CARD)) { + if (static_cast(status) <= + static_cast(RealmcIface::STATUS_CARD_FULL)) { + if (static_cast(status) >= + static_cast(RealmcIface::STATUS_WRONG_DEVICE)) + goto failed_check_autosave; + } + } + goto failed_skip_autosave; + failed_check_autosave: + if (gMemcardSetup.GetMethod() == 0x60) { + FEDatabase->GetGameplaySettings()->AutoSaveOn = false; + } + failed_skip_autosave: + msg = 0xdc12af2e; + FEDatabase->GetGameplaySettings()->AutoSaveOn = false; + + case MemoryCard::MO_Load: + if (GetMemcard()->IsTypeProfile()) { + bFree(GetMemcard()->m_pBuffer); + } + GetMemcard()->m_pBuffer = nullptr; + GetMemcard()->m_SpecialError = static_cast(status); + break; + + case MemoryCard::MO_BootUp: + GetMemcard()->m_pImp->DestructSaveInfo(); + break; + + case MemoryCard::MO_List: + if (GetMemcard()->InBootSequence()) { + msg = 0x8867412d; + } + break; + } + GetMemcard()->m_LastError = static_cast(status); + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + DisplayStatus(static_cast(status)); + if (status == RealmcIface::STATUS_FILE_CORRUPTED) { + GetMemcard()->BootupCheck(nullptr); + GetMemcard()->m_bRetryBootCheck = true; + } else { + cFEng::Get()->QueueGameMessage(msg, GetScreen()->GetPackageName(), + 0xff); + } +} + +void MemcardCallbacks::CardChanged(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) { + if ((result == RealmcIface::RESULT_RETRY && + status == RealmcIface::STATUS_CARD_CHANGED) || + status == RealmcIface::STATUS_OK) { + cFEng::Get()->QueueGameMessage(0x3a2be557, nullptr, 0xff); + } else if (result == RealmcIface::RESULT_CANCELLED) { + cFEng::Get()->QueueGameMessage(0x8867412d, nullptr, 0xff); + } +} + +void MemcardCallbacks::CardChecked(const RealmcIface::CardInfo* info) { + JLog(MJ_CardChecked); + const_cast(info)->mCardId = + static_cast(Joylog::AddOrGetData( + static_cast(info->mCardId), 0x20, + JOYLOG_CHANNEL_MEMORY_CARD)); + const_cast(info)->mStatus = + static_cast(Joylog::AddOrGetData( + static_cast(info->mStatus), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD)); + const_cast(info)->mFreeSpace = + Joylog::AddOrGetData(info->mFreeSpace, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + const_cast(info)->mFreeFiles = + Joylog::AddOrGetData(info->mFreeFiles, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + const_cast(info)->mTotalSpace = + Joylog::AddOrGetData(info->mTotalSpace, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + const_cast(info)->mFreeSpaceOverLimit = + Joylog::AddOrGetData(static_cast(info->mFreeSpaceOverLimit), + 1, JOYLOG_CHANNEL_MEMORY_CARD) != 0; + const_cast(info)->mTotalSpaceOverLimit = + Joylog::AddOrGetData( + static_cast(info->mTotalSpaceOverLimit), 1, + JOYLOG_CHANNEL_MEMORY_CARD) != 0; + unsigned int msg = 0x8867412d; + if (GetMemcard()->IsCheckingCardForAutoSave()) { + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + GetMemcard()->m_LastError = + *reinterpret_cast( + reinterpret_cast(info) + 6); + int cardStatus = info->mStatus; + switch (cardStatus) { + case RealmcIface::STATUS_CARD_CHANGED: + case RealmcIface::STATUS_CARD_DAMAGED: + case RealmcIface::STATUS_WRONG_DEVICE: + case RealmcIface::STATUS_CARD_FULL: + case RealmcIface::STATUS_ACCESS_DENIED: + GetMemcard()->m_bFoundAutoSaveFile = true; + GetMemcard()->DoAutoSave(); + return; + case RealmcIface::STATUS_OK: { + if (!FEDatabase->bAutoSaveOverwriteConfirmed) { + GetMemcard()->m_bCheckingCardForAutoSave = false; + GetMemcard()->m_bCheckingCardForOverwrite = true; + GetMemcard()->ShowMessages(true); + char filter[32]; + UserProfile* profile = FEDatabase->GetMultiplayerProfile(0); + bStrCat(filter, GetMemcard()->GetPrefix(), + profile->GetProfileName()); + GetMemcard()->m_bFoundAutoSaveFile = false; + GetMemcard()->List(filter, nullptr); + return; + } + GetMemcard()->DoAutoSave(); + return; + } + case RealmcIface::STATUS_NO_CARD: + GetMemcard()->HandleAutoSaveError(); + return; + default: + return; + } + } else { + MemoryCard::SetMessageMode(1, true); + if (info->mStatus == RealmcIface::STATUS_OK) { + msg = 0x461a18ee; + } + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + GetMemcard()->m_LastError = + *reinterpret_cast( + reinterpret_cast(info) + 6); + UIMemcardBase* pScreen = GetScreen(); + if (msg == 0) { + return; + } + if (pScreen == nullptr) { + return; + } + cFEng::Get()->QueueGameMessage(msg, pScreen->GetPackageName(), 0xff); + } +} + +void MemcardCallbacks::CardRemoved() { + JLog(MJ_CardRemoved); + GetMemcard()->m_bAutoSaveCardPulled = true; + if (GetMemcard()->GetOp() == MemoryCard::MO_Save) { + GetMemcard()->m_bAutoSaveCardPulledDuringSave = true; + } + if (GetMemcard()->IsCheckingCardForOverwrite()) { + GetMemcard()->HandleAutoSaveError(); + } else { + if (FEDatabase->GetGameplaySettings()->AutoSaveOn) { + if (!MemoryCard::GetInstance()->IsAutoSaving()) { + GetMemcard()->m_bCardRemoved = true; + } + } + FEDatabase->GetGameplaySettings()->AutoSaveOn = false; + if (FEDatabase->IsOptionsMode()) { + cFEng::Get()->QueueGameMessage(0x7e998e5e, nullptr, 0xff); + } + FEDatabase->bAutoSaveOverwriteConfirmed = false; + } +} + +void MemcardCallbacks::SetAutosaveDone(RealmcIface::TaskResult res, + RealmcIface::CardStatus status, + RealmcIface::AutosaveState flag) { + JLog(MJ_SetAutosaveDone); + Joylog::AddOrGetData(static_cast(res), 8, + JOYLOG_CHANNEL_MEMORY_CARD); + status = static_cast( + Joylog::AddOrGetData(static_cast(status), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD)); + flag = static_cast( + Joylog::AddOrGetData(static_cast(flag), 0x20, + JOYLOG_CHANNEL_MEMORY_CARD)); + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + GetMemcard()->m_bAutoSave = (flag == RealmcIface::AUTOSAVE_ENABLE); + GetMemcard()->m_bAutoSaveCardPulled = false; + GetMemcard()->m_bAutoSaveCardPulledDuringSave = false; + if (GetMemcard()->m_bDisablingAutoSaveForSave) { + GetMemcard()->m_bDisablingAutoSaveForSave = false; + GetMemcard()->ShowMessages(true); + cFEng::Get()->QueueGameMessage(0xc6c6b68f, + GetMemcard()->IsMemcardScreenShowing() + ? gMemcardSetup.mMemScreen + : nullptr, + 0xff); + } else { + unsigned int msg = 0x461a18ee; + if (status != RealmcIface::STATUS_OK && + flag != RealmcIface::AUTOSAVE_ENABLE) { + if (status == RealmcIface::STATUS_NO_CARD) { + msg = 0xb57fdb17; + FEDatabase->GetGameplaySettings()->AutoSaveOn = false; + } else { + FEDatabase->GetGameplaySettings()->AutoSaveOn = false; + } + } + if (gMemcardSetup.mPreviousCommand == 0x20) { + msg = 0xa4bb7ae1; + } + if (GetMemcard()->IsAutoSaving()) { + if (flag != RealmcIface::AUTOSAVE_ENABLE && + FEDatabase->GetGameplaySettings()->AutoSaveOn) { + FEDatabase->GetGameplaySettings()->AutoSaveOn = false; + GetMemcard()->m_bCardRemoved = true; + } + GetMemcard()->EndAutoSave(); + } else { + cFEng::Get()->QueueGameMessage(msg, nullptr, 0xff); + } + if (flag == RealmcIface::AUTOSAVE_ENABLE) { + if (gMemcardSetup.GetMethod() == 0xa0 && + FEDatabase->IsOptionsMode()) { + FEDatabase->bAutoSaveOverwriteConfirmed = false; + } + FEDatabase->GetGameplaySettings()->AutoSaveOn = true; + GetMemcard()->m_bCardRemoved = false; + } + } +} + +void MemcardCallbacks::SetMonitorDone(RealmcIface::CardStatus status, + RealmcIface::MonitorState state) { + JLog(MJ_SetMonitorDone); + status = static_cast( + Joylog::AddOrGetData(static_cast(status), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD)); + state = static_cast( + Joylog::AddOrGetData(static_cast(state), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD)); + GetMemcard()->m_MemOp = MemoryCard::MO_NONE; + GetMemcard()->m_bMonitorOn = + (static_cast(state) - 1u < 2u); + unsigned int msg; + if (state == RealmcIface::MONITOR_ON) { + if (status == RealmcIface::STATUS_OK) { + msg = 0x54b3ac6c; + } else { + msg = 0x8867412d; + } + } else { + if (cFEng::Get()->IsPackagePushed("MemCard.fng")) { + msg = 0xeb29392a; + } else { + msg = 0; + if (MemoryCard::GetInstance()->IsMemcardScreenShowing()) { + msg = 0x8867412d; + } + } + } + cFEng::Get()->QueueGameMessage(msg, nullptr, 0xff); +} + +RealmcIface::TaskStatus MemcardCallbacks::LoadReady(const char* entryName, + unsigned int headerSize, + unsigned int bodySize, + char*& headerData, + char*& bodyData) { + JLog(MJ_LoadReady); + Joylog::AddOrGetData(const_cast(entryName), + JOYLOG_CHANNEL_MEMORY_CARD); + RealmcIface::TaskStatus res = RealmcIface::TASK_CANCEL; + headerSize = Joylog::AddOrGetData(headerSize, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + bodySize = Joylog::AddOrGetData(bodySize, 0x20, JOYLOG_CHANNEL_MEMORY_CARD); + if (headerSize == 8 && bodySize == GetMemcard()->GetSize()) { + res = RealmcIface::TASK_CONTINUE; + bodyData = GetMemcard()->GetData(); + headerData = GetMemcard()->GetHeader(); + } + return res; +} diff --git a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardHelper.hpp b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardHelper.hpp index 2b2cd201a..fb3449197 100644 --- a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardHelper.hpp +++ b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardHelper.hpp @@ -5,6 +5,231 @@ #pragma once #endif +#include +#include +#include "RealmcIface.hpp" +#include "Speed/Indep/Src/Misc/Joylog.hpp" + +struct IAllocator; + +struct IThread { + virtual ~IThread() {} + virtual int AddRef() = 0; + virtual int Release() = 0; + virtual IThread* CreateInstance() = 0; + virtual void SetStackSize(unsigned int stacksize) = 0; + virtual void Begin(int (*func)(void*)) = 0; + virtual void WaitForEnd(int) = 0; + virtual void Sleep(int ticks) = 0; + virtual int (*GetEntryFunc())(void*) = 0; + virtual bool IsActive() = 0; +}; + +struct IMutex { + virtual ~IMutex() {} + virtual int AddRef() = 0; + virtual int Release() = 0; + virtual IMutex* CreateInstance() = 0; + virtual void Lock() = 0; + virtual void Unlock() = 0; +}; + +struct THREAD { + int reserved[198]; // offset 0x0, size 0x318 +}; + +void THREAD_destroy(THREAD* thread); + +struct MyThread : public IThread { + int mRefcount; // offset 0x4, size 0x4 + int (*mEntryFunc)(void*); // offset 0x8, size 0x4 + unsigned int mStackSize; // offset 0xC, size 0x4 + void* mStackBuffer; // offset 0x10, size 0x4 + THREAD mThreadData; // offset 0x14, size 0x318 + int mPriority; // offset 0x32C, size 0x4 + bool mActive; // offset 0x330, size 0x1 + + MyThread() { + mRefcount = 1; + mStackSize = 0x1000; + mStackBuffer = nullptr; + memset(&mThreadData, 0, sizeof(THREAD)); + mPriority = 0; + mActive = false; + } + + ~MyThread() { + if (mActive) { + WaitForEnd(0); + THREAD_destroy(&mThreadData); + } + } + + int AddRef() override; + int Release() override; + IThread* CreateInstance() override; + void SetStackSize(unsigned int stacksize) override { mStackSize = stacksize; } + void Begin(int (*func)(void*)) override; + void WaitForEnd(int) override; + void Sleep(int ticks) override; + void SetPriority(int priority) override; + static int EntryProc(void* pContext); + int (*GetEntryFunc())(void*) override; + bool IsActive() override; +}; + +struct MyMutex : public IMutex { + MUTEX mMutex; // offset 0x4, size 0x1C + int mRefcount; // offset 0x20, size 0x4 + + MyMutex() { + memset(&mMutex, 0, sizeof(MUTEX)); + mRefcount = 1; + MUTEX_create(&mMutex); + } + + ~MyMutex() { + MUTEX_destroy(&mMutex); + } + + int AddRef() override; + int Release() override; + IMutex* CreateInstance() override; + void Lock() override; + void Unlock() override; +}; + +namespace Realmc { + +// total size: 0x10 +struct SystemInterface { + IAllocator *mAllocator; // offset 0x0, size 0x4 + IThread *mThread; // offset 0x4, size 0x4 + IMutex *mMutex; // offset 0x8, size 0x4 + const char *(*mGetStrCallback)(int); // offset 0xC, size 0x4 + + void Clear(); +}; + +} // namespace Realmc + +struct MemoryCard; +struct UIMemcardBase; + +struct IGameInterface { + virtual void ShowMessage(const wchar_t *msg, unsigned int nOptions, + const wchar_t **options) = 0; + virtual void ClearMessage() = 0; + virtual void BootupCheckDone(RealmcIface::CardStatus status, + RealmcIface::BootupCheckResults res) = 0; + virtual void SaveCheckDone(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) = 0; + virtual void SaveDone(const char *filename) = 0; + virtual RealmcIface::DataStatus CheckLoadedData(const char *data) = 0; + virtual void LoadDone(const char *filename) = 0; + virtual void DeleteDone(const char *filename) = 0; + virtual void ClearEntries() = 0; + virtual void FoundEntry(const RealmcIface::EntryInfo *info) = 0; + virtual void FindEntriesDone(RealmcIface::CardStatus status) = 0; + virtual void Retry(RealmcIface::CardStatus status) = 0; + virtual void Failed(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) = 0; + virtual void CardChanged(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) = 0; + virtual void CardChecked(const RealmcIface::CardInfo *info) = 0; + virtual void CardRemoved() = 0; + virtual void SetAutosaveDone(RealmcIface::TaskResult res, + RealmcIface::CardStatus status, + RealmcIface::AutosaveState flag) = 0; + virtual void SetMonitorDone(RealmcIface::CardStatus status, + RealmcIface::MonitorState state) = 0; + virtual RealmcIface::TaskStatus LoadReady(const char *entryName, + unsigned int headerSize, + unsigned int bodySize, + char *&headerData, + char *&bodyData) = 0; +}; + +enum MemoryCardJoyLoggableEvents { + MJ_None = 0, + MJ_ShowMesssage = 1, + MJ_ClearMessage = 2, + MJ_BootupCheckDone = 3, + MJ_SaveCheckDone = 4, + MJ_SaveDone = 5, + MJ_CheckLoadedData = 6, + MJ_LoadDone = 7, + MJ_DeleteDone = 8, + MJ_ClearEntries = 9, + MJ_FoundEntry = 10, + MJ_FindEntriesDone = 11, + MJ_Retry = 12, + MJ_Failed = 13, + MJ_CardChecked = 14, + MJ_CardRemoved = 15, + MJ_SetAutosaveDone = 16, + MJ_LoadReady = 17, + MJ_SetMonitorDone = 18, +}; + +struct IJoyHelper { + static void EmulateMemoryCardLibrary(int aJoyOp); + + inline void JLog(MemoryCardJoyLoggableEvents op) { + if (Joylog::IsCapturing()) + Joylog::AddData(static_cast< int >(op), 8, JOYLOG_CHANNEL_MEMORY_CARD); + } + + inline void JLog(RealmcIface::CardStatus &status) { + status = static_cast( + Joylog::AddOrGetData(static_cast(status), 0x10, + JOYLOG_CHANNEL_MEMORY_CARD)); + } + + inline void JLog(RealmcIface::TaskResult &res) { + res = static_cast( + Joylog::AddOrGetData(static_cast(res), 8, + JOYLOG_CHANNEL_MEMORY_CARD)); + } +}; + +struct MemcardCallbacks : public IGameInterface, public IJoyHelper { + MemoryCard *GetMemcard(); + UIMemcardBase *GetScreen(); + + void ShowMessage(const wchar_t *msg, unsigned int nOptions, + const wchar_t **options) override; + void ClearMessage() override; + void BootupCheckDone(RealmcIface::CardStatus status, + RealmcIface::BootupCheckResults res) override; + void SaveCheckDone(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) override; + void SaveDone(const char *filename) override; + RealmcIface::DataStatus CheckLoadedData(const char *data) override; + void LoadDone(const char *filename) override; + void DeleteDone(const char *filename) override; + void ClearEntries() override; + void FoundEntry(const RealmcIface::EntryInfo *info) override; + void FindEntriesDone(RealmcIface::CardStatus status) override; + void Retry(RealmcIface::CardStatus status) override; + void Failed(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) override; + void CardChanged(RealmcIface::TaskResult result, + RealmcIface::CardStatus status) override; + void CardChecked(const RealmcIface::CardInfo *info) override; + void CardRemoved() override; + void SetAutosaveDone(RealmcIface::TaskResult res, + RealmcIface::CardStatus status, + RealmcIface::AutosaveState flag) override; + void SetMonitorDone(RealmcIface::CardStatus status, + RealmcIface::MonitorState state) override; + RealmcIface::TaskStatus LoadReady(const char *entryName, + unsigned int headerSize, + unsigned int bodySize, char *&headerData, + char *&bodyData) override; + + inline MemcardCallbacks() {} +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardImp.hpp b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardImp.hpp index 0846cc711..366bb1584 100644 --- a/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardImp.hpp +++ b/src/Speed/Indep/Src/Frontend/MemoryCard/MemoryCardImp.hpp @@ -5,6 +5,32 @@ #pragma once #endif +#include +#include "RealmcIface.hpp" + +struct MemoryCard; + +// total size: 0x8 +struct MemoryCardImp { + RealmcIface::SaveReq *m_pSaveReq; // offset 0x0, size 0x4 + RealmcIface::SaveReq m_SaveReq; // offset 0x4, size 0x8 + + inline MemoryCardImp() { + m_pSaveReq = &m_SaveReq; + m_SaveReq.mSaveInfo = nullptr; + m_SaveReq.mNumSaves = 1; + } + inline RealmcIface::SaveInfo *GetSaveInfo() { return m_SaveReq.mSaveInfo; } + inline RealmcIface::SaveReq **GetSaveReqArray() { return &m_pSaveReq; } + const char *GetPrefix(); + const char *GetTitleId(); + RealmcIface::SaveInfo *ConstructSaveInfo(MemoryCard::SaveType type, const char *DisplayName, int aSize); + void DestructSaveInfo(); + void BootupCheckDone(RealmcIface::CardStatus status, RealmcIface::BootupCheckResults *pParam); + + static unsigned short *gEntryType[3]; + static unsigned short gContentName[]; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MemoryCard/RealmcIface.hpp b/src/Speed/Indep/Src/Frontend/MemoryCard/RealmcIface.hpp new file mode 100644 index 000000000..4cd183c09 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MemoryCard/RealmcIface.hpp @@ -0,0 +1,293 @@ +#ifndef FRONTEND_MEMORYCARD_REALMCIFACE_H +#define FRONTEND_MEMORYCARD_REALMCIFACE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +namespace Realmc { +struct SystemInterface; +} // namespace Realmc +using Realmc::SystemInterface; + +struct IGameInterface; + +namespace RealmcIface { +struct GameInfo; + +enum MessageChoices { + CHOICE_NONE = 0, + CHOICE_OPTION1 = 1, + CHOICE_OPTION2 = 2, + CHOICE_OPTION3 = 3, + CHOICE_OPTION4 = 4, +}; + +enum CardStatus { + STATUS_OK = 0, + STATUS_NO_CARD = 1, + STATUS_CARD_CHANGED = 2, + STATUS_CARD_UNFORMATTED = 3, + STATUS_CARD_DAMAGED = 4, + STATUS_WRONG_DEVICE = 5, + STATUS_CARD_FULL = 6, + STATUS_ACCESS_DENIED = 7, + STATUS_INSUFFICIENT_SPACE = 8, + STATUS_FILE_NOT_FOUND = 9, + STATUS_ENTRY_NOT_FOUND = 10, + STATUS_ENTRY_ALREADY_EXISTS = 11, + STATUS_FILE_NOT_OPENED = 12, + STATUS_FILE_CORRUPTED = 13, + STATUS_DIRECTORY_NOT_FOUND = 14, + STATUS_DIRECTORY_NOT_EMPTY = 15, + STATUS_TOO_MANY_OPENED_FILES = 16, + STATUS_CANNOTMOUNT = 17, + STATUS_FILE_DELETED = 18, + STATUS_RANGE_ERROR = 19, + STATUS_CARD_REMOVED = 20, + STATUS_INACCESSIBLE_CARD = 21, + STATUS_EXIT_TO_CARD_MANAGER = 22, + STATUS_FAILED = 23, + STATUS_UNKNOWN = -1, +}; + +enum TaskResult { + RESULT_SUCCESS = 0, + RESULT_FAILED = 1, + RESULT_CANCELLED = 2, + RESULT_RETRY = 3, + RESULT_UNKNOWN = 4, +}; + +enum CardId { + PORT1_SLOT1 = 1, + PORT1_SLOT2 = 2, + PORT1_SLOT3 = 4, + PORT1_SLOT4 = 8, + PORT1_ALL = 15, + PORT1_DEFAULT = 1, + PORT2_SLOT1 = 16, + PORT2_SLOT2 = 32, + PORT2_SLOT3 = 64, + PORT2_SLOT4 = 128, + PORT2_ALL = 240, + PORT2_DEFAULT = 16, + PORT3_SLOT1 = 256, + PORT3_SLOT2 = 512, + PORT3_SLOT3 = 1024, + PORT3_SLOT4 = 2048, + PORT3_ALL = 3840, + PORT3_DEFAULT = 256, + PORT4_SLOT1 = 4096, + PORT4_SLOT2 = 8192, + PORT4_SLOT3 = 16384, + PORT4_SLOT4 = 32768, + PORT4_ALL = 61440, + PORT4_DEFAULT = 4096, + PORT5_SLOT1 = 65536, + PORT5_DEFAULT = 65536, + MAX_CARDID = 65536, + CARD_UNKNOWN = -1, +}; + +enum DataStatus { + DATA_OK = 0, + DATA_CORRUPT = 1, +}; + +enum AutosaveState { + AUTOSAVE_DISABLE = 0, + AUTOSAVE_ENABLE = 1, +}; + +enum MonitorState { + MONITOR_OFF = 0, + MONITOR_ON = 1, + MONITOR_ON_USER_CANCELLED = 2, +}; + +enum TaskStatus { + TASK_CONTINUE = 0, + TASK_CANCEL = 1, +}; + +// total size: 0xC +struct TimeInfo { + unsigned int mCreated; // offset 0x0, size 0x4 + unsigned int mLastModified; // offset 0x4, size 0x4 + unsigned int mLastAccessed; // offset 0x8, size 0x4 +}; + +// total size: 0xC +struct BootupCheckResults { + void Clear(); + + CardId mFirstGoodCard; // offset 0x0, size 0x4 + bool mEntryFound; // offset 0x4, size 0x1 + unsigned int mNumBlocksNeeded; // offset 0x8, size 0x4 +}; + +// total size: 0x1C +struct CardInfo { + CardInfo(); + void Clear(); + + CardId mCardId; // offset 0x0, size 0x4 + CardStatus mStatus; // offset 0x4, size 0x4 + unsigned int mFreeSpace; // offset 0x8, size 0x4 + unsigned int mFreeFiles; // offset 0xC, size 0x4 + unsigned int mTotalSpace; // offset 0x10, size 0x4 + bool mFreeSpaceOverLimit; // offset 0x14, size 0x1 + bool mTotalSpaceOverLimit; // offset 0x18, size 0x1 +}; + +// total size: 0x24 +struct EntryInfo { + EntryInfo(); + void Clear(); + + char *mName; // offset 0x0, size 0x4 + CardStatus mStatus; // offset 0x4, size 0x4 + unsigned int mEntryBlocks; // offset 0x8, size 0x4 + unsigned int mUserDataSize; // offset 0xC, size 0x4 + TimeInfo mTime; // offset 0x10, size 0xC + char mCompanyCode[2]; // offset 0x1C, size 0x2 + char mGameCode[4]; // offset 0x1E, size 0x4 +}; + +enum MessageState { + MESSAGE_SHOW = 0, + MESSAGE_HIDE = 1, + MESSAGE_FORCE = 2, +}; + +enum MemcardTask { + TASK_NONE = 0, + TASK_CHECKCARD = 1, + TASK_BOOTUPCHECK = 2, + TASK_SAVECHECK = 4, + TASK_SAVE = 8, + TASK_LOAD = 16, + TASK_DELETE = 32, + TASK_FINDENTRIES = 64, + TASK_SETAUTOSAVE = 128, + TASK_MONITOR = 256, +}; + +enum TitleType { + TITLE_DEFAULT = 0, + TITLE_ALTERNATE = 1, +}; + +enum NameType { + NAME_ENTRY = 0, + NAME_PATH = 1, +}; + +enum DataFormat { + FORMAT_LAYER2 = 0, + FORMAT_RAW = 1, +}; + +struct SaveInfo; +struct AutoloadEntry; +struct SaveReq { + unsigned int mNumSaves; // offset 0x0, size 0x4 + SaveInfo *mSaveInfo; // offset 0x4, size 0x4 + + SaveReq(); + void Clear(); +}; + +// total size: 0x10 +struct BootupCheckParams { + char *mEntryNamePattern; // offset 0x0, size 0x4 + unsigned int mNumSaveTypes; // offset 0x4, size 0x4 + SaveReq **mSaveReqs; // offset 0x8, size 0x4 + unsigned int mValidCardIds; // offset 0xC, size 0x4 + + void Clear(); +}; + +// total size: 0x10 +struct TitleInfo { + TitleType mTitleType; // offset 0x0, size 0x4 + unsigned int mTitleId; // offset 0x4, size 0x4 + NameType mNameType; // offset 0x8, size 0x4 + DataFormat mDataFormat; // offset 0xC, size 0x4 + + void Clear(); + void Init(TitleType titleType, unsigned int titleId, NameType nameType, DataFormat dataFormat); +}; + +struct MemcardInterfaceImpl; + +struct GameInfo { + wchar_t mGameTitle[33]; // offset 0x0, size 0x84 + unsigned int mTitleId; // offset 0x84, size 0x4 + bool mMultipleSaveTypesUsed; // offset 0x88, size 0x1 + bool mMultitapSupported; // offset 0x8C, size 0x1 + + GameInfo(const unsigned short *gameTitle, unsigned int titleId, + bool multipleSaveTypesUsed, bool multitapSupported); +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) + GameInfo(const wchar_t *gameTitle, unsigned int titleId, + bool multipleSaveTypesUsed, bool multitapSupported); +#endif + void Clear(); +}; + +struct MemcardInterface { + MemcardInterfaceImpl *mImpl; + + static MemcardInterface *CreateInstance(struct SystemInterface *iSystem, + struct IGameInterface *iGame, + struct GameInfo *gameInfo); + static const char *GetFilterForAllEntries(); + void Release(); + MemcardInterface(struct SystemInterface *iSystem, struct IGameInterface *iGame, + struct GameInfo *gameInfo); + ~MemcardInterface(); + void BootupCheck(const BootupCheckParams *params, unsigned int nEntries, + const char **entryNames, wchar_t *content); + void BootupCheck(const BootupCheckParams *params, unsigned int nEntries, + const AutoloadEntry *autoloadEntries); + void SaveCheck(const char *entryName, const SaveInfo *saveInfo, + const TitleInfo *titleInfo); + void Save(const char *entryName, const char *header, const char *body, + const SaveInfo *saveInfo, const TitleInfo *titleInfo); + void Save(const char *entryName, const char *header, const char *body, + const SaveInfo *saveInfo); + void Load(const char *entryName, char *header, char *body, + const unsigned short *contentName, const TitleInfo *titleInfo, + const unsigned short *typeName); + void Load(const char *entryName, char *header, char *body, + const wchar_t *contentName, const TitleInfo *titleInfo); + void Delete(const char *entryName, const unsigned short *contentName); +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) + void Delete(const char *entryName, const wchar_t *contentName); +#endif + void DeleteMultiple(unsigned int nEntryNames, const char **entryNames, + const unsigned short *contentName); + void FindEntries(const char *entryNamePattern, const TitleInfo *titleInfo); + void MessageDone(MessageChoices choice); + void CheckCard(CardId cardId); + void SetActiveCard(CardId cardId); + void SetAutosave(AutosaveState state, unsigned int nSaveReqs, + SaveReq **saveReqs, const char *entryName, CardId cardId); + void SetMonitor(MonitorState state); + void SetMessage(MessageState state, unsigned int message); + const unsigned short *GetCardName(); + const unsigned short *GetCardName(CardId cardId); + MemcardTask Update(unsigned int elapsedTime); + unsigned int CalcSaveSize(const SaveInfo *saveInfo); + void SetMaxCardNameLength(unsigned int maxLength); + bool IsResettable(); + void SetRootPath(const char *path); +}; + +} // namespace RealmcIface + +using RealmcIface::GameInfo; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.cpp index e69de29bb..cd0893736 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.cpp @@ -0,0 +1,72 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" + +int FEPrintf(const char* pkg_name, int object_hash, const char* fmt, ...); +const char* GetLocalizedString(unsigned int hash); + +int FEGameWonScreen::mCurrentScreen; + +FEGameWonScreen::FEGameWonScreen(ScreenConstructorData* sd) + : MenuScreen(sd) +{ + switch (mCurrentScreen) { + case 0: + break; + case 3: + FEPrintf(GetPackageName(), static_cast(0x3cc94d6), "> %s", FEDatabase->GetUserProfile(0)->GetProfileName()); + break; + case 4: { + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + UserProfile& prof = *FEDatabase->GetUserProfile(0); + HighScoresDatabase* scores = prof.GetHighScores(); + + FEPrintf(GetPackageName(), static_cast(0x1232703a), GetLocalizedString(0xe21d083c), prof.GetCareer()->GetCaseFileName()); + FEPrintf(GetPackageName(), static_cast(0xe3da78e7), GetLocalizedString(0x6031106e), prof.GetProfileName()); + FEPrintf(GetPackageName(), static_cast(0x22f33e0a), GetLocalizedString(0x6031106e), prof.GetProfileName()); + FEPrintf(GetPackageName(), static_cast(0xe3da78e8), GetLocalizedString(0x364e4525), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), static_cast(0xe3da78e9), GetLocalizedString(0xa355fedd), scores->GetCareerPursuitScore(static_cast(7))); + FEPrintf(GetPackageName(), static_cast(0xe3da78ea), GetLocalizedString(0xb1e58db1), stable->GetNumImpoundedCars()); + FEPrintf(GetPackageName(), static_cast(0xe3da78eb), GetLocalizedString(0x79fb7d16), stable->GetTotalFines(true)); + FEPrintf(GetPackageName(), static_cast(0xe3da78ec), GetLocalizedString(0x463b461b), stable->GetTotalEvadedPursuits()); + FEPrintf(GetPackageName(), static_cast(0xe3da78ed), GetLocalizedString(0xc5094459), stable->GetTotalBustedPursuits()); + FEPrintf(GetPackageName(), static_cast(0xe3da78ee), GetLocalizedString(0x6dee0c7a), stable->GetNumCareerCarsWithARecord()); + break; + } + } +} + +FEGameWonScreen::~FEGameWonScreen() {} + +void FEGameWonScreen::NotificationMessage(unsigned long msg, FEObject* obj, + unsigned long param1, unsigned long param2) { + if (msg == 0xe1fde1d1) { + QueuePackageSwitchForNextScreen(); + } +} + +void FEGameWonScreen::QueuePackageSwitchForNextScreen() { + mCurrentScreen++; + switch (mCurrentScreen) { + case 1: + cFEng::Get()->QueuePackageSwitch("Credits.fng", 0, 0, false); + break; + case 2: + cFEng::Get()->QueuePackageSwitch("RapSheetLogin_ENDGAME.fng", 0, 0, false); + break; + case 3: + cFEng::Get()->QueuePackageSwitch("RapSheetLogin2_ENDGAME.fng", 0, 0, false); + break; + case 4: + cFEng::Get()->QueuePackageSwitch("RapSheetMain_ENDGAME.fng", 0, 0, false); + break; + case 5: + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + break; + } +} + +void FEGameWonScreen::Initialize() { + mCurrentScreen = 0; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.hpp new file mode 100644 index 000000000..68a23c014 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.hpp @@ -0,0 +1,23 @@ +#ifndef FRONTEND_MENUSCREENS_CAREER_FEGAMEWONSCREEN_H +#define FRONTEND_MENUSCREENS_CAREER_FEGAMEWONSCREEN_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" + +// total size: 0x2C +struct FEGameWonScreen : public MenuScreen { + static int mCurrentScreen; + + FEGameWonScreen(ScreenConstructorData* sd); + ~FEGameWonScreen() override; + static MenuScreen* Create(ScreenConstructorData* sd); + void Initialize(); + void Setup(); + void NotificationMessage(unsigned long, FEObject*, unsigned long, unsigned long) override; + static void QueuePackageSwitchForNextScreen(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEPkg_EngageEventDialog.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEPkg_EngageEventDialog.cpp index e69de29bb..835afddff 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEPkg_EngageEventDialog.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Career/FEPkg_EngageEventDialog.cpp @@ -0,0 +1,115 @@ +#include "Speed/Indep/Src/Misc/FixedPoint.hpp" + +#ifndef GRACESAVEINFO_DEFINED +#define GRACESAVEINFO_DEFINED +struct GRaceSaveInfo { + unsigned int mRaceHash; // offset 0x0, size 0x4 + unsigned int mFlags; // offset 0x4, size 0x4 + float mHighScores; // offset 0x8, size 0x4 + unsigned short mTopSpeed; // offset 0xC, size 0x2 + unsigned short mAverageSpeed; // offset 0xE, size 0x2 +}; +#endif + +struct UITrackMapStreamer { + char _data[0xD8]; + virtual ~UITrackMapStreamer(); + UITrackMapStreamer(); + void Init(GRaceParameters *track, FEMultiImage *map, int unused, int region_unlock); + void UpdateAnimation(); +}; + +unsigned int CalcLanguageHash(const char *prefix, GRaceParameters *parms); + +namespace nsEngageEventDialog { + +struct EngageEventDialog : MenuScreen { + GRuntimeInstance *mpRaceActivity; // offset 0x2C + UITrackMapStreamer *MapStreamer; // offset 0x30 + FEMultiImage *TrackMap; // offset 0x34 + + EngageEventDialog(ScreenConstructorData *sd); + ~EngageEventDialog() override; + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + void NotifyTheGameAcceptEvent(); + void NotifyTheGameDeclineEvent(); +}; + +EngageEventDialog::EngageEventDialog(ScreenConstructorData *sd) : MenuScreen(sd) { + MapStreamer = nullptr; + + GActivity *activity = reinterpret_cast(sd->Arg); + mpRaceActivity = reinterpret_cast(activity); + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromActivity(activity); + + FEngSetTextureHash(GetPackageName(), 0xad9e232a, FEDatabase->GetRaceIconHash(parms->GetRaceType())); + FEngSetLanguageHash(GetPackageName(), 0xa01b9361, FEDatabase->GetRaceNameHash(parms->GetRaceType())); + FEngSetLanguageHash(GetPackageName(), 0xf601f2d4, CalcLanguageHash("TRACKNAME_", parms)); + + FEPrintf(GetPackageName(), 0x644ab208, "%d", parms->GetNumLaps()); + + bool kph; + const char *distUnits; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + kph = true; + distUnits = GetLocalizedString(0x8569a26a); + } else { + kph = false; + distUnits = GetLocalizedString(0x867dcfd9); + } + + float length = parms->GetRaceLengthMeters(); + if (kph) { + length *= 0.001f; + } else { + length *= 0.000625f; + } + FEPrintf(GetPackageName(), 0xbce13923, "%$0.1f %s", length, distUnits); + + unsigned int hash; + if (parms->GetCopsEnabled()) { + hash = 0x61d1c5a5; + } else { + hash = 0x73c615a3; + } + FEngSetLanguageHash(GetPackageName(), 0x762f1d7a, hash); + + GRaceSaveInfo *info = GRaceDatabase::Get().GetScoreInfo(parms->GetEventHash()); + + if (parms->GetRaceType() == GRace::kRaceType_P2P || + parms->GetRaceType() == GRace::kRaceType_Circuit || + parms->GetRaceType() == GRace::kRaceType_Drag || + parms->GetRaceType() == GRace::kRaceType_Knockout || + parms->GetRaceType() == GRace::kRaceType_Tollbooth) { + Timer timer; + timer.SetTime(info->mHighScores); + char buf[64]; + timer.PrintToString(buf, 0); + FEPrintf(GetPackageName(), 0x8fd41bb4, "%s", buf); + } else { + FEPrintf(GetPackageName(), 0x8fd41bb4, "%s", GetLocalizedString(0x472aa00a)); + } + + const char *speedUnits; + float conversion; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + conversion = 3.6f; + speedUnits = GetLocalizedString(0x8569a25f); + } else { + conversion = 2.23699f; + speedUnits = GetLocalizedString(0x8569ab44); + } + + float avg_speed = static_cast(info->mAverageSpeed) / static_cast(FixedPoint::GetScale()) * conversion; + float top_speed = static_cast(info->mTopSpeed) / static_cast(FixedPoint::GetScale()) * conversion; + FEPrintf(GetPackageName(), 0x35d1ab83, "%$0.0f %s", avg_speed, speedUnits); + FEPrintf(GetPackageName(), 0xde9145fb, "%$0.0f %s", top_speed, speedUnits); + + FEPrintf(GetPackageName(), 0x45276f1f, "%$0.0f", parms->GetCashValue()); + + TrackMap = static_cast(FEngFindObject(GetPackageName(), FEngHashString("TRACK_MAP"))); + MapStreamer = new ("", 0) UITrackMapStreamer(); + MapStreamer->Init(parms, TrackMap, 0, 0); +} + +} // namespace nsEngageEventDialog diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/CTextScroller.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/CTextScroller.hpp new file mode 100644 index 000000000..010338b6f --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/CTextScroller.hpp @@ -0,0 +1,50 @@ +#ifndef FRONTEND_MENUSCREENS_COMMON_CTEXTSCROLLER_H +#define FRONTEND_MENUSCREENS_COMMON_CTEXTSCROLLER_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +struct MenuScreen; +struct FEngFont; +class FEScrollBar; + +// total size: 0x54 +struct CTextScroller { + MenuScreen* m_pOwner; // offset 0x0, size 0x4 + FEngFont* m_pFont; // offset 0x4, size 0x4 + FEScrollBar* m_pScrollBar; // offset 0x8, size 0x4 + char m_TextBoxNameTemplate[32]; // offset 0xC, size 0x20 + int m_ViewWidth; // offset 0x2C, size 0x4 + int m_ViewVisibleLines; // offset 0x30, size 0x4 + int m_NumAddedLines; // offset 0x34, size 0x4 + short** m_pLines; // offset 0x38, size 0x4 + char* m_pRawDataBlock; // offset 0x3C, size 0x4 + unsigned int m_DataBlockSize; // offset 0x40, size 0x4 + unsigned int m_DataBlockCurPos; // offset 0x44, size 0x4 + int m_TopLine; // offset 0x48, size 0x4 + unsigned int m_ScrollDownMsg; // offset 0x4C, size 0x4 + unsigned int m_ScrollUpMsg; // offset 0x50, size 0x4 + + inline void UseScrollBar(FEScrollBar* pScrollBar) { m_pScrollBar = pScrollBar; } + + CTextScroller(); + ~CTextScroller(); + void Initialise(MenuScreen* pOwner, int ViewWidth, int ViewLines, char* pTextDisplayNameTempl, FEngFont* pFont); + void SetTextHash(unsigned int language_hash); + void SetText(short* pText); + void Scroll(int Amount); + bool HandleNotificationMessage(unsigned int Msg); + void Display(int TopLine); + void AddLine(short *pLine, int Size); + void WordWrapCountLinesAndChars(short* pTextStart, short* pTextEnd, int& NumLines, int& NumChars); + int WordWrapAddLines(short* pTextStart, short* pTextEnd, bool bCountOnly, int* pNumCharsOut); + short* FindCR(short* pText); + short *FindEND(short *pText); + int GetNumVisibleLines() { return m_ViewVisibleLines; } + int GetNumLines() { return m_NumAddedLines; } + int GetTopLine() { return m_TopLine; } + void UpdateScrollBar(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp new file mode 100644 index 000000000..8dd5c1793 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp @@ -0,0 +1,102 @@ +#ifndef _DIALOGINTERFACE +#define _DIALOGINTERFACE + +#include + +enum eDialogTitle { + dialog_none = 0, + dialog_alert = 1, + dialog_info = 2, + dialog_confirmation = 3, + dialog_fatalerror = 4, + dialog_countdown = 5, +}; + +enum eDialogFirstButtons {}; + +struct feDialogConfig { + enum { + DIALOG_BLURB_MAX_LENGTH = 512, + }; + + feDialogConfig(); + + char BlurbString[512]; // offset 0x0, size 0x200 + eDialogTitle Title; // offset 0x200, size 0x4 + unsigned int Button1TextHash; // offset 0x204, size 0x4 + unsigned int Button1PressedMessage; // offset 0x208, size 0x4 + unsigned int Button2TextHash; // offset 0x20C, size 0x4 + unsigned int Button2PressedMessage; // offset 0x210, size 0x4 + unsigned int Button3TextHash; // offset 0x214, size 0x4 + unsigned int Button3PressedMessage; // offset 0x218, size 0x4 + unsigned int DialogCancelledMessage; // offset 0x21C, size 0x4 + unsigned int FirstButton; // offset 0x220, size 0x4 + const char *ParentPackage; // offset 0x224, size 0x4 + const char *DialogPackage; // offset 0x228, size 0x4 + int NumButtons; // offset 0x22C, size 0x4 + bool bIsDismissable; // offset 0x230, size 0x1 + bool bDetectController; // offset 0x234, size 0x1 + bool bBlurbIsUTF8; // offset 0x238, size 0x1 + unsigned int DismissedByOtherDialogMsg; // offset 0x23C, size 0x4 + int DialogHandle; // offset 0x240, size 0x4 + float fCountdown; // offset 0x244, size 0x4 +}; + +struct DialogInterface { + static int ShowDialog(feDialogConfig *config); + static void DismissDialog(int handle); + + static int ShowOk(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + const char *fmt, va_list *arg_list); + static int ShowOk(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int message_hash, ...); + + static int ShowOneButton(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int cancel_message, bool dismissable, + const char *fmt, va_list *arg_list); + static int ShowOneButton(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int cancel_message, unsigned int blurb_fmt, ...); + static int ShowOneButton(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int blurb_fmt, ...); + + static int ShowTwoButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int cancel_message, bool dismissable, + eDialogFirstButtons first_button, + const char *fmt, va_list *arg_list); + static int ShowTwoButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int cancel_message, eDialogFirstButtons first_button, + const char *fmt, ...); + static int ShowTwoButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int cancel_message, eDialogFirstButtons first_button, + unsigned int blurb_fmt, ...); + static int ShowTwoButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + eDialogFirstButtons first_button, unsigned int blurb_fmt, ...); + + static int ShowThreeButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button3_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int button3_pressed_message, unsigned int cancel_message, + eDialogFirstButtons first_button, + const char *fmt, va_list *arg_list); + static int ShowThreeButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button3_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int button3_pressed_message, unsigned int cancel_message, + eDialogFirstButtons first_button, + unsigned int blurb_fmt, ...); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp index e69de29bb..a4e6f5f70 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.cpp @@ -0,0 +1,118 @@ +#include "FEAnyMovieScreen.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +extern void DismissChyron(); +extern bool MoviePlayer_Bypass(); + +struct FEMovie; +extern FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash); +extern void FEngSetMovieName(FEMovie* movie, const char* name); + +// GarageMainScreen already defined in uiMain.cpp (earlier in TU) + +char FEAnyMovieScreen::MovieFilename[64]; +char FEAnyMovieScreen::ReturnToPackageName[64]; + +FEAnyMovieScreen::FEAnyMovieScreen(ScreenConstructorData* sd) + : MenuScreen(sd) // + , mSubtitler() // + , bHidGarage(false) // + , bAllowingControllerErrors(false) +{ + const unsigned int FEObj_movie = 0x348FF9F; + + bAllowingControllerErrors = FEManager::Get()->IsAllowingControllerError(); + FEManager::Get()->AllowControllerError(false); + + FEMovie* movie = static_cast(FEngFindObject(GetPackageName(), FEObj_movie)); + FEngSetMovieName(movie, MovieFilename); + + mSubtitler.BeginningMovie(MovieFilename, GetPackageName()); + DismissChyron(); + + EFadeScreenOff* fadeEvent = new EFadeScreenOff(0x14035FB); + + GarageMainScreen* garageMainScreen = GarageMainScreen::GetInstance(); + if (garageMainScreen != nullptr && !garageMainScreen->IsVisable()) { + garageMainScreen->NotificationMessage(0xAD4BBDCUL, nullptr, 0, 0); + bHidGarage = true; + } +} + +FEAnyMovieScreen::~FEAnyMovieScreen() { + if (bHidGarage) { + GarageMainScreen* garageMainScreen = GarageMainScreen::GetInstance(); + if (garageMainScreen != nullptr) { + garageMainScreen->NotificationMessage(0x18883F75UL, nullptr, 0, 0); + } + } + FEManager::Get()->SetEATraxSecondButton(); + FEManager::Get()->AllowControllerError(bAllowingControllerErrors); +} + +MenuScreen* FEAnyMovieScreen::Create(ScreenConstructorData* sd) { + return new ("", 0) FEAnyMovieScreen(sd); +} + +void FEAnyMovieScreen::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + mSubtitler.Update(msg); + + switch (msg) { + case 0xC3960EB9: + DismissMovie(); + break; + case 0x406415E3: + case 0xB5AF2461: + if (FEDatabase->IsDDay()) { + if (!MoviePlayer_Bypass()) break; + } + mSubtitler.Update(0xC3960EB9); + DismissMovie(); + break; + } +} + +void FEAnyMovieScreen::LaunchMovie(const char* return_to_pkg, const char* filename) { + bStrNCpy(ReturnToPackageName, return_to_pkg, 64); + SetMovieName(filename); + cFEng::Get()->QueuePackageSwitch(GetFEngPackageName(), 0, 0, false); +} + +void FEAnyMovieScreen::PlaySafehouseIntroMovie() { + SetMovieName("SafehouseIntroMovie"); + bStrNCpy(ReturnToPackageName, "IG_SafehouseMain.fng", 64); +} + +void FEAnyMovieScreen::DismissMovie() { + if (ReturnToPackageName[0] != '\0') { + cFEng::Get()->QueuePackageSwitch(ReturnToPackageName, 0, 0, false); + ReturnToPackageName[0] = '\0'; + } else { + if (FEDatabase->IsPostRivalMode()) { + uiRepSheetRivalFlow* flow = uiRepSheetRivalFlow::Get(); + flow->Next(); + } else { + cFEng::Get()->QueuePackagePop(1); + } + } +} + +void FEAnyMovieScreen::SetMovieName(const char* filename) { + bStrNCpy(MovieFilename, filename, 64); +} + +extern int eIsWidescreen(); + +const char* FEAnyMovieScreen::GetFEngPackageName() { + if (eIsWidescreen()) { + return "FEAnyMovieScreenW.fng"; + } + return "FEAnyMovieScreen.fng"; +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.hpp new file mode 100644 index 000000000..898f1c104 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.hpp @@ -0,0 +1,30 @@ +#ifndef FRONTEND_MENUSCREENS_COMMON_FEANYMOVIESCREEN_H +#define FRONTEND_MENUSCREENS_COMMON_FEANYMOVIESCREEN_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/SubTitle.hpp" + +// total size: 0x58 +struct FEAnyMovieScreen : public MenuScreen { + SubTitler mSubtitler; // offset 0x2C + bool bHidGarage; // offset 0x50 + bool bAllowingControllerErrors; // offset 0x54 + + FEAnyMovieScreen(ScreenConstructorData* sd); + ~FEAnyMovieScreen() override; + static MenuScreen* Create(ScreenConstructorData* sd); + void NotificationMessage(unsigned long, FEObject*, unsigned long, unsigned long) override; + static void LaunchMovie(const char*, const char*); + static void PlaySafehouseIntroMovie(); + static void DismissMovie(); + static void SetMovieName(const char*); + static const char* GetFEngPackageName(); + static char MovieFilename[64]; + static char ReturnToPackageName[64]; +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp index e69de29bb..3d96b59ed 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.cpp @@ -0,0 +1,140 @@ +#include "FEAnyTutorialScreen.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +struct FEMovie; +extern FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash); +extern void FEngSetMovieName(FEMovie* movie, const char* name); +extern int eIsWidescreen(); +extern void DismissChyron(); +extern unsigned int FEngHashString(const char*, ...); +extern unsigned int bStringHash(const char*, int); +extern void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int language); + +inline void FEngSetMovieName(const char* pkg_name, unsigned int obj_hash, const char* name) { + FEMovie* movie = static_cast(FEngFindObject(pkg_name, obj_hash)); + FEngSetMovieName(movie, name); +} + +char FEAnyTutorialScreen::MovieFilename[64]; +char FEAnyTutorialScreen::PackageFilename[64]; +bool FEAnyTutorialScreen::PackageSet; + +static const char* FEAnyTutorialScreenName = "FEAnyTutorialScreen.fng"; + +FEAnyTutorialScreen::FEAnyTutorialScreen(ScreenConstructorData* sd) + : MenuScreen(sd), // + mTimer(0) +{ + unsigned int str_hash = 0; + bool mSkipable = true; + + DismissChyron(); + + FEngSetMovieName(GetPackageName(), 0x0348FF9F, MovieFilename); + + if (eIsWidescreen()) { + cFEng::Get()->QueuePackageMessage(0x70D2183B, GetPackageName(), nullptr); + } + + CareerSettings* career = FEDatabase->GetCareerSettings(); + + if (bStrCmp(MovieFilename, "TUT_DRAG") == 0) { + if (career != nullptr && !(career->SpecialFlags & 0x40)) { + career->SpecialFlags |= 0x40; + mSkipable = false; + } + str_hash = FEngHashString("TUT_DRAG_LABEL"); + } else if (bStrCmp(MovieFilename, "TUT_SPEEDTRAP") == 0) { + if (career != nullptr && !(career->SpecialFlags & 0x80)) { + career->SpecialFlags |= 0x80; + mSkipable = false; + } + str_hash = FEngHashString("TUT_SPEEDTRAP_LABEL"); + } else if (bStrCmp(MovieFilename, "TUT_TOLLBOOTH") == 0) { + if (career != nullptr && !(career->SpecialFlags & 0x100)) { + career->SpecialFlags |= 0x100; + mSkipable = false; + } + str_hash = FEngHashString("TUT_TOLLBOOTH_LABEL"); + } else if (bStrCmp(MovieFilename, "TUT_BOUNTY") == 0) { + if (career != nullptr && !(career->SpecialFlags & 0x400)) { + career->SpecialFlags |= 0x400; + mSkipable = false; + } + str_hash = FEngHashString("TUT_BOUNTY_LABEL"); + } else if (bStrCmp(MovieFilename, "TUT_PURSUIT") == 0) { + if (career != nullptr && !(career->SpecialFlags & 0x200)) { + career->SpecialFlags |= 0x200; + mSkipable = false; + } + str_hash = FEngHashString("TUT_PURSUIT_LABEL"); + } + + if (mSkipable) { + cFEng::Get()->QueuePackageMessage(0x59291F95, GetPackageName(), nullptr); + } + + unsigned int label_hash = bStringHash("TUTORIAL_LABEL", str_hash); + FEngSetLanguageHash(GetPackageName(), 0x5A0EE0D9, label_hash); + FEngSetLanguageHash(GetPackageName(), 0xF414BF3E, label_hash); + FEngSetLanguageHash(GetPackageName(), 0x5A0EE0D8, label_hash); + FEngSetLanguageHash(GetPackageName(), 0x07D2EA5D, label_hash); + + mSubtitler.BeginningMovie(MovieFilename, GetPackageName()); + + new EFadeScreenOff(0x14035FB); +} + +MenuScreen* FEAnyTutorialScreen::Create(ScreenConstructorData* sd) { + return new ("", 0) FEAnyTutorialScreen(sd); +} + +FEAnyTutorialScreen::~FEAnyTutorialScreen() { + FEManager::Get()->SetEATraxSecondButton(); +} + +void FEAnyTutorialScreen::NotificationMessage(unsigned long msg, FEObject* obj, + unsigned long param1, unsigned long param2) { + mSubtitler.Update(msg); + + switch (msg) { + case 0xC3960EB9: + DismissMovie(false); + break; + case 0xB5AF2461: + case 0x406415E3: + DismissMovie(true); + mSubtitler.Update(0xC3960EB9); + break; + } +} + +void FEAnyTutorialScreen::LaunchMovie(const char* filename, const char* packageName) { + PackageSet = false; + SetMovieName(filename); + if (packageName != nullptr) { + SetPackageName(packageName); + } + cFEng::Get()->QueuePackagePush(FEAnyTutorialScreenName, 0, 0, false); +} + +void FEAnyTutorialScreen::DismissMovie(bool send_message) { + cFEng::Get()->QueuePackagePop(1); + if (send_message) { + cFEng::Get()->QueueGameMessage(0xC3960EB9, PackageFilename, 0xFF); + } +} + +void FEAnyTutorialScreen::SetMovieName(const char* filename) { + bStrNCpy(MovieFilename, filename, 64); +} + +void FEAnyTutorialScreen::SetPackageName(const char* packageName) { + PackageSet = true; + bStrNCpy(PackageFilename, packageName, 64); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.hpp new file mode 100644 index 000000000..8b68a61e7 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.hpp @@ -0,0 +1,33 @@ +#ifndef FRONTEND_MENUSCREENS_COMMON_FEANYTUTORIALSCREEN_H +#define FRONTEND_MENUSCREENS_COMMON_FEANYTUTORIALSCREEN_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/SubTitle.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" + +// total size: 0x60 +struct FEAnyTutorialScreen : public MenuScreen { + unsigned int LastTime; // offset 0x2C + float TimeElapsed; // offset 0x30 + float TextToggleTiming; // offset 0x34 + Timer mTimer; // offset 0x38 + SubTitler mSubtitler; // offset 0x3C + + FEAnyTutorialScreen(ScreenConstructorData* sd); + ~FEAnyTutorialScreen() override; + static MenuScreen* Create(ScreenConstructorData* sd); + void NotificationMessage(unsigned long, FEObject*, unsigned long, unsigned long) override; + static void LaunchMovie(const char*, const char*); + static void DismissMovie(bool); + static void SetMovieName(const char*); + static void SetPackageName(const char*); + static char MovieFilename[64]; + static char PackageFilename[64]; + static bool PackageSet; +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp index 93d21674c..62177c896 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp @@ -1,10 +1,144 @@ -#ifndef FRONTEND_MENUSCREENS_COMMON_FEICONSCROLLERMENU_H -#define FRONTEND_MENUSCREENS_COMMON_FEICONSCROLLERMENU_H +#ifndef _FEICONSCROLLERMENU +#define _FEICONSCROLLERMENU -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +struct FEObject; +struct FEImage; +struct FEString; +struct FEGroup; +int FEngSNPrintf(char* dest, int size, const char* fmt, ...); + +struct ScrollerDatumNode : public bTNode { + char String[128]; // offset 0x8 + unsigned int LanguageHash; // offset 0x88 + + ScrollerDatumNode(const char* string, unsigned int hash) { + FEngSNPrintf(String, 0x80, string); + LanguageHash = hash; + } + virtual ~ScrollerDatumNode() {} +}; + +struct ScrollerSlotNode : public bTNode { + FEObject* String; // offset 0x8 + + ScrollerSlotNode(FEObject* string) { + String = string; + } + virtual ~ScrollerSlotNode() {} +}; + +struct ScrollerDatum : public bTNode { + bTList Strings; // offset 0x8 + bool bEnabled; // offset 0x10 + + ScrollerDatum() {} + ScrollerDatum(const char* string, unsigned int hash); + virtual ~ScrollerDatum() {} + void AddData(const char* string, unsigned int hash) { + Strings.AddTail(new(__FILE__, __LINE__) ScrollerDatumNode(string, hash)); + } + ScrollerDatumNode* Find(const char* to_find); + ScrollerDatumNode* Find(unsigned int hash); + void Printf(); + char* GetTopDatumModeString(); + void Enable() { bEnabled = true; } + void Disable() { bEnabled = false; } + bool IsEnabled() { return bEnabled; } +}; + +struct ScrollerSlot : public bTNode { + bTList FEStrings; // offset 0x8 + FEObject* pBacking; // offset 0x10 + bVector2 vTopLeft; // offset 0x14 + bVector2 vSize; // offset 0x1C + bool bEnabled; // offset 0x24 + + ScrollerSlot() {} + ScrollerSlot(FEObject* string); + virtual ~ScrollerSlot() {} + void AddData(FEObject* string) { + FEStrings.AddTail(new(__FILE__, __LINE__) ScrollerSlotNode(string)); + } + void SetBacking(FEObject* obj) { pBacking = obj; } + void Highlight(); + void UnHighlight(); + void Enable() { bEnabled = true; } + void Disable() { bEnabled = false; } + void GetSize(bVector2& size) { size = vSize; } + void GetTopLeft(bVector2& top_left) { top_left = vTopLeft; } + bool IsEnabled() { return bEnabled; } + void SetScript(unsigned int script_hash); + void FindSize(); + void Show(); + void Hide(); + bool Find(FEObject* obj); +}; + +// 0x5C +struct IconOption : public bTNode { + unsigned int Item; // 0x08 + FEObject* FEngObject; // 0x0C + float XPos; // 0x10 + float YPos; // 0x14 + unsigned int OriginalColor; // 0x18 + bool IsGreyOut; // 0x1C + bool IsFlashable; // 0x20 + bool Locked; // 0x24 + float OrigWidth; // 0x28 + float OrigHeight; // 0x2C + +private: + unsigned int NameHash; // 0x30 + unsigned int DescHash; // 0x34 + float fScaleToPcnt; // 0x38 + float fScaleStartSecs; // 0x3C + float fScaleDurSecs; // 0x40 + float fScaleAtStart; // 0x44 + bool bAnimComplete; // 0x48 + bool bReactImmediately; // 0x4C + bool bIsTutorialAvailable; // 0x50 + const char* pTutorialMovieName; // 0x54 + +public: + IconOption(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash); + virtual ~IconOption() {} + virtual void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) = 0; + + unsigned int GetName(); + unsigned int GetDesc(); + void StartScale(float scale_to, float duration); + float GetScaleToPcnt(); + float GetScaleStartSecs(); + float GetScaleDurSecs(); + float GetScaleAtStart(); + void SetScaleAtStart(float scale); + bool IsAnimComplete(); + void SetAnimComplete(bool b); + bool ReactsImmediately(); + bool IsLocked(); + void SetLocked(bool b); + void SetReactImmediately(bool b); + bool IsTutorialAvailable(); + const char* GetTutorialMovieName(); + void SetTutorialMovieName(const char* name); + void SetFEngObject(FEObject* obj); +}; +inline unsigned int IconOption::GetName() { return NameHash; } +inline void IconOption::SetReactImmediately(bool b) { bReactImmediately = b; } +inline bool IconOption::ReactsImmediately() { return bReactImmediately; } +inline bool IconOption::IsTutorialAvailable() { return bIsTutorialAvailable; } +inline const char* IconOption::GetTutorialMovieName() { return pTutorialMovieName; } +struct FEScrollyBookEnd : public IconOption { + FEScrollyBookEnd(unsigned int tex_hash) : IconOption(tex_hash, 0, 0) {} + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override {} +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEInputWidget.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEInputWidget.hpp new file mode 100644 index 000000000..260af79e2 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEInputWidget.hpp @@ -0,0 +1,59 @@ +#ifndef FE_INPUT_WIDGET_HPP +#define FE_INPUT_WIDGET_HPP + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp" + +extern char *bStrNCpy(char *dest, const char *src, int n); + +// total size: 0x194 +struct FEInputWidget : public FEStatWidget { + char InputText[156]; // offset 0x54, size 0x9C + char Title[156]; // offset 0xF0, size 0x9C + unsigned int MaxInputLength; // offset 0x18C, size 0x4 + unsigned int EditMode; // offset 0x190, size 0x4 + + FEInputWidget(unsigned int max_input_length, const char *init_text, unsigned int edit_mode, bool enabled); + + void Act(const char *parent_pkg, unsigned int data) override; + void CheckMouse(const char *parent_pkg, const float mouse_x, const float mouse_y) override; + void Draw() override; + void Enable() override; + void Disable() override; + void Show() override; + void Hide() override; + void SetFocus(const char *parent_pkg) override; + void UnsetFocus() override; + + void SetInputFocus(); + void SetTitle(const char *text); + + inline void SetInputText(const char *text) { + bStrNCpy(InputText, text, 0x9b); + } + + inline const char *GetInputText() { + return InputText; + } + + inline void SetEditMode(unsigned int mode) { + EditMode = mode; + } + + inline unsigned int GetEditMode() { + return EditMode; + } + + inline const char *GetTitle() { + return Title; + } + + inline unsigned int GetMaxInputLength() { + return MaxInputLength; + } +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.cpp index e69de29bb..c7cfbd560 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.cpp @@ -0,0 +1,297 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/FEImage.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" + +extern KeyboardEditString gKeyboardManager; +extern MenuScreen *g_pOLCurrentScreen; + +extern void FESoundControl(bool, const char *); +extern FEImage *FEngFindImage(const char *pkg_name, int hash); +extern void FEngSetButtonTexture(FEImage *img, unsigned int tex_hash); +extern int bStrCmp(const char *s1, const char *s2); +extern char *bStrNCpy(char *to, const char *from, int max_count); +extern void bMemSet(void *dst, int value, unsigned int size); + +extern const char lbl_803E59BC[]; +extern const char lbl_803E5CB8[]; +extern const char lbl_803E5EEC[]; +extern const char lbl_803E6D54[]; +extern const char lbl_803E6D6C[]; +extern const char lbl_803E6D8C[]; +extern const char lbl_803E7FC4[]; +extern const char lbl_803E85A8[]; +extern const char lbl_803E85C4[]; +extern const char lbl_803E85E0[]; +extern const char lbl_803E8600[]; + +MenuScreen::MenuScreen(ScreenConstructorData *sd) + : mPlaySound(true) // + , mDirectionForNextSound(0) // + , bEnableEAMessenger(false) // + , PackageFilename(sd->PackageFilename) // + , ConstructData(*sd) // + , IsGarageScreen(false) // + , TextInputObject(nullptr) // + , mStartCapturingFromKeyboard(0) { + FESoundControl(true, PackageFilename); + FEngSetButtonTexture(FEngFindImage(PackageFilename, 0x6B364F8B), 0x5BC); + FEngSetButtonTexture(FEngFindImage(PackageFilename, 0x79354351), 0x682); + + if (bStrCmp(PackageFilename, lbl_803E5EEC) == 0 || bStrCmp(PackageFilename, lbl_803E6D54) == 0 || + bStrCmp(PackageFilename, lbl_803E7FC4) == 0 || bStrCmp(PackageFilename, lbl_803E6D6C) == 0 || + bStrCmp(PackageFilename, lbl_803E59BC) == 0 || bStrCmp(PackageFilename, lbl_803E6D8C) == 0 || + bStrCmp(PackageFilename, lbl_803E85A8) == 0 || bStrCmp(PackageFilename, lbl_803E85C4) == 0 || + bStrCmp(PackageFilename, lbl_803E85E0) == 0) { + } else if (!cFEng::Get()->IsPackagePushed(lbl_803E8600)) { + g_pOLCurrentScreen = this; + } +} + +MenuScreen::~MenuScreen() { + FESoundControl(false, PackageFilename); + + if (bStrCmp(PackageFilename, lbl_803E5EEC) == 0 || bStrCmp(PackageFilename, lbl_803E6D54) == 0 || + bStrCmp(PackageFilename, lbl_803E7FC4) == 0 || bStrCmp(PackageFilename, lbl_803E6D6C) == 0 || + bStrCmp(PackageFilename, lbl_803E59BC) == 0 || bStrCmp(PackageFilename, lbl_803E6D8C) == 0 || + bStrCmp(PackageFilename, lbl_803E85A8) == 0 || bStrCmp(PackageFilename, lbl_803E85C4) == 0 || + bStrCmp(PackageFilename, lbl_803E85E0) == 0) { + } else if (!cFEng::Get()->IsPackagePushed(lbl_803E8600)) { + g_pOLCurrentScreen = nullptr; + } +} + +void MenuScreen::BaseNotify(u32 Message, FEObject *pObject, u32 Param1, u32 Param2) { + if (!CheckKeyboard(Message)) { + if (Message != 0x9803F6E2 || ConstructData.pPackage->Controllers != 0) { + NotificationMessage(Message, pObject, Param1, Param2); + } + } +} + +const char *MenuScreen::FEngGetEditedString() { + return FEDatabase->mFEKeyboardSettings.Buffer; +} + +void MenuScreen::FEngEndTextInput() { + if (TextInputObject != nullptr) { + delete TextInputObject; + } + + mStartCapturingFromKeyboard = 0; + TextInputObject = nullptr; +} + +void MenuScreen::FEngBeginTextInput(uint32 /* object_hash */, uint32 edit_mode, + const char *initial_string, const char *title_string, + uint32 max_text_length) { + FEKeyboardSettings &settings = FEDatabase->mFEKeyboardSettings; + + bStrNCpy(settings.Buffer, initial_string, sizeof(settings.Buffer)); + settings.AcceptCallbackHash = 0xDA5B8712; + settings.MaxTextLength = max_text_length; + settings.DeclineCallbackHash = 0xC9D30688; + settings.DefaultTextHash = 0; + bMemSet(settings.Title, 0, sizeof(settings.Title)); + bStrNCpy(settings.Title, title_string, sizeof(settings.Title) - 1); + + settings.Mode = 1; + switch (edit_mode) { + case 0: + settings.Mode = 0; + break; + case 1: + case 3: + settings.Mode = 1; + break; + case 2: + settings.Mode = 5; + break; + case 4: + settings.Mode = 4; + break; + case 5: + settings.Mode = 2; + break; + case 6: + settings.Mode = 3; + break; + } + + cFEng::Get()->QueuePackagePush(lbl_803E5CB8, 0, 0, false); +} + +bool MenuScreen::CheckKeyboard(uint32 msg) { + if (msg == 0xC98356BA) { + if (TextInputObject == nullptr) { + return false; + } + + if (mStartCapturingFromKeyboard == 1) { + mStartCapturingFromKeyboard = 2; + return true; + } + + if (mStartCapturingFromKeyboard == 2) { + mStartCapturingFromKeyboard = 3; + gKeyboardManager.KeysProcessed = 1; + } + } + + if (TextInputObject == nullptr) { + return false; + } + + if (gKeyboardManager.KeysProcessed != 0 && gKeyboardManager.MaxTextLength != 0) { + if (msg == 0x911AB364) { + TextInputObject->EscapePressed(); + } else if (msg == 0x406415E3) { + TextInputObject->ReturnPressed(); + } else { + TextInputObject->Notify(msg); + } + } + + return true; +} + +void MenuScreen::BaseNotifySound(u32 msg, FEObject * /* obj */, u32 /* controller_mask */, u32 /* pkg_ptr */) { + eMenuSoundTriggers soundToPlay = UISND_NONE; + + switch (msg) { + case 0x00B06E92: soundToPlay = static_cast< eMenuSoundTriggers >(0x8c); break; + case 0x01CD9276: soundToPlay = static_cast< eMenuSoundTriggers >(0x72); break; + case 0x0217C99F: soundToPlay = static_cast< eMenuSoundTriggers >(0x92); break; + case 0x08464444: soundToPlay = static_cast< eMenuSoundTriggers >(0x72); break; + case 0x0A888DDC: soundToPlay = static_cast< eMenuSoundTriggers >(0x6B); break; + case 0x0BDDC0D2: soundToPlay = static_cast< eMenuSoundTriggers >(0x8e); break; + case 0x145B13C0: soundToPlay = static_cast< eMenuSoundTriggers >(0x58); break; + case 0x14C293B4: soundToPlay = static_cast< eMenuSoundTriggers >(0x83); break; + case 0x14C85C88: soundToPlay = static_cast< eMenuSoundTriggers >(0x7e); break; + case 0x1ABBE49B: soundToPlay = static_cast< eMenuSoundTriggers >(0x67); break; + case 0x1B325F9D: soundToPlay = static_cast< eMenuSoundTriggers >(0x8a); break; + case 0x1CA2354C: soundToPlay = static_cast< eMenuSoundTriggers >(0x94); break; + case 0x1CFAD52B: soundToPlay = static_cast< eMenuSoundTriggers >(0x14); break; + case 0x1E9AE519: soundToPlay = static_cast< eMenuSoundTriggers >(0x2a); break; + case 0x299148C8: soundToPlay = static_cast< eMenuSoundTriggers >(0x57); break; + case 0x2AC2CD24: soundToPlay = static_cast< eMenuSoundTriggers >(0x2f); break; + case 0x2DD4FE18: soundToPlay = static_cast< eMenuSoundTriggers >(0x26); break; + case 0x2E21E843: soundToPlay = static_cast< eMenuSoundTriggers >(0x36); break; + case 0x2E287C68: soundToPlay = static_cast< eMenuSoundTriggers >(0x33); break; + case 0x2E70DC92: soundToPlay = static_cast< eMenuSoundTriggers >(0x7f); break; + case 0x3159062B: soundToPlay = static_cast< eMenuSoundTriggers >(0x28); break; + case 0x334F3B79: soundToPlay = static_cast< eMenuSoundTriggers >(0x1b); break; + case 0x334F3B7A: soundToPlay = static_cast< eMenuSoundTriggers >(0x1C); break; + case 0x3A62513E: soundToPlay = static_cast< eMenuSoundTriggers >(0x6F); break; + case 0x3B7FAC4C: soundToPlay = static_cast< eMenuSoundTriggers >(0x70); break; + case 0x3B7FF2AB: soundToPlay = static_cast< eMenuSoundTriggers >(0x75); break; + case 0x40927E58: soundToPlay = static_cast< eMenuSoundTriggers >(0x74); break; + case 0x43BE891A: soundToPlay = static_cast< eMenuSoundTriggers >(0x1A); break; + case 0x44D43738: soundToPlay = static_cast< eMenuSoundTriggers >(0x91); break; + case 0x4688F23F: soundToPlay = static_cast< eMenuSoundTriggers >(0x73); break; + case 0x46BB84C6: soundToPlay = static_cast< eMenuSoundTriggers >(0x29); break; + case 0x478FFB47: soundToPlay = static_cast< eMenuSoundTriggers >(0x59); break; + case 0x480C9A58: soundToPlay = static_cast< eMenuSoundTriggers >(5); break; + case 0x480DF13F: soundToPlay = static_cast< eMenuSoundTriggers >(0x67); break; + case 0x4810A91B: soundToPlay = static_cast< eMenuSoundTriggers >(0x1C); break; + case 0x48ED7349: soundToPlay = static_cast< eMenuSoundTriggers >(0x90); break; + case 0x4949060A: soundToPlay = static_cast< eMenuSoundTriggers >(0x6A); break; + case 0x4A7ACC5A: soundToPlay = static_cast< eMenuSoundTriggers >(5); break; + case 0x4A7E0941: soundToPlay = static_cast< eMenuSoundTriggers >(0x85); break; + case 0x4A805994: soundToPlay = static_cast< eMenuSoundTriggers >(2); break; + case 0x4B680587: soundToPlay = static_cast< eMenuSoundTriggers >(0x9b); break; + case 0x4BEAF019: soundToPlay = static_cast< eMenuSoundTriggers >(0x32); break; + case 0x4E1DA7E5: soundToPlay = static_cast< eMenuSoundTriggers >(0x55); break; + case 0x4E7CCC80: soundToPlay = static_cast< eMenuSoundTriggers >(0x12); break; + case 0x4F072ECA: soundToPlay = static_cast< eMenuSoundTriggers >(0x9d); break; + case 0x4FB99CCD: soundToPlay = static_cast< eMenuSoundTriggers >(0x1A); break; + case 0x4FB99CCE: soundToPlay = static_cast< eMenuSoundTriggers >(0x29); break; + case 0x4FBFB02B: soundToPlay = static_cast< eMenuSoundTriggers >(0x4A); break; + case 0x50B3FA79: soundToPlay = static_cast< eMenuSoundTriggers >(0x87); break; + case 0x52F29A56: soundToPlay = static_cast< eMenuSoundTriggers >(0x76); break; + case 0x52F69B81: soundToPlay = static_cast< eMenuSoundTriggers >(7); break; + case 0x54EBD897: soundToPlay = static_cast< eMenuSoundTriggers >(0x7b); break; + case 0x56D0CB98: soundToPlay = static_cast< eMenuSoundTriggers >(0x27); break; + case 0x59E562D8: soundToPlay = static_cast< eMenuSoundTriggers >(0x6D); break; + case 0x5A0FDF7C: soundToPlay = static_cast< eMenuSoundTriggers >(0x18); break; + case 0x5A0FDF7D: soundToPlay = static_cast< eMenuSoundTriggers >(0x19); break; + case 0x5C928AC2: soundToPlay = static_cast< eMenuSoundTriggers >(0xe); break; + case 0x63E5ED6F: soundToPlay = static_cast< eMenuSoundTriggers >(0x96); break; + case 0x65A03426: soundToPlay = static_cast< eMenuSoundTriggers >(0x11); break; + case 0x672DFAFD: soundToPlay = static_cast< eMenuSoundTriggers >(0x86); break; + case 0x6A95F040: soundToPlay = static_cast< eMenuSoundTriggers >(0xf); break; + case 0x6A9AA0EA: soundToPlay = static_cast< eMenuSoundTriggers >(0x10); break; + case 0x6B283007: soundToPlay = static_cast< eMenuSoundTriggers >(0xA); break; + case 0x6BC1CD5D: soundToPlay = static_cast< eMenuSoundTriggers >(0xA); break; + case 0x6E437A7B: soundToPlay = static_cast< eMenuSoundTriggers >(0x9f); break; + case 0x7238782D: soundToPlay = static_cast< eMenuSoundTriggers >(0x68); break; + case 0x746D7CDB: soundToPlay = static_cast< eMenuSoundTriggers >(0x98); break; + case 0x7B6B89D7: soundToPlay = static_cast< eMenuSoundTriggers >(4); break; + case 0x7C002DCA: soundToPlay = static_cast< eMenuSoundTriggers >(0x99); break; + case 0x7D35DA25: soundToPlay = static_cast< eMenuSoundTriggers >(0xC); break; + case 0x7F63059B: soundToPlay = static_cast< eMenuSoundTriggers >(0x88); break; + case 0x80F21152: soundToPlay = static_cast< eMenuSoundTriggers >(0x9c); break; + case 0x81DA4B22: soundToPlay = static_cast< eMenuSoundTriggers >(0x6C); break; + case 0x84D07AF9: soundToPlay = static_cast< eMenuSoundTriggers >(0x73); break; + case 0x8C40ED95: soundToPlay = static_cast< eMenuSoundTriggers >(0x59); break; + case 0x8E2658C6: soundToPlay = static_cast< eMenuSoundTriggers >(0x70); break; + case 0x8E269F25: soundToPlay = static_cast< eMenuSoundTriggers >(0x75); break; + case 0x90429475: soundToPlay = static_cast< eMenuSoundTriggers >(0x2e); break; + case 0x905FB857: soundToPlay = static_cast< eMenuSoundTriggers >(0xC); break; + case 0x91970159: soundToPlay = static_cast< eMenuSoundTriggers >(0x15); break; + case 0x958E0BD3: soundToPlay = static_cast< eMenuSoundTriggers >(0x97); break; + case 0x97B397B0: soundToPlay = static_cast< eMenuSoundTriggers >(0x72); break; + case 0x997DD91D: soundToPlay = static_cast< eMenuSoundTriggers >(0x9e); break; + case 0x9AFA53A7: soundToPlay = static_cast< eMenuSoundTriggers >(3); break; + case 0x9B59E056: soundToPlay = static_cast< eMenuSoundTriggers >(7); break; + case 0x9F032957: soundToPlay = static_cast< eMenuSoundTriggers >(0x89); break; + case 0xA07F7EA9: soundToPlay = static_cast< eMenuSoundTriggers >(0x84); break; + case 0xA3CC3462: soundToPlay = static_cast< eMenuSoundTriggers >(0x6B); break; + case 0xA6362B4B: soundToPlay = static_cast< eMenuSoundTriggers >(0x8b); break; + case 0xAB7A5FC7: soundToPlay = static_cast< eMenuSoundTriggers >(0x69); break; + case 0xAC57BDB2: soundToPlay = static_cast< eMenuSoundTriggers >(0x13); break; + case 0xAD0FBB98: soundToPlay = static_cast< eMenuSoundTriggers >(0x82); break; + case 0xADCE9EEC: soundToPlay = static_cast< eMenuSoundTriggers >(0x7d); break; + case 0xB11FB113: soundToPlay = static_cast< eMenuSoundTriggers >(0x93); break; + case 0xB1BF9795: soundToPlay = static_cast< eMenuSoundTriggers >(0x67); break; + case 0xB205316C: soundToPlay = static_cast< eMenuSoundTriggers >(0x67); break; + case 0xB25C8563: soundToPlay = static_cast< eMenuSoundTriggers >(0x56); break; + case 0xB4FF8CD3: soundToPlay = static_cast< eMenuSoundTriggers >(0x7c); break; + case 0xB6471CC6: soundToPlay = static_cast< eMenuSoundTriggers >(0x5a); break; + case 0xB86367CB: soundToPlay = static_cast< eMenuSoundTriggers >(0x9a); break; + case 0xB8AFE646: soundToPlay = static_cast< eMenuSoundTriggers >(0x35); break; + case 0xBA0159B3: soundToPlay = static_cast< eMenuSoundTriggers >(0x81); break; + case 0xC45AF53B: soundToPlay = static_cast< eMenuSoundTriggers >(0x19); break; + case 0xC477473A: soundToPlay = static_cast< eMenuSoundTriggers >(0x16); break; + case 0xC4A06D0F: soundToPlay = static_cast< eMenuSoundTriggers >(0x59); break; + case 0xC989909F: soundToPlay = static_cast< eMenuSoundTriggers >(0xA); break; + case 0xCAF4D21E: soundToPlay = static_cast< eMenuSoundTriggers >(0x74); break; + case 0xD15F7512: soundToPlay = static_cast< eMenuSoundTriggers >(0x6D); break; + case 0xD792FF31: soundToPlay = static_cast< eMenuSoundTriggers >(0x6E); break; + case 0xD93BCF6E: soundToPlay = static_cast< eMenuSoundTriggers >(0x8d); break; + case 0xDC49A694: soundToPlay = static_cast< eMenuSoundTriggers >(6); break; + case 0xDDD9C41E: soundToPlay = static_cast< eMenuSoundTriggers >(0x31); break; + case 0xDEDF0B4C: soundToPlay = static_cast< eMenuSoundTriggers >(0x4B); break; + case 0xE28CAC90: soundToPlay = static_cast< eMenuSoundTriggers >(0x6A); break; + case 0xE2A0904F: soundToPlay = static_cast< eMenuSoundTriggers >(0x71); break; + case 0xE7DAFAEE: soundToPlay = static_cast< eMenuSoundTriggers >(0); break; + case 0xE9B28A67: soundToPlay = static_cast< eMenuSoundTriggers >(0x68); break; + case 0xEA2747C5: soundToPlay = static_cast< eMenuSoundTriggers >(0x95); break; + case 0xED78878C: soundToPlay = static_cast< eMenuSoundTriggers >(0x34); break; + case 0xEE0EEA7E: soundToPlay = static_cast< eMenuSoundTriggers >(0x8f); break; + case 0xEE54C987: soundToPlay = static_cast< eMenuSoundTriggers >(0xd); break; + case 0xF4B32D4D: soundToPlay = static_cast< eMenuSoundTriggers >(0xB); break; + case 0xF9545D5C: soundToPlay = static_cast< eMenuSoundTriggers >(0x6C); break; + case 0xFA274177: soundToPlay = static_cast< eMenuSoundTriggers >(0x80); break; + case 0xFABBBF40: soundToPlay = static_cast< eMenuSoundTriggers >(0xB); break; + case 0xFC543E69: soundToPlay = static_cast< eMenuSoundTriggers >(4); break; + } + + soundToPlay = NotifySoundMessage(msg, soundToPlay); + if (soundToPlay != UISND_NONE) { + g_pEAXSound->PlayUISoundFX(soundToPlay); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp index a07a83439..7d340d792 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp @@ -131,6 +131,8 @@ class MenuScreen { MenuScreen(ScreenConstructorData *sd); virtual ~MenuScreen(); + virtual void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) = 0; + virtual eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { return maybe; } void BaseNotify(u32 Message, FEObject *pObject, u32 Param1, u32 Param2); @@ -152,13 +154,11 @@ class MenuScreen { static void MaybeShutdownVoIPChat(); - // virtual enum eMenuSoundTriggers NotifySoundMessage(unsigned long msg, enum eMenuSoundTriggers maybe) {} + const char *GetPackageName() { return PackageFilename; } - const char *GetPackageName() {} + FEPackage *GetPackage() { return ConstructData.pPackage; } - // FEPackage *GetPackage() {} - - void SetAsGarageScreen() {} + void SetAsGarageScreen() { IsGarageScreen = true; } protected: bool mPlaySound; // offset 0x0, size 0x1 @@ -171,6 +171,7 @@ class MenuScreen { private: static int gEAMIconState; // size: 0x4, address: 0xFFFFFFFF + protected: const char *PackageFilename; // offset 0xC, size 0x4 ScreenConstructorData ConstructData; // offset 0x10, size 0xC bool IsGarageScreen; // offset 0x1C, size 0x1 diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp new file mode 100644 index 000000000..ab59e4c9f --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp @@ -0,0 +1,107 @@ +#ifndef _ICONPANEL +#define _ICONPANEL + +#include + +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp" + +struct FEImage; + +enum eScrollDir { + eSD_PREV = -1, + eSD_NEXT = 1, + eSD_PAGE_PREV = -10000, + eSD_PAGE_NEXT = 10000, + eSD_NONE = 10001, +}; + +// total size: 0x38 +struct IconPanel { + bTList Options; // offset 0x0, size 0x8 + IconOption* pCurrentNode; // offset 0x8, size 0x4 + FEObject* pMaster; // offset 0xC, size 0x4 + FEObject* pScrollRegion; // offset 0x10, size 0x4 + const char* pPackageName; // offset 0x14, size 0x4 + const char* pButtonName; // offset 0x18, size 0x4 + float fIconSpacing; // offset 0x1C, size 0x4 + int iIndexToAdd; // offset 0x20, size 0x4 + bool bWrap; // offset 0x24, size 0x1 + bool bHorizontal; // offset 0x28, size 0x1 + bool bJustScrolled; // offset 0x2C, size 0x1 + bool bReactToInput; // offset 0x30, size 0x1 + + IconPanel() {} + IconPanel(const char* pkg_name, const char* master, const char* fe_button, const char* scroll_region, bool wrap); + virtual ~IconPanel() {} + + virtual void Update(); + virtual FEImage* AddOption(IconOption* option); + virtual void RemoveAll(); + virtual void Act(unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2); + virtual IconOption* GetHead(); + virtual bool IsHead(IconOption* option); + virtual bool IsTail(IconOption* option); + virtual bool IsEndOfList(IconOption* opt); + virtual int GetOptionIndex(IconOption* to_find); + virtual bool SetSelection(IconOption* option); + virtual void SetInitialPos(); + virtual void Scroll(eScrollDir dir); + virtual void ScrollWrapped(eScrollDir dir); + + IconOption* GetOption(int to_find); + void AnimateList(); + void AnimateSelected(float& list_width, float& list_height); + void ResizeList(float list_width, float list_height); + + IconOption* GetCurrentOption() { + return pCurrentNode; + } + + FEObject* GetMaster() { + return pMaster; + } + + bool AtHead(); + bool AtTail(); + + unsigned int GetCurrentDesc(); + unsigned int GetCurrentName(); + bool CurrentReactsImmediately(); + + int GetIndexToAdd() { + return iIndexToAdd; + } + + int GetCurrentIndex() { + if (pCurrentNode) { + return GetOptionIndex(pCurrentNode); + } + return 0; + } + + void ScrollNext(); + void ScrollPrev(); + + bool JustScrolled() { + return bJustScrolled; + } + + void SetWrap(bool wrap) { + bWrap = wrap; + } + + void SetReactToInput(bool react) { + bReactToInput = react; + } + + bool IsHorizontal() { + return bHorizontal; + } + + bool ReactsToInput() { + return bReactToInput; + } +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconScroller.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconScroller.hpp new file mode 100644 index 000000000..dc4e9ddee --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconScroller.hpp @@ -0,0 +1,115 @@ +#ifndef _ICONSCROLLER +#define _ICONSCROLLER + +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp" + +struct tCubic1D; + +enum eScrollerAlignment { + eSA_TOP = 0, + eSA_MIDDLE = 1, + eSA_BOTTOM = 2, + eSA_LEFT = 0, + eSA_RIGHT = 2, +}; + +// total size: 0x11C +struct IconScroller : public IconPanel { + IconOption* HeadBookEnd; // offset 0x38, size 0x4 + IconOption* TailBookEnd; // offset 0x3C, size 0x4 + FEScrollBar ScrollBar; // offset 0x40, size 0x64 + char AnimateCubicData[0x2C]; // offset 0xA4, size 0x2C (tCubic1D) + eScrollerAlignment AlignmentToSelected; // offset 0xD0, size 0x4 + int iNumBookEnds; // offset 0xD4, size 0x4 + int iNumVisible; // offset 0xD8, size 0x4 + int iCurSelectedIndex; // offset 0xDC, size 0x4 + float fWidth; // offset 0xE0, size 0x4 + float fHeight; // offset 0xE4, size 0x4 + float fXCenter; // offset 0xE8, size 0x4 + float fYCenter; // offset 0xEC, size 0x4 + float fPulseState; // offset 0xF0, size 0x4 + float fCurrentAddPos; // offset 0xF4, size 0x4 + float fCurFadeTime; // offset 0xF8, size 0x4 + float fMaxFadeTime; // offset 0xFC, size 0x4 + bool bAllowColorAnim; // offset 0x100, size 0x1 + bool bFadingIn; // offset 0x104, size 0x1 + bool bFadingOut; // offset 0x108, size 0x1 + bool bInitialized; // offset 0x10C, size 0x1 + bool bDelayUpdate; // offset 0x110, size 0x1 + unsigned int IdleColor; // offset 0x114, size 0x4 + unsigned int FadeColor; // offset 0x118, size 0x4 + + IconScroller() {} + IconScroller(const char* pkg_name, const char* master, const char* fe_button, const char* scroll_region, float width); + ~IconScroller() override {} + + void Update() override; + virtual void AddInitialBookEnds(); + FEImage* AddOption(IconOption* option) override; + virtual void SetInitialPos(int index); + bool SetSelection(IconOption* option) override; + void RemoveAll() override; + int GetOptionIndex(IconOption* to_find) override; + void Scroll(eScrollDir dir) override; + void ScrollWrapped(eScrollDir dir) override; + void ClipEdges(IconOption* option, float pos); + float Scale(float x, float center, float scroll_size, float thumb_size); + void PositionOption(IconOption* option); + void UpdateFade(IconOption* option, float scale); + void UpdateArrows(); + void PulseSelected(); + + IconOption* GetHead() override; + bool IsHead(IconOption* option) override; + bool IsTail(IconOption* option) override; + bool IsEndOfList(IconOption* opt) override; + + void DelayUpdate() { + bDelayUpdate = true; + } + + void SetAllowFade(bool allow) { + bAllowColorAnim = allow; + } + + void StartFadeIn() { + bFadingIn = true; + bDelayUpdate = false; + bFadingOut = false; + fCurFadeTime = 0.0f; + } + + void StartFadeOut() { + bFadingOut = true; + fCurFadeTime = 0.0f; + } + + void SetIdleColor(unsigned int color) { + IdleColor = color; + } + + void SetFadeColor(unsigned int color) { + FadeColor = color; + } + + FEScrollBar* GetScrollBar() { + return &ScrollBar; + } + + bool IsInitialized() { + return bInitialized; + } + + void SetInitialized() { + bInitialized = true; + } + + int CountElements(); + + bool IsEmpty(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp new file mode 100644 index 000000000..2ae90c7d7 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp @@ -0,0 +1,56 @@ +#ifndef _ICONSCROLLERMENU +#define _ICONSCROLLERMENU + +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScroller.hpp" + +struct FEString; + +// total size: 0x16C +struct IconScrollerMenu : public MenuScreen { +protected: + IconScroller Options; // offset 0x2C, size 0x11C + bool bWasLeftMouseDown; // offset 0x148, size 0x1 + bool bFadeInIconsImmediately; // offset 0x14C, size 0x1 + FEString* pOptionName; // offset 0x150, size 0x4 + FEString* pOptionNameShadow; // offset 0x154, size 0x4 + FEString* pOptionDesc; // offset 0x158, size 0x4 + unsigned int PrevButtonMessage; // offset 0x15C, size 0x4 + FEObject* PrevButtonObj; // offset 0x160, size 0x4 + unsigned int PrevParam1; // offset 0x164, size 0x4 + unsigned int PrevParam2; // offset 0x168, size 0x4 + +public: + IconScrollerMenu(ScreenConstructorData* sd); + ~IconScrollerMenu() override {} + + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void StorePrevNotification(unsigned int msg, FEObject* pobj, unsigned int param1, unsigned int param2); + virtual void RefreshHeader(); + virtual void Setup() {} + void AddOption(IconOption* option); + + void DelayFadeIn() { + bFadeInIconsImmediately = true; + } + + void SetInitialOption(int index) { + if (bFadeInIconsImmediately) { + Options.StartFadeIn(); + } + Options.SetInitialPos(index); + } + + void StartInput() { + Options.SetReactToInput(true); + } + + void StopInput() { + Options.SetReactToInput(false); + } +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.cpp index e69de29bb..cdd6bb532 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.cpp @@ -0,0 +1,249 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.hpp" + +struct FEObject; + +extern FEImage *FEngFindImage(const char *pkg_name, int name_hash); +extern FEString *FEngFindString(const char *pkg_name, int name_hash); +extern unsigned int FEngHashString(const char *, ...); +extern void FEngSetVisible(FEObject *obj); +extern void FEngSetInvisible(FEObject *obj); +extern void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +extern void FEngGetSize(FEObject *object, float &x, float &y); +extern void FEngSetSize(FEObject *object, float x, float y); +extern void FEngGetTopLeft(FEObject *object, float &x, float &y); +extern void FEngSetTopLeft(FEObject *object, float x, float y); +extern void FEngGetBottomRightUV(FEImage *img, float &u, float &v); +extern void FEngSetBottomRightUV(FEImage *img, float u, float v); +extern int FEPrintf(FEString *text, const char *fmt, ...); + +cSlider::cSlider() { + pBase = nullptr; + pFillBar = nullptr; + pHandle = nullptr; + pLeftCap = nullptr; + pRightCap = nullptr; + pValue = nullptr; + fMaxValue = 0.0f; + fMinValue = 0.0f; + fPrevValue = 0.0f; + fCurValue = 0.0f; + fDesiredValue = 0.0f; + fIncrement = 0.0f; + fRange = 0.0f; + fInnerOffset = 0.0f; +} + +bool cSlider::Update(unsigned long msg) { + bool actual_scroll = false; + if (msg == 0x9120409E) { + fPrevValue = fCurValue; + float newVal = fCurValue - fIncrement; + float d = fMinValue; + if (fMinValue - newVal < 0.0f) { + d = newVal; + } + fCurValue = d; + if (d != fMinValue || fCurValue != fMinValue) { + actual_scroll = true; + } + } else if (msg == 0xB59719F1) { + fPrevValue = fCurValue; + float newVal = fCurValue + fIncrement; + float d = fMaxValue; + if (fMaxValue - newVal < 0.0f) { + d = newVal; + } + fCurValue = d; + if (d != fMaxValue || fCurValue != fMaxValue) { + actual_scroll = true; + } + } + Draw(); + return actual_scroll; +} + +void cSlider::Init(const char *pkg_name, const char *name, float min, float max, float inc, float cur, float range) { + InitObjects(pkg_name, name); + InitValues(min, max, inc, cur, range); +} + +void cSlider::InitObjects(const char *pkg_name, const char *name) { + if (pkg_name && name) { + pBase = FEngFindImage(pkg_name, FEngHashString("BASE_%s", name)); + pFillBar = FEngFindImage(pkg_name, FEngHashString("FILLBAR_%s", name)); + pHandle = FEngFindImage(pkg_name, FEngHashString("HANDLE_%s", name)); + pLeftCap = FEngFindImage(pkg_name, FEngHashString("LEFT_CAP_%s", name)); + pRightCap = FEngFindImage(pkg_name, FEngHashString("RIGHT_CAP_%s", name)); + pValue = FEngFindString(pkg_name, FEngHashString("VALUE_%s", name)); + } +} + +void cSlider::InitValues(float min, float max, float inc, float cur, float range) { + fRange = range; + cur = bMax(cur, min); + fIncrement = inc; + fMaxValue = max; + max = bMin(cur, max); + fMinValue = min; + fDesiredValue = max; + fCurValue = max; +} + +void cSlider::SetValue(float fvalue) { + fvalue = bMax(fvalue, fMinValue); + fPrevValue = fCurValue; + float max = bMin(fvalue, fMaxValue); + fCurValue = max; +} + +void cSlider::Draw() { + float d = fMaxValue - fMinValue; + if (d == 0.0f) { + fMinValue = 0.0f; + d = 1.0f; + fMaxValue = 1.0f; + } + float cur_pcnt = fRange * ((fCurValue - fMinValue) / d); + + float y; + FEngGetSize(reinterpret_cast(pFillBar), cur_pcnt, y); + FEngSetSize(reinterpret_cast(pFillBar), cur_pcnt, y); + + float base_x; + float dummy1; + FEngGetTopLeft(reinterpret_cast(pBase), base_x, dummy1); + + float dummy2; + float base_y; + FEngGetTopLeft(reinterpret_cast(pBase), dummy2, base_y); + float vert_offset = base_y + -12.0f; + + FEngSetTopLeft(reinterpret_cast(pFillBar), base_x + 2.0f, vert_offset); + + float dummy3; + float v; + FEngGetBottomRightUV(pFillBar, dummy3, v); + FEngSetBottomRightUV(pFillBar, cur_pcnt, v); + + FEngSetTopLeft(reinterpret_cast(pLeftCap), base_x - 1.5f, vert_offset); + FEngSetTopLeft(reinterpret_cast(pRightCap), base_x + cur_pcnt - 7.0f, vert_offset); + + if (pValue) { + FEPrintf(pValue, "%d", static_cast(fCurValue)); + } +} + +void cSlider::ToggleVisible(bool bOn) { + if (bOn) { + FEngSetVisible(reinterpret_cast(pBase)); + FEngSetVisible(reinterpret_cast(pFillBar)); + FEngSetVisible(reinterpret_cast(pValue)); + FEngSetVisible(reinterpret_cast(pHandle)); + } else { + FEngSetInvisible(reinterpret_cast(pValue)); + FEngSetInvisible(reinterpret_cast(pBase)); + FEngSetInvisible(reinterpret_cast(pFillBar)); + FEngSetInvisible(reinterpret_cast(pHandle)); + } +} + +void cSlider::Highlight() { + FEngSetScript(reinterpret_cast(pBase), 0x249DB7B7, true); + FEngSetScript(reinterpret_cast(pFillBar), 0x249DB7B7, true); + FEngSetScript(reinterpret_cast(pValue), 0x249DB7B7, true); + FEngSetScript(reinterpret_cast(pHandle), 0x249DB7B7, true); +} + +void cSlider::UnHighlight() { + FEngSetScript(reinterpret_cast(pBase), 0x7AB5521A, true); + FEngSetScript(reinterpret_cast(pFillBar), 0x7AB5521A, true); + FEngSetScript(reinterpret_cast(pValue), 0x7AB5521A, true); + FEngSetScript(reinterpret_cast(pHandle), 0x7AB5521A, true); +} + +void cSlider::SetPos(float x, float y) { + FEngSetTopLeft(reinterpret_cast(pBase), x, y); + FEngSetTopLeft(reinterpret_cast(pFillBar), x, y); +} + +void TwoStageSlider::Init(const char *pkg_name, const char *name, float min, float max, float inc, float cur, float preview, float range) { + cSlider::InitObjects(pkg_name, name); + InitObjects(pkg_name, name); + InitValues(min, max, inc, cur, preview, range); +} + +void TwoStageSlider::InitObjects(const char *pkg_name, const char *name) { + cSlider::InitObjects(pkg_name, name); + pPreviewBar = FEngFindImage(pkg_name, FEngHashString("PREVIEWBAR_%s", name)); +} + +void TwoStageSlider::InitValues(float min, float max, float inc, float cur, float preview, float range) { + cSlider::InitValues(min, max, inc, cur, range); + float d = fMaxValue; + if (fMaxValue - preview < 0.0f) { + d = preview; + } + float c = fMinValue; + if (fMinValue - d < 0.0f) { + c = d; + } + fPreviewValue = c; +} + +void TwoStageSlider::ToggleVisible(bool bOn) { + cSlider::ToggleVisible(bOn); + if (bOn) { + FEngSetVisible(reinterpret_cast(pPreviewBar)); + } else { + FEngSetInvisible(reinterpret_cast(pPreviewBar)); + } +} + +void TwoStageSlider::Draw() { + float d = fMaxValue - fMinValue; + if (d == 0.0f) { + fMinValue = 0.0f; + d = 1.0f; + fMaxValue = 1.0f; + } + float cur_pcnt = fRange * ((fCurValue - fMinValue) / d); + float fill_size = cur_pcnt; + + float y; + FEngGetSize(reinterpret_cast(pFillBar), fill_size, y); + FEngSetSize(reinterpret_cast(pFillBar), fill_size, y); + + float base_x; + float dummy1; + FEngGetTopLeft(reinterpret_cast(pBase), base_x, dummy1); + + float dummy2; + float base_y; + FEngGetTopLeft(reinterpret_cast(pBase), dummy2, base_y); + float vert_offset = base_y + -12.0f; + + FEngSetTopLeft(reinterpret_cast(pFillBar), base_x + 2.0f, vert_offset); + + float dummy3; + float v; + FEngGetBottomRightUV(pFillBar, dummy3, v); + FEngSetBottomRightUV(pFillBar, fill_size, v); + + FEngSetTopLeft(reinterpret_cast(pLeftCap), base_x - 1.5f, vert_offset); + FEngSetTopLeft(reinterpret_cast(pRightCap), base_x + fill_size - 7.0f, vert_offset); + + if (pValue) { + FEPrintf(pValue, "%d", static_cast(fCurValue)); + } + + float preview_pcnt = fRange * ((fPreviewValue - fMinValue) / d); + + float y2; + FEngGetSize(reinterpret_cast(pPreviewBar), preview_pcnt, y2); + FEngSetSize(reinterpret_cast(pPreviewBar), preview_pcnt, y2); + + float dummy4; + float v2; + FEngGetBottomRightUV(pPreviewBar, dummy4, v2); + FEngSetBottomRightUV(pPreviewBar, preview_pcnt, v2); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.hpp index 8bbeb8901..f9a7a6518 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.hpp @@ -1,10 +1,69 @@ -#ifndef FRONTEND_MENUSCREENS_COMMON_SLIDER_H -#define FRONTEND_MENUSCREENS_COMMON_SLIDER_H +#ifndef _CSLIDER +#define _CSLIDER -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +struct FEImage; +struct FEString; + +struct cSlider { +protected: + FEImage* pBase; // 0x00 + FEImage* pFillBar; // 0x04 + FEImage* pHandle; // 0x08 + FEImage* pLeftCap; // 0x0C + FEImage* pRightCap; // 0x10 + FEString* pValue; // 0x14 + float fMaxValue; // 0x18 + float fMinValue; // 0x1C + float fPrevValue; // 0x20 + float fCurValue; // 0x24 + float fDesiredValue; // 0x28 + float fIncrement; // 0x2C + float fRange; // 0x30 + float fInnerOffset; // 0x34 + +public: + cSlider(); + virtual ~cSlider() {} + virtual bool Update(unsigned long msg); + virtual void Init(const char* pkg_name, const char* name, float min, float max, float inc, float cur, float range); + virtual void InitObjects(const char* pkg_name, const char* name); + virtual void InitValues(float min, float max, float inc, float cur, float range); + virtual void ToggleVisible(bool bOn); + void SetValue(float fvalue); + void Increment(); + void Decrement(); + virtual void Highlight(); + virtual void UnHighlight(); + float GetMax() { return fMaxValue; } + float GetMin() { return fMinValue; } + float GetValue() { return fCurValue; } + float GetPrevValue() { return fPrevValue; } + float GetBaseWidth(); + float GetBaseHeight(); + virtual void SetPos(float x, float y); + void SetValueText(); + virtual void Draw(); +}; + +struct TwoStageSlider : public cSlider { + TwoStageSlider() {} + + ~TwoStageSlider() override {} + + float GetPreviewValue() { return fPreviewValue; } + + void SetPreviewValue(float preview_value) { fPreviewValue = preview_value; } + virtual void Init(const char *pkg_name, const char *name, float min, float max, float inc, float cur, float preview, float range); + void InitObjects(const char *pkg_name, const char *name) override; + virtual void InitValues(float min, float max, float inc, float cur, float preview, float range); + void ToggleVisible(bool bOn) override; + void Draw() override; + FEImage *pPreviewBar; // offset 0x3C + float fPreviewValue; // offset 0x40 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp new file mode 100644 index 000000000..16af555ba --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp @@ -0,0 +1,112 @@ +#ifndef _UIWIDGETMENU +#define _UIWIDGETMENU + +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +struct FEButtonWidget; +struct FEToggleImageWidget; +struct FEInputWidget; +struct FEDateWidget; + +// total size: 0x138 +struct UIWidgetMenu : public MenuScreen { + bTList Options; // offset 0x2C, size 0x8 + FEWidget* pCurrentOption; // offset 0x34, size 0x4 + FEWidget* pViewTop; // offset 0x38, size 0x4 + FEObject* pTitleMaster; // offset 0x3C, size 0x4 + FEObject* pDataMaster; // offset 0x40, size 0x4 + FEObject* pPrevButtonObj; // offset 0x44, size 0x4 + FEString* pDoneText; // offset 0x48, size 0x4 + FEObject* pDone; // offset 0x4C, size 0x4 + FEObject* pCursor; // offset 0x50, size 0x4 + FEScrollBar ScrollBar; // offset 0x54, size 0x64 + const char* pTitleName; // offset 0xB8, size 0x4 + const char* pDataName; // offset 0xBC, size 0x4 + const char* pDataImageName; // offset 0xC0, size 0x4 + const char* pBackingName; // offset 0xC4, size 0x4 + const char* pLeftArrowName; // offset 0xC8, size 0x4 + const char* pRightArrowName; // offset 0xCC, size 0x4 + const char* pSliderName; // offset 0xD0, size 0x4 + bVector2 vWidgetStartPos; // offset 0xD4, size 0x8 + bVector2 vLastWidgetPos; // offset 0xDC, size 0x8 + bVector2 vWidgetSize; // offset 0xE4, size 0x8 + bVector2 vMaxTitleSize; // offset 0xEC, size 0x8 + bVector2 vMaxDataSize; // offset 0xF4, size 0x8 + bVector2 vDataPos; // offset 0xFC, size 0x8 + bVector2 vWidgetSpacing; // offset 0x104, size 0x8 + unsigned int iIndexToAdd; // offset 0x10C, size 0x4 + unsigned int iLastSelectedIndex;// offset 0x110, size 0x4 + unsigned int iMaxWidgetsOnScreen; // offset 0x114, size 0x4 + unsigned int iPrevButtonMessage;// offset 0x118, size 0x4 + unsigned int iPrevParam1; // offset 0x11C, size 0x4 + unsigned int iPrevParam2; // offset 0x120, size 0x4 + bool bScrollWrapped; // offset 0x124, size 0x1 + bool bCurrentOptionSet; // offset 0x128, size 0x1 + bool bHasScrollBar; // offset 0x12C, size 0x1 + bool bViewNeedsSync; // offset 0x130, size 0x1 + bool bAllowScroll; // offset 0x134, size 0x1 + + UIWidgetMenu(ScreenConstructorData* sd); + ~UIWidgetMenu() override {} + + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void StorePrevNotification(unsigned int msg, FEObject* pobj, unsigned int param1, unsigned int param2); + FEWidget* GetWidget(unsigned int id); + void Scroll(eScrollDir dir); + void ScrollWrapped(eScrollDir dir); + void ScrollView(int dir); + void PageUp(); + void PageDown(); + unsigned int AddButtonOption(FEButtonWidget* option); + unsigned int AddStatOption(FEStatWidget* option); + unsigned int AddToggleOption(FEToggleWidget* option, bool use_arrow); + unsigned int AddToggleImageOption(FEToggleImageWidget* option, bool use_arrow); + unsigned int AddSliderOption(FESliderWidget* option, bool use_arrow); + unsigned int AddInputOption(FEInputWidget* option); + unsigned int AddDateOption(FEDateWidget* option); + FEString* GetCurrentFEString(const char* string_name); + FEImage* GetCurrentFEImage(const char* img_name); + FEObject* GetCurrentFEObject(const char* name); + void ClearWidgets(); + void RefreshWidgets(); + void SetInitialOption(int number); + void SetOption(FEWidget* opt); + void SetInitialPositions(); + void Reposition(); + void Reset(); + void UpdateCursorPos(); + void IncrementStartPos(); + void SyncViewToSelection(); + unsigned int GetWidgetIndex(FEWidget* opt); + + unsigned int GetNumWidgets(); + + virtual void Setup(); + + void SetWidgetStartPos(bVector2& pos) { + vWidgetStartPos = pos; + } + + void SetWidgetStartX(float x) { + vWidgetStartPos.x = x; + } + + void SetWidgetStartY(float y) { + vWidgetStartPos.y = y; + } + + void SetScrollWrapped(bool wrapped) { + bScrollWrapped = wrapped; + } + + FEInputWidget* GetCurrentInputWidget(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.cpp index e69de29bb..eb9646fd6 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.cpp @@ -0,0 +1,382 @@ +#include "feArrayScrollerMenu.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" + +extern void FEngSetTextureHash(FEImage *image, unsigned int hash); +extern void FEngSetVisible(FEObject *object); +extern void FEngSetInvisible(FEObject *object); +extern bool FEngIsScriptSet(FEObject *object, unsigned int hash); +extern void FEngSetScript(FEObject *object, unsigned int hash, bool play); +extern unsigned long FEHashUpper(const char *string); +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +extern void FEngSetLanguageHash(const char *pkg_name, unsigned int obj_hash, unsigned int language); +extern void FEngSetCurrentButton(const char *pkg_name, unsigned int hash); + +// ============================================================ +// ArrayScripts +// ============================================================ + +ArrayScripts::ArrayScripts() { + SetNormalHash(FEHashUpper("INIT")); + SetGreyHash(FEHashUpper("GREY")); + SetHighlightHash(FEHashUpper("HIGHLIGHT")); + SetUnHighlightHash(FEHashUpper("UNHIGHLIGHT")); +} + +// ============================================================ +// ArraySlot +// ============================================================ + +ArraySlot::ArraySlot(FEObject *obj) + : FEngObject(obj) // + , scripts(nullptr) { +} + +void ArraySlot::Update(ArrayDatum *datum, bool isSelected) { + if (!datum) { + FEngSetInvisible(GetFEngObject()); + } else { + FEngSetVisible(GetFEngObject()); + if (!datum->IsGreyedOut() && datum->IsEnabled()) { + if (isSelected) { + if (!FEngIsScriptSet(GetFEngObject(), scripts->GetHighlightHash())) { + FEngSetScript(GetFEngObject(), scripts->GetHighlightHash(), true); + } + } else { + if (FEngIsScriptSet(GetFEngObject(), scripts->GetHighlightHash())) { + FEngSetScript(GetFEngObject(), scripts->GetUnHighlightHash(), true); + } else { + if (!FEngIsScriptSet(GetFEngObject(), scripts->GetNormalHash())) { + FEngSetScript(GetFEngObject(), scripts->GetNormalHash(), true); + } + } + } + } else { + if (!FEngIsScriptSet(GetFEngObject(), scripts->GetGreyHash())) { + FEngSetScript(GetFEngObject(), scripts->GetGreyHash(), true); + } + } + } +} + +// ============================================================ +// ImageArraySlot +// ============================================================ + +ImageArraySlot::ImageArraySlot(FEImage *img) + : ArraySlot(static_cast< FEObject * >(static_cast< void * >(img))) { +} + +void ImageArraySlot::SetTexture(unsigned int tex_hash) { + FEngSetTextureHash(static_cast< FEImage * >(GetFEngObject()), tex_hash); +} + +void ImageArraySlot::Update(ArrayDatum *datum, bool isSelected) { + ArraySlot::Update(datum, isSelected); + if (datum) { + SetTexture(datum->GetHash()); + } +} + +// ============================================================ +// ArrayDatum +// ============================================================ + +ArrayDatum::ArrayDatum(uint32 h, uint32 d) + : hash(h) // + , desc(d) // + , enabled(true) // + , greyedOut(false) // + , locked(false) // + , checked(false) { +} + +// ============================================================ +// ArrayScroller +// ============================================================ + +ArrayScroller::ArrayScroller(const char *name, int w, int h, bool selectable) + : bShouldPlaySound(false) // + , currentDatum(nullptr) // + , startDatum(0) // + , width(w) // + , height(h) // + , descLabel(0) // + , pkg_name(name) // + , pScrollRegion(nullptr) // + , bSelectableArray(selectable) // + , mouseDownMsg(0) // + , bInClickToSelectMode(false) // +{ + ScrollBar.~FEScrollBar(); + new (&ScrollBar) FEScrollBar(name, "scrollbar", true, true, false); + scripts.~ArrayScripts(); + new (&scripts) ArrayScripts(); + pScrollRegion = FEngFindObject(name, FEHashUpper("ARRAY_SCROLL_REGION")); + pkg = cFEng::mInstance->FindPackage(name); +} + +void ArrayScroller::RefreshHeader() { + for (int i = 0; i < GetNumSlots(); i++) { + ArrayDatum *datum = GetDatumAt(startDatum + i); + ArraySlot *slot = GetSlotAt(i); + if (slot) { + slot->Update(datum, currentDatum == datum); + } + } + if (currentDatum) { + FEngSetLanguageHash(GetPkgName(), descLabel, currentDatum->GetDesc()); + } +} + +void ArrayScroller::AddSlot(ArraySlot *slot) { + slot->SetScripts(&scripts); + slots.AddTail(slot); +} + +void ArrayScroller::AddDatum(ArrayDatum *datum) { + data.AddTail(datum); + if (!currentDatum) { + currentDatum = datum; + } +} + +void ArrayScroller::SetSelection(ArrayDatum *newDatum, int newStartDatum) { + if (newDatum->IsEnabled()) { + startDatum = newStartDatum; + currentDatum = newDatum; + if (bSelectableArray) { + ArraySlot *pSlot = GetSlotAt(data.GetNodeNumber(currentDatum) - startDatum); + if (pSlot) { + FEngSetCurrentButton(GetPkgName(), pSlot->GetFEngObject()->NameHash); + } + } + } +} + +int ArrayScroller::ForceSelectionOnScreen(int new_datum, int start) { + int w = GetWidth(); + int h = GetHeight(); + int ret = start; + if (new_datum < start) { + ret = new_datum / w; + } else if (new_datum > start + w * h) { + ret = (new_datum / w - (h - 1)); + } + return ret * w; +} + +void ArrayScroller::ScrollHor(eScrollDir dir) { + int num_data = data.CountElements(); + if (num_data == 0) { + return; + } + ArrayDatum *new_datum = currentDatum; + int current_num = data.GetNodeNumber(currentDatum) - 1; + int new_index = current_num; + if (dir == eSD_PREV) { + int w = width; + new_index = current_num - 2; + if (current_num == (current_num / w) * w) { + new_index = new_index + w; + } + int num_data2 = data.CountElements(); + if (num_data2 <= new_index) { + new_index = data.CountElements() - 1; + } + } else if (dir == eSD_NEXT) { + int w = width; + new_index = current_num; + if (current_num == (current_num / w) * w) { + new_index = current_num - w; + } + int num_data2 = data.CountElements(); + if (num_data2 <= new_index) { + new_index = (new_index / width) * width; + } + } + if (new_index > current_num && new_index < data.CountElements()) { + ArrayDatum *old = currentDatum; + do { + current_num++; + new_datum = static_cast< ArrayDatum * >(new_datum->GetNext()); + } while (current_num < new_index); + } else if (new_index < current_num && new_index > -1) { + ArrayDatum *old = currentDatum; + for (int i = new_index; i < current_num; i++) { + new_datum = static_cast< ArrayDatum * >(new_datum->GetPrev()); + } + } + if (new_datum != currentDatum) { + int forced = ForceSelectionOnScreen(new_index, startDatum); + SetSelection(new_datum, forced); + bShouldPlaySound = true; + } + RefreshHeader(); +} + +void ArrayScroller::ScrollVer(eScrollDir dir) { + int num_data = data.CountElements(); + if (num_data == 0) { + return; + } + ArrayDatum *new_datum = currentDatum; + int current_num = data.GetNodeNumber(currentDatum) - 1; + int new_start = startDatum; + int new_index; + if (dir == eSD_PREV) { + int w = width; + new_index = current_num - w; + if (!pScrollRegion) { + int last_row_start = (height - 1) * w; + if (new_index >= last_row_start && new_index < height * w) { + new_index = new_index - last_row_start; + } + new_start = new_start - w; + } else if (new_index < new_start) { + new_start = new_start - w; + } + if (new_index > -1) { + int old_num = data.GetNodeNumber(currentDatum) - 1; + for (int i = 0; i < old_num - new_index; i++) { + new_datum = static_cast< ArrayDatum * >(new_datum->GetPrev()); + } + } + } else if (dir == eSD_NEXT) { + int w = width; + new_index = current_num + w; + if (!pScrollRegion) { + if (new_index >= w && new_index < w * 2) { + new_index = new_index + (height - 1) * w; + } + new_start = new_start + w; + } else { + int total = data.CountElements(); + if (total <= new_index) { + int total2 = data.CountElements(); + int w2 = width; + int h2 = height; + int cur_page = (data.GetNodeNumber(currentDatum) - 1) / width + 1; + if (cur_page * height < ((total2 - 1) / w2 + 1) * h2) { + new_index = data.CountElements() - 1; + } + } + int num_slots = slots.CountElements(); + if (new_start + num_slots <= new_index) { + new_start = new_start + width; + } + } + if (new_index < data.CountElements()) { + int old_num = data.GetNodeNumber(currentDatum); + for (int i = 0; i < new_index - (old_num - 1); i++) { + new_datum = static_cast< ArrayDatum * >(new_datum->GetNext()); + } + } + } + if (new_datum != currentDatum) { + int forced = ForceSelectionOnScreen(new_index, new_start); + SetSelection(new_datum, forced); + UpdateScrollbar(); + bShouldPlaySound = true; + } + RefreshHeader(); +} + +void ArrayScroller::UpdateScrollbar() { + int h = height; + int num_data = data.CountElements(); + int w = GetWidth(); + int top_item = startDatum / w + 1; + int selected_item = data.GetNodeNumber(currentDatum) / width + 1; + ScrollBar.Update(h, (num_data - 1) / w + 1, top_item, selected_item); +} + +ArraySlot *ArrayScroller::GetSlotAt(int index) { + if (index < GetNumSlots()) { + return slots.GetNode(index); + } + return nullptr; +} + +ArrayDatum *ArrayScroller::GetDatumAt(int index) { + if (index < GetNumDatum()) { + return data.GetNode(index); + } + return nullptr; +} + +void ArrayScroller::SetInitialPosition(int index) { + if (GetNumDatum() == 0) { + UpdateScrollbar(); + } + if (index < GetNumDatum()) { + int size = width * height; + int newStartDatum = 0; + if (index > size - 1) { + int new_start = ((index / width * width + 1) - size) / width * width; + newStartDatum = new_start + width; + if (width == 1) { + newStartDatum = new_start; + } + } + ArrayDatum *datum = GetDatumAt(index); + SetSelection(datum, newStartDatum); + UpdateScrollbar(); + } +} + +void ArrayScroller::UpdateMouse() {} + +void ArrayScroller::ClearData() { + data.DeleteAllElements(); + startDatum = 0; + currentDatum = nullptr; +} + +void ArrayScroller::NotificationMessage(u32 msg, FEObject *pObj, u32 param1, u32 param2) { + ArrayDatum *currentDatum = GetCurrentDatum(); + if (currentDatum) { + currentDatum->NotificationMessage(msg, pObj, param1, param2); + } + if (msg == 0x9120409E) { + ScrollHor(eSD_PREV); + } else if (msg == 0x72619778) { + ScrollVer(eSD_PREV); + } else if (msg == 0x911C0A4B) { + ScrollVer(eSD_NEXT); + } else if (msg == 0x9803F6E2) { + UpdateMouse(); + } else if (msg == 0xB5971BF1) { + ScrollHor(eSD_NEXT); + } +} + +// ============================================================ +// ArrayScrollerMenu +// ============================================================ + +ArrayScrollerMenu::ArrayScrollerMenu(ScreenConstructorData *sd, int w, int h, bool selectable) + : MenuScreen(sd) // + , ArrayScroller(sd->PackageFilename, w, h, selectable) // +{ +} + +void ArrayScrollerMenu::NotificationMessage(u32 msg, FEObject *pObj, u32 param1, u32 param2) { + ArrayScroller::NotificationMessage(msg, pObj, param1, param2); +} + +eMenuSoundTriggers ArrayScrollerMenu::NotifySoundMessage(u32 msg, eMenuSoundTriggers maybe) { + if (msg == 0x9120409E || msg == 0xB5971BF1 || msg == 0x911C0A4B || + msg == 0x72619778 || msg == 0x480DF13F || + msg == 0xB205316C || msg == 0x48122792 || msg == 0x4AC5E165) { + if (!bShouldPlaySound) { + maybe = static_cast< eMenuSoundTriggers >(-1); + } + bShouldPlaySound = false; + } + return maybe; +} + +void ArrayScrollerMenu::RefreshHeader() { + ArrayScroller::RefreshHeader(); +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp index ae8a89ebf..172890689 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp @@ -6,6 +6,8 @@ #endif #include "FEMenuScreen.hpp" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FEString.h" #include "Speed/Indep/bWare/Inc/bList.hpp" #include "feScrollerina.hpp" @@ -105,6 +107,16 @@ class ArrayDatum : public bTNode { checked = b; } + ArrayDatum() + : hash(0) // + , desc(0) // + , enabled(true) // + , greyedOut(false) // + , locked(false) // + , checked(false) {} + + ArrayDatum(uint32 hash, uint32 desc); + virtual ~ArrayDatum() {} virtual void NotificationMessage(u32 msg, FEObject *pObj, u32 param1, u32 param2) {} @@ -118,6 +130,19 @@ class ArrayDatum : public bTNode { bool checked; // offset 0x1C, size 0x1 }; +// total size: 0x28 +struct CarDatum : public ArrayDatum { + CarDatum(uint32 hash, uint32 desc, uint32 handle) + : ArrayDatum(hash, desc) // + , Handle(handle) {} + + ~CarDatum() override {} + + void NotificationMessage(u32 msg, FEObject *pObj, u32 param1, u32 param2) override; + + uint32 Handle; // offset 0x24, size 0x4 +}; + // total size: 0x14 class ArraySlot : public bTNode { public: @@ -161,9 +186,9 @@ class ArrayScroller { int ForceSelectionOnScreen(int new_datum, int start); - // void ScrollHor(enum eScrollDir dir); + void ScrollHor(enum eScrollDir dir); - // void ScrollVer(enum eScrollDir dir); + void ScrollVer(enum eScrollDir dir); void UpdateScrollbar(); @@ -177,33 +202,33 @@ class ArrayScroller { void ClearData(); - void SetDescLabel(uint32 hash) {} + void SetDescLabel(uint32 hash) { descLabel = hash; } - void SetDimensions(int w, int h) {} + void SetDimensions(int w, int h) { width = w; height = h; } - int GetWidth() {} + int GetWidth() { return width; } - int GetHeight() {} + int GetHeight() { return height; } - void ScrollLeft() {} + void ScrollLeft() { ScrollHor(eSD_PREV); } - void ScrollRight() {} + void ScrollRight() { ScrollHor(eSD_NEXT); } - void ScrollUp() {} + void ScrollUp() { ScrollVer(eSD_PREV); } - void ScrollDown() {} + void ScrollDown() { ScrollVer(eSD_NEXT); } - void SetMouseDownMsg(uint32 msg) {} + void SetMouseDownMsg(uint32 msg) { mouseDownMsg = msg; } - void SetClickToSelectMode(bool flag) {} + void SetClickToSelectMode(bool flag) { bInClickToSelectMode = flag; } - int GetNumSlots() {} + int GetNumSlots() { return slots.CountElements(); } - int GetStartDatumNum() {} + int GetStartDatumNum() { return startDatum; } - int GetCurrentDatumNum() {} + int GetCurrentDatumNum() { return startDatum; } - int GetNumDatum() {} + int GetNumDatum() { return data.CountElements(); } ArrayDatum *GetCurrentDatum() { return currentDatum; @@ -229,7 +254,7 @@ class ArrayScroller { return &scripts; } - private: + protected: bool bShouldPlaySound; // offset 0x0, size 0x1 bTList slots; // offset 0x4, size 0x8 bTList data; // offset 0xC, size 0x8 @@ -266,4 +291,13 @@ class ArrayScrollerMenu : public MenuScreen, public ArrayScroller { void RefreshHeader() override; }; +// total size: 0x14 +class ImageArraySlot : public ArraySlot { + public: + ImageArraySlot(FEImage *img); + ~ImageArraySlot() override {} + void Update(ArrayDatum *datum, bool isSelected) override; + void SetTexture(unsigned int tex_hash); +}; + #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feDialogBox.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feDialogBox.cpp index e69de29bb..f26c960d0 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feDialogBox.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feDialogBox.cpp @@ -0,0 +1,516 @@ +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/feimage.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEngFont.hpp" +#include "Speed/Indep/Src/Misc/Gameflow.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" + +extern int bStrCmp(const char *, const char *); +extern int bStrLen(const char *); +extern void GetLocalizedString(char *buf, unsigned int maxlen, unsigned int hash); +extern int FEngMapJoyportToJoyParam(int); +extern void FEngSetCreateCallback(const char *, MenuScreen *(*)(ScreenConstructorData *)); +extern FEObject *FEngGetCurrentButton(const char *); +extern FEObject *FEngFindObject(const char *, unsigned int); +extern void FEngSetInvisible(FEObject *); +extern FEImage *FEngFindImage(const char *, int); +extern void FEngSetTextureHash(FEImage *, unsigned int); +extern FEString *FEngFindString(const char *, int); +extern void FEngSetLanguageHash(const char *, unsigned int, unsigned int); +extern int FEPrintf(const char *, int, const char *, ...); +extern void FEngSetCurrentButton(const char *, unsigned int); +extern void FEngSetButtonState(const char *, unsigned int, bool); + +extern Timer RealTimer; + +struct feDialogScreen : MenuScreen { + feDialogScreen(ScreenConstructorData *sd); + ~feDialogScreen() override; + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void BuildFromConfig(); + + unsigned int ReturnWithMessage; // offset 0x2C + unsigned int mLastButtonHash; // offset 0x30 + feDialogConfig Config; // offset 0x34, size 0x248 + unsigned long ControllerPort; // offset 0x27C + Timer tCountdownTimer; // offset 0x280 +}; + +static feDialogConfig SecretDialogInfo; +static int gDialogHandle = 4; + +MenuScreen *DialogCreater(ScreenConstructorData *sd) { + return new ("", 0) feDialogScreen(sd); +} + +feDialogConfig::feDialogConfig() { + bMemSet(BlurbString, 0, DIALOG_BLURB_MAX_LENGTH); + Title = dialog_info; + DialogHandle = 0; + fCountdown = 0.0f; + Button1TextHash = 0; + Button1PressedMessage = 0; + Button2TextHash = 0; + Button2PressedMessage = 0; + Button3TextHash = 0; + Button3PressedMessage = 0; + DialogCancelledMessage = 0; + FirstButton = 0; + ParentPackage = nullptr; + DialogPackage = nullptr; + NumButtons = 0; + bIsDismissable = false; + bDetectController = false; + bBlurbIsUTF8 = false; +} + +extern void FormatMessage(char *buffer, int bufsize, const char *fmt, va_list *arglist); + +void DialogInterface::DismissDialog(int handle) { + if (SecretDialogInfo.DialogHandle == handle) { + if (cFEng::Get()->IsPackageInControl(SecretDialogInfo.DialogPackage)) { + if (SecretDialogInfo.Title == dialog_fatalerror) { + cFEng::Get()->PopErrorPackage(); + } else { + cFEng::Get()->QueuePackagePop(1); + } + } + SecretDialogInfo.DialogHandle = 0; + cFEng::Get()->QueuePackageMessage(0xd731d75e, nullptr, nullptr); + } +} + +int DialogInterface::ShowDialog(feDialogConfig *conf) { + const char *user_passed = conf->DialogPackage; + if (user_passed && *user_passed != '\0') { + if (bStrCmp(user_passed, "animating") == 0) { + conf->DialogPackage = "OL_Dialog.fng"; + } else if (bStrCmp(user_passed, "3button") == 0) { + conf->DialogPackage = "Dialog.fng"; + } else if (bStrCmp(user_passed, "3buttons_stacked") == 0) { + conf->DialogPackage = "OL_Dialog_Stacked_Buttons.fng"; + } + } else { + if (IsGameFlowInGame()) { + conf->DialogPackage = "InGameDialog.fng"; + } else { + conf->DialogPackage = "Dialog.fng"; + } + } + + if (SecretDialogInfo.DialogHandle != 0) { + DismissDialog(SecretDialogInfo.DialogHandle); + } + + SecretDialogInfo = *conf; + SecretDialogInfo.DialogHandle = gDialogHandle; + gDialogHandle++; + + FEngSetCreateCallback(SecretDialogInfo.DialogPackage, DialogCreater); + + if (SecretDialogInfo.Title == dialog_fatalerror) { + int port = FEDatabase->GetPlayersJoystickPort(0); + cFEng::Get()->PushErrorPackage(SecretDialogInfo.DialogPackage, + reinterpret_cast(&SecretDialogInfo), + FEngMapJoyportToJoyParam(port)); + } else { + cFEng::Get()->QueuePackagePush(SecretDialogInfo.DialogPackage, + reinterpret_cast(&SecretDialogInfo), 0, false); + } + return SecretDialogInfo.DialogHandle; +} + +int DialogInterface::ShowOk(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + const char *fmt, va_list *arg_list) { + feDialogConfig conf; + FormatMessage(conf.BlurbString, 0x200, fmt, arg_list); + conf.Button1TextHash = 0x417b2601; + conf.Button1PressedMessage = 0x34dc1bec; + conf.DialogCancelledMessage = 0xb4edeb6d; + conf.FirstButton = 0; + conf.bIsDismissable = true; + conf.NumButtons = 1; + conf.Title = title; + conf.ParentPackage = from_pkg; + conf.DialogPackage = dlg_pkg; + return ShowDialog(&conf); +} + +int DialogInterface::ShowOk(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int message_hash, ...) { + char fmt[512]; + va_list arg_list; + GetLocalizedString(fmt, 0x200, message_hash); + va_start(arg_list, message_hash); + int result = ShowOk(from_pkg, dlg_pkg, title, fmt, &arg_list); + va_end(arg_list); + return result; +} + +int DialogInterface::ShowOneButton(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int cancel_message, bool dismissable, + const char *fmt, va_list *arg_list) { + feDialogConfig conf; + FormatMessage(conf.BlurbString, 0x200, fmt, arg_list); + conf.NumButtons = 1; + conf.FirstButton = 0; + conf.Title = title; + conf.Button1TextHash = button_text_hash; + conf.Button1PressedMessage = button_pressed_message; + conf.DialogCancelledMessage = cancel_message; + conf.ParentPackage = from_pkg; + conf.DialogPackage = dlg_pkg; + conf.bIsDismissable = dismissable; + return ShowDialog(&conf); +} + +int DialogInterface::ShowOneButton(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int cancel_message, unsigned int blurb_fmt, ...) { + char fmt[512]; + va_list arg_list; + GetLocalizedString(fmt, 0x200, blurb_fmt); + va_start(arg_list, blurb_fmt); + int result = ShowOneButton(from_pkg, dlg_pkg, title, button_text_hash, button_pressed_message, + cancel_message, false, fmt, &arg_list); + va_end(arg_list); + return result; +} + +int DialogInterface::ShowOneButton(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int blurb_fmt, ...) { + char fmt[512]; + va_list arg_list; + GetLocalizedString(fmt, 0x200, blurb_fmt); + va_start(arg_list, blurb_fmt); + int result = ShowOneButton(from_pkg, dlg_pkg, title, button_text_hash, button_pressed_message, + button_pressed_message, false, fmt, &arg_list); + va_end(arg_list); + return result; +} + +int DialogInterface::ShowTwoButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int cancel_message, bool dismissable, + eDialogFirstButtons first_button, + const char *fmt, va_list *arg_list) { + feDialogConfig conf; + FormatMessage(conf.BlurbString, 0x200, fmt, arg_list); + conf.NumButtons = 2; + conf.Title = title; + conf.Button1TextHash = button1_text_hash; + conf.Button1PressedMessage = button1_pressed_message; + conf.Button2TextHash = button2_text_hash; + conf.Button2PressedMessage = button2_pressed_message; + conf.DialogCancelledMessage = cancel_message; + conf.FirstButton = first_button; + conf.DialogPackage = dlg_pkg; + conf.ParentPackage = from_pkg; + conf.bIsDismissable = dismissable; + if (dismissable) { + FEPackage *pkg = cFEng::Get()->FindPackage(from_pkg); + if (pkg) { + unsigned long mask = pkg->GetControlMask(); + if (mask != 0xff) { + conf.bDetectController = true; + } + } + } + return ShowDialog(&conf); +} + +int DialogInterface::ShowTwoButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int cancel_message, eDialogFirstButtons first_button, + const char *fmt, ...) { + va_list arg_list; + va_start(arg_list, fmt); + int result = ShowTwoButtons(from_pkg, dlg_pkg, title, button1_text_hash, button2_text_hash, + button1_pressed_message, button2_pressed_message, + cancel_message, false, first_button, fmt, &arg_list); + va_end(arg_list); + return result; +} + +int DialogInterface::ShowTwoButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int cancel_message, eDialogFirstButtons first_button, + unsigned int blurb_fmt, ...) { + char fmt[512]; + va_list arg_list; + GetLocalizedString(fmt, 0x200, blurb_fmt); + va_start(arg_list, blurb_fmt); + int result = ShowTwoButtons(from_pkg, dlg_pkg, title, button1_text_hash, button2_text_hash, + button1_pressed_message, button2_pressed_message, + cancel_message, false, first_button, fmt, &arg_list); + va_end(arg_list); + return result; +} + +int DialogInterface::ShowTwoButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + eDialogFirstButtons first_button, unsigned int blurb_fmt, ...) { + char fmt[512]; + va_list arg_list; + GetLocalizedString(fmt, 0x200, blurb_fmt); + va_start(arg_list, blurb_fmt); + int result = ShowTwoButtons(from_pkg, dlg_pkg, title, button1_text_hash, button2_text_hash, + button1_pressed_message, button2_pressed_message, + button1_pressed_message, false, first_button, fmt, &arg_list); + va_end(arg_list); + return result; +} + +int DialogInterface::ShowThreeButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button3_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int button3_pressed_message, unsigned int cancel_message, + eDialogFirstButtons first_button, + const char *fmt, va_list *arg_list) { + feDialogConfig conf; + FormatMessage(conf.BlurbString, 0x200, fmt, arg_list); + conf.NumButtons = 3; + conf.Title = title; + conf.Button1TextHash = button1_text_hash; + conf.Button2TextHash = button2_text_hash; + conf.Button3TextHash = button3_text_hash; + conf.Button1PressedMessage = button1_pressed_message; + conf.Button2PressedMessage = button2_pressed_message; + conf.Button3PressedMessage = button3_pressed_message; + conf.DialogCancelledMessage = cancel_message; + conf.FirstButton = first_button; + conf.ParentPackage = from_pkg; + conf.DialogPackage = dlg_pkg; + return ShowDialog(&conf); +} + +int DialogInterface::ShowThreeButtons(const char *from_pkg, const char *dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button3_text_hash, + unsigned int button1_pressed_message, unsigned int button2_pressed_message, + unsigned int button3_pressed_message, unsigned int cancel_message, + eDialogFirstButtons first_button, + unsigned int blurb_fmt, ...) { + char fmt[512]; + va_list arg_list; + GetLocalizedString(fmt, 0x200, blurb_fmt); + va_start(arg_list, blurb_fmt); + int result = ShowThreeButtons(from_pkg, dlg_pkg, title, button1_text_hash, button2_text_hash, + button3_text_hash, button1_pressed_message, button2_pressed_message, + button3_pressed_message, cancel_message, first_button, fmt, &arg_list); + va_end(arg_list); + return result; +} + +feDialogScreen::feDialogScreen(ScreenConstructorData *sd) + : MenuScreen(sd) +{ + ControllerPort = 0xff; + ReturnWithMessage = 0; + tCountdownTimer = Timer(0); + Config = *reinterpret_cast(sd->Arg); + BuildFromConfig(); + tCountdownTimer = RealTimer; +} + +feDialogScreen::~feDialogScreen() { + if (ReturnWithMessage != static_cast(-1)) { + cFEng::Get()->QueueGameMessage(ReturnWithMessage, Config.ParentPackage, ControllerPort); + } +} + +eMenuSoundTriggers feDialogScreen::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (msg != 0x48122792) { + if (msg < 0x48122793) { + if (msg != 0x480c9a58) { + return maybe; + } + if (!Config.bIsDismissable) { + return static_cast(-1); + } + return maybe; + } + if (msg != 0x4ac5e165) { + return maybe; + } + } + FEObject *btn = FEngGetCurrentButton(GetPackageName()); + if (btn && FEngGetCurrentButton(GetPackageName())->NameHash != mLastButtonHash) { + mLastButtonHash = FEngGetCurrentButton(GetPackageName())->NameHash; + return maybe; + } + return static_cast(-1); +} + +void feDialogScreen::BuildFromConfig() { + const unsigned long FEObj_messageblurb = 0x1e2640fa; + + if (Config.bBlurbIsUTF8) { + FEString *pFEStr = FEngFindString(GetPackageName(), FEObj_messageblurb); + pFEStr->SetStringFromUTF8(Config.BlurbString); + } else { + FEPrintf(GetPackageName(), FEObj_messageblurb, Config.BlurbString); + } + + if (Config.NumButtons == 3) { + FEngSetLanguageHash(GetPackageName(), 0xf9363f30, Config.Button1TextHash); + FEngSetLanguageHash(GetPackageName(), 0xfb8b67d1, Config.Button2TextHash); + FEngSetLanguageHash(GetPackageName(), 0xfde09072, Config.Button3TextHash); + } else if (Config.NumButtons == 2) { + FEngDisableButton(GetPackageName(), 0xb8a7c6ce); + FEngSetInvisible(GetPackageName(), 0xb8a7c6ce); + FEPrintf(GetPackageName(), 0xfde09072, ""); + FEngSetLanguageHash(GetPackageName(), 0xf9363f30, Config.Button1TextHash); + FEngSetLanguageHash(GetPackageName(), 0xfb8b67d1, Config.Button2TextHash); + } else if (Config.NumButtons == 1) { + FEngDisableButton(GetPackageName(), 0xb8a7c6ce); + FEngDisableButton(GetPackageName(), 0xb8a7c6cd); + FEngSetInvisible(GetPackageName(), 0xb8a7c6ce); + FEngSetInvisible(GetPackageName(), 0xb8a7c6cd); + FEPrintf(GetPackageName(), 0xfde09072, ""); + FEPrintf(GetPackageName(), 0xfb8b67d1, ""); + FEngSetLanguageHash(GetPackageName(), 0xf9363f30, Config.Button1TextHash); + } else { + FEngDisableButton(GetPackageName(), 0xb8a7c6cc); + FEngDisableButton(GetPackageName(), 0xb8a7c6cd); + FEngDisableButton(GetPackageName(), 0xb8a7c6ce); + FEngSetInvisible(GetPackageName(), 0xb8a7c6cc); + FEngSetInvisible(GetPackageName(), 0xb8a7c6cd); + FEngSetInvisible(GetPackageName(), 0xb8a7c6ce); + FEngSetInvisible(GetPackageName(), 0x7f9dca9); + FEPrintf(GetPackageName(), 0xf9363f30, ""); + FEPrintf(GetPackageName(), 0xfb8b67d1, ""); + FEPrintf(GetPackageName(), 0xfde09072, ""); + } + + FEngFont *font = FindFont(0x545570c6); + float numLines = static_cast(bStrLen(Config.BlurbString)) * font->GetHeight(); + + const int MAX_SIZE_SMALL = 2200; + const int MAX_SIZE_MED = 4400; + const unsigned long FEObj_dialogsmall = 0x79b0c1c7; + const unsigned long FEObj_dialogmedium = 0xa13adcaf; + const unsigned long FEObj_dialoglarge = 0x792bc959; + + if (numLines < MAX_SIZE_SMALL) { + cFEng::Get()->QueuePackageMessage(FEObj_dialogsmall, GetPackageName(), nullptr); + } else if (numLines < MAX_SIZE_MED) { + cFEng::Get()->QueuePackageMessage(FEObj_dialogmedium, GetPackageName(), nullptr); + } else { + cFEng::Get()->QueuePackageMessage(FEObj_dialoglarge, GetPackageName(), nullptr); + } + + switch (Config.Title) { + case dialog_alert: + case dialog_fatalerror: + if (TheGameFlowManager.IsInGame()) { + FEngSetLanguageHash(GetPackageName(), 0x42adb44c, 0x2bd146d3); + } else { + FEngSetLanguageHash(GetPackageName(), 0x42adb44c, 0x6fd91524); + } + FEngSetTextureHash(GetPackageName(), 0xd4f4069, 0x6948e2b3); + FEngSetTextureHash(GetPackageName(), 0xfac88427, 0x6948e2b3); + break; + case dialog_none: + case dialog_info: + case dialog_countdown: + FEngSetLanguageHash(GetPackageName(), 0x42adb44c, 0xdbe419d4); + FEngSetTextureHash(GetPackageName(), 0xd4f4069, 0x1a7afe27); + FEngSetTextureHash(GetPackageName(), 0xfac88427, 0x1a7afe27); + break; + case dialog_confirmation: + FEngSetLanguageHash(GetPackageName(), 0x42adb44c, 0x60249a74); + FEngSetTextureHash(GetPackageName(), 0xd4f4069, 0x39949433); + FEngSetTextureHash(GetPackageName(), 0xfac88427, 0x39949433); + break; + } +} + +void feDialogScreen::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x35f8620b: + if (Config.NumButtons != 0) { + if (Config.FirstButton == 0) { + FEngSetCurrentButton(GetPackageName(), 0xb8a7c6cc); + } else if (Config.FirstButton == 1) { + FEngSetCurrentButton(GetPackageName(), 0xb8a7c6cd); + } else if (Config.FirstButton == 2) { + FEngSetCurrentButton(GetPackageName(), 0xb8a7c6ce); + } + mLastButtonHash = Config.FirstButton; + } + break; + + case 0xc98356ba: + if (Config.Title == dialog_countdown) { + const unsigned long FEObj_messageblurb = 0x1e2640fa; + int elapsed = static_cast((RealTimer - tCountdownTimer).GetSeconds()); + FEPrintf(GetPackageName(), FEObj_messageblurb, Config.BlurbString, static_cast(Config.fCountdown) - elapsed); + if (static_cast(elapsed) >= Config.fCountdown) { + NotificationMessage(0x911ab364, nullptr, 0, 0); + } + } + break; + + case 0x911ab364: + if (Config.bIsDismissable) { + ReturnWithMessage = Config.DialogCancelledMessage; + DialogInterface::DismissDialog(Config.DialogHandle); + } + break; + + case 0x72619778: + if (obj->NameHash == 0xb8a7c6cd && Config.NumButtons == 3) { + FEngSetCurrentButton(GetPackageName(), 0xb8a7c6cc); + } else if (obj->NameHash == 0xb8a7c6ce && Config.NumButtons != 1) { + FEngSetCurrentButton(GetPackageName(), 0xb8a7c6cd); + } + break; + + case 0x911c0a4b: + if (obj->NameHash == 0xb8a7c6cc) { + FEngSetCurrentButton(GetPackageName(), 0xb8a7c6cd); + } else if (obj->NameHash == 0xb8a7c6cd) { + FEngSetCurrentButton(GetPackageName(), 0xb8a7c6ce); + } + break; + + case 0x1b91ebf4: + ControllerPort = param1; + break; + + case 0x0c407210: + switch (obj->NameHash) { + case 0xb8a7c6cc: + if (Config.NumButtons != 0) { + ReturnWithMessage = Config.Button1PressedMessage; + DialogInterface::DismissDialog(Config.DialogHandle); + } + break; + case 0xb8a7c6cd: + if (Config.NumButtons != 0) { + ReturnWithMessage = Config.Button2PressedMessage; + DialogInterface::DismissDialog(Config.DialogHandle); + } + break; + case 0xb8a7c6ce: + if (Config.NumButtons != 0) { + ReturnWithMessage = Config.Button3PressedMessage; + DialogInterface::DismissDialog(Config.DialogHandle); + } + break; + } + break; + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feIconScrollerMenu.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feIconScrollerMenu.cpp index e69de29bb..7371151fa 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feIconScrollerMenu.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feIconScrollerMenu.cpp @@ -0,0 +1,968 @@ +#include "IconPanel.hpp" +#include "IconScroller.hpp" +#include "IconScrollerMenu.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" + +extern void FEngSetTextureHash(FEImage *image, unsigned int hash); +extern void FEngSetCurrentButton(const char *pkg_name, unsigned int hash); +extern void FEngGetCenter(FEObject *object, float &x, float &y); +extern void FEngSetCenter(FEObject *object, float x, float y); +extern void FEngSetLanguageHash(FEString *text, unsigned int hash); +extern unsigned long FEHash(const char *str); +extern FEColor FEngGetObjectColor(FEObject *object); +extern void FEngSetColor(FEObject *obj, unsigned int color); +extern void FEngSetLastButton(const char *pkg_name, unsigned char button_hash); +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool unk); +extern Timer RealTimer; +extern float RealTimeElapsed; +extern char *bStrCat(char *dest, const char *str1, const char *str2); +extern FEString *FEngFindString(const char *pkg_name, int hash); + +struct tCubic1D { + float Val; + float dVal; + float ValDesired; + float dValDesired; + float Coeff[4]; + float time; + float duration; + short state; + short flags; + + void Snap(); + void SetValDesired(float v); +}; + +struct cPoint { + static void SplineSeek(tCubic1D *p, float time, float lower, float upper); +}; + +static const char *gTUTORIAL_MOVIE_DRAG = "TUT_DRAG"; +static const char *gTUTORIAL_MOVIE_SPEEDTRAP = "TUT_SPEEDTRAP"; +static const char *gTUTORIAL_MOVIE_TOLLBOOTH = "TUT_TOLLBOOTH"; + +// ============================================================ +// IconOption +// ============================================================ + +IconOption::IconOption(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) { + YPos = 0.0f; + NameHash = name_hash; + DescHash = desc_hash; + fScaleAtStart = 1.0f; + pTutorialMovieName = nullptr; + Item = tex_hash; + FEngObject = nullptr; + XPos = 0.0f; + IsGreyOut = false; + IsFlashable = true; + fScaleToPcnt = 1.0f; + fScaleStartSecs = 1.0f; + fScaleDurSecs = 1.0f; + bAnimComplete = true; + bReactImmediately = false; + bIsTutorialAvailable = false; + + if (tex_hash == 0xAAAB31E9) { + bIsTutorialAvailable = true; + pTutorialMovieName = gTUTORIAL_MOVIE_DRAG; + } else if (tex_hash == 0x66C9A7B6) { + bIsTutorialAvailable = true; + pTutorialMovieName = gTUTORIAL_MOVIE_SPEEDTRAP; + } +} + +void IconOption::SetFEngObject(FEObject *obj) { + if (obj) { + FEngObject = obj; + FEngGetSize(obj, OrigWidth, OrigHeight); + OriginalColor = FEngGetColor(obj); + } +} + +void IconOption::StartScale(float scale_to, float duration) { + fScaleToPcnt = scale_to; + fScaleDurSecs = duration; + bAnimComplete = false; + fScaleStartSecs = RealTimer.GetSeconds(); +} + +unsigned int IconOption::GetDesc() { return DescHash; } +float IconOption::GetScaleToPcnt() { return fScaleToPcnt; } +float IconOption::GetScaleStartSecs() { return fScaleStartSecs; } +float IconOption::GetScaleDurSecs() { return fScaleDurSecs; } +float IconOption::GetScaleAtStart() { return fScaleAtStart; } +void IconOption::SetScaleAtStart(float scale) { fScaleAtStart = scale; } +bool IconOption::IsAnimComplete() { return bAnimComplete; } +void IconOption::SetAnimComplete(bool b) { bAnimComplete = b; } +bool IconOption::IsLocked() { return Locked; } +void IconOption::SetLocked(bool b) { Locked = b; } +void IconOption::SetTutorialMovieName(const char *name) { pTutorialMovieName = name; } + +// ============================================================ +// IconPanel +// ============================================================ + +IconPanel::IconPanel(const char *pkg_name, const char *master, const char *fe_button, const char *scroll_region, bool wrap) { + pPackageName = pkg_name; + pButtonName = fe_button; + bWrap = wrap; + pCurrentNode = nullptr; + fIconSpacing = 10.0f; + bReactToInput = true; + iIndexToAdd = 1; + bHorizontal = true; + bJustScrolled = true; + pMaster = static_cast(FEngFindObject(pkg_name, FEHash(master))); + pScrollRegion = static_cast(FEngFindObject(pkg_name, FEHash(scroll_region))); +} + +FEImage *IconPanel::AddOption(IconOption *option) { + char sztemp[32]; + char sztemp2[32]; + + bStrCat(sztemp, pButtonName, "%d"); + FEngSNPrintf(sztemp2, 32, sztemp, iIndexToAdd); + FEImage *obj = FEngFindImage(pPackageName, FEHashUpper(sztemp2)); + if (!obj) { + return nullptr; + } + iIndexToAdd++; + if (!option) { + return nullptr; + } + option->SetFEngObject(obj); + Options.AddTail(option); + if (!pCurrentNode) { + pCurrentNode = Options.GetHead(); + FEngSetCurrentButton(pPackageName, pCurrentNode->FEngObject->NameHash); + } + return obj; +} + +void IconPanel::Act(unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) { + if (pCurrentNode && obj == pCurrentNode->FEngObject) { + pCurrentNode->React(pPackageName, data, obj, param1, param2); + } +} + +bool IconPanel::SetSelection(IconOption *option) { + if (option->IsGreyOut) { + return false; + } + pCurrentNode->StartScale(0.614f, 0.2f); + pCurrentNode = option; + FEngSetCurrentButton(pPackageName, option->FEngObject->NameHash); + pCurrentNode->StartScale(0.95f, 0.2f); + return true; +} + +void IconPanel::SetInitialPos() { + float num_opts = static_cast(Options.CountElements()); + float size_x, size_y; + FEngGetSize(Options.GetHead()->FEngObject, size_x, size_y); + float master_x; + float master_y; + FEngGetCenter(pScrollRegion, master_x, master_y); + float first_x = master_x - (size_x * num_opts + fIconSpacing * (num_opts - 1.0f)) * 0.5f; + float first_y = master_y - (size_y * num_opts + fIconSpacing * (num_opts - 1.0f)) * 0.5f; + float i = 0.0f; + for (IconOption *opt = Options.GetHead(); opt != Options.EndOfList(); opt = opt->GetNext()) { + if (!bHorizontal) { + FEngSetTopLeft(opt->FEngObject, master_x - size_y * 0.5f, (size_y + fIconSpacing) * i + first_y); + } else { + FEngSetTopLeft(opt->FEngObject, (size_x + fIconSpacing) * i + first_x, master_y - size_y * 0.5f); + } + i += 1.0f; + } + SetSelection(pCurrentNode); +} + +void IconPanel::Scroll(eScrollDir dir) { + if (Options.CountElements() == 0) { + return; + } + IconOption *new_option = pCurrentNode; + if (dir == eSD_PREV) { + do { + if (new_option == Options.GetHead()) { + goto check; + } + new_option = new_option->GetPrev(); + } while (new_option->IsGreyOut); + } else if (dir == eSD_NEXT) { + do { + if (new_option == Options.GetTail()) { + goto check; + } + new_option = new_option->GetNext(); + } while (new_option->IsGreyOut); + } else { +check: + if (new_option->IsGreyOut) { + return; + } + } + if (new_option != pCurrentNode) { + SetSelection(new_option); + bJustScrolled = true; + } +} + +void IconPanel::ScrollWrapped(eScrollDir dir) { + if (Options.CountElements() == 0) { + return; + } + IconOption *new_option = pCurrentNode; + if (dir == eSD_PREV) { + do { + if (new_option == Options.GetHead()) { + new_option = Options.GetTail(); + } else { + new_option = new_option->GetPrev(); + } + } while (new_option->IsGreyOut); + } else if (dir == eSD_NEXT) { + do { + if (new_option == Options.GetTail()) { + new_option = Options.GetHead(); + } else { + new_option = new_option->GetNext(); + } + } while (new_option->IsGreyOut); + } + if (!new_option->IsGreyOut && new_option != pCurrentNode) { + SetSelection(new_option); + bJustScrolled = true; + } +} + +void IconPanel::Update() { + AnimateList(); +} + +void IconPanel::AnimateList() { + float list_width = 0.0f; + float list_height = 0.0f; + AnimateSelected(list_width, list_height); +} + +void IconPanel::AnimateSelected(float &list_width, float &list_height) { + bJustScrolled = false; + list_width = 0.0f; + list_height = 0.0f; + for (IconOption *opt = Options.GetHead(); opt != Options.EndOfList(); opt = opt->GetNext()) { + float scale = 1.0f; + if (!opt->IsAnimComplete()) { + float pcnt_complete = (RealTimer.GetSeconds() - opt->GetScaleStartSecs()) / opt->GetScaleDurSecs(); + float delta_scale = opt->GetScaleToPcnt() - opt->GetScaleAtStart(); + scale = pcnt_complete * delta_scale + opt->GetScaleAtStart(); + if (delta_scale < 0.0f) { + if (scale <= opt->GetScaleToPcnt()) { + FEngSetSize(opt->FEngObject, opt->OrigWidth * opt->GetScaleToPcnt(), opt->OrigHeight * opt->GetScaleToPcnt()); + opt->SetScaleAtStart(scale); + opt->SetAnimComplete(true); + goto next; + } + } else if (scale >= opt->GetScaleToPcnt()) { + if (delta_scale < 0.0f) { + if (scale <= opt->GetScaleToPcnt()) { + FEngSetSize(opt->FEngObject, opt->OrigWidth * opt->GetScaleToPcnt(), opt->OrigHeight * opt->GetScaleToPcnt()); + opt->SetScaleAtStart(scale); + opt->SetAnimComplete(true); + goto next; + } + } else { + FEngSetSize(opt->FEngObject, opt->OrigWidth * opt->GetScaleToPcnt(), opt->OrigHeight * opt->GetScaleToPcnt()); + opt->SetScaleAtStart(scale); + opt->SetAnimComplete(true); + goto next; + } + } + FEngSetSize(opt->FEngObject, opt->OrigWidth * scale, opt->OrigHeight * scale); + bJustScrolled = true; + } +next: + list_width = opt->OrigWidth * scale + list_width; + list_height = opt->OrigHeight * scale + list_height; + if (opt != Options.GetTail()) { + list_width += fIconSpacing; + list_height += fIconSpacing; + } + } +} + +void IconPanel::RemoveAll() { + while (Options.GetHead() != Options.EndOfList()) { + IconOption *node = Options.GetHead(); + node->Remove(); + delete node; + } + iIndexToAdd = 1; +} + +// ============================================================ +// IconScroller +// ============================================================ + +IconScroller::IconScroller(const char *pkg_name, const char *master, const char *fe_button, const char *scroll_region, float width) + : IconPanel(pkg_name, master, fe_button, scroll_region, false) // + , ScrollBar(pkg_name, "ScrollBar", false, false, true) // +{ + HeadBookEnd = nullptr; + TailBookEnd = nullptr; + AlignmentToSelected = static_cast(1); + iNumBookEnds = 4; + fCurFadeTime = 0.0f; + fMaxFadeTime = 9.0f; + bAllowColorAnim = true; + IdleColor = 0xFFFFFFFF; + FadeColor = 0x00FFFFFF; + fWidth = 0.0f; + fHeight = 0.0f; + fXCenter = 0.0f; + fYCenter = 0.0f; + fPulseState = 0.0f; + fCurrentAddPos = 0.0f; + AlignmentToSelected = static_cast(1); + iCurSelectedIndex = 1; + fWidth = width; + bFadingIn = false; + bFadingOut = false; + bInitialized = false; + bDelayUpdate = false; + fIconSpacing = -5.0f; + + FEObject *scroll_obj = FEngFindObject(pkg_name, FEHashUpper(scroll_region)); + if (scroll_obj) { + FEngGetCenter(scroll_obj, fXCenter, fYCenter); + FEngSetInvisible(scroll_obj); + } + AddInitialBookEnds(); + AlignmentToSelected = static_cast(0); + iNumBookEnds = 0; +} + +void IconScroller::Update() { + if (!Options.IsEmpty() && pCurrentNode && !bDelayUpdate) { + if (bJustScrolled) { + bJustScrolled = false; + ScrollBar.Update(1, iIndexToAdd - (iNumBookEnds + 1), iCurSelectedIndex - iNumBookEnds, + iCurSelectedIndex - iNumBookEnds); + reinterpret_cast(AnimateCubicData)->SetValDesired(-pCurrentNode->XPos); + if (-pCurrentNode->XPos != reinterpret_cast(AnimateCubicData)->Val) { + reinterpret_cast(AnimateCubicData)->state = 2; + } + UpdateArrows(); + } + + for (IconOption *opt = Options.GetHead(); opt != Options.EndOfList(); opt = opt->GetNext()) { + PositionOption(opt); + } + + if (bFadingIn) { + fCurFadeTime += 1.0f; + if (fCurFadeTime >= fMaxFadeTime) { + fCurFadeTime = fMaxFadeTime; + bFadingIn = false; + } + } else if (bFadingOut) { + fCurFadeTime -= 1.0f; + if (fCurFadeTime <= 0.0f) { + fCurFadeTime = 0.0f; + } + } + + cPoint::SplineSeek(reinterpret_cast(AnimateCubicData), RealTimeElapsed, 0.0f, 0.0f); + } +} + +void IconScroller::AddInitialBookEnds() { + for (int i = 0; i < iNumBookEnds / 2; i++) { + FEScrollyBookEnd *bookend = new FEScrollyBookEnd(0x43B6310F); + FEImage *img = AddOption(bookend); + if (img) { + FEngSetTextureHash(img, bookend->Item); + } + } + HeadBookEnd = Options.GetTail(); +} + +FEImage *IconScroller::AddOption(IconOption *option) { + char sztemp[32]; + FEngSNPrintf(sztemp, 32, "%s%d", pButtonName, iIndexToAdd); + FEImage *obj = FEngFindImage(pPackageName, FEHashUpper(sztemp)); + if (!obj) { + if (option) { + delete option; + } + } else if (option) { + iIndexToAdd++; + option->SetFEngObject(obj); + option->XPos = fCurrentAddPos; + option->OriginalColor = IdleColor; + float size_w, size_h; + FEngGetSize(obj, option->OrigWidth, option->OrigHeight); + FEngGetSize(obj, size_w, size_h); + fCurrentAddPos += size_w + fIconSpacing; + Options.AddTail(option); + if (!pCurrentNode) { + if (iIndexToAdd > iNumBookEnds + 1) { + pCurrentNode = static_cast(HeadBookEnd->GetNext()); + FEngSetCurrentButton(pPackageName, pCurrentNode->FEngObject->NameHash); + } + } + return obj; + } + return nullptr; +} + +void IconScroller::SetInitialPos(int index) { + TailBookEnd = Options.GetTail(); + for (int i = 0; i < iNumBookEnds / 2; i++) { + FEScrollyBookEnd *option = new(__FILE__, __LINE__) FEScrollyBookEnd(0x43B6310F); + FEImage *img = AddOption(option); + if (img) { + FEngSetTextureHash(img, option->Item); + } + } + TailBookEnd = TailBookEnd->GetNext(); + + if (index > 0) { + index += iNumBookEnds / 2; + } + + IconOption *option = Options.GetNode(index - 1); + if (index == 0 || !option) { + SetSelection(static_cast(HeadBookEnd->GetNext())); + } else { + if (option->Item == 0x43B6310F) { + option = static_cast(TailBookEnd->GetPrev()); + } + SetSelection(option); + } + + if (!bHorizontal) { + reinterpret_cast(AnimateCubicData)->SetValDesired(-pCurrentNode->YPos); + } else { + reinterpret_cast(AnimateCubicData)->SetValDesired(-pCurrentNode->XPos); + } + reinterpret_cast(AnimateCubicData)->Snap(); + + if (!bDelayUpdate) { + for (IconOption *opt = Options.GetHead(); opt != Options.EndOfList(); opt = opt->GetNext()) { + PositionOption(opt); + } + } + + bInitialized = true; +} + +bool IconScroller::SetSelection(IconOption *option) { + if (option->IsGreyOut) { + return false; + } + pCurrentNode->StartScale(0.614f, 0.2f); + pCurrentNode = option; + FEngSetCurrentButton(pPackageName, option->FEngObject->NameHash); + pCurrentNode->StartScale(0.95f, 0.2f); + return true; +} + +void IconScroller::RemoveAll() { + for (IconOption *opt = Options.GetHead(); opt != Options.EndOfList(); opt = opt->GetNext()) { + FEngSetSize(opt->FEngObject, opt->OrigWidth, opt->OrigHeight); + FEngSetTopLeft(opt->FEngObject, 696969.0f, 696969.0f); + } + while (Options.GetHead() != Options.EndOfList()) { + IconOption *node = Options.GetHead(); + node->Remove(); + delete node; + } + iIndexToAdd = 1; + fCurrentAddPos = 0.0f; +} + +int IconScroller::GetOptionIndex(IconOption *to_find) { + if (!to_find) { + return -1; + } + IconOption *node = static_cast(HeadBookEnd->GetNext()); + int i = 1; + while (node != TailBookEnd) { + if (node == to_find) { + return i; + } + i++; + node = node->GetNext(); + } + return -1; +} + +void IconScroller::Scroll(eScrollDir dir) { + if (Options.CountElements() - iNumBookEnds < 1) { + return; + } + IconOption *new_option = pCurrentNode; + if (dir == eSD_PREV) { + if (new_option != static_cast(HeadBookEnd->GetNext())) { + do { + new_option = new_option->GetPrev(); + if (!new_option->IsGreyOut) { + goto done; + } + } while (new_option != static_cast(HeadBookEnd->GetNext())); + } + } else if (dir == eSD_NEXT) { + do { + if (new_option == static_cast(TailBookEnd->GetPrev())) { + goto check; + } + new_option = new_option->GetNext(); + } while (new_option->IsGreyOut); + goto done; + } +check: + if (new_option->IsGreyOut) { + return; + } +done: + if (new_option != pCurrentNode) { + SetSelection(new_option); + bJustScrolled = true; + } +} + +void IconScroller::ScrollWrapped(eScrollDir dir) { + if (Options.CountElements() - iNumBookEnds <= 0) { + return; + } + IconOption *new_option = pCurrentNode; + if (dir == eSD_PREV) { + do { + if (new_option == static_cast(HeadBookEnd->GetNext())) { + new_option = static_cast(TailBookEnd->GetPrev()); + } else { + new_option = new_option->GetPrev(); + } + } while (new_option->IsGreyOut); + } else if (dir == eSD_NEXT) { + do { + if (new_option == static_cast(TailBookEnd->GetPrev())) { + new_option = static_cast(HeadBookEnd->GetNext()); + } else { + new_option = new_option->GetNext(); + } + } while (new_option->IsGreyOut); + } + if (!new_option->IsGreyOut && new_option != pCurrentNode) { + SetSelection(new_option); + bJustScrolled = true; + } +} + +void IconScroller::ClipEdges(IconOption *option, float pos) { + if (pos < fXCenter - fWidth * 0.5f || pos > fXCenter + fWidth * 0.5f) { + FEngSetInvisible(option->FEngObject); + } else { + FEngSetVisible(option->FEngObject); + } +} + +float IconScroller::Scale(float x, float center, float scroll_size, float thumb_size) { + float neg_far_clip = center - scroll_size * 0.5f; + float pos_far_clip = center + scroll_size * 0.5f; + + if (x < neg_far_clip || x > pos_far_clip) { + return 0.0f; + } + if (x >= neg_far_clip && x < center - 1.5f) { + return (x - neg_far_clip) / (scroll_size * 0.5f); + } + if (x <= pos_far_clip && x > center + 1.5f) { + return (pos_far_clip - x) / (scroll_size * 0.5f); + } + return 1.0f; +} + +void IconScroller::PositionOption(IconOption *option) { + if (option) { + float xpos = fXCenter + reinterpret_cast(AnimateCubicData)->Val + option->XPos; + FEngSetSize(option->FEngObject, option->OrigWidth, option->OrigHeight); + float scale = Scale(xpos, fXCenter, fWidth, option->OrigWidth); + + if (fXCenter <= xpos) { + float aligned_pos = 1.0f - scale; + xpos -= option->OrigWidth * aligned_pos * aligned_pos * aligned_pos; + } else { + float aligned_pos = 1.0f - scale; + xpos += option->OrigWidth * aligned_pos * aligned_pos * aligned_pos; + } + + ClipEdges(option, xpos); + FEngSetCenter(option->FEngObject, xpos, fYCenter); + + if (bFadingIn || bFadingOut) { + scale *= fCurFadeTime / fMaxFadeTime; + } + + float aligned_pos = 0.0f; + if (AlignmentToSelected == eSA_MIDDLE) { + aligned_pos = (option->OrigHeight - option->OrigHeight * scale) * 0.5f + FEngGetTopLeftY(option->FEngObject); + } else if (AlignmentToSelected == eSA_TOP) { + aligned_pos = FEngGetTopLeftY(option->FEngObject); + } else if (AlignmentToSelected == eSA_BOTTOM) { + aligned_pos = FEngGetTopLeftY(option->FEngObject) + (option->OrigHeight - option->OrigHeight * scale); + } + + FEngSetSize(option->FEngObject, option->OrigWidth * scale, option->OrigHeight * scale); + FEngSetTopLeftY(option->FEngObject, aligned_pos); + + if (bAllowColorAnim) { + UpdateFade(option, scale); + } + } +} + +void IconScroller::UpdateFade(IconOption *option, float scale) { + FEObject *object; + + if (option != nullptr && (object = option->FEngObject) != nullptr && object->pData != nullptr) { + unsigned int idle_color = IdleColor; + unsigned int fade_color = FadeColor; + float a1 = static_cast(idle_color >> 24); + float r1 = static_cast(idle_color >> 16 & 0xFF); + float g1 = static_cast(idle_color >> 8 & 0xFF); + float b1 = static_cast(idle_color & 0xFF); + float a2 = static_cast(fade_color >> 24); + float r2 = static_cast(fade_color >> 16 & 0xFF); + float g2 = static_cast(fade_color >> 8 & 0xFF); + float b2 = static_cast(fade_color & 0xFF); + unsigned char alpha = static_cast(a1 * scale + a2 * (1.0f - scale)) & 0xFF; + + if (option->IsGreyOut) { + alpha = 0x96; + } else { + alpha = bClamp(alpha, 0, 0xFF); + } + + float inverse_scale = 1.0f - scale; + unsigned char red = static_cast(r1 * scale + r2 * inverse_scale) & 0xFF; + unsigned char green = static_cast(g1 * scale + g2 * inverse_scale) & 0xFF; + unsigned char blue = static_cast(b1 * scale + b2 * inverse_scale) & 0xFF; + red = bClamp(red, 0, 0xFF); + green = bClamp(green, 0, 0xFF); + blue = bClamp(blue, 0, 0xFF); + unsigned int color = alpha * 0x1000000 + red * 0x10000 + green * 0x100 + blue; + FEngSetColor(object, color); + } +} + +void IconScroller::UpdateArrows() { + if (pCurrentNode == Options.GetHead()) { + ScrollBar.SetArrowVisibility(1, false); + } else if (pCurrentNode == Options.GetTail()) { + ScrollBar.SetArrowVisibility(2, false); + } else { + ScrollBar.SetArrowVisibility(1, true); + ScrollBar.SetArrowVisibility(2, true); + } +} + +IconOption *IconScroller::GetHead() { + return static_cast(HeadBookEnd->GetNext()); +} + +bool IconScroller::IsHead(IconOption *option) { + return option == static_cast(HeadBookEnd->GetNext()); +} + +bool IconScroller::IsTail(IconOption *option) { + return option == static_cast(TailBookEnd->GetPrev()); +} + +bool IconScroller::IsEndOfList(IconOption *opt) { + return opt == TailBookEnd || opt == HeadBookEnd; +} + +// ============================================================ +// IconScrollerMenu +// ============================================================ + +IconScrollerMenu::IconScrollerMenu(ScreenConstructorData *sd) + : MenuScreen(sd) // +{ + new (&Options) IconScroller(GetPackageName(), "OPTION_MASTER", "option_", "ICON_SCROLL_REGION", 350.0f); + bWasLeftMouseDown = false; + bFadeInIconsImmediately = true; + pOptionName = nullptr; + pOptionNameShadow = nullptr; + pOptionDesc = nullptr; + PrevButtonMessage = 0; + PrevButtonObj = nullptr; + PrevParam1 = 0; + PrevParam2 = 0; + pOptionName = FEngFindString(GetPackageName(), 0x5E7B09C9); + pOptionNameShadow = FEngFindString(GetPackageName(), 0x0DFB7A2E); + pOptionDesc = FEngFindString(GetPackageName(), 0); +} + +void IconScrollerMenu::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + unsigned long message = msg; + FEObject *object = pobj; + unsigned long previous_param1 = param1; + unsigned long previous_param2 = param2; + + switch (message) { + case 0xC98356BA: + Options.Update(); + return; + case 0x84378BEF: + Options.bFadingIn = false; + Options.fCurFadeTime = Options.fMaxFadeTime; + Options.bFadingOut = true; + return; + case 0x35F8620B: + Options.bAllowColorAnim = true; + return; + case 0xE1FDE1D1: + Options.IconPanel::Act(PrevButtonMessage, PrevButtonObj, PrevParam1, PrevParam2); + return; + case 0x911AB364: + StorePrevNotification(0x911AB364, object, previous_param1, previous_param2); + Options.bReactToInput = false; + FEngSetLastButton(GetPackageName(), 0); + return; + case 0x0C407210: { + if (!Options.bReactToInput) { + return; + } + + IconPanel *panel = &Options; + IconOption *cur_option = Options.pCurrentNode; + if (cur_option->IsGreyOut) { + return; + } + if (object != cur_option->FEngObject) { + return; + } + + const char *pkg_name = GetPackageName(); + unsigned char current_index = 0; + if (cur_option) { + current_index = static_cast(panel->GetOptionIndex(cur_option)); + } + FEngSetLastButton(pkg_name, current_index); + + bool reacts_immediately = false; + if (Options.pCurrentNode) { + reacts_immediately = cur_option->ReactsImmediately(); + } + if (reacts_immediately) { + Options.IconPanel::Act(0x0C407210, object, previous_param1, previous_param2); + return; + } + + StorePrevNotification(0x0C407210, object, previous_param1, previous_param2); + Options.bReactToInput = false; + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), nullptr); + return; + } + case 0x9120409E: + if (!Options.bHorizontal) { + return; + } + if (!Options.bReactToInput) { + return; + } + { + IconPanel *panel = &Options; + if (Options.bWrap) { + panel->ScrollWrapped(eSD_PREV); + } else { + panel->Scroll(eSD_PREV); + } + } + RefreshHeader(); + return; + case 0xB5971BF1: + if (!Options.bHorizontal) { + return; + } + if (!Options.bReactToInput) { + return; + } + { + IconPanel *panel = &Options; + if (Options.bWrap) { + panel->ScrollWrapped(eSD_NEXT); + } else { + panel->Scroll(eSD_NEXT); + } + } + RefreshHeader(); + return; + case 0x72619778: { + if (Options.bHorizontal) { + return; + } + if (!Options.bReactToInput) { + return; + } + IconPanel *panel = &Options; + if (!panel->IsHead(Options.pCurrentNode)) { + if (Options.bWrap) { + panel->ScrollWrapped(eSD_PREV); + } else { + panel->Scroll(eSD_PREV); + } + } + RefreshHeader(); + return; + } + case 0x911C0A4B: { + if (Options.bHorizontal) { + return; + } + if (!Options.bReactToInput) { + return; + } + IconPanel *panel = &Options; + if (Options.bWrap) { + panel->ScrollWrapped(eSD_NEXT); + } else { + panel->Scroll(eSD_NEXT); + } + RefreshHeader(); + return; + } + case 0xC519BFC3: { + IconOption *cur_option = Options.pCurrentNode; + if (!cur_option->IsTutorialAvailable()) { + return; + } + FEngSetScript(GetPackageName(), 0x99344537, 0x16A259, true); + g_pEAXSound->PlayUISoundFX(UISND_COMMON_SELECT); + FEAnyTutorialScreen::LaunchMovie(cur_option->GetTutorialMovieName(), GetPackageName()); + + UserProfile *profile = FEDatabase->GetMultiplayerProfile(0); + CareerSettings *career = profile->GetCareer(); + unsigned int name_hash = cur_option->GetName(); + if (name_hash == 0xA15E4505) { + career->SetHasDoneTollBoothTutorial(); + } else if (name_hash > 0xA15E4505) { + if (name_hash == 0xEE1EDC76) { + career->SetHasDoneSpeedTrapTutorial(); + } + } else if (name_hash == 0x6F547E4C) { + career->SetHasDoneDragTutorial(); + } + return; + } + case 0xC3960EB9: + FEngSetScript(GetPackageName(), 0x99344537, 0x1744B3, true); + return; + default: + return; + } +} + +void IconScrollerMenu::StorePrevNotification(unsigned int msg, FEObject *pobj, unsigned int param1, unsigned int param2) { + PrevButtonMessage = msg; + PrevButtonObj = pobj; + PrevParam1 = param1; + PrevParam2 = param2; +} + +void IconScrollerMenu::RefreshHeader() { + const unsigned long FEObj_TUTORIALGROUP = 0x9C7D33FF; + + FEngSetLanguageHash(pOptionName, Options.GetCurrentName()); + FEngSetLanguageHash(pOptionNameShadow, Options.GetCurrentName()); + FEngSetLanguageHash(pOptionDesc, Options.GetCurrentDesc()); + + if (Options.AtHead()) { + const unsigned long FEObj_ENDPADLEFT = 0xD7118934; + cFEng::Get()->QueuePackageMessage(FEObj_ENDPADLEFT, GetPackageName(), nullptr); + } + + if (Options.AtTail()) { + const unsigned long FEObj_ENDPADRIGHT = 0xB9B17747; + cFEng::Get()->QueuePackageMessage(FEObj_ENDPADRIGHT, GetPackageName(), nullptr); + } + + if (!Options.GetCurrentOption()->IsTutorialAvailable()) { + FEngSetScript(GetPackageName(), FEObj_TUTORIALGROUP, 0x16A259, true); + } else { + FEngSetScript(GetPackageName(), FEObj_TUTORIALGROUP, 0x1CA7C0, true); + } +} + +eMenuSoundTriggers IconScrollerMenu::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if ((msg == 0x48122792 || msg == 0x4ac5e165) && !Options.JustScrolled()) { + return static_cast(-1); + } + return maybe; +} + +void IconScrollerMenu::AddOption(IconOption *option) { + FEImage *img = Options.AddOption(option); + FEngSetTextureHash(img, option->Item); +} + +IconOption *IconPanel::GetOption(int to_find) { + if (to_find < 1) { + return nullptr; + } + int index = 1; + { + IconOption *opt = Options.GetHead(); + while (opt != Options.EndOfList()) { + if (to_find == index) { + return opt; + } + index++; + opt = opt->GetNext(); + } + } + return nullptr; +} + +int IconPanel::GetOptionIndex(IconOption *to_find) { + if (!to_find) { + return -1; + } + int index = 1; + { + IconOption *opt = Options.GetHead(); + while (opt != Options.EndOfList()) { + if (opt == to_find) { + return index; + } + index++; + opt = opt->GetNext(); + } + } + return -1; +} + +IconOption *IconPanel::GetHead() { + return Options.GetHead(); +} + +bool IconPanel::IsHead(IconOption *option) { + return option == Options.GetHead(); +} + +bool IconPanel::IsTail(IconOption *option) { + return option == Options.GetTail(); +} + +bool IconPanel::IsEndOfList(IconOption *opt) { + return opt == Options.EndOfList(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.cpp index e69de29bb..ece643228 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.cpp @@ -0,0 +1,121 @@ +#include "Speed/Indep/Src/Frontend/FEngFont.hpp" + +extern void WideStringToPackedString(char *dest, int destSize, const unsigned short *src); +extern void PackedStringToWideString(unsigned short *wide_string, int wide_string_buffer_size, const char *packed_string); +extern void FEngSNMakeHidden(char *dest, int destSize, unsigned short *src); +extern void FESetString(FEString *text, const short *string); +extern void bStrCpy(unsigned short *dst, const char *src); +extern char *bStrCat(char *dest, const char *str1, const char *str2); + +KeyboardEditString::KeyboardEditString() { + TextInputObject = nullptr; + MaxTextLength = 0; + bMemSet(EditStringUCS2, 0, 0x200); + CursorPosUCS2 = 0; + bMemSet(EditStringPacked, 0, 0x100); + bMemSet(InitialString, 0, 0x100); + mEnabled = false; +} + +void KeyboardEditString::SyncEditIntoPacked() { + WideStringToPackedString(EditStringPacked, 0x100, EditStringUCS2); +} + +char *KeyboardEditString::GetEditedString() { + SyncEditIntoPacked(); + return EditStringPacked; +} + +void KeyboardEditString::EndCapture() { + TextInputObject = nullptr; + bMemSet(EditStringUCS2, 0, 0x200); + mEnabled = false; + bMemSet(EditStringPacked, 0, 0x100); + bMemSet(InitialString, 0, 0x100); +} + +void KeyboardEditString::GetStringForDisplay(char *buffer, int size) { + SyncEditIntoPacked(); + if (ModeFlags == 5) { + FEngSNMakeHidden(buffer, size, EditStringUCS2); + } else { + bStrNCpy(buffer, EditStringPacked, size); + } +} + +void KeyboardEditString::RevertToOriginalString() { + PackedStringToWideString(EditStringUCS2, 0x200, InitialString); + SyncEditIntoPacked(); +} + +void FEngTextInputObject::ReturnPressed() { + if (gKeyboardManager.GetModeFlags() == 6) { + if (bStrLen(gKeyboardManager.GetEditedString()) == 0) { + return; + } + } + cFEngJoyInput::Get()->FlushActions(); + RedrawString(false); + ParentPackage->NotificationMessage(0xda5b8712, DisplayString, 0, 0); + gKeyboardManager.EndCapture(); + ParentPackage->FEngEndTextInput(); +} + +void FEngTextInputObject::EscapePressed() { + gKeyboardManager.RevertToOriginalString(); + RedrawString(false); + ParentPackage->NotificationMessage(0xc9d30688, DisplayString, 0, 0); + gKeyboardManager.EndCapture(); + ParentPackage->FEngEndTextInput(); +} + +FEngTextInputObject::~FEngTextInputObject() { + gKeyboardManager.EndCapture(); +} + +void FEngTextInputObject::Notify(unsigned int msg) { + if (msg == 0xc98356ba) { + RedrawString(true); + } else if (msg == 0x0c407210) { + ReturnPressed(); + } +} + +void FEngTextInputObject::RedrawString(bool pIncludeCursor) { + if (DisplayString) { + char buffer[156]; + unsigned short widestring[156]; + FEngFont *font; + int width; + int flags; + short *fitstring; + + gKeyboardManager.GetStringForDisplay(buffer, 0x9C); + if (pIncludeCursor) { + int blink_time = mBlinkTime; + mBlinkTime = blink_time + 1; + if (blink_time + 1 > 0x59) { + mBlinkTime = 0; + } + if (mBlinkTime < 0x2D) { + bStrCat(buffer, buffer, "|"); + } else { + bStrCat(buffer, buffer, " "); + } + } + + bStrCpy(widestring, buffer); + font = FindFont(DisplayString->Handle); + width = DisplayString->MaxWidth; + flags = DisplayString->Flags; + fitstring = reinterpret_cast(widestring); + + for (; *fitstring != 0; fitstring++) { + if (font->GetLineWidth(fitstring, flags, 0, false) <= static_cast(width)) { + break; + } + } + + FESetString(DisplayString, fitstring); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.hpp index 9f2345f4d..80e93181f 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.hpp @@ -5,6 +5,122 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +struct FEPackage; +struct FEString; + +// total size: 0xC +struct FEngTextInputObject { + FEString *DisplayString; // offset 0x0, size 0x4 + MenuScreen *ParentPackage; // offset 0x4, size 0x4 + int mBlinkTime; // offset 0x8, size 0x4 + static int sCursorBlinkCycleTime; + + FEngTextInputObject(MenuScreen *pkg, FEString *obj, unsigned int mode, const char *start_string, + unsigned int max_text_length); + ~FEngTextInputObject(); + void Notify(unsigned int msg); + void ReturnPressed(); + void EscapePressed(); + void RedrawString(bool pIncludeCursor); + char *GetEditedString(); +}; + +// total size: 0x418 +struct KeyboardEditString { + char InitialString[256]; // offset 0x0, size 0x100 + unsigned short EditStringUCS2[256]; // offset 0x100, size 0x200 + int CursorPosUCS2; // offset 0x300, size 0x4 + char EditStringPacked[256]; // offset 0x304, size 0x100 + unsigned int ModeFlags; // offset 0x404, size 0x4 + int KeysProcessed; // offset 0x408, size 0x4 + int MaxTextLength; // offset 0x40C, size 0x4 + bool mEnabled; // offset 0x410, size 0x1 + FEngTextInputObject *TextInputObject; // offset 0x414, size 0x4 + + bool IsCapturing() { + return mEnabled && TextInputObject != nullptr; + } + + KeyboardEditString(); + void SyncEditIntoPacked(); + char *GetEditedString(); + void EndCapture(); + unsigned int GetModeFlags() { return ModeFlags; } + + void GetStringForDisplay(char *buffer, int size); + void RevertToOriginalString(); +}; + +extern KeyboardEditString gKeyboardManager; + +// total size: 0x360 +struct FEKeyboard : public MenuScreen { + enum MODE { + MODE_ALL_KEYS = 0, + MODE_ALPHANUMERIC = 1, + MODE_ALPHANUMERIC_PASSWORD = 2, + MODE_FILENAME = 3, + MODE_EMAIL = 4, + MODE_PROFILE_ENTRY = 5, + }; + + static FEColor ButtonHighlight; + static FEColor LetterHighlight; + static FEColor ButtonIdle; + static FEColor LetterIdle; + + FEKeyboard(ScreenConstructorData *sd); + ~FEKeyboard() override {} + + void NotificationMessage(unsigned long msg, FEObject *pObject, unsigned long param1, unsigned long param2) override; + + void SetString(char *pStr); + void SetMaxLength(int nLength); + int IsKeyButton(FEObject *pObj); + void AppendSpace(); + void AppendChar(char c); + void Initialize(); + void UpdateVisuals(); + void UpdateStringVisual(); + void MoveCursor(int nDelta); + bool IsSymbol(char character); + bool IsNotOkForEmail(char character); + bool IsEmailSymbol(char character); + void AppendBackspace(); + void ToggleSpecialCharacters(); + int GetCase(); + void AppendLetter(int nButton); + char GetLetterMap(int nButton); + bool IsNumericSymbol(char character); + void UpdateCursorPosition(); + void ToggleCapsLock(); + void ToggleShift(); + void Dispose(bool bFreeMem); + + int mnLetterMapIndex; // offset 0x2C + int mnCursorIndex; // offset 0x30 + int mnMaxLength; // offset 0x34 + bool mbIsFirstKey; // offset 0x38 + bool mbShift; // offset 0x3C + bool mbCaps; // offset 0x40 + bool mbOnSpecialCharacters; // offset 0x44 + FEString *mpInputString; // offset 0x48 + FEObject *mpCursor; // offset 0x4C + FEImage *mpTextBox; // offset 0x50 + FEString *mpKeyName[45]; // offset 0x54 + FEString *mpKeyNameShadow[45]; // offset 0x108 + FEObject *mpKeyButton[45]; // offset 0x1BC + FEObject *mpKeyDisable[45]; // offset 0x270 + char *mString; // offset 0x324 + FEPackage *mThis; // offset 0x328 + unsigned long mnAcceptHash; // offset 0x32C + unsigned long mnDeclineHash; // offset 0x330 + MODE mnMode; // offset 0x334 + int mnWindowStartIdx; // offset 0x338 + FEString *mpCursorTestString; // offset 0x33C + char mDisplayString[31]; // offset 0x340 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.cpp index e69de29bb..a784f3747 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.cpp @@ -0,0 +1,739 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" + +extern EAXSound *g_pEAXSound; +extern FEImage *FEngFindImage(const char *pkg_name, int hash); +extern void FEngGetTopLeft(FEObject *object, float &x, float &y); +extern void FEngGetSize(FEObject *object, float &x, float &y); +extern unsigned long FEHashUpper(const char *string); +extern int FEPrintf(FEString *text, const char *fmt, ...); +extern void FEngSetLanguageHash(FEString *text, unsigned int hash); + +inline float FEngGetSizeX(FEObject *obj) { + float x, y; + FEngGetSize(obj, x, y); + return x; +} + +Scrollerina::Scrollerina(const char *parent_pkg, const char *backing, const char *scrollbar, + bool vert, bool resize, bool wrapped, bool alwaysShowBacking) + : pParentPkg(parent_pkg) // + , iNumSlots(0) // + , iNumData(0) // + , iViewHeadDataIndex(0) // + , SelectedDatum(nullptr) // + , TopDatum(nullptr) // + , SelectedSlot(nullptr) // + , pBacking(nullptr) // + , ScrollBar(parent_pkg, scrollbar, vert, resize, false) // + , vTopLeft(0.0f, 0.0f) // + , vSize(0.0f, 0.0f) // + , bHasScrollBar(true) // + , bViewNeedsSync(false) // + , bWrapped(wrapped) // + , bAlwaysShowBacking(alwaysShowBacking) // + , bVertical(vert) // + , mouseDownMsg(0x406415e3) // + , bInClickToSelectMode(false) + , pScrollRegion(nullptr) +{ + if (!backing) { + bHasScrollBar = false; + } else { + unsigned int hash = FEHashUpper(backing); + pBacking = FEngFindImage(parent_pkg, hash); + FEngGetTopLeft(pBacking, vTopLeft.x, vTopLeft.y); + FEngGetSize(pBacking, vSize.x, vSize.y); + } +} + +void Scrollerina::FindSize() { + bVector2 top_left; + bVector2 size; + ScrollerSlot *slot; + float top; + float bottom; + float left; + float right; + + if (!pBacking) { + slot = Slots.GetHead(); + slot->GetTopLeft(top_left); + slot->GetSize(size); + top = top_left.y; + bottom = top + vSize.y; + left = top_left.x; + right = left + vSize.x; + + while (slot != Slots.EndOfList()) { + slot->GetTopLeft(top_left); + slot->GetSize(size); + left = bMin(left, top_left.x); + top = bMin(top, top_left.y); + bottom = bMax(bottom, top_left.y + size.y); + right = bMax(right, top_left.x + size.x); + slot = slot->GetNext(); + } + + if (pScrollRegion) { + FEngGetTopLeft(pScrollRegion, top_left.x, top_left.y); + FEngGetSize(pScrollRegion, size.x, size.y); + top = bMin(top, top_left.y); + bottom = bMax(bottom, top_left.y + size.y); + left = bMin(left, top_left.x); + right = bMax(right, top_left.x + size.x); + } + + vTopLeft.x = left; + vSize.x = bAbs(right - left); + vSize.y = bAbs(bottom - top); + vTopLeft.y = top; + } +} + +unsigned int Scrollerina::GetNodeIndex(ScrollerDatum* datum) { + ScrollerDatum* node = Data.GetHead(); + unsigned int index = 1; + while (node != Data.EndOfList()) { + if (datum == node) return index; + index++; + node = node->GetNext(); + } + return 0; +} + +unsigned int Scrollerina::GetNodeIndex(ScrollerSlot* slot) { + ScrollerSlot* node = Slots.GetHead(); + unsigned int index = 1; + while (node != Slots.EndOfList()) { + if (slot == node) return index; + index++; + node = node->GetNext(); + } + return 0; +} + +void Scrollerina::AddData(ScrollerDatum* datum) { + Data.AddTail(datum); + iNumData++; + if (!TopDatum) { + iViewHeadDataIndex = 1; + TopDatum = Data.GetHead(); + } + if (!SelectedDatum) { + SelectedDatum = Data.GetHead(); + } +} + +ScrollerDatum* Scrollerina::FindDatumInSlot(ScrollerSlot* to_find) { + ScrollerSlot* slot_node = Slots.GetHead(); + if (slot_node == Slots.EndOfList() || Data.GetHead() == Data.EndOfList() || !to_find) { + return nullptr; + } + ScrollerDatum* datum_node = TopDatum; + while (slot_node != Slots.EndOfList()) { + if (slot_node == to_find) return datum_node; + if (datum_node == Data.EndOfList()) return nullptr; + datum_node = datum_node->GetNext(); + slot_node = slot_node->GetNext(); + } + return nullptr; +} + +void Scrollerina::DrawScrollBar() { + if (bHasScrollBar) { + ScrollBar.Update(iNumSlots, iNumData, iViewHeadDataIndex, GetSelectedNodeIndex()); + } +} + +void Scrollerina::Update(bool print) { + if (print) { + Print(); + } + DrawScrollBar(); +} + +void ScrollerSlot::SetScript(unsigned int script_hash) { + ScrollerSlotNode *node = FEStrings.GetHead(); + while (node != FEStrings.EndOfList()) { + FEngSetScript(node->String, script_hash, true); + node = node->GetNext(); + } + if (pBacking) { + FEngSetScript(pBacking, script_hash, true); + } +} + +void ScrollerSlot::FindSize() { + float top = 0.0f; + float left = 0.0f; + float right = 0.0f; + float bottom = 0.0f; + if (pBacking) { + top = FEngGetTopLeftY(pBacking); + left = FEngGetTopLeftX(pBacking); + right = left + FEngGetSizeX(pBacking); + bottom = top + FEngGetSizeY(pBacking); + } + vTopLeft.x = left; + vSize.x = bAbs(left - right); + vSize.y = bAbs(top - bottom); + vTopLeft.y = top; +} + +void ScrollerSlot::Show() { + if (!FEStrings.IsEmpty()) { + ScrollerSlotNode *node = FEStrings.GetHead(); + while (node != FEStrings.EndOfList()) { + FEngSetVisible(node->String); + node = node->GetNext(); + } + } + FEngSetVisible(pBacking); +} + +void ScrollerSlot::Hide() { + if (!FEStrings.IsEmpty()) { + ScrollerSlotNode *node = FEStrings.GetHead(); + while (node != FEStrings.EndOfList()) { + FEngSetInvisible(node->String); + node = node->GetNext(); + } + } + FEngSetInvisible(pBacking); +} + +void Scrollerina::AddSlot(ScrollerSlot *slot) { + Slots.AddTail(slot); + iNumSlots++; + if (!SelectedSlot) { + SelectedSlot = Slots.GetHead(); + } + slot->FindSize(); +} + +ScrollerSlot *Scrollerina::FindSlotWithDatum(ScrollerDatum *to_find) { + if (Slots.IsEmpty() || Data.IsEmpty()) { + return nullptr; + } + ScrollerSlot *slot = Slots.GetHead(); + ScrollerDatum *datum = TopDatum; + while (slot != Slots.EndOfList()) { + if (datum == to_find) { + return slot; + } + if (datum == Data.EndOfList()) { + return nullptr; + } + datum = datum->GetNext(); + slot = slot->GetNext(); + } + return nullptr; +} + +void Scrollerina::SetDisabledScripts() { + ScrollerSlot *slot = Slots.GetHead(); + ScrollerDatum *datum = TopDatum; + while (slot != Slots.EndOfList()) { + if (datum->IsEnabled()) { + slot->bEnabled = true; + } else { + slot->bEnabled = false; + } + datum = datum->GetNext(); + slot = slot->GetNext(); + } +} + +void Scrollerina::Enable(ScrollerDatum *datum) { + if (!datum) { + return; + } + datum->bEnabled = true; + if (Slots.IsEmpty() || Data.IsEmpty()) { + return; + } + ScrollerSlot *slot = FindSlotWithDatum(datum); + if (slot) { + slot->bEnabled = true; + } +} + +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +extern int FEngSNPrintf(char *dest, int size, const char *fmt, ...); +extern void FEngSetVisible(FEObject *obj); +extern void FEngSetInvisible(FEObject *obj); +extern void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +extern void FEngSetTopLeft(FEObject *object, float x, float y); +extern void FEngSetSize(FEObject *object, float x, float y); + +FEScrollBar::FEScrollBar(const char *parent_pkg, const char *name, bool vert, bool resize, bool arrows_only) { + bVertical = vert; + bResizeHandle = resize; + bArrowsOnly = arrows_only; + bHandleGrabbed = false; + bVisible = false; + vGrabbedPos = bVector2(0.0f, 0.0f); + vCurPos = bVector2(0.0f, 0.0f); + vGrabOffset = bVector2(0.0f, 0.0f); + vBackingPos = bVector2(0.0f, 0.0f); + vBackingSize = bVector2(0.0f, 0.0f); + vHandleMinSize = bVector2(0.0f, 0.0f); + fSegSize = 0.0f; + pBacking = nullptr; + pHandle = nullptr; + pFirstArrow = nullptr; + pSecondArrow = nullptr; + pFirstBackingEnd = nullptr; + pSecondBackingEnd = nullptr; + + if (name) { + char sztemp[32]; + FEngSNPrintf(sztemp, 32, "%s%s", name, "_Backing"); + pBacking = FEngFindObject(parent_pkg, FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 32, "%s%s", name, "_Handle"); + pHandle = FEngFindObject(parent_pkg, FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 32, "%s%s", name, "_Arrow_1"); + pFirstArrow = FEngFindObject(parent_pkg, FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 32, "%s%s", name, "_Arrow_2"); + pSecondArrow = FEngFindObject(parent_pkg, FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 32, "%s%s", name, "_Backing_End_1"); + pFirstBackingEnd = FEngFindObject(parent_pkg, FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 32, "%s%s", name, "_Backing_End_2"); + pSecondBackingEnd = FEngFindObject(parent_pkg, FEHashUpper(sztemp)); + FEngGetTopLeft(pBacking, vBackingPos.x, vBackingPos.y); + FEngGetSize(pBacking, vBackingSize.x, vBackingSize.y); + if (!bResizeHandle) { + FEngGetSize(pHandle, vHandleMinSize.x, vHandleMinSize.y); + } else { + if (!bVertical) { + vHandleMinSize.x = vBackingSize.y; + } else { + vHandleMinSize.x = vBackingSize.x; + } + vHandleMinSize.y = vHandleMinSize.x; + } + } +} + +void FEScrollBar::SetGroupVisible(bool visible) { + if (visible) { + bVisible = true; + SetVisible(pBacking); + SetVisible(pHandle); + SetVisible(pFirstArrow); + SetVisible(pSecondArrow); + } else { + bVisible = false; + SetInvisible(pBacking); + SetInvisible(pHandle); + SetInvisible(pFirstArrow); + SetInvisible(pSecondArrow); + SetInvisible(pFirstBackingEnd); + SetInvisible(pSecondBackingEnd); + } +} + +void FEScrollBar::Update(int num_view_items, int num_list_items, int view_head_index, int selected_item) { + if (selected_item == -1) { + selected_item = view_head_index; + } + if (num_list_items <= num_view_items || num_list_items == 0) { + SetGroupVisible(false); + } else { + SetGroupVisible(true); + SetPosResized(num_view_items, num_list_items, view_head_index); + if (selected_item == 1) { + SetArrow1Dim(true); + } else if (selected_item == num_list_items) { + SetArrow2Dim(true); + } + } +} + +void FEScrollBar::SetArrowVisibility(int arrow_num, bool visible) { + if (visible) { + if (arrow_num == 1) { + SetVisible(pFirstArrow); + } else if (arrow_num == 2) { + SetVisible(pSecondArrow); + } + } else { + if (arrow_num == 1) { + SetInvisible(pFirstArrow); + } else if (arrow_num == 2) { + SetInvisible(pSecondArrow); + } + } +} + +void FEScrollBar::SetVisible(FEObject *obj) { + FEngSetVisible(obj); + FEngSetScript(obj, 0x001CA7C0, true); +} + +void FEScrollBar::SetInvisible(FEObject *obj) { + FEngSetInvisible(obj); + FEngSetScript(obj, 0x0016A259, true); +} + +void FEScrollBar::SetArrow1Dim(bool dim) { + FEngSetScript(pFirstArrow, dim ? 0x9E99 : 0x6EBBFB68, true); +} + +void FEScrollBar::SetArrow2Dim(bool dim) { + FEngSetScript(pSecondArrow, dim ? 0x9E99 : 0x6EBBFB68, true); +} + +inline void FEngSetSizeY(FEObject *obj, float y) { + float x = FEngGetSizeX(obj); + FEngSetSize(obj, x, y); +} + +inline void FEngSetTopLeftY(FEObject *obj, float y) { + float x = FEngGetTopLeftX(obj); + FEngSetTopLeft(obj, x, y); +} + +void FEScrollBar::SetPosResized(int num_view_items, int num_list_items, int view_head_index) { + if (bVertical) { + float barsize = (static_cast(num_view_items) / static_cast(num_list_items)) * vBackingSize.y; + FEngSetSizeY(pHandle, barsize); + float range = static_cast(num_list_items) - static_cast(num_view_items); + float num_segs = (vBackingSize.y - barsize) / bMax(1.0f, range); + fSegSize = num_segs; + float view_dist_to_head = (static_cast(view_head_index) - 1.0f) * num_segs + vBackingPos.y; + vCurPos.y = view_dist_to_head; + if (!bHandleGrabbed) { + FEngSetTopLeftY(pHandle, view_dist_to_head); + } + } else { + float barsize = (static_cast(num_view_items) / static_cast(num_list_items)) * vBackingSize.x; + FEngSetSizeX(pHandle, barsize); + float range = static_cast(num_list_items) - static_cast(num_view_items); + float num_segs = (vBackingSize.x - barsize) / bMax(1.0f, range); + fSegSize = num_segs; + float view_dist_to_head = (static_cast(view_head_index) - 1.0f) * num_segs + vBackingPos.x; + vCurPos.x = view_dist_to_head; + if (!bHandleGrabbed) { + FEngSetTopLeftX(pHandle, view_dist_to_head); + } + } +} + +void Scrollerina::ScrollNext() { + if (bWrapped) { + g_pEAXSound->PlayUISoundFX(static_cast(0)); + ScrollWrapped(eSD_NEXT); + } else { + eMenuSoundTriggers snd = static_cast(0); + if (SelectedDatum == GetLastDatum()) { + snd = static_cast(7); + } + g_pEAXSound->PlayUISoundFX(snd); + Scroll(eSD_NEXT); + } +} + +void Scrollerina::ScrollPrev() { + if (bWrapped) { + g_pEAXSound->PlayUISoundFX(static_cast(0)); + ScrollWrapped(eSD_PREV); + } else { + eMenuSoundTriggers snd = static_cast(0); + if (SelectedDatum == GetFirstDatum()) { + snd = static_cast(7); + } + g_pEAXSound->PlayUISoundFX(snd); + Scroll(eSD_PREV); + } +} + +bool Scrollerina::Scroll(eScrollDir dir) { + bool ret = false; + if (Slots.IsEmpty() || Data.IsEmpty()) { + return false; + } + + if (bViewNeedsSync) { + SyncViewToSelection(); + } else { + ScrollerDatum *new_datum = SelectedDatum; + ScrollerDatum *new_view = TopDatum; + unsigned int new_view_head = iViewHeadDataIndex; + + if (dir == eSD_NEXT) { + do { + if (new_datum == GetLastDatum()) return false; + new_datum = new_datum->GetNext(); + unsigned int idx = GetNodeIndex(new_datum); + if (idx >= new_view_head + iNumSlots) { + new_view = new_view->GetNext(); + new_view_head++; + } + } while (!new_datum->bEnabled); + } else if (dir == eSD_PREV) { + do { + if (new_datum == GetFirstDatum()) return false; + new_datum = new_datum->GetPrev(); + if (new_datum == new_view->GetPrev()) { + new_view_head--; + new_view = new_datum; + } + } while (!new_datum->bEnabled); + } + + if (new_datum != SelectedDatum) { + SelectedDatum = new_datum; + iViewHeadDataIndex = new_view_head; + TopDatum = new_view; + ret = true; + ScrollSelection(dir); + SetDisabledScripts(); + } + } + Update(true); + return ret; +} + +bool Scrollerina::ScrollWrapped(eScrollDir dir) { + bool ret = false; + if (Slots.IsEmpty() || Data.IsEmpty()) { + return false; + } + + if (bViewNeedsSync) { + SyncViewToSelection(); + } else { + ScrollerDatum *new_datum = SelectedDatum; + ScrollerDatum *new_view = TopDatum; + unsigned int new_view_head = iViewHeadDataIndex; + + if (dir == eSD_NEXT) { + do { + if (new_datum == GetLastDatum()) { + new_view = GetFirstDatum(); + new_view_head = 1; + new_datum = new_view; + } else { + new_datum = new_datum->GetNext(); + unsigned int idx = GetNodeIndex(new_datum); + if (idx >= new_view_head + iNumSlots) { + new_view = new_view->GetNext(); + new_view_head++; + } + } + } while (!new_datum->bEnabled); + } else if (dir == eSD_PREV) { + do { + if (new_datum == GetFirstDatum()) { + new_datum = GetLastDatum(); + new_view_head = iNumData - iNumSlots + 1; + new_view = new_datum; + } else { + new_datum = new_datum->GetPrev(); + if (new_datum == new_view->GetPrev()) { + new_view_head--; + new_view = new_datum; + } + } + } while (!new_datum->bEnabled); + } + + if (new_datum != SelectedDatum) { + SelectedDatum = new_datum; + iViewHeadDataIndex = new_view_head; + TopDatum = new_view; + ret = true; + ScrollSelection(dir); + SetDisabledScripts(); + } + } + Update(true); + return ret; +} + +bool Scrollerina::MoveSelected(eScrollDir dir, bool bprint) { + bool ret = false; + if (Slots.IsEmpty() || Data.IsEmpty()) { + return false; + } + + if (bViewNeedsSync) { + SyncViewToSelection(); + } else { + if (dir == eSD_NEXT) { + eMenuSoundTriggers snd = static_cast(0); + if (SelectedDatum == GetLastDatum()) { + snd = static_cast(7); + } + g_pEAXSound->PlayUISoundFX(snd); + ScrollerDatum *nextDatum = SelectedDatum; + if (nextDatum == GetLastDatum()) return false; + ScrollerDatum *removedDatum = nextDatum; + removedDatum->Remove(); + nextDatum = SelectedDatum; + removedDatum->AddAfter(nextDatum); + if (TopDatum == SelectedDatum) { + TopDatum = SelectedDatum->GetPrev(); + } else { + unsigned int idx = GetNodeIndex(SelectedDatum); + if (idx >= iViewHeadDataIndex + iNumSlots) { + iViewHeadDataIndex++; + TopDatum = TopDatum->GetNext(); + } + } + } else if (dir == eSD_PREV) { + eMenuSoundTriggers snd = static_cast(0); + if (SelectedDatum == GetFirstDatum()) { + snd = static_cast(7); + } + g_pEAXSound->PlayUISoundFX(snd); + ScrollerDatum *removedDatum = SelectedDatum; + if (removedDatum == GetFirstDatum()) return false; + removedDatum->Remove(); + ScrollerDatum *nextDatum = SelectedDatum; + removedDatum->AddBefore(nextDatum); + if (TopDatum == SelectedDatum) { + TopDatum = SelectedDatum; + iViewHeadDataIndex--; + } else { + ScrollerDatum *prev = TopDatum->GetPrev(); + if (prev != SelectedDatum) { + TopDatum = prev; + } + } + } + ScrollSelection(dir); + ret = true; + } + Update(bprint); + return ret; +} + +bool Scrollerina::ScrollSelection(eScrollDir dir) { + bool ret = false; + ScrollerSlot *slot = SelectedSlot; + + if (dir == eSD_NEXT) { + if (slot == Slots.GetTail()) { + return false; + } + ScrollerDatum *datum = FindDatumInSlot(slot); + do { + datum = datum->GetNext(); + slot = slot->GetNext(); + if (!slot || slot == Slots.EndOfList()) break; + } while (!datum->bEnabled); + } else if (dir == eSD_PREV) { + if (slot == Slots.GetHead()) { + return false; + } + ScrollerDatum *datum = FindDatumInSlot(slot); + do { + datum = datum->GetPrev(); + slot = slot->GetPrev(); + if (!slot || slot == Slots.GetHead()) break; + } while (!datum->bEnabled); + } + + ScrollerSlot *old_slot = SelectedSlot; + if (slot != old_slot) { + old_slot->SetScript(0x7ab5521a); + SelectedSlot = slot; + slot->SetScript(0x249db7b7); + } + return slot != old_slot; +} + +void Scrollerina::SyncViewToSelection() { + if (Data.IsEmpty() || Slots.IsEmpty()) { + return; + } + if (iNumSlots >= iNumData) { + return; + } + + unsigned int idx = GetNodeIndex(SelectedDatum); + if (idx > iNumData - iNumSlots + 1) { + TopDatum = Data.GetNode(iNumData - iNumSlots); + SelectedSlot = FindSlotWithDatum(SelectedDatum); + } else { + TopDatum = SelectedDatum; + SelectedSlot = Slots.GetHead(); + } + + bViewNeedsSync = false; + SetDisabledScripts(); + if (SelectedSlot) { + SelectedSlot->SetScript(0x249db7b7); + } + CountListIndices(); +} + +void Scrollerina::Print() { + ScrollerDatum *datum = TopDatum; + ScrollerSlot *slot = Slots.GetHead(); + + while (slot != Slots.EndOfList()) { + if (!datum || datum == Data.EndOfList()) { + slot->Hide(); + } else { + slot->Show(); + ScrollerDatumNode *dnode = datum->Strings.GetHead(); + ScrollerSlotNode *snode = slot->FEStrings.GetHead(); + while (snode != slot->FEStrings.EndOfList()) { + if (!dnode->LanguageHash) { + FEPrintf(static_cast(snode->String), "%s", dnode->String); + } else { + FEngSetLanguageHash(static_cast(snode->String), dnode->LanguageHash); + } + dnode = dnode->GetNext(); + snode = snode->GetNext(); + if (dnode == datum->Strings.EndOfList() || snode == slot->FEStrings.EndOfList()) break; + } + datum = datum->GetNext(); + } + slot = slot->GetNext(); + } +} + +void Scrollerina::CountListIndices() { + bool found_view = false; + iNumSlots = 0; + iViewHeadDataIndex = 1; + iNumData = 0; + { + ScrollerSlot *slot = Slots.GetHead(); + while (slot != Slots.EndOfList()) { + iNumSlots++; + slot = slot->GetNext(); + } + } + { + ScrollerDatum *datum = Data.GetHead(); + while (datum != Data.EndOfList()) { + iNumData++; + if (!found_view && datum != TopDatum) { + iViewHeadDataIndex++; + } else { + found_view = true; + } + datum = datum->GetNext(); + } + } +} + +void Scrollerina::SetSelected(ScrollerSlot *slot) { + if (!slot) return; + if (!slot->IsEnabled()) return; + ScrollerDatum *datum = FindDatumInSlot(slot); + if (!datum) return; + UnHighlightSelected(); + SelectedDatum = datum; + SelectedSlot = slot; + HighlightSelected(); + Update(true); + bViewNeedsSync = false; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp index e02e6cd4a..3a873d719 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp @@ -6,12 +6,15 @@ #endif #include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp" #include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" #include "Speed/Indep/bWare/Inc/bMath.hpp" // total size: 0x64 class FEScrollBar { public: + FEScrollBar() {} FEScrollBar(const char *parent_pkg, const char *name, bool vert, bool resize, bool arrows_only); ~FEScrollBar() {} @@ -66,4 +69,104 @@ class FEScrollBar { FEObject *pSecondBackingEnd; // offset 0x60, size 0x4 }; +struct FEImage; + +// total size: 0xC8 +struct Scrollerina { + const char* pParentPkg; // offset 0x0, size 0x4 + bTList Slots; // offset 0x4, size 0x8 + bTList Data; // offset 0xC, size 0x8 + unsigned int iNumSlots; // offset 0x14, size 0x4 + unsigned int iNumData; // offset 0x18, size 0x4 + unsigned int iViewHeadDataIndex; // offset 0x1C, size 0x4 + ScrollerDatum* SelectedDatum; // offset 0x20, size 0x4 + ScrollerDatum* TopDatum; // offset 0x24, size 0x4 + ScrollerSlot* SelectedSlot; // offset 0x28, size 0x4 + FEImage* pBacking; // offset 0x2C, size 0x4 + FEScrollBar ScrollBar; // offset 0x30, size 0x64 + bVector2 vTopLeft; // offset 0x94, size 0x8 + bVector2 vSize; // offset 0x9C, size 0x8 + bool bHasScrollBar; // offset 0xA4, size 0x1 + bool bViewNeedsSync; // offset 0xA8, size 0x1 + bool bWrapped; // offset 0xAC, size 0x1 + bool bAlwaysShowBacking; // offset 0xB0, size 0x1 + bool bVertical; // offset 0xB4, size 0x1 + unsigned int mouseDownMsg; // offset 0xB8, size 0x4 + bool bInClickToSelectMode; // offset 0xBC, size 0x1 + FEObject* pScrollRegion; // offset 0xC0, size 0x4 + + Scrollerina(const char* parent_pkg, const char* backing, const char* scrollbar, + bool vert, bool resize, bool wrapped, bool alwaysShowBacking); + virtual ~Scrollerina() {} + + ScrollerSlot* GetSelectedSlot() { return SelectedSlot; } + ScrollerDatum* GetSelectedDatum() { return SelectedDatum; } + unsigned int GetNumSlots() { return iNumSlots; } + unsigned int GetNumData() { return iNumData; } + ScrollerDatum* GetFirstDatum() { return Data.GetHead(); } + ScrollerDatum* GetLastDatum() { return Data.GetTail(); } + ScrollerDatum* GetTopDatum() { return TopDatum; } + ScrollerSlot* GetFirstSlot(); + ScrollerSlot* GetLastSlot(); + ScrollerSlot* GetSlot(int ordinal_number); + FEScrollBar* GetScrollBarPointer() { return &ScrollBar; } + void SetMouseDownMsg(unsigned int msg) { mouseDownMsg = msg; } + void SetClickToSelectMode(bool flag) { bInClickToSelectMode = flag; } + bool IsAtHead() { return SelectedDatum == Data.GetHead(); } + bool IsAtTail() { return SelectedDatum == Data.GetTail(); } + bool IsWrapped() { return bWrapped; } + bool HasActiveSelection(); + unsigned int GetSelectedNodeIndex() { return GetNodeIndex(GetSelectedDatum()); } + unsigned int GetSelectedSlotIndex(); + void SetSelectedDatum(ScrollerDatum* datum) { SelectedDatum = datum; } + + ScrollerSlot* AddSlot(const char* string_name, const char* backing); + void AddSlot(ScrollerSlot* slot); + ScrollerDatum* AddData(const char* string); + ScrollerDatum* AddData(unsigned int hash); + void AddData(ScrollerDatum* datum); + ScrollerDatum* FindDatum(const char* to_find); + ScrollerDatum* FindDatumInSlot(ScrollerSlot* to_find); + ScrollerSlot* FindSlotWithDatum(ScrollerDatum* to_find); + ScrollerSlot* FindSlot(FEObject* to_find); + void ScrollNext(); + void ScrollPrev(); + void ScrollViewNext() { ScrollView(1); } + void ScrollViewPrev() { ScrollView(-1); } + void MoveNext(); + void MovePrev(); + bool Scroll(eScrollDir dir); + bool ScrollWrapped(eScrollDir dir); + bool ScrollView(int dir); + bool MoveSelected(eScrollDir dir, bool bprint); + bool ScrollSelection(eScrollDir dir); + void SyncViewToSelection(); + void SetDisabledScripts(); + virtual void Print(); + void DrawScrollBar(); + bool Reset(bool update); + void Update(bool print); + void Enable(ScrollerDatum* datum); + void Disable(ScrollerDatum* datum); + void UpdateSlotVisibility(); + void CountListIndices(); + void OrganizeForDataRemoval(ScrollerDatum* to_remove); + unsigned int GetNodeIndex(ScrollerDatum* datum); + unsigned int GetNodeIndex(ScrollerSlot* slot); + bool SetSelectedSlot(unsigned int index); + void SetSelected(ScrollerSlot* slot); + void PageUp(); + void PageDown(); + void DeleteScrollerData(); + ScrollerDatum* RemoveDatum(ScrollerDatum* to_remove); + ScrollerDatum* RemoveDatum(const char* string); + ScrollerDatum* RemoveDatum(int index); + void UnHighlightSelected(); + void HighlightSelected(); + const char* GetActiveSelection(); + void FindSize(); + void SetWrapped(bool wrapped); + void SetSelectedDatum(unsigned int index); +}; + #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feUIWidgetMenu.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feUIWidgetMenu.cpp index e69de29bb..811547174 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feUIWidgetMenu.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feUIWidgetMenu.cpp @@ -0,0 +1,605 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEInputWidget.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" + +extern void FEngSetVisible(FEObject *obj); +extern void FEngSetInvisible(FEObject *obj); +extern void FEngSetScript(FEObject *object, unsigned int script_hash, bool start_at_beginning); +extern FEString *FEngFindString(const char *pkg_name, int name_hash); +extern FEImage *FEngFindImage(const char *pkg_name, int name_hash); +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +extern unsigned long FEHashUpper(const char *str); +extern int FEngSNPrintf(char *dest, int size, const char *fmt, ...); +extern void FEngGetTopLeft(FEObject *object, float &x, float &y); +extern void FEngSetTopLeft(FEObject *object, float x, float y); +extern void FEngGetSize(FEObject *object, float &x, float &y); +extern void FEngSetSize(FEObject *object, float x, float y); +extern char *bStrCat(char *dest, const char *src1, const char *src2); +extern unsigned int FEngHashString(const char *, ...); +extern void FEngSetCurrentButton(const char *pkg_name, unsigned int hash); + +extern char *bStrNCpy(char *dest, const char *src, int n); + +extern Timer RealTimer; +extern Timer KBCreationTimer; +extern float g_KBDelaySeconds; + +UIWidgetMenu::UIWidgetMenu(ScreenConstructorData *sd) + : MenuScreen(sd) // + , pCurrentOption(nullptr) // + , pViewTop(nullptr) // + , pTitleMaster(nullptr) // + , pDataMaster(nullptr) // + , pPrevButtonObj(nullptr) // + , pDone(nullptr) // +{ + ScrollBar.~FEScrollBar(); + new (&ScrollBar) FEScrollBar(GetPackageName(), "scrollbar", true, false, false); + iIndexToAdd = 1; + iLastSelectedIndex = 1; + bScrollWrapped = true; + pTitleName = "OPTION_NAME_"; + pDataName = "OPTION_DATA_"; + pDataImageName = "OPTION_IMAGE_"; + pBackingName = "OPTION_BACKING_"; + pLeftArrowName = "LEFT_ARROW_"; + pRightArrowName = "RIGHT_ARROW_"; + pSliderName = "SLIDER_"; + vWidgetStartPos = bVector2(0.0f, 0.0f); + vLastWidgetPos = bVector2(0.0f, 0.0f); + vWidgetSize = bVector2(0.0f, 0.0f); + vMaxTitleSize = bVector2(175.0f, 24.0f); + vMaxDataSize = bVector2(175.0f, 24.0f); + vDataPos = bVector2(0.0f, 0.0f); + vWidgetSpacing = bVector2(4.0f, 0.0f); + iMaxWidgetsOnScreen = 7; + iPrevButtonMessage = 0; + iPrevParam1 = 0; + iPrevParam2 = 0; + bCurrentOptionSet = false; + bHasScrollBar = true; + bViewNeedsSync = false; + bAllowScroll = true; + pTitleMaster = FEngFindObject(GetPackageName(), 0xA753C46C); + pDataMaster = FEngFindObject(GetPackageName(), 0xC128B184); + pCursor = FEngFindObject(GetPackageName(), 0x06745352); + pDoneText = FEngFindString(GetPackageName(), 0xF16CF3A9); + pDone = FEngFindObject(GetPackageName(), 0xD79B07A0); + if (pTitleMaster && pDataMaster) { + SetInitialPositions(); + mPlaySound = false; + } +} + +void UIWidgetMenu::Setup() { +} + +void UIWidgetMenu::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x35f8620b: + if (!pCurrentOption) return; + if (!pCurrentOption->IsEnabled()) return; + SetOption(pCurrentOption); + return; + case 0xc407210: + case 0x911ab364: + StorePrevNotification(msg, pobj, param1, param2); + case 0x9120409e: + case 0xb5971bf1: + if (!bAllowScroll) return; + if (!pCurrentOption) return; + if (!pCurrentOption->IsEnabled()) return; + pCurrentOption->Act(GetPackageName(), msg); + return; + case 0x72619778: + if (!bAllowScroll) return; + if (bScrollWrapped) { + ScrollWrapped(eSD_PREV); + return; + } + Scroll(eSD_PREV); + return; + case 0x911c0a4b: + if (!bAllowScroll) return; + if (bScrollWrapped) { + ScrollWrapped(eSD_NEXT); + return; + } + Scroll(eSD_NEXT); + return; + case 0x92b703b5: + RefreshWidgets(); + return; + case 0xaf0bbd92: + ClearWidgets(); + Setup(); + return; + case 0x81017864: { + if ((RealTimer - KBCreationTimer).GetSeconds() < g_KBDelaySeconds) return; + FEInputWidget *widge = static_cast(pCurrentOption); + widge->SetInputText(""); + FEngBeginTextInput(widge->GetDataObject()->NameHash, widge->GetEditMode(), widge->GetInputText(), widge->GetTitle(), widge->GetMaxInputLength()); + bAllowScroll = false; + return; + } + case 0xda5b8712: { + KBCreationTimer = RealTimer; + FEInputWidget *widge = static_cast(pCurrentOption); + widge->SetInputText(FEngGetEditedString()); + if (pCurrentOption && pCurrentOption->IsEnabled()) { + pCurrentOption->Act(GetPackageName(), 0xda5b8712); + } + widge->Draw(); + bAllowScroll = true; + return; + } + case 0xc9d30688: + bAllowScroll = true; + return; + case 0x84378bef: + default: + return; + } +} + +eMenuSoundTriggers UIWidgetMenu::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if ((msg == 0x48122792 || msg == 0x4AC5E165) && pCurrentOption && !pCurrentOption->IsEnabled()) { + return static_cast(-1); + } + return maybe; +} + +void UIWidgetMenu::StorePrevNotification(unsigned int msg, FEObject *pobj, unsigned int param1, unsigned int param2) { + iPrevButtonMessage = msg; + pPrevButtonObj = pobj; + iPrevParam1 = param1; + iPrevParam2 = param2; +} + +FEWidget *UIWidgetMenu::GetWidget(unsigned int id) { + if (id == 0) { + return nullptr; + } + return static_cast(Options.GetNode(id - 1)); +} + +unsigned int UIWidgetMenu::GetWidgetIndex(FEWidget *opt) { + unsigned int index = 1; + { + FEWidget *w = Options.GetHead(); + while (w != Options.EndOfList()) { + if (opt == w) { + return index; + } + index++; + w = w->GetNext(); + } + } + return 0; +} + +void UIWidgetMenu::IncrementStartPos() { + float y = vWidgetSize.y + vWidgetSpacing.y + vLastWidgetPos.y; + vLastWidgetPos.y = y; + vDataPos.y = y; +} + +FEString *UIWidgetMenu::GetCurrentFEString(const char *string_name) { + char sztemp[32]; + char sztemp2[32]; + bStrCat(sztemp, string_name, "%d"); + FEngSNPrintf(sztemp2, 0x20, sztemp, iIndexToAdd); + return FEngFindString(GetPackageName(), FEHashUpper(sztemp2)); +} + +FEImage *UIWidgetMenu::GetCurrentFEImage(const char *img_name) { + char sztemp[32]; + char sztemp2[32]; + bStrCat(sztemp, img_name, "%d"); + FEngSNPrintf(sztemp2, 0x20, sztemp, iIndexToAdd); + FEImage *obj = FEngFindImage(GetPackageName(), FEHashUpper(sztemp2)); + if (!obj) { + obj = FEngFindImage(GetPackageName(), FEngHashString("%s0", img_name)); + } + return obj; +} + +FEObject *UIWidgetMenu::GetCurrentFEObject(const char *name) { + char sztemp[32]; + char sztemp2[32]; + bStrCat(sztemp, name, "%d"); + FEngSNPrintf(sztemp2, 0x20, sztemp, iIndexToAdd); + return FEngFindObject(GetPackageName(), FEHashUpper(sztemp2)); +} + +void UIWidgetMenu::RefreshWidgets() { + { + FEWidget *w = Options.GetHead(); + while (w != Options.EndOfList()) { + w->Draw(); + w = w->GetNext(); + } + } +} + +void UIWidgetMenu::SetOption(FEWidget *opt) { + FEWidget *old = pCurrentOption; + if (old != opt && old) { + old->UnsetFocus(); + } + pCurrentOption = opt; + if (opt) { + opt->SetFocus(GetPackageName()); + } + UpdateCursorPos(); +} + +void UIWidgetMenu::SetInitialPositions() { + FEngGetTopLeft(pTitleMaster, vWidgetStartPos.x, vWidgetStartPos.y); + vLastWidgetPos = vWidgetStartPos; + FEngGetTopLeft(pDataMaster, vDataPos.x, vDataPos.y); + FEngGetSize(pTitleMaster, vMaxTitleSize.x, vMaxTitleSize.y); + FEngGetSize(pDataMaster, vMaxDataSize.x, vMaxDataSize.y); + vWidgetSize.y = vMaxTitleSize.y; + vWidgetSize.x = bAbs(vWidgetStartPos.x - (vDataPos.x + vMaxDataSize.x)); +} + +void UIWidgetMenu::ClearWidgets() { + FEWidget *w = Options.GetHead(); + while (w != Options.EndOfList()) { + w->Hide(); + w->UnsetFocus(); + w = w->GetNext(); + } + while (!Options.IsEmpty()) { + FEWidget *head = Options.RemoveHead(); + delete head; + } + pCurrentOption = nullptr; + iIndexToAdd = 1; + bCurrentOptionSet = false; + iLastSelectedIndex = 1; + SetInitialPositions(); +} + +void UIWidgetMenu::UpdateCursorPos() { + if (pCursor) { + if (pCurrentOption) { + unsigned int pos = GetWidgetIndex(pCurrentOption); + pos -= GetWidgetIndex(pViewTop); + if (pos + 1 && pos + 1 <= iMaxWidgetsOnScreen) { + FEngSetScript(pCursor, FEngHashString("POS%d", pos + 1), true); + } else { + FEngSetScript(pCursor, 0x16a259, true); + } + } else { + FEngSetScript(pCursor, 0x16a259, true); + } + } +} + +void UIWidgetMenu::Reset() { + FEWidget *head = Options.GetHead(); + if (head != Options.EndOfList()) { + bViewNeedsSync = false; + pCurrentOption = head; + pViewTop = head; + SetOption(head); + Reposition(); + } +} + +void UIWidgetMenu::Reposition() { + unsigned int index = 1; + unsigned int view_index = GetWidgetIndex(pViewTop); + float pos = vWidgetStartPos.y; + { + FEWidget *w = Options.GetHead(); + while (w != Options.EndOfList()) { + if (index >= view_index && index < view_index + iMaxWidgetsOnScreen) { + w->Show(); + w->SetPosY(pos); + w->Draw(); + w->Position(); + pos += vWidgetSize.y; + } else { + w->SetPosY(6969.0f); + w->Hide(); + } + index++; + w = w->GetNext(); + } + } + UpdateCursorPos(); +} + +void UIWidgetMenu::SyncViewToSelection() { + if (Options.IsEmpty()) { + return; + } + if (!pCurrentOption && !pDone) { + Reset(); + return; + } + if (static_cast< unsigned int >(iIndexToAdd - 1) > iMaxWidgetsOnScreen && + GetWidgetIndex(pCurrentOption) <= static_cast< unsigned int >(iIndexToAdd - iMaxWidgetsOnScreen)) { + pViewTop = pCurrentOption; + } else { + int node_index = iIndexToAdd - iMaxWidgetsOnScreen; + node_index = node_index - 1; + if (node_index < 0) { + node_index = 0; + } + pViewTop = static_cast< FEWidget * >(Options.GetNode(node_index)); + } + Reposition(); + bViewNeedsSync = false; +} + +unsigned int UIWidgetMenu::AddButtonOption(FEButtonWidget *option) { + option->SetTitleObject(GetCurrentFEString(pTitleName)); + option->SetBacking(GetCurrentFEObject(pBackingName)); + option->SetTopLeft(vLastWidgetPos); + option->SetMaxTitleSize(vMaxTitleSize); + Options.AddTail(option); + iIndexToAdd++; + IncrementStartPos(); + if (!option->IsEnabled()) { + option->Disable(); + } + option->Show(); + option->Draw(); + option->Position(); + option->SetWidth(bAbs(vWidgetSize.x)); + option->SetHeight(bAbs(vWidgetSize.y)); + return iIndexToAdd - 1; +} + +unsigned int UIWidgetMenu::AddToggleOption(FEToggleWidget *option, bool use_arrow) { + float img_left; + float img_right; + + option->SetTitleObject(GetCurrentFEString(pTitleName)); + option->SetDataObject(GetCurrentFEString(pDataName)); + option->SetBacking(GetCurrentFEObject(pBackingName)); + option->SetTopLeft(vLastWidgetPos); + option->SetMaxTitleSize(vMaxTitleSize); + option->SetMaxDataSize(vMaxDataSize); + option->SetDataPos(vDataPos); + option->SetLeftImage(GetCurrentFEImage(pLeftArrowName)); + option->SetRightImage(GetCurrentFEImage(pRightArrowName)); + Options.AddTail(option); + iIndexToAdd++; + IncrementStartPos(); + if (!option->IsEnabled()) { + option->Disable(); + } + option->Show(); + option->Draw(); + option->Position(); + img_left = FEngGetTopLeftX(option->GetRightImage()) + bAbs(FEngGetSizeX(option->GetRightImage())); + option->SetWidth(bAbs(option->GetTopLeftX() - img_left)); + img_right = bAbs(FEngGetSizeY(option->GetRightImage())); + option->SetHeight(img_right); + return iIndexToAdd - 1; +} + +unsigned int UIWidgetMenu::AddSliderOption(FESliderWidget *option, bool use_arrow) { + char sztemp[64]; + float img_left; + float img_right; + + FEngSNPrintf(sztemp, 0x40, "%s%d", pSliderName, iIndexToAdd); + option->SetTitleObject(GetCurrentFEString(pTitleName)); + option->InitSliderObjects(GetPackageName(), sztemp); + option->SetInitialValues(); + option->SetTopLeft(vLastWidgetPos); + option->SetMaxTitleSize(vMaxTitleSize); + option->SetMaxDataSize(vMaxDataSize); + option->SetDataPos(vDataPos); + option->SetLeftImage(GetCurrentFEImage(pLeftArrowName)); + option->SetRightImage(GetCurrentFEImage(pRightArrowName)); + Options.AddTail(option); + iIndexToAdd++; + IncrementStartPos(); + if (!option->IsEnabled()) { + option->Disable(); + } + option->Show(); + option->Draw(); + option->Position(); + img_left = FEngGetTopLeftX(option->GetRightImage()) + bAbs(FEngGetSizeX(option->GetRightImage())); + option->SetWidth(bAbs(option->GetTopLeftX() - img_left)); + img_right = bAbs(FEngGetSizeY(option->GetTitleObject())); + option->SetHeight(img_right); + return iIndexToAdd - 1; +} + +void UIWidgetMenu::SetInitialOption(int number) { + if (Options.IsEmpty()) { + if (bHasScrollBar) { + ScrollBar.Update(iMaxWidgetsOnScreen, iIndexToAdd - 1, + GetWidgetIndex(pViewTop), GetWidgetIndex(pCurrentOption)); + } + return; + } + if (bCurrentOptionSet) goto update_scrollbar; + + bool need_first_avail; + need_first_avail = false; + if (number != 0) { + FEWidget *w = GetWidget(number); + if (!w || w == Options.EndOfList() || !w->IsEnabled()) { + need_first_avail = true; + } else { + SetOption(w); + iLastSelectedIndex = number; + bCurrentOptionSet = true; + } + } else { + if (pDone) { + if (pCurrentOption) { + pCurrentOption->UnsetFocus(); + pCurrentOption = nullptr; + } + FEngSetCurrentButton(GetPackageName(), pDone->NameHash); + } else { + need_first_avail = true; + } + } + if (need_first_avail) { + FEWidget *w = Options.GetHead(); + iLastSelectedIndex = 1; + while (w) { + if (w->IsEnabled() || w == Options.EndOfList()) { + SetOption(w); + bCurrentOptionSet = true; + break; + } + w = w->GetNext(); + iLastSelectedIndex++; + } + } + SyncViewToSelection(); +update_scrollbar: + if (bHasScrollBar) { + ScrollBar.Update(iMaxWidgetsOnScreen, iIndexToAdd - 1, + GetWidgetIndex(pViewTop), GetWidgetIndex(pCurrentOption)); + } +} + +void UIWidgetMenu::Scroll(eScrollDir dir) { + if (Options.IsEmpty()) return; + + if (bViewNeedsSync) { + SyncViewToSelection(); + return; + } + + FEWidget *new_option = pCurrentOption; + FEWidget *new_view = pViewTop; + + if (dir == eSD_NEXT) { + if (new_option && new_option == Options.GetTail() && pDone) { + new_option = nullptr; + FEngSetCurrentButton(GetPackageName(), pDone->NameHash); + } else { + if (new_option != Options.GetTail()) { + do { + new_option = new_option->GetNext(); + iLastSelectedIndex = bMin(static_cast(iIndexToAdd - 1), + static_cast(iLastSelectedIndex + 1)); + } while (new_option && !new_option->IsEnabled() && + new_option != Options.GetTail()); + + unsigned int sel_idx = GetWidgetIndex(new_option); + int view_idx = GetWidgetIndex(pViewTop); + if (sel_idx >= static_cast(view_idx + iMaxWidgetsOnScreen)) { + new_view = pViewTop->GetNext(); + } + } + } + } else { + if (!new_option) { + new_option = Options.GetTail(); + } else { + if (new_option != Options.GetHead()) { + do { + new_option = new_option->GetPrev(); + iLastSelectedIndex = bMax(1, static_cast(iLastSelectedIndex - 1)); + } while (new_option && !new_option->IsEnabled() && + new_option != Options.GetHead()); + } + if (new_option == pViewTop->GetPrev()) { + new_view = new_option; + } + } + } + + if (pViewTop != new_view) { + pViewTop = new_view; + Reposition(); + } + if (pCurrentOption != new_option) { + SetOption(new_option); + if (bHasScrollBar && pCurrentOption) { + ScrollBar.Update(iMaxWidgetsOnScreen, iIndexToAdd - 1, + GetWidgetIndex(pViewTop), GetWidgetIndex(pCurrentOption)); + } + } +} + +void UIWidgetMenu::ScrollWrapped(eScrollDir dir) { + if (Options.IsEmpty()) return; + + if (bViewNeedsSync) { + SyncViewToSelection(); + return; + } + + FEWidget *new_option = pCurrentOption; + FEWidget *new_view = pViewTop; + + if (dir == eSD_NEXT) { + do { + if (!new_option || + (new_option == Options.GetTail() && !pDone)) { + new_view = Options.GetHead(); + new_option = new_view; + } else if (new_option == Options.GetTail() && pDone) { + new_option = nullptr; + FEngSetCurrentButton(GetPackageName(), pDone->NameHash); + } else { + unsigned int idx = iLastSelectedIndex + 1; + new_option = new_option->GetNext(); + iLastSelectedIndex = idx; + if (idx > iIndexToAdd - 1) { + iLastSelectedIndex = 1; + } + } + } while (new_option && !new_option->IsEnabled()); + + unsigned int sel_idx = GetWidgetIndex(new_option); + int view_idx = GetWidgetIndex(pViewTop); + if (sel_idx >= static_cast(view_idx + iMaxWidgetsOnScreen)) { + new_view = pViewTop->GetNext(); + } + } else { + do { + if (!new_option || + (new_option == Options.GetHead() && !pDone)) { + new_option = Options.GetTail(); + int idx = bMax(0, static_cast(iIndexToAdd - iMaxWidgetsOnScreen) - 1); + new_view = Options.GetNode(idx); + } else if (new_option == Options.GetHead() && pDone) { + new_option = nullptr; + FEngSetCurrentButton(GetPackageName(), pDone->NameHash); + } else { + new_option = new_option->GetPrev(); + int idx = iLastSelectedIndex - 1; + iLastSelectedIndex = idx; + if (idx == 0) { + iLastSelectedIndex = iIndexToAdd - 1; + } + } + } while (new_option && !new_option->IsEnabled()); + + if (iIndexToAdd - 1 > iMaxWidgetsOnScreen && + new_option == pViewTop->GetPrev()) { + new_view = new_option; + } + } + + if (pViewTop != new_view) { + pViewTop = new_view; + Reposition(); + } + if (pCurrentOption != new_option) { + SetOption(new_option); + if (bHasScrollBar && pCurrentOption) { + ScrollBar.Update(iMaxWidgetsOnScreen, iIndexToAdd - 1, + GetWidgetIndex(pViewTop), GetWidgetIndex(pCurrentOption)); + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.cpp index e69de29bb..d5a9e9a98 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.cpp @@ -0,0 +1,685 @@ +#include "feWidget.hpp" +#include "CTextScroller.hpp" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/Frontend/FEngFont.hpp" + +struct FEObject; +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetScript(FEObject* object, unsigned int script_hash, bool start_at_beginning); +void FEngGetCenter(FEObject* object, float& x, float& y); +void FEngGetSize(FEObject* object, float& x, float& y); +void FEngSetCenter(FEObject* object, float x, float y); +void FEngGetTopLeft(FEObject* object, float& x, float& y); +void FEngSetTopLeft(FEObject* object, float x, float y); +void FEngSetCurrentButton(const char* pkg_name, unsigned int hash); +inline void FEngSetCurrentButton(const char* pkg_name, FEObject* obj) { + FEngSetCurrentButton(pkg_name, obj->NameHash); +} + +FEWidget::FEWidget(FEObject* backing, bool enabled, bool hidden) + : vTopLeft(0.0f, 0.0f) // + , vSize(0.0f, 0.0f) // + , vBackingOffset(0.0f, 0.0f) // + , pBacking(backing) // + , bEnabled(enabled) // + , bHidden(hidden) // + , bMovedLastUpdate(false) // +{} + +void FEWidget::Act(const char* parent_pkg, unsigned int data) {} +void FEWidget::CheckMouse(const char* parent_pkg, const float mouse_x, const float mouse_y) {} +void FEWidget::Draw() {} +void FEWidget::Position() {} + +void FEWidget::Show() { + if (pBacking) { + FEngSetVisible(pBacking); + } +} + +void FEWidget::Hide() { + if (pBacking) { + FEngSetInvisible(pBacking); + } +} + +void FEWidget::SetFocus(const char* parent_pkg) {} +void FEWidget::UnsetFocus() {} + +FEButtonWidget::FEButtonWidget(bool enabled) + : FEWidget(nullptr, enabled, false) // + , pTitle(nullptr) // + , vMaxTitleSize(0.0f, 0.0f) // +{} + +void FEButtonWidget::Position() { + unsigned int format = pTitle->Format; + if ((format & 1) != 0) { + FEngSetCenter(reinterpret_cast(pTitle), vTopLeft.x, vTopLeft.y + vMaxTitleSize.y * 0.5f); + } else if ((format & 2) != 0) { + FEngSetCenter(reinterpret_cast(pTitle), vTopLeft.x + vMaxTitleSize.x, + vTopLeft.y + vMaxTitleSize.y * 0.5f); + } else { + FEngSetCenter(reinterpret_cast(pTitle), vTopLeft.x + vMaxTitleSize.x * 0.5f, + vTopLeft.y + vMaxTitleSize.y * 0.5f); + } + + if (pBacking) { + FEngSetTopLeft(pBacking, vTopLeft.x - vBackingOffset.x, vTopLeft.y - vBackingOffset.y); + } +} + +void FEButtonWidget::Show() { + FEngSetVisible(reinterpret_cast(pTitle)); + if (GetBacking()) { + FEngSetVisible(GetBacking()); + } +} + +void FEButtonWidget::Hide() { + FEngSetInvisible(reinterpret_cast(pTitle)); + if (GetBacking()) { + FEngSetInvisible(GetBacking()); + } +} + +void FEButtonWidget::CheckMouse(const char* parent_pkg, float mouse_x, float mouse_y) {} + +void FEButtonWidget::SetFocus(const char* parent_pkg) { + FEngSetCurrentButton(parent_pkg, GetTitleObject()); + FEngSetScript(GetTitleObject(), 0x249DB7B7, true); + if (GetBacking()) { + FEngSetScript(GetBacking(), 0x249DB7B7, true); + } +} + +void FEButtonWidget::UnsetFocus() { + FEngSetScript(GetTitleObject(), 0x7AB5521A, true); + if (GetBacking()) { + FEngSetScript(GetBacking(), 0x7AB5521A, true); + } +} + +void FEButtonWidget::SetPos(bVector2& pos) { + FEWidget::SetPosX(pos.x); + FEWidget::SetPosY(pos.y); +} + +FEStatWidget::FEStatWidget(bool enabled) + : FEWidget(nullptr, enabled, false) // + , pTitle(nullptr) // + , pData(nullptr) // + , vMaxTitleSize(0.0f, 0.0f) // + , vMaxDataSize(0.0f, 0.0f) // + , vDataPos(0.0f, 0.0f) // +{} + +void FEStatWidget::Act(const char* parent_pkg, unsigned int data) {} +void FEStatWidget::Draw() {} +void FEStatWidget::CheckMouse(const char* parent_pkg, const float mouse_x, const float mouse_y) {} + +void FEStatWidget::Position() { + float title_y = vTopLeft.y + vMaxTitleSize.y * 0.5f; + if (pTitle) { + unsigned int format = pTitle->Format; + float title_x; + if ((format & 1) != 0) { + title_x = vTopLeft.x; + } else if ((format & 2) != 0) { + title_x = vTopLeft.x + vMaxTitleSize.x; + } else { + title_x = vTopLeft.x + vMaxTitleSize.x * 0.5f; + } + FEngSetCenter(reinterpret_cast(pTitle), title_x, title_y); + } + if (pData) { + FEObject *data = reinterpret_cast(pData); + unsigned int format = pData->Format; + if ((format & 1) != 0) { + FEngSetCenter(data, vDataPos.x + vMaxDataSize.x * 0.5f, title_y); + } else if ((format & 2) != 0) { + float center_x, center_y; + FEngGetCenter(data, center_x, center_y); + FEngSetCenter(data, center_x, title_y); + + FEVector3 pos = pData->GetObjData()->Pos; + pos.x = vDataPos.x + vMaxDataSize.x; + data->SetPosition(pos, false); + } else { + FEngSetCenter(data, vDataPos.x, title_y); + } + } + if (pBacking) { + FEngSetTopLeft(pBacking, vTopLeft.x - vBackingOffset.x, vTopLeft.y - vBackingOffset.y); + } +} + +void FEStatWidget::Show() { + FEngSetVisible(reinterpret_cast(pTitle)); + FEngSetVisible(reinterpret_cast(pData)); + if (GetBacking()) { + FEngSetVisible(GetBacking()); + } +} + +void FEStatWidget::Hide() { + FEngSetInvisible(reinterpret_cast(pTitle)); + FEngSetInvisible(reinterpret_cast(pData)); + if (GetBacking()) { + FEngSetInvisible(GetBacking()); + } +} + +void FEStatWidget::SetFocus(const char* parent_pkg) {} + +void FEStatWidget::UnsetFocus() {} + +void FEStatWidget::SetPos(bVector2& pos) { + SetPosX(pos.x); + SetPosY(pos.y); +} + +void FEStatWidget::SetPosX(float x) { + float old_x = GetTopLeftX(); + SetTopLeftX(x); + vDataPos.x = x + (vDataPos.x - old_x); + if (pBacking) { + float bx, by; + FEngGetTopLeft(pBacking, bx, by); + FEngSetTopLeft(pBacking, x - GetBackingOffsetX(), by); + } +} + +void FEStatWidget::SetPosY(float y) { + float old_y = GetTopLeftY(); + SetTopLeftY(y); + vDataPos.y = y + (vDataPos.y - old_y); + if (pBacking) { + float bx, by; + FEngGetTopLeft(pBacking, bx, by); + FEngSetTopLeft(pBacking, bx, y - GetBackingOffsetY()); + } +} + +FEToggleWidget::FEToggleWidget(bool enabled) + : FEStatWidget(enabled) // + , pLeftImage(nullptr) // + , pRightImage(nullptr) // + , EnableScript(0x7AB5521A) // + , DisableScript(0x36819D93) // +{} + +FEToggleWidget::~FEToggleWidget() {} + +void FEToggleWidget::CheckMouse(const char* parent_pkg, const float mouse_x, const float mouse_y) {} +void FEToggleWidget::BlinkArrows(unsigned int data) {} + +void FEToggleWidget::Enable() { + SetDisableScript(FEHashUpper("NORMAL")); + FEWidget::Enable(); + SetScript(EnableScript); +} + +void FEToggleWidget::Disable() { + SetDisableScript(FEHashUpper("GREY")); + FEWidget::Disable(); + SetScript(DisableScript); +} + +void FEToggleWidget::SetScript(unsigned int script) { + FEngSetScript(GetTitleObject(), script, true); + FEngSetScript(GetDataObject(), script, true); + FEngSetScript(GetLeftImage(), script, true); + FEngSetScript(GetRightImage(), script, true); + if (GetBacking()) { + FEngSetScript(GetBacking(), script, true); + } +} + +void FEToggleWidget::Show() { + FEngSetVisible(GetTitleObject()); + FEngSetVisible(GetDataObject()); + FEngSetVisible(GetLeftImage()); + FEngSetVisible(GetRightImage()); + if (GetBacking()) { + FEngSetVisible(GetBacking()); + } +} + +void FEToggleWidget::Hide() { + FEngSetInvisible(GetTitleObject()); + FEngSetInvisible(GetDataObject()); + FEngSetInvisible(GetLeftImage()); + FEngSetInvisible(GetRightImage()); + if (GetBacking()) { + FEngSetInvisible(GetBacking()); + } +} + +void FEToggleWidget::SetFocus(const char* parent_pkg) { + FEngSetCurrentButton(parent_pkg, GetTitleObject()); + SetScript(0x249DB7B7); +} + +void FEToggleWidget::UnsetFocus() { + SetScript(0x7AB5521A); +} + +void FEToggleWidget::Position() { + FEStatWidget::Position(); + float left_width, left_height; + FEngGetSize(reinterpret_cast(pLeftImage), left_width, left_height); + float right_width, right_height; + FEngGetSize(reinterpret_cast(pRightImage), right_width, right_height); + FEngSetCenter(reinterpret_cast(pLeftImage), vDataPos.x, vDataPos.y + vMaxTitleSize.y * 0.5f); + FEngSetCenter(reinterpret_cast(pRightImage), vDataPos.x + vMaxDataSize.x, + vDataPos.y + vMaxTitleSize.y * 0.5f); +} + +FESliderWidget::FESliderWidget(bool enabled) + : FEToggleWidget(enabled) // + , Slider() // + , fVertOffset(9.5f) // +{} + +FESliderWidget::~FESliderWidget() {} + +void FESliderWidget::Position() { + unsigned int format = pTitle->Format; + if ((format & 1) != 0) { + FEngSetCenter(reinterpret_cast(pTitle), vTopLeft.x, vTopLeft.y + vMaxTitleSize.y * 0.5f); + } else if ((format & 2) != 0) { + FEngSetCenter(reinterpret_cast(pTitle), vTopLeft.x + vMaxTitleSize.x, + vTopLeft.y + vMaxTitleSize.y * 0.5f); + } else { + FEngSetCenter(reinterpret_cast(pTitle), vTopLeft.x + vMaxTitleSize.x * 0.5f, + vTopLeft.y + vMaxTitleSize.y * 0.5f); + } + + float slider_center_x = vDataPos.x + vMaxDataSize.x * 0.5f; + float slider_width, slider_height; + FEngGetSize(*reinterpret_cast(&Slider), slider_width, slider_height); + Slider.SetPos(slider_center_x - slider_width * 0.5f, vDataPos.y + fVertOffset); + Slider.Draw(); + + float left_width, left_height; + FEngGetSize(reinterpret_cast(pLeftImage), left_width, left_height); + float right_width, right_height; + FEngGetSize(reinterpret_cast(pRightImage), right_width, right_height); + FEngSetCenter(reinterpret_cast(pLeftImage), vDataPos.x, vDataPos.y + vMaxTitleSize.y * 0.5f); + FEngSetCenter(reinterpret_cast(pRightImage), vDataPos.x + vMaxDataSize.x, + vDataPos.y + vMaxTitleSize.y * 0.5f); + if (pBacking != nullptr) { + FEngSetTopLeft(pBacking, vTopLeft.x - vBackingOffset.x, vTopLeft.y - vBackingOffset.y); + } +} + +void FESliderWidget::Show() { + FEngSetVisible(reinterpret_cast(GetTitleObject())); + FEngSetVisible(reinterpret_cast(GetLeftImage())); + FEngSetVisible(reinterpret_cast(GetRightImage())); + ToggleSlider(true); + if (GetBacking()) { + FEngSetVisible(GetBacking()); + } +} + +void FESliderWidget::Hide() { + FEngSetInvisible(reinterpret_cast(GetTitleObject())); + FEngSetInvisible(reinterpret_cast(GetLeftImage())); + FEngSetInvisible(reinterpret_cast(GetRightImage())); + ToggleSlider(false); + if (GetBacking()) { + FEngSetInvisible(GetBacking()); + } +} + +void FESliderWidget::Disable() { + FEToggleWidget::Disable(); +} + +void FESliderWidget::SetFocus(const char* parent_pkg) { + FEngSetCurrentButton(parent_pkg, GetTitleObject()); + FEngSetScript(GetTitleObject(), 0x249DB7B7, true); + Slider.Highlight(); + if (GetBacking()) { + FEngSetScript(GetBacking(), 0x249DB7B7, true); + } +} + +void FESliderWidget::UnsetFocus() { + FEngSetScript(GetTitleObject(), 0x7AB5521A, true); + Slider.UnHighlight(); + if (GetBacking()) { + FEngSetScript(GetBacking(), 0x7AB5521A, true); + } +} + +void FESliderWidget::UpdateSlider(unsigned int msg) { + if (Slider.Update(msg)) { + BlinkArrows(msg); + bMovedLastUpdate = true; + } else { + bMovedLastUpdate = false; + } +} + +void FESliderWidget::Enable() { + FEWidget::Enable(); +} + +short *CTextScroller::FindEND(short *pText) { + while (*pText != 0) { + pText++; + } + return pText; +} + +extern char *bStrNCpy(char *dst, const char *src, int n); + +CTextScroller::CTextScroller() { + m_TopLine = 0; + m_ScrollDownMsg = 0x911C0A4B; + m_ScrollUpMsg = 0x72619778; + m_pOwner = nullptr; + m_pFont = nullptr; + m_pScrollBar = nullptr; + m_NumAddedLines = 0; + m_pLines = nullptr; + m_pRawDataBlock = nullptr; +} + +CTextScroller::~CTextScroller() { + if (m_pRawDataBlock) { + delete[] m_pRawDataBlock; + } +} + +void CTextScroller::Initialise(MenuScreen *pOwner, int ViewWidth, int ViewLines, char *pTextDisplayNameTempl, FEngFont *pFont) { + m_pOwner = pOwner; + m_ViewWidth = ViewWidth; + m_ViewVisibleLines = ViewLines; + bStrNCpy(m_TextBoxNameTemplate, pTextDisplayNameTempl, 31); + m_pFont = pFont; +} + +void CTextScroller::Scroll(int Amount) { + if (m_NumAddedLines <= m_ViewVisibleLines) { + return; + } + int top = m_TopLine + Amount; + if (top < 0) { + top = 0; + } else { + int maxTop = m_NumAddedLines - m_ViewVisibleLines; + if (top + m_ViewVisibleLines > m_NumAddedLines) { + top = maxTop; + } + } + m_TopLine = top; + Display(top); +} + +bool CTextScroller::HandleNotificationMessage(unsigned int Msg) { + if (Msg == m_ScrollUpMsg) { + Scroll(-1); + UpdateScrollBar(); + return true; + } + if (Msg != m_ScrollDownMsg) { + return false; + } + Scroll(1); + UpdateScrollBar(); + return true; +} + +short *CTextScroller::FindCR(short *pText) { + short c = *pText; + short *result = nullptr; + if (c == 0) { + return result; + } + do { + bool found = false; + if (c == '\n' || c == '^') { + found = true; + } + if (found) { + result = pText; + } + pText++; + c = *pText; + if (c == 0) break; + if (result) { + return result; + } + } while (true); + return result; +} + +void CTextScroller::WordWrapCountLinesAndChars(short *pTextStart, short *pTextEnd, int &NumLines, int &NumChars) { + NumChars = WordWrapAddLines(pTextStart, pTextEnd, true, nullptr); +} + +int CTextScroller::WordWrapAddLines(short *pTextStart, short *pTextEnd, bool bCountOnly, int *pNumCharsOut) { + int NumLines = 0; + + if (pNumCharsOut) { + *pNumCharsOut = 0; + } + + for (; pTextStart < pTextEnd; NumLines++) { + int StringLength = (reinterpret_cast(pTextEnd) - reinterpret_cast(pTextStart)) >> 1; + int StringSize = 0; + float TextWidth = 0.0f; + short *pChar = pTextStart; + short PrevChar = 0; + bool bStringSizeOverflow = false; + + while (TextWidth < static_cast(m_ViewWidth - 16) && pChar < pTextEnd) { + bStringSizeOverflow = false; + if (IsNewlineChar(*pChar)) { + bStringSizeOverflow = true; + } + if (bStringSizeOverflow) { + break; + } + + if (FEngFont::IsJoyEventTexture(pChar, 0)) { + const short *pNewChar; + + PrevChar = 0; + TextWidth += m_pFont->GetJoyEventTextureWidth(pChar); + pNewChar = m_pFont->SkipJoyEventTexture(pChar, 0); + StringSize += pNewChar - pChar; + pChar = const_cast(pNewChar); + } else { + TextWidth += m_pFont->GetCharacterWidth(*pChar, PrevChar, 0); + StringSize++; + PrevChar = *++pChar; + } + } + + if (StringSize < StringLength) { + int WordBreak = StringSize - 1; + + while (WordBreak > 0 && pTextStart[WordBreak] != ' ') { + WordBreak--; + } + + if (WordBreak > 0) { + StringSize = WordBreak + 1; + } + + if (!bCountOnly) { + AddLine(pTextStart, StringSize); + } + + if (pNumCharsOut) { + *pNumCharsOut += 1 + StringSize; + } + + pTextStart += StringSize; + } else { + if (!bCountOnly) { + AddLine(pTextStart, StringSize); + } + + pTextStart = pTextEnd; + + if (pNumCharsOut) { + *pNumCharsOut += 1 + StringSize; + } + } + } + + return NumLines; +} + +extern const char *GetTranslatedString(unsigned int hash); +extern int bStrLen(const char *str); +extern void PackedStringToWideString(short *dst, int dstSize, const char *src); + +void CTextScroller::SetTextHash(unsigned int hash) { + const char *str = GetTranslatedString(hash); + int len = bStrLen(str); + if (len + 1 > 0) { + int size = (len + 1) * 2; + short *wideStr = new short[size]; + PackedStringToWideString(wideStr, size, str); + SetText(wideStr); + delete[] wideStr; + } +} + +extern unsigned long FEHashUpper(const char *str); +extern FEString *FEngFindString(const char *pkg, int hash); +extern void FESetString(FEString *str, short *text); +extern int FEngSNPrintf(char *dest, int size, const char *fmt, ...); + +void CTextScroller::Display(int topLine) { + if (!m_pOwner || m_ViewVisibleLines <= 0) { + return; + } + for (int i = 0; i < m_ViewVisibleLines; i++) { + short emptyStr[1]; + emptyStr[0] = 0; + char szName[32]; + FEngSNPrintf(szName, 32, m_TextBoxNameTemplate, i); + FEString *feStr = FEngFindString(m_pOwner->GetPackageName(), FEHashUpper(szName)); + short *text; + if (topLine < m_NumAddedLines) { + text = m_pLines[topLine]; + } else { + text = emptyStr; + } + FESetString(feStr, text); + reinterpret_cast(feStr)->Flags |= 0x2000000; + topLine++; + } +} + +extern void bMemCpy(void *dst, const void *src, unsigned int size); + +void CTextScroller::AddLine(short *pText, int numChars) { + if (m_DataBlockSize - m_DataBlockCurPos < static_cast((numChars + 1) * 2)) { + return; + } + m_pLines[m_NumAddedLines] = reinterpret_cast(m_pRawDataBlock + m_DataBlockCurPos); + bMemCpy(m_pLines[m_NumAddedLines], pText, numChars * 2); + m_pLines[m_NumAddedLines][numChars] = 0; + m_NumAddedLines++; + m_DataBlockCurPos += (numChars + 1) * 2; +} + +void CTextScroller::UpdateScrollBar() { + if (m_pScrollBar) { + m_pScrollBar->Update(GetNumVisibleLines(), GetNumLines(), GetTopLine() + 1, GetTopLine() + 1); + } +} + +void CTextScroller::SetText(short *pText) { + if (!m_pFont || !pText) { + m_NumAddedLines = 0; + } else { + m_DataBlockCurPos = 0; + m_NumAddedLines = 0; + int totalLines = 0; + int totalChars = 0; + short *lineStart = pText; + short *lineEnd = FindCR(pText); + if (!lineEnd) { + lineEnd = FindEND(pText); + } + while (lineEnd) { + if (*lineStart == 0) { + break; + } + if (lineEnd == lineStart) { + totalLines++; + totalChars++; + } else { + int numLines, numChars; + WordWrapCountLinesAndChars(lineStart, lineEnd, numLines, numChars); + totalLines += numLines; + totalChars += numChars; + } + if (*lineEnd == 0) { + lineEnd = nullptr; + if (!lineEnd) { + break; + } + } else { + lineStart = lineEnd + 1; + lineEnd = FindCR(lineStart); + if (!lineEnd) { + lineEnd = FindEND(lineStart); + } + } + } + if (totalLines > 0) { + if (!m_pRawDataBlock) { + int blockSize = totalChars * 2 + totalLines * 4; + m_DataBlockSize = blockSize; + m_pRawDataBlock = new char[blockSize]; + } + m_DataBlockCurPos = totalLines * 4; + m_pLines = reinterpret_cast(m_pRawDataBlock); + lineEnd = FindCR(pText); + if (!lineEnd) { + lineEnd = FindEND(pText); + } + if (lineEnd) { + while (*pText != 0) { + if (lineEnd == pText) { + AddLine(nullptr, 0); + } else { + WordWrapAddLines(pText, lineEnd, false, nullptr); + } + if (*lineEnd == 0) { + lineEnd = nullptr; + if (!lineEnd) { + break; + } + } else { + pText = lineEnd + 1; + lineEnd = FindCR(pText); + if (!lineEnd) { + lineEnd = FindEND(pText); + if (!lineEnd) { + break; + } + } + } + } + } + } + } + m_TopLine = 0; + UpdateScrollBar(); + Display(m_TopLine); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp index cc234d778..bac48e8c3 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp @@ -1,10 +1,217 @@ -#ifndef FRONTEND_MENUSCREENS_COMMON_FEWIDGET_H -#define FRONTEND_MENUSCREENS_COMMON_FEWIDGET_H +#ifndef _FEWIDGET +#define _FEWIDGET -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.hpp" + +struct FEObject; +struct FEString; +struct FEImage; + +// 0x34 +struct FEWidget : public bTNode { +protected: + bVector2 vTopLeft; // 0x08 + bVector2 vSize; // 0x10 + bVector2 vBackingOffset; // 0x18 + FEObject* pBacking; // 0x20 + bool bEnabled; // 0x24 + bool bHidden; // 0x28 + bool bMovedLastUpdate; // 0x2C + +public: + FEWidget(FEObject* backing, bool enabled, bool hidden); + virtual ~FEWidget() {} + virtual void Act(const char* parent_pkg, unsigned int data); + virtual void CheckMouse(const char* parent_pkg, const float mouse_x, const float mouse_y); + virtual void Draw(); + virtual void Position(); + virtual void Show(); + virtual void Hide(); + virtual void Enable() { bEnabled = true; } + virtual void Disable() { bEnabled = false; } + virtual void SetFocus(const char* parent_pkg); + virtual void UnsetFocus(); + virtual void SetPos(bVector2& pos) { SetTopLeft(pos); } + virtual void SetPosX(float x) { SetTopLeftX(x); } + virtual void SetPosY(float y) { SetTopLeftY(y); } + + bool IsEnabled() { return bEnabled; } + bool IsHidden() { return bHidden; } + void GetTopLeft(bVector2& top_left) { top_left.x = vTopLeft.x; top_left.y = vTopLeft.y; } + float GetTopLeftX() { return vTopLeft.x; } + float GetTopLeftY() { return vTopLeft.y; } + void GetSize(bVector2& size) { size.x = vSize.x; size.y = vSize.y; } + float GetWidth() { return vSize.x; } + float GetHeight() { return vSize.y; } + void SetTopLeft(bVector2& top_left) { vTopLeft.x = top_left.x; vTopLeft.y = top_left.y; } + void SetTopLeftX(float x) { vTopLeft.x = x; } + void SetTopLeftY(float y) { vTopLeft.y = y; } + void SetSize(bVector2& size) { vSize = size; } + void SetWidth(float width) { vSize.x = width; } + void SetHeight(float height) { vSize.y = height; } + void SetBacking(FEObject* obj) { pBacking = obj; } + FEObject* GetBacking() { return pBacking; } + void GetBackingOffset(bVector2& offset) { offset.x = vBackingOffset.x; offset.y = vBackingOffset.y; } + float GetBackingOffsetX() { return vBackingOffset.x; } + float GetBackingOffsetY() { return vBackingOffset.y; } + void SetBackingOffset(bVector2& offset) { vBackingOffset = offset; } + void SetBackingOffset(float x, float y) { vBackingOffset.x = x; vBackingOffset.y = y; } + void SetBackingOffsetX(float x) { vBackingOffset.x = x; } + void SetBackingOffsetY(float y) { vBackingOffset.y = y; } + bool MovedLastUpdate() { return bMovedLastUpdate; } +}; + +// 0x40 +struct FEButtonWidget : public FEWidget { + FEString* pTitle; // 0x34 + bVector2 vMaxTitleSize; // 0x38 + + FEButtonWidget(bool enabled); + ~FEButtonWidget() override {} + + FEString* GetTitleObject() { return pTitle; } + void SetTitleObject(FEString* string) { pTitle = string; } + void SetPos(bVector2& pos) override; + void GetMaxTitleSize(bVector2& size) { size = vMaxTitleSize; } + float GetMaxTitleWidth() { return vMaxTitleSize.x; } + float GetMaxTitleHeight() { return vMaxTitleSize.y; } + void SetMaxTitleSize(bVector2& size) { vMaxTitleSize.x = size.x; vMaxTitleSize.y = size.y; } + void SetMaxTitleWidth(float width) { vMaxTitleSize.x = width; } + void SetMaxTitleHeight(float height) { vMaxTitleSize.y = height; } + void CheckMouse(const char* parent_pkg, float mouse_x, float mouse_y) override; + void Position() override; + void Show() override; + void Hide() override; + void SetFocus(const char* parent_pkg) override; + void UnsetFocus() override; +}; + +// 0x54 +struct FEStatWidget : public FEWidget { +protected: + FEString* pTitle; // 0x34 + FEString* pData; // 0x38 + bVector2 vMaxTitleSize; // 0x3C + bVector2 vMaxDataSize; // 0x44 + bVector2 vDataPos; // 0x4C + +public: + FEStatWidget(bool enabled); + ~FEStatWidget() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void CheckMouse(const char* parent_pkg, const float mouse_x, const float mouse_y) override; + void Draw() override; + void Position() override; + void Show() override; + void Hide() override; + void SetFocus(const char* parent_pkg) override; + void UnsetFocus() override; + void SetPos(bVector2& pos) override; + void SetPosX(float x) override; + void SetPosY(float y) override; + + FEString* GetTitleObject() { return pTitle; } + FEString* GetDataObject() { return pData; } + void SetTitleObject(FEString* string) { pTitle = string; } + void SetDataObject(FEString* string) { pData = string; } + void GetDataPos(bVector2& pos); + float GetDataPosX(); + float GetDataPosY(); + void SetDataPos(bVector2& pos) { vDataPos.x = pos.x; vDataPos.y = pos.y; } + void SetDataPosX(float x) { vDataPos.x = x; } + void SetDataPosY(float y) { vDataPos.y = y; } + void GetMaxTitleSize(bVector2& size); + float GetMaxTitleWidth(); + float GetMaxTitleHeight(); + void GetMaxDataSize(bVector2& size); + float GetMaxDataWidth(); + float GetMaxDataHeight(); + void SetMaxTitleSize(bVector2& size) { vMaxTitleSize.x = size.x; vMaxTitleSize.y = size.y; } + void SetMaxTitleWidth(float width); + void SetMaxTitleHeight(float height); + void SetMaxDataSize(bVector2& size) { vMaxDataSize.x = size.x; vMaxDataSize.y = size.y; } + void SetMaxDataWidth(float x); + void SetMaxDataHeight(float y); +}; + +// 0x64 +struct FEToggleWidget : public FEStatWidget { +protected: + FEImage* pLeftImage; // 0x54 + FEImage* pRightImage; // 0x58 + unsigned int EnableScript; // 0x5C + unsigned int DisableScript;// 0x60 + +public: + FEToggleWidget(bool enabled); + ~FEToggleWidget() override; + void Act(const char* parent_pkg, unsigned int data) override; + void CheckMouse(const char* parent_pkg, const float mouse_x, const float mouse_y) override; + void Draw() override; + void Position() override; + void Show() override; + void Hide() override; + void Enable() override; + void Disable() override; + void SetFocus(const char* parent_pkg) override; + void UnsetFocus() override; + virtual void BlinkArrows(unsigned int data); + + FEImage* GetLeftImage() { return pLeftImage; } + FEImage* GetRightImage() { return pRightImage; } + void SetLeftImage(FEImage* img) { pLeftImage = img; } + void SetRightImage(FEImage* img) { pRightImage = img; } + bool Update(unsigned int msg) { + bMovedLastUpdate = true; + BlinkArrows(msg); + Draw(); + return true; + } + unsigned int GetEnableScript(); + unsigned int GetDisableScript(); + void SetEnableScript(unsigned int script) { EnableScript = script; } + void SetDisableScript(unsigned int script) { DisableScript = script; } + void SetScript(unsigned int script); +}; + +// 0xA4 +struct FESliderWidget : public FEToggleWidget { +protected: + cSlider Slider; // 0x64 + float fVertOffset; // 0xA0 +public: + FESliderWidget(bool enabled); + ~FESliderWidget() override; + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; + void Position() override; + void Show() override; + void Hide() override; + void Enable() override; + void Disable() override; + void SetFocus(const char* parent_pkg) override; + void UnsetFocus() override; + virtual void SetInitialValues(); + void SetDataObject(FEString* string); + void InitSliderObjects(const char* pkg_name, const char* name) { Slider.InitObjects(pkg_name, name); } + void SetSliderValues(float min, float max, float inc, float cur) { + Slider.InitValues(min, max, inc, cur, 160.0f); + } + float GetValue() { return Slider.GetValue(); } + void SetValue(float val) { Slider.SetValue(val); } + void Increment() { Slider.Increment(); } + void Decrement() { Slider.Decrement(); } + void DrawSlider() { Slider.Draw(); } + void ToggleSlider(bool on) { Slider.ToggleVisible(on); } + void UpdateSlider(unsigned int msg); + float GetVertOffset(); + void SetVertOffset(bool vertOffset); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/ControllerUnplugged.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/ControllerUnplugged.cpp index e69de29bb..4e11bf48e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/ControllerUnplugged.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/ControllerUnplugged.cpp @@ -0,0 +1,40 @@ +#include "ControllerUnplugged.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp" +#include "Speed/Indep/Src/Generated/Events/EPause.hpp" + +const char* GetLocalizedString(unsigned int hash); +void FEPrintf(const char* pkg_name, unsigned int hash, const char* format, ...); +int FEngMapJoyParamToJoyport(int feng_param); +int GetPortsPlayer(int port); + +ControllerUnplugged::ControllerUnplugged(ScreenConstructorData* sd) + : MenuScreen(sd) // + , port(static_cast(sd->Arg)) +{ + Setup(); +} + +ControllerUnplugged::~ControllerUnplugged() {} + +void ControllerUnplugged::Setup() { + const char* pkg_name = GetPackageName(); + const char* text = GetLocalizedString(0x54EEF4C5); + FEPrintf(pkg_name, 0xB244CF71, text, port + 1); +} + +void ControllerUnplugged::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) { + if (msg == 0xEBFCDA65) { + if (port == -1) { + BootFlowManager::Get()->JumpToHead(); + } else { + int joyPort = FEngMapJoyParamToJoyport(param1); + cFEng::Get()->PopErrorPackage(joyPort); + if (FEManager::IsOkayToRequestPauseSimulation(GetPortsPlayer(joyPort), false, false)) { + new EPause(GetPortsPlayer(joyPort), 0, 0); + } + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/ControllerUnplugged.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/ControllerUnplugged.hpp new file mode 100644 index 000000000..40eb29f6b --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/ControllerUnplugged.hpp @@ -0,0 +1,22 @@ +#ifndef FRONTEND_MENUSCREENS_CONTROLLERUNPLUGGED_H +#define FRONTEND_MENUSCREENS_CONTROLLERUNPLUGGED_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" + +// total size: 0x30 +struct ControllerUnplugged : public MenuScreen { + JoystickPort port; // offset 0x2C + + ControllerUnplugged(ScreenConstructorData* sd); + ~ControllerUnplugged() override; + static MenuScreen* Create(ScreenConstructorData* sd); + void NotificationMessage(unsigned long, FEObject*, unsigned long, unsigned long) override; + void Setup(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.cpp index e69de29bb..ce55d4baa 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.cpp @@ -0,0 +1,551 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEImage.h" +#include "Speed/Indep/Src/Generated/Events/ETuneVehicle.hpp" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/CTextScroller.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" + +extern int FEPrintf(FEString *, const char *, ...); +extern unsigned int FEngHashString(const char *, ...); +extern FEObject *FEngFindObject(const char *, unsigned int); +extern FEString *FEngFindString(const char *, int); +extern void FEngGetSize(FEObject *, float &, float &); +extern void FEngGetTopLeft(FEObject *, float &, float &); +extern FEngFont *FindFont(unsigned int); +extern const char *GetLocalizedString(unsigned int); +extern void FEngSetCurrentButton(const char *, unsigned int); +extern void FEngSetLanguageHash(FEString *, unsigned int); +extern void FEngSetLanguageHash(const char *, unsigned int, unsigned int); +extern void FEngSetScript(FEObject *, unsigned int, bool); +extern int FEngSNPrintf(char *, int, const char *, ...); +extern int bSNPrintf(char *, int, const char *, ...); + +extern const char lbl_803E5088[]; +extern const char lbl_803E89B8[]; +extern const char lbl_803E89C4[]; +extern const char lbl_803E89CC[]; +extern const char lbl_803E89FC[]; +extern const char lbl_803E8A28[]; +extern const char lbl_803E8A38[]; +extern const char lbl_803E8A18[]; +extern const char lbl_803E4CFC[]; +extern const char lbl_803E5EEC[]; +extern const float lbl_803E89B4; +extern const float lbl_803E89C0; +extern const float lbl_803E89D8; +extern const float lbl_803E89DC; +extern const float lbl_803E89E0; +extern const float lbl_803E89E4; +extern const float lbl_803E8A24; + +namespace Physics { +namespace Upgrades { +void SetLevel(Attrib::Gen::pvehicle &vehicle, int type, int level); +} + +namespace Info { +eInductionType InductionType(const Attrib::Gen::pvehicle &vehicle); +} +} + +struct TuningSlider : public FEToggleWidget { + FEObject *pSliderGroup; + cSlider Negative; + cSlider Positive; + unsigned int Title; + unsigned int HelpBlurb; + float Min; + float Max; + float Current; + float Increment; + bool bActive; + Physics::Tunings::Path TuningPath; + + TuningSlider(Physics::Tunings::Path path, unsigned int title, unsigned int help_blurb, bool active); + ~TuningSlider() override {} + void Act(const char *parent_pkg, unsigned int data) override; + void CheckMouse(const char *parent_pkg, float mouse_x, float mouse_y) override; + void Draw() override; + void Position() override; + void SetFocus(const char *parent_pkg) override; + void UnsetFocus() override; + void SetSliderGroup(const char *pkg_name, unsigned int group_name); + void InitSliderObjects(const char *pkg_name, const char *name); + void SetSliderValues(float min, float max, float inc, float cur); +}; + +CustomTuningScreen::CustomTuningScreen(ScreenConstructorData *sd) + : UIWidgetMenu(sd) // + , HelpTextScroller(nullptr) // + , HelpScrollBar(nullptr) // + , TuningRecord(nullptr) // + , TempTuningRecord() // + , CurrentTuningType(0) // + , HelpVisible(false) // + , ExitWithStart(false) { + HelpScrollBar = new (__FILE__, __LINE__) FEScrollBar(sd->PackageFilename, lbl_803E89FC, true, true, false); + bHasScrollBar = false; + iMaxWidgetsOnScreen = 8; + Setup(); +} + +CustomTuningScreen::~CustomTuningScreen() { + delete HelpScrollBar; +} + +TuningSlider::TuningSlider(Physics::Tunings::Path path, unsigned int title, unsigned int help_blurb, bool active) + : FEToggleWidget(true) // +{ + Title = title; + HelpBlurb = help_blurb; + bActive = active; + TuningPath = path; + bMovedLastUpdate = true; + BlinkArrows(0); +} + +void TuningSlider::Act(const char * /* parent_pkg */, unsigned int data) { + if (!bActive) { + return; + } + + if (data == 0xB5971BF1) { + Current += Increment; + } else if (data == 0x9120409E) { + Current -= Increment; + } + + Current = Current < Min ? Min : Current; + Current = Current > Max ? Max : Current; + Negative.SetValue((Max + Min) * lbl_803E89B4 + Min - Current); + Positive.SetValue(Current); + Update(data); +} + +void TuningSlider::CheckMouse(const char * /* parent_pkg */, float /* mouse_x */, float /* mouse_y */) {} + +void TuningSlider::Draw() { + FEngSetLanguageHash(GetTitleObject(), Title); + FEPrintf(GetDataObject(), lbl_803E89B8, ((Current - Min) / (Max - Min)) * lbl_803E89C0); + if (bActive) { + FEngSetScript(GetTitleObject(), 0x7AB5521A, true); + FEngSetScript(pSliderGroup, 0x001744B3, true); + } else { + FEngSetScript(GetTitleObject(), 0x00163C76, true); + FEngSetScript(pSliderGroup, 0x00163C76, true); + } + Negative.Draw(); + Positive.Draw(); +} + +void TuningSlider::Position() {} + +void TuningSlider::SetFocus(const char *parent_pkg) { + FEngSetCurrentButton(parent_pkg, GetTitleObject()); + if (bActive) { + const unsigned long FEObj_Init = 0x7AB5521A; + + FEngSetScript(GetTitleObject(), FEObj_Init, true); + FEngSetScript(pSliderGroup, 0x001744B3, true); + } +} + +void TuningSlider::UnsetFocus() { + if (bActive) { + const unsigned long FEObj_Init = 0x7AB5521A; + FEngSetScript(GetTitleObject(), FEObj_Init, true); + FEngSetScript(pSliderGroup, 0x001744B3, true); + } else { + const unsigned long FEObj_GREY = 0x00163C76; + FEngSetScript(GetTitleObject(), FEObj_GREY, true); + FEngSetScript(pSliderGroup, FEObj_GREY, true); + } +} + +void TuningSlider::SetSliderGroup(const char *pkg_name, unsigned int group_name) { + pSliderGroup = FEngFindObject(pkg_name, group_name); +} + +void TuningSlider::InitSliderObjects(const char *pkg_name, const char *name) { + char slider_name[32]; + + bSNPrintf(slider_name, 32, lbl_803E89C4, name); + Negative.InitObjects(pkg_name, slider_name); + bSNPrintf(slider_name, 32, lbl_803E89CC, name); + Positive.InitObjects(pkg_name, slider_name); +} + +void TuningSlider::SetSliderValues(float min, float max, float inc, float cur) { + float middle = (max + min) * lbl_803E89D8; + + Increment = inc; + Min = min; + Max = max; + Current = cur; + Negative.InitValues(min, middle, lbl_803E89DC, middle + min - cur, lbl_803E89E0); + Positive.InitValues((Max + Min) * lbl_803E89D8, Max, lbl_803E89DC, Current, lbl_803E89E4); +} + +void CustomTuningScreen::ScrollTypes(eScrollDir dir) { + if (HelpVisible) { + return; + } + + int next_type = CurrentTuningType; + + if (dir == eSD_NEXT) { + next_type++; + if (next_type > 2) { + next_type = 0; + } + } else if (dir == eSD_PREV) { + next_type--; + if (next_type < 0) { + next_type = 2; + } + } + + if (next_type != CurrentTuningType) { + CurrentTuningType = next_type; + SetSlidersForType(); + } +} + +void CustomTuningScreen::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (HelpVisible) { + HelpTextScroller->HandleNotificationMessage(msg); + } + + if (msg == 0x35F8620B) { + for (FEWidget *option = Options.GetHead(); option != Options.EndOfList(); option = option->GetNext()) { + option->UnsetFocus(); + } + } + + if (!HelpVisible || (msg != 0x9120409E && msg != 0xB5971BF1 && msg != 0x72619778 && msg != 0x911C0A4B)) { + UIWidgetMenu::NotificationMessage(msg, pobj, param1, param2); + } + + switch (msg) { + case 0xB5AF2461: + if (!HelpVisible) { + ExitWithStart = true; + } else { + return; + } + case 0x406415E3: + if (!HelpVisible) { + StoreSettings(); + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), nullptr); + } + break; + case 0x9120409E: + case 0xB5971BF1: + if (!HelpVisible) { + TempTuningRecord.Tunings[CurrentTuningType].Value[static_cast< TuningSlider * >(pCurrentOption)->TuningPath] = + static_cast< TuningSlider * >(pCurrentOption)->Current; + } + break; + case 0x5073EF13: + ScrollTypes(eSD_PREV); + break; + case 0xD9FEEC59: + ScrollTypes(eSD_NEXT); + break; + case 0xC519BFC4: + if (!HelpVisible) { + ShowHelpBlurb(); + } + break; + case 0x911AB364: + if (HelpVisible) { + HideHelpBlurb(); + } else if (SettingsDidNotChange()) { + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), nullptr); + } else { + DialogInterface::ShowTwoButtons( + GetPackageName(), + lbl_803E5EEC, + static_cast< eDialogTitle >(1), + 0x70E01038, + 0x417B25E4, + 0x775DBA97, + 0x34DC1BCF, + 0x34DC1BCF, + static_cast< eDialogFirstButtons >(1), + GetLocalizedString(0xE9CB802F)); + } + break; + case 0x775DBA97: + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), nullptr); + break; + case 0xE1FDE1D1: + if (ExitWithStart) { + new EUnPause(); + } else { + cFEng::Get()->QueuePackageSwitch(lbl_803E4CFC, 0, 0, false); + } + break; + } +} + +void CustomTuningScreen::DrawSettingName(unsigned int tuning_type) { + switch (tuning_type) { + case 0: + FEngSetLanguageHash(GetPackageName(), 0x05CDDED4, 0x40230063); + break; + case 1: + FEngSetLanguageHash(GetPackageName(), 0x05CDDED4, 0x40230064); + break; + case 2: + FEngSetLanguageHash(GetPackageName(), 0x05CDDED4, 0x40230065); + break; + } +} + +unsigned int CustomTuningScreen::AddTuningSlider(FEPlayerCarDB *stable, FECarRecord *record, Physics::Tunings::Path path, bool turbo) { + char object_name[64]; + float button_width; + float x; + float y; + float width; + float height; + bool active = IsTuningAvailable(stable, record, path); + TuningSlider *slider = new TuningSlider(path, GetNameForPath(path, turbo), GetHelpForPath(path, active, turbo), active); + + slider->SetTitleObject(GetCurrentFEString(pTitleName)); + slider->SetDataObject(GetCurrentFEString(pDataName)); + slider->SetBacking(GetCurrentFEObject(pBackingName)); + FEngSNPrintf(object_name, 64, lbl_803E5088, pSliderName, iIndexToAdd); + slider->InitSliderObjects(GetPackageName(), object_name); + FEngSNPrintf(object_name, 64, lbl_803E8A18, pSliderName, iIndexToAdd); + slider->SetSliderGroup(GetPackageName(), FEngHashString(object_name)); + slider->SetLeftImage(GetCurrentFEImage(pLeftArrowName)); + slider->SetRightImage(GetCurrentFEImage(pRightArrowName)); + Options.AddTail(slider); + iIndexToAdd++; + IncrementStartPos(); + FEngGetTopLeft(slider->GetRightImage(), x, y); + FEngGetSize(slider->GetRightImage(), width, height); + button_width = (x + width) - x; + if (button_width < 0.0f) { + button_width = -button_width; + } + slider->SetWidth(button_width); + return iIndexToAdd - 1; +} + +void CustomTuningScreen::Setup() { + unsigned int current_car; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *record; + + if (FEDatabase->IsCareerMode()) { + current_car = FEDatabase->GetCareerSettings()->GetCurrentCar(); + } else { + current_car = FEDatabase->GetQuickRaceSettings(static_cast< GRace::Type >(0xB))->GetSelectedCar(0); + } + + record = stable->GetCarRecordByHandle(current_car); + TuningRecord = stable->GetCustomizationRecordByHandle(record->Customization); + if (TuningRecord != nullptr) { + TempTuningRecord = *TuningRecord; + CurrentTuningType = TuningRecord->ActiveTuning; + } + + AddTuningSlider(stable, record, Physics::Tunings::STEERING, false); + AddTuningSlider(stable, record, Physics::Tunings::HANDLING, false); + AddTuningSlider(stable, record, Physics::Tunings::BRAKES, false); + AddTuningSlider(stable, record, Physics::Tunings::RIDEHEIGHT, false); + AddTuningSlider(stable, record, Physics::Tunings::AERODYNAMICS, false); + AddTuningSlider(stable, record, Physics::Tunings::NOS, false); + + Attrib::Gen::pvehicle vehicle(record->VehicleKey, 0, 0); + + Physics::Upgrades::SetLevel(vehicle, 5, 1); + AddTuningSlider(stable, record, Physics::Tunings::INDUCTION, Physics::Info::InductionType(vehicle) == 1); + SetSlidersForType(); + SetInitialOption(1); +} + +void CustomTuningScreen::SetSlidersForType() { + DrawSettingName(CurrentTuningType); + + for (FEWidget *option = Options.GetHead(); option != Options.EndOfList(); option = option->GetNext()) { + TuningSlider *slider = static_cast< TuningSlider * >(option); + float lower = Physics::Tunings::LowerLimit(slider->TuningPath); + float upper = Physics::Tunings::UpperLimit(slider->TuningPath); + + slider->SetSliderValues( + lower, + upper, + (upper - lower) * lbl_803E8A24, + TempTuningRecord.Tunings[CurrentTuningType].Value[slider->TuningPath]); + slider->Draw(); + } +} + +void CustomTuningScreen::ShowHelpBlurb() { + cFEng::Get()->QueuePackageMessage(0x89D332A9, GetPackageName(), nullptr); + pCurrentOption->UnsetFocus(); + if (HelpTextScroller == nullptr) { + FEString *help_text = FEngFindString(GetPackageName(), FEHashUpper(lbl_803E8A28)); + + HelpTextScroller = new CTextScroller(); + HelpTextScroller->Initialise(this, help_text->MaxWidth, 7, const_cast< char * >(lbl_803E8A38), FindFont(help_text->Handle)); + HelpTextScroller->UseScrollBar(HelpScrollBar); + HelpTextScroller->SetTextHash(static_cast< TuningSlider * >(pCurrentOption)->HelpBlurb); + } + HelpVisible = true; +} + +void CustomTuningScreen::HideHelpBlurb() { + cFEng::Get()->QueuePackageMessage(0x950AD1C2, GetPackageName(), nullptr); + pCurrentOption->SetFocus(GetPackageName()); + if (HelpTextScroller != nullptr) { + delete HelpTextScroller; + HelpTextScroller = nullptr; + } + HelpVisible = false; +} + +void CustomTuningScreen::StoreSettings() { + for (int tuning = 0; tuning < Physics::NUM_CUSTOM_TUNINGS; tuning++) { + for (int path = 0; path < Physics::Tunings::MAX_TUNINGS; path++) { + TuningRecord->Tunings[tuning].Value[path] = TempTuningRecord.Tunings[tuning].Value[path]; + } + } + + TuningRecord->ActiveTuning = static_cast< Physics::eCustomTuningType >(CurrentTuningType); + new ETuneVehicle(0, reinterpret_cast< const Tunings * >(&TuningRecord->Tunings[TuningRecord->ActiveTuning])); +} + +bool CustomTuningScreen::SettingsDidNotChange() { + for (int tuning = 0; tuning < Physics::NUM_CUSTOM_TUNINGS; tuning++) { + for (int path = 0; path < Physics::Tunings::MAX_TUNINGS; path++) { + if (TuningRecord->Tunings[tuning].Value[path] != TempTuningRecord.Tunings[tuning].Value[path]) { + return false; + } + } + } + + return CurrentTuningType == TuningRecord->ActiveTuning; +} + +bool CustomTuningScreen::IsTuningAvailable(FEPlayerCarDB *stable, FECarRecord *record, Physics::Tunings::Path path) { + if (record == nullptr) { + return false; + } + + FECustomizationRecord *custom = stable->GetCustomizationRecordByHandle(record->Customization); + if (custom == nullptr) { + return false; + } + + switch (path) { + case Physics::Tunings::STEERING: + case Physics::Tunings::HANDLING: + case Physics::Tunings::RIDEHEIGHT: + return custom->InstalledPhysics.Part[2] > 0; + case Physics::Tunings::BRAKES: + return custom->InstalledPhysics.Part[1] > 0; + case Physics::Tunings::AERODYNAMICS: { + RideInfo ride; + + ride.Init(static_cast< CarType >(-1), CarRenderUsage_Player, 0, 0); + stable->BuildRideForPlayer(record->Handle, 0, &ride); + ride.SetStockParts(); + + struct CarPart *stock = ride.GetPart(0x2C); + struct CarPart *installed = custom->GetInstalledPart(record->GetType(), 0x2C); + if (installed != nullptr && installed != stock) { + return true; + } + + stock = ride.GetPart(0x17); + installed = custom->GetInstalledPart(record->GetType(), 0x17); + return installed != nullptr && installed != stock; + } + case Physics::Tunings::NOS: + return custom->InstalledPhysics.Part[6] > 0; + case Physics::Tunings::INDUCTION: + return custom->InstalledPhysics.Part[5] > 0; + default: + return false; + } +} + +unsigned int CustomTuningScreen::GetNameForPath(Physics::Tunings::Path path, bool turbo) { + switch (path) { + case Physics::Tunings::STEERING: + return 0xC56C5B36; + case Physics::Tunings::HANDLING: + return 0xFCEEBE1A; + case Physics::Tunings::BRAKES: + return 0x2EE2A74D; + case Physics::Tunings::RIDEHEIGHT: + return 0xCF6215D1; + case Physics::Tunings::AERODYNAMICS: + return 0x7196ACB4; + case Physics::Tunings::NOS: + return 0x934FCFA9; + case Physics::Tunings::INDUCTION: + return turbo ? 0x86945521 : 0xE3A932E0; + default: + return 0; + } +} + +unsigned int CustomTuningScreen::GetHelpForPath(Physics::Tunings::Path path, bool active, bool turbo) { + if (active) { + switch (path) { + case Physics::Tunings::STEERING: + return 0x4A1F18BE; + case Physics::Tunings::HANDLING: + return 0x81A17BA2; + case Physics::Tunings::BRAKES: + return 0x473862D5; + case Physics::Tunings::RIDEHEIGHT: + return 0xD6C24659; + case Physics::Tunings::AERODYNAMICS: + return 0x64FCEE3C; + case Physics::Tunings::NOS: + return 0xB65CFC31; + case Physics::Tunings::INDUCTION: + return turbo ? 0xB5DCBFA9 : 0xD70F7468; + default: + return 0; + } + } + + switch (path) { + case Physics::Tunings::STEERING: + return 0x221D7E85; + case Physics::Tunings::HANDLING: + return 0x18C12069; + case Physics::Tunings::BRAKES: + return 0xC213A6DC; + case Physics::Tunings::RIDEHEIGHT: + return 0xB6D02C60; + case Physics::Tunings::AERODYNAMICS: + return 0xC6A99483; + case Physics::Tunings::NOS: + return 0xB8124038; + case Physics::Tunings::INDUCTION: + return turbo ? 0xB7D6F7B0 : 0xE3F577AF; + default: + return 0; + } +} + +static MenuScreen *CreateCustomTuningScreen(ScreenConstructorData *sd) { + return new (__FILE__, __LINE__) CustomTuningScreen(sd); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.hpp index f16c209cb..0f697d361 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.hpp @@ -5,6 +5,38 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +struct CTextScroller; +class FEScrollBar; + +class CustomTuningScreen : public UIWidgetMenu { + public: + CustomTuningScreen(ScreenConstructorData *sd); + ~CustomTuningScreen() override; + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + static bool IsTuningAvailable(FEPlayerCarDB *stable, FECarRecord *record, Physics::Tunings::Path path); + void ScrollTypes(eScrollDir dir); + void DrawSettingName(unsigned int tuning_type); + unsigned int GetNameForPath(Physics::Tunings::Path path, bool turbo); + unsigned int GetHelpForPath(Physics::Tunings::Path path, bool active, bool turbo); + unsigned int AddTuningSlider(FEPlayerCarDB *stable, FECarRecord *record, Physics::Tunings::Path path, bool turbo); + void Setup() override; + void SetSlidersForType(); + void ShowHelpBlurb(); + void HideHelpBlurb(); + void StoreSettings(); + bool SettingsDidNotChange(); + + protected: + CTextScroller *HelpTextScroller; + FEScrollBar *HelpScrollBar; + FECustomizationRecord *TuningRecord; + FECustomizationRecord TempTuningRecord; + int CurrentTuningType; + bool HelpVisible; + bool ExitWithStart; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPKg_PostRace.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPKg_PostRace.cpp index e69de29bb..0862a9bd1 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPKg_PostRace.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPKg_PostRace.cpp @@ -0,0 +1,1811 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPKg_PostRace.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.hpp" +#include "Speed/Indep/Src/Generated/Events/EQuitToFE.hpp" +#include "Speed/Indep/Src/Generated/Events/ERestartRace.hpp" +#include "Speed/Indep/Src/Generated/Events/EShowResults.hpp" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Generated/Messages/MEnterSafeHouse.h" +#include "Speed/Indep/Src/Generated/Messages/MNotifyRaceAbandoned.h" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GInfractionManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Gameplay/GTimer.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/ISimable.h" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" + +extern FEString *FEngFindString(const char *pkg_name, int hash); +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +extern FEImage *FEngFindImage(const char *pkg_name, int hash); +extern int FEngSNPrintf(char *, int, const char *, ...); +extern unsigned long FEHashUpper(const char *); +extern int bStrCmp(const char *, const char *); +extern void MemcardEnter(const char *, const char *, unsigned int, void (*)(void *), void *, unsigned int, unsigned int); +extern void FEngSetInvisible(FEObject *obj); +extern void FEngSetVisible(FEObject *obj); +extern void FEngSetButtonTexture(FEImage *img, unsigned int hash); +extern void FEngSetInvisible(const char *pkg_name, unsigned int obj_hash); +extern bool FEngIsScriptSet(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash); +extern bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, + bool start_at_beginning); +extern void FEngSetScript(FEObject *obj, unsigned int script_hash, bool start_at_beginning); +extern void FEngSetLanguageHash(FEString *text, unsigned int hash); +extern void FEngSetLanguageHash(const char *pkg_name, unsigned int object_hash, unsigned int language_hash); +extern unsigned int FEngHashString(const char *, ...); +extern int FEPrintf(FEString *text, const char *fmt, ...); +extern int FEPrintf(const char *pkg_name, int object_hash, const char *fmt, ...); + +inline void FEngSetVisible(const char *pkg_name, unsigned int obj_hash) { + FEngSetVisible(FEngFindObject(pkg_name, obj_hash)); +} +extern const char *GetLocalizedPercentSign(); +extern float GRaceStatusGetRaceTimeElapsed(const GRaceStatus *race_status) asm("GetRaceTimeElapsed__C11GRaceStatus"); +extern float GRacerInfoCalcAverageSpeed(const GRacerInfo *racer_info) asm("CalcAverageSpeed__C10GRacerInfo"); +extern bool GRacerInfoAreStatsReady(const GRacerInfo *racer_info) asm("AreStatsReady__C10GRacerInfo"); + +extern const char lbl_803E4CB4[]; // "%d" +extern const char lbl_803E4CF0[]; // "%s" +extern const char lbl_803E43DC[]; + +int bSNPrintf(char *buf, int max_len, const char *format, ...); +const char *GetLocalizedString(unsigned int hash); +extern const char lbl_803E5074[]; +extern const char lbl_803E507C[]; +extern const char lbl_803E5084[]; +extern const char lbl_803E5088[]; +extern const char lbl_803E5E44[]; +extern const float lbl_803E5E4C; +extern const float lbl_803E5E50; +extern const float lbl_803E5E54; +extern const float lbl_803E5E58; +extern const float lbl_803E5E5C; +extern const float lbl_803E5E60; +extern const float lbl_803E5E64; +extern const float lbl_803E5E68; +extern const float lbl_803E5E6C; +extern const float lbl_803E5E70; +extern const float lbl_803E5E74; +extern const double lbl_803E5E78; +extern const float lbl_803E5E80; +extern const float lbl_803E5E84; +extern const float lbl_803E5E88; +extern const double lbl_803E5E90; +extern const float lbl_803E5E98; +extern const float lbl_803E5E9C; +extern const float lbl_803E5EA0; +extern const float lbl_803E5EB0; +extern const float lbl_803E5EB4; +extern const char lbl_803E5DB0[]; +extern const char lbl_803E5DCC[]; +extern const char lbl_803E5DDC[]; +extern const char lbl_803E5E04[]; +extern const char lbl_803E5E24[]; +extern const char lbl_803E5EEC[]; +extern const char lbl_803E5F00[]; +extern const char lbl_803E5F18[]; +extern const char lbl_803E5F38[]; +extern const char lbl_803E5F48[]; +extern const char lbl_803E5F50[]; +extern const char lbl_803E5FA0[]; +extern const char lbl_803E52A0[]; +extern const char lbl_803E52D4[]; + +PursuitData PostRacePursuitScreen::mPursuitData; + +struct LapStat : public ResultStat { + LapStat(FEString *lap, FEString *time, FEString *pos, int lap_num, float seconds, int pos_num) + : ResultStat(lap, time, pos, nullptr) // + , LapNum(lap_num) // + , Time(seconds) // + , PosNum(pos_num) {} + + ~LapStat() override; + void Draw() override; + + int LapNum; + Timer Time; + int PosNum; +}; + +struct SpeedStat : public ResultStat { + SpeedStat(FEString *speedtrap, FEString *speed, FEString *pos, int trap_num, float trap_speed, int pos_num) + : ResultStat(speedtrap, speed, pos, nullptr) // + , TrapNum(trap_num) // + , Speed(trap_speed) // + , PosNum(pos_num) {} + + ~SpeedStat() override; + void Draw() override; + + int TrapNum; + float Speed; + int PosNum; +}; + +RaceStat::RaceStat(FEString *title, FEString *data) + : FEStatWidget(true) { + SetTitleObject(title); + SetDataObject(data); +} + +RaceResultStat::~RaceResultStat() {} + +void RaceResultStat::Draw() { + char text[0x20]; + + FEPrintf(GetTitleObject(), lbl_803E4CF0, RacerInfo->GetName()); + + if (RacerInfo->GetIsEngineBlown()) { + FEngSetLanguageHash(GetDataObject(), 0x01E66364); + } else if (RacerInfo->GetIsTotalled()) { + FEngSetLanguageHash(GetDataObject(), 0xB7B75185); + } else if (RacerInfo->GetIsKnockedOut()) { + FEngSetLanguageHash(GetDataObject(), 0x5D82DBA2); + } else if (!RacerInfo->IsFinishedRacing()) { + FEngSetLanguageHash(GetDataObject(), 0x0FC1BF40); + } else { + Timer t(RacerInfo->GetRaceTimer().GetTime()); + t.PrintToString(text, 0); + FEPrintf(GetDataObject(), lbl_803E4CF0, text); + } + + FEPrintf(Position, lbl_803E4CB4, RacerInfo->GetRanking()); +} + +LapStat::~LapStat() {} + +void LapStat::Draw() { + FEPrintf(GetTitleObject(), lbl_803E4CB4, LapNum); + if (static_cast< float >(PosNum) == 0.0f) { + FEngSetLanguageHash(Position, 0x5D82DBA2); + FEngSetLanguageHash(GetDataObject(), 0x5D82DBA2); + return; + } + + if (Time.GetSeconds() == 0.0f) { + FEngSetLanguageHash(Position, 0x0FC1BF40); + FEngSetLanguageHash(GetDataObject(), 0x0FC1BF40); + return; + } + + char text[40]; + FEPrintf(Position, lbl_803E4CB4, PosNum); + Time.PrintToString(text, 0); + FEPrintf(GetDataObject(), lbl_803E4CF0, text); +} + +StageStat::~StageStat() {} + +void StageStat::Draw() { + FEPrintf(GetTitleObject(), lbl_803E5074, (StageNum + 1) * 0x14, GetLocalizedPercentSign()); + if (Time.GetSeconds() == 0.0f) { + FEngSetLanguageHash(Position, 0x0FC1BF40); + FEngSetLanguageHash(GetDataObject(), 0x0FC1BF40); + return; + } + + char text[32]; + FEPrintf(Position, lbl_803E4CB4, PosNum); + Time.PrintToString(text, 0); + FEPrintf(GetDataObject(), lbl_803E4CF0, text); +} + +SpeedStat::~SpeedStat() {} + +void SpeedStat::Draw() { + FEPrintf(GetTitleObject(), lbl_803E4CB4, TrapNum); + if (Speed == 0.0f) { + FEngSetLanguageHash(Position, 0x0FC1BF40); + FEngSetLanguageHash(GetDataObject(), 0x0FC1BF40); + return; + } + + float scale = 2.23699f; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + scale = 3.6f; + } + + FEPrintf(Position, lbl_803E4CB4, PosNum); + FEPrintf(GetDataObject(), lbl_803E507C, Speed * scale); +} + +TollboothStat::~TollboothStat() {} + +void TollboothStat::Draw() { + FEPrintf(GetTitleObject(), lbl_803E4CB4, TollboothNum); + if (static_cast< float >(PosNum) == 1.0f) { + FEngSetLanguageHash(Position, 0x5D82DBA2); + FEngSetLanguageHash(GetDataObject(), 0x5D82DBA2); + return; + } + + if (Time.GetSeconds() == 0.0f) { + FEngSetLanguageHash(Position, 0x0FC1BF40); + FEngSetLanguageHash(GetDataObject(), 0x0FC1BF40); + return; + } + + char text[32]; + FEPrintf(Position, lbl_803E4CB4, PosNum); + Time.PrintToString(text, 0); + FEPrintf(GetDataObject(), lbl_803E5084, text); +} + +StatsPanel::StatsPanel() + : TheStats() // + , iWidgetToAdd(1) // + , RacerName(lbl_803E43DC) // + , ParentPkg(lbl_803E43DC) {} + +void StatsPanel::Reset() { + TheStats.DeleteAllElements(); + iWidgetToAdd = 1; +} + +void StatsPanel::Draw(unsigned int numPlayers) { + if (numPlayers > 1 && RacerName != nullptr && bStrCmp(RacerName, lbl_803E43DC) != 0) { + if (!FEngIsScriptSet(ParentPkg, 0x8A41F5B9, 0x5079C8F8)) { + FEngSetScript(ParentPkg, 0x8A41F5B9, 0x5079C8F8, true); + } + + FEngSetButtonTexture(FEngFindImage(ParentPkg, 0x5BC), 0x5BC); + FEngSetButtonTexture(FEngFindImage(ParentPkg, 0x682), 0x682); + FEPrintf(ParentPkg, 0xEB43CCB0, lbl_803E4CF0, RacerName); + } else if (!FEngIsScriptSet(ParentPkg, 0x8A41F5B9, 0x0016A259)) { + FEngSetScript(ParentPkg, 0x8A41F5B9, 0x0016A259, true); + } + + { + FEWidget *widgey = TheStats.GetHead(); + while (widgey != TheStats.EndOfList()) { + widgey->Draw(); + widgey = widgey->GetNext(); + } + } +} + +void StatsPanel::AddStat(RaceStat *stat) { + FEngSetScript(ParentPkg, FEngHashString(lbl_803E5DB0, iWidgetToAdd), 0x001744B3, true); + TheStats.AddTail(stat); + ++iWidgetToAdd; +} + +void StatsPanel::AddInfoStat(unsigned int title, unsigned int info) { + FEngSetScript(ParentPkg, FEngHashString(lbl_803E5DB0, iWidgetToAdd), 0x001744B3, true); + InfoStat *stat = new ("", 0) InfoStat( + GetCurrentString(lbl_803E5DCC), + GetCurrentString(lbl_803E5E24), + title, + info); + TheStats.AddTail(stat); + ++iWidgetToAdd; +} + +void StatsPanel::AddGenericStat(float stat_data, unsigned int title_hash, unsigned int units_hash, const char *format) { + FEngSetScript(ParentPkg, FEngHashString(lbl_803E5DB0, iWidgetToAdd), 0x001744B3, true); + GenericStat *stat = new ("", 0) GenericStat( + GetCurrentString(lbl_803E5DCC), + GetCurrentString(lbl_803E5E24), + stat_data, + title_hash, + units_hash, + format); + TheStats.AddTail(stat); + ++iWidgetToAdd; +} + +void StatsPanel::AddTimerStat(float seconds, unsigned int title_hash) { + FEngSetScript(ParentPkg, FEngHashString(lbl_803E5DB0, iWidgetToAdd), 0x001744B3, true); + TimerStat *stat = new ("", 0) TimerStat( + GetCurrentString(lbl_803E5DCC), + GetCurrentString(lbl_803E5E24), + seconds, + title_hash); + TheStats.AddTail(stat); + ++iWidgetToAdd; +} + +PostRaceResultsScreen::PostRaceResultsScreen(ScreenConstructorData *sd) + : MenuScreen(sd) // + , RacerStats() // + , RaceResults() // + , mNumberOfRacers(GRaceStatus::Get().GetRacerCount()) // + , mIndexOfWinner(-1) // + , mIndexOfCurrentRacer(-1) // + , mNumberOfLaps(GRaceStatus::Get().GetRaceParameters()->GetNumLaps()) // + , mNumberOfStats(0) // + , mRaceType(GRaceStatus::Get().GetRaceType()) // + , mPostRaceScreenMode(POSTRACESCREENMODE_RESULTS) // + , mPlayerRacerInfo(nullptr) // + , mMaxSlotsLeftSide(11) // + , m_RaceButtonHash(0x5CED1D04) // + , m_raceResultsUploaded(false) { + bEnableEAMessenger = false; + + if (mRaceType == GRace::kRaceType_Tollbooth) { + mPostRaceScreenMode = POSTRACESCREENMODE_LAPSTATS; + } + + for (int i = 0; i < mNumberOfRacers; ++i) { + GRacerInfo *info = &GRaceStatus::Get().GetRacerInfo(i); + + if (info->GetSimable() != nullptr && mIndexOfCurrentRacer == -1 && info->GetSimable()->IsPlayer()) { + mPlayerRacerInfo = info; + mIndexOfCurrentRacer = i; + break; + } + } + + for (int i = 0; i < 16; ++i) { + RacerStats[i].SetParentPkg(GetPackageName()); + } + RaceResults.SetParentPkg(GetPackageName()); + + Setup(); +} + +PostRaceResultsScreen::~PostRaceResultsScreen() { +} + +void PostRaceResultsScreen::Setup() { + for (int i = 0; i < mNumberOfRacers; ++i) { + GRacerInfo *info = &GRaceStatus::Get().GetRacerInfo(i); + + if (info->IsFinishedRacing() && info->GetRanking() == 1) { + mIndexOfWinner = i; + break; + } + } + + for (int i = 0; i < mMaxSlotsLeftSide;) { + int slot = i + 1; + FEngSetScript(GetPackageName(), FEngHashString(lbl_803E5DB0), 0x0016A259, true); + + if (mPostRaceScreenMode == POSTRACESCREENMODE_STATS || + (mPostRaceScreenMode == POSTRACESCREENMODE_LAPSTATS && mRaceType == GRace::kRaceType_Tollbooth)) { + FEngSetInvisible(FEngFindObject(GetPackageName(), FEngHashString(lbl_803E5E04, slot))); + } else { + FEngSetVisible(FEngFindObject(GetPackageName(), FEngHashString(lbl_803E5E04, slot))); + } + + i = slot; + } + + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x586AB4A6)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x44AC8987)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x30EE5E68)); + + switch (mPostRaceScreenMode) { + case POSTRACESCREENMODE_RESULTS: + RaceResults.Reset(); + SetupResults(); + RaceResults.Draw(mNumberOfRacers); + break; + case POSTRACESCREENMODE_STATS: + RacerStats[mIndexOfCurrentRacer].Reset(); + SetupRacerStats(mIndexOfCurrentRacer, &GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer)); + RacerStats[mIndexOfCurrentRacer].Draw(mNumberOfRacers); + break; + case POSTRACESCREENMODE_LAPSTATS: + RacerStats[mIndexOfCurrentRacer].Reset(); + SetupLapStats(mIndexOfCurrentRacer, &GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer)); + RacerStats[mIndexOfCurrentRacer].Draw(mNumberOfRacers); + break; + default: + break; + } + + unsigned int fe_flags = FEDatabase->GetGameMode(); + + if ((fe_flags & 8) == 0) { + if ((fe_flags & 0x40) == 0 && + !FEngIsScriptSet(GetPackageName(), 0x445A862B, 0x5079C8F8)) { + FEngSetScript(GetPackageName(), 0x445A862B, 0x5079C8F8, true); + } + } +} + +void PostRaceResultsScreen::SetupResults() { + FEngSetVisible(GetPackageName(), 0x586AB4A6); + FEngSetVisible(GetPackageName(), 0x44AC8987); + FEngSetVisible(GetPackageName(), 0x30EE5E68); + + if (mRaceType >= GRace::kRaceType_P2P) { + if (mRaceType < GRace::kRaceType_Tollbooth) { + FEngSetLanguageHash(GetPackageName(), 0x586AB4A6, 0x96B05F47); + FEngSetLanguageHash(GetPackageName(), 0x44AC8987, 0xCE678AD3); + FEngSetLanguageHash(GetPackageName(), 0x30EE5E68, 0xB67DA102); + } else if (mRaceType == GRace::kRaceType_SpeedTrap) { + FEngSetLanguageHash(GetPackageName(), 0x586AB4A6, 0x96B05F47); + FEngSetLanguageHash(GetPackageName(), 0x44AC8987, 0xCE678AD3); + FEngSetLanguageHash(GetPackageName(), 0x30EE5E68, 0x7540FB04); + } + } + + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0xFF115FFF); + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0xD0B8AA33); + + unsigned int speed_units = 0x8569AB44; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + speed_units = 0x8569A25F; + } + + mNumberOfStats = 0; + RaceResults.Reset(); + + if (mRaceType >= GRace::kRaceType_P2P) { + if (mRaceType <= GRace::kRaceType_Tollbooth) { + int place = 0; + if (place < mNumberOfRacers) { + do { + ++place; + int i = 0; + GRacerInfo *racer_info = nullptr; + + while (true) { + racer_info = &GRaceStatus::Get().GetRacerInfo(i); + if (racer_info->GetRanking() == place) { + break; + } + ++i; + } + + RaceResults.AddStat(new ("", 0) RaceResultStat( + FEngFindString(RaceResults.ParentPkg, + FEngHashString(lbl_803E5088, "COLUMN2_DATA", RaceResults.iWidgetToAdd)), + FEngFindString(RaceResults.ParentPkg, + FEngHashString(lbl_803E5088, "COLUMN3_DATA", RaceResults.iWidgetToAdd)), + FEngFindString(RaceResults.ParentPkg, + FEngHashString(lbl_803E5088, "COLUMN1_DATA", RaceResults.iWidgetToAdd)), + racer_info)); + } while (place < mNumberOfRacers); + } + } else if (mRaceType == GRace::kRaceType_SpeedTrap) { + int place = 0; + if (place < mNumberOfRacers) { + do { + ++place; + int i = 0; + GRacerInfo *racer_info = nullptr; + + while (true) { + racer_info = &GRaceStatus::Get().GetRacerInfo(i); + if (racer_info->GetRanking() == place) { + break; + } + ++i; + } + + float speed = racer_info->GetPointTotal(); + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 0) { + speed = (speed * lbl_803E5E4C) * lbl_803E5E50; + } + + RaceResults.AddStat(new ("", 0) GenericResult( + FEngFindString(RaceResults.ParentPkg, + FEngHashString(lbl_803E5088, "COLUMN2_DATA", RaceResults.iWidgetToAdd)), + FEngFindString(RaceResults.ParentPkg, + FEngHashString(lbl_803E5088, "COLUMN3_DATA", RaceResults.iWidgetToAdd)), + FEngFindString(RaceResults.ParentPkg, + FEngHashString(lbl_803E5088, "COLUMN1_DATA", RaceResults.iWidgetToAdd)), + speed_units, + speed, + "%$0.0f", + racer_info)); + } while (place < mNumberOfRacers); + } + } + } +} + +void PostRaceResultsScreen::SetupStat_NosUsed() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + unsigned int mass_units = 0xC173E1BB; + float nos = *reinterpret_cast< const float * >(reinterpret_cast< const char * >(&racerInfo) + 0x11C); + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + mass_units = 0xC173DE1F; + nos *= lbl_803E5E54; + } + + if (GRaceStatusGetRaceTimeElapsed(&GRaceStatus::Get()) > lbl_803E5E58 && GRacerInfoAreStatsReady(&racerInfo)) { + RacerStats[mIndexOfCurrentRacer].AddGenericStat(nos, 0x114E759F, mass_units, lbl_803E5E44); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0x114E759F, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_TopSpeed() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + unsigned int speedUnits = 0x8569AB44; + float speed = *reinterpret_cast< const float * >(reinterpret_cast< const char * >(&racerInfo) + 0x114) * lbl_803E5E5C; + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + speedUnits = 0x8569A25F; + speed *= lbl_803E5E60; + } + + if (GRaceStatusGetRaceTimeElapsed(&GRaceStatus::Get()) > lbl_803E5E64 && GRacerInfoAreStatsReady(&racerInfo)) { + RacerStats[mIndexOfCurrentRacer].AddGenericStat(speed, 0x0EF34382, speedUnits, lbl_803E5E44); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0x0EF34382, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_AverageSpeed() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + unsigned int speedUnits = 0x8569AB44; + float speed = GRacerInfoCalcAverageSpeed(&racerInfo) * lbl_803E5E68; + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + speedUnits = 0x8569A25F; + speed *= lbl_803E5E6C; + } + + if (GRaceStatusGetRaceTimeElapsed(&GRaceStatus::Get()) > lbl_803E5E70 && GRacerInfoAreStatsReady(&racerInfo)) { + RacerStats[mIndexOfCurrentRacer].AddGenericStat(speed, 0x57F4140A, speedUnits, lbl_803E5E44); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0x57F4140A, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_TimeBehind() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + + if (mIndexOfWinner >= 0 && mIndexOfWinner != mIndexOfCurrentRacer) { + GRacerInfo &winnerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfWinner); + float winnerTime = + reinterpret_cast< const GTimer * >(reinterpret_cast< const char * >(&winnerInfo) + 0x160)->GetTime(); + float racerTime = + reinterpret_cast< const GTimer * >(reinterpret_cast< const char * >(&racerInfo) + 0x160)->GetTime(); + + RacerStats[mIndexOfCurrentRacer].AddTimerStat(bAbs(winnerTime - racerTime), 0xAB44ED8B); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0xAB44ED8B, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_LapVariance() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + + if (*reinterpret_cast< const int * >(reinterpret_cast< const char * >(&racerInfo) + 0x3C) > 1) { + float bestLapTime = GRaceStatus::Get().GetBestLapTime(mIndexOfCurrentRacer); + float worstLapTime = GRaceStatus::Get().GetWorstLapTime(mIndexOfCurrentRacer); + + RacerStats[mIndexOfCurrentRacer].AddTimerStat(worstLapTime - bestLapTime, 0x4121E8C4); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0x4121E8C4, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_StageVariance() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); +} + +void PostRaceResultsScreen::SetupStat_TrafficCollisions() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + + if (GRaceStatusGetRaceTimeElapsed(&GRaceStatus::Get()) > lbl_803E5E74) { + RacerStats[mIndexOfCurrentRacer].AddGenericStat( + static_cast< float >(*reinterpret_cast< const int * >(reinterpret_cast< const char * >(&racerInfo) + 0x12C)), + 0x094BFDFC, 0, lbl_803E5E44); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0x094BFDFC, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_ZeroToSixty() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + unsigned int speedUnits = 0xCCBC22B3; + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + speedUnits = 0xB8CF16FC; + } + + if (*reinterpret_cast< const float * >(reinterpret_cast< const char * >(&racerInfo) + 0x138) > lbl_803E5E80 && + GRacerInfoAreStatsReady(&racerInfo)) { + RacerStats[mIndexOfCurrentRacer].AddTimerStat( + *reinterpret_cast< const float * >(reinterpret_cast< const char * >(&racerInfo) + 0x138), speedUnits); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(speedUnits, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_QuarterMile() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + unsigned int timeUnits = 0x49FD5DCB; + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + timeUnits = 0x1C6F2A82; + } + + if (*reinterpret_cast< const float * >(reinterpret_cast< const char * >(&racerInfo) + 0x13C) > lbl_803E5E84 && + GRacerInfoAreStatsReady(&racerInfo)) { + RacerStats[mIndexOfCurrentRacer].AddTimerStat( + *reinterpret_cast< const float * >(reinterpret_cast< const char * >(&racerInfo) + 0x13C), timeUnits); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(timeUnits, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_PerfectShifts() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + + if (GRaceStatusGetRaceTimeElapsed(&GRaceStatus::Get()) > lbl_803E5E88 && GRacerInfoAreStatsReady(&racerInfo)) { + RacerStats[mIndexOfCurrentRacer].AddGenericStat( + static_cast< float >(*reinterpret_cast< const int * >(reinterpret_cast< const char * >(&racerInfo) + 0x128)), + 0x680AC597, 0, lbl_803E5E44); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0x680AC597, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_AccumulatedSpeed() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + unsigned int speedUnits = 0x8569A25F; + float speed = *reinterpret_cast< const float * >(reinterpret_cast< const char * >(&racerInfo) + 0x134); + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 0) { + speedUnits = 0x8569AB44; + speed = (speed * lbl_803E5E98) * lbl_803E5E9C; + } + + if (GRaceStatusGetRaceTimeElapsed(&GRaceStatus::Get()) > lbl_803E5EA0) { + RacerStats[mIndexOfCurrentRacer].AddGenericStat(speed, 0xD57E02AB, speedUnits, lbl_803E5E44); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0xD57E02AB, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_SpeedVariance() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + float bestSpeed = GRaceStatus::Get().GetBestSpeedTrapSpeed(mIndexOfCurrentRacer); + float worstSpeed = GRaceStatus::Get().GetWorstSpeedTrapSpeed(mIndexOfCurrentRacer); + + if (bestSpeed > 0.0f && worstSpeed > 0.0f) { + unsigned int speed_units = 0x8569A25F; + float speed = worstSpeed - bestSpeed; + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 0) { + speed_units = 0x8569AB44; + speed = MPS2MPH(KPH2MPS(speed)); + } + + RacerStats[mIndexOfCurrentRacer].AddGenericStat(speed, 0x6EEABE8C, speed_units, "%.2f"); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0x6EEABE8C, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupStat_SpeedBehind() { + GRacerInfo &racerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfCurrentRacer); + + if (mIndexOfWinner >= 0) { + unsigned int speed_units = 0x8569A25F; + GRacerInfo &winnerInfo = GRaceStatus::Get().GetRacerInfo(mIndexOfWinner); + float speed = bAbs(winnerInfo.GetPointTotal() - racerInfo.GetPointTotal()); + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 0) { + speed_units = 0x8569AB44; + speed = (speed * lbl_803E5EB0) * lbl_803E5EB4; + } + + RacerStats[mIndexOfCurrentRacer].AddGenericStat(speed, 0x2E54B7ED, speed_units, lbl_803E5E44); + return; + } + + RacerStats[mIndexOfCurrentRacer].AddInfoStat(0x2E54B7ED, 0x0FC1BF40); +} + +void PostRaceResultsScreen::SetupRacerStats(int index, GRacerInfo *racer_info) { + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0x4E706980); + switch (mRaceType) { + case GRace::kRaceType_P2P: + case GRace::kRaceType_Drag: + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0x1135F776); + break; + case GRace::kRaceType_Circuit: + case GRace::kRaceType_Knockout: + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0x7B8F45DF); + break; + case GRace::kRaceType_SpeedTrap: + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0xAEF51E9D); + break; + case GRace::kRaceType_Tollbooth: + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0xAC23368C); + break; + default: + break; + } + + RacerStats[index].SetRacerName(racer_info->GetName()); + + switch (mRaceType) { + case GRace::kRaceType_P2P: + SetupStat_TopSpeed(); + SetupStat_AverageSpeed(); + if (FEDatabase->IsLANMode()) { + SetupStat_NosUsed(); + } else if (FEDatabase->IsOnlineMode()) { + SetupStat_NosUsed(); + } else { + SetupStat_TimeBehind(); + SetupStat_TrafficCollisions(); + } + SetupStat_StageVariance(); + break; + case GRace::kRaceType_Circuit: + case GRace::kRaceType_Knockout: + SetupStat_TopSpeed(); + SetupStat_AverageSpeed(); + if (FEDatabase->IsLANMode()) { + SetupStat_NosUsed(); + } else if (FEDatabase->IsOnlineMode()) { + SetupStat_NosUsed(); + } else { + SetupStat_TimeBehind(); + SetupStat_LapVariance(); + SetupStat_TrafficCollisions(); + } + break; + case GRace::kRaceType_Drag: + SetupStat_ZeroToSixty(); + SetupStat_QuarterMile(); + SetupStat_PerfectShifts(); + if (FEDatabase->IsLANMode()) { + SetupStat_NosUsed(); + } else if (FEDatabase->IsOnlineMode()) { + SetupStat_NosUsed(); + } else { + SetupStat_TimeBehind(); + SetupStat_TrafficCollisions(); + } + break; + case GRace::kRaceType_Tollbooth: + SetupStat_AccumulatedSpeed(); + SetupStat_SpeedVariance(); + SetupStat_SpeedBehind(); + break; + case GRace::kRaceType_SpeedTrap: + SetupStat_TopSpeed(); + SetupStat_AverageSpeed(); + SetupStat_StageVariance(); + SetupStat_TrafficCollisions(); + break; + default: + break; + } +} + +void PostRaceResultsScreen::SetupLapStats(int racerIndex, GRacerInfo *racer_info) { + FEObject *obj = nullptr; + + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0x8159A0B2); + switch (mRaceType) { + case GRace::kRaceType_P2P: + case GRace::kRaceType_Drag: + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0x34BA50FF); + obj = FEngFindObject(GetPackageName(), 0x586AB4A6); + FEngSetVisible(obj); + obj = FEngFindObject(GetPackageName(), 0x44AC8987); + FEngSetVisible(obj); + obj = FEngFindObject(GetPackageName(), 0x30EE5E68); + FEngSetVisible(obj); + FEngSetLanguageHash(GetPackageName(), 0x586AB4A6, 0xE8B7D527); + FEngSetLanguageHash(GetPackageName(), 0x44AC8987, 0x96B05F47); + FEngSetLanguageHash(GetPackageName(), 0x30EE5E68, 0xB67DA102); + break; + case GRace::kRaceType_Circuit: + case GRace::kRaceType_Knockout: + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0x9C8D7FE8); + obj = FEngFindObject(GetPackageName(), 0x586AB4A6); + FEngSetVisible(obj); + obj = FEngFindObject(GetPackageName(), 0x44AC8987); + FEngSetVisible(obj); + obj = FEngFindObject(GetPackageName(), 0x30EE5E68); + FEngSetVisible(obj); + FEngSetLanguageHash(GetPackageName(), 0x586AB4A6, 0x0000BF9C); + FEngSetLanguageHash(GetPackageName(), 0x44AC8987, 0x96B05F47); + FEngSetLanguageHash(GetPackageName(), 0x30EE5E68, 0xB67DA102); + break; + case GRace::kRaceType_SpeedTrap: + if (GRaceStatus::Exists()) { + GRaceStatus::Get().SortCheckPointRankings(); + } + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0xECD0E6A6); + obj = FEngFindObject(GetPackageName(), 0x586AB4A6); + FEngSetVisible(obj); + obj = FEngFindObject(GetPackageName(), 0x44AC8987); + FEngSetVisible(obj); + obj = FEngFindObject(GetPackageName(), 0x30EE5E68); + FEngSetVisible(obj); + FEngSetLanguageHash(GetPackageName(), 0x586AB4A6, 0xEE1EDC76); + FEngSetLanguageHash(GetPackageName(), 0x44AC8987, 0x96B05F47); + FEngSetLanguageHash(GetPackageName(), 0x30EE5E68, 0x7540FB04); + break; + case GRace::kRaceType_Tollbooth: + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0xD10A8EA2); + obj = FEngFindObject(GetPackageName(), 0x586AB4A6); + FEngSetVisible(obj); + obj = FEngFindObject(GetPackageName(), 0x30EE5E68); + FEngSetVisible(obj); + FEngSetLanguageHash(GetPackageName(), 0x586AB4A6, 0xA15E4505); + FEngSetLanguageHash(GetPackageName(), 0x30EE5E68, 0xB67DA102); + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0xD0B8AA33); + break; + default: + break; + } + + StatsPanel &panel = RacerStats[racerIndex]; + panel.RacerName = racer_info->GetName(); + GRaceStatus &race_status = GRaceStatus::Get(); + + switch (mRaceType) { + case GRace::kRaceType_P2P: + case GRace::kRaceType_Drag: { +#ifdef EA_BUILD_A124 + const float split_times[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + const int split_rankings[4] = {0, 0, 0, 0}; +#else + const float *split_times = racer_info->GetSplitTimes(); + const int *split_rankings = racer_info->GetSplitRankings(); +#endif + + for (int i = 0; i < 4; ++i) { + RacerStats[racerIndex].AddStat(new ("", 0) + StageStat(FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DCC, + RacerStats[racerIndex].RacerName)), + FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DDC, + RacerStats[racerIndex].RacerName)), + FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5E24, + RacerStats[racerIndex].RacerName)), + i, + split_times[i], + split_rankings[i])); + } + + float final_time = 0.0f; + if (racer_info->IsFinishedRacing()) { + final_time = racer_info->GetRaceTimer().GetTime(); + } + + panel.AddStat(new ("", 0) + StageStat(FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DCC, panel.RacerName)), + FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DDC, panel.RacerName)), + FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5E24, panel.RacerName)), + 4, + final_time, + racer_info->GetRanking())); + break; + } + case GRace::kRaceType_Circuit: + case GRace::kRaceType_Knockout: { + for (int i = 0; i < race_status.GetRaceParameters()->GetNumLaps(); ++i) { + int lap_position = race_status.GetLapPosition(i, racerIndex, true); + + if (racer_info->GetIsKnockedOut() && lap_position < 2) { + lap_position = -1; + } + + RacerStats[racerIndex].AddStat(new ("", 0) + LapStat(FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DCC, + RacerStats[racerIndex].RacerName)), + FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DDC, + RacerStats[racerIndex].RacerName)), + FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5E24, + RacerStats[racerIndex].RacerName)), + i + 1, + race_status.GetLapTime(i, racerIndex, false), lap_position)); + } + break; + } + case GRace::kRaceType_SpeedTrap: { + int num_traps = race_status.GetNumSpeedTraps(); + + for (int i = 0; i < num_traps; ++i) { + panel.AddStat(new ("", 0) + SpeedStat(FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DCC, panel.RacerName)), + FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DDC, panel.RacerName)), + FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5E24, panel.RacerName)), + i + 1, + race_status.GetRaceSpeedTrapSpeed(i, racerIndex), + race_status.GetRaceSpeedTrapPosition(i, racerIndex))); + } + break; + } + case GRace::kRaceType_Tollbooth: { + int num_booths = race_status.GetNumTollbooths(); + + for (int i = 0; i < num_booths; ++i) { + RacerStats[racerIndex].AddStat(new ("", 0) + TollboothStat(FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DCC, + RacerStats[racerIndex].RacerName)), + FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DDC, + RacerStats[racerIndex].RacerName)), + FEngFindString(RacerStats[racerIndex].ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5E24, + RacerStats[racerIndex].RacerName)), + i + 1, + race_status.GetRaceTollboothTime(i, racerIndex), 1)); + } + + float remaining_time = 0.0f; + if (racer_info->IsFinishedRacing()) { + remaining_time = race_status.GetRaceTimeRemaining(); + } + + panel.AddStat(new ("", 0) + TollboothStat(FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DCC, panel.RacerName)), + FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5DDC, panel.RacerName)), + FEngFindString(panel.ParentPkg, + FEngHashString(lbl_803E5088, lbl_803E5E24, panel.RacerName)), + num_booths + 1, + remaining_time, + 1)); + break; + } + default: + break; + } +} + +void PostRaceResultsScreen::NotificationMessage(unsigned long msg, FEObject *pObject, unsigned long Param1, + unsigned long Param2) { + switch (msg) { + case 0x35F8620B: { + if (!FEDatabase->IsLANMode()) { + if (!FEDatabase->IsOnlineMode()) { + return; + } + } + + FEngSetScript(GetPackageName(), 0x812A09D4, 0x0016A259, true); + FEngSetScript(GetPackageName(), 0x05D85A9F, 0x5079C8F8, true); + return; + } + case 0x5073EF13: + if (mPostRaceScreenMode == POSTRACESCREENMODE_RESULTS) { + return; + } + + --mIndexOfCurrentRacer; + if (mIndexOfCurrentRacer < 0) { + mIndexOfCurrentRacer = mNumberOfRacers - 1; + } + + Setup(); + return; + case 0xD9FEEC59: + if (mPostRaceScreenMode == POSTRACESCREENMODE_RESULTS) { + return; + } + + ++mIndexOfCurrentRacer; + if (mIndexOfCurrentRacer >= mNumberOfRacers) { + mIndexOfCurrentRacer = 0; + } + + Setup(); + return; + case 0xC519BFC3: + if (mRaceType == GRace::kRaceType_Tollbooth) { + if (mPostRaceScreenMode == POSTRACESCREENMODE_LAPSTATS) { + mPostRaceScreenMode = POSTRACESCREENMODE_STATS; + } else { + mPostRaceScreenMode = POSTRACESCREENMODE_LAPSTATS; + } + } else { + mPostRaceScreenMode = static_cast< PostRaceScreenMode >(mPostRaceScreenMode + 1); + if (mPostRaceScreenMode == POSTRACESCREENMODE_NUMMODES) { + mPostRaceScreenMode = POSTRACESCREENMODE_RESULTS; + } + } + + Setup(); + return; + case 0xC519BFC4: { + if (FEDatabase->IsLANMode()) { + return; + } + + if (FEDatabase->IsOnlineMode()) { + return; + } + + DialogInterface::ShowTwoButtons(GetPackageName(), lbl_803E5EEC, static_cast< eDialogTitle >(1), 0x417B2601, + 0x1A294DAD, 0xE1A57D51, 0xB4623F67, 0xB4623F67, + static_cast< eDialogFirstButtons >(1), + static_cast< unsigned int >(0x4D3399A8)); + return; + } + case 0xE1A57D51: + new EUnPause(); + if (cFEng::Get()->IsPackagePushed(lbl_803E5F00)) { + PhotoFinishScreen::mRestartSelected = true; + return; + } + + new ERestartRace(); + return; + case 0xB4623F67: + cFEng::Get()->QueuePackageMessage(0xC6341FF6, GetPackageName(), nullptr); + return; + case 0x406415E3: { + if (FEngIsScriptSet(GetPackageName(), 0x57EFB2FB, 0x0016A259)) { + return; + } + + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career && + GRaceStatus::Get().GetRaceParameters()->GetIsBossRace()) { + bool playerDone = false; + + if (mPlayerRacerInfo != nullptr && + (mPlayerRacerInfo->IsFinishedRacing() || mPlayerRacerInfo->GetIsTotalled() || + mPlayerRacerInfo->GetIsEngineBlown() || mPlayerRacerInfo->GetIsKnockedOut() || + mPlayerRacerInfo->GetIsBusted())) { + playerDone = true; + } + + if (playerDone && mPlayerRacerInfo->GetRanking() != 1) { + DialogInterface::ShowTwoButtons(GetPackageName(), lbl_803E5EEC, static_cast< eDialogTitle >(1), + 0x417B2601, 0x1A294DAD, 0x30ED2368, 0xB4623F67, 0xB4623F67, + static_cast< eDialogFirstButtons >(1), + static_cast< unsigned int >(0x9887EB98)); + return; + } + } + + goto set_continue_script; + } + case 0x30ED2368: + set_continue_script: + if (!FEngIsScriptSet(GetPackageName(), 0x47FF4E7C, 0x001335F0)) { + FEngSetScript(GetPackageName(), 0x47FF4E7C, 0x001335F0, true); + } + return; + case 0xE1FDE1D1: { + if (FEDatabase->IsLANMode()) { + return; + } + + if (FEDatabase->IsOnlineMode()) { + return; + } + + if (cFEng::Get()->IsPackagePushed(lbl_803E5F00)) { + cFEng::Get()->QueuePackagePop(1); + if (cFEng::Get()->IsPackagePushed(lbl_803E5F18)) { + cFEng::Get()->QueuePackagePop(1); + } + return; + } + + { + bool playerDone = false; + if (mPlayerRacerInfo != nullptr && + (mPlayerRacerInfo->IsFinishedRacing() || mPlayerRacerInfo->GetIsTotalled() || + mPlayerRacerInfo->GetIsEngineBlown() || mPlayerRacerInfo->GetIsKnockedOut() || + mPlayerRacerInfo->GetIsBusted())) { + playerDone = true; + } + + GRaceParameters *parms = GRaceStatus::Get().GetRaceParameters(); + bool ddayRace = false; + if (parms != nullptr && parms->GetIsDDayRace()) { + ddayRace = true; + } + + if (GRaceStatus::Exists() && GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career && + !ddayRace) { + if (playerDone) { + GRaceStatus::Get().RaceAbandoned(); + + { + MNotifyRaceAbandoned abandoned; + abandoned.Post(MNotifyRaceAbandoned::_GetKind()); + } + } + + new EUnPause(); + return; + } + + if (playerDone) { + new EQuitToFE(GARAGETYPE_MAIN_FE, nullptr); + } else { + new EUnPause(); + } + } + return; + } + default: + return; + } +} + +eMenuSoundTriggers PostRaceResultsScreen::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (msg != 0x7B6B89D7) { + if (msg < 0x7B6B89D8) { + if (msg != 0x4A805994) { + return maybe; + } + } else if (msg != 0x9AFA53A7) { + return maybe; + } + + if (mNumberOfRacers < 2 || mPostRaceScreenMode == POSTRACESCREENMODE_RESULTS) { + return static_cast< eMenuSoundTriggers >( -1 ); + } + } + + if (FEngIsScriptSet(GetPackageName(), 0x57EFB2FB, 0x0016A259)) { + return static_cast< eMenuSoundTriggers >( -1 ); + } + + return maybe; +} + +static MenuScreen *CreatePostRaceResultsScreen(ScreenConstructorData *sd) { + return new ("", 0) PostRaceResultsScreen(sd); +} + +// Range: 0x80155F40 -> 0x801560EC +void PursuitData::PopulateData(IPursuit *ipursuit, IPerpetrator *iperpetrator, int exitToSafehouse) { + mPursuitIsActive = ipursuit && ipursuit->IsPursuitBailed() == false; + + if (ipursuit) { + mPursuitLength = ipursuit->GetPursuitDuration(); + mNumCopsDamaged = ipursuit->GetNumCopsDamaged(); + mNumCopsDestroyed = ipursuit->GetNumCopsDestroyed(); + mNumSpikeStripsDodged = ipursuit->GetNumSpikeStripsDodged(); + mNumRoadblocksDodged = ipursuit->GetNumRoadblocksDodged(); + mCostToStateAchieved = ipursuit->CalcTotalCostToState(); + } + + if (iperpetrator) { + if (iperpetrator->GetPendingRepPointsNormal() > 0) { + mRepAchievedNormal = iperpetrator->GetPendingRepPointsNormal(); + } + if (iperpetrator->GetPendingRepPointsFromCopDestruction() > 0) { + mRepAchievedCopDestruction = iperpetrator->GetPendingRepPointsFromCopDestruction(); + } + } + + if (exitToSafehouse >= 0) { + mExitToSafehouse = exitToSafehouse; + } +} + +bool PursuitData::AddMilestone(GMilestone *milestone) { + if (mNumMilestonesThisPursuit > 0x1f) { + return false; + } + mMilestonesCompleted[mNumMilestonesThisPursuit] = milestone; + mNumMilestonesThisPursuit = mNumMilestonesThisPursuit + 1; + return true; +} + +const GMilestone *const PursuitData::GetMilestone(int index) const { + if (index > 0x1F) { + return 0; + } + return mMilestonesCompleted[index]; +} + +void PursuitData::ClearData() { + mPursuitIsActive = false; + mPursuitLength = 0.0f; + mNumCopsDamaged = 0; + mNumCopsDestroyed = 0; + mNumSpikeStripsDodged = 0; + mNumRoadblocksDodged = 0; + mCostToStateAchieved = 0; + mRepAchievedNormal = 0; + mRepAchievedCopDestruction = 0; + mNumMilestonesThisPursuit = 0; + for (int i = 0; i <= 0x1F; i++) { + mMilestonesCompleted[i] = nullptr; + } +} + +void InfoStat::Draw() { + FEngSetLanguageHash(GetTitleObject(), TitleHash); + FEngSetLanguageHash(GetDataObject(), InfoHash); +} + +void GenericStat::Draw() { + char text[0x20]; + FEngSetLanguageHash(GetTitleObject(), TitleHash); + bSNPrintf(text, 0x20, Format, StatData); + if (UnitsHash != 0) { + FEString *data = GetDataObject(); + const char *units = GetLocalizedString(UnitsHash); + FEPrintf(data, "%s %s", text, units); + } else { + FEPrintf(GetDataObject(), lbl_803E4CF0, text); + } +} + +void TimerStat::Draw() { + char text[0x20]; + FEngSetLanguageHash(GetTitleObject(), TitleHash); + Seconds.PrintToString(text, 0); + FEPrintf(GetDataObject(), lbl_803E4CF0, text); +} + +void GenericResult::Draw() { + char text[0x20]; + + FEPrintf(GetTitleObject(), lbl_803E4CF0, RacerInfo->GetName()); + + if (!RacerInfo->IsFinishedRacing()) { + bool showData = false; + if (GRaceStatus::Exists()) { + if (GRaceStatus::Get().GetRaceType() == GRace::kRaceType_SpeedTrap) { + showData = true; + } + } + if (!showData) { + FEngSetLanguageHash(GetDataObject(), 0x0FC1BF40); + goto show_position; + } + } + + bSNPrintf(text, 0x20, Format, FData); + if (UnitsHash != 0) { + FEString *data = GetDataObject(); + const char *units = GetLocalizedString(UnitsHash); + FEPrintf(data, "%s %s", text, units); + } else { + FEPrintf(GetDataObject(), lbl_803E4CF0, text); + } + +show_position: + FEPrintf(Position, lbl_803E4CB4, RacerInfo->GetRanking()); +} + +FEImage *FEngFindImage(const char *pkg_name, int name_hash); + +PostRaceMilestonesScreen::PostRaceMilestonesScreen(ScreenConstructorData *sd) + : MenuScreen(sd) // + , mBountyEarned(0.0f) // + , mCurrMilestoneIndex(-1) // + , mCurrMilestoneScriptHash(0) // + , mCopDestructionBountyShown(false) // +{ + mpDataBigIcon = FEngFindImage(GetPackageName(), 0x14564FB9); +} + +PostRaceMilestonesScreen::~PostRaceMilestonesScreen() {} + +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +extern unsigned long FEHashUpper(const char *str); +extern const char *GetTranslatedString(unsigned int hash); +int FEPrintf(const char *pkg_name, int object_hash, const char *fmt, ...); + +void PostRaceMilestonesScreen::StartMilestoneDoneAnimations() { + FEngSetScript(mpDataBigIcon, 0x16a259, true); + FEngSetScript(GetPackageName(), 0xe526d0d2, 0x33113ac, true); + FEngSetScript(GetPackageName(), 0xe1045a4f, 0x33113ac, true); + FEngSetScript(GetPackageName(), 0x962b9c62, FEHashUpper("POS2"), true); + FEngSetScript(GetPackageName(), 0xec85c7e4, FEHashUpper("POS2"), true); +} + +void PostRaceMilestonesScreen::StartAnimations(bool isMilestone, int typeKey, float bountyEarned, const char *const descriptionStr) { + mBountyEarned += bountyEarned; + SetMilestoneAnimationScriptHash(isMilestone, typeKey); + FEngSetTextureHash(mpDataBigIcon, FEDatabase->GetMilestoneIconHash(typeKey, isMilestone)); + FEPrintf(GetPackageName(), 0xe526d0d2, "%s", descriptionStr); + if (bountyEarned > 0.0f) { + FEngSetVisible(GetPackageName(), 0xe1045a4f); + } else { + FEngSetInvisible(GetPackageName(), 0xe1045a4f); + } + FEPrintf(GetPackageName(), 0xe1045a4f, "%s: %$0.0f", GetTranslatedString(0x29b1b96a), bountyEarned); + FEPrintf(GetPackageName(), 0x324f7792, "%s: %$0.0f", GetTranslatedString(0x5ccf949a), mBountyEarned); + FEngSetScript(mpDataBigIcon, 0x5079c8f8, true); +} + +bool PostRaceMilestonesScreen::StartBountyAnimations(bool copDestruction) { + unsigned int key; + float bountyEarned; + char outputStr[64]; + if (!copDestruction) { + key = 0x33fa23a; + bountyEarned = static_cast(PostRacePursuitScreen::GetPursuitData().mRepAchievedNormal); + bSNPrintf(outputStr, 64, "%s", GetTranslatedString(0x4d64888d)); + } else { + key = 0x4fc942ca; + bountyEarned = static_cast(PostRacePursuitScreen::GetPursuitData().mRepAchievedCopDestruction); + bSNPrintf(outputStr, 64, "%s: %$d", GetTranslatedString(0x23f6e732), PostRacePursuitScreen::GetPursuitData().mNumCopsDestroyed); + } + StartAnimations(false, key, bountyEarned, outputStr); + return true; +} + +bool PostRaceMilestonesScreen::SetMilestoneAnimationScriptHash(bool isMilestone, int type) { + const char *posStr; + if (type == static_cast(0xFD989A3A)) { + posStr = "POS3"; + } else if (type > static_cast(0xFD989A3A)) { + if (type == 0x2377e50d) { + posStr = "POS1"; + } else if (type > 0x2377e50d) { + if (type == 0x4fc942ca) { + posStr = "POS00"; + } else if (type == 0x5392e4fd) { + posStr = "POS8"; + } else { + mCurrMilestoneScriptHash = 0; + goto done; + } + } else if (type == 0x33fa23a) { + posStr = isMilestone ? "POS7" : "POS0"; + } else { + mCurrMilestoneScriptHash = 0; + goto done; + } + } else { + if (type == static_cast(0xA61CAC24)) { + posStr = "POS2"; + } else if (type > static_cast(0xA61CAC24)) { + if (type == static_cast(0xCDF36FC2)) { + posStr = "POS5"; + } else if (type == static_cast(0xEB45F99D)) { + posStr = "POS4"; + } else { + mCurrMilestoneScriptHash = 0; + goto done; + } + } else { + if (type != static_cast(0x850A64BC)) { + mCurrMilestoneScriptHash = 0; + goto done; + } + posStr = "POS6"; + } + } + mCurrMilestoneScriptHash = FEHashUpper(posStr); +done: + return mCurrMilestoneScriptHash != 0; +} + +bool PostRaceMilestonesScreen::StartMilestoneAnimations() { + mCurrMilestoneIndex++; + const GMilestone *const milestone = PostRacePursuitScreen::GetPursuitData().GetMilestone(mCurrMilestoneIndex); + if (milestone) { + char descStr[32]; + char outputStr[64]; + FEDatabase->SetMilestoneDescriptionString( + descStr, milestone->GetTypeKey(), milestone->GetCurrentValue(), milestone->GetRequiredValue(), false); + bSNPrintf( + outputStr, 64, "%s: %s", GetTranslatedString(FEDatabase->GetMilestoneHeaderHash(milestone->GetLocalizationTag())), + descStr); + StartAnimations(true, milestone->GetTypeKey(), milestone->GetBounty(), outputStr); + return true; + } + + StartMilestoneDoneAnimations(); + return false; +} + +bool PostRaceMilestonesScreen::StartChallengeAnimations() { + mCurrMilestoneIndex++; + if (mCurrMilestoneIndex < 1 && GRaceStatus::Exists()) { + GRaceParameters *raceParams = GRaceStatus::Get().GetRaceParameters(); + if (raceParams && raceParams->GetIsPursuitRace() && !FEDatabase->IsFinalEpicChase()) { + float currVal = GManager::Get().GetBestValue(raceParams->GetChallengeType()); + float goalVal = raceParams->GetChallengeGoal(); + char descStr[32]; + char outputStr[64]; + FEDatabase->SetMilestoneDescriptionString(descStr, raceParams->GetChallengeType(), currVal, goalVal, false); + bSNPrintf( + outputStr, 64, "%s: %s", + GetTranslatedString(FEDatabase->GetChallengeHeaderHash(raceParams->GetLocalizationTag())), descStr); + StartAnimations( + true, raceParams->GetChallengeType(), static_cast(raceParams->GetReputation()), outputStr); + return true; + } + } + StartMilestoneDoneAnimations(); + return false; +} + +extern bool FEngIsScriptRunning(FEObject *obj, unsigned int script_hash); + +void PostRaceMilestonesScreen::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (msg == 0x35f8620b) { + StartBountyAnimations(false); + return; + } + + if (msg <= 0x35f8620a) { + if (msg != 0xd3c3de7) { + return; + } + + if (!mCopDestructionBountyShown) { + mCopDestructionBountyShown = true; + if (PostRacePursuitScreen::GetPursuitData().mNumCopsDestroyed > 0) { + StartBountyAnimations(true); + return; + } + } + + if (GRaceStatus::Exists() && GRaceStatus::Get().GetRaceParameters() && + GRaceStatus::Get().GetRaceParameters()->GetIsPursuitRace() && + !FEDatabase->IsFinalEpicChase()) { + StartChallengeAnimations(); + } else { + StartMilestoneAnimations(); + } + return; + } + + if (msg == 0x406415e3) { + cFEng::Get()->QueuePackagePop(1); + new EShowResults(FERESULTTYPE_PURSUIT, false); + return; + } + + if (msg != 0xc98356ba) { + return; + } + + if (FEngIsScriptSet(mpDataBigIcon, 0x5079c8f8) && + !FEngIsScriptRunning(mpDataBigIcon, 0x5079c8f8)) { + FEngSetScript(mpDataBigIcon, mCurrMilestoneScriptHash, true); + } +} + +PursuitResultsDatum::PursuitResultsDatum(PursuitResultsDatumType type, unsigned int itemName, float itemNumber, float itemGoal, PursuitResultsDatumCheckType itemChecked) + : ArrayDatum(0, 0) // + , mType(type) // + , mName(itemName) // + , mNumber(itemNumber >= 0.0f ? itemNumber : 0.0f) // + , mGoal(itemGoal) // + , mChecked(itemChecked) // +{} + +PursuitResultsArraySlot::PursuitResultsArraySlot(FEObject *obj, FEString *itemName, FEString *itemNumber, FEImage *itemChecked, FEImage *itemEmpty) + : ArraySlot(obj) // + , mLine(obj) // + , mItemName(itemName) // + , mItemNumber(itemNumber) // + , mItemChecked(itemChecked) // + , mItemEmpty(itemEmpty) // +{} + +void PursuitResultsArraySlot::Update(ArrayDatum *datum, bool isSelected) { + ArraySlot::Update(datum, isSelected); + if (!datum) { + return; + } + + PursuitResultsDatum *data = static_cast(datum); + + FEngSetScript(mItemChecked, 0x16A259, true); + FEngSetScript(mItemEmpty, 0x16A259, true); + FEngSetScript(mLine, 0x1744B3, true); + + if (mItemName) { + FEngSetScript(mItemName, 0x1744B3, true); + FEngSetLanguageHash(mItemName, data->mName); + } + + PursuitResultsDatum::PursuitResultsDatumType type = data->mType; + + if (type == PursuitResultsDatum::PursuitResultsDatumType_Number) { + FEPrintf(mItemNumber, lbl_803E4CB4, static_cast(data->mNumber)); + FEngSetScript(mItemNumber, 0x1744B3, true); + if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_On) { + FEngSetScript(mItemChecked, 0x1CA7C0, true); + } else if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_Greyed) { + FEngSetScript(mItemChecked, 0x163C76, true); + } else { + FEngSetScript(mItemChecked, 0x16A259, true); + } + } else if (type == PursuitResultsDatum::PursuitResultsDatumType_Time) { + Timer timer; + timer.SetTime(data->mNumber); + char text[32]; + timer.PrintToString(text, 0); + FEPrintf(mItemNumber, lbl_803E4CF0, text); + FEngSetScript(mItemNumber, 0x1744B3, true); + if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_On) { + FEngSetScript(mItemChecked, 0x1CA7C0, true); + } else if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_Greyed) { + FEngSetScript(mItemChecked, 0x163C76, true); + } else { + FEngSetScript(mItemChecked, 0x16A259, true); + } + } else if (type == PursuitResultsDatum::PursuitResultsDatumType_Milestone_Number) { + char text[32]; + FEDatabase->SetMilestoneDescriptionString(text, -1, data->mNumber, data->mGoal, false); + FEPrintf(mItemNumber, lbl_803E4CF0, text); + FEngSetScript(mItemNumber, 0x1744B3, true); + if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_On) { + FEngSetScript(mItemChecked, 0x1CA7C0, true); + } else if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_Greyed) { + FEngSetScript(mItemChecked, 0x163C76, true); + } else { + FEngSetScript(mItemChecked, 0x16A259, true); + } + } else if (type == PursuitResultsDatum::PursuitResultsDatumType_Milestone_Time) { + char text[32]; + FEDatabase->SetMilestoneDescriptionString(text, -1, data->mNumber, data->mGoal, true); + FEPrintf(mItemNumber, lbl_803E4CF0, text); + FEngSetScript(mItemNumber, 0x1744B3, true); + if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_On) { + FEngSetScript(mItemChecked, 0x1CA7C0, true); + } else if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_Greyed) { + FEngSetScript(mItemChecked, 0x163C76, true); + } else { + FEngSetScript(mItemChecked, 0x16A259, true); + } + } else if (type == PursuitResultsDatum::PursuitResultsDatumType_Milestone_Time_PursuitRemaining) { + FEngSetScript(mItemNumber, 0x16A259, true); + if (data->mChecked == PursuitResultsDatum::PursuitResultsDatumCheckType_On) { + FEngSetScript(mItemChecked, 0x163C76, true); + } else { + FEngSetScript(mItemChecked, 0x163C76, true); + } + } +} + +PostRacePursuitScreen::PostRacePursuitScreen(ScreenConstructorData *sd) + : ArrayScrollerMenu(sd, 1, 9, false) // + , mPostPursuitScreenMode(POSTPURSUITSCREENMODE_PURSUIT) // + , m_RaceButtonHash(0x5CED1D04) { + int i; + char sztemp[32]; + + i = 0; + while (i < GetHeight()) { + i++; + FEngSNPrintf(sztemp, 0x20, lbl_803E5DB0, i); + FEObject *wrapperGroup = FEngFindObject(GetPackageName(), FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 0x20, lbl_803E5F38, i); + FEString *itemName = FEngFindString(GetPackageName(), FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 0x20, lbl_803E5E04, i); + FEString *itemValue = FEngFindString(GetPackageName(), FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 0x20, lbl_803E5F48, i); + FEImage *checkMark = FEngFindImage(GetPackageName(), FEHashUpper(sztemp)); + FEngSNPrintf(sztemp, 0x20, lbl_803E5F50, i); + FEImage *emptyMark = FEngFindImage(GetPackageName(), FEHashUpper(sztemp)); + AddSlot(new ("", 0) PursuitResultsArraySlot(wrapperGroup, itemName, itemValue, checkMark, emptyMark)); + } + Initialize(); +} + +PostRacePursuitScreen::~PostRacePursuitScreen() { + if (GetPursuitData().mExitToSafehouse != 0) { + GetPursuitData().mExitToSafehouse = 0; + UCrc32 postCrc(0x20D60DBF); + MEnterSafeHouse msg(lbl_803E52A0); + msg.Post(postCrc); + } +} + +void PostRacePursuitScreen::Initialize() { + ClearData(); + if (mPostPursuitScreenMode == POSTPURSUITSCREENMODE_INFRACTIONS) { + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0xFB415E78); + if (TheGameFlowManager.IsInFrontend()) { + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0x7448870B); + } else { + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0x74413352); + if (GRaceStatus::Exists()) { + GRaceParameters *raceParams = GRaceStatus::Get().GetRaceParameters(); + if (raceParams && raceParams->GetIsPursuitRace() && !FEDatabase->IsFinalEpicChase()) { + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0x9145A5F2); + } + } + } + SetupInfractions(); + } else if (mPostPursuitScreenMode == POSTPURSUITSCREENMODE_MILESTONES) { + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0x578B767B); + if (GRaceStatus::Exists()) { + GRaceParameters *raceParams = GRaceStatus::Get().GetRaceParameters(); + if (raceParams && raceParams->GetIsPursuitRace() && !FEDatabase->IsFinalEpicChase()) { + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0x334FA7FB); + } + } + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0x7448870B); + SetupMilestones(); + } else { + FEngSetLanguageHash(GetPackageName(), 0x2D691760, 0xFEA872D4); + FEngSetLanguageHash(GetPackageName(), m_RaceButtonHash, 0xAEAEB62F); + SetupPursuit(); + } + SetInitialPosition(0); + ArrayScroller::RefreshHeader(); +} + +void PostRacePursuitScreen::NotificationMessage(unsigned long msg, FEObject *pObject, unsigned long Param1, unsigned long Param2) { + ArrayScrollerMenu::NotificationMessage(msg, pObject, Param1, Param2); + switch (msg) { + case 0x406415E3: + if (TheGameFlowManager.IsInFrontend()) { + if (FEDatabase->IsQuickRaceMode()) { + cFEng::Get()->QueuePackageSwitch(lbl_803E5FA0, 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch(lbl_803E52D4, 0, 0, false); + } + } else if (GRaceStatus::Exists() && GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Racing) { + GRacerInfo &info = GRaceStatus::Get().GetRacerInfo(0); + if (info.IsFinishedRacing() && GRaceStatus::Get().GetRaceParameters()->GetIsPursuitRace()) { + if (FEDatabase->IsChallengeMode() && MemoryCard::GetInstance()->ShouldDoAutoSave(false)) { + MemcardEnter(nullptr, nullptr, 0x100B1, nullptr, nullptr, 0, 0); + } else { + new EQuitToFE(static_cast(1), nullptr); + } + } else { + new EUnPause(); + } + } else { + new EUnPause(); + } + break; + case 0xC519BFC3: + if (TheGameFlowManager.IsInFrontend()) { + if (mPostPursuitScreenMode == POSTPURSUITSCREENMODE_INFRACTIONS) { + mPostPursuitScreenMode = POSTPURSUITSCREENMODE_PURSUIT; + } else { + mPostPursuitScreenMode = POSTPURSUITSCREENMODE_INFRACTIONS; + } + } else { + if (mPostPursuitScreenMode == POSTPURSUITSCREENMODE_INFRACTIONS) { + mPostPursuitScreenMode = POSTPURSUITSCREENMODE_MILESTONES; + } else if (mPostPursuitScreenMode == POSTPURSUITSCREENMODE_MILESTONES) { + mPostPursuitScreenMode = POSTPURSUITSCREENMODE_PURSUIT; + } else { + mPostPursuitScreenMode = POSTPURSUITSCREENMODE_INFRACTIONS; + } + } + Initialize(); + break; + } +} + +void PostRacePursuitScreen::SetupInfractions() { + PursuitResultsDatum::PursuitResultsDatumCheckType checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_Off; + + if (GInfractionManager::Get().DidInfractionOccur(GInfractionManager::kInfraction_Racing)) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } + AddDatum(new PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Check, 0x4b0ff103, 0.0f, 0.0f, checkType)); + + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_Off; + if (GInfractionManager::Get().DidInfractionOccur(GInfractionManager::kInfraction_Speeding)) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } + AddDatum(new PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Check, 0x8ec1a6ec, 0.0f, 0.0f, checkType)); + + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_Off; + if (GInfractionManager::Get().DidInfractionOccur(GInfractionManager::kInfraction_Reckless)) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } + AddDatum(new PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Check, 0x370f331d, 0.0f, 0.0f, checkType)); + + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_Off; + if (GInfractionManager::Get().DidInfractionOccur(GInfractionManager::kInfraction_Assault)) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } + AddDatum(new PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Check, 0x8462a784, 0.0f, 0.0f, checkType)); + + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_Off; + if (GInfractionManager::Get().DidInfractionOccur(GInfractionManager::kInfraction_HitAndRun)) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } + AddDatum(new PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Check, 0xdff254ba, 0.0f, 0.0f, checkType)); + + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_Off; + if (GInfractionManager::Get().DidInfractionOccur(GInfractionManager::kInfraction_Damage)) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } + AddDatum(new PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Check, 0x7dbd5b34, 0.0f, 0.0f, checkType)); + + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_Off; + if (GInfractionManager::Get().DidInfractionOccur(GInfractionManager::kInfraction_Resist)) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } + AddDatum(new PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Check, 0x2b1de2a9, 0.0f, 0.0f, checkType)); + + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_Off; + if (GInfractionManager::Get().DidInfractionOccur(GInfractionManager::kInfraction_OffRoad)) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } + AddDatum(new PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Check, 0xb0ef5c12, 0.0f, 0.0f, checkType)); +} + +void PostRacePursuitScreen::SetupPursuit() { + AddDatum(new(__FILE__, __LINE__) PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Time, 0x4d64888d, mPursuitData.mPursuitLength, 0.0f, PursuitResultsDatum::PursuitResultsDatumCheckType_Off)); + AddDatum(new(__FILE__, __LINE__) PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Number, 0xa999f6e2, static_cast(mPursuitData.mNumCopsDamaged), 0.0f, PursuitResultsDatum::PursuitResultsDatumCheckType_Off)); + AddDatum(new(__FILE__, __LINE__) PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Number, 0x23f6e732, static_cast(mPursuitData.mNumCopsDestroyed), 0.0f, PursuitResultsDatum::PursuitResultsDatumCheckType_Off)); + AddDatum(new(__FILE__, __LINE__) PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Number, 0x0291c816, static_cast(mPursuitData.mNumSpikeStripsDodged), 0.0f, PursuitResultsDatum::PursuitResultsDatumCheckType_Off)); + AddDatum(new(__FILE__, __LINE__) PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Number, 0x29daba15, static_cast(mPursuitData.mNumRoadblocksDodged), 0.0f, PursuitResultsDatum::PursuitResultsDatumCheckType_Off)); + AddDatum(new(__FILE__, __LINE__) PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Number, 0xd9bb7d2d, static_cast(mPursuitData.mCostToStateAchieved), 0.0f, PursuitResultsDatum::PursuitResultsDatumCheckType_Off)); + AddDatum(new(__FILE__, __LINE__) PursuitResultsDatum(PursuitResultsDatum::PursuitResultsDatumType_Number, 0xb7dfff96, static_cast(GInfractionManager::Get().GetNumInfractions()), 0.0f, PursuitResultsDatum::PursuitResultsDatumCheckType_Off)); +} + +void PostRacePursuitScreen::SetupMilestones() { + if (GRaceStatus::Exists()) { + GRaceParameters *raceParams = GRaceStatus::Get().GetRaceParameters(); + if (raceParams && raceParams->GetIsPursuitRace() && !FEDatabase->IsFinalEpicChase()) { + PursuitResultsDatum::PursuitResultsDatumType type = PursuitResultsDatum::PursuitResultsDatumType_Milestone_Number; + if (FEDatabase->IsMilestoneTimeFormat(raceParams->GetChallengeType())) { + type = PursuitResultsDatum::PursuitResultsDatumType_Milestone_Time; + } + float bestVal = GManager::Get().GetBestValue(raceParams->GetChallengeType()); + float goalVal = raceParams->GetChallengeGoal(); + if (raceParams->GetChallengeType() == 0x5392e4fd) { + type = PursuitResultsDatum::PursuitResultsDatumType_Milestone_Time_PursuitRemaining; + } + PursuitResultsDatum::PursuitResultsDatumCheckType checkType = + static_cast(static_cast(bestVal >= goalVal)); + AddDatum(new PursuitResultsDatum(type, + FEDatabase->GetChallengeHeaderHash(raceParams->GetLocalizationTag()), + bestVal, goalVal, checkType)); + } else { + unsigned int binNumber = FEDatabase->GetCareerSettings()->GetCurrentBin(); + GMilestone *milestone = GManager::Get().GetFirstMilestone(false, binNumber); + while (milestone) { + PursuitResultsDatum::PursuitResultsDatumType type = PursuitResultsDatum::PursuitResultsDatumType_Milestone_Number; + if (FEDatabase->IsMilestoneTimeFormat(milestone->GetTypeKey())) { + type = PursuitResultsDatum::PursuitResultsDatumType_Milestone_Time; + } + if (milestone->GetTypeKey() == 0x5392e4fd) { + type = PursuitResultsDatum::PursuitResultsDatumType_Milestone_Time_PursuitRemaining; + } + PursuitResultsDatum::PursuitResultsDatumCheckType checkType; + if (milestone->GetIsDonePendingEscape()) { + checkType = PursuitResultsDatum::PursuitResultsDatumCheckType_On; + } else { + checkType = static_cast( + milestone->GetIsAwarded() ? 2 : 0); + } + AddDatum(new PursuitResultsDatum(type, + FEDatabase->GetMilestoneHeaderHash(milestone->GetLocalizationTag()), + milestone->GetCurrentValue(), milestone->GetRequiredValue(), checkType)); + milestone = GManager::Get().GetNextMilestone(milestone, false, binNumber); + } + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.cpp index e69de29bb..24d3ef47f 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.cpp @@ -0,0 +1,143 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.hpp" +#include "Speed/Indep/Src/Camera/ICE/ICEManager.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEngFont.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" + +extern ICEManager TheICEManager; +extern Timer RealTimer; +extern MenuScreen *FEngFindScreen(const char *name); +extern FEString *FEngFindString(const char *pkg_name, int hash); +extern int FEPrintf(FEString *text, const char *fmt, ...); +extern void bStrCpy(unsigned short *to, const char *from); +extern unsigned long FEHashUpper(const char *string); + +extern const char lbl_803E59BC[]; + +static MenuScreen *ChyronScreenPtr; + +Chyron::Chyron(ScreenConstructorData *sd) + : MenuScreen(sd) // + , mDelayTimer(0) +{ +} + +Chyron::~Chyron() {} + +void Chyron::NotificationMessage(unsigned long msg, FEObject *pobject, unsigned long param1, unsigned long param2) { + if (mDelayTimer.IsSet()) { + if (!TheGameFlowManager.IsInFrontend()) { + if ((RealTimer - mDelayTimer).GetSeconds() < 5.0f) { + return; + } + } + Start(); + } +} + +void Chyron::Start() { + if (mDelayTimer.IsSet()) { + mDelayTimer.UnSet(); + } + + FEString *title = FEngFindString(GetPackageName(), 0xF5387816); + FEString *artist = FEngFindString(GetPackageName(), 0x11DB9E17); + FEString *album = FEngFindString(GetPackageName(), 0x48097852); + + unsigned short widestring[256]; + + FEngFont *font = FindFont(title->Handle); + bStrCpy(widestring, mTitle); + float maxString = font->GetTextWidth(reinterpret_cast(widestring), title->Flags); + + font = FindFont(artist->Handle); + bStrCpy(widestring, mArtist); + float artistWidth = font->GetTextWidth(reinterpret_cast(widestring), artist->Flags); + maxString = bMax(maxString, artistWidth); + + bStrCpy(widestring, mAlbum); + font = FindFont(album->Handle); + float albumWidth = font->GetTextWidth(reinterpret_cast(widestring), album->Flags); + maxString = bMax(maxString, albumWidth); + + unsigned int startScript; + if (maxString > 185.0f) { + startScript = 0x73D17048; + if (maxString <= 250.0f) { + startScript = 0x57520493; + } + } else { + startScript = 0x57521F29; + } + + FEPrintf(title, mTitle); + FEPrintf(artist, mArtist); + FEPrintf(album, mAlbum); + + cFEng::Get()->QueuePackageMessage(startScript, GetPackageName(), nullptr); + + if (TheGameFlowManager.IsInFrontend()) { + const char *posMsg = "TRAX_POS_2"; + cFEng::Get()->QueuePackageMessage(FEHashUpper(posMsg), GetPackageName(), nullptr); + } else { + if ((MemoryCard::GetInstance()->IsAutoSaving() + || MemoryCard::GetInstance()->AutoSaveRequested()) + && GRaceStatus::Exists() + && GRaceStatus::Get().GetRaceParameters() != nullptr + && GRaceStatus::Get().GetRaceParameters()->GetIsDDayRace()) { + const char *posMsg = "TRAX_POS_3"; + cFEng::Get()->QueuePackageMessage(FEHashUpper(posMsg), GetPackageName(), nullptr); + } else { + cFEng::Get()->QueuePackageMessage(FEHashUpper("TRAX_POS_1"), GetPackageName(), nullptr); + } + } + + bool widescreen = FEDatabase->GetOptionsSettings()->TheVideoSettings.WideScreen; + if (TheGameFlowManager.IsInFrontend()) { + if (widescreen) { + cFEng::Get()->QueuePackageMessage(FEHashUpper("WIDESCREEN_MODE"), nullptr, nullptr); + } else { + cFEng::Get()->QueuePackageMessage(FEHashUpper("NORMAL_MODE"), nullptr, nullptr); + } + } else { + if (widescreen) { + cFEng::Get()->QueuePackageMessage(FEHashUpper("WIDESCREEN_MODEHUD"), nullptr, nullptr); + } else { + cFEng::Get()->QueuePackageMessage(FEHashUpper("NORMAL_MODEHUD"), nullptr, nullptr); + } + } +} + +void InitChyron() { + ChyronScreenPtr = static_cast(bMalloc(0x30, 0)); +} + +MenuScreen *CreateChyronScreen(ScreenConstructorData *sd) { + return new (ChyronScreenPtr) Chyron(sd); +} + +void DismissChyron() { + if (cFEng::Get()->IsPackagePushed(lbl_803E59BC)) { + cFEng::Get()->PopNoControlPackage(lbl_803E59BC); + } +} + +void SummonChyron(char *title, char *artist, char *album) { + if (!TheICEManager.IsEditorOn()) { + if (title != nullptr && artist != nullptr && album != nullptr) { + Chyron::mTitle = title; + Chyron::mArtist = artist; + Chyron::mAlbum = album; + } + DismissChyron(); + if (!cFEng::Get()->IsPackagePushed(lbl_803E59BC)) { + cFEng::Get()->PushNoControlPackage(lbl_803E59BC, FE_PACKAGE_PRIORITY_CLOSEST); + } + Chyron *chyron = static_cast< Chyron * >(FEngFindScreen(lbl_803E59BC)); + if (chyron != nullptr) { + chyron->mDelayTimer = RealTimer; + } + } +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.hpp new file mode 100644 index 000000000..bdf734c94 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_Chyron.hpp @@ -0,0 +1,28 @@ +#ifndef FRONTEND_MENUSCREENS_INGAME_FEPKG_CHYRON_H +#define FRONTEND_MENUSCREENS_INGAME_FEPKG_CHYRON_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" + +struct Chyron : public MenuScreen { + Chyron(ScreenConstructorData *sd); + ~Chyron() override; + void NotificationMessage(unsigned long msg, FEObject *pobject, unsigned long param1, unsigned long param2) override; + void Start(); + + void *operator new(size_t, void *ptr) { return ptr; } + + static char *mTitle; + static char *mArtist; + static char *mAlbum; + + Timer mDelayTimer; // offset 0x2C +}; + +void SummonChyron(char *title, char *artist, char *album); +void DismissChyron(); + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_PostRace.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_PostRace.hpp index 50cc75573..a6794fd66 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_PostRace.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_PostRace.hpp @@ -5,7 +5,47 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp" #include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +#include "Speed/Indep/Src/Gameplay/GRace.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" + +struct GRacerInfo; +struct GMilestone; + +typedef int dialog_handle; + +enum PostRaceScreenMode { + POSTRACESCREENMODE_RESULTS = 0, + POSTRACESCREENMODE_STATS = 1, + POSTRACESCREENMODE_LAPSTATS = 2, + POSTRACESCREENMODE_NUMMODES = 3, +}; + +// total size: 0xAC +struct PursuitData { + void ClearData(); + void PopulateData(struct IPursuit *ipursuit, struct IPerpetrator *iperpetrator, int exitToSafehouse); + bool AddMilestone(GMilestone *milestone); + const GMilestone *const GetMilestone(int index) const; + int GetNumMilestones() { return mNumMilestonesThisPursuit; } + + static const int mMaxNumMilestones; + + bool mPursuitIsActive; // offset 0x0 + float mPursuitLength; // offset 0x4 + int mNumCopsDamaged; // offset 0x8 + int mNumCopsDestroyed; // offset 0xC + int mNumSpikeStripsDodged; // offset 0x10 + int mNumRoadblocksDodged; // offset 0x14 + int mCostToStateAchieved; // offset 0x18 + int mRepAchievedNormal; // offset 0x1C + int mRepAchievedCopDestruction; // offset 0x20 + int mExitToSafehouse; // offset 0x24 + int mNumMilestonesThisPursuit; // offset 0x28 + GMilestone *mMilestonesCompleted[32]; // offset 0x2C +}; enum PostPursuitScreenMode { POSTPURSUITSCREENMODE_PURSUIT = 0, @@ -13,68 +53,286 @@ enum PostPursuitScreenMode { POSTPURSUITSCREENMODE_MILESTONES = 2, }; -// TODO MOVE PursuitData, idk where +struct PostRacePursuitScreen : ArrayScrollerMenu { + PostRacePursuitScreen(ScreenConstructorData *); + ~PostRacePursuitScreen() override; + void Initialize(); + void SetupInfractions(); + void SetupMilestones(); + void SetupPursuit(); + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + static PursuitData mPursuitData; -// total size: 0xAC -class PursuitData { - public: - PursuitData() {} + static PursuitData &GetPursuitData() { + return mPursuitData; + } - // int GetNumMilestones() {} + PostPursuitScreenMode mPostPursuitScreenMode; // offset 0xE8 + unsigned int m_RaceButtonHash; // offset 0xEC +}; - // void PopulateData(struct IPursuit *ipursuit, struct IPerpetrator *iperpetrator, int exitToSafehouse); +struct RaceStat : public FEStatWidget { + RaceStat(FEString *title, FEString *data); + ~RaceStat() override {} +}; - // bool AddMilestone(struct GMilestone *milestone); +struct ResultStat : public RaceStat { + ResultStat(FEString *name_str, FEString *data, FEString *pos, GRacerInfo *racer_info) + : RaceStat(name_str, data) // + , Position(pos) // + , RacerInfo(racer_info) {} - // const struct GMilestone *const GetMilestone(const int index) const; + ~ResultStat() override {} - void ClearData(); + FEString *Position; + GRacerInfo *RacerInfo; +}; - private: - static const int mMaxNumMilestones; // size: 0x4, address: 0xFFFFFFFF - - bool mPursuitIsActive; // offset 0x0, size 0x1 - float mPursuitLength; // offset 0x4, size 0x4 - int mNumCopsDamaged; // offset 0x8, size 0x4 - int mNumCopsDestroyed; // offset 0xC, size 0x4 - int mNumSpikeStripsDodged; // offset 0x10, size 0x4 - int mNumRoadblocksDodged; // offset 0x14, size 0x4 - int mCostToStateAchieved; // offset 0x18, size 0x4 - int mRepAchievedNormal; // offset 0x1C, size 0x4 - int mRepAchievedCopDestruction; // offset 0x20, size 0x4 - int mExitToSafehouse; // offset 0x24, size 0x4 - int mNumMilestonesThisPursuit; // offset 0x28, size 0x4 - struct GMilestone *mMilestonesCompleted[32]; // offset 0x2C, size 0x80 -}; - -// total size: 0xF0 -class PostRacePursuitScreen : public ArrayScrollerMenu { +struct GenericStat : public RaceStat { + GenericStat(FEString *title, FEString *data, float stat_data, unsigned int title_hash, + unsigned int units_hash, const char *format) + : RaceStat(title, data) // + , StatData(stat_data) // + , TitleHash(title_hash) // + , UnitsHash(units_hash) // + , Format(format) {} + + ~GenericStat() override {} + void Draw() override; + + float StatData; + unsigned int TitleHash; + unsigned int UnitsHash; + const char *Format; +}; + +struct InfoStat : public RaceStat { + InfoStat(FEString *title, FEString *info, unsigned int title_hash, unsigned int info_hash) + : RaceStat(title, info) // + , TitleHash(title_hash) // + , InfoHash(info_hash) {} + + ~InfoStat() override {} + void Draw() override; + + unsigned int TitleHash; + unsigned int InfoHash; +}; + +struct TimerStat : public RaceStat { + TimerStat(FEString *title, FEString *data, float seconds, unsigned int title_hash) + : RaceStat(title, data) // + , Seconds(seconds) // + , TitleHash(title_hash) {} + + ~TimerStat() override {} + void Draw() override; + + Timer Seconds; + unsigned int TitleHash; +}; + +struct GenericResult : public ResultStat { + GenericResult(FEString *name, FEString *data, FEString *pos, unsigned int units_hash, + float fdata, const char *format, GRacerInfo *racer_info) + : ResultStat(name, data, pos, racer_info) // + , UnitsHash(units_hash) // + , FData(fdata) // + , Format(format) {} + + ~GenericResult() override {} + void Draw() override; + + unsigned int UnitsHash; + float FData; + const char *Format; +}; + +struct RaceResultStat : public ResultStat { + RaceResultStat(FEString *name_str, FEString *time, FEString *pos, GRacerInfo *info) + : ResultStat(name_str, time, pos, info) {} + + ~RaceResultStat() override; + void Draw() override; +}; + +struct StageStat : public ResultStat { + StageStat(FEString *stage, FEString *time, FEString *pos, int stage_num, float seconds, int pos_num) + : ResultStat(stage, time, pos, nullptr) // + , StageNum(stage_num) // + , Time(seconds) // + , PosNum(pos_num) {} + + ~StageStat() override; + void Draw() override; + + int StageNum; + Timer Time; + int PosNum; +}; + +struct TollboothStat : public ResultStat { + TollboothStat(FEString *tollbooth, FEString *time, FEString *pos, int tollbooth_num, float seconds, int pos_num) + : ResultStat(tollbooth, time, pos, nullptr) // + , TollboothNum(tollbooth_num) // + , Time(seconds) // + , PosNum(pos_num) {} + + ~TollboothStat() override; + void Draw() override; + + int TollboothNum; + Timer Time; + int PosNum; +}; + +struct StatsPanel { + StatsPanel(); + virtual ~StatsPanel() {} + + void SetParentPkg(const char *parent_pkg) { + ParentPkg = parent_pkg; + } + + void SetRacerName(const char *name) { + RacerName = name; + } + + FEString *GetCurrentString(const char *name) { + extern const char lbl_803E5088[]; + + return FEngFindString(ParentPkg, FEngHashString(lbl_803E5088, name, iWidgetToAdd)); + } + int GetNumStats() { + return TheStats.CountElements(); + } + + void Reset(); + void Draw(unsigned int numPlayers); + void AddStat(RaceStat *stat); + void AddInfoStat(unsigned int title, unsigned int info); + void AddGenericStat(float stat_data, unsigned int title_hash, unsigned int units_hash, const char *format); + void AddTimerStat(float seconds, unsigned int title_hash); + + bTList TheStats; + int iWidgetToAdd; + const char *RacerName; + const char *ParentPkg; +}; + +struct PostRaceResultsScreen : public MenuScreen { public: - static PursuitData &GetPursuitData() { - return mPursuitData; + PostRaceResultsScreen(ScreenConstructorData *sd); + ~PostRaceResultsScreen() override; + void NotificationMessage(unsigned long msg, FEObject *pObject, unsigned long Param1, unsigned long Param2) override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + + private: + StatsPanel &GetActiveStatsPanel() { + if (mPostRaceScreenMode != POSTRACESCREENMODE_RESULTS && mIndexOfCurrentRacer >= 0 && mIndexOfCurrentRacer < 16) { + return RacerStats[mIndexOfCurrentRacer]; + } + + return RaceResults; } - PostRacePursuitScreen(ScreenConstructorData *sd); + void Setup(); + void SetupResults(); + void SetupStat_NosUsed(); + void SetupStat_TopSpeed(); + void SetupStat_AverageSpeed(); + void SetupStat_TimeBehind(); + void SetupStat_LapVariance(); + void SetupStat_StageVariance(); + void SetupStat_TrafficCollisions(); + void SetupStat_ZeroToSixty(); + void SetupStat_QuarterMile(); + void SetupStat_PerfectShifts(); + void SetupStat_AccumulatedSpeed(); + void SetupStat_SpeedVariance(); + void SetupStat_SpeedBehind(); + void SetupRacerStats(int index, GRacerInfo *racer_info); + void SetupLapStats(int racerIndex, GRacerInfo *racer_info); - // Overrides: MenuScreen - ~PostRacePursuitScreen() override; + StatsPanel RacerStats[16]; + StatsPanel RaceResults; + // TODO: PS2 type info shows a dialog_handle at 0x1C4, but GC ctor/diffs clearly use + // this GameCube layout instead. Reconcile once NotificationMessage/upload paths are in. + int mNumberOfRacers; + int mIndexOfWinner; + int mIndexOfCurrentRacer; + int mNumberOfLaps; + int mNumberOfStats; + GRace::Type mRaceType; + PostRaceScreenMode mPostRaceScreenMode; + GRacerInfo *mPlayerRacerInfo; + int mMaxSlotsLeftSide; + unsigned int m_RaceButtonHash; + int m_lastErrorKind; + int m_lastErrorCode; + bool m_raceResultsUploaded; +}; - void Initialize(); +// total size: 0x38 +struct PursuitResultsDatum : public ArrayDatum { + enum PursuitResultsDatumType { + PursuitResultsDatumType_Number = 0, + PursuitResultsDatumType_Time = 1, + PursuitResultsDatumType_Milestone_Number = 2, + PursuitResultsDatumType_Milestone_Time = 3, + PursuitResultsDatumType_Milestone_Time_PursuitRemaining = 4, + PursuitResultsDatumType_Check = 5, + }; + enum PursuitResultsDatumCheckType { + PursuitResultsDatumCheckType_Off = 0, + PursuitResultsDatumCheckType_On = 1, + PursuitResultsDatumCheckType_Greyed = 2, + }; - void SetupInfractions(); + PursuitResultsDatum(PursuitResultsDatumType type, unsigned int itemName, float itemNumber, float itemGoal, PursuitResultsDatumCheckType itemChecked); + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) override {} - void SetupMilestones(); + PursuitResultsDatumType GetType() { return mType; } + unsigned int GetName() { return mName; } + float GetNumber() { return mNumber; } + float GetGoal() { return mGoal; } + bool GetChecked() { return mChecked == PursuitResultsDatumCheckType_On; } + bool GetGreyed() { return mChecked == PursuitResultsDatumCheckType_Greyed; } - void SetupPursuit(); + PursuitResultsDatumType mType; // offset 0x24, size 0x4 + unsigned int mName; // offset 0x28, size 0x4 + float mNumber; // offset 0x2C, size 0x4 + float mGoal; // offset 0x30, size 0x4 + PursuitResultsDatumCheckType mChecked; // offset 0x34, size 0x4 +}; - // Overrides: MenuScreen - void NotificationMessage(u32 msg, FEObject *pObject, u32 Param1, u32 Param2) override; +struct PursuitResultsArraySlot : public ArraySlot { + PursuitResultsArraySlot(FEObject *obj, FEString *itemName, FEString *itemNumber, FEImage *itemChecked, FEImage *itemEmpty); + ~PursuitResultsArraySlot() override {} + void Update(ArrayDatum *datum, bool isSelected) override; - private: - static PursuitData mPursuitData; // size: 0xAC, address: 0x8047306C + FEObject *mLine; // offset 0x14, size 0x4 + FEString *mItemName; // offset 0x18, size 0x4 + FEString *mItemNumber; // offset 0x1C, size 0x4 + FEImage *mItemChecked; // offset 0x20, size 0x4 + FEImage *mItemEmpty; // offset 0x24, size 0x4 +}; - PostPursuitScreenMode mPostPursuitScreenMode; // offset 0xE8, size 0x4 - unsigned int m_RaceButtonHash; // offset 0xEC, size 0x4 +struct PostRaceMilestonesScreen : MenuScreen { + PostRaceMilestonesScreen(ScreenConstructorData *); + ~PostRaceMilestonesScreen() override; + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + void StartMilestoneDoneAnimations(); + void StartAnimations(bool isMilestone, int typeKey, float bountyEarned, const char *descriptionStr); + bool StartBountyAnimations(bool copDestruction); + bool SetMilestoneAnimationScriptHash(bool isMilestone, int type); + bool StartMilestoneAnimations(); + bool StartChallengeAnimations(); + FEImage *mpDataBigIcon; // offset 0x2C + float mBountyEarned; // offset 0x30 + bool mCopDestructionBountyShown; // offset 0x34 + int mCurrMilestoneIndex; // offset 0x38 + int mCurrMilestoneScriptHash; // offset 0x3C }; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEpkg_MU_Keyboard.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEpkg_MU_Keyboard.cpp index e69de29bb..a0ab04a50 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEpkg_MU_Keyboard.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FEpkg_MU_Keyboard.cpp @@ -0,0 +1,546 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feKeyboardInput.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern int FEPrintf(FEString *text, const char *fmt, ...); +extern int FEPrintf(const char *pkg_name, FEObject *obj, const char *fmt, ...); +extern Timer RealTimer; +Timer KBCreationTimer; +extern FEKeyboard *gFEKeyboard; +extern bool KeyboardActive; +extern char FEKeyboard_mLetterMap[720] asm("_10FEKeyboard.mLetterMap"); +extern FEImage *FEngFindImage(const char *pkg_name, int hash); +extern void FEngSetButtonTexture(FEImage *img, unsigned int hash); +extern FEString *FEngFindString(const char *pkg_name, int hash); +extern int FEngSNPrintf(char *buf, int size, const char *fmt, ...); +extern unsigned long FEHashUpper(const char *str); +extern void FEngSetScript(FEObject *obj, unsigned int script_hash, bool b); +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool b); +extern eLanguages GetCurrentLanguage(); +extern void FEngSetLanguageHash(const char *pkg_name, unsigned int object_hash, unsigned int language_hash); +extern void FEngSNMakeHidden(char *dst, int size, const char *src); +extern void PackedStringToWideString(unsigned short *dst, int maxLen, const char *src); +extern void WideToCharString(char *dst, unsigned int maxLen, short *src); +extern void *FindFont_impl(unsigned int hash) asm("FindFont__FUi"); +extern float GetLineWidth_impl(void *font, short *str, unsigned long flags, int param, bool b) asm("GetLineWidth__8FEngFontPCsUlUlb"); +extern void FEngGetSize(FEObject *obj, float &w, float &h); +extern void FEngGetTopLeft(FEObject *obj, float &x, float &y); +extern void FEngSetTopLeft(FEObject *obj, float x, float y); +extern void FESetString(FEString *str, short *text); +extern int bStrLen(const unsigned short *str) asm("bStrLen__FPCUs"); + +FEColor FEKeyboard::ButtonHighlight(0xC8CFE9F2); +FEColor FEKeyboard::LetterHighlight(0xFFFFFFFF); +FEColor FEKeyboard::ButtonIdle(0x50549AC0); +FEColor FEKeyboard::LetterIdle(0xFF323232); + +FEKeyboard::FEKeyboard(ScreenConstructorData *sd) + : MenuScreen(sd) +{ + mnWindowStartIdx = 0; + mThis = sd->pPackage; + KBCreationTimer = RealTimer; + Initialize(); + UpdateVisuals(); +} + +int FEKeyboard::GetCase() { + if (mbShift) { + return !mbCaps; + } + if (!mbCaps) { + return 0; + } + return 1; +} + +void FEKeyboard::MoveCursor(int nDelta) { + mbIsFirstKey = false; + mnCursorIndex = mnCursorIndex + nDelta; + if (mnCursorIndex < 0) { + mnCursorIndex = 0; + } + int stringLength = bStrLen(mString); + if (mnCursorIndex >= stringLength) { + mnCursorIndex = stringLength; + } + UpdateVisuals(); +} + +bool FEKeyboard::IsSymbol(char character) { + char symbols[28] = "!@#$%^&*-_=+[{]}\\|;:'\",<.>/"; + for (unsigned int i = 0; i <= 0x1B; i++) { + if (character == symbols[i]) { + return true; + } + } + return false; +} + +bool FEKeyboard::IsNotOkForEmail(char character) { + char symbols[24] = "!#$%^&*=+[{]}\\|;'\",<>"; + for (int i = 0; i <= 0x17; i++) { + if (character == symbols[i]) { + return true; + } + } + return false; +} + +bool FEKeyboard::IsEmailSymbol(char character) { + char symbols[5] = ":;()"; + for (int i = 0; i <= 3; i++) { + if (character == symbols[i]) { + return true; + } + } + return false; +} + +void FEKeyboard::AppendLetter(int nButton) { + AppendChar(GetLetterMap(nButton)); +} + +void FEKeyboard::AppendBackspace() { + if (mbIsFirstKey) { + mbIsFirstKey = false; + mString[0] = 0; + mnCursorIndex = 0; + } + int len = bStrLen(mString); + if (len > 0 && mnCursorIndex > 0) { + int i = mnCursorIndex - 1; + for (; i < len; i++) { + mString[i] = mString[i + 1]; + } + mString[len - 1] = 0; + mnCursorIndex = mnCursorIndex - 1; + } + UpdateStringVisual(); +} + +void FEKeyboard::ToggleSpecialCharacters() { + bool newVal = true; + if (mbOnSpecialCharacters == true) { + newVal = false; + } + mbOnSpecialCharacters = newVal; + if (mnMode == MODE_ALL_KEYS) { + g_pEAXSound->PlayUISoundFX(static_cast< eMenuSoundTriggers >(0x2E)); + } else { + mbOnSpecialCharacters = false; + } + UpdateVisuals(); +} + +void FEKeyboard::SetString(char *pStr) { + FEPrintf(mpInputString, pStr); +} + +void FEKeyboard::SetMaxLength(int nLength) { + if (nLength > 0x9C) { + nLength = 0x9C; + } + mnMaxLength = nLength; +} + +int FEKeyboard::IsKeyButton(FEObject *pObj) { + int i = 0; + do { + if (mpKeyButton[i] == pObj) { + return i; + } + i = i + 1; + } while (i < 0x2D); + return -1; +} + +void FEKeyboard::AppendSpace() { + if (mnMode == MODE_ALL_KEYS) { + AppendChar(0x20); + } +} + +void FEKeyboard::Initialize() { + mString = FEDatabase->mFEKeyboardSettings.Buffer; + SetMaxLength(FEDatabase->mFEKeyboardSettings.MaxTextLength); + mnAcceptHash = FEDatabase->mFEKeyboardSettings.AcceptCallbackHash; + mnDeclineHash = FEDatabase->mFEKeyboardSettings.DeclineCallbackHash; + mnCursorIndex = 0; + mnMode = static_cast(FEDatabase->mFEKeyboardSettings.Mode); + + eLanguages language = static_cast(GetCurrentLanguage()); + switch (language) { + case eLANGUAGE_ITALIAN: + case eLANGUAGE_DUTCH: + case eLANGUAGE_SWEDISH: + mnLetterMapIndex = language; + break; + case eLANGUAGE_FRENCH: + mnLetterMapIndex = language; + break; + case eLANGUAGE_GERMAN: + mnLetterMapIndex = 2; + break; + case eLANGUAGE_SPANISH: + mnLetterMapIndex = 4; + break; + default: + mnLetterMapIndex = 0; + break; + } + + FEngSetButtonTexture(FEngFindImage(GetPackageName(), 0x5BC), 0x5BC); + FEngSetButtonTexture(FEngFindImage(GetPackageName(), 0x682), 0x682); + + mbOnSpecialCharacters = false; + mbIsFirstKey = true; + mbShift = false; + mbCaps = false; + if (mnMode == MODE_FILENAME || mnMode == MODE_PROFILE_ENTRY) { + mbCaps = true; + } + + mpInputString = FEngFindString(GetPackageName(), 0xADDA7E89); + mpCursor = FEngFindObject(GetPackageName(), 0xA2DEEF46); + mpTextBox = FEngFindImage(GetPackageName(), 0x128FDCB8); + mpCursorTestString = reinterpret_cast(FEngFindObject(GetPackageName(), 0x95D7D3D2)); + + char tmp[32]; + for (int i = 0; i < 0x2D; i++) { + unsigned int letter_hash; + + FEngSNPrintf(tmp, 0x20, "KEYNUM %.2d", i + 1); + letter_hash = FEHashUpper(tmp); + mpKeyName[i] = reinterpret_cast(FEngFindObject(GetPackageName(), letter_hash)); + + FEngSNPrintf(tmp, 0x20, "KEYNUM SHADOW %.2d", i + 1); + letter_hash = FEHashUpper(tmp); + mpKeyNameShadow[i] = reinterpret_cast(FEngFindObject(GetPackageName(), letter_hash)); + + FEngSNPrintf(tmp, 0x20, "KEY %.2d", i + 1); + letter_hash = FEHashUpper(tmp); + mpKeyButton[i] = FEngFindObject(GetPackageName(), letter_hash); + + FEngSNPrintf(tmp, 0x20, "KEY %.2d DISABLE", i + 1); + letter_hash = FEHashUpper(tmp); + mpKeyDisable[i] = FEngFindObject(GetPackageName(), letter_hash); + } + + if (mnMode != MODE_ALL_KEYS || GetCurrentLanguage() == eLANGUAGE_KOREAN) { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x2C99C4E2)); + FEngSetScript(GetPackageName(), 0xDCBC8286, 0x1CA7C0, true); + } + + if (mnMode == MODE_PROFILE_ENTRY) { + unsigned int disableHash = FEHashUpper("KEY 61 DISABLE"); + unsigned int showHash = FEHashUpper("SHOW"); + FEngSetScript(GetPackageName(), disableHash, showHash, true); + } + + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0x7F042BCD); + + mpInputString->string = mDisplayString; + mpInputString->Flags |= 0x400000; + + mnCursorIndex = bStrLen(mString); + KeyboardActive = true; + gFEKeyboard = this; +} + +void FEKeyboard::UpdateVisuals() { + const unsigned long SHOW_SCRIPT = 0x16A259; + const unsigned long HIDE_SCRIPT = 0x1CA7C0; + + for (int i = 0; i < 0x2D; i++) { + char ch = GetLetterMap(i); + if (GetCurrentLanguage() == eLANGUAGE_KOREAN) { + short bla[16]; + bMemSet(bla, 0, 0x20); + bla[0] = static_cast(ch); + mpKeyName[i]->string = bla; + mpKeyName[i]->Flags |= 0x400000; + mpKeyNameShadow[i]->string = bla; + mpKeyNameShadow[i]->Flags |= 0x400000; + } else { + FEPrintf(mpKeyName[i], "%c", ch); + FEPrintf(mpKeyNameShadow[i], "%c", ch); + } + + bool disabled = ch == 0; + if (!disabled) { + if (mnMode - 1U < 3) { + disabled = IsSymbol(ch); + } else if (mnMode == MODE_EMAIL) { + disabled = IsNotOkForEmail(ch); + } + } + + unsigned long script = SHOW_SCRIPT; + if (disabled) { + script = HIDE_SCRIPT; + } + FEngSetScript(mpKeyDisable[i], script, true); + } + + if (GetCase() == 0) { + FEngSetLanguageHash(GetPackageName(), 0xDE045B20, 0x20BC9973); + } else { + FEngSetLanguageHash(GetPackageName(), 0xDE045B20, 0x36F274D0); + } + UpdateStringVisual(); +} + +void FEKeyboard::UpdateStringVisual() { + mpInputString->Flags |= 2; + if (mnMode == MODE_ALPHANUMERIC_PASSWORD) { + char aHidden[64]; + FEngSNMakeHidden(aHidden, 0x40, mString); + SetString(aHidden); + } else { + mnWindowStartIdx = 0; + unsigned short widestring[156]; + PackedStringToWideString(widestring, 0x9C, mString); + void *font = FindFont_impl(mpInputString->Format); + unsigned long flags = mpInputString->Flags; + unsigned int width = mpInputString->MaxWidth; + short *fitstring = reinterpret_cast(widestring); + for (; *fitstring != 0; fitstring += 4) { + if (static_cast(GetLineWidth_impl(font, fitstring, flags, 0, false)) <= static_cast(width)) { + break; + } + mnWindowStartIdx = mnWindowStartIdx + 4; + } + + int wsi = mnWindowStartIdx; + while (mnCursorIndex < wsi) { + if (wsi - 4 < 0) { + mnWindowStartIdx = 0; + fitstring = reinterpret_cast(widestring); + } else { + mnWindowStartIdx = wsi - 4; + fitstring = fitstring - 4; + } + wsi = mnWindowStartIdx; + } + + while (*fitstring != 0) { + if (static_cast(GetLineWidth_impl(font, fitstring, flags, 0, false)) <= static_cast(width)) { + break; + } + int wlen = bStrLen(widestring); + widestring[wlen - 1] = 0; + } + + FESetString(mpInputString, fitstring); + } + UpdateCursorPosition(); +} + +void FEKeyboard::UpdateCursorPosition() { + mpCursorTestString->Flags |= 2; + short *pSrc = mpInputString->string; + char pBuf[150]; + bMemSet(pBuf, 0, 0x96); + WideToCharString(pBuf, static_cast(mnCursorIndex - mnWindowStartIdx + 1), pSrc); + FEPrintf(GetPackageName(), mpCursorTestString, "%s", pBuf); + float width; + float h; + FEngGetSize(mpCursorTestString, width, h); + float string_x = reinterpret_cast(mpInputString->pData)[7]; + float cursor_x; + float y; + FEngGetTopLeft(mpCursor, cursor_x, y); + FEngSetTopLeft(mpCursor, string_x + width, y); +} + +void FEKeyboard::NotificationMessage(unsigned long msg, FEObject *pObject, unsigned long param1, unsigned long param2) { + int nButton = -1; + switch (msg) { + case 0x9120409E: + g_pEAXSound->PlayUISoundFX(static_cast(2)); + return; + case 0xB5971BF1: + g_pEAXSound->PlayUISoundFX(static_cast(3)); + return; + case 0x72619778: + g_pEAXSound->PlayUISoundFX(static_cast(0)); + return; + case 0x911C0A4B: + g_pEAXSound->PlayUISoundFX(static_cast(1)); + return; + case 0xC407210: + if (!pObject) { + return; + } + nButton = IsKeyButton(pObject); + if (nButton > -1 && GetLetterMap(nButton) != 0) { + g_pEAXSound->PlayUISoundFX(static_cast(0x2E)); + AppendLetter(nButton); + return; + } + g_pEAXSound->PlayUISoundFX(static_cast(7)); + return; + case 0xE1FDE1D1: + if (bStrCmp(mString, "") == 0 && mnMode == MODE_FILENAME) { + return; + } + goto dispose_keyboard; + case 0xC1A6F000: + if (mnMode == MODE_PROFILE_ENTRY) { + g_pEAXSound->PlayUISoundFX(static_cast(7)); + return; + } + AppendSpace(); + g_pEAXSound->PlayUISoundFX(static_cast(0x2E)); + return; + case 0xDB3D597C: + play_backspace_sound: + g_pEAXSound->PlayUISoundFX(static_cast(0x30)); + AppendBackspace(); + return; + case 0xD7AD0DD9: + if (mnMode == MODE_PROFILE_ENTRY && !(FEDatabase->GetGameMode() & 8) && !(FEDatabase->GetGameMode() & 0x40)) { + g_pEAXSound->PlayUISoundFX(static_cast(7)); + return; + } + ToggleCapsLock(); + g_pEAXSound->PlayUISoundFX(static_cast(0x30)); + return; + case 0xB5AF2461: + if (bStrCmp(mString, "") == 0) { + cFEng::Get()->QueuePackageMessage(0x8CB81F09, GetPackageName(), nullptr); + return; + } + goto dispose_keyboard; + dispose_keyboard: + g_pEAXSound->PlayUISoundFX(static_cast(0x2F)); + Dispose(false); + return; + case 0x5073EF13: + MoveCursor(-1); + return; + case 0xD9FEEC59: + MoveCursor(1); + return; + case 0xC519BFC4: + if (GetCurrentLanguage() == eLANGUAGE_KOREAN) { + return; + } + ToggleSpecialCharacters(); + return; + case 0x911AB364: + if (mnDeclineHash == -1U) { + return; + } + Dispose(true); + return; + default: + return; + } +} + +void FEKeyboard::ToggleCapsLock() { + if (mnMode != MODE_PROFILE_ENTRY) { + mbCaps = mbCaps != 1; + mbShift = false; + if (mnMode == MODE_FILENAME) { + mbCaps = true; + } + UpdateVisuals(); + } +} + +void FEKeyboard::ToggleShift() { + mbShift = mbShift != 1; + if (mnMode == MODE_FILENAME) { + mbShift = false; + } + UpdateVisuals(); +} + +bool FEKeyboard::IsNumericSymbol(char character) { + char symbols[10] = "!@#$%^&*("; + for (unsigned int i = 0; i <= 9; i++) { + if (character == symbols[i]) { + return true; + } + } + return false; +} + +char FEKeyboard::GetLetterMap(int nButton) { + int c = GetCase(); + char returnChar = FEKeyboard_mLetterMap[nButton + c * 0x2D + mnLetterMapIndex * 0x5A]; + if (mnMode == 0 && mbOnSpecialCharacters) { + c = GetCase(); + return FEKeyboard_mLetterMap[nButton + c * 0x2D + 0x276]; + } + if (mnMode - 1U > 2) { + if (mnMode == MODE_EMAIL) { + if (IsEmailSymbol(returnChar) || IsNumericSymbol(returnChar)) { + returnChar = FEKeyboard_mLetterMap[nButton + mnLetterMapIndex * 0x5A]; + } + if (IsNotOkForEmail(returnChar)) { + returnChar = 0; + } + return returnChar; + } + if (mnMode != MODE_PROFILE_ENTRY) { + return returnChar; + } + if (IsNumericSymbol(returnChar)) { + return FEKeyboard_mLetterMap[nButton + mnLetterMapIndex * 0x5A]; + } + } + if (IsSymbol(returnChar)) { + returnChar = 0; + } + return returnChar; +} + +void FEKeyboard::AppendChar(char ch) { + if (ch) { + if (mbIsFirstKey) { + mbIsFirstKey = false; + mString[0] = 0; + mnCursorIndex = 0; + } + int len = bStrLen(mString); + if (len < mnMaxLength) { + int i = 0x9A; + for (; i > mnCursorIndex; i--) { + mString[i] = mString[i - 1]; + } + mString[mnCursorIndex] = ch; + mString[0x9B] = 0; + mnCursorIndex = mnCursorIndex + 1; + if (mnCursorIndex > mnMaxLength) { + mnCursorIndex = mnMaxLength; + } + UpdateStringVisual(); + } + if (mbShift && ch != 0x20) { + ToggleShift(); + } + } +} + +void FEKeyboard::Dispose(bool bBack) { + if (bBack) { + bMemSet(mString, 0, 0x9c); + } + if (bBack == true) { + cFEng::Get()->QueueGameMessage(mnDeclineHash, mThis->GetParentPackage()->GetName(), 0xff); + } else { + cFEng::Get()->QueueGameMessage(mnAcceptHash, mThis->GetParentPackage()->GetName(), 0xff); + } + cFEng::Get()->QueuePackagePop(1); + gFEKeyboard = nullptr; + KeyboardActive = false; +} + +MenuScreen *CreateFEKeyboard(ScreenConstructorData *sd) { + return new ("", 0) FEKeyboard(sd); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.cpp index e69de29bb..0fdbbc7cb 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.cpp @@ -0,0 +1,67 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.hpp" + +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_PostRace.hpp" +#include "Speed/Indep/Src/Gameplay/GInfractionManager.h" +#include "Speed/Indep/Src/Interfaces/SimActivities/INIS.h" + +extern int FEPrintf(const char *pkg_name, int object_hash, const char *fmt, ...); +extern void FEngSetLanguageHash(const char *pkg_name, unsigned int object_hash, unsigned int language_hash); +const char *GetLocalizedString(unsigned int hash); + +BustedOverlayScreen::BustedOverlayScreen(ScreenConstructorData *sd) : MenuScreen(sd) { + if (INIS::Get()) { + if (INIS::Get()->GetType() == CAnimChooser::Arrest) { + PursuitData &pd = PostRacePursuitScreen::GetPursuitData(); + unsigned int languageHashToUse = 0x626ac043; + if (pd.mNumRoadblocksDodged >= 2) { + languageHashToUse = 0x626ac042; + if (pd.mNumSpikeStripsDodged > 1) { + languageHashToUse = 0x626ac045; + } + } else if (pd.mNumCopsDestroyed >= 2) { + languageHashToUse = 0x626ac044; + } else if (static_cast(pd.mCostToStateAchieved) > 10000.0f) { + languageHashToUse = 0x626ac046; + } + Timer finishTimer(pd.mPursuitLength); + char finishTimerString[32]; + finishTimer.PrintToString(finishTimerString, 0); + switch (languageHashToUse) { + case 0x626ac042: + FEPrintf(GetPackageName(), static_cast(0xf1ed545a), + GetLocalizedString(0x626ac042), + pd.mNumCopsDamaged, pd.mNumCopsDestroyed, + pd.mNumRoadblocksDodged, pd.mCostToStateAchieved, + finishTimerString); + break; + case 0x626ac043: + FEPrintf(GetPackageName(), static_cast(0xf1ed545a), + GetLocalizedString(0x626ac043), + finishTimerString, GInfractionManager::Get().GetNumInfractions()); + break; + case 0x626ac044: + FEPrintf(GetPackageName(), static_cast(0xf1ed545a), + GetLocalizedString(0x626ac044), + finishTimerString, pd.mNumCopsDamaged, pd.mNumCopsDestroyed, + GInfractionManager::Get().GetNumInfractions()); + break; + case 0x626ac045: + FEPrintf(GetPackageName(), static_cast(0xf1ed545a), + GetLocalizedString(0x626ac045), + finishTimerString, pd.mNumSpikeStripsDodged, pd.mNumRoadblocksDodged, + GInfractionManager::Get().GetNumInfractions()); + break; + case 0x626ac046: + FEPrintf(GetPackageName(), static_cast(0xf1ed545a), + GetLocalizedString(0x626ac046), + finishTimerString, pd.mCostToStateAchieved, + GInfractionManager::Get().GetNumInfractions()); + break; + } + } else if (INIS::Get()->GetType() == CAnimChooser::Intro) { + FEngSetLanguageHash(GetPackageName(), 0xf1ed545a, 0x626ac047); + } + } +} + +BustedOverlayScreen::~BustedOverlayScreen() {} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.hpp index 5932f151c..29f643a19 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeBustedOverlay.hpp @@ -5,6 +5,13 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +// total size: 0x2C +struct BustedOverlayScreen : public MenuScreen { + BustedOverlayScreen(ScreenConstructorData *sd); + ~BustedOverlayScreen() override; + void NotificationMessage(unsigned long Message, FEObject *pObject, unsigned long Param1, unsigned long Param2) override {} +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeFadeScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeFadeScreen.cpp index e69de29bb..9a5007c55 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeFadeScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/FeFadeScreen.cpp @@ -0,0 +1,19 @@ +#include "Speed/Indep/Src/Frontend/FEManager.hpp" + +bool FEngIsScriptSet(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash); + +FadeScreen::FadeScreen(ScreenConstructorData *sd) : MenuScreen(sd) {} + +FadeScreen::~FadeScreen() {} + +void FadeScreen::NotificationMessage(unsigned long Message, FEObject *pObject, unsigned long Param1, unsigned long Param2) { + if (Message == 0x83323AEB) { + FEManager::Get()->SuppressControllerError(true); + } else if (Message == 0xC7D61AC7) { + FEManager::Get()->SuppressControllerError(false); + } +} + +bool FadeScreen::IsFadeScreenOn() { + return FEngIsScriptSet("FadeScreen.fng", 0x027FF2DC, 0x5079C8F8) != false; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameMovieScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameMovieScreen.cpp index e69de29bb..3075055b9 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameMovieScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameMovieScreen.cpp @@ -0,0 +1,130 @@ +static bool gInGameMoviePlaying; + +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/Src/Frontend/SubTitle.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/Src/Generated/Messages/MNotifyMovieFinished.h" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOn.hpp" +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" + +struct FEMovie; +struct MenuScreen; +struct ScreenConstructorData; + +extern int SkipMovies; +extern const char *GetLoadingScreenPackageName(); +extern void MiniMainLoop(); +extern void DismissChyron(); +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +extern void FEngSetMovieName(FEMovie *movie, const char *name); + +inline void FEngSetMovieName(const char *pkg_name, unsigned int obj_hash, const char *name) { + FEngSetMovieName(reinterpret_cast(FEngFindObject(pkg_name, obj_hash)), name); +} +extern bool TrackStreamerIsLoadingInProgress() asm("IsLoadingInProgress__13TrackStreamer"); + +struct CarLoader { + char _pad[0x14]; // + int LoadingInProgress; // + int IsLoadingInProgress() { return LoadingInProgress; } +}; +extern CarLoader TheCarLoader; + +struct InGameAnyMovieScreen : MenuScreen { + InGameAnyMovieScreen(ScreenConstructorData *sd); + ~InGameAnyMovieScreen() override; + static MenuScreen *Create(ScreenConstructorData *sd); + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) override; + static void LaunchMovie(const char *filename); + void DismissMovie(); + static bool IsPlaying(); + static void SetMovieName(const char *filename); + static const char *GetFEngPackageName(); + static char MovieFilename[64]; + SubTitler mSubtitler; // offset 0x2C + bool bAllowingControllerErrors; // offset 0x50 +}; + +extern bool eIsWidescreen(); + +bool InGameAnyMovieScreen::IsPlaying() { + return gInGameMoviePlaying; +} + +char InGameAnyMovieScreen::MovieFilename[64]; + +void InGameAnyMovieScreen::SetMovieName(const char *filename) { + bStrNCpy(MovieFilename, filename, 0x40); +} + +const char *InGameAnyMovieScreen::GetFEngPackageName() { + bool ps2_widescreen = eIsWidescreen(); + if (!ps2_widescreen) { + return "InGameAnyMovie.fng"; + } + return "WS_InGameAnyMovie.fng"; +} + +MenuScreen *InGameAnyMovieScreen::Create(ScreenConstructorData *sd) { + return new ("", 0) InGameAnyMovieScreen(sd); +} + +InGameAnyMovieScreen::InGameAnyMovieScreen(ScreenConstructorData *sd) : MenuScreen(sd) { + bAllowingControllerErrors = FEManager::Get()->IsAllowingControllerError(); + FEManager::Get()->AllowControllerError(false); + while (TheCarLoader.IsLoadingInProgress() || TrackStreamerIsLoadingInProgress() || + g_pEAXSound->AreResourceLoadsPending()) { + MiniMainLoop(); + } + DismissChyron(); + FEngSetMovieName(GetPackageName(), 0x348ff9f, MovieFilename); + mSubtitler.BeginningMovie(MovieFilename, GetPackageName()); + new EFadeScreenOff(0x14035fb); +} + +InGameAnyMovieScreen::~InGameAnyMovieScreen() { + FEManager::Get()->AllowControllerError(bAllowingControllerErrors); + gInGameMoviePlaying = false; +} + +void InGameAnyMovieScreen::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + mSubtitler.Update(msg); + if (msg != 0xb5af2461) { + if (msg > 0xb5af2461) { + if (msg != 0xc3960eb9) { + return; + } + DismissMovie(); + return; + } + if (msg != 0x406415e3) { + return; + } + } + if (FEDatabase->GetCareerSettings()->GetCurrentBin() < 0x10 || SkipMovies || MoviePlayer_Bypass()) { + mSubtitler.Update(0xc3960eb9); + DismissMovie(); + } +} + +void InGameAnyMovieScreen::LaunchMovie(const char *filename) { + InGameAnyMovieScreen::SetMovieName(filename); + gInGameMoviePlaying = true; + if (cFEng::Get()->IsPackageInControl(GetLoadingScreenPackageName())) { + cFEng::Get()->QueuePackageSwitch(GetFEngPackageName(), 0, 0, false); + } else { + cFEng::Get()->QueuePackagePush(GetFEngPackageName(), 0, 0, false); + } +} + +void InGameAnyMovieScreen::DismissMovie() { + UCrc32 port(0x20d60dbf); + gInGameMoviePlaying = false; + MNotifyMovieFinished msg; + msg.Post(port); + cFEng::mInstance->QueuePackagePop(0); + new EFadeScreenOn(false); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameTutorialScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameTutorialScreen.cpp index e69de29bb..354994039 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameTutorialScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/InGameTutorialScreen.cpp @@ -0,0 +1,159 @@ +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/Src/Frontend/SubTitle.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Generated/Messages/MNotifyMovieFinished.h" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOn.hpp" + +extern unsigned int FEngHashString(const char *, ...); +extern unsigned int bStringHash(const char *, int); + +extern bool gInGameMoviePlaying; + +struct MenuScreen; +struct ScreenConstructorData; + +extern const char *GetLoadingScreenPackageName(); +static const char *InGameTutorialScreenName = "InGameTutorial.fng"; + +struct InGameAnyTutorialScreen : MenuScreen { + InGameAnyTutorialScreen(ScreenConstructorData *sd); + ~InGameAnyTutorialScreen() override; + static MenuScreen *Create(ScreenConstructorData *sd); + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) override; + static void LaunchMovie(const char *filename, const char *packageName); + void DismissMovie(); + static void SetMovieName(const char *filename); + static void SetPackageName(const char *packageName); + static char MovieFilename[64]; + static char PackageFilename[64]; + static bool PackageSet; + SubTitler mSubtitler; // offset 0x2C +}; + +char InGameAnyTutorialScreen::MovieFilename[64]; +char InGameAnyTutorialScreen::PackageFilename[64]; +bool InGameAnyTutorialScreen::PackageSet; + +void InGameAnyTutorialScreen::SetMovieName(const char *filename) { + bStrNCpy(MovieFilename, filename, 0x40); +} + +void InGameAnyTutorialScreen::SetPackageName(const char *packageName) { + PackageSet = true; + bStrNCpy(PackageFilename, packageName, 0x40); +} + +MenuScreen *InGameAnyTutorialScreen::Create(ScreenConstructorData *sd) { + return new ("", 0) InGameAnyTutorialScreen(sd); +} + +InGameAnyTutorialScreen::InGameAnyTutorialScreen(ScreenConstructorData *sd) : MenuScreen(sd) { + bool mSkipable = true; + unsigned int str_hash = 0; + const char *label; + + DismissChyron(); + FEngSetMovieName(GetPackageName(), 0x348ff9f, MovieFilename); + + if (eIsWidescreen()) { + cFEng::Get()->QueuePackageMessage(0x70d2183b, GetPackageName(), 0); + } + + CareerSettings *career = FEDatabase->GetCareerSettings(); + + if (bStrCmp(MovieFilename, "drag_tutorial") == 0) { + if (career && !career->HasDoneDragTutorial()) { + mSkipable = false; + career->SetHasDoneDragTutorial(); + } + label = "TUTORIAL_DRAG"; + } else if (bStrCmp(MovieFilename, "speedtrap_tutorial") == 0) { + if (career && !career->HasDoneSpeedTrapTutorial()) { + mSkipable = false; + career->SetHasDoneSpeedTrapTutorial(); + } + label = "TUTORIAL_SPEEDTRAPRACE"; + } else if (bStrCmp(MovieFilename, "tollbooth_tutorial") == 0) { + if (career && !career->HasDoneTollBoothTutorial()) { + mSkipable = false; + career->SetHasDoneTollBoothTutorial(); + } + label = "TUTORIAL_TOLLBOOTH"; + } else if (bStrCmp(MovieFilename, "bounty_tutorial") == 0) { + if (career && !career->HasDoneBountyTutorial()) { + mSkipable = false; + career->SetHasDoneBountyTutorial(); + } + label = "TUTORIAL_BOUNTY"; + } else if (bStrCmp(MovieFilename, "pursuit_tutorial") == 0) { + if (career && !career->HasDonePursuitTutorial()) { + mSkipable = false; + career->SetHasDonePursuitTutorial(); + } + label = "TUTORIAL_PURSUIT"; + } else { + goto skip_hash; + } + + str_hash = FEngHashString(label); + +skip_hash: + if (mSkipable) { + cFEng::Get()->QueuePackageMessage(0x59291f95, GetPackageName(), 0); + } + + unsigned int label_hash = bStringHash("_LABEL", str_hash); + FEngSetLanguageHash(GetPackageName(), 0x5a0ee0d9, label_hash); + FEngSetLanguageHash(GetPackageName(), 0xf414bf3e, label_hash); + FEngSetLanguageHash(GetPackageName(), 0x5a0ee0d8, label_hash); + FEngSetLanguageHash(GetPackageName(), 0x07d2ea5d, label_hash); + + mSubtitler.BeginningMovie(MovieFilename, GetPackageName()); + new EFadeScreenOff(0x14035fb); +} + +InGameAnyTutorialScreen::~InGameAnyTutorialScreen() { + gInGameMoviePlaying = false; +} + +void InGameAnyTutorialScreen::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + mSubtitler.Update(msg); + if (msg != 0xb5af2461) { + if (msg > 0xb5af2461) { + if (msg != 0xc3960eb9) { + return; + } + DismissMovie(); + return; + } + if (msg != 0x406415e3) { + return; + } + } + DismissMovie(); + mSubtitler.Update(0xc3960eb9); +} + +void InGameAnyTutorialScreen::LaunchMovie(const char *filename, const char *packageName) { + gInGameMoviePlaying = true; + PackageSet = false; + SetMovieName(filename); + if (packageName) { + SetPackageName(packageName); + } + if (cFEng::Get()->IsPackageInControl(GetLoadingScreenPackageName())) { + cFEng::Get()->QueuePackageSwitch(InGameTutorialScreenName, 0, 0, false); + } else { + cFEng::Get()->QueuePackagePush(InGameTutorialScreenName, 0, 0, false); + } +} + +void InGameAnyTutorialScreen::DismissMovie() { + UCrc32 port(0x20d60dbf); + gInGameMoviePlaying = false; + MNotifyMovieFinished msg; + msg.Post(port); + cFEng::mInstance->QueuePackagePop(0); + cFEng::mInstance->QueueGameMessage(0xc3960eb9, PackageFilename, 0xff); + new EFadeScreenOn(false); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.cpp index e69de29bb..f90b7f75b 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.cpp @@ -0,0 +1,480 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.hpp" + +#include "Speed/Indep/Src/Camera/ICE/ICEManager.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/Ecstasy/EcstasyData.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/Localization/Localize.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp" +#include "Speed/Indep/Src/Generated/Events/EAutoSave.hpp" +#include "Speed/Indep/Src/Generated/Events/ECinematicMoment.hpp" +#include "Speed/Indep/Src/Generated/Events/ECameraPhotoFinish.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOn.hpp" +#include "Speed/Indep/Src/Generated/Events/EMomentStrm.hpp" +#include "Speed/Indep/Src/Generated/Events/EQuitToFE.hpp" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOn.hpp" +#include "Speed/Indep/Src/Generated/Events/ERestartRace.hpp" +#include "Speed/Indep/Src/Generated/Events/ESndGameState.hpp" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Generated/Messages/MFlowReadyForOutro.h" +#include "Speed/Indep/Src/Generated/Messages/MMiscSound.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h" +#include "Speed/Indep/Src/World/TrackStreamer.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +extern void eLoadStreamingTexturePack(const char *filename, void (*callback)(void *), void *param, + int flags); +extern void eUnloadStreamingTexturePack(const char *name); +extern void eWaitForStreamingTexturePackLoading(const char *name); +extern void eUnloadStreamingTexture(unsigned int *textures, int count); +extern void eLoadStreamingTexture(unsigned int *textures, int count, void (*callback)(unsigned int), + unsigned int param, int pool); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +extern void FEngSetVisible(FEObject *obj); + +extern Timer RealTimer; + +extern unsigned int bStringHash(const char *str); +extern int FEPrintf(const char *pkg_name, int hash, const char *fmt, ...); +extern FEImage *FEngFindImage(const char *pkg_name, int hash); +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +extern void FEngSetInvisible(FEObject *obj); +extern bool FEngIsScriptSet(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash); +extern bool FEngIsScriptRunning(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash); +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, + bool start_at_beginning); +extern void HideEverySingleHud(); +extern void SoundPause(bool pause, eSNDPAUSE_REASON reason); +extern void StartCinematicSlowdown(EVIEW_ID view_id, float amount); + +extern const char lbl_803E485C[]; +extern const char lbl_803E43DC[]; +extern const char lbl_803E6080[]; +extern const char lbl_803E60A4[]; +extern const char lbl_803E60B8[]; +extern const char lbl_803E60C8[]; +extern const char lbl_803E60D4[]; +extern const char lbl_803E60E0[]; +extern const char lbl_803E60FC[]; +extern const char lbl_803E610C[]; +extern const char lbl_803E611C[]; +extern const char lbl_803E6128[]; +extern const char lbl_803E6138[]; +extern const char lbl_803E6148[]; +extern const char lbl_803E6158[]; +extern const char lbl_803E6164[]; +extern const char lbl_803E619C[]; +extern const char lbl_803E4CF0[]; +extern const char lbl_803E4FF8[]; +extern const char lbl_803E5084[]; +extern const char lbl_803E5EEC[]; +extern const char lbl_803E5F18[]; +extern const char lbl_803E5FD8[]; +extern const char lbl_803E617C[]; +extern const char lbl_803E6190[]; +extern const char lbl_803E61C4[]; +extern const char lbl_803E61D4[]; +extern const char lbl_803E61E0[]; +extern const char lbl_803E61F0[]; +extern const char lbl_803E4744[]; +extern const float lbl_803E60C4; +extern const float lbl_803E61B8; +extern const float lbl_803E61BC; +extern const float lbl_803E61C0; +extern const float lbl_803E6200; +extern const float lbl_803E6204; +extern const float lbl_803E6208; +extern const float lbl_803E620C; +extern const float lbl_803E6210; + +static bool gSillyTextureStreamerActive; + +SillyTextureStreamerManager::SillyTextureStreamerManager(const char *stream_pack) { + bStrNCpy(BundleFileName, stream_pack, 0x100); + bMemSet(LoadInfos, 0, sizeof(LoadInfos)); + mCurrentLoadingIndex = -1; + mMakeSpaceInPoolComplete = false; + mCurrentlyLoading = true; + gSillyTextureStreamerActive = true; + TheTrackStreamer.MakeSpaceInPool(0x60000, MakeSpaceInPoolCallbackBridge, + reinterpret_cast(this)); +} + +SillyTextureStreamerManager::~SillyTextureStreamerManager() { + if (!mMakeSpaceInPoolComplete) { + TheTrackStreamer.WaitForCurrentLoadingToComplete(); + } + eWaitForStreamingTexturePackLoading(nullptr); + for (int i = 0; i < 4; i++) { + if (LoadInfos[i].LoadingTexture) { + unsigned int tex = LoadInfos[i].LoadingTexture; + eUnloadStreamingTexture(&tex, 1); + } + } + eUnloadStreamingTexturePack(BundleFileName); + gSillyTextureStreamerActive = false; +} + +void SillyTextureStreamerManager::MakeSpaceInPoolCallback() { + mMakeSpaceInPoolComplete = true; + eLoadStreamingTexturePack(BundleFileName, reinterpret_cast(LoadCallbackBridge), + reinterpret_cast(this), 0); +} + +void SillyTextureStreamerManager::LoadCallback() { + mCurrentlyLoading = false; + if (mCurrentLoadingIndex >= 0) { + int idx = mCurrentLoadingIndex; + FEngSetTextureHash(LoadInfos[idx].LoadIntoImage, LoadInfos[idx].LoadingTexture); + FEngSetVisible(reinterpret_cast(LoadInfos[idx].LoadIntoImage)); + LoadInfos[idx].IsLoaded = true; + mCurrentLoadingIndex = -1; + } + for (int i = 0; i < 4; i++) { + if (LoadInfos[i].LoadingTexture != 0 && !LoadInfos[i].IsLoaded) { + mCurrentlyLoading = true; + mCurrentLoadingIndex = i; + unsigned int tex = LoadInfos[i].LoadingTexture; + eLoadStreamingTexture(&tex, 1, + LoadCallbackBridge, + reinterpret_cast(this), 7); + return; + } + } + cFEng::Get()->MakeLoadedPackagesDirty(); +} + +void SillyTextureStreamerManager::Load(unsigned int hash, FEImage *image) { + for (int i = 0; i < 4; i++) { + if (LoadInfos[i].LoadingTexture == 0) { + LoadInfos[i].LoadingTexture = hash; + LoadInfos[i].LoadIntoImage = image; + FEngSetInvisible(reinterpret_cast(image)); + if (!mCurrentlyLoading) { + LoadCallback(); + } + return; + } + } +} + +void SillyTextureStreamerManager::UnloadAll() { + for (int i = 0; i < 4; i++) { + if (LoadInfos[i].IsLoaded) { + unsigned int tex = LoadInfos[i].LoadingTexture; + eUnloadStreamingTexture(&tex, 1); + LoadInfos[i].LoadingTexture = 0; + LoadInfos[i].IsLoaded = false; + } + } +} + +bool PhotoFinishScreen::mRestartSelected = false; +float PhotoFinishScreen::mSpeedtrapSpeed = 0.0f; +float PhotoFinishScreen::mSpeedtrapBounty = 0.0f; +bool PhotoFinishScreen::mActive = false; + +PhotoFinishScreen::PhotoFinishScreen(ScreenConstructorData *sd) + : MenuScreen(sd) // + , mIceCamTimer() // + , mSlowdownTimer() // + , fResultType(static_cast< FERESULTTYPE >(sd->Arg)) // + , mPhotoHash(0) // + , StreamTex(lbl_803E6080) { + if (fResultType == FERESULTTYPE_RACE) { + if (GRaceStatus::Exists()) { + GRaceStatus &race_status = GRaceStatus::Get(); + GRaceParameters *race_parameters = race_status.GetRaceParameters(); + if (race_parameters != nullptr) { + bool is_boss_race = race_parameters->GetIsBossRace(); + const char *photo_texture = lbl_803E60A4; + + if (race_status.GetRaceContext() != GRace::kRaceContext_Career || !is_boss_race) { + photo_texture = race_parameters->GetPhotoFinishTexture(); + } + + mPhotoHash = bStringHash(photo_texture); + StreamTex.Load(mPhotoHash, FEngFindImage(GetPackageName(), 0x286A9CD4)); + } + } + } + + mSlowdownTimer = RealTimer; + + StartCinematicSlowdown(static_cast< EVIEW_ID >(1), lbl_803E60C4); + SetSoundControlState(true, static_cast< eSNDCTLSTATE >(0xF), lbl_803E60B8); + new EMomentStrm(UMath::Vector4::kZero, UMath::Vector4::kZero, UMath::Vector4::kZero, 0, nullptr, 0x9FE1EE17); +} + +void PhotoFinishScreen::Setup() { + FEManager *fe_manager = FEManager::Get(); + reinterpret_cast< unsigned int * >(fe_manager)[1] = 1; + + unsigned int locale_hash = 0x8569AB44; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + locale_hash = 0x8569A25F; + } + + if (fResultType == FERESULTTYPE_SPEEDTRAP) { + float display_speed = mSpeedtrapSpeed * (locale_hash == 0x8569A25F ? lbl_803E6200 : lbl_803E6204); + unsigned int speed_hash = bStringHash(lbl_803E61C4); + + FEPrintf(GetPackageName(), speed_hash, lbl_803E4FF8, GetTranslatedString(locale_hash), display_speed); + unsigned int bounty_hash = bStringHash(lbl_803E61D4); + FEPrintf(GetPackageName(), bounty_hash, GetTranslatedString(0x060C058A), static_cast< int >(mSpeedtrapBounty)); + return; + } + + GRaceStatus &race_status = GRaceStatus::Get(); + GRaceParameters *race_params = race_status.GetRaceParameters(); + GRacerInfo *racer_info = &race_status.GetRacerInfo(0); + int racer_count = race_status.GetRacerCount(); + + for (int i = 0; i < racer_count; ++i) { + racer_info = &race_status.GetRacerInfo(i); + if (racer_info->GetSimable() != nullptr) { + break; + } + } + + float cash = race_params->GetCashValue(); + float finishing_speed = racer_info->GetFinishingSpeed() * lbl_803E6204; + float point_total = racer_info->GetPointTotal(); + + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + finishing_speed *= lbl_803E6208; + } else { + point_total *= lbl_803E620C; + point_total *= lbl_803E6204; + } + + Timer race_time; + Timer lap_time; + char race_time_buffer[32]; + char lap_time_buffer[32]; + char summary_buffer[64]; + + race_time.SetTime(race_status.GetRaceTimeRemaining()); + race_time.PrintToString(race_time_buffer, 0); + lap_time.SetTime(racer_info->GetRaceTimer().GetTime()); + lap_time.PrintToString(lap_time_buffer, 0); + + bSNPrintf(summary_buffer, 64, lbl_803E61E0, lap_time_buffer, GetTranslatedString(0x474), + GetTranslatedString(locale_hash), finishing_speed); + + unsigned int result_hash; + + if (FEngIsScriptSet(GetPackageName(), bStringHash(lbl_803E617C), 0x5079C8F8)) { + FEPrintf(GetPackageName(), 0x8BB39726, lbl_803E4FF8, GetTranslatedString(locale_hash), finishing_speed); + FEPrintf(GetPackageName(), 0x424BB244, lbl_803E4CF0, summary_buffer); + FEPrintf(GetPackageName(), 0x8A7F929C, lbl_803E5084, race_time_buffer); + result_hash = 0x42423E94; + } else if (FEngIsScriptSet(GetPackageName(), bStringHash(lbl_803E6190), 0x5079C8F8)) { + if (race_params->GetRaceType() == GRace::kRaceType_SpeedTrap) { + FEPrintf(GetPackageName(), 0x37BEA03B, lbl_803E61F0, GetTranslatedString(0x7F54569D), + GetTranslatedString(locale_hash), point_total); + } else { + FEPrintf(GetPackageName(), 0x37BEA03B, lbl_803E4CF0, summary_buffer); + } + result_hash = 0x9F4DF5BB; + } else { + if (race_params->GetRaceType() == GRace::kRaceType_SpeedTrap) { + FEPrintf(GetPackageName(), 0xAB6AAFDD, lbl_803E61F0, GetTranslatedString(0x7F54569D), + GetTranslatedString(locale_hash), point_total); + } else { + FEPrintf(GetPackageName(), 0xAB6AAFDD, lbl_803E4CF0, summary_buffer); + } + result_hash = 0x3D1773DD; + } + + if (cash > lbl_803E6210) { + FEPrintf(GetPackageName(), result_hash, lbl_803E5FD8, GetTranslatedString(0xB7F2B3C8), cash); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), result_hash)); + } + + if (race_params->GetEventHash() == Attrib::StringHash32(lbl_803E4744)) { + DialogInterface::ShowOneButton(GetPackageName(), lbl_803E43DC, static_cast< eDialogTitle >(1), + 0x417B2601, 0x1FAB5998, 0x4C54B7EA); + FEDatabase->GetCareerSettings()->SpecialFlags |= 0x2000; + } +} + +void PhotoFinishScreen::NotificationMessage(unsigned long msg, FEObject *, unsigned long, unsigned long) { + switch (msg) { + case 0x406415E3: + if (fResultType == FERESULTTYPE_SPEEDTRAP) { + new EUnPause(); + new EAutoSave(); + + MFlowReadyForOutro().Post(UCrc32(0x20D60DBF)); + SoundPause(false, static_cast< eSNDPAUSE_REASON >(0xA)); + SetSoundControlState(false, static_cast< eSNDCTLSTATE >(0xC), lbl_803E60D4); + return; + } + + if (FEngIsScriptSet(GetPackageName(), 0x286A9CD4, 0x0016A259)) { + return; + } + + if (FEngIsScriptRunning(GetPackageName(), 0x286A9CD4, 0x5079C8F8)) { + return; + } + + if (GRaceStatus::Exists() && GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + GRaceParameters *race_parameters = GRaceStatus::Get().GetRaceParameters(); + if (race_parameters->GetIsBossRace()) { + unsigned char current_bin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + GRaceBin *race_bin = GRaceDatabase::Get().GetBinNumber(current_bin); + int remaining_races = 0; + + for (unsigned int i = 0; i < race_bin->GetBossRaceCount(); ++i) { + if (!GRaceDatabase::Get().CheckRaceScoreFlags(race_bin->GetBossRaceHash(i), + GRaceDatabase::kCompleted_ContextCareer)) { + ++remaining_races; + } + } + + new EFadeScreenOn(false); + + if (current_bin != 1) { + if (remaining_races == 0) { + new EQuitToFE(GARAGETYPE_CAREER_SAFEHOUSE, lbl_803E60E0); + return; + } + } else if (remaining_races == 1) { + cFEng::Get()->QueuePackagePop(1); + + MFlowReadyForOutro().Post(UCrc32(0x20D60DBF)); + return; + } + + cFEng::Get()->QueuePackagePop(1); + new ERaceSheetOn(2); + return; + } + + new EUnPause(); + + MFlowReadyForOutro().Post(UCrc32(0x20D60DBF)); + return; + } + + if ((FEDatabase->GetGameMode() & 0x2) && MemoryCard::GetInstance()->ShouldDoAutoSave(false)) { + MemcardEnter(nullptr, nullptr, 0x100B1, nullptr, nullptr, 0, 0); + } else { + new EQuitToFE(GARAGETYPE_MAIN_FE, nullptr); + } + return; + case 0xC519BFC3: + if (fResultType != FERESULTTYPE_SPEEDTRAP) { + cFEng::Get()->QueuePackageMessage(bStringHash(lbl_803E60FC), GetPackageName(), nullptr); + if (!cFEng::Get()->IsPackagePushed(lbl_803E5F18)) { + cFEng::Get()->QueuePackagePush(lbl_803E5F18, 0, 0, false); + } + new EShowResults(fResultType, false); + } + return; + case 0xC519BFC4: + if (fResultType != FERESULTTYPE_SPEEDTRAP) { + DialogInterface::ShowTwoButtons(GetPackageName(), lbl_803E5EEC, static_cast< eDialogTitle >(1), + 0x417B2601, 0x1A294DAD, 0xE1A57D51, 0xB4623F67, 0xB4623F67, + static_cast< eDialogFirstButtons >(1), 0x4D3399A8); + } + return; + case 0xE1A57D51: + cFEng::Get()->QueuePackageMessage(bStringHash(lbl_803E610C), GetPackageName(), nullptr); + mRestartSelected = true; + new EUnPause(); + return; + case 0xC98356BA: { + if (mSlowdownTimer.IsSet() && + static_cast< float >(RealTimer.GetPackedTime() - mSlowdownTimer.GetPackedTime()) * lbl_803E61B8 >= + lbl_803E61BC) { + mSlowdownTimer.UnSet(); + mIceCamTimer = RealTimer; + + HideEverySingleHud(); + FEManager::RequestPauseSimulation(GetPackageName()); + *reinterpret_cast< unsigned int * >(reinterpret_cast< char * >(&TheICEManager) + 0x7C) = 1; + + if (fResultType == FERESULTTYPE_PURSUIT) { + new ECinematicMoment(lbl_803E611C, lbl_803E6128, lbl_803E61C0); + } else if (fResultType == FERESULTTYPE_SPEEDTRAP) { + new ECinematicMoment(lbl_803E611C, lbl_803E6138, lbl_803E61C0); + } else { + new ECinematicMoment(lbl_803E611C, GRaceStatus::Get().GetRaceParameters()->GetPhotoFinishCamera(), + lbl_803E61C0); + } + return; + } + + if (mIceCamTimer.IsSet() && + static_cast< float >(RealTimer.GetPackedTime() - mIceCamTimer.GetPackedTime()) * lbl_803E61B8 >= + lbl_803E61BC) { + mIceCamTimer.UnSet(); + + if (!FEngIsScriptSet(GetPackageName(), 0x47FF4E7C, 0x0013C37B)) { + FEngSetScript(GetPackageName(), 0x47FF4E7C, 0x0013C37B, true); + } + + if (fResultType == FERESULTTYPE_SPEEDTRAP) { + if (!FEngIsScriptSet(GetPackageName(), 0x857FB472, 0x5079C8F8)) { + FEngSetScript(GetPackageName(), 0x857FB472, 0x5079C8F8, true); + } + + FEngSetScript(GetPackageName(), bStringHash(lbl_803E6148), 0x5079C8F8, true); + cFEng::Get()->QueuePackageMessage(bStringHash(lbl_803E6158), GetPackageName(), nullptr); + } else { + if (mPhotoHash == bStringHash(lbl_803E6164)) { + FEngSetScript(GetPackageName(), bStringHash(lbl_803E617C), 0x5079C8F8, true); + } else if (mPhotoHash == bStringHash(lbl_803E60A4)) { + FEngSetScript(GetPackageName(), bStringHash(lbl_803E6190), 0x5079C8F8, true); + } else { + FEngSetScript(GetPackageName(), bStringHash(lbl_803E619C), 0x5079C8F8, true); + } + + if (!FEngIsScriptSet(GetPackageName(), 0x286A9CD4, 0x5079C8F8)) { + FEngSetScript(GetPackageName(), 0x286A9CD4, 0x5079C8F8, true); + } + } + + Setup(); + *reinterpret_cast< unsigned int * >(reinterpret_cast< char * >(&TheICEManager) + 0x7C) = 0; + + MMiscSound sound_message(2); + sound_message.Send(Attrib::StringHash32(lbl_803E485C)); + + new ESndGameState(7, true); + SoundPause(true, static_cast< eSNDPAUSE_REASON >(0xA)); + SetSoundControlState(false, static_cast< eSNDCTLSTATE >(0xF), lbl_803E60B8); + SetSoundControlState(true, static_cast< eSNDCTLSTATE >(1), lbl_803E60D4); + } + return; + } + } +} + +PhotoFinishScreen::~PhotoFinishScreen() { + StreamTex.UnloadAll(); + + if (mRestartSelected) { + mRestartSelected = false; + new ERestartRace(); + } + + TheICEManager.SetGenericCameraToPlay(lbl_803E43DC, lbl_803E43DC); + new ESndGameState(7, false); + SetSoundControlState(false, static_cast< eSNDCTLSTATE >(1), lbl_803E60C8); + mActive = false; +} + +MenuScreen *PhotoFinishScreen::Create(ScreenConstructorData *sd) { + return new ("", 0) PhotoFinishScreen(sd); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.hpp index a750400b7..3de839dfd 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/PhotoFinish.hpp @@ -5,6 +5,64 @@ #pragma once #endif +#include "Speed/Indep/Src/Generated/Events/EShowResults.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +struct FEImage; + +struct load_info { + FEImage *LoadIntoImage; + unsigned int LoadingTexture; + bool IsLoaded; +}; + +class SillyTextureStreamerManager { + public: + SillyTextureStreamerManager(const char *stream_pack); + ~SillyTextureStreamerManager(); + void Load(unsigned int hash, FEImage *image); + void Unload(unsigned int hash); + void UnloadAll(); + bool IsLoaded(unsigned int hash); + bool IsBusyLoading(); + + private: + static void MakeSpaceInPoolCallbackBridge(int param) { + reinterpret_cast(param)->MakeSpaceInPoolCallback(); + } + void MakeSpaceInPoolCallback(); + static void LoadCallbackBridge(unsigned int param) { + reinterpret_cast(param)->LoadCallback(); + } + void LoadCallback(); + + char BundleFileName[256]; + load_info LoadInfos[4]; + bool mCurrentlyLoading; + bool mMakeSpaceInPoolComplete; + int mCurrentLoadingIndex; +}; + +class PhotoFinishScreen : public MenuScreen { + public: + PhotoFinishScreen(ScreenConstructorData *sd); + ~PhotoFinishScreen() override; + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void Setup(); + static MenuScreen *Create(ScreenConstructorData *sd); + static bool mRestartSelected; + + protected: + static float mSpeedtrapSpeed; + static float mSpeedtrapBounty; + static bool mActive; + + Timer mIceCamTimer; + Timer mSlowdownTimer; + FERESULTTYPE fResultType; + int mPhotoHash; + SillyTextureStreamerManager StreamTex; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.cpp index e69de29bb..0770236c2 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.cpp @@ -0,0 +1,466 @@ +#include "uiPause.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/FEPkg_PostRace.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Misc/DemoDisc.hpp" +#include "Speed/Indep/Src/Generated/Events/EQuitDemo.hpp" +#include "Speed/Indep/Src/Generated/Events/EQuitToFE.hpp" +#include "Speed/Indep/Src/Generated/Events/ERestartRace.hpp" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Generated/Messages/MNotifyRaceAbandoned.h" +#include "Speed/Indep/Src/Frontend/MenuScreens/InGame/CustomTuning.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" + +struct FEObject; + +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, + bool start_at_beginning); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +unsigned char FEngGetLastButton(const char* pkg_name); +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +void FEngSetInvisible(FEObject* obj); + +unsigned long PauseMenu::mSelectionHash; + +PauseMenu::PauseMenu(ScreenConstructorData* sd) + : IconScrollerMenu(sd) // +{ + mCalledFromPostRace = (sd->Arg != 0); + Options.SetIdleColor(0xFFFFAE40); + Options.SetFadeColor(0x00FFAE40); + FEDatabase->GetOptionsSettings()->CurrentCategory = static_cast(0); + Setup(); +} + +PauseMenu::~PauseMenu() {} + +eMenuSoundTriggers PauseMenu::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (msg == 0x480C9A58 && mCalledFromPostRace) { + return static_cast(-1); + } + return maybe; +} + +void PauseMenu::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, + unsigned long param2) { + if (msg != 0x911AB364 || !mCalledFromPostRace) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + } + if (msg == 0x9120409E) { + return; + } + if (msg > 0x9120409E) { + goto msg_gt_9120409E; + } + if (msg == 0x43DA9FD0) { + goto show_script; + } + if (msg > 0x43DA9FD0) { + goto msg_gt_43DA9FD0; + } + if (msg == 0x30EB8F53 || msg == 0x30F32A49) { + goto show_script; + } + return; + +msg_gt_43DA9FD0: + if (msg == 0x451E768E) { + goto show_script; + } + if (msg == 0x911AB364) { + goto message_911AB364; + } + return; + +msg_gt_9120409E: + if (msg == 0xB5AF2461) { + goto message_B5AF2461; + } + if (msg > 0xB5AF2461) { + goto msg_gt_B5AF2461; + } + if (msg == 0xB4623F67) { + goto message_B4623F67; + } + return; + +msg_gt_B5AF2461: + if (msg == 0xE1A57D51) { + goto show_script; + } + if (msg > 0xE1A57D51) { + goto msg_gt_E1A57D51; + } + if (msg == 0xC9BFD1C3) { + goto show_script; + } + return; + +msg_gt_E1A57D51: + if (msg == 0xE1FDE1D1) { + goto message_E1FDE1D1; + } + return; + +message_911AB364: + if (mCalledFromPostRace) { + return; + } + FEngSetScript(GetPackageName(), 0x47FF4E7C, 0xDE6EFF34, true); + StorePrevNotification(0x911AB364, pobj, param1, param2); + return; + +message_B5AF2461: { + if (mCalledFromPostRace) { + return; + } + const char* pkg = GetPackageName(); + mSelectionHash = 0xFDAE152F; + FEngSetScript(pkg, 0x47FF4E7C, 0xDE6EFF34, true); + return; +} + +show_script: + FEngSetScript(GetPackageName(), 0x47FF4E7C, 0xDE6EFF34, true); + return; + +message_B4623F67: + Options.StartFadeIn(); + cFEng::Get()->QueuePackageMessage(0xC6341FF6, GetPackageName(), 0); + return; + +message_E1FDE1D1: + if (PrevButtonMessage != 0x911AB364) { + switch (mSelectionHash) { + case 0xFBDF2EE3: + if (GRaceStatus::Exists() && GRaceStatus::Get().GetRaceParameters() + && GRaceStatus::Get().GetRaceParameters()->GetIsDDayRace()) { + MemoryCard::GetInstance()->CancelNextAutoSave(); + } + new ERestartRace(); + break; + case 0xFDAE152F: + break; + case 0xCDD2635A: { + new EUnPause(); + if (GRaceStatus::Exists()) { + GRaceStatus::Get().RaceAbandoned(); + } + MNotifyRaceAbandoned().Post(MNotifyRaceAbandoned::_GetKind()); + return; + } + case 0x0506202D: + new EQuitDemo(DEMO_DISC_ENDREASON_PLAYABLE_QUIT); + return; + case 0x33195CF0: + FEDatabase->SetGameMode(eFE_GAME_MODE_OPTIONS); + cFEng::Get()->QueuePackageSwitch("Pause_Main.fng", 1, 0, false); + return; + case 0x78F1C035: + cFEng::Get()->QueuePackageSwitch("Pause_Performance_Tuning.fng", 0, 0, false); + return; + case 0xE5C9C609: { + if (GRaceStatus::Exists()) { + GRaceStatus::Get().RaceAbandoned(); + } + new EQuitToFE(GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career + ? static_cast(2) + : static_cast(1), + static_cast(0)); + return; + } + case 0x85162CB0: + if (GRaceStatus::Exists()) { + GRaceStatus::Get().RaceAbandoned(); + } + new EQuitToFE(static_cast(1), "MainMenu.fng"); + return; + default: + return; + } + } + new EUnPause(); + return; +} + +bool PauseMenu::IsTuningAvailable() { + bool avail = false; + unsigned int player_car; + if (FEDatabase->IsCareerMode()) { + player_car = FEDatabase->GetCareerSettings()->GetCurrentCar(); + } else { + player_car = FEDatabase->GetQuickRaceSettings(GRace::kRaceType_NumTypes)->GetSelectedCar(0); + } + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord* record = stable->GetCarRecordByHandle(player_car); + FECustomizationRecord* custom = stable->GetCustomizationRecordByHandle(record->Customization); + if (custom != nullptr) { + for (int i = 0; i <= 6; i++) { + avail = avail | CustomTuningScreen::IsTuningAvailable(stable, record, static_cast< Physics::Tunings::Path >(i)); + } + } + return avail; +} + +void PauseMenu::Setup() { + if (mCalledFromPostRace) { + FEngSetLanguageHash(GetPackageName(), 0x863404B5, 0x376EB982); + } else { + FEngSetLanguageHash(GetPackageName(), 0x863404B5, 0x6C839FBE); + } + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_TimeTrial) { + SetupOnlineOptions(); + } else { + SetupOptions(); + } + int lastButton = FEngGetLastButton(GetPackageName()); + if (bFadeInIconsImmediately) { + Options.StartFadeIn(); + } + Options.SetInitialPos(lastButton); + RefreshHeader(); +} + +void PauseMenu::SetupOptions() { + if (mCalledFromPostRace) { + FEngSetInvisible(GetPackageName(), 0x812A09D4); + } + if (mCalledFromPostRace) { + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + if (FEDatabase->IsDDay() || FEDatabase->IsFinalEpicChase()) { + AddOption(new("", 0) pm_RestartRace(0xB295A6B6, 0xF893AFA1, 0)); + AddOption(new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0x690E9B7C, 0)); + } else { + AddOption(new("", 0) pm_RestartRace(0xB295A6B6, 0xF893AFA1, 0)); + AddOption(new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0x3C14C420, 0)); + AddOption(new("", 0) pm_QuitRaceToFreeRoam(0x56FFBD2C, 0x9DC599B0, 0)); + } + } else { + AddOption(new("", 0) pm_RestartRace(0xB295A6B6, 0xF893AFA1, 0)); + GRaceParameters* pParams = GRaceStatus::Get().GetRaceParameters(); + if (pParams != nullptr && pParams->GetIsChallengeSeriesRace()) { + pm_QuitMainMenu* opt = new("", 0) pm_QuitMainMenu(0x4C9E34E6, 0xE950B7AF, 0); + AddOption(opt); + } else { + pm_QuitQuickRace* opt = new("", 0) pm_QuitQuickRace(0x4C9E34E6, 0x4349998B, 0); + AddOption(opt); + } + } + return; + } + if (GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career) { + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Roaming) { + if (FEDatabase->IsDDay()) { + AddOption(new("", 0) pm_ResumeFreeRoam(0x12BB5EA2, 0x01BD185C, 0)); + AddOption(new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0x690E9B7C, 0)); + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } else if (FEDatabase->IsFinalEpicChase()) { + AddOption(new("", 0) pm_ResumeFreeRoam(0x12BB5EA2, 0x01BD185C, 0)); + AddOption(new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0x690E9B7C, 0)); + bool tuningAvailable = IsTuningAvailable(); + pm_SwitchToTuning* tuning = new("", 0) pm_SwitchToTuning(0x483238FD, 0x6A3672A2, 0); + tuning->Locked = !tuningAvailable; + AddOption(tuning); + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } else if (PostRacePursuitScreen::GetPursuitData().mPursuitIsActive) { + AddOption(new("", 0) pm_ResumeFreeRoam(0x12BB5EA2, 0x01BD185C, 0)); + bool tuningAvailable = IsTuningAvailable(); + pm_SwitchToTuning* tuning = new("", 0) pm_SwitchToTuning(0x483238FD, 0x6A3672A2, 0); + tuning->Locked = !tuningAvailable; + AddOption(tuning); + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } else { + AddOption(new("", 0) pm_ResumeFreeRoam(0x12BB5EA2, 0x01BD185C, 0)); + AddOption(new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0x3C14C420, 0)); + bool tuningAvailable = IsTuningAvailable(); + pm_SwitchToTuning* tuning = new("", 0) pm_SwitchToTuning(0x483238FD, 0x6A3672A2, 0); + tuning->Locked = !tuningAvailable; + AddOption(tuning); + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } + } else { + GRaceParameters* pParams = nullptr; + if (GRaceStatus::Exists()) { + pParams = GRaceStatus::Get().GetRaceParameters(); + } + bool isEpicPursuit = false; + if (pParams != nullptr && pParams->GetIsEpicPursuitRace()) { + isEpicPursuit = true; + } + if (FEDatabase->IsDDay()) { + AddOption(new("", 0) pm_ResumeRace(0x12BB5EA2, 0xDED357E7, 0)); + AddOption(new("", 0) pm_RestartRace(0xB295A6B6, 0xF893AFA1, 0)); + AddOption(new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0x690E9B7C, 0)); + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } else if (FEDatabase->IsFinalEpicChase() || isEpicPursuit) { + AddOption(new("", 0) pm_ResumeRace(0x12BB5EA2, 0xDED357E7, 0)); + AddOption(new("", 0) pm_RestartRace(0xB295A6B6, 0xF893AFA1, 0)); + AddOption(new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0x690E9B7C, 0)); + bool tuningAvailable = IsTuningAvailable(); + pm_SwitchToTuning* tuning = new("", 0) pm_SwitchToTuning(0x483238FD, 0x6A3672A2, 0); + tuning->Locked = !tuningAvailable; + AddOption(tuning); + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } else { + AddOption(new("", 0) pm_ResumeRace(0x12BB5EA2, 0xDED357E7, 0)); + AddOption(new("", 0) pm_RestartRace(0xB295A6B6, 0xF893AFA1, 0)); + AddOption(new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0x3C14C420, 0)); + AddOption(new("", 0) pm_QuitRaceToFreeRoam(0x56FFBD2C, 0x9DC599B0, 0)); + bool tuningAvailable = IsTuningAvailable(); + pm_SwitchToTuning* tuning = new("", 0) pm_SwitchToTuning(0x483238FD, 0x6A3672A2, 0); + tuning->Locked = !tuningAvailable; + AddOption(tuning); + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } + } + } else { + int userMode = Sim::GetUserMode(); + if (userMode == 1) { + AddOption(new("", 0) pm_ResumeRace(0x12BB5EA2, 0xDED357E7, 0)); + AddOption(new("", 0) pm_RestartRace(0xB295A6B6, 0xF893AFA1, 0)); + AddOption(new("", 0) pm_QuitQuickRace(0x4C9E34E6, 0x4349998B, 0)); + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } else { + AddOption(new("", 0) pm_ResumeRace(0x12BB5EA2, 0xDED357E7, 0)); + AddOption(new("", 0) pm_RestartRace(0xB295A6B6, 0xF893AFA1, 0)); + GRaceParameters* pParams = GRaceStatus::Get().GetRaceParameters(); + if (pParams != nullptr && pParams->GetIsChallengeSeriesRace()) { + pm_QuitMainMenu* opt = new("", 0) pm_QuitMainMenu(0x4C9E34E6, 0xE950B7AF, 0); + AddOption(opt); + } else { + pm_QuitQuickRace* opt = new("", 0) pm_QuitQuickRace(0x4C9E34E6, 0x4349998B, 0); + AddOption(opt); + } + if (!GRaceStatus::IsTollboothRace() && + (pParams == nullptr || !pParams->GetIsChallengeSeriesRace())) { + bool tuningAvailable = IsTuningAvailable(); + pm_SwitchToTuning* tuning = new("", 0) pm_SwitchToTuning(0x483238FD, 0x6A3672A2, 0); + tuning->Locked = !tuningAvailable; + AddOption(tuning); + } + AddOption(new("", 0) pm_SwitchToOptions(0x520DE4E3, 0x2B5A03A8, 0)); + } + } +} + +void PauseMenu::SetupOnlineOptions() { + pm_QuitRaceToFE* opt = new("", 0) pm_QuitRaceToFE(0x4C9E34E6, 0xF95320B8, 0); + AddOption(opt); +} + +void pm_ResumeRace::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + PauseMenu::SetSelectionHash(0xFDAE152F); + FEngSetScript(pkg_name, 0x47FF4E7C, 0xDE6EFF34, true); + } +} + +void pm_ResumeFreeRoam::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + PauseMenu::SetSelectionHash(0xFDAE152F); + FEngSetScript(pkg_name, 0x47FF4E7C, 0xDE6EFF34, true); + } +} + +void pm_RestartRace::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + PauseMenu::SetSelectionHash(0xFBDF2EE3); + DialogInterface::ShowTwoButtons(pkg_name, "InGameDialog.fng", + static_cast(1), 0x417B2601, 0x1A294DAD, + 0xE1A57D51, 0xB4623F67, 0xB4623F67, + static_cast(1), 0x4D3399A8); + } +} + +void pm_SwitchToOptions::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + PauseMenu::SetSelectionHash(0x33195CF0); + FEngSetScript(pkg_name, 0x47FF4E7C, 0xDE6EFF34, true); + } +} + +void pm_SwitchToTuning::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + PauseMenu::SetSelectionHash(0x78F1C035); + if (Locked) { + DialogInterface::ShowOneButton(pkg_name, "InGameDialog.fng", + static_cast(1), 0x417B2601, 0xB4623F67, + 0xB4623F67, 0xA7EE8554); + } else { + FEngSetScript(pkg_name, 0x47FF4E7C, 0xDE6EFF34, true); + } + } +} + +void pm_QuitMainMenu::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + PauseMenu::SetSelectionHash(0xE5C9C609); + DialogInterface::ShowTwoButtons(pkg_name, "InGameDialog.fng", + static_cast(1), 0x417B2601, 0x1A294DAD, + 0xC9BFD1C3, 0xB4623F67, 0xB4623F67, + static_cast(1), 0xA2E9B449); + } +} + +void pm_QuitQuickRace::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + PauseMenu::SetSelectionHash(0xE5C9C609); + DialogInterface::ShowTwoButtons(pkg_name, "InGameDialog.fng", + static_cast(1), 0x417B2601, 0x1A294DAD, + 0x30F32A49, 0xB4623F67, 0xB4623F67, + static_cast(1), 0x1DB1CDE5); + } +} + +void pm_QuitRaceToFreeRoam::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + PauseMenu::SetSelectionHash(0xCDD2635A); + DialogInterface::ShowTwoButtons(pkg_name, "InGameDialog.fng", + static_cast(1), 0x417B2601, 0x1A294DAD, + 0x451E768E, 0xB4623F67, 0xB4623F67, + static_cast(1), 0x9887EB98); + } +} + +void pm_QuitRaceToFE::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + unsigned int quitMessageHash = 0; + PauseMenu::SetSelectionHash(0xE5C9C609); + GRace::Context ctx = GRaceStatus::Get().GetRaceContext(); + if (ctx == GRace::kRaceContext_TimeTrial) { + } else if (ctx == GRace::kRaceContext_QuickRace) { + quitMessageHash = 0x1DB1CDE5; + } else { + if (FEDatabase->IsDDay() || FEDatabase->IsFinalEpicChase()) { + quitMessageHash = 0xECD92696; + } else { + if (GRaceStatus::Get().GetPlayMode() == GRaceStatus::kPlayMode_Racing) { + quitMessageHash = 0xCDE4CAE8; + } else { + if (PostRacePursuitScreen::GetPursuitData().mPursuitIsActive) { + quitMessageHash = 0x15A1B5A9; + } else { + quitMessageHash = 0x6925D0BE; + } + } + } + } + DialogInterface::ShowTwoButtons(pkg_name, "InGameDialog.fng", + static_cast(1), 0x417B2601, 0x1A294DAD, + 0x43DA9FD0, 0xB4623F67, 0xB4623F67, + static_cast(1), quitMessageHash); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.hpp index d298cc108..966b3b3be 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiPause.hpp @@ -1,10 +1,97 @@ -#ifndef FRONTEND_MENUSCREENS_INGAME_UIPAUSE_H -#define FRONTEND_MENUSCREENS_INGAME_UIPAUSE_H +#ifndef _PAUSEMENU +#define _PAUSEMENU -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" + +// total size: 0x170 +struct PauseMenu : public IconScrollerMenu { + static unsigned long mSelectionHash; + + bool mCalledFromPostRace; // offset 0x16C, size 0x1 + + PauseMenu(ScreenConstructorData* sd); + ~PauseMenu() override; + + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + + bool IsTuningAvailable(); + void Setup() override; + void SetupOptions(); + void SetupOnlineOptions(); + + static void SetSelectionHash(unsigned long selectionHash) { + mSelectionHash = selectionHash; + } + + static unsigned long GetSelectionHash() { + return mSelectionHash; + } +}; + +struct pm_ResumeRace : public IconOption { + pm_ResumeRace(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_ResumeRace() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct pm_ResumeFreeRoam : public IconOption { + pm_ResumeFreeRoam(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_ResumeFreeRoam() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct pm_RestartRace : public IconOption { + pm_RestartRace(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_RestartRace() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct pm_SwitchToOptions : public IconOption { + pm_SwitchToOptions(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_SwitchToOptions() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct pm_SwitchToTuning : public IconOption { + pm_SwitchToTuning(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_SwitchToTuning() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct pm_QuitMainMenu : public IconOption { + pm_QuitMainMenu(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_QuitMainMenu() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct pm_QuitQuickRace : public IconOption { + pm_QuitQuickRace(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_QuitQuickRace() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; +struct pm_QuitRaceToFreeRoam : public IconOption { + pm_QuitRaceToFreeRoam(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_QuitRaceToFreeRoam() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; +struct pm_QuitRaceToFE : public IconOption { + pm_QuitRaceToFE(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) { SetReactImmediately(true); } + ~pm_QuitRaceToFE() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.cpp index e69de29bb..79813649c 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.cpp @@ -0,0 +1,347 @@ +#include "uiSMS.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +struct FEObject; +struct FEGroup; + +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +FEObject* FEngFindGroup(const char* pkg_name, unsigned int obj_hash); +FEImage* FEngFindImage(const char* pkg_name, int hash); +FEString* FEngFindString(const char* pkg_name, int name_hash); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +void FEngSetLanguageHash(FEString* text, unsigned int hash); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning); +unsigned int bStringHash(const char* str); + +SMSMessage* the_sms_msg; + +struct SMSDatum : public ArrayDatum { + SMSMessage* my_msg; // offset 0x24, size 0x4 + + SMSDatum(SMSMessage* msg) + : ArrayDatum(0, 0) // + , my_msg(msg) + {} + + ~SMSDatum() override {} + + void NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) override; +}; + +struct SMSSortNode : public bTNode { + SMSMessage* the_msg; // offset 0x8, size 0x4 + + SMSSortNode(SMSMessage* msg) + : the_msg(msg) + {} + + ~SMSSortNode() {} +}; + +void SMSDatum::NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) { + if (msg != 0x0C407210) { + return; + } + the_sms_msg = my_msg; +} + +int SortSMS(SMSSortNode* before, SMSSortNode* after) { + return after->the_msg->GetSortOrder() < before->the_msg->GetSortOrder(); +} + +void SMSSlot::Update(ArrayDatum* datum, bool isSelected) { + ArraySlot::Update(datum, isSelected); + if (datum != nullptr) { + SMSMessage* msg = static_cast(datum)->my_msg; + FEngSetLanguageHash(pText, FEngHashString("SMS_SUBJECT_%d", msg->GetHandle())); + if (datum->IsChecked()) { + FEngSetVisible(reinterpret_cast(pIcon)); + } else { + FEngSetInvisible(reinterpret_cast(pIcon)); + } + } +} + +uiSMS::uiSMS(ScreenConstructorData* sd) + : ArrayScrollerMenu(sd, 1, 6, true) { + bInitCompleted = false; + bVoiceMsg = true; + button_pressed = 0; + bAutoPlay = false; + bWaitingForMemcard = true; + for (int i = 0; i < 2; i++) { + last_msg[i] = 0xFF; + } + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + AddSMSSlot(i + 1); + } + Setup(); + the_sms_msg = reinterpret_cast(sd->Arg); + if (the_sms_msg == nullptr) { + if (GetCurrentDatum() == nullptr) { + the_sms_msg = nullptr; + } else { + the_sms_msg = static_cast(GetCurrentDatum())->my_msg; + } + FEngSetScript(GetPackageName(), 0x2CF801C2, 0x5079C8F8, true); + } else { + bAutoPlay = true; + } + if (the_sms_msg != nullptr) { + bVoiceMsg = the_sms_msg->IsVoice(); + if (!the_sms_msg->IsVoice()) { + last_msg[1] = the_sms_msg->GetHandle(); + } else { + last_msg[0] = the_sms_msg->GetHandle(); + } + } + if (MemoryCard::GetInstance()->IsAutoSaving() || MemoryCard::GetInstance()->AutoSaveRequested()) { + cFEng::Get()->QueuePackageMessage(FEHashUpper("SMS_SAVING"), GetPackageName(), nullptr); + cFEng::Get()->QueuePackageMessage(FEHashUpper("SMS_SAVING2"), nullptr, nullptr); + } +} + +void uiSMS::Setup() { + bool new_voice = false; + bool new_text = false; + ClearData(); + bTList msgs; + for (int i = 0x95; i >= 0; i--) { + SMSMessage* msg = FEDatabase->GetCareerSettings()->GetSMSMessage(i); + if (msg->IsValid() && (msg->IsRead() || msg->IsUnRead())) { + SMSSortNode* node = new (__FILE__, __LINE__) SMSSortNode(msg); + msgs.AddTail(node); + } + } + msgs.Sort(SortSMS); + int index = 0; + for (int i = 0; i < msgs.CountElements(); i++) { + SMSMessage* msg = msgs.GetNode(i)->the_msg; + if (msg->IsUnRead()) { + if (msg->IsVoice()) { + new_voice = true; + } else { + new_text = true; + } + } + if (bVoiceMsg) { + if (msg->IsVoice()) { + AddSMSDatum(msg); + } + } else { + if (!msg->IsVoice()) { + AddSMSDatum(msg); + } + } + } + if (bVoiceMsg) { + if (new_voice) { + FEngSetScript(GetPackageName(), 0x19161CCC, 0x249DB7B7, true); + } else { + FEngSetScript(GetPackageName(), 0x19161CCC, 0x16A259, true); + } + if (new_text) { + FEngSetScript(GetPackageName(), 0x0D6FD6F9, 0x1CA7C0, true); + } else { + FEngSetScript(GetPackageName(), 0x0D6FD6F9, 0x16A259, true); + } + } else { + if (new_voice) { + FEngSetScript(GetPackageName(), 0x19161CCC, 0x1CA7C0, true); + } else { + FEngSetScript(GetPackageName(), 0x19161CCC, 0x16A259, true); + } + if (new_text) { + FEngSetScript(GetPackageName(), 0x0D6FD6F9, 0x249DB7B7, true); + } else { + FEngSetScript(GetPackageName(), 0x0D6FD6F9, 0x16A259, true); + } + } + for (int i = 0; i < GetNumDatum(); i++) { + SMSDatum* datum = static_cast(GetDatumAt(i)); + if (bVoiceMsg) { + if (datum->my_msg->GetHandle() == last_msg[0]) { + index = i; + } + } else { + if (datum->my_msg->GetHandle() == last_msg[1]) { + index = i; + } + } + } + SetInitialPosition(index); + if (GetCurrentDatum()) { + the_sms_msg = static_cast(GetCurrentDatum())->my_msg; + } else { + the_sms_msg = nullptr; + } + RefreshHeader(); +} + +void uiSMS::AddSMSDatum(SMSMessage* msg) { + if (bVoiceMsg) { + if (last_msg[0] == 0xFF) { + last_msg[0] = msg->GetHandle(); + } + } else { + if (last_msg[1] == 0xFF) { + last_msg[1] = msg->GetHandle(); + } + } + SMSDatum* datum = new (__FILE__, __LINE__) SMSDatum(msg); + AddDatum(datum); + if (msg->IsUnRead()) { + ArrayDatum* d = GetDatumAt(GetNumDatum() - 1); + d->SetChecked(true); + } +} + +void uiSMS::AddSMSSlot(unsigned int index) { + ArrayScroller* scroller = this; + unsigned int grp_hash = FEngHashString("SMS_GROUP_%d", index); + unsigned int img_hash = FEngHashString("SMS_ICON_%d", index); + unsigned int txt_hash = FEngHashString("SMS_TEXT_%d", index); + FEObject* grp = FEngFindGroup(GetPackageName(), grp_hash); + FEImage* img = FEngFindImage(GetPackageName(), img_hash); + FEString* txt = FEngFindString(GetPackageName(), txt_hash); + SMSSlot* slot = new (__FILE__, __LINE__) SMSSlot(reinterpret_cast(grp), img, txt); + scroller->AddSlot(slot); +} + +void uiSMS::RefreshHeader() { + ArrayScrollerMenu::RefreshHeader(); + if (bVoiceMsg) { + FEngSetScript(GetPackageName(), 0x4A2EEBC8, 0x1B20C2, true); + FEngSetScript(GetPackageName(), 0x8A6AD1C1, 0x249DB7B7, true); + FEngSetScript(GetPackageName(), 0x8F2FAD70, 0x7AB5521A, true); + } else { + FEngSetScript(GetPackageName(), 0x4A2EEBC8, 0x1B20C3, true); + FEngSetScript(GetPackageName(), 0x8A6AD1C1, 0x7AB5521A, true); + FEngSetScript(GetPackageName(), 0x8F2FAD70, 0x249DB7B7, true); + } + if (GetNumDatum() < 1) { + FEngSetScript(GetPackageName(), 0x07890734, 0x16A259, true); + } +} + +void uiSMS::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) { + ArrayScrollerMenu::NotificationMessage(msg, obj, param1, param2); + switch (msg) { + case 0xc98356ba: + if (cFEng::Get()->IsPackagePushed("InGame_MC_Main_GC.fng")) { + bWaitingForMemcard = true; + } else { + bWaitingForMemcard = false; + } + if (bWaitingForMemcard) { + break; + } + if (!bInitCompleted) { + break; + } + if (the_sms_msg != nullptr && bAutoPlay) { + FEngSetScript(GetPackageName(), 0x47ff4e7c, bStringHash("READ"), true); + cFEng::Get()->QueuePackagePush("SMS_Message.fng", reinterpret_cast(the_sms_msg), 0, false); + } + bInitCompleted = false; + break; + case 0x35f8620b: + bInitCompleted = true; + break; + case 0x775ce5df: + if (!the_sms_msg->IsValid()) { + for (int i = 0; i < 2; i++) { + last_msg[i] = 0xFF; + } + } + Setup(); + break; + case 0x0c407210: + if (GetCurrentDatum() == nullptr) { + goto fallthrough_msg; + } + button_pressed = 0x0c407210; + FEngSetScript(GetPackageName(), 0x47ff4e7c, bStringHash("READ"), true); + break; + case 0x72619778: + case 0x911c0a4b: { + SMSDatum* datum = static_cast(GetCurrentDatum()); + if (datum == nullptr) { + break; + } + if (bVoiceMsg) { + last_msg[0] = datum->my_msg->GetHandle(); + } else { + last_msg[1] = datum->my_msg->GetHandle(); + } + break; + } + case 0x9120409e: + ScrollBoxes(static_cast(-1)); + break; + case 0xb5971bf1: + ScrollBoxes(static_cast(1)); + break; + case 0xc519bfc4: + if (the_sms_msg == nullptr) { + goto fallthrough_msg; + } + if (!the_sms_msg->IsValid()) { + goto fallthrough_msg; + } + DialogInterface::ShowTwoButtons(GetPackageName(), "InGameDialog.fng", + static_cast(1), 0x70e01038, 0x417b25e4, + 0xd05fc3a3, 0x34dc1bcf, 0x34dc1bcf, + static_cast(1), 0x8c3c2171); + break; + case 0xd05fc3a3: { + cFEng::Get()->QueuePackageMessage(0x8cb81f09, GetPackageName(), nullptr); + SMSDatum* datum = static_cast(GetCurrentDatum()); + if (datum == nullptr) { + break; + } + datum->my_msg->ClearFlags(); + Setup(); + break; + } + case 0x34dc1bcf: + case 0x1fab5998: + fallthrough_msg: + cFEng::Get()->QueuePackageMessage(0x8cb81f09, GetPackageName(), nullptr); + break; + case 0xe1fde1d1: + if (button_pressed != 0x0c407210) { + break; + } + if (GetCurrentDatum() == nullptr) { + break; + } + cFEng::Get()->QueuePackagePush("SMS_Message.fng", reinterpret_cast(the_sms_msg), 0, false); + break; + case 0x911ab364: + button_pressed = 0x911ab364; + cFEng::Get()->QueuePackagePop(0); + break; + } +} + +eMenuSoundTriggers uiSMS::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (msg == 0x9120409e || msg == 0xb5971bf1 || msg == 0x48122792 || msg == 0x4ac5e165) { + return maybe; + } + return ArrayScrollerMenu::NotifySoundMessage(msg, maybe); +} + +void uiSMS::ScrollBoxes(eScrollDir dir) { + bVoiceMsg = !bVoiceMsg; + Setup(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.hpp index 4db856dac..13637d5bd 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMS.hpp @@ -5,6 +5,49 @@ #pragma once #endif +#include +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" + +struct SMSMessage; +struct FEGroup; +struct FEImage; +struct FEString; +enum eScrollDir; + +struct SMSSlot : public ArraySlot { + FEImage* pIcon; // offset 0x14 + FEString* pText; // offset 0x18 + + SMSSlot(FEGroup* grp, FEImage* icon, FEString* text) + : ArraySlot(reinterpret_cast(grp)) // + , pIcon(icon) // + , pText(text) + {} + ~SMSSlot() override {} + void Update(ArrayDatum* datum, bool isSelected) override; +}; + +// total size: 0x100 +struct uiSMS : public ArrayScrollerMenu { + unsigned char last_msg[2]; // offset 0xE8, size 0x2 + int button_pressed; // offset 0xEC, size 0x4 + bool bVoiceMsg; // offset 0xF0, size 0x1 + bool bAutoPlay; // offset 0xF4, size 0x1 + bool bWaitingForMemcard; // offset 0xF8, size 0x1 + bool bInitCompleted; // offset 0xFC, size 0x1 + + uiSMS(ScreenConstructorData* sd); + ~uiSMS() override {} + + void Setup(); + void AddSMSDatum(SMSMessage* msg); + void AddSMSSlot(unsigned int index); + void RefreshHeader() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, + unsigned long param2) override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void ScrollBoxes(eScrollDir dir); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMSMessage.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMSMessage.cpp index e69de29bb..914f10e57 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMSMessage.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMSMessage.cpp @@ -0,0 +1,152 @@ +#include "uiSMSMessage.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Generated/Events/ESndGameState.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +struct FEngFont; +struct FEString; + +struct Module { + virtual ~Module(); + virtual void Init(); + virtual void LoadBanks(); + virtual int TestSentenceRuleCallback(); + virtual int SetSentenceRuleCallback(); + virtual int EventRuleCallback(); + virtual int GetNumBanks(); + virtual unsigned int GetBankOffset(int bnum); + virtual void Update(); + virtual const char* GetFilename(); + virtual bool QueStream(int stream_type, void (*callback)(), bool trigger); + virtual unsigned int SampleRequestCallback(); + virtual bool IsStreamQueued(); + virtual const char* GetCSIptr(); + virtual int GetChannel(); + virtual const char* GetEventDat(); + virtual bool IsDataLoaded(); + virtual bool PlayStream(int stream_id); + virtual void ReleaseResource(); +}; + +namespace Speech { +namespace Manager { +Module* GetSpeechModule(int id); +void ClearPlayback(); +} // namespace Manager +} // namespace Speech + +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +FEString* FEngFindString(const char* pkg_name, int hash); +FEngFont* FindFont(unsigned int hash); +unsigned int FEngHashString(const char* format, ...); +void FEngSetInvisible(FEObject* obj); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +void SoundPause(bool pause, eSNDPAUSE_REASON reason); +void SetSoundControlState(bool state, eSNDCTLSTATE ctl, const char* name); + +namespace MiscSpeed { +void SMSCellCall(int handle); +} + +uiSMSMessage::uiSMSMessage(ScreenConstructorData* sd) + : MenuScreen(sd) // + , ScrollBar(sd->PackageFilename, "scrollbar", true, true, false) { + the_msg = reinterpret_cast< SMSMessage* >(sd->Arg); + new ESndGameState(0xd, true); + SoundPause(true, static_cast< eSNDPAUSE_REASON >(0xb)); + SetSoundControlState(false, static_cast< eSNDCTLSTATE >(4), "SMSMesUnPause"); + SetSoundControlState(true, static_cast< eSNDCTLSTATE >(5), "SMSMes"); + Setup(); +} + +uiSMSMessage::~uiSMSMessage() { + new ESndGameState(0xd, false); + SoundPause(false, static_cast< eSNDPAUSE_REASON >(0xb)); + SetSoundControlState(true, static_cast< eSNDCTLSTATE >(4), "SMSMesPause"); + SetSoundControlState(false, static_cast< eSNDCTLSTATE >(5), "SMSMes"); +} + +void uiSMSMessage::Setup() { + FEString* pString = FEngFindString(GetPackageName(), FEHashUpper("MESSAGE_TEXT_1")); + m_TextScroller.Initialise(this, static_cast< int >(pString->MaxWidth), 10, + "MESSAGE_TEXT_%d", FindFont(pString->Handle)); + m_TextScroller.UseScrollBar(&ScrollBar); + m_TextScroller.SetTextHash(FEngHashString("SMS_MESSAGE_%d", the_msg->GetHandle())); + if (!the_msg->IsVoice()) { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x2a631207)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x914614e5)); + } + Module* cop_speech = Speech::Manager::GetSpeechModule(1); + if (cop_speech != nullptr) { + cop_speech->ReleaseResource(); + Speech::Manager::ClearPlayback(); + } + RefreshHeader(); +} + +void uiSMSMessage::RefreshHeader() { + FEngSetLanguageHash(GetPackageName(), 0xfeced617, FEngHashString("SMS_MESSAGE_%d_FROM", the_msg->GetHandle())); + FEngSetLanguageHash(GetPackageName(), 0x2c167533, FEngHashString("SMS_MESSAGE_%d_SUBJECT", the_msg->GetHandle())); +} + +eMenuSoundTriggers uiSMSMessage::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (msg == 0x610fb237 && !the_msg->IsVoice()) { + return static_cast< eMenuSoundTriggers >(-1); + } + return maybe; +} + +void uiSMSMessage::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + m_TextScroller.HandleNotificationMessage(msg); + switch (msg) { + case 0x34dc1bcf: + break; + case 0x35f8620b: { + if (the_msg->IsVoice() && the_msg->IsUnRead()) { + MiscSpeed::SMSCellCall(the_msg->GetHandle()); + } + the_msg->ClearFlags(); + the_msg->SetFlag(4); + break; + } + case 0xe1fde1d1: { + Module* cop_speech = Speech::Manager::GetSpeechModule(1); + if (cop_speech != nullptr) { + cop_speech->ReleaseResource(); + Speech::Manager::ClearPlayback(); + } + cFEng::Get()->QueuePackagePop(1); + break; + } + case 0xc519bfc3: { + if (the_msg->IsVoice()) { + Module* cop_speech = Speech::Manager::GetSpeechModule(1); + if (cop_speech != nullptr) { + cop_speech->ReleaseResource(); + Speech::Manager::ClearPlayback(); + } + MiscSpeed::SMSCellCall(the_msg->GetHandle()); + } + break; + } + case 0xc519bfc4: { + DialogInterface::ShowTwoButtons( + GetPackageName(), "InGameDialog.fng", + static_cast< eDialogTitle >(1), + 0x70e01038, 0x417b25e4, + 0xd05fc3a3, 0x34dc1bcf, 0x34dc1bcf, + static_cast< eDialogFirstButtons >(1), + 0x8c3c2171); + break; + } + case 0xd05fc3a3: { + the_msg->ClearFlags(); + cFEng::Get()->QueuePackageMessage(0x911ab364, GetPackageName(), nullptr); + break; + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMSMessage.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMSMessage.hpp new file mode 100644 index 000000000..54df40d21 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSMSMessage.hpp @@ -0,0 +1,32 @@ +#ifndef FRONTEND_MENUSCREENS_INGAME_UISMSMESSAGE_H +#define FRONTEND_MENUSCREENS_INGAME_UISMSMESSAGE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/CTextScroller.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp" + +struct FEObject; +struct SMSMessage; + +// total size: 0xE8 +struct uiSMSMessage : public MenuScreen { + CTextScroller m_TextScroller; // offset 0x2C, size 0x54 + FEScrollBar ScrollBar; // offset 0x80, size 0x64 + SMSMessage* the_msg; // offset 0xE4, size 0x4 + + uiSMSMessage(ScreenConstructorData* sd); + ~uiSMSMessage() override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + + void Setup(); + void RefreshHeader(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSixDaysLater.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSixDaysLater.cpp index e69de29bb..b308fe929 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSixDaysLater.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiSixDaysLater.cpp @@ -0,0 +1,48 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/Src/Generated/Messages/MNotifyMessageDone.h" + +extern FEString *FEngFindString(const char *pkg_name, int name_hash); +extern unsigned int FEngHashString(const char *, ...); +extern void FEngSetLanguageHash(FEString *, unsigned int); +extern bool FEngIsScriptSet(FEObject *obj, unsigned int script_hash); +extern bool FEngIsScriptRunning(FEObject *object, unsigned int script_hash); +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning); + +struct SixDaysLater : MenuScreen { + SixDaysLater(ScreenConstructorData *sd); + ~SixDaysLater() override; + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + FEString *mpDataMainString; // offset 0x2C + int mStringMode; // offset 0x30 +}; + +SixDaysLater::SixDaysLater(ScreenConstructorData *sd) + : MenuScreen(sd) // +{ + mStringMode = sd->Arg; + mpDataMainString = FEngFindString(GetPackageName(), 0xb769701e); + FEngSetLanguageHash(mpDataMainString, FEngHashString("DDAY_TIMELAPSE_%d", mStringMode + 1)); + new EFadeScreenOff(0x14035fb); +} + +SixDaysLater::~SixDaysLater() {} + +void SixDaysLater::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (msg == 0xC98356BA) { + if (FEngIsScriptSet(mpDataMainString, 0x5079c8f8)) { + if (!FEngIsScriptSet(GetPackageName(), 0x53d9eb7e, 0x5079c8f8)) { + FEngSetScript(GetPackageName(), 0x53d9eb7e, 0x5079c8f8, true); + } + } + if (FEngIsScriptSet(mpDataMainString, 0x5a8e4ebe)) { + if (!FEngIsScriptRunning(mpDataMainString, 0x5a8e4ebe)) { + cFEng::Get()->QueuePackagePop(0); + UCrc32 target(0x20d60dbf); + MNotifyMessageDone done; + done.Post(target); + } + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.cpp index e69de29bb..fcdcdc287 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.cpp @@ -0,0 +1,1449 @@ +#include "uiWorldMap.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp" +#include "Speed/Indep/Src/Gameplay/GIcon.h" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/World/TrackInfo.hpp" +#include "Speed/Indep/Src/World/RaceParameters.hpp" +#include "Speed/Indep/Src/Generated/Events/EWorldMapOff.hpp" +#include "Speed/Indep/Src/Input/ActionQueue.h" +#include "Speed/Indep/Src/Interfaces/Simables/IAI.h" +#include "Speed/Indep/Src/Interfaces/Simables/IVehicle.h" +#include "Speed/Indep/Src/Interfaces/SimEntities/IPlayer.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Src/Physics/Common/VehicleSystem.h" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +struct Minimap { + struct GameplayIconInfo { + GIcon::Type mIconType; + eWorldMapItemType mItemType; + const char* mElementString; + unsigned int mWorldMapTitle; + unsigned int mworldIconTexHash; + }; + static GameplayIconInfo kGameplayIconInfo[]; + static GameplayIconInfo& GetGameplayIconInfo(GIcon::Type iconType) { + return kGameplayIconInfo[iconType]; + } + static GameplayIconInfo& GetGameplayIconInfo(eWorldMapItemType itemType) { + for (int i = 0; i < GIcon::kType_Count; i++) { + if (kGameplayIconInfo[i].mItemType == itemType) { + return kGameplayIconInfo[i]; + } + } + return kGameplayIconInfo[0]; + } +}; + +extern Timer RealTimer; +extern RaceParameters TheRaceParameters; +void FEngGetSize(FEObject* obj, float& x, float& y); +void FEngGetCenter(FEObject* obj, float& x, float& y); +void FEngGetTopLeft(FEObject* obj, float& x, float& y); +void FEngGetBottomRight(FEObject* obj, float& x, float& y); +float FEngGetScaleX(FEObject* obj); +float FEngGetScaleY(FEObject* obj); +void FEngSetButtonTexture(FEImage* img, unsigned int tex_hash); +void FEngSetColor(FEObject* obj, unsigned int color); +void FEngSetScript(FEObject* object, unsigned int script_hash, bool start_at_beginning); +bool FEngIsScriptSet(FEObject* obj, unsigned int script_hash); +void FEngSetTopLeft(FEObject* object, float x, float y); +void FEngSetLanguageHash(FEString* text, unsigned int hash); +bool FEngTestForIntersection(float xPos, float yPos, FEObject* obj); +void FEngSetLastButton(const char* pkg_name, unsigned char button); +void FEngSetRotationZ(FEObject* obj, float z); +FEColor FEngGetObjectColor(FEObject* obj); +unsigned int FEngGetTextureHash(FEImage* image); +bool GPS_IsEngaged(); +void GPS_Disengage(); +int GPS_Engage(const UMath::Vector3& pos, float radius); +void GetVehicleVectors(bVector2* pos, bVector2* dir, ISimable* simable); + +float FEngGetCenterX(FEObject* obj); +float FEngGetCenterY(FEObject* obj); + +inline float bDistBetween(const bVector2* v1, const bVector2* v2) { + float x = v1->x - v2->x; + float y = v1->y - v2->y; + return bSqrt(x * x + y * y); +} +inline float bDistBetween(const bVector2& v1, const bVector2& v2) { + return bDistBetween(&v1, &v2); +} + +inline int tCubic1D::HasArrived() { + return state == 0; +} + +inline int tCubic2D::HasArrived() { + return x.HasArrived() && y.HasArrived(); +} + +inline bool UITrackMapStreamer::IsZooming() { + return !ZoomCubic.HasArrived(); +} + +inline float FEngGetSizeY(FEObject* obj) { + float x; + float y; + FEngGetSize(obj, x, y); + return y; +} + +inline void FEngSetSizeX(FEObject* obj, float x) { + float y = FEngGetSizeY(obj); + FEngSetSize(obj, x, y); +} + +inline MapItem::MapItem(eWorldMapItemType type, FEObject* iconObj, bVector2& map_pos, bVector2& world_pos, + float rot, GIcon* icon) { + unsigned int* initial_pos_words = reinterpret_cast< unsigned int* >(&InitialPos); + unsigned int* world_pos_words = reinterpret_cast< unsigned int* >(&WorldPos); + const unsigned int* map_pos_words = reinterpret_cast< const unsigned int* >(&map_pos); + const unsigned int* source_world_pos_words = reinterpret_cast< const unsigned int* >(&world_pos); + + pIcon = iconObj; + initial_pos_words[0] = map_pos_words[0]; + initial_pos_words[1] = map_pos_words[1]; + world_pos_words[0] = source_world_pos_words[0]; + world_pos_words[1] = source_world_pos_words[1]; + Rot = rot; + TheType = type; + TheIcon = icon; + bHidden = false; + if (!FEDatabase->GetGameplaySettings()->IsMapItemEnabled(type)) { + bHidden = true; + Hide(); + } else { + bHidden = false; + Show(); + } + FEngGetSize(pIcon, InitialSize.x, InitialSize.y); + FEngSetCenter(pIcon, InitialPos.x, InitialPos.y); + FEngSetRotationZ(pIcon, Rot); +} + +inline CopItem::CopItem(FEObject* icon, bVector2& pos, bVector2& world_pos, float rot, + eWorldMapItemType type) + : MapItem(type, icon, pos, world_pos, rot, nullptr) { + FlashTimer = -1; +} + +inline HeliItem::HeliItem(FEImage* view, FEObject* icon, bVector2& pos, bVector2& world_pos, float rot) + : CopItem(icon, pos, world_pos, rot, WMIT_COP_HELI) { + pViewCone = view; + InitialSize.x = FEngGetScaleX(pIcon); + InitialSize.y = FEngGetScaleY(pIcon); + FEngSetCenter(static_cast< FEObject* >(pViewCone), pos.x, pos.y); + FEngSetRotationZ(static_cast< FEObject* >(pViewCone), rot); +} + +void MapItem::Show() { + FEngSetVisible(pIcon); +} + +void MapItem::Hide() { + FEngSetInvisible(pIcon); +} + +inline ItemTypeToggle::ItemTypeToggle(unsigned int name_hash, eWorldMapItemType type, bool vis) + : FEButtonWidget(true) { + ItemType = type; + NameHash = name_hash; + pIcon = nullptr; + bVisibility = vis; + bExiting = 0; +} + +void CopItem::Draw() { + if (!bHidden) { + unsigned int color = 0xffcccccc; + if (FlashTimer < 3) { + color = 0xff0000ff; + } else if (FlashTimer - 5U < 2) { + color = 0xffa00000; + } + FEngSetColor(pIcon, color); + FlashTimer = FlashTimer + 1; + if (FlashTimer > 8) { + FlashTimer = 1; + } + } +} + +void HeliItem::Draw() { + if (!bHidden) { + float width = bSin(RealTimer.GetSeconds()) * 88.0f + 88.0f; + FEngSetSizeX(static_cast< FEObject* >(pViewCone), width); + FlashTimer++; + if (FlashTimer > 32) { + FlashTimer = 1; + } + } +} + +void ItemTypeToggle::Act(const char* parent_pkg, unsigned int data) { + if (data == 0xc407210) { + bVisibility ^= 1; + FEDatabase->GetGameplaySettings()->SetMapItem(GetType(), bVisibility); + g_pEAXSound->PlayUISoundFX(static_cast< eMenuSoundTriggers >(2)); + Draw(); + } +} + +void ItemTypeToggle::CheckMouse(const char* parent_pkg, const float mouse_x, const float mouse_y) { + if (FEngTestForIntersection(mouse_x, mouse_y, static_cast< FEObject* >(GetTitleObject()))) { + cFEng::Get()->QueueGameMessage(0xc407210, parent_pkg, 0xff); + } +} + +void ItemTypeToggle::Draw() { + const unsigned long FEObj_Highlight = 0x249db7b7; + FEngSetLanguageHash(GetTitleObject(), NameHash); + if (bVisibility) { + const unsigned long FEObj_GREY = 0x6ebbfb68; + FEngSetScript(pIconGroup, FEObj_GREY, true); + if (!FEngIsScriptSet(static_cast< FEObject* >(GetTitleObject()), FEObj_Highlight)) { + FEngSetScript(static_cast< FEObject* >(GetTitleObject()), FEObj_GREY, true); + } + } else { + const unsigned long FEObj_NORMAL = 0x163c76; + FEngSetScript(pIconGroup, FEObj_NORMAL, true); + if (!FEngIsScriptSet(static_cast< FEObject* >(GetTitleObject()), FEObj_Highlight)) { + FEngSetScript(static_cast< FEObject* >(GetTitleObject()), FEObj_NORMAL, true); + } + } +} + +void ItemTypeToggle::Position() { + FEButtonWidget::Position(); + FEngSetTopLeft(pIconGroup, GetTopLeftX() - 23.0f, GetTopLeftY() + 2.0f); +} + +void ItemTypeToggle::UnsetFocus() { + if (GetVisibility() || bExiting) { + const unsigned long FEObj_GREY = 0x6ebbfb68; + FEButtonWidget::UnsetFocus(); + FEngSetScript(pIconGroup, FEObj_GREY, true); + } else { + const unsigned long FEObj_NORMAL = 0x163c76; + FEngSetScript(static_cast(GetTitleObject()), FEObj_NORMAL, true); + FEngSetScript(pIconGroup, FEObj_NORMAL, true); + if (GetBacking()) { + FEngSetScript(GetBacking(), FEObj_NORMAL, true); + } + } +} + +void ItemTypeToggle::SetIcon(FEImage* icon, unsigned int texHash, unsigned int texColour) { + unsigned int color = texColour; + unsigned int tex_hash = texHash; + pIcon = icon; + switch (ItemType) { + case WMIT_PLAYER_CAR: + color = 0xffabda4d; + tex_hash = 0xada85247; + break; + case WMIT_COP_CAR: + color = 0xffffffff; + tex_hash = 0xdac364e9; + break; + case WMIT_ROADBLOCK: + color = 0xffffed00; + tex_hash = 0x123f07e2; + break; + default: + break; + } + FEngSetColor(static_cast< FEObject* >(pIcon), color); + FEngSetTextureHash(pIcon, tex_hash); +} + +void ItemTypeToggle::Show() { + FEButtonWidget::Show(); + FEngSetVisible(static_cast< FEObject* >(pIcon)); +} + +void ItemTypeToggle::Hide() { + FEButtonWidget::Hide(); + FEngSetInvisible(static_cast< FEObject* >(pIcon)); +} + + +struct FEObject; +struct FEMultiImage; + +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +FEImage* FEngFindImage(const char* pkg_name, int hash); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetTextureHash(FEImage* image, unsigned int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning); +int FEPrintf(const char* pkg_name, int hash, const char* format, ...); +unsigned int FEngHashString(const char* format, ...); +unsigned char FEngGetLastButton(const char* pkg_name); +void FEngSetRotationZ(FEObject* obj, float rot); +void FEngSetPosition(FEObject* obj, float x, float y); +const char* GetLocalizedString(unsigned int hash); +void FEngSetVisible(const char* pkg_name, unsigned int obj_hash); +void FEngSetInvisible(const char* pkg_name, unsigned int obj_hash); +void FEngSetTextureHash(const char* pkg_name, unsigned int obj_hash, unsigned int tex_hash); + +extern unsigned int iCurrentViewBin; + +GIcon* WorldMap::mGPSingIcon; + +void WorldMap::SetGPSing(GIcon* icon) { + if (icon != nullptr) { + mGPSingIcon = icon; + icon->SetFlag(0x80); + } +} + +void WorldMap::ClearGPSing() { + if (mGPSingIcon != nullptr) { + mGPSingIcon->ClearFlag(0x80); + mGPSingIcon = nullptr; + } +} + +void GIcon::SetFlag(unsigned int mask) { + mFlags |= mask; +} + +void GIcon::ClearFlag(unsigned int mask) { + mFlags &= ~mask; +} + +WorldMap::WorldMap(ScreenConstructorData* sd) + : UIWidgetMenu(sd) { + MapSize.y = 0.0f; + TheMapItems.HeadNode.Prev = &TheMapItems.HeadNode; + CurrentRaceType = -1; + fSnapDist = 30.0f; + CurrentVelocity.x = 0.0f; + CurrentVelocity.y = 0.0f; + CursorMoveFrom.x = 0.0f; + CursorMoveFrom.y = 0.0f; + MapTopLeft.x = 0.0f; + MapTopLeft.y = 0.0f; + MapSize.x = 0.0f; + TheMapItems.HeadNode.Next = &TheMapItems.HeadNode; + bLeftHeldOnMap = false; + Cursor = nullptr; + mActionQ = nullptr; + TimeSinceLastMove.ResetLow(); + pCurrentTrack = nullptr; + TrackMap = nullptr; + SelectedItem = nullptr; + MapStreamer = nullptr; + CurrentView = 0; + CurrentZoom = 0; + bInToggleMode = false; + bCursorMoving = false; + bCursorOn = false; + + signed char joyport = FEDatabase->PlayerJoyports[0]; + mActionQ = new ActionQueue(joyport, 0x82d21520, "WorldMapMain", false); + mActionQ->Enable(true); + iMaxWidgetsOnScreen = 10; + Setup(); + RefreshHeader(); +} + +WorldMap::~WorldMap() { + delete mActionQ; + delete MapStreamer; + MapStreamer = nullptr; + + IPlayer* iplayer = IPlayer::First(PLAYER_LOCAL); + if (iplayer != nullptr) { + { + IHud* hud = iplayer->GetHud(); + hud->RefreshMiniMapItems(); + } + } +} + +void WorldMap::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { +#ifdef EA_PLATFORM_PLAYSTATION2 + unsigned long message = msg; +#else + register unsigned long message asm("r30") = msg; +#endif + UMath::Vector3 pos; + + if (!bInToggleMode) { + if (message == 0x72619778) { + goto after_base_message; + } + if (message == 0x911c0a4b) { + goto after_base_message; + } + } + if (message != 0xc407210) { + UIWidgetMenu::NotificationMessage(message, obj, param1, param2); + } +after_base_message: + if (message == 0xa16ca7bd) { + goto handle_gps; + } + if (message > 0xa16ca7bd) { + goto msg_gt_a16ca7bd; + } + if (message == 0x72619778) { + goto refresh_and_end; + } + if (message > 0x72619778) { + goto msg_gt_72619778; + } + if (message == 0x35f8620b) { + goto clear_focus; + } + if (message > 0x35f8620b) { + goto msg_gt_35f8620b; + } + if (message == 0xc407210) { + goto handle_toggle_or_dialog; + } + return; + +msg_gt_35f8620b: + if (message == 0x5073ef13) { + goto zoom_prev; + } + return; + +msg_gt_72619778: + if (message == 0x911c0a4b) { + goto refresh_and_end; + } + if (message > 0x911c0a4b) { + goto msg_gt_911c0a4b; + } + if (message == 0x911ab364) { + goto leave_screen; + } + return; + +msg_gt_911c0a4b: + if (message == 0x9120409e) { + goto maybe_view_switch; + } + return; + +msg_gt_a16ca7bd: + if (message == 0xc519bfc4) { + return; + } + if (message > 0xc519bfc4) { + goto msg_gt_c519bfc4; + } + if (message == 0xb5af2461) { + goto set_last_button_and_leave; + } + if (message > 0xb5af2461) { + goto msg_gt_b5af2461; + } + if (message == 0xb5971bf1) { + goto maybe_view_switch; + } + return; + +msg_gt_b5af2461: + if (message == 0xc519bfc3) { + goto handle_toggle; + } + return; + +msg_gt_c519bfc4: + if (message == 0xd9feec59) { + goto zoom_next; + } + if (message > 0xd9feec59) { + goto msg_gt_d9feec59; + } + if (message == 0xc98356ba) { + goto update_map; + } + return; + +msg_gt_d9feec59: + if (message == 0xe1fde1d1) { + goto world_map_off; + } + return; + +clear_focus : { + FEWidget* w = pCurrentOption; + if (w != nullptr) { + w->UnsetFocus(); + } +} + return; + +update_map: + if (!cFEng::Get()->IsPackageInControl(GetPackageName())) { + return; + } else { + float zoom; + float max_zoom; + bVector2 pan; + bVector2* pPan = &pan; + + UpdateCursor(false); + MapStreamer->UpdateAnimation(); + UpdateCursor(true); + zoom = MapStreamer->GetZoomFactor(); + max_zoom = GetZoomFactor(WMZ_LEVEL_4); + pPan->x = 0.0f; + pPan->y = 0.0f; + MapStreamer->GetPan(*pPan); + + bVector2 map_center; + bVector2* pMapCenter = &map_center; + bVector2* pSavedMapCenter = pMapCenter; + FEngGetCenter(static_cast< FEObject* >(TrackMap), pMapCenter->x, pMapCenter->y); + + bVector2 map_br; + FEngGetBottomRight(static_cast< FEObject* >(TrackMap), map_br.x, map_br.y); + + bVector2 pos; + bVector2* pPos = &pos; + bVector2 delta; + bVector2* pDelta = δ + bVector2 map_pos; + bVector2* pMapPos = &map_pos; + bVector2 pan_offset; + bVector2* pPanOffset = &pan_offset; + bVector2 zoomed_pan; + bVector2* pZoomedPan = &zoomed_pan; + bVector2 final_pos; + bVector2* pFinalPos = &final_pos; + + for (MapItem* item = TheMapItems.GetHead(); item != TheMapItems.EndOfList(); + item = item->GetNext()) { + pPos->x = 0.0f; + pPos->y = 0.0f; + item->GetInitialPos(*pPos); + pDelta->x = pPos->x - pSavedMapCenter->x; + pDelta->y = pPos->y - pSavedMapCenter->y; + pDelta->x *= zoom; + pDelta->y *= zoom; + pMapPos->x = pDelta->x + pSavedMapCenter->x; + pMapPos->y = pDelta->y + pSavedMapCenter->y; + pPos->x = pMapPos->x; + pPos->y = pMapPos->y; + + reinterpret_cast< unsigned int* >(pPanOffset)[0] = + reinterpret_cast< const unsigned int* >(pPan)[0]; + reinterpret_cast< unsigned int* >(pPanOffset)[1] = + reinterpret_cast< const unsigned int* >(pPan)[1]; + float pan_offset_x = pPanOffset->x * MapSize.x; + float pan_offset_y = pPanOffset->y * MapSize.y; + pPanOffset->x = pan_offset_x; + pPanOffset->y = pan_offset_y; + pZoomedPan->x = pan_offset_x * zoom; + pZoomedPan->y = pan_offset_y * zoom; + pFinalPos->x = pPos->x - pZoomedPan->x; + pFinalPos->y = pPos->y - pZoomedPan->y; + reinterpret_cast< unsigned int* >(pPos)[0] = + reinterpret_cast< const unsigned int* >(pFinalPos)[0]; + reinterpret_cast< unsigned int* >(pPos)[1] = + reinterpret_cast< const unsigned int* >(pFinalPos)[1]; + + item->UpdatePos(*pPos); + + float icon_scale = ((zoom - 1.0f) / (max_zoom - 1.0f)) * 0.5f + 1.0f; + item->UpdateScale(icon_scale); + + item->GetCurrentPos(*pPos); + if (ClampToMapBounds(pPos->x, pPos->y)) { + item->Hide(); + } else if (!item->IsHidden()) { + item->Show(); + } + item->Draw(); + } + } + return; + +handle_toggle_or_dialog: + if (bInToggleMode) { + FEWidget* w = pCurrentOption; + if (w == nullptr) { + return; + } + ItemTypeToggle* tog = static_cast< ItemTypeToggle* >(w); + tog->Act(GetPackageName(), message); + UpdateIconVisibility(tog->GetType(), tog->GetVisibility()); + goto refresh_and_end; + } else { + IPlayer* iplayer = IPlayer::First(PLAYER_LOCAL); + if (iplayer == nullptr) { + return; + } + ISimable* isimable = iplayer->GetSimable(); + if (isimable == nullptr) { + return; + } + + unsigned int title_hash; + unsigned int message_hash; + unsigned int button_hash; + if (SelectedItem != nullptr && SelectedItem->GetIcon() != nullptr) { + title_hash = 0x70e01038; + message_hash = 0x417b25e4; + button_hash = 0x96ac0a32; + } else { + if (mGPSingIcon == nullptr) { + return; + } + title_hash = 0x417b2601; + message_hash = 0x1a294dad; + button_hash = 0xa6be2ebb; + } + DialogInterface::ShowTwoButtons(GetPackageName(), "InGameDialog.fng", + static_cast< eDialogTitle >(3), title_hash, message_hash, + 0xa16ca7bd, 0xb4edeb6d, 0xb4edeb6d, + static_cast< eDialogFirstButtons >(1), button_hash); + } + return; + +handle_gps: + if (GPS_IsEngaged()) { + GPS_Disengage(); + ClearGPSing(); + } + if (SelectedItem == nullptr) { + goto refresh_and_end; + } + if (SelectedItem->GetIcon() == nullptr) { + goto refresh_and_end; + } + + eUnSwizzleWorldVector(SelectedItem->GetIcon()->GetPosition(), + reinterpret_cast< bVector3& >(pos)); + if (GPS_Engage(pos, 0.0f) == 0) { + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast< eDialogTitle >(1), + 0x417b2601, 0x34dc1bec, 0x7afdf4cc); + goto refresh_and_end; + } + SetGPSing(SelectedItem->GetIcon()); + FEngSetLastButton(GetPackageName(), 0); + cFEng::Get()->QueuePackageMessage(0x911ab364, GetPackageName(), nullptr); + goto refresh_and_end; + +set_last_button_and_leave: + FEngSetLastButton(GetPackageName(), 0); +leave_screen: + if (!bInToggleMode) { + cFEng::Get()->QueuePackageMessage(0x587c018b, GetPackageName(), nullptr); + return; + } + bInToggleMode = false; + cFEng::Get()->QueuePackageMessage(0x947e6205, GetPackageName(), nullptr); + goto finish_toggle; + +maybe_view_switch: + if (bInToggleMode || CurrentView == 3) { + return; + } + +view_switch : { + const unsigned int _UNSNAP = 0x7efe8ff4; + FEngSetScript(Cursor, _UNSNAP, true); + SelectedItem = nullptr; + ClearItems(); + AddPlayerCar(); + if (CurrentView == 0) { + CurrentView = 1; + SetupEvent(); + SetInitialOption(0); + } else if (CurrentView == 1) { + CurrentView = 0; + SetupNavigation(); + SetInitialOption(0); + } + FEDatabase->GetGameplaySettings()->LastMapView = static_cast< unsigned char >(CurrentView); + goto refresh_and_end; +} + +handle_toggle: + if (!bInToggleMode) { + bInToggleMode = true; + cFEng::Get()->QueuePackageMessage(0x5c28136d, GetPackageName(), nullptr); + FEWidget* w = pCurrentOption; + if (w != nullptr) { + w->SetFocus(GetPackageName()); + } + goto refresh_and_end; + } + bInToggleMode = false; + cFEng::Get()->QueuePackageMessage(0x947e6205, GetPackageName(), nullptr); + goto finish_toggle; + +finish_toggle : { + FEWidget* w = pCurrentOption; + if (w != nullptr) { + w->UnsetFocus(); + } +} + +refresh_and_end: + RefreshHeader(); + return; + +zoom_prev: + if (!bInToggleMode) { + ScrollZoom(eSD_PREV); + } + return; + +zoom_next: + if (!bInToggleMode) { + ScrollZoom(eSD_NEXT); + } + return; + +world_map_off: + new EWorldMapOff(); + return; +} + +void WorldMap::ScrollZoom(eScrollDir dir) { + int zoom = CurrentZoom; + if (dir == eSD_PREV) { + zoom--; + if (zoom < 0) { + zoom = WMZ_MAX_ZOOM; + } + } else if (dir == eSD_NEXT) { + zoom++; + if (zoom > WMZ_MAX_ZOOM) { + zoom = 0; + } + } + if (zoom != CurrentZoom) { + CurrentZoom = zoom; + RefreshHeader(); + float factor = GetZoomFactor(static_cast(zoom)); + float factorInv = 1.0f / factor; + bVector2 scale; + scale.y = factorInv; + scale.x = factorInv; + MapStreamer->ZoomTo(scale); + PanToCursor(factor); + switch (CurrentView) { + case 0: + case 1: + FEDatabase->GetGameplaySettings()->LastMapZoom = static_cast(CurrentZoom); + break; + case 3: + FEDatabase->GetGameplaySettings()->LastPursuitMapZoom = static_cast(CurrentZoom); + break; + } + } +} + +float WorldMap::GetZoomFactor(eWorldMapZoomLevels level) { + float factor = 1.0f; + switch (level) { + case WMZ_LEVEL_1: factor = 2.0f; break; + case WMZ_LEVEL_2: factor = 4.0f; break; + case WMZ_LEVEL_4: factor = 8.0f; break; + default: break; + } + return factor; +} + +void WorldMap::UpdateIconVisibility(eWorldMapItemType type, bool visible) { + MapItem* item = TheMapItems.GetHead(); + while (item != TheMapItems.EndOfList()) { + if (item->TheType == type) { + if (visible) { + item->bHidden = false; + item->Show(); + } else { + item->bHidden = true; + item->Hide(); + } + } + item = item->GetNext(); + } +} + +void WorldMap::ClearItems() { + MapItem* item = TheMapItems.GetHead(); + while (item != TheMapItems.EndOfList()) { + item->Hide(); + item->ResetSize(); + item = item->GetNext(); + } + + while (!TheMapItems.IsEmpty()) { + delete TheMapItems.RemoveHead(); + } + + FEWidget* widget = Options.GetHead(); + while (widget != Options.EndOfList()) { + static_cast< ItemTypeToggle* >(widget)->StartExit(); + widget = widget->GetNext(); + } + + ClearWidgets(); +} + +bool WorldMap::ClampToMapBounds(float& x, float& y) { + bVector2 bottom_right; + FEngGetBottomRight(static_cast< FEObject* >(TrackMap), bottom_right.x, bottom_right.y); + + bool changed = false; + float min_x = MapTopLeft.x + 8.0f; + if (x < min_x) { + x = min_x; + changed = true; + } else { + float max_x = bottom_right.x - 8.0f; + if (x > max_x) { + x = max_x; + changed = true; + } else { + float min_y = MapTopLeft.y + 26.0f; + if (y < min_y) { + y = min_y; + changed = true; + } else { + float max_y = bottom_right.y - 32.0f; + if (y > max_y) { + y = max_y; + changed = true; + } + } + } + } + return changed; +} + +void WorldMap::UpdateAnalogInput() { + if (mActionQ != nullptr) { + while (!mActionQ->IsEmpty() && !bInToggleMode) { + ActionRef aRef = mActionQ->GetAction(); + float speed = 14.0f; + switch (aRef.ID()) { + case FRONTENDACTION_RUP: + CurrentVelocity.y = -aRef.Data() * speed; + break; + case FRONTENDACTION_RDOWN: + CurrentVelocity.y = aRef.Data() * speed; + break; + case FRONTENDACTION_RLEFT: + CurrentVelocity.x = -aRef.Data() * speed; + break; + case FRONTENDACTION_RRIGHT: + CurrentVelocity.x = aRef.Data() * speed; + break; + } + mActionQ->PopAction(); + } + } +} + +void WorldMap::UpdateCursor(bool zoom_thing) { + UpdateAnalogInput(); + if (MapStreamer->IsZooming()) { + float zoom = MapStreamer->GetZoomFactor(); + bVector2 pan; + pan.y = 0.0f; + pan.x = 0.0f; + MapStreamer->GetPan(pan); + bVector2 map_center; + FEngGetCenter(static_cast< FEObject* >(TrackMap), map_center.x, map_center.y); + FEngGetTopLeft(static_cast< FEObject* >(TrackMap), MapTopLeft.x, MapTopLeft.y); + bVector2 pos; + pos = CursorMoveFrom; + bVector2 delta = pos - map_center; + delta *= zoom; + bVector2 map_br = delta + map_center; + pos = map_br; + pan.x *= MapSize.x; + pan.y *= MapSize.y; + pan = pan * zoom; + pos = pos - pan; + ClampToMapBounds(pos.x, pos.y); + FEngSetCenter(Cursor, pos.x, pos.y); + } else if (!zoom_thing) { + if (CurrentVelocity.x != 0.0f || CurrentVelocity.y != 0.0f) { + if (!bCursorMoving) { + cFEng::Get()->QueuePackageMessage(0x9f710838, GetPackageName(), nullptr); + bCursorMoving = true; + } + MoveCursor(CurrentVelocity.x, CurrentVelocity.y); + if (SelectedItem != nullptr) { + bVector2 cursor; + bVector2 pos; + FEngGetCenter(Cursor, cursor.x, cursor.y); + SelectedItem->GetCurrentPos(pos); + float dist = bDistBetween(cursor, pos); + if (dist >= fSnapDist) { + const unsigned int _UNSNAP = 0x7efe8ff4; + FEngSetScript(Cursor, _UNSNAP, true); + SelectedItem = nullptr; + RefreshHeader(); + } + } + } else { + if (bCursorMoving) { + cFEng::Get()->QueuePackageMessage(0x7e6687da, GetPackageName(), nullptr); + bCursorMoving = false; + } + if (SnapCursor()) { + RefreshHeader(); + } + } + } +} + +void WorldMap::MoveCursor(float x, float y) { + bVector2 cursor_x; + FEngGetCenter(Cursor, cursor_x.x, cursor_x.y); + float dx = cursor_x.x + x; + bVector2 cursor_y; + FEngGetCenter(Cursor, cursor_y.x, cursor_y.y); + float dy = cursor_y.y + y; + bVector2 excess(0.0f, 0.0f); + bVector2 bottom_right; + bVector2* pExcess = &excess; + bVector2* pBottomRight = &bottom_right; + FEngGetBottomRight(static_cast< FEObject* >(TrackMap), pBottomRight->x, pBottomRight->y); + if (CurrentZoom != 0 && (x != 0.0f || y != 0.0f)) { + if (dx < MapTopLeft.x + 8.0f) { + pExcess->x = (MapTopLeft.x + 8.0f) - dx; + } else if (dx > pBottomRight->x + -8.0f) { + pExcess->x = dx - (pBottomRight->x + -8.0f); + } else if (dy < MapTopLeft.y + 26.0f) { + pExcess->y = (MapTopLeft.y + 26.0f) - dy; + } else if (dy > pBottomRight->y + -32.0f) { + pExcess->y = dy - (pBottomRight->y + -32.0f); + } + if (pExcess->x != 0.0f || pExcess->y != 0.0f) { + bVector2 cur_pan; + bVector2* pCurPan = &cur_pan; + MapStreamer->GetPan(*pCurPan); + if (pExcess->x != 0.0f) { + pExcess->x = x / MapSize.x; + } + if (pExcess->y != 0.0f) { + pExcess->y = y / MapSize.y; + } + float factor = MapStreamer->GetZoomFactor(); + *pCurPan += *pExcess; + float max_pan = 0.5f - 1.0f / factor * 0.5f; + pCurPan->x = bClamp(pCurPan->x, -max_pan, max_pan); + pCurPan->y = bClamp(pCurPan->y, -max_pan, max_pan); + bVector2 prev_pan; + bVector2* pPrevPan = &prev_pan; + MapStreamer->GetPan(*pPrevPan); + bVector2 pan_to = *pCurPan + *pPrevPan; + *pCurPan = pan_to * 0.5f; + pCurPan->x += 0.5f; + pCurPan->y += 0.5f; + MapStreamer->SetPan(*pCurPan); + } + } + dx = bClamp(dx, MapTopLeft.x + 8.0f, pBottomRight->x + -8.0f); + dy = bClamp(dy, MapTopLeft.y + 26.0f, pBottomRight->y + -32.0f); + FEngSetCenter(Cursor, dx, dy); +} + +bool WorldMap::SnapCursor() { + bVector2 cursor; + bVector2 item_pos; + MapItem* snap_to = nullptr; + float last_closest = 100000000.0f; + FEngGetCenter(Cursor, cursor.x, cursor.y); + for (MapItem* item = TheMapItems.GetHead(); item != TheMapItems.EndOfList(); item = item->GetNext()) { + bVector2 pos; + item->GetCurrentPos(pos); + float cur_dist = bDistBetween(cursor, pos); + if (!item->IsHidden() && cur_dist < fSnapDist && cur_dist < last_closest) { + item_pos = pos; + snap_to = item; + last_closest = cur_dist; + } + } + if (snap_to != nullptr) { + const unsigned int _SNAP = 0x1cbf71; + FEngSetCenter(Cursor, item_pos.x, item_pos.y); + if (snap_to == SelectedItem) { + return false; + } + SelectedItem = snap_to; + FEngSetScript(Cursor, _SNAP, true); + } else { + if (SelectedItem == nullptr) { + return false; + } + const unsigned int _UNSNAP = 0x7efe8ff4; + FEngSetScript(Cursor, _UNSNAP, true); + SelectedItem = nullptr; + } + return true; +} + +void WorldMap::PanToCursor(float to_zoom) { + bVector2 cursor; + bVector2 pan; + bVector2 map_c; + bVector2* pCursor = &cursor; + bVector2* pPan = &pan; + bVector2* pMap_c = &map_c; + FEngGetCenter(Cursor, pCursor->x, pCursor->y); + MapStreamer->GetPan(*pPan); + pPan->x += 0.5f; + pPan->y += 0.5f; + float zoom = MapStreamer->GetZoomFactor(); + FEngGetCenter(static_cast< FEObject* >(TrackMap), pMap_c->x, pMap_c->y); + bVector2 offset; + bVector2* pOffset = &offset; + pOffset->x = pCursor->x - pMap_c->x; + pOffset->y = pCursor->y - pMap_c->y; + pOffset->x = pOffset->x / MapSize.x; + pOffset->y = pOffset->y / MapSize.y; + float max_pan = 1.0f / to_zoom * 0.5f; + float zoom_factor = 1.0f / zoom; + pOffset->x *= zoom_factor; + pOffset->y *= zoom_factor; + bVector2 scaled_offset; + bVector2* pScaledOffset = &scaled_offset; + reinterpret_cast< unsigned int* >(pScaledOffset)[0] = + reinterpret_cast< const unsigned int* >(pOffset)[0]; + reinterpret_cast< unsigned int* >(pScaledOffset)[1] = + reinterpret_cast< const unsigned int* >(pOffset)[1]; + bVector2 pan_to; + bVector2* pPanTo = &pan_to; + pPanTo->x = pPan->x + pScaledOffset->x; + pPanTo->y = pPan->y + pScaledOffset->y; + CursorMoveFrom.y = pPanTo->y * MapSize.y + MapTopLeft.y; + CursorMoveFrom.x = pPanTo->x * MapSize.x + MapTopLeft.x; + pPanTo->x = bClamp(pPanTo->x, max_pan, 1.0f - max_pan); + pPanTo->y = bClamp(pPanTo->y, max_pan, 1.0f - max_pan); + MapStreamer->PanTo(*pPanTo); +} + +void WorldMap::PanToPlayer() { + IPlayer* player = *IPlayer::GetList(PLAYER_LOCAL).begin(); + ISimable* isimable = player->GetSimable(); + bVector2 target_pos; + bVector2 target_dir; + bVector2* pTargetPos = &target_pos; + GetVehicleVectors(pTargetPos, &target_dir, isimable); + pTargetPos->x = (pTargetPos->x - pCurrentTrack->TrackMapCalibrationUpperLeft.x) / pCurrentTrack->TrackMapCalibrationMapWidthMetres; + pTargetPos->y = (pCurrentTrack->TrackMapCalibrationUpperLeft.y - pTargetPos->y) / pCurrentTrack->TrackMapCalibrationMapWidthMetres + 1.0f; + float max_pan = 1.0f / GetZoomFactor(static_cast< eWorldMapZoomLevels >(CurrentZoom)) * 0.5f; + pTargetPos->x = bClamp(pTargetPos->x, max_pan, 1.0f - max_pan); + pTargetPos->y = bClamp(pTargetPos->y, max_pan, 1.0f - max_pan); + MapStreamer->SetPan(*pTargetPos); +} + +void WorldMap::Setup() { + SetInitialPositions(); + + FEImage* img; + img = FEngFindImage(GetPackageName(), 0x5bc); + FEngSetButtonTexture(img, 0x5bc); + img = FEngFindImage(GetPackageName(), 0x682); + FEngSetButtonTexture(img, 0x682); + img = FEngFindImage(GetPackageName(), 0xfbb0b78e); + FEngSetButtonTexture(img, 0xfbb0b78e); + + TrackMap = static_cast< FEMultiImage* >(FEngFindObject(GetPackageName(), 0x0f365871)); + FEngGetTopLeft(static_cast< FEObject* >(TrackMap), MapTopLeft.x, MapTopLeft.y); + FEngGetSize(static_cast< FEObject* >(TrackMap), MapSize.x, MapSize.y); + Cursor = FEngFindObject(GetPackageName(), 0xf156f6c5); + + int region_unlock = 0; + unsigned char bin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + if (bin >= 13) { + region_unlock = 1; + } else if (bin > 8) { + region_unlock = 2; + } + + MapStreamer = new (__FILE__, __LINE__) UITrackMapStreamer(); + GRaceParameters* params = GRaceStatus::Get().GetRaceParameters(); + MapStreamer->Init(params, TrackMap, 0, region_unlock); + MapStreamer->SetZoomSpeed(0.5f); + MapStreamer->SetPanSpeed(0.5f); + MapStreamer->ResetZoom(false); + MapStreamer->ResetPan(false); + + if (params != nullptr) { + CurrentRaceType = params->GetRaceType(); + } else { + CurrentRaceType = -1; + } + + pCurrentTrack = TrackInfo::GetTrackInfo(TheRaceParameters.TrackNumber); + AddPlayerCar(); + + IPlayer* player = *IPlayer::GetList(PLAYER_LOCAL).begin(); + ISimable* isimable = player->GetSimable(); + IVehicle* ivehicle; + if (isimable->QueryInterface(&ivehicle)) { + IVehicleAI* ivehicleai = ivehicle->GetAIVehiclePtr(); + if (ivehicleai->GetPursuit() != nullptr) { + CurrentView = 3; + } + } + + if (CurrentView != 3) { + CurrentView = FEDatabase->GetGameplaySettings()->LastMapView; + } + + switch (CurrentView) { + case 0: + CurrentZoom = FEDatabase->GetGameplaySettings()->LastMapZoom; + SetupNavigation(); + break; + case 1: + CurrentZoom = FEDatabase->GetGameplaySettings()->LastMapZoom; + SetupEvent(); + break; + case 3: + CurrentZoom = FEDatabase->GetGameplaySettings()->LastPursuitMapZoom; + SetupPursuit(); + break; + } + + PanToPlayer(); + float zoomFactor = GetZoomFactor(static_cast< eWorldMapZoomLevels >(CurrentZoom)); + bVector2 zoom; + zoom.x = 1.0f / zoomFactor; + zoom.y = zoom.x; + MapStreamer->SetZoom(zoom); + SetInitialOption(0); + RefreshHeader(); +} + +void WorldMap::AddMapItemOption(unsigned int name_hash, eWorldMapItemType type) { + ItemTypeToggle* option = new ItemTypeToggle(name_hash, type, FEDatabase->GetGameplaySettings()->IsMapItemEnabled(type)); + Minimap::GameplayIconInfo& iconInfo = Minimap::GetGameplayIconInfo(type); + unsigned int tex_hash = 0; + unsigned int colour = 0xffffffff; + FEObject* iconObj = FEngFindObject(GetPackageName(), FEngHashString(iconInfo.mElementString, 0)); + if (iconObj != nullptr) { + FEColor c = FEngGetObjectColor(iconObj); + colour = static_cast< unsigned long >(c); + tex_hash = FEngGetTextureHash(static_cast< FEImage* >(iconObj)); + } + option->SetIcon(GetCurrentFEImage("OPTION_ICON_"), tex_hash, colour); + option->SetIconGroup(GetCurrentFEObject("ICON_VIS_GROUP_")); + AddButtonOption(option); +} + +void WorldMap::AddPlayerCar() { + const unsigned int FEObj_PlayerCarIndicator = 0xdd9ef5ff; + FEImage* icon = FEngFindImage(GetPackageName(), FEObj_PlayerCarIndicator); + IPlayer* player = *IPlayer::GetList(PLAYER_LOCAL).begin(); + ISimable* isimable = player->GetSimable(); + bVector2 target_dir; + bVector2 target_pos; + GetVehicleVectors(&target_pos, &target_dir, isimable); + bVector2 world_pos; + world_pos = target_pos; + ConvertPos(target_pos); + float rot = ConvertRot(target_dir); + MapItem* item = new MapItem(WMIT_PLAYER_CAR, static_cast< FEObject* >(icon), target_pos, world_pos, rot, nullptr); + TheMapItems.AddTail(item); +} + +void WorldMap::AddCops() { + int img_num = 0; + const IVehicle::List& vehicles = IVehicle::GetList(VEHICLE_AICOPS); + for (IVehicle* const* iter = vehicles.begin(); iter != vehicles.end(); iter++) { + if (!(*iter)->IsActive()) { + continue; + } + IPursuitAI* ipursuitai = nullptr; + (*iter)->QueryInterface(&ipursuitai); + ISimable* isimable = (*iter)->GetSimable(); + bVector2 target_pos; + bVector2 target_dir; + GetVehicleVectors(&target_pos, &target_dir, isimable); + bVector2 world_pos; + world_pos = target_pos; + ConvertPos(target_pos); + float rot = ConvertRot(target_dir); + if (ipursuitai != nullptr && ipursuitai->GetInPursuit()) { + const UCrc32& vehicleClass = (*iter)->GetVehicleClass(); + if (vehicleClass == VehicleClass::CHOPPER) { + AddMapItemOption(0xead9bd85, WMIT_COP_HELI); + FEObject* icon = FEngFindObject(GetPackageName(), 0xe26be422); + FEImage* view = FEngFindImage(GetPackageName(), 0x21390e47); + HeliItem* item = new HeliItem(view, icon, target_pos, world_pos, rot); + TheMapItems.AddTail(item); + } else { + FEImage* icon = FEngFindImage(GetPackageName(), + FEngHashString("MMICON_COPCAR_%d", img_num)); + CopItem* item = new CopItem(static_cast< FEObject* >(icon), target_pos, world_pos, + rot, WMIT_COP_CAR); + TheMapItems.AddTail(item); + img_num++; + } + } + } + if (img_num > 0) { + AddMapItemOption(0xead6ef6c, WMIT_COP_CAR); + } +} + +void WorldMap::AddRoadBlocks() { + int img_num = 0; + const IRoadBlock::List& blocks = IRoadBlock::GetList(); + for (IRoadBlock* const* i = blocks.begin(); i != blocks.end(); i++) { + IRoadBlock* rb = *i; + UMath::Vector3 pos; + UMath::Vector3* pPos = &pos; + UMath::Vector3 dir; + UMath::Vector3* pDir = &dir; + *pPos = rb->GetRoadBlockCentre(); + *pDir = rb->GetRoadBlockDir(); + bVector2 target_pos; + bVector2* pTargetPos = &target_pos; + bVector2 target_dir; + bVector2* pTargetDir = &target_dir; + pTargetPos->x = pPos->z; + pTargetPos->y = -pPos->x; + pTargetDir->x = pDir->z; + pTargetDir->y = -pDir->x; + bVector2 world_pos; + bVector2* pWorldPos = &world_pos; + pWorldPos->x = pTargetPos->x; + pWorldPos->y = pTargetPos->y; + ConvertPos(*pTargetPos); + float rot = ConvertRot(*pTargetDir); + FEImage* icon = FEngFindImage(GetPackageName(), FEngHashString("MMICON_ROADBLOCK_%d", img_num)); + img_num++; + MapItem* item = new MapItem(WMIT_ROADBLOCK, static_cast< FEObject* >(icon), *pTargetPos, + *pWorldPos, rot, nullptr); + TheMapItems.AddTail(item); + } + if (img_num > 0) { + AddMapItemOption(0x411f1f86, WMIT_ROADBLOCK); + } +} + +void WorldMap::AddIcon(eWorldMapItemType type, unsigned int hash, GIcon* icon) { + if (hash != 0 && icon != nullptr) { + FEImage* image = FEngFindImage(GetPackageName(), hash); + if (image != nullptr) { + bVector2 pos2D; + icon->GetPosition2D(pos2D); + bVector2 world_pos = pos2D; + float rot = 0.0f; + ConvertPos(pos2D); + MapItem* item = new MapItem(type, static_cast< FEObject* >(image), pos2D, world_pos, rot, icon); + TheMapItems.AddTail(item); + } + } +} + +void WorldMap::AddIcons(GIcon::Type desiredIconType) { + GIcon* sortedIcons[200]; + int numIcons; + int numIconsPlaced; + + numIconsPlaced = 0; + IPlayer* player = IPlayer::First(PLAYER_LOCAL); + numIcons = GManager::Get().GatherVisibleIcons(sortedIcons, player); + for (int onIcon = 0; onIcon < numIcons; onIcon++) { + GIcon* icon = sortedIcons[onIcon]; + GIcon::Type iconType = icon->GetType(); + Minimap::GameplayIconInfo& iconInfo = Minimap::GetGameplayIconInfo(iconType); + if (iconInfo.mItemType != 0 && iconType == desiredIconType) { + unsigned int hash = FEngHashString(iconInfo.mElementString, numIconsPlaced); + AddIcon(iconInfo.mItemType, hash, icon); + numIconsPlaced++; + } + } + if (numIconsPlaced > 0) { + Minimap::GameplayIconInfo& desiredIconInfo = Minimap::GetGameplayIconInfo(desiredIconType); + AddMapItemOption(desiredIconInfo.mWorldMapTitle, desiredIconInfo.mItemType); + } +} + +void WorldMap::SetupNavigation() { + FEngSetVisible(Cursor); + AddIcons(GIcon::kType_GateCustomShop); + AddIcons(GIcon::kType_GateSafehouse); + AddIcons(GIcon::kType_GateCarLot); +} + +void WorldMap::SetupEvent() { + FEngSetVisible(Cursor); + AddIcons(GIcon::kType_RaceSprint); + AddIcons(GIcon::kType_RaceCircuit); + AddIcons(GIcon::kType_RaceDrag); + AddIcons(GIcon::kType_RaceKnockout); + AddIcons(GIcon::kType_RaceTollbooth); + AddIcons(GIcon::kType_RaceSpeedtrap); + AddIcons(GIcon::kType_RaceRival); + AddIcons(GIcon::kType_SpeedTrap); + AddIcons(GIcon::kType_SpeedTrapInRace); +} + +void WorldMap::SetupPursuit() { + FEngSetInvisible(GetPackageName(), 0xa808e057); + FEngSetInvisible(GetPackageName(), 0x95fdfc4e); + AddIcons(GIcon::kType_GateSafehouse); + AddIcons(GIcon::kType_PursuitBreaker); + AddIcons(GIcon::kType_HidingSpot); + AddCops(); + AddRoadBlocks(); +} + +void WorldMap::ConvertPos(bVector2& pos) { + float x = (pos.x - pCurrentTrack->TrackMapCalibrationUpperLeft.x) / pCurrentTrack->TrackMapCalibrationMapWidthMetres; + pos.x = x; + float y = (pCurrentTrack->TrackMapCalibrationUpperLeft.y - pos.y) / pCurrentTrack->TrackMapCalibrationMapWidthMetres + 1.0f; + pos.y = y; + pos.x = MapTopLeft.x + x * MapSize.x; + pos.y = MapTopLeft.y + y * MapSize.y; +} + +float WorldMap::ConvertRot(bVector2& dir) { + return bAngToDeg(bATan(dir.y, dir.x)); +} + +void WorldMap::DrawItemType() { + Minimap::GameplayIconInfo& desiredIconInfo = Minimap::GetGameplayIconInfo(SelectedItem->GetType()); + FEngSetLanguageHash(GetPackageName(), 0x9331fd4f, desiredIconInfo.mWorldMapTitle); + if (desiredIconInfo.mWorldMapTitle != 0) { + FEngSetVisible(GetPackageName(), 0x9331fd4f); + } else { + FEngSetInvisible(GetPackageName(), 0x9331fd4f); + } +} + +void WorldMap::DrawItemStats() { + IPlayer* player = *IPlayer::GetList(PLAYER_LOCAL).begin(); + ISimable* isimable = player->GetSimable(); + UMath::Vector3 player_pos = isimable->GetPosition(); + bVector2 real_player; + bVector2 real_trigger; + real_player.x = player_pos.z; + real_player.y = -player_pos.x; + SelectedItem->GetWorldPos(real_trigger); + float distance = bDistBetween(real_trigger, real_player); + bool kph = true; + const char* distUnits; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + distUnits = GetLocalizedString(0x8569a26a); + } else { + kph = false; + distUnits = GetLocalizedString(0x867dcfd9); + } + if (SelectedItem->GetType() != WMIT_PLAYER_CAR) { + float length; + if (kph) { + length = distance * 0.001f; + } else { + length = distance * 0.000625f; + } + FEPrintf(GetPackageName(), 0xfeeeb39b, "%$.1f %s", length, distUnits); + FEngSetVisible(GetPackageName(), 0xfeeeb39b); + } else { + FEngSetInvisible(GetPackageName(), 0xfeeeb39b); + } + Minimap::GameplayIconInfo& desiredIconInfo = Minimap::GetGameplayIconInfo(SelectedItem->GetType()); + if (desiredIconInfo.mworldIconTexHash != 0) { + FEngSetTextureHash(GetPackageName(), 0x9a5ab124, desiredIconInfo.mworldIconTexHash); + FEngSetVisible(GetPackageName(), 0x9a5ab124); + } else { + FEngSetInvisible(GetPackageName(), 0x9a5ab124); + } +} + +void WorldMap::RefreshHeader() { + switch (CurrentView) { + case 0: + FEngSetLanguageHash(GetPackageName(), 0xd259525f, 0xbf55e8b2); + break; + case 1: + FEngSetLanguageHash(GetPackageName(), 0xd259525f, 0xdfd23484); + break; + case 2: + FEngSetLanguageHash(GetPackageName(), 0xd259525f, 0xf74b357d); + break; + case 3: + FEngSetLanguageHash(GetPackageName(), 0xd259525f, 0xfea872d4); + break; + } + + unsigned int zoom_hash = 0x213587bf; + switch (CurrentZoom) { + case 1: + zoom_hash = 0x0a9be7d7; + break; + case 2: + zoom_hash = 0x0a9be7d8; + break; + case 3: + zoom_hash = 0x0a9be7da; + break; + } + FEngSetLanguageHash(GetPackageName(), 0xcb76ce5b, zoom_hash); + + if (SelectedItem != nullptr) { + DrawItemType(); + DrawItemStats(); + } else { + FEPrintf(GetPackageName(), 0x9331fd4f, ""); + FEPrintf(GetPackageName(), 0xfeeeb39b, ""); + } + + if (pCurrentOption != nullptr && bInToggleMode) { + ItemTypeToggle* tog = static_cast< ItemTypeToggle* >(pCurrentOption); + if (tog->GetVisibility()) { + FEngSetScript(GetPackageName(), 0x32490131, 0x6ebbfb68, true); + FEngSetLanguageHash(GetPackageName(), 0x29456cc8, 0x2c35ec64); + } else { + FEngSetScript(GetPackageName(), 0x32490131, 0x6ebbfb68, true); + FEngSetLanguageHash(GetPackageName(), 0x29456cc8, 0xba0a6a2b); + } + FEngSetLanguageHash(GetPackageName(), 0x51f0064f, 0x58b828ed); + return; + } + + IPlayer* iplayer = IPlayer::First(PLAYER_LOCAL); + if (iplayer == nullptr) { + return; + } + + ISimable* isimable = iplayer->GetSimable(); + if (isimable == nullptr) { + return; + } + + if (SelectedItem != nullptr && SelectedItem->TheIcon != 0) { + FEngSetLanguageHash(GetPackageName(), 0x29456cc8, 0x43512519); + FEngSetScript(GetPackageName(), 0x32490131, 0x6ebbfb68, true); + } else if (mGPSingIcon != nullptr) { + FEngSetLanguageHash(GetPackageName(), 0x29456cc8, 0xf1d0d8a5); + FEngSetScript(GetPackageName(), 0x32490131, 0x6ebbfb68, true); + } else { + FEngSetLanguageHash(GetPackageName(), 0x29456cc8, 0x43512519); + FEngSetScript(GetPackageName(), 0x32490131, 0x00163c76, true); + } + FEngSetLanguageHash(GetPackageName(), 0x51f0064f, 0x001335f0); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.hpp index 9634b97f5..20400f300 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/InGame/uiWorldMap.hpp @@ -5,4 +5,247 @@ #pragma once #endif +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp" +#include "Speed/Indep/Src/Gameplay/GIcon.h" +#include "Speed/Indep/Src/Gameplay/GRace.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" + +void FEngGetCenter(FEObject* obj, float& x, float& y); +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +struct FEObject; +struct FEImage; +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetCenter(FEObject* obj, float x, float y); +void FEngSetSize(FEObject* obj, float x, float y); +void FEngSetScaleX(FEObject* obj, float x); +void FEngSetScaleY(FEObject* obj, float y); + +struct FEMultiImage; +struct ActionQueue; +struct TrackInfo; +struct UITrackMapStreamer; + +#ifndef FRONTEND_DATABASE_EWORLDMAPITEMTYPE_DEFINED +#define FRONTEND_DATABASE_EWORLDMAPITEMTYPE_DEFINED +enum eWorldMapItemType { + WMIT_NONE = 0, + WMIT_PLAYER_CAR = 1, + WMIT_AI_RACE_CAR = 2, + WMIT_COP_CAR = 4, + WMIT_COP_HELI = 8, + WMIT_TRAFFIC_CAR = 16, + WMIT_ROADBLOCK = 32, + WMIT_CHECKPOINT = 64, + WMIT_CIRCUIT_RACE = 128, + WMIT_SPRINT_RACE = 256, + WMIT_LAP_KO_RACE = 512, + WMIT_DRAG_RACE = 1024, + WMIT_SPEED_TRAP_RACE = 2048, + WMIT_TOLLBOOTH_RACE = 4096, + WMIT_MULTIPOINT_RACE = 8192, + WMIT_CELL_PHONE_RACE = 16384, + WMIT_RIVAL_RACE = 32768, + WMIT_CASH_GRAB_RACE = 65536, + WMIT_CASH_GRAB_SMALL = 131072, + WMIT_CASH_GRAB_MED = 262144, + WMIT_CASH_GRAB_LARGE = 524288, + WMIT_CASH_GRAB_ALL = 917504, + WMIT_SPEED_TRAP = 1048576, + WMIT_SAFEHOUSE = 2097152, + WMIT_SHOP = 4194304, + WMIT_CAR_LOT = 8388608, + WMIT_TOKEN = 16777216, + WMIT_HIDING_SPOT = 33554432, + WMIT_PURSUIT_BREAKER = 67108864, +}; +#endif + +enum eWorldMapZoomLevels { + WMZ_ALL = 0, + WMZ_LEVEL_1 = 1, + WMZ_LEVEL_2 = 2, + WMZ_LEVEL_4 = 3, + WMZ_MAX_ZOOM = 3, + NUM_ZOOM_LEVELS = 4, +}; + +// total size: 0x38 +struct MapItem : public bTNode { + FEObject* pIcon; // offset 0x8, size 0x4 + bVector2 InitialPos; // offset 0xC, size 0x8 + bVector2 InitialSize; // offset 0x14, size 0x8 + bVector2 WorldPos; // offset 0x1C, size 0x8 + float Rot; // offset 0x24, size 0x4 + eWorldMapItemType TheType; // offset 0x28, size 0x4 + GIcon* TheIcon; // offset 0x2C, size 0x4 + bool bHidden; // offset 0x30, size 0x1 + // vtable at 0x34 + + MapItem(eWorldMapItemType type, FEObject* iconObj, bVector2& map_pos, bVector2& world_pos, + float rot, GIcon* icon); + virtual ~MapItem() {} + + void GetInitialPos(bVector2& pos) { + pos = InitialPos; + } + void GetWorldPos(bVector2& pos) { + pos = WorldPos; + } + void GetCurrentPos(bVector2& pos) { + FEngGetCenter(pIcon, pos.x, pos.y); + } + virtual void UpdatePos(bVector2& pos) { + FEngSetCenter(pIcon, pos.x, pos.y); + } + virtual void UpdateScale(float scale) { + FEngSetSize(pIcon, InitialSize.x * scale, InitialSize.y * scale); + } + virtual void Draw() {} + virtual void Show(); + virtual void Hide(); + virtual void ResetSize() { + FEngSetSize(pIcon, InitialSize.x, InitialSize.y); + } + GIcon* GetIcon() { return TheIcon; } + void SetHidden(bool b) { + bHidden = b; + if (!b) { + Show(); + } else { + Hide(); + } + } + bool IsHidden() { return bHidden; } + eWorldMapItemType GetType() { return TheType; } +}; + +struct CopItem : public MapItem { + int FlashTimer; // offset 0x38, size 0x4 + + CopItem(FEObject* icon, bVector2& pos, bVector2& world_pos, float rot, eWorldMapItemType type); + ~CopItem() override {} + void Draw() override; +}; + +struct HeliItem : public CopItem { + FEImage* pViewCone; // offset 0x3C, size 0x4 + + HeliItem(FEImage* view, FEObject* icon, bVector2& pos, bVector2& world_pos, float rot); + ~HeliItem() override {} + void UpdatePos(bVector2& pos) override { + FEngSetCenter(pIcon, pos.x, pos.y); + FEngSetCenter(static_cast< FEObject* >(pViewCone), pos.x, pos.y); + } + void UpdateScale(float scale) override { + FEngSetScaleX(pIcon, InitialSize.x * scale); + FEngSetScaleY(pIcon, InitialSize.y * scale); + } + void Draw() override; + void Show() override { + FEngSetVisible(pIcon); + FEngSetVisible(static_cast< FEObject* >(pViewCone)); + } + void Hide() override { + FEngSetInvisible(pIcon); + FEngSetInvisible(static_cast< FEObject* >(pViewCone)); + } + void ResetSize() override { + FEngSetScaleX(pIcon, InitialSize.x); + FEngSetScaleY(pIcon, InitialSize.y); + } +}; + +struct ItemTypeToggle : public FEButtonWidget { + eWorldMapItemType ItemType; // offset 0x40, size 0x4 + unsigned int NameHash; // offset 0x44, size 0x4 + FEImage* pIcon; // offset 0x48, size 0x4 + FEObject* pIconGroup; // offset 0x4C, size 0x4 + int bVisibility; // offset 0x50, size 0x4 + int bExiting; // offset 0x54, size 0x4 + + ItemTypeToggle(unsigned int name_hash, eWorldMapItemType type, bool vis); + ~ItemTypeToggle() override {} + void SetIconGroup(FEObject* obj) { pIconGroup = obj; } + eWorldMapItemType GetType() { return ItemType; } + int GetVisibility() { return bVisibility; } + void StartExit() { bExiting = true; } + + void Act(const char* parent_pkg, unsigned int data) override; + void CheckMouse(const char* parent_pkg, const float mouse_x, const float mouse_y) override; + void Draw() override; + void Position() override; + void UnsetFocus() override; + void SetIcon(FEImage* icon, unsigned int texHash, unsigned int texColour); + void Show() override; + void Hide() override; +}; + +// total size: 0x19C +struct WorldMap : public UIWidgetMenu { + FEObject* Cursor; // offset 0x138, size 0x4 + ActionQueue* mActionQ; // offset 0x13C, size 0x4 + bVector2 CurrentVelocity; // offset 0x140, size 0x8 + Timer TimeSinceLastMove; // offset 0x148, size 0x4 + bVector2 CursorMoveFrom; // offset 0x14C, size 0x8 + TrackInfo* pCurrentTrack; // offset 0x154, size 0x4 + FEMultiImage* TrackMap; // offset 0x158, size 0x4 + bVector2 MapTopLeft; // offset 0x15C, size 0x8 + bVector2 MapSize; // offset 0x164, size 0x8 + bTList TheMapItems; // offset 0x16C, size 0x8 + MapItem* SelectedItem; // offset 0x174, size 0x4 + UITrackMapStreamer* MapStreamer; // offset 0x178, size 0x4 + unsigned int CurrentView; // offset 0x17C, size 0x4 + int CurrentZoom; // offset 0x180, size 0x4 + int CurrentRaceType; // offset 0x184, size 0x4 + bool bInToggleMode; // offset 0x188, size 0x1 + bool bCursorOn; // offset 0x18C, size 0x1 + bool bCursorMoving; // offset 0x190, size 0x1 + bool bLeftHeldOnMap; // offset 0x194, size 0x1 + float fSnapDist; // offset 0x198, size 0x4 + + static GIcon* mGPSingIcon; + + static GIcon* GetGPSingIcon() { return mGPSingIcon; } + static void SetGPSing(GIcon* icon); + static void ClearGPSing(); + + WorldMap(ScreenConstructorData* sd); + ~WorldMap() override; + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; + + void ScrollZoom(eScrollDir dir); + float GetZoomFactor(eWorldMapZoomLevels level); + void UpdateIconVisibility(eWorldMapItemType type, bool vis); + void ClearItems(); + bool ClampToMapBounds(float& x, float& y); + void UpdateAnalogInput(); + void UpdateCursor(bool zoom_thing); + void MoveCursor(float x, float y); + bool SnapCursor(); + void PanToCursor(float to_zoom); + void PanToPlayer(); + void Setup() override; + void AddMapItemOption(unsigned int name_hash, eWorldMapItemType type); + void AddPlayerCar(); + void AddCops(); + void AddRoadBlocks(); + void AddIcon(eWorldMapItemType type, unsigned int icon_hash, GIcon* icon); + void AddIcons(GIcon::Type desiredIconType); + void SetupNavigation(); + void SetupEvent(); + void SetupPursuit(); + void ConvertPos(bVector2& pos); + float ConvertRot(bVector2& dir); + void DrawItemType(); + void DrawItemStats(); + void RefreshHeader(); +}; + #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.cpp index e69de29bb..e5dc0c7ff 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.cpp @@ -0,0 +1,140 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" + +extern bool BuildRegion_IsPal(); +extern bool eIsWidescreen(); +extern char *bStrStr(const char *s1, const char *s2); + +extern const char *sBootFlowNTSC[]; +extern const char *sBootFlowPAL[]; +extern const char *sBootFlowWideScreen[]; +extern const char *sBootFlowPALWidescreen[]; + +BootFlowManager *BootFlowManager::mInstance; + +BootFlowManager *BootFlowManager::Get() { + return mInstance; +} + +void BootFlowManager::JumpToHead() { + CurrentScreen = BootFlowScreens.GetHead(); + JumpToScreen(CurrentScreen->Name); +} + +void BootFlowManager::Init() { + if (mInstance == nullptr) { + mInstance = new BootFlowManager(); + } +} + +void BootFlowManager::Destroy() { + if (mInstance != nullptr) { + delete mInstance; + mInstance = nullptr; + CarViewer::ShowAllCars(); + } + g_pEAXSound->PlayFEMusic(-1); +} + +BootFlowManager::~BootFlowManager() { +} + +BootFlowManager::BootFlowManager() { + if (!BuildRegion_IsPal()) { + if (eIsWidescreen()) { + for (int i = 0; i < 7; i++) { + if (*sBootFlowWideScreen[i] != '\0') { + BootFlowScreen *screen = new BootFlowScreen(); + screen->Name = sBootFlowWideScreen[i]; + BootFlowScreens.AddTail(screen); + } + } + } else { + for (int i = 0; i < 7; i++) { + if (*sBootFlowNTSC[i] != '\0') { + BootFlowScreen *screen = new BootFlowScreen(); + screen->Name = sBootFlowNTSC[i]; + BootFlowScreens.AddTail(screen); + } + } + } + } else { + if (eIsWidescreen()) { + for (int i = 0; i < 7; i++) { + if (*sBootFlowPALWidescreen[i] != '\0') { + BootFlowScreen *screen = new BootFlowScreen(); + screen->Name = sBootFlowPALWidescreen[i]; + BootFlowScreens.AddTail(screen); + } + } + } else { + for (int i = 0; i < 7; i++) { + if (*sBootFlowPAL[i] != '\0') { + BootFlowScreen *screen = new BootFlowScreen(); + screen->Name = sBootFlowPAL[i]; + BootFlowScreens.AddTail(screen); + } + } + } + } + CurrentScreen = BootFlowScreens.GetHead(); +} + +BootFlowScreen *BootFlowManager::FindScreen(const char *name) { + { + BootFlowScreen *s = BootFlowScreens.GetHead(); + while (s != BootFlowScreens.EndOfList()) { + if (bStrICmp(s->Name, name) == 0) { + return s; + } + s = s->GetNext(); + } + } + return nullptr; +} + +BootFlowScreen *BootFlowManager::FindScreenSubStr(const char *name) { + { + BootFlowScreen *s = BootFlowScreens.GetHead(); + while (s != BootFlowScreens.EndOfList()) { + if (bStrStr(s->Name, name) != nullptr) { + return s; + } + s = s->GetNext(); + } + } + return nullptr; +} + +bool BootFlowManager::JumpToScreen(const char *screen_name) { + BootFlowScreen *screen = FindScreen(screen_name); + if (screen == nullptr) { + return false; + } + CurrentScreen = screen; + cFEng::mInstance->QueuePackagePop(0); + cFEng::mInstance->QueuePackagePush(CurrentScreen->Name, 0, 0, false); + if (BootFlowScreens.GetTail() == CurrentScreen) { + Destroy(); + } + return true; +} + +bool BootFlowManager::DoAttract() { + BootFlowScreen *screen = FindScreenSubStr("Attract"); + if (screen == nullptr) { + return false; + } + return JumpToScreen(screen->Name); +} + +void BootFlowManager::ChangeToNextBootFlowScreen(int mask) { + CurrentScreen = CurrentScreen->GetNext(); + cFEng::mInstance->QueuePackageSwitch(CurrentScreen->Name, 0, mask, false); + if (BootFlowScreens.GetTail() == CurrentScreen) { + Destroy(); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp index 41ddb7d3a..08e3cc83b 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp @@ -5,6 +5,30 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bList.hpp" +struct BootFlowScreen : public bTNode { + const char *Name; // offset 0x8 + virtual ~BootFlowScreen() {} +}; + +struct BootFlowManager { + static void Init(); + static void Destroy(); + static BootFlowManager *Get(); + BootFlowManager(); + virtual ~BootFlowManager(); + BootFlowScreen *FindScreen(const char *name); + BootFlowScreen *FindScreenSubStr(const char *name); + void JumpToHead(); + bool JumpToScreen(const char *screen_name); + bool DoAttract(); + void ChangeToNextBootFlowScreen(int mask); + + bTList BootFlowScreens; // offset 0x0 + BootFlowScreen *CurrentScreen; // offset 0x8 + + static BootFlowManager *mInstance; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.cpp index e69de29bb..82e7dfa03 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.cpp @@ -0,0 +1,24 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.hpp" + +extern Timer RealTimer; + +LanguageSelectScreen::LanguageSelectScreen(ScreenConstructorData *sd) + : IconScrollerMenu(sd) // +{ + StartedTimer.ResetLow(); + if (bFadeInIconsImmediately) { + Options.fCurFadeTime = 0.0f; + Options.bDelayUpdate = false; + Options.bFadingIn = true; + Options.bFadingOut = false; + } + Options.SetInitialPos(0); + RefreshHeader(); + StartedTimer = RealTimer; +} + +LanguageSelectScreen::~LanguageSelectScreen() {} + +void LanguageSelectScreen::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, obj, param1, param2); +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.hpp index ab2ec088f..d4820b310 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELanguageSelect.hpp @@ -5,6 +5,19 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +// total size: 0x170 +struct LanguageSelectScreen : public IconScrollerMenu { + LanguageSelectScreen(ScreenConstructorData *sd); + ~LanguageSelectScreen() override; + void Setup() override {} + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) override; + + static bool bChoiceMade; + + Timer StartedTimer; // offset 0x16C +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.cpp index e69de29bb..b95d9742c 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.cpp @@ -0,0 +1,121 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning); +extern void eUnloadStreamingTexture(unsigned int *textures, int count); + +void *LoadingControllerScreen::mLoadingControllerScreenPtr; + +LoadingControllerScreen::LoadingControllerScreen(ScreenConstructorData *sd) : MenuScreen(sd) {} + +void LoadingControllerScreen::NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) {} + +void LoadingControllerScreen::ShowControllerConfig() { + FEngSetScript(GetPackageName(), 0x3248E720, 0x001CA7C0, true); +} + +void LoadingControllerScreen::HideControllerConfig() { + FEngSetScript(GetPackageName(), 0x3248E720, 0x0016A259, true); + WhichControllerTexture = 0; +} + +void LoadingControllerScreen::ClearLoadedControllerTexture() { + unsigned int tex[1]; + tex[0] = WhichControllerTexture; + if (tex[0]) { + eUnloadStreamingTexture(tex, 1); + } +} + +void LoadingControllerScreen::FinishLoadingControllerTextureCallback(unsigned int p) { + ShowControllerConfig(); +} + +extern bool IsJoystickTypeWheel(JoystickPort port); +extern FEImage *FEngFindImage(const char *pkg_name, int hash); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +extern void eLoadStreamingTexture(unsigned int *textures, int count, void (*callback)(unsigned int), void *user, int priority); +extern void FinishLoadingControllerTextureCallbackBridge(unsigned int p); + +void LoadingControllerScreen::PrepToShowControllerConfig() { + unsigned int texHash; + if (!IsJoystickTypeWheel(static_cast(FEDatabase->PlayerJoyports[0]))) { + texHash = 0xed543bac; + if (FEDatabase->CurrentUserProfiles[0]->GetOptions()->ThePlayerSettings[0].DriveWithAnalog != 0) { + texHash = 0xed543bab; + } + } else { + FEDatabase->CurrentUserProfiles[0]->GetOptions()->ThePlayerSettings[0].Config = static_cast(0); + texHash = 0xb511476b; + } + WhichControllerTexture = texHash; + FEImage *img = FEngFindImage(GetPackageName(), 0x922a39c4); + FEngSetTextureHash(img, texHash); + unsigned int texArray[1]; + texArray[0] = WhichControllerTexture; + eLoadStreamingTexture(texArray, 1, FinishLoadingControllerTextureCallbackBridge, this, 0); +} + +void FinishLoadingControllerTextureCallbackBridge(unsigned int p) { + LoadingControllerScreen *ls; + if (p != 0) { + ls = reinterpret_cast(p); + ls->FinishLoadingControllerTextureCallback(0); + } +} + +void LoadingControllerScreen::InitLoadingControllerScreen() { + mLoadingControllerScreenPtr = bMalloc(0x38, nullptr, 0, 0); +} + +MenuScreen *CreateLoadingControllerScreen(ScreenConstructorData *sd) { + return new (LoadingControllerScreen::mLoadingControllerScreenPtr) LoadingControllerScreen(sd); +} + +extern unsigned int FindButtonNameHashForFEString(int config, int string_number, JoystickPort player); +extern int FEngSNPrintf(char *, int, const char *, ...); +extern unsigned long FEHashUpper(const char *name); +extern void FEngSetVisible(const char *pkg_name, unsigned int obj_hash); +extern void FEngSetInvisible(const char *pkg_name, unsigned int obj_hash); +extern void FEngSetLanguageHash(const char *pkg_name, unsigned int object_hash, unsigned int language_hash); +extern void FEngSetTextureHash(const char *pkg_name, unsigned int obj_hash, unsigned int texture_hash); + +void LoadingControllerScreen::SetupControllerConfig() { + if (!FEDatabase->IsCareerMode()) { + cFEng::Get()->QueuePackageMessage(0xde511657, GetPackageName(), nullptr); + } + JoystickPort port = static_cast(FEDatabase->GetPlayersJoystickPort(0)); + int config = FEDatabase->GetPlayerSettings(0)->Config; + for (int i = 0; i < 17; i++) { + char sztemp[32]; + FEngSNPrintf(sztemp, 0x20, "BUTTON%d", i + 1); + unsigned int obj_hash = FEHashUpper(sztemp); + FEngSNPrintf(sztemp, 0x20, "BUTTON%d_I", i + 1); + unsigned int img_hash = FEHashUpper(sztemp); + unsigned int button_hash = FindButtonNameHashForFEString(config, i, port); + if (button_hash != 0) { + FEngSetVisible(GetPackageName(), obj_hash); + FEngSetLanguageHash(GetPackageName(), obj_hash, button_hash); + FEngSetVisible(GetPackageName(), img_hash); + } else { + FEngSetInvisible(GetPackageName(), obj_hash); + FEngSetInvisible(GetPackageName(), img_hash); + } + } + if (FEDatabase->GetPlayerSettings(0)->DriveWithAnalog != 0) { + FEngSetTextureHash(GetPackageName(), 0x4592229c, 0x148e38); + } else { + FEngSetButtonTexture(FEngFindImage(GetPackageName(), 0x4592229c), 0xb30961b); + } + FEngSetInvisible(GetPackageName(), 0xf274b86); + FEngSetInvisible(GetPackageName(), 0x673d77bc); + FEngSetInvisible(GetPackageName(), 0x351ae442); + FEImage *img0 = FEngFindImage(GetPackageName(), 0x81b57400); + FEngSetTextureHash(img0, 0x2959349); + FEImage *img1 = FEngFindImage(GetPackageName(), 0x81b57401); + FEngSetTextureHash(img1, 0x6851aaf5); + FEImage *img2 = FEngFindImage(GetPackageName(), 0x81b57402); + FEngSetTextureHash(img2, 0x3b7f86d); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.hpp index 4009cbb64..432626d4f 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingControllerScreen.hpp @@ -5,6 +5,29 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +struct GameTipInfo; + +struct LoadingControllerScreen : public MenuScreen { + LoadingControllerScreen(ScreenConstructorData *sd); + static void InitLoadingControllerScreen(); + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + void PrepToShowControllerConfig(); + void SetupControllerConfig(); + void ShowControllerConfig(); + void HideControllerConfig(); + void ClearLoadedControllerTexture(); + void FinishLoadingControllerTextureCallback(unsigned int p); + + void *operator new(size_t, void *ptr) { return ptr; } + + static void *mLoadingControllerScreenPtr; + + private: + int LoadingFinished; // offset 0x2C + GameTipInfo *GameTipToShow; // offset 0x30 + unsigned int WhichControllerTexture; // offset 0x34 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.cpp index e69de29bb..668a36b67 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.cpp @@ -0,0 +1,52 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Misc/BuildRegion.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Generated/Events/ESndGameState.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool start_at_beginning); +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +extern void FEngSetVisible(FEObject *obj); +extern bool eIsWidescreen(); +extern void SetSoundControlState(bool bON, eSNDCTLSTATE esndstate, const char *Reason); + +static bool bSawLoadingScreen; + +void *LoadingScreen::mLoadingScreenPtr; + +LoadingScreen::LoadingScreen(ScreenConstructorData *sd) : MenuScreen(sd) { + const unsigned long FEObj_LoadingBlinker = 0xCF281D29; + + if (FEManager::Get()->IsFirstBoot()) { + if (BuildRegion::ShowLanguageSelect()) { + FEngSetScript(PackageFilename, FEObj_LoadingBlinker, 0x5D7C6A21, true); + } + } + + bSawLoadingScreen = true; + + { + const unsigned long FEObj_LOADINGGROUP = 0x06D91704; + + FEngSetVisible(FEngFindObject(GetPackageName(), FEObj_LOADINGGROUP)); + } + + if (eIsWidescreen()) { + cFEng::Get()->QueuePackageMessage(bStringHash("CURRENT_GEN_WIDESCREEN"), GetPackageName(), nullptr); + } + + new ESndGameState(10, true); + SetSoundControlState(true, SNDSTATE_OFF, "FELoad"); +} + +void LoadingScreen::NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) {} + +void LoadingScreen::InitLoadingScreen() { + mLoadingScreenPtr = bMalloc(0x2C, nullptr, 0, 0); +} + +MenuScreen *CreateLoadingScreen(ScreenConstructorData *sd) { + return new (LoadingScreen::mLoadingScreenPtr) LoadingScreen(sd); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.hpp index a8e7f61c4..61679ee91 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.hpp @@ -5,6 +5,21 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +struct LoadingScreen : public MenuScreen { + enum LoadingScreenTypes { + LS_LOADING_FE = 0, + LS_LOADING_GAME_FROM_FE = 1, + }; + + LoadingScreen(ScreenConstructorData *sd); + static void InitLoadingScreen(); + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + + void *operator new(size_t, void *ptr) { return ptr; } + + static void *mLoadingScreenPtr; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.cpp index e69de29bb..fec444a4e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.cpp @@ -0,0 +1,242 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern MenuScreen *FEngFindScreen(const char *package_name); +extern void eUnloadStreamingTexture(unsigned int *name_hash, int param); + +void *LoadingTips::mLoadingTipsScreenPtr; +bool LoadingTips::mDoneLoading; +bool LoadingTips::mDoneShowingLoadingTips; + +LoadingTips::LoadingTips(ScreenConstructorData *sd) + : MenuScreen(sd) // +{ + DisplayTime.ResetHigh(); + CurrentTip = nullptr; + GameTipInfo *tip = GetGameTip(static_cast(sd->Arg)); + CurrentTip = tip; + if (tip->Flags & 0x400) { + mDoneShowingLoadingTips = false; + } else { + mDoneShowingLoadingTips = true; + } + mDoneLoading = false; + mPressAcceptHasBeenShown = false; + StartLoadingTipImage(); +} + +LoadingTips::~LoadingTips() { + unsigned int hash = TipTextureHash; + eUnloadStreamingTexture(&hash, 1); +} + +GameTipInfo *LoadingTips::GetGameTip(eGameTips tip) { + if (static_cast(tip) - 1 <= 0x19) { + return &GameTipInfoTable[tip]; + } + return &GameTipInfoTable[0]; +} + +void LoadingTips::InitLoadingTipsScreen() { + mLoadingTipsScreenPtr = bMalloc(0x3C, nullptr, 0, 0); +} + +void LoadingTips::FinishLoadingTexCallback(unsigned int p) { + ShowTipInfo(); +} + +MenuScreen *CreateLoadingTipsScreen(ScreenConstructorData *sd) { + return new (LoadingTips::mLoadingTipsScreenPtr) LoadingTips(sd); +} + +static void LoadingTips_FinishLoadingTexBridge(unsigned int p) { + LoadingTips *ls = static_cast(FEngFindScreen("Loading_Tips.fng")); + if (ls) { + ls->FinishLoadingTexCallback(p); + } +} + +void LoadingTips::StartLoadingTipImage() { + if (CurrentTip) { + unsigned int hash = FEngHashString(CurrentTip->Name); + TipTextureHash = hash; + eLoadStreamingTexture(&hash, 1, LoadingTips_FinishLoadingTexBridge, 0, 0); + } +} + +void LoadingTips::ShowTipInfo() { + if (!CurrentTip) { + CurrentTip = &GameTipInfoTable[0]; + } + unsigned int lang_hash = FEngHashString("%s_DESC", CurrentTip->Name); + FEngSetLanguageHash(GetPackageName(), 0xC5FBC710, lang_hash); + lang_hash = FEngHashString("%s_HEADER", CurrentTip->Name); + FEngSetLanguageHash(GetPackageName(), 0x0D555245, lang_hash); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xC9D77CB6), TipTextureHash); + FEngSetScript(GetPackageName(), 0x3248E720, 0x5079C8F8, true); + DisplayTime = RealTimer; +} + +void LoadingTips::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (msg == 0x406415E3) { + goto shared; + } + if (msg <= 0x406415E3) { + if (msg == 0x0C407210) { + goto shared; + } + return; + } + if (msg != 0xC98356BA) { + return; + } + if (!mDoneLoading) { + return; + } + if ((RealTimer - DisplayTime).GetSeconds() <= 5.0f) { + return; + } + if (!(CurrentTip->Flags & 0x400)) { + return; + } + AllowInput(); + return; + +shared: + if (CurrentTip && (CurrentTip->Flags & 0x400)) { + mDoneShowingLoadingTips = true; + FEManager::Get()->AllowControllerError(false); + } +} + +void LoadingTips::AllowInput() { + if (!mPressAcceptHasBeenShown) { + mPressAcceptHasBeenShown = true; + cFEng::Get()->QueuePackageMessage(0x9938A38F, nullptr, nullptr); + FEManager::Get()->AllowControllerError(true); + } +} + +eGameTips LoadingTips::WhatTipScreenShouldIUseToday(LoadingScreen::LoadingScreenTypes loading_direction) { + if (TipTestLastCarWithTwoStrikes(loading_direction)) { + return static_cast(0); + } + if (TipTestFirstTimeIntoSafeHouse(loading_direction)) { + return static_cast(1); + } + if (TipTestFirstTimeOutOfSafeHouse(loading_direction)) { + return static_cast(2); + } + return GetARandomTipScreen(loading_direction); +} + +eGameTips LoadingTips::GetARandomTipScreen(LoadingScreen::LoadingScreenTypes loading_direction) { + unsigned int bin = 0; + unsigned int type = 1; + unsigned int flags = 0; + CareerSettings *career = FEDatabase->GetCareerSettings(); + if (!career->HasCareerStarted()) { + if (loading_direction == LoadingScreen::LS_LOADING_GAME_FROM_FE) { + bin = 0x1E000; + } + } else { + bin = 1 << (career->GetCurrentBin() & 0x3F); + } + if (GRaceDatabase::Exists() && GRaceDatabase::Get().GetStartupRace() == nullptr) { + type |= 0x10; + } + if (loading_direction == LoadingScreen::LS_LOADING_GAME_FROM_FE) { + flags |= 0x20F; + } else { + flags |= 0x100; + } + int valid_tips[28]; + int num_tips = 0; + for (int i = 0; i < 0x1C; i++) { + GameTipInfo *tip = &GameTipInfoTable[i]; + if ((tip->Bin & bin) && (tip->Category & type) && (tip->Flags & flags)) { + valid_tips[num_tips] = i; + num_tips++; + } + } + int result = 0x1C; + if (num_tips != 0) { + result = valid_tips[bRandom(num_tips)]; + } + return static_cast(result); +} + +bool LoadingTips::TipTestLastCarWithTwoStrikes(LoadingScreen::LoadingScreenTypes loading_direction) { + bool lolley_says_this_means_free_roam = false; + if (GRaceDatabase::Exists() && GRaceDatabase::Get().GetStartupRace() == nullptr) { + lolley_says_this_means_free_roam = true; + } + if (!FEDatabase->IsCareerMode() || !lolley_says_this_means_free_roam || + loading_direction != LoadingScreen::LS_LOADING_GAME_FROM_FE) { + return false; + } + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + if (!stable) { + return false; + } + if (stable->GetNumAvailableCareerCars() != 1) { + return false; + } + UserProfile *prof = FEDatabase->GetUserProfile(0); + if (!prof) { + return false; + } + CareerSettings *fe_career = prof->GetCareer(); + if (!fe_career) { + return false; + } + FECarRecord *fe_car = stable->GetCarRecordByHandle(fe_career->GetCurrentCar()); + if (!fe_car) { + return false; + } + if (!fe_car->IsValid()) { + return false; + } + FECareerRecord *record = stable->GetCareerRecordByHandle(fe_car->CareerHandle); + if (!record) { + return false; + } + if (record->GetTimesBusted() == record->GetMaxBusted() - 1) { + return true; + } + return false; +} + +bool LoadingTips::TipTestFirstTimeOutOfSafeHouse(LoadingScreen::LoadingScreenTypes loading_direction) { + bool lolley_says_this_means_free_roam = false; + if (GRaceDatabase::Exists() && GRaceDatabase::Get().GetStartupRace() == nullptr) { + lolley_says_this_means_free_roam = true; + } + if (!FEDatabase->IsCareerMode() || !lolley_says_this_means_free_roam || + loading_direction != LoadingScreen::LS_LOADING_GAME_FROM_FE) { + return false; + } + CareerSettings *career = FEDatabase->GetCareerSettings(); + if (!career) { + return false; + } + if (!career->HasDoneCareerIntro()) { + return false; + } + if (career->HasDoneMapLoadigTip()) { + return false; + } + career->SetHasDoneMapLoadigTip(); + return true; +} + +bool LoadingTips::TipTestFirstTimeIntoSafeHouse(LoadingScreen::LoadingScreenTypes loading_direction) { + if (FEDatabase->IsCareerMode()) { + if (loading_direction == LoadingScreen::LS_LOADING_FE) { + if (!FEDatabase->GetCareerSettings()->HasDoneCareerIntro() && FEDatabase->IsPostRivalMode()) { + return true; + } + } + } + return false; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.hpp index e3785c165..83dcc5e93 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingTips.hpp @@ -5,6 +5,48 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FELoadingScreen.hpp" +enum eGameTips {}; + +struct GameTipInfo { + char *Name; // offset 0x0 + unsigned int Bin; // offset 0x4 + unsigned int Category; // offset 0x8 + unsigned int Flags; // offset 0xC +}; + +struct LoadingTips : public MenuScreen { + LoadingTips(ScreenConstructorData *sd); + ~LoadingTips() override; + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + void ShowTipInfo(); + GameTipInfo *GetGameTip(eGameTips tip); + void StartLoadingTipImage(); + static void InitLoadingTipsScreen(); + void FinishLoadingTexCallback(unsigned int p); + static void CloseLoadingTipsScreen(); + void AllowInput(); + static eGameTips WhatTipScreenShouldIUseToday(LoadingScreen::LoadingScreenTypes loading_direction); + static eGameTips GetARandomTipScreen(LoadingScreen::LoadingScreenTypes loading_direction); + static bool TipTestLastCarWithTwoStrikes(LoadingScreen::LoadingScreenTypes loading_direction); + static bool TipTestFirstTimeOutOfSafeHouse(LoadingScreen::LoadingScreenTypes loading_direction); + static bool TipTestFirstTimeIntoSafeHouse(LoadingScreen::LoadingScreenTypes loading_direction); + + void *operator new(size_t, void *ptr) { return ptr; } + + static bool mDoneLoading; + static bool mDoneShowingLoadingTips; + static void *mLoadingTipsScreenPtr; + + unsigned int TipTextureHash; // offset 0x2C + Timer DisplayTime; // offset 0x30 + GameTipInfo *CurrentTip; // offset 0x34 + bool mPressAcceptHasBeenShown; // offset 0x38 +}; + +extern GameTipInfo GameTipInfoTable[]; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FESplashScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FESplashScreen.cpp index e69de29bb..5c2b39420 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FESplashScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Loading/FESplashScreen.cpp @@ -0,0 +1,2 @@ +// Forward declarations for SplashScreen methods that need ActionQueue +// Actual implementation in FEPackageData.cpp which is after this in the jumbo build \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.cpp index e69de29bb..48a02873e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.cpp @@ -0,0 +1,523 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" + +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, + bool start_at_beginning); +FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash); +FEString* FEngFindString(const char* pkg_name, int name_hash); +void FEngSetInvisible(FEObject* obj); +void FEngSetVisible(FEObject* obj); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int language); +int FEPrintf(const char* pkg_name, int object_hash, const char* fmt, ...); +int FEngSNPrintf(char* buffer, int buf_size, const char* fmt, ...); +void FEngSetCurrentButton(const char* pkg_name, unsigned int hash); +void FEngSetButtonState(const char* pkg_name, unsigned int button_hash, bool enabled); +unsigned long FEHashUpper(const char* str); +bool FEngIsScriptSet(const char* pkg_name, unsigned long obj_hash, unsigned long script_hash); +int FEngMapJoyParamToJoyport(int param); + +extern bool IsMemcardEnabled; +extern GameFlowManager TheGameFlowManager; + +void MemcardExit(unsigned int msg); +unsigned int MemcardGetCurrentUIOperation(); + +extern "C" void ChangeToNextBootFlowScreen__15BootFlowManageri(void* self, int param); + +static unsigned int gButtonIDs[3] = {0xb8a7c6cc, 0xb8a7c6cd, 0xb8a7c6ce}; +static unsigned int gButtonTextIDs[3] = {0xf9363f30, 0xfb8b67d1, 0xfde09072}; + +static const unsigned int sOpName[] = {0x841c21af, 0xe85326e2}; + +// ===== FEMemWidget ===== + +struct FEMemWidget : public ScrollerDatum { + MemCardFileFlag m_Flag; + int m_Size; + UIMemcardList* m_pParent; + + FEMemWidget() {} + ~FEMemWidget() override; +}; + +FEMemWidget::~FEMemWidget() {} + +// ===== UIMemcardList forward ===== + +struct UIMemcardList : public MenuScreen { + enum ListOp { + MCLO_Load = 0, + MCLO_Delete = 1, + }; + + Scrollerina m_SaveGameList; + int m_Initialized; + int m_ListOp; + unsigned int m_LastMsg; + FEMemWidget* m_pCreateNew; + + UIMemcardList(ScreenConstructorData* sd); + ~UIMemcardList() override; + + int GetSize() { return m_SaveGameList.GetNumData(); } + bool IsReady() { return m_Initialized != 0; } + ListOp GetListOp() { return static_cast< ListOp >(m_ListOp); } + + const char* GetFileName(int find); + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; + FEMemWidget* AddItem(const char* pName, const char* pDate, int size, int flag); +}; + +// ===== UIMemcardBoot ===== + +struct UIMemcardBoot : public UIMemcardBase { + UIMemcardBoot(ScreenConstructorData* sd) : UIMemcardBase(sd) {} + ~UIMemcardBoot() override; + + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, + eMenuSoundTriggers maybe) override; +}; + +UIMemcardBoot::~UIMemcardBoot() {} + +eMenuSoundTriggers UIMemcardBoot::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + return maybe; +} + +void UIMemcardBoot::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + UIMemcardBase::NotificationMessage(msg, obj, param1, param2); + switch (msg) { + case 0x35f8620b: + HideAllButtons(); + MemoryCard::GetInstance()->ShowMessages(true); + if (!IsMemcardEnabled) { + cFEng::Get()->QueueGameMessage(0x461a18ee, GetPackageName(), 0xff); + return; + } + SetStringCheckingCard(); + MemoryCard::GetInstance()->BootupCheck(nullptr); + SetScreenVisible(true, 0); + m_bVisible = true; + break; + case 0x461a18ee: + MemoryCard::GetInstance()->StartBootSequence(); + // fall through + case 0x8867412d: + ChangeToNextBootFlowScreen__15BootFlowManageri(BootFlowManager::Get(), 0xff); + MemoryCard::GetInstance()->m_bInitialized = false; + break; + } +} + +MenuScreen* CreateMemCardBootScreen(ScreenConstructorData* sd) { + UIMemcardBoot* boot = new UIMemcardBoot(sd); + FEString* blurb = FEngFindString(boot->GetPackageName(), 0x1e2640fa); + blurb->Flags &= ~0x200; + boot->Setup(); + return boot; +} + +// ===== UIMemcardMain ===== + +struct UIMemcardMain : public UIMemcardBase { + ~UIMemcardMain() override; + + void SetPopupWindow(UIMemcardList* pChild) { m_pChild = pChild; } + + UIMemcardMain(ScreenConstructorData* sd); + void DoSelect(const char* pName) override; + void ListDone(); + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; +}; + +UIMemcardMain::~UIMemcardMain() {} + +UIMemcardMain::UIMemcardMain(ScreenConstructorData* sd) : UIMemcardBase(sd) { + FEString* blurb = FEngFindString(GetPackageName(), 0x1e2640fa); + blurb->Flags &= ~0x200; +} + +void UIMemcardMain::DoSelect(const char* pName) { + bStrCpy(m_FileName, pName); + int listOp = m_pChild->m_ListOp; + switch (listOp) { + case 0: + MemoryCard::GetInstance()->RequestTask(5, m_FileName); + SetStringCheckingCard(); + break; + case 1: + MemoryCard::GetInstance()->RequestTask(6, m_FileName); + SetStringCheckingCard(); + break; + } + PopChild(); +} + +void UIMemcardMain::ListDone() { + bool bCreateChild = false; + MemoryCard::GetInstance()->ShowMessages(true); + + int nSize = m_Items.CountElements(); + + unsigned int uiOp = MemcardGetCurrentUIOperation(); + + switch (uiOp) { + case 0x30: + if (nSize == 0) { + SetupPromptNoProfileFound(); + } else { + bCreateChild = true; + } + break; + case 0x10: + case 0x70: + if (nSize != 0) { + bCreateChild = true; + } else { + SetupPromptNoProfileFound(); + } + break; + case 0x20: + gMemcardSetup.mInBootFlow = true; + if (nSize > 1) { + bCreateChild = true; + } else if (nSize == 1) { + const char* pName = m_Items.GetHead()->m_Name; + pName += MemoryCard::GetInstance()->GetPrefixLength(); + bStrCpy(m_FileName, pName); + MemoryCard::GetInstance()->Load(m_FileName); + } else { + MemoryCard::GetInstance()->BootupCheck(nullptr); + } + break; + case 0xf0: + if (nSize > 1) { + bCreateChild = true; + } else if (nSize != 1) { + DoSaveFlow(2); + } else { + const char* pName = m_Items.GetHead()->m_Name; + pName += MemoryCard::GetInstance()->GetPrefixLength(); + bStrCpy(m_FileName, pName); + MemoryCard::GetInstance()->Load(m_FileName); + } + break; + case 0x40: + if (FEDatabase->bProfileLoaded) { + const char* pName = m_FileName; + const char* profileName = FEDatabase->CurrentUserProfiles[0]->GetProfileName(); + bStrCpy(const_cast(pName), profileName); + DoSaveFlow(4); + } else { + DoSaveFlow(2); + } + break; + case 0x60: + if (FEDatabase->bProfileLoaded && (gMemcardSetup.mOp & 0x80000) == 0) { + DoSaveFlow(1); + } else { + DoSaveFlow(2); + } + break; + } + + if (bCreateChild) { + ActivateChild(); + } +} + +void UIMemcardMain::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + UIMemcardBase::NotificationMessage(msg, obj, param1, param2); + switch (msg) { + case 0x5a051729: { + cFEng* feng = cFEng::Get(); + unsigned long hideHash = FEHashUpper("HIDE LOADER"); + feng->QueuePackageMessage(hideHash, GetPackageName(), nullptr); + ListDone(); + break; + } + case 0xa4bb7ae1: + cFEng::Get()->QueueGameMessage(0x461a18ee, nullptr, 0xff); + goto hide_loader; + case 0xfe202e3b: + DoSaveFlow(4); + break; + case 0x461a18ee: + if (MemoryCard::GetInstance()->InBootSequence()) { + PopChild(); + } + FEDatabase->DeallocBackupDB(); + MemcardExit(0x461a18ee); + goto hide_loader; + case 0xa643dee3: + if (MemoryCard::GetInstance()->IsAutoLoading()) { + return; + } + cFEng::Get()->QueueGameMessage(0x461a18ee, GetPackageName(), 0xff); + break; + case 0x15457de1: + PopChild(); + break; + case 0xc6c6b68f: + DoSaveFlow(8); + break; + case 0x8867412d: + case 0xdc12af2e: + PopChild(); + if ((gMemcardSetup.mOp & 0x800) != 0 && + FEDatabase->GetUserProfile(0)->IsProfileNamed()) { + cFEng::Get()->QueueGameMessage(0x461a18ee, GetPackageName(), 0xff); + goto hide_loader; + } + if (FEDatabase->IsCareerManagerMode() && + FEDatabase->bProfileLoaded && + FEDatabase->GetGameplaySettings()->AutoSaveOn != 0 && + (gMemcardSetup.mOp & 0xf0) != 0x10 && + msg != 0xdc12af2e) { + MemoryCard::GetInstance()->SetAutoSaveEnabled(true); + } else { + if ((gMemcardSetup.mOp & 0xf0) == 0x60 && msg == 0xdc12af2e) { + FEDatabase->GetGameplaySettings()->AutoSaveOn = 0; + ShowOK(0xb04da4ad, 0x7000000); + } else { + MemcardExit(msg); + } + } + if (MemoryCard::GetInstance()->IsCheckingCardForAutoSave() || + MemoryCard::GetInstance()->IsAutoSaving()) { + MemoryCard::GetInstance()->EndAutoSave(); + } + FEDatabase->DeallocBackupDB(); + goto hide_loader; + case 0xb57fdb17: + SetupPromptAutoSaveEnableFailedNoCard(); + break; + case 0x8d0cc9f9: + PopChild(); + SetStringCheckingCard(); + MemoryCard::GetInstance()->BootupCheck(nullptr); + break; + case 0xc98356ba: + if (!m_ExpectingInput) { + return; + } + { + const char* packageName = GetPackageName(); + unsigned long handlerHash = FEHashUpper("LOADER"); + unsigned long appearHash = FEHashUpper("APPEAR"); + if (!FEngIsScriptSet(packageName, handlerHash, appearHash)) { + return; + } + goto hide_loader; + } + break; + } + return; + +hide_loader: { + cFEng* feng = cFEng::Get(); + unsigned long hideHash = FEHashUpper("HIDE LOADER"); + feng->QueuePackageMessage(hideHash, GetPackageName(), nullptr); + return; + } +} + +MenuScreen* CreateMemcardMainMenu(ScreenConstructorData* sd) { + UIMemcardMain* screen = new UIMemcardMain(sd); + screen->Setup(); + return screen; +} + +// ===== UIMemcardList ===== + +UIMemcardList::UIMemcardList(ScreenConstructorData* sd) + : MenuScreen(sd) // + , m_SaveGameList(GetPackageName(), "", "Scrollbar", true, true, false, false) // +{ + m_Initialized = 0; + const char* profileName = FEDatabase->CurrentUserProfiles[0]->GetProfileName(); + FEPrintf(GetPackageName(), 0xeb406fec, profileName); + + for (int i = 1; i < 9; i++) { + char buffer[32]; + ScrollerSlot* slot = new (__FILE__, __LINE__) ScrollerSlot(); + slot->pBacking = nullptr; + slot->vSize.y = 0.0f; + slot->vSize.x = 0.0f; + slot->vTopLeft.y = 0.0f; + slot->vTopLeft.x = 0.0f; + slot->bEnabled = true; + + FEngSNPrintf(buffer, 0x20, "option_name_%d", i); + slot->AddData(FEngFindString(GetPackageName(), FEHashUpper(buffer))); + + FEngSNPrintf(buffer, 0x20, "option_data_%d", i); + slot->AddData(FEngFindString(GetPackageName(), FEHashUpper(buffer))); + + FEngSNPrintf(buffer, 0x20, "option_mouse_%d", i); + slot->SetBacking(FEngFindObject(GetPackageName(), FEHashUpper(buffer))); + slot->Hide(); + m_SaveGameList.AddSlot(slot); + } + + m_ListOp = static_cast< int >((gMemcardSetup.mOp & 0xf0) == 0x30); + FEngSetLanguageHash(GetPackageName(), 0x48d4fcae, sOpName[m_ListOp]); + FEngSetLanguageHash(GetPackageName(), 0x426c7b4d, sOpName[m_ListOp]); +} + +UIMemcardList::~UIMemcardList() {} + +void UIMemcardList::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + switch (msg) { + case 0x35f8620b: { + Scrollerina& saveGameList = m_SaveGameList; + saveGameList.SetSelected(saveGameList.Slots.GetHead()); + if (saveGameList.SelectedSlot != nullptr) { + saveGameList.SelectedSlot->SetScript(0x249db7b7); + } + MemoryCard::GetInstance()->GetScreen()->m_ExpectingInput = true; + m_Initialized++; + if (MemoryCard::GetInstance()->InBootSequence()) { + FEngSetLanguageHash(GetPackageName(), 0xb8a7c6cd, 0x1a294dad); + } + break; + } + case 0xc98356ba: + if (m_Initialized == 0) { + m_Initialized = 1; + UIMemcardBase* parent = MemoryCard::GetInstance()->GetScreen(); + UIMemcardBase::Item* pItem = parent->m_Items.GetHead(); + while (pItem != parent->m_Items.EndOfList()) { + int prefixLen = MemoryCard::GetInstance()->GetPrefixLength(); + const char* name = pItem->m_Name + prefixLen; + if (parent->IsProfile(name)) { + AddItem(name, pItem->m_Data, pItem->m_Size, pItem->m_Flag); + } + pItem = pItem->GetNext(); + } + FEngSetScript("MC_List.fng", 0x47ff4e7c, 0x13c37b, true); + } + break; + case 0x911ab364: + if (MemoryCard::GetInstance()->InBootSequence()) { + cFEng::Get()->QueueGameMessage(0x8d0cc9f9, "MC_Main_GC.fng", 0xff); + } else { + cFEng::Get()->QueueGameMessage(0x8867412d, + MemoryCard::GetInstance()->GetScreen()->GetPackageName(), 0xff); + } + gMemcardSetup.mLastController = param1; + break; + case 0x72619778: + gMemcardSetup.mLastController = param1; + m_SaveGameList.ScrollPrev(); + break; + case 0x911c0a4b: + gMemcardSetup.mLastController = param1; + m_SaveGameList.ScrollNext(); + break; + case 0x406415e3: { + gMemcardSetup.mLastController = param1; + cFrontendDatabase* database = FEDatabase; + bool isMultitap = false; + if (database->IsSplitScreenMode()) { + isMultitap = database->iNumPlayers == 2; + } + if (!isMultitap) { + MemoryCard* memoryCard = MemoryCard::GetInstance(); + int playerNum = memoryCard->GetPlayerNum(); + signed char port = static_cast< signed char >(FEngMapJoyParamToJoyport(static_cast< int >(param1))); + database->SetPlayersJoystickPort(playerNum, port); + } + MemoryCard::GetInstance()->SetMonitor(false); + break; + } + case 0xeb29392a: + if (m_LastMsg == 0x406415e3) { + UIMemcardBase* parent = MemoryCard::GetInstance()->GetScreen(); + parent->DoSelect(m_SaveGameList.SelectedDatum->Strings.GetNode(0)->String); + } + break; + } + m_LastMsg = msg; +} + +FEMemWidget* UIMemcardList::AddItem(const char* pName, const char* pDate, int size, int flag) { + FEMemWidget* widget = new FEMemWidget(); + widget->m_Flag = static_cast< MemCardFileFlag >(flag); + widget->m_Size = size; + widget->AddData(pName, 0); + widget->AddData(pDate, 0); + m_SaveGameList.AddData(widget); + m_SaveGameList.Enable(widget); + m_SaveGameList.Update(true); + return widget; +} + +MenuScreen* CreateMemcardListFiles(ScreenConstructorData* sd) { + UIMemcardList* screen = new UIMemcardList(sd); + MemoryCard::GetInstance()->GetScreen()->m_pChild = screen; + return screen; +} + +// ===== MemcardEnter / MemcardExit ===== + +void MemcardEnter(const char* from, const char* to, unsigned int op, + void (*termFunc)(void*), void* termParam, + unsigned int successMsg, unsigned int failedMsg) { + gMemcardSetup.mOp = op; + gMemcardSetup.mFromScreen = from; + gMemcardSetup.mToScreen = to; + gMemcardSetup.mTermFunc = termFunc; + gMemcardSetup.mTermFuncParam = termParam; + gMemcardSetup.mSuccessMsg = successMsg; + gMemcardSetup.mFailedMsg = failedMsg; + gMemcardSetup.mMemScreen = nullptr; + MemoryCard::GetInstance()->ShowMessages(true); + MemoryCard::GetInstance()->SetPlayerNum((op >> 17) & 1); + if (TheGameFlowManager.GetState() == GAMEFLOW_STATE_IN_FRONTEND) { + gMemcardSetup.mMemScreen = "MC_Main_GC.fng"; + } else { + gMemcardSetup.mMemScreen = "InGame_MC_Main_GC.fng"; + } + int cmd = gMemcardSetup.mOp & 0xf; + switch (cmd) { + case 2: + cFEng::Get()->QueuePackageSwitch(gMemcardSetup.mMemScreen, 0, 0, false); + break; + case 1: + case 3: + cFEng::Get()->QueuePackagePush(gMemcardSetup.mMemScreen, 0, 0, false); + break; + } + MemoryCard::GetInstance()->SetMemcardScreenShowing(true); +} + +void MemcardExit(unsigned int msg) { + gMemcardSetup.mLastMessage = msg; + if (!MemoryCard::GetInstance()->m_bInitialized) { + cFEng* feng = cFEng::Get(); + unsigned long hash = FEHashUpper("EXIT_COMPLETE"); + feng->QueueGameMessage(hash, gMemcardSetup.mMemScreen, 0xff); + } else { + cFEng* feng = cFEng::Get(); + unsigned long hash = FEHashUpper("LEAVE_SCREEN"); + feng->QueuePackageMessage(hash, gMemcardSetup.mMemScreen, nullptr); + } + MemoryCard::GetInstance()->m_bInitialized = false; + MemoryCard::GetInstance()->SetMemcardScreenExiting(true); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.hpp index 24c16ea1e..7401246ef 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcard.hpp @@ -5,6 +5,74 @@ #pragma once #endif +#include "uiMemcardBase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp" +struct FEMemWidget : public ScrollerDatum { + MemCardFileFlag m_Flag; + int m_Size; + UIMemcardList *m_pParent; + + FEMemWidget() {} + ~FEMemWidget() override {} + + void Act(const char *parent_pkg, unsigned int data); + bool IsCorrupt(); + int GetSize() const { return m_Size; } + const char *GetFileName(); + + static const int MAX_SIZE; +}; + +struct UIMemcardList : public MenuScreen { + enum ListOp { + MCLO_Load = 0, + MCLO_Delete = 1, + }; + + Scrollerina m_SaveGameList; + int m_Initialized; + int m_ListOp; + unsigned int m_LastMsg; + FEMemWidget *m_pCreateNew; + + UIMemcardList(ScreenConstructorData *sd); + ~UIMemcardList() override; + + int GetSize() { return m_SaveGameList.GetNumData(); } + bool IsReady() { return m_Initialized != 0; } + ListOp GetListOp() { return static_cast(m_ListOp); } + + const char *GetFileName(int find); + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, + unsigned long param2) override; + FEMemWidget *AddItem(const char *pName, const char *pDate, int size, int flag); +}; + +struct UIMemcardBoot : public UIMemcardBase { + UIMemcardBoot(ScreenConstructorData *sd) : UIMemcardBase(sd) {} + ~UIMemcardBoot() override {} + + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, + unsigned long param2) override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, + eMenuSoundTriggers maybe) override; +}; + +struct UIMemcardMain : public UIMemcardBase { + ~UIMemcardMain() override {} + + void SetPopupWindow(UIMemcardList *pChild) { m_pChild = pChild; } + + UIMemcardMain(ScreenConstructorData *sd); + void DoSelect(const char *pName) override; + void ListDone(); + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, + unsigned long param2) override; +}; + +MenuScreen *CreateMemCardBootScreen(ScreenConstructorData *sd); +MenuScreen *CreateMemcardMainMenu(ScreenConstructorData *sd); +MenuScreen *CreateMemcardListFiles(ScreenConstructorData *sd); #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.cpp index e69de29bb..1e1b5fa79 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.cpp @@ -0,0 +1,1020 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp" + +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Loading/FEBootFlowManager.hpp" +#include "Speed/Indep/Src/Generated/Events/EQuitToFE.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" + +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, + bool start_at_beginning); +FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash); +FEString* FEngFindString(const char* pkg_name, int name_hash); +FEImage* FEngFindImage(const char* pkg_name, int name_hash); +void FEngSetInvisible(FEObject* obj); +void FEngSetVisible(FEObject* obj); +void FEngSetLanguageHash(FEString* text, unsigned int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int language); +void FESetString(FEString* text, const short* string); +int FEPrintf(FEString* text, const char* fmt, ...); +int FEPrintf(const char* pkg_name, int object_hash, const char* fmt, ...); +void FEngSetCurrentButton(const char* pkg_name, unsigned int hash); +void FEngSetButtonState(const char* pkg_name, unsigned int button_hash, bool enabled); +unsigned long FEHashUpper(const char* str); +void FEngSetTextureHash(FEImage* img, unsigned int hash); + +const char* GetLocalizedString(unsigned int hash); +void bStrCpy(unsigned short* to, const char* from); + +extern bool IsMemcardEnabled; +extern GameFlowManager TheGameFlowManager; +extern unsigned int gMemcardSetupPreviousOp; + +void MemcardExit(unsigned int msg); + +extern "C" { +void ChangeToNextBootFlowScreen__15BootFlowManageri(void* self, int param); +void StartNewCareer__14CareerSettingsb(void* self, int bEnterGameplay); +void ResumeCareer__14CareerSettings(void* self); +int SetAudioModeFromMemoryCard__8EAXSound13eSndAudioMode(void* self, int mode); +void UpdateVolumes__8EAXSoundP13AudioSettingsf(void* self, void* settings, float vol); +void InitializeEATrax__Fb(int b); +void* Get__19uiRepSheetRivalFlow(); +void Next__19uiRepSheetRivalFlow(void* self); +} + +struct EAXSound; +extern EAXSound* g_pEAXSound; + +// gButtonIDs and gButtonTextIDs are defined in uiMemcard.cpp (included before this file) + +// ===== UIMemcardKeyboard ===== + +UIMemcardKeyboard::UIMemcardKeyboard(ScreenConstructorData* sd) : MenuScreen(sd) { + m_pDisplayMsg = static_cast< FEString* >(FEngFindObject(GetPackageName(), 0x1e2640fa)); + const char* pkg = GetPackageName(); + unsigned int shadowHash = FEHashUpper("message_blurb_shadow"); + m_pDisplayMsgShadow = static_cast< FEString* >(FEngFindObject(pkg, shadowHash)); + m_pTitleMaster = static_cast< FEString* >(FEngFindObject(GetPackageName(), 0x426c7b4d)); + m_pOK = static_cast< FEString* >(FEngFindObject(GetPackageName(), gButtonIDs[0])); + m_pCancel = static_cast< FEString* >(FEngFindObject(GetPackageName(), gButtonIDs[1])); +} + +void UIMemcardKeyboard::Setup() { + FEngSetScript(GetPackageName(), gButtonIDs[0], 0x5b0d9106, true); + FEngSetScript(GetPackageName(), gButtonIDs[1], 0x5b0d9106, true); + FEngSetVisible(FEngFindObject(GetPackageName(), gButtonIDs[0])); + FEngSetVisible(FEngFindObject(GetPackageName(), gButtonIDs[1])); + FEngSetCurrentButton(GetPackageName(), gButtonIDs[1]); +} + +void UIMemcardKeyboard::ShowKeyboard() { + FEngSetScript(GetPackageName(), 0x47ff4e7c, 0x9e99, true); + const char* title = GetLocalizedString(0x70513bd4); + const char* prompt = GetLocalizedString(0xd48d95f); + FEngBeginTextInput(0, 6, title, prompt, 7); + FEDatabase->LoadSaveGame = static_cast< eLoadSaveGame >(5); +} + +void UIMemcardKeyboard::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + if (msg == 0xC9D30688) { + FEngSetScript(GetPackageName(), 0x47FF4E7C, 0x03D8EABC, true); + } +} + +// ===== UIMemcardBase ===== + +UIMemcardBase::UIMemcardBase(ScreenConstructorData* sd) + : UIMemcardKeyboard(sd) // + , mIndex(1) // + , m_ExpectingInput(false) // + , m_LoadedNetConfig(0) // + , m_nMsgOptions(0) // + , m_bVisible(false) // + , m_bDelayedFailed(false) // + , m_bInButtonAnimation(false) // + , m_pChild(nullptr) // + , m_SimPausedForMemcard(false) { +} + +UIMemcardBase::~UIMemcardBase() { + m_pDisplayMsg = nullptr; + MemoryCard::GetInstance()->m_pFEScreen = nullptr; + if ((gMemcardSetup.mOp & 0x1000) != 0) { + int savedLastMsg = gMemcardSetup.mLastMessage; + if (gMemcardSetup.mTermFunc != nullptr) { + gMemcardSetup.mTermFunc(gMemcardSetup.mTermFuncParam); + } + gMemcardSetup.mOp = 0; + gMemcardSetup.mMemScreen = nullptr; + gMemcardSetup.mToScreen = nullptr; + gMemcardSetup.mFromScreen = nullptr; + gMemcardSetup.mTermFunc = nullptr; + gMemcardSetup.mTermFuncParam = nullptr; + gMemcardSetup.mSuccessMsg = 0; + gMemcardSetup.mFailedMsg = 0; + gMemcardSetup.mInBootFlow = false; + gMemcardSetup.mPreviousCommand = 0; + gMemcardSetup.mPreviousPrompt = 0; + gMemcardSetup.mLastMessage = savedLastMsg; + } +} + +void UIMemcardBase::Abort() { + cFEng::Get()->QueueGameMessage(0x8867412d, GetPackageName(), 0xff); +} + +void UIMemcardBase::DoSelect(const char* pFileName) {} + +bool UIMemcardBase::AddItem(const char* pName, const char* pDate, int size, int flag) { + Item* pItem = new Item(); + bStrNCpy(pItem->m_Name, pName, 0x1f); + pItem->m_Name[31] = '\0'; + bStrCpy(pItem->m_Data, pDate); + pItem->m_Size = size; + pItem->m_Flag = static_cast< MemCardFileFlag >(flag); + m_Items.AddTail(pItem); + return true; +} + +bool UIMemcardBase::IsProfile(const char* pName) { + int len = bStrLen(pName); + return len < 8; +} + +void UIMemcardBase::EmptyFileList() { + m_Items.DeleteAllElements(); +} + +void UIMemcardBase::Setup() { + FEngSetLanguageHash(GetPackageName(), 0x42adb44c, 0x774e4dd9); + FEngSetLanguageHash(m_pDisplayMsg, 0x99054304); + MemoryCard::GetInstance()->FEngLinkObjects(this); + SetIcon(0x6948e2b3); +} + +void UIMemcardBase::SetStringCheckingCard() { + SetScreenVisible(true, 0); + SetMessageBlurbText(static_cast< unsigned int >(0x99054304)); + cFEng* pFeng = cFEng::Get(); + unsigned long hash = FEHashUpper("0_BUTTONS"); + pFeng->QueuePackageMessage(hash, GetPackageName(), nullptr); + HideAllButtons(); + m_ExpectingInput = false; +} + +void UIMemcardBase::HideAllButtons() { + m_bAnyButtonVisible = false; + for (int i = 0; i <= 2; i++) { + ShowButton(i, false, nullptr); + } + FEngSetScript(GetPackageName(), 0x07f9dca9, 0x0016a259, true); +} + +void UIMemcardBase::ShowButton(int idx, bool bShow, short* pText) { + if (bShow) { + m_bAnyButtonVisible = true; + if (pText != nullptr) { + FESetString(static_cast< FEString* >( + FEngFindObject(GetPackageName(), gButtonTextIDs[idx])), pText); + } + FEngSetButtonState(GetPackageName(), gButtonIDs[idx], true); + FEngSetVisible(FEngFindObject(GetPackageName(), gButtonIDs[idx])); + FEngSetVisible(FEngFindObject(GetPackageName(), gButtonTextIDs[idx])); + FEngSetScript(GetPackageName(), 0x57689fdd, 0xde6eff34, true); + } else { + FEngSetButtonState(GetPackageName(), gButtonIDs[idx], false); + FEngSetInvisible(FEngFindObject(GetPackageName(), gButtonIDs[idx])); + FEngSetInvisible(FEngFindObject(GetPackageName(), gButtonTextIDs[idx])); + } +} + +void UIMemcardBase::SetButtonText(short* b1, short* b2, short* b3) { + int active = 0; + if (b3 != nullptr) { + m_nMsgOptions = 3; + ShowButton(0, true, b1); + ShowButton(1, true, b2); + ShowButton(2, true, b3); + } else if (b2 != nullptr) { + m_nMsgOptions = 2; + ShowButton(0, true, b1); + ShowButton(1, true, b2); + ShowButton(2, false, nullptr); + } else if (b1 != nullptr) { + m_nMsgOptions = 1; + ShowButton(0, true, b1); + ShowButton(1, false, nullptr); + ShowButton(2, false, nullptr); + } + FEngSetCurrentButton(GetPackageName(), gButtonIDs[active]); + m_ExpectingInput = true; + gMemcardSetup.mPreviousPrompt = gMemcardSetup.mOp & 0xf000000; + gMemcardSetup.mOp = gMemcardSetup.mOp & 0xf0ffffff; +} + +void UIMemcardBase::SetMessage(short* pMsg) { + if (pMsg == nullptr) { + SetMessageBlurbText(const_cast< char* >("")); + HideAllButtons(); + } else { + SetMessageBlurbText(pMsg); + m_pDisplayMsg->Flags |= 2; + FEngSetScript(GetPackageName(), 0x47ff4e7c, 0xe18da018, true); + } +} + +void UIMemcardBase::SetMessageBlurbText(short* pText) { + FESetString(m_pDisplayMsg, pText); + if (m_pDisplayMsgShadow != nullptr) { + FESetString(m_pDisplayMsgShadow, pText); + } + FindScreenSize(reinterpret_cast< const wchar_t* >(pText)); +} + +void UIMemcardBase::SetMessageBlurbText(char* pText) { + int wText[1024]; + FEPrintf(m_pDisplayMsg, pText); + if (m_pDisplayMsgShadow != nullptr) { + FEPrintf(m_pDisplayMsgShadow, pText); + } + bStrCpy(reinterpret_cast< unsigned short* >(wText), pText); + FindScreenSize(reinterpret_cast< const wchar_t* >(wText)); +} + +void UIMemcardBase::SetMessageBlurbText(unsigned int textHash) { + FEngSetLanguageHash(m_pDisplayMsg, textHash); + if (m_pDisplayMsgShadow != nullptr) { + FEngSetLanguageHash(m_pDisplayMsgShadow, textHash); + } + const char* str = GetLocalizedString(textHash); + unsigned short buf[2048]; + bStrCpy(buf, str); + FindScreenSize(reinterpret_cast< const wchar_t* >(buf)); +} + +void UIMemcardBase::ShowOK(unsigned int textHash, unsigned int flag) { + cFEng* pFeng = cFEng::Get(); + unsigned long msg = FEHashUpper("HIDE LOADER"); + pFeng->QueuePackageMessage(msg, GetPackageName(), nullptr); + SetMessageBlurbText(textHash); + gMemcardSetup.mOp = gMemcardSetup.mOp | static_cast< int >(flag & 0xf000000); + ShowButton(0, true, nullptr); + FEngSetLanguageHash(GetPackageName(), gButtonTextIDs[0], 0x417b2601); + FEngSetCurrentButton(GetPackageName(), gButtonIDs[0]); + ShowButton(1, false, nullptr); + ShowButton(2, false, nullptr); + m_ExpectingInput = true; + SetScreenVisible(true, 1); +} + +void UIMemcardBase::ShowYesNo(unsigned int textHash, unsigned int flag) { + cFEng* pFeng = cFEng::Get(); + unsigned long msg = FEHashUpper("HIDE LOADER"); + pFeng->QueuePackageMessage(msg, GetPackageName(), nullptr); + SetMessageBlurbText(textHash); + gMemcardSetup.mOp = gMemcardSetup.mOp | static_cast< int >(flag & 0xf000000); + ShowButton(0, true, nullptr); + FEngSetLanguageHash(GetPackageName(), gButtonTextIDs[0], 0x417b25e4); + ShowButton(1, true, nullptr); + FEngSetLanguageHash(GetPackageName(), gButtonTextIDs[1], 0x70e01038); + FEngSetCurrentButton(GetPackageName(), gButtonIDs[0]); + ShowButton(2, false, nullptr); + m_ExpectingInput = true; + SetScreenVisible(true, 2); +} + +void UIMemcardBase::SetScreenVisible(bool bVisible, int nButtons) { + if (m_bVisible != bVisible) { + cFEng* pFeng = cFEng::Get(); + m_bVisible = bVisible; + unsigned long msg = bVisible ? 0xc0f2ae7cUL : 0x4f3559b5UL; + pFeng->QueuePackageMessage(msg, GetPackageName(), nullptr); + if (bVisible) { + pFeng = cFEng::Get(); + unsigned long resetMsg = FEHashUpper("INITIALIZE_SCREEN"); + pFeng->QueuePackageMessage(resetMsg, GetPackageName(), nullptr); + } + MemoryCard::GetInstance()->m_bInitialized = m_bVisible; + } + if (bVisible) { + char buf[36]; + bSPrintf(buf, "%d_BUTTONS", nButtons); + cFEng* pFeng = cFEng::Get(); + unsigned long hash = FEHashUpper(buf); + pFeng->QueuePackageMessage(hash, GetPackageName(), nullptr); + } +} + +void UIMemcardBase::SetIcon(unsigned int iconHash) { + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xd4f4069), iconHash); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xfac88427), iconHash); +} + +void UIMemcardBase::TranslateButton(FEObject* obj) { + if (obj->Flags & 1) { + return; + } + unsigned long nameHash = obj->NameHash; + if (nameHash == gButtonIDs[0]) { + MemoryCard::GetInstance()->MessageDone(static_cast< RealmcIface::MessageChoices >(1)); + } else if (nameHash == gButtonIDs[1]) { + MemoryCard::GetInstance()->MessageDone(static_cast< RealmcIface::MessageChoices >(2)); + } else if (nameHash == gButtonIDs[2]) { + MemoryCard::GetInstance()->MessageDone(static_cast< RealmcIface::MessageChoices >(3)); + } + m_ExpectingInput = false; +} + +void UIMemcardBase::SetupPromptNoProfileFound() { + ShowOK(0xba373453, 0x3000000); +} + +void UIMemcardBase::SetupPromptSaveConfirm() { + char text[512]; + unsigned int fmtHash; + if ((gMemcardSetup.mOp & 0x8000) != 0) { + fmtHash = 0x391a0aac; + } else if ((gMemcardSetup.mOp & 0x40000) != 0) { + fmtHash = 0xb0af33a5; + } else if ((gMemcardSetup.mOp & 0x200000) != 0) { + fmtHash = 0xd80818f8; + } else { + fmtHash = 0x39b3ccba; + } + char* fmt = const_cast< char* >(GetLocalizedString(fmtHash)); + ShowYesNo(0x39b3ccba, 0x4000000); + bSPrintf(text, fmt, m_FileName, m_FileName); + SetMessageBlurbText(text); +} + +void UIMemcardBase::SetupAutoSaveConfirmPrompt() { + gMemcardSetup.mOp = gMemcardSetup.mOp | 0xa000000; + const char* mainText = GetLocalizedString(0xa0b434a2); + SetMessageBlurbText(const_cast< char* >(mainText)); + FEngSetButtonState(GetPackageName(), gButtonIDs[0], true); + FEngSetVisible(FEngFindObject(GetPackageName(), gButtonIDs[0])); + FEngSetVisible(FEngFindObject(GetPackageName(), gButtonTextIDs[0])); + const char* yesStr = GetLocalizedString(0x417b25e4); + FEPrintf(GetPackageName(), static_cast< int >(gButtonTextIDs[0]), yesStr); + FEngSetButtonState(GetPackageName(), gButtonIDs[1], true); + FEngSetVisible(FEngFindObject(GetPackageName(), gButtonIDs[1])); + FEngSetVisible(FEngFindObject(GetPackageName(), gButtonTextIDs[1])); + const char* noStr = GetLocalizedString(0x2b07a03d); + FEPrintf(GetPackageName(), static_cast< int >(gButtonTextIDs[1]), noStr); + FEngSetButtonState(GetPackageName(), gButtonIDs[2], false); + FEngSetInvisible(FEngFindObject(GetPackageName(), gButtonIDs[2])); + FEngSetInvisible(FEngFindObject(GetPackageName(), gButtonTextIDs[2])); + FEngSetCurrentButton(GetPackageName(), gButtonIDs[0]); + unsigned long handlerHash = FEHashUpper("HANDLER"); + unsigned long forwardHash = FEHashUpper("FORWARD"); + FEngSetScript(GetPackageName(), handlerHash, forwardHash, true); + SetScreenVisible(true, 2); +} + +void UIMemcardBase::SetupPromptForSave() { + ShowYesNo(0x83f4bb3e, 0x4000000); + unsigned int textHash = 0x83f4bb3e; + if ((gMemcardSetup.mOp & 0x200000) != 0) { + textHash = 0xd80818f8; + } + const char* localStr = GetLocalizedString(textHash); + char buf[512]; + bSPrintf(buf, localStr, m_FileName, m_FileName); + SetMessageBlurbText(buf); +} + +void UIMemcardBase::SetupPromptCorruptProfile() { + ShowOK(0x821e4444, 0xd000000); + const char* localStr = GetLocalizedString(0x821e4444); + char buf[512]; + bSPrintf(buf, localStr, m_FileName); + SetMessageBlurbText(buf); +} + +void UIMemcardBase::SetupPromptAutoSaveEnableFailedNoCard() { + ShowOK(0x9e85bba8, 0xb000000); +} + +void UIMemcardBase::ShowKeyboard() { + SetScreenVisible(false, 0); + HideAllButtons(); + UIMemcardKeyboard::ShowKeyboard(); +} + +void UIMemcardBase::FindScreenSize(const wchar_t* msg) { + FEngFont* font = FindFont(0x545570c6); + int len = bStrLen(reinterpret_cast< const unsigned short* >(msg)); + float height = font->GetHeight(); + float numLines = static_cast< float >(len) * height; + unsigned int hash; + if (numLines < 2200.0f) { + hash = 0x79b0c1c7; + } else if (numLines < 4400.0f) { + hash = 0xa13adcaf; + } else { + cFEng::Get()->QueuePackageMessage(0x792bc959, GetPackageName(), nullptr); + return; + } + cFEng::Get()->QueuePackageMessage(hash, GetPackageName(), nullptr); +} + +void UIMemcardBase::ShowMessage(MemoryCardMessage* msg) { + ShowMessage(reinterpret_cast< const wchar_t* >(msg->mMsg), msg->mnOptions, + reinterpret_cast< const wchar_t* >(msg->mOptions[0]), + reinterpret_cast< const wchar_t* >(msg->mOptions[1]), + reinterpret_cast< const wchar_t* >(msg->mOptions[2])); + MemoryCard::GetInstance()->ReleasePendingMessage(); +} + +void UIMemcardBase::ShowMessage(const wchar_t* msg, unsigned int nOptions, + const wchar_t* option1, const wchar_t* option2, + const wchar_t* option3) { + PopChild(); + HideAllButtons(); + SetMessage(reinterpret_cast< short* >(const_cast< wchar_t* >(msg))); + switch (nOptions) { + case 1: + SetButtonText(reinterpret_cast< short* >(const_cast< wchar_t* >(option1)), + nullptr, nullptr); + break; + case 2: + SetButtonText(reinterpret_cast< short* >(const_cast< wchar_t* >(option1)), + reinterpret_cast< short* >(const_cast< wchar_t* >(option2)), + nullptr); + break; + case 3: + SetButtonText(reinterpret_cast< short* >(const_cast< wchar_t* >(option1)), + reinterpret_cast< short* >(const_cast< wchar_t* >(option2)), + reinterpret_cast< short* >(const_cast< wchar_t* >(option3))); + break; + default: + MemoryCard::GetInstance()->SetWaitingForResponse(false); + break; + } + SetScreenVisible(true, nOptions); + cFEng* pFEng = cFEng::Get(); + const char* hashStr; + if (nOptions == 0) { + hashStr = "SHOW LOADER"; + } else { + hashStr = "HIDE LOADER"; + } + pFEng->QueuePackageMessage(FEHashUpper(hashStr), GetPackageName(), nullptr); +} + +void UIMemcardBase::ActivateChild() { + MemoryCard::GetInstance()->SetMonitor(true); +} + +void UIMemcardBase::PopChild() { + if (m_pChild != nullptr && cFEng::Get()->IsPackagePushed("MC_List.fng")) { + cFEng::Get()->QueuePackagePop(1); + } + m_pChild = nullptr; +} + +void UIMemcardBase::HandleAutoSaveError() { + if (!MemoryCard::GetInstance()->IsCheckingCardForAutoSave() && + !MemoryCard::GetInstance()->IsCheckingCardForOverwrite()) { + if ((gMemcardSetup.mOp & 0xf0) != 0xb0) { + gMemcardSetup.mOp = (gMemcardSetup.mOp & ~0xf) | 1; + } + gMemcardSetup.mPreviousCommand = gMemcardSetup.mOp & 0xf0; + gMemcardSetup.mOp = (gMemcardSetup.mOp & ~0xf0) | 0x50; + } + char* dst = m_FileName; + const char* profileName = FEDatabase->CurrentUserProfiles[0]->GetProfileName(); + bStrCpy(dst, profileName); + if (MemoryCard::GetInstance()->IsCheckingCardForAutoSave() || + MemoryCard::GetInstance()->IsCheckingCardForOverwrite() || + MemoryCard::GetInstance()->WasCardRemovedWithAutoSaveEnabled()) { + MemoryCard::GetInstance()->ReleasePendingMessage(); + SetupAutoSaveConfirmPrompt(); + MemoryCard::GetInstance()->SetCardRemovedWithAutoSaveEnabled(false); + } else { + MemoryCard::GetInstance()->SetRetryAutoSave(true); + ShowMessage(MemoryCard::GetInstance()->GetPendingMessage()); + } + MemoryCard::GetInstance()->EndAutoSave(); +} + +void UIMemcardBase::HandleAutoSaveOverwriteMessage() { + char* dst = m_FileName; + const char* profileName = FEDatabase->CurrentUserProfiles[0]->GetProfileName(); + bStrCpy(dst, profileName); + MemoryCard::GetInstance()->EndAutoSave(); + FEDatabase->bAutoSaveOverwriteConfirmed = true; + gMemcardSetup.mPreviousCommand = gMemcardSetup.mOp & 0xf0; + gMemcardSetup.mOp = (gMemcardSetup.mOp & ~0xf0) | 0x50; + MemoryCard::GetInstance()->ShowMessages(true); + DoSaveFlow(12); +} + +void UIMemcardBase::DoSaveFlow(int flow) { + if (flow != 0) { + m_Flow = flow; + } else { + if (!FEDatabase->GetUserProfile(0)->IsProfileNamed()) { + m_Flow = 2; + } + } + switch (m_Flow) { + case 9: + ShowOK(0xd9783c57, 0x3000000); + break; + case 1: + ShowYesNo(0x7209349f, 0x5000000); + break; + case 2: { + unsigned int msg; + if ((gMemcardSetup.mOp & 0x80000) != 0) { + msg = 0xbadd522c; + } else if ((gMemcardSetup.mOp & 0x10000) != 0) { + msg = 0x93c25b3d; + } else if ((gMemcardSetup.mOp & 0x8000) != 0) { + msg = 0xf8448956; + } else { + msg = 0xbe97590f; + } + ShowYesNo(msg, 0x1000000); + break; + } + case 3: + ShowKeyboard(); + break; + case 6: + SetupPromptSaveConfirm(); + break; + case 4: + SetupPromptForSave(); + break; + case 12: + MemoryCard::GetInstance()->SetAutoSaveEnabled(false); + break; + case 8: + FEDatabase->GetUserProfile(0)->SetProfileName(m_FileName, true); + MemoryCard::GetInstance()->Save(m_FileName); + SetStringCheckingCard(); + break; + case 10: { + cFEng::Get()->QueuePackageMessage(0x1c8ace, GetPackageName(), nullptr); + unsigned int warning = GetAutoSaveWarning(); + ShowOK(warning, 0x9000000); + break; + } + case 11: { + unsigned int warning = GetAutoSaveWarning2(); + ShowOK(warning, 0x9000000); + break; + } + } +} + +eMenuSoundTriggers UIMemcardBase::NotifySoundMessage(unsigned long msg, + eMenuSoundTriggers maybe) { + if (m_bAnyButtonVisible) { + return maybe; + } + if (msg == 0x48122792 || msg == 0x4ac5e165) { + return UISND_NONE; + } + return maybe; +} + +void UIMemcardBase::InitCompleteDoList() { + m_Items.DeleteAllElements(); + SetStringCheckingCard(); + MemoryCard::GetInstance()->RequestTask(7, nullptr); + cFEng* pFeng = cFEng::Get(); + unsigned long hash = FEHashUpper("SHOW LOADER"); + pFeng->QueuePackageMessage(hash, GetPackageName(), nullptr); +} + +void UIMemcardBase::InitComplete() { + if (!IsMemcardEnabled) { + cFEng::Get()->QueueGameMessage(0x461a18ee, GetPackageName(), 0xff); + return; + } + SetMessageBlurbText(const_cast< char* >(" ")); + const char* pkg = GetPackageName(); + unsigned int hash = FEHashUpper("Button"); + FEngSetInvisible(FEngFindObject(pkg, hash)); + m_pDisplayMsg->Flags |= 0x80; + if ((gMemcardSetup.mOp & 0x4000) != 0) { + cFEng::Get()->QueueGameMessage(0x5afe12f4, gMemcardSetup.mFromScreen, 0xff); + } + if ((gMemcardSetup.mOp & 0x400000) != 0) goto doQueuePackageMessage; + if ((gMemcardSetup.mOp & 0x10000) != 0) { + if ((gMemcardSetup.mOp & 0xf0) == 0xb0) { + doQueuePackageMessage: + cFEng* pFeng = cFEng::Get(); + unsigned int memcardOnHash = FEHashUpper("MEMCARD_ON"); + pFeng->QueuePackageMessage(memcardOnHash, GetPackageName(), nullptr); + } + } + switch (MemcardGetCurrentUIOperation()) { + case 0x10: + case 0x70: + if (FEDatabase->bProfileLoaded && (gMemcardSetup.mOp & 0x20000) == 0) { + ShowYesNo(0x87c7577e, 0x6000000); + return; + } + InitCompleteDoList(); + break; + case 0x20: + MemcardExit(0x8867412d); + break; + case 0x30: + SetStringCheckingCard(); + InitCompleteDoList(); + break; + case 0x40: + case 0x60: + cFEng::Get()->QueueGameMessage(0x5a051729, nullptr, 0xff); + break; + case 0x50: { + char* dst = m_FileName; + const char* profileName = FEDatabase->CurrentUserProfiles[0]->GetProfileName(); + bStrCpy(dst, profileName); + DoSaveFlow(6); + break; + } + case 0x80: + MemoryCard::GetInstance()->CheckCard(0); + break; + case 0xa0: + if ((gMemcardSetup.mOp & 0x8000) != 0) { + MemoryCard::GetInstance()->SetAutoSaveEnabled(true); + return; + } + SetStringCheckingCard(); + ShowYesNo(0x750eb45c, 0xc000000); + break; + case 0x90: + m_SimPausedForMemcard = true; + HandleAutoSaveError(); + break; + case 0xd0: + m_SimPausedForMemcard = true; + HandleAutoSaveOverwriteMessage(); + break; + case 0xb0: + if (FEDatabase->bProfileLoaded) { + if (MemoryCard::GetInstance()->ShouldDoAutoSave(false)) { + char* dst = m_FileName; + SetScreenVisible(true, 0); + SetStringCheckingCard(); + const char* profileName = FEDatabase->CurrentUserProfiles[0]->GetProfileName(); + bStrCpy(dst, profileName); + MemoryCard::GetInstance()->StartAutoSave(true); + return; + } + gMemcardSetup.mPreviousCommand = gMemcardSetup.mOp & 0xf0; + gMemcardSetup.mOp = (gMemcardSetup.mOp & ~0xf0) | 0x50; + } else { + gMemcardSetup.mPreviousCommand = gMemcardSetup.mOp & 0xf0; + gMemcardSetup.mOp = (gMemcardSetup.mOp & ~0xf0) | 0x60; + } + InitComplete(); + break; + case 0xf0: + if (MemoryCard::IsCardAvailable() && IsMemcardEnabled) { + InitCompleteDoList(); + } else { + MemcardExit(0x8867412d); + } + break; + } +} + +void UIMemcardBase::ExitComplete() { + int lastMsg = gMemcardSetup.mLastMessage; + if ((gMemcardSetup.mOp & 0x100) != 0) { + cFEng::Get()->QueuePackageMessage(lastMsg, gMemcardSetup.mToScreen, nullptr); + } + if ((gMemcardSetup.mOp & 0x400) != 0) { + unsigned int gameMsg; + if (lastMsg == 0x461a18ee) { + gameMsg = gMemcardSetup.mSuccessMsg; + } else { + gameMsg = gMemcardSetup.mFailedMsg; + } + cFEng::Get()->QueueGameMessage(gameMsg, gMemcardSetup.mToScreen, 0xff); + } + if ((FEDatabase->GetGameMode() & 0x100) && + TheGameFlowManager.GetState() == GAMEFLOW_STATE_IN_FRONTEND) { + FEDatabase->ResetGameMode(); + if (FEDatabase->bProfileLoaded && + !(((gMemcardSetup.mOp & 0xf0) == 0x10 && + static_cast< unsigned int >(lastMsg) == 0x8867412d) || + gMemcardSetup.mPreviousPrompt == 0x1000000 || + gMemcardSetup.mPreviousPrompt == 0x3000000 || + gMemcardSetup.mPreviousPrompt == 0x5000000)) { + CareerSettings* career = FEDatabase->CurrentUserProfiles[0]->GetCareer(); + if (career->SpecialFlags & 1) { + ResumeCareer__14CareerSettings(career); + } else { + StartNewCareer__14CareerSettingsb(career, 1); + } + } else { + gMemcardSetup.mOp = (gMemcardSetup.mOp & ~0xf) | 1; + FEDatabase->RestoreFromBackupDB(); + FEDatabase->SetGameMode(static_cast< eFEGameModes >(0x100)); + } + } + + if ((gMemcardSetup.mOp & 0x400000) != 0) { + void* rivalFlow = Get__19uiRepSheetRivalFlow(); + Next__19uiRepSheetRivalFlow(rivalFlow); + } else if ((gMemcardSetup.mOp & 0x10000) != 0) { + if (TheGameFlowManager.GetState() == GAMEFLOW_STATE_IN_FRONTEND) { + cFEng::Get()->QueuePackagePop(1); + if (FEDatabase->bProfileLoaded) { + FEDatabase->ResetGameMode(); + FEDatabase->SetGameMode(static_cast< eFEGameModes >(2)); + cFEng::Get()->QueuePackageSwitch(gMemcardSetup.mToScreen, 0, 0, false); + } + } else { + new EQuitToFE(static_cast< eGarageType >(1), static_cast< const char* >(0)); + } + } else { + unsigned int cmd = gMemcardSetup.mOp & 0xf; + switch (cmd) { + case 1: { + bool popExtra; + cFEng* feng; + if (m_SimPausedForMemcard) { + m_SimPausedForMemcard = false; + feng = cFEng::Get(); + popExtra = feng->IsPackagePushed("SMS_Mailboxes.fng"); + } else { + feng = cFEng::Get(); + popExtra = true; + } + feng->QueuePackagePop(popExtra ? 1 : 0); + break; + } + case 2: + cFEng::Get()->QueuePackageSwitch(gMemcardSetup.mToScreen, + MemoryCard::GetInstance()->GetPlayerNum(), 0, false); + break; + case 3: + cFEng::Get()->QueuePackagePop(1); + cFEng::Get()->QueuePackageSwitch(gMemcardSetup.mToScreen, + MemoryCard::GetInstance()->GetPlayerNum(), 0, false); + break; + } + } + if (m_SimPausedForMemcard) { + m_SimPausedForMemcard = false; + } + + int audioMode = FEDatabase->CurrentUserProfiles[0]->GetOptions()->TheAudioSettings.AudioMode; + int newMode = SetAudioModeFromMemoryCard__8EAXSound13eSndAudioMode(g_pEAXSound, audioMode); + FEDatabase->CurrentUserProfiles[0]->GetOptions()->TheAudioSettings.AudioMode = newMode; + UpdateVolumes__8EAXSoundP13AudioSettingsf( + g_pEAXSound, &FEDatabase->CurrentUserProfiles[0]->GetOptions()->TheAudioSettings, 1.0f); + InitializeEATrax__Fb(1); + + FEPackage* pkg = cFEng::Get()->FindPackage(gMemcardSetup.mMemScreen); + if (pkg != nullptr && pkg->pParentPackage != nullptr) { + pkg->pParentPackage->bInputEnabled = true; + } + + int savedMsg = gMemcardSetup.mLastMessage; + if (gMemcardSetup.mTermFunc != nullptr) { + gMemcardSetup.mTermFunc(gMemcardSetup.mTermFuncParam); + } + gMemcardSetup.mOp = 0; + gMemcardSetup.mMemScreen = nullptr; + gMemcardSetup.mToScreen = nullptr; + gMemcardSetup.mFromScreen = nullptr; + gMemcardSetup.mTermFunc = nullptr; + gMemcardSetup.mTermFuncParam = nullptr; + gMemcardSetup.mSuccessMsg = 0; + gMemcardSetup.mFailedMsg = 0; + gMemcardSetup.mInBootFlow = false; + gMemcardSetup.mPreviousCommand = 0; + gMemcardSetup.mPreviousPrompt = 0; + gMemcardSetup.mLastMessage = savedMsg; + + if (MemoryCard::GetInstance()->InBootSequence()) { + ChangeToNextBootFlowScreen__15BootFlowManageri(BootFlowManager::Get(), 0xff); + MemoryCard::GetInstance()->EndBootSequence(); + } + cFEng::Get()->QueueGameMessage(0x7e998e5e, nullptr, 0xff); + MemoryCard::GetInstance()->SetMemcardScreenShowing(false); + MemoryCard::GetInstance()->SetMemcardScreenExiting(false); + if (MemoryCard::GetInstance()->IsMonitorOn()) { + MemoryCard::GetInstance()->SetMonitor(false); + } +} + +void UIMemcardBase::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + if (msg != 0xc407210 && MemoryCard::GetInstance()->GetOp() == 0) { + UIMemcardKeyboard::NotificationMessage(msg, obj, param1, param2); + } + switch (msg) { + case 0xe1fde1d1: + ExitComplete(); + break; + case 0x3a2be557: + case 0x35f8620b: + InitComplete(); + break; + case 0xda5b8712: { + const char* editStr = FEngGetEditedString(); + bStrCpy(m_FileName, editStr); + FEDatabase->CurrentUserProfiles[0]->SetProfileName(m_FileName, true); + FEDatabase->DeallocBackupDB(); + FEDatabase->bProfileLoaded = true; + DoSaveFlow(4); + break; + } + case 0xc9d30688: + if ((gMemcardSetup.mOp & 0xf0) == 0x60 && !FEDatabase->bProfileLoaded) { + DoSaveFlow(2); + } else if ((gMemcardSetup.mOp & 0x60) != 0 && FEDatabase->bProfileLoaded) { + DoSaveFlow(1); + } else { + FEPrintf(m_pDisplayMsg, ""); + m_bDelayedFailed = true; + } + break; + case 0xc98356ba: + if (m_bDelayedFailed) { + m_bDelayedFailed = false; + cFEng::Get()->QueueGameMessage(0x8867412d, GetPackageName(), 0xff); + } + break; + case 0xc502df5d: + m_bInButtonAnimation = true; + TranslateButton(obj); + break; + case 0xc407210: + m_bInButtonAnimation = false; + gMemcardSetup.mLastController = param1; + HandleButtonPressed(0xc407210, obj, param1, param2, false); + break; + case 0xf35d144e: + SetupPromptCorruptProfile(); + break; + case 0x54b3ac6c: + SetScreenVisible(false, 0); + cFEng::Get()->QueuePackagePush("MC_List.fng", 0, 0, false); + break; + } +} + +void UIMemcardBase::HandleButtonPressed(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2, bool bPadBack) { + bool isSecondBtn = (obj->NameHash == gButtonIDs[0]) && !bPadBack; + int promptFlags = gMemcardSetup.mOp & 0xf000000; + gMemcardSetup.mOp = gMemcardSetup.mOp & 0xf0ffffff; + gMemcardSetup.mPreviousPrompt = promptFlags; + HideAllButtons(); + + switch (promptFlags) { + case 0x1000000: + if (isSecondBtn) { + FEDatabase->AllocBackupDB(true); + if ((gMemcardSetup.mOp & 0x40000) == 0) { + if ((gMemcardSetup.mOp & 0x200000) == 0) { + FEDatabase->DefaultProfile(); + } + } + if ((gMemcardSetup.mOp & 0x80000) != 0) { + StartNewCareer__14CareerSettingsb( + FEDatabase->CurrentUserProfiles[0]->GetCareer(), 0); + } + if ((gMemcardSetup.mOp & 0xf0) == 0x20) { + gMemcardSetup.mOp = (gMemcardSetup.mOp & ~0xf0) | 0x60; + gMemcardSetup.mPreviousCommand = 0x20; + } + } else { + if ((gMemcardSetup.mOp & 0x80000) != 0) { + FEDatabase->RestoreFromBackupDB(); + } + cFEng::Get()->QueueGameMessage(0x8867412d, GetPackageName(), 0xff); + } + break; + case 0x4000000: + if (isSecondBtn) { + DoSaveFlow(12); + } else { + if ((gMemcardSetup.mOp & 0xf0) == 0x60) { + FEDatabase->CurrentUserProfiles[0]->GetOptions()->TheGameplaySettings.AutoSaveOn = 0; + } + cFEng::Get()->QueueGameMessage(0xdc12af2e, GetPackageName(), 0xff); + } + break; + case 0x5000000: + if (isSecondBtn) { + FEDatabase->AllocBackupDB(true); + if ((gMemcardSetup.mOp & 0x40000) == 0) { + if ((gMemcardSetup.mOp & 0x200000) == 0) { + FEDatabase->DefaultProfile(); + } + } + DoSaveFlow(10); + } else { + MemcardExit(0x8867412d); + } + break; + case 0x6000000: + if (isSecondBtn) { + InitCompleteDoList(); + } else { + cFEng::Get()->QueueGameMessage(0x8867412d, GetPackageName(), 0xff); + } + break; + case 0x7000000: + cFEng::Get()->QueueGameMessage(0x461a18ee, GetPackageName(), 0xff); + break; + case 0x8000000: + DoSaveFlow(11); + break; + case 0x9000000: + cFEng::Get()->QueuePackageMessage(0x40E73793, GetPackageName(), nullptr); + DoSaveFlow(3); + break; + case 0xa000000: + if (isSecondBtn) { + FEDatabase->CurrentUserProfiles[0]->GetOptions()->TheGameplaySettings.AutoSaveOn = 0; + cFEng::Get()->QueueGameMessage(0x8867412d, GetPackageName(), 0xff); + } else { + MemoryCard::GetInstance()->SetRetryAutoSave(true); + FEDatabase->CurrentUserProfiles[0]->GetOptions()->TheGameplaySettings.AutoSaveOn = 1; + gMemcardSetup.mPreviousCommand = gMemcardSetup.mOp & 0xf0; + gMemcardSetup.mOp = gMemcardSetup.mOp & ~0xf0; + MemoryCard::GetInstance()->ShowMessages(true); + gMemcardSetup.mOp = gMemcardSetup.mOp | 0x50; + DoSaveFlow(12); + } + break; + case 0xb000000: + if ((gMemcardSetup.mOp & 0xf0) == 0xa0) { + if ((gMemcardSetup.mOp & 0x8000) == 0) { + gMemcardSetup.mOp = (gMemcardSetup.mOp & ~0xf) | 1; + } + } + cFEng::Get()->QueueGameMessage(0x7e998e5e, nullptr, 0xff); + cFEng::Get()->QueueGameMessage(0x461a18ee, nullptr, 0xff); + break; + case 0xc000000: + if (isSecondBtn) { + MemoryCard::GetInstance()->SetAutoSaveEnabled(true); + } else { + FEDatabase->CurrentUserProfiles[0]->GetOptions()->TheGameplaySettings.AutoSaveOn = 0; + cFEng::Get()->QueueGameMessage(0x7e998e5e, nullptr, 0xff); + cFEng::Get()->QueueGameMessage(0x461a18ee, nullptr, 0xff); + } + break; + case 0x3000000: + case 0xd000000: + cFEng::Get()->QueueGameMessage(0x8867412d, GetPackageName(), 0xff); + break; + default: + SetStringCheckingCard(); + if (MemoryCard::GetInstance()->GetPendingMessage() != nullptr) { + ShowMessage(MemoryCard::GetInstance()->GetPendingMessage()); + } + if (MemoryCard::GetInstance()->GetOp() == 7) { + cFEng* feng = cFEng::Get(); + unsigned long hash = FEHashUpper("SHOW LOADER"); + feng->QueuePackageMessage(hash, GetPackageName(), nullptr); + } + break; + } +} + +void UIMemcardBase::NotificationMessageGoThroughAll(unsigned long msg, FEObject* obj, + unsigned long param1, unsigned long param2) { + NotificationMessage(msg, obj, param1, param2); +} + +void UIMemcardBase::SetupPromptSaveCorrupt() {} +void UIMemcardBase::SetupPromptOverwrite() {} +void UIMemcardBase::SetupPromptDelete() {} +void UIMemcardBase::SetupPromptLoadingCorrupt() {} +void UIMemcardBase::SetupPromptFormatCard() {} +void UIMemcardBase::SetupPromptAutoSaveEnable() {} +void UIMemcardBase::SetupPromptAutoSaveDisable() {} +void UIMemcardBase::SetupPromptOverwriteNoSaves() {} +void UIMemcardBase::SetupPromptAutoSaveEnableFailed() {} +int UIMemcardBase::BuildDeleteList(const char* pName, const char** pList) { return 0; } +UIMemcardBase::Item* UIMemcardBase::FindItem(const char* pName) { return nullptr; } + +unsigned int UIMemcardBase::GetAutoSaveWarning() { + return 0xb39899c2; +} + +unsigned int UIMemcardBase::GetAutoSaveWarning2() { + return 0x2386f454; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.hpp index eec880178..3cc3a83eb 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardBase.hpp @@ -5,6 +5,126 @@ #pragma once #endif +#include +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" + +struct UIMemcardList; +struct MemoryCardMessage; + +enum MemCardFileFlag { + MCFF_None = 0, +}; + +// total size: 0x40 +struct UIMemcardKeyboard : public MenuScreen { + FEString* m_pTitleMaster; // offset 0x2C, size 0x4 + FEString* m_pDisplayMsg; // offset 0x30, size 0x4 + FEString* m_pDisplayMsgShadow; // offset 0x34, size 0x4 + FEString* m_pOK; // offset 0x38, size 0x4 + FEString* m_pCancel; // offset 0x3C, size 0x4 + + UIMemcardKeyboard(ScreenConstructorData* sd); + ~UIMemcardKeyboard() override {} + virtual void Setup(); + virtual void Abort() {} + void ShowKeyboard(); + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; +}; + +// total size: 0x94 +struct UIMemcardBase : public UIMemcardKeyboard { + // total size: 0x50 + struct Item : public bTNode { + char m_Name[32]; // offset 0x8, size 0x20 + char m_Data[32]; // offset 0x28, size 0x20 + MemCardFileFlag m_Flag; // offset 0x48, size 0x4 + int m_Size; // offset 0x4C, size 0x4 + + ~Item() {} + Item() {} + }; + + char m_FileName[32]; // offset 0x40, size 0x20 + int mIndex; // offset 0x60, size 0x4 + int m_Flow; // offset 0x64, size 0x4 + bool m_ExpectingInput; // offset 0x68, size 0x1 + int m_LoadedNetConfig; // offset 0x6C, size 0x4 + int m_nMsgOptions; // offset 0x70, size 0x4 + bool m_bVisible; // offset 0x74, size 0x1 + bool m_bDelayedFailed; // offset 0x78, size 0x1 + bool m_bInButtonAnimation; // offset 0x7C, size 0x1 + bool m_bAnyButtonVisible; // offset 0x80, size 0x1 + UIMemcardList* m_pChild; // offset 0x84, size 0x4 + bTList m_Items; // offset 0x88, size 0x8 + bool m_SimPausedForMemcard; // offset 0x90, size 0x1 + + bool IsInButtonAnimation() { return m_bInButtonAnimation; } + + UIMemcardBase(ScreenConstructorData* sd); + ~UIMemcardBase() override; + void Abort() override; + virtual void ShowKeyboard(); + virtual void DoSelect(const char* pFileName); + + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, + eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; + void HandleButtonPressed(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2, bool bSound); + void HideAllButtons(); + void ShowButton(int index, bool bShow, short* pText); + void SetButtonText(short* pBtn1, short* pBtn2, short* pBtn3); + void SetMessage(short* pText); + void ShowOK(unsigned int textHash, unsigned int iconHash); + void ShowYesNo(unsigned int textHash, unsigned int iconHash); + void SetScreenVisible(bool bVisible, int delay); + void SetIcon(unsigned int iconHash); + void TranslateButton(FEObject* pButton); + bool AddItem(const char* pName, const char* pDate, int size, int flag); + bool IsProfile(const char* pName); + int BuildDeleteList(const char* pName, const char** pList); + void EmptyFileList(); + Item* FindItem(const char* pName); + void InitCompleteDoList(); + void InitComplete(); + void ExitComplete(); + void NotificationMessageGoThroughAll(unsigned long msg, FEObject* obj, + unsigned long param1, unsigned long param2); + void SetupPromptSaveCorrupt(); + void SetupPromptOverwrite(); + void SetupPromptDelete(); + void SetupPromptLoadingCorrupt(); + void SetupPromptFormatCard(); + void SetupPromptAutoSaveEnable(); + void SetupPromptAutoSaveDisable(); + void SetupPromptNoProfileFound(); + void SetupPromptAutoSaveEnableFailed(); + void SetupPromptOverwriteNoSaves(); + void SetupPromptSaveConfirm(); + void SetupAutoSaveConfirmPrompt(); + void SetupPromptForSave(); + void SetupPromptCorruptProfile(); + void SetupPromptAutoSaveEnableFailedNoCard(); + void Setup() override; + void SetStringCheckingCard(); + void DoSaveFlow(int flow); + void SetMessageBlurbText(short* pText); + void SetMessageBlurbText(char* pText); + void SetMessageBlurbText(unsigned int textHash); + void FindScreenSize(const wchar_t* msg); + unsigned int GetAutoSaveWarning(); + unsigned int GetAutoSaveWarning2(); + void ShowMessage(MemoryCardMessage* msg); + void ShowMessage(const wchar_t* msg, unsigned int nOptions, const wchar_t* option1, + const wchar_t* option2, const wchar_t* option3); + void ActivateChild(); + void PopChild(); + void HandleAutoSaveError(); + void HandleAutoSaveOverwriteMessage(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.cpp index e69de29bb..51e79fcf9 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.cpp @@ -0,0 +1,22 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp" + +MemoryCardSetup gMemcardSetup; + +void MemoryCardSetup::Clear() { + mOp = 0; + mMemScreen = nullptr; + mToScreen = nullptr; + mFromScreen = nullptr; + mTermFunc = nullptr; + mTermFuncParam = nullptr; + mLastMessage = 0; + mSuccessMsg = 0; + mFailedMsg = 0; + mInBootFlow = false; + mPreviousCommand = 0; + mPreviousPrompt = 0; +} + +unsigned int MemcardGetCurrentUIOperation() { + return gMemcardSetup.mOp & 0xf0; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp index 8ef9e226a..4bee7fb95 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/MemCard/uiMemcardInterface.hpp @@ -1,8 +1,104 @@ -#ifndef FRONTEND_MENUSCREENS_MEMCARD_UIMEMCARDINTERFACE_H -#define FRONTEND_MENUSCREENS_MEMCARD_UIMEMCARDINTERFACE_H +#ifndef _UIMEMCARDINTERFACE +#define _UIMEMCARDINTERFACE -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +struct MemoryCardSetup { + unsigned int mOp; + const char* mFromScreen; + const char* mToScreen; + const char* mMemScreen; + void (*mTermFunc)(void*); + void* mTermFuncParam; + unsigned int mLastMessage; + unsigned int mPreviousCommand; + unsigned int mPreviousPrompt; + unsigned int mSuccessMsg; + unsigned int mFailedMsg; + unsigned int mLastController; + bool mInBootFlow; + + MemoryCardSetup() { Clear(); } + + unsigned int GetCommand() { + return mOp & 0xf0; + } + + unsigned int GetCommand() const { + return mOp & 0xf; + } + + unsigned int GetMethod() const { + return mOp & 0xf0; + } + + unsigned int GetExtraOptions() const { + return (mOp >> 8) & 0xf; + } + + unsigned int GetPrompt() const { + return (mOp >> 12) & 0xf; + } + + void SetCommand(int command) { + mOp = (mOp & ~0xf0) | (command & 0xf0); + } + + void SetMethod(int method) { + mOp = (mOp & ~0xf0) | (method & 0xf0); + } + + void SetExtraOption(int eo) { + mOp = (mOp & ~0xf00) | ((eo & 0xf) << 8); + } + + void SetPrompt(int prompt) { + mOp = (mOp & ~0xf000) | ((prompt & 0xf) << 12); + } + + void ClearCommand() { + mOp = mOp & ~0xf0; + } + + void ClearMethod() { + mOp = mOp & ~0xf0; + } + + void ClearPrompt() { + unsigned int p = GetPrompt(); + if (p != 0) { + mPreviousPrompt = p; + mOp = mOp & ~0xf000; + } + } + + bool IsSaving() const { + unsigned int cmd = GetCommand(); + return cmd == 3 || cmd == 4; + } + + void Clear(); + + void SendTermMessage(unsigned int msg) { + if (mTermFunc != nullptr) { + mTermFunc(mTermFuncParam); + } + } + + void Complete(unsigned int msg) { + mLastMessage = msg; + SendTermMessage(msg); + Clear(); + } +}; + +typedef MemoryCardSetup MemcardSetup; + +extern MemoryCardSetup gMemcardSetup; + +void MemcardEnter(const char *from, const char *to, unsigned int op, + void (*termFunc)(void *), void *termParam, + unsigned int successMsg, unsigned int failedMsg); +void MemcardExit(unsigned int msg); #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.cpp index e69de29bb..140648ebe 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.cpp @@ -0,0 +1,1088 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/Frontend/FECarLoader.hpp" +#include "Speed/Indep/Src/World/CarLoader.hpp" +#include "Speed/Indep/Src/World/CarRender.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOn.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/Src/Ecstasy/Ecstasy.hpp" +#include "Speed/Indep/Src/Ecstasy/EmitterSystem.h" +#include "Speed/Indep/Src/Ecstasy/eModel.hpp" +#include "Speed/Indep/Src/Ecstasy/eSolid.hpp" +#include "Speed/Indep/Src/Ecstasy/EcstasyData.hpp" +#include "Speed/Indep/Src/Input/ActionQueue.h" +#include "Speed/Indep/Src/Input/ActionRef.h" +#include "Speed/Indep/Src/Misc/ResourceLoader.hpp" +#include "Speed/Indep/Src/Misc/DemoDisc.hpp" +#include "Speed/Indep/Libs/Support/Utility/FastMem.h" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/Camera/CameraMover.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +extern MenuScreen *FEngFindScreen(const char *name); +extern FEString *FEngFindString(const char *pkg_name, int hash); +extern void FEngSetLanguageHash(FEString *text, unsigned int hash); +extern int FEPrintf(FEString *text, const char *fmt, ...); +extern int FEngSNPrintf(char *, int, const char *, ...); +extern unsigned long FEHashUpper(const char *str); +extern int FEngMapJoyParamToJoyport(int feng_param); +extern unsigned int AttribGenFrontendClassKey() asm("ClassKey__Q36Attrib3Gen8frontend"); + +extern void SetSelectCarLighting(int view_id, float f, int); +extern void eRotateZ(bMatrix4 *, bMatrix4 *, unsigned short); +extern void eRotateX(bMatrix4 *, bMatrix4 *, unsigned short); +extern void eRotateY(bMatrix4 *, bMatrix4 *, unsigned short); +extern void eMulVector(bVector3 *, const bMatrix4 *, const bVector3 *); +extern void PSMTX44Identity(void *mtx); +extern void PSMTX44Copy(const void *src, void *dst); +extern void eInitFEEnvMapPlat(); +extern void eRemoveFEEnvMapPlat(); +extern void GameFlowLoadGarageScreen(void (*callback)(int), int param); +extern void AddScreenEffect(ScreenEffectDB *db, ScreenEffectType type, float a, float b, float c, float d); + +RideInfo TopOrFullScreenRide; +CarCustomizeManager gCarCustomizeManager; +eSetRideInfoReasons TopOrFullScreenLoadingReason; + +extern CarLoader TheCarLoader; + +extern EmitterSystem gEmitterSystem; +extern float RealTimeElapsed; +extern unsigned int FrameMallocFailed; +extern unsigned int FrameMallocFailAmount; + +float carPosX = 0.0f; +float carPosY = 0.0f; +float CarSelectTireSteerAngle = 21.6723f; +extern int CarTypeInfoArrayUpdated; + +struct SelectCarCameraData { + float OrbitVAngle; + float OrbitHAngle; + float Radius; + float RollAngle; + float FOV; + bVector3 LookAt; +}; + +struct SelectCarCameraMover : CameraMover { + SelectCarCameraMover(int view_id); + ~SelectCarCameraMover() override; + void Update(float dT) override; + void SetVRotateSpeed(float f); + void SetHRotateSpeed(float f); + void SetZoomSpeed(float f); + void SetCurrentOrientation(bVector3 &orbit, float roll, float fov, bVector3 &lookAt); + void SetDesiredOrientation(bVector3 &orbit, float roll, float fov, bVector3 &lookAt, float animSpeed, float damping, int periods); + float FindBestAngleGoal(float start, float goal); + + int ControlMode; + int DramaticMode; + int LookingAtParts; + SelectCarCameraData CurrentCameraData; + SelectCarCameraData StartAnimCameraData; + SelectCarCameraData GoalAnimCameraData; + float RadiusSpeed; + float OrbitVSpeed; + float OrbitHSpeed; + float Damping; + int Periods; + float CurrentAnimationTime; + float TotalAnimationTime; +}; +extern void SetHRotateSpeed(SelectCarCameraMover *mover, float speed); +extern void SetVRotateSpeed(SelectCarCameraMover *mover, float speed); +extern void SetZoomSpeed(SelectCarCameraMover *mover, float speed); +extern void SetCurrentOrientation(SelectCarCameraMover *mover, bVector3 *target, float roll, float fov, bVector3 *lookat); +extern void SetDesiredOrientation(SelectCarCameraMover *mover, bVector3 *target, float roll, float fov, float anim_speed, float damping, bVector3 *lookat, int periods); +extern SelectCarCameraMover *NewSelectCarCameraMover(int view_id); +extern void SelectCarCameraMover_SetTime(SelectCarCameraMover *mover, float time); + +struct EAXFrontEnd; +extern void DestroyAllDriveOnSnds(EAXFrontEnd *fe_snd); +extern void SetFEDrivingCarState(EAXFrontEnd *fe_snd, bVector3 *pos, bVector3 *vel, void *camera, int view_id); + +extern bTList SolidList; + +static float cam_blur = 0.0f; +extern int CarGuysCamera; +static float CarRotateSpeed = 0.5f; + +extern unsigned char *CurrentBufferPos; +extern unsigned char *CurrentBufferEnd; + +extern void UnloadResourceFile(ResourceFile *file); + +extern void DeleteSelectCarCameraMover(SelectCarCameraMover *mover); +extern void EAXFrontEnd_DestroyAllDriveOnSnds(void *); + +extern DemoDiscManager TheDemoDiscManager; + +extern ScreenEffectDB *iRam80462020; + +extern char *bStrIStr(const char *, const char *); +extern int bStrNICmp(const char *, const char *, int); + +#ifndef ABS +#define ABS(x) ((x) < 0 ? -(x) : (x)) +#endif + +static int sNumTicksSinceUserMovedCamera = 0; +static int sNumTicksBeforeCamMovesBackToScreenPosition = 300; +static int bAutoMovement = 0; +static int bPass1 = 0; +static bool RenderLookAtPoint = false; + +static const char lbl_GarageMain[] = "GarageMain.fng"; + +static unsigned int FindScreenInfo(const char *pkg_name, int category); +static unsigned int FindScreenCameraInfo(unsigned int screen_key); +static unsigned int FindGarageEntryCameraInfo(); +static unsigned int FindGarageFinalCameraInfo(); + +GarageCarLoader *GetGarageCarLoader(); + +// --- Free functions --- + +static bool HaveAttributesChanged(Attrib::Gen::frontend &) { + return false; +} + +static const char *GetCurrentGarageName() { + eGarageType type = FEManager::Get()->GetGarageType(); + switch (type) { + case GARAGETYPE_CUSTOMIZATION_SHOP_BACKROOM: + return "backroom"; + case GARAGETYPE_CAREER_SAFEHOUSE: + return "career_safehouse"; + case GARAGETYPE_CUSTOMIZATION_SHOP: + return "customization_shop"; + case GARAGETYPE_CAR_LOT: + return "car_lot"; + case GARAGETYPE_MAIN_FE: + default: + break; + } + const char *name; + if (FEDatabase->IsCareerManagerMode()) { + name = "career_manager"; + } else { + name = "main_fe"; + } + return name; +} + +// --- FEGeometryModels --- + +void FEGeometryModels::Init(char *filterPrefix) { + const int kMaxModels = 32; + eSolid *SolidTable[kMaxModels]; + int filterPrefixSize; + + mNumModels = 0; + filterPrefixSize = bStrLen(filterPrefix); + for (eSolid *solid = SolidList.GetHead(); solid != SolidList.EndOfList(); solid = solid->GetNext()) { + if (bStrNICmp(solid->GetName(), filterPrefix, filterPrefixSize) == 0) { + SolidTable[mNumModels++] = solid; + } + } + + if (mNumModels != 0) { + mModels = new eModel[mNumModels]; + for (int i = 0; i < mNumModels; i++) { + mModels[i].Init(SolidTable[i]->NameHash); + if (bStrIStr(mModels[i].GetSolid()->GetName(), "CAST_SHADOW_MAP")) { + mModelCastsShadowMapFlags |= 1 << i; + } + if (bStrIStr(mModels[i].GetSolid()->GetName(), "CURRGEN")) { + mModelCurrGenOnly |= 1 << i; + } + if (bStrIStr(mModels[i].GetSolid()->GetName(), "NEXTGEN")) { + mModelNextGenOnly |= 1 << i; + } + } + } +} + +void FEGeometryModels::UnInit() { + if (mModels) { + int count = *reinterpret_cast(reinterpret_cast(mModels) - 8); + eModel *end = mModels + count; + while (mModels != end) { + end--; + end->UnInit(); + } + ::operator delete[](reinterpret_cast(mModels) - 8); + } + mModels = nullptr; + mModelCastsShadowMapFlags = 0; + mModelCurrGenOnly = 0; + mModelNextGenOnly = 0; +} + +void FEGeometryModels::Render(eView *view, bMatrix4 *local, unsigned int render_flags) { + for (int i = 0; i < mNumModels; i++) { + bool renderModel = true; + if ((render_flags & 4) != 0) { + if ((1 << i & mModelCastsShadowMapFlags) == 0) { + renderModel = false; + } + } + if ((render_flags & 1) != 0) { + if ((1 << i & mModelNextGenOnly) != 0) { + renderModel = false; + } + } + if ((render_flags & 2) != 0 && (1 << i & mModelCurrGenOnly) != 0) { + renderModel = false; + } + if (renderModel) { + view->Render(&mModels[i], local, nullptr, 4, nullptr); + } + } +} + +// --- GarageMainScreen --- + +GarageMainScreen *GarageMainScreen::sInstance; + +GarageMainScreen *GarageMainScreen::GetInstance() { + return static_cast(FEngFindScreen(lbl_GarageMain)); +} + +void GarageMainScreen::EnableCarRendering() { + if (RenderingCar) { + RenderingCar->Visible = 1; + } +} + +void GarageMainScreen::DisableCarRendering() { + if (RenderingCar) { + RenderingCar->Visible = 0; + } +} + +bool GarageMainScreen::IsCarRendering() { + if (RenderingCar && RenderingCar->Visible) { + return true; + } + return false; +} + +void GarageMainScreen::HandleHidePackage(unsigned int msg) { + RenderingCar->Visible = 0; +} + +void GarageMainScreen::CancelCarLoad() { + CarState = 1; + TheGarageCarLoader->CancelCarLoad(); +} + +void GarageMainScreen::NotificationMessage(unsigned long Message, FEObject *pObject, unsigned long Param1, unsigned long Param2) { + switch (Message) { + case 0x0AD4BBDC: + HideEntireScreen = 1; + HandleHidePackage(0x0AD4BBDC); + break; + case 0x18883F75: + HideEntireScreen = 0; + HandleShowPackage(0x18883F75); + break; + case 0xD0678849: + HandleJoyEvents(); + break; + case 0xC98356BA: + HandleTick(0xC98356BA); + break; + } +} + +GarageMainScreen::GarageMainScreen(ScreenConstructorData *sd, int eview_id, RideInfo *start_ride, int player) + : MenuScreen(sd) // +{ + HideEntireScreen = 1; + ViewID = eview_id; + bUserRotate = false; + mZoom = 0.0f; + mCustomizationCategory = -1; + LoadingReason = static_cast(1); + RenderingCar = nullptr; + mGeometryModels.mModelCastsShadowMapFlags = 0; + mGeometryModels.mModelCurrGenOnly = 0; + mGeometryModels.mModelNextGenOnly = 0; + mGeometryModels.mNumModels = 0; + mGeometryModels.mModels = nullptr; + Player = player; + CameraPushRequested = false; + mScreenKeyCamIsSetTo = 0; + mOrbitV = 0.0f; + mOrbitH = 0.0f; + + int i = 0; + do { + mActionQ[i] = new ActionQueue(i, 0x82d21520, "GarageMainScreen", false); + mActionQ[i]->Enable(true); + i++; + } while (i < 2); + + if (player == 0) { + pCarName = FEngFindString(GetPackageName(), 0xdb8ccef6); + pPlayerName = FEngFindString(GetPackageName(), 0x83003e0d); + FEPrintf(pPlayerName, "%s", FEDatabase->GetUserProfile(0)->GetProfileName()); + } else if (player == 1) { + pCarName = FEngFindString(GetPackageName(), 0xdb8ccef7); + pPlayerName = FEngFindString(GetPackageName(), 0x83003e0e); + FEPrintf(pPlayerName, "%s", FEDatabase->GetUserProfile(1)->GetProfileName()); + } + + TheGarageCarLoader = GetGarageCarLoader(); + SetRideInfo(start_ride, LoadingReason); + CarState = 0; + RenderingCar = new FrontEndRenderingCar(nullptr, ViewID); + pCameraMover = new SelectCarCameraMover(ViewID); + mGeometryModels.Init("BACKDROP"); + + char sztemp[32]; + FEngSNPrintf(sztemp, 0x20, "CAR_NAME_%s", GetCarTypeInfo(start_ride->Type)->CarTypeName); + FEngSetLanguageHash(pCarName, FEHashUpper(sztemp)); + SetSelectCarLighting(ViewID, 1.0f, 0); + HandleTick(0); +} + +GarageMainScreen::~GarageMainScreen() { + if (pCameraMover) { + delete pCameraMover; + } + if (RenderingCar) { + delete RenderingCar; + } + mGeometryModels.UnInit(); + if (g_pEAXSound->GetFrontEnd()) { + EAXFrontEnd_DestroyAllDriveOnSnds(g_pEAXSound->GetFrontEnd()); + } + for (int i = 0; i < 2; i++) { + if (mActionQ[i]) { + delete mActionQ[i]; + mActionQ[i] = nullptr; + } + } +} + +void GarageMainScreen::SetRideInfo(RideInfo *ride, eSetRideInfoReasons reason) { + TheGarageCarLoader->LoadRideInfo(ride); + CarState = 0; + RideInfo *current = TheGarageCarLoader->GetCurrentRideInfo(); + if (current) { + RideInfo *current2 = TheGarageCarLoader->GetCurrentRideInfo(); + if (current2->Type != ride->Type) { + DisableCarRendering(); + cFEng::Get()->QueuePackageMessage(0xa05a328e, nullptr, nullptr); + } + } + char sztemp[32]; + FEngSNPrintf(sztemp, 32, "CAR_NAME_%s", GetCarTypeInfo(ride->Type)->CarTypeName); + FEngSetLanguageHash(pCarName, FEHashUpper(sztemp)); +} + +void GarageMainScreen::HandleTick(unsigned long msg) { + bool have_new_car = false; + if (CarState == 0 && TheGarageCarLoader->IsDifferent) { + TheGarageCarLoader->Switch(); + have_new_car = true; + CarState = 1; + } + if (have_new_car) { + RideInfo *CurrentRideInfo = TheGarageCarLoader->GetCurrentRideInfo(); + if (CurrentRideInfo) { + RenderingCar->ReInit(CurrentRideInfo); + RenderingCar->Visible = 1; + cFEng::Get()->QueuePackageMessage(0x913fa282, nullptr, nullptr); + } + } + HandleJoyEvents(); + + if (mOrbitV == 0.0f && mOrbitH == 0.0f && mZoom == 0.0f && sNumTicksSinceUserMovedCamera > 0 && CarGuysCamera == 0) { + sNumTicksSinceUserMovedCamera--; + } + + bool bTimeToRotate = false; + if (sNumTicksSinceUserMovedCamera == 0 && bUserRotate) { + bTimeToRotate = bAutoMovement == 0; + } + if (bTimeToRotate && bPass1) { + bTimeToRotate = false; + SetHRotateSpeed(pCameraMover, CarRotateSpeed); + bAutoMovement = 1; + bPass1 = 0; + } + + FEPackage *currentControllingPackage = cFEng::Get()->FindPackageAtBase(); + if (!currentControllingPackage) goto after_camera; + { + const unsigned int screenKey = FindScreenInfo(currentControllingPackage->GetName(), mCustomizationCategory); + const unsigned int attribKey = FindScreenCameraInfo(screenKey); + Attrib::Gen::frontend camera(Attrib::FindCollection(Attrib::Gen::frontend::ClassKey(), attribKey), 0, nullptr); + if (!camera.GetLayoutPointer()) { + camera.SetDefaultLayout(sizeof(Attrib::Gen::frontend::_LayoutStruct)); + } + Attrib::Gen::frontend screen(Attrib::FindCollection(Attrib::Gen::frontend::ClassKey(), screenKey), 0, nullptr); + if (!screen.GetLayoutPointer()) { + screen.SetDefaultLayout(sizeof(Attrib::Gen::frontend::_LayoutStruct)); + } + + if (screenKey != mScreenKeyCamIsSetTo) { + float anim_speed = camera.cam_anim_speed(); + bAutoMovement = 0; + bPass1 = 0; + sNumTicksSinceUserMovedCamera = static_cast(anim_speed * 60.0f); + mScreenKeyCamIsSetTo = screenKey; + bUserRotate = screen.cam_user_rotate(); + if (!CameraPushRequested) { + bVector3 orbit(camera.cam_orbit_vertical(), camera.cam_orbit_horizontal(), camera.cam_orbit_radius()); + bVector3 lookat(camera.cam_lookat_x(), camera.cam_lookat_y(), camera.cam_lookat_z()); + SetDesiredOrientation(pCameraMover, &orbit, camera.cam_roll_angle(), camera.cam_fov(), camera.cam_anim_speed(), camera.cam_damping(), &lookat, camera.cam_periods()); + } + } else { + if (bTimeToRotate) { + float anim_speed = camera.cam_anim_speed(); + bPass1 = 1; + sNumTicksSinceUserMovedCamera = static_cast(anim_speed * 60.0f); + mScreenKeyCamIsSetTo = screenKey; + bUserRotate = screen.cam_user_rotate(); + if (!CameraPushRequested) { + bVector3 orbit(camera.cam_orbit_vertical(), camera.cam_orbit_horizontal(), camera.cam_orbit_radius()); + bVector3 lookat(camera.cam_lookat_x(), camera.cam_lookat_y(), camera.cam_lookat_z()); + SetDesiredOrientation(pCameraMover, &orbit, camera.cam_roll_angle(), camera.cam_fov(), camera.cam_anim_speed(), camera.cam_damping(), &lookat, camera.cam_periods()); + } + } else { + if (HaveAttributesChanged(camera)) { + bVector3 orbit(camera.cam_orbit_vertical(), camera.cam_orbit_horizontal(), camera.cam_orbit_radius()); + bVector3 lookat(camera.cam_lookat_x(), camera.cam_lookat_y(), camera.cam_lookat_z()); + SetCurrentOrientation(pCameraMover, &orbit, camera.cam_roll_angle(), camera.cam_fov(), &lookat); + } + } + } + } +after_camera: + if (iRam80462020) { + AddScreenEffect(iRam80462020, static_cast(4), cam_blur, 0.0f, 0.0f, 0.0f); + } + UpdateRenderingCarParameters(RenderingCar); + RefreshBackground(); +} + +void GarageMainScreen::UpdateRenderingCarParameters(FrontEndRenderingCar *fe_car) { + if (reinterpret_cast(fe_car) == -8 || fe_car->GetRideInfo()->Type == static_cast(-1) || HideEntireScreen) { + fe_car->Visible = 0; + return; + } + + if (CarTypeInfoArrayUpdated) { + CarTypeInfoArrayUpdated = 0; + } + + bVector4 wheel_positions[4]; + float wheel_radius[4]; + float average_wheel_radius = 0.0f; + float average_wheel_z = 0.0f; + + for (unsigned int i = 0; i <= 3; i++) { + if (!fe_car->LookupWheelPosition(i, &wheel_positions[i])) { + wheel_positions[i] = bVector4(0.0f, 0.0f, 0.0f, 1.0f); + } + if (!fe_car->LookupWheelRadius(i, wheel_radius[i])) { + wheel_radius[i] = 1.5f; + } + average_wheel_radius += wheel_radius[i]; + average_wheel_z += wheel_positions[i].z; + wheel_positions[i].w = 1.0f; + } + + average_wheel_z *= 0.25f; + fe_car->LightsOn = 0; + float height = average_wheel_radius * 0.25f - average_wheel_z + (-0.025f); + bVector3 position(carPosX, carPosY, height); + fe_car->CopLightsOn = 0; + + bMatrix4 temp; + PSMTX44Identity(&temp); + eRotateZ(&temp, &temp, static_cast(GetGeometryZAngle() * 65536.0f) / 360 & 0xffff); + eMulVector(&position, &temp, &position); + fe_car->SetPosition(&position); + + bMatrix4 body_matrix; + PSMTX44Identity(&body_matrix); + eRotateZ(&body_matrix, &body_matrix, static_cast(GetCarRotationZ() * 65536.0f) / 360 & 0xffff); + eRotateX(&body_matrix, &body_matrix, static_cast(GetCarRotationX() * 65536.0f) / 360 & 0xffff); + eRotateY(&body_matrix, &body_matrix, static_cast(GetCarRotationY() * 65536.0f) / 360 & 0xffff); + fe_car->SetBodyMatrix(&body_matrix); + + bMatrix4 tire_matrices[4]; + bMatrix4 brake_matrices[4]; + unsigned short front_tire_angle = static_cast(CarSelectTireSteerAngle * 65536.0f) / 360 & 0xffff; + + for (int tire_num = 0; tire_num < 4; tire_num++) { + PSMTX44Identity(&tire_matrices[tire_num]); + PSMTX44Identity(&brake_matrices[tire_num]); + if (tire_num < 2) { + eRotateZ(&brake_matrices[tire_num], &brake_matrices[tire_num], front_tire_angle); + eRotateZ(&tire_matrices[tire_num], &tire_matrices[tire_num], front_tire_angle); + } + tire_matrices[tire_num].v3 = wheel_positions[tire_num]; + brake_matrices[tire_num].v3 = wheel_positions[tire_num]; + } + + fe_car->SetTireMatrices(tire_matrices); + fe_car->SetBrakeMatrices(brake_matrices); + + if (g_pEAXSound->GetFrontEnd()) { + RideInfo *CurrentRideInfo = TheGarageCarLoader->GetCurrentRideInfo(); + if (CurrentRideInfo) { + bVector3 car_velocity(0.0f, 0.0f, 0.0f); + Camera *camera = eViews[0].GetCamera(); + SetFEDrivingCarState(g_pEAXSound->GetFrontEnd(), &position, &car_velocity, camera, ViewID); + } + } +} + +void GarageMainScreen::UpdateCurrentCameraView(bool bForce) { + if (CameraPushRequested || bForce) { + unsigned int entryKey = FindGarageEntryCameraInfo(); + Attrib::Gen::frontend entry(Attrib::FindCollection(Attrib::Gen::frontend::ClassKey(), entryKey), 0, nullptr); + if (!entry.GetLayoutPointer()) { + entry.SetDefaultLayout(sizeof(Attrib::Gen::frontend::_LayoutStruct)); + } + bVector3 orbit(entry.cam_orbit_vertical(), entry.cam_orbit_horizontal(), entry.cam_orbit_radius()); + bVector3 lookat(entry.cam_lookat_x(), entry.cam_lookat_y(), entry.cam_lookat_z()); + SetCurrentOrientation(pCameraMover, &orbit, entry.cam_roll_angle(), entry.cam_fov(), &lookat); + + unsigned int finalKey = FindGarageFinalCameraInfo(); + Attrib::Gen::frontend final_cam(Attrib::FindCollection(Attrib::Gen::frontend::ClassKey(), finalKey), 0, nullptr); + if (!final_cam.GetLayoutPointer()) { + final_cam.SetDefaultLayout(sizeof(Attrib::Gen::frontend::_LayoutStruct)); + } + bVector3 orbit2(final_cam.cam_orbit_vertical(), final_cam.cam_orbit_horizontal(), final_cam.cam_orbit_radius()); + bVector3 lookat2(final_cam.cam_lookat_x(), final_cam.cam_lookat_y(), final_cam.cam_lookat_z()); + SetDesiredOrientation(pCameraMover, &orbit2, final_cam.cam_roll_angle(), final_cam.cam_fov(), final_cam.cam_anim_speed(), final_cam.cam_damping(), &lookat2, final_cam.cam_periods()); + + CameraPushRequested = false; + } +} + +void GarageMainScreen::RefreshBackground() { + const char *garageName = FEManager::Get()->GetGarageNameFromType(); + ResourceFile *bg = FEManager::Get()->GetGarageBackground(); + char name[128]; + bStrCpy(name, bg->GetFilename()); + char *dot = bStrIStr(name, "."); + bStrCpy(dot, ".BIN"); + if (!bg || bStrCmp(name, garageName) != 0) { + new EFadeScreenOn(false); + eRemoveFEEnvMapPlat(); + eInitFEEnvMapPlat(); + UnloadResourceFile(bg); + GameFlowLoadGarageScreen(BackgroundLoaded, 0); + } +} + +void GarageMainScreen::BackgroundLoaded(int param) { + GarageMainScreen *inst = GetInstance(); + if (inst) { + new EFadeScreenOff(0x14035fb); + inst->mGeometryModels.UnInit(); + inst->mGeometryModels.Init("BACKDROP"); + inst->UpdateCurrentCameraView(true); + SelectCarCameraMover_SetTime(inst->pCameraMover, 0.0f); + } +} + +void GarageMainScreen::HandleRender(unsigned int render_flags) { + if (HideEntireScreen == 0) { + eView *view = &eViews[ViewID]; + bMatrix4 *local = reinterpret_cast(CurrentBufferPos); + if (CurrentBufferPos + 0x40 >= CurrentBufferEnd) { + FrameMallocFailed = 1; + FrameMallocFailAmount += 0x40; + local = nullptr; + } else { + CurrentBufferPos += 0x40; + } + if (local) { + PSMTX44Identity(local); + float angle = GetGeometryZAngle(); + eRotateZ(local, local, static_cast(angle * 65536.0f) / 360 & 0xffff); + local->v3.x = GetGeometryXPos(); + local->v3.y = GetGeometryYPos(); + local->v3.z = GetGeometryZPos(); + mGeometryModels.Render(view, local, render_flags); + } + gEmitterSystem.Update(RealTimeElapsed); + } +} + +void GarageMainScreen::HandleShowPackage(unsigned int msg) { + RenderingCar->Visible = 1; + if (!(FEDatabase->GetGameMode() & 4)) { + UpdateCurrentCameraView(true); + SelectCarCameraMover_SetTime(pCameraMover, 0.0f); + CameraPushRequested = true; + } +} + +void GarageMainScreen::HandleJoyEvents() { + static float zoomIn = 0.0f; + static float zoomOut = 0.0f; + int startPort = 0; + int endPort = 2; + bool isQR = false; + if (FEDatabase->GetGameMode() & 4) { + isQR = FEDatabase->iNumPlayers == 2; + } + if (isQR) { + FEPackage *ctrl = cFEng::mInstance->FindPackageWithControl(); + if (ctrl) { + startPort = FEngMapJoyParamToJoyport(ctrl->Controllers); + endPort = startPort + 1; + } + } + for (int port = startPort; port < endPort; port++) { + if (!mActionQ[port]) continue; + while (!mActionQ[port]->IsEmpty()) { + if (bUserRotate || CarGuysCamera != 0) { + ActionRef action = mActionQ[port]->GetAction(); + float dVar7; + if (!mActionQ[port]->IsConnected()) { + dVar7 = 0.0f; + } else if (action.ID() == 0) { + dVar7 = 0.0f; + } else { + dVar7 = action.Data(); + } + int id = action.ID(); + if (id == 0x20) { + mOrbitH = -dVar7; + SetHRotateSpeed(pCameraMover, -dVar7); + sNumTicksSinceUserMovedCamera = sNumTicksBeforeCamMovesBackToScreenPosition; + } else if (id < 0x21) { + if (id == 0x1e) { + mOrbitV = -dVar7; + SetVRotateSpeed(pCameraMover, -dVar7); + sNumTicksSinceUserMovedCamera = sNumTicksBeforeCamMovesBackToScreenPosition; + } else if (id == 0x1d) { + mOrbitV = dVar7; + SetVRotateSpeed(pCameraMover, dVar7); + sNumTicksSinceUserMovedCamera = sNumTicksBeforeCamMovesBackToScreenPosition; + } else if (id == 0x1f) { + mOrbitH = dVar7; + SetHRotateSpeed(pCameraMover, dVar7); + sNumTicksSinceUserMovedCamera = sNumTicksBeforeCamMovesBackToScreenPosition; + } + } else if (id > 0x2a) { + if (id < 0x2d) { + if (id == 0x2b) { + zoomOut = dVar7; + } else { + zoomIn = -dVar7; + } + sNumTicksSinceUserMovedCamera = sNumTicksBeforeCamMovesBackToScreenPosition; + if (ABS(zoomIn) > ABS(zoomOut)) { + mZoom = zoomIn; + } else if (ABS(zoomOut) > ABS(zoomIn)) { + mZoom = zoomOut; + } else { + if (zoomOut == 0.0f && zoomIn == 0.0f) { + mZoom = 0.0f; + } + } + SetZoomSpeed(pCameraMover, mZoom); + } else if (id == 0x88) { + SetVRotateSpeed(pCameraMover, 0.0f); + SetHRotateSpeed(pCameraMover, 0.0f); + SetZoomSpeed(pCameraMover, 0.0f); + sNumTicksSinceUserMovedCamera = sNumTicksBeforeCamMovesBackToScreenPosition; + } + } + if (sNumTicksSinceUserMovedCamera > 0) { + if (bAutoMovement) { + if (action.ID() != 0x1f && action.ID() != 0x20) { + SetHRotateSpeed(pCameraMover, 0.0f); + } + } + bAutoMovement = 0; + } + } + mActionQ[port]->PopAction(); + } + } +} + +float GarageMainScreen::GetCarRotationX() { + eGarageType type = FEManager::Get()->GetGarageType(); + switch (type) { + case GARAGETYPE_CAR_LOT: + return -0.3796229958534241f; + case GARAGETYPE_NONE: + case GARAGETYPE_MAIN_FE: + default: + return 0.0f; + case GARAGETYPE_CAREER_SAFEHOUSE: + return 0.0f; + case GARAGETYPE_CUSTOMIZATION_SHOP: + return 0.0f; + } +} + +float GarageMainScreen::GetCarRotationY() { + eGarageType type = FEManager::Get()->GetGarageType(); + switch (type) { + case GARAGETYPE_CAR_LOT: + return -0.00019299999985378236f; + case GARAGETYPE_NONE: + case GARAGETYPE_MAIN_FE: + default: + return 0.0f; + case GARAGETYPE_CAREER_SAFEHOUSE: + return 0.0f; + case GARAGETYPE_CUSTOMIZATION_SHOP: + return 0.0f; + } +} + +float GarageMainScreen::GetCarRotationZ() { + eGarageType type = FEManager::Get()->GetGarageType(); + switch (type) { + case GARAGETYPE_CAR_LOT: + return 340.0f; + case GARAGETYPE_NONE: + case GARAGETYPE_MAIN_FE: + default: + return 304.96978759765625f; + case GARAGETYPE_CAREER_SAFEHOUSE: + return 304.96978759765625f; + case GARAGETYPE_CUSTOMIZATION_SHOP: + return 304.96978759765625f; + } +} + +float GarageMainScreen::GetGeometryZAngle() { + eGarageType type = FEManager::Get()->GetGarageType(); + switch (type) { + case GARAGETYPE_CAREER_SAFEHOUSE: + return 302.85308837890625f; + case GARAGETYPE_CUSTOMIZATION_SHOP: + case GARAGETYPE_CAR_LOT: + return 0.0f; + case GARAGETYPE_NONE: + case GARAGETYPE_MAIN_FE: + default: + return 134.41250610351562f; + } +} + +float GarageMainScreen::GetGeometryXPos() { + eGarageType type = FEManager::Get()->GetGarageType(); + switch (type) { + case GARAGETYPE_NONE: + case GARAGETYPE_MAIN_FE: + default: + return 0.0f; + case GARAGETYPE_CAREER_SAFEHOUSE: + return 0.0f; + case GARAGETYPE_CUSTOMIZATION_SHOP: + return 0.0f; + case GARAGETYPE_CAR_LOT: + return 0.0f; + } +} + +float GarageMainScreen::GetGeometryYPos() { + eGarageType type = FEManager::Get()->GetGarageType(); + switch (type) { + case GARAGETYPE_CAR_LOT: + return 0.07500000298023224f; + case GARAGETYPE_NONE: + case GARAGETYPE_MAIN_FE: + default: + return 0.0f; + case GARAGETYPE_CAREER_SAFEHOUSE: + return 0.0f; + case GARAGETYPE_CUSTOMIZATION_SHOP: + return 0.0f; + } +} + +float GarageMainScreen::GetGeometryZPos() { + eGarageType type = FEManager::Get()->GetGarageType(); + switch (type) { + case GARAGETYPE_NONE: + case GARAGETYPE_MAIN_FE: + default: + return 0.0f; + case GARAGETYPE_CAREER_SAFEHOUSE: + return 0.0f; + case GARAGETYPE_CUSTOMIZATION_SHOP: + return 0.0f; + case GARAGETYPE_CAR_LOT: + return 0.0f; + } +} + +// --- CarViewer --- + +GarageMainScreen *CarViewer::FindWhichScreenToUpdate(eCarViewerWhichCar which_car) { + cFEng *eng = cFEng::mInstance; + const char *name = lbl_GarageMain; + if (eng->IsPackagePushed(name)) { + return static_cast(FEngFindScreen(name)); + } + return nullptr; +} + +void CarViewer::SetRideInfo(RideInfo *ride, eSetRideInfoReasons reason, eCarViewerWhichCar which_car) { + GarageMainScreen *screen = FindWhichScreenToUpdate(which_car); + TopOrFullScreenRide = *ride; + TopOrFullScreenLoadingReason = reason; + if (screen) { + screen->SetRideInfo(&TopOrFullScreenRide, reason); + } +} + +void CarViewer::CancelCarLoad(eCarViewerWhichCar which_car) { + FindWhichScreenToUpdate(which_car)->CancelCarLoad(); +} + +RideInfo *CarViewer::GetRideInfo(eCarViewerWhichCar which_car) { + return &TopOrFullScreenRide; +} + +void CarViewer::HideAllCars() { + cFEng::mInstance->QueueGameMessage(0x0AD4BBDC, lbl_GarageMain, 0xFF); +} + +void CarViewer::ShowAllCars() { + cFEng::mInstance->QueueGameMessage(0x18883F75, lbl_GarageMain, 0xFF); +} + +void CarViewer::ShowCarScreen() { + if (!cFEng::mInstance->IsPackagePushed(lbl_GarageMain)) { + cFEng::mInstance->PushNoControlPackage(lbl_GarageMain, static_cast(100)); + } +} + +bool CarViewer::haveLoadedOnce; + +// --- Free functions --- + +static unsigned int FindScreenInfo(const char *pkg_name, int category) { + char name[128]; + char prefix[128]; + if (pkg_name) { + bStrCpy(name, pkg_name); + } else { + bStrCpy(name, ""); + } + int len = bStrLen(name); + if (len > 3) { + name[len - 4] = 0; + bMemSet(prefix, 0, 128); + unsigned int flags = FEDatabase->GetGameMode(); + if (flags & 0x20) { + bStrCat(prefix, "customize_", name); + if (category > -1) { + bSPrintf(prefix, "%s_%d", prefix, category); + } + unsigned int key = Attrib::StringToLowerCaseKey(prefix); + { + Attrib::Gen::frontend inst(Attrib::FindCollection(0x85885722, key), 0, nullptr); + bool hasCollection = inst.GetConstCollection() != 0; + if (hasCollection) { + return key; + } + if (category > -1) { + return FindScreenInfo(pkg_name, -1); + } + } + } else if (flags & 0x8000) { + bStrCat(prefix, "carlot_", name); + } else if (flags & 1) { + bStrCat(prefix, "career_", name); + } else if (flags & 4) { + if (flags & 0x400) { + bStrCat(prefix, "quickrace_", name); + } else { + bStrCat(prefix, "quickracemain_", name); + } + } else if (flags & 8) { + bStrCat(prefix, "quickracemain_", name); + } else if (flags & 0x40) { + bStrCat(prefix, "quickracemain_", name); + } else if (flags & 0x10) { + bStrCat(prefix, "options_", name); + } else if (flags & 0x100) { + bStrCat(prefix, "career_", "manager"); + } else { + bStrCat(prefix, "", name); + } + unsigned int key = Attrib::StringToLowerCaseKey(prefix); + { + Attrib::Gen::frontend inst(Attrib::FindCollection(0x85885722, key), 0, nullptr); + bool hasCollection = inst.GetConstCollection() != 0; + if (hasCollection) { + return key; + } + } + } + return 0x3b5aea62; +} + +static unsigned int FindGarageCameraInfo(const char *prefix) { + char buf[64]; + bStrCpy(buf, prefix); + const char *garage_name = GetCurrentGarageName(); + bStrCat(buf, buf, garage_name); + unsigned int key = Attrib::StringToLowerCaseKey(buf); + Attrib::Gen::frontend inst(Attrib::FindCollection(AttribGenFrontendClassKey(), key), 0, nullptr); + bool hasCollection = inst.GetConstCollection() != 0; + if (hasCollection) { + return key; + } + return 0xf907e767; +} + +static unsigned int FindScreenCameraInfo(unsigned int screen_key) { + Attrib::Gen::frontend inst(Attrib::FindCollection(0x85885722, screen_key), 0, nullptr); + bool hasCollection = inst.GetConstCollection() != 0; + unsigned int result = 0xf907e767; + if (hasCollection) { + Attrib::Gen::frontend cam_inst(reinterpret_cast(inst.GetLayoutPointer())->cam_angle, 0, nullptr); + result = cam_inst.GetCollection(); + } + return result; +} + +static unsigned int FindGarageEntryCameraInfo() { + return FindGarageCameraInfo("angle_entry_"); +} + +static unsigned int FindGarageFinalCameraInfo() { + return FindGarageCameraInfo("angle_final_"); +} + +// --- GarageCarLoader --- + +GarageCarLoader::GarageCarLoader() { + reinterpret_cast(_pad_ride0)->Init(CARTYPE_NONE, CarRenderUsage_Player, 0, 0); + reinterpret_cast(_pad_ride1)->Init(CARTYPE_NONE, CarRenderUsage_Player, 0, 0); + IsLoadingRide = false; + IsCurrentRide = false; + LoadingCar = 0; + CurrentCar = 0; + IsDifferent = false; + UseFirstDummyTexturesForNextLoad = true; +} + +GarageCarLoader::~GarageCarLoader() { + CleanUp(); +} + +void GarageCarLoader::LoadRideInfo(RideInfo *ride_info) { + if (IsLoadingRide) { + TheCarLoader.Unload(LoadingCar); + } + int dummy_texture_number = 1; + if (UseFirstDummyTexturesForNextLoad == 0) { + dummy_texture_number = 2; + } + ride_info->SetCompositeNameHash(dummy_texture_number); + LoadingCar = TheCarLoader.Load(ride_info); + TheCarLoader.BeginLoading(nullptr, 0); + IsLoadingRide = true; + *reinterpret_cast(_pad_ride0) = *ride_info; + IsDifferent = false; +} + +RideInfo *GarageCarLoader::GetLoadingRideInfo() { + if (IsLoadingRide) { + return reinterpret_cast(_pad_ride0); + } + return nullptr; +} + +void GarageCarLoader::Update() { + if (IsLoadingRide && TheCarLoader.IsLoaded(LoadingCar)) { + if (IsCurrentRide) { + TheCarLoader.Unload(CurrentCar); + } + IsCurrentRide = true; + CurrentCar = LoadingCar; + *reinterpret_cast(_pad_ride1) = *reinterpret_cast(_pad_ride0); + IsDifferent = true; + LoadingCar = 0; + IsLoadingRide = false; + UseFirstDummyTexturesForNextLoad = (UseFirstDummyTexturesForNextLoad != 1); + } +} + +GarageCarLoader *GetGarageCarLoader() { + static GarageCarLoader TheGarageCarLoader; + return &TheGarageCarLoader; +} + +void GarageCarLoader::Init() { + LoadingCar = 0; + CurrentCar = 0; + IsLoadingRide = false; + IsCurrentRide = false; +} + +void GarageCarLoader::Switch() { + IsDifferent = false; +} + +RideInfo *GarageCarLoader::GetCurrentRideInfo() { + if (IsCurrentRide) { + return reinterpret_cast(&_pad_ride1[0]); + } + return nullptr; +} + +void GarageCarLoader::CancelCarLoad() { + if (IsLoadingRide) { + TheCarLoader.Unload(LoadingCar); + } +} + +void GarageCarLoader::CleanUp() { + if (IsLoadingRide && LoadingCar) { + TheCarLoader.Unload(LoadingCar); + } + if (IsCurrentRide && CurrentCar) { + TheCarLoader.Unload(CurrentCar); + } + LoadingCar = 0; + CurrentCar = 0; + IsLoadingRide = false; + IsCurrentRide = false; +} + +void InitGarageCarLoaders() { + GetGarageCarLoader()->Init(); +} + +void CleanUpGarageCarLoaders() { + GetGarageCarLoader()->CleanUp(); +} + +void UpdateGarageCarLoaders() { + GetGarageCarLoader()->Update(); +} + +MenuScreen *CreateGarageMainScreen(ScreenConstructorData *sd) { + return new GarageMainScreen(sd, 1, &TopOrFullScreenRide, 0); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.hpp index 04a0d5af3..002a4963f 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.hpp @@ -1,10 +1,82 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_FEPKG_GARAGEMAIN_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_FEPKG_GARAGEMAIN_H - #ifdef EA_PRAGMA_ONCE_SUPPORTED #pragma once #endif - - - +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include +struct bMatrix4; +struct eView; +struct eModel; +struct FrontEndRenderingCar; +struct ActionQueue; +struct FEString; +struct SelectCarCameraMover; +struct GarageCarLoader; +namespace Attrib { namespace Gen { struct frontend; } } +struct FEGeometryModels { + FEGeometryModels() {} + void Init(char *filterPrefix); + void UnInit(); + void Render(eView *view, bMatrix4 *local, unsigned int render_flags); + unsigned int mModelCastsShadowMapFlags; // offset 0x0, size 0x4 + unsigned int mModelCurrGenOnly; // offset 0x4, size 0x4 + unsigned int mModelNextGenOnly; // offset 0x8, size 0x4 + int mNumModels; // offset 0xC, size 0x4 + eModel *mModels; // offset 0x10, size 0x4 +}; +struct GarageMainScreen : public MenuScreen { + GarageMainScreen(ScreenConstructorData *sd, int eview_id, RideInfo *start_ride, int player); + ~GarageMainScreen() override; + void NotificationMessage(unsigned long Message, FEObject *pObject, unsigned long Param1, unsigned long Param2) override; + void RequestCameraPush() {} + void CancelCameraPush() {} + void SetCustomizationCategory(int category) { mCustomizationCategory = category; } + bool IsVisable() {} + static GarageMainScreen *GetInstance(); + void EnableCarRendering(); + void DisableCarRendering(); + bool IsCarRendering(); + void HandleTick(unsigned long msg); + void SetRideInfo(RideInfo *ride, eSetRideInfoReasons reason); + void CancelCarLoad(); + void UpdateCurrentCameraView(bool bForce); + void RefreshBackground(); + static void BackgroundLoaded(int param); + float GetCarRotationX(); + float GetCarRotationY(); + float GetCarRotationZ(); + float GetGeometryZAngle(); + float GetGeometryXPos(); + float GetGeometryYPos(); + float GetGeometryZPos(); + void UpdateRenderingCarParameters(FrontEndRenderingCar *fe_car); + void HandleRender(unsigned int render_flags); + void HandleShowPackage(unsigned int msg); + void HandleHidePackage(unsigned int msg); + void HandleJoyEvents(); + static GarageMainScreen *sInstance; + GarageCarLoader *TheGarageCarLoader; // offset 0x2C, size 0x4 + eSetRideInfoReasons LoadingReason; // offset 0x30, size 0x4 + FrontEndRenderingCar *RenderingCar; // offset 0x34, size 0x4 + FEGeometryModels mGeometryModels; // offset 0x38, size 0x14 + SelectCarCameraMover *pCameraMover; // offset 0x4C, size 0x4 + int Player; // offset 0x50, size 0x4 + int CarState; // offset 0x54, size 0x4 + bool CameraPushRequested; // offset 0x58, size 0x1 + unsigned int DesiredCamKey; // offset 0x5C, size 0x4 + ActionQueue *mActionQ[2]; // offset 0x60, size 0x8 + FEString *pCarName; // offset 0x68, size 0x4 + FEString *pPlayerName; // offset 0x6C, size 0x4 + int HideEntireScreen; // offset 0x70, size 0x4 + int ViewID; // offset 0x74, size 0x4 + unsigned int mScreenKeyCamIsSetTo; // offset 0x78, size 0x4 + bool bUserRotate; // offset 0x7C, size 0x1 + float mOrbitV; // offset 0x80, size 0x4 + float mOrbitH; // offset 0x84, size 0x4 + float mZoom; // offset 0x88, size 0x4 + int mCustomizationCategory; // offset 0x8C, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.cpp index e69de29bb..debc13221 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.cpp @@ -0,0 +1,186 @@ +#include "uiCareerMain.hpp" + +#include "Speed/Indep/Src/FEng/FEList.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Misc/Config.h" + +// GarageMainScreen already defined in uiMain.cpp (earlier in TU) + +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int language); +unsigned char FEngGetLastButton(const char* pkg_name); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, + bool start_at_beginning); +const char* GetLocalizedString(unsigned int hash); +int FEngMapJoyParamToJoyport(int feng_param); +void MemcardEnter(const char* from, const char* to, unsigned int op, void (*pTermFunc)(void*), + void* pTermFuncParam, unsigned int msgSuccess, unsigned int msgFailed); +int GetCurrentLanguage(); + +class RaceStarter { + public: + static void StartCareerFreeRoam(); +}; + +extern bool SkipDDayRaces; +extern unsigned int iCurrentViewBin; + +uiCareerCrib::uiCareerCrib(ScreenConstructorData* sd) + : IconScrollerMenu(sd) { + Setup(); +} + +uiCareerCrib::~uiCareerCrib() {} + +void uiCareerCrib::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, + unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + + switch (msg) { + case 0x1265ECE9: + GarageMainScreen::GetInstance()->UpdateCurrentCameraView(false); + return; + case 0xE1FDE1D1: + if (PrevButtonMessage != 0x911AB364) { + return; + } + FEManager::Get()->SetGarageType(GARAGETYPE_MAIN_FE); + FEDatabase->ClearGameMode(eFE_GAME_MODE_CAREER); + if (IsMemcardEnabled) { + FEDatabase->SetGameMode(eFE_GAME_MODE_CAREER_MANAGER); + cFEng::Get()->QueuePackageSwitch(GetPackageName(), 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("MainMenu.fng", 0, 0, false); + } + return; + case 0xD05FC3A3: { + bool dday_flow_completed = false; + if (!SkipDDayRaces) { + GRaceParameters* parms = + GRaceDatabase::Get().GetRaceFromHash(Attrib::StringHash32(GRaceDatabase::Get().GetDDayEndRace())); + dday_flow_completed = + GRaceDatabase::Get().CheckRaceScoreFlags(parms->GetEventHash(), GRaceDatabase::kCompleted_ContextCareer); + } else { + dday_flow_completed = true; + } + + if (dday_flow_completed) { + RaceStarter::StartCareerFreeRoam(); + } else { + const char* firstDDayRace; + if (!SkipDDayRaces) { + firstDDayRace = GRaceDatabase::Get().GetNextDDayRace(); + } else { + firstDDayRace = GRaceDatabase::Get().GetDDayEndRace(); + } + GRaceParameters* parms = + GRaceDatabase::Get().GetRaceFromHash(Attrib::StringHash32(firstDDayRace)); + GRaceCustom* race = GRaceDatabase::Get().AllocCustomRace(parms); + GRaceDatabase::Get().SetStartupRace(race, kRaceContext_Career); + GRaceDatabase::Get().FreeCustomRace(race); + RaceStarter::StartCareerFreeRoam(); + } + FEDatabase->SetGameMode(eFE_GAME_MODE_CAREER); + return; + } + case 0x34DC1BCF: + return; + } +} + +void uiCareerCrib::Setup() { + CResumeFreeRoam* resumeFreeRoam = new CResumeFreeRoam(0x12BB5EA2, 0x1BD185C, 0); + resumeFreeRoam->SetReactImmediately(true); + AddOption(resumeFreeRoam); + + AddOption(new CTop15(0x2C14AC23, 0x80B9FF9B, 0)); + AddOption(new CCarSelect(0xC6A1A6E0, 0xD5F627, 0)); + + if (FEDatabase->GetCareerSettings()->HasRapSheet()) { + AddOption(new CRapSheet(0x2FD8B206, 0xAC22F27E, 0)); + } + + if (IsMemcardEnabled) { + CSave* save = new CSave(0x228B7E32, 0x1C8ACE, 0); + save->SetReactImmediately(true); + AddOption(save); + } + + int lastButton = FEngGetLastButton(GetPackageName()); + SetInitialOption(lastButton); + + FEngSetLanguageHash(GetPackageName(), 0x3C458C1, 0xE596C4A3); + FEngSetLanguageHash(GetPackageName(), 0xB5C74226, 0xE596C4A3); + + const char* szPercentUnit = "%"; + eLanguages currLang = static_cast(GetCurrentLanguage()); + if (currLang == eLANGUAGE_DANISH || currLang == eLANGUAGE_FINNISH || + currLang == eLANGUAGE_FRENCH || currLang == eLANGUAGE_GERMAN || + currLang == eLANGUAGE_SWEDISH) { + szPercentUnit = "%%"; + } + + unsigned int hash = FEHashUpper("ICON_GROUP"); + unsigned int hash2 = FEHashUpper("CAREER_CRIB"); + FEngSetScript(GetPackageName(), hash, hash2, true); + + GameCompletionStats stats = FEDatabase->GetGameCompletionStats(); + FEPrintf(GetPackageName(), static_cast(FEHashUpper("GAME_COMPLETE")), "%d%s", stats.m_nCareer, szPercentUnit); + + FEPrintf(GetPackageName(), static_cast(FEHashUpper("TOTAL_BOUNTY")), "%d", + FEDatabase->GetPlayerCarStable(0)->GetTotalBounty()); + + FEPrintf(GetPackageName(), static_cast(FEHashUpper("TOTAL_CASH")), "%d", + FEDatabase->GetCareerSettings()->GetCash()); + + RefreshHeader(); + FEDatabase->RefreshCurrentRide(); +} + +void CResumeFreeRoam::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + cFrontendDatabase* db = FEDatabase; + signed char port = FEngMapJoyParamToJoyport(param1); + db->SetPlayersJoystickPort(0, port); + const char* blurb = GetLocalizedString(0xEB694C0C); + DialogInterface::ShowTwoButtons(pkg_name, "", static_cast(1), 0x70E01038, + 0x417B25E4, 0xD05FC3A3, 0x34DC1BCF, 0x34DC1BCF, + static_cast(1), blurb); + } +} + +void CCarSelect::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + cFEng::Get()->QueuePackageSwitch("IG_CarLot.fng", 0, 0, false); + } +} + +void CRapSheet::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + FEDatabase->SetGameMode(eFE_GAME_MODE_RAP_SHEET); + cFEng::Get()->QueuePackageSwitch("RapSheetMain.fng", 0, 0, false); + } +} + +void CTop15::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + iCurrentViewBin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + cFEng::Get()->QueuePackageSwitch("WorldMap_Main.fng", 0, 0, false); + } +} + +void CSave::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + MemcardEnter(pkg_name, pkg_name, 0x2251, 0, 0, 0, 0); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.hpp index aa5d2b977..79e77f403 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.hpp @@ -5,6 +5,69 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" +struct uiCareerCrib : public IconScrollerMenu { + uiCareerCrib(ScreenConstructorData* sd); + ~uiCareerCrib() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; +}; + +struct CResumeFreeRoam : public IconOption { + CResumeFreeRoam(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~CResumeFreeRoam() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct CCarSelect : public IconOption { + CCarSelect(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~CCarSelect() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct CRapSheet : public IconOption { + CRapSheet(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~CRapSheet() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct CTop15 : public IconOption { + CTop15(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~CTop15() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct CSave : public IconOption { + CSave(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~CSave() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct CResumeCareer : public IconOption { + CResumeCareer(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~CResumeCareer() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct CStartNewCareer : public IconOption { + CStartNewCareer(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~CStartNewCareer() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +struct CLoadCareer : public IconOption { + CLoadCareer(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~CLoadCareer() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.cpp index e69de29bb..3b013c93e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.cpp @@ -0,0 +1,149 @@ +#include "uiCareerManager.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerMain.hpp" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Misc/Config.h" + +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int language); +unsigned char FEngGetLastButton(const char* pkg_name); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, + bool start_at_beginning); +const char* GetLocalizedString(unsigned int hash); +int FEngMapJoyParamToJoyport(int feng_param); +void MemcardEnter(const char* from, const char* to, unsigned int op, void (*pTermFunc)(void*), + void* pTermFuncParam, unsigned int msgSuccess, unsigned int msgFailed); + +uiCareerManager::uiCareerManager(ScreenConstructorData* sd) + : IconScrollerMenu(sd) { + Setup(); +} + +uiCareerManager::~uiCareerManager() {} + +void uiCareerManager::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, + unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + + switch (msg) { + case 0x1265ECE9: + GarageMainScreen::GetInstance()->UpdateCurrentCameraView(false); + break; + case 0xE1FDE1D1: + if (PrevButtonMessage == 0x911AB364) { + if (FEDatabase->GetCareerSettings()->IsGameOver()) { + cFEng::Get()->QueuePackageSwitch(GetPackageName(), 0, 0, false); + } else { + FEDatabase->ClearGameMode(eFE_GAME_MODE_CAREER_MANAGER); + cFEng::Get()->QueuePackageSwitch("MainMenu.fng", 0, 0, false); + } + } + break; + case 0x7E998E5E: + FEDatabase->RefreshCurrentRide(); + break; + } +} + +void uiCareerManager::Setup() { + IconOption* pLoadOption; + + if (FEDatabase->GetCareerSettings()->HasCareerStarted()) { + if (!FEDatabase->GetCareerSettings()->IsGameOver()) { + AddOption(new CResumeCareer(0xC1C089CE, 0xE072DB21, 0)); + } + + CStartNewCareer* startNew = new CStartNewCareer(0xE7353BE7, 0x17E18F87, 0); + startNew->SetReactImmediately(true); + AddOption(startNew); + } else { + CStartNewCareer* startNew = new CStartNewCareer(0xE7353BE7, 0x6005281E, 0); + startNew->SetReactImmediately(true); + AddOption(startNew); + } + + CLoadCareer* loadCareer = new CLoadCareer(0x2287E063, 0x18ECFF, 0); + loadCareer->SetReactImmediately(true); + AddOption(loadCareer); + pLoadOption = loadCareer; + + if (FEDatabase->GetCareerSettings()->IsGameOver()) { + int index = Options.GetOptionIndex(pLoadOption); + if (bFadeInIconsImmediately) { + Options.StartFadeIn(); + } + Options.SetInitialPos(index); + } else { + int lastButton = FEngGetLastButton(GetPackageName()); + if (bFadeInIconsImmediately) { + Options.StartFadeIn(); + } + Options.SetInitialPos(lastButton); + } + + FEngSetLanguageHash(GetPackageName(), 0x3C458C1, 0x8FFF61F2); + FEngSetLanguageHash(GetPackageName(), 0xB5C74226, 0x8FFF61F2); + + if (FEDatabase->bProfileLoaded) { + FEngSetScript(GetPackageName(), 0xC87422F7, 0x1CA7C0, true); + FEPrintf(GetPackageName(), 0xEB406FEC, "%s", + FEDatabase->GetUserProfile(0)->GetProfileName()); + } + + RefreshHeader(); + FEDatabase->RefreshCurrentRide(); +} + +void CResumeCareer::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + bool should_go_into_epic_pursuit = false; + cFrontendDatabase* db = FEDatabase; + signed char port = FEngMapJoyParamToJoyport(param1); + db->SetPlayersJoystickPort(0, port); + FEDatabase->GetCareerSettings()->ResumeCareer(); + + if (!FEDatabase->GetCareerSettings()->HasBeatenCareer()) { + GRaceDatabase &rdb = GRaceDatabase::Get(); + GRaceParameters* parms = rdb.GetRaceFromName(rdb.GetFinalBossRace()); + if (rdb.IsCareerRaceComplete(parms->GetEventHash())) + should_go_into_epic_pursuit = true; + } + + if (FEDatabase->GetCareerSettings()->GetCurrentBin() != 16 && + !should_go_into_epic_pursuit) { + GManager::Get().SetStartingFreeRoamFromSafeHouse(); + cFEng::Get()->QueuePackageSwitch("IG_SafehouseMain.fng", 0, 0, false); + } + } +} + +void CStartNewCareer::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + cFrontendDatabase* db = FEDatabase; + signed char port = FEngMapJoyParamToJoyport(param1); + db->SetPlayersJoystickPort(0, port); + + if (FEDatabase->GetCareerSettings()->HasCareerStarted() || + !FEDatabase->bProfileLoaded) { + MemcardEnter(pkg_name, pkg_name, 0x80063, 0, 0, 0, 0); + } else { + FEDatabase->GetCareerSettings()->StartNewCareer(true); + cFEng::Get()->QueuePackageSwitch(pkg_name, 0, 0, false); + } + } +} + +void CLoadCareer::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + cFrontendDatabase* db = FEDatabase; + signed char port = FEngMapJoyParamToJoyport(param1); + db->SetPlayersJoystickPort(0, port); + MemcardEnter(pkg_name, pkg_name, 0x413, 0, 0, 0x7E998E5E, 0x8867412D); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.hpp index 25208aa69..fbd2c533f 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiCareerManager.hpp @@ -5,6 +5,13 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" +struct uiCareerManager : public IconScrollerMenu { + uiCareerManager(ScreenConstructorData* sd); + ~uiCareerManager() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiInfractions.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiInfractions.cpp index e69de29bb..0a51cedeb 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiInfractions.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiInfractions.cpp @@ -0,0 +1,236 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" + +struct FECareerRecord; +void eUnloadStreamingTexture(unsigned int *textures, int count); +void WaitForResourceLoadingComplete(); +FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +void FEngSetVisible(FEObject *obj); +extern void FEngSetButtonState(const char *pkg_name, unsigned int button_hash, bool enabled); +extern void eLoadStreamingTexture(unsigned int *textures, int count, void (*callback)(unsigned int), unsigned int param0, int priority); +extern void FEngSetCurrentButton(const char *pkg_name, unsigned int button_hash); + +inline void FEngSetInvisible(const char *pkg_name, unsigned int obj_hash) { + FEngSetInvisible(FEngFindObject(pkg_name, obj_hash)); +} + +inline void FEngDisableButton(const char *pkg_name, unsigned int button_hash) { + FEngSetButtonState(pkg_name, button_hash, false); +} + +struct PostPursuitInfractionsScreen : MenuScreen { + PostPursuitInfractionsScreen(ScreenConstructorData *sd); + ~PostPursuitInfractionsScreen() override; + static MenuScreen *Create(ScreenConstructorData *sd); + void NotificationMessage(unsigned long, FEObject *, unsigned long, unsigned long) override; + void NotifyBustedTextureLoaded(); + unsigned int CalcBustedTexture(); + static void TextureLoadedCallback(unsigned int arg) { + reinterpret_cast(arg)->NotifyBustedTextureLoaded(); + } + + FECareerRecord *WorkingCareerRecord; // offset 0x2C + bool bStrikeLimitReached; // offset 0x30 + int AmountToPay; // offset 0x34 + int AmountPlayerHas; // offset 0x38 + bool bHasMarker; // offset 0x3C + unsigned int BustedTexture; // offset 0x40 + bool bFirstTimeBusted; // offset 0x44 +}; + +MenuScreen *PostPursuitInfractionsScreen::Create(ScreenConstructorData *sd) { + return new ("", 0) PostPursuitInfractionsScreen(sd); +} + +PostPursuitInfractionsScreen::~PostPursuitInfractionsScreen() { + eUnloadStreamingTexture(BustedTexture); + WaitForResourceLoadingComplete(); +} + +void PostPursuitInfractionsScreen::NotifyBustedTextureLoaded() { + FEngSetVisible(FEngFindObject(GetPackageName(), 0x2347122A)); +} + +extern eLanguages GetCurrentLanguage(); + +unsigned int PostPursuitInfractionsScreen::CalcBustedTexture() { + switch (GetCurrentLanguage()) { + case 1: + return 0xb419f122; + case 2: + return 0xb419f3c3; + case 3: + return 0xb419fe23; + case 4: + return 0xb41a2829; + case 5: + return 0xb419e912; + case 6: + return 0xb41a2914; + case 7: + return 0xb419e678; + case 8: + return 0xb41a0611; + case 9: + return 0xb419e319; + case 10: + return 0xb41a0000; + case 11: + return 0xb41a2b62; + case 12: + return 0xb41a1b50; + case 13: + return 0xb419f002; + default: + return 0xb419ec5f; + } +} + +PostPursuitInfractionsScreen::PostPursuitInfractionsScreen(ScreenConstructorData *sd) + : MenuScreen(sd) +{ + WorkingCareerRecord = nullptr; + bStrikeLimitReached = false; + BustedTexture = 0; + bFirstTimeBusted = false; + + if (!FEDatabase->GetCareerSettings()->HasBeenBustedOnce()) { + if (TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_GET_OUT_OF_JAIL, 0) < 1) { + for (int i = 0; i < 4; i++) { + TheFEMarkerManager.AddMarkerToInventory(FEMarkerManager::MARKER_GET_OUT_OF_JAIL, 0); + } + } + bFirstTimeBusted = true; + } + + FEDatabase->GetCareerSettings()->SetBeenBustedOnce(); + + const int FEObj_BustedStamp = 0x2347122A; + FEngSetInvisible(GetPackageName(), FEObj_BustedStamp); + + BustedTexture = CalcBustedTexture(); + FEngSetTextureHash(GetPackageName(), FEObj_BustedStamp, BustedTexture); + eLoadStreamingTexture(BustedTexture, TextureLoadedCallback, reinterpret_cast(this), 0); + + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *record = stable->GetCarRecordByHandle(FEDatabase->GetCareerSettings()->GetCurrentCar()); + WorkingCareerRecord = stable->GetCareerRecordByHandle(record->CareerHandle); + + FEInfractionsData scott_says_i_should_call_this_previous_infractions_and_phil_needs_to_have_it_spelled_correctly(GInfractionManager::Get().GetInfractions()); + + int this_pursuit_cost = scott_says_i_should_call_this_previous_infractions_and_phil_needs_to_have_it_spelled_correctly.GetFineValue(); + const int FEObj_THISPURSUITCOST = 0xBD66334A; + FEPrintf(GetPackageName(), FEObj_THISPURSUITCOST, "%$d", this_pursuit_cost); + + int num_infractions_pursuit = scott_says_i_should_call_this_previous_infractions_and_phil_needs_to_have_it_spelled_correctly.NumInfractions(); + const int FEObj_NUMBEROFINFRACTIONSTHISPURSUIT = 0xB967F64D; + FEPrintf(GetPackageName(), FEObj_NUMBEROFINFRACTIONSTHISPURSUIT, "%d", num_infractions_pursuit); + + int infraction_total_cost = WorkingCareerRecord->GetInfractions(true).GetFineValue(); + const int FEObj_UNSERVEDINFRACTIONSCOST = 0xA4C79522; + FEPrintf(GetPackageName(), FEObj_UNSERVEDINFRACTIONSCOST, "%$d", infraction_total_cost - this_pursuit_cost); + + int total_unserved_number = WorkingCareerRecord->GetInfractions(true).NumInfractions(); + const int FEObj_NUMBEROFINFRACTIONSUNSERVED = 0x5344F2A6; + FEPrintf(GetPackageName(), FEObj_NUMBEROFINFRACTIONSUNSERVED, "%d", total_unserved_number - num_infractions_pursuit); + + bHasMarker = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_GET_OUT_OF_JAIL, 0) > 0; + + FEPrintf(GetPackageName(), 0x5B875870, "%d", TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_GET_OUT_OF_JAIL, 0)); + FEPrintf(GetPackageName(), 0xEA8AECD9, "%d", TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_GET_OUT_OF_JAIL, 0)); + + if (!bHasMarker) { + const int FEObj_Button1Text = 0xF9363F30; + const int GREY = 0x163C76; + const int FEObj_MARKER = 0x6B6973C1; + const int FEObj_Button1 = 0xB8A7C6CC; + FEngSetScript(GetPackageName(), FEObj_Button1Text, GREY, true); + FEngSetScript(GetPackageName(), FEObj_MARKER, GREY, true); + FEngSetScript(GetPackageName(), 0x39F11E5C, GREY, true); + FEngDisableButton(GetPackageName(), FEObj_Button1); + } else { + const int FEObj_NORMAL = 0x6EBBFB68; + FEngSetScript(GetPackageName(), 0x39F11E5C, FEObj_NORMAL, true); + } + + AmountToPay = WorkingCareerRecord->GetInfractions(true).GetFineValue(); + const int FEObj_TOTALCOSTDATA = 0x854AF1F4; + FEPrintf(GetPackageName(), FEObj_TOTALCOSTDATA, "%$d", AmountToPay); + + AmountPlayerHas = FEDatabase->GetCareerSettings()->GetCash(); + const int FEObj_CASHDATA = 0x1930B057; + FEPrintf(GetPackageName(), FEObj_CASHDATA, "%$d", AmountPlayerHas); +} + +void PostPursuitInfractionsScreen::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x35f8620b: { + const int FEObj_Button2 = 0xB8A7C6CC; + const int FEObj_Button1 = 0xB8A7C6CD; + if (bFirstTimeBusted) { + FEngSetCurrentButton(GetPackageName(), FEObj_Button2); + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), 0x417b2601, 0xb4edeb6d, 0x9c14b5f1); + } else { + FEngSetCurrentButton(GetPackageName(), FEObj_Button1); + } + break; + } + case 0x0c407210: { + bool paid_with_cash = (pobj->NameHash == 0xB8A7C6CD); + bool paid_with_marker = (pobj->NameHash == 0xB8A7C6CC); + bool not_enough_cash; + bool busted_by_cross; + + if (paid_with_cash) { + FEDatabase->GetCareerSettings()->SpendCash(AmountToPay); + bStrikeLimitReached = WorkingCareerRecord->TheImpoundData.NotifyBusted(); + WorkingCareerRecord->ServeAllIncractions(); + WorkingCareerRecord->SetVehicleHeat(1.0f); + } else if (paid_with_marker) { + TheFEMarkerManager.UtilizeMarker(FEMarkerManager::MARKER_GET_OUT_OF_JAIL, 0); + int num_markers = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_GET_OUT_OF_JAIL, 0); + FEPrintf(GetPackageName(), 0x5b875870, "%d", num_markers); + FEPrintf(GetPackageName(), 0xea8aecd9, "%d", num_markers); + if (num_markers <= 0) { + const int GREY = 0x163c76; + const int FEObj_MARKER = 0x6b6973c1; + FEngSetScript(GetPackageName(), FEObj_MARKER, GREY, true); + FEngSetScript(GetPackageName(), 0x39f11e5c, GREY, true); + } + WorkingCareerRecord->WaiveIncractions(GInfractionManager::Get().GetInfractions()); + } + + not_enough_cash = false; + if (!paid_with_marker) { + not_enough_cash = (AmountToPay > AmountPlayerHas); + } + + FEImpoundData::eImpoundReasons impound_reason = FEImpoundData::IMPOUND_REASON_NONE; + unsigned int message_hash = 0; + if (bStrikeLimitReached) { + message_hash = 0x78f0e298; + impound_reason = FEImpoundData::IMPOUND_REASON_STRIKE_LIMIT_REACHED; + } else if (not_enough_cash) { + message_hash = 0x1ecffa6e; + impound_reason = FEImpoundData::IMPOUND_REASON_INSUFFICIENT_FUNDS; + } + + if (message_hash != 0) { + WorkingCareerRecord->TheImpoundData.BecomeImpounded(impound_reason); + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), 0x417b2601, 0x34dc1bec, message_hash); + } else { + if (paid_with_marker) { + cFEng::Get()->QueuePackageSwitch("Car_Select.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("Car_Select.fng", 0x100, 0, false); + } + } + break; + } + case 0x34dc1bec: + cFEng::Get()->QueuePackageSwitch("Car_Select.fng", 0x200, 0, false); + break; + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.cpp index e69de29bb..57abb4415 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.cpp @@ -0,0 +1,264 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +extern cFrontendDatabase *FEDatabase; +extern unsigned int FEngHashString(const char *, ...); +extern void FEngSetScript(FEObject *, unsigned int, bool); +extern void FEngSetCurrentButton(const char *, unsigned int); +extern FEObject *FEngGetCurrentButton(const char *); +extern bool DoesCategoryHaveNewUnlock(eUnlockableEntity); +extern void GetLocalizedString(char *buffer, unsigned int bufsize, unsigned int string_label); + +// total size: 0x1C +struct MarkerSelectInfo { + FEMarkerManager::ePossibleMarker Marker; // offset 0x0 + unsigned int IconHash; // offset 0x4 + unsigned int CategoryIconHash; // offset 0x8 + unsigned int NameHash; // offset 0xC + unsigned int CategoryNameHash; // offset 0x10 + unsigned int BlurbHash; // offset 0x14 + unsigned int CategoryBlurbHash; // offset 0x18 +}; + +extern MarkerSelectInfo MarkerSelectInfos[]; + +MarkerSelectInfo *GetMarkerSelectInfo(FEMarkerManager::ePossibleMarker marker) { + for (int i = 0; i < 0x15; i++) { + if (MarkerSelectInfos[i].Marker == marker) { + return &MarkerSelectInfos[i]; + } + } + return nullptr; +} + +unsigned int FEMarkerSelection::GetIconHashForType(FEMarkerManager::ePossibleMarker marker) { + MarkerSelectInfo *info = GetMarkerSelectInfo(marker); + return info->IconHash; +} + +unsigned int FEMarkerSelection::GetCategoryIconHashForType(FEMarkerManager::ePossibleMarker marker) { + MarkerSelectInfo *info = GetMarkerSelectInfo(marker); + return info->CategoryIconHash; +} + +unsigned int FEMarkerSelection::GetNameHashForType(FEMarkerManager::ePossibleMarker marker) { + MarkerSelectInfo *info = GetMarkerSelectInfo(marker); + return info->NameHash; +} + +unsigned int FEMarkerSelection::GetCategoryNameHashForType(FEMarkerManager::ePossibleMarker marker) { + MarkerSelectInfo *info = GetMarkerSelectInfo(marker); + return info->CategoryNameHash; +} + +unsigned int FEMarkerSelection::GetBlurbHashForType(FEMarkerManager::ePossibleMarker marker) { + MarkerSelectInfo *info = GetMarkerSelectInfo(marker); + return info->BlurbHash; +} + +unsigned int FEMarkerSelection::GetCategoryBlurbHashForType(FEMarkerManager::ePossibleMarker marker) { + MarkerSelectInfo *info = GetMarkerSelectInfo(marker); + return info->CategoryBlurbHash; +} + +int FEMarkerSelection::GetNumSelected() { + int count = 0; + for (int i = 0; i < NumVisibleMarkers; i++) { + if (TheMarkers[i].Marker != static_cast(0) && TheMarkers[i].Selected) { + count++; + } + } + return count; +} + +int FEMarkerSelection::GetButtonIndex(unsigned int hash) { + if (hash == 0xcda0a66b) return 0; + if (hash == 0xcda0a66c) return 1; + if (hash == 0xcda0a66d) return 2; + if (hash == 0xcda0a66e) return 3; + if (hash == 0xcda0a66f) return 4; + if (hash == 0xcda0a670) return 5; + return 0; +} + +int FEMarkerSelection::GetSelectedButtonIndex() { + FEObject *btn = FEngGetCurrentButton(GetPackageName()); + if (btn) { + return GetButtonIndex(btn->NameHash); + } + return 0; +} + +FEMarkerSelection::FEMarkerSelection(ScreenConstructorData *sd) + : MenuScreen(sd) // + , NumVisibleMarkers(0) // + , RivalStreamer(sd->PackageFilename, false) { + unsigned int CategoryOrder[] = {0xbdaa5794, 0xe69d4f7c, 0x73272ed2, 0xc61c8d3a}; + for (int cat = 0; cat < 4; cat++) { + unsigned int categoryHash = CategoryOrder[cat]; + for (int j = 0; j < 6; j++) { + FEMarkerManager::ePossibleMarker marker = static_cast(0); + int param = 0; + TheFEMarkerManager.GetMarkerForLaterSelection(j, marker, param); + if (marker != static_cast(0) && + categoryHash == GetCategoryIconHashForType(marker)) { + TheMarkers[NumVisibleMarkers].Marker = marker; + TheMarkers[NumVisibleMarkers].Param = param; + TheMarkers[NumVisibleMarkers].Selected = false; + NumVisibleMarkers++; + } + } + } + + for (int i = 0; i < 3; i++) { + int r = bRandom(3); + Selection temp = TheMarkers[i]; + TheMarkers[i] = TheMarkers[r]; + TheMarkers[r] = temp; + } + + pRivalImg = FEngFindImage(GetPackageName(), 0xc1f62308); + pTagImg = FEngFindImage(GetPackageName(), 0xf5a2a087); + pBGImg = FEngFindImage(GetPackageName(), 0x2cbe1dd0); + + RivalStreamer.Init(FEDatabase->GetCareerSettings()->GetCurrentBin() + 1, pRivalImg, pTagImg, pBGImg); + Redraw(); + FEngSetLanguageHash(GetPackageName(), 0xbdb541b3, 0x9a375734); + FEngSetLanguageHash(GetPackageName(), 0x7603f3d5, 0x9a375734); + SetUnlockIcon(static_cast(1), 0x9f04347d); + SetUnlockIcon(static_cast(2), 0x5b032d25); + SetUnlockIcon(static_cast(3), 0x96b11f47); + SetUnlockIcon(static_cast(0), 0x7f8aaf09); +} + + +void FEMarkerSelection::SetUnlockIcon(eUnlockableEntity ent, unsigned int message) { + if (ent == static_cast(0)) { + bool found = false; + FEPlayerCarDB *carDB = FEDatabase->GetPlayerCarStable(0); + for (int i = 0; i < 200; i++) { + FECarRecord *car = carDB->GetCarByIndex(i); + if (car && car->IsValid()) { + Attrib::Gen::frontend fe(car->FEKey, 0, nullptr); + found = (fe.UnlockedAt() == FEDatabase->GetCareerSettings()->GetCurrentBin()) || found; + } + } + if (found) { + cFEng::Get()->QueuePackageMessage(message, GetPackageName(), nullptr); + } + } else { + if (DoesCategoryHaveNewUnlock(ent)) { + cFEng::Get()->QueuePackageMessage(message, GetPackageName(), nullptr); + } + } +} + +void FEMarkerSelection::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0xe1fde1d1: + TheFEMarkerManager.ClearMarkersForLaterSelection(); + uiRepSheetRivalFlow::Get()->Next(); + break; + case 0x35f8620b: + FEngSetCurrentButton(GetPackageName(), 0xcda0a66b); + break; + case 0x0c407210: { + if (GetNumSelected() < 2) { + int idx = GetSelectedButtonIndex(); + if (TheMarkers[idx].Selected) break; + FEngSetScript(pobj, 0x15970a, true); + TheMarkers[idx].Selected = true; + switch (static_cast(TheMarkers[idx].Marker)) { + case 0x12: + FEDatabase->GetPlayerCarStable(0)->AwardRivalCar(TheMarkers[idx].Param); + break; + case 0x13: + FEDatabase->GetCareerSettings()->AddCash(TheMarkers[idx].Param); + break; + default: + TheFEMarkerManager.AddMarkerToInventory(TheMarkers[idx].Marker, TheMarkers[idx].Param); + break; + } + if (GetNumSelected() >= 2) { + FEngSetLanguageHash(GetPackageName(), 0xbdb541b3, 0x8098a54c); + FEngSetLanguageHash(GetPackageName(), 0x7603f3d5, 0x8098a54c); + } + } else { + cFEng::Get()->QueuePackageMessage(0x587c018b, GetPackageName(), nullptr); + } + break; + } + case 0xabc08912: { + FEPackage *pkg = cFEng::Get()->FindPackage(GetPackageName()); + if (!pkg->bInputEnabled) return; + int idx = GetButtonIndex(pobj->NameHash); + if (TheMarkers[idx].Selected) { + FEngSetScript(pobj, 0x6b718fa1, true); + } else { + FEngSetScript(pobj, 0x249db7b7, true); + } + } + case 0xbb3e313d: + case 0xf0966d46: + Redraw(); + break; + case 0x55d1e635: { + FEPackage *pkg = cFEng::Get()->FindPackage(GetPackageName()); + if (!pkg->bInputEnabled) return; + int idx = GetButtonIndex(pobj->NameHash); + if (TheMarkers[idx].Selected) { + FEngSetScript(pobj, 0xc5decc84, true); + } else { + FEngSetScript(pobj, 0x7ab5521a, true); + } + break; + } + } +} + +void FEMarkerSelection::Redraw() { + for (int i = 0; i < NumVisibleMarkers; i++) { + FEMarkerManager::ePossibleMarker marker = TheMarkers[i].Marker; + if (TheMarkers[i].Selected) { + FEImage *img = FEngFindImage(GetPackageName(), FEngHashString("BUTTON_%d", i + 1)); + FEngSetTextureHash(img, GetIconHashForType(marker)); + } else { + FEImage *img = FEngFindImage(GetPackageName(), FEngHashString("BUTTON_%d", i + 1)); + FEngSetTextureHash(img, GetCategoryIconHashForType(marker)); + } + } + + int idx = GetSelectedButtonIndex(); + Selection selection = TheMarkers[idx]; + + if (selection.Selected && selection.Marker != FEMarkerManager::MARKER_NONE) { + FEngSetLanguageHash(GetPackageName(), 0x4960f369, GetNameHashForType(selection.Marker)); + unsigned int blurb = GetBlurbHashForType(selection.Marker); + if (selection.Marker == static_cast(0x13)) { + const char *str = GetLocalizedString(blurb); + FEPrintf(GetPackageName(), 0xeb0a8abd, str, selection.Param); + } else { + FEngSetLanguageHash(GetPackageName(), 0xeb0a8abd, blurb); + } + } else { + FEngSetLanguageHash(GetPackageName(), 0x4960f369, GetCategoryNameHashForType(selection.Marker)); + FEngSetLanguageHash(GetPackageName(), 0xeb0a8abd, GetCategoryBlurbHashForType(selection.Marker)); + } + + const char *remaining_str = GetLocalizedString(0x5bb3a130); + FEPrintf(GetPackageName(), 0x38deac6b, remaining_str, 2 - GetNumSelected()); + + int current_bin = FEDatabase->GetCareerSettings()->GetCurrentBin() + 1; + char buf[256]; + GetLocalizedString(buf, 0x100, 0xae5bc899); + unsigned int rival_hash = FEngHashString("BLACKLIST_RIVAL_%02d_AKA", current_bin); + const char *rival_name = GetLocalizedString(rival_hash); + FEPrintf(GetPackageName(), 0xd6c0e097, buf, 2 - GetNumSelected(), rival_name); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.hpp index 0816aaa31..597674ca1 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiMarkerSelect.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_CAREER_UIMARKERSELECT_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_CAREER_UIMARKERSELECT_H @@ -5,6 +6,47 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp" +#include +struct FEImage; + +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" + +// total size: 0xC8 (from DWARF: 0xC0 data + 0x8 for possible trailing alignment) +struct FEMarkerSelection : public MenuScreen { + // total size: 0xC + struct Selection { + FEMarkerManager::ePossibleMarker Marker; // offset 0x0, size 0x4 + int Param; // offset 0x4, size 0x4 + bool Selected; // offset 0x8, size 0x1 + }; + + FEMarkerSelection(ScreenConstructorData *sd); + ~FEMarkerSelection() override {} + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + void SetUnlockIcon(eUnlockableEntity ent, unsigned int message); + int GetButtonIndex(unsigned int hash); + int GetSelectedButtonIndex(); + unsigned int GetIconHashForType(FEMarkerManager::ePossibleMarker marker); + unsigned int GetCategoryIconHashForType(FEMarkerManager::ePossibleMarker marker); + unsigned int GetNameHashForType(FEMarkerManager::ePossibleMarker marker); + unsigned int GetCategoryNameHashForType(FEMarkerManager::ePossibleMarker marker); + unsigned int GetBlurbHashForType(FEMarkerManager::ePossibleMarker marker); + unsigned int GetCategoryBlurbHashForType(FEMarkerManager::ePossibleMarker marker); + int GetNumSelected(); + void Redraw(); + + int NumVisibleMarkers; // offset 0x2C, size 0x4 + FEImage *pRivalImg; // offset 0x30, size 0x4 + FEImage *pTagImg; // offset 0x34, size 0x4 + FEImage *pBGImg; // offset 0x38, size 0x4 + uiRepSheetRivalStreamer RivalStreamer; // offset 0x3C, size 0x3C + Selection TheMarkers[6]; // offset 0x78, size 0x48 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.cpp index e69de29bb..41e980dc9 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.cpp @@ -0,0 +1,61 @@ +#include "uiRapSheetCTS.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +int FEPrintf(FEString* text, const char* fmt, ...); +void FEngSetLanguageHash(FEString* text, unsigned int hash); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +void RapSheetCTSDatum::NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) {} +void RapSheetCTSArraySlot::Update(ArrayDatum* datum, bool isSelected) { + ArraySlot::Update(datum, isSelected); + if (datum != nullptr) { + RapSheetCTSDatum* d = static_cast(datum); + FEPrintf(pTimes, "%d", d->getNumTimes()); + FEngSetLanguageHash(pItem, d->getItemHash()); + FEPrintf(pValue, "%d", d->getTotalValue()); + } +} +uiRapSheetCTS::uiRapSheetCTS(ScreenConstructorData* sd) : ArrayScrollerMenu(sd, 1, 9, false) { + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + FEString* pTimes = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_TIMES_%d", i + 1)); + FEString* pItem = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_ITEM_%d", i + 1)); + FEString* pValue = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE_%d", i + 1)); + AddSlot(new(__FILE__, __LINE__) RapSheetCTSArraySlot(pTimes, pItem, pValue)); + } + Setup(); +} +uiRapSheetCTS::~uiRapSheetCTS() {} +void uiRapSheetCTS::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + ArrayScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + if (msg == 0xE1FDE1D1) { cFEng::Get()->QueuePackageSwitch("RapSheetMain.fng", 0, 0, false); } +} +void uiRapSheetCTS::Setup() { + int quantity; + unsigned int value; + unsigned int total_value; + ClearData(); + quantity = 0; + value = 0; + const HighScoresDatabase& scores = *FEDatabase->GetUserProfile(0)->GetHighScores(); + scores.GetCareerCST(RAP_CTS_PROPERTY_DAMAGE, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0x3682A8CF, value)); + scores.GetCareerCST(RAP_CTS_TRAFFIC_CAR_HIT, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0x6DE4810A, value)); + scores.GetCareerCST(RAP_CTS_COP_CAR_DEPLOYED, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0x89A9C941, value)); + scores.GetCareerCST(RAP_CTS_SUPPORT_VEHICLE_DEPLOYED, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0x443B615F, value)); + scores.GetCareerCST(RAP_CTS_COP_DAMAGED, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0xD3AA88DA, value)); + scores.GetCareerCST(RAP_CTS_COP_DESTROYED, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0xBDB16FEA, value)); + scores.GetCareerCST(RAP_CTS_ROADBLOCK_DEPLOYED, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0xD320C6C3, value)); + scores.GetCareerCST(RAP_CTS_SPIKE_STRIP_DEPLOYED, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0xA83862AF, value)); + scores.GetCareerCST(RAP_CTS_HELI_SPAWN, quantity, value); AddDatum(new(__FILE__, __LINE__) RapSheetCTSDatum(quantity, 0x80E9CCB2, value)); + RefreshHeader(); +} +void uiRapSheetCTS::RefreshHeader() { + UserProfile* prof = FEDatabase->GetUserProfile(0); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + HighScoresDatabase* scores = prof->GetHighScores(); + FEPrintf(GetPackageName(), 0x1232703A, GetLocalizedString(0xE21D083C), prof->GetCareer()->GetCaseFileName()); + FEPrintf(GetPackageName(), 0xE3DA78E7, GetLocalizedString(0x6031106E), prof->GetProfileName()); + FEPrintf(GetPackageName(), 0xE3DA78E8, GetLocalizedString(0x364E4525), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), 0x358672CC, GetLocalizedString(0xA355FEDD), scores->GetCareerPursuitScore(static_cast(7))); + ArrayScrollerMenu::RefreshHeader(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.hpp index 3e7334f11..fda32558b 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetCTS.hpp @@ -5,6 +5,48 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +struct RapSheetCTSArraySlot : public ArraySlot { + FEString* pTimes; // offset 0x14, size 0x4 + FEString* pItem; // offset 0x18, size 0x4 + FEString* pValue; // offset 0x1C, size 0x4 + + RapSheetCTSArraySlot(FEString* times, FEString* item, FEString* value) + : ArraySlot(times) // + , pTimes(times) // + , pItem(item) // + , pValue(value) + {} + ~RapSheetCTSArraySlot() override {} + void Update(ArrayDatum* datum, bool isSelected) override; +}; + +struct RapSheetCTSDatum : public ArrayDatum { + int times; // offset 0x24, size 0x4 + unsigned long itemHash; // offset 0x28, size 0x4 + int value; // offset 0x2C, size 0x4 + + RapSheetCTSDatum(int num_times, unsigned long item_name, int total_value) + : ArrayDatum(0, 0) // + , times(num_times) // + , itemHash(item_name) // + , value(total_value) + {} + ~RapSheetCTSDatum() override {} + void NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) override; + + int getNumTimes() { return times; } + unsigned long getItemHash() { return itemHash; } + int getTotalValue() { return value; } +}; + +struct uiRapSheetCTS : public ArrayScrollerMenu { + uiRapSheetCTS(ScreenConstructorData* sd); + ~uiRapSheetCTS() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup(); + void RefreshHeader() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.cpp index e69de29bb..5b2752e19 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.cpp @@ -0,0 +1,53 @@ +#include "uiRapSheetLogin.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" + +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); + +uiRapSheetLogin::uiRapSheetLogin(ScreenConstructorData* sd) + : MenuScreen(sd) // + , screen(sd->Arg) // + , returnToMainMenu(false) +{ + Setup(); +} + +uiRapSheetLogin::~uiRapSheetLogin() {} + +void uiRapSheetLogin::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x7EABCA56: + case 0x406415E3: + if (screen == 0) { + g_pEAXSound->StopUISoundFX(UISND_RAPSHEET_LOGIN); + } else if (screen == 2) { + g_pEAXSound->StopUISoundFX(UISND_RAPSHEET_LOGIN2); + } + screen = 3; + break; + case 0x911AB364: + returnToMainMenu = true; + break; + case 0xE1FDE1D1: + if (returnToMainMenu) { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + FEDatabase->ClearGameMode(eFE_GAME_MODE_RAP_SHEET); + } else if (screen - 2U < 2) { + cFEng::Get()->QueuePackageSwitch("RapSheetMain.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("RapSheetLogin2.fng", 2, 0, false); + } + break; + } +} + +void uiRapSheetLogin::Setup() { + if (screen == 2) { + const char *pkg = GetPackageName(); + const char *fmt = "> %s"; + const char *name = FEDatabase->CurrentUserProfiles[0]->GetProfileName(); + FEPrintf(pkg, 0x3CC94D6, fmt, name); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.hpp index 5999f1cfb..a02c26689 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetLogin.hpp @@ -5,6 +5,16 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +struct uiRapSheetLogin : public MenuScreen { + int screen; // offset 0x2C, size 0x4 + bool returnToMainMenu; // offset 0x30, size 0x1 + + uiRapSheetLogin(ScreenConstructorData* sd); + ~uiRapSheetLogin() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.cpp index e69de29bb..4475d066a 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.cpp @@ -0,0 +1,77 @@ +#include "uiRapSheetMain.hpp" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +unsigned char FEngGetLastButton(const char* pkg_name); +void FEngSetLastButton(const char* pkg_name, unsigned char button); +void FEngSetCurrentButton(const char* pkg_name, unsigned int hash); +uiRapSheetMain::uiRapSheetMain(ScreenConstructorData* sd) + : UIWidgetMenu(sd) // + , button_pressed(0) +{ RefreshHeader(); } +uiRapSheetMain::~uiRapSheetMain() {} +void uiRapSheetMain::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x0C407210: + button_pressed = pobj->NameHash; + break; + case 0x35F8620B: { + unsigned char button = FEngGetLastButton(GetPackageName()); + if (button == 0) { button = 1; } + FEngSetCurrentButton(GetPackageName(), FEngHashString("BL_%d", button)); + break; + } + case 0xE1FDE1D1: { + int button_num = 1; + switch (button_pressed) { + case 0xCDA0A66B: + cFEng::Get()->QueuePackageSwitch("RapSheetRS.fng", 0, 0, false); + break; + case 0xCDA0A66C: + cFEng::Get()->QueuePackageSwitch("RapSheetUS.fng", 0, 0, false); + button_num = 2; + break; + case 0xCDA0A66D: + cFEng::Get()->QueuePackageSwitch("RapSheetCTS.fng", 0, 0, false); + button_num = 3; + break; + case 0xCDA0A66E: + cFEng::Get()->QueuePackageSwitch("RapSheetTEP.fng", 0, 0, false); + button_num = 4; + break; + case 0xCDA0A66F: + cFEng::Get()->QueuePackageSwitch("RapSheetRankings.fng", 0, 0, false); + button_num = 5; + break; + case 0xCDA0A670: + cFEng::Get()->QueuePackageSwitch("RapSheetVD.fng", 0, 0, false); + button_num = 6; + break; + default: + button_num = 1; + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + FEDatabase->ClearGameMode(eFE_GAME_MODE_RAP_SHEET); + break; + } + FEngSetLastButton(GetPackageName(), static_cast(button_num)); + break; + } + } +} +void uiRapSheetMain::RefreshHeader() { + UserProfile* prof = FEDatabase->GetUserProfile(0); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + HighScoresDatabase* scores = prof->GetHighScores(); + FEPrintf(GetPackageName(), 0x1232703A, GetLocalizedString(0xE21D083C), prof->GetCareer()->GetCaseFileName()); + FEPrintf(GetPackageName(), 0xE3DA78E7, GetLocalizedString(0x6031106E), prof->GetProfileName()); + FEPrintf(GetPackageName(), 0xE3DA78E8, GetLocalizedString(0x364E4525), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), 0xE3DA78E9, GetLocalizedString(0xA355FEDD), scores->GetCareerPursuitScore(static_cast(7))); + FEPrintf(GetPackageName(), 0xE3DA78EA, GetLocalizedString(0xB1E58DB1), stable->GetNumImpoundedCars()); + FEPrintf(GetPackageName(), 0xE3DA78EB, GetLocalizedString(0x79FB7D16), stable->GetTotalFines(true)); + FEPrintf(GetPackageName(), 0xE3DA78EC, GetLocalizedString(0x463B461B), stable->GetTotalEvadedPursuits()); + FEPrintf(GetPackageName(), 0xE3DA78ED, GetLocalizedString(0xC5094459), stable->GetTotalBustedPursuits()); + FEPrintf(GetPackageName(), 0xE3DA78EE, GetLocalizedString(0x6DEE0C7A), stable->GetNumCareerCarsWithARecord()); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.hpp index 83b62d114..5b7dc7b77 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetMain.hpp @@ -5,6 +5,15 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" +struct uiRapSheetMain : public UIWidgetMenu { + unsigned int button_pressed; // offset 0x138, size 0x4 + + uiRapSheetMain(ScreenConstructorData* sd); + ~uiRapSheetMain() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void RefreshHeader(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.cpp index e69de29bb..d6ca54438 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.cpp @@ -0,0 +1,31 @@ +#include "uiRapSheetPD.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +const char* GetLocalizedString(unsigned int hash); +uiRapSheetPD::uiRapSheetPD(ScreenConstructorData* sd) : MenuScreen(sd) , pursuit_number(sd->Arg) { Setup(); } +uiRapSheetPD::~uiRapSheetPD() {} +void uiRapSheetPD::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + if (msg == 0xE1FDE1D1) { cFEng::Get()->QueuePackageSwitch("RapSheetTEP.fng", 0, 0, false); } +} +void uiRapSheetPD::Setup() { + UserProfile* prof = FEDatabase->GetUserProfile(0); + HighScoresDatabase* scores = prof->GetHighScores(); + const TopEvadedPursuitDetail& pursuit = scores->GetTopEvadedPursuitScores(static_cast(pursuit_number)); + FEPrintf(GetPackageName(), 0x1232703A, GetLocalizedString(0xE21D083C), prof->GetCareer()->GetCaseFileName()); + FEPrintf(GetPackageName(), 0xEB406FEC, GetLocalizedString(0x6031106E), prof->GetProfileName()); + FEPrintf(GetPackageName(), 0xD91934E7, GetLocalizedString(0xA656CD29), pursuit.PursuitName); + Timer t(pursuit.Length); + char time_str[16]; + t.PrintToString(time_str, 0); + FEPrintf(GetPackageName(), 0x9068C46D, "%s", time_str); + FEPrintf(GetPackageName(), 0x9068C46E, "%d", pursuit.NumCops); + FEPrintf(GetPackageName(), 0x9068C46F, "%d", pursuit.NumCopsDamaged); + FEPrintf(GetPackageName(), 0x9068C470, "%d", pursuit.NumCopsDestroyed); + FEPrintf(GetPackageName(), 0x9068C471, "%d", pursuit.NumRoadblocksDodged); + FEPrintf(GetPackageName(), 0x9068C472, "%d", pursuit.NumSpikeStripsDodged); + FEPrintf(GetPackageName(), 0x9068C473, "%d", pursuit.TotalCostToState); + FEPrintf(GetPackageName(), 0x9068C474, "%d", pursuit.NumInfractions); + FEPrintf(GetPackageName(), 0x9068C475, "%d", pursuit.NumHelicopters); + FEPrintf(GetPackageName(), 0x9D81523D, "%d", pursuit.Bounty); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.hpp index a3b88626b..fd23d23f0 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetPD.hpp @@ -5,6 +5,15 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +struct uiRapSheetPD : public MenuScreen { + int pursuit_number; // offset 0x2C, size 0x4 + + uiRapSheetPD(ScreenConstructorData* sd); + ~uiRapSheetPD() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.cpp index e69de29bb..601055b15 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.cpp @@ -0,0 +1,41 @@ +#include "uiRapSheetRS.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +uiRapSheetRS::uiRapSheetRS(ScreenConstructorData* sd) : MenuScreen(sd) { RefreshHeader(); } +uiRapSheetRS::~uiRapSheetRS() {} +void uiRapSheetRS::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + if (msg == 0xE1FDE1D1) { cFEng::Get()->QueuePackageSwitch("RapSheetMain.fng", 0, 0, false); } +} +void uiRapSheetRS::RefreshHeader() { + UserProfile* prof = FEDatabase->GetUserProfile(0); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + HighScoresDatabase* scores = prof->GetHighScores(); + FEPrintf(GetPackageName(), 0x1232703A, GetLocalizedString(0xE21D083C), prof->GetCareer()->GetCaseFileName()); + FEPrintf(GetPackageName(), 0xB259EEA7, GetLocalizedString(0x6031106E), prof->GetProfileName()); + FEPrintf(GetPackageName(), 0xB259EEA8, GetLocalizedString(0x364E4525), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), 0xB259EEA9, GetLocalizedString(0xA355FEDD), scores->GetCareerPursuitScore(static_cast(7))); + FEPrintf(GetPackageName(), 0xB259EEAA, GetLocalizedString(0xB1E58DB1), stable->GetNumImpoundedCars()); + FEPrintf(GetPackageName(), 0x6EB5AC50, GetLocalizedString(0x091CB790), stable->GetTotalNumInfractions(true)); + FEPrintf(GetPackageName(), 0x6EB5AC51, GetLocalizedString(0x1903C44D), stable->GetTotalNumInfractions(false)); + FEPrintf(GetPackageName(), 0xD919049F, GetLocalizedString(0x3598476F), stable->GetTotalEvadedPursuits()); + FEPrintf(GetPackageName(), 0xD91904A0, GetLocalizedString(0x2E90D7ED), stable->GetTotalBustedPursuits()); + FEPrintf(GetPackageName(), 0xD01E18C5, GetLocalizedString(0x82A67697), stable->GetTotalFines(true)); + FEPrintf(GetPackageName(), 0xD01E18C6, GetLocalizedString(0xD77B89B7), stable->GetTotalFines(false)); + FEPrintf(GetPackageName(), 0xD7E5D0CC, GetLocalizedString(0x50EC3763), scores->GetCareerPursuitScore(static_cast(2))); + FEPrintf(GetPackageName(), 0xD7E5D0CD, GetLocalizedString(0xE8DB4BF3), scores->GetCareerPursuitScore(static_cast(3))); + Attrib::Gen::frontend rapsheetSummaryString(Attrib::StringToKey("rap_sheet_summary"), 0, nullptr); + if (rapsheetSummaryString.IsValid()) { + unsigned int wanring_val = rapsheetSummaryString.Num_WarningLevel(); + int totalInfractions = stable->GetTotalNumInfractions(true) + stable->GetTotalNumInfractions(false); + for (unsigned int i = 0; i < rapsheetSummaryString.Num_WarningLevel(); i++) { + if (static_cast(totalInfractions) <= rapsheetSummaryString.WarningLevel(i)) { wanring_val = i; break; } + } + if (wanring_val == 0) { wanring_val = 1; } + FEngSetLanguageHash(GetPackageName(), 0x90211462, FEngHashString("RAPSHEET_WARNING_%d", wanring_val)); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.hpp index b9007ee3f..341660143 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRS.hpp @@ -5,6 +5,13 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +struct uiRapSheetRS : public MenuScreen { + uiRapSheetRS(ScreenConstructorData* sd); + ~uiRapSheetRS() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void RefreshHeader(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.cpp index e69de29bb..56321f856 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.cpp @@ -0,0 +1,77 @@ +#include "uiRapSheetRankingsDetail.hpp" +#include "uiRapSheetRankings.hpp" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +unsigned char FEngGetLastButton(const char* pkg_name); +void FEngSetLastButton(const char* pkg_name, unsigned char button); +void FEngSetCurrentButton(const char* pkg_name, unsigned int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +void RapSheetRankingsDatum::NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) {} +bool uiRapSheetRankings::career_view = false; +uiRapSheetRankings::uiRapSheetRankings(ScreenConstructorData* sd) : MenuScreen(sd) , button_pressed(0) , init_button(0) { Setup(); } +uiRapSheetRankings::~uiRapSheetRankings() {} +void uiRapSheetRankings::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x0C407210: + button_pressed = pobj->NameHash; + break; + case 0xC519BFC4: + career_view = !career_view; + Setup(); + break; + case 0x35F8620B: + FEngSetCurrentButton(GetPackageName(), init_button); + break; + case 0xE1FDE1D1: { + int index = 10; + switch (button_pressed) { + case 0xCDA0A66B: index = 0; break; + case 0xCDA0A66C: index = 1; break; + case 0xCDA0A66D: index = 2; break; + case 0xCDA0A66E: index = 3; break; + case 0xCDA0A66F: index = 5; break; + case 0xCDA0A670: index = 4; break; + case 0xCDA0A671: index = 7; break; + case 0xCDA0A672: index = 8; break; + case 0xCDA0A673: index = 6; break; + case 0x81B573FB: index = 9; break; + } + if (index != 10) { uiRapSheetRankingsDetail::career_view = career_view; cFEng::Get()->QueuePackageSwitch("RapSheetRankingsDetail.fng", index, 0, false); FEngSetLastButton(GetPackageName(), static_cast(index)); } + else { cFEng::Get()->QueuePackageSwitch("RapSheetMain.fng", 0, 0, false); FEngSetLastButton(GetPackageName(), 0); } + break; + } + } +} +void uiRapSheetRankings::RefreshHeader() { + UserProfile* prof = FEDatabase->GetUserProfile(0); + FEPrintf(GetPackageName(), 0x1232703A, GetLocalizedString(0xE21D083C), prof->GetCareer()->GetCaseFileName()); + FEPrintf(GetPackageName(), static_cast(0xEB406FEC), GetLocalizedString(0x6031106E), prof->GetProfileName()); + FEngSetLanguageHash(GetPackageName(), 0x1E4FDA, career_view ? 0x96DDF504 : 0x56E940F4); + FEngSetLanguageHash(GetPackageName(), 0xDD2F4FB, career_view ? 0x554BBDB5 : 0xA88B3FC5); + FEngSetLanguageHash(GetPackageName(), 0x9AE9B5CD, career_view ? 0x554BBDB5 : 0xA88B3FC5); +} +void uiRapSheetRankings::Setup() { + PrintRanking(0x7711109B, 0xCDA0A66B, ePDT_CostToState); + PrintRanking(0x7711109C, 0xCDA0A66C, ePDT_Bounty); + PrintRanking(0x7711109D, 0xCDA0A66D, ePDT_Infractions); + PrintRanking(0x7711109E, 0xCDA0A66E, ePDT_SpeedingTotalFine); + PrintRanking(0x7711109F, 0xCDA0A66F, static_cast(5)); + PrintRanking(0x771110A0, 0xCDA0A670, static_cast(4)); + PrintRanking(0x771110A1, 0xCDA0A671, static_cast(7)); + PrintRanking(0x771110A2, 0xCDA0A672, static_cast(8)); + PrintRanking(0x771110A3, 0xCDA0A673, static_cast(6)); + PrintRanking(0x5933242B, 0x81B573FB, static_cast(9)); + RefreshHeader(); +} +void uiRapSheetRankings::PrintRanking(unsigned int fe_rank, unsigned int button_hash, ePursuitDetailTypes type) { + UserProfile* prof = FEDatabase->GetUserProfile(0); + int rank = prof->GetHighScores()->CalcPursuitRank(type, career_view); + if (rank != 0x10) { FEPrintf(GetPackageName(), fe_rank, "%d", rank); } + else { FEPrintf(GetPackageName(), fe_rank, "%s", GetLocalizedString(0xF3799455)); } + unsigned char lastButton = FEngGetLastButton(GetPackageName()); + if (static_cast(type) == lastButton) { init_button = button_hash; } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.hpp index 6ff798bea..1cd84ef8d 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankings.hpp @@ -5,6 +5,21 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/Database/RaceDB.hpp" +struct uiRapSheetRankings : public MenuScreen { + unsigned int button_pressed; // offset 0x2C, size 0x4 + unsigned int init_button; // offset 0x30, size 0x4 + + static bool career_view; + + uiRapSheetRankings(ScreenConstructorData* sd); + ~uiRapSheetRankings() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void RefreshHeader(); + void Setup(); + void PrintRanking(unsigned int fe_rank, unsigned int button_hash, ePursuitDetailTypes type); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.cpp index e69de29bb..d2b0c76cd 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.cpp @@ -0,0 +1,244 @@ +#include "uiRapSheetRankingsDetail.hpp" +#include "uiRapSheetRankings.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +int FEPrintf(FEString* text, const char* fmt, ...); +void FEngSetLanguageHash(FEString* text, unsigned int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +unsigned int FEngHashString(const char* format, ...); +unsigned int GetFECarNameHashFromFEKey(unsigned int feKey); +const char* GetLocalizedString(unsigned int hash); +bool uiRapSheetRankingsDetail::career_view = false; +void RapSheetRankingsArraySlot::Update(ArrayDatum* datum, bool isSelected) { + ArraySlot::Update(datum, isSelected); + if (datum != nullptr) { + RapSheetRankingsDatum* d = static_cast(datum); + FEPrintf(pValue, "%.0f", d->getValue()); + if (d->getItemNum() != 0x10) { FEPrintf(pItemNum, "%d", d->getItemNum()); } + else { FEngSetLanguageHash(pItemNum, 0xFC1BF40); } + if (d->getCarName() != 0) { FEngSetLanguageHash(pCarName, d->getCarName()); } + else { FEPrintf(pCarName, ""); } + if (d->getPlayerName() != 1) { FEngSetLanguageHash(pPlayerName, d->getPlayerName()); } + else { FEPrintf(pPlayerName, "%s", FEDatabase->GetUserProfile(0)->GetProfileName()); } + } +} +void RapSheetRankingsTimerArraySlot::Update(ArrayDatum* datum, bool isSelected) { + ArraySlot::Update(datum, isSelected); + if (datum != nullptr) { + RapSheetRankingsDatum* d = static_cast(datum); + if (d->getItemNum() != 0x10) { FEPrintf(pItemNum, "%d"); } + else { FEPrintf(pItemNum, "#"); } + if (d->getCarName() != 0) { FEngSetLanguageHash(pCarName, d->getCarName()); } + else { FEPrintf(pCarName, ""); } + Timer t; + t.SetTime(d->getValue()); + char time_str[16]; + t.PrintToString(time_str, 16); + FEPrintf(pValue, "%s", time_str); + if (d->getPlayerName() != 1) { FEngSetLanguageHash(pPlayerName, d->getPlayerName()); } + else { FEPrintf(pPlayerName, "%s", FEDatabase->GetUserProfile(0)->GetProfileName()); } + } +} +uiRapSheetRankingsDetail::uiRapSheetRankingsDetail(ScreenConstructorData* sd) + : ArrayScrollerMenu(sd, 1, 10, false) // + , rank_type(static_cast(sd->Arg)) // + , player_rank(0x10) +{ + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + FEString* pItemNum = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_RANK_%d", i + 1)); + FEString* pPlayerName = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_PLAYER_%d", i + 1)); + FEString* pCarName = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE_%d", i + 1)); + FEString* pValue = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE2_%d", i + 1)); + if (rank_type == ePDT_CostToState) { AddSlot(new(__FILE__, __LINE__) RapSheetRankingsTimerArraySlot(pItemNum, pPlayerName, pCarName, pValue)); } + else { AddSlot(new(__FILE__, __LINE__) RapSheetRankingsArraySlot(pItemNum, pPlayerName, pCarName, pValue)); } + } + Setup(); +} +uiRapSheetRankingsDetail::~uiRapSheetRankingsDetail() {} +void uiRapSheetRankingsDetail::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + ArrayScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0xC519BFC4: + career_view = !career_view; + Setup(); + break; + case 0x911C0A4B: + case 0x35F8620B: + case 0x72619778: + UpdateHighlight(); + break; + case 0xE1FDE1D1: + uiRapSheetRankings::career_view = career_view; + cFEng::Get()->QueuePackageSwitch("RapSheetRankings.fng", 0, 0, false); + break; + } +} +void uiRapSheetRankingsDetail::Setup() { + ClearData(); + HighScoresDatabase* const scores = FEDatabase->GetUserProfile(0)->GetHighScores(); + player_rank = scores->CalcPursuitRank(rank_type, career_view); + Attrib::Key key; + unsigned int value_label; + switch (static_cast(rank_type)) { + case 0: + if (career_view) { + key = Attrib::StringToKey("pursuit_length_in_pursuit"); + } else { + key = Attrib::StringToKey("pursuit_length"); + } + value_label = 0xD70811D1; + break; + case 1: + if (career_view) { + key = Attrib::StringToKey("cops_involved_in_pursuit"); + } else { + key = Attrib::StringToKey("cops_involved"); + } + value_label = 0xC6113FCF; + break; + case 2: + if (career_view) { + key = Attrib::StringToKey("cops_damaged_in_pursuit"); + } else { + key = Attrib::StringToKey("cops_damaged"); + } + value_label = 0x2A1815D9; + break; + case 3: + if (career_view) { + key = Attrib::StringToKey("cops_destroyed_in_pursuit"); + } else { + key = Attrib::StringToKey("cops_destroyed"); + } + value_label = 0x189EAF7B; + break; + case 4: + if (career_view) { + key = Attrib::StringToKey("tire_spikes_dodged_in_pursuit"); + } else { + key = Attrib::StringToKey("tire_spikes_dodged"); + } + value_label = 0xDCD6B9BA; + break; + case 5: + if (career_view) { + key = Attrib::StringToKey("roadblocks_dodged_in_pursuit"); + } else { + key = Attrib::StringToKey("roadblocks_dodged"); + } + value_label = 0x9EF589BE; + break; + case 6: + if (career_view) { + key = Attrib::StringToKey("helis_involved_in_pursuit"); + } else { + key = Attrib::StringToKey("helis_involved"); + } + value_label = 0x39A1413C; + break; + case 7: + if (career_view) { + key = Attrib::StringToKey("cost_to_state_in_pursuit"); + } else { + key = Attrib::StringToKey("cost_to_state"); + } + value_label = 0xB3F963F8; + break; + case 8: + if (career_view) { + key = Attrib::StringToKey("total_infractions_in_pursuit"); + } else { + key = Attrib::StringToKey("total_infractions"); + } + value_label = 0xE34B2E6F; + break; + case 9: + if (career_view) { + key = Attrib::StringToKey("bounty_in_pursuit"); + } else { + key = Attrib::StringToKey("bounty"); + } + value_label = 0x48B4B99C; + break; + default: + key = 0; + value_label = 0; + break; + } + Attrib::Gen::frontend rankingsData(key, 0, nullptr); + if (rankingsData.IsValid()) { + int last = rankingsData.Num_RapSheetRanks(); + if (last == 15) { + int num_rankings_to_show = rankingsData.Num_RapSheetRanks(); + int rival_offset = 0; + int player_rank_index = player_rank - 1; + if (player_rank == 0x10) { + num_rankings_to_show = 0x10; + } + for (int i = 0; i < num_rankings_to_show; i++) { + if (i == player_rank_index) { + unsigned int car_hash = 0; + int player_value; + if (career_view) { + player_value = scores->GetCareerPursuitScore(rank_type); + } else { + car_hash = GetFECarNameHashFromFEKey(scores->GetBestPursuitScore(rank_type).CarFEKey); + player_value = scores->GetBestPursuitScore(rank_type).Value; + } + + float value; + if (rank_type == ePDT_CostToState) { + value = static_cast(player_value) * 0.00025f; + } else { + value = static_cast(player_value); + } + + AddDatum(new(__FILE__, __LINE__) RapSheetRankingsDatum(player_rank, 1, car_hash, value)); + rival_offset--; + } else { + int rank_index = i + rival_offset; + unsigned int name_hash = FEngHashString("BLACKLIST_RIVAL_%.2d_AKA", static_cast(rankingsData.NameId(rank_index))); + unsigned int car_hash; + if (career_view) { + car_hash = 0; + } else { + car_hash = FEngHashString("BLACKLIST_RIVAL_%.2d_CAR", static_cast(rankingsData.NameId(rank_index))); + } + AddDatum(new(__FILE__, __LINE__) RapSheetRankingsDatum(i + 1, name_hash, car_hash, + rankingsData.RapSheetRanks(rank_index))); + } + } + + SetInitialPosition(0); + int dist_off_screen = player_rank - GetHeight() + 4; + for (; dist_off_screen > 0; dist_off_screen--) { + ScrollDown(); + } + } + } + FEngSetLanguageHash(GetPackageName(), 0x8224E17C, value_label); + UpdateHighlight(); + ArrayScroller* scroller = this; + scroller->RefreshHeader(); +} +void uiRapSheetRankingsDetail::RefreshHeader() { + UserProfile* prof = FEDatabase->GetUserProfile(0); + FEPrintf(GetPackageName(), 0x1232703A, GetLocalizedString(0xE21D083C), prof->GetCareer()->GetCaseFileName()); + FEngSetLanguageHash(GetPackageName(), 0x1E4FDA, career_view ? 0x96DDF504 : 0x56E940F4); + FEngSetLanguageHash(GetPackageName(), 0xDD2F4FB, career_view ? 0x554BBDB5 : 0xA88B3FC5); + FEngSetLanguageHash(GetPackageName(), 0x9AE9B5CD, career_view ? 0x554BBDB5 : 0xA88B3FC5); + ArrayScrollerMenu::RefreshHeader(); +} +void uiRapSheetRankingsDetail::UpdateHighlight() { + int highlight = player_rank - GetStartDatumNum(); + if (highlight > 0) { + int numSlots = GetNumSlots(); + if (highlight <= numSlots) { + cFEng::Get()->QueuePackageMessage(FEngHashString("RAPSHEET_HIGHLIGHT_%d", highlight), nullptr, nullptr); + return; + } + } + cFEng::Get()->QueuePackageMessage(0x58B123F7, nullptr, nullptr); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.hpp index 2eae15717..087a7370a 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetRankingsDetail.hpp @@ -5,6 +5,68 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/Database/RaceDB.hpp" +struct RapSheetRankingsArraySlot : public ArraySlot { + FEString* pItemNum; // offset 0x14, size 0x4 + FEString* pPlayerName; // offset 0x18, size 0x4 + FEString* pCarName; // offset 0x1C, size 0x4 + FEString* pValue; // offset 0x20, size 0x4 + + RapSheetRankingsArraySlot(FEString* item_num, FEString* player_name, FEString* car_name, FEString* value) + : ArraySlot(item_num) // + , pItemNum(item_num) // + , pPlayerName(player_name) // + , pCarName(car_name) // + , pValue(value) + {} + ~RapSheetRankingsArraySlot() override {} + void Update(ArrayDatum* datum, bool isSelected) override; +}; + +struct RapSheetRankingsTimerArraySlot : public RapSheetRankingsArraySlot { + RapSheetRankingsTimerArraySlot(FEString* item_num, FEString* player_name, FEString* car_name, FEString* value) + : RapSheetRankingsArraySlot(item_num, player_name, car_name, value) + {} + ~RapSheetRankingsTimerArraySlot() override {} + void Update(ArrayDatum* datum, bool isSelected) override; +}; + +struct RapSheetRankingsDatum : public ArrayDatum { + unsigned int itemNum; // offset 0x24, size 0x4 + unsigned int nameHash; // offset 0x28, size 0x4 + unsigned int carHash; // offset 0x2C, size 0x4 + float value; // offset 0x30, size 0x4 + + RapSheetRankingsDatum(unsigned int item_num, unsigned int player_hash, unsigned int car_hash, float val) + : ArrayDatum(0, 0) // + , itemNum(item_num) // + , nameHash(player_hash) // + , carHash(car_hash) // + , value(val) + {} + ~RapSheetRankingsDatum() override {} + void NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) override; + + unsigned int getItemNum() { return itemNum; } + unsigned int getPlayerName() { return nameHash; } + unsigned int getCarName() { return carHash; } + float getValue() { return value; } +}; + +struct uiRapSheetRankingsDetail : public ArrayScrollerMenu { + ePursuitDetailTypes rank_type; // offset 0xE8, size 0x4 + int player_rank; // offset 0xEC, size 0x4 + + static bool career_view; + + uiRapSheetRankingsDetail(ScreenConstructorData* sd); + ~uiRapSheetRankingsDetail() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup(); + void RefreshHeader() override; + void UpdateHighlight(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.cpp index e69de29bb..b2e5a3d69 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.cpp @@ -0,0 +1,92 @@ +#include "uiRapSheetTEP.hpp" +#include "Speed/Indep/Src/FEng/FEObject.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +void FEngSetInvisible(FEObject* obj); +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +unsigned char FEngGetLastButton(const char* pkg_name); +void FEngSetLastButton(const char* pkg_name, unsigned char button); +void FEngSetCurrentButton(const char* pkg_name, unsigned int hash); +void FEngSetButtonState(const char* pkg_name, unsigned int button_hash, bool enabled); +unsigned int GetFECarNameHashFromFEKey(unsigned int fekey); +uiRapSheetTEP::uiRapSheetTEP(ScreenConstructorData* sd) : UIWidgetMenu(sd) , button_pressed(0) , num_pursuits(0) { Setup(); } +uiRapSheetTEP::~uiRapSheetTEP() {} +void uiRapSheetTEP::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x0C407210: + button_pressed = pobj->NameHash; + break; + case 0x406415E3: + if (num_pursuits == 0) { return; } + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), nullptr); + break; + case 0x72619778: + if (pobj == nullptr) { return; } + if (pobj->NameHash != 0xCDA0A66B) { return; } + FEngSetCurrentButton(GetPackageName(), FEngHashString("BL_%d", num_pursuits)); + break; + case 0x911C0A4B: + if (pobj == nullptr) { return; } + if (pobj->NameHash != static_cast(FEngHashString("BL_%d", num_pursuits))) { return; } + FEngSetCurrentButton(GetPackageName(), 0xCDA0A66B); + break; + case 0x35F8620B: + if (num_pursuits == 0) { return; } + { + unsigned char button = FEngGetLastButton(GetPackageName()); + if (button == 0) { button = 1; } + FEngSetCurrentButton(GetPackageName(), FEngHashString("BL_%d", button)); + } + break; + case 0xE1FDE1D1: { + int index; + switch (button_pressed) { + case 0xCDA0A66B: index = 0; break; + case 0xCDA0A66C: index = 1; break; + case 0xCDA0A66D: index = 2; break; + case 0xCDA0A66E: index = 3; break; + case 0xCDA0A66F: index = 4; break; + default: index = -1; break; + } + if (index != -1) { cFEng::Get()->QueuePackageSwitch("RapSheetPD.fng", index, 0, false); FEngSetLastButton(GetPackageName(), static_cast(index + 1)); } + else { cFEng::Get()->QueuePackageSwitch("RapSheetMain.fng", 0, 0, false); FEngSetLastButton(GetPackageName(), 1); } + break; + } + } +} +void uiRapSheetTEP::Setup() { + UserProfile& prof = *FEDatabase->GetUserProfile(0); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + HighScoresDatabase* scores = prof.GetHighScores(); + FEPrintf(GetPackageName(), 0x1232703A, GetLocalizedString(0xE21D083C), prof.GetCareer()->GetCaseFileName()); + FEPrintf(GetPackageName(), 0xE3DA78E7, GetLocalizedString(0x6031106E), prof.GetProfileName()); + FEPrintf(GetPackageName(), 0xE3DA78E8, GetLocalizedString(0x364E4525), stable->GetTotalBounty()); + for (int i = 0; i < 5; i++) { + const TopEvadedPursuitDetail& pursuit = scores->GetTopEvadedPursuitScores(static_cast(i)); + if (pursuit.Length != 0) { + char time_str[16]; + Timer t(pursuit.Length); + t.PrintToString(time_str, 0); + int index = i + 1; + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_CAR_%d", index), GetLocalizedString(0x69EAB50F), GetLocalizedString(GetFECarNameHashFromFEKey(pursuit.CarFEKey))); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE_%d", index), GetLocalizedString(0x060C058A), pursuit.Bounty); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE2_%d", index), GetLocalizedString(0x41474FB1), pursuit.PursuitName); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE3_%d", index), GetLocalizedString(0x36175146), time_str); + num_pursuits++; + } else { + int index = i + 1; + FEngSetButtonState(GetPackageName(), FEngHashString("BL_%d", index), false); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_CAR_%d", index), GetLocalizedString(0xE3274304)); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE_%d", index), ""); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE2_%d", index), ""); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE3_%d", index), ""); + } + } + if (num_pursuits == 0) { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xEB5E7757)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x73DCB662)); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.hpp index d93f2483c..fdff88309 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetTEP.hpp @@ -5,6 +5,16 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" +struct uiRapSheetTEP : public UIWidgetMenu { + unsigned int button_pressed; // offset 0x138, size 0x4 + int num_pursuits; // offset 0x13C, size 0x4 + + uiRapSheetTEP(ScreenConstructorData* sd); + ~uiRapSheetTEP() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.cpp index e69de29bb..dff39b284 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.cpp @@ -0,0 +1,83 @@ +#include "uiRapSheetUS.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +int FEPrintf(FEString* text, const char* fmt, ...); +void FEngSetLanguageHash(FEString* text, unsigned int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +const char* GetLocalizedString(unsigned int hash); +void RapSheetUSDatum::NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) {} +void RapSheetUSArraySlot::Update(ArrayDatum* datum, bool isSelected) { + ArraySlot::Update(datum, isSelected); + if (datum != nullptr) { + RapSheetUSDatum* d = static_cast(datum); + FEngSetLanguageHash(pItemName, d->getItemName()); + FEPrintf(pUnserved, "%d", d->getNumUnserved()); + FEPrintf(pTotal, "%d", d->getTotalUnserved()); + } +} +uiRapSheetUS::uiRapSheetUS(ScreenConstructorData* sd) + : ArrayScrollerMenu(sd, 1, 8, false) // + , view_unserved(true) +{ + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + FEString* pName = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_ITEM_%d", i + 1)); + FEString* pUns = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE_%d", i + 1)); + FEString* pTot = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE2_%d", i + 1)); + AddSlot(new(__FILE__, __LINE__) RapSheetUSArraySlot(reinterpret_cast(pName), pName, pUns, pTot)); + } + Setup(); +} +uiRapSheetUS::~uiRapSheetUS() {} +void uiRapSheetUS::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + ArrayScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0xC519BFC4: ToggleView(); break; + case 0xE1FDE1D1: cFEng::Get()->QueuePackageSwitch("RapSheetMain.fng", 0, 0, false); break; + } +} +void uiRapSheetUS::ToggleView() { view_unserved = !view_unserved; Setup(); } +void uiRapSheetUS::Setup() { + ClearData(); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + { unsigned short num = stable->GetNumInfraction(GInfractionManager::kInfraction_Speeding, view_unserved); unsigned short total = (num + stable->GetNumInfraction(GInfractionManager::kInfraction_Speeding, !view_unserved)) & 0xFFFF; AddDatum(new(__FILE__, __LINE__) RapSheetUSDatum(0x676B575C, num, total)); } + { unsigned short num = stable->GetNumInfraction(GInfractionManager::kInfraction_Racing, view_unserved); unsigned short total = (num + stable->GetNumInfraction(GInfractionManager::kInfraction_Racing, !view_unserved)) & 0xFFFF; AddDatum(new(__FILE__, __LINE__) RapSheetUSDatum(0x81705AC5, num, total)); } + { unsigned short num = stable->GetNumInfraction(GInfractionManager::kInfraction_Reckless, view_unserved); unsigned short total = (num + stable->GetNumInfraction(GInfractionManager::kInfraction_Reckless, !view_unserved)) & 0xFFFF; AddDatum(new(__FILE__, __LINE__) RapSheetUSDatum(0x0E9D1CB6, num, total)); } + { unsigned short num = stable->GetNumInfraction(GInfractionManager::kInfraction_Assault, view_unserved); unsigned short total = (num + stable->GetNumInfraction(GInfractionManager::kInfraction_Assault, !view_unserved)) & 0xFFFF; AddDatum(new(__FILE__, __LINE__) RapSheetUSDatum(0x1536B1FA, num, total)); } + { unsigned short num = stable->GetNumInfraction(GInfractionManager::kInfraction_HitAndRun, view_unserved); unsigned short total = (num + stable->GetNumInfraction(GInfractionManager::kInfraction_HitAndRun, !view_unserved)) & 0xFFFF; AddDatum(new(__FILE__, __LINE__) RapSheetUSDatum(0xAAF89AB3, num, total)); } + { unsigned short num = stable->GetNumInfraction(GInfractionManager::kInfraction_Damage, view_unserved); unsigned short total = (num + stable->GetNumInfraction(GInfractionManager::kInfraction_Damage, !view_unserved)) & 0xFFFF; AddDatum(new(__FILE__, __LINE__) RapSheetUSDatum(0x706C0F0D, num, total)); } + { unsigned short num = stable->GetNumInfraction(GInfractionManager::kInfraction_Resist, view_unserved); unsigned short total = (num + stable->GetNumInfraction(GInfractionManager::kInfraction_Resist, !view_unserved)) & 0xFFFF; AddDatum(new(__FILE__, __LINE__) RapSheetUSDatum(0xAD524B30, num, total)); } + { unsigned short num = stable->GetNumInfraction(GInfractionManager::kInfraction_OffRoad, view_unserved); unsigned short total = (num + stable->GetNumInfraction(GInfractionManager::kInfraction_OffRoad, !view_unserved)) & 0xFFFF; AddDatum(new(__FILE__, __LINE__) RapSheetUSDatum(0xF9748B0B, num, total)); } + unsigned int label_hash = view_unserved ? 0xC225D554 : 0x6A1151D1; + FEngSetLanguageHash(GetPackageName(), 0x9D974DF3, label_hash); + RefreshHeader(); +} +void uiRapSheetUS::RefreshHeader() { + const char* packageName = GetPackageName(); + UserProfile* prof = FEDatabase->GetUserProfile(0); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + FEPrintf(packageName, 0x1232703A, GetLocalizedString(0xE21D083C), prof->GetCareer()->GetCaseFileName()); + FEPrintf(packageName, 0x1FFFB988, GetLocalizedString(0x6031106E), prof->GetProfileName()); + unsigned int infract_string = 0xAD0B7F09; + if (view_unserved) { + infract_string = 0xBDFE114C; + } + FEPrintf(packageName, 0x1FFFB989, GetLocalizedString(infract_string), stable->GetTotalNumInfractions(view_unserved)); + FEPrintf(packageName, 0x1FFFB98A, GetLocalizedString(0x364E4525), stable->GetTotalBounty()); + unsigned int fine_string = 0x1E424873; + if (view_unserved) { + fine_string = 0x1FF24DD3; + } + FEPrintf(packageName, 0x1FFFB98B, GetLocalizedString(fine_string), stable->GetTotalFines(view_unserved)); + unsigned int total_string = 0x3177BB0D; + if (view_unserved) { + total_string = 0x8422B22A; + } + FEPrintf(packageName, 0x2ECAFA80, GetLocalizedString(total_string), stable->GetTotalNumInfractions(view_unserved)); + total_string = 0x3177BB0D; + if (view_unserved) { + total_string = 0x8422B22A; + } + FEPrintf(packageName, 0xBBE88932, GetLocalizedString(total_string), stable->GetTotalNumInfractions(view_unserved)); + ArrayScrollerMenu::RefreshHeader(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.hpp index dcedf9a2d..b97cfa554 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetUS.hpp @@ -5,6 +5,51 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +struct RapSheetUSArraySlot : public ArraySlot { + FEString* pItemName; // offset 0x14, size 0x4 + FEString* pUnserved; // offset 0x18, size 0x4 + FEString* pTotal; // offset 0x1C, size 0x4 + + RapSheetUSArraySlot(FEObject* obj, FEString* item_name, FEString* unserved_infractions, FEString* total_infractions) + : ArraySlot(obj) // + , pItemName(item_name) // + , pUnserved(unserved_infractions) // + , pTotal(total_infractions) + {} + ~RapSheetUSArraySlot() override {} + void Update(ArrayDatum* datum, bool isSelected) override; +}; + +struct RapSheetUSDatum : public ArrayDatum { + unsigned int itemName; // offset 0x24, size 0x4 + unsigned int unserved; // offset 0x28, size 0x4 + unsigned int total; // offset 0x2C, size 0x4 + + RapSheetUSDatum(unsigned int item_name, unsigned int unserved_infractions, unsigned int total_infractions) + : ArrayDatum(0, 0) // + , itemName(item_name) // + , unserved(unserved_infractions) // + , total(total_infractions) + {} + ~RapSheetUSDatum() override {} + void NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) override; + + unsigned int getItemName() { return itemName; } + unsigned int getNumUnserved() { return unserved; } + unsigned int getTotalUnserved() { return total; } +}; + +struct uiRapSheetUS : public ArrayScrollerMenu { + bool view_unserved; // offset 0xE8, size 0x1 + + uiRapSheetUS(ScreenConstructorData* sd); + ~uiRapSheetUS() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup(); + void RefreshHeader() override; + void ToggleView(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.cpp index e69de29bb..2f2bb99a5 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.cpp @@ -0,0 +1,91 @@ +#include "uiRapSheetVD.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +int FEPrintf(FEString* text, const char* fmt, ...); +void FEngSetLanguageHash(FEString* text, unsigned int hash); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +void RapSheetVDDatum::NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) {} +void RapSheetVDArraySlot::Update(ArrayDatum* datum, bool isSelected) { + ArraySlot::Update(datum, isSelected); + if (datum != nullptr) { + RapSheetVDDatum* d = static_cast(datum); + FEngSetLanguageHash(pCar, d->getCarHash()); + FEPrintf(pBounty, GetLocalizedString(0xDFC1111B), d->getBounty()); + FEPrintf(pFines, GetLocalizedString(0x092D727E), d->getFines()); + FEPrintf(pUnserved, GetLocalizedString(0x71DAD325), d->getUnserved()); + FEPrintf(pToDrive, "%s", GetLocalizedString(d->getStatusHash())); + FEPrintf(pEvaded, GetLocalizedString(0x49F29E04), d->getEvaded()); + FEPrintf(pBusted, GetLocalizedString(0x42EB2E82), d->getBusted()); + } +} +uiRapSheetVD::uiRapSheetVD(ScreenConstructorData* sd) : ArrayScrollerMenu(sd, 1, 2, false) { + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + FEString* pCar = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_CAR_%d", i + 1)); + FEString* pBounty = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE_%d", i + 1)); + FEString* pFines = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE2_%d", i + 1)); + FEString* pUnserved = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE3_%d", i + 1)); + FEString* pToDrive = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE4_%d", i + 1)); + FEString* pEvaded = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE5_%d", i + 1)); + FEString* pBusted = FEngFindString(GetPackageName(), FEngHashString("RAPSHEET_VALUE6_%d", i + 1)); + AddSlot(new(__FILE__, __LINE__) RapSheetVDArraySlot(pCar, pBounty, pFines, pUnserved, pToDrive, pEvaded, pBusted)); + } + Setup(); +} +uiRapSheetVD::~uiRapSheetVD() {} +void uiRapSheetVD::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + ArrayScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + if (msg == 0xE1FDE1D1) { cFEng::Get()->QueuePackageSwitch("RapSheetMain.fng", 0, 0, false); } +} +void uiRapSheetVD::Setup() { + int numCars = 0; + ClearData(); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + for (int i = 0; i < 200; i++) { + FECarRecord* fe_car = stable->GetCarByIndex(i); + if (fe_car->IsValid() && fe_car->MatchesFilter(0xF0002)) { + FECareerRecord* record = stable->GetCareerRecordByHandle(fe_car->CareerHandle); + if (record != nullptr) { + unsigned int name_hash = fe_car->GetNameHash(); + unsigned int bounty = record->GetBounty(); + unsigned int fines = record->GetInfractions(true).GetFineValue(); + unsigned short unserved = stable->GetNumInfractionsOnCar(fe_car->Handle, true); + unsigned int evaded = record->GetNumEvadedPursuits(); + unsigned int busted = record->GetNumBustedPursuits(); + unsigned int status_hash; + if (record->TheImpoundData.IsImpounded()) { status_hash = 0x35E4E01F; } + else if (busted != 0) { status_hash = 0x2089554C; } + else { status_hash = 0xD3EFE2E5; } + AddDatum(new(__FILE__, __LINE__) RapSheetVDDatum(name_hash, status_hash, bounty, fines, unserved, evaded, busted)); + numCars++; + } + } + } + for (int i = numCars; i < GetWidth() * GetHeight(); i++) { + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_CAR_%d", i + 1), GetLocalizedString(0x73AF0386)); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE_%d", i + 1), ""); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE2_%d", i + 1), ""); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE3_%d", i + 1), ""); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE4_%d", i + 1), ""); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE5_%d", i + 1), ""); + FEPrintf(GetPackageName(), FEngHashString("RAPSHEET_VALUE6_%d", i + 1), ""); + } + SetInitialPosition(0); + RefreshHeader(); +} +void uiRapSheetVD::RefreshHeader() { + UserProfile* prof = FEDatabase->GetUserProfile(0); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + HighScoresDatabase* scores = prof->GetHighScores(); + FEPrintf(GetPackageName(), 0x1232703A, GetLocalizedString(0xE21D083C), prof->GetCareer()->GetCaseFileName()); + FEPrintf(GetPackageName(), 0x1FFFB988, GetLocalizedString(0x6031106E), prof->GetProfileName()); + FEPrintf(GetPackageName(), 0x1FFFB989, GetLocalizedString(0x364E4525), stable->GetTotalBounty()); + unsigned int prefName = stable->GetPreferedCarName(); + if (prefName != 0) { FEPrintf(GetPackageName(), 0x1FFFB98A, GetLocalizedString(0xFBAF89DF), GetLocalizedString(stable->GetPreferedCarName())); } + else { FEPrintf(GetPackageName(), 0x1FFFB98A, GetLocalizedString(0xFBAF89DF), GetLocalizedString(0x73AF0386)); } + unsigned int prevCar = scores->GetPreviouslyPursuedCarNameHash(); + if (prevCar != 0) { FEPrintf(GetPackageName(), 0x1FFFB98B, GetLocalizedString(0x074A86E1), GetLocalizedString(scores->GetPreviouslyPursuedCarNameHash())); } + else { FEPrintf(GetPackageName(), 0x1FFFB98B, GetLocalizedString(0x074A86E1), GetLocalizedString(0x73AF0386)); } + ArrayScrollerMenu::RefreshHeader(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.hpp index 4046b4065..3fe8ff425 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRapSheetVD.hpp @@ -5,6 +5,68 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +struct RapSheetVDArraySlot : public ArraySlot { + FEString* pCar; // offset 0x14, size 0x4 + FEString* pBounty; // offset 0x18, size 0x4 + FEString* pFines; // offset 0x1C, size 0x4 + FEString* pUnserved; // offset 0x20, size 0x4 + FEString* pToDrive; // offset 0x24, size 0x4 + FEString* pEvaded; // offset 0x28, size 0x4 + FEString* pBusted; // offset 0x2C, size 0x4 + + RapSheetVDArraySlot(FEString* CarName, FEString* Bounty, FEString* Fines, FEString* Unserved, FEString* ToDrive, FEString* Evaded, FEString* Busted) + : ArraySlot(reinterpret_cast(ToDrive)) // + , pCar(CarName) // + , pBounty(Bounty) // + , pFines(Fines) // + , pUnserved(Unserved) // + , pToDrive(ToDrive) // + , pEvaded(Evaded) // + , pBusted(Busted) + {} + ~RapSheetVDArraySlot() override {} + void Update(ArrayDatum* datum, bool isSelected) override; +}; + +struct RapSheetVDDatum : public ArrayDatum { + unsigned int CarHash; // offset 0x24, size 0x4 + unsigned int StatusHash; // offset 0x28, size 0x4 + int Bounty; // offset 0x2C, size 0x4 + int Fines; // offset 0x30, size 0x4 + int Unserved; // offset 0x34, size 0x4 + int Evaded; // offset 0x38, size 0x4 + int Busted; // offset 0x3C, size 0x4 + + RapSheetVDDatum(unsigned int carHash, unsigned int statusHash, int bounty, int fines, int unserved, int evaded, int busted) + : ArrayDatum(0, 0) // + , CarHash(carHash) // + , StatusHash(statusHash) // + , Bounty(bounty) // + , Fines(fines) // + , Unserved(unserved) // + , Evaded(evaded) // + , Busted(busted) + {} + ~RapSheetVDDatum() override {} + void NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) override; + + unsigned int getCarHash() { return CarHash; } + unsigned int getStatusHash() { return StatusHash; } + int getBounty() { return Bounty; } + int getFines() { return Fines; } + int getUnserved() { return Unserved; } + int getEvaded() { return Evaded; } + int getBusted() { return Busted; } +}; + +struct uiRapSheetVD : public ArrayScrollerMenu { + uiRapSheetVD(ScreenConstructorData* sd); + ~uiRapSheetVD() override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup(); + void RefreshHeader() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.cpp index e69de29bb..6a4f1cbd7 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.cpp @@ -0,0 +1,248 @@ +#include "uiRepSheetBounty.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOff.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +struct FEObject; +struct FEMultiImage; + +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +FEImage* FEngFindImage(const char* pkg_name, int hash); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, bool); +void FEngSetRotationZ(FEObject* obj, float angle); +void FEngSetTextureHash(FEImage* image, unsigned int hash); +int FEngMapJoyParamToJoyport(int feng_param); + +void RaceStarterStartCareerFreeRoam() asm("StartCareerFreeRoam__11RaceStarter"); +void InGameAnyTutorialScreenLaunchMovie(const char*, const char*) asm("LaunchMovie__23InGameAnyTutorialScreenPCcT1"); + +extern unsigned int iCurrentViewBin; +extern unsigned int theMarker; +extern const char* gTUTORIAL_MOVIE_BOUNTY; + +// FEngSetInvisible/FEngSetVisible inlines defined in uiMain.cpp +// FEngSetTextureHash inline already defined in uiOptionsScreen.cpp + +uiRepSheetBounty::uiRepSheetBounty(ScreenConstructorData* sd) + : ArrayScrollerMenu(sd, 3, 3, true) { + bIsInGame = sd->Arg != 0; + TrackMapStreamer = nullptr; + TrackMap = nullptr; + tutorialPlaying = false; + TrackMapStreamer = new (__FILE__, __LINE__) UITrackMapStreamer(); + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + FEImage* img = FEngFindImage(GetPackageName(), FEngHashString("EVENT_ICON_%d", i + 1)); + if (img) { + AddSlot(new (__FILE__, __LINE__) ImageArraySlot(img)); + } + } + TrackMap = reinterpret_cast< FEMultiImage* >( + FEngFindObject(GetPackageName(), FEngHashString("TRACK_MAP"))); + if (bIsInGame) { + FEngSetLanguageHash(GetPackageName(), 0xbde82fcc, 0x6ddfa694); + } else { + FEngSetLanguageHash(GetPackageName(), 0xbde82fcc, 0xe451941e); + } + Setup(); +} + +eMenuSoundTriggers uiRepSheetBounty::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (msg == 0x7b6b89d7 && bIsInGame) return static_cast(-1); + if (currentDatum->IsLocked()) return static_cast(7); + return maybe; +} + +void uiRepSheetBounty::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) { + int currentIndex = data.TraversebList(currentDatum) - 1; + ArrayScrollerMenu::NotificationMessage(msg, obj, param1, param2); + switch (msg) { + case 0xc407210: { + BountyDatum* d = static_cast(GetCurrentDatum()); + if (GetNumDatum() < 1) { + return; + } + if (d->IsLocked()) { + return; + } + if (!bIsInGame) { + signed char joyPort = static_cast(FEngMapJoyParamToJoyport(param1)); + FEDatabase->SetPlayersJoystickPort(0, joyPort); + } + const char* dialog = ""; + if (bIsInGame) { + dialog = "InGameDialog.fng"; + } + DialogInterface::ShowTwoButtons(GetPackageName(), dialog, static_cast(1), + 0x70e01038, 0x417b25e4, 0xd05fc3a3, 0x34dc1bcf, 0x34dc1bcf, + static_cast(1), 0xcd195d0b); + return; + } + case 0xc519bfc3: + if (bIsInGame) { + return; + } + tutorialPlaying = true; + FEngSetScript(GetPackageName(), 0x99344537, 0x16a259, true); + FEAnyTutorialScreen::LaunchMovie(gTUTORIAL_MOVIE_BOUNTY, GetPackageName()); + return; + case 0xd05fc3a3: { + CareerSettings* career = FEDatabase->GetCareerSettings(); + if (((career->SpecialFlags >> 10) & 1) == 0) { + if (bIsInGame) { + if (TrackMapStreamer != nullptr) { + delete TrackMapStreamer; + } + TrackMapStreamer = nullptr; + InGameAnyTutorialScreenLaunchMovie(gTUTORIAL_MOVIE_BOUNTY, GetPackageName()); + FEngSetInvisible("IG_BL_TRACKMAP.fng", 0x2716cdbf); + } else { + FEAnyTutorialScreen::LaunchMovie(gTUTORIAL_MOVIE_BOUNTY, GetPackageName()); + } + FEngSetScript(GetPackageName(), 0x99344537, 0x16a259, true); + FEngSetInvisible(GetPackageName(), FEngHashString("MASTERBLASTER")); + career->SpecialFlags |= 0x400; + return; + } + cFEng::Get()->QueueGameMessage(0xc3960eb9, GetPackageName(), 0xff); + return; + } + case 0xc3960eb9: + if (tutorialPlaying) { + tutorialPlaying = false; + FEngSetScript(GetPackageName(), 0x99344537, 0x1744b3, true); + return; + } + if (bIsInGame) { + FEngSetVisible("IG_BL_TRACKMAP.fng", 0x2716cdbf); + GManager::Get().WarpToMarker(theMarker, true); + new ERaceSheetOff(); + return; + } + GManager::Get().OverrideFreeRoamStartMarker(theMarker); + GManager::Get().QueueFreeRoamPursuit(0.0f); + GManager::Get().QueueFreeRoamPursuit(0.0f); + RaceStarterStartCareerFreeRoam(); + return; + case 0x911ab364: + if (bIsInGame) { + cFEng::Get()->QueuePackageSwitch("IG_BL_MAIN", 1, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("BL_MAIN", 0, 0, false); + } + return; + case 0xc98356ba: + if (TrackMapStreamer != nullptr) { + TrackMapStreamer->UpdateAnimation(); + } + return; + case 0x72619778: + case 0x911c0a4b: + case 0x9120409e: + case 0xb5971bf1: + break; + case 0x34dc1bcf: + return; + default: + return; + } + int newIndex = data.TraversebList(currentDatum) - 1; + if (currentIndex != newIndex && currentDatum != nullptr) { + RefreshTrack(); + } +} + +void uiRepSheetBounty::Setup() { + ClearData(); + unsigned int bin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + for (int i = 0; i < 9; i++) { + unsigned int check_hash = FEngHashString("CHECK_ICON_%d", i + 1); + FEngSetInvisible(GetPackageName(), check_hash); + } + for (unsigned int i = 0; i < GManager::Get().GetNumBountySpawnMarkers(); i++) { + int index = GManager::Get().GetBountySpawnMarkerTag(i); + orderedList[index - 1] = static_cast(i); + } + for (unsigned int i = 0; i < GManager::Get().GetNumBountySpawnMarkers(); i++) { + if (i < 4 || (i < 8 && bin < 13) || bin < 9) { + BountyDatum* datum = new BountyDatum( + FEDatabase->GetBountyIconHash(i + 1), + FEDatabase->GetBountyHeaderHash(i + 1), + i); + datum->index = static_cast(static_cast(orderedList[i])); + AddDatum(datum); + } + } + SetDescLabel(0xb5117fde); + SetInitialPosition(0); + RefreshTrack(); + RefreshHeader(); +} + +void uiRepSheetBounty::RefreshTrack() { + if (GetCurrentDatum() != nullptr) { + bVector2 position; + float rotation = 0.0f; + if (TrackMapStreamer != nullptr) { + TrackMapStreamer->Init(nullptr, TrackMap, 0, 0); + TrackMapStreamer->ResetZoom(false); + } + BountyDatum* d = static_cast(GetCurrentDatum()); + unsigned int key = GManager::Get().GetBountySpawnMarker(d->index); + GManager::Get().CalcMapCoordsForMarker(key, position, rotation); + if (TrackMapStreamer != nullptr) { + TrackMapStreamer->PanTo(position); + bVector2 zoom(0.5f, 0.5f); + TrackMapStreamer->ZoomTo(zoom); + } + FEngSetRotationZ(FEngFindObject(GetPackageName(), 0xaf51dd73), rotation); + } +} + +void uiRepSheetBounty::RefreshHeader() { + ArrayScrollerMenu::RefreshHeader(); + ArrayDatum* currentDatum = GetCurrentDatum(); + FEPrintf(GetPackageName(), 0x5a856a34, "%d", data.GetNodeNumber(currentDatum)); + FEPrintf(GetPackageName(), 0x2d4d22c8, "%d", GetNumDatum()); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + FEPrintf(GetPackageName(), 0xb514e2d8, "%s %d", GetLocalizedString(0xce6b99b1), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), 0xf91a59f6, "%s %d", GetLocalizedString(0x73b79e0), FEDatabase->GetCareerSettings()->GetCash()); + int loc_tag = GManager::Get().GetBountySpawnMarkerTag(data.GetNodeNumber(currentDatum) - 1); + FEngSetTextureHash(GetPackageName(), 0xf97ec5d5, + FEDatabase->GetBountyIconHash(loc_tag)); + BountyDatum* d = static_cast(currentDatum); + if (d != nullptr) { + if (d->IsLocked()) { + cFEng::Get()->QueuePackageMessage(0xc5dd9d68, GetPackageName(), nullptr); + } else { + cFEng::Get()->QueuePackageMessage(0x38091fa1, GetPackageName(), nullptr); + } + FEngSetLanguageHash(GetPackageName(), 0x28049d6, + FEDatabase->GetBountyDescHash(data.GetNodeNumber(currentDatum))); + for (int i = 0; i < GetNumSlots(); i++) { + ArrayDatum* datum = GetDatumAt(i + GetStartDatumNum()); + unsigned int check_hash = FEngHashString("CHECK_ICON_%d", i + 1); + FEngSetTextureHash(GetPackageName(), check_hash, 0x18ed48); + if (datum != nullptr) { + FEngSetInvisible(GetPackageName(), check_hash); + } + } + } +} + +void BountyDatum::NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) { + if (msg == 0xc407210) { + theMarker = GManager::Get().GetBountySpawnMarker(static_cast< unsigned int >(index)); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.hpp index 3646bfd62..b09e58f69 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetBounty.hpp @@ -5,6 +5,44 @@ #pragma once #endif +#include +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" + +struct UITrackMapStreamer; +struct FEMultiImage; + +// total size: 0x178 +struct uiRepSheetBounty : public ArrayScrollerMenu { + bool bIsInGame; // offset 0xE8, size 0x1 + UITrackMapStreamer* TrackMapStreamer; // offset 0xEC, size 0x4 + FEMultiImage* TrackMap; // offset 0xF0, size 0x4 + char orderedList[128]; // offset 0xF4, size 0x80 + bool tutorialPlaying; // offset 0x174, size 0x1 + + uiRepSheetBounty(ScreenConstructorData* sd); + ~uiRepSheetBounty() override { + delete TrackMapStreamer; + } + + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; + + void Setup(); + void RefreshTrack(); + void RefreshHeader() override; +}; + +struct BountyDatum : public ArrayDatum { + int index; // offset 0x24, size 0x4 + + BountyDatum(unsigned int hash, unsigned int desc, unsigned int idx) + : ArrayDatum(hash, desc) // + , index(idx) { + } + ~BountyDatum() override {} + void NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.cpp index e69de29bb..ec2eac2e6 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.cpp @@ -0,0 +1,336 @@ +#include "uiRepSheetMain.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOff.hpp" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +struct FEObject; + +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +FEImage* FEngFindImage(const char* pkg_name, int hash); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetTextureHash(FEImage* image, unsigned int hash); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, bool); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +int FEngSNPrintf(char* buffer, int buf_size, const char* fmt, ...); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +int GetCurrentLanguage(); +void eUnloadStreamingTexture(unsigned int* textures, int count); +void WaitForResourceLoadingComplete(); +unsigned char FEngGetLastButton(const char* pkg_name); + +extern unsigned int iCurrentViewBin; +extern int selection; + +struct RepSheetIcon : public IconOption { + unsigned int id; + + RepSheetIcon(unsigned int tex_hash, unsigned int name_hash, unsigned int the_id) + : IconOption(tex_hash, name_hash, 0) { + id = the_id; + } + + ~RepSheetIcon() override {} + + void React(const char* pkg, unsigned int data, FEObject* obj, unsigned int p1, unsigned int p2) override; +}; + +void RepSheetIcon::React(const char* pkg, unsigned int data, FEObject* obj, unsigned int p1, unsigned int p2) { + if (data != 0xc407210) return; + selection = id; +} + +uiRepSheetMain::uiRepSheetMain(ScreenConstructorData* sd) + : IconScrollerMenu(sd) + , bIsInGame(sd->Arg != 0), // + bBossAvailable(false), // + bBossBeaten(false), // + DefeatedTextureHash(0), // + RivalStreamer(sd->PackageFilename, bIsInGame) { + if (bIsInGame) { + Options.SetIdleColor(0xffffae40); + Options.SetFadeColor(0x00ffae40); + new EFadeScreenOff(0x14035fb); + } else { + RideInfo ride; + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + stable->BuildRideForPlayer(FEDatabase->GetCareerSettings()->GetCurrentCar(), 0, &ride); + CarViewer::SetRideInfo(&ride, SET_RIDE_INFO_REASON_LOAD_CAR, eCARVIEWER_PLAYER1_CAR); + GarageMainScreen::GetInstance()->CancelCameraPush(); + } + Setup(); +} + +uiRepSheetMain::~uiRepSheetMain() { + eUnloadStreamingTexture(DefeatedTextureHash); + WaitForResourceLoadingComplete(); +} + +eMenuSoundTriggers uiRepSheetMain::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (bBossBeaten && msg == 0x7b6b89d7) { + return static_cast< eMenuSoundTriggers >(-1); + } + return maybe; +} + +void uiRepSheetMain::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + + switch (msg) { + case 0x911c0a4b: + ScrollRival(static_cast(1)); + return; + case 0x72619778: + ScrollRival(static_cast(-1)); + return; + case 0xe1fde1d1: { + if (PrevButtonMessage == 0xc407210) { + goto handle_selection; + } + if (PrevButtonMessage == 0x911ab364) { + goto handle_eracesheet; + } + return; + handle_selection: + if (selection == 0) { + if (!bIsInGame) { + cFEng::Get()->QueuePackageSwitch("SafeHouseRaceSheet.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("InGameRaceSheet.fng", 1, 0, false); + } + } else if (selection == 1) { + if (!bIsInGame) { + cFEng::Get()->QueuePackageSwitch("SafeHouseMilestones.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("InGameMilestones.fng", 1, 0, false); + } + } else if (selection == 2) { + if (!bIsInGame) { + cFEng::Get()->QueuePackageSwitch("SafeHouseBounty.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("InGameBounty.fng", 1, 0, false); + } + } else if (selection == 4) { + if (!bIsInGame) { + cFEng::Get()->QueuePackageSwitch("SafeHouseRivalBio.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("InGameRivalBio.fng", 1, 0, false); + } + } + return; + handle_eracesheet: + if (bIsInGame) { + new ERaceSheetOff(); + return; + } + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + return; + } + case 0xc519bfc3: + if (bBossBeaten) { + return; + } + if (!bBossAvailable) { + return; + } + if (!bIsInGame) { + cFEng::Get()->QueuePackageSwitch("SafeHouseRivalChallenge.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("InGameRivalChallenge.fng", 1, 0, false); + } + return; + default: + return; + } +} + +void uiRepSheetMain::Setup() { + if (FEDatabase->GetCareerSettings()->GetCurrentBin() == 0xf) { + FEngSetInvisible(GetPackageName(), 0x47b22fca); + FEngSetInvisible(GetPackageName(), 0x72ad598c); + } + + AddOption(new RepSheetIcon(0xefc9662e, 0x84e4a54c, 0)); + AddOption(new RepSheetIcon(0xd807e9b3, 0x216f1b81, 1)); + AddOption(new RepSheetIcon(0x021a4b0c, 0xe451941e, 2)); + AddOption(new RepSheetIcon(0xe97e4e83, 0x2d159737, 4)); + + selection = 0; + + int lastButton = FEngGetLastButton(GetPackageName()); + + if (bFadeInIconsImmediately) { + Options.StartFadeIn(); + } + + Options.SetInitialPos(lastButton); + IconScrollerMenu::RefreshHeader(); + + if (bIsInGame) { + FEngSetLanguageHash(GetPackageName(), 0xb71b576d, 0x2e3919e9); + } else { + FEngSetLanguageHash(GetPackageName(), 0xb71b576d, 0xcace5999); + } + + pRivalImg = FEngFindImage(GetPackageName(), 0xc1f62308); + pTagImg = FEngFindImage(GetPackageName(), 0xf5a2a087); + + RivalStreamer.Init(iCurrentViewBin, pRivalImg, pTagImg, nullptr); + + FEngSetInvisible(GetPackageName(), 0x7fe4020f); + + DefeatedTextureHash = GetDefeatedTexture(); + FEngSetTextureHash(GetPackageName(), 0x7fe4020f, DefeatedTextureHash); + + eLoadStreamingTexture(DefeatedTextureHash, TextureLoadedCallback, reinterpret_cast< unsigned int >(this), 0); + + UpdateInfo(); +} + +void uiRepSheetMain::NotifyTextureLoaded() { + FEngSetVisible(FEngFindObject(GetPackageName(), 0x7FE4020F)); +} + +unsigned int uiRepSheetMain::GetDefeatedTexture() { + int lang = GetCurrentLanguage(); + switch (lang) { + case 1: return 0x87b81cd; + case 2: return 0x87b846e; + case 3: return 0x87b8ece; + case 4: return 0x87bb8d4; + case 5: return 0x87b79bd; + case 6: return 0x87bb9bf; + case 7: return 0x87b7723; + case 12: return 0x87babfb; + case 13: return 0x87b80ad; + case 8: return 0x87b96bc; + case 9: return 0x87b73c4; + case 10: return 0x87b90ab; + case 11: return 0x87bbc0d; + default: return 0x87b7d0a; + } +} + +void uiRepSheetMain::UpdateInfo() { + GRaceBin* bin = GRaceDatabase::Get().GetBinNumber(iCurrentViewBin); + int completed_races = bin->GetAwardedRaceWins(); + int required_races = bin->GetRequiredRaceWins(); + int completed_challenges = bin->GetCompletedChallenges(); + int required_challenges = bin->GetRequiredChallenges(); + int completed_bounty = FEDatabase->GetPlayerCarStable(0)->GetTotalBounty(); + int required_bounty = bin->GetRequiredBounty(); + + FEPrintf(GetPackageName(), 0x15d80973, "%d", completed_races); + FEPrintf(GetPackageName(), 0xd802fba8, "%d", completed_challenges); + FEPrintf(GetPackageName(), 0x322b18f9, "%u", completed_bounty); + FEPrintf(GetPackageName(), 0xde7ad199, "%d", required_races); + FEPrintf(GetPackageName(), 0x7242962e, "%d", required_challenges); + FEPrintf(GetPackageName(), 0x055c6e5f, "%u", required_bounty); + + if (completed_races >= required_races) { + FEngSetScript(GetPackageName(), 0x4c3b1536, 0xe6361f46, true); + } else { + FEngSetScript(GetPackageName(), 0x4c3b1536, 0x16a259, true); + } + if (completed_challenges >= required_challenges) { + FEngSetScript(GetPackageName(), 0x4c3b1537, 0xe6361f46, true); + } else { + FEngSetScript(GetPackageName(), 0x4c3b1537, 0x16a259, true); + } + if (completed_bounty >= required_bounty) { + FEngSetScript(GetPackageName(), 0x4c3b1538, 0xe6361f46, true); + } else { + FEngSetScript(GetPackageName(), 0x4c3b1538, 0x16a259, true); + } + + char buf[32]; + if (bIsInGame) { + FEngSNPrintf(buf, 32, GetLocalizedString(0x96ca2471), iCurrentViewBin); + } else { + FEngSNPrintf(buf, 32, GetLocalizedString(0x3a64de21), iCurrentViewBin); + } + FEPrintf(GetPackageName(), 0x242657ce, "%s", buf); + + const char* rival_name = GetLocalizedString(FEngHashString("BL_NAME_%d", iCurrentViewBin)); + const char* challenge_blurb = GetLocalizedString(FEngHashString("BL_BLURB_%d", iCurrentViewBin)); + FEPrintf(GetPackageName(), 0x7ac3d0c9, "%s", rival_name); + FEPrintf(GetPackageName(), 0x79cf0442, "%s", challenge_blurb); + + unsigned int bossRaceCount = bin->GetBossRaceCount(); + bBossAvailable = false; + for (unsigned int i = 0; i < bossRaceCount; i++) { + unsigned int hash = bin->GetBossRaceHash(i); + GRaceParameters* race = GRaceDatabase::Get().GetRaceFromHash(hash); + int available = race->GetIsAvailable(GRace::kRaceContext_Career); + bBossAvailable = (bBossAvailable != 0) | (available != 0); + } + + bBossBeaten = false; + if (FEDatabase->GetCareerSettings()->HasBeatenCareer() || + static_cast(iCurrentViewBin) > static_cast(FEDatabase->GetCareerSettings()->GetCurrentBin())) { + bBossBeaten = true; + } + + FEngSetInvisible(GetPackageName(), 0x34d4433b); + + if (bBossBeaten) { + FEngSetInvisible(GetPackageName(), 0x55f6aa1a); + FEngSetVisible(GetPackageName(), 0x34d4433b); + cFEng::Get()->QueuePackageMessage(0xb4c144b1, GetPackageName(), nullptr); + } else { + if (bBossAvailable) { + unsigned int msgHash = FEngHashString("BOSS_AVAILABLE"); + cFEng::Get()->QueuePackageMessage(msgHash, GetPackageName(), nullptr); + FEngSetVisible(GetPackageName(), 0x55f6aa1a); + } else { + unsigned int msgHash = FEngHashString("BOSS_UNAVAILABLE"); + cFEng::Get()->QueuePackageMessage(msgHash, GetPackageName(), nullptr); + FEngSetInvisible(GetPackageName(), 0x55f6aa1a); + } + } + + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + FEPrintf(GetPackageName(), 0xb514e2d8, "%s %d", GetLocalizedString(0xce6b99b1), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), 0xf91a59f6, "%s %d", GetLocalizedString(0x73b79e0), FEDatabase->GetCareerSettings()->GetCash()); +} + +void uiRepSheetMain::ScrollRival(eScrollDir dir) { + unsigned char currentBin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + unsigned int oldViewBin = iCurrentViewBin; + if (currentBin == 15) { + return; + } + if (dir == static_cast(1)) { + iCurrentViewBin--; + if (static_cast(iCurrentViewBin) < 0 || iCurrentViewBin < currentBin) { + iCurrentViewBin = 15; + } + } else if (dir == static_cast(-1)) { + iCurrentViewBin++; + if (static_cast(iCurrentViewBin) > 15) { + iCurrentViewBin = currentBin; + } + } + if (oldViewBin != iCurrentViewBin) { + if (dir == static_cast(1)) { + FEngSetScript(GetPackageName(), 0xc1f62308, 0xaf9d73f2, true); + } else if (dir == static_cast(-1)) { + FEngSetScript(GetPackageName(), 0xc1f62308, 0x9e5e6b5f, true); + } + RivalStreamer.Init(iCurrentViewBin, pRivalImg, pTagImg, nullptr); + UpdateInfo(); + } +} + +void uiRepSheetMain::TextureLoadedCallback(unsigned int tex) { + reinterpret_cast< uiRepSheetMain* >(tex)->NotifyTextureLoaded(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.hpp index d7a30a803..87b6d5a70 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMain.hpp @@ -5,6 +5,36 @@ #pragma once #endif +#include +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp" + +struct FEImage; +enum eScrollDir; + +// total size: 0x1C0 +struct uiRepSheetMain : public IconScrollerMenu { + bool bIsInGame; // offset 0x16C, size 0x1 + bool bBossAvailable; // offset 0x170, size 0x1 + bool bBossBeaten; // offset 0x174, size 0x1 + FEImage* pRivalImg; // offset 0x178, size 0x4 + FEImage* pTagImg; // offset 0x17C, size 0x4 + unsigned int DefeatedTextureHash; // offset 0x180, size 0x4 + uiRepSheetRivalStreamer RivalStreamer; // offset 0x184, size 0x3C + + static void TextureLoadedCallback(unsigned int arg); + + uiRepSheetMain(ScreenConstructorData* sd); + ~uiRepSheetMain() override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + + void NotifyTextureLoaded(); + unsigned int GetDefeatedTexture(); + void UpdateInfo(); + void ScrollRival(eScrollDir dir); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.cpp index e69de29bb..55b725f0f 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.cpp @@ -0,0 +1,388 @@ +#include "uiRepSheetMilestones.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyTutorialScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOff.hpp" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +struct FEObject; +struct FEMultiImage; + +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +FEImage* FEngFindImage(const char* pkg_name, int hash); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +unsigned int FEngHashString(const char* format, ...); +const char* GetLocalizedString(unsigned int hash); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, bool); +void FEngSetRotationZ(FEObject* obj, float angle); +void FEngSetTextureHash(FEImage* image, unsigned int hash); +int FEngMapJoyParamToJoyport(int feng_param); + +void RaceStarterStartCareerFreeRoam() asm("StartCareerFreeRoam__11RaceStarter"); +void InGameAnyTutorialScreenLaunchMovie(const char*, const char*) asm("LaunchMovie__23InGameAnyTutorialScreenPCcT1"); + +extern unsigned int iCurrentViewBin; +extern const char* gTUTORIAL_MOVIE_PURSUIT; + +// FEngSetInvisible/FEngSetVisible/FEngSetTextureHash inlines defined in uiMain.cpp + +MilestoneDatum* theMilestone; + +void MilestoneDatum::NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, + unsigned long param2) { + if (msg != 0xc407210) { + return; + } + if (!IsChecked()) { + theMilestone = this; + } else { + theMilestone = nullptr; + } +} + +uiRepSheetMilestones::uiRepSheetMilestones(ScreenConstructorData* sd) + : ArrayScrollerMenu(sd, 3, 3, true) { + bIsInGame = sd->Arg != 0; + TrackMapStreamer = nullptr; + theMilestone = nullptr; + TrackMapStreamer = new (__FILE__, __LINE__) UITrackMapStreamer(); + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + FEImage* img = FEngFindImage(GetPackageName(), FEngHashString("EVENT_ICON_%d", i + 1)); + if (img) { + AddSlot(new (__FILE__, __LINE__) ImageArraySlot(img)); + } + } + TrackMap = reinterpret_cast< FEMultiImage* >( + FEngFindObject(GetPackageName(), FEngHashString("TRACK_MAP"))); + if (bIsInGame) { + FEngSetLanguageHash(GetPackageName(), 0xbde82fcc, 0x578b767b); + } else { + FEngSetLanguageHash(GetPackageName(), 0xbde82fcc, 0x216f1b81); + } + Setup(); +} + +eMenuSoundTriggers uiRepSheetMilestones::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + eMenuSoundTriggers result = ArrayScrollerMenu::NotifySoundMessage(msg, maybe); + if (msg == 0x7b6b89d7 && bIsInGame) { + return static_cast(-1); + } + return result; +} + +void uiRepSheetMilestones::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) { + int currentIndex = data.TraversebList(currentDatum) - 1; + ArrayScrollerMenu::NotificationMessage(msg, obj, param1, param2); + if (msg == 0x911c0a4b) { + goto refresh; + } + if (msg <= 0x911c0a4b) { + if (msg == 0x34dc1bcf) { + return; + } + if (msg <= 0x34dc1bcf) { + if (msg == 0xc407210) { + goto handleActivate; + } + return; + } + if (msg == 0x72619778) { + goto refresh; + } + if (msg == 0x911ab364) { + goto handlePackageSwitch; + } + return; + } + if (msg == 0xc3960eb9) { + goto handleWarp; + } + if (msg <= 0xc3960eb9) { + if (msg == 0x9120409e || msg == 0xb5971bf1) { + goto refresh; + } + return; + } + if (msg == 0xc98356ba) { + goto handleUpdateAnimation; + } + if (msg <= 0xc98356ba) { + if (msg == 0xc519bfc3) { + goto handleTutorial; + } + return; + } + if (msg == 0xd05fc3a3) { + goto handleTutorialAccept; + } + return; + +handleActivate: + { + if (theMilestone == nullptr) { + return; + } + if (theMilestone->IsChecked()) { + g_pEAXSound->PlayUISoundFX(static_cast(7)); + return; + } + if (!bIsInGame) { + signed char joyPort = static_cast< signed char >(FEngMapJoyParamToJoyport(param1)); + FEDatabase->SetPlayersJoystickPort(0, joyPort); + } + const char* dialog = ""; + if (bIsInGame) { + dialog = "InGameDialog.fng"; + } + unsigned int messageHash = 0xa5a8409a; + if (theMilestone->GetType() != 0) { + messageHash = 0xbf1dcd38; + } + DialogInterface::ShowTwoButtons(GetPackageName(), dialog, static_cast(1), + 0x70e01038, 0x417b25e4, 0xd05fc3a3, 0x34dc1bcf, 0x34dc1bcf, + static_cast(1), messageHash); + return; + } + +handleTutorial: + { + if (bIsInGame) { + return; + } + FEngSetScript(GetPackageName(), 0x99344537, 0x16a259, true); + FEAnyTutorialScreen::LaunchMovie(gTUTORIAL_MOVIE_PURSUIT, GetPackageName()); + return; + } + +handleWarp: + { + if (bIsInGame) { + FEngSetVisible(FEngFindObject("InGameBackground.fng", 0x2716cdbf)); + } + FEngSetScript(GetPackageName(), 0x99344537, 0x1744b3, true); + if (theMilestone == nullptr) { + return; + } + unsigned int marker; + bool pursuit = false; + if (theMilestone->GetType() == 0) { + GMilestone* pMilestone = theMilestone->my_milestone; + marker = pMilestone->GetJumpMarkerKey(); + pursuit = true; + } else { + SpeedTrapDatum* st = static_cast(theMilestone); + GSpeedTrap* pSpeedTrap = st->my_speedtrap; + marker = pSpeedTrap->GetJumpMarkerKey(); + } + if (bIsInGame) { + new ERaceSheetOff(); + GManager::Get().WarpToMarker(marker, pursuit); + return; + } + GManager::Get().OverrideFreeRoamStartMarker(marker); + if (pursuit) { + GManager::Get().QueueFreeRoamPursuit(0.0f); + } + RaceStarterStartCareerFreeRoam(); + return; + } + +handleUpdateAnimation: + if (TrackMapStreamer != nullptr) { + TrackMapStreamer->UpdateAnimation(); + } + return; + +refresh: + { + int newIndex = data.TraversebList(currentDatum) - 1; + if (currentIndex != newIndex && currentDatum != nullptr) { + RefreshTrack(); + } + return; + } + +handleTutorialAccept: + { + CareerSettings* career = FEDatabase->GetCareerSettings(); + if (((career->SpecialFlags >> 9) & 1) == 0) { + if (bIsInGame) { + if (TrackMapStreamer != nullptr) { + delete TrackMapStreamer; + } + TrackMapStreamer = nullptr; + InGameAnyTutorialScreenLaunchMovie(gTUTORIAL_MOVIE_PURSUIT, GetPackageName()); + FEngSetInvisible(FEngFindObject("InGameBackground.fng", 0x2716cdbf)); + } else { + FEAnyTutorialScreen::LaunchMovie(gTUTORIAL_MOVIE_PURSUIT, GetPackageName()); + } + FEngSetScript(GetPackageName(), 0x99344537, 0x16a259, true); + FEngSetInvisible(FEngFindObject(GetPackageName(), FEngHashString("TRACK_MAP"))); + career->SpecialFlags |= 0x200; + return; + } + cFEng::Get()->QueueGameMessage(0xc3960eb9, GetPackageName(), 0xff); + return; + } + +handlePackageSwitch: + if (bIsInGame) { + cFEng::Get()->QueuePackageSwitch("InGameReputationOverview.fng", 1, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("SafeHouseReputationOverview.fng", 0, 0, false); + } + return; +} + +void uiRepSheetMilestones::Setup() { + ClearData(); + GMilestone* ms = GManager::mObj->GetFirstMilestone(false, iCurrentViewBin); + while (ms != nullptr) { + AddMilestone(ms); + if (ms->GetIsLocked()) { + GetDatumAt(GetNumDatum() - 1)->SetLocked(true); + } + if (ms->GetIsAwarded()) { + GetDatumAt(GetNumDatum() - 1)->SetChecked(true); + } + ms = GManager::mObj->GetNextMilestone(ms, false, iCurrentViewBin); + } + GSpeedTrap* st = GManager::mObj->GetFirstSpeedTrap(false, iCurrentViewBin); + while (st != nullptr) { + AddSpeedtrap(st); + if (st->IsFlagClear(GSpeedTrap::kFlag_Unlocked)) { + GetDatumAt(GetNumDatum() - 1)->SetLocked(true); + } + if (st->IsFlagSet(GSpeedTrap::kFlag_Completed)) { + GetDatumAt(GetNumDatum() - 1)->SetChecked(true); + } + st = GManager::mObj->GetNextSpeedTrap(st, false, iCurrentViewBin); + } + SetDescLabel(0xB5117FDE); + SetInitialPosition(0); + RefreshTrack(); + RefreshHeader(); +} + +void uiRepSheetMilestones::RefreshTrack() { + if (GetCurrentDatum() != nullptr) { + bVector2 position; + float rotation = 0.0f; + if (TrackMapStreamer != nullptr) { + TrackMapStreamer->Init(nullptr, TrackMap, 0, 0); + TrackMapStreamer->ResetZoom(false); + } + MilestoneDatum* d = static_cast(GetCurrentDatum()); + unsigned int key; + if (d->GetType() == 0) { + GMilestone* pMilestone = d->my_milestone; + key = pMilestone->GetJumpMarkerKey(); + } else { + SpeedTrapDatum* sdt = static_cast(d); + GSpeedTrap* pSpeedTrap = sdt->my_speedtrap; + key = pSpeedTrap->GetJumpMarkerKey(); + } + GManager::Get().CalcMapCoordsForMarker(key, position, rotation); + if (TrackMapStreamer != nullptr) { + TrackMapStreamer->PanTo(position); + bVector2 zoom(0.5f, 0.5f); + TrackMapStreamer->ZoomTo(zoom); + } + FEngSetRotationZ(FEngFindObject(GetPackageName(), 0xaf51dd73), rotation); + } +} + +void uiRepSheetMilestones::AddMilestone(GMilestone* milestone) { + ArrayScroller* scroller = this; + MilestoneDatum* datum = new MilestoneDatum( + FEDatabase->GetMilestoneIconHash(milestone->GetTypeKey(), true), + FEDatabase->GetMilestoneHeaderHash(milestone->GetLocalizationTag()), + milestone); + scroller->AddDatum(datum); +} + +void uiRepSheetMilestones::AddSpeedtrap(GSpeedTrap* trap) { + ArrayScroller* scroller = this; + SpeedTrapDatum* datum = new SpeedTrapDatum( + FEDatabase->GetRaceIconHash(static_cast(5)), + 0xF3B3D8DC, + trap); + scroller->AddDatum(datum); +} + +void uiRepSheetMilestones::RefreshHeader() { + ArrayScrollerMenu::RefreshHeader(); + ArrayDatum* currentDatum = GetCurrentDatum(); + FEPrintf(GetPackageName(), 0x5a856a34, "%d", data.GetNodeNumber(currentDatum)); + FEPrintf(GetPackageName(), 0x2d4d22c8, "%d", data.CountElements()); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + FEPrintf(GetPackageName(), 0xb514e2d8, "%s %$d", GetLocalizedString(0xce6b99b1), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), 0xf91a59f6, "%s %$d", GetLocalizedString(0x73b79e0), FEDatabase->GetCareerSettings()->GetCash()); + if (currentDatum != nullptr) { + MilestoneDatum* d = static_cast(currentDatum); + if (d->GetType() == 0) { + GMilestone* pMilestone = d->my_milestone; + FEngSetTextureHash(GetPackageName(), 0xf97ec5d5, + FEDatabase->GetMilestoneIconHash(pMilestone->GetTypeKey(), true)); + FEPrintf(GetPackageName(), 0xb21d69bd, "%$0.0f", pMilestone->GetBounty()); + float goal = pMilestone->GetRequiredValue(); + if (FEDatabase->IsMilestoneTimeFormat(pMilestone->GetTypeKey())) { + goal = goal * 0.001f; + } + char buf[32]; + bSNPrintf(buf, 32, "%$0.0f", goal); + FEPrintf(GetPackageName(), 0x28049d6, "%s %s", + GetLocalizedString(FEDatabase->GetMilestoneDescHash(pMilestone->GetLocalizationTag())), + buf, buf); + } else { + SpeedTrapDatum* p = static_cast(d); + GSpeedTrap* pSpeedTrap = p->my_speedtrap; + FEngSetTextureHash(GetPackageName(), 0xf97ec5d5, + FEDatabase->GetRaceIconHash(GRace::kRaceType_SpeedTrap)); + FEPrintf(GetPackageName(), 0xb21d69bd, "%$0.0f", pSpeedTrap->GetBounty()); + float value; + const char* distUnits; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + value = static_cast(static_cast(pSpeedTrap->GetTriggerSpeed() * 2.23699f)); + distUnits = GetLocalizedString(0x8569a25f); + } else { + value = static_cast(static_cast(pSpeedTrap->GetTriggerSpeed() * 3.6f)); + distUnits = GetLocalizedString(0x8569ab44); + } + char buf[32]; + bSNPrintf(buf, 32, "%$0.0f %s", value, distUnits); + FEPrintf(GetPackageName(), 0x28049d6, "%s %s", + GetLocalizedString(0xb14018bd), buf); + } + for (int i = 0; i < GetNumSlots(); i++) { + ArrayDatum* datum = GetDatumAt(i + GetStartDatumNum()); + unsigned int check_hash = FEngHashString("MEDAL_THUMB_%d", i + 1); + FEngSetInvisible(GetPackageName(), check_hash); + if (datum) { + if (datum->IsLocked()) { + FEngSetVisible(GetPackageName(), check_hash); + FEngSetTextureHash(GetPackageName(), check_hash, 0x18ed48); + } else if (datum->IsChecked()) { + FEngSetVisible(GetPackageName(), check_hash); + FEngSetTextureHash(GetPackageName(), check_hash, 0x28feadd); + } else { + FEngSetInvisible(GetPackageName(), check_hash); + } + } else { + FEngSetInvisible(GetPackageName(), check_hash); + } + } + } +} + +bool GSpeedTrap::IsFlagSet(unsigned int mask) const { return (mFlags & mask) != 0; } +bool GSpeedTrap::IsFlagClear(unsigned int mask) const { return (mFlags & mask) == 0; } diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.hpp index fbed3fdd7..b99b8263b 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetMilestones.hpp @@ -5,6 +5,71 @@ #pragma once #endif +#include +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" + +struct GMilestone; +struct GSpeedTrap; +struct UITrackMapStreamer; +struct FEMultiImage; + +// total size: 0x28 +struct MilestoneDatum : public ArrayDatum { + GMilestone* my_milestone; // offset 0x24, size 0x4 + + MilestoneDatum() + : ArrayDatum(0, 0) // + , my_milestone(nullptr) {} + + MilestoneDatum(unsigned int hash, unsigned int desc, GMilestone* milestone) + : ArrayDatum(hash, desc) // + , my_milestone(milestone) {} + + ~MilestoneDatum() override {} + + virtual unsigned int GetType() { return 0; } + void NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, + unsigned long param2) override; +}; + +// total size: 0x2C +struct SpeedTrapDatum : public MilestoneDatum { + GSpeedTrap* my_speedtrap; // offset 0x28, size 0x4 + + SpeedTrapDatum() + : MilestoneDatum() // + , my_speedtrap(nullptr) {} + + SpeedTrapDatum(unsigned int hash, unsigned int desc, GSpeedTrap* speedtrap) + : MilestoneDatum(hash, desc, nullptr) // + , my_speedtrap(speedtrap) {} + + ~SpeedTrapDatum() override {} + + unsigned int GetType() override { return 1; } +}; + +// total size: 0xF4 +struct uiRepSheetMilestones : public ArrayScrollerMenu { + bool bIsInGame; // offset 0xE8, size 0x1 + UITrackMapStreamer* TrackMapStreamer; // offset 0xEC, size 0x4 + FEMultiImage* TrackMap; // offset 0xF0, size 0x4 + + uiRepSheetMilestones(ScreenConstructorData* sd); + ~uiRepSheetMilestones() override { + delete TrackMapStreamer; + } + + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; + + void Setup(); + void RefreshTrack(); + void AddMilestone(GMilestone* pMilestone); + void AddSpeedtrap(GSpeedTrap* pSpeedTrap); + void RefreshHeader() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.cpp index e69de29bb..c0218dcab 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.cpp @@ -0,0 +1,296 @@ +#include "uiRepSheetRaceEvents.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp" +#include "Speed/Indep/Src/Gameplay/GRace.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOff.hpp" +#include "Speed/Indep/Src/Misc/FixedPoint.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/Tools/Inc/ConversionUtil.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +struct FEObject; + +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +FEImage* FEngFindImage(const char* pkg_name, int hash); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetTextureHash(FEImage* image, unsigned int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +unsigned int FEngHashString(const char* format, ...); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, bool); +const char* GetLocalizedString(unsigned int hash); +unsigned int CalcLanguageHash(const char* prefix, GRaceParameters* pRaceParams); +int FEngMapJoyParamToJoyport(int feng_param); +void StartRace(); + +extern unsigned int FEDBGetRaceIconHash(cFrontendDatabase*, GRace::Type) asm("GetRaceIconHash__17cFrontendDatabaseQ25GRace4Type"); +extern unsigned int FEDBGetRaceNameHash(cFrontendDatabase*, GRace::Type) asm("GetRaceNameHash__17cFrontendDatabaseQ25GRace4Type"); + +struct GRaceSaveInfo { + unsigned int mRaceHash; + unsigned int mFlags; + float mHighScores; + FixedPoint< unsigned short, 10, 2 > mTopSpeed; + FixedPoint< unsigned short, 10, 2 > mAverageSpeed; +}; + +extern unsigned int iCurrentViewBin; +extern GRaceParameters* theRace; + +void RaceDatum::NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, unsigned long param2) { + if (msg == 0xc407210) { + if (!IsLocked()) { + theRace = race; + } + } +} + +UISafehouseRaceSheet::UISafehouseRaceSheet(ScreenConstructorData* sd) + : ArrayScrollerMenu(sd, 3, 3, true) { + bIsInGame = sd->Arg != 0; + currentEvents = true; + currentIndex = 0; + theRace = nullptr; + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + FEImage* image = FEngFindImage(GetPackageName(), FEngHashString("EVENT_ICON_%d", i + 1)); + if (image) { + AddSlot(new ImageArraySlot(image)); + } + } + TrackMap = reinterpret_cast( + FEngFindObject(GetPackageName(), FEngHashString("TRACK_MAP"))); + if (bIsInGame) { + FEngSetLanguageHash(GetPackageName(), 0xbde82fcc, 0x2f32a021); + } else { + FEngSetLanguageHash(GetPackageName(), 0xbde82fcc, 0x84e4a54c); + } + Setup(); +} + +UISafehouseRaceSheet::~UISafehouseRaceSheet() { +} + +eMenuSoundTriggers UISafehouseRaceSheet::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + eMenuSoundTriggers result = ArrayScrollerMenu::NotifySoundMessage(msg, maybe); + if (msg == 0x7b6b89d7 && !theRace) { + return static_cast(7); + } + return result; +} + +void UISafehouseRaceSheet::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) { + ArrayScrollerMenu::NotificationMessage(msg, obj, param1, param2); + switch (msg) { + case 0xc98356ba: + TrackMapStreamer.UpdateAnimation(); + break; + case 0x72619778: + case 0x9120409e: + case 0x911c0a4b: + case 0xb5971bf1: + RefreshHeader(); + break; + case 0x5073ef13: + case 0xd9feec59: + ToggleList(); + break; + case 0x0c407210: { + if (theRace == nullptr) { + break; + } + if (!bIsInGame) { + signed char joyPort = static_cast< signed char >(FEngMapJoyParamToJoyport(param1)); + FEDatabase->SetPlayersJoystickPort(0, joyPort); + } + const char* dialog = ""; + if (bIsInGame) { + dialog = "InGameDialog.fng"; + } + DialogInterface::ShowTwoButtons(GetPackageName(), dialog, + static_cast< eDialogTitle >(1), 0x70E01038, + 0x417B25E4, 0xD05FC3A3, 0x34DC1BCF, 0x34DC1BCF, + static_cast< eDialogFirstButtons >(1), 0x77CF03C5); + break; + } + case 0xd05fc3a3: + if (bIsInGame) { + new ERaceSheetOff(); + GManager::Get().StartRaceFromInGame(theRace->GetEventHash()); + } else { + GRaceCustom* race = GRaceDatabase::Get().AllocCustomRace(theRace); + GRaceDatabase::Get().SetStartupRace(race, kRaceContext_Career); + GRaceDatabase::Get().FreeCustomRace(race); + StartRace(); + } + break; + case 0x911ab364: + if (bIsInGame) { + cFEng::Get()->QueuePackageSwitch("InGameReputationOverview.fng", 1, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("SafeHouseReputationOverview.fng", 0, 0, false); + } + break; + case 0x34dc1bcf: + break; + } +} + +void UISafehouseRaceSheet::RefreshHeader() { + ArrayScrollerMenu::RefreshHeader(); + FEPrintf(GetPackageName(), 0x5a856a34, "%d", data.TraversebList(currentDatum)); + FEPrintf(GetPackageName(), 0x2d4d22c8, "%d", data.TraversebList(nullptr)); + unsigned int eventsHash = 0x6475236d; + if (currentEvents) { + eventsHash = 0xc948ef80; + } + FEngSetLanguageHash(GetPackageName(), 0x78008599, eventsHash); + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + FEPrintf(GetPackageName(), 0xb514e2d8, "%s %", + GetLocalizedString(0xce6b99b1), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), 0xf91a59f6, "%s %", + GetLocalizedString(0x73b79e0), FEDatabase->GetCareerSettings()->GetCash()); + ArrayDatum* datum = currentDatum; + if (datum == nullptr) { + return; + } + GRaceParameters* race = static_cast< RaceDatum* >(datum)->race; + FEPrintf(GetPackageName(), 0x13c45e, "%.0f", race->GetCashValue()); + const char* distUnits; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + distUnits = GetLocalizedString(0x8569a26a); + } else { + distUnits = GetLocalizedString(0x867dcfd9); + } + FEPrintf(GetPackageName(), 0x18b36f, "%d", race->GetNumLaps()); + FEPrintf(GetPackageName(), 0x80c9daa, "%ash.1f %s", + race->GetRaceLengthMeters() * 0.001f, distUnits); + unsigned int trackNameHash = CalcLanguageHash("TRACKNAME_", race); + FEngSetLanguageHash(GetPackageName(), 0xf2cd475, trackNameHash); + unsigned int copsHash; + if (race->GetCopsEnabled()) { + copsHash = 0x61d1c5a5; + } else { + copsHash = 0x73c615a3; + } + FEngSetLanguageHash(GetPackageName(), 0x9b21, copsHash); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x1c8fc866)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x7af67920)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xbbf970cd)); + GRaceSaveInfo* info = GRaceDatabase::Get().GetScoreInfo(race->GetEventHash()); + if (race->GetRaceType() == GRace::kRaceType_P2P || race->GetRaceType() == GRace::kRaceType_Circuit || + race->GetRaceType() == GRace::kRaceType_Drag || race->GetRaceType() == GRace::kRaceType_Knockout || + race->GetRaceType() == GRace::kRaceType_Tollbooth) { + if (info->mHighScores == 0.0f) { + FEPrintf(GetPackageName(), 0x8fd41bb4, GetLocalizedString(0x472aa00a)); + } else { + Timer t(info->mHighScores); + char buf[64]; + t.PrintToString(buf, 0); + FEPrintf(GetPackageName(), 0x8fd41bb4, "%s", buf); + } + } else { + FEPrintf(GetPackageName(), 0x8fd41bb4, "%s", GetLocalizedString(0x472aa00a)); + } + float top_speed; + float avg_speed; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + distUnits = GetLocalizedString(0x8569a25f); + avg_speed = MPS2KPH(static_cast< float >(info->mAverageSpeed)); + top_speed = MPS2KPH(static_cast< float >(info->mTopSpeed)); + } else { + distUnits = GetLocalizedString(0x8569ab44); + avg_speed = MPS2MPH(static_cast< float >(info->mAverageSpeed)); + top_speed = MPS2MPH(static_cast< float >(info->mTopSpeed)); + } + FEPrintf(GetPackageName(), 0xebd7f926, "%ash.2f %s", top_speed, distUnits); + FEPrintf(GetPackageName(), 0xde9145fb, "%ash.2f %s", avg_speed, distUnits); + FEPrintf(GetPackageName(), 0x763f4b5b, "%ash.0f", race->GetCashValue()); + unsigned int iconHash = FEDBGetRaceIconHash(FEDatabase, race->GetRaceType()); + FEImage* img = FEngFindImage(GetPackageName(), 0xf97ec5d5); + FEngSetTextureHash(img, iconHash); + for (int i = 0; i < GetNumSlots(); i++) { + RaceDatum* rdatum = static_cast< RaceDatum* >(GetDatumAt(i + GetStartDatumNum())); + unsigned int check_hash = FEngHashString("MEDAL_THUMB_%d", i + 1); + FEngSetInvisible(FEngFindObject(GetPackageName(), check_hash)); + if (rdatum == nullptr) { + continue; + } + if (rdatum->IsLocked()) { + FEngSetVisible(FEngFindObject(GetPackageName(), check_hash)); + FEngSetTextureHash(FEngFindImage(GetPackageName(), check_hash), 0x18ed48); + } else if (rdatum->IsChecked()) { + FEngSetVisible(FEngFindObject(GetPackageName(), check_hash)); + FEngSetTextureHash(FEngFindImage(GetPackageName(), check_hash), 0x28feadd); + } + } + if (currentIndex != data.TraversebList(currentDatum) - 1 && currentDatum != nullptr) { + TrackMapStreamer.Init(static_cast< RaceDatum* >(currentDatum)->race, + TrackMap, 0, 0); + currentIndex = data.TraversebList(currentDatum) - 1; + } +} + +bool UISafehouseRaceSheet::AddRace(GRaceParameters* race) { + GRace::Type type = race->GetRaceType(); + switch (type) { + case GRace::kRaceType_JumpToSpeedTrap: + case GRace::kRaceType_JumpToMilestone: + return false; + default: + break; + } + RaceDatum* datum = new ("", 0) RaceDatum( + FEDBGetRaceIconHash(FEDatabase, race->GetRaceType()), + FEDBGetRaceNameHash(FEDatabase, race->GetRaceType()), + race); + AddDatum(datum); + return true; +} + +void UISafehouseRaceSheet::Setup() { + ClearData(); + if (currentEvents) { + GRaceBin* bin = GRaceDatabase::Get().GetBinNumber(iCurrentViewBin); + for (unsigned int i = 0; i < bin->GetWorldRaceCount(); i++) { + unsigned int raceHash = bin->GetWorldRaceHash(i); + GRaceParameters* race = GRaceDatabase::Get().GetRaceFromHash(raceHash); + if (AddRace(race)) { + GetDatumAt(GetNumDatum() - 1)->SetLocked(false); + if (GRaceDatabase::Get().IsCareerRaceComplete(raceHash)) { + GetDatumAt(GetNumDatum() - 1)->SetChecked(true); + } + } + } + } else { + unsigned int bindex = FEDatabase->GetCareerSettings()->GetCurrentBin(); + while (bindex <= GRaceDatabase::Get().GetBinCount()) { + GRaceBin* bin = GRaceDatabase::Get().GetBinNumber(bindex); + bindex++; + if (bin != nullptr) { + for (unsigned int i = 0; i < bin->GetWorldRaceCount(); i++) { + if (AddRace(GRaceDatabase::Get().GetRaceFromHash(bin->GetWorldRaceHash(i)))) { + GetDatumAt(GetNumDatum() - 1)->SetLocked(false); + } + } + } + } + } + SetDescLabel(0x9ba78ba2); + if (GetCurrentDatum() != nullptr) { + RaceDatum* datum = static_cast< RaceDatum* >(GetCurrentDatum()); + TrackMapStreamer.Init(datum->race, TrackMap, 0, 0); + } + SetInitialPosition(0); + RefreshHeader(); +} + +void UISafehouseRaceSheet::ToggleList() { +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.hpp index 8fcce4889..d4006e447 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRaceEvents.hpp @@ -5,6 +5,50 @@ #pragma once #endif +#include +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp" + +struct GRaceParameters; +struct FEMultiImage; + +// total size: 0x28 +struct RaceDatum : public ArrayDatum { + GRaceParameters* race; // offset 0x24, size 0x4 + + RaceDatum() + : ArrayDatum(0, 0) // + , race(nullptr) {} + + RaceDatum(unsigned int hash, unsigned int desc, GRaceParameters* race) + : ArrayDatum(hash, desc) // + , race(race) {} + + ~RaceDatum() override {} + + void NotificationMessage(unsigned long msg, FEObject* pObj, unsigned long param1, + unsigned long param2) override; +}; + +// total size: 0x1D4 +struct UISafehouseRaceSheet : public ArrayScrollerMenu { + UITrackMapStreamer TrackMapStreamer; // offset 0xE8, size 0xDC + FEMultiImage* TrackMap; // offset 0x1C4, size 0x4 + bool bIsInGame; // offset 0x1C8, size 0x1 + bool currentEvents; // offset 0x1CC, size 0x1 + int currentIndex; // offset 0x1D0, size 0x4 + + UISafehouseRaceSheet(ScreenConstructorData* sd); + ~UISafehouseRaceSheet() override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) override; + void RefreshHeader() override; + + bool AddRace(GRaceParameters* race); + void Setup(); + void ToggleList(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.cpp index e69de29bb..0f0844132 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.cpp @@ -0,0 +1,206 @@ +#include "uiRepSheetRival.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp" +#include "Speed/Indep/Src/Gameplay/GManager.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Generated/Events/EEnterBin.hpp" +#include "Speed/Indep/Src/Generated/Events/EFadeScreenOff.hpp" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOff.hpp" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Generated/Messages/MFlowReadyForOutro.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +struct FEObject; + +FEImage* FEngFindImage(const char* pkg_name, int hash); +void FEngSetInvisible(FEObject* obj); +void FEngSetVisible(FEObject* obj); +void FEngSetTextureHash(FEImage* image, unsigned int hash); +void FEngSetTextureHash(const char* pkg_name, unsigned int obj_hash, unsigned int texture_hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +unsigned int FEngHashString(const char* format, ...); +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +unsigned long FEHashUpper(const char* str); +const char* GetLocalizedString(unsigned int hash); +int GetCurrentLanguage(); +void eLoadStreamingTexture(unsigned int* textures, int count, void (*callback)(void*), void* param, int pool); +void eUnloadStreamingTexture(unsigned int* textures, int count); +void eWaitForStreamingTexturePackLoading(const char* name); +unsigned int CalcLanguageHash(const char* prefix, GRaceParameters* pRaceParams); +void StartRace(); + +extern unsigned int iCurrentViewBin; + +uiRepSheetRival::uiRepSheetRival(ScreenConstructorData* sd) + : MenuScreen(sd) + , bIsInGame(sd->Arg != 0) // + , launch_race(nullptr) // + , RivalStreamer(sd->PackageFilename, bIsInGame) { + new EFadeScreenOff(0x161a918); + bMidRivalFlow = false; + bOneOff = false; + if (bIsInGame) { + bMidRivalFlow = sd->Arg == 2; + bOneOff = sd->Arg == 3; + } + Setup(); +} + +uiRepSheetRival::~uiRepSheetRival() { + eWaitForStreamingTexturePackLoading(nullptr); + unsigned int tex = GetDefeatedTexture(); + eUnloadStreamingTexture(&tex, 1); +} + +eMenuSoundTriggers uiRepSheetRival::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (bMidRivalFlow && msg == 0x911ab364) { + return static_cast< eMenuSoundTriggers >(-1); + } + return maybe; +} + +void uiRepSheetRival::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x406415e3: + if (bMidRivalFlow) { + new ERaceSheetOff(); + UCrc32 kind; + kind.SetValue(0x20d60dbf); + MFlowReadyForOutro msg; + msg.Post(kind); + } else if ((FEDatabase->GetGameMode() & 0x20000) != 0) { + new EEnterBin(FEDatabase->GetCareerSettings()->GetCurrentBin() - 1); + uiRepSheetRivalFlow::Get()->StartFlow(1); + } else if (launch_race != nullptr) { + if (bIsInGame) { + new ERaceSheetOff(); + GManager::Get().StartRaceFromInGame(launch_race->GetEventHash()); + } else { + GRaceCustom* race = GRaceDatabase::Get().AllocCustomRace(launch_race); + GRaceDatabase::Get().SetStartupRace(race, kRaceContext_Career); + GRaceDatabase::Get().FreeCustomRace(race); + StartRace(); + } + } + break; + case 0x911ab364: + if (!bMidRivalFlow) { + if (bOneOff) { + new EUnPause(); + } else if ((FEDatabase->GetGameMode() & 0x20000) == 0) { + if (bIsInGame) { + cFEng::Get()->QueuePackageSwitch("InGameReputationOverview.fng", 1, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("SafeHouseReputationOverview.fng", 0, 0, false); + } + } + } + break; + } +} + +void uiRepSheetRival::Setup() { + pRivalImg = FEngFindImage(GetPackageName(), 0xc1f62308); + pDefeatedImg = FEngFindImage(GetPackageName(), 0x7fe4020f); + pDefeatedImgBG = FEngFindImage(GetPackageName(), 0x26869897); + pTagImg = FEngFindImage(GetPackageName(), 0xf5a2a087); + pBGImg = FEngFindImage(GetPackageName(), 0x2cbe1dd0); + RivalStreamer.Init(iCurrentViewBin, pRivalImg, pTagImg, pBGImg); + FEngSetInvisible(reinterpret_cast< FEObject* >(pDefeatedImg)); + FEngSetInvisible(reinterpret_cast< FEObject* >(pDefeatedImgBG)); + unsigned int defeatedTexture = GetDefeatedTexture(); + FEngSetTextureHash(pDefeatedImg, defeatedTexture); + FEngSetTextureHash(pDefeatedImgBG, defeatedTexture); + eLoadStreamingTexture(defeatedTexture, TextureLoadedCallback, reinterpret_cast< unsigned int >(this), 0); + if (bIsInGame && bMidRivalFlow) { + cFEng::Get()->QueuePackageMessage(0x34297cb0, GetPackageName(), nullptr); + } else { + if (FEDatabase->IsPostRivalMode()) { + CarViewer::HideAllCars(); + iCurrentViewBin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + cFEng::Get()->QueuePackageMessage(0x0b21a45f, GetPackageName(), nullptr); + cFEng::Get()->QueuePackageMessage(0xb4c144b1, GetPackageName(), nullptr); + } else { + cFEng::Get()->QueuePackageMessage(0xaf922178, GetPackageName(), nullptr); + } + } + RefreshHeader(); +} + +void uiRepSheetRival::NotifyTextureLoaded() { + FEngSetVisible(reinterpret_cast(pDefeatedImg)); + FEngSetVisible(reinterpret_cast(pDefeatedImgBG)); +} + +unsigned int uiRepSheetRival::GetDefeatedTexture() { + int lang = GetCurrentLanguage(); + switch (lang) { + case 1: return 0x87b81cd; + case 2: return 0x87b846e; + case 3: return 0x87b8ece; + case 4: return 0x87bb8d4; + case 5: return 0x87b79bd; + case 6: return 0x87bb9bf; + case 7: return 0x87b7723; + case 12: return 0x87babfb; + case 13: return 0x87b80ad; + case 8: + case 9: + case 10: + case 11: + default: return 0x87b7d0a; + } +} + +void uiRepSheetRival::RefreshHeader() { + GRaceBin* bin = GRaceDatabase::Get().GetBinNumber(iCurrentViewBin); + unsigned int num_boss_races = bin->GetBossRaceCount(); + if (num_boss_races >= 5) { + cFEng::Get()->QueuePackageMessage(0xe7177701, GetPackageName(), nullptr); + } else if (num_boss_races == 4) { + cFEng::Get()->QueuePackageMessage(0x9a1d3a40, GetPackageName(), nullptr); + } else if (num_boss_races == 3) { + cFEng::Get()->QueuePackageMessage(0x4d22fd7f, GetPackageName(), nullptr); + } else { + cFEng::Get()->QueuePackageMessage(0x0028c0be, GetPackageName(), nullptr); + } + for (unsigned int i = 0; i < bin->GetBossRaceCount(); i++) { + unsigned int raceHash = bin->GetBossRaceHash(i); + GRaceParameters* race = GRaceDatabase::mObj->GetRaceFromHash(raceHash); + if (launch_race == nullptr) { + launch_race = race; + } + if ((GRaceDatabase::Get(), GRaceDatabase::Get().IsCareerRaceComplete(race->GetEventHash()))) { + char buf[64]; + bSNPrintf(buf, 64, "CROSSOUT_%d", i + 1); + cFEng::Get()->QueuePackageMessage(FEHashUpper(buf), GetPackageName(), nullptr); + } + SetupRace(i + 1, race); + } + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + FEPrintf(GetPackageName(), 0xb514e2d8, "%s %$d", GetLocalizedString(0xce6b99b1), stable->GetTotalBounty()); + FEPrintf(GetPackageName(), 0xf91a59f6, "%s %$d", GetLocalizedString(0x073b79e0), FEDatabase->GetCareerSettings()->GetCash()); +} + +void uiRepSheetRival::SetupRace(unsigned int num, GRaceParameters* race) { + unsigned int icon_hash = FEngHashString("EVENT_ICON_%d", num); + unsigned int type_hash = FEngHashString("EVENT NAME_%d", num); + unsigned int name_hash = FEngHashString("DATA_%d", num); + unsigned int best_hash = FEngHashString("RIVAL_BEST_DATA_%d", num); + FEngSetTextureHash(GetPackageName(), icon_hash, FEDatabase->GetRaceIconHash(race->GetRaceType())); + FEngSetLanguageHash(GetPackageName(), type_hash, FEDatabase->GetRaceNameHash(race->GetRaceType())); + FEngSetLanguageHash(GetPackageName(), name_hash, CalcLanguageHash("TRACKNAME_", race)); + Timer t(race->GetRivalBestTime()); + char buf[64]; + t.PrintToString(buf, 0); + FEPrintf(GetPackageName(), best_hash, "%s", buf); +} + +void uiRepSheetRival::TextureLoadedCallback(unsigned int tex) { + reinterpret_cast< uiRepSheetRival* >(tex)->NotifyTextureLoaded(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.hpp index a812c8bd9..daaada72d 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRival.hpp @@ -5,6 +5,46 @@ #pragma once #endif +#include +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp" + +struct FEImage; +struct GRaceParameters; + +// total size: 0x8C +struct uiRepSheetRival : public MenuScreen { + enum UIREPSHEETRIVAL { + UIREPSHEETRIVAL_FE = 0, + UIREPSHEETRIVAL_INGAME = 1, + UIREPSHEETRIVAL_INGAME_MIDFLOW = 2, + UIREPSHEETRIVAL_INGAME_ONEOFF = 3, + }; + + bool bIsInGame; // offset 0x2C, size 0x1 + bool bMidRivalFlow; // offset 0x30, size 0x1 + bool bOneOff; // offset 0x34, size 0x1 + GRaceParameters* launch_race; // offset 0x38, size 0x4 + FEImage* pRivalImg; // offset 0x3C, size 0x4 + FEImage* pDefeatedImg; // offset 0x40, size 0x4 + FEImage* pDefeatedImgBG; // offset 0x44, size 0x4 + FEImage* pTagImg; // offset 0x48, size 0x4 + FEImage* pBGImg; // offset 0x4C, size 0x4 + uiRepSheetRivalStreamer RivalStreamer; // offset 0x50, size 0x3C + + static void TextureLoadedCallback(unsigned int arg); + + uiRepSheetRival(ScreenConstructorData* sd); + ~uiRepSheetRival() override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) override; + + void Setup(); + void NotifyTextureLoaded(); + unsigned int GetDefeatedTexture(); + void RefreshHeader(); + void SetupRace(unsigned int num, GRaceParameters* race); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.cpp index e69de29bb..8e3da5e81 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.cpp @@ -0,0 +1,143 @@ +#include "uiRepSheetRivalBio.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp" +#include "Speed/Indep/Src/Generated/Events/EEnterBin.hpp" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +struct FEObject; +FEImage* FEngFindImage(const char* pkg_name, int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +unsigned int FEngHashString(const char* format, ...); +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +int FEngSNPrintf(char* buffer, int buf_size, const char* fmt, ...); +const char* GetLocalizedString(unsigned int hash); + +extern unsigned int iCurrentViewBin; + +namespace Showcase { +extern const char* FromPackage; +extern int FromArgs; +extern int BlackListNumber; +} // namespace Showcase + +uiRepSheetRivalBio::uiRepSheetRivalBio(ScreenConstructorData* sd) + : MenuScreen(sd) + , bIsInGame(sd->Arg != 0) + , RivalStreamer(sd->PackageFilename, bIsInGame) { + if (FEDatabase->IsPostRivalMode()) { + CarViewer::HideAllCars(); + if (FEDatabase->GetCareerSettings()->GetCurrentBin() == 16) { + new EEnterBin(FEDatabase->GetCareerSettings()->GetCurrentBin() - 1); + } + iCurrentViewBin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + cFEng::Get()->QueuePackageMessage(0xb21a45f, GetPackageName(), nullptr); + } else { + cFEng::Get()->QueuePackageMessage(0xaf922178, GetPackageName(), nullptr); + if (!bIsInGame) { + GarageMainScreen::GetInstance()->DisableCarRendering(); + } + } + Setup(); +} + +void uiRepSheetRivalBio::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + switch (msg) { + case 0xc519bfbf: + if (FEDatabase->IsPostRivalMode()) { + break; + } + { + char buf[64]; + if (iCurrentViewBin == 1) { + bSNPrintf(buf, 64, "E3_DEMO_BMW"); + } else { + bSNPrintf(buf, 64, "BL%d", iCurrentViewBin); + } + FEPlayerCarDB* stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord* pCar = stable->CreateNewPresetCar(buf); + if (pCar == nullptr) { + break; + } + RideInfo ride; + stable->BuildRideForPlayer(pCar->Handle, 0, &ride); + CarViewer::SetRideInfo(&ride, SET_RIDE_INFO_REASON_LOAD_CAR, eCARVIEWER_PLAYER1_CAR); + Showcase::FromArgs = 0; + Showcase::FromPackage = GetPackageName(); + Showcase::BlackListNumber = iCurrentViewBin; + cFEng::Get()->QueuePackageSwitch("Showcase.fng", reinterpret_cast< int >(pCar), 0, false); + break; + } + case 0xc519bfc3: + if (FEDatabase->IsPostRivalMode()) { + break; + } + { + char buf[64]; + bSNPrintf(buf, 64, "blacklist_%02d", iCurrentViewBin); + FEAnyMovieScreen::LaunchMovie(GetPackageName(), buf); + break; + } + case 0x406415e3: + if (!FEDatabase->IsPostRivalMode()) { + break; + } + if (uiRepSheetRivalFlow::Get()->GetStage() == -1) { + uiRepSheetRivalFlow::Get()->StartFlow(5); + } else { + uiRepSheetRivalFlow::Get()->Next(); + } + break; + case 0x911ab364: + if (FEDatabase->IsPostRivalMode()) { + break; + } + if (bIsInGame) { + cFEng::Get()->QueuePackageSwitch("InGameReputationOverview.fng", 1, 0, false); + } else { + GarageMainScreen::GetInstance()->EnableCarRendering(); + cFEng::Get()->QueuePackageSwitch("SafeHouseReputationOverview.fng", 0, 0, false); + } + break; + } +} + +void uiRepSheetRivalBio::RefreshHeader() { + char buf[32]; + if (bIsInGame) { + FEngSNPrintf(buf, 32, GetLocalizedString(0x96ca2471), iCurrentViewBin); + } else { + FEngSNPrintf(buf, 32, GetLocalizedString(0x3a64de21), iCurrentViewBin); + } + FEPrintf(GetPackageName(), 0x242657ce, "%s", buf); + const char* pkgName; + pkgName = GetPackageName(); + unsigned int hash = FEngHashString("BL_NAME_%d", iCurrentViewBin); + FEngSetLanguageHash(pkgName, 0x7ac3d0c9, hash); + pkgName = GetPackageName(); + hash = FEngHashString("BL_RIDE_%d", iCurrentViewBin); + FEngSetLanguageHash(pkgName, 0xb1f2748d, hash); + pkgName = GetPackageName(); + hash = FEngHashString("BL_BIO_1_%d", iCurrentViewBin); + FEngSetLanguageHash(pkgName, 0x27e1d6d8, hash); + pkgName = GetPackageName(); + hash = FEngHashString("BL_BIO_2_%d", iCurrentViewBin); + FEngSetLanguageHash(pkgName, 0xcb5bf41a, hash); + pkgName = GetPackageName(); + hash = FEngHashString("BL_BIO_3_%d", iCurrentViewBin); + FEngSetLanguageHash(pkgName, 0xa6f07bf3, hash); +} + +void uiRepSheetRivalBio::Setup() { + pRivalImg = FEngFindImage(GetPackageName(), 0xc1f62308); + pTagImg = FEngFindImage(GetPackageName(), 0xf5a2a087); + pBGImg = FEngFindImage(GetPackageName(), 0x2cbe1dd0); + RivalStreamer.Init(iCurrentViewBin, pRivalImg, pTagImg, pBGImg); + RefreshHeader(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.hpp index 0f9a42049..2ad493c22 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalBio.hpp @@ -5,6 +5,28 @@ #pragma once #endif +#include +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp" + +struct FEImage; + +// total size: 0x78 +struct uiRepSheetRivalBio : public MenuScreen { + bool bIsInGame; // offset 0x2C, size 0x1 + FEImage* pRivalImg; // offset 0x30, size 0x4 + FEImage* pTagImg; // offset 0x34, size 0x4 + FEImage* pBGImg; // offset 0x38, size 0x4 + uiRepSheetRivalStreamer RivalStreamer; // offset 0x3C, size 0x3C + + uiRepSheetRivalBio(ScreenConstructorData* sd); + ~uiRepSheetRivalBio() override {} + + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) override; + + void RefreshHeader(); + void Setup(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.cpp index e69de29bb..0ae17935f 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.cpp @@ -0,0 +1,99 @@ +#include "uiRepSheetRivalFlow.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEAnyMovieScreen.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Generated/Events/ERaceSheetOn.hpp" +#include "Speed/Indep/Src/Generated/Messages/MFlowReadyForOutro.h" +#include "Speed/Indep/bWare/Inc/bPrintf.hpp" + +void MemcardEnter(const char* from, const char* to, unsigned int op, + void (*pTermFunc)(void*), void* pTermFuncParam, + unsigned int successMsg, unsigned int failedMsg); + +void RaceStarterStartCareerFreeRoam() asm("StartCareerFreeRoam__11RaceStarter"); + +void ShowAllCarsCarViewer() asm("ShowAllCars__9CarViewer"); + +static const char* ScreenNames[] = { + "SafeHouseRivalChallenge.fng", + "SafeHouseMarkers.fng", + "SafeHouseRegionUnlock.fng", + "MC_Main_GC.fng", + "SafeHouseRivalBio.fng", +}; + +uiRepSheetRivalFlow* uiRepSheetRivalFlow::mInstance; + +void uiRepSheetRivalFlow::Init() { + mInstance = new uiRepSheetRivalFlow(); +} + +uiRepSheetRivalFlow* uiRepSheetRivalFlow::Get() { + return mInstance; +} + +uiRepSheetRivalFlow::uiRepSheetRivalFlow() { + mStage = -1; +} + +void uiRepSheetRivalFlow::StartFlow(int start_stage) { + mStage = start_stage - 1; + Next(); +} + +void uiRepSheetRivalFlow::Next() { + int old_stage = mStage; + mStage = old_stage + 1; + + if (mStage == 5) { + char buf[64]; + bSNPrintf(buf, 64, "blacklist_%02d", + FEDatabase->GetCareerSettings()->GetCurrentBin()); + FEAnyMovieScreen::SetMovieName(buf); + cFEng::Get()->QueuePackageSwitch(FEAnyMovieScreen::GetFEngPackageName(), 0, 0, false); + } else if (mStage == 6) { + unsigned char bin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + if (bin == 15) { + FEDatabase->ClearGameMode(static_cast< eFEGameModes >(0x20000)); + FEDatabase->SetGameMode(static_cast< eFEGameModes >(1)); + ShowAllCarsCarViewer(); + FEDatabase->GetCareerSettings()->SetHasDoneCareerIntro(); + cFEng::Get()->QueuePackagePop(-1); + cFEng::Get()->QueuePackagePush("SafeHouseReputationOverview.fng", 0, 0, false); + mStage = -1; + } else if (!FEDatabase->GetCareerSettings()->HasRapSheet() && bin == 13) { + mStage = old_stage; + FEDatabase->GetCareerSettings()->SetHasRapSheet(); + FEAnyMovieScreen::SetMovieName("blacklist_13"); + cFEng::Get()->QueuePackageSwitch(FEAnyMovieScreen::GetFEngPackageName(), 0, 0, false); + } else { + RaceStarterStartCareerFreeRoam(); + } + } else if (mStage == 7) { + UCrc32 kind(0x20d60dbf); + MFlowReadyForOutro msg; + msg.Post(kind); + new ERaceSheetOn(0); + FEDatabase->ClearGameMode(static_cast< eFEGameModes >(0x20000)); + mStage = -1; + } else { + if (mStage == 2) { + unsigned char bin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + if (bin == 8 || bin == 12) { + cFEng::Get()->QueuePackageSwitch(ScreenNames[2], 0, 0, false); + return; + } + } else if (mStage == 3) { + if (MemoryCard::GetInstance()->ShouldDoAutoSave(false)) { + MemcardEnter(nullptr, ScreenNames[mStage + 1], 0x4000b2, nullptr, nullptr, 0, 0); + return; + } + } else { + cFEng::Get()->QueuePackageSwitch(ScreenNames[mStage], 0, 0, false); + return; + } + Next(); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp index 98b9d0abf..375e98629 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp @@ -5,6 +5,37 @@ #pragma once #endif +#include +// total size: 0x8 +struct uiRepSheetRivalFlow { + enum Stage { + CHALLENGE_SCREEN = 0, + MARKER_SELECTION = 1, + REGION_UNLOCK = 2, + SAVE_FLOW = 3, + BIO_NEW_RIVAL = 4, + BIO_MOVIE = 5, + BACK_TO_FREE_ROAM = 6, + IN_GAME_BLACKLIST = 7, + }; + + int mStage; // offset 0x0, size 0x4 + // vtable at 0x4 + + static uiRepSheetRivalFlow* mInstance; + + uiRepSheetRivalFlow(); + virtual ~uiRepSheetRivalFlow() {} + + static void Init(); + static void Destroy(); + static uiRepSheetRivalFlow* Get(); + + void StartFlow(int start_stage); + void Next(); + + int GetStage() { return mStage; } +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.cpp index e69de29bb..5a38c265c 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.cpp @@ -0,0 +1,160 @@ +#include "uiRepSheetRivalStreamer.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/World/TrackStreamer.hpp" + +struct FEObject; + +FEObject* FEngFindObject(const char* pkg_name, unsigned int hash); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(FEObject* obj); +void FEngSetTextureHash(FEImage* image, unsigned int hash); +unsigned int FEngHashString(const char* format, ...); +void eLoadStreamingTexture(unsigned int* textures, int count, void (*callback)(void*), void* param, int pool); +void eUnloadStreamingTexture(unsigned int* textures, int count); +void eLoadStreamingTexturePack(const char* name, void (*callback)(void*), void* param, int flags); +void eUnloadStreamingTexturePack(const char* name); +void eWaitForStreamingTexturePackLoading(const char* name); +struct TextureInfo; +TextureInfo* GetTextureInfo(unsigned int hash, int, int); + +uiRepSheetRivalStreamer::uiRepSheetRivalStreamer(const char* name, bool in_game) { + pkg_name = name; + MemPoolNum = 0; + bInGame = in_game; + DesiredBin = -1; + LoadedBin = -1; + LoadingInProgress = true; + bMakeSpaceInPoolComplete = false; + NumLoadedTextures = 0; + Rival = nullptr; + Tag = nullptr; + BG = nullptr; + if (bInGame) { + MemPoolNum = 7; + TheTrackStreamer.DisableZoneSwitching(); + TheTrackStreamer.MakeSpaceInPool(0x30000, MakeSpaceInPoolCallbackBridge, reinterpret_cast(this)); + } else { + eLoadStreamingTexturePack("BL_RIVAL_PACK", TexturePackLoadedCallbackBridge, this, 0); + } +} + +uiRepSheetRivalStreamer::~uiRepSheetRivalStreamer() { + if (bInGame) { + if (!bMakeSpaceInPoolComplete) { + TheTrackStreamer.WaitForCurrentLoadingToComplete(); + } + TheTrackStreamer.EnableZoneSwitching(); + TheTrackStreamer.RefreshLoading(); + } + eWaitForStreamingTexturePackLoading("BL_RIVAL_PACK"); + UnloadTextures(); + eUnloadStreamingTexturePack("BL_RIVAL_PACK"); +} + +void uiRepSheetRivalStreamer::MakeSpaceInPoolCallback() { + bMakeSpaceInPoolComplete = true; + eLoadStreamingTexturePack("BL_RIVAL_PACK", TexturePackLoadedCallbackBridge, this, 0); +} + +void uiRepSheetRivalStreamer::TexturePackLoadedCallback() { + LoadingInProgress = false; + LoadTextures(); +} + +void uiRepSheetRivalStreamer::Init(unsigned int the_bin, FEImage* the_rival, FEImage* the_tag, FEImage* the_bg) { + DesiredBin = the_bin; + Rival = the_rival; + Tag = the_tag; + BG = the_bg; + FEngSetInvisible(reinterpret_cast(Rival)); + FEngSetInvisible(reinterpret_cast(Tag)); + FEngSetInvisible(reinterpret_cast(BG)); + if (!LoadingInProgress) { + LoadTextures(); + } +} + +void uiRepSheetRivalStreamer::LoadTextures() { + if (LoadedBin != DesiredBin) { + if (NumLoadedTextures != 0) { + UnloadTextures(); + } + LoadingInProgress = true; + LoadedBin = DesiredBin; + NumLoadedTextures = CalcTexturesToLoad(LoadedTextures, DesiredBin); + eLoadStreamingTexture(LoadedTextures, NumLoadedTextures, TexturesLoadedCallbackBridge, this, MemPoolNum); + } +} + +void uiRepSheetRivalStreamer::UnloadTextures() { + eUnloadStreamingTexture(LoadedTextures, NumLoadedTextures); + NumLoadedTextures = 0; + LoadedBin = -1; +} + +int uiRepSheetRivalStreamer::CalcTexturesToLoad(unsigned int* temp, int bin) { + int count = 0; + if (Rival != nullptr) { + if (bInGame) { + temp[count++] = FEngHashString("BL_INGAME_RIVAL_%d", bin); + } else { + temp[count++] = FEngHashString("BL_RIVAL_%d", bin); + } + } + if (Tag != nullptr) { + if (bInGame) { + temp[count++] = FEngHashString("BL_INGAME_TAG_%d", bin); + } else { + temp[count++] = FEngHashString("BL_TAG_%d", bin); + } + } + if (BG != nullptr) { + if (bInGame) { + temp[count++] = FEngHashString("BL_INGAME_BG_%d", bin); + } else { + temp[count++] = FEngHashString("BL_BG_%d", bin); + } + } + return count; +} + +void uiRepSheetRivalStreamer::TexturesLoadedCallback() { + int idx; + LoadingInProgress = false; + if (LoadedBin != DesiredBin) { + LoadTextures(); + return; + } + idx = 0; + if (Rival != nullptr) { + cFEng::Get()->QueuePackageMessage(0xC0942E85, pkg_name, nullptr); + GetTextureInfo(LoadedTextures[0], false, false); + FEngSetTextureHash(Rival, LoadedTextures[0]); + FEngSetVisible(reinterpret_cast(Rival)); + idx = 1; + } + if (Tag != nullptr) { + cFEng::Get()->QueuePackageMessage(0x8C9D4547, pkg_name, nullptr); + FEngSetTextureHash(Tag, LoadedTextures[idx]); + idx++; + FEngSetVisible(reinterpret_cast(Tag)); + } + if (BG != nullptr) { + cFEng::Get()->QueuePackageMessage(0xD22B95D0, pkg_name, nullptr); + FEngSetTextureHash(BG, LoadedTextures[idx]); + FEngSetVisible(reinterpret_cast(BG)); + } +} + +void uiRepSheetRivalStreamer::MakeSpaceInPoolCallbackBridge(int param) { + reinterpret_cast(param)->MakeSpaceInPoolCallback(); +} + +void uiRepSheetRivalStreamer::TexturePackLoadedCallbackBridge(void* param) { + static_cast(param)->TexturePackLoadedCallback(); +} + +void uiRepSheetRivalStreamer::TexturesLoadedCallbackBridge(void* param) { + static_cast(param)->TexturesLoadedCallback(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp index 4e8fd4b11..5c1001f8b 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp @@ -5,6 +5,40 @@ #pragma once #endif +#include +struct FEImage; + +// total size: 0x3C +struct uiRepSheetRivalStreamer { + int MemPoolNum; // offset 0x0, size 0x4 + bool bInGame; // offset 0x4, size 0x1 + bool bMakeSpaceInPoolComplete; // offset 0x8, size 0x1 + const char* pkg_name; // offset 0xC, size 0x4 + int DesiredBin; // offset 0x10, size 0x4 + int LoadedBin; // offset 0x14, size 0x4 + bool LoadingInProgress; // offset 0x18, size 0x1 + int NumLoadedTextures; // offset 0x1C, size 0x4 + unsigned int LoadedTextures[3]; // offset 0x20, size 0xC + FEImage* Rival; // offset 0x2C, size 0x4 + FEImage* Tag; // offset 0x30, size 0x4 + FEImage* BG; // offset 0x34, size 0x4 + // vtable at 0x38 + + static void MakeSpaceInPoolCallbackBridge(int param); + static void TexturePackLoadedCallbackBridge(void* param); + static void TexturesLoadedCallbackBridge(void* param); + + uiRepSheetRivalStreamer(const char* pkg_name, bool in_game); + virtual ~uiRepSheetRivalStreamer(); + + void MakeSpaceInPoolCallback(); + void TexturePackLoadedCallback(); + void Init(unsigned int the_bin, FEImage* the_rival, FEImage* the_tag, FEImage* the_bg); + void LoadTextures(); + void UnloadTextures(); + int CalcTexturesToLoad(unsigned int* temp, int bin); + void TexturesLoadedCallback(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiSafehouseRegionUnlock.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiSafehouseRegionUnlock.cpp index e69de29bb..a1853b046 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiSafehouseRegionUnlock.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiSafehouseRegionUnlock.cpp @@ -0,0 +1,52 @@ +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalFlow.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" + +struct FEObject; +struct FEImage; +FEImage* FEngFindImage(const char* pkg_name, int hash); +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); + +extern unsigned int iCurrentViewBin; + +struct uiSafehouseRegionUnlock : public MenuScreen { + FEImage* pRivalImg; + FEImage* pTagImg; + FEImage* pBGImg; + uiRepSheetRivalStreamer RivalStreamer; + + uiSafehouseRegionUnlock(ScreenConstructorData* sd); + ~uiSafehouseRegionUnlock() override; + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) override; + void Setup(); +}; + +uiSafehouseRegionUnlock::uiSafehouseRegionUnlock(ScreenConstructorData* sd) + : MenuScreen(sd) + , RivalStreamer(sd->PackageFilename, false) { + Setup(); +} + +uiSafehouseRegionUnlock::~uiSafehouseRegionUnlock() { +} + +void uiSafehouseRegionUnlock::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) { + if (msg == 0x406415e3) { + uiRepSheetRivalFlow::Get()->Next(); + } +} + +void uiSafehouseRegionUnlock::Setup() { + pRivalImg = FEngFindImage(PackageFilename, 0xc1f62308); + pTagImg = FEngFindImage(PackageFilename, 0xf5a2a087); + pBGImg = FEngFindImage(PackageFilename, 0x2cbe1dd0); + unsigned char bin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + int next_bin = bin + 1; + if (bin == 12) { + FEngSetLanguageHash(PackageFilename, 0xd6c0e097, 0x29e4b193); + } else if (next_bin == 9) { + FEngSetLanguageHash(PackageFilename, 0xd6c0e097, 0x2b0bca2d); + } + RivalStreamer.Init(static_cast(FEDatabase->GetCareerSettings()->GetCurrentBin()) + 1, pRivalImg, pTagImg, pBGImg); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.cpp index e69de29bb..daaa9c36a 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.cpp @@ -0,0 +1,592 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/feimage.h" +#include "Speed/Indep/Src/FEng/FEString.h" + +extern void FEngSetVisible(FEObject *obj); +extern void FEngSetInvisible(FEObject *obj); +extern FEObject *FEngFindObject(const char *pkg, unsigned int hash); +extern FEImage *FEngFindImage(const char *pkg, int hash); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +extern bool CustomizeIsInBackRoom(); +extern void CustomizeSetInParts(bool b); +extern void CustomizeSetInPerformance(bool b); +extern int GetCurrentLanguage(); +extern const char *GetLocalizedString(unsigned int hash); +extern int FEPrintf(const char *pkg, int hash, const char *fmt, ...); +extern int bSNPrintf(char *buf, int size, const char *fmt, ...); + +extern const char *g_pCustomizeMainPkg; +extern const char *g_pCustomizeSubPkg; +extern const char *g_pCustomizeSubTopPkg; +extern const char *g_pCustomizePartsPkg; +extern const char *g_pCustomizePerfPkg; +extern const char *g_pCustomizeDecalsPkg; +extern const char *g_pCustomizePaintPkg; +extern const char *g_pCustomizeRimsPkg; +extern const char *g_pCustomizeHudPkg; +extern const char *g_pCustomizeSpoilerPkg; + +extern CarCustomizeManager gCarCustomizeManager; +extern FEMarkerManager TheFEMarkerManager; + +// --- CustomizeMeter --- + +void CustomizeMeter::SetCurrent(float current) { + Current = bMin(bMax(current, Min), Max); +} + +void CustomizeMeter::SetPreview(float preview) { + PreviousPreview = Preview; + Preview = bMin(bMax(preview, Min), Max); +} + +void CustomizeMeter::SetVisibility(bool b) { + if (b) { + FEngSetVisible(pMeterGroup); + } else { + FEngSetInvisible(pMeterGroup); + } +} + +// --- FEShoppingCartItem --- + +void FEShoppingCartItem::Show() { + FEStatWidget::Show(); + FEngSetVisible(pTradeInPrice); + FEngSetVisible(pCheckIcon); +} + +void FEShoppingCartItem::Hide() { + FEStatWidget::Hide(); + FEngSetInvisible(pTradeInPrice); + FEngSetInvisible(pCheckIcon); +} + +// --- CustomizeSub --- + +CustomizeMainOption *CustomizeSub::FindInCartOption() { + if (InCartPartOptionIndex) { + return static_cast(Options.GetOption(InCartPartOptionIndex)); + } + return nullptr; +} + +// --- CustomizeParts --- + +void CustomizeParts::LoadHudTextures() { + PacksLoadedCount = 0; + LoadNextHudTexturePack(); +} + +// --- CustomizePaint --- + +void CustomizePaint::SetupBasePaint() { + BuildSwatchList(0x4C); +} + +// --- CustomizeSub Setup functions --- + +void CustomizeSub::Setup() { + switch (Category) { + case 0x801: SetupParts(); break; + case 0x802: SetupPerformance(); break; + case 0x803: SetupVisual(); break; + case 0x302: SetupVinylGroups(); break; + case 0x305: SetupDecalLocations(); break; + case 0x103: SetupRimBrands(); break; + case 0x501: case 0x502: case 0x503: + case 0x504: case 0x505: case 0x506: + SetupDecalPositions(); break; + } + RefreshHeader(); +} + +void CustomizeSub::SetupParts() { + if (CustomizeIsInBackRoom()) { + TitleHash = 0x5d285ae7; + } else { + TitleHash = 0x055dce1a; + } + CustomizeSetInParts(true); + BackToPkg = g_pCustomizeMainPkg; + if (!CustomizeIsInBackRoom()) { + AddCustomOption(g_pCustomizePartsPkg, 0x028c24f6, 0x6134c218, 0x101); + AddCustomOption(g_pCustomizeSpoilerPkg, 0xbb034ea6, 0x94e73021, 0x102); + AddCustomOption(g_pCustomizeSubTopPkg, 0x0294d2a3, 0xf868eb0b, 0x103); + AddCustomOption(g_pCustomizePartsPkg, 0x028f7092, 0x04d4a88d, 0x104); + AddCustomOption(g_pCustomizePartsPkg, 0x79165861, 0x61e8f83c, 0x105); + } else { + AddCustomOption(g_pCustomizePartsPkg, 0xaf393dba, 0x6134c218, 0x101); + AddCustomOption(g_pCustomizeSpoilerPkg, 0xc51a4f62, 0x94e73021, 0x102); + AddCustomOption(g_pCustomizeSubTopPkg, 0xc19491cc, 0xf868eb0b, 0x103); + AddCustomOption(g_pCustomizePartsPkg, 0xf375276e, 0x04d4a88d, 0x104); + AddCustomOption(g_pCustomizePartsPkg, 0x25a4375e, 0x61e8f83c, 0x105); + } + SetInitialOption(FromCategory & 0xFFFF00FF); +} + +void CustomizeSub::SetupPerformance() { + if (CustomizeIsInBackRoom()) { + TitleHash = 0xbfd5b50f; + } else { + TitleHash = 0xbaef8282; + } + BackToPkg = g_pCustomizeMainPkg; + CustomizeSetInPerformance(true); + if (!CustomizeIsInBackRoom()) { + AddCustomOption(g_pCustomizePerfPkg, 0xc15c94e6, 0x9853d9a6, 0x201); + AddCustomOption(g_pCustomizePerfPkg, 0x01a29ffa, 0x29aa74ba, 0x202); + AddCustomOption(g_pCustomizePerfPkg, 0x178475e7, 0x6e101aa7, 0x203); + AddCustomOption(g_pCustomizePerfPkg, 0x9701bde4, 0x4ce19aa4, 0x204); + AddCustomOption(g_pCustomizePerfPkg, 0x06e8e477, 0x05aa9137, 0x205); + AddCustomOption(g_pCustomizePerfPkg, 0xbaa23a28, 0x91997ee8, 0x206); + if (gCarCustomizeManager.IsTurbo()) { + AddCustomOption(g_pCustomizePerfPkg, 0x06ef789c, 0x05b1255c, 0x207); + } else { + AddCustomOption(g_pCustomizePerfPkg, 0x93603dfb, 0xbb6812bb, 0x207); + } + } else { + AddCustomOption(g_pCustomizePerfPkg, 0x4f424e0f, 0x9853d9a6, 0x201); + AddCustomOption(g_pCustomizePerfPkg, 0xd142d3e3, 0x29aa74ba, 0x202); + AddCustomOption(g_pCustomizePerfPkg, 0x00190eb6, 0x6e101aa7, 0x203); + AddCustomOption(g_pCustomizePerfPkg, 0x6fea04c8, 0x4ce19aa4, 0x204); + AddCustomOption(g_pCustomizePerfPkg, 0x7373f1ef, 0x05aa9137, 0x205); + AddCustomOption(g_pCustomizePerfPkg, 0x4887f351, 0x91997ee8, 0x206); + if (gCarCustomizeManager.IsTurbo()) { + AddCustomOption(g_pCustomizePerfPkg, 0x12fe30a5, 0x05b1255c, 0x207); + } else { + AddCustomOption(g_pCustomizePerfPkg, 0x630071e4, 0xbb6812bb, 0x207); + } + } + SetInitialOption(FromCategory & 0xFFFF00FF); +} + +void CustomizeSub::SetupVisual() { + if (CustomizeIsInBackRoom()) { + TitleHash = 0x10c3fe31; + } else { + TitleHash = 0xbfa7d7c4; + } + BackToPkg = g_pCustomizeMainPkg; + if (!CustomizeIsInBackRoom()) { + AddCustomOption(g_pCustomizePaintPkg, 0xa3b76154, 0x055da70c, 0x301); + AddCustomOption(g_pCustomizeSubTopPkg, 0x55778e5a, 0xbfa52c55, 0x302); + if (!gCarCustomizeManager.IsHeroCar()) { + AddCustomOption(g_pCustomizePaintPkg, 0xd223f84a, 0xe126ff53, 0x303); + } + AddCustomOption(g_pCustomizePartsPkg, 0x3f23165c, 0xd32729a6, 0x304); + AddCustomOption(g_pCustomizeSubTopPkg, 0xda1dae54, 0x955980bc, 0x305); + AddCustomOption("FeCustomize_ToolBox", 0x45a1c644, 0x6857e5ac, 0x306); + AddCustomOption(g_pCustomizeHudPkg, 0x028f88bc, 0x78980a6b, 0x307); + } else { + AddCustomOption(g_pCustomizePaintPkg, 0x0db89e17, 0x055da70c, 0x301); + AddCustomOption(g_pCustomizeSubTopPkg, 0xd35f04c0, 0xbfa52c55, 0x302); + AddCustomOption(g_pCustomizeSubTopPkg, 0xa9135927, 0x955980bc, 0x305); + AddCustomOption(g_pCustomizeHudPkg, 0x8ba602fc, 0x78980a6b, 0x307); + } + SetInitialOption(FromCategory & 0xFFFF00FF); +} + +void CustomizeSub::SetupDecalLocations() { + TitleHash = 0x9de6e6e1; + BackToPkg = g_pCustomizeSubPkg; + AddCustomOption(g_pCustomizeDecalsPkg, 0x52ded91d, 0x301dedd3, 0x501); + AddCustomOption(g_pCustomizeDecalsPkg, 0xac7937b4, 0x48e6ca49, 0x502); + AddCustomOption(g_pCustomizeSubTopPkg, 0xda88b711, 0x34367c86, 0x503); + AddCustomOption(g_pCustomizeSubTopPkg, 0xc9a967c4, 0xddf80259, 0x504); + AddCustomOption(g_pCustomizeDecalsPkg, 0x2c710c4d, 0x8a7697d6, 0x505); + AddCustomOption(g_pCustomizeDecalsPkg, 0xffa7d360, 0xb1f9b0c9, 0x506); + if (FromCategory == 0x803) { + SetInitialOption(1); + } else { + SetInitialOption(FromCategory & 0xFFFF00FF); + } + if (FromCategory - 0x501u < 6u) { + FromCategory = 0x803; + } +} + +void CustomizeSub::SetupDecalPositions() { + TitleHash = 0x74d1887d; + BackToPkg = g_pCustomizeSubTopPkg; + switch (Category) { + case 0x503: + AddCustomOption(g_pCustomizeDecalsPkg, 0xfe957f48, 0x7d212cfa, 0x601); + AddCustomOption(g_pCustomizeDecalsPkg, 0xfe957f49, 0x7d212cfb, 0x602); + AddCustomOption(g_pCustomizeDecalsPkg, 0xfe957f4a, 0x7d212cfc, 0x603); + AddCustomOption(g_pCustomizeDecalsPkg, 0xfe957f4b, 0x7d212cfd, 0x604); + AddCustomOption(g_pCustomizeDecalsPkg, 0xfe957f4c, 0x7d212cfe, 0x605); + AddCustomOption(g_pCustomizeDecalsPkg, 0xfe957f4d, 0x7d212cff, 0x606); + break; + case 0x504: + AddCustomOption(g_pCustomizeDecalsPkg, 0x2e40eadb, 0x7d212cfa, 0x601); + AddCustomOption(g_pCustomizeDecalsPkg, 0x2e40eadc, 0x7d212cfb, 0x602); + AddCustomOption(g_pCustomizeDecalsPkg, 0x2e40eadd, 0x7d212cfc, 0x603); + AddCustomOption(g_pCustomizeDecalsPkg, 0x2e40eade, 0x7d212cfd, 0x604); + AddCustomOption(g_pCustomizeDecalsPkg, 0x2e40eadf, 0x7d212cfe, 0x605); + AddCustomOption(g_pCustomizeDecalsPkg, 0x2e40eae0, 0x7d212cff, 0x606); + break; + } + if (FromCategory == 0x305) { + SetInitialOption(1); + } else { + SetInitialOption(FromCategory & 0xFFFF00FF); + FromCategory = 0x305; + } +} + +void CustomizeSub::RefreshHeader() { + CustomizeCategoryScreen::RefreshHeader(); + const char *title_str = GetLocalizedString(TitleHash); + char buf[64]; + bSNPrintf(buf, 64, "%s", title_str); + int lang = GetCurrentLanguage(); + if (lang != 2 && lang != 13) { + int i = 0; + while (buf[i] != 0) { + char c = buf[i]; + if (static_cast(c - 'A') < 26u) { + c = c | 0x20; + } + buf[i] = c; + i++; + } + } + FEPrintf(GetPackageName(), 0xb71b576d, "%s", buf); + if (Category == 0x103 || Category == 0x302) { + int sel = Options.GetCurrentIndex(); + if (sel == InCartPartOptionIndex) { + FEngSetVisible(FEngFindObject(GetPackageName(), 0xd0582feb)); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xd0582feb), 0x1a777e25); + } else if (sel == InstalledPartOptionIndex) { + FEngSetVisible(FEngFindObject(GetPackageName(), 0xd0582feb)); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xd0582feb), 0x696ae039); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xd0582feb)); + } + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xd0582feb)); + } + if (!gCarCustomizeManager.IsCareerMode() && Category == 0x802) { + FEngSetVisible(FEngFindObject(GetPackageName(), 0x5aec8d91)); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x5aec8d91)); + } +} + +// --- Free functions --- + +int TranslateCustomizeCatToMarker(eCustomizeCategory cat) { + switch (static_cast(cat)) { + case CC_BODY_KIT: return FEMarkerManager::MARKER_BODY; + case CC_SPOILERS: return FEMarkerManager::MARKER_SPOILER; + case CC_HOODS: return FEMarkerManager::MARKER_HOOD; + case CC_ROOF_SCOOPS: return FEMarkerManager::MARKER_ROOF_SCOOP; + case CC_CUSTOM_HUD: return FEMarkerManager::MARKER_CUSTOM_HUD; + case CC_ENGINE: return FEMarkerManager::MARKER_ENGINE; + case CC_TRANSMISSION: return FEMarkerManager::MARKER_TRANSMISSION; + case CC_SUSPENSION: return FEMarkerManager::MARKER_CHASSIS; + case CC_NITROUS: return FEMarkerManager::MARKER_NOS; + case CC_TIRES: return FEMarkerManager::MARKER_TIRES; + case CC_BRAKES: return FEMarkerManager::MARKER_BRAKES; + case CC_FORCED_INDUCTION: return FEMarkerManager::MARKER_INDUCTION; + case CC_PAINT: + case CC_RIM_PAINT: return FEMarkerManager::MARKER_PAINT; + case CC_VINYL_TYPES: + case CC_VINYL_GROUP_FLAME: + case CC_VINYL_GROUP_TRIBAL: + case CC_VINYL_GROUP_STRIPE: + case CC_VINYL_GROUP_RACING_FLAG: + case CC_VINYL_GROUP_NATIONAL_FLAG: + case CC_VINYL_GROUP_BODY: + case CC_VINYL_GROUP_UNIQUE: + case CC_VINYL_GROUP_CONTEST: + return FEMarkerManager::MARKER_VINYL; + case CC_RIM_BRANDS: + case CC_RIM_BRAND_5_ZIGEN: + case CC_RIM_BRAND_ADR: + case CC_RIM_BRAND_BBS: + case CC_RIM_BRAND_ENKEI: + case CC_RIM_BRAND_KONIG: + case CC_RIM_BRAND_LOWENHART: + case CC_RIM_BRAND_RACING_HART: + case CC_RIM_BRAND_OZ: + case CC_RIM_BRAND_VOLK: + case CC_RIM_BRAND_ROJA: + return FEMarkerManager::MARKER_RIMS; + case CC_DECAL_LOCATION: + case CC_DECAL_WINDSHIELD: + case CC_DECAL_REAR_WINDOW: + case CC_DECAL_LEFT_DOOR: + case CC_DECAL_RIGHT_DOOR: + case CC_DECAL_LEFT_QP: + case CC_DECAL_RIGHT_QP: + case CC_DECAL_SLOT_1: + case CC_DECAL_SLOT_2: + case CC_DECAL_SLOT_3: + case CC_DECAL_SLOT_4: + case CC_DECAL_SLOT_5: + case CC_DECAL_SLOT_6: + return FEMarkerManager::MARKER_DECAL; + default: + return 0; + } +} + +unsigned int GetMarkerNameFromCategory(eCustomizeCategory cat) { + switch (static_cast(cat)) { + case CC_PARTS: return 0xd3a2fbe1; + case CC_PERFORMANCE: return 0x3c27a989; + case CC_VISUAL: return 0x5692be6b; + case CC_BODY_KIT: return 0x7c50498c; + case CC_SPOILERS: return 0x52012995; + case CC_RIM_BRANDS: + case CC_RIM_BRAND_5_ZIGEN: + case CC_RIM_BRAND_ADR: + case CC_RIM_BRAND_BBS: + case CC_RIM_BRAND_ENKEI: + case CC_RIM_BRAND_KONIG: + case CC_RIM_BRAND_LOWENHART: + case CC_RIM_BRAND_RACING_HART: + case CC_RIM_BRAND_OZ: + case CC_RIM_BRAND_VOLK: + case CC_RIM_BRAND_ROJA: + return 0x8a4bfbf2; + case CC_HOODS: return 0x8a4699e1; + case CC_ROOF_SCOOPS: return 0x830100f0; + case CC_CUSTOM_HUD: return 0xc253ec92; + case CC_ENGINE: return 0x2f3ec04d; + case CC_TRANSMISSION: return 0xd1e77ca1; + case CC_SUSPENSION: return 0xb7cbfcce; + case CC_NITROUS: return 0xc129562b; + case CC_TIRES: return 0xd3efbefe; + case CC_BRAKES: return 0x2884658f; + case CC_FORCED_INDUCTION: + if (gCarCustomizeManager.IsTurbo()) { + return 0xd3f65323; + } + return 0x63a51aa2; + case CC_PAINT: return 0xd3a2d4d3; + case CC_VINYL_TYPES: + case CC_VINYL_GROUP_FLAME: + case CC_VINYL_GROUP_TRIBAL: + case CC_VINYL_GROUP_STRIPE: + case CC_VINYL_GROUP_RACING_FLAG: + case CC_VINYL_GROUP_NATIONAL_FLAG: + case CC_VINYL_GROUP_BODY: + case CC_VINYL_GROUP_UNIQUE: + case CC_VINYL_GROUP_CONTEST: + return 0xd413e189; + case CC_DECAL_LOCATION: + case CC_DECAL_WINDSHIELD: + case CC_DECAL_REAR_WINDOW: + case CC_DECAL_LEFT_DOOR: + case CC_DECAL_RIGHT_DOOR: + case CC_DECAL_LEFT_QP: + case CC_DECAL_RIGHT_QP: + case CC_DECAL_SLOT_1: + case CC_DECAL_SLOT_2: + case CC_DECAL_SLOT_3: + case CC_DECAL_SLOT_4: + case CC_DECAL_SLOT_5: + case CC_DECAL_SLOT_6: + return 0xd2cbc510; + default: + return 0; + } +} + +unsigned int GetNumMarkersFromCategory(eCustomizeCategory cat) { + switch (static_cast(cat)) { + case CC_PARTS: { + int total = GetNumMarkersFromCategory(CC_BODY_KIT); + total += GetNumMarkersFromCategory(CC_SPOILERS); + total += GetNumMarkersFromCategory(CC_RIM_BRANDS); + total += GetNumMarkersFromCategory(CC_HOODS); + total += GetNumMarkersFromCategory(CC_ROOF_SCOOPS); + return total + GetNumMarkersFromCategory(CC_CUSTOM_HUD); + } + case CC_PERFORMANCE: { + int total = GetNumMarkersFromCategory(CC_ENGINE); + total += GetNumMarkersFromCategory(CC_TRANSMISSION); + total += GetNumMarkersFromCategory(CC_SUSPENSION); + total += GetNumMarkersFromCategory(CC_NITROUS); + total += GetNumMarkersFromCategory(CC_TIRES); + total += GetNumMarkersFromCategory(CC_BRAKES); + return total + GetNumMarkersFromCategory(CC_FORCED_INDUCTION); + } + case CC_VISUAL: { + int total = GetNumMarkersFromCategory(CC_PAINT); + total += GetNumMarkersFromCategory(CC_VINYL_TYPES); + return total + GetNumMarkersFromCategory(CC_DECAL_LOCATION); + } + default: + return TheFEMarkerManager.GetNumMarkers( + static_cast(TranslateCustomizeCatToMarker(cat)), 0); + } +} + +void CustomizeSub::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + unsigned int to_cat = static_cast( + static_cast(Options.GetCurrentOption())->Category); + + if (Category != 0x803 || to_cat != 0x303 || msg != 0xc407210) { + CustomizeCategoryScreen::NotificationMessage(msg, pobj, param1, param2); + } + + switch (msg) { + case 0x5a928018: { + CustomizeMainOption *opt = FindInCartOption(); + if (!opt) return; + int slot_id = 0; + switch (opt->Category) { + case 0x103: + slot_id = 0x42; + break; + case 0x302: + slot_id = 0x4d; + break; + } + if (slot_id == 0) return; + if (gCarCustomizeManager.IsPartTypeInCart(slot_id)) return; + InCartPartOptionIndex = 0; + RefreshHeader(); + break; + } + case 0xc407210: { + if (to_cat <= 0x506) { + if (to_cat >= 0x501) { + CustomizeDecals::CurrentDecalLocation = to_cat; + } + } + + SetStockPartOption *copt = static_cast(Options.GetCurrentOption()); + copt->IsStockOption(); + bool stockOption = copt->IsStockOption(); + + if (stockOption && + (copt->ThePart->GetPartState() & CPS_PLAYER_STATE_MASK) == CPS_INSTALLED && + InCartPartOptionIndex != 0) { + int slot_id = 0; + switch (static_cast(copt->Category)) { + case 0x701: + slot_id = 0x42; + break; + case 0x401: + slot_id = 0x4d; + for (int i = 0; i <= 2; i++) { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(i + 0x4f); + if (item) { + gCarCustomizeManager.RemoveFromCart(item); + } + } + break; + } + if (slot_id != 0) { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(slot_id); + if (item) { + gCarCustomizeManager.RemoveFromCart(item); + InCartPartOptionIndex = 0; + RefreshHeader(); + } + } + } + + if (bStrICmp(GetPackageName(), g_pCustomizeSubTopPkg) != 0 && + bStrICmp(GetPackageName(), g_pCustomizeSubPkg) != 0) { + return; + } + + bool ok_to_leave = false; + switch (Category) { + case 0x803: + if (to_cat == 0x303) { + CarPart *stock_rim = gCarCustomizeManager.GetStockCarPart(0x42); + CarPart *installed = gCarCustomizeManager.GetInstalledCarPart(0x42); + if (stock_rim == installed) { + DialogInterface::ShowOneButton(GetPackageName(), "", + static_cast(1), 0x417b2601u, 0xb4edeb6du, 0xbdb19a9fu); + } else { + CustomizeMainOption *opt2 = static_cast(Options.GetCurrentOption()); + cFEng::Get()->QueuePackageSwitch(opt2->ToPkg, opt2->Category, 0, false); + ok_to_leave = true; + } + } + break; + case 0x103: + if (Options.GetCurrentIndex() == 1) { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(0x42); + if (item) { + unsigned int brandHash = item->GetBuyingPart()->GetPart()->GetAppliedAttributeUParam(0xebb03e66, 0); + InCartPartOptionIndex = GetRimBrandIndex(brandHash); + } + CarPart *installed2 = gCarCustomizeManager.GetInstalledCarPart(0x42); + if (installed2) { + unsigned int brandHash = installed2->GetAppliedAttributeUParam(0xebb03e66, 0); + InstalledPartOptionIndex = GetRimBrandIndex(brandHash); + } + Options.SetReactToInput(true); + RefreshHeader(); + } else { + ok_to_leave = true; + } + break; + case 0x302: + if (Options.GetCurrentIndex() == 1) { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(0x4d); + if (item) { + CarPart *car_part = item->GetBuyingPart()->GetPart(); + if (car_part) { + InCartPartOptionIndex = GetVinylGroupIndex(car_part->GetGroupNumber() & 0x1f); + } else { + InCartPartOptionIndex = 1; + } + } + CarPart *installed3 = gCarCustomizeManager.GetInstalledCarPart(0x4d); + if (installed3) { + InstalledPartOptionIndex = GetVinylGroupIndex(installed3->GetGroupNumber() & 0x1f); + } else { + InstalledPartOptionIndex = 1; + } + Options.SetReactToInput(true); + RefreshHeader(); + } else { + ok_to_leave = true; + } + break; + default: + ok_to_leave = true; + break; + } + if (!ok_to_leave) return; + cFEng::Get()->QueuePackageMessage(0x587c018b, GetPackageName(), nullptr); + break; + } + case 0xc519bfc3: + if (gCarCustomizeManager.IsCareerMode()) return; + if (Category != 0x802) return; + DialogInterface::ShowTwoButtons(GetPackageName(), "", + static_cast(3), 0x70e01038u, 0x417b25e4u, 0x6820e23eu, 0xb4edeb6du, + static_cast(0), 0x892cb612u); + RefreshHeader(); + break; + case 0x6820e23e: + gCarCustomizeManager.MaxOutPerformance(); + RefreshHeader(); + break; + case 0xb4edeb6d: + Options.SetReactToInput(true); + RefreshHeader(); + break; + case 0xcf91aacd: + CustomizeShoppingCart::ExitShoppingCart(); + return; + } +} + +CustomizeSub::~CustomizeSub() {} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.hpp index f8556c946..196807b05 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CarCustomize.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE____CUSTOMIZE_CARCUSTOMIZE_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE____CUSTOMIZE_CARCUSTOMIZE_H @@ -5,6 +6,364 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" + +#include + +struct FEImage; +struct FEString; +struct CarPart; +struct HUDColorOption; + +// total size: 0x1CC +struct CustomizeCategoryScreen : public IconScrollerMenu { + CustomizeCategoryScreen(ScreenConstructorData *sd); + ~CustomizeCategoryScreen() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + void RefreshHeader() override; + void Setup() override {} + + int AddCustomOption(const char *to_pkg, unsigned int tex_hash, unsigned int name_hash, unsigned int to_cat); + + bool bBackingOut; // offset 0x16C, size 0x1 + const char *BackToPkg; // offset 0x170, size 0x4 + unsigned int Category; // offset 0x174, size 0x4 + unsigned int FromCategory; // offset 0x178, size 0x4 + CustomizeMeter HeatMeter; // offset 0x17C, size 0x50 +}; + +// total size: 0x1D4 +struct CustomizeMain : public CustomizeCategoryScreen { + CustomizeMain(ScreenConstructorData *sd); + ~CustomizeMain() override {} + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void RefreshHeader() override; + void Setup() override; + + void SwitchRooms(); + void SetScreenNames(); + void SetTitle(bool isInBackroom); + void BuildOptionsList(); + + int iPerfIndex; // offset 0x1CC, size 0x4 + int invalidMarkers; // offset 0x1D0, size 0x4 +}; + +// total size: 0x1D8 +struct CustomizeSub : public CustomizeCategoryScreen { + CustomizeSub(ScreenConstructorData *sd); + ~CustomizeSub() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void RefreshHeader() override; + void Setup() override; + + void RemoveCarPart(); + void AddRemovalPart(unsigned int slot_id); + CustomizeMainOption *FindInCartOption(); + void SetupParts(); + void SetupPerformance(); + void SetupVisual(); + int GetRimBrandIndex(unsigned int brand); + void SetupRimBrands(); + int GetVinylGroupIndex(int group); + void SetupVinylGroups(); + void SetupDecalLocations(); + void SetupDecalPositions(); + + int InstalledPartOptionIndex; // offset 0x1CC, size 0x4 + int InCartPartOptionIndex; // offset 0x1D0, size 0x4 + unsigned int TitleHash; // offset 0x1D4, size 0x4 +}; + +// total size: 0x1E4 +struct CustomizationScreen : public IconScrollerMenu { + CustomizationScreen(ScreenConstructorData *sd); + ~CustomizationScreen() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void RefreshHeader() override; + + void AddPartOption(SelectablePart *part, unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash, unsigned int unlock_hash, bool locked); + CustomizePartOption *GetSelectedOption() { return static_cast(Options.GetCurrentOption()); } + virtual SelectablePart *GetSelectedPart() { return GetSelectedOption()->GetPart(); } + virtual SelectablePart *FindInCartPart(); + virtual CustomizePartOption *FindMatchingOption(SelectablePart *to_find); + + void SetCareerStatusIcon(eCustomizePartState state) { DisplayHelper.SetCareerStatusIcon(state); } + void SetPlayerCarStatusIcon(eCustomizePartState state) { DisplayHelper.SetPlayerCarStatusIcon(state); } + void SetTitleHash(unsigned int title_hash) { DisplayHelper.SetTitleHash(title_hash); } + unsigned int GetCategory() { return Category; } + unsigned int GetFromCategory() { return FromCategory; } + void SetCashVisibility(bool visible) { DisplayHelper.SetCashVisibility(visible); } + void SetUnlockOverlayState(bool show, unsigned int blurb_hash) { DisplayHelper.SetUnlockOverlayState(show, blurb_hash); } + void PlayLocked() { DisplayHelper.PlayLocked(); } + void PlayInCart() { DisplayHelper.PlayInCart(); } + void PlayInstalled() { DisplayHelper.PlayInstalled(); } + + unsigned int Category; // offset 0x16C, size 0x4 + unsigned int FromCategory; // offset 0x170, size 0x4 + CustomizePartOption *pReplacingOption; // offset 0x174, size 0x4 + CustomizationScreenHelper DisplayHelper; // offset 0x178, size 0x64 + bool bNeedsRefresh; // offset 0x1DC, size 0x1 + Timer ScrollTime; // offset 0x1E0, size 0x4 +}; + +// total size: 0x1F4 +struct CustomizeParts : public CustomizationScreen { + static void TexturePackLoadedCallbackAccessor(unsigned int parts_screen) { + reinterpret_cast(parts_screen)->TexturePackLoadedCallback(); + } + static void TextureLoadedCallbackAccessor(unsigned int parts_screen) { + reinterpret_cast(parts_screen)->TextureLoadedCallback(); + } + + CustomizeParts(ScreenConstructorData *sd); + ~CustomizeParts() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + void RefreshHeader() override; + + void LoadHudTextures(); + void LoadNextHudTexturePack(); + void TexturePackLoadedCallback(); + void TextureLoadedCallback(); + void ShowHudObjects(); + void SetHUDTextures(); + void SetHUDColors(); + + static bool TexturePackLoaded; // address: 0x80439228 + + int PacksLoadedCount; // offset 0x1E4, size 0x4 + int TexturesLoadedCount; // offset 0x1E8, size 0x4 + int TachRPM; // offset 0x1EC, size 0x4 + bool bTexturesNeedUnload; // offset 0x1F0, size 0x1 +}; + +// total size: 0x324 +struct CustomizePaint : public CustomizationScreen { + CustomizePaint(ScreenConstructorData *sd); + ~CustomizePaint() override; + + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + SelectablePart *FindInCartPart() override; + CustomizePartOption *FindMatchingOption(SelectablePart *to_find) override; + SelectablePart *GetSelectedPart() override; + + unsigned int GetUnlockBlurb() { return 0; } + + void AddVinylAndColorsToCart(); + void ScrollFilters(eScrollDir dir); + void Setup() override; + void SetupBasePaint(); + void SetupRimPaint(); + void SetupVinylColor(); + unsigned int CalcBrandHash(CarPart *part); + void BuildSwatchList(unsigned int slot_id); + void RefreshHeader() override; + + int TheFilter; // offset 0x1E4, size 0x4 + int SelectedIndex[3]; // offset 0x1E8, size 0xC + CustomizePartOption MatchingPaint; // offset 0x1F4, size 0x64 + ArrayScroller ThePaints; // offset 0x258, size 0xBC + SelectablePart *VinylColors[3]; // offset 0x314, size 0xC + int NumRemapColors; // offset 0x320, size 0x4 +}; + +// total size: 0x2C8 +struct CustomizePerformance : public CustomizationScreen { + CustomizePerformance(ScreenConstructorData *sd); + ~CustomizePerformance() override {} + + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void RefreshHeader() override; + void Setup() override; + + unsigned int GetPerfPkgDesc(Physics::Upgrades::Type type, int level, int line, bool turbo); + unsigned int GetPerfPkgBrand(Physics::Upgrades::Type type, int level, int line); + + FEString *DescLines[3]; // offset 0x1E4, size 0xC + FEImage *DescBullets[3]; // offset 0x1F0, size 0xC + TwoStageSlider AccelSlider; // offset 0x1FC, size 0x44 + TwoStageSlider HandlingSlider; // offset 0x240, size 0x44 + TwoStageSlider TopSpeedSlider; // offset 0x284, size 0x44 +}; + +// total size: 0xB8 +struct CustomizeNumbers : public MenuScreen { + CustomizeNumbers(ScreenConstructorData *sd); + ~CustomizeNumbers() override {} + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + unsigned int GetCategory() { return Category; } + unsigned int GetFromCategory() { return FromCategory; } + + void UnsetShoppingCart(); + void ScrollNumbers(eScrollDir dir); + void RefreshHeader(); + void Setup(); + + static bool bShowcaseOn; // address: 0x80439230 + + bTList LeftNumberList; // offset 0x2C, size 0x8 + bTList RightNumberList; // offset 0x34, size 0x8 + SelectablePart *TheLeftNumber; // offset 0x3C, size 0x4 + SelectablePart *TheRightNumber; // offset 0x40, size 0x4 + unsigned int Category; // offset 0x44, size 0x4 + unsigned int FromCategory; // offset 0x48, size 0x4 + short LeftDisplayValue; // offset 0x4C, size 0x2 + short RightDisplayValue; // offset 0x4E, size 0x2 + bool bLeft; // offset 0x50, size 0x1 + CustomizationScreenHelper DisplayHelper; // offset 0x54, size 0x64 +}; + +// total size: 0x1F8 +struct CustomizeHUDColor : public CustomizationScreen { + CustomizeHUDColor(ScreenConstructorData *sd); + ~CustomizeHUDColor() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + void RefreshHeader() override; + + void ScrollColors(eScrollDir dir); + void AddLayerOption(unsigned int layer, unsigned int icon_hash, unsigned int name_hash); + void SetInitialColors(); + void SetHUDTextures(); + void BuildColorOptions(); + + bTList ColorOptions; // offset 0x1E4, size 0x8 + HUDColorOption *SelectedColor; // offset 0x1EC, size 0x4 + FEObject *Cursor; // offset 0x1F0, size 0x4 + bool bTexturesNeedUnload; // offset 0x1F4, size 0x1 +}; + +// total size: 0x1E8 +struct CustomizeDecals : public CustomizationScreen { + CustomizeDecals(ScreenConstructorData *sd); + ~CustomizeDecals() override {} + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void RefreshHeader() override; + void Setup() override; + + unsigned int GetSlotIDFromCategory(); + void BuildDecalList(unsigned int selected_name_hash); + + static unsigned int CurrentDecalLocation; // address: 0x8043922C + + int bIsBlack; // offset 0x1E4, size 0x4 +}; + +// total size: 0x1F8 +struct CustomizeSpoiler : public CustomizationScreen { + CustomizeSpoiler(ScreenConstructorData *sd); + ~CustomizeSpoiler() override {} + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + void RefreshHeader() override; + + void BuildPartOptionListFromFilter(CarPart *installed); + void ScrollFilters(eScrollDir dir); + + int TheFilter; // offset 0x1E4, size 0x4 + unsigned int SelectedIndex[4]; // offset 0x1E8, size 0x10 +}; + +// total size: 0x1F0 +struct CustomizeRims : public CustomizationScreen { + CustomizeRims(ScreenConstructorData *sd); + ~CustomizeRims() override {} + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + void RefreshHeader() override; + + void ScrollRimSizes(eScrollDir dir); + void BuildRimsList(int selected_index); + unsigned int GetCategoryLanguageHash(); + unsigned int GetCategoryBrandHash(); + + int InnerRadius; // offset 0x1E4, size 0x4 + int MinRadius; // offset 0x1E8, size 0x4 + int MaxRadius; // offset 0x1EC, size 0x4 +}; + +// total size: 0x60 +struct FEShoppingCartItem : public FEStatWidget { + FEShoppingCartItem(ShoppingCartItem *item) + : FEStatWidget(true) // + , TheItem(item) {} + ~FEShoppingCartItem() override {} + + void SetCheckIcon(FEImage *img) { pCheckIcon = img; } + void SetTradeInString(FEString *string) { pTradeInPrice = string; } + ShoppingCartItem *GetItem() { return TheItem; } + + void Show() override; + void Hide() override; + void Draw() override; + void Position() override; + void SetFocus(const char *parent_pkg) override; + void UnsetFocus() override; + void SetCheckScripts(); + void SetActiveScripts(); + void DrawPartName(); + unsigned int GetPerfPkgCatHash(Physics::Upgrades::Type phys_type); + unsigned int GetPerfPkgLevelHash(int level); + unsigned int GetCarPartCatHash(unsigned int slot_id); + + FEImage *pCheckIcon; // offset 0x54, size 0x4 + FEString *pTradeInPrice; // offset 0x58, size 0x4 + ShoppingCartItem *TheItem; // offset 0x5C, size 0x4 +}; +// total size: 0x188 +struct CustomizeShoppingCart : public UIWidgetMenu { + CustomizeShoppingCart(ScreenConstructorData *sd); + ~CustomizeShoppingCart() override {} + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + + FEShoppingCartItem *GetCurrentItem() { return nullptr; } + + static void ShowShoppingCart(const char *parent_pkg); + static void ExitShoppingCart(); + bool IsSlotIDNumberDecal(int slotID); + void ToggleAllNumberDecals(); + void ToggleChecked(); + bool CanCheckout(); + void SetMarkerBloomScript(unsigned int group, unsigned int bloom, ShoppingCartItem *item); + void SetMarkerData(int num, ShoppingCartItem *item, int num_markers); + int GetNumMarkersSpending(unsigned int slot_id); + void SetMarkerAmounts(); + void RefreshHeader(); + void AddItem(ShoppingCartItem *item); + void ClearUncheckedItems(); + void UncheckAllItems(); + void SetMarkerImages(); + + static const char *pParentPkg; // address: 0x804391C4 + + CustomizeMeter HeatMeter; // offset 0x138, size 0x50 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeEntryPoint.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeEntryPoint.hpp new file mode 100644 index 000000000..41ce9f18b --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeEntryPoint.hpp @@ -0,0 +1,14 @@ +#ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_CUSTOMIZEENTRYPOINT_H +#define FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_CUSTOMIZEENTRYPOINT_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +enum eCustomizeEntryPoint { + CEP_GAMEPLAY = 0, + CEP_MAIN_MENU = 1, + CEP_ONLINE_MENU = 2, +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.cpp index e69de29bb..9c7617110 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.cpp @@ -0,0 +1,1401 @@ +// OWNED BY zFeOverlay AGENT +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Misc/EasterEggs.hpp" +#include "Speed/Indep/Src/Physics/PhysicsUpgrades.hpp" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" +#include "Speed/Indep/Src/Ecstasy/eStreamingPack.hpp" + +namespace Physics { +namespace Upgrades { + bool CanInstallJunkman(const Attrib::Gen::pvehicle &pvehicle, Type type); + void SetLevel(Attrib::Gen::pvehicle &pvehicle, Type type, int level); +} +} + +extern CarTypeInfo *GetCarTypeInfoFromHash(unsigned int hash); + +extern FEMarkerManager TheFEMarkerManager; + +extern int g_bTestCareerCustomization; +extern int g_bCustomizeManagerHasControl; +extern SelectablePart *_8Showcase_FromColor; +extern float gTradeInFactor; +extern bool CustomizeIsInBackRoom(); +extern CarPart *GetCarPart(RideInfo *ride, unsigned int slot_id); +extern unsigned int GetVinylLayerHash(CarPart *part, CarType type, int param); +extern bool GetIsCollectorsEdition(); + +struct CarPartAttribute; +struct CarPart { + unsigned short PartNameHashBot; // offset 0x0, size 0x2 + unsigned short PartNameHashTop; // offset 0x2, size 0x2 + char PartID; // offset 0x4, size 0x1 + unsigned char GroupNumber_UpgradeLevel; // offset 0x5, size 0x1 + char BaseModelNameHashSelector; // offset 0x6, size 0x1 + unsigned char CarTypeNameHashIndex; // offset 0x7, size 0x1 + unsigned short NameOffset; // offset 0x8, size 0x2 + unsigned short AttributeTableOffset; // offset 0xA, size 0x2 + unsigned short ModelNameHashTableOffset; // offset 0xC, size 0x2 + + const char *GetName(); + unsigned int GetCarTypeNameHash(); + unsigned int GetPartNameHash(); + char GetPartID(); + char GetUpgradeLevel(); + char GetGroupNumber() { return GroupNumber_UpgradeLevel & 0x1f; } + int HasAppliedAttribute(unsigned int namehash); + const char *GetAppliedAttributeString(unsigned int namehash, const char *default_string); + float GetAppliedAttributeFParam(unsigned int namehash, float default_value); + int GetAppliedAttributeIParam(unsigned int namehash, int default_value); + unsigned int GetAppliedAttributeUParam(unsigned int namehash, unsigned int default_value); + unsigned int GetBrandNameHash() { return GetAppliedAttributeUParam(0xebb03e66, 0); } + unsigned int GetModelNameHash(int param1, int param2); +}; + +// These wrappers are header-inlined in the repo, but the original overlay still emits +// standalone copies that zFeOverlay compares against. +#ifndef EA_PLATFORM_PLAYSTATION2 +asm( + ".globl GetCarTypeInfo__F7CarType\n" + "GetCarTypeInfo__F7CarType:\n" + "lis 4, CarTypeInfoArray@ha\n" + "lwz 4, CarTypeInfoArray@l(4)\n" + "mulli 0, 3, 0xd0\n" + "add 3, 4, 0\n" + "blr\n" + ".globl GetPart__14SelectablePart\n" + "GetPart__14SelectablePart:\n" + "lwz 3, 8(3)\n" + "blr\n" + ".globl GetSlotID__14SelectablePart\n" + "GetSlotID__14SelectablePart:\n" + "lwz 3, 0xc(3)\n" + "blr\n" + ".globl GetUpgradeLevel__14SelectablePart\n" + "GetUpgradeLevel__14SelectablePart:\n" + "lwz 3, 0x10(3)\n" + "blr\n" + ".globl GetPhysicsType__14SelectablePart\n" + "GetPhysicsType__14SelectablePart:\n" + "lwz 3, 0x14(3)\n" + "blr\n" + ".globl IsPerformancePkg__14SelectablePart\n" + "IsPerformancePkg__14SelectablePart:\n" + "lwz 3, 0x18(3)\n" + "blr\n" + ".globl GetPartState__14SelectablePart\n" + "GetPartState__14SelectablePart:\n" + "lwz 3, 0x1c(3)\n" + "blr\n" + ".globl GetPrice__14SelectablePart\n" + "GetPrice__14SelectablePart:\n" + "lwz 3, 0x20(3)\n" + "blr\n" + ".globl IsJunkmanPart__14SelectablePart\n" + "IsJunkmanPart__14SelectablePart:\n" + "lwz 3, 0x24(3)\n" + "blr\n"); +#endif + +int CarCustomizeManager::GetNumPackages(Physics::Upgrades::Type type) { + return Physics::Upgrades::GetMaxLevel(ThePVehicle, type); +} + +bool CarCustomizeManager::CanInstallJunkman(Physics::Upgrades::Type type) { + return Physics::Upgrades::CanInstallJunkman(ThePVehicle, type); +} + +bool CarCustomizeManager::IsCareerMode() { + return FEDatabase->IsCareerMode() || g_bTestCareerCustomization; +} + +bool CarCustomizeManager::IsHeroCar() { + return TuningCar->GetType() == 0x29; +} + +int CarCustomizeManager::GetNumCustomizeMarkers() { + if (g_bTestCareerCustomization) { + return 1; + } + return TheFEMarkerManager.GetNumCustomizeMarkers(); +} + +bool CarCustomizeManager::IsCastrolCar() { + if (TuningCar->GetType() == 0x34) { + return gEasterEggs.IsEasterEggUnlocked(EASTER_EGG_CASTROL); + } + return false; +} + +bool CarCustomizeManager::IsRotaryCar() { + int type = TuningCar->GetType(); + if (type == 0x05 || type == 0x38) { + return true; + } + return false; +} + +int CarCustomizeManager::GetMinInnerRadius() { + CarTypeInfo *info = GetCarTypeInfo(TuningCar->GetType()); + if (info) { + return info->WheelInnerRadiusMin; + } + return 0; +} + +int CarCustomizeManager::GetMaxInnerRadius() { + CarTypeInfo *info = GetCarTypeInfo(TuningCar->GetType()); + if (info) { + return info->WheelInnerRadiusMax; + } + return 0; +} + +int CarCustomizeManager::GetMaxPackages(Physics::Upgrades::Type type) { + switch (type) { + case 0: return 3; + case 1: return 4; + case 2: return 3; + case 3: return 4; + case 4: return 4; + case 5: return 3; + case 6: return 3; + default: return -1; + } +} + +int CarCustomizeManager::GetInstalledPerfPkg(Physics::Upgrades::Type type) { + FEPlayerCarDB *db = FEDatabase->GetPlayerCarStable(0); + FECustomizationRecord *record = db->GetCustomizationRecordByHandle(TuningCar->Customization); + const Physics::Upgrades::Package *pkg = &record->InstalledPhysics; + return pkg->Part[type]; +} + +void CarCustomizeManager::TakeControl(eCustomizeEntryPoint entry_point, FECarRecord *tuning_car) { + if (!g_bCustomizeManagerHasControl) { + FEDatabase->SetGameMode(eFE_GAME_MODE_CUSTOMIZE); + g_bCustomizeManagerHasControl = 1; + for (int i = 0; i < 3; i++) { + (&_8Showcase_FromColor)[i] = nullptr; + } + TheTempColoredPart = nullptr; + EntryPoint = entry_point; + TuningCar = tuning_car; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + PreviewRecord = *stable->GetCustomizationRecordByHandle(TuningCar->Customization); + Attrib::Gen::pvehicle vehicle(TuningCar->VehicleKey, 0, nullptr); + stable->WriteRecordIntoPhysics(TuningCar->Handle, vehicle); + ThePVehicle = vehicle; + RideInfo info; + stable->BuildRideForPlayer(TuningCar->Handle, 0, &info); + CarViewer::SetRideInfo(&info, SET_RIDE_INFO_REASON_LOAD_CAR, eCARVIEWER_PLAYER1_CAR); + NumPartsInCart = 0; + } +} + +void CarCustomizeManager::RelinquishControl() { + FEDatabase->ClearGameMode(eFE_GAME_MODE_CUSTOMIZE); + for (int i = 0; i < 3; i++) { + delete (&_8Showcase_FromColor)[i]; + (&_8Showcase_FromColor)[i] = nullptr; + } + ClearTempColoredPart(); + g_bCustomizeManagerHasControl = 0; +} + +bool CarCustomizeManager::CanTradeIn(SelectablePart *part) { + if (!part->IsPerformancePkg()) { + int slot = part->GetSlotID(); + if (slot <= 0x73) { + if (slot >= 0x63) { + return false; + } + if (slot < 0x4c) { + return true; + } + if (slot <= 0x53) { + return false; + } + if (slot == 0x5b) { + return false; + } + } else if (slot == 0x7b) { + return false; + } else if (slot < 0x7b) { + return true; + } else if (slot > 0x87) { + return true; + } else if (slot < 0x83) { + return true; + } + } + return false; +} + +void CarCustomizeManager::AddToCart(SelectablePart *part) { + ShoppingCartItem *existing = IsPartTypeInCart(part); + SelectablePart *trade_in = nullptr; + if (existing) { + if (CanTradeIn(part) && existing->TradeIn) { + trade_in = new SelectablePart(existing->TradeIn); + } + RemoveFromCart(existing); + } else { + if (!part->PerformancePkg) { + if (CanTradeIn(part)) { + CarPart *installed = GetInstalledCarPart(part->CarSlotID); + if (installed) { + trade_in = new SelectablePart(installed, part->CarSlotID, + static_cast(installed->GroupNumber_UpgradeLevel >> 5), static_cast(7), false, + CPS_INSTALLED, 0, false); + trade_in->SetPrice(GetPartPrice(trade_in)); + } + } + } + } + SelectablePart *to_buy = new SelectablePart(part); + to_buy->SetInCart(); + ShoppingCartItem *item = new ShoppingCartItem(to_buy, trade_in); + ShoppingCart.AddTail(item); + NumPartsInCart++; +} + +bool CarCustomizeManager::RemoveFromCart(ShoppingCartItem *item) { + if (item) { + item->Remove(); + delete item; + NumPartsInCart--; + return true; + } + return false; +} + +ShoppingCartItem *CarCustomizeManager::IsPartTypeInCart(SelectablePart *to_find) { + if (!to_find) return nullptr; + ShoppingCartItem *end = reinterpret_cast(&ShoppingCart); + ShoppingCartItem *item = GetFirstCartItem(); + while (true) { + if (item == end) { + break; + } + SelectablePart *buying = item->ToBuy; + if (to_find->PerformancePkg) { + if (buying->PhysicsType == to_find->PhysicsType) { + return item; + } + } else { + if (buying->CarSlotID == to_find->CarSlotID) { + return item; + } + } + item = static_cast(item->Next); + } + return nullptr; +} + +ShoppingCartItem *CarCustomizeManager::IsPartTypeInCart(unsigned int slot_id) { + SelectablePart temp(nullptr, slot_id, 0, static_cast(static_cast(Physics::Upgrades::kType_Count)), false, CPS_AVAILABLE, 0, false); + return IsPartTypeInCart(&temp); +} + +ShoppingCartItem *CarCustomizeManager::IsPartTypeInCart(Physics::Upgrades::Type type) { + SelectablePart test_part(nullptr, 0, 0, static_cast(static_cast(type)), true, CPS_AVAILABLE, 0, false); + return IsPartTypeInCart(&test_part); +} + +ShoppingCartItem *CarCustomizeManager::IsPartInCart(SelectablePart *to_find) { + ShoppingCartItem *end = reinterpret_cast(&ShoppingCart); + ShoppingCartItem *item = GetFirstCartItem(); + while (true) { + if (item == end) { + break; + } + SelectablePart *buyPart = item->ToBuy; + if (to_find->PerformancePkg) { + if (buyPart->PhysicsType == to_find->PhysicsType && + buyPart->UpgradeLevel == to_find->UpgradeLevel) { + return item; + } + } else { + if (buyPart->ThePart == to_find->ThePart && + buyPart->CarSlotID == to_find->CarSlotID) { + return item; + } + } + item = static_cast(item->Next); + } + return nullptr; +} + +CarPart *CarCustomizeManager::GetActivePartFromSlot(unsigned int slot_id) { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(slot_id); + if (item) { + return item->ToBuy->ThePart; + } + return gCarCustomizeManager.GetInstalledCarPart(slot_id); +} + +int CarCustomizeManager::GetCartTotal(eCustomizeCartTotals type) { + int total = 0; + for (ShoppingCartItem *item = GetFirstCartItem(); item != reinterpret_cast(&ShoppingCart); item = item->GetNext()) { + if (!item->IsActive()) continue; + if (type == 0 || type == 2) { + SelectablePart *buy = item->GetBuyingPart(); + if (!buy->IsPerformancePkg()) { + int slot = buy->GetSlotID(); + if (slot == 0x72) continue; + if ((slot >= 0x4f && slot <= 0x52) || (slot >= 0x85 && slot <= 0x87)) continue; + } + if (!CustomizeIsInBackRoom()) { + total += buy->GetPrice(); + } else { + total += 1; + } + } + if (type == 1 || (type == 2 && !CustomizeIsInBackRoom())) { + int trade_val = 0; + if (item->GetTradeInPart()) { + trade_val = static_cast(static_cast(item->GetTradeInPart()->GetPrice()) * gTradeInFactor); + } + total -= trade_val; + } + } + return total; +} + +void CarCustomizeManager::Checkout() { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECareerRecord *career = stable->GetCareerRecordByHandle(TuningCar->CareerHandle); + for (ShoppingCartItem *item = GetFirstCartItem(); item != reinterpret_cast(&ShoppingCart); item = item->GetNext()) { + if (!item->IsActive()) continue; + SelectablePart *buy = item->GetBuyingPart(); + if (IsCareerMode() && buy->GetSlotID() != 0x72) { + if (!CustomizeIsInBackRoom()) { + int trade_val = 0; + if (item->GetTradeInPart()) { + trade_val = static_cast(static_cast(item->GetTradeInPart()->GetPrice()) * gTradeInFactor); + } + int cost = buy->GetPrice() - trade_val; + if (cost < 0) { + FEDatabase->GetCareerSettings()->AddCash(-cost); + } else { + FEDatabase->GetCareerSettings()->SpendCash(cost); + } + } else { + if (!buy->IsPerformancePkg()) { + TheFEMarkerManager.UtilizeMarker(buy->GetSlotID()); + } else { + TheFEMarkerManager.UtilizeMarker(static_cast(static_cast(buy->GetPhysicsType()))); + } + } + } + if (!buy->IsPerformancePkg()) { + InstallPart(buy->GetSlotID(), buy->GetPart()); + UpdateHeatOnVehicle(buy, career); + } else { + InstallPerfPkg(static_cast(static_cast(buy->GetPhysicsType())), buy->GetUpgradeLevel()); + } + } + EmptyCart(); +} + +bool CarCustomizeManager::DoesCartHaveActiveParts() { + ShoppingCartItem *end = reinterpret_cast(&ShoppingCart); + for (ShoppingCartItem *item = GetFirstCartItem(); item != end; item = item->GetNext()) { + SelectablePart *buy = item->GetBuyingPart(); + if (buy && !buy->IsPerformancePkg()) { + int slot = buy->GetSlotID(); + if (slot > 0x4e) { + if (slot <= 0x52) continue; + if (slot <= 0x87) { + if (slot > 0x84) continue; + } + } + } + if (item->IsActive()) return true; + } + return false; +} + +int CarCustomizeManager::GetPartPrice(SelectablePart *part) { + int price = 0; + if (part && !CustomizeIsInBackRoom()) { + if (part->IsPerformancePkg()) { + price = GetMaxPackages(static_cast(static_cast(part->GetPhysicsType()))); + price -= GetNumPackages(static_cast(static_cast(part->GetPhysicsType()))) - part->GetUpgradeLevel(); + eUnlockFilters filter = GetUnlockFilter(); + price = UnlockSystem::GetPerfPackageCost(filter, static_cast(static_cast(part->GetPhysicsType())), price, 0); + } else { + if (part->GetSlotID() < 0x4f || part->GetSlotID() > 0x52) { + if (part->GetSlotID() > 0x87 || part->GetSlotID() < 0x85) { + eUnlockFilters filter = GetUnlockFilter(); + price = UnlockSystem::GetCarPartCost(filter, part->GetSlotID(), part->GetPart(), 0); + } + } + } + } + return price; +} + +void CarCustomizeManager::SetTempColoredPart(SelectablePart *part) { + if (TheTempColoredPart) { + delete TheTempColoredPart; + } + TheTempColoredPart = part; +} + +void CarCustomizeManager::ClearTempColoredPart() { + if (TheTempColoredPart) { + delete TheTempColoredPart; + } + TheTempColoredPart = nullptr; +} + +CarPart *CarCustomizeManager::GetStockCarPart(unsigned int slot_id) { + RideInfo ride; + FEDatabase->GetPlayerCarStable(0)->BuildRideForPlayer(TuningCar->Handle, 0, &ride); + ride.SetStockParts(); + return ride.GetPart(slot_id); +} + +void CarCustomizeManager::ResetToStockCarParts() { + RideInfo ride; + FEDatabase->GetPlayerCarStable(0)->BuildRideForPlayer(TuningCar->Handle, 0, &ride); + ride.SetStockParts(); + PreviewRecord.WriteRideIntoRecord(&ride); + CarViewer::SetRideInfo(&ride, SET_RIDE_INFO_REASON_LOAD_CAR, eCARVIEWER_PLAYER1_CAR); +} + +void CarCustomizeManager::ResetPreview() { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECustomizationRecord *src = stable->GetCustomizationRecordByHandle(TuningCar->Customization); + PreviewRecord = *src; + RideInfo ride; + stable->BuildRideForPlayer(TuningCar->Handle, 0, &ride); + PreviewRecord.WriteRecordIntoRide(&ride); + CarViewer::SetRideInfo(&ride, SET_RIDE_INFO_REASON_LOAD_CAR, eCARVIEWER_PLAYER1_CAR); + ShoppingCartItem *end = reinterpret_cast(&ShoppingCart); + for (ShoppingCartItem *item = GetFirstCartItem(); item != end; item = item->GetNext()) { + SelectablePart *buy = item->GetBuyingPart(); + if (buy->IsPerformancePkg()) { + PreviewPerfPkg(static_cast(static_cast(buy->GetPhysicsType())), buy->GetUpgradeLevel()); + } else { + PreviewPart(buy->GetSlotID(), buy->GetPart()); + } + } +} + +void CarCustomizeManager::PreviewPart(int slot_id, CarPart *part) { + PreviewRecord.SetInstalledPart(slot_id, part); + RideInfo ride; + FEDatabase->GetPlayerCarStable(0)->BuildRideForPlayer(TuningCar->Handle, 0, &ride); + PreviewRecord.WriteRecordIntoRide(&ride); + CarViewer::SetRideInfo(&ride, SET_RIDE_INFO_REASON_LOAD_CAR, eCARVIEWER_PLAYER1_CAR); +} + +void CarCustomizeManager::InstallPart(int slot_id, CarPart *part) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECustomizationRecord *record = stable->GetCustomizationRecordByHandle(TuningCar->Customization); + record->SetInstalledPart(slot_id, part); + PreviewPart(slot_id, part); +} + +CarPart *CarCustomizeManager::GetInstalledCarPart(int slot_id) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECustomizationRecord *record = stable->GetCustomizationRecordByHandle(TuningCar->Customization); + return record->GetInstalledPart(TuningCar->GetType(), slot_id); +} + +void CarCustomizeManager::PreviewPerfPkg(Physics::Upgrades::Type part_type, int level) { + if (level == 7) { + PreviewRecord.InstalledPhysics.Junkman |= (1 << part_type); + PreviewRecord.WriteRecordIntoPhysics(ThePVehicle); + } else { + PreviewRecord.InstalledPhysics.Part[part_type] = level; + PreviewRecord.WriteRecordIntoPhysics(ThePVehicle); + } +} + +void CarCustomizeManager::InstallPerfPkg(Physics::Upgrades::Type part_type, int level) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECustomizationRecord *record = stable->GetCustomizationRecordByHandle(TuningCar->Customization); + if (level == 7) { + unsigned int mask = 1 << part_type; + record->InstalledPhysics.Junkman |= mask; + if (!record->WriteRecordIntoPhysics(ThePVehicle)) { + record->InstalledPhysics.Junkman &= ~mask; + record->WriteRecordIntoPhysics(ThePVehicle); + } + } else { + record->InstalledPhysics.Part[part_type] = level; + if (!record->WriteRecordIntoPhysics(ThePVehicle)) { + record->InstalledPhysics.Part[part_type] = 0; + record->WriteRecordIntoPhysics(ThePVehicle); + } + } + PreviewPerfPkg(part_type, level); +} + +bool CarCustomizeManager::IsJunkmanInstalled(Physics::Upgrades::Type type) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECustomizationRecord *record = stable->GetCustomizationRecordByHandle(TuningCar->Customization); + return (record->InstalledPhysics.Junkman & (1 << type)) != 0; +} + +eUnlockFilters CarCustomizeManager::GetUnlockFilter() { + if (!FEDatabase->IsCareerMode()) { + if ((FEDatabase->GetGameMode() & 0x28) == 0x28) { + return static_cast(4); + } + return static_cast(1); + } + if (CustomizeIsInBackRoom()) { + return static_cast(10); + } + return static_cast(2); +} + +bool CarCustomizeManager::IsTurbo() { + Attrib::Gen::pvehicle pveh(TuningCar->VehicleKey, 0, nullptr); + Physics::Upgrades::SetLevel(pveh, static_cast(5), 1); + return Physics::Info::InductionType(pveh) == 1; +} + +float CarCustomizeManager::GetActualHeat() { + if (!TuningCar) return 0.0f; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECareerRecord *career = stable->GetCareerRecordByHandle(TuningCar->CareerHandle); + if (!career) return 0.0f; + return career->GetVehicleHeat(); +} + +float CarCustomizeManager::GetPerformanceRating(ePerformanceRatingType type, bool preview) { + Physics::Info::Performance perf; + if (!preview) { + Attrib::Gen::pvehicle pveh(TuningCar->VehicleKey, 0, nullptr); + FEDatabase->GetPlayerCarStable(0)->WriteRecordIntoPhysics(TuningCar->Handle, pveh); + Physics::Info::EstimatePerformance(pveh, perf); + } else { + Physics::Info::EstimatePerformance(ThePVehicle, perf); + } + switch (type) { + case 0: return perf.TopSpeed; + case 1: return perf.Handling; + case 2: return perf.Acceleration; + default: return perf.Handling; + } +} + +extern eUnlockableEntity MapCarPartToUnlockable(int slot_id, CarPart *part); +extern eUnlockableEntity MapPerfPkgToUnlockable(Physics::Upgrades::Type type); +extern unsigned int FEngHashString(const char *fmt, ...); +extern bool DoesStringExist(unsigned int hash); +extern int FEngSNPrintf(char *buf, int size, const char *fmt, ...); +extern void bMemSet(void *dst, int value, unsigned int size); + +struct CarPartDatabase { + CarType GetCarType(unsigned int model_hash); + CarPart *GetCarPartByIndex(int index); + int GetPartIndex(CarPart *part); + CarPart *NewGetCarPart(CarType cartype, int slot, unsigned int part_name_hash, CarPart *fallback, int index); + CarPart *NewGetFirstCarPart(CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upg_level); + CarPart *NewGetNextCarPart(CarPart *car_part, CarType car_type, int car_slot_id, unsigned int car_part_namehash, int upg_level); +}; +extern CarPartDatabase CarPartDB; + +namespace Physics { +namespace Upgrades { +extern float GetHeat(Attrib::Gen::pvehicle pvehicle, Type type, int level); +} +} + +bool CarCustomizeManager::IsPartInstalled(SelectablePart *part) { + if (part) { + if (part->IsPerformancePkg()) { + if (part->IsJunkmanPart()) { + return IsJunkmanInstalled(static_cast(static_cast(part->GetPhysicsType()))); + } + int lvl = GetInstalledPerfPkg(static_cast(static_cast(part->GetPhysicsType()))); + if (static_cast(part->GetUpgradeLevel()) == lvl) { + return true; + } + } else { + CarPart *installed = GetInstalledCarPart(part->GetSlotID()); + if (installed == part->GetPart()) { + return true; + } + } + } + return false; +} + +bool CarCustomizeManager::IsPartLocked(SelectablePart *part, int perf_unlock_level) { + bool unlocked; + if (part->IsPerformancePkg()) { + eUnlockFilters filter = GetUnlockFilter(); + int physType = static_cast(part->GetPhysicsType()); + bool backroom = CustomizeIsInBackRoom(); + unlocked = UnlockSystem::IsPerfPackageUnlocked(filter, static_cast(physType), perf_unlock_level, 0, backroom); + goto done; + } + { + int slot = part->GetSlotID(); + switch (slot) { + case 0x53: + case 0x5b: { + eUnlockFilters filter = GetUnlockFilter(); + bool backroom = CustomizeIsInBackRoom(); + unlocked = UnlockSystem::IsUnlockableUnlocked(filter, static_cast(0x2c), 1, 0, backroom); + break; + } + case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: + case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: case 0x70: { + eUnlockFilters filter = GetUnlockFilter(); + bool backroom = CustomizeIsInBackRoom(); + unlocked = UnlockSystem::IsUnlockableUnlocked(filter, static_cast(0x2e), 2, 0, backroom); + break; + } + case 0x73: + case 0x7b: { + eUnlockFilters filter = GetUnlockFilter(); + bool backroom = CustomizeIsInBackRoom(); + unlocked = UnlockSystem::IsUnlockableUnlocked(filter, static_cast(0x30), 3, 0, backroom); + break; + } + default: { + eUnlockFilters filter = GetUnlockFilter(); + CarPart *p = part->GetPart(); + int sid = part->GetSlotID(); + bool backroom = CustomizeIsInBackRoom(); + unlocked = UnlockSystem::IsCarPartUnlocked(filter, sid, p, 0, backroom); + break; + } + } + } +done: + return !unlocked; +} + +bool CarCustomizeManager::IsPartNew(SelectablePart *part, int perf_unlock_level) { + eUnlockableEntity ent; + eUnlockFilters filter; + if (!part->IsPerformancePkg()) { + ent = MapCarPartToUnlockable(part->GetSlotID(), part->GetPart()); + filter = GetUnlockFilter(); + perf_unlock_level = static_cast(part->GetUpgradeLevel()); + } else { + ent = MapPerfPkgToUnlockable(static_cast(static_cast(part->GetPhysicsType()))); + filter = GetUnlockFilter(); + } + return UnlockSystem::IsUnlockableNew(filter, ent, perf_unlock_level); +} + +bool CarCustomizeManager::IsCategoryNew(unsigned int cat) { + eUnlockableEntity titty; + + switch (cat) { + case 0x801: { + for (unsigned int i = 0x101; i <= 0x105; i++) { + if (IsCategoryNew(i)) return true; + } + return false; + } + case 0x802: { + for (unsigned int i = 0x201; i <= 0x207; i++) { + if (IsCategoryNew(i)) return true; + } + return false; + } + case 0x803: { + for (unsigned int i = 0x301; i <= 0x307; i++) { + if (IsCategoryNew(i)) return true; + } + return false; + } + case 0x101: + titty = static_cast(0xb); + break; + case 0x102: + titty = static_cast(0xc); + break; + case 0x103: { + for (unsigned int i = 0x702; i <= 0x70b; i++) { + if (IsCategoryNew(i)) return true; + } + return false; + } + case 0x104: + titty = static_cast(0xe); + break; + case 0x105: + titty = static_cast(0xf); + break; + case 0x307: + titty = static_cast(0x11); + break; + case 0x201: + titty = static_cast(8); + break; + case 0x202: + titty = static_cast(7); + break; + case 0x203: + titty = static_cast(6); + break; + case 0x204: + titty = static_cast(10); + break; + case 0x205: + titty = static_cast(4); + break; + case 0x206: + titty = static_cast(5); + break; + case 0x207: + titty = static_cast(9); + break; + case 0x301: + titty = static_cast(0x17); + break; + case 0x302: { + for (unsigned int i = 0x402; i <= 0x409; i++) { + if (IsCategoryNew(i)) return true; + } + return false; + } + case 0x303: + titty = static_cast(0x18); + break; + case 0x304: + titty = static_cast(0x12); + break; + case 0x305: + if (IsCategoryNew(0x501)) return true; + if (IsCategoryNew(0x505)) return true; + if (IsCategoryNew(0x503)) return true; + return false; + case 0x306: + titty = static_cast(0x2b); + break; + case 0x402: + titty = static_cast(0x23); + break; + case 0x403: + titty = static_cast(0x24); + break; + case 0x404: + titty = static_cast(0x25); + break; + case 0x405: + titty = static_cast(0x26); + break; + case 0x406: + titty = static_cast(0x27); + break; + case 0x407: + titty = static_cast(0x28); + break; + case 0x408: + titty = static_cast(0x29); + break; + case 0x409: + titty = static_cast(0x2a); + break; + case 0x702: + titty = static_cast(0x19); + break; + case 0x703: + titty = static_cast(0x1a); + break; + case 0x704: + titty = static_cast(0x1b); + break; + case 0x705: + titty = static_cast(0x1c); + break; + case 0x706: + titty = static_cast(0x1d); + break; + case 0x707: + titty = static_cast(0x1e); + break; + case 0x708: + titty = static_cast(0x1f); + break; + case 0x709: + titty = static_cast(0x20); + break; + case 0x70a: + titty = static_cast(0x21); + break; + case 0x70b: + titty = static_cast(0x22); + break; + case 0x501: + case 0x502: + titty = static_cast(0x2c); + break; + case 0x503: + case 0x504: + case 0x601: + case 0x602: + case 0x603: + case 0x604: + case 0x605: + case 0x606: + titty = static_cast(0x2e); + break; + case 0x505: + case 0x506: + titty = static_cast(0x30); + break; + default: + return true; + } + + eUnlockFilters filter = GetUnlockFilter(); + return UnlockSystem::IsUnlockableNew(filter, titty, -2); +} + +bool CarCustomizeManager::IsCategoryLocked(unsigned int cat, bool backroom) { + eUnlockableEntity titty; + int level = 0; + + switch (cat) { + case 0x801: { + for (unsigned int i = 0x101; i <= 0x105; i++) { + if (!IsCategoryLocked(i, backroom)) return false; + } + return true; + } + case 0x802: { + for (unsigned int i = 0x201; i <= 0x207; i++) { + if (!IsCategoryLocked(i, backroom)) return false; + } + return true; + } + case 0x803: { + for (unsigned int i = 0x301; i <= 0x307; i++) { + if (!IsCategoryLocked(i, backroom)) return false; + } + return true; + } + case 0x101: + titty = static_cast(0xb); + break; + case 0x102: + titty = static_cast(0xc); + break; + case 0x103: { + for (unsigned int i = 0x702; i <= 0x70b; i++) { + if (!IsRimCategoryLocked(i, backroom)) return false; + } + return true; + } + case 0x104: + titty = static_cast(0xe); + break; + case 0x105: + titty = static_cast(0xf); + break; + case 0x307: + titty = static_cast(0x11); + break; + case 0x201: + if (backroom && !CanInstallJunkman(static_cast(4))) return true; + titty = static_cast(8); + break; + case 0x202: + if (backroom && !CanInstallJunkman(static_cast(3))) return true; + titty = static_cast(7); + break; + case 0x203: + if (backroom && !CanInstallJunkman(static_cast(2))) return true; + titty = static_cast(6); + break; + case 0x204: + if (backroom && !CanInstallJunkman(static_cast(6))) return true; + titty = static_cast(10); + break; + case 0x205: + if (backroom && !CanInstallJunkman(static_cast(0))) return true; + titty = static_cast(4); + break; + case 0x206: + if (backroom && !CanInstallJunkman(static_cast(1))) return true; + titty = static_cast(5); + break; + case 0x207: + if (backroom && !CanInstallJunkman(static_cast(5))) return true; + titty = static_cast(9); + break; + case 0x301: + titty = static_cast(0x17); + break; + case 0x302: { + for (unsigned int i = 0x402; i <= 0x409; i++) { + if (!IsVinylCategoryLocked(i, backroom)) return false; + } + return true; + } + case 0x303: + titty = static_cast(0x18); + break; + case 0x304: + titty = static_cast(0x12); + break; + case 0x305: + if (!IsCategoryLocked(0x501, backroom)) return false; + if (!IsCategoryLocked(0x505, backroom)) return false; + if (!IsCategoryLocked(0x503, backroom)) return false; + return true; + case 0x306: + titty = static_cast(0x2b); + break; + case 0x402: + case 0x403: + case 0x404: + case 0x405: + case 0x406: + case 0x407: + case 0x408: + case 0x409: + return IsVinylCategoryLocked(cat, backroom); + case 0x702: + case 0x703: + case 0x704: + case 0x705: + case 0x706: + case 0x707: + case 0x708: + case 0x709: + case 0x70a: + case 0x70b: + return IsRimCategoryLocked(cat, backroom); + case 0x501: + case 0x502: + level = 1; + titty = static_cast(0x2c); + break; + case 0x503: + case 0x504: + case 0x601: + case 0x602: + case 0x603: + case 0x604: + case 0x605: + case 0x606: + level = 2; + titty = static_cast(0x2e); + break; + case 0x505: + case 0x506: + level = 3; + titty = static_cast(0x30); + break; + default: + return true; + } + + eUnlockFilters filter = GetUnlockFilter(); + if (backroom) { + return !UnlockSystem::IsBackroomAvailable(filter, titty, level); + } else { + return !UnlockSystem::IsUnlockableUnlocked(filter, titty, level, 0, false); + } +} + +bool CarCustomizeManager::IsRimCategoryLocked(unsigned int cat, bool backroom) { + unsigned int brand_name = 0; + switch (cat) { + case 0x702: brand_name = 0x352d08d1; break; + case 0x703: brand_name = 0x9136; break; + case 0x704: brand_name = 0x9536; break; + case 0x705: brand_name = 0x2b77feb; break; + case 0x706: brand_name = 0x324ac97; break; + case 0x707: brand_name = 0x48e25793; break; + case 0x708: brand_name = 0xdd544a02; break; + case 0x709: brand_name = 0x648; break; + case 0x70a: brand_name = 0x1e6a3b; break; + case 0x70b: brand_name = 0x1c386b; break; + } + bTList list; + GetCarPartList(0x42, list, brand_name); + bool locked = true; + SelectablePart *part = list.GetHead(); + while (part != list.EndOfList()) { + if (part->GetPart()->GetBrandNameHash() == brand_name && !IsPartLocked(part, 0)) { + locked = false; + break; + } + part = static_cast(part->GetNext()); + } + list.DeleteAllElements(); + if (backroom && !locked) { + locked = true; + if (TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_RIMS, 0) > 0) { + locked = false; + } + } + return locked; +} + +bool CarCustomizeManager::IsVinylCategoryLocked(unsigned int cat, bool backroom) { + unsigned int group = 0; + switch (cat) { + case 0x402: break; + case 0x403: group = 1; break; + case 0x404: group = 2; break; + case 0x405: group = 3; break; + case 0x406: group = 4; break; + case 0x407: group = 5; break; + case 0x408: group = 6; break; + case 0x409: group = 7; break; + } + bTList list; + GetCarPartList(0x4d, list, group); + bool locked = true; + SelectablePart *part = list.GetHead(); + while (part != list.EndOfList()) { + if (part->GetPart()->GetGroupNumber() == group && !IsPartLocked(part, 0)) { + locked = false; + break; + } + part = static_cast(part->GetNext()); + } + list.DeleteAllElements(); + if (backroom && !locked) { + locked = true; + if (TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_VINYL, 0) > 0) { + locked = false; + } + } + return locked; +} + +void CarCustomizeManager::UpdateHeatOnVehicle(SelectablePart *part, FECareerRecord *record) { + if (!part) return; + if (!record) return; + if (part->IsPerformancePkg()) return; + if (!IsCareerMode()) return; + + register float heat_factor = 1.0f; + if (CustomizeIsInBackRoom()) { + heat_factor = 0.75f; + } + + int slot = part->GetSlotID(); + if (slot != 0x53) { + if (slot < 0x54) { + if (slot == 0x3f) goto call_hood; + if (slot > 0x3f) { + if (slot == 0x4c) goto call_paint; + if (slot > 0x4c) { + if (slot == 0x4d) goto call_vinyl; + if (slot != 0x4e) return; + goto call_rimpaint; + } + if (slot != 0x42) return; + goto call_rim; + } + if (slot == 0x2c) goto call_spoiler; + if (slot > 0x2c) { + if (slot != 0x3e) return; + goto call_roofscoop; + } + if (slot != 0x17) return; + goto call_bodykit; + } + if (slot < 0x71) { + if (slot >= 0x6b) goto call_decal; + if (slot > 0x68) { + if (slot != 0x69) return; + goto call_bodykit; + } + if (slot >= 0x63) goto call_decal; + if (slot == 0x5b) goto call_decal; + return; + } + if (slot == 0x7b) goto call_decal; + if (slot > 0x7b) { + if (slot != 0x83) return; + goto call_windowtint; + } + if (slot != 0x73) return; + } + goto call_decal; +call_spoiler: record->AdjustHeatOnSpoilerApplied(heat_factor); return; +call_hood: record->AdjustHeatOnHoodApplied(heat_factor); return; +call_roofscoop: record->AdjustHeatOnRoofScoopApplied(heat_factor); return; +call_rim: record->AdjustHeatOnRimApplied(heat_factor); return; +call_windowtint: record->AdjustHeatOnWindowTintApplied(heat_factor); return; +call_paint: record->AdjustHeatOnPaintApplied(heat_factor); return; +call_rimpaint: record->AdjustHeatOnRimPaintApplied(heat_factor); return; +call_vinyl: record->AdjustHeatOnVinylApplied(heat_factor); return; +call_bodykit: record->AdjustHeatOnBodyKitApplied(heat_factor); return; +call_decal: record->AdjustHeatOnDecalApplied(heat_factor); return; +} + +unsigned int CarCustomizeManager::GetUnlockHash(eCustomizeCategory cat, int upgrade_lvl) { + const char *name = nullptr; + switch (cat) { + case 0x101: name = "PARTS_BODYKITS"; break; + case 0x102: name = "PARTS_SPOILERS"; break; + case 0x104: name = "PARTS_HOODS"; break; + case 0x105: name = "PARTS_ROOFSCOOPS"; break; + case 0x201: name = "PERF_ENGINE"; break; + case 0x202: name = "PERF_TRANSMISSION"; break; + case 0x203: name = "PERF_SUSPENSION"; break; + case 0x204: name = "PERF_NITROUS"; break; + case 0x205: name = "PERF_TIRES"; break; + case 0x206: name = "PERF_BRAKES"; break; + case 0x207: + if (IsTurbo()) name = "PERF_TURBO"; + else name = "PERF_SUPERCHARGER"; + break; + case 0x301: name = "VISUAL_PAINT"; break; + case 0x303: name = "VISUAL_RIMPAINT"; break; + case 0x304: name = "VISUAL_WINDOWTINT"; break; + case 0x306: name = "VISUAL_NUMBERS"; break; + case 0x307: name = "VISUAL_HUDS"; break; + case 0x402: case 0x403: case 0x404: case 0x405: + case 0x406: case 0x407: case 0x408: case 0x409: + name = "VISUAL_VINYLS"; break; + case 0x501: case 0x502: case 0x503: case 0x504: + case 0x505: case 0x506: + case 0x601: case 0x602: case 0x603: case 0x604: + case 0x605: case 0x606: + name = "VISUAL_DECALS"; break; + case 0x701: case 0x702: case 0x703: case 0x704: + case 0x705: case 0x706: case 0x707: case 0x708: + case 0x709: case 0x70a: case 0x70b: + name = "PARTS_RIMS"; break; + } + if (name && upgrade_lvl) { + char buf[100]; + FEngSNPrintf(buf, 100, "CUSTOMIZATION_%s_%d", name, upgrade_lvl); + unsigned int hash = FEngHashString(buf); + if (DoesStringExist(hash) == 1) { + return hash; + } + } + return 0x9bb9ccc3; +} + +void CarCustomizeManager::GetCarPartList(int car_slot, bTList &the_list, unsigned int param) { + CarType cartype = static_cast(-1); + if (gCarCustomizeManager.TuningCar) { + cartype = gCarCustomizeManager.TuningCar->GetType(); + } + CarPart *part = CarPartDB.NewGetFirstCarPart(cartype, car_slot, 0, -1); + eUnlockableEntity unlockable = MapCarPartToUnlockable(car_slot, nullptr); + while (part) { + int next_slot = car_slot; + if (car_slot == 0x17) { + bool valid = false; + unsigned int modelHash = part->GetModelNameHash(0, 1); + if (modelHash && StreamingSolidPackLoader.GetStreamingEntry(modelHash)) { + valid = true; + } + if (!valid) goto next_part; + } else if (car_slot == 0x42) { + if (param != 0) { + if (part->GetAppliedAttributeUParam(0xebb03e66, 0) != param) { + goto next_part; + } + } + } else if (car_slot == 0x4d) { + unsigned int vinylHash = GetVinylLayerHash(part, cartype, 1); + eStreamingEntry *streaming = StreamingTexturePackLoader.GetStreamingEntry(vinylHash); + unsigned int brand = part->GetAppliedAttributeUParam(0xebb03e66, 0); + unsigned int specialHash = bStringHash("SPECIAL"); + bool isSpecial = brand == specialHash; + if (!streaming) { + goto next_part; + } + if ((part->GetGroupNumber() & 0x1f) != param) { + goto next_part; + } + if (isSpecial) { + if (!GetIsCollectorsEdition()) { + goto next_part; + } + } + } + + { + SelectablePart *sp; + if (unlockable == 0x2e || unlockable == 0x2c || unlockable == 0x30) { + int level = 0; + if (unlockable == 0x2e) { + level = 2; + } else if (unlockable == 0x30) { + level = 3; + } else { + level = 1; + } + if (CustomizeIsInBackRoom() && + !UnlockSystem::IsUnlockableUnlocked(UNLOCK_CAREER_MODE, unlockable, level, 0, true)) { + goto next_part; + } + sp = new SelectablePart(part, car_slot, static_cast(level), static_cast(7), false, CPS_AVAILABLE, 0, false); + } else { + if (CustomizeIsInBackRoom()) { + if (!UnlockSystem::IsCarPartUnlocked(UNLOCK_CAREER_MODE, car_slot, part, 0, true)) { + goto next_part; + } + } else if (!FEDatabase->GetCareerSettings()->HasBeatenCareer() && + static_cast(part->GroupNumber_UpgradeLevel >> 5) == 7u) { + goto next_part; + } + sp = new SelectablePart(part, car_slot, + static_cast(part->GroupNumber_UpgradeLevel >> 5), + static_cast(7), false, CPS_AVAILABLE, 0, false); + } + + { + unsigned int state = CPS_AVAILABLE; + if (IsPartLocked(sp, 0)) { + state = CPS_LOCKED; + } else if (IsPartNew(sp, 0)) { + state = CPS_NEW; + } + if (IsPartInstalled(sp)) { + state = state | CPS_INSTALLED; + } else if (IsPartInCart(sp)) { + state = state | CPS_IN_CART; + } + sp->PartState = static_cast(state); + sp->Price = GetPartPrice(sp); + the_list.AddTail(sp); + } + } + next_part: + part = CarPartDB.NewGetNextCarPart(part, cartype, next_slot, 0, -1); + } +} + +void CarCustomizeManager::GetPerformancePartsList(Physics::Upgrades::Type type, bTList &the_list) { + int max_level = GetMaxPackages(type); + if (max_level > 0) { + int i = 0; + do { + int level = i + 1; + SelectablePart *sp = new SelectablePart(nullptr, 0, static_cast(level), static_cast(static_cast(type)), true, CPS_AVAILABLE, 0, false); + eCustomizePartState state = CPS_AVAILABLE; + int remaining = GetMaxPackages(type) - GetNumPackages(type); + int perf_unlock_level = remaining + i + 1; + if (IsPartLocked(sp, perf_unlock_level)) { + state = CPS_LOCKED; + } else if (IsPartNew(sp, perf_unlock_level)) { + state = CPS_NEW; + } + if (IsPartInstalled(sp)) { + state = static_cast(state | CPS_INSTALLED); + } else if (IsPartInCart(sp)) { + state = static_cast(state | CPS_IN_CART); + } + sp->SetPartState(state); + int price = GetPartPrice(sp); + sp->SetPrice(price); + the_list.AddTail(sp); + i = level; + } while (i < max_level); + } +} + +float CarCustomizeManager::GetPreviewHeat(SelectablePart *part) { + if (!DoesCartHaveActiveParts() || !IsCareerMode()) { + return GetActualHeat(); + } + FECareerRecord temp_record; + FECareerRecord *career_record = FEDatabase->GetPlayerCarStable(0)->GetCareerRecordByHandle(TuningCar->CareerHandle); + if (!career_record) return 0.0f; + float heat = career_record->GetVehicleHeat(); + temp_record.SetVehicleHeat(heat); + if (part && part->GetPart() != GetInstalledCarPart(part->GetSlotID())) { + UpdateHeatOnVehicle(part, &temp_record); + } + ShoppingCartItem *item = ShoppingCart.GetHead(); + while (item != ShoppingCart.EndOfList()) { + if (!part) { + if (item->IsActive()) { + UpdateHeatOnVehicle(item->GetBuyingPart(), &temp_record); + } + } else if (part->GetSlotID() != item->GetBuyingPart()->GetSlotID() && item->IsActive()) { + UpdateHeatOnVehicle(item->GetBuyingPart(), &temp_record); + } + item = static_cast(item->GetNext()); + } + return temp_record.GetVehicleHeat(); +} + +float CarCustomizeManager::GetCartHeat() { + if (DoesCartHaveActiveParts()) { + if (IsCareerMode()) { + FECareerRecord temp_record; + FECareerRecord *career_record = FEDatabase->GetPlayerCarStable(0)->GetCareerRecordByHandle(TuningCar->CareerHandle); + if (!career_record) return 0.0f; + float heat = career_record->GetVehicleHeat(); + temp_record.SetVehicleHeat(heat); + ShoppingCartItem *item = ShoppingCart.GetHead(); + while (item != ShoppingCart.EndOfList()) { + if (item->IsActive()) { + UpdateHeatOnVehicle(item->GetBuyingPart(), &temp_record); + } + item = static_cast(item->GetNext()); + } + return temp_record.GetVehicleHeat(); + } + } + return GetActualHeat(); +} + +void CarCustomizeManager::MaxOutPerformance() { + for (int i = 0; i < 7; i++) { + Physics::Upgrades::Type type = static_cast(i); + int num_packages = GetNumPackages(type); + int best_level = 0; + + for (int j = 0; j < num_packages; j++) { + SelectablePart sp(nullptr, 0, static_cast(j + 1), static_cast(static_cast(type)), true, CPS_AVAILABLE, 0, false); + int level_param = GetMaxPackages(type) - GetNumPackages(type) + j + 1; + if (!IsPartLocked(&sp, level_param)) { + best_level = j + 1; + } + } + + if (best_level != 0) { + ShoppingCartItem *existing = IsPartTypeInCart(type); + if (existing) { + RemoveFromCart(existing); + } + + SelectablePart *sp = new SelectablePart(nullptr, 0, static_cast(best_level), static_cast(static_cast(type)), true, CPS_AVAILABLE, 0, false); + + eCustomizePartState status = CPS_AVAILABLE; + int level_param = GetMaxPackages(type) - GetNumPackages(type) + best_level + 1; + if (IsPartLocked(sp, level_param)) { + status = CPS_LOCKED; + } else if (IsPartNew(sp, level_param)) { + status = CPS_NEW; + } + + if (IsPartInstalled(sp)) { + status = static_cast(status | CPS_INSTALLED); + } else if (IsPartInCart(sp)) { + status = static_cast(status | CPS_IN_CART); + } + + sp->SetPartState(static_cast(status)); + sp->SetPrice(gCarCustomizeManager.GetPartPrice(sp)); + + AddToCart(sp); + delete sp; + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp index ec8315718..b17f9fc4e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp @@ -1,10 +1,118 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_CUSTOMIZEMANAGER_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_CUSTOMIZEMANAGER_H - #ifdef EA_PRAGMA_ONCE_SUPPORTED #pragma once #endif +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include +#include "Speed/Indep/Src/Physics/PhysicsUpgrades.hpp" + +struct FECarRecord; +struct CarPart; + +bool CustomizeIsInBackRoom(); +void CustomizeSetInBackRoom(bool b); +bool CustomizeIsInPerformance(); +void CustomizeSetInPerformance(bool b); +bool CustomizeIsInParts(); +void CustomizeSetInParts(bool b); + +struct CarCustomizeManager { + ShoppingCartItem *GetFirstCartItem() { return static_cast(ShoppingCart.GetHead()); } + ShoppingCartItem *GetLastCartItem() { return static_cast(ShoppingCart.GetTail()); } + int GetNumCartItems() { return NumPartsInCart; } + ShoppingCartItem *GetCartItem(int index); + void EmptyCart(); + SelectablePart *GetTempColoredPart() { return TheTempColoredPart; } + void SetInBackRoom(bool in_back) { CustomizeSetInBackRoom(in_back); } + bool IsInBackRoom() { return CustomizeIsInBackRoom(); } + void SetInPerformance(bool b) { CustomizeSetInPerformance(b); } + bool IsInPerformance() { return CustomizeIsInPerformance(); } + void SetInParts(bool b) { CustomizeSetInParts(b); } + bool IsInParts() { return CustomizeIsInParts(); } + const FECustomizationRecord *GetPreviewRecord() { return &PreviewRecord; } + const FECarRecord *GetTuningCar() { return TuningCar; } + + void TakeControl(eCustomizeEntryPoint entry_point, FECarRecord *tuning_car); + void RelinquishControl(); + bool CanTradeIn(SelectablePart *part); + void AddToCart(SelectablePart *part); + bool RemoveFromCart(ShoppingCartItem *item); + void AddRemovalCarPart(unsigned int slot_id); + ShoppingCartItem *IsPartTypeInCart(SelectablePart *to_find); + ShoppingCartItem *IsPartTypeInCart(unsigned int slot_id); + ShoppingCartItem *IsPartTypeInCart(Physics::Upgrades::Type type); + ShoppingCartItem *IsPartInCart(SelectablePart *to_find); + CarPart *GetActivePartFromSlot(unsigned int slot_id); + int GetCartTotal(eCustomizeCartTotals type); + void Checkout(); + bool DoesCartHaveActiveParts(); + int GetPartPrice(SelectablePart *part); + void SetTempColoredPart(SelectablePart *part); + void ClearTempColoredPart(); + CarPart *GetStockCarPart(unsigned int slot_id); + void ResetToStockCarParts(); + void ResetToStockPerformance(); + void PreviewPerformanceRating(ePerformanceRatingType rating, float *base, float *preview); + void UpdatePerformanceRating(ePerformanceRatingType rating, float *base, float *preview); + bool IsVinylColorSupported(int slot); + void ClearVinylColors(); + float GetHeatFromParts(); + CarPart *GetInstalledCarPart(int slot_id); + void PreviewPart(int slot_id, CarPart *part); + void InstallPart(int slot_id, CarPart *part); + void ResetPreview(); + void PreviewPerfPkg(Physics::Upgrades::Type part_type, int level); + void InstallPerfPkg(Physics::Upgrades::Type part_type, int level); + bool IsJunkmanInstalled(Physics::Upgrades::Type type); + int GetInstalledPerfPkg(Physics::Upgrades::Type type); + int GetMaxPackages(Physics::Upgrades::Type type); + int GetNumPackages(Physics::Upgrades::Type type); + void MaxOutPerformance(); + unsigned int GetUnlockHash(eCustomizeCategory cat, int upgrade_lvl); + float GetPerformanceRating(ePerformanceRatingType type, bool preview); + void UpdateHeatOnVehicle(SelectablePart *part, FECareerRecord *record); + eUnlockFilters GetUnlockFilter(); + bool IsPartInstalled(SelectablePart *part); + bool IsPartLocked(SelectablePart *part, int perf_unlock_level); + bool IsPartNew(SelectablePart *part, int perf_unlock_level); + bool IsCategoryNew(unsigned int cat); + bool IsCategoryLocked(unsigned int cat, bool backroom); + bool IsRimCategoryLocked(unsigned int cat, bool backroom); + bool IsVinylCategoryLocked(unsigned int cat, bool backroom); + int GetOuterRadius(); + int GetMinInnerRadius(); + int GetMaxInnerRadius(); + void GetCarPartList(int car_slot, bTList &the_list, unsigned int param); + void GetPerformancePartsList(Physics::Upgrades::Type type, bTList &the_list); + bool CanInstallJunkman(Physics::Upgrades::Type type); + bool IsCareerMode(); + bool IsTurbo(); + float GetActualHeat(); + float GetPreviewHeat(SelectablePart *part); + int GetNumCustomizeMarkers(); + bool IsCastrolCar(); + bool IsRotaryCar(); + bool IsHeroCar(); + float GetCartHeat(); + + CarCustomizeManager() + : ThePVehicle(static_cast(nullptr), 0, nullptr) // + , PreviewRecord() {} + eCustomizeEntryPoint EntryPoint; // offset 0x0, size 0x4 + FECarRecord *TuningCar; // offset 0x4, size 0x4 + Attrib::Gen::pvehicle ThePVehicle; // offset 0x8, size 0x14 + FECustomizationRecord PreviewRecord; // offset 0x1C, size 0x198 + bTList ShoppingCart; // offset 0x1B4, size 0x8 + int NumPartsInCart; // offset 0x1BC, size 0x4 + SelectablePart *TheTempColoredPart; // offset 0x1C0, size 0x4 +}; +extern CarCustomizeManager gCarCustomizeManager; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp new file mode 100644 index 000000000..75ad6cbec --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp @@ -0,0 +1,369 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite +#ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_CUSTOMIZETYPES_H +#define FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_CUSTOMIZETYPES_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +extern cFEng *cFEng_mInstance; +#include "Speed/Indep/Src/Gameplay/GRace.h" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include + +struct FEImage; +struct FEObject; + +struct CarPart; + +enum eCustomizePartState { + CPS_AVAILABLE = 1, + CPS_LOCKED = 2, + CPS_NEW = 3, + CPS_INSTALLED = 16, + CPS_IN_CART = 32, + CPS_GAME_STATE_MASK = 15, + CPS_PLAYER_STATE_MASK = 240, +}; + +enum eCustomizeCategory { + CC_TO_CAT_MASK = 65535, + CC_FROM_CAT_MASK = -65536, + CC_SCREEN_ID_MAIN = 2048, + CC_SCREEN_ID_PARTS = 256, + CC_SCREEN_ID_PERFORMANCE = 512, + CC_SCREEN_ID_VISUAL = 768, + CC_SCREEN_ID_VINYL_TYPES = 1024, + CC_SCREEN_ID_DECAL_LOCATION = 1280, + CC_SCREEN_ID_DECAL_POSITION = 1536, + CC_SCREEN_ID_RIM_BRANDS = 1792, + CC_ID_MASK = 65280, + CC_NO_CATEGORY = 0, + CC_PARTS = 2049, + CC_PERFORMANCE = 2050, + CC_VISUAL = 2051, + CC_BODY_KIT = 257, + CC_SPOILERS = 258, + CC_RIM_BRANDS = 259, + CC_HOODS = 260, + CC_ROOF_SCOOPS = 261, + CC_ENGINE = 513, + CC_TRANSMISSION = 514, + CC_SUSPENSION = 515, + CC_NITROUS = 516, + CC_TIRES = 517, + CC_BRAKES = 518, + CC_FORCED_INDUCTION = 519, + CC_PAINT = 769, + CC_VINYL_TYPES = 770, + CC_RIM_PAINT = 771, + CC_WINDOW_TINT = 772, + CC_DECAL_LOCATION = 773, + CC_NUMBERS = 774, + CC_CUSTOM_HUD = 775, + CC_VINYL_GROUP_STOCK = 1025, + CC_VINYL_GROUP_FLAME = 1026, + CC_VINYL_GROUP_TRIBAL = 1027, + CC_VINYL_GROUP_STRIPE = 1028, + CC_VINYL_GROUP_RACING_FLAG = 1029, + CC_VINYL_GROUP_NATIONAL_FLAG = 1030, + CC_VINYL_GROUP_BODY = 1031, + CC_VINYL_GROUP_UNIQUE = 1032, + CC_VINYL_GROUP_CONTEST = 1033, + CC_RIM_BRAND_STOCK = 1793, + CC_RIM_BRAND_5_ZIGEN = 1794, + CC_RIM_BRAND_ADR = 1795, + CC_RIM_BRAND_BBS = 1796, + CC_RIM_BRAND_ENKEI = 1797, + CC_RIM_BRAND_KONIG = 1798, + CC_RIM_BRAND_LOWENHART = 1799, + CC_RIM_BRAND_RACING_HART = 1800, + CC_RIM_BRAND_OZ = 1801, + CC_RIM_BRAND_VOLK = 1802, + CC_RIM_BRAND_ROJA = 1803, + CC_DECAL_WINDSHIELD = 1281, + CC_DECAL_REAR_WINDOW = 1282, + CC_DECAL_LEFT_DOOR = 1283, + CC_DECAL_RIGHT_DOOR = 1284, + CC_DECAL_LEFT_QP = 1285, + CC_DECAL_RIGHT_QP = 1286, + CC_DECAL_SLOT_1 = 1537, + CC_DECAL_SLOT_2 = 1538, + CC_DECAL_SLOT_3 = 1539, + CC_DECAL_SLOT_4 = 1540, + CC_DECAL_SLOT_5 = 1541, + CC_DECAL_SLOT_6 = 1542, +}; + +enum eCustomizeEntryPoint { + CEP_GAMEPLAY = 0, + CEP_MAIN_MENU = 1, + CEP_ONLINE_MENU = 2, +}; + +enum eCustomizeCartTotals { + CCT_PART_PRICES = 0, + CCT_TRADE_IN = 1, + CCT_TOTAL = 2, +}; + +enum ePerformanceRatingType { + PRT_TOP_SPEED = 0, + PRT_HANDLING = 1, + PRT_ACCELERATION = 2, +}; + +enum eUnlockFilters { + UNLOCK_QUICK_RACE = 1, + UNLOCK_CAREER_MODE = 2, + UNLOCK_ONLINE = 4, + UNLOCK_BACKROOM = 10, +}; + +// total size: 0x50 +struct CustomizeMeter { + CustomizeMeter(); + + virtual ~CustomizeMeter() {} + + void Init(const char *pkg_name, const char *name, float min, float max, float current, float preview); + void SetCurrent(float current); + void SetPreview(float preview); + void Draw(); + void SetVisibility(bool b); + + float Min; // offset 0x0, size 0x4 + float Max; // offset 0x4, size 0x4 + float Current; // offset 0x8, size 0x4 + float Preview; // offset 0xC, size 0x4 + float PreviousPreview; // offset 0x10, size 0x4 + int NumStages; // offset 0x14, size 0x4 + FEImage *pMultiplier; // offset 0x18, size 0x4 + FEImage *pMultiplierZoom; // offset 0x1C, size 0x4 + FEImage *pBases[10]; // offset 0x20, size 0x28 + FEObject *pMeterGroup; // offset 0x48, size 0x4 + // vtable at 0x4C +}; + +// total size: 0x2C +struct SelectablePart : public bTNode { + static void *operator new(size_t s) { return ::operator new[](s); } + static void operator delete(void *p) { ::operator delete[](p); } + + SelectablePart(SelectablePart *part) + : ThePart(part->ThePart) // + , CarSlotID(part->CarSlotID) // + , UpgradeLevel(part->UpgradeLevel) // + , PhysicsType(part->PhysicsType) // + , PerformancePkg(part->PerformancePkg) // + , PartState(part->PartState) // + , Price(part->Price) // + , JunkmanPart(part->JunkmanPart) {} + + SelectablePart(CarPart *part, int slot_id, unsigned int lvl, GRace::Type phys_type, bool is_perf, eCustomizePartState state, int price, bool junkman) + : ThePart(part) // + , CarSlotID(slot_id) // + , UpgradeLevel(lvl) // + , PhysicsType(phys_type) // + , PerformancePkg(is_perf) // + , PartState(state) // + , Price(price) // + , JunkmanPart(junkman) {} + + virtual ~SelectablePart() {} + + CarPart *GetPart() { return ThePart; } + int GetSlotID() { return CarSlotID; } + unsigned int GetUpgradeLevel() { return UpgradeLevel; } + GRace::Type GetPhysicsType() { return PhysicsType; } + int IsPerformancePkg() { return PerformancePkg; } + eCustomizePartState GetPartState() { return PartState; } + int GetPrice() { return Price; } + int IsJunkmanPart() { return JunkmanPart; } + + void SetSlotID(unsigned int id) { CarSlotID = static_cast(id); } + + bool IsAvailable() { return (PartState & CPS_GAME_STATE_MASK) == CPS_AVAILABLE; } + bool IsLocked() { return (PartState & CPS_GAME_STATE_MASK) == CPS_LOCKED; } + bool IsNew() { return (PartState & CPS_GAME_STATE_MASK) == CPS_NEW; } + bool IsInstalled() { return (PartState & CPS_PLAYER_STATE_MASK) == CPS_INSTALLED; } + bool IsInCart() { return (PartState & CPS_PLAYER_STATE_MASK) == CPS_IN_CART; } + bool IsInstalledX() { return (PartState & CPS_INSTALLED) != 0; } + bool IsInCartX() { return (PartState & CPS_IN_CART) != 0; } + + void SetPartState(unsigned int state) { PartState = static_cast(state); } + void SetInCart() { PartState = static_cast((PartState & CPS_GAME_STATE_MASK) | CPS_IN_CART); } + void SetInCartPreserve() { PartState = static_cast(PartState | CPS_IN_CART); } + void SetInstalled() { PartState = static_cast((PartState & CPS_GAME_STATE_MASK) | CPS_INSTALLED); } + void UnSetInCart() { PartState = static_cast(PartState & CPS_GAME_STATE_MASK); } + void UnSetInCartPreserve() { PartState = static_cast(PartState & ~CPS_IN_CART); } + void SetPrice(int price) { Price = price; } + + CarPart *ThePart; // offset 0x8, size 0x4 + int CarSlotID; // offset 0xC, size 0x4 + unsigned int UpgradeLevel; // offset 0x10, size 0x4 + GRace::Type PhysicsType; // offset 0x14, size 0x4 + int PerformancePkg; // offset 0x18, size 0x4 + eCustomizePartState PartState; // offset 0x1C, size 0x4 + int Price; // offset 0x20, size 0x4 + int JunkmanPart; // offset 0x24, size 0x4 + // vtable at 0x28 +}; + +// total size: 0x18 +struct ShoppingCartItem : public bTNode { + static void *operator new(size_t s) { return ::operator new[](s); } + static void operator delete(void *p) { ::operator delete[](p); } + + ShoppingCartItem(SelectablePart *to_buy, SelectablePart *trade_in) + : ToBuy(to_buy) // + , TradeIn(trade_in) // + , bActive(true) {} + + virtual ~ShoppingCartItem() { + delete ToBuy; + delete TradeIn; + } + + SelectablePart *GetBuyingPart() { return ToBuy; } + SelectablePart *GetTradeInPart() { return TradeIn; } + int GetPartPrice() { return ToBuy->GetPrice(); } + int GetTradeInPrice() { return TradeIn ? TradeIn->GetPrice() : 0; } + void ToggleActive() { bActive = !bActive; } + bool IsActive() { return bActive; } + + SelectablePart *ToBuy; // offset 0x8, size 0x4 + SelectablePart *TradeIn; // offset 0xC, size 0x4 + bool bActive; // offset 0x10, size 0x1 + // vtable at 0x14 +}; + +// total size: 0x64 +struct CustomizePartOption : public IconOption { + CustomizePartOption(SelectablePart *part, unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash, unsigned int unlock_blurb) + : IconOption(tex_hash, name_hash, desc_hash) // + , ThePart(part) // + , UnlockBlurb(unlock_blurb) {} + + ~CustomizePartOption() override { + delete ThePart; + } + + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override {} + + void SetPart(SelectablePart *part) { ThePart = part; } + SelectablePart *GetPart() { return ThePart; } + unsigned int GetUnlockBlurb() { return UnlockBlurb; } + + SelectablePart *ThePart; // offset 0x5C, size 0x4 + unsigned int UnlockBlurb; // offset 0x60, size 0x4 +}; + +// total size: 0x68 +struct CustomizeMainOption : public IconOption { + CustomizeMainOption(const char *to_pkg, unsigned int tex_hash, unsigned int name_hash, unsigned int to_cat, unsigned int from_cat) + : IconOption(tex_hash, name_hash, 0) // + , ToPkg(to_pkg) { + UnlockStatus = CPS_AVAILABLE; + Category = to_cat | (from_cat << 16); + } + + ~CustomizeMainOption() override {} + + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override { + if (data == 0xc407210) { + cFEng_mInstance->QueuePackageSwitch(ToPkg, Category, 0, false); + } + } + + virtual bool IsStockOption() { return false; } + + const char *ToPkg; // offset 0x5C, size 0x4 + unsigned int Category; // offset 0x60, size 0x4 + eCustomizePartState UnlockStatus; // offset 0x64, size 0x4 +}; + +// total size: 0x6C +struct SetStockPartOption : public CustomizeMainOption { + SetStockPartOption(SelectablePart *part, unsigned int icon, unsigned int to_cat) + : CustomizeMainOption("", icon, 0x60a662f5, to_cat, to_cat) // + , ThePart(part) { + SetReactImmediately(true); + } + + ~SetStockPartOption() override { + delete ThePart; + } + + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override; + bool IsStockOption() override { return true; } + + SelectablePart *ThePart; // offset 0x68, size 0x4 +}; + +// total size: 0x74 +struct HUDLayerOption : public CustomizePartOption { + HUDLayerOption(unsigned int layer, unsigned int icon_hash, unsigned int name_hash); + ~HUDLayerOption() override {} + + void React(const char *parent_pkg, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override {} + + unsigned int GetLayer() { return HUDLayer; } + + unsigned int HUDLayer; // offset 0x64, size 0x4 + bTList TheColors; // offset 0x68, size 0x8 + SelectablePart *SelectedPart; // offset 0x70, size 0x4 +}; + +// total size: 0x64 +struct HUDColorOption : public IconOption { + HUDColorOption(SelectablePart *part) + : IconOption(0, 0, 0) // + , ThePart(part) {} + + ~HUDColorOption() override {} + + void React(const char *parent_pkg, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override {} + + SelectablePart *ThePart; // offset 0x5C, size 0x4 + unsigned int color; // offset 0x60, size 0x4 +}; + +// total size: 0x64 +struct CustomizationScreenHelper { + CustomizationScreenHelper(const char *pkg_name); + + virtual ~CustomizationScreenHelper() {} + + const char *GetPackageName() { return pPackageName; } + void SetTitleHash(unsigned int hash) { TitleHash = hash; } + unsigned int GetTitleHash() { return TitleHash; } + void SetInitComplete(bool b) { bInitComplete = b; } + bool IsInitComplete() { return bInitComplete; } + void PlayLocked() {} + void PlayInCart() {} + void PlayInstalled() {} + void DrawMeters() { HeatMeter.Draw(); } + void SetHeatValue(float f) { HeatMeter.SetCurrent(f); } + void SetHeatPreview(float f) { HeatMeter.SetPreview(f); } + + void DrawTitle(); + void SetCareerStatusIcon(eCustomizePartState state); + void SetPlayerCarStatusIcon(eCustomizePartState state); + void SetCashVisibility(bool visible); + void SetUnlockOverlayState(bool show, unsigned int blurb_hash); + void SetCareerStuff(SelectablePart *part, unsigned int cat, unsigned int tradeInValue); + void SetPartStatus(SelectablePart *part, unsigned int unlock_blurb, int part_num, int max_parts); + void FlashStatusIcon(eCustomizePartState state, bool play_sound); + + const char *pPackageName; // offset 0x0, size 0x4 + unsigned int TitleHash; // offset 0x4, size 0x4 + bool bUnlockOverlayShowing; // offset 0x8, size 0x1 + bool bInitComplete; // offset 0xC, size 0x1 + CustomizeMeter HeatMeter; // offset 0x10, size 0x50 + // vtable at 0x60 +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.cpp index e69de29bb..5b4c5b312 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.cpp @@ -0,0 +1,331 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp" +#include "Speed/Indep/Src/World/CarInfo.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" + +// CarPart is already defined in CustomizeManager.cpp (earlier in jumbo build) + +extern cFrontendDatabase *FEDatabase; +extern CarCustomizeManager gCarCustomizeManager; +extern int gLookupCarSlotID; +extern const char *GetCarSlotNameFromID(int id); +extern CarPart *GetCarPartFromSlot(int slot_id); +extern const char *GetCarPartNameFromID(int id); +extern unsigned int bStringHash(const char *text); + +int SortCarsByName(DebugCar *before, DebugCar *after) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + const char *before_name = stable->GetCarRecordByHandle(before->mHandle)->GetDebugName(); + const char *after_name = stable->GetCarRecordByHandle(after->mHandle)->GetDebugName(); + return bStrCmp(before_name, after_name) <= 0; +} + +DebugCarCustomizeScreen::DebugCarOption::DebugCarOption(const char *name, int value) + : Intval(value) { + bStrNCpy(String, name, 0x40); +} + +DebugCarCustomizeScreen::DebugCarCustomizeScreen(ScreenConstructorData *sd) + : MenuScreen(sd) // + , iFastScroll(1) { + FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + for (int i = 0; i < 200; i++) { + FECarRecord *car = stable->GetCarByIndex(i); + if (car->Handle != 0xFFFFFFFF) { + DebugCar *dc = new DebugCar(car->Handle); + FilteredCarsList.AddTail(dc); + } + } + FilteredCarsList.Sort(SortCarsByName); + custom = stable->CreateNewCustomizationRecord(); + pDebugCar = static_cast(FilteredCarsList.GetHead()); + LoadCurrentCar(); + BuildOptionsLists(); + RebuildPartsList(); + Redraw(); +} + +DebugCarCustomizeScreen::~DebugCarCustomizeScreen() { + custom->Handle = 0xFF; +} + +DebugCarCustomizeScreen::DebugCarOption *DebugCarCustomizeScreen::FindElement(bTList &list, int id) { + for (DebugCarOption *node = list.GetHead(); node != reinterpret_cast(&list); node = node->GetNext()) { + if (node->GetValue() == id) { + return node; + } + } + return nullptr; +} + +void DebugCarCustomizeScreen::BuildOptionsLists() { + DebugCarOption *opt = new DebugCarOption("CARTYPENAME_ANY", 0); + CarTypeNameHashes.AddTail(opt); + for (int i = 0; i < 0x54; i++) { + CarTypeInfo *info = &CarTypeInfoArray[i]; + if (info) { + DebugCarOption *node = new DebugCarOption(reinterpret_cast(info), info->CarTypeNameHash); + CarTypeNameHashes.AddTail(node); + } + } + CurrentCarTypeNameHash = CarTypeNameHashes.GetHead(); + + for (int i = 0; i < 0x8b; i++) { + DebugCarOption *node = new DebugCarOption(GetCarSlotNameFromID(i), i); + LookupCarSlotIDs.AddTail(node); + } + CurrentLookupSlotID = FindElement(LookupCarSlotIDs, gLookupCarSlotID); + + DebugCarOption *partOpt = new DebugCarOption("CARPARTNAME_ANY", 0); + CurrentPartNameHash = CarPartNameHashes.GetHead(); + CarPartNameHashes.AddTail(partOpt); +} + +void DebugCarCustomizeScreen::LoadCurrentCar() { + if (pDebugCar) { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(pDebugCar->mHandle); + if (car->Customization == 0xFF) { + wasCarCustomized = false; + car->Customization = custom->Handle; + RideInfo ride; + ride.Init(static_cast(-1), static_cast(0), 0, 0); + ride.Init(car->GetType(), static_cast(0), 0, 0); + ride.SetRandomPaint(); + ride.SetStockParts(); + custom->WriteRideIntoRecord(&ride); + } else { + wasCarCustomized = true; + } + gCarCustomizeManager.RelinquishControl(); + gCarCustomizeManager.TakeControl(static_cast(1), car); + } +} + +void DebugCarCustomizeScreen::RebuildPartsList() { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(pDebugCar->mHandle); + if (car->Customization != 0xFF) { + InstallableParts.DeleteAllElements(); + int slotId = CurrentLookupSlotID->GetValue(); + GetCarPartFromSlot(slotId); + bStringHash("CARPARTNAME_ANY"); + CarType type = car->GetType(); + for (CarPart *part = CarPartDB.NewGetFirstCarPart(type, slotId, 0, -1); + part; + part = CarPartDB.NewGetNextCarPart(part, type, slotId, 0, -1)) { + InstallableParts.AddTail(part); + } + CurrentInstallablePart = InstallableParts.GetHead(); + bPNode *node = InstallableParts.GetHead(); + while (node != reinterpret_cast(&InstallableParts)) { + CarPart *cp = static_cast(node->GetpObject()); + if (cp == gCarCustomizeManager.GetInstalledCarPart(slotId)) { + CurrentInstallablePart = node; + break; + } + node = node->GetNext(); + } + gCarCustomizeManager.ResetPreview(); + NewPreviewPart(); + } +} + +void DebugCarCustomizeScreen::NewPreviewPart() { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(pDebugCar->mHandle); + if (car->Customization != 0xFF && !InstallableParts.IsEmpty()) { + CarPart *part = static_cast(CurrentInstallablePart->GetpObject()); + part->GetCarTypeNameHash(); + GetCarTypeInfoFromHash(0); + gCarCustomizeManager.PreviewPart(CurrentLookupSlotID->GetValue(), part); + } +} + +void DebugCarCustomizeScreen::InstallPreviewingPart() { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(pDebugCar->mHandle); + if (car->Customization != 0xFF && !InstallableParts.IsEmpty()) { + CarPart *part = static_cast(CurrentInstallablePart->GetpObject()); + part->GetCarTypeNameHash(); + GetCarTypeInfoFromHash(0); + gCarCustomizeManager.InstallPart(CurrentLookupSlotID->GetValue(), part); + } +} + +void DebugCarCustomizeScreen::DumpPresetRide() { + FECarRecord *car = const_cast(gCarCustomizeManager.GetTuningCar()); + RideInfo ride; + ride.Init(car->GetType(), static_cast(0), 0, 0); + gCarCustomizeManager.GetPreviewRecord()->WriteRecordIntoRide(&ride); + ride.DumpForPreset(const_cast(car)); +} + +void DebugCarCustomizeScreen::Redraw() { + FEPrintf(GetPackageName(), 0x36db742, "CarName"); + FEPrintf(GetPackageName(), 0x36db743, "LookupSlotID"); + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(pDebugCar->mHandle); + if (car) { + FEPrintf(GetPackageName(), 0x3e40712, car->GetDebugName()); + } else { + FEPrintf(GetPackageName(), 0x3e40712, "NULL"); + } + FEPrintf(GetPackageName(), 0x3e40713, CurrentLookupSlotID->GetString()); + if (CurrentInstallablePart == reinterpret_cast(&InstallableParts) || !car) { + goto dash_section; + } + { + bool hasCustom = car->Customization != 0xFF; + if (!hasCustom) goto dash_section; + } + { + CarPart *part = static_cast(CurrentInstallablePart->GetpObject()); + unsigned int typeHash = part->GetCarTypeNameHash(); + CarTypeInfo *typeInfo = GetCarTypeInfoFromHash(typeHash); + FEPrintf(GetPackageName(), 0xd6d32016, "%s", reinterpret_cast(typeInfo)); + const char *partName = GetCarPartNameFromID(part->GetPartID()); + FEPrintf(GetPackageName(), 0xeffe7224, "%s", partName); + const char *name = part->GetName(); + FEPrintf(GetPackageName(), 0xb1027477, "%s", name); + FEPrintf(GetPackageName(), 0x6a81554, "0x%x", part->GetPartNameHash()); + int idx = InstallableParts.TraversebList(CurrentInstallablePart); + int total = InstallableParts.CountElements(); + FEPrintf(GetPackageName(), 0x36db746, "Part Info (%d/%d)", idx, total); + goto end; + } +dash_section: + FEPrintf(GetPackageName(), 0xd6d32016, "----"); + FEPrintf(GetPackageName(), 0xeffe7224, "----"); + FEPrintf(GetPackageName(), 0xb1027477, "----"); + FEPrintf(GetPackageName(), 0x6a81554, "----"); + FEPrintf(GetPackageName(), 0x36db746, "Part Info (NONE)"); +end:; +} + +void DebugCarCustomizeScreen::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0xc519bfc2: + iFastScroll = 10; + return; + case 0xe086d2e6: + iFastScroll = 1; + return; + case 0xc519bfbf: + if (InstallableParts.IsEmpty()) return; + gCarCustomizeManager.ResetToStockCarParts(); + NewPreviewPart(); + return; + case 0xc519bfc0: + DumpPresetRide(); + return; + case 0x9120409e: { + unsigned int hash = pobj->NameHash; + switch (hash) { + case 0x36db742: { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(pDebugCar->mHandle); + if (!wasCarCustomized) { + car->Customization = 0xFF; + } + for (int i = 0; i < iFastScroll; i++) { + DebugCar *prev = pDebugCar->GetPrev(); + if (prev == reinterpret_cast(&FilteredCarsList)) { + prev = FilteredCarsList.GetTail(); + } + pDebugCar = prev; + } + CurrentCarTypeNameHash = CurrentCarTypeNameHash->GetPrev(); + if (CurrentCarTypeNameHash == reinterpret_cast(&CarTypeNameHashes)) { + CurrentCarTypeNameHash = CarTypeNameHashes.GetTail(); + } + LoadCurrentCar(); + break; + } + case 0x36db743: + for (int i = 0; i < iFastScroll; i++) { + DebugCarOption *prev = CurrentLookupSlotID->GetPrev(); + if (prev == reinterpret_cast(&LookupCarSlotIDs)) { + prev = LookupCarSlotIDs.GetTail(); + } + CurrentLookupSlotID = prev; + } + break; + case 0x36db746: + if (InstallableParts.IsEmpty()) goto done; + for (int i = 0; i < iFastScroll; i++) { + bPNode *prev = CurrentInstallablePart->GetPrev(); + if (prev == reinterpret_cast(&InstallableParts)) { + prev = InstallableParts.GetTail(); + } + CurrentInstallablePart = prev; + } + NewPreviewPart(); + goto done; + default: + goto done; + } + RebuildPartsList(); + break; + } + case 0xb5971bf1: { + unsigned int hash = pobj->NameHash; + switch (hash) { + case 0x36db742: { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(pDebugCar->mHandle); + if (!wasCarCustomized) { + car->Customization = 0xFF; + } + for (int i = 0; i < iFastScroll; i++) { + DebugCar *next = pDebugCar->GetNext(); + if (next == reinterpret_cast(&FilteredCarsList)) { + next = FilteredCarsList.GetHead(); + } + pDebugCar = next; + } + CurrentCarTypeNameHash = CurrentCarTypeNameHash->GetNext(); + if (CurrentCarTypeNameHash == reinterpret_cast(&CarTypeNameHashes)) { + CurrentCarTypeNameHash = CarTypeNameHashes.GetHead(); + } + LoadCurrentCar(); + break; + } + case 0x36db743: + for (int i = 0; i < iFastScroll; i++) { + DebugCarOption *next = CurrentLookupSlotID->GetNext(); + if (next == reinterpret_cast(&LookupCarSlotIDs)) { + next = LookupCarSlotIDs.GetHead(); + } + CurrentLookupSlotID = next; + } + break; + case 0x36db746: + if (InstallableParts.IsEmpty()) goto done; + for (int i = 0; i < iFastScroll; i++) { + bPNode *next = CurrentInstallablePart->GetNext(); + if (next == reinterpret_cast(&InstallableParts)) { + next = InstallableParts.GetHead(); + } + CurrentInstallablePart = next; + } + NewPreviewPart(); + goto done; + default: + goto done; + } + RebuildPartsList(); + break; + } + case 0x406415e3: + InstallPreviewingPart(); + return; + case 0x911ab364: + gCarCustomizeManager.RelinquishControl(); + cFEng::Get()->QueuePackageSwitch("MainMenu.fng", 0, 0, false); + return; + default: + return; + } +done: + Redraw(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.hpp index 1114bdc58..37db0f39c 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/DebugCarCustomize.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_DEBUGCARCUSTOMIZE_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_DEBUGCARCUSTOMIZE_H @@ -5,6 +6,66 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include + +struct CarPart; +struct FECustomizationRecord; + +// total size: 0xC +struct DebugCar : public bTNode { + DebugCar(unsigned int handle) + : mHandle(handle) {} + ~DebugCar() {} + + unsigned int mHandle; // offset 0x8, size 0x4 +}; + +// total size: 0x80 +struct DebugCarCustomizeScreen : public MenuScreen { + // total size: 0x4C + struct DebugCarOption : public bTNode { + DebugCarOption(const char *name, int value); + + int GetValue() { return Intval; } + char *GetString() { return String; } + ~DebugCarOption() {} + + char String[64]; // offset 0x8, size 0x40 + int Intval; // offset 0x48, size 0x4 + }; + + DebugCarCustomizeScreen(ScreenConstructorData *sd); + ~DebugCarCustomizeScreen() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + DebugCarOption *FindElement(bTList &list, int id); + void BuildOptionsLists(); + void LoadCurrentCar(); + void RebuildPartsList(); + void NewPreviewPart(); + void InstallPreviewingPart(); + void DumpPresetRide(); + void Redraw(); + + bTList FilteredCarsList; // offset 0x2C, size 0x8 + DebugCar *pDebugCar; // offset 0x34, size 0x4 + bTList CarTypeNameHashes; // offset 0x38, size 0x8 + DebugCarOption *CurrentCarTypeNameHash; // offset 0x40, size 0x4 + bTList LookupCarSlotIDs; // offset 0x44, size 0x8 + DebugCarOption *CurrentLookupSlotID; // offset 0x4C, size 0x4 + bTList CarPartNameHashes; // offset 0x50, size 0x8 + DebugCarOption *CurrentPartNameHash; // offset 0x58, size 0x4 + bTList InstallCarPartIDs; // offset 0x5C, size 0x8 + DebugCarOption *CurrentInstallPartID; // offset 0x64, size 0x4 + bPList InstallableParts; // offset 0x68, size 0x8 + bPNode *CurrentInstallablePart; // offset 0x70, size 0x4 + FECustomizationRecord *custom; // offset 0x74, size 0x4 + bool wasCarCustomized; // offset 0x78, size 0x1 + int iFastScroll; // offset 0x7C, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/FECustomize.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/FECustomize.cpp index e69de29bb..5d5c017bf 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/FECustomize.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/FECustomize.cpp @@ -0,0 +1,4724 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/FECustomize.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp" + +struct FECarRecord; + +static bool gInBackRoom; +static bool gInPerformance; +static bool gInParts; + +bool CustomizeIsInBackRoom() { return gInBackRoom; } +void CustomizeSetInBackRoom(bool b) { gInBackRoom = b; } +bool CustomizeIsInPerformance() { return gInPerformance; } +void CustomizeSetInPerformance(bool b) { gInPerformance = b; } +bool CustomizeIsInParts() { return gInParts; } +void CustomizeSetInParts(bool b) { gInParts = b; } + +extern int g_TheCustomizeEntryPoint; +extern FECarRecord *g_pCustomizeCarRecordToUse; + +void BeginCarCustomize(eCustomizeEntryPoint entry_point, FECarRecord *theCustomCar) { + CustomizeSetInBackRoom(false); + CustomizeSetInPerformance(false); + CustomizeSetInParts(false); + if (entry_point) { + cFEng::Get()->QueuePackageSwitch("CustomizeMain.fng", 0, 0, false); + } + g_TheCustomizeEntryPoint = entry_point; + g_pCustomizeCarRecordToUse = theCustomCar; +} + +#ifdef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE____CUSTOMIZE_CARCUSTOMIZE_H +// The rest of this file only compiles when CarCustomize.hpp has been +// included earlier in the jumbo build (zFeOverlay). In zFe2 the +// header is absent, so only the static getters/setters above survive. + +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeManager.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/feimage.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Physics/PhysicsUpgrades.hpp" + +namespace Attrib { namespace Gen { struct pvehicle; } } +namespace Physics { namespace Info { float Redline(const Attrib::Gen::pvehicle &pvehicle); } } + +#include + +struct EAXSound; + +extern void FEngSetVisible(FEObject *obj); +extern void FEngSetInvisible(FEObject *obj); +extern FEObject *FEngFindObject(const char *pkg, unsigned int hash); +extern FEImage *FEngFindImage(const char *pkg, int hash); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +extern void FEngSetScript(const char *pkg, unsigned int hash, unsigned int script, bool b); +extern void FEngSetScript(FEObject *obj, unsigned int script, bool b); +extern void FEngSetLanguageHash(const char *pkg, unsigned int obj_hash, unsigned int lang_hash); +extern void FEngSetCurrentButton(const char *pkg, unsigned int hash); +inline void FEngSetCurrentButton(const char *pkg_name, FEObject *obj) { + FEngSetCurrentButton(pkg_name, obj->NameHash); +} +extern void FEngSetTopLeft(FEObject *obj, float x, float y); +extern void FEngGetTopLeft(FEObject *obj, float &x, float &y); +extern void FEngGetSize(FEObject *obj, float &x, float &y); + +inline float FEngGetTopLeftX(FEObject *obj) { + float x, y; + FEngGetTopLeft(obj, x, y); + return x; +} + +inline float FEngGetTopLeftY(FEObject *obj) { + float x, y; + FEngGetTopLeft(obj, x, y); + return y; +} +extern void FEngSetBottomRight(FEObject *obj, float x, float y); +extern void FEngGetBottomRight(FEObject *obj, float &x, float &y); +extern bool CustomizeIsInPerformance(); +extern bool CustomizeIsInParts(); +extern void CustomizeSetInParts(bool b); +extern void CustomizeSetInPerformance(bool b); +extern int GetCurrentLanguage(); +extern const char *GetLocalizedString(unsigned int hash); +extern void GetLocalizedString(char *buf, int size, unsigned int hash); +extern int FEPrintf(const char *pkg, int hash, const char *fmt, ...); +extern int FEPrintf(FEString *text, const char *fmt, ...); +extern int bSNPrintf(char *buf, int size, const char *fmt, ...); +extern int bSPrintf(char *buf, const char *fmt, ...); +extern int bStrLen(const char *s); + +extern CarCustomizeManager gCarCustomizeManager; +extern cFrontendDatabase *FEDatabase; +extern cFEng *cFEng_mInstance; + +extern const char *g_pCustomizeMainPkg; +const char *g_pCustomizeSubPkg = nullptr; +const char *g_pCustomizeSubTopPkg = nullptr; +extern const char *g_pCustomizePartsPkg; +extern const char *g_pCustomizePerfPkg; +extern const char *g_pCustomizeDecalsPkg; +extern const char *g_pCustomizePaintPkg; +extern const char *g_pCustomizeRimsPkg; +extern const char *g_pCustomizeHudPkg; +extern const char *g_pCustomizeSpoilerPkg; + +extern EAXSound *g_pEAXSound; +extern void PlayUISoundFX(EAXSound *snd, int trigger); + +extern Timer RealTimer; +extern float gTradeInFactor; + + +extern int eLoadStreamingTexturePack(const char *name, void (*callback)(void *), void *param, int priority); +extern void eUnloadStreamingTexturePack(const char *name); +extern void eUnloadStreamingTexture(int *handles, int count); + +extern int CustomizeHUDTexPackResources[11]; +extern int CustomizeHUDTexTextureResources[55]; + +extern int g_TheCustomizeEntryPoint; +struct FECarRecord; +extern FECarRecord *g_pCustomizeCarRecordToUse; +extern int CarViewer_haveLoadedOnce; + +extern void MarkUnlockableThingSeen(int idx, unsigned int filter); +extern unsigned long FEHashUpper(const char *str); +extern unsigned int bStringHash(const char *str); +extern unsigned int bStringHash(const char *str, int prefix_hash); + +extern unsigned int GetNumMarkersFromCategory(eCustomizeCategory cat); +extern unsigned int GetMarkerNameFromCategory(eCustomizeCategory cat); + +extern void FEngSetColor(FEObject *obj, unsigned int color); +extern unsigned int FEngHashString(const char *fmt, ...); +extern bool DoesStringExist(unsigned int hash); +struct TextureInfo; +extern TextureInfo *GetTextureInfo(unsigned int handle, int p2, int p3); +extern void FEngSetLanguageHash(FEString *str, unsigned int hash); +extern void eLoadStreamingTexture(unsigned int *textures, int count, void (*callback)(void *), void *param, int priority); + +extern const char *g_pCustomizeShowcasePkg; +extern const char g_pCustomizeDlgPkg[]; +extern void StartCareerFreeRoam(); +extern char FEngMapJoyParamToJoyport(unsigned long param); +extern void *MemoryCard_s_pThis; + +class GarageMainScreen; +extern GarageMainScreen *GetInstance_GarageMainScreen(); + +inline void CustomizeFEngSetVisible(const char *pkg_name, unsigned int obj_hash) { + FEngSetVisible(FEngFindObject(pkg_name, obj_hash)); +} + +inline void CustomizeFEngSetInvisible(const char *pkg_name, unsigned int obj_hash) { + FEngSetInvisible(FEngFindObject(pkg_name, obj_hash)); +} + +inline void CustomizeFEngSetTextureHash(const char *pkg_name, unsigned int obj_hash, unsigned int texture_hash) { + FEngSetTextureHash(FEngFindImage(pkg_name, obj_hash), texture_hash); +} + +// --- CustomizationScreenHelper --- + +CustomizationScreenHelper::CustomizationScreenHelper(const char *pkg_name) { + pPackageName = pkg_name; + bInitComplete = false; + bUnlockOverlayShowing = false; + float actual = gCarCustomizeManager.GetActualHeat(); + float cart = gCarCustomizeManager.GetCartHeat(); + HeatMeter.Init(pkg_name, "HEAT_METER", 0.0f, 5.0f, actual, cart); +} + +void CustomizationScreenHelper::DrawTitle() { + const char *title_str = GetLocalizedString(TitleHash); + char buf[64]; + bSNPrintf(buf, 64, "%s", title_str); + int lang = GetCurrentLanguage(); + if (lang != 2 && lang != 13) { + int i = 0; + while (buf[i] != 0) { + char c = buf[i]; + if (static_cast(c - 'A') < 26u) { + c = c | 0x20; + } + buf[i] = c; + i++; + } + } + FEPrintf(pPackageName, 0xb71b576d, "%s", buf); +} + +void CustomizationScreenHelper::SetPlayerCarStatusIcon(eCustomizePartState state) { + if (state == CPS_INSTALLED) { + FEngSetVisible(FEngFindObject(pPackageName, 0xd0582feb)); + FEngSetTextureHash(FEngFindImage(pPackageName, 0xd0582feb), 0x696ae039); + } else if (state < CPS_INSTALLED + 1) { + if (state == CPS_AVAILABLE) { + FEngSetInvisible(FEngFindObject(pPackageName, 0xd0582feb)); + } + } else if (state == CPS_IN_CART) { + FEngSetVisible(FEngFindObject(pPackageName, 0xd0582feb)); + FEngSetTextureHash(FEngFindImage(pPackageName, 0xd0582feb), 0x1a777e25); + } +} + +void CustomizationScreenHelper::SetUnlockOverlayState(bool show, unsigned int blurb_hash) { + unsigned int script = 0x5079c8f8; + bUnlockOverlayShowing = show; + if (!show) { + script = 0x33113ac; + } else { + FEngSetLanguageHash(pPackageName, 0xa6298e25, blurb_hash); + } + FEngSetScript(pPackageName, 0xebc3e6b7, script, true); +} + +void CustomizationScreenHelper::FlashStatusIcon(eCustomizePartState state, bool play_sound) { + unsigned int hash = 0; + if (state == CPS_INSTALLED || state == CPS_IN_CART) { + hash = 0xd0582feb; + } else if (state == CPS_LOCKED) { + hash = 0xcffb7033; + } + FEngSetScript(pPackageName, hash, 0x280164f, true); + if (play_sound) { + PlayUISoundFX(g_pEAXSound, 7); + } +} + +// --- CustomizeCategoryScreen --- + +CustomizeCategoryScreen::~CustomizeCategoryScreen() { +} + +void CustomizeCategoryScreen::RefreshHeader() { + IconScrollerMenu::RefreshHeader(); + int status = static_cast(Options.GetCurrentOption())->UnlockStatus; + if (status == 2) { + FEngSetVisible(FEngFindObject(GetPackageName(), 0xcffb7033)); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xcffb7033), 0xf0574bb2); + FEngSetScript(GetPackageName(), 0xcffb7033, 0x5079c8f8, true); + } else if (status == 3) { + FEngSetVisible(FEngFindObject(GetPackageName(), 0xcffb7033)); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xcffb7033), 0xcffb7033); + FEngSetScript(GetPackageName(), 0xcffb7033, 0x5079c8f8, true); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xcffb7033)); + } + CarCustomizeManager &mgr = gCarCustomizeManager; + if (mgr.IsCareerMode()) { + CustomizeMeter *meter = &HeatMeter; + meter->SetCurrent(mgr.GetActualHeat()); + meter->SetPreview(mgr.GetCartHeat()); + meter->Draw(); + if (CustomizeIsInBackRoom()) { + FEngSetLanguageHash(GetPackageName(), 0x63ca8308, GetMarkerNameFromCategory(static_cast(Category))); + const char *fmt = "%d"; + FEPrintf(GetPackageName(), 0x83e3cd39, fmt, GetNumMarkersFromCategory(static_cast(Category))); + FEPrintf(GetPackageName(), 0x23d918fe, fmt, TheFEMarkerManager.GetNumCustomizeMarkers()); + } else { + FEPrintf(GetPackageName(), 0x7a6d2f71, "%d", mgr.GetCartTotal(CCT_TOTAL)); + FEPrintf(GetPackageName(), 0xc60adcfd, "%d", FEDatabase->GetCareerSettings()->GetCash()); + } + } else { + HeatMeter.SetVisibility(false); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x8d1559a4)); + } +} + +// --- CustomizationScreen --- + +CustomizationScreen::CustomizationScreen(ScreenConstructorData *sd) : IconScrollerMenu(sd), DisplayHelper(GetPackageName()) { + pReplacingOption = nullptr; + bNeedsRefresh = false; + Options.bFadingIn = true; + ScrollTime = 0; + Category = sd->Arg & 0xFFFF; + FromCategory = static_cast(static_cast(sd->Arg >> 16)); + unsigned int cat = Category; + GarageMainScreen *gms; + if (cat > 0x600 && cat < 0x607) { + gms = GetInstance_GarageMainScreen(); + *(unsigned int *)((char *)gms + 0x8c) = CustomizeDecals::CurrentDecalLocation; + } else { + gms = GetInstance_GarageMainScreen(); + *(unsigned int *)((char *)gms + 0x8c) = Category; + } +} + +CustomizationScreen::~CustomizationScreen() { + GarageMainScreen *gms = GetInstance_GarageMainScreen(); + *(unsigned int *)((char *)gms + 0x8c) = 0xFFFFFFFF; +} + +void CustomizationScreen::AddPartOption(SelectablePart *part, unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash, unsigned int unlock_hash, bool locked) { + CustomizePartOption *opt = new CustomizePartOption(part, tex_hash, name_hash, desc_hash, unlock_hash); + AddOption(opt); + opt->SetLocked(locked); +} + +CustomizePartOption *CustomizationScreen::FindMatchingOption(SelectablePart *to_find) { + IconOption *tail = Options.TailBookEnd; + IconOption *cur = Options.HeadBookEnd->GetNext(); + for (;;) { + bool atEnd = (cur == tail) || (cur == Options.HeadBookEnd); + if (atEnd) break; + SelectablePart *part = static_cast(cur)->ThePart; + if (to_find->PerformancePkg) { + if (part->PhysicsType == to_find->PhysicsType && part->UpgradeLevel == to_find->UpgradeLevel) { + return static_cast(cur); + } + } else { + if (part->ThePart == to_find->ThePart) { + return static_cast(cur); + } + } + cur = cur->GetNext(); + } + return nullptr; +} + +// --- FEShoppingCartItem --- + +void FEShoppingCartItem::SetFocus(const char *parent_pkg) { + FEngSetCurrentButton(parent_pkg, GetTitleObject()); + FEngSetScript(GetTitleObject(), 0x249db7b7, true); + FEngSetScript(GetDataObject(), 0x249db7b7, true); + FEngSetScript(pTradeInPrice, 0x249db7b7, true); + if (GetBacking()) { + FEngSetVisible(GetBacking()); + FEngSetScript(GetBacking(), 0x249db7b7, true); + } +} + +void FEShoppingCartItem::UnsetFocus() { + unsigned int script = 0x7ab5521a; + if (!TheItem->IsActive()) { + script = 0x163c76; + } + FEngSetScript(pTitle, script, true); + FEngSetScript(pData, script, true); + FEngSetScript(pTradeInPrice, script, true); + if (pBacking) { + FEngSetInvisible(pBacking); + FEngSetScript(pBacking, 0x7ab5521a, true); + } +} + +void FEShoppingCartItem::SetCheckScripts() { + if (TheItem->IsActive()) { + FEngSetScript(pCheckIcon, 0xe6361f46, true); + } else { + FEngSetScript(pCheckIcon, 0x77cdc4e9, true); + } +} + +void FEShoppingCartItem::SetActiveScripts() { + if (!TheItem->IsActive()) { + FEngSetScript(pCheckIcon, 0x163c76, true); + } +} + +void FEShoppingCartItem::Draw() { + if (TheItem->IsActive()) { + FEngSetTextureHash(pCheckIcon, 0x696ae039); + } else { + FEngSetTextureHash(pCheckIcon, 0xe719881c); + } + DrawPartName(); + if (TheItem->GetTradeInPart() && gCarCustomizeManager.IsCareerMode() && !CustomizeIsInBackRoom()) { + int tradeIn = TheItem->GetTradeInPart()->GetPrice(); + if (tradeIn == 0) { + tradeIn = 0; + } else { + tradeIn = static_cast(static_cast(tradeIn) * gTradeInFactor); + } + FEPrintf(pTradeInPrice, "%d", tradeIn); + } else { + FEPrintf(pTradeInPrice, ""); + } + if (gCarCustomizeManager.IsCareerMode() && !CustomizeIsInBackRoom()) { + FEPrintf(pData, "%d", TheItem->GetBuyingPart()->GetPrice()); + } else { + FEPrintf(pData, ""); + } +} + +void FEShoppingCartItem::Position() { + FEngSetTopLeft(pCheckIcon, vTopLeft.x, vTopLeft.y - 10.0f); + FEngSetTopLeft(pBacking, vTopLeft.x + 30.0f, vTopLeft.y); + float tx, ty; + FEngGetTopLeft(pTradeInPrice, tx, ty); + FEngSetTopLeft(pTradeInPrice, tx, vTopLeft.y); + float bx, by; + FEngGetBottomRight(pTradeInPrice, bx, by); + FEngSetBottomRight(pTradeInPrice, vSize.x + 150.0f, by); + FEngGetTopLeft(pData, tx, ty); + FEngSetTopLeft(pData, tx, vTopLeft.y); + FEngGetBottomRight(pData, bx, by); + FEngSetBottomRight(pData, vSize.x + 40.0f, by); + if (pCheckIcon) { + FEngSetTopLeft(pCheckIcon, vTopLeft.x - vBackingOffset.x, vTopLeft.y - vBackingOffset.y); + } +} + +unsigned int FEShoppingCartItem::GetPerfPkgCatHash(Physics::Upgrades::Type phys_type) { + unsigned int hash = 0; + switch (phys_type) { + case Physics::Upgrades::kType_Tires: hash = 0x5aa9137; break; + case Physics::Upgrades::kType_Brakes: hash = 0x91997ee8; break; + case Physics::Upgrades::kType_Chassis: hash = 0x6e101aa7; break; + case Physics::Upgrades::kType_Transmission: hash = 0x29aa74ba; break; + case Physics::Upgrades::kType_Engine: hash = 0x9853d9a6; break; + case Physics::Upgrades::kType_Induction: + if (gCarCustomizeManager.IsTurbo()) { + hash = 0x5b1255c; + } else { + hash = 0xbb6812bb; + } + break; + case Physics::Upgrades::kType_Nitrous: hash = 0x4ce19aa4; break; + default: break; + } + return hash; +} + +unsigned int FEShoppingCartItem::GetPerfPkgLevelHash(int level) { + switch (level) { + case 0: return 0x69c270c3; + case 1: return 0x69c270c4; + case 2: return 0x69c270c5; + case 3: return 0x69c270c6; + case 4: return 0x69c270c7; + case 5: return 0x69c270c8; + case 6: return 0x69c270c9; + default: return 0x69c270c3; + } +} + +unsigned int FEShoppingCartItem::GetCarPartCatHash(unsigned int slot_id) { + switch (slot_id) { + case 0x17: return 0x6134c218; + case 0x2c: return 0x94e73021; + case 0x3e: return 0x61e8f83c; + case 0x3f: return 0x4d4a88d; + case 0x42: return 0xf868eb0b; + case 0x4c: return 0x55da70c; + case 0x84: return 0x78980a6b; + case 0x83: return 0xd32729a6; + case 0x4e: return 0xe126ff53; + case 0x53: return 0x301dedd3; + case 0x5b: return 0x48e6ca49; + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: return 0x34367c86; + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: return 0xddf80259; + case 0x73: return 0x8a7697d6; + case 0x7b: return 0xb1f9b0c9; + case 0x71: return 0x6857e5ac; + case 0x4d: return 0xbfa52c55; + default: return 0; + } +} + +void FEShoppingCartItem::DrawPartName() { + SelectablePart *buyPart = TheItem->GetBuyingPart(); + if (buyPart->PerformancePkg) { + unsigned int level = buyPart->UpgradeLevel; + Physics::Upgrades::Type phys_type = static_cast(static_cast(buyPart->PhysicsType)); + if (static_cast(level) == 7) { + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetPerfPkgCatHash(phys_type)), + GetLocalizedString(0xedd14807)); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetPerfPkgCatHash(phys_type)), + GetLocalizedString(0xedd14807)); + } + } else { + int numPkgs = gCarCustomizeManager.GetNumPackages(phys_type); + int displayLevel = static_cast(level) - (numPkgs - 6); + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetPerfPkgCatHash(phys_type)), + GetLocalizedString(GetPerfPkgLevelHash(displayLevel))); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetPerfPkgCatHash(phys_type)), + GetLocalizedString(GetPerfPkgLevelHash(displayLevel))); + } + } + return; + } + + switch (buyPart->CarSlotID) { + case 0x4e: { + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0xb3100a3e), + GetLocalizedString(buyPart->ThePart->GetAppliedAttributeUParam(bStringHash("SPEECHCOLOUR"), 0))); + } else { + FEPrintf(GetTitleObject(), "%s: %s %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0xb3100a3e), + GetLocalizedString(buyPart->ThePart->GetAppliedAttributeUParam(bStringHash("SPEECHCOLOUR"), 0))); + } + return; + } + + case 0x4c: { + unsigned int paint_type = buyPart->ThePart->GetAppliedAttributeUParam(0xebb03e66, 0); + unsigned int colorHash = 0x452b5481; + if (paint_type == 0x2daab07) { + colorHash = 0xb6763cde; + } else if (paint_type > 0x2daab07) { + if (paint_type != 0x3437a52) { + if (paint_type == 0x3797533) { + colorHash = 0xb715070a; + } + } + } else if (paint_type == 0xda27) { + colorHash = 0xb3100a3e; + } + const char *fmt; + if (GetCurrentLanguage() == 1) { + fmt = "%s : %s %s"; + } else { + fmt = "%s: %s %s"; + } + FEPrintf(GetTitleObject(), fmt, + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(colorHash), + GetLocalizedString(buyPart->ThePart->GetAppliedAttributeUParam(bStringHash("SPEECHCOLOUR"), 0))); + return; + } + + case 0x71: { + ShoppingCartItem *leftItem = gCarCustomizeManager.IsPartTypeInCart(0x71); + ShoppingCartItem *rightItem = gCarCustomizeManager.IsPartTypeInCart(0x72); + if (!leftItem) return; + if (!rightItem) return; + CarPart *left_part = leftItem->GetBuyingPart()->ThePart; + CarPart *right_part = rightItem->GetBuyingPart()->ThePart; + if (!left_part) { + goto missing_parts; + } + if (!right_part) { + goto missing_parts; + } + const char *fmt; + if (GetCurrentLanguage() == 1) { + fmt = "%s : %s%s"; + } else { + fmt = "%s: %s%s"; + } + FEPrintf(GetTitleObject(), fmt, + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + left_part->GetName(), + right_part->GetName()); + return; + missing_parts: + { + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0xbe434a38)); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0xbe434a38)); + } + return; + } + } + + case 0x53: case 0x5b: + case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: + case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: case 0x70: + case 0x73: case 0x7b: { + if (!buyPart->ThePart) { + const char *fmt; + if (GetCurrentLanguage() == 1) { + fmt = "%s : %s - %s"; + } else { + fmt = "%s: %s - %s"; + } + FEPrintf(GetTitleObject(), fmt, + GetLocalizedString(0x955980bc), + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0x7177dc17)); + return; + } + buyPart->ThePart->GetAppliedAttributeUParam(0xebb03e66, 0); + buyPart->ThePart->GetAppliedAttributeUParam(bStringHash("NAME"), 0); + unsigned int subCatHash = 0; + switch (buyPart->CarSlotID) { + case 0x63: case 0x6b: subCatHash = 0x7d212cfa; break; + case 0x64: case 0x6c: subCatHash = 0x7d212cfb; break; + case 0x65: case 0x6d: subCatHash = 0x7d212cfc; break; + case 0x66: case 0x6e: subCatHash = 0x7d212cfd; break; + case 0x67: case 0x6f: subCatHash = 0x7d212cfe; break; + case 0x68: case 0x70: subCatHash = 0x7d212cff; break; + } + if (subCatHash) { + const char *fmt; + if (GetCurrentLanguage() == 1) { + fmt = "%s : %s %s %s"; + } else { + fmt = "%s: %s %s %s"; + } + FEPrintf(GetTitleObject(), fmt, + GetLocalizedString(0x955980bc), + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(subCatHash), + buyPart->ThePart->GetName()); + return; + } + const char *fmt; + if (GetCurrentLanguage() == 1) { + fmt = "%s : %s %s"; + } else { + fmt = "%s: %s %s"; + } + FEPrintf(GetTitleObject(), fmt, + GetLocalizedString(0x955980bc), + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + buyPart->ThePart->GetName()); + return; + } + + case 0x17: { + CarPart *part = buyPart->ThePart; + const char *lang_str = "LANGUAGEHASH"; + if (part->HasAppliedAttribute(bStringHash(lang_str))) { + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash(lang_str), 0))); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash(lang_str), 0))); + } + return; + } + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + part->GetName()); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + part->GetName()); + } + return; + } + + case 0x42: { + CarPart *car_part = buyPart->ThePart; + CarPart *stock = gCarCustomizeManager.GetStockCarPart(0x42); + if (car_part != stock) { + char buf[64]; + bSNPrintf(buf, 64, "%s", car_part->GetName()); + int len = bStrLen(buf); + if (len < 1) return; + int trimStart = len - 6; + for (; trimStart <= len; len--) { + buf[len] = 0; + } + const char *fmt; + if (GetCurrentLanguage() == 1) { + fmt = "%s : %s %$d\""; + } else { + fmt = "%s: %s %$d\""; + } + FEPrintf(GetTitleObject(), fmt, + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + buf, + static_cast(car_part->GetAppliedAttributeIParam(0xeb0101e2, 0))); + return; + } + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0x60a662f5)); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0x60a662f5)); + } + return; + } + + case 0x2c: + case 0x3e: + case 0x3f: { + CarPart *part = buyPart->ThePart; + if (part->HasAppliedAttribute(bStringHash("CARBONFIBRE"))) { + if (part->GetAppliedAttributeIParam(bStringHash("CARBONFIBRE"), 0) != 0) { + const char *fmt; + if (GetCurrentLanguage() == 1) { + fmt = "%s : %s %s"; + } else { + fmt = "%s: %s %s"; + } + FEPrintf(GetTitleObject(), fmt, + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0x5415b874), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash("LANGUAGEHASH"), 0))); + return; + } + } + const char *lang_str = "LANGUAGEHASH"; + if (part->HasAppliedAttribute(bStringHash(lang_str))) { + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash(lang_str), 0))); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash(lang_str), 0))); + } + return; + } + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + part->GetName()); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + part->GetName()); + } + return; + } + + case 0x4d: { + CarPart *part = buyPart->ThePart; + if (!part) { + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0x60a662f5)); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(0x60a662f5)); + } + return; + } + const char *lang_str = "LANGUAGEHASH"; + if (part->HasAppliedAttribute(bStringHash(lang_str))) { + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash(lang_str), 0))); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash(lang_str), 0))); + } + return; + } + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + part->GetName()); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + part->GetName()); + } + return; + } + + default: { + CarPart *part = buyPart->ThePart; + const char *lang_str = "LANGUAGEHASH"; + if (part->HasAppliedAttribute(bStringHash(lang_str))) { + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash(lang_str), 0))); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + GetLocalizedString(part->GetAppliedAttributeUParam(bStringHash(lang_str), 0))); + } + return; + } + if (GetCurrentLanguage() == 1) { + FEPrintf(GetTitleObject(), "%s : %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + part->GetName()); + } else { + FEPrintf(GetTitleObject(), "%s: %s", + GetLocalizedString(GetCarPartCatHash(buyPart->CarSlotID)), + part->GetName()); + } + return; + } + } +} + +CustomizeShoppingCart::CustomizeShoppingCart(ScreenConstructorData *sd) : UIWidgetMenu(sd) { + bScrollWrapped = false; + if (gCarCustomizeManager.IsCareerMode()) { + iMaxWidgetsOnScreen = 4; + } else { + iMaxWidgetsOnScreen = 6; + } + Setup(); +} + +bool CustomizeShoppingCart::CanCheckout() { + if (gCarCustomizeManager.IsCareerMode()) { + if (CustomizeIsInBackRoom()) { + return true; + } + return gCarCustomizeManager.GetCartTotal(CCT_TOTAL) <= FEDatabase->GetCareerSettings()->GetCash(); + } + return true; +} + +void CustomizeShoppingCart::ToggleAllNumberDecals() { + int count = gCarCustomizeManager.ShoppingCart.TraversebList(nullptr); + for (int i = 0; i < count; i++) { + ShoppingCartItem *item = static_cast(gCarCustomizeManager.ShoppingCart.GetNode(i)); + if (item && item->GetBuyingPart()) { + if (IsSlotIDNumberDecal(item->GetBuyingPart()->GetSlotID())) { + item->ToggleActive(); + } + } + } +} + +void CustomizeShoppingCart::ToggleChecked() { + if (pCurrentOption) { + ShoppingCartItem *item = static_cast(pCurrentOption)->GetItem(); + if (item) { + item->ToggleActive(); + if (item->GetBuyingPart() && IsSlotIDNumberDecal(item->GetBuyingPart()->GetSlotID())) { + ToggleAllNumberDecals(); + item->ToggleActive(); + } + } + static_cast(pCurrentOption)->SetCheckScripts(); + pCurrentOption->Draw(); + } +} + +void CustomizeShoppingCart::UncheckAllItems() { + ShoppingCartItem *cart_item = gCarCustomizeManager.GetFirstCartItem(); + while (cart_item != gCarCustomizeManager.GetLastCartItem()->GetNext()) { + if (cart_item->bActive) { + cart_item->ToggleActive(); + } + cart_item = cart_item->GetNext(); + } + FEShoppingCartItem *w = static_cast(Options.GetHead()); + while (w != Options.EndOfList()) { + w->SetCheckScripts(); + w->Draw(); + w = static_cast(w->GetNext()); + } +} + +// --- CustomizeParts --- + +static void UnLoadCustomHUDPacksAndTextures(); + +CustomizeParts::CustomizeParts(ScreenConstructorData *sd) : CustomizationScreen(sd) { + bTexturesNeedUnload = false; + if (Category == 0x307) { + if (!TexturePackLoaded) { + for (int i = 0; i < 11; i++) { + CustomizeHUDTexPackResources[i] = 0; + for (unsigned int j = 0; j < 5; j++) { + CustomizeHUDTexTextureResources[i * 5 + j] = 0; + } + } + } + TachRPM = static_cast(Physics::Info::Redline(gCarCustomizeManager.ThePVehicle)); + if (TachRPM >= 0x251d) { + TachRPM = 10000; + } else if (TachRPM >= 0x2135) { + TachRPM = 9000; + } else if (TachRPM > 0x1d4c) { + TachRPM = 8000; + } else { + TachRPM = 7000; + } + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xdee8632b)); + } + Setup(); +} + +CustomizeParts::~CustomizeParts() { + if (TexturePackLoaded && bTexturesNeedUnload) { + UnLoadCustomHUDPacksAndTextures(); + } +} + +void CustomizeParts::LoadNextHudTexturePack() { + char buf[64]; + bSPrintf(buf, "HUD_TEX_%02d", PacksLoadedCount); + int result = eLoadStreamingTexturePack(buf, reinterpret_cast(TexturePackLoadedCallbackAccessor), this, 0); + CustomizeHUDTexPackResources[PacksLoadedCount] = (result != 0) ? 1 : 0; +} + +void CustomizeParts::TextureLoadedCallback() { + if (PacksLoadedCount < 11) { + LoadNextHudTexturePack(); + } else { + TexturePackLoaded = true; + cFEng_mInstance->MakeLoadedPackagesDirty(); + ShowHudObjects(); + RefreshHeader(); + cFEng_mInstance->QueuePackageMessage(0x8cb81f09, GetPackageName(), nullptr); + } +} + +// --- CustomizeMain --- + +CustomizeMain::CustomizeMain(ScreenConstructorData *sd) : CustomizeCategoryScreen(sd) { + int entryPoint = g_TheCustomizeEntryPoint; + iPerfIndex = 0; + invalidMarkers = 0; + if (entryPoint == 0) { + CarViewer_haveLoadedOnce = 0; + } + gCarCustomizeManager.TakeControl(static_cast(entryPoint), g_pCustomizeCarRecordToUse); + for (int i = 0; i < 0x39; i++) { + MarkUnlockableThingSeen(i, gCarCustomizeManager.GetUnlockFilter()); + } + Setup(); + if (gCarCustomizeManager.IsCareerMode()) { + FEDatabase->BackupCarStable(); + } +} + +void CustomizeMain::Setup() { + BackToPkg = "FeGarageMain.fng"; + SetTitle(CustomizeIsInBackRoom()); + SetScreenNames(); + CustomizeSetInPerformance(false); + CustomizeSetInParts(false); + Category = 0; + BuildOptionsList(); + SetInitialOption(FromCategory & 0xFFFF00FF); + RefreshHeader(); +} + +// --- CustomizeSpoiler --- + +CustomizeSpoiler::CustomizeSpoiler(ScreenConstructorData *sd) : CustomizationScreen(sd) { + TheFilter = 0; + for (int i = 0; i < 4; i++) { + SelectedIndex[i] = 1; + } + Setup(); +} + +void CustomizeSpoiler::ScrollFilters(eScrollDir dir) { + int filter = TheFilter; + if (dir == eScrollDir(-1)) { + filter--; + if (filter < 0) { + filter = 3; + } + } else if (dir == eScrollDir(1)) { + filter++; + if (filter > 3) { + filter = 0; + } + } + if (filter != TheFilter) { + TheFilter = filter; + BuildPartOptionListFromFilter(nullptr); + RefreshHeader(); + } +} + +// --- CustomizeSub --- + +int CustomizeSub::GetVinylGroupIndex(int group) { + switch (group) { + case 0: return 2; + case 1: return 3; + case 2: return 4; + case 3: return 5; + case 4: return 6; + case 5: return 7; + case 6: return 8; + case 7: return 9; + default: return 1; + } +} + +// --- UnLoadCustomHUDPacksAndTextures --- + +static void UnLoadCustomHUDPacksAndTextures() { + for (int i = 0; i < 11; i++) { + for (unsigned int j = 0; j < 5; j++) { + int idx = j * 4 + i * 0x14; + if (CustomizeHUDTexTextureResources[i * 5 + j] != 0) { + int handle = CustomizeHUDTexTextureResources[i * 5 + j]; + eUnloadStreamingTexture(&handle, 1); + } + CustomizeHUDTexTextureResources[i * 5 + j] = 0; + } + if (CustomizeHUDTexPackResources[i] != 0) { + char buf[64]; + bSPrintf(buf, "HUD_TEX_%02d", i); + eUnloadStreamingTexturePack(buf); + } + CustomizeHUDTexPackResources[i] = 0; + } + CustomizeParts::TexturePackLoaded = 0; +} + +// --- CustomizationScreenHelper additional --- + +void CustomizationScreenHelper::SetCareerStatusIcon(eCustomizePartState state) { + if (state == CPS_LOCKED) { + FEngSetVisible(FEngFindObject(pPackageName, 0xcffb7033)); + FEngSetTextureHash(FEngFindImage(pPackageName, 0xcffb7033), 0xf0574bb2); + } else if (state == CPS_NEW) { + FEngSetVisible(FEngFindObject(pPackageName, 0xcffb7033)); + FEngSetTextureHash(FEngFindImage(pPackageName, 0xcffb7033), 0xcffb7033); + } else { + FEngSetInvisible(FEngFindObject(pPackageName, 0xcffb7033)); + } +} + +void CustomizationScreenHelper::SetCashVisibility(bool visible) { + if (visible) { + FEngSetVisible(FEngFindObject(pPackageName, 0x8d1559a4)); + } else { + FEngSetInvisible(FEngFindObject(pPackageName, 0x8d1559a4)); + } +} + +void CustomizationScreenHelper::SetCareerStuff(SelectablePart *part, unsigned int cat, unsigned int tradeInValue) { + if (gCarCustomizeManager.IsCareerMode()) { + if (CustomizeIsInBackRoom()) { + FEngSetLanguageHash(GetPackageName(), 0x63ca8308, GetMarkerNameFromCategory(static_cast(cat))); + FEPrintf(GetPackageName(), 0x23d918fe, "1"); + FEPrintf(GetPackageName(), 0x83e3cd39, "%d", GetNumMarkersFromCategory(static_cast(cat))); + } else { + if (part) { + FEPrintf(GetPackageName(), 0xdbb80edd, "%d", part->GetPrice()); + } else { + SelectablePart *tempPart = gCarCustomizeManager.GetTempColoredPart(); + if (tempPart) { + FEPrintf(GetPackageName(), 0xdbb80edd, "%d", tempPart->GetPrice()); + } else { + FEPrintf(GetPackageName(), 0xdbb80edd, "-1"); + } + } + FEPrintf(GetPackageName(), 0xc60adcfd, "%d", FEDatabase->GetCareerSettings()->GetCash()); + FEPrintf(GetPackageName(), 0x7a6d2f71, "%d", gCarCustomizeManager.GetCartTotal(static_cast(2))); + FEPrintf(GetPackageName(), 0xa91eda8a, "%d", tradeInValue); + } + SetHeatValue(gCarCustomizeManager.GetActualHeat()); + SetHeatPreview(gCarCustomizeManager.GetPreviewHeat(part)); + DrawMeters(); + } else { + SetCareerStatusIcon(CPS_AVAILABLE); + SetCashVisibility(false); + HeatMeter.SetVisibility(false); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x24c6bfad)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xea903012)); + } +} + +void CustomizationScreenHelper::SetPartStatus(SelectablePart *part, unsigned int unlock_blurb, int part_num, int max_parts) { + if (part) { + if (part->IsInstalled()) { + SetPlayerCarStatusIcon(CPS_INSTALLED); + } else if (part->IsInCart()) { + SetPlayerCarStatusIcon(CPS_IN_CART); + } else { + SetPlayerCarStatusIcon(CPS_AVAILABLE); + } + if (part->IsLocked() && unlock_blurb) { + if (IsInitComplete()) { + SetUnlockOverlayState(true, unlock_blurb); + } + SetCareerStatusIcon(CPS_LOCKED); + } else { + if (bUnlockOverlayShowing) { + SetUnlockOverlayState(false, 0); + } + if (part->IsNew()) { + SetCareerStatusIcon(CPS_NEW); + } else { + SetCareerStatusIcon(CPS_AVAILABLE); + } + } + } + FEPrintf(GetPackageName(), 0x6f25a248, "%d", part_num); + FEPrintf(GetPackageName(), 0xb2037bdc, "%d", max_parts); +} + +// --- CustomizationScreen additional --- + +void CustomizationScreen::RefreshHeader() { + IconScrollerMenu::RefreshHeader(); + DisplayHelper.DrawTitle(); + int count = Options.Options.CountElements(); + if (count != Options.iNumBookEnds) { + int tradeInValue = 0; + if (gCarCustomizeManager.IsCareerMode()) { + if (!CustomizeIsInBackRoom()) { + SelectablePart *part = GetSelectedPart(); + if (part) { + if (gCarCustomizeManager.CanTradeIn(part)) { + CarPart *installed = gCarCustomizeManager.GetInstalledCarPart(part->GetSlotID()); + if (installed) { + eUnlockFilters filter = gCarCustomizeManager.GetUnlockFilter(); + tradeInValue = UnlockSystem::GetCarPartCost(filter, part->GetSlotID(), installed, 0); + } + tradeInValue = static_cast(static_cast(tradeInValue) * gTradeInFactor); + } + } + } + } + SelectablePart *selPart = GetSelectedPart(); + DisplayHelper.SetCareerStuff(selPart, Category, tradeInValue); + SelectablePart *selPart2 = GetSelectedPart(); + unsigned int unlockBlurb = static_cast(Options.GetCurrentOption())->UnlockBlurb; + int partNum = Options.GetCurrentIndex(); + int numParts = Options.Options.CountElements(); + DisplayHelper.SetPartStatus(selPart2, unlockBlurb, partNum, numParts - Options.iNumBookEnds); + } +} + +SelectablePart *CustomizationScreen::FindInCartPart() { + IconOption *tail = Options.TailBookEnd; + IconOption *cur = Options.HeadBookEnd->GetNext(); + for (;;) { + bool atEnd = (cur == tail) || (cur == Options.HeadBookEnd); + if (atEnd) break; + SelectablePart *part = static_cast(cur)->ThePart; + if (part && (part->GetPartState() & 0xF0) == CPS_IN_CART) { + return part; + } + cur = cur->GetNext(); + } + return nullptr; +} + +void CustomizationScreen::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (msg == 0x35f8620b) { + DisplayHelper.SetInitComplete(true); + RefreshHeader(); + } + if (msg == 0x9120409e || msg == 0xb5971bf1) { + ScrollTime.SetPackedTime(RealTimer.GetPackedTime()); + } + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0xc98356ba: + if (!bNeedsRefresh) { + return; + } + { + float elapsed = static_cast(static_cast(RealTimer.GetPackedTime() - ScrollTime.GetPackedTime())) * 0.001f; + if (elapsed <= 0.25f) { + return; + } + } + bNeedsRefresh = false; + RefreshHeader(); + break; + case 0x5e6ea975: + Options.SetAllowFade(true); + Options.bDelayUpdate = false; + break; + case 0x406415e3: { + CustomizePartOption *curOpt = static_cast(Options.GetCurrentOption()); + if (curOpt) { + eCustomizePartState state = curOpt->GetPart()->GetPartState(); + if ((state & 0xF) == CPS_LOCKED) { + DisplayHelper.FlashStatusIcon(CPS_LOCKED, true); + return; + } + unsigned int stateFlags = state & 0xF0; + if (stateFlags == CPS_IN_CART) { + DisplayHelper.FlashStatusIcon(CPS_IN_CART, true); + return; + } + if (stateFlags == CPS_INSTALLED) { + ShoppingCartItem *inCart = gCarCustomizeManager.IsPartTypeInCart(curOpt->GetPart()); + if (inCart) { + gCarCustomizeManager.RemoveFromCart(inCart); + CustomizePartOption *found = FindMatchingOption(inCart->GetBuyingPart()); + if (found) { + found->GetPart()->PartState = static_cast(found->GetPart()->PartState & 0xF); + } + } + DisplayHelper.FlashStatusIcon(CPS_INSTALLED, true); + return; + } + } + ShoppingCartItem *inCart = gCarCustomizeManager.IsPartTypeInCart(static_cast(Options.GetCurrentOption())->GetPart()); + if (inCart) { + pReplacingOption = FindMatchingOption(inCart->GetBuyingPart()); + } + cFEng_mInstance->QueueGameMessage(0x91dfdf84, GetPackageName(), 0xFF); + break; + } + case 0x91dfdf84: { + if (pReplacingOption) { + SelectablePart *rp = pReplacingOption->GetPart(); + rp->PartState = static_cast(rp->PartState & 0xF); + pReplacingOption = nullptr; + } + CustomizePartOption *curOpt = static_cast(Options.GetCurrentOption()); + gCarCustomizeManager.AddToCart(curOpt->GetPart()); + curOpt = static_cast(Options.GetCurrentOption()); + curOpt->GetPart()->PartState = static_cast((curOpt->GetPart()->PartState & 0xF) | CPS_IN_CART); + RefreshHeader(); + break; + } + case 0xcf91aacd: + CustomizeShoppingCart::ExitShoppingCart(); + break; + case 0xc519bfbf: { + unsigned int cat = Category; + if (cat > 0x200 && cat < 0x208) { + return; + } + if (cat == 0x307) { + return; + } + if (Options.pCurrentNode == nullptr) { + Showcase::FromIndex = 0; + } else { + Showcase::FromIndex = Options.GetCurrentIndex(); + } + Showcase::FromPackage = GetPackageName(); + Showcase::FromArgs = Category | (FromCategory << 16); + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeShowcasePkg, 0, 0, false); + break; + } + case 0xb5af2461: + CustomizeShoppingCart::ShowShoppingCart(GetPackageName()); + break; + } +} + +// --- CustomizeShoppingCart additional --- + +extern const char *g_pCustomizeShoppingCartPkg; + +void CustomizeShoppingCart::ShowShoppingCart(const char *pkg) { + pParentPkg = pkg; + cFEng_mInstance->QueuePackagePush(g_pCustomizeShoppingCartPkg, 0, 0, false); +} + +void CustomizeShoppingCart::ExitShoppingCart() { + if (gCarCustomizeManager.IsInBackRoom()) { + gCarCustomizeManager.SetInBackRoom(false); + FEManager::Get()->SetGarageType(GARAGETYPE_CUSTOMIZATION_SHOP); + } + cFEng::Get()->QueuePackageSwitch(g_pCustomizeMainPkg, 0, 0, false); +} + +bool CustomizeShoppingCart::IsSlotIDNumberDecal(int slot_id) { + if (slot_id == 0x71 || slot_id == 0x72 || slot_id == 0x69 || slot_id == 0x6a) { + return true; + } + return false; +} + +void CustomizeShoppingCart::ClearUncheckedItems() { + ShoppingCartItem *item = gCarCustomizeManager.ShoppingCart.GetHead(); + while (item != gCarCustomizeManager.ShoppingCart.EndOfList()) { + if (!item->IsActive()) { + if (item->GetBuyingPart()->GetSlotID() == 0x4d) { + ShoppingCartItem *inner = gCarCustomizeManager.ShoppingCart.GetHead(); + while (inner != gCarCustomizeManager.ShoppingCart.EndOfList()) { + if (inner->GetBuyingPart()->GetSlotID() < 0x53 && inner->GetBuyingPart()->GetSlotID() > 0x4e) { + ShoppingCartItem *next_inner = static_cast(inner->Next); + gCarCustomizeManager.RemoveFromCart(inner); + inner = next_inner; + } else { + inner = static_cast(inner->Next); + } + } + } + ShoppingCartItem *next = static_cast(item->Next); + gCarCustomizeManager.RemoveFromCart(item); + item = next; + } else { + item = static_cast(item->Next); + } + } + gCarCustomizeManager.ResetPreview(); +} + +void CustomizeShoppingCart::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + UIWidgetMenu::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0xc519bfc3: + ToggleChecked(); + RefreshHeader(); + break; + case 0x406415e3: + if (!gCarCustomizeManager.DoesCartHaveActiveParts()) { + gCarCustomizeManager.EmptyCart(); + gCarCustomizeManager.ResetPreview(); + gCarCustomizeManager.ResetPreview(); + cFEng_mInstance->QueueGameMessage(0xcf91aacd, pParentPkg, 0xFF); + cFEng_mInstance->QueuePackagePop(1); + break; + } + if (CanCheckout()) { + unsigned int dialog_hash; + if (gCarCustomizeManager.IsCareerMode()) { + if (CustomizeIsInBackRoom()) { + dialog_hash = 0x4810898; + } else { + dialog_hash = 0x8ebaa44b; + } + } else { + dialog_hash = 0x71d9e710; + } + DialogInterface::ShowTwoButtons(GetPackageName(), g_pCustomizeDlgPkg, static_cast(1), + 0x70e01038, 0x417b25e4, 0xd05fc3a3, 0x34dc1bcf, 0x34dc1bcf, static_cast(1), dialog_hash); + } else { + DialogInterface::ShowOk(GetPackageName(), g_pCustomizeDlgPkg, static_cast(1), 0xa984a42); + } + break; + case 0xd05fc3a3: + gCarCustomizeManager.Checkout(); + cFEng_mInstance->QueueGameMessage(0xcf91aacd, pParentPkg, 0xFF); + cFEng_mInstance->QueuePackagePop(1); + break; + case 0xc519bfc4: + UncheckAllItems(); + RefreshHeader(); + break; + case 0x72619778: + case 0x911c0a4b: + RefreshHeader(); + break; + case 0x911ab364: + ClearUncheckedItems(); + cFEng_mInstance->QueueGameMessage(0x5a928018, pParentPkg, 0xFF); + cFEng_mInstance->QueuePackagePop(1); + break; + } +} + +void CustomizeShoppingCart::RefreshHeader() { + if (pCurrentOption) { + FEngSetVisible(FEngFindObject(GetPackageName(), 0x842b0e89)); + ShoppingCartItem *item = static_cast(pCurrentOption)->GetItem(); + if (item->IsActive()) { + FEngSetLanguageHash(GetPackageName(), 0xd57c95e1, 0x5dabcbc0); + } else { + FEngSetLanguageHash(GetPackageName(), 0xd57c95e1, 0x28feadd); + } + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x842b0e89)); + } + HeatMeter.SetCurrent(gCarCustomizeManager.GetActualHeat()); + HeatMeter.SetPreview(gCarCustomizeManager.GetCartHeat()); + HeatMeter.Draw(); + if (gCarCustomizeManager.IsCareerMode()) { + if (CustomizeIsInBackRoom()) { + SetMarkerAmounts(); + int total; + FEMarkerManager::ePossibleMarker availableMarker; + if (CustomizeIsInParts()) { + FEngSetLanguageHash(GetPackageName(), 0x8cdcb8ed, 0xa03a752f); + FEngSetLanguageHash(GetPackageName(), 0xd3d3b1f4, 0x4ac68298); + total = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_BODY, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_HOOD, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_SPOILER, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_RIMS, 0); + availableMarker = FEMarkerManager::MARKER_ROOF_SCOOP; + } else if (CustomizeIsInPerformance()) { + FEngSetLanguageHash(GetPackageName(), 0x8cdcb8ed, 0x358db897); + FEngSetLanguageHash(GetPackageName(), 0xd3d3b1f4, 0x68342700); + total = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_BRAKES, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_ENGINE, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_NOS, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_INDUCTION, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_CHASSIS, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_TIRES, 0); + availableMarker = FEMarkerManager::MARKER_TRANSMISSION; + } else { + FEngSetLanguageHash(GetPackageName(), 0x8cdcb8ed, 0x93296e59); + FEngSetLanguageHash(GetPackageName(), 0xd3d3b1f4, 0x78f1c602); + total = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_VINYL, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_DECAL, 0); + total += TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_PAINT, 0); + availableMarker = FEMarkerManager::MARKER_CUSTOM_HUD; + } + int available = TheFEMarkerManager.GetNumMarkers(availableMarker, 0); + FEPrintf(GetPackageName(), 0xd1497a06, "%d", gCarCustomizeManager.GetCartTotal(static_cast(0))); + int cartCost = gCarCustomizeManager.GetCartTotal(static_cast(0)); + FEPrintf(GetPackageName(), 0x18661565, "%d", (total + available) - cartCost); + } else { + FEPrintf(GetPackageName(), 0xd1497a06, "%d", gCarCustomizeManager.GetCartTotal(static_cast(0))); + FEPrintf(GetPackageName(), 0x34f7c0e8, "%d", gCarCustomizeManager.GetCartTotal(static_cast(1))); + int totalCost = gCarCustomizeManager.GetCartTotal(static_cast(2)); + FEPrintf(GetPackageName(), 0x18661565, "%d", totalCost); + FEPrintf(GetPackageName(), 0x8531e22e, "%d", FEDatabase->GetCareerSettings()->GetCash() - totalCost); + } + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x9ea22e0b)); + } +} + + +void CustomizeShoppingCart::SetMarkerAmounts() { + if (CustomizeIsInPerformance()) { + static Physics::Upgrades::Type phys_type[7] = { + Physics::Upgrades::kType_Transmission, + Physics::Upgrades::kType_Tires, + Physics::Upgrades::kType_Induction, + Physics::Upgrades::kType_Brakes, + Physics::Upgrades::kType_Chassis, + Physics::Upgrades::kType_Engine, + Physics::Upgrades::kType_Nitrous, + }; + static int markers[7] = { + FEMarkerManager::MARKER_BRAKES, + FEMarkerManager::MARKER_ENGINE, + FEMarkerManager::MARKER_NOS, + FEMarkerManager::MARKER_INDUCTION, + FEMarkerManager::MARKER_CHASSIS, + FEMarkerManager::MARKER_TIRES, + FEMarkerManager::MARKER_TRANSMISSION, + }; + int i = 0; + do { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(phys_type[i]); + int num = TheFEMarkerManager.GetNumMarkers(static_cast(markers[i]), 0); + if (item && item->IsActive()) { + num--; + } + i++; + SetMarkerData(i, item, num); + } while (i < 7); + } else if (CustomizeIsInParts()) { + static unsigned int slot_id[5] = {0x17, 0x3F, 0x2C, 0x42, 0x3E}; + static int markers[5] = { + FEMarkerManager::MARKER_BODY, + FEMarkerManager::MARKER_HOOD, + FEMarkerManager::MARKER_SPOILER, + FEMarkerManager::MARKER_RIMS, + FEMarkerManager::MARKER_ROOF_SCOOP, + }; + int i = 0; + do { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(slot_id[i]); + int num = TheFEMarkerManager.GetNumMarkers(static_cast(markers[i]), 0); + if (item && item->IsActive()) { + num--; + } + i++; + SetMarkerData(i, item, num); + } while (i < 5); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e22)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e23)); + } else { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(static_cast(0x4d)); + int num = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_VINYL, 0); + if (item && item->IsActive()) { + num--; + } + SetMarkerData(1, item, num); + + int spending = GetNumMarkersSpending(0x53); + spending += GetNumMarkersSpending(0x5b); + spending += GetNumMarkersSpending(99); + spending += GetNumMarkersSpending(100); + spending += GetNumMarkersSpending(0x65); + spending += GetNumMarkersSpending(0x66); + spending += GetNumMarkersSpending(0x67); + spending += GetNumMarkersSpending(0x68); + spending += GetNumMarkersSpending(0x6b); + spending += GetNumMarkersSpending(0x6c); + spending += GetNumMarkersSpending(0x6d); + spending += GetNumMarkersSpending(0x6e); + spending += GetNumMarkersSpending(0x6f); + spending += GetNumMarkersSpending(0x70); + spending += GetNumMarkersSpending(0x73); + spending += GetNumMarkersSpending(0x7b); + int numDecals = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_DECAL, 0); + SetMarkerData(2, item, numDecals - spending); + + item = gCarCustomizeManager.IsPartTypeInCart(static_cast(0x4c)); + num = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_PAINT, 0); + if (item && item->IsActive()) { + num--; + } + SetMarkerData(3, item, num); + + item = gCarCustomizeManager.IsPartTypeInCart(static_cast(0x84)); + num = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_CUSTOM_HUD, 0); + if (item && item->IsActive()) { + num--; + } + SetMarkerData(4, item, num); + + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e21)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e22)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e23)); + } +} + +void CustomizeShoppingCart::SetMarkerData(int idx, ShoppingCartItem *item, int spending) { + const char *pkg = GetPackageName(); + unsigned int hash = FEngHashString("MARKER_GROUP_%d", idx); + unsigned int script = 0x6ebbfb68; + if (spending == 0) { + script = 0x163c76; + } + FEngSetScript(pkg, hash, script, true); + + hash = FEngHashString("MARKER_NUM_%d", idx); + FEPrintf(GetPackageName(), hash, "%$d", spending); + + hash = FEngHashString("MARKER_BLOOM_%d", idx); + FEPrintf(GetPackageName(), hash, "%$d", spending); +} + +int CustomizeShoppingCart::GetNumMarkersSpending(unsigned int marker) { + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(marker); + int result = 0; + if (item && item->IsActive()) { + result = 1; + } + return result; +} + +void CustomizeShoppingCart::SetMarkerImages() { + if (CustomizeIsInPerformance()) { + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957471), 0x4887f351); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957472), 0x4f424e0f); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957473), 0x6fea04c8); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957474), 0x8e284227); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957475), 0x190eb6); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957476), 0x7373f1ef); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957477), 0xd142d3e3); + } else if (CustomizeIsInParts()) { + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957471), 0xaf393dba); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957472), 0xf375276e); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957473), 0xc51a4f62); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957474), 0xc19491cc); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957475), 0x25a4375e); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e22)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e23)); + } else { + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957471), 0xd35f04c0); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957472), 0xa9135927); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957473), 0xdb89e17); + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xeb957474), 0x8ba602fc); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e21)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e22)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x47df0e23)); + } +} + +void CustomizeShoppingCart::Setup() { + const char *pkg = GetPackageName(); + CustomizeMeter *meter = &HeatMeter; + const char *meter_name = "HEAT_METER"; + float actual = gCarCustomizeManager.GetActualHeat(); + float cart = gCarCustomizeManager.GetCartHeat(); + meter->Init(pkg, meter_name, 1.0f, 5.0f, actual, cart); + + ShoppingCartItem *item = gCarCustomizeManager.GetFirstCartItem(); + while (item != gCarCustomizeManager.GetLastCartItem()->GetNext()) { + switch (item->GetBuyingPart()->GetSlotID()) { + case 0x4f: case 0x50: case 0x51: case 0x52: + case 0x69: case 0x6a: + case 0x72: + case 0x85: case 0x86: case 0x87: + break; + default: + AddItem(item); + break; + } + item = item->GetNext(); + } + + if (CustomizeIsInBackRoom()) { + SetMarkerImages(); + } + + SetInitialOption(0); + + FEShoppingCartItem *widget = static_cast(Options.GetHead()); + while (widget != static_cast(Options.EndOfList())) { + widget->SetCheckScripts(); + widget->SetActiveScripts(); + widget = static_cast(widget->GetNext()); + } + + RefreshHeader(); +} + +void CustomizeShoppingCart::AddItem(ShoppingCartItem *item) { + FEShoppingCartItem *widget = new FEShoppingCartItem(item); + widget->SetTitleObject(GetCurrentFEString("PART_NAME_")); + widget->SetDataObject(GetCurrentFEString("PRICE_")); + widget->SetBacking(GetCurrentFEObject(pBackingName)); + widget->SetCheckIcon(GetCurrentFEImage("CHECK_ICON_")); + widget->SetTradeInString(GetCurrentFEString("TRADE_IN_")); + widget->SetTopLeft(vLastWidgetPos); + widget->SetMaxTitleSize(vMaxTitleSize); + widget->SetMaxDataSize(vMaxDataSize); + widget->SetDataPos(vDataPos); + Options.AddTail(widget); + iIndexToAdd++; + IncrementStartPos(); + widget->Show(); + widget->Draw(); + widget->Position(); + + float data_x, data_y; + float data_w, data_h; + FEngGetTopLeft(reinterpret_cast(widget->GetDataObject()), data_x, data_y); + FEngGetSize(reinterpret_cast(widget->GetDataObject()), data_w, data_h); + widget->SetWidth(bAbs(widget->GetTopLeftX() - (data_x + data_w))); +} + +// --- CustomizeCategoryScreen additional --- + +CustomizeCategoryScreen::CustomizeCategoryScreen(ScreenConstructorData *sd) : IconScrollerMenu(sd) // + , bBackingOut(false) // + , BackToPkg(nullptr) // + , HeatMeter() { + Category = sd->Arg & 0xFFFF; + FromCategory = static_cast(static_cast(sd->Arg >> 16)); + if (Category != 0 || !CustomizeIsInBackRoom()) { + GarageMainScreen::GetInstance()->SetCustomizationCategory(Category); + } + const char *meter_name = "HEAT_METER"; + const char *pkg = GetPackageName(); + float actual = gCarCustomizeManager.GetActualHeat(); + CustomizeMeter *meter = &HeatMeter; + float cart = gCarCustomizeManager.GetCartHeat(); + meter->Init(pkg, meter_name, 1.0f, 5.0f, actual, cart); +} + +int CustomizeCategoryScreen::AddCustomOption(const char *to_pkg, unsigned int tex_hash, unsigned int name_hash, unsigned int to_cat) { + if (gCarCustomizeManager.IsCareerMode()) { + if (CustomizeIsInBackRoom()) { + if (gCarCustomizeManager.IsCategoryLocked(to_cat, true)) { + return -1; + } + } + } + CustomizeMainOption *opt = new CustomizeMainOption(to_pkg, tex_hash, name_hash, to_cat, Category); + AddOption(opt); + if (gCarCustomizeManager.IsCategoryLocked(to_cat, false)) { + opt->UnlockStatus = CPS_LOCKED; + } else if (gCarCustomizeManager.IsCategoryNew(to_cat)) { + opt->UnlockStatus = CPS_NEW; + } + return Options.GetIndexToAdd() - 3; +} + +void CustomizeCategoryScreen::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0xb5af2461: + CustomizeShoppingCart::ShowShoppingCart(GetPackageName()); + break; + case 0xe1fde1d1: + if (!bBackingOut) { + break; + } + cFEng_mInstance->QueuePackageSwitch(BackToPkg, FromCategory | (Category << 16), 0, false); + break; + case 0x911ab364: { + bool leave = true; + if (Category <= 0x803) { + if (Category >= 0x801) { + if (gCarCustomizeManager.DoesCartHaveActiveParts()) { + cFEng_mInstance->QueueGameMessage(0x1720b124, GetPackageName(), 0xFF); + leave = false; + Options.bReactToInput = true; + } else { + gCarCustomizeManager.EmptyCart(); + gCarCustomizeManager.ResetPreview(); + } + } + } + if (leave) { + bBackingOut = true; + cFEng_mInstance->QueuePackageMessage(0x587c018b, GetPackageName(), nullptr); + } + break; + } + case 0xc519bfbf: { + CustomizeMainOption *opt = static_cast(Options.GetCurrentOption()); + Showcase::FromPackage = GetPackageName(); + Showcase::FromArgs = Category | (opt->Category << 16); + cFEng::Get()->QueuePackageSwitch(g_pCustomizeShowcasePkg, gCarCustomizeManager.EntryPoint, 0, false); + break; + } + case 0xb4edeb6d: + Options.bReactToInput = true; + break; + case 0x7a318ee0: + gCarCustomizeManager.EmptyCart(); + gCarCustomizeManager.ResetPreview(); + cFEng_mInstance->QueuePackageMessage(0x587c018b, GetPackageName(), nullptr); + break; + case 0x1720b124: + CustomizeShoppingCart::ShowShoppingCart(GetPackageName()); + break; + } +} + +// --- SetStockPartOption --- + +// SetStockPartOption::React is out-of-line (not inline) +void SetStockPartOption::React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) { + if (!ThePart->IsInstalled()) { + gCarCustomizeManager.AddToCart(ThePart); + ThePart->SetInCart(); + } +} + +// --- CustomizeMain additional --- + +void CustomizeMain::SetTitle(bool isInBackroom) { + char local_48[64]; + if (isInBackroom) { + const char *fmt = "%s"; + const char *str = GetLocalizedString(0x92fcdbf0); + bSNPrintf(local_48, 0x40, fmt, str); + } else { + const char *fmt = "%s"; + const char *str = GetLocalizedString(0x1f242e03); + bSNPrintf(local_48, 0x40, fmt, str); + } + int lang = GetCurrentLanguage(); + if (lang != 2 && lang != 0xd) { + int n = 0; + while (local_48[n] != 0) { + char c = local_48[n]; + if (static_cast(c - 0x41) < 0x1a) { + c = c | 0x20; + } + local_48[n] = c; + n++; + } + } + FEPrintf(GetPackageName(), 0xb71b576d, "%s", local_48); +} + +void CustomizeMain::RefreshHeader() { + CustomizeCategoryScreen::RefreshHeader(); + int isCareer = gCarCustomizeManager.IsCareerMode(); + if (isCareer && !gCarCustomizeManager.IsHeroCar()) { + int inBackRoom = CustomizeIsInBackRoom(); + if (!inBackRoom && gCarCustomizeManager.GetNumCustomizeMarkers() > 0) { + FEngSetVisible(FEngFindObject(GetPackageName(), 0xdc6ee739)); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xdc6ee739)); + } + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xdc6ee739)); + } + if (Options.GetCurrentOption()) { + gCarCustomizeManager.IsCategoryNew(static_cast(static_cast(Options.GetCurrentOption())->Category)); + } +} + +void CustomizeMain::SwitchRooms() { + bool newState = !CustomizeIsInBackRoom(); + CustomizeSetInBackRoom(newState); + SetTitle(newState); + int savedIdx = Options.GetCurrentIndex(); + if (newState) { + cFEng_mInstance->QueuePackageMessage(0xa1caff8d, GetPackageName(), nullptr); + FEManager::Get()->SetGarageType(static_cast(4)); + } else { + cFEng_mInstance->QueuePackageMessage(0x5c01c5, GetPackageName(), nullptr); + FEManager::Get()->SetGarageType(static_cast(3)); + } + SetScreenNames(); + Options.RemoveAll(); + Options.AddInitialBookEnds(); + BuildOptionsList(); + if (bFadeInIconsImmediately) { + Options.StartFadeIn(); + } + Options.SetInitialPos(savedIdx); + RefreshHeader(); +} + +void CustomizeMain::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (!gCarCustomizeManager.IsCareerMode() || msg != 0x911ab364) { + CustomizeCategoryScreen::NotificationMessage(msg, pobj, param1, param2); + } + switch (msg) { + case 0x1265ece9: { + GetInstance_GarageMainScreen()->UpdateCurrentCameraView(false); + if (CustomizeIsInBackRoom()) { + cFEng_mInstance->QueuePackageMessage(0xa1caff8d, GetPackageName(), nullptr); + } else { + cFEng_mInstance->QueuePackageMessage(0x5c01c5, GetPackageName(), nullptr); + } + break; + } + case 0x911ab364: + if (gCarCustomizeManager.IsCareerMode()) { + if (!CustomizeIsInBackRoom()) { + cFEng_mInstance->QueuePackageMessage(0x6d5d86a1, GetPackageName(), nullptr); + cFrontendDatabase *db = FEDatabase; + GarageMainScreen *gms = GetInstance_GarageMainScreen(); + *(unsigned int *)((char *)gms + 0x8c) = 0xFFFFFFFF; + char port = FEngMapJoyParamToJoyport(param1); + db->SetPlayersJoystickPort(0, port); + if (!db->IsCarStableDirty()) { + *(int *)((char *)MemoryCard_s_pThis + 0x78) = 1; + } + CarViewer_haveLoadedOnce = 0; + StartCareerFreeRoam(); + } else { + SwitchRooms(); + return; + } + } else { + cFEng_mInstance->QueuePackageMessage(0x6d5d86a1, GetPackageName(), nullptr); + } + gCarCustomizeManager.RelinquishControl(); + break; + case 0x34dc1bec: + if (gCarCustomizeManager.GetNumCustomizeMarkers() > invalidMarkers) { + SwitchRooms(); + } + invalidMarkers = 0; + break; + case 0xc519bfc4: + if (!gCarCustomizeManager.IsCareerMode() || gCarCustomizeManager.GetNumCustomizeMarkers() != 0) { + if (gCarCustomizeManager.IsCareerMode() && !CustomizeIsInBackRoom() && !gCarCustomizeManager.IsHeroCar()) { + invalidMarkers = 0; + if (TheFEMarkerManager.IsMarkerAvailable(FEMarkerManager::MARKER_INDUCTION, 0) && + !gCarCustomizeManager.CanInstallJunkman(Physics::Upgrades::kType_Brakes)) { + invalidMarkers++; + } + if (TheFEMarkerManager.IsMarkerAvailable(FEMarkerManager::MARKER_NOS, 0) && + !gCarCustomizeManager.CanInstallJunkman(Physics::Upgrades::kType_Induction)) { + invalidMarkers++; + } + if (invalidMarkers > 0) { + DialogInterface::ShowOneButton(GetPackageName(), g_pCustomizeDlgPkg, static_cast(2), + 0x417b2601, 0x34dc1bec, 0x3b3e83); + } else { + SwitchRooms(); + } + } + } + break; + } +} + +// --- Constructors --- + +CustomizeSub::CustomizeSub(ScreenConstructorData *sd) + : CustomizeCategoryScreen(sd) // + , InstalledPartOptionIndex(0) // + , InCartPartOptionIndex(0) // + , TitleHash(0) +{ + Setup(); + gCarCustomizeManager.ResetPreview(); +} + +CustomizePerformance::CustomizePerformance(ScreenConstructorData *sd) : CustomizationScreen(sd) { + Setup(); +} + +CustomizeRims::CustomizeRims(ScreenConstructorData *sd) + : CustomizationScreen(sd) // + , InnerRadius(0xf) // + , MinRadius(0xf) // + , MaxRadius(0xf) +{ + Setup(); +} + +CustomizeDecals::CustomizeDecals(ScreenConstructorData *sd) : CustomizationScreen(sd) // + , bIsBlack(true) +{ + Setup(); +} + +CustomizeHUDColor::CustomizeHUDColor(ScreenConstructorData *sd) : CustomizationScreen(sd) // + , SelectedColor(nullptr) // + , Cursor(nullptr) // + , bTexturesNeedUnload(false) +{ + Cursor = FEngFindObject(GetPackageName(), 0xB893252A); + Setup(); +} + +// --- CustomizePaint helpers --- + +struct CustomizePaintDatum : public ArrayDatum { + CustomizePaintDatum(SelectablePart *part, unsigned int unlock_blurb) + : ArrayDatum(0xc6afdd7e, 0) // + , ThePart(part) // + , UnlockBlurb(unlock_blurb) {} + + ~CustomizePaintDatum() override; + + SelectablePart *ThePart; // offset 0x24, size 0x4 + unsigned int UnlockBlurb; // offset 0x28, size 0x4 +}; + +SelectablePart *CustomizePaint::FindInCartPart() { + int count = ThePaints.GetNumDatum(); + for (int i = 0; i < count; i++) { + CustomizePaintDatum *datum = static_cast(ThePaints.GetDatumAt(i)); + SelectablePart *part = datum->ThePart; + if ((part->GetPartState() & 0xF0) == CPS_IN_CART) { + return part; + } + } + return nullptr; +} + +CustomizePartOption *CustomizePaint::FindMatchingOption(SelectablePart *to_find) { + int count = ThePaints.GetNumDatum(); + CustomizePaintDatum *found = nullptr; + for (int i = 0; i < count; i++) { + CustomizePaintDatum *datum = static_cast(ThePaints.GetDatumAt(i)); + if (datum->ThePart->GetPart() == to_find->GetPart()) { + found = datum; + break; + } + } + if (found) { + MatchingPaint.SetPart(found->ThePart); + return &MatchingPaint; + } else { + return nullptr; + } +} + +void CustomizePaint::SetupRimPaint() { + FEngSetInvisible(GetPackageName(), 0x2C3CC2D3); + FEngSetInvisible(GetPackageName(), 0x53639A10); + BuildSwatchList(0x4E); +} + +// --- CustomizeParts helpers --- + +void CustomizeParts::ShowHudObjects() { + FEngSetScript(GetPackageName(), 0xDEE8632B, 0x5079C8F8, true); + FEngSetVisible(FEngFindObject(GetPackageName(), 0xDEE8632B)); +} + +// --- CustomizeNumbers helpers --- + +void CustomizeNumbers::UnsetShoppingCart() { + SelectablePart *cur = LeftNumberList.GetHead(); + while (cur != reinterpret_cast(&LeftNumberList)) { + if ((cur->PartState & 0xF0) == CPS_IN_CART) { + cur->PartState = static_cast(cur->PartState & ~CPS_IN_CART); + break; + } + cur = static_cast(cur->Next); + } + cur = RightNumberList.GetHead(); + while (cur != reinterpret_cast(&RightNumberList)) { + if ((cur->PartState & 0xF0) == CPS_IN_CART) { + cur->PartState = static_cast(cur->PartState & ~CPS_IN_CART); + return; + } + cur = static_cast(cur->Next); + } +} + +// --- CustomizeMain additional --- + +extern const char *g_pCustomizeHudColorPkg; +extern const char *g_pCustomizeShoppingCartPkg; + +void CustomizeMain::SetScreenNames() { + if (CustomizeIsInBackRoom()) { + g_pCustomizeSubPkg = "CustomizeCategory_BACKROOM.fng"; + g_pCustomizeSubTopPkg = "CustomizeGenericTop_BACKROOM.fng"; + g_pCustomizePartsPkg = "CustomizeParts_BACKROOM.fng"; + g_pCustomizePerfPkg = "CustomizePerformance_BACKROOM.fng"; + g_pCustomizeDecalsPkg = "Decals_BACKROOM.fng"; + g_pCustomizePaintPkg = "Paint_BACKROOM.fng"; + g_pCustomizeRimsPkg = "Rims_BACKROOM.fng"; + g_pCustomizeHudColorPkg = "CustomHUDColor_BACKROOM.fng"; + g_pCustomizeShoppingCartPkg = "ShoppingCart_BACKROOM.fng"; + g_pCustomizeHudPkg = "CustomHUD_BACKROOM.fng"; + g_pCustomizeSpoilerPkg = "Spoilers_BACKROOM.fng"; + } else { + g_pCustomizeSubPkg = "CustomizeCategory.fng"; + g_pCustomizeSubTopPkg = "CustomizeGenericTop.fng"; + g_pCustomizePartsPkg = "CustomizeParts.fng"; + g_pCustomizePerfPkg = "CustomizePerformance.fng"; + g_pCustomizeDecalsPkg = "Decals.fng"; + g_pCustomizePaintPkg = "Paint.fng"; + g_pCustomizeRimsPkg = "Rims.fng"; + g_pCustomizeHudColorPkg = "CustomHUDColor.fng"; + if (gCarCustomizeManager.IsCareerMode()) { + g_pCustomizeShoppingCartPkg = "ShoppingCart.fng"; + } else { + g_pCustomizeShoppingCartPkg = "ShoppingCart_QR.fng"; + } + g_pCustomizeHudPkg = "CustomHUD.fng"; + g_pCustomizeSpoilerPkg = "Spoilers.fng"; + } +} + +void CustomizeMain::BuildOptionsList() { + int isHero = gCarCustomizeManager.IsHeroCar(); + if (!CustomizeIsInBackRoom()) { + if (!isHero) { + AddCustomOption(g_pCustomizeSubPkg, 0x6e0ca66c, 0x55dce1a, 0x801); + iPerfIndex = AddCustomOption(g_pCustomizeSubPkg, 0x3987d054, 0xbaef8282, 0x802); + } + AddCustomOption(g_pCustomizeSubPkg, 0x3e31ba56, 0xbfa7d7c4, 0x803); + } else { + if (!isHero) { + AddCustomOption(g_pCustomizeSubPkg, 0x73272ed2, 0x55dce1a, 0x801); + AddCustomOption(g_pCustomizeSubPkg, 0xc61c8d3a, 0xbaef8282, 0x802); + } + AddCustomOption(g_pCustomizeSubPkg, 0xe69d4f7c, 0xbfa7d7c4, 0x803); + } +} + +// --- CustomizeSpoiler --- + +void CustomizeSpoiler::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + CustomizationScreen::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0x5073ef13: + ScrollFilters(eSD_PREV); + break; + case 0xd9feec59: + ScrollFilters(eSD_NEXT); + break; + case 0x9120409e: + case 0xb5971bf1: + SelectedIndex[TheFilter] = Options.GetCurrentIndex(); + break; + case 0xc519bfbf: + Showcase::FromFilter = TheFilter; + break; + case 0x5a928018: { + SelectablePart *sel = FindInCartPart(); + if (!sel) { + return; + } + if (gCarCustomizeManager.IsPartInCart(sel)) { + return; + } + sel->PartState = static_cast(sel->PartState & 0xF); + RefreshHeader(); + break; + } + case 0x911ab364: + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeSubPkg, FromCategory | (Category << 16), 0, false); + break; + } +} + +void CustomizeSpoiler::Setup() { + SetTitleHash(0x94e73021); + FEImage *img1 = FEngFindImage(GetPackageName(), 0x91c4a50); + FEngSetButtonTexture(img1, 0x5bc); + FEImage *img2 = FEngFindImage(GetPackageName(), 0x2d145be3); + FEngSetButtonTexture(img2, 0x682); + CarPart *activePart = gCarCustomizeManager.GetActivePartFromSlot(0x2c); + if (Showcase::FromFilter != -1) { + TheFilter = Showcase::FromFilter; + Showcase::FromFilter = -1; + } else if (activePart) { + unsigned int filter = activePart->GetGroupNumber(); + if (filter != 4) { + TheFilter = filter; + } + } + BuildPartOptionListFromFilter(activePart); + RefreshHeader(); +} + +void CustomizeSpoiler::RefreshHeader() { + CustomizationScreen::RefreshHeader(); + int filter = TheFilter; + switch (filter) { + case 0: FEngSetLanguageHash(GetPackageName(), 0x78008599, 0x1f0e2b2); break; + case 1: FEngSetLanguageHash(GetPackageName(), 0x78008599, 0x205b328); break; + case 2: FEngSetLanguageHash(GetPackageName(), 0x78008599, 0x9912746); break; + case 3: FEngSetLanguageHash(GetPackageName(), 0x78008599, 0xe7416fc); break; + default: break; + } + SelectablePart *sel = GetSelectedPart(); + int elapsed = RealTimer.GetPackedTime() - ScrollTime.GetPackedTime(); + Timer scrollDelay; + scrollDelay.SetTime(0.3f); + if (elapsed > scrollDelay.GetPackedTime()) { + gCarCustomizeManager.PreviewPart(sel->GetSlotID(), sel->GetPart()); + } else { + bNeedsRefresh = true; + } + const char *lang_str = "LANGUAGEHASH"; + CarPart *part = sel->GetPart(); + if (part->HasAppliedAttribute(bStringHash(lang_str))) { + const char *pkg = GetPackageName(); + unsigned int langHash = sel->GetPart()->GetAppliedAttributeUParam(bStringHash(lang_str), 0); + FEngSetLanguageHash(pkg, 0x5e7b09c9, langHash); + } else { + FEPrintf(GetPackageName(), 0x5e7b09c9, "%s", sel->GetPart()->GetName()); + } +} + +void CustomizeSpoiler::BuildPartOptionListFromFilter(CarPart *activePart) { + Options.RemoveAll(); + int activeIdx = 1; + Options.AddInitialBookEnds(); + bTList partList; + gCarCustomizeManager.GetCarPartList(0x2c, partList, 0); + while (!partList.IsEmpty()) { + SelectablePart *cur = static_cast(partList.GetHead()); + cur->Remove(); + unsigned int groupNum = cur->GetPart()->GetGroupNumber(); + if (groupNum == static_cast(TheFilter) || groupNum == 4) { + unsigned int texHash = 0xbb034ea6; + unsigned int unlockHash = gCarCustomizeManager.GetUnlockHash(static_cast(Category), cur->GetUpgradeLevel()); + if (CustomizeIsInBackRoom()) { + texHash = 0xc51a4f62; + } + CarPart *cpart = cur->GetPart(); + if (cpart->GetAppliedAttributeIParam(bStringHash("SPOILER_TYPE"), 0) != 0) { + texHash = 0x4d1c18ba; + if (CustomizeIsInBackRoom()) { + texHash = 0x611d142a; + } + } + bool locked = gCarCustomizeManager.IsPartLocked(cur, 0); + AddPartOption(cur, texHash, cpart->GetUpgradeLevel(), 0, unlockHash, locked); + if (SelectedIndex[TheFilter] == 1) { + if (activePart && cur->GetPart() == activePart) { + SelectedIndex[TheFilter] = activeIdx; + } + activeIdx++; + } + } else { + delete cur; + } + } + if (Showcase::FromIndex == 0) { + Options.SetInitialPos(SelectedIndex[TheFilter]); + } else { + SelectedIndex[TheFilter] = Showcase::FromIndex; + Options.SetInitialPos(Showcase::FromIndex); + Showcase::FromIndex = 0; + } +} + +// --- CustomizePerformance --- + +void CustomizePerformance::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + CustomizationScreen::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0xe1fde1d1: + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeSubPkg, FromCategory | (Category << 16), 0, false); + break; + case 0x5a928018: { + SelectablePart *sel = FindInCartPart(); + if (!sel) return; + if (gCarCustomizeManager.IsPartInCart(sel)) return; + sel->PartState = static_cast(sel->PartState & CPS_GAME_STATE_MASK); + RefreshHeader(); + break; + } + case 0x911ab364: + cFEng_mInstance->QueuePackageMessage(0x587c018b, GetPackageName(), nullptr); + break; + } +} + +eMenuSoundTriggers CustomizePerformance::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + eMenuSoundTriggers toBeReturned = maybe; + if (toBeReturned == static_cast(4)) { + switch (GetCategory()) { + case 0x201: + toBeReturned = static_cast(0x31); + break; + case 0x202: + toBeReturned = static_cast(0x35); + break; + case 0x204: + toBeReturned = static_cast(0x34); + break; + case 0x205: + toBeReturned = static_cast(0x36); + break; + case 0x203: + case 0x206: + toBeReturned = static_cast(0x32); + break; + case 0x207: + toBeReturned = static_cast(0x33); + break; + } + } + return toBeReturned; +} + +void CustomizePerformance::Setup() { + if (!gCarCustomizeManager.IsCareerMode()) { + const unsigned long FEObj_QUICKRACE = 0xde511657; + cFEng::Get()->QueuePackageMessage(FEObj_QUICKRACE, GetPackageName(), nullptr); + } + + for (int i = 0; i < 3; i++) { + DescLines[i] = FEngFindString(GetPackageName(), FEngHashString("DETAIL_TEXT_LINE%d", i + 1)); + DescBullets[i] = FEngFindImage(GetPackageName(), FEngHashString("PERFORMANCE_DETAILS_ICON%d", i + 1)); + } + + AccelSlider.Init( + GetPackageName(), "ACCELERATION", 0.0f, 10.0f, 0.0f, + gCarCustomizeManager.GetPerformanceRating(PRT_ACCELERATION, true), + gCarCustomizeManager.GetPerformanceRating(PRT_ACCELERATION, false), 1.0f); + HandlingSlider.Init( + GetPackageName(), "HANDLING", 0.0f, 10.0f, 0.0f, + gCarCustomizeManager.GetPerformanceRating(PRT_HANDLING, true), + gCarCustomizeManager.GetPerformanceRating(PRT_HANDLING, false), 1.0f); + TopSpeedSlider.Init( + GetPackageName(), "TOPSPEED", 0.0f, 10.0f, 0.0f, + gCarCustomizeManager.GetPerformanceRating(PRT_TOP_SPEED, true), + gCarCustomizeManager.GetPerformanceRating(PRT_TOP_SPEED, false), 1.0f); + + Physics::Upgrades::Type type = Physics::Upgrades::kType_Tires; + switch (Category) { + case CC_ENGINE: + SetTitleHash(0x9853d9a6); + break; + case CC_TRANSMISSION: + type = Physics::Upgrades::kType_Nitrous; + SetTitleHash(0x29aa74ba); + break; + case CC_SUSPENSION: + type = Physics::Upgrades::kType_Chassis; + SetTitleHash(0x6e101aa7); + break; + case CC_NITROUS: + type = Physics::Upgrades::kType_Induction; + SetTitleHash(0x4ce19aa4); + break; + case CC_TIRES: + type = Physics::Upgrades::kType_Engine; + SetTitleHash(0x5aa9137); + break; + case CC_BRAKES: + type = Physics::Upgrades::kType_Transmission; + SetTitleHash(0x91997ee8); + break; + case CC_FORCED_INDUCTION: + type = Physics::Upgrades::kType_Brakes; + if (gCarCustomizeManager.IsTurbo()) { + SetTitleHash(0x5b1255c); + } else { + SetTitleHash(0xbb6812bb); + } + break; + } + + unsigned int icon_hash = 0xb8c8c0d4; + bTList part_list; + int j; + bool is_locked; + unsigned int desc_hash = 0; + SelectablePart *part; + + if (!gCarCustomizeManager.IsInBackRoom()) { + goto get_part_list; + } + if (!gCarCustomizeManager.IsCareerMode()) { + goto get_part_list; + } + if (gCarCustomizeManager.IsHeroCar()) { + goto get_part_list; + } + + { + unsigned int unlock_hash = 0; + if (!gCarCustomizeManager.IsInBackRoom()) { + unlock_hash = gCarCustomizeManager.GetUnlockHash(static_cast(Category), 7); + } + SelectablePart *new_part = new SelectablePart(nullptr, 0, 7, static_cast(static_cast(type)), true, CPS_AVAILABLE, 0, true); + AddPartOption(new_part, icon_hash, 7, desc_hash, unlock_hash, false); + if (gCarCustomizeManager.IsPartInstalled(new_part)) { + new_part->SetInstalled(); + } else if (gCarCustomizeManager.IsPartInCart(new_part)) { + new_part->SetInCart(); + } + goto after_initial_part_list; + } + +get_part_list: + gCarCustomizeManager.GetPerformancePartsList(type, part_list); + + after_initial_part_list: + for (j = 1;; j++) { + bNode *end = &part_list.HeadNode; + if (part_list.HeadNode.GetNext() == end) { + break; + } + SelectablePart *temp_part = static_cast(part_list.HeadNode.GetNext()); + bNode *next = temp_part->Next; + bNode *prev = temp_part->Prev; + prev->Next = next; + next->Prev = prev; + part = temp_part; + int unlock_level = gCarCustomizeManager.GetMaxPackages(type) - gCarCustomizeManager.GetNumPackages(type) + part->GetUpgradeLevel(); + unsigned int unlock_hash = gCarCustomizeManager.GetUnlockHash(static_cast(Category), unlock_level); + is_locked = gCarCustomizeManager.IsPartLocked(part, 0); + AddPartOption(part, icon_hash, j, desc_hash, unlock_hash, is_locked); + } + + if (((FEDatabase->GetCareerSettings()->HasBeenAwardedBKReward() && !FEDatabase->IsCareerMode()) || + (FEDatabase->GetUserProfile(0)->CareerModeHasBeenCompletedAtLeastOnce && !gCarCustomizeManager.IsHeroCar())) && + gCarCustomizeManager.CanInstallJunkman(type)) { + SelectablePart *new_part = new SelectablePart(nullptr, 0, 7, static_cast(static_cast(type)), true, CPS_AVAILABLE, 0, true); + AddPartOption(new_part, icon_hash, 7, desc_hash, 0, false); + if (gCarCustomizeManager.IsPartInstalled(new_part)) { + new_part->SetInstalled(); + } else if (gCarCustomizeManager.IsPartInCart(new_part)) { + new_part->SetInCart(); + } + } + + if (!gCarCustomizeManager.IsInBackRoom()) { + goto set_installed_option; + } + if (!gCarCustomizeManager.IsCareerMode()) { + goto set_installed_option; + } + SetInitialOption(1); + goto after_initial_option; + +set_installed_option: + { + int installed_index = gCarCustomizeManager.GetInstalledPerfPkg(type); + ShoppingCartItem *item = gCarCustomizeManager.IsPartTypeInCart(type); + if (item) { + installed_index = item->GetBuyingPart()->GetUpgradeLevel(); + } + SetInitialOption(installed_index); + } + +after_initial_option: + RefreshHeader(); +} + +void CustomizePerformance::RefreshHeader() { + int num_lines = 3; + + SelectablePart *sel = GetSelectedPart(); + int phys_type = sel->GetPhysicsType(); + int upg_level = GetSelectedPart()->GetUpgradeLevel(); + + gCarCustomizeManager.PreviewPerfPkg(static_cast(phys_type), upg_level); + + AccelSlider.SetValue(gCarCustomizeManager.GetPerformanceRating(PRT_ACCELERATION, true)); + HandlingSlider.SetValue(gCarCustomizeManager.GetPerformanceRating(PRT_HANDLING, true)); + TopSpeedSlider.SetValue(gCarCustomizeManager.GetPerformanceRating(PRT_TOP_SPEED, true)); + + AccelSlider.Draw(); + HandlingSlider.Draw(); + TopSpeedSlider.Draw(); + + sel = GetSelectedPart(); + phys_type = sel->GetPhysicsType(); + int level = GetSelectedPart()->GetUpgradeLevel(); + + int maxPkgs = gCarCustomizeManager.GetMaxPackages(static_cast(phys_type)); + int numPkgs = gCarCustomizeManager.GetNumPackages(static_cast(phys_type)); + int pkg_index = (maxPkgs - numPkgs) + level; + + if (CustomizeIsInBackRoom() || level == 7) { + level = 0; + pkg_index = 0; + num_lines = 1; + } + + int i = 0; + while (i < num_lines) { + int line_idx = i; + unsigned int desc_hash = GetPerfPkgDesc(static_cast(phys_type), pkg_index, line_idx + 1, gCarCustomizeManager.IsTurbo()); + i = line_idx + 1; + if (DoesStringExist(desc_hash)) { + FEngSetVisible(DescLines[line_idx]); + FEngSetVisible(DescBullets[line_idx]); + FEngSetLanguageHash(GetPackageName(), DescLines[line_idx]->NameHash, desc_hash); + } else { + FEngSetInvisible(DescLines[line_idx]); + FEngSetInvisible(DescBullets[line_idx]); + } + + Attrib::Gen::frontend inst(Attrib::FindCollection(0x85885722, gCarCustomizeManager.GetTuningCar()->FEKey), 0, nullptr); + + unsigned int brand_hash = GetPerfPkgBrand(static_cast(phys_type), pkg_index, line_idx); + unsigned int brand_icon_hash = FEngHashString("BRAND_ICON_%d", i); + + if (GetTextureInfo(brand_hash, 0, 0)) { + CustomizeFEngSetVisible(GetPackageName(), brand_icon_hash); + CustomizeFEngSetTextureHash(GetPackageName(), brand_icon_hash, brand_hash); + } else { + CustomizeFEngSetInvisible(GetPackageName(), brand_icon_hash); + } + } + + while (i < 3) { + int line_idx = i; + i = line_idx + 1; + FEngSetInvisible(DescLines[line_idx]); + FEngSetInvisible(DescBullets[line_idx]); + unsigned int icon_hash = FEngHashString("BRAND_ICON_%d", i); + CustomizeFEngSetInvisible(GetPackageName(), icon_hash); + } + + CustomizationScreen::RefreshHeader(); + + unsigned int level_hash; + if (GetSelectedPart()->GetUpgradeLevel() == 7) { + level_hash = 0xedd14807; + } else { + int num = gCarCustomizeManager.GetNumPackages(static_cast(phys_type)); + level_hash = FEngHashString("PN_LEVEL_%d", level - (num - 6)); + } + FEngSetLanguageHash(pOptionName, level_hash); +} + +HUDLayerOption::HUDLayerOption(unsigned int layer, unsigned int icon_hash, unsigned int name_hash) + : CustomizePartOption(nullptr, icon_hash, name_hash, 0, 0) // + , HUDLayer(layer) // + , SelectedPart(nullptr) { + gCarCustomizeManager.GetCarPartList(layer, TheColors, 0); +} + +CustomizeHUDColor::~CustomizeHUDColor() { + if (CustomizeParts::TexturePackLoaded && bTexturesNeedUnload) { + UnLoadCustomHUDPacksAndTextures(); + } +} + +void CustomizeHUDColor::AddLayerOption(unsigned int layer, unsigned int icon_hash, unsigned int name_hash) { + HUDLayerOption *opt = new HUDLayerOption(layer, icon_hash, name_hash); + AddOption(opt); +} + +void CustomizeHUDColor::Setup() { + DisplayHelper.TitleHash = 0xb1b0e8af; + AddLayerOption(0x86, 0x70f56628, 0xe18ddce1); + AddLayerOption(0x87, 0xbf6682c9, 0xe18ddce0); + AddLayerOption(0x85, 0xcc9e1ce4, 0xe18ddcdf); + if (Showcase::FromIndex) { + SetInitialOption(Showcase::FromIndex); + Showcase::FromIndex = 0; + } else { + SetInitialOption(1); + } + BuildColorOptions(); + SetHUDTextures(); + SetInitialColors(); + RefreshHeader(); +} + + +// --- CustomizeParts --- + +void CustomizeParts::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (msg != 0x406415e3) { + CustomizationScreen::NotificationMessage(msg, pobj, param1, param2); + } + switch (msg) { + case 0x406415e3: + if (Category == 0x307) { + if (!TexturePackLoaded) { + return; + } + SelectablePart *sel = GetSelectedPart(); + if (sel && (sel->GetPartState() & CPS_GAME_STATE_MASK) == CPS_LOCKED) { + DisplayHelper.FlashStatusIcon(CPS_LOCKED, true); + return; + } + if (gCarCustomizeManager.GetTempColoredPart()) { + gCarCustomizeManager.ClearTempColoredPart(); + } + gCarCustomizeManager.SetTempColoredPart(new SelectablePart(sel)); + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeHudColorPkg, Category | (FromCategory << 16), 0, false); + } else if (Category >= 0x402 && Category <= 0x409) { + SelectablePart *sel = GetSelectedPart(); + if (sel && (sel->GetPartState() & CPS_GAME_STATE_MASK) == CPS_LOCKED) { + DisplayHelper.FlashStatusIcon(CPS_LOCKED, true); + return; + } + unsigned int tunable = sel->GetPart()->GetAppliedAttributeUParam(0x6212682b, 0); + if (tunable == 0) { + CustomizationScreen::NotificationMessage(0x406415e3, pobj, param1, param2); + return; + } + gCarCustomizeManager.SetTempColoredPart(new SelectablePart(sel)); + cFEng_mInstance->QueuePackageSwitch(g_pCustomizePaintPkg, Category | (FromCategory << 16), 0, false); + } else { + CustomizationScreen::NotificationMessage(0x406415e3, pobj, param1, param2); + return; + } + break; + case 0xcf91aacd: + if (Category != 0x307) { + return; + } + if (!TexturePackLoaded) { + return; + } + bTexturesNeedUnload = true; + break; + case 0x5a928018: { + SelectablePart *sel = FindInCartPart(); + if (!sel) { + return; + } + if (gCarCustomizeManager.IsPartInCart(sel)) { + return; + } + sel->SetPartState(sel->GetPartState() & CPS_GAME_STATE_MASK); + RefreshHeader(); + break; + } + case 0x911ab364: + if (Category == 0x307) { + if (!TexturePackLoaded) { + return; + } + bTexturesNeedUnload = true; + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeSubPkg, FromCategory | (0x307 << 16), 0, false); + } else if (Category >= 0x402 && Category <= 0x409) { + gCarCustomizeManager.ClearTempColoredPart(); + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeSubTopPkg, Category | (FromCategory << 16), 0, false); + } else { + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeSubPkg, FromCategory | (Category << 16), 0, false); + } + break; + } +} + +void CustomizeParts::Setup() { + unsigned int icon_hash = 0; + int car_slot_id = 0; + unsigned int vinyl_group_number = 0; + bool is_vinyl = false; + CarPart *installed_part = nullptr; + bool part_found = false; + + switch (Category) { + case 0x101: + SetTitleHash(0x6134c218); + icon_hash = 0x28c24f6; + if (CustomizeIsInBackRoom()) { + icon_hash = 0xaf393dba; + } + car_slot_id = 0x17; + goto after_switch; + case 0x104: + SetTitleHash(0x4d4a88d); + icon_hash = 0x28f7092; + if (CustomizeIsInBackRoom()) { + icon_hash = 0xf375276e; + } + car_slot_id = 0x3f; + goto after_switch; + case 0x105: + SetTitleHash(0x61e8f83c); + icon_hash = 0x79165861; + if (CustomizeIsInBackRoom()) { + icon_hash = 0x25a4375e; + } + car_slot_id = 0x3e; + goto after_switch; + case 0x307: + if (!CustomizeParts::TexturePackLoaded) { + cFEng::Get()->QueuePackageMessage(0x13fd3296, GetPackageName(), nullptr); + LoadHudTextures(); + } else { + ShowHudObjects(); + cFEng::Get()->QueuePackageMessage(0x8cb81f09, GetPackageName(), nullptr); + } + if (gCarCustomizeManager.GetTempColoredPart()) { + installed_part = gCarCustomizeManager.GetTempColoredPart()->GetPart(); + part_found = true; + } + SetTitleHash(0x78980a6b); + icon_hash = 0x28f88bc; + if (CustomizeIsInBackRoom()) { + icon_hash = 0x8ba602fc; + } + car_slot_id = 0x84; + goto after_switch; + case 0x304: + SetTitleHash(0xd32729a6); + icon_hash = 0x3f23165c; + car_slot_id = 0x83; + goto after_switch; + case 0x402: + SetTitleHash(0xd9228fc6); + icon_hash = 0xf8148554; + car_slot_id = 0x4d; + vinyl_group_number = 0; + goto set_vinyl; + case 0x403: + SetTitleHash(0x1e8d885f); + icon_hash = 0x192d84da; + car_slot_id = 0x4d; + vinyl_group_number = 1; + goto set_vinyl; + case 0x404: + SetTitleHash(0x1c619fd8); + icon_hash = 0xf7352706; + car_slot_id = 0x4d; + vinyl_group_number = 2; + goto set_vinyl; + case 0x405: + SetTitleHash(0x9c1b8935); + icon_hash = 0x1223cc89; + car_slot_id = 0x4d; + vinyl_group_number = 3; + goto set_vinyl; + case 0x406: + SetTitleHash(0x7956f7b0); + icon_hash = 0xbc44bbcb; + car_slot_id = 0x4d; + vinyl_group_number = 4; + goto set_vinyl; + case 0x407: + SetTitleHash(0x2d5bff0f); + icon_hash = 0x694ca0ca; + car_slot_id = 0x4d; + vinyl_group_number = 5; + goto set_vinyl; + case 0x408: + SetTitleHash(0x209a9158); + icon_hash = 0x1b3a8dd3; + car_slot_id = 0x4d; + vinyl_group_number = 6; + goto set_vinyl; + case 0x409: + SetTitleHash(0xcd057d21); + icon_hash = 0x1ba508fc; + car_slot_id = 0x4d; + vinyl_group_number = 7; + goto set_vinyl; + default: + goto after_switch; + } + +set_vinyl: + is_vinyl = true; + +after_switch: + if (is_vinyl && gCarCustomizeManager.GetTempColoredPart()) { + installed_part = gCarCustomizeManager.GetTempColoredPart()->GetPart(); + part_found = true; + } + if (!part_found) { + installed_part = gCarCustomizeManager.GetActivePartFromSlot(car_slot_id); + } + + bTList part_list; + if (is_vinyl) { + gCarCustomizeManager.GetCarPartList(car_slot_id, part_list, vinyl_group_number); + } else { + gCarCustomizeManager.GetCarPartList(car_slot_id, part_list, 0); + } + + int installed_index; + int current_part_index; + unsigned int original_icon_hash; + SelectablePart *part; + installed_index = 0; + current_part_index = 1; + original_icon_hash = icon_hash; + part = part_list.GetHead(); + + while (!part_list.IsEmpty()) { + part = part_list.RemoveHead(); + unsigned int unlock_hash = gCarCustomizeManager.GetUnlockHash(static_cast(Category), part->GetUpgradeLevel()); + + if (is_vinyl) { + CarPart *cpart = part->GetPart(); + unsigned char gl = cpart->GroupNumber_UpgradeLevel; + if ((gl & 0x1f) == vinyl_group_number) { + unsigned int part_name_hash = static_cast(cpart->PartNameHashTop) << 16 | cpart->PartNameHashBot; + if (UnlockSystem::IsUnlockableAvailable(part_name_hash)) { + unsigned int upgrade_level = gl >> 5; + bool locked = gCarCustomizeManager.IsPartLocked(part, 0); + AddPartOption(part, icon_hash, upgrade_level, 0, unlock_hash, locked); + } else { + delete part; + part = nullptr; + } + } else { + delete part; + part = nullptr; + } + } else { + CarPart *cpart = part->GetPart(); + if (cpart->HasAppliedAttribute(bStringHash("CARBONFIBRE"))) { + int cfVal = cpart->GetAppliedAttributeIParam(bStringHash("CARBONFIBRE"), 0); + if (cfVal != 0) { + if (Category != 0x104) { + if (Category == 0x105) { + if (CustomizeIsInBackRoom()) { + icon_hash = 0xcd6b4e26; + } else { + icon_hash = 0xfc618215; + } + } else { + icon_hash = original_icon_hash; + } + } else { + if (CustomizeIsInBackRoom()) { + icon_hash = 0x2478e136; + } else { + icon_hash = 0x68495926; + } + } + } else { + icon_hash = original_icon_hash; + } + } else { + icon_hash = original_icon_hash; + } + unsigned int upgrade_level = cpart->GroupNumber_UpgradeLevel >> 5; + bool locked = gCarCustomizeManager.IsPartLocked(part, 0); + AddPartOption(part, icon_hash, upgrade_level, 0, unlock_hash, locked); + } + if (part) { + if (installed_part && part->GetPart() == installed_part) { + installed_index = current_part_index; + } + current_part_index++; + } + } + + if (Showcase::FromIndex != 0) { + SetInitialOption(Showcase::FromIndex); + Showcase::FromIndex = 0; + } else { + SetInitialOption(installed_index); + } + RefreshHeader(); + + while (!part_list.IsEmpty()) { + delete part_list.RemoveHead(); + } +} + +void CustomizeParts::RefreshHeader() { + CustomizationScreen::RefreshHeader(); + int numOpts = Options.Options.TraversebList(nullptr); + if (numOpts != Options.iNumBookEnds) { + SelectablePart *sel = GetSelectedPart(); + if (sel->GetPart()->HasAppliedAttribute(0x6212682b)) { + unsigned int tunable = sel->GetPart()->GetAppliedAttributeUParam(0x6212682b, 0); + if (tunable) { + FEngSetLanguageHash(GetPackageName(), 0xb94139f4, 0x8098a54c); + } else { + FEngSetLanguageHash(GetPackageName(), 0xb94139f4, 0x649f4a65); + } + } + if (Category == 0x307) { + SetHUDTextures(); + SetHUDColors(); + } else { + if ((RealTimer - ScrollTime).GetSeconds() > 0.3f) { + gCarCustomizeManager.PreviewPart(sel->GetSlotID(), sel->GetPart()); + } else { + bNeedsRefresh = true; + } + } + const char *lang_str = "LANGUAGEHASH"; + if (sel->GetPart()->HasAppliedAttribute(bStringHash(lang_str))) { + const char *pkg = GetPackageName(); + unsigned int langHash = sel->GetPart()->GetAppliedAttributeUParam(bStringHash(lang_str), 0); + FEngSetLanguageHash(pkg, 0x5e7b09c9, langHash); + } else { + FEPrintf(GetPackageName(), 0x5e7b09c9, "%s", sel->GetPart()->GetName()); + } + } +} + +// --- CustomizeMeter --- + +CustomizeMeter::CustomizeMeter() + : Min(0.0f) // + , Max(1.0f) // + , Current(0.0f) // + , Preview(0.0f) // + , PreviousPreview(0.0f) // + , NumStages(5) // + , pMultiplier(nullptr) // + , pMeterGroup(nullptr) // +{ + for (int i = 0; i < 10; i++) { + pBases[i] = nullptr; + } +} + +void CustomizeMeter::Init(const char *pkg_name, const char *name, float min, float max, float current, float preview) { + Min = min; + Max = max; + SetCurrent(current); + SetPreview(preview); + pMultiplier = FEngFindImage(pkg_name, 0x5ffee1d8); + pMultiplierZoom = FEngFindImage(pkg_name, 0xe637955c); + pMeterGroup = FEngFindObject(pkg_name, 0xf2492598); + for (int i = 0; i < 10; i++) { + unsigned int hash = FEngHashString("METER_BASE_%d", i + 1); + pBases[i] = FEngFindImage(pkg_name, hash); + } +} + +void CustomizeMeter::Draw() { + float stage_size = 1.0f; + float multiplier = 5.0f; + float stage_bottom = Min; + while (Preview - stage_bottom >= stage_size) { + stage_bottom = stage_bottom + stage_size; + multiplier = multiplier + stage_size; + } + float heat_level = bMin(multiplier, 5.0f); + unsigned int hash = FEngHashString("HEAT_X%.0f", heat_level); + FEngSetTextureHash(pMultiplier, hash); + hash = FEngHashString("HEAT_X%.0f", heat_level); + FEngSetTextureHash(pMultiplierZoom, hash); + if (Preview != PreviousPreview) { + FEngSetScript(pMultiplierZoom, 0x209c24, true); + } + float segment_bottom = stage_bottom + stage_size; + float segment_size = 0.1f; + if (stage_bottom <= segment_bottom) { + int cur_icon = 10; + do { + float current_segment = segment_bottom - stage_size * segment_size; + segment_bottom = current_segment; + int icon_idx = cur_icon - 1; + unsigned int script; + if (current_segment + 0.0005f < Current) { + script = 0x63c; + if (Preview <= current_segment + 0.0005f) { + script = 0x13ff6c; + } + } else { + script = 0xccfa; + } + FEngSetScript(pBases[cur_icon - 1], script, true); + if (stage_bottom > current_segment || icon_idx <= -1) break; + cur_icon = icon_idx; + } while (true); + } +} + +// --- CustomizeSub --- + +int CustomizeSub::GetRimBrandIndex(unsigned int brand) { + switch (brand) { + case 0x352d08d1: return 2; + case 0x9136: return 3; + case 0x9536: return 4; + case 0x2b77feb: return 5; + case 0x324ac97: return 6; + case 0x48e25793: return 7; + case 0xdd544a02: return 8; + case 0x648: return 9; + case 0x1e6a3b: return 10; + case 0x1c386b: return 0xb; + default: return 1; + } +} + +void CustomizeSub::SetupRimBrands() { + BackToPkg = g_pCustomizeSubPkg; + TitleHash = 0xe032d89e; + CarPart *stockCarPart = gCarCustomizeManager.GetStockCarPart(0x42); + SelectablePart *stockPart = new SelectablePart( + stockCarPart, 0x42, 0, static_cast(7), false, CPS_AVAILABLE, 0, false); + if (gCarCustomizeManager.IsPartInstalled(stockPart)) { + stockPart->SetPartState(CPS_INSTALLED); + } + SetStockPartOption *stockOpt = new SetStockPartOption(stockPart, 0xf3990b6, 0x701); + AddOption(stockOpt); + + AddCustomOption(g_pCustomizeRimsPkg, 0xb0da3de4, 0x56b51a0e, 0x702); + AddCustomOption(g_pCustomizeRimsPkg, 0xf224a729, 0xf93f2d34, 0x703); + AddCustomOption(g_pCustomizeRimsPkg, 0xf224ab29, 0xf93f3134, 0x704); + AddCustomOption(g_pCustomizeRimsPkg, 0xe38de9e, 0x460d1369, 0x705); + AddCustomOption(g_pCustomizeRimsPkg, 0xea60b4a, 0x467a4015, 0x706); + AddCustomOption(g_pCustomizeRimsPkg, 0xafc6b9cb, 0x9bb17a11, 0x707); + AddCustomOption(g_pCustomizeRimsPkg, 0x27ebd095, 0xcca3063f, 0x708); + AddCustomOption(g_pCustomizeRimsPkg, 0x6c2fa9db, 0xc1bc1a86, 0x709); + AddCustomOption(g_pCustomizeRimsPkg, 0x36c53e8e, 0x213085f9, 0x70a); + AddCustomOption(g_pCustomizeRimsPkg, 0x36c2d130, 0x212e5429, 0x70b); + + ShoppingCartItem *inCart = gCarCustomizeManager.IsPartTypeInCart(static_cast(0x42)); + if (inCart) { + InCartPartOptionIndex = GetRimBrandIndex(inCart->GetBuyingPart()->GetPart()->GetAppliedAttributeUParam(0xebb03e66, 0)); + } + CarPart *installed = gCarCustomizeManager.GetInstalledCarPart(0x42); + if (installed) { + InstalledPartOptionIndex = GetRimBrandIndex(installed->GetAppliedAttributeUParam(0xebb03e66, 0)); + } + + if (FromCategory == 0x801) { + int pos = InCartPartOptionIndex; + if (pos == 0) { + pos = InstalledPartOptionIndex; + } + if (pos != 0) { + SetInitialOption(pos); + } else { + SetInitialOption(1); + } + } else { + SetInitialOption(FromCategory & 0xFFFF00FF); + } + if (FromCategory - 0x701u < 0xbu) { + FromCategory = 0x801; + } +} + +void CustomizeSub::SetupVinylGroups() { + TitleHash = 0xda129752; + BackToPkg = g_pCustomizeSubPkg; + SelectablePart *stockPart = new SelectablePart( + static_cast(nullptr), 0x4d, 0, + static_cast(7), false, CPS_AVAILABLE, 0, false); + if (gCarCustomizeManager.IsPartInstalled(stockPart)) { + stockPart->SetPartState(CPS_INSTALLED); + } + SetStockPartOption *stockOpt = new SetStockPartOption(stockPart, 0x21f3d114, 0x401); + AddOption(stockOpt); + + AddCustomOption(g_pCustomizePartsPkg, 0xf8148554, 0xd9228fc6, 0x402); + AddCustomOption(g_pCustomizePartsPkg, 0x192d84da, 0x1e8d885f, 0x403); + AddCustomOption(g_pCustomizePartsPkg, 0xf7352706, 0x1c619fd8, 0x404); + AddCustomOption(g_pCustomizePartsPkg, 0x1223cc89, 0x9c1b8935, 0x405); + AddCustomOption(g_pCustomizePartsPkg, 0xbc44bbcb, 0x7956f7b0, 0x406); + AddCustomOption(g_pCustomizePartsPkg, 0x694ca0ca, 0x2d5bff0f, 0x407); + AddCustomOption(g_pCustomizePartsPkg, 0x1b3a8dd3, 0x209a9158, 0x408); + AddCustomOption(g_pCustomizePartsPkg, 0x1ba508fc, 0xcd057d21, 0x409); + + ShoppingCartItem *inCart = gCarCustomizeManager.IsPartTypeInCart(static_cast(0x4d)); + if (inCart && inCart->GetBuyingPart()) { + CarPart *part = inCart->GetBuyingPart()->GetPart(); + if (part) { + InCartPartOptionIndex = GetVinylGroupIndex(part->GetGroupNumber()); + } else { + InCartPartOptionIndex = 1; + } + } + CarPart *installed = gCarCustomizeManager.GetInstalledCarPart(0x4d); + if (installed) { + InstalledPartOptionIndex = GetVinylGroupIndex(installed->GetGroupNumber()); + } else { + InstalledPartOptionIndex = 1; + } + + if (FromCategory == 0x803) { + if (InCartPartOptionIndex != 0) { + SetInitialOption(InCartPartOptionIndex); + } else if (InstalledPartOptionIndex != 0) { + SetInitialOption(InstalledPartOptionIndex); + } else { + SetInitialOption(1); + } + } else { + SetInitialOption(FromCategory & 0xFFFF00FF); + } + if (FromCategory - 0x401u < 9u) { + FromCategory = 0x803; + } +} + +// --- CustomizeHUDColor --- + +void CustomizeHUDColor::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (msg == 0x9120409e || msg == 0xb5971bf1) { + HUDLayerOption *layer = static_cast(Options.GetCurrentOption()); + layer->SelectedPart = SelectedColor->ThePart; + } + if (msg != 0x91dfdf84) { + CustomizationScreen::NotificationMessage(msg, pobj, param1, param2); + } + switch (msg) { + case 0x9120409e: + case 0xb5971bf1: + BuildColorOptions(); + RefreshHeader(); + break; + case 0x72619778: + ScrollColors(eSD_PREV); + break; + case 0x911c0a4b: + ScrollColors(eSD_NEXT); + break; + case 0x91dfdf84: { + SelectablePart *temp = gCarCustomizeManager.GetTempColoredPart(); + ShoppingCartItem *inCart = gCarCustomizeManager.IsPartTypeInCart(0x84u); + if (inCart && temp->ThePart == inCart->GetBuyingPart()->ThePart) { + int slot = 0x85; + do { + ShoppingCartItem *colorItem = gCarCustomizeManager.IsPartTypeInCart(static_cast(slot)); + slot++; + gCarCustomizeManager.RemoveFromCart(colorItem); + } while (slot < 0x88); + } + gCarCustomizeManager.AddToCart(temp); + gCarCustomizeManager.ClearTempColoredPart(); + HUDColorOption *node = static_cast(ColorOptions.GetHead()); + while (node != reinterpret_cast(&ColorOptions)) { + if (node->ThePart->PartState != 0) { + gCarCustomizeManager.AddToCart(node->ThePart); + } + node = static_cast(node->Next); + } + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeHudPkg, Category | (FromCategory << 16), 0, false); + break; + } + case 0x911ab364: + gCarCustomizeManager.ClearTempColoredPart(); + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeHudPkg, Category | (FromCategory << 16), 0, false); + break; + case 0xcf91aacd: + gCarCustomizeManager.ClearTempColoredPart(); + bNeedsRefresh = true; + break; + } +} + +void CustomizeHUDColor::ScrollColors(eScrollDir dir) { + HUDColorOption *prev = SelectedColor; + if (dir == eSD_PREV) { + HUDColorOption *node = static_cast(prev->Prev); + if (node == reinterpret_cast(&ColorOptions)) { + node = static_cast(ColorOptions.GetTail()); + } + SelectedColor = node; + } else if (dir == eSD_NEXT) { + HUDColorOption *node = static_cast(prev->Next); + if (node == reinterpret_cast(&ColorOptions)) { + node = static_cast(ColorOptions.GetHead()); + } + SelectedColor = node; + } + if (SelectedColor != prev) { + HUDLayerOption *layer = static_cast(Options.GetCurrentOption()); + layer->SelectedPart = SelectedColor->ThePart; + FEngSetScript(prev->FEngObject, 0x7ab5521a, true); + FEngSetScript(SelectedColor->FEngObject, 0x249db7b7, true); + float x, y; + FEngGetTopLeft(SelectedColor->FEngObject, x, y); + float dx, dy; + FEngGetTopLeft(SelectedColor->FEngObject, dx, dy); + FEngSetTopLeft(Cursor, x + dx, y + dy); + RefreshHeader(); + } +} + +void CustomizeHUDColor::RefreshHeader() { + CustomizationScreen::RefreshHeader(); + HUDColorOption *sel = SelectedColor; + switch (sel->ThePart->CarSlotID) { + case 0x85: { + unsigned int color = sel->color; + FEngSetColor(FEngFindObject(GetPackageName(), 0x5d19f25), color); + break; + } + case 0x87: { + unsigned int color = sel->color; + FEngSetColor(FEngFindObject(GetPackageName(), 0xc0721eb9), color); + color = SelectedColor->color; + FEngSetColor(FEngFindObject(GetPackageName(), 0xc62ad685), color); + color = SelectedColor->color; + FEngSetColor(FEngFindObject(GetPackageName(), 0xb8f1f802), color); + break; + } + case 0x86: { + unsigned int color = sel->color; + FEngSetColor(FEngFindObject(GetPackageName(), 0xd312f0cb), color); + color = SelectedColor->color; + FEngSetColor(FEngFindObject(GetPackageName(), 0x8fe2a217), color); + break; + } + } +} + +void CustomizeHUDColor::BuildColorOptions() { + if (SelectedColor) { + FEngSetScript(SelectedColor->FEngObject, 0x7ab5521a, true); + SelectedColor = nullptr; + } + HUDLayerOption *opt = static_cast(Options.GetCurrentOption()); + if (opt && !opt->TheColors.IsEmpty()) { + ColorOptions.DeleteAllElements(); + int i = 0; + ShoppingCartItem *cart_item = gCarCustomizeManager.IsPartTypeInCart(0x84u); + CarPart *installed_hud = gCarCustomizeManager.GetInstalledCarPart(0x84); + SelectablePart *part = opt->TheColors.GetHead(); + while (part != opt->TheColors.EndOfList()) { + i++; + HUDColorOption *color_option = new HUDColorOption(part); + FEImage *obj = FEngFindImage(GetPackageName(), FEngHashString("COLOR_%d", i)); + color_option->SetFEngObject(obj); + ColorOptions.AddTail(color_option); + unsigned char r = part->GetPart()->GetAppliedAttributeIParam(bStringHash("RED"), 0); + unsigned char g = part->GetPart()->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + unsigned char b = part->GetPart()->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + unsigned int color = static_cast(g) << 8; + color |= 0xFF000000; + color |= static_cast(r) << 16; + color |= static_cast(b); + color_option->color = color; + FEngSetColor(obj, color); + if (!opt->SelectedPart) { + if (cart_item && gCarCustomizeManager.GetTempColoredPart()->GetPart() == cart_item->GetBuyingPart()->GetPart()) { + if (gCarCustomizeManager.IsPartInCart(part)) { + SelectedColor = color_option; + opt->SelectedPart = part; + } + } else if (gCarCustomizeManager.GetTempColoredPart()->GetPart() == installed_hud) { + if (gCarCustomizeManager.IsPartInstalled(part)) { + SelectedColor = color_option; + opt->SelectedPart = part; + } + } + } else if (opt->SelectedPart == part) { + SelectedColor = color_option; + } + part = part->GetNext(); + } + if (!SelectedColor) { + SelectedColor = ColorOptions.GetHead(); + } + float x_offset = 69.0f; + float y_offset = 56.0f; + FEObject *cursor_obj = Cursor; + float x = FEngGetTopLeftX(SelectedColor->FEngObject); + x += x_offset; + float y = FEngGetTopLeftY(SelectedColor->FEngObject); + FEngSetTopLeft(cursor_obj, x, y + y_offset); + } +} + +// --- CustomizeParts --- + +void CustomizeParts::TexturePackLoadedCallback() { + int idx = PacksLoadedCount; + int offset = idx * 5; + PacksLoadedCount = idx + 1; + CustomizeHUDTexTextureResources[offset] = FEngHashString("HUD_GAUGE_%02d", idx); + CustomizeHUDTexTextureResources[offset + 1] = FEngHashString("HUD_NEEDLE_%d_%02d", TachRPM, idx); + CustomizeHUDTexTextureResources[offset + 2] = FEngHashString("HUD_NEEDLE_TURBO_%02d", idx); + CustomizeHUDTexTextureResources[offset + 3] = FEngHashString("HUD_SPEEDOMETER_%02d", idx); + CustomizeHUDTexTextureResources[offset + 4] = FEngHashString("HUD_NOS_%02d", idx); + eLoadStreamingTexture(reinterpret_cast(&CustomizeHUDTexTextureResources[offset]), 5, + reinterpret_cast(TextureLoadedCallbackAccessor), + reinterpret_cast(reinterpret_cast(this)), 0); +} + +void CustomizeParts::SetHUDTextures() { + SelectablePart *sel = GetSelectedPart(); + CarPart *part = sel->ThePart; + unsigned int hudStyleHash = FEngHashString("HUD_STYLE"); + int hudStyle = part->GetAppliedAttributeIParam(hudStyleHash, 0); + const char *pkg = GetPackageName(); + unsigned int hash = FEngHashString("HUD_NEEDLE_%d_%02d", TachRPM, hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0xc0721eb9), hash); + pkg = GetPackageName(); + hash = FEngHashString("HUD_GAUGE_%02d", hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0x5d19f25), hash); + pkg = GetPackageName(); + hash = FEngHashString("HUD_SPEEDOMETER_%02d", hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0xd312f0cb), hash); + if (gCarCustomizeManager.IsTurbo()) { + pkg = GetPackageName(); + hash = FEngHashString("HUD_NEEDLE_TURBO_%02d", hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0xc62ad685), hash); + pkg = GetPackageName(); + hash = FEngHashString("HUD_NOS_%02d", hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0x8fe2a217), hash); + FEngSetVisible(FEngFindObject(GetPackageName(), 0xc5d551b7)); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xc5d551b7)); + } +} + +void CustomizeParts::SetHUDColors() { + ShoppingCartItem *hudInCart = gCarCustomizeManager.IsPartTypeInCart(0x84u); + CarPart *installedHud = gCarCustomizeManager.GetInstalledCarPart(0x84); + SelectablePart *sel = GetSelectedPart(); + if (sel->ThePart != installedHud) { + if (!hudInCart) goto not_installed; + SelectablePart *selPart = GetSelectedPart(); + if (selPart->ThePart != hudInCart->GetBuyingPart()->ThePart) + goto not_installed; + } + { + unsigned int colors[3]; + int slot = 0x85; + int idx = 0; + do { + ShoppingCartItem *colorInCart = gCarCustomizeManager.IsPartTypeInCart(static_cast(slot)); + CarPart *colorPart; + if (colorInCart && hudInCart) { + SelectablePart *selPart = GetSelectedPart(); + if (selPart->ThePart == hudInCart->GetBuyingPart()->ThePart) { + colorPart = colorInCart->GetBuyingPart()->ThePart; + } else { + colorPart = gCarCustomizeManager.GetInstalledCarPart(slot); + } + } else { + colorPart = gCarCustomizeManager.GetInstalledCarPart(slot); + } + unsigned char r = colorPart->GetAppliedAttributeIParam(bStringHash("RED"), 0); + unsigned char g = colorPart->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + unsigned int b = colorPart->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + colors[idx] = (static_cast(r) << 16) | (static_cast(g) << 8) | 0xff000000 | (b & 0xff); + slot++; + idx++; + } while (idx < 3); + FEngSetColor(FEngFindObject(GetPackageName(), 0x5d19f25), colors[0]); + FEngSetColor(FEngFindObject(GetPackageName(), 0xc0721eb9), colors[2]); + FEngSetColor(FEngFindObject(GetPackageName(), 0xc62ad685), colors[2]); + FEngSetColor(FEngFindObject(GetPackageName(), 0xb8f1f802), colors[2]); + FEngSetColor(FEngFindObject(GetPackageName(), 0xd312f0cb), colors[1]); + FEngSetColor(FEngFindObject(GetPackageName(), 0x8fe2a217), colors[1]); + return; + } +not_installed: + FEngSetColor(FEngFindObject(GetPackageName(), 0x5d19f25), 0xffffc373u); + FEngSetColor(FEngFindObject(GetPackageName(), 0xc0721eb9), 0xffffffffu); + FEngSetColor(FEngFindObject(GetPackageName(), 0xc62ad685), 0xffffffffu); + FEngSetColor(FEngFindObject(GetPackageName(), 0xb8f1f802), 0xffffffffu); + FEngSetColor(FEngFindObject(GetPackageName(), 0xd312f0cb), 0xffffae40u); + FEngSetColor(FEngFindObject(GetPackageName(), 0x8fe2a217), 0xffffae40u); +} + +// --- CustomizeHUDColor --- + +void CustomizeHUDColor::SetHUDTextures() { + float redline = Physics::Info::Redline(*reinterpret_cast(0x804ab3ec)); + int rpm = static_cast(redline); + int tachRPM; + if (rpm >= 0x251d) { + tachRPM = 10000; + } else if (rpm >= 0x2135) { + tachRPM = 9000; + } else if (rpm > 0x1d4c) { + tachRPM = 8000; + } else { + tachRPM = 7000; + } + CarPart *part = gCarCustomizeManager.GetTempColoredPart()->ThePart; + unsigned int hudStyleHash = FEngHashString("HUD_STYLE"); + int hudStyle = part->GetAppliedAttributeIParam(hudStyleHash, 0); + const char *pkg = GetPackageName(); + unsigned int hash = FEngHashString("HUD_NEEDLE_%d_%02d", tachRPM, hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0xc0721eb9), hash); + pkg = GetPackageName(); + hash = FEngHashString("HUD_GAUGE_%02d", hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0x5d19f25), hash); + pkg = GetPackageName(); + hash = FEngHashString("HUD_SPEEDOMETER_%02d", hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0xd312f0cb), hash); + if (gCarCustomizeManager.IsTurbo()) { + pkg = GetPackageName(); + hash = FEngHashString("HUD_NEEDLE_TURBO_%02d", hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0xc62ad685), hash); + pkg = GetPackageName(); + hash = FEngHashString("HUD_NOS_%02d", hudStyle); + FEngSetTextureHash(FEngFindImage(pkg, 0x8fe2a217), hash); + FEngSetVisible(FEngFindObject(GetPackageName(), 0xc5d551b7)); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xc5d551b7)); + } +} + +void CustomizeHUDColor::SetInitialColors() { + ShoppingCartItem *hudInCart = gCarCustomizeManager.IsPartTypeInCart(0x84u); + CarPart *installedHud = gCarCustomizeManager.GetInstalledCarPart(0x84); + unsigned int colors[5]; + colors[0] = 0xffffc373u; + colors[1] = 0xffffae40u; + colors[2] = 0xffffffffu; + SelectablePart *temp = gCarCustomizeManager.GetTempColoredPart(); + if (hudInCart && temp->ThePart == hudInCart->GetBuyingPart()->ThePart) { + int slot = 0x85; + int idx = 0; + do { + ShoppingCartItem *colorItem = gCarCustomizeManager.IsPartTypeInCart(static_cast(slot)); + slot++; + if (colorItem) { + CarPart *colorPart = colorItem->GetBuyingPart()->ThePart; + unsigned int r = colorPart->GetAppliedAttributeIParam(bStringHash("RED"), 0); + unsigned int g = colorPart->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + unsigned int b = colorPart->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + colors[idx] = ((r & 0xff) << 16) | ((g & 0xff) << 8) | 0xff000000 | (b & 0xff); + } + idx++; + } while (idx < 3); + } else if (temp->ThePart == installedHud) { + int slot = 0x85; + int idx = 0; + do { + CarPart *colorPart = gCarCustomizeManager.GetInstalledCarPart(slot); + slot++; + if (colorPart) { + unsigned int r = colorPart->GetAppliedAttributeIParam(bStringHash("RED"), 0); + unsigned int g = colorPart->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + unsigned int b = colorPart->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + colors[idx] = ((r & 0xff) << 16) | ((g & 0xff) << 8) | 0xff000000 | (b & 0xff); + } + idx++; + } while (idx < 3); + } + FEngSetColor(FEngFindObject(GetPackageName(), 0x5d19f25), colors[0]); + FEngSetColor(FEngFindObject(GetPackageName(), 0xd312f0cb), colors[1]); + FEngSetColor(FEngFindObject(GetPackageName(), 0x8fe2a217), colors[1]); + FEngSetColor(FEngFindObject(GetPackageName(), 0xc0721eb9), colors[2]); + FEngSetColor(FEngFindObject(GetPackageName(), 0xc62ad685), colors[2]); + FEngSetColor(FEngFindObject(GetPackageName(), 0xb8f1f802), colors[2]); +} + +// --- CustomizeRims --- + +void CustomizeRims::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + CustomizationScreen::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0xc519bfbf: + Showcase::FromFilter = InnerRadius; + break; + case 0x5073ef13: + ScrollRimSizes(eSD_PREV); + break; + case 0xd9feec59: + ScrollRimSizes(eSD_NEXT); + break; + case 0x5a928018: { + SelectablePart *sel = FindInCartPart(); + if (sel && !gCarCustomizeManager.IsPartInCart(sel)) { + sel->PartState = static_cast(sel->PartState & CPS_GAME_STATE_MASK); + RefreshHeader(); + } + break; + } + case 0x406415e3: + break; + case 0x911ab364: + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeSubTopPkg, FromCategory | (Category << 16), 0, false); + break; + } +} + +void CustomizeRims::ScrollRimSizes(eScrollDir dir) { + int radius = InnerRadius; + if (dir == eSD_PREV) { + radius--; + if (radius < MinRadius) { + radius = MaxRadius; + } + } else if (dir == eSD_NEXT) { + radius++; + if (radius > MaxRadius) { + radius = MinRadius; + } + } + if (radius != InnerRadius) { + IconScroller *options = &Options; + InnerRadius = radius; + int sel; + if (options->pCurrentNode) { + sel = options->GetCurrentIndex(); + } else { + sel = 0; + } + BuildRimsList(sel); + RefreshHeader(); + } +} + +void CustomizeRims::Setup() { + FEImage *leftBtn = FEngFindImage(GetPackageName(), 0x91c4a50); + FEngSetButtonTexture(leftBtn, 0x5bc); + FEImage *rightBtn = FEngFindImage(GetPackageName(), 0x2d145be3); + FEngSetButtonTexture(rightBtn, 0x682); + DisplayHelper.TitleHash = 0xe167f7c8; + InnerRadius = gCarCustomizeManager.GetMinInnerRadius(); + MinRadius = InnerRadius; + MaxRadius = gCarCustomizeManager.GetMaxInnerRadius(); + CarPart *activePart = gCarCustomizeManager.GetActivePartFromSlot(0x42); + if (Showcase::FromFilter != -1) { + InnerRadius = Showcase::FromFilter; + Showcase::FromFilter = -1; + } else if (activePart) { + InnerRadius = static_cast(activePart->GetAppliedAttributeIParam(0xeb0101e2, 0)); + } + BuildRimsList(-1); + RefreshHeader(); +} + +void CustomizeRims::BuildRimsList(int selected_index) { + Options.RemoveAll(); + Options.AddInitialBookEnds(); + int matchIdx = 0; + int curIdx = 1; + CarPart *activeMatch = nullptr; + bTList tempList; + gCarCustomizeManager.GetCarPartList(0x42, tempList, GetCategoryBrandHash()); + if (selected_index == -1) { + activeMatch = gCarCustomizeManager.GetActivePartFromSlot(0x42); + } + while (tempList.GetHead() != reinterpret_cast(&tempList)) { + SelectablePart *node = static_cast(tempList.GetHead()); + node->Prev->Next = node->Next; + node->Next->Prev = node->Prev; + signed char rimSize = static_cast(node->ThePart->GetAppliedAttributeIParam(0xeb0101e2, 0)); + if (static_cast(rimSize) == InnerRadius) { + unsigned int unlockHash = gCarCustomizeManager.GetUnlockHash(static_cast(Category), node->GetUpgradeLevel()); + unsigned char gl = *reinterpret_cast(reinterpret_cast(node->ThePart) + 5); + unsigned int wheelPos = gl >> 5; + bool locked = gCarCustomizeManager.IsPartLocked(node, 0); + AddPartOption(node, 0x294d2a3, wheelPos, 0, unlockHash, locked); + if (activeMatch && node->ThePart == activeMatch) { + matchIdx = curIdx; + } + curIdx++; + } else { + delete node; + } + } + if (selected_index == -1) { + selected_index = 1; + if (activeMatch) { + selected_index = matchIdx; + } + } + if (Showcase::FromIndex != 0) { + if (bFadeInIconsImmediately) { + Options.bFadingIn = true; + Options.bFadingOut = false; + Options.bDelayUpdate = false; + Options.fCurFadeTime = 0.0f; + } + Options.SetInitialPos(0); + Showcase::FromIndex = 0; + } else { + if (bFadeInIconsImmediately) { + Options.bFadingIn = true; + Options.bFadingOut = false; + Options.bDelayUpdate = false; + Options.fCurFadeTime = 0.0f; + } + Options.SetInitialPos(selected_index); + } + // Clean up remaining temp list nodes + while (tempList.GetHead() != reinterpret_cast(&tempList)) { + SelectablePart *del = static_cast(tempList.GetHead()); + del->Prev->Next = del->Next; + del->Next->Prev = del->Prev; + delete del; + } +} + +void CustomizeRims::RefreshHeader() { + CustomizationScreen::RefreshHeader(); + int numOpts = Options.Options.TraversebList(nullptr); + if (numOpts != Options.iNumBookEnds) { + SelectablePart *part = GetSelectedPart(); + gCarCustomizeManager.PreviewPart(part->GetSlotID(), part->GetPart()); + FEPrintf(GetPackageName(), 0xe6782841, "%$d\"", InnerRadius); + char buf[64]; + const char *name = part->GetPart()->GetName(); + bSNPrintf(buf, 64, "%s", name); + int len = bStrLen(buf); + for (int i = len; i >= len - 6; i--) { + buf[i] = 0; + } + FEPrintf(GetPackageName(), 0x5e7b09c9, "%s", buf); + } +} + +unsigned int CustomizeRims::GetCategoryBrandHash() { + switch (Category) { + case 0x702: return 0x352d08d1; + case 0x703: return 0x9136; + case 0x704: return 0x9536; + case 0x705: return 0x2b77feb; + case 0x706: return 0x324ac97; + case 0x707: return 0x48e25793; + case 0x708: return 0xdd544a02; + case 0x709: return 0x648; + case 0x70a: return 0x1e6a3b; + case 0x70b: return 0x1c386b; + default: return 0; + } +} + +// --- CustomizeNumbers --- + +CustomizeNumbers::CustomizeNumbers(ScreenConstructorData *sd) + : MenuScreen(sd) // + , LeftNumberList() // + , RightNumberList() // + , TheLeftNumber(nullptr) // + , TheRightNumber(nullptr) // + , LeftDisplayValue(-1) // + , RightDisplayValue(-1) // + , bLeft(1) // + , DisplayHelper(sd->PackageFilename) { + Category = sd->Arg & 0xFFFF; + FromCategory = static_cast(static_cast(sd->Arg >> 16)); + Setup(); +} + +void CustomizeNumbers::ScrollNumbers(eScrollDir dir) { + if (LeftDisplayValue == -1 || RightDisplayValue == -1) { + RightDisplayValue = 0; + TheLeftNumber = static_cast(LeftNumberList.GetHead()); + TheRightNumber = static_cast(RightNumberList.GetHead()); + LeftDisplayValue = 0; + RefreshHeader(); + } else { + SelectablePart *current; + if (bLeft) { + current = TheRightNumber; + } else { + current = TheLeftNumber; + } + if (dir == eSD_PREV) { + if (!bLeft) { + current = static_cast(current->Prev); + if (current == reinterpret_cast(&RightNumberList)) { + current = static_cast(RightNumberList.GetTail()); + } + RightDisplayValue = static_cast(RightDisplayValue - 1); + if (RightDisplayValue < 0) { + RightDisplayValue = 9; + } + } else { + current = static_cast(current->Prev); + if (current == reinterpret_cast(&LeftNumberList)) { + current = static_cast(LeftNumberList.GetTail()); + } + unsigned short val = static_cast(LeftDisplayValue - 1); + LeftDisplayValue = static_cast(val); + if (val & 0x8000) { + LeftDisplayValue = 9; + } + } + } else if (dir == eSD_NEXT) { + if (!bLeft) { + current = static_cast(current->Next); + if (current == reinterpret_cast(&RightNumberList)) { + current = static_cast(RightNumberList.GetHead()); + } + RightDisplayValue = static_cast(RightDisplayValue + 1); + if (RightDisplayValue > 9) { + RightDisplayValue = 0; + } + } else { + current = static_cast(current->Next); + if (current == reinterpret_cast(&LeftNumberList)) { + current = static_cast(LeftNumberList.GetHead()); + } + short val = static_cast(LeftDisplayValue + 1); + LeftDisplayValue = val; + if (val > 9) { + LeftDisplayValue = 0; + } + } + } + SelectablePart *prev; + if (!bLeft) { + prev = TheRightNumber; + } else { + prev = TheLeftNumber; + } + if (current != prev) { + if (!bLeft) { + TheRightNumber = current; + } else { + TheLeftNumber = current; + } + RefreshHeader(); + } + } +} + +void CustomizeNumbers::RefreshHeader() { + DisplayHelper.DrawTitle(); + DisplayHelper.SetCareerStuff(TheLeftNumber, Category, 0); + if (LeftDisplayValue != -1 && RightDisplayValue != -1) { + FEObject *numGroup = FEngFindObject(GetPackageName(), 0x7a8355d9); + FEngSetVisible(numGroup); + SelectablePart tempPart(TheLeftNumber); + eCustomizePartState state = CPS_AVAILABLE; + if ((TheLeftNumber->PartState & CPS_GAME_STATE_MASK) == CPS_LOCKED && + (TheRightNumber->PartState & CPS_GAME_STATE_MASK) == CPS_LOCKED) { + state = CPS_LOCKED; + } else if ((TheLeftNumber->PartState & CPS_GAME_STATE_MASK) == CPS_NEW && + (TheRightNumber->PartState & CPS_GAME_STATE_MASK) == CPS_NEW) { + state = CPS_NEW; + } + if ((TheLeftNumber->PartState & CPS_INSTALLED) != 0 && + (TheRightNumber->PartState & CPS_INSTALLED) != 0) { + state = static_cast(state | CPS_INSTALLED); + } else if ((TheLeftNumber->PartState & CPS_IN_CART) != 0 && + (TheRightNumber->PartState & CPS_IN_CART) != 0) { + state = static_cast(state | CPS_IN_CART); + } + tempPart.PartState = state; + unsigned int unlockHash = gCarCustomizeManager.GetUnlockHash(static_cast(Category), 1); + DisplayHelper.SetPartStatus(&tempPart, unlockHash, 0, 0); + FEPrintf(GetPackageName(), 0x2a08ba92, "%$d", static_cast(LeftDisplayValue)); + FEPrintf(GetPackageName(), 0x1a88dc05, "%$d", static_cast(RightDisplayValue)); + gCarCustomizeManager.PreviewPart(0x71, TheLeftNumber->ThePart); + gCarCustomizeManager.PreviewPart(0x72, TheRightNumber->ThePart); + gCarCustomizeManager.PreviewPart(0x69, TheLeftNumber->ThePart); + gCarCustomizeManager.PreviewPart(0x6a, TheRightNumber->ThePart); + } else { + FEObject *numGroup = FEngFindObject(GetPackageName(), 0x7a8355d9); + FEngSetInvisible(numGroup); + ShoppingCartItem *inCart = gCarCustomizeManager.IsPartTypeInCart(TheLeftNumber); + CarPart *installed = gCarCustomizeManager.GetInstalledCarPart(0x71); + if (!installed) { + DisplayHelper.SetPlayerCarStatusIcon(CPS_INSTALLED); + } else if (inCart && !inCart->GetBuyingPart()->ThePart) { + DisplayHelper.SetPlayerCarStatusIcon(CPS_IN_CART); + } else { + DisplayHelper.SetPlayerCarStatusIcon(CPS_AVAILABLE); + } + FEPrintf(GetPackageName(), 0x2a08ba92, "-"); + FEPrintf(GetPackageName(), 0x1a88dc05, "-"); + gCarCustomizeManager.ResetPreview(); + } +} + +void CustomizeNumbers::Setup() { + DisplayHelper.TitleHash = 0x6857e5ac; + gCarCustomizeManager.GetCarPartList(0x71, LeftNumberList, 0); + gCarCustomizeManager.GetCarPartList(0x72, RightNumberList, 0); + bool leftFound = false; + int leftIdx = 0; + CarPart *activeLeft = gCarCustomizeManager.GetActivePartFromSlot(0x71); + SelectablePart *node = static_cast(LeftNumberList.GetHead()); + while (node != reinterpret_cast(&LeftNumberList)) { + unsigned int attrVal = node->ThePart->GetAppliedAttributeUParam(0xebb03e66, 0); + unsigned int expectedHash = bStringHash("NUMBER_LEFT"); + if (attrVal == expectedHash) { + if (!leftFound) { + if (bShowcaseOn == 1 && Showcase::FromIndex == leftIdx) { + TheLeftNumber = node; + if (gCarCustomizeManager.IsPartInCart(node)) { + TheLeftNumber->PartState = static_cast(TheLeftNumber->PartState | CPS_IN_CART); + } + LeftDisplayValue = static_cast(leftIdx); + leftFound = true; + Showcase::FromIndex = 0; + } else if (node->ThePart == activeLeft) { + TheLeftNumber = node; + if (gCarCustomizeManager.IsPartInCart(node)) { + TheLeftNumber->PartState = static_cast(TheLeftNumber->PartState | CPS_IN_CART); + } + LeftDisplayValue = static_cast(leftIdx); + } + } + leftIdx++; + node = static_cast(node->Next); + } else { + SelectablePart *next = static_cast(node->Next); + node->Prev->Next = node->Next; + node->Next->Prev = node->Prev; + delete node; + node = next; + } + } + if (!TheLeftNumber) { + LeftDisplayValue = -1; + TheLeftNumber = static_cast(LeftNumberList.GetHead()); + } + + bool rightFound = false; + int rightIdx = 0; + CarPart *activeRight = gCarCustomizeManager.GetActivePartFromSlot(0x72); + node = static_cast(RightNumberList.GetHead()); + while (node != reinterpret_cast(&RightNumberList)) { + unsigned int attrVal = node->ThePart->GetAppliedAttributeUParam(0xebb03e66, 0); + unsigned int expectedHash = bStringHash("NUMBER_RIGHT"); + if (attrVal == expectedHash) { + if (!rightFound) { + if (bShowcaseOn == 1 && Showcase::FromFilter == rightIdx) { + TheRightNumber = node; + if (gCarCustomizeManager.IsPartInCart(node)) { + TheRightNumber->PartState = static_cast(TheRightNumber->PartState | CPS_IN_CART); + } + RightDisplayValue = static_cast(rightIdx); + rightFound = true; + Showcase::FromFilter = -1; + } else if (node->ThePart == activeRight) { + TheRightNumber = node; + if (gCarCustomizeManager.IsPartInCart(node)) { + TheRightNumber->PartState = static_cast(TheRightNumber->PartState | CPS_IN_CART); + } + RightDisplayValue = static_cast(rightIdx); + } + } + rightIdx++; + node = static_cast(node->Next); + } else { + SelectablePart *next = static_cast(node->Next); + node->Prev->Next = node->Next; + node->Next->Prev = node->Prev; + delete node; + node = next; + } + } + if (!TheRightNumber) { + RightDisplayValue = -1; + TheRightNumber = static_cast(RightNumberList.GetHead()); + } + RefreshHeader(); +} + +void CustomizeNumbers::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x35f8620b: + DisplayHelper.SetInitComplete(true); + FEngSetCurrentButton(GetPackageName(), 0x2a08ba92); + break; + case 0xc519bfbf: + Showcase::FromFilter = static_cast(RightDisplayValue); + Showcase::FromIndex = static_cast(LeftDisplayValue); + Showcase::FromArgs = Category | (FromCategory << 16); + Showcase::FromPackage = GetPackageName(); + bShowcaseOn = 1; + cFEng_mInstance->QueuePackageSwitch("Showcase.fng", gCarCustomizeManager.TuningCar->FEKey, 0, false); + break; + case 0xb5af2461: + CustomizeShoppingCart::ShowShoppingCart(GetPackageName()); + break; + case 0x9120409e: + case 0xb5971bf1: { + bLeft = !bLeft; + FEngSetCurrentButton(GetPackageName(), bLeft ? 0x2a08ba92 : 0x1a88dc05); + break; + } + case 0x72619778: + ScrollNumbers(eSD_NEXT); + break; + case 0x911c0a4b: + ScrollNumbers(eSD_PREV); + break; + case 0x406415e3: + if (LeftDisplayValue == -1 || RightDisplayValue == -1) return; + if (!TheLeftNumber || !TheRightNumber) return; + if (TheLeftNumber->IsLocked() && TheRightNumber->IsLocked()) { + DisplayHelper.FlashStatusIcon(CPS_LOCKED, true); + } else if (TheLeftNumber->IsInCartX() && TheRightNumber->IsInCartX()) { + DisplayHelper.FlashStatusIcon(CPS_IN_CART, true); + } else if (TheLeftNumber->IsInstalledX() && TheRightNumber->IsInstalledX()) { + DisplayHelper.FlashStatusIcon(CPS_INSTALLED, true); + } else { + cFEng_mInstance->QueueGameMessage(0x91dfdf84, GetPackageName(), 0xff); + return; + } + break; + case 0xc519bfc3: { + CarPart *installed = gCarCustomizeManager.GetInstalledCarPart(0x71); + if (installed) { + UnsetShoppingCart(); + SelectablePart stockPart(nullptr, 0x71, 0, static_cast(7), false, CPS_AVAILABLE, 0, false); + gCarCustomizeManager.AddToCart(&stockPart); + stockPart.CarSlotID = 0x72; + gCarCustomizeManager.AddToCart(&stockPart); + stockPart.CarSlotID = 0x69; + gCarCustomizeManager.AddToCart(&stockPart); + stockPart.CarSlotID = 0x6a; + gCarCustomizeManager.AddToCart(&stockPart); + } else { + if ((TheLeftNumber->PartState & CPS_PLAYER_STATE_MASK) == CPS_IN_CART || + (TheRightNumber->PartState & CPS_PLAYER_STATE_MASK) == CPS_IN_CART) { + UnsetShoppingCart(); + ShoppingCartItem *current = gCarCustomizeManager.GetFirstCartItem(); + ShoppingCartItem *last = gCarCustomizeManager.GetLastCartItem(); + while (current) { + ShoppingCartItem *next = static_cast(current->Next); + int slotID = current->GetBuyingPart()->GetSlotID(); + if (slotID == 0x71 || slotID == 0x72 || slotID == 0x69 || slotID == 0x6a) { + gCarCustomizeManager.RemoveFromCart(current); + } + if (current == last) break; + current = next; + } + } + } + RightDisplayValue = -1; + TheLeftNumber = static_cast(LeftNumberList.GetHead()); + TheRightNumber = static_cast(RightNumberList.GetHead()); + LeftDisplayValue = -1; + RefreshHeader(); + break; + } + case 0x91dfdf84: + UnsetShoppingCart(); + TheLeftNumber->PartState = static_cast(TheLeftNumber->PartState | CPS_IN_CART); + TheRightNumber->PartState = static_cast(TheRightNumber->PartState | CPS_IN_CART); + gCarCustomizeManager.AddToCart(TheLeftNumber); + gCarCustomizeManager.AddToCart(TheRightNumber); + { + SelectablePart *copyLeft = new SelectablePart(TheLeftNumber); + SelectablePart *copyRight = new SelectablePart(TheRightNumber); + copyLeft->Price = 0; + copyLeft->PartState = static_cast((copyLeft->PartState & CPS_GAME_STATE_MASK) | CPS_IN_CART); + copyRight->PartState = static_cast((copyRight->PartState & CPS_GAME_STATE_MASK) | CPS_IN_CART); + copyLeft->CarSlotID = 0x69; + copyRight->CarSlotID = 0x6a; + copyRight->Price = 0; + gCarCustomizeManager.AddToCart(copyLeft); + gCarCustomizeManager.AddToCart(copyRight); + delete copyLeft; + delete copyRight; + } + RefreshHeader(); + break; + case 0xcf91aacd: { + SelectablePart *lnode = static_cast(LeftNumberList.GetHead()); + SelectablePart *lsentinel = reinterpret_cast(&LeftNumberList); + while (lnode != lsentinel) { + if (lnode->ThePart == gCarCustomizeManager.GetInstalledCarPart(0x69) || + lnode->ThePart == gCarCustomizeManager.GetInstalledCarPart(0x6a)) { + lnode->PartState = static_cast((lnode->PartState & CPS_GAME_STATE_MASK) | CPS_INSTALLED); + } + lnode = static_cast(lnode->Next); + } + SelectablePart *rnode = static_cast(RightNumberList.GetHead()); + SelectablePart *rsentinel = reinterpret_cast(&RightNumberList); + while (rnode != rsentinel) { + if (rnode->ThePart == gCarCustomizeManager.GetInstalledCarPart(0x71) || + rnode->ThePart == gCarCustomizeManager.GetInstalledCarPart(0x72)) { + rnode->PartState = static_cast((rnode->PartState & CPS_GAME_STATE_MASK) | CPS_INSTALLED); + } + rnode = static_cast(rnode->Next); + } + CustomizeShoppingCart::ExitShoppingCart(); + break; + } + case 0x5a928018: { + ShoppingCartItem *leftInCart = gCarCustomizeManager.IsPartTypeInCart(0x69u); + ShoppingCartItem *rightInCart = gCarCustomizeManager.IsPartTypeInCart(0x6au); + if (!leftInCart && !rightInCart) { + SelectablePart *lnode = static_cast(LeftNumberList.GetHead()); + SelectablePart *lsentinel = reinterpret_cast(&LeftNumberList); + for (; lnode != lsentinel; lnode = static_cast(lnode->Next)) { + if ((lnode->PartState & CPS_PLAYER_STATE_MASK) == CPS_IN_CART) { + lnode->PartState = static_cast(lnode->PartState & CPS_GAME_STATE_MASK); + break; + } + } + SelectablePart *rnode = static_cast(RightNumberList.GetHead()); + SelectablePart *rsentinel = reinterpret_cast(&RightNumberList); + for (; rnode != rsentinel; rnode = static_cast(rnode->Next)) { + if ((rnode->PartState & CPS_PLAYER_STATE_MASK) == CPS_IN_CART) { + rnode->PartState = static_cast(rnode->PartState & CPS_GAME_STATE_MASK); + break; + } + } + } + RefreshHeader(); + break; + } + case 0x911ab364: + bShowcaseOn = 0; + cFEng_mInstance->QueuePackageSwitch(g_pCustomizeSubPkg, FromCategory | (Category << 16), 0, false); + break; + } +} + +// --- CustomizeDecals --- + +unsigned int CustomizeDecals::GetSlotIDFromCategory() { + switch (CurrentDecalLocation) { + case 0x501: + return 0x53; + case 0x502: + return 0x5b; + case 0x503: + switch (Category) { + case 0x601: return 0x63; + case 0x602: return 0x64; + case 0x603: return 0x65; + case 0x604: return 0x66; + case 0x605: return 0x67; + case 0x606: return 0x68; + } + // fall through + case 0x504: + switch (Category) { + case 0x601: return 0x6b; + case 0x602: return 0x6c; + case 0x603: return 0x6d; + case 0x604: return 0x6e; + case 0x605: return 0x6f; + case 0x606: return 0x70; + default: return 0x73; + } + case 0x505: + return 0x73; + case 0x506: + return 0x7b; + default: + return 0x53; + } +} + +void CustomizeDecals::Setup() { + unsigned int slotID = GetSlotIDFromCategory(); + FEImage *leftBtn = FEngFindImage(GetPackageName(), 0x91c4a50); + FEngSetButtonTexture(leftBtn, 0x5bc); + FEImage *rightBtn = FEngFindImage(GetPackageName(), 0x2d145be3); + FEngSetButtonTexture(rightBtn, 0x682); + unsigned int titleHash = 0; + switch (Category) { + case 0x501: titleHash = 0x301dedd3; break; + case 0x502: titleHash = 0x48e6ca49; break; + case 0x505: titleHash = 0x8a7697d6; break; + case 0x506: titleHash = 0xb1f9b0c9; break; + case 0x601: titleHash = 0x7d212cfa; break; + case 0x602: titleHash = 0x7d212cfb; break; + case 0x603: titleHash = 0x7d212cfc; break; + case 0x604: titleHash = 0x7d212cfd; break; + case 0x605: titleHash = 0x7d212cfe; break; + case 0x606: titleHash = 0x7d212cff; break; + default: break; + } + DisplayHelper.TitleHash = titleHash; + ShoppingCartItem *cartItem = gCarCustomizeManager.IsPartTypeInCart(slotID); + unsigned int selectedHash = 0; + CarPart *activePart = nullptr; + if (cartItem && (activePart = cartItem->GetBuyingPart()->GetPart(), activePart)) { + } else { + activePart = gCarCustomizeManager.GetInstalledCarPart(slotID); + } + if (activePart) { + unsigned int brand = activePart->GetAppliedAttributeUParam(0xebb03e66, 0); + unsigned int mirrorHash = activePart->GetAppliedAttributeUParam(bStringHash("DECAL_MIRROR_HASH"), 0); + bIsBlack = (brand == mirrorHash); + selectedHash = activePart->GetAppliedAttributeUParam(0xebb03e66, 0); + } + if (Showcase::FromFilter != -1) { + bIsBlack = (Showcase::FromFilter != 0); + Showcase::FromFilter = -1; + } + BuildDecalList(selectedHash); + RefreshHeader(); +} + +void CustomizeDecals::BuildDecalList(unsigned int selected_name_hash) { + int matchIdx = 1; + int curIdx = 2; + bNeedsRefresh = true; + ScrollTime = 0; + Options.RemoveAll(); + Options.AddInitialBookEnds(); + + unsigned int slotID = GetSlotIDFromCategory(); + bTList tempList; + SelectablePart *stockPart = new SelectablePart(nullptr, slotID, 0, static_cast(7), false, static_cast(1), 0, false); + eCustomizePartState stockState = CPS_AVAILABLE; + if (gCarCustomizeManager.IsPartInstalled(stockPart)) { + stockState = static_cast(0x11); + } else if (gCarCustomizeManager.IsPartInCart(stockPart)) { + stockState = static_cast(0x21); + } + stockPart->PartState = stockState; + AddPartOption(stockPart, 0x697b4ad4, 0x60a662f5, 0, 0, false); + + gCarCustomizeManager.GetCarPartList(slotID, tempList, 0); + + int unlockLevel = MapCarPartToUnlockable(slotID, nullptr); + if (unlockLevel == 0x2e) { + unlockLevel = 2; + } else if (unlockLevel > 0x2e) { + if (unlockLevel == 0x30) { + unlockLevel = 3; + } + } else if (unlockLevel == 0x2c) { + unlockLevel = 1; + } + + SelectablePart *node = static_cast(tempList.GetHead()); + SelectablePart *sentinel = reinterpret_cast(&tempList); + while (node != sentinel) { + SelectablePart *next = static_cast(node->Next); + unsigned int nameHash = node->GetPart()->GetAppliedAttributeUParam(0xebb03e66, 0); + if (!bIsBlack) { + nameHash = bStringHash("_WHITE", nameHash); + } + unsigned int mirrorHash = node->GetPart()->GetAppliedAttributeUParam(bStringHash("DECAL_MIRROR_HASH"), 0); + if (nameHash == mirrorHash) { + unsigned int unlockHash = gCarCustomizeManager.GetUnlockHash(static_cast(Category), unlockLevel); + node->Prev->Next = node->Next; + node->Next->Prev = node->Prev; + bool locked = gCarCustomizeManager.IsPartLocked(node, 0); + AddPartOption(node, 0x697b4ad4, nameHash, 0, unlockHash, locked); + if (node->GetPart()->GetAppliedAttributeUParam(0xebb03e66, 0) == selected_name_hash) { + matchIdx = curIdx; + } + curIdx++; + } else { + node->Prev->Next = node->Next; + node->Next->Prev = node->Prev; + delete node; + } + node = next; + } + if (Showcase::FromIndex != 0) { + SetInitialOption(0); + Showcase::FromIndex = 0; + } else { + SetInitialOption(matchIdx); + } + + while (tempList.GetHead() != tempList.EndOfList()) { + SelectablePart *sp = static_cast(tempList.GetHead()); + sp->Remove(); + delete sp; + } +} + +void CustomizeDecals::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + CustomizationScreen::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0xc519bfbf: + Showcase::FromFilter = bIsBlack; + break; + case 0x5073ef13: + case 0xd9feec59: + bIsBlack ^= 1; + { + CustomizePartOption *opt = GetSelectedOption(); + if (opt->GetPart()) { + unsigned int nameHash = opt->GetPart()->GetPart()->GetAppliedAttributeUParam(0xebb03e66, 0); + BuildDecalList(nameHash); + } else { + BuildDecalList(0); + } + } + RefreshHeader(); + break; + case 0x5a928018: { + SelectablePart *sel = GetSelectedPart(); + if (sel) { + if (gCarCustomizeManager.IsPartInCart(sel)) { + return; + } + sel->PartState = static_cast(sel->PartState & 0xf); + RefreshHeader(); + } + break; + } + case 0x911ab364: + cFEng::Get()->QueuePackageSwitch(g_pCustomizeSubTopPkg, FromCategory | Category << 16, 0, false); + break; + case 0xc519bfc3: + return; + } +} + +void CustomizeDecals::RefreshHeader() { + CustomizationScreen::RefreshHeader(); + SelectablePart *sel = GetSelectedPart(); + if (sel->GetPart() == nullptr) { + FEngSetLanguageHash(GetPackageName(), 0x5e7b09c9, Options.GetCurrentName()); + } else { + FEPrintf(GetPackageName(), 0x5e7b09c9, "%s", GetSelectedPart()->GetPart()->GetName()); + } + unsigned int hash = 0x436a98e9; + if (bIsBlack) { + hash = 0x41f0a3a5; + } + FEngSetLanguageHash(GetPackageName(), 0x889bacb6, hash); + if ((RealTimer - ScrollTime).GetSeconds() <= 0.3f) { + bNeedsRefresh = true; + } else { + unsigned int slotId = GetSlotIDFromCategory(); + SelectablePart *cur = GetSelectedPart(); + gCarCustomizeManager.PreviewPart(slotId, cur->GetPart()); + } +} + +// --- CustomizePaint helpers --- + +unsigned int CustomizePaint::CalcBrandHash(CarPart *part) { + if (Category == 0x301) { + switch (TheFilter) { + case 0: return 0x02daab07; + case 1: return 0x03437a52; + case 2: return 0x03797533; + default: return part->GetAppliedAttributeUParam(0xebb03e66, 0); + } + } else if (Category == 0x303) { + return 0xda27; + } + return 0x3e871f1; +} + +CustomizePaint::CustomizePaint(ScreenConstructorData *sd) + : CustomizationScreen(sd) // + , TheFilter(-1) // + , MatchingPaint(nullptr, 0, 0, 0, 0) // + , ThePaints(sd->PackageFilename, 20, 4, true) { + NumRemapColors = 0; + for (int i = 0; i <= 2; i++) { + VinylColors[i] = nullptr; + } + ThePaints.SetMouseDownMsg(0x406415e3); + Setup(); +} + +void CustomizePaint::Setup() { + FEImage *leftBtn = FEngFindImage(GetPackageName(), 0x91c4a50); + FEngSetButtonTexture(leftBtn, 0x5bc); + FEImage *rightBtn = FEngFindImage(GetPackageName(), 0x2d145be3); + FEngSetButtonTexture(rightBtn, 0x682); + for (int i = 1; i <= 0x50; i++) { + ArraySlot *slot = new ArraySlot(FEngFindImage(GetPackageName(), static_cast< int >(FEngHashString("COLOR_%d", i)))); + ThePaints.AddSlot(slot); + } + for (int i = 0; i < 3; i++) { + SelectedIndex[i] = -1; + } + if (Showcase::FromFilter != -1) { + TheFilter = Showcase::FromFilter; + } + switch (Category) { + case 0x301: + cFEng::Get()->QueuePackageMessage(0x1a7240f3, GetPackageName(), nullptr); + DisplayHelper.TitleHash = 0x55da70c; + SetupBasePaint(); + break; + case 0x303: + DisplayHelper.TitleHash = 0xe126ff53; + SetupRimPaint(); + break; + case 0x402: case 0x403: case 0x404: case 0x405: + case 0x406: case 0x407: case 0x408: case 0x409: + DisplayHelper.TitleHash = 0xd8ee1a80; + SetupVinylColor(); + break; + } + Showcase::FromFilter = -1; + Options.bInitialized = true; + RefreshHeader(); +} + +void CustomizePaint::AddVinylAndColorsToCart() { + SelectablePart *mainPart = gCarCustomizeManager.GetTempColoredPart(); + gCarCustomizeManager.AddToCart(mainPart); + for (int i = 0; i < NumRemapColors; i++) { + if (VinylColors[i]) { + gCarCustomizeManager.AddToCart(VinylColors[i]); + VinylColors[i] = nullptr; + } + } +} + +eMenuSoundTriggers CustomizePaint::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (static_cast(maybe - 0x29) < 2) { + if (Category == 0x303) { + return static_cast(-1); + } + SelectablePart *temp = gCarCustomizeManager.GetTempColoredPart(); + if (temp) { + CarPart *part = temp->GetPart(); + if (part && part->GetAppliedAttributeUParam(0x6212682b, 0) < 2) { + return static_cast(-1); + } + } + } + return maybe; +} + +void CustomizePaint::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x406415e3: + if (Category == 0x301 || Category == 0x303) { + CustomizationScreen::NotificationMessage(0x406415e3, pobj, param1, param2); + } + break; + default: + CustomizationScreen::NotificationMessage(msg, pobj, param1, param2); + break; + case 0x9120409e: + case 0xb5971bf1: + break; + } + + ThePaints.NotificationMessage(msg, pobj, param1, param2); + + switch (msg) { + case 0xc519bfbf: + Showcase::FromFilter = TheFilter; + Showcase::FromIndex = ThePaints.GetCurrentDatumNum(); + for (int i = 0; i < 3; i++) { + Showcase::FromColor[i] = VinylColors[i]; + } + break; + case 0x5073ef13: + ScrollFilters(static_cast(-1)); + break; + case 0xd9feec59: + ScrollFilters(static_cast(1)); + break; + case 0x406415e3: + if (Category == 0x301 || Category == 0x303) { + return; + } + { + CarPart *installed = gCarCustomizeManager.GetTempColoredPart()->GetPart(); + if (VinylColors[TheFilter]) { + delete VinylColors[TheFilter]; + } + int savedFilter = TheFilter; + bool add_to_cart = false; + SelectablePart *newPart = new SelectablePart(gCarCustomizeManager.GetTempColoredPart()); + VinylColors[savedFilter] = newPart; + if (installed != gCarCustomizeManager.GetActivePartFromSlot(0x4d)) { + add_to_cart = true; + } else { + for (int i = 0; i < NumRemapColors; i++) { + CarPart *active = gCarCustomizeManager.GetActivePartFromSlot(i + 0x4f); + if (VinylColors[i] && active != VinylColors[i]->GetPart()) { + add_to_cart = true; + break; + } + } + } + if (!add_to_cart) { + return; + } + AddVinylAndColorsToCart(); + } + { + unsigned int cat = Category | (FromCategory << 16); + cFEng::Get()->QueuePackageSwitch(g_pCustomizePartsPkg, cat, 0, false); + } + break; + case 0x911ab364: + if (Category == 0x301 || Category == 0x303) { + unsigned int cat = Category | (FromCategory << 16); + cFEng::Get()->QueuePackageSwitch(g_pCustomizeSubPkg, cat, 0, false); + return; + } + for (int i = 0; i < 3; i++) { + if (VinylColors[i]) { + delete VinylColors[i]; + } + VinylColors[i] = nullptr; + Showcase::FromColor[i] = nullptr; + } + gCarCustomizeManager.ResetPreview(); + { + unsigned int cat = Category | (FromCategory << 16); + cFEng::Get()->QueuePackageSwitch(g_pCustomizePartsPkg, cat, 0, false); + } + break; + case 0x5a928018: { + SelectablePart *part = GetSelectedPart(); + if (part && !gCarCustomizeManager.IsPartInCart(part)) { + part->UnSetInCart(); + RefreshHeader(); + } + RefreshHeader(); + break; + } + case 0x9120409e: + case 0xb5971bf1: + case 0x911c0a4b: + case 0x72619778: + RefreshHeader(); + break; + case 0xcf91aacd: + for (int i = 0; i < 3; i++) { + if (VinylColors[i]) { + delete VinylColors[i]; + } + VinylColors[i] = nullptr; + } + break; + } +} + +void CustomizePaint::ScrollFilters(eScrollDir dir) { + int maxFilter; + if (Category == 0x301) { + maxFilter = 2; + } else if (Category == 0x303) { + return; + } else { + maxFilter = NumRemapColors - 1; + if (maxFilter != 0) { + SelectablePart *current = GetSelectedPart(); + if (current != VinylColors[TheFilter]) { + if (VinylColors[TheFilter]) { + delete VinylColors[TheFilter]; + } + int savedFilter = TheFilter; + SelectablePart *newPart = new SelectablePart(gCarCustomizeManager.GetTempColoredPart()); + VinylColors[savedFilter] = newPart; + } + } + } + int cur = TheFilter; + int next; + if (dir == static_cast(-1)) { + next = cur - 1; + if (next < 0) { + next = maxFilter; + } + } else if (dir == static_cast(1)) { + next = cur + 1; + if (next > maxFilter) { + next = 0; + } + } else { + next = cur; + } + if (next != cur) { + SelectedIndex[TheFilter] = ThePaints.GetCurrentDatumNum() - 1; + TheFilter = next; + if (Category == 0x301 || Category == 0x303) { + SelectablePart *sel = GetSelectedPart(); + BuildSwatchList(sel->GetSlotID()); + } else { + BuildSwatchList(next + 0x4f); + } + RefreshHeader(); + } +} + +void CustomizePaint::SetupVinylColor() { + unsigned int slot = 0x4f; + if (Showcase::FromFilter != -1) { + if (Showcase::FromFilter == 1) { + slot = 0x50; + } else if (Showcase::FromFilter == 2) { + slot = 0x51; + } + Showcase::FromFilter = -1; + } + BuildSwatchList(slot); + CarPart *activePart = gCarCustomizeManager.GetActivePartFromSlot(0x4d); + NumRemapColors = activePart->GetAppliedAttributeUParam(0x6212682b, 0); + if (NumRemapColors < 2) { + FEObject *obj = FEngFindObject(PackageFilename, 0x2c3cc2d3); + FEngSetInvisible(obj); + obj = FEngFindObject(PackageFilename, 0x53639a10); + FEngSetInvisible(obj); + } else { + cFEng::Get()->QueuePackageMessage(0x1a7240f3, PackageFilename, nullptr); + } + for (int i = 0; i < 3; i++) { + int slotID = i + 0x4f; + if (!Showcase::FromColor[i]) { + CarPart *active = gCarCustomizeManager.GetActivePartFromSlot(slotID); + if (!active) { + VinylColors[i] = nullptr; + } else { + SelectablePart *sp = new SelectablePart( + active, slotID, active->GetUpgradeLevel(), static_cast(7), + false, static_cast(1), 0, false); + VinylColors[i] = sp; + } + } else { + VinylColors[i] = static_cast(Showcase::FromColor[i]); + Showcase::FromColor[i] = nullptr; + } + } +} + +SelectablePart *CustomizePaint::GetSelectedPart() { + return static_cast(ThePaints.GetCurrentDatum())->ThePart; +} + +void CustomizePaint::BuildSwatchList(unsigned int slot) { + CarPart *matchPart = nullptr; + ThePaints.ClearData(); + int colorIndex = 0; + switch (slot) { + case 0x4f: colorIndex = 0; break; + case 0x50: colorIndex = 1; break; + case 0x51: colorIndex = 2; break; + default: goto skip_color; + } + if (Showcase::FromColor[colorIndex] && !VinylColors[colorIndex]) { + matchPart = static_cast(Showcase::FromColor[colorIndex])->GetPart(); + } +skip_color: + if (!matchPart) { + matchPart = gCarCustomizeManager.GetActivePartFromSlot(slot); + } + unsigned int brand = CalcBrandHash(matchPart); + if (TheFilter == -1) { + if (brand == 0x2daab07) { + TheFilter = 0; + } else if (brand > 0x2daab07) { + if (brand == 0x3437a52) { + TheFilter = 1; + } else if (brand == 0x3797533) { + TheFilter = 2; + } + } else if (brand == 0xda27) { + TheFilter = 0; + } else { + TheFilter = 0; + } + } + bTList partList; + gCarCustomizeManager.GetCarPartList(slot, partList, 0); + int datumIndex = 0; + while (partList.GetHead() != partList.EndOfList()) { + SelectablePart *sp = static_cast(partList.GetHead()); + unsigned int partBrand = sp->GetPart()->GetAppliedAttributeUParam(0xebb03e66, 0); + if (partBrand == brand) { + sp->Remove(); + unsigned int unlockHash = gCarCustomizeManager.GetUnlockHash( + static_cast(Category), + static_cast(sp->GetPart()->GroupNumber_UpgradeLevel >> 5)); + CustomizePaintDatum *datum = new CustomizePaintDatum(sp, unlockHash); + if (SelectedIndex[TheFilter] == -1 && matchPart == sp->GetPart()) { + SelectedIndex[TheFilter] = datumIndex; + } + ThePaints.AddDatum(datum); + ArraySlot *aslot = ThePaints.GetSlotAt(datumIndex); + if (aslot) { + unsigned char r = datum->ThePart->GetPart()->GetAppliedAttributeIParam(bStringHash("RED"), 0); + unsigned char g = datum->ThePart->GetPart()->GetAppliedAttributeIParam(bStringHash("GREEN"), 0); + unsigned char b = datum->ThePart->GetPart()->GetAppliedAttributeIParam(bStringHash("BLUE"), 0); + FEngSetColor(aslot->GetFEngObject(), 0xff000000 | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)); + } + datumIndex++; + } else { + sp->Remove(); + delete sp; + } + } + if (Showcase::FromIndex != 0) { + SelectedIndex[TheFilter] = Showcase::FromIndex - 1; + Showcase::FromIndex = 0; + } else { + if (SelectedIndex[TheFilter] == -1) { + SelectedIndex[TheFilter] = 0; + } + ThePaints.SetInitialPosition(SelectedIndex[TheFilter]); + } + RefreshHeader(); + while (partList.GetHead() != partList.EndOfList()) { + SelectablePart *sp = static_cast(partList.GetHead()); + sp->Remove(); + delete sp; + } +} + +void CustomizePaint::RefreshHeader() { + DisplayHelper.DrawTitle(); + ThePaints.RefreshHeader(); + int filter = TheFilter; + unsigned int hash = 0; + switch (filter) { + case 0: + if (Category == 0x301) { + hash = 0xb6763cde; + } else if (NumRemapColors == 2) { + hash = 0x5198ba16; + } else if (NumRemapColors == 3) { + hash = 0x5198ba17; + } else { + hash = 0xd8ee1a80; + } + break; + case 1: + if (Category == 0x301) { + hash = 0x452b5481; + } else if (NumRemapColors == 2) { + hash = 0x5198be57; + } else if (NumRemapColors == 3) { + hash = 0x5198be58; + } + break; + case 2: + if (Category == 0x301) { + hash = 0xb715070a; + } else if (NumRemapColors == 3) { + hash = 0x5198c299; + } + break; + } + FEngSetLanguageHash(PackageFilename, 0x78008599, hash); + if (Category == 0x301) { + SelectablePart *sel = GetSelectedPart(); + int slotID = sel->GetSlotID(); + sel = GetSelectedPart(); + gCarCustomizeManager.PreviewPart(slotID, sel->GetPart()); + } else if (Category == 0x303) { + SelectablePart *sel = GetSelectedPart(); + int slotID = sel->GetSlotID(); + sel = GetSelectedPart(); + gCarCustomizeManager.PreviewPart(slotID, sel->GetPart()); + FEObject *obj = FEngFindObject(PackageFilename, 0x2c526172); + FEngSetInvisible(obj); + FEngSetLanguageHash(PackageFilename, 0x78008599, 0xb3100a3e); + } else { + gCarCustomizeManager.PreviewPart( + gCarCustomizeManager.GetTempColoredPart()->GetSlotID(), + gCarCustomizeManager.GetTempColoredPart()->GetPart()); + if (NumRemapColors == 1) { + FEObject *obj = FEngFindObject(PackageFilename, 0x2c526172); + FEngSetInvisible(obj); + } + for (int i = 0; i < 3; i++) { + if (i < NumRemapColors && VinylColors[i]) { + gCarCustomizeManager.PreviewPart(VinylColors[i]->GetSlotID(), VinylColors[i]->GetPart()); + } + } + SelectablePart *sel = GetSelectedPart(); + int slotID = sel->GetSlotID(); + sel = GetSelectedPart(); + gCarCustomizeManager.PreviewPart(slotID, sel->GetPart()); + } + SelectablePart *sel = GetSelectedPart(); + DisplayHelper.SetCareerStuff(sel, Category, 0); + sel = GetSelectedPart(); + unsigned int unlockBlurb = static_cast(ThePaints.GetCurrentDatum())->UnlockBlurb; + int curNum = ThePaints.GetCurrentDatumNum(); + int totalNum = ThePaints.GetCurrentDatumNum(); + DisplayHelper.SetPartStatus(sel, unlockBlurb, curNum, totalNum); +} + +CustomizePaint::~CustomizePaint() { + MatchingPaint.ThePart = nullptr; +} + +CustomizePaintDatum::~CustomizePaintDatum() { + if (ThePart) { + delete ThePart; + } +} + +unsigned int CustomizePerformance::GetPerfPkgDesc(Physics::Upgrades::Type type, int level, int num_packages, bool has_turbo) { + if (level == 0) { + switch (type) { + case static_cast(0): return 0xe5c1020c; + case static_cast(1): return 0x927db4fd; + case static_cast(2): return 0x8c96b853; + case static_cast(3): return 0x2f525e4f; + case static_cast(4): return 0xe74dedbb; + case static_cast(5): + if (has_turbo) return 0x5317eb31; + return 0x704a6d50; + case static_cast(6): return 0x9a0ef8f9; + default: return 0; + } + } + const char *fmt; + switch (type) { + case static_cast(0): fmt = "PD_TIRES_%d_%d"; break; + case static_cast(1): fmt = "PD_BRAKES_%d_%d"; break; + case static_cast(2): fmt = "PD_CHASSIS_%d_%d"; break; + case static_cast(3): fmt = "PD_TRANSMISSION_%d_%d"; break; + case static_cast(4): + if (gCarCustomizeManager.IsCastrolCar() && level == 4 && num_packages == 3) { + return FEngHashString("PD_ENGINE_%d_%d_CASTROL", 4, 3); + } + if (gCarCustomizeManager.IsRotaryCar() && (level == 2 || level == 4) && num_packages == 1) { + return FEngHashString("PD_ENGINE_%d_%d_ROTARY", level, 1); + } + fmt = "PD_ENGINE_%d_%d"; + break; + case static_cast(5): + if (!has_turbo) { + fmt = "PD_SUPERCHARGER_%d_%d"; + } else { + fmt = "PD_TURBO_%d_%d"; + } + break; + case static_cast(6): fmt = "PD_NITROUS_%d_%d"; break; + default: return 0; + } + return FEngHashString(fmt, level, num_packages); +} + +unsigned int CustomizePerformance::GetPerfPkgBrand(Physics::Upgrades::Type type, int level, int num_packages) { + unsigned int hash = 0; + CarCustomizeManager *mgr = &gCarCustomizeManager; + Attrib::Gen::frontend inst(mgr->GetTuningCar()->FEKey, 0, nullptr); + Attrib::Instance *inst_ptr = &inst; + unsigned int *ptr = nullptr; + switch (type) { + case static_cast(0): + switch (level) { + case 0: hash = 0xad6a0504; goto done; + case 1: ptr = static_cast(inst_ptr->GetAttributePointer(0xf0c7c400, num_packages)); break; + case 2: ptr = static_cast(inst_ptr->GetAttributePointer(0x1e6ddf1, num_packages)); break; + case 3: ptr = static_cast(inst_ptr->GetAttributePointer(0x92378a0a, num_packages)); break; + case 4: ptr = static_cast(inst_ptr->GetAttributePointer(0x16b700d6, num_packages)); break; + default: goto done; + } + break; + case static_cast(1): + switch (level) { + case 0: hash = 0xa1a5e9e5; goto done; + case 1: ptr = static_cast(inst_ptr->GetAttributePointer(0xe4af1260, num_packages)); break; + case 2: ptr = static_cast(inst_ptr->GetAttributePointer(0x70b14851, num_packages)); break; + case 3: ptr = static_cast(inst_ptr->GetAttributePointer(0x8e8b78e1, num_packages)); break; + case 4: ptr = static_cast(inst_ptr->GetAttributePointer(0xb4df5439, num_packages)); break; + default: goto done; + } + break; + case static_cast(2): + switch (level) { + case 0: hash = 0xad6a0504; goto done; + case 1: ptr = static_cast(inst_ptr->GetAttributePointer(0x37ea2169, num_packages)); break; + case 2: ptr = static_cast(inst_ptr->GetAttributePointer(0xe5650914, num_packages)); break; + case 3: ptr = static_cast(inst_ptr->GetAttributePointer(0xe321687d, num_packages)); break; + case 4: ptr = static_cast(inst_ptr->GetAttributePointer(0xfb1ef23f, num_packages)); break; + default: goto done; + } + break; + case static_cast(3): + switch (level) { + case 0: hash = 0x98ed935e; goto done; + case 1: ptr = static_cast(inst_ptr->GetAttributePointer(0x1e823f0b, num_packages)); break; + case 2: ptr = static_cast(inst_ptr->GetAttributePointer(0x79c8d7e9, num_packages)); break; + case 3: ptr = static_cast(inst_ptr->GetAttributePointer(0xa1b53a33, num_packages)); break; + case 4: ptr = static_cast(inst_ptr->GetAttributePointer(0xf424c06d, num_packages)); break; + default: goto done; + } + break; + case static_cast(4): + if (mgr->IsCastrolCar() && level == 4 && num_packages == 2) { + return 0xb95d4df; + } + switch (level) { + case 0: hash = 0x7d0ac98f; goto done; + case 1: ptr = static_cast(inst_ptr->GetAttributePointer(0x512303af, num_packages)); break; + case 2: ptr = static_cast(inst_ptr->GetAttributePointer(0xdb8a8a1d, num_packages)); break; + case 3: ptr = static_cast(inst_ptr->GetAttributePointer(0x4f56a655, num_packages)); break; + case 4: ptr = static_cast(inst_ptr->GetAttributePointer(0x85ab21da, num_packages)); break; + default: goto done; + } + break; + case static_cast(5): + switch (level) { + case 0: hash = 0x9e8f71ad; goto done; + case 1: ptr = static_cast(inst_ptr->GetAttributePointer(0xe141cde, num_packages)); break; + case 2: ptr = static_cast(inst_ptr->GetAttributePointer(0x4d3b62f3, num_packages)); break; + case 3: ptr = static_cast(inst_ptr->GetAttributePointer(0xea7f3fe4, num_packages)); break; + case 4: ptr = static_cast(inst_ptr->GetAttributePointer(0xb6be1d52, num_packages)); break; + default: goto done; + } + break; + case static_cast(6): + switch (level) { + case 0: hash = 0x98ed935e; goto done; + case 1: ptr = static_cast(inst_ptr->GetAttributePointer(0x7f6e85a3, num_packages)); break; + case 2: ptr = static_cast(inst_ptr->GetAttributePointer(0xd810d2dc, num_packages)); break; + case 3: ptr = static_cast(inst_ptr->GetAttributePointer(0xa459ecef, num_packages)); break; + case 4: ptr = static_cast(inst_ptr->GetAttributePointer(0x8da087a4, num_packages)); break; + default: goto done; + } + break; + default: goto done; + } + if (!ptr) { + ptr = static_cast(Attrib::DefaultDataArea(4)); + } + hash = *ptr; +done: + return hash; +} + +#endif // FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE____CUSTOMIZE_CARCUSTOMIZE_H diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.cpp index e69de29bb..8fe418b9e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.cpp @@ -0,0 +1,266 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/FEPkg_GarageMain.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.hpp" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" +#include "Speed/Indep/Src/World/CarInfo.hpp" + +extern cFrontendDatabase *FEDatabase; +extern Timer RealTimer; +extern bool IsMemcardEnabled; +extern unsigned int FEngHashString(const char *, ...); + +void MemcardEnter(const char *from, const char *to, unsigned int op, + void (*termFunc)(void *), void *termParam, + unsigned int successMsg, unsigned int failedMsg); +extern void BeginCarCustomize(eCustomizeEntryPoint entry, FECarRecord *car); + +MyCarsManager::MyCarsManager(ScreenConstructorData *sd) + : ArrayScrollerMenu(sd, 5, 2, true) // + , AccelerationSlider() // + , TopSpeedSlider() // + , HandlingSlider() // + , bGoToShowcase(false) { + bShouldPlaySound = true; + pSelectedCar = nullptr; + tCarLoadTimer.UnSet(); + Setup(); +} + +eMenuSoundTriggers MyCarsManager::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + ArrayScrollerMenu::NotifySoundMessage(msg, maybe); + if (msg == 0x480c9a58) { + return static_cast(5); + } + return maybe; +} + +void MyCarsManager::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + ArrayScrollerMenu::NotificationMessage(msg, obj, param1, param2); + + switch (msg) { + case 0x34dc1bcf: + break; + case 0x35f8620b: + FEDatabase->BackupCarStable(); + break; + case 0xc98356ba: { + if (tCarLoadTimer.IsSet()) { + float elapsed = static_cast(RealTimer.GetPackedTime() - tCarLoadTimer.GetPackedTime()) / 4000.0f; + if (elapsed >= 0.5f && pSelectedCar) { + RideInfo ride; + FEDatabase->GetPlayerCarStable(0)->BuildRideForPlayer(pSelectedCar->Handle, 0, &ride); + CarViewer::SetRideInfo(&ride, static_cast(1), static_cast(0)); + tCarLoadTimer.UnSet(); + } + } + break; + } + case 0x911ab364: { + if (!pSelectedCar) { + RideInfo ride; + FEPlayerCarDB *carDB = FEDatabase->GetPlayerCarStable(0); + RaceSettings *rs = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + carDB->BuildRideForPlayer(rs->SelectedCar[0], 0, &ride); + CarViewer::SetRideInfo(&ride, static_cast(1), static_cast(0)); + } + if (FEDatabase->IsCarStableDirty() && IsMemcardEnabled) { + MemcardEnter(GetPackageName(), "MainMenu.fng", 0x2000b3, nullptr, nullptr, 0, 0); + } else { + cFEng::Get()->QueuePackageSwitch("MainMenu.fng", 0, 0, false); + } + break; + } + case 0xc519bfc4: { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle( + static_cast(GetCurrentDatum())->Handle); + if (car->IsValid()) { + DialogInterface::ShowTwoButtons(GetPackageName(), "", static_cast(1), + 0x70e01038, 0x417b25e4, 0xd05fc3a3, + 0x34dc1bcf, 0x34dc1bcf, static_cast(1), 0x4f68196e); + } + break; + } + case 0xd05fc3a3: { + unsigned int handle = static_cast(GetCurrentDatum())->Handle; + FEPlayerCarDB *carDB = FEDatabase->GetPlayerCarStable(0); + FEDatabase->NotifyDeleteCar(handle); + carDB->DeleteCustomCar(handle); + RefreshCarList(); + if (data.CountElements() >= 2) { + pSelectedCar = carDB->GetCarRecordByHandle( + static_cast(GetCurrentDatum())->Handle); + } else { + pSelectedCar = nullptr; + } + RefreshHeader(); + break; + } + case 0xc519bfbf: + if (pSelectedCar) { + cFEng::Get()->QueuePackageMessage(0x587c018b, GetPackageName(), nullptr); + bGoToShowcase = true; + } + break; + case 0xe1fde1d1: + if (bGoToShowcase) { + Showcase::FromArgs = 0; + Showcase::FromPackage = GetPackageName(); + cFEng::Get()->QueuePackageSwitch("Showcase.fng", reinterpret_cast(pSelectedCar), 0, false); + } + break; + } +} + +void MyCarsManager::Setup() { + for (int i = 0; i < GetWidth() * GetHeight(); i++) { + FEImage *img = FEngFindImage(GetPackageName(), FEngHashString("CAR_ICON_%d", i + 1)); + ImageArraySlot *slot = new ImageArraySlot(img); + AddSlot(slot); + } + descLabel = 0xb271b295; + RefreshCarList(); + AccelerationSlider.Init(GetPackageName(), "ACCELERATION", 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 160.0f); + TopSpeedSlider.Init(GetPackageName(), "TOPSPEED", 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 160.0f); + HandlingSlider.Init(GetPackageName(), "HANDLING", 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 160.0f); + ArrayScroller::RefreshHeader(); +} + +bool MyCarsManager::CanAddMoreCars() { + if (FEDatabase->GetPlayerCarStable(0)->GetNumQuickRaceCars() > 19) { + return false; + } + if (!FEDatabase->GetPlayerCarStable(0)->CanCreateNewCustomizationRecord()) { + return false; + } + return FEDatabase->GetPlayerCarStable(0)->CanCreateNewCarRecord() != false; +} + +void MyCarsManager::RefreshCarList() { + int selectedIdx = 0; + int idx = 0; + ClearData(); + FEPlayerCarDB *carDB = FEDatabase->GetPlayerCarStable(0); + for (int i = 0; i < 200; i++) { + FECarRecord *car = carDB->GetCarByIndex(i); + if (car->IsValid() && car->MatchesFilter(0xf0004)) { + CarDatum *datum = new CarDatum(car->GetManuLogoHash(), car->GetNameHash(), car->Handle); + AddDatum(datum); + RaceSettings *rs = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + if (rs->SelectedCar[0] == static_cast(car->Handle)) { + selectedIdx = idx; + } + idx++; + } + } + if (CanAddMoreCars()) { + CarDatum *datum = new CarDatum(0x91d3a6ba, 0x29ae0714, 0xFFFFFFFF); + AddDatum(datum); + } + SetInitialPosition(selectedIdx); + GarageMainScreen::GetInstance()->DisableCarRendering(); + UpdateCar(); +} + +void MyCarsManager::RefreshHeader() { + ArrayScrollerMenu::RefreshHeader(); + if (GetCurrentDatum()) { + unsigned int handle = static_cast(GetCurrentDatum())->Handle; + FEPlayerCarDB *carDB = FEDatabase->GetPlayerCarStable(0); + FECarRecord *car = carDB->GetCarRecordByHandle(handle); + if (car->IsValid()) { + unsigned int logo = carDB->GetCarRecordByHandle(handle)->GetLogoHash(); + FEImage *img = FEngFindImage(GetPackageName(), 0xb05dd708); + FEngSetTextureHash(img, logo); + } + if (static_cast(GetCurrentDatum())->Handle == 0xFFFFFFFF) { + FEngSetLanguageHash(GetPackageName(), 0xbfa25765, 0xc2598bd8); + cFEng::Get()->QueuePackageMessage(0x42ea22dd, GetPackageName(), nullptr); + } else { + FEngSetLanguageHash(GetPackageName(), 0xbfa25765, 0xc9847935); + cFEng::Get()->QueuePackageMessage(0x06d41ccc, GetPackageName(), nullptr); + for (int i = 0; i < 0xb; i++) { + RaceSettings *rs = FEDatabase->GetQuickRaceSettings(static_cast(i)); + rs->SelectedCar[0] = static_cast(GetCurrentDatum())->Handle; + } + } + FEPrintf(GetPackageName(), 0x6f25a248, "%d", data.IsInList(static_cast(GetCurrentDatum()))); + FEPrintf(GetPackageName(), 0xb2037bdc, "%d", data.CountElements()); + UpdateSliders(); + UpdateCar(); + } +} + +void MyCarsManager::UpdateSliders() { + if (static_cast(GetCurrentDatum())->Handle != 0xFFFFFFFF) { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle( + static_cast(GetCurrentDatum())->Handle); + Physics::Info::Performance performance; + if (car) { + Attrib::Gen::pvehicle pv(car->VehicleKey, 0, nullptr); + if (car->IsCustomized()) { + FECustomizationRecord *cr = FEDatabase->GetPlayerCarStable(0)->GetCustomizationRecordByHandle(car->Customization); + cr->WriteRecordIntoPhysics(pv); + } + Physics::Info::EstimatePerformance(pv, performance); + } + AccelerationSlider.SetValue(performance.Acceleration); + AccelerationSlider.SetPreviewValue(performance.Acceleration); + AccelerationSlider.Draw(); + TopSpeedSlider.SetValue(performance.TopSpeed); + TopSpeedSlider.SetPreviewValue(performance.TopSpeed); + TopSpeedSlider.Draw(); + HandlingSlider.SetValue(performance.Handling); + HandlingSlider.SetPreviewValue(performance.Handling); + HandlingSlider.Draw(); + } +} + +void MyCarsManager::UpdateCar() { + if (GetCurrentDatum()) { + int handle = static_cast(GetCurrentDatum())->Handle; + FEPlayerCarDB *carDB = FEDatabase->GetPlayerCarStable(0); + bool samecar; + if (!pSelectedCar) { + samecar = handle == -1; + } else { + samecar = static_cast(pSelectedCar->Handle) == handle; + } + if (!samecar) { + GarageMainScreen::GetInstance()->DisableCarRendering(); + if (handle == -1) { + cFEng::Get()->QueuePackageMessage(0x913fa282, nullptr, nullptr); + pSelectedCar = nullptr; + CarViewer::CancelCarLoad(static_cast(0)); + } else { + cFEng::Get()->QueuePackageMessage(0xa05a328e, nullptr, nullptr); + pSelectedCar = carDB->GetCarRecordByHandle(handle); + } + tCarLoadTimer = RealTimer; + } + } +} + +void CarDatum::NotificationMessage(u32 msg, FEObject *pObj, u32 param1, u32 param2) { + if (msg == 0xc407210 || msg == 0x406415e3) { + if (Handle == 0xFFFFFFFF) { + FEDatabase->SetGameMode(static_cast(FEDatabase->GetGameMode() | 0x20)); + cFEng::Get()->QueuePackageSwitch("Car_Select.fng", 0, 0, false); + } else { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *carRecord = stable->GetCarRecordByHandle(Handle); + if (carRecord) { + if (!carRecord->IsCustomized()) { + carRecord = stable->CreateNewCustomCar(carRecord->Handle); + } + BeginCarCustomize(static_cast(1), carRecord); + } + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.hpp index ba91cadc3..78d4948cb 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/MyCarsManager.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_MYCARSMANAGER_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_CUSTOMIZE_MYCARSMANAGER_H @@ -5,6 +6,35 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include + +struct FECarRecord; + +// total size: 0x1BC +struct MyCarsManager : public ArrayScrollerMenu { + MyCarsManager(ScreenConstructorData *sd); + ~MyCarsManager() override {} + + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) override; + + void Setup(); + bool CanAddMoreCars(); + void RefreshCarList(); + void RefreshHeader() override; + void UpdateSliders(); + void UpdateCar(); + + TwoStageSlider AccelerationSlider; // offset 0xE8, size 0x44 + TwoStageSlider TopSpeedSlider; // offset 0x12C, size 0x44 + TwoStageSlider HandlingSlider; // offset 0x170, size 0x44 + FECarRecord *pSelectedCar; // offset 0x1B4, size 0x4 + Timer tCarLoadTimer; // offset 0x1B8, size 0x4 + bool bGoToShowcase; // offset 0x1BC, size 0x1 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.cpp index e69de29bb..f033424b0 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.cpp @@ -0,0 +1,113 @@ +#include "uiCredits.hpp" + +#include "Speed/Indep/Src/FEng/FEPackage.h" +#include "Speed/Indep/Src/FEng/FEString.h" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Career/FEGameWonScreen.hpp" +#include "Speed/Indep/Src/Frontend/cFEngRender.hpp" +#include "Speed/Indep/Src/Misc/BuildRegion.hpp" + +FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash); +void FEngSetInvisible(FEObject* obj); +FEString* FEngFindString(const char* pkg_name, int name_hash); +int FEngSNPrintf(char* buffer, int buf_size, const char* fmt, ...); +int GetCurrentLanguage(); +const char* GetLanguageName(eLanguages lang); + +MenuScreen* uiCredits::Create(ScreenConstructorData* sd) { + return new uiCredits(sd); +} + +uiCredits::uiCredits(ScreenConstructorData* sd) + : MenuScreen(sd) // + , initComplete_(false) // + , prototypeStr_(nullptr) // + , pendingDelete_(nullptr) // + , uf_() { + if (!FEDatabase->IsBeatGameMode()) { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xeb4cf244)); + cFEng::Get()->QueuePackageMessage(0x8cb81f09, GetPackageName(), nullptr); + } else { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x0bf41045)); + cFEng::Get()->QueuePackageMessage(0x3111b806, GetPackageName(), nullptr); + } +} + +uiCredits::~uiCredits() {} + +void uiCredits::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, + unsigned long param2) { + switch (msg) { + case 0x35f8620b: { + char filename[32]; + const char* languageName = + GetLanguageName(static_cast(GetCurrentLanguage())); + const char* prefix = ""; + if (GetCurrentLanguage() == eLANGUAGE_ENGLISH) { + if (BuildRegion::IsAmerica()) { + prefix = "NA_"; + } else if (BuildRegion::IsEurope()) { + prefix = "UK_"; + } else { + languageName = "GERMAN"; + } + } + FEngSNPrintf(filename, 0x20, "CREDITS\\%s%s.TXT", prefix, languageName); + uf_.Load(filename); + uf_.LineWrap(0x2d); + prototypeStr_ = FEngFindString(GetPackageName(), FEHashUpper("CreditsArea")); + initComplete_ = true; + break; + } + case 0xe1fde1d1: + uf_.Unload(); + initComplete_ = false; + if (!FEDatabase->IsBeatGameMode()) { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } else { + FEGameWonScreen::QueuePackageSwitchForNextScreen(); + } + break; + case 0xc98356ba: + if (pendingDelete_ != nullptr) { + ConstructData.pPackage->Objects.RemNode(pendingDelete_); + cFEngRender::mInstance->RemoveCachedRender(pendingDelete_, nullptr); + delete pendingDelete_; + pendingDelete_ = nullptr; + } + break; + case 0xe6e946b8: + if (initComplete_) { + short* creditLine = uf_.Next(); + if (creditLine == nullptr) { + creditLine = uf_.First(); + } + if (creditLine != nullptr) { + FEString* ns = static_cast(prototypeStr_->Clone(false)); + ns->Cached = nullptr; + *ns->GetObjData() = *prototypeStr_->GetObjData(); + ns->SetString(creditLine); + ns->Flags |= 0x400000; + if (!FEDatabase->IsBeatGameMode()) { + ns->SetScript(FEHashUpper("RollCredit"), false); + } else { + ns->SetScript(FEHashUpper("RollCredit_ENDGAME"), false); + } + ConstructData.pPackage->Objects.AddNode(ConstructData.pPackage->Objects.GetTail(), ns); + } + } + break; + case 0x29161540: + pendingDelete_ = pobj; + break; + case 0x911ab364: + cFEng::Get()->QueuePackageMessage(0x587c018b, nullptr, nullptr); + break; + case 0x406415e3: + if (FEDatabase->IsBeatGameMode()) { + cFEng::Get()->QueuePackageMessage(0x587c018b, nullptr, nullptr); + } + break; + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.hpp index 8d135f663..5689943aa 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiCredits.hpp @@ -5,6 +5,23 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/UnicodeFile.hpp" +struct FEString; + +// total size: 0x48 +struct uiCredits : public MenuScreen { + bool initComplete_; // offset 0x2C + FEString* prototypeStr_; // offset 0x30 + short* creditLine_; // offset 0x34 + FEObject* pendingDelete_; // offset 0x38 + UnicodeFile uf_; // offset 0x3C + + uiCredits(ScreenConstructorData* sd); + ~uiCredits() override; + static MenuScreen* Create(ScreenConstructorData* sd); + void NotificationMessage(unsigned long, FEObject*, unsigned long, unsigned long) override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp index e69de29bb..5fea27d8e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.cpp @@ -0,0 +1,319 @@ +#include "uiEATraxJukebox.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Generated/Messages/MControlPathfinder.h" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +struct stSongInfo { + char* SongName; // offset 0x0, size 0x4 + char* Artist; // offset 0x4, size 0x4 + char* Album; // offset 0x8, size 0x4 + char* DefPlay; // offset 0xC, size 0x4 + int PathEvent; // offset 0x10, size 0x4 +}; + +struct SongInfoList { + stSongInfo** _M_start; + stSongInfo** _M_finish; + + unsigned int size() const { + return static_cast< unsigned int >((_M_finish - _M_start)); + } + + stSongInfo*& operator[](unsigned int n) { + return _M_start[n]; + } +}; + +extern SongInfoList Songs; +extern cFrontendDatabase* FEDatabase; + +FEString* FEngFindString(const char* pkg_name, int name_hash); +unsigned int FEngHashString(const char* fmt, ...); +void GetLocalizedString(char* buf, unsigned int buf_size, unsigned int hash); + +void UIEATraxScreen::AddTrackSlot(ScrollerSlot* slot, unsigned int baseHash, int num) { + FEObject* string = FEngFindObject(GetPackageName(), baseHash + num); + slot->AddData(string); +} + +UIEATraxScreen::UIEATraxScreen(ScreenConstructorData* sd) + : MenuScreen(sd) // + , Tracks(GetPackageName(), "ARRAY_SCROLL_REGION", "ScrollBar", true, true, false, true) // +{ + const unsigned long FEObj_TrackModeType = 0xCA74A2FA; + NumSlots = 4; + bTrackGrabbed = false; + Tracks.SetMouseDownMsg(1); + Tracks.SetClickToSelectMode(true); + NumSongs = Songs.size(); + trackOrder = FEngFindString(GetPackageName(), FEObj_TrackModeType); + JukeboxEntry* playlist = FEDatabase->GetUserProfile(0)->Playlist; + OriginalPlaylist = new (__FILE__, __LINE__) JukeboxEntry[NumSongs]; + bMemCpy(OriginalPlaylist, playlist, NumSongs * sizeof(JukeboxEntry)); + OriginalPlayState = FEDatabase->GetAudioSettings()->PlayState; + SetupSongList(); +} + +UIEATraxScreen::~UIEATraxScreen() { + if (OriginalPlaylist != nullptr) { + delete[] OriginalPlaylist; + } +} + +void UIEATraxScreen::RefreshHeader() { + unsigned int hash = + GetStateString(static_cast< unsigned char >(FEDatabase->GetAudioSettings()->PlayState)); + FEngSetLanguageHash(trackOrder, hash); +} + +unsigned int UIEATraxScreen::GetPlaybilityString(unsigned char playability) { + switch (playability) { + case 0: + return 0x9CCE9F86; + case 1: + return 0x5278C50B; + case 2: + return 0x5C1B351C; + case 3: + return 0x9CCE64C4; + default: + return 0; + } +} + +unsigned int UIEATraxScreen::GetStateString(unsigned char state) { + switch (state) { + case 0: + return 0x4CA36B89; + case 1: + return 0xA9C9C8F7; + default: + return 0; + } +} + +void UIEATraxScreen::SetupSongList() { + char playability_string[128]; + char num_string[8]; + + for (int i = 0; i < NumSlots; i++) { + JukeBoxScrollerSlot* slot = new (__FILE__, __LINE__) JukeBoxScrollerSlot(); + slot->SetBacking( + FEngFindObject(GetPackageName(), FEngHashString("MOUSE_REGION_%d", i + 1))); + AddTrackSlot(slot, 0xE454E9A5, i); + AddTrackSlot(slot, 0x66646FC4, i); + AddTrackSlot(slot, 0x2890C8AF, i); + AddTrackSlot(slot, 0x77BD189E, i); + AddTrackSlot(slot, 0xF3EBDC4E, i); + Tracks.AddSlot(slot); + } + + for (int i = 0; i < NumSongs; i++) { + JukeBoxScrollerDatum* datum = new (__FILE__, __LINE__) JukeBoxScrollerDatum(); + Tracks.AddData(datum); + + FEngSNPrintf(num_string, 8, "%.2d", i + 1); + datum->AddData(num_string, 0); + + JukeboxEntry* playlist = FEDatabase->GetUserProfile(0)->Playlist; + datum->AddData(Songs[playlist[i].SongIndex]->Artist, 0); + datum->AddData(Songs[playlist[i].SongIndex]->SongName, 0); + datum->AddData(Songs[playlist[i].SongIndex]->Album, 0); + datum->SongIndex = playlist[i].SongIndex; + datum->PlayabilityField = playlist[i].PlayabilityField; + + GetLocalizedString(playability_string, 128, + GetPlaybilityString(datum->PlayabilityField)); + datum->AddData(playability_string, 0); + } + + Tracks.Update(true); + RefreshHeader(); +} + +void UIEATraxScreen::ScrollOrderState(unsigned long msg) { + unsigned char state = FEDatabase->GetAudioSettings()->PlayState; + FEDatabase->GetAudioSettings()->PlayState = (state == 0); + RefreshHeader(); + MControlPathfinder(true, 0, 0, 0).Send("EATraxInit"); +} + +void UIEATraxScreen::ScrollTracks(unsigned long msg) { + if (msg == 0x72619778) { + if (!Tracks.IsAtHead()) { + Tracks.ScrollPrev(); + } + } else { + if (!Tracks.IsAtTail()) { + Tracks.ScrollNext(); + } + } +} + +void UIEATraxScreen::ScrollTrackPlayability(unsigned long msg) { + JukeBoxScrollerDatum* datum = + static_cast< JukeBoxScrollerDatum* >(Tracks.GetSelectedDatum()); + JukeboxEntry* entry; + JukeboxEntry* playlist = FEDatabase->GetUserProfile(0)->Playlist; + int play_flag; + ScrollerDatumNode* node; + + entry = playlist; + for (int i = 0; i < NumSongs; i++) { + if (playlist[i].SongIndex == datum->SongIndex) { + entry = &playlist[i]; + break; + } + } + + play_flag = entry->PlayabilityField; + if (msg == 0x9120409E) { + play_flag--; + if (play_flag < 0) { + play_flag = 3; + } + } else if (msg == 0xB5971BF1) { + play_flag++; + if (play_flag > 3) { + play_flag = 0; + } + } + entry->PlayabilityField = play_flag; + datum->PlayabilityField = play_flag; + + node = datum->Strings.GetTail(); + const char* playabilityString = GetLocalizedString(GetPlaybilityString(entry->PlayabilityField)); + FEngSNPrintf(node->String, 128, playabilityString); + Tracks.Update(true); + MControlPathfinder(true, 0, 0, 0).Send("EATraxInit"); +} + +void UIEATraxScreen::MoveTrack(unsigned long msg) { + ScrollerSlot* old_slot = Tracks.GetSelectedSlot(); + int oldSlotIndex = Tracks.GetSelectedSlotIndex(); + + if (msg == 0x72619778) { + Tracks.MovePrev(); + } else if (msg == 0x911C0A4B) { + Tracks.MoveNext(); + } + + int num = 1; + ScrollerDatum* datum = Tracks.GetFirstDatum(); + while (datum != Tracks.GetLastDatum()) { + ScrollerDatumNode* node = datum->Strings.GetHead(); + if (node != nullptr) { + FEngSNPrintf(node->String, 8, "%.2d", num); + num++; + } + datum = datum->GetNext(); + } + Tracks.Update(true); + ReInsertSong(); +} + +void UIEATraxScreen::PreviewSong() { + JukeBoxScrollerDatum* cur_datum = + static_cast< JukeBoxScrollerDatum* >(Tracks.GetSelectedDatum()); + int path_event = Songs[cur_datum->SongIndex]->PathEvent; + MControlPathfinder(true, path_event, 0, 0).Send("Pathfinder5"); +} + +void UIEATraxScreen::ReInsertSong() { + JukeboxEntry* playlist = FEDatabase->GetUserProfile(0)->Playlist; + JukeBoxScrollerDatum* datum = + static_cast< JukeBoxScrollerDatum* >(Tracks.GetFirstDatum()); + + for (int i = 0; i < NumSongs; i++) { + playlist[i].SongIndex = datum->SongIndex; + playlist[i].PlayabilityField = datum->PlayabilityField; + datum = static_cast< JukeBoxScrollerDatum* >(datum->GetNext()); + } + + MControlPathfinder(true, 0, 0, 0).Send("EATraxInit"); +} + +void UIEATraxScreen::NotificationMessage(unsigned long msg, FEObject* pObject, unsigned long Param1, + unsigned long Param2) { + switch (msg) { + case 0x35F8620B: { + ScrollerSlot* slot = Tracks.GetSelectedSlot(); + if (slot != nullptr) { + slot->SetScript(0x249DB7B7); + } + break; + } + case 0x5073EF13: + case 0xD9FEEC59: + ScrollOrderState(msg); + break; + case 0x9120409E: + case 0xB5971BF1: + ScrollTrackPlayability(msg); + break; + case 0x72619778: + case 0x911C0A4B: + if (bTrackGrabbed) { + MoveTrack(msg); + } else { + ScrollTracks(msg); + } + break; + case 0x911AB364: + if (OptionsDidNotChange()) { + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), nullptr); + } else { + const char* blurb = GetLocalizedString(0xE9CB802F); + DialogInterface::ShowTwoButtons(GetPackageName(), "Dialog.fng", + static_cast< eDialogTitle >(1), 0x70E01038, 0x417B25E4, + 0x775DBA97, 0x34DC1BCF, 0x34DC1BCF, + static_cast< eDialogFirstButtons >(1), blurb); + } + MControlPathfinder(true, 0, 0, 0).Send("EATraxInit"); + break; + case 0x775DBA97: + RestoreOriginals(); + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), nullptr); + break; + case 0xC519BFC4: + bTrackGrabbed = bTrackGrabbed ^ 1; + break; + case 0xC519BFC3: + PreviewSong(); + break; + case 0xE1FDE1D1: + MControlPathfinder(true, 0xFFFFFFFF, 0, 0).Send("EATraxInit"); + { + bool dirty = false; + if (FEDatabase->IsOptionsDirty() || !OptionsDidNotChange()) { + dirty = true; + } + FEDatabase->SetOptionsDirty(dirty); + } + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + break; + } +} + +bool UIEATraxScreen::OptionsDidNotChange() { + bool ret; + JukeboxEntry* playlist = FEDatabase->GetUserProfile(0)->Playlist; + int cmp = bMemCmp(playlist, OriginalPlaylist, NumSongs * sizeof(JukeboxEntry)); + ret = (cmp == 0); + if (FEDatabase->GetAudioSettings()->PlayState != OriginalPlayState) { + ret = false; + } + return ret; +} + +void UIEATraxScreen::RestoreOriginals() { + JukeboxEntry* playlist = FEDatabase->GetUserProfile(0)->Playlist; + bMemCpy(playlist, OriginalPlaylist, NumSongs * sizeof(JukeboxEntry)); + FEDatabase->GetAudioSettings()->PlayState = OriginalPlayState; +} + +static MenuScreen* CreateUIEATraxScreen(ScreenConstructorData* sd) { + return new (__FILE__, __LINE__) UIEATraxScreen(sd); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.hpp index c3a15d95c..4616182d6 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiEATraxJukebox.hpp @@ -5,6 +5,57 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feScrollerina.hpp" +struct JukeboxEntry; + +struct JukeBoxScrollerSlot : public ScrollerSlot { + JukeBoxScrollerSlot() {} + ~JukeBoxScrollerSlot() override {} +}; + +struct JukeBoxScrollerDatum : public ScrollerDatum { + unsigned int SongIndex; // offset 0x18, size 0x4 + unsigned char PlayabilityField; // offset 0x1C, size 0x1 + + JukeBoxScrollerDatum() + : SongIndex(0) // + , PlayabilityField(0) {} + ~JukeBoxScrollerDatum() override {} +}; + +// total size: 0x120 +struct UIEATraxScreen : public MenuScreen { + Scrollerina Tracks; // offset 0x2C, size 0xC8 + unsigned char playOrder; // offset 0xF4, size 0x1 + FEString* trackOrder; // offset 0xF8, size 0x4 + FEObject* pSlotHighlight[4]; // offset 0xFC, size 0x10 + int NumSlots; // offset 0x10C, size 0x4 + int NumSongs; // offset 0x110, size 0x4 + JukeboxEntry* OriginalPlaylist; // offset 0x114, size 0x4 + int OriginalPlayState; // offset 0x118, size 0x4 + int bTrackGrabbed; // offset 0x11C, size 0x1 + + UIEATraxScreen(ScreenConstructorData* sd); + ~UIEATraxScreen() override; + + void AddTrackSlot(ScrollerSlot* slot, unsigned int baseHash, int num); + void RefreshHeader(); + unsigned int GetPlaybilityString(unsigned char playability); + unsigned int GetStateString(unsigned char state); + void SetupSongList(); + void ScrollOrderState(unsigned long msg); + void ScrollTracks(unsigned long msg); + void ScrollTrackPlayability(unsigned long msg); + void MoveTrack(unsigned long msg); + void PreviewSong(); + void ReInsertSong(); + void NotificationMessage(unsigned long msg, FEObject* pObject, unsigned long Param1, + unsigned long Param2) override; + bool OptionsDidNotChange(); + void RestoreOriginals(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.cpp index e69de29bb..13be6eb72 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.cpp @@ -0,0 +1,757 @@ +#include "uiOptionWidgets.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.hpp" +#include "Speed/Indep/Src/Input/IOModule.h" +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +#ifdef EA_PLATFORM_GAMECUBE +#include "Speed/GameCube/bWare/GameCube/dolphinsdk/include/dolphin/os.h" +#endif + +struct FEString; +struct FEObject; +#include "Speed/Indep/Src/FEng/feimage.h" + +void FEngSetLanguageHash(FEString* text, unsigned int hash); +int FEPrintf(FEString* text, const char* fmt, ...); +FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash); +void FEngSetInvisible(FEObject* obj); +void FEngSetVisible(FEObject* obj); +void FEngSetInvisible(const char* pkg_name, unsigned int obj_hash); +void FEngSetVisible(const char* pkg_name, unsigned int obj_hash); + +enum POVTypes { + POV_BUMPER = 0, + POV_HOOD = 1, + POV_OUTSIDE_CLOSE = 2, + POV_OUTSIDE_FAR = 3, + POV_SUPER_FAR = 4, + POV_DRIFT = 5, + POV_PURSUIT = 6, + NUM_POV_TYPES = 7, +}; +POVTypes GetPOVTypeFromPlayerCamera(ePlayerSettingsCameras cam); +bool IsPlayerCameraSelectable(POVTypes pov_type); + +void MemcardEnter(const char* from, const char* to, unsigned int op, + void (*pTermFunc)(void*), void* pTermFuncParam, + unsigned int msgSuccess, unsigned int msgFailed); + +void OMAudio::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, + unsigned int param2) { + if (data != 0xC407210) { + return; + } + FEDatabase->GetOptionsSettings()->CurrentCategory = OC_AUDIO; +} + +void OMVideo::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, + unsigned int param2) { + if (data != 0xC407210) { + return; + } + FEDatabase->GetOptionsSettings()->CurrentCategory = OC_VIDEO; +} + +void OMGameplay::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, + unsigned int param2) { + if (data != 0xC407210) { + return; + } + FEDatabase->GetOptionsSettings()->CurrentCategory = OC_GAMEPLAY; +} + +void OMPlayer::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, + unsigned int param2) { + if (data != 0xC407210) { + return; + } + FEDatabase->GetOptionsSettings()->CurrentCategory = OC_PLAYER; +} + +void OMController::React(const char* pkg_name, unsigned int data, FEObject* obj, + unsigned int param1, unsigned int param2) { + if (data != 0xC407210) { + return; + } + FEDatabase->GetOptionsSettings()->CurrentCategory = OC_CONTROLS; +} + +void OMEATrax::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, + unsigned int param2) { + if (data != 0xC407210) { + return; + } + FEDatabase->GetOptionsSettings()->CurrentCategory = OC_EATRAX; +} + +void OMCredits::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, + unsigned int param2) { + if (data != 0xC407210) { + return; + } + FEDatabase->GetOptionsSettings()->CurrentCategory = OC_CREDITS; +} + +void AOSFXMasterVol::Act(const char* parent_pkg, unsigned int data) { + UpdateSlider(data); + FEDatabase->GetAudioSettings()->SoundEffectsVol = GetValue(); + g_pEAXSound->UpdateVolumes(FEDatabase->GetAudioSettings(), GetValue()); + Update(data); +} + +void AOSFXMasterVol::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xFD487543); + SetValue(FEDatabase->GetAudioSettings()->SoundEffectsVol); + DrawSlider(); +} + +void AOSFXMasterVol::SetInitialValues() { + SetSliderValues(0.0f, 1.0f, 0.1f, FEDatabase->GetAudioSettings()->SoundEffectsVol); +} + +void AOInteractiveMusicMode::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + FEDatabase->GetAudioSettings()->InteractiveMusicMode = + static_cast(FEDatabase->GetAudioSettings()->InteractiveMusicMode == 0); + } + Update(data); +} + +void AOInteractiveMusicMode::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xA3DBB390); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetAudioSettings()->InteractiveMusicMode != 0) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void AOEATraxMusicMode::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + FEDatabase->GetAudioSettings()->EATraxMode = + static_cast(FEDatabase->GetAudioSettings()->EATraxMode == 0); + } + Update(data); +} + +void AOEATraxMusicMode::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xDCFB6B36); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetAudioSettings()->EATraxMode != 0) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void AOCarVol::Act(const char* parent_pkg, unsigned int data) { + UpdateSlider(data); + FEDatabase->GetAudioSettings()->CarVol = GetValue(); + g_pEAXSound->UpdateVolumes(FEDatabase->GetAudioSettings(), GetValue()); + Update(data); +} + +void AOCarVol::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x218E4B08); + SetValue(FEDatabase->GetAudioSettings()->CarVol); + DrawSlider(); +} + +void AOCarVol::SetInitialValues() { + SetSliderValues(0.0f, 1.0f, 0.1f, FEDatabase->GetAudioSettings()->CarVol); +} + +void AOSpeechVol::Act(const char* parent_pkg, unsigned int data) { + UpdateSlider(data); + FEDatabase->GetAudioSettings()->SpeechVol = GetValue(); + g_pEAXSound->UpdateVolumes(FEDatabase->GetAudioSettings(), GetValue()); + Update(data); +} + +void AOSpeechVol::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x9E5FB82A); + SetValue(FEDatabase->GetAudioSettings()->SpeechVol); + DrawSlider(); +} + +void AOSpeechVol::SetInitialValues() { + SetSliderValues(0.0f, 1.0f, 0.1f, FEDatabase->GetAudioSettings()->SpeechVol); +} + +void AOFEMusicVol::Act(const char* parent_pkg, unsigned int data) { + UpdateSlider(data); + float value = GetValue(); + if (bEqual(0.0f, value, 0.001f)) { + value = 0.0f; + } + FEDatabase->GetAudioSettings()->FEMusicVol = value; + g_pEAXSound->UpdateVolumes(FEDatabase->GetAudioSettings(), GetValue()); + Update(data); +} + +void AOFEMusicVol::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x418E681D); + SetValue(FEDatabase->GetAudioSettings()->FEMusicVol); + DrawSlider(); +} + +void AOFEMusicVol::SetInitialValues() { + SetSliderValues(0.0f, 1.0f, 0.1f, FEDatabase->GetAudioSettings()->FEMusicVol); +} + +void AOIGMusicVol::Act(const char* parent_pkg, unsigned int data) { + UpdateSlider(data); + FEDatabase->GetAudioSettings()->IGMusicVol = GetValue(); + g_pEAXSound->UpdateVolumes(FEDatabase->GetAudioSettings(), GetValue()); + Update(data); +} + +void AOIGMusicVol::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xDF21EAC2); + SetValue(FEDatabase->GetAudioSettings()->IGMusicVol); + DrawSlider(); +} + +void AOIGMusicVol::SetInitialValues() { + SetSliderValues(0.0f, 1.0f, 0.1f, FEDatabase->GetAudioSettings()->IGMusicVol); +} + +void AOAudioMode::Act(const char* parent_pkg, unsigned int data) { + int mode = FEDatabase->GetAudioSettings()->AudioMode; + if (data == 0x9120409E) { + mode--; + if (mode < 0) { + mode = 2; + } + } else if (data == 0xB5971BF1) { + mode++; + if (mode > 2) { + mode = 0; + } + } + if (FEDatabase->GetAudioSettings()->AudioMode != mode) { +#ifdef EA_PLATFORM_GAMECUBE + OSSetSoundMode(mode != 0); +#endif + } + FEDatabase->GetAudioSettings()->AudioMode = mode; + Update(data); +} + +void AOAudioMode::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x2881AB87); + unsigned int hash = 0; + int mode = FEDatabase->GetAudioSettings()->AudioMode; + switch (mode) { + case 0: + hash = 0xC50FA35F; + break; + case 1: + hash = 0x55DA8BF8; + break; + case 2: + hash = 0xF6FAFF24; + break; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void VOWideScreen::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + FEDatabase->GetVideoSettings()->WideScreen ^= 1; + } + Update(data); +} + +void VOWideScreen::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xD3588630); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetVideoSettings()->WideScreen) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void GODamage::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + FEDatabase->GetGameplaySettings()->Damage ^= 1; + } + Update(data); +} + +void GODamage::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x1582ADFF); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetGameplaySettings()->Damage) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void GOAutoSave::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + FEDatabase->GetGameplaySettings()->AutoSaveOn ^= 1; + if (FEDatabase->GetGameplaySettings()->AutoSaveOn) { + MemcardEnter(parent_pkg, parent_pkg, 0xA1, nullptr, nullptr, 0, 0); + } + } + Update(data); +} + +void GOAutoSave::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xD1056C88); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetGameplaySettings()->AutoSaveOn) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void GOJumpCams::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + FEDatabase->GetGameplaySettings()->JumpCam ^= 1; + } + Update(data); +} + +void GOJumpCams::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xF26A5CBF); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetGameplaySettings()->JumpCam) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void GORearview::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + FEDatabase->GetGameplaySettings()->RearviewOn ^= 1; + } + Update(data); +} + +void GORearview::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x85A6CE05); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetGameplaySettings()->RearviewOn) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void GOSpeedoUnits::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + unsigned char units = FEDatabase->GetGameplaySettings()->SpeedoUnits; + if (units == 0) { + FEDatabase->GetGameplaySettings()->SpeedoUnits = 1; + } else if (units == 1) { + FEDatabase->GetGameplaySettings()->SpeedoUnits = 0; + } + } + Update(data); +} + +void GOSpeedoUnits::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x01E19173); + unsigned int hash = 0xAF70E736; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 0) { + hash = 0xFBD74FC5; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void GORacingMiniMap::Act(const char* parent_pkg, unsigned int data) { + int mode = FEDatabase->GetGameplaySettings()->RacingMiniMapMode; + if (data == 0x9120409E) { + mode--; + if (mode < 0) { + mode = 2; + } + } else if (data == 0xB5971BF1) { + mode++; + if (mode > 2) { + mode = 0; + } + } + FEDatabase->GetGameplaySettings()->RacingMiniMapMode = static_cast(mode); + Update(data); +} + +void GORacingMiniMap::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x9FA5EC9E); + unsigned int hash = 0; + unsigned char mode = FEDatabase->GetGameplaySettings()->RacingMiniMapMode; + switch (mode) { + case 1: + hash = 0xF4B00E99; + break; + case 0: + hash = 0xF75595F2; + break; + case 2: + hash = 0x70DFE5C2; + break; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void GOExploringMiniMap::Act(const char* parent_pkg, unsigned int data) { + int mode = FEDatabase->GetGameplaySettings()->ExploringMiniMapMode; + if (data == 0x9120409E) { + mode--; + if (mode < 0) { + mode = 2; + } + } else if (data == 0xB5971BF1) { + mode++; + if (mode > 2) { + mode = 0; + } + } + FEDatabase->GetGameplaySettings()->ExploringMiniMapMode = static_cast(mode); + Update(data); +} + +void GOExploringMiniMap::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xC6269082); + unsigned int hash = 0; + unsigned char mode = FEDatabase->GetGameplaySettings()->ExploringMiniMapMode; + switch (mode) { + case 1: + hash = 0xF4B00E99; + break; + case 0: + hash = 0xF75595F2; + break; + case 2: + hash = 0x70DFE5C2; + break; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void POTransmission::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + int player = GetPlayerToEditForOptions(); + unsigned char trans = FEDatabase->GetPlayerSettings(player)->Transmission; + if (trans == 0) { + trans = 1; + } else if (trans == 1) { + trans = 0; + } + player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->Transmission = trans; + } + Update(data); +} + +void POTransmission::Draw() { + unsigned int hash = 0; + FEngSetLanguageHash(GetTitleObject(), 0xD31407E7); + int player = GetPlayerToEditForOptions(); + unsigned char trans = FEDatabase->GetPlayerSettings(player)->Transmission; + switch (trans) { + case 0: + hash = 0x8CD532A0; + break; + case 1: + hash = 0x317D3005; + break; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void PODriveCam::Act(const char* parent_pkg, unsigned int data) { + int player = GetPlayerToEditForOptions(); + int cam = static_cast(FEDatabase->GetPlayerSettings(player)->CurCam); + if (data == 0x9120409E) { + do { + cam--; + if (cam < 0) { + cam = 6; + } + } while (!IsPlayerCameraSelectable( + GetPOVTypeFromPlayerCamera(static_cast(cam)))); + } else if (data == 0xB5971BF1) { + do { + cam++; + if (cam > 6) { + cam = 0; + } + } while (!IsPlayerCameraSelectable( + GetPOVTypeFromPlayerCamera(static_cast(cam)))); + } + player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->CurCam = static_cast(cam); + Update(data); +} + +void PODriveCam::Draw() { + unsigned int hash = 0; + FEngSetLanguageHash(GetTitleObject(), 0xF6CCDC5F); + int player = GetPlayerToEditForOptions(); + int cam = static_cast(FEDatabase->GetPlayerSettings(player)->CurCam); + switch (cam) { + case 3: + hash = 0x1EA4CEC2; + break; + case 2: + hash = 0x5AE3441F; + break; + case 1: + hash = 0x414F19D7; + break; + case 0: + hash = 0xC3E9AE58; + break; + case 4: + hash = 0x916039B4; + break; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void POGauges::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + int player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->GaugesOn = + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->GaugesOn ^ 1; + } + Update(data); +} + +void POGauges::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xAC148579); + int player = GetPlayerToEditForOptions(); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetPlayerSettings(player)->GaugesOn) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void POPosition::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + int player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->PositionOn = + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->PositionOn ^ 1; + } + Update(data); +} + +void POPosition::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0x82CD8F92); + int player = GetPlayerToEditForOptions(); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetPlayerSettings(player)->PositionOn) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void POScore::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + int player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->ScoreOn = + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->ScoreOn ^ 1; + } + Update(data); +} + +void POScore::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xC03F9F19); + int player = GetPlayerToEditForOptions(); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetPlayerSettings(player)->ScoreOn) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void POSplitTime::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + int type = (!FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->SplitTimeType) + << 2; + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->SplitTimeType = type; + } + Update(data); +} + +void POSplitTime::Draw() { + unsigned int hash = 0; + FEngSetLanguageHash(GetTitleObject(), 0x084BC378); + int player = GetPlayerToEditForOptions(); + unsigned char splitTime = FEDatabase->GetPlayerSettings(player)->SplitTimeType; + switch (splitTime) { + case 0: + hash = 0x417B2604; + break; + case 1: + hash = 0xC44D3943; + break; + case 2: + hash = 0x17FAFC32; + break; + case 3: + hash = 0x1EA459F8; + break; + case 4: + hash = 0x70DFE5C2; + break; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void POLeaderBoard::Act(const char* parent_pkg, unsigned int data) { + if (data == 0x9120409E || data == 0xB5971BF1) { + int player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->LeaderboardOn = + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->LeaderboardOn ^ 1; + } + Update(data); +} + +void POLeaderBoard::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xC93FBFB5); + int player = GetPlayerToEditForOptions(); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetPlayerSettings(player)->LeaderboardOn) { + hash = 0x417B2604; + } + FEngSetLanguageHash(GetDataObject(), hash); +} + +void COVibration::Act(const char* parent_pkg, unsigned int data) { + switch (data) { + case 0x9120409E: { + int player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->Rumble = 0; + FEngSetInvisible(parent_pkg, 0xBFF41BD9); + FEngSetInvisible(parent_pkg, 0x7BCD6703); + FEngSetInvisible(GetLeftImage()); + FEngSetVisible(parent_pkg, 0xBEE65E8C); + FEngSetVisible(parent_pkg, 0x7C51B6D6); + FEngSetVisible(GetRightImage()); + break; + } + default: + goto end; + case 0xB5971BF1: { + int player = GetPlayerToEditForOptions(); + if (FEDatabase->GetPlayerSettings(player)->Rumble) { + return; + } + player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->Rumble = 1; + FEngSetInvisible(parent_pkg, 0xBEE65E8C); + FEngSetInvisible(parent_pkg, 0x7C51B6D6); + FEngSetInvisible(GetRightImage()); + FEngSetVisible(parent_pkg, 0xBFF41BD9); + FEngSetVisible(parent_pkg, 0x7BCD6703); + FEngSetVisible(GetLeftImage()); + break; + } + } + { + int player = GetPlayerToEditForOptions(); + if (FEDatabase->GetPlayerSettings(player)->Rumble) { + InputDevice* device = IOModule::GetIOModule().GetDevice( + FEDatabase->GetPlayersJoystickPort(GetPlayerToEditForOptions())); + if (device != nullptr) { + device->StartVibration(); + } + } + } +end: + Update(data); +} + +void COVibration::Draw() { + FEngSetLanguageHash(GetTitleObject(), 0xDDDC5E1B); + FEString* dataObj = GetDataObject(); + int player = GetPlayerToEditForOptions(); + unsigned int hash = 0x70DFE5C2; + if (FEDatabase->GetPlayerSettings(player)->Rumble) { + hash = 0x417B2604; + } + FEngSetLanguageHash(dataObj, hash); +} + +void COVibration::UnsetFocus() { + const unsigned long FEObj_ArrowMainLeft = 0xBFF41BD9; + const unsigned long FEObj_LEFTARROW0 = 0x7BCD6703; + const unsigned long FEObj_ArrowMainRight = 0xBEE65E8C; + const unsigned long FEObj_RIGHTARROW0 = 0x7C51B6D6; + FEngSetVisible("Pause_Controller.fng", FEObj_ArrowMainLeft); + FEngSetVisible("Pause_Controller.fng", FEObj_LEFTARROW0); + FEngSetVisible(GetLeftImage()); + FEngSetVisible("Pause_Controller.fng", FEObj_ArrowMainRight); + FEngSetVisible("Pause_Controller.fng", FEObj_RIGHTARROW0); + FEngSetVisible(GetRightImage()); + FEToggleWidget::UnsetFocus(); +} + +void COVibration::SetFocus(const char* parent_pkg) { + int player = GetPlayerToEditForOptions(); + if (FEDatabase->GetPlayerSettings(player)->Rumble) { + const unsigned long FEObj_ArrowMainRight = 0xBEE65E8C; + const unsigned long FEObj_RIGHTARROW0 = 0x7C51B6D6; + FEngSetInvisible("Pause_Controller.fng", FEObj_ArrowMainRight); + FEngSetInvisible("Pause_Controller.fng", FEObj_RIGHTARROW0); + FEngSetInvisible(GetRightImage()); + } else { + const unsigned long FEObj_ArrowMainLeft = 0xBFF41BD9; + const unsigned long FEObj_LEFTARROW0 = 0x7BCD6703; + FEngSetInvisible("Pause_Controller.fng", FEObj_ArrowMainLeft); + FEngSetInvisible("Pause_Controller.fng", FEObj_LEFTARROW0); + FEngSetInvisible(GetLeftImage()); + } + FEToggleWidget::SetFocus(parent_pkg); +} + +void COConfig::Act(const char* parent_pkg, unsigned int data) { + int player = GetPlayerToEditForOptions(); + int config = static_cast(FEDatabase->GetPlayerSettings(player)->Config); + player = GetPlayerToEditForOptions(); + int isAnalogSwiched = FEDatabase->GetPlayerSettings(player)->DriveWithAnalog; + if (UIOptionsController::isWheelConfig) { + config = 0; + isAnalogSwiched = 1; + } else { + if (data == 0x9120409E) { + config--; + if (config < 0) { + isAnalogSwiched ^= 1; + config = MAX_CONFIG; + } + } else if (data == 0xB5971BF1) { + config++; + if (config > MAX_CONFIG) { + isAnalogSwiched ^= 1; + config = 0; + } + } + } + player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->DriveWithAnalog = isAnalogSwiched; + player = GetPlayerToEditForOptions(); + FEDatabase->GetPlayerSettings(player)->Config = static_cast(config); + cFEng::Get()->QueueGameMessage(0x92B703B5, parent_pkg, 0xFF); + Update(data); +} + +void COConfig::Draw() { + int player = GetPlayerToEditForOptions(); + int config = static_cast(FEDatabase->GetPlayerSettings(player)->Config); + player = GetPlayerToEditForOptions(); + int val = config + 1; + if (!FEDatabase->GetPlayerSettings(player)->DriveWithAnalog) { + val = config + 6; + } + FEngSetLanguageHash(GetTitleObject(), 0xBA7306AA); + FEPrintf(GetDataObject(), "%d", val); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.hpp index 89fca49eb..68f9e372e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.hpp @@ -1,10 +1,286 @@ -#ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_OPTIONS_UIOPTIONWIDGETS_H -#define FRONTEND_MENUSCREENS_SAFEHOUSE_OPTIONS_UIOPTIONWIDGETS_H +#ifndef _UIOPTIONWIDGETS +#define _UIOPTIONWIDGETS -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp" + +// ===== OM* classes (extend IconOption) ===== + +// 0x5C +struct OMAudio : public IconOption { + OMAudio(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~OMAudio() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +// 0x5C +struct OMVideo : public IconOption { + OMVideo(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~OMVideo() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +// 0x5C +struct OMGameplay : public IconOption { + OMGameplay(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~OMGameplay() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +// 0x5C +struct OMPlayer : public IconOption { + OMPlayer(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~OMPlayer() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +// 0x5C +struct OMController : public IconOption { + OMController(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~OMController() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +// 0x5C +struct OMEATrax : public IconOption { + OMEATrax(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~OMEATrax() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +// 0x5C +struct OMCredits : public IconOption { + OMCredits(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~OMCredits() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +// ===== AO* slider classes (extend FESliderWidget) ===== + +// 0xA4 +struct AOSFXMasterVol : public FESliderWidget { + AOSFXMasterVol(bool enabled) : FESliderWidget(enabled) {} + ~AOSFXMasterVol() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; + void SetInitialValues() override; +}; + +// 0xA4 +struct AOCarVol : public FESliderWidget { + AOCarVol(bool enabled) : FESliderWidget(enabled) {} + ~AOCarVol() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; + void SetInitialValues() override; +}; + +// 0xA4 +struct AOSpeechVol : public FESliderWidget { + AOSpeechVol(bool enabled) : FESliderWidget(enabled) {} + ~AOSpeechVol() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; + void SetInitialValues() override; +}; + +// 0xA4 +struct AOFEMusicVol : public FESliderWidget { + AOFEMusicVol(bool enabled) : FESliderWidget(enabled) {} + ~AOFEMusicVol() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; + void SetInitialValues() override; +}; + +// 0xA4 +struct AOIGMusicVol : public FESliderWidget { + AOIGMusicVol(bool enabled) : FESliderWidget(enabled) {} + ~AOIGMusicVol() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; + void SetInitialValues() override; +}; + +// ===== AO* toggle classes (extend FEToggleWidget) ===== + +// 0x64 +struct AOInteractiveMusicMode : public FEToggleWidget { + AOInteractiveMusicMode(bool enabled) : FEToggleWidget(enabled) {} + ~AOInteractiveMusicMode() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct AOEATraxMusicMode : public FEToggleWidget { + AOEATraxMusicMode(bool enabled) : FEToggleWidget(enabled) {} + ~AOEATraxMusicMode() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct AOAudioMode : public FEToggleWidget { + AOAudioMode(bool enabled) : FEToggleWidget(enabled) {} + ~AOAudioMode() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// ===== VO* classes (extend FEToggleWidget) ===== + +// 0x64 +struct VOWideScreen : public FEToggleWidget { + VOWideScreen(bool enabled) : FEToggleWidget(enabled) {} + ~VOWideScreen() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// ===== GO* classes (extend FEToggleWidget) ===== + +// 0x64 +struct GODamage : public FEToggleWidget { + GODamage(bool enabled) : FEToggleWidget(enabled) {} + ~GODamage() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct GOAutoSave : public FEToggleWidget { + GOAutoSave(bool enabled) : FEToggleWidget(enabled) {} + ~GOAutoSave() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct GOJumpCams : public FEToggleWidget { + GOJumpCams(bool enabled) : FEToggleWidget(enabled) {} + ~GOJumpCams() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct GORearview : public FEToggleWidget { + GORearview(bool enabled) : FEToggleWidget(enabled) {} + ~GORearview() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct GOSpeedoUnits : public FEToggleWidget { + GOSpeedoUnits(bool enabled) : FEToggleWidget(enabled) {} + ~GOSpeedoUnits() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct GORacingMiniMap : public FEToggleWidget { + GORacingMiniMap(bool enabled) : FEToggleWidget(enabled) {} + ~GORacingMiniMap() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct GOExploringMiniMap : public FEToggleWidget { + GOExploringMiniMap(bool enabled) : FEToggleWidget(enabled) {} + ~GOExploringMiniMap() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// ===== PO* classes (extend FEToggleWidget) ===== + +// 0x64 +struct POTransmission : public FEToggleWidget { + POTransmission(bool enabled) : FEToggleWidget(enabled) {} + ~POTransmission() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct PODriveCam : public FEToggleWidget { + PODriveCam(bool enabled) : FEToggleWidget(enabled) {} + ~PODriveCam() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct POGauges : public FEToggleWidget { + POGauges(bool enabled) : FEToggleWidget(enabled) {} + ~POGauges() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct POPosition : public FEToggleWidget { + POPosition(bool enabled) : FEToggleWidget(enabled) {} + ~POPosition() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct POScore : public FEToggleWidget { + POScore(bool enabled) : FEToggleWidget(enabled) {} + ~POScore() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct POSplitTime : public FEToggleWidget { + POSplitTime(bool enabled) : FEToggleWidget(enabled) {} + ~POSplitTime() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// 0x64 +struct POLeaderBoard : public FEToggleWidget { + POLeaderBoard(bool enabled) : FEToggleWidget(enabled) {} + ~POLeaderBoard() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; + +// ===== CO* classes (extend FEToggleWidget) ===== +// 0x64 +struct COVibration : public FEToggleWidget { + COVibration(int player_num, bool enabled) : FEToggleWidget(enabled) {} + ~COVibration() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; + void SetFocus(const char* parent_pkg) override; + void UnsetFocus() override; +}; +// 0x64 +struct COConfig : public FEToggleWidget { + COConfig(bool enabled) : FEToggleWidget(enabled) {} + ~COConfig() override {} + void Act(const char* parent_pkg, unsigned int data) override; + void Draw() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.cpp index e69de29bb..c7cfb23be 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.cpp @@ -0,0 +1,332 @@ +#include "uiOptionsController.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.hpp" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" + +void FEngSetButtonTexture(FEImage* img, unsigned int tex_hash); +unsigned long FEHashUpper(const char* str); +unsigned int FindButtonNameHashForFEString(int config, int string_number, JoystickPort player); +bool IsJoystickTypeWheel(JoystickPort port); + +void eLoadStreamingTexture(unsigned int* textures, int count, void (*callback)(void*), + void* param, int pool); +void eUnloadStreamingTexture(unsigned int* textures, int count); + +int UIOptionsController::PortToConfigure = 0; +int UIOptionsController::isWheelConfig = 0; + +void MyFinishLoadingControllerTextureCallbackBridge(unsigned int p); + +UIOptionsController::UIOptionsController(ScreenConstructorData* sd) + : UIWidgetMenu(sd) // + , WhichControllerTexture(0) // + , PrevJoystickType(-1) // + , mCalledFromPauseMenu(sd->Arg != 0) // + , NeedSetup(true) { + if (Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + cFEng::Get()->QueuePackageMessage(0x7DB7B6D7, GetPackageName(), 0); + const char* pkg = GetPackageName(); + int player = GetPlayerToEditForOptions(); + unsigned int lang = 0x7B070985; + if (player == 0) { + lang = 0x7B070984; + } + FEngSetLanguageHash(pkg, 0x53BF826D, lang); + } + + oldConfig = FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Config; + reinterpret_cast(oldVibration) = FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Rumble; + reinterpret_cast(oldDriveWithAnalog) = FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DriveWithAnalog; + + CalcControllerTextureToLoad(); + + if (isWheelConfig) { + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Config = + static_cast(0); + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DriveWithAnalog = true; + } + + Setup(); +} + +UIOptionsController::~UIOptionsController() { + ClearLoadedControllerTexture(); +} + +bool UIOptionsController::OptionsDidNotChange() { + bool result = + (oldConfig == FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Config); + if (oldVibration != FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Rumble) { + result = false; + } + if (oldDriveWithAnalog != + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DriveWithAnalog) { + result = false; + } + return result; +} + +void UIOptionsController::NotificationMessage(unsigned long msg, FEObject* pobj, + unsigned long param1, unsigned long param2) { + if (msg == 0x9120409E || msg == 0xB5971BF1) { + int joyPort = FEngMapJoyParamToJoyport(param1); + FEDatabase->SetPlayersJoystickPort(GetPlayerToEditForOptions(), joyPort); + } + + UIWidgetMenu::NotificationMessage(msg, pobj, param1, param2); + + switch (msg) { + case 0xE1FDE1D1: { + bool dirty = false; + if (FEDatabase->IsOptionsDirty() || !OptionsDidNotChange()) { + dirty = true; + } + FEDatabase->SetOptionsDirty(dirty); + + if (mCalledFromPauseMenu) { + cFEng::Get()->QueuePackageSwitch("Pause_Main.fng", 1, 0, false); + } else { + if (FEDatabase->IsOnlineMode()) { + cFEng::Get()->QueuePackageSwitch("OL_MAIN.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } + } + break; + } + case 0x911AB364: + if (OptionsDidNotChange()) { + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), 0); + } else { + DialogInterface::ShowTwoButtons(GetPackageName(), "", static_cast(1), + 0x70E01038, 0x417B25E4, 0x775DBA97, 0x34DC1BCF, + 0x34DC1BCF, static_cast(1), + GetLocalizedString(0xE9CB802F)); + } + break; + case 0x775DBA97: + RestoreOriginals(); + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), 0); + break; + case 0xD9FEEC59: + case 0x5073EF13: + if (!OptionsDidNotChange()) { + char buf[128]; + FEngSNPrintf(buf, 128, GetLocalizedString(0xBA463431), + GetPlayerToEditForOptions() + 1); + const char* pkgName = GetPackageName(); + const char* dlg_pkg; + if (mCalledFromPauseMenu) { + dlg_pkg = "InGameDialog.fng"; + } else { + dlg_pkg = "Dialog.fng"; + } + DialogInterface::ShowTwoButtons(pkgName, dlg_pkg, + static_cast(1), 0x70E01038, 0x417B25E4, + 0x9A5AD46D, 0xA2A07AC4, 0x34DC1BCF, + static_cast(1), buf); + } else { + cFEng::Get()->QueueGameMessage(0x9A5AD46D, 0, 0xFF); + } + break; + case 0xA2A07AC4: + RestoreOriginals(); + TogglePlayer(); + break; + case 0x9A5AD46D: { + bool dirty = false; + if (FEDatabase->IsOptionsDirty() || !OptionsDidNotChange()) { + dirty = true; + } + FEDatabase->SetOptionsDirty(dirty); + TogglePlayer(); + break; + } + case 0xB5AF2461: + if (mCalledFromPauseMenu) { + new EUnPause(); + } + break; + case 0x92B703B5: + SetupControllerConfig(); + break; + case 0xC98356BA: + DetectControllers(); + break; + case 0x34DC1BCF: + return; + } +} + +void UIOptionsController::Setup() { + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + FEngSetScript(GetPackageName(), 0x8A41F5B9, 0x16A259, true); + } + + if (!FEDatabase->IsCareerMode()) { + cFEng::Get()->QueuePackageMessage(0xDE511657, GetPackageName(), 0); + } + + COConfig* config = new COConfig(true); + config->SetBackingOffsetX(-295.0f); + AddToggleOption(config, true); + + COVibration* vibration = new COVibration(GetPlayerToEditForOptions(), true); + int idx = AddToggleOption(vibration, true); + Options.GetNode(idx - 1)->SetBackingOffsetX(-295.0f); + + FEngSetLanguageHash(GetPackageName(), 0x53BF826D, + GetPlayerToEditForOptions() == 0 ? 0x7B070984 : 0x7B070985); + + SetInitialOption(0); + HideControllerConfig(); + PrepToShowControllerConfig(); +} + +void UIOptionsController::SetupControllerConfig() { + unsigned int newTex = CalcControllerTextureToLoad(); + if (WhichControllerTexture != newTex) { + ClearLoadedControllerTexture(); + PrepToShowControllerConfig(); + } + + JoystickPort port = static_cast(GetPlayerToEditForOptions()); + int config = FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Config; + char sztemp[32]; + + for (int i = 0; i < 17; i++) { + FEngSNPrintf(sztemp, 32, "CButton_%d", i + 1); + unsigned int obj_hash = FEHashUpper(sztemp); + FEngSNPrintf(sztemp, 32, "BUTTON_%d", i + 1); + unsigned int img_hash = FEHashUpper(sztemp); + + unsigned int button_hash = FindButtonNameHashForFEString(config, i, port); + if (button_hash != 0) { + FEngSetVisible(GetPackageName(), obj_hash); + FEngSetLanguageHash(GetPackageName(), obj_hash, button_hash); + FEngSetVisible(GetPackageName(), img_hash); + } else { + FEngSetInvisible(GetPackageName(), obj_hash); + FEngSetInvisible(GetPackageName(), img_hash); + } + } + + if (FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DriveWithAnalog) { + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0x4592229C), 0x148E38); + } else { + FEngSetButtonTexture(FEngFindImage(GetPackageName(), 0x4592229C), 0x0B30961B); + } + + FEngSetInvisible(GetPackageName(), 0x0F274B86); + FEngSetInvisible(GetPackageName(), 0x673D77BC); + FEngSetInvisible(GetPackageName(), 0x351AE442); + + FEngSetTextureHash(GetPackageName(), 0x81B57400, 0x02959349); + FEngSetTextureHash(GetPackageName(), 0x81B57401, 0x6851AAF5); + FEngSetTextureHash(GetPackageName(), 0x81B57402, 0x03B7F86D); +} + +void UIOptionsController::DetectControllers() { + unsigned int newTex = CalcControllerTextureToLoad(); + if (WhichControllerTexture != newTex) { + ClearLoadedControllerTexture(); + PrepToShowControllerConfig(); + } +} + +void UIOptionsController::ClearLoadedControllerTexture() { + if (WhichControllerTexture != 0) { + unsigned int tex = WhichControllerTexture; + eUnloadStreamingTexture(&tex, 1); + } +} + +void UIOptionsController::FinishLoadingTexCallback() { + SetupControllerConfig(); + ShowControllerConfig(); + SetupControllerConfig(); +} + +unsigned int UIOptionsController::CalcControllerTextureToLoad() { + unsigned int texture_hash; + isWheelConfig = 0; + + GetPlayerToEditForOptions(); + if (IsJoystickTypeWheel(static_cast(GetPlayerToEditForOptions()))) { + texture_hash = 0xB511476B; + isWheelConfig = 1; + } else { + if (FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DriveWithAnalog) { + texture_hash = 0xED543BAB; + } else { + texture_hash = 0xED543BAC; + } + } + return texture_hash; +} + +void UIOptionsController::PrepToShowControllerConfig() { + if (isWheelConfig) { + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Config = + static_cast(0); + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DriveWithAnalog = true; + cFEng::Get()->QueueGameMessage(0x92B703B5, 0, 0xFF); + } + + HideControllerConfig(); + unsigned int tex = CalcControllerTextureToLoad(); + WhichControllerTexture = tex; + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0x922A39C4), tex); + + unsigned int texArr = WhichControllerTexture; + eLoadStreamingTexture(&texArr, 1, + reinterpret_cast(MyFinishLoadingControllerTextureCallbackBridge), + this, 0); +} + +void UIOptionsController::ShowControllerConfig() { + FEngSetVisible(GetPackageName(), 0x3248E720); +} + +void UIOptionsController::HideControllerConfig() { + FEngSetInvisible(GetPackageName(), 0x3248E720); + WhichControllerTexture = 0; +} + +void UIOptionsController::RestoreOriginals() { + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Config = oldConfig; + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DriveWithAnalog = oldDriveWithAnalog; + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Rumble = oldVibration; +} + +void UIOptionsController::TogglePlayer() { + SetPlayerToEditForOptions(GetPlayerToEditForOptions() == 0); + + oldConfig = FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Config; + reinterpret_cast(oldVibration) = FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->Rumble; + reinterpret_cast(oldDriveWithAnalog) = FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DriveWithAnalog; + + const char* pkg = GetPackageName(); + int player = GetPlayerToEditForOptions(); + unsigned int lang = 0x7B070985; + if (player == 0) { + lang = 0x7B070984; + } + FEngSetLanguageHash(pkg, 0x53BF826D, lang); + + for (int i = 0; i < Options.CountElements(); i++) { + Options.GetNode(i)->Draw(); + } + + SetupControllerConfig(); +} + +void MyFinishLoadingControllerTextureCallbackBridge(unsigned int p) { + UIOptionsController* ls = reinterpret_cast(p); + ls->FinishLoadingTexCallback(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.hpp new file mode 100644 index 000000000..77da73086 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.hpp @@ -0,0 +1,42 @@ +#ifndef _UIOPTIONSCONTROLLER +#define _UIOPTIONSCONTROLLER + +#include + +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" + +// total size: 0x154 +struct UIOptionsController : public UIWidgetMenu { + static int PortToConfigure; + static int isWheelConfig; + + unsigned int WhichControllerTexture; // offset 0x138, size 0x4 + int PrevJoystickType; // offset 0x13C, size 0x4 + eControllerConfig oldConfig; // offset 0x140, size 0x4 + bool oldVibration; // offset 0x144, size 0x1 + bool oldDriveWithAnalog; // offset 0x148, size 0x1 + bool mCalledFromPauseMenu; // offset 0x14C, size 0x1 + bool NeedSetup; // offset 0x150, size 0x1 + + UIOptionsController(ScreenConstructorData* sd); + ~UIOptionsController() override; + + bool OptionsDidNotChange(); + + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + + void SetupControllerConfig(); + void DetectControllers(); + void ClearLoadedControllerTexture(); + void FinishLoadingTexCallback(); + unsigned int CalcControllerTextureToLoad(); + void PrepToShowControllerConfig(); + void ShowControllerConfig(); + void HideControllerConfig(); + void RestoreOriginals(); + void TogglePlayer(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.cpp index e69de29bb..facc2c97e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.cpp @@ -0,0 +1,167 @@ +#include "uiOptionsMain.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsController.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.hpp" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Misc/Config.h" + +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int language); +unsigned char FEngGetLastButton(const char* pkg_name); +void FEngSetScript(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash, + bool start_at_beginning); +bool FEngIsScriptRunning(const char* pkg_name, unsigned int obj_hash, unsigned int script_hash); +void MemcardEnter(const char* from, const char* to, unsigned int op, void (*pTermFunc)(void*), + void* pTermFuncParam, unsigned int msgSuccess, unsigned int msgFailed); +int FEngMapJoyParamToJoyport(int feng_param); + +extern const char* gOnlineMainMenu; + +UIOptionsMain::UIOptionsMain(ScreenConstructorData* sd) + : IconScrollerMenu(sd) // + , mCalledFromPauseMenu(sd->Arg != 0) { + if (mCalledFromPauseMenu) { + Options.SetIdleColor(0xFFFFAE40); + Options.SetFadeColor(0x00FFAE40); + } else { + Options.SetIdleColor(0xFFFFFFFF); + Options.SetFadeColor(0x00FFFFFF); + } + Setup(); +} + +void UIOptionsMain::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, + unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + + switch (msg) { + case 0xB5AF2461: + FEDatabase->ClearGameMode(eFE_GAME_MODE_OPTIONS); + StorePrevNotification(0xB5AF2461, pobj, param1, param2); + goto SetScript; + case 0x911AB364: + FEDatabase->ClearGameMode(eFE_GAME_MODE_OPTIONS); + StorePrevNotification(msg, pobj, param1, param2); + if (!mCalledFromPauseMenu) { + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), 0); + } + return; + } + goto SetScript; + case 0x0C407210: + if (FEngIsScriptRunning(GetPackageName(), 0x47FF4E7C, 0xDE6EFF34)) { + return; + } + StorePrevNotification(0x0C407210, pobj, param1, param2); +SetScript: + FEngSetScript(GetPackageName(), 0x47FF4E7C, 0xDE6EFF34, true); + return; + case 0xE1FDE1D1: + if (PrevButtonMessage == 0xB5AF2461) { + new EUnPause(); + return; + } + if (PrevButtonMessage == 0x911AB364) { + if (mCalledFromPauseMenu) { + cFEng::Get()->QueuePackageSwitch("Pause_Main.fng", 0, 0, false); + return; + } + if (FEDatabase->IsLANMode() || FEDatabase->IsOnlineMode()) { + ExitOptions(gOnlineMainMenu); + } else { + ExitOptions("MainMenu.fng"); + } + return; + } + if (PrevButtonMessage == 0x0C407210) { + eOptionsCategory curCat = FEDatabase->GetOptionsSettings()->CurrentCategory; + switch (curCat) { + case OC_AUDIO: + case OC_VIDEO: + case OC_GAMEPLAY: + case OC_PLAYER: + case OC_ONLINE: + if (mCalledFromPauseMenu && !FEDatabase->IsOnlineMode() && + !FEDatabase->IsLANMode()) { + cFEng::Get()->QueuePackageSwitch("Pause_Options.fng", 1, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("Options.fng", 0, 0, false); + } + return; + case OC_CREDITS: + cFEng::Get()->QueuePackageSwitch("Credits.fng", 0, 0, false); + return; + case OC_TRAILERS: + FEDatabase->SetGameMode(eFE_GAME_TRAILERS); + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + return; + case OC_CONTROLS: + UIOptionsController::PortToConfigure = FEngMapJoyParamToJoyport(PrevParam1); + if (mCalledFromPauseMenu) { + cFEng::Get()->QueuePackageSwitch("Pause_Controller.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("UI_OptionsController.fng", 0, 0, false); + } + return; + case OC_EATRAX: + cFEng::Get()->QueuePackageSwitch("EA_Trax_Jukebox.fng", 0, 0, false); + return; + default: + return; + } + } + return; + default: + return; + } +} + +void UIOptionsMain::Setup() { + AddOption(new OMAudio(0xF37AF144, 0xE76CD783, 0)); + AddOption(new OMVideo(0x8A006328, 0xE8E24508, 0)); + AddOption(new OMGameplay(0x4DF98FB2, 0xD0CF6EE1, 0)); + AddOption(new OMPlayer(0xD708EFEF, 0xF760EABE, 0)); + AddOption(new OMController(0xA04A7B26, 0x04DC6DB5, 0)); + + if (!mCalledFromPauseMenu && !FEDatabase->IsOnlineMode()) { + AddOption(new OMEATrax(0xC52CCBF6, 0xDCFB6B36, 0)); + AddOption(new OMCredits(0x51009E20, 0x0905101F, 0)); + } + + int lastButton = FEngGetLastButton(GetPackageName()); + if (bFadeInIconsImmediately) { + Options.bDelayUpdate = false; + Options.bFadingOut = false; + Options.StartFadeIn(); + } + + Options.SetInitialPos(lastButton); + + const unsigned long FEObj_TITLEGROUP = 0xB71B576D; + if (!mCalledFromPauseMenu) { + FEngSetLanguageHash(GetPackageName(), FEObj_TITLEGROUP, 0x4ECA678C); + } else { + FEngSetLanguageHash(GetPackageName(), 0x863404B5, 0x1D7BB6C9); + } + + RefreshHeader(); +} + +void UIOptionsMain::ExitOptions(const char* nextPackage) { + if (FEDatabase->IsOptionsDirty() && IsMemcardEnabled) { + MemcardEnter(GetPackageName(), nextPackage, 0x400B3, 0, 0, 0, 0); + } else { + cFEng::Get()->QueuePackageSwitch(nextPackage, 0, 0, false); + } +} + +int GetPlayerToEditForOptions() { + return UIOptionsScreen::PlayerToEdit; +} + +void SetPlayerToEditForOptions(int player) { + UIOptionsScreen::PlayerToEdit = player; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.hpp index edf98f558..180eacff6 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.hpp @@ -1,10 +1,23 @@ -#ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_OPTIONS_UIOPTIONSMAIN_H -#define FRONTEND_MENUSCREENS_SAFEHOUSE_OPTIONS_UIOPTIONSMAIN_H +#ifndef _UIOPTIONSMAIN +#define _UIOPTIONSMAIN -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" + +// total size: 0x170 +struct UIOptionsMain : public IconScrollerMenu { + bool mCalledFromPauseMenu; // offset 0x16C, size 0x1 + + UIOptionsMain(ScreenConstructorData* sd); + ~UIOptionsMain() override {} + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + void ExitOptions(const char* nextPackage); +}; +int GetPlayerToEditForOptions(); +void SetPlayerToEditForOptions(int player); #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.cpp index e69de29bb..4e9517568 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.cpp @@ -0,0 +1,469 @@ +#include "uiOptionsScreen.hpp" + +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionWidgets.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsMain.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Generated/Events/EUnPause.hpp" +#include "Speed/Indep/Src/Misc/Config.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/Sim/Simulation.h" + +FEImage* FEngFindImage(const char* pkg_name, int name_hash); +void FEngSetTextureHash(FEImage* image, unsigned int texture_hash); +int FEngSNPrintf(char* buffer, int buf_size, const char* fmt, ...); +const char* GetLocalizedString(unsigned int hash); + +extern EAXSound* g_pEAXSound; + +#define _DIALOGINTERFACE +enum eDialogTitle {}; +enum eDialogFirstButtons {}; + +struct DialogInterface { + static int ShowTwoButtons(const char* from_pkg, const char* dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, + unsigned int button2_pressed_message, unsigned int cancel_message, + eDialogFirstButtons first_button, ...); + static int ShowTwoButtons(const char* from_pkg, const char* dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, + unsigned int button2_pressed_message, unsigned int cancel_message, + eDialogFirstButtons first_button, const char* fmt, ...); + static int ShowTwoButtons(const char* from_pkg, const char* dlg_pkg, eDialogTitle title, + unsigned int button1_text_hash, unsigned int button2_text_hash, + unsigned int button1_pressed_message, + unsigned int button2_pressed_message, unsigned int cancel_message, + eDialogFirstButtons first_button, unsigned int lang_hash, ...); + static int ShowOneButton(const char* from_pkg, const char* dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int cancel_message, ...); + static int ShowOneButton(const char* from_pkg, const char* dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int cancel_message, const char* fmt, ...); + static int ShowOneButton(const char* from_pkg, const char* dlg_pkg, eDialogTitle title, + unsigned int button_text_hash, unsigned int button_pressed_message, + unsigned int cancel_message, unsigned int lang_hash, ...); +}; + +inline void FEngSetTextureHash(const char* pkg_name, unsigned int obj_hash, + unsigned int texture_hash) { + FEngSetTextureHash(FEngFindImage(pkg_name, obj_hash), texture_hash); +} + +int UIOptionsScreen::PlayerToEdit = 0; + +UIOptionsScreen::UIOptionsScreen(ScreenConstructorData* sd) + : UIWidgetMenu(sd) // + , mCalledFromPauseMenu(sd->Arg != 0) // + , NeedsColorCal(false) // + , OriginalAudioSettings(nullptr) // + , OriginalVideoSettings(nullptr) // + , OriginalGameplaySettings(nullptr) // + , OriginalPlayerSettings(nullptr) { + iMaxWidgetsOnScreen = mCalledFromPauseMenu ? 10 : 9; + + if (FEDatabase->GetOptionsSettings()->CurrentCategory == OC_PLAYER && + Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN) { + cFEng::Get()->QueuePackageMessage(0x7DB7B6D7, GetPackageName(), 0); + const char* pkg = GetPackageName(); + FEngSetLanguageHash(pkg, 0x53BF826D, + GetPlayerToEditForOptions() == 0 ? 0x7B070984 : 0x7B070985); + } + + Setup(); +} + +UIOptionsScreen::~UIOptionsScreen() { + delete OriginalAudioSettings; + delete OriginalVideoSettings; + delete OriginalGameplaySettings; + delete OriginalPlayerSettings; +} + +void UIOptionsScreen::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, + unsigned long param2) { + UIWidgetMenu::NotificationMessage(msg, pobj, param1, param2); + + switch (msg) { + case 0x911AB364: + if (OptionsDidNotChange()) { + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), 0); + } else { + const char* pkg_name = GetPackageName(); + const char* prompt = GetLocalizedString(0xE9CB802F); + DialogInterface::ShowTwoButtons(pkg_name, + mCalledFromPauseMenu ? "InGameDialog.fng" : "Dialog.fng", + static_cast(1), 0x70E01038, 0x417B25E4, + 0x775DBA97, 0x34DC1BCF, 0x34DC1BCF, + static_cast(1), prompt); + } + break; + case 0x775DBA97: + RestoreOriginals(); + MemoryCard::GetInstance()->SetCardRemovedWithAutoSaveEnabled(false); + cFEng::Get()->QueuePackageMessage(0x587C018B, GetPackageName(), 0); + break; + case 0xC519BFC4: { + const char* pkg_name = GetPackageName(); + const char* prompt = GetLocalizedString(0x8AEF5AE8); + DialogInterface::ShowTwoButtons(pkg_name, + mCalledFromPauseMenu ? "InGameDialog.fng" : "Dialog.fng", + static_cast(1), 0x70E01038, 0x417B25E4, + 0xD05FC3A3, 0x34DC1BCF, 0x34DC1BCF, + static_cast(1), prompt); + break; + } + case 0xD9FEEC59: + case 0x5073EF13: + if (FEDatabase->GetOptionsSettings()->CurrentCategory != OC_PLAYER) { + return; + } + { + cFEng* eng = cFEng::Get(); + unsigned int snd = 0xF4B32D4D; + if (msg == 0x5073EF13) { + snd = 0x6B283007; + } + eng->QueueSoundMessage(snd, GetPackageName()); + if (!OptionsDidNotChange()) { + char buf[128]; + const char* fmt = GetLocalizedString(0xBA463431); + FEngSNPrintf(buf, 128, fmt, GetPlayerToEditForOptions() + 1); + DialogInterface::ShowTwoButtons(GetPackageName(), + mCalledFromPauseMenu ? "InGameDialog.fng" : "Dialog.fng", + static_cast(1), 0x70E01038, 0x417B25E4, + 0x9A5AD46D, 0xA2A07AC4, 0x34DC1BCF, + static_cast(1), buf); + } else { + eng->QueueGameMessage(0x9A5AD46D, 0, 0xFF); + } + } + break; + case 0xA2A07AC4: + TogglePlayer(true); + break; + case 0x9A5AD46D: + TogglePlayer(false); + break; + case 0xD05FC3A3: + { + OptionsSettings* options_settings = FEDatabase->GetOptionsSettings(); + if (!FEDatabase->GetGameplaySettings()->AutoSaveOn && + options_settings->CurrentCategory == OC_GAMEPLAY) { + MemcardEnter(GetPackageName(), GetPackageName(), 0xA1, 0, 0, 0, 0); + } + } + RestoreDefaults(); + break; + case 0xE1FDE1D1: { + bool dirty = false; + if (FEDatabase->IsOptionsDirty() || !OptionsDidNotChange()) { + dirty = true; + } + FEDatabase->SetOptionsDirty(dirty); + + if (mCalledFromPauseMenu) { + cFEng::Get()->QueuePackageSwitch("Pause_Main.fng", 1, 0, false); + } else if (FEDatabase->IsOnlineMode()) { + cFEng::Get()->QueuePackageSwitch("OL_MAIN.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } + + OptionsSettings* options_settings = FEDatabase->GetOptionsSettings(); + if (options_settings->CurrentCategory != OC_AUDIO) { + return; + } + g_pEAXSound->UpdateVolumes(FEDatabase->GetAudioSettings(), -1.0f); + break; + } + case 0xB5AF2461: + new EUnPause(); + break; + case 0x7E998E5E: + if (FEDatabase->GetOptionsSettings()->CurrentCategory == OC_GAMEPLAY) { + ClearWidgets(); + SetupGameplay(); + SetInitialOption(0); + } else { + for (int i = 0; i < Options.CountElements(); i++) { + Options.GetNode(i)->Draw(); + } + } + break; + case 0x72619778: + case 0x406415E3: + break; + } +} + +void UIOptionsScreen::Setup() { + ClearWidgets(); + NeedsColorCal = false; + mInitialAutoSaveValue = FEDatabase->GetGameplaySettings()->AutoSaveOn; + + FEngSetInvisible(GetPackageName(), 0xE54C30BE); + FEngSetInvisible(GetPackageName(), 0x8E1BEA84); + FEngSetInvisible(GetPackageName(), 0x608BB8C8); + FEngSetInvisible(GetPackageName(), 0x444969FD); + FEngSetInvisible(GetPackageName(), 0x444969FE); + + eOptionsCategory curCat = FEDatabase->GetOptionsSettings()->CurrentCategory; + switch (curCat) { + case OC_AUDIO: + SetupAudio(); + break; + case OC_VIDEO: + SetupVideo(); + break; + case OC_GAMEPLAY: + SetupGameplay(); + break; + case OC_PLAYER: + SetupPlayer(); + FEngSetVisible(GetPackageName(), 0x444969FD); + FEngSetVisible(GetPackageName(), 0x444969FE); + break; + case OC_ONLINE: + SetupOnline(); + break; + } + + SetInitialOption(0); +} + +void UIOptionsScreen::SetupAudio() { + FEngSetTextureHash(GetPackageName(), 0x8007B4C, 0xF37AF144); + + if (mCalledFromPauseMenu) { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0xB1426DFA); + } else { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0x3932C2E4); + } + + AddSliderOption(new AOSFXMasterVol(true), true); + AddSliderOption(new AOCarVol(true), true); + AddSliderOption(new AOSpeechVol(true), true); + AddSliderOption(new AOFEMusicVol(true), true); + AddSliderOption(new AOIGMusicVol(true), true); + AddToggleOption(new AOInteractiveMusicMode(true), true); + AddToggleOption(new AOEATraxMusicMode(true), true); + + if (TheGameFlowManager.IsInFrontend()) { + AddToggleOption(new AOAudioMode(true), true); + } + + AudioSettings* temp = new AudioSettings(); + temp->Default(); + OriginalAudioSettings = temp; + *OriginalAudioSettings = *FEDatabase->GetAudioSettings(); +} + +void UIOptionsScreen::SetupVideo() { + FEngSetTextureHash(GetPackageName(), 0x8007B4C, 0x8A006328); + + if (mCalledFromPauseMenu) { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0xD94EA03F); + } else { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0x48478029); + } + + AddToggleOption(new VOWideScreen(true), true); + + VideoSettings* temp = new VideoSettings(); + temp->Default(); + OriginalVideoSettings = temp; + *OriginalVideoSettings = *FEDatabase->GetVideoSettings(); + + FEngSetScript(GetPackageName(), 0xAD6B204F, 0x5079C8F8, true); +} + +void UIOptionsScreen::SetupGameplay() { + FEngSetTextureHash(GetPackageName(), 0x8007B4C, 0x4DF98FB2); + + if (mCalledFromPauseMenu) { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0x3936D9F8); + } else { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0x01CCE8C2); + } + + bool split = ShouldShowAutoSave(); + if (split) { + AddToggleOption(new GOAutoSave(true), true); + } + + if (Sim::GetUserMode() != Sim::USER_SPLIT_SCREEN) { + AddToggleOption(new GOJumpCams(true), true); + } + + AddToggleOption(new GODamage(true), true); + AddToggleOption(new GORearview(true), true); + AddToggleOption(new GOSpeedoUnits(true), true); + + bool is_split = Sim::GetUserMode() == Sim::USER_SPLIT_SCREEN; + if (!mCalledFromPauseMenu) is_split = false; + if (!is_split) { + if (!FEDatabase->IsOnlineMode() && !FEDatabase->IsLANMode()) { + AddToggleOption(new GOExploringMiniMap(true), true); + } + AddToggleOption(new GORacingMiniMap(true), true); + } + + if (OriginalGameplaySettings == nullptr) { + GameplaySettings* temp = new GameplaySettings(); + temp->Default(); + OriginalGameplaySettings = temp; + *OriginalGameplaySettings = *FEDatabase->GetGameplaySettings(); + } +} + +void UIOptionsScreen::SetupPlayer() { + FEngSetTextureHash(GetPackageName(), 0x8007B4C, 0xD708EFEF); + + if (mCalledFromPauseMenu) { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0xD9DC2F12); + } else { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0xC055165F); + } + + FEngSetScript(GetPackageName(), 0x8A41F5B9, 0x5079C8F8, true); + + FEngSetLanguageHash(GetPackageName(), 0x53BF826D, + GetPlayerToEditForOptions() == 0 ? 0x7B070984 : 0x7B070985); + + if (!GRaceStatus::Exists() || GRaceStatus::Get().GetRaceType() != GRace::kRaceType_Drag) { + AddToggleOption(new POTransmission(true), true); + } + + AddToggleOption(new PODriveCam(true), true); + AddToggleOption(new POGauges(true), true); + AddToggleOption(new POPosition(true), true); + AddToggleOption(new POSplitTime(true), true); + AddToggleOption(new POScore(true), true); + + if (!GRaceStatus::Exists() || (!GRaceStatus::IsDragRace() && !GRaceStatus::IsTollboothRace())) { + AddToggleOption(new POLeaderBoard(true), true); + } + + PlayerSettings* temp = new PlayerSettings(); + temp->Default(); + OriginalPlayerSettings = temp; + *OriginalPlayerSettings = + *FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions()); +} + +void UIOptionsScreen::SetupOnline() { + if (mCalledFromPauseMenu) { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0x966C856D); + } else { + FEngSetLanguageHash(GetPackageName(), 0x42ADB44C, 0xE463B5F7); + } +} + +void UIOptionsScreen::RestoreDefaults() { + bool bOldAutoSaveVal; + + eOptionsCategory curCat = FEDatabase->GetOptionsSettings()->CurrentCategory; + switch (curCat) { + case OC_AUDIO: + FEDatabase->GetAudioSettings()->Default(); + break; + case OC_VIDEO: + FEDatabase->GetVideoSettings()->Default(); + break; + case OC_GAMEPLAY: + bOldAutoSaveVal = FEDatabase->GetGameplaySettings()->AutoSaveOn; + FEDatabase->GetGameplaySettings()->Default(); + if (!ShouldShowAutoSave()) { + FEDatabase->GetGameplaySettings()->AutoSaveOn = bOldAutoSaveVal; + } + break; + case OC_PLAYER: + FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions())->DefaultFromOptionsScreen(); + break; + } + + FEDatabase->GetOptionsSettings()->CurrentCategory = curCat; + + for (int i = 0; i < Options.CountElements(); i++) { + Options.GetNode(i)->Draw(); + } +} + +bool UIOptionsScreen::OptionsDidNotChange() { + eOptionsCategory curCat = FEDatabase->GetOptionsSettings()->CurrentCategory; + switch (curCat) { + case OC_AUDIO: + return *FEDatabase->GetAudioSettings() == *OriginalAudioSettings; + case OC_VIDEO: + return *FEDatabase->GetVideoSettings() == *OriginalVideoSettings; + case OC_GAMEPLAY: + return *FEDatabase->GetGameplaySettings() == *OriginalGameplaySettings; + case OC_PLAYER: + return *FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions()) == + *OriginalPlayerSettings; + default: + return false; + } +} + +void UIOptionsScreen::RestoreOriginals() { + eOptionsCategory curCat = FEDatabase->GetOptionsSettings()->CurrentCategory; + switch (curCat) { + case OC_AUDIO: + *FEDatabase->GetAudioSettings() = *OriginalAudioSettings; + break; + case OC_VIDEO: + *FEDatabase->GetVideoSettings() = *OriginalVideoSettings; + break; + case OC_GAMEPLAY: + *FEDatabase->GetGameplaySettings() = *OriginalGameplaySettings; + break; + case OC_PLAYER: + *FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions()) = *OriginalPlayerSettings; + break; + } +} + +void UIOptionsScreen::TogglePlayer(bool revert_changes) { + if (revert_changes) { + RestoreOriginals(); + } + + SetPlayerToEditForOptions(GetPlayerToEditForOptions() == 0); + + if (FEDatabase->GetOptionsSettings()->CurrentCategory == OC_PLAYER) { + *OriginalPlayerSettings = + *FEDatabase->GetPlayerSettings(GetPlayerToEditForOptions()); + + const char* pkg = GetPackageName(); + unsigned int lang = 0x7B070985; + if (GetPlayerToEditForOptions() == 0) { + lang = 0x7B070984; + } + FEngSetLanguageHash(pkg, 0x53BF826D, lang); + } + + for (int i = 0; i < Options.CountElements(); i++) { + Options.GetNode(i)->Draw(); + } +} + +bool UIOptionsScreen::ShouldShowAutoSave() { + bool result = false; + if (!GRaceStatus::Exists() || + GRaceStatus::Get().GetRaceContext() == GRace::kRaceContext_Career || + (GRaceStatus::Get().GetRaceParameters() && + GRaceStatus::Get().GetRaceParameters()->GetIsChallengeSeriesRace())) { + if (IsMemcardEnabled && IsAutoSaveEnabled && FEDatabase->bProfileLoaded && + !FEDatabase->IsOnlineMode()) { + result = true; + } + } + return result; +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.hpp new file mode 100644 index 000000000..0b74760de --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsScreen.hpp @@ -0,0 +1,45 @@ +#ifndef _UIOPTIONSSCREEN +#define _UIOPTIONSSCREEN + +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" + +struct AudioSettings; +struct VideoSettings; +struct GameplaySettings; +struct PlayerSettings; + +// total size: 0x15C +struct UIOptionsScreen : public UIWidgetMenu { + static int PlayerToEdit; + + int mCalledFromPauseMenu; // offset 0x138, size 0x1 + int NeedsColorCal; // offset 0x13C, size 0x1 + int mInitialAutoSaveValue; // offset 0x140, size 0x1 + FEToggleWidget* speakeroption; // offset 0x144, size 0x4 + FESliderWidget* volumeoption; // offset 0x148, size 0x4 + AudioSettings* OriginalAudioSettings; // offset 0x14C, size 0x4 + VideoSettings* OriginalVideoSettings; // offset 0x150, size 0x4 + GameplaySettings* OriginalGameplaySettings; // offset 0x154, size 0x4 + PlayerSettings* OriginalPlayerSettings; // offset 0x158, size 0x4 + + UIOptionsScreen(ScreenConstructorData* sd); + ~UIOptionsScreen() override; + + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + + void SetupAudio(); + void SetupVideo(); + void SetupGameplay(); + void SetupPlayer(); + void SetupOnline(); + void RestoreDefaults(); + bool OptionsDidNotChange(); + void RestoreOriginals(); + void TogglePlayer(bool revert_changes); + bool ShouldShowAutoSave(); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp index e69de29bb..ab4f6f340 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.cpp @@ -0,0 +1,53 @@ +#include "uiOptionsTrailers.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" + +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int language); +unsigned char FEngGetLastButton(const char* pkg_name); + +UIOptionsTrailers::UIOptionsTrailers(ScreenConstructorData* sd) + : IconScrollerMenu(sd) { + Setup(); +} + +void UIOptionsTrailers::NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, + unsigned long param2) { + if (msg != 0x0c407210) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + } + + switch (msg) { + case 0x911ab364: + StorePrevNotification(0x911ab364, pobj, param1, param2); + cFEng::Get()->QueuePackageMessage(0x587c018b, GetPackageName(), nullptr); + break; + case 0x0c407210: + cFEng::Get()->QueuePackageMessage(0x8cb81f09, nullptr, nullptr); + Options.GetCurrentOption()->React(GetPackageName(), 0x0c407210, pobj, param1, param2); + break; + case 0xd05fc3a3: + Options.GetCurrentOption()->React(GetPackageName(), 0xd05fc3a3, pobj, param1, param2); + break; + case 0xe1fde1d1: + if (PrevButtonMessage == 0x911ab364) { + FEDatabase->ClearGameMode(eFE_GAME_TRAILERS); + FEDatabase->GetOptionsSettings()->CurrentCategory = static_cast(-1); + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } + break; + } +} + +void UIOptionsTrailers::Setup() { + unsigned char lastButton = FEngGetLastButton(GetPackageName()); + + if (bFadeInIconsImmediately) { + Options.StartFadeIn(); + } + + Options.SetInitialPos(lastButton); + GarageMainScreen::GetInstance()->CancelCameraPush(); + FEngSetLanguageHash(GetPackageName(), 0xb71b576d, 0xb65a46d8); + RefreshHeader(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.hpp index 0b70d511e..a6315f173 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/options/uiOptionsTrailers.hpp @@ -1,10 +1,17 @@ -#ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_OPTIONS_UIOPTIONSTRAILERS_H -#define FRONTEND_MENUSCREENS_SAFEHOUSE_OPTIONS_UIOPTIONSTRAILERS_H +#ifndef _UIOPTIONSTRAILERS +#define _UIOPTIONSTRAILERS -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" +// total size: 0x16C +struct UIOptionsTrailers : public IconScrollerMenu { + UIOptionsTrailers(ScreenConstructorData* sd); + ~UIOptionsTrailers() override {} + void NotificationMessage(unsigned long msg, FEObject* pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.cpp index e69de29bb..6dc1b1115 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.cpp @@ -0,0 +1,283 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.hpp" + +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/World/CarInfo.hpp" + +extern FEImage *FEngFindImage(const char *pkg, int hash); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +struct TextureInfo; +extern TextureInfo *GetTextureInfo(unsigned int hash, int, int); +extern unsigned int CalcLanguageHash(const char *prefix, GRaceParameters *params); +extern bool DoesStringExist(unsigned int hash); +extern int FEPrintf(const char *pkg, int hash, const char *fmt, ...); +extern void FEngSetLanguageHash(const char *pkg, unsigned int hash, unsigned int lang_hash); +extern const char *GetLocalizedString(unsigned int hash); +extern void FEngSetScript(const char *pkg, unsigned int hash, unsigned int script, bool); +extern void SetRideInfo(RideInfo *ride, eSetRideInfoReasons reason, eCarViewerWhichCar car); +extern void PlayUISoundFX(EAXSound *snd, eMenuSoundTriggers trigger); +extern EAXSound *g_pEAXSound; +extern void StartRace(); + +UIQRBrief::UIQRBrief(ScreenConstructorData *sd) + : MenuScreen(sd) // + , pSelectedCar(nullptr) // + , pSelectedTrack(nullptr) // + , randomCount(0) { + raceSettings.Default(); + Setup(); + AccelerationSlider.Init(GetPackageName(), GetPackageName(), 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f); + TopSpeedSlider.Init(GetPackageName(), GetPackageName(), 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f); + HandlingSlider.Init(GetPackageName(), GetPackageName(), 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f); +} + +UIQRBrief::~UIQRBrief() { +} + +SelectableCar *UIQRBrief::GetRandomCar() { + int size = FilteredCarsList.CountElements(); + return FilteredCarsList.GetNode(bRandom(size)); +} + +SelectableTrack *UIQRBrief::GetRandomTrack() { + int size = FilteredTracksList.CountElements(); + return FilteredTracksList.GetNode(bRandom(size)); +} + +void UIQRBrief::Setup() { + FilteredCarsList.DeleteAllElements(); + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + unsigned int current_bin = FEDatabase->GetCareerSettings()->GetCurrentBin(); + for (int i = 0; i < 200; i++) { + FECarRecord *fe_car = stable->GetCarByIndex(i); + if (fe_car->IsValid() && fe_car->MatchesFilter(0xf0001)) { + Attrib::Gen::frontend fe_attrib(Attrib::FindCollection(Attrib::Gen::frontend::ClassKey(), fe_car->FEKey), 0, nullptr); + unsigned char unlocked_at = fe_attrib.UnlockedAt(); + if (static_cast(unlocked_at) >= static_cast(current_bin - 1) && + unlocked_at <= current_bin + 3 && + fe_attrib.GetCollection() != static_cast(-0x3e3cd251)) { + SelectableCar *car = new SelectableCar(fe_car->Handle, false); + FilteredCarsList.AddTail(car); + } + } + } + for (unsigned int i = 0; i < GRaceDatabase::Get().GetRaceCount(); i++) { + GRaceParameters *parms = GRaceDatabase::Get().GetRaceParameters(i); + if (parms->GetRaceType() != static_cast(8) && + parms->GetRaceType() != static_cast(9) && + parms->GetRaceType() != static_cast(10) && + parms->GetRaceType() != static_cast(-1) && + parms->GetRaceType() != static_cast(4)) { + if (parms->GetEventHash() != Attrib::StringHash32("19.8.31")) { + if (UnlockSystem::IsEventAvailable(parms->GetEventHash())) { + if (UnlockSystem::IsTrackUnlocked(static_cast(1), parms->GetEventHash(), 0)) { + SelectableTrack *track = new SelectableTrack(parms, true, 0); + FilteredTracksList.AddTail(track); + } + } + } + } + } + cFEng::Get()->QueueGameMessage(0xc519bfc4, GetPackageName(), 0xff); +} + +void UIQRBrief::RefreshHeader() { + FECarRecord *car_rec = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(pSelectedCar->mHandle); + unsigned int manu_logo = car_rec->GetManuLogoHash(); + if (GetTextureInfo(manu_logo, 0, 0)) { + FEImage *img = FEngFindImage(PackageFilename, 0x3e01ad1d); + FEngSetTextureHash(img, manu_logo); + } else { + unsigned int placeholder = FEHashUpper("GENERICPLACEHOLDER"); + FEImage *img = FEngFindImage(PackageFilename, 0x3e01ad1d); + FEngSetTextureHash(img, placeholder); + } + unsigned int car_logo = car_rec->GetLogoHash(); + if (GetTextureInfo(car_logo, 0, 0)) { + FEImage *img = FEngFindImage(PackageFilename, 0xb05dd708); + FEngSetTextureHash(img, car_logo); + } else { + unsigned int placeholder = FEHashUpper("GENERICPLACEHOLDER"); + FEImage *img = FEngFindImage(PackageFilename, 0xb05dd708); + FEngSetTextureHash(img, placeholder); + } + GRaceParameters *track_params = pSelectedTrack->pRaceParams; + unsigned int race_name = FEDatabase->GetRaceNameHash(track_params->GetRaceType()); + FEngSetLanguageHash(PackageFilename, 0xb5154998, race_name); + unsigned int race_icon = FEDatabase->GetRaceIconHash(track_params->GetRaceType()); + FEImage *icon_img = FEngFindImage(PackageFilename, 0x2521e5eb); + FEngSetTextureHash(icon_img, race_icon); + unsigned int track_name = CalcLanguageHash("TRACKNAME_", track_params); + if (DoesStringExist(track_name)) { + FEngSetLanguageHash(PackageFilename, 0xb5154999, track_name); + } else { + FEPrintf(PackageFilename, 0xb5154999, track_params->GetEventID()); + } + unsigned int unit_hash = 0x867dcfd9; + if (FEDatabase->GetUserProfile(0)->GetOptions()->TheGameplaySettings.SpeedoUnits == 1) { + unit_hash = 0x8569a26a; + } + const char *unit_str = GetLocalizedString(unit_hash); + float race_length = track_params->GetRaceLengthMeters() * 0.001f; + FEPrintf(PackageFilename, 0xb515499a, "%$0.1f %s", race_length, unit_str); + GRace::Type race_type = track_params->GetRaceType(); + if (race_type == static_cast(1) || race_type == static_cast(3)) { + FEPrintf(PackageFilename, 0xb515499b, "%d", raceSettings.NumLaps); + } else { + FEPrintf(PackageFilename, 0xb515499b, "--"); + } + FEPrintf(PackageFilename, 0xb515499c, "%d", raceSettings.NumOpponents); + unsigned int ai_hash; + switch (raceSettings.TrafficDensity) { + case 0: ai_hash = 0x8cdc3937; break; + case 1: ai_hash = 0x73c615a3; break; + case 2: ai_hash = 0xa2cca838; break; + case 3: ai_hash = 0x61d1c5a5; break; + default: ai_hash = 0; break; + } + race_type = track_params->GetRaceType(); + if (race_type == static_cast(0) || track_params->GetRaceType() == static_cast(2)) { + ai_hash = 0x7f2f7ad6; + } + FEngSetLanguageHash(PackageFilename, 0xb515499d, ai_hash); + unsigned int traffic_hash; + switch (raceSettings.AISkill) { + case 0: traffic_hash = 0x61973e01; break; + case 1: traffic_hash = 0x3747f6d0; break; + case 2: traffic_hash = 0x6198e2ee; break; + default: traffic_hash = 0; break; + } + FEngSetLanguageHash(PackageFilename, 0xb515499e, traffic_hash); + unsigned int cops_hash = 0x70dfe5c2; + if (raceSettings.CatchUp) { + cops_hash = 0x417b2604; + } + FEngSetLanguageHash(PackageFilename, 0xb515499e, cops_hash); + UpdateSliders(); +} + +void UIQRBrief::UpdateSliders() { + Physics::Info::Performance stock_perf; + Physics::Info::Performance tuned_perf; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *car_rec = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + Attrib::Gen::pvehicle pveh(car_rec->VehicleKey, 0, nullptr); + bool hasCustomization = (car_rec->Customization != 0xff); + if (hasCustomization) { + FECustomizationRecord *cust = stable->GetCustomizationRecordByHandle(car_rec->Customization); + cust->WriteRecordIntoPhysics(pveh); + } + Physics::Info::EstimatePerformance(pveh, stock_perf); + + AccelerationSlider.SetValue(stock_perf.Acceleration); + float acc_val = stock_perf.Acceleration; + float acc_min = AccelerationSlider.GetMin(); + float acc_max = AccelerationSlider.GetMax(); + if (acc_val < acc_min) acc_val = acc_min; + if (acc_val > acc_max) acc_val = acc_max; + AccelerationSlider.SetPreviewValue(acc_val); + AccelerationSlider.Draw(); + + TopSpeedSlider.SetValue(stock_perf.TopSpeed); + float top_val = stock_perf.TopSpeed; + float top_min = TopSpeedSlider.GetMin(); + float top_max = TopSpeedSlider.GetMax(); + if (top_val < top_min) top_val = top_min; + if (top_val > top_max) top_val = top_max; + TopSpeedSlider.SetPreviewValue(top_val); + TopSpeedSlider.Draw(); + + HandlingSlider.SetValue(stock_perf.Handling); + float hdl_val = stock_perf.Handling; + float hdl_min = HandlingSlider.GetMin(); + float hdl_max = HandlingSlider.GetMax(); + if (hdl_val < hdl_min) hdl_val = hdl_min; + if (hdl_val > hdl_max) hdl_val = hdl_max; + HandlingSlider.SetPreviewValue(hdl_val); + HandlingSlider.Draw(); +} + +void UIQRBrief::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0xc98356ba: { + if (randomCount < 1) return; + SelectableCar *next_car = static_cast(pSelectedCar->GetNext()); + if (next_car == static_cast(FilteredCarsList.EndOfList())) { + next_car = FilteredCarsList.GetHead(); + } + pSelectedCar = next_car; + SelectableTrack *next_track = static_cast(pSelectedTrack->GetNext()); + if (next_track == static_cast(FilteredTracksList.EndOfList())) { + next_track = FilteredTracksList.GetHead(); + } + pSelectedTrack = next_track; + FEDatabase->GetRandomRaceOptions(&raceSettings, pSelectedTrack->pRaceParams->GetRaceType()); + RefreshHeader(); + randomCount--; + PlayUISoundFX(g_pEAXSound, static_cast(0x8b)); + if (randomCount != 0) return; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *car_rec = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + Attrib::Gen::frontend fe_attrib(car_rec->FEKey, 0, nullptr); + int unlocked_at = fe_attrib.UnlockedAt(); + if (unlocked_at < FEDatabase->GetUserProfile(0)->GetCareer()->GetCurrentBin()) { + FEngSetScript(PackageFilename, 0xfe8fdbf7, 0x5079c8f8, true); + char buf[128]; + int req_bin = unlocked_at + 1; + FEngSNPrintf(buf, 128, "BLACKLIST_%d", req_bin); + const char *pkg = PackageFilename; + const char *locked_str = GetLocalizedString(0x4ef2a115); + unsigned int bin_hash = FEHashUpper(buf); + const char *bin_name = GetLocalizedString(bin_hash); + FEPrintf(pkg, 0xfe8fdbf7, locked_str, bin_name, req_bin); + } + RideInfo ride; + stable->BuildRideForPlayer(pSelectedCar->mHandle, 0, &ride); + ride.SetRandomPaint(); + ride.SetRandomParts(); + SetRideInfo(&ride, static_cast(1), static_cast(0)); + break; + } + case 0x406415e3: { + cFrontendDatabase *db = FEDatabase; + char port = FEngMapJoyParamToJoyport(param1); + db->SetPlayersJoystickPort(0, port); + break; + } + case 0xe1fde1d1: { + RaceSettings *qr_settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + qr_settings->SelectedCar[0] = 0x12345678; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *placeholder = stable->GetCarRecordByHandle(0x12345678); + FECarRecord *real_car = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + *placeholder = *real_car; + placeholder->FilterBits = 0xf0020; + FECustomizationRecord *cust_rec = stable->GetCustomizationRecordByHandle(placeholder->Customization); + RideInfo *player_ride = CarViewer::GetRideInfo(static_cast(0)); + cust_rec->WriteRideIntoRecord(player_ride); + Attrib::Gen::pvehicle pveh(placeholder->VehicleKey, 0, nullptr); + int max_nitrous = Physics::Upgrades::GetMaxLevel(pveh, static_cast(6)); + Physics::Upgrades::SetLevel(pveh, static_cast(6), max_nitrous); + cust_rec->WritePhysicsIntoRecord(pveh); + GRaceCustom *custom = GRaceDatabase::Get().AllocCustomRace(pSelectedTrack->pRaceParams); + FEDatabase->FillCustomRace(custom, &raceSettings); + GRaceDatabase::Get().SetStartupRace(custom, kRaceContext_QuickRace); + GRaceDatabase::Get().FreeCustomRace(custom); + StartRace(); + break; + } + case 0xc519bfc4: + pSelectedCar = GetRandomCar(); + pSelectedTrack = GetRandomTrack(); + randomCount = 30; + GarageMainScreen::GetInstance()->DisableCarRendering(); + cFEng::Get()->QueuePackageMessage(0xa05a328e, nullptr, nullptr); + FEngSetScript(PackageFilename, 0xfe8fdbf7, 0x16a259, true); + break; + case 0x911ab364: + cFEng::Get()->QueuePackageSwitch("FeQuickRaceMainMenu.fng", 0, 0, false); + break; + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.hpp index 7c2cc6a1a..1a8df5f01 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRBrief.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRBRIEF_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRBRIEF_H @@ -5,6 +6,67 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include + +struct SelectableCar; +struct SelectableTrack; + +// total size: 0x10 +struct SelectableCar : public bTNode { + SelectableCar(unsigned int handle, bool locked) + : mHandle(handle) // + , bLocked(locked) {} + + ~SelectableCar() {} + + unsigned int mHandle; // offset 0x8, size 0x4 + bool bLocked; // offset 0xC, size 0x1 +}; + +// total size: 0x14 +struct SelectableTrack : public bTNode { + static void *operator new(size_t s) { return ::operator new[](s); } + static void operator delete(void *p) { ::operator delete[](p); } + + SelectableTrack(GRaceParameters *rp, bool locked, int bin_num) + : pRaceParams(rp) // + , bLocked(locked) // + , bin(bin_num) {} + + ~SelectableTrack() {} + + GRaceParameters *pRaceParams; // offset 0x8, size 0x4 + bool bLocked; // offset 0xC, size 0x1 + int bin; // offset 0x10, size 0x4 +}; + +// total size: 0x138 +struct UIQRBrief : public MenuScreen { + UIQRBrief(ScreenConstructorData *sd); + ~UIQRBrief() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + void RefreshHeader(); + void UpdateSliders(); + void Setup(); + SelectableCar *GetRandomCar(); + SelectableTrack *GetRandomTrack(); + + bTList FilteredCarsList; // offset 0x2C, size 0x8 + SelectableCar *pSelectedCar; // offset 0x34, size 0x4 + bTList FilteredTracksList; // offset 0x38, size 0x8 + SelectableTrack *pSelectedTrack; // offset 0x40, size 0x4 + RaceSettings raceSettings; // offset 0x44, size 0x24 + int randomCount; // offset 0x68, size 0x4 + TwoStageSlider AccelerationSlider; // offset 0x6C, size 0x44 + TwoStageSlider TopSpeedSlider; // offset 0xB0, size 0x44 + TwoStageSlider HandlingSlider; // offset 0xF4, size 0x44 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.cpp index e69de29bb..89f87478a 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.cpp @@ -0,0 +1,1483 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.hpp" +#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Physics/PhysicsInfo.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h" + +extern void eLoadStreamingTexture(unsigned int *textures, int count, void (*callback)(unsigned int), void *user, int priority); +extern void eUnloadStreamingTexture(unsigned int *textures, int count); +extern int GetCurrentLanguage(); +extern int GetMikeMannBuild(); +extern FEMarkerManager TheFEMarkerManager; +extern int CheatImpounded; +extern int CheatBustedCount; +extern int CheatMaxBusted; +extern int CheatReleasable; +extern int CheatCanAddImpoundBox; +extern int CheatReleaseFromImpoundMarker; +extern int g_MaximumMaximumTimesBusted; +extern int gPlayerNum; + +extern void LoadOneTexture(const char *pkg_name, unsigned int hash, void (*callback)(unsigned int), unsigned int param); +extern bool GetIsCollectorsEdition(); +extern FEImage *FEngFindImage(const char *pkg, int hash); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +extern void FEngSetScript(const char *pkg, unsigned int obj_hash, unsigned int script_hash, bool p); +extern bool FEngIsScriptSet(const char *pkg, unsigned int obj_hash, unsigned int script_hash); +extern void FEngSetLanguageHash(const char *pkg, unsigned int obj_hash, unsigned int lang_hash); +extern unsigned int FEngHashString(const char *fmt, ...); + +extern Timer RealTimer; +extern int Showcase_FromIndex; +extern const char *Showcase_FromPackage; +extern unsigned int Showcase_FromArgs; +extern int Showcase_FromFilter; + +extern int bSNPrintf(char *buf, int size, const char *fmt, ...); +extern const char *GetLocalizedString(unsigned int hash); +extern unsigned long FEHashUpper(const char *str); +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int obj_hash); +extern void FEngSetInvisible(FEObject *obj); + +void MemcardEnter(const char *from, const char *to, unsigned int op, void (*pTermFunc)(void *), + void *pTermFuncParam, unsigned int msgSuccess, unsigned int msgFailed); + +void RaceStarterStartCareerFreeRoam() asm("StartCareerFreeRoam__11RaceStarter"); +void RaceStarterStartRace() asm("StartRace__11RaceStarter"); + +unsigned int UIQRCarSelect::ForceCar; +bool QRCarSelectBustedManager::bPlayerJustGotBusted; + +QRCarSelectBustedManager::QRCarSelectBustedManager(const char *pkg_name, int flags) { + Flags = static_cast(flags); + ParentPkg = pkg_name; + ImpoundStampHash = 0; + bWantsImpound = false; + WorkingCareerRecord = nullptr; + WorkingCarRecord = nullptr; +} + +QRCarSelectBustedManager::~QRCarSelectBustedManager() { + if (ImpoundStampHash) { + unsigned int hash = ImpoundStampHash; + eUnloadStreamingTexture(&hash, 1); + ImpoundStampHash = 0; + } +} + +bool QRCarSelectBustedManager::IsImpoundInfoVisible() { + unsigned int mode = FEDatabase->GetGameMode(); + if (!(mode & 1)) return false; + return !(mode & 0x8000); +} + +bool QRCarSelectBustedManager::ShowImpoundedTexture() { + return WorkingCareerRecord->TheImpoundData.IsImpounded(); +} + +void QRCarSelectBustedManager::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0x8defa48b: + TheFEMarkerManager.UtilizeMarker(FEMarkerManager::MARKER_ADD_IMPOUND_BOX, 0); + WorkingCareerRecord->TheImpoundData.AddMaxBusted(); + break; + case 0xa0fc39f9: + WorkingCareerRecord->TheImpoundData.NotifyPlayerPaidToRelease(); + WorkingCareerRecord->SetVehicleHeat(0.0f); + { + unsigned int cost = WorkingCarRecord->GetReleaseFromImpoundCost(); + FEDatabase->GetCareerSettings()->SpendCash(static_cast(static_cast(cost))); + } + break; + case 0xe845bc1c: + WorkingCareerRecord->TheImpoundData.NotifyPlayerUsedMarkerToRelease(); + WorkingCareerRecord->SetVehicleHeat(0.0f); + TheFEMarkerManager.UtilizeMarker(FEMarkerManager::MARKER_IMPOUND_RELEASE, 0); + break; + case 0x3fdc64c1: + FEManager::Get()->SetGarageType(static_cast(1)); + FEDatabase->ClearGameMode(static_cast(1)); + FEDatabase->SetGameMode(static_cast(0x100)); + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + return; + case 0xe0b38195: + if (!CalcGameOver()) return; + FEDatabase->GetCareerSettings()->SetGameOver(); + DialogInterface::ShowOneButton(ParentPkg, "GameOver.fng", static_cast(1), + 0x417b2601, 0x3fdc64c1, 0x164bed94); + return; + default: + return; + } + RefreshHeader(); +} + +void QRCarSelectBustedManager::TextureLoadedCallback() { + if (!ShowImpoundedTexture()) { + FEngSetScript(ParentPkg, 0xbc7b91f, 0x16a259, true); + } else { + FEImage *img1 = FEngFindImage(ParentPkg, 0xce18427d); + FEngSetTextureHash(img1, ImpoundStampHash); + FEImage *img2 = FEngFindImage(ParentPkg, 0x5b8f2a45); + FEngSetTextureHash(img2, ImpoundStampHash); + if (Flags == BUSTED_ANIM_SHOW_IMPOUNDED) { + FEngSetScript(ParentPkg, 0xbc7b91f, 0x5a8e4ebe, true); + Flags = BUSTED_ANIM_NOTHING; + } else { + FEngSetScript(ParentPkg, 0xbc7b91f, 0x6ebbfb68, true); + } + } +} + +void QRCarSelectBustedManager::LoadImpoundTexture() { + int lang = GetCurrentLanguage(); + unsigned int hash; + switch (lang) { + case 1: hash = 0xce184740; break; + case 2: hash = 0xce1849e1; break; + case 3: hash = 0xce185441; break; + case 4: hash = 0xce187e47; break; + case 5: hash = 0xce183f30; break; + case 6: hash = 0xce187f32; break; + case 7: hash = 0xce183c96; break; + case 8: hash = 0xce185c2f; break; + case 9: hash = 0xce183937; break; + case 10: hash = 0xce18561e; break; + case 11: hash = 0xce188180; break; + case 12: hash = 0xce18716e; break; + case 13: hash = 0xce184620; break; + default: hash = 0xce18427d; break; + } + ImpoundStampHash = hash; + unsigned int texArray[1]; + texArray[0] = ImpoundStampHash; + eLoadStreamingTexture(texArray, 1, TextureLoadedCallbackAccessor, reinterpret_cast(this), 0); +} + +void QRCarSelectBustedManager::SetSelectedCar(FECarRecord *record) { + WorkingCarRecord = record; + FECareerRecord *career = FEDatabase->GetPlayerCarStable(0)->GetCareerRecordByHandle(record->CareerHandle); + WorkingCareerRecord = career; + if (CheatImpounded != 0) { + career->TheImpoundData.MaxBusted = static_cast(CheatMaxBusted); + career->TheImpoundData.TimesBusted = static_cast(CheatMaxBusted); + career->TheImpoundData.BecomeImpounded(FEImpoundData::IMPOUND_REASON_STRIKE_LIMIT_REACHED); + Flags = BUSTED_ANIM_SHOW_IMPOUNDED; + } else if (CheatBustedCount != 0) { + career->TheImpoundData.TimesBusted = static_cast(CheatBustedCount); + career->TheImpoundData.MaxBusted = static_cast(CheatMaxBusted); + Flags = BUSTED_ANIM_SHOW_STRIKE; + } + if (CheatReleasable != 0) { + bool released; + do { + released = career->TheImpoundData.NotifyWin(); + } while (!released); + } + RefreshHeader(); +} + +void QRCarSelectBustedManager::RefreshHeader() { + if (!IsImpoundInfoVisible()) return; + + bool bNotImpounded = false; + if (ShowImpoundedTexture()) { + TextureInfo *texInfo = GetTextureInfo(ImpoundStampHash, 0, 0); + if (texInfo) { + FEngSetScript(ParentPkg, 0xbc7b91f, 0x6ebbfb68, true); + FEngSetScript(ParentPkg, 0x64f3a49c, 0x5079c8f8, true); + FEImage *img1 = FEngFindImage(ParentPkg, 0xce18427d); + FEngSetTextureHash(img1, ImpoundStampHash); + FEImage *img2 = FEngFindImage(ParentPkg, 0x5b8f2a45); + FEngSetTextureHash(img2, ImpoundStampHash); + } + unsigned int cost = WorkingCarRecord->GetReleaseFromImpoundCost(); + int playerCash = *reinterpret_cast(reinterpret_cast(FEDatabase->GetUserProfile(0)) + 0xf0); + bool canAffordRelease = playerCash >= static_cast(static_cast(cost)); + bool hasMarkers = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_IMPOUND_RELEASE, 0) > 0; + if (WorkingCareerRecord->TheImpoundData.ImpoundedState == 4 && canAffordRelease) { + FEngSetLanguageHash(ParentPkg, 0xb94139f4, 0x281dee8a); + } else if (hasMarkers) { + FEngSetLanguageHash(ParentPkg, 0xb94139f4, 0xf9c73cc2); + } else { + FEngSetLanguageHash(ParentPkg, 0xb94139f4, 0x2b65a216); + } + } else { + FEngSetLanguageHash(ParentPkg, 0xb94139f4, 0x2b65a216); + FEngSetScript(ParentPkg, 0x64f3a49c, 0x16a259, true); + } + if ((WorkingCareerRecord->TheImpoundData.TimesBusted & 0x80) == 0) { + FEngSetVisible(FEngFindObject(ParentPkg, 0x75721326)); + int posIndex = 1; + unsigned int script1 = 0x16a259; + unsigned int script2 = 0x16a259; + if (WorkingCareerRecord->TheImpoundData.MaxBusted == 4) { + posIndex = 2; + script2 = 0x1ca7c0; + } else if (WorkingCareerRecord->TheImpoundData.MaxBusted == 5) { + posIndex = 3; + script2 = 0x1ca7c0; + script1 = 0x1ca7c0; + } + FEngSetScript(ParentPkg, 0x5bc78037, script2, true); + FEngSetScript(ParentPkg, 0x48095518, script1, true); + FEngSetScript(ParentPkg, 0xf9a5ce86, FEngHashString("POS%d", posIndex), true); + FEngSetScript(ParentPkg, 0xebf0016e, FEngHashString("POS%d", posIndex), true); + if (Flags == BUSTED_ANIM_SHOW_STRIKE) { + FEngSetScript(ParentPkg, FEngHashString("IMPOUND_STATE_%d", static_cast(WorkingCareerRecord->TheImpoundData.TimesBusted)), 0x5a8e4ebe, true); + Flags = BUSTED_ANIM_NOTHING; + } + for (int i = 1; i <= static_cast(static_cast(WorkingCareerRecord->TheImpoundData.MaxBusted)); i++) { + if (WorkingCareerRecord->TheImpoundData.TimesBusted >= i) { + if (!FEngIsScriptSet(ParentPkg, FEngHashString("IMPOUND_STATE_%d", i), 0x5a8e4ebe)) { + FEngSetScript(ParentPkg, FEngHashString("IMPOUND_STATE_%d", i), 0x1ca7c0, true); + } + } else { + FEngSetScript(ParentPkg, FEngHashString("IMPOUND_STATE_%d", i), 0x16a259, true); + } + } + } else { + signed char impState = WorkingCareerRecord->TheImpoundData.ImpoundedState; + if (impState == 4) { + FEngSetInvisible(FEngFindObject(ParentPkg, 0x75721326)); + } else if (impState != 0) { + FEngSetInvisible(FEngFindObject(ParentPkg, 0x75721326)); + } else { + bNotImpounded = true; + } + } + if (bNotImpounded) { + FEngSetScript(ParentPkg, 0xbc7b91f, 0x16a259, true); + FEngSetInvisible(FEngFindObject(ParentPkg, 0x75721326)); + FEngSetLanguageHash(ParentPkg, 0xb94139f4, 0x2b65a216); + } +} + +bool QRCarSelectBustedManager::CalcGameOver() { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + bool game_over = false; + if (stable->GetNumAvailableCareerCars() < 1) { + if (!TheFEMarkerManager.HasMarker(FEMarkerManager::MARKER_IMPOUND_RELEASE, 0)) { + game_over = true; + } + } + return game_over; +} + +void QRCarSelectBustedManager::MaybeReleaseCar() { + FECareerRecord *career = FEDatabase->GetPlayerCarStable(0)->GetCareerRecordByHandle(WorkingCarRecord->CareerHandle); + unsigned int cost = WorkingCarRecord->GetReleaseFromImpoundCost(); + int cash = FEDatabase->GetCareerSettings()->GetCash(); + int numMarkers = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_IMPOUND_RELEASE, 0); + + if (career->TheImpoundData.ImpoundedState == 4 && static_cast(cost) <= cash) { + if (numMarkers > 0) { + DialogInterface::ShowThreeButtons(ParentPkg, "", static_cast(3), + 0xf9c73cc2, 0x4eb9591f, 0x1a294dad, 0xe845bc1c, 0xa0fc39f9, 0x5ee58948, 0x5ee58948, + static_cast(1), 0xb715ae8f, cost); + } else { + DialogInterface::ShowTwoButtons(ParentPkg, "", static_cast(3), + 0xf9c73cc2, 0x4eb9591f, 0x1a294dad, 0xa0fc39f9, 0xcad5722e, + static_cast(1), 0xb715ae8f, cost); + } + } else { + if (numMarkers > 0) { + DialogInterface::ShowTwoButtons(ParentPkg, "", static_cast(3), + 0x417b2601, 0x1a294dad, 0xe845bc1c, 0xcad5722e, 0x5ee58948, + static_cast(1), 0xb715ae8f, cost); + } else { + DialogInterface::ShowOneButton(ParentPkg, "", static_cast(1), + 0x417b2601, 0x5ee58948, 0xe96fa0c5); + } + } +} + +void QRCarSelectBustedManager::MaybeAddImpoundBox() { + bool canAdd = false; + if (WorkingCareerRecord->TheImpoundData.CanAddMaxBusted()) { + int numMarkers = TheFEMarkerManager.GetNumMarkers(FEMarkerManager::MARKER_ADD_IMPOUND_BOX, 0); + if (numMarkers > 0) canAdd = true; + } + bool showDialog = canAdd || CheatCanAddImpoundBox != 0; + if (showDialog) { + DialogInterface::ShowTwoButtons(ParentPkg, "", static_cast(3), + 0x417b2601, 0x1a294dad, 0x8defa48b, 0xb4edeb6d, 0xb4edeb6d, + static_cast(1), 0xcebda20); + } else if (g_MaximumMaximumTimesBusted <= static_cast(WorkingCareerRecord->TheImpoundData.MaxBusted)) { + DialogInterface::ShowOneButton(ParentPkg, "", static_cast(2), + 0x417b2601, 0xb4edeb6d, 0xbcae8539); + } +} + +UIQRCarSelect::UIQRCarSelect(ScreenConstructorData *sd) : MenuScreen(sd) // + , TheBustedManager(GetPackageName(), sd->Arg >> 8) +{ + FilteredCarsList.InitList(); + originalCar = 0xFFFFFFFF; + tLastEventTimer = 0; + pManuLogo = nullptr; + pCarBadge = nullptr; + pCarName = nullptr; + pCarNameShadow = nullptr; + pFilter = nullptr; + tLastEventTimer = 0; + bLoadingBarActive = false; + bShowcaseMode = false; + iPlayerNum = sd->Arg & 0xFF; + filter = 0; + iPrevButtonMsg = 0; + + pManuLogo = FEngFindImage(GetPackageName(), 0x3e01ad1d); + pCarBadge = FEngFindImage(GetPackageName(), 0xb05dd708); + pCarName = FEngFindString(GetPackageName(), 0xd6d32016); + pCarNameShadow = FEngFindString(GetPackageName(), 0x79d6e45c); + pFilter = FEngFindString(GetPackageName(), 0x5ba2f765); + + bool isSplit = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplit = FEDatabase->GetGameMode() == 2; + } + if (isSplit) { + gPlayerNum = iPlayerNum; + if (gPlayerNum == 0) { + FEngSetLanguageHash(GetPackageName(), 0xe3fe27fe, 0x7b070984); + FEDatabase->DeleteMultiplayerProfile(1); + } else { + gPlayerNum = 1; + FEDatabase->CreateMultiplayerProfile(1); + FEngSetLanguageHash(GetPackageName(), 0xe3fe27fe, 0x7b070985); + } + } else { + gPlayerNum = 0; + } + + if (!FEDatabase->IsCarLotMode()) { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x19398802)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x1930b057)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x20d113dc)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x20c83c31)); + } + + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xe9ed0a2)); + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x18a4384f)); + } + + if (TheBustedManager.IsImpoundInfoVisible()) { + TheBustedManager.LoadImpoundTexture(); + } + + InitStatsSliders(); + Setup(); +} + +UIQRCarSelect::~UIQRCarSelect() { +} + +bool UIQRCarSelect::IsCarImpounded(unsigned int handle) { + FECarRecord *car = FEDatabase->GetPlayerCarStable(0)->GetCarRecordByHandle(handle); + if (!car) return false; + FECareerRecord *career = FEDatabase->GetPlayerCarStable(0)->GetCareerRecordByHandle(car->CareerHandle); + if (!career) return false; + return career->TheImpoundData.IsImpounded(); +} + +void UIQRCarSelect::CommitChangeStartRace(bool allowError) { + FEManager::Get()->AllowControllerError(allowError); + FEDatabase->DeleteMultiplayerProfile(1); + RaceStarterStartRace(); +} + +void UIQRCarSelect::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + if (TheBustedManager.IsImpoundInfoVisible()) { + TheBustedManager.NotificationMessage(msg, pobj, param1, param2); + } + + switch (msg) { + case 0x1265ece9: { + if (!FEDatabase->IsCareerMode()) return; + if (!FEDatabase->IsCarLotMode()) return; + GarageMainScreen::GetInstance()->UpdateCurrentCameraView(false); + return; + } + case 0x35f8620b: { + bool isSplitScreen = false; + if ((FEDatabase->GetGameMode() & 4) != 0) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) return; + cFEng::Get()->QueuePackageMessage(0x841d518a, GetPackageName(), nullptr); + RefreshHeader(); + return; + } + case 0xc98356ba: { + GarageMainScreen::GetInstance(); + if (GarageMainScreen::GetInstance()->IsCarRendering() && bLoadingBarActive) { + cFEng::Get()->QueuePackageMessage(0x913fa282, GetPackageName(), nullptr); + bLoadingBarActive = false; + } + if (!tLastEventTimer.IsSet()) return; + float elapsed = (RealTimer - tLastEventTimer).GetSeconds(); + if (elapsed < 0.5f) return; + { + RideInfo ride; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + stable->BuildRideForPlayer(pSelectedCar->mHandle, iPlayerNum, &ride); + SetRideInfo(&ride, static_cast(1), static_cast(0)); + tLastEventTimer.UnSet(); + } + return; + } + case 0x9120409e: + ScrollCars(eSD_PREV); + return; + case 0xb5971bf1: + ScrollCars(eSD_NEXT); + return; + case 0x72619778: + ScrollLists(eSD_PREV); + return; + case 0x911c0a4b: + ScrollLists(eSD_NEXT); + return; + case 0xc519bfbf: { + if (!pSelectedCar) return; + if (pSelectedCar->bLocked != 0) return; + unsigned int flags = FEDatabase->GetGameMode(); + if ((flags & 8) != 0) return; + if ((flags & 0x40) != 0) return; + if ((flags & 1) != 0 && (flags & 0x8000) == 0) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *car = GetSelectedCarRecord(); + FECareerRecord *career = stable->GetCareerRecordByHandle(car->CareerHandle); + if (career && career->TheImpoundData.IsImpounded()) return; + } + cFEng::Get()->QueuePackageMessage(0x89d0649c, GetPackageName(), nullptr); + bShowcaseMode = true; + return; + } + case 0xa46253ba: { + FECarRecord *car = GetSelectedCarRecord(); + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + unsigned int cost = car->GetCost(); + FEDatabase->GetCareerSettings()->AddCash(static_cast(cost >> 1)); + FEPlayerCarDB *stable2 = FEDatabase->GetPlayerCarStable(iPlayerNum); + stable2->DeleteCareerCar(pSelectedCar->mHandle, true); + unsigned int old_handle = pSelectedCar->mHandle; + RefreshCarList(); + if (old_handle == originalCar) { + SetupForPlayer(0); + originalCar = FEDatabase->GetCareerSettings()->GetCurrentCar(); + } + RefreshHeader(); + return; + } + case 0xc519bfc4: { + if (!FEDatabase->IsCareerMode()) return; + if (FEDatabase->IsCarLotMode()) return; + if (FEDatabase->GetCareerSettings()->GetCurrentBin() > 15) return; + if (!pSelectedCar) return; + FECarRecord *car = GetSelectedCarRecord(); + bool isCareer = car->CareerHandle != 0xff; + if (isCareer) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + FECareerRecord *career = stable->GetCareerRecordByHandle(car->CareerHandle); + if (career->TheImpoundData.IsImpounded()) { + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), + 0x417b2601, 0x34dc1bcf, 0x80e4f27c); + return; + } + } + FEPlayerCarDB *stable2 = FEDatabase->GetPlayerCarStable(iPlayerNum); + if (stable2->GetNumAvailableCareerCars() < 2) { + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), + 0x417b2601, 0x34dc1bcf, 0x9a772bd6); + return; + } + unsigned int cost = car->GetCost(); + char buf[512]; + char cost_str[16]; + bSNPrintf(cost_str, 0x10, "%d", cost >> 1); + bSNPrintf(buf, 0x200, GetLocalizedString(0xb4a40135), cost_str); + DialogInterface::ShowTwoButtons(GetPackageName(), "", static_cast(1), + 0x70e01038, 0x417b25e4, 0xa46253ba, 0x34dc1bcf, 0x34dc1bcf, + static_cast(1), buf); + return; + } + case 0xc519bfc3: { + bool isSplitScreen = false; + if ((FEDatabase->GetGameMode() & 4) != 0) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (isSplitScreen) { + unsigned int op = 0x411; + if (iPlayerNum == 1) { + op = 0x20411; + } + MemcardEnter(GetPackageName(), GetPackageName(), op, nullptr, nullptr, + 0x7e998e5e, 0x8867412d); + return; + } + if (!TheBustedManager.IsImpoundInfoVisible()) return; + TheBustedManager.MaybeAddImpoundBox(); + return; + } + case 0xe1fde1d1: { + if (bShowcaseMode) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + FECarRecord *car = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + Showcase_FromPackage = GetPackageName(); + ForceCar = pSelectedCar->mHandle; + Showcase_FromArgs = iPlayerNum; + cFEng::Get()->QueuePackageSwitch("Showcase.fng", reinterpret_cast(car), 0, false); + return; + } + ForceCar = 0xffffffff; + if (iPrevButtonMsg == 0x406415e3) { + unsigned int flags = FEDatabase->GetGameMode(); + if ((flags & 8) != 0 || (flags & 0x40) != 0) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->SelectedCar[iPlayerNum] = originalCar; + cFEng::Get()->QueuePackageSwitch("OL_MAIN.fng", 0, 0, false); + return; + } + if ((flags & 1) != 0) { + if ((flags & 0x8000) == 0) { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + return; + } + if (FEDatabase->iNumPlayers != 2) { + if (FEDatabase->GetPlayerSettings(iPlayerNum)->TransmissionPromptOn != 0) { + ChooseTransmission(); + return; + } + } + CommitChangeStartRace(true); + return; + } else { + if ((flags & 0x20) != 0) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + FECarRecord *car = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + if (!car->IsCustomized()) { + FECarRecord *new_car = FEDatabase->GetPlayerCarStable(iPlayerNum)->CreateNewCustomCar(car->Handle); + car = new_car; + } + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->SelectedCar[iPlayerNum] = car->Handle; + cFEng::Get()->QueuePackageSwitch("MyCarsManager.fng", 0, 0, false); + return; + } + if (FEDatabase->iNumPlayers == 2 || + FEDatabase->GetPlayerSettings(iPlayerNum)->TransmissionPromptOn == 0) { + bool isSplitScreen = false; + if ((FEDatabase->GetGameMode() & 4) != 0) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (isSplitScreen && iPlayerNum != 1) { + return; + } + CommitChangeStartRace(true); + return; + } + ChooseTransmission(); + return; + } + } else if (iPrevButtonMsg == 0x911ab364) { + int iVar3 = -1; + unsigned int flags = FEDatabase->GetGameMode(); + if ((flags & 1) != 0) { + if ((flags & 0x8000) != 0) { + RaceStarterStartCareerFreeRoam(); + return; + } + if (IsCarImpounded(originalCar) == 0) { + FEDatabase->GetCareerSettings()->SetCurrentCar(originalCar); + } + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } else if ((flags & 8) != 0 || (flags & 0x40) != 0) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->SelectedCar[iPlayerNum] = originalCar; + cFEng::Get()->QueuePackageSwitch("OL_MAIN.fng", 0, 0, false); + } else if ((flags & 0x20) != 0) { + cFEng::Get()->QueuePackageSwitch("MyCarsManager.fng", 0, 0, false); + } else { + bool isSplitScreen = false; + if ((flags & 4) != 0) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (isSplitScreen) { + bool returnToPressStart = iPlayerNum != 1; + if (returnToPressStart) { + FEManager::Get()->SetGarageType(GARAGETYPE_NONE); + } else { + FEDatabase->SetPlayersJoystickPort(1, -1); + } + returnToPressStart = !returnToPressStart; + cFEng::Get()->QueuePackageSwitch("PressStart.fng", returnToPressStart, 0xff, false); + } else { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->SelectedCar[iPlayerNum] = originalCar; + iVar3 = originalCar; + FEManager::Get()->SetGarageType(GARAGETYPE_NONE); + cFEng::Get()->QueuePackageSwitch("Track_Options.fng", 0, 0, false); + } + } + if (iVar3 != -1) { + RideInfo ride; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + stable->BuildRideForPlayer(iVar3, iPlayerNum, &ride); + SetRideInfo(&ride, static_cast(1), static_cast(0)); + } + return; + } + ForceCar = 0xffffffff; + return; + } + case 0xd05fc3a3: { + FECarRecord *car = GetSelectedCarRecord(); + FECarRecord *new_car = FEDatabase->GetPlayerCarStable(0)->CreateNewCareerCar(car->Handle); + if (new_car) { + FEDatabase->GetCareerSettings()->SpendCash(new_car->GetCost()); + FEDatabase->GetCareerSettings()->SetCurrentCar(new_car->Handle); + } + RaceStarterStartCareerFreeRoam(); + return; + } + case 0xb1ee867d: { + FECarRecord *car = GetSelectedCarRecord(); + FECarRecord *new_car = FEDatabase->GetPlayerCarStable(0)->CreateNewCareerCar(car->Handle); + if (new_car) { + FEDatabase->GetCareerSettings()->SpendCash(new_car->GetCost()); + } + RaceStarterStartCareerFreeRoam(); + return; + } + case 0x0c407210: + case 0x406415e3: { + if (!pSelectedCar) return; + if (pSelectedCar->bLocked != 0) return; + + float elapsed = (RealTimer - tLastEventTimer).GetSeconds(); + if (elapsed < 0.5f) return; + + unsigned int flags = FEDatabase->GetGameMode(); + if ((flags & 8) != 0 || (flags & 0x40) != 0) { + OnlineActOnSelect(); + iPrevButtonMsg = 0x406415e3; + ChooseTransmission(); + return; + } + if ((flags & 1) != 0) { + if ((flags & 0x8000) != 0) { + if (MemoryCard::GetInstance()->m_bListingOldSaveFiles) return; + unsigned int cost = GetSelectedCarRecord()->GetCost(); + if (cost > static_cast(FEDatabase->GetCareerSettings()->GetCash())) { + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), + 0x417b2601, 0x34dc1bcf, 0x40fa955d); + return; + } + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + if (stable->GetNumPurchasedCars() > 9) { + DialogInterface::ShowOneButton(GetPackageName(), "", static_cast(1), + 0x417b2601, 0x34dc1bcf, 0x41030a1b); + return; + } + if (FEDatabase->GetCareerSettings()->GetCurrentBin() > 15) { + DialogInterface::ShowTwoButtons(GetPackageName(), "", static_cast(1), + 0x70e01038, 0x417b25e4, 0xd05fc3a3, 0x34dc1bcf, 0x34dc1bcf, + static_cast(1), 0x74317cbc); + return; + } + DialogInterface::ShowThreeButtons(GetPackageName(), "", static_cast(1), + 0x5b9d89d0, 0x889d822e, 0x1a294dad, 0xd05fc3a3, 0xb1ee867d, + 0x34dc1bcf, 0x34dc1bcf, static_cast(2), 0x8c451eba); + return; + } + FEPlayerCarDB *stable2 = FEDatabase->GetPlayerCarStable(0); + FECarRecord *car = stable2->GetCarRecordByHandle(pSelectedCar->mHandle); + FECareerRecord *career = stable2->GetCareerRecordByHandle(car->CareerHandle); + if (career->TheImpoundData.IsImpounded() || + career->TheImpoundData.ImpoundedState == FEImpoundData::IMPOUND_RELEASED) { + TheBustedManager.MaybeReleaseCar(); + return; + } + iPrevButtonMsg = 0x406415e3; + cFEng::Get()->QueuePackageMessage(0x2e76edfb, GetPackageName(), nullptr); + return; + } + if ((flags & 0x20) != 0) { + iPrevButtonMsg = 0x406415e3; + cFEng::Get()->QueuePackageMessage(0x2e76edfb, GetPackageName(), nullptr); + return; + } + if (FEDatabase->iNumPlayers != 2) { + if (FEDatabase->GetPlayerSettings(iPlayerNum)->TransmissionPromptOn != 0) { + ChooseTransmission(); + return; + } + } + char port = FEngMapJoyParamToJoyport(param2); + FEDatabase->SetPlayersJoystickPort(iPlayerNum, port); + bool isSplitScreen = false; + if ((flags & 4) != 0) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen || iPlayerNum != 0) { + iPrevButtonMsg = 0x406415e3; + cFEng::Get()->QueuePackageMessage(0x2e76edfb, GetPackageName(), nullptr); + return; + } + cFEng::Get()->QueuePackageSwitch("PressStart.fng", true, 0xff, false); + return; + } + case 0x911ab364: { + bool bShouldProceed = true; + unsigned int flags = FEDatabase->GetGameMode(); + if ((flags & 1) == 0) { + if ((flags & 8) != 0 || (flags & 0x40) != 0) { + RideInfo ride; + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + stable->BuildRideForPlayer(originalCar, iPlayerNum, &ride); + SetRideInfo(&ride, static_cast(1), static_cast(0)); + } + } else if ((flags & 0x8000) == 0) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(0); + FECarRecord *car = stable->GetCarRecordByHandle(originalCar); + FECareerRecord *career = stable->GetCareerRecordByHandle(car->CareerHandle); + if (career->TheImpoundData.IsImpounded() || + career->TheImpoundData.ImpoundedState == FEImpoundData::IMPOUND_RELEASED) { + DialogInterface::ShowOk(GetPackageName(), "", static_cast(1), 0x630931b6); + bShouldProceed = false; + } + } else { + bShouldProceed = FEDatabase->GetCareerSettings()->GetCurrentBin() < 16; + } + if (!bShouldProceed) return; + iPrevButtonMsg = 0x911ab364; + cFEng::Get()->QueuePackageMessage(0x93e8a57c, GetPackageName(), nullptr); + return; + } + case 0x1fab5998: { + if (FEDatabase->IsCareerMode()) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + unsigned int handle = pSelectedCar->mHandle; + FECarRecord *car = stable->GetCarRecordByHandle(handle); + FEDatabase->GetCareerSettings()->SetCurrentCar(car->Handle); + FEManager::Get()->SetGarageType(GARAGETYPE_NONE); + return; + } + bool isSplitScreen = false; + if ((FEDatabase->GetGameMode() & 4) != 0) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (isSplitScreen) return; + FEManager::Get()->SetGarageType(GARAGETYPE_NONE); + return; + } + case 0x1a2826e1: { + char port = FEngMapJoyParamToJoyport(param2); + FEDatabase->SetPlayersJoystickPort(iPlayerNum, port); + FEDatabase->GetPlayerSettings(iPlayerNum)->Transmission = 0; + bool isSplitScreen = false; + if ((FEDatabase->GetGameMode() & 4) != 0) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (isSplitScreen && iPlayerNum == 0) { + cFEng::Get()->QueuePackageSwitch("PressStart.fng", true, 0xff, false); + return; + } + CommitChangeStartRace(false); + return; + } + case 0x5f5e3886: { + char port = FEngMapJoyParamToJoyport(param2); + FEDatabase->SetPlayersJoystickPort(iPlayerNum, port); + FEDatabase->GetPlayerSettings(iPlayerNum)->Transmission = 1; + bool isSplitScreen = false; + if ((FEDatabase->GetGameMode() & 4) != 0) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (isSplitScreen && iPlayerNum == 0) { + cFEng::Get()->QueuePackageSwitch("PressStart.fng", true, 0xff, false); + return; + } + CommitChangeStartRace(false); + return; + } + case 0x7e998e5e: + filter = 0xf0001; + RefreshCarList(); + RefreshHeader(); + cFEng::Get()->QueuePackageMessage(FEHashUpper("ENABLE_INPUTS"), GetPackageName(), nullptr); + return; + case 0x8defa48b: + case 0xa0fc39f9: + case 0xe845bc1c: + RefreshHeader(); + return; + } +} + +eMenuSoundTriggers UIQRCarSelect::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + if (msg == 0x72619778 || msg == 0x911c0a4b || msg == 0xb205316c || msg == 0x480df13f) { + unsigned int mode = FEDatabase->GetGameMode(); + if ((mode & 0x20) || (mode & 0x8000) || (mode & 1)) { + return static_cast(-1); + } + } + return MenuScreen::NotifySoundMessage(msg, maybe); +} + +void UIQRCarSelect::Setup() { + unsigned int mode = FEDatabase->GetGameMode(); + unsigned int pkgMsg; + + if ((mode & 0x8000) != 0) { + if (FEDatabase->GetCareerSettings()->GetCurrentBin() > 15) { + pkgMsg = 0x3a12d2f5; + } else { + pkgMsg = 0x5415e304; + } + goto queue_msg; + } + + if ((mode & 4) != 0) { + bool isTwoPlayer = false; + if ((mode & 4) != 0) { + isTwoPlayer = FEDatabase->iNumPlayers == 2; + } + if (isTwoPlayer) { + pkgMsg = 0x2cf6c390; + } else { + pkgMsg = 0xde511657; + } + goto queue_msg; + } + if ((mode & 8) != 0) { + online_lan: + pkgMsg = 0x70fbb1e4; + goto queue_msg; + } + if ((mode & 0x40) != 0) goto online_lan; + if ((mode & 0x20) != 0) { + pkgMsg = 0xa936c3a2; + goto queue_msg; + } + if ((mode & 1) != 0) { + cFEng::Get()->QueuePackageMessage(0x5415c3f1, GetPackageName(), nullptr); + } + goto after_queue; + +queue_msg: + cFEng::Get()->QueuePackageMessage(pkgMsg, GetPackageName(), nullptr); + +after_queue: + + FEngSetInvisible(FEngFindObject(GetPackageName(), 0x64f6d21f)); + + if ((FEDatabase->GetGameMode() & 1) != 0) { + originalCar = FEDatabase->GetCareerSettings()->GetCurrentCar(); + if ((FEDatabase->GetGameMode() & 0x8000) != 0) { + filter = 0xf0001; + UserProfile *profile = FEDatabase->GetUserProfile(0); + if ((profile->GetCareer()->SpecialFlags & 2) == 0) { + cFEng::Get()->QueuePackageMessage(FEHashUpper("DISABLE_INPUTS"), GetPackageName(), nullptr); + MemoryCard::GetInstance()->StartListingOldSaveFiles(); + } + goto init_list_handles; + } + filter = 0xf0002; + } else { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + originalCar = settings->GetSelectedCar(iPlayerNum); + if ((FEDatabase->GetGameMode() & 0x20) == 0 && originalCar != 0x12345678) { + unsigned int m3gtrHash = FEHashUpper("M3GTRCAREERSTART"); + if (originalCar != m3gtrHash) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + if (stable) { + FECarRecord *car = stable->GetCarRecordByHandle(originalCar); + if (car) { + filter = (car->FilterBits & 0x3f) | 0xf0000; + goto init_list_handles; + } + } + } + } + filter = 0xf0001; + } + +init_list_handles: + int i = 0; + do { + ListHandles[i] = 0xFFFFFFFF; + i++; + } while (i < 6); + + RefreshCarList(); + RefreshHeader(); +} + +void UIQRCarSelect::InitStatsSliders() { + AccelerationSlider.Init(GetPackageName(), "AccelSlider", 0.0f, 10.0f, 1.0f, 0.0f, 0.0f, 0.0f); + TopSpeedSlider.Init(GetPackageName(), "TopSpeedSlider", 0.0f, 10.0f, 1.0f, 0.0f, 0.0f, 0.0f); + HandlingSlider.Init(GetPackageName(), "HandlingSlider", 0.0f, 10.0f, 1.0f, 0.0f, 0.0f, 0.0f); +} + +void UIQRCarSelect::UpdateSliders() { + Physics::Info::Performance perf1; + Physics::Info::Performance perf2; + + if (pSelectedCar != nullptr) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + if (stable != nullptr) { + FECarRecord *car = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + if (car != nullptr) { + Attrib::Gen::pvehicle pveh(car->VehicleKey, 0, nullptr); + bool hasCustomization = (car->Customization != 0xff); + if (hasCustomization) { + FECustomizationRecord *cust = stable->GetCustomizationRecordByHandle(car->Customization); + cust->WriteRecordIntoPhysics(pveh); + } + Physics::Info::EstimatePerformance(pveh, perf1); + } + if ((FEDatabase->GetGameMode() & 1) != 0) { + car = stable->GetCarRecordByHandle(originalCar); + } + if (car != nullptr) { + Attrib::Gen::pvehicle pveh2(car->VehicleKey, 0, nullptr); + bool hasCustomization = (car->Customization != 0xff); + if (hasCustomization) { + FECustomizationRecord *cust = stable->GetCustomizationRecordByHandle(car->Customization); + cust->WriteRecordIntoPhysics(pveh2); + } + Physics::Info::EstimatePerformance(pveh2, perf2); + } + } + } + + if (FEDatabase->GetCareerSettings()->GetCurrentBin() > 15) { + perf2 = perf1; + } + + AccelerationSlider.SetValue(perf1.Acceleration); + float acc_preview = bMin(bMax(perf2.Acceleration, AccelerationSlider.GetMin()), AccelerationSlider.GetMax()); + AccelerationSlider.SetPreviewValue(acc_preview); + AccelerationSlider.Draw(); + + TopSpeedSlider.SetValue(perf1.TopSpeed); + float top_preview = bMin(bMax(perf2.TopSpeed, TopSpeedSlider.GetMin()), TopSpeedSlider.GetMax()); + TopSpeedSlider.SetPreviewValue(top_preview); + TopSpeedSlider.Draw(); + + HandlingSlider.SetValue(perf1.Handling); + float hdl_preview = bMin(bMax(perf2.Handling, HandlingSlider.GetMin()), HandlingSlider.GetMax()); + HandlingSlider.SetPreviewValue(hdl_preview); + HandlingSlider.Draw(); +} + +int UIQRCarSelect::GetFilterType() { + switch (static_cast(filter)) { + case 1: return 0; + case 2: return 1; + case 4: return 2; + case 8: return 3; + case 0x10: return 4; + case 0x20: return 5; + default: return 0; + } +} + +void UIQRCarSelect::SetupForPlayer(int player) { + SelectableCar *found = nullptr; + SelectableCar *node = FilteredCarsList.GetHead(); + for (; node != FilteredCarsList.EndOfList(); node = static_cast(node->GetNext())) { + found = node; + if (ForceCar == 0xFFFFFFFF) { + int ft = GetFilterType(); + unsigned int nodeHandle = node->mHandle; + if (nodeHandle == ListHandles[ft]) break; + unsigned int targetHandle; + if ((FEDatabase->GetGameMode() & 1) == 0) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + nodeHandle = node->mHandle; + targetHandle = settings->SelectedCar[iPlayerNum]; + } else { + targetHandle = FEDatabase->CurrentUserProfiles[0]->GetCareer()->CurrentCar; + } + if (nodeHandle == targetHandle) break; + } else { + if (node->mHandle == ForceCar) break; + } + } + if ((FEDatabase->GetGameMode() & 0x8000) != 0 && ForceCar == 0xFFFFFFFF) { + CarViewer::CancelCarLoad(eCARVIEWER_PLAYER1_CAR); + found = FilteredCarsList.GetHead(); + originalCar = found->mHandle; + } + ForceCar = 0xFFFFFFFF; + if (found == nullptr && FilteredCarsList.GetHead() != FilteredCarsList.EndOfList()) { + found = FilteredCarsList.GetHead(); + } + SetSelectedCar(found, iPlayerNum); + RefreshHeader(); +} + +int UIQRCarSelect::GetBonusUnlockText(FECarRecord *fe_car) { + unsigned int handle = fe_car->Handle; + if (handle < 0x136254u) { + if (handle > 0x13624du || (handle < 0x9667u && handle > 0x965eu)) { + return 0x4ef2a115; + } + } else { + switch (handle) { + case 0x2cf370f0u: return 0xbd8bac94; + case 0x03a94520u: return 0xbd8bac93; + case 0x2cf385b2u: return 0xbd8bac92; + case 0xcb6aaf2fu: return 0xbd8bac91; + } + } + return 0; +} + +int UIQRCarSelect::GetBonusUnlockBinNumber(FECarRecord *fe_car) { + unsigned int handle = fe_car->Handle; + switch (handle) { + case 0x965F: return 2; + case 0x9660: return 3; + case 0x9661: return 4; + case 0x9662: return 5; + case 0x9663: return 6; + case 0x9664: return 7; + case 0x9665: return 8; + case 0x9666: return 9; + case 0x13624E: return 10; + case 0x13624F: return 11; + case 0x136250: return 12; + case 0x136251: return 13; + case 0x136252: return 14; + case 0x136253: return 15; + default: return -1; + } +} + +void UIQRCarSelect::RefreshHeader() { + UpdateSliders(); + + unsigned int langhash; + unsigned int texhash; + unsigned short list = static_cast(filter); + switch (list) { + case 1: + langhash = 0xd9d6b954; + texhash = 0x3a541f7f; + break; + case 2: + langhash = 0xee053562; + texhash = 0xf0bddecd; + break; + case 4: + langhash = 0x2414de28; + texhash = 0x9996ca1e; + break; + case 8: + langhash = 0xd8a058f7; + texhash = 0xbe5ad8a2; + break; + case 0x10: + langhash = 0x0d8500c3; + texhash = 0x03704f3d; + break; + case 0x20: + langhash = 0x3ec63978; + texhash = 0x03704f3d; + break; + default: + langhash = 0; + texhash = 0; + break; + } + + if (FEDatabase->IsCarLotMode() || !FEDatabase->IsCareerMode()) { + FEngSetInvisible(GetPackageName(), 0x39dc21f9); + FEngSetInvisible(GetPackageName(), 0xe998fe99); + } + + FEngSetLanguageHash(GetPackageName(), 0xaa9834bc, langhash); + FEImage *filterImg = FEngFindImage(GetPackageName(), 0xe3b271b8); + FEngSetTextureHash(filterImg, texhash); + FEngSetScript(GetPackageName(), 0xd0f7c7cc, 0x16a259, true); + + if (!pSelectedCar) { + FEngSetInvisible(GetPackageName(), 0x7379349b); + } else if (pSelectedCar->bLocked) { + FEngSetInvisible(GetPackageName(), 0x7379349b); + } + + if (!pSelectedCar) { + FEngSetLanguageHash(GetPackageName(), 0x2d25b2c4, 0x58bbed2a); + cFEng::Get()->QueuePackageMessage(0xd9420cd5, GetPackageName(), nullptr); + if (filter & 4) { + FEngSetLanguageHash(GetPackageName(), 0x36c1e04d, 0x0da87b01); + } else { + FEngSetLanguageHash(GetPackageName(), 0x36c1e04d, 0x58bbed2a); + } + FEngSetInvisible(GetPackageName(), 0x0e9ed0a2); + FEngSetInvisible(GetPackageName(), 0x18a4384f); + CarViewer::CancelCarLoad(static_cast(0)); + GarageMainScreen::GetInstance()->DisableCarRendering(); + cFEng::Get()->QueuePackageMessage(0x913fa282, GetPackageName(), nullptr); + bLoadingBarActive = false; + tLastEventTimer = 0; + return; + } + + if (!FEDatabase->IsOnlineMode() && !FEDatabase->IsLANMode()) { + FEngSetVisible(GetPackageName(), 0x0e9ed0a2); + FEngSetVisible(GetPackageName(), 0x18a4384f); + } + + FEngSetVisible(GetPackageName(), 0x7379349b); + cFEng::Get()->QueuePackageMessage(0x7c4583dc, GetPackageName(), nullptr); + FEPrintf(GetPackageName(), 0x6f25a248, "%d", FilteredCarsList.TraversebList(pSelectedCar)); + FEPrintf(GetPackageName(), 0xb2037bdc, "%d", FilteredCarsList.CountElements()); + + FEPlayerCarDB *stable; + if (iPlayerNum < 2) { + stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + } else { + stable = nullptr; + } + + if (pSelectedCar->bLocked) { + FEngSetScript(GetPackageName(), 0xd0f7c7cc, 0x1ca7c0, true); + FECarRecord *car = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + if (car->MatchesFilter(0xf0008)) { + int unlockText = GetBonusUnlockText(car); + if (unlockText == 0x4ef2a115) { + int binNum = GetBonusUnlockBinNumber(car); + char rival_name_locdb[128]; + bSNPrintf(rival_name_locdb, 0x80, "blacklist_rival_%02d_aka", binNum); + const char *fmt = GetLocalizedString(0x4ef2a115); + const char *name = GetLocalizedString(FEHashUpper(rival_name_locdb)); + FEPrintf(GetPackageName(), 0x2d25b2c4, fmt, name, binNum); + } else { + FEngSetLanguageHash(GetPackageName(), 0x2d25b2c4, static_cast(unlockText)); + } + } else { + Attrib::Gen::frontend fe_attrib(car->FEKey, 0, nullptr); + int rival_num = fe_attrib.UnlockedAt() + 1; + char rival_name_locdb[128]; + bSNPrintf(rival_name_locdb, 0x80, "blacklist_rival_%02d_aka", rival_num); + const char *fmt = GetLocalizedString(0x4ef2a115); + const char *name = GetLocalizedString(FEHashUpper(rival_name_locdb)); + FEPrintf(GetPackageName(), 0x2d25b2c4, fmt, name, rival_num); + } + } + + FECarRecord *car = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + FEngSetInvisible(GetPackageName(), 0xd6d32016); + FEngSetInvisible(GetPackageName(), 0x79d6e45c); + FEngSetTextureHash(pManuLogo, car->GetManuLogoHash()); + FEngSetTextureHash(pCarBadge, car->GetLogoHash()); + + if (FEDatabase->IsCarLotMode()) { + FEPrintf(GetPackageName(), 0x1930b057, "%$d", FEDatabase->GetCareerSettings()->GetCash()); + FEPrintf(GetPackageName(), 0x20c83c31, "%$d", car->GetCost()); + FEngSetLanguageHash(GetPackageName(), 0xdc18c4d4, 0xa9950b93); + FEngSetLanguageHash(GetPackageName(), 0xb94139f4, 0x7010bbf2); + } + + if (FEDatabase->IsCareerMode() && !FEDatabase->IsCarLotMode()) { + TheHeatMeter.SetVisibility(true); + } else { + TheHeatMeter.SetVisibility(false); + } + + if (car->IsCareer()) { + FEngSetInvisible(GetPackageName(), 0x39dc21f9); + FEngSetInvisible(GetPackageName(), 0xe998fe99); + + FECareerRecord *career = stable->GetCareerRecordByHandle(car->CareerHandle); + + int num_markers = TheFEMarkerManager.GetNumMarkers(static_cast(0x14), 0); + bool hasMarkers = num_markers > 0; + if (hasMarkers || (CheatCanAddImpoundBox && career->TheImpoundData.ImpoundedState == 0)) { + num_markers = TheFEMarkerManager.GetNumMarkers(static_cast(0x14), 0); + FEngSetVisible(GetPackageName(), 0x39dc21f9); + FEPrintf(GetPackageName(), 0x5b875870, "%2d", num_markers); + FEPrintf(GetPackageName(), 0xea8aecd9, "%2d", num_markers); + } else { + FEngSetInvisible(GetPackageName(), 0x39dc21f9); + } + + if (career->TheImpoundData.ImpoundedState == FEImpoundData::IMPOUND_RELEASED) { + FEngSetLanguageHash(GetPackageName(), 0x72e7ea88, 0x9db4df7d); + FEngSetLanguageHash(GetPackageName(), 0x9d974df3, 0x073b79e0); + unsigned int cost = car->GetReleaseFromImpoundCost(); + FEPrintf(GetPackageName(), 0x322b18f9, "%$0.0f", static_cast(cost)); + FEPrintf(GetPackageName(), 0x7044a5a4, "%$d", FEDatabase->GetCareerSettings()->GetCash()); + FEngSetInvisible(GetPackageName(), 0x0e9ed0a2); + } else if (career->TheImpoundData.ImpoundedState == FEImpoundData::IMPOUND_REASON_NONE) { + FEngSetLanguageHash(GetPackageName(), 0x72e7ea88, 0x17574b0e); + FEngSetLanguageHash(GetPackageName(), 0x9d974df3, 0x915f4d26); + if (!FEDatabase->IsOnlineMode() && !FEDatabase->IsLANMode()) { + FEngSetVisible(GetPackageName(), 0x0e9ed0a2); + } + FECareerRecord *record = stable->GetCareerRecordByHandle(car->CareerHandle); + if (record) { + FEPrintf(GetPackageName(), 0x322b18f9, "%$d", record->GetBounty()); + FEPrintf(GetPackageName(), 0x7044a5a4, "%$d", record->GetInfractions(true).GetFineValue()); + } + } else { + FEngSetLanguageHash(GetPackageName(), 0x72e7ea88, 0x9db4df7d); + FEngSetLanguageHash(GetPackageName(), 0x9d974df3, 0x073b79e0); + FEngSetLanguageHash(GetPackageName(), 0x322b18f9, 0xaefedad9); + FEPrintf(GetPackageName(), 0x7044a5a4, "%$d", FEDatabase->GetCareerSettings()->GetCash()); + FEngSetInvisible(GetPackageName(), 0x0e9ed0a2); + + num_markers = TheFEMarkerManager.GetNumMarkers(static_cast(0x15), 0); + bool hasReleaseMarkers = num_markers > 0; + if (hasReleaseMarkers || CheatReleaseFromImpoundMarker) { + num_markers = TheFEMarkerManager.GetNumMarkers(static_cast(0x15), 0); + FEngSetVisible(GetPackageName(), 0xe998fe99); + FEPrintf(GetPackageName(), 0xcc59b910, "%2d", num_markers); + FEPrintf(GetPackageName(), 0xb8f9938a, "%2d", num_markers); + FEngSetInvisible(GetPackageName(), 0x39dc21f9); + } + } + + { + FECareerRecord *record = stable->GetCareerRecordByHandle(car->CareerHandle); + if (record) { + if (!FEDatabase->IsOnlineMode() && !FEDatabase->IsLANMode()) { + FEngSetVisible(GetPackageName(), 0x18a4384f); + } + FEPrintf(GetPackageName(), 0xd6d32016, "%$d", record->GetBounty()); + FEPrintf(GetPackageName(), 0x79d6e45c, "%$d", record->GetInfractions(true).GetFineValue()); + } + } + + TheHeatMeter.SetCurrent(career->GetVehicleHeat()); + TheHeatMeter.SetPreview(career->GetVehicleHeat()); + TheHeatMeter.Draw(); + } else { + TheHeatMeter.SetVisibility(false); + } +} + +void UIQRCarSelect::ChooseTransmission() { + DialogInterface::ShowTwoButtons(GetPackageName(), "", static_cast(3), + 0x317d3005, 0x8cd532a0, 0x5f5e3886, 0x1a2826e1, 0x34dc1bcf, + (eDialogFirstButtons)(FEDatabase->GetPlayerSettings(iPlayerNum)->Transmission == 0), 0x6f5401d1); +} + +FECarRecord *UIQRCarSelect::GetSelectedCarRecord() { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(iPlayerNum); + FECarRecord *selected_car = stable->GetCarRecordByHandle(pSelectedCar->mHandle); + return selected_car; +} + +void UIQRCarSelect::SetSelectedCar(SelectableCar *newCar, int player_num) { + pSelectedCar = newCar; + if (TheBustedManager.IsImpoundInfoVisible() && pSelectedCar != nullptr) { + FECarRecord *rec = GetSelectedCarRecord(); + TheBustedManager.SetSelectedCar(rec); + } + if (newCar != nullptr) { + int filterIdx = GetFilterType(); + ListHandles[filterIdx] = newCar->mHandle; + GarageMainScreen::GetInstance()->DisableCarRendering(); + cFEng::Get()->QueuePackageMessage(0xa05a328e, nullptr, nullptr); + cFEng::Get()->QueuePackageMessage(0x9c0a27eb, GetPackageName(), nullptr); + bLoadingBarActive = true; + unsigned int mode = FEDatabase->GetGameMode(); + if ((mode & 1) != 0) { + if ((mode & 0x8000) == 0) { + FEDatabase->GetCareerSettings()->SetCurrentCar(newCar->mHandle); + } + } else if ((mode & 0x20) == 0) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->SelectedCar[player_num] = newCar->mHandle; + } + tLastEventTimer = RealTimer; + } +} + +int SortCarsByUnlock(SelectableCar *a, SelectableCar *b) { + FEPlayerCarDB *stable = FEDatabase->GetPlayerCarStable(gPlayerNum); + FECarRecord *carA = stable->GetCarRecordByHandle(a->mHandle); + Attrib::Gen::frontend fe_a(carA->FEKey, 0, nullptr); + FECarRecord *carB = stable->GetCarRecordByHandle(b->mHandle); + Attrib::Gen::frontend fe_b(carB->FEKey, 0, nullptr); + int binA = fe_a.UnlockedAt(); + int binB = fe_b.UnlockedAt(); + return static_cast(binA > binB); +} + +bool IsValidMikeMannCar(FECarRecord *fe_car, unsigned int filter) { + if (GetMikeMannBuild() == 1) { + return fe_car->GetType() != CARTYPE_CAYMANS; + } + if (GetMikeMannBuild() != 2) { + return true; + } + unsigned short lowFilter = static_cast(filter); + if (lowFilter == 1) { + switch (fe_car->GetType()) { + case CARTYPE_RX8: + case CARTYPE_SLR: + case CARTYPE_BMWM3GTR: + case CARTYPE_CAYMANS: + case CARTYPE_GALLARDO: + case CARTYPE_PUNTO: + return true; + default: + return false; + } + } + if (lowFilter != 0x10) { + return true; + } + return fe_car->Handle == bStringHash("M3GTRCAREERSTART"); +} + +void UIQRCarSelect::RefreshBonusCarList() { + bool bCarUnlocked; + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x136253, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x136253, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x136252, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x136252, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x136251, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x136251, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x136250, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x136250, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x13624f, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x13624f, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x13624e, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x13624e, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x9666, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x9666, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x9665, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x9665, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x9664, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x9664, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x9663, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x9663, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x9662, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x9662, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x9661, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x9661, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x9660, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x9660, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x965f, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x965f, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x3a94520, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x3a94520, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0xcb6aaf2f, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0xcb6aaf2f, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x2cf370f0, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x2cf370f0, !bCarUnlocked)); + bCarUnlocked = UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x2cf385b2, iPlayerNum); + FilteredCarsList.AddTail(new SelectableCar(0x2cf385b2, !bCarUnlocked)); + if (UnlockSystem::IsCarUnlocked(UNLOCK_QUICK_RACE, 0x34498eb2, iPlayerNum)) { + FilteredCarsList.AddTail(new SelectableCar(0x34498eb2, false)); + } + if (GetIsCollectorsEdition()) { + FilteredCarsList.AddTail(new SelectableCar(0x634d1bd2, false)); + FilteredCarsList.AddTail(new SelectableCar(0xe1075862, false)); + FilteredCarsList.AddTail(new SelectableCar(0x02d642b8, false)); + FilteredCarsList.AddTail(new SelectableCar(0x03d3401a, false)); + FilteredCarsList.AddTail(new SelectableCar(0x03d8a6d1, false)); + FilteredCarsList.AddTail(new SelectableCar(0x54653c71, false)); + FilteredCarsList.AddTail(new SelectableCar(0xe115ead0, false)); + FilteredCarsList.AddTail(new SelectableCar(0x54655133, false)); + FilteredCarsList.AddTail(new SelectableCar(0x582f21d9, false)); + FilteredCarsList.AddTail(new SelectableCar(0x363a1fea, false)); + } + SetupForPlayer(iPlayerNum); +} + +void UIQRCarSelect::RefreshCarList() { + ClearCarList(); + if ((filter & 8) != 0) { + RefreshBonusCarList(); + return; + } + FEPlayerCarDB *carDB = FEDatabase->GetPlayerCarStable(iPlayerNum); + unsigned int mode = FEDatabase->GetGameMode(); + unsigned int unlockFilter; + if ((mode & 1) == 0) { + if ((mode & 4) == 0 && ((mode & 8) != 0 || (mode & 0x40) != 0)) { + unlockFilter = 7; + } else { + unlockFilter = 1; + } + } else { + unlockFilter = 2; + } + int i = 0; + do { + FECarRecord *car = carDB->GetCarByIndex(i); + if (car->Handle != 0xFFFFFFFF && car->MatchesFilter(filter)) { + bool unlocked = UnlockSystem::IsCarUnlocked(static_cast(unlockFilter), car->Handle, iPlayerNum); + if (!GetMikeMannBuild() || IsValidMikeMannCar(car, filter)) { + SelectableCar *newCar = new SelectableCar(car->Handle, !unlocked); + FilteredCarsList.AddTail(newCar); + } + } + i++; + } while (i < 200); + unsigned short f = static_cast(filter); + if (f == 1 || f == 2 || f == 8 || f == 0x10) { + FilteredCarsList.Sort(SortCarsByUnlock); + } + SetupForPlayer(iPlayerNum); +} + +void UIQRCarSelect::ClearCarList() { + FilteredCarsList.DeleteAllElements(); +} + +void UIQRCarSelect::ScrollCars(eScrollDir dir) { + SelectableCar *cur = pSelectedCar; + if (!cur) return; + SelectableCar *newCar = static_cast(cur->GetPrev()); + if (newCar == FilteredCarsList.EndOfList()) { + newCar = FilteredCarsList.GetTail(); + } + if (dir == eSD_PREV) { + } else if (dir == eSD_NEXT) { + newCar = static_cast(cur->GetNext()); + if (newCar == FilteredCarsList.EndOfList()) { + newCar = FilteredCarsList.GetHead(); + } + } + if (newCar != cur) { + SetSelectedCar(newCar, iPlayerNum); + RefreshHeader(); + } +} + +void UIQRCarSelect::ScrollLists(eScrollDir dir) { + if (dir == eSD_NEXT) { + filter++; + if (filter >= NUM_LISTS) filter = 0; + } else { + filter--; + if (filter < 0) filter = NUM_LISTS - 1; + } + RefreshCarList(); + RefreshHeader(); +} + +void UIQRCarSelect::OnlineActOnSelect() { + unsigned int handle = pSelectedCar->mHandle; + FEDatabase->GetQuickRaceSettings(static_cast(1))->SetSelectedCar(handle, 0); + FEDatabase->GetQuickRaceSettings(static_cast(0))->SetSelectedCar(handle, 0); + FEDatabase->GetQuickRaceSettings(static_cast(2))->SetSelectedCar(handle, 0); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.hpp index 6c6b1a321..ad0fea98b 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRCarSelect.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRCARSELECT_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRCARSELECT_H @@ -5,8 +6,20 @@ #pragma once #endif -#include "Speed/Indep/Src/FEng/FEObject.h" -#include "Speed/Indep/Src/Frontend/Database/VehicleDB.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/Slider.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/customize/CustomizeTypes.hpp" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" + +#include + +struct FEImage; +struct FEString; +struct FECarRecord; +struct FECareerRecord; +struct SelectableCar; // total size: 0x1C class QRCarSelectBustedManager { @@ -17,7 +30,9 @@ class QRCarSelectBustedManager { BUSTED_ANIM_SHOW_IMPOUNDED = 2, }; - static void TextureLoadedCallbackAccessor(unsigned int this_screen) {} + static void TextureLoadedCallbackAccessor(unsigned int this_screen) { + reinterpret_cast(this_screen)->TextureLoadedCallback(); + } static void SetPlayerBusted() { bPlayerJustGotBusted = true; @@ -67,4 +82,66 @@ class QRCarSelectBustedManager { bool bWantsImpound; // offset 0x14, size 0x1 }; +// total size: 0x1B8 +struct UIQRCarSelect : public MenuScreen { + enum CarLists { + LIST_STOCK = 0, + LIST_CAREER = 1, + LIST_QUICK_RACE = 2, + LIST_BONUS = 3, + LIST_PRESET = 4, + LIST_DEBUG = 5, + NUM_LISTS = 6, + }; + + UIQRCarSelect(ScreenConstructorData *sd); + ~UIQRCarSelect() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + + bool IsCarImpounded(unsigned int handle); + void CommitChangeStartRace(bool allowError); + void Setup(); + void InitStatsSliders(); + void UpdateSliders(); + int GetFilterType(); + void SetupForPlayer(int player); + int GetBonusUnlockText(FECarRecord *fe_car); + int GetBonusUnlockBinNumber(FECarRecord *fe_car); + void RefreshHeader(); + void ChooseTransmission(); + FECarRecord *GetSelectedCarRecord(); + void SetSelectedCar(SelectableCar *newCar, int player_num); + void RefreshBonusCarList(); + void RefreshCarList(); + void ClearCarList(); + void ScrollCars(eScrollDir dir); + void ScrollLists(eScrollDir dir); + void OnlineActOnSelect(); + + static unsigned int ForceCar; // size: 0x4 + + bTList FilteredCarsList; // offset 0x2C, size 0x8 + SelectableCar *pSelectedCar; // offset 0x34, size 0x4 + Timer tLastEventTimer; // offset 0x38, size 0x4 + FEImage *pManuLogo; // offset 0x3C, size 0x4 + FEImage *pCarBadge; // offset 0x40, size 0x4 + FEString *pCarName; // offset 0x44, size 0x4 + FEString *pCarNameShadow; // offset 0x48, size 0x4 + FEString *pFilter; // offset 0x4C, size 0x4 + unsigned int ListHandles[6]; // offset 0x50, size 0x18 + unsigned int originalCar; // offset 0x68, size 0x4 + QRCarSelectBustedManager TheBustedManager; // offset 0x6C, size 0x1C + CustomizeMeter TheHeatMeter; // offset 0x88, size 0x50 + TwoStageSlider AccelerationSlider; // offset 0xD8, size 0x44 + TwoStageSlider TopSpeedSlider; // offset 0x11C, size 0x44 + TwoStageSlider HandlingSlider; // offset 0x160, size 0x44 + bool bLoadingBarActive; // offset 0x1A4, size 0x1 + bool bShowcaseMode; // offset 0x1A8, size 0x1 + int iPlayerNum; // offset 0x1AC, size 0x4 + int filter; // offset 0x1B0, size 0x4 + unsigned int iPrevButtonMsg; // offset 0x1B4, size 0x4 +}; + #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.cpp index e69de29bb..5ecd24ac1 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.cpp @@ -0,0 +1,266 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/Careers/UnlockSystem.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" + +extern GRaceParameters *theChallengeRace; +extern int GetMikeMannBuild(); +extern int bStrCmp(const char *, const char *); +extern int bStrICmp(const char *, const char *); +extern unsigned int FEngHashString(const char *, ...); +extern void FEAnyTutorialScreen_LaunchMovie(const char *movie, const char *pkg); + +class RaceStarter { +public: + static void StartRace(); +}; +extern const char *gTUTORIAL_MOVIE_TOLLBOOTH; + +void ChallengeDatum::NotificationMessage(unsigned long msg, FEObject *pObj, unsigned long param1, unsigned long param2) { + if (msg != 0x0C407210) + return; + if (!IsLocked()) { + theChallengeRace = race; + } else { + theChallengeRace = nullptr; + } +} + +UIQRChallengeSeries::UIQRChallengeSeries(ScreenConstructorData *sd) + : ArrayScrollerMenu(sd, 4, 3, true) // + , prev_race_hash(0) // + , pMovieName(0) +{ + theChallengeRace = nullptr; + int numSlots = GetWidth() * GetHeight(); + for (int i = 0; i < numSlots; i++) { + unsigned int hash = FEngHashString("TRACK_IMAGE_%d", i + 1); + FEImage *img = FEngFindImage(GetPackageName(), hash); + if (img) { + ImageArraySlot *slot = new ImageArraySlot(img); + AddSlot(slot); + } + } + TrackMap = (FEMultiImage *)FEngFindObject(GetPackageName(), FEngHashString("TRACK_MAP")); + Setup(); +} + +UIQRChallengeSeries::~UIQRChallengeSeries() { +} + +eMenuSoundTriggers UIQRChallengeSeries::NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) { + ArrayScrollerMenu::NotifySoundMessage(msg, maybe); + if (msg == 0x480c9a58) { + return static_cast(5); + } + return maybe; +} + +void UIQRChallengeSeries::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + ArrayScrollerMenu::NotificationMessage(msg, obj, param1, param2); + switch (msg) { + case 0xc98356ba: + TrackMapStreamer.UpdateAnimation(); + break; + case 0xc407210: + if (!theChallengeRace) { + g_pEAXSound->PlayUISoundFX(static_cast(7)); + return; + } + DialogInterface::ShowTwoButtons(GetPackageName(), "", static_cast(1), + 0x70e01038, 0x417b25e4, 0xd05fc3a3, 0x34dc1bcf, 0x34dc1bcf, static_cast(1), 0x77cf03c5); + break; + case 0xc519bfc3: + if (static_cast(currentDatum)->race->GetChallengeType() != 0) { + return; + } + FEngSetScript(GetPackageName(), 0x99344537, 0x16a259, true); + FEAnyTutorialScreen_LaunchMovie(gTUTORIAL_MOVIE_TOLLBOOTH, GetPackageName()); + break; + case 0x1a2826e1: + FEDatabase->GetPlayerSettings(0)->Transmission = 0; + goto start_race; + case 0x5f5e3886: + FEDatabase->GetPlayerSettings(0)->Transmission = 1; + goto start_race; + case 0xd05fc3a3: { + signed char port = static_cast(FEngMapJoyParamToJoyport(param1)); + FEDatabase->SetPlayersJoystickPort(0, port); + if (FEDatabase->GetPlayerSettings(0)->TransmissionPromptOn != 0) { + ChooseTransmission(); + return; + } + } +start_race: + { + GRaceCustom *race = GRaceDatabase::Get().AllocCustomRace(theChallengeRace); + GRaceDatabase::Get().SetStartupRace(race, kRaceContext_QuickRace); + GRaceDatabase::Get().FreeCustomRace(race); + RaceStarter::StartRace(); + } + break; + case 0x911ab364: + cFEng::Get()->QueuePackageSwitch("FeQrPkg", 0, 0, false); + break; + case 0xc3960eb9: + FEngSetScript(GetPackageName(), 0x99344537, 0x1744b3, true); + break; + } +} + +void UIQRChallengeSeries::ChooseTransmission() { + DialogInterface::ShowTwoButtons(GetPackageName(), "", static_cast(3), + 0x317d3005, 0x8cd532a0, 0x5f5e3886, 0x1a2826e1, 0x34dc1bcf, + (eDialogFirstButtons)(FEDatabase->GetPlayerSettings(0)->Transmission == 0), 0x6f5401d1); +} + +void UIQRChallengeSeries::RefreshHeader() { + ArrayScrollerMenu::RefreshHeader(); + if (!currentDatum) return; + + int pos = data.TraversebList(currentDatum); + FEPrintf(GetPackageName(), 0x5a856a34, "%d", pos); + FEPrintf(GetPackageName(), 0x2d4d22c8, "%d", GetNumDatum()); + + ChallengeDatum *cd = static_cast(currentDatum); + GRaceParameters *race = cd->race; + if (!race || prev_race_hash == race->GetEventHash()) return; + + prev_race_hash = race->GetEventHash(); + FEPrintf(GetPackageName(), 0x13c45e, "%.0f", race->GetCashValue()); + + const char *unit; + float conv; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + unit = GetLocalizedString(0x8569a26a); + conv = 0.001f; + } else { + unit = GetLocalizedString(0x867dcfd9); + conv = 0.000621371f; + } + float length = race->GetRaceLengthMeters() * conv; + int laps = race->GetNumLaps(); + FEPrintf(GetPackageName(), 0x80c9daa, "%$0.1f %s - %d laps", length, unit, laps); + + FEngSetInvisible(FEngFindObject(GetPackageName(), 0xbbf970cd)); + int challengeType = race->GetChallengeType(); + unsigned int iconHash; + if (challengeType == 0) { + iconHash = 0x65818ee8; + cFEng::Get()->QueuePackageMessage(0xb295482e, GetPackageName(), nullptr); + } else { + iconHash = FEDatabase->GetMilestoneIconHash(challengeType, true); + cFEng::Get()->QueuePackageMessage(0xf7b54c7, GetPackageName(), nullptr); + } + FEngSetTextureHash(FEngFindImage(GetPackageName(), 0xa018de49), iconHash); + + float goal = static_cast(race->GetChallengeGoal()); + if (FEDatabase->IsMilestoneTimeFormat(race->GetChallengeType())) { + goal *= (1.0f / 60.0f); + } + char buf[32]; + bSNPrintf(buf, 32, "%$0.0f", goal); + + const char *pkg = GetPackageName(); + cFrontendDatabase *db = FEDatabase; + unsigned int tag = race->GetLocalizationTag(); + unsigned int descHash = db->GetChallengeDescHash(tag); + const char *desc = GetLocalizedString(descHash); + FEPrintf(pkg, 0x7b230d64, desc, buf, buf); + + if (static_cast(currentDatum)->IsLocked()) { + cFEng::Get()->QueuePackageMessage(0xc5dd9d68, GetPackageName(), nullptr); + int lockedPos = data.TraversebList(currentDatum); + int index = lockedPos - 1; + int page = (lockedPos / 5) * 5; + if (lockedPos < 61) { + if (static_cast(lockedPos - page - 1) <= 1) { + FEPrintf(GetPackageName(), 0x68215623, GetLocalizedString(0xced8931e), page); + } else if (static_cast(lockedPos - page - 3) <= 1) { + FEPrintf(GetPackageName(), 0x68215623, GetLocalizedString(0xced8931d), page + 1, page + 2); + } else { + FEPrintf(GetPackageName(), 0x68215623, GetLocalizedString(0xced8931d), page - 2, page - 1); + } + } else { + FEPrintf(GetPackageName(), 0x68215623, GetLocalizedString(0xced8931e), index); + } + } else { + cFEng::Get()->QueuePackageMessage(0x38091fa1, GetPackageName(), nullptr); + } + + for (int i = 0; i < GetNumSlots(); i++) { + ChallengeDatum *datum = static_cast(GetDatumAt(i + GetStartDatumNum())); + unsigned int check_hash = FEngHashString("CHECK_%d", i + 1); + if (!datum) { + FEngSetScript(GetPackageName(), check_hash, 0x16a259, true); + } else if (datum->IsLocked()) { + FEngSetScript(GetPackageName(), check_hash, 0x5079c8f8, true); + FEngSetTextureHash(FEngFindImage(GetPackageName(), check_hash), 0x18ed48); + } else if (datum->IsChecked()) { + FEngSetScript(GetPackageName(), check_hash, 0x5079c8f8, true); + FEngSetTextureHash(FEngFindImage(GetPackageName(), check_hash), 0x28feadd); + } else { + FEngSetScript(GetPackageName(), check_hash, 0x16a259, true); + } + } + TrackMapStreamer.Init(race, TrackMap, 0, 0); +} + +void UIQRChallengeSeries::AddRace(GRaceParameters *race) { + unsigned int iconHash; + int challengeType = race->GetChallengeType(); + if (challengeType == 0) { + iconHash = 0x65818ee8; + } else { + iconHash = FEDatabase->GetMilestoneIconHash(challengeType, true); + } + unsigned int tag = race->GetLocalizationTag(); + unsigned int headerHash = FEDatabase->GetChallengeHeaderHash(tag); + ChallengeDatum *datum = new ChallengeDatum(iconHash, headerHash, race); + AddDatum(datum); +} + +bool UIQRChallengeSeries::IsRaceValidForMike(GRaceParameters *parms) { + int build = GetMikeMannBuild(); + if (build == 1) { + if (bStrCmp(parms->GetEventID(), "16_1_1_circuit") == 0) return true; + if (bStrCmp(parms->GetEventID(), "16_1_2_sprint") == 0) return true; + if (bStrCmp(parms->GetEventID(), "16_1_3_drag") == 0) return true; + if (bStrCmp(parms->GetEventID(), "16_1_4_lap_ko") == 0) return true; + return bStrCmp(parms->GetEventID(), "16_1_5_tollbooth") == 0; + } else if (GetMikeMannBuild() == 2) { + if (bStrICmp(parms->GetEventID(), "16_1_4_lap_ko") == 0) return true; + return bStrICmp(parms->GetEventID(), "16_1_6_speedtrap") == 0; + } + return true; +} + +void UIQRChallengeSeries::Setup() { + ClearData(); + GRaceBin *bin = GRaceDatabase::Get().GetBinNumber(0x13); + for (unsigned int i = 0; i < bin->GetWorldRaceCount(); i++) { + unsigned int raceHash = bin->GetWorldRaceHash(i); + GRaceParameters *race = GRaceDatabase::Get().GetRaceFromHash(raceHash); + if (GetMikeMannBuild() != 0) { + if (IsRaceValidForMike(race)) { + AddRace(race); + } + } else { + if (UnlockSystem::IsEventAvailable(race->GetEventHash())) { + bool unlocked = UnlockSystem::IsTrackUnlocked(UNLOCK_QUICK_RACE, race->GetEventHash(), 0); + AddRace(race); + GetDatumAt(GetNumDatum() - 1)->SetLocked(!unlocked); + if (GRaceDatabase::Get().IsQuickRaceComplete(raceHash)) { + GetDatumAt(GetNumDatum() - 1)->SetChecked(true); + } + } + } + } + SetDescLabel(0x790ce49); + SetInitialPosition(0); + RefreshHeader(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.hpp index 3ed2e06db..19715958e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRChallengeSeries.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRCHALLENGESERIES_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRCHALLENGESERIES_H @@ -5,6 +6,44 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feArrayScrollerMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp" +#include +struct GRaceParameters; +struct FEMultiImage; + +// total size: 0x28 +struct ChallengeDatum : public ArrayDatum { + ChallengeDatum(unsigned int hash, unsigned int desc, GRaceParameters *race) + : ArrayDatum(hash, desc) // + , race(race) {} + + ~ChallengeDatum() override {} + + void NotificationMessage(unsigned long msg, FEObject *pObj, unsigned long param1, unsigned long param2) override; + + GRaceParameters *race; // offset 0x24, size 0x4 +}; + +// total size: 0x1D0 +struct UIQRChallengeSeries : public ArrayScrollerMenu { + UIQRChallengeSeries(ScreenConstructorData *sd); + ~UIQRChallengeSeries() override; + + eMenuSoundTriggers NotifySoundMessage(unsigned long msg, eMenuSoundTriggers maybe) override; + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + void ChooseTransmission(); + void RefreshHeader() override; + void AddRace(GRaceParameters *race); + bool IsRaceValidForMike(GRaceParameters *parms); + void Setup(); + + unsigned int prev_race_hash; // offset 0xE8, size 0x4 + FEMultiImage *TrackMap; // offset 0xEC, size 0x4 + UITrackMapStreamer TrackMapStreamer; // offset 0xF0, size 0xDC + char *pMovieName; // offset 0x1CC, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.cpp index e69de29bb..8d0d1de68 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.cpp @@ -0,0 +1,109 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" + +extern int QRMode; +extern int GetMikeMannBuild(); +extern int FEngGetLastButton(const char *pkg_name); +void FEngSetLanguageHash(const char *pkg_name, unsigned int obj_hash, unsigned int lang_hash); +// _SetQRMode defined in uiQRModeSelect.cpp +static void _SetQRMode(int mode); + +struct QuickPlay : public IconOption { + QuickPlay(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override { + if (data == 0xc407210) { + _SetQRMode(0); + } + } +}; + +struct CustomRace : public IconOption { + CustomRace(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override { + if (data == 0xc407210) { + _SetQRMode(1); + } + } +}; + +struct SplitScreenOption : public IconOption { + SplitScreenOption(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override { + if (data == 0xc407210) { + _SetQRMode(2); + } + } +}; + +UIQRMainMenu::~UIQRMainMenu() {} + +UIQRMainMenu::UIQRMainMenu(ScreenConstructorData *sd) : IconScrollerMenu(sd) { + Setup(); + RefreshHeader(); +} + +void UIQRMainMenu::RefreshHeader() { + IconScrollerMenu::RefreshHeader(); + FEngSetLanguageHash(PackageFilename, 0x3c458c1, 0xcdf0cc03); + FEngSetLanguageHash(PackageFilename, 0xb5c74226, 0xcdf0cc03); +} + +void UIQRMainMenu::Setup() { + if (!GetMikeMannBuild()) { + IconOption *qp = new QuickPlay(0xe6313967, 0xb5e8f82f, 0); + AddOption(qp); + IconOption *cr = new CustomRace(0x2a49b5e2, 0x25bbd4c3, 0); + AddOption(cr); + IconOption *ss = new SplitScreenOption(0xf365b5f5, 0x841d518a, 0); + AddOption(ss); + } else { + IconOption *cr = new CustomRace(0x2a49b5e2, 0x25bbd4c3, 0); + AddOption(cr); + } + int lastBtn = FEngGetLastButton(PackageFilename); + if (bFadeInIconsImmediately) { + Options.bFadingIn = true; + Options.bFadingOut = false; + Options.bDelayUpdate = false; + Options.fCurFadeTime = 0.0f; + } + Options.SetInitialPos(lastBtn); + FEDatabase->RefreshCurrentRide(); +} + +void UIQRMainMenu::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + if (msg == 0xe1fde1d1) { + switch (PrevButtonMessage) { + case 0xc407210: { + FEDatabase->iNumPlayers = 1; + switch (QRMode) { + case 1: + FEDatabase->SetGameMode(static_cast(FEDatabase->GetGameMode() | 0x400)); + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + break; + case 0: + cFEng::Get()->QueuePackageSwitch("Quick_Race_Brief.fng", 0, 0, false); + break; + case 2: + FEDatabase->iNumPlayers = 2; + FEDatabase->SetGameMode(static_cast(FEDatabase->GetGameMode() | 0x400)); + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + break; + } + break; + } + case 0x911ab364: + cFEng::Get()->QueuePackageSwitch("MainMenu.fng", 0, 0, false); + break; + } + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.hpp index 1a1380311..1db852e4b 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRMainMenu.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRMAINMENU_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRMAINMENU_H @@ -5,6 +6,19 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" +#include + +// total size: 0x16C +struct UIQRMainMenu : public IconScrollerMenu { + UIQRMainMenu(ScreenConstructorData *sd); + ~UIQRMainMenu() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + void RefreshHeader() override; + void Setup() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.cpp index e69de29bb..2bc3c4dd5 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.cpp @@ -0,0 +1,125 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEIconScrollerMenu.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" + +extern int QRMode; +extern int GetMikeMannBuild(); +extern int FEngGetLastButton(const char *pkg_name); +void FEngSetLanguageHash(const char *pkg_name, unsigned int obj_hash, unsigned int lang_hash); +extern const char *gOnlineMainMenu; + +static void _SetQRMode(int mode) { + QRMode = mode; +} + +struct MSOption : public IconOption { + MSOption(unsigned int tex_hash, unsigned int name_hash, GRace::Type race_type) + : IconOption(tex_hash, name_hash, 0) // + , raceType(race_type) {} + + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override; + + GRace::Type raceType; // offset 0x5C, size 0x4 +}; + +void MSOption::React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) { + if (data == 0xc407210) { + FEDatabase->RaceMode = raceType; + } +} + +UIQRModeSelect::~UIQRModeSelect() {} + +UIQRModeSelect::UIQRModeSelect(ScreenConstructorData *sd) : IconScrollerMenu(sd) { + Setup(); + RefreshHeader(); +} + +void UIQRModeSelect::RefreshHeader() { + IconScrollerMenu::RefreshHeader(); + unsigned int hash = 0x1f203817; + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + hash = 0x6703b807; + } else { + bool isSplitQR = false; + if (FEDatabase->IsQuickRaceMode()) { + isSplitQR = FEDatabase->iNumPlayers == 2; + } + if (isSplitQR) { + hash = 0x43c825ed; + } + } + FEngSetLanguageHash(PackageFilename, 0xb71b576d, hash); +} + +void UIQRModeSelect::Setup() { + if (GetMikeMannBuild()) { + MSOption *opt; + opt = new MSOption(0xe9638d3e, 0x34fa2c1, GRace::kRaceType_Circuit); + AddOption(opt); + if (GetMikeMannBuild() == 1) { + opt = new MSOption(0x2521e5eb, 0xb94fd70e, GRace::kRaceType_P2P); + AddOption(opt); + opt = new MSOption(0xaaab31e9, 0x6f547e4c, GRace::kRaceType_Drag); + AddOption(opt); + opt = new MSOption(0x1a091045, 0xa15e4505, GRace::kRaceType_Tollbooth); + AddOption(opt); + } + } else { + MSOption *opt; + opt = new MSOption(0xe9638d3e, 0x34fa2c1, GRace::kRaceType_Circuit); + AddOption(opt); + opt = new MSOption(0x2521e5eb, 0xb94fd70e, GRace::kRaceType_P2P); + AddOption(opt); + opt = new MSOption(0xaaab31e9, 0x6f547e4c, GRace::kRaceType_Drag); + AddOption(opt); + if (!FEDatabase->IsOnlineMode() && !FEDatabase->IsLANMode()) { + bool isSplitQR = false; + if (FEDatabase->IsQuickRaceMode()) { + isSplitQR = FEDatabase->iNumPlayers == 2; + } + if (!isSplitQR) { + opt = new MSOption(0x3a015595, 0x4930f5fc, GRace::kRaceType_Knockout); + AddOption(opt); + } + opt = new MSOption(0x66c9a7b6, 0xee1edc76, GRace::kRaceType_SpeedTrap); + AddOption(opt); + } + } + int lastBtn = FEngGetLastButton(PackageFilename); + if (bFadeInIconsImmediately) { + Options.bFadingIn = true; + Options.bFadingOut = false; + Options.bDelayUpdate = false; + Options.fCurFadeTime = 0.0f; + } + Options.SetInitialPos(lastBtn); + cFEng::Get()->QueuePackageMessage(0x21828323, PackageFilename, nullptr); +} + +void UIQRModeSelect::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0x911ab364: + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + cFEng::Get()->QueuePackageMessage(0x587c018b, PackageFilename, nullptr); + } + break; + case 0xe1fde1d1: + switch (PrevButtonMessage) { + case 0xc407210: + cFEng::Get()->QueuePackageSwitch("Track_Select.fng", 0, 0, false); + break; + case 0x911ab364: + FEDatabase->ClearGameMode(static_cast(0x400)); + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + cFEng::Get()->QueuePackageSwitch(gOnlineMainMenu, 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } + break; + } + break; + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.hpp index 3d171e67c..30af556e5 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRModeSelect.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRMODESELECT_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRMODESELECT_H @@ -5,6 +6,19 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" +#include + +// total size: 0x16C +struct UIQRModeSelect : public IconScrollerMenu { + UIQRModeSelect(ScreenConstructorData *sd); + ~UIQRModeSelect() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + void RefreshHeader() override; + void Setup() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.cpp index e69de29bb..72fc5b849 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.cpp @@ -0,0 +1,77 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Frontend/FEManager.hpp" + +extern int FEngMapJoyParamToJoyport(int param); +extern unsigned int FEngMapJoyportToJoyParam(int port); + +uiQRPressStart::~uiQRPressStart() {} + +uiQRPressStart::uiQRPressStart(ScreenConstructorData *sd) : MenuScreen(sd) { + iPlayerNum = sd->Arg; + param = 0; + Setup(); +} + +void uiQRPressStart::Setup() { + const char *str = GetLocalizedString(0xcf538e1c); + FEPrintf(PackageFilename, 0xb244cf71, str, iPlayerNum + 1); + str = GetLocalizedString(0xa065effe); + FEPrintf(PackageFilename, 0x545570c6, str); +} + +void uiQRPressStart::NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) { + if (msg == 0xe1fde1d1) { + goto queue_car_select; + } + if (msg > 0xe1fde1d1) { + goto handle_controller_change; + } + if (msg == 0x911ab364) { + goto handle_back; + } + return; +handle_controller_change: { + if (msg != 0xebfcda65) return; + int joyport = FEngMapJoyParamToJoyport(param1); + if (iPlayerNum != 1 || joyport != FEDatabase->GetPlayersJoystickPort(0)) { + FEDatabase->SetPlayersJoystickPort(iPlayerNum, static_cast(joyport)); + this->param = param1; + if ((static_cast(this->param) & 1) != 0) { + this->param = 1; + } + if ((static_cast(this->param) & 2) != 0) { + this->param = 2; + } + if ((static_cast(this->param) & 4) != 0) { + this->param = 4; + } + if ((static_cast(this->param) & 8) != 0) { + this->param = 8; + } + FEManager::Get()->AllowControllerError(true); + cFEng::Get()->QueuePackageMessage(0x587c018b, PackageFilename, nullptr); + } + return; + } +handle_back: { + if (iPlayerNum == 1) { + unsigned int joyParam = FEngMapJoyportToJoyParam(static_cast(FEDatabase->GetPlayersJoystickPort(0))); + cFEng::Get()->QueuePackageSwitch("Car_Select.fng", 0, joyParam, false); + } else { + bool isSplitQR = false; + if ((FEDatabase->GetGameMode() & 4) != 0) { + isSplitQR = FEDatabase->iNumPlayers == 2; + } + if (isSplitQR && (FEDatabase->RaceMode == GRace::kRaceType_Drag || FEDatabase->RaceMode == GRace::kRaceType_P2P || FEDatabase->RaceMode == GRace::kRaceType_SpeedTrap)) { + cFEng::Get()->QueuePackageSwitch("Track_Select.fng", 0, 0, false); + } else { + cFEng::Get()->QueuePackageSwitch("Track_Options.fng", 0, 0, false); + } + } + return; + } +queue_car_select: + cFEng::Get()->QueuePackageSwitch("Car_Select.fng", iPlayerNum, param, false); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.hpp new file mode 100644 index 000000000..0bff29b8e --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRPressStart.hpp @@ -0,0 +1,25 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRPRESSSTART_H +#define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRPRESSSTART_H +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" + +#include + +// total size: 0x34 +struct uiQRPressStart : public MenuScreen { + uiQRPressStart(ScreenConstructorData *sd); + ~uiQRPressStart() override; + + void NotificationMessage(unsigned long msg, FEObject *obj, unsigned long param1, unsigned long param2) override; + + void Setup(); + + int iPlayerNum; // offset 0x2C, size 0x4 + int param; // offset 0x30, size 0x4 +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.cpp index e69de29bb..46fcf7a42 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.cpp @@ -0,0 +1,570 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/feWidget.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/DialogInterface.hpp" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" +#include "Speed/Indep/Src/FEng/cFEng.h" + +#include + +class GRaceCustom; +extern void GRaceCustom_SetCopsEnabled(GRaceCustom *self, bool enabled); +extern void GRaceCustom_SetNumOpponents(GRaceCustom *self, int num); + +extern cFrontendDatabase *FEDatabase; +extern GRaceDatabase *GRaceDatabase_mObj; +extern cFEng *cFEng_mInstance; + +extern unsigned long FEHashUpper(const char *str); +extern void FEngSetLanguageHash(const char *pkg, unsigned int obj_hash, unsigned int lang_hash); +extern void FEngSetLanguageHash(FEString *text, unsigned int hash); +extern const char *GetLocalizedString(unsigned int hash); + +struct NumOpponents : public FEToggleWidget { + NumOpponents(bool enabled) : FEToggleWidget(enabled) {} + ~NumOpponents() override; + void Act(const char *parent_pkg, unsigned int data) override; + void Draw() override; +}; + +struct AISkill : public FEToggleWidget { + AISkill(bool enabled) : FEToggleWidget(enabled) {} + ~AISkill() override; + void Act(const char *parent_pkg, unsigned int data) override; + void Draw() override; +}; + +struct CatchUp : public FEToggleWidget { + CatchUp(bool enabled) : FEToggleWidget(enabled) {} + ~CatchUp() override; + void Act(const char *parent_pkg, unsigned int data) override; + void Draw() override; +}; + +struct TrafficLevel : public FEToggleWidget { + TrafficLevel(bool enabled) : FEToggleWidget(enabled) {} + ~TrafficLevel() override; + void Act(const char *parent_pkg, unsigned int data) override; + void Draw() override; +}; + +struct NumLaps : public FEToggleWidget { + NumLaps(bool enabled) : FEToggleWidget(enabled) {} + ~NumLaps() override; + void Act(const char *parent_pkg, unsigned int data) override; + void Draw() override; +}; + +struct TrackDirection : public FEToggleWidget { + TrackDirection(bool enabled) : FEToggleWidget(enabled) {} + ~TrackDirection() override; + void Act(const char *parent_pkg, unsigned int data) override; + void Draw() override; +}; + +UIQRTrackOptions::UIQRTrackOptions(ScreenConstructorData *sd) : UIWidgetMenu(sd) { + m_code = 0; + msgHandle = 0; + m_boDisconnectPercAvail = false; + race = GRaceDatabase_mObj->GetRaceFromHash(FEDatabase->GetQuickRaceSettings(FEDatabase->RaceMode)->EventHash); + iMaxWidgetsOnScreen = 9; + Setup(); +} + +void UIQRTrackOptions::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + UIWidgetMenu::NotificationMessage(msg, pobj, param1, param2); + switch (msg) { + case 0x406415e3: + if (!(FEDatabase->IsOnlineMode()) && !(FEDatabase->IsLANMode())) { + GRaceCustom *custom = GRaceDatabase_mObj->AllocCustomRace(race); + GRaceCustom_SetCopsEnabled(custom, false); + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(race->GetRaceType()); + FEDatabase->FillCustomRace(custom, settings); + bool isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (isSplitScreen) { + GRaceCustom_SetNumOpponents(custom, 1); + } + GRaceDatabase_mObj->SetStartupRace(custom, kRaceContext_QuickRace); + GRaceDatabase_mObj->FreeCustomRace(custom); + } + { + bool isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (isSplitScreen) { + cFEng_mInstance->QueuePackageSwitch("PressStart.fng", 0, 0, false); + } else { + cFEng_mInstance->QueuePackageSwitch("Car_Select.fng", 0, 0, false); + } + } + break; + case 0x911ab364: + cFEng_mInstance->QueuePackageSwitch("Track_Select.fng", 0, 0, false); + break; + case 0xc519bfc4: { + const char *locStr = GetLocalizedString(0x8aef5ae8); + DialogInterface::ShowTwoButtons(GetPackageName(), "", dialog_alert, 0x70e01038, 0x417b25e4, 0xd05fc3a3, 0x34dc1bcf, 0x34dc1bcf, static_cast(1), locStr); + break; + } + case 0xd05fc3a3: + FEDatabase->DefaultRaceSettings(); + { + int count = Options.TraversebList(nullptr); + for (int i = 0; i < count; i++) { + FEWidget *w = static_cast(Options.GetNode(i)); + w->Draw(); + } + } + break; + case 0x34dc1bcf: + break; + } +} + +void UIQRTrackOptions::Setup() { + GRace::Type raceType = FEDatabase->RaceMode; + if (raceType == GRace::kRaceType_Drag) { + SetupDrag(); + } else if (raceType < GRace::kRaceType_Drag) { + if (raceType == GRace::kRaceType_P2P) { + SetupSprint(); + } else if (raceType == GRace::kRaceType_Circuit) { + SetupCircuit(); + } else { + SetupCircuit(); + } + } else if (raceType == GRace::kRaceType_Tollbooth) { + SetupTollbooth(); + } else if (raceType == GRace::kRaceType_Knockout) { + SetupKnockout(); + } else if (raceType == GRace::kRaceType_SpeedTrap) { + SetupSpeedTrap(); + } else { + SetupCircuit(); + } + SetInitialOption(0); + if ((FEDatabase->IsOnlineMode()) || (FEDatabase->IsLANMode())) { + FEngSetLanguageHash(GetPackageName(), 0x42adb44c, 0x7dadee33); + return; + } + unsigned int titleHash = 0; + GRace::Type type = race->GetRaceType(); + switch (type) { + case GRace::kRaceType_P2P: titleHash = 0xb80bfc8a; break; + case GRace::kRaceType_Circuit: titleHash = 0xa6ed015d; break; + case GRace::kRaceType_Drag: titleHash = 0xec86e188; break; + case GRace::kRaceType_Knockout: titleHash = 0xcc959b8; break; + case GRace::kRaceType_Tollbooth: titleHash = 0x141edfe1; break; + case GRace::kRaceType_SpeedTrap: titleHash = 0xf2745852; break; + default: break; + } + FEngSetLanguageHash(GetPackageName(), 0x42adb44c, titleHash); +} + +void UIQRTrackOptions::BoilerPlateOnline(const bool &boAddLaps) { +} + +void UIQRTrackOptions::SetupCircuit() { + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + NumLaps *nl = new NumLaps(true); + AddToggleOption(nl, true); + bool isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + TrafficLevel *tl = new TrafficLevel(true); + AddToggleOption(tl, true); + } + isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + NumOpponents *no = new NumOpponents(true); + AddToggleOption(no, true); + AISkill *ai = new AISkill(true); + AddToggleOption(ai, true); + CatchUp *cu = new CatchUp(true); + AddToggleOption(cu, true); + } +} + +void UIQRTrackOptions::SetupSprint() { + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + bool isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + TrafficLevel *tl = new TrafficLevel(true); + AddToggleOption(tl, true); + } + isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + NumOpponents *no = new NumOpponents(true); + AddToggleOption(no, true); + AISkill *ai = new AISkill(true); + AddToggleOption(ai, true); + CatchUp *cu = new CatchUp(true); + AddToggleOption(cu, true); + } +} + +void UIQRTrackOptions::SetupDrag() { + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + bool isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + NumOpponents *no = new NumOpponents(true); + AddToggleOption(no, true); + AISkill *ai = new AISkill(true); + AddToggleOption(ai, true); + CatchUp *cu = new CatchUp(true); + AddToggleOption(cu, true); + } +} + +void UIQRTrackOptions::SetupKnockout() { + if ((FEDatabase->GetGameMode() & 8) != 0 || (FEDatabase->GetGameMode() & 0x40) != 0) { + bool boAddLaps = false; + BoilerPlateOnline(boAddLaps); + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + NumLaps *nl = new NumLaps(true); + AddToggleOption(nl, true); + } else { + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + NumOpponents *no = new NumOpponents(true); + AddToggleOption(no, true); + NumLaps *nl = new NumLaps(true); + unsigned int idx = AddToggleOption(nl, true); + FEToggleWidget *tw = static_cast(GetWidget(idx)); + tw->SetDisableScript(FEHashUpper("KO_ROUNDS")); + tw = static_cast(GetWidget(idx)); + tw->Draw(); + bool isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + TrafficLevel *tl = new TrafficLevel(true); + AddToggleOption(tl, true); + } + AISkill *ai = new AISkill(true); + AddToggleOption(ai, true); + CatchUp *cu = new CatchUp(true); + AddToggleOption(cu, true); + } +} + +void UIQRTrackOptions::SetupSpeedTrap() { + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + bool boAddLaps = false; + BoilerPlateOnline(boAddLaps); + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + } else { + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + bool isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + TrafficLevel *tl = new TrafficLevel(true); + AddToggleOption(tl, true); + } + isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + NumOpponents *no = new NumOpponents(true); + AddToggleOption(no, true); + AISkill *ai = new AISkill(true); + AddToggleOption(ai, true); + CatchUp *cu = new CatchUp(true); + AddToggleOption(cu, true); + } + } +} + +void UIQRTrackOptions::SetupTollbooth() { + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + bool boAddLaps = false; + BoilerPlateOnline(boAddLaps); + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + } else { + if (race->GetCanBeReversed()) { + TrackDirection *td = new TrackDirection(true); + AddToggleOption(td, true); + } + bool isSplitScreen = false; + if (FEDatabase->IsSplitScreenMode()) { + isSplitScreen = FEDatabase->iNumPlayers == 2; + } + if (!isSplitScreen) { + TrafficLevel *tl = new TrafficLevel(true); + AddToggleOption(tl, true); + } + } +} + +// --- Widget Destructors --- + +NumOpponents::~NumOpponents() {} +AISkill::~AISkill() {} +CatchUp::~CatchUp() {} +TrafficLevel::~TrafficLevel() {} +NumLaps::~NumLaps() {} +TrackDirection::~TrackDirection() {} + +// --- SplitScreen --- + +struct SplitScreen : public IconOption { + SplitScreen(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~SplitScreen() override; + void React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) override; +}; + +SplitScreen::~SplitScreen() {} + +void SplitScreen::React(const char *pkg_name, unsigned int data, FEObject *obj, unsigned int param1, unsigned int param2) { + if (data == 0xc407210) { + _SetQRMode(2); + } +} + +// --- NumOpponents --- + +void NumOpponents::Act(const char *parent_pkg, unsigned int data) { + int numPlayers = FEDatabase->iNumPlayers; + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + int val = settings->NumOpponents; + if (data == 0x9120409e) { + val = val - 1; + if (val < 1) { + val = 4 - numPlayers; + } + } else if (data == 0xb5971bf1) { + val = val + 1; + if (val > 4 - numPlayers) { + val = 1; + } + } + settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->NumOpponents = static_cast(val); + if (FEDatabase->RaceMode == static_cast(3)) { + settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->NumLaps = static_cast(val); + cFEng_mInstance->QueueGameMessage(0x92b703b5, parent_pkg, 0xff); + } + bMovedLastUpdate = true; + BlinkArrows(data); + Draw(); +} + +void NumOpponents::Draw() { + FEngSetLanguageHash(pTitle, 0x3384a679); + FEString *data = pData; + const char *fmt = "%d"; + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + FEPrintf(data, fmt, settings->NumOpponents); +} + +// --- AISkill --- + +void AISkill::Act(const char *parent_pkg, unsigned int data) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + int val = settings->AISkill; + if (data == 0x9120409e) { + val = val - 1; + if (val < 0) { + val = 2; + } + } else if (data == 0xb5971bf1) { + val = val + 1; + if (val > 2) { + val = 0; + } + } + settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->AISkill = static_cast(val); + bMovedLastUpdate = true; + BlinkArrows(data); + Draw(); +} + +void AISkill::Draw() { + unsigned int hash = 0; + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + unsigned char skill = settings->AISkill; + switch (skill) { + case 0: + hash = 0x61973e01; + break; + case 1: + hash = 0x3747f6d0; + break; + case 2: + hash = 0x6198e2ee; + break; + } + FEngSetLanguageHash(pTitle, 0x4d156786); + FEngSetLanguageHash(pData, hash); +} + +// --- CatchUp --- + +void CatchUp::Act(const char *parent_pkg, unsigned int data) { + if (data == 0x9120409e || data == 0xb5971bf1) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + RaceSettings *settings2 = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->CatchUp = !settings2->CatchUp; + } + bMovedLastUpdate = true; + BlinkArrows(data); + Draw(); +} + +void CatchUp::Draw() { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + if (settings->CatchUp) { + FEngSetLanguageHash(pData, 0x417b2604); + } else { + FEngSetLanguageHash(pData, 0x70dfe5c2); + } + FEngSetLanguageHash(pTitle, 0x8b8e913a); +} + +// --- TrafficLevel --- + +void TrafficLevel::Act(const char *parent_pkg, unsigned int data) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + int val = settings->TrafficDensity; + if (data == 0x9120409e) { + val = val - 1; + if (val < 0) { + val = 3; + } + } else if (data == 0xb5971bf1) { + val = val + 1; + if (val > 3) { + val = 0; + } + } + settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->TrafficDensity = static_cast(val); + bMovedLastUpdate = true; + BlinkArrows(data); + Draw(); +} + +void TrafficLevel::Draw() { + unsigned int hash = 0; + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + unsigned char level = settings->TrafficDensity; + switch (level) { + case 0: + hash = 0x8cdc3937; + break; + case 1: + hash = 0x73c615a3; + break; + case 2: + hash = 0xa2cca838; + break; + case 3: + hash = 0x61d1c5a5; + break; + } + FEngSetLanguageHash(pData, hash); + FEngSetLanguageHash(pTitle, 0xeb9dfc09); +} + +// --- NumLaps --- + +void NumLaps::Act(const char *parent_pkg, unsigned int data) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + int val = settings->NumLaps; + if (data == 0x9120409e) { + val = val - 1; + if (val < 1) { + val = 8; + } + } else if (data == 0xb5971bf1) { + val = val + 1; + if (val > 8) { + val = 1; + } + } + settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->NumLaps = static_cast(val); + bMovedLastUpdate = true; + BlinkArrows(data); + Draw(); +} + +void NumLaps::Draw() { + FEString *data = pData; + const char *fmt = "%d"; + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + FEPrintf(data, fmt, settings->NumLaps); + FEngSetLanguageHash(pTitle, 0x48494e83); +} + +// --- TrackDirection --- + +void TrackDirection::Act(const char *parent_pkg, unsigned int data) { + if (data == 0x9120409e || data == 0xb5971bf1) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + RaceSettings *settings2 = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->TrackDirection = (settings2->TrackDirection == 0); + } + bMovedLastUpdate = true; + BlinkArrows(data); + Draw(); +} + +void TrackDirection::Draw() { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + if (settings->TrackDirection == 0) { + FEngSetLanguageHash(pData, 0xde6eff34); + } else { + FEngSetLanguageHash(pData, 0xa1cd823e); + } + FEngSetLanguageHash(pTitle, 0xa88ffeb4); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.hpp index ef19ea5fe..618d68d28 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackOptions.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRTRACKOPTIONS_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRTRACKOPTIONS_H @@ -5,6 +6,35 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/UIWidgetMenu.hpp" +#include + +struct GRaceParameters; + +// total size: 0x154 +struct UIQRTrackOptions : public UIWidgetMenu { + UIQRTrackOptions(ScreenConstructorData *sd); + ~UIQRTrackOptions() override {} + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + void Setup() override; + + void BoilerPlateOnline(const bool &boAddLaps); + void SetupCircuit(); + void SetupSprint(); + void SetupDrag(); + void SetupKnockout(); + void SetupSpeedTrap(); + void SetupTollbooth(); + + GRaceParameters *race; // offset 0x138, size 0x4 + unsigned int ConfirmPasswordId; // offset 0x13C, size 0x4 + unsigned int EnterPasswordId; // offset 0x140, size 0x4 + unsigned int PasswordProtectedId; // offset 0x144, size 0x4 + int m_code; // offset 0x148, size 0x4 + int msgHandle; // offset 0x14C, size 0x4 + bool m_boDisconnectPercAvail; // offset 0x150, size 0x1 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.cpp index e69de29bb..6db60ea1f 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.cpp @@ -0,0 +1,456 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Gameplay/GRaceDatabase.h" + +void FEngSetLanguageHash(const char *pkg_name, unsigned int obj_hash, unsigned int lang_hash); +extern int FEngGetLastButton(const char *pkg_name); +extern unsigned int FEngHashString(const char *format, ...); +extern FEObject *FEngFindObject(const char *pkg_name, unsigned int hash); +extern void FEngSetVisible(FEObject *obj); +extern void FEngSetInvisible(FEObject *obj); +extern void FEngSetButtonTexture(FEImage *img, unsigned int hash); +extern FEImage *FEngFindImage(const char *pkg_name, int hash); +extern void FEngSetTextureHash(FEImage *img, unsigned int hash); +extern int FEngSNPrintf(char *buf, int size, const char *fmt, ...); +extern const char *GetLocalizedString(unsigned int hash); +extern int FEPrintf(const char *pkg_name, int hash, const char *fmt, ...); +extern unsigned int CalcLanguageHash(const char *prefix, GRaceParameters *rp); +extern bool DoesStringExist(unsigned int hash); +extern unsigned long FEHashUpper(const char *str); +extern int GetMikeMannBuild(); +extern void StartRace(); +extern void FEngSetScript(const char *pkg_name, unsigned int obj_hash, unsigned int script_hash, bool play); + +extern void SetNumOpponents(void *custom, int num); +extern void SetCopsEnabled(void *custom, bool enabled); +extern const char *gOnlineMainMenu; +extern int bStrICmp(const char *, const char *); + +struct GRaceSaveInfo { + unsigned int mRaceHash; + unsigned int mFlags; + float mHighScores; + unsigned short mTopSpeed; + unsigned short mAverageSpeed; +}; + +inline void FEngSetVisible(const char *pkg_name, unsigned int obj_hash) { + FEngSetVisible(FEngFindObject(pkg_name, obj_hash)); +} + +inline void FEngSetInvisible(const char *pkg_name, unsigned int obj_hash) { + FEngSetInvisible(FEngFindObject(pkg_name, obj_hash)); +} + +UIQRTrackSelect::UIQRTrackSelect(ScreenConstructorData *sd) : MenuScreen(sd) { + TrackMapStreamer.Init(nullptr, nullptr, 0, 0); + Tracks.InitList(); + pCurrentTrack = nullptr; + pCurrentNode = nullptr; + Setup(); +} + +UIQRTrackSelect::~UIQRTrackSelect() {} + +void UIQRTrackSelect::Setup() { + if (cFEng::Get()->IsPackagePushed("UI_OLViewTrack.fng")) { + return; + } + GRace::Type raceMode = FEDatabase->RaceMode; + unsigned int hash; + switch (raceMode) { + case GRace::kRaceType_Circuit: + hash = 0x3de80a85; + break; + case GRace::kRaceType_Drag: + hash = 0x136c5c90; + break; + case GRace::kRaceType_Knockout: + hash = 0xd6d65640; + break; + case GRace::kRaceType_P2P: + hash = 0xc2d85652; + break; + case GRace::kRaceType_Tollbooth: + hash = 0xe3afadc9; + break; + case GRace::kRaceType_SpeedTrap: + hash = 0x3070453a; + break; + default: + hash = 0; + break; + } + FEngSetLanguageHash(PackageFilename, 0xb71b576d, hash); + const char *pkg = PackageFilename; + unsigned int objHash = FEngHashString("TRACK_MAP"); + FEObject *obj = FEngFindObject(pkg, objHash); + TrackMap = reinterpret_cast(obj); + BuildPresetTrackList(); + RefreshHeader(); +} + +void UIQRTrackSelect::SetSelectedTrack(GRaceParameters *track) { + if (track) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(track->GetRaceType()); + settings->EventHash = track->GetEventHash(); + } +} + +bool UIQRTrackSelect::IsRaceValidForMike(GRaceParameters *parms) { + static const char *ValidForMikeMann[] = { + "15.2.1", "14.2.1", "16.2.3", "15.1.1", "16.1.1", + "14.1.2", "5.1.1", "11.4.2", "7.4.2", "5.4.14.4.1", "10.7.1" + }; + static const char *goddamcrap[] = { + "16.1.1.r", "15.1.1" + }; + + int build = GetMikeMannBuild(); + if (build == 1) { + for (int i = 0; i < 11; i++) { + if (bStrICmp(parms->GetEventID(), ValidForMikeMann[i]) == 0) { + return true; + } + } + } else { + build = GetMikeMannBuild(); + if (build == 2) { + for (int i = 0; i < 2; i++) { + if (bStrICmp(parms->GetEventID(), goddamcrap[i]) == 0) { + return true; + } + } + } + } + return false; +} + +void UIQRTrackSelect::TryToAddTrack(GRaceParameters *parms, int unlock_filter, int bin_num) { + if (!UnlockSystem::IsEventAvailable(parms->GetEventHash())) { + return; + } + if (parms->GetNeverInQuickRace()) { + return; + } + if (parms->GetRaceType() != FEDatabase->RaceMode) { + return; + } + int isDDay = parms->GetIsDDayRace(); + if (isDDay) { + return; + } + if (GetMikeMannBuild()) { + if (!IsRaceValidForMike(parms)) { + return; + } + SelectableTrack *node = new SelectableTrack(parms, isDDay, bin_num); + Tracks.AddTail(node); + } else { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + unsigned char region = settings->RegionFilterBits; + if (parms->GetRegion() != region && region != kRaceRegion_NumRegions) { + return; + } + unsigned int eventHash = parms->GetEventHash(); + bool isUnlocked = UnlockSystem::IsTrackUnlocked(static_cast(unlock_filter), eventHash, 0); + SelectableTrack *node = new SelectableTrack(parms, !isUnlocked, bin_num); + Tracks.AddTail(node); + settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + unsigned int settingsHash = settings->EventHash; + if (parms->GetEventHash() == settingsHash) { + pCurrentNode = node; + } + } +} + +void UIQRTrackSelect::BuildPresetTrackList() { + // TODO: normalized DWARF still differs around DeleteAllElements/GetHead ownership. + Tracks.DeleteAllElements(); + int unlock_filter = 0; + if (FEDatabase->IsCareerMode()) { + unlock_filter = 2; + } else if (FEDatabase->IsQuickRaceMode()) { + unlock_filter = 1; + } else if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + unlock_filter = 4; + } + { + int bin_num = 0x15; + pCurrentNode = nullptr; + do { + GRaceBin *bin = GRaceDatabase::Get().GetBinNumber(bin_num); + for (unsigned int i = 0; i < bin->GetWorldRaceCount(); i++) { + unsigned int raceHash = bin->GetWorldRaceHash(i); + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromHash(raceHash); + TryToAddTrack(parms, unlock_filter, bin_num); + } + for (unsigned int i = 0; i < bin->GetBossRaceCount(); i++) { + unsigned int raceHash = bin->GetBossRaceHash(i); + GRaceParameters *parms = GRaceDatabase::Get().GetRaceFromHash(raceHash); + TryToAddTrack(parms, unlock_filter, bin_num); + } + if (bin_num == 0x15) { + bin_num = 0x10; + } + bin_num--; + } while (bin_num > 0); + } + if (!pCurrentNode) { + pCurrentTrack = nullptr; + if (Tracks.CountElements() > 0) { + pCurrentNode = Tracks.GetHead(); + } + } + if (pCurrentNode) { + pCurrentTrack = pCurrentNode->pRaceParams; + } + TrackMapStreamer.Init(pCurrentTrack, TrackMap, 0, 0); +} + +void UIQRTrackSelect::ScrollTracks(eScrollDir dir) { + int count = Tracks.CountElements(); + if (count < 1) { + return; + } + GRaceParameters *oldTrack = pCurrentTrack; + if (dir == eSD_PREV) { + SelectableTrack *prev = pCurrentNode->GetPrev(); + if (prev == Tracks.EndOfList()) { + prev = Tracks.GetTail(); + } + pCurrentNode = prev; + pCurrentTrack = prev->pRaceParams; + } else if (dir == eSD_NEXT) { + SelectableTrack *next = pCurrentNode->GetNext(); + if (next == Tracks.EndOfList()) { + next = Tracks.GetHead(); + } + pCurrentNode = next; + pCurrentTrack = next->pRaceParams; + } + if (oldTrack != pCurrentTrack) { + TrackMapStreamer.Init(pCurrentTrack, TrackMap, 0, 0); + RefreshHeader(); + } +} + +void UIQRTrackSelect::ScrollRegions(eScrollDir dir) { + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + unsigned char region = settings->RegionFilterBits; + if (dir == eSD_PREV) { + if (region == 0) { + region = 3; + } else { + region--; + } + } else if (dir == eSD_NEXT) { + if (region == 3) { + region = 0; + } else { + region++; + } + } + settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->RegionFilterBits = region; + BuildPresetTrackList(); + RefreshHeader(); +} + +void UIQRTrackSelect::RefreshHeader() { + FEImage *img; + img = FEngFindImage(PackageFilename, 0x91c4a50); + FEngSetButtonTexture(img, 0x5bc); + img = FEngFindImage(PackageFilename, 0x2d145be3); + FEngSetButtonTexture(img, 0x682); + + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + unsigned int hash; + switch (settings->RegionFilterBits) { + case 0: + hash = 0xa6850651; + break; + case 1: + hash = 0xa5c20e7d; + break; + case 2: + hash = 0x8663faef; + break; + case 3: + hash = 0x632dd19b; + break; + default: + hash = 0; + break; + } + FEngSetLanguageHash(PackageFilename, 0x78008599, hash); + FEngSetLanguageHash(PackageFilename, 0x4510987f, hash); + FEPrintf(PackageFilename, 0x6f25a248, "%d", Tracks.GetNodeNumber(pCurrentNode)); + FEPrintf(PackageFilename, 0xb2037bdc, "%d", Tracks.CountElements()); + + FEngSetLanguageHash(PackageFilename, 0xb5154998, + FEDatabase->GetRaceNameHash(FEDatabase->RaceMode)); + + FEngSetVisible(PackageFilename, 0x6b67d70b); + + if (!pCurrentTrack) { + FEPrintf(PackageFilename, 0x6f25a248, "0"); + FEPrintf(PackageFilename, 0xb2037bdc, "0"); + FEPrintf(PackageFilename, 0x5e7b09c9, ""); + FEPrintf(PackageFilename, 0xdfb7a2e, ""); + FEPrintf(PackageFilename, 0xb5154999, "--"); + FEPrintf(PackageFilename, 0xb515499c, "%s", GetLocalizedString(0x472aa00a)); + FEngSetLanguageHash(PackageFilename, 0x68215623, 0xf9c0519a); + FEngSetInvisible(PackageFilename, 0xe08434fc); + } else { + if (!pCurrentNode->bLocked) { + FEngSetInvisible(PackageFilename, 0x6b67d70b); + FEngSetVisible(PackageFilename, 0xe08434fc); + } else { + char rival_name_locdb[128]; + FEngSNPrintf(rival_name_locdb, 0x80, "blacklist_rival_%02d_aka", pCurrentNode->bin); + const char *pkg = PackageFilename; + const char *rival_label = GetLocalizedString(0xbd563be5); + unsigned int aka_hash = FEHashUpper(rival_name_locdb); + const char *aka_name = GetLocalizedString(aka_hash); + FEPrintf(pkg, 0x68215623, rival_label, aka_name, pCurrentNode->bin); + FEngSetInvisible(PackageFilename, 0xe08434fc); + } + + unsigned int trackNameHash = CalcLanguageHash("TRACKNAME_", pCurrentTrack); + if (DoesStringExist(trackNameHash)) { + FEngSetLanguageHash(PackageFilename, 0x5e7b09c9, trackNameHash); + FEngSetLanguageHash(PackageFilename, 0xdfb7a2e, trackNameHash); + } else { + FEPrintf(PackageFilename, 0x5e7b09c9, pCurrentTrack->GetEventID()); + FEPrintf(PackageFilename, 0xdfb7a2e, pCurrentTrack->GetEventID()); + } + + FEngSetInvisible(PackageFilename, 0xbbf970cd); + + bool kph = true; + const char *distUnits; + const char *speedUnits; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + distUnits = GetLocalizedString(0x8569a26a); + speedUnits = GetLocalizedString(0x8569a25f); + } else { + distUnits = GetLocalizedString(0x867dcfd9); + speedUnits = GetLocalizedString(0x8569ab44); + kph = false; + } + + const char *distFmt = "%$0.1f %s"; + const char *distPkg = PackageFilename; + float distance = pCurrentTrack->GetRaceLengthMeters(); + if (kph) { + distance *= 0.001f; + } else { + distance *= 0.000621371f; + } + FEPrintf(distPkg, 0xb5154999, distFmt, distance, distUnits); + + GRaceSaveInfo *info = GRaceDatabase::Get().GetScoreInfo(pCurrentTrack->GetEventHash()); + + if (pCurrentTrack->GetRaceType() == GRace::kRaceType_P2P || + pCurrentTrack->GetRaceType() == GRace::kRaceType_Circuit || + pCurrentTrack->GetRaceType() == GRace::kRaceType_Drag || + pCurrentTrack->GetRaceType() == GRace::kRaceType_Knockout || + pCurrentTrack->GetRaceType() == GRace::kRaceType_Tollbooth) { + Timer t(info->mHighScores); + char buf[64]; + t.PrintToString(buf, 0); + FEPrintf(PackageFilename, 0xb515499c, "%s", buf); + } else if (pCurrentTrack->GetRaceType() == GRace::kRaceType_SpeedTrap) { + float max_speed; + if (FEDatabase->GetGameplaySettings()->SpeedoUnits == 1) { + max_speed = info->mHighScores; + } else { + max_speed = MPS2MPH(KPH2MPS(info->mHighScores)); + } + FEngSetLanguageHash(PackageFilename, 0x28462c64, 0x512e823); + FEPrintf(PackageFilename, 0xb515499c, "%$0.0f %s", max_speed, speedUnits); + } else { + FEPrintf(PackageFilename, 0xb515499c, "%s", GetLocalizedString(0x472aa00a)); + } + + if (pCurrentTrack->GetRaceType() == GRace::kRaceType_Circuit || + pCurrentTrack->GetRaceType() == GRace::kRaceType_Knockout) { + FEngSetLanguageHash(PackageFilename, 0x28462c64, 0xc5b5a177); + } + + const char *pkg = PackageFilename; + unsigned int raceIconHash = FEDatabase->GetRaceIconHash(pCurrentTrack->GetRaceType()); + img = FEngFindImage(pkg, 0x8007b4c); + FEngSetTextureHash(img, raceIconHash); + } +} + +void UIQRTrackSelect::NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) { + switch (msg) { + case 0xe1fde1d1: + if (pCurrentTrack) { + bool isSplitQR = false; + if ((FEDatabase->GetGameMode() & 4) != 0) { + isSplitQR = FEDatabase->iNumPlayers == 2; + } + GRace::Type rt = pCurrentTrack->GetRaceType(); + if (isSplitQR && (rt == GRace::kRaceType_Drag || + pCurrentTrack->GetRaceType() == GRace::kRaceType_P2P || + pCurrentTrack->GetRaceType() == GRace::kRaceType_SpeedTrap)) { + GRaceCustom *custom = GRaceDatabase::Get().AllocCustomRace(pCurrentTrack); + SetNumOpponents(custom, 1); + SetCopsEnabled(custom, false); + GRaceDatabase::Get().SetStartupRace(custom, kRaceContext_QuickRace); + GRaceDatabase::Get().FreeCustomRace(custom); + cFEng::Get()->QueuePackageSwitch("PressStart.fng", 0, 0, false); + return; + } + } + cFEng::Get()->QueuePackageSwitch("Track_Options.fng", static_cast(reinterpret_cast(pCurrentTrack)), 0, false); + RefreshHeader(); + break; + case 0x911ab364: { + GRaceDatabase::Get().ClearStartupRace(); + RaceSettings *settings = FEDatabase->GetQuickRaceSettings(static_cast(0xb)); + settings->EventHash = 0; + const char *pkg; + if (FEDatabase->IsOnlineMode() || FEDatabase->IsLANMode()) { + pkg = "OL_MAIN.fng"; + } else { + pkg = "MainMenu_Sub.fng"; + } + cFEng::Get()->QueuePackageSwitch(pkg, 0, 0, false); + break; + } + case 0x406415e3: + if (!pCurrentTrack) { + return; + } + if (pCurrentNode->bLocked) { + return; + } + SetSelectedTrack(pCurrentTrack); + if (FEDatabase->RaceMode == GRace::kRaceType_None) { + FEDatabase->RaceMode = pCurrentTrack->GetRaceType(); + } + cFEng::Get()->QueuePackageMessage(0x2e76edfb, PackageFilename, nullptr); + break; + case 0x9120409e: + ScrollTracks(eSD_PREV); + break; + case 0xb5971bf1: + ScrollTracks(eSD_NEXT); + break; + case 0xd9feec59: + ScrollRegions(eSD_NEXT); + break; + case 0x5073ef13: + ScrollRegions(eSD_PREV); + break; + case 0xc98356ba: + TrackMapStreamer.UpdateAnimation(); + break; + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.hpp index a6f0d5c05..06abe983a 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiQRTrackSelect.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRTRACKSELECT_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UIQRTRACKSELECT_H @@ -5,6 +6,40 @@ #pragma once #endif +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconPanel.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp" +#include "Speed/Indep/bWare/Inc/bList.hpp" +#include + +struct FEMultiImage; +struct GRaceParameters; +struct SelectableTrack; + +// total size: 0x120 +struct UIQRTrackSelect : public MenuScreen { + UIQRTrackSelect(ScreenConstructorData *sd); + ~UIQRTrackSelect() override; + + void NotificationMessage(unsigned long msg, FEObject *pobj, unsigned long param1, unsigned long param2) override; + + virtual void Setup(); + + void SetSelectedTrack(GRaceParameters *track); + bool IsRaceValidForMike(GRaceParameters *parms); + void TryToAddTrack(GRaceParameters *parms, int unlock_filter, int bin_num); + void BuildPresetTrackList(); + void RefreshHeader(); + void ScrollTracks(eScrollDir dir); + void ScrollRegions(eScrollDir dir); + + UITrackMapStreamer TrackMapStreamer; // offset 0x2C, size 0xDC + bTList Tracks; // offset 0x108, size 0x8 + SelectableTrack *pCurrentNode; // offset 0x110, size 0x4 + GRaceParameters *pCurrentTrack; // offset 0x114, size 0x4 + FEMultiImage *TrackMap; // offset 0x118, size 0x4 + unsigned int MapHash; // offset 0x11C, size 0x4 +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.cpp index e69de29bb..6ad5ad0e2 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.cpp @@ -0,0 +1,89 @@ +// OWNED BY zFeOverlay AGENT - DO NOT MODIFY OR EMPTY +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.hpp" +#include "Speed/Indep/Src/Input/IOModule.h" +#include "Speed/Indep/Src/Input/ISteeringWheel.h" + +extern void FEngSetButtonTexture(FEImage *img, unsigned int hash); +extern bool eIsWidescreen(); + +const char *Showcase::FromPackage; +unsigned int Showcase::FromArgs; +unsigned int Showcase::FromIndex; +unsigned int Showcase::BlackListNumber; +int Showcase::FromFilter; +void *Showcase::FromColor[3]; + +Showcase::Showcase(ScreenConstructorData *sd) : MenuScreen(sd) // + , RivalStreamer(sd->PackageFilename, false) +{ + if (eIsWidescreen()) { + cFEng::Get()->QueuePackageMessage(bStringHash("WidescreenFix"), GetPackageName(), 0); + } + + car = reinterpret_cast(sd->Arg); + + if (car) { + if (BlackListNumber != 0) { + const char *titleStr = GetLocalizedString(0x3a64de21); + char buf[32]; + FEngSNPrintf(buf, 0x20, titleStr, BlackListNumber); + FEPrintf(GetPackageName(), 0x242657ce, "%s", buf); + cFEng::Get()->QueuePackageMessage(0x89d0649c, GetPackageName(), 0); + FEPrintf(GetPackageName(), 0xb695a891, "%d", BlackListNumber); + FEngSetLanguageHash(GetPackageName(), 0x7ac3d0c9, FEngHashString("BL_RIVAL_%d", BlackListNumber)); + pTagImg = FEngFindImage(GetPackageName(), 0xf5a2a087); + RivalStreamer.Init(BlackListNumber, nullptr, pTagImg, nullptr); + } else { + const char *pkg = GetPackageName(); + unsigned int manuLogoHash = car->GetManuLogoHash(); + FEImage *manuLogo = FEngFindImage(pkg, 0x3e01ad1d); + FEngSetTextureHash(manuLogo, manuLogoHash); + unsigned int logoHash = car->GetLogoHash(); + FEImage *carBadge = FEngFindImage(pkg, 0xb05dd708); + FEngSetTextureHash(carBadge, logoHash); + RivalStreamer.Init(1, nullptr, nullptr, nullptr); + } + } + + bool isDeviceWheel = false; + FEImage *controlGroupImg = FEngFindImage(GetPackageName(), 0x66be0542); + FEngSetButtonTexture(controlGroupImg, 0xfbb0b78e); + FEImage *img1 = FEngFindImage(GetPackageName(), 0x5bc); + FEngSetButtonTexture(img1, 0x5bc); + FEImage *img2 = FEngFindImage(GetPackageName(), 0x682); + FEngSetButtonTexture(img2, 0x682); + + for (int i = 0; i < 4; i++) { + if (i < IOModule::GetIOModule().GetNumDevices()) { + InputDevice *dev = IOModule::GetIOModule().GetDevice(i); + if (dev) { + UTL::COM::IUnknown *pUnk = dev->GetInterfaces(); + if (pUnk) { + ISteeringWheel *wheelDevice = nullptr; + if (pUnk->QueryInterface(&wheelDevice) && wheelDevice && wheelDevice->IsConnected()) { + isDeviceWheel = true; + } + } + } + } + } + + if (isDeviceWheel) { + FEObject *obj = FEngFindObject(GetPackageName(), 0xd86aacf8); + FEngSetInvisible(obj); + } +} + +Showcase::~Showcase() {} + +void Showcase::NotificationMessage(unsigned long msg, FEObject *pObj, unsigned long param1, unsigned long param2) { + if (msg == 0x406415e3 || msg == 0x911ab364) { + cFEng::Get()->QueuePackageSwitch(FromPackage, FromArgs, 0, false); + if (BlackListNumber != 0) { + car->Handle = 0xFFFFFFFF; + GarageMainScreen::GetInstance()->DisableCarRendering(); + } + FromArgs = 0; + BlackListNumber = 0; + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.hpp new file mode 100644 index 000000000..4cbdfaff1 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiShowcase.hpp @@ -0,0 +1,35 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite +#ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UISHOWCASE_H +#define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UISHOWCASE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/FEMenuScreen.hpp" +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/career/uiRepSheetRivalStreamer.hpp" +#include + +struct FECarRecord; +struct FEImage; + +// total size: 0x70 +struct Showcase : public MenuScreen { + Showcase(ScreenConstructorData *sd); + ~Showcase() override; + + void NotificationMessage(unsigned long msg, FEObject *pObj, unsigned long param1, unsigned long param2) override; + + static const char *FromPackage; + static unsigned int FromArgs; + static unsigned int FromIndex; + static unsigned int BlackListNumber; + static int FromFilter; + static void *FromColor[3]; + + FECarRecord *car; // offset 0x2C, size 0x4 + FEImage *pTagImg; // offset 0x30, size 0x4 + uiRepSheetRivalStreamer RivalStreamer; // offset 0x34, size 0x3C +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.cpp index e69de29bb..7bb531a73 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.cpp @@ -0,0 +1,321 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite +#include "Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp" + +#include "Speed/Indep/Src/FEng/FEMultiImage.h" +#include "Speed/Indep/Src/FEng/FETypes.h" +#include "Speed/Indep/Src/Gameplay/GRaceStatus.h" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/World/TrackStreamer.hpp" + +struct Vector2 { + float x; + float y; +}; + +struct FEObject; + +void FEngSetInvisible(FEObject* obj); +void FEngSetVisible(FEObject* obj); +void FEngSetTextureHash(FEImage* image, unsigned int texture_hash); +unsigned long FEHashUpper(const char* string); +unsigned int FEngHashString(const char* format, ...); +unsigned int CalcLanguageHash(const char* prefix, GRaceParameters* pRaceParams); + +void eLoadStreamingTexturePack(const char* filename, void (*callback)(void*), void* param, + int memory_pool_num); +void eLoadStreamingTexture(unsigned int* name_hash_table, int num_hashes, + void (*callback)(void*), void* param, int memory_pool_num); +void eUnloadStreamingTexture(unsigned int* name_hash_table, int num_hashes); + +inline void eLoadStreamingTexture(unsigned int name_hash, void (*callback)(unsigned int), + unsigned int param0, int memory_pool_num) { + eLoadStreamingTexture(&name_hash, 1, reinterpret_cast< void (*)(void*) >(callback), + reinterpret_cast< void* >(param0), memory_pool_num); +} + +inline void eUnloadStreamingTexture(unsigned int name_hash) { + eUnloadStreamingTexture(&name_hash, 1); +} + +void eWaitForStreamingTexturePackLoading(const char* filename); +void eUnloadAllStreamingTextures(const char* filename); +void eUnloadStreamingTexturePack(const char* filename); +int eIsStreamingTexturePackLoaded(const char* filename); + +extern float RealTimeElapsed; + +struct cPoint { + static void SplineSeek(tCubic2D* p, float time); +}; + +static UITrackMapStreamer* pInstance; + +UITrackMapStreamer::UITrackMapStreamer() + : bMapPackLoaded(false) // + , bLoadingMap(false) // + , pCurrentTrack(nullptr) // + , TrackMap(nullptr) // + , MapHash(0) // + , ZoomCubic(0, 1.0f) // + , PanCubic(0, 1.0f) +{ + bUsingTrackForAnim = true; + pInstance = this; + + ZoomCubic.SetDuration(1.0f); + PanCubic.SetDuration(1.0f); + ZoomCubic.SetFlags(0); + PanCubic.SetFlags(0); + ZoomCubic.SetVal(1.0f, 1.0f); + + bMakeSpaceInPoolComplete = false; + MemPoolNum = 0; + + bUseTrackStreamerMem = TheGameFlowManager.IsInGame(); + + if (bUseTrackStreamerMem) { + MemPoolNum = 7; + TheTrackStreamer.DisableZoneSwitching(); + TheTrackStreamer.MakeSpaceInPool(0x60000, MakeSpaceInPoolCallbackBridge, + reinterpret_cast< int >(this)); + } else { + eLoadStreamingTexturePack( + "TRACKS\\L2RA\\TrackMaps.bin", + reinterpret_cast< void (*)(void*) >(MapPackLoadCallback), + reinterpret_cast< void* >(this), 0); + } +} + +UITrackMapStreamer::~UITrackMapStreamer() { + if (bUseTrackStreamerMem) { + if (!bMakeSpaceInPoolComplete) { + TheTrackStreamer.WaitForCurrentLoadingToComplete(); + } + TheTrackStreamer.EnableZoneSwitching(); + TheTrackStreamer.RefreshLoading(); + } + eWaitForStreamingTexturePackLoading("TRACKS\\L2RA\\TrackMaps.bin"); + eUnloadStreamingTexture(MapHash); + eUnloadAllStreamingTextures("TRACKS\\L2RA\\TrackMaps.bin"); + if (bMapPackLoaded) { + eUnloadStreamingTexturePack("TRACKS\\L2RA\\TrackMaps.bin"); + } + pInstance = nullptr; +} + +void UITrackMapStreamer::MakeSpaceInPoolCallback() { + bMakeSpaceInPoolComplete = true; + eLoadStreamingTexturePack("TRACKS\\L2RA\\TrackMaps.bin", + reinterpret_cast< void (*)(void*) >(MapPackLoadCallback), + reinterpret_cast< void* >(this), 0); +} + +void UITrackMapStreamer::Init(GRaceParameters* track, FEMultiImage* map, int unused, + int region_unlock) { + RegionUnlock = region_unlock; + pCurrentTrack = track; + TrackMap = map; + FEngSetInvisible(static_cast< FEObject* >(map)); + if (bMapPackLoaded && !bLoadingMap) { + eUnloadStreamingTexture(MapHash); + eWaitForStreamingTexturePackLoading("TRACKS\\L2RA\\TrackMaps.bin"); + MapHash = CalcMapTextureHash(); + eLoadStreamingTexture(MapHash, MapLoadCallback, MapHash, MemPoolNum); + bLoadingMap = true; + } +} + +void UITrackMapStreamer::MapLoadCallback(unsigned int texture) { + pInstance->SetMapLoaded(texture); +} + +unsigned int UITrackMapStreamer::CalcMapTextureHash() { + if (pCurrentTrack) { + return CalcLanguageHash("TRACK_MAP_", pCurrentTrack); + } else if (!RegionUnlock) { + return FEHashUpper("TRACK_MAP"); + } else { + return FEngHashString("TRACK_MAP_UNLOCK_%d", RegionUnlock); + } +} + +void UITrackMapStreamer::SetMapPackLoaded() { + if (eIsStreamingTexturePackLoaded("TRACKS\\L2RA\\TrackMaps.bin")) { + bMapPackLoaded = true; + MapHash = CalcMapTextureHash(); + eLoadStreamingTexture(MapHash, MapLoadCallback, MapHash, MemPoolNum); + bLoadingMap = true; + } +} + +void UITrackMapStreamer::SetMapLoaded(unsigned int texture) { + unsigned int hash = CalcMapTextureHash(); + if (hash != texture) { + unsigned int old_texture = texture; + eUnloadStreamingTexture(&old_texture, 1); + MapHash = hash; + FEngSetInvisible(static_cast< FEObject* >(TrackMap)); + unsigned int new_hash = MapHash; + eLoadStreamingTexture(&new_hash, 1, + reinterpret_cast< void (*)(void*) >(MapLoadCallback), + reinterpret_cast< void* >(new_hash), MemPoolNum); + } else { + bLoadingMap = false; + FEngSetTextureHash(static_cast< FEImage* >(TrackMap), hash); + FEngSetVisible(static_cast< FEObject* >(TrackMap)); + if (bUsingTrackForAnim) { + ZoomToTrack(); + PanToTrack(); + } + } +} + +void UITrackMapStreamer::UpdateMap() { + if (TrackMap != nullptr) { + bVector2 mapTL(0.0f, 0.0f); + bVector2 mapBR(0.0f, 0.0f); + bVector2 zoom; + bVector2 pan; + bVector2* pMapTL = &mapTL; + bVector2* pMapBR = &mapBR; + bVector2* pZoom = &zoom; + bVector2* pPan = &pan; + + ZoomCubic.GetVal(pZoom); + PanCubic.GetVal(pPan); + + pMapTL->x = pPan->x - pZoom->x * 0.5f; + pMapTL->y = pPan->y - pZoom->y * 0.5f; + pMapBR->x = pZoom->x * 0.5f + pPan->x; + pMapBR->y = pZoom->y * 0.5f + pPan->y; + + float halfSizeX = (pMapBR->x - pMapTL->x) * 0.5f; + float halfSizeY = (pMapBR->y - pMapTL->y) * 0.5f; + float halfSize = bMax(halfSizeX, halfSizeY); + + FEVector2 mapCenter(pMapTL->x + halfSizeX, pMapTL->y + halfSizeY); + FEVector2 TL(mapCenter.x - halfSize, mapCenter.y - halfSize); + FEVector2 BR(mapCenter.x + halfSize, mapCenter.y + halfSize); + + TrackMap->SetTopLeft(TL, false); + TrackMap->SetBottomRight(BR, false); + } +} + +void UITrackMapStreamer::CalcBoundsForRace(bVector2& top_left, bVector2& bottom_right) { + if (pCurrentTrack != nullptr) { + Vector2 topLeftMap; + Vector2 botRightMap; + pCurrentTrack->GetBoundingBox(topLeftMap, botRightMap); + float margin = 0.125f; + top_left.x = topLeftMap.x - margin; + top_left.y = topLeftMap.y + margin; + bottom_right.x = botRightMap.x + margin; + bottom_right.y = botRightMap.y - margin; + } +} + +void UITrackMapStreamer::UpdateAnimation() { + cPoint::SplineSeek(&ZoomCubic, RealTimeElapsed); + cPoint::SplineSeek(&PanCubic, RealTimeElapsed); + UpdateMap(); +} + +float UITrackMapStreamer::GetZoomFactor() { + bVector2 temp; + ZoomCubic.GetVal(&temp); + if (temp.x == 0.0f) { + return 1.0f; + } + return 1.0f / temp.x; +} + +void UITrackMapStreamer::GetPan(bVector2& pan) { + bVector2 center(0.5f, 0.5f); + PanCubic.GetVal(&pan); + pan -= center; +} + +void UITrackMapStreamer::ZoomToTrack() { + bVector2 mapTL(0.0f, 0.0f); + bVector2 mapBR(1.0f, 1.0f); + bUsingTrackForAnim = true; + CalcBoundsForRace(mapTL, mapBR); + bVector2 zoom_to(mapBR.x - mapTL.x, mapTL.y - mapBR.y); + ZoomTo(zoom_to); +} + +void UITrackMapStreamer::PanToTrack() { + bVector2 mapTL; + bVector2 mapBR; + bVector2 pan_to; + bVector2* pMapTL = &mapTL; + bVector2* pMapBR = &mapBR; + bVector2* pPanTo = &pan_to; + + bUsingTrackForAnim = true; + pMapTL->x = 0.0f; + pMapTL->y = 0.0f; + pMapBR->x = 1.0f; + pMapBR->y = 1.0f; + CalcBoundsForRace(mapTL, mapBR); + + pPanTo->x = (pMapTL->x + pMapBR->x) * 0.5f; + pPanTo->y = (pMapTL->y + pMapBR->y) * 0.5f; + PanTo(pan_to); +} + +void UITrackMapStreamer::SetZoom(const bVector2& factor) { + ZoomTo(factor); + ZoomCubic.Snap(); +} + +void UITrackMapStreamer::SetPan(const bVector2& pos) { + PanTo(pos); + PanCubic.Snap(); +} + +void UITrackMapStreamer::SetZoomSpeed(float sec) { + ZoomCubic.SetDuration(sec); +} + +void UITrackMapStreamer::SetPanSpeed(float sec) { + PanCubic.SetDuration(sec); +} + +void UITrackMapStreamer::ResetZoom(bool use_track) { + bUsingTrackForAnim = use_track; + if (use_track) { + ZoomToTrack(); + ZoomCubic.Snap(); + } else { + SetZoom(bVector2(1.0f, 1.0f)); + } +} + +void UITrackMapStreamer::ResetPan(bool use_track) { + bUsingTrackForAnim = use_track; + if (use_track) { + PanToTrack(); + PanCubic.Snap(); + } else { + bVector2 pan(0.5f, 0.5f); + SetPan(pan); + } +} + +void UITrackMapStreamer::ZoomTo(const bVector2& factor) { + ZoomCubic.SetValDesired(const_cast< bVector2* >(&factor)); +} + +void UITrackMapStreamer::PanTo(const bVector2& pos) { + PanCubic.SetValDesired(const_cast< bVector2* >(&pos)); +} + +void UITrackMapStreamer::MapPackLoadCallback(unsigned int screenPtr) { + reinterpret_cast< UITrackMapStreamer* >(screenPtr)->SetMapPackLoaded(); +} + +void UITrackMapStreamer::MakeSpaceInPoolCallbackBridge(int param) { + reinterpret_cast< UITrackMapStreamer* >(param)->MakeSpaceInPoolCallback(); +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp index 81c937b27..580586256 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/quickrace/uiTrackMapStreamer.hpp @@ -1,3 +1,4 @@ +// OWNED BY zFeOverlay AGENT - do not empty or overwrite #ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UITRACKMAPSTREAMER_H #define FRONTEND_MENUSCREENS_SAFEHOUSE_QUICKRACE_UITRACKMAPSTREAMER_H @@ -5,6 +6,160 @@ #pragma once #endif +#include +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +struct GRaceParameters; +struct FEMultiImage; + +// total size: 0x2C +struct tCubic1D { + float Val; // offset 0x0, size 0x4 + float dVal; // offset 0x4, size 0x4 + float ValDesired; // offset 0x8, size 0x4 + float dValDesired; // offset 0xC, size 0x4 + float Coeff[4]; // offset 0x10, size 0x10 + float time; // offset 0x20, size 0x4 + float duration; // offset 0x24, size 0x4 + short state; // offset 0x28, size 0x2 + short flags; // offset 0x2A, size 0x2 + + tCubic1D(short type, float dur) + : Val(0.0f) // + , dVal(0.0f) // + , ValDesired(0.0f) // + , dValDesired(0.0f) // + , time(0.0f) // + , duration(dur) // + , state(type) // + , flags(1) + { + Coeff[0] = 0.0f; + Coeff[1] = 0.0f; + Coeff[2] = 0.0f; + Coeff[3] = 0.0f; + } + + void Snap() { + Val = ValDesired; + dVal = dValDesired; + state = 0; + } + void SetVal(const float v) { Val = v; state = 2; } + void SetdVal(float v) { dVal = v; state = 2; } + void SetValDesired(float v) { ValDesired = v; state = 2; } + void SetdValDesired(float v) { dValDesired = v; } + void SetDuration(const float t) { duration = t; } + void SetState(short s) { state = s; } + void SetFlags(short f) { flags = f; } + float GetVal(); + float GetdVal(); + float GetddVal(); + int HasArrived(); + void PathdValDesired(float v); + void MakeCoeffs(); + float GetVal(float t); + float GetdVal(float t); + float GetddVal(float t); + float GetValDesired(); + float GetdValDesired(); + float GetDerivative(float t); + float GetSecondDerivative(float t); + void ClampDerivative(float fMag); + void ClampSecondDerivative(float fMag); + void Update(float fSeconds, float fDClamp, float fDDClamp); +}; + +// total size: 0x58 +struct tCubic2D { + tCubic1D x; // offset 0x0, size 0x2C + tCubic1D y; // offset 0x2C, size 0x2C + + tCubic2D(short type, float dur) : x(type, dur), y(type, dur) {} + tCubic2D(short type, bVector2* pDuration); + + int HasArrived(); + void Snap() { + x.Snap(); + y.Snap(); + } + void SetVal(const float vx, const float vy) { x.SetVal(vx); y.SetVal(vy); } + void SetdVal(float vx, float vy) { x.SetdVal(vx); y.SetdVal(vy); } + void SetValDesired(float vx, float vy) { x.SetValDesired(vx); y.SetValDesired(vy); } + void SetdValDesired(float vx, float vy) { x.SetdValDesired(vx); y.SetdValDesired(vy); } + void SetDuration(const float t) { + x.SetDuration(t); + y.SetDuration(t); + } + void SetDuration(const float tx, const float ty); + void SetState(short s) { x.SetState(s); y.SetState(s); } + void SetFlags(short s) { x.SetFlags(s); y.SetFlags(s); } + void PathdValDesired(float x2, float y2); + void PathdValDesired(bVector2* v); + void MakeCoeffs(); + + void SetVal(const bVector2* pV); + void SetdVal(bVector2* pV); + void SetValDesired(bVector2* pV); + void SetdValDesired(bVector2* pV); + void SetDuration(const bVector2* pV); + void GetVal(bVector2* pV); + void GetdVal(bVector2* pV); + void GetddVal(bVector2* pV); + void GetVal(bVector2* pV, float t); + void GetdVal(bVector2* pV, float t); + void GetddVal(bVector2* pV, float t); + void GetValDesired(bVector2* pV); + void GetdValDesired(bVector2* pV); + void Update(float fSeconds, float fDClamp, float fDDClamp); +}; + +// total size: 0xDC +struct UITrackMapStreamer { + bool bMapPackLoaded; // offset 0x0, size 0x1 + bool bMakeSpaceInPoolComplete; // offset 0x4, size 0x1 + bool bLoadingMap; // offset 0x8, size 0x1 + bool bUseTrackStreamerMem; // offset 0xC, size 0x1 + int MemPoolNum; // offset 0x10, size 0x4 + GRaceParameters* pCurrentTrack; // offset 0x14, size 0x4 + FEMultiImage* TrackMap; // offset 0x18, size 0x4 + unsigned int MapHash; // offset 0x1C, size 0x4 + int RegionUnlock; // offset 0x20, size 0x4 + tCubic2D ZoomCubic; // offset 0x24, size 0x58 + tCubic2D PanCubic; // offset 0x7C, size 0x58 + bool bUsingTrackForAnim; // offset 0xD4, size 0x1 + // vtable at 0xD8 + + static void MakeSpaceInPoolCallbackBridge(int param); + + UITrackMapStreamer(); + virtual ~UITrackMapStreamer(); + + void MakeSpaceInPoolCallback(); + void Init(GRaceParameters* track, FEMultiImage* map, int unused, int region_unlock); + void UpdateAnimation(); + bool IsZooming(); + float GetZoomFactor(); + void GetPan(bVector2& pan); + void ZoomTo(const bVector2& factor); + void PanTo(const bVector2& pos); + void ZoomToTrack(); + void PanToTrack(); + void SetZoom(const bVector2& factor); + void SetPan(const bVector2& pos); + void SetZoomSpeed(float sec); + void SetPanSpeed(float sec); + void ResetZoom(bool use_track); + void ResetPan(bool use_track); + void SetMapPackLoaded(); + void SetMapLoaded(unsigned int texture); + + static void MapLoadCallback(unsigned int texture); + static void MapPackLoadCallback(unsigned int screenPtr); + void UpdateMap(); + void CalcBoundsForRace(bVector2& top_left, bVector2& bottom_right); + unsigned int CalcMapTextureHash(); +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.cpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.cpp index e69de29bb..f1c47c82e 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.cpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.cpp @@ -0,0 +1,252 @@ +#include "uiMain.hpp" + +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/FEng/FEList.h" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Frontend/MemoryCard/MemoryCard.hpp" +#include "Speed/Indep/Src/Misc/Config.h" + +const char* gOnlineMainMenu = "OL_MAIN.fng"; + +// GarageMainScreen forward definition (full definition in FEAnyMovieScreen.cpp later in TU) +struct GarageMainScreen : public MenuScreen { + char _pad_2c[0x2C]; // offset 0x2C to 0x58 + bool CameraPushRequested; // offset 0x58 + char _pad_59[0x17]; // offset 0x59 to 0x70 + int HideEntireScreen; // offset 0x70 + + GarageMainScreen(ScreenConstructorData* sd) : MenuScreen(sd) {} + ~GarageMainScreen() override; + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) override; + bool IsVisable() { return HideEntireScreen == 0; } + void CancelCameraPush() { CameraPushRequested = false; } + void UpdateCurrentCameraView(bool b); + void EnableCarRendering(); + void DisableCarRendering(); + static GarageMainScreen* GetInstance(); +}; + +void MemcardEnter(const char* from, const char* to, unsigned int op, + void (*pTermFunc)(void*), void* pTermFuncParam, + unsigned int msgSuccess, unsigned int msgFailed); +bool GetMikeMannBuild(); +extern int UnlockAllThings; +void FEngSetLanguageHash(const char* pkg_name, unsigned int obj_hash, unsigned int lang_hash); +unsigned char FEngGetLastButton(const char* pkg_name); +int FEPrintf(const char* pkg_name, int hash, const char* fmt, ...); +int GetCurrentLanguage(); + +FEObject* FEngFindObject(const char* pkg_name, unsigned int obj_hash); +void FEngSetInvisible(FEObject* obj); +void FEngSetVisible(FEObject* obj); + +inline void FEngSetInvisible(const char* pkg_name, unsigned int obj_hash) { + FEngSetInvisible(FEngFindObject(pkg_name, obj_hash)); +} +inline void FEngSetVisible(const char* pkg_name, unsigned int obj_hash) { + FEngSetVisible(FEngFindObject(pkg_name, obj_hash)); +} + +struct MainQuickRace : public IconOption { + MainQuickRace(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~MainQuickRace() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +void MainQuickRace::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) { + if (data != 0x0C407210) return; + FEDatabase->SetGameMode(eFE_GAME_MODE_QUICK_RACE); +} + +struct MainCustomize : public IconOption { + MainCustomize(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~MainCustomize() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +void MainCustomize::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) { + if (data != 0x0C407210) return; + FEDatabase->SetGameMode(eFE_GAME_MODE_CUSTOMIZE); +} + +struct MainProfileManager : public IconOption { + MainProfileManager(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~MainProfileManager() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +void MainProfileManager::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) { + if (data != 0x0C407210) return; + FEDatabase->SetGameMode(eFE_GAME_MODE_PROFILE_MANAGER); +} + +struct MainOptions : public IconOption { + MainOptions(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~MainOptions() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; + +void MainOptions::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) { + if (data != 0x0C407210) return; + FEDatabase->SetGameMode(eFE_GAME_MODE_OPTIONS); +} + +void MainCareer::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) { + if (data != 0x0C407210) return; + if (IsMemcardEnabled) { + FEDatabase->SetGameMode(eFE_GAME_MODE_CAREER_MANAGER); + } else { + FEDatabase->SetGameMode(eFE_GAME_MODE_CAREER); + } +} + +void Challenge::React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) { + if (data == 0x0C407210) { + if (FEDatabase->bProfileLoaded || !IsMemcardEnabled) { + FEDatabase->SetGameMode(eFE_GAME_MODE_CHALLENGE); + SetReactImmediately(false); + cFEng::Get()->QueuePackageMessage(0x0C407210, pkg_name, obj); + } else { + MemcardEnter("MainMenu.fng", "ChallengeSeries.fng", 0x10063, nullptr, nullptr, 0, 0); + } + } +} + +UIMain::UIMain(ScreenConstructorData* sd) + : IconScrollerMenu(sd) // + , m_bStatsShowing(false) { + Setup(); +} + +void UIMain::NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, + unsigned long param2) { + IconScrollerMenu::NotificationMessage(msg, obj, param1, param2); + + switch (msg) { + case 0x1265ece9: + GarageMainScreen::GetInstance()->UpdateCurrentCameraView(false); + break; + case 0x35f8620b: + if (!MemoryCard::GetInstance()->IsAutoLoadDone()) { + MemoryCard::GetInstance()->SetAutoLoadDone(true); + MemcardEnter(nullptr, nullptr, 0xF1, nullptr, nullptr, 0, 0); + } + break; + case 0xe1fde1d1: + if (PrevButtonMessage != 0x0c407210) { + break; + } + if (FEDatabase->IsCareerMode()) { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } else if (FEDatabase->IsCareerManagerMode()) { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } else if (FEDatabase->IsQuickRaceMode()) { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } else if (FEDatabase->IsOptionsMode()) { + cFEng::Get()->QueuePackageSwitch("MainMenu_Sub.fng", 0, 0, false); + } else if (FEDatabase->IsProfileManagerMode()) { + cFEng::Get()->QueuePackageSwitch("MC_ProfileManager.fng", 0, 0, false); + } else if (FEDatabase->IsChallengeMode()) { + cFEng::Get()->QueuePackageSwitch("ChallengeSeries.fng", 0, 0, false); + } else if (FEDatabase->IsCustomizeMode()) { + cFEng::Get()->QueuePackageSwitch("MyCarsManager.fng", 0, 0, false); + } + break; + case 0xc519bfc4: + if (FEDatabase->bProfileLoaded) { + const char* scriptName; + if (!m_bStatsShowing) { + scriptName = "GAMESTATS_APPEAR"; + } else { + scriptName = "GAMESTATS_LEAVE"; + } + cFEng::Get()->QueuePackageMessage(FEHashUpper(scriptName), GetPackageName(), nullptr); + m_bStatsShowing = !m_bStatsShowing; + } + break; + case 0x7e998e5e: + UpdateProfileData(); + break; + case 0x9120409e: + break; + } +} + +void UIMain::Setup() { + const unsigned long FEObj_TITLEGROUP = 0xb71b576d; + + FEDatabase->ResetGameMode(); + FEDatabase->SetPlayersJoystickPort(0, -1); + + if (GetMikeMannBuild()) { + Challenge* challenge = new Challenge(0x9a962438, 0xcc8cb746, 0); + challenge->SetReactImmediately(true); + AddOption(challenge); + AddOption(new MainQuickRace(0x4e6fbb02, 0x54020a7a, 0)); + AddOption(new MainCustomize(0xb0c46023, 0x1afd5be6, 0)); + AddOption(new MainOptions(0x3058fe37, 0x19a8c0af, 0)); + UnlockAllThings = 1; + } else { + AddOption(new MainCareer(0x3704f3d, 0x5815a2b5, 0)); + Challenge* challenge = new Challenge(0x9a962438, 0xcc8cb746, 0); + challenge->SetReactImmediately(true); + AddOption(challenge); + AddOption(new MainQuickRace(0x4e6fbb02, 0x54020a7a, 0)); + AddOption(new MainCustomize(0xb0c46023, 0x1afd5be6, 0)); + if (IsMemcardEnabled) { + AddOption(new MainProfileManager(0x6b303856, 0xbcb18f38, 0)); + } + AddOption(new MainOptions(0x3058fe37, 0x19a8c0af, 0)); + } + + FEngSetLanguageHash(GetPackageName(), FEObj_TITLEGROUP, 0xb24aae58); + unsigned char lastButton = FEngGetLastButton(GetPackageName()); + + if (bFadeInIconsImmediately) { + Options.bDelayUpdate = false; + Options.bFadingOut = false; + Options.StartFadeIn(); + } + + Options.SetInitialPos(lastButton); + RefreshHeader(); + UpdateProfileData(); +} + +void UIMain::UpdateProfileData() { + if (FEDatabase->bProfileLoaded) { + GameCompletionStats stats = FEDatabase->GetGameCompletionStats(); + const unsigned long FEObj_PLAYERNAMEGROUP = 0xb514e2d8; + + const char* szPercentUnit = "%"; + eLanguages currLang = static_cast(GetCurrentLanguage()); + if (currLang == eLANGUAGE_DANISH || currLang == eLANGUAGE_FINNISH || + currLang == eLANGUAGE_FRENCH || currLang == eLANGUAGE_GERMAN || + currLang == eLANGUAGE_SWEDISH) { + szPercentUnit = " %"; + } + + FEPrintf(GetPackageName(), FEHashUpper("GAME_COMPLETED_DATA"), "%d%s", stats.m_nOverall, + szPercentUnit); + FEPrintf(GetPackageName(), FEHashUpper("CAREER_COMPLETED_DATA"), "%d%s", stats.m_nCareer, + szPercentUnit); + FEngSetInvisible(GetPackageName(), FEHashUpper("RAP_SHEET_ITEMS_DATA_1")); + FEPrintf(GetPackageName(), FEHashUpper("RAP_SHEET_ITEMS_DATA_2"), "%d%s", + stats.m_nRapSheetRankings, szPercentUnit); + FEPrintf(GetPackageName(), FEHashUpper("CHALLENGE_SERIES_DATA_1"), "%d/%d", + stats.m_nCompletedChallengeRaces, stats.m_nTotalChallengeRaces); + FEPrintf(GetPackageName(), FEHashUpper("CHALLENGE_SERIES_DATA_2"), "%d%s", + stats.m_nChallenge, szPercentUnit); + FEPrintf(GetPackageName(), FEObj_PLAYERNAMEGROUP, "%s", + FEDatabase->GetMultiplayerProfile(0)->GetProfileName()); + FEngSetVisible(GetPackageName(), FEHashUpper("NAME_GROUP")); + FEngSetVisible(GetPackageName(), FEHashUpper("GAME STATS GROUP")); + } else { + FEngSetInvisible(GetPackageName(), FEHashUpper("NAME_GROUP")); + FEngSetInvisible(GetPackageName(), FEHashUpper("GAME STATS GROUP")); + } +} diff --git a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.hpp b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.hpp index 9ee3c4a82..dbb211825 100644 --- a/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.hpp +++ b/src/Speed/Indep/Src/Frontend/MenuScreens/Safehouse/uiMain.hpp @@ -1,10 +1,34 @@ -#ifndef FRONTEND_MENUSCREENS_SAFEHOUSE_UIMAIN_H -#define FRONTEND_MENUSCREENS_SAFEHOUSE_UIMAIN_H +#ifndef _UIMAIN +#define _UIMAIN -#ifdef EA_PRAGMA_ONCE_SUPPORTED -#pragma once -#endif +#include + +#include "Speed/Indep/Src/Frontend/MenuScreens/Common/IconScrollerMenu.hpp" + +// total size: 0x170 +struct UIMain : public IconScrollerMenu { + bool m_bStatsShowing; // offset 0x16C, size 0x1 + + UIMain(ScreenConstructorData* sd); + ~UIMain() override {} + + void NotificationMessage(unsigned long msg, FEObject* obj, unsigned long param1, unsigned long param2) override; + void Setup() override; + void UpdateProfileData(); +}; +struct MainCareer : public IconOption { + MainCareer(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~MainCareer() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; +struct Challenge : public IconOption { + Challenge(unsigned int tex_hash, unsigned int name_hash, unsigned int desc_hash) + : IconOption(tex_hash, name_hash, desc_hash) {} + ~Challenge() override {} + void React(const char* pkg_name, unsigned int data, FEObject* obj, unsigned int param1, unsigned int param2) override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.cpp b/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.cpp index e69de29bb..15e4e4012 100644 --- a/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.cpp +++ b/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.cpp @@ -0,0 +1,448 @@ +#include "Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/EAXSound/EAXSOund.hpp" +#include "Speed/Indep/bWare/Inc/Strings.hpp" +#include "Speed/Indep/Src/World/TrackStreamer.hpp" +#include "Speed/Indep/Src/Misc/Joylog.hpp" + +struct TextureInfo; +struct Shape; +struct DECODER; + +extern TrackStreamer TheTrackStreamer; +extern bool IsSoundEnabled; +extern int SkipMovies; +extern bool ShutJosieUp; +extern TextureInfo MovieTextureInfo; +extern void* RCMPDecodeBuffer; + +extern int bStrNICmp(const char*, const char*, int); + +typedef void* (*RCMP_AllocFunc)(const char*, int, int, int, int); +typedef void (*RCMP_FreeFunc)(void*); + +void* RCMP_PlayerAllocAlign(const char*, int, int, int, int); +extern RCMP_FreeFunc RCMP_PlayerFree_func; + +struct RCMP_System { + RCMP_AllocFunc AllocMem; + RCMP_FreeFunc FreeMem; + int memClass; +}; +extern RCMP_System __4RCMP_rcmp_sys; + +struct MovieVolumeEntry { + const char* name; + unsigned int volume; +}; +extern MovieVolumeEntry MovieVolumeArray[]; + +extern void PlatFinishMovie(); +extern void PlatSetFirstMovieFrame(TextureInfo*, Shape*, bool); +extern unsigned int RCMP_GetMaxFramesOutStanding(); +extern void RCMP_PlayerFree(void*); +extern unsigned int bGetTicker(); +extern float bGetTickerDifference(unsigned int); +extern void* bMalloc(int, int); +extern void bFree(void*); +extern int GetVideoMode(); +extern void eWaitUntilRenderingDone(); +extern void NotifyFirstFrame_SubTitler(); +extern void SoundPause(bool pause, eSNDPAUSE_REASON reason); +extern void SYNCTASK_run(); +extern void THREAD_yield(int); + +namespace RealShape { +void SetAllocator(EA::Allocator::IAllocator*); +} + +class CarLoader { + public: + void MakeSpaceInPool(int size); +}; +extern CarLoader TheCarLoader; +extern int CarLoaderMemoryPoolNumber; + +void* GamecubeMaybeAllocateFromCarLoader(int size, const char* name, int alloc_params) { + bool track_stream_pool_exists = TheTrackStreamer.HasMemoryPool(); + if ((track_stream_pool_exists && size <= bLargestMalloc(7)) || size <= bLargestMalloc(0)) { + return nullptr; + } + TheCarLoader.MakeSpaceInPool(size); + void* ptr = bMalloc(size, name, __LINE__, (CarLoaderMemoryPoolNumber & 0xf) | alloc_params); + if (ptr != nullptr) { + return ptr; + } + return nullptr; +} + +// AV_PLAYER member functions not declared in header (using mangled names) +extern "C" { +AV_PLAYER* __Q24RCMP9AV_PLAYERPCciQ34RCMP9AV_PLAYER9LOAD_ENUMQ34RCMP9AV_PLAYER10SOUND_ENUM( + void*, const char*, int, int, int); +FRAME* GetFrame__Q24RCMP9AV_PLAYERf(AV_PLAYER*, float); +int IsTimeForDecode__Q24RCMP9AV_PLAYER(AV_PLAYER*); +int IsAudioFinished__Q24RCMP9AV_PLAYER(AV_PLAYER*); +void ReleaseFrame__Q24RCMP7DECODERPQ24RCMP5FRAME(DECODER*, FRAME*); +void FillInTextureInfo__11MoviePlayerPUiP11TextureInfoPQ29RealShape5Shape( + MoviePlayer*, unsigned int*, TextureInfo*, Shape*); +} + +MoviePlayer* gMoviePlayer; +unsigned int gMovieStartTime = 0xFFFFFFFF; +ShapeMemoryAllocator gShapeMemoryAllocator; + +bool MoviePlayer_Bypass() { + return bGetTickerDifference(gMovieStartTime) > 30.0f; +} + +void MoviePlayer_Play() { + if (gMoviePlayer != nullptr) { + gMovieStartTime = bGetTicker(); + gMoviePlayer->Play(); + } +} + +void MoviePlayer_StartUp() { + if (gMoviePlayer == nullptr) { + gMoviePlayer = new MoviePlayer(0); + } +} + +void MoviePlayer_ShutDown() { + gMovieStartTime = 0xFFFFFFFF; + if (gMoviePlayer != nullptr) { + delete gMoviePlayer; + gMoviePlayer = nullptr; + } + TheTrackStreamer.RefreshLoading(); +} + +MoviePlayer::MoviePlayer(int memClass) { + mSettings.preload = false; + mSettings.filename[0] = '\0'; + mSettings.bufferSize = 0x40000; + mSettings.activeController = 0; + mSettings.sound = IsSoundEnabled != false; + mSettings.volume = 0; + fStatus = 3; + fLiveStatus = 3; + fPlayer = nullptr; + CurFrame = nullptr; + mSettings.loop = false; + mSettings.pal = false; + mSettings.type = 0; + mSettings.movieId = 0; + fCurFrameNum = 0; + __4RCMP_rcmp_sys.AllocMem = RCMP_PlayerAllocAlign; + __4RCMP_rcmp_sys.FreeMem = RCMP_PlayerFree_func; + __4RCMP_rcmp_sys.memClass = memClass; + RealShape::SetAllocator(&gShapeMemoryAllocator); + if (TheTrackStreamer.HasMemoryPool()) { + TheTrackStreamer.MakeSpaceInPool(0x271000, true); + } +} + +MoviePlayer::~MoviePlayer() { + if (fPlayer != nullptr) { + delete fPlayer; + } + fPlayer = nullptr; + RCMP_PlayerFree(RCMPDecodeBuffer); + RCMPDecodeBuffer = nullptr; + PlatFinishMovie(); +} + +void MoviePlayer::Init(Settings& newSettings) { + mSettings.activeController = newSettings.activeController; + mSettings.bufferSize = newSettings.bufferSize; + mSettings.loop = newSettings.loop; + mSettings.preload = newSettings.preload; + mSettings.volume = newSettings.volume; + mSettings.sound = newSettings.sound; + mSettings.pal = newSettings.pal; + mSettings.type = newSettings.type; + mSettings.movieId = newSettings.movieId; + bStrNCpy(mSettings.filename, newSettings.filename, 256); + mSettings.volume = GetMovieCategoryVolume(); + ResetTimer(); + HandleFatalError(); +} + +void MoviePlayer::ResetTimer() { + mTicker = 0; + mMoviePaused = false; + mTickerFirstTime = true; + mili_seconds = 0; + seconds = 0; + minutes = 0; + milliseconds = 0.0f; + prevMilliseconds = 0.0f; +} + +void MoviePlayer::Stop() { + fStatus = 1; + fLiveStatus = 1; + ResetTimer(); +} + +unsigned int MoviePlayer::GetMillisecondsPerFrame() { + if (GetVideoMode() == 0) { + return 20; + } + return 16; +} + +int MoviePlayer::GetMovieCategoryVolume() { + unsigned int vol = 0x7F; + for (int i = 0; i < 0x26; i++) { + const char* name = MovieVolumeArray[i].name; + int len = bStrLen(name); + if (bStrNICmp(name, mSettings.filename, len) == 0) { + vol = MovieVolumeArray[i].volume; + if (ShutJosieUp) { + const char* name2 = MovieVolumeArray[i].name; + int len2 = bStrLen(name2); + if (bStrNICmp(name2, "josie", len2) == 0) { + vol = 0; + } + } + } + } + return vol; +} + +void MoviePlayer::HandleFatalError() {} + +void MoviePlayer::Play() { + if (SkipMovies == 0) { + int loadType = 0; + if (mSettings.preload) { + loadType = 1; + } + int soundType = mSettings.sound == false; + void* mem = __4RCMP_rcmp_sys.AllocMem("avplayer", 0x94, 0, 0, __4RCMP_rcmp_sys.memClass); + fPlayer = __Q24RCMP9AV_PLAYERPCciQ34RCMP9AV_PLAYER9LOAD_ENUMQ34RCMP9AV_PLAYER10SOUND_ENUM( + mem, mSettings.filename, mSettings.bufferSize, loadType, soundType); + HandleFatalError(); + if (fPlayer == nullptr) { + fStatus = 2; + fLiveStatus = 2; + return; + } + GetFirstFrame(); + if (mSettings.sound != false) { + fPlayer->SetVol(mSettings.volume); + } + fCurFrameNum = fCurFrameNum + 1; + fPlayer->Pause(); + if (CurFrame != nullptr) { + goto curframe_found; + } + } + cFEng::Get()->QueueGameMessage(0xc3960eb9, nullptr, 0xff); + return; +curframe_found: + { + Shape* shape = *reinterpret_cast(reinterpret_cast(CurFrame) + 4); + PlatSetFirstMovieFrame(&MovieTextureInfo, shape, mSettings.type == 0); + NotifyFirstFrame_SubTitler(); + fStatus = 5; + fLiveStatus = 5; + fPlayer->UnPause(); + } +} + +void MoviePlayer::GetFirstFrame() { + AV_PLAYER* player = fPlayer; + unsigned int maxFrames = RCMP_GetMaxFramesOutStanding(); + int msPerFrame = GetMillisecondsPerFrame(); + CurFrame = player->GetFirstFrame(maxFrames, msPerFrame << 1); +} + +void MoviePlayer::Update() { + if (fStatus == 5) { + UpdateFunction(); + int movie_done = (fLiveStatus != 5); + if (Joylog::IsReplaying()) { + int joylog_movie_done = Joylog::GetData(4, static_cast(8)); + if (joylog_movie_done != 0 && movie_done == 0) { + while (fLiveStatus != 0) { + UpdateFunction(); + } + } + movie_done = joylog_movie_done; + } + Joylog::AddData(movie_done, 4, static_cast(8)); + if (movie_done != 0) { + fStatus = fLiveStatus; + eWaitUntilRenderingDone(); + cFEng::Get()->QueueGameMessage(0xc3960eb9, nullptr, 0xff); + SoundPause(false, static_cast(6)); + SetSoundControlState(false, static_cast(1), ""); + if (fPlayer != nullptr) { + delete fPlayer; + } + fPlayer = nullptr; + void* buf = RCMPDecodeBuffer; + RCMP_PlayerFree(buf); + RCMPDecodeBuffer = nullptr; + ResetTimer(); + } + HandleFatalError(); + } +} + +void MoviePlayer::UpdateFunction() { + static int recurse = 0; + if (recurse == 0) { + int finished = 0; + recurse = 1; + bool MovieFinished = false; + SYNCTASK_run(); + THREAD_yield(0); + if (IsTimeForDecode__Q24RCMP9AV_PLAYER(fPlayer) && CurFrame != nullptr) { + DECODER* decoder = *reinterpret_cast(reinterpret_cast(fPlayer) + 0x64); + ReleaseFrame__Q24RCMP7DECODERPQ24RCMP5FRAME(decoder, CurFrame); + float goalFrame = *reinterpret_cast(reinterpret_cast(fPlayer) + 0x48); + CurFrame = GetFrame__Q24RCMP9AV_PLAYERf(fPlayer, goalFrame); + } else { + MovieFinished = true; + } + if (CurFrame == nullptr) { + finished = IsAudioFinished__Q24RCMP9AV_PLAYER(fPlayer); + } else { + Shape* shape = *reinterpret_cast(reinterpret_cast(CurFrame) + 4); + if (!MovieFinished) { + FillInTextureInfo__11MoviePlayerPUiP11TextureInfoPQ29RealShape5Shape( + this, reinterpret_cast(RCMPDecodeBuffer), &MovieTextureInfo, shape); + } + } + HandleFatalError(); + if (finished != 0) { + eWaitUntilRenderingDone(); + fLiveStatus = 0; + } + recurse = 0; + } +} + +bool GiveTheMoviePlayerBandwidth() { + bool result = false; + if (gMoviePlayer != nullptr) { + int status = gMoviePlayer->fStatus; + status -= 3; + result = static_cast(status) < 3u; + } + return result; +} + +void* ShapeMemoryAllocator::Alloc(unsigned int size, const EA::TagValuePair& flags) { + const char* name = ""; + int allocation_params = 0x40; + int offset = 0; + const EA::TagValuePair* p = &flags; + while (p != nullptr) { + unsigned int tag = p->mTag; + if (tag == 2) { + goto tag_2; + } + if (tag > 2) { + goto tag_gt_2; + } + if (tag == 1) { + name = static_cast(p->mValue.mPointer); + goto next; + } +next: + p = p->mNext; + continue; + + tag_gt_2: + if (tag == 3) { + int value = p->mValue.mInt; + + offset = value; + allocation_params |= (value & 0x1FFC) << 17; + goto next; + } + if (tag == 4) { + allocation_params &= ~0x40; + } + goto next; + + tag_2: + allocation_params |= (p->mValue.mInt & 0x1FFC) << 6; + goto next; + } + void* maybe = GamecubeMaybeAllocateFromCarLoader(size, name, allocation_params); + if (maybe == nullptr) { + if (TheTrackStreamer.HasMemoryPool()) { + maybe = TheTrackStreamer.AllocateUserMemory(size, "shape_mem", offset); + } else { + maybe = bMalloc(size, allocation_params); + } + } + return maybe; +} + +void ShapeMemoryAllocator::Free(void* pBlock, unsigned int size) { + if (!TheTrackStreamer.HasMemoryPool() || + (pBlock != nullptr && !TheTrackStreamer.IsUserMemory(pBlock))) { + bFree(pBlock); + } else { + TheTrackStreamer.FreeUserMemory(pBlock); + } +} + +int ShapeMemoryAllocator::AddRef() { + return ++mRefcount; +} + +int ShapeMemoryAllocator::Release() { + int ref = mRefcount - 1; + mRefcount = ref; + if (ref < 1) { + delete this; + ref = 0; + } + return ref; +} + +void* RCMP_PlayerAllocAlign(const char* name, int size, int alignment, int headersize, int type) { + if (name == nullptr || *name == '\0') { + name = "RCMP_Mem"; + } + size = size + headersize; + int alloc_params = (headersize & 0x1FFC) << 17; + void* maybe = GamecubeMaybeAllocateFromCarLoader(size, name, alloc_params | (alignment & 0x1FFC) << 6); + if (maybe == nullptr) { + if (TheTrackStreamer.HasMemoryPool()) { + return TheTrackStreamer.AllocateUserMemory(size, name, headersize); + } else { + if (alignment == 0) { + alignment = 0x80; + } + void* ptr = bMalloc(size, name, __LINE__, alloc_params | (alignment & 0x1FFC) << 6 | 0x40); + return ptr; + } + } + return maybe; +} + +void RCMP_PlayerFree(void* ptr) { + if (!TheTrackStreamer.HasMemoryPool()) { + goto bfree_label; + } + if (ptr == nullptr) { + return; + } + if (TheTrackStreamer.IsUserMemory(ptr)) { + goto freeuser_label; + } +bfree_label: + bFree(ptr); + return; +freeuser_label: + TheTrackStreamer.FreeUserMemory(ptr); +} diff --git a/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp b/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp index ccc798413..61c21ba20 100644 --- a/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp +++ b/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer.hpp @@ -5,6 +5,95 @@ #pragma once #endif +#include +#include "Speed/Indep/bWare/Inc/bMemory.hpp" + +struct FRAME; +struct AV_PLAYER { + ~AV_PLAYER(); + FRAME* GetFirstFrame(unsigned int MaxFramesOutstanding, int VideoLatencyInMs); + int Pause(); + int UnPause(); + int SetVol(unsigned int Vol); +}; +struct FRAME; + +// total size: 0x158 +struct MoviePlayer { + // total size: 0x124 + struct Settings { + unsigned int volume; // offset 0x0 + unsigned int bufferSize; // offset 0x4 + unsigned int activeController; // offset 0x8 + int type; // offset 0xC + int movieId; // offset 0x10 + bool preload; // offset 0x14 + bool sound; // offset 0x18 + bool loop; // offset 0x1C + bool pal; // offset 0x20 + char filename[256]; // offset 0x24 + }; + + Settings mSettings; // offset 0x0 + unsigned int fCurFrameNum; // offset 0x124 + int fStatus; // offset 0x128 + int fLiveStatus; // offset 0x12C + unsigned int mTicker; // offset 0x130 + bool mTickerFirstTime; // offset 0x134 + bool mMoviePaused; // offset 0x138 + int mili_seconds; // offset 0x13C + int seconds; // offset 0x140 + int minutes; // offset 0x144 + float milliseconds; // offset 0x148 + float prevMilliseconds; // offset 0x14C + AV_PLAYER* fPlayer; // offset 0x150 + FRAME* CurFrame; // offset 0x154 + + AV_PLAYER* GetPlayer() { return fPlayer; } + bool IsMoviePaused() { return mMoviePaused; } + Settings GetSettings() { return mSettings; } + int GetStatus() { return fStatus; } + int GetLiveStatus() { return fLiveStatus; } + bool IsMoviePlaying(); + + MoviePlayer(int memClass); + ~MoviePlayer(); + void Init(Settings& newSettings); + void ResetTimer(); + void Play(); + void Stop(); + void Pause(); + void UnPause(); + char* const GetMovieFilename(); + int GetMovieCategoryVolume(); + void GetFirstFrame(); + void DisplayTime(); + void Update(); + void UpdateFunction(); + unsigned int GetMillisecondsPerFrame(); + void HandleFatalError(); +}; + +extern MoviePlayer* gMoviePlayer; +extern unsigned int gMovieStartTime; + +bool MoviePlayer_Bypass(); +void MoviePlayer_Play(); +void MoviePlayer_StartUp(); +void MoviePlayer_ShutDown(); +bool GiveTheMoviePlayerBandwidth(); + +struct ShapeMemoryAllocator : public EA::Allocator::IAllocator { + int mRefcount; // offset 0x4, size 0x4 + + ShapeMemoryAllocator() : mRefcount(1) {} + ~ShapeMemoryAllocator() override {} + void* Alloc(unsigned int size, const EA::TagValuePair& flags) override; + void* Alloc(unsigned int size); + void Free(void* pBlock, unsigned int size) override; + int AddRef() override; + int Release() override; +}; #endif diff --git a/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer_new.cpp b/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer_new.cpp new file mode 100644 index 000000000..09d4352e8 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/MoviePlayer/MoviePlayer_new.cpp @@ -0,0 +1 @@ +// test diff --git a/src/Speed/Indep/Src/Frontend/RaceStarter.cpp b/src/Speed/Indep/Src/Frontend/RaceStarter.cpp index e69de29bb..c223b994b 100644 --- a/src/Speed/Indep/Src/Frontend/RaceStarter.cpp +++ b/src/Speed/Indep/Src/Frontend/RaceStarter.cpp @@ -0,0 +1,113 @@ +#include "Speed/Indep/Src/Frontend/FEJoyInput.hpp" +#include "Speed/Indep/Src/Frontend/Database/FEDatabase.hpp" +#include "Speed/Indep/Src/Misc/GameFlow.hpp" +#include "Speed/Indep/Src/World/RaceParameters.hpp" + +extern RaceParameters TheRaceParameters; +extern cFrontendDatabase *FEDatabase; + +extern int OnlineEnabled; +extern int SkipFETrackNumber; +extern eTrackDirection SkipFETrackDirection; +extern float SkipFETrafficOncoming; +extern RaceTypes SkipFERaceType; +extern int SkipFEPoint2Point; +extern int SkipFENumLaps; +extern int SkipFENumPlayerCars; +extern int SkipFENumAICars; +extern int SkipFEMaxCops; +extern int SkipFEDamageEnabled; +extern eOpponentStrength SkipFEDifficulty; +extern int SkipFEControllerConfig1; +extern int SkipFEControllerConfig2; +extern int SkipFEPovType1; + +enum POVTypes { + kPOV_Far = 0, + kPOV_Close = 1, + kPOV_Bumper = 2, + kPOV_Hood = 3, + kPOV_Drift = 4, + kPOV_Pursuit = 5, + kPOV_Pullback = 6 +}; + +extern ePlayerSettingsCameras GetPlayerCameraFromPOVType(POVTypes pov); + +class RaceStarter { + public: + static void StartRace(); + static void StartSkipFERace(); + static void StartCareerFreeRoam(); + static void SetControllerConfig(int config, JoystickPort port); +}; + +void RaceStarter::StartRace() { + TheRaceParameters.InitWithDefaults(); + TheRaceParameters.bOnlineRace = false; + TheRaceParameters.TrackNumber = 2000; + for (int i = 0; i < TheRaceParameters.NumPlayerCars; i++) { + RaceStarter::SetControllerConfig( + FEDatabase->GetPlayerSettings(i)->Config, + static_cast(FEDatabase->GetPlayersJoystickPort(i))); + } + TheGameFlowManager.UnloadFrontend(); +} + +void RaceStarter::SetControllerConfig(int config, JoystickPort port) { +} + +void RaceStarter::StartSkipFERace() { + int track_num = SkipFETrackNumber; + + if (OnlineEnabled) { + TheRaceParameters.bOnlineRace = true; + } + + TheRaceParameters.TrackNumber = track_num; + TheRaceParameters.NumPlayerCars = SkipFENumPlayerCars; + TheRaceParameters.TrackDirection = SkipFETrackDirection; + TheRaceParameters.TrafficOncoming = SkipFETrafficOncoming; + TheRaceParameters.RaceType = SkipFERaceType; + TheRaceParameters.Point2Point = SkipFEPoint2Point; + TheRaceParameters.NumLapsInRace = SkipFENumLaps; + TheRaceParameters.NumAICars = SkipFENumAICars; + TheRaceParameters.nMaxCops = SkipFEMaxCops; + TheRaceParameters.DamageEnabled = SkipFEDamageEnabled != 0; + TheRaceParameters.PlayerStartPosition[0] = static_cast(SkipFENumAICars + 1); + TheRaceParameters.PlayerStartPosition[1] = static_cast(SkipFENumAICars + 2); + TheRaceParameters.CopStrength = SkipFEDifficulty; + TheRaceParameters.OpponentStrength = SkipFEDifficulty; + + if (SkipFEControllerConfig1 != -1) { + FEDatabase->GetPlayerSettings(0)->Config = static_cast(SkipFEControllerConfig1); + } + if (SkipFEControllerConfig2 != -1) { + FEDatabase->GetPlayerSettings(0)->Config = static_cast(SkipFEControllerConfig2); + } + + FEDatabase->GetPlayerSettings(0)->CurCam = GetPlayerCameraFromPOVType(static_cast(SkipFEPovType1)); + FEDatabase->GetPlayerSettings(1)->CurCam = GetPlayerCameraFromPOVType(static_cast(SkipFEPovType1)); + + for (int kk = 0; kk < TheRaceParameters.NumPlayerCars; kk++) { + TheRaceParameters.PlayerJoyports[kk] = static_cast(FEDatabase->GetPlayersJoystickPort(kk)); + } + + TheRaceParameters.NumDriverInfo = 0; + TheRaceParameters.BoostScale[1] = 1.0f; + TheRaceParameters.BoostScale[0] = 1.0f; + + if (TheGameFlowManager.IsInFrontend()) { + TheGameFlowManager.UnloadFrontend(); + } else { + TheGameFlowManager.LoadTrack(); + } +} + +void RaceStarter::StartCareerFreeRoam() { + if (TheGameFlowManager.GetState() == GAMEFLOW_STATE_IN_FRONTEND) { + TheGameFlowManager.UnloadFrontend(); + } else { + TheGameFlowManager.LoadTrack(); + } +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Frontend/RealFontOld.hpp b/src/Speed/Indep/Src/Frontend/RealFontOld.hpp new file mode 100644 index 000000000..8da236a50 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/RealFontOld.hpp @@ -0,0 +1,95 @@ +#ifndef FRONTEND_REAL_FONT_OLD_H +#define FRONTEND_REAL_FONT_OLD_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include + +enum FontState { + FONT_COLOUR = 0, + FONT_HEIGHT = 1, + FONT_Z = 2, + FONT_W = 3, + FONT_SHAPE = 4, + FONT_UNDEF = 5, + FONT_SCALE_X = 6, + FONT_SCALE_Y = 7, + FONT_SCALE_U = 8, + FONT_SCALE_V = 9, + FONT_FILTER = 10, + FONT_DEPTHWRITE = 11, + FONT_DRAWLISTMAX = 12, + FONT_ANGLE = 13, + FONT_DANGLE = 14, + FONT_BLEND = 15, + FONT_STATEMAX = 24, +}; + +namespace RealFontOld { + +// total size: 0x10 +struct Glyph { + unsigned short mUnicode; // offset 0x0 + unsigned char mWidth; // offset 0x2 + unsigned char mHeight; // offset 0x3 + unsigned short mU; // offset 0x4 + unsigned short mV; // offset 0x6 + signed char mAdvanceY; // offset 0x8 + signed char mOffsetX; // offset 0x9 + signed char mOffsetY; // offset 0xA + unsigned char mNumKern; // offset 0xB + unsigned short mKernIndex; // offset 0xC + short mAdvanceX; // offset 0xE +}; + +// total size: 0x80 +struct Font { + char mSignature[4]; // offset 0x0 + unsigned int mSize; // offset 0x4 + unsigned short mVersion; // offset 0x8 + unsigned short mNum; // offset 0xA + int mFlags; // offset 0xC + signed char mCenterX; // offset 0x10 + signed char mCenterY; // offset 0x11 + unsigned char mAscent; // offset 0x12 + unsigned char mDescent; // offset 0x13 + int mGlyphTbl; // offset 0x14 + int mKernTbl; // offset 0x18 + int mShape; // offset 0x1C + int mStates[24]; // offset 0x20 + + static Font* Create(void* data); + static void Destroy(Font* font); + + int GetState(FontState state) const { return mStates[state]; } + int GetHeight() const { return GetState(FONT_HEIGHT); } + short GetKern(const Glyph* glyph, short prev_char) const; + + const Glyph* GetGlyph(int unicode) const { + const int GlyphSize = (mFlags & 0x40000) ? 0x10 : 0x0C; + const Glyph* GlyphTbl = reinterpret_cast(reinterpret_cast(this) + mGlyphTbl); + if (static_cast(unicode - 0x20) < mNum) { + const Glyph* ch = reinterpret_cast(reinterpret_cast(GlyphTbl) + (unicode - 0x20) * GlyphSize); + if (ch->mUnicode == static_cast(unicode)) { + return ch; + } + } + return nullptr; + } +}; + +const Glyph* BSearch(short unicode, const Glyph* table, unsigned int count, int stride = 0); + +} // namespace RealFontOld + +inline bool IsNewlineChar(short c) { + bool result = false; + if (c == '\n' || c == '^') { + result = true; + } + return result; +} + +#endif diff --git a/src/Speed/Indep/Src/Frontend/SubTitle.cpp b/src/Speed/Indep/Src/Frontend/SubTitle.cpp index e69de29bb..958fd83b2 100644 --- a/src/Speed/Indep/Src/Frontend/SubTitle.cpp +++ b/src/Speed/Indep/Src/Frontend/SubTitle.cpp @@ -0,0 +1,204 @@ +#include "SubTitle.hpp" +#include "MoviePlayer/MoviePlayer.hpp" +#include "Speed/Indep/Src/FEng/cFEng.h" +#include "Speed/Indep/Src/Misc/Timer.hpp" +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +extern int GetCurrentLanguage(); +extern int bSNPrintf(char*, int, const char*, ...); +extern void* bGetFile(const char*, int*, int); +extern void bFree(void*); +extern unsigned int bGetTicker(); +extern float bGetTickerDifference(unsigned int, unsigned int); +extern bool IsMovieTimerPrintf; +extern FEString* FEngFindString(const char*, int); +extern FEObject* FEngFindObject(const char*, unsigned int); +extern int FEPrintf(FEString*, const char*, ...); +extern unsigned int FEngHashString(const char*, ...); +extern void FEngSetScript(FEObject*, unsigned int, bool); +extern void FEngSetLanguageHash(FEString*, unsigned int); +extern void FEngGetTopLeft(FEObject*, float&, float&); +extern void FEngSetTopLeft(FEObject*, float, float); +extern const char* GetLocalizedString(unsigned int); +extern int bStrCmp(const char*, const char*); +extern unsigned int bStringHash(const char*, int); +extern int DoesStringExist(unsigned int); + +inline float FEngGetTopLeftX(FEObject* obj) { + float x, y; + FEngGetTopLeft(obj, x, y); + return x; +} + +inline float FEngGetTopLeftY(FEObject* obj) { + float x, y; + FEngGetTopLeft(obj, x, y); + return y; +} + +inline void FEngSetTopLeftY(FEObject* obj, float y) { + float x = FEngGetTopLeftX(obj); + FEngSetTopLeft(obj, x, y); +} + +SubTitler* SubTitler::gCurrentSubtitler_; + +SubTitler::SubTitler() { + next_ = 0; + data_ = nullptr; + str_ = nullptr; + str2_ = nullptr; + back_ = nullptr; + gCurrentSubtitler_ = this; + timeElapsed = 0.0f; + lastTime = 0; + mSubtitlePaused = false; +} + +SubTitler::~SubTitler() { + Unload(); + gCurrentSubtitler_ = nullptr; +} + +bool SubTitler::ShouldShowSubTitles(const char* movie_name) { + if (GetCurrentLanguage() != 0) { + return true; + } + if (!mIsTutorial) { + return false; + } + return true; +} + +void SubTitler::BeginningMovie(const char* moviename, const char* packagename) { + SetIsTutorialMovie(moviename); + if (ShouldShowSubTitles(moviename)) { + Load(moviename, packagename); + } +} + +void SubTitler::Load(const char* movieName, const char* packageName) { + char filename[64]; + + Unload(); + if (movieName != nullptr) { + bSNPrintf(filename, 64, "SUBTITLES\\%s", movieName); + data_ = static_cast< SubtitleInfo* >(bGetFile(filename, 0, 0)); + if (data_ != nullptr) { + next_ = 0; + timeElapsed = 0.0f; + lastTime = 0; + for (int i = 0; data_[i].startTime != 0xFFFF; i++) { + bPlatEndianSwap(&data_[i].startTime); + bPlatEndianSwap(&data_[i].stringHash); + } + str_ = FEngFindString(packageName, 0x599B8442); + str2_ = FEngFindString(packageName, 0x2E8DA933); + back_ = FEngFindObject(packageName, 0x8BD49BCC); + FEPrintf(str_, ""); + FEngSetScript(str_, FEngHashString("SHOWSUBS"), true); + FEPrintf(str2_, ""); + FEngSetScript(str2_, FEngHashString("SHOWSUBS"), true); + } + } +} + +void SubTitler::Unload() { + if (data_ != nullptr) { + bFree(data_); + data_ = nullptr; + } +} + +// NONMATCHING: regalloc - fmadds targets f1 directly instead of f0 + fmr +float SubTitler::GetElapsedTime() { + unsigned int timenow; + float thetime_ms; + if (!mSubtitlePaused) { + timenow = bGetTicker(); + thetime_ms = bGetTickerDifference(lastTime, timenow) * 0.001f + timeElapsed; + lastTime = timenow; + timeElapsed = thetime_ms; + } else { + lastTime = bGetTicker(); + thetime_ms = timeElapsed; + } + return thetime_ms; +} + +void SubTitler::Update(unsigned int msg) { + if (gMoviePlayer != nullptr) { + int paused = gMoviePlayer->IsMoviePaused(); + if (paused) paused = 1; + mSubtitlePaused = paused; + if (msg == 0xC98356BA) { + if (data_ != nullptr && lastTime != 0) { + float timenow = GetElapsedTime(); + if (IsMovieTimerPrintf) { + Timer timer(static_cast< int >(timenow * 4000.0f + 0.5f)); + char timer_str[100]; + timer.PrintToString(timer_str, 0); + } + unsigned short delta = static_cast< unsigned short >(timenow * 10.0f); + if (data_[next_].startTime <= delta) { + RefreshText(); + next_++; + } + } + } else if (msg == 0xC3960EB9) { + Unload(); + } + } +} + +void SubTitler::Start() { + lastTime = bGetTicker(); +} + +void SubTitler::NotifyFirstFrame() { + if (gCurrentSubtitler_ != nullptr) { + gCurrentSubtitler_->Start(); + } +} + +void SubTitler::RefreshText() { + if (!mIsTutorial) { + if (data_[next_].stringHash != 0x1A20BA && + bStrCmp("", GetLocalizedString(data_[next_].stringHash)) != 0) { + FEngSetLanguageHash(str_, data_[next_].stringHash); + FEngSetTopLeftY(back_, FEngGetTopLeftY(str_)); + } else { + FEngSetTopLeftY(back_, 6000.0f); + FEPrintf(str_, ""); + } + } else { + if (data_[next_].stringHash != 0x1A20BA) { + FEngSetScript(str_, 0x16A259, true); + FEngSetScript(str2_, 0x16A259, true); + unsigned int text_hash = bStringHash("_A", data_[next_].stringHash); + if (DoesStringExist(text_hash)) { + FEngSetLanguageHash(str_, text_hash); + FEngSetScript(str_, 0xBCBF0306, true); + } + text_hash = bStringHash("_B", data_[next_].stringHash); + if (DoesStringExist(text_hash)) { + FEngSetLanguageHash(str2_, text_hash); + FEngSetScript(str2_, 0xBCBF0306, true); + } + } else { + cFEng::Get()->QueuePackageMessage(0xDBDF2888, nullptr, nullptr); + } + } +} + +void SubTitler::SetIsTutorialMovie(const char* movieName) { + if (bStrCmp(movieName, "drag_tutorial") == 0 || + bStrCmp(movieName, "speedtrap_tutorial") == 0 || + bStrCmp(movieName, "tollbooth_tutorial") == 0 || + bStrCmp(movieName, "bounty_tutorial") == 0 || + bStrCmp(movieName, "pursuit_tutorial") == 0) { + mIsTutorial = true; + } else { + mIsTutorial = false; + } +} diff --git a/src/Speed/Indep/Src/Frontend/SubTitle.hpp b/src/Speed/Indep/Src/Frontend/SubTitle.hpp new file mode 100644 index 000000000..aa826c598 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/SubTitle.hpp @@ -0,0 +1,47 @@ +#ifndef FRONTEND_SUBTITLE_H +#define FRONTEND_SUBTITLE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include + +struct FEObject; +struct FEString; + +// total size: 0x4 +struct SubtitleInfo { + unsigned short startTime; // offset 0x0 + unsigned int stringHash; // offset 0x4 +}; + +// total size: 0x24 +struct SubTitler { + unsigned int next_; // offset 0x0 + SubtitleInfo* data_; // offset 0x4 + FEString* str_; // offset 0x8 + FEString* str2_; // offset 0xC + FEObject* back_; // offset 0x10 + float timeElapsed; // offset 0x14 + unsigned int lastTime; // offset 0x18 + bool mIsTutorial; // offset 0x1C + int mSubtitlePaused; // offset 0x20 + + static SubTitler* gCurrentSubtitler_; + + SubTitler(); + ~SubTitler(); + void BeginningMovie(const char*, const char*); + void Load(const char*, const char*); + void Unload(); + float GetElapsedTime(); + void Update(unsigned int); + void Start(); + static void NotifyFirstFrame(); + void RefreshText(); + void SetIsTutorialMovie(const char*); + bool ShouldShowSubTitles(const char*); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/UnicodeFile.cpp b/src/Speed/Indep/Src/Frontend/UnicodeFile.cpp index e69de29bb..b2b2297f9 100644 --- a/src/Speed/Indep/Src/Frontend/UnicodeFile.cpp +++ b/src/Speed/Indep/Src/Frontend/UnicodeFile.cpp @@ -0,0 +1,120 @@ +#include "UnicodeFile.hpp" + +#include "Speed/Indep/bWare/Inc/bWare.hpp" + +void bEndianSwap(short* value); +extern void* bGetFile(const char* filename, int* size, int flags); + +UnicodeFile::UnicodeFile() + : data_(nullptr) // + , next_(nullptr) // + , end_(nullptr) +{} + +UnicodeFile::~UnicodeFile() { + Unload(); +} + +bool UnicodeFile::Load(const char* filename) { + int size; + data_ = static_cast(bGetFile(filename, &size, 0)); + next_ = nullptr; + if (data_ != nullptr) { + end_ = data_ + size / 2; + if (*data_ == static_cast(0xFFFE)) { + FixEndian(); + } + FixEOLs(); + *(end_ - 1) = 0; + } + return data_ != nullptr; +} + +void UnicodeFile::Unload() { + if (data_ != nullptr) { + bFree(data_); + data_ = nullptr; + } +} + +short* UnicodeFile::First() { + short* p = data_; + if (p == nullptr) { + return nullptr; + } + next_ = p; + if (*p == static_cast(0xFEFF)) { + next_ = p + 1; + } + return next_; +} + +short* UnicodeFile::Next() { + if (data_ == nullptr || next_ == nullptr) { + return nullptr; + } + if (next_ != end_) { + while (*next_ != 0) { + next_++; + if (next_ == end_) { + goto done; + } + } + if (next_ != end_) { + while (*next_ == 0) { + next_++; + if (next_ == end_) { + goto done; + } + } + if (next_ != end_) { + return next_; + } + } + } +done: + next_ = nullptr; + return next_; +} + +void UnicodeFile::FixEndian() { + short* p = data_; + while (p != end_) { + bEndianSwap(p); + p++; + } +} + +void UnicodeFile::FixEOLs() { + short* p = data_; + while (p != end_) { + if (*p == 10 || *p == 13) { + *p = 0; + } + p++; + } +} + +void UnicodeFile::LineWrap(int maxCharacters) { + short* p = First(); + while (p != nullptr) { + int count = 0; + short* lastSpace = nullptr; + while (*p != 0) { + count++; + if (count > 1 && *p == 0x20 && *(p - 1) != 0x20) { + lastSpace = p; + } + if (count >= maxCharacters && lastSpace != nullptr) { + while (*lastSpace == 0x20) { + *lastSpace = 0; + lastSpace++; + } + count = (p - lastSpace); + lastSpace = nullptr; + } + p++; + } + p = Next(); + } +} diff --git a/src/Speed/Indep/Src/Frontend/UnicodeFile.hpp b/src/Speed/Indep/Src/Frontend/UnicodeFile.hpp new file mode 100644 index 000000000..ed229d209 --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/UnicodeFile.hpp @@ -0,0 +1,27 @@ +#ifndef FRONTEND_UNICODEFILE_H +#define FRONTEND_UNICODEFILE_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include + +struct UnicodeFile { + short* data_; // offset 0x0, size 0x4 + short* next_; // offset 0x4, size 0x4 + short* end_; // offset 0x8, size 0x4 + + UnicodeFile(); + UnicodeFile(const char* filename); + ~UnicodeFile(); + bool Load(const char* filename); + void Unload(); + short* First(); + short* Next(); + void FixEndian(); + void FixEOLs(); + void LineWrap(int maxCharacters); +}; + +#endif diff --git a/src/Speed/Indep/Src/Frontend/cFEngRender.hpp b/src/Speed/Indep/Src/Frontend/cFEngRender.hpp new file mode 100644 index 000000000..7265780db --- /dev/null +++ b/src/Speed/Indep/Src/Frontend/cFEngRender.hpp @@ -0,0 +1,67 @@ +#ifndef FRONTEND_CFENGRENDER_H +#define FRONTEND_CFENGRENDER_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +#include "Speed/Indep/bWare/Inc/bMath.hpp" + +struct FEObject; +struct FEPackage; +struct FEPackageRenderInfo; +struct FEGroup; +struct FEModel; +struct FERenderObject; +struct FEMovie; +struct FEColoredImage; +struct FEImage; +struct FEMultiImage; +struct FEString; + +struct FEClipInfo { + bVector3 normals[4]; // offset 0x0, size 0x40 + float constants[4]; // offset 0x40, size 0x10 + unsigned int flags; // offset 0x50, size 0x4 +}; + +struct RenderContext { + bMatrix4 matrix; // offset 0x0, size 0x40 + FEClipInfo clipInfo; // offset 0x40, size 0x54 + FEGroup* group; // offset 0x94, size 0x4 + FEObject* clipObject; // offset 0x98, size 0x4 + unsigned char a; // offset 0x9C, size 0x1 + unsigned char r; // offset 0x9D, size 0x1 + unsigned char g; // offset 0x9E, size 0x1 + unsigned char b; // offset 0x9F, size 0x1 +}; + +struct cFEngRender { + unsigned int Highwater; // offset 0x0, size 0x4 + RenderContext RContexts[180]; // offset 0x4, size 0x7080 + + static cFEngRender* mInstance; + static cFEngRender* Get() { return mInstance; } + + cFEngRender(); + ~cFEngRender(); + + RenderContext* GetRenderContext(unsigned short ctx); + void GenerateRenderContext(unsigned short ctx, FEObject* obj); + FEClipInfo* MakeRenderMatrix(FEObjData* data, bMatrix4* matrix, FEColor& color, int parentCtx, float scale); + void PrepForPackage(FEPackage* pkg); + void PackageFinished(FEPackage* pkg); + void AddToRenderList(FEObject* obj); + void RemoveCachedRender(FEObject* obj, FEPackageRenderInfo* info); + FERenderObject* FindCachedRender(FEObject* obj); + void RenderModel(FEModel* model, FERenderObject* renderObj); + void RenderMovie(FEMovie* movie, FERenderObject* cached, FEPackageRenderInfo* pkg_render_info); + void RenderImage(FEImage* image, FERenderObject* cached, FEPackageRenderInfo* pkg_render_info); + void RenderCBVImage(FEColoredImage* image, FERenderObject* cached, FEPackageRenderInfo* pkg_render_info); + void RenderMultiImage(FEMultiImage* image, FERenderObject* cached, FEPackageRenderInfo* pkg_render_info); + void RenderString(FEString* string, FERenderObject* cached, FEPackageRenderInfo* pkg_render_info); + void RenderObject(FEObject* obj, FEPackageRenderInfo* pkg_render_info); + FERenderObject* CreateCachedRender(FEObject* object, struct TextureInfo* texture_info); +}; + +#endif diff --git a/src/Speed/Indep/Src/Gameplay/GIcon.h b/src/Speed/Indep/Src/Gameplay/GIcon.h index a6438b96c..04d52b591 100644 --- a/src/Speed/Indep/Src/Gameplay/GIcon.h +++ b/src/Speed/Indep/Src/Gameplay/GIcon.h @@ -5,6 +5,51 @@ #pragma once #endif +#include "Speed/Indep/Libs/Support/Utility/UMath.h" +struct EmitterGroup; +struct WorldModel; + +struct GIcon { + enum Type { + kType_Invalid = 0, kType_RaceSprint = 1, kType_RaceCircuit = 2, + kType_RaceDrag = 3, kType_RaceKnockout = 4, kType_RaceTollbooth = 5, + kType_RaceSpeedtrap = 6, kType_RaceRival = 7, kType_GateSafehouse = 8, + kType_GateCarLot = 9, kType_GateCustomShop = 10, kType_HidingSpot = 11, + kType_PursuitBreaker = 12, kType_SpeedTrap = 13, kType_SpeedTrapInRace = 14, + kType_AreaUnlock = 15, kType_Checkpoint = 16, kType_Count = 17, + }; + struct EffectInfo { unsigned int mType; unsigned int mModelHash; unsigned int mParticleHash; }; + unsigned short mType; + unsigned short mFlags; + short mSectionID; + short mCombSectionID; + WorldModel* mModel; + EmitterGroup* mEmitter; + UMath::Vector3 mPosition; + unsigned short mRotation; + unsigned short mPad; + static EffectInfo kEffectInfo[]; + void SetFlag(unsigned int mask); + void ClearFlag(unsigned int mask); + bool IsFlagSet(unsigned int mask) const; + bool IsFlagClear(unsigned int mask) const { return (mFlags & mask) == 0; } + void SetGPSing() { SetFlag(0x80); } + void ClearGPSing() { ClearFlag(0x80); } + bool GetIsGPSing() const { return IsFlagSet(0x80); } + Type GetType() const { return static_cast< Type >(mType); } + int GetSectionID() const { return mSectionID; } + int GetCombinedSectionID() const { return mCombSectionID; } + const UMath::Vector3& GetPosition() const { return mPosition; } + void GetPosition2D(bVector2& outPos) { outPos.x = mPosition.x; outPos.y = mPosition.y; } + GIcon(Type type, const UMath::Vector3& pos, float rotDeg); + ~GIcon(); + void Spawn(); + void Unspawn(); + void FindSection(); + void SnapToGround(); + void Enable(); + void Disable(); +}; #endif diff --git a/src/Speed/Indep/Src/Gameplay/GManager.h b/src/Speed/Indep/Src/Gameplay/GManager.h index f1a37ee98..832fa185e 100644 --- a/src/Speed/Indep/Src/Gameplay/GManager.h +++ b/src/Speed/Indep/Src/Gameplay/GManager.h @@ -76,6 +76,9 @@ class GManager : public UTL::COM::Object, public IVehicleCache { void TrackValue(const char *valueName, float value); void IncValue(const char *valueName); float GetValue(const char *valueName); + float GetValue(unsigned int valueKey); + float GetBestValue(const char *valueName); + float GetBestValue(unsigned int valueKey); void RegisterInstance(GRuntimeInstance *instance); void UnregisterInstance(GRuntimeInstance *instance); @@ -170,6 +173,9 @@ class GManager : public UTL::COM::Object, public IVehicleCache { unsigned int SaveSMSInfo(int *saveInfo); void LoadSMSInfo(int *loadInfo, unsigned int count); + void SaveGameplayData(unsigned char *buf, unsigned int size); + void LoadGameplayData(unsigned char *buf, unsigned int size); + void ResetAllGameplayData(); bool GetHasPendingSMS() const; bool CanPlaySMS() const; void AddSMS(int smsID); @@ -193,6 +199,19 @@ class GManager : public UTL::COM::Object, public IVehicleCache { return mStartFreeRoamPursuit; } + void SetStartingFreeRoamFromSafeHouse() { + mStartFreeRoamFromSafeHouse = true; + } + + void OverrideFreeRoamStartMarker(unsigned int markerKey) { + mOverrideFreeRoamStartMarker = markerKey; + } + + void QueueFreeRoamPursuit(float minHeat) { + mStartFreeRoamPursuit = true; + mQueuedPursuitMinHeat = minHeat; + } + void TrackValue(const char *valueName, int value) { TrackValue(valueName, static_cast(value)); } @@ -200,8 +219,10 @@ class GManager : public UTL::COM::Object, public IVehicleCache { private: GManager(const char *vaultPackName); + public: static GManager *mObj; + private: const char *mVaultPackFileName; // offset 0x1C, size 0x4 bFile *mVaultPackFile; // offset 0x20, size 0x4 unsigned int mVaultCount; // offset 0x24, size 0x4 diff --git a/src/Speed/Indep/Src/Gameplay/GMilestone.h b/src/Speed/Indep/Src/Gameplay/GMilestone.h index 4de0c87cf..30c7220c0 100644 --- a/src/Speed/Indep/Src/Gameplay/GMilestone.h +++ b/src/Speed/Indep/Src/Gameplay/GMilestone.h @@ -14,6 +14,31 @@ struct MilestoneTypeInfo { // total size: 0x14 class GMilestone { + public: + bool GetIsLocked() const { return mState == 1; } + bool GetIsAvailable() const { return mState == 2; } + bool GetIsDonePendingEscape() const { return mState == 3; } + bool GetIsAwarded() const { return mState == 4; } + unsigned int GetTypeKey() const { return mTypeKey; } + unsigned int GetChallengeKey() const { return mChallengeKey; } + unsigned int GetBinNumber() const { return mBinNumber; } + float GetRequiredValue() const { return mRequiredValue; } + float GetRecordedPassValue() const { return mRecordedValue; } + + GMilestone(); + float GetCurrentValue() const; + float GetBounty() const; + int GetLocalizationTag() const; + unsigned int GetJumpMarkerKey() const; + void DebugForceComplete(); + void Init(unsigned int challengeKey); + void Reset(); + void Unlock(); + void SetGoal(float required); + bool ValueMeetsGoal(float value); + void NotifyProgress(float value); + void NotifyPursuitOver(bool escaped); + private: unsigned int mTypeKey; // offset 0x0, size 0x4 unsigned int mChallengeKey; // offset 0x4, size 0x4 diff --git a/src/Speed/Indep/Src/Gameplay/GRaceDatabase.h b/src/Speed/Indep/Src/Gameplay/GRaceDatabase.h index 0cc743d17..912cbacc3 100644 --- a/src/Speed/Indep/Src/Gameplay/GRaceDatabase.h +++ b/src/Speed/Indep/Src/Gameplay/GRaceDatabase.h @@ -38,7 +38,9 @@ class GRaceBin { GVault *GetChildVault() const; + GRaceBin* GetBin(unsigned int index); int GetBinNumber() const; + int GetBinNumber(int index); int GetBossReputation() const; @@ -104,6 +106,8 @@ class GRaceBin { BinStats mStats; // offset 0x18, size 0x4 }; +class GActivity; + // total size: 0x40 class GRaceDatabase { public: @@ -119,11 +123,16 @@ class GRaceDatabase { static void Init(); GRaceCustom *GetStartupRace(); + void ClearStartupRace(); void SetStartupRace(GRaceCustom *custom, Context context); void FreeCustomRace(GRaceCustom *custom); GRaceParameters *GetRaceFromHash(unsigned int hash); + GRaceParameters *GetRaceFromActivity(GActivity *activity); GRaceCustom *AllocCustomRace(GRaceParameters *parms); + unsigned int GetRaceCount() const; + GRaceParameters *GetRaceParameters(unsigned int index) const; + static GRaceDatabase &Get() { return *mObj; } @@ -136,6 +145,43 @@ class GRaceDatabase { return GetRaceFromHash(Attrib::StringHash32(name)); } + bool IsCareerRaceComplete(unsigned int eventHash) { + return CheckRaceScoreFlags(eventHash, kCompleted_ContextCareer); + } + + bool IsQuickRaceComplete(unsigned int eventHash) { + return CheckRaceScoreFlags(eventHash, kCompleted_ContextQuickRace); + } + + const char *GetBurgerKingRace() const { return "19.8.31"; } + + const char *GetDDayStartRace() const { + return sDDayRaces[0]; + } + + const char *GetDDayEndRace() const { + return sDDayRaces[7]; + } + + const char *GetFinalBossRace() const { + return sDDayRaces[4]; + } + + bool CheckRaceScoreFlags(unsigned int eventHash, ScoreFlags mask); + const char *GetNextDDayRace(); + struct GRaceSaveInfo* GetScoreInfo(unsigned int eventHash); + struct GRaceSaveInfo *GetScoreInfo(); + unsigned int GetScoreInfoCount(); + void LoadBestScores(struct GRaceSaveInfo *entries, unsigned int count); + void SimulateDDayComplete(); + void ClearRaceScores(); + + unsigned int GetBinCount(); + GRaceBin* GetBin(unsigned int index); + GRaceBin* GetBinNumber(int number); + + static const char sDDayRaces[8][5]; + private: unsigned int mRaceCountStatic; // offset 0x0, size 0x4 unsigned int mRaceCountDynamic; // offset 0x4, size 0x4 @@ -151,6 +197,7 @@ class GRaceDatabase { unsigned int *mInitialUnlockHash; // offset 0x38, size 0x4 struct GRaceSaveInfo *mRaceScoreInfo; // offset 0x3C, size 0x4 + public: static GRaceDatabase *mObj; }; diff --git a/src/Speed/Indep/Src/Gameplay/GRaceStatus.h b/src/Speed/Indep/Src/Gameplay/GRaceStatus.h index efbb40541..186ea44ae 100644 --- a/src/Speed/Indep/Src/Gameplay/GRaceStatus.h +++ b/src/Speed/Indep/Src/Gameplay/GRaceStatus.h @@ -29,6 +29,27 @@ struct GRacerInfo { return mPctRaceComplete; } + ISimable *GetSimable() const { + return ISimable::FindInstance(mhSimable); + } + float GetFinishingSpeed() const { return mFinishingSpeed; } + float GetPointTotal() const { return mPointTotal; } +#ifndef EA_BUILD_A124 + float GetSplitTime(int split) const { return mSplitTimes[split]; } + int GetSplitRanking(int split) const { return mSplitRankings[split]; } + const float *GetSplitTimes() const { return mSplitTimes; } + const int *GetSplitRankings() const { return mSplitRankings; } +#endif + const GTimer &GetRaceTimer() const { return mRaceTimer; } + + bool GetIsKnockedOut() const { return mKnockedOut; } + bool GetIsTotalled() const { return mTotalled; } + bool GetIsEngineBlown() const { return mEngineBlown; } + bool GetIsBusted() const { return mBusted; } + bool IsFinishedRacing() const { return mFinishedRacing; } + const char *GetName() const { return mName; } + int GetRanking() const { return mRanking; } + private: HSIMABLE mhSimable; // offset 0x0, size 0x4 GCharacter *mGameCharacter; // offset 0x4, size 0x4 @@ -83,6 +104,22 @@ struct GRacerInfo { DECLARE_CONTAINER_TYPE(ID_GRaceStatusTriggerList); +enum CopDensity { + kRaceCops_Off = 0, + kRaceCops_Light = 1, + kRaceCops_Medium = 2, + kRaceCops_Heavy = 3, + kRaceCops_NumDensities = 4, +}; + +enum Region { + kRaceRegion_None = -1, + kRaceRegion_College = 0, + kRaceRegion_Coastal = 1, + kRaceRegion_City = 2, + kRaceRegion_NumRegions = 3, +}; + // total size: 0x14 class GRaceParameters { public: @@ -231,6 +268,7 @@ class GRaceParameters { GRace::Type GetRaceType() const; // enum Region GetRegion() const; + Region GetRegion() const; void ExtractPosition(Attrib::Gen::gameplay &collection, UMath::Vector3 &pos) const; @@ -238,7 +276,7 @@ class GRaceParameters { unsigned int GetEventHash() const; - // bool GetIsAvailable(enum Context context) const; + bool GetIsAvailable(GRace::Context context) const; bool GetIsSunsetRace() const; @@ -250,7 +288,7 @@ class GRaceParameters { // enum Difficulty GetDifficulty() const; - // enum CopDensity GetCopDensity() const; + CopDensity GetCopDensity() const; bool GetCanBeReversed() const; @@ -468,14 +506,38 @@ class GRaceStatus : public UTL::COM::Object, public IVehicleCache { return mRaceParms ? mRaceParms->GetRaceType() : GRace::kRaceType_None; } + float GetRaceLength() const { + return fRaceLength; + } + + int GetNumTollbooths() const { + return mNumTollbooths; + } + + int GetNumSpeedTraps() const { + return nSpeedTraps; + } + static bool IsChallengeRace() { return Exists() && Get().GetRaceType() == GRace::kRaceType_Challenge; } - PlayMode GetPlayMode() { + static bool IsDragRace() { + return Exists() && Get().GetRaceType() == GRace::kRaceType_Drag; + } + + static bool IsTollboothRace() { + return Exists() && Get().GetRaceType() == GRace::kRaceType_Tollbooth; + } + + PlayMode GetPlayMode() const { return mPlayMode; } + bool GetIsTimeLimited() const { + return mRaceParms && mRaceParms->GetTimeLimit() > 0.0f; + } + unsigned int GetTrafficPattern() const { return mTrafficPattern; } diff --git a/src/Speed/Indep/Src/Gameplay/GSpeedTrap.h b/src/Speed/Indep/Src/Gameplay/GSpeedTrap.h index 77dc76771..89e7375f9 100644 --- a/src/Speed/Indep/Src/Gameplay/GSpeedTrap.h +++ b/src/Speed/Indep/Src/Gameplay/GSpeedTrap.h @@ -5,15 +5,37 @@ #pragma once #endif -// total size: 0x14 -class GSpeedTrap { - private: - unsigned short mFlags; // offset 0x0, size 0x2 - unsigned short mBinNumber; // offset 0x2, size 0x2 - unsigned int mSpeedTrapKey; // offset 0x4, size 0x4 - unsigned int mCameraMarkerKey; // offset 0x8, size 0x4 - float mRequiredValue; // offset 0xC, size 0x4 - float mRecordedValue; // offset 0x10, size 0x4 +struct GSpeedTrap { + enum Flags { kFlag_Unlocked = 1, kFlag_Active = 2, kFlag_Completed = 4, kFlag_KnockedOver = 8, }; + unsigned short mFlags; + unsigned short mBinNumber; + unsigned int mSpeedTrapKey; + unsigned int mCameraMarkerKey; + float mRequiredValue; + float mRecordedValue; + void SetFlag(unsigned int mask) { mFlags |= mask; } + void ClearFlag(unsigned int mask) { mFlags &= ~mask; } + bool IsFlagSet(unsigned int mask) const; + bool IsFlagClear(unsigned int mask) const; + bool GetIsLocked() const { return IsFlagClear(kFlag_Unlocked); } + bool GetIsUnlocked() const { return IsFlagSet(kFlag_Unlocked); } + bool GetIsCompleted() const { return IsFlagSet(kFlag_Completed); } + bool GetIsKnockedOver() const { return IsFlagSet(kFlag_KnockedOver); } + bool GetIsActive() const { return IsFlagSet(kFlag_Active); } + unsigned int GetSpeedTrapKey() const { return mSpeedTrapKey; } + unsigned int GetBinNumber() const { return mBinNumber; } + float GetTriggerSpeed() const { return mRequiredValue; } + float GetRecordedPassSpeed() const { return mRecordedValue; } + GSpeedTrap(); + float GetBounty() const; + int GetLocalizationTag() const; + unsigned int GetJumpMarkerKey() const; + void Init(unsigned int trapKey); + void Reset(); + void Unlock(); + void Activate(); + void NotifyTriggered(float value); + void DebugForceComplete(); }; #endif diff --git a/src/Speed/Indep/Src/Gameplay/GTimer.h b/src/Speed/Indep/Src/Gameplay/GTimer.h index 90169c489..ed7313ecf 100644 --- a/src/Speed/Indep/Src/Gameplay/GTimer.h +++ b/src/Speed/Indep/Src/Gameplay/GTimer.h @@ -8,6 +8,7 @@ // total size: 0xC class GTimer { public: + float GetTime() const; private: float mStartTime; // offset 0x0, size 0x4 float mTotalTime; // offset 0x4, size 0x4 diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/audiosystem.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/audiosystem.h index 111fe96b5..1243ba816 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/audiosystem.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/audiosystem.h @@ -23,6 +23,10 @@ struct audiosystem : Instance { Attrib::StringKey EVTPath; // offset 0x30, size 0x10 }; + void *operator new(size_t bytes) { + return Attrib::Alloc(bytes, "audiosystem"); + } + void operator delete(void *ptr, size_t bytes) { Attrib::Free(ptr, bytes, "audiosystem"); } @@ -36,6 +40,10 @@ struct audiosystem : Instance { this->SetDefaultLayout(sizeof(_LayoutStruct)); } + audiosystem(const RefSpec &refspec, unsigned int msgPort, UTL::COM::IUnknown *owner) : Instance(refspec, msgPort, owner) { + this->SetDefaultLayout(sizeof(_LayoutStruct)); + } + ~audiosystem() {} void Change(const Collection *c) { @@ -46,6 +54,10 @@ struct audiosystem : Instance { Change(FindCollection(ClassKey(), collectionkey)); } + void Change(const RefSpec &refspec) { + Instance::Change(refspec); + } + static Key ClassKey() { return 0xd3c18f03; } diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/engine.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/engine.h index 74336534a..697978a7e 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/engine.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/engine.h @@ -40,6 +40,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() {} void Change(const Collection *c) { diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h index 017e617d2..5b724aa63 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/frontend.h @@ -12,6 +12,76 @@ #include "Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h" #include "Speed/Indep/Tools/AttribSys/Runtime/Common/AttribPrivate.h" +typedef unsigned int type_bStringHash; +typedef unsigned int reflection_typedef_eFEPartUpgradeLevels; + +struct FECarPartInfo { + reflection_typedef_eFEPartUpgradeLevels Level; + float Rep; + float Cost; +}; + +enum eUnlockableEntity { + UNLOCKABLE_THING_UNKNOWN = 0, + UNLOCKABLE_THING_CUSTOMIZE_PARTS = 1, + UNLOCKABLE_THING_CUSTOMIZE_PERFORMANCE = 2, + UNLOCKABLE_THING_CUSTOMIZE_VISUAL = 3, + UNLOCKABLE_THING_PUT_TIRES = 4, + UNLOCKABLE_THING_PUT_BRAKES = 5, + UNLOCKABLE_THING_PUT_CHASSIS = 6, + UNLOCKABLE_THING_PUT_TRANSMISSION = 7, + UNLOCKABLE_THING_PUT_ENGINE = 8, + UNLOCKABLE_THING_PUT_INDUCTION = 9, + UNLOCKABLE_THING_PUT_NOS = 10, + UNLOCKABLE_THING_BODY_KIT = 11, + UNLOCKABLE_THING_SPOILERS = 12, + UNLOCKABLE_THING_RIM_BRANDS = 13, + UNLOCKABLE_THING_HOODS = 14, + UNLOCKABLE_THING_ROOF_SCOOPS = 15, + UNLOCKABLE_THING_LICENSE_PLATE = 16, + UNLOCKABLE_THING_CUSTOM_HUD = 17, + UNLOCKABLE_THING_WINDOW_TINT = 18, + UNLOCKABLE_THING_PAINT_METALLIC = 19, + UNLOCKABLE_THING_PAINT_PEARL = 20, + UNLOCKABLE_THING_PAINT_GLOSS = 21, + UNLOCKABLE_THING_PAINT_STOCK = 22, + UNLOCKABLE_THING_PAINTABLE_BODY = 23, + UNLOCKABLE_THING_PAINTABLE_RIMS = 24, + UNLOCKABLE_THING_RIM_BRAND_5_ZIGEN = 25, + UNLOCKABLE_THING_RIM_BRAND_ADR = 26, + UNLOCKABLE_THING_RIM_BRAND_BBS = 27, + UNLOCKABLE_THING_RIM_BRAND_ENKEI = 28, + UNLOCKABLE_THING_RIM_BRAND_KONIG = 29, + UNLOCKABLE_THING_RIM_BRAND_LOWENHART = 30, + UNLOCKABLE_THING_RIM_BRAND_RACING_HART = 31, + UNLOCKABLE_THING_RIM_BRAND_OZ = 32, + UNLOCKABLE_THING_RIM_BRAND_VOLK = 33, + UNLOCKABLE_THING_RIM_BRAND_ROJA = 34, + UNLOCKABLE_VINYLS_GROUP_FLAME = 35, + UNLOCKABLE_VINYLS_GROUP_TRIBAL = 36, + UNLOCKABLE_VINYLS_GROUP_STRIPE = 37, + UNLOCKABLE_VINYLS_GROUP_RACING_FLAG = 38, + UNLOCKABLE_VINYLS_GROUP_NATIONAL_FLAG = 39, + UNLOCKABLE_VINYLS_GROUP_BODY = 40, + UNLOCKABLE_VINYLS_GROUP_UNIQUE = 41, + UNLOCKABLE_VINYLS_GROUP_CONTEST = 42, + UNLOCKABLE_DECAL_NUMBERS = 43, + UNLOCKABLE_DECAL_WINDSHIELD = 44, + UNLOCKABLE_DECAL_REAR_WINDOW = 45, + UNLOCKABLE_DECAL_LEFT_DOOR = 46, + UNLOCKABLE_DECAL_RIGHT_DOOR = 47, + UNLOCKABLE_DECAL_LEFT_QP = 48, + UNLOCKABLE_DECAL_RIGHT_QP = 49, + UNLOCKABLE_DECAL_HOOD = 50, + UNLOCKABLE_DECAL_SLOT_1 = 51, + UNLOCKABLE_DECAL_SLOT_2 = 52, + UNLOCKABLE_DECAL_SLOT_3 = 53, + UNLOCKABLE_DECAL_SLOT_4 = 54, + UNLOCKABLE_DECAL_SLOT_5 = 55, + UNLOCKABLE_DECAL_SLOT_6 = 56, + NUM_UNLOCKABLES = 57, +}; + namespace Attrib { namespace Gen { diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/music.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/music.h index 6ad5df27f..70bcee6ee 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/music.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/music.h @@ -62,6 +62,10 @@ struct music : Instance { Instance::Change(refspec); } + void ChangeWithDefault(const RefSpec &refspec) { + Instance::ChangeWithDefault(refspec); + } + static Key ClassKey() { return 0x565465f8; } diff --git a/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h b/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h index 3e03054bf..efb3fb466 100644 --- a/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h +++ b/src/Speed/Indep/Src/Generated/AttribSys/Classes/pvehicle.h @@ -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) { diff --git a/src/Speed/Indep/Src/Generated/Events/EQuitDemo.hpp b/src/Speed/Indep/Src/Generated/Events/EQuitDemo.hpp index d4faf2ee2..71dbc5774 100644 --- a/src/Speed/Indep/Src/Generated/Events/EQuitDemo.hpp +++ b/src/Speed/Indep/Src/Generated/Events/EQuitDemo.hpp @@ -6,6 +6,7 @@ #endif #include "Speed/Indep/Src/Main/Event.h" +#include "Speed/Indep/Src/Misc/DemoDisc.hpp" // total size: 0xc class EQuitDemo : public Event { diff --git a/src/Speed/Indep/Src/Generated/Events/EShowResults.hpp b/src/Speed/Indep/Src/Generated/Events/EShowResults.hpp index 7c2d7f2cf..b89ef7b27 100644 --- a/src/Speed/Indep/Src/Generated/Events/EShowResults.hpp +++ b/src/Speed/Indep/Src/Generated/Events/EShowResults.hpp @@ -7,6 +7,12 @@ #include "Speed/Indep/Src/Main/Event.h" +enum FERESULTTYPE { + FERESULTTYPE_RACE = 0, + FERESULTTYPE_PURSUIT = 1, + FERESULTTYPE_SPEEDTRAP = 2, +}; + // total size: 0x10 class EShowResults : public Event { public: diff --git a/src/Speed/Indep/Src/Input/ActionQueue.h b/src/Speed/Indep/Src/Input/ActionQueue.h index 2e5892ee3..702fe0dab 100644 --- a/src/Speed/Indep/Src/Input/ActionQueue.h +++ b/src/Speed/Indep/Src/Input/ActionQueue.h @@ -43,28 +43,21 @@ class ActionQueue : public UTL::Collections::Listable { // void operator delete(void *mem, void *ptr) {} - // const char *GetName() {} + const char *GetName() const { return mQueueName; } + int GetPort() const { return mPort; } // bool IsRequired() const {} - // void SetRequired(bool b) {} + void SetRequired(bool b) { mRequired = b; } - // bool IsValid() const {} - - // int GetPort() const {} - - // bool IsActive() const {} - - // int Size() {} - - // Timer LastActionTime() const {} - - // Timer ActivationTime() const {} + bool IsConnected() const; unsigned int GetConfig() const { return mConfig; } + Timer LastActionTime() const { return mActionTime; } + private: UCircularQueue fQueue; // offset 0x4, size 0x268 int mPort; // offset 0x26C, size 0x4 diff --git a/src/Speed/Indep/Src/Input/IOModule.h b/src/Speed/Indep/Src/Input/IOModule.h index 5bd8696b5..845f33c2a 100644 --- a/src/Speed/Indep/Src/Input/IOModule.h +++ b/src/Speed/Indep/Src/Input/IOModule.h @@ -46,6 +46,7 @@ class IOModule { // bool IsUnplugged(int port) {} // int GetNumDevices() {} + int GetNumDevices() { return fNumDevices; } InputDevice *GetDevice(int i) { return fDevices[i]; diff --git a/src/Speed/Indep/Src/Interfaces/IFengHud.h b/src/Speed/Indep/Src/Interfaces/IFengHud.h index 8611948d3..29de402d1 100644 --- a/src/Speed/Indep/Src/Interfaces/IFengHud.h +++ b/src/Speed/Indep/Src/Interfaces/IFengHud.h @@ -6,7 +6,9 @@ #endif #include "Speed/Indep/Libs/Support/Utility/UCOM.h" +#include "Speed/Indep/Libs/Support/Utility/UCrc.h" #include "Speed/Indep/Libs/Support/Utility/UListable.h" +#include "Speed/Indep/Src/Physics/PhysicsTypes.h" class IHud : public UTL::COM::IUnknown, public UTL::Collections::Listable { public: @@ -16,11 +18,12 @@ class IHud : public UTL::COM::IUnknown, public UTL::Collections::Listable +int RaiseToPower(int power) { + if (power == 0) return 1; + return RaiseToPower< BASE >(power - 1) * BASE; +} + +template< typename T, int BASE, unsigned int PRECISION > +struct FixedPoint { + T mWord; // offset 0x0 + + static int GetScale() { + static const unsigned int scale = RaiseToPower< BASE >(PRECISION - 1) * BASE; + return scale; + } + + operator float() { + return static_cast< float >(mWord) / GetScale(); + } + + FixedPoint(float val) { + mWord = static_cast< T >(val * GetScale()); + } +}; + +#endif diff --git a/src/Speed/Indep/Src/Misc/GameFlow.hpp b/src/Speed/Indep/Src/Misc/GameFlow.hpp index a1034b17e..48c531386 100644 --- a/src/Speed/Indep/Src/Misc/GameFlow.hpp +++ b/src/Speed/Indep/Src/Misc/GameFlow.hpp @@ -22,6 +22,8 @@ enum GameFlowState { class GameFlowManager { public: void LoadFrontend(); + void UnloadFrontend(); + void LoadTrack(); void Service(); void CheckForDemoDiscTimeout(); diff --git a/src/Speed/Indep/Src/Misc/MD5.cpp b/src/Speed/Indep/Src/Misc/MD5.cpp index e69de29bb..8858f0b5d 100644 --- a/src/Speed/Indep/Src/Misc/MD5.cpp +++ b/src/Speed/Indep/Src/Misc/MD5.cpp @@ -0,0 +1,10 @@ +#include "Speed/Indep/Src/Misc/MD5.hpp" + +void MD5::Reset() { + uCount = 0; + uRegs[0] = 0x67452301; + uRegs[1] = 0xEFCDAB89; + uRegs[2] = 0x98BADCFE; + uRegs[3] = 0x10325476; + computed = false; +} \ No newline at end of file diff --git a/src/Speed/Indep/Src/Misc/MD5.hpp b/src/Speed/Indep/Src/Misc/MD5.hpp index dcb20e28b..af78acb07 100644 --- a/src/Speed/Indep/Src/Misc/MD5.hpp +++ b/src/Speed/Indep/Src/Misc/MD5.hpp @@ -5,6 +5,26 @@ #pragma once #endif +// total size: 0x90 +class MD5 { + public: + MD5() { /* vtable set by compiler */ } + virtual ~MD5() {} + void Reset(); + void Update(const void *buffer, int length); + void *GetRaw(); + const char *GetString(); + private: + void _Transform(); + void _Final(); + + unsigned int uCount; // offset 0x0, size 0x4 + unsigned int uRegs[4]; // offset 0x4, size 0x10 + unsigned char strData[64]; // offset 0x14, size 0x40 + bool computed; // offset 0x54, size 0x1 + unsigned char rawMD5[16]; // offset 0x58, size 0x10 + unsigned char strMD5[33]; // offset 0x68, size 0x21 +}; #endif diff --git a/src/Speed/Indep/Src/Misc/ResourceLoader.hpp b/src/Speed/Indep/Src/Misc/ResourceLoader.hpp index 512339d9b..aa5499dd6 100644 --- a/src/Speed/Indep/Src/Misc/ResourceLoader.hpp +++ b/src/Speed/Indep/Src/Misc/ResourceLoader.hpp @@ -78,8 +78,14 @@ class ResourceFile : public bTNode { // int GetAddress() {} // void BeginLoading(ASYNCFILE_CALLBACK *callback, int callback_param) {} + void BeginLoading(void (*callback)(int), int callback_param) { + BeginLoading(reinterpret_cast(callback), + reinterpret_cast(callback_param)); + } - // void BeginLoading() {} + void BeginLoading() { + BeginLoading(static_cast(nullptr), nullptr); + } int IsFinishedLoading() { return LoadingFinishedFlag; @@ -142,8 +148,15 @@ void EndianSwapChunkHeadersRecursive(bChunk *first_chunk, bChunk *last_chunk); int ServiceResourceLoading(); ResourceFile *CreateResourceFile(const char *filename, ResourceFileType type, int flags, int flag_offset, int file_size); +ResourceFile *LoadResourceFile(const char *filename, ResourceFileType type, int flags, + void (*callback)(void *), void *callback_param, + int file_offset, int file_size); void UnloadResourceFile(ResourceFile *resource_file); +inline ResourceFile *LoadResourceFile(const char *filename, ResourceFileType type, int flags) { + return LoadResourceFile(filename, type, flags, nullptr, nullptr, 0, 0); +} + extern int ChunkMovementOffset; // size: 0x4 inline bool AreChunksBeingMoved() { diff --git a/src/Speed/Indep/Src/Misc/Table.hpp b/src/Speed/Indep/Src/Misc/Table.hpp index 6672953f1..7daa9f40d 100644 --- a/src/Speed/Indep/Src/Misc/Table.hpp +++ b/src/Speed/Indep/Src/Misc/Table.hpp @@ -236,7 +236,9 @@ struct PidError { return gFastMem.Alloc(size, name); } - // void operator delete(void *mem, const char *name) {} +#ifdef _MSC_VER + void operator delete(void *mem, const char *name) {} +#endif // void operator delete(void *mem, unsigned int size, const char *name) {} diff --git a/src/Speed/Indep/Src/Misc/Timer.hpp b/src/Speed/Indep/Src/Misc/Timer.hpp index 634d10422..112636aa7 100644 --- a/src/Speed/Indep/Src/Misc/Timer.hpp +++ b/src/Speed/Indep/Src/Misc/Timer.hpp @@ -8,12 +8,10 @@ // total size: 0x4 class Timer { public: - Timer() { - this->PackedTime = 0; - } + Timer() {} Timer(float seconds) { - this->PackedTime = static_cast(seconds * 4000); + SetTime(seconds); } Timer(int packed_time) { @@ -22,7 +20,9 @@ class Timer { int operator=(const Timer &t) const {} - int operator!=(const Timer &t) const {} + int operator!=(const Timer &t) const { + return PackedTime != t.PackedTime; + } Timer &operator=(const Timer &t) { this->PackedTime = t.PackedTime; @@ -47,6 +47,10 @@ class Timer { Timer operator+(const Timer &t) const {} + Timer operator-(const Timer &t) const { + return Timer(PackedTime - t.PackedTime); + } + Timer operator*(const Timer &t) const {} Timer &operator+=(const Timer &t) {} @@ -59,13 +63,15 @@ class Timer { this->PackedTime = 0; } - void ResetHigh() {} + void ResetHigh() { PackedTime = 0x7fffffff; } - void UnSet() {} + void UnSet() { PackedTime = 0; } - int IsSet() {} + int IsSet() { return PackedTime != 0 && PackedTime != 0x7fffffff; } - void SetTime(float seconds) {} + void SetTime(float seconds) { + PackedTime = static_cast(seconds * 4000.0f + 0.5f); + } float GetSeconds() { return this->PackedTime / 4000.0f; @@ -75,7 +81,9 @@ class Timer { return this->PackedTime; } - void SetPackedTime(int packed_time) {} + void SetPackedTime(int packed_time) { PackedTime = packed_time; } + + void PrintToString(char*, int); private: int PackedTime; // offset 0x0, size 0x4 diff --git a/src/Speed/Indep/Src/Physics/PhysicsInfo.hpp b/src/Speed/Indep/Src/Physics/PhysicsInfo.hpp index de214a52b..f2737461a 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsInfo.hpp +++ b/src/Speed/Indep/Src/Physics/PhysicsInfo.hpp @@ -59,6 +59,7 @@ void Init(); float AerodynamicDownforce(const Attrib::Gen::chassis &chassis, const float speed); float EngineInertia(const Attrib::Gen::engine &engine, const bool loaded); eInductionType InductionType(const Attrib::Gen::induction &induction); +eInductionType InductionType(const Attrib::Gen::pvehicle &pvehicle); bool HasNos(const Attrib::Gen::pvehicle &pvehicle); bool HasRunflatTires(const Attrib::Gen::pvehicle &pvehicle); float NosBoost(const Attrib::Gen::nos &nos, const Tunings *tunings); @@ -78,6 +79,7 @@ bool ShiftPoints(const Attrib::Gen::transmission &transmission, const Attrib::Ge float *shift_up, float *shift_down, unsigned int numpts); 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(const Attrib::Gen::pvehicle &pvehicle, Performance &perf); bool EstimatePerformance(Performance &perf); bool ComputeAccelerationTable(const Attrib::Gen::pvehicle &pvehicle, float &top_speed, float *table, int num_entries); diff --git a/src/Speed/Indep/Src/Physics/PhysicsTunings.h b/src/Speed/Indep/Src/Physics/PhysicsTunings.h index 2dec61ee5..0c2456c3c 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsTunings.h +++ b/src/Speed/Indep/Src/Physics/PhysicsTunings.h @@ -5,6 +5,8 @@ #pragma once #endif +#include "Speed/Indep/bWare/Inc/bWare.hpp" + namespace Physics { enum eCustomTuningType { @@ -27,6 +29,8 @@ struct Tunings { }; float Value[7]; // offset 0x0, size 0x1C + void Default(); + static float LowerLimit(Path path); static float UpperLimit(Path path); }; diff --git a/src/Speed/Indep/Src/Physics/PhysicsUpgrades.hpp b/src/Speed/Indep/Src/Physics/PhysicsUpgrades.hpp index 11a6db840..bb59ee1c5 100644 --- a/src/Speed/Indep/Src/Physics/PhysicsUpgrades.hpp +++ b/src/Speed/Indep/Src/Physics/PhysicsUpgrades.hpp @@ -5,16 +5,33 @@ #pragma once #endif +namespace Attrib { namespace Gen { struct pvehicle; } } + namespace Physics { namespace Upgrades { +enum Type { + kType_Engine = 0, + kType_Transmission = 1, + kType_Chassis = 2, + kType_Nitrous = 3, + kType_Tires = 4, + kType_Brakes = 5, + kType_Induction = 6, + kType_Count = 7, +}; + // total size: 0x20 struct Package { int Part[7]; // offset 0x0, size 0x1C int Junkman; // offset 0x1C, size 0x4 }; +int GetMaxLevel(const Attrib::Gen::pvehicle &vehicle, Type type); +int GetLevel(const Attrib::Gen::pvehicle &vehicle, Type type); +void SetLevel(Attrib::Gen::pvehicle &vehicle, Type type, int level); + }; // namespace Upgrades }; // namespace Physics diff --git a/src/Speed/Indep/Src/World/CarInfo.hpp b/src/Speed/Indep/Src/World/CarInfo.hpp index 28e8872fd..0a897b828 100644 --- a/src/Speed/Indep/Src/World/CarInfo.hpp +++ b/src/Speed/Indep/Src/World/CarInfo.hpp @@ -39,8 +39,14 @@ enum CarRenderUsage { class RideInfo { public: void Init(CarType type, CarRenderUsage usage, int has_dash, int can_be_vertex_damaged); - struct CarPart *GetPart(int car_slot_id) const; - int GetSpecialLODRangeForCarSlot(int slot_id, CARPART_LOD *special_minimum, CARPART_LOD *special_maximum, bool in_front_end); + void SetStockParts(); + void SetRandomPaint(); + void SetRandomParts(); + void DumpForPreset(struct FECarRecord *car); + void FillWithPreset(unsigned int preset); + struct CarPart *GetPart(int carslotid) const; + void SetPart(int carslotid, struct CarPart *part, bool enabled); + void SetCompositeNameHash(int skin_number); RideInfo() { Init(CARTYPE_NONE, CarRenderUsage_Player, 0, 0); @@ -139,4 +145,10 @@ struct CarTypeInfo { int DefaultBasePaint; // offset 0xCC, size 0x4 }; +extern CarTypeInfo *CarTypeInfoArray; + +inline CarTypeInfo *GetCarTypeInfo(CarType type) { + return &CarTypeInfoArray[type]; +} + #endif diff --git a/src/Speed/Indep/Src/World/CarLoader.hpp b/src/Speed/Indep/Src/World/CarLoader.hpp index ede4f2a62..f51ef413a 100644 --- a/src/Speed/Indep/Src/World/CarLoader.hpp +++ b/src/Speed/Indep/Src/World/CarLoader.hpp @@ -124,6 +124,11 @@ class CarLoader { return LoadingInProgress; } + int Load(RideInfo *ride_info); + void Unload(int handle); + void BeginLoading(void (*callback)(unsigned int), unsigned int param); + int IsLoaded(int handle); + private: void (*pCallback)(unsigned int); // offset 0x0, size 0x4 unsigned int Param; // offset 0x4, size 0x4 diff --git a/src/Speed/Indep/Src/World/CarPart.hpp b/src/Speed/Indep/Src/World/CarPart.hpp new file mode 100644 index 000000000..a6fa3a3fc --- /dev/null +++ b/src/Speed/Indep/Src/World/CarPart.hpp @@ -0,0 +1,34 @@ +#ifndef WORLD_CARPART_H +#define WORLD_CARPART_H + +#ifdef EA_PRAGMA_ONCE_SUPPORTED +#pragma once +#endif + +// total size: 0xE +struct CarPart { + unsigned short PartNameHashBot; // offset 0x0, size 0x2 + unsigned short PartNameHashTop; // offset 0x2, size 0x2 + char PartID; // offset 0x4, size 0x1 + unsigned char GroupNumber_UpgradeLevel; // offset 0x5, size 0x1 + char BaseModelNameHashSelector; // offset 0x6, size 0x1 + unsigned char CarTypeNameHashIndex; // offset 0x7, size 0x1 + unsigned short NameOffset; // offset 0x8, size 0x2 + unsigned short AttributeTableOffset; // offset 0xA, size 0x2 + unsigned short ModelNameHashTableOffset; // offset 0xC, size 0x2 + + char GetUpgradeLevel() { return GroupNumber_UpgradeLevel >> 5; } + char GetGroupNumber() { return GroupNumber_UpgradeLevel & 0x1f; } + + const char *GetName(); + unsigned int GetCarTypeNameHash(); + unsigned int GetPartNameHash(); + char GetPartID(); + int HasAppliedAttribute(unsigned int namehash); + const char *GetAppliedAttributeString(unsigned int namehash, const char *default_string); + float GetAppliedAttributeFParam(unsigned int namehash, float default_value); + int GetAppliedAttributeIParam(unsigned int namehash, int default_value); + unsigned int GetAppliedAttributeUParam(unsigned int namehash, unsigned int default_value); +}; + +#endif diff --git a/src/Speed/Indep/Src/World/CarRender.hpp b/src/Speed/Indep/Src/World/CarRender.hpp index 77977c403..3422889f1 100644 --- a/src/Speed/Indep/Src/World/CarRender.hpp +++ b/src/Speed/Indep/Src/World/CarRender.hpp @@ -520,25 +520,33 @@ class CarRenderInfo { class FrontEndRenderingCar : public bTNode { public: // Functions - void SetPosition(bVector3 *position) {} + void SetPosition(bVector3 *position) { Position = *position; } - void SetBodyMatrix(bMatrix4 *body_matrix) {} + void SetBodyMatrix(bMatrix4 *body_matrix) { BodyMatrix = *body_matrix; } - void SetTireMatrices(bMatrix4 *tire_matrices) {} + void SetTireMatrices(bMatrix4 *tire_matrices) { + for (int n = 0; n < 4; n++) { + TireMatrices[n] = tire_matrices[n]; + } + } - void SetBrakeMatrices(bMatrix4 *brake_matrices) {} + void SetBrakeMatrices(bMatrix4 *brake_matrices) { + for (int n = 0; n < 4; n++) { + BrakeMatrices[n] = brake_matrices[n]; + } + } - void SetTireMatrix(int n, bMatrix4 *m) {} + void SetTireMatrix(int n, bMatrix4 *m) { TireMatrices[n] = *m; } - void SetBrakeMatrix(int n, bMatrix4 *m) {} + void SetBrakeMatrix(int n, bMatrix4 *m) { BrakeMatrices[n] = *m; } void SetOverrideModel(eModel *override_model) {} - RideInfo *GetRideInfo() {} + RideInfo *GetRideInfo() { return &mRideInfo; } CarRenderInfo *GetRenderInfo() {} - CarType GetCarType() {} + CarType GetCarType() { return mRideInfo.Type; } FrontEndRenderingCar(RideInfo *ride_info, int view_id); diff --git a/src/Speed/Indep/Src/World/TrackInfo.hpp b/src/Speed/Indep/Src/World/TrackInfo.hpp index f2cd58ff0..bfd578e92 100644 --- a/src/Speed/Indep/Src/World/TrackInfo.hpp +++ b/src/Speed/Indep/Src/World/TrackInfo.hpp @@ -80,6 +80,8 @@ class TrackInfo { const char *GetLoadedTrackInfo() { return this->RegionName; } + + static TrackInfo* GetTrackInfo(int track_number); }; extern TrackInfo *LoadedTrackInfo; diff --git a/src/Speed/Indep/Src/World/TrackStreamer.hpp b/src/Speed/Indep/Src/World/TrackStreamer.hpp index aa1028f1b..18dab4c05 100644 --- a/src/Speed/Indep/Src/World/TrackStreamer.hpp +++ b/src/Speed/Indep/Src/World/TrackStreamer.hpp @@ -153,15 +153,27 @@ class TrackStreamer { void BlockUntilLoadingComplete(); void *AllocateUserMemory(int size, const char *debug_name, int offset); void FreeUserMemory(void *mem); + bool IsUserMemory(void *mem); + bool HasMemoryPool() { + return pMemoryPoolMem != nullptr; + } void DisableZoneSwitching() { ZoneSwitchingDisabled = true; } + void EnableZoneSwitching() { + ZoneSwitchingDisabled = false; + } + int IsSectionVisible(int section_number) { return CurrentVisibleSectionTable.IsSet(section_number); } + bool IsPermFileLoading() { + return PermFileLoading; + } + private: TrackStreamingSection *pTrackStreamingSections; // offset 0x0, size 0x4 int NumTrackStreamingSections; // offset 0x4, size 0x4 @@ -215,6 +227,12 @@ class TrackStreamer { void (*MakeSpaceInPoolCallback)(int); // offset 0x87C, size 0x4 int MakeSpaceInPoolCallbackParam; // offset 0x880, size 0x4 int MakeSpaceInPoolSize; // offset 0x884, size 0x4 + + public: + void MakeSpaceInPool(int size, void (*callback)(int), int param); + bool MakeSpaceInPool(int size, bool force_unloading); + void WaitForCurrentLoadingToComplete(); + void RefreshLoading(); }; extern TrackStreamer TheTrackStreamer; diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h index 6633f4de8..dd66a684d 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribHash.h @@ -42,6 +42,10 @@ class StringKey { return mHash32; } + const char *GetString() const { + return mString; + } + bool IsValid() const { return mString != nullptr; } diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h index f458308a4..d0f0d3839 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/AttribSys.h @@ -349,6 +349,10 @@ class Array { // TODO is this really overriden? void operator delete(void *ptr) {} +#ifdef _MSC_VER + void operator delete(void *mem, void *ptr) {} +#endif + ~Array() { if (IsReferences()) { ITypeHandler *typeHandler = GetTypeDesc().GetHandler(); @@ -773,7 +777,7 @@ class Instance { return mCollection; } - void SetDefaultLayout(unsigned int bytes) { + void SetDefaultLayout(unsigned int bytes) const { if (mLayoutPtr == nullptr) { mLayoutPtr = const_cast(DefaultDataArea(bytes)); } @@ -820,7 +824,7 @@ class Instance { UTL::COM::IUnknown *mOwner; // offset 0x0, size 0x4 const Collection *mCollection; // offset 0x4, size 0x4 - void *mLayoutPtr; // offset 0x8, size 0x4 + mutable void *mLayoutPtr; // offset 0x8, size 0x4 uint32_t mMsgPort; // offset 0xC, size 0x4 uint16_t mFlags; // offset 0x10, size 0x2 mutable uint16_t mLocks; // offset 0x12, size 0x2 diff --git a/src/Speed/Indep/Tools/AttribSys/Runtime/VecHashMap64.h b/src/Speed/Indep/Tools/AttribSys/Runtime/VecHashMap64.h index 644670807..534271374 100644 --- a/src/Speed/Indep/Tools/AttribSys/Runtime/VecHashMap64.h +++ b/src/Speed/Indep/Tools/AttribSys/Runtime/VecHashMap64.h @@ -87,12 +87,10 @@ template GetHead()->Remove(); } bNode *RemoveTail(); // TODO - int GetNodeNumber(bNode *node); // TODO + int GetNodeNumber(bNode *node) { + return this->TraversebList(node); + } int IsInList(bNode *node) { return this->TraversebList(node); diff --git a/src/Speed/Indep/bWare/Inc/bMath.hpp b/src/Speed/Indep/bWare/Inc/bMath.hpp index da7b7cc80..0e6d0e566 100644 --- a/src/Speed/Indep/bWare/Inc/bMath.hpp +++ b/src/Speed/Indep/bWare/Inc/bMath.hpp @@ -46,7 +46,11 @@ inline float bTan(unsigned short angle) { inline float bSqrt(float x) { const float bSqrtEPS = 5e-11f; - float y0; + float y0 +#ifdef _MSC_VER + = 0.0f +#endif + ; float y1; float t0; float t1; @@ -199,7 +203,7 @@ inline float bDegToRad(float degrees) { } inline float bAngToDeg(unsigned short angle) { - return static_cast(angle) * (65536.0f / 360.0f); + return static_cast(angle) * (360.0f / 65536.0f); } inline float bCos(float angle) { @@ -243,7 +247,7 @@ struct bVector2 { int operator==(const bVector2 &v); - // bVector2 &operator=(const bVector2 &v) {} // compiler generated? shown in dwarf + bVector2 &operator=(const bVector2 &v); // bVector2(const bVector2 &v) {} // compiler generated @@ -262,6 +266,19 @@ inline bVector2 *bFill(bVector2 *dest, float x, float y) { return dest; } +inline bVector2 *bSub(bVector2 *dest, const bVector2 *v1, const bVector2 *v2) { + float x1 = v1->x; + float y1 = v1->y; + float x2 = v2->x; + float y2 = v2->y; + return bFill(dest, x1 - x2, y1 - y2); +} + +inline bVector2 &bVector2::operator-=(const bVector2 &v) { + bSub(this, this, &v); + return *this; +} + inline bVector2::bVector2(float _x, float _y) { bFill(this, _x, _y); } @@ -286,6 +303,38 @@ inline bVector2 bVector2::operator-(const bVector2 &v) const { return bVector2(_x, _y); } +inline bVector2 *bCopy(bVector2 *dest, const bVector2 *v) { + float x = v->x; + float y = v->y; + return bFill(dest, x, y); +} + +inline bVector2 &bVector2::operator=(const bVector2 &v) { + bCopy(this, &v); + return *this; +} + +inline bVector2 *bScale(bVector2 *dest, const bVector2 *v, float scale) { + float x = v->x * scale; + float y = v->y * scale; + return bFill(dest, x, y); +} + +inline bVector2 bScale(const bVector2 &v, float scale) { + bVector2 dest; + bScale(&dest, &v, scale); + return dest; +} + +inline bVector2 &bVector2::operator*=(float scale) { + bScale(this, this, scale); + return *this; +} + +inline bVector2 bVector2::operator*(float f) const { + return bScale(*this, f); +} + inline float bLength(const bVector2 *v) { float x = v->x; float y = v->y; @@ -556,18 +605,20 @@ struct bVector4 { bVector4 operator+(const bVector4 &v) { bVector4 *pv; - float x1; - float y1; - float z1; - float w1; - float x2; - float y2; - float z2; - float w2; - float _x; - float _y; - float _z; - float _w; + float x1 = x; + float y1 = y; + float z1 = z; + float w1 = w; + float x2 = v.x; + float y2 = v.y; + float z2 = v.z; + float w2 = v.w; + float _x = x1 + x2; + float _y = y1 + y2; + float _z = z1 + z2; + float _w = w1 + w2; + + return bVector4(_x, _y, _z, _w); } bVector4 operator-() { @@ -916,6 +967,9 @@ struct bQuaternion { this->w = _w; } + void GetMatrix(bMatrix4 &mat) const; + inline void GetMatrix(bMatrix4 *mat) const { GetMatrix(*mat); } + bQuaternion &Slerp(bQuaternion &r, const bQuaternion &target, float t) const; }; diff --git a/src/Speed/Indep/bWare/Inc/bWare.hpp b/src/Speed/Indep/bWare/Inc/bWare.hpp index 047e10809..ab3657f72 100644 --- a/src/Speed/Indep/bWare/Inc/bWare.hpp +++ b/src/Speed/Indep/bWare/Inc/bWare.hpp @@ -12,6 +12,10 @@ #ifdef MILESTONE_OPT void *bMalloc(int size, const char *debug_text, int debug_line, int allocation_params); + +inline void *bMalloc(int size, int allocation_params) { + return bMalloc(size, nullptr, 0, allocation_params); +} #else void *bMalloc(int size, int allocation_params); @@ -48,6 +52,24 @@ inline void *operator new[](size_t size, const char *file, int line) { #endif } +#ifdef _MSC_VER +inline void operator delete(void *ptr, const char *, int) { +#if MILESTONE_OPT + bFree(ptr); +#else + delete[] reinterpret_cast(ptr); +#endif +} + +inline void operator delete[](void *ptr, const char *, int) { +#if MILESTONE_OPT + bFree(ptr); +#else + delete[] reinterpret_cast(ptr); +#endif +} +#endif + void bEndianSwap64(void *value); void bEndianSwap32(void *value); void bEndianSwap16(void *value); diff --git a/src/Speed/Indep/bWare/Src/bDebug.cpp b/src/Speed/Indep/bWare/Src/bDebug.cpp index c29e2a345..203b531a3 100644 --- a/src/Speed/Indep/bWare/Src/bDebug.cpp +++ b/src/Speed/Indep/bWare/Src/bDebug.cpp @@ -146,7 +146,7 @@ float bGetTickerDifference(unsigned int start_ticks, unsigned int end_ticks) { } void bMutex::Create() { - MUTEX_create(reinterpret_cast(this)); + reinterpret_cast(this)->Create(); } void bMutex::Destroy() { diff --git a/src/types.h b/src/types.h index f0182b650..0b229cb62 100644 --- a/src/types.h +++ b/src/types.h @@ -19,6 +19,8 @@ typedef unsigned int type_operator_new; #define ONLINE_SUPPORT (0) +#define OVERLAYED __attribute__((section(".over"))) + #elif defined(EA_PLATFORM_XENON) #include @@ -83,6 +85,8 @@ typedef unsigned __int8 u8; #define ONLINE_SUPPORT (1) +#define OVERLAYED + #elif defined(EA_PLATFORM_PLAYSTATION2) #include @@ -152,6 +156,8 @@ typedef signed char i8; #define ONLINE_SUPPORT (1) +#define OVERLAYED + #endif #include 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/build_zFeOverlay.sh b/tools/build_zFeOverlay.sh new file mode 100755 index 000000000..1f7eb413f --- /dev/null +++ b/tools/build_zFeOverlay.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e +cd "$(dirname "$0")/.." +ninja build/GOWE69/src/Speed/Indep/SourceLists/zFeOverlay.o +python3 tools/rename_section.py build/GOWE69/src/Speed/Indep/SourceLists/zFeOverlay.o .text .over diff --git a/tools/code_style.py b/tools/code_style.py index ecb85f713..61c3f2ee3 100644 --- a/tools/code_style.py +++ b/tools/code_style.py @@ -84,6 +84,7 @@ class Finding: ) USING_NAMESPACE_PATTERN = re.compile(r"^\s*using\s+namespace\b") NULL_PATTERN = re.compile(r"\bNULL\b") +BARE_PRESENCE_IF_PATTERN = re.compile(r"^\s*#if\s+([A-Za-z_][A-Za-z0-9_]*)\s*$") HEADER_GUARD_IFNDEF_PATTERN = re.compile(r"^\s*#ifndef\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) HEADER_GUARD_DEFINE_PATTERN = re.compile(r"^\s*#define\s+[A-Za-z0-9_]+\s*$", re.MULTILINE) EA_PRAGMA_BLOCK_PATTERN = re.compile( @@ -92,6 +93,16 @@ class Finding: r".*?^\s*#endif\s*$", re.MULTILINE | re.DOTALL, ) +EA_PRAGMA_IFDEF_PATTERN = re.compile( + r"^\s*#ifdef\s+EA_PRAGMA_ONCE_SUPPORTED\s*$", re.MULTILINE +) +RECOVERED_LAYOUT_COMMENT_PATTERN = re.compile( + r"//\s*offset 0x[0-9A-Fa-f]+,\s*size 0x[0-9A-Fa-f]+" +) +RECOVERED_NARROW_UNSIGNED_PATTERN = re.compile(r"\bunsigned\s+(char|short)\b") +BARE_RECOVERY_MARKER_PATTERN = re.compile( + r"//\s*(TODO|UNSOLVED|STRIPPED)\b(?:\s*[.:,-]*)?\s*$" +) SUSPICIOUS_MEMBER_PATTERN = re.compile( r"^(?:" r"_?pad(?:ding)?[0-9A-Fa-f_]*" @@ -441,6 +452,16 @@ def audit_style_guide_rules( if touched_lines is not None and idx not in touched_lines: continue stripped = line.strip() + bare_recovery_marker_match = BARE_RECOVERY_MARKER_PATTERN.search(line) + if bare_recovery_marker_match is not None: + findings.append( + Finding( + path, + idx, + "INFO", + f"`// {bare_recovery_marker_match.group(1)}` has no context; add a short reason or remove the stale recovery marker", + ) + ) if stripped.startswith("//"): continue @@ -471,9 +492,35 @@ def audit_style_guide_rules( "use `nullptr` instead of `NULL`", ) ) + bare_presence_if_match = BARE_PRESENCE_IF_PATTERN.match(line) + if bare_presence_if_match is not None: + findings.append( + Finding( + path, + idx, + "WARN", + f"bare `#if {bare_presence_if_match.group(1)}` looks like a presence check; prefer `#ifdef {bare_presence_if_match.group(1)}` unless a numeric test is intentional", + ) + ) + narrow_type_match = RECOVERED_NARROW_UNSIGNED_PATTERN.search(line) + if ( + narrow_type_match is not None + and RECOVERED_LAYOUT_COMMENT_PATTERN.search(line) is not None + ): + preferred = "uint8" if narrow_type_match.group(1) == "char" else "uint16" + findings.append( + Finding( + path, + idx, + "INFO", + f"recovered layout member uses `{narrow_type_match.group(0)}`; prefer explicit-width `{preferred}` when the field width is known", + ) + ) if ext in HEADER_EXTS: - should_check_guard = touched_lines is None or any(line_no <= 8 for line_no in touched_lines) + should_check_guard = touched_lines is None or any( + line_no <= 12 for line_no in touched_lines + ) if should_check_guard: has_ifndef = HEADER_GUARD_IFNDEF_PATTERN.search(text) is not None has_define = HEADER_GUARD_DEFINE_PATTERN.search(text) is not None @@ -487,6 +534,20 @@ def audit_style_guide_rules( "header guard should use `#ifndef` / `#define` plus the `EA_PRAGMA_ONCE_SUPPORTED` `#pragma once` block", ) ) + pragma_ifdef_match = EA_PRAGMA_IFDEF_PATTERN.search(text) + if pragma_ifdef_match is not None: + pragma_ifdef_line = text[: pragma_ifdef_match.start()].count("\n") + 1 + for idx, line in enumerate(text.splitlines(), 1): + if line.strip().startswith("#include ") and idx < pragma_ifdef_line: + findings.append( + Finding( + path, + idx, + "WARN", + "header include appears before the `EA_PRAGMA_ONCE_SUPPORTED` block; keep the guard / pragma block ahead of includes", + ) + ) + break return findings diff --git a/tools/decomp-diff.py b/tools/decomp-diff.py index 5ad03c640..2fc7bbb96 100644 --- a/tools/decomp-diff.py +++ b/tools/decomp-diff.py @@ -19,6 +19,7 @@ import sys from typing import Any, Dict, List, Optional, Tuple from _common import ( + RELOC_DIFF_CHOICES, ROOT_DIR, RELOC_DIFF_CHOICES, ToolError, diff --git a/tools/decomp-status.py b/tools/decomp-status.py index 8dcf5d6c7..00c5a5804 100644 --- a/tools/decomp-status.py +++ b/tools/decomp-status.py @@ -92,9 +92,13 @@ def analyze_unit(diff_data: Dict[str, Any]) -> Dict[str, Any]: matching_code_size += size remaining_code_size_est += row["unmatched_bytes_est"] - text_section = section_stats.get(".text", {}) - text_match = text_section.get("match_percent") - text_size = text_section.get("size", 0) + code_section_name = next( + (sec["name"] for sec in left_sections if sec.get("kind") == "SECTION_CODE"), + ".text", + ) + code_section = section_stats.get(code_section_name, {}) + text_match = code_section.get("match_percent") + text_size = code_section.get("size", 0) return { "total_functions": total_funcs, @@ -102,6 +106,7 @@ def analyze_unit(diff_data: Dict[str, Any]) -> Dict[str, Any]: "total_code_size": total_code_size, "matching_code_size": matching_code_size, "remaining_code_size_est": remaining_code_size_est, + "code_section_name": code_section_name, "text_match_percent": text_match, "text_size": text_size, "sections": section_stats, @@ -238,11 +243,12 @@ def main(): elif status == "incomplete": tf = entry.get("total_functions", 0) mf = entry.get("matching_functions", 0) + code_section_name = entry.get("code_section_name", ".text") tm = entry.get("text_match_percent") tm_str = f"{tm:.1f}%" if tm is not None else "?" remain = entry.get("remaining_code_size_est", 0) print( - f" {display_name:<50s} .text {tm_str:>6s} ~{remain:>6}B rem ({mf}/{tf} functions)" + f" {display_name:<50s} {code_section_name:<6s} {tm_str:>6s} ~{remain:>6}B rem ({mf}/{tf} functions)" ) cat_funcs += tf cat_matching += mf diff --git a/tools/decomp-workflow.py b/tools/decomp-workflow.py index 84f2f83d5..78e7fee04 100644 --- a/tools/decomp-workflow.py +++ b/tools/decomp-workflow.py @@ -16,12 +16,15 @@ python tools/decomp-workflow.py function -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll --no-source python tools/decomp-workflow.py diff -u main/Speed/Indep/SourceLists/zCamera -d UpdateAll --reloc-diffs all python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll + python tools/decomp-workflow.py dwarf-scan -u main/Speed/Indep/SourceLists/zCamera + python tools/decomp-workflow.py dwarf-scan -u main/Speed/Indep/SourceLists/zCamera --objdiff-status match python tools/decomp-workflow.py dwarf -u main/Speed/Indep/SourceLists/zAttribSys -f 'Attrib::Class::RemoveCollection(Attrib::Collection *)' --full-diff python tools/decomp-workflow.py verify -u main/Speed/Indep/SourceLists/zCamera -f UpdateAll python tools/decomp-workflow.py unit -u main/Speed/Indep/SourceLists/zCamera """ import argparse +import difflib import json import re import os @@ -45,6 +48,8 @@ make_abs, run_objdiff_json, ) +from lookup import _candidate_func_names, _sig_contains_name, read_text, split_functions +from split_dwarf_info import apply_umath_fixups SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) @@ -56,24 +61,51 @@ PS2_SYMBOLS = os.path.join(ROOT_DIR, "config", "SLES-53558-A124", "symbols.txt") GC_DWARF = os.path.join(ROOT_DIR, "symbols", "Dwarf") DEBUG_LINES = os.path.join(ROOT_DIR, "symbols", "debug_lines.txt") +X360_COMPILER_DIR = os.path.join(ROOT_DIR, "build", "compilers", "X360", "14.00.2110") +PS2_COMPILER_DIR = os.path.join(ROOT_DIR, "build", "compilers", "PS2", "ee-gcc2.9-991111") +MIPS_BINUTILS_DIR = os.path.join(ROOT_DIR, "build", "mips_binutils") DEFAULT_SMOKE_UNIT = "main/Speed/Indep/SourceLists/zCamera" DEBUG_SYMBOL_PROBE_MANGLED = "UpdateAll__6Cameraf" DEBUG_SYMBOL_PROBE_DEMANGLED = "Camera::UpdateAll(float)" DEBUG_SYMBOL_PROBE_GC_ADDR = "0x80065A84" REBUILT_DEBUG_LINE_RE = re.compile(r"^\s*([0-9A-Fa-f]+)\s*:") +DWARF_HEX_RE = re.compile(r"0x[0-9A-Fa-f]+") LOW_MATCH_PRIORITY_THRESHOLD = 60.0 VERY_LOW_MATCH_PRIORITY_THRESHOLD = 40.0 HIGH_MATCH_CLEANUP_THRESHOLD = 85.0 VERY_HIGH_MATCH_CLEANUP_THRESHOLD = 95.0 +FunctionBlock = Tuple[str, str, str, str] SHARED_ASSET_REQUIREMENTS = [ (os.path.join("build", "tools"), "downloaded tooling"), (os.path.join("orig", "GOWE69", "NFSMWRELEASE.ELF"), "GameCube original ELF"), + ( + os.path.join("orig", "EUROPEGERMILESTONE", "NfsMWEuropeGerMilestone.xex"), + "Xbox original XEX", + ), (os.path.join("orig", "SLES-53558-A124", "NFS.ELF"), "PS2 original ELF"), (os.path.join("symbols", "Dwarf"), "DWARF dump"), ] +PLATFORM_BUILD_REQUIREMENTS = [ + ( + "x360-compiler", + X360_COMPILER_DIR, + "missing (seed build/compilers in this worktree for Xbox builds)", + ), + ( + "ps2-compiler", + PS2_COMPILER_DIR, + "missing (seed build/compilers in this worktree for PS2 builds)", + ), + ( + "ps2-binutils", + MIPS_BINUTILS_DIR, + "missing (seed build/mips_binutils in this worktree for PS2 builds)", + ), +] + class WorkflowError(RuntimeError): pass @@ -292,6 +324,14 @@ def choose_objdiff_row(unit_name: str, function_name: str, reloc_diffs: str = "n return matches[0] +def resolve_exact_function_name( + unit_name: str, function_name: str, reloc_diffs: str = "none" +) -> str: + return str( + choose_objdiff_row(unit_name, function_name, reloc_diffs=reloc_diffs)["name"] + ) + + def load_dwarf_report( unit_name: str, function_name: str, @@ -307,6 +347,355 @@ def load_dwarf_report( raise WorkflowError(f"dwarf-compare.py returned invalid JSON: {e}") +def load_dwarf_blocks( + path: str, folder_mode: bool, apply_split_fixups_in_ram: bool = False +) -> List[FunctionBlock]: + if folder_mode: + text = read_text(os.path.join(path, "functions.nothpp")) + else: + text = read_text(path) + if apply_split_fixups_in_ram: + text = apply_umath_fixups(text) + return split_functions(text) + + +def find_dwarf_function_blocks( + funcs: Sequence[FunctionBlock], query: str +) -> List[FunctionBlock]: + candidates = _candidate_func_names(query) + exact_matches: List[FunctionBlock] = [] + fuzzy_matches: List[FunctionBlock] = [] + + for func in funcs: + sig_line = func[2] + if sig_line == query: + exact_matches.append(func) + elif any(_sig_contains_name(sig_line, candidate) for candidate in candidates): + fuzzy_matches.append(func) + + if exact_matches: + return exact_matches + return fuzzy_matches + + +def choose_dwarf_function_block( + funcs: Sequence[FunctionBlock], query: str, label: str +) -> FunctionBlock: + matches = find_dwarf_function_blocks(funcs, query) + if not matches: + raise WorkflowError(f"{label}: function '{query}' not found.") + if len(matches) > 1: + preview = "\n".join(f" - {match[2]}" for match in matches[:8]) + extra = "" + if len(matches) > 8: + extra = f"\n ... {len(matches) - 8} more" + raise WorkflowError( + f"{label}: function query '{query}' matched multiple DWARF blocks.\n" + f"Use a more specific function name.\n{preview}{extra}" + ) + return matches[0] + + +def normalize_dwarf_line(line: str) -> str: + stripped = line.rstrip("\n").rstrip() + if stripped.startswith("// Range:"): + return "// Range: " + return DWARF_HEX_RE.sub("0xADDR", stripped) + + +def normalize_dwarf_block(block: str) -> List[str]: + return [normalize_dwarf_line(line) for line in block.splitlines()] + + +def count_dwarf_opcodes( + opcodes: Sequence[Tuple[str, int, int, int, int]] +) -> Dict[str, int]: + matching = 0 + original_only = 0 + rebuilt_only = 0 + changed_groups = 0 + for tag, i1, i2, j1, j2 in opcodes: + if tag == "equal": + matching += i2 - i1 + continue + changed_groups += 1 + if tag in ("replace", "delete"): + original_only += i2 - i1 + if tag in ("replace", "insert"): + rebuilt_only += j2 - j1 + return { + "matching_lines": matching, + "original_only_lines": original_only, + "rebuilt_only_lines": rebuilt_only, + "changed_groups": changed_groups, + } + + +def build_dwarf_scan_row( + row: Dict[str, Any], + original_funcs: Sequence[FunctionBlock], + rebuilt_funcs: Sequence[FunctionBlock], +) -> Dict[str, Any]: + function_name = str(row["name"]) + result: Dict[str, Any] = { + "function": function_name, + "symbol_name": row["symbol_name"], + "objdiff_status": row["status"], + "objdiff_match_percent": row["match_percent"], + "unmatched_bytes_est": row["unmatched_bytes_est"], + "size": row["size"], + } + + try: + original_block = choose_dwarf_function_block( + original_funcs, function_name, "original DWARF" + ) + rebuilt_block = choose_dwarf_function_block( + rebuilt_funcs, function_name, "rebuilt DWARF" + ) + original_lines = normalize_dwarf_block(original_block[3]) + rebuilt_lines = normalize_dwarf_block(rebuilt_block[3]) + matcher = difflib.SequenceMatcher(a=original_lines, b=rebuilt_lines) + counts = count_dwarf_opcodes(matcher.get_opcodes()) + total_lines = max(len(original_lines), len(rebuilt_lines), 1) + result.update( + { + "dwarf_status": "exact" + if original_lines == rebuilt_lines + else "mismatch", + "dwarf_match_percent": 100.0 * counts["matching_lines"] / total_lines, + "changed_groups": counts["changed_groups"], + "matching_lines": counts["matching_lines"], + "total_lines": total_lines, + "original_line_count": len(original_lines), + "rebuilt_line_count": len(rebuilt_lines), + "signature_match": normalize_dwarf_line(original_block[2]) + == normalize_dwarf_line(rebuilt_block[2]), + } + ) + except WorkflowError as e: + result.update( + { + "dwarf_status": "error", + "dwarf_match_percent": None, + "changed_groups": None, + "matching_lines": None, + "total_lines": None, + "original_line_count": None, + "rebuilt_line_count": None, + "signature_match": None, + "error": str(e), + } + ) + return result + + +def filter_dwarf_scan_rows( + rows: Sequence[Dict[str, Any]], dwarf_status: str +) -> List[Dict[str, Any]]: + if dwarf_status == "all": + return list(rows) + if dwarf_status == "problem": + return [row for row in rows if row["dwarf_status"] in ("mismatch", "error")] + return [row for row in rows if row["dwarf_status"] == dwarf_status] + + +def filter_dwarf_signature_rows( + rows: Sequence[Dict[str, Any]], signature_status: str +) -> List[Dict[str, Any]]: + if signature_status == "all": + return list(rows) + want_match = signature_status == "match" + return [ + row + for row in rows + if row.get("signature_match") is not None + and bool(row["signature_match"]) == want_match + ] + + +def sort_dwarf_scan_rows(rows: List[Dict[str, Any]]) -> None: + status_rank = {"error": 0, "mismatch": 1, "exact": 2} + rows.sort( + key=lambda row: ( + status_rank.get(str(row["dwarf_status"]), 3), + row["dwarf_match_percent"] + if row["dwarf_match_percent"] is not None + else -1.0, + 0 + if row.get("signature_match") is True + else 1 + if row.get("signature_match") is False + else 2, + -(row["changed_groups"] or 0), + -(row["unmatched_bytes_est"] or 0), + row["objdiff_match_percent"] + if row["objdiff_match_percent"] is not None + else -1.0, + row["function"].lower(), + ) + ) + + +def command_dwarf_scan(args: argparse.Namespace) -> None: + ensure_decomp_prereqs() + if not args.json: + print_section(f"DWARF Scan: {args.unit}") + ensure_shared_unit_output(args.unit) + + rebuilt_dwarf_path = ( + os.path.abspath(args.rebuilt_dwarf_file) if args.rebuilt_dwarf_file else None + ) + cleanup_rebuilt_dwarf = False + try: + if not rebuilt_dwarf_path: + rebuilt_dwarf_path = dtk_dwarf_dump(get_unit_build_output(args.unit)) + cleanup_rebuilt_dwarf = True + + data = run_objdiff_json( + OBJDIFF_CLI, + args.unit, + reloc_diffs=args.reloc_diffs, + root_dir=ROOT_DIR, + ) + rows = [ + row + for row in build_objdiff_symbol_rows(data) + if row["type"] == "function" and row["side"] == "left" + ] + if args.objdiff_status != "all": + rows = [row for row in rows if row["status"] == args.objdiff_status] + if args.search: + rows = [ + row + for row in rows + if fuzzy_match(args.search, row["name"]) + or fuzzy_match(args.search, row["symbol_name"]) + ] + if not rows: + raise WorkflowError("No functions match the given filters.") + + original_funcs = load_dwarf_blocks(GC_DWARF, folder_mode=True) + rebuilt_funcs = load_dwarf_blocks( + rebuilt_dwarf_path, folder_mode=False, apply_split_fixups_in_ram=True + ) + scan_rows = [ + build_dwarf_scan_row(row, original_funcs, rebuilt_funcs) for row in rows + ] + + summary = { + "scanned_functions": len(scan_rows), + "exact_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "exact" + ), + "mismatch_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "mismatch" + ), + "error_functions": sum( + 1 for row in scan_rows if row["dwarf_status"] == "error" + ), + "byte_matched_dwarf_problems": sum( + 1 + for row in scan_rows + if row["objdiff_status"] == "match" + and row["dwarf_status"] in ("mismatch", "error") + ), + "signature_mismatch_functions": sum( + 1 for row in scan_rows if row.get("signature_match") is False + ), + } + + filtered_rows = filter_dwarf_scan_rows(scan_rows, args.dwarf_status) + filtered_rows = filter_dwarf_signature_rows( + filtered_rows, args.signature_status + ) + sort_dwarf_scan_rows(filtered_rows) + if args.limit is not None: + filtered_rows = filtered_rows[: args.limit] + + if args.json: + print( + json.dumps( + { + "unit": args.unit, + "summary": summary, + "rows": filtered_rows, + }, + indent=2, + ) + ) + return + + print( + f"Scanned {summary['scanned_functions']} function(s): " + f"{summary['exact_functions']} exact, " + f"{summary['mismatch_functions']} mismatched, " + f"{summary['error_functions']} errors." + ) + print( + "Byte-matched but DWARF-problem functions: " + f"{summary['byte_matched_dwarf_problems']}" + ) + print( + "Signature-mismatch functions: " + f"{summary['signature_mismatch_functions']}" + ) + + if not filtered_rows: + print("No functions match the given filters.") + return + + print() + print( + f"{'DSTAT':<8} {'DWARF':>7} {'SIG':>3} {'CHG':>4} {'OBJ':>7} {'OSTAT':<10} {'UNM':>6} FUNCTION" + ) + print("-" * 120) + for row in filtered_rows: + dwarf_percent = ( + f"{row['dwarf_match_percent']:.1f}%" + if row["dwarf_match_percent"] is not None + else "ERR" + ) + objdiff_percent = ( + f"{row['objdiff_match_percent']:.1f}%" + if row["objdiff_match_percent"] is not None + else "-" + ) + changed_groups = ( + str(row["changed_groups"]) if row["changed_groups"] is not None else "-" + ) + signature_state = ( + "yes" + if row.get("signature_match") is True + else "no" + if row.get("signature_match") is False + else "-" + ) + print( + f"{row['dwarf_status']:<8} {dwarf_percent:>7} {signature_state:>3} {changed_groups:>4} " + f"{objdiff_percent:>7} {row['objdiff_status']:<10} " + f"{row['unmatched_bytes_est']:>5}B {row['function']}" + ) + if args.show_errors and row.get("error"): + first_line = str(row["error"]).splitlines()[0] + print(f" error: {first_line}") + + print() + print( + "Tip: focus matched-byte functions first with " + "`python tools/decomp-workflow.py dwarf-scan " + f"-u {shlex.quote(args.unit)} --objdiff-status match`" + ) + if summary["signature_mismatch_functions"]: + print( + "Tip: add `--signature-status match` to focus body/local DWARF mismatches " + "instead of signature-only trouble." + ) + finally: + if cleanup_rebuilt_dwarf: + maybe_remove(rebuilt_dwarf_path) + + def lookup_symbol_address(symbols_file: str, mangled_name: str) -> Optional[str]: if not os.path.exists(symbols_file): return None @@ -395,6 +784,14 @@ def build_shared_unit_cached(unit: str) -> str: except WorkflowError as e: report(False, "ghidra", str(e)) + print_section("Platform Build Inputs") + for label, abs_path, missing_detail in PLATFORM_BUILD_REQUIREMENTS: + report( + os.path.exists(abs_path), + label, + describe_path(abs_path) if os.path.exists(abs_path) else missing_detail, + ) + print_section("Debug Symbol Checks") try: gc_addr = lookup_symbol_address(GC_SYMBOLS, DEBUG_SYMBOL_PROBE_MANGLED) @@ -642,6 +1039,9 @@ def command_function(args: argparse.Namespace) -> None: ensure_decomp_prereqs() print_section(f"Function Workflow: {args.function}") ensure_shared_unit_output(args.unit) + resolved_function_name = resolve_exact_function_name( + args.unit, args.function, reloc_diffs=args.reloc_diffs + ) cmd = python_tool("decomp-context.py", "-u", args.unit, "-f", args.function) if args.no_source: cmd.append("--no-source") @@ -661,9 +1061,14 @@ def command_function(args: argparse.Namespace) -> None: print(flush=True) print( "Required completion check: python tools/decomp-workflow.py verify " - f"-u {shlex.quote(args.unit)} -f {shlex.quote(args.function)}", + f"-u {shlex.quote(args.unit)} -f {shlex.quote(resolved_function_name)}", flush=True, ) + if resolved_function_name != args.function: + print( + f"(Resolved exact function name for DWARF-safe follow-up: {resolved_function_name})", + flush=True, + ) def command_unit(args: argparse.Namespace) -> None: @@ -810,8 +1215,11 @@ def command_dwarf(args: argparse.Namespace) -> None: print_section(f"DWARF Workflow: {args.unit} / {args.function}") if not args.rebuilt_dwarf_file: ensure_shared_unit_output(args.unit) + resolved_function_name = resolve_exact_function_name(args.unit, args.function) - cmd: List[str] = python_tool("dwarf-compare.py", "-u", args.unit, "-f", args.function) + cmd: List[str] = python_tool( + "dwarf-compare.py", "-u", args.unit, "-f", resolved_function_name + ) if args.summary: cmd.append("--summary") if args.json: @@ -833,18 +1241,24 @@ def command_verify(args: argparse.Namespace) -> None: ensure_shared_unit_output(args.unit) objdiff_row = choose_objdiff_row(args.unit, args.function, reloc_diffs=args.reloc_diffs) - dwarf_report = load_dwarf_report( - args.unit, - args.function, - rebuilt_dwarf_file=args.rebuilt_dwarf_file, - ) + resolved_function_name = str(objdiff_row["name"]) + dwarf_load_error: Optional[str] = None + dwarf_report: Optional[Dict[str, Any]] = None + try: + dwarf_report = load_dwarf_report( + args.unit, + resolved_function_name, + rebuilt_dwarf_file=args.rebuilt_dwarf_file, + ) + except WorkflowError as e: + dwarf_load_error = str(e) objdiff_exact = ( objdiff_row["status"] == "match" and objdiff_row["match_percent"] is not None and float(objdiff_row["match_percent"]) >= 100.0 ) - dwarf_exact = bool(dwarf_report["normalized_exact_match"]) + dwarf_exact = bool(dwarf_report["normalized_exact_match"]) if dwarf_report else False overall_ok = objdiff_exact and dwarf_exact objdiff_percent = ( @@ -852,34 +1266,56 @@ def command_verify(args: argparse.Namespace) -> None: if objdiff_row["match_percent"] is not None else "-" ) - dwarf_percent = f"{float(dwarf_report['match_percent']):.1f}%" + dwarf_percent = ( + f"{float(dwarf_report['match_percent']):.1f}%" if dwarf_report else "-" + ) print( f"objdiff: {'PASS' if objdiff_exact else 'FAIL'} | " f"{objdiff_percent} | status={objdiff_row['status']} | " f"unmatched~{objdiff_row['unmatched_bytes_est']}B" ) - print( - f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " - f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " - f"change groups={dwarf_report['changed_groups']}" - ) + if dwarf_report: + print( + f"DWARF: {'PASS' if dwarf_exact else 'FAIL'} | " + f"{dwarf_percent} | normalized exact={'yes' if dwarf_exact else 'no'} | " + f"change groups={dwarf_report['changed_groups']}" + ) + else: + print("DWARF: FAIL | unable to compare rebuilt vs original DWARF", flush=True) + if resolved_function_name != args.function: + print(f"Resolved DWARF symbol: {resolved_function_name}") print(f"Overall: {'PASS' if overall_ok else 'FAIL'}") print("Done means both objdiff and normalized DWARF are exact for the function.") if overall_ok: return + if dwarf_load_error: + print(flush=True) + print("DWARF compare could not complete:", flush=True) + print(dwarf_load_error, flush=True) + if ( + objdiff_row["status"] == "missing" + and "rebuilt DWARF: function" in dwarf_load_error + and "not found" in dwarf_load_error + ): + print( + "Hint: the rebuilt object does not contain this function yet. " + "Implement the function or fix its ownership/signature first, then rerun verify.", + flush=True, + ) + print(flush=True) print("Follow-up commands:", flush=True) print( f" python tools/decomp-workflow.py diff -u {shlex.quote(args.unit)} " - f"-d {shlex.quote(args.function)}", + f"-d {shlex.quote(resolved_function_name)}", flush=True, ) print( f" python tools/decomp-workflow.py dwarf -u {shlex.quote(args.unit)} " - f"-f {shlex.quote(args.function)}", + f"-f {shlex.quote(resolved_function_name)}", flush=True, ) raise WorkflowError( @@ -897,7 +1333,7 @@ def build_parser() -> argparse.ArgumentParser: health = subparsers.add_parser( "health", - help="Check whether the current worktree is ready for GC and PS2 decomp work", + help="Check whether the current worktree is ready for GC, Xbox, and PS2 work", ) health.add_argument( "--full", @@ -941,7 +1377,12 @@ def build_parser() -> argparse.ArgumentParser: help="Run decomp-context.py for one function", ) function.add_argument("-u", "--unit", required=True, help="Translation unit name") - function.add_argument("-f", "--function", required=True, help="Function name to inspect") + function.add_argument( + "-f", + "--function", + required=True, + help="Function name to inspect (full name or a unique substring)", + ) function.add_argument( "--no-source", action="store_true", @@ -1086,7 +1527,12 @@ def build_parser() -> argparse.ArgumentParser: help="Compare original vs rebuilt DWARF for one function", ) dwarf.add_argument("-u", "--unit", required=True, help="Translation unit name") - dwarf.add_argument("-f", "--function", required=True, help="Function name to compare") + dwarf.add_argument( + "-f", + "--function", + required=True, + help="Function name to compare (full name or a unique substring)", + ) dwarf.add_argument( "--summary", action="store_true", @@ -1122,12 +1568,72 @@ def build_parser() -> argparse.ArgumentParser: ) dwarf.set_defaults(func=command_dwarf) + dwarf_scan = subparsers.add_parser( + "dwarf-scan", + help="Scan one translation unit and rank per-function DWARF problem areas", + ) + dwarf_scan.add_argument("-u", "--unit", required=True, help="Translation unit name") + dwarf_scan.add_argument( + "--search", + help="Only include functions whose name or symbol contains this text", + ) + dwarf_scan.add_argument( + "--objdiff-status", + choices=["all", "match", "nonmatching", "missing"], + default="all", + help="Filter functions by objdiff status before scanning (default: all)", + ) + dwarf_scan.add_argument( + "--dwarf-status", + choices=["all", "problem", "exact", "mismatch", "error"], + default="problem", + help="Filter scan results by DWARF outcome after scanning (default: problem)", + ) + dwarf_scan.add_argument( + "--signature-status", + choices=["all", "match", "mismatch"], + default="all", + help="Filter scan results by whether the DWARF signature already matches (default: all)", + ) + dwarf_scan.add_argument( + "--limit", + type=int, + default=20, + help="Maximum rows to print after sorting (default: 20)", + ) + dwarf_scan.add_argument( + "--json", + action="store_true", + help="Print the scan summary and rows as JSON", + ) + dwarf_scan.add_argument( + "--show-errors", + action="store_true", + help="Print one-line error details under rows that could not be compared", + ) + dwarf_scan.add_argument( + "--reloc-diffs", + choices=RELOC_DIFF_CHOICES, + default="none", + help="Pass through objdiff relocation diff mode when loading unit symbols", + ) + dwarf_scan.add_argument( + "--rebuilt-dwarf-file", + help="Use an existing rebuilt DWARF dump instead of dumping the unit object", + ) + dwarf_scan.set_defaults(func=command_dwarf_scan) + verify = subparsers.add_parser( "verify", help="Fail unless one function matches in both objdiff and DWARF", ) verify.add_argument("-u", "--unit", required=True, help="Translation unit name") - verify.add_argument("-f", "--function", required=True, help="Function name to verify") + verify.add_argument( + "-f", + "--function", + required=True, + help="Function name to verify (full name or a unique substring)", + ) verify.add_argument( "--reloc-diffs", choices=RELOC_DIFF_CHOICES, diff --git a/tools/project.py b/tools/project.py index 793200414..40e66b54f 100644 --- a/tools/project.py +++ b/tools/project.py @@ -69,6 +69,7 @@ def __init__(self, completed: bool, name: str, **options: Any) -> None: "extra_cflags": [], "extra_clang_flags": [], "lib": None, + "section_renames": None, "toolchain_version": None, "progress_category": None, "scratch_preset_id": None, @@ -82,6 +83,7 @@ def __init__(self, completed: bool, name: str, **options: Any) -> None: self.src_path: Optional[Path] = None self.asm_path: Optional[Path] = None self.src_obj_path: Optional[Path] = None + self.objdiff_obj_path: Optional[Path] = None self.asm_obj_path: Optional[Path] = None self.ctx_path: Optional[Path] = None @@ -130,6 +132,11 @@ def check_category(category: str): obj_extension = ".obj" if config.platform == Platform.X360 else ".o" base_name = Path(self.name).with_suffix("") obj.src_obj_path = build_dir / "src" / base_name.with_suffix(obj_extension) + if obj.options["section_renames"]: + obj.objdiff_obj_path = ( + obj.src_obj_path.parent + / f"{obj.src_obj_path.stem}.objdiff{obj.src_obj_path.suffix}" + ) obj.asm_obj_path = build_dir / "mod" / base_name.with_suffix(obj_extension) obj.ctx_path = build_dir / "src" / base_name.with_suffix(".ctx") return obj @@ -530,6 +537,13 @@ def generate_build_ninja( deps="gcc", ) + rename_section = config.tools_dir / "rename_section.py" + n.rule( + name="rename_sections", + command=f"$python {rename_section} $in $out $renames", + description="RENSEC $out", + ) + cargo_rule_written = False def write_cargo_rule(): @@ -1216,10 +1230,31 @@ def is_lang_flag(flag): "defines": defines, }, ) + + objdiff_output = obj.src_obj_path + section_renames = obj.options["section_renames"] + if ( + section_renames + and obj.objdiff_obj_path is not None + and obj.objdiff_obj_path not in source_added + ): + source_added.add(obj.objdiff_obj_path) + rename_args = " ".join( + f"{old_name} {new_name}" + for old_name, new_name in section_renames + ) + n.build( + outputs=obj.objdiff_obj_path, + rule="rename_sections", + inputs=obj.src_obj_path, + implicit=rename_section, + variables={"renames": rename_args}, + ) + objdiff_output = obj.objdiff_obj_path n.newline() if obj.options["add_to_all"]: - source_inputs.append(obj.src_obj_path) + source_inputs.append(objdiff_output) return obj.src_obj_path @@ -1864,7 +1899,7 @@ def add_unit( src_exists = obj.src_path is not None and obj.src_path.exists() if src_exists: - unit_config["base_path"] = obj.src_obj_path + unit_config["base_path"] = obj.objdiff_obj_path or obj.src_obj_path unit_config["metadata"]["source_path"] = obj.src_path # Filter out include directories diff --git a/tools/rename_section.py b/tools/rename_section.py new file mode 100644 index 000000000..eb99a9ee3 --- /dev/null +++ b/tools/rename_section.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Rename ELF section names by editing the section-string table in-place. + +Usage: + python tools/rename_section.py + python tools/rename_section.py [ ...] + +Section names must stay the same length because this is a byte-for-byte string-table +replacement used for objdiff-only post-processing. +""" +import struct +import sys + +def rename_sections(input_path, output_path, renames): + for old_name, new_name in renames: + assert len(old_name) == len(new_name), ( + f"Names must be same length: {old_name!r} vs {new_name!r}" + ) + + with open(input_path, "rb") as f: + data = bytearray(f.read()) + assert data[:4] == b"\x7fELF", "Not an ELF file" + endian = ">" if data[5] == 2 else "<" + e_shoff = struct.unpack_from(endian + "I", data, 32)[0] + e_shstrndx = struct.unpack_from(endian + "H", data, 50)[0] + e_shentsize = struct.unpack_from(endian + "H", data, 46)[0] + strtab_off = e_shoff + e_shstrndx * e_shentsize + strtab_sh_offset = struct.unpack_from(endian + "I", data, strtab_off + 16)[0] + strtab_sh_size = struct.unpack_from(endian + "I", data, strtab_off + 20)[0] + strtab = data[strtab_sh_offset : strtab_sh_offset + strtab_sh_size] + + for old_name, new_name in renames: + needle = old_name.encode() + b"\x00" + pos = strtab.find(needle) + if pos < 0: + continue + abs_pos = strtab_sh_offset + pos + data[abs_pos : abs_pos + len(old_name)] = new_name.encode() + + with open(output_path, "wb") as f: + f.write(data) + +def main(argv): + if len(argv) == 4: + rename_sections(argv[1], argv[1], [(argv[2], argv[3])]) + return + + if len(argv) >= 5 and (len(argv) - 3) % 2 == 0: + renames = list(zip(argv[3::2], argv[4::2])) + rename_sections(argv[1], argv[2], renames) + return + + raise SystemExit( + "Usage:\n" + " python tools/rename_section.py \n" + " python tools/rename_section.py " + " [ ...]" + ) + +if __name__ == "__main__": + main(sys.argv) 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)