mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-09-02 16:47:59 -04:00
Merge branch 'main' of ssh://github.com/TwilitRealm/dusklight into better-tools
This commit is contained in:
@@ -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(¤t.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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, ¤t.pos, 8, 3, 0xff);
|
||||
fopAcM_delete(this);
|
||||
} else {
|
||||
|
||||
+12
-12
@@ -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,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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(¤t.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);
|
||||
}
|
||||
|
||||
@@ -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(¤t.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(¤t.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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(¤t.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)) {
|
||||
|
||||
@@ -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(¤t.pos, itemNo, 0, -1, -1, NULL,
|
||||
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
|
||||
NULL IF_DUSK_ARG(itemGiveTag));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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(¤t.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;
|
||||
|
||||
@@ -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(¤t.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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
¤t.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(¤t.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
|
||||
|
||||
@@ -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(
|
||||
¤t.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(¤t.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL);
|
||||
mItemId = fopAcM_createItemForTrBoxDemo(
|
||||
¤t.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(¤t.pos, item, -1,
|
||||
fopAcM_GetRoomNo(this), 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag_sky_character()));
|
||||
mItemId =
|
||||
fopAcM_createItemForTrBoxDemo(¤t.pos, item, -1, fopAcM_GetRoomNo(this), 0,
|
||||
0 IF_DUSK_ARG(itemCheck.tag));
|
||||
|
||||
JUT_ASSERT(580, mItemId != fpcM_ERROR_PROCESS_ID_e);
|
||||
|
||||
|
||||
@@ -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(
|
||||
¤t.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag));
|
||||
¤t.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag));
|
||||
} else {
|
||||
item_id = fopAcM_createItemForTrBoxDemo(
|
||||
¤t.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag));
|
||||
¤t.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag));
|
||||
}
|
||||
|
||||
if (item_id != fpcM_ERROR_PROCESS_ID_e) {
|
||||
|
||||
@@ -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(¤t.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag));
|
||||
partner_id = fopAcM_createItemForPresentDemo(
|
||||
¤t.pos, item_no, 1, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag));
|
||||
} else {
|
||||
partner_id = fopAcM_createItemForTrBoxDemo(¤t.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(giveTag));
|
||||
partner_id = fopAcM_createItemForTrBoxDemo(
|
||||
¤t.pos, item_no, -1, -1, NULL, NULL IF_DUSK_ARG(itemCheck.tag));
|
||||
}
|
||||
|
||||
if (partner_id != -1) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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(),
|
||||
|
||||
@@ -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(¤t.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(
|
||||
¤t.pos, itemNo, 0, -1, -1, NULL, NULL IF_DUSK_ARG(itemGiveTag));
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -145,6 +145,7 @@ struct UserSettings {
|
||||
ConfigVar<bool> rememberWindowSize;
|
||||
ConfigVar<int> lastWindowWidth;
|
||||
ConfigVar<int> lastWindowHeight;
|
||||
ConfigVar<int> uiScale;
|
||||
} video;
|
||||
|
||||
struct {
|
||||
|
||||
@@ -185,8 +185,6 @@ AchievementsWindow::AchievementsWindow() {
|
||||
*confirmingAll = false;
|
||||
clearAllPtr->set_text("Clear All Achievements");
|
||||
});
|
||||
|
||||
pane.finalize();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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"}},
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)) {}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user