Merge branch 'main' of ssh://github.com/TwilitRealm/dusklight into better-tools

This commit is contained in:
TakaRikka
2026-08-29 18:37:14 -07:00
144 changed files with 12422 additions and 681 deletions
+24 -1
View File
@@ -1027,6 +1027,9 @@ void daAlink_c::setGetItemFace(u16 i_itemNo) {
|| i_itemNo == dItemNo_ANCIENT_DOCUMENT2_e
|| i_itemNo == dItemNo_DROP_CONTAINER_e
|| i_itemNo == dItemNo_DROP_CONTAINER02_e
#if TARGET_PC
|| i_itemNo == dItemNo_SHADOW_CRYSTAL_e
#endif
|| i_itemNo == dItemNo_DROP_CONTAINER03_e)
{
setFaceBasicBck(dRes_ID_ALANM_BCK_FI_e);
@@ -2009,7 +2012,11 @@ void daAlink_c::setGetSubBgm(int i_itemNo) {
/* dItemNo_WEAR_KOKIRI_e */ SETYPE_NONE,
/* dItemNo_ARMOR_e */ SETYPE_ITEM_GET,
/* dItemNo_WEAR_ZORA_e */ SETYPE_ITEM_GET,
#if TARGET_PC
/* dItemNo_SHADOW_CRYSTAL_e */ SETYPE_ITEM_GET,
#else
/* dItemNo_MAGIC_LV1_e */ SETYPE_NONE,
#endif
/* dItemNo_DUNGEON_EXIT_2_e */ SETYPE_ITEM_GET_MINI,
/* dItemNo_WALLET_LV1_e */ SETYPE_NONE,
/* dItemNo_WALLET_LV2_e */ SETYPE_ITEM_GET,
@@ -2307,8 +2314,13 @@ int daAlink_c::procCoGetItemInit() {
item_no = dComIfGp_event_getGtItm();
}
#if TARGET_PC
const u32 giveTag = mDemo.getParam0() != 0x100 && mDemo.getParam1() != 0 ?
static_cast<u32>(mDemo.getParam1()) :
dusk::mods::item_give_queue_take_tag();
#endif
fpc_ProcID item_partner_id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1,
fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(dusk::mods::item_give_queue_take_tag()));
fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(giveTag));
if (item_partner_id != fpcM_ERROR_PROCESS_ID_e) {
dComIfGp_event_setItemPartnerId(item_partner_id);
}
@@ -2533,6 +2545,16 @@ int daAlink_c::procCoGetItem() {
} else if (mProcVar2.field_0x300c == 0x23 && checkStageName("D_MN11")) {
field_0x32cc = 0x5C0;
} else if (mProcVar2.field_0x300c == 0xE0) {
#if TARGET_PC
// Dusklight increments the Poe count when the displayed item is granted.
if (dComIfGs_getPohSpiritNum() + 1 == 20) {
field_0x32cc = 0x4CF;
} else if (dComIfGs_getPohSpiritNum() + 1 == 60) {
field_0x32cc = 0x4D0;
} else {
field_0x32cc = mProcVar2.field_0x300c + 0x65;
}
#else
if (dComIfGs_getPohSpiritNum() == 20) {
field_0x32cc = 0x4CF;
} else if (dComIfGs_getPohSpiritNum() == 60) {
@@ -2540,6 +2562,7 @@ int daAlink_c::procCoGetItem() {
} else {
field_0x32cc = mProcVar2.field_0x300c + 0x65;
}
#endif
} else if (mProcVar2.field_0x300c == 0x21) {
static u32 const heartPieceMessage[5] = {0x86, 0x9C, 0x9D, 0x9E, 0x9F};
field_0x32cc = heartPieceMessage[dComIfGs_getMaxLife() % 5];
+6 -1
View File
@@ -19,6 +19,10 @@
#include "f_op/f_op_actor_enemy.h"
#include "Z2AudioLib/Z2Instances.h"
#if TARGET_PC
#include "mods/items.h"
#endif
enum daB_DS_Joint {
DS_JNT_BACKBONE1,
DS_JNT_BACKBONE2,
@@ -4086,7 +4090,8 @@ void daB_DS_c::executeBattle2Dead() {
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);
dusk::mods::item_check_enqueue_deferred(
ITEM_CHECK_DUNGEON_REWARD_ARBITERS, dItemNo_NONE_e);
#endif
/* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */
dComIfGs_onEventBit(0x2010);
+7
View File
@@ -1090,6 +1090,13 @@ inline int daDemo00_c::execute() {
break;
}
case 1:
#if TARGET_PC
// Suppress demo22_01's M_077 write. On Dusklight, the transform flag
// is set by the Shadow Crystal item.
if (dSv_event_flag_c::saveBitLabels[sp0E] == dSv_event_flag_c::M_077) {
break;
}
#endif
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[sp0E]);
break;
+14 -2
View File
@@ -449,7 +449,7 @@ int daDitem_c::Delete() {
endInsectEffect();
mSound.deleteObject();
return DeleteBase(dItem_data::getArcName(m_itemNo));
return DeleteBase(dItem_data::getArcName(DUSK_IF_ELSE(getDisplayItemNo(), m_itemNo)));
}
static int daDitem_Delete(daDitem_c* i_this) {
@@ -461,8 +461,16 @@ int daDitem_c::create() {
m_itemNo = daDitem_prm::getNo(this);
#if TARGET_PC
const auto [item, displayItem] = dusk::mods::item_check_resolve(mItemGiveTag, m_itemNo, this);
m_itemNo = item;
setDisplayItemNo(displayItem);
const char* arc_name = dItem_data::getArcName(displayItem);
s16 bmd_name = dItem_data::getBmdName(displayItem);
#else
const char* arc_name = dItem_data::getArcName(m_itemNo);
s16 bmd_name = dItem_data::getBmdName(m_itemNo);
#endif
if (bmd_name < 0 || arc_name == NULL) {
if (bmd_name == 0) {
@@ -476,9 +484,13 @@ int daDitem_c::create() {
}
m_itemNo = dItemNo_GREEN_RUPEE_e;
#if TARGET_PC
setDisplayItemNo(dItemNo_NONE_e);
#endif
}
int phase_state = dComIfG_resLoad(&mPhase, dItem_data::getArcName(m_itemNo));
int phase_state = dComIfG_resLoad(
&mPhase, dItem_data::getArcName(DUSK_IF_ELSE(getDisplayItemNo(), m_itemNo)));
if (phase_state == cPhs_COMPLEATE_e) {
if (!fopAcM_entrySolidHeap(this, CheckItemCreateHeap, 0x80003390)) {
return cPhs_ERROR_e;
+15 -8
View File
@@ -727,19 +727,26 @@ void daE_HP_c::executeDead() {
}
#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
const auto itemCheck = dusk::mods::item_check_commit(
dusk::mods::item_give_tag_poe(bitSw), dItemNo_POU_SPIRIT_e, this);
mItemCheckHandled = true;
if (itemCheck.itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(itemCheck, this);
} else {
daAlink_getAlinkActorClass()->procWolfAtnActorMoveInit();
dusk::mods::item_check_enqueue(itemCheck, dusk::mods::ItemGiveMode::ForcedDemo);
}
#else
dComIfGs_addPohSpiritNum();
#endif
dComIfGs_addPohSpiritNum();
field_0x784 = -1;
if (dComIfGs_getPohSpiritNum() == 20 IF_DUSK(&&!mItemCheckOverridden)) {
#if !TARGET_PC
if (dComIfGs_getPohSpiritNum() == 20) {
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[0x1c9]);
}
#endif
movemode++;
}
@@ -759,7 +766,7 @@ void daE_HP_c::executeDead() {
field_0x788 = 1;
}
}
} else if (field_0x788 != 0 IF_DUSK(|| mItemCheckOverridden)) {
} else if (field_0x788 != 0 IF_DUSK(|| mItemCheckHandled)) {
fopAcM_createDisappear(this, &current.pos, 8, 3, 0xff);
fopAcM_delete(this);
} else {
+12 -12
View File
@@ -1132,20 +1132,14 @@ static void e_po_dead(e_po_class* i_this) {
camera_player->mCamera.Start();
camera_player->mCamera.SetTrimSize(0);
dComIfGp_event_reset();
#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 !TARGET_PC
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) {
@@ -1274,10 +1268,16 @@ 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));
const auto itemCheck = dusk::mods::item_check_commit(
dusk::mods::item_give_tag_poe(i_this->BitSW), dItemNo_POU_SPIRIT_e, a_this);
if (itemCheck.itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(itemCheck, a_this);
i_this->field_0x75B = 1;
} else {
i_this->field_0x75C = fopAcM_createItemForPresentDemo(
&a_this->current.pos, itemCheck.itemNo, 0, -1, -1, NULL, NULL,
itemCheck.tag);
}
#else
i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, 0xE0, 0,
-1, -1, NULL, NULL);
+16 -3
View File
@@ -16,6 +16,10 @@
#include "f_op/f_op_camera_mng.h"
#include <cstring>
#if TARGET_PC
#include "mods/items.h"
#endif
class daE_RDB_HIO_c : public JORReflexible {
public:
daE_RDB_HIO_c();
@@ -1250,9 +1254,18 @@ static void demo_camera(e_rdb_class* i_this) {
}
if (iVar1 != 0) {
daPy_getPlayerActorClass()->changeDemoMode(11,
DUSK_ITEM_CHECK_EXPR("bulblin_key:D_MN09", dItemNo_SMALL_KEY_e, &i_this->enemy), 0,
0);
#if TARGET_PC
const auto itemCheck = dusk::mods::item_check_commit(
ITEM_CHECK_BULBLIN_KEY, dItemNo_SMALL_KEY_e, &i_this->enemy);
if (itemCheck.itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(itemCheck, &i_this->enemy);
}
daPy_getPlayerActorClass()->changeDemoMode(
11, itemCheck.itemNo == dItemNo_NONE_e ? 0 : itemCheck.itemNo,
itemCheck.itemNo == dItemNo_NONE_e ? 0 : static_cast<int>(itemCheck.tag), 0);
#else
daPy_getPlayerActorClass()->changeDemoMode(11, dItemNo_SMALL_KEY_e, 0, 0);
#endif
i_this->mDemoMode = 12;
i_this->field_0x10aa = 0;
i_this->field_0xfe5 = 1;
+5 -5
View File
@@ -242,21 +242,21 @@ BOOL daItemBase_c::chkFlag(int i_flag) {
}
s8 daItemBase_c::getTevFrm() {
return dItem_data::getTevFrm(m_itemNo);
return dItem_data::getTevFrm(DUSK_IF_ELSE(getDisplayItemNo(), m_itemNo));
}
s8 daItemBase_c::getBtpFrm() {
return dItem_data::getBtpFrm(m_itemNo);
return dItem_data::getBtpFrm(DUSK_IF_ELSE(getDisplayItemNo(), m_itemNo));
}
u8 daItemBase_c::getShadowSize() {
return dItem_data::getShadowSize(m_itemNo);
return dItem_data::getShadowSize(DUSK_IF_ELSE(getDisplayItemNo(), m_itemNo));
}
u8 daItemBase_c::getCollisionH() {
return dItem_data::getH(m_itemNo);
return dItem_data::getH(DUSK_IF_ELSE(getDisplayItemNo(), m_itemNo));
}
u8 daItemBase_c::getCollisionR() {
return dItem_data::getR(m_itemNo);
return dItem_data::getR(DUSK_IF_ELSE(getDisplayItemNo(), m_itemNo));
}
+32
View File
@@ -27,8 +27,10 @@
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#include "dusk/mods/item.hpp"
#include "dusk/settings.h"
#include "dusk/version.hpp"
#include "mods/items.h"
#endif
class dmg_rod_HIO_c : public JORReflexible {
@@ -2955,7 +2957,17 @@ static void lure_heart(dmg_rod_class* i_this) {
if (obj_life != NULL) {
fopAcM_delete(obj_life);
fopAcM_onItem(obj_life, 0x80);
#if TARGET_PC
const auto itemCheck = dusk::mods::item_check_commit(
ITEM_CHECK_FISHING_HEART_PIECE, dItemNo_KAKERA_HEART_e, actor);
if (itemCheck.itemNo == dItemNo_KAKERA_HEART_e) {
execItemGet(dItemNo_KAKERA_HEART_e, itemCheck.tag, actor);
} else if (itemCheck.itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(itemCheck, actor);
}
#else
execItemGet(dItemNo_KAKERA_HEART_e);
#endif
u8 eventReg = dComIfGs_getEventReg(0xECFF);
eventReg |= (u8)0x40;
dComIfGs_setEventReg(0xECFF, eventReg);
@@ -4090,7 +4102,15 @@ static void uki_catch(dmg_rod_class* i_this) {
} else if (mgfish->mCaughtType == MG_CATCH_BIN) {
i_this->msgflow.init(actor, 0x139A, 0, NULL);
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[468]);
#if TARGET_PC
const auto itemCheck = dusk::mods::item_check_commit(
ITEM_CHECK_FISHING_BOTTLE, dItemNo_EMPTY_BOTTLE_e, actor);
if (itemCheck.itemNo == dItemNo_EMPTY_BOTTLE_e) {
dComIfGs_setEmptyBottle();
}
#else
dComIfGs_setEmptyBottle();
#endif
} else if (mgfish->mCaughtType == MG_CATCH_KN) {
i_this->msgflow.init(actor, 0x139C, 0, NULL);
} else if (mgfish->mCaughtType == MG_CATCH_ED) {
@@ -4167,6 +4187,18 @@ static void uki_catch(dmg_rod_class* i_this) {
if (mgfish->mCaughtType == MG_CATCH_LH) {
dComIfGp_setItemRupeeCount(10.0f + cM_rndF(40.9f));
}
#if TARGET_PC
else if (mgfish->mCaughtType == MG_CATCH_BIN)
{
const auto itemCheck = dusk::mods::item_check_commit(
ITEM_CHECK_FISHING_BOTTLE, dItemNo_EMPTY_BOTTLE_e, actor);
if (itemCheck.itemNo == dItemNo_EMPTY_BOTTLE_e ||
itemCheck.itemNo == dItemNo_NONE_e)
{
dusk::mods::item_check_complete(itemCheck, actor);
}
}
#endif
} else {
dComIfGs_addFishNum(fish_kind);
if (i_this->field_0x14c0 != 0) {
+6 -2
View File
@@ -1806,17 +1806,21 @@ int daNpcChin_c::_Evt_GameSucceed_CutMain(const int& param_0) {
#if TARGET_PC
const char* itemCheckName = nullptr;
u32 itemGiveTag = 0;
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);
const auto itemCheck =
dusk::mods::item_check_commit(itemCheckName, itemId1, this);
itemId1 = itemCheck.itemNo;
itemGiveTag = itemCheck.tag;
}
#endif
fpc_ProcID itemId2 = fopAcM_createItemForPresentDemo(&current.pos, itemId1, 0, -1, -1,
0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
0, 0 IF_DUSK_ARG(itemGiveTag));
if (itemId2 != -1) {
dComIfGp_event_setItemPartnerId(itemId2);
}
+11 -6
View File
@@ -12,6 +12,9 @@
#include "d/actor/d_a_npc_gra.h"
#include "d/actor/d_a_tag_gra.h"
#include "Z2AudioLib/Z2Instances.h"
#if TARGET_PC
#include "mods/items.h"
#endif
#include <cstring>
DUSK_GAME_DATA const daNpc_grA_HIOParam daNpc_grA_Param_c::m = {
@@ -1323,7 +1326,7 @@ BOOL daNpc_grA_c::isDelete() {
return FALSE;
}
fopAcM_createItemForBoss(&current.pos, 0x21, fopAcM_GetRoomNo(this), NULL, NULL, 0.0f, 0.0f,
0x80);
0x80 IF_DUSK_ARG(ITEM_CHECK_GORON_SPRINGWATER_RUSH));
return TRUE;
}
return TRUE;
@@ -2812,7 +2815,7 @@ BOOL daNpc_grA_c::ECut_carrySpaWater(int i_staffID) {
c.y += 200.0f;
csXyz c2(0, fopAcM_searchPlayerAngleY(this), 0);
fopAcM_createItemForBoss(&c, 0x21, fopAcM_GetRoomNo(this), &c2, NULL, 0.0f, 20.0f,
0x80);
0x80 IF_DUSK_ARG(ITEM_CHECK_GORON_SPRINGWATER_RUSH));
} break;
}
}
@@ -4019,14 +4022,16 @@ BOOL daNpc_grA_c::talk(void*) {
if (r26 && talkProc(NULL, TRUE, NULL)) {
if (mFlow.getEventId(&sp8) == 1) {
#if TARGET_PC
const char* itemCheckName = nullptr;
u32 itemGiveTag = 0;
if (sp8 == dItemNo_BOMB_IN_BAG_e) {
itemCheckName = "goron_reward:F_SP113";
sp8 = dusk::mods::item_check(itemCheckName, sp8, this);
const auto itemCheck =
dusk::mods::item_check_commit("goron_reward:F_SP113", sp8, this);
sp8 = itemCheck.itemNo;
itemGiveTag = itemCheck.tag;
}
#endif
field_0x1480 = fopAcM_createItemForPresentDemo(&current.pos, sp8, 0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
NULL IF_DUSK_ARG(itemGiveTag));
if (field_0x1480 != fpcM_ERROR_PROCESS_ID_e) {
s16 r25 = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xff);
dComIfGp_getEvent()->reset(this);
+5 -3
View File
@@ -1495,9 +1495,11 @@ int daNpcIns_c::talk(void* param_1) {
#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);
const auto itemCheck = dusk::mods::item_check_commit(
dusk::mods::item_give_tag_bug(s_givenInsectId), itemNo & 0xFF,
this);
itemNo = itemCheck.itemNo;
itemGiveTag = itemCheck.tag;
s_givenInsectId = dItemNo_NONE_e;
}
#endif
+6 -4
View File
@@ -1182,15 +1182,17 @@ int daNpc_Kkri_c::talk(void*) {
case 1:
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
#if TARGET_PC
const char* itemCheckName = nullptr;
u32 itemGiveTag = 0;
if (item_no == dItemNo_OIL_BOTTLE3_e) {
itemCheckName = "coro_bottle";
item_no = dusk::mods::item_check(itemCheckName, item_no, this);
const auto itemCheck =
dusk::mods::item_check_commit("coro_bottle", item_no, this);
item_no = itemCheck.itemNo;
itemGiveTag = itemCheck.tag;
}
#endif
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, item_no,
0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
NULL IF_DUSK_ARG(itemGiveTag));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
+6 -4
View File
@@ -1192,14 +1192,16 @@ int daNpc_myna2_c::ECut_gameGoalSuccess(int i_staffId) {
int itemNo = 0;
if (mFlow.getEventId(&itemNo) == 1) {
#if TARGET_PC
const char* itemCheckName = nullptr;
u32 itemGiveTag = 0;
if (itemNo == dItemNo_KAKERA_HEART_e) {
itemCheckName = "plumm_minigame_reward";
itemNo = dusk::mods::item_check(itemCheckName, itemNo, this);
const auto itemCheck =
dusk::mods::item_check_commit("plumm_minigame_reward", itemNo, this);
itemNo = itemCheck.itemNo;
itemGiveTag = itemCheck.tag;
}
#endif
mItemPid = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
NULL IF_DUSK_ARG(itemGiveTag));
}
break;
}
+6 -2
View File
@@ -960,17 +960,21 @@ int daNpc_Pouya_c::cutHaveFavorToAsk(int param_0) {
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
#if TARGET_PC
const char* itemCheckName = nullptr;
u32 itemGiveTag = 0;
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);
const auto itemCheck =
dusk::mods::item_check_commit(itemCheckName, local_64, this);
local_64 = itemCheck.itemNo;
itemGiveTag = itemCheck.tag;
}
#endif
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, local_64, 0,
-1, -1, 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
-1, -1, 0, 0 IF_DUSK_ARG(itemGiveTag));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
field_0xfce = 1;
+6 -2
View File
@@ -2611,17 +2611,21 @@ BOOL daNpc_zrA_c::ECut_thanksBlast(int i_staffID) {
if (mFlow.getEventId(&item_id) == 1) {
#if TARGET_PC
const char* itemCheckName = nullptr;
u32 itemGiveTag = 0;
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);
const auto itemCheck =
dusk::mods::item_check_commit(itemCheckName, item_id, this);
item_id = itemCheck.itemNo;
itemGiveTag = itemCheck.tag;
}
#endif
mItemID = fopAcM_createItemForPresentDemo(&current.pos, item_id, 0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
NULL IF_DUSK_ARG(itemGiveTag));
}
field_0x9eb = true;
break;
+72 -19
View File
@@ -14,6 +14,11 @@
#include "d/actor/d_a_obj_ystone.h"
#include <cstring>
#if TARGET_PC
#include "dusk/mods/item.hpp"
#include "mods/items.h"
#endif
static DUSK_CONST char* l_arcName = "ef_Portal";
static char const* l_clearEvName[9] = {
@@ -77,6 +82,27 @@ static int getNowLevel() {
return -1;
}
#if TARGET_PC
static const char* dungeon_reward_check_name(int level) {
switch (level) {
case 0:
return ITEM_CHECK_DUNGEON_REWARD_FOREST;
case 1:
return ITEM_CHECK_DUNGEON_REWARD_GORON;
case 2:
return ITEM_CHECK_DUNGEON_REWARD_LAKEBED;
case 4:
return ITEM_CHECK_DUNGEON_REWARD_SNOWPEAK;
case 5:
return ITEM_CHECK_DUNGEON_REWARD_TIME;
case 6:
return ITEM_CHECK_DUNGEON_REWARD_CITY;
default:
return nullptr;
}
}
#endif
void daObjBossWarp_c::initBaseMtx() {
scale.y = 0.15f;
mpModel->setBaseScale(scale);
@@ -485,6 +511,9 @@ int daObjBossWarp_c::demoProc() {
player->onSceneChangeArea(scene, 0xff, 0);
break;
case 4: // STONE_FALL
#if TARGET_PC
{
#endif
if (ystone != NULL) {
mYstonePos = ystone->current.pos;
if (getNowLevel() < 3) {
@@ -506,28 +535,52 @@ int daObjBossWarp_c::demoProc() {
mDoMtx_stack_c::multVec(&mYstoneTargetPos, &mYstoneTargetPos);
mYstonePos.x = mYstoneTargetPos.x;
mYstonePos.z = mYstoneTargetPos.z;
switch (getNowLevel()) {
case 0:
dComIfGs_onCollectCrystal(0);
break;
case 1:
dComIfGs_onCollectCrystal(1);
break;
case 2:
dComIfGs_onCollectCrystal(2);
break;
case 4:
dComIfGs_onCollectMirror(1);
break;
case 5:
dComIfGs_onCollectMirror(2);
break;
case 6:
dComIfGs_onCollectMirror(3);
break;
#if TARGET_PC
int level = getNowLevel();
const char* checkName = dungeon_reward_check_name(level);
dusk::mods::ItemCheckResult itemCheck{};
bool applyVanillaReward = true;
if (checkName != nullptr) {
itemCheck = dusk::mods::item_check_commit(checkName, dItemNo_NONE_e, this);
applyVanillaReward = itemCheck.itemNo == dItemNo_NONE_e;
}
if (applyVanillaReward) {
switch (level) {
#else
switch (getNowLevel()) {
#endif
case 0:
dComIfGs_onCollectCrystal(0);
break;
case 1:
dComIfGs_onCollectCrystal(1);
break;
case 2:
dComIfGs_onCollectCrystal(2);
break;
case 4:
dComIfGs_onCollectMirror(1);
break;
case 5:
dComIfGs_onCollectMirror(2);
break;
case 6:
dComIfGs_onCollectMirror(3);
break;
}
#if TARGET_PC
if (checkName != nullptr) {
dusk::mods::item_check_complete(itemCheck, this);
}
} else {
dusk::mods::item_check_enqueue(itemCheck, dusk::mods::ItemGiveMode::Silent);
}
#endif
mCounter = 0;
break;
#if TARGET_PC
}
#endif
case 5: // STONE_MIDNA
mCounter = 0;
break;
+38 -15
View File
@@ -158,8 +158,8 @@ void daItem_c::CreateInit() {
mCcCyl.SetCoHitCallback(itemGetCoCallBack);
mCcCyl.SetTgHitCallback(itemGetTgCallBack);
f32 cylHeight = dItem_data::getH(m_itemNo);
f32 cylRadius = dItem_data::getR(m_itemNo);
f32 cylHeight = DUSK_IF_ELSE(getCollisionH(), dItem_data::getH(m_itemNo));
f32 cylRadius = DUSK_IF_ELSE(getCollisionR(), dItem_data::getR(m_itemNo));
if (scale.x > 1.0f) {
cylHeight *= scale.x;
@@ -267,12 +267,13 @@ int daItem_c::_daItem_create() {
#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));
const auto [item, displayItem] =
dusk::mods::item_check_resolve(mItemGiveTag, mOriginalItemNo, this);
mItemOverridden = item != mOriginalItemNo;
setDisplayItemNo(displayItem);
if (mItemOverridden) {
fopAcM_SetParam(this, (params & 0xFFFFFF00) | resolvedItem);
fopAcM_SetParam(this, (params & 0xFFFFFF00) | item);
}
#endif
field_0x95d = true;
@@ -287,7 +288,11 @@ int daItem_c::_daItem_create() {
BOOL flag = dItem_data::chkFlag(m_itemNo, 2);
#if DEBUG
#if TARGET_PC
if (dItem_fieldModelArc(getDisplayItemNo()) == NULL) {
#else
if (dItem_data::getFieldArc(m_itemNo) == NULL) {
#endif
// "Item Num<%d>: No Resource Archive specified!!\n"
OS_REPORT_ERROR("アイテム番号<%d>:リソースのアーカイブ指定がありません!!\n", m_itemNo);
return cPhs_ERROR_e;
@@ -319,10 +324,22 @@ int daItem_c::_daItem_create() {
if (flag) {
CreateInit();
} else {
#if TARGET_PC
const u8 displayItemNo = getDisplayItemNo();
phase_state = dComIfG_resLoad(&mPhase, dItem_fieldModelArc(displayItemNo));
#else
phase_state = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(m_itemNo));
#endif
if (phase_state == cPhs_COMPLEATE_e) {
#if TARGET_PC
const bool useModelFallback = dItem_data::getFieldArc(displayItemNo) == NULL;
if (!fopAcM_entrySolidHeap(this,
useModelFallback ? CheckItemCreateHeap : CheckFieldItemCreateHeap,
useModelFallback ? 0x4000 : dItem_data::getFieldHeapSize(displayItemNo)))
#else
if (!fopAcM_entrySolidHeap(this, CheckFieldItemCreateHeap,
dItem_data::getFieldHeapSize(m_itemNo)))
#endif
{
return cPhs_ERROR_e;
}
@@ -339,7 +356,7 @@ int daItem_c::_daItem_execute() {
CountTimer();
eyePos = current.pos;
eyePos.y += (f32)dItem_data::getH(m_itemNo) / 2;
eyePos.y += (f32)DUSK_IF_ELSE(getCollisionH(), dItem_data::getH(m_itemNo)) / 2;
attention_info.position = current.pos;
@@ -412,7 +429,11 @@ int daItem_c::_daItem_delete() {
mSparkleEmtr.remove();
}
#if TARGET_PC
DeleteBase(dItem_fieldModelArc(getDisplayItemNo()));
#else
DeleteBase(dItem_data::getFieldArc(m_itemNo));
#endif
return 1;
}
@@ -520,9 +541,10 @@ void daItem_c::procInitGetDemoEvent() {
#if TARGET_PC
const u8 displayItemNo = m_itemNo;
if (mItemOverridden) {
m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this);
}
const auto itemCheck = dusk::mods::item_check_commit(mItemGiveTag, mOriginalItemNo, this);
m_itemNo = itemCheck.itemNo;
mItemGiveTag = itemCheck.tag;
mItemOverridden = m_itemNo != mOriginalItemNo;
#endif
m_item_id = fopAcM_createItemForTrBoxDemo(
&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(mItemGiveTag));
@@ -581,8 +603,8 @@ void daItem_c::procInitBoomerangCarry() {
scale = mItemScale;
mBoomerangMove.initOffset(&current.pos);
u8 height = dItem_data::getH(m_itemNo);
u8 radius = dItem_data::getR(m_itemNo);
u8 height = DUSK_IF_ELSE(getCollisionH(), dItem_data::getH(m_itemNo));
u8 radius = DUSK_IF_ELSE(getCollisionR(), dItem_data::getR(m_itemNo));
mCcCyl.SetR((f32)radius * 2.0f);
mCcCyl.SetH((f32)height * 2.0f);
mCcCyl.OnCoSPrmBit(1);
@@ -907,9 +929,10 @@ 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);
}
const auto itemCheck = dusk::mods::item_check_commit(mItemGiveTag, mOriginalItemNo, this);
m_itemNo = itemCheck.itemNo;
mItemGiveTag = itemCheck.tag;
mItemOverridden = m_itemNo != mOriginalItemNo;
#endif
switch (m_itemNo) {
#if TARGET_PC
+45 -12
View File
@@ -6,6 +6,7 @@
#include "d/dolzel_rel.h" // IWYU pragma: keep
#include "d/actor/d_a_obj_life_container.h"
#include "d/d_a_itembase_static.h"
#include "d/d_com_inf_game.h"
#include "d/actor/d_a_player.h"
#include "d/d_item_data.h"
@@ -87,8 +88,8 @@ int daObjLife_c::Create() {
mCcCyl.SetStts(&mCcStts);
mCcCyl.SetCoHitCallback(lifeGetCoCallBack);
mCcCyl.SetTgHitCallback(lifeGetTgCallBack);
mCcCyl.SetR(dItem_data::getR(m_itemNo));
mCcCyl.SetH(dItem_data::getH(m_itemNo));
mCcCyl.SetR(DUSK_IF_ELSE(getCollisionR(), dItem_data::getR(m_itemNo)));
mCcCyl.SetH(DUSK_IF_ELSE(getCollisionH(), dItem_data::getH(m_itemNo)));
fopAcM_SetCullSize(this, fopAc_CULLSPHERE_0_e);
fopAcM_SetGravity(this, -3.2f);
@@ -151,16 +152,30 @@ int daObjLife_c::create() {
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;
const auto [item, displayItem] =
dusk::mods::item_check_resolve(mItemGiveTag, mOriginalItemNo, this);
setDisplayItemNo(displayItem);
mItemOverridden = item != mOriginalItemNo;
if (mItemOverridden) {
fopAcM_SetParam(this, (params & 0xFFFFFF00) | resolvedItem);
fopAcM_SetParam(this, (params & 0xFFFFFF00) | item);
}
} else if (mGoldenWolfItem) {
mOriginalItemNo = mItemGiveOriginalNo;
mItemGiveTag = dusk::mods::item_give_tag_golden_wolf(static_cast<u16>(field_0x938));
const auto [item, displayItem] =
dusk::mods::item_check_resolve(mItemGiveTag, mOriginalItemNo, this);
setDisplayItemNo(displayItem);
mItemOverridden = item != mOriginalItemNo;
if (item != parameterItemNo) {
fopAcM_SetParam(this, (params & 0xFFFFFF00) | item);
}
} else {
mOriginalItemNo = mItemGiveOriginalNo;
mItemOverridden = parameterItemNo != mItemGiveOriginalNo;
setDisplayItemNo(
dusk::mods::item_check_resolve(mItemGiveTag, parameterItemNo, this)
.display_item);
mItemOverridden = parameterItemNo != mOriginalItemNo;
}
mOverrideHover =
mItemOverridden &&
@@ -184,11 +199,24 @@ int daObjLife_c::create() {
return cPhs_ERROR_e;
}
#if TARGET_PC
// Resolved items may have no field model; display their get-demo model instead.
const u8 displayItemNo = getDisplayItemNo();
const bool useGetModel = dItem_data::getFieldArc(displayItemNo) == NULL;
int phase_state = dComIfG_resLoad(&mPhase, dItem_fieldModelArc(displayItemNo));
if (phase_state == cPhs_COMPLEATE_e) {
if (!fopAcM_entrySolidHeap(
this, useGetModel ? CheckItemCreateHeap : CheckFieldItemCreateHeap, 0x4000))
{
return cPhs_ERROR_e;
}
#else
int phase_state = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(m_itemNo));
if (phase_state == cPhs_COMPLEATE_e) {
if (!fopAcM_entrySolidHeap(this, CheckFieldItemCreateHeap, 0x4000)) {
return cPhs_ERROR_e;
}
#endif
if (!Create()) {
return cPhs_ERROR_e;
@@ -325,9 +353,10 @@ int daObjLife_c::initActionOrderGetDemo() {
#if TARGET_PC
const u8 displayItemNo = m_itemNo;
if (mItemOverridden) {
m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this);
}
const auto itemCheck = dusk::mods::item_check_commit(mItemGiveTag, mOriginalItemNo, this);
m_itemNo = itemCheck.itemNo;
mItemGiveTag = itemCheck.tag;
mItemOverridden = m_itemNo != mOriginalItemNo;
#endif
mItemId = fopAcM_createItemForTrBoxDemo(
&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(mItemGiveTag));
@@ -397,8 +426,8 @@ int daObjLife_c::actionInitBoomerangCarry() {
mCcCyl.OnTgSPrmBit(1);
mCcCyl.OnCoSPrmBit(1);
f32 height = dItem_data::getH(m_itemNo) * 4.0f;
f32 radius = dItem_data::getR(m_itemNo) * 4.0f;
f32 height = DUSK_IF_ELSE(getCollisionH(), dItem_data::getH(m_itemNo)) * 4.0f;
f32 radius = DUSK_IF_ELSE(getCollisionR(), dItem_data::getR(m_itemNo)) * 4.0f;
mCcCyl.SetR(radius);
mCcCyl.SetH(height);
@@ -519,7 +548,11 @@ int daObjLife_c::_delete() {
endEffect00();
endEffect02();
#if TARGET_PC
DeleteBase(dItem_fieldModelArc(getDisplayItemNo()));
#else
DeleteBase(dItem_data::getFieldArc(m_itemNo));
#endif
return 1;
}
+40 -1
View File
@@ -1,6 +1,6 @@
/**
* @file d_a_obj_master_sword.cpp
*
*
*/
#include "d/dolzel_rel.h" // IWYU pragma: keep
@@ -10,6 +10,12 @@
#include "d/d_com_inf_game.h"
#include "d/d_meter2_info.h"
#if TARGET_PC
#include "d/d_item.h"
#include "dusk/mods/item.hpp"
#include "mods/items.h"
#endif
DUSK_GAME_DATA daObjMasterSword_Attr_c const daObjMasterSword_c::mAttr = {1.0f};
void daObjMasterSword_c::initBaseMtx() {
@@ -145,7 +151,11 @@ void daObjMasterSword_c::create_init() {
int daObjMasterSword_c::create() {
fopAcM_ct(this, daObjMasterSword_c);
#if TARGET_PC
if (dComIfGs_isEventBit(dSv_event_flag_c::F_0264)) {
#else
if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[getFlagNo()])) {
#endif
return cPhs_ERROR_e;
}
@@ -186,12 +196,41 @@ int daObjMasterSword_c::execute() {
mBrk.play();
if (dComIfGs_isTmpBit(dSv_event_tmp_flag_c::tempBitLabels[73])) {
#if TARGET_PC
const auto masterSword = dusk::mods::item_check_commit(
ITEM_CHECK_MASTER_SWORD, dItemNo_MASTER_SWORD_e, this);
if (masterSword.itemNo == dItemNo_MASTER_SWORD_e) {
dComIfGs_onItemFirstBit(dItemNo_MASTER_SWORD_e);
dMeter2Info_setSword(dItemNo_MASTER_SWORD_e, false);
dComIfGs_setSelectEquipSword(dItemNo_MASTER_SWORD_e);
dusk::mods::item_check_complete(masterSword, this);
} else if (masterSword.itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(masterSword, this);
} else {
dusk::mods::item_check_enqueue(masterSword, dusk::mods::ItemGiveMode::Demo);
}
dComIfGp_setItemLifeCount(dComIfGs_getMaxLife(), 0);
const auto shadowCrystal = dusk::mods::item_check_commit(
ITEM_CHECK_SHADOW_CRYSTAL, dItemNo_SHADOW_CRYSTAL_e, this);
if (shadowCrystal.itemNo == dItemNo_SHADOW_CRYSTAL_e) {
execItemGet(shadowCrystal.itemNo, shadowCrystal.tag, this);
} else if (shadowCrystal.itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(shadowCrystal, this);
} else {
dusk::mods::item_check_enqueue(shadowCrystal, dusk::mods::ItemGiveMode::Demo);
}
dComIfGs_onEventBit(dSv_event_flag_c::F_0264);
#else
dComIfGs_onItemFirstBit(dItemNo_MASTER_SWORD_e);
dMeter2Info_setSword(dItemNo_MASTER_SWORD_e, false);
dComIfGs_setSelectEquipSword(dItemNo_MASTER_SWORD_e);
dComIfGp_setItemLifeCount(dComIfGs_getMaxLife(), 0);
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[getFlagNo()]);
#endif
fopAcM_delete(this);
}
+1 -1
View File
@@ -109,7 +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);
DUSK_ITEM_CHECK_PREVIEW("ordon_shield", m_itemNo, this);
if (fopAcM_isSwitch(this, getSwBit2())) {
OS_REPORT("木の盾:もう取ったので出ません\n");
return cPhs_ERROR_e;
+53 -7
View File
@@ -1,6 +1,6 @@
/**
* @file d_a_obj_smallkey.cpp
*
*
*/
#include "d/dolzel_rel.h" // IWYU pragma: keep
@@ -119,9 +119,9 @@ int daKey_c::Create() {
mCcCyl.SetStts(&mCcStts);
mCcCyl.SetCoHitCallback(keyGetCoCallBack);
mCcCyl.SetTgHitCallback(keyGetTgCallBack);
mCcCyl.SetR(dItem_data::getR(m_itemNo));
mCcCyl.SetH(dItem_data::getH(m_itemNo));
mCcCyl.SetR(DUSK_IF_ELSE(getCollisionR(), dItem_data::getR(m_itemNo)));
mCcCyl.SetH(DUSK_IF_ELSE(getCollisionH(), dItem_data::getH(m_itemNo)));
fopAcM_SetCullSize(this, fopAc_CULLSPHERE_0_e);
if (getSwNo() == 0xFF) {
@@ -155,7 +155,21 @@ int daKey_c::create() {
mIsPrmInit = TRUE;
}
#if TARGET_PC
mItemGiveTag = dusk::mods::item_give_tag_freestanding(getSaveBitNo());
const auto [item, displayItem] =
dusk::mods::item_check_resolve(mItemGiveTag, dItemNo_SMALL_KEY_e, this);
m_itemNo = item;
if (m_itemNo == dItemNo_NONE_e) {
// Keep the pickup visible until the empty check is collected.
m_itemNo = dItemNo_SMALL_KEY_e;
setDisplayItemNo(dItemNo_NONE_e);
} else {
setDisplayItemNo(displayItem);
}
#else
m_itemNo = dItemNo_SMALL_KEY_e;
#endif
if (strcmp(dComIfGp_getStartStageName(), "F_SP118") == 0) {
OS_REPORT(" SMKEY 0\n");
@@ -170,9 +184,21 @@ int daKey_c::create() {
return cPhs_ERROR_e;
}
#if TARGET_PC
const u8 displayItemNo = getDisplayItemNo();
int phase_state = dComIfG_resLoad(&mPhase, dItem_fieldModelArc(displayItemNo));
#else
int phase_state = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(m_itemNo));
#endif
if (phase_state == cPhs_COMPLEATE_e) {
#if TARGET_PC
const bool useModelFallback = dItem_data::getFieldArc(displayItemNo) == NULL;
if (!fopAcM_entrySolidHeap(
this, useModelFallback ? CheckItemCreateHeap : CheckFieldItemCreateHeap, 0x4000))
{
#else
if (!fopAcM_entrySolidHeap(this, CheckFieldItemCreateHeap, 0x840)) {
#endif
return cPhs_ERROR_e;
}
@@ -317,10 +343,26 @@ int daKey_c::initActionOrderGetDemo() {
hide();
effectStop();
#if TARGET_PC
const auto itemCheck =
dusk::mods::item_check_commit(mItemGiveTag, dItemNo_SMALL_KEY_e, this);
m_itemNo = itemCheck.itemNo;
if (m_itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(itemCheck, this);
dComIfGs_onTbox(getSaveBitNo());
dTres_c::offStatus(getSaveBitNo(), 1);
dComIfGp_event_reset();
fopAcM_delete(this);
return 1;
}
#endif
fopAcM_orderItemEvent(this, 0, 0);
eventInfo.onCondition(8);
mItemId = fopAcM_createItemForTrBoxDemo(&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL);
mItemId = fopAcM_createItemForTrBoxDemo(
&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL,
NULL IF_DUSK_ARG(itemCheck.tag));
JUT_ASSERT(699, mItemId != fpcM_ERROR_PROCESS_ID_e);
setStatus(STATUS_ORDER_GET_DEMO_e);
@@ -387,8 +429,8 @@ int daKey_c::actionInitBoomerangCarry() {
mCcCyl.OnTgSPrmBit(1);
mCcCyl.OnCoSPrmBit(1);
u8 height = dItem_data::getH(m_itemNo);
u8 radius = dItem_data::getR(m_itemNo);
u8 height = DUSK_IF_ELSE(getCollisionH(), dItem_data::getH(m_itemNo));
u8 radius = DUSK_IF_ELSE(getCollisionR(), dItem_data::getR(m_itemNo));
mCcCyl.SetR(4.0f * radius);
mCcCyl.SetH(4.0f * height);
@@ -527,7 +569,11 @@ int daKey_c::draw() {
int daKey_c::_delete() {
effectStop();
#if TARGET_PC
DeleteBase(dItem_fieldModelArc(getDisplayItemNo()));
#else
DeleteBase(dItem_data::getFieldArc(m_itemNo));
#endif
return 1;
}
+1 -1
View File
@@ -37,7 +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);
DUSK_ITEM_CHECK_PREVIEW("ordon_sword", m_itemNo, this);
if (fopAcM_isItem(this, getItemBit())) {
return cPhs_ERROR_e;
}
+1 -1
View File
@@ -32,7 +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);
DUSK_ITEM_CHECK_PREVIEW("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)) {
+11 -9
View File
@@ -105,17 +105,19 @@ const char* daShopItem_c::getShopArcname() {
#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;
mItemGiveTag = dusk::mods::item_give_tag_shop(mItemGiveOriginalNo);
const auto [_, displayItem] =
dusk::mods::item_check_resolve(mItemGiveTag, mItemGiveOriginalNo, this);
mItemOverridden = displayItem != 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.mArcName = dItem_data::getArcName(displayItem);
mOverrideData.mBmdName = dItem_data::getBmdName(displayItem);
mOverrideData.mBtkName = dItem_data::getBtkName(displayItem);
mOverrideData.mBckName = dItem_data::getBckName(displayItem);
mOverrideData.mBrkName = dItem_data::getBrkName(displayItem);
mOverrideData.mBtpName = dItem_data::getBtpName(displayItem);
mOverrideData.mTevFrm = dItem_data::getTevFrm(displayItem);
mOverrideData.mBtpFrm = -1;
mOverrideData.mFlag = static_cast<u32>(-1);
mOverrideData.mOffsetY = mShopItemID == SHOP_ITEMNO_ARMOR ? 60.0f : 15.0f;
+6 -3
View File
@@ -363,10 +363,13 @@ int daTagStatue_c::demoProc() {
}
#if TARGET_PC
item = dusk::mods::item_check_sky_character(item & 0xFF, this);
const auto itemCheck = dusk::mods::item_check_commit(
dusk::mods::item_give_tag_sky_character(), item & 0xFF, this);
item = itemCheck.itemNo;
#endif
mItemId = fopAcM_createItemForTrBoxDemo(&current.pos, item, -1,
fopAcM_GetRoomNo(this), 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag_sky_character()));
mItemId =
fopAcM_createItemForTrBoxDemo(&current.pos, item, -1, fopAcM_GetRoomNo(this), 0,
0 IF_DUSK_ARG(itemCheck.tag));
JUT_ASSERT(580, mItemId != fpcM_ERROR_PROCESS_ID_e);
+5 -4
View File
@@ -1391,8 +1391,9 @@ u8 daTbox_c::getBombItemNoMain(u8 i_itemNo) {
int daTbox_c::setGetDemoItem() {
u8 item_no = getItemNo();
#if TARGET_PC
const u32 giveTag = dusk::mods::item_give_tag_chest(getTboxNo());
item_no = dusk::mods::item_check_tagged(giveTag, mOriginalItemNo, this);
const auto itemCheck = dusk::mods::item_check_commit(
dusk::mods::item_give_tag_chest(getTboxNo()), mOriginalItemNo, this);
item_no = itemCheck.itemNo;
#endif
if (item_no == dItemNo_BOMB_5_e || item_no == dItemNo_BOMB_10_e || item_no == dItemNo_BOMB_20_e || item_no == dItemNo_BOMB_30_e ||
item_no == dItemNo_WATER_BOMB_5_e || item_no == dItemNo_WATER_BOMB_10_e || item_no == dItemNo_WATER_BOMB_20_e || item_no == dItemNo_WATER_BOMB_30_e ||
@@ -1404,10 +1405,10 @@ int daTbox_c::setGetDemoItem() {
fpc_ProcID item_id;
if (field_0x718) {
item_id = fopAcM_createItemForPresentDemo(
&current.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag));
&current.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag));
} else {
item_id = fopAcM_createItemForTrBoxDemo(
&current.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag));
&current.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag));
}
if (item_id != fpcM_ERROR_PROCESS_ID_e) {
+7 -4
View File
@@ -383,15 +383,18 @@ int daTbox2_c::setGetDemoItem() {
u8 item_no = getItemNo();
#if TARGET_PC
int tboxNo = fopAcM_GetParamBit(this, 16, 8);
const u32 giveTag = dusk::mods::item_give_tag_chest(tboxNo);
item_no = dusk::mods::item_check_tagged(giveTag, mOriginalItemNo, this);
const auto itemCheck = dusk::mods::item_check_commit(
dusk::mods::item_give_tag_chest(tboxNo), mOriginalItemNo, this);
item_no = itemCheck.itemNo;
#endif
u32 partner_id;
if (mReturnRupee) {
partner_id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag));
partner_id = fopAcM_createItemForPresentDemo(
&current.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag));
} else {
partner_id = fopAcM_createItemForTrBoxDemo(&current.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag));
partner_id = fopAcM_createItemForTrBoxDemo(
&current.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag));
}
if (partner_id != -1) {
+9 -2
View File
@@ -50,17 +50,24 @@ bool daItemBase_c::chkDead() {
int CheckItemCreateHeap(fopAc_ac_c* i_this) {
daItemBase_c* a_this = static_cast<daItemBase_c*>(i_this);
u8 item_no = a_this->getItemNo();
u8 item_no = DUSK_IF_ELSE(a_this->getDisplayItemNo(), a_this->getItemNo());
return a_this->CreateItemHeap(dItem_data::getArcName(item_no), dItem_data::getBmdName(item_no),
dItem_data::getBtkName(item_no), dItem_data::getBpkName(item_no),
dItem_data::getBckName(item_no), dItem_data::getBxaName(item_no),
dItem_data::getBrkName(item_no), dItem_data::getBtpName(item_no));
}
#if TARGET_PC
const char* dItem_fieldModelArc(u8 itemNo) {
const char* arcName = dItem_data::getFieldArc(itemNo);
return arcName != NULL ? arcName : dItem_data::getArcName(itemNo);
}
#endif
int CheckFieldItemCreateHeap(fopAc_ac_c* i_this) {
daItemBase_c* a_this = static_cast<daItemBase_c*>(i_this);
u8 item_no = a_this->getItemNo();
u8 item_no = DUSK_IF_ELSE(a_this->getDisplayItemNo(), a_this->getItemNo());
return a_this->CreateItemHeap(
dItem_data::getFieldArc(item_no), dItem_data::getItemBmdName(item_no),
dItem_data::getItemBtkName(item_no), dItem_data::getItemBpkName(item_no),
File diff suppressed because it is too large Load Diff
+40 -7
View File
@@ -23,6 +23,7 @@
#include "m_Do/m_Do_graphic.h"
#include <cstring>
#include "dusk/utilities.hpp"
#include "dusk/version.hpp"
#if TARGET_PC
@@ -4153,11 +4154,43 @@ bool dFile_select_c::yesnoWakuAlpahAnm(u8 param_1) {
}
#if TARGET_PC
static dusk::utils::PaneCache mSelDtPanes[] = {
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
{MULTI_CHAR('gray_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('b_base'), 0.0f, 0.0f, false},
{MULTI_CHAR('b_base1'), 0.0f, 0.0f, false},
};
static dusk::utils::PaneCache fileSelPanes[] = {
{MULTI_CHAR('w_uzu00'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu01'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu02'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu03'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu04'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu05'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu06'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu07'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu08'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_uzu09'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_er_msg'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_er_msE'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_er_msR'), 0.0f, 0.0f, false},
{MULTI_CHAR('er_for0'), 0.0f, 0.0f, false},
{MULTI_CHAR('er_for1'), 0.0f, 0.0f, false},
};
void dFile_select_c::fileSelectWide() {
static bool cachedPanes = false;
// Get pre-scale values for each pane
if (!cachedPanes) {
for (PaneCache& entry : mSelDtPanes) {
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -4165,7 +4198,7 @@ void dFile_select_c::fileSelectWide() {
entry.cached = true;
}
}
for (PaneCache& entry : fileSelPanes) {
for (dusk::utils::PaneCache& entry : fileSelPanes) {
J2DPane* pane = fileSel.Scr->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -4179,13 +4212,13 @@ void dFile_select_c::fileSelectWide() {
// Reset all panes
mSelDt.ScrDt->scale(1.0f, 1.0f);
mSelDt.ScrDt->translate(0.0f, 0.0f);
for (PaneCache& entry : mSelDtPanes) {
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
pane->translate(entry.origTransX, entry.origTransY);
}
for (PaneCache& entry : fileSelPanes) {
for (dusk::utils::PaneCache& entry : fileSelPanes) {
J2DPane* pane = fileSel.Scr->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
@@ -4257,7 +4290,7 @@ void dFile_select_c::fileSelectWide() {
mSelDt.ScrDt->search(MULTI_CHAR('fuku_n2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
// Spirals & Memory Card Text
for (PaneCache& entry : fileSelPanes) {
for (dusk::utils::PaneCache& entry : fileSelPanes) {
J2DPane* pane = fileSel.Scr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
@@ -4283,7 +4316,7 @@ void dFile_select_c::fileSelectWide() {
const f32 wideShiftFactor = mSelDt.ScrDt->search(MULTI_CHAR('gray_n'))->getTranslateX() * (wideScaleFactor - mDoGph_gInf_c::hudAspectScaleDown);
const f32 ultraShiftFactor = mSelDt.ScrDt->search(MULTI_CHAR('gray_n'))->getTranslateX() * (ultraScaleFactor - mDoGph_gInf_c::hudAspectScaleDown);
for (PaneCache& entry : mSelDtPanes) {
for (dusk::utils::PaneCache& entry : mSelDtPanes) {
const size_t index = &entry - mSelDtPanes;
J2DPane* pane = mSelDt.ScrDt->search(entry.tag);
pane->setBasePosition(J2DBasePosition_0);
@@ -4351,7 +4384,7 @@ void dFile_select_c::fileSelectWide() {
}
// Spirals & Memory Card Text
for (PaneCache& entry : fileSelPanes) {
for (dusk::utils::PaneCache& entry : fileSelPanes) {
J2DPane* pane = fileSel.Scr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
+17 -1
View File
@@ -741,8 +741,12 @@ void item_func_ARMOR() {}
void item_func_WEAR_ZORA() {}
void item_func_MAGIC_LV1() {
#if TARGET_PC
dComIfGs_onEventBit(0xD04);
#else
dComIfGp_setItemMagicCount(16);
dComIfGp_setItemMaxMagicCount(16);
#endif
}
void item_func_DUNGEON_EXIT_2() {
@@ -1152,7 +1156,15 @@ void item_func_M_MAYFLY() {}
void item_func_F_MAYFLY() {}
void item_func_POU_SPIRIT() {}
void item_func_POU_SPIRIT() {
#if TARGET_PC
dComIfGs_addPohSpiritNum();
if (dComIfGs_getPohSpiritNum() == 20) {
/* dSv_event_flag_c::F_0457 - Castle Town - Revived cat */
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[457]);
}
#endif
}
void item_func_ANCIENT_DOCUMENT() {
dComIfGs_setItem(SLOT_22, dItemNo_ANCIENT_DOCUMENT_e);
@@ -1411,7 +1423,11 @@ int item_getcheck_func_WEAR_ZORA() {
}
int item_getcheck_func_MAGIC_LV1() {
#if TARGET_PC
return dComIfGs_isEventBit(0xD04);
#else
return -1;
#endif
}
int item_getcheck_func_DUNGEON_EXIT_2() {
+4
View File
@@ -53,7 +53,11 @@ DUSK_GAME_DATA dItem_itemResource dItem_data::item_resource[] = {
{"F_gD_rupy", 0x0004,-0x0001,-0x0001, 0x0007,-0x0001, 0x0, -0x1, 0x006B, 0x64, 0x0000},
{"O_gD_marm", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x0041, 0x64, 0x01A9},
{"O_gD_zora", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x004D, 0x64, 0x01AA},
#if TARGET_PC
{"O_gD_Injy", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x002E, 0x64, 0x0000},
#else
{"O_gD_Injy", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x002D, 0x64, 0x0000},
#endif
{"O_gD_TKS", 0x0008,-0x0001, 0x0005,-0x0001,-0x0001, -0x1, -0x1, 0x0028, 0x64, 0x0000},
{"O_gD_puL2", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x0047, 0x64, 0x0000},
{"O_gD_puL2", 0x0003,-0x0001,-0x0001,-0x0001,-0x0001, -0x1, -0x1, 0x0048, 0x64, 0x0000},
+36 -2
View File
@@ -39,6 +39,7 @@
#if TARGET_PC
#include "dusk/menu_pointer.h"
#include "dusk/utilities.hpp"
#endif
typedef void (dMenu_Collect2D_c::*initFunc)();
@@ -101,11 +102,44 @@ dMenu_Collect2D_c::~dMenu_Collect2D_c() {
}
#if TARGET_PC
static dusk::utils::PaneCache mpScreenPanes[] = {
{MULTI_CHAR('sa_tex_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('op_tex_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('heart_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('wolf_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('item_0_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('item_1_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('item_2_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('fish_3_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('lett_4_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('maki_5_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('fuku_n2'), 0.0f, 0.0f, false},
{MULTI_CHAR('tate_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('tate_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('ken_n0'), 0.0f, 0.0f, false},
{MULTI_CHAR('ken_n1'), 0.0f, 0.0f, false},
{MULTI_CHAR('kabu_6n'), 0.0f, 0.0f, false},
{MULTI_CHAR('t_t00'), 0.0f, 0.0f, false},
{MULTI_CHAR('f_t00'), 0.0f, 0.0f, false},
{MULTI_CHAR('itemn_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('infotxtn'), 0.0f, 0.0f, false},
{MULTI_CHAR('sa_op_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('title_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('menu_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('w_er_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('center_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('info_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('lavel_n'), 0.0f, 0.0f, false},
{MULTI_CHAR('modelbgn'), 0.0f, 0.0f, false},
};
void dMenu_Collect2D_c::menuCollectWide() {
static bool cachedPanes = false;
// Get pre-scale values for each pane
if (!cachedPanes) {
for (PaneCache& entry : mpScreenPanes) {
for (dusk::utils::PaneCache& entry : mpScreenPanes) {
J2DPane* pane = mpScreen->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -119,7 +153,7 @@ void dMenu_Collect2D_c::menuCollectWide() {
// Reset all panes
mpScreen->scale(1.0f, 1.0f);
mpScreen->translate(0.0f, 0.0f);
for (PaneCache& entry : mpScreenPanes) {
for (dusk::utils::PaneCache& entry : mpScreenPanes) {
J2DPane* pane = mpScreen->search(entry.tag);
pane->scale(1.0f, 1.0f);
pane->translate(entry.origTransX, entry.origTransY);
+539
View File
@@ -1900,3 +1900,542 @@ void dMeter2Info_set2DVibrationT() {}
void dMeter2Info_set2DVibration() {}
void dMeter2Info_set2DVibrationM() {}
#if TARGET_PC
void dMeter2Info_Initialize() {
g_meter2_info.init();
}
dMeterMap_c* dMeter2Info_getMeterMapClass() {
return g_meter2_info.getMeterMapClass();
}
dMw_c* dMeter2Info_getMenuWindowClass() {
return g_meter2_info.getMenuWindowClass();
}
void dMeter2Info_setWindowStatus(u8 i_status) {
g_meter2_info.setWindowStatus(i_status);
}
CPaneMgr* dMeter2Info_getMeterItemPanePtr(s32 i_idx) {
return g_meter2_info.getMeterItemPanePtr(i_idx);
}
void dMeter2Info_getString(u32 i_stringID, TEXT_SPAN o_string, JMSMesgEntry_c* i_msgEntry) {
g_meter2_info.getString(i_stringID, o_string, i_msgEntry);
}
void dMeter2Info_getStringKanji(u32 i_stringID, TEXT_SPAN o_string, JMSMesgEntry_c* i_msgEntry) {
g_meter2_info.getStringKanji(i_stringID, o_string, i_msgEntry);
}
void dMeter2Info_getStringKana(u32 i_stringID, TEXT_SPAN o_string, JMSMesgEntry_c* i_msgEntry) {
g_meter2_info.getStringKana(i_stringID, o_string, i_msgEntry);
}
f32 dMeter2Info_getStringLength(JUTFont* i_font, f32 param_2, f32 param_3, char* i_string) {
return g_meter2_info.getStringLength(i_font, param_2, param_3, i_string);
}
f32 dMeter2Info_getStringLength(J2DTextBox* i_textbox, char* i_string) {
return g_meter2_info.getStringLength(i_textbox, i_string);
}
void dMeter2Info_setHotSpringTimer(u8 i_slotNo) {
g_meter2_info.setHotSpringTimer(i_slotNo);
}
u16 dMeter2Info_getOilGaugeBackUp() {
return g_meter2_info.getOilGaugeBackUp();
}
void dMeter2Info_setOilGaugeBackUp(u16 param_1) {
g_meter2_info.setOilGaugeBackUp(param_1);
}
void dMeter2Info_setSaveStageName(const char* i_stageName) {
g_meter2_info.setSaveStageName(i_stageName);
}
u8 dMeter2Info_getInsectSelectType() {
return g_meter2_info.getInsectSelectType();
}
void dMeter2Info_setInsectSelectType(u8 i_type) {
g_meter2_info.setInsectSelectType(i_type);
}
u8 dMeter2Info_getWarpStatus() {
return g_meter2_info.getWarpStatus();
}
void dMeter2Info_setWarpStatus(u8 i_status) {
return g_meter2_info.setWarpStatus(i_status);
}
int dMeter2Info_readItemTexture(u8 i_itemNo, void* i_texBuf1, J2DPicture* i_pic1, void* i_texBuf2,
J2DPicture* i_pic2, void* i_texBuf3, J2DPicture* i_pic3, void* i_texBuf4, J2DPicture* i_pic4,
int param_9) {
return g_meter2_info.readItemTexture(i_itemNo, i_texBuf1, i_pic1, i_texBuf2, i_pic2, i_texBuf3,
i_pic3, i_texBuf4, i_pic4, param_9);
}
void dMeter2Info_setItemColor(
u8 i_itemNo, J2DPicture* i_pic1, J2DPicture* i_pic2, J2DPicture* i_pic3, J2DPicture* i_pic4) {
g_meter2_info.setItemColor(i_itemNo, i_pic1, i_pic2, i_pic3, i_pic4);
}
u8 dMeter2Info_getWarpRoomNo() {
return g_meter2_info.getWarpRoomNo();
}
u8 dMeter2Info_getWarpPlayerNo() {
return g_meter2_info.getWarpPlayerNo();
}
cXyz& dMeter2Info_getWarpPos() {
return g_meter2_info.getWarpPos();
}
const char* dMeter2Info_getWarpStageName() {
return g_meter2_info.getWarpStageName();
}
void dMeter2Info_setNowCount(u8 i_count) {
g_meter2_info.setNowCount(i_count);
}
void dMeter2Info_setMaxCount(u8 i_count) {
g_meter2_info.setMaxCount(i_count);
}
BOOL dMeter2Info_isDirectUseItem(int param_0) {
return g_meter2_info.isDirectUseItem(param_0);
}
bool dMeter2Info_isUseButton(int i_buttonBit) {
return g_meter2_info.isUseButton(i_buttonBit);
}
void dMeter2Info_setMeterMapClass(dMeterMap_c* i_map) {
g_meter2_info.setMeterMapClass(i_map);
}
void dMeter2Info_decHotSpringTimer() {
g_meter2_info.decHotSpringTimer();
}
void dMeter2Info_allUseButton() {
g_meter2_info.allUseButton();
}
void dMeter2Info_offUseButton(int i_buttonBit) {
g_meter2_info.offUseButton(i_buttonBit);
}
void dMeter2Info_resetGameStatus() {
g_meter2_info.resetGameStatus();
}
void dMeter2Info_onGameStatus(int i_status) {
g_meter2_info.onGameStatus(i_status);
}
void dMeter2Info_setMapStatus(u8 i_status) {
g_meter2_info.setMapStatus(i_status);
}
u8 dMeter2Info_getMapStatus() {
return g_meter2_info.getMapStatus();
}
void dMeter2Info_setPauseStatus(u8 i_status) {
g_meter2_info.setPauseStatus(i_status);
}
void dMeter2Info_resetPauseStatus() {
g_meter2_info.resetPauseStatus();
}
u8 dMeter2Info_getPauseStatus() {
return g_meter2_info.getPauseStatus();
}
bool dMeter2Info_isGameStatus(int i_status) {
return g_meter2_info.isGameStatus(i_status);
}
bool dMeter2Info_isTouchKeyCheck(int i_status) {
return g_meter2_info.isTouchKeyCheck(i_status);
}
void dMeter2Info_setMapKeyDirection(u16 i_direction) {
g_meter2_info.setMapKeyDirection(i_direction);
}
bool dMeter2Info_isSub2DStatus(int i_flag) {
return g_meter2_info.isSub2DStatus(i_flag);
}
void dMeter2Info_offMenuInForce(int i_flag) {
g_meter2_info.offMenuInForce(i_flag);
}
bool dMeter2Info_isMenuInForce(int i_flag) {
return g_meter2_info.isMenuInForce(i_flag);
}
void dMeter2Info_setMenuWindowClass(dMw_c* i_menu) {
g_meter2_info.setMenuWindowClass(i_menu);
}
void dMeter2Info_resetWarpStatus() {
g_meter2_info.resetWarpStatus();
}
u16 dMeter2Info_getMapKeyDirection() {
return g_meter2_info.getMapKeyDirection();
}
u8 dMeter2Info_getWindowStatus() {
return g_meter2_info.getWindowStatus();
}
void dMeter2Info_setMsgResource(void* i_res) {
g_meter2_info.setMsgResource(i_res);
}
void dMeter2Info_setStageMsgResource(void* i_res) {
g_meter2_info.setStageMsgResource(i_res);
}
void dMeter2Info_setMsgUnitResource(void* i_res) {
g_meter2_info.setMsgUnitResource(i_res);
}
void* dMeter2Info_getMsgResource() {
return g_meter2_info.getMsgResource();
}
void* dMeter2Info_getStageMsgResource() {
return g_meter2_info.getStageMsgResource();
}
void* dMeter2Info_getMsgUnitResource() {
return g_meter2_info.getMsgUnitResource();
}
u8 dMeter2Info_getLightDropGetFlag(int i_no) {
return g_meter2_info.getLightDropGetFlag(i_no);
}
s32 dMeter2Info_getMeterStringType() {
return g_meter2_info.getMeterStringType();
}
void dMeter2Info_setMeterClass(dMeter2_c* i_meter) {
g_meter2_info.setMeterClass(i_meter);
}
void dMeter2Info_onLifeGaugeSE() {
g_meter2_info.onLifeGaugeSE();
}
void dMeter2Info_offLifeGaugeSE() {
g_meter2_info.offLifeGaugeSE();
}
u8 dMeter2Info_getLifeGaugeSE() {
return g_meter2_info.getLifeGaugeSE();
}
const char* dMeter2Info_getSaveStageName() {
return g_meter2_info.getSaveStageName();
}
void dMeter2Info_onShopTalkFlag() {
g_meter2_info.onShopTalkFlag();
}
void dMeter2Info_setLightDropGetFlag(int i_no, u8 i_flag) {
g_meter2_info.setLightDropGetFlag(i_no, i_flag);
}
u8 dMeter2Info_getRentalBombBag() {
return g_meter2_info.getRentalBombBag();
}
u8 dMeter2Info_getMiniGameItemSetFlag() {
return g_meter2_info.getMiniGameItemSetFlag();
}
void dMeter2Info_setMiniGameItem(u8 i_minigameFlag) {
g_meter2_info.setMiniGameItem(i_minigameFlag);
}
void dMeter2Info_resetMiniGameItem(bool i_saveItem) {
g_meter2_info.resetMiniGameItem(i_saveItem);
}
void dMeter2Info_setTableMapRegionNo(u8 i_regionNo) {
g_meter2_info.setTableMapRegionNo(i_regionNo);
}
void dMeter2Info_setGoldWolfMapType(u8 i_mapType) {
g_meter2_info.setGoldWolfMapType(i_mapType);
}
void dMeter2Info_changeWater(u8 i_slotNo) {
g_meter2_info.changeWater(i_slotNo);
}
void dMeter2Info_warpInProc() {
g_meter2_info.warpInProc();
}
void dMeter2Info_warpOutProc() {
g_meter2_info.warpOutProc();
}
dMeter2_c* dMeter2Info_getMeterClass() {
return g_meter2_info.getMeterClass();
}
s16 dMeter2Info_getMsgKeyWaitTimer() {
return g_meter2_info.getMsgKeyWaitTimer();
}
void dMeter2Info_onDirectUseItem(int param_0) {
g_meter2_info.onDirectUseItem(param_0);
}
void dMeter2Info_setFloatingFlow(u16 i_flowID, s16 i_msgTimer, bool i_wakuVisible) {
g_meter2_info.setFloatingFlow(i_flowID, i_msgTimer, i_wakuVisible);
}
u8 dMeter2Info_getGameOverType() {
return g_meter2_info.getGameOverType();
}
void dMeter2Info_setGameOverType(u8 i_gameoverType) {
g_meter2_info.setGameOverType(i_gameoverType);
}
void dMeter2Info_setMsgKeyWaitTimer(s16 i_waitTimer) {
g_meter2_info.setMsgKeyWaitTimer(i_waitTimer);
}
s32 dMeter2Info_getMsgTimeMs() {
return g_meter2_info.getMsgTimeMs();
}
s32 dMeter2Info_getTimeMs() {
return g_meter2_info.getTimeMs();
}
void dMeter2Info_setMsgTimeMs(s32 i_msgTime) {
g_meter2_info.setMsgTimeMs(i_msgTime);
}
void dMeter2Info_setTimeMs(s32 i_time) {
g_meter2_info.setTimeMs(i_time);
}
u8 dMeter2Info_getNowCount() {
return g_meter2_info.getNowCount();
}
u8 dMeter2Info_getMaxCount() {
return g_meter2_info.getMaxCount();
}
void dMeter2Info_setScopeZoomPointer(u8 param_0) {
g_meter2_info.setScopeZoomPointer(param_0);
}
void dMeter2Info_decMsgKeyWaitTimer() {
g_meter2_info.decMsgKeyWaitTimer();
}
bool dMeter2Info_isFloatingMessageVisible() {
return g_meter2_info.isFloatingMessageVisible();
}
u8 dMeter2Info_getItemExplainWindowStatus() {
return g_meter2_info.getItemExplainWindowStatus();
}
void dMeter2Info_setItemExplainWindowStatus(u8 i_value) {
g_meter2_info.setItemExplainWindowStatus(i_value);
}
void dMeter2Info_resetDirectUseItem() {
g_meter2_info.resetDirectUseItem();
}
u16 dMeter2Info_getFloatingFlowID() {
return g_meter2_info.getFloatingFlowID();
}
u8 dMeter2Info_getCollectCursorPosX() {
return g_meter2_info.getCollectCursorPosX();
}
u8 dMeter2Info_getCollectCursorPosY() {
return g_meter2_info.getCollectCursorPosY();
}
void dMeter2Info_setCollectCursorPosXY(u8 x, u8 y) {
g_meter2_info.setCollectCursorPosXY(x, y);
}
void dMeter2Info_onBlinkButton(int i_flag) {
g_meter2_info.onBlinkButton(i_flag);
}
bool dMeter2Info_isFloatingMessageWakuVisible() {
return g_meter2_info.isFloatingMessageWakuVisible();
}
bool dMeter2Info_isBlinkButton(int i_flag) {
return g_meter2_info.isBlinkButton(i_flag);
}
void dMeter2Info_resetBlinkButton() {
g_meter2_info.resetBlinkButton();
}
void dMeter2Info_resetFloatingMessage() {
g_meter2_info.resetFloatingMessage();
}
s16 dMeter2Info_decFloatingMessageTimer() {
return g_meter2_info.decFloatingMessageTimer();
}
s16 dMeter2Info_getFloatingMessageTimer() {
return g_meter2_info.getFloatingMessageTimer();
}
u16 dMeter2Info_getFloatingMessageID() {
return g_meter2_info.getFloatingMessageID();
}
s16 dMeter2Info_getHorseLifeCount() {
return g_meter2_info.getHorseLifeCount();
}
void dMeter2Info_setMeterString(s32 i_string) {
g_meter2_info.setMeterString(i_string);
}
void dMeter2Info_resetMeterString() {
g_meter2_info.resetMeterString();
}
void dMeter2Info_setMeterItemPanePtr(int i_no, CPaneMgr* i_pane) {
g_meter2_info.setMeterItemPanePtr(i_no, i_pane);
}
bool dMeter2Info_isShopTalkFlag() {
return g_meter2_info.isShopTalkFlag();
}
void dMeter2Info_setMapDrugFlag(bool i_flag) {
g_meter2_info.setMapDrugFlag(i_flag);
}
bool dMeter2Info_isTempBit(int i_bit) {
return g_meter2_info.isTempBit(i_bit);
}
void dMeter2Info_offSub2DStatus(int i_bit) {
g_meter2_info.offSub2DStatus(i_bit);
}
void dMeter2Info_onSub2DStatus(int i_bit) {
g_meter2_info.onSub2DStatus(i_bit);
}
void dMeter2Info_set2DWidth(f32 i_width) {
g_meter2_info.set2DWidth(i_width);
}
void dMeter2Info_set2DHeight(f32 i_height) {
g_meter2_info.set2DHeight(i_height);
}
void dMeter2Info_set2DPosH(f32 i_posH) {
g_meter2_info.set2DPosH(i_posH);
}
void dMeter2Info_set2DPosV(f32 i_posV) {
g_meter2_info.set2DPosV(i_posV);
}
void dMeter2Info_offShopTalkFlag() {
g_meter2_info.offShopTalkFlag();
}
void dMeter2Info_onUseButton(int i_button) {
g_meter2_info.onUseButton(i_button);
}
f32 dMeter2Info_get2DWidth() {
return g_meter2_info.get2DWidth();
}
f32 dMeter2Info_get2DHeight() {
return g_meter2_info.get2DHeight();
}
void dMeter2Info_setWarpInfo(const char* i_stageName, const cXyz& i_position, s16 i_angle,
u8 i_roomNo, u8 param_4, u8 i_warpPlayerNo) {
g_meter2_info.setWarpInfo(i_stageName, i_position, i_angle, i_roomNo, param_4, i_warpPlayerNo);
}
u8 dMeter2Info_getTableMapRegionNo() {
return g_meter2_info.getTableMapRegionNo();
}
u8 dMeter2Info_getGoldWolfMapType() {
return g_meter2_info.getGoldWolfMapType();
}
bool dMeter2Info_isWindowAccept(int param_0) {
return g_meter2_info.isWindowAccept(param_0);
}
void dMeter2Info_setHorseLifeCount(s16 i_count) {
g_meter2_info.setHorseLifeCount(i_count);
}
void dMeter2Info_offTempBit(int i_bit) {
g_meter2_info.offTempBit(i_bit);
}
void dMeter2Info_onTempBit(int i_bit) {
g_meter2_info.onTempBit(i_bit);
}
void dMeter2Info_setFloatingMessage(u16 i_msgID, s16 i_msgTimer, bool i_wakuVisible) {
g_meter2_info.setFloatingMessage(i_msgID, i_msgTimer, i_wakuVisible);
}
void dMeter2Info_setMiniGameCount(s8 i_count) {
g_meter2_info.setMiniGameCount(i_count);
}
void dMeter2Info_onWindowAccept(int param_0) {
g_meter2_info.onWindowAccept(param_0);
}
void dMeter2Info_offWindowAccept(int param_0) {
g_meter2_info.offWindowAccept(param_0);
}
void dMeter2Info_onMenuInForce(int param_0) {
g_meter2_info.onMenuInForce(param_0);
}
#endif
+93 -3
View File
@@ -16,6 +16,21 @@
#if TARGET_PC
#include "dusk/menu_pointer.h"
#include "dusk/scope_guard.hpp"
#include "helpers/bits.hpp"
static bool read_full_color(const void* data, u32 size, u32& ccColor, u32& gcColor) {
if (size == 4) {
ccColor = dusk::read_bits<u32>(data);
gcColor = ccColor;
return true;
}
if (size == 8) {
ccColor = dusk::read_bits<u32>(data);
gcColor = dusk::read_bits<u32>(static_cast<const u8*>(data) + sizeof(ccColor));
return true;
}
return false;
}
#endif
#if TARGET_PC
@@ -400,6 +415,9 @@ jmessage_tReference::jmessage_tReference() {
resetCharCountBuffer();
mNowColorType = 0;
mTopColorType = 0;
#if TARGET_PC
resetColors();
#endif
mButtonTagStopFlag = 0;
mPageEndCount = 0;
mSelectNum = 0;
@@ -594,6 +612,13 @@ void jmessage_tReference::pageSend() {
if (mNowColorType != mTopColorType) {
mTopColorType = mNowColorType;
}
#if TARGET_PC
mTopFullColor = mNowFullColor;
if (mNowFullColor) {
mTopCCColor = mNowCCColor;
mTopGCColor = mNowGCColor;
}
#endif
mTopWordCount = mNowWordCount;
mCharAlpha = 0.0f;
@@ -1917,6 +1942,9 @@ void jmessage_tSequenceProcessor::do_begin(void const* pEntry, char const* pszTe
pReference->resetCharCnt();
pReference->setNowColorType(0);
pReference->setTopColorType(0);
#if TARGET_PC
pReference->resetColors();
#endif
pReference->setNowWordCount(0);
pReference->setTopWordCount(0);
pReference->setBatchColorFlag(0);
@@ -2280,9 +2308,21 @@ bool jmessage_tSequenceProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_si
return true;
case MSGTAG_GROUP(255):
switch (i_tag) {
case MSGTAG_COLOR:
case MSGTAG_COLOR: {
#if TARGET_PC
u32 ccColor;
u32 gcColor;
if (read_full_color(i_data, i_size, ccColor, gcColor)) {
pReference->setFullColor(ccColor, gcColor);
} else if (i_size == 1) {
pReference->clearNowFullColor();
pReference->setNowColorType(*(u8*)i_data & 0xFF);
}
#else
pReference->setNowColorType(*(u8*)i_data & 0xFF);
#endif
return true;
}
case MSGTAG_SCALE:
pReference->setNowTagScale(*(BE(u16)*)i_data & 0xFFFF);
return true;
@@ -2863,7 +2903,15 @@ void jmessage_tRenderingProcessor::do_begin(void const* pEntry, char const* pszT
do_scale(field_0x44);
}
#if TARGET_PC
if (pReference->hasTopFullColor()) {
do_fullcolor(pReference->getTopCCColor(), pReference->getTopGCColor());
} else {
do_color(pReference->getTopColorType());
}
#else
do_color(pReference->getTopColorType());
#endif
pReference->resetDrawLightCount();
do_widthcenter();
@@ -3017,9 +3065,20 @@ bool jmessage_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_s
return 1;
case MSGTAG_GROUP(255):
switch (i_tag) {
case MSGTAG_COLOR:
case MSGTAG_COLOR: {
#if TARGET_PC
u32 ccColor;
u32 gcColor;
if (read_full_color(i_data, i_size, ccColor, gcColor)) {
do_fullcolor(ccColor, gcColor);
} else if (i_size == 1) {
do_color(*(u8*)i_data & 0xFF);
}
#else
do_color(*(u8*)i_data & 0xFF);
#endif
return 1;
}
case MSGTAG_SCALE:
field_0x13c = *(BE(u16)*)i_data & 0xFFFF;
do_scale(field_0x13c / 100.0f);
@@ -3630,6 +3689,18 @@ void jmessage_tRenderingProcessor::do_color(u8 i_colorNo) {
do_strcat(buffer, false, false, false);
}
#if TARGET_PC
void jmessage_tRenderingProcessor::do_fullcolor(u32 ccColor, u32 gcColor) {
mColorNo = 0xFF;
mCCColor = ccColor;
mGCColor = gcColor;
char buffer[40];
SAFE_SPRINTF(buffer, "\x1B" "CC[%08x]" "\x1B" "GC[%08x]", mCCColor, mGCColor);
do_strcat(buffer, false, false, false);
}
#endif
void jmessage_tRenderingProcessor::do_scale(f32 param_1) {
jmessage_tReference* pReference = (jmessage_tReference*)getReference();
@@ -4684,9 +4755,20 @@ bool jmessage_string_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data,
switch(i_tag & 0xFF0000) {
case MSGTAG_GROUP(255):
switch(i_tag) {
case MSGTAG_COLOR:
case MSGTAG_COLOR: {
#if TARGET_PC
u32 ccColor;
u32 gcColor;
if (read_full_color(i_data, i_size, ccColor, gcColor)) {
do_fullcolor(ccColor, gcColor);
} else if (i_size == 1) {
do_color(*(u8*)i_data & 0xFF);
}
#else
do_color(*(u8*)i_data & 0xFF);
#endif
break;
}
case MSGTAG_SCALE:
do_scale(*(BE(u16)*)i_data / 100.0f);
break;
@@ -5336,6 +5418,14 @@ void jmessage_string_tRenderingProcessor::do_color(u8 i_colorNo) {
do_strcat(buffer);
}
#if TARGET_PC
void jmessage_string_tRenderingProcessor::do_fullcolor(u32 ccColor, u32 gcColor) {
char buffer[32];
SAFE_SPRINTF(buffer, "\x1b" "CC[%08x]" "\x1b" "GC[%08x]", ccColor, gcColor);
do_strcat(buffer);
}
#endif
void jmessage_string_tRenderingProcessor::do_scale(f32 i_scale) {
J2DTextBox::TFontSize fontSize;
mpReference->getPanePtr()->getFontSize(fontSize);
+13 -4
View File
@@ -43,6 +43,11 @@ u16 message_id(const MessageEntryData& entry) {
return static_cast<u16>(static_cast<u16>(entry.bytes[4]) << 8 | entry.bytes[5]);
}
u16 flow_group(int flowId) {
const bool isCommonFlow = flowId >= 3000 && flowId < dusk::flow::kCustomNodeMin;
return isCommonFlow ? 0 : static_cast<u16>(dMsgObject_getGroupID());
}
} // namespace
#endif
@@ -70,8 +75,7 @@ void dMsgFlow_c::init(fopAc_ac_c* i_partner, int i_flowID, int param_2, fopAc_ac
dMsgObject_changeFlowGroup(i_flowID);
#if TARGET_PC
dusk::flow::bind_resource(dMsgObject_getMsgDtPtr(),
i_flowID >= 3000 ? 0 : static_cast<u16>(dMsgObject_getGroupID()));
dusk::flow::bind_resource(dMsgObject_getMsgDtPtr(), flow_group(i_flowID));
#endif
if (param_2 == 0) {
@@ -399,8 +403,7 @@ void dMsgFlow_c::setInitValueGroupChange(int i_msgNo, fopAc_ac_c** i_talkPartner
dMsgObject_changeFlowGroup(i_msgNo);
#if TARGET_PC
dusk::flow::bind_resource(dMsgObject_getMsgDtPtr(),
i_msgNo >= 3000 ? 0 : static_cast<u16>(dMsgObject_getGroupID()));
dusk::flow::bind_resource(dMsgObject_getMsgDtPtr(), flow_group(i_msgNo));
#endif
setInitValue(0);
@@ -446,6 +449,12 @@ u8* dMsgFlow_c::getMsgDataBlock(char const* block_tag) {
}
u16 dMsgFlow_c::getInitNodeIndex(u16 param_1) {
#if TARGET_PC
if (param_1 >= dusk::flow::kCustomNodeMin) {
return param_1;
}
#endif
u8* var_r30 = NULL;
u16 var_r27 = -1;
+8
View File
@@ -36,6 +36,7 @@
#include "dusk/language.hpp"
#include "dusk/logging.h"
#include "dusk/menu_pointer.h"
#include "dusk/mods/item.hpp"
#include "dusk/mods/svc/flow.hpp"
#include "dusk/settings.h"
#include "dusk/version.hpp"
@@ -832,6 +833,9 @@ u32 dMsgObject_c::getMessageIndex(u32 param_0) {
u32 dMsgObject_c::getRevoMessageIndex(u32 param_1) {
#if TARGET_PC
const u16 sourceGroup = param_1 > 5000 ? static_cast<u16>(s_groupID) : 0;
param_1 = dusk::mods::item_check_message(sourceGroup, param_1);
u16 customGroup = 0;
if (param_1 <= 0xffff &&
dusk::flow::custom_message_group(static_cast<u16>(param_1), customGroup))
@@ -1861,7 +1865,11 @@ void dMsgObject_c::readMessageGroupLocal(mDoDvdThd_mountXArchive_c** p_arcMount)
void dMsgObject_c::changeFlowGroupLocal(s32 param_0) {
mFlowChk = 1;
#if TARGET_PC
changeGroup(param_0 >= 3000 && param_0 < dusk::flow::kCustomNodeMin ? (s16)0 : s_groupID);
#else
changeGroup(param_0 >= 3000 ? (s16)0 : s_groupID);
#endif
}
void dMsgObject_c::demoMessageGroupLocal() {
+30 -9
View File
@@ -10,6 +10,7 @@
#include <cstring>
#include "JSystem/J2DGraph/J2DAnmLoader.h"
#include "dusk/utilities.hpp"
#include "dusk/version.hpp"
#include "f_op/f_op_msg_mng.h"
@@ -1512,11 +1513,31 @@ void dName_c::selectCursorPosSet(int row) {
}
#if TARGET_PC
static dusk::utils::PaneCache l_tagName[] = {
{MULTI_CHAR('m_00_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_00_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_01_3'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_01_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_02_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_2'), 0.0f, 0.0f, false},
{MULTI_CHAR('m03_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m03_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_04_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_1'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_05_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_05_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_06_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_0'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_07_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_07_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_08_4'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_09_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_09_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_10_3'), 0.0f, 0.0f, false},
{MULTI_CHAR('m_10_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m_11_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_0'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_2'), 0.0f, 0.0f, false},
{MULTI_CHAR('m12_3'), 0.0f, 0.0f, false}, {MULTI_CHAR('m12_4'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_2'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_1'), 0.0f, 0.0f, false}, {MULTI_CHAR('p_end_0'), 0.0f, 0.0f, false},
};
static dusk::utils::PaneCache l_nameTagName[] = {
{MULTI_CHAR('name_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('name_07'), 0.0f, 0.0f, false},
};
static dusk::utils::PaneCache l_nameCurTagName[] = {
{MULTI_CHAR('s__n_00'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_01'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_02'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_03'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_04'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_05'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_06'), 0.0f, 0.0f, false}, {MULTI_CHAR('s__n_07'), 0.0f, 0.0f, false},
};
void dName_c::nameWide() {
static bool cachedPanes = false;
// Get pre-scale values for each pane
if (!cachedPanes) {
for (PaneCache& entry : l_tagName) {
for (dusk::utils::PaneCache& entry : l_tagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -1524,7 +1545,7 @@ void dName_c::nameWide() {
entry.cached = true;
}
}
for (PaneCache& entry : l_nameTagName) {
for (dusk::utils::PaneCache& entry : l_nameTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -1532,7 +1553,7 @@ void dName_c::nameWide() {
entry.cached = true;
}
}
for (PaneCache& entry : l_nameCurTagName) {
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
if (!entry.cached) {
entry.origTransX = pane->getTranslateX();
@@ -1546,19 +1567,19 @@ void dName_c::nameWide() {
// Reset all panes
nameIn.NameInScr->scale(1.0f, 1.0f);
nameIn.NameInScr->translate(0.0f, 0.0f);
for (PaneCache& entry : l_tagName) {
for (dusk::utils::PaneCache& entry : l_tagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
pane->translate(entry.origTransX, entry.origTransY);
}
for (PaneCache& entry : l_nameTagName) {
for (dusk::utils::PaneCache& entry : l_nameTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
pane->translate(entry.origTransX, entry.origTransY);
}
for (PaneCache& entry : l_nameCurTagName) {
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->setBasePosition(J2DBasePosition_4);
pane->scale(1.0f, 1.0f);
@@ -1574,17 +1595,17 @@ void dName_c::nameWide() {
break;
default: // Wii and Dusklight
// List of Characters Box
for (PaneCache& entry : l_tagName) {
for (dusk::utils::PaneCache& entry : l_tagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
// Letters being typed
for (PaneCache& entry : l_nameTagName) {
for (dusk::utils::PaneCache& entry : l_nameTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
// Underscores when typing below letters
for (PaneCache& entry : l_nameCurTagName) {
for (dusk::utils::PaneCache& entry : l_nameCurTagName) {
J2DPane* pane = nameIn.NameInScr->search(entry.tag);
pane->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f);
}
+28 -2
View File
@@ -43,7 +43,9 @@
#include "dusk/autosave.h"
#include "dusk/d_trigger_view.h"
#include "dusk/memory.h"
#include "dusk/mods/item.hpp"
#include "dusk/ui/ui.hpp"
#include "mods/items.h"
#endif
#if DEBUG
@@ -1197,11 +1199,35 @@ static int phase_1(dScnPly_c* i_this) {
}
// Stage: Ordon Spring, Room: Ordon Spring
#if TARGET_PC
const bool iliaMemoryArrival =
!strcmp(dComIfGp_getStartStageName(), "F_SP104") && dComIfGp_getStartStageRoomNo() == 1 &&
dComIfGp_getStartStagePoint() == 23 && dComIfGp_getStartStageLayer() == 12;
const u32 iliaMemoryTag = dusk::mods::item_give_tag(ITEM_CHECK_ILIA_MEMORY);
if (!iliaMemoryArrival) {
dusk::mods::item_check_cancel(iliaMemoryTag);
}
if (iliaMemoryArrival)
#else
if (!strcmp(dComIfGp_getStartStageName(), "F_SP104") && dComIfGp_getStartStageRoomNo() == 1 &&
dComIfGp_getStartStagePoint() == 23 && dComIfGp_getStartStageLayer() == 12)
#endif
{
dComIfGs_onItemFirstBit(dItemNo_HORSE_FLUTE_e);
dComIfGs_setItem(SLOT_21, dItemNo_HORSE_FLUTE_e);
#if TARGET_PC
const auto itemCheck =
dusk::mods::item_check_commit(iliaMemoryTag, dItemNo_HORSE_FLUTE_e, nullptr);
if (itemCheck.itemNo == dItemNo_HORSE_FLUTE_e) {
#endif
dComIfGs_onItemFirstBit(dItemNo_HORSE_FLUTE_e);
dComIfGs_setItem(SLOT_21, dItemNo_HORSE_FLUTE_e);
#if TARGET_PC
dusk::mods::item_check_complete(itemCheck, nullptr);
} else if (itemCheck.itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(itemCheck, nullptr);
} else {
dusk::mods::item_check_enqueue(itemCheck, dusk::mods::ItemGiveMode::Silent);
}
#endif
}
if ((u8)dKy_darkworld_stage_check(dComIfGp_getStartStageName(),
+9 -5
View File
@@ -906,15 +906,17 @@ int dShopSystem_c::seq_start(fopAc_ac_c* actor, dMsgFlow_c* i_flow) {
if (mFlow.getEventId(&itemNo) == 1) {
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
#if TARGET_PC
const char* itemCheckName = nullptr;
u32 itemGiveTag = 0;
if (itemNo == dItemNo_HALF_MILK_BOTTLE_e) {
itemCheckName = "sera_reward";
itemNo = dusk::mods::item_check(itemCheckName, itemNo, actor);
const auto itemCheck =
dusk::mods::item_check_commit("sera_reward", itemNo, actor);
itemNo = itemCheck.itemNo;
itemGiveTag = itemCheck.tag;
}
#endif
mItemPartnerId =
fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
NULL IF_DUSK_ARG(itemGiveTag));
}
if (fpcEx_IsExist(mItemPartnerId)) {
@@ -1205,7 +1207,9 @@ int dShopSystem_c::seq_decide_yes(fopAc_ac_c* actor, dMsgFlow_c* i_flow) {
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
#if TARGET_PC
const u32 itemGiveTag = dusk::mods::item_give_tag_shop(itemNo & 0xFF);
itemNo = dusk::mods::item_check_shop(itemNo & 0xFF, actor);
const auto itemCheck =
dusk::mods::item_check_commit(itemGiveTag, itemNo & 0xFF, actor);
itemNo = itemCheck.itemNo;
#endif
mItemPartnerId = fopAcM_createItemForPresentDemo(
&current.pos, itemNo, 0, -1, -1, NULL, NULL IF_DUSK_ARG(itemGiveTag));
+1 -1
View File
@@ -76,7 +76,7 @@ namespace dusk {
{ dItemNo_WEAR_KOKIRI_e, {"Hero's Clothes"} },
{ dItemNo_ARMOR_e, {"Magic Armor"} },
{ dItemNo_WEAR_ZORA_e, {"Zora Armor"} },
{ dItemNo_MAGIC_LV1_e, {"Magic Level 1"} },
{ dItemNo_SHADOW_CRYSTAL_e, {"Shadow Crystal"} },
{ dItemNo_DUNGEON_EXIT_2_e, {"Ooccoo Sr.", ITEMTYPE_EQUIP_e} },
{ dItemNo_WALLET_LV1_e, {"Wallet"} },
{ dItemNo_WALLET_LV2_e, {"Big Wallet"} },
+78 -6
View File
@@ -1,37 +1,109 @@
#pragma once
#include "mods/svc/item.h"
#include <cstdint>
#include <unordered_map>
#include <utility>
class fopAc_ac_c;
namespace dusk::mods {
enum class ItemGiveMode : uint8_t {
Demo,
Silent,
ForcedDemo,
};
struct ItemCheckResult {
uint32_t tag = 0;
uint8_t itemNo = 0;
uint8_t displayItemNo = 0;
};
ItemCheckResolution item_check_resolve(const char* name, uint8_t itemNo, fopAc_ac_c* giver);
ItemCheckResolution item_check_resolve(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver);
uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver);
uint8_t item_check_tagged(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver);
ItemCheckResult item_check_commit(const char* name, uint8_t itemNo, fopAc_ac_c* giver);
ItemCheckResult item_check_commit(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver);
uint8_t item_check_chest(uint8_t boxNo, uint8_t itemNo, fopAc_ac_c* chest);
uint8_t item_check_boss(uint8_t itemNo, fopAc_ac_c* boss);
uint8_t item_check_freestanding(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* item);
uint8_t item_check_poe(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* poe);
uint8_t item_check_golden_wolf(uint16_t eventFlag, uint8_t itemNo, fopAc_ac_c* item);
uint8_t item_check_shop(uint8_t itemNo, fopAc_ac_c* giver);
uint8_t item_check_bug(uint8_t insectId, uint8_t itemNo, fopAc_ac_c* agitha);
uint8_t item_check_sky_character(uint8_t itemNo, fopAc_ac_c* statue);
uint32_t item_give_tag(const char* name);
uint32_t item_give_tag_chest(uint8_t boxNo);
uint32_t item_give_tag_boss();
uint32_t item_give_tag_freestanding(uint8_t bitNo);
uint32_t item_give_tag_golden_wolf(uint16_t eventFlag);
uint32_t item_give_tag_poe(uint8_t bitNo);
uint32_t item_give_tag_shop(uint8_t itemNo);
uint32_t item_give_tag_bug(uint8_t insectId);
uint32_t item_give_tag_sky_character();
void item_check_enqueue(const char* name, uint8_t itemNo);
void item_check_enqueue_poe(uint8_t bitNo, uint8_t itemNo);
void item_check_enqueue_deferred(const char* name, uint8_t itemNo);
bool item_check_enqueue(ItemCheckResult check, ItemGiveMode mode);
void item_check_complete(ItemCheckResult check, fopAc_ac_c* giver);
void item_check_cancel(uint32_t giveTag);
void item_check_clear_committed();
void item_granted(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver);
uint32_t item_check_message(uint16_t group, uint32_t messageId);
bool item_give_queue_dispatching();
uint32_t item_give_queue_take_tag();
namespace detail {
struct CommittedCheck {
uint8_t vanillaItem = 0;
ItemCheckResolution resolution{};
bool queued = false;
};
class ItemCommitStore {
public:
CommittedCheck* find(uint32_t giveTag) {
const auto it = mChecks.find(giveTag);
return it != mChecks.end() ? &it->second : nullptr;
}
const CommittedCheck* find(uint32_t giveTag) const {
const auto it = mChecks.find(giveTag);
return it != mChecks.end() ? &it->second : nullptr;
}
template <typename ResolveFn>
CommittedCheck& commit(uint32_t giveTag, uint8_t vanillaItem, ResolveFn&& resolve) {
const auto existing = mChecks.find(giveTag);
if (existing != mChecks.end()) {
return existing->second;
}
const ItemCheckResolution resolution = std::forward<ResolveFn>(resolve)();
return mChecks
.emplace(giveTag,
CommittedCheck{
.vanillaItem = vanillaItem,
.resolution = resolution,
})
.first->second;
}
bool contains(uint32_t giveTag) const { return mChecks.contains(giveTag); }
void erase(uint32_t giveTag) { mChecks.erase(giveTag); }
void clear() { mChecks.clear(); }
bool empty() const { return mChecks.empty(); }
private:
std::unordered_map<uint32_t, CommittedCheck> mChecks;
};
} // namespace detail
} // namespace dusk::mods
+176 -29
View File
@@ -6,9 +6,14 @@
#include "aurora/lib/logging.hpp"
#include "d/d_com_inf_game.h"
#include "d/d_item_data.h"
#include "mods/items.h"
#include <fmt/format.h>
#include <algorithm>
#include <array>
#include <cstring>
#include <exception>
#include <string>
#include <unordered_map>
@@ -45,6 +50,78 @@ struct PendingResolve {
void* userData = nullptr;
};
struct MessageCheck {
uint16_t group = 0;
uint16_t messageId = 0;
const char* name = nullptr;
uint8_t vanillaItem = 0;
bool enqueueAtDisplay = true;
};
constexpr std::array kMessageChecks{
MessageCheck{
.group = 0,
.messageId = 1822,
.name = ITEM_CHECK_FISHING_BOTTLE,
.vanillaItem = dItemNo_EMPTY_BOTTLE_e,
},
MessageCheck{
.group = 7,
.messageId = 7564,
.name = ITEM_CHECK_FISHING_HEART_PIECE,
.vanillaItem = dItemNo_KAKERA_HEART_e,
},
MessageCheck{
.group = 7,
.messageId = 7578,
.name = ITEM_CHECK_FISHING_HEART_PIECE,
.vanillaItem = dItemNo_KAKERA_HEART_e,
},
MessageCheck{
.group = 5,
.messageId = 5001,
.name = ITEM_CHECK_DUNGEON_REWARD_FOREST,
.vanillaItem = dItemNo_NONE_e,
},
MessageCheck{
.group = 5,
.messageId = 6011,
.name = ITEM_CHECK_DUNGEON_REWARD_GORON,
.vanillaItem = dItemNo_NONE_e,
},
MessageCheck{
.group = 5,
.messageId = 7001,
.name = ITEM_CHECK_DUNGEON_REWARD_LAKEBED,
.vanillaItem = dItemNo_NONE_e,
},
MessageCheck{
.group = 5,
.messageId = 9301,
.name = ITEM_CHECK_DUNGEON_REWARD_SNOWPEAK,
.vanillaItem = dItemNo_NONE_e,
},
MessageCheck{
.group = 5,
.messageId = 9401,
.name = ITEM_CHECK_DUNGEON_REWARD_TIME,
.vanillaItem = dItemNo_NONE_e,
},
MessageCheck{
.group = 5,
.messageId = 11001,
.name = ITEM_CHECK_DUNGEON_REWARD_CITY,
.vanillaItem = dItemNo_NONE_e,
},
MessageCheck{
.group = 0,
.messageId = 233,
.name = ITEM_CHECK_ILIA_MEMORY,
.vanillaItem = dItemNo_HORSE_FLUTE_e,
.enqueueAtDisplay = false,
},
};
std::unordered_map<LoadedMod*, ModItemChecks> s_modChecks;
std::unordered_set<std::string> s_warnedCollisions;
ItemCheckHandle s_nextCheckHandle = 1;
@@ -66,6 +143,10 @@ std::string freestanding_check_name(uint8_t bitNo) {
return fmt::format("freestanding:{}:{}", current_stage_name(), bitNo);
}
std::string golden_wolf_check_name(uint16_t eventFlag) {
return fmt::format("golden_wolf:{}", eventFlag);
}
std::string poe_check_name(uint8_t bitNo) {
return fmt::format("poe:{}:{}", current_stage_name(), bitNo);
}
@@ -79,14 +160,35 @@ std::string bug_check_name(uint8_t insectId) {
}
std::string sky_character_check_name() {
return fmt::format("skychar:{}:{}", current_stage_name(), dStage_roomControl_c::getStayNo());
return fmt::format("sky:{}:{}", current_stage_name(), dStage_roomControl_c::getStayNo());
}
bool ilia_memory_context() {
return std::strcmp(current_stage_name(), "R_SP109") == 0 &&
dComIfGp_roomControl_getStayNo() == 0 && dComIfG_play_c::getLayerNo(0) == 9;
}
uint16_t item_get_message(uint8_t itemNo) {
if (itemNo == dItemNo_KAKERA_HEART_e) {
constexpr std::array<uint16_t, 5> heartPieceMessages{0x86, 0x9C, 0x9D, 0x9E, 0x9F};
return heartPieceMessages[dComIfGs_getMaxLife() % heartPieceMessages.size()];
}
return static_cast<uint16_t>(itemNo + 0x65);
}
void invalidate_committed_check(const std::string& name) {
if (name.empty()) {
item_check_clear_committed();
} else {
item_check_cancel(item_give_tag(name.c_str()));
}
}
} // namespace
uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver) {
ItemCheckResolution item_check_resolve(const char* name, uint8_t itemNo, fopAc_ac_c* giver) {
if (name == nullptr || *name == '\0' || s_modChecks.empty()) {
return itemNo;
return {.item = itemNo, .display_item = itemNo};
}
// Callbacks may change registrations, so copy the applicable chain before invoking one.
@@ -126,6 +228,7 @@ uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver) {
.giver_actor = giver,
.vanilla_item = itemNo,
.current_item = itemNo,
.current_display_item = itemNo,
};
for (const auto& resolve : resolves) {
if (!resolve.mod->active) {
@@ -133,13 +236,21 @@ uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver) {
}
if (resolve.fixedValue) {
info.current_item = resolve.itemNo;
info.current_display_item = resolve.itemNo;
continue;
}
uint8_t resolvedItem = info.current_item;
ItemCheckResolution resolution{
.item = info.current_item,
.display_item = dItemNo_NONE_e,
};
try {
if (resolve.fn(resolve.mod->context.get(), &info, &resolvedItem, resolve.userData)) {
info.current_item = resolvedItem;
if (resolve.fn(resolve.mod->context.get(), &info, &resolution, resolve.userData)) {
if (resolution.display_item == UINT8_MAX) {
resolution.display_item = resolution.item;
}
info.current_item = resolution.item;
info.current_display_item = resolution.display_item;
}
} catch (const std::exception& e) {
fail_mod(*resolve.mod, MOD_ERROR,
@@ -149,7 +260,14 @@ uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver) {
fmt::format("Unknown exception in item check resolver for '{}'", name));
}
}
return info.current_item;
return {
.item = info.current_item,
.display_item = info.current_display_item,
};
}
uint8_t item_check(const char* name, uint8_t itemNo, fopAc_ac_c* giver) {
return item_check_resolve(name, itemNo, giver).item;
}
uint8_t item_check_chest(uint8_t boxNo, uint8_t itemNo, fopAc_ac_c* chest) {
@@ -176,12 +294,12 @@ uint8_t item_check_freestanding(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* item)
return item_check(name.c_str(), itemNo, item);
}
uint8_t item_check_poe(uint8_t bitNo, uint8_t itemNo, fopAc_ac_c* poe) {
uint8_t item_check_golden_wolf(uint16_t eventFlag, uint8_t itemNo, fopAc_ac_c* item) {
if (s_modChecks.empty()) {
return itemNo;
}
const auto name = poe_check_name(bitNo);
return item_check(name.c_str(), itemNo, poe);
const auto name = golden_wolf_check_name(eventFlag);
return item_check(name.c_str(), itemNo, item);
}
uint8_t item_check_shop(uint8_t itemNo, fopAc_ac_c* giver) {
@@ -192,20 +310,24 @@ uint8_t item_check_shop(uint8_t itemNo, fopAc_ac_c* giver) {
return item_check(name.c_str(), itemNo, giver);
}
uint8_t item_check_bug(uint8_t insectId, uint8_t itemNo, fopAc_ac_c* agitha) {
if (s_modChecks.empty()) {
return itemNo;
}
const auto name = bug_check_name(insectId);
return item_check(name.c_str(), itemNo, agitha);
}
uint32_t item_check_message(uint16_t group, uint32_t messageId) {
for (const auto& check : kMessageChecks) {
if (check.group != group || check.messageId != messageId ||
(check.group == 0 && check.messageId == 233 && !ilia_memory_context()))
{
continue;
}
uint8_t item_check_sky_character(uint8_t itemNo, fopAc_ac_c* statue) {
if (s_modChecks.empty()) {
return itemNo;
const ItemCheckResult result = item_check_commit(check.name, check.vanillaItem, nullptr);
if (result.itemNo == check.vanillaItem || result.itemNo == dItemNo_NONE_e) {
return messageId;
}
if (check.enqueueAtDisplay) {
item_check_enqueue(result, ItemGiveMode::Silent);
}
return item_get_message(result.itemNo);
}
const auto name = sky_character_check_name();
return item_check(name.c_str(), itemNo, statue);
return messageId;
}
uint32_t item_give_tag_chest(uint8_t boxNo) {
@@ -220,6 +342,10 @@ uint32_t item_give_tag_freestanding(uint8_t bitNo) {
return item_give_tag(freestanding_check_name(bitNo).c_str());
}
uint32_t item_give_tag_golden_wolf(uint16_t eventFlag) {
return item_give_tag(golden_wolf_check_name(eventFlag).c_str());
}
uint32_t item_give_tag_poe(uint8_t bitNo) {
return item_give_tag(poe_check_name(bitNo).c_str());
}
@@ -236,10 +362,6 @@ uint32_t item_give_tag_sky_character() {
return item_give_tag(sky_character_check_name().c_str());
}
void item_check_enqueue_poe(uint8_t bitNo, uint8_t itemNo) {
item_check_enqueue(poe_check_name(bitNo).c_str(), itemNo);
}
namespace svc {
ModResult item_check_set_override(LoadedMod& mod, const char* name, uint8_t itemNo) {
@@ -247,10 +369,12 @@ ModResult item_check_set_override(LoadedMod& mod, const char* name, uint8_t item
for (auto& checkOverride : checks.overrides) {
if (checkOverride.name == name) {
checkOverride.itemNo = itemNo;
invalidate_committed_check(checkOverride.name);
return MOD_OK;
}
}
checks.overrides.push_back({.name = name, .itemNo = itemNo});
invalidate_committed_check(checks.overrides.back().name);
return MOD_OK;
}
@@ -261,6 +385,9 @@ ModResult item_check_clear_override(LoadedMod& mod, const char* name) {
}
const auto removed = std::erase_if(modIt->second.overrides,
[&](const auto& checkOverride) { return checkOverride.name == name; });
if (removed != 0) {
invalidate_committed_check(name);
}
return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT;
}
@@ -272,6 +399,7 @@ ModResult item_check_add_resolver(LoadedMod& mod, const char* name, ItemCheckRes
resolver.fn = fn;
resolver.userData = userData;
outHandle = resolver.handle;
invalidate_committed_check(resolver.name);
return MOD_OK;
}
@@ -280,12 +408,31 @@ ModResult item_check_remove_resolver(LoadedMod& mod, ItemCheckHandle handle) {
if (modIt == s_modChecks.end()) {
return MOD_INVALID_ARGUMENT;
}
const auto removed = std::erase_if(
modIt->second.resolvers, [&](const auto& resolver) { return resolver.handle == handle; });
return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT;
const auto resolverIt =
std::find_if(modIt->second.resolvers.begin(), modIt->second.resolvers.end(),
[&](const auto& resolver) { return resolver.handle == handle; });
if (resolverIt == modIt->second.resolvers.end()) {
return MOD_INVALID_ARGUMENT;
}
const std::string name = resolverIt->name;
modIt->second.resolvers.erase(resolverIt);
invalidate_committed_check(name);
return MOD_OK;
}
void item_checks_remove_mod(LoadedMod& mod) {
if (const auto modIt = s_modChecks.find(&mod); modIt != s_modChecks.end()) {
for (const auto& resolver : modIt->second.resolvers) {
if (resolver.name.empty()) {
item_check_clear_committed();
break;
}
invalidate_committed_check(resolver.name);
}
for (const auto& checkOverride : modIt->second.overrides) {
invalidate_committed_check(checkOverride.name);
}
}
s_modChecks.erase(&mod);
s_warnedCollisions.clear();
}
+146 -36
View File
@@ -17,6 +17,7 @@
#include <exception>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace dusk::mods {
@@ -28,6 +29,8 @@ aurora::Module Log{"dusk::mods::item_gives"};
std::deque<std::string> s_giveNames;
std::unordered_map<std::string, uint32_t> s_giveNameIds;
detail::ItemCommitStore s_committedChecks;
const char* item_give_name(uint32_t tag) {
if (tag == 0 || tag > s_giveNames.size()) {
return nullptr;
@@ -92,7 +95,18 @@ void notify_gives(const char* checkName, uint8_t itemNo, fopAc_ac_c* giver, Item
}
}
constexpr size_t kGiveQueueLimit = 64;
void complete_check(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver, ItemGiveOrigin origin) {
if (const auto* committed = s_committedChecks.find(giveTag);
committed != nullptr && committed->resolution.item != itemNo)
{
Log.error("committed check '{}' completed with item {:#x} instead of {:#x}",
item_give_name(giveTag) != nullptr ? item_give_name(giveTag) : "", itemNo,
committed->resolution.item);
}
s_committedChecks.erase(giveTag);
notify_gives(item_give_name(giveTag), itemNo, giver, origin);
}
constexpr int kGiveMaxRetries = 5;
struct QueuedGive {
@@ -101,6 +115,7 @@ struct QueuedGive {
uint8_t itemNo = 0;
bool silent = false;
bool resolveAtDispatch = false;
bool forced = false;
};
std::deque<QueuedGive> s_giveQueue;
@@ -111,6 +126,11 @@ bool s_inFlight = false;
bool s_inFlightSpawned = false;
bool s_dispatchingSilent = false;
bool has_forced_give() {
return std::any_of(
s_giveQueue.begin(), s_giveQueue.end(), [](const QueuedGive& give) { return give.forced; });
}
bool safe_to_dispatch() {
daAlink_c* link = daAlink_getAlinkActorClass();
if (link == nullptr) {
@@ -142,13 +162,13 @@ bool safe_to_dispatch() {
bool resolve_queued_give(const QueuedGive& give, ItemGiveOrigin origin, uint8_t& outItem) {
outItem = give.itemNo;
if (give.resolveAtDispatch) {
outItem = item_check(item_give_name(give.tag), give.itemNo, nullptr);
outItem = item_check_commit(give.tag, give.itemNo, nullptr).itemNo;
}
if (outItem != dItemNo_NONE_e) {
return true;
}
notify_gives(item_give_name(give.tag), dItemNo_NONE_e, nullptr, origin);
complete_check(dItemNo_NONE_e, give.tag, nullptr, origin);
return false;
}
@@ -171,11 +191,40 @@ void dispatch_demo_give() {
daAlink_c* link = daAlink_getAlinkActorClass();
dComIfGp_getEvent()->setGtItm(s_inFlightItem);
link->procCoGetItemInit();
link->mProcID = daAlink_c::PROC_GET_ITEM;
const s16 eventIndex = dComIfGp_getEventManager().getEventIdx(link, "DEFAULT_GETITEM", 0xFF);
fopAcM_orderChangeEventId(link, eventIndex, 1, 0xFFFF);
}
void dispatch_next_give() {
while (!s_giveQueue.empty()) {
while (!s_giveQueue.empty() && s_giveQueue.front().silent) {
const QueuedGive give = s_giveQueue.front();
s_giveQueue.pop_front();
dispatch_silent_give(give);
}
if (s_giveQueue.empty()) {
return;
}
const QueuedGive give = s_giveQueue.front();
s_giveQueue.pop_front();
uint8_t itemNo = 0;
if (!resolve_queued_give(give, ITEM_GIVE_ORIGIN_QUEUE, itemNo)) {
continue;
}
s_inFlightGive = give;
s_inFlightItem = itemNo;
s_inFlightRetries = 0;
s_inFlight = true;
s_inFlightSpawned = false;
dispatch_demo_give();
return;
}
}
} // namespace
uint32_t item_give_tag(const char* name) {
@@ -192,16 +241,47 @@ uint32_t item_give_tag(const char* name) {
return tag;
}
uint8_t item_check_tagged(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver) {
ItemCheckResolution item_check_resolve(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver) {
if (const auto* committed = s_committedChecks.find(giveTag); committed != nullptr) {
return committed->resolution;
}
const char* name = item_give_name(giveTag);
return name != nullptr ? item_check(name, itemNo, giver) : itemNo;
return name != nullptr ? item_check_resolve(name, itemNo, giver) :
ItemCheckResolution{.item = itemNo, .display_item = itemNo};
}
void item_check_enqueue(const char* name, uint8_t itemNo) {
if (s_giveQueue.size() >= kGiveQueueLimit) {
Log.warn("item give queue is full; dropping check '{}'", name != nullptr ? name : "");
return;
ItemCheckResult item_check_commit(uint32_t giveTag, uint8_t itemNo, fopAc_ac_c* giver) {
const char* name = item_give_name(giveTag);
if (name == nullptr) {
return {.tag = giveTag, .itemNo = itemNo, .displayItemNo = itemNo};
}
if (const auto* committed = s_committedChecks.find(giveTag); committed != nullptr) {
if (committed->vanillaItem != itemNo) {
Log.error("committed check '{}' changed vanilla item from {:#x} to {:#x}", name,
committed->vanillaItem, itemNo);
}
return {
.tag = giveTag,
.itemNo = committed->resolution.item,
.displayItemNo = committed->resolution.display_item,
};
}
const auto& committed = s_committedChecks.commit(
giveTag, itemNo, [&] { return item_check_resolve(name, itemNo, giver); });
return {
.tag = giveTag,
.itemNo = committed.resolution.item,
.displayItemNo = committed.resolution.display_item,
};
}
ItemCheckResult item_check_commit(const char* name, uint8_t itemNo, fopAc_ac_c* giver) {
return item_check_commit(item_give_tag(name), itemNo, giver);
}
void item_check_enqueue_deferred(const char* name, uint8_t itemNo) {
s_giveQueue.push_back({
.tag = item_give_tag(name),
.itemNo = itemNo,
@@ -209,6 +289,53 @@ void item_check_enqueue(const char* name, uint8_t itemNo) {
});
}
bool item_check_enqueue(ItemCheckResult check, ItemGiveMode mode) {
auto* committed = s_committedChecks.find(check.tag);
if (committed == nullptr) {
Log.error("cannot enqueue uncommitted check '{}'",
item_give_name(check.tag) != nullptr ? item_give_name(check.tag) : "");
return false;
}
if (committed->resolution.item != check.itemNo) {
Log.error("committed check '{}' changed item from {:#x} to {:#x}",
item_give_name(check.tag) != nullptr ? item_give_name(check.tag) : "", check.itemNo,
committed->resolution.item);
return false;
}
if (committed->queued) {
return false;
}
committed->queued = true;
s_giveQueue.push_back({
.tag = check.tag,
.itemNo = committed->resolution.item,
.silent = mode == ItemGiveMode::Silent,
.forced = mode == ItemGiveMode::ForcedDemo,
});
if (mode == ItemGiveMode::ForcedDemo && !s_inFlight) {
dispatch_next_give();
}
return true;
}
void item_check_complete(ItemCheckResult check, fopAc_ac_c* giver) {
complete_check(check.itemNo, check.tag, giver, ITEM_GIVE_ORIGIN_GAME);
}
void item_check_cancel(uint32_t giveTag) {
s_committedChecks.erase(giveTag);
std::erase_if(s_giveQueue,
[&](const QueuedGive& give) { return give.owner == nullptr && give.tag == giveTag; });
}
void item_check_clear_committed() {
std::erase_if(s_giveQueue, [&](const QueuedGive& give) {
return give.owner == nullptr && s_committedChecks.contains(give.tag);
});
s_committedChecks.clear();
}
void item_granted(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver) {
ItemGiveOrigin origin = ITEM_GIVE_ORIGIN_GAME;
if (s_dispatchingSilent) {
@@ -218,7 +345,7 @@ void item_granted(uint8_t itemNo, uint32_t giveTag, fopAc_ac_c* giver) {
s_inFlight = false;
s_inFlightSpawned = false;
}
notify_gives(item_give_name(giveTag), itemNo, giver, origin);
complete_check(itemNo, giveTag, giver, origin);
}
bool item_give_queue_dispatching() {
@@ -236,17 +363,21 @@ uint32_t item_give_queue_take_tag() {
namespace svc {
void item_gives_tick() {
if ((!s_inFlight && s_giveQueue.empty()) || !safe_to_dispatch()) {
if (!s_inFlight && s_giveQueue.empty()) {
return;
}
if (s_inFlight) {
if (!safe_to_dispatch()) {
return;
}
if (++s_inFlightRetries > kGiveMaxRetries) {
Log.error("item {:#x} for '{}' did not complete after {} attempts; dropping it",
s_inFlightItem,
item_give_name(s_inFlightGive.tag) != nullptr ? item_give_name(s_inFlightGive.tag) :
"",
kGiveMaxRetries);
item_check_cancel(s_inFlightGive.tag);
s_inFlight = false;
s_inFlightSpawned = false;
return;
@@ -256,29 +387,10 @@ void item_gives_tick() {
return;
}
while (!s_giveQueue.empty() && s_giveQueue.front().silent) {
const QueuedGive give = s_giveQueue.front();
s_giveQueue.pop_front();
dispatch_silent_give(give);
}
if (s_giveQueue.empty()) {
if (!has_forced_give() && !safe_to_dispatch()) {
return;
}
const QueuedGive give = s_giveQueue.front();
uint8_t itemNo = 0;
if (!resolve_queued_give(give, ITEM_GIVE_ORIGIN_QUEUE, itemNo)) {
s_giveQueue.pop_front();
return;
}
s_giveQueue.pop_front();
s_inFlightGive = give;
s_inFlightItem = itemNo;
s_inFlightRetries = 0;
s_inFlight = true;
s_inFlightSpawned = false;
dispatch_demo_give();
dispatch_next_give();
}
void item_gives_clear() {
@@ -289,12 +401,10 @@ void item_gives_clear() {
s_giveQueue.clear();
s_inFlight = false;
s_inFlightSpawned = false;
s_committedChecks.clear();
}
ModResult item_give_enqueue(LoadedMod& mod, const char* checkName, uint8_t itemNo, uint32_t flags) {
if (s_giveQueue.size() >= kGiveQueueLimit) {
return MOD_UNAVAILABLE;
}
s_giveQueue.push_back({
.owner = &mod,
.tag = item_give_tag(checkName),
+12 -3
View File
@@ -11,6 +11,7 @@
#include "JSystem/JMessage/control.h"
#include "JSystem/JMessage/processor.h"
#include "JSystem/JMessage/resource.h"
#include "d/d_msg_class.h"
#include <algorithm>
#include <array>
@@ -467,11 +468,19 @@ bool valid_encoded_text(const uint8_t* text, size_t size) {
if (text[offset] == 0) {
return offset + 1 == size;
}
if (text[offset] == 0x1a) {
if (text[offset] == JMessage::data::gcTagBegin) {
if (offset + 2 > size || text[offset + 1] < 5 || text[offset + 1] > size - offset) {
return false;
}
offset += text[offset + 1];
const size_t tagSize = text[offset + 1];
const uint32_t tag =
MSGTAG_GROUP(text[offset + 2]) | read_bits<uint16_t>(text + offset + 3);
const size_t argumentSize = tagSize - 5;
if (tag == MSGTAG_COLOR && argumentSize != 1 && argumentSize != 4 && argumentSize != 8)
{
return false;
}
offset += tagSize;
} else {
++offset;
}
@@ -490,7 +499,7 @@ size_t encoded_text_size(const ResourceInfo& resource, const char* text) {
if (bytes[offset] == 0) {
return offset + 1;
}
if (bytes[offset] == 0x1a) {
if (bytes[offset] == JMessage::data::gcTagBegin) {
if (offset + 2 > maximum || bytes[offset + 1] < 5 ||
bytes[offset + 1] > maximum - offset)
{
+1
View File
@@ -214,6 +214,7 @@ void ModLoader::init_services() {
&svc::g_overlayModule,
&svc::g_textureModule,
&svc::g_configModule,
&svc::g_uiModule_v1,
&svc::g_uiModule,
&svc::g_gameModule,
&svc::g_cameraModule,
+1
View File
@@ -72,6 +72,7 @@ extern const ServiceModule g_hookModule;
extern const ServiceModule g_overlayModule;
extern const ServiceModule g_textureModule;
extern const ServiceModule g_configModule;
extern const ServiceModule g_uiModule_v1;
extern const ServiceModule g_uiModule;
extern const ServiceModule g_gameModule;
extern const ServiceModule g_cameraModule;
+407 -59
View File
@@ -3,10 +3,13 @@
#include "config.hpp"
#include "registry.hpp"
#include "slot_map.hpp"
#include "ui_v1.hpp"
#include <borealis/log.hpp>
#include "dusk/mod_loader.hpp"
#include "dusk/mods/loader/loader.hpp"
#include "dusk/mods/log_buffer.hpp"
#include "dusk/ui/list.hpp"
#include "dusk/ui/menu_bar.hpp"
#include "dusk/ui/mod_window.hpp"
#include "dusk/ui/modal.hpp"
@@ -20,17 +23,33 @@
#include <algorithm>
#include <chrono>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "SDL3/SDL_clipboard.h"
namespace {
constexpr size_t kUiControlSelectedSize =
offsetof(UiControlDesc, is_selected) + sizeof(UiPredicateFn);
constexpr size_t kUiControlStringSetModeSize =
offsetof(UiControlDesc, string_set_mode) + sizeof(UiStringSetMode);
constexpr size_t kUiListItemV21Size = offsetof(UiListItem, label) + sizeof(const char*);
constexpr size_t kUiListDescV21Size = offsetof(UiListDesc, user_data) + sizeof(void*);
} // namespace
namespace dusk::mods::svc::ui_impl {
namespace {
@@ -43,6 +62,7 @@ enum class UiSlotKind : u8 {
Text,
Progress,
Control,
List,
Style,
MenuTab,
};
@@ -61,6 +81,8 @@ const char* slot_kind_name(UiSlotKind kind) {
return "progress";
case UiSlotKind::Control:
return "control";
case UiSlotKind::List:
return "list";
case UiSlotKind::Style:
return "style";
case UiSlotKind::MenuTab:
@@ -79,6 +101,8 @@ struct UiSlot {
// Pane payload
ui::Pane* pane = nullptr;
ui::Pane* helpPane = nullptr;
// List payload
ui::List* list = nullptr;
// Window/Dialog payload (non-owning; the document stack owns the document)
ui::Document* document = nullptr;
UiWindowClosedFn onClosed = nullptr;
@@ -248,18 +272,16 @@ void wire_callback_binding(
if (!slot_live(guardHandle)) {
return value;
}
guarded_call(*modPtr, "control getter", [&] {
get(modPtr->context.get(), userData, &value);
});
guarded_call(
*modPtr, "control getter", [&] { get(modPtr->context.get(), userData, &value); });
return value;
};
const auto setValue = [modPtr, set, userData, guardHandle](const UiControlValue& value) {
if (!slot_live(guardHandle)) {
return;
}
guarded_call(*modPtr, "control setter", [&] {
set(modPtr->context.get(), userData, &value);
});
guarded_call(
*modPtr, "control setter", [&] { set(modPtr->context.get(), userData, &value); });
};
switch (desc.kind) {
case UI_CONTROL_TOGGLE:
@@ -280,6 +302,7 @@ void wire_callback_binding(
};
break;
case UI_CONTROL_STRING:
case UI_CONTROL_COLOR:
spec.getString = [getValue]() -> Rml::String {
const UiControlValue value = getValue();
return value.string_value != nullptr ? value.string_value : "";
@@ -358,7 +381,8 @@ bool wire_config_var_binding(LoadedMod& mod, const UiControlDesc& desc, ui::ModC
}
return true;
}
case UI_CONTROL_STRING: {
case UI_CONTROL_STRING:
case UI_CONTROL_COLOR: {
const auto find = [modPtr, varHandle] {
return static_cast<ConfigVar<std::string>*>(
config_find_var(*modPtr, varHandle, CONFIG_VAR_STRING));
@@ -401,9 +425,8 @@ void on_mod_window_destroyed(uint64_t handle) {
const UiWindowClosedFn onClosed = released->value.onClosed;
void* userData = released->value.onClosedUserData;
if (mod != nullptr && onClosed != nullptr) {
guarded_call(*mod, "window on_closed callback", [&] {
onClosed(mod->context.get(), handle, userData);
});
guarded_call(*mod, "window on_closed callback",
[&] { onClosed(mod->context.get(), handle, userData); });
}
}
@@ -439,10 +462,8 @@ void push_stacked_document(std::unique_ptr<ui::Document> document) {
}
}
// Shared by dialog_push and dialog_add_action; the guard handle keeps a
// pressed callback from calling into a torn-down mod.
ui::ModalAction make_dialog_action(LoadedMod& mod, uint64_t handle, const UiDialogAction& action) {
return {
ui::ModalAction result{
.label = action.label,
.onPressed =
[modPtr = &mod, handle, fn = action.on_pressed, userData = action.user_data,
@@ -451,9 +472,8 @@ ui::ModalAction make_dialog_action(LoadedMod& mod, uint64_t handle, const UiDial
return; // already being torn down
}
if (fn != nullptr) {
guarded_call(*modPtr, "dialog action callback", [&] {
fn(modPtr->context.get(), handle, userData);
});
guarded_call(*modPtr, "dialog action callback",
[&] { fn(modPtr->context.get(), handle, userData); });
}
// The callback may have closed the dialog already
if (!keepOpen && dialog_open(handle)) {
@@ -461,6 +481,17 @@ ui::ModalAction make_dialog_action(LoadedMod& mod, uint64_t handle, const UiDial
}
},
};
if (action.is_disabled != nullptr) {
result.isDisabled = [modPtr = &mod, handle, fn = action.is_disabled,
userData = action.user_data] {
if (!dialog_open(handle)) {
return false;
}
return guarded_call(*modPtr, "dialog action is_disabled callback", false,
[&] { return fn(modPtr->context.get(), userData); });
};
}
return result;
}
} // namespace
@@ -494,9 +525,8 @@ void ui_update_mods_panels(LoadedMod& mod) {
if (!mod.active || panel.update == nullptr) {
return;
}
invoke_mod_ui_callback(mod, "mod UI panel update", [&](ModError* error) {
return panel.update(mod.context.get(), panel.userData, error);
});
invoke_mod_ui_callback(mod, "mod UI panel update",
[&](ModError* error) { return panel.update(mod.context.get(), panel.userData, error); });
}
ModResult ui_pane_add_section(LoadedMod& mod, uint64_t pane, const char* title) {
@@ -568,15 +598,19 @@ ModResult ui_pane_add_control(
spec.isModified = wrap_predicate(mod, desc.is_modified, desc.user_data, pane);
switch (desc.kind) {
case UI_CONTROL_BUTTON:
spec.kind = ui::ModControlSpec::Kind::Button;
case UI_CONTROL_GROUP:
spec.kind = desc.kind == UI_CONTROL_BUTTON ? ui::ModControlSpec::Kind::Button :
ui::ModControlSpec::Kind::Group;
if (desc.struct_size >= kUiControlSelectedSize) {
spec.isSelected = wrap_predicate(mod, desc.is_selected, desc.user_data, pane);
}
spec.onPressed = [modPtr = &mod, fn = desc.on_pressed, userData = desc.user_data,
guardHandle = pane] {
if (!slot_live(guardHandle)) {
return;
}
guarded_call(*modPtr, "control on_pressed callback", [&] {
fn(modPtr->context.get(), userData);
});
guarded_call(*modPtr, "control on_pressed callback",
[&] { fn(modPtr->context.get(), userData); });
};
break;
case UI_CONTROL_TOGGLE:
@@ -598,6 +632,15 @@ ModResult ui_pane_add_control(
case UI_CONTROL_STRING:
spec.kind = ui::ModControlSpec::Kind::String;
spec.maxLength = desc.max_length < 1 ? -1 : desc.max_length;
spec.stringSetOnChange = desc.struct_size >= kUiControlStringSetModeSize &&
desc.string_set_mode == UI_STRING_SET_ON_CHANGE;
break;
case UI_CONTROL_COLOR:
spec.kind = ui::ModControlSpec::Kind::Color;
spec.colorAlpha = desc.color_alpha;
for (size_t i = 0; i < desc.color_preset_count; ++i) {
spec.colorPresets.emplace_back(desc.color_presets[i]);
}
break;
case UI_CONTROL_SELECT:
spec.kind = ui::ModControlSpec::Kind::Select;
@@ -614,7 +657,7 @@ ModResult ui_pane_add_control(
return MOD_INVALID_ARGUMENT;
}
if (desc.kind != UI_CONTROL_BUTTON) {
if (desc.kind != UI_CONTROL_BUTTON && desc.kind != UI_CONTROL_GROUP) {
if (desc.binding == UI_BINDING_CONFIG_VAR) {
if (!wire_config_var_binding(mod, desc, spec)) {
Log.error("[{}] pane_add_control: config var handle {:#x} is unknown or its type "
@@ -641,6 +684,102 @@ ModResult ui_pane_add_control(
return MOD_OK;
}
ModResult ui_pane_add_list(LoadedMod& mod, uint64_t pane, const UiListDesc& desc,
std::vector<ui::List::Item> items, uint64_t& outHandle) {
outHandle = 0;
auto* paneSlot = resolve(mod, pane, UiSlotKind::Pane, "pane_add_list");
if (paneSlot == nullptr) {
return MOD_INVALID_ARGUMENT;
}
auto* paneComponent = paneSlot->pane;
uint64_t handle = 0;
alloc_slot(mod, UiSlotKind::List, handle);
ui::List::Props props;
props.items = std::move(items);
props.onPressed = [modPtr = &mod, handle, fn = desc.on_pressed, userData = desc.user_data](
uint64_t key) {
if (!slot_live(handle)) {
return;
}
guarded_call(*modPtr, "list on_pressed callback",
[&] { fn(modPtr->context.get(), handle, key, userData); });
};
if (desc.is_selected != nullptr) {
props.isSelected = [modPtr = &mod, handle, fn = desc.is_selected,
userData = desc.user_data](uint64_t key) {
if (!slot_live(handle)) {
return false;
}
return guarded_call(*modPtr, "list is_selected callback", false,
[&] { return fn(modPtr->context.get(), handle, key, userData); });
};
}
if (desc.is_disabled != nullptr) {
props.isDisabled = [modPtr = &mod, handle, fn = desc.is_disabled,
userData = desc.user_data](uint64_t key) {
if (!slot_live(handle)) {
return false;
}
return guarded_call(*modPtr, "list is_disabled callback", false,
[&] { return fn(modPtr->context.get(), handle, key, userData); });
};
}
auto& list = paneComponent->add_child<ui::List>(std::move(props));
auto* listSlot = slot_from_handle(handle);
if (listSlot == nullptr) {
return MOD_ERROR;
}
listSlot->list = &list;
track_element(handle, *listSlot, *list.root());
outHandle = handle;
return MOD_OK;
}
ModResult ui_list_set_items(LoadedMod& mod, uint64_t handle, std::vector<ui::List::Item> items) {
auto* slot = resolve(mod, handle, UiSlotKind::List, "list_set_items");
if (slot == nullptr || slot->list == nullptr) {
return MOD_INVALID_ARGUMENT;
}
slot->list->set_items(std::move(items));
return MOD_OK;
}
ModResult ui_pane_add_group(LoadedMod& mod, uint64_t groupPaneHandle, uint64_t targetPaneHandle,
const UiGroupDesc& desc, uint64_t* outElem) {
auto* groupSlot = resolve(mod, groupPaneHandle, UiSlotKind::Pane, "pane_add_group");
auto* targetSlot = resolve(mod, targetPaneHandle, UiSlotKind::Pane, "pane_add_group");
if (groupSlot == nullptr || targetSlot == nullptr) {
return MOD_INVALID_ARGUMENT;
}
if (groupSlot->helpPane != targetSlot->pane) {
Log.error("[{}] pane_add_group: panes are not a paired tab layout", mod.metadata.id);
return MOD_UNSUPPORTED;
}
auto* groupPane = groupSlot->pane;
auto* targetPane = targetSlot->pane;
auto& button = groupPane->add_group_button(ui::GroupButton::Props{.text = desc.label});
groupPane->register_control(button, *targetPane,
[modPtr = &mod, groupPaneHandle, targetPaneHandle, build = desc.build,
userData = desc.user_data](ui::Pane&) {
if (!slot_live(groupPaneHandle) || !slot_live(targetPaneHandle) || !modPtr->active) {
return;
}
invoke_mod_ui_callback(*modPtr, "mod UI group build", [&](ModError* error) {
return build(modPtr->context.get(), targetPaneHandle, userData, error);
});
});
if (outElem != nullptr) {
auto& elemSlot = alloc_slot(mod, UiSlotKind::Control, *outElem);
track_element(*outElem, elemSlot, *button.root());
}
return MOD_OK;
}
ModResult ui_elem_set_text(LoadedMod& mod, uint64_t elem, const char* text) {
auto* slot = resolve(mod, elem, UiSlotKind::Text, "elem_set_text");
if (slot == nullptr) {
@@ -798,20 +937,32 @@ ModResult ui_dialog_push(LoadedMod& mod, const UiDialogDesc& desc, uint64_t& out
return; // already being torn down
}
if (fn != nullptr) {
guarded_call(*modPtr, "dialog on_dismiss callback", [&] {
fn(modPtr->context.get(), handle, userData);
});
guarded_call(*modPtr, "dialog on_dismiss callback",
[&] { fn(modPtr->context.get(), handle, userData); });
}
if (dialog_open(handle)) {
static_cast<ModDialog&>(modal).close();
}
};
auto* actionData = reinterpret_cast<const std::byte*>(desc.actions);
for (size_t i = 0; i < desc.action_count; ++i) {
props.actions.push_back(make_dialog_action(mod, handle, desc.actions[i]));
const auto& action = *reinterpret_cast<const UiDialogAction*>(actionData);
props.actions.push_back(make_dialog_action(mod, handle, action));
actionData += action.struct_size;
}
auto dialog =
std::make_unique<ModDialog>(std::move(props), [handle] { on_mod_dialog_destroyed(handle); });
auto dialog = std::make_unique<ModDialog>(
std::move(props), [handle] { on_mod_dialog_destroyed(handle); });
if (desc.build != nullptr) {
auto& pane = dialog->content_pane();
const uint64_t paneHandle = wrap_pane(mod, pane, nullptr);
invoke_mod_ui_callback(mod, "mod UI dialog build", [&](ModError* error) {
return desc.build(mod.context.get(), paneHandle, desc.user_data, error);
});
if (!mod.active) {
return MOD_ERROR;
}
}
if (auto* slot = slot_from_handle(handle)) {
slot->document = dialog.get();
}
@@ -848,15 +999,6 @@ ModResult ui_dialog_set_icon(LoadedMod& mod, uint64_t handle, const char* icon)
return MOD_OK;
}
ModResult ui_dialog_add_action(LoadedMod& mod, uint64_t handle, const UiDialogAction& action) {
auto* slot = resolve(mod, handle, UiSlotKind::Dialog, "dialog_add_action");
if (slot == nullptr || slot->document == nullptr) {
return MOD_INVALID_ARGUMENT;
}
static_cast<ModDialog*>(slot->document)->add_action(make_dialog_action(mod, handle, action));
return MOD_OK;
}
ModResult ui_register_menu_tab(LoadedMod& mod, const UiMenuTabDesc& desc, uint64_t& outHandle) {
outHandle = 0;
for (const auto& [owner, tabs] : s_modMenuTabs) {
@@ -915,9 +1057,8 @@ std::vector<ModMenuTabEntry> ui_mod_menu_tabs() {
if (!slot_live(handle) || !modPtr->active) {
return; // registered by a since-unloaded mod image
}
guarded_call(*modPtr, "menu tab on_selected callback", [&] {
fn(modPtr->context.get(), userData);
});
guarded_call(*modPtr, "menu tab on_selected callback",
[&] { fn(modPtr->context.get(), userData); });
}});
}
}
@@ -1038,7 +1179,8 @@ void ui_remove_mod(LoadedMod& mod) {
}
}
ModResult ui_get_clipboard_text(LoadedMod& mod, char* buffer, size_t bufferSize, size_t* outLength) {
ModResult ui_get_clipboard_text(
LoadedMod& mod, char* buffer, size_t bufferSize, size_t* outLength) {
if (outLength != nullptr) {
*outLength = 0;
}
@@ -1082,21 +1224,48 @@ namespace {
// Validation of the tagged control descriptor: required fields per kind/binding. Value
// translation and cvar wiring live in loader/ui.cpp.
bool valid_color_preset(const char* value, bool alpha) {
const std::string_view text{value};
if (text == "rainbow") {
return true;
}
if (text.size() != 6 && (!alpha || text.size() != 8)) {
return false;
}
return std::ranges::all_of(text, [](char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
});
}
bool valid_control_desc(const UiControlDesc& desc) {
if (desc.struct_size < sizeof(UiControlDesc) || desc.label == nullptr) {
constexpr size_t kLegacyDescSize = offsetof(UiControlDesc, color_presets);
constexpr size_t kColorDescSize = offsetof(UiControlDesc, is_selected);
if (desc.struct_size < kLegacyDescSize || desc.label == nullptr) {
return false;
}
switch (desc.kind) {
case UI_CONTROL_BUTTON:
case UI_CONTROL_GROUP:
return desc.on_pressed != nullptr;
case UI_CONTROL_TOGGLE:
case UI_CONTROL_NUMBER:
case UI_CONTROL_STRING:
case UI_CONTROL_SELECT:
break;
case UI_CONTROL_COLOR:
if (desc.struct_size < kColorDescSize) {
return false;
}
break;
default:
return false;
}
if (desc.kind == UI_CONTROL_STRING && desc.struct_size >= kUiControlStringSetModeSize &&
desc.string_set_mode != UI_STRING_SET_ON_COMMIT &&
desc.string_set_mode != UI_STRING_SET_ON_CHANGE)
{
return false;
}
if (desc.kind == UI_CONTROL_SELECT) {
if (desc.options == nullptr || desc.option_count == 0) {
return false;
@@ -1107,6 +1276,18 @@ bool valid_control_desc(const UiControlDesc& desc) {
}
}
}
if (desc.kind == UI_CONTROL_COLOR && desc.color_preset_count != 0) {
if (desc.color_presets == nullptr) {
return false;
}
for (size_t i = 0; i < desc.color_preset_count; ++i) {
if (desc.color_presets[i] == nullptr ||
!valid_color_preset(desc.color_presets[i], desc.color_alpha))
{
return false;
}
}
}
switch (desc.binding) {
case UI_BINDING_CALLBACKS:
return desc.get != nullptr && desc.set != nullptr;
@@ -1117,6 +1298,36 @@ bool valid_control_desc(const UiControlDesc& desc) {
}
}
bool copy_list_items(
const UiListItem* items, size_t itemCount, std::vector<ui::List::Item>& outItems) {
if (itemCount != 0 && items == nullptr) {
return false;
}
std::vector<ui::List::Item> copy;
copy.reserve(itemCount);
std::unordered_set<uint64_t> keys;
keys.reserve(itemCount);
auto cursor = reinterpret_cast<uintptr_t>(items);
for (size_t i = 0; i < itemCount; ++i) {
const auto* item = reinterpret_cast<const UiListItem*>(cursor);
const size_t recordSize = item->struct_size;
if (recordSize < kUiListItemV21Size || recordSize % alignof(UiListItem) != 0 ||
cursor > std::numeric_limits<uintptr_t>::max() - recordSize)
{
return false;
}
if (item->label == nullptr || !keys.insert(item->key).second) {
return false;
}
copy.push_back({.key = item->key, .label = item->label});
cursor += recordSize;
}
outItems = std::move(copy);
return true;
}
ModResult ui_register_mods_panel(ModContext* context, const UiModsPanelDesc* desc) {
auto* mod = mod_from_context(context);
if (mod == nullptr || desc == nullptr || desc->struct_size < sizeof(UiModsPanelDesc) ||
@@ -1183,6 +1394,59 @@ ModResult ui_pane_add_control(ModContext* context, UiElementHandle pane, const U
return ui_impl::ui_pane_add_control(*mod, pane, *desc, outElem);
}
ModResult ui_pane_add_list(
ModContext* context, UiElementHandle pane, const UiListDesc* desc, UiListHandle* outList) {
if (outList != nullptr) {
*outList = 0;
}
auto* mod = mod_from_context(context);
if (mod == nullptr || pane == 0 || desc == nullptr || desc->struct_size < kUiListDescV21Size ||
desc->on_pressed == nullptr)
{
return MOD_INVALID_ARGUMENT;
}
std::vector<ui::List::Item> items;
if ((desc->items != nullptr || desc->item_count > 0) &&
!copy_list_items(desc->items, desc->item_count, items))
{
return MOD_INVALID_ARGUMENT;
}
uint64_t handle = 0;
const ModResult result = ui_impl::ui_pane_add_list(*mod, pane, *desc, std::move(items), handle);
if (result == MOD_OK && outList != nullptr) {
*outList = handle;
}
return result;
}
ModResult ui_list_set_items(
ModContext* context, UiListHandle list, const UiListItem* items, size_t itemCount) {
auto* mod = mod_from_context(context);
if (mod == nullptr || list == 0) {
return MOD_INVALID_ARGUMENT;
}
std::vector<ui::List::Item> copiedItems;
if (!copy_list_items(items, itemCount, copiedItems)) {
return MOD_INVALID_ARGUMENT;
}
return ui_impl::ui_list_set_items(*mod, list, std::move(copiedItems));
}
ModResult ui_pane_add_group(ModContext* context, UiElementHandle groupPane,
UiElementHandle targetPane, const UiGroupDesc* desc, UiElementHandle* outElem) {
if (outElem != nullptr) {
*outElem = 0;
}
auto* mod = mod_from_context(context);
if (mod == nullptr || groupPane == 0 || targetPane == 0 || desc == nullptr ||
desc->struct_size < sizeof(UiGroupDesc) || desc->label == nullptr || desc->build == nullptr)
{
return MOD_INVALID_ARGUMENT;
}
return ui_impl::ui_pane_add_group(*mod, groupPane, targetPane, *desc, outElem);
}
ModResult ui_elem_set_text(ModContext* context, UiElementHandle elem, const char* text) {
auto* mod = mod_from_context(context);
if (mod == nullptr || elem == 0 || text == nullptr) {
@@ -1259,10 +1523,15 @@ ModResult ui_dialog_push(ModContext* context, const UiDialogDesc* desc, UiDialog
{
return MOD_INVALID_ARGUMENT;
}
auto* actionData = reinterpret_cast<const std::byte*>(desc->actions);
for (size_t i = 0; i < desc->action_count; ++i) {
if (desc->actions[i].label == nullptr) {
const auto* action = reinterpret_cast<const UiDialogAction*>(actionData);
if (action->struct_size < sizeof(UiDialogAction) ||
action->struct_size % alignof(UiDialogAction) != 0 || action->label == nullptr)
{
return MOD_INVALID_ARGUMENT;
}
actionData += action->struct_size;
}
uint64_t handle = 0;
const auto result = ui_impl::ui_dialog_push(*mod, *desc, handle);
@@ -1397,16 +1666,8 @@ ModResult ui_dialog_set_icon(ModContext* context, UiDialogHandle dialog, const c
return ui_impl::ui_dialog_set_icon(*mod, dialog, icon);
}
ModResult ui_dialog_add_action(
ModContext* context, UiDialogHandle dialog, const UiDialogAction* action) {
auto* mod = mod_from_context(context);
if (mod == nullptr || dialog == 0 || action == nullptr || action->label == nullptr) {
return MOD_INVALID_ARGUMENT;
}
return ui_impl::ui_dialog_add_action(*mod, dialog, *action);
}
ModResult ui_get_clipboard_text(ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength) {
ModResult ui_get_clipboard_text(
ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength) {
auto* mod = mod_from_context(ctx);
if (mod == nullptr || (buffer == nullptr && bufferSize != 0)) {
return MOD_INVALID_ARGUMENT;
@@ -1423,8 +1684,55 @@ ModResult ui_set_clipboard_text(ModContext* ctx, const char* text) {
return ui_impl::ui_set_clipboard_text(*mod, text);
}
constexpr UiService s_uiService{
.header = SERVICE_HEADER(UiService, UI_SERVICE_MAJOR, UI_SERVICE_MINOR),
ModResult ui_v1_dialog_push(
ModContext* context, const ui_v1::UiDialogDesc* desc, UiDialogHandle* outDialog) {
if (outDialog != nullptr) {
*outDialog = 0;
}
constexpr size_t kLegacyDescSize = offsetof(ui_v1::UiDialogDesc, build);
if (desc == nullptr || desc->struct_size < kLegacyDescSize || desc->actions == nullptr ||
desc->action_count == 0)
{
return MOD_INVALID_ARGUMENT;
}
std::vector<UiDialogAction> actions;
actions.reserve(desc->action_count);
for (size_t i = 0; i < desc->action_count; ++i) {
UiDialogAction action = UI_DIALOG_ACTION_INIT;
action.label = desc->actions[i].label;
action.on_pressed = desc->actions[i].on_pressed;
action.user_data = desc->actions[i].user_data;
action.keep_open = desc->actions[i].keep_open;
actions.push_back(action);
}
UiDialogDesc translated = UI_DIALOG_DESC_INIT;
translated.title = desc->title;
translated.body_rml = desc->body_rml;
translated.variant = desc->variant;
translated.icon = desc->icon;
translated.actions = actions.data();
translated.action_count = actions.size();
translated.on_dismiss = desc->on_dismiss;
translated.user_data = desc->user_data;
translated.build = desc->struct_size >= sizeof(ui_v1::UiDialogDesc) ? desc->build : nullptr;
return ui_dialog_push(context, &translated, outDialog);
}
ModResult ui_v1_dialog_add_action(
ModContext* context, UiDialogHandle, const ui_v1::UiDialogAction*) {
auto* mod = mod_from_context(context);
if (mod == nullptr) {
return MOD_INVALID_ARGUMENT;
}
log::write(
mod->metadata.id, LOG_LEVEL_WARN, "UiService v1 dialog_add_action is no longer supported");
return MOD_UNSUPPORTED;
}
constexpr ui_v1::UiService s_uiService_v1{
.header = SERVICE_HEADER(ui_v1::UiService, ui_v1::kMajorVersion, ui_v1::kMinorVersion),
.register_mods_panel = ui_register_mods_panel,
.pane_add_section = ui_pane_add_section,
.pane_add_text = ui_pane_add_text,
@@ -1437,11 +1745,11 @@ constexpr UiService s_uiService{
.elem_set_class = ui_elem_set_class,
.window_push = ui_window_push,
.window_close = ui_window_close,
.dialog_push = ui_dialog_push,
.dialog_push = ui_v1_dialog_push,
.dialog_close = ui_dialog_close,
.dialog_set_body = ui_dialog_set_body,
.dialog_set_icon = ui_dialog_set_icon,
.dialog_add_action = ui_dialog_add_action,
.dialog_add_action = ui_v1_dialog_add_action,
.is_any_document_visible = ui_is_any_document_visible,
.register_styles = ui_register_styles,
.register_styles_file = ui_register_styles_file,
@@ -1451,6 +1759,39 @@ constexpr UiService s_uiService{
.push_toast = ui_push_toast,
.get_clipboard_text = ui_get_clipboard_text,
.set_clipboard_text = ui_set_clipboard_text,
.pane_add_group = ui_pane_add_group,
};
constexpr UiService s_uiService{
.header = SERVICE_HEADER(UiService, UI_SERVICE_MAJOR, UI_SERVICE_MINOR),
.register_mods_panel = ui_register_mods_panel,
.pane_add_section = ui_pane_add_section,
.pane_add_text = ui_pane_add_text,
.pane_add_rml = ui_pane_add_rml,
.pane_add_progress = ui_pane_add_progress,
.pane_add_control = ui_pane_add_control,
.pane_add_group = ui_pane_add_group,
.elem_set_text = ui_elem_set_text,
.elem_set_rml = ui_elem_set_rml,
.elem_set_progress = ui_elem_set_progress,
.elem_set_class = ui_elem_set_class,
.window_push = ui_window_push,
.window_close = ui_window_close,
.dialog_push = ui_dialog_push,
.dialog_close = ui_dialog_close,
.dialog_set_body = ui_dialog_set_body,
.dialog_set_icon = ui_dialog_set_icon,
.is_any_document_visible = ui_is_any_document_visible,
.register_styles = ui_register_styles,
.register_styles_file = ui_register_styles_file,
.unregister_styles = ui_unregister_styles,
.register_menu_tab = ui_register_menu_tab,
.unregister_menu_tab = ui_unregister_menu_tab,
.push_toast = ui_push_toast,
.get_clipboard_text = ui_get_clipboard_text,
.set_clipboard_text = ui_set_clipboard_text,
.pane_add_list = ui_pane_add_list,
.list_set_items = ui_list_set_items,
};
} // namespace
@@ -1467,6 +1808,13 @@ std::vector<ModMenuTabEntry> ui_mod_menu_tabs() {
return ui_impl::ui_mod_menu_tabs();
}
constinit const ServiceModule g_uiModule_v1{
.id = UI_SERVICE_ID,
.majorVersion = ui_v1::kMajorVersion,
.minorVersion = ui_v1::kMinorVersion,
.service = &s_uiService_v1,
};
constinit const ServiceModule g_uiModule{
.id = UI_SERVICE_ID,
.majorVersion = UI_SERVICE_MAJOR,
+82
View File
@@ -0,0 +1,82 @@
#pragma once
#include "mods/svc/ui.h"
namespace dusk::mods::svc::ui_v1 {
constexpr uint16_t kMajorVersion = 1;
constexpr uint16_t kMinorVersion = 5;
struct UiDialogAction {
const char* label;
UiDialogActionFn on_pressed;
void* user_data;
bool keep_open;
};
struct UiDialogDesc {
uint32_t struct_size;
const char* title;
const char* body_rml;
UiDialogVariant variant;
const char* icon;
const UiDialogAction* actions;
size_t action_count;
UiDialogActionFn on_dismiss;
void* user_data;
UiPaneBuildFn build;
};
struct UiService {
ServiceHeader header;
ModResult (*register_mods_panel)(ModContext* ctx, const UiModsPanelDesc* desc);
ModResult (*pane_add_section)(ModContext* ctx, UiElementHandle pane, const char* title);
ModResult (*pane_add_text)(
ModContext* ctx, UiElementHandle pane, const char* text, UiElementHandle* out_elem);
ModResult (*pane_add_rml)(
ModContext* ctx, UiElementHandle pane, const char* rml, UiElementHandle* out_elem);
ModResult (*pane_add_progress)(
ModContext* ctx, UiElementHandle pane, float value, UiElementHandle* out_elem);
ModResult (*pane_add_control)(ModContext* ctx, UiElementHandle pane, const UiControlDesc* desc,
UiElementHandle* out_elem);
ModResult (*elem_set_text)(ModContext* ctx, UiElementHandle elem, const char* text);
ModResult (*elem_set_rml)(ModContext* ctx, UiElementHandle elem, const char* rml);
ModResult (*elem_set_progress)(ModContext* ctx, UiElementHandle elem, float value);
ModResult (*elem_set_class)(
ModContext* ctx, UiElementHandle elem, const char* name, bool active);
ModResult (*window_push)(ModContext* ctx, const UiWindowDesc* desc, UiWindowHandle* out_window);
ModResult (*window_close)(ModContext* ctx, UiWindowHandle window);
ModResult (*dialog_push)(ModContext* ctx, const UiDialogDesc* desc, UiDialogHandle* out_dialog);
ModResult (*dialog_close)(ModContext* ctx, UiDialogHandle dialog);
ModResult (*dialog_set_body)(ModContext* ctx, UiDialogHandle dialog, const char* body_rml);
ModResult (*dialog_set_icon)(ModContext* ctx, UiDialogHandle dialog, const char* icon);
ModResult (*dialog_add_action)(
ModContext* ctx, UiDialogHandle dialog, const UiDialogAction* action);
ModResult (*is_any_document_visible)(ModContext* ctx, bool* out_visible);
ModResult (*register_styles)(
ModContext* ctx, UiStyleScope scope, const char* rcss, UiStyleHandle* out_style);
ModResult (*register_styles_file)(
ModContext* ctx, UiStyleScope scope, const char* path, UiStyleHandle* out_style);
ModResult (*unregister_styles)(ModContext* ctx, UiStyleHandle style);
ModResult (*register_menu_tab)(
ModContext* ctx, const UiMenuTabDesc* desc, UiMenuTabHandle* out_tab);
ModResult (*unregister_menu_tab)(ModContext* ctx, UiMenuTabHandle tab);
ModResult (*push_toast)(ModContext* ctx, const UiToastDesc* desc);
ModResult (*get_clipboard_text)(
ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength);
ModResult (*set_clipboard_text)(ModContext* ctx, const char* text);
ModResult (*pane_add_group)(ModContext* ctx, UiElementHandle group_pane,
UiElementHandle target_pane, const UiGroupDesc* desc, UiElementHandle* out_elem);
};
} // namespace dusk::mods::svc::ui_v1
+4
View File
@@ -1,6 +1,7 @@
#include "dusk/settings.h"
#include <aurora/aurora.h>
#include "dusk/config.hpp"
#include "dusk/ui/ui.hpp"
#include "dusk/game_mode.hpp"
namespace dusk {
@@ -16,6 +17,7 @@ UserSettings g_userSettings = {
.rememberWindowSize {"video.rememberWindowSize", false},
.lastWindowWidth {"video.lastWindowWidth", 0},
.lastWindowHeight {"video.lastWindowHeight", 0},
.uiScale {"video.uiScale", 100},
},
.audio = {
@@ -230,6 +232,8 @@ void registerSettings() {
Register(g_userSettings.video.rememberWindowSize);
Register(g_userSettings.video.lastWindowWidth);
Register(g_userSettings.video.lastWindowHeight);
Register(g_userSettings.video.uiScale,
[](const int&, const int&) { dusk::ui::apply_scale(); });
// Audio
Register(g_userSettings.audio.masterVolume);
+1
View File
@@ -145,6 +145,7 @@ struct UserSettings {
ConfigVar<bool> rememberWindowSize;
ConfigVar<int> lastWindowWidth;
ConfigVar<int> lastWindowHeight;
ConfigVar<int> uiScale;
} video;
struct {
-2
View File
@@ -185,8 +185,6 @@ AchievementsWindow::AchievementsWindow() {
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
});
pane.finalize();
});
}
}
+744
View File
@@ -0,0 +1,744 @@
#include "color_input.hpp"
#include "button.hpp"
#include "input.hpp"
#include "nav_group.hpp"
#include "m_Do/m_Do_audio.h"
#include <SDL3/SDL_clipboard.h>
#include <fmt/format.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <optional>
#include <string_view>
namespace dusk::ui {
namespace {
// These dimensions must match the controls in res/rml/popover.rcss.
constexpr float kSvWidthDp = 240.0f;
constexpr float kSvHeightDp = 150.0f;
constexpr float kBarHeightDp = 14.0f;
constexpr float kCursorDp = 14.0f;
constexpr size_t kHistoryLimit = 8;
constexpr int kSwatchColumns = 8;
constexpr float kSvAnalogRate = 0.7f;
constexpr float kHueAnalogRate = 240.0f;
constexpr float kAlphaAnalogRate = 0.7f;
constexpr float kSvNudge = 1.0f / 255.0f;
constexpr float kHueNudge = 1.0f;
constexpr float kAlphaNudge = 1.0f / 255.0f;
std::vector<Rml::String> sColorHistory;
bool directional_command(NavCommand command) {
return command == NavCommand::Up || command == NavCommand::Down ||
command == NavCommand::Left || command == NavCommand::Right;
}
float analog_response(float value) {
return value * std::abs(value);
}
class AdjustmentRegion : public Component {
public:
struct Props {
std::function<void()> begin;
std::function<void()> cancel;
std::function<void(NavCommand)> nudge;
std::function<void(float, float, float)> adjust;
};
AdjustmentRegion(Rml::Element* root, Props props) : Component{root}, mProps{std::move(props)} {
listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto command = map_nav_event(event);
if (command == NavCommand::Confirm) {
if (mActive) {
finish();
} else {
if (mProps.begin) {
mProps.begin();
}
mActive = true;
mAnalogReady = mAxis.x == 0.0f && mAxis.y == 0.0f;
mRoot->SetClass("adjusting", true);
}
event.StopPropagation();
return;
}
if (command == NavCommand::Cancel && mActive) {
if (mProps.cancel) {
mProps.cancel();
}
finish();
event.StopPropagation();
return;
}
if (directional_command(command) && (mActive || mAwaitingNeutral)) {
if (mActive && mAxis.x == 0.0f && mAxis.y == 0.0f && mProps.nudge) {
mProps.nudge(command);
}
event.StopPropagation();
}
});
listen(mRoot, Rml::String{input::kNavAxisEvent}, [this](Rml::Event& event) {
mAxis = {
event.GetParameter<float>("x", 0.0f),
event.GetParameter<float>("y", 0.0f),
};
if (mAxis.x == 0.0f && mAxis.y == 0.0f) {
mAwaitingNeutral = false;
mAnalogReady = true;
}
const float deltaSeconds = event.GetParameter<float>("dt", 0.0f);
if (mActive && mAnalogReady && deltaSeconds > 0.0f && mProps.adjust) {
mProps.adjust(mAxis.x, mAxis.y, deltaSeconds);
}
});
listen(mRoot, Rml::EventId::Blur, [this](Rml::Event&) {
if (mActive) {
finish();
}
});
}
private:
void finish() {
mActive = false;
mAnalogReady = false;
mAwaitingNeutral = mAxis.x != 0.0f || mAxis.y != 0.0f;
mRoot->SetClass("adjusting", false);
}
Props mProps;
Rml::Vector2f mAxis;
bool mActive = false;
bool mAnalogReady = false;
bool mAwaitingNeutral = false;
};
const std::vector<Rml::String>& default_presets() {
static const std::vector<Rml::String> presets = {
"ff4d4d",
"ff8a3d",
"ffd43b",
"61c454",
"3dc7c2",
"4d88ff",
"8b5cf6",
"e56aa6",
"8b6a45",
"ffffff",
"888888",
"202020",
};
return presets;
}
int hex_digit(char value) {
if (value >= '0' && value <= '9') {
return value - '0';
}
if (value >= 'a' && value <= 'f') {
return value - 'a' + 10;
}
if (value >= 'A' && value <= 'F') {
return value - 'A' + 10;
}
return -1;
}
std::optional<Rml::Colourb> parse_color(std::string_view value, bool alpha) {
if (value.starts_with('#')) {
value.remove_prefix(1);
}
if (value.size() != 6 && (!alpha || value.size() != 8)) {
return std::nullopt;
}
uint8_t channels[4] = {0, 0, 0, 255};
for (size_t i = 0; i < value.size() / 2; ++i) {
const int high = hex_digit(value[i * 2]);
const int low = hex_digit(value[i * 2 + 1]);
if (high < 0 || low < 0) {
return std::nullopt;
}
channels[i] = static_cast<uint8_t>((high << 4) | low);
}
return Rml::Colourb{channels[0], channels[1], channels[2], channels[3]};
}
Rml::String normalize_value(Rml::String value, bool alpha) {
if (value == "rainbow") {
return value;
}
const auto color = parse_color(value, alpha);
if (!color.has_value()) {
return value;
}
if (alpha && color->alpha != 255) {
return fmt::format(
"{:02x}{:02x}{:02x}{:02x}", color->red, color->green, color->blue, color->alpha);
}
return fmt::format("{:02x}{:02x}{:02x}", color->red, color->green, color->blue);
}
Rml::String css_color(Rml::Colourb color) {
return fmt::format("rgba({},{},{},{})", color.red, color.green, color.blue, color.alpha);
}
Rml::String format_color(Rml::Colourb color, bool hex, bool alpha) {
if (hex) {
if (alpha && color.alpha != 255) {
return fmt::format(
"#{:02X}{:02X}{:02X}{:02X}", color.red, color.green, color.blue, color.alpha);
}
return fmt::format("#{:02X}{:02X}{:02X}", color.red, color.green, color.blue);
}
if (alpha) {
return fmt::format("rgba({}, {}, {}, {:.0f}%)", color.red, color.green, color.blue,
static_cast<float>(color.alpha) / 255.0f * 100.0f);
}
return fmt::format("rgb({}, {}, {})", color.red, color.green, color.blue);
}
Rml::Colourb hsv_to_rgb(float hue, float sat, float val, float alpha) {
hue -= 360.0f * std::floor(hue / 360.0f);
const float chroma = val * sat;
const float intermediate = chroma * (1.0f - std::abs(std::fmod(hue / 60.0f, 2.0f) - 1.0f));
const float offset = val - chroma;
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
switch (static_cast<int>(hue / 60.0f) % 6) {
case 0:
red = chroma;
green = intermediate;
break;
case 1:
red = intermediate;
green = chroma;
break;
case 2:
green = chroma;
blue = intermediate;
break;
case 3:
green = intermediate;
blue = chroma;
break;
case 4:
red = intermediate;
blue = chroma;
break;
case 5:
default:
red = chroma;
blue = intermediate;
break;
}
const auto toByte = [](float value) {
return static_cast<Rml::byte>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
};
return {toByte(red + offset), toByte(green + offset), toByte(blue + offset), toByte(alpha)};
}
void rgb_to_hsv(Rml::Colourb color, float& hue, float& sat, float& val) {
const float red = static_cast<float>(color.red) / 255.0f;
const float green = static_cast<float>(color.green) / 255.0f;
const float blue = static_cast<float>(color.blue) / 255.0f;
const float maxChannel = std::max({red, green, blue});
const float minChannel = std::min({red, green, blue});
const float delta = maxChannel - minChannel;
val = maxChannel;
sat = maxChannel > 0.0f ? delta / maxChannel : 0.0f;
if (delta <= 0.0f) {
hue = 0.0f;
return;
}
if (maxChannel == red) {
hue = 60.0f * std::fmod((green - blue) / delta + 6.0f, 6.0f);
} else if (maxChannel == green) {
hue = 60.0f * ((blue - red) / delta + 2.0f);
} else {
hue = 60.0f * ((red - green) / delta + 4.0f);
}
}
void apply_swatch(Rml::Element* element, const Rml::String& value, bool alpha) {
element->RemoveProperty("background-color");
element->RemoveProperty("decorator");
element->SetClass("empty", false);
element->SetClass("rainbow", value == "rainbow");
if (value == "rainbow") {
element->SetProperty("decorator", "linear-gradient(90deg, #ff4d4d, #ffd43b, #61c454, "
"#3dc7c2, #4d88ff, #8b5cf6, #e56aa6)");
return;
}
const auto color = parse_color(value, alpha);
if (!color.has_value()) {
element->SetClass("empty", true);
return;
}
element->SetProperty("background-color", css_color(*color));
}
void remember_color(Rml::String value, bool alpha) {
value = normalize_value(std::move(value), alpha);
if (value != "rainbow" && !parse_color(value, alpha).has_value()) {
return;
}
std::erase(sColorHistory, value);
sColorHistory.insert(sColorHistory.begin(), std::move(value));
if (sColorHistory.size() > kHistoryLimit) {
sColorHistory.resize(kHistoryLimit);
}
}
} // namespace
ColorInput::ColorInput(Rml::Element* parent, Props props)
: BaseControlledSelectButton{parent, {.key = props.key, .submit = false}},
mProps{std::move(props)} {
mRoot->SetClass("color-input", true);
mSwatch = append(mRoot, "color-swatch");
refresh_swatch();
}
ColorInput::~ColorInput() {
if (mPicker != nullptr) {
mPicker->on_close(nullptr);
mPicker->on_focus(nullptr);
mPicker->dismiss();
}
}
void ColorInput::update() {
refresh_swatch();
if (mPickerNavigation != nullptr) {
mPickerNavigation->update();
}
BaseControlledSelectButton::update();
}
bool ColorInput::modified() const {
return mProps.isModified ? mProps.isModified() : BaseControlledSelectButton::modified();
}
bool ColorInput::disabled() const {
return mProps.isDisabled ? mProps.isDisabled() : BaseControlledSelectButton::disabled();
}
Rml::String ColorInput::format_value() {
const Rml::String value = mProps.getValue ? mProps.getValue() : "";
if (value == "rainbow") {
return "Rainbow";
}
const auto color = parse_color(value, mProps.alpha);
if (color.has_value()) {
return format_color(*color, mHexFormat, mProps.alpha);
}
return value.empty() ? "Default" : value;
}
bool ColorInput::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
toggle_picker();
return true;
}
return false;
}
void ColorInput::toggle_picker() {
if (mPicker != nullptr) {
mPicker->dismiss();
return;
}
mInitialValue = mProps.getValue ? mProps.getValue() : "";
mInitialValueRemembered = false;
const auto initialColor = parse_color(mInitialValue, mProps.alpha);
const Rml::Colourb color = initialColor.value_or(Rml::Colourb{128, 128, 128, 255});
rgb_to_hsv(color, mHue, mSat, mVal);
mAlpha = static_cast<float>(color.alpha) / 255.0f;
auto picker = std::make_unique<Popover>(mRoot, mProps.side, "color-picker");
mPicker = picker.get();
mPicker->on_close([this] {
if (mInitialValueRemembered && mProps.getValue) {
remember_color(mProps.getValue(), mProps.alpha);
}
mPicker = nullptr;
mPickerNavigation.reset();
mPickerListeners.clear();
mSvArea = nullptr;
mSvCursor = nullptr;
mHueBar = nullptr;
mHueCursor = nullptr;
mAlphaBar = nullptr;
mAlphaCursor = nullptr;
mPickerValue = nullptr;
});
build_picker();
refresh_picker();
mPicker->on_focus(
[this] { return mPickerNavigation != nullptr && mPickerNavigation->focus(); });
push_document(std::move(picker));
}
void ColorInput::build_picker() {
auto* body = mPicker->body();
mPickerNavigation = std::make_unique<NavGroup>(
body, NavGroup::Props{
.layout = NavGroup::Layout::Vertical,
.horizontalBoundary = NavGroup::Boundary::Stop,
.verticalBoundary = NavGroup::Boundary::Stop,
});
mSvArea = append(body, "color-sv");
mSvCursor = append(mSvArea, "color-cursor");
mPickerNavigation->add_existing_item<AdjustmentRegion>(
mSvArea, AdjustmentRegion::Props{
.begin = [this] { begin_adjustment(); },
.cancel = [this] { cancel_adjustment(); },
.nudge = [this](NavCommand direction) { nudge_sv(direction); },
.adjust = [this](float x, float y,
float deltaSeconds) { adjust_sv(x, y, deltaSeconds); },
});
mHueBar = append(body, "color-hue");
mHueCursor = append(mHueBar, "color-cursor");
mPickerNavigation->add_existing_item<AdjustmentRegion>(mHueBar,
AdjustmentRegion::Props{
.begin = [this] { begin_adjustment(); },
.cancel = [this] { cancel_adjustment(); },
.nudge = [this](NavCommand direction) { nudge_hue(direction); },
.adjust = [this](float x, float, float deltaSeconds) { adjust_hue(x, deltaSeconds); },
});
if (mProps.alpha) {
mAlphaBar = append(body, "color-alpha");
mAlphaCursor = append(mAlphaBar, "color-cursor");
mPickerNavigation->add_existing_item<AdjustmentRegion>(
mAlphaBar, AdjustmentRegion::Props{
.begin = [this] { begin_adjustment(); },
.cancel = [this] { cancel_adjustment(); },
.nudge = [this](NavCommand direction) { nudge_alpha(direction); },
.adjust = [this](float x, float,
float deltaSeconds) { adjust_alpha(x, deltaSeconds); },
});
}
add_presets(*mPickerNavigation);
add_history(*mPickerNavigation);
auto* footer = append(body, "color-footer");
auto& footerNavigation = mPickerNavigation->add_existing_item<NavGroup>(
footer, NavGroup::Props{
.layout = NavGroup::Layout::Horizontal,
.horizontalBoundary = NavGroup::Boundary::Stop,
.verticalBoundary = NavGroup::Boundary::Bubble,
});
const auto formatLabel = [this] {
return mHexFormat ? "Hex" : (mProps.alpha ? "RGBA" : "RGB");
};
auto& formatButton = footerNavigation.add_item<Button>(formatLabel());
formatButton.on_pressed([this, formatButtonPtr = &formatButton, formatLabel] {
mHexFormat = !mHexFormat;
formatButtonPtr->set_text(formatLabel());
refresh_picker();
});
footerNavigation.add_item<Button>("Default").on_pressed([this] { commit_value(""); });
auto& valueButton = footerNavigation.add_item<Button>("", "color-value");
mPickerValue = valueButton.root();
mPickerValue->SetAttribute("title", "Copy color");
valueButton.on_pressed([this] {
SDL_SetClipboardText(format_color(current_color(), mHexFormat, mProps.alpha).c_str());
push_toast({
.type = "info",
.title = "Color",
.content = "Copied to clipboard",
.duration = std::chrono::seconds{2},
});
});
const auto track = [this](Rml::Element* element, float widthDp, float heightDp,
std::function<void(float, float)> apply) {
const auto handler = [this, element, widthDp, heightDp, apply = std::move(apply)](
Rml::Event& event) {
const float dpRatio = element->GetContext()->GetDensityIndependentPixelRatio();
const auto origin = element->GetAbsoluteOffset(Rml::BoxArea::Border);
const float x =
(event.GetParameter<float>("mouse_x", 0.0f) - origin.x) / (widthDp * dpRatio);
const float y =
(event.GetParameter<float>("mouse_y", 0.0f) - origin.y) / (heightDp * dpRatio);
element->Focus(true);
apply(std::clamp(x, 0.0f, 1.0f), std::clamp(y, 0.0f, 1.0f));
commit_color();
};
mPickerListeners.push_back(
std::make_unique<ScopedEventListener>(element, Rml::EventId::Mousedown, handler));
mPickerListeners.push_back(
std::make_unique<ScopedEventListener>(element, Rml::EventId::Drag, handler));
};
track(mSvArea, kSvWidthDp, kSvHeightDp, [this](float x, float y) {
mSat = x;
mVal = 1.0f - y;
});
track(mHueBar, kSvWidthDp, kBarHeightDp,
[this](float x, float) { mHue = std::min(x * 360.0f, 359.9f); });
if (mAlphaBar != nullptr) {
track(mAlphaBar, kSvWidthDp, kBarHeightDp, [this](float x, float) { mAlpha = x; });
}
}
void ColorInput::add_presets(NavGroup& navigation) {
append_text(append(navigation.root(), "color-heading"), "Presets");
auto* gridElement = append(navigation.root(), "color-presets");
auto& grid = navigation.add_existing_item<NavGroup>(
gridElement, NavGroup::Props{
.layout = NavGroup::Layout::Grid,
.columns = kSwatchColumns,
.horizontalBoundary = NavGroup::Boundary::Stop,
.verticalBoundary = NavGroup::Boundary::Bubble,
});
const auto& presets = mProps.presets.empty() ? default_presets() : mProps.presets;
for (const auto& preset : presets) {
add_swatch_button(grid, preset);
}
}
void ColorInput::add_history(NavGroup& navigation) {
const bool supportsRainbow =
std::ranges::any_of(mProps.presets, [](const auto& preset) { return preset == "rainbow"; });
std::vector<Rml::String> compatibleHistory;
for (const auto& value : sColorHistory) {
if (parse_color(value, mProps.alpha).has_value() || (value == "rainbow" && supportsRainbow))
{
compatibleHistory.push_back(value);
}
}
if (compatibleHistory.empty()) {
return;
}
append_text(append(navigation.root(), "color-heading"), "Recent");
auto* gridElement = append(navigation.root(), "color-history");
auto& grid = navigation.add_existing_item<NavGroup>(
gridElement, NavGroup::Props{
.layout = NavGroup::Layout::Grid,
.columns = kSwatchColumns,
.horizontalBoundary = NavGroup::Boundary::Stop,
.verticalBoundary = NavGroup::Boundary::Bubble,
});
for (const auto& value : compatibleHistory) {
add_swatch_button(grid, value);
}
}
void ColorInput::add_swatch_button(NavGroup& navigation, const Rml::String& value) {
auto& button = navigation.add_item<Button>("");
button.root()->SetClass("color-swatch-button", true);
button.root()->SetInnerRML("");
auto* chip = append(button.root(), "color-chip");
apply_swatch(chip, value, mProps.alpha);
Rml::String title = value == "rainbow" ? "Rainbow" : value;
if (const auto color = parse_color(value, mProps.alpha)) {
title = format_color(*color, true, mProps.alpha);
}
button.root()->SetAttribute("title", title);
button.on_pressed([this, value] {
mDoAud_seStartMenu(kSoundItemChange);
commit_value(value);
});
}
void ColorInput::begin_adjustment() {
mAdjustmentStartValue = mProps.getValue ? mProps.getValue() : "";
mAdjustmentStartHue = mHue;
mAdjustmentStartSat = mSat;
mAdjustmentStartVal = mVal;
mAdjustmentStartAlpha = mAlpha;
}
void ColorInput::cancel_adjustment() {
mHue = mAdjustmentStartHue;
mSat = mAdjustmentStartSat;
mVal = mAdjustmentStartVal;
mAlpha = mAdjustmentStartAlpha;
commit_value(mAdjustmentStartValue);
}
void ColorInput::adjust_sv(float x, float y, float deltaSeconds) {
const float sat =
std::clamp(mSat + analog_response(x) * kSvAnalogRate * deltaSeconds, 0.0f, 1.0f);
const float val =
std::clamp(mVal - analog_response(y) * kSvAnalogRate * deltaSeconds, 0.0f, 1.0f);
if (sat == mSat && val == mVal) {
return;
}
mSat = sat;
mVal = val;
commit_color();
}
void ColorInput::adjust_hue(float x, float deltaSeconds) {
const float hue =
std::clamp(mHue + analog_response(x) * kHueAnalogRate * deltaSeconds, 0.0f, 359.9f);
if (hue == mHue) {
return;
}
mHue = hue;
commit_color();
}
void ColorInput::adjust_alpha(float x, float deltaSeconds) {
const float alpha =
std::clamp(mAlpha + analog_response(x) * kAlphaAnalogRate * deltaSeconds, 0.0f, 1.0f);
if (alpha == mAlpha) {
return;
}
mAlpha = alpha;
commit_color();
}
void ColorInput::nudge_sv(NavCommand direction) {
const float sat = std::clamp(mSat + (direction == NavCommand::Right ? kSvNudge :
direction == NavCommand::Left ? -kSvNudge :
0.0f),
0.0f, 1.0f);
const float val = std::clamp(mVal + (direction == NavCommand::Up ? kSvNudge :
direction == NavCommand::Down ? -kSvNudge :
0.0f),
0.0f, 1.0f);
if (sat == mSat && val == mVal) {
return;
}
mSat = sat;
mVal = val;
mDoAud_seStartMenu(kSoundItemChange);
commit_color();
}
void ColorInput::nudge_hue(NavCommand direction) {
const float hue = std::clamp(mHue + (direction == NavCommand::Right ? kHueNudge :
direction == NavCommand::Left ? -kHueNudge :
0.0f),
0.0f, 359.9f);
if (hue == mHue) {
return;
}
mHue = hue;
mDoAud_seStartMenu(kSoundItemChange);
commit_color();
}
void ColorInput::nudge_alpha(NavCommand direction) {
const float alpha = std::clamp(mAlpha + (direction == NavCommand::Right ? kAlphaNudge :
direction == NavCommand::Left ? -kAlphaNudge :
0.0f),
0.0f, 1.0f);
if (alpha == mAlpha) {
return;
}
mAlpha = alpha;
mDoAud_seStartMenu(kSoundItemChange);
commit_color();
}
void ColorInput::commit_color() {
const auto color = current_color();
Rml::String value;
if (mProps.alpha && color.alpha != 255) {
value = fmt::format(
"{:02x}{:02x}{:02x}{:02x}", color.red, color.green, color.blue, color.alpha);
} else {
value = fmt::format("{:02x}{:02x}{:02x}", color.red, color.green, color.blue);
}
const bool changed = !mProps.getValue || mProps.getValue() != value;
if (changed) {
remember_initial_value();
if (mProps.setValue) {
mProps.setValue(std::move(value));
}
refresh_swatch();
}
refresh_picker();
}
void ColorInput::commit_value(Rml::String value) {
remember_initial_value();
value = normalize_value(std::move(value), mProps.alpha);
if (const auto color = parse_color(value, mProps.alpha)) {
rgb_to_hsv(*color, mHue, mSat, mVal);
mAlpha = static_cast<float>(color->alpha) / 255.0f;
}
if (mProps.setValue) {
mProps.setValue(std::move(value));
}
refresh_swatch();
refresh_picker();
}
void ColorInput::remember_initial_value() {
if (mInitialValueRemembered) {
return;
}
remember_color(mInitialValue, mProps.alpha);
mInitialValueRemembered = true;
}
void ColorInput::refresh_swatch() {
const Rml::String value = mProps.getValue ? mProps.getValue() : "";
if (mHasDisplayedValue && value == mDisplayedValue) {
return;
}
mHasDisplayedValue = true;
mDisplayedValue = value;
apply_swatch(mSwatch, value, mProps.alpha);
}
void ColorInput::refresh_picker() {
if (mPicker == nullptr) {
return;
}
const float dpRatio = mSvArea->GetContext()->GetDensityIndependentPixelRatio();
const auto place = [dpRatio](Rml::Element* cursor, float xDp, float yDp) {
cursor->SetProperty(Rml::PropertyId::Left,
Rml::Property{(xDp - kCursorDp / 2.0f) * dpRatio, Rml::Unit::PX});
cursor->SetProperty(
Rml::PropertyId::Top, Rml::Property{(yDp - kCursorDp / 2.0f) * dpRatio, Rml::Unit::PX});
};
const Rml::Colourb hueColor = hsv_to_rgb(mHue, 1.0f, 1.0f, 1.0f);
const Rml::Colourb color = current_color();
const Rml::Colourb opaque{color.red, color.green, color.blue, 255};
mSvArea->SetProperty(
"decorator", fmt::format("linear-gradient(180deg, rgba(0,0,0,0), #000000), "
"linear-gradient(90deg, #ffffff, {})",
css_color(hueColor)));
place(mSvCursor, mSat * kSvWidthDp, (1.0f - mVal) * kSvHeightDp);
place(mHueCursor, mHue / 360.0f * kSvWidthDp, kBarHeightDp / 2.0f);
if (mAlphaBar != nullptr) {
mAlphaBar->SetProperty(
"decorator", fmt::format("linear-gradient(90deg, rgba({0},{1},{2},0), {3})", opaque.red,
opaque.green, opaque.blue, css_color(opaque)));
place(mAlphaCursor, mAlpha * kSvWidthDp, kBarHeightDp / 2.0f);
}
mPickerValue->SetInnerRML(format_color(color, mHexFormat, mProps.alpha));
}
Rml::Colourb ColorInput::current_color() const {
return hsv_to_rgb(mHue, mSat, mVal, mAlpha);
}
} // namespace dusk::ui
+85
View File
@@ -0,0 +1,85 @@
#pragma once
#include "popover.hpp"
#include "select_button.hpp"
namespace dusk::ui {
class NavGroup;
class ColorInput : public BaseControlledSelectButton {
public:
struct Props {
Rml::String key;
Popover::Side side = Popover::Side::Left;
std::function<Rml::String()> getValue;
std::function<void(Rml::String)> setValue;
std::function<bool()> isDisabled;
std::function<bool()> isModified;
std::vector<Rml::String> presets;
bool alpha = false;
};
ColorInput(Rml::Element* parent, Props props);
~ColorInput() override;
void update() override;
bool modified() const override;
bool disabled() const override;
protected:
Rml::String format_value() override;
bool handle_nav_command(NavCommand cmd) override;
private:
void toggle_picker();
void build_picker();
void add_presets(NavGroup& navigation);
void add_history(NavGroup& navigation);
void add_swatch_button(NavGroup& navigation, const Rml::String& value);
void begin_adjustment();
void cancel_adjustment();
void adjust_sv(float x, float y, float deltaSeconds);
void adjust_hue(float x, float deltaSeconds);
void adjust_alpha(float x, float deltaSeconds);
void nudge_sv(NavCommand direction);
void nudge_hue(NavCommand direction);
void nudge_alpha(NavCommand direction);
void commit_color();
void commit_value(Rml::String value);
void remember_initial_value();
void refresh_swatch();
void refresh_picker();
Rml::Colourb current_color() const;
Props mProps;
Rml::Element* mSwatch = nullptr;
Rml::String mDisplayedValue;
Rml::String mInitialValue;
Rml::String mAdjustmentStartValue;
bool mHasDisplayedValue = false;
Popover* mPicker = nullptr;
Rml::Element* mSvArea = nullptr;
Rml::Element* mSvCursor = nullptr;
Rml::Element* mHueBar = nullptr;
Rml::Element* mHueCursor = nullptr;
Rml::Element* mAlphaBar = nullptr;
Rml::Element* mAlphaCursor = nullptr;
Rml::Element* mPickerValue = nullptr;
std::unique_ptr<NavGroup> mPickerNavigation;
std::vector<std::unique_ptr<ScopedEventListener>> mPickerListeners;
float mHue = 0.0f;
float mSat = 0.0f;
float mVal = 0.0f;
float mAlpha = 1.0f;
float mAdjustmentStartHue = 0.0f;
float mAdjustmentStartSat = 0.0f;
float mAdjustmentStartVal = 0.0f;
float mAdjustmentStartAlpha = 1.0f;
bool mHexFormat = true;
bool mInitialValueRemembered = false;
};
} // namespace dusk::ui
+21 -1
View File
@@ -49,13 +49,24 @@ void Component::set_disabled(bool value) {
if (Component::disabled() == value) {
return;
}
auto* context = mRoot->GetContext();
if (value) {
if (context != nullptr && context->GetFocusElement() == mRoot) {
// RmlUi moves focus to the parent when a focused control is disabled.
mDisabledFocusFallback = mRoot->GetParentNode();
}
mRoot->SetAttribute("disabled", "");
mRoot->SetPseudoClass("disabled", true);
mRoot->Blur();
} else {
const bool restoreFocus = mDisabledFocusFallback != nullptr && context != nullptr &&
context->GetFocusElement() == mDisabledFocusFallback;
mRoot->RemoveAttribute("disabled");
mRoot->SetPseudoClass("disabled", false);
mDisabledFocusFallback = nullptr;
if (restoreFocus) {
focus();
}
}
}
@@ -68,6 +79,15 @@ void Component::listen(Rml::Element* element, Rml::EventId event,
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
}
void Component::listen(Rml::Element* element, const Rml::String& event,
ScopedEventListener::Callback callback, bool capture) {
if (element == nullptr) {
element = mRoot;
}
mListeners.emplace_back(
std::make_unique<ScopedEventListener>(element, event, std::move(callback), capture));
}
bool Component::contains(Rml::Element* element) const {
for (const auto* node = element; node != nullptr; node = node->GetParentNode()) {
if (node == mRoot) {
@@ -84,4 +104,4 @@ void Component::clear_children() {
}
}
} // namespace dusk::ui
} // namespace dusk::ui
+8 -3
View File
@@ -26,6 +26,7 @@ public:
virtual void update();
virtual bool focus();
virtual bool focus_from(NavCommand direction) { return focus(); }
virtual bool selected() const { return mRoot->IsPseudoClassSet("selected"); }
virtual void set_selected(bool selected);
@@ -34,10 +35,13 @@ public:
void listen(Rml::Element* element, Rml::EventId event, ScopedEventListener::Callback callback,
bool capture = false);
void listen(Rml::Element* element, const Rml::String& event,
ScopedEventListener::Callback callback, bool capture = false);
bool contains(Rml::Element* element) const;
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T> T& add_child(Args&&... args) {
requires std::is_base_of_v<Component, T>
T& add_child(Args&&... args) {
auto child = std::make_unique<T>(mRoot, std::forward<Args>(args)...);
T& ref = *child;
mChildren.emplace_back(std::move(child));
@@ -50,8 +54,9 @@ protected:
void clear_children();
Rml::Element* mRoot = nullptr;
std::vector<std::unique_ptr<Component> > mChildren;
std::vector<std::unique_ptr<ScopedEventListener> > mListeners;
Rml::Element* mDisabledFocusFallback = nullptr;
std::vector<std::unique_ptr<Component>> mChildren;
std::vector<std::unique_ptr<ScopedEventListener>> mListeners;
};
template <class Derived>
+17 -10
View File
@@ -291,10 +291,9 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
mActivePort = port;
auto addPageButton = [this, &leftPane, &rightPane, port](
Page page, Rml::String key, auto getValue, auto isDisabled) {
leftPane.register_control(leftPane.add_select_button({
.key = std::move(key),
.getValue = std::move(getValue),
Page page, Rml::String text, std::function<bool()> isDisabled = {}) {
leftPane.register_control(leftPane.add_group_button({
.text = std::move(text),
.isDisabled = std::move(isDisabled),
}),
rightPane, [this, port, page](Pane& pane) {
@@ -303,12 +302,20 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
});
};
addPageButton(Page::Controller, "Device", [port] { return current_controller_name(port); }, [] { return false; });
addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); }, [] { return false; });
addPageButton(Page::Rumble, "Rumble", [] { return Rml::String(">"); }, [port] { return !PADSupportsRumbleIntensity(static_cast<u32>(port)); });
addPageButton(Page::Actions, "Custom Action Bindings", [] {return Rml::String(">"); }, [] { return false; });
leftPane.register_control(leftPane.add_select_button({
.key = "Device",
.getValue = [port] { return current_controller_name(port); },
}),
rightPane, [this, port](Pane& pane) {
mPage = Page::Controller;
render_page(pane, port, Page::Controller);
});
addPageButton(Page::Buttons, "Buttons");
addPageButton(Page::Triggers, "Triggers");
addPageButton(Page::Sticks, "Sticks");
addPageButton(Page::Rumble, "Rumble",
[port] { return !PADSupportsRumbleIntensity(static_cast<u32>(port)); });
addPageButton(Page::Actions, "Custom Action Bindings");
leftPane.add_section("Options");
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
+1 -1
View File
@@ -275,7 +275,7 @@ std::map<int, itemInfo> itemMap = {
{dItemNo_WEAR_KOKIRI_e, {"Hero's Clothes"}},
{dItemNo_ARMOR_e, {"Magic Armor"}},
{dItemNo_WEAR_ZORA_e, {"Zora Armor"}},
{dItemNo_MAGIC_LV1_e, {"Magic Level 1"}},
{dItemNo_SHADOW_CRYSTAL_e, {"Shadow Crystal"}},
{dItemNo_DUNGEON_EXIT_2_e, {"Ooccoo Sr.", ITEMTYPE_EQUIP_e}},
{dItemNo_WALLET_LV1_e, {"Wallet"}},
{dItemNo_WALLET_LV2_e, {"Big Wallet"}},
+25
View File
@@ -0,0 +1,25 @@
#include "group_button.hpp"
namespace dusk::ui {
GroupButton::GroupButton(Rml::Element* parent, Props props)
: SelectButton{parent, {.key = std::move(props.text)}},
mIsSelected{std::move(props.isSelected)}, mIsDisabled{std::move(props.isDisabled)} {
mRoot->SetClass("group-button", true);
}
void GroupButton::update() {
set_selected(selected());
set_disabled(disabled());
SelectButton::update();
}
bool GroupButton::selected() const {
return mIsSelected ? mIsSelected() : SelectButton::selected();
}
bool GroupButton::disabled() const {
return mIsDisabled ? mIsDisabled() : SelectButton::disabled();
}
} // namespace dusk::ui
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "select_button.hpp"
namespace dusk::ui {
class GroupButton : public SelectButton {
public:
struct Props {
Rml::String text;
std::function<bool()> isSelected;
std::function<bool()> isDisabled;
};
GroupButton(Rml::Element* parent, Props props);
void update() override;
bool selected() const override;
bool disabled() const override;
private:
std::function<bool()> mIsSelected;
std::function<bool()> mIsDisabled;
};
} // namespace dusk::ui
+161 -4
View File
@@ -11,6 +11,7 @@
#include <algorithm>
#include <array>
#include <cmath>
#include "dusk/action_bindings.h"
@@ -24,6 +25,8 @@ constexpr double kGamepadRepeatRampDuration = 1.0;
constexpr double kGamepadMenuChordGraceDuration = 0.12;
constexpr Sint16 kGamepadAxisPressThreshold = 16384;
constexpr Sint16 kGamepadAxisReleaseThreshold = 12000;
constexpr float kNavAxisDeadZone = 0.12f;
constexpr double kMaxNavAxisDt = 0.05;
constexpr int kGamepadAxisDirectionCount = SDL_GAMEPAD_AXIS_COUNT * 2;
constexpr int kMenuTapFingerCount = 3;
constexpr float kMenuTapMoveThreshold = 12.0f;
@@ -59,6 +62,10 @@ std::array<GamepadRepeatState, kGamepadAxisDirectionCount> sGamepadAxisRepeats;
std::array<u32, PAD_MAX_CONTROLLERS> sPadHoldMasks;
std::array<bool, PAD_MAX_CONTROLLERS> sMenuChordConsumed;
TouchTapState sTouchMenuTap;
Rml::Vector2f sNavAxis;
std::array<Rml::Vector2f, SDL_GAMEPAD_AXIS_COUNT> sNavAxisContributions;
SDL_JoystickID sNavAxisSource = 0;
double sLastInputUpdateAt = 0.0;
double now_seconds() noexcept {
return static_cast<double>(SDL_GetTicksNS()) / 1000000000.0;
@@ -335,7 +342,9 @@ Rml::Input::KeyIdentifier map_gamepad_button(const SDL_GamepadButtonEvent& event
}
}
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port)) {
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK &&
!isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port))
{
return Rml::Input::KI_F1;
}
@@ -367,6 +376,138 @@ Rml::Input::KeyIdentifier map_gamepad_axis(
return Rml::Input::KI_UNKNOWN;
}
struct LogicalNavAxis {
enum class Component {
None,
X,
Y,
};
Component component = Component::None;
float direction = 0.0f;
};
LogicalNavAxis logical_nav_axis(PADAxis axis) noexcept {
switch (axis) {
case PAD_AXIS_LEFT_X_POS:
return {LogicalNavAxis::Component::X, 1.0f};
case PAD_AXIS_LEFT_X_NEG:
return {LogicalNavAxis::Component::X, -1.0f};
case PAD_AXIS_LEFT_Y_POS:
return {LogicalNavAxis::Component::Y, -1.0f};
case PAD_AXIS_LEFT_Y_NEG:
return {LogicalNavAxis::Component::Y, 1.0f};
default:
return {};
}
}
struct LogicalNavAxisMapping {
LogicalNavAxis active;
bool participates = false;
};
LogicalNavAxisMapping mapped_logical_nav_axis(const SDL_GamepadAxisEvent& event) noexcept {
u32 port = 0;
if (find_event_port(event.which, port)) {
LogicalNavAxisMapping result;
const PADAxisSign activeSign = event.value < 0 ? AXIS_SIGN_NEGATIVE : AXIS_SIGN_POSITIVE;
for (const PADAxisSign sign : {AXIS_SIGN_NEGATIVE, AXIS_SIGN_POSITIVE}) {
PADAxis axis = 0;
if (!find_mapped_pad_axis(port, static_cast<SDL_GamepadAxis>(event.axis), sign, axis)) {
continue;
}
const auto logical = logical_nav_axis(axis);
if (logical.component == LogicalNavAxis::Component::None) {
continue;
}
result.participates = true;
if (event.value != 0 && sign == activeSign) {
result.active = logical;
}
}
return result;
}
switch (static_cast<SDL_GamepadAxis>(event.axis)) {
case SDL_GAMEPAD_AXIS_LEFTX:
return {
.active = {LogicalNavAxis::Component::X, event.value < 0 ? -1.0f : 1.0f},
.participates = true,
};
case SDL_GAMEPAD_AXIS_LEFTY:
return {
.active = {LogicalNavAxis::Component::Y, event.value < 0 ? -1.0f : 1.0f},
.participates = true,
};
default:
return {};
}
}
float normalize_nav_axis(Sint16 value) noexcept {
const float normalized =
value < 0 ? -static_cast<float>(value) / 32768.0f : static_cast<float>(value) / 32767.0f;
if (normalized <= kNavAxisDeadZone) {
return 0.0f;
}
return (normalized - kNavAxisDeadZone) / (1.0f - kNavAxisDeadZone);
}
bool update_nav_axis(const SDL_GamepadAxisEvent& event) noexcept {
const auto mapping = mapped_logical_nav_axis(event);
const int axisIndex = static_cast<int>(event.axis);
if (!mapping.participates || axisIndex < 0 || axisIndex >= SDL_GAMEPAD_AXIS_COUNT) {
return false;
}
const float magnitude = normalize_nav_axis(event.value);
if (magnitude != 0.0f && mapping.active.component != LogicalNavAxis::Component::None &&
sNavAxisSource != event.which)
{
sNavAxis = {};
sNavAxisContributions = {};
sNavAxisSource = event.which;
} else if (sNavAxisSource != 0 && sNavAxisSource != event.which) {
return false;
}
auto& contribution = sNavAxisContributions[axisIndex];
contribution = {};
if (mapping.active.component == LogicalNavAxis::Component::X) {
contribution.x = mapping.active.direction * magnitude;
} else if (mapping.active.component == LogicalNavAxis::Component::Y) {
contribution.y = mapping.active.direction * magnitude;
}
const Rml::Vector2f previous = sNavAxis;
sNavAxis = {};
for (const auto value : sNavAxisContributions) {
if (std::abs(value.x) > std::abs(sNavAxis.x)) {
sNavAxis.x = value.x;
}
if (std::abs(value.y) > std::abs(sNavAxis.y)) {
sNavAxis.y = value.y;
}
}
if (sNavAxis.x == 0.0f && sNavAxis.y == 0.0f) {
sNavAxisSource = 0;
}
return sNavAxis != previous;
}
void dispatch_nav_axis(Rml::Context& context, double dtSeconds) noexcept {
auto* target = context.GetFocusElement();
if (target == nullptr) {
return;
}
target->DispatchEvent(kNavAxisEvent, {
{"x", Rml::Variant{sNavAxis.x}},
{"y", Rml::Variant{sNavAxis.y}},
{"dt", Rml::Variant{static_cast<float>(dtSeconds)}},
});
}
bool is_repeatable_key(Rml::Input::KeyIdentifier key) noexcept {
switch (key) {
case Rml::Input::KI_UP:
@@ -657,7 +798,9 @@ void process_axis_direction(
if (chorded) {
consume_menu_chord(port, context);
}
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ?
Rml::Input::KI_F1 :
map_gamepad_axis(event, sign);
if (key == Rml::Input::KI_UNKNOWN) {
return;
}
@@ -695,6 +838,9 @@ void release_input_block() noexcept {
void reset_input_state() noexcept {
clear_gamepad_repeats();
reset_touch_menu_tap();
sNavAxis = {};
sNavAxisContributions = {};
sNavAxisSource = 0;
}
void handle_event(const SDL_Event& event) noexcept {
@@ -728,6 +874,9 @@ void handle_event(const SDL_Event& event) noexcept {
}
if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
if (update_nav_axis(event.gaxis)) {
dispatch_nav_axis(*context, 0.0);
}
process_axis_direction(*context, event.gaxis, AXIS_SIGN_POSITIVE);
process_axis_direction(*context, event.gaxis, AXIS_SIGN_NEGATIVE);
sync_input_block();
@@ -744,7 +893,9 @@ void handle_event(const SDL_Event& event) noexcept {
if (chorded) {
consume_menu_chord(port, *context);
}
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ?
Rml::Input::KI_F1 :
map_gamepad_button(event.gbutton);
if (key != Rml::Input::KI_UNKNOWN) {
bool deferred = false;
if (repeat != nullptr) {
@@ -782,6 +933,10 @@ void update_input() noexcept {
auto* context = aurora::rmlui::get_context();
if (context != nullptr) {
const double now = now_seconds();
const double dtSeconds = sLastInputUpdateAt == 0.0 ?
0.0 :
std::clamp(now - sLastInputUpdateAt, 0.0, kMaxNavAxisDt);
sLastInputUpdateAt = now;
auto process_repeats = [context, now](auto& repeats) {
for (auto& repeat : repeats) {
if (!repeat.held) {
@@ -814,9 +969,11 @@ void update_input() noexcept {
};
process_repeats(sGamepadButtonRepeats);
process_repeats(sGamepadAxisRepeats);
dispatch_nav_axis(*context, dtSeconds);
} else {
reset_input_state();
sLastInputUpdateAt = 0.0;
}
}
} // namespace dusk::ui
} // namespace dusk::ui::input
+2
View File
@@ -4,6 +4,8 @@ union SDL_Event;
namespace dusk::ui::input {
inline constexpr char kNavAxisEvent[] = "navaxis";
void handle_event(const SDL_Event& event) noexcept;
void update_input() noexcept;
void reset_input_state() noexcept;
+363
View File
@@ -0,0 +1,363 @@
#include "list.hpp"
#include "ui.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <algorithm>
#include <ranges>
#include <utility>
namespace dusk::ui {
namespace {
Rml::Element* create_root(Rml::Element* parent) {
auto* document = parent->GetOwnerDocument();
auto root = document->CreateElement("ui-list");
return parent->AppendChild(std::move(root));
}
Rml::Element* append_element(Rml::Element* parent, const Rml::String& tag) {
auto element = parent->GetOwnerDocument()->CreateElement(tag);
return parent->AppendChild(std::move(element));
}
} // namespace
List::List(Rml::Element* parent, Props props)
: FluentComponent(create_root(parent)), mProps(std::move(props)) {
mViewport = append_element(mRoot, "ui-list-viewport");
mContent = append_element(mViewport, "ui-list-content");
mEmpty = append_element(mRoot, "ui-list-empty");
append_text(mEmpty, "No items");
Component::listen(mViewport, Rml::EventId::Scroll, [this](Rml::Event&) { mCullDirty = true; });
listen(Rml::EventId::Keydown, [this](Rml::Event& event) { handle_keydown(event); });
apply_items(std::move(mProps.items));
}
void List::set_items(std::vector<Item> items) {
mPendingSnapshotFocus = capture_snapshot_focus();
mPendingItems = std::move(items);
}
void List::update() {
if (mPendingItems) {
auto items = std::move(*mPendingItems);
const auto snapshotFocus = mPendingSnapshotFocus;
mPendingItems.reset();
mPendingSnapshotFocus.reset();
apply_items(std::move(items), snapshotFocus);
}
for (const auto& row : mRows) {
if (!row->culled) {
row->button->update();
}
}
const float scrollTop = mViewport->GetScrollTop();
const float viewportWidth = mViewport->GetClientWidth();
const float viewportHeight = mViewport->GetClientHeight();
if (scrollTop != mLastScrollTop || viewportWidth != mLastViewportWidth ||
viewportHeight != mLastViewportHeight)
{
mCullDirty = true;
if (viewportWidth != mLastViewportWidth || viewportHeight != mLastViewportHeight) {
mLayoutScanFrames = std::max(mLayoutScanFrames, 2);
}
mLastScrollTop = scrollTop;
mLastViewportWidth = viewportWidth;
mLastViewportHeight = viewportHeight;
}
if (mCullDirty || mLayoutScanFrames > 0) {
update_culling();
mCullDirty = false;
if (mLayoutScanFrames > 0) {
--mLayoutScanFrames;
}
}
update_pending_focus();
}
bool List::focus() {
if (mActiveKey) {
const int activeIndex = row_index(*mActiveKey);
if (activeIndex >= 0 && focus_row(activeIndex, true)) {
return true;
}
}
for (int i = 0; i < static_cast<int>(mRows.size()); ++i) {
if (focus_row(i, true)) {
return true;
}
}
return false;
}
bool List::focus_from(NavCommand direction) {
if (direction != NavCommand::Up) {
return focus();
}
for (int i = static_cast<int>(mRows.size()) - 1; i >= 0; --i) {
if (focus_row(i, true)) {
return true;
}
}
return false;
}
List::Row* List::row_from_element(Rml::Element* element) const {
for (const auto& row : mRows) {
if (row->button->contains(element)) {
return row.get();
}
}
return nullptr;
}
int List::row_index(uint64_t key) const {
for (int i = 0; i < static_cast<int>(mRows.size()); ++i) {
if (mRows[i]->key == key) {
return i;
}
}
return -1;
}
List::SnapshotFocus List::capture_snapshot_focus() {
auto* context = mRoot->GetContext();
auto* focusedElement = context != nullptr ? context->GetFocusElement() : nullptr;
Row* focusedRow = row_from_element(focusedElement);
if (focusedRow == nullptr) {
return {};
}
mActiveKey = focusedRow->key;
return {
.owned = true,
.key = focusedRow->key,
.index = row_index(focusedRow->key),
};
}
std::unique_ptr<List::Row> List::create_row(const Item& item) {
auto row = std::make_unique<Row>();
row->key = item.key;
row->button = std::make_unique<ControlledButton>(mContent,
ControlledButton::Props{
.text = item.label,
.isSelected =
[this, key = item.key] { return mProps.isSelected && mProps.isSelected(key); },
.isDisabled =
[this, key = item.key] { return mProps.isDisabled && mProps.isDisabled(key); },
});
row->button->root()->SetClass("ui-list-row", true);
row->button->root()->SetProperty("visibility", "hidden");
row->button->Component::listen(row->button->root(), Rml::EventId::Focus,
[this, key = item.key](Rml::Event&) { mActiveKey = key; });
row->button->on_pressed([this, key = item.key] {
mActiveKey = key;
if (mProps.onPressed) {
mProps.onPressed(key);
}
});
return row;
}
void List::apply_items(std::vector<Item> items, const std::optional<SnapshotFocus>& snapshotFocus) {
const SnapshotFocus focusState = snapshotFocus ? *snapshotFocus : capture_snapshot_focus();
std::unordered_map<uint64_t, std::unique_ptr<Row>> oldRows;
oldRows.reserve(mRows.size());
for (auto& row : mRows) {
oldRows.emplace(row->key, std::move(row));
}
mRows.clear();
mRows.reserve(items.size());
for (const auto& item : items) {
if (auto node = oldRows.extract(item.key); !node.empty()) {
auto row = std::move(node.mapped());
row->button->set_text(item.label);
mRows.push_back(std::move(row));
} else {
mRows.push_back(create_row(item));
}
}
for (auto& row : oldRows | std::views::values) {
mContent->RemoveChild(row->button->root());
}
for (int i = 0; i < static_cast<int>(mRows.size()); ++i) {
auto* desired = mRows[i]->button->root();
auto* current = mContent->GetChild(i);
if (current != desired) {
auto element = mContent->RemoveChild(desired);
mContent->InsertBefore(std::move(element), current);
}
}
mRowsByKey.clear();
mRowsByKey.reserve(mRows.size());
for (const auto& row : mRows) {
mRowsByKey.emplace(row->key, row.get());
}
mItems = std::move(items);
if (mRows.empty()) {
mViewport->SetProperty("display", "none");
mEmpty->RemoveProperty("display");
} else {
mViewport->RemoveProperty("display");
mEmpty->SetProperty("display", "none");
}
if (focusState.owned && !mRows.empty()) {
uint64_t targetKey =
mRows[std::min(focusState.index, static_cast<int>(mRows.size()) - 1)]->key;
if (focusState.key && mRowsByKey.contains(*focusState.key)) {
targetKey = *focusState.key;
}
request_focus(targetKey, false);
} else {
mPendingFocusKey.reset();
mPendingFocusFrames = 0;
mPendingFocusMayEnterList = false;
}
mCullDirty = true;
mLayoutScanFrames = 2;
}
void List::update_culling() {
const float viewTop = mViewport->GetAbsoluteOffset(Rml::BoxArea::Border).y;
const float viewHeight = mViewport->GetClientHeight();
if (viewHeight <= 0.0f) {
return;
}
auto* context = mRoot->GetContext();
const Row* focusedRow = context != nullptr ? row_from_element(context->GetFocusElement()) : nullptr;
for (const auto& row : mRows) {
auto* element = row->button->root();
const float top = element->GetAbsoluteOffset(Rml::BoxArea::Border).y - viewTop;
const bool inWindow =
top + element->GetOffsetHeight() >= -viewHeight && top <= viewHeight * 2.0f;
const bool focusGuard =
row.get() == focusedRow || (mPendingFocusKey && row->key == *mPendingFocusKey);
const bool shouldShow = inWindow || focusGuard;
if (shouldShow && row->culled) {
show_row(*row);
} else if (!shouldShow && !row->culled) {
row->culled = true;
element->SetProperty("visibility", "hidden");
}
}
}
void List::show_row(Row& row) {
if (!row.culled) {
return;
}
row.button->update();
row.button->root()->RemoveProperty("visibility");
row.culled = false;
}
bool List::focus_row(int index, bool mayEnterList) {
if (index < 0 || index >= static_cast<int>(mRows.size())) {
return false;
}
auto& row = *mRows[index];
show_row(row);
row.button->update();
if (row.button->root()->IsPseudoClassSet("disabled")) {
return false;
}
if (row.button->focus()) {
mActiveKey = row.key;
mPendingFocusKey.reset();
mPendingFocusFrames = 0;
mPendingFocusMayEnterList = false;
return true;
}
request_focus(row.key, mayEnterList);
return true;
}
void List::request_focus(uint64_t key, bool mayEnterList) {
const auto it = mRowsByKey.find(key);
if (it == mRowsByKey.end()) {
return;
}
show_row(*it->second);
mPendingFocusKey = key;
mPendingFocusFrames = 2;
mPendingFocusMayEnterList = mayEnterList;
}
void List::update_pending_focus() {
if (!mPendingFocusKey) {
return;
}
const auto it = mRowsByKey.find(*mPendingFocusKey);
if (it == mRowsByKey.end()) {
mPendingFocusKey.reset();
return;
}
auto* context = mRoot->GetContext();
auto* focusedElement = context != nullptr ? context->GetFocusElement() : nullptr;
if (!mPendingFocusMayEnterList && focusedElement != nullptr && !contains(focusedElement)) {
mPendingFocusKey.reset();
return;
}
if (it->second->button->contains(focusedElement)) {
mPendingFocusKey.reset();
mPendingFocusFrames = 0;
mPendingFocusMayEnterList = false;
return;
}
if (mPendingFocusFrames > 0) {
--mPendingFocusFrames;
return;
}
auto& row = *it->second;
show_row(row);
row.button->update();
if (!row.button->root()->IsPseudoClassSet("disabled") && row.button->focus()) {
mActiveKey = row.key;
}
mPendingFocusKey.reset();
mPendingFocusMayEnterList = false;
}
void List::handle_keydown(Rml::Event& event) {
const NavCommand command = map_nav_event(event);
if (command != NavCommand::Down && command != NavCommand::Up) {
return;
}
Row* focusedRow = row_from_element(event.GetTargetElement());
if (focusedRow == nullptr) {
return;
}
mActiveKey = focusedRow->key;
const int direction = command == NavCommand::Down ? 1 : -1;
for (int i = row_index(focusedRow->key) + direction;
i >= 0 && i < static_cast<int>(mRows.size()); i += direction)
{
if (focus_row(i, true)) {
mDoAud_seStartMenu(kSoundItemFocus);
event.StopPropagation();
return;
}
}
}
} // namespace dusk::ui
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include "button.hpp"
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
namespace dusk::ui {
class List : public FluentComponent<List> {
public:
struct Item {
uint64_t key = 0;
Rml::String label;
};
struct Props {
std::vector<Item> items;
std::function<void(uint64_t)> onPressed;
std::function<bool(uint64_t)> isSelected;
std::function<bool(uint64_t)> isDisabled;
};
List(Rml::Element* parent, Props props);
void set_items(std::vector<Item> items);
void update() override;
bool focus() override;
bool focus_from(NavCommand direction) override;
private:
struct SnapshotFocus {
bool owned = false;
std::optional<uint64_t> key;
int index = -1;
};
struct Row {
uint64_t key = 0;
std::unique_ptr<ControlledButton> button;
bool culled = true;
};
Row* row_from_element(Rml::Element* element) const;
int row_index(uint64_t key) const;
SnapshotFocus capture_snapshot_focus();
std::unique_ptr<Row> create_row(const Item& item);
void apply_items(std::vector<Item> items, const std::optional<SnapshotFocus>& snapshotFocus = {});
void update_culling();
void show_row(Row& row);
bool focus_row(int index, bool mayEnterList);
void request_focus(uint64_t key, bool mayEnterList);
void update_pending_focus();
void handle_keydown(Rml::Event& event);
Props mProps;
Rml::Element* mViewport = nullptr;
Rml::Element* mContent = nullptr;
Rml::Element* mEmpty = nullptr;
std::vector<Item> mItems;
std::optional<std::vector<Item>> mPendingItems;
std::optional<SnapshotFocus> mPendingSnapshotFocus;
std::vector<std::unique_ptr<Row>> mRows;
std::unordered_map<uint64_t, Row*> mRowsByKey;
std::optional<uint64_t> mActiveKey;
std::optional<uint64_t> mPendingFocusKey;
int mPendingFocusFrames = 0;
bool mPendingFocusMayEnterList = false;
bool mCullDirty = true;
int mLayoutScanFrames = 2;
float mLastScrollTop = -1.0f;
float mLastViewportWidth = -1.0f;
float mLastViewportHeight = -1.0f;
};
} // namespace dusk::ui
-1
View File
@@ -129,7 +129,6 @@ void LogsWindow::build_content(Rml::Element* content) {
mScrollElem = pane.root();
mLinesElem = append(pane.root(), "div");
mLinesElem->SetClass("log-lines", true);
pane.finalize();
listen(mScrollElem, Rml::EventId::Scroll, [this](Rml::Event&) {
const float bottom = mScrollElem->GetScrollHeight() - mScrollElem->GetClientHeight();
+26
View File
@@ -1,6 +1,7 @@
#include "mod_window.hpp"
#include "bool_button.hpp"
#include "color_input.hpp"
#include "number_button.hpp"
#include "string_button.hpp"
@@ -16,6 +17,7 @@ Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec) {
case ModControlSpec::Kind::Button:
control = &pane.add_button(ControlledButton::Props{
.text = s.label,
.isSelected = s.isSelected,
.isDisabled = s.isDisabled,
})
.on_pressed([shared] {
@@ -24,6 +26,18 @@ Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec) {
}
});
break;
case ModControlSpec::Kind::Group:
control = &pane.add_group_button(GroupButton::Props{
.text = s.label,
.isSelected = s.isSelected,
.isDisabled = s.isDisabled,
})
.on_pressed([shared] {
if (shared->onPressed) {
shared->onPressed();
}
});
break;
case ModControlSpec::Kind::Toggle:
control = &pane.add_child<BoolButton>(BoolButton::Props{
.key = s.label,
@@ -55,6 +69,18 @@ Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec) {
.isDisabled = s.isDisabled,
.isModified = s.isModified,
.maxLength = s.maxLength,
.setOnChange = s.stringSetOnChange,
});
break;
case ModControlSpec::Kind::Color:
control = &pane.add_child<ColorInput>(ColorInput::Props{
.key = s.label,
.getValue = s.getString,
.setValue = s.setString,
.isDisabled = s.isDisabled,
.isModified = s.isModified,
.presets = s.colorPresets,
.alpha = s.colorAlpha,
});
break;
case ModControlSpec::Kind::Select:
+6
View File
@@ -10,10 +10,12 @@ namespace dusk::ui {
struct ModControlSpec {
enum class Kind : u8 {
Button,
Group,
Toggle,
Number,
String,
Select,
Color,
};
Kind kind = Kind::Button;
@@ -27,6 +29,7 @@ struct ModControlSpec {
std::function<void(int)> setInt;
std::function<Rml::String()> getString;
std::function<void(Rml::String)> setString;
std::function<bool()> isSelected;
std::function<bool()> isDisabled;
std::function<bool()> isModified;
int min = 0;
@@ -36,6 +39,9 @@ struct ModControlSpec {
Rml::String suffix;
std::vector<Rml::String> options;
int maxLength = -1;
bool stringSetOnChange = false;
std::vector<Rml::String> colorPresets;
bool colorAlpha = false;
};
Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec);
+70 -9
View File
@@ -1,5 +1,7 @@
#include "modal.hpp"
#include <algorithm>
namespace dusk::ui {
Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::move(props)) {
@@ -23,6 +25,9 @@ Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::mo
body->SetClass("modal-body", true);
body->SetInnerRML(mProps.bodyRml);
mContentRoot = append(mDialog, "div");
mContentRoot->SetClass("modal-content", true);
auto* actions = append(mDialog, "div");
actions->SetClass("modal-actions", true);
if (props.isVertical) {
@@ -36,12 +41,43 @@ Modal::Modal(Props props) : WindowSmall("modal", "modal-dialog"), mProps(std::mo
mDoAud_seStartMenu(kSoundWindowOpen);
}
void Modal::update() {
if (mContentPane != nullptr) {
mContentPane->update();
}
for (const auto& button : mButtons) {
button->update();
}
if (mPendingAction) {
auto action = std::move(mPendingAction);
action(*this);
}
WindowSmall::update();
}
Pane& Modal::content_pane() {
if (mContentPane == nullptr) {
mContentRoot->SetClass("active", true);
mContentPane = std::make_unique<Pane>(mContentRoot, Pane::Type::Uncontrolled);
}
return *mContentPane;
}
void Modal::add_action(ModalAction action) {
auto* actions = mDialog->QuerySelector(".modal-actions");
auto btn = std::make_unique<Button>(actions, action.label);
auto btn =
std::make_unique<ControlledButton>(actions, ControlledButton::Props{
.text = std::move(action.label),
.isDisabled = std::move(action.isDisabled),
});
btn->root()->SetClass("modal-btn", true);
btn->on_pressed([this, callback = std::move(action.onPressed)] {
if (callback) {
if (!callback) {
return;
}
if (mContentPane != nullptr) {
mPendingAction = callback;
} else {
callback(*this);
}
});
@@ -68,8 +104,13 @@ void Modal::set_icon(const Rml::String& icon) {
}
bool Modal::focus() {
if (!mButtons.empty()) {
return mButtons.front()->focus();
if (mContentPane != nullptr && mContentPane->focus()) {
return true;
}
for (const auto& button : mButtons) {
if (button->focus()) {
return true;
}
}
return false;
}
@@ -89,6 +130,24 @@ bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
return true;
}
auto* target = event.GetTargetElement();
if (mContentPane != nullptr && mContentPane->contains(target) && cmd == NavCommand::Down) {
for (const auto& button : mButtons) {
if (button->focus()) {
mDoAud_seStartMenu(kSoundItemFocus);
return true;
}
}
}
if (mContentPane != nullptr && cmd == NavCommand::Up &&
std::ranges::any_of(
mButtons, [target](const auto& button) { return button->contains(target); }) &&
mContentPane->focus_last())
{
mDoAud_seStartMenu(kSoundItemFocus);
return true;
}
int direction = 0;
NavCommand prevCommand = mProps.isVertical ? NavCommand::Up : NavCommand::Left;
NavCommand nextCommand = mProps.isVertical ? NavCommand::Down : NavCommand::Right;
@@ -100,13 +159,15 @@ bool Modal::handle_nav_command(Rml::Event& event, NavCommand cmd) {
return false;
}
auto* target = event.GetTargetElement();
for (int i = 0; i < static_cast<int>(mButtons.size()); ++i) {
if (mButtons[i]->contains(target)) {
const int next = i + direction;
if (next >= 0 && next < static_cast<int>(mButtons.size()) && mButtons[next]->focus()) {
mDoAud_seStartMenu(kSoundItemFocus);
return true;
for (int next = i + direction; next >= 0 && next < static_cast<int>(mButtons.size());
next += direction)
{
if (mButtons[next]->focus()) {
mDoAud_seStartMenu(kSoundItemFocus);
return true;
}
}
return false;
}
+10 -3
View File
@@ -1,6 +1,7 @@
#pragma once
#include "button.hpp"
#include "pane.hpp"
#include "window.hpp"
namespace dusk::ui {
@@ -9,6 +10,7 @@ class Modal;
struct ModalAction {
Rml::String label;
std::function<void(Modal&)> onPressed;
std::function<bool()> isDisabled;
};
class Modal : public WindowSmall {
@@ -20,14 +22,15 @@ public:
std::function<void(Modal&)> onDismiss;
Rml::String variant;
Rml::String icon = "";
bool isVertical;
bool isVertical = false;
};
explicit Modal(Props props);
void update() override;
bool focus() override;
void add_action(ModalAction action);
Pane& content_pane();
void set_body(const Rml::String& bodyRml);
void set_icon(const Rml::String& icon);
@@ -35,10 +38,14 @@ protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
void add_action(ModalAction action);
void dismiss();
Props mProps;
std::vector<std::unique_ptr<Button> > mButtons;
Rml::Element* mContentRoot = nullptr;
std::unique_ptr<Pane> mContentPane;
std::function<void(Modal&)> mPendingAction;
std::vector<std::unique_ptr<Button>> mButtons;
};
} // namespace dusk::ui
-6
View File
@@ -192,8 +192,6 @@ void ModsWindow::build_content(Rml::Element* content) {
if (mods::ModLoader::instance().mods().empty()) {
listPane.add_text("No mods installed.");
listPane.finalize();
detailPane.finalize();
return;
}
@@ -214,8 +212,6 @@ void ModsWindow::build_content(Rml::Element* content) {
}
build_detail(detailPane, *mSelectedMod);
mark_current_entry();
listPane.finalize();
}
void ModsWindow::build_detail(Pane& pane, mods::LoadedMod& mod) {
@@ -279,8 +275,6 @@ void ModsWindow::build_detail(Pane& pane, mods::LoadedMod& mod) {
if (mod.active) {
mods::svc::ui_build_mods_panels(mod, pane);
}
pane.finalize();
}
void ModsWindow::mark_current_entry() {
+180
View File
@@ -0,0 +1,180 @@
#include "nav_group.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <algorithm>
namespace dusk::ui {
NavGroup::NavGroup(Rml::Element* root, Props props) : Component{root}, mProps{props} {
mProps.columns = std::max(mProps.columns, 1);
listen(
mRoot, Rml::EventId::Focus,
[this](Rml::Event& event) {
const int index = item_index(event.GetTargetElement());
if (index >= 0) {
mRememberedIndex = index;
}
},
true);
listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto direction = map_nav_event(event);
const auto result = navigate(event.GetTargetElement(), direction);
if (result == MoveResult::Moved) {
mDoAud_seStartMenu(kSoundItemFocus);
}
if (result != MoveResult::Unhandled) {
event.StopPropagation();
}
});
}
bool NavGroup::focus() {
if (mRememberedIndex >= 0 && focus_index(mRememberedIndex, NavCommand::None)) {
return true;
}
return focus_linear(0, 1, NavCommand::None);
}
bool NavGroup::focus_from(NavCommand direction) {
if (mRememberedIndex >= 0 && focus_index(mRememberedIndex, direction)) {
return true;
}
const bool reverse = direction == NavCommand::Up || direction == NavCommand::Left;
return focus_linear(
reverse ? static_cast<int>(mItems.size()) - 1 : 0, reverse ? -1 : 1, direction);
}
NavGroup::MoveResult NavGroup::navigate(Rml::Element* target, NavCommand direction) {
const bool horizontal = direction == NavCommand::Left || direction == NavCommand::Right;
const bool vertical = direction == NavCommand::Up || direction == NavCommand::Down;
if (!horizontal && !vertical) {
return MoveResult::Unhandled;
}
const int current = item_index(target);
if (current < 0) {
return MoveResult::Unhandled;
}
mRememberedIndex = current;
if (mProps.layout == Layout::Grid) {
return navigate_grid(current, direction);
}
if ((mProps.layout == Layout::Horizontal && horizontal) ||
(mProps.layout == Layout::Vertical && vertical))
{
return navigate_linear(current, direction);
}
return handle_boundary(direction);
}
NavGroup::MoveResult NavGroup::navigate_linear(int current, NavCommand direction) {
const int step = direction == NavCommand::Left || direction == NavCommand::Up ? -1 : 1;
if (focus_linear(current + step, step, direction)) {
return MoveResult::Moved;
}
return handle_boundary(direction);
}
NavGroup::MoveResult NavGroup::navigate_grid(int current, NavCommand direction) {
const int columns = mProps.columns;
const int row = current / columns;
const int column = current % columns;
const int rowStart = row * columns;
const int rowEnd = std::min(rowStart + columns, static_cast<int>(mItems.size()));
if (direction == NavCommand::Left || direction == NavCommand::Right) {
const int step = direction == NavCommand::Left ? -1 : 1;
const int edge = direction == NavCommand::Left ? rowStart - 1 : rowEnd;
for (int index = current + step; index != edge; index += step) {
if (focus_index(index, direction)) {
return MoveResult::Moved;
}
}
return handle_boundary(direction);
}
const int rowCount = (static_cast<int>(mItems.size()) + columns - 1) / columns;
const int step = direction == NavCommand::Up ? -1 : 1;
for (int nextRow = row + step; nextRow >= 0 && nextRow < rowCount; nextRow += step) {
if (focus_grid_row(nextRow, column, direction)) {
return MoveResult::Moved;
}
}
return handle_boundary(direction);
}
NavGroup::MoveResult NavGroup::handle_boundary(NavCommand direction) {
switch (boundary_for(direction)) {
case Boundary::Bubble:
return MoveResult::Unhandled;
case Boundary::Stop:
return MoveResult::Consumed;
}
return MoveResult::Unhandled;
}
bool NavGroup::focus_index(int index, NavCommand direction) {
if (index < 0 || index >= static_cast<int>(mItems.size())) {
return false;
}
if (!mItems[index]->focus_from(direction)) {
return false;
}
mRememberedIndex = index;
return true;
}
bool NavGroup::focus_linear(int start, int step, NavCommand direction) {
for (int index = start; index >= 0 && index < static_cast<int>(mItems.size()); index += step) {
if (focus_index(index, direction)) {
return true;
}
}
return false;
}
bool NavGroup::focus_grid_row(int row, int preferredColumn, NavCommand direction) {
const int rowStart = row * mProps.columns;
const int rowEnd = std::min(rowStart + mProps.columns, static_cast<int>(mItems.size()));
if (rowStart >= rowEnd) {
return false;
}
const int column = std::min(preferredColumn, rowEnd - rowStart - 1);
if (focus_index(rowStart + column, direction)) {
return true;
}
for (int distance = 1; distance < mProps.columns; ++distance) {
if (column - distance >= 0 && focus_index(rowStart + column - distance, direction)) {
return true;
}
if (rowStart + column + distance < rowEnd &&
focus_index(rowStart + column + distance, direction))
{
return true;
}
}
return false;
}
int NavGroup::item_index(Rml::Element* target) const {
for (int index = 0; index < static_cast<int>(mItems.size()); ++index) {
if (mItems[index]->contains(target)) {
return index;
}
}
return -1;
}
NavGroup::Boundary NavGroup::boundary_for(NavCommand direction) const {
if (direction == NavCommand::Left || direction == NavCommand::Right) {
return mProps.horizontalBoundary;
}
return mProps.verticalBoundary;
}
} // namespace dusk::ui
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include "component.hpp"
namespace dusk::ui {
class NavGroup : public Component {
public:
enum class Layout {
Horizontal,
Vertical,
Grid,
};
enum class Boundary {
Bubble,
Stop,
};
struct Props {
Layout layout = Layout::Vertical;
int columns = 1;
Boundary horizontalBoundary = Boundary::Bubble;
Boundary verticalBoundary = Boundary::Bubble;
};
NavGroup(Rml::Element* root, Props props);
bool focus() override;
bool focus_from(NavCommand direction) override;
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T>
T& add_item(Args&&... args) {
auto child = std::make_unique<T>(mRoot, std::forward<Args>(args)...);
return add_item(std::move(child));
}
template <typename T, typename... Args>
requires std::is_base_of_v<Component, T>
T& add_existing_item(Rml::Element* root, Args&&... args) {
auto child = std::make_unique<T>(root, std::forward<Args>(args)...);
return add_item(std::move(child));
}
private:
template <typename T>
T& add_item(std::unique_ptr<T> child) {
T& ref = *child;
mItems.push_back(&ref);
mChildren.emplace_back(std::move(child));
return ref;
}
enum class MoveResult {
Unhandled,
Consumed,
Moved,
};
MoveResult navigate(Rml::Element* target, NavCommand direction);
MoveResult navigate_linear(int current, NavCommand direction);
MoveResult navigate_grid(int current, NavCommand direction);
MoveResult handle_boundary(NavCommand direction);
bool focus_index(int index, NavCommand direction);
bool focus_linear(int start, int step, NavCommand direction);
bool focus_grid_row(int row, int preferredColumn, NavCommand direction);
int item_index(Rml::Element* target) const;
Boundary boundary_for(NavCommand direction) const;
Props mProps;
std::vector<Component*> mItems;
int mRememberedIndex = -1;
};
} // namespace dusk::ui
+9 -19
View File
@@ -90,11 +90,6 @@ Pane::Pane(Rml::Element* parent, Type type) : FluentComponent(createRoot(parent)
}
}
void Pane::update() {
finalize();
Component::update();
}
void Pane::set_selected_item(int index) {
if (mType == Type::Uncontrolled) {
return;
@@ -163,6 +158,15 @@ bool Pane::focus() {
return false;
}
bool Pane::focus_last() {
for (auto child = mChildren.rbegin(); child != mChildren.rend(); ++child) {
if ((*child)->focus()) {
return true;
}
}
return false;
}
Rml::Element* Pane::add_section(const Rml::String& text) {
auto* elem = append(mRoot, "div");
elem->SetClass("section-heading", true);
@@ -182,22 +186,8 @@ Rml::Element* Pane::add_rml(const Rml::String& rml) {
return elem;
}
void Pane::finalize() {
if (finalized) {
return;
}
finalized = true;
// Append spacer element to the bottom. RmlUi does not properly handle
// padding-bottom or margin-bottom on a scrollable flex container, so
// we need to create a fake spacer with an actual layout height to get
// padding at the bottom of a scrollable container.
append(mRoot, "spacer");
}
void Pane::clear() {
clear_children();
finalized = false;
}
} // namespace dusk::ui
+5 -3
View File
@@ -2,6 +2,7 @@
#include "button.hpp"
#include "component.hpp"
#include "group_button.hpp"
#include "select_button.hpp"
namespace dusk::ui {
@@ -16,7 +17,7 @@ public:
explicit Pane(Rml::Element* parent, Type type);
bool focus() override;
void update() override;
bool focus_last();
void set_selected_item(int index);
Component& register_control(
@@ -26,18 +27,19 @@ public:
ControlledButton& add_button(ControlledButton::Props props) {
return add_child<ControlledButton>(std::move(props));
}
GroupButton& add_group_button(GroupButton::Props props) {
return add_child<GroupButton>(std::move(props));
}
Button& add_button(Rml::String text) { return add_child<Button>(std::move(text)); }
ControlledSelectButton& add_select_button(ControlledSelectButton::Props props) {
return add_child<ControlledSelectButton>(std::move(props));
}
Rml::Element* add_text(const Rml::String& text);
Rml::Element* add_rml(const Rml::String& rml);
void finalize();
void clear();
private:
Type mType;
bool finalized = false;
};
} // namespace dusk::ui
+154
View File
@@ -0,0 +1,154 @@
#include "popover.hpp"
#include <algorithm>
namespace dusk::ui {
namespace {
const Rml::String kDocumentSource = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/popover.rcss" />
</head>
<body>
<popover id="popover"/>
</body>
</rml>
)RML";
constexpr float kAnchorGapDp = 8.0f;
constexpr float kViewportMarginDp = 8.0f;
} // namespace
Popover::Popover(Rml::Element* anchor, Side side, const Rml::String& windowClass)
: Document{kDocumentSource}, mAnchor{anchor}, mSide{side},
mBody{mDocument->GetElementById("popover")} {
if (!windowClass.empty()) {
mBody->SetClass(windowClass, true);
}
listen(mBody, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mBody && !mBody->HasAttribute("open") &&
Document::visible())
{
Document::hide(mPendingClose);
}
});
listen(
mDocument->GetContext()->GetRootElement(), Rml::EventId::Mousedown,
[this](const Rml::Event& event) {
const auto within = [](const Rml::Element* element, const Rml::Element* container) {
while (element != nullptr && element != container) {
element = element->GetParentNode();
}
return element != nullptr;
};
if (visible() && !within(event.GetTargetElement(), mBody)) {
dismiss();
}
},
true);
}
Popover::~Popover() {
notify_close(false);
}
void Popover::show() {
Document::show();
reposition();
mBody->SetAttribute("open", "");
focus();
}
void Popover::hide(bool close) {
notify_close(true);
mBody->RemoveAttribute("open");
mPendingClose = close;
}
bool Popover::focus() {
if (mOnFocus && mOnFocus()) {
return true;
}
return mBody != nullptr && mBody->Focus(true);
}
bool Popover::visible() const {
return mBody != nullptr && mBody->HasAttribute("open");
}
void Popover::update() {
Document::update();
if (!visible()) {
return;
}
if (mAnchor == nullptr || !mAnchor->IsVisible(true)) {
dismiss();
return;
}
reposition();
}
void Popover::dismiss() {
if (visible()) {
hide(true);
}
}
bool Popover::handle_nav_command(Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Cancel || cmd == NavCommand::Menu) {
dismiss();
return true;
}
return false;
}
void Popover::reposition() {
auto* context = mDocument->GetContext();
const auto dimensions = Rml::Vector2f{context->GetDimensions()};
const float dpRatio = context->GetDensityIndependentPixelRatio();
const float gap = kAnchorGapDp * dpRatio;
const float margin = kViewportMarginDp * dpRatio;
const Rml::Vector2f anchorPos = mAnchor->GetAbsoluteOffset(Rml::BoxArea::Border);
const Rml::Vector2f anchorSize = mAnchor->GetBox().GetSize(Rml::BoxArea::Border);
const Rml::Vector2f size = mBody->GetBox().GetSize(Rml::BoxArea::Border);
Rml::Vector2f pos;
switch (mSide) {
case Side::Above:
pos = {anchorPos.x + (anchorSize.x - size.x) * 0.5f, anchorPos.y - size.y - gap};
break;
case Side::Left:
pos = {anchorPos.x - size.x - gap, anchorPos.y + (anchorSize.y - size.y) * 0.5f};
break;
case Side::Right:
pos = {anchorPos.x + anchorSize.x + gap, anchorPos.y + (anchorSize.y - size.y) * 0.5f};
break;
case Side::Below:
default:
pos = {anchorPos.x + (anchorSize.x - size.x) * 0.5f, anchorPos.y + anchorSize.y + gap};
break;
}
pos.x = std::clamp(pos.x, margin, std::max(margin, dimensions.x - size.x - margin));
pos.y = std::clamp(pos.y, margin, std::max(margin, dimensions.y - size.y - margin));
mBody->SetProperty(Rml::PropertyId::Left, Rml::Property{pos.x, Rml::Unit::PX});
mBody->SetProperty(Rml::PropertyId::Top, Rml::Property{pos.y, Rml::Unit::PX});
}
void Popover::notify_close(bool restoreFocus) {
if (mOnClose) {
auto callback = std::move(mOnClose);
mOnClose = nullptr;
callback();
}
if (restoreFocus && mAnchor != nullptr && mAnchor->IsVisible(true)) {
mAnchor->Focus(true);
}
}
} // namespace dusk::ui
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "document.hpp"
namespace dusk::ui {
class Popover : public Document {
public:
enum class Side {
Below,
Above,
Left,
Right,
};
Popover(Rml::Element* anchor, Side side, const Rml::String& windowClass = "");
~Popover() override;
void show() override;
void hide(bool close) override;
bool focus() override;
bool visible() const override;
void update() override;
Rml::Element* body() const { return mBody; }
void dismiss();
void on_close(std::function<void()> callback) { mOnClose = std::move(callback); }
void on_focus(std::function<bool()> callback) { mOnFocus = std::move(callback); }
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
void reposition();
void notify_close(bool restoreFocus);
Rml::Element* mAnchor = nullptr;
Side mSide;
Rml::Element* mBody = nullptr;
std::function<void()> mOnClose;
std::function<bool()> mOnFocus;
};
} // namespace dusk::ui
+26 -22
View File
@@ -770,6 +770,12 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
},
.isDisabled = [] { return IsMobile; },
});
config_int_select(leftPane, rightPane, getSettings().video.uiScale,
"UI Scale",
"Scales the Dusklight interface relative to the display's DPI scale. Has no effect on the game's UI and HUD.",
50, 200, 25, {}, {}, "%");
leftPane.add_section("Resolution");
graphics_tuner_control(*this, leftPane, rightPane,
getSettings().game.internalResolutionScale,
@@ -887,7 +893,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.key = "Disable Cutscene Pillarboxing",
.helpText = "Disable black bars on the left and right sides of the screen "
"during some cutscenes, particularly on ultra-wide displays. "
"Visuals beyond the original intended framing may appear buggy."
"Visuals beyond the original intended framing may appear buggy.",
});
});
@@ -906,9 +912,10 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
};
leftPane.add_section("Inputs");
leftPane.register_control(leftPane.add_button("Configure Inputs").on_pressed([this] {
push(std::make_unique<ControllerConfigWindow>());
}),
leftPane.register_control(
leftPane.add_group_button({.text = "Configure Inputs"}).on_pressed([this] {
push(std::make_unique<ControllerConfigWindow>());
}),
rightPane, [](Pane& pane) {
pane.clear();
pane.add_text("Open input binding configuration.");
@@ -925,7 +932,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
addOption("Touch Controls", getSettings().game.enableTouchControls,
"Enables controls overlay for touch screens.<br/><br/>Press and drag on the left side "
"of the screen to move, and on the right side of the screen to control the camera.");
auto& customizeTouchLayout = leftPane.add_button(ControlledButton::Props{
auto& customizeTouchLayout = leftPane.add_group_button(GroupButton::Props{
.text = "Customize Layout",
.isDisabled = [] { return !getSettings().game.enableTouchControls; },
});
@@ -935,23 +942,20 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
pane.clear();
pane.add_text("Open the touch controls layout editor.");
});
leftPane.register_control(leftPane.add_select_button({
.key = "Touch Targeting",
.getValue =
[] {
return touch_targeting_label(
getSettings().game.touchTargeting.getValue());
},
.isDisabled =
[] { return !getSettings().game.enableTouchControls; },
.isModified =
[] {
const auto& targeting =
getSettings().game.touchTargeting;
return targeting.getValue() !=
targeting.getDefaultValue();
},
}),
leftPane.register_control(
leftPane.add_select_button({
.key = "Touch Targeting",
.getValue =
[] {
return touch_targeting_label(getSettings().game.touchTargeting.getValue());
},
.isDisabled = [] { return !getSettings().game.enableTouchControls; },
.isModified =
[] {
const auto& targeting = getSettings().game.touchTargeting;
return targeting.getValue() != targeting.getDefaultValue();
},
}),
rightPane, [](Pane& pane) {
pane.clear();
for (int i = 0; i < static_cast<int>(kTouchTargetingLabels.size()); ++i) {
+26 -6
View File
@@ -6,8 +6,8 @@ namespace dusk::ui {
BaseStringButton::BaseStringButton(Rml::Element* parent, Props props)
: BaseControlledSelectButton(parent, {std::move(props.key)}), mType(std::move(props.type)),
mMaxLength(props.maxLength) {
mInputListeners.reserve(4);
mMaxLength(props.maxLength), mSetOnChange(props.setOnChange) {
mInputListeners.reserve(5);
}
void BaseStringButton::update() {
@@ -35,8 +35,9 @@ void BaseStringButton::start_editing() {
if (mInputElem == nullptr) {
return;
}
mOriginalValue = input_value();
mInputElem->SetAttribute("type", mType);
mInputElem->SetAttribute("value", input_value());
mInputElem->SetAttribute("value", mOriginalValue);
if (mMaxLength > -1) {
mInputElem->SetAttribute("maxlength", mMaxLength);
}
@@ -58,7 +59,10 @@ void BaseStringButton::start_editing() {
mInputElem, Rml::EventId::Textinput, [this](Rml::Event& event) {
if (event.GetTargetElement() == mInputElem) {
const Rml::String text = event.GetParameter("text", Rml::String{});
if (!text.empty() && std::ranges::all_of(text, [](const char c) { return c == '\r' || c == '\n' || c == '\t'; })) {
if (!text.empty() &&
std::ranges::all_of(
text, [](const char c) { return c == '\r' || c == '\n' || c == '\t'; }))
{
event.StopImmediatePropagation();
}
}
@@ -78,6 +82,14 @@ void BaseStringButton::start_editing() {
mInputElem, Rml::EventId::Click, [](Rml::Event& event) { event.StopPropagation(); }));
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(mInputElem,
Rml::EventId::Blur, [this](Rml::Event&) { request_stop_editing(true, false); }));
if (mSetOnChange) {
mInputListeners.emplace_back(std::make_unique<ScopedEventListener>(
mInputElem, Rml::EventId::Change, [this](Rml::Event& event) {
if (event.GetTargetElement() == mInputElem) {
set_value(mInputElem->GetValue());
}
}));
}
}
void BaseStringButton::request_stop_editing(bool commit, bool refocusRoot) {
@@ -123,12 +135,15 @@ void BaseStringButton::stop_editing(bool commit, bool refocusRoot) {
if (!is_editing()) {
return;
}
if (commit) {
if (!mSetOnChange && commit) {
set_value(mInputElem->GetValue());
} else if (mSetOnChange && !commit) {
set_value(mOriginalValue);
}
mInputListeners.clear();
mRoot->RemoveChild(mInputElem);
mInputElem = nullptr;
mOriginalValue.clear();
// Restore value element
mValueElem->SetProperty(Rml::PropertyId::Visibility, Rml::Style::Visibility::Visible);
@@ -140,7 +155,12 @@ void BaseStringButton::stop_editing(bool commit, bool refocusRoot) {
}
StringButton::StringButton(Rml::Element* parent, Props props)
: BaseStringButton(parent, {.key = std::move(props.key), .maxLength = props.maxLength}),
: BaseStringButton(parent,
{
.key = std::move(props.key),
.maxLength = props.maxLength,
.setOnChange = props.setOnChange,
}),
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)),
mIsDisabled(std::move(props.isDisabled)), mIsModified(std::move(props.isModified)) {}
+5 -1
View File
@@ -12,6 +12,7 @@ public:
Rml::String key;
Rml::String type = "text";
int maxLength = -1;
bool setOnChange = false;
};
BaseStringButton(Rml::Element* parent, Props props);
@@ -30,13 +31,15 @@ private:
void stop_editing(bool commit = true, bool refocusRoot = false);
Rml::ElementFormControlInput* mInputElem = nullptr;
std::vector<std::unique_ptr<ScopedEventListener> > mInputListeners;
std::vector<std::unique_ptr<ScopedEventListener>> mInputListeners;
Rml::String mType;
int mMaxLength;
int mPendingInputFocusFrames = 0;
bool mPendingStopEditing = false;
bool mPendingCommit = true;
bool mPendingRefocusRoot = false;
bool mSetOnChange = false;
Rml::String mOriginalValue;
};
class StringButton : public BaseStringButton {
@@ -48,6 +51,7 @@ public:
std::function<bool()> isDisabled;
std::function<bool()> isModified;
int maxLength = -1;
bool setOnChange = false;
};
StringButton(Rml::Element* parent, Props props);
+12
View File
@@ -217,6 +217,8 @@ void handle_event(const SDL_Event& event) noexcept {
});
}
sConnectedGamepads.erase(event.gdevice.which);
} else if (event.type == SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED) {
apply_scale();
}
if (event.type == SDL_EVENT_WINDOW_FOCUS_LOST) {
sConsoleShortcutHeld = false;
@@ -527,4 +529,14 @@ bool consume_menu_notification_request() noexcept {
return requested;
}
void apply_scale() noexcept {
const auto userScale = getSettings().video.uiScale.getValue();
auto scale = 0.0f;
if (userScale != 0) {
const auto displayScale = aurora::window::get_window_size().scale;
scale = static_cast<float>(userScale) / 100.0f * (displayScale > 0.0f ? displayScale : 1.0f);
}
aurora::rmlui::set_ui_scale(scale);
}
} // namespace dusk::ui
+2
View File
@@ -116,4 +116,6 @@ bool consume_menu_notification_request() noexcept;
const char* battery_icon(SDL_PowerState state, int level) noexcept;
const char* connection_state_icon(SDL_JoystickConnectionState state) noexcept;
void apply_scale() noexcept;
} // namespace dusk::ui
+7
View File
@@ -14,4 +14,11 @@ namespace dusk::utils {
std::string base64_encode(const std::vector<uint8_t>& data);
bool base64_decode(const std::string& text, std::vector<uint8_t>& out);
uint32_t crc32(const void* data, size_t size);
struct PaneCache {
uint64_t tag;
float origTransX;
float origTransY;
bool cached;
};
} // namespace dusk::utils
+28 -4
View File
@@ -257,9 +257,10 @@ 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 IF_DUSK_ARG(u32 i_itemGiveTag)) {
createFunc i_createFunc IF_DUSK_ARG(u32 i_itemGiveTag) IF_DUSK_ARG(u8 i_itemOriginalNo)) {
fopAcM_prm_class* append = createAppend(i_setId, i_parameters, i_pos, i_roomNo, i_angle,
i_scale, i_argument, fpcM_ERROR_PROCESS_ID_e IF_DUSK_ARG(i_itemGiveTag));
i_scale, i_argument, fpcM_ERROR_PROCESS_ID_e IF_DUSK_ARG(i_itemGiveTag)
IF_DUSK_ARG(i_itemOriginalNo));
if (append == NULL) {
return fpcM_ERROR_PROCESS_ID_e;
}
@@ -268,9 +269,10 @@ fpc_ProcID fopAcM_create(s16 i_procName, u16 i_setId, u32 i_parameters, const cX
}
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 IF_DUSK_ARG(u32 i_itemGiveTag)) {
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument IF_DUSK_ARG(u32 i_itemGiveTag)
IF_DUSK_ARG(u8 i_itemOriginalNo)) {
return fopAcM_create(i_procName, 0xFFFF, i_parameters, i_pos, i_roomNo, i_angle, i_scale,
i_argument, NULL IF_DUSK_ARG(i_itemGiveTag));
i_argument, NULL IF_DUSK_ARG(i_itemGiveTag) IF_DUSK_ARG(i_itemOriginalNo));
}
fopAc_ac_c* fopAcM_fastCreate(s16 i_procName, u32 i_parameters, const cXyz* i_pos, int i_roomNo,
@@ -1592,6 +1594,12 @@ fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomN
int _ = -1;
#if TARGET_PC
const u8 originalItemNo = i_itemNo & 0xFF;
// Overridden item checks do not set StageLife, so boss containers
// also need a persistent local item bit.
constexpr u8 kBossHeartContainerFlag = 0x9F;
if (originalItemNo == dItemNo_UTAWA_HEART_e && param_8 == -1) {
param_8 = kBossHeartContainerFlag;
}
u32 giveTag;
if (i_itemCheckName != NULL) {
i_itemNo = dusk::mods::item_check(i_itemCheckName, originalItemNo, NULL);
@@ -1600,6 +1608,22 @@ fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomN
i_itemNo = dusk::mods::item_check_boss(originalItemNo, NULL);
giveTag = dusk::mods::item_give_tag_boss();
}
if (i_itemNo == dItemNo_NONE_e) {
const auto itemCheck = dusk::mods::item_check_commit(giveTag, originalItemNo, NULL);
i_itemNo = itemCheck.itemNo;
if (i_itemNo == dItemNo_NONE_e) {
dusk::mods::item_check_complete(itemCheck, NULL);
dComIfGs_onItem(param_8, i_roomNo);
return fpcM_ERROR_PROCESS_ID_e;
}
}
if (i_itemNo != originalItemNo) {
const u32 params = 0xFFFF0000 | param_8 << 8 | (i_itemNo & 0xFF);
return fopAcM_create(fpcNm_Obj_LifeContainer_e, params, i_pos, i_roomNo, i_angle,
i_scale, -1, giveTag, originalItemNo);
}
#endif
u32 params = 0xFFFF0000 | param_8 << 8 | (i_itemNo & 0xFF);
+1
View File
@@ -783,6 +783,7 @@ int game_main(int argc, char* argv[]) {
dusk::texture_replacements::reload();
dusk::ui::initialize();
dusk::ui::apply_scale();
dusk::ui::push_document(std::make_unique<dusk::ui::Overlay>(), true, true);
dusk::ui::push_document(std::make_unique<dusk::ui::TouchControls>(), false, true);
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), false);