diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index d7b650965..b02327256 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -70,7 +70,7 @@ jobs: - name: Checkout if: steps.configure.outputs.tag != null - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -162,7 +162,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Restore compiler cache (ccache) id: ccache_restore @@ -339,7 +339,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml index 087b27b79..433f302a5 100644 --- a/.github/workflows/desktop-lint.yml +++ b/.github/workflows/desktop-lint.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 20 # should be enough to find merge base diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 028d5ddf9..1846766bf 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Docker metadata id: metadata diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index 12df6bc26..396ccd62b 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -21,12 +21,33 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 - - name: Install Doxygen and Graphviz - run: sudo apt-get install -y doxygen graphviz + - name: Install Graphviz + run: | + sudo apt-get install -y graphviz + dot -V + + - name: Install Doxygen + uses: ssciwr/doxygen-install@v1 + with: + version: "1.14.0" + + - name: Update Doxygen Configuration + run: | + git diff Doxyfile + doxygen -u Doxyfile + if git diff --quiet Doxyfile; then + echo "::notice::No config changes in Doxyfile detected." + else + echo "::error::Config changes in Doxyfile detected! Please update the file by running 'doxygen -u Doxyfile'." + echo "" + git diff --color=always Doxyfile + exit 1 + fi - name: Generate Documentation + if: always() run: doxygen Doxyfile - name: Deploy to cockatrice.github.io diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml index 7834e06b0..81d6f4e75 100644 --- a/.github/workflows/translations-pull.yml +++ b/.github/workflows/translations-pull.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Pull translated strings from Transifex uses: transifex/cli-action@v2 diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml index 602845a49..d201e7ad8 100644 --- a/.github/workflows/translations-push.yml +++ b/.github/workflows/translations-push.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install lupdate shell: bash diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml index 91080fa29..8d756da02 100644 --- a/.github/workflows/web-build.yml +++ b/.github/workflows/web-build.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml index a07463780..8a90325e7 100644 --- a/.github/workflows/web-lint.yml +++ b/.github/workflows/web-lint.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 diff --git a/.gitignore b/.gitignore index f147c1458..33c1ae31d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ compile_commands.json .cache .gdb_history cockatrice/resources/config/qtlogging.ini +docs/ diff --git a/Doxyfile b/Doxyfile index 85d66bbd3..d5a3a4b7a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,5 +1,4 @@ # Doxyfile 1.14.0 -# Doxygen Docs: https://www.doxygen.nl/manual # This file describes the settings to be used by the documentation system # Doxygen (www.doxygen.org) for a project. @@ -43,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "Cockatrice" +PROJECT_NAME = Cockatrice # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -55,7 +54,7 @@ PROJECT_NUMBER = $(COCKATRICE_REF) # for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = A cross-platform virtual tabletop for multiplayer card games +PROJECT_BRIEF = "A cross-platform virtual tabletop for multiplayer card games" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 @@ -64,6 +63,12 @@ PROJECT_BRIEF = A cross-platform virtual tabletop for multiplayer card PROJECT_LOGO = cockatrice/resources/cockatrice.png +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = cockatrice/resources/cockatrice.png + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where Doxygen was started. If @@ -374,7 +379,7 @@ TOC_INCLUDE_HEADINGS = 6 # The default value is: DOXYGEN. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. -MARKDOWN_ID_STYLE = DOXYGEN +MARKDOWN_ID_STYLE = GITHUB # When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can @@ -385,6 +390,14 @@ MARKDOWN_ID_STYLE = DOXYGEN AUTOLINK_SUPPORT = YES +# This tag specifies a list of words that, when matching the start of a word in +# the documentation, will suppress auto links generation, if it is enabled via +# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \# +# or the \link or commands. +# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. + +AUTOLINK_IGNORE_WORDS = + # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let Doxygen match functions declarations and @@ -486,7 +499,7 @@ TYPEDEF_HIDES_STRUCT = NO # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. -LOOKUP_CACHE_SIZE = 1 +LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use # during processing. When set to 0 Doxygen will based this on the number of @@ -596,6 +609,14 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO +# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all +# undocumented namespaces that are normally visible in the namespace hierarchy. +# If set to NO, these namespaces will be included in the various overviews. This +# option has no effect if EXTRACT_ALL is enabled. +# The default value is: YES. + +HIDE_UNDOC_NAMESPACES = YES + # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. @@ -830,6 +851,22 @@ LAYOUT_FILE = doc/doxygen/DoxygenLayout.xml CITE_BIB_FILES = +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- @@ -890,6 +927,14 @@ WARN_NO_PARAMDOC = NO WARN_IF_UNDOC_ENUM_VAL = NO +# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found +# while parsing the user defined layout file, such as missing or wrong elements. +# See also LAYOUT_FILE for details. If set to NO, problems with the layout file +# will be suppressed. +# The default value is: YES. + +WARN_LAYOUT_FILE = YES + # If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but @@ -986,8 +1031,7 @@ INPUT_FILE_ENCODING = # provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. -FILE_PATTERNS = *.c \ - *.cc \ +FILE_PATTERNS = *.cc \ *.cxx \ *.cxxm \ *.cpp \ @@ -1022,7 +1066,11 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which Doxygen is # run. -EXCLUDE = build/ cmake/ vcpkg/ dbconverter/ webclient/ +EXCLUDE = build/ \ + cmake/ \ + dbconverter/ \ + vcpkg/ \ + webclient/ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1038,7 +1086,8 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = .* .*/ +EXCLUDE_PATTERNS = .* \ + .*/ # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -1135,6 +1184,15 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = +# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- +# directories of the project's root, is used as the documentation for that sub- +# directory, except when the README.md starts with a \dir, \page or \mainpage +# command. If set to NO, the README.md file needs to start with an explicit \dir +# command in order to be used as directory documentation. +# The default value is: YES. + +IMPLICIT_DIR_DOCS = YES + # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The @@ -1231,6 +1289,46 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then Doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by Doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1414,6 +1512,26 @@ HTML_DYNAMIC_SECTIONS = NO HTML_CODE_FOLDING = YES +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1666,6 +1784,16 @@ DISABLE_INDEX = YES GENERATE_TREEVIEW = YES +# When GENERATE_TREEVIEW is set to YES, the PAGE_OUTLINE_PANEL option determines +# if an additional navigation panel is shown at the right hand side of the +# screen, displaying an outline of the contents of the main page, similar to +# e.g. https://developer.android.com/reference If GENERATE_TREEVIEW is set to +# NO, this option has no effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +PAGE_OUTLINE_PANEL = YES + # When GENERATE_TREEVIEW is set to YES, the FULL_SIDEBAR option determines if # the side bar is limited to only the treeview area (value NO) or if it should # extend to the full height of the window (value YES). Setting this to YES gives @@ -1687,6 +1815,12 @@ FULL_SIDEBAR = NO ENUM_VALUES_PER_LINE = 4 +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. @@ -2135,6 +2269,14 @@ RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = +# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the RTF_OUTPUT output directory. +# Note that the files will be copied as-is; there are no commands or markers +# available. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTRA_FILES = + #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -2481,7 +2623,7 @@ DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" # The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10,arrowhead=open, arrowtail=open, arrowsize=0.5" +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10,arrowhead=open, arrowtail=open, arrowsize=0.5" # DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes # around nodes set 'shape=plain' or 'shape=plaintext' value("interface/doubleclicktoplay", true).toBool(); clickPlaysAllSelected = settings->value("interface/clickPlaysAllSelected", true).toBool(); playToStack = settings->value("interface/playtostack", true).toBool(); + doNotDeleteArrowsInSubPhases = settings->value("interface/doNotDeleteArrowsInSubPhases", true).toBool(); startingHandSize = settings->value("interface/startinghandsize", 7).toInt(); annotateTokens = settings->value("interface/annotatetokens", false).toBool(); tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray(); @@ -664,6 +665,12 @@ void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T _playToStack) settings->setValue("interface/playtostack", playToStack); } +void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases) +{ + doNotDeleteArrowsInSubPhases = static_cast(_doNotDeleteArrowsInSubPhases); + settings->setValue("interface/doNotDeleteArrowsInSubPhases", doNotDeleteArrowsInSubPhases); +} + void SettingsCache::setStartingHandSize(int _startingHandSize) { startingHandSize = _startingHandSize; diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index cf0daff69..eb504e387 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -228,6 +228,7 @@ private: bool doubleClickToPlay; bool clickPlaysAllSelected; bool playToStack; + bool doNotDeleteArrowsInSubPhases; int startingHandSize; bool annotateTokens; QByteArray tabGameSplitterSizes; @@ -525,6 +526,10 @@ public: { return playToStack; } + bool getDoNotDeleteArrowsInSubPhases() const + { + return doNotDeleteArrowsInSubPhases; + } int getStartingHandSize() const { return startingHandSize; @@ -984,6 +989,7 @@ public slots: void setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay); void setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected); void setPlayToStack(QT_STATE_CHANGED_T _playToStack); + void setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T _doNotDeleteArrowsInSubPhases); void setStartingHandSize(int _startingHandSize); void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens); void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes); diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index 22e3ceb50..d5d0ebfe0 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -151,8 +151,8 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) } } -ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color) - : ArrowItem(_owner, -1, _startItem, 0, _color) +ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase) + : ArrowItem(_owner, -1, _startItem, 0, _color), deleteInPhase(_deleteInPhase) { } @@ -231,20 +231,28 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_target_zone(targetZone->getName().toStdString()); cmd.set_target_card_id(targetCard->getId()); - } else { + } else { // failed to cast target to card, this means it's a player PlayerTarget *targetPlayer = qgraphicsitem_cast(targetItem); cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId()); } - if (startZone->getName().compare("hand") == 0) { + + // if the card is in hand then we will move the card to stack or table as part of drawing the arrow + if (startZone->getName() == "hand") { startCard->playCard(false); CardInfoPtr ci = startCard->getCard().getCardPtr(); - if (ci && ((!SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow == 3) || - (SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow != 0 && - startCard->getZone()->getName().toStdString() != "stack"))) + bool playToStack = SettingsCache::instance().getPlayToStack(); + if (ci && + ((!playToStack && ci->getUiAttributes().tableRow == 3) || + (playToStack && ci->getUiAttributes().tableRow != 0 && startCard->getZone()->getName() != "stack"))) cmd.set_start_zone("stack"); else - cmd.set_start_zone(SettingsCache::instance().getPlayToStack() ? "stack" : "table"); + cmd.set_start_zone(playToStack ? "stack" : "table"); } + + if (deleteInPhase != 0) { + cmd.set_delete_in_phase(deleteInPhase); + } + player->getPlayerActions()->sendGameCommand(cmd); } delArrow(); diff --git a/cockatrice/src/game/board/arrow_item.h b/cockatrice/src/game/board/arrow_item.h index 8405083fe..cb78ee066 100644 --- a/cockatrice/src/game/board/arrow_item.h +++ b/cockatrice/src/game/board/arrow_item.h @@ -82,10 +82,11 @@ class ArrowDragItem : public ArrowItem { Q_OBJECT private: + int deleteInPhase; QList childArrows; public: - ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color); + ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase); void addChildArrow(ArrowDragItem *childArrow); protected: diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp index 52e237897..e786b5329 100644 --- a/cockatrice/src/game/board/card_item.cpp +++ b/cockatrice/src/game/board/card_item.cpp @@ -3,6 +3,7 @@ #include "../../client/settings/cache_settings.h" #include "../../interface/widgets/tabs/tab_game.h" #include "../game_scene.h" +#include "../phase.h" #include "../player/player.h" #include "../zones/card_zone.h" #include "../zones/logic/view_zone_logic.h" @@ -275,9 +276,14 @@ void CardItem::drawArrow(const QColor &arrowColor) if (owner->getGame()->getPlayerManager()->isSpectator()) return; - Player *arrowOwner = - owner->getGame()->getPlayerManager()->getActiveLocalPlayer(owner->getGame()->getGameState()->getActivePlayer()); - ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor); + auto *game = owner->getGame(); + Player *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); + int phase = 0; // 0 means to not set the phase + if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) { + int currentPhase = game->getGameState()->getCurrentPhase(); + phase = Phases::getLastSubphase(currentPhase) + 1; + } + ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor, phase); scene()->addItem(arrow); arrow->grabMouse(); @@ -288,7 +294,7 @@ void CardItem::drawArrow(const QColor &arrowColor) if (card->getZone() != zone) continue; - ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor); + ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase); scene()->addItem(childArrow); arrow->addChildArrow(childArrow); } diff --git a/cockatrice/src/game/phase.cpp b/cockatrice/src/game/phase.cpp index 32c9060ee..7fb1700a5 100644 --- a/cockatrice/src/game/phase.cpp +++ b/cockatrice/src/game/phase.cpp @@ -22,6 +22,28 @@ Phase Phases::getPhase(int phase) } } +int Phases::getLastSubphase(int phase) +{ + if (0 <= phase && phase < Phases::phaseTypesCount) { + return subPhasesEnd[phase]; + } else { + return phase; + } +} + +QVector getSubPhasesEnd() +{ + QVector array(Phases::phaseTypesCount); + for (int phaseEnd = Phases::phaseTypesCount - 1; phaseEnd >= 0;) { + int subPhase = phaseEnd; + for (; subPhase >= 0 && Phases::phases[phaseEnd].color == Phases::phases[subPhase].color; --subPhase) { + array[subPhase] = phaseEnd; + } + phaseEnd = subPhase; + } + return array; +} + const Phase Phases::unknownPhase(QT_TRANSLATE_NOOP("Phase", "Unknown Phase"), "black", "unknown_phase"); const Phase Phases::phases[Phases::phaseTypesCount] = { {QT_TRANSLATE_NOOP("Phase", "Untap"), "green", "untap_step"}, @@ -35,3 +57,4 @@ const Phase Phases::phases[Phases::phaseTypesCount] = { {QT_TRANSLATE_NOOP("Phase", "End of Combat"), "red", "end_combat"}, {QT_TRANSLATE_NOOP("Phase", "Second Main"), "blue", "main_2"}, {QT_TRANSLATE_NOOP("Phase", "End/Cleanup"), "green", "end_step"}}; +const QVector Phases::subPhasesEnd = getSubPhasesEnd(); diff --git a/cockatrice/src/game/phase.h b/cockatrice/src/game/phase.h index 6e451161e..2e712932f 100644 --- a/cockatrice/src/game/phase.h +++ b/cockatrice/src/game/phase.h @@ -28,8 +28,10 @@ struct Phases const static int phaseTypesCount = 11; const static Phase unknownPhase; const static Phase phases[phaseTypesCount]; + const static QVector subPhasesEnd; static Phase getPhase(int); + static int getLastSubphase(int phase); }; #endif // PHASE_H diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index 73508d843..e92593d55 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -801,6 +801,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setPlayToStack); + doNotDeleteArrowsInSubPhasesCheckBox.setChecked(SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()); + connect(&doNotDeleteArrowsInSubPhasesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setDoNotDeleteArrowsInSubPhases); + closeEmptyCardViewCheckBox.setChecked(SettingsCache::instance().getCloseEmptyCardView()); connect(&closeEmptyCardViewCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setCloseEmptyCardView); @@ -821,10 +825,11 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); generalGrid->addWidget(&playToStackCheckBox, 2, 0); - generalGrid->addWidget(&closeEmptyCardViewCheckBox, 3, 0); - generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 4, 0); - generalGrid->addWidget(&annotateTokensCheckBox, 5, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 6, 0); + generalGrid->addWidget(&doNotDeleteArrowsInSubPhasesCheckBox, 3, 0); + generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0); + generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0); + generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 7, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -942,6 +947,7 @@ void UserInterfaceSettingsPage::retranslateUi() doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)")); clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)")); playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); + doNotDeleteArrowsInSubPhasesCheckBox.setText(tr("Do not delete &arrows inside of subphases")); closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h index 22c9fa1c0..10134f3a8 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h @@ -163,6 +163,7 @@ private: QCheckBox doubleClickToPlayCheckBox; QCheckBox clickPlaysAllSelectedCheckBox; QCheckBox playToStackCheckBox; + QCheckBox doNotDeleteArrowsInSubPhasesCheckBox; QCheckBox closeEmptyCardViewCheckBox; QCheckBox focusCardViewSearchBarCheckBox; QCheckBox annotateTokensCheckBox; diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 75195fc96..b97bd09c7 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -182,6 +182,9 @@ void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T /* _clickPlaysAl void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T /* _playToStack */) { } +void SettingsCache::setDoNotDeleteArrowsInSubPhases(QT_STATE_CHANGED_T /* _doNotDeleteArrowsInSubPhases */) +{ +} void SettingsCache::setStartingHandSize(int /* _startingHandSize */) { } @@ -445,4 +448,4 @@ void SettingsCache::setRoundCardCorners(bool /* _roundCardCorners */) void CardPictureLoader::clearPixmapCache(CardInfoPtr /* card */) { -} \ No newline at end of file +} diff --git a/doc/doxygen/DoxygenLayout.xml b/doc/doxygen/DoxygenLayout.xml index a1070d696..d6847755b 100644 --- a/doc/doxygen/DoxygenLayout.xml +++ b/doc/doxygen/DoxygenLayout.xml @@ -121,6 +121,7 @@ + @@ -132,6 +133,7 @@ + @@ -167,6 +169,7 @@ + @@ -179,6 +182,7 @@ + diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp index 1c81b6823..aaf7f1332 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp @@ -375,20 +375,17 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, eventPrivate.set_x(newX); - // Other players do not get to see the start and/or target position of the card if the respective - // part of the zone is being looked at. The information is not needed anyway because in hidden zones, - // all cards are equal. - if (((startzone->getType() == ServerInfo_Zone::HiddenZone) && - ((startzone->getCardsBeingLookedAt() > position) || (startzone->getCardsBeingLookedAt() == -1))) || - (startzone->getType() == ServerInfo_Zone::PublicZone)) { - eventOthers.set_position(-1); - } else { + if ( + // cards from public zones have their id known, their previous position is already known, the event does + // not accomodate for previous locations in zones with coordinates (which are always public) + (startzone->getType() != ServerInfo_Zone::PublicZone) && + // other players are not allowed to be able to track which card is which in private zones like the hand + (startzone->getType() != ServerInfo_Zone::PrivateZone)) { eventOthers.set_position(position); } - if ((targetzone->getType() == ServerInfo_Zone::HiddenZone) && - ((targetzone->getCardsBeingLookedAt() > newX) || (targetzone->getCardsBeingLookedAt() == -1))) { - eventOthers.set_x(-1); - } else { + if ( + // other players are not allowed to be able to track which card is which in private zones like the hand + (targetzone->getType() != ServerInfo_Zone::PrivateZone)) { eventOthers.set_x(newX); } @@ -1210,7 +1207,9 @@ Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseCo } } - auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color()); + int currentPhase = game->getActivePhase(); + int deletionPhase = cmd.has_delete_in_phase() ? cmd.delete_in_phase() : currentPhase; + auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase, deletionPhase); addArrow(arrow); Event_CreateArrow event; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp index 9433d8d3f..f6787baa2 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp @@ -6,8 +6,14 @@ #include -Server_Arrow::Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const color &_arrowColor) - : id(_id), startCard(_startCard), targetItem(_targetItem), arrowColor(_arrowColor) +Server_Arrow::Server_Arrow(int _id, + Server_Card *_startCard, + Server_ArrowTarget *_targetItem, + const color &_arrowColor, + int _phaseCreated, + int _phaseDeleted) + : id(_id), startCard(_startCard), targetItem(_targetItem), arrowColor(_arrowColor), phaseCreated(_phaseCreated), + phaseDeleted(_phaseDeleted) { } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.h index 0b3e63550..1f302358b 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.h @@ -14,9 +14,15 @@ private: Server_Card *startCard; Server_ArrowTarget *targetItem; color arrowColor; + int phaseCreated, phaseDeleted; public: - Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const color &_arrowColor); + Server_Arrow(int _id, + Server_Card *_startCard, + Server_ArrowTarget *_targetItem, + const color &_arrowColor, + int _phaseCreated, + int _phaseDeleted); int getId() const { return id; @@ -45,6 +51,10 @@ public: { return arrowColor; } + bool checkPhaseDeletion(int phase) const // returns true if the arrow should be deleted in this phase + { + return phase < phaseCreated || phase >= phaseDeleted; + } void getInfo(ServerInfo_Arrow *info); }; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index 82b24ccde..b0dc7fc5b 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -654,6 +654,8 @@ void Server_Game::setActivePlayer(int _activePlayer) { QMutexLocker locker(&gameMutex); + removeArrows(0, true); + activePlayer = _activePlayer; Event_SetActivePlayer event; @@ -663,30 +665,35 @@ void Server_Game::setActivePlayer(int _activePlayer) setActivePhase(0); } -void Server_Game::setActivePhase(int _activePhase) +void Server_Game::setActivePhase(int newPhase) { QMutexLocker locker(&gameMutex); - for (auto *player : getPlayers().values()) { - QList toDelete = player->getArrows().values(); - for (int i = 0; i < toDelete.size(); ++i) { - Server_Arrow *a = toDelete[i]; - - Event_DeleteArrow event; - event.set_arrow_id(a->getId()); - sendGameEventContainer(prepareGameEvent(event, player->getPlayerId())); - - player->deleteArrow(a->getId()); - } - } - - activePhase = _activePhase; + removeArrows(newPhase); + activePhase = newPhase; Event_SetActivePhase event; event.set_phase(activePhase); sendGameEventContainer(prepareGameEvent(event, -1)); } +void Server_Game::removeArrows(int newPhase, bool force) +{ + QMutexLocker locker(&gameMutex); + + for (auto *anyPlayer : getPlayers().values()) { + for (auto *arrowToDelete : anyPlayer->getArrows().values()) { // values creates a copy + if (force || arrowToDelete->checkPhaseDeletion(newPhase)) { + Event_DeleteArrow event; + event.set_arrow_id(arrowToDelete->getId()); + sendGameEventContainer(prepareGameEvent(event, anyPlayer->getPlayerId())); + + anyPlayer->deleteArrow(arrowToDelete->getId()); + } + } + } +} + void Server_Game::nextTurn() { QMutexLocker locker(&gameMutex); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h index 50ffbef36..033542fad 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -198,8 +198,9 @@ public: { return activePhase; } - void setActivePlayer(int _activePlayer); - void setActivePhase(int _activePhase); + void setActivePlayer(int newPlayer); + void setActivePhase(int newPhase); + void removeArrows(int newPhase, bool force = false); void nextTurn(); int getSecondsElapsed() const { diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_create_arrow.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_create_arrow.proto index fcfa028a6..cb3871b60 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_create_arrow.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/command_create_arrow.proto @@ -27,4 +27,9 @@ message Command_CreateArrow { // the color of the arrow optional color arrow_color = 7; + + // the phase that this arrow is deleted in, arrows are deleted when this or a later phase is activated and also + // when the phase moves back before the current phase or the turn is passed, when not set the arrow is deleted + // immediately when the phase is changed + optional sint32 delete_in_phase = 8; }