mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-21 05:34:10 -04:00
Merge remote-tracking branch 'origin/main' into better-tools
# Conflicts: # files.cmake # src/dusk/game_clock.cpp # src/dusk/imgui/ImGuiConsole.cpp # src/dusk/settings.cpp # src/dusk/settings.h # src/m_Do/m_Do_main.cpp
This commit is contained in:
@@ -68,13 +68,18 @@ body:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: game-region
|
||||
id: game-disc
|
||||
attributes:
|
||||
label: Game Region
|
||||
description: The game region you are playing on
|
||||
label: Game Disc
|
||||
description: The game disc you are playing on
|
||||
options:
|
||||
- NTSC-U (North America)
|
||||
- PAL (Europe)
|
||||
- GameCube NTSC-U (North America)
|
||||
- GameCube PAL (Europe)
|
||||
- GameCube NTSC-J (Japan)
|
||||
- Wii NTSC-U (North America) Revision 0
|
||||
- Wii NTSC-U (North America) Revision 2
|
||||
- Wii PAL (Europe)
|
||||
- Wii NTSC-J (Japan)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -519,6 +519,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
|
||||
add_subdirectory(mods/ao_mod)
|
||||
add_subdirectory(mods/shadow_mod)
|
||||
add_subdirectory(mods/window_demo)
|
||||
add_subdirectory(mods/flow_demo)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
|
||||
@@ -26,7 +26,10 @@ It aims to be as accurate as possible to the original while also providing new o
|
||||
|
||||
You must dump your own copy of the game. Please see [this article](https://wiki.dolphin-emu.org/index.php?title=Ripping_Games) for instructions. After dumping, you can use a program like [Dolphin](https://dolphin-emu.org/) or [nodtool](https://github.com/encounter/nod/releases) to convert the `.iso` to `.rvz` to save space.
|
||||
|
||||
Currently, only the GameCube releases are supported. Support for other versions of the game is planned in the future.
|
||||
Dusklight currently supports all commercial discs except for Wii's Korean release.
|
||||
|
||||
> [!NOTE]
|
||||
> Dusklight is based on the [Twilight Princess decompilation](https://github.com/zeldaret/tp), which is currently only matching for GameCube. As a result, even when playing Dusklight with a Wii disc, you will be presented with the GameCube version's HUD and certain other specificities.
|
||||
|
||||
### 2. Install Dusklight
|
||||
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
- A Windows, Linux, or macOS device
|
||||
- iOS device connected to computer via USB
|
||||
- Dusklight IPA file (download the latest `Dusklight-vX.X.X-ios-arm64.ipa` from the [releases page](https://github.com/TwilitRealm/dusklight/releases))
|
||||
- Legally acquired game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL)
|
||||
- Legally acquired game disc:
|
||||
- `GZ2E01` (GameCube USA)
|
||||
- `GZ2P01` (GameCube PAL)
|
||||
- `GZ2J01` (GameCube JPN)
|
||||
- `RZDE01` (Wii USA Rev. 0 *or* Rev. 2)
|
||||
- `RZDP01` (Wii PAL)
|
||||
- `RZDJ01` (Wii JPN)
|
||||
|
||||
## 1. Install iloader
|
||||
|
||||
|
||||
+131
-2
@@ -262,14 +262,16 @@ Installs hooks on game functions and resolves symbols by name. You'll rarely cal
|
||||
### OverlayService (`mods/svc/overlay.h`)
|
||||
|
||||
Registers DVD file overlays at runtime: the dynamic counterpart to the static `overlay/` directory (see
|
||||
[Asset Overlays](#asset-overlays)). Overlay a disc path with a file from your bundle, or with a caller-owned buffer
|
||||
[Asset Overlays](#asset-overlays)). Overlay a disc path with a file from your bundle, a file within an archive,
|
||||
or with a caller-owned buffer
|
||||
(copied on registration):
|
||||
|
||||
```cpp
|
||||
IMPORT_SERVICE(OverlayService, svc_overlay);
|
||||
|
||||
OverlayHandle handle = 0;
|
||||
svc_overlay->add_file(mod_ctx, "/res/Msgus.arc", "res/replacement.arc", &handle);
|
||||
svc_overlay->add_file(mod_ctx, "/Movie/demo_movie98_00.thp", "res/replacement.thp", &handle); // Replaces the demo movie
|
||||
svc_overlay->add_file(mod_ctx, "/res/Object/Kmdl/archive/bmwr/al.bmd", "res/link_model.bmd", &handle); // Replaces link's model
|
||||
svc_overlay->add_buffer(mod_ctx, "/generated.txt", data, size, nullptr);
|
||||
svc_overlay->remove(mod_ctx, handle);
|
||||
```
|
||||
@@ -278,6 +280,9 @@ svc_overlay->remove(mod_ctx, handle);
|
||||
on the disc are added as new files. Changes are applied at the next frame boundary, and data the game already read
|
||||
stays in memory until the file is re-read: sometimes a scene reload, and in the worst case, a full restart.
|
||||
|
||||
Dusklight reloads core archive files during scene transitions so modifications to Link, Midna or other globally-loaded
|
||||
data get refreshed without a full restart.
|
||||
|
||||
See [Asset Overlays](#asset-overlays) for priority and conflict handling.
|
||||
|
||||
### TextureService (`mods/svc/texture.h`)
|
||||
@@ -636,6 +641,125 @@ first in-game frame. Projection matrices match the renderer's WebGPU clip conven
|
||||
Camera operators allow overriding the main camera. When an operator callback returns true, its values replace the camera
|
||||
state for the current frame. Register and unregister using `register_camera_operator` / `unregister_camera_operator`.
|
||||
|
||||
### GameModeService (`mods/svc/game_mode.h`)
|
||||
|
||||
Allows a mod to register a game mode with callbacks for key gameplay and save lifecycle events. Registered game modes
|
||||
appear in the prelaunch menu. Game modes may use a unique set of saves by configuring `save_name`; leave it empty to use
|
||||
the vanilla `gczelda2` save.
|
||||
|
||||
```cpp
|
||||
IMPORT_SERVICE(LogService, svc_log);
|
||||
IMPORT_SERVICE(HookService, svc_hook);
|
||||
IMPORT_SERVICE(GameModeService, svc_game_mode);
|
||||
|
||||
DEFINE_HOOK(fopAcM_createItem, CreateItem);
|
||||
|
||||
#define MY_GAME_MODE_ID "game-mode-id"
|
||||
|
||||
static HookAction my_function_hook(ModContext* ctx, void* args, void*, void*) {
|
||||
// If we wish to have this hook only run while the gamemode is registered, we need to hook the function from the
|
||||
// gamemode's onActivatedFunction, and uninstall the hook during the onDeactivatedFunction. Example below.
|
||||
// Alternatively, check with `svc_game_mode->is_active(mod_ctx, MY_GAME_MODE_ID, &active) == MOD_OK && active`.
|
||||
return HOOK_CONTINUE;
|
||||
}
|
||||
|
||||
ModResult on_game_mode_activated(void*, ModError* outError) {
|
||||
// Setup the gamemode, Add any hooks that are gamemode specific
|
||||
// Overlay any files that are gamemode specific
|
||||
ModResult result = mods::hook_add_pre<CreateItem>(svc_hook, my_function_hook);
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(outError, result, "failed to install fopAcM_createItem hook");
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult on_game_mode_deactivated(void*, ModError* outError) {
|
||||
// Uninstall any hooks that are gamemode specific
|
||||
// Remove any file overlays that are gamemode specific
|
||||
ModResult result = mods::hook_uninstall<CreateItem>();
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(outError, result, "failed to uninstall fopAcM_createItem hook");
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult on_save_loaded(void*, ModError*) {
|
||||
// This function will be invoked by the game as a save is loaded
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
const GameModeDesc gameModeDesc = {
|
||||
.struct_size = sizeof(GameModeDesc),
|
||||
.game_mode_id = MY_GAME_MODE_ID,
|
||||
.full_name = "My Game Mode",
|
||||
.save_name = "my-unique-save",
|
||||
.user_data = nullptr,
|
||||
.on_activated = on_game_mode_activated,
|
||||
.on_deactivated = on_game_mode_deactivated,
|
||||
.on_save_loaded = on_save_loaded,
|
||||
};
|
||||
svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
|
||||
```
|
||||
|
||||
A game mode can also open UI for per-save settings when creating a new file. The state begins as
|
||||
`GAME_MODE_STATE_PENDING` and remains valid until the mod selects `PROCEED` or `RETURN`.
|
||||
|
||||
```cpp
|
||||
IMPORT_SERVICE(GameModeService, svc_game_mode);
|
||||
IMPORT_SERVICE(UiService, svc_ui);
|
||||
|
||||
ModResult on_new_save_select(void*, GameModeNewSaveState* state, ModError* outError) {
|
||||
static GameModeNewSaveState* newSaveState;
|
||||
static UiWindowHandle windowHandle;
|
||||
|
||||
newSaveState = state;
|
||||
|
||||
UiTabDesc tabs[1]{};
|
||||
|
||||
tabs[0].struct_size = sizeof(UiTabDesc);
|
||||
tabs[0].title = "Play";
|
||||
tabs[0].build = [](ModContext* ctx, UiWindowHandle, UiElementHandle leftPane, UiElementHandle rightPane, void*, ModError*) {
|
||||
UiControlDesc desc = UI_CONTROL_DESC_INIT;
|
||||
desc.kind = UI_CONTROL_BUTTON;
|
||||
desc.label = "Play";
|
||||
desc.help_rml = "Play Button";
|
||||
desc.on_pressed = [](ModContext* ctx, void* userdata) {
|
||||
*newSaveState = GAME_MODE_STATE_PROCEED;
|
||||
svc_ui->window_close(ctx, *static_cast<UiWindowHandle*>(userdata));
|
||||
};
|
||||
desc.user_data = &windowHandle;
|
||||
svc_ui->pane_add_control(mod_ctx, leftPane, &desc, nullptr);
|
||||
return MOD_OK;
|
||||
};
|
||||
|
||||
UiWindowDesc desc = UI_WINDOW_DESC_INIT;
|
||||
desc.tabs = tabs;
|
||||
desc.tab_count = 1;
|
||||
desc.on_closed = [](ModContext *, UiWindowHandle, void *userdata) {
|
||||
// If closing the window through backing out, return to file select
|
||||
if (*newSaveState == GAME_MODE_STATE_PENDING) {
|
||||
*newSaveState = GAME_MODE_STATE_RETURN;
|
||||
}
|
||||
};
|
||||
|
||||
ModResult result = svc_ui->window_push(mod_ctx, &desc, &windowHandle);
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(outError, result, "failed to open new-save settings");
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
const GameModeDesc gameModeDesc = {
|
||||
.struct_size = sizeof(GameModeDesc),
|
||||
.game_mode_id = "my-game-mode-id",
|
||||
.full_name = "My Game Mode",
|
||||
.save_name = "my-unique-save",
|
||||
.on_new_save_select = on_new_save_select,
|
||||
};
|
||||
svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hooking Game Functions
|
||||
@@ -763,6 +887,11 @@ For reference parameters (e.g. `const cXyz& pos`), `arg_ref<cXyz>` yields a dire
|
||||
|
||||
Files placed under `overlay/` in the `.dusk` archive override game files at the corresponding path, equivalent to
|
||||
replacing files in the .iso. This requires no code: an archive with just `mod.json` and `overlay/` is a complete mod.
|
||||
To replace a file within an `.arc` archive, replace the archive suffix with a directory and place the replacement at
|
||||
its path within the archive.
|
||||
|
||||
- `overlay/Audiores/Stream/menu_select.ast` replaces the main title's audio stream.
|
||||
- `overlay/res/Layout/main2D/main2d/timg/midona64.bti` replaces Midna's UI icon inside `main2D.arc`.
|
||||
|
||||
Files placed under `textures/` register as texture replacements, and act just like the user's general
|
||||
`texture_replacements/` directory: Dolphin-style naming, matched by texture hash
|
||||
|
||||
Vendored
+1
-1
Submodule extern/aurora updated: 8005d336ab...0f15fb65d1
+12
@@ -1412,6 +1412,7 @@ set(DOLPHIN_FILES
|
||||
|
||||
set(DUSK_FILES
|
||||
include/helpers/batch.hpp
|
||||
include/helpers/bits.hpp
|
||||
include/helpers/endian_gx.hpp
|
||||
src/d/actor/d_a_alink_dusk.cpp
|
||||
src/dusk/OSContext.cpp
|
||||
@@ -1435,12 +1436,14 @@ set(DUSK_FILES
|
||||
src/dusk/commands.cpp
|
||||
src/dusk/commands.hpp
|
||||
src/dusk/game_clock.cpp
|
||||
src/dusk/game_mode.cpp
|
||||
src/dusk/gamepad_color.cpp
|
||||
src/dusk/globals.cpp
|
||||
src/dusk/gyro.cpp
|
||||
src/dusk/game_combos.cpp
|
||||
src/dusk/d_trigger_view.cpp
|
||||
#src/dusk/m_Do_ext_dusk.cpp
|
||||
src/dusk/hq_minimap.cpp
|
||||
src/dusk/imgui/ImGuiActorSpawner.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.cpp
|
||||
src/dusk/imgui/ImGuiBloomWindow.hpp
|
||||
@@ -1461,6 +1464,8 @@ set(DUSK_FILES
|
||||
src/dusk/imgui/ImGuiStubLog.cpp
|
||||
src/dusk/io.cpp
|
||||
src/dusk/iso_validate.cpp
|
||||
src/dusk/language.cpp
|
||||
src/dusk/language.hpp
|
||||
src/dusk/layout.cpp
|
||||
src/dusk/livesplit.cpp
|
||||
src/dusk/logging.cpp
|
||||
@@ -1477,6 +1482,9 @@ set(DUSK_FILES
|
||||
src/dusk/mods/loader/native_module.hpp
|
||||
src/dusk/mods/loader/prepatch.cpp
|
||||
src/dusk/mods/loader/prepatch.hpp
|
||||
src/dusk/mods/item.hpp
|
||||
src/dusk/mods/item_checks.cpp
|
||||
src/dusk/mods/item_gives.cpp
|
||||
src/dusk/mods/log_buffer.cpp
|
||||
src/dusk/mods/log_buffer.hpp
|
||||
src/dusk/mods/manifest.cpp
|
||||
@@ -1486,8 +1494,11 @@ set(DUSK_FILES
|
||||
src/dusk/mods/svc/config.hpp
|
||||
src/dusk/mods/svc/game.cpp
|
||||
src/dusk/mods/svc/gfx.cpp
|
||||
src/dusk/mods/svc/flow.cpp
|
||||
src/dusk/mods/svc/hook.cpp
|
||||
src/dusk/mods/svc/host.cpp
|
||||
src/dusk/mods/svc/item.cpp
|
||||
src/dusk/mods/svc/item.hpp
|
||||
src/dusk/mods/svc/log.cpp
|
||||
src/dusk/mods/svc/overlay.cpp
|
||||
src/dusk/mods/svc/registry.cpp
|
||||
@@ -1496,6 +1507,7 @@ set(DUSK_FILES
|
||||
src/dusk/mods/svc/texture.cpp
|
||||
src/dusk/mods/svc/ui.cpp
|
||||
src/dusk/mods/svc/ui.hpp
|
||||
src/dusk/mods/svc/game_mode.cpp
|
||||
src/dusk/mods/svc/window.cpp
|
||||
src/dusk/mods/svc/window.hpp
|
||||
src/dusk/mods/svc/save.cpp
|
||||
|
||||
@@ -96,6 +96,9 @@ public:
|
||||
/* 0xDE0 */ cXyz field_0xde0;
|
||||
/* 0xDEC */ u8 field_0xdec[0xdf9 - 0xdec];
|
||||
/* 0xDF9 */ u8 field_0xdf9;
|
||||
#if TARGET_PC
|
||||
bool mItemCheckOverridden;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(daE_HP_c) == 0xdfc);
|
||||
|
||||
@@ -88,6 +88,11 @@ private:
|
||||
/* 0x99C */ dPa_followEcallBack mEffect2;
|
||||
/* 0x9B0 */ Z2SoundObjSimple mSound;
|
||||
/* 0x9D0 */ u8 mIsHookCarry;
|
||||
#if TARGET_PC
|
||||
u8 mOriginalItemNo;
|
||||
bool mItemOverridden;
|
||||
bool mOverrideHover;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(daObjLife_c) == 0x9d4);
|
||||
|
||||
@@ -194,6 +194,9 @@ private:
|
||||
/* 0x9FC */ u8 field_0x9fc;
|
||||
/* 0x9FD */ u8 field_0x9fd;
|
||||
/* 0xA00 */ Mtx mDrawMtx;
|
||||
#if TARGET_PC
|
||||
u8 mOriginalItemNo;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(daTbox_c) == 0xA30);
|
||||
|
||||
@@ -83,6 +83,10 @@ private:
|
||||
/* 0xAC0 */ u8 field_0xAC0[0xAC4 - 0xAC0];
|
||||
/* 0xAC4 */ int mStaffIdx;
|
||||
/* 0xAC8 */ dBgW* mBoxBgW;
|
||||
#if TARGET_PC
|
||||
bool mParamsInit;
|
||||
u8 mOriginalItemNo;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(daTbox2_c) == 0xACC);
|
||||
|
||||
@@ -145,6 +145,10 @@ public:
|
||||
/* 0x9BC */ u8 field_0x9bc[4];
|
||||
/* 0x9C0 */ u8 field_0x9c0;
|
||||
/* 0x9C1 */ u8 field_0x9c1;
|
||||
#if TARGET_PC
|
||||
u8 mOriginalItemNo;
|
||||
bool mItemOverridden;
|
||||
#endif
|
||||
}; // Size: 0x9C4
|
||||
|
||||
#endif /* D_A_D_A_ITEM_STATIC_H */
|
||||
|
||||
@@ -50,6 +50,9 @@ public:
|
||||
csXyz* getRotateP();
|
||||
cXyz* getPosP();
|
||||
const char* getShopArcname();
|
||||
#if TARGET_PC
|
||||
const ResourceData& getResourceData() const;
|
||||
#endif
|
||||
u16 getHeapSize();
|
||||
void CreateInit();
|
||||
void set_mtx();
|
||||
@@ -110,6 +113,10 @@ private:
|
||||
/* 0x960 */ s16 mAngleX;
|
||||
/* 0x962 */ s16 mAngleY;
|
||||
/* 0x964 */ u8 mShopItemID;
|
||||
#if TARGET_PC
|
||||
ResourceData mOverrideData;
|
||||
bool mItemOverridden;
|
||||
#endif
|
||||
};
|
||||
|
||||
int CheckShopItemCreateHeap(fopAc_ac_c* i_this);
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "JSystem/J3DGraphLoader/J3DModelLoader.h"
|
||||
#include "JSystem/J3DGraphLoader/J3DAnmLoader.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "mods/svc/game_mode.h"
|
||||
#endif
|
||||
|
||||
class dFile_info_c;
|
||||
class J2DPicture;
|
||||
|
||||
@@ -420,6 +424,13 @@ public:
|
||||
bool pointerMenuSelect();
|
||||
bool pointerCopyDataToSelect();
|
||||
bool pointerYesNoSelect(bool errorSelect);
|
||||
void backToDataSelectMove() {
|
||||
headerTxtSet(0x43, 1, 0);
|
||||
fileRecScaleAnmInitSet2(0.0f, 1.0f);
|
||||
nameMoveAnmInitSet(0xd29, 0xd1f);
|
||||
modoruTxtDispAnmInit(0);
|
||||
mDataSelProc = DATASELPROC_NAME_TO_DATA_SELECT_MOVE;
|
||||
}
|
||||
#endif
|
||||
void _draw();
|
||||
void errorMoveAnmInitSet(int, int);
|
||||
@@ -733,6 +744,8 @@ public:
|
||||
#endif
|
||||
#ifdef TARGET_PC
|
||||
dDlst_FileSelFade_c mFadeDlst;
|
||||
bool mGameModeSaveStartBuildUi = true;
|
||||
GameModeNewSaveState mGameModeNewSaveState = GAME_MODE_STATE_PENDING;
|
||||
#endif
|
||||
|
||||
#if PLATFORM_WII || PLATFORM_SHIELD
|
||||
|
||||
+3
-1
@@ -11,7 +11,9 @@ public:
|
||||
static DUSK_GAME_DATA u8* mData;
|
||||
};
|
||||
|
||||
void execItemGet(u8 item_id);
|
||||
class fopAc_ac_c;
|
||||
void execItemGet(
|
||||
u8 item_id IF_DUSK_ARG(u32 item_give_tag = 0) IF_DUSK_ARG(fopAc_ac_c* giver = NULL));
|
||||
|
||||
void item_func_HEART();
|
||||
void item_func_GREEN_RUPEE();
|
||||
|
||||
+14
-12
@@ -27,18 +27,20 @@ public:
|
||||
|
||||
// Attributes
|
||||
/* 0x04 */ BE(u16) message_id;
|
||||
/* 0x06 */ BE(u16) event_label_id;
|
||||
/* 0x08 */ u8 se_speaker;
|
||||
/* 0x09 */ u8 fuki_kind;
|
||||
/* 0x0A */ u8 output_type;
|
||||
/* 0x0B */ u8 fuki_pos_type;
|
||||
/* 0x0C */ u8 unk_0xc;
|
||||
/* 0x0D */ u8 unk_0xd;
|
||||
/* 0x0E */ u8 se_mood;
|
||||
/* 0x0F */ u8 camera_id;
|
||||
/* 0x10 */ u8 base_anm_id;
|
||||
/* 0x11 */ u8 face_anm_id;
|
||||
/* 0x12 */ BE(u16) unk_0x12;
|
||||
/* 0x06 */ BE(u16) event_label_id; // saveBitLabels index set when the message displays
|
||||
/* 0x08 */ u8 speaker; // Z2SpeechMgr2 voice bank ID
|
||||
/* 0x09 */ u8 box_kind; // screen class, see dMsgObject_c::talkStartInit
|
||||
/* 0x0A */ u8 draw_type; // text pacing, see jmessage_tSequenceProcessor::do_begin
|
||||
/* 0x0B */ u8 box_position; // see dMsgObject_c::fukiPosCalc
|
||||
/* 0x0C */ u8 item_no; // unused; legacy dItemNo, 0xFF = none
|
||||
/* 0x0D */ u8 line_alignment; // 0 centered (JP only), 1 left; also copied to
|
||||
// jmessage_tReference::mForm
|
||||
/* 0x0E */ u8 speaker_mood; // grunt emotion index for the voice bank
|
||||
/* 0x0F */ u8 camera_attr; // 1-10 talk-actor slot, >=11 talk-camera style
|
||||
/* 0x10 */ u8 talk_anim; // NPC talk motion attribute
|
||||
/* 0x11 */ u8 face_anim; // NPC talk face attribute
|
||||
/* 0x12 */ u8 lines_per_page; // unused; runtime uses getLineMax()
|
||||
/* 0x13 */ u8 _pad;
|
||||
};
|
||||
|
||||
class JMSMesgInfo_c {
|
||||
|
||||
@@ -22,7 +22,7 @@ struct msg_class;
|
||||
// all mesg_flow_node structs members might be wrong
|
||||
struct mesg_flow_node {
|
||||
/* 0x00 */ u8 type;
|
||||
/* 0x01 */ u8 field_0x1;
|
||||
/* 0x01 */ u8 subtype;
|
||||
/* 0x02 */ BE(u16) msg_index;
|
||||
/* 0x04 */ BE(u16) next_node_idx;
|
||||
/* 0x06 */ BE(u16) unk_0x6;
|
||||
@@ -30,7 +30,7 @@ struct mesg_flow_node {
|
||||
|
||||
struct mesg_flow_node_branch {
|
||||
/* 0x00 */ u8 type;
|
||||
/* 0x01 */ u8 field_0x1;
|
||||
/* 0x01 */ u8 result_count;
|
||||
/* 0x02 */ BE(u16) query_idx;
|
||||
/* 0x04 */ BE(u16) param;
|
||||
/* 0x06 */ BE(u16) next_node_idx;
|
||||
|
||||
@@ -280,13 +280,13 @@ public:
|
||||
/* 0x150 */ f32 field_0x150;
|
||||
/* 0x154 */ u32 mMessageID;
|
||||
/* 0x158 */ u32 field_0x158;
|
||||
/* 0x15C */ u32 field_0x15c;
|
||||
/* 0x15C */ u32 mSelectMessageID; // message ID of the selection options message; 1000 = none
|
||||
/* 0x160 */ int mIdx;
|
||||
/* 0x164 */ u16 mNodeIdx;
|
||||
/* 0x166 */ u16 field_0x166;
|
||||
/* 0x168 */ u16 field_0x168;
|
||||
/* 0x16A */ s16 field_0x16a;
|
||||
/* 0x16C */ s16 field_0x16c;
|
||||
/* 0x16C */ s16 mCurrentGroupID; // group whose BMG is parsed; -1 none, 0 common, 1-8 stage
|
||||
/* 0x16E */ s16 field_0x16e;
|
||||
/* 0x170 */ s16 mNowTalkFlowNo;
|
||||
/* 0x172 */ s16 field_0x172;
|
||||
|
||||
@@ -318,6 +318,11 @@ public:
|
||||
/* 0x566 */ s8 field_0x566;
|
||||
/* 0x567 */ s8 field_0x567;
|
||||
|
||||
#if TARGET_PC
|
||||
u32 mItemGiveTag;
|
||||
u8 mItemGiveOriginalNo;
|
||||
#endif
|
||||
|
||||
#if !__MWERKS__
|
||||
s8 actor_last_base_field;
|
||||
#endif
|
||||
|
||||
@@ -79,6 +79,10 @@ struct fopAcM_prm_class {
|
||||
/* 0x1C */ fpc_ProcID parent_id;
|
||||
/* 0x20 */ s8 argument;
|
||||
/* 0x21 */ s8 room_no;
|
||||
#if TARGET_PC
|
||||
u32 mItemGiveTag;
|
||||
u8 mItemGiveOriginalNo;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct fopAcM_search4ev_prm {
|
||||
@@ -516,8 +520,9 @@ s32 fopAcM_SearchByName(s16 i_procName, fopAc_ac_c** i_outActor);
|
||||
fopAcM_prm_class* fopAcM_CreateAppend();
|
||||
|
||||
fopAcM_prm_class* createAppend(u16 i_setId, u32 i_parameters, const cXyz* i_pos, int i_roomNo,
|
||||
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
|
||||
fpc_ProcID i_parentId);
|
||||
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
|
||||
fpc_ProcID i_parentId IF_DUSK_ARG(u32 i_itemGiveTag = 0)
|
||||
IF_DUSK_ARG(u8 i_itemOriginalNo = 0xFF));
|
||||
|
||||
void fopAcM_Log(fopAc_ac_c const* i_actor, char const* i_message);
|
||||
|
||||
@@ -526,19 +531,20 @@ s32 fopAcM_delete(fopAc_ac_c* i_actor);
|
||||
s32 fopAcM_delete(fpc_ProcID i_actorID);
|
||||
|
||||
fpc_ProcID fopAcM_create(s16 i_procName, u16 i_setId, u32 i_parameters, const cXyz* i_pos,
|
||||
int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
|
||||
createFunc i_createFunc);
|
||||
int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
|
||||
createFunc i_createFunc IF_DUSK_ARG(u32 i_itemGiveTag = 0));
|
||||
|
||||
fpc_ProcID fopAcM_create(s16 i_procName, u32 i_parameters, const cXyz* i_pos, int i_roomNo,
|
||||
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument);
|
||||
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument IF_DUSK_ARG(u32 i_itemGiveTag = 0));
|
||||
|
||||
inline fpc_ProcID fopAcM_Create(s16 i_procName, createFunc i_createFunc, void* params) {
|
||||
return fpcM_Create(i_procName, i_createFunc,params);
|
||||
}
|
||||
|
||||
fopAc_ac_c* fopAcM_fastCreate(s16 i_procName, u32 i_parameters, const cXyz* i_pos, int i_roomNo,
|
||||
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
|
||||
createFunc i_createFunc, void* i_createFuncData);
|
||||
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, createFunc i_createFunc,
|
||||
void* i_createFuncData IF_DUSK_ARG(u32 i_itemGiveTag = 0)
|
||||
IF_DUSK_ARG(u8 i_itemOriginalNo = 0xFF));
|
||||
|
||||
fopAc_ac_c* fopAcM_fastCreate(const char* i_actorname, u32 i_parameters, const cXyz* i_pos,
|
||||
int i_roomNo, const csXyz* i_angle, const cXyz* i_scale,
|
||||
@@ -623,11 +629,11 @@ fopAc_ac_c* fopAcM_getItemEventPartner(const fopAc_ac_c*);
|
||||
fopAc_ac_c* fopAcM_getEventPartner(const fopAc_ac_c*);
|
||||
|
||||
fpc_ProcID fopAcM_createItemForPresentDemo(cXyz const* i_pos, int i_itemNo, u8 param_2,
|
||||
int i_itemBitNo, int i_roomNo, csXyz const* i_angle,
|
||||
cXyz const* i_scale);
|
||||
int i_itemBitNo, int i_roomNo, csXyz const* i_angle,
|
||||
cXyz const* i_scale IF_DUSK_ARG(u32 i_itemGiveTag = 0));
|
||||
|
||||
fpc_ProcID fopAcM_createItemForTrBoxDemo(cXyz const* i_pos, int i_itemNo, int i_itemBitNo,
|
||||
int i_roomNo, csXyz const* i_angle, cXyz const* i_scale);
|
||||
int i_roomNo, csXyz const* i_angle, cXyz const* i_scale IF_DUSK_ARG(u32 i_itemGiveTag = 0));
|
||||
|
||||
u8 fopAcM_getItemNoFromTableNo(u8 i_tableNo);
|
||||
|
||||
@@ -641,11 +647,12 @@ fpc_ProcID fopAcM_createItemFromTable(cXyz const* i_pos, int i_tableNo, int i_it
|
||||
bool i_createDirect);
|
||||
|
||||
fpc_ProcID fopAcM_createDemoItem(const cXyz* i_pos, int i_itemNo, int i_itemBitNo,
|
||||
const csXyz* i_angle, int i_roomNo, const cXyz* scale, u8 param_7);
|
||||
const csXyz* i_angle, int i_roomNo, const cXyz* scale,
|
||||
u8 param_7 IF_DUSK_ARG(u32 i_itemGiveTag = 0));
|
||||
|
||||
fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomNo,
|
||||
const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF,
|
||||
f32 i_speedY, int param_8);
|
||||
const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF, f32 i_speedY,
|
||||
int param_8 IF_DUSK_ARG(const char* i_itemCheckName = NULL));
|
||||
|
||||
fpc_ProcID fopAcM_createItemForMidBoss(const cXyz* i_pos, int i_itemNo, int i_roomNo,
|
||||
const csXyz* i_angle, const cXyz* i_scale, int param_6,
|
||||
|
||||
@@ -30,7 +30,7 @@ struct msg_class {
|
||||
/* 0xDC */ fopAc_ac_c* talk_actor;
|
||||
/* 0xE0 */ cXyz pos;
|
||||
/* 0xEC */ u32 msg_idx;
|
||||
/* 0xF0 */ u32 field_0xf0;
|
||||
/* 0xF0 */ u32 select_msg_idx; // selection options message ID; 1000 = none
|
||||
/* 0xF4 */ u32 field_0xf4;
|
||||
/* 0xF8 */ u16 mode;
|
||||
/* 0xFA */ u8 select_idx;
|
||||
|
||||
@@ -19,7 +19,7 @@ struct fopMsg_prm_class {
|
||||
/* 0x00 */ fopAc_ac_c* talk_actor;
|
||||
/* 0x04 */ cXyz pos;
|
||||
/* 0x10 */ u32 msg_idx;
|
||||
/* 0x14 */ u32 field_0x14;
|
||||
/* 0x14 */ u32 select_msg_idx; // selection options message ID; 1000 = none
|
||||
/* 0x18 */ fpc_ProcID field_0x18;
|
||||
}; // Size: 0x1C
|
||||
|
||||
@@ -46,8 +46,8 @@ void fopMsgM_setMessageID(fpc_ProcID msg_id);
|
||||
void fopMsgM_destroyExpHeap(JKRExpHeap* i_heap);
|
||||
f32 fopMsgM_valueIncrease(int param_0, int param_1, u8 i_type);
|
||||
s32 fopMsgM_setStageLayer(void* i_process);
|
||||
fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, fopAc_ac_c* i_talkActor, u32 param_2);
|
||||
fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, u32 param_1);
|
||||
fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, fopAc_ac_c* i_talkActor, u32 i_selectMsgIdx);
|
||||
fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, u32 i_selectMsgIdx);
|
||||
fpc_ProcID fopMsgM_messageSetDemo(u32 i_msgidx);
|
||||
msg_class* fopMsgM_SearchByID(fpc_ProcID i_id);
|
||||
TEXT_SPAN fopMsgM_messageGet(TEXT_SPAN i_stringBuf, u32 i_msgId);
|
||||
|
||||
@@ -256,4 +256,17 @@ using std::isnan;
|
||||
#define DUSK_CONST IF_DUSK(const)
|
||||
#define DUSK_CONSTEXPR IF_DUSK(constexpr)
|
||||
|
||||
#if TARGET_PC && defined(DUSK_BUILDING_GAME)
|
||||
#include "dusk/mods/item.hpp"
|
||||
#define DUSK_ITEM_CHECK(name, item_no, giver) \
|
||||
(item_no) = ::dusk::mods::item_check(name, (item_no), giver)
|
||||
#define DUSK_ITEM_CHECK_EXPR(name, item_no, giver) \
|
||||
(::dusk::mods::item_check(name, (item_no), giver))
|
||||
#define DUSK_GIVE_TAG(name) IF_DUSK_ARG(::dusk::mods::item_give_tag(name))
|
||||
#else
|
||||
#define DUSK_ITEM_CHECK(name, item_no, giver)
|
||||
#define DUSK_ITEM_CHECK_EXPR(name, item_no, giver) (item_no)
|
||||
#define DUSK_GIVE_TAG(name)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
namespace dusk {
|
||||
namespace detail {
|
||||
|
||||
template <size_t Size>
|
||||
struct uint_of_size;
|
||||
|
||||
template <>
|
||||
struct uint_of_size<1> {
|
||||
using type = uint8_t;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct uint_of_size<2> {
|
||||
using type = uint16_t;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct uint_of_size<4> {
|
||||
using type = uint32_t;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct uint_of_size<8> {
|
||||
using type = uint64_t;
|
||||
};
|
||||
|
||||
template <size_t Size>
|
||||
using uint_of_size_t = uint_of_size<Size>::type;
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_trivially_copyable_v<T>)
|
||||
T unaligned_load(const void* source) noexcept {
|
||||
T value;
|
||||
std::memcpy(&value, source, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_trivially_copyable_v<T>)
|
||||
void unaligned_store(void* destination, T value) noexcept {
|
||||
std::memcpy(destination, &value, sizeof(value));
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_unsigned_v<T>)
|
||||
constexpr T bswap(T value) noexcept {
|
||||
if constexpr (sizeof(T) == 1) {
|
||||
return value;
|
||||
} else if constexpr (sizeof(T) == 2) {
|
||||
return static_cast<T>((value << 8) | (value >> 8));
|
||||
} else if constexpr (sizeof(T) == 4) {
|
||||
return static_cast<T>(((value & 0x000000ffU) << 24) | ((value & 0x0000ff00U) << 8) |
|
||||
((value & 0x00ff0000U) >> 8) | ((value & 0xff000000U) >> 24));
|
||||
} else {
|
||||
static_assert(sizeof(T) == 8);
|
||||
return static_cast<T>(
|
||||
((value & 0x00000000000000ffULL) << 56) | ((value & 0x000000000000ff00ULL) << 40) |
|
||||
((value & 0x0000000000ff0000ULL) << 24) | ((value & 0x00000000ff000000ULL) << 8) |
|
||||
((value & 0x000000ff00000000ULL) >> 8) | ((value & 0x0000ff0000000000ULL) >> 24) |
|
||||
((value & 0x00ff000000000000ULL) >> 40) | ((value & 0xff00000000000000ULL) >> 56));
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads an unaligned integral value in the specified byte order.
|
||||
template <typename T>
|
||||
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
|
||||
T read_bits(const void* source, std::endian endian = std::endian::big) noexcept {
|
||||
using Bits = std::make_unsigned_t<T>;
|
||||
Bits value = detail::unaligned_load<Bits>(source);
|
||||
if constexpr (sizeof(Bits) > 1) {
|
||||
if (endian != std::endian::native) {
|
||||
value = bswap(value);
|
||||
}
|
||||
}
|
||||
return std::bit_cast<T>(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
|
||||
constexpr T read_bits(const uint8_t* source, std::endian endian = std::endian::big) noexcept {
|
||||
if (!std::is_constant_evaluated()) {
|
||||
return read_bits<T>(static_cast<const void*>(source), endian);
|
||||
}
|
||||
using Bits = std::make_unsigned_t<T>;
|
||||
Bits value{};
|
||||
if (endian == std::endian::big) {
|
||||
for (size_t i = 0; i < sizeof(Bits); ++i) {
|
||||
value = static_cast<Bits>((value << 8) | source[i]);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < sizeof(Bits); ++i) {
|
||||
value |= static_cast<Bits>(source[i]) << (i * 8);
|
||||
}
|
||||
}
|
||||
return std::bit_cast<T>(value);
|
||||
}
|
||||
|
||||
/// Reads an unaligned floating-point value in the specified byte order.
|
||||
template <typename T>
|
||||
requires(
|
||||
std::is_floating_point_v<T> && requires { typename detail::uint_of_size_t<sizeof(T)>; })
|
||||
T read_bits(const void* source, std::endian endian = std::endian::big) noexcept {
|
||||
using Bits = detail::uint_of_size_t<sizeof(T)>;
|
||||
return std::bit_cast<T>(read_bits<Bits>(source, endian));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(
|
||||
std::is_floating_point_v<T> && requires { typename detail::uint_of_size_t<sizeof(T)>; })
|
||||
constexpr T read_bits(const uint8_t* source, std::endian endian = std::endian::big) noexcept {
|
||||
using Bits = detail::uint_of_size_t<sizeof(T)>;
|
||||
return std::bit_cast<T>(read_bits<Bits>(source, endian));
|
||||
}
|
||||
|
||||
/// Writes an unaligned integral value in the specified byte order.
|
||||
template <typename T>
|
||||
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
|
||||
void write_bits(void* destination, T value, std::endian endian = std::endian::big) noexcept {
|
||||
using Bits = std::make_unsigned_t<T>;
|
||||
Bits bits = std::bit_cast<Bits>(value);
|
||||
if constexpr (sizeof(Bits) > 1) {
|
||||
if (endian != std::endian::native) {
|
||||
bits = bswap(bits);
|
||||
}
|
||||
}
|
||||
detail::unaligned_store(destination, bits);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
|
||||
constexpr void write_bits(
|
||||
uint8_t* destination, T value, std::endian endian = std::endian::big) noexcept {
|
||||
if (!std::is_constant_evaluated()) {
|
||||
write_bits(static_cast<void*>(destination), value, endian);
|
||||
return;
|
||||
}
|
||||
using Bits = std::make_unsigned_t<T>;
|
||||
const Bits bits = std::bit_cast<Bits>(value);
|
||||
if (endian == std::endian::big) {
|
||||
for (size_t i = 0; i < sizeof(Bits); ++i) {
|
||||
destination[sizeof(Bits) - i - 1] = static_cast<uint8_t>(bits >> (i * 8));
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < sizeof(Bits); ++i) {
|
||||
destination[i] = static_cast<uint8_t>(bits >> (i * 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes an unaligned floating-point value in the specified byte order.
|
||||
template <typename T>
|
||||
requires(
|
||||
std::is_floating_point_v<T> && requires { typename detail::uint_of_size_t<sizeof(T)>; })
|
||||
void write_bits(void* destination, T value, std::endian endian = std::endian::big) noexcept {
|
||||
using Bits = detail::uint_of_size_t<sizeof(T)>;
|
||||
write_bits(destination, std::bit_cast<Bits>(value), endian);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(
|
||||
std::is_floating_point_v<T> && requires { typename detail::uint_of_size_t<sizeof(T)>; })
|
||||
constexpr void write_bits(
|
||||
uint8_t* destination, T value, std::endian endian = std::endian::big) noexcept {
|
||||
using Bits = detail::uint_of_size_t<sizeof(T)>;
|
||||
write_bits(destination, std::bit_cast<Bits>(value), endian);
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
@@ -112,6 +112,11 @@ public:
|
||||
mSerialNo = serial_no;
|
||||
}
|
||||
|
||||
#ifdef TARGET_PC
|
||||
void setFileName(const std::string& fileName);
|
||||
const char* getFileName();
|
||||
#endif
|
||||
|
||||
/* 0x0000 */ u8 mData[SAVEFILE_SIZE];
|
||||
/* 0x1FBC */ u8 mChannel;
|
||||
/* 0x1FBD */ u8 mCopyToPos;
|
||||
@@ -124,6 +129,11 @@ public:
|
||||
/* 0x1FEC */ s32 mNandState;
|
||||
/* 0x1FF0 */ u64 mSerialNo;
|
||||
/* 0x1FF8 */ u32 mDataVersion;
|
||||
#ifdef TARGET_PC
|
||||
bool mInitialized;
|
||||
std::string mFileName;
|
||||
#endif
|
||||
|
||||
}; // Size: 0x2000
|
||||
|
||||
STATIC_ASSERT(sizeof(mDoMemCd_Ctrl_c) == 8192);
|
||||
@@ -230,4 +240,14 @@ inline s32 mDoMemCd_checkNANDFile() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TARGET_PC
|
||||
inline void mDoMemCd_SetFileName(const std::string& fileName) {
|
||||
g_mDoMemCd_control.setFileName(fileName);
|
||||
}
|
||||
|
||||
inline const char* mDoMemCd_GetFileName() {
|
||||
return g_mDoMemCd_control.getFileName();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* M_DO_M_DO_MEMCARD_H */
|
||||
|
||||
@@ -6,11 +6,17 @@
|
||||
#include "global.h"
|
||||
#include "helpers/endian.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#endif
|
||||
|
||||
class JKRHeap;
|
||||
|
||||
/**
|
||||
* @ingroup jsystem-jkernel
|
||||
*
|
||||
*
|
||||
*/
|
||||
struct SArcHeader {
|
||||
/* 0x00 */ BE(u32) signature;
|
||||
@@ -25,7 +31,7 @@ struct SArcHeader {
|
||||
|
||||
/**
|
||||
* @ingroup jsystem-jkernel
|
||||
*
|
||||
*
|
||||
*/
|
||||
struct SArcDataInfo {
|
||||
/* 0x00 */ BE(u32) num_nodes;
|
||||
@@ -59,7 +65,7 @@ extern u32 sCurrentDirID__10JKRArchive; // JKRArchive::sCurrentDirID
|
||||
|
||||
/**
|
||||
* @ingroup jsystem-jkernel
|
||||
*
|
||||
*
|
||||
*/
|
||||
class JKRArchive : public JKRFileLoader {
|
||||
public:
|
||||
@@ -192,9 +198,7 @@ public:
|
||||
u32 countFile() const { return mArcInfoBlock->num_file_entries; }
|
||||
s32 countDirectory() const { return mArcInfoBlock->num_nodes; }
|
||||
u8 getMountMode() const { return mMountMode; }
|
||||
bool isFileEntry(u32 param_0) const {
|
||||
return getFileAttribute(param_0) & 1;
|
||||
}
|
||||
bool isFileEntry(u32 param_0) const { return getFileAttribute(param_0) & 1; }
|
||||
|
||||
public:
|
||||
/* 0x00 */ // vtable
|
||||
@@ -210,7 +214,36 @@ public:
|
||||
/* 0x54 */ const char* mStringTable;
|
||||
|
||||
#if TARGET_PC
|
||||
u32 getFileSize(SDIFileEntry* entry) const;
|
||||
void* getOverlayData(SDIFileEntry* entry, u32* outSize);
|
||||
bool getOverlayFileSize(SDIFileEntry* entry, u32* outSize) const;
|
||||
static void notifyOverlayFilesChanged();
|
||||
|
||||
protected:
|
||||
void** mFileData;
|
||||
|
||||
struct ArcOverlayResource {
|
||||
u32 entryIndex;
|
||||
u32 size;
|
||||
u64 generation;
|
||||
};
|
||||
|
||||
// Resource pointers remain valid until removed through the JKR resource APIs.
|
||||
// That way, any pointers to old overlay data remain valid even when the overlay changed.
|
||||
std::unordered_map<void*, ArcOverlayResource> mArcOverlayResources;
|
||||
mutable std::unordered_map<u32, void*> mActiveArcOverlayResources;
|
||||
mutable std::string mArcOverlaysPath;
|
||||
mutable std::unordered_map<u32, std::string> mIdxToPathMap;
|
||||
mutable bool mArcOverlaysPathResolved = false;
|
||||
|
||||
bool buildArcOverlaysPath() const;
|
||||
void buildIndexToPathMap(u32 dirIndex, const std::string& currentPath) const;
|
||||
bool getOverlayPath(SDIFileEntry* entry, std::string& path) const;
|
||||
void* getActiveOverlayData(SDIFileEntry* entry, u32* outSize) const;
|
||||
bool copyOverlayData(void* buffer, u32 bufferSize, SDIFileEntry* entry, u32* outSize);
|
||||
bool getOverlayResourceSize(const void* data, u32* outSize) const;
|
||||
bool removeOverlayResource(void* resource, bool freeResource);
|
||||
void removeAllOverlayResources();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
@@ -234,7 +267,7 @@ public:
|
||||
} else if (attr & JKRARCHIVE_ATTR_YAZ0) {
|
||||
return COMPRESSION_YAZ0;
|
||||
} else {
|
||||
return COMPRESSION_YAY0;
|
||||
return COMPRESSION_YAY0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +276,9 @@ public:
|
||||
|
||||
protected:
|
||||
static DUSK_GAME_DATA u32 sCurrentDirID;
|
||||
#if TARGET_PC
|
||||
static std::atomic<u64> sArcOverlayGeneration;
|
||||
#endif
|
||||
};
|
||||
|
||||
inline JKRCompression JKRConvertAttrToCompressionType(int attr) {
|
||||
@@ -261,8 +297,8 @@ inline bool JKRRemoveResource(void* resource, JKRFileLoader* fileLoader) {
|
||||
return JKRFileLoader::removeResource(resource, fileLoader);
|
||||
}
|
||||
|
||||
inline JKRArchive* JKRMountArchive(void* ptr, JKRHeap* heap,
|
||||
JKRArchive::EMountDirection mountDirection) {
|
||||
inline JKRArchive* JKRMountArchive(
|
||||
void* ptr, JKRHeap* heap, JKRArchive::EMountDirection mountDirection) {
|
||||
return JKRArchive::mount(ptr, heap, mountDirection);
|
||||
}
|
||||
|
||||
|
||||
@@ -174,10 +174,14 @@ struct TProcessor {
|
||||
}
|
||||
void on_character(int iCharacter) { do_character(iCharacter); }
|
||||
|
||||
#if TARGET_PC
|
||||
const char* on_message_limited(u16 u16Index) const;
|
||||
#else
|
||||
const char* on_message_limited(u16 u16Index) const {
|
||||
JUT_ASSERT(482, pResourceCache_!=NULL);
|
||||
return pResourceCache_->getMessageText_messageIndex(u16Index);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool on_setBegin_isReady_() const { return do_setBegin_isReady_(); }
|
||||
|
||||
@@ -195,6 +199,10 @@ struct TProcessor {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
void* getMessageEntry_messageCode(u16 u16Code, u16 u16Index) const;
|
||||
const char* getMessageText_messageCode(u16 u16Code, u16 u16Index) const;
|
||||
#else
|
||||
void* getMessageEntry_messageCode(u16 u16Code, u16 u16Index) const {
|
||||
const TResource* pResource = getResource_groupID(u16Code);
|
||||
|
||||
@@ -214,6 +222,7 @@ struct TProcessor {
|
||||
|
||||
return pResourceCache_->getMessageText_messageEntry(pEntry);
|
||||
}
|
||||
#endif
|
||||
|
||||
void stack_pushCurrent_(const char* pszText) {
|
||||
oStack_.push(getCurrent());
|
||||
|
||||
@@ -17,7 +17,7 @@ size_t JASResArcLoader::getResSize(JKRArchive const* i_archiveP, u16 i_resourceI
|
||||
return 0;
|
||||
}
|
||||
|
||||
return file->data_size;
|
||||
return DUSK_IF_ELSE(i_archiveP->getFileSize(file), file->data_size);
|
||||
}
|
||||
|
||||
size_t JASResArcLoader::getResMaxSize(JKRArchive const* i_archiveP) {
|
||||
@@ -27,9 +27,16 @@ size_t JASResArcLoader::getResMaxSize(JKRArchive const* i_archiveP) {
|
||||
for (index = 0; index < fileEntries; index++) {
|
||||
JKRArchive::SDIFileEntry* file = i_archiveP->findIdxResource(index);
|
||||
if (file) {
|
||||
#if TARGET_PC
|
||||
const u32 fileSize = i_archiveP->getFileSize(file);
|
||||
if (maxSize < fileSize) {
|
||||
maxSize = fileSize;
|
||||
}
|
||||
#else
|
||||
if (maxSize < file->data_size) {
|
||||
maxSize = file->data_size;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ void JFWDisplay::ctor_subroutine(bool enableAlpha) {
|
||||
mTickRate = 0;
|
||||
mCombinationRatio = 0.0f;
|
||||
field_0x30 = 0;
|
||||
field_0x2c = OSGetTick();
|
||||
field_0x2c = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
|
||||
field_0x34 = 0;
|
||||
field_0x48 = 0;
|
||||
field_0x4a = 0;
|
||||
@@ -258,7 +258,7 @@ void JFWDisplay::beginRender() {
|
||||
waitForTick(mTickRate, mFrameRate);
|
||||
JUTVideo::getManager()->waitRetraceIfNeed();
|
||||
|
||||
OSTick tick = OSGetTick();
|
||||
OSTick tick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
|
||||
field_0x30 = tick - field_0x2c;
|
||||
field_0x2c = tick;
|
||||
field_0x34 = field_0x2c - JUTVideo::getVideoLastTick();
|
||||
@@ -371,7 +371,7 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast<std::chrono::nanosecond
|
||||
constexpr auto RETRACE_PERIOD = FRAME_PERIOD / 2;
|
||||
|
||||
static void waitPrecise(Limiter& limiter, Limiter::duration_t targetNs) {
|
||||
const auto sleepTime = limiter.Sleep(targetNs);
|
||||
const auto sleepTime = limiter.Sleep(targetNs);
|
||||
dusk::frameUsagePct =
|
||||
100.0f * (1.0f - static_cast<float>(sleepTime) / static_cast<float>(targetNs));
|
||||
}
|
||||
@@ -381,15 +381,12 @@ static void waitForTick(u32 p1, u16 p2) {
|
||||
#if TARGET_PC
|
||||
static Limiter limiter;
|
||||
|
||||
if (dusk::frame_interp::is_enabled() && !dusk::getTransientSettings().skipFrameRateLimit) {
|
||||
dusk::frameUsagePct = 0.f;
|
||||
return;
|
||||
if (dusk::frame_interp::is_enabled() || dusk::getTransientSettings().turboMode) {
|
||||
limiter.Reset();
|
||||
dusk::frameUsagePct = 0.f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (dusk::getTransientSettings().skipFrameRateLimit) {
|
||||
p1 = OS_TIMER_CLOCK / 120;
|
||||
}
|
||||
|
||||
if (fopOvlpM_IsPeek() && dusk::getTransientSettings().stateShareLoadActive) {
|
||||
return;
|
||||
}
|
||||
@@ -399,7 +396,6 @@ static void waitForTick(u32 p1, u16 p2) {
|
||||
|
||||
if (p1 != 0) {
|
||||
#if TARGET_PC
|
||||
static Limiter limiter;
|
||||
waitPrecise(limiter, static_cast<Uint64>(OSTicksToMicroseconds(p1)) * 1000ULL);
|
||||
#else
|
||||
static OSTime nextTick = OSGetTime();
|
||||
@@ -413,7 +409,6 @@ static void waitForTick(u32 p1, u16 p2) {
|
||||
} else {
|
||||
u32 uVar1 = (p2 == 0) ? 1 : p2;
|
||||
#if TARGET_PC
|
||||
static Limiter limiter;
|
||||
waitPrecise(limiter, static_cast<Uint64>((RETRACE_PERIOD * uVar1).count()));
|
||||
#else
|
||||
static u32 nextCount = VIGetRetraceCount();
|
||||
|
||||
@@ -196,6 +196,13 @@ cleanup:
|
||||
|
||||
void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) {
|
||||
JUT_ASSERT(442, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (void* data = getOverlayData(pEntry, pOutSize); data != nullptr) {
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 outSize;
|
||||
u8* outBuf;
|
||||
if (pOutSize == NULL) {
|
||||
@@ -231,6 +238,13 @@ void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) {
|
||||
void* JKRAramArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* pEntry,
|
||||
u32* resourceSize) {
|
||||
JUT_ASSERT(515, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (copyOverlayData(buffer, bufferSize, pEntry, resourceSize)) {
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 size = pEntry->data_size;
|
||||
if (size > bufferSize) {
|
||||
size = bufferSize;
|
||||
@@ -337,6 +351,12 @@ u32 JKRAramArchive::getExpandedResSize(const void* ptr) const {
|
||||
return this->getResSize(ptr);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (u32 size; getOverlayResourceSize(ptr, &size)) {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
|
||||
JKRArchive::SDIFileEntry* entry = this->findPtrResource(ptr);
|
||||
if (entry == NULL) {
|
||||
return 0xFFFFFFFF;
|
||||
|
||||
@@ -7,6 +7,37 @@
|
||||
|
||||
#if TARGET_PC
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
#include "JSystem/JKernel/JKRDvdRipper.h"
|
||||
#if _WIN32
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
std::atomic<u64> JKRArchive::sArcOverlayGeneration{0};
|
||||
|
||||
namespace {
|
||||
|
||||
void* alloc_overlay_buffer(u32 size) {
|
||||
#if _WIN32
|
||||
return _aligned_malloc(size, alignof(std::max_align_t));
|
||||
#else
|
||||
return std::malloc(size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void free_overlay_buffer(void* data) {
|
||||
#if _WIN32
|
||||
_aligned_free(data);
|
||||
#else
|
||||
std::free(data);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
DUSK_GAME_DATA u32 JKRArchive::sCurrentDirID;
|
||||
@@ -42,6 +73,7 @@ JKRArchive::JKRArchive(s32 entryNumber, JKRArchive::EMountMode mountMode) {
|
||||
|
||||
JKRArchive::~JKRArchive() {
|
||||
#if TARGET_PC
|
||||
removeAllOverlayResources();
|
||||
if (mFileData != nullptr) {
|
||||
JKRHeap::getSystemHeap()->free(mFileData);
|
||||
mFileData = nullptr;
|
||||
@@ -270,4 +302,240 @@ void JKRArchive::initFileDataPointers() {
|
||||
mFiles[i].index = i;
|
||||
}
|
||||
}
|
||||
|
||||
void JKRArchive::notifyOverlayFilesChanged() {
|
||||
sArcOverlayGeneration.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool JKRArchive::buildArcOverlaysPath() const {
|
||||
if (mArcOverlaysPathResolved) {
|
||||
return !mArcOverlaysPath.empty();
|
||||
}
|
||||
mArcOverlaysPathResolved = true;
|
||||
|
||||
if (mEntryNum < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char pathBuffer[1024];
|
||||
if (!DVDConvertEntrynumToPath(mEntryNum, pathBuffer, sizeof(pathBuffer))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path{pathBuffer};
|
||||
constexpr std::string_view extension{".arc"};
|
||||
if (path.size() < extension.size() ||
|
||||
path.compare(path.size() - extension.size(), extension.size(), extension) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
path.resize(path.size() - extension.size());
|
||||
path.push_back('/');
|
||||
|
||||
mArcOverlaysPath = std::move(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
void JKRArchive::buildIndexToPathMap(u32 dirIndex, const std::string& currentPath) const {
|
||||
const SDIDirEntry& dir = mNodes[dirIndex];
|
||||
for (int i = 0; i < dir.num_entries; i++) {
|
||||
const SDIFileEntry& entry = mFiles[dir.first_file_index + i];
|
||||
std::string entryName{&mStringTable[entry.getNameOffset()]};
|
||||
if (entryName == "." || entryName == "..") {
|
||||
continue;
|
||||
}
|
||||
if (entry.isDirectory()) {
|
||||
buildIndexToPathMap(entry.data_offset, currentPath + entryName + "/");
|
||||
} else {
|
||||
mIdxToPathMap[entry.index] = currentPath + entryName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool JKRArchive::getOverlayPath(SDIFileEntry* entry, std::string& path) const {
|
||||
if (entry == nullptr || !buildArcOverlaysPath()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mIdxToPathMap.empty()) {
|
||||
buildIndexToPathMap(0, std::string{&mStringTable[mNodes[0].name_offset]} + "/");
|
||||
}
|
||||
|
||||
const auto pathIt = mIdxToPathMap.find(entry->index);
|
||||
if (pathIt == mIdxToPathMap.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
path = mArcOverlaysPath + pathIt->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
void* JKRArchive::getActiveOverlayData(SDIFileEntry* entry, u32* outSize) const {
|
||||
const auto activeIt = mActiveArcOverlayResources.find(entry->index);
|
||||
if (activeIt == mActiveArcOverlayResources.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto resourceIt = mArcOverlayResources.find(activeIt->second);
|
||||
const u64 generation = sArcOverlayGeneration.load(std::memory_order_acquire);
|
||||
if (resourceIt == mArcOverlayResources.end() || resourceIt->second.generation != generation) {
|
||||
// Keep the allocation owned so raw pointers returned by earlier fetches remain valid.
|
||||
mActiveArcOverlayResources.erase(activeIt);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (outSize != nullptr) {
|
||||
*outSize = resourceIt->second.size;
|
||||
}
|
||||
return resourceIt->first;
|
||||
}
|
||||
|
||||
void* JKRArchive::getOverlayData(SDIFileEntry* entry, u32* outSize) {
|
||||
if (entry == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (void* data = getActiveOverlayData(entry, outSize)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (!getOverlayPath(entry, path)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
constexpr u32 alignmentMask = 0x1f;
|
||||
const u64 generation = sArcOverlayGeneration.load(std::memory_order_acquire);
|
||||
DVDFileInfo fileInfo{};
|
||||
if (!DVDOpen(path.c_str(), &fileInfo)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const u32 logicalSize = fileInfo.length;
|
||||
if (logicalSize > static_cast<u32>(std::numeric_limits<s32>::max()) - alignmentMask) {
|
||||
DVDClose(&fileInfo);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const u32 readSize = ALIGN_NEXT(logicalSize, 0x20);
|
||||
const u32 allocationSize = readSize == 0 ? 1 : readSize;
|
||||
void* data = alloc_overlay_buffer(allocationSize);
|
||||
if (data == nullptr) {
|
||||
DVDClose(&fileInfo);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const s32 status = DVDReadPrio(&fileInfo, data, readSize, 0, 2);
|
||||
DVDClose(&fileInfo);
|
||||
if (status < DVD_RESULT_GOOD || static_cast<u32>(status) != logicalSize) {
|
||||
free_overlay_buffer(data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mArcOverlayResources.emplace(data, ArcOverlayResource{
|
||||
.entryIndex = entry->index,
|
||||
.size = logicalSize,
|
||||
.generation = generation,
|
||||
});
|
||||
mActiveArcOverlayResources[entry->index] = data;
|
||||
|
||||
if (outSize != nullptr) {
|
||||
*outSize = logicalSize;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
bool JKRArchive::copyOverlayData(void* buffer, u32 bufferSize, SDIFileEntry* entry, u32* outSize) {
|
||||
u32 overlaySize;
|
||||
const void* overlayData = getOverlayData(entry, &overlaySize);
|
||||
if (overlayData == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 copySize = overlaySize < bufferSize ? overlaySize : bufferSize;
|
||||
if (copySize != 0) {
|
||||
memcpy(buffer, overlayData, copySize);
|
||||
}
|
||||
if (outSize != nullptr) {
|
||||
*outSize = copySize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JKRArchive::getOverlayResourceSize(const void* data, u32* outSize) const {
|
||||
const auto resourceIt = mArcOverlayResources.find(const_cast<void*>(data));
|
||||
if (resourceIt == mArcOverlayResources.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outSize != nullptr) {
|
||||
*outSize = resourceIt->second.size;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JKRArchive::getOverlayFileSize(SDIFileEntry* entry, u32* outSize) const {
|
||||
if (entry == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 activeSize;
|
||||
if (getActiveOverlayData(entry, &activeSize) != nullptr) {
|
||||
if (outSize != nullptr) {
|
||||
*outSize = activeSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (!getOverlayPath(entry, path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DVDFileInfo fileInfo{};
|
||||
if (!DVDOpen(path.c_str(), &fileInfo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outSize != nullptr) {
|
||||
*outSize = fileInfo.length;
|
||||
}
|
||||
DVDClose(&fileInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
u32 JKRArchive::getFileSize(SDIFileEntry* entry) const {
|
||||
u32 size;
|
||||
if (getOverlayFileSize(entry, &size)) {
|
||||
return size;
|
||||
}
|
||||
return entry != nullptr ? entry->getSize() : 0;
|
||||
}
|
||||
|
||||
bool JKRArchive::removeOverlayResource(void* resource, bool freeResource) {
|
||||
const auto resourceIt = mArcOverlayResources.find(resource);
|
||||
if (resourceIt == mArcOverlayResources.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto activeIt = mActiveArcOverlayResources.find(resourceIt->second.entryIndex);
|
||||
if (activeIt != mActiveArcOverlayResources.end() && activeIt->second == resource) {
|
||||
mActiveArcOverlayResources.erase(activeIt);
|
||||
}
|
||||
|
||||
if (freeResource) {
|
||||
free_overlay_buffer(resource);
|
||||
}
|
||||
mArcOverlayResources.erase(resourceIt);
|
||||
return true;
|
||||
}
|
||||
|
||||
void JKRArchive::removeAllOverlayResources() {
|
||||
mActiveArcOverlayResources.clear();
|
||||
for (const auto& key : mArcOverlayResources | std::views::keys) {
|
||||
free_overlay_buffer(key);
|
||||
}
|
||||
mArcOverlayResources.clear();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -246,6 +246,7 @@ u32 JKRArchive::readResource(void* buffer, u32 bufferSize, u16 id) {
|
||||
|
||||
void JKRArchive::removeResourceAll() {
|
||||
if (mArcInfoBlock && mMountMode != MOUNT_MEM) {
|
||||
IF_DUSK(removeAllOverlayResources();)
|
||||
SDIFileEntry* fileEntry = mFiles;
|
||||
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
if (JKAR_DATA(fileEntry)) {
|
||||
@@ -259,6 +260,13 @@ void JKRArchive::removeResourceAll() {
|
||||
|
||||
bool JKRArchive::removeResource(void* resource) {
|
||||
JUT_ASSERT(678, resource != NULL);
|
||||
|
||||
#if TARGET_PC
|
||||
if (removeOverlayResource(resource, true)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDIFileEntry* fileEntry = findPtrResource(resource);
|
||||
if (fileEntry == NULL)
|
||||
return false;
|
||||
@@ -270,6 +278,13 @@ bool JKRArchive::removeResource(void* resource) {
|
||||
|
||||
bool JKRArchive::detachResource(void* resource) {
|
||||
JUT_ASSERT(707, resource != NULL);
|
||||
|
||||
#if TARGET_PC
|
||||
if (removeOverlayResource(resource, false)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDIFileEntry* fileEntry = findPtrResource(resource);
|
||||
if (fileEntry == NULL)
|
||||
return false;
|
||||
@@ -280,6 +295,13 @@ bool JKRArchive::detachResource(void* resource) {
|
||||
|
||||
u32 JKRArchive::getResSize(const void* resource) const {
|
||||
JUT_ASSERT(732, resource != NULL);
|
||||
|
||||
#if TARGET_PC
|
||||
if (u32 size; getOverlayResourceSize(resource, &size)) {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDIFileEntry* fileEntry = findPtrResource(resource);
|
||||
if (fileEntry == NULL)
|
||||
return -1;
|
||||
|
||||
@@ -226,6 +226,13 @@ bool JKRCompArchive::open(s32 entryNum) {
|
||||
|
||||
void* JKRCompArchive::fetchResource(SDIFileEntry *fileEntry, u32 *pSize) {
|
||||
JUT_ASSERT(597, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (void* data = getOverlayData(fileEntry, pSize); data != nullptr) {
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 ptrSize;
|
||||
u32 size = fileEntry->data_size;
|
||||
int compression = JKRConvertAttrToCompressionType(u8(fileEntry->type_flags_and_name_offset >> 0x18));
|
||||
@@ -274,6 +281,13 @@ void *JKRCompArchive::fetchResource(void *data, u32 compressedSize, SDIFileEntry
|
||||
{
|
||||
u32 size = 0;
|
||||
JUT_ASSERT(708, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (copyOverlayData(data, compressedSize, fileEntry, pSize)) {
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 fileSize = fileEntry->data_size;
|
||||
u32 alignedSize = ALIGN_NEXT(fileSize, 32);
|
||||
u32 fileFlag = fileEntry->type_flags_and_name_offset >> 0x18;
|
||||
@@ -319,6 +333,7 @@ void *JKRCompArchive::fetchResource(void *data, u32 compressedSize, SDIFileEntry
|
||||
|
||||
void JKRCompArchive::removeResourceAll() {
|
||||
if (mArcInfoBlock != NULL && mMountMode != MOUNT_MEM) {
|
||||
IF_DUSK(removeAllOverlayResources();)
|
||||
SDIFileEntry* fileEntry = mFiles;
|
||||
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
|
||||
int tmp = fileEntry->type_flags_and_name_offset >> 0x18;
|
||||
@@ -336,6 +351,12 @@ void JKRCompArchive::removeResourceAll() {
|
||||
}
|
||||
|
||||
bool JKRCompArchive::removeResource(void* resource) {
|
||||
#if TARGET_PC
|
||||
if (removeOverlayResource(resource, true)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDIFileEntry* fileEntry = findPtrResource(resource);
|
||||
if (!fileEntry)
|
||||
return false;
|
||||
@@ -356,6 +377,12 @@ u32 JKRCompArchive::getExpandedResSize(const void *resource) const
|
||||
return getResSize(resource);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (u32 size; getOverlayResourceSize(resource, &size)) {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDIFileEntry *fileEntry = findPtrResource(resource);
|
||||
if(!fileEntry) {
|
||||
return 0xffffffff;
|
||||
|
||||
@@ -147,6 +147,13 @@ cleanup:
|
||||
|
||||
void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) {
|
||||
JUT_ASSERT(428, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (void* data = getOverlayData(fileEntry, returnSize); data != nullptr) {
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 tempReturnSize;
|
||||
if (returnSize == NULL) {
|
||||
returnSize = &tempReturnSize;
|
||||
@@ -181,6 +188,13 @@ void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) {
|
||||
void* JKRDvdArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* fileEntry,
|
||||
u32* returnSize) {
|
||||
JUT_ASSERT(504, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (copyOverlayData(buffer, bufferSize, fileEntry, returnSize)) {
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 size = fileEntry->data_size;
|
||||
JKRCompression fileCompression = JKRConvertAttrToCompressionType(u8(fileEntry->type_flags_and_name_offset >> 24));
|
||||
|
||||
@@ -344,6 +358,12 @@ u32 JKRDvdArchive::getExpandedResSize(const void* resource) const {
|
||||
return getResSize(resource);
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (u32 size; getOverlayResourceSize(resource, &size)) {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDIFileEntry* fileEntry = findPtrResource(resource);
|
||||
if (!fileEntry) {
|
||||
return -1;
|
||||
|
||||
@@ -135,6 +135,13 @@ bool JKRMemArchive::open(void* buffer, u32 bufferSize, JKRMemBreakFlag flag) {
|
||||
|
||||
void* JKRMemArchive::fetchResource(SDIFileEntry* fileEntry, u32* resourceSize) {
|
||||
JUT_ASSERT(555, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (void* data = getOverlayData(fileEntry, resourceSize); data != nullptr) {
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!JKAR_DATA(fileEntry)) {
|
||||
JKAR_DATA(fileEntry) = mArchiveData + fileEntry->data_offset;
|
||||
}
|
||||
@@ -149,6 +156,13 @@ void* JKRMemArchive::fetchResource(SDIFileEntry* fileEntry, u32* resourceSize) {
|
||||
void* JKRMemArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* fileEntry,
|
||||
u32* resourceSize) {
|
||||
JUT_ASSERT(595, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (copyOverlayData(buffer, bufferSize, fileEntry, resourceSize)) {
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 srcLength = fileEntry->data_size;
|
||||
if (srcLength > bufferSize) {
|
||||
srcLength = bufferSize;
|
||||
@@ -173,6 +187,8 @@ void* JKRMemArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* f
|
||||
void JKRMemArchive::removeResourceAll(void) {
|
||||
JUT_ASSERT(642, isMounted());
|
||||
|
||||
IF_DUSK(removeAllOverlayResources();)
|
||||
|
||||
if (mArcInfoBlock == NULL)
|
||||
return;
|
||||
if (mMountMode == MOUNT_MEM)
|
||||
@@ -192,6 +208,12 @@ void JKRMemArchive::removeResourceAll(void) {
|
||||
bool JKRMemArchive::removeResource(void* resource) {
|
||||
JUT_ASSERT(673, isMounted());
|
||||
|
||||
#if TARGET_PC
|
||||
if (removeOverlayResource(resource, true)) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDIFileEntry* fileEntry = findPtrResource(resource);
|
||||
if (!fileEntry)
|
||||
return false;
|
||||
@@ -231,6 +253,12 @@ u32 JKRMemArchive::fetchResource_subroutine(u8* src, u32 srcLength, u8* dst, u32
|
||||
}
|
||||
|
||||
u32 JKRMemArchive::getExpandedResSize(const void* resource) const {
|
||||
#if TARGET_PC
|
||||
if (u32 overlaySize; getOverlayResourceSize(resource, &overlaySize)) {
|
||||
return overlaySize;
|
||||
}
|
||||
#endif
|
||||
|
||||
SDIFileEntry* fileEntry = findPtrResource(resource);
|
||||
if (fileEntry == NULL)
|
||||
return -1;
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
#include "JSystem/JMessage/control.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/mods/svc/flow.hpp"
|
||||
#endif
|
||||
|
||||
JMessage::TControl::TControl()
|
||||
: pSequenceProcessor_(NULL),
|
||||
pRenderingProcessor_(NULL),
|
||||
@@ -19,9 +23,16 @@ JMessage::TControl::TControl()
|
||||
pMessageText_current_(NULL)
|
||||
{}
|
||||
|
||||
JMessage::TControl::~TControl() {}
|
||||
JMessage::TControl::~TControl() {
|
||||
#if TARGET_PC
|
||||
dusk::flow::release_message_control(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void JMessage::TControl::reset() {
|
||||
#if TARGET_PC
|
||||
dusk::flow::release_message_control(this);
|
||||
#endif
|
||||
pEntry_ = NULL;
|
||||
pMessageText_begin_ = NULL;
|
||||
pszText_update_current_ = NULL;
|
||||
@@ -78,18 +89,57 @@ int JMessage::TControl::setMessageID(u32 uMsgID, u32 param_1, bool* pbValid) {
|
||||
}
|
||||
|
||||
bool JMessage::TControl::setMessageCode_inSequence_(JMessage::TProcessor const* pProcessor, u16 u16GroupID, u16 u16Index) {
|
||||
#if TARGET_PC
|
||||
const TResource* resource = NULL;
|
||||
const void* resolvedEntry = NULL;
|
||||
const char* resolvedText = NULL;
|
||||
if (u16Index >= dusk::flow::kCustomMessageMin) {
|
||||
if (!dusk::flow::custom_message_for_processor(
|
||||
this, pProcessor, u16Index, resource, resolvedEntry, resolvedText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const_cast<TProcessor*>(pProcessor)->setResourceCache(const_cast<TResource*>(resource));
|
||||
} else {
|
||||
resource = pProcessor->getResource_groupID(u16GroupID);
|
||||
if (resource == NULL) {
|
||||
return false;
|
||||
}
|
||||
const void* nativeEntry = resource->getMessageEntry_messageIndex(u16Index);
|
||||
const char* nativeText =
|
||||
nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL;
|
||||
resolvedEntry = nativeEntry;
|
||||
resolvedText = nativeText;
|
||||
dusk::flow::resolve_message_for_control(this, resource->oParse_THeader_.getRaw(), u16Index,
|
||||
nativeEntry, nativeText, resolvedEntry, resolvedText);
|
||||
}
|
||||
pEntry_ = const_cast<void*>(resolvedEntry);
|
||||
if (pEntry_ == NULL || resolvedText == NULL) {
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
pEntry_ = pProcessor->getMessageEntry_messageCode(u16GroupID, u16Index);
|
||||
if (pEntry_ == NULL) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TARGET_PC
|
||||
uMessageGroupID_ = resource->getGroupID();
|
||||
pResourceCache_ = resource;
|
||||
#else
|
||||
uMessageGroupID_ = u16GroupID;
|
||||
uMessageID_ = u16Index;
|
||||
|
||||
pResourceCache_ = pProcessor->getResourceCache();
|
||||
#endif
|
||||
uMessageID_ = u16Index;
|
||||
|
||||
JUT_ASSERT(155, pResourceCache_!=NULL);
|
||||
|
||||
#if TARGET_PC
|
||||
pMessageText_begin_ = resolvedText;
|
||||
#else
|
||||
pMessageText_begin_ = pResourceCache_->getMessageText_messageEntry(pEntry_);
|
||||
#endif
|
||||
pMessageText_current_ = pMessageText_begin_;
|
||||
oStack_renderingProcessor_.clear();
|
||||
return true;
|
||||
|
||||
@@ -5,15 +5,26 @@
|
||||
#include "JSystem/JUtility/JUTAssert.h"
|
||||
#include <cstdint>
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/mods/svc/flow.hpp"
|
||||
#endif
|
||||
|
||||
JMessage::TReference::~TReference() {}
|
||||
|
||||
const char* JMessage::TReference::do_word(u32 param_0) const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JMessage::TProcessor::~TProcessor() {}
|
||||
JMessage::TProcessor::~TProcessor() {
|
||||
#if TARGET_PC
|
||||
dusk::flow::release_message_processor(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void JMessage::TProcessor::reset() {
|
||||
#if TARGET_PC
|
||||
dusk::flow::release_message_processor(this);
|
||||
#endif
|
||||
on_resetStatus_(NULL);
|
||||
do_reset();
|
||||
}
|
||||
@@ -44,6 +55,15 @@ const JMessage::TResource* JMessage::TProcessor::getResource_groupID(u16 u16Grou
|
||||
}
|
||||
|
||||
u32 JMessage::TProcessor::toMessageCode_messageID(u32 uMsgID, u32 param_1, bool* pbValid) const {
|
||||
#if TARGET_PC
|
||||
u32 customCode = 0;
|
||||
if (dusk::flow::message_code_for_id(this, uMsgID, customCode)) {
|
||||
if (pbValid != NULL) {
|
||||
*pbValid = true;
|
||||
}
|
||||
return customCode;
|
||||
}
|
||||
#endif
|
||||
const TResource* pResourceCache = (const TResource*)getResourceCache();
|
||||
if (pResourceCache != NULL) {
|
||||
u16 u16Index = pResourceCache->toMessageIndex_messageID(uMsgID, param_1, pbValid);
|
||||
@@ -73,6 +93,74 @@ u32 JMessage::TProcessor::toMessageCode_messageID(u32 uMsgID, u32 param_1, bool*
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
void* JMessage::TProcessor::getMessageEntry_messageCode(u16 groupId, u16 index) const {
|
||||
if (index >= dusk::flow::kCustomMessageMin) {
|
||||
const TResource* customResource = NULL;
|
||||
const void* customEntry = NULL;
|
||||
const char* customText = NULL;
|
||||
if (!dusk::flow::custom_message_for_processor(
|
||||
NULL, this, index, customResource, customEntry, customText))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
const_cast<TProcessor*>(this)->setResourceCache(const_cast<TResource*>(customResource));
|
||||
return const_cast<void*>(customEntry);
|
||||
}
|
||||
const TResource* resource = getResource_groupID(groupId);
|
||||
if (resource == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
void* nativeEntry = resource->getMessageEntry_messageIndex(index);
|
||||
const char* nativeText =
|
||||
nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL;
|
||||
const void* resolvedEntry = nativeEntry;
|
||||
const char* resolvedText = nativeText;
|
||||
dusk::flow::resolve_message(this, resource->oParse_THeader_.getRaw(), index, nativeEntry,
|
||||
nativeText, resolvedEntry, resolvedText);
|
||||
return const_cast<void*>(resolvedEntry);
|
||||
}
|
||||
|
||||
const char* JMessage::TProcessor::getMessageText_messageCode(u16 groupId, u16 index) const {
|
||||
if (index >= dusk::flow::kCustomMessageMin) {
|
||||
const TResource* customResource = NULL;
|
||||
const void* customEntry = NULL;
|
||||
const char* customText = NULL;
|
||||
if (!dusk::flow::custom_message_for_processor(
|
||||
NULL, this, index, customResource, customEntry, customText))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
const_cast<TProcessor*>(this)->setResourceCache(const_cast<TResource*>(customResource));
|
||||
return customText;
|
||||
}
|
||||
const TResource* resource = getResource_groupID(groupId);
|
||||
if (resource == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
const void* nativeEntry = resource->getMessageEntry_messageIndex(index);
|
||||
const char* nativeText =
|
||||
nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL;
|
||||
const void* resolvedEntry = nativeEntry;
|
||||
const char* resolvedText = nativeText;
|
||||
dusk::flow::resolve_message(this, resource->oParse_THeader_.getRaw(), index, nativeEntry,
|
||||
nativeText, resolvedEntry, resolvedText);
|
||||
return resolvedText;
|
||||
}
|
||||
|
||||
const char* JMessage::TProcessor::on_message_limited(u16 index) const {
|
||||
JUT_ASSERT(482, pResourceCache_!=NULL);
|
||||
const void* nativeEntry = pResourceCache_->getMessageEntry_messageIndex(index);
|
||||
const char* nativeText =
|
||||
nativeEntry != NULL ? pResourceCache_->getMessageText_messageEntry(nativeEntry) : NULL;
|
||||
const void* resolvedEntry = nativeEntry;
|
||||
const char* resolvedText = nativeText;
|
||||
dusk::flow::resolve_message(this, pResourceCache_->oParse_THeader_.getRaw(), index,
|
||||
nativeEntry, nativeText, resolvedEntry, resolvedText);
|
||||
return resolvedText;
|
||||
}
|
||||
#endif
|
||||
|
||||
void JMessage::TProcessor::on_select_begin(char const* (*pfn)(JMessage::TProcessor*),
|
||||
void const* pOffset, char const* pcBase, u32 uNumber) {
|
||||
JUT_ASSERT(191, uNumber>0);
|
||||
|
||||
@@ -71,7 +71,7 @@ void JUTResFont::initJoinedTexture() {
|
||||
int pageCount = 0;
|
||||
u32 pageNumCells = block.numRows * block.numColumns;
|
||||
|
||||
if (dusk::version::getGameVersion() == dusk::version::GameVersion::GcnJpn) {
|
||||
if (dusk::version::isRegionJpn()) {
|
||||
pageCount = 1;
|
||||
if (pageNumCells > 0 && block.endCode > block.startCode) {
|
||||
pageCount = (block.endCode - block.startCode + pageNumCells - 1) / pageNumCells;
|
||||
|
||||
@@ -42,7 +42,7 @@ JUTVideo::JUTVideo(GXRenderModeObj const* param_0) {
|
||||
mRetraceCount = VIGetRetraceCount();
|
||||
field_0x10 = 1;
|
||||
field_0x18 = 0;
|
||||
sVideoLastTick = OSGetTick();
|
||||
sVideoLastTick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
|
||||
sVideoInterval = 670000;
|
||||
mPreRetraceCallback = VISetPreRetraceCallback(preRetraceProc);
|
||||
mPostRetraceCallback = VISetPostRetraceCallback(postRetraceProc);
|
||||
@@ -68,8 +68,13 @@ void JUTVideo::preRetraceProc(u32 retrace_count) {
|
||||
(*sManager->mPreCallback)(retrace_count);
|
||||
}
|
||||
|
||||
OSTick tick = OSGetTick();
|
||||
OSTick tick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
|
||||
sVideoInterval = tick - sVideoLastTick;
|
||||
#if TARGET_PC
|
||||
if (sVideoInterval <= 0) {
|
||||
sVideoInterval = 1;
|
||||
}
|
||||
#endif
|
||||
sVideoLastTick = tick;
|
||||
|
||||
JUTXfb* xfb = JUTXfb::getManager();
|
||||
|
||||
+12
-10
@@ -53,6 +53,7 @@ ResourceBuffer g_denoiseSource = RESOURCE_BUFFER_INIT;
|
||||
ResourceBuffer g_compositeSource = RESOURCE_BUFFER_INIT;
|
||||
|
||||
GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT;
|
||||
GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT;
|
||||
WGPUComputePipeline g_preprocessPipeline = nullptr;
|
||||
WGPUComputePipeline g_mip4Pipeline = nullptr;
|
||||
WGPUComputePipeline g_gtaoPipeline = nullptr;
|
||||
@@ -226,19 +227,17 @@ bool build_composite_pipeline(
|
||||
.dstFactor = WGPUBlendFactor_One,
|
||||
},
|
||||
};
|
||||
WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT;
|
||||
colorTarget.format = g_deviceInfo.color_format;
|
||||
if (blend) {
|
||||
colorTarget.blend = &blendState;
|
||||
}
|
||||
WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS];
|
||||
const uint32_t colorTargetCount = gfx_init_color_target_states(
|
||||
&g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All);
|
||||
WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT;
|
||||
fragment.module = module;
|
||||
fragment.entryPoint = {"fs_main", WGPU_STRLEN};
|
||||
fragment.targetCount = 1;
|
||||
fragment.targets = &colorTarget;
|
||||
fragment.targetCount = colorTargetCount;
|
||||
fragment.targets = colorTargets;
|
||||
// Depth state must match the EFB pass despite never touching depth.
|
||||
WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT;
|
||||
depthStencil.format = g_deviceInfo.depth_format;
|
||||
depthStencil.format = g_sceneTargetLayout.depth_stencil_format;
|
||||
depthStencil.depthWriteEnabled = WGPUOptionalBool_False;
|
||||
depthStencil.depthCompare = WGPUCompareFunction_Always;
|
||||
|
||||
@@ -248,7 +247,7 @@ bool build_composite_pipeline(
|
||||
pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN};
|
||||
pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
|
||||
pipelineDesc.depthStencil = &depthStencil;
|
||||
pipelineDesc.multisample.count = g_deviceInfo.sample_count;
|
||||
pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count;
|
||||
pipelineDesc.fragment = &fragment;
|
||||
outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc);
|
||||
wgpuShaderModuleRelease(module);
|
||||
@@ -504,7 +503,7 @@ void on_compute(
|
||||
// Render worker thread: composite the AO over the scene (or show it, in debug view).
|
||||
void on_draw(
|
||||
ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) {
|
||||
if (payloadSize != sizeof(CompositePayload)) {
|
||||
if (payloadSize != sizeof(CompositePayload) || ctx->layout.key != g_sceneTargetLayout.key) {
|
||||
return;
|
||||
}
|
||||
CompositePayload data;
|
||||
@@ -816,6 +815,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||
if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) {
|
||||
return mods::set_error(error, MOD_ERROR, "failed to query device info");
|
||||
}
|
||||
if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) {
|
||||
return mods::set_error(error, MOD_ERROR, "failed to query scene target layout");
|
||||
}
|
||||
if (!build_compute_pipeline("AO preprocess depth", g_preprocessSource, "preprocess_depth",
|
||||
g_preprocessPipeline, g_preprocessLayout) ||
|
||||
!build_compute_pipeline("AO downsample mip4", g_preprocessSource, "downsample_mip4",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(flow_demo CXX)
|
||||
|
||||
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root")
|
||||
option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
if (DUSK_MOD_USE_FULL_TREE)
|
||||
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
|
||||
else ()
|
||||
add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
add_mod(flow_demo
|
||||
FEATURES fmt
|
||||
SOURCES src/mod.cpp
|
||||
MOD_JSON mod.json
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"id": "dev.twilitrealm.flow_demo",
|
||||
"name": "[Demo] Flow & Messages",
|
||||
"version": "1.0.0",
|
||||
"author": "Twilit Realm",
|
||||
"description": "Adds a FlowService demonstration submenu to Midna's menu."
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
#include "mods/service.hpp"
|
||||
#include "mods/svc/flow.hpp"
|
||||
#include "mods/svc/log.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
DEFINE_MOD();
|
||||
IMPORT_SERVICE(LogService, svc_log);
|
||||
IMPORT_SERVICE(FlowService, svc_flow);
|
||||
IMPORT_SERVICE(MessageService, svc_message);
|
||||
|
||||
namespace {
|
||||
|
||||
// The Midna flow lives in BMG group 0.
|
||||
constexpr uint16_t kMessageGroup = 0;
|
||||
// Midna's "speaker" value for messages.
|
||||
constexpr uint16_t kMidnaSpeaker = 21;
|
||||
|
||||
// Vanilla flow node/branch/edge IDs that we reference or override.
|
||||
constexpr uint16_t kMidnaPromptHumanNode = 0x018c;
|
||||
constexpr uint16_t kMidnaPromptWolfNode = 0x018d;
|
||||
constexpr uint16_t kMidnaHumanBranch = 0x0190;
|
||||
constexpr uint16_t kMidnaWolfBranch = 0x0193;
|
||||
constexpr uint16_t kMidnaTalkNode = 0x018f;
|
||||
constexpr uint16_t kMidnaHumanTalkEdge = 0x0113;
|
||||
constexpr uint16_t kMidnaWolfTalkEdge = 0x0119;
|
||||
|
||||
// For vanilla messages, we need both the entry index and the message ID.
|
||||
// For registered custom messages, they're the same.
|
||||
constexpr uint16_t kMidnaMenuPromptEntry = 3003; // message index used in flow message data
|
||||
constexpr uint16_t kMidnaMenuPromptId = 2042; // message ID used to override
|
||||
|
||||
constexpr std::array kAllLanguages{
|
||||
MESSAGE_LANGUAGE_ENGLISH,
|
||||
MESSAGE_LANGUAGE_GERMAN,
|
||||
MESSAGE_LANGUAGE_FRENCH,
|
||||
MESSAGE_LANGUAGE_SPANISH,
|
||||
MESSAGE_LANGUAGE_ITALIAN,
|
||||
MESSAGE_LANGUAGE_JAPANESE,
|
||||
};
|
||||
|
||||
constexpr mods::flow::MessageStyle kResponseStyle =
|
||||
mods::flow::MessageStyle{}.speaker(kMidnaSpeaker).box_kind(MESSAGE_BOX_MIDNA);
|
||||
constexpr mods::flow::MessageStyle kPromptStyle =
|
||||
kResponseStyle.draw_type(MESSAGE_DRAW_INSTANT).talk_anim(31).face_anim(31);
|
||||
|
||||
mods::flow::Query g_demoQuery;
|
||||
mods::flow::Event g_demoEvent;
|
||||
mods::flow::Graph g_graph;
|
||||
std::vector<mods::flow::RegisteredMessage> g_messages;
|
||||
std::vector<mods::flow::MessageOverride> g_overrides;
|
||||
std::vector<uint8_t> g_visitedPromptText;
|
||||
uint32_t g_queryExecutions = 0;
|
||||
uint32_t g_eventExecutions = 0;
|
||||
|
||||
mods::flow::MessageBuilder build_prompt(std::string_view suffix) {
|
||||
return mods::flow::MessageBuilder{kPromptStyle}
|
||||
.text("What is it, ")
|
||||
.text_color(MESSAGE_COLOR_RED)
|
||||
.player_name()
|
||||
.text_color(MESSAGE_COLOR_DEFAULT)
|
||||
.text("?\n")
|
||||
.text_scale(85)
|
||||
.text(suffix)
|
||||
.text_scale(100)
|
||||
.await_choice();
|
||||
}
|
||||
|
||||
// Registers a single message for all languages.
|
||||
mods::flow::RegisteredMessage register_message(const mods::flow::MessageBuilder& builder) {
|
||||
std::vector<mods::flow::MessageVariant> variants;
|
||||
variants.reserve(kAllLanguages.size());
|
||||
for (const MessageLanguage language : kAllLanguages) {
|
||||
variants.push_back(builder.build(language));
|
||||
}
|
||||
return mods::flow::register_message(kMessageGroup, variants);
|
||||
}
|
||||
|
||||
ModResult add_message(const mods::flow::MessageBuilder& builder, MessageId& outId) {
|
||||
auto message = register_message(builder);
|
||||
if (!message) {
|
||||
return message.result();
|
||||
}
|
||||
outId = message.id();
|
||||
g_messages.push_back(std::move(message));
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult add_fixed_override(
|
||||
uint16_t messageId, const std::vector<uint8_t>& text, bool addCallback) {
|
||||
for (const MessageLanguage language : kAllLanguages) {
|
||||
auto fixed =
|
||||
mods::flow::override_message(kMessageGroup, messageId, language, std::span{text});
|
||||
if (!fixed) {
|
||||
return fixed.result();
|
||||
}
|
||||
g_overrides.push_back(std::move(fixed));
|
||||
if (!addCallback) {
|
||||
continue;
|
||||
}
|
||||
auto callback = mods::flow::override_message_fn(kMessageGroup, messageId, language,
|
||||
[](ModContext*, const MessageOverrideContext*, MessageTextData* outText,
|
||||
void*) -> bool {
|
||||
if (g_eventExecutions == 0 || outText == nullptr) {
|
||||
return false;
|
||||
}
|
||||
outText->text = g_visitedPromptText.data();
|
||||
outText->text_size = g_visitedPromptText.size();
|
||||
return true;
|
||||
});
|
||||
if (!callback) {
|
||||
return callback.result();
|
||||
}
|
||||
g_overrides.push_back(std::move(callback));
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
uint16_t demo_query(ModContext*, const FlowQueryContext* query, void*) {
|
||||
if (query == nullptr || query->parameter != 1234 || query->result_count < 3) {
|
||||
return 0;
|
||||
}
|
||||
const auto result = static_cast<uint16_t>(g_queryExecutions % 3);
|
||||
if (query->phase == FLOW_QUERY_PHASE_EXECUTE) {
|
||||
++g_queryExecutions;
|
||||
mods::log::info("Flow demo query selected result {}", result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void demo_event(ModContext*, const FlowEventContext* event, void*) {
|
||||
++g_eventExecutions;
|
||||
const uint8_t path = event != nullptr ? event->parameters[3] : 0;
|
||||
mods::log::info("Flow demo event {} executed (activation {})", path, g_eventExecutions);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
MOD_EXPORT ModResult mod_initialize(ModError* outError) {
|
||||
g_queryExecutions = 0;
|
||||
g_eventExecutions = 0;
|
||||
|
||||
MessageId humanSelectionId = 0;
|
||||
MessageId wolfSelectionId = 0;
|
||||
MessageId labPromptId = 0;
|
||||
MessageId labSelectionId = 0;
|
||||
MessageId formattedId = 0;
|
||||
MessageId evenId = 0;
|
||||
MessageId oddId = 0;
|
||||
MessageId timeoutId = 0;
|
||||
|
||||
auto result = add_message(mods::flow::MessageBuilder{}
|
||||
.speaker(kMidnaSpeaker)
|
||||
.options("Transform into human", "Warp", "Flow demo"),
|
||||
humanSelectionId);
|
||||
if (result == MOD_OK) {
|
||||
result = add_message(mods::flow::MessageBuilder{}
|
||||
.speaker(kMidnaSpeaker)
|
||||
.options("Transform into wolf", "Warp", "Flow demo"),
|
||||
wolfSelectionId);
|
||||
}
|
||||
if (result == MOD_OK) {
|
||||
result = add_message(build_prompt("Choose a FlowService test."), labPromptId);
|
||||
}
|
||||
if (result == MOD_OK) {
|
||||
result = add_message(mods::flow::MessageBuilder{}
|
||||
.speaker(kMidnaSpeaker)
|
||||
.options("Formatted message", "Callback branch", "Talk to Midna"),
|
||||
labSelectionId);
|
||||
}
|
||||
if (result == MOD_OK) {
|
||||
result = add_message(mods::flow::MessageBuilder{kResponseStyle}
|
||||
.text_color(MESSAGE_COLOR_DEFAULT)
|
||||
.text("Hello from ")
|
||||
.text_color(MESSAGE_COLOR_RED)
|
||||
.text("FlowService")
|
||||
.text_color(MESSAGE_COLOR_DEFAULT)
|
||||
.text(", ")
|
||||
.player_name()
|
||||
.text("!\n")
|
||||
.character_delay(3)
|
||||
.text("This text is slow. ")
|
||||
.character_delay(0)
|
||||
.pause(12)
|
||||
.text_scale(125)
|
||||
.text("Big")
|
||||
.text_scale(100)
|
||||
.text(" text too.")
|
||||
.input_after_delay(15),
|
||||
formattedId);
|
||||
}
|
||||
if (result == MOD_OK) {
|
||||
result = add_message(mods::flow::MessageBuilder{kResponseStyle}
|
||||
.text("Query returned ")
|
||||
.text_color(MESSAGE_COLOR_GREEN)
|
||||
.text("zero")
|
||||
.text_color(MESSAGE_COLOR_DEFAULT)
|
||||
.text(".")
|
||||
.auto_advance(75),
|
||||
evenId);
|
||||
}
|
||||
if (result == MOD_OK) {
|
||||
result = add_message(mods::flow::MessageBuilder{kResponseStyle}
|
||||
.box_kind(MESSAGE_BOX_LIGHT_SPIRIT)
|
||||
.text("Query returned ")
|
||||
.text_color(MESSAGE_COLOR_RED)
|
||||
.text("one")
|
||||
.text_color(MESSAGE_COLOR_DEFAULT)
|
||||
.text(".")
|
||||
.auto_advance(75),
|
||||
oddId);
|
||||
}
|
||||
if (result == MOD_OK) {
|
||||
result = add_message(mods::flow::MessageBuilder{kResponseStyle}
|
||||
.text("Query returned ")
|
||||
.text_color(MESSAGE_COLOR_YELLOW)
|
||||
.text("three")
|
||||
.text_color(MESSAGE_COLOR_DEFAULT)
|
||||
.text(".\nThis will timeout after three seconds.")
|
||||
.input_or_timeout(90),
|
||||
timeoutId);
|
||||
}
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(outError, result, "failed to register custom demo messages");
|
||||
}
|
||||
|
||||
g_visitedPromptText =
|
||||
build_prompt("A custom event has run.").build(MESSAGE_LANGUAGE_ENGLISH).text();
|
||||
result = add_fixed_override(kMidnaMenuPromptId,
|
||||
build_prompt("Flow demo is installed.").build(MESSAGE_LANGUAGE_ENGLISH).text(), true);
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(outError, result, "failed to register demo message overrides");
|
||||
}
|
||||
|
||||
g_demoQuery = mods::flow::register_query("flow_demo cycling branch", demo_query);
|
||||
g_demoEvent = mods::flow::register_event("flow_demo activation", demo_event);
|
||||
if (!g_demoQuery || !g_demoEvent) {
|
||||
const auto callbackResult = !g_demoQuery ? g_demoQuery.result() : g_demoEvent.result();
|
||||
return mods::set_error(outError, callbackResult, "failed to register flow callbacks");
|
||||
}
|
||||
|
||||
// Set up our demo graph
|
||||
mods::flow::GraphBuilder graph{kMessageGroup};
|
||||
const auto demoSetup = graph.add_event(FLOW_EVENT_SELECT_VERTICAL, {0, 0, 0, 4});
|
||||
const auto formattedMessage = graph.add_message(formattedId).next(demoSetup);
|
||||
const auto formattedEvent =
|
||||
graph.add_event(g_demoEvent.id(), {0, 0, 0, 1}).next(formattedMessage);
|
||||
const auto evenMessage = graph.add_message(evenId).next(demoSetup);
|
||||
const auto evenEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 2}).next(evenMessage);
|
||||
const auto oddMessage = graph.add_message(oddId).next(demoSetup);
|
||||
const auto oddEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 3}).next(oddMessage);
|
||||
const auto timeoutMessage = graph.add_message(timeoutId).next(demoSetup);
|
||||
const auto timeoutEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 4}).next(timeoutMessage);
|
||||
const auto callbackBranch =
|
||||
graph.add_branch(g_demoQuery.id(), 1234).results({evenEvent, oddEvent, timeoutEvent});
|
||||
const auto choiceBranch =
|
||||
graph.add_branch(FLOW_QUERY_SELECT_3_CANCEL, 0)
|
||||
.results({formattedEvent, callbackBranch, kMidnaTalkNode, mods::flow::kEnd});
|
||||
const auto demoSelection = graph.add_message(labSelectionId).next(choiceBranch);
|
||||
const auto demoPrompt = graph.add_message(labPromptId).next(demoSelection);
|
||||
demoSetup.next(demoPrompt);
|
||||
|
||||
// Patch original nodes and edges so they flow into our custom graph
|
||||
const auto humanSelection = graph.add_message(humanSelectionId).next(kMidnaHumanBranch);
|
||||
const auto wolfSelection = graph.add_message(wolfSelectionId).next(kMidnaWolfBranch);
|
||||
graph.patch_node(
|
||||
kMidnaPromptHumanNode, mods::flow::message(0, kMidnaMenuPromptEntry, humanSelection));
|
||||
graph.patch_node(
|
||||
kMidnaPromptWolfNode, mods::flow::message(0, kMidnaMenuPromptEntry, wolfSelection));
|
||||
graph.patch_edge(kMidnaHumanTalkEdge, demoSetup);
|
||||
graph.patch_edge(kMidnaWolfTalkEdge, demoSetup);
|
||||
|
||||
g_graph = graph.commit();
|
||||
if (!g_graph) {
|
||||
return mods::set_error(outError, g_graph.result(), "failed to commit the flow demo graph");
|
||||
}
|
||||
|
||||
mods::log::info("Flow demo ready: {} custom messages", g_messages.size());
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_update(ModError*) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_shutdown(ModError*) {
|
||||
mods::log::info("Flow demo unloaded after {} events", g_eventExecutions);
|
||||
g_graph.reset();
|
||||
g_overrides.clear();
|
||||
g_messages.clear();
|
||||
g_visitedPromptText.clear();
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
+13
-11
@@ -20,12 +20,12 @@
|
||||
#include "dolphin/gx/GXPixel.h"
|
||||
#include "dolphin/gx/GXTransform.h"
|
||||
#include "m_Do/m_Do_mtx.h"
|
||||
#include "mods/svc/hook.hpp"
|
||||
#include "mods/service.hpp"
|
||||
#include "mods/svc/camera.h"
|
||||
#include "mods/svc/config.h"
|
||||
#include "mods/svc/gfx.h"
|
||||
#include "mods/svc/hook.h"
|
||||
#include "mods/svc/hook.hpp"
|
||||
#include "mods/svc/log.h"
|
||||
#include "mods/svc/resource.h"
|
||||
#include "mods/svc/ui.h"
|
||||
@@ -69,6 +69,7 @@ GfxStageHookHandle g_frameBeforeHudHook = 0;
|
||||
UiWindowHandle g_controlsWindow = 0;
|
||||
ResourceBuffer g_shaderSource = RESOURCE_BUFFER_INIT;
|
||||
GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT;
|
||||
GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT;
|
||||
WGPURenderPipeline g_compositePipeline = nullptr; // multiply blend
|
||||
WGPURenderPipeline g_compositeDebugPipeline = nullptr; // no blend (debug views)
|
||||
WGPUBindGroupLayout g_compositeLayout = nullptr;
|
||||
@@ -399,18 +400,16 @@ bool build_composite_pipeline(
|
||||
.srcFactor = WGPUBlendFactor_Zero,
|
||||
.dstFactor = WGPUBlendFactor_One},
|
||||
};
|
||||
WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT;
|
||||
colorTarget.format = g_deviceInfo.color_format;
|
||||
if (blend) {
|
||||
colorTarget.blend = &blendState;
|
||||
}
|
||||
WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS];
|
||||
const uint32_t colorTargetCount = gfx_init_color_target_states(
|
||||
&g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All);
|
||||
WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT;
|
||||
fragment.module = module;
|
||||
fragment.entryPoint = {"fs_main", WGPU_STRLEN};
|
||||
fragment.targetCount = 1;
|
||||
fragment.targets = &colorTarget;
|
||||
fragment.targetCount = colorTargetCount;
|
||||
fragment.targets = colorTargets;
|
||||
WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT;
|
||||
depthStencil.format = g_deviceInfo.depth_format;
|
||||
depthStencil.format = g_sceneTargetLayout.depth_stencil_format;
|
||||
depthStencil.depthWriteEnabled = WGPUOptionalBool_False;
|
||||
depthStencil.depthCompare = WGPUCompareFunction_Always;
|
||||
|
||||
@@ -420,7 +419,7 @@ bool build_composite_pipeline(
|
||||
pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN};
|
||||
pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
|
||||
pipelineDesc.depthStencil = &depthStencil;
|
||||
pipelineDesc.multisample.count = g_deviceInfo.sample_count;
|
||||
pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count;
|
||||
pipelineDesc.fragment = &fragment;
|
||||
outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc);
|
||||
wgpuShaderModuleRelease(module);
|
||||
@@ -518,7 +517,7 @@ WGPUBindGroup create_composite_bind_group(WGPUDevice device, WGPUBindGroupLayout
|
||||
// Render worker thread: fullscreen deferred-shadow composite.
|
||||
void on_draw(
|
||||
ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) {
|
||||
if (payloadSize != sizeof(DrawPayload)) {
|
||||
if (payloadSize != sizeof(DrawPayload) || ctx->layout.key != g_sceneTargetLayout.key) {
|
||||
return;
|
||||
}
|
||||
DrawPayload data;
|
||||
@@ -1243,6 +1242,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||
if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) {
|
||||
return mods::set_error(error, MOD_ERROR, "failed to query device info");
|
||||
}
|
||||
if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) {
|
||||
return mods::set_error(error, MOD_ERROR, "failed to query scene target layout");
|
||||
}
|
||||
if (!build_composite_pipeline(true, g_compositePipeline, g_compositeLayout) ||
|
||||
!build_composite_pipeline(false, g_compositeDebugPipeline, g_compositeDebugLayout))
|
||||
{
|
||||
|
||||
@@ -189,12 +189,6 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||
return mods::set_error(error, MOD_ERROR, "failed to register mod panel");
|
||||
}
|
||||
|
||||
if (open_window() != MOD_OK) {
|
||||
svc_gfx->unregister_stage_hook(mod_ctx, g_stageHook);
|
||||
g_stageHook = 0;
|
||||
return mods::set_error(error, MOD_ERROR, "failed to open auxiliary window");
|
||||
}
|
||||
|
||||
mods::log::info("auxiliary WebGPU window ready");
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
@@ -143,6 +143,74 @@ eyebrow span {
|
||||
transition: decorator color opacity 0.1s linear-in-out;
|
||||
}
|
||||
|
||||
#menu-list button.game-mode-button {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#menu-list game-mode-label-viewport {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 8dp;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#menu-list game-mode-label {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 8dp 16dp;
|
||||
opacity: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
transition: opacity 0.24s cubic-in-out;
|
||||
}
|
||||
|
||||
#menu-list game-mode-label.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#menu-list game-mode-previous,
|
||||
#menu-list game-mode-next {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 48dp;
|
||||
height: 54dp;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #FFFFFF;
|
||||
font-family: "Material Symbols Rounded";
|
||||
font-weight: normal;
|
||||
font-size: 30dp;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#menu-list game-mode-previous {
|
||||
left: -48dp;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
#menu-list game-mode-next {
|
||||
right: -48dp;
|
||||
decorator: text("" center center);
|
||||
}
|
||||
|
||||
#menu-list button.game-mode-button.can-cycle game-mode-previous,
|
||||
#menu-list button.game-mode-button.can-cycle game-mode-next {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#menu-list button:hover,
|
||||
#menu-list button:focus-visible {
|
||||
color: black;
|
||||
|
||||
@@ -519,3 +519,13 @@ progress.verification-progress-bar {
|
||||
flex: 0 0 auto;
|
||||
padding-top: 4dp;
|
||||
}
|
||||
|
||||
.modal-actions-vertical {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.modal-actions-vertical button.modal-btn {
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
// Really lazy way to avoid duplicating these helpers
|
||||
#define dusk mods
|
||||
#include "../../../include/helpers/bits.hpp"
|
||||
#undef dusk
|
||||
@@ -0,0 +1,182 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <mods/service.hpp>
|
||||
#endif
|
||||
|
||||
#define FLOW_SERVICE_ID "dev.twilitrealm.dusklight.flow"
|
||||
#define FLOW_SERVICE_MAJOR 1u
|
||||
#define FLOW_SERVICE_MINOR 0u
|
||||
|
||||
typedef uint64_t FlowGraphHandle;
|
||||
typedef uint16_t FlowQueryId;
|
||||
typedef uint8_t FlowEventId;
|
||||
|
||||
/* FLW1 node data */
|
||||
typedef struct FlowNodeData {
|
||||
uint8_t bytes[8];
|
||||
} FlowNodeData;
|
||||
static_assert(sizeof(FlowNodeData) == 8);
|
||||
|
||||
/* Built-in branch queries. The result selects the edge slot next_node_index + result;
|
||||
* [param] is the branch node's 16-bit parameter. */
|
||||
typedef enum FlowGameQuery {
|
||||
FLOW_QUERY_SELECT_2 = 0, /* two-way selection result: 0 first, 1 second */
|
||||
FLOW_QUERY_EVENT_FLAG = 1, /* 0 if event flag [param] is set */
|
||||
FLOW_QUERY_PLAYER_FORM = 2, /* 0 human, 1 wolf, 2 riding */
|
||||
FLOW_QUERY_RANDOM = 3, /* random result in [0, param) */
|
||||
FLOW_QUERY_SELECT_3 = 4, /* three-way selection result: 0/1/2 */
|
||||
FLOW_QUERY_TALK_DISTANCE = 5, /* player within talk range; param overrides max distance */
|
||||
FLOW_QUERY_RUPEES = 6, /* 0 if rupees >= param; param 0 checks wallet max */
|
||||
FLOW_QUERY_SWORD_TUTORIAL_STEP = 7, /* 0 if the scarecrow tutorial step matches param */
|
||||
FLOW_QUERY_SWORD_TUTORIAL_RESULT = 8, /* 0 on tutorial success */
|
||||
FLOW_QUERY_SWORD_TUTORIAL_COUNT = 9, /* 0 if first success */
|
||||
FLOW_QUERY_TEMP_FLAG = 10, /* 0 if temporary event flag [param] is set */
|
||||
FLOW_QUERY_CHEST_FLAG = 11, /* 0 if treasure chest flag [param] is set */
|
||||
FLOW_QUERY_SAVE_SWITCH = 12, /* 0 if save switch [param] is set */
|
||||
FLOW_QUERY_SAVE_ITEM_FLAG = 13,
|
||||
FLOW_QUERY_DUNGEON_SWITCH = 14,
|
||||
FLOW_QUERY_DUNGEON_ITEM_FLAG = 15,
|
||||
FLOW_QUERY_ZONE_SWITCH = 16,
|
||||
FLOW_QUERY_ZONE_ITEM_FLAG = 17,
|
||||
FLOW_QUERY_ONE_ZONE_SWITCH = 18,
|
||||
FLOW_QUERY_ONE_ZONE_ITEM_FLAG = 19,
|
||||
FLOW_QUERY_EQUIPPED = 20, /* 1 if item [param] is equipped or on an item slot */
|
||||
FLOW_QUERY_ITEM_OWNED = 21, /* 0 if item [param] is owned */
|
||||
FLOW_QUERY_BOMB_BAG_COUNT = 22, /* number of bomb bags owned: 0-3 */
|
||||
FLOW_QUERY_ARROWS = 23, /* 0 if arrows >= param */
|
||||
FLOW_QUERY_EMPTY_BOTTLES = 24, /* 0 if empty bottles >= param */
|
||||
FLOW_QUERY_SHOP_CLERK = 25, /* shop system conversation flag */
|
||||
FLOW_QUERY_TEARS_OF_LIGHT = 26, /* 0 if tears >= param; param 0 uses the required count */
|
||||
/* 0 if goat-herding time <= param seconds; publishes the time for display */
|
||||
FLOW_QUERY_HERDING_TIME = 27,
|
||||
FLOW_QUERY_LANTERN_OIL = 28, /* 0 full, 1 partial, 2 empty */
|
||||
FLOW_QUERY_REGISTER = 29, /* flow scratch register value */
|
||||
FLOW_QUERY_GOATS_CAUGHT = 30, /* 0 if caught runaway goats >= param */
|
||||
FLOW_QUERY_HEARTS = 31, /* 0 if life >= param */
|
||||
FLOW_QUERY_HOLDING_LANTERN = 32, /* 0 if the player has the lantern out */
|
||||
FLOW_QUERY_TIME_OF_DAY = 33, /* current hour of game time (0-23) */
|
||||
FLOW_QUERY_MAGIC = 34, /* 0 if magic >= param */
|
||||
FLOW_QUERY_SELECT_2_CANCEL = 35, /* 0/1 choice, 2 on B cancel */
|
||||
FLOW_QUERY_SELECT_3_CANCEL = 36, /* 0/1/2 choice, 3 on B cancel */
|
||||
FLOW_QUERY_BOMB_BAG_CONTENTS = 37, /* 0 empty, 1 bombs, 2 water bombs, 3 bomblings */
|
||||
FLOW_QUERY_BOMBS_FIT = 38, /* 1 if param more bombs fit in the bag, 0 if over max */
|
||||
FLOW_QUERY_BOMB_BAG_FILL = 39, /* 0 empty, 1 partial, 2 full */
|
||||
FLOW_QUERY_WATER_BOMBS_FIT = 40,
|
||||
/* 0 clear, 1 NPC near, 2 NPC far, 3 environment, 4 Sacred Grove */
|
||||
FLOW_QUERY_TRANSFORM_BLOCKED = 41,
|
||||
FLOW_QUERY_BOMBLINGS_FIT = 42,
|
||||
FLOW_QUERY_WARP_ALLOWED = 43, /* 0 if a dungeon warp is accepted here */
|
||||
FLOW_QUERY_GOLDEN_BUGS = 44, /* 0 none, 1 1-11, 2 12-22, 3 23, 4 all 24 */
|
||||
FLOW_QUERY_UNDELIVERED_BUG = 45, /* 1 if carrying a golden bug not yet delivered to Agitha */
|
||||
FLOW_QUERY_UNUSED_46 = 46, /* asserts; do not use */
|
||||
FLOW_QUERY_NEW_LETTERS = 47, /* 0 none, 1 one (& stores its name for the tag), 2 more */
|
||||
FLOW_QUERY_POE_SOULS = 48, /* 0 none, 1 <20, 2 <40, 3 <60, 4 60+ */
|
||||
FLOW_QUERY_DONATION_TOTAL = 49, /* 0 if donations >= param */
|
||||
FLOW_QUERY_BALLOON_SCORE = 50, /* 0 zero, 1 <1000, 2 <10000, 3 <61454, 4 max */
|
||||
FLOW_QUERY_IN_WATER = 51, /* 1 if the player is swimming */
|
||||
FLOW_QUERY_IRON_BOOTS = 52, /* 1 if iron boots are equipped */
|
||||
FLOW_QUERY_BUILTIN_COUNT,
|
||||
} FlowGameQuery;
|
||||
|
||||
/* Built-in event actions. Params (big-endian): one u32, two u16 (p0, p1), or four u8. */
|
||||
typedef enum FlowGameEvent {
|
||||
FLOW_EVENT_SET_EVENT_FLAG = 0, /* sets event flags [p0] and [p1]; 0 = none */
|
||||
FLOW_EVENT_CLEAR_EVENT_FLAG = 1,
|
||||
FLOW_EVENT_ADD_RUPEES = 2,
|
||||
FLOW_EVENT_REMOVE_RUPEES = 3,
|
||||
FLOW_EVENT_ADD_HEARTS = 4,
|
||||
FLOW_EVENT_REMOVE_HEARTS = 5,
|
||||
FLOW_EVENT_ADD_MAGIC = 6,
|
||||
FLOW_EVENT_REMOVE_MAGIC = 7,
|
||||
FLOW_EVENT_START_EVENT = 8, /* publish (p0 event id, p1 item id) for the speaker to poll */
|
||||
FLOW_EVENT_JUMP_FLOW = 9, /* continue at flow [p]; 0 jumps to the stage/Midna flow */
|
||||
FLOW_EVENT_SET_TEMP_FLAG = 10,
|
||||
FLOW_EVENT_CLEAR_TEMP_FLAG = 11,
|
||||
FLOW_EVENT_OPEN_DOOR = 12, /* marks the flow as a door unlock path (probe only) */
|
||||
FLOW_EVENT_SELECT_VERTICAL = 13, /* vertical selection; p = result index chosen on B cancel */
|
||||
FLOW_EVENT_SET_SWITCH = 14, /* p0 scope: 0 save, 1 dungeon, 2 zone, 3 one-zone; p1 bit */
|
||||
FLOW_EVENT_CLEAR_SWITCH = 15,
|
||||
FLOW_EVENT_SHOP_SELECT = 16, /* start shop item selection; four u8 shop params */
|
||||
FLOW_EVENT_GIVE_ITEM = 17, /* p0 item number, p1 count */
|
||||
/* four u8 direction values for the speaker; p3 plays a sound */
|
||||
FLOW_EVENT_STAGE_DIRECTION = 18,
|
||||
FLOW_EVENT_SET_SPEAKER = 19, /* point the box at talk partner [p1] */
|
||||
FLOW_EVENT_WARP_PLAYER = 20, /* move the player to the room spawn tagged [p] */
|
||||
FLOW_EVENT_WAIT = 21, /* close the box and wait [p] frames */
|
||||
FLOW_EVENT_FILL_LANTERN = 22, /* refill oil to [p] percent; 0 = full */
|
||||
/* p: 1-3 red/green/blue potion, 4 milk, 5 half milk, 6 oil, 7 hot spring water */
|
||||
FLOW_EVENT_FILL_BOTTLE = 23,
|
||||
FLOW_EVENT_SHOP_SOLD_OUT = 24,
|
||||
FLOW_EVENT_SET_REGISTER = 25, /* set the flow scratch register (see FLOW_QUERY_REGISTER) */
|
||||
FLOW_EVENT_TENT_PURCHASE = 26, /* unattended stand purchase; marks the item sold out */
|
||||
FLOW_EVENT_FILL_BOMBS = 27, /* u8 p0 bag select, u8 p1 operation; u16 p1 count */
|
||||
FLOW_EVENT_SELL_BOMBS = 28, /* empty the selected bag and pay out */
|
||||
/* horizontal selection; p = result index chosen on B cancel */
|
||||
FLOW_EVENT_SELECT_HORIZONTAL = 29,
|
||||
FLOW_EVENT_FILL_ARROWS = 30, /* p1 count, 0 = max; p0 nonzero defers the refill */
|
||||
FLOW_EVENT_RETURN_RENTAL_BOMB_BAG = 31,
|
||||
FLOW_EVENT_FADE_IN = 32, /* p0: 0 black, 1 white; p1 frames */
|
||||
FLOW_EVENT_FADE_OUT = 33,
|
||||
FLOW_EVENT_SET_TRADE_ITEM = 34, /* set the trade-quest item */
|
||||
FLOW_EVENT_REMOVE_ITEM = 35,
|
||||
FLOW_EVENT_SET_SAVE_SWITCH = 36, /* set save switch (p0 area, p1 bit) */
|
||||
FLOW_EVENT_CLEAR_SAVE_SWITCH = 37,
|
||||
FLOW_EVENT_RECEIVE_LETTER = 38,
|
||||
FLOW_EVENT_UNLOCK_MAP_REGION = 39,
|
||||
FLOW_EVENT_EMPTY_BOTTLE = 40, /* p as in FLOW_EVENT_FILL_BOTTLE */
|
||||
FLOW_EVENT_ADD_DONATION = 41,
|
||||
FLOW_EVENT_UNUSED_42 = 42, /* no-op */
|
||||
FLOW_EVENT_BUILTIN_COUNT,
|
||||
} FlowGameEvent;
|
||||
|
||||
typedef enum FlowQueryPhase {
|
||||
FLOW_QUERY_PHASE_PROBE = 0,
|
||||
FLOW_QUERY_PHASE_EXECUTE = 1,
|
||||
} FlowQueryPhase;
|
||||
|
||||
typedef struct FlowQueryContext {
|
||||
const void* speaker_actor;
|
||||
uint16_t parameter;
|
||||
uint8_t result_count;
|
||||
uint8_t phase; /* FlowQueryPhase */
|
||||
} FlowQueryContext;
|
||||
|
||||
typedef uint16_t (*FlowQueryFn)(ModContext* ctx, const FlowQueryContext* query, void* user_data);
|
||||
|
||||
typedef struct FlowEventContext {
|
||||
const void* speaker_actor;
|
||||
uint8_t parameters[4];
|
||||
} FlowEventContext;
|
||||
|
||||
typedef void (*FlowEventFn)(ModContext* ctx, const FlowEventContext* event, void* user_data);
|
||||
|
||||
typedef struct FlowService {
|
||||
ServiceHeader header;
|
||||
|
||||
ModResult (*begin_graph)(ModContext* ctx, uint16_t group, FlowGraphHandle* out_handle);
|
||||
/* Allocates one node ID. Fill it with fill_node before commit_graph. */
|
||||
ModResult (*allocate_node)(ModContext* ctx, FlowGraphHandle handle, uint16_t* out_id);
|
||||
/* Adds a series of edges with the given targets. */
|
||||
ModResult (*add_edges)(ModContext* ctx, FlowGraphHandle handle, const uint16_t* targets,
|
||||
uint16_t count, uint16_t* out_first);
|
||||
/* May be called again to replace a node until commit_graph. */
|
||||
ModResult (*fill_node)(
|
||||
ModContext* ctx, FlowGraphHandle handle, uint16_t node_index, const FlowNodeData* node);
|
||||
/* Replace a native node or edge when the graph commits; reverted when it is removed. */
|
||||
ModResult (*patch_node)(
|
||||
ModContext* ctx, FlowGraphHandle handle, uint16_t node_index, const FlowNodeData* node);
|
||||
ModResult (*patch_edge)(
|
||||
ModContext* ctx, FlowGraphHandle handle, uint16_t edge_index, uint16_t target_node);
|
||||
ModResult (*commit_graph)(ModContext* ctx, FlowGraphHandle handle);
|
||||
ModResult (*remove_graph)(ModContext* ctx, FlowGraphHandle handle);
|
||||
|
||||
ModResult (*register_query)(ModContext* ctx, const char* debug_name, FlowQueryFn fn,
|
||||
void* user_data, FlowQueryId* out_id);
|
||||
ModResult (*register_event)(ModContext* ctx, const char* debug_name, FlowEventFn fn,
|
||||
void* user_data, FlowEventId* out_id);
|
||||
} FlowService;
|
||||
|
||||
MOD_DECLARE_SERVICE(FlowService, svc_flow, FLOW_SERVICE_ID, FLOW_SERVICE_MAJOR, FLOW_SERVICE_MINOR);
|
||||
@@ -0,0 +1,633 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/bits.hpp>
|
||||
#include <mods/svc/flow.h>
|
||||
#include <mods/svc/message.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace mods::flow {
|
||||
|
||||
static_assert(sizeof(FlowNodeData) == 8);
|
||||
static_assert(sizeof(MessageEntryData) == 20);
|
||||
|
||||
inline constexpr uint16_t kCustomNodeMin = 0x8000;
|
||||
inline constexpr uint16_t kCustomEdgeMin = 0x8000;
|
||||
inline constexpr uint16_t kCustomMessageMin = 0x8000;
|
||||
inline constexpr uint16_t kEnd = 0xffff;
|
||||
|
||||
/* entryIndex is the INF1 entry index, not the message ID stored inside the entry.
|
||||
* For custom messages the entry index equals the MessageId. */
|
||||
constexpr FlowNodeData message(
|
||||
uint8_t subtype, uint16_t entryIndex, uint16_t nextNode, uint16_t unknown = 0) {
|
||||
FlowNodeData node{};
|
||||
node.bytes[0] = 1;
|
||||
node.bytes[1] = subtype;
|
||||
write_bits(node.bytes + 2, entryIndex);
|
||||
write_bits(node.bytes + 4, nextNode);
|
||||
write_bits(node.bytes + 6, unknown);
|
||||
return node;
|
||||
}
|
||||
|
||||
constexpr FlowNodeData branch(
|
||||
uint8_t resultCount, FlowQueryId query, uint16_t parameter, uint16_t firstEdge) {
|
||||
FlowNodeData node{};
|
||||
node.bytes[0] = 2;
|
||||
node.bytes[1] = resultCount;
|
||||
write_bits(node.bytes + 2, query);
|
||||
write_bits(node.bytes + 4, parameter);
|
||||
write_bits(node.bytes + 6, firstEdge);
|
||||
return node;
|
||||
}
|
||||
|
||||
constexpr FlowNodeData event(FlowEventId eventId, uint16_t edge, std::array<uint8_t, 4> params) {
|
||||
FlowNodeData node{};
|
||||
node.bytes[0] = 3;
|
||||
node.bytes[1] = eventId;
|
||||
write_bits(node.bytes + 2, edge);
|
||||
for (size_t i = 0; i < params.size(); ++i) {
|
||||
node.bytes[4 + i] = params[i];
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
class Graph {
|
||||
public:
|
||||
Graph() = default;
|
||||
Graph(FlowGraphHandle handle, ModResult result) : mHandle{handle}, mResult{result} {}
|
||||
Graph(const Graph&) = delete;
|
||||
Graph& operator=(const Graph&) = delete;
|
||||
Graph(Graph&& other) noexcept { *this = std::move(other); }
|
||||
Graph& operator=(Graph&& other) noexcept {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
mHandle = std::exchange(other.mHandle, 0);
|
||||
mResult = other.mResult;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
~Graph() { reset(); }
|
||||
|
||||
explicit operator bool() const { return mResult == MOD_OK; }
|
||||
ModResult result() const { return mResult; }
|
||||
FlowGraphHandle handle() const { return mHandle; }
|
||||
void reset() {
|
||||
if (mHandle != 0 && svc_flow != nullptr) {
|
||||
svc_flow->remove_graph(mod_ctx, mHandle);
|
||||
mHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
FlowGraphHandle mHandle{};
|
||||
ModResult mResult = MOD_UNAVAILABLE;
|
||||
};
|
||||
|
||||
class GraphBuilder;
|
||||
|
||||
class NodeRef {
|
||||
public:
|
||||
NodeRef() = default;
|
||||
|
||||
/* Set the successor of a message or event node: a node ID or kEnd */
|
||||
NodeRef next(uint16_t target) const;
|
||||
/* Set a branch node's result targets */
|
||||
NodeRef results(std::initializer_list<uint16_t> targets) const;
|
||||
|
||||
uint16_t id() const { return mId; }
|
||||
operator uint16_t() const { return mId; }
|
||||
|
||||
private:
|
||||
friend class GraphBuilder;
|
||||
NodeRef(GraphBuilder* builder, uint16_t id) : mBuilder{builder}, mId{id} {}
|
||||
|
||||
GraphBuilder* mBuilder = nullptr;
|
||||
uint16_t mId = 0;
|
||||
};
|
||||
|
||||
class GraphBuilder {
|
||||
public:
|
||||
explicit GraphBuilder(uint16_t group) {
|
||||
if (svc_flow == nullptr) {
|
||||
mResult = MOD_UNAVAILABLE;
|
||||
return;
|
||||
}
|
||||
mResult = svc_flow->begin_graph(mod_ctx, group, &mHandle);
|
||||
}
|
||||
GraphBuilder(const GraphBuilder&) = delete;
|
||||
GraphBuilder& operator=(const GraphBuilder&) = delete;
|
||||
~GraphBuilder() {
|
||||
if (mHandle != 0 && svc_flow != nullptr) {
|
||||
svc_flow->remove_graph(mod_ctx, mHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/* entryIndex is an INF1 entry index for native messages, or a registered MessageId */
|
||||
NodeRef add_message(uint16_t entryIndex, uint8_t subtype = 0) {
|
||||
return append(message(subtype, entryIndex, 0), true);
|
||||
}
|
||||
NodeRef add_branch(FlowQueryId query, uint16_t parameter) {
|
||||
return append(branch(0, query, parameter, 0), true);
|
||||
}
|
||||
NodeRef add_event(FlowEventId eventId, std::array<uint8_t, 4> params) {
|
||||
return append(event(eventId, 0, params), true);
|
||||
}
|
||||
NodeRef add_node(const FlowNodeData& node) { return append(node, false); }
|
||||
|
||||
GraphBuilder& patch_node(uint16_t nodeIndex, const FlowNodeData& node) {
|
||||
if (mResult == MOD_OK) {
|
||||
mResult = svc_flow->patch_node(mod_ctx, mHandle, nodeIndex, &node);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
GraphBuilder& patch_edge(uint16_t edgeIndex, uint16_t targetNode) {
|
||||
if (mResult == MOD_OK) {
|
||||
mResult = svc_flow->patch_edge(mod_ctx, mHandle, edgeIndex, targetNode);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Graph commit() {
|
||||
for (const auto& node : mNodes) {
|
||||
if (node.typed && !node.wired) {
|
||||
mResult = MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
for (const auto& node : mNodes) {
|
||||
if (mResult != MOD_OK) {
|
||||
break;
|
||||
}
|
||||
mResult = svc_flow->fill_node(mod_ctx, mHandle, node.id, &node.data);
|
||||
}
|
||||
if (mResult == MOD_OK) {
|
||||
mResult = svc_flow->commit_graph(mod_ctx, mHandle);
|
||||
}
|
||||
if (mResult != MOD_OK) {
|
||||
return {0, mResult}; // the builder destructor removes the graph
|
||||
}
|
||||
return {std::exchange(mHandle, 0), MOD_OK};
|
||||
}
|
||||
|
||||
private:
|
||||
friend class NodeRef;
|
||||
|
||||
struct BuilderNode {
|
||||
uint16_t id;
|
||||
FlowNodeData data;
|
||||
bool typed;
|
||||
bool wired;
|
||||
};
|
||||
|
||||
NodeRef append(const FlowNodeData& node, bool typed) {
|
||||
uint16_t id = 0;
|
||||
if (mResult == MOD_OK) {
|
||||
mResult = svc_flow->allocate_node(mod_ctx, mHandle, &id);
|
||||
}
|
||||
if (mResult == MOD_OK) {
|
||||
mNodes.push_back({id, node, typed, false});
|
||||
}
|
||||
return {this, id};
|
||||
}
|
||||
|
||||
BuilderNode* find(uint16_t id) {
|
||||
for (auto& node : mNodes) {
|
||||
if (node.id == id) {
|
||||
return &node;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set_next(uint16_t id, uint16_t target) {
|
||||
if (mResult != MOD_OK) {
|
||||
return;
|
||||
}
|
||||
auto* node = find(id);
|
||||
if (node == nullptr || node->wired) {
|
||||
mResult = MOD_INVALID_ARGUMENT;
|
||||
return;
|
||||
}
|
||||
node->wired = true;
|
||||
switch (node->data.bytes[0]) {
|
||||
case 1:
|
||||
write_bits(node->data.bytes + 4, target);
|
||||
break;
|
||||
case 3: {
|
||||
uint16_t first = 0;
|
||||
mResult = svc_flow->add_edges(mod_ctx, mHandle, &target, 1, &first);
|
||||
if (mResult == MOD_OK) {
|
||||
write_bits(node->data.bytes + 2, first);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
mResult = MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
void set_results(uint16_t id, std::initializer_list<uint16_t> targets) {
|
||||
if (mResult != MOD_OK) {
|
||||
return;
|
||||
}
|
||||
auto* node = find(id);
|
||||
if (node == nullptr || node->wired || node->data.bytes[0] != 2 || targets.size() == 0 ||
|
||||
targets.size() > 0xff)
|
||||
{
|
||||
mResult = MOD_INVALID_ARGUMENT;
|
||||
return;
|
||||
}
|
||||
node->wired = true;
|
||||
uint16_t first = 0;
|
||||
mResult = svc_flow->add_edges(
|
||||
mod_ctx, mHandle, targets.begin(), static_cast<uint16_t>(targets.size()), &first);
|
||||
if (mResult == MOD_OK) {
|
||||
node->data.bytes[1] = static_cast<uint8_t>(targets.size());
|
||||
write_bits(node->data.bytes + 6, first);
|
||||
}
|
||||
}
|
||||
|
||||
FlowGraphHandle mHandle{};
|
||||
std::vector<BuilderNode> mNodes;
|
||||
ModResult mResult = MOD_OK;
|
||||
};
|
||||
|
||||
inline NodeRef NodeRef::next(uint16_t target) const {
|
||||
if (mBuilder != nullptr) {
|
||||
mBuilder->set_next(mId, target);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline NodeRef NodeRef::results(std::initializer_list<uint16_t> targets) const {
|
||||
if (mBuilder != nullptr) {
|
||||
mBuilder->set_results(mId, targets);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Single-patch convenience functions */
|
||||
inline Graph patch_node(uint16_t group, uint16_t nodeIndex, const FlowNodeData& node) {
|
||||
GraphBuilder builder{group};
|
||||
builder.patch_node(nodeIndex, node);
|
||||
return builder.commit();
|
||||
}
|
||||
|
||||
inline Graph patch_edge(uint16_t group, uint16_t edgeIndex, uint16_t targetNode) {
|
||||
GraphBuilder builder{group};
|
||||
builder.patch_edge(edgeIndex, targetNode);
|
||||
return builder.commit();
|
||||
}
|
||||
|
||||
class Query {
|
||||
public:
|
||||
Query() = default;
|
||||
Query(FlowQueryId id, ModResult result) : mId{id}, mResult{result} {}
|
||||
explicit operator bool() const { return mResult == MOD_OK; }
|
||||
FlowQueryId id() const { return mId; }
|
||||
ModResult result() const { return mResult; }
|
||||
|
||||
private:
|
||||
FlowQueryId mId{};
|
||||
ModResult mResult = MOD_UNAVAILABLE;
|
||||
};
|
||||
|
||||
inline Query register_query(const char* debugName, FlowQueryFn fn, void* userData = nullptr) {
|
||||
FlowQueryId id{};
|
||||
const ModResult result = svc_flow != nullptr ?
|
||||
svc_flow->register_query(mod_ctx, debugName, fn, userData, &id) :
|
||||
MOD_UNAVAILABLE;
|
||||
return {id, result};
|
||||
}
|
||||
|
||||
class Event {
|
||||
public:
|
||||
Event() = default;
|
||||
Event(FlowEventId id, ModResult result) : mId{id}, mResult{result} {}
|
||||
explicit operator bool() const { return mResult == MOD_OK; }
|
||||
FlowEventId id() const { return mId; }
|
||||
ModResult result() const { return mResult; }
|
||||
|
||||
private:
|
||||
FlowEventId mId{};
|
||||
ModResult mResult = MOD_UNAVAILABLE;
|
||||
};
|
||||
|
||||
inline Event register_event(const char* debugName, FlowEventFn fn, void* userData = nullptr) {
|
||||
FlowEventId id{};
|
||||
const ModResult result = svc_flow != nullptr ?
|
||||
svc_flow->register_event(mod_ctx, debugName, fn, userData, &id) :
|
||||
MOD_UNAVAILABLE;
|
||||
return {id, result};
|
||||
}
|
||||
|
||||
/* Message presentation/style attributes (INF1) */
|
||||
class MessageStyle {
|
||||
public:
|
||||
constexpr MessageStyle() { mData.bytes[12] = 0xff; }
|
||||
constexpr explicit MessageStyle(MessageEntryData data) : mData{data} {}
|
||||
|
||||
/* saveBitLabels index set when the message displays. */
|
||||
[[nodiscard]] constexpr MessageStyle event_label_id(uint16_t value) const {
|
||||
return set_u16(6, value);
|
||||
}
|
||||
/* Z2SpeechMgr2 voice bank ID */
|
||||
[[nodiscard]] constexpr MessageStyle speaker(uint8_t value) const { return set_u8(8, value); }
|
||||
[[nodiscard]] constexpr MessageStyle box_kind(MessageBoxKind value) const {
|
||||
return set_u8(9, static_cast<uint8_t>(value));
|
||||
}
|
||||
[[nodiscard]] constexpr MessageStyle draw_type(MessageDrawType value) const {
|
||||
return set_u8(10, static_cast<uint8_t>(value));
|
||||
}
|
||||
[[nodiscard]] constexpr MessageStyle box_position(MessageBoxPosition value) const {
|
||||
return set_u8(11, static_cast<uint8_t>(value));
|
||||
}
|
||||
/* 0 centered (JP builds only), 1 left */
|
||||
[[nodiscard]] constexpr MessageStyle line_alignment(uint8_t value) const {
|
||||
return set_u8(13, value);
|
||||
}
|
||||
/* Grunt emotion index for the voice bank */
|
||||
[[nodiscard]] constexpr MessageStyle speaker_mood(uint8_t value) const {
|
||||
return set_u8(14, value);
|
||||
}
|
||||
/* 1-10 focus talk-actor slot, >=11 talk-camera style, 0 none */
|
||||
[[nodiscard]] constexpr MessageStyle camera_attr(uint8_t value) const {
|
||||
return set_u8(15, value);
|
||||
}
|
||||
/* NPC talk motion attribute */
|
||||
[[nodiscard]] constexpr MessageStyle talk_anim(uint8_t value) const {
|
||||
return set_u8(16, value);
|
||||
}
|
||||
/* NPC talk face attribute */
|
||||
[[nodiscard]] constexpr MessageStyle face_anim(uint8_t value) const {
|
||||
return set_u8(17, value);
|
||||
}
|
||||
[[nodiscard]] constexpr const MessageEntryData& data() const { return mData; }
|
||||
|
||||
private:
|
||||
[[nodiscard]] constexpr MessageStyle set_u8(size_t offset, uint8_t value) const {
|
||||
MessageStyle style = *this;
|
||||
style.mData.bytes[offset] = value;
|
||||
return style;
|
||||
}
|
||||
[[nodiscard]] constexpr MessageStyle set_u16(size_t offset, uint16_t value) const {
|
||||
MessageStyle style = *this;
|
||||
write_bits(style.mData.bytes + offset, value);
|
||||
return style;
|
||||
}
|
||||
MessageEntryData mData{};
|
||||
};
|
||||
|
||||
class MessageVariant {
|
||||
public:
|
||||
MessageVariant() = default;
|
||||
MessageVariant(MessageLanguage language, MessageEntryData entry, std::vector<uint8_t> text,
|
||||
ModResult result = MOD_OK)
|
||||
: mLanguage{language}, mEntry{entry}, mText{std::move(text)}, mResult{result} {}
|
||||
|
||||
explicit operator bool() const { return mResult == MOD_OK; }
|
||||
ModResult result() const { return mResult; }
|
||||
MessageLanguage language() const { return mLanguage; }
|
||||
const MessageEntryData& entry() const { return mEntry; }
|
||||
const std::vector<uint8_t>& text() const { return mText; }
|
||||
MessageVariantData data() const {
|
||||
return {static_cast<uint8_t>(mLanguage), mEntry, mText.data(), mText.size()};
|
||||
}
|
||||
|
||||
private:
|
||||
MessageLanguage mLanguage = MESSAGE_LANGUAGE_ENGLISH;
|
||||
MessageEntryData mEntry{};
|
||||
std::vector<uint8_t> mText;
|
||||
ModResult mResult = MOD_UNAVAILABLE;
|
||||
};
|
||||
|
||||
class MessageBuilder {
|
||||
public:
|
||||
explicit MessageBuilder(MessageStyle style = {}) : mStyle{style} { mText.push_back(0); }
|
||||
|
||||
/* Style setters forwarded to MessageStyle */
|
||||
MessageBuilder& event_label_id(uint16_t value) {
|
||||
return style(&MessageStyle::event_label_id, value);
|
||||
}
|
||||
MessageBuilder& speaker(uint8_t value) { return style(&MessageStyle::speaker, value); }
|
||||
MessageBuilder& box_kind(MessageBoxKind value) { return style(&MessageStyle::box_kind, value); }
|
||||
MessageBuilder& draw_type(MessageDrawType value) {
|
||||
return style(&MessageStyle::draw_type, value);
|
||||
}
|
||||
MessageBuilder& box_position(MessageBoxPosition value) {
|
||||
return style(&MessageStyle::box_position, value);
|
||||
}
|
||||
MessageBuilder& line_alignment(uint8_t value) {
|
||||
return style(&MessageStyle::line_alignment, value);
|
||||
}
|
||||
MessageBuilder& speaker_mood(uint8_t value) {
|
||||
return style(&MessageStyle::speaker_mood, value);
|
||||
}
|
||||
MessageBuilder& camera_attr(uint8_t value) { return style(&MessageStyle::camera_attr, value); }
|
||||
MessageBuilder& talk_anim(uint8_t value) { return style(&MessageStyle::talk_anim, value); }
|
||||
MessageBuilder& face_anim(uint8_t value) { return style(&MessageStyle::face_anim, value); }
|
||||
|
||||
/* Content builder functions */
|
||||
MessageBuilder& text(std::string_view value) {
|
||||
if (!mText.empty()) {
|
||||
mText.pop_back();
|
||||
}
|
||||
mText.insert(mText.end(), value.begin(), value.end());
|
||||
mText.push_back(0);
|
||||
return *this;
|
||||
}
|
||||
MessageBuilder& raw_tag(uint8_t group, uint16_t type, std::span<const uint8_t> arguments) {
|
||||
if (arguments.size() > 250) {
|
||||
mResult = MOD_INVALID_ARGUMENT;
|
||||
return *this;
|
||||
}
|
||||
mText.pop_back();
|
||||
mText.push_back(0x1a);
|
||||
mText.push_back(static_cast<uint8_t>(5 + arguments.size()));
|
||||
mText.push_back(group);
|
||||
mText.push_back(static_cast<uint8_t>(type >> 8));
|
||||
mText.push_back(static_cast<uint8_t>(type));
|
||||
mText.insert(mText.end(), arguments.begin(), arguments.end());
|
||||
mText.push_back(0);
|
||||
return *this;
|
||||
}
|
||||
MessageBuilder& text_color(MessageTextColor color) {
|
||||
const auto index = static_cast<uint8_t>(color);
|
||||
return raw_tag(255, 0, {&index, 1});
|
||||
}
|
||||
MessageBuilder& text_scale(uint16_t percent) { return timed_tag(255, 1, percent); }
|
||||
MessageBuilder& character_delay(uint16_t frames) { return timed_tag(0, 6, frames); }
|
||||
MessageBuilder& pause(uint16_t frames) { return timed_tag(0, 7, frames); }
|
||||
MessageBuilder& auto_advance(uint16_t frames) { return timed_tag(0, 4, frames); }
|
||||
MessageBuilder& auto_advance_alternate(uint16_t frames) { return timed_tag(0, 3, frames); }
|
||||
MessageBuilder& input_or_timeout(uint16_t frames) { return timed_tag(0, 5, frames); }
|
||||
MessageBuilder& input_after_delay(uint16_t frames) { return timed_tag(0, 54, frames); }
|
||||
/* Insert the player's name. */
|
||||
MessageBuilder& player_name() { return raw_tag(0, 0, {}); }
|
||||
/* End a prompt that presents a selection; the options live in the target flow node. */
|
||||
MessageBuilder& await_choice() { return raw_tag(0, 32, {}); }
|
||||
/* Option list for a vertical selection message. Only two or three options are supported.
|
||||
* `initial` configures the highlighted option on open. */
|
||||
MessageBuilder& options(std::string_view first, std::string_view second, uint8_t initial = 0) {
|
||||
option(8, 0, initial, first);
|
||||
return option(8, 1, initial, second);
|
||||
}
|
||||
MessageBuilder& options(std::string_view first, std::string_view second, std::string_view third,
|
||||
uint8_t initial = 0) {
|
||||
option(9, 0, initial, first);
|
||||
option(9, 1, initial, second);
|
||||
return option(9, 2, initial, third);
|
||||
}
|
||||
MessageVariant build(MessageLanguage language) const {
|
||||
return {language, mStyle.data(), mText, mResult};
|
||||
}
|
||||
|
||||
private:
|
||||
MessageBuilder& option(
|
||||
uint16_t type, uint8_t position, uint8_t initial, std::string_view value) {
|
||||
if (position > 0) {
|
||||
text("\n");
|
||||
}
|
||||
uint8_t marker = static_cast<uint8_t>(position + 1);
|
||||
if (position == initial) {
|
||||
marker = 1;
|
||||
} else if (position == 0) {
|
||||
marker = static_cast<uint8_t>(initial + 1);
|
||||
}
|
||||
raw_tag(0, type, {&marker, 1});
|
||||
return text(value);
|
||||
}
|
||||
|
||||
template <typename Setter, typename Value>
|
||||
MessageBuilder& style(Setter setter, Value value) {
|
||||
mStyle = (mStyle.*setter)(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MessageBuilder& timed_tag(uint8_t group, uint16_t type, uint16_t value) {
|
||||
return raw_tag(
|
||||
group, type, std::array{static_cast<uint8_t>(value >> 8), static_cast<uint8_t>(value)});
|
||||
}
|
||||
|
||||
MessageStyle mStyle;
|
||||
std::vector<uint8_t> mText;
|
||||
ModResult mResult = MOD_OK;
|
||||
};
|
||||
|
||||
class MessageOverride {
|
||||
public:
|
||||
MessageOverride() = default;
|
||||
MessageOverride(MessageOverrideHandle handle, ModResult result)
|
||||
: mHandle{handle}, mResult{result} {}
|
||||
MessageOverride(const MessageOverride&) = delete;
|
||||
MessageOverride& operator=(const MessageOverride&) = delete;
|
||||
MessageOverride(MessageOverride&& other) noexcept { *this = std::move(other); }
|
||||
MessageOverride& operator=(MessageOverride&& other) noexcept {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
mHandle = std::exchange(other.mHandle, 0);
|
||||
mResult = other.mResult;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
~MessageOverride() { reset(); }
|
||||
|
||||
explicit operator bool() const { return mResult == MOD_OK; }
|
||||
ModResult result() const { return mResult; }
|
||||
MessageOverrideHandle handle() const { return mHandle; }
|
||||
void reset() {
|
||||
if (mHandle != 0 && svc_message != nullptr) {
|
||||
svc_message->remove_override(mod_ctx, mHandle);
|
||||
mHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
MessageOverrideHandle mHandle{};
|
||||
ModResult mResult = MOD_UNAVAILABLE;
|
||||
};
|
||||
|
||||
inline MessageOverride override_message(
|
||||
uint16_t group, uint16_t messageId, MessageLanguage language, std::span<const uint8_t> text) {
|
||||
MessageOverrideHandle handle{};
|
||||
const ModResult result = svc_message != nullptr ? svc_message->override_message(mod_ctx, group,
|
||||
messageId, static_cast<uint8_t>(language),
|
||||
text.data(), text.size(), &handle) :
|
||||
MOD_UNAVAILABLE;
|
||||
return {handle, result};
|
||||
}
|
||||
|
||||
inline MessageOverride override_message_fn(uint16_t group, uint16_t messageId,
|
||||
MessageLanguage language, MessageOverrideFn fn, void* userData = nullptr) {
|
||||
MessageOverrideHandle handle{};
|
||||
const ModResult result = svc_message != nullptr ?
|
||||
svc_message->override_message_fn(mod_ctx, group, messageId,
|
||||
static_cast<uint8_t>(language), fn, userData, &handle) :
|
||||
MOD_UNAVAILABLE;
|
||||
return {handle, result};
|
||||
}
|
||||
|
||||
class RegisteredMessage {
|
||||
public:
|
||||
RegisteredMessage() = default;
|
||||
RegisteredMessage(MessageId id, MessageHandle handle, ModResult result)
|
||||
: mId{id}, mHandle{handle}, mResult{result} {}
|
||||
RegisteredMessage(const RegisteredMessage&) = delete;
|
||||
RegisteredMessage& operator=(const RegisteredMessage&) = delete;
|
||||
RegisteredMessage(RegisteredMessage&& other) noexcept { *this = std::move(other); }
|
||||
RegisteredMessage& operator=(RegisteredMessage&& other) noexcept {
|
||||
if (this != &other) {
|
||||
reset();
|
||||
mId = other.mId;
|
||||
mHandle = std::exchange(other.mHandle, 0);
|
||||
mResult = other.mResult;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
~RegisteredMessage() { reset(); }
|
||||
explicit operator bool() const { return mResult == MOD_OK; }
|
||||
MessageId id() const { return mId; }
|
||||
ModResult result() const { return mResult; }
|
||||
void reset() {
|
||||
if (mHandle != 0 && svc_message != nullptr) {
|
||||
svc_message->remove_message(mod_ctx, mHandle);
|
||||
mHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
MessageId mId{};
|
||||
MessageHandle mHandle{};
|
||||
ModResult mResult = MOD_UNAVAILABLE;
|
||||
};
|
||||
|
||||
inline RegisteredMessage register_message(
|
||||
uint16_t group, std::span<const MessageVariant> variants) {
|
||||
std::vector<MessageVariantData> data;
|
||||
data.reserve(variants.size());
|
||||
for (const auto& variant : variants) {
|
||||
if (!variant) {
|
||||
return {0, 0, variant.result()};
|
||||
}
|
||||
data.push_back(variant.data());
|
||||
}
|
||||
MessageId id{};
|
||||
MessageHandle handle{};
|
||||
const ModResult result = svc_message != nullptr ? svc_message->register_message(mod_ctx, group,
|
||||
data.data(), data.size(), &id, &handle) :
|
||||
MOD_UNAVAILABLE;
|
||||
return {id, handle, result};
|
||||
}
|
||||
|
||||
inline RegisteredMessage register_message(
|
||||
uint16_t group, std::initializer_list<MessageVariant> variants) {
|
||||
return register_message(group, std::span{variants.begin(), variants.size()});
|
||||
}
|
||||
|
||||
} // namespace mods::flow
|
||||
@@ -16,7 +16,7 @@
|
||||
* of letting them corrupt memory.
|
||||
*/
|
||||
#define GAME_SERVICE_ID "dev.twilitrealm.dusklight.game"
|
||||
#define GAME_SERVICE_MAJOR 1u
|
||||
#define GAME_SERVICE_MAJOR 2u
|
||||
#define GAME_SERVICE_MINOR 0u
|
||||
|
||||
typedef struct GameService {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
#include <mods/svc/config.h>
|
||||
|
||||
#define GAME_MODE_SERVICE_ID "dev.twilitrealm.dusklight.gamemode"
|
||||
#define GAME_MODE_SERVICE_MAJOR 1u
|
||||
#define GAME_MODE_SERVICE_MINOR 0u
|
||||
|
||||
/* MOD_OK on success; failures disable the mod. */
|
||||
typedef ModResult (*GameModeCallback)(void* user_data, ModError* out_error);
|
||||
|
||||
typedef enum GameModeNewSaveState {
|
||||
GAME_MODE_STATE_PENDING = 0,
|
||||
GAME_MODE_STATE_PROCEED,
|
||||
GAME_MODE_STATE_RETURN,
|
||||
} GameModeNewSaveState;
|
||||
|
||||
/* Set state to PROCEED after custom UI completes, or RETURN to cancel.
|
||||
* State pointer is valid until selection completes. */
|
||||
typedef ModResult (*GameModeNewSaveSelectCallback)(
|
||||
void* user_data, GameModeNewSaveState* state, ModError* out_error);
|
||||
|
||||
typedef struct {
|
||||
uint32_t struct_size;
|
||||
const char* game_mode_id;
|
||||
const char* full_name;
|
||||
const char save_name[32]; // Empty uses default (gczelda2); max 31 chars
|
||||
void* user_data; // Pointer will be passed to all callbacks
|
||||
GameModeCallback on_activated; // Called when the game mode is selected
|
||||
GameModeCallback on_deactivated; // Called when the game mode is deselected
|
||||
GameModeCallback on_play; // Called when play is pressed
|
||||
GameModeCallback on_save_loaded; // Called whenever a save file is loaded
|
||||
GameModeCallback on_new_save; // Called when a new save is created
|
||||
GameModeNewSaveSelectCallback on_new_save_select;
|
||||
GameModeCallback on_game_reset; // Called when the game is reset
|
||||
GameModeCallback on_tick; // Called on every game tick while active
|
||||
} GameModeDesc;
|
||||
|
||||
#define GAME_MODE_DESC_INIT {sizeof(GameModeDesc)}
|
||||
|
||||
typedef struct GameModeService {
|
||||
ServiceHeader header;
|
||||
ModResult (*register_game_mode)(ModContext* ctx, const GameModeDesc* desc);
|
||||
ModResult (*unregister_game_mode)(ModContext* ctx, const char* id);
|
||||
ModResult (*is_active)(ModContext* ctx, const char* game_mode_id, bool* out_active);
|
||||
} GameModeService;
|
||||
|
||||
MOD_DECLARE_SERVICE(GameModeService, svc_game_mode, GAME_MODE_SERVICE_ID, GAME_MODE_SERVICE_MAJOR,
|
||||
GAME_MODE_SERVICE_MINOR);
|
||||
@@ -33,10 +33,68 @@
|
||||
|
||||
#define GFX_SERVICE_ID "dev.twilitrealm.dusklight.gfx"
|
||||
#define GFX_SERVICE_MAJOR 1u
|
||||
#define GFX_SERVICE_MINOR 1u
|
||||
#define GFX_SERVICE_MINOR 2u
|
||||
|
||||
/* Maximum size for push_draw payload */
|
||||
#define GFX_INLINE_DRAW_PAYLOAD_SIZE 128u
|
||||
#define GFX_MAX_COLOR_ATTACHMENTS 8u
|
||||
#define GFX_SCENE_COLOR_ATTACHMENT_INDEX 0u
|
||||
|
||||
typedef enum GfxAttachmentSemantic {
|
||||
GFX_ATTACHMENT_SCENE_COLOR,
|
||||
GFX_ATTACHMENT_NORMAL,
|
||||
GFX_ATTACHMENT_AUXILIARY,
|
||||
} GfxAttachmentSemantic;
|
||||
|
||||
typedef struct GfxColorAttachmentLayout {
|
||||
GfxAttachmentSemantic semantic;
|
||||
WGPUTextureFormat format;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
} GfxColorAttachmentLayout;
|
||||
|
||||
typedef struct GfxRenderTargetLayout {
|
||||
uint32_t struct_size;
|
||||
uint64_t key;
|
||||
/* At least one; scene color is at GFX_SCENE_COLOR_ATTACHMENT_INDEX. */
|
||||
uint32_t color_attachment_count;
|
||||
GfxColorAttachmentLayout color_attachments[GFX_MAX_COLOR_ATTACHMENTS];
|
||||
WGPUTextureFormat depth_stencil_format;
|
||||
uint32_t sample_count;
|
||||
} GfxRenderTargetLayout;
|
||||
|
||||
#define GFX_RENDER_TARGET_LAYOUT_INIT \
|
||||
{sizeof(GfxRenderTargetLayout), 0u, 0u, \
|
||||
{{GFX_ATTACHMENT_AUXILIARY, WGPUTextureFormat_Undefined}}, WGPUTextureFormat_Undefined, \
|
||||
1u}
|
||||
|
||||
/*
|
||||
* Initializes pipeline color targets for a render-target layout. Only scene color is writable;
|
||||
* callers that write another semantic should override that target afterward.
|
||||
*/
|
||||
static uint32_t gfx_init_color_target_states(const GfxRenderTargetLayout* layout,
|
||||
WGPUColorTargetState targets[GFX_MAX_COLOR_ATTACHMENTS], const WGPUBlendState* scene_blend,
|
||||
WGPUColorWriteMask scene_write_mask) {
|
||||
if (layout == NULL || targets == NULL) {
|
||||
return 0;
|
||||
}
|
||||
const uint32_t count = layout->color_attachment_count < GFX_MAX_COLOR_ATTACHMENTS ?
|
||||
layout->color_attachment_count :
|
||||
GFX_MAX_COLOR_ATTACHMENTS;
|
||||
for (uint32_t i = 0; i < GFX_MAX_COLOR_ATTACHMENTS; ++i) {
|
||||
WGPUColorTargetState target = WGPU_COLOR_TARGET_STATE_INIT;
|
||||
if (i < count) {
|
||||
target.format = layout->color_attachments[i].format;
|
||||
target.writeMask = WGPUColorWriteMask_None;
|
||||
}
|
||||
targets[i] = target;
|
||||
}
|
||||
if (count != 0) {
|
||||
targets[GFX_SCENE_COLOR_ATTACHMENT_INDEX].blend = scene_blend;
|
||||
targets[GFX_SCENE_COLOR_ATTACHMENT_INDEX].writeMask = scene_write_mask;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* 0 is never a valid handle. */
|
||||
typedef uint64_t GfxDrawTypeHandle;
|
||||
@@ -51,8 +109,8 @@ typedef struct GfxRange {
|
||||
} GfxRange;
|
||||
|
||||
/*
|
||||
* Device and scene pass configuration. Valid from mod_initialize onward and stable for the
|
||||
* session. Offscreen passes from create_pass are always single-sample.
|
||||
* Device and legacy primary scene-pass configuration. Use get_scene_target_layout when creating
|
||||
* scene pipelines. Offscreen passes from create_pass are always single-sample.
|
||||
*/
|
||||
typedef struct GfxDeviceInfo {
|
||||
uint32_t struct_size;
|
||||
@@ -83,12 +141,18 @@ typedef struct GfxDrawContext {
|
||||
WGPUBuffer index_buffer;
|
||||
WGPUBuffer uniform_buffer;
|
||||
WGPUBuffer storage_buffer;
|
||||
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].format */
|
||||
WGPUTextureFormat color_format;
|
||||
/* deprecated: use layout.depth_stencil_format */
|
||||
WGPUTextureFormat depth_format;
|
||||
/* deprecated: use layout.sample_count */
|
||||
uint32_t sample_count;
|
||||
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].width */
|
||||
uint32_t target_width;
|
||||
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].height */
|
||||
uint32_t target_height;
|
||||
bool uses_reversed_z;
|
||||
GfxRenderTargetLayout layout; /* added in GfxService 1.2 */
|
||||
} GfxDrawContext;
|
||||
|
||||
typedef void (*GfxDrawFn)(ModContext* ctx, const GfxDrawContext* draw_ctx, const void* payload,
|
||||
@@ -269,6 +333,10 @@ typedef struct GfxService {
|
||||
*/
|
||||
ModResult (*push_present)(
|
||||
ModContext* ctx, GfxPresentTargetHandle handle, const void* payload, size_t payload_size);
|
||||
|
||||
/* Minor version 2 */
|
||||
|
||||
ModResult (*get_scene_target_layout)(ModContext* ctx, GfxRenderTargetLayout* out_layout);
|
||||
} GfxService;
|
||||
|
||||
MOD_DECLARE_SERVICE(GfxService, svc_gfx, GFX_SERVICE_ID, GFX_SERVICE_MAJOR, GFX_SERVICE_MINOR);
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <mods/service.hpp>
|
||||
#endif
|
||||
|
||||
#define ITEM_SERVICE_ID "dev.twilitrealm.dusklight.item"
|
||||
#define ITEM_SERVICE_MAJOR 2u
|
||||
#define ITEM_SERVICE_MINOR 0u
|
||||
|
||||
/* 0 is never a valid handle. */
|
||||
typedef uint64_t ItemCheckHandle;
|
||||
typedef uint64_t ItemGiveHandle;
|
||||
|
||||
/*
|
||||
* Item check resolution and inventory grants.
|
||||
*
|
||||
* Check names are case-sensitive. Resolvers must be free of side effects because the game may
|
||||
* resolve a check more than once, including once for display and again when granting the item.
|
||||
* Registrations and pending grants are removed when the calling mod is detached. Callbacks run
|
||||
* on the game thread.
|
||||
*/
|
||||
|
||||
/* Host-owned callback data, valid only for the duration of the callback. */
|
||||
typedef struct ItemCheckInfo {
|
||||
const char* name;
|
||||
const void* giver_actor; /* fopAc_ac_c*, or NULL when unavailable */
|
||||
uint8_t vanilla_item;
|
||||
uint8_t current_item;
|
||||
} ItemCheckInfo;
|
||||
|
||||
/* Return true and write out_item to replace current_item, or false to leave it unchanged. */
|
||||
typedef bool (*ItemCheckResolveFn)(
|
||||
ModContext* ctx, const ItemCheckInfo* info, uint8_t* out_item, void* user_data);
|
||||
|
||||
typedef enum ItemGiveOrigin {
|
||||
ITEM_GIVE_ORIGIN_GAME = 0,
|
||||
ITEM_GIVE_ORIGIN_QUEUE = 1,
|
||||
ITEM_GIVE_ORIGIN_QUEUE_SILENT = 2,
|
||||
} ItemGiveOrigin;
|
||||
|
||||
/* Host-owned callback data, valid only for the duration of the callback. */
|
||||
typedef struct ItemGiveInfo {
|
||||
const char* check_name; /* NULL when the grant is not attributed to a check */
|
||||
const void* giver_actor; /* fopAc_ac_c*, or NULL when unavailable */
|
||||
uint8_t item;
|
||||
uint8_t origin; /* ItemGiveOrigin */
|
||||
} ItemGiveInfo;
|
||||
|
||||
typedef void (*ItemGiveObserveFn)(ModContext* ctx, const ItemGiveInfo* info, void* user_data);
|
||||
|
||||
enum {
|
||||
/* Apply the inventory change without a get-item demo. */
|
||||
ITEM_GIVE_SILENT = 1u << 0,
|
||||
/* Resolve item_no as the named check's vanilla item when the queue entry is dispatched. */
|
||||
ITEM_GIVE_RESOLVE = 1u << 1,
|
||||
};
|
||||
|
||||
typedef struct ItemService {
|
||||
ServiceHeader header;
|
||||
|
||||
/* Set or replace the calling mod's fixed override for name. */
|
||||
ModResult (*set_check_override)(ModContext* ctx, const char* name, uint8_t item_no);
|
||||
|
||||
ModResult (*clear_check_override)(ModContext* ctx, const char* name);
|
||||
|
||||
/* Register for name, or for every check when name is NULL. out_handle may be NULL. */
|
||||
ModResult (*set_check_resolver)(ModContext* ctx, const char* name, ItemCheckResolveFn fn,
|
||||
void* user_data, ItemCheckHandle* out_handle);
|
||||
|
||||
ModResult (*clear_check_resolver)(ModContext* ctx, ItemCheckHandle handle);
|
||||
|
||||
/* Resolve without granting an item or notifying give observers. */
|
||||
ModResult (*resolve_check)(
|
||||
ModContext* ctx, const char* name, uint8_t vanilla_item, uint8_t* out_item);
|
||||
|
||||
/*
|
||||
* Add a grant to the global FIFO. check_name may be NULL unless ITEM_GIVE_RESOLVE is set.
|
||||
* Returns MOD_UNAVAILABLE when the queue is full. Entries wait until gameplay is in a safe
|
||||
* state and are cleared when the active save slot changes.
|
||||
*/
|
||||
ModResult (*give_item)(
|
||||
ModContext* ctx, const char* check_name, uint8_t item_no, uint32_t flags);
|
||||
|
||||
/* Observe inventory grants. out_handle may be NULL. */
|
||||
ModResult (*observe_gives)(
|
||||
ModContext* ctx, ItemGiveObserveFn fn, void* user_data, ItemGiveHandle* out_handle);
|
||||
|
||||
ModResult (*unobserve_gives)(ModContext* ctx, ItemGiveHandle handle);
|
||||
} ItemService;
|
||||
|
||||
MOD_DECLARE_SERVICE(ItemService, svc_item, ITEM_SERVICE_ID, ITEM_SERVICE_MAJOR, ITEM_SERVICE_MINOR);
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <mods/service.hpp>
|
||||
#endif
|
||||
|
||||
#define MESSAGE_SERVICE_ID "dev.twilitrealm.dusklight.message"
|
||||
#define MESSAGE_SERVICE_MAJOR 1u
|
||||
#define MESSAGE_SERVICE_MINOR 0u
|
||||
|
||||
typedef uint16_t MessageId;
|
||||
typedef uint64_t MessageHandle;
|
||||
typedef uint64_t MessageOverrideHandle;
|
||||
|
||||
/* INF1 entry data */
|
||||
typedef struct MessageEntryData {
|
||||
uint8_t bytes[20];
|
||||
} MessageEntryData;
|
||||
static_assert(sizeof(MessageEntryData) == 20);
|
||||
|
||||
/* INF1 box kind (offset 0x09): message screen class */
|
||||
typedef enum MessageBoxKind {
|
||||
MESSAGE_BOX_TALK = 0, /* ordinary dialogue box */
|
||||
MESSAGE_BOX_DEMO_CAPTION = 1, /* boxless cutscene caption */
|
||||
MESSAGE_BOX_SIGN = 2, /* signs and posted notices */
|
||||
MESSAGE_BOX_PLAIN = 5, /* boxless system text */
|
||||
MESSAGE_BOX_KANBAN = 6, /* signboard screen class; unused */
|
||||
MESSAGE_BOX_STAFF_ROLL = 7,
|
||||
MESSAGE_BOX_LIGHT_SPIRIT = 8, /* spirit text window and glow */
|
||||
MESSAGE_BOX_ITEM_GET = 9, /* centered item-get box */
|
||||
MESSAGE_BOX_ITEM_NAME = 11, /* UI string fetch, no box */
|
||||
MESSAGE_BOX_PLACE_NAME = 12, /* area intro banner */
|
||||
MESSAGE_BOX_MIDNA = 13, /* Midna dialogue colors and glow */
|
||||
MESSAGE_BOX_ANIMAL = 14, /* wolf-form animal speech glow */
|
||||
MESSAGE_BOX_NOTICE = 15, /* floating gameplay notice; can't be used during dialogue */
|
||||
MESSAGE_BOX_SAVE = 16, /* save and memory card prompts */
|
||||
MESSAGE_BOX_HOWL = 17, /* howling stone UI */
|
||||
MESSAGE_BOX_BOSS_NAME = 19, /* boss intro banner */
|
||||
} MessageBoxKind;
|
||||
|
||||
/* INF1 draw type (offset 0x0A): text pacing */
|
||||
typedef enum MessageDrawType {
|
||||
MESSAGE_DRAW_TYPED = 0, /* types per-character; A skips typing */
|
||||
MESSAGE_DRAW_INSTANT = 1, /* whole page at once (menus, prompts) */
|
||||
MESSAGE_DRAW_TYPED_NO_SKIP = 2, /* types per-character; A does not skip */
|
||||
MESSAGE_DRAW_FADE = 3, /* page fades in */
|
||||
MESSAGE_DRAW_UI_NAME = 4, /* UI string fetch (item names), no box pacing */
|
||||
MESSAGE_DRAW_TYPED_SLOW = 5, /* weighted slow typing (light spirit speech) */
|
||||
MESSAGE_DRAW_UI_ACTION = 7, /* UI string fetch (action button labels) */
|
||||
MESSAGE_DRAW_FADE_SLOW = 9, /* slow page fade (staff credits) */
|
||||
} MessageDrawType;
|
||||
|
||||
/* INF1 box position (offset 0x0B) */
|
||||
typedef enum MessageBoxPosition {
|
||||
MESSAGE_POSITION_BOTTOM = 0,
|
||||
MESSAGE_POSITION_TOP = 1,
|
||||
MESSAGE_POSITION_MIDDLE = 2,
|
||||
MESSAGE_POSITION_AUTO = 3, /* top or bottom, avoiding the speaker on screen */
|
||||
} MessageBoxPosition;
|
||||
|
||||
/* Applies to both the text and gradient colors. */
|
||||
typedef enum MessageTextColor {
|
||||
MESSAGE_COLOR_DEFAULT = 0, /* box default */
|
||||
MESSAGE_COLOR_RED = 1, /* 0xF07878 */
|
||||
MESSAGE_COLOR_GREEN = 2, /* 0xAADC8C */
|
||||
MESSAGE_COLOR_BLUE = 3, /* 0xA0B4DC */
|
||||
MESSAGE_COLOR_YELLOW = 4, /* 0xDCDC82 */
|
||||
MESSAGE_COLOR_SKY = 5, /* 0xB4C8E6 */
|
||||
MESSAGE_COLOR_PURPLE = 6, /* 0xC8A0DC */
|
||||
MESSAGE_COLOR_WHITE = 7, /* 0xFFFFFF */
|
||||
MESSAGE_COLOR_ORANGE = 8, /* 0xDCAA78 */
|
||||
} MessageTextColor;
|
||||
|
||||
/* Message text language. 5 (Dutch) is unused. */
|
||||
typedef enum MessageLanguage {
|
||||
MESSAGE_LANGUAGE_ENGLISH = 0,
|
||||
MESSAGE_LANGUAGE_GERMAN = 1,
|
||||
MESSAGE_LANGUAGE_FRENCH = 2,
|
||||
MESSAGE_LANGUAGE_SPANISH = 3,
|
||||
MESSAGE_LANGUAGE_ITALIAN = 4,
|
||||
MESSAGE_LANGUAGE_JAPANESE = 6,
|
||||
} MessageLanguage;
|
||||
|
||||
typedef struct MessageVariantData {
|
||||
uint8_t language; /* MessageLanguage */
|
||||
MessageEntryData entry;
|
||||
const uint8_t* text;
|
||||
size_t text_size;
|
||||
} MessageVariantData;
|
||||
|
||||
typedef struct MessageTextData {
|
||||
const uint8_t* text;
|
||||
size_t text_size;
|
||||
} MessageTextData;
|
||||
|
||||
typedef struct MessageOverrideContext {
|
||||
uint16_t group;
|
||||
uint16_t message_id;
|
||||
uint8_t language; /* MessageLanguage */
|
||||
const uint8_t* original_text;
|
||||
size_t original_text_size;
|
||||
} MessageOverrideContext;
|
||||
|
||||
/* Return true with a valid out_text to override, or false to try the next registration. */
|
||||
typedef bool (*MessageOverrideFn)(ModContext* ctx, const MessageOverrideContext* message,
|
||||
MessageTextData* out_text, void* user_data);
|
||||
|
||||
typedef struct MessageService {
|
||||
ServiceHeader header;
|
||||
|
||||
ModResult (*override_message)(ModContext* ctx, uint16_t group, uint16_t message_id,
|
||||
uint8_t language, const uint8_t* text, size_t text_size, MessageOverrideHandle* out_handle);
|
||||
ModResult (*override_message_fn)(ModContext* ctx, uint16_t group, uint16_t message_id,
|
||||
uint8_t language, MessageOverrideFn fn, void* user_data, MessageOverrideHandle* out_handle);
|
||||
ModResult (*remove_override)(ModContext* ctx, MessageOverrideHandle handle);
|
||||
|
||||
ModResult (*register_message)(ModContext* ctx, uint16_t group,
|
||||
const MessageVariantData* variants, size_t variant_count, MessageId* out_id,
|
||||
MessageHandle* out_handle);
|
||||
ModResult (*remove_message)(ModContext* ctx, MessageHandle handle);
|
||||
} MessageService;
|
||||
|
||||
MOD_DECLARE_SERVICE(
|
||||
MessageService, svc_message, MESSAGE_SERVICE_ID, MESSAGE_SERVICE_MAJOR, MESSAGE_SERVICE_MINOR);
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui"
|
||||
#define UI_SERVICE_MAJOR 1u
|
||||
#define UI_SERVICE_MINOR 1u
|
||||
#define UI_SERVICE_MINOR 2u
|
||||
|
||||
/*
|
||||
* UI primitives: a panel inside the host Mods window, mod-owned windows, dialogs, toasts,
|
||||
@@ -291,6 +291,10 @@ typedef struct UiService {
|
||||
|
||||
/* Enqueue a toast notification. */
|
||||
ModResult (*push_toast)(ModContext* ctx, const UiToastDesc* desc);
|
||||
|
||||
/* Minor version 2 */
|
||||
ModResult (*get_clipboard_text)(ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength);
|
||||
ModResult (*set_clipboard_text)(ModContext* ctx, const char* text);
|
||||
} UiService;
|
||||
|
||||
MOD_DECLARE_SERVICE(UiService, svc_ui, UI_SERVICE_ID, UI_SERVICE_MAJOR, UI_SERVICE_MINOR);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/svc/ui.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace mods::ui {
|
||||
|
||||
inline ModResult get_clipboard_text(std::string& outText) {
|
||||
outText.clear();
|
||||
|
||||
size_t textLength = 0;
|
||||
auto result = svc_ui->get_clipboard_text(mod_ctx, nullptr, 0, &textLength);
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
std::string buffer(textLength + 1, '\0');
|
||||
size_t actualLength = 0;
|
||||
|
||||
result = svc_ui->get_clipboard_text(
|
||||
mod_ctx, buffer.data(), buffer.size(), &actualLength);
|
||||
|
||||
if (result == MOD_OK) {
|
||||
buffer.resize(actualLength);
|
||||
outText = std::move(buffer);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
// Retry if the clipboard grew between the two calls.
|
||||
if (result != MOD_INVALID_ARGUMENT || actualLength <= textLength) {
|
||||
return result;
|
||||
}
|
||||
|
||||
textLength = actualLength;
|
||||
}
|
||||
|
||||
return MOD_ERROR;
|
||||
}
|
||||
|
||||
inline ModResult set_clipboard_text(const std::string& text) {
|
||||
return svc_ui->set_clipboard_text(mod_ctx, text.c_str());
|
||||
}
|
||||
|
||||
} // namespace mods::ui
|
||||
@@ -11,7 +11,10 @@
|
||||
#include "Z2AudioLib/SpotName.h"
|
||||
#include "os_report.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/audio.h"
|
||||
#include "dusk/version.hpp"
|
||||
#endif
|
||||
|
||||
Z2SeqMgr::Z2SeqMgr() : JASGlobalInstance<Z2SeqMgr>(true) {
|
||||
mMainBgmMaster.forceIn();
|
||||
@@ -584,7 +587,7 @@ void Z2SeqMgr::bgmStreamPlay() {
|
||||
}
|
||||
|
||||
#if !PLATFORM_SHIELD
|
||||
else if (getStreamBgmID() == 0x2000000) {
|
||||
else if (getStreamBgmID() == 0x2000000 IF_DUSK(&& dusk::version::isGcn())) {
|
||||
if (mStreamBgmHandle) {
|
||||
mStreamBgmHandle->stop();
|
||||
}
|
||||
|
||||
@@ -551,7 +551,7 @@ void daAlink_c::setBowSight() {
|
||||
getArrowFlyData(&dist, &speed, TRUE);
|
||||
checkSightLine(dist, &sight_pos);
|
||||
mSight.setPos(&sight_pos);
|
||||
mSight.offDrawFlg();
|
||||
DUSK_IF_ELSE(dusk::getSettings().game.aimingReticle ? mSight.onDrawFlg() : mSight.offDrawFlg(), mSight.offDrawFlg());
|
||||
} else {
|
||||
mSight.offDrawFlg();
|
||||
}
|
||||
|
||||
@@ -23,10 +23,13 @@
|
||||
#include "d/actor/d_a_npc_tkc.h"
|
||||
#include <cstring>
|
||||
|
||||
#ifdef TARGET_PC
|
||||
#include "dusk/game_mode.hpp"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/version.hpp"
|
||||
#endif
|
||||
|
||||
BOOL daAlink_c::checkEventRun() const {
|
||||
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
|
||||
@@ -2260,6 +2263,12 @@ int daAlink_c::procCoGetItemInit() {
|
||||
s16 var_r22 = 0;
|
||||
BOOL var_r31 = FALSE;
|
||||
BOOL var_r30 = FALSE;
|
||||
#if TARGET_PC
|
||||
// Queued grants have no event partner, so create the demo item from the event's GtItm.
|
||||
if (dusk::mods::item_give_queue_dispatching()) {
|
||||
mDemo.setParam0(0x100);
|
||||
}
|
||||
#endif
|
||||
if (mProcID == PROC_GET_ITEM || mProcID == PROC_INSECT_CATCH ||
|
||||
(mProcID == PROC_PREACTION_UNEQUIP && !checkNoUpperAnime()))
|
||||
{
|
||||
@@ -2299,7 +2308,7 @@ int daAlink_c::procCoGetItemInit() {
|
||||
}
|
||||
|
||||
fpc_ProcID item_partner_id = fopAcM_createItemForPresentDemo(¤t.pos, item_no, 0, -1,
|
||||
fopAcM_GetRoomNo(this), NULL, NULL);
|
||||
fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(dusk::mods::item_give_queue_take_tag()));
|
||||
if (item_partner_id != fpcM_ERROR_PROCESS_ID_e) {
|
||||
dComIfGp_event_setItemPartnerId(item_partner_id);
|
||||
}
|
||||
@@ -4010,9 +4019,9 @@ int daAlink_c::procGanonFinishInit() {
|
||||
onEndResetFlg1(ERFLG1_SHIELD_BACKBONE);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.speedrunMode) {
|
||||
if (dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::m_speedrunInfo.stopRun();
|
||||
if (dusk::speedrun::isActive()) {
|
||||
if (dusk::speedrun::g_speedrunInfo.m_isRunStarted) {
|
||||
dusk::speedrun::g_speedrunInfo.stopRun();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -4084,6 +4084,10 @@ void daB_DS_c::executeBattle2Dead() {
|
||||
camera->mCamera.SetTrimSize(0);
|
||||
dComIfGp_event_reset();
|
||||
dComIfGs_onStageBossEnemy(0x13);
|
||||
#if TARGET_PC
|
||||
// This reward has no original grant at this point in the cutscene.
|
||||
dusk::mods::item_check_enqueue("Arbiters Grounds Dungeon Reward", dItemNo_NONE_e);
|
||||
#endif
|
||||
/* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */
|
||||
dComIfGs_onEventBit(0x2010);
|
||||
fopAcM_delete(this);
|
||||
|
||||
@@ -170,7 +170,7 @@ void daDitem_c::actionEvent() {
|
||||
|
||||
if (chkDead()) {
|
||||
if (!chkArgFlag(0x1)) {
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
}
|
||||
|
||||
if (m_itemNo == dItemNo_KAKERA_HEART_e) {
|
||||
|
||||
@@ -726,11 +726,18 @@ void daE_HP_c::executeDead() {
|
||||
fopAcM_onSwitch(this, bitSw);
|
||||
}
|
||||
|
||||
dComIfGs_addPohSpiritNum();
|
||||
#if TARGET_PC
|
||||
mItemCheckOverridden =
|
||||
dusk::mods::item_check_poe(bitSw, dItemNo_POU_SPIRIT_e, this) != dItemNo_POU_SPIRIT_e;
|
||||
if (mItemCheckOverridden) {
|
||||
dusk::mods::item_check_enqueue_poe(bitSw, dItemNo_POU_SPIRIT_e);
|
||||
} else
|
||||
#endif
|
||||
dComIfGs_addPohSpiritNum();
|
||||
|
||||
field_0x784 = -1;
|
||||
|
||||
if (dComIfGs_getPohSpiritNum() == 20) {
|
||||
if (dComIfGs_getPohSpiritNum() == 20 IF_DUSK(&&!mItemCheckOverridden)) {
|
||||
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[0x1c9]);
|
||||
}
|
||||
|
||||
@@ -752,13 +759,13 @@ void daE_HP_c::executeDead() {
|
||||
field_0x788 = 1;
|
||||
}
|
||||
}
|
||||
} else if (field_0x788 != 0) {
|
||||
} else if (field_0x788 != 0 IF_DUSK(|| mItemCheckOverridden)) {
|
||||
fopAcM_createDisappear(this, ¤t.pos, 8, 3, 0xff);
|
||||
fopAcM_delete(this);
|
||||
} else {
|
||||
if (field_0x784 == -1) {
|
||||
field_0x784 = fopAcM_createItemForPresentDemo(¤t.pos, dItemNo_POU_SPIRIT_e, 0, -1,
|
||||
-1, 0, 0);
|
||||
field_0x784 = fopAcM_createItemForPresentDemo(¤t.pos, dItemNo_POU_SPIRIT_e, 0,
|
||||
-1, -1, 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag_poe(bitSw)));
|
||||
}
|
||||
|
||||
if (fopAcM_IsExecuting(field_0x784) != FALSE) {
|
||||
|
||||
@@ -1132,12 +1132,20 @@ static void e_po_dead(e_po_class* i_this) {
|
||||
camera_player->mCamera.Start();
|
||||
camera_player->mCamera.SetTrimSize(0);
|
||||
dComIfGp_event_reset();
|
||||
dComIfGs_addPohSpiritNum();
|
||||
#if TARGET_PC
|
||||
if (dusk::mods::item_check_poe(i_this->BitSW, dItemNo_POU_SPIRIT_e, a_this) ==
|
||||
dItemNo_POU_SPIRIT_e)
|
||||
{
|
||||
#endif
|
||||
dComIfGs_addPohSpiritNum();
|
||||
#if !PLATFORM_SHIELD
|
||||
if (dComIfGs_getPohSpiritNum() == 0x14) {
|
||||
/* dSv_event_flag_c::F_0457 - Castle Town - Revived cat */
|
||||
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[457]);
|
||||
}
|
||||
#endif
|
||||
#if TARGET_PC
|
||||
}
|
||||
#endif
|
||||
daPy_getPlayerActorClass()->cancelOriginalDemo();
|
||||
} else if (mArg0Check(i_this, 0) != 0) {
|
||||
@@ -1265,8 +1273,15 @@ static void e_po_dead(e_po_class* i_this) {
|
||||
}
|
||||
} else {
|
||||
if (i_this->field_0x75C == -1) {
|
||||
#if TARGET_PC
|
||||
const u8 itemNo =
|
||||
dusk::mods::item_check_poe(i_this->BitSW, dItemNo_POU_SPIRIT_e, a_this);
|
||||
i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, itemNo,
|
||||
0, -1, -1, NULL, NULL, dusk::mods::item_give_tag_poe(i_this->BitSW));
|
||||
#else
|
||||
i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, 0xE0, 0,
|
||||
-1, -1, NULL, NULL);
|
||||
#endif
|
||||
}
|
||||
if (fopAcM_IsExecuting(i_this->field_0x75C)) {
|
||||
i_this->field_0x762 =
|
||||
|
||||
@@ -1250,7 +1250,9 @@ static void demo_camera(e_rdb_class* i_this) {
|
||||
}
|
||||
|
||||
if (iVar1 != 0) {
|
||||
daPy_getPlayerActorClass()->changeDemoMode(11, 32, 0, 0);
|
||||
daPy_getPlayerActorClass()->changeDemoMode(11,
|
||||
DUSK_ITEM_CHECK_EXPR("bulblin_key:D_MN09", dItemNo_SMALL_KEY_e, &i_this->enemy), 0,
|
||||
0);
|
||||
i_this->mDemoMode = 12;
|
||||
i_this->field_0x10aa = 0;
|
||||
i_this->field_0xfe5 = 1;
|
||||
|
||||
@@ -931,7 +931,9 @@ static void get_demo(e_th_ball_class* i_this) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
demo_id = fopAcM_createItemForTrBoxDemo(&i_this->current.pos, dItemNo_IRONBALL_e, -1, fopAcM_GetRoomNo(i_this), NULL, NULL);
|
||||
demo_id = fopAcM_createItemForTrBoxDemo(&i_this->current.pos,
|
||||
DUSK_ITEM_CHECK_EXPR("ball_and_chain:D_MN11", dItemNo_IRONBALL_e, i_this), -1,
|
||||
fopAcM_GetRoomNo(i_this), NULL, NULL DUSK_GIVE_TAG("ball_and_chain:D_MN11"));
|
||||
JUT_ASSERT(1670, demo_id != fpcM_ERROR_PROCESS_ID_e);
|
||||
i_this->mDemoMode = 2;
|
||||
break;
|
||||
|
||||
+74
-18
@@ -15,12 +15,50 @@
|
||||
#include "dusk/dvd_asset.hpp"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
|
||||
using GameVersion = dusk::version::GameVersion;
|
||||
#include <type_traits>
|
||||
|
||||
using namespace dusk::version;
|
||||
|
||||
#define MANT_REL_PATH platformSelect<const char*>("/rel/Final/Release/d_a_mant.rel", "/rel/Rfinal/Release/d_a_mant.rel")
|
||||
|
||||
#define DEFINE_MANT_ASSET(name, type, count, ...) \
|
||||
static type* name##_get() { \
|
||||
alignas(32) static std::remove_cv_t<type> buf[count]; \
|
||||
static bool _ = (dusk::LoadRelAsset(buf, MANT_REL_PATH, __VA_ARGS__), true); \
|
||||
return buf; \
|
||||
}
|
||||
|
||||
// keep the original version of the cape texture const so we don't need to reload the file
|
||||
static u8 const * l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x1C00}, {GameVersion::GcnPal, 0x1C00}, {GameVersion::GcnJpn, 0x1C00}}, 0x4000), true); return buf; }
|
||||
static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x5C00}, {GameVersion::GcnPal, 0x5C00}, {GameVersion::GcnJpn, 0x5C00}}, 0x4000), true); return buf; }
|
||||
static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x9C00}, {GameVersion::GcnPal, 0x9C00}, {GameVersion::GcnJpn, 0x9C00}}, 0x60), true); return buf; }
|
||||
DEFINE_MANT_ASSET(l_Egnd_mantTEX, u8 const, 0x4000, {
|
||||
{GameVersion::GcnUsa, 0x1C00},
|
||||
{GameVersion::GcnPal, 0x1C00},
|
||||
{GameVersion::GcnJpn, 0x1C00},
|
||||
{GameVersion::WiiUsaRev0, 0x1B00},
|
||||
{GameVersion::WiiUsa, 0x1900},
|
||||
{GameVersion::WiiPal, 0x1900},
|
||||
{GameVersion::WiiJpn, 0x1900},
|
||||
});
|
||||
|
||||
DEFINE_MANT_ASSET(l_Egnd_mantTEX_U, u8, 0x4000, {
|
||||
{GameVersion::GcnUsa, 0x5C00},
|
||||
{GameVersion::GcnPal, 0x5C00},
|
||||
{GameVersion::GcnJpn, 0x5C00},
|
||||
{GameVersion::WiiUsaRev0, 0x5B00},
|
||||
{GameVersion::WiiUsa, 0x5900},
|
||||
{GameVersion::WiiPal, 0x5900},
|
||||
{GameVersion::WiiJpn, 0x5900},
|
||||
});
|
||||
|
||||
DEFINE_MANT_ASSET(l_Egnd_mantPAL, u8, 0x60, {
|
||||
{GameVersion::GcnUsa, 0x9C00},
|
||||
{GameVersion::GcnPal, 0x9C00},
|
||||
{GameVersion::GcnJpn, 0x9C00},
|
||||
{GameVersion::WiiUsaRev0, 0x9B00},
|
||||
{GameVersion::WiiUsa, 0x9900},
|
||||
{GameVersion::WiiPal, 0x9900},
|
||||
{GameVersion::WiiJpn, 0x9900},
|
||||
});
|
||||
|
||||
#define l_Egnd_mantTEX (l_Egnd_mantTEX_get())
|
||||
#define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get())
|
||||
#define l_Egnd_mantPAL (l_Egnd_mantPAL_get())
|
||||
@@ -35,14 +73,39 @@ static TGXTexObj mainTexObj;
|
||||
static TGXTexObj undersideTexObj;
|
||||
|
||||
// l_pos is unused
|
||||
//static f32* l_pos_get() { alignas(32) static f32 buf[507]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA44C}, {GameVersion::GcnPal, 0xA44C}}, sizeof(buf)), true); return buf; }
|
||||
static f32* l_normal_get() { alignas(32) static f32 buf[3]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x9C60}, {GameVersion::GcnPal, 0x9C60}, {GameVersion::GcnJpn, 0x9C60}}, sizeof(buf)), true); return buf; }
|
||||
static f32* l_texCoord_get() { alignas(32) static f32 buf[338]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA458}, {GameVersion::GcnPal, 0xA458}, {GameVersion::GcnJpn, 0xA458}}, sizeof(buf)), true); return buf; }
|
||||
//#define l_pos (l_pos_get())
|
||||
#define l_normal (l_normal_get())
|
||||
// DEFINE_MANT_ASSET(l_pos, f32, 507, {
|
||||
// {GameVersion::GcnUsa, 0x9C60},
|
||||
// {GameVersion::GcnPal, 0x9C60},
|
||||
// });
|
||||
|
||||
alignas(32) static f32 const l_normal[3] = {0.0f, 1.0f, 0.0f};
|
||||
|
||||
DEFINE_MANT_ASSET(l_texCoord, f32, 338, {
|
||||
{GameVersion::GcnUsa, 0xA458},
|
||||
{GameVersion::GcnPal, 0xA458},
|
||||
{GameVersion::GcnJpn, 0xA458},
|
||||
{GameVersion::WiiUsaRev0, 0x9B60},
|
||||
{GameVersion::WiiUsa, 0x9960},
|
||||
{GameVersion::WiiPal, 0x9960},
|
||||
{GameVersion::WiiJpn, 0x9960},
|
||||
});
|
||||
|
||||
// #define l_pos (l_pos_get())
|
||||
#define l_texCoord (l_texCoord_get())
|
||||
|
||||
static bool l_Egnd_mantTEX_hasReplacement = false;
|
||||
|
||||
DEFINE_MANT_ASSET(l_Egnd_mantDL, u8, 0x3EC, {
|
||||
{GameVersion::GcnUsa, 0xA9A0},
|
||||
{GameVersion::GcnPal, 0xA9A0},
|
||||
{GameVersion::GcnJpn, 0xA9A0},
|
||||
{GameVersion::WiiUsaRev0, 0xA0C0},
|
||||
{GameVersion::WiiUsa, 0x9EC0},
|
||||
{GameVersion::WiiPal, 0x9EC0},
|
||||
{GameVersion::WiiJpn, 0x9EC0},
|
||||
});
|
||||
|
||||
#define l_Egnd_mantDL (l_Egnd_mantDL_get())
|
||||
#else
|
||||
#include "assets/l_Egnd_mantTEX.h"
|
||||
|
||||
@@ -52,16 +115,9 @@ static bool l_Egnd_mantTEX_hasReplacement = false;
|
||||
#endif
|
||||
#include "d/d_s_play.h"
|
||||
|
||||
#if TARGET_PC
|
||||
using GameVersion = dusk::version::GameVersion;
|
||||
|
||||
static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA9A0}, {GameVersion::GcnPal, 0xA9A0}, {GameVersion::GcnJpn, 0xA9A0}}, 0x3EC), true); return buf; }
|
||||
#define l_Egnd_mantDL (l_Egnd_mantDL_get())
|
||||
#else
|
||||
#include "assets/l_Egnd_mantDL.h"
|
||||
#endif
|
||||
|
||||
#if !TARGET_PC
|
||||
#include "assets/l_Egnd_mantDL.h"
|
||||
|
||||
static void* pal_d = (void*)&l_Egnd_mantPAL;
|
||||
|
||||
static void* tex_d[2] = {
|
||||
|
||||
@@ -5874,8 +5874,8 @@ static int dmg_rod_Execute(dmg_rod_class* i_this) {
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.buttonFishing) {
|
||||
if ((item_any_fishing_rod(dComIfGp_getSelectItem(0)) && mDoCPd_c::getHoldX(PAD_1)) ||
|
||||
(item_any_fishing_rod(dComIfGp_getSelectItem(1)) && mDoCPd_c::getHoldY(PAD_1)))
|
||||
{
|
||||
(item_any_fishing_rod(dComIfGp_getSelectItem(1)) && mDoCPd_c::getHoldY(PAD_1)) ||
|
||||
(i_this->action == ACTION_LURE_STANDBY && mDoCPd_c::getTrigB(PAD_1))) {
|
||||
i_this->rod_stick_y = -1.0f;
|
||||
i_this->rod_substick_y = -1.0f;
|
||||
}
|
||||
|
||||
@@ -1744,7 +1744,9 @@ int daNpc_Aru_c::cutSpeakTo(int i_staffID) {
|
||||
switch (eventId) {
|
||||
case 1:
|
||||
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("goats_reward", itemNo, this);
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, itemNo,
|
||||
0, -1, -1, NULL, NULL DUSK_GIVE_TAG("goats_reward"));
|
||||
}
|
||||
|
||||
if (fopAcM_IsExecuting(mItemPartnerId)) {
|
||||
|
||||
@@ -1001,8 +1001,9 @@ BOOL daNpcAshB_c::EvCut_Appear(int i_staffID) {
|
||||
case '0008':
|
||||
local_30[0] = 0;
|
||||
if (mFlow.getEventId(local_30) == 1) {
|
||||
mItemPartnerId =
|
||||
fopAcM_createItemForPresentDemo(¤t.pos, local_30[0], 0, -1, -1, 0, 0);
|
||||
DUSK_ITEM_CHECK("ashei_sketch", local_30[0], this);
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(
|
||||
¤t.pos, local_30[0], 0, -1, -1, 0, 0 DUSK_GIVE_TAG("ashei_sketch"));
|
||||
dComIfGp_event_setItemPartnerId(mItemPartnerId);
|
||||
mItemPartnerId = -1;
|
||||
}
|
||||
|
||||
@@ -1804,8 +1804,19 @@ int daNpcChin_c::_Evt_GameSucceed_CutMain(const int& param_0) {
|
||||
itemId1 = 0;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
const char* itemCheckName = nullptr;
|
||||
if (itemId1 == dItemNo_ARROW_LV2_e) {
|
||||
itemCheckName = "star_reward_1";
|
||||
} else if (itemId1 == dItemNo_ARROW_LV3_e) {
|
||||
itemCheckName = "star_reward_2";
|
||||
}
|
||||
if (itemCheckName != nullptr) {
|
||||
itemId1 = dusk::mods::item_check(itemCheckName, itemId1, this);
|
||||
}
|
||||
#endif
|
||||
fpc_ProcID itemId2 = fopAcM_createItemForPresentDemo(¤t.pos, itemId1, 0, -1, -1,
|
||||
0, 0);
|
||||
0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
|
||||
if (itemId2 != -1) {
|
||||
dComIfGp_event_setItemPartnerId(itemId2);
|
||||
}
|
||||
|
||||
@@ -1333,7 +1333,9 @@ void daNpc_Fairy_c::PresentDemoCall() {
|
||||
item_no = 0;
|
||||
}
|
||||
|
||||
fpc_ProcID id = fopAcM_createItemForPresentDemo(¤t.pos, item_no, 0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("fairy_reward:D_SB01", item_no, this);
|
||||
fpc_ProcID id = fopAcM_createItemForPresentDemo(¤t.pos, item_no, 0, -1, -1, NULL,
|
||||
NULL DUSK_GIVE_TAG("fairy_reward:D_SB01"));
|
||||
if (id != fpcM_ERROR_PROCESS_ID_e) {
|
||||
dComIfGp_event_setItemPartnerId(id);
|
||||
}
|
||||
|
||||
@@ -4018,8 +4018,15 @@ BOOL daNpc_grA_c::talk(void*) {
|
||||
}
|
||||
if (r26 && talkProc(NULL, TRUE, NULL)) {
|
||||
if (mFlow.getEventId(&sp8) == 1) {
|
||||
field_0x1480 =
|
||||
fopAcM_createItemForPresentDemo(¤t.pos, sp8, 0, -1, -1, NULL, NULL);
|
||||
#if TARGET_PC
|
||||
const char* itemCheckName = nullptr;
|
||||
if (sp8 == dItemNo_BOMB_IN_BAG_e) {
|
||||
itemCheckName = "goron_reward:F_SP113";
|
||||
sp8 = dusk::mods::item_check(itemCheckName, sp8, this);
|
||||
}
|
||||
#endif
|
||||
field_0x1480 = fopAcM_createItemForPresentDemo(¤t.pos, sp8, 0, -1, -1, NULL,
|
||||
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
|
||||
if (field_0x1480 != fpcM_ERROR_PROCESS_ID_e) {
|
||||
s16 r25 = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xff);
|
||||
dComIfGp_getEvent()->reset(this);
|
||||
|
||||
@@ -1681,7 +1681,9 @@ int daNpc_grO_c::talk(void* param_1) {
|
||||
if (facePlayerFlag && talkProc(NULL, TRUE, NULL)) {
|
||||
if (mType == TYPE_MINES) {
|
||||
if (mFlow.getEventId(&itemId) == 1) {
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, itemId, 0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("key_shard_2:D_MN04", itemId, this);
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, itemId, 0, -1, -1,
|
||||
NULL, NULL DUSK_GIVE_TAG("key_shard_2:D_MN04"));
|
||||
if (mItemID != fpcM_ERROR_PROCESS_ID_e) {
|
||||
s16 eventIdx = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xFF);
|
||||
dComIfGp_getEvent()->reset(this);
|
||||
|
||||
@@ -1338,7 +1338,9 @@ int daNpc_grR_c::talk(void* param_1) {
|
||||
if (bVar1 && talkProc(NULL, TRUE, NULL)) {
|
||||
if (mType == TYPE_0) {
|
||||
if (mFlow.getEventId(&i_itemNo) == 1) {
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, i_itemNo, 0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("key_shard_3:D_MN04", i_itemNo, this);
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, i_itemNo, 0, -1, -1,
|
||||
NULL, NULL DUSK_GIVE_TAG("key_shard_3:D_MN04"));
|
||||
|
||||
if (mItemID != fpcM_ERROR_PROCESS_ID_e) {
|
||||
s16 i_eventID = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xFF);
|
||||
|
||||
@@ -1190,8 +1190,9 @@ int daNpc_grS_c::talk(void* param_0) {
|
||||
if (unkFlag1 && talkProc(NULL, 1, NULL)) {
|
||||
if (mType == 0) {
|
||||
if (mFlow.getEventId(&unkInt2) == 1) {
|
||||
mPresentItemId =
|
||||
fopAcM_createItemForPresentDemo(¤t.pos, unkInt2, 0, -1, -1, 0, 0);
|
||||
DUSK_ITEM_CHECK("key_shard_1:D_MN04", unkInt2, this);
|
||||
mPresentItemId = fopAcM_createItemForPresentDemo(¤t.pos, unkInt2, 0, -1,
|
||||
-1, 0, 0 DUSK_GIVE_TAG("key_shard_1:D_MN04"));
|
||||
|
||||
if (mPresentItemId != fpcM_ERROR_PROCESS_ID_e) {
|
||||
s16 eventIdx =
|
||||
|
||||
@@ -971,8 +971,9 @@ BOOL daNpcImpal_c::EvCut_ImpalAppear1(int i_cut_index) {
|
||||
if (talkProc(NULL, 1, NULL)) {
|
||||
int evt_id = 0;
|
||||
if (mFlow.getEventId(&evt_id) == 1) {
|
||||
mItemPartnerId =
|
||||
fopAcM_createItemForPresentDemo(¤t.pos, evt_id, 0, -1, -1, 0, 0);
|
||||
DUSK_ITEM_CHECK("ilia_charm", evt_id, this);
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(
|
||||
¤t.pos, evt_id, 0, -1, -1, 0, 0 DUSK_GIVE_TAG("ilia_charm"));
|
||||
if (mItemPartnerId != 0xffffffff) {
|
||||
s16 evt_idx =
|
||||
dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xff);
|
||||
@@ -1060,8 +1061,9 @@ BOOL daNpcImpal_c::EvCut_CopyRod(int i_cut_index) {
|
||||
case '0003':
|
||||
int evt_id = 0;
|
||||
if (mFlow.getEventId(&evt_id) == 1) {
|
||||
mItemPartnerId =
|
||||
fopAcM_createItemForPresentDemo(¤t.pos, evt_id, 0, -1, -1, 0, 0);
|
||||
DUSK_ITEM_CHECK("skybook", evt_id, this);
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(
|
||||
¤t.pos, evt_id, 0, -1, -1, 0, 0 DUSK_GIVE_TAG("skybook"));
|
||||
dComIfGp_event_setItemPartnerId(mItemPartnerId);
|
||||
mItemPartnerId = -1;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "d/d_msg_object.h"
|
||||
#include <cstring>
|
||||
|
||||
#if TARGET_PC
|
||||
static u8 s_givenInsectId = dItemNo_NONE_e;
|
||||
#endif
|
||||
|
||||
enum Ins_RES_File_ID {
|
||||
/* BCK */
|
||||
/* 0x06 */ BCK_INS_F_HAPPY = 0x6,
|
||||
@@ -1259,6 +1263,7 @@ int daNpcIns_c::waitPresent(void* param_1) {
|
||||
daPy_py_c* player = daPy_getPlayerActorClass();
|
||||
player->changeOriginalDemo();
|
||||
player->changeDemoMode(0x25, 2, type, 0);
|
||||
IF_DUSK(s_givenInsectId = type;)
|
||||
} else {
|
||||
mInsectMsgNo = 0x719;
|
||||
}
|
||||
@@ -1487,7 +1492,17 @@ int daNpcIns_c::talk(void* param_1) {
|
||||
OS_REPORT("会話終了時 イベントID=%d アイテムNo=%d\n", eventID, itemNo);
|
||||
|
||||
if (eventID == 1) {
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, NULL, NULL);
|
||||
#if TARGET_PC
|
||||
u32 itemGiveTag = 0;
|
||||
if (s_givenInsectId != dItemNo_NONE_e) {
|
||||
itemNo = dusk::mods::item_check_bug(
|
||||
s_givenInsectId, itemNo & 0xFF, this);
|
||||
itemGiveTag = dusk::mods::item_give_tag_bug(s_givenInsectId);
|
||||
s_givenInsectId = dItemNo_NONE_e;
|
||||
}
|
||||
#endif
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1,
|
||||
-1, NULL, NULL IF_DUSK_ARG(itemGiveTag));
|
||||
|
||||
if (mItemID != fpcM_ERROR_PROCESS_ID_e) {
|
||||
daPy_getPlayerActorClass()->cancelOriginalDemo();
|
||||
|
||||
@@ -1181,7 +1181,16 @@ int daNpc_Kkri_c::talk(void*) {
|
||||
switch (eventId) {
|
||||
case 1:
|
||||
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, item_no, 0, -1, -1, NULL, NULL);
|
||||
#if TARGET_PC
|
||||
const char* itemCheckName = nullptr;
|
||||
if (item_no == dItemNo_OIL_BOTTLE3_e) {
|
||||
itemCheckName = "coro_bottle";
|
||||
item_no = dusk::mods::item_check(itemCheckName, item_no, this);
|
||||
}
|
||||
#endif
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, item_no,
|
||||
0, -1, -1, NULL,
|
||||
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
|
||||
}
|
||||
|
||||
if (fopAcM_IsExecuting(mItemPartnerId)) {
|
||||
|
||||
@@ -1236,8 +1236,9 @@ int daNpc_Len_c::talk(void* param_0) {
|
||||
switch (evt_id) {
|
||||
case 1:
|
||||
if (mItemPartnerId == -1) {
|
||||
DUSK_ITEM_CHECK("renado_letter", local_18, this);
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, local_18,
|
||||
0, -1, -1, 0, 0);
|
||||
0, -1, -1, 0, 0 DUSK_GIVE_TAG("renado_letter"));
|
||||
}
|
||||
if (fopAcM_IsExecuting(mItemPartnerId)) {
|
||||
mEvtNo = 1;
|
||||
|
||||
@@ -2393,7 +2393,10 @@ int daNpc_Maro_c::cutArrowTutorial(int arg0) {
|
||||
switch (evt_ret) {
|
||||
case 1: {
|
||||
if (mItemPartnerId == -1) {
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, evt_id, 0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("archery_reward:F_SP109", evt_id, this);
|
||||
mItemPartnerId =
|
||||
fopAcM_createItemForPresentDemo(¤t.pos, evt_id, 0, -1, -1,
|
||||
NULL, NULL DUSK_GIVE_TAG("archery_reward:F_SP109"));
|
||||
}
|
||||
|
||||
if (fopAcM_IsExecuting(mItemPartnerId)) {
|
||||
|
||||
@@ -1191,7 +1191,15 @@ int daNpc_myna2_c::ECut_gameGoalSuccess(int i_staffId) {
|
||||
case 20: {
|
||||
int itemNo = 0;
|
||||
if (mFlow.getEventId(&itemNo) == 1) {
|
||||
mItemPid = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, NULL, NULL);
|
||||
#if TARGET_PC
|
||||
const char* itemCheckName = nullptr;
|
||||
if (itemNo == dItemNo_KAKERA_HEART_e) {
|
||||
itemCheckName = "plumm_minigame_reward";
|
||||
itemNo = dusk::mods::item_check(itemCheckName, itemNo, this);
|
||||
}
|
||||
#endif
|
||||
mItemPid = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, NULL,
|
||||
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -958,8 +958,19 @@ int daNpc_Pouya_c::cutHaveFavorToAsk(int param_0) {
|
||||
switch (evt_id) {
|
||||
case 1:
|
||||
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
|
||||
#if TARGET_PC
|
||||
const char* itemCheckName = nullptr;
|
||||
if (local_64 == dItemNo_DROP_BOTTLE_e) {
|
||||
itemCheckName = "jovani_reward_1";
|
||||
} else if (local_64 == dItemNo_SILVER_RUPEE_e) {
|
||||
itemCheckName = "jovani_reward_2";
|
||||
}
|
||||
if (itemCheckName != nullptr) {
|
||||
local_64 = dusk::mods::item_check(itemCheckName, local_64, this);
|
||||
}
|
||||
#endif
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, local_64, 0,
|
||||
-1, -1, 0, 0);
|
||||
-1, -1, 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
|
||||
}
|
||||
if (fopAcM_IsExecuting(mItemPartnerId)) {
|
||||
field_0xfce = 1;
|
||||
|
||||
@@ -720,7 +720,8 @@ fpc_ProcID daNpcPray_c::createHeart() {
|
||||
mDoMtx_stack_c::ZXYrotS(rot);
|
||||
mDoMtx_stack_c::multVec(&offset, &offset);
|
||||
pos += offset;
|
||||
return fopAcM_createItemForBoss(&pos, dItemNo_KAKERA_HEART_e, fopAcM_GetRoomNo(this), &rot, &size, 0.0f, 0.0f, 0);
|
||||
return fopAcM_createItemForBoss(&pos, dItemNo_KAKERA_HEART_e, fopAcM_GetRoomNo(this), &rot,
|
||||
&size, 0.0f, 0.0f, 0 IF_DUSK_ARG("prayer_reward"));
|
||||
}
|
||||
|
||||
BOOL daNpcPray_c::_Evt_GetHeart(int i_staffID) {
|
||||
|
||||
@@ -1310,7 +1310,9 @@ bool daNpcRafrel_c::talk(void* param_0) {
|
||||
OS_REPORT("会話終了時 イベントID=%d アイテムNo=%d\n", eventId, itemNo);
|
||||
|
||||
if (eventId == 1) {
|
||||
field_0xe00 = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("auru_memo", itemNo, this);
|
||||
field_0xe00 = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1,
|
||||
NULL, NULL DUSK_GIVE_TAG("auru_memo"));
|
||||
if (field_0xe00 != fpcM_ERROR_PROCESS_ID_e) {
|
||||
s16 eventIdx = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xFF);
|
||||
dComIfGp_getEvent()->reset(this);
|
||||
@@ -1562,7 +1564,9 @@ int daNpcRafrel_c::EvCut_Appear(int i_staffId) {
|
||||
int itemNo = 0;
|
||||
u16 eventId = mFlow.getEventId(&itemNo);
|
||||
if (eventId == 1) {
|
||||
field_0xe00 = fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("auru_memo", itemNo, this);
|
||||
field_0xe00 = fopAcM_createItemForPresentDemo(
|
||||
¤t.pos, itemNo, 0, -1, -1, NULL, NULL DUSK_GIVE_TAG("auru_memo"));
|
||||
dComIfGp_event_setItemPartnerId(field_0xe00);
|
||||
field_0xe00 = fpcM_ERROR_PROCESS_ID_e;
|
||||
}
|
||||
|
||||
@@ -823,8 +823,9 @@ BOOL daNpcThe_c::talk(void* param_0) {
|
||||
}
|
||||
int item_no = 0;
|
||||
if (mFlow.getEventId(&item_no) == 1) {
|
||||
DUSK_ITEM_CHECK("telma_invoice", item_no, this);
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, item_no, 0, -1, -1,
|
||||
NULL, NULL);
|
||||
NULL, NULL DUSK_GIVE_TAG("telma_invoice"));
|
||||
if (mItemID != -1) {
|
||||
s16 event_id = dComIfGp_getEventManager().getEventIdx(
|
||||
this, "DEFAULT_GETITEM", 0xff);
|
||||
|
||||
@@ -1165,8 +1165,9 @@ int daNpc_Uri_c::cutEndCarryTutorial(int param_1) {
|
||||
(s32)mFlow.getEventId(&local_48) == 1)
|
||||
{
|
||||
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
|
||||
mItemPartnerId =
|
||||
fopAcM_createItemForPresentDemo(¤t.pos, local_48, 0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("uli_cradle_reward", local_48, this);
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, local_48, 0, -1, -1,
|
||||
NULL, NULL DUSK_GIVE_TAG("uli_cradle_reward"));
|
||||
}
|
||||
if (fopAcM_IsExecuting(mItemPartnerId)) {
|
||||
/* T_0007 - Ordon Village - During Uli's pick-up tutorial */
|
||||
|
||||
@@ -2213,8 +2213,9 @@ int daNpc_ykW_c::cutEndSnowboardRace(int param_0) {
|
||||
switch (eventId) {
|
||||
case 1:
|
||||
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, itemId, 0,
|
||||
-1, -1, 0, 0);
|
||||
DUSK_ITEM_CHECK("snowboard_race_reward", itemId, this);
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, itemId, 0, -1,
|
||||
-1, 0, 0 DUSK_GIVE_TAG("snowboard_race_reward"));
|
||||
}
|
||||
|
||||
if (fopAcM_IsExecuting(mItemPartnerId)) {
|
||||
@@ -2911,8 +2912,9 @@ int daNpc_ykW_c::talk(void* param_0) {
|
||||
switch (eventId) {
|
||||
case 1:
|
||||
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
|
||||
mItemPartnerId =
|
||||
fopAcM_createItemForPresentDemo(¤t.pos, itemNo, 0, -1, -1, 0, 0);
|
||||
DUSK_ITEM_CHECK("dungeon_map:D_MN11", itemNo, this);
|
||||
mItemPartnerId = fopAcM_createItemForPresentDemo(¤t.pos, itemNo,
|
||||
0, -1, -1, 0, 0 DUSK_GIVE_TAG("dungeon_map:D_MN11"));
|
||||
}
|
||||
|
||||
if (fopAcM_IsExecuting(mItemPartnerId)) {
|
||||
|
||||
@@ -2609,8 +2609,19 @@ BOOL daNpc_zrA_c::ECut_thanksBlast(int i_staffID) {
|
||||
case 31: {
|
||||
int item_id = 0;
|
||||
if (mFlow.getEventId(&item_id) == 1) {
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, item_id,
|
||||
0, -1, -1, NULL, NULL);
|
||||
#if TARGET_PC
|
||||
const char* itemCheckName = nullptr;
|
||||
if (item_id == dItemNo_BOMB_IN_BAG_e) {
|
||||
itemCheckName = "iza_reward_1";
|
||||
} else if (item_id == dItemNo_BOMB_BAG_LV2_e) {
|
||||
itemCheckName = "iza_reward_2";
|
||||
}
|
||||
if (itemCheckName != nullptr) {
|
||||
item_id = dusk::mods::item_check(itemCheckName, item_id, this);
|
||||
}
|
||||
#endif
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, item_id, 0, -1, -1, NULL,
|
||||
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
|
||||
}
|
||||
field_0x9eb = true;
|
||||
break;
|
||||
|
||||
@@ -1681,8 +1681,9 @@ BOOL daNpc_zrC_c::ECut_earringGet(int i_staffID) {
|
||||
case 40: {
|
||||
int item_no = 0;
|
||||
if (mFlow.getEventId(&item_no) == 1) {
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, item_no,
|
||||
0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("coral_earring", item_no, this);
|
||||
mItemID = fopAcM_createItemForPresentDemo(
|
||||
¤t.pos, item_no, 0, -1, -1, NULL, NULL DUSK_GIVE_TAG("coral_earring"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1729,8 +1729,9 @@ BOOL daNpc_zrZ_c::ECut_clothesGet(int i_staffID) {
|
||||
}
|
||||
item_no = 0;
|
||||
if (mFlow.getEventId(&item_no) == 1) {
|
||||
mItemID = fopAcM_createItemForPresentDemo(¤t.pos, item_no,
|
||||
0, -1, -1, NULL, NULL);
|
||||
DUSK_ITEM_CHECK("zora_armor", item_no, this);
|
||||
mItemID = fopAcM_createItemForPresentDemo(
|
||||
¤t.pos, item_no, 0, -1, -1, NULL, NULL DUSK_GIVE_TAG("zora_armor"));
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ void daItem_c::CreateInit() {
|
||||
initBaseMtx();
|
||||
animPlay(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
if (m_itemNo == dItemNo_BOOMERANG_e) {
|
||||
if (m_itemNo == dItemNo_BOOMERANG_e IF_DUSK(&&!mItemOverridden)) {
|
||||
itemGetNextExecute();
|
||||
} else if ((m_itemNo == dItemNo_ORANGE_RUPEE_e || m_itemNo == dItemNo_SILVER_RUPEE_e) &&
|
||||
mSparkleEmtr.getEmitter() == NULL)
|
||||
@@ -264,6 +264,17 @@ int daItem_c::_daItem_create() {
|
||||
shape_angle.z = 0;
|
||||
shape_angle.x = 0;
|
||||
|
||||
#if TARGET_PC
|
||||
const u32 params = fopAcM_GetParam(this);
|
||||
mOriginalItemNo = params & 0xFF;
|
||||
const u8 resolvedItem = dusk::mods::item_check_freestanding(
|
||||
daItem_prm::getItemBitNo(this), mOriginalItemNo, this);
|
||||
mItemOverridden = resolvedItem != mOriginalItemNo;
|
||||
mItemGiveTag = dusk::mods::item_give_tag_freestanding(daItem_prm::getItemBitNo(this));
|
||||
if (mItemOverridden) {
|
||||
fopAcM_SetParam(this, (params & 0xFFFFFF00) | resolvedItem);
|
||||
}
|
||||
#endif
|
||||
field_0x95d = true;
|
||||
}
|
||||
|
||||
@@ -507,9 +518,18 @@ void daItem_c::procInitGetDemoEvent() {
|
||||
fopAcM_orderItemEvent(this, 0, 0);
|
||||
eventInfo.onCondition(dEvtCnd_CANGETITEM_e);
|
||||
|
||||
m_item_id = fopAcM_createItemForTrBoxDemo(¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this),
|
||||
NULL, NULL);
|
||||
#if TARGET_PC
|
||||
const u8 displayItemNo = m_itemNo;
|
||||
if (mItemOverridden) {
|
||||
m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this);
|
||||
}
|
||||
#endif
|
||||
m_item_id = fopAcM_createItemForTrBoxDemo(
|
||||
¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(mItemGiveTag));
|
||||
JUT_ASSERT(0, m_item_id != fpcM_ERROR_PROCESS_ID_e);
|
||||
#if TARGET_PC
|
||||
m_itemNo = displayItemNo;
|
||||
#endif
|
||||
|
||||
setStatus(STATUS_WAIT_GET_DEMO_EVENT_e);
|
||||
}
|
||||
@@ -521,7 +541,7 @@ void daItem_c::procWaitGetDemoEvent() {
|
||||
dComIfGp_event_setItemPartnerId(m_item_id);
|
||||
}
|
||||
} else {
|
||||
if (m_itemNo == dItemNo_BOOMERANG_e) {
|
||||
if (m_itemNo == dItemNo_BOOMERANG_e IF_DUSK(&&!mItemOverridden)) {
|
||||
fopAcM_orderItemEvent(this, 0, 0);
|
||||
eventInfo.onCondition(dEvtCnd_CANGETITEM_e);
|
||||
return;
|
||||
@@ -537,7 +557,9 @@ void daItem_c::procWaitGetDemoEvent() {
|
||||
|
||||
procInitSimpleGetDemo();
|
||||
itemGet();
|
||||
if (!haveItem) {
|
||||
if (!haveItem IF_DUSK(&&(!mItemOverridden || (m_itemNo >= dItemNo_GREEN_RUPEE_e &&
|
||||
m_itemNo <= dItemNo_SILVER_RUPEE_e))))
|
||||
{
|
||||
dComIfGs_offItemFirstBit(m_itemNo);
|
||||
}
|
||||
} else {
|
||||
@@ -864,6 +886,12 @@ void daItem_c::itemGetNextExecute() {
|
||||
procInitGetDemoEvent();
|
||||
break;
|
||||
default:
|
||||
#if TARGET_PC
|
||||
if (mItemOverridden) {
|
||||
procInitGetDemoEvent();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
// "[daItem_c] Get process not defined[%d]\n"
|
||||
OS_REPORT_ERROR("[daItem_c]ゲット処理が定義されていません[%d]\n", m_itemNo);
|
||||
}
|
||||
@@ -877,38 +905,48 @@ void daItem_c::itemGetNextExecute() {
|
||||
}
|
||||
|
||||
void daItem_c::itemGet() {
|
||||
#if TARGET_PC
|
||||
const u8 displayItemNo = m_itemNo;
|
||||
if (mItemOverridden) {
|
||||
m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this);
|
||||
}
|
||||
#endif
|
||||
switch (m_itemNo) {
|
||||
#if TARGET_PC
|
||||
case dItemNo_UTAWA_HEART_e:
|
||||
case dItemNo_KAKERA_HEART_e:
|
||||
#endif
|
||||
case dItemNo_HEART_e:
|
||||
mDoAud_seStart(Z2SE_HEART_PIECE_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
break;
|
||||
case dItemNo_GREEN_RUPEE_e:
|
||||
mDoAud_seStart(Z2SE_GREEN_LUPY_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
break;
|
||||
case dItemNo_BLUE_RUPEE_e:
|
||||
mDoAud_seStart(Z2SE_BLUE_LUPY_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
break;
|
||||
case dItemNo_YELLOW_RUPEE_e:
|
||||
mDoAud_seStart(Z2SE_BLUE_LUPY_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
break;
|
||||
case dItemNo_RED_RUPEE_e:
|
||||
mDoAud_seStart(Z2SE_RED_LUPY_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
break;
|
||||
case dItemNo_PURPLE_RUPEE_e:
|
||||
mDoAud_seStart(Z2SE_RED_LUPY_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
break;
|
||||
case dItemNo_ORANGE_RUPEE_e:
|
||||
mDoAud_seStart(Z2SE_RED_LUPY_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
break;
|
||||
case dItemNo_SILVER_RUPEE_e:
|
||||
mDoAud_seStart(Z2SE_RED_LUPY_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
break;
|
||||
case dItemNo_BOOMERANG_e:
|
||||
break;
|
||||
@@ -918,12 +956,25 @@ void daItem_c::itemGet() {
|
||||
case dItemNo_ARROW_1_e:
|
||||
case dItemNo_PACHINKO_SHOT_e:
|
||||
mDoAud_seStart(Z2SE_CONSUMP_ITEM_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo);
|
||||
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
|
||||
#if TARGET_PC
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
#if TARGET_PC
|
||||
if (mItemOverridden) {
|
||||
mDoAud_seStart(Z2SE_CONSUMP_ITEM_GET, NULL, 0, 0);
|
||||
execItemGet(m_itemNo, mItemGiveTag, this);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
// "[daItem_c] Get process not defined[%d]\n"
|
||||
OS_REPORT_ERROR("[daItem_c]ゲット処理が定義されていません[%d]\n", m_itemNo);
|
||||
break;
|
||||
}
|
||||
#if TARGET_PC
|
||||
m_itemNo = displayItemNo;
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOL daItem_c::checkCountTimer() {
|
||||
|
||||
@@ -97,6 +97,12 @@ int daObjLife_c::Create() {
|
||||
field_0x94c = 0.7f;
|
||||
mRotateSpeed = 7000;
|
||||
|
||||
#if TARGET_PC
|
||||
if (mOverrideHover) {
|
||||
fopAcM_SetGravity(this, 0.0f);
|
||||
mRotateSpeed = 550;
|
||||
}
|
||||
#endif
|
||||
setEffect();
|
||||
mSound.init(¤t.pos, 1);
|
||||
return 1;
|
||||
@@ -140,6 +146,27 @@ int daObjLife_c::create() {
|
||||
home.angle.x = home.angle.z = 0;
|
||||
current.angle.x = current.angle.z = 0;
|
||||
shape_angle.x = shape_angle.z = 0;
|
||||
#if TARGET_PC
|
||||
const u32 params = fopAcM_GetParam(this);
|
||||
const u8 parameterItemNo = params & 0xFF;
|
||||
if (mItemGiveOriginalNo == dItemNo_NONE_e) {
|
||||
mOriginalItemNo = parameterItemNo;
|
||||
const u8 resolvedItem =
|
||||
dusk::mods::item_check_freestanding(getSaveBitNo(), mOriginalItemNo, this);
|
||||
mItemGiveTag = dusk::mods::item_give_tag_freestanding(getSaveBitNo());
|
||||
mItemOverridden = resolvedItem != mOriginalItemNo;
|
||||
if (mItemOverridden) {
|
||||
fopAcM_SetParam(this, (params & 0xFFFFFF00) | resolvedItem);
|
||||
}
|
||||
} else {
|
||||
mOriginalItemNo = mItemGiveOriginalNo;
|
||||
mItemOverridden = parameterItemNo != mItemGiveOriginalNo;
|
||||
}
|
||||
mOverrideHover =
|
||||
mItemOverridden &&
|
||||
(mOriginalItemNo == dItemNo_UTAWA_HEART_e ||
|
||||
(mOriginalItemNo >= dItemNo_M_BEETLE_e && mOriginalItemNo <= dItemNo_F_MAYFLY_e));
|
||||
#endif
|
||||
mIsPrmsInit = true;
|
||||
}
|
||||
|
||||
@@ -153,7 +180,7 @@ int daObjLife_c::create() {
|
||||
return cPhs_ERROR_e;
|
||||
}
|
||||
|
||||
if (m_itemNo == dItemNo_UTAWA_HEART_e && dComIfGs_isStageLife()) {
|
||||
if (m_itemNo == dItemNo_UTAWA_HEART_e && dComIfGs_isStageLife() IF_DUSK(&&!mItemOverridden)) {
|
||||
return cPhs_ERROR_e;
|
||||
}
|
||||
|
||||
@@ -296,8 +323,18 @@ int daObjLife_c::initActionOrderGetDemo() {
|
||||
fopAcM_orderItemEvent(this, 0, 0);
|
||||
eventInfo.onCondition(dEvtCnd_CANGETITEM_e);
|
||||
|
||||
mItemId = fopAcM_createItemForTrBoxDemo(¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL);
|
||||
#if TARGET_PC
|
||||
const u8 displayItemNo = m_itemNo;
|
||||
if (mItemOverridden) {
|
||||
m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this);
|
||||
}
|
||||
#endif
|
||||
mItemId = fopAcM_createItemForTrBoxDemo(
|
||||
¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(mItemGiveTag));
|
||||
JUT_ASSERT(699, mItemId != fpcM_ERROR_PROCESS_ID_e);
|
||||
#if TARGET_PC
|
||||
m_itemNo = displayItemNo;
|
||||
#endif
|
||||
|
||||
setStatus(STATUS_ORDER_GET_DEMO_e);
|
||||
return 1;
|
||||
|
||||
@@ -109,6 +109,7 @@ int daItemShield_c::__CreateHeap() {
|
||||
int daItemShield_c::create() {
|
||||
fopAcM_ct(this, daItemShield_c);
|
||||
m_itemNo = dItemNo_WOOD_SHIELD_e;
|
||||
DUSK_ITEM_CHECK("ordon_shield", m_itemNo, this);
|
||||
if (fopAcM_isSwitch(this, getSwBit2())) {
|
||||
OS_REPORT("木の盾:もう取ったので出ません\n");
|
||||
return cPhs_ERROR_e;
|
||||
@@ -240,8 +241,9 @@ int daItemShield_c::initActionOrderGetDemo() {
|
||||
daItemBase_c::hide();
|
||||
fopAcM_orderItemEvent(this, 0, 0);
|
||||
eventInfo.onCondition(dEvtCnd_CANGETITEM_e);
|
||||
mItemId =
|
||||
fopAcM_createItemForTrBoxDemo(¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), 0, 0);
|
||||
mItemId = fopAcM_createItemForTrBoxDemo(¤t.pos,
|
||||
DUSK_ITEM_CHECK_EXPR("ordon_shield", dItemNo_WOOD_SHIELD_e, this), -1,
|
||||
fopAcM_GetRoomNo(this), 0, 0 DUSK_GIVE_TAG("ordon_shield"));
|
||||
JUT_ASSERT(682, mItemId != fpcM_ERROR_PROCESS_ID_e)
|
||||
setStatus(STATUS_ORDERGETDEMO);
|
||||
return 1;
|
||||
|
||||
@@ -37,6 +37,7 @@ int daObjSword_c::Create() {
|
||||
cPhs_Step daObjSword_c::create() {
|
||||
fopAcM_ct(this, daObjSword_c);
|
||||
m_itemNo = 0x28;
|
||||
DUSK_ITEM_CHECK("ordon_sword", m_itemNo, this);
|
||||
if (fopAcM_isItem(this, getItemBit())) {
|
||||
return cPhs_ERROR_e;
|
||||
}
|
||||
@@ -71,8 +72,9 @@ int daObjSword_c::initActionOrderGetDemo() {
|
||||
hide();
|
||||
fopAcM_orderItemEvent(this, 0, 0);
|
||||
eventInfo.onCondition(8);
|
||||
mProcID = fopAcM_createItemForTrBoxDemo(¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this),
|
||||
NULL, NULL);
|
||||
mProcID =
|
||||
fopAcM_createItemForTrBoxDemo(¤t.pos, DUSK_ITEM_CHECK_EXPR("ordon_sword", 0x28, this),
|
||||
-1, fopAcM_GetRoomNo(this), NULL, NULL DUSK_GIVE_TAG("ordon_sword"));
|
||||
setStatus(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -255,8 +255,9 @@ int daObjWStatue_c::initActionOrderGetDemo() {
|
||||
s16 eventIdx = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xff);
|
||||
dComIfGp_getEvent()->reset(this);
|
||||
fopAcM_orderChangeEventId(this, eventIdx, 1, 0xffff);
|
||||
mItemId = fopAcM_createItemForTrBoxDemo(¤t.pos, m_itemNo, 0xffffffff,
|
||||
fopAcM_GetRoomNo(this), 0, 0);
|
||||
mItemId = fopAcM_createItemForTrBoxDemo(¤t.pos,
|
||||
DUSK_ITEM_CHECK_EXPR("wood_statue", m_itemNo, this), 0xffffffff, fopAcM_GetRoomNo(this),
|
||||
0, 0 DUSK_GIVE_TAG("wood_statue"));
|
||||
JUT_ASSERT(544, mItemId != fpcM_ERROR_PROCESS_ID_e);
|
||||
setStatus(STATUS_ORDER_GET_DEMO);
|
||||
return 1;
|
||||
|
||||
@@ -32,6 +32,7 @@ int daObjZCloth_c::Create() {
|
||||
int daObjZCloth_c::create() {
|
||||
fopAcM_ct(this, daObjZCloth_c);
|
||||
m_itemNo = 0x31;
|
||||
DUSK_ITEM_CHECK("zora_armor", m_itemNo, this);
|
||||
int phase = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(m_itemNo));
|
||||
if (phase == cPhs_COMPLEATE_e) {
|
||||
if (!fopAcM_entrySolidHeap(this, (heapCallbackFunc)CheckFieldItemCreateHeap, 0x2fb0)) {
|
||||
|
||||
@@ -375,10 +375,14 @@ static const u8* l_sightDL_get() {
|
||||
static bool _ = (
|
||||
dusk::LoadDolAsset(
|
||||
buf,
|
||||
{
|
||||
{GameVersion::GcnUsa, 0x803BA0C0},
|
||||
{GameVersion::GcnPal, 0x803BBDA0},
|
||||
{GameVersion::GcnJpn, 0x803B4220}
|
||||
{
|
||||
{GameVersion::GcnUsa, 0x803BA0C0},
|
||||
{GameVersion::GcnPal, 0x803BBDA0},
|
||||
{GameVersion::GcnJpn, 0x803B4220},
|
||||
{GameVersion::WiiUsaRev0, 0x803F63C0},
|
||||
{GameVersion::WiiUsa, 0x803E1640},
|
||||
{GameVersion::WiiPal, 0x803E23A0},
|
||||
{GameVersion::WiiJpn, 0x803DF600}
|
||||
},
|
||||
0x89
|
||||
),
|
||||
|
||||
@@ -11,6 +11,20 @@
|
||||
#include "m_Do/m_Do_lib.h"
|
||||
#include <cstring>
|
||||
|
||||
#if TARGET_PC
|
||||
#include "d/d_item_data.h"
|
||||
|
||||
const ResourceData& daShopItem_c::getResourceData() const {
|
||||
if (mItemOverridden) {
|
||||
return mOverrideData;
|
||||
}
|
||||
return mData[mShopItemID];
|
||||
}
|
||||
#define SHOP_RESOURCE_DATA getResourceData()
|
||||
#else
|
||||
#define SHOP_RESOURCE_DATA mData[mShopItemID]
|
||||
#endif
|
||||
|
||||
const char* daShopItem_c::getShopArcname() {
|
||||
switch (m_itemNo) {
|
||||
case dItemNo_NONE_e:
|
||||
@@ -88,7 +102,29 @@ const char* daShopItem_c::getShopArcname() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mData[mShopItemID].get_arcName();
|
||||
#if TARGET_PC
|
||||
if (m_itemNo != dItemNo_NONE_e && mItemGiveOriginalNo == dItemNo_NONE_e) {
|
||||
mItemGiveOriginalNo = m_itemNo;
|
||||
const u8 resolvedItem = dusk::mods::item_check_shop(mItemGiveOriginalNo, this);
|
||||
mItemOverridden = resolvedItem != mItemGiveOriginalNo;
|
||||
if (mItemOverridden) {
|
||||
mOverrideData = mData[mShopItemID];
|
||||
mOverrideData.mArcName = dItem_data::getArcName(resolvedItem);
|
||||
mOverrideData.mBmdName = dItem_data::getBmdName(resolvedItem);
|
||||
mOverrideData.mBtkName = dItem_data::getBtkName(resolvedItem);
|
||||
mOverrideData.mBckName = dItem_data::getBckName(resolvedItem);
|
||||
mOverrideData.mBrkName = dItem_data::getBrkName(resolvedItem);
|
||||
mOverrideData.mBtpName = dItem_data::getBtpName(resolvedItem);
|
||||
mOverrideData.mTevFrm = dItem_data::getTevFrm(resolvedItem);
|
||||
mOverrideData.mBtpFrm = -1;
|
||||
mOverrideData.mFlag = static_cast<u32>(-1);
|
||||
mOverrideData.mOffsetY = mShopItemID == SHOP_ITEMNO_ARMOR ? 60.0f : 15.0f;
|
||||
mOverrideData.mScale = 1.0f;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return SHOP_RESOURCE_DATA.get_arcName();
|
||||
}
|
||||
|
||||
DUSK_GAME_DATA const f32 daShopItem_c::m_cullfar_max = 5000.0f;
|
||||
@@ -104,6 +140,11 @@ u16 daShopItem_c::getHeapSize() {
|
||||
OS_REPORT("ShopItemID [%u]\n", a_ShopItemID);
|
||||
ASSERT(a_ShopItemID < SHOP_ITEMNO_MAX);
|
||||
|
||||
#if TARGET_PC
|
||||
if (mItemOverridden) {
|
||||
return 0x8000;
|
||||
}
|
||||
#endif
|
||||
return HeapSizeTbl[a_ShopItemID];
|
||||
}
|
||||
|
||||
@@ -119,11 +160,11 @@ void daShopItem_c::CreateInit() {
|
||||
|
||||
if (strcmp("R_SP109", dComIfGp_getStartStageName()) == 0 && dComIfGp_getStartStageRoomNo() == 1)
|
||||
{
|
||||
scale.set(mData[mShopItemID].get_scale() * 0.8f, mData[mShopItemID].get_scale() * 0.8f,
|
||||
mData[mShopItemID].get_scale() * 0.8f);
|
||||
scale.set(SHOP_RESOURCE_DATA.get_scale() * 0.8f, SHOP_RESOURCE_DATA.get_scale() * 0.8f,
|
||||
SHOP_RESOURCE_DATA.get_scale() * 0.8f);
|
||||
} else {
|
||||
scale.set(mData[mShopItemID].get_scale(), mData[mShopItemID].get_scale(),
|
||||
mData[mShopItemID].get_scale());
|
||||
scale.set(SHOP_RESOURCE_DATA.get_scale(), SHOP_RESOURCE_DATA.get_scale(),
|
||||
SHOP_RESOURCE_DATA.get_scale());
|
||||
}
|
||||
|
||||
home.pos = current.pos;
|
||||
@@ -137,8 +178,8 @@ void daShopItem_c::set_mtx() {
|
||||
if (daShopItem_prm::getGroup(this) == 15) {
|
||||
mDoMtx_stack_c::transS(current.pos.x, current.pos.y, current.pos.z);
|
||||
} else {
|
||||
mDoMtx_stack_c::transS(current.pos.x, current.pos.y + mData[mShopItemID].get_offsetY(),
|
||||
current.pos.z);
|
||||
mDoMtx_stack_c::transS(
|
||||
current.pos.x, current.pos.y + SHOP_RESOURCE_DATA.get_offsetY(), current.pos.z);
|
||||
}
|
||||
|
||||
MTXCopy(mDoMtx_stack_c::get(), mMtx);
|
||||
@@ -147,8 +188,8 @@ void daShopItem_c::set_mtx() {
|
||||
if (daShopItem_prm::getGroup(this) == 15) {
|
||||
mDoMtx_stack_c::ZXYrotM(-11300, 32700, 7300);
|
||||
} else {
|
||||
mDoMtx_stack_c::ZXYrotM(mAngleX + mData[mShopItemID].get_angleX(),
|
||||
mData[mShopItemID].get_angleY(), mData[mShopItemID].get_angleZ());
|
||||
mDoMtx_stack_c::ZXYrotM(mAngleX + SHOP_RESOURCE_DATA.get_angleX(),
|
||||
SHOP_RESOURCE_DATA.get_angleY(), SHOP_RESOURCE_DATA.get_angleZ());
|
||||
}
|
||||
|
||||
mDoMtx_stack_c::ZXYrotM(current.angle.x, current.angle.y, current.angle.z);
|
||||
@@ -156,7 +197,7 @@ void daShopItem_c::set_mtx() {
|
||||
if (daShopItem_prm::getGroup(this) == 15) {
|
||||
mDoMtx_stack_c::XrotM(0);
|
||||
} else {
|
||||
mDoMtx_stack_c::XrotM(mData[mShopItemID].get_angleOffsetX());
|
||||
mDoMtx_stack_c::XrotM(SHOP_RESOURCE_DATA.get_angleOffsetX());
|
||||
}
|
||||
|
||||
mpModel->setBaseTRMtx(mDoMtx_stack_c::get());
|
||||
@@ -190,29 +231,31 @@ void daShopItem_c::setShadow() {
|
||||
}
|
||||
|
||||
BOOL daShopItem_c::chkFlag(int i_flag) {
|
||||
return mData[mShopItemID].get_flag() & i_flag;
|
||||
return SHOP_RESOURCE_DATA.get_flag() & i_flag;
|
||||
}
|
||||
|
||||
s8 daShopItem_c::getTevFrm() {
|
||||
return mData[mShopItemID].get_tevfrm();
|
||||
return SHOP_RESOURCE_DATA.get_tevfrm();
|
||||
}
|
||||
|
||||
s8 daShopItem_c::getBtpFrm() {
|
||||
return mData[mShopItemID].get_btpfrm();
|
||||
return SHOP_RESOURCE_DATA.get_btpfrm();
|
||||
}
|
||||
|
||||
u8 daShopItem_c::getShadowSize() {
|
||||
return mData[mShopItemID].get_shadowSize();
|
||||
return SHOP_RESOURCE_DATA.get_shadowSize();
|
||||
}
|
||||
|
||||
u8 daShopItem_c::getCollisionH() {
|
||||
return mData[mShopItemID].get_collisionH();
|
||||
return SHOP_RESOURCE_DATA.get_collisionH();
|
||||
}
|
||||
|
||||
u8 daShopItem_c::getCollisionR() {
|
||||
return mData[mShopItemID].get_collisionR();
|
||||
return SHOP_RESOURCE_DATA.get_collisionR();
|
||||
}
|
||||
|
||||
#undef SHOP_RESOURCE_DATA
|
||||
|
||||
int daShopItem_c::_create() {
|
||||
fopAcM_ct(this, daShopItem_c);
|
||||
|
||||
|
||||
@@ -362,9 +362,11 @@ int daTagStatue_c::demoProc() {
|
||||
item = dItemNo_AIR_LETTER_e;
|
||||
}
|
||||
|
||||
mItemId =
|
||||
fopAcM_createItemForTrBoxDemo(¤t.pos, item, -1,
|
||||
fopAcM_GetRoomNo(this), 0, 0);
|
||||
#if TARGET_PC
|
||||
item = dusk::mods::item_check_sky_character(item & 0xFF, this);
|
||||
#endif
|
||||
mItemId = fopAcM_createItemForTrBoxDemo(¤t.pos, item, -1,
|
||||
fopAcM_GetRoomNo(this), 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag_sky_character()));
|
||||
|
||||
JUT_ASSERT(580, mItemId != fpcM_ERROR_PROCESS_ID_e);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user