diff --git a/data/missable.h b/data/missable.h new file mode 100644 index 00000000..2f5dd2da --- /dev/null +++ b/data/missable.h @@ -0,0 +1,27 @@ +#ifndef MISSABLE_H +#define MISSABLE_H + +const uint8_t MISSABLE_PIXELS[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x23,0xae,0xa7,0x00,0x0a,0xe1,0xab,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x27,0x00,0x14,0xfe,0xf4,0x05,0x3a,0xff,0xce,0x00,0x03,0x4c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x02,0x96,0xf8,0x38,0x00,0x56,0x6e,0x80,0x80,0x6d,0x35,0x00,0x87,0xff,0x88,0x00,0x00,0x00, + 0x00,0x00,0x00,0x90,0xf7,0x55,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x5d,0xf0,0x3d,0x00,0x00,0x00, + 0x00,0x0c,0x00,0x04,0x2b,0x00,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x00,0x11,0x00,0x0d,0x34,0x00, + 0x07,0xe2,0xaa,0x0f,0x00,0x00,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x00,0x00,0x21,0xeb,0xe6,0x09, + 0x3c,0xf7,0xcf,0x01,0x00,0x00,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x00,0x00,0x01,0xcb,0xc1,0x1f, + 0x00,0x18,0x31,0x00,0x00,0x00,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x00,0x00,0x00,0x11,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x00,0x00,0x00,0x02,0x40,0x46, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xfd, + 0xff,0xff,0x04,0x00,0x00,0x00,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x00,0x00,0x00,0x00,0xbc,0xb8, + 0x8d,0x89,0x08,0x00,0x00,0x00,0x00,0x00,0x72,0xab,0xab,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x45,0x5a,0x06, + 0x04,0x6a,0xb5,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc7,0xff,0x61, + 0x0a,0xe4,0xff,0x49,0x00,0x00,0x00,0x00,0x8e,0xd5,0xd5,0x8e,0x00,0x00,0x00,0x00,0x02,0x62,0xbf,0x0a, + 0x00,0x51,0x51,0x01,0x01,0x00,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x00,0x35,0x27,0x00,0x00,0x00, + 0x00,0x00,0x00,0x08,0xc6,0x62,0x00,0x00,0xab,0xff,0xff,0xab,0x00,0x00,0x3a,0xf5,0xdc,0x14,0x00,0x00, + 0x00,0x00,0x00,0x60,0xff,0xd8,0x08,0x19,0x6b,0x80,0x80,0x68,0x62,0x17,0x0a,0xca,0x9d,0x05,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5d,0x33,0x00,0x83,0xff,0x81,0x00,0xaf,0xff,0x61,0x00,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xe6,0x57,0x00,0x60,0xb2,0x47,0x00,0x00,0x00,0x00,0x00,0x00, +}; + +#endif MISSABLE_H diff --git a/data/missable.png b/data/missable.png new file mode 100644 index 00000000..2ce3e8e7 Binary files /dev/null and b/data/missable.png differ diff --git a/data/png_to_array.py b/data/png_to_array.py new file mode 100644 index 00000000..963a1ece --- /dev/null +++ b/data/png_to_array.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 +import PIL.Image as Image + +def convert(source, output, name): + bytes = [] + with Image.open(source, mode='r') as img: + bytes = img.tobytes() + + with open(output, mode='w') as f: + f.write("#ifndef {0}_H\n".format(name)); + f.write("#define {0}_H\n\n".format(name)); + f.write("const uint8_t {0}_PIXELS[] = {{\n".format(name)); + + i = 0 + num_bytes = len(bytes) + while i < num_bytes: + f.write(" ") + for j in range(0,20): + if i < num_bytes: + # just write the alpha value. if it's non-zero the RGB will be constant + f.write("0x{0:02x},".format(bytes[i+3])) + i += 4 + f.write("\n") + + f.write("};\n\n"); + f.write("#endif {0}_H\n".format(name)); + +convert('missable.png', 'missable.h', 'MISSABLE') +convert('progression.png', 'progression.h', 'PROGRESSION') +convert('win-condition.png', 'win-condition.h', 'WIN_CONDITION') + diff --git a/data/progression.h b/data/progression.h new file mode 100644 index 00000000..fc332866 --- /dev/null +++ b/data/progression.h @@ -0,0 +1,27 @@ +#ifndef PROGRESSION_H +#define PROGRESSION_H + +const uint8_t PROGRESSION_PIXELS[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x14,0x00,0x00,0x14,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x11,0x89,0xe7,0xfa,0x17,0x17,0xfa,0xe8,0x89,0x13,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x06,0xe7,0xff,0xd6,0x7a,0x02,0x02,0x7a,0xd6,0xff,0xe8,0x41,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x84,0x58,0x01,0x00,0x00,0x00,0x00,0x01,0x5a,0xf0,0xf7,0x41,0x00,0x00,0x00, + 0x00,0x00,0x11,0x7e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2e,0xef,0xe7,0x13,0x00,0x00, + 0x00,0x00,0x8a,0xff,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5a,0xff,0x87,0x00,0x00, + 0x00,0x02,0xeb,0xd5,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x6d,0x0c,0x00,0x01,0xd6,0xea,0x02,0x00, + 0x00,0x2b,0xff,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xc9,0xff,0x3e,0x00,0x00,0x87,0xff,0x2c,0x00, + 0x00,0x08,0x7e,0x1d,0x00,0x00,0x25,0xc2,0x3b,0x0f,0xc8,0xff,0x83,0x00,0x00,0x00,0x60,0xff,0x4a,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x25,0xf2,0xf2,0xd0,0xff,0x83,0x00,0x00,0x00,0x00,0x61,0xff,0x49,0x00, + 0x00,0x00,0x9b,0x50,0x00,0x00,0x00,0x3b,0xf2,0xff,0x83,0x00,0x00,0x00,0x00,0x00,0x87,0xff,0x2c,0x00, + 0x00,0x01,0xe9,0xd5,0x01,0x00,0x00,0x00,0x32,0x5a,0x00,0x00,0x00,0x00,0x00,0x01,0xd7,0xea,0x02,0x00, + 0x00,0x00,0x8a,0xff,0x57,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5d,0xff,0x86,0x00,0x00, + 0x00,0x00,0x12,0xe6,0x87,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0xef,0xe6,0x12,0x00,0x00, + 0x00,0x00,0x00,0x07,0x01,0x0f,0x3e,0x01,0x00,0x00,0x00,0x00,0x01,0x5c,0xf1,0xf7,0x40,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0xd5,0x7a,0x02,0x02,0x7a,0xd7,0xff,0xe7,0x3f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x12,0x8a,0xea,0xfa,0x17,0x17,0xfa,0xe7,0x87,0x12,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x15,0x00,0x00,0x14,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +}; + +#endif PROGRESSION_H diff --git a/data/progression.png b/data/progression.png new file mode 100644 index 00000000..df06ba1e Binary files /dev/null and b/data/progression.png differ diff --git a/data/win-condition.h b/data/win-condition.h new file mode 100644 index 00000000..f08ef9e4 --- /dev/null +++ b/data/win-condition.h @@ -0,0 +1,27 @@ +#ifndef WIN_CONDITION_H +#define WIN_CONDITION_H + +const uint8_t WIN_CONDITION_PIXELS[] = { + 0x00,0x00,0x00,0x00,0x00,0x12,0x06,0x00,0x00,0x0c,0x0c,0x00,0x00,0x06,0x12,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xeb,0x96,0x00,0x00,0xc1,0xc1,0x00,0x00,0x96,0xeb,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xa9,0x00,0x00,0xd4,0xd4,0x00,0x00,0xa9,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xa9,0x00,0x00,0xd4,0xd4,0x00,0x00,0xa9,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xa9,0x00,0x00,0xd3,0xd3,0x00,0x00,0xa9,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xa9,0x00,0x00,0x5c,0x5c,0x00,0x00,0xa9,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xa9,0x00,0x00,0x00,0x00,0x00,0x00,0xa9,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xa9,0x00,0x00,0x00,0x00,0x00,0x00,0xa9,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x95,0x56,0x00,0x00,0x96,0x96,0x00,0x00,0x56,0x95,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x34,0xfe,0xfe,0x34,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb3,0xfd,0xfd,0xb3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x05,0x43,0x6f,0x9d,0xfe,0xa8,0xa8,0xfe,0x9d,0x6f,0x43,0x05,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x71,0xff,0xff,0xff,0xe4,0x28,0x28,0xe4,0xff,0xff,0xff,0x71,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x1b,0xdc,0xff,0x78,0x00,0x00,0x00,0x00,0x78,0xff,0xdc,0x1b,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x1c,0xdc,0xfd,0x25,0x00,0x00,0x25,0xfd,0xdc,0x1c,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x8f,0xff,0x1e,0x07,0x07,0x1e,0xff,0x8f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xb4,0xfa,0x6c,0xe2,0xe2,0x6c,0xfa,0xb4,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xd8,0xff,0xff,0xe8,0xe8,0xff,0xff,0xd8,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xdd,0xe8,0x73,0x0a,0x0a,0x73,0xe8,0xdd,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x09,0x00,0x00,0x00,0x00,0x09,0x10,0x00,0x00,0x00,0x00,0x00,0x00, +}; + +#endif WIN_CONDITION_H diff --git a/data/win-condition.png b/data/win-condition.png new file mode 100644 index 00000000..d4b17951 Binary files /dev/null and b/data/win-condition.png differ diff --git a/src/RA_Resource.h b/src/RA_Resource.h index 72e61579..032dadbe 100644 --- a/src/RA_Resource.h +++ b/src/RA_Resource.h @@ -145,6 +145,8 @@ #define IDC_RA_MEMVIEWER_4 1240 #define IDC_RA_DISPLAY_PROGRESS_INDICATOR 1241 #define IDC_RA_MEMVIEW_32BITBE 1242 +#define IDC_RA_LBL_TYPE 1243 +#define IDC_RA_TYPE 1244 #define IDD_RA_MEMORY 1501 diff --git a/src/RA_Shared.rc b/src/RA_Shared.rc index cd5d3f7d..519671fa 100644 --- a/src/RA_Shared.rc +++ b/src/RA_Shared.rc @@ -122,9 +122,11 @@ BEGIN EDITTEXT IDC_RA_TITLE,50,4,214,13,ES_AUTOHSCROLL LTEXT "ID:",IDC_RA_LBL_ID,282,7,11,8 EDITTEXT IDC_RA_ID,293,5,44,13,ES_AUTOHSCROLL | WS_DISABLED,WS_EX_RIGHT - CONTROL "Active",IDC_RA_CHK_ACTIVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,345,6,40,10 + CONTROL "Active",IDC_RA_CHK_ACTIVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,340,6,40,10 LTEXT "Description:",IDC_STATIC,4,23,42,8 - EDITTEXT IDC_RA_DESCRIPTION,50,21,333,13,ES_AUTOHSCROLL + EDITTEXT IDC_RA_DESCRIPTION,50,21,287,13,ES_AUTOHSCROLL + LTEXT "Type:",IDC_RA_LBL_TYPE,328,23,23,8 + COMBOBOX IDC_RA_TYPE,348,21,22,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Points:",IDC_RA_LBL_POINTS,4,40,23,8 COMBOBOX IDC_RA_POINTS,50,38,42,13,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Format:",IDC_RA_LBL_FORMAT,4,40,23,8 diff --git a/src/api/UpdateAchievement.hh b/src/api/UpdateAchievement.hh index 931d09eb..ad2144d0 100644 --- a/src/api/UpdateAchievement.hh +++ b/src/api/UpdateAchievement.hh @@ -27,6 +27,7 @@ public: unsigned int Points{ 0U }; unsigned int Category{ 0U }; std::string Badge; + unsigned int Type { 0U }; using Callback = std::function; diff --git a/src/api/impl/ConnectedServer.cpp b/src/api/impl/ConnectedServer.cpp index 3e56133b..47e8ab55 100644 --- a/src/api/impl/ConnectedServer.cpp +++ b/src/api/impl/ConnectedServer.cpp @@ -688,6 +688,7 @@ UpdateAchievement::Response ConnectedServer::UpdateAchievement(const UpdateAchie api_params.points = request.Points; api_params.category = request.Category; api_params.badge = request.Badge.c_str(); + api_params.type = request.Type; rc_api_request_t api_request; if (rc_api_init_update_achievement_request(&api_request, &api_params) == RC_OK) diff --git a/src/data/models/AchievementModel.cpp b/src/data/models/AchievementModel.cpp index 82134595..6a5db1b8 100644 --- a/src/data/models/AchievementModel.cpp +++ b/src/data/models/AchievementModel.cpp @@ -1,5 +1,7 @@ #include "AchievementModel.hh" +#include "RA_Log.h" + #include "data\context\GameContext.hh" #include "data\models\LocalBadgesModel.hh" @@ -17,6 +19,7 @@ namespace data { namespace models { const IntModelProperty AchievementModel::PointsProperty("AchievementModel", "Points", 5); +const IntModelProperty AchievementModel::AchievementTypeProperty("AchievementModel", "Type", ra::etoi(ra::data::models::AchievementType::None)); const StringModelProperty AchievementModel::BadgeProperty("AchievementModel", "Badge", L"00000"); const BoolModelProperty AchievementModel::PauseOnResetProperty("AchievementModel", "PauseOnReset", false); const BoolModelProperty AchievementModel::PauseOnTriggerProperty("AchievementModel", "PauseOnTrigger", false); @@ -30,6 +33,7 @@ AchievementModel::AchievementModel() noexcept GSL_SUPPRESS_F6 SetValue(TypeProperty, ra::etoi(AssetType::Achievement)); SetTransactional(PointsProperty); + SetTransactional(AchievementTypeProperty); SetTransactional(BadgeProperty); GSL_SUPPRESS_F6 AddAssetDefinition(m_pTrigger, TriggerProperty); @@ -65,6 +69,11 @@ void AchievementModel::OnValueChanged(const IntModelProperty::ChangeArgs& args) if (m_pAchievement) SyncPoints(); } + else if (args.Property == AchievementTypeProperty) + { + if (m_pAchievement) + SyncAchievementType(); + } else if (args.Property == IDProperty) { if (m_pAchievement) @@ -319,6 +328,28 @@ void AchievementModel::SyncPoints() m_pAchievement->public_.points = GetPoints(); } +void AchievementModel::SyncAchievementType() +{ + switch (GetAchievementType()) + { + case ra::data::models::AchievementType::None: + m_pAchievement->public_.type = RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD; + break; + case ra::data::models::AchievementType::Missable: + m_pAchievement->public_.type = RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE; + break; + case ra::data::models::AchievementType::Progression: + m_pAchievement->public_.type = RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION; + break; + case ra::data::models::AchievementType::Win: + m_pAchievement->public_.type = RC_CLIENT_ACHIEVEMENT_TYPE_WIN; + break; + default: + m_pAchievement->public_.type = gsl::narrow_cast(GetValue(AchievementTypeProperty)); + break; + } +} + void AchievementModel::SyncCategory() { switch (GetCategory()) @@ -421,6 +452,26 @@ void AchievementModel::Attach(struct rc_client_achievement_info_t& pAchievement, SetUpdatedTime(pAchievement.updated_time); SetTrigger(sTrigger); + switch (pAchievement.public_.type) + { + case RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD: + SetAchievementType(ra::data::models::AchievementType::None); + break; + case RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE: + SetAchievementType(ra::data::models::AchievementType::Missable); + break; + case RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION: + SetAchievementType(ra::data::models::AchievementType::Progression); + break; + case RC_CLIENT_ACHIEVEMENT_TYPE_WIN: + SetAchievementType(ra::data::models::AchievementType::Win); + break; + default: + RA_LOG_ERR("Unsupported achievement type: %d", pAchievement.public_.type); + SetValue(AchievementTypeProperty, pAchievement.public_.type); + break; + } + CreateServerCheckpoint(); CreateLocalCheckpoint(); @@ -446,6 +497,7 @@ void AchievementModel::AttachAndInitialize(struct rc_client_achievement_info_t& SyncCategory(); SyncTrigger(); SyncState(); + SyncAchievementType(); } void AchievementModel::Serialize(ra::services::TextWriter& pWriter) const @@ -453,7 +505,27 @@ void AchievementModel::Serialize(ra::services::TextWriter& pWriter) const WriteQuoted(pWriter, GetLocalAssetDefinition(m_pTrigger)); WritePossiblyQuoted(pWriter, GetLocalValue(NameProperty)); WritePossiblyQuoted(pWriter, GetLocalValue(DescriptionProperty)); - pWriter.Write(":::"); // progress/max/format/author + pWriter.Write("::"); // progress/max + + switch (GetAchievementType()) + { + case ra::data::models::AchievementType::None: + WritePossiblyQuoted(pWriter, ""); + break; + case ra::data::models::AchievementType::Missable: + WritePossiblyQuoted(pWriter, "missable"); + break; + case ra::data::models::AchievementType::Progression: + WritePossiblyQuoted(pWriter, "progression"); + break; + case ra::data::models::AchievementType::Win: + WritePossiblyQuoted(pWriter, "win_condition"); + break; + default: + WriteNumber(pWriter, GetValue(TypeProperty)); + break; + } + WritePossiblyQuoted(pWriter, GetLocalValue(AuthorProperty)); WriteNumber(pWriter, GetLocalValue(PointsProperty)); pWriter.Write("::::"); // created/modified/upvotes/downvotes @@ -504,9 +576,9 @@ bool AchievementModel::Deserialize(ra::Tokenizer& pTokenizer) if (!pTokenizer.Consume(':')) return false; - // field 7: progress format (unused) - pTokenizer.AdvanceTo(':'); - if (!pTokenizer.Consume(':')) + // field 7: achievement type + std::string sType; + if (!ReadPossiblyQuoted(pTokenizer, sType)) return false; // field 8: author (unused) @@ -555,6 +627,17 @@ bool AchievementModel::Deserialize(ra::Tokenizer& pTokenizer) SetBadge(ra::Widen(sBadge)); SetTrigger(sTrigger); + if (sType.empty()) + SetAchievementType(ra::data::models::AchievementType::None); + else if (sType == "missable") + SetAchievementType(ra::data::models::AchievementType::Missable); + else if (sType == "progression") + SetAchievementType(ra::data::models::AchievementType::Progression); + else if (sType == "win_condition") + SetAchievementType(ra::data::models::AchievementType::Win); + else + SetValue(AchievementTypeProperty, atoi(sType.c_str())); + return true; } diff --git a/src/data/models/AchievementModel.hh b/src/data/models/AchievementModel.hh index 43ff9962..72d68f4b 100644 --- a/src/data/models/AchievementModel.hh +++ b/src/data/models/AchievementModel.hh @@ -12,6 +12,14 @@ namespace ra { namespace data { namespace models { +enum AchievementType +{ + None = 0, + Missable, + Progression, + Win, +}; + class AchievementModel : public AssetModelBase { public: @@ -32,6 +40,21 @@ public: /// void SetPoints(int nValue) { SetValue(PointsProperty, nValue); } + /// + /// The for the achievement type. + /// + static const IntModelProperty AchievementTypeProperty; + + /// + /// Gets the type of the achievement. + /// + AchievementType GetAchievementType() const { return ra::itoe(GetValue(AchievementTypeProperty)); } + + /// + /// Sets the type for the achievement. + /// + void SetAchievementType(AchievementType nValue) { SetValue(AchievementTypeProperty, ra::etoi(nValue)); } + /// /// The for the badge. /// @@ -166,6 +189,7 @@ private: void SyncDescription(); void SyncBadge(); void SyncPoints(); + void SyncAchievementType(); void SyncCategory(); void SyncState(); void SyncTrigger(); diff --git a/src/ui/drawing/ISurface.hh b/src/ui/drawing/ISurface.hh index 3849cee8..4e4c7d75 100644 --- a/src/ui/drawing/ISurface.hh +++ b/src/ui/drawing/ISurface.hh @@ -114,6 +114,8 @@ public: /// The new opacity (0.0-1.0). virtual void SetOpacity(double fAlpha) = 0; + virtual void SetPixels(int nX, int nY, int nWidth, int nHeight, uint32_t* pARGB) = 0; + protected: ISurface() noexcept = default; }; diff --git a/src/ui/drawing/gdi/GDIBitmapSurface.cpp b/src/ui/drawing/gdi/GDIBitmapSurface.cpp index 7465059b..fe5f80ec 100644 --- a/src/ui/drawing/gdi/GDIBitmapSurface.cpp +++ b/src/ui/drawing/gdi/GDIBitmapSurface.cpp @@ -76,6 +76,18 @@ static constexpr uint8_t BlendPixel(std::uint8_t nTarget, std::uint8_t nBlend, s return gsl::narrow_cast(((nBlend * nAlpha) + (nTarget * (256 - nAlpha))) / 256); } +void GDIBitmapSurface::SetPixels(int nX, int nY, int nWidth, int nHeight, uint32_t* pARGB) noexcept +{ + const auto nStride = ra::to_signed(GetWidth()); + std::uint32_t* pBits = m_pBits + gsl::narrow_cast(nY) * nStride + nX; + + while (nHeight--) + { + memcpy(pBits + gsl::narrow_cast(nHeight) * nStride, pARGB, nWidth * sizeof(uint32_t)); + pARGB += nWidth; + } +} + void GDIBitmapSurface::WriteText(int nX, int nY, int nFont, Color nColor, const std::wstring& sText) { if (sText.empty()) diff --git a/src/ui/drawing/gdi/GDIBitmapSurface.hh b/src/ui/drawing/gdi/GDIBitmapSurface.hh index f4329488..79cc15e6 100644 --- a/src/ui/drawing/gdi/GDIBitmapSurface.hh +++ b/src/ui/drawing/gdi/GDIBitmapSurface.hh @@ -45,6 +45,8 @@ public: HBITMAP GetHBitmap() const noexcept { return m_hBitmap; } + void SetPixels(int nX, int nY, int nWidth, int nHeight, uint32_t* pARGB) noexcept override; + protected: std::uint32_t* m_pBits; // see note below about initializing this diff --git a/src/ui/drawing/gdi/GDISurface.hh b/src/ui/drawing/gdi/GDISurface.hh index 455bb20b..07035687 100644 --- a/src/ui/drawing/gdi/GDISurface.hh +++ b/src/ui/drawing/gdi/GDISurface.hh @@ -39,6 +39,10 @@ public: void DrawSurface(int nX, int nY, const ISurface& pSurface, int nSurfaceX, int nSurfaceY, int nWidth, int nHeight) noexcept override; GSL_SUPPRESS_F6 void SetOpacity(_UNUSED double) override { assert("This surface does not support opacity"); } + GSL_SUPPRESS_F6 void SetPixels(_UNUSED int, _UNUSED int, _UNUSED int, _UNUSED int, _UNUSED uint32_t*) override + { + assert("This surface does not support setting pixels"); + } HDC GetHDC() const noexcept { return m_hDC; } diff --git a/src/ui/viewmodels/AssetEditorViewModel.cpp b/src/ui/viewmodels/AssetEditorViewModel.cpp index 1d5be330..aa67c263 100644 --- a/src/ui/viewmodels/AssetEditorViewModel.cpp +++ b/src/ui/viewmodels/AssetEditorViewModel.cpp @@ -46,6 +46,7 @@ const StringModelProperty AssetEditorViewModel::AssetValidationWarningProperty(" const BoolModelProperty AssetEditorViewModel::HasMeasuredProperty("AssetEditorViewModel", "HasMeasured", false); const StringModelProperty AssetEditorViewModel::MeasuredValueProperty("AssetEditorViewModel", "MeasuredValue", L"[Not Active]"); const StringModelProperty AssetEditorViewModel::WaitingLabelProperty("AssetEditorViewModel", "WaitingLabel", L"Active"); +const IntModelProperty AssetEditorViewModel::AchievementTypeProperty("AssetEditorViewModel", "AchievementType", ra::etoi(ra::data::models::AchievementType::None)); const IntModelProperty AssetEditorViewModel::LeaderboardPartViewModel::ColorProperty("LeaderboardPartViewModel", "Color", 0); AssetEditorViewModel::AssetEditorViewModel() noexcept @@ -54,6 +55,11 @@ AssetEditorViewModel::AssetEditorViewModel() noexcept m_vmTrigger.AddNotifyTarget(*this); + m_vAchievementTypes.Add(ra::etoi(ra::data::models::AchievementType::None), L""); + m_vAchievementTypes.Add(ra::etoi(ra::data::models::AchievementType::Missable), L"Missable"); + m_vAchievementTypes.Add(ra::etoi(ra::data::models::AchievementType::Progression), L"Progression"); + m_vAchievementTypes.Add(ra::etoi(ra::data::models::AchievementType::Win), L"Win"); + m_vFormats.Add(ra::etoi(ra::data::ValueFormat::Value), L"Value"); m_vFormats.Add(ra::etoi(ra::data::ValueFormat::Score), L"Score"); m_vFormats.Add(ra::etoi(ra::data::ValueFormat::Frames), L"Frames"); @@ -204,6 +210,7 @@ void AssetEditorViewModel::LoadAsset(ra::data::models::AssetModelBase* pAsset, b { SetPoints(pAchievement->GetPoints()); SetBadge(pAchievement->GetBadge()); + SetAchievementType(pAchievement->GetAchievementType()); SetPauseOnReset(pAchievement->IsPauseOnReset()); SetPauseOnTrigger(pAchievement->IsPauseOnTrigger()); @@ -251,6 +258,7 @@ void AssetEditorViewModel::LoadAsset(ra::data::models::AssetModelBase* pAsset, b SetCategory(ra::itoe(CategoryProperty.GetDefaultValue())); SetPoints(PointsProperty.GetDefaultValue()); SetBadge(BadgeProperty.GetDefaultValue()); + SetAchievementType(ra::itoe(AchievementTypeProperty.GetDefaultValue())); SetPauseOnReset(PauseOnResetProperty.GetDefaultValue()); SetPauseOnTrigger(PauseOnTriggerProperty.GetDefaultValue()); SetWindowTitle(L"Achievement Editor"); @@ -412,6 +420,8 @@ void AssetEditorViewModel::OnDataModelIntValueChanged(const IntModelProperty::Ch SetCategory(ra::itoe(args.tNewValue)); else if (args.Property == ra::data::models::AchievementModel::PointsProperty) SetPoints(args.tNewValue); + else if (args.Property == ra::data::models::AchievementModel::TypeProperty) + SetAchievementType(ra::itoe(args.tNewValue)); else if (args.Property == ra::data::models::LeaderboardModel::ValueFormatProperty) SetValueFormat(ra::itoe(args.tNewValue)); else if (args.Property == ra::data::models::AssetModelBase::IDProperty) @@ -557,6 +567,8 @@ void AssetEditorViewModel::OnValueChanged(const IntModelProperty::ChangeArgs& ar { if (args.Property == PointsProperty) pAchievement->SetPoints(args.tNewValue); + else if (args.Property == AchievementTypeProperty) + pAchievement->SetAchievementType(ra::itoe(args.tNewValue)); } else { diff --git a/src/ui/viewmodels/AssetEditorViewModel.hh b/src/ui/viewmodels/AssetEditorViewModel.hh index 06501a45..9d31fd23 100644 --- a/src/ui/viewmodels/AssetEditorViewModel.hh +++ b/src/ui/viewmodels/AssetEditorViewModel.hh @@ -5,6 +5,7 @@ #include "TriggerViewModel.hh" #include "data\Types.hh" +#include "data\models\AchievementModel.hh" #include "data\models\AssetModelBase.hh" #include "ui\WindowViewModelBase.hh" @@ -202,6 +203,29 @@ public: /// void SelectBadgeFile(); + /// + /// The for the achievement type. + /// + static const IntModelProperty AchievementTypeProperty; + + /// + /// Gets the achievement type. + /// + ra::data::models::AchievementType GetAchievementType() const + { + return ra::itoe(GetValue(AchievementTypeProperty)); + } + + /// + /// Sets the achievement type. + /// + void SetAchievementType(ra::data::models::AchievementType nValue) { SetValue(AchievementTypeProperty, ra::etoi(nValue)); } + + /// + /// Gets the list of achievement types. + /// + const LookupItemViewModelCollection& AchievementTypes() const noexcept { return m_vAchievementTypes; } + // ===== Leaderboard Specific ===== /// @@ -452,6 +476,7 @@ protected: ra::data::models::AssetModelBase* m_pAsset = nullptr; bool m_bIgnoreTriggerUpdate = false; + LookupItemViewModelCollection m_vAchievementTypes; LookupItemViewModelCollection m_vFormats; ViewModelCollection m_vLeaderboardParts; }; diff --git a/src/ui/viewmodels/AssetUploadViewModel.cpp b/src/ui/viewmodels/AssetUploadViewModel.cpp index 001640a2..7cacf9b9 100644 --- a/src/ui/viewmodels/AssetUploadViewModel.cpp +++ b/src/ui/viewmodels/AssetUploadViewModel.cpp @@ -15,6 +15,8 @@ #include "RA_Defs.h" #include "RA_Log.h" +#include + namespace ra { namespace ui { namespace viewmodels { @@ -233,6 +235,25 @@ void AssetUploadViewModel::UploadAchievement(ra::data::models::AchievementModel& request.Points = pAchievement.GetPoints(); request.Badge = ra::Narrow(pAchievement.GetBadge()); + switch (pAchievement.GetAchievementType()) + { + case ra::data::models::AchievementType::None: + request.Type = RC_ACHIEVEMENT_TYPE_STANDARD; + break; + case ra::data::models::AchievementType::Missable: + request.Type = RC_ACHIEVEMENT_TYPE_MISSABLE; + break; + case ra::data::models::AchievementType::Progression: + request.Type = RC_ACHIEVEMENT_TYPE_PROGRESSION; + break; + case ra::data::models::AchievementType::Win: + request.Type = RC_ACHIEVEMENT_TYPE_WIN; + break; + default: + request.Type = ra::etoi(pAchievement.GetAchievementType()); + break; + } + if (pAchievement.GetCategory() == ra::data::models::AssetCategory::Local) { request.Category = ra::to_unsigned(ra::etoi(ra::data::models::AssetCategory::Unofficial)); diff --git a/src/ui/viewmodels/OverlayAchievementsPageViewModel.cpp b/src/ui/viewmodels/OverlayAchievementsPageViewModel.cpp index c30b65ca..4dde3a00 100644 --- a/src/ui/viewmodels/OverlayAchievementsPageViewModel.cpp +++ b/src/ui/viewmodels/OverlayAchievementsPageViewModel.cpp @@ -89,6 +89,18 @@ void OverlayAchievementsPageViewModel::Refresh() { OverlayListPageViewModel::Refresh(); + if (m_pMissableSurface == nullptr) + { + #include "..\data\missable.h" + m_pMissableSurface = LoadDecorator(MISSABLE_PIXELS, 20, 20); + + #include "..\data\progression.h" + m_pProgressionSurface = LoadDecorator(PROGRESSION_PIXELS, 20, 20); + + #include "..\data\win-condition.h" + m_pWinConditionSurface = LoadDecorator(WIN_CONDITION_PIXELS, 20, 20); + } + // title const auto& pGameContext = ra::services::ServiceLocator::Get(); const auto& sTitle = pGameContext.GameTitle(); @@ -177,6 +189,22 @@ void OverlayAchievementsPageViewModel::Refresh() { auto& pvmAchievement = GetNextItem(&nIndex); SetAchievement(pvmAchievement, **pAchievement); + + switch ((*pAchievement)->type) + { + case RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE: + pvmAchievement.SetDecorator(m_pMissableSurface.get()); + break; + case RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION: + pvmAchievement.SetDecorator(m_pProgressionSurface.get()); + break; + case RC_CLIENT_ACHIEVEMENT_TYPE_WIN: + pvmAchievement.SetDecorator(m_pWinConditionSurface.get()); + break; + default: + pvmAchievement.SetDecorator(nullptr); + break; + } } } @@ -278,7 +306,7 @@ const wchar_t* OverlayAchievementsPageViewModel::GetNextButtonText() const noexc return L"Leaderboards"; } -void OverlayAchievementsPageViewModel::RenderDetail(ra::ui::drawing::ISurface& pSurface, int nX, int nY, _UNUSED int nWidth, int nHeight) const +void OverlayAchievementsPageViewModel::RenderDetail(ra::ui::drawing::ISurface& pSurface, int nX, int nY, int nWidth, int nHeight) const { const auto* pAchievement = m_vItems.GetItemAt(GetSelectedItemIndex()); if (pAchievement == nullptr) @@ -294,12 +322,17 @@ void OverlayAchievementsPageViewModel::RenderDetail(ra::ui::drawing::ISurface& p pSurface.WriteText(nX + nAchievementSize + 12, nY + 4, nFont, pTheme.ColorOverlayText(), pAchievement->GetLabel()); pSurface.WriteText(nX + nAchievementSize + 12, nY + 4 + 26, nSubFont, pTheme.ColorOverlaySubText(), pAchievement->GetDetail()); - nY += nAchievementSize + 8; + + const auto* pDecorator = pAchievement->GetDecorator(); + if (pDecorator != nullptr) + pSurface.DrawSurface(nWidth - 5 - pDecorator->GetWidth(), nY + 6, *pDecorator); const auto pDetail = m_vAchievementDetails.find(pAchievement->GetId()); if (pDetail == m_vAchievementDetails.end()) return; + nY += nAchievementSize + 8; + const auto szDetail = pSurface.MeasureText(nDetailFont, L"M"); pSurface.WriteText(nX, nY, nDetailFont, pTheme.ColorOverlaySubText(), L"Created: " + pDetail->second.GetCreatedDate()); nY += szDetail.Height; diff --git a/src/ui/viewmodels/OverlayAchievementsPageViewModel.hh b/src/ui/viewmodels/OverlayAchievementsPageViewModel.hh index c59ad5d1..e45928b3 100644 --- a/src/ui/viewmodels/OverlayAchievementsPageViewModel.hh +++ b/src/ui/viewmodels/OverlayAchievementsPageViewModel.hh @@ -95,6 +95,10 @@ private: std::wstring m_sSummary; std::map m_mHeaderKeys; + + std::unique_ptr m_pMissableSurface; + std::unique_ptr m_pProgressionSurface; + std::unique_ptr m_pWinConditionSurface; }; } // namespace viewmodels diff --git a/src/ui/viewmodels/OverlayListPageViewModel.cpp b/src/ui/viewmodels/OverlayListPageViewModel.cpp index d1a4ca42..e102b089 100644 --- a/src/ui/viewmodels/OverlayListPageViewModel.cpp +++ b/src/ui/viewmodels/OverlayListPageViewModel.cpp @@ -117,6 +117,39 @@ bool OverlayListPageViewModel::Update(double fElapsed) return bUpdated; } +std::unique_ptr OverlayListPageViewModel::LoadDecorator( + const uint8_t* pixels, int nWidth, int nHeight) +{ + uint32_t* argb_pixels = static_cast(malloc(gsl::narrow_cast(nWidth) * nHeight * sizeof(uint32_t))); + if (argb_pixels == nullptr) + return nullptr; + + const auto& pTheme = ra::services::ServiceLocator::Get(); + ra::ui::Color nTextColor = pTheme.ColorOverlayDisabledText(); + + auto& pSurfaceFactory = ra::services::ServiceLocator::GetMutable(); + auto pDecorator = pSurfaceFactory.CreateTransparentSurface(nWidth, nHeight); + GSL_SUPPRESS_F23 uint32_t* dst = argb_pixels; + GSL_SUPPRESS_F23 const uint8_t* src = pixels; + const uint8_t* stop = pixels + gsl::narrow_cast(nWidth) * nHeight; + + while (src < stop) + { + nTextColor.Channel.A = *src++; + + if (nTextColor.Channel.A == 0) + *dst++ = 0; + else + *dst++ = nTextColor.ARGB; + } + + pDecorator->SetPixels(0, 0, nWidth, nHeight, argb_pixels); + free(argb_pixels); + + return pDecorator; +} + + void OverlayListPageViewModel::Render(ra::ui::drawing::ISurface& pSurface, int nX, int nY, int nWidth, int nHeight) const { if (m_bDetail) @@ -222,6 +255,10 @@ void OverlayListPageViewModel::RenderList(ra::ui::drawing::ISurface& pSurface, i const auto szProgress = pSurface.MeasureText(nProgressFont, sProgress); pSurface.WriteText(nTextX + 12 + nProgressBarWidth + 6, nY + 1 + 26 + 25 + 4 - (szProgress.Height / 2) - 1, nProgressFont, nSubTextColor, sProgress); } + + const auto* pDecorator = pItem->GetDecorator(); + if (pDecorator != nullptr) + pSurface.DrawSurface(nWidth - 5 - pDecorator->GetWidth(), nY + 6, *pDecorator); } nIndex++; diff --git a/src/ui/viewmodels/OverlayListPageViewModel.hh b/src/ui/viewmodels/OverlayListPageViewModel.hh index 6727e934..bc374991 100644 --- a/src/ui/viewmodels/OverlayListPageViewModel.hh +++ b/src/ui/viewmodels/OverlayListPageViewModel.hh @@ -138,6 +138,12 @@ public: /// Gets whether the current item is a header item. /// bool IsHeader() const { return GetId() == 0; } + + ra::ui::drawing::ISurface* GetDecorator() const noexcept { return m_pDecorator; } + void SetDecorator(ra::ui::drawing::ISurface* pDecorator) noexcept { m_pDecorator = pDecorator; } + + private: + ra::ui::drawing::ISurface* m_pDecorator = nullptr; }; protected: @@ -157,6 +163,8 @@ protected: static void SetHeader(OverlayListPageViewModel::ItemViewModel& vmItem, const std::wstring& sHeader); static bool AssetAppearsInFilter(const ra::data::models::AssetModelBase& pAsset); + std::unique_ptr LoadDecorator(const uint8_t* pixels, int nWidth, int nHeight); + ra::ui::ViewModelCollection m_vItems; private: diff --git a/src/ui/win32/AssetEditorDialog.cpp b/src/ui/win32/AssetEditorDialog.cpp index 7720df20..d8cd1d1b 100644 --- a/src/ui/win32/AssetEditorDialog.cpp +++ b/src/ui/win32/AssetEditorDialog.cpp @@ -711,6 +711,7 @@ AssetEditorDialog::AssetEditorDialog(AssetEditorViewModel& vmAssetEditor) m_bindBadgeImage(vmAssetEditor), m_bindPoints(vmAssetEditor), m_bindFormats(vmAssetEditor), + m_bindAchievementTypes(vmAssetEditor), m_bindLeaderboardParts(vmAssetEditor), m_bindLowerIsBetter(vmAssetEditor), m_bindErrorIcon(vmAssetEditor), @@ -771,6 +772,10 @@ AssetEditorDialog::AssetEditorDialog(AssetEditorViewModel& vmAssetEditor) m_bindWindow.BindVisible(IDC_RA_BADGE_SPIN, AssetEditorViewModel::IsAchievementProperty); m_bindWindow.BindVisible(IDC_RA_UPLOAD_BADGE, AssetEditorViewModel::IsAchievementProperty); m_bindWindow.BindVisible(IDC_RA_BADGEPIC, AssetEditorViewModel::IsAchievementProperty); + m_bindAchievementTypes.BindItems(vmAssetEditor.AchievementTypes()); + m_bindAchievementTypes.BindSelectedItem(AssetEditorViewModel::AchievementTypeProperty); + m_bindWindow.BindVisible(IDC_RA_LBL_TYPE, AssetEditorViewModel::IsAchievementProperty); + m_bindWindow.BindVisible(IDC_RA_TYPE, AssetEditorViewModel::IsAchievementProperty); // leaderboard only fields m_bindFormats.BindItems(vmAssetEditor.Formats()); @@ -940,6 +945,7 @@ BOOL AssetEditorDialog::OnInitDialog() m_bindPoints.SetControl(*this, IDC_RA_POINTS); m_bindFormats.SetControl(*this, IDC_RA_FORMAT); m_bindLeaderboardParts.SetControl(*this, IDC_RA_LBX_LBOARD_PARTS); + m_bindAchievementTypes.SetControl(*this, IDC_RA_TYPE); m_bindGroups.SetControl(*this, IDC_RA_LBX_GROUPS); m_bindConditions.SetControl(*this, IDC_RA_LBX_CONDITIONS); @@ -975,6 +981,8 @@ BOOL AssetEditorDialog::OnInitDialog() SendMessage(m_hTooltip, TTM_ACTIVATE, TRUE, 0); } + SendMessage(::GetDlgItem(GetHWND(), IDC_RA_TYPE), CB_SETDROPPEDWIDTH, 70, 0); + return DialogBase::OnInitDialog(); } diff --git a/src/ui/win32/AssetEditorDialog.hh b/src/ui/win32/AssetEditorDialog.hh index 76e1d642..27bbd8ff 100644 --- a/src/ui/win32/AssetEditorDialog.hh +++ b/src/ui/win32/AssetEditorDialog.hh @@ -143,6 +143,7 @@ private: ra::ui::win32::bindings::ImageBinding m_bindBadgeImage; PointBinding m_bindPoints; ra::ui::win32::bindings::ComboBoxBinding m_bindFormats; + ra::ui::win32::bindings::ComboBoxBinding m_bindAchievementTypes; ra::ui::win32::bindings::GridBinding m_bindLeaderboardParts; ra::ui::win32::bindings::CheckBoxBinding m_bindLowerIsBetter; diff --git a/tests/data/DataAsserts.hh b/tests/data/DataAsserts.hh index 0fa69661..ee0e9221 100644 --- a/tests/data/DataAsserts.hh +++ b/tests/data/DataAsserts.hh @@ -152,6 +152,24 @@ std::wstring ToString(cons } } +template<> +std::wstring ToString(const ra::data::models::AchievementType& nAchievementType) +{ + switch (nAchievementType) + { + case ra::data::models::AchievementType::None: + return L"None"; + case ra::data::models::AchievementType::Missable: + return L"Missable"; + case ra::data::models::AchievementType::Progression: + return L"Progression"; + case ra::data::models::AchievementType::Win: + return L"Win"; + default: + return std::to_wstring(static_cast(nAchievementType)); + } +} + #pragma warning(pop) } // namespace CppUnitTestFramework diff --git a/tests/data/models/AchievementModel_Tests.cpp b/tests/data/models/AchievementModel_Tests.cpp index 16c98834..9e02a616 100644 --- a/tests/data/models/AchievementModel_Tests.cpp +++ b/tests/data/models/AchievementModel_Tests.cpp @@ -62,6 +62,7 @@ public: Assert::AreEqual(AssetState::Inactive, achievement.GetState()); Assert::AreEqual(AssetChanges::None, achievement.GetChanges()); Assert::AreEqual(5, achievement.GetPoints()); + Assert::AreEqual(AchievementType::None, achievement.GetAchievementType()); Assert::AreEqual(std::wstring(L"00000"), achievement.GetBadge()); Assert::IsFalse(achievement.IsPauseOnReset()); Assert::IsFalse(achievement.IsPauseOnTrigger()); diff --git a/tests/mocks/MockSurface.hh b/tests/mocks/MockSurface.hh index 7eb4ab51..e0df477f 100644 --- a/tests/mocks/MockSurface.hh +++ b/tests/mocks/MockSurface.hh @@ -36,6 +36,7 @@ public: void DrawSurface(int, int, const ISurface&) noexcept override {} void DrawSurface(int, int, const ISurface&, int, int, int, int) noexcept override {} void SetOpacity(double) noexcept override {} + void SetPixels(int, int, int, int, uint32_t*) noexcept override {} private: unsigned int m_nWidth; diff --git a/tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp b/tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp index e318a6e8..859a3aca 100644 --- a/tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp +++ b/tests/ui/viewmodels/AssetEditorViewModel_Tests.cpp @@ -131,6 +131,16 @@ public: Assert::AreEqual((int)AssetEditorViewModel::LeaderboardPart::Start, (int)editor.GetSelectedLeaderboardPart()); Assert::AreEqual(ra::data::ValueFormat::Value, editor.GetValueFormat()); + Assert::AreEqual({ 4U }, editor.AchievementTypes().Count()); + Assert::AreEqual((int)ra::data::models::AchievementType::None, editor.AchievementTypes().GetItemAt(0)->GetId()); + Assert::AreEqual(std::wstring(L""), editor.AchievementTypes().GetItemAt(0)->GetLabel()); + Assert::AreEqual((int)ra::data::models::AchievementType::Missable, editor.AchievementTypes().GetItemAt(1)->GetId()); + Assert::AreEqual(std::wstring(L"Missable"), editor.AchievementTypes().GetItemAt(1)->GetLabel()); + Assert::AreEqual((int)ra::data::models::AchievementType::Progression, editor.AchievementTypes().GetItemAt(2)->GetId()); + Assert::AreEqual(std::wstring(L"Progression"), editor.AchievementTypes().GetItemAt(2)->GetLabel()); + Assert::AreEqual((int)ra::data::models::AchievementType::Win, editor.AchievementTypes().GetItemAt(3)->GetId()); + Assert::AreEqual(std::wstring(L"Win"), editor.AchievementTypes().GetItemAt(3)->GetLabel()); + Assert::AreEqual({ 6U }, editor.Formats().Count()); Assert::AreEqual((int)ra::data::ValueFormat::Value, editor.Formats().GetItemAt(0)->GetId()); Assert::AreEqual(std::wstring(L"Value"), editor.Formats().GetItemAt(0)->GetLabel()); @@ -966,6 +976,31 @@ public: Assert::AreEqual(50, achievement.GetPoints()); } + TEST_METHOD(TestSyncAchievementType) + { + AssetEditorViewModelHarness editor; + AchievementModel achievement; + achievement.SetAchievementType(ra::data::models::AchievementType::Progression); + achievement.CreateServerCheckpoint(); + achievement.CreateLocalCheckpoint(); + + editor.LoadAsset(&achievement); + Assert::AreEqual(ra::data::models::AchievementType::Progression, editor.GetAchievementType()); + Assert::AreEqual(ra::data::models::AchievementType::Progression, achievement.GetAchievementType()); + + editor.SetAchievementType(ra::data::models::AchievementType::None); + Assert::AreEqual(ra::data::models::AchievementType::None, editor.GetAchievementType()); + Assert::AreEqual(ra::data::models::AchievementType::None, achievement.GetAchievementType()); + + editor.SetAchievementType(ra::data::models::AchievementType::Win); + Assert::AreEqual(ra::data::models::AchievementType::Win, editor.GetAchievementType()); + Assert::AreEqual(ra::data::models::AchievementType::Win, achievement.GetAchievementType()); + + editor.SetAchievementType(ra::data::models::AchievementType::Missable); + Assert::AreEqual(ra::data::models::AchievementType::Missable, editor.GetAchievementType()); + Assert::AreEqual(ra::data::models::AchievementType::Missable, achievement.GetAchievementType()); + } + TEST_METHOD(TestSyncBadge) { AssetEditorViewModelHarness editor; diff --git a/tests/ui/viewmodels/AssetUploadViewModel_Tests.cpp b/tests/ui/viewmodels/AssetUploadViewModel_Tests.cpp index 74558f41..879550b8 100644 --- a/tests/ui/viewmodels/AssetUploadViewModel_Tests.cpp +++ b/tests/ui/viewmodels/AssetUploadViewModel_Tests.cpp @@ -283,6 +283,46 @@ public: Assert::AreEqual(std::string("12345"), pRequest.Badge); Assert::AreEqual(3U, pRequest.Category); Assert::AreEqual(1U, pRequest.AchievementId); + Assert::AreEqual(0U, pRequest.Type); + + pResponse.AchievementId = pRequest.AchievementId; + pResponse.Result = ra::api::ApiResult::Success; + return true; + }); + + vmUpload.DoUpload(); + + Assert::IsTrue(bApiCalled); + Assert::AreEqual(AssetChanges::None, pAchievement.GetChanges()); + + vmUpload.AssertSuccess(1); + } + + TEST_METHOD(TestSingleCoreAchievementMissable) + { + AssetUploadViewModelHarness vmUpload; + auto& pAchievement = vmUpload.AddAchievement(AssetCategory::Core, 5, L"Title1", L"Desc1", L"12345", "0xH1234=1"); + pAchievement.SetAchievementType(ra::data::models::AchievementType::Missable); + pAchievement.UpdateLocalCheckpoint(); + Assert::AreEqual(AssetChanges::Unpublished, pAchievement.GetChanges()); + + vmUpload.QueueAsset(pAchievement); + Assert::AreEqual({ 1U }, vmUpload.TaskCount()); + + bool bApiCalled = false; + vmUpload.mockServer.HandleRequest([&bApiCalled] + (const ra::api::UpdateAchievement::Request& pRequest, ra::api::UpdateAchievement::Response& pResponse) + { + bApiCalled = true; + Assert::AreEqual(AssetUploadViewModelHarness::GameId, pRequest.GameId); + Assert::AreEqual(std::wstring(L"Title1"), pRequest.Title); + Assert::AreEqual(std::wstring(L"Desc1"), pRequest.Description); + Assert::AreEqual(std::string("0xH1234=1"), pRequest.Trigger); + Assert::AreEqual(5U, pRequest.Points); + Assert::AreEqual(std::string("12345"), pRequest.Badge); + Assert::AreEqual(3U, pRequest.Category); + Assert::AreEqual(1U, pRequest.AchievementId); + Assert::AreEqual(1U, pRequest.Type); pResponse.AchievementId = pRequest.AchievementId; pResponse.Result = ra::api::ApiResult::Success; diff --git a/tests/ui/viewmodels/OverlayAchievementsPageViewModel_Tests.cpp b/tests/ui/viewmodels/OverlayAchievementsPageViewModel_Tests.cpp index 8937c911..70b67dc5 100644 --- a/tests/ui/viewmodels/OverlayAchievementsPageViewModel_Tests.cpp +++ b/tests/ui/viewmodels/OverlayAchievementsPageViewModel_Tests.cpp @@ -7,8 +7,10 @@ #include "tests\mocks\MockGameContext.hh" #include "tests\mocks\MockImageRepository.hh" #include "tests\mocks\MockOverlayManager.hh" +#include "tests\mocks\MockOverlayTheme.hh" #include "tests\mocks\MockServer.hh" #include "tests\mocks\MockSessionTracker.hh" +#include "tests\mocks\MockSurface.hh" #include "tests\mocks\MockThreadPool.hh" #include "tests\mocks\MockUserContext.hh" #include "tests\mocks\MockWindowManager.hh" @@ -38,6 +40,8 @@ private: ra::services::mocks::MockClock mockClock; ra::services::mocks::MockThreadPool mockThreadPool; ra::ui::mocks::MockImageRepository mockImageRepository; + ra::ui::mocks::MockOverlayTheme mockOverlayTheme; + ra::ui::drawing::mocks::MockSurfaceFactory mockSurfaceFactory; ra::ui::viewmodels::mocks::MockOverlayManager mockOverlayManager; ra::ui::viewmodels::mocks::MockWindowManager mockWindowManager;