diff --git a/.gitignore b/.gitignore index 8674b2f6a..e6b38694c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ out /src/CMakeSettings.json +# ctest +/src/Testing + # Possible symlink for fixing tools that hardcode "game/mod_hl2mp" - we use "game/neo". /game/mod_hl2mp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e5ff4f721..f6abe6bf9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -602,3 +602,6 @@ if(NEO_EXTRA_ASSETS) "${neo_assets_SOURCE_DIR}/neo" "${CMAKE_SOURCE_DIR}/../game/neo") endif() + +add_subdirectory(tests) + diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index da9041233..d5b529b26 100644 --- a/src/game/client/CMakeLists.txt +++ b/src/game/client/CMakeLists.txt @@ -1656,6 +1656,8 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_enums.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_crosshair.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_crosshair.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.h ) target_sources_grouped( @@ -1666,6 +1668,7 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_aa13.h ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_balc.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_balc.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_bits.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_bits.h ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_detpack.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_detpack.h @@ -1725,6 +1728,7 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_zr68l.h ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_zr68s.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_zr68s.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/neo_weapon_types.h ) if(NEO_BUILD_WEAPON_PBK56S) diff --git a/src/game/client/cdll_client_int.cpp b/src/game/client/cdll_client_int.cpp index 18bb8fb60..b0b6cbd6f 100644 --- a/src/game/client/cdll_client_int.cpp +++ b/src/game/client/cdll_client_int.cpp @@ -1416,10 +1416,19 @@ void CHLClient::PostInit() { g_pCVar->FindVar("neo_name")->InstallChangeCallback(NeoConVarStrLimitChangeCallback); g_pCVar->FindVar("neo_clantag")->InstallChangeCallback(NeoConVarStrLimitChangeCallback); - g_pCVar->FindVar("cl_neo_crosshair")->InstallChangeCallback(NeoConVarStrLimitChangeCallback); + g_pCVar->FindVar("cl_neo_crosshair")->InstallChangeCallback(NeoConVarCrosshairChangeCallback); g_pCVar->FindVar("sv_use_steam_networking")->SetValue(false); RestrictNeoClientCheats(); + // Fixup invalid crosshair to default + ConVarRef cl_neo_crosshair("cl_neo_crosshair"); + if (false == ValidateCrosshairSerial(cl_neo_crosshair.GetString())) + { + char szSequence[NEO_XHAIR_SEQMAX] = {}; + DefaultCrosshairSerial(szSequence); + cl_neo_crosshair.SetValue(szSequence); + } + ConVar *sv_maxupdaterate = g_pCVar->FindVar( "sv_maxupdaterate" ); Assert(sv_maxupdaterate); ConVar *cl_updaterate = g_pCVar->FindVar( "cl_updaterate" ); Assert(cl_updaterate); static char svMaxUpdateRateDefault[4]; @@ -1523,6 +1532,18 @@ void CHLClient::PostInit() } } + if (iCfgVerMajor < 29) + { + // Upgrade pre NEOXHAIR_SERIAL_ALPHA_V29 crosshairs to NEOXHAIR_SERIAL_ALPHA_V29+ + CrosshairInfo xhairInfo = {}; + if (ImportCrosshair(&xhairInfo, cl_neo_crosshair.GetString())) + { + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + cl_neo_crosshair.SetValue(szExportSeq); + } + } + cvr_cl_neo_cfg_version_major.SetValue(NEO_VERSION_MAJOR); cvr_cl_neo_cfg_version_minor.SetValue(NEO_VERSION_MINOR); } diff --git a/src/game/client/hud_crosshair.cpp b/src/game/client/hud_crosshair.cpp index 5bf3ce8c1..30397eba6 100644 --- a/src/game/client/hud_crosshair.cpp +++ b/src/game/client/hud_crosshair.cpp @@ -62,14 +62,6 @@ void CVGlobal_NeoClCrosshair(IConVar *var, [[maybe_unused]] const char *pOldStri } } -ConVar cl_neo_crosshair_hip_fire("cl_neo_crosshair_hip_fire", "0", FCVAR_ARCHIVE, "Show the crosshair when not aiming", true, 0, true, 1, - []([[maybe_unused]] IConVar* var, [[maybe_unused]] const char* pOldString, [[maybe_unused]] float flOldValue)->void{ - CHudCrosshair *crosshair = GET_HUDELEMENT(CHudCrosshair); - if (crosshair) - { - crosshair->SetHiddenBits(HIDEHUD_PLAYERDEAD | (cl_neo_crosshair_hip_fire.GetBool() ? 0 : HIDEHUD_CROSSHAIR)); - } - }); ConVar cl_neo_crosshair_scope_inaccuracy("cl_neo_crosshair_scope_inaccuracy", "1", FCVAR_ARCHIVE, "Show the player's inaccuracy when scoped", true, 0, true, 1); ConVar cl_neo_crosshair_friendly_fire_warning("cl_neo_crosshair_friendly_fire_warning", "1", FCVAR_ARCHIVE, "Replace crosshair with friendly fire warning where applicable", true, 0, true, 1); #endif @@ -116,7 +108,7 @@ CHudCrosshair::CHudCrosshair( const char *pElementName ) : surface()->DrawSetTextureFile(m_hCrosshairLight, "vgui/hud/scopes/scope03-1", 1, false); surface()->DrawGetTextureSize(m_hCrosshairLight, m_iCrosshairLightWidth, m_iCrosshairLightHeight); - SetHiddenBits( HIDEHUD_PLAYERDEAD | (cl_neo_crosshair_hip_fire.GetBool() ? 0 : HIDEHUD_CROSSHAIR) ); + SetHiddenBits( HIDEHUD_PLAYERDEAD ); #else SetHiddenBits( HIDEHUD_PLAYERDEAD | HIDEHUD_CROSSHAIR ); #endif // NEO @@ -531,7 +523,22 @@ void CHudCrosshair::Paint( void ) m_bRefreshCrosshair = false; } } - const int iXHairStyle = pCrosshairInfo->iStyle; + + bool bHideCrosshair = (NEORules() && NEORules()->GetHiddenHudElements() & NEO_HUD_ELEMENT_CROSSHAIR); + + ENeoCrosshairWep eNeoXHairWep = CROSSHAIR_WEP_DEFAULT; + if (pWeapon) + { + int iNeoXHairWep = MAP_WEAPON_TYPE_TO_XHAIR[NEO_WEAPON_TYPE[pWeapon->WeaponIndex()]]; + if (iNeoXHairWep >= CROSSHAIR_WEP_DEFAULT && false == pNeoPlayer->m_bInAim) + { + iNeoXHairWep += CROSSHAIR_WEP_DEFAULT_HIPFIRE; + } + eNeoXHairWep = static_cast( + UseCrosshairIndexFor(pCrosshairInfo, iNeoXHairWep, &bHideCrosshair)); + } + CrosshairWepInfo *crh = &pCrosshairInfo->wep[eNeoXHairWep]; + const int iTexXHId = m_iTexXHId[clamp(crh->iStyle, 0, CROSSHAIR_STYLE__TOTAL - 1)]; bool showFriendlyFireCrosshair = false; if (NEORules()->GetGameType() != NEO_GAME_TYPE_DM && cl_neo_crosshair_friendly_fire_warning.GetBool()) @@ -604,19 +611,19 @@ void CHudCrosshair::Paint( void ) vgui::surface()->DrawSetColor(COLOR_RED); vgui::surface()->DrawTexturedRect(iX - iTexWide, iY - iTexTall, iX + iTexWide, iY + iTexTall); } - else if (m_iTexXHId[iXHairStyle] > 0) + else if (iTexXHId > 0) { - vgui::surface()->DrawSetTexture(m_iTexXHId[iXHairStyle]); + vgui::surface()->DrawSetTexture(iTexXHId); int iTexWide, iTexTall; - vgui::surface()->DrawGetTextureSize(m_iTexXHId[iXHairStyle], iTexWide, iTexTall); + vgui::surface()->DrawGetTextureSize(iTexXHId, iTexWide, iTexTall); iTexWide >>= 1; iTexTall >>= 1; - vgui::surface()->DrawSetColor(pCrosshairInfo->color); + vgui::surface()->DrawSetColor(crh->color); vgui::surface()->DrawTexturedRect(iX - iTexWide, iY - iTexTall, iX + iTexWide, iY + iTexTall); } - else + else if (!bHideCrosshair) { - PaintCrosshair(*pCrosshairInfo, HalfInaccuracyConeInScreenPixels(pWeapon, m_iHalfScreenWidth), iX, iY); + PaintCrosshair(crh, HalfInaccuracyConeInScreenPixels(pWeapon, m_iHalfScreenWidth), iX, iY); } if (bIsScopedWep && pPlayer->m_bInAim) diff --git a/src/game/client/neo/ui/neo_root_settings.cpp b/src/game/client/neo/ui/neo_root_settings.cpp index 44a6eb96e..60b68b01d 100644 --- a/src/game/client/neo/ui/neo_root_settings.cpp +++ b/src/game/client/neo/ui/neo_root_settings.cpp @@ -30,6 +30,11 @@ extern int g_iRowsInScreen; extern bool g_bOBSDetected; extern ConVar cl_neo_streamermode; +const wchar_t *RINGBOX_BOOL_LABELS_REVERSE[] = { + L"Enabled", + L"Disabled", +}; + const wchar_t *QUALITY_LABELS[] = { L"Low", L"Medium", @@ -116,6 +121,18 @@ static const wchar_t* AUTOMATIC_LEAN_LABELS[] = { L"Always", }; +static const wchar_t *CROSSHAIR_HIPFIRE_LABELS_DEFAULT[HIPFIREOPT__TOTAL] = { + L"Disabled", + L"Use default", + L"Enabled", +}; + +static const wchar_t *CROSSHAIR_HIPFIRE_LABELS[HIPFIREOPT__TOTAL] = { + L"Disabled", + L"Use hipfire default", + L"Enabled", +}; + static inline CUtlVector g_vecConVarRefPtrs; ConVarRefEx::ConVarRefEx(const char *pName, const bool bExcludeGlobalPtrs) @@ -614,16 +631,15 @@ void NeoSettingsRestore(NeoSettings *ns, const NeoSettings::Keys::Flags flagsKey } { NeoSettings::Crosshair *pCrosshair = &ns->crosshair; - const bool bImported = ImportCrosshair(&pCrosshair->info, cvr->cl_neo_crosshair.GetString()); + const bool bImported = ImportCrosshair(&pCrosshair->info, cvr->cl_neo_crosshair.GetString(), &pCrosshair->aeHipfireOpts); if (!bImported) { - ResetCrosshairToDefault(&pCrosshair->info); + ResetCrosshairToDefault(&pCrosshair->info, &pCrosshair->aeHipfireOpts); } pCrosshair->bPreviewDynamicAccuracy = false; pCrosshair->eClipboardInfo = XHAIREXPORTNOTIFY_NONE; pCrosshair->bNetworkCrosshair = cvr->cl_neo_crosshair_network.GetBool(); pCrosshair->bInaccuracyInScope = cvr->cl_neo_crosshair_scope_inaccuracy.GetBool(); - pCrosshair->bHipFireCrosshair = cvr->cl_neo_crosshair_hip_fire.GetBool(); pCrosshair->bFriendlyFireWarning = cvr->cl_neo_crosshair_friendly_fire_warning.GetBool(); } { @@ -878,7 +894,6 @@ void NeoSettingsSave(const NeoSettings *ns) cvr->cl_neo_crosshair.SetValue(szSequence); cvr->cl_neo_crosshair_network.SetValue(pCrosshair->bNetworkCrosshair); cvr->cl_neo_crosshair_scope_inaccuracy.SetValue(pCrosshair->bInaccuracyInScope); - cvr->cl_neo_crosshair_hip_fire.SetValue(pCrosshair->bHipFireCrosshair); cvr->cl_neo_crosshair_friendly_fire_warning.SetValue(pCrosshair->bFriendlyFireWarning); } { @@ -1249,32 +1264,43 @@ void NeoSettings_Video(NeoSettings *ns) void NeoSettings_Crosshair(NeoSettings *ns) { static constexpr int IVIEW_ROWS = 5; + static constexpr int IMISC_ROWS = 4; NeoSettings::Crosshair *pCrosshair = &ns->crosshair; g_uiCtx.dPanel.y += g_uiCtx.dPanel.tall; g_uiCtx.dPanel.tall = g_uiCtx.layout.iRowTall * IVIEW_ROWS; - const bool bTextured = CROSSHAIR_FILES[pCrosshair->info.iStyle][0]; + const int iXHairWep = static_cast(pCrosshair->eXHairWep); + bool bIsHipfireHide = false; + const int iUseXHairIdx = UseCrosshairIndexFor(&pCrosshair->info, iXHairWep, &bIsHipfireHide); + const bool bIsInUse = (iXHairWep == iUseXHairIdx); + + CrosshairWepInfo *crh = &pCrosshair->info.wep[iUseXHairIdx]; + + const bool bTextured = CROSSHAIR_FILES[crh->iStyle][0]; NeoUI::BeginSection(NeoUI::SECTIONFLAG_EXCLUDECONTROLLER); { NeoUI::Divider(L"PREVIEW"); - if (bTextured) - { - NeoSettings::Crosshair::Texture *pTex = &ns->crosshair.arTextures[pCrosshair->info.iStyle]; - vgui::surface()->DrawSetTexture(pTex->iTexId); - vgui::surface()->DrawSetColor(pCrosshair->info.color); - vgui::surface()->DrawTexturedRect( - g_uiCtx.dPanel.x + g_uiCtx.iLayoutX - (pTex->iWide / 2) + (g_uiCtx.dPanel.wide / 2), - g_uiCtx.dPanel.y + g_uiCtx.iLayoutY, - g_uiCtx.dPanel.x + g_uiCtx.iLayoutX + (pTex->iWide / 2) + (g_uiCtx.dPanel.wide / 2), - g_uiCtx.dPanel.y + g_uiCtx.iLayoutY + pTex->iTall); - } - else + if (!bIsHipfireHide) { - const int iPreviewDynamicAccuracy = (pCrosshair->bPreviewDynamicAccuracy) ? (Max(0, (int)(sin(gpGlobals->curtime) * 24) + 16)) : 0; - PaintCrosshair(pCrosshair->info, iPreviewDynamicAccuracy, - g_uiCtx.dPanel.x + g_uiCtx.iLayoutX + (g_uiCtx.dPanel.wide / 2), - g_uiCtx.dPanel.y + g_uiCtx.iLayoutY + (g_uiCtx.dPanel.tall / 2)); + if (bTextured) + { + NeoSettings::Crosshair::Texture *pTex = &ns->crosshair.arTextures[crh->iStyle]; + vgui::surface()->DrawSetTexture(pTex->iTexId); + vgui::surface()->DrawSetColor(crh->color); + vgui::surface()->DrawTexturedRect( + g_uiCtx.dPanel.x + g_uiCtx.iLayoutX - (pTex->iWide / 2) + (g_uiCtx.dPanel.wide / 2), + g_uiCtx.dPanel.y + g_uiCtx.iLayoutY, + g_uiCtx.dPanel.x + g_uiCtx.iLayoutX + (pTex->iWide / 2) + (g_uiCtx.dPanel.wide / 2), + g_uiCtx.dPanel.y + g_uiCtx.iLayoutY + pTex->iTall); + } + else + { + const int iPreviewDynamicAccuracy = (pCrosshair->bPreviewDynamicAccuracy) ? (Max(0, (int)(sin(gpGlobals->curtime) * 24) + 16)) : 0; + PaintCrosshair(crh, iPreviewDynamicAccuracy, + g_uiCtx.dPanel.x + g_uiCtx.iLayoutX + (g_uiCtx.dPanel.wide / 2), + g_uiCtx.dPanel.y + g_uiCtx.iLayoutY + (g_uiCtx.dPanel.tall / 2)); + } } vgui::surface()->DrawSetColor(g_uiCtx.colors.normalBg); @@ -1322,7 +1348,7 @@ void NeoSettings_Crosshair(NeoSettings *ns) #endif if (iClipboardBytes > 0) { - bImported = ImportCrosshair(&pCrosshair->info, szClipboardCrosshair); + bImported = ImportCrosshair(&pCrosshair->info, szClipboardCrosshair, &pCrosshair->aeHipfireOpts); if (bImported) { ns->bModified = true; @@ -1337,7 +1363,7 @@ void NeoSettings_Crosshair(NeoSettings *ns) if (bDefaultPressed) { - ResetCrosshairToDefault(&pCrosshair->info); + ResetCrosshairToDefault(&pCrosshair->info, &pCrosshair->aeHipfireOpts); pCrosshair->eClipboardInfo = XHAIREXPORTNOTIFY_RESET_TO_DEFAULT; ns->bModified = true; } @@ -1358,70 +1384,149 @@ void NeoSettings_Crosshair(NeoSettings *ns) NeoUI::EndSection(); g_uiCtx.dPanel.y += g_uiCtx.dPanel.tall; - g_uiCtx.dPanel.tall = g_uiCtx.layout.iRowTall * (g_iRowsInScreen - IVIEW_ROWS); + g_uiCtx.dPanel.tall = g_uiCtx.layout.iRowTall; + NeoUI::BeginSection(); + { + static const wchar_t *CROSSHAIR_WEP_LABELS[CROSSHAIR_WEP__TOTAL] = { + L"Default", + L"Secondary", + L"Shotgun", + L"Default hipfire", + L"Secondary hipfire", + L"Shotgun hipfire", + }; + NeoUI::SetPerRowLayout(1); + static_assert(sizeof(int) == sizeof(ENeoCrosshairWep)); + const ENeoCrosshairWep eCmpXHairWep = pCrosshair->eXHairWep; + NeoUI::Tabs(CROSSHAIR_WEP_LABELS, CROSSHAIR_WEP__TOTAL, (int *)(&pCrosshair->eXHairWep), + NeoUI::TABFLAG_NOSIDEKEYS | NeoUI::TABFLAG_NOSTATERESETS); + if (eCmpXHairWep != pCrosshair->eXHairWep) + { + V_memset(g_uiCtx.iYOffset, 0, sizeof(g_uiCtx.iYOffset)); + } + } + NeoUI::EndSection(); + + g_uiCtx.dPanel.y += g_uiCtx.dPanel.tall; + g_uiCtx.dPanel.tall = g_uiCtx.layout.iRowTall * (g_iRowsInScreen - IVIEW_ROWS - IMISC_ROWS - 1); NeoUI::BeginSection(NeoUI::SECTIONFLAG_DEFAULTFOCUS); { NeoUI::SetPerRowLayout(2, NeoUI::ROWLAYOUT_TWOSPLIT); - NeoUI::RingBox(L"Crosshair style", CROSSHAIR_LABELS, CROSSHAIR_STYLE__TOTAL, &pCrosshair->info.iStyle); - NeoUI::ColorEdit(L"Crosshair color", - &pCrosshair->info.color[0], - &pCrosshair->info.color[1], - &pCrosshair->info.color[2], - &pCrosshair->info.color[3]); - if (!bTextured) + if (pCrosshair->eXHairWep != CROSSHAIR_WEP_DEFAULT) { - static_assert(sizeof(int) == sizeof(NeoHudCrosshairSizeType)); - NeoUI::RingBox(L"Size type", CROSSHAIR_SIZETYPE_LABELS, CROSSHAIR_SIZETYPE__TOTAL, (int *)(&pCrosshair->info.eSizeType)); - switch (pCrosshair->info.eSizeType) + if (iXHairWep >= CROSSHAIR_WEP_DEFAULT_HIPFIRE) { - case CROSSHAIR_SIZETYPE_ABSOLUTE: NeoUI::SliderInt(L"Size", &pCrosshair->info.iSize, 0, CROSSHAIR_MAX_SIZE); break; - case CROSSHAIR_SIZETYPE_SCREEN: NeoUI::Slider(L"Size", &pCrosshair->info.flScrSize, 0.0f, 1.0f, 3, 0.01f); break; + static_assert(sizeof(int) == sizeof(EHipfireOpt)); + const int iPrevIdx = pCrosshair->aeHipfireOpts[iXHairWep]; + NeoUI::RingBox(L"Use crosshair", + (iXHairWep == CROSSHAIR_WEP_DEFAULT_HIPFIRE) + ? CROSSHAIR_HIPFIRE_LABELS_DEFAULT + : CROSSHAIR_HIPFIRE_LABELS, + HIPFIREOPT__TOTAL, + (int *)(&pCrosshair->aeHipfireOpts[iXHairWep])); + if (iPrevIdx != pCrosshair->aeHipfireOpts[iXHairWep]) + { + const NeoCrosshairWepFlags wepMask = (1 << (iXHairWep - 1)); + const NeoCrosshairHipfireCustomFlags wepHipfireMask = (1 << (iXHairWep - CROSSHAIR_WEP_DEFAULT_HIPFIRE)); + + switch (pCrosshair->aeHipfireOpts[iXHairWep]) + { + case HIPFIREOPT_DISABLED: + pCrosshair->info.wepFlags &= ~(wepMask); + pCrosshair->info.hipfireFlags &= ~(wepHipfireMask); + break; + case HIPFIREOPT_USEDEFAULT: + pCrosshair->info.wepFlags |= wepMask; + pCrosshair->info.hipfireFlags &= ~(wepHipfireMask); + break; + case HIPFIREOPT_ENABLED: + pCrosshair->info.wepFlags |= wepMask; + pCrosshair->info.hipfireFlags |= wepHipfireMask; + break; + } + } } - NeoUI::SliderInt(L"Thickness", &pCrosshair->info.iThick, 0, CROSSHAIR_MAX_THICKNESS); - NeoUI::SliderInt(L"Gap", &pCrosshair->info.iGap, 0, CROSSHAIR_MAX_GAP); - NeoUI::SliderInt(L"Outline", &pCrosshair->info.iOutline, 0, CROSSHAIR_MAX_OUTLINE); - if (pCrosshair->info.iOutline > 0) + else { - NeoUI::ColorEdit(L"Outline color", - &pCrosshair->info.colorOutline[0], - &pCrosshair->info.colorOutline[1], - &pCrosshair->info.colorOutline[2], - &pCrosshair->info.colorOutline[3]); + static const wchar_t *RINGBOX_FLAG_LABELS_USEXHAIR[] = { + L"Use default", + L"Enabled", + }; + NeoUI::RingBoxFlag(L"Use crosshair", (1 << (pCrosshair->eXHairWep - 1)), + &pCrosshair->info.wepFlags, RINGBOX_FLAG_LABELS_USEXHAIR); } - NeoUI::SliderInt(L"Center dot", &pCrosshair->info.iCenterDot, 0, CROSSHAIR_MAX_CENTER_DOT); - if (pCrosshair->info.iCenterDot > 0) + } + + if (bIsInUse) + { + NeoUI::RingBox(L"Crosshair style", CROSSHAIR_LABELS, CROSSHAIR_STYLE__TOTAL, &crh->iStyle); + NeoUI::ColorEdit(L"Crosshair color", + &crh->color[0], + &crh->color[1], + &crh->color[2], + &crh->color[3]); + if (!bTextured) { - NeoUI::RingBoxBool(L"Separate dot color", &pCrosshair->info.bSeparateColorDot); - if (pCrosshair->info.bSeparateColorDot) + static_assert(sizeof(int) == sizeof(NeoHudCrosshairSizeType)); + NeoUI::RingBox(L"Size type", CROSSHAIR_SIZETYPE_LABELS, CROSSHAIR_SIZETYPE__TOTAL, (int *)(&crh->eSizeType)); + switch (crh->eSizeType) + { + case CROSSHAIR_SIZETYPE_ABSOLUTE: NeoUI::SliderInt(L"Size", &crh->iSize, 0, CROSSHAIR_MAX_SIZE); break; + case CROSSHAIR_SIZETYPE_SCREEN: NeoUI::Slider(L"Size", &crh->flScrSize, 0.0f, 1.0f, 3, 0.01f); break; + } + NeoUI::SliderInt(L"Thickness", &crh->iThick, 0, CROSSHAIR_MAX_THICKNESS); + NeoUI::SliderInt(L"Gap", &crh->iGap, 0, CROSSHAIR_MAX_GAP); + NeoUI::SliderInt(L"Outline", &crh->iOutline, 0, CROSSHAIR_MAX_OUTLINE); + if (crh->iOutline > 0) + { + NeoUI::ColorEdit(L"Outline color", + &crh->colorOutline[0], + &crh->colorOutline[1], + &crh->colorOutline[2], + &crh->colorOutline[3]); + } + NeoUI::SliderInt(L"Center dot", &crh->iCenterDot, 0, CROSSHAIR_MAX_CENTER_DOT); + if (crh->iCenterDot > 0) { - NeoUI::ColorEdit(L"Dot color", - &pCrosshair->info.colorDot[0], - &pCrosshair->info.colorDot[1], - &pCrosshair->info.colorDot[2], - &pCrosshair->info.colorDot[3]); - if (pCrosshair->info.iOutline > 0) + NeoUI::RingBoxFlag(L"Separate dot color", CROSSHAIR_FLAG_SEPERATEDOTCOLOR, &crh->flags); + if (crh->flags & CROSSHAIR_FLAG_SEPERATEDOTCOLOR) { - NeoUI::ColorEdit(L"Dot outline", - &pCrosshair->info.colorDotOutline[0], - &pCrosshair->info.colorDotOutline[1], - &pCrosshair->info.colorDotOutline[2], - &pCrosshair->info.colorDotOutline[3]); + NeoUI::ColorEdit(L"Dot color", + &crh->colorDot[0], + &crh->colorDot[1], + &crh->colorDot[2], + &crh->colorDot[3]); + if (crh->iOutline > 0) + { + NeoUI::ColorEdit(L"Dot outline", + &crh->colorDotOutline[0], + &crh->colorDotOutline[1], + &crh->colorDotOutline[2], + &crh->colorDotOutline[3]); + } } } + NeoUI::RingBoxFlag(L"Draw top line", CROSSHAIR_FLAG_NOTOPLINE, &crh->flags, RINGBOX_BOOL_LABELS_REVERSE); + NeoUI::SliderInt(L"Circle radius", &crh->iCircleRad, 0, CROSSHAIR_MAX_CIRCLE_RAD); + if (crh->iCircleRad > 0) + { + NeoUI::SliderInt(L"Circle segments", &crh->iCircleSegments, 0, CROSSHAIR_MAX_CIRCLE_SEGMENTS); + } + static_assert(sizeof(int) == sizeof(NeoHudCrosshairDynamicType)); + NeoUI::RingBox(L"Dynamic type", CROSSHAIR_DYNAMICTYPE_LABELS, CROSSHAIR_DYNAMICTYPE__TOTAL, (int *)(&crh->eDynamicType)); } - NeoUI::RingBoxBool(L"Draw top line", &pCrosshair->info.bTopLine); - NeoUI::SliderInt(L"Circle radius", &pCrosshair->info.iCircleRad, 0, CROSSHAIR_MAX_CIRCLE_RAD); - if (pCrosshair->info.iCircleRad > 0) - { - NeoUI::SliderInt(L"Circle segments", &pCrosshair->info.iCircleSegments, 0, CROSSHAIR_MAX_CIRCLE_SEGMENTS); - } - static_assert(sizeof(int) == sizeof(NeoHudCrosshairDynamicType)); - NeoUI::RingBox(L"Dynamic type", CROSSHAIR_DYNAMICTYPE_LABELS, CROSSHAIR_DYNAMICTYPE__TOTAL, (int *)(&pCrosshair->info.eDynamicType)); } + } + NeoUI::EndSection(); + + g_uiCtx.dPanel.y += g_uiCtx.dPanel.tall; + g_uiCtx.dPanel.tall = g_uiCtx.layout.iRowTall * IMISC_ROWS; + NeoUI::BeginSection(); + { + NeoUI::SetPerRowLayout(2, NeoUI::ROWLAYOUT_TWOSPLIT); NeoUI::Divider(L"MISCELLANEOUS"); NeoUI::RingBoxBool(L"Show other players' crosshairs", &pCrosshair->bNetworkCrosshair); NeoUI::RingBoxBool(L"Inaccuracy in scope", &pCrosshair->bInaccuracyInScope); - NeoUI::RingBoxBool(L"Hip fire crosshair", &pCrosshair->bHipFireCrosshair); NeoUI::RingBoxBool(L"Friendly Fire warning", &pCrosshair->bFriendlyFireWarning); } NeoUI::EndSection(); diff --git a/src/game/client/neo/ui/neo_root_settings.h b/src/game/client/neo/ui/neo_root_settings.h index ad2f62e71..3d134d3e3 100644 --- a/src/game/client/neo/ui/neo_root_settings.h +++ b/src/game/client/neo/ui/neo_root_settings.h @@ -169,11 +169,13 @@ struct NeoSettings struct Crosshair { + ENeoCrosshairWep eXHairWep; + EHipfireOpt aeHipfireOpts[CROSSHAIR_WEP__TOTAL]; + CrosshairInfo info; XHairExportNotify eClipboardInfo; bool bNetworkCrosshair; bool bInaccuracyInScope; - bool bHipFireCrosshair; bool bFriendlyFireWarning; bool bPreviewDynamicAccuracy; @@ -324,7 +326,6 @@ struct NeoSettings CONVARREF_DEFNOGLOBALPTR(cl_neo_crosshair); CONVARREF_DEF(cl_neo_crosshair_network); CONVARREF_DEF(cl_neo_crosshair_scope_inaccuracy); - CONVARREF_DEF(cl_neo_crosshair_hip_fire); CONVARREF_DEF(cl_neo_crosshair_friendly_fire_warning); // Friendly Markers diff --git a/src/game/client/neo/ui/neo_ui.cpp b/src/game/client/neo/ui/neo_ui.cpp index 28607a409..18165aed0 100644 --- a/src/game/client/neo/ui/neo_ui.cpp +++ b/src/game/client/neo/ui/neo_ui.cpp @@ -1467,18 +1467,44 @@ void ResetTextures() pHtTexMap->Purge(); } -void RingBoxBool(bool *bChecked) +void RingBoxFlag(const int iToggleFlag, int *iFlags, const wchar_t **wszLabelsCustomList) +{ + const bool bWasOnFlag = (*iFlags & iToggleFlag); + bool bNowOnFlag = bWasOnFlag; + RingBoxBool(&bNowOnFlag, wszLabelsCustomList); + if (bNowOnFlag != bWasOnFlag) + { + if (bNowOnFlag) + { + *iFlags |= iToggleFlag; + } + else + { + *iFlags &= ~(iToggleFlag); + } + } +} + +void RingBoxFlag(const wchar_t *wszLeftLabel, const int iToggleFlag, int *iFlags, const wchar_t **wszLabelsCustomList) +{ + BeginMultiWidgetHighlighter(2); + Label(wszLeftLabel); + RingBoxFlag(iToggleFlag, iFlags, wszLabelsCustomList); + EndMultiWidgetHighlighter(); +} + +void RingBoxBool(bool *bChecked, const wchar_t **wszLabelsCustomList) { int iIndex = static_cast(*bChecked); - RingBox(ENABLED_LABELS, 2, &iIndex); + RingBox((wszLabelsCustomList) ? wszLabelsCustomList : ENABLED_LABELS, 2, &iIndex); *bChecked = static_cast(iIndex); } -void RingBoxBool(const wchar_t *wszLeftLabel, bool *bChecked) +void RingBoxBool(const wchar_t *wszLeftLabel, bool *bChecked, const wchar_t **wszLabelsCustomList) { BeginMultiWidgetHighlighter(2); Label(wszLeftLabel); - RingBoxBool(bChecked); + RingBoxBool(bChecked, wszLabelsCustomList); EndMultiWidgetHighlighter(); } @@ -1591,168 +1617,171 @@ void Tabs(const wchar_t **wszLabelsList, const int iLabelsSize, int *iIndex, // Sanity check if iLabelsSize dynamically changes *iIndex = clamp(*iIndex, 0, iLabelsSize - 1); - SwapFont(FONT_NTNORMAL); - - int iTabWide = 0; - if (piTabWide) + if (wdgState.bInView) { - if (*piTabWide <= 0) + SwapFont(FONT_NTNORMAL); + + int iTabWide = 0; + if (piTabWide) { - for (int i = 0; i < iLabelsSize; ++i) + if (*piTabWide <= 0) { - int iCurTabWide, iTabTall; - vgui::surface()->GetTextSize(c->fonts[c->eFont].hdl, wszLabelsList[i], iCurTabWide, iTabTall); - iTabWide = Max(iCurTabWide, iTabWide); + for (int i = 0; i < iLabelsSize; ++i) + { + int iCurTabWide, iTabTall; + vgui::surface()->GetTextSize(c->fonts[c->eFont].hdl, wszLabelsList[i], iCurTabWide, iTabTall); + iTabWide = Max(iCurTabWide, iTabWide); + } + iTabWide += 2 * c->iMarginX; + *piTabWide = iTabWide; + Assert(iTabWide > 0); } - iTabWide += 2 * c->iMarginX; - *piTabWide = iTabWide; - Assert(iTabWide > 0); + iTabWide = *piTabWide; } - iTabWide = *piTabWide; - } - else - { - iTabWide = (c->dPanel.wide / iLabelsSize); - } - - const int iTotalTabsWidth = (iTabWide * iLabelsSize); - const int iCenterTabsXOffset = iTotalTabsWidth < c->irWidgetWide ? (c->irWidgetWide - iTotalTabsWidth) * 0.5 : 0; - bool bResetSectionStates = false; - - const int iMWheelJump = iTabWide * 0.2; - switch (c->eMode) - { - case MODE_PAINT: - { - vgui::surface()->SetFullscreenViewport(c->rWidgetArea.x0, c->rWidgetArea.y0, c->irWidgetWide, c->irWidgetTall); - vgui::surface()->PushFullscreenViewport(); + else + { + iTabWide = (c->dPanel.wide / iLabelsSize); + } + + const int iTotalTabsWidth = (iTabWide * iLabelsSize); + const int iCenterTabsXOffset = iTotalTabsWidth < c->irWidgetWide ? (c->irWidgetWide - iTotalTabsWidth) * 0.5 : 0; + bool bResetSectionStates = false; + + const int iMWheelJump = iTabWide * 0.2; + switch (c->eMode) + { + case MODE_PAINT: + { + vgui::surface()->SetFullscreenViewport(c->rWidgetArea.x0, c->rWidgetArea.y0, c->irWidgetWide, c->irWidgetTall); + vgui::surface()->PushFullscreenViewport(); - const auto *pFontI = &c->fonts[c->eFont]; - for (int i = 0, iXPosTab = 0; i < iLabelsSize; ++i, iXPosTab += iTabWide) - { - vgui::IntRect tabRect = { - .x0 = c->iXOffset[c->iSection] + iCenterTabsXOffset + iXPosTab, - .y0 = 0, - .x1 = c->iXOffset[c->iSection] + iCenterTabsXOffset + iXPosTab + iTabWide, - .y1 = c->irWidgetTall, - }; - const bool bHotTab = IN_BETWEEN_EQ(tabRect.x0, c->iMouseAbsX - c->rWidgetArea.x0, tabRect.x1) && - IN_BETWEEN_EQ(tabRect.y0, c->iMouseAbsY - c->rWidgetArea.y0, tabRect.y1); - const bool bActiveTab = i == *iIndex; - if (bHotTab || bActiveTab) + const auto *pFontI = &c->fonts[c->eFont]; + for (int i = 0, iXPosTab = 0; i < iLabelsSize; ++i, iXPosTab += iTabWide) { - vgui::surface()->DrawSetColor(bActiveTab ? c->colors.activeBg : c->colors.hotBg); - vgui::surface()->DrawFilledRectArray(&tabRect, 1); + vgui::IntRect tabRect = { + .x0 = c->iXOffset[c->iSection] + iCenterTabsXOffset + iXPosTab, + .y0 = 0, + .x1 = c->iXOffset[c->iSection] + iCenterTabsXOffset + iXPosTab + iTabWide, + .y1 = c->irWidgetTall, + }; + const bool bHotTab = IN_BETWEEN_EQ(tabRect.x0, c->iMouseAbsX - c->rWidgetArea.x0, tabRect.x1) && + IN_BETWEEN_EQ(tabRect.y0, c->iMouseAbsY - c->rWidgetArea.y0, tabRect.y1); + const bool bActiveTab = i == *iIndex; + if (bHotTab || bActiveTab) + { + vgui::surface()->DrawSetColor(bActiveTab ? c->colors.activeBg : c->colors.hotBg); + vgui::surface()->DrawFilledRectArray(&tabRect, 1); + } + const wchar_t *wszText = wszLabelsList[i]; + int wide, tall; + vgui::surface()->GetTextSize(c->fonts[c->eFont].hdl, wszText, wide, tall); + vgui::surface()->DrawSetTextPos(c->iXOffset[c->iSection] + iCenterTabsXOffset + iXPosTab + ((iTabWide - wide) * 0.5), pFontI->iYOffset); + vgui::surface()->DrawSetTextColor(bActiveTab ? c->colors.activeFg : bHotTab ? c->colors.hotFg : c->colors.normalFg); + vgui::surface()->DrawPrintText(wszText, V_wcslen(wszText)); + if (bHotTab) + { + const int iHotTabMargin = static_cast(FL_BORDER_RATIO * c->iMarginY); + vgui::surface()->DrawSetColor(c->colors.hotBorder); + DrawBorder(tabRect, iHotTabMargin); + } } - const wchar_t *wszText = wszLabelsList[i]; - int wide, tall; - vgui::surface()->GetTextSize(c->fonts[c->eFont].hdl, wszText, wide, tall); - vgui::surface()->DrawSetTextPos(c->iXOffset[c->iSection] + iCenterTabsXOffset + iXPosTab + ((iTabWide - wide) * 0.5), pFontI->iYOffset); - vgui::surface()->DrawSetTextColor(bActiveTab ? c->colors.activeFg : bHotTab ? c->colors.hotFg : c->colors.normalFg); - vgui::surface()->DrawPrintText(wszText, V_wcslen(wszText)); - if (bHotTab) + if (c->iXOffset[c->iSection] != 0) { - const int iHotTabMargin = static_cast(FL_BORDER_RATIO * c->iMarginY); - vgui::surface()->DrawSetColor(c->colors.hotBorder); - DrawBorder(tabRect, iHotTabMargin); + // more tab content to the left + vgui::surface()->DrawSetColor(COLOR_WHITE); + vgui::surface()->DrawFilledRect(0, 0, 2, c->irWidgetTall); + } + const int iTabsWiderBy = c->dPanel.wide - iTotalTabsWidth; + if (iTabsWiderBy < 0 && c->iXOffset[c->iSection] != iTabsWiderBy) + { + // more tab content to the right + vgui::surface()->DrawSetColor(COLOR_WHITE); + vgui::surface()->DrawFilledRect(c->irWidgetWide, 0, c->irWidgetWide-2, c->irWidgetTall); } - } - if (c->iXOffset[c->iSection] != 0) - { - // more tab content to the left - vgui::surface()->DrawSetColor(COLOR_WHITE); - vgui::surface()->DrawFilledRect(0, 0, 2, c->irWidgetTall); - } - const int iTabsWiderBy = c->dPanel.wide - iTotalTabsWidth; - if (iTabsWiderBy < 0 && c->iXOffset[c->iSection] != iTabsWiderBy) - { - // more tab content to the right - vgui::surface()->DrawSetColor(COLOR_WHITE); - vgui::surface()->DrawFilledRect(c->irWidgetWide, 0, c->irWidgetWide-2, c->irWidgetTall); - } - - vgui::surface()->PopFullscreenViewport(); - vgui::surface()->SetFullscreenViewport(0, 0, 0, 0); - if (!(flags & TABFLAG_NOSIDEKEYS)) - { - // Draw the side-hints text - // NEO NOTE (nullsystem): F# as 1 is thinner than 3/not monospaced font - int iFontWidth, iFontHeight; - vgui::surface()->GetTextSize(c->fonts[c->eFont].hdl, L"F #", iFontWidth, iFontHeight); - const int iHintYPos = c->rWidgetArea.y0 + pFontI->iYOffset; + vgui::surface()->PopFullscreenViewport(); + vgui::surface()->SetFullscreenViewport(0, 0, 0, 0); - vgui::surface()->DrawSetTextColor(c->colors.tabHintsFg); - vgui::surface()->DrawSetTextPos(c->dPanel.x - c->iMarginX - iFontWidth, iHintYPos); - vgui::surface()->DrawPrintText((c->eKeyHints == KEYHINTS_KEYBOARD) ? L"F 1" : L"L 1", 3); - vgui::surface()->DrawSetTextPos(c->dPanel.x + c->dPanel.wide + c->iMarginX, iHintYPos); - vgui::surface()->DrawPrintText((c->eKeyHints == KEYHINTS_KEYBOARD) ? L"F 3" : L"R 1", 3); - } - } - break; - case MODE_MOUSEPRESSED: - { - if (wdgState.bHot && c->eCode == MOUSE_LEFT && c->iMouseAbsX >= (c->rWidgetArea.x0 + iCenterTabsXOffset) && c->iMouseAbsX <= c->rWidgetArea.x0 + iCenterTabsXOffset + (iTabWide * iLabelsSize)) - { - const int iNextIndex = ((-c->iXOffset[c->iSection] + c->iMouseAbsX - c->rWidgetArea.x0 - iCenterTabsXOffset) / iTabWide); - if (iNextIndex != *iIndex) + if (!(flags & TABFLAG_NOSIDEKEYS)) { - *iIndex = clamp(iNextIndex, 0, iLabelsSize - 1); - bResetSectionStates = true; + // Draw the side-hints text + // NEO NOTE (nullsystem): F# as 1 is thinner than 3/not monospaced font + int iFontWidth, iFontHeight; + vgui::surface()->GetTextSize(c->fonts[c->eFont].hdl, L"F #", iFontWidth, iFontHeight); + const int iHintYPos = c->rWidgetArea.y0 + pFontI->iYOffset; + + vgui::surface()->DrawSetTextColor(c->colors.tabHintsFg); + vgui::surface()->DrawSetTextPos(c->dPanel.x - c->iMarginX - iFontWidth, iHintYPos); + vgui::surface()->DrawPrintText((c->eKeyHints == KEYHINTS_KEYBOARD) ? L"F 1" : L"L 1", 3); + vgui::surface()->DrawSetTextPos(c->dPanel.x + c->dPanel.wide + c->iMarginX, iHintYPos); + vgui::surface()->DrawPrintText((c->eKeyHints == KEYHINTS_KEYBOARD) ? L"F 3" : L"R 1", 3); } } - } - break; - case MODE_KEYPRESSED: - { - if (!(flags & TABFLAG_NOSIDEKEYS)) + break; + case MODE_MOUSEPRESSED: { - const bool bLeftKey = c->eCode == KEY_F1 || c->eCode == KEY_XBUTTON_LEFT_SHOULDER || - c->eCode == STEAMCONTROLLER_LEFT_TRIGGER; - const bool bRightKey = c->eCode == KEY_F3 || c->eCode == KEY_XBUTTON_RIGHT_SHOULDER || - c->eCode == STEAMCONTROLLER_RIGHT_TRIGGER; - if (bLeftKey || bRightKey) // Global keybind + if (wdgState.bHot && c->eCode == MOUSE_LEFT && c->iMouseAbsX >= (c->rWidgetArea.x0 + iCenterTabsXOffset) && c->iMouseAbsX <= c->rWidgetArea.x0 + iCenterTabsXOffset + (iTabWide * iLabelsSize)) { - *iIndex += (bLeftKey) ? -1 : +1; - *iIndex = LoopAroundInArray(*iIndex, iLabelsSize); - const int distanceToStartOfButton = *iIndex * iTabWide; - if (-c->iXOffset[c->iSection] > distanceToStartOfButton) + const int iNextIndex = ((-c->iXOffset[c->iSection] + c->iMouseAbsX - c->rWidgetArea.x0 - iCenterTabsXOffset) / iTabWide); + if (iNextIndex != *iIndex) { - c->iXOffset[c->iSection] = -distanceToStartOfButton; + *iIndex = clamp(iNextIndex, 0, iLabelsSize - 1); + bResetSectionStates = true; } - const int distanceToEndOfButton = (*iIndex + 1) * iTabWide; - if (-c->iXOffset[c->iSection] + c->irWidgetWide < distanceToEndOfButton) + } + } + break; + case MODE_KEYPRESSED: + { + if (!(flags & TABFLAG_NOSIDEKEYS)) + { + const bool bLeftKey = c->eCode == KEY_F1 || c->eCode == KEY_XBUTTON_LEFT_SHOULDER || + c->eCode == STEAMCONTROLLER_LEFT_TRIGGER; + const bool bRightKey = c->eCode == KEY_F3 || c->eCode == KEY_XBUTTON_RIGHT_SHOULDER || + c->eCode == STEAMCONTROLLER_RIGHT_TRIGGER; + if (bLeftKey || bRightKey) // Global keybind { - c->iXOffset[c->iSection] = c->irWidgetWide - distanceToEndOfButton; + *iIndex += (bLeftKey) ? -1 : +1; + *iIndex = LoopAroundInArray(*iIndex, iLabelsSize); + const int distanceToStartOfButton = *iIndex * iTabWide; + if (-c->iXOffset[c->iSection] > distanceToStartOfButton) + { + c->iXOffset[c->iSection] = -distanceToStartOfButton; + } + const int distanceToEndOfButton = (*iIndex + 1) * iTabWide; + if (-c->iXOffset[c->iSection] + c->irWidgetWide < distanceToEndOfButton) + { + c->iXOffset[c->iSection] = c->irWidgetWide - distanceToEndOfButton; + } + bResetSectionStates = true; } - bResetSectionStates = true; } } - } - break; - case MODE_MOUSEWHEELED: - if (!(c->bMouseInPanel)) - { + break; + case MODE_MOUSEWHEELED: + if (!(c->bMouseInPanel)) + { + break; + } + c->iXOffset[c->iSection] += (c->eCode == MOUSE_WHEEL_UP) ? iMWheelJump : -iMWheelJump; // NEO TODO (Adam) customizable horizontal scroll direction? + c->iXOffset[c->iSection] = clamp(c->iXOffset[c->iSection], -(iTotalTabsWidth - c->dPanel.wide), 0); + break; + default: break; } - c->iXOffset[c->iSection] += (c->eCode == MOUSE_WHEEL_UP) ? iMWheelJump : -iMWheelJump; // NEO TODO (Adam) customizable horizontal scroll direction? - c->iXOffset[c->iSection] = clamp(c->iXOffset[c->iSection], -(iTotalTabsWidth - c->dPanel.wide), 0); - break; - default: - break; - } - if (bResetSectionStates && !(flags & TABFLAG_NOSTATERESETS)) - { - V_memset(c->iYOffset, 0, sizeof(c->iYOffset)); - c->iActive = FOCUSOFF_NUM; - c->iActiveSection = -1; - c->iHot = FOCUSOFF_NUM; - c->iHotSection = -1; + if (bResetSectionStates && !(flags & TABFLAG_NOSTATERESETS)) + { + V_memset(c->iYOffset, 0, sizeof(c->iYOffset)); + c->iActive = FOCUSOFF_NUM; + c->iActiveSection = -1; + c->iHot = FOCUSOFF_NUM; + c->iHotSection = -1; + } + SwapFont(FONT_NTNORMAL); } EndWidget(wdgState); - SwapFont(FONT_NTNORMAL); } static float ClampAndLimitDp(const float curValue, const float flMin, const float flMax, const int iDp) diff --git a/src/game/client/neo/ui/neo_ui.h b/src/game/client/neo/ui/neo_ui.h index b7f3d00d0..e7c4874d3 100644 --- a/src/game/client/neo/ui/neo_ui.h +++ b/src/game/client/neo/ui/neo_ui.h @@ -469,8 +469,11 @@ typedef int TabsFlags; /*1W*/ RetButton Button(const wchar_t *wszText); /*2W*/ RetButton Button(const wchar_t *wszLeftLabel, const wchar_t *wszText); /*1W*/ RetButton ButtonTexture(const char *szTexturePath); -/*1W*/ void RingBoxBool(bool *bChecked); -/*2W*/ void RingBoxBool(const wchar_t *wszLeftLabel, bool *bChecked); + +/*1W*/ void RingBoxFlag(const int iToggleFlag, int *iFlags, const wchar_t **wszLabelsCustomList = nullptr); +/*2W*/ void RingBoxFlag(const wchar_t *wszLeftLabel, const int iToggleFlag, int *iFlags, const wchar_t **wszLabelsCustomList = nullptr); +/*1W*/ void RingBoxBool(bool *bChecked, const wchar_t **wszLabelsCustomList = nullptr); +/*2W*/ void RingBoxBool(const wchar_t *wszLeftLabel, bool *bChecked, const wchar_t **wszLabelsCustomList = nullptr); /*1W*/ void RingBox(const wchar_t **wszLabelsList, const int iLabelsSize, int *iIndex); /*2W*/ void RingBox(const wchar_t *wszLeftLabel, const wchar_t **wszLabelsList, const int iLabelsSize, int *iIndex); /*1W*/ void Progress(const float flValue, const float flMin, const float flMax); diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index 16dfcf289..426157588 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -1376,6 +1376,8 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_enums.h ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_crosshair.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_crosshair.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.h neo/neo_bloom_controller.cpp neo/neo_client.cpp neo/neo_detpack.cpp @@ -1519,6 +1521,7 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_aa13.h ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_balc.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_balc.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_bits.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_bits.h ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_detpack.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_detpack.h @@ -1578,6 +1581,7 @@ target_sources_grouped( ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_zr68l.h ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_zr68s.cpp ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/weapon_zr68s.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons/neo_weapon_types.h ) if(NEO_BUILD_WEAPON_PBK56S) diff --git a/src/game/shared/neo/neo_crosshair.cpp b/src/game/shared/neo/neo_crosshair.cpp index a9009fabc..aa0fc3445 100644 --- a/src/game/shared/neo/neo_crosshair.cpp +++ b/src/game/shared/neo/neo_crosshair.cpp @@ -1,7 +1,29 @@ #include "neo_crosshair.h" +#ifdef UNIT_TEST_DLL +#include "strtools.h" +#include "tier0/dbg.h" + +extern const char *g_testFnName; +extern int g_verbose; +#else #include "cbase.h" #include "shareddefs.h" +#endif // UNIT_TEST_DLL + +#include "mathlib/mathlib.h" +#include "neo_serial.h" + +const ENeoCrosshairWep MAP_WEAPON_TYPE_TO_XHAIR[WEP_TYPE__TOTAL] = { + CROSSHAIR_WEP_NONE, // WEP_TYPE_NIL + CROSSHAIR_WEP_NONE, // WEP_TYPE_THROWABLE + CROSSHAIR_WEP_SECONDARY, // WEP_TYPE_PISTOL + CROSSHAIR_WEP_DEFAULT, // WEP_TYPE_SMG + CROSSHAIR_WEP_SHOTGUN, // WEP_TYPE_SHOTGUN + CROSSHAIR_WEP_DEFAULT, // WEP_TYPE_RIFLE + CROSSHAIR_WEP_DEFAULT, // WEP_TYPE_MACHINEGUN + CROSSHAIR_WEP_NONE, // WEP_TYPE_SNIPER +}; #ifdef CLIENT_DLL #include "neo_gamerules.h" @@ -29,23 +51,18 @@ const wchar_t **CROSSHAIR_DYNAMICTYPE_LABELS = INTERNAL_CROSSHAIR_DYNAMICTYPE_LA static const wchar_t *INTERNAL_CROSSHAIR_SIZETYPE_LABELS[CROSSHAIR_SIZETYPE__TOTAL] = { L"Absolute", L"Screen halves" }; const wchar_t **CROSSHAIR_SIZETYPE_LABELS = INTERNAL_CROSSHAIR_SIZETYPE_LABELS; -void PaintCrosshair(const CrosshairInfo &crh, int inaccuracy, const int x, const int y) +void PaintCrosshair(const CrosshairWepInfo *crh, const int inaccuracy, const int x, const int y) { - if (NEORules() && NEORules()->GetHiddenHudElements() & NEO_HUD_ELEMENT_CROSSHAIR) - { - return; - } - int wide, tall; vgui::surface()->GetScreenSize(wide, tall); - int iSize = crh.iSize; - int iThick = crh.iThick; - int iGap = crh.iGap; - int iCircleRad = crh.iCircleRad; - int iCircleSegments = crh.iCircleSegments; + int iSize = crh->iSize; + int iThick = crh->iThick; + int iGap = crh->iGap; + int iCircleRad = crh->iCircleRad; + int iCircleSegments = crh->iCircleSegments; - switch (crh.eDynamicType) + switch (crh->eDynamicType) { // Do we want to add the inaccuracy or replace the inaccuracy, or set the size to whatever is larger? should the operation be another option for the player? case CROSSHAIR_DYNAMICTYPE_SIZE: iSize += inaccuracy; @@ -78,14 +95,14 @@ void PaintCrosshair(const CrosshairInfo &crh, int inaccuracy, const int x, const if (iSize > 0) { - if (crh.eSizeType == CROSSHAIR_SIZETYPE_SCREEN && crh.eDynamicType == CROSSHAIR_DYNAMICTYPE_NONE) + if (crh->eSizeType == CROSSHAIR_SIZETYPE_SCREEN && crh->eDynamicType == CROSSHAIR_DYNAMICTYPE_NONE) { - iSize = (crh.flScrSize * (Max(wide, tall) / 2)); + iSize = (crh->flScrSize * (Max(wide, tall) / 2)); } - const bool bOdd = ((crh.iThick % 2) == 1); + const bool bOdd = ((crh->iThick % 2) == 1); const int iRBOffset = bOdd ? -1 : 0; // Right + bottom, odd-px offset - const int iHalf = crh.iThick / 2; + const int iHalf = crh->iThick / 2; const int iStartThick = bOdd ? iHalf + 1 : iHalf; const int iEndThick = iHalf; vgui::IntRect iRects[4] = { @@ -101,47 +118,48 @@ void PaintCrosshair(const CrosshairInfo &crh, int inaccuracy, const int x, const rect.x1 += x; rect.y1 += y; } - const int iRectSize = (crh.bTopLine) ? 4 : 3; - if (crh.iOutline > 0) + const int iRectSize = (crh->flags & CROSSHAIR_FLAG_NOTOPLINE) ? 3 : 4; + if (crh->iOutline > 0) { vgui::IntRect iOutRects[4] = {}; V_memcpy(iOutRects, iRects, sizeof(vgui::IntRect) * 4); for (vgui::IntRect &rect : iOutRects) { - rect.x0 -= crh.iOutline; - rect.y0 -= crh.iOutline; - rect.x1 += crh.iOutline; - rect.y1 += crh.iOutline; + rect.x0 -= crh->iOutline; + rect.y0 -= crh->iOutline; + rect.x1 += crh->iOutline; + rect.y1 += crh->iOutline; } - vgui::surface()->DrawSetColor(crh.colorOutline); + vgui::surface()->DrawSetColor(crh->colorOutline); vgui::surface()->DrawFilledRectArray(iOutRects, iRectSize); } - vgui::surface()->DrawSetColor(crh.color); + vgui::surface()->DrawSetColor(crh->color); vgui::surface()->DrawFilledRectArray(iRects, iRectSize); } - if (crh.iCenterDot > 0) + if (crh->iCenterDot > 0) { - const bool bOdd = ((crh.iCenterDot % 2) == 1); - const int iHalf = crh.iCenterDot / 2; + const bool bOdd = ((crh->iCenterDot % 2) == 1); + const int iHalf = crh->iCenterDot / 2; const int iStartCenter = bOdd ? iHalf + 1 : iHalf; const int iEndCenter = iHalf; + const bool bSeparateColorDot = (crh->flags & CROSSHAIR_FLAG_SEPERATEDOTCOLOR); - if (crh.iOutline > 0) + if (crh->iOutline > 0) { - vgui::surface()->DrawSetColor(crh.bSeparateColorDot ? crh.colorDotOutline : crh.colorOutline); - vgui::surface()->DrawFilledRect(-iStartCenter + x -crh.iOutline, -iStartCenter + y -crh.iOutline, - iEndCenter + x +crh.iOutline, iEndCenter + y +crh.iOutline); + vgui::surface()->DrawSetColor(bSeparateColorDot ? crh->colorDotOutline : crh->colorOutline); + vgui::surface()->DrawFilledRect(-iStartCenter + x -crh->iOutline, -iStartCenter + y -crh->iOutline, + iEndCenter + x +crh->iOutline, iEndCenter + y +crh->iOutline); } - vgui::surface()->DrawSetColor(crh.bSeparateColorDot ? crh.colorDot : crh.color); + vgui::surface()->DrawSetColor(bSeparateColorDot ? crh->colorDot : crh->color); vgui::surface()->DrawFilledRect(-iStartCenter + x, -iStartCenter + y, iEndCenter + x, iEndCenter + y); } if (iCircleRad > 0 && iCircleSegments > 0) { - vgui::surface()->DrawSetColor(crh.color); + vgui::surface()->DrawSetColor(crh->color); vgui::surface()->DrawOutlinedCircle(x, y, iCircleRad, iCircleSegments); } } @@ -165,28 +183,44 @@ void InitializeClNeoCrosshair() cl_neo_crosshair.SetDefault(static_szCrhSerialDefault); } -#endif // CLIENT_DLL - -// NEO WARNING (nullsystem): When adding new/removing items to serialize, only append enum one after another -// before the NEOXHAIR_SERIAL__LATESTPLUSONE section! -// When working on this put in the current in-dev/unreleased version for the enum name. -// Bump NEO_XHAIR_SEQMAX if it's going to go over the string length -enum NeoXHairSerial +void NeoConVarCrosshairChangeCallback(IConVar *cvar, const char *pOldVal, [[maybe_unused]] float flOldVal) { - NEOXHAIR_SERIAL_PREALPHA_V8_2 = 1, - NEOXHAIR_SERIAL_ALPHA_V17, - NEOXHAIR_SERIAL_ALPHA_V19, - NEOXHAIR_SERIAL_ALPHA_V22, - NEOXHAIR_SERIAL_ALPHA_V28, - - NEOXHAIR_SERIAL__LATESTPLUSONE, - NEOXHAIR_SERIAL_CURRENT = NEOXHAIR_SERIAL__LATESTPLUSONE - 1, -}; + static bool bStaticCallbackChangedXHairCVar = false; + if (bStaticCallbackChangedXHairCVar) + { + return; + } -void ResetCrosshairToDefault(CrosshairInfo *crh) + ConVarRef cvarRef(cvar); + if (false == ValidateCrosshairSerial(cvarRef.GetString())) + { + bStaticCallbackChangedXHairCVar = true; + char mutStr[NEO_XHAIR_SEQMAX]; + if (ValidateCrosshairSerial(pOldVal)) + { + V_strcpy_safe(mutStr, pOldVal); + } + else + { + DefaultCrosshairSerial(mutStr); + } + Q_UnicodeRepair(mutStr); + cvarRef.SetValue(mutStr); + bStaticCallbackChangedXHairCVar = false; + } +} + +#endif // CLIENT_DLL + +void ResetCrosshairToDefault(CrosshairInfo *xhairInfo, + EHipfireOpt (*paeHipfireOpts)[CROSSHAIR_WEP__TOTAL]) { + xhairInfo->wepFlags = CROSSHAIR_WEP_FLAG_DEFAULT; + xhairInfo->hipfireFlags = CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL; + CrosshairWepInfo *crh = &(xhairInfo->wep[CROSSHAIR_WEP_DEFAULT]); crh->iStyle = CROSSHAIR_STYLE_DEFAULT; crh->color = COLOR_WHITE; + crh->flags = CROSSHAIR_FLAG_DEFAULT; crh->eSizeType = CROSSHAIR_SIZETYPE_ABSOLUTE; crh->iSize = 6; crh->flScrSize = 0.0f; @@ -194,192 +228,224 @@ void ResetCrosshairToDefault(CrosshairInfo *crh) crh->iGap = 4; crh->iOutline = 0; crh->iCenterDot = 0; - crh->bTopLine = true; crh->iCircleRad = 0; crh->iCircleSegments = 0; crh->eDynamicType = CROSSHAIR_DYNAMICTYPE_NONE; - crh->bSeparateColorDot = false; crh->colorDot = COLOR_WHITE; crh->colorDotOutline = COLOR_BLACK; crh->colorOutline = COLOR_BLACK; + for (int i = 1; i < CROSSHAIR_WEP__TOTAL; ++i) + { + xhairInfo->wep[i] = *crh; + } + if (paeHipfireOpts) + { + V_memset(*paeHipfireOpts, 0, sizeof(EHipfireOpt) * CROSSHAIR_WEP__TOTAL); + } } void DefaultCrosshairSerial(char (&szSequence)[NEO_XHAIR_SEQMAX]) { - CrosshairInfo crh = {}; - ResetCrosshairToDefault(&crh); - ExportCrosshair(&crh, szSequence); + CrosshairInfo xhairInfo = {}; + ResetCrosshairToDefault(&xhairInfo); + ExportCrosshair(&xhairInfo, szSequence); } -union NeoXHairVariant +int UseCrosshairIndexFor(const CrosshairInfo *xhairInfo, const int iXHairWep, bool *pbHide) { - int iVal; - float flVal; - bool bVal; -}; - -enum NeoXHairVariantType -{ - NEOXHAIRVARTYPE_INT = 0, - NEOXHAIRVARTYPE_FLOAT, - NEOXHAIRVARTYPE_BOOL, -}; - -static NeoXHairVariant DeserialVariant(char (&szMutStr)[NEO_XHAIR_SEQMAX], const int iSzMutStrSize, - int *idx, const NeoXHairVariantType eType, const NeoXHairVariant varDefault) -{ - NeoXHairVariant var; - V_memcpy(&var, &varDefault, sizeof(NeoXHairVariant)); - - const int iPrevSegment = *idx; - Assert(iSzMutStrSize <= NEO_XHAIR_SEQMAX); + if (pbHide && iXHairWep == CROSSHAIR_WEP_NONE) + { + *pbHide = true; + } - for (; *idx < iSzMutStrSize; ++(*idx)) + if (iXHairWep > CROSSHAIR_WEP_DEFAULT) { - if (szMutStr[*idx] == ';') + if (xhairInfo->wepFlags & (1 << (iXHairWep - 1))) { - szMutStr[*idx] = '\0'; - const char *pszCurSegment = szMutStr + iPrevSegment; - if (pszCurSegment[0] != '\0') + if ((iXHairWep < CROSSHAIR_WEP_DEFAULT_HIPFIRE) || + (xhairInfo->hipfireFlags & (1 << (iXHairWep - CROSSHAIR_WEP_DEFAULT_HIPFIRE)))) { - switch (eType) + return iXHairWep; + } + + if (iXHairWep > CROSSHAIR_WEP_DEFAULT_HIPFIRE) + { + if (pbHide && !(xhairInfo->wepFlags & CROSSHAIR_WEP_FLAG_DEFAULT_HIPFIRE)) { - case NEOXHAIRVARTYPE_INT: - var.iVal = atoi(pszCurSegment); - break; - case NEOXHAIRVARTYPE_BOOL: - var.bVal = (atoi(pszCurSegment) != 0); - break; - case NEOXHAIRVARTYPE_FLOAT: - var.flVal = static_cast(atof(pszCurSegment)); - break; + *pbHide = true; + } + + if (xhairInfo->hipfireFlags & CROSSHAIR_HIPFIRECUSTOM_FLAG_DEFAULT) + { + return CROSSHAIR_WEP_DEFAULT_HIPFIRE; } } - ++(*idx); - break; + } + else if (pbHide && iXHairWep >= CROSSHAIR_WEP_DEFAULT_HIPFIRE) + { + *pbHide = true; } } - - return var; + return CROSSHAIR_WEP_DEFAULT; } -enum ESerialMode -{ - SERIALMODE_DESERIALIZE = 0, - SERIALMODE_SERIALIZE, -}; - -[[nodiscard]] static inline int SerialInt(const int iVal, const ESerialMode eSerialMode, - char (&szMutStr)[NEO_XHAIR_SEQMAX], const int iSzMutStrSize, int *idx) +static bool ImportOrExportCrosshair(const ESerialMode eSerialMode, CrosshairInfo *xhairInfo, + char (&szMutSeq)[NEO_XHAIR_SEQMAX], const int iSeqSize, const int iExportSerialVersion) { - if (eSerialMode == SERIALMODE_SERIALIZE) - { - char szTmp[NEO_XHAIR_SEQMAX]; - V_sprintf_safe(szTmp, "%d;", iVal); - V_strcat_safe(szMutStr, szTmp); - return iVal; - } - else + SerialContext ctx = { + .eSerialMode = eSerialMode, + .iSeqSize = iSeqSize, + }; +#ifdef UNIT_TEST_DLL + if (g_verbose > 0) fprintf(stderr, "%s: ImportOrExportCrosshair: iSeqSize: %d\n", g_testFnName, iSeqSize); +#endif + + const int iSerialVersion = SerialInt(iExportSerialVersion, NEOXHAIR_SERIAL_CURRENT, + COMPMODE_IGNORE, szMutSeq, &ctx); + if (iSerialVersion <= NEOXHAIR_SERIAL_PREALPHA_V8_2 || iSerialVersion > NEOXHAIR_SERIAL_CURRENT) { - return DeserialVariant(szMutStr, iSzMutStrSize, idx, NEOXHAIRVARTYPE_INT, { .iVal = iVal }).iVal; + // Unsupported serialization version or corrupted from first character + return false; } -} -[[nodiscard]] static inline bool SerialBool(const bool bVal, const ESerialMode eSerialMode, - char (&szMutStr)[NEO_XHAIR_SEQMAX], const int iSzMutStrSize, int *idx) -{ - if (eSerialMode == SERIALMODE_SERIALIZE) - { - char szTmp[NEO_XHAIR_SEQMAX]; - V_sprintf_safe(szTmp, "%d;", static_cast(bVal)); - V_strcat_safe(szMutStr, szTmp); - return bVal; - } - else - { - return DeserialVariant(szMutStr, iSzMutStrSize, idx, NEOXHAIRVARTYPE_BOOL, { .bVal = bVal } ).bVal; - } -} + // v28 onwards cuts out segments if unused + const bool bNotCompact = (iSerialVersion < NEOXHAIR_SERIAL_ALPHA_V28); -[[nodiscard]] static inline float SerialFloat(const float flVal, const ESerialMode eSerialMode, - char (&szMutStr)[NEO_XHAIR_SEQMAX], const int iSzMutStrSize, int *idx) -{ - if (eSerialMode == SERIALMODE_SERIALIZE) - { - char szTmp[NEO_XHAIR_SEQMAX]; - V_sprintf_safe(szTmp, "%.3f;", flVal); - V_strcat_safe(szMutStr, szTmp); - return flVal; - } - else + if (iSerialVersion >= NEOXHAIR_SERIAL_ALPHA_V29) { - return DeserialVariant(szMutStr, iSzMutStrSize, idx, NEOXHAIRVARTYPE_FLOAT, { .flVal = flVal } ).flVal; + xhairInfo->wepFlags = SerialInt(xhairInfo->wepFlags, xhairInfo->wepFlags, + COMPMODE_IGNORE, szMutSeq, &ctx, 0, CROSSHAIR_WEP_FLAG__HIGHESTFLAG); + xhairInfo->hipfireFlags = SerialInt(xhairInfo->hipfireFlags, xhairInfo->hipfireFlags, + COMPMODE_IGNORE, szMutSeq, &ctx, 0, CROSSHAIR_HIPFIRECUSTOM_FLAG__HIGHESTFLAG); } -} - -static void ImportOrExportCrosshair(const ESerialMode eSerialMode, CrosshairInfo *crh, - char (&szMutSeq)[NEO_XHAIR_SEQMAX], const int iSeqSize) -{ - int idx = 0; - const int iSerialVersion = SerialInt(NEOXHAIR_SERIAL_CURRENT, eSerialMode, - szMutSeq, iSeqSize, &idx); - - // v28 onwards cuts out segments if unused - const bool bNotCompact = (iSerialVersion < NEOXHAIR_SERIAL_ALPHA_V28); + for (int i = 0; i < CROSSHAIR_WEP__TOTAL; ++i) + { + const ECompMode eCompMode = (i == CROSSHAIR_WEP_DEFAULT) ? COMPMODE_IGNORE : COMPMODE_EQUALS; + const int iCompIdx = (i > CROSSHAIR_WEP_DEFAULT_HIPFIRE) + ? CROSSHAIR_WEP_DEFAULT_HIPFIRE + : CROSSHAIR_WEP_DEFAULT; - crh->iStyle = SerialInt(crh->iStyle, eSerialMode, szMutSeq, iSeqSize, &idx); - crh->color.SetRawColor(SerialInt(crh->color.GetRawColor(), eSerialMode, szMutSeq, iSeqSize, &idx)); + CrosshairWepInfo *crh = &xhairInfo->wep[i]; + const CrosshairWepInfo *cmpCrh = &xhairInfo->wep[iCompIdx]; - if (bNotCompact || crh->iStyle == CROSSHAIR_STYLE_CUSTOM) - { - crh->eSizeType = static_cast(SerialInt(crh->eSizeType, eSerialMode, szMutSeq, iSeqSize, &idx)); - if (bNotCompact || crh->eSizeType == CROSSHAIR_SIZETYPE_ABSOLUTE) - { - crh->iSize = SerialInt(crh->iSize, eSerialMode, szMutSeq, iSeqSize, &idx); - } - if (bNotCompact || crh->eSizeType == CROSSHAIR_SIZETYPE_SCREEN) + const int iUseXHairIdx = UseCrosshairIndexFor(xhairInfo, i); + if (iUseXHairIdx != i) { - crh->flScrSize = SerialFloat(crh->flScrSize, eSerialMode, szMutSeq, iSeqSize, &idx); + if (eSerialMode == SERIALMODE_DESERIALIZE) + { + V_memcpy(crh, &xhairInfo->wep[iUseXHairIdx], sizeof(CrosshairWepInfo)); + } + continue; } - crh->iThick = SerialInt(crh->iThick, eSerialMode, szMutSeq, iSeqSize, &idx); - crh->iGap = SerialInt(crh->iGap, eSerialMode, szMutSeq, iSeqSize, &idx); - crh->iOutline = SerialInt(crh->iOutline, eSerialMode, szMutSeq, iSeqSize, &idx); - crh->iCenterDot = SerialInt(crh->iCenterDot, eSerialMode, szMutSeq, iSeqSize, &idx); - crh->bTopLine = SerialBool(crh->bTopLine, eSerialMode, szMutSeq, iSeqSize, &idx); - crh->iCircleRad = SerialInt(crh->iCircleRad, eSerialMode, szMutSeq, iSeqSize, &idx); - if (bNotCompact || crh->iCircleRad > 0) + + if (iSerialVersion >= NEOXHAIR_SERIAL_ALPHA_V29) { - crh->iCircleSegments = SerialInt(crh->iCircleSegments, eSerialMode, szMutSeq, iSeqSize, &idx); + crh->flags = SerialInt(crh->flags, cmpCrh->flags, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_FLAG__HIGHESTFLAG); } - - crh->eDynamicType = (iSerialVersion >= NEOXHAIR_SERIAL_ALPHA_V19) - ? static_cast(SerialInt(crh->eDynamicType, eSerialMode, szMutSeq, iSeqSize, &idx)) - : CROSSHAIR_DYNAMICTYPE_NONE; + crh->iStyle = SerialInt(crh->iStyle, cmpCrh->iStyle, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_STYLE__TOTAL - 1); + crh->color.SetRawColor(SerialInt(crh->color.GetRawColor(), cmpCrh->color.GetRawColor(), eCompMode, szMutSeq, &ctx)); - if (iSerialVersion >= NEOXHAIR_SERIAL_ALPHA_V22) + if (bNotCompact || crh->iStyle == CROSSHAIR_STYLE_CUSTOM) { + crh->eSizeType = static_cast(SerialInt(crh->eSizeType, cmpCrh->eSizeType, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_SIZETYPE__TOTAL - 1)); + if (bNotCompact || crh->eSizeType == CROSSHAIR_SIZETYPE_ABSOLUTE) + { + crh->iSize = SerialInt(crh->iSize, cmpCrh->iSize, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_MAX_SIZE); + } + if (bNotCompact || crh->eSizeType == CROSSHAIR_SIZETYPE_SCREEN) + { + crh->flScrSize = SerialFloat(crh->flScrSize, cmpCrh->flScrSize, eCompMode, szMutSeq, &ctx, 0.0f, 1.0f); + } + crh->iThick = SerialInt(crh->iThick, cmpCrh->iThick, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_MAX_THICKNESS); + crh->iGap = SerialInt(crh->iGap, cmpCrh->iGap, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_MAX_GAP); + crh->iOutline = SerialInt(crh->iOutline, cmpCrh->iOutline, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_MAX_OUTLINE); + crh->iCenterDot = SerialInt(crh->iCenterDot, cmpCrh->iCenterDot, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_MAX_CENTER_DOT); + if (iSerialVersion < NEOXHAIR_SERIAL_ALPHA_V29) + { + const bool bTopLine = SerialBool(!(crh->flags & CROSSHAIR_FLAG_NOTOPLINE), !(cmpCrh->flags & CROSSHAIR_FLAG_NOTOPLINE), eCompMode, szMutSeq, &ctx); + if (!bTopLine) + { + crh->flags |= CROSSHAIR_FLAG_NOTOPLINE; + } + } + crh->iCircleRad = SerialInt(crh->iCircleRad, cmpCrh->iCircleRad, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_MAX_CIRCLE_RAD); + if (bNotCompact || crh->iCircleRad > 0) + { + crh->iCircleSegments = SerialInt(crh->iCircleSegments, cmpCrh->iCircleSegments, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_MAX_CIRCLE_SEGMENTS); + } + + if (iSerialVersion < NEOXHAIR_SERIAL_ALPHA_V19) + { + continue; + } + // >= NEOXHAIR_SERIAL_ALPHA_V19 segments + + crh->eDynamicType = (iSerialVersion >= NEOXHAIR_SERIAL_ALPHA_V19) + ? static_cast(SerialInt(crh->eDynamicType, cmpCrh->eDynamicType, eCompMode, szMutSeq, &ctx, 0, CROSSHAIR_DYNAMICTYPE__TOTAL - 1)) + : CROSSHAIR_DYNAMICTYPE_NONE; + + if (iSerialVersion < NEOXHAIR_SERIAL_ALPHA_V22) + { + continue; + } + // >= NEOXHAIR_SERIAL_ALPHA_V22 segments + if (bNotCompact || crh->iCenterDot > 0) { - crh->bSeparateColorDot = SerialBool(crh->bSeparateColorDot, eSerialMode, szMutSeq, iSeqSize, &idx); - if (bNotCompact || crh->bSeparateColorDot) + if (iSerialVersion < NEOXHAIR_SERIAL_ALPHA_V29) { - crh->colorDot.SetRawColor(SerialInt(crh->colorDot.GetRawColor(), eSerialMode, szMutSeq, iSeqSize, &idx)); + const bool bSeparateColorDot = SerialBool((crh->flags & CROSSHAIR_FLAG_SEPERATEDOTCOLOR), (cmpCrh->flags & CROSSHAIR_FLAG_SEPERATEDOTCOLOR), eCompMode, szMutSeq, &ctx); + if (bSeparateColorDot) + { + crh->flags |= CROSSHAIR_FLAG_SEPERATEDOTCOLOR; + } + } + if (bNotCompact || (crh->flags & CROSSHAIR_FLAG_SEPERATEDOTCOLOR)) + { + crh->colorDot.SetRawColor(SerialInt(crh->colorDot.GetRawColor(), cmpCrh->colorDot.GetRawColor(), eCompMode, szMutSeq, &ctx)); if (bNotCompact || crh->iOutline > 0) { - crh->colorDotOutline.SetRawColor(SerialInt(crh->colorDotOutline.GetRawColor(), eSerialMode, szMutSeq, iSeqSize, &idx)); + crh->colorDotOutline.SetRawColor(SerialInt(crh->colorDotOutline.GetRawColor(), cmpCrh->colorDotOutline.GetRawColor(), eCompMode, szMutSeq, &ctx)); } } } if (bNotCompact || crh->iOutline > 0) { - crh->colorOutline.SetRawColor(SerialInt(crh->colorOutline.GetRawColor(), eSerialMode, szMutSeq, iSeqSize, &idx)); + crh->colorOutline.SetRawColor(SerialInt(crh->colorOutline.GetRawColor(), cmpCrh->colorOutline.GetRawColor(), eCompMode, szMutSeq, &ctx)); } } } + + if (SERIALMODE_CHECK == ctx.eSerialMode) + { + return (false == ctx.bOutOfBound); + } + + // Further compress with RLE + SerialRLEncode(szMutSeq, ctx.eSerialMode); + return true; +} + +bool ValidateCrosshairSerial(const char *pszSequence) +{ + const int iSeqSize = V_strlen(pszSequence); + if (iSeqSize <= 0 || iSeqSize > NEO_XHAIR_SEQMAX) + { + return false; + } + + char szMutSeq[NEO_XHAIR_SEQMAX]; + V_strcpy_safe(szMutSeq, pszSequence); + + CrosshairInfo xhairInfo = {}; + ResetCrosshairToDefault(&xhairInfo, nullptr); + + return ImportOrExportCrosshair(SERIALMODE_CHECK, &xhairInfo, szMutSeq, iSeqSize, NEOXHAIR_SERIAL_CURRENT); } -bool ImportCrosshair(CrosshairInfo *crh, const char *pszSequence) +bool ImportCrosshair(CrosshairInfo *xhairInfo, const char *pszSequence, + EHipfireOpt (*paeHipfireOpts)[CROSSHAIR_WEP__TOTAL]) { const int iSeqSize = V_strlen(pszSequence); if (iSeqSize <= 0 || iSeqSize > NEO_XHAIR_SEQMAX) @@ -388,17 +454,44 @@ bool ImportCrosshair(CrosshairInfo *crh, const char *pszSequence) } char szMutSeq[NEO_XHAIR_SEQMAX]; - V_memcpy(szMutSeq, pszSequence, sizeof(char) * iSeqSize); - szMutSeq[iSeqSize] = '\0'; + V_strcpy_safe(szMutSeq, pszSequence); + + ResetCrosshairToDefault(xhairInfo, paeHipfireOpts); + const bool bValid = ImportOrExportCrosshair(SERIALMODE_DESERIALIZE, xhairInfo, szMutSeq, iSeqSize, NEOXHAIR_SERIAL_CURRENT); + if (!bValid) + { + return false; + } + + if (paeHipfireOpts) + { + for (int i = CROSSHAIR_WEP_DEFAULT_HIPFIRE; i < CROSSHAIR_WEP__TOTAL; ++i) + { + const NeoCrosshairWepFlags wepMask = (1 << (i - 1)); + const NeoCrosshairHipfireCustomFlags wepHipfireMask = (1 << (i - CROSSHAIR_WEP_DEFAULT_HIPFIRE)); + // NeoCrosshairHipfireCustomFlags flag equiv. only sets when NeoCrosshairWepFlags flag equiv. is set + Assert(false == ((false == (xhairInfo->wepFlags & wepMask)) && (xhairInfo->hipfireFlags & wepHipfireMask))); + (*paeHipfireOpts)[i] = static_cast(static_cast(xhairInfo->wepFlags & wepMask) + static_cast(xhairInfo->hipfireFlags & wepHipfireMask)); + } + } - ResetCrosshairToDefault(crh); - ImportOrExportCrosshair(SERIALMODE_DESERIALIZE, crh, szMutSeq, iSeqSize); return true; } -void ExportCrosshair(CrosshairInfo *crh, char (&szSequence)[NEO_XHAIR_SEQMAX]) +void ExportCrosshair(CrosshairInfo *xhairInfo, char (&szSequence)[NEO_XHAIR_SEQMAX] +#ifdef UNIT_TEST_DLL + , const int iExportSerialVersion +#endif + ) { +#ifndef UNIT_TEST_DLL + static constexpr int iExportSerialVersion = NEOXHAIR_SERIAL_CURRENT; +#endif szSequence[0] = '\0'; - ImportOrExportCrosshair(SERIALMODE_SERIALIZE, crh, szSequence, NEO_XHAIR_SEQMAX); + ImportOrExportCrosshair(SERIALMODE_SERIALIZE, xhairInfo, szSequence, NEO_XHAIR_SEQMAX + , iExportSerialVersion); +#ifdef DEBUG + Assert(ValidateCrosshairSerial(szSequence)); +#endif } diff --git a/src/game/shared/neo/neo_crosshair.h b/src/game/shared/neo/neo_crosshair.h index 5fb552e81..f9d8e4b66 100644 --- a/src/game/shared/neo/neo_crosshair.h +++ b/src/game/shared/neo/neo_crosshair.h @@ -2,6 +2,12 @@ #include #include "Color.h" +#include "neo_weapon_types.h" + +#ifdef UNIT_TEST_DLL +#define COLOR_WHITE Color(255, 255, 255, 255) +#define COLOR_BLACK Color(0, 0, 0, 255) +#endif #ifdef CLIENT_DLL #include "neo_player_shared.h" @@ -9,11 +15,10 @@ class C_NEOBaseCombatWeapon; #endif // CLIENT_DLL -// NEO NOTE (nullsystem): Max string length is -// something like: "2;2;-16711936;1;6;1.000;25;25;5;25;1;50;50;2;1;-16711936;-16711936;-16711936;" -// which is ~77 for v5 serialization | 96 length is enough for now till -// more comes in -static constexpr const size_t NEO_XHAIR_SEQMAX = 96; +// NEO NOTE (nullsystem): Check in test/test_neo_crosshair.cpp TestSerial_LongestLength unit test +// on longest length. Currently it sits under 400. +static constexpr const size_t NEO_XHAIR_SEQMAX = 400; + static constexpr int CROSSHAIR_MAX_SIZE = 100; static constexpr int CROSSHAIR_MAX_THICKNESS = 25; static constexpr int CROSSHAIR_MAX_GAP = 25; @@ -49,6 +54,75 @@ enum NeoHudCrosshairDynamicType CROSSHAIR_DYNAMICTYPE__TOTAL, }; +enum ENeoCrosshairWep +{ + CROSSHAIR_WEP_NONE = -1, + CROSSHAIR_WEP_DEFAULT = 0, + CROSSHAIR_WEP_SECONDARY, + CROSSHAIR_WEP_SHOTGUN, + CROSSHAIR_WEP_DEFAULT_HIPFIRE, + CROSSHAIR_WEP_SECONDARY_HIPFIRE, + CROSSHAIR_WEP_SHOTGUN_HIPFIRE, + + CROSSHAIR_WEP__TOTAL, +}; + +extern const ENeoCrosshairWep MAP_WEAPON_TYPE_TO_XHAIR[WEP_TYPE__TOTAL]; + +enum NeoCrosshairWepFlag_ +{ + CROSSHAIR_WEP_FLAG_DEFAULT = 0, // Default + CROSSHAIR_WEP_FLAG_SECONDARY = 1 << 0, // Pistols (Milso, Tachi, Kyla) + CROSSHAIR_WEP_FLAG_SHOTGUN = 1 << 1, // Shotguns (Supa, AA13) + CROSSHAIR_WEP_FLAG_DEFAULT_HIPFIRE = 1 << 2, // Default hipfire + CROSSHAIR_WEP_FLAG_SECONDARY_HIPFIRE = 1 << 3, // Pistols hipfire + CROSSHAIR_WEP_FLAG_SHOTGUN_HIPFIRE = 1 << 4, // Shotguns hipfire + + CROSSHAIR_WEP_FLAG__FINALENUM, + CROSSHAIR_WEP_FLAG__HIGHESTFLAG = 2 * (CROSSHAIR_WEP_FLAG__FINALENUM - 1), +}; +typedef int NeoCrosshairWepFlags; + +// EX: CROSSHAIR_WEP_FLAG_DEFAULT_HIPFIRE = 0, CROSSHAIR_HIPFIRECUSTOM_FLAG_DEFAULT = 0 +// Default hipfire disabled +// CROSSHAIR_WEP_FLAG_DEFAULT_HIPFIRE = 1, CROSSHAIR_HIPFIRECUSTOM_FLAG_DEFAULT = 0 +// Default hipfire enabled + use default, fallback to non-hipfire default +// CROSSHAIR_WEP_FLAG_DEFAULT_HIPFIRE = 1, CROSSHAIR_HIPFIRECUSTOM_FLAG_DEFAULT = 1 +// Default hipfire enabled + fully custom +// CROSSHAIR_WEP_FLAG_SECONDARY_HIPFIRE = 1, CROSSHAIR_HIPFIRECUSTOM_FLAG_SECONDARY = 0 +// Secondary hipfire enabled + use default, fallback to hipfire default +enum NeoCrosshairHipfireCustomFlag_ +{ + CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL = 0, + CROSSHAIR_HIPFIRECUSTOM_FLAG_DEFAULT = 1 << 0, // If not set: Fallback to non-hipfire default + CROSSHAIR_HIPFIRECUSTOM_FLAG_SECONDARY = 1 << 1, // If not set: Fallback to hipfire default + CROSSHAIR_HIPFIRECUSTOM_FLAG_SHOTGUN = 1 << 2, // If not set: Fallback to hipfire default + + CROSSHAIR_HIPFIRECUSTOM_FLAG__FINALENUM, + CROSSHAIR_HIPFIRECUSTOM_FLAG__HIGHESTFLAG = 2 * (CROSSHAIR_HIPFIRECUSTOM_FLAG__FINALENUM - 1), +}; +typedef int NeoCrosshairHipfireCustomFlags; + +enum NeoCrosshairFlag_ +{ + CROSSHAIR_FLAG_DEFAULT = 0, + CROSSHAIR_FLAG_NOTOPLINE = 1 << 0, // Do not draw top line + CROSSHAIR_FLAG_SEPERATEDOTCOLOR = 1 << 1, // Draw dot as separate color + + CROSSHAIR_FLAG__FINALENUM, + CROSSHAIR_FLAG__HIGHESTFLAG = 2 * (CROSSHAIR_FLAG__FINALENUM - 1), +}; +typedef int NeoCrosshairFlags; + +enum EHipfireOpt +{ + HIPFIREOPT_DISABLED = 0, + HIPFIREOPT_USEDEFAULT, + HIPFIREOPT_ENABLED, + + HIPFIREOPT__TOTAL, +}; + #ifdef CLIENT_DLL extern ConVar cl_neo_crosshair; extern ConVar cl_neo_crosshair_network; @@ -59,10 +133,11 @@ extern const wchar_t **CROSSHAIR_SIZETYPE_LABELS; extern const wchar_t **CROSSHAIR_DYNAMICTYPE_LABELS; #endif // CLIENT_DLL -struct CrosshairInfo +struct CrosshairWepInfo { int iStyle; Color color; + NeoCrosshairFlags flags; NeoHudCrosshairSizeType eSizeType; int iSize; float flScrSize; @@ -70,27 +145,68 @@ struct CrosshairInfo int iGap; int iOutline; int iCenterDot; - bool bTopLine; int iCircleRad; int iCircleSegments; NeoHudCrosshairDynamicType eDynamicType; - bool bSeparateColorDot; Color colorDot; Color colorDotOutline; Color colorOutline; }; +struct CrosshairInfo +{ + NeoCrosshairWepFlags wepFlags; + NeoCrosshairHipfireCustomFlags hipfireFlags; + CrosshairWepInfo wep[CROSSHAIR_WEP__TOTAL]; +}; + +// NEO WARNING (nullsystem): When adding new/removing items to serialize, only append enum one after another +// before the NEOXHAIR_SERIAL__LATESTPLUSONE section! +// When working on this put in the current in-dev/unreleased version for the enum name. +// Bump NEO_XHAIR_SEQMAX if it's going to go over the string length +enum NeoXHairSerial +{ + NEOXHAIR_SERIAL_PREALPHA_V8_2 = 1, + NEOXHAIR_SERIAL_ALPHA_V17, + NEOXHAIR_SERIAL_ALPHA_V19, + NEOXHAIR_SERIAL_ALPHA_V22, + NEOXHAIR_SERIAL_ALPHA_V28, + NEOXHAIR_SERIAL_ALPHA_V29, + + NEOXHAIR_SERIAL__LATESTPLUSONE, + NEOXHAIR_SERIAL_CURRENT = NEOXHAIR_SERIAL__LATESTPLUSONE - 1, +}; + #ifdef CLIENT_DLL -void PaintCrosshair(const CrosshairInfo &crh, int inaccuracy, const int x, const int y); +void PaintCrosshair(const CrosshairWepInfo *crh, const int inaccuracy, const int x, const int y); int HalfInaccuracyConeInScreenPixels(C_NEOBaseCombatWeapon *pWeapon, int halfScreenWidth); void InitializeClNeoCrosshair(); + +class IConVar; +void NeoConVarCrosshairChangeCallback(IConVar *cvar, const char *pOldVal, float flOldVal); + #endif // CLIENT_DLL -void ResetCrosshairToDefault(CrosshairInfo *crh); +void ResetCrosshairToDefault(CrosshairInfo *xhairInfo, + EHipfireOpt (*paeHipfireOpts)[CROSSHAIR_WEP__TOTAL] = nullptr); void DefaultCrosshairSerial(char (&szSequence)[NEO_XHAIR_SEQMAX]); +int UseCrosshairIndexFor(const CrosshairInfo *xhairInfo, const int iXHairWep, bool *pbHide = nullptr); + +bool ValidateCrosshairSerial(const char *pszSequence); + // NEO NOTE (nullsystem): (*&)[NUM] enforces array size -bool ImportCrosshair(CrosshairInfo *crh, const char *pszSequence); -void ExportCrosshair(CrosshairInfo *crh, char (&szSequence)[NEO_XHAIR_SEQMAX]); +// paeHipfireOpts - Maps NeoUI RingBox int <-> NeoCrosshairWepFlags + NeoCrosshairHipfireCustomFlags +// Only for import as export must have directly already set by flags and managed within UI +bool ImportCrosshair(CrosshairInfo *xhairInfo, const char *pszSequence, + EHipfireOpt (*paeHipfireOpts)[CROSSHAIR_WEP__TOTAL] = nullptr); + +// iExportSerialVersion is only used for unit testing purpose, for usage in-game +// it should always be exporting to NEOXHAIR_SERIAL_CURRENT +void ExportCrosshair(CrosshairInfo *xhairInfo, char (&szSequence)[NEO_XHAIR_SEQMAX] +#ifdef UNIT_TEST_DLL + , const int iExportSerialVersion = NEOXHAIR_SERIAL_CURRENT +#endif + ); diff --git a/src/game/shared/neo/neo_serial.cpp b/src/game/shared/neo/neo_serial.cpp new file mode 100644 index 000000000..8ed2cdb42 --- /dev/null +++ b/src/game/shared/neo/neo_serial.cpp @@ -0,0 +1,231 @@ +#include "neo_crosshair.h" +#include "neo_serial.h" + +#include "strtools.h" +#include "mathlib/mathlib.h" + +static constexpr char CH_XH_SEGEND = ';'; +static constexpr char CH_XH_SEGSKIP = '^'; + +union SerialVariant +{ + int iVal; + float flVal; + bool bVal; +}; + +enum ESerialVariantType +{ + SERIALVARIANTTYPE_INT = 0, + SERIALVARIANTTYPE_FLOAT, + SERIALVARIANTTYPE_BOOL, +}; + +static void StrCatCh(char (&szMutStr)[NEO_XHAIR_SEQMAX], const char ch) +{ + const int iSize = V_strlen(szMutStr); + if ((iSize + 1) < NEO_XHAIR_SEQMAX) + { + szMutStr[iSize] = ch; + szMutStr[iSize + 1] = '\0'; + } +} + +static SerialVariant DeserialVariant(char (&szMutStr)[NEO_XHAIR_SEQMAX], + const ESerialVariantType eType, const SerialVariant varDefault, + const SerialVariant varMin, const SerialVariant varMax, SerialContext *ctx) +{ + SerialVariant var = varDefault; + + if (ctx->iSkipIdx > 0) + { + --(ctx->iSkipIdx); + return var; + } + + int *idx = &ctx->idx; + const int iPrevSegment = *idx; + Assert(ctx->iSeqSize <= NEO_XHAIR_SEQMAX); + + for (; *idx < ctx->iSeqSize; ++(*idx)) + { + const char endCh = szMutStr[*idx]; + if (endCh == CH_XH_SEGEND || endCh == CH_XH_SEGSKIP) + { + szMutStr[*idx] = '\0'; + const char *pszCurSegment = szMutStr + iPrevSegment; + if (pszCurSegment[0] != '\0') + { + if (endCh == CH_XH_SEGEND) // Value + { + switch (eType) + { + case SERIALVARIANTTYPE_INT: + { + const int iInitVal = V_atoi(pszCurSegment); + if (SERIALMODE_CHECK == ctx->eSerialMode && varMin.iVal < varMax.iVal) + { + ctx->bOutOfBound = (iInitVal > varMax.iVal || iInitVal < varMin.iVal); + } + var.iVal = (varMin.iVal < varMax.iVal) ? clamp(iInitVal, varMin.iVal, varMax.iVal) : iInitVal; + } break; + case SERIALVARIANTTYPE_BOOL: + { + var.bVal = (V_atoi(pszCurSegment) != 0); + } break; + case SERIALVARIANTTYPE_FLOAT: + { + const float flInitVal = static_cast(V_atof(pszCurSegment)); + if (SERIALMODE_CHECK == ctx->eSerialMode && varMin.flVal < varMax.flVal) + { + ctx->bOutOfBound = (flInitVal > varMax.flVal || flInitVal < varMin.flVal); + } + var.flVal = (varMin.flVal < varMax.flVal) ? clamp(flInitVal, varMin.flVal, varMax.flVal) : flInitVal; + } break; + } + } + else if (endCh == CH_XH_SEGSKIP) // Run-length encoding skip + { + const int iSkipLen = V_atoi(pszCurSegment); + if (iSkipLen > 1) + { + ctx->iSkipIdx = iSkipLen - 1; + } + } + } + ++(*idx); + break; + } + } + + return var; +} + +[[nodiscard]] int SerialInt(const int iVal, const int iCompVal, + const ECompMode eCompMode, char (&szMutStr)[NEO_XHAIR_SEQMAX], SerialContext *ctx, + const int iMin, const int iMax) +{ + if (ctx->eSerialMode == SERIALMODE_SERIALIZE) + { + if (eCompMode == COMPMODE_EQUALS && iVal == iCompVal) + { + StrCatCh(szMutStr, CH_XH_SEGEND); + } + else + { + char szTmp[NEO_XHAIR_SEQMAX]; + V_sprintf_safe(szTmp, "%d%c", (iMin < iMax) ? clamp(iVal, iMin, iMax) : iVal, CH_XH_SEGEND); + V_strcat_safe(szMutStr, szTmp); + } + return iVal; + } + else + { + return DeserialVariant(szMutStr, SERIALVARIANTTYPE_INT, + { .iVal = (eCompMode == COMPMODE_EQUALS) ? iCompVal : iVal }, + { .iVal = iMin }, { .iVal = iMax }, ctx).iVal; + } +} + +[[nodiscard]] bool SerialBool(const bool bVal, const bool bCompVal, + const ECompMode eCompMode, char (&szMutStr)[NEO_XHAIR_SEQMAX], SerialContext *ctx) +{ + if (ctx->eSerialMode == SERIALMODE_SERIALIZE) + { + if (eCompMode == COMPMODE_EQUALS && bVal == bCompVal) + { + StrCatCh(szMutStr, CH_XH_SEGEND); + } + else + { + char szTmp[NEO_XHAIR_SEQMAX]; + V_sprintf_safe(szTmp, "%d%c", static_cast(bVal), CH_XH_SEGEND); + V_strcat_safe(szMutStr, szTmp); + } + return bVal; + } + else + { + return DeserialVariant(szMutStr, SERIALVARIANTTYPE_BOOL, + { .bVal = (eCompMode == COMPMODE_EQUALS) ? bCompVal : bVal }, + {}, {}, ctx).bVal; + } +} + +[[nodiscard]] float SerialFloat(const float flVal, const float flCompVal, + const ECompMode eCompMode, char (&szMutStr)[NEO_XHAIR_SEQMAX], SerialContext *ctx, + const float flMin, const float flMax) +{ + if (ctx->eSerialMode == SERIALMODE_SERIALIZE) + { + if (eCompMode == COMPMODE_EQUALS && flVal == flCompVal) + { + StrCatCh(szMutStr, CH_XH_SEGEND); + } + else + { + char szTmp[NEO_XHAIR_SEQMAX]; + V_sprintf_safe(szTmp, "%.3f%c", (flMin < flMax) ? clamp(flVal, flMin, flMax) : flVal, CH_XH_SEGEND); + V_strcat_safe(szMutStr, szTmp); + } + return flVal; + } + else + { + return DeserialVariant(szMutStr, SERIALVARIANTTYPE_FLOAT, + { .flVal = (eCompMode == COMPMODE_EQUALS) ? flCompVal : flVal }, + { .flVal = flMin }, { .flVal = flMax }, ctx).flVal; + } +} + +void SerialRLEncode(char (&szMutSeq)[NEO_XHAIR_SEQMAX], const ESerialMode eSerialMode) +{ + // NEO NOTE (nullsystem): This RLE doesn't "properly" decode/encode right from the + // first character. But in the general case it'll never really hit that scenario + // as typically you'll need some properly values to be serialize before the whole + // empty segments + run-length encoding becomes useful anyway. + static constexpr char SZ_XH_MINSEGCOMPRESS[] = ";;;;"; + + if (eSerialMode != SERIALMODE_SERIALIZE) + { + return; + } + + char szFinalSeq[NEO_XHAIR_SEQMAX] = {}; + char *pszToRLE = nullptr; + int iOffset = 0; + const int iSzMutSeqSize = V_strlen(szMutSeq); + + while ((pszToRLE = V_strstr(szMutSeq + iOffset, SZ_XH_MINSEGCOMPRESS))) + { + // NEO NOTE (nullsystem): ";;;;" is already 3 segments (after the first), + // so start counting from the character after that (in index 4) + // It's also the minimal to take use of RLE as ";;;;" -> ";3^" saving 1 byte, + // greater saving the greater the repeats. + int iLen = 3; + while (((iOffset + iLen + 1) < iSzMutSeqSize) && (pszToRLE[iLen + 1] == CH_XH_SEGEND)) + { + ++iLen; + } + const int iPszToRLEPos = pszToRLE - szMutSeq; + if (iPszToRLEPos > iOffset) + { + V_strncat(szFinalSeq, szMutSeq + iOffset, NEO_XHAIR_SEQMAX, iPszToRLEPos - iOffset + 1); + } + { + // iPszToRLEPos == 0 never really going to happen for crosshair, but deal + // with the edge case anyway + char szTmp[NEO_XHAIR_SEQMAX]; + V_sprintf_safe(szTmp, "%s%d%c", (iPszToRLEPos == 0) ? ";" : "", iLen, CH_XH_SEGSKIP); + V_strcat_safe(szFinalSeq, szTmp); + } + iOffset = iPszToRLEPos + iLen + 1; + } + if (iOffset < iSzMutSeqSize) + { + V_strcat_safe(szFinalSeq, szMutSeq + iOffset); + } + + V_strcpy_safe(szMutSeq, szFinalSeq); +} + diff --git a/src/game/shared/neo/neo_serial.h b/src/game/shared/neo/neo_serial.h new file mode 100644 index 000000000..a832de440 --- /dev/null +++ b/src/game/shared/neo/neo_serial.h @@ -0,0 +1,44 @@ +#pragma once + +// Requires neo_crosshair.h included before this for NEO_XHAIR_SEQMAX + +enum ESerialMode +{ + SERIALMODE_DESERIALIZE = 0, + SERIALMODE_SERIALIZE, + SERIALMODE_CHECK, // SERIALMODE_DESERIALIZE but checks for bounds +}; + +// Value comparison mode +enum ECompMode +{ + COMPMODE_IGNORE = 0, + + // Serialize: Serial empty if it matches flCompVal + // Deserialize: Deserialize take flCompVal if seen empty + COMPMODE_EQUALS, +}; + +struct SerialContext +{ + ESerialMode eSerialMode; + int iSeqSize; + int idx; + int iSkipIdx; + bool bOutOfBound; +}; + +[[nodiscard]] int SerialInt(const int iVal, const int iCompVal, + const ECompMode eCompMode, char (&szMutStr)[NEO_XHAIR_SEQMAX], SerialContext *ctx, + const int iMin = 0, const int iMax = 0); + +[[nodiscard]] bool SerialBool(const bool bVal, const bool bCompVal, + const ECompMode eCompMode, char (&szMutStr)[NEO_XHAIR_SEQMAX], SerialContext *ctx); + +[[nodiscard]] float SerialFloat(const float flVal, const float flCompVal, + const ECompMode eCompMode, char (&szMutStr)[NEO_XHAIR_SEQMAX], SerialContext *ctx, + const float flMin = 0.0f, const float flMax = 0.0f); + +// 2nd pass - Serialization run length encoding on empty +void SerialRLEncode(char (&szMutSeq)[NEO_XHAIR_SEQMAX], const ESerialMode eSerialMode); + diff --git a/src/game/shared/neo/weapons/neo_weapon_types.h b/src/game/shared/neo/weapons/neo_weapon_types.h new file mode 100644 index 000000000..04cb5e4a1 --- /dev/null +++ b/src/game/shared/neo/weapons/neo_weapon_types.h @@ -0,0 +1,18 @@ +#pragma once + +// NEO NOTE (nullsystem): If this is altered and may need to alter +// for crosshair, check MAP_WEAPON_TYPE_TO_XHAIR in neo_crosshair.h +enum ENeoWeaponType +{ + WEP_TYPE_NIL = 0, + WEP_TYPE_THROWABLE, + WEP_TYPE_PISTOL, + WEP_TYPE_SMG, + WEP_TYPE_SHOTGUN, + WEP_TYPE_RIFLE, + WEP_TYPE_MACHINEGUN, + WEP_TYPE_SNIPER, + + WEP_TYPE__TOTAL, +}; + diff --git a/src/game/shared/neo/weapons/weapon_aa13.h b/src/game/shared/neo/weapons/weapon_aa13.h index 073fc5103..6dfd92101 100644 --- a/src/game/shared/neo/weapons/weapon_aa13.h +++ b/src/game/shared/neo/weapons/weapon_aa13.h @@ -25,6 +25,7 @@ class CWeaponAA13 : public CNEOBaseCombatWeapon virtual void PrimaryAttack(void) OVERRIDE; + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_AA13; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const OVERRIDE { return NEO_WEP_AA13 | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE { return 20; } diff --git a/src/game/shared/neo/weapons/weapon_balc.h b/src/game/shared/neo/weapons/weapon_balc.h index d43f58c2a..668cef48d 100644 --- a/src/game/shared/neo/weapons/weapon_balc.h +++ b/src/game/shared/neo/weapons/weapon_balc.h @@ -38,6 +38,7 @@ class CWeaponBALC : public CNEOBaseCombatWeapon virtual void Spawn() override; virtual void PrimaryAttack() override; virtual void SecondaryAttack() override; + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_BALC; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_BALC | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 20; } virtual void ItemPostFrame() override; diff --git a/src/game/shared/neo/weapons/weapon_bits.cpp b/src/game/shared/neo/weapons/weapon_bits.cpp new file mode 100644 index 000000000..8099c8f64 --- /dev/null +++ b/src/game/shared/neo/weapons/weapon_bits.cpp @@ -0,0 +1,13 @@ +#include "weapon_bits.h" + +#define DEFINE_WEAPON_TYPE(_x1, _x2, _x3, weptype) weptype, +#define DEFINE_WEAPON_TYPE_ALT(_x1, _x2, _x3, _x4, weptype) weptype, + +const ENeoWeaponType NEO_WEAPON_TYPE[NEO_WIDX__TOTAL] = { + WEP_TYPE_NIL, // 0 = invalid, set none + FOR_LIST_WEAPONS(DEFINE_WEAPON_TYPE, DEFINE_WEAPON_TYPE_ALT) +#ifdef INCLUDE_WEP_PBK + WEP_TYPE_MACHINEGUN, +#endif +}; + diff --git a/src/game/shared/neo/weapons/weapon_bits.h b/src/game/shared/neo/weapons/weapon_bits.h index a5829dee0..f95d29d88 100644 --- a/src/game/shared/neo/weapons/weapon_bits.h +++ b/src/game/shared/neo/weapons/weapon_bits.h @@ -1,40 +1,43 @@ #pragma once +#include "neo_weapon_types.h" + +// Upper name Lower name Detailed name, (X_ALT) alt name Weapon type #define FOR_LIST_WEAPONS(X, X_ALT) \ - X(AA13, aa13, "AA13") \ - X(BALC, balc, "BALC3") \ - X(DETPACK, detpack, "Detpack") \ - X(GHOST, ghost, "Ghost") \ - X(FRAG_GRENADE, grenade, "Grenade") \ - X(JITTE, jitte, "Jitte") \ - X_ALT(JITTE_S, jittes, "Jitte (with scope)", "jittescoped") \ - X(KNIFE, knife, "Knife") \ - X(KYLA, kyla, "Kyla") \ - X_ALT(M41, mosok, "Mosok", "m41") \ - X_ALT(M41_L, mosokl, "Mosok Silenced", "m41s") \ - X_ALT(M41_S, mosoks, "Mosok Scoped", "m41s") \ - X(MILSO, milso, "Milso") \ - X(MPN, mpn_unsilenced, "MPN45 Unsilenced") \ - X(MPN_S, mpn, "MPN45") \ - X(MX, mx, "MX") \ - X_ALT(MX_S, mxs, "MX Silenced", "mx_silenced") \ - X(PROX_MINE, proxmine, "Proximity Mine") \ - X(PZ, pz, "PZ252") \ - X(SMAC, smac, "SMAC") \ - X(SMOKE_GRENADE, smoke, "Smoke Grenade") \ - X(SRM, srm, "SRM") \ - X_ALT(SRM_S, srms, "SRM (silenced)", "srm_s") \ - X(SRS, srs, "SRS") \ - X(SUPA7, supa7, "Murata Supa-7") \ - X(TACHI, tachi, "Tachi") \ - X(ZR68_C, zr68c, "ZR68C") \ - X(ZR68_L, zr68l, "ZR68-L (accurized)") \ - X(ZR68_S, zr68s, "ZR68-S (silenced)") \ - X(SCOPEDWEAPON, scopedwep, "") \ - X(THROWABLE, throwable, "") \ - X(SUPPRESSED, suppressed, "") \ - X(FIREARM, firearm, "") \ - X(EXPLOSIVE, explosive, "") + X(AA13, aa13, "AA13", WEP_TYPE_SHOTGUN) \ + X(BALC, balc, "BALC3", WEP_TYPE_MACHINEGUN) \ + X(DETPACK, detpack, "Detpack", WEP_TYPE_THROWABLE) \ + X(GHOST, ghost, "Ghost", WEP_TYPE_NIL) \ + X(FRAG_GRENADE, grenade, "Grenade", WEP_TYPE_THROWABLE) \ + X(JITTE, jitte, "Jitte", WEP_TYPE_SMG) \ + X_ALT(JITTE_S, jittes, "Jitte (with scope)", "jittescoped",WEP_TYPE_SMG) \ + X(KNIFE, knife, "Knife", WEP_TYPE_NIL) \ + X(KYLA, kyla, "Kyla", WEP_TYPE_PISTOL) \ + X_ALT(M41, mosok, "Mosok", "m41", WEP_TYPE_RIFLE) \ + X_ALT(M41_L, mosokl, "Mosok Silenced", "m41s", WEP_TYPE_RIFLE) \ + X_ALT(M41_S, mosoks, "Mosok Scoped", "m41s", WEP_TYPE_RIFLE) \ + X(MILSO, milso, "Milso", WEP_TYPE_PISTOL) \ + X(MPN, mpn_unsilenced, "MPN45 Unsilenced", WEP_TYPE_SMG) \ + X(MPN_S, mpn, "MPN45", WEP_TYPE_SMG) \ + X(MX, mx, "MX", WEP_TYPE_RIFLE) \ + X_ALT(MX_S, mxs, "MX Silenced", "mx_silenced", WEP_TYPE_RIFLE) \ + X(PROX_MINE, proxmine, "Proximity Mine", WEP_TYPE_NIL) \ + X(PZ, pz, "PZ252", WEP_TYPE_MACHINEGUN) \ + X(SMAC, smac, "SMAC", WEP_TYPE_SMG) \ + X(SMOKE_GRENADE,smoke, "Smoke Grenade", WEP_TYPE_THROWABLE) \ + X(SRM, srm, "SRM", WEP_TYPE_SMG) \ + X_ALT(SRM_S, srms, "SRM (silenced)", "srm_s", WEP_TYPE_SMG) \ + X(SRS, srs, "SRS", WEP_TYPE_SNIPER) \ + X(SUPA7, supa7, "Murata Supa-7", WEP_TYPE_SHOTGUN) \ + X(TACHI, tachi, "Tachi", WEP_TYPE_PISTOL) \ + X(ZR68_C, zr68c, "ZR68C", WEP_TYPE_RIFLE) \ + X(ZR68_L, zr68l, "ZR68-L (accurized)", WEP_TYPE_SNIPER) \ + X(ZR68_S, zr68s, "ZR68-S (silenced)", WEP_TYPE_RIFLE) \ + X(SCOPEDWEAPON, scopedwep, "", WEP_TYPE_NIL) \ + X(THROWABLE, throwable, "", WEP_TYPE_NIL) \ + X(SUPPRESSED, suppressed, "", WEP_TYPE_NIL) \ + X(FIREARM, firearm, "", WEP_TYPE_NIL) \ + X(EXPLOSIVE, explosive, "", WEP_TYPE_NIL) // SCOPEDWEAPON - Scoped weapons should OR this in their flags. // THROWABLE - Generic for grenades @@ -70,6 +73,8 @@ enum NeoWepBits : NEO_WEP_BITS_UNDERLYING_TYPE #endif }; +extern const ENeoWeaponType NEO_WEAPON_TYPE[NEO_WIDX__TOTAL]; + // Some other related type safety checks also rely on this equaling zero. static_assert(NEO_WEP_INVALID == 0); diff --git a/src/game/shared/neo/weapons/weapon_detpack.h b/src/game/shared/neo/weapons/weapon_detpack.h index ad7c643c7..0a7938c6c 100644 --- a/src/game/shared/neo/weapons/weapon_detpack.h +++ b/src/game/shared/neo/weapons/weapon_detpack.h @@ -34,6 +34,7 @@ class CWeaponDetpack : public CNEOBaseProjectile CWeaponDetpack(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_DETPACK; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const { return NEO_WEP_DETPACK | NEO_WEP_THROWABLE | NEO_WEP_EXPLOSIVE; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE; diff --git a/src/game/shared/neo/weapons/weapon_ghost.h b/src/game/shared/neo/weapons/weapon_ghost.h index 661c054a7..dffbe25ca 100644 --- a/src/game/shared/neo/weapons/weapon_ghost.h +++ b/src/game/shared/neo/weapons/weapon_ghost.h @@ -68,6 +68,7 @@ class CWeaponGhost : public CNEOBaseCombatWeapon bool CanBePickedUpByClass(int classId) OVERRIDE; virtual bool CanAim() final { return false; } + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_GHOST; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const { return NEO_WEP_GHOST; } virtual int GetNeoWepXPCost(const int neoClass) const { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_grenade.h b/src/game/shared/neo/weapons/weapon_grenade.h index 77bad1b71..68f0acbd9 100644 --- a/src/game/shared/neo/weapons/weapon_grenade.h +++ b/src/game/shared/neo/weapons/weapon_grenade.h @@ -40,6 +40,7 @@ class CWeaponGrenade : public CNEOBaseProjectile CWeaponGrenade(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_FRAG_GRENADE; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const { return NEO_WEP_FRAG_GRENADE | NEO_WEP_THROWABLE | NEO_WEP_EXPLOSIVE; } virtual float GetSpeedScale(void) const OVERRIDE { return 0.85f; } diff --git a/src/game/shared/neo/weapons/weapon_jitte.h b/src/game/shared/neo/weapons/weapon_jitte.h index 2ab7757c1..aa61a70b7 100644 --- a/src/game/shared/neo/weapons/weapon_jitte.h +++ b/src/game/shared/neo/weapons/weapon_jitte.h @@ -33,6 +33,7 @@ class CWeaponJitte : public CNEOBaseCombatWeapon CWeaponJitte(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_JITTE; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const OVERRIDE { return NEO_WEP_JITTE | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_jittes.h b/src/game/shared/neo/weapons/weapon_jittes.h index bef3553eb..aa39c00de 100644 --- a/src/game/shared/neo/weapons/weapon_jittes.h +++ b/src/game/shared/neo/weapons/weapon_jittes.h @@ -33,6 +33,7 @@ class CWeaponJitteS : public CNEOBaseCombatWeapon CWeaponJitteS(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_JITTE_S; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const OVERRIDE { return NEO_WEP_JITTE | NEO_WEP_SUPPRESSED | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_knife.h b/src/game/shared/neo/weapons/weapon_knife.h index 13346eefc..0ffe8957c 100644 --- a/src/game/shared/neo/weapons/weapon_knife.h +++ b/src/game/shared/neo/weapons/weapon_knife.h @@ -54,6 +54,7 @@ class CWeaponKnife : public CNEOBaseCombatWeapon virtual Activity GetPrimaryAttackActivity() final { return ACT_VM_HITCENTER; } virtual Activity GetSecondaryAttackActivity() final { return ACT_VM_HITCENTER2; } virtual float GetSpeedScale() const OVERRIDE { return 0.85f; } + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_KNIFE; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits() const { return NEO_WEP_KNIFE; } protected: diff --git a/src/game/shared/neo/weapons/weapon_kyla.h b/src/game/shared/neo/weapons/weapon_kyla.h index 35c1bbf29..c73aab9a0 100644 --- a/src/game/shared/neo/weapons/weapon_kyla.h +++ b/src/game/shared/neo/weapons/weapon_kyla.h @@ -23,6 +23,7 @@ class CWeaponKyla : public CNEOBaseCombatWeapon CWeaponKyla(void); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_KYLA; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_KYLA | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_m41.h b/src/game/shared/neo/weapons/weapon_m41.h index 2b678256b..611d29448 100644 --- a/src/game/shared/neo/weapons/weapon_m41.h +++ b/src/game/shared/neo/weapons/weapon_m41.h @@ -33,6 +33,7 @@ class CWeaponM41 : public CNEOBaseCombatWeapon CWeaponM41(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_M41; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_M41 | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_m41l.h b/src/game/shared/neo/weapons/weapon_m41l.h index c6ea785e0..ce06491a3 100644 --- a/src/game/shared/neo/weapons/weapon_m41l.h +++ b/src/game/shared/neo/weapons/weapon_m41l.h @@ -33,6 +33,7 @@ class CWeaponM41L : public CNEOBaseCombatWeapon CWeaponM41L(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_M41_L; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const OVERRIDE { return NEO_WEP_M41_L | NEO_WEP_SCOPEDWEAPON | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_m41s.h b/src/game/shared/neo/weapons/weapon_m41s.h index 5eff3cae3..d21c239fd 100644 --- a/src/game/shared/neo/weapons/weapon_m41s.h +++ b/src/game/shared/neo/weapons/weapon_m41s.h @@ -33,6 +33,7 @@ class CWeaponM41S : public CNEOBaseCombatWeapon CWeaponM41S(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_M41_S; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const OVERRIDE { return NEO_WEP_M41_S | NEO_WEP_SUPPRESSED | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_milso.h b/src/game/shared/neo/weapons/weapon_milso.h index df0f200f6..a001beb23 100644 --- a/src/game/shared/neo/weapons/weapon_milso.h +++ b/src/game/shared/neo/weapons/weapon_milso.h @@ -33,6 +33,7 @@ class CWeaponMilso : public CNEOBaseCombatWeapon CWeaponMilso(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_MILSO; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_MILSO | NEO_WEP_SUPPRESSED | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_mpn.h b/src/game/shared/neo/weapons/weapon_mpn.h index 8212ecf38..899f2c109 100644 --- a/src/game/shared/neo/weapons/weapon_mpn.h +++ b/src/game/shared/neo/weapons/weapon_mpn.h @@ -33,6 +33,7 @@ class CWeaponMPN : public CNEOBaseCombatWeapon CWeaponMPN(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_MPN; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const OVERRIDE { return NEO_WEP_MPN | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_mpns.h b/src/game/shared/neo/weapons/weapon_mpns.h index 565fab0b4..2dbb0ee02 100644 --- a/src/game/shared/neo/weapons/weapon_mpns.h +++ b/src/game/shared/neo/weapons/weapon_mpns.h @@ -33,6 +33,7 @@ class CWeaponMPN_S : public CNEOBaseCombatWeapon CWeaponMPN_S(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_MPN_S; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_MPN_S | NEO_WEP_SUPPRESSED | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_mx.h b/src/game/shared/neo/weapons/weapon_mx.h index 027b2edd7..40f667ba9 100644 --- a/src/game/shared/neo/weapons/weapon_mx.h +++ b/src/game/shared/neo/weapons/weapon_mx.h @@ -33,6 +33,7 @@ class CWeaponMX : public CNEOBaseCombatWeapon CWeaponMX(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_MX; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_MX | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_mxs.h b/src/game/shared/neo/weapons/weapon_mxs.h index 1ae8ecd07..2b46c7a98 100644 --- a/src/game/shared/neo/weapons/weapon_mxs.h +++ b/src/game/shared/neo/weapons/weapon_mxs.h @@ -33,6 +33,7 @@ class CWeaponMX_S : public CNEOBaseCombatWeapon CWeaponMX_S(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_MX_S; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_MX_S | NEO_WEP_SUPPRESSED | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index 4f459615a..575682e9f 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -1145,7 +1145,7 @@ void CNEOBaseCombatWeapon::DrawCrosshair() if (GetWpnData().iconCrosshair) { - crosshair->SetCrosshair(GetWpnData().iconCrosshair, crosshair->m_crosshairInfo.color); + crosshair->SetCrosshair(GetWpnData().iconCrosshair, crosshair->m_crosshairInfo.wep[CROSSHAIR_WEP_DEFAULT].color); } else { diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index d00e539f9..c72b686d4 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -140,6 +140,7 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon virtual bool CanBeSelected(void) override; + virtual NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const { Assert(false); return NEO_WIDX_INVALID; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const { Assert(false); return NEO_WEP_INVALID; } // Should never call this base class; implement in children. virtual int GetNeoWepXPCost(const int neoClass) const { Assert(false); return 0; } // Should never call this base class; implement in children. diff --git a/src/game/shared/neo/weapons/weapon_pz.h b/src/game/shared/neo/weapons/weapon_pz.h index e75d1044e..a56a4f043 100644 --- a/src/game/shared/neo/weapons/weapon_pz.h +++ b/src/game/shared/neo/weapons/weapon_pz.h @@ -33,6 +33,7 @@ class CWeaponPZ : public CNEOBaseCombatWeapon CWeaponPZ(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_PZ; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_PZ | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 20; } diff --git a/src/game/shared/neo/weapons/weapon_smac.h b/src/game/shared/neo/weapons/weapon_smac.h index 24fb87409..07f214b64 100644 --- a/src/game/shared/neo/weapons/weapon_smac.h +++ b/src/game/shared/neo/weapons/weapon_smac.h @@ -33,6 +33,7 @@ class CWeaponSMAC : public CNEOBaseCombatWeapon CWeaponSMAC(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_SMAC; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_SMAC | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_smokegrenade.h b/src/game/shared/neo/weapons/weapon_smokegrenade.h index c2aef3f5e..5e0f01a17 100644 --- a/src/game/shared/neo/weapons/weapon_smokegrenade.h +++ b/src/game/shared/neo/weapons/weapon_smokegrenade.h @@ -39,6 +39,7 @@ class CWeaponSmokeGrenade : public CNEOBaseProjectile CWeaponSmokeGrenade(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_SMOKE_GRENADE; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const { return NEO_WEP_SMOKE_GRENADE | NEO_WEP_THROWABLE; } virtual float GetSpeedScale(void) const OVERRIDE { return 0.85f; } diff --git a/src/game/shared/neo/weapons/weapon_srm.h b/src/game/shared/neo/weapons/weapon_srm.h index f559545d2..6ccd24ae9 100644 --- a/src/game/shared/neo/weapons/weapon_srm.h +++ b/src/game/shared/neo/weapons/weapon_srm.h @@ -33,6 +33,7 @@ class CWeaponSRM : public CNEOBaseCombatWeapon CWeaponSRM(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_SRM; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_SRM | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_srms.h b/src/game/shared/neo/weapons/weapon_srms.h index 97782fe28..d7f5bc846 100644 --- a/src/game/shared/neo/weapons/weapon_srms.h +++ b/src/game/shared/neo/weapons/weapon_srms.h @@ -33,6 +33,7 @@ class CWeaponSRM_S : public CNEOBaseCombatWeapon CWeaponSRM_S(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_SRM_S; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_SRM_S | NEO_WEP_SUPPRESSED | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_srs.h b/src/game/shared/neo/weapons/weapon_srs.h index 127a40e1a..b6c632f15 100644 --- a/src/game/shared/neo/weapons/weapon_srs.h +++ b/src/game/shared/neo/weapons/weapon_srs.h @@ -38,6 +38,7 @@ class CWeaponSRS : public CNEOBaseCombatWeapon virtual void PrimaryAttack() override; virtual bool Reload(void) OVERRIDE; + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_SRS; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_SRS | NEO_WEP_SCOPEDWEAPON | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 20; } diff --git a/src/game/shared/neo/weapons/weapon_supa7.h b/src/game/shared/neo/weapons/weapon_supa7.h index 1b9ec61bc..f585f1580 100644 --- a/src/game/shared/neo/weapons/weapon_supa7.h +++ b/src/game/shared/neo/weapons/weapon_supa7.h @@ -34,6 +34,7 @@ class CWeaponSupa7 : public CNEOBaseCombatWeapon CWeaponSupa7(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_SUPA7; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const OVERRIDE { return NEO_WEP_SUPA7 | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_tachi.h b/src/game/shared/neo/weapons/weapon_tachi.h index f176ae130..3e8cf4e4d 100644 --- a/src/game/shared/neo/weapons/weapon_tachi.h +++ b/src/game/shared/neo/weapons/weapon_tachi.h @@ -47,6 +47,7 @@ class CWeaponTachi : public CNEOBaseCombatWeapon virtual void ForceSetFireMode( Tachi::Firemode primaryMode, bool bPlaySound = false, float flSoonestSwitch = 0.0f ); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_TACHI; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_TACHI | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_zr68c.h b/src/game/shared/neo/weapons/weapon_zr68c.h index 90e16ed0d..83f2c4899 100644 --- a/src/game/shared/neo/weapons/weapon_zr68c.h +++ b/src/game/shared/neo/weapons/weapon_zr68c.h @@ -33,6 +33,7 @@ class CWeaponZR68C : public CNEOBaseCombatWeapon CWeaponZR68C(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_ZR68_C; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_ZR68_C | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_zr68l.h b/src/game/shared/neo/weapons/weapon_zr68l.h index 5f4a077fc..a304c3131 100644 --- a/src/game/shared/neo/weapons/weapon_zr68l.h +++ b/src/game/shared/neo/weapons/weapon_zr68l.h @@ -33,6 +33,7 @@ class CWeaponZR68L : public CNEOBaseCombatWeapon CWeaponZR68L(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_ZR68_L; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const override { return NEO_WEP_ZR68_L | NEO_WEP_SCOPEDWEAPON | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const override { return 0; } diff --git a/src/game/shared/neo/weapons/weapon_zr68s.h b/src/game/shared/neo/weapons/weapon_zr68s.h index 275fa90ae..7c3c06eb4 100644 --- a/src/game/shared/neo/weapons/weapon_zr68s.h +++ b/src/game/shared/neo/weapons/weapon_zr68s.h @@ -33,6 +33,7 @@ class CWeaponZR68S : public CNEOBaseCombatWeapon CWeaponZR68S(); + NEO_WEP_BITS_UNDERLYING_TYPE WeaponIndex() const override { return NEO_WIDX_ZR68_S; } virtual NEO_WEP_BITS_UNDERLYING_TYPE GetNeoWepBits(void) const OVERRIDE { return NEO_WEP_ZR68_S | NEO_WEP_SUPPRESSED | NEO_WEP_FIREARM; } virtual int GetNeoWepXPCost(const int neoClass) const OVERRIDE { return 0; } diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 000000000..e38cfef19 --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,171 @@ + +# To run all the unit tests: +# $ ctest --test-dir build/[PRESET]/tests/ --output-on-failure +# To run unit tests executable matching regex: +# $ ctest --test-dir build/[PRESET]/tests/ --output-on-failure -R [REGEX] +# To directly run the test executables: +# Windows: +# $ build\windows-[debug/release]\tests\test_.... +# Linux: +# $ LD_LIBRARY_PATH=lib/public/linux64/ ./build/linux-[debug/release]/tests/test_.... + +include(CTest) + +add_executable(test_neo_crosshair + test_neo_crosshair.cpp + test_util.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_crosshair.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_crosshair.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.h +) +add_executable(test_neo_serial + test_neo_serial.cpp + test_util.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_crosshair.h + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.cpp + ${CMAKE_SOURCE_DIR}/game/shared/neo/neo_serial.h +) + +set(TEST_EXE_LIST + test_neo_crosshair + test_neo_serial +) + +foreach (TEST_EXE ${TEST_EXE_LIST}) + add_test(NAME ${TEST_EXE} + COMMAND $) + + if (OS_LINUX) + set_property(TEST ${TEST_EXE} + PROPERTY + ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_SOURCE_DIR}/lib/public/linux64/" + ) + endif () + + target_compile_definitions(${TEST_EXE} + PRIVATE + UNIT_TEST_DLL + DBGFLAG_ASSERTFATAL + ) + + target_include_directories(${TEST_EXE} + PRIVATE + ${CMAKE_SOURCE_DIR}/common + ${CMAKE_SOURCE_DIR}/game/client + ${CMAKE_SOURCE_DIR}/game/shared + ${CMAKE_SOURCE_DIR}/game/shared/hl2 + ${CMAKE_SOURCE_DIR}/game/shared/hl2mp + ${CMAKE_SOURCE_DIR}/game/shared/Multiplayer + ${CMAKE_SOURCE_DIR}/game/shared/neo + ${CMAKE_SOURCE_DIR}/game/shared/neo/weapons + ${CMAKE_SOURCE_DIR}/public + ${CMAKE_SOURCE_DIR}/thirdparty/sixensesdk/include + ${CMAKE_SOURCE_DIR}/vendor + ${CMAKE_SOURCE_DIR}/game/client/NextBot + ) + + target_link_libraries(${TEST_EXE} + PRIVATE + appframework::appframework + bitmap::bitmap + mathlib::mathlib + choreo_objects::choreo_objects + dmx_loader::dmx_loader + tier0::tier0 + tier1::tier1 + tier2::tier2 + tier3::tier3 + matsys_controls::matsys_controls + vstdlib::vstdlib + particles::particles + vtf::vtf + steam_api::steam_api + vgui_controls::vgui_controls + ) + + target_link_directories(${TEST_EXE} + PRIVATE + ${LIBCOMMON} + ${LIBPUBLIC} + ) + + if(NEO_STAGING_ONLY) + target_compile_definitions(${TEST_EXE} PRIVATE STAGING_ONLY) + endif() + + if(NEO_RELEASE_ASSERTS) + target_compile_definitions(${TEST_EXE} PRIVATE RELEASEASSERTS) + endif() + + if(NEO_RETAIL) + target_compile_definitions(${TEST_EXE} PRIVATE RETAIL) + endif() + + if(OS_LINUX OR OS_MACOS) + target_compile_definitions(${TEST_EXE} + PRIVATE + GL_GLEXT_PROTOTYPES + DX_TO_GL_ABSTRACTION + USE_SDL + ) + endif() + + if(NEO_BUILD_WEAPON_PBK56S) + target_compile_definitions(${TEST_EXE} PRIVATE INCLUDE_WEP_PBK) + endif() + + if(OS_WINDOWS) + if(NEO_BUILD_REPLAY) + target_compile_definitions(${TEST_EXE} PRIVATE CURL_STATICLIB) + + target_link_libraries(${TEST_EXE} + PRIVATE + wsock32.lib + Ws2_32.lib + ) + endif() + + target_compile_definitions(${TEST_EXE} + PRIVATE + AVI_VIDEO + WMV_VIDEO + fopen=dont_use_fopen + PSAPI_VERSION=1 + ) + + target_link_libraries(${TEST_EXE} + PRIVATE + legacy_stdio_definitions.lib + #winmm.lib + libz + Psapi.lib + ) + endif() + + if(OS_LINUX) + target_compile_definitions(${TEST_EXE} + PRIVATE + LINUX + _LINUX + BINK_VIDEO + USE_WEBM_FOR_REPLAY + ) + + target_link_libraries(${TEST_EXE} + PRIVATE + #${CMAKE_SOURCE_DIR}/lib/common/linux64/libcrypto.a + #${CMAKE_SOURCE_DIR}/lib/common/linux64/libssl.a + #${LIBCOMMON}/libcurl.a + #${LIBCOMMON}/libcurlssl.a + vgui_controls::vgui_controls + dl + m + pthread + rt + z + GL + ) + endif() +endforeach (TEST_EXE) + diff --git a/src/tests/test_neo_crosshair.cpp b/src/tests/test_neo_crosshair.cpp new file mode 100644 index 000000000..6e0a3497d --- /dev/null +++ b/src/tests/test_neo_crosshair.cpp @@ -0,0 +1,1177 @@ +#include "neo_crosshair.h" +#include "test_util.h" + +#define CURRENT_VER "6" + +void TestDeserial_V1_PREALPHA_V8_2() +{ + // v1 - is actually non-existant, just a placeholder version + // for back when the crosshair wasn't string serialized + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, "1;"); + TEST_COMPARE_INT(bValid, false); + TEST_COMPARE_INT(false, ValidateCrosshairSerial("1;")); +} + +void TestDeserial_V2_ALPHA_V17() +{ + // v2 - First string serialized version and networked + static const char SERIAL_TEST_STR[] = "2;2;-1;0;3;0.000;2;4;1;6;1;5;7;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;3;2;4;1;6;5;7;0;-16777216;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->color.GetRawColor(), -1); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); + TEST_COMPARE_INT(chr->iSize, 3); + TEST_COMPARE_FLT(chr->flScrSize, 0.0f, 0.0001f); + TEST_COMPARE_INT(chr->iThick, 2); + TEST_COMPARE_INT(chr->iGap, 4); + TEST_COMPARE_INT(chr->iOutline, 1); + TEST_COMPARE_INT(chr->iCenterDot, 6); + TEST_COMPARE_INT(chr->iCircleRad, 5); + TEST_COMPARE_INT(chr->iCircleSegments, 7); + + // Test default values + TEST_COMPARE_INT(chr->eDynamicType, defChr->eDynamicType); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), defChr->colorDot.GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), defChr->colorDotOutline.GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), defChr->colorDotOutline.GetRawColor()); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V17); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestDeserial_V3_ALPHA_V19() +{ + // v3 - Added dynamic crosshair + static const char SERIAL_TEST_STR[] = "3;2;-1;0;3;0.000;2;4;1;6;1;5;7;1;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;3;2;4;1;6;5;7;1;-16777216;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->color.GetRawColor(), -1); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); + TEST_COMPARE_INT(chr->iSize, 3); + TEST_COMPARE_FLT(chr->flScrSize, 0.0f, 0.0001f); + TEST_COMPARE_INT(chr->iThick, 2); + TEST_COMPARE_INT(chr->iGap, 4); + TEST_COMPARE_INT(chr->iOutline, 1); + TEST_COMPARE_INT(chr->iCenterDot, 6); + TEST_COMPARE_INT(chr->iCircleRad, 5); + TEST_COMPARE_INT(chr->iCircleSegments, 7); + TEST_COMPARE_INT(chr->eDynamicType, CROSSHAIR_DYNAMICTYPE_GAP); + + // Test default values + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), defChr->colorDot.GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), defChr->colorDotOutline.GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), defChr->colorDotOutline.GetRawColor()); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V19); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestDeserial_V4_ALPHA_V22() +{ + // v4 - Separate colors for dots and outlines + static const char SERIAL_TEST_STR[] = "4;2;-1;0;3;0.000;2;4;1;6;1;5;7;1;1;-16776961;-16711936;-65536;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;2;2;-1;0;3;2;4;1;6;5;7;1;-16776961;-16711936;-65536;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->color.GetRawColor(), -1); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); + TEST_COMPARE_INT(chr->iSize, 3); + TEST_COMPARE_FLT(chr->flScrSize, 0.0f, 0.0001f); + TEST_COMPARE_INT(chr->iThick, 2); + TEST_COMPARE_INT(chr->iGap, 4); + TEST_COMPARE_INT(chr->iOutline, 1); + TEST_COMPARE_INT(chr->iCenterDot, 6); + TEST_COMPARE_INT(chr->iCircleRad, 5); + TEST_COMPARE_INT(chr->iCircleSegments, 7); + TEST_COMPARE_INT(chr->eDynamicType, CROSSHAIR_DYNAMICTYPE_GAP); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), Color(255, 0, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), Color(0, 255, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), Color(0, 0, 255, 255).GetRawColor()); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V22); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestDeserial_V5_ALPHA_V28() +{ + // v5 - Omits unused segments + static const char SERIAL_TEST_STR[] = "5;2;-1;0;3;2;4;1;6;1;5;7;1;1;-16776961;-16711936;-65536;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;2;2;-1;0;3;2;4;1;6;5;7;1;-16776961;-16711936;-65536;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->color.GetRawColor(), -1); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); + TEST_COMPARE_INT(chr->iSize, 3); + TEST_COMPARE_INT(chr->iThick, 2); + TEST_COMPARE_INT(chr->iGap, 4); + TEST_COMPARE_INT(chr->iOutline, 1); + TEST_COMPARE_INT(chr->iCenterDot, 6); + TEST_COMPARE_INT(chr->iCircleRad, 5); + TEST_COMPARE_INT(chr->iCircleSegments, 7); + TEST_COMPARE_INT(chr->eDynamicType, CROSSHAIR_DYNAMICTYPE_GAP); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), Color(255, 0, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), Color(0, 255, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), Color(0, 0, 255, 255).GetRawColor()); + + // Test default values + TEST_COMPARE_FLT(chr->flScrSize, defChr->flScrSize, 0.0001f); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V28); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestDeserial_V6_ALPHA_V29() +{ + // v6 - Secondary + shotgun crosshairs, and hipfire variant + // Further compact cross-segments by default's values + // Run-length encoding/decoding on empty segments + static const char SERIAL_TEST_STR[] = "6;0;0;2;2;-1;0;3;2;4;1;6;5;7;1;-16776961;-16711936;-65536;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;2;2;-1;0;3;2;4;1;6;5;7;1;-16776961;-16711936;-65536;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->color.GetRawColor(), -1); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); + TEST_COMPARE_INT(chr->iSize, 3); + TEST_COMPARE_INT(chr->iThick, 2); + TEST_COMPARE_INT(chr->iGap, 4); + TEST_COMPARE_INT(chr->iOutline, 1); + TEST_COMPARE_INT(chr->iCenterDot, 6); + TEST_COMPARE_INT(chr->iCircleRad, 5); + TEST_COMPARE_INT(chr->iCircleSegments, 7); + TEST_COMPARE_INT(chr->eDynamicType, CROSSHAIR_DYNAMICTYPE_GAP); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), Color(255, 0, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), Color(0, 255, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), Color(0, 0, 255, 255).GetRawColor()); + + // Test default values + TEST_COMPARE_FLT(chr->flScrSize, defChr->flScrSize, 0.0001f); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V29); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestSerial_LongestLength() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";31;7;3;2;-1;1;0.999;10;10;5;10;10;10;1;-1;-1;-1;2;;-16777217;0;11;11;11;4;11;11;11;2;-16777217;-16777217;-16777217;2;;-16777217;0;11;11;11;4;11;11;11;2;-16777217;-16777217;-16777217;2;;-16777217;0;11;11;11;4;11;11;11;2;-16777217;-16777217;-16777217;3;;-33554433;1;0.997;12;12;3;12;12;12;3;-33554433;-33554433;-33554433;3;;-33554433;1;0.997;12;12;3;12;12;12;3;-33554433;-33554433;-33554433;"; + + CrosshairInfo xhairInfo = {}; + xhairInfo.wepFlags = CROSSHAIR_WEP_FLAG_SECONDARY | CROSSHAIR_WEP_FLAG_SHOTGUN | CROSSHAIR_WEP_FLAG_DEFAULT_HIPFIRE | CROSSHAIR_WEP_FLAG_SECONDARY_HIPFIRE | CROSSHAIR_WEP_FLAG_SHOTGUN_HIPFIRE; + xhairInfo.hipfireFlags = CROSSHAIR_HIPFIRECUSTOM_FLAG_DEFAULT | CROSSHAIR_HIPFIRECUSTOM_FLAG_SECONDARY | CROSSHAIR_HIPFIRECUSTOM_FLAG_SHOTGUN; + + { + CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + chr->flags = CROSSHAIR_FLAG_NOTOPLINE | CROSSHAIR_FLAG_SEPERATEDOTCOLOR; + chr->iStyle = CROSSHAIR_STYLE_CUSTOM; + chr->color = Color(255, 255, 255, 255); + chr->eSizeType = CROSSHAIR_SIZETYPE_SCREEN; + chr->iSize = 10; + chr->flScrSize = 0.999; + chr->iThick = 10; + chr->iGap = 10; + chr->iOutline = 5; + chr->iCenterDot = 10; + chr->iCircleRad = 10; + chr->iCircleSegments = 10; + chr->eDynamicType = CROSSHAIR_DYNAMICTYPE_GAP; + chr->colorDot = Color(255, 255, 255, 255); + chr->colorDotOutline = Color(255, 255, 255, 255); + chr->colorOutline = Color(255, 255, 255, 255); + } + for (int i = (CROSSHAIR_WEP_DEFAULT + 1); i <= CROSSHAIR_WEP_DEFAULT_HIPFIRE; ++i) + { + CrosshairWepInfo *chr = &xhairInfo.wep[i]; + chr->flags = CROSSHAIR_FLAG_SEPERATEDOTCOLOR; + chr->iStyle = CROSSHAIR_STYLE_CUSTOM; + chr->color = Color(255, 255, 255, 254); + chr->eSizeType = CROSSHAIR_SIZETYPE_ABSOLUTE; + chr->iSize = 11; + chr->flScrSize = 0.998; + chr->iThick = 11; + chr->iGap = 11; + chr->iOutline = 4; + chr->iCenterDot = 11; + chr->iCircleRad = 11; + chr->iCircleSegments = 11; + chr->eDynamicType = CROSSHAIR_DYNAMICTYPE_CIRCLE; + chr->colorDot = Color(255, 255, 255, 254); + chr->colorDotOutline = Color(255, 255, 255, 254); + chr->colorOutline = Color(255, 255, 255, 254); + } + for (int i = (CROSSHAIR_WEP_DEFAULT_HIPFIRE + 1); i < CROSSHAIR_WEP__TOTAL; ++i) + { + CrosshairWepInfo *chr = &xhairInfo.wep[i]; + chr->flags = CROSSHAIR_FLAG_NOTOPLINE | CROSSHAIR_FLAG_SEPERATEDOTCOLOR; + chr->iStyle = CROSSHAIR_STYLE_CUSTOM; + chr->color = Color(255, 255, 255, 253); + chr->eSizeType = CROSSHAIR_SIZETYPE_SCREEN; + chr->iSize = 12; + chr->flScrSize = 0.997; + chr->iThick = 12; + chr->iGap = 12; + chr->iOutline = 3; + chr->iCenterDot = 12; + chr->iCircleRad = 12; + chr->iCircleSegments = 12; + chr->eDynamicType = CROSSHAIR_DYNAMICTYPE_SIZE; + chr->colorDot = Color(255, 255, 255, 253); + chr->colorDotOutline = Color(255, 255, 255, 253); + chr->colorOutline = Color(255, 255, 255, 253); + } + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + // Make sure it's not maxing out the string + TEST_VERIFY(V_strlen(szExportSeq) < (NEO_XHAIR_SEQMAX - 1 - 1)); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFailure_Invalid_SerialValues() +{ + CrosshairInfo xhairInfo = {}; + TEST_COMPARE_INT(ImportCrosshair(&xhairInfo, "0;"), false); + TEST_COMPARE_INT(ImportCrosshair(&xhairInfo, "-1;"), false); + TEST_COMPARE_INT(ImportCrosshair(&xhairInfo, "999999;"), false); + TEST_COMPARE_INT(ImportCrosshair(&xhairInfo, "-999999;"), false); +} + +void TestFailure_OutOfBoundStr_Under() +{ + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + TEST_COMPARE_INT(ImportCrosshair(&xhairInfo, "6;0;0;2;2;-1;0;3;2;4;1;6;5;"), true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + TEST_COMPARE_INT(chr->iCenterDot, 6); + TEST_COMPARE_INT(chr->iCircleRad, 5); + TEST_COMPARE_INT(chr->iCircleSegments, defChr->iCircleSegments); + TEST_COMPARE_INT(chr->eDynamicType, defChr->eDynamicType); +} + +void TestFailure_OutOfBoundStr_Over() +{ + CrosshairInfo xhairInfo = {}; + TEST_COMPARE_INT(ImportCrosshair(&xhairInfo, "6;0;0;2;2;-1;0;3;2;4;1;6;5;7;1;-16776961;-16711936;-65536;12;24;48;"), true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + TEST_COMPARE_INT(chr->iCenterDot, 6); + TEST_COMPARE_INT(chr->iCircleRad, 5); + TEST_COMPARE_INT(chr->iCircleSegments, 7); + TEST_COMPARE_INT(chr->eDynamicType, CROSSHAIR_DYNAMICTYPE_GAP); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), Color(255, 0, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), Color(0, 255, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), Color(0, 0, 255, 255).GetRawColor()); + + // Because both wepFlags and hipfireFlags are 0, the rest should really just be + // copies of the default segments, nothing will pick up the "12;24;48;" at the end + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); +} + +void TestFailure_OutOfBoundValues() +{ + static const char SERIAL_TEST_STR_OUTBOUND[] = CURRENT_VER ";0;0;2;99;-1;-2;101;123;-111;1000;999;121212;123;80;-16776961;-16711936;-65536;"; + static const char SERIAL_TEST_STR_FIXED[] = CURRENT_VER ";0;0;2;2;-1;0;100;25;0;5;25;50;50;3;-16776961;-16711936;-65536;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR_OUTBOUND); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); // 99 -> 2 + TEST_COMPARE_INT(chr->color.GetRawColor(), -1); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); // -2 -> 0 + TEST_COMPARE_INT(chr->iSize, CROSSHAIR_MAX_SIZE); // 101 -> 100 + TEST_COMPARE_INT(chr->iThick, CROSSHAIR_MAX_THICKNESS); // 123 -> 25 + TEST_COMPARE_INT(chr->iGap, 0); // -111 -> 0 + TEST_COMPARE_INT(chr->iOutline, CROSSHAIR_MAX_OUTLINE); // 1000 -> 5 + TEST_COMPARE_INT(chr->iCenterDot, CROSSHAIR_MAX_CENTER_DOT); // 999 -> 25 + TEST_COMPARE_INT(chr->iCircleRad, CROSSHAIR_MAX_CIRCLE_RAD); // 121212 -> 50 + TEST_COMPARE_INT(chr->iCircleSegments, CROSSHAIR_MAX_CIRCLE_SEGMENTS); // 123 -> 50 + TEST_COMPARE_INT(chr->eDynamicType, CROSSHAIR_DYNAMICTYPE_SIZE); // 80 -> 3 + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), Color(255, 0, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), Color(0, 255, 0, 255).GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), Color(0, 0, 255, 255).GetRawColor()); + + // Test default values + TEST_COMPARE_FLT(chr->flScrSize, defChr->flScrSize, 0.0001f); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR_FIXED); + + TEST_COMPARE_INT(true, ValidateCrosshairSerial(SERIAL_TEST_STR_FIXED)); + TEST_COMPARE_INT(false, ValidateCrosshairSerial(SERIAL_TEST_STR_OUTBOUND)); +} + +void TestFeature_Omit_Style_Default() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;0;-1;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_DEFAULT); + TEST_COMPARE_INT(chr->color.GetRawColor(), -1); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_Style_AltB() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;1;-1;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_ALT_B); + TEST_COMPARE_INT(chr->color.GetRawColor(), -1); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_SizeType_Absolute() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;5;0;0;0;0;0;0;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_ABSOLUTE); + TEST_COMPARE_INT(chr->iSize, 5); + TEST_COMPARE_FLT(chr->flScrSize, defChr->flScrSize, 0.0001f); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_SizeType_Screen() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;2;-1;1;0.333;0;0;0;0;0;0;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->eSizeType, CROSSHAIR_SIZETYPE_SCREEN); + TEST_COMPARE_INT(chr->iSize, defChr->iSize); + TEST_COMPARE_FLT(chr->flScrSize, 0.333f, 0.001f); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_CircleRad_Empty() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;6;0;0;0;1;0;1;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->iCircleRad, 0); + TEST_COMPARE_INT(chr->iCircleSegments, defChr->iCircleSegments); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_CircleRad_Used() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;6;0;0;0;1;6;8;1;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->iCircleRad, 6); + TEST_COMPARE_INT(chr->iCircleSegments, 8); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_Outline_Empty() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;6;0;0;0;1;0;1;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->iOutline, 0); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), defChr->colorOutline.GetRawColor()); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_Outline_Used() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;6;0;0;1;1;0;1;-2;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->iOutline, 1); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), -2); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_CenterDot_Empty() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;6;0;0;0;0;0;1;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->iOutline, 0); + TEST_COMPARE_INT(chr->iCenterDot, 0); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), defChr->colorDot.GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), defChr->colorDotOutline.GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), defChr->colorOutline.GetRawColor()); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_CenterDot_SepDotColor_Not() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;6;0;0;0;1;0;1;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + TEST_COMPARE_INT(chr->iOutline, 0); + TEST_COMPARE_INT(chr->iCenterDot, 1); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), defChr->colorDot.GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), defChr->colorDotOutline.GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), defChr->colorOutline.GetRawColor()); + + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_CenterDot_SepDotColor_NoOutline() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;2;2;-1;0;6;0;0;0;1;0;1;-2;"; + + CrosshairInfo xhairDef = {}; + ResetCrosshairToDefault(&xhairDef); + const CrosshairWepInfo *defChr = &xhairDef.wep[CROSSHAIR_WEP_DEFAULT]; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + TEST_COMPARE_INT(chr->iOutline, 0); + TEST_COMPARE_INT(chr->iCenterDot, 1); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), -2); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), defChr->colorDotOutline.GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), defChr->colorOutline.GetRawColor()); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Omit_CenterDot_SepDotColor_WithOutline() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;2;2;-1;0;6;0;0;2;1;0;1;-2;-3;-4;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + TEST_COMPARE_INT(chr->iOutline, 2); + TEST_COMPARE_INT(chr->iCenterDot, 1); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), -2); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), -3); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), -4); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Flags_OldBool_None() +{ + // Convert v4 (non-compressed, 2 bools) to v6+ (flagged) + static const char SERIAL_TEST_STR[] = "4;2;-1;0;3;0.000;2;4;1;6;1;5;7;1;0;-16776961;-16711936;-65536;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;0;2;-1;0;3;2;4;1;6;5;7;1;-65536;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_DEFAULT); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V22); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Flags_OldBool_ToplineOff() +{ + // Convert v4 (non-compressed, 2 bools) to v6+ (flagged) + static const char SERIAL_TEST_STR[] = "4;2;-1;0;3;0.000;2;4;1;6;0;5;7;1;0;-16776961;-16711936;-65536;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;1;2;-1;0;3;2;4;1;6;5;7;1;-65536;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_NOTOPLINE); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V22); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Flags_OldBool_SepDotColor() +{ + // Convert v4 (non-compressed, 2 bools) to v6+ (flagged) + static const char SERIAL_TEST_STR[] = "4;2;-1;0;3;0.000;2;4;1;6;1;5;7;1;1;-16776961;-16711936;-65536;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;2;2;-1;0;3;2;4;1;6;5;7;1;-16776961;-16711936;-65536;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V22); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Flags_OldBool_ToplineOff_SepDotColor() +{ + // Convert v4 (non-compressed, 2 bools) to v6+ (flagged) + static const char SERIAL_TEST_STR[] = "4;2;-1;0;3;0.000;2;4;1;6;0;5;7;1;1;-16776961;-16711936;-65536;"; + static const char SERIAL_TEST_LATEST_STR[] = CURRENT_VER ";0;0;3;2;-1;0;3;2;4;1;6;5;7;1;-16776961;-16711936;-65536;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_NOTOPLINE | CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SECONDARY_HIPFIRE], sizeof(CrosshairWepInfo))); + TEST_VERIFY(0 == V_memcmp(chr, &xhairInfo.wep[CROSSHAIR_WEP_SHOTGUN_HIPFIRE], sizeof(CrosshairWepInfo))); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_LATEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + ExportCrosshair(&xhairInfo, szExportSeq, NEOXHAIR_SERIAL_ALPHA_V22); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Flags_Topline_Off() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;1;2;-1;0;6;0;0;0;0;0;1;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_NOTOPLINE); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_Flags_Topline_Off_SepDotColor() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";0;0;3;2;-1;0;6;0;0;0;0;0;1;"; + + CrosshairInfo xhairInfo = {}; + const bool bValid = ImportCrosshair(&xhairInfo, SERIAL_TEST_STR); + TEST_COMPARE_INT(bValid, true); + TEST_COMPARE_INT(xhairInfo.wepFlags, CROSSHAIR_WEP_FLAG_DEFAULT); + TEST_COMPARE_INT(xhairInfo.hipfireFlags, CROSSHAIR_HIPFIRECUSTOM_FLAG_NIL); + + const CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + + // Test values that's from the string itself + TEST_COMPARE_INT(chr->iStyle, CROSSHAIR_STYLE_CUSTOM); + TEST_COMPARE_INT(chr->flags, CROSSHAIR_FLAG_NOTOPLINE | CROSSHAIR_FLAG_SEPERATEDOTCOLOR); + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); +} + +void TestFeature_RunLengthEncode_Full() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";31;7;3;2;-1;0;10;10;10;5;10;10;10;1;-1;-1;-1;75^"; + + CrosshairInfo xhairInfo = {}; + ResetCrosshairToDefault(&xhairInfo); + xhairInfo.wepFlags = CROSSHAIR_WEP_FLAG_SECONDARY | CROSSHAIR_WEP_FLAG_SHOTGUN | CROSSHAIR_WEP_FLAG_DEFAULT_HIPFIRE | CROSSHAIR_WEP_FLAG_SECONDARY_HIPFIRE | CROSSHAIR_WEP_FLAG_SHOTGUN_HIPFIRE; + xhairInfo.hipfireFlags = CROSSHAIR_HIPFIRECUSTOM_FLAG_DEFAULT | CROSSHAIR_HIPFIRECUSTOM_FLAG_SECONDARY | CROSSHAIR_HIPFIRECUSTOM_FLAG_SHOTGUN; + + for (int i = 0; i < CROSSHAIR_WEP__TOTAL; ++i) + { + CrosshairWepInfo *chr = &xhairInfo.wep[i]; + chr->flags = CROSSHAIR_FLAG_NOTOPLINE | CROSSHAIR_FLAG_SEPERATEDOTCOLOR; + chr->iStyle = CROSSHAIR_STYLE_CUSTOM; + chr->color = Color(255, 255, 255, 255); + chr->eSizeType = CROSSHAIR_SIZETYPE_ABSOLUTE; + chr->iSize = 10; + chr->iThick = 10; + chr->iGap = 10; + chr->iOutline = 5; + chr->iCenterDot = 10; + chr->iCircleRad = 10; + chr->iCircleSegments = 10; + chr->eDynamicType = CROSSHAIR_DYNAMICTYPE_GAP; + chr->colorDot = Color(255, 255, 255, 255); + chr->colorDotOutline = Color(255, 255, 255, 255); + chr->colorOutline = Color(255, 255, 255, 255); + } + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + CrosshairInfo xhairInfoImport = {}; + ImportCrosshair(&xhairInfoImport, szExportSeq); + TEST_COMPARE_INT(xhairInfoImport.wepFlags, xhairInfo.wepFlags); + TEST_COMPARE_INT(xhairInfoImport.hipfireFlags, xhairInfo.hipfireFlags); + for (int i = 0; i < CROSSHAIR_WEP__TOTAL; ++i) + { + const CrosshairWepInfo *chr = &xhairInfo.wep[i]; + const CrosshairWepInfo *chrImp = &xhairInfoImport.wep[i]; + TEST_COMPARE_INT(chr->flags, chrImp->flags); + TEST_COMPARE_INT(chr->iStyle, chrImp->iStyle); + TEST_COMPARE_INT(chr->color.GetRawColor(), chrImp->color.GetRawColor()); + TEST_COMPARE_INT(chr->eSizeType, chrImp->eSizeType); + TEST_COMPARE_INT(chr->iSize, chrImp->iSize); + TEST_COMPARE_INT(chr->iThick, chrImp->iThick); + TEST_COMPARE_INT(chr->iGap, chrImp->iGap); + TEST_COMPARE_INT(chr->iOutline, chrImp->iOutline); + TEST_COMPARE_INT(chr->iCenterDot, chrImp->iCenterDot); + TEST_COMPARE_INT(chr->iCircleRad, chrImp->iCircleRad); + TEST_COMPARE_INT(chr->iCircleSegments, chrImp->iCircleSegments); + TEST_COMPARE_INT(chr->eDynamicType, chrImp->eDynamicType); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), chrImp->colorDot.GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), chrImp->colorDotOutline.GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), chrImp->colorOutline.GetRawColor()); + } +} + +void TestFeature_RunLengthEncode_Partial() +{ + static const char SERIAL_TEST_STR[] = CURRENT_VER ";31;7;2;2;-1;1;0.999;10;10;5;10;10;10;1;-1;-1;-1;;;-16777217;0;10;;11;7^-16777217;;;-16777217;0;10;;11;7^-16777217;;;-16777217;0;10;;11;7^-16777217;;;-33554433;;12;;10;;12;;;3;-16777217;;-1;;;-33554433;;12;;10;;12;;;3;-16777217;;-1;"; + + CrosshairInfo xhairInfo = {}; + ResetCrosshairToDefault(&xhairInfo); + xhairInfo.wepFlags = CROSSHAIR_WEP_FLAG_SECONDARY | CROSSHAIR_WEP_FLAG_SHOTGUN | CROSSHAIR_WEP_FLAG_DEFAULT_HIPFIRE | CROSSHAIR_WEP_FLAG_SECONDARY_HIPFIRE | CROSSHAIR_WEP_FLAG_SHOTGUN_HIPFIRE; + xhairInfo.hipfireFlags = CROSSHAIR_HIPFIRECUSTOM_FLAG_DEFAULT | CROSSHAIR_HIPFIRECUSTOM_FLAG_SECONDARY | CROSSHAIR_HIPFIRECUSTOM_FLAG_SHOTGUN; + + { + CrosshairWepInfo *chr = &xhairInfo.wep[CROSSHAIR_WEP_DEFAULT]; + chr->flags = CROSSHAIR_FLAG_SEPERATEDOTCOLOR; + chr->iStyle = CROSSHAIR_STYLE_CUSTOM; + chr->color = Color(255, 255, 255, 255); + chr->eSizeType = CROSSHAIR_SIZETYPE_SCREEN; + chr->flScrSize = 0.999; + chr->iThick = 10; + chr->iGap = 10; + chr->iOutline = 5; + chr->iCenterDot = 10; + chr->iCircleRad = 10; + chr->iCircleSegments = 10; + chr->eDynamicType = CROSSHAIR_DYNAMICTYPE_GAP; + chr->colorDot = Color(255, 255, 255, 255); + chr->colorDotOutline = Color(255, 255, 255, 255); + chr->colorOutline = Color(255, 255, 255, 255); + } + for (int i = (CROSSHAIR_WEP_DEFAULT + 1); i <= CROSSHAIR_WEP_DEFAULT_HIPFIRE; ++i) + { + CrosshairWepInfo *chr = &xhairInfo.wep[i]; + chr->flags = CROSSHAIR_FLAG_SEPERATEDOTCOLOR; + chr->iStyle = CROSSHAIR_STYLE_CUSTOM; + chr->color = Color(255, 255, 255, 254); + chr->eSizeType = CROSSHAIR_SIZETYPE_ABSOLUTE; + chr->iSize = 10; + chr->iThick = 10; + chr->iGap = 11; + chr->iOutline = 5; + chr->iCenterDot = 10; + chr->iCircleRad = 10; + chr->iCircleSegments = 10; + chr->eDynamicType = CROSSHAIR_DYNAMICTYPE_GAP; + chr->colorDot = Color(255, 255, 255, 255); + chr->colorDotOutline = Color(255, 255, 255, 255); + chr->colorOutline = Color(255, 255, 255, 254); + } + for (int i = (CROSSHAIR_WEP_DEFAULT_HIPFIRE + 1); i < CROSSHAIR_WEP__TOTAL; ++i) + { + CrosshairWepInfo *chr = &xhairInfo.wep[i]; + chr->flags = CROSSHAIR_FLAG_SEPERATEDOTCOLOR; + chr->iStyle = CROSSHAIR_STYLE_CUSTOM; + chr->color = Color(255, 255, 255, 253); + chr->eSizeType = CROSSHAIR_SIZETYPE_ABSOLUTE; + chr->iSize = 12; + chr->iThick = 10; + chr->iGap = 10; + chr->iOutline = 5; + chr->iCenterDot = 12; + chr->iCircleRad = 10; + chr->iCircleSegments = 10; + chr->eDynamicType = CROSSHAIR_DYNAMICTYPE_SIZE; + chr->colorDot = Color(255, 255, 255, 254); + chr->colorDotOutline = Color(255, 255, 255, 255); + chr->colorOutline = Color(255, 255, 255, 255); + } + + char szExportSeq[NEO_XHAIR_SEQMAX]; + ExportCrosshair(&xhairInfo, szExportSeq); + TEST_COMPARE_STR(szExportSeq, SERIAL_TEST_STR); + TEST_COMPARE_INT(true, ValidateCrosshairSerial(szExportSeq)); + + CrosshairInfo xhairInfoImport = {}; + ImportCrosshair(&xhairInfoImport, szExportSeq); + TEST_COMPARE_INT(xhairInfoImport.wepFlags, xhairInfo.wepFlags); + TEST_COMPARE_INT(xhairInfoImport.hipfireFlags, xhairInfo.hipfireFlags); + for (int i = 0; i < CROSSHAIR_WEP__TOTAL; ++i) + { + const CrosshairWepInfo *chr = &xhairInfo.wep[i]; + const CrosshairWepInfo *chrImp = &xhairInfoImport.wep[i]; + TEST_COMPARE_INT(chr->flags, chrImp->flags); + TEST_COMPARE_INT(chr->iStyle, chrImp->iStyle); + TEST_COMPARE_INT(chr->color.GetRawColor(), chrImp->color.GetRawColor()); + TEST_COMPARE_INT(chr->eSizeType, chrImp->eSizeType); + if (chr->eSizeType == CROSSHAIR_SIZETYPE_ABSOLUTE) + { + TEST_COMPARE_INT(chr->iSize, chrImp->iSize); + } + else + { + TEST_COMPARE_FLT(chr->flScrSize, chrImp->flScrSize, 0.0001f); + } + TEST_COMPARE_INT(chr->iThick, chrImp->iThick); + TEST_COMPARE_INT(chr->iGap, chrImp->iGap); + TEST_COMPARE_INT(chr->iOutline, chrImp->iOutline); + TEST_COMPARE_INT(chr->iCenterDot, chrImp->iCenterDot); + TEST_COMPARE_INT(chr->iCircleRad, chrImp->iCircleRad); + TEST_COMPARE_INT(chr->iCircleSegments, chrImp->iCircleSegments); + TEST_COMPARE_INT(chr->eDynamicType, chrImp->eDynamicType); + TEST_COMPARE_INT(chr->colorDot.GetRawColor(), chrImp->colorDot.GetRawColor()); + TEST_COMPARE_INT(chr->colorDotOutline.GetRawColor(), chrImp->colorDotOutline.GetRawColor()); + TEST_COMPARE_INT(chr->colorOutline.GetRawColor(), chrImp->colorOutline.GetRawColor()); + } +} + +TEST_INIT() + TEST_RUN(TestDeserial_V1_PREALPHA_V8_2); + TEST_RUN(TestDeserial_V2_ALPHA_V17); + TEST_RUN(TestDeserial_V3_ALPHA_V19); + TEST_RUN(TestDeserial_V4_ALPHA_V22); + TEST_RUN(TestDeserial_V5_ALPHA_V28); + TEST_RUN(TestDeserial_V6_ALPHA_V29); + TEST_RUN(TestSerial_LongestLength); + TEST_RUN(TestFailure_Invalid_SerialValues); + TEST_RUN(TestFailure_OutOfBoundStr_Under); + TEST_RUN(TestFailure_OutOfBoundStr_Over); + TEST_RUN(TestFailure_OutOfBoundValues); + TEST_RUN(TestFeature_Omit_Style_Default); + TEST_RUN(TestFeature_Omit_Style_AltB); + TEST_RUN(TestFeature_Omit_SizeType_Absolute); + TEST_RUN(TestFeature_Omit_SizeType_Screen); + TEST_RUN(TestFeature_Omit_CircleRad_Empty); + TEST_RUN(TestFeature_Omit_CircleRad_Used); + TEST_RUN(TestFeature_Omit_Outline_Empty); + TEST_RUN(TestFeature_Omit_Outline_Used); + TEST_RUN(TestFeature_Omit_CenterDot_Empty); + TEST_RUN(TestFeature_Omit_CenterDot_SepDotColor_Not); + TEST_RUN(TestFeature_Omit_CenterDot_SepDotColor_NoOutline); + TEST_RUN(TestFeature_Omit_CenterDot_SepDotColor_WithOutline); + TEST_RUN(TestFeature_Flags_OldBool_None); + TEST_RUN(TestFeature_Flags_OldBool_ToplineOff); + TEST_RUN(TestFeature_Flags_OldBool_SepDotColor); + TEST_RUN(TestFeature_Flags_OldBool_ToplineOff_SepDotColor); + TEST_RUN(TestFeature_Flags_Topline_Off); + TEST_RUN(TestFeature_Flags_Topline_Off_SepDotColor); + TEST_RUN(TestFeature_RunLengthEncode_Full); + TEST_RUN(TestFeature_RunLengthEncode_Partial); +TEST_END() + diff --git a/src/tests/test_neo_serial.cpp b/src/tests/test_neo_serial.cpp new file mode 100644 index 000000000..2cdce2108 --- /dev/null +++ b/src/tests/test_neo_serial.cpp @@ -0,0 +1,280 @@ +#include "neo_crosshair.h" +#include "neo_serial.h" +#include "test_util.h" + +void TestDeserialInt() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = "0;2;30;20000;-5;-2;"; + SerialContext ctx = { + .eSerialMode = SERIALMODE_DESERIALIZE, + .iSeqSize = V_strlen(szMutStr), + }; + TEST_COMPARE_INT(0, SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(2, SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(10, SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx, 0, 10)); + TEST_COMPARE_INT(20000, SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(-5, SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(1, SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx, 1, 2)); +} + +void TestSerialInt() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = {}; + SerialContext ctx = { + .eSerialMode = SERIALMODE_SERIALIZE, + .iSeqSize = NEO_XHAIR_SEQMAX, + }; + int iVal = 0; + iVal = SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0;"); + iVal = SerialInt(2, 0, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0;2;"); + iVal = SerialInt(30, 0, COMPMODE_IGNORE, szMutStr, &ctx, 0, 10); + TEST_COMPARE_STR(szMutStr, "0;2;10;"); + iVal = SerialInt(20000, 0, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0;2;10;20000;"); + iVal = SerialInt(-5, 0, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0;2;10;20000;-5;"); + iVal = SerialInt(-2, 0, COMPMODE_IGNORE, szMutStr, &ctx, 1, 2); + TEST_COMPARE_STR(szMutStr, "0;2;10;20000;-5;1;"); +} + +void TestDeserialBool() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = "1;0;-1;5;0;"; + SerialContext ctx = { + .eSerialMode = SERIALMODE_DESERIALIZE, + .iSeqSize = V_strlen(szMutStr), + }; + TEST_COMPARE_INT(true, SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(false, SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(true, SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(true, SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(false, SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx)); +} + +void TestSerialBool() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = {}; + SerialContext ctx = { + .eSerialMode = SERIALMODE_SERIALIZE, + .iSeqSize = NEO_XHAIR_SEQMAX, + }; + bool bVal = 0; + bVal = SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0;"); + bVal = SerialBool(true, false, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0;1;"); + bVal = SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0;1;0;"); +} + +void TestDeserialFloat() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = "0.000;0.999;1;-1.0;-999.999;999.99;123.457;-2;"; + SerialContext ctx = { + .eSerialMode = SERIALMODE_DESERIALIZE, + .iSeqSize = V_strlen(szMutStr), + }; + TEST_COMPARE_FLT(0.0f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); + TEST_COMPARE_FLT(0.999f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); + TEST_COMPARE_FLT(1.0f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); + TEST_COMPARE_FLT(-1.0f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); + TEST_COMPARE_FLT(-999.999f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); + TEST_COMPARE_FLT(999.99f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); + TEST_COMPARE_FLT(123.457f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); + TEST_COMPARE_FLT(-2.0f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); +} + +void TestSerialFloat() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = {}; + SerialContext ctx = { + .eSerialMode = SERIALMODE_SERIALIZE, + .iSeqSize = NEO_XHAIR_SEQMAX, + }; + float flVal = 0; + flVal = SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0.000;"); + flVal = SerialFloat(0.999f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0.000;0.999;"); + flVal = SerialFloat(1.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0.000;0.999;1.000;"); + flVal = SerialFloat(-1.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0.000;0.999;1.000;-1.000;"); + flVal = SerialFloat(-999.999f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0.000;0.999;1.000;-1.000;-999.999;"); + flVal = SerialFloat(999.99f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0.000;0.999;1.000;-1.000;-999.999;999.990;"); + flVal = SerialFloat(123.4567f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0.000;0.999;1.000;-1.000;-999.999;999.990;123.457;"); + flVal = SerialFloat(2, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "0.000;0.999;1.000;-1.000;-999.999;999.990;123.457;2.000;"); +} + +void TestDeserialMixture() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = "1;0;12.24;-5;-2;"; + SerialContext ctx = { + .eSerialMode = SERIALMODE_DESERIALIZE, + .iSeqSize = V_strlen(szMutStr), + }; + TEST_COMPARE_INT(1, SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(false, SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_FLT(12.24f, SerialFloat(0.0f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx), 0.001f); + TEST_COMPARE_INT(-5, SerialInt(0, 0, COMPMODE_IGNORE, szMutStr, &ctx)); + TEST_COMPARE_INT(true, SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx)); +} + +void TestSerialMixture() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = {}; + SerialContext ctx = { + .eSerialMode = SERIALMODE_SERIALIZE, + .iSeqSize = NEO_XHAIR_SEQMAX, + }; + bool bVal = 0; + int iVal = 0; + float flVal = 0; + iVal = SerialInt(1, 0, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "1;"); + bVal = SerialBool(false, false, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "1;0;"); + flVal = SerialFloat(12.24f, 0.0f, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "1;0;12.240;"); + iVal = SerialInt(-5, 0, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "1;0;12.240;-5;"); + bVal = SerialBool(true, false, COMPMODE_IGNORE, szMutStr, &ctx); + TEST_COMPARE_STR(szMutStr, "1;0;12.240;-5;1;"); +} + +void TestDeserialEmpty() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = ";;;;"; + SerialContext ctx = { + .eSerialMode = SERIALMODE_DESERIALIZE, + .iSeqSize = V_strlen(szMutStr), + }; + TEST_COMPARE_INT(2, SerialInt(2, 2, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(true, SerialBool(true, true, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_FLT(12.24f, SerialFloat(12.24f, 12.24f, COMPMODE_EQUALS, szMutStr, &ctx), 0.001f); + TEST_COMPARE_INT(-5, SerialInt(-5, -5, COMPMODE_EQUALS, szMutStr, &ctx)); +} + +void TestSerialEmpty() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = {}; + SerialContext ctx = { + .eSerialMode = SERIALMODE_SERIALIZE, + .iSeqSize = NEO_XHAIR_SEQMAX, + }; + TEST_COMPARE_INT(2, SerialInt(2, 2, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_STR(szMutStr, ";"); + TEST_COMPARE_INT(true, SerialBool(true, true, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_STR(szMutStr, ";;"); + TEST_COMPARE_FLT(12.24f, SerialFloat(12.24f, 12.24f, COMPMODE_EQUALS, szMutStr, &ctx), 0.001f); + TEST_COMPARE_STR(szMutStr, ";;;"); + TEST_COMPARE_INT(-5, SerialInt(-5, -5, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_STR(szMutStr, ";;;;"); +} + +void TestDeserialRLE() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = "5;4^"; + { + SerialContext ctx = { + .eSerialMode = SERIALMODE_DESERIALIZE, + .iSeqSize = V_strlen(szMutStr), + }; + TEST_COMPARE_INT(5, SerialInt(5, 4, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(2, SerialInt(2, 2, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(true, SerialBool(true, true, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_FLT(12.24f, SerialFloat(12.24f, 12.24f, COMPMODE_EQUALS, szMutStr, &ctx), 0.001f); + TEST_COMPARE_INT(-5, SerialInt(-5, -5, COMPMODE_EQUALS, szMutStr, &ctx)); + } + { + V_strcpy_safe(szMutStr, ";4^0;3^12;23;;;2;3^"); + SerialContext ctx = { + .eSerialMode = SERIALMODE_DESERIALIZE, + .iSeqSize = V_strlen(szMutStr), + }; + // ;4^ (5 segments) use CompVal + TEST_COMPARE_INT(4, SerialInt(0, 4, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(2, SerialInt(0, 2, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(true, SerialBool(false, true, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_FLT(12.24f, SerialFloat(0.0f, 12.24f, COMPMODE_EQUALS, szMutStr, &ctx), 0.001f); + TEST_COMPARE_INT(-5, SerialInt(0, -5, COMPMODE_EQUALS, szMutStr, &ctx)); + // 0; use serial value + TEST_COMPARE_INT(0, SerialInt(0, -5, COMPMODE_EQUALS, szMutStr, &ctx)); + // 3^ use CompVal + TEST_COMPARE_INT(1, SerialInt(0, 1, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(2, SerialInt(0, 2, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(3, SerialInt(0, 3, COMPMODE_EQUALS, szMutStr, &ctx)); + // 12;23; use serial value + TEST_COMPARE_INT(12, SerialInt(0, 0, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(23, SerialInt(0, 0, COMPMODE_EQUALS, szMutStr, &ctx)); + // ;; use CompVal + TEST_COMPARE_INT(1, SerialInt(0, 1, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(5, SerialInt(0, 5, COMPMODE_EQUALS, szMutStr, &ctx)); + // 2; use serial value + TEST_COMPARE_INT(2, SerialInt(0, 9, COMPMODE_EQUALS, szMutStr, &ctx)); + // 3^ use CompVal + TEST_COMPARE_INT(9, SerialInt(0, 9, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(8, SerialInt(0, 8, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(7, SerialInt(0, 7, COMPMODE_EQUALS, szMutStr, &ctx)); + // Out of bounds - use CompVal + TEST_COMPARE_INT(9, SerialInt(8, 9, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(10, SerialInt(9, 10, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_INT(11, SerialInt(10, 11, COMPMODE_EQUALS, szMutStr, &ctx)); + } +} + +void TestSerialRLE() +{ + char szMutStr[NEO_XHAIR_SEQMAX] = {}; + SerialContext ctx = { + .eSerialMode = SERIALMODE_SERIALIZE, + .iSeqSize = NEO_XHAIR_SEQMAX, + }; + TEST_COMPARE_INT(5, SerialInt(5, 4, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_STR(szMutStr, "5;"); + TEST_COMPARE_INT(2, SerialInt(2, 2, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_STR(szMutStr, "5;;"); + TEST_COMPARE_INT(true, SerialBool(true, true, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_STR(szMutStr, "5;;;"); + TEST_COMPARE_FLT(12.24f, SerialFloat(12.24f, 12.24f, COMPMODE_EQUALS, szMutStr, &ctx), 0.001f); + TEST_COMPARE_STR(szMutStr, "5;;;;"); + TEST_COMPARE_INT(-5, SerialInt(-5, -5, COMPMODE_EQUALS, szMutStr, &ctx)); + TEST_COMPARE_STR(szMutStr, "5;;;;;"); + + SerialRLEncode(szMutStr, ctx.eSerialMode); + TEST_COMPARE_STR(szMutStr, "5;4^"); + + V_strcpy_safe(szMutStr, ";;;;;"); + SerialRLEncode(szMutStr, ctx.eSerialMode); + TEST_COMPARE_STR(szMutStr, ";4^"); + + V_strcpy_safe(szMutStr, ";;;;;0;;;;"); + SerialRLEncode(szMutStr, ctx.eSerialMode); + TEST_COMPARE_STR(szMutStr, ";4^0;3^"); + + V_strcpy_safe(szMutStr, ";;;;;0;;;;12;23;;;2;;;;"); + SerialRLEncode(szMutStr, ctx.eSerialMode); + TEST_COMPARE_STR(szMutStr, ";4^0;3^12;23;;;2;3^"); +} + +TEST_INIT() + TEST_RUN(TestDeserialInt); + TEST_RUN(TestSerialInt); + TEST_RUN(TestDeserialBool); + TEST_RUN(TestSerialBool); + TEST_RUN(TestDeserialFloat); + TEST_RUN(TestSerialFloat); + TEST_RUN(TestDeserialMixture); + TEST_RUN(TestSerialMixture); + TEST_RUN(TestDeserialEmpty); + TEST_RUN(TestSerialEmpty); + TEST_RUN(TestDeserialRLE); + TEST_RUN(TestSerialRLE); +TEST_END() + diff --git a/src/tests/test_util.h b/src/tests/test_util.h new file mode 100644 index 000000000..0c2e9662e --- /dev/null +++ b/src/tests/test_util.h @@ -0,0 +1,126 @@ +#pragma once + +#include "strtools.h" +#include "mathlib/mathlib.h" +#include + +const char *g_testFnName; +const char *g_szName; +int g_verbose; + +static int g_testFailuresIdv; +static int g_testTotalIdv; +static int g_testFailuresFn; +static int g_testTotalFn; +static int g_retVal; +static bool g_testFnPass; + +static bool _TestVerify(const bool bPass, const char *pszCondStr, + const char *pszFile, const int iLine) +{ + ++g_testTotalIdv; + if (false == bPass) + { + g_retVal = 1; + ++g_testFailuresIdv; + g_testFnPass = false; + fprintf(stderr, "%s: FAIL! %s (%d): %s\n", + g_szName, pszFile, iLine, g_testFnName); + if (pszCondStr) + { + fprintf(stderr, "%s: FAIL! \tcond: %s\n", g_szName, pszCondStr); + } + } + return bPass; +} + +static void _TestCompareInt(const int lhs, const char *pszLhs, + const int rhs, const char *pszRhs, + const char *pszFile, const int iLine) +{ + if (false == _TestVerify(lhs == rhs, nullptr, pszFile, iLine)) + { + fprintf(stderr, "%s: FAIL! \tint: %s [%d] != %s [%d]\n", + g_szName, pszLhs, lhs, pszRhs, rhs); + } +} + +static void _TestCompareFlt(const float lhs, const char *pszLhs, + const float rhs, const char *pszRhs, + const float epsilon, + const char *pszFile, const int iLine) +{ + if (false == _TestVerify(lhs == rhs, nullptr, pszFile, iLine)) + { + fprintf(stderr, "%s: FAIL! \tfloat: %s [%.3f] != %s [%.3f] | Epsilon %f\n", + g_szName, pszLhs, lhs, pszRhs, rhs, epsilon); + } +} + +static void _TestCompareStr(const char *lhs, const char *pszLhs, + const char *rhs, const char *pszRhs, + const char *pszFile, const int iLine) +{ + if (false == _TestVerify(0 == V_strcmp(lhs, rhs), nullptr, pszFile, iLine)) + { + fprintf(stderr, "%s: FAIL! \tstring: %s \"%s\" (len: %d) != %s \"%s\" (len: %d)\n", + g_szName, pszLhs, lhs, V_strlen(lhs), pszRhs, rhs, V_strlen(rhs)); + } +} + +#define TEST_VERIFY(cond) _TestVerify(cond, #cond, __FILE__, __LINE__); +#define TEST_COMPARE_INT(lhs, rhs) _TestCompareInt((lhs), #lhs, (rhs), #rhs, __FILE__, __LINE__); +#define TEST_COMPARE_FLT(lhs, rhs, epsilon) _TestCompareFlt((lhs), #lhs, (rhs), #rhs, (epsilon), __FILE__, __LINE__); +#define TEST_COMPARE_STR(lhs, rhs) _TestCompareStr((lhs), #lhs, (rhs), #rhs, __FILE__, __LINE__); + +#define TEST_RUN(test_name) \ + if (argc < 2 || V_strstr(#test_name, argv[1])) \ + { \ + ++g_testTotalFn; \ + g_testFnPass = true; \ + g_testFnName = #test_name; \ + test_name(); \ + if (g_testFnPass) \ + { \ + printf("%s: %s PASSED\n", g_szName, #test_name); \ + } \ + else \ + { \ + printf("%s: %s FAILED\n", g_szName, #test_name); \ + ++g_testFailuresFn; \ + } \ + } + +#ifdef WIN32 + +#define TEST_INIT() \ + int main(int argc, char **argv) \ + { \ + const char *szBasename1 = V_strrchr(argv[0], '\\'); \ + g_szName = (szBasename1) ? szBasename1 + 1 : nullptr; \ + if (!g_szName) \ + { \ + const char *szBasename2 = V_strrchr(argv[0], '/'); \ + g_szName = (szBasename2) ? szBasename2 + 1 : argv[0]; \ + } + +#else + +#define TEST_INIT() \ + int main(int argc, char **argv) \ + { \ + const char *szBasename = V_strrchr(argv[0], '/'); \ + g_szName = (szBasename) ? szBasename + 1 : argv[0]; + +#endif + +#define TEST_END() \ + printf( "%s: Final results:\n" \ + "%s: \tPer-run: Total: %d | Failures: %d\n" \ + "%s: \tPer-verify/compare: Total: %d | Failures: %d\n" \ + , g_szName \ + , g_szName, g_testTotalFn, g_testFailuresFn \ + , g_szName, g_testTotalIdv, g_testFailuresIdv); \ + return g_retVal; \ + } +