minor: New Game Digimon Digi-Battle (#17)

* digimon digi battle added to supported games

* sonarqube update

* readme update

---------

Co-authored-by: sdine <sdine@sdine.com>
This commit is contained in:
Sebastian Dine
2026-07-19 12:06:57 +02:00
committed by GitHub
parent 42926f2fb5
commit e5c830e945
53 changed files with 2170 additions and 49 deletions
+1
View File
@@ -26,6 +26,7 @@ struct AppContext {
IGameModule& magicModule;
IGameModule& pokemonModule;
IGameModule& yuGiOhModule;
IGameModule& digiBattle99Module;
// Active per-game UI bundles. The order is the order shown in the
// Game menu; the composition root constructs them and hands raw
// pointers in. `MainFrame` does not own these — `app/main.cpp` does.
+13 -1
View File
@@ -295,9 +295,11 @@ private:
case Game::YuGiOh:
// Yugipedia English TCG backing (thumbnail — smaller than full scan).
return "https://ms.yugipedia.com/thumb/e/e5/Back-EN.png/250px-Back-EN.png";
default:
case Game::DigiBattle99:
// No stable public Digi-Battle back URL; UI uses bundled PNG.
return {};
}
return {};
}
void buildInfoGrid(wxBoxSizer* root) {
@@ -424,6 +426,16 @@ private:
applyFallbackPayload(ss.str());
}
}
} else if (game == Game::DigiBattle99) {
namespace fs = std::filesystem;
const fs::path asset =
fs::path(exeDirCopy) / "assets" / "digibattle99_card_back.png";
std::ifstream in(asset, std::ios::binary);
if (in) {
std::ostringstream ss;
ss << in.rdbuf();
applyFallbackPayload(ss.str());
}
} else {
const std::string fallbackUrl = fallbackImageUrlForGame(game);
if (!fallbackUrl.empty()) {
@@ -0,0 +1,81 @@
#pragma once
#include "ccm/domain/DigiBattle99Card.hpp"
#include "ccm/ports/ICardPreviewSource.hpp"
#include "ccm/services/CardPreviewService.hpp"
#include "ccm/ui/BaseCardEditDialog.hpp"
#include <wx/button.h>
#include <atomic>
#include <memory>
#include <string>
#include <vector>
namespace ccm::ui {
class DigiBattle99CardEditDialog final : public BaseCardEditDialog<DigiBattle99Card> {
public:
DigiBattle99CardEditDialog(wxWindow* parent,
ImageService& imageService,
SetService& setService,
CardPreviewService& cardPreview,
EditMode mode,
DigiBattle99Card initial,
const std::vector<Set>* preloadedSets = nullptr);
~DigiBattle99CardEditDialog() override;
protected:
void buildFlagsRow(wxBoxSizer* flagsBox) override;
void appendExtraRows(wxFlexGridSizer* grid) override;
void readExtraFromCard() override;
void writeExtraToCard() override;
[[nodiscard]] std::string updateMenuName() const override {
return "Update Digimon (Digi-Battle)";
}
void onCardLookupContextChanged() override;
private:
struct VariantFetchState {
std::atomic<bool> alive{true};
};
void onAutoDetectSetNo(wxCommandEvent&);
void onNextSetNo(wxCommandEvent&);
void onSetSelectionChanged(wxCommandEvent&);
void autoDetectFromApi();
void clearCachedPrintVariants();
void requestVariantsAsync(unsigned capturedEpoch,
std::string name,
std::string setName,
bool fillSetNoOnSuccess,
bool showFailureDialog);
void applyDetectedVariants(unsigned capturedEpoch,
Result<std::vector<AutoDetectedPrint>> detected,
bool fillSetNoOnSuccess,
bool showFailureDialog);
void rebuildVariantRingFromCache();
void syncRingPositionToControls();
void refreshVariantNextControls();
void scheduleDeferredVariantPrefetch();
void prefetchVariantsForCurrentCardSilent(unsigned capturedEpoch);
[[nodiscard]] static std::string storedSetNoFromControls(const wxTextCtrl* ctrl);
[[nodiscard]] static std::string normalizedStoredSetNo(std::string_view setNo);
EditMode dialogMode_;
unsigned variantFetchEpoch_{0};
CardPreviewService& cardPreview_;
std::shared_ptr<VariantFetchState> variantFetchState_;
wxTextCtrl* setNoCtrl_{nullptr};
wxButton* autoSetNoBtn_{nullptr};
wxButton* nextSetNoBtn_{nullptr};
wxCheckBox* holoCheck_{nullptr};
wxCheckBox* firstEditionCheck_{nullptr};
wxCheckBox* signedCheck_{nullptr};
wxCheckBox* alteredCheck_{nullptr};
std::vector<AutoDetectedPrint> cachedVariants_;
std::vector<std::string> uniqueSetNos_;
std::size_t setNoRingPos_{0};
};
} // namespace ccm::ui
@@ -0,0 +1,26 @@
#pragma once
#include "ccm/domain/DigiBattle99Card.hpp"
#include "ccm/services/CardSorter.hpp"
#include "ccm/ui/BaseCardListPanel.hpp"
namespace ccm::ui {
class DigiBattle99CardListPanel final
: public BaseCardListPanel<DigiBattle99Card, DigiBattle99SortColumn> {
public:
explicit DigiBattle99CardListPanel(wxWindow* parent);
protected:
[[nodiscard]] std::vector<TextColumnSpec> declareTextColumns() const override;
[[nodiscard]] std::vector<IconColumnSpec> declareIconColumns() const override;
[[nodiscard]] std::string renderTextCell(const DigiBattle99Card& card,
std::size_t idx) const override;
[[nodiscard]] bool isIconColumnSet(const DigiBattle99Card& card,
std::size_t idx) const override;
void sortBy(DigiBattle99SortColumn column, bool ascending) override;
[[nodiscard]] bool matchesFilter(const DigiBattle99Card& card,
std::string_view filter) const override;
};
} // namespace ccm::ui
@@ -0,0 +1,64 @@
#pragma once
#include "ccm/domain/DigiBattle99Card.hpp"
#include "ccm/games/IGameModule.hpp"
#include "ccm/services/CardPreviewService.hpp"
#include "ccm/services/CollectionService.hpp"
#include "ccm/services/ConfigService.hpp"
#include "ccm/services/ImageService.hpp"
#include "ccm/services/SetService.hpp"
#include "ccm/ui/IGameView.hpp"
#include <string>
#include <string_view>
#include <vector>
namespace ccm::ui {
class DigiBattle99CardListPanel;
class DigiBattle99SelectedCardPanel;
class DigiBattle99GameView final : public IGameView {
public:
DigiBattle99GameView(ConfigService& config,
CollectionService<DigiBattle99Card>& collection,
SetService& sets,
ImageService& images,
CardPreviewService& cardPreview,
IGameModule& module);
[[nodiscard]] Game gameId() const noexcept override { return Game::DigiBattle99; }
[[nodiscard]] std::string displayName() const override { return "Digimon (Digi-Battle)"; }
wxPanel* listPanel(wxWindow* parent) override;
wxPanel* selectedPanel(wxWindow* parent) override;
void refreshCollection() override;
void onAddCard(wxWindow* parentWindow) override;
void onEditCard(wxWindow* parentWindow) override;
void onDeleteCard(wxWindow* parentWindow) override;
std::string onUpdateSets(wxWindow* parentWindow) override;
void setFilter(std::string_view filter) override;
void applyTheme(const ThemePalette& palette) override;
[[nodiscard]] std::string updateSetsMenuLabel() const override {
return "Update Digimon (Digi-Battle)";
}
private:
void ensureSetsLoaded();
const std::vector<Set>& setsForDialog();
ConfigService& config_;
CollectionService<DigiBattle99Card>& collection_;
SetService& sets_;
ImageService& images_;
CardPreviewService& cardPreview_;
IGameModule& module_;
DigiBattle99CardListPanel* listPanel_{nullptr};
DigiBattle99SelectedCardPanel* selectedPanel_{nullptr};
std::vector<Set> setsCache_;
bool attemptedInitialSetLoad_{false};
};
} // namespace ccm::ui
@@ -0,0 +1,25 @@
#pragma once
#include "ccm/domain/DigiBattle99Card.hpp"
#include "ccm/ui/BaseSelectedCardPanel.hpp"
namespace ccm::ui {
class DigiBattle99SelectedCardPanel final : public BaseSelectedCardPanel<DigiBattle99Card> {
public:
DigiBattle99SelectedCardPanel(wxWindow* parent,
ImageService& imageService,
CardPreviewService& cardPreview);
protected:
[[nodiscard]] std::vector<DetailRowSpec> declareDetailRows() const override;
[[nodiscard]] std::vector<FlagIconSpec> declareFlagIcons() const override;
[[nodiscard]] std::string detailValueFor(const DigiBattle99Card& card,
DetailKey key) const override;
[[nodiscard]] bool isFlagSet(const DigiBattle99Card& card, DetailKey key) const override;
[[nodiscard]] std::tuple<std::string, std::string, std::string>
previewKey(const DigiBattle99Card& card) const override;
[[nodiscard]] Game gameId() const noexcept override { return Game::DigiBattle99; }
};
} // namespace ccm::ui