diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..df1a5c9fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +tags +build* +*.qm +.directory +mysql.cnf diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..74f6feff6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: cpp +os: + - linux + - osx +compiler: + - gcc + - clang +script: mkdir build && cd build && cmake .. -DWITH_SERVER=1 && make +install: ./travis-dependencies.sh +cache: apt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..414df9f89 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,222 @@ +# Cockatrice's main CMakeLists.txt +# +# This is basically a wrapper to enable/disable the compilation +# of the different projects: servatrice, cockatrice, test +# This file sets all the variables shared between the projects +# like the installation path, compilation flags etc.. + +cmake_minimum_required(VERSION 2.6) + +if(POLICY CMP0020) + cmake_policy(SET CMP0020 OLD) +endif() + +if(POLICY CMP0043) + cmake_policy(SET CMP0043 OLD) +endif() + +if(POLICY CMP0048) + cmake_policy(SET CMP0048 OLD) +endif() + +set(PROJECT_NAME "Cockatrice") + +# Default to "Release" build type +# User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call +IF(DEFINED CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Type of build") +ELSE() + SET(CMAKE_BUILD_TYPE Release CACHE STRING "Type of build") +ENDIF() + +# A project name is needed for CPack +PROJECT("${PROJECT_NAME}") + + +# Set conventional loops +set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) + +# Search path for cmake modules +SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + +# Retrieve git version hash +include(getversion) + +# Create a header and a cpp file containing the version hash +include(createversionfile) + +# Define a proper install path +if(UNIX) + if(APPLE) + # MacOS X + # Due to the special bundle structure ignore + # the prefix eventually set by the user. + set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/release) + else() + # Linux / BSD + if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + #fix package build + if(PREFIX) + set(CMAKE_INSTALL_PREFIX ${PREFIX}) + else() + set(CMAKE_INSTALL_PREFIX /usr/local) + endif() + endif() + endif() +elseif(WIN32) + set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/release) +endif() + +# Define proper compilation flags +IF(MSVC) + # Visual Studio: + # Maximum optimization + set(CMAKE_CXX_FLAGS_RELEASE "/Ox") + # Generate complete debugging information + #set(CMAKE_CXX_FLAGS_DEBUG "/Zi") +ELSEIF (CMAKE_COMPILER_IS_GNUCXX) + # linux/gcc, bsd/gcc, windows/mingw + include(CheckCXXCompilerFlag) + + set(CMAKE_CXX_FLAGS_RELEASE "-s -O2") + set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0 -Wall -Wextra -pedantic -Werror") + + set(ADDITIONAL_DEBUG_FLAGS -Wcast-align -Wmissing-declarations -Winline -Wno-long-long -Wno-error=extra -Wno-error=unused-parameter -Wno-inline -Wno-error=delete-non-virtual-dtor -Wno-error=sign-compare -Wno-error=reorder -Wno-error=missing-declarations) + + FOREACH(FLAG ${ADDITIONAL_DEBUG_FLAGS}) + CHECK_CXX_COMPILER_FLAG("${FLAG}" CXX_HAS_WARNING_${FLAG}) + IF(CXX_HAS_WARNING_${FLAG}) + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}") + ENDIF() + ENDFOREACH() +ELSE() + # other: osx/llvm, bsd/llvm + set(CMAKE_CXX_FLAGS_RELEASE "-O2") + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0") +ENDIF() + +# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning +IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + ADD_DEFINITIONS("-DSFMT_MEXP=19937") +ENDIF() + +# Find Qt and enable the needed features +# Default is Qt5 unless WITH_QT4 option is enabled +option(WITH_QT4 "Force the use of Qt4 libraries" OFF) +OPTION(UPDATE_TRANSLATIONS "Update translations on compile" OFF) +MESSAGE("UPDATE TRANSLATIONS: ${UPDATE_TRANSLATIONS}") + +IF(NOT WITH_QT4) + FIND_PACKAGE(Qt5Widgets) +ENDIF() + +IF(Qt5Widgets_FOUND) + MESSAGE(STATUS "Found Qt ${Qt5Widgets_VERSION_STRING}") + + if(UNIX AND NOT APPLE AND "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") + # FIX: Qt was built with -reduce-relocations + add_definitions(-fPIC) + endif() + + FIND_PACKAGE(Qt5LinguistTools) + IF(UPDATE_TRANSLATIONS) + IF(NOT Qt5_LUPDATE_EXECUTABLE) + MESSAGE(WARNING "Qt's lupdate not found.") + ENDIF() + ENDIF() + + IF(NOT Qt5_LRELEASE_EXECUTABLE) + MESSAGE(WARNING "Qt's lrelease not found.") + ENDIF() + +ELSE() + FIND_PACKAGE(Qt4 4.8.0 REQUIRED) + IF(QT4_FOUND) + # Old FindQt4.cmake modules used the same flag with different case + SET(Qt4_FOUND TRUE) + ENDIF() + IF(Qt4_FOUND) + MESSAGE(STATUS "Found Qt ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}") + + IF(UPDATE_TRANSLATIONS) + IF(NOT QT_LUPDATE_EXECUTABLE) + MESSAGE(WARNING "Qt's lupdate not found.") + ENDIF() + ENDIF() + + IF(NOT QT_LRELEASE_EXECUTABLE) + MESSAGE(WARNING "Qt's lrelease not found.") + ENDIF() + + ELSE() + MESSAGE(FATAL_ERROR "No Qt4 or Qt5 found!") + ENDIF() +ENDIF() + +set(CMAKE_AUTOMOC TRUE) + +# Find other needed libraries +FIND_PACKAGE(Protobuf REQUIRED) + +# Package builder +set(CPACK_PACKAGE_CONTACT "Daenyth+github@gmail.com") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_NAME}) +set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md") +set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/COPYING") +set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") + +if(UNIX) + if(APPLE) + set(CPACK_GENERATOR DragNDrop ${CPACK_GENERATOR}) + set(CPACK_GENERATOR "DragNDrop") + set(CPACK_DMG_FORMAT "UDBZ") + set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME}") + set(CPACK_SYSTEM_NAME "OSX") + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-osx_git-${PROJECT_VERSION}") + set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice/resources/appicon.icns") + else() + # linux + set(CPACK_GENERATOR DEB ${CPACK_GENERATOR}) + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}/${PROJECT_VERSION}") + set(CPACK_STRIP_FILES "bin/${PROJECT_NAME}") + set(CPACK_SOURCE_STRIP_FILES "") + endif() +elseif(WIN32) + set(CPACK_GENERATOR NSIS ${CPACK_GENERATOR}) + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_win32_git-${PROJECT_VERSION}") + + # Configure file with custom definitions for NSIS. + configure_file( + ${CMAKE_MODULE_PATH}/NSIS.definitions.nsh.in + ${PROJECT_BINARY_DIR}/NSIS.definitions.nsh + ) +endif() + +include(CPack) + +# Compile servatrice (default off) +option(WITH_SERVER "build servatrice" OFF) +add_subdirectory(common) +if(WITH_SERVER) + add_subdirectory(servatrice) + SET(CPACK_INSTALL_CMAKE_PROJECTS "servatrice;servatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) +endif() + +# Compile cockatrice (default on) +option(WITH_CLIENT "build cockatrice" ON) +if(WITH_CLIENT) + add_subdirectory(cockatrice) + add_subdirectory(sounds) + add_subdirectory(zonebg) + SET(CPACK_INSTALL_CMAKE_PROJECTS "cockatrice;cockatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) +endif() + +# Compile oracle (default on) +option(WITH_ORACLE "build oracle" ON) +if(WITH_ORACLE) + add_subdirectory(oracle) + SET(CPACK_INSTALL_CMAKE_PROJECTS "release/oracle.app;oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..cb8473890 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,134 @@ +# Style Guide # + +### Compatibility ### + +Cockatrice is written in C++-03, so do not use C++11 constructs such as `auto`. + +Cockatrice support both Qt 4 and Qt 5, so make sure that your code compiles +with both. If there have been breaking changes between Qt 4 and 5, use a +statement such as + + #if QT_VERSION >= 0x500000 + doSomethingWithQt5(); + #else + doSomethingWithQt4(); + #endif + +For consistency, use Qt data structures where possible, such as `QString` over +`std::string` or `QList` over `std::vector`. + +### Header files ### + +Use header files with the extension `.h` and source files with the extension +`.cpp`. + +Use header guards in the form of `FILE_NAME_H`. + +Simple functions, such as getters, may be written inline in the header file, +but other functions should be written in the source file. + +Keep library includes and project includes grouped together. So this is okay: + + // Good: + #include + #include + #include "card.h" + #include "deck.h" + + // Good: + #include "card.h" + #include "deck.h" + #include + #include + + // Bad: + #include + #include "card.h" + #include + #include "deck.h" + +### Naming ### + +Use `UpperCamelCase` for classes, structs, enums, etc. and `lowerCamelCase` for +function and variable names. + +Member variables aren't decorated in any way. Don't prefix or suffix with +underscores, etc. + +For arguments to constructors which have the same names as member variables, +prefix those arguments with underscores: + + MyClass::MyClass(int _myData) + : myData(_myData) + {} + +Pointers and references should be denoted with the `*` or `&` going with the +variable name: + + // Good: + Foo *foo1 = new Foo; + Foo &foo2 = *foo1; + + // Bad: + Bar* bar1 = new Bar; + Bar& bar2 = *bar1; + +Use `0` instead of `NULL` (or `nullptr`) for null pointers. + +### Braces ### + +Use K&R-style braces. Braces for function implementations go on their own +lines, but they go on the same line everywhere else: + + int main() + { + if (someCondition) { + doSomething(); + } else { + while (someOtherCondition) { + doSomethingElse(); + } + } + } + +Braces can be omitted for single-statement if's and the like, as long as it is +still legible. + +### Tabs ### + +Use only spaces. Four spaces per tab. + +### Lines ### + +Do not have trailing whitespace in your lines. + +Lines should be 80 characters or less, as a soft limit. + +### Memory Management ### + +New code should be written using references over pointers and stack allocation +over heap allocation wherever possible. + + // Good: uses stack allocation and references + void showCard(const Card &card); + int main() + { + Card card; + showCard(card); + } + + // Bad: relies on manual memory management and doesn't give us much + // null-safety. + void showCard(const Card *card); + int main() + { + Card *card = new Card; + showCard(card); + delete card; + } + +(Remember to pass by `const` reference wherever possible, to avoid accidentally +mutating objects.) + +When pointers can't be avoided, try to use a smart pointer of some sort, such +as `QScopedPointer`, or, less preferably, `QSharedPointer`. diff --git a/servatrice/copying b/COPYING old mode 100755 new mode 100644 similarity index 95% rename from servatrice/copying rename to COPYING index 5b6e7c66c..d159169d1 --- a/servatrice/copying +++ b/COPYING @@ -1,12 +1,12 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to +the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not @@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - - GNU GENERAL PUBLIC LICENSE + + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions: License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -225,7 +225,7 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it @@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. @@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names: This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General +library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 000000000..1d79addc6 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# Cockatrice + +Cockatrice is an open-source multiplatform software for playing card games, +such as Magic: The Gathering, over a network. It is fully client-server based +to prevent any kind of cheating, though it supports single-player games without +a network interface as well. Both client and server are written in Qt, supporting both Qt4 and Qt5. + +# Get Involved + +Chat with the Cockatrice developers on Gitter. Come here to talk about the application, features, or just to hang out. For support regarding specific servers, please contact that server's admin or forum for support rather than asking here. + +[![Gitter chat](https://badges.gitter.im/Daenyth/Cockatrice.png)](https://gitter.im/Daenyth/Cockatrice) + +# Building + +[![Build Status](https://travis-ci.org/Daenyth/Cockatrice.svg?branch=master)](https://travis-ci.org/Daenyth/Cockatrice) + +Dependencies: + +- [Qt](http://qt-project.org/) +- [protobuf](http://code.google.com/p/protobuf/) +- [CMake](http://www.cmake.org/) + +The server requires an additional dependency: + +- [libgcrypt](http://www.gnu.org/software/libgcrypt/) + +To compile: + + mkdir build + cd build + cmake .. + make + make install + +The following flags can be passed to `cmake`: + +- `-DWITH_SERVER=1` Build the server +- `-DWITH_CLIENT=0` Do not build the client +- `-DWITH_ORACLE=0` Do not build Oracle +- `-DWITH_QT4=1` Force compilation to use Qt4 instead of Qt5. +- `-DCMAKE_BUILD_TYPE=Debug` Compile in debug mode. Enables extra logging output, debug symbols, and much more verbose compiler warnings. +- `-DUPDATE_TRANSLATIONS=1` Configure `make` to update the translation .ts files for new strings in the source code. Note: Running `make clean` will remove the .ts files. You should run this when contributing code that changes user messages. Translation file updates should be added in the same commit as the string that was changed. + +# Running + +`oracle` fetches card data +`cockatrice` is the game client +`servatrice` is the server + +# License + +Cockatrice is free software, licensed under the GPLv2; see COPYING for details. diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..0dc0d849d --- /dev/null +++ b/TODO.md @@ -0,0 +1,57 @@ +#TODOs + +This is an unordered list of possible todo items for Cockatrice. +Note that "improve" and "write" always also means: "document and comment" + +##Docs +* Improve README.md, it is very minimalistic and technical + +##Improve packaging: +* Add SSL libraries to Windows build to support https links in oracle.exe +* Improve nsis file git hash extraction, it only works if the build directory is cleared as version_string.cpp does not seem to get updated by git pull/cmake +* Fix prepareMacRelease.sh to use default installation paths for Qt and protobuf +* Add Mac compile+package howto to the documentation +* Create script/... for creating Linux packages (deb, rpm, ebuild, ...) or at least an official tarball/git tags; package maintainers dislike using git snapshots so much that they rather ignore software without stable tarballs. +* Move everything package related into one directory + +##Scripts +* Write example init script for servatrice. + +##Webinterface +* Write a default cgi webinterface in some language for the servatrice database. Keep it simple and think about SQL-injections and other security risks - especially if you do it in PHP :-P + +##Improve usermanual: +* Improve Layout, Text, Screenshots; nearly everything is still quick n dirty copy and paste. +* Add missing TODOs +* Translate the Documentation +* Include faq.txt and shortcuts.txt in usermanual, then remove them. +* Rename the picture filenames to something more meaningful. +* Create an index, lists of tables/figures/... + +## Storage +* Find a better place for sets.xml than doc. + +##Create developer documentation: +* Create developer manual +* Add comments to code +* Describe which components exist and how they work and interact +* Describe the *.proto files +* Comment and document servatrice.sql +* Document everything!1!! +* Coding guidelines + +##Else + +* Update SFMT library (http://www.math.sci.hiroshima-u.ac.jp/~m-mat@math.sci.hiroshima-u.ac.jp/MT/SFMT/) in common/sfmt and adapt common/rng_sfmt.cpp + +* Prepare to update to Qt5, as Qt4 will die some day. See http://qt-project.org/wiki/Transition_from_Qt_4.x_to_Qt5 for relevant information. + +* Move hardcoded URLs (especially from oracle and cockatrice) into a config file. + +* Update the oracle to find all MtG extensions by itself, why the need for sets.xml when the oracle could ask the Gatherer or magiccards.info directly for this information? + +* Update the oracle with some intelligence or improve it otherwise to deal with changes from the Gatherer. + +* Remove cockatrice/resources/mtgforum.png as it is unused. Find other dead files or dead references to websites and files in the program code and documentation. + +* Search git log for useful information/problems/bugs/... diff --git a/cmake/FindLibgcrypt.cmake b/cmake/FindLibgcrypt.cmake new file mode 100644 index 000000000..880a5d69e --- /dev/null +++ b/cmake/FindLibgcrypt.cmake @@ -0,0 +1,46 @@ +# -*- cmake -*- + +# Copied from http://code.google.com/p/emeraldviewer/ + +# - Find libgcrypt +# Find the libgcrypt includes and library +# This module defines +# LIBGCRYPT_INCLUDE_DIR, where to find gcrypt.h, etc. +# LIBGCRYPT_LIBRARIES, the libraries needed to use libgcrypt. +# LIBGCRYPT_FOUND, If false, do not try to use libgcrypt. +# also defined, but not for general use are +# LIBGCRYPT_LIBRARY, where to find the libgcrypt library. + +FIND_PATH(LIBGCRYPT_INCLUDE_DIR gcrypt.h) + +SET(LIBGCRYPT_NAMES ${LIBGCRYPT_NAMES} gcrypt) +FIND_LIBRARY(LIBGCRYPT_LIBRARY + NAMES ${LIBGCRYPT_NAMES} + ) + +IF (LIBGCRYPT_LIBRARY AND LIBGCRYPT_INCLUDE_DIR) + SET(LIBGCRYPT_LIBRARIES ${LIBGCRYPT_LIBRARY}) + SET(LIBGCRYPT_FOUND "YES") +ELSE (LIBGCRYPT_LIBRARY AND LIBGCRYPT_INCLUDE_DIR) + SET(LIBGCRYPT_FOUND "NO") +ENDIF (LIBGCRYPT_LIBRARY AND LIBGCRYPT_INCLUDE_DIR) + + +IF (LIBGCRYPT_FOUND) + IF (NOT LIBGCRYPT_FIND_QUIETLY) + MESSAGE(STATUS "Found libgcrypt: '${LIBGCRYPT_LIBRARIES}' and header in '${LIBGCRYPT_INCLUDE_DIR}'") + ENDIF (NOT LIBGCRYPT_FIND_QUIETLY) +ELSE (LIBGCRYPT_FOUND) + IF (LIBGCRYPT_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find libgcrypt library") + ENDIF (LIBGCRYPT_FIND_REQUIRED) +ENDIF (LIBGCRYPT_FOUND) + +# Deprecated declarations. +SET (NATIVE_LIBGCRYPT_INCLUDE_PATH ${LIBGCRYPT_INCLUDE_DIR} ) +GET_FILENAME_COMPONENT (NATIVE_LIBGCRYPT_LIB_PATH ${LIBGCRYPT_LIBRARY} PATH) + +MARK_AS_ADVANCED( + LIBGCRYPT_LIBRARY + LIBGCRYPT_INCLUDE_DIR + ) diff --git a/cmake/FindQtMobility.cmake b/cmake/FindQtMobility.cmake new file mode 100644 index 000000000..8e7edf5e2 --- /dev/null +++ b/cmake/FindQtMobility.cmake @@ -0,0 +1,158 @@ +INCLUDE(FindQt4) + +set(MOBILITY_CONFIG_MKSPECS_FILE "") +IF(EXISTS "${QT_MKSPECS_DIR}/features/mobilityconfig.prf") + set(MOBILITY_CONFIG_MKSPECS_FILE "${QT_MKSPECS_DIR}/features/mobilityconfig.prf") +ELSEIF(EXISTS "${QT_MKSPECS_DIR}/features/mobility.prf") + set(MOBILITY_CONFIG_MKSPECS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/mobilityconfig.prf") +ENDIF() + +macro(export_component component) + IF(NOT ${MOBILITY_CONFIG_MKSPECS_FILE} STREQUAL "") + FILE(READ ${MOBILITY_CONFIG_MKSPECS_FILE} MOBILITY_FILE_CONTENTS) + STRING(TOLOWER ${component} _COMPONENT) + IF(${MOBILITY_FILE_CONTENTS} MATCHES "MOBILITY_CONFIG=.*${_COMPONENT}.*") + STRING(TOUPPER ${component} _COMPONENT) + SET(QT_MOBILITY_${_COMPONENT}_FOUND 1) + SET(QT_MOBILITY_${_COMPONENT}_INCLUDE_DIR ${QT_MOBILITY_PARENT_INCLUDE_DIR}/Qt${component}) + SET(QT_MOBILITY_${_COMPONENT}_LIBRARY Qt${component}) + ENDIF() + ENDIF() +endmacro() + +set(VERSION_INFO "") +set(FEATURE_FILE_PREFIX "${QT_MKSPECS_DIR}/features/mobility") + +if(DEFINED MOBILITY_VERSION) + if(MOBILITY_VERSION STREQUAL "1.1" AND EXISTS "${FEATURE_FILE_PREFIX}11.prf") + set(MOBILITY_PRF_FILE "${FEATURE_FILE_PREFIX}11.prf") + set(VERSION_INFO "1.1") + elseif(MOBILITY_VERSION STREQUAL "1.2" AND EXISTS "${FEATURE_FILE_PREFIX}12.prf") + set(MOBILITY_PRF_FILE "${FEATURE_FILE_PREFIX}12.prf") + set(VERSION_INFO "1.2") + elseif(MOBILITY_VERSION STREQUAL "default" AND EXISTS "${FEATURE_FILE_PREFIX}.prf") + set(MOBILITY_PRF_FILE "${FEATURE_FILE_PREFIX}.prf") + set(VERSION_INFO "system's default") + else() + message(STATUS "Couldn't find QtMobility version: ${MOBILITY_VERSION}") + endif() +endif() + +if(NOT DEFINED MOBILITY_PRF_FILE) + if(EXISTS "${FEATURE_FILE_PREFIX}.prf") + set(MOBILITY_PRF_FILE "${FEATURE_FILE_PREFIX}.prf") + set(VERSION_INFO "system's default") + elseif(EXISTS "${FEATURE_FILE_PREFIX}12.prf") + set(MOBILITY_PRF_FILE "${FEATURE_FILE_PREFIX}12.prf") + set(VERSION_INFO "1.2") + elseif(EXISTS "${FEATURE_FILE_PREFIX}11.prf") + set(MOBILITY_PRF_FILE "${FEATURE_FILE_PREFIX}11.prf") + set(VERSION_INFO "1.1") + else() + message(FATAL_ERROR "Couldn't find any version of QtMobility.") + endif() +endif() + +message(STATUS "Using QtMobility version: ${VERSION_INFO}") + +IF(DEFINED MOBILITY_PRF_FILE) + FILE(READ ${MOBILITY_PRF_FILE} MOBILITY_FILE_CONTENTS) + + STRING(REGEX MATCH "MOBILITY_PREFIX=([^\n]+)" QT_MOBILITY_PREFIX "${MOBILITY_FILE_CONTENTS}") + SET(QT_MOBILITY_PREFIX ${CMAKE_MATCH_1}) + + STRING(REGEX MATCH "MOBILITY_INCLUDE=([^\n]+)" QT_MOBILITY_INCLUDE_DIR "${MOBILITY_FILE_CONTENTS}") + SET(QT_MOBILITY_INCLUDE_DIR ${CMAKE_MATCH_1}) + + STRING(REGEX MATCH "MOBILITY_LIB=([^\n]+)" "\\1" QT_MOBILITY_LIBRARY "${MOBILITY_FILE_CONTENTS}") + SET(QT_MOBILITY_LIBRARY_DIR ${CMAKE_MATCH_1}) + + #VERSION + IF(NOT ${MOBILITY_CONFIG_MKSPECS_FILE} STREQUAL "") + FILE(READ ${MOBILITY_CONFIG_MKSPECS_FILE} MOBILITY_CONFIG_FILE_CONTENTS) + STRING(REGEX MATCH "MOBILITY_VERSION = ([^\n]+)" QT_MOBILITY_VERSION "${MOBILITY_CONFIG_FILE_CONTENTS}") + SET(QT_MOBILITY_VERSION ${CMAKE_MATCH_1}) + + STRING(REGEX MATCH "MOBILITY_MAJOR_VERSION = ([^\n]+)" QT_MOBILITY_MAJOR_VERSION "${MOBILITY_CONFIG_FILE_CONTENTS}") + SET(QT_MOBILITY_MAJOR_VERSION ${CMAKE_MATCH_1}) + + STRING(REGEX MATCH "MOBILITY_MINOR_VERSION = ([^\n]+)" QT_MOBILITY_MINOR_VERSION "${MOBILITY_CONFIG_FILE_CONTENTS}") + SET(QT_MOBILITY_MINOR_VERSION ${CMAKE_MATCH_1}) + + STRING(REGEX MATCH "MOBILITY_PATCH_VERSION = ([^\n]+)" QT_MOBILITY_PATCH_VERSION "${MOBILITY_CONFIG_FILE_CONTENTS}") + SET(QT_MOBILITY_PATCH_VERSION ${CMAKE_MATCH_1}) + ELSE() + SET(QT_MOBILITY_VERSION 1.0.0) + SET(QT_MOBILITY_MAJOR_VERSION 1) + SET(QT_MOBILITY_MINOR_VERSION 0) + SET(QT_MOBILITY_PATCH_VERSION 0) + ENDIF() + + SET(QT_MOBILITY_PARENT_INCLUDE_DIR ${QT_MOBILITY_INCLUDE_DIR}) + SET(QT_MOBILITY_INCLUDE_DIR ${QT_MOBILITY_INCLUDE_DIR}/QtMobility) + + IF(QtMobility_FIND_VERSION_EXACT) + IF(QT_MOBILITY_VERSION VERSION_EQUAL QtMobility_FIND_VERSION) + SET(QT_MOBILITY_FOUND TRUE) + ELSE() + SET(QT_MOBILITY_FOUND FALSE) + IF(QT_MOBILITY_VERSION VERSION_LESS QtMobility_FIND_VERSION) + SET(QT_MOBILITY_TOO_OLD TRUE) + ELSE() + SET(QT_MOBILITY_TOO_NEW TRUE) + ENDIF() + ENDIF() + ELSE() + IF(QT_MOBILITY_VERSION VERSION_LESS QtMobility_FIND_VERSION) + SET(QT_MOBILITY_FOUND FALSE) + SET(QT_MOBILITY_TOO_OLD TRUE) + ELSE() + SET(QT_MOBILITY_FOUND TRUE) + ENDIF() + ENDIF() +ELSE() + SET(QT_MOBILITY_FOUND NOTFOUND) + SET(QT_MOBILITY_PREFIX NOTFOUND) + SET(QT_MOBILITY_INCLUDE NOTFOUND) + SET(QT_MOBILITY_LIB NOTFOUND) +ENDIF() + +IF(NOT QT_MOBILITY_FOUND) + if(QT_MOBILITY_TOO_OLD) + MESSAGE(FATAL_ERROR "The installed QtMobility version ${QT_MOBILITY_VERSION} it too old, version ${QtMobility_FIND_VERSION} is required.") + ELSEIF(QT_MOBILITY_TOO_NEW) + MESSAGE(FATAL_ERROR "The installed QtMobility version ${QT_MOBILITY_VERSION} it too new, version ${QtMobility_FIND_VERSION} is required.") + ELSE() + MESSAGE(FATAL_ERROR "QtMobility not found.") + ENDIF() +ELSE() + export_component(Bearer) + export_component(Feedback) + export_component(Gallery) + export_component(PublishSubscribe) + export_component(Location) + export_component(Organizer) + export_component(ServiceFramework) + export_component(SystemInfo) + export_component(Contacts) + export_component(Messaging) + export_component(Versit) + export_component(Sensors) + # VersitOrganizer + if(${QT_MOBILITY_CONTACTS_FOUND} AND ${QT_MOBILITY_VERSIT_FOUND}) + SET(QT_MOBILITY_VERSITORGANIZER_FOUND 1) + SET(QT_MOBILITY_VERSITORGANIZER_INCLUDE_DIR ${QT_MOBILITY_PARENT_INCLUDE_DIR}/QtVersitOrganizer) + SET(QT_MOBILITY_VERSITORGANIZER_LIBRARY QtVersitOrganizer) + endif() + + # MultimediaKit - it's just 'multimedia' in the .prf file. + IF(NOT ${MOBILITY_CONFIG_MKSPECS_FILE} STREQUAL "") + FILE(READ ${MOBILITY_CONFIG_MKSPECS_FILE} MOBILITY_FILE_CONTENTS) + IF(${MOBILITY_FILE_CONTENTS} MATCHES "MOBILITY_CONFIG=.*multimedia.*") + SET(QT_MOBILITY_MULTIMEDIAKIT_FOUND 1) + SET(QT_MOBILITY_MULTIMEDIAKIT_INCLUDE_DIR ${QT_MOBILITY_PARENT_INCLUDE_DIR}/QtMultimediaKit) + SET(QT_MOBILITY_MULTIMEDIAKIT_LIBRARY QtMultimediaKit) + ENDIF() + ENDIF() + +ENDIF() diff --git a/cmake/NSIS.definitions.nsh.in b/cmake/NSIS.definitions.nsh.in new file mode 100644 index 000000000..cc4b014c0 --- /dev/null +++ b/cmake/NSIS.definitions.nsh.in @@ -0,0 +1 @@ +!define NSIS_SOURCE_PATH "@PROJECT_SOURCE_DIR@" \ No newline at end of file diff --git a/nsis/cockatrice.nsi b/cmake/NSIS.template.in similarity index 53% rename from nsis/cockatrice.nsi rename to cmake/NSIS.template.in index a82115d25..c04169c8b 100644 --- a/nsis/cockatrice.nsi +++ b/cmake/NSIS.template.in @@ -1,23 +1,26 @@ +!include ..\..\..\NSIS.definitions.nsh !include "MUI2.nsh" !include "FileFunc.nsh" -Name "Cockatrice" -OutFile "cockatrice_win32.exe" -SetCompressor lzma +Name "@CPACK_PACKAGE_NAME@" +OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@" +SetCompressor /SOLID lzma InstallDir "$PROGRAMFILES\Cockatrice" +!define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@" !define MUI_ABORTWARNING -!define MUI_WELCOMEFINISHPAGE_BITMAP "leftimage.bmp" -!define MUI_UNWELCOMEFINISHPAGE_BITMAP "leftimage.bmp" +!define MUI_WELCOMEFINISHPAGE_BITMAP "${NSIS_SOURCE_PATH}\cmake\leftimage.bmp" +!define MUI_UNWELCOMEFINISHPAGE_BITMAP "${NSIS_SOURCE_PATH}\cmake\leftimage.bmp" !define MUI_HEADERIMAGE -!define MUI_HEADERIMAGE_BITMAP "headerimage.bmp" -!define MUI_HEADERIMAGE_UNBITMAP "headerimage.bmp" +!define MUI_HEADERIMAGE_BITMAP "${NSIS_SOURCE_PATH}\cmake\headerimage.bmp" +!define MUI_HEADERIMAGE_UNBITMAP "${NSIS_SOURCE_PATH}\cmake\headerimage.bmp" !define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of Cockatrice.$\r$\n$\r$\nClick Next to continue." !define MUI_FINISHPAGE_RUN "$INSTDIR/oracle.exe" !define MUI_FINISHPAGE_RUN_TEXT "Run card database downloader now" !define MUI_FINISHPAGE_RUN_PARAMETERS "-dlsets" !insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\COPYING" !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES @@ -30,34 +33,15 @@ InstallDir "$PROGRAMFILES\Cockatrice" !insertmacro MUI_LANGUAGE "English" Section "Application" SecApplication + SetShellVarContext all SetOutPath "$INSTDIR" - File ..\cockatrice\release\cockatrice.exe - File ..\oracle\release\oracle.exe - File data\libgcc_s_dw2-1.dll - File data\mingwm10.dll - File data\QtCore4.dll - File data\QtGui4.dll - File data\QtNetwork4.dll - File data\QtSvg4.dll - File data\QtXml4.dll - File data\QtMultimedia4.dll - File data\QtScript4.dll - SetOutPath "$INSTDIR\zonebg" - File /r ..\zonebg\*.* - - SetOutPath "$INSTDIR\plugins" - File /r data\plugins\*.* + @CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@ + @CPACK_NSIS_FULL_INSTALL@ - SetOutPath "$INSTDIR\sounds" - File /r ..\sounds\*.* - - SetOutPath "$INSTDIR\pics" - SetOutPath "$INSTDIR\decks" - - WriteUninstaller "$INSTDIR\uninstall.exe" - ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 - IntFmt $0 "0x%08X" $0 + WriteUninstaller "$INSTDIR\uninstall.exe" + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayName" "Cockatrice" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" @@ -67,50 +51,48 @@ Section "Application" SecApplication SectionEnd Section "Update configuration" SecUpdateConfig - WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "carddatabase" "$INSTDIR\cards.xml" - WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "decks" "$INSTDIR\decks" - WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "pics" "$INSTDIR\pics" - WriteRegStr HKCU "Software\Cockatrice\Cockatrice\sound" "path" "$INSTDIR\sounds" + WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "carddatabase" "$APPDATA\Cockatrice\cards.xml" + WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "decks" "$APPDATA\Cockatrice\decks" + WriteRegStr HKCU "Software\Cockatrice\Cockatrice\paths" "pics" "$APPDATA\Cockatrice\pics" + WriteRegStr HKCU "Software\Cockatrice\Cockatrice\sound" "path" "$APPDATA\Cockatrice\sounds" SectionEnd Section "Start menu item" SecStartMenu createDirectory "$SMPROGRAMS\Cockatrice" - createShortCut "$SMPROGRAMS\Cockatrice\Cockatrice.lnk" "$INSTDIR\cockatrice.exe" + createShortCut "$SMPROGRAMS\Cockatrice\Cockatrice.lnk" "$INSTDIR\cockatrice.exe" '--debug-output' createShortCut "$SMPROGRAMS\Cockatrice\Oracle.lnk" "$INSTDIR\oracle.exe" + createShortCut "$SMPROGRAMS\Cockatrice\Usermanual.lnk" "$INSTDIR\Usermanual.pdf" SectionEnd Section Uninstall - RMDir /r "$INSTDIR\zonebg" - RMDir /r "$INSTDIR\plugins" - RMDir /r "$INSTDIR\sounds" - RMDir "$INSTDIR\decks" - RMDir /r "$INSTDIR\pics\downloadedPics" - RMDir "$INSTDIR\pics" - Delete "$INSTDIR\uninstall.exe" - Delete "$INSTDIR\cockatrice.exe" - Delete "$INSTDIR\oracle.exe" - Delete "$INSTDIR\cards.xml" - Delete "$INSTDIR\libgcc_s_dw2-1.dll" - Delete "$INSTDIR\mingwm10.dll" - Delete "$INSTDIR\QtCore4.dll" - Delete "$INSTDIR\QtGui4.dll" - Delete "$INSTDIR\QtNetwork4.dll" - Delete "$INSTDIR\QtSvg4.dll" - Delete "$INSTDIR\QtXml4.dll" - RMDir "$INSTDIR" +SetShellVarContext all + RMDir /r "$INSTDIR\zonebg" + RMDir /r "$INSTDIR\plugins" + RMDir /r "$INSTDIR\sounds" + RMDir /r "$INSTDIR\translations" + Delete "$INSTDIR\uninstall.exe" + Delete "$INSTDIR\cockatrice.exe" + Delete "$INSTDIR\oracle.exe" + Delete "$INSTDIR\Usermanual.pdf" + Delete "$INSTDIR\libprotobuf.lib" + Delete "$INSTDIR\Qt*.dll" + Delete "$INSTDIR\icu*.dll" + Delete "$INSTDIR\qt.conf" + Delete "$INSTDIR\qdebug.txt" + RMDir "$INSTDIR" - RMDir /r "$SMPROGRAMS\Cockatrice" - - DeleteRegKey HKCU "Software\Cockatrice" - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" + RMDir "$SMPROGRAMS\Cockatrice" + + DeleteRegKey HKCU "Software\Cockatrice" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" SectionEnd LangString DESC_SecApplication ${LANG_ENGLISH} "Cockatrice program files" LangString DESC_SecUpdateConfig ${LANG_ENGLISH} "Update the paths in the application settings according to the installation paths." LangString DESC_SecStartMenu ${LANG_ENGLISH} "Create start menu items for Cockatrice and Oracle." !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN - !insertmacro MUI_DESCRIPTION_TEXT ${SecApplication} $(DESC_SecApplication) - !insertmacro MUI_DESCRIPTION_TEXT ${SecUpdateConfig} $(DESC_SecUpdateConfig) - !insertmacro MUI_DESCRIPTION_TEXT ${SecStartMenu} $(DESC_SecStartMenu) + !insertmacro MUI_DESCRIPTION_TEXT ${SecApplication} $(DESC_SecApplication) + !insertmacro MUI_DESCRIPTION_TEXT ${SecUpdateConfig} $(DESC_SecUpdateConfig) + !insertmacro MUI_DESCRIPTION_TEXT ${SecStartMenu} $(DESC_SecStartMenu) !insertmacro MUI_FUNCTION_DESCRIPTION_END diff --git a/cmake/createversionfile.cmake b/cmake/createversionfile.cmake new file mode 100644 index 000000000..b71aa8adf --- /dev/null +++ b/cmake/createversionfile.cmake @@ -0,0 +1,17 @@ +set(VERSION_STRING_CPP "${PROJECT_BINARY_DIR}/version_string.cpp") +set(VERSION_STRING_H "${PROJECT_BINARY_DIR}/version_string.h") +INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}) + +set( hstring "extern const char *VERSION_STRING\;\n" ) +set( cppstring "const char * VERSION_STRING = \"${PROJECT_VERSION}\"\;\n") + +file(WRITE ${PROJECT_BINARY_DIR}/version_string.cpp.txt ${cppstring} ) +file(WRITE ${PROJECT_BINARY_DIR}/version_string.h.txt ${hstring} ) + +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_BINARY_DIR}/version_string.h.txt ${VERSION_STRING_H} +) +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_BINARY_DIR}/version_string.cpp.txt ${VERSION_STRING_CPP} +) + diff --git a/cmake/getversion.cmake b/cmake/getversion.cmake new file mode 100644 index 000000000..e434e992a --- /dev/null +++ b/cmake/getversion.cmake @@ -0,0 +1,20 @@ +find_package(Git) +if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --long --always + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE res_var + OUTPUT_VARIABLE GIT_COM_ID + ) + if( NOT ${res_var} EQUAL 0 ) + set( GIT_COMMIT_ID "git commit id unknown") + message( WARNING "Git failed (not a repo, or no tags). Build will not contain git revision info." ) + endif() + string( REPLACE "\n" "" GIT_COMMIT_ID "${GIT_COM_ID}" ) +else() + set( GIT_COMMIT_ID "unknown (git not found!)") + message( WARNING "Git not found. Build will not contain git revision info." ) +endif() + +set(PROJECT_VERSION_MAJOR ${GIT_COMMIT_ID}) +set(PROJECT_VERSION ${GIT_COMMIT_ID} ) \ No newline at end of file diff --git a/nsis/headerimage.bmp b/cmake/headerimage.bmp similarity index 100% rename from nsis/headerimage.bmp rename to cmake/headerimage.bmp diff --git a/nsis/leftimage.bmp b/cmake/leftimage.bmp similarity index 100% rename from nsis/leftimage.bmp rename to cmake/leftimage.bmp diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt new file mode 100644 index 000000000..454f1a1f1 --- /dev/null +++ b/cockatrice/CMakeLists.txt @@ -0,0 +1,324 @@ +# CMakeLists for cockatrice directory +# +# provides the cockatrice binary + +PROJECT(cockatrice) + +SET(cockatrice_SOURCES + src/abstractcounter.cpp + src/counter_general.cpp + src/dlg_creategame.cpp + src/dlg_filter_games.cpp + src/dlg_connect.cpp + src/dlg_create_token.cpp + src/dlg_edit_tokens.cpp + src/abstractclient.cpp + src/remoteclient.cpp + src/main.cpp + src/window_main.cpp + src/gamesmodel.cpp + src/player.cpp + src/playertarget.cpp + src/cardzone.cpp + src/selectzone.cpp + src/cardlist.cpp + src/abstractcarditem.cpp + src/carditem.cpp + src/tablezone.cpp + src/handzone.cpp + src/handcounter.cpp + src/carddatabase.cpp + src/keysignals.cpp + src/gameview.cpp + src/gameselector.cpp + src/decklistmodel.cpp + src/deck_loader.cpp + src/dlg_load_deck_from_clipboard.cpp + src/dlg_load_remote_deck.cpp + src/cardinfowidget.cpp + src/cardframe.cpp + src/cardinfopicture.cpp + src/cardinfotext.cpp + src/filterbuilder.cpp + src/cardfilter.cpp + src/filtertreemodel.cpp + src/filtertree.cpp + src/messagelogwidget.cpp + src/zoneviewzone.cpp + src/zoneviewwidget.cpp + src/pilezone.cpp + src/stackzone.cpp + src/carddragitem.cpp + src/carddatabasemodel.cpp + src/setsmodel.cpp + src/window_sets.cpp + src/abstractgraphicsitem.cpp + src/abstractcarddragitem.cpp + src/dlg_settings.cpp + src/dlg_cardsearch.cpp + src/phasestoolbar.cpp + src/gamescene.cpp + src/arrowitem.cpp + src/arrowtarget.cpp + src/tab.cpp + src/tab_server.cpp + src/tab_room.cpp + src/tab_message.cpp + src/tab_game.cpp + src/tab_deck_storage.cpp + src/tab_replays.cpp + src/tab_supervisor.cpp + src/tab_admin.cpp + src/tab_userlists.cpp + src/tab_deck_editor.cpp + src/replay_timeline_widget.cpp + src/deckstats_interface.cpp + src/chatview.cpp + src/userlist.cpp + src/userinfobox.cpp + src/user_context_menu.cpp + src/remotedecklist_treewidget.cpp + src/remotereplaylist_treewidget.cpp + src/deckview.cpp + src/playerlistwidget.cpp + src/pixmapgenerator.cpp + src/settingscache.cpp + src/localserver.cpp + src/localserverinterface.cpp + src/localclient.cpp + src/priceupdater.cpp + src/qt-json/json.cpp + src/soundengine.cpp + src/pending_command.cpp + ${VERSION_STRING_CPP} +) + +if (UNIX AND NOT APPLE) + set_source_files_properties(src/main.cpp PROPERTIES COMPILE_FLAGS -DTRANSLATION_PATH=\\"${CMAKE_INSTALL_PREFIX}/share/cockatrice/translations\\") +endif (UNIX AND NOT APPLE) + +set(cockatrice_RESOURCES cockatrice.qrc) +FILE(GLOB cockatrice_TS "${CMAKE_CURRENT_SOURCE_DIR}/translations/*.ts") + +IF(UPDATE_TRANSLATIONS) + FILE(GLOB_RECURSE translate_cockatrice_SRCS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp) + FILE(GLOB_RECURSE translate_common_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/common/*.cpp) + SET(translate_SRCS ${translate_cockatrice_SRCS} ${translate_common_SRCS}) +ENDIF(UPDATE_TRANSLATIONS) + +if(WIN32) + set(cockatrice_SOURCES ${cockatrice_SOURCES} cockatrice.rc) +endif(WIN32) + +if(APPLE) + set(MACOSX_BUNDLE_ICON_FILE appicon.icns) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns) +ENDIF(APPLE) + +set(COCKATRICE_LIBS) + +# Qt4 stuff +if(Qt4_FOUND) + if (NOT QT_QTMULTIMEDIA_FOUND) + FIND_PACKAGE(QtMobility REQUIRED) + endif() + + SET(QT_USE_QTNETWORK TRUE) + SET(QT_USE_QTMULTIMEDIA TRUE) + SET(QT_USE_QTXML TRUE) + SET(QT_USE_QTSVG TRUE) + + # Include directories + INCLUDE(${QT_USE_FILE}) + INCLUDE_DIRECTORIES(${QT_INCLUDES}) + INCLUDE_DIRECTORIES(${QT_MOBILITY_INCLUDE_DIR}) + INCLUDE_DIRECTORIES(${QT_MOBILITY_MULTIMEDIAKIT_INCLUDE_DIR}) + LIST(APPEND COCKATRICE_LIBS ${QT_LIBRARIES}) + LIST(APPEND COCKATRICE_LIBS ${QT_QTMAIN_LIBRARY}) + LIST(APPEND COCKATRICE_LIBS ${QT_MOBILITY_MULTIMEDIAKIT_LIBRARY}) + + # Let cmake chew Qt4's translations and resource files + # Note: header files are MOC-ed automatically by cmake + IF(UPDATE_TRANSLATIONS) + QT4_CREATE_TRANSLATION(cockatrice_QM ${translate_SRCS} ${cockatrice_TS}) + ELSE(UPDATE_TRANSLATIONS) + QT4_ADD_TRANSLATION(cockatrice_QM ${cockatrice_TS}) + ENDIF(UPDATE_TRANSLATIONS) + + QT4_ADD_RESOURCES(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES}) +endif() + +# qt5 stuff +if(Qt5Widgets_FOUND) + include_directories(${Qt5Widgets_INCLUDE_DIRS}) + list(APPEND COCKATRICE_LIBS Widgets) + + # QtNetwork + find_package(Qt5Network) + if(Qt5Network_FOUND) + include_directories(${Qt5Network_INCLUDE_DIRS}) + list(APPEND COCKATRICE_LIBS Network) + endif() + + # QtMultimedia + find_package(Qt5Multimedia) + if(Qt5Multimedia_FOUND) + include_directories(${Qt5Multimedia_INCLUDE_DIRS}) + list(APPEND COCKATRICE_LIBS Multimedia) + endif() + + # QtPrinter + find_package(Qt5PrintSupport) + if(Qt5PrintSupport_FOUND) + include_directories(${Qt5PrintSupport_INCLUDE_DIRS}) + list(APPEND COCKATRICE_LIBS PrintSupport) + endif() + + # QtXml + find_package(Qt5Xml) + if(Qt5Xml_FOUND) + include_directories(${Qt5Xml_INCLUDE_DIRS}) + list(APPEND COCKATRICE_LIBS Xml) + endif() + + # QtSvg + find_package(Qt5Svg) + if(Qt5Svg_FOUND) + include_directories(${Qt5Svg_INCLUDE_DIRS}) + list(APPEND COCKATRICE_LIBS Svg) + endif() + + # Qt5LinguistTools + find_package(Qt5LinguistTools) + if(Qt5LinguistTools_FOUND) + include_directories(${Qt5LinguistTools_INCLUDE_DIRS}) + list(APPEND COCKATRICE_LIBS LinguistTools) + endif() + + # Let cmake chew Qt5's translations and resource files + # Note: header files are MOC-ed automatically by cmake + IF(UPDATE_TRANSLATIONS) + QT5_CREATE_TRANSLATION(cockatrice_QM ${translate_SRCS} ${cockatrice_TS}) + ELSE() + QT5_ADD_TRANSLATION(cockatrice_QM ${cockatrice_TS}) + ENDIF() + + QT5_ADD_RESOURCES(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES}) + + # guess plugins and libraries directory + set(QT_PLUGINS_DIR "${Qt5Widgets_DIR}/../../../plugins") + get_target_property(QT_LIBRARY_DIR Qt5::Core LOCATION) + get_filename_component(QT_LIBRARY_DIR ${QT_LIBRARY_DIR} PATH) +endif() + +# Declare path variables +set(ICONDIR share/icons CACHE STRING "icon dir") +set(DESKTOPDIR share/applications CACHE STRING "desktop file destination") + +# Include directories +INCLUDE_DIRECTORIES(../common) +INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}/common) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) + +# Build cockatrice binary and link it +ADD_EXECUTABLE(cockatrice WIN32 MACOSX_BUNDLE ${cockatrice_SOURCES} ${cockatrice_QM} ${cockatrice_RESOURCES_RCC} ${cockatrice_MOC_SRCS}) + +if(Qt4_FOUND) + if(MSVC) + set(QT_USE_QTMAIN true) + endif() + TARGET_LINK_LIBRARIES(cockatrice cockatrice_common ${COCKATRICE_LIBS}) +endif() +if(Qt5Widgets_FOUND) + if(MSVC) + TARGET_LINK_LIBRARIES(cockatrice cockatrice_common Qt5::WinMain) + else() + TARGET_LINK_LIBRARIES(cockatrice cockatrice_common) + endif() + qt5_use_modules(cockatrice ${COCKATRICE_LIBS}) +endif() + +if(UNIX) + if(APPLE) + INSTALL(TARGETS cockatrice BUNDLE DESTINATION ./) + INSTALL(FILES ${cockatrice_QM} DESTINATION ./cockatrice.app/Contents/Resources/translations) + else() + # Assume linux + INSTALL(TARGETS cockatrice RUNTIME DESTINATION bin/) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.png DESTINATION ${ICONDIR}/hicolor/48x48/apps) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/cockatrice.svg DESTINATION ${ICONDIR}/hicolor/scalable/apps) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cockatrice.desktop DESTINATION ${DESKTOPDIR}) + INSTALL(FILES ${cockatrice_QM} DESTINATION share/cockatrice/translations) + endif() +elseif(WIN32) + INSTALL(TARGETS cockatrice RUNTIME DESTINATION ./) + INSTALL(FILES ${cockatrice_QM} DESTINATION ./translations) +endif() + +if(APPLE) + # these needs to be relative to CMAKE_INSTALL_PREFIX + set(plugin_dest_dir cockatrice.app/Contents/Plugins) + set(qtconf_dest_dir cockatrice.app/Contents/Resources) + + # note: no codecs in qt5 + # note: phonon_backend => mediaservice + # note: needs platform on osx + + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*_debug\\.dylib") + else() + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/[^_]*\\.dylib") + endif() + + install(CODE " + file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] +Plugins = Plugins +Translations = Resources/translations +Data = Resources\") + " COMPONENT Runtime) + + install(CODE " + file(GLOB_RECURSE QTPLUGINS + \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dylib\") + set(BU_CHMOD_BUNDLE_ITEMS ON) + include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/cockatrice.app\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\") + " COMPONENT Runtime) +endif() + +if(WIN32) + # these needs to be relative to CMAKE_INSTALL_PREFIX + set(plugin_dest_dir Plugins) + set(qtconf_dest_dir .) + + # note: no codecs in qt5 + # note: phonon_backend => mediaservice + # note: needs platform on osx + + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*d\\.dll") + else() + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*[^d]\\.dll") + endif() + + install(CODE " + file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] +Plugins = Plugins +Translations = Resources/translations +Data = Resources\") + " COMPONENT Runtime) + + install(CODE " + file(GLOB_RECURSE QTPLUGINS + \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dll\") + set(BU_CHMOD_BUNDLE_ITEMS ON) + include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/cockatrice.exe\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\") + " COMPONENT Runtime) +endif() \ No newline at end of file diff --git a/cockatrice/Doxyfile b/cockatrice/Doxyfile deleted file mode 100644 index 0ae4b93f1..000000000 --- a/cockatrice/Doxyfile +++ /dev/null @@ -1,311 +0,0 @@ -# Doxyfile 1.5.5-KDevelop - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- -DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = clienttest -PROJECT_NUMBER = 0.1 -OUTPUT_DIRECTORY = -CREATE_SUBDIRS = NO -OUTPUT_LANGUAGE = English -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = YES -STRIP_FROM_PATH = /home/brukie/ -STRIP_FROM_INC_PATH = -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -QT_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = NO -DETAILS_AT_TOP = NO -INHERIT_DOCS = YES -SEPARATE_MEMBER_PAGES = NO -TAB_SIZE = 8 -ALIASES = -OPTIMIZE_OUTPUT_FOR_C = NO -OPTIMIZE_OUTPUT_JAVA = NO -OPTIMIZE_FOR_FORTRAN = NO -OPTIMIZE_OUTPUT_VHDL = NO -BUILTIN_STL_SUPPORT = NO -CPP_CLI_SUPPORT = NO -SIP_SUPPORT = NO -IDL_PROPERTY_SUPPORT = YES -DISTRIBUTE_GROUP_DOC = NO -SUBGROUPING = YES -TYPEDEF_HIDES_STRUCT = NO -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- -EXTRACT_ALL = NO -EXTRACT_PRIVATE = NO -EXTRACT_STATIC = NO -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -EXTRACT_ANON_NSPACES = NO -HIDE_UNDOC_MEMBERS = NO -HIDE_UNDOC_CLASSES = NO -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = YES -HIDE_SCOPE_NAMES = NO -SHOW_INCLUDE_FILES = YES -INLINE_INFO = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -SHOW_DIRECTORIES = NO -SHOW_FILES = YES -SHOW_NAMESPACES = YES -FILE_VERSION_FILTER = -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- -INPUT = /home/brukie/clienttest -INPUT_ENCODING = UTF-8 -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.d \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.idl \ - *.odl \ - *.cs \ - *.php \ - *.php3 \ - *.inc \ - *.m \ - *.mm \ - *.dox \ - *.py \ - *.f90 \ - *.f \ - *.vhd \ - *.vhdl \ - *.C \ - *.CC \ - *.C++ \ - *.II \ - *.I++ \ - *.H \ - *.HH \ - *.H++ \ - *.CS \ - *.PHP \ - *.PHP3 \ - *.M \ - *.MM \ - *.PY \ - *.F90 \ - *.F \ - *.VHD \ - *.VHDL \ - *.C \ - *.H \ - *.tlh \ - *.diff \ - *.patch \ - *.moc \ - *.xpm \ - *.dox -RECURSIVE = yes -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXCLUDE_SYMBOLS = -EXAMPLE_PATH = -EXAMPLE_PATTERNS = * -EXAMPLE_RECURSIVE = NO -IMAGE_PATH = -INPUT_FILTER = -FILTER_PATTERNS = -FILTER_SOURCE_FILES = NO -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- -SOURCE_BROWSER = NO -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = NO -REFERENCES_RELATION = NO -REFERENCES_LINK_SOURCE = YES -USE_HTAGS = NO -VERBATIM_HEADERS = YES -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- -ALPHABETICAL_INDEX = NO -COLS_IN_ALPHA_INDEX = 5 -IGNORE_PREFIX = -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_ALIGN_MEMBERS = YES -GENERATE_HTMLHELP = NO -GENERATE_DOCSET = NO -DOCSET_FEEDNAME = "Doxygen generated docs" -DOCSET_BUNDLE_ID = org.doxygen.Project -HTML_DYNAMIC_SECTIONS = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -BINARY_TOC = NO -TOC_EXPAND = NO -DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 4 -GENERATE_TREEVIEW = NONE -TREEVIEW_WIDTH = 250 -FORMULA_FONTSIZE = 10 -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- -GENERATE_LATEX = YES -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4wide -EXTRA_PACKAGES = -LATEX_HEADER = -PDF_HYPERLINKS = YES -USE_PDFLATEX = YES -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- -GENERATE_MAN = NO -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_LINKS = NO -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- -GENERATE_XML = yes -XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = -XML_PROGRAMLISTING = YES -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- -GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- -TAGFILES = -GENERATE_TAGFILE = clienttest.tag -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -PERL_PATH = /usr/bin/perl -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- -CLASS_DIAGRAMS = YES -MSCGEN_PATH = -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = NO -DOT_FONTNAME = FreeSans -DOT_FONTPATH = -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -GROUP_GRAPHS = YES -UML_LOOK = NO -TEMPLATE_RELATIONS = NO -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -CALLER_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DIRECTORY_GRAPH = YES -DOT_IMAGE_FORMAT = png -DOT_PATH = -DOTFILE_DIRS = -DOT_GRAPH_MAX_NODES = 50 -MAX_DOT_GRAPH_DEPTH = 1000 -DOT_TRANSPARENT = YES -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = YES -DOT_CLEANUP = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- -SEARCHENGINE = NO diff --git a/cockatrice/cockatrice.pro b/cockatrice/cockatrice.pro deleted file mode 100644 index 2f53911a4..000000000 --- a/cockatrice/cockatrice.pro +++ /dev/null @@ -1,207 +0,0 @@ -TEMPLATE = app -TARGET = cockatrice -DEPENDPATH += . src ../common -INCLUDEPATH += . src ../common -MOC_DIR = build -OBJECTS_DIR = build -RESOURCES = cockatrice.qrc -QT += network script svg -unix:!macx { - CONFIG += mobility - MOBILITY = multimedia -} else { - QT += multimedia -} - -HEADERS += src/abstractcounter.h \ - src/counter_general.h \ - src/dlg_creategame.h \ - src/dlg_connect.h \ - src/dlg_create_token.h \ - src/gamesmodel.h \ - src/abstractclient.h \ - src/remoteclient.h \ - src/window_main.h \ - src/cardzone.h \ - src/selectzone.h \ - src/player.h \ - src/playertarget.h \ - src/cardlist.h \ - src/abstractcarditem.h \ - src/carditem.h \ - src/tablezone.h \ - src/handzone.h \ - src/handcounter.h \ - src/carddatabase.h \ - src/gameview.h \ - src/gameselector.h \ - src/gametypemap.h \ - src/decklistmodel.h \ - src/dlg_load_deck_from_clipboard.h \ - src/dlg_load_remote_deck.h \ - src/cardinfowidget.h \ - src/messagelogwidget.h \ - src/zoneviewzone.h \ - src/zoneviewwidget.h \ - src/pilezone.h \ - src/stackzone.h \ - src/carddragitem.h \ - src/carddatabasemodel.h \ - src/window_deckeditor.h \ - src/setsmodel.h \ - src/window_sets.h \ - src/abstractgraphicsitem.h \ - src/abstractcarddragitem.h \ - src/dlg_settings.h \ - src/dlg_cardsearch.h \ - src/phasestoolbar.h \ - src/gamescene.h \ - src/arrowitem.h \ - src/arrowtarget.h \ - src/tab.h \ - src/tab_server.h \ - src/tab_room.h \ - src/tab_message.h \ - src/tab_game.h \ - src/tab_deck_storage.h \ - src/tab_supervisor.h \ - src/tab_admin.h \ - src/tab_userlists.h \ - src/chatview.h \ - src/userlist.h \ - src/userinfobox.h \ - src/remotedecklist_treewidget.h \ - src/deckview.h \ - src/playerlistwidget.h \ - src/pixmapgenerator.h \ - src/settingscache.h \ - src/localserver.h \ - src/localserverinterface.h \ - src/localclient.h \ - src/translation.h \ - src/priceupdater.h \ - src/soundengine.h \ - ../common/color.h \ - ../common/serializable_item.h \ - ../common/decklist.h \ - ../common/protocol.h \ - ../common/protocol_items.h \ - ../common/protocol_datastructures.h \ - ../common/rng_abstract.h \ - ../common/rng_sfmt.h \ - ../common/server.h \ - ../common/server_arrow.h \ - ../common/server_card.h \ - ../common/server_cardzone.h \ - ../common/server_room.h \ - ../common/server_counter.h \ - ../common/server_game.h \ - ../common/server_player.h \ - ../common/server_protocolhandler.h \ - ../common/server_arrowtarget.h - -SOURCES += src/abstractcounter.cpp \ - src/counter_general.cpp \ - src/dlg_creategame.cpp \ - src/dlg_connect.cpp \ - src/dlg_create_token.cpp \ - src/abstractclient.cpp \ - src/remoteclient.cpp \ - src/main.cpp \ - src/window_main.cpp \ - src/gamesmodel.cpp \ - src/player.cpp \ - src/playertarget.cpp \ - src/cardzone.cpp \ - src/selectzone.cpp \ - src/cardlist.cpp \ - src/abstractcarditem.cpp \ - src/carditem.cpp \ - src/tablezone.cpp \ - src/handzone.cpp \ - src/handcounter.cpp \ - src/carddatabase.cpp \ - src/gameview.cpp \ - src/gameselector.cpp \ - src/decklistmodel.cpp \ - src/dlg_load_deck_from_clipboard.cpp \ - src/dlg_load_remote_deck.cpp \ - src/cardinfowidget.cpp \ - src/messagelogwidget.cpp \ - src/zoneviewzone.cpp \ - src/zoneviewwidget.cpp \ - src/pilezone.cpp \ - src/stackzone.cpp \ - src/carddragitem.cpp \ - src/carddatabasemodel.cpp \ - src/window_deckeditor.cpp \ - src/setsmodel.cpp \ - src/window_sets.cpp \ - src/abstractgraphicsitem.cpp \ - src/abstractcarddragitem.cpp \ - src/dlg_settings.cpp \ - src/dlg_cardsearch.cpp \ - src/phasestoolbar.cpp \ - src/gamescene.cpp \ - src/arrowitem.cpp \ - src/arrowtarget.cpp \ - src/tab.cpp \ - src/tab_server.cpp \ - src/tab_room.cpp \ - src/tab_message.cpp \ - src/tab_game.cpp \ - src/tab_deck_storage.cpp \ - src/tab_supervisor.cpp \ - src/tab_admin.cpp \ - src/tab_userlists.cpp \ - src/chatview.cpp \ - src/userlist.cpp \ - src/userinfobox.cpp \ - src/remotedecklist_treewidget.cpp \ - src/deckview.cpp \ - src/playerlistwidget.cpp \ - src/pixmapgenerator.cpp \ - src/settingscache.cpp \ - src/localserver.cpp \ - src/localserverinterface.cpp \ - src/localclient.cpp \ - src/priceupdater.cpp \ - src/soundengine.cpp \ - ../common/serializable_item.cpp \ - ../common/decklist.cpp \ - ../common/protocol.cpp \ - ../common/protocol_items.cpp \ - ../common/protocol_datastructures.cpp \ - ../common/rng_abstract.cpp \ - ../common/rng_sfmt.cpp \ - ../common/sfmt/SFMT.c \ - ../common/server.cpp \ - ../common/server_card.cpp \ - ../common/server_cardzone.cpp \ - ../common/server_room.cpp \ - ../common/server_game.cpp \ - ../common/server_player.cpp \ - ../common/server_protocolhandler.cpp - -TRANSLATIONS += \ - translations/cockatrice_de.ts \ - translations/cockatrice_en.ts \ - translations/cockatrice_es.ts \ - translations/cockatrice_pt.ts \ - translations/cockatrice_pt-br.ts \ - translations/cockatrice_fr.ts \ - translations/cockatrice_it.ts \ - translations/cockatrice_ja.ts \ - translations/cockatrice_ru.ts \ - translations/cockatrice_cs.ts \ - translations/cockatrice_pl.ts \ - translations/cockatrice_sk.ts - -win32 { - RC_FILE = cockatrice.rc -} -macx { - ICON = resources/appicon.icns - CONFIG += x86 ppc x86_64 release - LIBS += -bind_at_load -} diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 6866e7789..451f9c1b3 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -1,6 +1,7 @@ resources/back.svg + resources/lock.svg resources/icon_delete.svg resources/icon_tab_changed.svg resources/icon_config_general.svg @@ -20,13 +21,15 @@ resources/phases/icon_phase_main2.svg resources/phases/icon_phase_cleanup.svg resources/phases/icon_phase_nextturn.svg + resources/icon_settings.svg resources/hand.svg resources/pencil.svg resources/icon_search.svg resources/icon_clearsearch.svg resources/icon_update.png + resources/icon_view.svg resources/hr.jpg - resources/appicon.svg + resources/cockatrice.svg resources/add_to_sideboard.svg resources/decrement.svg resources/increment.svg @@ -38,24 +41,19 @@ resources/icon_conceded.svg resources/icon_player.svg resources/icon_spectator.svg + + resources/replay_start.svg + resources/replay_stop.svg + resources/replay_fastforward.svg + resources/replay_rewind.svg + resources/replay_toend.svg + resources/replay_tostart.svg + resources/replay_pause.svg resources/genders/male.svg resources/genders/female.svg resources/genders/unknown.svg - translations/cockatrice_de.qm - translations/cockatrice_en.qm - translations/cockatrice_es.qm - translations/cockatrice_it.qm - translations/cockatrice_pt.qm - translations/cockatrice_fr.qm - translations/cockatrice_ja.qm - translations/cockatrice_ru.qm - resources/countries/ar.svg resources/countries/at.svg resources/countries/au.svg @@ -63,16 +61,20 @@ resources/countries/br.svg resources/countries/by.svg resources/countries/ca.svg + resources/countries/cl.svg resources/countries/cz.svg resources/countries/ch.svg resources/countries/cn.svg resources/countries/de.svg resources/countries/dk.svg + resources/countries/do.svg resources/countries/es.svg resources/countries/fi.svg resources/countries/fr.svg + resources/countries/ge.svg resources/countries/gr.svg resources/countries/gt.svg + resources/countries/hr.svg resources/countries/hu.svg resources/countries/ie.svg resources/countries/il.svg @@ -85,6 +87,7 @@ resources/countries/my.svg resources/countries/nl.svg resources/countries/no.svg + resources/countries/nz.svg resources/countries/pe.svg resources/countries/ph.svg resources/countries/pl.svg @@ -99,6 +102,7 @@ resources/countries/ua.svg resources/countries/uk.svg resources/countries/us.svg + resources/countries/ve.svg resources/countries/za.svg resources/counters/w.svg diff --git a/cockatrice/copying b/cockatrice/copying deleted file mode 100644 index 5b6e7c66c..000000000 --- a/cockatrice/copying +++ /dev/null @@ -1,340 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff --git a/cockatrice/resources/cockatrice.png b/cockatrice/resources/cockatrice.png new file mode 100644 index 000000000..ed79b88a0 Binary files /dev/null and b/cockatrice/resources/cockatrice.png differ diff --git a/cockatrice/resources/appicon.svg b/cockatrice/resources/cockatrice.svg similarity index 100% rename from cockatrice/resources/appicon.svg rename to cockatrice/resources/cockatrice.svg diff --git a/cockatrice/resources/countries/cl.svg b/cockatrice/resources/countries/cl.svg new file mode 100644 index 000000000..885743eb9 --- /dev/null +++ b/cockatrice/resources/countries/cl.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cockatrice/resources/countries/do.svg b/cockatrice/resources/countries/do.svg new file mode 100644 index 000000000..f3f806915 --- /dev/null +++ b/cockatrice/resources/countries/do.svg @@ -0,0 +1,3752 @@ + + + + + + + + + Dominican Republic + + + + + united_nations_member + caribbean + america + flag + sign + + + + + Caleb Moore + + + + + Caleb Moore + + + + + Caleb Moore + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cockatrice/resources/countries/ge.svg b/cockatrice/resources/countries/ge.svg new file mode 100644 index 000000000..8e3e0189e --- /dev/null +++ b/cockatrice/resources/countries/ge.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/cockatrice/resources/countries/hr.svg b/cockatrice/resources/countries/hr.svg new file mode 100644 index 000000000..c502bd3b5 --- /dev/null +++ b/cockatrice/resources/countries/hr.svg @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cockatrice/resources/countries/nz.svg b/cockatrice/resources/countries/nz.svg new file mode 100644 index 000000000..30cc93b98 --- /dev/null +++ b/cockatrice/resources/countries/nz.svg @@ -0,0 +1,35 @@ + + + +Flag of New Zealand + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cockatrice/resources/countries/ve.svg b/cockatrice/resources/countries/ve.svg new file mode 100644 index 000000000..1b58073bb --- /dev/null +++ b/cockatrice/resources/countries/ve.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cockatrice/resources/icon_settings.svg b/cockatrice/resources/icon_settings.svg new file mode 100644 index 000000000..f29af4d0f --- /dev/null +++ b/cockatrice/resources/icon_settings.svg @@ -0,0 +1,93 @@ + + + + + + + Ruota dentata grigia + + + + unsorted + + + + + Open Clip Art Library + + + + + Architetto Francesco Rollandin + + + + + Architetto Francesco Rollandin + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cockatrice/resources/icon_view.svg b/cockatrice/resources/icon_view.svg new file mode 100644 index 000000000..e5676d825 --- /dev/null +++ b/cockatrice/resources/icon_view.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + \ No newline at end of file diff --git a/cockatrice/resources/lock.svg b/cockatrice/resources/lock.svg new file mode 100644 index 000000000..51f4ff8f1 --- /dev/null +++ b/cockatrice/resources/lock.svg @@ -0,0 +1,173 @@ + + + + + + + + padlock + + + + icon + + lock + security + secure + padlock + signs_and_symbols + + + + + AJ Ashton + + + + + AJ Ashton + + + + + AJ Ashton + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cockatrice/resources/replay_fastforward.svg b/cockatrice/resources/replay_fastforward.svg new file mode 100644 index 000000000..f46a38c46 --- /dev/null +++ b/cockatrice/resources/replay_fastforward.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/cockatrice/resources/replay_pause.svg b/cockatrice/resources/replay_pause.svg new file mode 100644 index 000000000..89bf0b7eb --- /dev/null +++ b/cockatrice/resources/replay_pause.svg @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/cockatrice/resources/replay_rewind.svg b/cockatrice/resources/replay_rewind.svg new file mode 100644 index 000000000..9700cc0ae --- /dev/null +++ b/cockatrice/resources/replay_rewind.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/cockatrice/resources/replay_start.svg b/cockatrice/resources/replay_start.svg new file mode 100644 index 000000000..30af90d08 --- /dev/null +++ b/cockatrice/resources/replay_start.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/cockatrice/resources/replay_stop.svg b/cockatrice/resources/replay_stop.svg new file mode 100644 index 000000000..17da89956 --- /dev/null +++ b/cockatrice/resources/replay_stop.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/cockatrice/resources/replay_toend.svg b/cockatrice/resources/replay_toend.svg new file mode 100644 index 000000000..3f6b82b62 --- /dev/null +++ b/cockatrice/resources/replay_toend.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/cockatrice/resources/replay_tostart.svg b/cockatrice/resources/replay_tostart.svg new file mode 100644 index 000000000..9b8b09364 --- /dev/null +++ b/cockatrice/resources/replay_tostart.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/cockatrice/src/abstractcarddragitem.cpp b/cockatrice/src/abstractcarddragitem.cpp index ac6400462..14724db36 100644 --- a/cockatrice/src/abstractcarddragitem.cpp +++ b/cockatrice/src/abstractcarddragitem.cpp @@ -5,47 +5,47 @@ #include AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag) - : QGraphicsItem(), item(_item), hotSpot(_hotSpot) + : QGraphicsItem(), item(_item), hotSpot(_hotSpot) { - if (parentDrag) { - parentDrag->addChildDrag(this); - setZValue(2000000007 + hotSpot.x() * 1000000 + hotSpot.y() * 1000 + 1000); - } else { - if ((hotSpot.x() < 0) || (hotSpot.y() < 0)) { - qDebug() << "CardDragItem: coordinate overflow: x =" << hotSpot.x() << ", y =" << hotSpot.y(); - hotSpot = QPointF(); - } else if ((hotSpot.x() > CARD_WIDTH) || (hotSpot.y() > CARD_HEIGHT)) { - qDebug() << "CardDragItem: coordinate overflow: x =" << hotSpot.x() << ", y =" << hotSpot.y(); - hotSpot = QPointF(CARD_WIDTH, CARD_HEIGHT); - } - setCursor(Qt::ClosedHandCursor); - setZValue(2000000007); - } - if (item->getTapped()) - setTransform(QTransform().translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2).rotate(90).translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2)); + if (parentDrag) { + parentDrag->addChildDrag(this); + setZValue(2000000007 + hotSpot.x() * 1000000 + hotSpot.y() * 1000 + 1000); + } else { + if ((hotSpot.x() < 0) || (hotSpot.y() < 0)) { + qDebug() << "CardDragItem: coordinate overflow: x =" << hotSpot.x() << ", y =" << hotSpot.y(); + hotSpot = QPointF(); + } else if ((hotSpot.x() > CARD_WIDTH) || (hotSpot.y() > CARD_HEIGHT)) { + qDebug() << "CardDragItem: coordinate overflow: x =" << hotSpot.x() << ", y =" << hotSpot.y(); + hotSpot = QPointF(CARD_WIDTH, CARD_HEIGHT); + } + setCursor(Qt::ClosedHandCursor); + setZValue(2000000007); + } + if (item->getTapped()) + setTransform(QTransform().translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2).rotate(90).translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2)); - setCacheMode(DeviceCoordinateCache); + setCacheMode(DeviceCoordinateCache); } AbstractCardDragItem::~AbstractCardDragItem() { - qDebug("CardDragItem destructor"); - for (int i = 0; i < childDrags.size(); i++) - delete childDrags[i]; + qDebug("CardDragItem destructor"); + for (int i = 0; i < childDrags.size(); i++) + delete childDrags[i]; } void AbstractCardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - item->paint(painter, option, widget); + item->paint(painter, option, widget); } void AbstractCardDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - event->accept(); - updatePosition(event->scenePos()); + event->accept(); + updatePosition(event->scenePos()); } void AbstractCardDragItem::addChildDrag(AbstractCardDragItem *child) { - childDrags << child; + childDrags << child; } diff --git a/cockatrice/src/abstractcarddragitem.h b/cockatrice/src/abstractcarddragitem.h index 047c4f55f..d7c3a40e5 100644 --- a/cockatrice/src/abstractcarddragitem.h +++ b/cockatrice/src/abstractcarddragitem.h @@ -8,24 +8,25 @@ class CardZone; class CardInfo; class AbstractCardDragItem : public QObject, public QGraphicsItem { - Q_OBJECT + Q_OBJECT + Q_INTERFACES(QGraphicsItem) protected: - AbstractCardItem *item; - QPointF hotSpot; - QList childDrags; + AbstractCardItem *item; + QPointF hotSpot; + QList childDrags; public: - enum { Type = typeCardDrag }; - int type() const { return Type; } - AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); - ~AbstractCardDragItem(); - QRectF boundingRect() const { return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - AbstractCardItem *getItem() const { return item; } - QPointF getHotSpot() const { return hotSpot; } - void addChildDrag(AbstractCardDragItem *child); - virtual void updatePosition(const QPointF &cursorScenePos) = 0; + enum { Type = typeCardDrag }; + int type() const { return Type; } + AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); + ~AbstractCardDragItem(); + QRectF boundingRect() const { return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); } + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + AbstractCardItem *getItem() const { return item; } + QPointF getHotSpot() const { return hotSpot; } + void addChildDrag(AbstractCardDragItem *child); + virtual void updatePosition(const QPointF &cursorScenePos) = 0; protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); }; #endif diff --git a/cockatrice/src/abstractcarditem.cpp b/cockatrice/src/abstractcarditem.cpp index 186533995..018cc5e06 100644 --- a/cockatrice/src/abstractcarditem.cpp +++ b/cockatrice/src/abstractcarditem.cpp @@ -2,7 +2,10 @@ #include #include #include -#include +#include +#ifdef _WIN32 +#include "round.h" +#endif /* _WIN32 */ #include "carddatabase.h" #include "cardinfowidget.h" #include "abstractcarditem.h" @@ -11,233 +14,246 @@ #include "gamescene.h" #include -AbstractCardItem::AbstractCardItem(const QString &_name, Player *_owner, QGraphicsItem *parent) - : ArrowTarget(_owner, parent), infoWidget(0), name(_name), tapped(false), tapAngle(0), isHovered(false), realZValue(0) +AbstractCardItem::AbstractCardItem(const QString &_name, Player *_owner, int _id, QGraphicsItem *parent) + : ArrowTarget(_owner, parent), infoWidget(0), id(_id), name(_name), tapped(false), facedown(false), tapAngle(0), isHovered(false), realZValue(0) { - setCursor(Qt::OpenHandCursor); - setFlag(ItemIsSelectable); - setCacheMode(DeviceCoordinateCache); - - connect(db, SIGNAL(cardListChanged()), this, SLOT(cardInfoUpdated())); - connect(settingsCache, SIGNAL(displayCardNamesChanged()), this, SLOT(callUpdate())); - cardInfoUpdated(); + setCursor(Qt::OpenHandCursor); + setFlag(ItemIsSelectable); + setCacheMode(DeviceCoordinateCache); + + connect(db, SIGNAL(cardListChanged()), this, SLOT(cardInfoUpdated())); + connect(settingsCache, SIGNAL(displayCardNamesChanged()), this, SLOT(callUpdate())); + cardInfoUpdated(); } AbstractCardItem::~AbstractCardItem() { - qDebug() << "AbstractCardItem destructor:" << name; - emit deleteCardInfoPopup(name); + emit deleteCardInfoPopup(name); } QRectF AbstractCardItem::boundingRect() const { - return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); + return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); } void AbstractCardItem::pixmapUpdated() { - update(); + update(); + emit sigPixmapUpdated(); } void AbstractCardItem::cardInfoUpdated() { - info = db->getCard(name); - connect(info, SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated())); + info = db->getCard(name); + connect(info, SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated())); } void AbstractCardItem::setRealZValue(qreal _zValue) { - realZValue = _zValue; - setZValue(_zValue); + realZValue = _zValue; + setZValue(_zValue); } QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const { - return QSizeF( - painter->combinedTransform().map(QLineF(0, 0, boundingRect().width(), 0)).length(), - painter->combinedTransform().map(QLineF(0, 0, 0, boundingRect().height())).length() - ); + return QSizeF( + painter->combinedTransform().map(QLineF(0, 0, boundingRect().width(), 0)).length(), + painter->combinedTransform().map(QLineF(0, 0, 0, boundingRect().height())).length() + ); } void AbstractCardItem::transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle) { - QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect()); - - painter->resetTransform(); - - QTransform pixmapTransform; - pixmapTransform.translate(totalBoundingRect.width() / 2, totalBoundingRect.height() / 2); - pixmapTransform.rotate(angle); - pixmapTransform.translate(-translatedSize.width() / 2, -translatedSize.height() / 2); - painter->setTransform(pixmapTransform); + QRectF totalBoundingRect = painter->combinedTransform().mapRect(boundingRect()); + + painter->resetTransform(); + + QTransform pixmapTransform; + pixmapTransform.translate(totalBoundingRect.width() / 2, totalBoundingRect.height() / 2); + pixmapTransform.rotate(angle); + pixmapTransform.translate(-translatedSize.width() / 2, -translatedSize.height() / 2); + painter->setTransform(pixmapTransform); - QFont f; - int fontSize = round(translatedSize.height() / 8); - if (fontSize < 9) - fontSize = 9; - if (fontSize > 10) - fontSize = 10; - f.setPixelSize(fontSize); + QFont f; + int fontSize = round(translatedSize.height() / 8); + if (fontSize < 9) + fontSize = 9; + if (fontSize > 10) + fontSize = 10; + f.setPixelSize(fontSize); - painter->setFont(f); + painter->setFont(f); } void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle) { - qreal scaleFactor = translatedSize.width() / boundingRect().width(); - - QPixmap *translatedPixmap = info->getPixmap(translatedSize.toSize()); - painter->save(); - QColor bgColor = Qt::transparent; - if (translatedPixmap) { - painter->save(); - transformPainter(painter, translatedSize, angle); - painter->drawPixmap(QPointF(0, 0), *translatedPixmap); - painter->restore(); - } else { - QString colorStr; - if (!color.isEmpty()) - colorStr = color; - else if (info->getColors().size() > 1) - colorStr = "m"; - else if (!info->getColors().isEmpty()) - colorStr = info->getColors().first().toLower(); - - if (colorStr == "b") - bgColor = QColor(0, 0, 0); - else if (colorStr == "u") - bgColor = QColor(0, 140, 180); - else if (colorStr == "w") - bgColor = QColor(255, 250, 140); - else if (colorStr == "r") - bgColor = QColor(230, 0, 0); - else if (colorStr == "g") - bgColor = QColor(0, 160, 0); - else if (colorStr == "m") - bgColor = QColor(250, 190, 30); - else - bgColor = QColor(230, 230, 230); - } - painter->setBrush(bgColor); - QPen pen(Qt::black); - pen.setWidth(2); - painter->setPen(pen); - painter->drawRect(QRectF(1, 1, CARD_WIDTH - 2, CARD_HEIGHT - 2)); - - if (!translatedPixmap || settingsCache->getDisplayCardNames()) { - painter->save(); - transformPainter(painter, translatedSize, angle); - painter->setPen(Qt::white); - painter->setBackground(Qt::black); - painter->setBackgroundMode(Qt::OpaqueMode); - painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor, translatedSize.height() - 6 * scaleFactor), Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere, name); - painter->restore(); - } - - painter->restore(); + qreal scaleFactor = translatedSize.width() / boundingRect().width(); + + CardInfo *imageSource = facedown ? db->getCard() : info; + QPixmap *translatedPixmap = imageSource->getPixmap(translatedSize.toSize()); + painter->save(); + QColor bgColor = Qt::transparent; + if (translatedPixmap) { + painter->save(); + transformPainter(painter, translatedSize, angle); + painter->drawPixmap(QPointF(0, 0), *translatedPixmap); + painter->restore(); + } else { + QString colorStr; + if (!color.isEmpty()) + colorStr = color; + else if (info->getColors().size() > 1) + colorStr = "m"; + else if (!info->getColors().isEmpty()) + colorStr = info->getColors().first().toLower(); + + if (colorStr == "b") + bgColor = QColor(0, 0, 0); + else if (colorStr == "u") + bgColor = QColor(0, 140, 180); + else if (colorStr == "w") + bgColor = QColor(255, 250, 140); + else if (colorStr == "r") + bgColor = QColor(230, 0, 0); + else if (colorStr == "g") + bgColor = QColor(0, 160, 0); + else if (colorStr == "m") + bgColor = QColor(250, 190, 30); + else + bgColor = QColor(230, 230, 230); + } + painter->setBrush(bgColor); + QPen pen(Qt::black); + pen.setWidth(2); + painter->setPen(pen); + painter->drawRect(QRectF(1, 1, CARD_WIDTH - 2, CARD_HEIGHT - 2)); + + if (!translatedPixmap || settingsCache->getDisplayCardNames() || facedown) { + painter->save(); + transformPainter(painter, translatedSize, angle); + painter->setPen(Qt::white); + painter->setBackground(Qt::black); + painter->setBackgroundMode(Qt::OpaqueMode); + QString nameStr; + if (facedown) + nameStr = "# " + QString::number(id); + else + nameStr = name; + painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor, translatedSize.height() - 6 * scaleFactor), Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere, nameStr); + painter->restore(); + } + + painter->restore(); } -void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) +void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - painter->save(); + painter->save(); - QSizeF translatedSize = getTranslatedSize(painter); - paintPicture(painter, translatedSize, tapAngle); - - painter->save(); - painter->setRenderHint(QPainter::Antialiasing, false); - transformPainter(painter, translatedSize, tapAngle); - if (isSelected()) { - painter->setPen(Qt::red); - painter->drawRect(QRectF(0.5, 0.5, translatedSize.width() - 1, translatedSize.height() - 1)); - } else if (isHovered) { - painter->setPen(Qt::yellow); - painter->drawRect(QRectF(0.5, 0.5, translatedSize.width() - 1, translatedSize.height() - 1)); - } - painter->restore(); + QSizeF translatedSize = getTranslatedSize(painter); + paintPicture(painter, translatedSize, tapAngle); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + transformPainter(painter, translatedSize, tapAngle); + if (isSelected()) { + painter->setPen(Qt::red); + painter->drawRect(QRectF(0.5, 0.5, translatedSize.width() - 1, translatedSize.height() - 1)); + } else if (isHovered) { + painter->setPen(Qt::yellow); + painter->drawRect(QRectF(0.5, 0.5, translatedSize.width() - 1, translatedSize.height() - 1)); + } + painter->restore(); - painter->restore(); + painter->restore(); } void AbstractCardItem::setName(const QString &_name) { - if (name == _name) - return; - - emit deleteCardInfoPopup(name); - disconnect(info, 0, this, 0); - name = _name; - info = db->getCard(name); - connect(info, SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated())); - update(); + if (name == _name) + return; + + emit deleteCardInfoPopup(name); + disconnect(info, 0, this, 0); + name = _name; + info = db->getCard(name); + connect(info, SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated())); + update(); } void AbstractCardItem::setHovered(bool _hovered) { - if (isHovered == _hovered) - return; - - if (_hovered) - processHoverEvent(); - isHovered = _hovered; - setZValue(_hovered ? 2000000004 : realZValue); - update(); + if (isHovered == _hovered) + return; + + if (_hovered) + processHoverEvent(); + isHovered = _hovered; + setZValue(_hovered ? 2000000004 : realZValue); + update(); } void AbstractCardItem::setColor(const QString &_color) { - color = _color; - update(); + color = _color; + update(); } void AbstractCardItem::setTapped(bool _tapped, bool canAnimate) { - if (tapped == _tapped) - return; - - tapped = _tapped; - if (settingsCache->getTapAnimation() && canAnimate) - static_cast(scene())->registerAnimationItem(this); - else { - tapAngle = tapped ? 90 : 0; - setTransform(QTransform().translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2).rotate(tapAngle).translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2)); - update(); - } + if (tapped == _tapped) + return; + + tapped = _tapped; + if (settingsCache->getTapAnimation() && canAnimate) + static_cast(scene())->registerAnimationItem(this); + else { + tapAngle = tapped ? 90 : 0; + setTransform(QTransform().translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2).rotate(tapAngle).translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2)); + update(); + } +} + +void AbstractCardItem::setFaceDown(bool _facedown) +{ + facedown = _facedown; + update(); + emit updateCardMenu(this); } void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (!isSelected()) { - scene()->clearSelection(); - setSelected(true); - } - if (event->button() == Qt::LeftButton) - setCursor(Qt::ClosedHandCursor); - else if (event->button() == Qt::MidButton) - emit showCardInfoPopup(event->screenPos(), name); - event->accept(); + if (!isSelected()) { + scene()->clearSelection(); + setSelected(true); + } + if (event->button() == Qt::LeftButton) + setCursor(Qt::ClosedHandCursor); + else if (event->button() == Qt::MidButton) + emit showCardInfoPopup(event->screenPos(), name); + event->accept(); } void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::MidButton) - emit deleteCardInfoPopup(name); - - // This function ensures the parent function doesn't mess around with our selection. - event->accept(); + if (event->button() == Qt::MidButton) + emit deleteCardInfoPopup(name); + + // This function ensures the parent function doesn't mess around with our selection. + event->accept(); } void AbstractCardItem::processHoverEvent() { - emit hovered(this); + emit hovered(this); } QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { - if (change == ItemSelectedHasChanged) { - update(); - return value; - } else - return QGraphicsItem::itemChange(change, value); + if (change == ItemSelectedHasChanged) { + update(); + return value; + } else + return QGraphicsItem::itemChange(change, value); } diff --git a/cockatrice/src/abstractcarditem.h b/cockatrice/src/abstractcarditem.h index 2060537d1..7f68ddcd2 100644 --- a/cockatrice/src/abstractcarditem.h +++ b/cockatrice/src/abstractcarditem.h @@ -12,51 +12,59 @@ const int CARD_WIDTH = 72; const int CARD_HEIGHT = 102; class AbstractCardItem : public ArrowTarget { - Q_OBJECT + Q_OBJECT protected: - CardInfo *info; - CardInfoWidget *infoWidget; - QString name; - bool tapped; - int tapAngle; - QString color; + CardInfo *info; + CardInfoWidget *infoWidget; + int id; + QString name; + bool tapped; + bool facedown; + int tapAngle; + QString color; private: - bool isHovered; - qreal realZValue; + bool isHovered; + qreal realZValue; private slots: - void pixmapUpdated(); - void cardInfoUpdated(); - void callUpdate() { update(); } + void pixmapUpdated(); + void cardInfoUpdated(); + void callUpdate() { update(); } signals: - void hovered(AbstractCardItem *card); - void showCardInfoPopup(QPoint pos, QString cardName); - void deleteCardInfoPopup(QString cardName); + void hovered(AbstractCardItem *card); + void showCardInfoPopup(QPoint pos, QString cardName); + void deleteCardInfoPopup(QString cardName); + void updateCardMenu(AbstractCardItem *card); + void sigPixmapUpdated(); public: - enum { Type = typeCard }; - int type() const { return Type; } - AbstractCardItem(const QString &_name = QString(), Player *_owner = 0, QGraphicsItem *parent = 0); - ~AbstractCardItem(); - QRectF boundingRect() const; - QSizeF getTranslatedSize(QPainter *painter) const; - void paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - CardInfo *getInfo() const { return info; } - QString getName() const { return name; } - void setName(const QString &_name = QString()); - qreal getRealZValue() const { return realZValue; } - void setRealZValue(qreal _zValue); - void setHovered(bool _hovered); - QString getColor() const { return color; } - void setColor(const QString &_color); - bool getTapped() const { return tapped; } - void setTapped(bool _tapped, bool canAnimate = false); - void processHoverEvent(); - void deleteCardInfoPopup() { emit deleteCardInfoPopup(name); } + enum { Type = typeCard }; + int type() const { return Type; } + AbstractCardItem(const QString &_name = QString(), Player *_owner = 0, int _id = -1, QGraphicsItem *parent = 0); + ~AbstractCardItem(); + QRectF boundingRect() const; + QSizeF getTranslatedSize(QPainter *painter) const; + void paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + CardInfo *getInfo() const { return info; } + int getId() const { return id; } + void setId(int _id) { id = _id; } + QString getName() const { return name; } + void setName(const QString &_name = QString()); + qreal getRealZValue() const { return realZValue; } + void setRealZValue(qreal _zValue); + void setHovered(bool _hovered); + QString getColor() const { return color; } + void setColor(const QString &_color); + bool getTapped() const { return tapped; } + void setTapped(bool _tapped, bool canAnimate = false); + bool getFaceDown() const { return facedown; } + void setFaceDown(bool _facedown); + void processHoverEvent(); + void deleteCardInfoPopup() { emit deleteCardInfoPopup(name); } protected: - void transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value); + void transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value); }; #endif diff --git a/cockatrice/src/abstractclient.cpp b/cockatrice/src/abstractclient.cpp index 7edb7a2ea..f2e8915bd 100644 --- a/cockatrice/src/abstractclient.cpp +++ b/cockatrice/src/abstractclient.cpp @@ -1,79 +1,162 @@ #include "abstractclient.h" -#include "protocol.h" -#include "protocol_items.h" -#include + +#include "pending_command.h" +#include "pb/commands.pb.h" +#include "pb/server_message.pb.h" +#include "pb/event_server_identification.pb.h" +#include "pb/event_server_message.pb.h" +#include "pb/event_server_shutdown.pb.h" +#include "pb/event_connection_closed.pb.h" +#include "pb/event_user_message.pb.h" +#include "pb/event_list_rooms.pb.h" +#include "pb/event_add_to_list.pb.h" +#include "pb/event_remove_from_list.pb.h" +#include "pb/event_user_joined.pb.h" +#include "pb/event_user_left.pb.h" +#include "pb/event_game_joined.pb.h" +#include "pb/event_replay_added.pb.h" +#include "get_pb_extension.h" +#include +#include "client_metatypes.h" AbstractClient::AbstractClient(QObject *parent) - : QObject(parent), status(StatusDisconnected) + : QObject(parent), nextCmdId(0), status(StatusDisconnected) { + qRegisterMetaType("QVariant"); + qRegisterMetaType("CommandContainer"); + qRegisterMetaType("Response"); + qRegisterMetaType("Response::ResponseCode"); + qRegisterMetaType("ClientStatus"); + qRegisterMetaType("RoomEvent"); + qRegisterMetaType("GameEventContainer"); + qRegisterMetaType("Event_ServerIdentification"); + qRegisterMetaType("Event_ConnectionClosed"); + qRegisterMetaType("Event_ServerShutdown"); + qRegisterMetaType("Event_AddToList"); + qRegisterMetaType("Event_RemoveFromList"); + qRegisterMetaType("Event_UserJoined"); + qRegisterMetaType("Event_UserLeft"); + qRegisterMetaType("Event_ServerMessage"); + qRegisterMetaType("Event_ListRooms"); + qRegisterMetaType("Event_GameJoined"); + qRegisterMetaType("Event_UserMessage"); + qRegisterMetaType("ServerInfo_User"); + qRegisterMetaType >("QList"); + qRegisterMetaType("Event_ReplayAdded"); + + connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *))); } AbstractClient::~AbstractClient() { } -void AbstractClient::processProtocolItem(ProtocolItem *item) +void AbstractClient::processProtocolItem(const ServerMessage &item) { - ProtocolResponse *response = qobject_cast(item); - if (response) { - CommandContainer *cmdCont = pendingCommands.value(response->getCmdId(), 0); - if (!cmdCont) - return; - - pendingCommands.remove(cmdCont->getCmdId()); - cmdCont->processResponse(response); - if (response->getReceiverMayDelete()) - delete response; - cmdCont->deleteLater(); - - return; - } - - GenericEvent *genericEvent = qobject_cast(item); - if (genericEvent) { - switch (genericEvent->getItemId()) { - case ItemId_Event_ConnectionClosed: emit connectionClosedEventReceived(static_cast(item)); break; - case ItemId_Event_ServerShutdown: emit serverShutdownEventReceived(static_cast(item)); break; - case ItemId_Event_AddToList: emit addToListEventReceived(static_cast(item)); break; - case ItemId_Event_RemoveFromList: emit removeFromListEventReceived(static_cast(item)); break; - case ItemId_Event_UserJoined: emit userJoinedEventReceived(static_cast(item)); break; - case ItemId_Event_UserLeft: emit userLeftEventReceived(static_cast(item)); break; - case ItemId_Event_ServerMessage: emit serverMessageEventReceived(static_cast(item)); break; - case ItemId_Event_ListRooms: emit listRoomsEventReceived(static_cast(item)); break; - case ItemId_Event_GameJoined: emit gameJoinedEventReceived(static_cast(item)); break; - case ItemId_Event_Message: emit messageEventReceived(static_cast(item)); break; - } - if (genericEvent->getReceiverMayDelete()) - delete genericEvent; - return; - } - - GameEventContainer *gameEventContainer = qobject_cast(item); - if (gameEventContainer) { - emit gameEventContainerReceived(gameEventContainer); - if (gameEventContainer->getReceiverMayDelete()) - delete gameEventContainer; - return; - } - - RoomEvent *roomEvent = qobject_cast(item); - if (roomEvent) { - emit roomEventReceived(roomEvent); - if (roomEvent->getReceiverMayDelete()) - delete roomEvent; - return; - } + switch (item.message_type()) { + case ServerMessage::RESPONSE: { + const Response &response = item.response(); + const int cmdId = response.cmd_id(); + + PendingCommand *pend = pendingCommands.value(cmdId, 0); + if (!pend) + return; + pendingCommands.remove(cmdId); + + pend->processResponse(response); + pend->deleteLater(); + break; + } + case ServerMessage::SESSION_EVENT: { + const SessionEvent &event = item.session_event(); + switch ((SessionEvent::SessionEventType) getPbExtension(event)) { + case SessionEvent::SERVER_IDENTIFICATION: emit serverIdentificationEventReceived(event.GetExtension(Event_ServerIdentification::ext)); break; + case SessionEvent::SERVER_MESSAGE: emit serverMessageEventReceived(event.GetExtension(Event_ServerMessage::ext)); break; + case SessionEvent::SERVER_SHUTDOWN: emit serverShutdownEventReceived(event.GetExtension(Event_ServerShutdown::ext)); break; + case SessionEvent::CONNECTION_CLOSED: emit connectionClosedEventReceived(event.GetExtension(Event_ConnectionClosed::ext)); break; + case SessionEvent::USER_MESSAGE: emit userMessageEventReceived(event.GetExtension(Event_UserMessage::ext)); break; + case SessionEvent::LIST_ROOMS: emit listRoomsEventReceived(event.GetExtension(Event_ListRooms::ext)); break; + case SessionEvent::ADD_TO_LIST: emit addToListEventReceived(event.GetExtension(Event_AddToList::ext)); break; + case SessionEvent::REMOVE_FROM_LIST: emit removeFromListEventReceived(event.GetExtension(Event_RemoveFromList::ext)); break; + case SessionEvent::USER_JOINED: emit userJoinedEventReceived(event.GetExtension(Event_UserJoined::ext)); break; + case SessionEvent::USER_LEFT: emit userLeftEventReceived(event.GetExtension(Event_UserLeft::ext)); break; + case SessionEvent::GAME_JOINED: emit gameJoinedEventReceived(event.GetExtension(Event_GameJoined::ext)); break; + case SessionEvent::REPLAY_ADDED: emit replayAddedEventReceived(event.GetExtension(Event_ReplayAdded::ext)); break; + default: break; + } + break; + } + case ServerMessage::GAME_EVENT_CONTAINER: { + emit gameEventContainerReceived(item.game_event_container()); + break; + } + case ServerMessage::ROOM_EVENT: { + emit roomEventReceived(item.room_event()); + break; + } + } } void AbstractClient::setStatus(const ClientStatus _status) { - if (_status != status) { - status = _status; - emit statusChanged(_status); - } + QMutexLocker locker(&clientMutex); + if (_status != status) { + status = _status; + emit statusChanged(_status); + } } -void AbstractClient::sendCommand(Command *cmd) +void AbstractClient::sendCommand(const CommandContainer &cont) { - sendCommandContainer(new CommandContainer(QList() << cmd)); + sendCommand(new PendingCommand(cont)); +} + +void AbstractClient::sendCommand(PendingCommand *pend) +{ + pend->moveToThread(thread()); + emit sigQueuePendingCommand(pend); +} + +void AbstractClient::queuePendingCommand(PendingCommand *pend) +{ + // This function is always called from the client thread via signal/slot. + const int cmdId = getNewCmdId(); + pend->getCommandContainer().set_cmd_id(cmdId); + + pendingCommands.insert(cmdId, pend); + + sendCommandContainer(pend->getCommandContainer()); +} + +PendingCommand *AbstractClient::prepareSessionCommand(const ::google::protobuf::Message &cmd) +{ + CommandContainer cont; + SessionCommand *c = cont.add_session_command(); + c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); + return new PendingCommand(cont); +} + +PendingCommand *AbstractClient::prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId) +{ + CommandContainer cont; + RoomCommand *c = cont.add_room_command(); + cont.set_room_id(roomId); + c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); + return new PendingCommand(cont); +} + +PendingCommand *AbstractClient::prepareModeratorCommand(const ::google::protobuf::Message &cmd) +{ + CommandContainer cont; + ModeratorCommand *c = cont.add_moderator_command(); + c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); + return new PendingCommand(cont); +} + +PendingCommand *AbstractClient::prepareAdminCommand(const ::google::protobuf::Message &cmd) +{ + CommandContainer cont; + AdminCommand *c = cont.add_admin_command(); + c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); + return new PendingCommand(cont); } diff --git a/cockatrice/src/abstractclient.h b/cockatrice/src/abstractclient.h index d896a89dc..4dc9af2bf 100644 --- a/cockatrice/src/abstractclient.h +++ b/cockatrice/src/abstractclient.h @@ -2,16 +2,17 @@ #define ABSTRACTCLIENT_H #include -#include "protocol_datastructures.h" +#include +#include +#include "pb/response.pb.h" +#include "pb/serverinfo_user.pb.h" -class Command; -class CommandContainer; -class ProtocolItem; -class ProtocolResponse; -class TopLevelProtocolItem; +class PendingCommand; class CommandContainer; class RoomEvent; class GameEventContainer; +class ServerMessage; +class Event_ServerIdentification; class Event_AddToList; class Event_RemoveFromList; class Event_UserJoined; @@ -19,57 +20,73 @@ class Event_UserLeft; class Event_ServerMessage; class Event_ListRooms; class Event_GameJoined; -class Event_Message; +class Event_UserMessage; class Event_ConnectionClosed; class Event_ServerShutdown; +class Event_ReplayAdded; enum ClientStatus { - StatusDisconnected, - StatusDisconnecting, - StatusConnecting, - StatusAwaitingWelcome, - StatusLoggingIn, - StatusLoggedIn, + StatusDisconnected, + StatusDisconnecting, + StatusConnecting, + StatusAwaitingWelcome, + StatusLoggingIn, + StatusLoggedIn }; class AbstractClient : public QObject { - Q_OBJECT + Q_OBJECT signals: - void statusChanged(ClientStatus _status); - void serverError(ResponseCode resp); - - // Room events - void roomEventReceived(RoomEvent *event); - // Game events - void gameEventContainerReceived(GameEventContainer *event); - // Generic events - void connectionClosedEventReceived(Event_ConnectionClosed *event); - void serverShutdownEventReceived(Event_ServerShutdown *event); - void addToListEventReceived(Event_AddToList *event); - void removeFromListEventReceived(Event_RemoveFromList *event); - void userJoinedEventReceived(Event_UserJoined *event); - void userLeftEventReceived(Event_UserLeft *event); - void serverMessageEventReceived(Event_ServerMessage *event); - void listRoomsEventReceived(Event_ListRooms *event); - void gameJoinedEventReceived(Event_GameJoined *event); - void messageEventReceived(Event_Message *event); - void userInfoChanged(ServerInfo_User *userInfo); - void buddyListReceived(const QList &buddyList); - void ignoreListReceived(const QList &ignoreList); + void statusChanged(ClientStatus _status); + + // Room events + void roomEventReceived(const RoomEvent &event); + // Game events + void gameEventContainerReceived(const GameEventContainer &event); + // Session events + void serverIdentificationEventReceived(const Event_ServerIdentification &event); + void connectionClosedEventReceived(const Event_ConnectionClosed &event); + void serverShutdownEventReceived(const Event_ServerShutdown &event); + void addToListEventReceived(const Event_AddToList &event); + void removeFromListEventReceived(const Event_RemoveFromList &event); + void userJoinedEventReceived(const Event_UserJoined &event); + void userLeftEventReceived(const Event_UserLeft &event); + void serverMessageEventReceived(const Event_ServerMessage &event); + void listRoomsEventReceived(const Event_ListRooms &event); + void gameJoinedEventReceived(const Event_GameJoined &event); + void userMessageEventReceived(const Event_UserMessage &event); + void userInfoChanged(const ServerInfo_User &userInfo); + void buddyListReceived(const QList &buddyList); + void ignoreListReceived(const QList &ignoreList); + void replayAddedEventReceived(const Event_ReplayAdded &event); + + void sigQueuePendingCommand(PendingCommand *pend); +private: + int nextCmdId; + mutable QMutex clientMutex; + ClientStatus status; +private slots: + void queuePendingCommand(PendingCommand *pend); protected slots: - void processProtocolItem(ProtocolItem *item); + void processProtocolItem(const ServerMessage &item); protected: - QMap pendingCommands; - ClientStatus status; - QString userName, password; - void setStatus(ClientStatus _status); + QMap pendingCommands; + QString userName, password; + void setStatus(ClientStatus _status); + int getNewCmdId() { return nextCmdId++; } + virtual void sendCommandContainer(const CommandContainer &cont) = 0; public: - AbstractClient(QObject *parent = 0); - ~AbstractClient(); - - ClientStatus getStatus() const { return status; } - virtual void sendCommand(Command *cmd); - virtual void sendCommandContainer(CommandContainer *cont) = 0; + AbstractClient(QObject *parent = 0); + ~AbstractClient(); + + ClientStatus getStatus() const { QMutexLocker locker(&clientMutex); return status; } + void sendCommand(const CommandContainer &cont); + void sendCommand(PendingCommand *pend); + + static PendingCommand *prepareSessionCommand(const ::google::protobuf::Message &cmd); + static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId); + static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd); + static PendingCommand *prepareAdminCommand(const ::google::protobuf::Message &cmd); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/abstractcounter.cpp b/cockatrice/src/abstractcounter.cpp index 5a361708a..b9debae40 100644 --- a/cockatrice/src/abstractcounter.cpp +++ b/cockatrice/src/abstractcounter.cpp @@ -1,130 +1,155 @@ #include "abstractcounter.h" #include "player.h" -#include "protocol_items.h" #include #include #include #include #include +#include "pb/command_inc_counter.pb.h" +#include "pb/command_set_counter.pb.h" AbstractCounter::AbstractCounter(Player *_player, int _id, const QString &_name, bool _shownInCounterArea, int _value, QGraphicsItem *parent) - : QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value), hovered(false), aDec(0), aInc(0), dialogSemaphore(false), deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea) + : QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value), hovered(false), aDec(0), aInc(0), dialogSemaphore(false), deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea) { - setAcceptsHoverEvents(true); - - if (player->getLocal()) { - menu = new QMenu(name); - aSet = new QAction(this); - connect(aSet, SIGNAL(triggered()), this, SLOT(setCounter())); - menu->addAction(aSet); - menu->addSeparator(); - for (int i = -10; i <= 10; ++i) - if (i == 0) - menu->addSeparator(); - else { - QAction *aIncrement = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this); - if (i == -1) - aDec = aIncrement; - else if (i == 1) - aInc = aIncrement; - aIncrement->setData(i); - connect(aIncrement, SIGNAL(triggered()), this, SLOT(incrementCounter())); - menu->addAction(aIncrement); - } - } else - menu = 0; - - retranslateUi(); +#if QT_VERSION < 0x050000 + setAcceptsHoverEvents(true); +#else + setAcceptHoverEvents(true); +#endif + + if (player->getLocal()) { + menu = new QMenu(name); + aSet = new QAction(this); + connect(aSet, SIGNAL(triggered()), this, SLOT(setCounter())); + menu->addAction(aSet); + menu->addSeparator(); + for (int i = -10; i <= 10; ++i) + if (i == 0) + menu->addSeparator(); + else { + QAction *aIncrement = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this); + if (i == -1) + aDec = aIncrement; + else if (i == 1) + aInc = aIncrement; + aIncrement->setData(i); + connect(aIncrement, SIGNAL(triggered()), this, SLOT(incrementCounter())); + menu->addAction(aIncrement); + } + } else + menu = 0; + + retranslateUi(); } AbstractCounter::~AbstractCounter() { - delete menu; + delete menu; } void AbstractCounter::delCounter() { - if (dialogSemaphore) - deleteAfterDialog = true; - else - deleteLater(); + if (dialogSemaphore) + deleteAfterDialog = true; + else + deleteLater(); } void AbstractCounter::retranslateUi() { - if (menu) { - aSet->setText(tr("&Set counter...")); - } + if (menu) { + aSet->setText(tr("&Set counter...")); + } } void AbstractCounter::setShortcutsActive() { - if (name == "life") { - aSet->setShortcut(tr("Ctrl+L")); - aDec->setShortcut(tr("F11")); - aInc->setShortcut(tr("F12")); - } + if (name == "life") { + aSet->setShortcut(tr("Ctrl+L")); + aDec->setShortcut(tr("F11")); + aInc->setShortcut(tr("F12")); + } } void AbstractCounter::setShortcutsInactive() { - if (name == "life") { - aSet->setShortcut(QKeySequence()); - aDec->setShortcut(QKeySequence()); - aInc->setShortcut(QKeySequence()); - } + if (name == "life") { + aSet->setShortcut(QKeySequence()); + aDec->setShortcut(QKeySequence()); + aInc->setShortcut(QKeySequence()); + } } void AbstractCounter::setValue(int _value) { - value = _value; - update(); + value = _value; + update(); } void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - player->sendGameCommand(new Command_IncCounter(-1, id, 1)); - event->accept(); - } else if (event->button() == Qt::RightButton) { - player->sendGameCommand(new Command_IncCounter(-1, id, -1)); - event->accept(); - } else if (event->button() == Qt::MidButton) { - if (menu) - menu->exec(event->screenPos()); - event->accept(); - } else - event->ignore(); + if (event->button() == Qt::LeftButton) { + Command_IncCounter cmd; + cmd.set_counter_id(id); + cmd.set_delta(1); + player->sendGameCommand(cmd); + event->accept(); + } else if (event->button() == Qt::RightButton) { + Command_IncCounter cmd; + cmd.set_counter_id(id); + cmd.set_delta(-1); + player->sendGameCommand(cmd); + event->accept(); + } else if (event->button() == Qt::MidButton) { + if (menu) + menu->exec(event->screenPos()); + event->accept(); + } else + event->ignore(); } void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/) { - hovered = true; - update(); + hovered = true; + update(); } void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/) { - hovered = false; - update(); + hovered = false; + update(); } void AbstractCounter::incrementCounter() { - int delta = static_cast(sender())->data().toInt(); - player->sendGameCommand(new Command_IncCounter(-1, id, delta)); + const int delta = static_cast(sender())->data().toInt(); + Command_IncCounter cmd; + cmd.set_counter_id(id); + cmd.set_delta(delta); + player->sendGameCommand(cmd); } void AbstractCounter::setCounter() { - bool ok; - dialogSemaphore = true; - int newValue = QInputDialog::getInteger(0, tr("Set counter"), tr("New value for counter '%1':").arg(name), value, -2000000000, 2000000000, 1, &ok); - if (deleteAfterDialog) { - deleteLater(); - return; - } - dialogSemaphore = false; - if (ok) - player->sendGameCommand(new Command_SetCounter(-1, id, newValue)); + bool ok; + dialogSemaphore = true; + int newValue = +#if QT_VERSION < 0x050000 + QInputDialog::getInteger( +#else + QInputDialog::getInt( +#endif + 0, tr("Set counter"), tr("New value for counter '%1':").arg(name), value, -2000000000, 2000000000, 1, &ok); + if (deleteAfterDialog) { + deleteLater(); + return; + } + dialogSemaphore = false; + if (!ok) + return; + + Command_SetCounter cmd; + cmd.set_counter_id(id); + cmd.set_value(newValue); + player->sendGameCommand(cmd); } diff --git a/cockatrice/src/abstractcounter.h b/cockatrice/src/abstractcounter.h index 6a4a41880..174cbf841 100644 --- a/cockatrice/src/abstractcounter.h +++ b/cockatrice/src/abstractcounter.h @@ -8,41 +8,42 @@ class QMenu; class QAction; class AbstractCounter : public QObject, public QGraphicsItem { - Q_OBJECT + Q_OBJECT + Q_INTERFACES(QGraphicsItem) protected: - Player *player; - int id; - QString name; - int value; - bool hovered; - - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void hoverEnterEvent(QGraphicsSceneHoverEvent *event); - void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + Player *player; + int id; + QString name; + int value; + bool hovered; + + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); private: - QAction *aSet, *aDec, *aInc; - QMenu *menu; - bool dialogSemaphore, deleteAfterDialog; - bool shownInCounterArea; + QAction *aSet, *aDec, *aInc; + QMenu *menu; + bool dialogSemaphore, deleteAfterDialog; + bool shownInCounterArea; private slots: - void incrementCounter(); - void setCounter(); + void incrementCounter(); + void setCounter(); public: - AbstractCounter(Player *_player, int _id, const QString &_name, bool _shownInCounterArea, int _value, QGraphicsItem *parent = 0); - ~AbstractCounter(); - - QMenu *getMenu() const { return menu; } - void retranslateUi(); - - int getId() const { return id; } - QString getName() const { return name; } - bool getShownInCounterArea() const { return shownInCounterArea; } - int getValue() const { return value; } - void setValue(int _value); - void delCounter(); - - void setShortcutsActive(); - void setShortcutsInactive(); + AbstractCounter(Player *_player, int _id, const QString &_name, bool _shownInCounterArea, int _value, QGraphicsItem *parent = 0); + ~AbstractCounter(); + + QMenu *getMenu() const { return menu; } + void retranslateUi(); + + int getId() const { return id; } + QString getName() const { return name; } + bool getShownInCounterArea() const { return shownInCounterArea; } + int getValue() const { return value; } + void setValue(int _value); + void delCounter(); + + void setShortcutsActive(); + void setShortcutsInactive(); }; #endif diff --git a/cockatrice/src/abstractgraphicsitem.cpp b/cockatrice/src/abstractgraphicsitem.cpp index 47b21bbcd..c0cc330c4 100644 --- a/cockatrice/src/abstractgraphicsitem.cpp +++ b/cockatrice/src/abstractgraphicsitem.cpp @@ -3,48 +3,48 @@ void AbstractGraphicsItem::paintNumberEllipse(int number, int fontSize, const QColor &color, int position, int count, QPainter *painter) { - painter->save(); + painter->save(); - QString numStr = QString::number(number); - QFont font("Serif"); - font.setPixelSize(fontSize); - font.setWeight(QFont::Bold); - - QFontMetrics fm(font); - double w = fm.width(numStr) * 1.3; - double h = fm.height() * 1.3; - if (w < h) - w = h; + QString numStr = QString::number(number); + QFont font("Serif"); + font.setPixelSize(fontSize); + font.setWeight(QFont::Bold); + + QFontMetrics fm(font); + double w = fm.width(numStr) * 1.3; + double h = fm.height() * 1.3; + if (w < h) + w = h; - painter->setPen(QColor(255, 255, 255, 0)); - QRadialGradient grad(QPointF(0.5, 0.5), 0.5); - grad.setCoordinateMode(QGradient::ObjectBoundingMode); - QColor color1(color), color2(color); - color1.setAlpha(255); - color2.setAlpha(0); - grad.setColorAt(0, color1); - grad.setColorAt(0.8, color1); - grad.setColorAt(1, color2); - painter->setBrush(QBrush(grad)); - - QRectF textRect; - if (position == -1) - textRect = QRectF((boundingRect().width() - w) / 2.0, (boundingRect().height() - h) / 2.0, w, h); - else { - qreal xOffset = 10; - qreal yOffset = 20; - qreal spacing = 2; - if (position < 2) - textRect = QRectF(count == 1 ? ((boundingRect().width() - w) / 2.0) : (position % 2 == 0 ? xOffset : (boundingRect().width() - xOffset - w)), yOffset, w, h); - else - textRect = QRectF(count == 3 ? ((boundingRect().width() - w) / 2.0) : (position % 2 == 0 ? xOffset : (boundingRect().width() - xOffset - w)), yOffset + (spacing + h) * (position / 2), w, h); - } - - painter->drawEllipse(textRect); + painter->setPen(QColor(255, 255, 255, 0)); + QRadialGradient grad(QPointF(0.5, 0.5), 0.5); + grad.setCoordinateMode(QGradient::ObjectBoundingMode); + QColor color1(color), color2(color); + color1.setAlpha(255); + color2.setAlpha(0); + grad.setColorAt(0, color1); + grad.setColorAt(0.8, color1); + grad.setColorAt(1, color2); + painter->setBrush(QBrush(grad)); + + QRectF textRect; + if (position == -1) + textRect = QRectF((boundingRect().width() - w) / 2.0, (boundingRect().height() - h) / 2.0, w, h); + else { + qreal xOffset = 10; + qreal yOffset = 20; + qreal spacing = 2; + if (position < 2) + textRect = QRectF(count == 1 ? ((boundingRect().width() - w) / 2.0) : (position % 2 == 0 ? xOffset : (boundingRect().width() - xOffset - w)), yOffset, w, h); + else + textRect = QRectF(count == 3 ? ((boundingRect().width() - w) / 2.0) : (position % 2 == 0 ? xOffset : (boundingRect().width() - xOffset - w)), yOffset + (spacing + h) * (position / 2), w, h); + } + + painter->drawEllipse(textRect); - painter->setPen(Qt::black); - painter->setFont(font); - painter->drawText(textRect, Qt::AlignCenter, numStr); + painter->setPen(Qt::black); + painter->setFont(font); + painter->drawText(textRect, Qt::AlignCenter, numStr); - painter->restore(); + painter->restore(); } diff --git a/cockatrice/src/abstractgraphicsitem.h b/cockatrice/src/abstractgraphicsitem.h index 2bb094d30..f838dcd3b 100644 --- a/cockatrice/src/abstractgraphicsitem.h +++ b/cockatrice/src/abstractgraphicsitem.h @@ -4,20 +4,21 @@ #include enum GraphicsItemType { - typeCard = QGraphicsItem::UserType + 1, - typeCardDrag = QGraphicsItem::UserType + 2, - typeZone = QGraphicsItem::UserType + 3, - typePlayerTarget = QGraphicsItem::UserType + 4, - typeDeckViewCardContainer = QGraphicsItem::UserType + 5, - typeOther = QGraphicsItem::UserType + 6 + typeCard = QGraphicsItem::UserType + 1, + typeCardDrag = QGraphicsItem::UserType + 2, + typeZone = QGraphicsItem::UserType + 3, + typePlayerTarget = QGraphicsItem::UserType + 4, + typeDeckViewCardContainer = QGraphicsItem::UserType + 5, + typeOther = QGraphicsItem::UserType + 6 }; class AbstractGraphicsItem : public QObject, public QGraphicsItem { - Q_OBJECT + Q_OBJECT + Q_INTERFACES(QGraphicsItem) protected: - void paintNumberEllipse(int number, int radius, const QColor &color, int position, int count, QPainter *painter); + void paintNumberEllipse(int number, int radius, const QColor &color, int position, int count, QPainter *painter); public: - AbstractGraphicsItem(QGraphicsItem *parent = 0) : QObject(), QGraphicsItem(parent) { } + AbstractGraphicsItem(QGraphicsItem *parent = 0) : QObject(), QGraphicsItem(parent) { } }; #endif diff --git a/cockatrice/src/arrowitem.cpp b/cockatrice/src/arrowitem.cpp index 1dc1bf2f7..969be2b43 100644 --- a/cockatrice/src/arrowitem.cpp +++ b/cockatrice/src/arrowitem.cpp @@ -1,136 +1,145 @@ +#define _USE_MATH_DEFINES +#include + #include "arrowitem.h" #include "playertarget.h" #include "carditem.h" #include "cardzone.h" #include "player.h" -#include "math.h" -#include "protocol_items.h" #include #include #include #include +#include "color.h" +#include "pb/command_attach_card.pb.h" +#include "pb/command_create_arrow.pb.h" +#include "pb/command_delete_arrow.pb.h" + ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color) : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color), fullColor(true) { - qDebug() << "ArrowItem constructor: startItem=" << static_cast(startItem); - setZValue(2000000005); - - if (startItem) - startItem->addArrowFrom(this); - if (targetItem) - targetItem->addArrowTo(this); - - if (startItem && targetItem) - updatePath(); + qDebug() << "ArrowItem constructor: startItem=" << static_cast(startItem); + setZValue(2000000005); + + if (startItem) + startItem->addArrowFrom(this); + if (targetItem) + targetItem->addArrowTo(this); + + if (startItem && targetItem) + updatePath(); } ArrowItem::~ArrowItem() { - qDebug() << "ArrowItem destructor"; + qDebug() << "ArrowItem destructor"; } void ArrowItem::delArrow() { - if (startItem) { - startItem->removeArrowFrom(this); - startItem = 0; - } - - if (targetItem) { - targetItem->setBeingPointedAt(false); - targetItem->removeArrowTo(this); - targetItem = 0; - } - - player->removeArrow(this); - deleteLater(); + if (startItem) { + startItem->removeArrowFrom(this); + startItem = 0; + } + + if (targetItem) { + targetItem->setBeingPointedAt(false); + targetItem->removeArrowTo(this); + targetItem = 0; + } + + player->removeArrow(this); + deleteLater(); } void ArrowItem::updatePath() { - if (!targetItem) - return; - - QPointF endPoint = targetItem->mapToScene(QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2)); - updatePath(endPoint); + if (!targetItem) + return; + + QPointF endPoint = targetItem->mapToScene(QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2)); + updatePath(endPoint); } void ArrowItem::updatePath(const QPointF &endPoint) { - const double arrowWidth = 15.0; - const double headWidth = 40.0; - const double headLength = headWidth / sqrt(2); - const double phi = 15; - - if (!startItem) - return; - - QPointF startPoint = startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2)); - QLineF line(startPoint, endPoint); - qreal lineLength = line.length(); - - prepareGeometryChange(); - if (lineLength < 30) - path = QPainterPath(); - else { - QPointF c(lineLength / 2, tan(phi * M_PI / 180) * lineLength); - - QPainterPath centerLine; - centerLine.moveTo(0, 0); - centerLine.quadTo(c, QPointF(lineLength, 0)); - - double percentage = 1 - headLength / lineLength; - QPointF arrowBodyEndPoint = centerLine.pointAtPercent(percentage); - QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(percentage + 0.001)); - qreal alpha = testLine.angle() - 90; - QPointF endPoint1 = arrowBodyEndPoint + arrowWidth / 2 * QPointF(cos(alpha * M_PI / 180), -sin(alpha * M_PI / 180)); - QPointF endPoint2 = arrowBodyEndPoint + arrowWidth / 2 * QPointF(-cos(alpha * M_PI / 180), sin(alpha * M_PI / 180)); - QPointF point1 = endPoint1 + (headWidth - arrowWidth) / 2 * QPointF(cos(alpha * M_PI / 180), -sin(alpha * M_PI / 180)); - QPointF point2 = endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-cos(alpha * M_PI / 180), sin(alpha * M_PI / 180)); - - path = QPainterPath(-arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180))); - path.quadTo(c, endPoint1); - path.lineTo(point1); - path.lineTo(QPointF(lineLength, 0)); - path.lineTo(point2); - path.lineTo(endPoint2); - path.quadTo(c, arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180))); - path.lineTo(-arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180))); - } - - setPos(startPoint); - setTransform(QTransform().rotate(-line.angle())); + const double arrowWidth = 15.0; + const double headWidth = 40.0; + const double headLength = headWidth / pow(2, 0.5); // aka headWidth / sqrt (2) but this produces a compile error with MSVC++ + const double phi = 15; + + if (!startItem) + return; + + QPointF startPoint = startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2)); + QLineF line(startPoint, endPoint); + qreal lineLength = line.length(); + + prepareGeometryChange(); + if (lineLength < 30) + path = QPainterPath(); + else { + QPointF c(lineLength / 2, tan(phi * M_PI / 180) * lineLength); + + QPainterPath centerLine; + centerLine.moveTo(0, 0); + centerLine.quadTo(c, QPointF(lineLength, 0)); + + double percentage = 1 - headLength / lineLength; + QPointF arrowBodyEndPoint = centerLine.pointAtPercent(percentage); + QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(percentage + 0.001)); + qreal alpha = testLine.angle() - 90; + QPointF endPoint1 = arrowBodyEndPoint + arrowWidth / 2 * QPointF(cos(alpha * M_PI / 180), -sin(alpha * M_PI / 180)); + QPointF endPoint2 = arrowBodyEndPoint + arrowWidth / 2 * QPointF(-cos(alpha * M_PI / 180), sin(alpha * M_PI / 180)); + QPointF point1 = endPoint1 + (headWidth - arrowWidth) / 2 * QPointF(cos(alpha * M_PI / 180), -sin(alpha * M_PI / 180)); + QPointF point2 = endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-cos(alpha * M_PI / 180), sin(alpha * M_PI / 180)); + + path = QPainterPath(-arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180))); + path.quadTo(c, endPoint1); + path.lineTo(point1); + path.lineTo(QPointF(lineLength, 0)); + path.lineTo(point2); + path.lineTo(endPoint2); + path.quadTo(c, arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180))); + path.lineTo(-arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180))); + } + + setPos(startPoint); + setTransform(QTransform().rotate(-line.angle())); } void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QColor paintColor(color); - if (fullColor) - paintColor.setAlpha(200); - else - paintColor.setAlpha(150); - painter->setBrush(paintColor); - painter->drawPath(path); + QColor paintColor(color); + if (fullColor) + paintColor.setAlpha(200); + else + paintColor.setAlpha(150); + painter->setBrush(paintColor); + painter->drawPath(path); } void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (!player->getLocal()) { - event->ignore(); - return; - } - - QList colliding = scene()->items(event->scenePos()); - for (int i = 0; i < colliding.size(); ++i) - if (qgraphicsitem_cast(colliding[i])) { - event->ignore(); - return; - } - - event->accept(); - if (event->button() == Qt::RightButton) - player->sendGameCommand(new Command_DeleteArrow(-1, id)); + if (!player->getLocal()) { + event->ignore(); + return; + } + + QList colliding = scene()->items(event->scenePos()); + for (int i = 0; i < colliding.size(); ++i) + if (qgraphicsitem_cast(colliding[i])) { + event->ignore(); + return; + } + + event->accept(); + if (event->button() == Qt::RightButton) { + Command_DeleteArrow cmd; + cmd.set_arrow_id(id); + player->sendGameCommand(cmd); + } } ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color) @@ -140,93 +149,85 @@ ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QCol void ArrowDragItem::addChildArrow(ArrowDragItem *childArrow) { - childArrows.append(childArrow); + childArrows.append(childArrow); } void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - // This ensures that if a mouse move event happens after a call to delArrow(), - // the event will be discarded as it would create some stray pointers. - if (!startItem) - return; - - QPointF endPos = event->scenePos(); - - QList colliding = scene()->items(endPos); - ArrowTarget *cursorItem = 0; - qreal cursorItemZ = -1; - for (int i = colliding.size() - 1; i >= 0; i--) - if (qgraphicsitem_cast(colliding.at(i)) || qgraphicsitem_cast(colliding.at(i))) - if (colliding.at(i)->zValue() > cursorItemZ) { - cursorItem = static_cast(colliding.at(i)); - cursorItemZ = cursorItem->zValue(); - } - if ((cursorItem != targetItem) && targetItem) { - targetItem->setBeingPointedAt(false); - targetItem->removeArrowTo(this); - } + // This ensures that if a mouse move event happens after a call to delArrow(), + // the event will be discarded as it would create some stray pointers. + if (!startItem) + return; + + QPointF endPos = event->scenePos(); + + QList colliding = scene()->items(endPos); + ArrowTarget *cursorItem = 0; + qreal cursorItemZ = -1; + for (int i = colliding.size() - 1; i >= 0; i--) + if (qgraphicsitem_cast(colliding.at(i)) || qgraphicsitem_cast(colliding.at(i))) + if (colliding.at(i)->zValue() > cursorItemZ) { + cursorItem = static_cast(colliding.at(i)); + cursorItemZ = cursorItem->zValue(); + } + if ((cursorItem != targetItem) && targetItem) { + targetItem->setBeingPointedAt(false); + targetItem->removeArrowTo(this); + } if (!cursorItem) { - fullColor = false; - targetItem = 0; - updatePath(endPos); - } else { - if (cursorItem != targetItem) { - fullColor = true; - if (cursorItem != startItem) { - cursorItem->setBeingPointedAt(true); - cursorItem->addArrowTo(this); - } - targetItem = cursorItem; - } - updatePath(); - } - update(); - - for (int i = 0; i < childArrows.size(); ++i) - childArrows[i]->mouseMoveEvent(event); + fullColor = false; + targetItem = 0; + updatePath(endPos); + } else { + if (cursorItem != targetItem) { + fullColor = true; + if (cursorItem != startItem) { + cursorItem->setBeingPointedAt(true); + cursorItem->addArrowTo(this); + } + targetItem = cursorItem; + } + updatePath(); + } + update(); + + for (int i = 0; i < childArrows.size(); ++i) + childArrows[i]->mouseMoveEvent(event); } void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (!startItem) - return; - - if (targetItem && (targetItem != startItem)) { - CardZone *startZone = static_cast(startItem)->getZone(); - // For now, we can safely assume that the start item is always a card. - // The target item can be a player as well. - CardItem *startCard = qgraphicsitem_cast(startItem); - CardItem *targetCard = qgraphicsitem_cast(targetItem); - if (targetCard) { - CardZone *targetZone = targetCard->getZone(); - player->sendGameCommand(new Command_CreateArrow( - -1, - startZone->getPlayer()->getId(), - startZone->getName(), - startCard->getId(), - targetZone->getPlayer()->getId(), - targetZone->getName(), - targetCard->getId(), - color - )); - } else { - PlayerTarget *targetPlayer = qgraphicsitem_cast(targetItem); - player->sendGameCommand(new Command_CreateArrow( - -1, - startZone->getPlayer()->getId(), - startZone->getName(), - startCard->getId(), - targetPlayer->getOwner()->getId(), - QString(), - -1, - color - )); - } - } - delArrow(); + if (!startItem) + return; + + if (targetItem && (targetItem != startItem)) { + CardZone *startZone = static_cast(startItem)->getZone(); + // For now, we can safely assume that the start item is always a card. + // The target item can be a player as well. + CardItem *startCard = qgraphicsitem_cast(startItem); + CardItem *targetCard = qgraphicsitem_cast(targetItem); - for (int i = 0; i < childArrows.size(); ++i) - childArrows[i]->mouseReleaseEvent(event); + Command_CreateArrow cmd; + cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color)); + cmd.set_start_player_id(startZone->getPlayer()->getId()); + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_start_card_id(startCard->getId()); + + if (targetCard) { + CardZone *targetZone = targetCard->getZone(); + cmd.set_target_player_id(targetZone->getPlayer()->getId()); + cmd.set_target_zone(targetZone->getName().toStdString()); + cmd.set_target_card_id(targetCard->getId()); + } else { + PlayerTarget *targetPlayer = qgraphicsitem_cast(targetItem); + cmd.set_target_player_id(targetPlayer->getOwner()->getId()); + } + player->sendGameCommand(cmd); + } + delArrow(); + + for (int i = 0; i < childArrows.size(); ++i) + childArrows[i]->mouseReleaseEvent(event); } ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem) @@ -236,57 +237,57 @@ ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem) void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if (!startItem) - return; + if (!startItem) + return; - QPointF endPos = event->scenePos(); - - QList colliding = scene()->items(endPos); + QPointF endPos = event->scenePos(); + + QList colliding = scene()->items(endPos); ArrowTarget *cursorItem = 0; - qreal cursorItemZ = -1; + qreal cursorItemZ = -1; for (int i = colliding.size() - 1; i >= 0; i--) if (qgraphicsitem_cast(colliding.at(i))) - if (colliding.at(i)->zValue() > cursorItemZ) { - cursorItem = static_cast(colliding.at(i)); - cursorItemZ = cursorItem->zValue(); - } - - if ((cursorItem != targetItem) && targetItem) - targetItem->setBeingPointedAt(false); + if (colliding.at(i)->zValue() > cursorItemZ) { + cursorItem = static_cast(colliding.at(i)); + cursorItemZ = cursorItem->zValue(); + } + + if ((cursorItem != targetItem) && targetItem) + targetItem->setBeingPointedAt(false); if (!cursorItem) { - fullColor = false; - targetItem = 0; - updatePath(endPos); - } else { - fullColor = true; - if (cursorItem != startItem) - cursorItem->setBeingPointedAt(true); - targetItem = cursorItem; - updatePath(); - } - update(); + fullColor = false; + targetItem = 0; + updatePath(endPos); + } else { + fullColor = true; + if (cursorItem != startItem) + cursorItem->setBeingPointedAt(true); + targetItem = cursorItem; + updatePath(); + } + update(); } void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/) { - if (!startItem) - return; + if (!startItem) + return; - if (targetItem && (targetItem != startItem)) { - CardItem *startCard = qgraphicsitem_cast(startItem); - CardZone *startZone = startCard->getZone(); - CardItem *targetCard = qgraphicsitem_cast(targetItem); - CardZone *targetZone = targetCard->getZone(); + if (targetItem && (targetItem != startItem)) { + CardItem *startCard = qgraphicsitem_cast(startItem); + CardZone *startZone = startCard->getZone(); + CardItem *targetCard = qgraphicsitem_cast(targetItem); + CardZone *targetZone = targetCard->getZone(); + + Command_AttachCard cmd; + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_card_id(startCard->getId()); + cmd.set_target_player_id(targetZone->getPlayer()->getId()); + cmd.set_target_zone(targetZone->getName().toStdString()); + cmd.set_target_card_id(targetCard->getId()); - player->sendGameCommand(new Command_AttachCard( - -1, - startZone->getName(), - startCard->getId(), - targetZone->getPlayer()->getId(), - targetZone->getName(), - targetCard->getId() - )); - } - - delArrow(); + player->sendGameCommand(cmd); + } + + delArrow(); } diff --git a/cockatrice/src/arrowitem.h b/cockatrice/src/arrowitem.h index f97c83e7f..7e897d31d 100644 --- a/cockatrice/src/arrowitem.h +++ b/cockatrice/src/arrowitem.h @@ -10,54 +10,55 @@ class Player; class ArrowTarget; class ArrowItem : public QObject, public QGraphicsItem { - Q_OBJECT + Q_OBJECT + Q_INTERFACES(QGraphicsItem) private: - QPainterPath path; - QMenu *menu; + QPainterPath path; + QMenu *menu; protected: - Player *player; - int id; - ArrowTarget *startItem, *targetItem; - QColor color; - bool fullColor; - void mousePressEvent(QGraphicsSceneMouseEvent *event); + Player *player; + int id; + ArrowTarget *startItem, *targetItem; + QColor color; + bool fullColor; + void mousePressEvent(QGraphicsSceneMouseEvent *event); public: - ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color); - ~ArrowItem(); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - QRectF boundingRect() const { return path.boundingRect(); } - QPainterPath shape() const { return path; } - void updatePath(); - void updatePath(const QPointF &endPoint); - - int getId() const { return id; } - Player *getPlayer() const { return player; } - void setStartItem(ArrowTarget *_item) { startItem = _item; } - void setTargetItem(ArrowTarget *_item) { targetItem = _item; } - ArrowTarget *getStartItem() const { return startItem; } - ArrowTarget *getTargetItem() const { return targetItem; } - void delArrow(); + ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color); + ~ArrowItem(); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QRectF boundingRect() const { return path.boundingRect(); } + QPainterPath shape() const { return path; } + void updatePath(); + void updatePath(const QPointF &endPoint); + + int getId() const { return id; } + Player *getPlayer() const { return player; } + void setStartItem(ArrowTarget *_item) { startItem = _item; } + void setTargetItem(ArrowTarget *_item) { targetItem = _item; } + ArrowTarget *getStartItem() const { return startItem; } + ArrowTarget *getTargetItem() const { return targetItem; } + void delArrow(); }; class ArrowDragItem : public ArrowItem { - Q_OBJECT + Q_OBJECT private: - QList childArrows; + QList childArrows; public: - ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color); - void addChildArrow(ArrowDragItem *childArrow); + ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color); + void addChildArrow(ArrowDragItem *childArrow); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); }; class ArrowAttachItem : public ArrowItem { - Q_OBJECT + Q_OBJECT public: - ArrowAttachItem(ArrowTarget *_startItem); + ArrowAttachItem(ArrowTarget *_startItem); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); }; #endif // ARROWITEM_H diff --git a/cockatrice/src/arrowtarget.cpp b/cockatrice/src/arrowtarget.cpp index 1740eb955..f20405a51 100644 --- a/cockatrice/src/arrowtarget.cpp +++ b/cockatrice/src/arrowtarget.cpp @@ -3,24 +3,24 @@ #include "player.h" ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent) - : AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false) + : AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false) { } ArrowTarget::~ArrowTarget() { - for (int i = 0; i < arrowsFrom.size(); ++i) { - arrowsFrom[i]->setStartItem(0); - arrowsFrom[i]->delArrow(); - } - for (int i = 0; i < arrowsTo.size(); ++i) { - arrowsTo[i]->setTargetItem(0); - arrowsTo[i]->delArrow(); - } + for (int i = 0; i < arrowsFrom.size(); ++i) { + arrowsFrom[i]->setStartItem(0); + arrowsFrom[i]->delArrow(); + } + for (int i = 0; i < arrowsTo.size(); ++i) { + arrowsTo[i]->setTargetItem(0); + arrowsTo[i]->delArrow(); + } } void ArrowTarget::setBeingPointedAt(bool _beingPointedAt) { - beingPointedAt = _beingPointedAt; - update(); + beingPointedAt = _beingPointedAt; + update(); } diff --git a/cockatrice/src/arrowtarget.h b/cockatrice/src/arrowtarget.h index dc3e2036e..31fcf157d 100644 --- a/cockatrice/src/arrowtarget.h +++ b/cockatrice/src/arrowtarget.h @@ -8,27 +8,28 @@ class Player; class ArrowItem; class ArrowTarget : public AbstractGraphicsItem { + Q_OBJECT protected: - Player *owner; + Player *owner; private: - bool beingPointedAt; - QList arrowsFrom, arrowsTo; + bool beingPointedAt; + QList arrowsFrom, arrowsTo; public: - ArrowTarget(Player *_owner, QGraphicsItem *parent = 0); - ~ArrowTarget(); - - Player *getOwner() const { return owner; } - - void setBeingPointedAt(bool _beingPointedAt); - bool getBeingPointedAt() const { return beingPointedAt; } - - const QList &getArrowsFrom() const { return arrowsFrom; } - void addArrowFrom(ArrowItem *arrow) { arrowsFrom.append(arrow); } - void removeArrowFrom(ArrowItem *arrow) { arrowsFrom.removeAt(arrowsFrom.indexOf(arrow)); } - - const QList &getArrowsTo() const { return arrowsTo; } - void addArrowTo(ArrowItem *arrow) { arrowsTo.append(arrow); } - void removeArrowTo(ArrowItem *arrow) { arrowsTo.removeAt(arrowsTo.indexOf(arrow)); } + ArrowTarget(Player *_owner, QGraphicsItem *parent = 0); + ~ArrowTarget(); + + Player *getOwner() const { return owner; } + + void setBeingPointedAt(bool _beingPointedAt); + bool getBeingPointedAt() const { return beingPointedAt; } + + const QList &getArrowsFrom() const { return arrowsFrom; } + void addArrowFrom(ArrowItem *arrow) { arrowsFrom.append(arrow); } + void removeArrowFrom(ArrowItem *arrow) { arrowsFrom.removeAt(arrowsFrom.indexOf(arrow)); } + + const QList &getArrowsTo() const { return arrowsTo; } + void addArrowTo(ArrowItem *arrow) { arrowsTo.append(arrow); } + void removeArrowTo(ArrowItem *arrow) { arrowsTo.removeAt(arrowsTo.indexOf(arrow)); } }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp index 65df3884a..79a225ece 100644 --- a/cockatrice/src/carddatabase.cpp +++ b/cockatrice/src/carddatabase.cpp @@ -13,740 +13,939 @@ #include #include #include +#include -CardSet::CardSet(const QString &_shortName, const QString &_longName) - : shortName(_shortName), longName(_longName) +const int CardDatabase::versionNeeded = 3; + +static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSet *set) { - updateSortKey(); + xml.writeStartElement("set"); + xml.writeTextElement("name", set->getShortName()); + xml.writeTextElement("longname", set->getLongName()); + xml.writeEndElement(); + + return xml; } -QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSet *set) +CardSet::CardSet(const QString &_shortName, const QString &_longName) + : shortName(_shortName), longName(_longName) { - xml.writeStartElement("set"); - xml.writeTextElement("name", set->getShortName()); - xml.writeTextElement("longname", set->getLongName()); - xml.writeEndElement(); + updateSortKey(); +} - return xml; +QString CardSet::getCorrectedShortName() const +{ + // Because windows is horrible. + QSet invalidFileNames; + invalidFileNames << "CON" << "PRN" << "AUX" << "NUL" << "COM1" << "COM2" << + "COM3" << "COM4" << "COM5" << "COM6" << "COM7" << "COM8" << "COM9" << + "LPT1" << "LPT2" << "LPT3" << "LPT4" << "LPT5" << "LPT6" << "LPT7" << + "LPT8" << "LPT9"; + + return invalidFileNames.contains(shortName) ? shortName + "_" : shortName; } void CardSet::setSortKey(unsigned int _sortKey) { - sortKey = _sortKey; + sortKey = _sortKey; - QSettings settings; - settings.beginGroup("sets"); - settings.beginGroup(shortName); - settings.setValue("sortkey", sortKey); + QSettings settings; + settings.beginGroup("sets"); + settings.beginGroup(shortName); + settings.setValue("sortkey", sortKey); } void CardSet::updateSortKey() { - QSettings settings; - settings.beginGroup("sets"); - settings.beginGroup(shortName); - sortKey = settings.value("sortkey", 0).toInt(); + QSettings settings; + settings.beginGroup("sets"); + settings.beginGroup(shortName); + sortKey = settings.value("sortkey", 0).toInt(); } class SetList::CompareFunctor { public: - inline bool operator()(CardSet *a, CardSet *b) const - { - return a->getSortKey() < b->getSortKey(); - } + inline bool operator()(CardSet *a, CardSet *b) const + { + return a->getSortKey() < b->getSortKey(); + } }; void SetList::sortByKey() { - qSort(begin(), end(), CompareFunctor()); + qSort(begin(), end(), CompareFunctor()); } PictureToLoad::PictureToLoad(CardInfo *_card, bool _stripped, bool _hq) - : card(_card), stripped(_stripped), setIndex(0), hq(_hq) + : card(_card), stripped(_stripped), setIndex(0), hq(_hq) { - if (card) { - sortedSets = card->getSets(); - sortedSets.sortByKey(); - } + if (card) { + sortedSets = card->getSets(); + sortedSets.sortByKey(); + } } bool PictureToLoad::nextSet() { - if (setIndex == sortedSets.size() - 1) - return false; - ++setIndex; - return true; + if (setIndex == sortedSets.size() - 1) + return false; + ++setIndex; + return true; } -PictureLoader::PictureLoader(QObject *parent) - : QObject(parent), downloadRunning(false), loadQueueRunning(false) +QString PictureToLoad::getSetName() const { - connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection); - - networkManager = new QNetworkAccessManager(this); - connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *))); + if (setIndex < sortedSets.size()) + return sortedSets[setIndex]->getCorrectedShortName(); + else + return QString(""); +} + +PictureLoader::PictureLoader(const QString &__picsPath, bool _picDownload, bool _picDownloadHq, QObject *parent) + : QObject(parent), + _picsPath(__picsPath), picDownload(_picDownload), picDownloadHq(_picDownloadHq), + downloadRunning(false), loadQueueRunning(false) +{ + connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection); + + networkManager = new QNetworkAccessManager(this); + connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *))); +} + +PictureLoader::~PictureLoader() +{ + // This does not work with the destroyed() signal as this destructor is called after the main event loop is done. + thread()->quit(); } void PictureLoader::processLoadQueue() { - if (loadQueueRunning) - return; - - loadQueueRunning = true; - forever { - mutex.lock(); - if (loadQueue.isEmpty()) { - mutex.unlock(); - loadQueueRunning = false; - return; - } - PictureToLoad ptl = loadQueue.takeFirst(); - mutex.unlock(); - QString correctedName = ptl.getCard()->getCorrectedName(); - QString picsPath = _picsPath; - QString setName = ptl.getSetName(); - - QImage image; - if (!image.load(QString("%1/%2/%3.full.jpg").arg(picsPath).arg(setName).arg(correctedName))) - if (!image.load(QString("%1/%2/%3%4.full.jpg").arg(picsPath).arg(setName).arg(correctedName).arg(1))) - if (!image.load(QString("%1/%2/%3/%4.full.jpg").arg(picsPath).arg("downloadedPics").arg(setName).arg(correctedName))) { - if (picDownload) { - cardsToDownload.append(ptl); - if (!downloadRunning) - startNextPicDownload(); - } else { - if (ptl.nextSet()) - loadQueue.prepend(ptl); - else - emit imageLoaded(ptl.getCard(), QImage()); - } - continue; - } - - emit imageLoaded(ptl.getCard(), image); - } + if (loadQueueRunning) + return; + + loadQueueRunning = true; + forever { + mutex.lock(); + if (loadQueue.isEmpty()) { + mutex.unlock(); + loadQueueRunning = false; + return; + } + PictureToLoad ptl = loadQueue.takeFirst(); + mutex.unlock(); + + //The list of paths to the folders in which to search for images + QList picsPaths = QList() << _picsPath + "/CUSTOM/" + ptl.getCard()->getCorrectedName() + ".full"; + + + QString setName=ptl.getSetName(); + if(!setName.isEmpty()) + { + picsPaths << _picsPath + "/" + setName + "/" + ptl.getCard()->getCorrectedName() + ".full" + << _picsPath + "/downloadedPics/" + setName + "/" + ptl.getCard()->getCorrectedName() + ".full"; + } + + QImage image; + QImageReader imgReader; + imgReader.setDecideFormatFromContent(true); + bool found = false; + + //Iterates through the list of paths, searching for images with the desired name with any QImageReader-supported extension + for (int i = 0; i < picsPaths.length() && !found; i ++) { + imgReader.setFileName(picsPaths.at(i)); + if (imgReader.read(&image)) { + emit imageLoaded(ptl.getCard(), image); + found = true; + } + } + + if (!found) { + if (picDownload) { + cardsToDownload.append(ptl); + if (!downloadRunning) + startNextPicDownload(); + } else { + if (ptl.nextSet()) + loadQueue.prepend(ptl); + else + emit imageLoaded(ptl.getCard(), QImage()); + } + } + } +} + +QString PictureLoader::getPicUrl(CardInfo *card) +{ + if (!picDownload) return QString(""); + + CardSet *set = card->getPreferredSet(); + QString picUrl = QString(""); + + // if sets have been defined for the card, they can contain custom picUrls + if(set) + { + // first check if Hq is enabled and a custom Hq card url exists in cards.xml + if(picDownloadHq) + { + picUrl = card->getCustomPicURLHq(set->getShortName()); + if (!picUrl.isEmpty()) + return picUrl; + } + + // then, test for a custom, non-Hq card url in cards.xml + picUrl = card->getCustomPicURL(set->getShortName()); + if (!picUrl.isEmpty()) + return picUrl; + } + + // otherwise, fallback to the default url + picUrl = picDownloadHq ? settingsCache->getPicUrlHq() : settingsCache->getPicUrl(); + picUrl.replace("!name!", QUrl::toPercentEncoding(card->getCorrectedName())); + + if (set) { + picUrl.replace("!setcode!", QUrl::toPercentEncoding(set->getShortName())); + picUrl.replace("!setname!", QUrl::toPercentEncoding(set->getLongName())); + } + int muid = card->getPreferredMuId(); + if (muid) + picUrl.replace("!cardid!", QUrl::toPercentEncoding(QString::number(muid))); + + if (picUrl.contains("!name!") || + picUrl.contains("!setcode!") || + picUrl.contains("!setname!") || + picUrl.contains("!cardid!")) { + qDebug() << "Insufficient card data to download" << card->getName() << "Url:" << picUrl; + return QString(""); + } + + return picUrl; } void PictureLoader::startNextPicDownload() { - if (cardsToDownload.isEmpty()) { - cardBeingDownloaded = 0; - downloadRunning = false; - return; - } - - downloadRunning = true; - - cardBeingDownloaded = cardsToDownload.takeFirst(); - QString picUrl; - if (cardBeingDownloaded.getStripped()) - picUrl = cardBeingDownloaded.getCard()->getPicURLSt(cardBeingDownloaded.getSetName()); - else if (cardBeingDownloaded.getHq()) { - picUrl = cardBeingDownloaded.getCard()->getPicURLHq(cardBeingDownloaded.getSetName()); - if (picUrl.isEmpty()) - picUrl = cardBeingDownloaded.getCard()->getPicURL(cardBeingDownloaded.getSetName()); - } else - picUrl = cardBeingDownloaded.getCard()->getPicURL(cardBeingDownloaded.getSetName()); - QUrl url(picUrl); - - QNetworkRequest req(url); - qDebug() << "starting picture download:" << req.url(); - networkManager->get(req); + if (cardsToDownload.isEmpty()) { + cardBeingDownloaded = 0; + downloadRunning = false; + return; + } + + downloadRunning = true; + + cardBeingDownloaded = cardsToDownload.takeFirst(); + + QString picUrl = getPicUrl(cardBeingDownloaded.getCard()); + if (picUrl.isEmpty()) { + qDebug() << "No url for" << cardBeingDownloaded.getCard()->getName(); + cardBeingDownloaded = 0; + downloadRunning = false; + return; + } + + QUrl url(picUrl); + + QNetworkRequest req(url); + qDebug() << "starting picture download:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url(); + networkManager->get(req); } void PictureLoader::picDownloadFinished(QNetworkReply *reply) { - QString picsPath = _picsPath; - const QByteArray &picData = reply->readAll(); - QImage testImage; - if (testImage.loadFromData(picData)) { - if (!QDir(QString(picsPath + "/downloadedPics/")).exists()) { - QDir dir(picsPath); - if (!dir.exists()) - return; - dir.mkdir("downloadedPics"); - } - if (!QDir(QString(picsPath + "/downloadedPics/" + cardBeingDownloaded.getSetName())).exists()) { - QDir dir(QString(picsPath + "/downloadedPics")); - dir.mkdir(cardBeingDownloaded.getSetName()); - } - - QString suffix; - if (!cardBeingDownloaded.getStripped()) - suffix = ".full"; - - QFile newPic(picsPath + "/downloadedPics/" + cardBeingDownloaded.getSetName() + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + suffix + ".jpg"); - if (!newPic.open(QIODevice::WriteOnly)) - return; - newPic.write(picData); - newPic.close(); - - emit imageLoaded(cardBeingDownloaded.getCard(), testImage); - } else if (cardBeingDownloaded.getHq()) { - qDebug() << "HQ: received invalid picture. URL:" << reply->request().url(); - cardBeingDownloaded.setHq(false); - cardsToDownload.prepend(cardBeingDownloaded); - } else { - qDebug() << "LQ: received invalid picture. URL:" << reply->request().url(); - if (cardBeingDownloaded.nextSet()) { - cardBeingDownloaded.setHq(true); - mutex.lock(); - loadQueue.prepend(cardBeingDownloaded); - mutex.unlock(); - emit startLoadQueue(); - } else - emit imageLoaded(cardBeingDownloaded.getCard(), QImage()); - } - - reply->deleteLater(); - startNextPicDownload(); + QString picsPath = _picsPath; + if (reply->error()) { + qDebug() << "Download failed:" << reply->errorString(); + } + + const QByteArray &picData = reply->peek(reply->size()); //peek is used to keep the data in the buffer for use by QImageReader + QImage testImage; + + QImageReader imgReader; + imgReader.setDecideFormatFromContent(true); + imgReader.setDevice(reply); + QString extension = "." + imgReader.format(); //the format is determined prior to reading the QImageReader data into a QImage object, as that wipes the QImageReader buffer + if (extension == ".jpeg") + extension = ".jpg"; + + if (imgReader.read(&testImage)) { + QString setName = cardBeingDownloaded.getSetName(); + if(!setName.isEmpty()) + { + if (!QDir().mkpath(picsPath + "/downloadedPics/" + setName)) { + qDebug() << picsPath + "/downloadedPics/" + setName + " could not be created."; + return; + } + + QString suffix; + if (!cardBeingDownloaded.getStripped()) + suffix = ".full"; + + QFile newPic(picsPath + "/downloadedPics/" + setName + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + suffix + extension); + if (!newPic.open(QIODevice::WriteOnly)) + return; + newPic.write(picData); + newPic.close(); + } + + emit imageLoaded(cardBeingDownloaded.getCard(), testImage); + } else if (cardBeingDownloaded.getHq()) { + qDebug() << "HQ: received invalid picture. URL:" << reply->request().url(); + cardBeingDownloaded.setHq(false); + cardsToDownload.prepend(cardBeingDownloaded); + } else { + qDebug() << "LQ: received invalid picture. URL:" << reply->request().url(); + if (cardBeingDownloaded.nextSet()) { + cardBeingDownloaded.setHq(true); + mutex.lock(); + loadQueue.prepend(cardBeingDownloaded); + mutex.unlock(); + emit startLoadQueue(); + } else + emit imageLoaded(cardBeingDownloaded.getCard(), QImage()); + } + + reply->deleteLater(); + startNextPicDownload(); } void PictureLoader::loadImage(CardInfo *card, bool stripped) { - QMutexLocker locker(&mutex); - - loadQueue.append(PictureToLoad(card, stripped)); - emit startLoadQueue(); + QMutexLocker locker(&mutex); + + loadQueue.append(PictureToLoad(card, stripped)); + emit startLoadQueue(); } void PictureLoader::setPicsPath(const QString &path) { - QMutexLocker locker(&mutex); - _picsPath = path; + QMutexLocker locker(&mutex); + _picsPath = path; } void PictureLoader::setPicDownload(bool _picDownload) { - QMutexLocker locker(&mutex); - picDownload = _picDownload; + QMutexLocker locker(&mutex); + picDownload = _picDownload; } -PictureLoadingThread::PictureLoadingThread(const QString &_picsPath, bool _picDownload, QObject *parent) - : QThread(parent), picsPath(_picsPath), picDownload(_picDownload) +void PictureLoader::setPicDownloadHq(bool _picDownloadHq) { -} - -PictureLoadingThread::~PictureLoadingThread() -{ - quit(); - wait(); -} - -void PictureLoadingThread::run() -{ - pictureLoader = new PictureLoader; - connect(pictureLoader, SIGNAL(imageLoaded(CardInfo *, const QImage &)), this, SIGNAL(imageLoaded(CardInfo *, const QImage &))); - pictureLoader->setPicsPath(picsPath); - pictureLoader->setPicDownload(picDownload); - - usleep(100000); - initWaitCondition.wakeAll(); - - exec(); - - delete pictureLoader; -} - -void PictureLoadingThread::waitForInit() -{ - QMutex mutex; - mutex.lock(); - initWaitCondition.wait(&mutex); - mutex.unlock(); + QMutexLocker locker(&mutex); + picDownloadHq = _picDownloadHq; } CardInfo::CardInfo(CardDatabase *_db, - const QString &_name, - const QString &_manacost, - const QString &_cardtype, - const QString &_powtough, - const QString &_text, - const QStringList &_colors, - int _loyalty, - bool _cipt, - int _tableRow, - const SetList &_sets, - const QMap &_picURLs, - const QMap &_picURLsHq, - const QMap &_picURLsSt) - : db(_db), - name(_name), - sets(_sets), - manacost(_manacost), - cardtype(_cardtype), - powtough(_powtough), - text(_text), - colors(_colors), - loyalty(_loyalty), - picURLs(_picURLs), - picURLsHq(_picURLsHq), - picURLsSt(_picURLsSt), - cipt(_cipt), - tableRow(_tableRow), - pixmap(NULL) + const QString &_name, + bool _isToken, + const QString &_manacost, + const QString &_cardtype, + const QString &_powtough, + const QString &_text, + const QStringList &_colors, + int _loyalty, + bool _cipt, + int _tableRow, + const SetList &_sets, + const QStringMap &_customPicURLs, + const QStringMap &_customPicURLsHq, + MuidMap _muIds) + : db(_db), + name(_name), + isToken(_isToken), + sets(_sets), + manacost(_manacost), + cardtype(_cardtype), + powtough(_powtough), + text(_text), + colors(_colors), + loyalty(_loyalty), + customPicURLs(_customPicURLs), + customPicURLsHq(_customPicURLsHq), + muIds(_muIds), + cipt(_cipt), + tableRow(_tableRow), + pixmap(NULL) { - for (int i = 0; i < sets.size(); i++) - sets[i]->append(this); + for (int i = 0; i < sets.size(); i++) + sets[i]->append(this); } CardInfo::~CardInfo() { - clearPixmapCache(); + clearPixmapCache(); } QString CardInfo::getMainCardType() const { - QString result = getCardType(); - /* - Legendary Artifact Creature - Golem - Instant // Instant - */ + QString result = getCardType(); + /* + Legendary Artifact Creature - Golem + Instant // Instant + */ - int pos; - if ((pos = result.indexOf('-')) != -1) - result.remove(pos, result.length()); - if ((pos = result.indexOf("//")) != -1) - result.remove(pos, result.length()); - result = result.simplified(); - /* - Legendary Artifact Creature - Instant - */ + int pos; + if ((pos = result.indexOf('-')) != -1) + result.remove(pos, result.length()); + if ((pos = result.indexOf("—")) != -1) + result.remove(pos, result.length()); + if ((pos = result.indexOf("//")) != -1) + result.remove(pos, result.length()); + result = result.simplified(); + /* + Legendary Artifact Creature + Instant + */ - if ((pos = result.lastIndexOf(' ')) != -1) - result = result.mid(pos + 1); - /* - Creature - Instant - */ + if ((pos = result.lastIndexOf(' ')) != -1) + result = result.mid(pos + 1); + /* + Creature + Instant + */ - return result; + return result; } QString CardInfo::getCorrectedName() const { - QString result = name; - // Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental? - return result.remove(" // ").remove(':').remove('"').remove('?').replace('/', ' '); + QString result = name; + // Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental? + return result.remove(" // ").remove(':').remove('"').remove('?').replace('/', ' '); } void CardInfo::addToSet(CardSet *set) { - set->append(this); - sets << set; -} - -QString CardInfo::getPicURL() const -{ - SetList sortedSets = sets; - sortedSets.sortByKey(); - return picURLs.value(sortedSets.first()->getShortName()); + set->append(this); + sets << set; } QPixmap *CardInfo::loadPixmap() { - if (pixmap) - return pixmap; - pixmap = new QPixmap(); - - if (getName().isEmpty()) { - pixmap->load(settingsCache->getCardBackPicturePath()); - return pixmap; - } - db->loadImage(this); - return pixmap; + if (pixmap) + return pixmap; + pixmap = new QPixmap(); + + if (getName().isEmpty()) { + pixmap->load(settingsCache->getCardBackPicturePath()); + return pixmap; + } + db->loadImage(this); + return pixmap; } void CardInfo::imageLoaded(const QImage &image) { - if (!image.isNull()) { - *pixmap = QPixmap::fromImage(image); - emit pixmapUpdated(); - } + if (!image.isNull()) { + *pixmap = QPixmap::fromImage(image); + emit pixmapUpdated(); + } } QPixmap *CardInfo::getPixmap(QSize size) { - QPixmap *cachedPixmap = scaledPixmapCache.value(size.width()); - if (cachedPixmap) - return cachedPixmap; - QPixmap *bigPixmap = loadPixmap(); - QPixmap *result; - if (bigPixmap->isNull()) { - if (!getName().isEmpty()) - return 0; - else { - result = new QPixmap(size); - result->fill(Qt::transparent); - QSvgRenderer svg(QString(":/back.svg")); - QPainter painter(result); - svg.render(&painter, QRectF(0, 0, size.width(), size.height())); - } - } else - result = new QPixmap(bigPixmap->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - scaledPixmapCache.insert(size.width(), result); - return result; + QPixmap *cachedPixmap = scaledPixmapCache.value(size.width()); + if (cachedPixmap) + return cachedPixmap; + QPixmap *bigPixmap = loadPixmap(); + QPixmap *result; + if (bigPixmap->isNull()) { + if (!getName().isEmpty()) + return 0; + else { + result = new QPixmap(size); + result->fill(Qt::transparent); + QSvgRenderer svg(QString(":/back.svg")); + QPainter painter(result); + svg.render(&painter, QRectF(0, 0, size.width(), size.height())); + } + } else + result = new QPixmap(bigPixmap->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + scaledPixmapCache.insert(size.width(), result); + return result; } void CardInfo::clearPixmapCache() { - if (pixmap) { - qDebug() << "Deleting pixmap for" << name; - delete pixmap; - pixmap = 0; - QMapIterator i(scaledPixmapCache); - while (i.hasNext()) { - i.next(); - qDebug() << " Deleting cached pixmap for width" << i.key(); - delete i.value(); - } - scaledPixmapCache.clear(); - } + if (pixmap) { + qDebug() << "Deleting pixmap for" << name; + delete pixmap; + pixmap = 0; + QMapIterator i(scaledPixmapCache); + while (i.hasNext()) { + i.next(); + qDebug() << " Deleting cached pixmap for width" << i.key(); + delete i.value(); + } + scaledPixmapCache.clear(); + } } void CardInfo::clearPixmapCacheMiss() { - if (!pixmap) - return; - if (pixmap->isNull()) - clearPixmapCache(); + if (!pixmap) + return; + if (pixmap->isNull()) + clearPixmapCache(); } void CardInfo::updatePixmapCache() { - qDebug() << "Updating pixmap cache for" << name; - clearPixmapCache(); - loadPixmap(); - - emit pixmapUpdated(); + qDebug() << "Updating pixmap cache for" << name; + clearPixmapCache(); + loadPixmap(); + + emit pixmapUpdated(); } -QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info) +CardSet* CardInfo::getPreferredSet() { - xml.writeStartElement("card"); - xml.writeTextElement("name", info->getName()); + if(sets.isEmpty()) + return 0; + SetList sortedSets = sets; + sortedSets.sortByKey(); + return sortedSets.first(); +} - const SetList &sets = info->getSets(); - for (int i = 0; i < sets.size(); i++) { - xml.writeStartElement("set"); - xml.writeAttribute("picURL", info->getPicURL(sets[i]->getShortName())); - xml.writeAttribute("picURLHq", info->getPicURLHq(sets[i]->getShortName())); - xml.writeAttribute("picURLSt", info->getPicURLSt(sets[i]->getShortName())); - xml.writeCharacters(sets[i]->getShortName()); - xml.writeEndElement(); - } - const QStringList &colors = info->getColors(); - for (int i = 0; i < colors.size(); i++) - xml.writeTextElement("color", colors[i]); +int CardInfo::getPreferredMuId() +{ + CardSet *set = getPreferredSet(); + return set ? muIds[set->getShortName()] : 0; +} - xml.writeTextElement("manacost", info->getManaCost()); - xml.writeTextElement("type", info->getCardType()); - if (!info->getPowTough().isEmpty()) - xml.writeTextElement("pt", info->getPowTough()); - xml.writeTextElement("tablerow", QString::number(info->getTableRow())); - xml.writeTextElement("text", info->getText()); - if (info->getMainCardType() == "Planeswalker") - xml.writeTextElement("loyalty", QString::number(info->getLoyalty())); - if (info->getCipt()) - xml.writeTextElement("cipt", "1"); - xml.writeEndElement(); // card +QString CardInfo::simplifyName(const QString &name) { + QString simpleName(name); - return xml; + // So Aetherling would work, but not Ætherling since 'Æ' would get replaced + // with nothing. + simpleName.replace("æ", "ae"); + simpleName.replace("Æ", "AE"); + + // Replace Jötun Grunt with Jotun Grunt. + simpleName = simpleName.normalized(QString::NormalizationForm_KD); + + // Replace dashes with spaces so that we can say "garruk the veil cursed" + // instead of the unintuitive "garruk the veilcursed". + simpleName = simpleName.replace("-", " "); + + simpleName.remove(QRegExp("[^a-zA-Z0-9 ]")); + simpleName = simpleName.toLower(); + return simpleName; +} + +static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info) +{ + xml.writeStartElement("card"); + xml.writeTextElement("name", info->getName()); + + const SetList &sets = info->getSets(); + QString tmpString; + QString tmpSet; + for (int i = 0; i < sets.size(); i++) { + xml.writeStartElement("set"); + + tmpSet=sets[i]->getShortName(); + xml.writeAttribute("muId", QString::number(info->getMuId(tmpSet))); + + tmpString = info->getCustomPicURL(tmpSet); + if(!tmpString.isEmpty()) + xml.writeAttribute("picURL", tmpString); + + tmpString = info->getCustomPicURLHq(tmpSet); + if(!tmpString.isEmpty()) + xml.writeAttribute("picURLHq", tmpString); + + xml.writeCharacters(tmpSet); + xml.writeEndElement(); + } + const QStringList &colors = info->getColors(); + for (int i = 0; i < colors.size(); i++) + xml.writeTextElement("color", colors[i]); + + xml.writeTextElement("manacost", info->getManaCost()); + xml.writeTextElement("type", info->getCardType()); + if (!info->getPowTough().isEmpty()) + xml.writeTextElement("pt", info->getPowTough()); + xml.writeTextElement("tablerow", QString::number(info->getTableRow())); + xml.writeTextElement("text", info->getText()); + if (info->getMainCardType() == "Planeswalker") + xml.writeTextElement("loyalty", QString::number(info->getLoyalty())); + if (info->getCipt()) + xml.writeTextElement("cipt", "1"); + if (info->getIsToken()) + xml.writeTextElement("token", "1"); + xml.writeEndElement(); // card + + return xml; } CardDatabase::CardDatabase(QObject *parent) - : QObject(parent), loadSuccess(false), noCard(0) + : QObject(parent), noCard(0), loadStatus(NotLoaded) { - connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); - connect(settingsCache, SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabase())); - connect(settingsCache, SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged())); - - loadCardDatabase(); - - loadingThread = new PictureLoadingThread(settingsCache->getPicsPath(), settingsCache->getPicDownload(), this); - connect(loadingThread, SIGNAL(imageLoaded(CardInfo *, QImage)), this, SLOT(imageLoaded(CardInfo *, QImage))); - loadingThread->start(QThread::LowPriority); - loadingThread->waitForInit(); + connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); + connect(settingsCache, SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabase())); + connect(settingsCache, SIGNAL(tokenDatabasePathChanged()), this, SLOT(loadTokenDatabase())); + connect(settingsCache, SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged())); + connect(settingsCache, SIGNAL(picDownloadHqChanged()), this, SLOT(picDownloadHqChanged())); - noCard = new CardInfo(this); - noCard->loadPixmap(); // cache pixmap for card back - connect(settingsCache, SIGNAL(cardBackPicturePathChanged()), noCard, SLOT(updatePixmapCache())); + loadCardDatabase(); + loadTokenDatabase(); + + pictureLoaderThread = new QThread; + pictureLoader = new PictureLoader(settingsCache->getPicsPath(), settingsCache->getPicDownload(), settingsCache->getPicDownloadHq()); + pictureLoader->moveToThread(pictureLoaderThread); + connect(pictureLoader, SIGNAL(imageLoaded(CardInfo *, const QImage &)), this, SLOT(imageLoaded(CardInfo *, const QImage &))); + pictureLoaderThread->start(QThread::LowPriority); + + noCard = new CardInfo(this); + noCard->loadPixmap(); // cache pixmap for card back + connect(settingsCache, SIGNAL(cardBackPicturePathChanged()), noCard, SLOT(updatePixmapCache())); } CardDatabase::~CardDatabase() { - clear(); - delete noCard; + clear(); + delete noCard; + + pictureLoader->deleteLater(); + pictureLoaderThread->wait(); + delete pictureLoaderThread; } void CardDatabase::clear() { - QHashIterator setIt(setHash); - while (setIt.hasNext()) { - setIt.next(); - delete setIt.value(); - } - setHash.clear(); + QHashIterator setIt(sets); + while (setIt.hasNext()) { + setIt.next(); + delete setIt.value(); + } + sets.clear(); + + QHashIterator i(cards); + while (i.hasNext()) { + i.next(); + delete i.value(); + } + cards.clear(); - QHashIterator i(cardHash); - while (i.hasNext()) { - i.next(); - delete i.value(); - } - cardHash.clear(); + // The pointers themselves were already deleted, so we don't delete them + // again. + simpleNameCards.clear(); } -CardInfo *CardDatabase::getCard(const QString &cardName) +void CardDatabase::addCard(CardInfo *card) { - if (cardName.isEmpty()) - return noCard; - else if (cardHash.contains(cardName)) - return cardHash.value(cardName); - else { - CardInfo *newCard = new CardInfo(this, cardName); - newCard->addToSet(getSet("TK")); - cardHash.insert(cardName, newCard); - return newCard; - } + cards.insert(card->getName(), card); + simpleNameCards.insert(CardInfo::simplifyName(card->getName()), card); + emit cardAdded(card); +} + +void CardDatabase::removeCard(CardInfo *card) +{ + cards.remove(card->getName()); + simpleNameCards.remove(CardInfo::simplifyName(card->getName())); + emit cardRemoved(card); +} + +CardInfo *CardDatabase::getCard(const QString &cardName, bool createIfNotFound) { + return getCardFromMap(cards, cardName, createIfNotFound); +} + +CardInfo *CardDatabase::getCardBySimpleName(const QString &cardName, bool createIfNotFound) { + QString simpleName = CardInfo::simplifyName(cardName); + return getCardFromMap(simpleNameCards, simpleName, createIfNotFound); } CardSet *CardDatabase::getSet(const QString &setName) { - if (setHash.contains(setName)) - return setHash.value(setName); - else { - CardSet *newSet = new CardSet(setName); - setHash.insert(setName, newSet); - return newSet; - } + if (sets.contains(setName)) + return sets.value(setName); + else { + CardSet *newSet = new CardSet(setName); + sets.insert(setName, newSet); + return newSet; + } } SetList CardDatabase::getSetList() const { - SetList result; - QHashIterator i(setHash); - while (i.hasNext()) { - i.next(); - result << i.value(); - } - return result; + SetList result; + QHashIterator i(sets); + while (i.hasNext()) { + i.next(); + result << i.value(); + } + return result; } void CardDatabase::clearPixmapCache() { - QHashIterator i(cardHash); - while (i.hasNext()) { - i.next(); - i.value()->clearPixmapCache(); - } - if (noCard) - noCard->clearPixmapCache(); + // This also clears the cards in simpleNameCards since they point to the + // same object. + QHashIterator i(cards); + while (i.hasNext()) { + i.next(); + i.value()->clearPixmapCache(); + } + if (noCard) + noCard->clearPixmapCache(); } void CardDatabase::loadSetsFromXml(QXmlStreamReader &xml) { - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "set") { - QString shortName, longName; - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "name") - shortName = xml.readElementText(); - else if (xml.name() == "longname") - longName = xml.readElementText(); - } - setHash.insert(shortName, new CardSet(shortName, longName)); - } - } + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) + break; + if (xml.name() == "set") { + QString shortName, longName; + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) + break; + if (xml.name() == "name") + shortName = xml.readElementText(); + else if (xml.name() == "longname") + longName = xml.readElementText(); + } + sets.insert(shortName, new CardSet(shortName, longName)); + } + } } void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml) { - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "card") { - QString name, manacost, type, pt, text; - QStringList colors; - QMap picURLs, picURLsHq, picURLsSt; - SetList sets; - int tableRow = 0; - int loyalty = 0; - bool cipt = false; - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "name") - name = xml.readElementText(); - else if (xml.name() == "manacost") - manacost = xml.readElementText(); - else if (xml.name() == "type") - type = xml.readElementText(); - else if (xml.name() == "pt") - pt = xml.readElementText(); - else if (xml.name() == "text") - text = xml.readElementText(); - else if (xml.name() == "set") { - QString picURL = xml.attributes().value("picURL").toString(); - QString picURLHq = xml.attributes().value("picURLHq").toString(); - QString picURLSt = xml.attributes().value("picURLSt").toString(); - QString setName = xml.readElementText(); - sets.append(getSet(setName)); - picURLs.insert(setName, picURL); - picURLsHq.insert(setName, picURLHq); - picURLsSt.insert(setName, picURLSt); - } else if (xml.name() == "color") - colors << xml.readElementText(); - else if (xml.name() == "tablerow") - tableRow = xml.readElementText().toInt(); - else if (xml.name() == "cipt") - cipt = (xml.readElementText() == "1"); - else if (xml.name() == "loyalty") - loyalty = xml.readElementText().toInt(); - } - cardHash.insert(name, new CardInfo(this, name, manacost, type, pt, text, colors, loyalty, cipt, tableRow, sets, picURLs, picURLsHq, picURLsSt)); - } - } + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) + break; + if (xml.name() == "card") { + QString name, manacost, type, pt, text; + QStringList colors; + QStringMap customPicURLs, customPicURLsHq; + MuidMap muids; + SetList sets; + int tableRow = 0; + int loyalty = 0; + bool cipt = false; + bool isToken = false; + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) + break; + if (xml.name() == "name") + name = xml.readElementText(); + else if (xml.name() == "manacost") + manacost = xml.readElementText(); + else if (xml.name() == "type") + type = xml.readElementText(); + else if (xml.name() == "pt") + pt = xml.readElementText(); + else if (xml.name() == "text") + text = xml.readElementText(); + else if (xml.name() == "set") { + QXmlStreamAttributes attrs = xml.attributes(); + QString setName = xml.readElementText(); + sets.append(getSet(setName)); + if (attrs.hasAttribute("muId")) { + muids[setName] = attrs.value("muId").toString().toInt(); + } + if (attrs.hasAttribute("picURL")) { + customPicURLs[setName] = attrs.value("picURL").toString(); + } + if (attrs.hasAttribute("picURLHq")) { + customPicURLsHq[setName] = attrs.value("picURLHq").toString(); + } + } else if (xml.name() == "color") + colors << xml.readElementText(); + else if (xml.name() == "tablerow") + tableRow = xml.readElementText().toInt(); + else if (xml.name() == "cipt") + cipt = (xml.readElementText() == "1"); + else if (xml.name() == "loyalty") + loyalty = xml.readElementText().toInt(); + else if (xml.name() == "token") + isToken = xml.readElementText().toInt(); + } + addCard(new CardInfo(this, name, isToken, manacost, type, pt, text, colors, loyalty, cipt, tableRow, sets, customPicURLs, customPicURLsHq, muids)); + } + } } -bool CardDatabase::loadFromFile(const QString &fileName) -{ - QFile file(fileName); - file.open(QIODevice::ReadOnly); - if (!file.isOpen()) - return false; - QXmlStreamReader xml(&file); - clear(); - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::StartElement) { - if (xml.name() != "cockatrice_carddatabase") - return false; - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "sets") - loadSetsFromXml(xml); - else if (xml.name() == "cards") - loadCardsFromXml(xml); - } - } - } - qDebug() << cardHash.size() << "cards in" << setHash.size() << "sets loaded"; - return !cardHash.isEmpty(); +CardInfo *CardDatabase::getCardFromMap(CardNameMap &cardMap, const QString &cardName, bool createIfNotFound) { + if (cardName.isEmpty()) + return noCard; + else if (cardMap.contains(cardName)) + return cardMap.value(cardName); + else if (createIfNotFound) { + CardInfo *newCard = new CardInfo(this, cardName, true); + newCard->addToSet(getSet("TK")); + cardMap.insert(cardName, newCard); + return newCard; + } else + return 0; } -bool CardDatabase::saveToFile(const QString &fileName) +LoadStatus CardDatabase::loadFromFile(const QString &fileName) { - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) - return false; - QXmlStreamWriter xml(&file); + QFile file(fileName); + file.open(QIODevice::ReadOnly); + if (!file.isOpen()) + return FileError; - xml.setAutoFormatting(true); - xml.writeStartDocument(); - xml.writeStartElement("cockatrice_carddatabase"); - xml.writeAttribute("version", "1"); + QXmlStreamReader xml(&file); + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::StartElement) { + if (xml.name() != "cockatrice_carddatabase") + return Invalid; + int version = xml.attributes().value("version").toString().toInt(); + if (version < versionNeeded) { + qDebug() << "loadFromFile(): Version too old: " << version; + return VersionTooOld; + } + while (!xml.atEnd()) { + if (xml.readNext() == QXmlStreamReader::EndElement) + break; + if (xml.name() == "sets") + loadSetsFromXml(xml); + else if (xml.name() == "cards") + loadCardsFromXml(xml); + } + } + } + qDebug() << cards.size() << "cards in" << sets.size() << "sets loaded"; - xml.writeStartElement("sets"); - QHashIterator setIterator(setHash); - while (setIterator.hasNext()) - xml << setIterator.next().value(); - xml.writeEndElement(); // sets + if (cards.isEmpty()) return NoCards; - xml.writeStartElement("cards"); - QHashIterator cardIterator(cardHash); - while (cardIterator.hasNext()) - xml << cardIterator.next().value(); - xml.writeEndElement(); // cards + return Ok; +} - xml.writeEndElement(); // cockatrice_carddatabase - xml.writeEndDocument(); +bool CardDatabase::saveToFile(const QString &fileName, bool tokens) +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) + return false; + QXmlStreamWriter xml(&file); - return true; + xml.setAutoFormatting(true); + xml.writeStartDocument(); + xml.writeStartElement("cockatrice_carddatabase"); + xml.writeAttribute("version", QString::number(versionNeeded)); + + if (!tokens) { + xml.writeStartElement("sets"); + QHashIterator setIterator(sets); + while (setIterator.hasNext()) + xml << setIterator.next().value(); + xml.writeEndElement(); // sets + } + + xml.writeStartElement("cards"); + QHashIterator cardIterator(cards); + while (cardIterator.hasNext()) { + CardInfo *card = cardIterator.next().value(); + xml << card; + } + xml.writeEndElement(); // cards + + xml.writeEndElement(); // cockatrice_carddatabase + xml.writeEndDocument(); + + return true; } void CardDatabase::picDownloadChanged() { - loadingThread->getPictureLoader()->setPicDownload(settingsCache->getPicDownload()); - if (settingsCache->getPicDownload()) { - QHashIterator cardIterator(cardHash); - while (cardIterator.hasNext()) - cardIterator.next().value()->clearPixmapCacheMiss(); - } + pictureLoader->setPicDownload(settingsCache->getPicDownload()); + if (settingsCache->getPicDownload()) { + QHashIterator cardIterator(cards); + while (cardIterator.hasNext()) + cardIterator.next().value()->clearPixmapCacheMiss(); + } } -bool CardDatabase::loadCardDatabase(const QString &path) +void CardDatabase::picDownloadHqChanged() { - if (!path.isEmpty()) - loadSuccess = loadFromFile(path); - else loadSuccess = false; - - if (loadSuccess) { - SetList allSets; - QHashIterator setsIterator(setHash); - while (setsIterator.hasNext()) - allSets.append(setsIterator.next().value()); - allSets.sortByKey(); - for (int i = 0; i < allSets.size(); ++i) - allSets[i]->setSortKey(i); - - emit cardListChanged(); - } - - return loadSuccess; + pictureLoader->setPicDownloadHq(settingsCache->getPicDownloadHq()); + if (settingsCache->getPicDownloadHq()) { + QHashIterator cardIterator(cards); + while (cardIterator.hasNext()) + cardIterator.next().value()->clearPixmapCacheMiss(); + } } -bool CardDatabase::loadCardDatabase() +LoadStatus CardDatabase::loadCardDatabase(const QString &path, bool tokens) { - return loadCardDatabase(settingsCache->getCardDatabasePath()); + LoadStatus tempLoadStatus = NotLoaded; + if (!path.isEmpty()) + tempLoadStatus = loadFromFile(path); + + if (tempLoadStatus == Ok) { + SetList allSets; + QHashIterator setsIterator(sets); + while (setsIterator.hasNext()) + allSets.append(setsIterator.next().value()); + allSets.sortByKey(); + for (int i = 0; i < allSets.size(); ++i) + allSets[i]->setSortKey(i); + + emit cardListChanged(); + } + + if (!tokens) { + loadStatus = tempLoadStatus; + qDebug() << "loadCardDatabase(): Status = " << loadStatus; + } + + + return tempLoadStatus; +} + +void CardDatabase::loadCardDatabase() +{ + loadCardDatabase(settingsCache->getCardDatabasePath(), false); +} + +void CardDatabase::loadTokenDatabase() +{ + loadCardDatabase(settingsCache->getTokenDatabasePath(), true); } QStringList CardDatabase::getAllColors() const { - QSet colors; - QHashIterator cardIterator(cardHash); - while (cardIterator.hasNext()) { - const QStringList &cardColors = cardIterator.next().value()->getColors(); - if (cardColors.isEmpty()) - colors.insert("X"); - else - for (int i = 0; i < cardColors.size(); ++i) - colors.insert(cardColors[i]); - } - return colors.toList(); + QSet colors; + QHashIterator cardIterator(cards); + while (cardIterator.hasNext()) { + const QStringList &cardColors = cardIterator.next().value()->getColors(); + if (cardColors.isEmpty()) + colors.insert("X"); + else + for (int i = 0; i < cardColors.size(); ++i) + colors.insert(cardColors[i]); + } + return colors.toList(); } QStringList CardDatabase::getAllMainCardTypes() const { - QSet types; - QHashIterator cardIterator(cardHash); - while (cardIterator.hasNext()) - types.insert(cardIterator.next().value()->getMainCardType()); - return types.toList(); + QSet types; + QHashIterator cardIterator(cards); + while (cardIterator.hasNext()) + types.insert(cardIterator.next().value()->getMainCardType()); + return types.toList(); } void CardDatabase::cacheCardPixmaps(const QStringList &cardNames) { - for (int i = 0; i < cardNames.size(); ++i) - getCard(cardNames[i])->loadPixmap(); + for (int i = 0; i < cardNames.size(); ++i) + getCard(cardNames[i])->loadPixmap(); } void CardDatabase::loadImage(CardInfo *card) { - loadingThread->getPictureLoader()->loadImage(card, false); + pictureLoader->loadImage(card, false); } void CardDatabase::imageLoaded(CardInfo *card, QImage image) { - card->imageLoaded(image); + card->imageLoaded(image); } void CardDatabase::picsPathChanged() { - loadingThread->getPictureLoader()->setPicsPath(settingsCache->getPicsPath()); - clearPixmapCache(); + pictureLoader->setPicsPath(settingsCache->getPicsPath()); + clearPixmapCache(); } diff --git a/cockatrice/src/carddatabase.h b/cockatrice/src/carddatabase.h index fe892a03a..91abac59e 100644 --- a/cockatrice/src/carddatabase.h +++ b/cockatrice/src/carddatabase.h @@ -20,193 +20,240 @@ class QNetworkRequest; typedef QMap QStringMap; +// If we don't typedef this, CardInfo::CardInfo will refuse to compile on OS X < 10.9 +typedef QMap MuidMap; + class CardSet : public QList { private: - QString shortName, longName; - unsigned int sortKey; + QString shortName, longName; + unsigned int sortKey; public: - CardSet(const QString &_shortName = QString(), const QString &_longName = QString()); - QString getShortName() const { return shortName; } - QString getLongName() const { return longName; } - int getSortKey() const { return sortKey; } - void setSortKey(unsigned int _sortKey); - void updateSortKey(); + CardSet(const QString &_shortName = QString(), const QString &_longName = QString()); + QString getCorrectedShortName() const; + QString getShortName() const { return shortName; } + QString getLongName() const { return longName; } + int getSortKey() const { return sortKey; } + void setSortKey(unsigned int _sortKey); + void updateSortKey(); }; class SetList : public QList { private: - class CompareFunctor; + class CompareFunctor; public: - void sortByKey(); + void sortByKey(); }; class PictureToLoad { private: - CardInfo *card; - bool stripped; - SetList sortedSets; - int setIndex; - bool hq; + CardInfo *card; + bool stripped; + SetList sortedSets; + int setIndex; + bool hq; public: - PictureToLoad(CardInfo *_card = 0, bool _stripped = false, bool _hq = true); - CardInfo *getCard() const { return card; } - bool getStripped() const { return stripped; } - QString getSetName() const { return sortedSets[setIndex]->getShortName(); } - bool nextSet(); - - bool getHq() const { return hq; } - void setHq(bool _hq) { hq = _hq; } - + PictureToLoad(CardInfo *_card = 0, bool _stripped = false, bool _hq = true); + CardInfo *getCard() const { return card; } + bool getStripped() const { return stripped; } + QString getSetName() const; + bool nextSet(); + bool getHq() const { return hq; } + void setHq(bool _hq) { hq = _hq; } }; class PictureLoader : public QObject { - Q_OBJECT + Q_OBJECT private: - QString _picsPath; - QList loadQueue; - QMutex mutex; - QNetworkAccessManager *networkManager; - QList cardsToDownload; - PictureToLoad cardBeingDownloaded; - bool picDownload, downloadRunning, loadQueueRunning; - void startNextPicDownload(); + QString _picsPath; + QList loadQueue; + QMutex mutex; + QNetworkAccessManager *networkManager; + QList cardsToDownload; + PictureToLoad cardBeingDownloaded; + bool picDownload, picDownloadHq, downloadRunning, loadQueueRunning; + void startNextPicDownload(); + QString getPicUrl(CardInfo* card); public: - PictureLoader(QObject *parent = 0); - void setPicsPath(const QString &path); - void setPicDownload(bool _picDownload); - void loadImage(CardInfo *card, bool stripped); + PictureLoader(const QString &__picsPath, bool _picDownload, bool _picDownloadHq, QObject *parent = 0); + ~PictureLoader(); + void setPicsPath(const QString &path); + void setPicDownload(bool _picDownload); + void setPicDownloadHq(bool _picDownloadHq); + void loadImage(CardInfo *card, bool stripped); private slots: - void picDownloadFinished(QNetworkReply *reply); + void picDownloadFinished(QNetworkReply *reply); public slots: - void processLoadQueue(); + void processLoadQueue(); signals: - void startLoadQueue(); - void imageLoaded(CardInfo *card, const QImage &image); -}; - -class PictureLoadingThread : public QThread { - Q_OBJECT -private: - QString picsPath; - bool picDownload; - PictureLoader *pictureLoader; - QWaitCondition initWaitCondition; -protected: - void run(); -public: - PictureLoadingThread(const QString &_picsPath, bool _picDownload, QObject *parent); - ~PictureLoadingThread(); - PictureLoader *getPictureLoader() const { return pictureLoader; } - void waitForInit(); -signals: - void imageLoaded(CardInfo *card, const QImage &image); + void startLoadQueue(); + void imageLoaded(CardInfo *card, const QImage &image); }; class CardInfo : public QObject { - Q_OBJECT + Q_OBJECT private: - CardDatabase *db; + CardDatabase *db; - QString name; - SetList sets; - QString manacost; - QString cardtype; - QString powtough; - QString text; - QStringList colors; - int loyalty; - QMap picURLs, picURLsHq, picURLsSt; - bool cipt; - int tableRow; - QPixmap *pixmap; - QMap scaledPixmapCache; + QString name; + + /* + * The name without punctuation or capitalization, for better card tag name + * recognition. + */ + QString simpleName; + + bool isToken; + SetList sets; + QString manacost; + QString cardtype; + QString powtough; + QString text; + QStringList colors; + int loyalty; + QStringMap customPicURLs, customPicURLsHq; + MuidMap muIds; + bool cipt; + int tableRow; + QPixmap *pixmap; + QMap scaledPixmapCache; public: - CardInfo(CardDatabase *_db, - const QString &_name = QString(), - const QString &_manacost = QString(), - const QString &_cardtype = QString(), - const QString &_powtough = QString(), - const QString &_text = QString(), - const QStringList &_colors = QStringList(), - int _loyalty = 0, - bool cipt = false, - int _tableRow = 0, - const SetList &_sets = SetList(), - const QStringMap &_picURLs = QStringMap(), - const QStringMap &_picURLsHq = QStringMap(), - const QStringMap &_picURLsSt = QStringMap()); - ~CardInfo(); - const QString &getName() const { return name; } - const SetList &getSets() const { return sets; } - const QString &getManaCost() const { return manacost; } - const QString &getCardType() const { return cardtype; } - const QString &getPowTough() const { return powtough; } - const QString &getText() const { return text; } - const int &getLoyalty() const { return loyalty; } - bool getCipt() const { return cipt; } - void setText(const QString &_text) { text = _text; } - const QStringList &getColors() const { return colors; } - QString getPicURL(const QString &set) const { return picURLs.value(set); } - QString getPicURLHq(const QString &set) const { return picURLsHq.value(set); } - QString getPicURLSt(const QString &set) const { return picURLsSt.value(set); } - QString getPicURL() const; - const QMap &getPicURLs() const { return picURLs; } - QString getMainCardType() const; - QString getCorrectedName() const; - int getTableRow() const { return tableRow; } - void setTableRow(int _tableRow) { tableRow = _tableRow; } - void setLoyalty(int _loyalty) { loyalty = _loyalty; } - void setPicURL(const QString &_set, const QString &_picURL) { picURLs.insert(_set, _picURL); } - void setPicURLHq(const QString &_set, const QString &_picURL) { picURLsHq.insert(_set, _picURL); } - void setPicURLSt(const QString &_set, const QString &_picURL) { picURLsSt.insert(_set, _picURL); } - void addToSet(CardSet *set); - QPixmap *loadPixmap(); - QPixmap *getPixmap(QSize size); - void clearPixmapCache(); - void clearPixmapCacheMiss(); - void imageLoaded(const QImage &image); + CardInfo(CardDatabase *_db, + const QString &_name = QString(), + bool _isToken = false, + const QString &_manacost = QString(), + const QString &_cardtype = QString(), + const QString &_powtough = QString(), + const QString &_text = QString(), + const QStringList &_colors = QStringList(), + int _loyalty = 0, + bool _cipt = false, + int _tableRow = 0, + const SetList &_sets = SetList(), + const QStringMap &_customPicURLs = QStringMap(), + const QStringMap &_customPicURLsHq = QStringMap(), + MuidMap muids = MuidMap()); + ~CardInfo(); + const QString &getName() const { return name; } + bool getIsToken() const { return isToken; } + const SetList &getSets() const { return sets; } + const QString &getManaCost() const { return manacost; } + const QString &getCardType() const { return cardtype; } + const QString &getPowTough() const { return powtough; } + const QString &getText() const { return text; } + const int &getLoyalty() const { return loyalty; } + bool getCipt() const { return cipt; } + void setManaCost(const QString &_manaCost) { manacost = _manaCost; emit cardInfoChanged(this); } + void setCardType(const QString &_cardType) { cardtype = _cardType; emit cardInfoChanged(this); } + void setPowTough(const QString &_powTough) { powtough = _powTough; emit cardInfoChanged(this); } + void setText(const QString &_text) { text = _text; emit cardInfoChanged(this); } + void setColors(const QStringList &_colors) { colors = _colors; emit cardInfoChanged(this); } + const QStringList &getColors() const { return colors; } + QString getCustomPicURL(const QString &set) const { return customPicURLs.value(set); } + QString getCustomPicURLHq(const QString &set) const { return customPicURLsHq.value(set); } + int getMuId(const QString &set) const { return muIds.value(set); } + QString getMainCardType() const; + QString getCorrectedName() const; + int getTableRow() const { return tableRow; } + void setTableRow(int _tableRow) { tableRow = _tableRow; } + void setLoyalty(int _loyalty) { loyalty = _loyalty; emit cardInfoChanged(this); } + void setCustomPicURL(const QString &_set, const QString &_customPicURL) { customPicURLs.insert(_set, _customPicURL); } + void setCustomPicURLHq(const QString &_set, const QString &_customPicURL) { customPicURLsHq.insert(_set, _customPicURL); } + void setMuId(const QString &_set, const int &_muId) { muIds.insert(_set, _muId); } + void addToSet(CardSet *set); + QPixmap *loadPixmap(); + QPixmap *getPixmap(QSize size); + void clearPixmapCache(); + void clearPixmapCacheMiss(); + void imageLoaded(const QImage &image); + CardSet *getPreferredSet(); + int getPreferredMuId(); + + /** + * Simplify a name to have no punctuation and lowercase all letters, for + * less strict name-matching. + */ + static QString simplifyName(const QString &name); public slots: - void updatePixmapCache(); + void updatePixmapCache(); signals: - void pixmapUpdated(); + void pixmapUpdated(); + void cardInfoChanged(CardInfo *card); }; +enum LoadStatus { Ok, VersionTooOld, Invalid, NotLoaded, FileError, NoCards }; + +typedef QHash CardNameMap; +typedef QHash SetNameMap; + class CardDatabase : public QObject { - Q_OBJECT + Q_OBJECT protected: - QHash cardHash; - QHash setHash; - bool loadSuccess; - CardInfo *noCard; - PictureLoadingThread *loadingThread; + /* + * The cards, indexed by name. + */ + CardNameMap cards; + + /** + * The cards, indexed by their simple name. + */ + CardNameMap simpleNameCards; + + /* + * The sets, indexed by short name. + */ + SetNameMap sets; + + CardInfo *noCard; + + QThread *pictureLoaderThread; + PictureLoader *pictureLoader; + LoadStatus loadStatus; private: - void loadCardsFromXml(QXmlStreamReader &xml); - void loadSetsFromXml(QXmlStreamReader &xml); + static const int versionNeeded; + void loadCardsFromXml(QXmlStreamReader &xml); + void loadSetsFromXml(QXmlStreamReader &xml); + + CardInfo *getCardFromMap(CardNameMap &cardMap, const QString &cardName, bool createIfNotFound); public: - CardDatabase(QObject *parent = 0); - ~CardDatabase(); - void clear(); - CardInfo *getCard(const QString &cardName = QString()); - CardSet *getSet(const QString &setName); - QList getCardList() const { return cardHash.values(); } - SetList getSetList() const; - bool loadFromFile(const QString &fileName); - bool saveToFile(const QString &fileName); - QStringList getAllColors() const; - QStringList getAllMainCardTypes() const; - bool getLoadSuccess() const { return loadSuccess; } - void cacheCardPixmaps(const QStringList &cardNames); - void loadImage(CardInfo *card); + CardDatabase(QObject *parent = 0); + ~CardDatabase(); + void clear(); + void addCard(CardInfo *card); + void removeCard(CardInfo *card); + CardInfo *getCard(const QString &cardName = QString(), bool createIfNotFound = true); + + /* + * Get a card by its simple name. The name will be simplified in this + * function, so you don't need to simplify it beforehand. + */ + CardInfo *getCardBySimpleName(const QString &cardName = QString(), bool createIfNotFound = true); + + CardSet *getSet(const QString &setName); + QList getCardList() const { return cards.values(); } + SetList getSetList() const; + LoadStatus loadFromFile(const QString &fileName); + bool saveToFile(const QString &fileName, bool tokens = false); + QStringList getAllColors() const; + QStringList getAllMainCardTypes() const; + LoadStatus getLoadStatus() const { return loadStatus; } + bool getLoadSuccess() const { return loadStatus == Ok; } + void cacheCardPixmaps(const QStringList &cardNames); + void loadImage(CardInfo *card); public slots: - void clearPixmapCache(); - bool loadCardDatabase(const QString &path); - bool loadCardDatabase(); + void clearPixmapCache(); + LoadStatus loadCardDatabase(const QString &path, bool tokens = false); private slots: - void imageLoaded(CardInfo *card, QImage image); - void picDownloadChanged(); - void picsPathChanged(); + void imageLoaded(CardInfo *card, QImage image); + void picDownloadChanged(); + void picDownloadHqChanged(); + void picsPathChanged(); + + void loadCardDatabase(); + void loadTokenDatabase(); signals: - void cardListChanged(); + void cardListChanged(); + void cardAdded(CardInfo *card); + void cardRemoved(CardInfo *card); }; #endif diff --git a/cockatrice/src/carddatabasemodel.cpp b/cockatrice/src/carddatabasemodel.cpp index 8e2a556b9..a77bbd127 100644 --- a/cockatrice/src/carddatabasemodel.cpp +++ b/cockatrice/src/carddatabasemodel.cpp @@ -1,114 +1,180 @@ #include "carddatabasemodel.h" +#include "filtertree.h" CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, QObject *parent) - : QAbstractListModel(parent), db(_db) + : QAbstractListModel(parent), db(_db) { - connect(db, SIGNAL(cardListChanged()), this, SLOT(updateCardList())); - cardList = db->getCardList(); + connect(db, SIGNAL(cardListChanged()), this, SLOT(updateCardList())); + connect(db, SIGNAL(cardAdded(CardInfo *)), this, SLOT(cardAdded(CardInfo *))); + connect(db, SIGNAL(cardRemoved(CardInfo *)), this, SLOT(cardRemoved(CardInfo *))); + updateCardList(); } CardDatabaseModel::~CardDatabaseModel() { - } int CardDatabaseModel::rowCount(const QModelIndex &/*parent*/) const { - return cardList.size(); + return cardList.size(); } int CardDatabaseModel::columnCount(const QModelIndex &/*parent*/) const { - return 5; + return 5; } QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - if ((index.row() >= cardList.size()) || (index.column() >= 5)) - return QVariant(); - if (role != Qt::DisplayRole) - return QVariant(); + if (!index.isValid()) + return QVariant(); + if ((index.row() >= cardList.size()) || (index.column() >= 5)) + return QVariant(); + if (role != Qt::DisplayRole) + return QVariant(); - CardInfo *card = cardList.at(index.row()); - switch (index.column()){ - case 0: return card->getName(); - case 1: { - QStringList setList; - const QList &sets = card->getSets(); - for (int i = 0; i < sets.size(); i++) - setList << sets[i]->getShortName(); - return setList.join(", "); - } - case 2: return card->getManaCost(); - case 3: return card->getCardType(); - case 4: return card->getPowTough(); - default: return QVariant(); - } + CardInfo *card = cardList.at(index.row()); + switch (index.column()){ + case 0: return card->getName(); + case 1: { + QStringList setList; + const QList &sets = card->getSets(); + for (int i = 0; i < sets.size(); i++) + setList << sets[i]->getShortName(); + return setList.join(", "); + } + case 2: return card->getManaCost(); + case 3: return card->getCardType(); + case 4: return card->getPowTough(); + default: return QVariant(); + } } QVariant CardDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole) - return QVariant(); - if (orientation != Qt::Horizontal) - return QVariant(); - switch (section) { - case 0: return QString(tr("Name")); - case 1: return QString(tr("Sets")); - case 2: return QString(tr("Mana cost")); - case 3: return QString(tr("Card type")); - case 4: return QString(tr("P/T")); - default: return QVariant(); - } + if (role != Qt::DisplayRole) + return QVariant(); + if (orientation != Qt::Horizontal) + return QVariant(); + switch (section) { + case 0: return QString(tr("Name")); + case 1: return QString(tr("Sets")); + case 2: return QString(tr("Mana cost")); + case 3: return QString(tr("Card type")); + case 4: return QString(tr("P/T")); + default: return QVariant(); + } } void CardDatabaseModel::updateCardList() { - cardList = db->getCardList(); - reset(); + beginResetModel(); + + for (int i = 0; i < cardList.size(); ++i) + disconnect(cardList[i], 0, this, 0); + + cardList = db->getCardList(); + for (int i = 0; i < cardList.size(); ++i) + connect(cardList[i], SIGNAL(cardInfoChanged(CardInfo *)), this, SLOT(cardInfoChanged(CardInfo *))); + + endResetModel(); +} + +void CardDatabaseModel::cardInfoChanged(CardInfo *card) +{ + const int row = cardList.indexOf(card); + if (row == -1) + return; + + emit dataChanged(index(row, 0), index(row, 4)); +} + +void CardDatabaseModel::cardAdded(CardInfo *card) +{ + beginInsertRows(QModelIndex(), cardList.size(), cardList.size()); + cardList.append(card); + connect(card, SIGNAL(cardInfoChanged(CardInfo *)), this, SLOT(cardInfoChanged(CardInfo *))); + endInsertRows(); +} + +void CardDatabaseModel::cardRemoved(CardInfo *card) +{ + const int row = cardList.indexOf(card); + if (row == -1) + return; + + beginRemoveRows(QModelIndex(), row, row); + cardList.removeAt(row); + endRemoveRows(); } CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent) - : QSortFilterProxyModel(parent) + : QSortFilterProxyModel(parent), + isToken(ShowAll) { - setFilterCaseSensitivity(Qt::CaseInsensitive); - setSortCaseSensitivity(Qt::CaseInsensitive); + filterTree = NULL; + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSortCaseSensitivity(Qt::CaseInsensitive); } bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const { - CardInfo *info = static_cast(sourceModel())->getCard(sourceRow); - - if (!cardNameBeginning.isEmpty()) - if (!info->getName().startsWith(cardNameBeginning, Qt::CaseInsensitive)) - return false; - - if (!cardName.isEmpty()) - if (!info->getName().contains(cardName, Qt::CaseInsensitive)) - return false; - - if (!cardText.isEmpty()) - if (!info->getText().contains(cardText, Qt::CaseInsensitive)) - return false; - - if (!cardColors.isEmpty()) - if (QSet::fromList(info->getColors()).intersect(cardColors).isEmpty() && !(info->getColors().isEmpty() && cardColors.contains("X"))) - return false; - - if (!cardTypes.isEmpty()) - if (!cardTypes.contains(info->getMainCardType())) - return false; + CardInfo const *info = static_cast(sourceModel())->getCard(sourceRow); + if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken())) + return false; + + if (!cardNameBeginning.isEmpty()) + if (!info->getName().startsWith(cardNameBeginning, Qt::CaseInsensitive)) + return false; + + if (!cardName.isEmpty()) + if (!info->getName().contains(cardName, Qt::CaseInsensitive)) + return false; + + if (!cardNameSet.isEmpty()) + if (!cardNameSet.contains(info->getName())) + return false; + + if (!cardText.isEmpty()) + if (!info->getText().contains(cardText, Qt::CaseInsensitive)) + return false; + + if (!cardColors.isEmpty()) + if (QSet::fromList(info->getColors()).intersect(cardColors).isEmpty() && !(info->getColors().isEmpty() && cardColors.contains("X"))) + return false; + + if (!cardTypes.isEmpty()) + if (!cardTypes.contains(info->getMainCardType())) + return false; - return true; + if (filterTree != NULL) + return filterTree->acceptsCard(info); + + return true; } void CardDatabaseDisplayModel::clearSearch() { - cardName.clear(); - cardText.clear(); - cardTypes.clear(); - cardColors.clear(); - invalidateFilter(); + cardName.clear(); + cardText.clear(); + cardTypes.clear(); + cardColors.clear(); + if (filterTree != NULL) + filterTree->clear(); + invalidateFilter(); +} + +void CardDatabaseDisplayModel::setFilterTree(FilterTree *filterTree) +{ + if (this->filterTree != NULL) + disconnect(this->filterTree, 0, this, 0); + + this->filterTree = filterTree; + connect(this->filterTree, SIGNAL(changed()), this, SLOT(filterTreeChanged())); + invalidate(); +} + +void CardDatabaseDisplayModel::filterTreeChanged() +{ + invalidate(); } diff --git a/cockatrice/src/carddatabasemodel.h b/cockatrice/src/carddatabasemodel.h index d6404b422..eb0f5bc2c 100644 --- a/cockatrice/src/carddatabasemodel.h +++ b/cockatrice/src/carddatabasemodel.h @@ -7,38 +7,53 @@ #include #include "carddatabase.h" +class FilterTree; + class CardDatabaseModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - CardDatabaseModel(CardDatabase *_db, QObject *parent = 0); - ~CardDatabaseModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - CardInfo *getCard(int index) const { return cardList[index]; } + CardDatabaseModel(CardDatabase *_db, QObject *parent = 0); + ~CardDatabaseModel(); + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + CardDatabase *getDatabase() const { return db; } + CardInfo *getCard(int index) const { return cardList[index]; } private: - QList cardList; - CardDatabase *db; + QList cardList; + CardDatabase *db; private slots: - void updateCardList(); + void updateCardList(); + void cardAdded(CardInfo *card); + void cardRemoved(CardInfo *card); + void cardInfoChanged(CardInfo *card); }; class CardDatabaseDisplayModel : public QSortFilterProxyModel { - Q_OBJECT -private: - QString cardNameBeginning, cardName, cardText; - QSet cardTypes, cardColors; + Q_OBJECT public: - CardDatabaseDisplayModel(QObject *parent = 0); - void setCardNameBeginning(const QString &_beginning) { cardNameBeginning = _beginning; invalidate(); } - void setCardName(const QString &_cardName) { cardName = _cardName; invalidate(); } - void setCardText(const QString &_cardText) { cardText = _cardText; invalidate(); } - void setCardTypes(const QSet &_cardTypes) { cardTypes = _cardTypes; invalidate(); } - void setCardColors(const QSet &_cardColors) { cardColors = _cardColors; invalidate(); } - void clearSearch(); + enum FilterBool { ShowTrue, ShowFalse, ShowAll }; +private: + FilterBool isToken; + QString cardNameBeginning, cardName, cardText; + QSet cardNameSet, cardTypes, cardColors; + FilterTree *filterTree; +public: + CardDatabaseDisplayModel(QObject *parent = 0); + void setFilterTree(FilterTree *filterTree); + void setIsToken(FilterBool _isToken) { isToken = _isToken; invalidate(); } + void setCardNameBeginning(const QString &_beginning) { cardNameBeginning = _beginning; invalidate(); } + void setCardName(const QString &_cardName) { cardName = _cardName; invalidate(); } + void setCardNameSet(const QSet &_cardNameSet) { cardNameSet = _cardNameSet; invalidate(); } + void setCardText(const QString &_cardText) { cardText = _cardText; invalidate(); } + void setCardTypes(const QSet &_cardTypes) { cardTypes = _cardTypes; invalidate(); } + void setCardColors(const QSet &_cardColors) { cardColors = _cardColors; invalidate(); } + void clearSearch(); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; +private slots: + void filterTreeChanged(); }; #endif diff --git a/cockatrice/src/carddragitem.cpp b/cockatrice/src/carddragitem.cpp index df0e52bc2..0ddb91098 100644 --- a/cockatrice/src/carddragitem.cpp +++ b/cockatrice/src/carddragitem.cpp @@ -1,4 +1,5 @@ #include "carddragitem.h" +#include "carditem.h" #include "cardzone.h" #include "tablezone.h" #include "zoneviewzone.h" @@ -8,82 +9,82 @@ #include CardDragItem::CardDragItem(CardItem *_item, int _id, const QPointF &_hotSpot, bool _faceDown, AbstractCardDragItem *parentDrag) - : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), occupied(false), currentZone(0) + : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), occupied(false), currentZone(0) { } void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - AbstractCardDragItem::paint(painter, option, widget); - - if (occupied) - painter->fillRect(boundingRect(), QColor(200, 0, 0, 100)); + AbstractCardDragItem::paint(painter, option, widget); + + if (occupied) + painter->fillRect(boundingRect(), QColor(200, 0, 0, 100)); } void CardDragItem::updatePosition(const QPointF &cursorScenePos) { - QList colliding = scene()->items(cursorScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, static_cast(scene())->getViewTransform()); + QList colliding = scene()->items(cursorScenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, static_cast(scene())->getViewTransform()); - CardZone *cardZone = 0; - ZoneViewZone *zoneViewZone = 0; - for (int i = colliding.size() - 1; i >= 0; i--) { - CardZone *temp = qgraphicsitem_cast(colliding.at(i)); - if (!cardZone) - cardZone = temp; - if (!zoneViewZone) - zoneViewZone = qobject_cast(temp); - } - CardZone *cursorZone = 0; - if (zoneViewZone) - cursorZone = zoneViewZone; - else if (cardZone) - cursorZone = cardZone; - if (!cursorZone) - return; - currentZone = cursorZone; - - QPointF zonePos = currentZone->scenePos(); - QPointF cursorPosInZone = cursorScenePos - zonePos; - QPointF cardTopLeft = cursorPosInZone - hotSpot; - QPointF closestGridPoint = cursorZone->closestGridPoint(cardTopLeft); - QPointF newPos = zonePos + closestGridPoint; - - if (newPos != pos()) { - for (int i = 0; i < childDrags.size(); i++) - childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); - setPos(newPos); - - bool newOccupied = false; - TableZone *table = qobject_cast(cursorZone); - if (table) - if (table->getCardFromCoords(closestGridPoint)) - newOccupied = true; - if (newOccupied != occupied) { - occupied = newOccupied; - update(); - } - } + CardZone *cardZone = 0; + ZoneViewZone *zoneViewZone = 0; + for (int i = colliding.size() - 1; i >= 0; i--) { + CardZone *temp = qgraphicsitem_cast(colliding.at(i)); + if (!cardZone) + cardZone = temp; + if (!zoneViewZone) + zoneViewZone = qobject_cast(temp); + } + CardZone *cursorZone = 0; + if (zoneViewZone) + cursorZone = zoneViewZone; + else if (cardZone) + cursorZone = cardZone; + if (!cursorZone) + return; + currentZone = cursorZone; + + QPointF zonePos = currentZone->scenePos(); + QPointF cursorPosInZone = cursorScenePos - zonePos; + QPointF cardTopLeft = cursorPosInZone - hotSpot; + QPointF closestGridPoint = cursorZone->closestGridPoint(cardTopLeft); + QPointF newPos = zonePos + closestGridPoint; + + if (newPos != pos()) { + for (int i = 0; i < childDrags.size(); i++) + childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); + setPos(newPos); + + bool newOccupied = false; + TableZone *table = qobject_cast(cursorZone); + if (table) + if (table->getCardFromCoords(closestGridPoint)) + newOccupied = true; + if (newOccupied != occupied) { + occupied = newOccupied; + update(); + } + } } void CardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - setCursor(Qt::OpenHandCursor); - QGraphicsScene *sc = scene(); - QPointF sp = pos(); - sc->removeItem(this); + setCursor(Qt::OpenHandCursor); + QGraphicsScene *sc = scene(); + QPointF sp = pos(); + sc->removeItem(this); - QList dragItemList; - CardZone *startZone = static_cast(item)->getZone(); - if (currentZone && !(static_cast(item)->getAttachedTo() && (startZone == currentZone))) { - dragItemList.append(this); - for (int i = 0; i < childDrags.size(); i++) { - CardDragItem *c = static_cast(childDrags[i]); - if (!(static_cast(c->item)->getAttachedTo() && (startZone == currentZone)) && !c->occupied) - dragItemList.append(c); - sc->removeItem(c); - } - } - currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint()); + QList dragItemList; + CardZone *startZone = static_cast(item)->getZone(); + if (currentZone && !(static_cast(item)->getAttachedTo() && (startZone == currentZone))) { + dragItemList.append(this); + for (int i = 0; i < childDrags.size(); i++) { + CardDragItem *c = static_cast(childDrags[i]); + if (!(static_cast(c->item)->getAttachedTo() && (startZone == currentZone)) && !c->occupied) + dragItemList.append(c); + sc->removeItem(c); + } + } + currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint()); - event->accept(); + event->accept(); } diff --git a/cockatrice/src/carddragitem.h b/cockatrice/src/carddragitem.h index aae6fbe14..ea81998f7 100644 --- a/cockatrice/src/carddragitem.h +++ b/cockatrice/src/carddragitem.h @@ -6,19 +6,20 @@ class CardItem; class CardDragItem : public AbstractCardDragItem { + Q_OBJECT private: - int id; - bool faceDown; - bool occupied; - CardZone *currentZone; + int id; + bool faceDown; + bool occupied; + CardZone *currentZone; public: - CardDragItem(CardItem *_item, int _id, const QPointF &_hotSpot, bool _faceDown, AbstractCardDragItem *parentDrag = 0); - int getId() const { return id; } - bool getFaceDown() const { return faceDown; } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void updatePosition(const QPointF &cursorScenePos); + CardDragItem(CardItem *_item, int _id, const QPointF &_hotSpot, bool _faceDown, AbstractCardDragItem *parentDrag = 0); + int getId() const { return id; } + bool getFaceDown() const { return faceDown; } + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void updatePosition(const QPointF &cursorScenePos); protected: - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); }; #endif diff --git a/cockatrice/src/cardfilter.cpp b/cockatrice/src/cardfilter.cpp new file mode 100644 index 000000000..fb59adcf5 --- /dev/null +++ b/cockatrice/src/cardfilter.cpp @@ -0,0 +1,37 @@ +#include "cardfilter.h" + +const char *CardFilter::typeName(Type t) +{ + switch (t) { + case TypeAnd: + return "and"; + case TypeOr: + return "or"; + case TypeAndNot: + return "and not"; + case TypeOrNot: + return "or not"; + default: + return ""; + } +} + +const char *CardFilter::attrName(Attr a) +{ + switch (a) { + case AttrName: + return "name"; + case AttrType: + return "type"; + case AttrColor: + return "color"; + case AttrText: + return "text"; + case AttrSet: + return "set"; + case AttrManaCost: + return "mana cost"; + default: + return ""; + } +} diff --git a/cockatrice/src/cardfilter.h b/cockatrice/src/cardfilter.h new file mode 100644 index 000000000..e5f881233 --- /dev/null +++ b/cockatrice/src/cardfilter.h @@ -0,0 +1,44 @@ +#ifndef CARDFILTER_H +#define CARDFILTER_H + +#include + +class CardFilter { +public: + enum Type { + TypeAnd = 0, + TypeOr, + TypeAndNot, + TypeOrNot, + TypeEnd + }; + + /* if you add an atribute here you also need to + * add its string representation in attrName */ + enum Attr { + AttrName = 0, + AttrType, + AttrColor, + AttrText, + AttrSet, + AttrManaCost, + AttrEnd + }; + +private: + QString trm; + enum Type t; + enum Attr a; + +public: + CardFilter(QString term, Type type, Attr attr) : trm(term), t(type), a(attr) {}; + + Type type() const { return t; } + const QString &term() const { return trm; } + Attr attr() const { return a; } + + static const char *typeName(Type t); + static const char *attrName(Attr a); +}; + +#endif diff --git a/cockatrice/src/cardframe.cpp b/cockatrice/src/cardframe.cpp new file mode 100644 index 000000000..2ec2b42c3 --- /dev/null +++ b/cockatrice/src/cardframe.cpp @@ -0,0 +1,59 @@ +#include "cardframe.h" + +#include "carditem.h" +#include "carddatabase.h" +#include "main.h" +#include "cardinfopicture.h" +#include "cardinfotext.h" + +CardFrame::CardFrame(int width, int height, + const QString &cardName, QWidget *parent) + : QStackedWidget(parent) + , info(0) + , cardTextOnly(false) +{ + setFrameStyle(QFrame::Panel | QFrame::Raised); + setMaximumWidth(width); + setMinimumWidth(width); + setMaximumHeight(height); + setMinimumHeight(height); + pic = new CardInfoPicture(width); + addWidget(pic); + text = new CardInfoText(); + addWidget(text); + connect(pic, SIGNAL(hasPictureChanged()), this, SLOT(hasPictureChanged())); + setCard(db->getCard(cardName)); +} + +void CardFrame::setCard(CardInfo *card) +{ + if (info) + disconnect(info, 0, this, 0); + info = card; + connect(info, SIGNAL(destroyed()), this, SLOT(clear())); + text->setCard(info); + pic->setCard(info); +} + +void CardFrame::setCard(const QString &cardName) +{ + setCard(db->getCard(cardName)); +} + +void CardFrame::setCard(AbstractCardItem *card) +{ + setCard(card->getInfo()); +} + +void CardFrame::clear() +{ + setCard(db->getCard()); +} + +void CardFrame::hasPictureChanged() +{ + if (pic->hasPicture() && !cardTextOnly) + setCurrentWidget(pic); + else + setCurrentWidget(text); +} diff --git a/cockatrice/src/cardframe.h b/cockatrice/src/cardframe.h new file mode 100644 index 000000000..d75ce45a9 --- /dev/null +++ b/cockatrice/src/cardframe.h @@ -0,0 +1,36 @@ +#ifndef CARDFRAME_H +#define CARDFRAME_H + +#include + +class AbstractCardItem; +class CardInfo; +class CardInfoPicture; +class CardInfoText; + +class CardFrame : public QStackedWidget { + Q_OBJECT + +private: + CardInfo *info; + CardInfoPicture *pic; + CardInfoText *text; + bool cardTextOnly; + +public: + CardFrame(int width, int height, const QString &cardName = QString(), + QWidget *parent = 0); + void setCardTextOnly(bool status) { cardTextOnly = status; hasPictureChanged(); } + +public slots: + void setCard(CardInfo *card); + void setCard(const QString &cardName); + void setCard(AbstractCardItem *card); + void clear(); + +private slots: + void hasPictureChanged(); + void toggleCardTextOnly() { setCardTextOnly(!cardTextOnly); } +}; + +#endif diff --git a/cockatrice/src/cardinfopicture.cpp b/cockatrice/src/cardinfopicture.cpp new file mode 100644 index 000000000..8b560958a --- /dev/null +++ b/cockatrice/src/cardinfopicture.cpp @@ -0,0 +1,53 @@ +#include "cardinfopicture.h" + +#include +#include "carditem.h" +#include "carddatabase.h" +#include "main.h" + +CardInfoPicture::CardInfoPicture(int maximumWidth, QWidget *parent) + : QLabel(parent) + , info(0) + , noPicture(true) +{ + setMaximumWidth(maximumWidth); +} + +void CardInfoPicture::setNoPicture(bool status) +{ + if (noPicture != status) { + noPicture = status; + emit hasPictureChanged(); + } +} + +void CardInfoPicture::setCard(CardInfo *card) +{ + if (info) + disconnect(info, 0, this, 0); + info = card; + connect(info, SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap())); + + updatePixmap(); +} + +void CardInfoPicture::updatePixmap() +{ + qreal aspectRatio = (qreal) CARD_HEIGHT / (qreal) CARD_WIDTH; + qreal pixmapWidth = this->width(); + + if (pixmapWidth == 0) { + setNoPicture(true); + return; + } + + QPixmap *resizedPixmap = info->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio)); + if (resizedPixmap) { + setNoPicture(false); + this->setPixmap(*resizedPixmap); + } + else { + setNoPicture(true); + this->setPixmap(*(db->getCard()->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio)))); + } +} diff --git a/cockatrice/src/cardinfopicture.h b/cockatrice/src/cardinfopicture.h new file mode 100644 index 000000000..2773f99fd --- /dev/null +++ b/cockatrice/src/cardinfopicture.h @@ -0,0 +1,34 @@ +#ifndef CARDINFOPICTURE_H +#define CARDINFOPICTURE_H + +#include + +class AbstractCardItem; +class CardInfo; + +class CardInfoPicture : public QLabel { + Q_OBJECT + +signals: + void hasPictureChanged(); + +private: + CardInfo *info; + bool noPicture; + +public: + CardInfoPicture(int maximumWidth, QWidget *parent = 0); + bool hasPicture() const { return !noPicture; } + +private: + void setNoPicture(bool status); + +public slots: + void setCard(CardInfo *card); + +private slots: + void updatePixmap(); + +}; + +#endif diff --git a/cockatrice/src/cardinfotext.cpp b/cockatrice/src/cardinfotext.cpp new file mode 100644 index 000000000..21336a837 --- /dev/null +++ b/cockatrice/src/cardinfotext.cpp @@ -0,0 +1,67 @@ +#include "cardinfotext.h" + +#include +#include +#include +#include "carditem.h" +#include "carddatabase.h" +#include "main.h" + +CardInfoText::CardInfoText(QWidget *parent) + : QFrame(parent) + , info(0) +{ + nameLabel1 = new QLabel; + nameLabel2 = new QLabel; + nameLabel2->setWordWrap(true); + manacostLabel1 = new QLabel; + manacostLabel2 = new QLabel; + manacostLabel2->setWordWrap(true); + cardtypeLabel1 = new QLabel; + cardtypeLabel2 = new QLabel; + cardtypeLabel2->setWordWrap(true); + powtoughLabel1 = new QLabel; + powtoughLabel2 = new QLabel; + loyaltyLabel1 = new QLabel; + loyaltyLabel2 = new QLabel; + + textLabel = new QTextEdit(); + textLabel->setReadOnly(true); + + QGridLayout *grid = new QGridLayout(this); + int row = 0; + grid->addWidget(nameLabel1, row, 0); + grid->addWidget(nameLabel2, row++, 1); + grid->addWidget(manacostLabel1, row, 0); + grid->addWidget(manacostLabel2, row++, 1); + grid->addWidget(cardtypeLabel1, row, 0); + grid->addWidget(cardtypeLabel2, row++, 1); + grid->addWidget(powtoughLabel1, row, 0); + grid->addWidget(powtoughLabel2, row++, 1); + grid->addWidget(loyaltyLabel1, row, 0); + grid->addWidget(loyaltyLabel2, row++, 1); + grid->addWidget(textLabel, row, 0, -1, 2); + grid->setRowStretch(row, 1); + grid->setColumnStretch(1, 1); + + retranslateUi(); +} + +void CardInfoText::setCard(CardInfo *card) +{ + nameLabel2->setText(card->getName()); + manacostLabel2->setText(card->getManaCost()); + cardtypeLabel2->setText(card->getCardType()); + powtoughLabel2->setText(card->getPowTough()); + loyaltyLabel2->setText(card->getLoyalty() > 0 ? QString::number(card->getLoyalty()) : QString()); + textLabel->setText(card->getText()); +} + +void CardInfoText::retranslateUi() +{ + nameLabel1->setText(tr("Name:")); + manacostLabel1->setText(tr("Mana cost:")); + cardtypeLabel1->setText(tr("Card type:")); + powtoughLabel1->setText(tr("P / T:")); + loyaltyLabel1->setText(tr("Loyalty:")); +} diff --git a/cockatrice/src/cardinfotext.h b/cockatrice/src/cardinfotext.h new file mode 100644 index 000000000..c0f59480b --- /dev/null +++ b/cockatrice/src/cardinfotext.h @@ -0,0 +1,31 @@ +#ifndef CARDINFOTEXT_H +#define CARDINFOTEXT_H + +#include + +class QLabel; +class QTextEdit; +class CardInfo; + +class CardInfoText : public QFrame { + Q_OBJECT + +private: + QLabel *nameLabel1, *nameLabel2; + QLabel *manacostLabel1, *manacostLabel2; + QLabel *cardtypeLabel1, *cardtypeLabel2; + QLabel *powtoughLabel1, *powtoughLabel2; + QLabel *loyaltyLabel1, *loyaltyLabel2; + QTextEdit *textLabel; + + CardInfo *info; + +public: + CardInfoText(QWidget *parent = 0); + void retranslateUi(); + +public slots: + void setCard(CardInfo *card); +}; + +#endif diff --git a/cockatrice/src/cardinfowidget.cpp b/cockatrice/src/cardinfowidget.cpp index 19873363e..21b00a131 100644 --- a/cockatrice/src/cardinfowidget.cpp +++ b/cockatrice/src/cardinfowidget.cpp @@ -4,180 +4,221 @@ #include #include #include +#include #include "cardinfowidget.h" #include "carditem.h" #include "carddatabase.h" #include "main.h" #include "settingscache.h" -CardInfoWidget::CardInfoWidget(ResizeMode _mode, QWidget *parent, Qt::WindowFlags flags) - : QFrame(parent, flags) - , pixmapWidth(160) - , aspectRatio((qreal) CARD_HEIGHT / (qreal) CARD_WIDTH) - , minimized(settingsCache->getCardInfoMinimized()) // Initialize the cardinfo view status from cache. - , mode(_mode) - , info(0) +CardInfoWidget::CardInfoWidget(ResizeMode _mode, const QString &cardName, QWidget *parent, Qt::WindowFlags flags) + : QFrame(parent, flags) + , pixmapWidth(0) + , aspectRatio((qreal) CARD_HEIGHT / (qreal) CARD_WIDTH) + , minimized(settingsCache->getCardInfoMinimized()) // Initialize the cardinfo view status from cache. + , mode(_mode) + , info(0) { - if (mode == ModeGameTab) { - // Create indexed list of status views for card. - const QStringList cardInfoStatus = QStringList() << tr("Show card only") << tr("Show text only") << tr("Show full info"); - - // Create droplist for cardinfo view selection, and set right current index. - dropList = new QComboBox(); - dropList->addItems(cardInfoStatus); - dropList->setCurrentIndex(minimized); - connect(dropList, SIGNAL(currentIndexChanged(int)), this, SLOT(minimizeClicked(int))); - } + if (mode == ModeGameTab) { + // Create indexed list of status views for card. + const QStringList cardInfoStatus = QStringList() << tr("Show card only") << tr("Show text only") << tr("Show full info"); + + // Create droplist for cardinfo view selection, and set right current index. + dropList = new QComboBox(); + dropList->addItems(cardInfoStatus); + dropList->setCurrentIndex(minimized); + connect(dropList, SIGNAL(currentIndexChanged(int)), this, SLOT(minimizeClicked(int))); + } - cardPicture = new QLabel; - cardPicture->setAlignment(Qt::AlignCenter); + cardPicture = new QLabel; + cardPicture->setAlignment(Qt::AlignCenter); - nameLabel1 = new QLabel; - nameLabel2 = new QLabel; - nameLabel2->setWordWrap(true); - manacostLabel1 = new QLabel; - manacostLabel2 = new QLabel; - manacostLabel2->setWordWrap(true); - cardtypeLabel1 = new QLabel; - cardtypeLabel2 = new QLabel; - cardtypeLabel2->setWordWrap(true); - powtoughLabel1 = new QLabel; - powtoughLabel2 = new QLabel; + nameLabel1 = new QLabel; + nameLabel2 = new QLabel; + nameLabel2->setWordWrap(true); + manacostLabel1 = new QLabel; + manacostLabel2 = new QLabel; + manacostLabel2->setWordWrap(true); + cardtypeLabel1 = new QLabel; + cardtypeLabel2 = new QLabel; + cardtypeLabel2->setWordWrap(true); + powtoughLabel1 = new QLabel; + powtoughLabel2 = new QLabel; + loyaltyLabel1 = new QLabel; + loyaltyLabel2 = new QLabel; - textLabel = new QTextEdit(); - textLabel->setReadOnly(true); + textLabel = new QTextEdit(); + textLabel->setReadOnly(true); - QGridLayout *grid = new QGridLayout(this); - int row = 0; - if (mode == ModeGameTab) - grid->addWidget(dropList, row++, 1, 1, 1, Qt::AlignRight); - grid->addWidget(cardPicture, row++, 0, 1, 2); - grid->addWidget(nameLabel1, row, 0); - grid->addWidget(nameLabel2, row++, 1); - grid->addWidget(manacostLabel1, row, 0); - grid->addWidget(manacostLabel2, row++, 1); - grid->addWidget(cardtypeLabel1, row, 0); - grid->addWidget(cardtypeLabel2, row++, 1); - grid->addWidget(powtoughLabel1, row, 0); - grid->addWidget(powtoughLabel2, row++, 1); - grid->addWidget(textLabel, row, 0, -1, 2); - grid->setRowStretch(row, 1); - grid->setColumnStretch(1, 1); + QGridLayout *grid = new QGridLayout(this); + int row = 0; + if (mode == ModeGameTab) + grid->addWidget(dropList, row++, 1, 1, 1, Qt::AlignRight); + grid->addWidget(cardPicture, row++, 0, 1, 2); + grid->addWidget(nameLabel1, row, 0); + grid->addWidget(nameLabel2, row++, 1); + grid->addWidget(manacostLabel1, row, 0); + grid->addWidget(manacostLabel2, row++, 1); + grid->addWidget(cardtypeLabel1, row, 0); + grid->addWidget(cardtypeLabel2, row++, 1); + grid->addWidget(powtoughLabel1, row, 0); + grid->addWidget(powtoughLabel2, row++, 1); + grid->addWidget(loyaltyLabel1, row, 0); + grid->addWidget(loyaltyLabel2, row++, 1); + grid->addWidget(textLabel, row, 0, -1, 2); + grid->setRowStretch(row, 1); + grid->setColumnStretch(1, 1); - CardInfo *cardBack = db->getCard(); - setCard(cardBack); - - retranslateUi(); - setFrameStyle(QFrame::Panel | QFrame::Raised); - if (mode == ModeGameTab) { - textLabel->setMinimumHeight(100); - setFixedWidth(sizeHint().width()); - } else if (mode == ModePopUp) - setFixedWidth(350); - else - setFixedWidth(250); - setMinimized(settingsCache->getCardInfoMinimized()); + retranslateUi(); + setFrameStyle(QFrame::Panel | QFrame::Raised); + if (mode == ModeGameTab) { + textLabel->setMinimumHeight(100); + setFixedWidth(sizeHint().width()); + } else if (mode == ModePopUp) { + QDesktopWidget desktopWidget; + pixmapWidth = desktopWidget.screenGeometry().height() / 3 / aspectRatio; + setFixedWidth(pixmapWidth + 150); + } else + setFixedWidth(250); + + setCard(getCard(cardName)); + setMinimized(settingsCache->getCardInfoMinimized()); } void CardInfoWidget::minimizeClicked(int newMinimized) { - // Set new status, and store it in the settings cache. - setMinimized(newMinimized); - settingsCache->setCardInfoMinimized(newMinimized); + // Set new status, and store it in the settings cache. + setMinimized(newMinimized); + settingsCache->setCardInfoMinimized(newMinimized); +} + +bool CardInfoWidget::shouldShowPowTough() +{ +// return (!info->getPowTough().isEmpty() && (minimized != 0)); + return (minimized != 0); +} + +bool CardInfoWidget::shouldShowLoyalty() +{ +// return ((info->getLoyalty() > 0) && (minimized != 0)); + return (minimized != 0); } void CardInfoWidget::setMinimized(int _minimized) { - minimized = _minimized; + minimized = _minimized; - // Toggle oracle fields according to selected view. - bool showAll = ((minimized == 1) || (minimized == 2)); - if (mode == ModeGameTab) { - nameLabel1->setVisible(showAll); - nameLabel2->setVisible(showAll); - manacostLabel1->setVisible(showAll); - manacostLabel2->setVisible(showAll); - cardtypeLabel1->setVisible(showAll); - cardtypeLabel2->setVisible(showAll); - powtoughLabel1->setVisible(showAll); - powtoughLabel2->setVisible(showAll); - textLabel->setVisible(showAll); - } + // Toggle oracle fields according to selected view. + bool showAll = ((minimized == 1) || (minimized == 2)); + bool showPowTough = info ? (showAll && shouldShowPowTough()) : true; + bool showLoyalty = info ? (showAll && shouldShowLoyalty()) : true; + if (mode == ModeGameTab) { + nameLabel1->setVisible(showAll); + nameLabel2->setVisible(showAll); + manacostLabel1->setVisible(showAll); + manacostLabel2->setVisible(showAll); + cardtypeLabel1->setVisible(showAll); + cardtypeLabel2->setVisible(showAll); + powtoughLabel1->setVisible(showPowTough); + powtoughLabel2->setVisible(showPowTough); + loyaltyLabel1->setVisible(showLoyalty); + loyaltyLabel2->setVisible(showLoyalty); + textLabel->setVisible(showAll); + } - cardPicture->hide(); - cardHeightOffset = minimumSizeHint().height() + 10; - - // Set the picture to be shown only at "card only" (0) and "full info" (2) - if (mode == ModeGameTab) { - cardPicture->setVisible((minimized == 0) || (minimized == 2)); + cardPicture->hide(); + cardHeightOffset = minimumSizeHint().height() + 10; + + // Set the picture to be shown only at "card only" (0) and "full info" (2) + if (mode == ModeGameTab) { + cardPicture->setVisible((minimized == 0) || (minimized == 2)); - if (minimized == 0) - setMaximumHeight(cardHeightOffset + width() * aspectRatio); - else - setMaximumHeight(1000000); - } else - cardPicture->show(); - resize(width(), sizeHint().height()); + if (minimized == 0) + setMaximumHeight(cardHeightOffset + width() * aspectRatio); + else + setMaximumHeight(1000000); + } else + cardPicture->show(); + resize(width(), sizeHint().height()); } void CardInfoWidget::setCard(CardInfo *card) { - if (info) - disconnect(info, 0, this, 0); - info = card; - connect(info, SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap())); - connect(info, SIGNAL(destroyed()), this, SLOT(clear())); + if (info) + disconnect(info, 0, this, 0); + info = card; + connect(info, SIGNAL(pixmapUpdated()), this, SLOT(updatePixmap())); + connect(info, SIGNAL(destroyed()), this, SLOT(clear())); - updatePixmap(); - nameLabel2->setText(card->getName()); - manacostLabel2->setText(card->getManaCost()); - cardtypeLabel2->setText(card->getCardType()); - powtoughLabel2->setText(card->getPowTough()); - textLabel->setText(card->getText()); + updatePixmap(); + nameLabel2->setText(card->getName()); + manacostLabel2->setText(card->getManaCost()); + cardtypeLabel2->setText(card->getCardType()); + powtoughLabel2->setText(card->getPowTough()); + loyaltyLabel2->setText(card->getLoyalty() > 0 ? QString::number(card->getLoyalty()) : QString()); + textLabel->setText(card->getText()); + + powtoughLabel1->setVisible(shouldShowPowTough()); + powtoughLabel2->setVisible(shouldShowPowTough()); + loyaltyLabel1->setVisible(shouldShowLoyalty()); + loyaltyLabel2->setVisible(shouldShowLoyalty()); } void CardInfoWidget::setCard(const QString &cardName) { - setCard(db->getCard(cardName)); + setCard(getCard(cardName)); } void CardInfoWidget::setCard(AbstractCardItem *card) { - setCard(card->getInfo()); + setCard(card->getInfo()); } void CardInfoWidget::clear() { - setCard(db->getCard()); + setCard(getCard()); +} + +CardInfo *CardInfoWidget::getCard(const QString &cardName) { + return db->getCardBySimpleName(cardName); } void CardInfoWidget::updatePixmap() { - QPixmap *resizedPixmap = info->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio)); - if (resizedPixmap) - cardPicture->setPixmap(*resizedPixmap); - else - cardPicture->setPixmap(*(db->getCard()->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio)))); + if (pixmapWidth == 0) + return; + + QPixmap *resizedPixmap = info->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio)); + if (resizedPixmap) + cardPicture->setPixmap(*resizedPixmap); + else + cardPicture->setPixmap(*(getCard()->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio)))); } void CardInfoWidget::retranslateUi() { - nameLabel1->setText(tr("Name:")); - manacostLabel1->setText(tr("Mana cost:")); - cardtypeLabel1->setText(tr("Card type:")); - powtoughLabel1->setText(tr("P / T:")); + nameLabel1->setText(tr("Name:")); + manacostLabel1->setText(tr("Mana cost:")); + cardtypeLabel1->setText(tr("Card type:")); + powtoughLabel1->setText(tr("P / T:")); + loyaltyLabel1->setText(tr("Loyalty:")); } - void CardInfoWidget::resizeEvent(QResizeEvent * /*event*/) { - if ((mode == ModeGameTab) && (minimized == 1)) - return; - - pixmapWidth = qMax(100.0, qMin((qreal) cardPicture->width(), (height() - cardHeightOffset) / aspectRatio)); - updatePixmap(); + if (mode == ModePopUp) + return; + if ((minimized == 1) && (mode == ModeGameTab)) { + pixmapWidth = 0; + return; + } + qreal newPixmapWidth = qMax((qreal) 100.0, qMin((qreal) cardPicture->width(), (qreal) ((height() - cardHeightOffset) / aspectRatio))); + if (newPixmapWidth != pixmapWidth) { + pixmapWidth = newPixmapWidth; + updatePixmap(); + } } QString CardInfoWidget::getCardName() const { - return nameLabel2->text(); -} \ No newline at end of file + return nameLabel2->text(); +} diff --git a/cockatrice/src/cardinfowidget.h b/cockatrice/src/cardinfowidget.h index 7ceab5dc2..b9988a7a1 100644 --- a/cockatrice/src/cardinfowidget.h +++ b/cockatrice/src/cardinfowidget.h @@ -14,46 +14,56 @@ class QResizeEvent; class QMouseEvent; class CardInfoWidget : public QFrame { - Q_OBJECT + Q_OBJECT public: - enum ResizeMode { ModeDeckEditor, ModeGameTab, ModePopUp }; + enum ResizeMode { ModeDeckEditor, ModeGameTab, ModePopUp }; private: - int pixmapWidth; - qreal cardHeightOffset; - qreal aspectRatio; - int minimized; // 0 - card, 1 - oracle only, 2 - full - ResizeMode mode; + int pixmapWidth; + qreal cardHeightOffset; + qreal aspectRatio; + // XXX: Why isn't this an eunm? + int minimized; // 0 - card, 1 - oracle only, 2 - full + ResizeMode mode; - QComboBox *dropList; - QLabel *cardPicture; - QLabel *nameLabel1, *nameLabel2; - QLabel *manacostLabel1, *manacostLabel2; - QLabel *cardtypeLabel1, *cardtypeLabel2; - QLabel *powtoughLabel1, *powtoughLabel2; - QTextEdit *textLabel; + QComboBox *dropList; + QLabel *cardPicture; + QLabel *nameLabel1, *nameLabel2; + QLabel *manacostLabel1, *manacostLabel2; + QLabel *cardtypeLabel1, *cardtypeLabel2; + QLabel *powtoughLabel1, *powtoughLabel2; + QLabel *loyaltyLabel1, *loyaltyLabel2; + QTextEdit *textLabel; - CardInfo *info; - void setMinimized(int _minimized); + bool shouldShowPowTough(); + bool shouldShowLoyalty(); + + CardInfo *info; + void setMinimized(int _minimized); + + /* + * Wrapper around db->getCardBySimpleName. + */ + CardInfo *getCard(const QString &cardName = QString()); public: - CardInfoWidget(ResizeMode _mode, QWidget *parent = 0, Qt::WindowFlags f = 0); - void retranslateUi(); - QString getCardName() const; + CardInfoWidget(ResizeMode _mode, const QString &cardName = QString(), QWidget *parent = 0, Qt::WindowFlags f = 0); + void retranslateUi(); + QString getCardName() const; public slots: - void setCard(CardInfo *card); - void setCard(const QString &cardName); - void setCard(AbstractCardItem *card); + void setCard(CardInfo *card); + void setCard(const QString &cardName); + void setCard(AbstractCardItem *card); private slots: - void clear(); - void updatePixmap(); - void minimizeClicked(int newMinimized); + void clear(); + void updatePixmap(); + void minimizeClicked(int newMinimized); protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event); }; #endif diff --git a/cockatrice/src/carditem.cpp b/cockatrice/src/carditem.cpp index 61bd9a0fc..e7ce64821 100644 --- a/cockatrice/src/carditem.cpp +++ b/cockatrice/src/carditem.cpp @@ -8,619 +8,385 @@ #include "carddragitem.h" #include "carddatabase.h" #include "cardzone.h" +#include "zoneviewzone.h" #include "tablezone.h" #include "player.h" #include "arrowitem.h" #include "main.h" -#include "protocol_datastructures.h" #include "settingscache.h" #include "tab_game.h" +#include "pb/serverinfo_card.pb.h" CardItem::CardItem(Player *_owner, const QString &_name, int _cardid, bool _revealedCard, QGraphicsItem *parent) - : AbstractCardItem(_name, _owner, parent), zone(0), id(_cardid), revealedCard(_revealedCard), attacking(false), facedown(false), destroyOnZoneChange(false), doesntUntap(false), dragItem(0), attachedTo(0) + : AbstractCardItem(_name, _owner, _cardid, parent), zone(0), revealedCard(_revealedCard), attacking(false), destroyOnZoneChange(false), doesntUntap(false), dragItem(0), attachedTo(0) { - owner->addCard(this); - - aTap = new QAction(this); - aTap->setData(0); - connect(aTap, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - aUntap = new QAction(this); - aUntap->setData(1); - connect(aUntap, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - aDoesntUntap = new QAction(this); - aDoesntUntap->setData(2); - connect(aDoesntUntap, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - aAttach = new QAction(this); - connect(aAttach, SIGNAL(triggered()), this, SLOT(actAttach())); - aUnattach = new QAction(this); - connect(aUnattach, SIGNAL(triggered()), this, SLOT(actUnattach())); - aDrawArrow = new QAction(this); - connect(aDrawArrow, SIGNAL(triggered()), this, SLOT(actDrawArrow())); - aIncP = new QAction(this); - connect(aIncP, SIGNAL(triggered()), this, SLOT(actIncP())); - aDecP = new QAction(this); - connect(aDecP, SIGNAL(triggered()), this, SLOT(actDecP())); - aIncT = new QAction(this); - connect(aIncT, SIGNAL(triggered()), this, SLOT(actIncT())); - aDecT = new QAction(this); - connect(aDecT, SIGNAL(triggered()), this, SLOT(actDecT())); - aIncPT = new QAction(this); - connect(aIncPT, SIGNAL(triggered()), this, SLOT(actIncPT())); - aDecPT = new QAction(this); - connect(aDecPT, SIGNAL(triggered()), this, SLOT(actDecPT())); - aSetPT = new QAction(this); - connect(aSetPT, SIGNAL(triggered()), this, SLOT(actSetPT())); - aSetAnnotation = new QAction(this); - connect(aSetAnnotation, SIGNAL(triggered()), this, SLOT(actSetAnnotation())); - aFlip = new QAction(this); - aFlip->setData(3); - connect(aFlip, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - aClone = new QAction(this); - aClone->setData(4); - connect(aClone, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - aMoveToTopLibrary = new QAction(this); - aMoveToTopLibrary->setData(5); - aMoveToBottomLibrary = new QAction(this); - aMoveToBottomLibrary->setData(6); - aMoveToGraveyard = new QAction(this); - aMoveToGraveyard->setData(7); - aMoveToExile = new QAction(this); - aMoveToExile->setData(8); - connect(aMoveToTopLibrary, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - connect(aMoveToBottomLibrary, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - connect(aMoveToGraveyard, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - connect(aMoveToExile, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - - aPlay = new QAction(this); - connect(aPlay, SIGNAL(triggered()), this, SLOT(actPlay())); - aHide = new QAction(this); - connect(aHide, SIGNAL(triggered()), this, SLOT(actHide())); - - for (int i = 0; i < 3; ++i) { - QAction *tempAddCounter = new QAction(this); - tempAddCounter->setData(9 + i * 1000); - QAction *tempRemoveCounter = new QAction(this); - tempRemoveCounter->setData(10 + i * 1000); - QAction *tempSetCounter = new QAction(this); - tempSetCounter->setData(11 + i * 1000); - aAddCounter.append(tempAddCounter); - aRemoveCounter.append(tempRemoveCounter); - aSetCounter.append(tempSetCounter); - connect(tempAddCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); - connect(tempRemoveCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); - connect(tempSetCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); - } - cardMenu = new QMenu; - ptMenu = new QMenu; - ptMenu->addAction(aIncP); - ptMenu->addAction(aDecP); - ptMenu->addSeparator(); - ptMenu->addAction(aIncT); - ptMenu->addAction(aDecT); - ptMenu->addSeparator(); - ptMenu->addAction(aIncPT); - ptMenu->addAction(aDecPT); - ptMenu->addSeparator(); - ptMenu->addAction(aSetPT); - moveMenu = new QMenu; - - retranslateUi(); - updateCardMenu(); + owner->addCard(this); + + cardMenu = new QMenu; + ptMenu = new QMenu; + moveMenu = new QMenu; + + retranslateUi(); + emit updateCardMenu(this); } CardItem::~CardItem() { - prepareDelete(); - - delete cardMenu; - cardMenu = 0; - delete moveMenu; - moveMenu = 0; - - deleteDragItem(); + prepareDelete(); + + if (scene()) + static_cast(scene())->unregisterAnimationItem(this); + + delete cardMenu; + delete ptMenu; + delete moveMenu; + + deleteDragItem(); } void CardItem::prepareDelete() { - if (owner) { - if (owner->getCardMenu() == cardMenu) - owner->setCardMenu(0); - owner = 0; - } - - while (!attachedCards.isEmpty()) { - attachedCards.first()->setZone(0); // so that it won't try to call reorganizeCards() - attachedCards.first()->setAttachedTo(0); - } - - if (attachedTo) { - attachedTo->removeAttachedCard(this); - attachedTo = 0; - } + if (owner) { + if (owner->getCardMenu() == cardMenu) { + owner->setCardMenu(0); + owner->getGame()->setActiveCard(0); + } + owner = 0; + } + + while (!attachedCards.isEmpty()) { + attachedCards.first()->setZone(0); // so that it won't try to call reorganizeCards() + attachedCards.first()->setAttachedTo(0); + } + + if (attachedTo) { + attachedTo->removeAttachedCard(this); + attachedTo = 0; + } } void CardItem::deleteLater() { - prepareDelete(); - AbstractCardItem::deleteLater(); + prepareDelete(); + AbstractCardItem::deleteLater(); } void CardItem::setZone(CardZone *_zone) { - zone = _zone; - updateCardMenu(); -} - -void CardItem::updateCardMenu() -{ - cardMenu->clear(); - - if (revealedCard) - cardMenu->addAction(aHide); - else if (owner->getLocal()) { - moveMenu->clear(); - moveMenu->addAction(aMoveToTopLibrary); - moveMenu->addAction(aMoveToBottomLibrary); - moveMenu->addAction(aMoveToGraveyard); - moveMenu->addAction(aMoveToExile); - - if (zone) { - if (zone->getName() == "table") { - cardMenu->addAction(aTap); - cardMenu->addAction(aUntap); - cardMenu->addAction(aDoesntUntap); - cardMenu->addAction(aFlip); - cardMenu->addSeparator(); - cardMenu->addAction(aAttach); - if (attachedTo) - cardMenu->addAction(aUnattach); - cardMenu->addAction(aDrawArrow); - cardMenu->addSeparator(); - cardMenu->addMenu(ptMenu); - cardMenu->addAction(aSetAnnotation); - cardMenu->addSeparator(); - cardMenu->addAction(aClone); - cardMenu->addMenu(moveMenu); - - for (int i = 0; i < aAddCounter.size(); ++i) { - cardMenu->addSeparator(); - cardMenu->addAction(aAddCounter[i]); - cardMenu->addAction(aRemoveCounter[i]); - cardMenu->addAction(aSetCounter[i]); - } - cardMenu->addSeparator(); - } else if (zone->getName() == "stack") { - cardMenu->addAction(aDrawArrow); - cardMenu->addMenu(moveMenu); - } else { - cardMenu->addAction(aPlay); - cardMenu->addMenu(moveMenu); - } - } else - cardMenu->addMenu(moveMenu); - } + zone = _zone; + emit updateCardMenu(this); } void CardItem::retranslateUi() { - aPlay->setText(tr("&Play")); - aHide->setText(tr("&Hide")); - - aTap->setText(tr("&Tap")); - aUntap->setText(tr("&Untap")); - aDoesntUntap->setText(tr("Toggle &normal untapping")); - aFlip->setText(tr("&Flip")); - aClone->setText(tr("&Clone")); - aClone->setShortcut(tr("Ctrl+H")); - aAttach->setText(tr("&Attach to card...")); - aAttach->setShortcut(tr("Ctrl+A")); - aUnattach->setText(tr("Unattac&h")); - aDrawArrow->setText(tr("&Draw arrow...")); - ptMenu->setTitle(tr("&Power / toughness")); - aIncP->setText(tr("&Increase power")); - aIncP->setShortcut(tr("Ctrl++")); - aDecP->setText(tr("&Decrease power")); - aDecP->setShortcut(tr("Ctrl+-")); - aIncT->setText(tr("I&ncrease toughness")); - aIncT->setShortcut(tr("Alt++")); - aDecT->setText(tr("D&ecrease toughness")); - aDecT->setShortcut(tr("Alt+-")); - aIncPT->setText(tr("In&crease power and toughness")); - aIncPT->setShortcut(tr("Ctrl+Alt++")); - aDecPT->setText(tr("Dec&rease power and toughness")); - aDecPT->setShortcut(tr("Ctrl+Alt+-")); - aSetPT->setText(tr("Set &power and toughness...")); - aSetPT->setShortcut(tr("Ctrl+P")); - aSetAnnotation->setText(tr("&Set annotation...")); - QStringList counterColors; - counterColors.append(tr("red")); - counterColors.append(tr("yellow")); - counterColors.append(tr("green")); - for (int i = 0; i < aAddCounter.size(); ++i) - aAddCounter[i]->setText(tr("&Add counter (%1)").arg(counterColors[i])); - for (int i = 0; i < aRemoveCounter.size(); ++i) - aRemoveCounter[i]->setText(tr("&Remove counter (%1)").arg(counterColors[i])); - for (int i = 0; i < aSetCounter.size(); ++i) - aSetCounter[i]->setText(tr("&Set counters (%1)...").arg(counterColors[i])); - aMoveToTopLibrary->setText(tr("&top of library")); - aMoveToBottomLibrary->setText(tr("&bottom of library")); - aMoveToGraveyard->setText(tr("&graveyard")); - aMoveToGraveyard->setShortcut(tr("Ctrl+Del")); - aMoveToExile->setText(tr("&exile")); - - moveMenu->setTitle(tr("&Move to")); + moveMenu->setTitle(tr("&Move to")); + ptMenu->setTitle(tr("&Power / toughness")); } void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - painter->save(); - AbstractCardItem::paint(painter, option, widget); - - int i = 0; - QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) { - counterIterator.next(); - QColor color; - color.setHsv(counterIterator.key() * 60, 150, 255); - - paintNumberEllipse(counterIterator.value(), 14, color, i, counters.size(), painter); - ++i; - } - - QSizeF translatedSize = getTranslatedSize(painter); - qreal scaleFactor = translatedSize.width() / boundingRect().width(); - - if (!pt.isEmpty()) { - painter->save(); - - transformPainter(painter, translatedSize, tapAngle); - painter->setBackground(Qt::black); - painter->setBackgroundMode(Qt::OpaqueMode); - painter->setPen(Qt::white); - - painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor, translatedSize.height() - 8 * scaleFactor), Qt::AlignRight | Qt::AlignBottom, pt); - painter->restore(); - } - if (!annotation.isEmpty()) { - painter->save(); + painter->save(); + AbstractCardItem::paint(painter, option, widget); + + int i = 0; + QMapIterator counterIterator(counters); + while (counterIterator.hasNext()) { + counterIterator.next(); + QColor color; + color.setHsv(counterIterator.key() * 60, 150, 255); + + paintNumberEllipse(counterIterator.value(), 14, color, i, counters.size(), painter); + ++i; + } + + QSizeF translatedSize = getTranslatedSize(painter); + qreal scaleFactor = translatedSize.width() / boundingRect().width(); + + if (!pt.isEmpty()) { + painter->save(); + + transformPainter(painter, translatedSize, tapAngle); + painter->setBackground(Qt::black); + painter->setBackgroundMode(Qt::OpaqueMode); + painter->setPen(Qt::white); + + painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor, translatedSize.height() - 8 * scaleFactor), Qt::AlignRight | Qt::AlignBottom, pt); + painter->restore(); + } + if (!annotation.isEmpty()) { + painter->save(); - transformPainter(painter, translatedSize, tapAngle); - painter->setBackground(Qt::black); - painter->setBackgroundMode(Qt::OpaqueMode); - painter->setPen(Qt::white); - - painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor, translatedSize.height() - 8 * scaleFactor), Qt::AlignCenter | Qt::TextWrapAnywhere, annotation); - painter->restore(); - } - if (getBeingPointedAt()) - painter->fillRect(boundingRect(), QBrush(QColor(255, 0, 0, 100))); - painter->restore(); + transformPainter(painter, translatedSize, tapAngle); + painter->setBackground(Qt::black); + painter->setBackgroundMode(Qt::OpaqueMode); + painter->setPen(Qt::white); + + painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor, translatedSize.height() - 8 * scaleFactor), Qt::AlignCenter | Qt::TextWrapAnywhere, annotation); + painter->restore(); + } + if (getBeingPointedAt()) + painter->fillRect(boundingRect(), QBrush(QColor(255, 0, 0, 100))); + painter->restore(); } void CardItem::setAttacking(bool _attacking) { - attacking = _attacking; - update(); -} - -void CardItem::setFaceDown(bool _facedown) -{ - facedown = _facedown; - if (facedown) - setName(QString()); - update(); + attacking = _attacking; + update(); } void CardItem::setCounter(int _id, int _value) { - if (_value) - counters.insert(_id, _value); - else - counters.remove(_id); - update(); + if (_value) + counters.insert(_id, _value); + else + counters.remove(_id); + update(); } void CardItem::setAnnotation(const QString &_annotation) { - annotation = _annotation; - update(); + annotation = _annotation; + update(); } void CardItem::setDoesntUntap(bool _doesntUntap) { - doesntUntap = _doesntUntap; + doesntUntap = _doesntUntap; } void CardItem::setPT(const QString &_pt) { - pt = _pt; - update(); + pt = _pt; + update(); } void CardItem::setAttachedTo(CardItem *_attachedTo) { - if (attachedTo) - attachedTo->removeAttachedCard(this); - - gridPoint.setX(-1); - attachedTo = _attachedTo; - if (attachedTo) { - setParentItem(attachedTo->getZone()); - attachedTo->addAttachedCard(this); - if (zone != attachedTo->getZone()) - attachedTo->getZone()->reorganizeCards(); - } else - setParentItem(zone); + if (attachedTo) + attachedTo->removeAttachedCard(this); + + gridPoint.setX(-1); + attachedTo = _attachedTo; + if (attachedTo) { + setParentItem(attachedTo->getZone()); + attachedTo->addAttachedCard(this); + if (zone != attachedTo->getZone()) + attachedTo->getZone()->reorganizeCards(); + } else + setParentItem(zone); - if (zone) - zone->reorganizeCards(); - - updateCardMenu(); + if (zone) + zone->reorganizeCards(); + + emit updateCardMenu(this); } void CardItem::resetState() { - attacking = false; - facedown = false; - counters.clear(); - pt.clear(); - annotation.clear(); - attachedTo = 0; - attachedCards.clear(); - setTapped(false, false); - setDoesntUntap(false); - if (scene()) - static_cast(scene())->unregisterAnimationItem(this); - update(); + attacking = false; + facedown = false; + counters.clear(); + pt.clear(); + annotation.clear(); + attachedTo = 0; + attachedCards.clear(); + setTapped(false, false); + setDoesntUntap(false); + if (scene()) + static_cast(scene())->unregisterAnimationItem(this); + update(); } -void CardItem::processCardInfo(ServerInfo_Card *info) +void CardItem::processCardInfo(const ServerInfo_Card &info) { - counters.clear(); - const QList &_counterList = info->getCounters(); - for (int i = 0; i < _counterList.size(); ++i) - counters.insert(_counterList[i]->getId(), _counterList[i]->getValue()); - - setId(info->getId()); - setName(info->getName()); - setAttacking(info->getAttacking()); - setFaceDown(info->getFaceDown()); - setPT(info->getPT()); - setAnnotation(info->getAnnotation()); - setColor(info->getColor()); - setTapped(info->getTapped()); - setDestroyOnZoneChange(info->getDestroyOnZoneChange()); - setDoesntUntap(info->getDoesntUntap()); + counters.clear(); + const int counterListSize = info.counter_list_size(); + for (int i = 0; i < counterListSize; ++i) { + const ServerInfo_CardCounter &counterInfo = info.counter_list(i); + counters.insert(counterInfo.id(), counterInfo.value()); + } + + setId(info.id()); + setName(QString::fromStdString(info.name())); + setAttacking(info.attacking()); + setFaceDown(info.face_down()); + setPT(QString::fromStdString(info.pt())); + setAnnotation(QString::fromStdString(info.annotation())); + setColor(QString::fromStdString(info.color())); + setTapped(info.tapped()); + setDestroyOnZoneChange(info.destroy_on_zone_change()); + setDoesntUntap(info.doesnt_untap()); } CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown) { - deleteDragItem(); - dragItem = new CardDragItem(this, _id, _pos, faceDown); - dragItem->setVisible(false); - scene()->addItem(dragItem); - dragItem->updatePosition(_scenePos); - dragItem->setVisible(true); + deleteDragItem(); + dragItem = new CardDragItem(this, _id, _pos, faceDown); + dragItem->setVisible(false); + scene()->addItem(dragItem); + dragItem->updatePosition(_scenePos); + dragItem->setVisible(true); - return dragItem; + return dragItem; } void CardItem::deleteDragItem() { - dragItem->deleteLater(); - dragItem = NULL; + if(dragItem) + dragItem->deleteLater(); + dragItem = NULL; } void CardItem::drawArrow(const QColor &arrowColor) { - if (static_cast(owner->parent())->getSpectator()) - return; - - Player *arrowOwner = static_cast(owner->parent())->getActiveLocalPlayer(); - ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor); - scene()->addItem(arrow); - arrow->grabMouse(); - - QListIterator itemIterator(scene()->selectedItems()); - while (itemIterator.hasNext()) { - CardItem *c = qgraphicsitem_cast(itemIterator.next()); - if (!c || (c == this)) - continue; - if (c->getZone() != zone) - continue; - - ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, c, arrowColor); - scene()->addItem(childArrow); - arrow->addChildArrow(childArrow); - } + if (static_cast(owner->parent())->getSpectator()) + return; + + Player *arrowOwner = static_cast(owner->parent())->getActiveLocalPlayer(); + ArrowDragItem *arrow = new ArrowDragItem(arrowOwner, this, arrowColor); + scene()->addItem(arrow); + arrow->grabMouse(); + + QListIterator itemIterator(scene()->selectedItems()); + while (itemIterator.hasNext()) { + CardItem *c = qgraphicsitem_cast(itemIterator.next()); + if (!c || (c == this)) + continue; + if (c->getZone() != zone) + continue; + + ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, c, arrowColor); + scene()->addItem(childArrow); + arrow->addChildArrow(childArrow); + } } void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if (event->buttons().testFlag(Qt::RightButton)) { - if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() < 2 * QApplication::startDragDistance()) - return; - - QColor arrowColor = Qt::red; - if (event->modifiers().testFlag(Qt::ControlModifier)) - arrowColor = Qt::yellow; - else if (event->modifiers().testFlag(Qt::AltModifier)) - arrowColor = Qt::blue; - else if (event->modifiers().testFlag(Qt::ShiftModifier)) - arrowColor = Qt::green; - - drawArrow(arrowColor); - } else if (event->buttons().testFlag(Qt::LeftButton)) { - if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < 2 * QApplication::startDragDistance()) - return; - if (!owner->getLocal()) - return; - - bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier); - - createDragItem(id, event->pos(), event->scenePos(), facedown || forceFaceDown); - dragItem->grabMouse(); - - QList sel = scene()->selectedItems(); - int j = 0; - for (int i = 0; i < sel.size(); i++) { - CardItem *c = (CardItem *) sel.at(i); - if (c == this) - continue; - ++j; - QPointF childPos; - if (zone->getHasCardAttr()) - childPos = c->pos() - pos(); - else - childPos = QPointF(j * CARD_WIDTH / 2, 0); - CardDragItem *drag = new CardDragItem(c, c->getId(), childPos, c->getFaceDown() || forceFaceDown, dragItem); - drag->setPos(dragItem->pos() + childPos); - scene()->addItem(drag); - } - } - setCursor(Qt::OpenHandCursor); + if (event->buttons().testFlag(Qt::RightButton)) { + if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() < 2 * QApplication::startDragDistance()) + return; + + QColor arrowColor = Qt::red; + if (event->modifiers().testFlag(Qt::ControlModifier)) + arrowColor = Qt::yellow; + else if (event->modifiers().testFlag(Qt::AltModifier)) + arrowColor = Qt::blue; + else if (event->modifiers().testFlag(Qt::ShiftModifier)) + arrowColor = Qt::green; + + drawArrow(arrowColor); + } else if (event->buttons().testFlag(Qt::LeftButton)) { + if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < 2 * QApplication::startDragDistance()) + return; + if (zone->getIsView()) { + const ZoneViewZone *const view = static_cast(zone); + if (view->getRevealZone() && !view->getWriteableRevealZone()) + return; + } else if (!owner->getLocal()) + return; + + bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier); + + createDragItem(id, event->pos(), event->scenePos(), facedown || forceFaceDown); + dragItem->grabMouse(); + + QList sel = scene()->selectedItems(); + int j = 0; + for (int i = 0; i < sel.size(); i++) { + CardItem *c = (CardItem *) sel.at(i); + if ((c == this) || (c->getZone() != zone)) + continue; + ++j; + QPointF childPos; + if (zone->getHasCardAttr()) + childPos = c->pos() - pos(); + else + childPos = QPointF(j * CARD_WIDTH / 2, 0); + CardDragItem *drag = new CardDragItem(c, c->getId(), childPos, c->getFaceDown() || forceFaceDown, dragItem); + drag->setPos(dragItem->pos() + childPos); + scene()->addItem(drag); + } + } + setCursor(Qt::OpenHandCursor); } void CardItem::playCard(bool faceDown) { - // Do nothing if the card belongs to another player - if (!owner->getLocal()) - return; + // Do nothing if the card belongs to another player + if (!owner->getLocal()) + return; - TableZone *tz = qobject_cast(zone); - if (tz) - tz->toggleTapped(); - else - zone->getPlayer()->playCard(this, faceDown, info->getCipt()); + TableZone *tz = qobject_cast(zone); + if (tz) + tz->toggleTapped(); + else + zone->getPlayer()->playCard(this, faceDown, info->getCipt()); } void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::RightButton) { - if (cardMenu) - if (!cardMenu->isEmpty()) - cardMenu->exec(event->screenPos()); - } else if ((event->button() == Qt::LeftButton) && !settingsCache->getDoubleClickToPlay()) { - setCursor(Qt::OpenHandCursor); - if (revealedCard) - actHide(); - else - playCard(event->modifiers().testFlag(Qt::ShiftModifier)); - } + if (event->button() == Qt::RightButton) { + if (cardMenu) + if (!cardMenu->isEmpty()) + cardMenu->exec(event->screenPos()); + } else if ((event->button() == Qt::LeftButton) && !settingsCache->getDoubleClickToPlay()) { + + bool hideCard = false; + if (zone->getIsView()) { + ZoneViewZone *view = static_cast(zone); + if (view->getRevealZone() && !view->getWriteableRevealZone()) + hideCard = true; + } + if (hideCard) + zone->removeCard(this); + else + playCard(event->modifiers().testFlag(Qt::ShiftModifier)); + } - AbstractCardItem::mouseReleaseEvent(event); + setCursor(Qt::OpenHandCursor); + AbstractCardItem::mouseReleaseEvent(event); } void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { - if (settingsCache->getDoubleClickToPlay()) { - if (revealedCard) - actHide(); - else - playCard(event->modifiers().testFlag(Qt::ShiftModifier)); - } - event->accept(); + if (settingsCache->getDoubleClickToPlay()) { + if (revealedCard) + zone->removeCard(this); + else + playCard(event->modifiers().testFlag(Qt::ShiftModifier)); + } + event->accept(); } bool CardItem::animationEvent() { - int delta = 18; - if (!tapped) - delta *= -1; - - tapAngle += delta; - - setTransform(QTransform().translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2).rotate(tapAngle).translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2)); - setHovered(false); - update(); + int delta = 18; + if (!tapped) + delta *= -1; + + tapAngle += delta; + + setTransform(QTransform().translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2).rotate(tapAngle).translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2)); + setHovered(false); + update(); - if ((tapped && (tapAngle >= 90)) || (!tapped && (tapAngle <= 0))) - return false; - return true; + if ((tapped && (tapAngle >= 90)) || (!tapped && (tapAngle <= 0))) + return false; + return true; } QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value) { - if ((change == ItemSelectedHasChanged) && owner) { - if (value == true) - owner->setCardMenu(cardMenu); - else if (owner->getCardMenu() == cardMenu) - owner->setCardMenu(0); - } - return QGraphicsItem::itemChange(change, value); -} - -void CardItem::cardMenuAction() -{ - owner->cardMenuAction(static_cast(sender())); -} - -void CardItem::actAttach() -{ - owner->actAttach(static_cast(sender())); -} - -void CardItem::actUnattach() -{ - owner->actUnattach(static_cast(sender())); -} - -void CardItem::actDrawArrow() -{ - drawArrow(Qt::red); -} - -void CardItem::actIncP() -{ - owner->actIncPT(1, 0); -} - -void CardItem::actDecP() -{ - owner->actIncPT(-1, 0); -} - -void CardItem::actIncT() -{ - owner->actIncPT(0, 1); -} - -void CardItem::actDecT() -{ - owner->actIncPT(0, -1); -} - -void CardItem::actIncPT() -{ - owner->actIncPT(1, 1); -} - -void CardItem::actDecPT() -{ - owner->actIncPT(-1, -1); -} - -void CardItem::actSetPT() -{ - owner->actSetPT(static_cast(sender())); -} - -void CardItem::actSetAnnotation() -{ - owner->actSetAnnotation(static_cast(sender())); -} - -void CardItem::actCardCounterTrigger() -{ - owner->actCardCounterTrigger(static_cast(sender())); -} - -void CardItem::actPlay() -{ - playCard(false); -} - -void CardItem::actHide() -{ - zone->removeCard(this); + if ((change == ItemSelectedHasChanged) && owner) { + if (value == true) { + owner->setCardMenu(cardMenu); + owner->getGame()->setActiveCard(this); + } else if (owner->getCardMenu() == cardMenu) { + owner->setCardMenu(0); + owner->getGame()->setActiveCard(0); + } + } + return QGraphicsItem::itemChange(change, value); } diff --git a/cockatrice/src/carditem.h b/cockatrice/src/carditem.h index 6c02a4bbe..9952550d3 100644 --- a/cockatrice/src/carditem.h +++ b/cockatrice/src/carditem.h @@ -14,99 +14,75 @@ class QColor; const int MAX_COUNTERS_ON_CARD = 999; class CardItem : public AbstractCardItem { - Q_OBJECT + Q_OBJECT private: - CardZone *zone; - int id; - bool revealedCard; - bool attacking; - bool facedown; - QMap counters; - QString annotation; - QString pt; - bool destroyOnZoneChange; - bool doesntUntap; - QPoint gridPoint; - CardDragItem *dragItem; - CardItem *attachedTo; - QList attachedCards; - - QList aAddCounter, aSetCounter, aRemoveCounter; - QAction *aPlay, - *aHide, - *aTap, *aUntap, *aDoesntUntap, *aAttach, *aUnattach, *aDrawArrow, *aSetPT, *aIncP, *aDecP, *aIncT, *aDecT, *aIncPT, *aDecPT, *aSetAnnotation, *aFlip, *aClone, - *aMoveToTopLibrary, *aMoveToBottomLibrary, *aMoveToGraveyard, *aMoveToExile; - QMenu *cardMenu, *ptMenu, *moveMenu; + CardZone *zone; + bool revealedCard; + bool attacking; + QMap counters; + QString annotation; + QString pt; + bool destroyOnZoneChange; + bool doesntUntap; + QPoint gridPoint; + CardDragItem *dragItem; + CardItem *attachedTo; + QList attachedCards; + + QMenu *cardMenu, *ptMenu, *moveMenu; - void playCard(bool faceDown); - void drawArrow(const QColor &arrowColor); - void prepareDelete(); -private slots: - void cardMenuAction(); - void actCardCounterTrigger(); - void actAttach(); - void actUnattach(); - void actDrawArrow(); - void actSetPT(); - void actIncP(); - void actDecP(); - void actIncT(); - void actDecT(); - void actIncPT(); - void actDecPT(); - void actSetAnnotation(); - void actPlay(); - void actHide(); + void prepareDelete(); public slots: - void deleteLater(); + void deleteLater(); public: - enum { Type = typeCard }; - int type() const { return Type; } - CardItem(Player *_owner, const QString &_name = QString(), int _cardid = -1, bool revealedCard = false, QGraphicsItem *parent = 0); - ~CardItem(); - void retranslateUi(); - CardZone *getZone() const { return zone; } - void setZone(CardZone *_zone); - QMenu *getCardMenu() const { return cardMenu; } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - QPoint getGridPoint() const { return gridPoint; } - void setGridPoint(const QPoint &_gridPoint) { gridPoint = _gridPoint; } - QPoint getGridPos() const { return gridPoint; } - Player *getOwner() const { return owner; } - void setOwner(Player *_owner) { owner = _owner; } - int getId() const { return id; } - void setId(int _id) { id = _id; } - bool getAttacking() const { return attacking; } - void setAttacking(bool _attacking); - bool getFaceDown() const { return facedown; } - void setFaceDown(bool _facedown); - const QMap &getCounters() const { return counters; } - void setCounter(int _id, int _value); - QString getAnnotation() const { return annotation; } - void setAnnotation(const QString &_annotation); - bool getDoesntUntap() const { return doesntUntap; } - void setDoesntUntap(bool _doesntUntap); - QString getPT() const { return pt; } - void setPT(const QString &_pt); - bool getDestroyOnZoneChange() const { return destroyOnZoneChange; } - void setDestroyOnZoneChange(bool _destroy) { destroyOnZoneChange = _destroy; } - CardItem *getAttachedTo() const { return attachedTo; } - void setAttachedTo(CardItem *_attachedTo); - void addAttachedCard(CardItem *card) { attachedCards.append(card); } - void removeAttachedCard(CardItem *card) { attachedCards.removeAt(attachedCards.indexOf(card)); } - const QList &getAttachedCards() const { return attachedCards; } - void resetState(); - void processCardInfo(ServerInfo_Card *info); - void updateCardMenu(); + enum { Type = typeCard }; + int type() const { return Type; } + CardItem(Player *_owner, const QString &_name = QString(), int _cardid = -1, bool revealedCard = false, QGraphicsItem *parent = 0); + ~CardItem(); + void retranslateUi(); + CardZone *getZone() const { return zone; } + void setZone(CardZone *_zone); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QPoint getGridPoint() const { return gridPoint; } + void setGridPoint(const QPoint &_gridPoint) { gridPoint = _gridPoint; } + QPoint getGridPos() const { return gridPoint; } + Player *getOwner() const { return owner; } + void setOwner(Player *_owner) { owner = _owner; } + bool getRevealedCard() const { return revealedCard; } + bool getAttacking() const { return attacking; } + void setAttacking(bool _attacking); + const QMap &getCounters() const { return counters; } + void setCounter(int _id, int _value); + QString getAnnotation() const { return annotation; } + void setAnnotation(const QString &_annotation); + bool getDoesntUntap() const { return doesntUntap; } + void setDoesntUntap(bool _doesntUntap); + QString getPT() const { return pt; } + void setPT(const QString &_pt); + bool getDestroyOnZoneChange() const { return destroyOnZoneChange; } + void setDestroyOnZoneChange(bool _destroy) { destroyOnZoneChange = _destroy; } + CardItem *getAttachedTo() const { return attachedTo; } + void setAttachedTo(CardItem *_attachedTo); + void addAttachedCard(CardItem *card) { attachedCards.append(card); } + void removeAttachedCard(CardItem *card) { attachedCards.removeAt(attachedCards.indexOf(card)); } + const QList &getAttachedCards() const { return attachedCards; } + void resetState(); + void processCardInfo(const ServerInfo_Card &info); - bool animationEvent(); - CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown); - void deleteDragItem(); + QMenu *getCardMenu() const { return cardMenu; } + QMenu *getPTMenu() const { return ptMenu; } + QMenu *getMoveMenu() const { return moveMenu; } + + bool animationEvent(); + CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown); + void deleteDragItem(); + void drawArrow(const QColor &arrowColor); + void playCard(bool faceDown); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); - QVariant itemChange(GraphicsItemChange change, const QVariant &value); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + QVariant itemChange(GraphicsItemChange change, const QVariant &value); }; #endif diff --git a/cockatrice/src/cardlist.cpp b/cockatrice/src/cardlist.cpp index e4d809efc..344662659 100644 --- a/cockatrice/src/cardlist.cpp +++ b/cockatrice/src/cardlist.cpp @@ -3,57 +3,57 @@ #include "carddatabase.h" CardList::CardList(bool _contentsKnown) - : QList(), contentsKnown(_contentsKnown) + : QList(), contentsKnown(_contentsKnown) { } CardItem *CardList::findCard(const int id, const bool remove, int *position) { - if (!contentsKnown) { - if (empty()) - return 0; - CardItem *temp = at(0); - if (remove) - removeAt(0); - if (position) - *position = id; - return temp; - } else - for (int i = 0; i < size(); i++) { - CardItem *temp = at(i); - if (temp->getId() == id) { - if (remove) - removeAt(i); - if (position) - *position = i; - return temp; - } - } - return 0; + if (!contentsKnown) { + if (empty()) + return 0; + CardItem *temp = at(0); + if (remove) + removeAt(0); + if (position) + *position = id; + return temp; + } else + for (int i = 0; i < size(); i++) { + CardItem *temp = at(i); + if (temp->getId() == id) { + if (remove) + removeAt(i); + if (position) + *position = i; + return temp; + } + } + return 0; } class CardList::compareFunctor { private: - int flags; + int flags; public: - compareFunctor(int _flags) : flags(_flags) - { - } - inline bool operator()(CardItem *a, CardItem *b) const - { - if (flags & SortByType) { - QString t1 = a->getInfo()->getMainCardType(); - QString t2 = b->getInfo()->getMainCardType(); - if ((t1 == t2) && (flags & SortByName)) - return a->getName() < b->getName(); - return t1 < t2; - } else - return a->getName() < b->getName(); - } + compareFunctor(int _flags) : flags(_flags) + { + } + inline bool operator()(CardItem *a, CardItem *b) const + { + if (flags & SortByType) { + QString t1 = a->getInfo()->getMainCardType(); + QString t2 = b->getInfo()->getMainCardType(); + if ((t1 == t2) && (flags & SortByName)) + return a->getName() < b->getName(); + return t1 < t2; + } else + return a->getName() < b->getName(); + } }; void CardList::sort(int flags) { - compareFunctor cf(flags); - qSort(begin(), end(), cf); + compareFunctor cf(flags); + qSort(begin(), end(), cf); } diff --git a/cockatrice/src/cardlist.h b/cockatrice/src/cardlist.h index 101973af6..d148ca6de 100644 --- a/cockatrice/src/cardlist.h +++ b/cockatrice/src/cardlist.h @@ -7,15 +7,15 @@ class CardItem; class CardList : public QList { private: - class compareFunctor; + class compareFunctor; protected: - bool contentsKnown; + bool contentsKnown; public: - enum SortFlags { SortByName = 1, SortByType = 2 }; - CardList(bool _contentsKnown); - CardItem *findCard(const int id, const bool remove, int *position = NULL); - bool getContentsKnown() const { return contentsKnown; } - void sort(int flags = SortByName); + enum SortFlags { SortByName = 1, SortByType = 2 }; + CardList(bool _contentsKnown); + CardItem *findCard(const int id, const bool remove, int *position = NULL); + bool getContentsKnown() const { return contentsKnown; } + void sort(int flags = SortByName); }; #endif diff --git a/cockatrice/src/cardzone.cpp b/cockatrice/src/cardzone.cpp index cb2c24ccf..dc26263b4 100644 --- a/cockatrice/src/cardzone.cpp +++ b/cockatrice/src/cardzone.cpp @@ -6,188 +6,261 @@ #include "carditem.h" #include "player.h" #include "zoneviewzone.h" -#include "protocol_items.h" +#include "pb/command_move_card.pb.h" +#include "pb/serverinfo_user.pb.h" -CardZone::CardZone(Player *_p, const QString &_name, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent, bool isView) - : AbstractGraphicsItem(parent), player(_p), name(_name), cards(_contentsKnown), view(NULL), menu(NULL), doubleClickAction(0), hasCardAttr(_hasCardAttr), isShufflable(_isShufflable) +CardZone::CardZone(Player *_p, const QString &_name, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent, bool _isView) + : AbstractGraphicsItem(parent), player(_p), name(_name), cards(_contentsKnown), view(NULL), menu(NULL), doubleClickAction(0), hasCardAttr(_hasCardAttr), isShufflable(_isShufflable), isView(_isView) { - if (!isView) - player->addZone(this); + if (!isView) + player->addZone(this); } CardZone::~CardZone() { - qDebug() << "CardZone destructor: " << name; - delete view; - clearContents(); + qDebug() << "CardZone destructor: " << name; + delete view; + clearContents(); } void CardZone::retranslateUi() { - for (int i = 0; i < cards.size(); ++i) - cards[i]->retranslateUi(); + for (int i = 0; i < cards.size(); ++i) + cards[i]->retranslateUi(); } void CardZone::clearContents() { - for (int i = 0; i < cards.size(); i++) { - // If an incorrectly implemented server doesn't return attached cards to whom they belong before dropping a player, - // we have to return them to avoid a crash. - const QList &attachedCards = cards[i]->getAttachedCards(); - for (int j = 0; j < attachedCards.size(); ++j) - attachedCards[j]->setParentItem(attachedCards[j]->getZone()); - - player->deleteCard(cards.at(i)); - } - cards.clear(); - emit cardCountChanged(); + for (int i = 0; i < cards.size(); i++) { + // If an incorrectly implemented server doesn't return attached cards to whom they belong before dropping a player, + // we have to return them to avoid a crash. + const QList &attachedCards = cards[i]->getAttachedCards(); + for (int j = 0; j < attachedCards.size(); ++j) + attachedCards[j]->setParentItem(attachedCards[j]->getZone()); + + player->deleteCard(cards.at(i)); + } + cards.clear(); + emit cardCountChanged(); } QString CardZone::getTranslatedName(bool hisOwn, GrammaticalCase gc) const { - QString ownerName = player->getName(); - bool female = player->getUserInfo()->getGender() == ServerInfo_User::Female; - if (name == "hand") - switch (gc) { - case CaseNominative: return female ? (hisOwn ? tr("her hand", "nominative, female owner") : tr("%1's hand", "nominative, female owner").arg(ownerName)) : (hisOwn ? tr("his hand", "nominative, male owner") : tr("%1's hand", "nominative, male owner").arg(ownerName)); - case CaseGenitive: return female ? (hisOwn ? tr("of her hand", "genitive, female owner") : tr("of %1's hand", "genitive, female owner").arg(ownerName)) : (hisOwn ? tr("of his hand", "genitive, male owner") : tr("of %1's hand", "genitive, male owner").arg(ownerName)); - case CaseAccusative: return female ? (hisOwn ? tr("her hand", "accusative, female owner") : tr("%1's hand", "accusative, female owner").arg(ownerName)) : (hisOwn ? tr("his hand", "accusative, male owner") : tr("%1's hand", "accusative, male owner").arg(ownerName)); - } - else if (name == "deck") - switch (gc) { - case CaseNominative: return female ? (hisOwn ? tr("her library", "nominative, female owner") : tr("%1's library", "nominative, female owner").arg(ownerName)) : (hisOwn ? tr("his library", "nominative, male owner") : tr("%1's library", "nominative, male owner").arg(ownerName)); - case CaseGenitive: return female ? (hisOwn ? tr("of her library", "genitive, female owner") : tr("of %1's library", "genitive, female owner").arg(ownerName)) : (hisOwn ? tr("of his library", "genitive, male owner") : tr("of %1's library", "genitive, male owner").arg(ownerName)); - case CaseAccusative: return female ? (hisOwn ? tr("her library", "accusative, female owner") : tr("%1's library", "accusative, female owner").arg(ownerName)) : (hisOwn ? tr("his library", "accusative, male owner") : tr("%1's library", "accusative, male owner").arg(ownerName)); - } - else if (name == "grave") - switch (gc) { - case CaseNominative: return female ? (hisOwn ? tr("her graveyard", "nominative, female owner") : tr("%1's graveyard", "nominative, female owner").arg(ownerName)) : (hisOwn ? tr("his graveyard", "nominative, male owner") : tr("%1's graveyard", "nominative, male owner").arg(ownerName)); - case CaseGenitive: return female ? (hisOwn ? tr("of her graveyard", "genitive, female owner") : tr("of %1's graveyard", "genitive, female owner").arg(ownerName)) : (hisOwn ? tr("of his graveyard", "genitive, male owner") : tr("of %1's graveyard", "genitive, male owner").arg(ownerName)); - case CaseAccusative: return female ? (hisOwn ? tr("her graveyard", "accusative, female owner") : tr("%1's graveyard", "accusative, female owner").arg(ownerName)) : (hisOwn ? tr("his graveyard", "accusative, male owner") : tr("%1's graveyard", "accusative, male owner").arg(ownerName)); - } - else if (name == "rfg") - switch (gc) { - case CaseNominative: return female ? (hisOwn ? tr("her exile", "nominative, female owner") : tr("%1's exile", "nominative, female owner").arg(ownerName)) : (hisOwn ? tr("his exile", "nominative, male owner") : tr("%1's exile", "nominative, male owner").arg(ownerName)); - case CaseGenitive: return female ? (hisOwn ? tr("of her exile", "genitive, female owner") : tr("of %1's exile", "genitive, female owner").arg(ownerName)) : (hisOwn ? tr("of his exile", "genitive, male owner") : tr("of %1's exile", "genitive, male owner").arg(ownerName)); - case CaseAccusative: return female ? (hisOwn ? tr("her exile", "accusative, female owner") : tr("%1's exile", "accusative, female owner").arg(ownerName)) : (hisOwn ? tr("his exile", "accusative, male owner") : tr("%1's exile", "accusative, male owner").arg(ownerName)); - } - else if (name == "sb") - switch (gc) { - case CaseNominative: return female ? (hisOwn ? tr("her sideboard", "nominative, female owner") : tr("%1's sideboard", "nominative, female owner").arg(ownerName)) : (hisOwn ? tr("his sideboard", "nominative, male owner") : tr("%1's sideboard", "nominative, male owner").arg(ownerName)); - case CaseGenitive: return female ? (hisOwn ? tr("of her sideboard", "genitive, female owner") : tr("of %1's sideboard", "genitive, female owner").arg(ownerName)) : (hisOwn ? tr("of his sideboard", "genitive, male owner") : tr("of %1's sideboard", "genitive, male owner").arg(ownerName)); - case CaseAccusative: return female ? (hisOwn ? tr("her sideboard", "accusative, female owner") : tr("%1's sideboard", "accusative, female owner").arg(ownerName)) : (hisOwn ? tr("his sideboard", "accusative, male owner") : tr("%1's sideboard", "accusative, male owner").arg(ownerName)); - } - return QString(); + QString ownerName = player->getName(); + bool female = player->getUserInfo()->gender() == ServerInfo_User::Female; + if (name == "hand") + return female + ? (hisOwn + ? tr("her hand", "nominative, female owner") + : tr("%1's hand", "nominative, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his hand", "nominative, male owner") + : tr("%1's hand", "nominative, male owner").arg(ownerName) + ); + else if (name == "deck") + switch (gc) { + case CaseLookAtZone: + return female + ? (hisOwn + ? tr("her library", "look at zone, female owner") + : tr("%1's library", "look at zone, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his library", "look at zone, male owner") + : tr("%1's library", "look at zone, male owner").arg(ownerName) + ); + case CaseTopCardsOfZone: + return female + ? (hisOwn + ? tr("of her library", "top cards of zone, female owner") + : tr("of %1's library", "top cards of zone, female owner").arg(ownerName) + ) : (hisOwn + ? tr("of his library", "top cards of zone, male owner") + : tr("of %1's library", "top cards of zone, male owner").arg(ownerName) + ); + case CaseRevealZone: + return female + ? (hisOwn + ? tr("her library", "reveal zone, female owner") + : tr("%1's library", "reveal zone, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his library", "reveal zone, male owner") + : tr("%1's library", "reveal zone, male owner").arg(ownerName) + ); + case CaseShuffleZone: + return female + ? (hisOwn + ? tr("her library", "shuffle, female owner") + : tr("%1's library", "shuffle, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his library", "shuffle, male owner") + : tr("%1's library", "shuffle, male owner").arg(ownerName) + ); + default: + return female + ? (hisOwn + ? tr("her library", "nominative, female owner") + : tr("%1's library", "nominative, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his library", "nominative, male owner") + : tr("%1's library", "nominative, male owner").arg(ownerName) + ); + } + else if (name == "grave") + return female + ? (hisOwn + ? tr("her graveyard", "nominative, female owner") + : tr("%1's graveyard", "nominative, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his graveyard", "nominative, male owner") + : tr("%1's graveyard", "nominative, male owner").arg(ownerName) + ); + else if (name == "rfg") + return female + ? (hisOwn + ? tr("her exile", "nominative, female owner") + : tr("%1's exile", "nominative, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his exile", "nominative, male owner") + : tr("%1's exile", "nominative, male owner").arg(ownerName) + ); + else if (name == "sb") + switch (gc) { + case CaseLookAtZone: + return female + ? (hisOwn + ? tr("her sideboard", "look at zone, female owner") + : tr("%1's sideboard", "look at zone, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his sideboard", "look at zone, male owner") + : tr("%1's sideboard", "look at zone, male owner").arg(ownerName) + ); + case CaseNominative: + return female + ? (hisOwn + ? tr("her sideboard", "nominative, female owner") + : tr("%1's sideboard", "nominative, female owner").arg(ownerName) + ) : (hisOwn + ? tr("his sideboard", "nominative, male owner") + : tr("%1's sideboard", "nominative, male owner").arg(ownerName) + ); + default: break; + } + return QString(); } -void CardZone::mouseDoubleClickEvent(QGraphicsSceneMouseEvent */*event*/) +void CardZone::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/) { - if (doubleClickAction) - doubleClickAction->trigger(); + if (doubleClickAction) + doubleClickAction->trigger(); } bool CardZone::showContextMenu(const QPoint &screenPos) { - if (menu) { - menu->exec(screenPos); - return true; - } - return false; + if (menu) { + menu->exec(screenPos); + return true; + } + return false; } void CardZone::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::RightButton) { - if (showContextMenu(event->screenPos())) - event->accept(); - else - event->ignore(); - } else - event->ignore(); + if (event->button() == Qt::RightButton) { + if (showContextMenu(event->screenPos())) + event->accept(); + else + event->ignore(); + } else + event->ignore(); } void CardZone::addCard(CardItem *card, bool reorganize, int x, int y) { - if (view) - if ((x <= view->getCards().size()) || (view->getNumberCards() == -1)) - view->addCard(new CardItem(player, card->getName(), card->getId()), reorganize, x, y); + if (view) + if ((x <= view->getCards().size()) || (view->getNumberCards() == -1)) + view->addCard(new CardItem(player, card->getName(), card->getId()), reorganize, x, y); - card->setZone(this); - addCardImpl(card, x, y); + card->setZone(this); + addCardImpl(card, x, y); - if (reorganize) - reorganizeCards(); - - emit cardCountChanged(); + if (reorganize) + reorganizeCards(); + + emit cardCountChanged(); } CardItem *CardZone::getCard(int cardId, const QString &cardName) { - CardItem *c = cards.findCard(cardId, false); - if (!c) { - qDebug() << "CardZone::getCard: card id=" << cardId << "not found"; - return 0; - } - // If the card's id is -1, this zone is invisible, - // so we need to give the card an id and a name as it comes out. - // It can be assumed that in an invisible zone, all cards are equal. - if ((c->getId() == -1) || (c->getName().isEmpty())) { - c->setId(cardId); - c->setName(cardName); - } - return c; + CardItem *c = cards.findCard(cardId, false); + if (!c) { + qDebug() << "CardZone::getCard: card id=" << cardId << "not found"; + return 0; + } + // If the card's id is -1, this zone is invisible, + // so we need to give the card an id and a name as it comes out. + // It can be assumed that in an invisible zone, all cards are equal. + if ((c->getId() == -1) || (c->getName().isEmpty())) { + c->setId(cardId); + c->setName(cardName); + } + return c; } CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/) { - if (position == -1) { - // position == -1 means either that the zone is indexed by card id - // or that it doesn't matter which card you take. - for (int i = 0; i < cards.size(); ++i) - if (cards[i]->getId() == cardId) { - position = i; - break; - } - if (position == -1) - position = 0; - } - if (position >= cards.size()) - return 0; + if (position == -1) { + // position == -1 means either that the zone is indexed by card id + // or that it doesn't matter which card you take. + for (int i = 0; i < cards.size(); ++i) + if (cards[i]->getId() == cardId) { + position = i; + break; + } + if (position == -1) + position = 0; + } + if (position >= cards.size()) + return 0; - CardItem *c = cards.takeAt(position); + CardItem *c = cards.takeAt(position); - if (view) - view->removeCard(position); + if (view) + view->removeCard(position); - c->setId(cardId); + c->setId(cardId); - reorganizeCards(); - emit cardCountChanged(); - return c; + reorganizeCards(); + emit cardCountChanged(); + return c; } void CardZone::removeCard(CardItem *card) { - cards.removeAt(cards.indexOf(card)); - reorganizeCards(); - emit cardCountChanged(); - player->deleteCard(card); + cards.removeAt(cards.indexOf(card)); + reorganizeCards(); + emit cardCountChanged(); + player->deleteCard(card); } void CardZone::moveAllToZone() { - QList data = static_cast(sender())->data().toList(); - QString targetZone = data[0].toString(); - int targetX = data[1].toInt(); - - QList idList; - for (int i = 0; i < cards.size(); ++i) - idList.append(new CardToMove(cards[i]->getId())); - - player->sendGameCommand(new Command_MoveCard(-1, getName(), idList, player->getId(), targetZone, targetX)); + QList data = static_cast(sender())->data().toList(); + QString targetZone = data[0].toString(); + int targetX = data[1].toInt(); + + Command_MoveCard cmd; + cmd.set_start_zone(getName().toStdString()); + cmd.set_target_player_id(player->getId()); + cmd.set_target_zone(targetZone.toStdString()); + cmd.set_x(targetX); + + for (int i = 0; i < cards.size(); ++i) + cmd.mutable_cards_to_move()->add_card()->set_card_id(cards[i]->getId()); + + player->sendGameCommand(cmd); } QPointF CardZone::closestGridPoint(const QPointF &point) { - return point; + return point; } diff --git a/cockatrice/src/cardzone.h b/cockatrice/src/cardzone.h index 31955b158..adaf0141e 100644 --- a/cockatrice/src/cardzone.h +++ b/cockatrice/src/cardzone.h @@ -3,7 +3,6 @@ #include #include "cardlist.h" -#include "carditem.h" #include "abstractgraphicsitem.h" #include "translation.h" @@ -15,51 +14,56 @@ class QPainter; class CardDragItem; class CardZone : public AbstractGraphicsItem { - Q_OBJECT + Q_OBJECT protected: - Player *player; - QString name; - CardList cards; - ZoneViewZone *view; - QMenu *menu; - QAction *doubleClickAction; - bool hasCardAttr; - bool isShufflable; - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - virtual void addCardImpl(CardItem *card, int x, int y) = 0; + Player *player; + QString name; + CardList cards; + ZoneViewZone *view; + QMenu *menu; + QAction *doubleClickAction; + bool hasCardAttr; + bool isShufflable; + bool isView; + bool alwaysRevealTopCard; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + virtual void addCardImpl(CardItem *card, int x, int y) = 0; signals: - void cardCountChanged(); + void cardCountChanged(); public slots: - void moveAllToZone(); - bool showContextMenu(const QPoint &screenPos); + void moveAllToZone(); + bool showContextMenu(const QPoint &screenPos); public: - enum { Type = typeZone }; - int type() const { return Type; } - virtual void handleDropEvent(const QList &dragItem, CardZone *startZone, const QPoint &dropPoint) = 0; - CardZone(Player *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent = 0, bool isView = false); - ~CardZone(); - void retranslateUi(); - void clearContents(); - bool getHasCardAttr() const { return hasCardAttr; } - bool getIsShufflable() const { return isShufflable; } - QMenu *getMenu() const { return menu; } - void setMenu(QMenu *_menu, QAction *_doubleClickAction = 0) { menu = _menu; doubleClickAction = _doubleClickAction; } - QString getName() const { return name; } - QString getTranslatedName(bool hisOwn, GrammaticalCase gc) const; - Player *getPlayer() const { return player; } - bool contentsKnown() const { return cards.getContentsKnown(); } - const CardList &getCards() const { return cards; } - void addCard(CardItem *card, bool reorganize, int x, int y = -1); - // getCard() finds a card by id. - CardItem *getCard(int cardId, const QString &cardName); - // takeCard() finds a card by position and removes it from the zone and from all of its views. - virtual CardItem *takeCard(int position, int cardId, bool canResize = true); - void removeCard(CardItem *card); - ZoneViewZone *getView() const { return view; } - void setView(ZoneViewZone *_view) { view = _view; } - virtual void reorganizeCards() = 0; - virtual QPointF closestGridPoint(const QPointF &point); + enum { Type = typeZone }; + int type() const { return Type; } + virtual void handleDropEvent(const QList &dragItem, CardZone *startZone, const QPoint &dropPoint) = 0; + CardZone(Player *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent = 0, bool _isView = false); + ~CardZone(); + void retranslateUi(); + void clearContents(); + bool getHasCardAttr() const { return hasCardAttr; } + bool getIsShufflable() const { return isShufflable; } + QMenu *getMenu() const { return menu; } + void setMenu(QMenu *_menu, QAction *_doubleClickAction = 0) { menu = _menu; doubleClickAction = _doubleClickAction; } + QString getName() const { return name; } + QString getTranslatedName(bool hisOwn, GrammaticalCase gc) const; + Player *getPlayer() const { return player; } + bool contentsKnown() const { return cards.getContentsKnown(); } + const CardList &getCards() const { return cards; } + void addCard(CardItem *card, bool reorganize, int x, int y = -1); + // getCard() finds a card by id. + CardItem *getCard(int cardId, const QString &cardName); + // takeCard() finds a card by position and removes it from the zone and from all of its views. + virtual CardItem *takeCard(int position, int cardId, bool canResize = true); + void removeCard(CardItem *card); + ZoneViewZone *getView() const { return view; } + void setView(ZoneViewZone *_view) { view = _view; } + virtual void reorganizeCards() = 0; + virtual QPointF closestGridPoint(const QPointF &point); + bool getIsView() const { return isView; } + bool getAlwaysRevealTopCard() const { return alwaysRevealTopCard; } + void setAlwaysRevealTopCard(bool _alwaysRevealTopCard) { alwaysRevealTopCard = _alwaysRevealTopCard; } }; #endif diff --git a/cockatrice/src/chatview.cpp b/cockatrice/src/chatview.cpp index 3694fb0b6..ae1576473 100644 --- a/cockatrice/src/chatview.cpp +++ b/cockatrice/src/chatview.cpp @@ -4,197 +4,262 @@ #include #include #include "chatview.h" +#include "user_level.h" +#include "user_context_menu.h" +#include "tab_supervisor.h" +#include "pixmapgenerator.h" -ChatView::ChatView(const QString &_ownName, bool _showTimestamps, QWidget *parent) - : QTextBrowser(parent), evenNumber(true), ownName(_ownName), showTimestamps(_showTimestamps) +ChatView::ChatView(const TabSupervisor *_tabSupervisor, TabGame *_game, bool _showTimestamps, QWidget *parent) + : QTextBrowser(parent), tabSupervisor(_tabSupervisor), game(_game), evenNumber(true), showTimestamps(_showTimestamps), hoveredItemType(HoveredNothing) { - setReadOnly(true); - setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); - setOpenLinks(false); - connect(this, SIGNAL(anchorClicked(const QUrl &)), this, SLOT(openLink(const QUrl &))); + document()->setDefaultStyleSheet("a { text-decoration: none; color: blue; }"); + userContextMenu = new UserContextMenu(tabSupervisor, this, game); + connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); + + viewport()->setCursor(Qt::IBeamCursor); + setReadOnly(true); + setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); + setOpenLinks(false); + connect(this, SIGNAL(anchorClicked(const QUrl &)), this, SLOT(openLink(const QUrl &))); +} + +void ChatView::retranslateUi() +{ + userContextMenu->retranslateUi(); } QTextCursor ChatView::prepareBlock(bool same) { - lastSender.clear(); - - QTextCursor cursor(document()->lastBlock()); - cursor.movePosition(QTextCursor::End); - if (!same) { - QTextBlockFormat blockFormat; - if ((evenNumber = !evenNumber)) - blockFormat.setBackground(palette().alternateBase()); - blockFormat.setBottomMargin(2); - cursor.insertBlock(blockFormat); - } else - cursor.insertHtml("
"); - - return cursor; + lastSender.clear(); + + QTextCursor cursor(document()->lastBlock()); + cursor.movePosition(QTextCursor::End); + if (!same) { + QTextBlockFormat blockFormat; + if ((evenNumber = !evenNumber)) + blockFormat.setBackground(palette().alternateBase()); + blockFormat.setBottomMargin(2); + cursor.insertBlock(blockFormat); + } else + cursor.insertHtml("
"); + + return cursor; } void ChatView::appendHtml(const QString &html) { - bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum(); - prepareBlock().insertHtml(html); - if (atBottom) - verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum(); + prepareBlock().insertHtml(html); + if (atBottom) + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } -void ChatView::appendMessage(QString sender, QString message, QColor playerColor, bool playerBold) +void ChatView::appendCardTag(QTextCursor &cursor, const QString &cardName) { - bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum(); - bool sameSender = (sender == lastSender) && !lastSender.isEmpty(); - QTextCursor cursor = prepareBlock(sameSender); - lastSender = sender; - - if (showTimestamps && !sameSender) { - QTextCharFormat timeFormat; - timeFormat.setForeground(Qt::black); - cursor.setCharFormat(timeFormat); - cursor.insertText(QDateTime::currentDateTime().toString("[hh:mm] ")); - } - - QTextCharFormat senderFormat; - if (sender == ownName) { - senderFormat.setFontWeight(QFont::Bold); - senderFormat.setForeground(Qt::red); - } else { - if (playerColor == QColor()) - senderFormat.setForeground(QColor(0, 0, 254)); - else - senderFormat.setForeground(playerColor); - if (playerBold) - senderFormat.setFontWeight(QFont::Bold); - } - if (!sameSender) { - cursor.setCharFormat(senderFormat); - if (!sender.isEmpty()) - sender.append(": "); - cursor.insertText(sender); - } else - cursor.insertText(" "); - - QTextCharFormat messageFormat; - if (sender.isEmpty()) - messageFormat.setForeground(Qt::darkGreen); - cursor.setCharFormat(messageFormat); - - int from = 0, index = 0; - while ((index = message.indexOf('[', from)) != -1) { - cursor.insertText(message.left(index)); - message = message.mid(index); - if (message.isEmpty()) - break; - - if (message.startsWith("[card]")) { - message = message.mid(6); - QTextCharFormat tempFormat = messageFormat; - tempFormat.setForeground(Qt::blue); - cursor.setCharFormat(tempFormat); - int closeTagIndex = message.indexOf("[/card]"); - cursor.insertText(message.left(closeTagIndex)); - cursor.setCharFormat(messageFormat); - if (closeTagIndex == -1) - message.clear(); - else - message = message.mid(closeTagIndex + 7); - } else if (message.startsWith("[url]")) { - message = message.mid(5); - int closeTagIndex = message.indexOf("[/url]"); - QString url = message.left(closeTagIndex); - if (!url.startsWith("http://")) - url.prepend("http://"); - QTextCharFormat tempFormat = messageFormat; - tempFormat.setForeground(QColor(0, 0, 254)); - tempFormat.setAnchor(true); - tempFormat.setAnchorHref(url); - cursor.setCharFormat(tempFormat); - cursor.insertText(url); - cursor.setCharFormat(messageFormat); - if (closeTagIndex == -1) - message.clear(); - else - message = message.mid(closeTagIndex + 6); - } else - from = 1; - } - if (!message.isEmpty()) - cursor.insertText(message); - - if (atBottom) - verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + QTextCharFormat oldFormat = cursor.charFormat(); + QTextCharFormat anchorFormat = oldFormat; + anchorFormat.setForeground(Qt::blue); + anchorFormat.setAnchor(true); + anchorFormat.setAnchorHref("card://" + cardName); + + cursor.setCharFormat(anchorFormat); + cursor.insertText(cardName); + cursor.setCharFormat(oldFormat); +} + +void ChatView::appendUrlTag(QTextCursor &cursor, QString url) +{ + if (!url.contains("://")) + url.prepend("http://"); + + QTextCharFormat oldFormat = cursor.charFormat(); + QTextCharFormat anchorFormat = oldFormat; + anchorFormat.setForeground(Qt::blue); + anchorFormat.setAnchor(true); + anchorFormat.setAnchorHref(url); + + cursor.setCharFormat(anchorFormat); + cursor.insertText(url); + cursor.setCharFormat(oldFormat); +} + +void ChatView::appendMessage(QString message, QString sender, UserLevelFlags userLevel, bool playerBold) +{ + bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum(); + bool sameSender = (sender == lastSender) && !lastSender.isEmpty(); + QTextCursor cursor = prepareBlock(sameSender); + lastSender = sender; + + if (showTimestamps && !sameSender) { + QTextCharFormat timeFormat; + timeFormat.setForeground(Qt::black); + cursor.setCharFormat(timeFormat); + cursor.insertText(QDateTime::currentDateTime().toString("[hh:mm] ")); + } + + QTextCharFormat senderFormat; + if (tabSupervisor && tabSupervisor->getUserInfo() && (sender == QString::fromStdString(tabSupervisor->getUserInfo()->name()))) { + senderFormat.setFontWeight(QFont::Bold); + senderFormat.setForeground(Qt::red); + } else { + senderFormat.setForeground(Qt::blue); + if (playerBold) + senderFormat.setFontWeight(QFont::Bold); + } + senderFormat.setAnchor(true); + senderFormat.setAnchorHref("user://" + QString::number(userLevel) + "_" + sender); + if (!sameSender) { + if (!sender.isEmpty()) { + const int pixelSize = QFontInfo(cursor.charFormat().font()).pixelSize(); + cursor.insertImage(UserLevelPixmapGenerator::generatePixmap(pixelSize, userLevel).toImage(), QString::number(pixelSize) + "_" + QString::number((int) userLevel)); + cursor.insertText(" "); + } + cursor.setCharFormat(senderFormat); + if (!sender.isEmpty()) + sender.append(": "); + cursor.insertText(sender); + } else + cursor.insertText(" "); + + QTextCharFormat messageFormat; + if (sender.isEmpty()) + messageFormat.setForeground(Qt::darkGreen); + cursor.setCharFormat(messageFormat); + + int from = 0, index = 0; + while ((index = message.indexOf('[', from)) != -1) { + cursor.insertText(message.left(index)); + message = message.mid(index); + if (message.isEmpty()) + break; + + if (message.startsWith("[card]")) { + message = message.mid(6); + int closeTagIndex = message.indexOf("[/card]"); + QString cardName = message.left(closeTagIndex); + if (closeTagIndex == -1) + message.clear(); + else + message = message.mid(closeTagIndex + 7); + + appendCardTag(cursor, cardName); + } else if (message.startsWith("[[")) { + message = message.mid(2); + int closeTagIndex = message.indexOf("]]"); + QString cardName = message.left(closeTagIndex); + if (closeTagIndex == -1) + message.clear(); + else + message = message.mid(closeTagIndex + 2); + + appendCardTag(cursor, cardName); + } else if (message.startsWith("[url]")) { + message = message.mid(5); + int closeTagIndex = message.indexOf("[/url]"); + QString url = message.left(closeTagIndex); + if (closeTagIndex == -1) + message.clear(); + else + message = message.mid(closeTagIndex + 6); + + appendUrlTag(cursor, url); + } else + from = 1; + } + if (!message.isEmpty()) + cursor.insertText(message); + + if (atBottom) + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } void ChatView::enterEvent(QEvent * /*event*/) { - setMouseTracking(true); + setMouseTracking(true); } void ChatView::leaveEvent(QEvent * /*event*/) { - setMouseTracking(false); + setMouseTracking(false); } QTextFragment ChatView::getFragmentUnderMouse(const QPoint &pos) const { - QTextCursor cursor(cursorForPosition(pos)); - QTextBlock block(cursor.block()); - QTextBlock::iterator it; - for (it = block.begin(); !(it.atEnd()); ++it) { - QTextFragment frag = it.fragment(); - if (frag.contains(cursor.position())) - return frag; - } - return QTextFragment(); -} - -QString ChatView::getCardNameUnderMouse(QTextFragment frag) const -{ - if (frag.charFormat().foreground().color() == Qt::blue) - return frag.text(); - return QString(); -} - -QString ChatView::getCardNameUnderMouse(const QPoint &pos) const -{ - return getCardNameUnderMouse(getFragmentUnderMouse(pos)); + QTextCursor cursor(cursorForPosition(pos)); + QTextBlock block(cursor.block()); + QTextBlock::iterator it; + for (it = block.begin(); !(it.atEnd()); ++it) { + QTextFragment frag = it.fragment(); + if (frag.contains(cursor.position())) + return frag; + } + return QTextFragment(); } void ChatView::mouseMoveEvent(QMouseEvent *event) { - QTextFragment frag = getFragmentUnderMouse(event->pos()); - QString cardName = getCardNameUnderMouse(frag); - if (!cardName.isEmpty()) { - viewport()->setCursor(Qt::PointingHandCursor); - emit cardNameHovered(cardName); - } else if (frag.charFormat().isAnchor()) - viewport()->setCursor(Qt::PointingHandCursor); - else - viewport()->setCursor(Qt::IBeamCursor); - - QTextBrowser::mouseMoveEvent(event); + QString anchorHref = getFragmentUnderMouse(event->pos()).charFormat().anchorHref(); + if (!anchorHref.isEmpty()) { + const int delimiterIndex = anchorHref.indexOf("://"); + if (delimiterIndex != -1) { + const QString scheme = anchorHref.left(delimiterIndex); + hoveredContent = anchorHref.mid(delimiterIndex + 3); + if (scheme == "card") { + hoveredItemType = HoveredCard; + emit cardNameHovered(hoveredContent); + } else if (scheme == "user") + hoveredItemType = HoveredUser; + else + hoveredItemType = HoveredUrl; + viewport()->setCursor(Qt::PointingHandCursor); + } else { + hoveredItemType = HoveredNothing; + viewport()->setCursor(Qt::IBeamCursor); + } + } else { + hoveredItemType = HoveredNothing; + viewport()->setCursor(Qt::IBeamCursor); + } + + QTextBrowser::mouseMoveEvent(event); } void ChatView::mousePressEvent(QMouseEvent *event) { - if ((event->button() == Qt::MidButton) || (event->button() == Qt::LeftButton)) { - QString cardName = getCardNameUnderMouse(event->pos()); - if (!cardName.isEmpty()) - emit showCardInfoPopup(event->globalPos(), cardName); - } - - QTextBrowser::mousePressEvent(event); + switch (hoveredItemType) { + case HoveredCard: { + if ((event->button() == Qt::MidButton) || (event->button() == Qt::LeftButton)) + emit showCardInfoPopup(event->globalPos(), hoveredContent); + break; + } + case HoveredUser: { + if (event->button() == Qt::RightButton) { + const int delimiterIndex = hoveredContent.indexOf("_"); + UserLevelFlags userLevel(hoveredContent.left(delimiterIndex).toInt()); + const QString userName = hoveredContent.mid(delimiterIndex + 1); + + userContextMenu->showContextMenu(event->globalPos(), userName, userLevel); + } + break; + } + default: { + QTextBrowser::mousePressEvent(event); + } + } } void ChatView::mouseReleaseEvent(QMouseEvent *event) { - if ((event->button() == Qt::MidButton) || (event->button() == Qt::LeftButton)) - emit deleteCardInfoPopup(QString("_")); - - QTextBrowser::mouseReleaseEvent(event); + if ((event->button() == Qt::MidButton) || (event->button() == Qt::LeftButton)) + emit deleteCardInfoPopup(QString("_")); + + QTextBrowser::mouseReleaseEvent(event); } void ChatView::openLink(const QUrl &link) { - QDesktopServices::openUrl(link); + if ((link.scheme() == "card") || (link.scheme() == "user")) + return; + + QDesktopServices::openUrl(link); } diff --git a/cockatrice/src/chatview.h b/cockatrice/src/chatview.h index 33c583c68..1e8a2022e 100644 --- a/cockatrice/src/chatview.h +++ b/cockatrice/src/chatview.h @@ -5,37 +5,49 @@ #include #include #include +#include "user_level.h" class QTextTable; class QMouseEvent; +class UserContextMenu; +class TabSupervisor; +class TabGame; class ChatView : public QTextBrowser { - Q_OBJECT; -private: - QString lastSender; - bool evenNumber; - QString ownName; - bool showTimestamps; - QTextFragment getFragmentUnderMouse(const QPoint &pos) const; - QString getCardNameUnderMouse(QTextFragment frag) const; - QString getCardNameUnderMouse(const QPoint &pos) const; - QTextCursor prepareBlock(bool same = false); -private slots: - void openLink(const QUrl &link); -public: - ChatView(const QString &_ownName, bool _showTimestamps, QWidget *parent = 0); - void appendHtml(const QString &html); - void appendMessage(QString sender, QString message, QColor playerColor = QColor(), bool playerBold = false); + Q_OBJECT protected: - void enterEvent(QEvent *event); - void leaveEvent(QEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void mousePressEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); + const TabSupervisor * const tabSupervisor; + TabGame * const game; +private: + enum HoveredItemType { HoveredNothing, HoveredUrl, HoveredCard, HoveredUser }; + UserContextMenu *userContextMenu; + QString lastSender; + bool evenNumber; + bool showTimestamps; + HoveredItemType hoveredItemType; + QString hoveredContent; + QTextFragment getFragmentUnderMouse(const QPoint &pos) const; + QTextCursor prepareBlock(bool same = false); + void appendCardTag(QTextCursor &cursor, const QString &cardName); + void appendUrlTag(QTextCursor &cursor, QString url); +private slots: + void openLink(const QUrl &link); +public: + ChatView(const TabSupervisor *_tabSupervisor, TabGame *_game, bool _showTimestamps, QWidget *parent = 0); + void retranslateUi(); + void appendHtml(const QString &html); + void appendMessage(QString message, QString sender = QString(), UserLevelFlags userLevel = UserLevelFlags(), bool playerBold = false); +protected: + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); signals: - void cardNameHovered(QString cardName); - void showCardInfoPopup(QPoint pos, QString cardName); - void deleteCardInfoPopup(QString cardName); + void openMessageDialog(const QString &userName, bool focus); + void cardNameHovered(QString cardName); + void showCardInfoPopup(QPoint pos, QString cardName); + void deleteCardInfoPopup(QString cardName); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/client_metatypes.h b/cockatrice/src/client_metatypes.h new file mode 100644 index 000000000..db1c90f70 --- /dev/null +++ b/cockatrice/src/client_metatypes.h @@ -0,0 +1,29 @@ +#ifndef CLIENT_METATYPES_H +#define CLIENT_METATYPES_H + +#include + +Q_DECLARE_METATYPE(QVariant) +Q_DECLARE_METATYPE(CommandContainer) +Q_DECLARE_METATYPE(Response) +Q_DECLARE_METATYPE(Response::ResponseCode) +Q_DECLARE_METATYPE(ClientStatus) +Q_DECLARE_METATYPE(RoomEvent) +Q_DECLARE_METATYPE(GameEventContainer) +Q_DECLARE_METATYPE(Event_ServerIdentification) +Q_DECLARE_METATYPE(Event_ConnectionClosed) +Q_DECLARE_METATYPE(Event_ServerShutdown) +Q_DECLARE_METATYPE(Event_AddToList) +Q_DECLARE_METATYPE(Event_RemoveFromList) +Q_DECLARE_METATYPE(Event_UserJoined) +Q_DECLARE_METATYPE(Event_UserLeft) +Q_DECLARE_METATYPE(Event_ServerMessage) +Q_DECLARE_METATYPE(Event_ListRooms) +Q_DECLARE_METATYPE(Event_GameJoined) +Q_DECLARE_METATYPE(Event_UserMessage) +Q_DECLARE_METATYPE(ServerInfo_User) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(Event_ReplayAdded) + + +#endif diff --git a/cockatrice/src/counter_general.cpp b/cockatrice/src/counter_general.cpp index 6ded4732e..e282f1bd8 100644 --- a/cockatrice/src/counter_general.cpp +++ b/cockatrice/src/counter_general.cpp @@ -3,34 +3,34 @@ #include GeneralCounter::GeneralCounter(Player *_player, int _id, const QString &_name, const QColor &_color, int _radius, int _value, QGraphicsItem *parent) - : AbstractCounter(_player, _id, _name, true, _value, parent), color(_color), radius(_radius) + : AbstractCounter(_player, _id, _name, true, _value, parent), color(_color), radius(_radius) { - setCacheMode(DeviceCoordinateCache); + setCacheMode(DeviceCoordinateCache); } QRectF GeneralCounter::boundingRect() const { - return QRectF(0, 0, radius * 2, radius * 2); + return QRectF(0, 0, radius * 2, radius * 2); } -void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) +void GeneralCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QRectF mapRect = painter->combinedTransform().mapRect(boundingRect()); - int translatedHeight = mapRect.size().height(); - qreal scaleFactor = translatedHeight / boundingRect().height(); - QPixmap pixmap = CounterPixmapGenerator::generatePixmap(translatedHeight, name, hovered); - - painter->save(); - painter->resetTransform(); - painter->drawPixmap(QPoint(0, 0), pixmap); + QRectF mapRect = painter->combinedTransform().mapRect(boundingRect()); + int translatedHeight = mapRect.size().height(); + qreal scaleFactor = translatedHeight / boundingRect().height(); + QPixmap pixmap = CounterPixmapGenerator::generatePixmap(translatedHeight, name, hovered); + + painter->save(); + painter->resetTransform(); + painter->drawPixmap(QPoint(0, 0), pixmap); - if (value) { - QFont f("Serif"); - f.setPixelSize(qMax((int) (radius * scaleFactor), 10)); - f.setWeight(QFont::Bold); - painter->setPen(Qt::black); - painter->setFont(f); - painter->drawText(mapRect, Qt::AlignCenter, QString::number(value)); - } - painter->restore(); + if (value) { + QFont f("Serif"); + f.setPixelSize(qMax((int) (radius * scaleFactor), 10)); + f.setWeight(QFont::Bold); + painter->setPen(Qt::black); + painter->setFont(f); + painter->drawText(mapRect, Qt::AlignCenter, QString::number(value)); + } + painter->restore(); } diff --git a/cockatrice/src/counter_general.h b/cockatrice/src/counter_general.h index 58468aa8c..2808b6542 100644 --- a/cockatrice/src/counter_general.h +++ b/cockatrice/src/counter_general.h @@ -4,14 +4,14 @@ #include "abstractcounter.h" class GeneralCounter : public AbstractCounter { - Q_OBJECT + Q_OBJECT private: - QColor color; - int radius; + QColor color; + int radius; public: - GeneralCounter(Player *_player, int _id, const QString &_name, const QColor &_color, int _radius, int _value, QGraphicsItem *parent = 0); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + GeneralCounter(Player *_player, int _id, const QString &_name, const QColor &_color, int _radius, int _value, QGraphicsItem *parent = 0); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/deck_loader.cpp b/cockatrice/src/deck_loader.cpp new file mode 100644 index 000000000..4443b43b3 --- /dev/null +++ b/cockatrice/src/deck_loader.cpp @@ -0,0 +1,110 @@ +#include +#include +#include +#include "deck_loader.h" +#include "decklist.h" + +const QStringList DeckLoader::fileNameFilters = QStringList() + << QObject::tr("Common deck formats (*.cod *.dec *.mwDeck)") + << QObject::tr("All files (*.*)"); + +DeckLoader::DeckLoader() + : DeckList(), + lastFileName(QString()), + lastFileFormat(CockatriceFormat), + lastRemoteDeckId(-1) +{ +} + +DeckLoader::DeckLoader(const QString &nativeString) + : DeckList(nativeString), + lastFileName(QString()), + lastFileFormat(CockatriceFormat), + lastRemoteDeckId(-1) +{ +} + +DeckLoader::DeckLoader(const DeckList &other) + : DeckList(other), + lastFileName(QString()), + lastFileFormat(CockatriceFormat), + lastRemoteDeckId(-1) +{ +} + +DeckLoader::DeckLoader(const DeckLoader &other) + : DeckList(other), + lastFileName(other.lastFileName), + lastFileFormat(other.lastFileFormat), + lastRemoteDeckId(other.lastRemoteDeckId) +{ +} + +bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return false; + + bool result = false; + switch (fmt) { + case PlainTextFormat: result = loadFromFile_Plain(&file); break; + case CockatriceFormat: + result = loadFromFile_Native(&file); + qDebug() << "Loaded from" << fileName << "-" << result; + if (!result) { + qDebug() << "Retying as plain format"; + file.seek(0); + result = loadFromFile_Plain(&file); + fmt = PlainTextFormat; + } + break; + } + if (result) { + lastFileName = fileName; + lastFileFormat = fmt; + + emit deckLoaded(); + } + qDebug() << "Deck was loaded -" << result; + return result; +} + +bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) +{ + bool result = loadFromString_Native(nativeString); + if (result) { + lastFileName = QString(); + lastFileFormat = CockatriceFormat; + lastRemoteDeckId = remoteDeckId; + + emit deckLoaded(); + } + return result; +} + +bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + return false; + + bool result = false; + switch (fmt) { + case PlainTextFormat: result = saveToFile_Plain(&file); break; + case CockatriceFormat: result = saveToFile_Native(&file); break; + } + if (result) { + lastFileName = fileName; + lastFileFormat = fmt; + } + return result; +} + +DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName) +{ + if (fileName.endsWith(".cod", Qt::CaseInsensitive)) { + return CockatriceFormat; + } + return PlainTextFormat; +} diff --git a/cockatrice/src/deck_loader.h b/cockatrice/src/deck_loader.h new file mode 100644 index 000000000..70c481d19 --- /dev/null +++ b/cockatrice/src/deck_loader.h @@ -0,0 +1,33 @@ +#ifndef DECK_LOADER_H +#define DECK_LOADER_H + +#include "decklist.h" + +class DeckLoader : public DeckList { + Q_OBJECT +signals: + void deckLoaded(); +public: + enum FileFormat { PlainTextFormat, CockatriceFormat }; + static const QStringList fileNameFilters; +private: + QString lastFileName; + FileFormat lastFileFormat; + int lastRemoteDeckId; +public: + DeckLoader(); + DeckLoader(const QString &nativeString); + DeckLoader(const DeckList &other); + DeckLoader(const DeckLoader &other); + const QString &getLastFileName() const { return lastFileName; } + FileFormat getLastFileFormat() const { return lastFileFormat; } + int getLastRemoteDeckId() const { return lastRemoteDeckId; } + + static FileFormat getFormatFromName(const QString &fileName); + + bool loadFromFile(const QString &fileName, FileFormat fmt); + bool loadFromRemote(const QString &nativeString, int remoteDeckId); + bool saveToFile(const QString &fileName, FileFormat fmt); +}; + +#endif diff --git a/cockatrice/src/decklistmodel.cpp b/cockatrice/src/decklistmodel.cpp index c46ebe2e2..c55af0135 100644 --- a/cockatrice/src/decklistmodel.cpp +++ b/cockatrice/src/decklistmodel.cpp @@ -11,427 +11,468 @@ #include "decklistmodel.h" #include "carddatabase.h" #include "settingscache.h" +#include "deck_loader.h" DeckListModel::DeckListModel(QObject *parent) - : QAbstractItemModel(parent) + : QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder) { - deckList = new DeckList; - connect(deckList, SIGNAL(deckLoaded()), this, SLOT(rebuildTree())); - connect(deckList, SIGNAL(deckHashChanged()), this, SIGNAL(deckHashChanged())); - root = new InnerDecklistNode; + deckList = new DeckLoader; + connect(deckList, SIGNAL(deckLoaded()), this, SLOT(rebuildTree())); + connect(deckList, SIGNAL(deckHashChanged()), this, SIGNAL(deckHashChanged())); + root = new InnerDecklistNode; } DeckListModel::~DeckListModel() { - delete root; - delete deckList; + delete root; + delete deckList; } void DeckListModel::rebuildTree() { - root->clearTree(); - InnerDecklistNode *listRoot = deckList->getRoot(); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - InnerDecklistNode *node = new InnerDecklistNode(currentZone->getName(), root); - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - // XXX better sanity checking - if (!currentCard) - continue; + beginResetModel(); - CardInfo *info = db->getCard(currentCard->getName()); - QString cardType; - if (!info) - cardType = "unknown"; - else - cardType = info->getMainCardType(); - InnerDecklistNode *cardTypeNode = dynamic_cast(node->findChild(cardType)); - if (!cardTypeNode) - cardTypeNode = new InnerDecklistNode(cardType, node); + root->clearTree(); + InnerDecklistNode *listRoot = deckList->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + InnerDecklistNode *node = new InnerDecklistNode(currentZone->getName(), root); + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + // XXX better sanity checking + if (!currentCard) + continue; - new DecklistModelCardNode(currentCard, cardTypeNode); - } - } + CardInfo *info = db->getCard(currentCard->getName()); + QString cardType; + if (!info) + cardType = "unknown"; + else + cardType = info->getMainCardType(); + InnerDecklistNode *cardTypeNode = dynamic_cast(node->findChild(cardType)); + if (!cardTypeNode) + cardTypeNode = new InnerDecklistNode(cardType, node); - reset(); + new DecklistModelCardNode(currentCard, cardTypeNode); + } + } + + endResetModel(); } int DeckListModel::rowCount(const QModelIndex &parent) const { -// debugIndexInfo("rowCount", parent); - InnerDecklistNode *node = getNode(parent); - if (node) - return node->size(); - else - return 0; +// debugIndexInfo("rowCount", parent); + InnerDecklistNode *node = getNode(parent); + if (node) + return node->size(); + else + return 0; } int DeckListModel::columnCount(const QModelIndex &/*parent*/) const { - if (settingsCache->getPriceTagFeature()) - return 3; - else - return 2; + if (settingsCache->getPriceTagFeature()) + return 3; + else + return 2; } QVariant DeckListModel::data(const QModelIndex &index, int role) const { -// debugIndexInfo("data", index); - if (!index.isValid()) - return QVariant(); +// debugIndexInfo("data", index); + if (!index.isValid()) + return QVariant(); if (index.column() >= columnCount()) - return QVariant(); + return QVariant(); - AbstractDecklistNode *temp = static_cast(index.internalPointer()); - DecklistModelCardNode *card = dynamic_cast(temp); - if (!card) { - InnerDecklistNode *node = dynamic_cast(temp); - switch (role) { - case Qt::FontRole: { - QFont f; - f.setBold(true); - return f; - } - case Qt::DisplayRole: - case Qt::EditRole: - switch (index.column()) { + AbstractDecklistNode *temp = static_cast(index.internalPointer()); + DecklistModelCardNode *card = dynamic_cast(temp); + if (!card) { + InnerDecklistNode *node = dynamic_cast(temp); + switch (role) { + case Qt::FontRole: { + QFont f; + f.setBold(true); + return f; + } + case Qt::DisplayRole: + case Qt::EditRole: + switch (index.column()) { case 0: return node->recursiveCount(true); case 1: return node->getVisibleName(); case 2: return QString().sprintf("$%.2f", node->recursivePrice(true)); - default: return QVariant(); - } - case Qt::BackgroundRole: { - int color = 90 + 60 * node->depth(); - return QBrush(QColor(color, 255, color)); - } - default: return QVariant(); - } - } else { - switch (role) { - case Qt::DisplayRole: - case Qt::EditRole: { - switch (index.column()) { + default: return QVariant(); + } + case Qt::BackgroundRole: { + int color = 90 + 60 * node->depth(); + return QBrush(QColor(color, 255, color)); + } + default: return QVariant(); + } + } else { + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: { + switch (index.column()) { case 0: return card->getNumber(); case 1: return card->getName(); case 2: return QString().sprintf("$%.2f", card->getTotalPrice()); - default: return QVariant(); - } - } - case Qt::BackgroundRole: { - int color = 255 - (index.row() % 2) * 30; - return QBrush(QColor(color, color, color)); - } - default: return QVariant(); - } - } + default: return QVariant(); + } + } + case Qt::BackgroundRole: { + int color = 255 - (index.row() % 2) * 30; + return QBrush(QColor(color, color, color)); + } + default: return QVariant(); + } + } } QVariant DeckListModel::headerData(int section, Qt::Orientation orientation, int role) const { - if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) - return QVariant(); + if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) + return QVariant(); if (section >= columnCount()) - return QVariant(); - switch (section) { + return QVariant(); + switch (section) { case 0: return tr("Number"); case 1: return tr("Card"); case 2: return tr("Price"); - default: return QVariant(); - } + default: return QVariant(); + } } QModelIndex DeckListModel::index(int row, int column, const QModelIndex &parent) const { -// debugIndexInfo("index", parent); - if (!hasIndex(row, column, parent)) - return QModelIndex(); +// debugIndexInfo("index", parent); + if (!hasIndex(row, column, parent)) + return QModelIndex(); - InnerDecklistNode *parentNode = getNode(parent); - if (row >= parentNode->size()) - return QModelIndex(); + InnerDecklistNode *parentNode = getNode(parent); + if (row >= parentNode->size()) + return QModelIndex(); - return createIndex(row, column, parentNode->at(row)); + return createIndex(row, column, parentNode->at(row)); } QModelIndex DeckListModel::parent(const QModelIndex &ind) const { - if (!ind.isValid()) - return QModelIndex(); + if (!ind.isValid()) + return QModelIndex(); - return nodeToIndex(static_cast(ind.internalPointer())->getParent()); + return nodeToIndex(static_cast(ind.internalPointer())->getParent()); } Qt::ItemFlags DeckListModel::flags(const QModelIndex &index) const { - if (!index.isValid()) - return 0; + if (!index.isValid()) + return 0; - Qt::ItemFlags result = Qt::ItemIsEnabled; - if (getNode(index)) - result |= Qt::ItemIsSelectable; + Qt::ItemFlags result = Qt::ItemIsEnabled; + result |= Qt::ItemIsSelectable; - return result; + return result; } void DeckListModel::emitRecursiveUpdates(const QModelIndex &index) { - if (!index.isValid()) - return; - emit dataChanged(index, index); - emitRecursiveUpdates(index.parent()); + if (!index.isValid()) + return; + emit dataChanged(index, index); + emitRecursiveUpdates(index.parent()); } bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, int role) { - DecklistModelCardNode *node = getNode(index); - if (!node || (role != Qt::EditRole)) - return false; + DecklistModelCardNode *node = getNode(index); + if (!node || (role != Qt::EditRole)) + return false; - switch (index.column()) { + switch (index.column()) { case 0: node->setNumber(value.toInt()); break; case 1: node->setName(value.toString()); break; case 2: node->setPrice(value.toFloat()); break; - default: return false; - } - emitRecursiveUpdates(index); - deckList->updateDeckHash(); - return true; + default: return false; + } + emitRecursiveUpdates(index); + deckList->updateDeckHash(); + return true; } bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent) { - InnerDecklistNode *node = getNode(parent); - if (!node) - return false; - if (row + count > node->size()) - return false; + InnerDecklistNode *node = getNode(parent); + if (!node) + return false; + if (row + count > node->size()) + return false; - beginRemoveRows(parent, row, row + count - 1); - for (int i = 0; i < count; i++) { - AbstractDecklistNode *toDelete = node->takeAt(row); - if (DecklistModelCardNode *temp = dynamic_cast(toDelete)) - deckList->deleteNode(temp->getDataNode()); - delete toDelete; - } - endRemoveRows(); + beginRemoveRows(parent, row, row + count - 1); + for (int i = 0; i < count; i++) { + AbstractDecklistNode *toDelete = node->takeAt(row); + if (DecklistModelCardNode *temp = dynamic_cast(toDelete)) + deckList->deleteNode(temp->getDataNode()); + delete toDelete; + } + endRemoveRows(); - if (!node->size() && (node != root)) - removeRows(parent.row(), 1, parent.parent()); - else - emitRecursiveUpdates(parent); + if (!node->size() && (node != root)) + removeRows(parent.row(), 1, parent.parent()); + else + emitRecursiveUpdates(parent); - return true; + return true; } InnerDecklistNode *DeckListModel::createNodeIfNeeded(const QString &name, InnerDecklistNode *parent) { - InnerDecklistNode *newNode = dynamic_cast(parent->findChild(name)); - if (!newNode) { - beginInsertRows(nodeToIndex(parent), parent->size(), parent->size()); - newNode = new InnerDecklistNode(name, parent); - endInsertRows(); - } - return newNode; + InnerDecklistNode *newNode = dynamic_cast(parent->findChild(name)); + if (!newNode) { + beginInsertRows(nodeToIndex(parent), parent->size(), parent->size()); + newNode = new InnerDecklistNode(name, parent); + endInsertRows(); + } + return newNode; +} + +DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName, const QString &zoneName) const +{ + InnerDecklistNode *zoneNode, *typeNode; + CardInfo *info; + QString cardType; + + zoneNode = dynamic_cast(root->findChild(zoneName)); + if(!zoneNode) + return NULL; + + info = db->getCard(cardName); + if(!info) + return NULL; + + cardType = info->getMainCardType(); + typeNode = dynamic_cast(zoneNode->findChild(cardType)); + if(!typeNode) + return NULL; + + return dynamic_cast(typeNode->findChild(cardName)); +} + +QModelIndex DeckListModel::findCard(const QString &cardName, const QString &zoneName) const +{ + DecklistModelCardNode *cardNode; + + cardNode = findCardNode(cardName, zoneName); + if(!cardNode) + return QModelIndex(); + return nodeToIndex(cardNode); } QModelIndex DeckListModel::addCard(const QString &cardName, const QString &zoneName) { - InnerDecklistNode *zoneNode = createNodeIfNeeded(zoneName, root); + InnerDecklistNode *zoneNode = createNodeIfNeeded(zoneName, root); - CardInfo *info = db->getCard(cardName); - QString cardType = info->getMainCardType(); - InnerDecklistNode *cardTypeNode = createNodeIfNeeded(cardType, zoneNode); + CardInfo *info = db->getCard(cardName); + QString cardType = info->getMainCardType(); + InnerDecklistNode *cardTypeNode = createNodeIfNeeded(cardType, zoneNode); - DecklistModelCardNode *cardNode = dynamic_cast(cardTypeNode->findChild(cardName)); - if (!cardNode) { - DecklistCardNode *decklistCard = deckList->addCard(cardName, zoneName); - QModelIndex parentIndex = nodeToIndex(cardTypeNode); - beginInsertRows(parentIndex, cardTypeNode->size(), cardTypeNode->size()); - cardNode = new DecklistModelCardNode(decklistCard, cardTypeNode); - endInsertRows(); - sort(1); - emitRecursiveUpdates(parentIndex); - return nodeToIndex(cardNode); - } else { - cardNode->setNumber(cardNode->getNumber() + 1); - QModelIndex ind = nodeToIndex(cardNode); - emitRecursiveUpdates(ind); - deckList->updateDeckHash(); - return ind; - } + QModelIndex parentIndex = nodeToIndex(cardTypeNode); + DecklistModelCardNode *cardNode = dynamic_cast(cardTypeNode->findChild(cardName)); + if (!cardNode) { + DecklistCardNode *decklistCard = deckList->addCard(cardName, zoneName); + beginInsertRows(parentIndex, cardTypeNode->size(), cardTypeNode->size()); + cardNode = new DecklistModelCardNode(decklistCard, cardTypeNode); + endInsertRows(); + } else { + cardNode->setNumber(cardNode->getNumber() + 1); + deckList->updateDeckHash(); + } + sort(lastKnownColumn, lastKnownOrder); + emitRecursiveUpdates(parentIndex); + return nodeToIndex(cardNode); } QModelIndex DeckListModel::nodeToIndex(AbstractDecklistNode *node) const { - if (node == root) - return QModelIndex(); - return createIndex(node->getParent()->indexOf(node), 0, node); + if (node == root) + return QModelIndex(); + return createIndex(node->getParent()->indexOf(node), 0, node); } void DeckListModel::sortHelper(InnerDecklistNode *node, Qt::SortOrder order) { - // Sort children of node and save the information needed to - // update the list of persistent indexes. - QVector > sortResult = node->sort(order); - - QModelIndexList from, to; - for (int i = sortResult.size() - 1; i >= 0; --i) { - const int fromRow = sortResult[i].first; - const int toRow = sortResult[i].second; - AbstractDecklistNode *temp = node->at(toRow); - for (int j = columnCount(); j; --j) { - from << createIndex(fromRow, 0, temp); - to << createIndex(toRow, 0, temp); - } - } - changePersistentIndexList(from, to); - - // Recursion - for (int i = node->size() - 1; i >= 0; --i) { - InnerDecklistNode *subNode = dynamic_cast(node->at(i)); - if (subNode) - sortHelper(subNode, order); - } + // Sort children of node and save the information needed to + // update the list of persistent indexes. + QVector > sortResult = node->sort(order); + + QModelIndexList from, to; + for (int i = sortResult.size() - 1; i >= 0; --i) { + const int fromRow = sortResult[i].first; + const int toRow = sortResult[i].second; + AbstractDecklistNode *temp = node->at(toRow); + for (int j = columnCount(); j; --j) { + from << createIndex(fromRow, 0, temp); + to << createIndex(toRow, 0, temp); + } + } + changePersistentIndexList(from, to); + + // Recursion + for (int i = node->size() - 1; i >= 0; --i) { + InnerDecklistNode *subNode = dynamic_cast(node->at(i)); + if (subNode) + sortHelper(subNode, order); + } } -void DeckListModel::sort(int /*column*/, Qt::SortOrder order) +void DeckListModel::sort(int column, Qt::SortOrder order) { - emit layoutAboutToBeChanged(); - sortHelper(root, order); - emit layoutChanged(); + lastKnownColumn = column; + lastKnownOrder = order; + + emit layoutAboutToBeChanged(); + DeckSortMethod sortMethod; + switch(column) { + case 0: + sortMethod = ByNumber; + break; + case 1: + sortMethod = ByName; + break; + case 2: + sortMethod = ByPrice; + break; + default: + sortMethod = ByName; + } + root->setSortMethod(sortMethod); + sortHelper(root, order); + emit layoutChanged(); } void DeckListModel::cleanList() { - root->clearTree(); - deckList->cleanList(); - reset(); + setDeckList(new DeckLoader); } -void DeckListModel::setDeckList(DeckList *_deck) +void DeckListModel::setDeckList(DeckLoader *_deck) { - delete deckList; - deckList = _deck; - connect(deckList, SIGNAL(deckLoaded()), this, SLOT(rebuildTree())); - connect(deckList, SIGNAL(deckHashChanged()), this, SIGNAL(deckHashChanged())); - rebuildTree(); + delete deckList; + deckList = _deck; + connect(deckList, SIGNAL(deckLoaded()), this, SLOT(rebuildTree())); + connect(deckList, SIGNAL(deckHashChanged()), this, SIGNAL(deckHashChanged())); + rebuildTree(); } void DeckListModel::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node) { - const int totalColumns = settingsCache->getPriceTagFeature() ? 3 : 2; + const int totalColumns = settingsCache->getPriceTagFeature() ? 3 : 2; - if (node->height() == 1) { - QTextBlockFormat blockFormat; - QTextCharFormat charFormat; - charFormat.setFontPointSize(11); - charFormat.setFontWeight(QFont::Bold); - cursor->insertBlock(blockFormat, charFormat); - QString priceStr; - if (settingsCache->getPriceTagFeature()) - priceStr = QString().sprintf(": $%.2f", node->recursivePrice(true)); + if (node->height() == 1) { + QTextBlockFormat blockFormat; + QTextCharFormat charFormat; + charFormat.setFontPointSize(11); + charFormat.setFontWeight(QFont::Bold); + cursor->insertBlock(blockFormat, charFormat); + QString priceStr; + if (settingsCache->getPriceTagFeature()) + priceStr = QString().sprintf(": $%.2f", node->recursivePrice(true)); cursor->insertText(QString("%1: %2").arg(node->getVisibleName()).arg(node->recursiveCount(true)).append(priceStr)); - QTextTableFormat tableFormat; - tableFormat.setCellPadding(0); - tableFormat.setCellSpacing(0); - tableFormat.setBorder(0); + QTextTableFormat tableFormat; + tableFormat.setCellPadding(0); + tableFormat.setCellSpacing(0); + tableFormat.setBorder(0); QTextTable *table = cursor->insertTable(node->size() + 1, totalColumns, tableFormat); - for (int i = 0; i < node->size(); i++) { - AbstractDecklistCardNode *card = dynamic_cast(node->at(i)); + for (int i = 0; i < node->size(); i++) { + AbstractDecklistCardNode *card = dynamic_cast(node->at(i)); - QTextCharFormat cellCharFormat; - cellCharFormat.setFontPointSize(9); + QTextCharFormat cellCharFormat; + cellCharFormat.setFontPointSize(9); - QTextTableCell cell = table->cellAt(i, 0); - cell.setFormat(cellCharFormat); - QTextCursor cellCursor = cell.firstCursorPosition(); - cellCursor.insertText(QString("%1 ").arg(card->getNumber())); + QTextTableCell cell = table->cellAt(i, 0); + cell.setFormat(cellCharFormat); + QTextCursor cellCursor = cell.firstCursorPosition(); + cellCursor.insertText(QString("%1 ").arg(card->getNumber())); - cell = table->cellAt(i, 1); - cell.setFormat(cellCharFormat); - cellCursor = cell.firstCursorPosition(); - cellCursor.insertText(card->getName()); + cell = table->cellAt(i, 1); + cell.setFormat(cellCharFormat); + cellCursor = cell.firstCursorPosition(); + cellCursor.insertText(card->getName()); - if (settingsCache->getPriceTagFeature()) { - cell = table->cellAt(i, 2); - cell.setFormat(cellCharFormat); - cellCursor = cell.firstCursorPosition(); - cellCursor.insertText(QString().sprintf("$%.2f ", card->getTotalPrice())); - } - } - } else if (node->height() == 2) { - QTextBlockFormat blockFormat; - QTextCharFormat charFormat; - charFormat.setFontPointSize(14); - charFormat.setFontWeight(QFont::Bold); + if (settingsCache->getPriceTagFeature()) { + cell = table->cellAt(i, 2); + cell.setFormat(cellCharFormat); + cellCursor = cell.firstCursorPosition(); + cellCursor.insertText(QString().sprintf("$%.2f ", card->getTotalPrice())); + } + } + } else if (node->height() == 2) { + QTextBlockFormat blockFormat; + QTextCharFormat charFormat; + charFormat.setFontPointSize(14); + charFormat.setFontWeight(QFont::Bold); - cursor->insertBlock(blockFormat, charFormat); - QString priceStr; - if (settingsCache->getPriceTagFeature()) - priceStr = QString().sprintf(": $%.2f", node->recursivePrice(true)); + cursor->insertBlock(blockFormat, charFormat); + QString priceStr; + if (settingsCache->getPriceTagFeature()) + priceStr = QString().sprintf(": $%.2f", node->recursivePrice(true)); cursor->insertText(QString("%1: %2").arg(node->getVisibleName()).arg(node->recursiveCount(true)).append(priceStr)); - QTextTableFormat tableFormat; - tableFormat.setCellPadding(10); - tableFormat.setCellSpacing(0); - tableFormat.setBorder(0); - QVector constraints; - for (int i = 0; i < totalColumns; i++) - constraints << QTextLength(QTextLength::PercentageLength, 100.0 / totalColumns); - tableFormat.setColumnWidthConstraints(constraints); + QTextTableFormat tableFormat; + tableFormat.setCellPadding(10); + tableFormat.setCellSpacing(0); + tableFormat.setBorder(0); + QVector constraints; + for (int i = 0; i < totalColumns; i++) + constraints << QTextLength(QTextLength::PercentageLength, 100.0 / totalColumns); + tableFormat.setColumnWidthConstraints(constraints); - QTextTable *table = cursor->insertTable(1, totalColumns, tableFormat); - for (int i = 0; i < node->size(); i++) { - QTextCursor cellCursor = table->cellAt(0, (i * totalColumns) / node->size()).lastCursorPosition(); - printDeckListNode(&cellCursor, dynamic_cast(node->at(i))); - } - } - cursor->movePosition(QTextCursor::End); + QTextTable *table = cursor->insertTable(1, totalColumns, tableFormat); + for (int i = 0; i < node->size(); i++) { + QTextCursor cellCursor = table->cellAt(0, (i * totalColumns) / node->size()).lastCursorPosition(); + printDeckListNode(&cellCursor, dynamic_cast(node->at(i))); + } + } + cursor->movePosition(QTextCursor::End); } void DeckListModel::printDeckList(QPrinter *printer) { - QTextDocument doc; + QTextDocument doc; - QFont font("Serif"); - font.setStyleHint(QFont::Serif); - doc.setDefaultFont(font); + QFont font("Serif"); + font.setStyleHint(QFont::Serif); + doc.setDefaultFont(font); - QTextCursor cursor(&doc); + QTextCursor cursor(&doc); - QTextBlockFormat headerBlockFormat; - QTextCharFormat headerCharFormat; - headerCharFormat.setFontPointSize(16); - headerCharFormat.setFontWeight(QFont::Bold); + QTextBlockFormat headerBlockFormat; + QTextCharFormat headerCharFormat; + headerCharFormat.setFontPointSize(16); + headerCharFormat.setFontWeight(QFont::Bold); - cursor.insertBlock(headerBlockFormat, headerCharFormat); - cursor.insertText(deckList->getName()); + cursor.insertBlock(headerBlockFormat, headerCharFormat); + cursor.insertText(deckList->getName()); - headerCharFormat.setFontPointSize(12); - cursor.insertBlock(headerBlockFormat, headerCharFormat); - cursor.insertText(deckList->getComments()); - cursor.insertBlock(headerBlockFormat, headerCharFormat); + headerCharFormat.setFontPointSize(12); + cursor.insertBlock(headerBlockFormat, headerCharFormat); + cursor.insertText(deckList->getComments()); + cursor.insertBlock(headerBlockFormat, headerCharFormat); - for (int i = 0; i < root->size(); i++) { - cursor.insertHtml("
"); - //cursor.insertHtml("
"); - cursor.insertBlock(headerBlockFormat, headerCharFormat); + for (int i = 0; i < root->size(); i++) { + cursor.insertHtml("
"); + //cursor.insertHtml("
"); + cursor.insertBlock(headerBlockFormat, headerCharFormat); - printDeckListNode(&cursor, dynamic_cast(root->at(i))); - } + printDeckListNode(&cursor, dynamic_cast(root->at(i))); + } - doc.print(printer); + doc.print(printer); } -void DeckListModel::pricesUpdated(InnerDecklistNode *node) +void DeckListModel::pricesUpdated() { - if (!node) - node = root; - - if (node->isEmpty()) - return; - - emit dataChanged(createIndex(0, 2, node->at(0)), createIndex(node->size() - 1, 2, node->last())); + emit layoutChanged(); } diff --git a/cockatrice/src/decklistmodel.h b/cockatrice/src/decklistmodel.h index bab462330..f4e93e3bb 100644 --- a/cockatrice/src/decklistmodel.h +++ b/cockatrice/src/decklistmodel.h @@ -5,6 +5,7 @@ #include #include "decklist.h" +class DeckLoader; class CardDatabase; class QProgressDialog; class QPrinter; @@ -12,60 +13,64 @@ class QTextCursor; class DecklistModelCardNode : public AbstractDecklistCardNode { private: - DecklistCardNode *dataNode; + DecklistCardNode *dataNode; public: - DecklistModelCardNode(DecklistCardNode *_dataNode, InnerDecklistNode *_parent) : AbstractDecklistCardNode(_parent), dataNode(_dataNode) { } - int getNumber() const { return dataNode->getNumber(); } - void setNumber(int _number) { dataNode->setNumber(_number); } + DecklistModelCardNode(DecklistCardNode *_dataNode, InnerDecklistNode *_parent) : AbstractDecklistCardNode(_parent), dataNode(_dataNode) { } + int getNumber() const { return dataNode->getNumber(); } + void setNumber(int _number) { dataNode->setNumber(_number); } float getPrice() const { return dataNode->getPrice(); } - void setPrice(float _price) { dataNode->setPrice(_price); } - QString getName() const { return dataNode->getName(); } - void setName(const QString &_name) { dataNode->setName(_name); } - DecklistCardNode *getDataNode() const { return dataNode; } + void setPrice(const float _price) { dataNode->setPrice(_price); } + QString getName() const { return dataNode->getName(); } + void setName(const QString &_name) { dataNode->setName(_name); } + DecklistCardNode *getDataNode() const { return dataNode; } }; class DeckListModel : public QAbstractItemModel { - Q_OBJECT + Q_OBJECT private slots: - void rebuildTree(); + void rebuildTree(); public slots: - void printDeckList(QPrinter *printer); + void printDeckList(QPrinter *printer); signals: - void deckHashChanged(); + void deckHashChanged(); public: - DeckListModel(QObject *parent = 0); - ~DeckListModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; + DeckListModel(QObject *parent = 0); + ~DeckListModel(); + int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex &index) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - bool setData(const QModelIndex &index, const QVariant &value, int role); - bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); - QModelIndex addCard(const QString &cardName, const QString &zoneName); - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); - void cleanList(); - DeckList *getDeckList() const { return deckList; } - void setDeckList(DeckList *_deck); - void pricesUpdated(InnerDecklistNode *node = 0); + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + QModelIndex findCard(const QString &cardName, const QString &zoneName) const; + QModelIndex addCard(const QString &cardName, const QString &zoneName); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + void cleanList(); + DeckLoader *getDeckList() const { return deckList; } + void setDeckList(DeckLoader *_deck); + void pricesUpdated(); private: - DeckList *deckList; - InnerDecklistNode *root; - InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent); - QModelIndex nodeToIndex(AbstractDecklistNode *node) const; - void emitRecursiveUpdates(const QModelIndex &index); - void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); + DeckLoader *deckList; + InnerDecklistNode *root; + int lastKnownColumn; + Qt::SortOrder lastKnownOrder; + InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent); + QModelIndex nodeToIndex(AbstractDecklistNode *node) const; + DecklistModelCardNode *findCardNode(const QString &cardName, const QString &zoneName) const; + void emitRecursiveUpdates(const QModelIndex &index); + void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); - void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node); + void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node); - template T getNode(const QModelIndex &index) const - { - if (!index.isValid()) - return dynamic_cast(root); - return dynamic_cast(static_cast(index.internalPointer())); - } + template T getNode(const QModelIndex &index) const + { + if (!index.isValid()) + return dynamic_cast(root); + return dynamic_cast(static_cast(index.internalPointer())); + } }; #endif diff --git a/cockatrice/src/deckstats_interface.cpp b/cockatrice/src/deckstats_interface.cpp new file mode 100644 index 000000000..6bdae12f6 --- /dev/null +++ b/cockatrice/src/deckstats_interface.cpp @@ -0,0 +1,106 @@ +#include "deckstats_interface.h" +#include "decklist.h" +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050000 +#include +#endif + +DeckStatsInterface::DeckStatsInterface( + CardDatabase &_cardDatabase, + QObject *parent +) : QObject(parent), cardDatabase(_cardDatabase) +{ + manager = new QNetworkAccessManager(this); + connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(queryFinished(QNetworkReply *))); +} + +void DeckStatsInterface::queryFinished(QNetworkReply *reply) +{ + if (reply->error() != QNetworkReply::NoError) { + QMessageBox::critical(0, tr("Error"), reply->errorString()); + reply->deleteLater(); + deleteLater(); + return; + } + + QString data(reply->readAll()); + reply->deleteLater(); + + QRegExp rx("id=\"deckstats_deck_url\" value=\"([^\"]+)\""); + if (!rx.indexIn(data)) { + QMessageBox::critical(0, tr("Error"), tr("The reply from the server could not be parsed.")); + deleteLater(); + return; + } + + QString deckUrl = rx.cap(1); + QDesktopServices::openUrl(deckUrl); + + deleteLater(); +} + +void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data) +{ + DeckList deckWithoutTokens; + copyDeckWithoutTokens(*deck, deckWithoutTokens); + +#if QT_VERSION < 0x050000 + QUrl params; + params.addQueryItem("deck", deckWithoutTokens.writeToString_Plain()); + data->append(params.encodedQuery()); +#else + QUrl params; + QUrlQuery urlQuery; + urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain()); + params.setQuery(urlQuery); + data->append(params.query(QUrl::EncodeReserved)); +#endif +} + +void DeckStatsInterface::analyzeDeck(DeckList *deck) +{ + QByteArray data; + getAnalyzeRequestData(deck, &data); + + QNetworkRequest request(QUrl("http://deckstats.net/index.php")); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + manager->post(request, data); +} + +struct CopyIfNotAToken { + CardDatabase &cardDatabase; + DeckList &destination; + + CopyIfNotAToken( + CardDatabase &_cardDatabase, + DeckList &_destination + ) : cardDatabase(_cardDatabase), destination(_destination) {}; + + void operator()( + const InnerDecklistNode *node, + const DecklistCardNode *card + ) const { + if (!cardDatabase.getCard(card->getName())->getIsToken()) { + DecklistCardNode *addedCard = destination.addCard( + card->getName(), + node->getName() + ); + addedCard->setNumber(card->getNumber()); + } + } +}; + +void DeckStatsInterface::copyDeckWithoutTokens( + const DeckList &source, + DeckList &destination +) { + CopyIfNotAToken copyIfNotAToken(cardDatabase, destination); + source.forEachCard(copyIfNotAToken); +} diff --git a/cockatrice/src/deckstats_interface.h b/cockatrice/src/deckstats_interface.h new file mode 100644 index 000000000..c3a59c9b7 --- /dev/null +++ b/cockatrice/src/deckstats_interface.h @@ -0,0 +1,35 @@ +#ifndef DECKSTATS_INTERFACE_H +#define DECKSTATS_INTERFACE_H + +#include "carddatabase.h" +#include "decklist.h" +#include + +class QByteArray; +class QNetworkAccessManager; +class QNetworkReply; +class DeckList; + +class DeckStatsInterface : public QObject { + Q_OBJECT +private: + QNetworkAccessManager *manager; + + CardDatabase &cardDatabase; + + /** + * Deckstats doesn't recognize token cards, and instead tries to find the + * closest non-token card instead. So we construct a new deck which has no + * tokens. + */ + void copyDeckWithoutTokens(const DeckList &source, DeckList& destination); + +private slots: + void queryFinished(QNetworkReply *reply); + void getAnalyzeRequestData(DeckList *deck, QByteArray *data); +public: + DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = 0); + void analyzeDeck(DeckList *deck); +}; + +#endif diff --git a/cockatrice/src/deckview.cpp b/cockatrice/src/deckview.cpp index 83f93893c..5a14ec3f0 100644 --- a/cockatrice/src/deckview.cpp +++ b/cockatrice/src/deckview.cpp @@ -8,454 +8,483 @@ #include "main.h" DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag) - : AbstractCardDragItem(_item, _hotSpot, parentDrag), currentZone(0) + : AbstractCardDragItem(_item, _hotSpot, parentDrag), currentZone(0) { } void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos) { - QList colliding = scene()->items(cursorScenePos); + QList colliding = scene()->items(cursorScenePos); - DeckViewCardContainer *cursorZone = 0; - for (int i = colliding.size() - 1; i >= 0; i--) - if ((cursorZone = qgraphicsitem_cast(colliding.at(i)))) - break; - if (!cursorZone) - return; - currentZone = cursorZone; - - QPointF newPos = cursorScenePos; - if (newPos != pos()) { - for (int i = 0; i < childDrags.size(); i++) - childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); - setPos(newPos); - } + DeckViewCardContainer *cursorZone = 0; + for (int i = colliding.size() - 1; i >= 0; i--) + if ((cursorZone = qgraphicsitem_cast(colliding.at(i)))) + break; + if (!cursorZone) + return; + currentZone = cursorZone; + + QPointF newPos = cursorScenePos; + if (newPos != pos()) { + for (int i = 0; i < childDrags.size(); i++) + childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); + setPos(newPos); + } } void DeckViewCardDragItem::handleDrop(DeckViewCardContainer *target) { - DeckViewCard *card = static_cast(item); - DeckViewCardContainer *start = static_cast(item->parentItem()); - start->removeCard(card); - target->addCard(card); - card->setParentItem(target); + DeckViewCard *card = static_cast(item); + DeckViewCardContainer *start = static_cast(item->parentItem()); + start->removeCard(card); + target->addCard(card); + card->setParentItem(target); } void DeckViewCardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - setCursor(Qt::OpenHandCursor); - DeckViewScene *sc = static_cast(scene()); - sc->removeItem(this); + setCursor(Qt::OpenHandCursor); + DeckViewScene *sc = static_cast(scene()); + sc->removeItem(this); - if (currentZone) { - handleDrop(currentZone); - for (int i = 0; i < childDrags.size(); i++) { - DeckViewCardDragItem *c = static_cast(childDrags[i]); - c->handleDrop(currentZone); - sc->removeItem(c); - } - - sc->updateContents(); - } - - event->accept(); + if (currentZone) { + handleDrop(currentZone); + for (int i = 0; i < childDrags.size(); i++) { + DeckViewCardDragItem *c = static_cast(childDrags[i]); + c->handleDrop(currentZone); + sc->removeItem(c); + } + + sc->updateContents(); + } + + event->accept(); } DeckViewCard::DeckViewCard(const QString &_name, const QString &_originZone, QGraphicsItem *parent) - : AbstractCardItem(_name, 0, parent), originZone(_originZone), dragItem(0) + : AbstractCardItem(_name, 0, -1, parent), originZone(_originZone), dragItem(0) { - setAcceptsHoverEvents(true); +#if QT_VERSION < 0x050000 + setAcceptsHoverEvents(true); +#else + setAcceptHoverEvents(true); +#endif } DeckViewCard::~DeckViewCard() { - delete dragItem; + delete dragItem; } void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - AbstractCardItem::paint(painter, option, widget); - - painter->save(); - QPen pen;//(Qt::DotLine); - pen.setWidth(3); - if (originZone == "main") - pen.setColor(QColor(0, 255, 0)); - else - pen.setColor(QColor(255, 0, 0)); - painter->setPen(pen); - painter->drawRect(QRectF(1.5, 1.5, CARD_WIDTH - 3, CARD_HEIGHT - 3)); - painter->restore(); + AbstractCardItem::paint(painter, option, widget); + + painter->save(); + QPen pen;//(Qt::DotLine); + pen.setWidth(3); + if (originZone == "main") + pen.setColor(QColor(0, 255, 0)); + else + pen.setColor(QColor(255, 0, 0)); + painter->setPen(pen); + painter->drawRect(QRectF(1.5, 1.5, CARD_WIDTH - 3, CARD_HEIGHT - 3)); + painter->restore(); } void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < 2 * QApplication::startDragDistance()) - return; - - if (static_cast(scene())->getLocked()) - return; - - delete dragItem; - dragItem = new DeckViewCardDragItem(this, event->pos()); - scene()->addItem(dragItem); - dragItem->updatePosition(event->scenePos()); - dragItem->grabMouse(); - - QList sel = scene()->selectedItems(); - int j = 0; - for (int i = 0; i < sel.size(); i++) { - DeckViewCard *c = static_cast(sel.at(i)); - if (c == this) - continue; - ++j; - QPointF childPos = QPointF(j * CARD_WIDTH / 2, 0); - DeckViewCardDragItem *drag = new DeckViewCardDragItem(c, childPos, dragItem); - drag->setPos(dragItem->pos() + childPos); - scene()->addItem(drag); - } - setCursor(Qt::OpenHandCursor); + if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < 2 * QApplication::startDragDistance()) + return; + + if (static_cast(scene())->getLocked()) + return; + + delete dragItem; + dragItem = new DeckViewCardDragItem(this, event->pos()); + scene()->addItem(dragItem); + dragItem->updatePosition(event->scenePos()); + dragItem->grabMouse(); + + QList sel = scene()->selectedItems(); + int j = 0; + for (int i = 0; i < sel.size(); i++) { + DeckViewCard *c = static_cast(sel.at(i)); + if (c == this) + continue; + ++j; + QPointF childPos = QPointF(j * CARD_WIDTH / 2, 0); + DeckViewCardDragItem *drag = new DeckViewCardDragItem(c, childPos, dragItem); + drag->setPos(dragItem->pos() + childPos); + scene()->addItem(drag); + } + setCursor(Qt::OpenHandCursor); } void DeckViewCard::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { - event->accept(); - processHoverEvent(); + event->accept(); + processHoverEvent(); } DeckViewCardContainer::DeckViewCardContainer(const QString &_name) - : QGraphicsItem(), name(_name), width(0), height(0) + : QGraphicsItem(), name(_name), width(0), height(0) { - QString bgPath = settingsCache->getTableBgPath(); - if (!bgPath.isEmpty()) - bgPixmap.load(bgPath); + QString bgPath = settingsCache->getTableBgPath(); + if (!bgPath.isEmpty()) + bgPixmap.load(bgPath); - setCacheMode(DeviceCoordinateCache); + setCacheMode(DeviceCoordinateCache); } QRectF DeckViewCardContainer::boundingRect() const { - return QRectF(0, 0, width, height); + return QRectF(0, 0, width, height); } void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - qreal totalTextWidth = getCardTypeTextWidth(); - - if (bgPixmap.isNull()) - painter->fillRect(boundingRect(), QColor(0, 0, 100)); - else - painter->fillRect(boundingRect(), QBrush(bgPixmap)); - painter->setPen(QColor(255, 255, 255, 100)); - painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); - - painter->setPen(QColor(Qt::white)); - QFont f("Serif"); - f.setStyleHint(QFont::Serif); - f.setPixelSize(24); - f.setWeight(QFont::Bold); - painter->setFont(f); - painter->drawText(10, 0, width - 20, separatorY, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, InnerDecklistNode::visibleNameFromName(name) + QString(": %1").arg(cards.size())); - - f.setPixelSize(16); - painter->setFont(f); - QList cardTypeList = cardsByType.uniqueKeys(); - qreal yUntilNow = separatorY + paddingY; - for (int i = 0; i < cardTypeList.size(); ++i) { - if (i != 0) { - painter->setPen(QColor(255, 255, 255, 100)); - painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2)); - } - - qreal thisRowHeight = CARD_HEIGHT * currentRowsAndCols[i].first; - QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight); - yUntilNow += thisRowHeight + paddingY; - - painter->setPen(Qt::white); - painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine, cardTypeList[i]); - } + qreal totalTextWidth = getCardTypeTextWidth(); + + if (bgPixmap.isNull()) + painter->fillRect(boundingRect(), QColor(0, 0, 100)); + else + painter->fillRect(boundingRect(), QBrush(bgPixmap)); + painter->setPen(QColor(255, 255, 255, 100)); + painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); + + painter->setPen(QColor(Qt::white)); + QFont f("Serif"); + f.setStyleHint(QFont::Serif); + f.setPixelSize(24); + f.setWeight(QFont::Bold); + painter->setFont(f); + painter->drawText(10, 0, width - 20, separatorY, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, InnerDecklistNode::visibleNameFromName(name) + QString(": %1").arg(cards.size())); + + f.setPixelSize(16); + painter->setFont(f); + QList cardTypeList = cardsByType.uniqueKeys(); + qreal yUntilNow = separatorY + paddingY; + for (int i = 0; i < cardTypeList.size(); ++i) { + if (i != 0) { + painter->setPen(QColor(255, 255, 255, 100)); + painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2)); + } + qreal thisRowHeight = CARD_HEIGHT * currentRowsAndCols[i].first; + QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight); + yUntilNow += thisRowHeight + paddingY; + + painter->setPen(Qt::white); + painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine, cardTypeList[i]); + } } void DeckViewCardContainer::addCard(DeckViewCard *card) { - cards.append(card); - cardsByType.insert(card->getInfo()->getMainCardType(), card); + cards.append(card); + cardsByType.insert(card->getInfo()->getMainCardType(), card); } void DeckViewCardContainer::removeCard(DeckViewCard *card) { - cards.removeAt(cards.indexOf(card)); - cardsByType.remove(card->getInfo()->getMainCardType(), card); + cards.removeAt(cards.indexOf(card)); + cardsByType.remove(card->getInfo()->getMainCardType(), card); } QList > DeckViewCardContainer::getRowsAndCols() const { - QList > result; - QList cardTypeList = cardsByType.uniqueKeys(); - for (int i = 0; i < cardTypeList.size(); ++i) - result.append(QPair(1, cardsByType.count(cardTypeList[i]))); - return result; + QList > result; + QList cardTypeList = cardsByType.uniqueKeys(); + for (int i = 0; i < cardTypeList.size(); ++i) + result.append(QPair(1, cardsByType.count(cardTypeList[i]))); + return result; } int DeckViewCardContainer::getCardTypeTextWidth() const { - QFont f("Serif"); - f.setStyleHint(QFont::Serif); - f.setPixelSize(16); - f.setWeight(QFont::Bold); - QFontMetrics fm(f); - - int maxCardTypeWidth = 0; - QMapIterator i(cardsByType); - while (i.hasNext()) { - int cardTypeWidth = fm.size(Qt::TextSingleLine, i.next().key()).width(); - if (cardTypeWidth > maxCardTypeWidth) - maxCardTypeWidth = cardTypeWidth; - } - - return maxCardTypeWidth + 15; + QFont f("Serif"); + f.setStyleHint(QFont::Serif); + f.setPixelSize(16); + f.setWeight(QFont::Bold); + QFontMetrics fm(f); + + int maxCardTypeWidth = 0; + QMapIterator i(cardsByType); + while (i.hasNext()) { + int cardTypeWidth = fm.size(Qt::TextSingleLine, i.next().key()).width(); + if (cardTypeWidth > maxCardTypeWidth) + maxCardTypeWidth = cardTypeWidth; + } + + return maxCardTypeWidth + 15; } QSizeF DeckViewCardContainer::calculateBoundingRect(const QList > &rowsAndCols) const { - qreal totalHeight = 0; - qreal totalWidth = 0; - - // Calculate space needed for cards - for (int i = 0; i < rowsAndCols.size(); ++i) { - totalHeight += CARD_HEIGHT * rowsAndCols[i].first + paddingY; - if (CARD_WIDTH * rowsAndCols[i].second > totalWidth) - totalWidth = CARD_WIDTH * rowsAndCols[i].second; - } - - return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY); + qreal totalHeight = 0; + qreal totalWidth = 0; + + // Calculate space needed for cards + for (int i = 0; i < rowsAndCols.size(); ++i) { + totalHeight += CARD_HEIGHT * rowsAndCols[i].first + paddingY; + if (CARD_WIDTH * rowsAndCols[i].second > totalWidth) + totalWidth = CARD_WIDTH * rowsAndCols[i].second; + } + + return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY); } void DeckViewCardContainer::rearrangeItems(const QList > &rowsAndCols) { - currentRowsAndCols = rowsAndCols; - - int totalCols = 0, totalRows = 0; - qreal yUntilNow = separatorY + paddingY; - qreal x = (qreal) getCardTypeTextWidth(); - for (int i = 0; i < rowsAndCols.size(); ++i) { - const int tempRows = rowsAndCols[i].first; - const int tempCols = rowsAndCols[i].second; - totalRows += tempRows; - if (tempCols > totalCols) - totalCols = tempCols; - - QList cardTypeList = cardsByType.uniqueKeys(); - QList row = cardsByType.values(cardTypeList[i]); - for (int j = 0; j < row.size(); ++j) { - DeckViewCard *card = row[j]; - card->setPos(x + (j % tempCols) * CARD_WIDTH, yUntilNow + (j / tempCols) * CARD_HEIGHT); - } - yUntilNow += tempRows * CARD_HEIGHT + paddingY; - } - - prepareGeometryChange(); - QSizeF bRect = calculateBoundingRect(rowsAndCols); - width = bRect.width(); - height = bRect.height(); + currentRowsAndCols = rowsAndCols; + + int totalCols = 0, totalRows = 0; + qreal yUntilNow = separatorY + paddingY; + qreal x = (qreal) getCardTypeTextWidth(); + for (int i = 0; i < rowsAndCols.size(); ++i) { + const int tempRows = rowsAndCols[i].first; + const int tempCols = rowsAndCols[i].second; + totalRows += tempRows; + if (tempCols > totalCols) + totalCols = tempCols; + + QList cardTypeList = cardsByType.uniqueKeys(); + QList row = cardsByType.values(cardTypeList[i]); + for (int j = 0; j < row.size(); ++j) { + DeckViewCard *card = row[j]; + card->setPos(x + (j % tempCols) * CARD_WIDTH, yUntilNow + (j / tempCols) * CARD_HEIGHT); + } + yUntilNow += tempRows * CARD_HEIGHT + paddingY; + } + + prepareGeometryChange(); + QSizeF bRect = calculateBoundingRect(rowsAndCols); + width = bRect.width(); + height = bRect.height(); } void DeckViewCardContainer::setWidth(qreal _width) { - prepareGeometryChange(); - width = _width; - update(); + prepareGeometryChange(); + width = _width; + update(); } DeckViewScene::DeckViewScene(QObject *parent) - : QGraphicsScene(parent), locked(false), deck(0), optimalAspectRatio(1.0) + : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0) { } DeckViewScene::~DeckViewScene() { + clearContents(); + delete deck; } -void DeckViewScene::setDeck(DeckList *_deck) +void DeckViewScene::clearContents() { - if (deck) - delete deck; - QMapIterator i(cardContainers); - while (i.hasNext()) - delete i.next().value(); - cardContainers.clear(); - - deck = _deck; - rebuildTree(); - applySideboardPlan(deck->getCurrentSideboardPlan()); - rearrangeItems(); + QMapIterator i(cardContainers); + while (i.hasNext()) + delete i.next().value(); + cardContainers.clear(); +} + +void DeckViewScene::setDeck(const DeckList &_deck) +{ + if (deck) + delete deck; + + deck = new DeckList(_deck); + rebuildTree(); + applySideboardPlan(deck->getCurrentSideboardPlan()); + rearrangeItems(); } void DeckViewScene::rebuildTree() { - InnerDecklistNode *listRoot = deck->getRoot(); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - - DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0); - if (!container) { - container = new DeckViewCardContainer(currentZone->getName()); - cardContainers.insert(currentZone->getName(), container); - addItem(container); - } - - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; + clearContents(); + + if (!deck) + return; + + InnerDecklistNode *listRoot = deck->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + + DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0); + if (!container) { + container = new DeckViewCardContainer(currentZone->getName()); + cardContainers.insert(currentZone->getName(), container); + addItem(container); + } + + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; - for (int k = 0; k < currentCard->getNumber(); ++k) { - DeckViewCard *newCard = new DeckViewCard(currentCard->getName(), currentZone->getName(), container); - container->addCard(newCard); - emit newCardAdded(newCard); - } - } - } + for (int k = 0; k < currentCard->getNumber(); ++k) { + DeckViewCard *newCard = new DeckViewCard(currentCard->getName(), currentZone->getName(), container); + container->addCard(newCard); + emit newCardAdded(newCard); + } + } + } } -void DeckViewScene::applySideboardPlan(const QList &plan) +void DeckViewScene::applySideboardPlan(const QList &plan) { - for (int i = 0; i < plan.size(); ++i) { - MoveCardToZone *m = plan[i]; - - DeckViewCardContainer *start = cardContainers.value(m->getStartZone()); - DeckViewCardContainer *target = cardContainers.value(m->getTargetZone()); - if (!start || !target) - continue; - - DeckViewCard *card = 0; - const QList &cardList = start->getCards(); - for (int j = 0; j < cardList.size(); ++j) - if (cardList[j]->getName() == m->getCardName()) { - card = cardList[j]; - break; - } - if (!card) - continue; - - start->removeCard(card); - target->addCard(card); - card->setParentItem(target); - } + for (int i = 0; i < plan.size(); ++i) { + const MoveCard_ToZone &m = plan[i]; + DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone())); + DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone())); + if (!start || !target) + continue; + + DeckViewCard *card = 0; + const QList &cardList = start->getCards(); + for (int j = 0; j < cardList.size(); ++j) + if (cardList[j]->getName() == QString::fromStdString(m.card_name())) { + card = cardList[j]; + break; + } + if (!card) + continue; + + start->removeCard(card); + target->addCard(card); + card->setParentItem(target); + } } void DeckViewScene::rearrangeItems() { - const int spacing = CARD_HEIGHT / 3; - QList contList = cardContainers.values(); - - // Initialize space requirements - QList > > rowsAndColsList; - QList > cardCountList; - for (int i = 0; i < contList.size(); ++i) { - QList > rowsAndCols = contList[i]->getRowsAndCols(); - rowsAndColsList.append(rowsAndCols); - - cardCountList.append(QList()); - for (int j = 0; j < rowsAndCols.size(); ++j) - cardCountList[i].append(rowsAndCols[j].second); - } - - qreal totalHeight, totalWidth; - for (;;) { - // Calculate total size before this iteration - totalHeight = -spacing; - totalWidth = 0; - for (int i = 0; i < contList.size(); ++i) { - QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]); - totalHeight += contSize.height() + spacing; - if (contSize.width() > totalWidth) - totalWidth = contSize.width(); - } - - // We're done when the aspect ratio shifts from too high to too low. - if (totalWidth / totalHeight <= optimalAspectRatio) - break; - - // Find category with highest column count - int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0; - for (int i = 0; i < rowsAndColsList.size(); ++i) - for (int j = 0; j < rowsAndColsList[i].size(); ++j) - if (rowsAndColsList[i][j].second > maxCols) { - maxIndex1 = i; - maxIndex2 = j; - maxCols = rowsAndColsList[i][j].second; - } - - // Add row to category - const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first; - const int maxCardCount = cardCountList[maxIndex1][maxIndex2]; - rowsAndColsList[maxIndex1][maxIndex2] = QPair(maxRows + 1, (int) ceil((qreal) maxCardCount / (qreal) (maxRows + 1))); - } - - totalHeight = -spacing; - for (int i = 0; i < contList.size(); ++i) { - DeckViewCardContainer *c = contList[i]; - c->rearrangeItems(rowsAndColsList[i]); - c->setPos(0, totalHeight + spacing); - totalHeight += c->boundingRect().height() + spacing; - } - - totalWidth = totalHeight * optimalAspectRatio; - for (int i = 0; i < contList.size(); ++i) - contList[i]->setWidth(totalWidth); - - setSceneRect(QRectF(0, 0, totalWidth, totalHeight)); + const int spacing = CARD_HEIGHT / 3; + QList contList = cardContainers.values(); + + // Initialize space requirements + QList > > rowsAndColsList; + QList > cardCountList; + for (int i = 0; i < contList.size(); ++i) { + QList > rowsAndCols = contList[i]->getRowsAndCols(); + rowsAndColsList.append(rowsAndCols); + + cardCountList.append(QList()); + for (int j = 0; j < rowsAndCols.size(); ++j) + cardCountList[i].append(rowsAndCols[j].second); + } + + qreal totalHeight, totalWidth; + for (;;) { + // Calculate total size before this iteration + totalHeight = -spacing; + totalWidth = 0; + for (int i = 0; i < contList.size(); ++i) { + QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]); + totalHeight += contSize.height() + spacing; + if (contSize.width() > totalWidth) + totalWidth = contSize.width(); + } + + // We're done when the aspect ratio shifts from too high to too low. + if (totalWidth / totalHeight <= optimalAspectRatio) + break; + + // Find category with highest column count + int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0; + for (int i = 0; i < rowsAndColsList.size(); ++i) + for (int j = 0; j < rowsAndColsList[i].size(); ++j) + if (rowsAndColsList[i][j].second > maxCols) { + maxIndex1 = i; + maxIndex2 = j; + maxCols = rowsAndColsList[i][j].second; + } + + // Add row to category + const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first; + const int maxCardCount = cardCountList[maxIndex1][maxIndex2]; + rowsAndColsList[maxIndex1][maxIndex2] = QPair(maxRows + 1, (int) ceil((qreal) maxCardCount / (qreal) (maxRows + 1))); + } + + totalHeight = -spacing; + for (int i = 0; i < contList.size(); ++i) { + DeckViewCardContainer *c = contList[i]; + c->rearrangeItems(rowsAndColsList[i]); + c->setPos(0, totalHeight + spacing); + totalHeight += c->boundingRect().height() + spacing; + } + + totalWidth = totalHeight * optimalAspectRatio; + for (int i = 0; i < contList.size(); ++i) + contList[i]->setWidth(totalWidth); + + setSceneRect(QRectF(0, 0, totalWidth, totalHeight)); } void DeckViewScene::updateContents() { - rearrangeItems(); - emit sideboardPlanChanged(); + rearrangeItems(); + emit sideboardPlanChanged(); } -QList DeckViewScene::getSideboardPlan() const +QList DeckViewScene::getSideboardPlan() const { - QList result; - QMapIterator containerIterator(cardContainers); - while (containerIterator.hasNext()) { - DeckViewCardContainer *cont = containerIterator.next().value(); - const QList cardList = cont->getCards(); - for (int i = 0; i < cardList.size(); ++i) - if (cardList[i]->getOriginZone() != cont->getName()) - result.append(new MoveCardToZone(cardList[i]->getName(), cardList[i]->getOriginZone(), cont->getName())); - } - return result; + QList result; + QMapIterator containerIterator(cardContainers); + while (containerIterator.hasNext()) { + DeckViewCardContainer *cont = containerIterator.next().value(); + const QList cardList = cont->getCards(); + for (int i = 0; i < cardList.size(); ++i) + if (cardList[i]->getOriginZone() != cont->getName()) { + MoveCard_ToZone m; + m.set_card_name(cardList[i]->getName().toStdString()); + m.set_start_zone(cardList[i]->getOriginZone().toStdString()); + m.set_target_zone(cont->getName().toStdString()); + result.append(m); + } + } + return result; +} + +void DeckViewScene::resetSideboardPlan() +{ + rebuildTree(); + rearrangeItems(); } DeckView::DeckView(QWidget *parent) - : QGraphicsView(parent) + : QGraphicsView(parent) { - deckViewScene = new DeckViewScene(this); - - setBackgroundBrush(QBrush(QColor(0, 0, 0))); - setDragMode(RubberBandDrag); - setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing/* | QPainter::SmoothPixmapTransform*/); - setScene(deckViewScene); + deckViewScene = new DeckViewScene(this); + + setBackgroundBrush(QBrush(QColor(0, 0, 0))); + setDragMode(RubberBandDrag); + setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing/* | QPainter::SmoothPixmapTransform*/); + setScene(deckViewScene); - connect(deckViewScene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &))); - connect(deckViewScene, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); - connect(deckViewScene, SIGNAL(sideboardPlanChanged()), this, SIGNAL(sideboardPlanChanged())); + connect(deckViewScene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &))); + connect(deckViewScene, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); + connect(deckViewScene, SIGNAL(sideboardPlanChanged()), this, SIGNAL(sideboardPlanChanged())); } void DeckView::resizeEvent(QResizeEvent *event) { - QGraphicsView::resizeEvent(event); - deckViewScene->setOptimalAspectRatio((qreal) width() / (qreal) height()); - deckViewScene->rearrangeItems(); + QGraphicsView::resizeEvent(event); + deckViewScene->setOptimalAspectRatio((qreal) width() / (qreal) height()); + deckViewScene->rearrangeItems(); } void DeckView::updateSceneRect(const QRectF &rect) { - fitInView(rect, Qt::KeepAspectRatio); + fitInView(rect, Qt::KeepAspectRatio); } -void DeckView::setDeck(DeckList *_deck) +void DeckView::setDeck(const DeckList &_deck) { - deckViewScene->setDeck(_deck); + deckViewScene->setDeck(_deck); +} + +void DeckView::resetSideboardPlan() +{ + deckViewScene->resetSideboardPlan(); } diff --git a/cockatrice/src/deckview.h b/cockatrice/src/deckview.h index 34c73c987..059afa2ad 100644 --- a/cockatrice/src/deckview.h +++ b/cockatrice/src/deckview.h @@ -6,9 +6,10 @@ #include #include #include -#include "abstractcarditem.h" #include "abstractcarddragitem.h" +#include "pb/move_card_to_zone.pb.h" + class DeckList; class InnerDecklistNode; class CardInfo; @@ -18,98 +19,101 @@ class MoveCardToZone; class DeckViewCard : public AbstractCardItem { private: - QString originZone; - DeckViewCardDragItem *dragItem; + QString originZone; + DeckViewCardDragItem *dragItem; public: - DeckViewCard(const QString &_name = QString(), const QString &_originZone = QString(), QGraphicsItem *parent = 0); - ~DeckViewCard(); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - const QString &getOriginZone() const { return originZone; } + DeckViewCard(const QString &_name = QString(), const QString &_originZone = QString(), QGraphicsItem *parent = 0); + ~DeckViewCard(); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + const QString &getOriginZone() const { return originZone; } protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); }; class DeckViewCardDragItem : public AbstractCardDragItem { private: - DeckViewCardContainer *currentZone; - void handleDrop(DeckViewCardContainer *target); + DeckViewCardContainer *currentZone; + void handleDrop(DeckViewCardContainer *target); public: - DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); - void updatePosition(const QPointF &cursorScenePos); + DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); + void updatePosition(const QPointF &cursorScenePos); protected: - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); }; class DeckViewCardContainer : public QGraphicsItem { private: - static const int separatorY = 20; - static const int paddingY = 10; - - QString name; - QList cards; - QMultiMap cardsByType; - QList > currentRowsAndCols; - qreal width, height; - QPixmap bgPixmap; - int getCardTypeTextWidth() const; + static const int separatorY = 20; + static const int paddingY = 10; + + QString name; + QList cards; + QMultiMap cardsByType; + QList > currentRowsAndCols; + qreal width, height; + QPixmap bgPixmap; + int getCardTypeTextWidth() const; public: - enum { Type = typeDeckViewCardContainer }; - int type() const { return Type; } - DeckViewCardContainer(const QString &_name); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void addCard(DeckViewCard *card); - void removeCard(DeckViewCard *card); - const QList &getCards() const { return cards; } - const QString &getName() const { return name; } - void setWidth(qreal _width); + enum { Type = typeDeckViewCardContainer }; + int type() const { return Type; } + DeckViewCardContainer(const QString &_name); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void addCard(DeckViewCard *card); + void removeCard(DeckViewCard *card); + const QList &getCards() const { return cards; } + const QString &getName() const { return name; } + void setWidth(qreal _width); - QList > getRowsAndCols() const; - QSizeF calculateBoundingRect(const QList > &rowsAndCols) const; - void rearrangeItems(const QList > &rowsAndCols); + QList > getRowsAndCols() const; + QSizeF calculateBoundingRect(const QList > &rowsAndCols) const; + void rearrangeItems(const QList > &rowsAndCols); }; class DeckViewScene : public QGraphicsScene { - Q_OBJECT + Q_OBJECT signals: - void newCardAdded(AbstractCardItem *card); - void sideboardPlanChanged(); + void newCardAdded(AbstractCardItem *card); + void sideboardPlanChanged(); private: - bool locked; - DeckList *deck; - QMap cardContainers; - qreal optimalAspectRatio; - void rebuildTree(); - void applySideboardPlan(const QList &plan); + bool locked; + DeckList *deck; + QMap cardContainers; + qreal optimalAspectRatio; + void clearContents(); + void rebuildTree(); + void applySideboardPlan(const QList &plan); public: - DeckViewScene(QObject *parent = 0); - ~DeckViewScene(); - void setLocked(bool _locked) { locked = _locked; } - bool getLocked() const { return locked; } - void setDeck(DeckList *_deck); - void setOptimalAspectRatio(qreal _optimalAspectRatio) { optimalAspectRatio = _optimalAspectRatio; } - void rearrangeItems(); - void updateContents(); - QList getSideboardPlan() const; + DeckViewScene(QObject *parent = 0); + ~DeckViewScene(); + void setLocked(bool _locked) { locked = _locked; } + bool getLocked() const { return locked; } + void setDeck(const DeckList &_deck); + void setOptimalAspectRatio(qreal _optimalAspectRatio) { optimalAspectRatio = _optimalAspectRatio; } + void rearrangeItems(); + void updateContents(); + QList getSideboardPlan() const; + void resetSideboardPlan(); }; class DeckView : public QGraphicsView { - Q_OBJECT + Q_OBJECT private: - DeckViewScene *deckViewScene; + DeckViewScene *deckViewScene; protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event); public slots: - void updateSceneRect(const QRectF &rect); + void updateSceneRect(const QRectF &rect); signals: - void newCardAdded(AbstractCardItem *card); - void sideboardPlanChanged(); + void newCardAdded(AbstractCardItem *card); + void sideboardPlanChanged(); public: - DeckView(QWidget *parent = 0); - void setDeck(DeckList *_deck); - void setLocked(bool _locked) { deckViewScene->setLocked(_locked); } - QList getSideboardPlan() const { return deckViewScene->getSideboardPlan(); } + DeckView(QWidget *parent = 0); + void setDeck(const DeckList &_deck); + void setLocked(bool _locked) { deckViewScene->setLocked(_locked); } + QList getSideboardPlan() const { return deckViewScene->getSideboardPlan(); } + void resetSideboardPlan(); }; #endif diff --git a/cockatrice/src/dlg_cardsearch.cpp b/cockatrice/src/dlg_cardsearch.cpp index 55d9419cf..e73e11a2d 100644 --- a/cockatrice/src/dlg_cardsearch.cpp +++ b/cockatrice/src/dlg_cardsearch.cpp @@ -10,86 +10,86 @@ #include "main.h" DlgCardSearch::DlgCardSearch(QWidget *parent) - : QDialog(parent) + : QDialog(parent) { - QLabel *cardNameLabel = new QLabel(tr("Card name:")); - cardNameEdit = new QLineEdit; - - QLabel *cardTextLabel = new QLabel(tr("Card text:")); - cardTextEdit = new QLineEdit; - - QLabel *cardTypesLabel = new QLabel(tr("Card type (OR):")); - const QStringList &cardTypes = db->getAllMainCardTypes(); - QVBoxLayout *cardTypesLayout = new QVBoxLayout; - for (int i = 0; i < cardTypes.size(); ++i) { - QCheckBox *cardTypeCheckBox = new QCheckBox(cardTypes[i]); - cardTypeCheckBox->setChecked(true); - cardTypeCheckBoxes.append(cardTypeCheckBox); - cardTypesLayout->addWidget(cardTypeCheckBox); - } - - QLabel *cardColorsLabel = new QLabel(tr("Color (OR):")); - const QStringList &cardColors = db->getAllColors(); - QHBoxLayout *cardColorsLayout = new QHBoxLayout; - for (int i = 0; i < cardColors.size(); ++i) { - QCheckBox *cardColorCheckBox = new QCheckBox(cardColors[i]); - cardColorCheckBox->setChecked(true); - cardColorCheckBoxes.append(cardColorCheckBox); - cardColorsLayout->addWidget(cardColorCheckBox); - } - - QPushButton *okButton = new QPushButton(tr("O&K")); - okButton->setDefault(true); - okButton->setAutoDefault(true); - QPushButton *cancelButton = new QPushButton(tr("&Cancel")); - connect(okButton, SIGNAL(clicked()), this, SLOT(accept())); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); - QHBoxLayout *buttonHBox = new QHBoxLayout; - buttonHBox->addStretch(); - buttonHBox->addWidget(okButton); - buttonHBox->addWidget(cancelButton); - - QGridLayout *optionsLayout = new QGridLayout; - optionsLayout->addWidget(cardNameLabel, 0, 0); - optionsLayout->addWidget(cardNameEdit, 0, 1); - optionsLayout->addWidget(cardTextLabel, 1, 0); - optionsLayout->addWidget(cardTextEdit, 1, 1); - optionsLayout->addWidget(cardTypesLabel, 2, 0); - optionsLayout->addLayout(cardTypesLayout, 2, 1); - optionsLayout->addWidget(cardColorsLabel, 3, 0); - optionsLayout->addLayout(cardColorsLayout, 3, 1); - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addLayout(optionsLayout); - mainLayout->addLayout(buttonHBox); - setLayout(mainLayout); - setWindowTitle(tr("Card search")); + QLabel *cardNameLabel = new QLabel(tr("Card name:")); + cardNameEdit = new QLineEdit; + + QLabel *cardTextLabel = new QLabel(tr("Card text:")); + cardTextEdit = new QLineEdit; + + QLabel *cardTypesLabel = new QLabel(tr("Card type (OR):")); + const QStringList &cardTypes = db->getAllMainCardTypes(); + QVBoxLayout *cardTypesLayout = new QVBoxLayout; + for (int i = 0; i < cardTypes.size(); ++i) { + QCheckBox *cardTypeCheckBox = new QCheckBox(cardTypes[i]); + cardTypeCheckBox->setChecked(true); + cardTypeCheckBoxes.append(cardTypeCheckBox); + cardTypesLayout->addWidget(cardTypeCheckBox); + } + + QLabel *cardColorsLabel = new QLabel(tr("Color (OR):")); + const QStringList &cardColors = db->getAllColors(); + QHBoxLayout *cardColorsLayout = new QHBoxLayout; + for (int i = 0; i < cardColors.size(); ++i) { + QCheckBox *cardColorCheckBox = new QCheckBox(cardColors[i]); + cardColorCheckBox->setChecked(true); + cardColorCheckBoxes.append(cardColorCheckBox); + cardColorsLayout->addWidget(cardColorCheckBox); + } + + QPushButton *okButton = new QPushButton(tr("O&K")); + okButton->setDefault(true); + okButton->setAutoDefault(true); + QPushButton *cancelButton = new QPushButton(tr("&Cancel")); + connect(okButton, SIGNAL(clicked()), this, SLOT(accept())); + connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + QHBoxLayout *buttonHBox = new QHBoxLayout; + buttonHBox->addStretch(); + buttonHBox->addWidget(okButton); + buttonHBox->addWidget(cancelButton); + + QGridLayout *optionsLayout = new QGridLayout; + optionsLayout->addWidget(cardNameLabel, 0, 0); + optionsLayout->addWidget(cardNameEdit, 0, 1); + optionsLayout->addWidget(cardTextLabel, 1, 0); + optionsLayout->addWidget(cardTextEdit, 1, 1); + optionsLayout->addWidget(cardTypesLabel, 2, 0); + optionsLayout->addLayout(cardTypesLayout, 2, 1); + optionsLayout->addWidget(cardColorsLabel, 3, 0); + optionsLayout->addLayout(cardColorsLayout, 3, 1); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(optionsLayout); + mainLayout->addLayout(buttonHBox); + setLayout(mainLayout); + setWindowTitle(tr("Card search")); } QString DlgCardSearch::getCardName() const { - return cardNameEdit->text(); + return cardNameEdit->text(); } QString DlgCardSearch::getCardText() const { - return cardTextEdit->text(); + return cardTextEdit->text(); } QSet DlgCardSearch::getCardTypes() const { - QStringList result; - for (int i = 0; i < cardTypeCheckBoxes.size(); ++i) - if (cardTypeCheckBoxes[i]->isChecked()) - result.append(cardTypeCheckBoxes[i]->text()); - return QSet::fromList(result); + QStringList result; + for (int i = 0; i < cardTypeCheckBoxes.size(); ++i) + if (cardTypeCheckBoxes[i]->isChecked()) + result.append(cardTypeCheckBoxes[i]->text()); + return QSet::fromList(result); } QSet DlgCardSearch::getCardColors() const { - QStringList result; - for (int i = 0; i < cardColorCheckBoxes.size(); ++i) - if (cardColorCheckBoxes[i]->isChecked()) - result.append(cardColorCheckBoxes[i]->text()); - return QSet::fromList(result); + QStringList result; + for (int i = 0; i < cardColorCheckBoxes.size(); ++i) + if (cardColorCheckBoxes[i]->isChecked()) + result.append(cardColorCheckBoxes[i]->text()); + return QSet::fromList(result); } diff --git a/cockatrice/src/dlg_cardsearch.h b/cockatrice/src/dlg_cardsearch.h index 43f8fee3b..0736085f0 100644 --- a/cockatrice/src/dlg_cardsearch.h +++ b/cockatrice/src/dlg_cardsearch.h @@ -9,16 +9,16 @@ class QLineEdit; class QCheckBox; class DlgCardSearch : public QDialog { - Q_OBJECT + Q_OBJECT private: - QLineEdit *cardNameEdit, *cardTextEdit; - QList cardTypeCheckBoxes, cardColorCheckBoxes; + QLineEdit *cardNameEdit, *cardTextEdit; + QList cardTypeCheckBoxes, cardColorCheckBoxes; public: - DlgCardSearch(QWidget *parent = 0); - QString getCardName() const; - QString getCardText() const; - QSet getCardTypes() const; - QSet getCardColors() const; + DlgCardSearch(QWidget *parent = 0); + QString getCardName() const; + QString getCardText() const; + QSet getCardTypes() const; + QSet getCardColors() const; }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/dlg_connect.cpp b/cockatrice/src/dlg_connect.cpp index 0497448d5..d1955d14c 100644 --- a/cockatrice/src/dlg_connect.cpp +++ b/cockatrice/src/dlg_connect.cpp @@ -1,74 +1,72 @@ #include #include -#include +#include #include #include +#include #include "dlg_connect.h" DlgConnect::DlgConnect(QWidget *parent) - : QDialog(parent) + : QDialog(parent) { - QSettings settings; - settings.beginGroup("server"); + QSettings settings; + settings.beginGroup("server"); - hostLabel = new QLabel(tr("&Host:")); - hostEdit = new QLineEdit(settings.value("hostname", "play.cockatrice.de").toString()); - hostLabel->setBuddy(hostEdit); + hostLabel = new QLabel(tr("&Host:")); + hostEdit = new QLineEdit(settings.value("hostname", "cockatrice.woogerworks.com").toString()); + hostLabel->setBuddy(hostEdit); - portLabel = new QLabel(tr("&Port:")); - portEdit = new QLineEdit(settings.value("port", "4747").toString()); - portLabel->setBuddy(portEdit); + portLabel = new QLabel(tr("&Port:")); + portEdit = new QLineEdit(settings.value("port", "4747").toString()); + portLabel->setBuddy(portEdit); - playernameLabel = new QLabel(tr("Player &name:")); - playernameEdit = new QLineEdit(settings.value("playername", "Player").toString()); - playernameLabel->setBuddy(playernameEdit); + playernameLabel = new QLabel(tr("Player &name:")); + playernameEdit = new QLineEdit(settings.value("playername", "Player").toString()); + playernameLabel->setBuddy(playernameEdit); - passwordLabel = new QLabel(tr("P&assword:")); - passwordEdit = new QLineEdit(settings.value("password").toString()); - passwordLabel->setBuddy(passwordEdit); - passwordEdit->setEchoMode(QLineEdit::Password); + passwordLabel = new QLabel(tr("P&assword:")); + passwordEdit = new QLineEdit(settings.value("password").toString()); + passwordLabel->setBuddy(passwordEdit); + passwordEdit->setEchoMode(QLineEdit::Password); + + savePasswordCheckBox = new QCheckBox(tr("&Save password")); + savePasswordCheckBox->setChecked(settings.value("save_password", 1).toInt()); - okButton = new QPushButton(tr("&OK")); - okButton->setDefault(true); - cancelButton = new QPushButton(tr("&Cancel")); + QGridLayout *grid = new QGridLayout; + grid->addWidget(hostLabel, 0, 0); + grid->addWidget(hostEdit, 0, 1); + grid->addWidget(portLabel, 1, 0); + grid->addWidget(portEdit, 1, 1); + grid->addWidget(playernameLabel, 2, 0); + grid->addWidget(playernameEdit, 2, 1); + grid->addWidget(passwordLabel, 3, 0); + grid->addWidget(passwordEdit, 3, 1); + grid->addWidget(savePasswordCheckBox, 4, 0, 1, 2); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(grid); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); - QGridLayout *grid = new QGridLayout; - grid->addWidget(hostLabel, 0, 0); - grid->addWidget(hostEdit, 0, 1); - grid->addWidget(portLabel, 1, 0); - grid->addWidget(portEdit, 1, 1); - grid->addWidget(playernameLabel, 2, 0); - grid->addWidget(playernameEdit, 2, 1); - grid->addWidget(passwordLabel, 3, 0); - grid->addWidget(passwordEdit, 3, 1); - - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(); - buttonLayout->addWidget(okButton); - buttonLayout->addWidget(cancelButton); - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addLayout(grid); - mainLayout->addLayout(buttonLayout); - setLayout(mainLayout); - - setWindowTitle(tr("Connect to server")); - setFixedHeight(sizeHint().height()); - setMinimumWidth(300); - - connect(okButton, SIGNAL(clicked()), this, SLOT(actOk())); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + setWindowTitle(tr("Connect to server")); + setFixedHeight(sizeHint().height()); + setMinimumWidth(300); } void DlgConnect::actOk() { - QSettings settings; - settings.beginGroup("server"); - settings.setValue("hostname", hostEdit->text()); - settings.setValue("port", portEdit->text()); - settings.setValue("playername", playernameEdit->text()); - settings.setValue("password", passwordEdit->text()); - settings.endGroup(); + QSettings settings; + settings.beginGroup("server"); + settings.setValue("hostname", hostEdit->text()); + settings.setValue("port", portEdit->text()); + settings.setValue("playername", playernameEdit->text()); + settings.setValue("password", savePasswordCheckBox->isChecked() ? passwordEdit->text() : QString()); + settings.setValue("save_password", savePasswordCheckBox->isChecked() ? 1 : 0); + settings.endGroup(); - accept(); + accept(); } diff --git a/cockatrice/src/dlg_connect.h b/cockatrice/src/dlg_connect.h index 036595f31..87fe4ed9b 100644 --- a/cockatrice/src/dlg_connect.h +++ b/cockatrice/src/dlg_connect.h @@ -6,21 +6,22 @@ class QLabel; class QPushButton; +class QCheckBox; class DlgConnect : public QDialog { - Q_OBJECT + Q_OBJECT public: - DlgConnect(QWidget *parent = 0); - QString getHost() const { return hostEdit->text(); } - int getPort() const { return portEdit->text().toInt(); } - QString getPlayerName() const { return playernameEdit->text(); } - QString getPassword() const { return passwordEdit->text(); } + DlgConnect(QWidget *parent = 0); + QString getHost() const { return hostEdit->text(); } + int getPort() const { return portEdit->text().toInt(); } + QString getPlayerName() const { return playernameEdit->text(); } + QString getPassword() const { return passwordEdit->text(); } private slots: - void actOk(); + void actOk(); private: - QLabel *hostLabel, *portLabel, *playernameLabel, *passwordLabel; - QLineEdit *hostEdit, *portEdit, *playernameEdit, *passwordEdit; - QPushButton *okButton, *cancelButton; + QLabel *hostLabel, *portLabel, *playernameLabel, *passwordLabel; + QLineEdit *hostEdit, *portEdit, *playernameEdit, *passwordEdit; + QCheckBox *savePasswordCheckBox; }; #endif diff --git a/cockatrice/src/dlg_create_token.cpp b/cockatrice/src/dlg_create_token.cpp index e7c631431..9775ed0c9 100644 --- a/cockatrice/src/dlg_create_token.cpp +++ b/cockatrice/src/dlg_create_token.cpp @@ -1,102 +1,180 @@ #include #include #include -#include #include #include #include #include +#include +#include +#include +#include +#include +#include "decklist.h" #include "dlg_create_token.h" +#include "carddatabasemodel.h" +#include "main.h" -DlgCreateToken::DlgCreateToken(QWidget *parent) - : QDialog(parent) +DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent) + : QDialog(parent), predefinedTokens(_predefinedTokens) { - nameLabel = new QLabel(tr("&Name:")); - nameEdit = new QLineEdit(tr("Token")); - nameEdit->selectAll(); - nameLabel->setBuddy(nameEdit); + nameLabel = new QLabel(tr("&Name:")); + nameEdit = new QLineEdit(tr("Token")); + nameEdit->selectAll(); + nameLabel->setBuddy(nameEdit); - colorLabel = new QLabel(tr("C&olor:")); - colorEdit = new QComboBox; - colorEdit->addItem(tr("white"), "w"); - colorEdit->addItem(tr("blue"), "u"); - colorEdit->addItem(tr("black"), "b"); - colorEdit->addItem(tr("red"), "r"); - colorEdit->addItem(tr("green"), "g"); - colorEdit->addItem(tr("multicolor"), "m"); - colorEdit->addItem(tr("colorless"), ""); - colorLabel->setBuddy(colorEdit); + colorLabel = new QLabel(tr("C&olor:")); + colorEdit = new QComboBox; + colorEdit->addItem(tr("white"), "w"); + colorEdit->addItem(tr("blue"), "u"); + colorEdit->addItem(tr("black"), "b"); + colorEdit->addItem(tr("red"), "r"); + colorEdit->addItem(tr("green"), "g"); + colorEdit->addItem(tr("multicolor"), "m"); + colorEdit->addItem(tr("colorless"), QString()); + colorLabel->setBuddy(colorEdit); - ptLabel = new QLabel(tr("&P/T:")); - ptEdit = new QLineEdit; - ptLabel->setBuddy(ptEdit); + ptLabel = new QLabel(tr("&P/T:")); + ptEdit = new QLineEdit; + ptLabel->setBuddy(ptEdit); - annotationLabel = new QLabel(tr("&Annotation:")); - annotationEdit = new QLineEdit; - annotationLabel->setBuddy(annotationEdit); - - destroyCheckBox = new QCheckBox(tr("&Destroy token when it leaves the table")); - destroyCheckBox->setChecked(true); + annotationLabel = new QLabel(tr("&Annotation:")); + annotationEdit = new QLineEdit; + annotationLabel->setBuddy(annotationEdit); + + destroyCheckBox = new QCheckBox(tr("&Destroy token when it leaves the table")); + destroyCheckBox->setChecked(true); - okButton = new QPushButton(tr("&OK")); - okButton->setDefault(true); - cancelButton = new QPushButton(tr("&Cancel")); + QGridLayout *grid = new QGridLayout; + grid->addWidget(nameLabel, 0, 0); + grid->addWidget(nameEdit, 0, 1); + grid->addWidget(colorLabel, 1, 0); + grid->addWidget(colorEdit, 1, 1); + grid->addWidget(ptLabel, 2, 0); + grid->addWidget(ptEdit, 2, 1); + grid->addWidget(annotationLabel, 3, 0); + grid->addWidget(annotationEdit, 3, 1); + grid->addWidget(destroyCheckBox, 4, 0, 1, 2); + + QGroupBox *tokenDataGroupBox = new QGroupBox(tr("Token data")); + tokenDataGroupBox->setLayout(grid); + + cardDatabaseModel = new CardDatabaseModel(db, this); + cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this); + cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel); + cardDatabaseDisplayModel->setIsToken(CardDatabaseDisplayModel::ShowTrue); + + chooseTokenFromAllRadioButton = new QRadioButton(tr("Show &all tokens")); + connect(chooseTokenFromAllRadioButton, SIGNAL(toggled(bool)), this, SLOT(actChooseTokenFromAll(bool))); + chooseTokenFromDeckRadioButton = new QRadioButton(tr("Show tokens from this &deck")); + connect(chooseTokenFromDeckRadioButton, SIGNAL(toggled(bool)), this, SLOT(actChooseTokenFromDeck(bool))); + QTreeView *chooseTokenView = new QTreeView; + chooseTokenView->setModel(cardDatabaseDisplayModel); + chooseTokenView->setUniformRowHeights(true); + chooseTokenView->setRootIsDecorated(false); + chooseTokenView->setAlternatingRowColors(true); + chooseTokenView->setSortingEnabled(true); + chooseTokenView->sortByColumn(0, Qt::AscendingOrder); + chooseTokenView->resizeColumnToContents(0); + chooseTokenView->header()->setStretchLastSection(false); + chooseTokenView->header()->hideSection(1); + chooseTokenView->header()->hideSection(2); +#if QT_VERSION < 0x050000 + chooseTokenView->header()->setResizeMode(3, QHeaderView::ResizeToContents); + chooseTokenView->header()->setResizeMode(4, QHeaderView::ResizeToContents); +#else + chooseTokenView->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + chooseTokenView->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents); +#endif + connect(chooseTokenView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), this, SLOT(tokenSelectionChanged(QModelIndex, QModelIndex))); + + if (predefinedTokens.isEmpty()) + chooseTokenFromAllRadioButton->setChecked(true); + else { + chooseTokenFromDeckRadioButton->setChecked(true); + cardDatabaseDisplayModel->setCardNameSet(QSet::fromList(predefinedTokens)); + } + + QVBoxLayout *tokenChooseLayout = new QVBoxLayout; + tokenChooseLayout->addWidget(chooseTokenFromAllRadioButton); + tokenChooseLayout->addWidget(chooseTokenFromDeckRadioButton); + tokenChooseLayout->addWidget(chooseTokenView); + + QGroupBox *tokenChooseGroupBox = new QGroupBox(tr("Choose token from list")); + tokenChooseGroupBox->setLayout(tokenChooseLayout); + + QVBoxLayout *leftVBox = new QVBoxLayout; + leftVBox->addWidget(tokenDataGroupBox); + leftVBox->addStretch(); + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addLayout(leftVBox); + hbox->addWidget(tokenChooseGroupBox); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(hbox); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); - QGridLayout *grid = new QGridLayout; - grid->addWidget(nameLabel, 0, 0); - grid->addWidget(nameEdit, 0, 1); - grid->addWidget(colorLabel, 1, 0); - grid->addWidget(colorEdit, 1, 1); - grid->addWidget(ptLabel, 2, 0); - grid->addWidget(ptEdit, 2, 1); - grid->addWidget(annotationLabel, 3, 0); - grid->addWidget(annotationEdit, 3, 1); - grid->addWidget(destroyCheckBox, 4, 0, 1, 2); + setWindowTitle(tr("Create token")); + setFixedHeight(sizeHint().height()); + setMinimumWidth(300); +} - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(); - buttonLayout->addWidget(okButton); - buttonLayout->addWidget(cancelButton); +void DlgCreateToken::tokenSelectionChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) +{ + const QModelIndex realIndex = cardDatabaseDisplayModel->mapToSource(current); + const CardInfo *cardInfo = current.row() >= 0 ? cardDatabaseModel->getCard(realIndex.row()) : db->getCard(); + + nameEdit->setText(cardInfo->getName()); + const QString cardColor = cardInfo->getColors().isEmpty() ? QString() : (cardInfo->getColors().size() > 1 ? QString("m") : cardInfo->getColors().first()); + colorEdit->setCurrentIndex(colorEdit->findData(cardColor, Qt::UserRole, Qt::MatchFixedString)); + ptEdit->setText(cardInfo->getPowTough()); + annotationEdit->setText(cardInfo->getText()); +} - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addLayout(grid); - mainLayout->addLayout(buttonLayout); - setLayout(mainLayout); +void DlgCreateToken::actChooseTokenFromAll(bool checked) +{ + if (checked) + cardDatabaseDisplayModel->setCardNameSet(QSet()); +} - setWindowTitle(tr("Create token")); - setFixedHeight(sizeHint().height()); - setMinimumWidth(300); - - connect(okButton, SIGNAL(clicked()), this, SLOT(actOk())); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); +void DlgCreateToken::actChooseTokenFromDeck(bool checked) +{ + if (checked) + cardDatabaseDisplayModel->setCardNameSet(QSet::fromList(predefinedTokens)); } void DlgCreateToken::actOk() { - accept(); + accept(); } QString DlgCreateToken::getName() const { - return nameEdit->text(); + return nameEdit->text(); } QString DlgCreateToken::getColor() const { - return colorEdit->itemData(colorEdit->currentIndex()).toString(); + return colorEdit->itemData(colorEdit->currentIndex()).toString(); } QString DlgCreateToken::getPT() const { - return ptEdit->text(); + return ptEdit->text(); } QString DlgCreateToken::getAnnotation() const { - return annotationEdit->text(); + return annotationEdit->text(); } bool DlgCreateToken::getDestroy() const { - return destroyCheckBox->isChecked(); + return destroyCheckBox->isChecked(); } diff --git a/cockatrice/src/dlg_create_token.h b/cockatrice/src/dlg_create_token.h index 1e2b3b75b..45d7c2c52 100644 --- a/cockatrice/src/dlg_create_token.h +++ b/cockatrice/src/dlg_create_token.h @@ -2,30 +2,41 @@ #define DLG_CREATETOKEN_H #include -#include +#include class QLabel; +class QLineEdit; class QComboBox; class QCheckBox; class QPushButton; +class QRadioButton; +class DeckList; +class CardDatabaseModel; +class CardDatabaseDisplayModel; class DlgCreateToken : public QDialog { - Q_OBJECT + Q_OBJECT public: - DlgCreateToken(QWidget *parent = 0); - QString getName() const; - QString getColor() const; - QString getPT() const; - QString getAnnotation() const; - bool getDestroy() const; + DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent = 0); + QString getName() const; + QString getColor() const; + QString getPT() const; + QString getAnnotation() const; + bool getDestroy() const; private slots: - void actOk(); + void tokenSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void actChooseTokenFromAll(bool checked); + void actChooseTokenFromDeck(bool checked); + void actOk(); private: - QLabel *nameLabel, *colorLabel, *ptLabel, *annotationLabel; - QComboBox *colorEdit; - QLineEdit *nameEdit, *ptEdit, *annotationEdit; - QCheckBox *destroyCheckBox; - QPushButton *okButton, *cancelButton; + CardDatabaseModel *cardDatabaseModel; + CardDatabaseDisplayModel *cardDatabaseDisplayModel; + QStringList predefinedTokens; + QLabel *nameLabel, *colorLabel, *ptLabel, *annotationLabel; + QComboBox *colorEdit; + QLineEdit *nameEdit, *ptEdit, *annotationEdit; + QCheckBox *destroyCheckBox; + QRadioButton *chooseTokenFromAllRadioButton, *chooseTokenFromDeckRadioButton; }; #endif diff --git a/cockatrice/src/dlg_creategame.cpp b/cockatrice/src/dlg_creategame.cpp index 0d5b69f43..fc62df7d5 100644 --- a/cockatrice/src/dlg_creategame.cpp +++ b/cockatrice/src/dlg_creategame.cpp @@ -7,146 +7,193 @@ #include #include #include +#include #include +#include #include "dlg_creategame.h" -#include "protocol_items.h" +#include "tab_room.h" -DlgCreateGame::DlgCreateGame(AbstractClient *_client, int _roomId, const QMap &_gameTypes, QWidget *parent) - : QDialog(parent), client(_client), roomId(_roomId), gameTypes(_gameTypes) +#include "pending_command.h" +#include "pb/room_commands.pb.h" +#include "pb/serverinfo_game.pb.h" +#include "pb/response.pb.h" + +void DlgCreateGame::sharedCtor() { - descriptionLabel = new QLabel(tr("&Description:")); - descriptionEdit = new QLineEdit; - descriptionLabel->setBuddy(descriptionEdit); - descriptionEdit->setMaxLength(60); + descriptionLabel = new QLabel(tr("&Description:")); + descriptionEdit = new QLineEdit; + descriptionLabel->setBuddy(descriptionEdit); + descriptionEdit->setMaxLength(60); - maxPlayersLabel = new QLabel(tr("P&layers:")); - maxPlayersEdit = new QSpinBox(); - maxPlayersEdit->setMinimum(1); - maxPlayersEdit->setMaximum(100); - maxPlayersEdit->setValue(2); - maxPlayersLabel->setBuddy(maxPlayersEdit); - - QGridLayout *generalGrid = new QGridLayout; - generalGrid->addWidget(descriptionLabel, 0, 0); - generalGrid->addWidget(descriptionEdit, 0, 1); - generalGrid->addWidget(maxPlayersLabel, 1, 0); - generalGrid->addWidget(maxPlayersEdit, 1, 1); - - QVBoxLayout *gameTypeLayout = new QVBoxLayout; - QMapIterator gameTypeIterator(gameTypes); - while (gameTypeIterator.hasNext()) { - gameTypeIterator.next(); - QCheckBox *gameTypeCheckBox = new QCheckBox(gameTypeIterator.value()); - gameTypeLayout->addWidget(gameTypeCheckBox); - gameTypeCheckBoxes.insert(gameTypeIterator.key(), gameTypeCheckBox); - } - QGroupBox *gameTypeGroupBox = new QGroupBox(tr("Game type")); - gameTypeGroupBox->setLayout(gameTypeLayout); - - passwordLabel = new QLabel(tr("&Password:")); - passwordEdit = new QLineEdit; - passwordLabel->setBuddy(passwordEdit); + maxPlayersLabel = new QLabel(tr("P&layers:")); + maxPlayersEdit = new QSpinBox(); + maxPlayersEdit->setMinimum(1); + maxPlayersEdit->setMaximum(100); + maxPlayersEdit->setValue(2); + maxPlayersLabel->setBuddy(maxPlayersEdit); + + QGridLayout *generalGrid = new QGridLayout; + generalGrid->addWidget(descriptionLabel, 0, 0); + generalGrid->addWidget(descriptionEdit, 0, 1); + generalGrid->addWidget(maxPlayersLabel, 1, 0); + generalGrid->addWidget(maxPlayersEdit, 1, 1); + + QVBoxLayout *gameTypeLayout = new QVBoxLayout; + QMapIterator gameTypeIterator(gameTypes); + while (gameTypeIterator.hasNext()) { + gameTypeIterator.next(); + QCheckBox *gameTypeCheckBox = new QCheckBox(gameTypeIterator.value()); + gameTypeLayout->addWidget(gameTypeCheckBox); + gameTypeCheckBoxes.insert(gameTypeIterator.key(), gameTypeCheckBox); + } + QGroupBox *gameTypeGroupBox = new QGroupBox(tr("Game type")); + gameTypeGroupBox->setLayout(gameTypeLayout); + + passwordLabel = new QLabel(tr("&Password:")); + passwordEdit = new QLineEdit; + passwordLabel->setBuddy(passwordEdit); - onlyBuddiesCheckBox = new QCheckBox(tr("Only &buddies can join")); - onlyRegisteredCheckBox = new QCheckBox(tr("Only ®istered users can join")); - - QGridLayout *joinRestrictionsLayout = new QGridLayout; - joinRestrictionsLayout->addWidget(passwordLabel, 0, 0); - joinRestrictionsLayout->addWidget(passwordEdit, 0, 1); - joinRestrictionsLayout->addWidget(onlyBuddiesCheckBox, 1, 0, 1, 2); - joinRestrictionsLayout->addWidget(onlyRegisteredCheckBox, 2, 0, 1, 2); - - QGroupBox *joinRestrictionsGroupBox = new QGroupBox(tr("Joining restrictions")); - joinRestrictionsGroupBox->setLayout(joinRestrictionsLayout); - - spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators allowed")); - spectatorsAllowedCheckBox->setChecked(true); - connect(spectatorsAllowedCheckBox, SIGNAL(stateChanged(int)), this, SLOT(spectatorsAllowedChanged(int))); - spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to join")); - spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat")); - spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators see &everything")); - QVBoxLayout *spectatorsLayout = new QVBoxLayout; - spectatorsLayout->addWidget(spectatorsAllowedCheckBox); - spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox); - spectatorsLayout->addWidget(spectatorsCanTalkCheckBox); - spectatorsLayout->addWidget(spectatorsSeeEverythingCheckBox); - spectatorsGroupBox = new QGroupBox(tr("Spectators")); - spectatorsGroupBox->setLayout(spectatorsLayout); + onlyBuddiesCheckBox = new QCheckBox(tr("Only &buddies can join")); + onlyRegisteredCheckBox = new QCheckBox(tr("Only ®istered users can join")); + if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) + onlyRegisteredCheckBox->setChecked(true); + + QGridLayout *joinRestrictionsLayout = new QGridLayout; + joinRestrictionsLayout->addWidget(passwordLabel, 0, 0); + joinRestrictionsLayout->addWidget(passwordEdit, 0, 1); + joinRestrictionsLayout->addWidget(onlyBuddiesCheckBox, 1, 0, 1, 2); + joinRestrictionsLayout->addWidget(onlyRegisteredCheckBox, 2, 0, 1, 2); + + QGroupBox *joinRestrictionsGroupBox = new QGroupBox(tr("Joining restrictions")); + joinRestrictionsGroupBox->setLayout(joinRestrictionsLayout); + + spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators allowed")); + spectatorsAllowedCheckBox->setChecked(true); + connect(spectatorsAllowedCheckBox, SIGNAL(stateChanged(int)), this, SLOT(spectatorsAllowedChanged(int))); + spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to join")); + spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat")); + spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators see &everything")); + QVBoxLayout *spectatorsLayout = new QVBoxLayout; + spectatorsLayout->addWidget(spectatorsAllowedCheckBox); + spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox); + spectatorsLayout->addWidget(spectatorsCanTalkCheckBox); + spectatorsLayout->addWidget(spectatorsSeeEverythingCheckBox); + spectatorsGroupBox = new QGroupBox(tr("Spectators")); + spectatorsGroupBox->setLayout(spectatorsLayout); - QGridLayout *grid = new QGridLayout; - grid->addLayout(generalGrid, 0, 0); - grid->addWidget(spectatorsGroupBox, 1, 0); - grid->addWidget(joinRestrictionsGroupBox, 0, 1); - grid->addWidget(gameTypeGroupBox, 1, 1); + QGridLayout *grid = new QGridLayout; + grid->addLayout(generalGrid, 0, 0); + grid->addWidget(spectatorsGroupBox, 1, 0); + grid->addWidget(joinRestrictionsGroupBox, 0, 1); + grid->addWidget(gameTypeGroupBox, 1, 1); - okButton = new QPushButton(tr("&OK")); - okButton->setDefault(true); - cancelButton = new QPushButton(tr("&Cancel")); + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(grid); + mainLayout->addWidget(buttonBox); - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(); - buttonLayout->addWidget(okButton); - buttonLayout->addWidget(cancelButton); + setLayout(mainLayout); - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addLayout(grid); - mainLayout->addLayout(buttonLayout); + setFixedHeight(sizeHint().height()); +} - setLayout(mainLayout); +DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap &_gameTypes, QWidget *parent) + : QDialog(parent), room(_room), gameTypes(_gameTypes) +{ + sharedCtor(); + + buttonBox->addButton(QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOK())); + + setWindowTitle(tr("Create game")); +} - setWindowTitle(tr("Create game")); - setFixedHeight(sizeHint().height()); - - connect(okButton, SIGNAL(clicked()), this, SLOT(actOK())); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); +DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap &_gameTypes, QWidget *parent) + : QDialog(parent), room(0), gameTypes(_gameTypes) +{ + sharedCtor(); + + descriptionEdit->setEnabled(false); + maxPlayersEdit->setEnabled(false); + passwordEdit->setEnabled(false); + onlyBuddiesCheckBox->setEnabled(false); + onlyRegisteredCheckBox->setEnabled(false); + spectatorsAllowedCheckBox->setEnabled(false); + spectatorsNeedPasswordCheckBox->setEnabled(false); + spectatorsCanTalkCheckBox->setEnabled(false); + spectatorsSeeEverythingCheckBox->setEnabled(false); + + descriptionEdit->setText(QString::fromStdString(gameInfo.description())); + maxPlayersEdit->setValue(gameInfo.max_players()); + onlyBuddiesCheckBox->setChecked(gameInfo.only_buddies()); + onlyRegisteredCheckBox->setChecked(gameInfo.only_registered()); + spectatorsAllowedCheckBox->setChecked(gameInfo.spectators_allowed()); + spectatorsNeedPasswordCheckBox->setChecked(gameInfo.spectators_need_password()); + spectatorsCanTalkCheckBox->setChecked(gameInfo.spectators_can_chat()); + spectatorsSeeEverythingCheckBox->setChecked(gameInfo.spectators_omniscient()); + + QSet types; + for (int i = 0; i < gameInfo.game_types_size(); ++i) + types.insert(gameInfo.game_types(i)); + + QMapIterator gameTypeIterator(gameTypes); + while (gameTypeIterator.hasNext()) { + gameTypeIterator.next(); + + QCheckBox *gameTypeCheckBox = gameTypeCheckBoxes.value(gameTypeIterator.key()); + gameTypeCheckBox->setEnabled(false); + gameTypeCheckBox->setChecked(types.contains(gameTypeIterator.key())); + } + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + + setWindowTitle(tr("Game information")); } void DlgCreateGame::actOK() { - QList gameTypeList; - QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); - while (gameTypeCheckBoxIterator.hasNext()) { - gameTypeCheckBoxIterator.next(); - if (gameTypeCheckBoxIterator.value()->isChecked()) - gameTypeList.append(new GameTypeId(gameTypeCheckBoxIterator.key())); - } - - Command_CreateGame *createCommand = new Command_CreateGame( - roomId, - descriptionEdit->text(), - passwordEdit->text(), - maxPlayersEdit->value(), - gameTypeList, - onlyBuddiesCheckBox->isChecked(), - onlyRegisteredCheckBox->isChecked(), - spectatorsAllowedCheckBox->isChecked(), - spectatorsNeedPasswordCheckBox->isChecked(), - spectatorsCanTalkCheckBox->isChecked(), - spectatorsSeeEverythingCheckBox->isChecked() - ); - connect(createCommand, SIGNAL(finished(ResponseCode)), this, SLOT(checkResponse(ResponseCode))); - client->sendCommand(createCommand); - - okButton->setEnabled(false); - cancelButton->setEnabled(false); + Command_CreateGame cmd; + cmd.set_description(descriptionEdit->text().toStdString()); + cmd.set_password(passwordEdit->text().toStdString()); + cmd.set_max_players(maxPlayersEdit->value()); + cmd.set_only_buddies(onlyBuddiesCheckBox->isChecked()); + cmd.set_only_registered(onlyRegisteredCheckBox->isChecked()); + cmd.set_spectators_allowed(spectatorsAllowedCheckBox->isChecked()); + cmd.set_spectators_need_password(spectatorsNeedPasswordCheckBox->isChecked()); + cmd.set_spectators_can_talk(spectatorsCanTalkCheckBox->isChecked()); + cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked()); + + QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); + while (gameTypeCheckBoxIterator.hasNext()) { + gameTypeCheckBoxIterator.next(); + if (gameTypeCheckBoxIterator.value()->isChecked()) + cmd.add_game_type_ids(gameTypeCheckBoxIterator.key()); + } + + PendingCommand *pend = room->prepareRoomCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(checkResponse(Response))); + room->sendRoomCommand(pend); + + buttonBox->setEnabled(false); } -void DlgCreateGame::checkResponse(ResponseCode response) +void DlgCreateGame::checkResponse(const Response &response) { - okButton->setEnabled(true); - cancelButton->setEnabled(true); + buttonBox->setEnabled(true); - if (response == RespOk) - accept(); - else { - QMessageBox::critical(this, tr("Error"), tr("Server error.")); - return; - } + if (response.response_code() == Response::RespOk) + accept(); + else { + QMessageBox::critical(this, tr("Error"), tr("Server error.")); + return; + } } void DlgCreateGame::spectatorsAllowedChanged(int state) { - spectatorsNeedPasswordCheckBox->setEnabled(state); - spectatorsCanTalkCheckBox->setEnabled(state); - spectatorsSeeEverythingCheckBox->setEnabled(state); + spectatorsNeedPasswordCheckBox->setEnabled(state); + spectatorsCanTalkCheckBox->setEnabled(state); + spectatorsSeeEverythingCheckBox->setEnabled(state); } diff --git a/cockatrice/src/dlg_creategame.h b/cockatrice/src/dlg_creategame.h index c0ddc63ff..032bfc6fc 100644 --- a/cockatrice/src/dlg_creategame.h +++ b/cockatrice/src/dlg_creategame.h @@ -2,7 +2,7 @@ #define DLG_CREATEGAME_H #include -#include "abstractclient.h" +#include class QLabel; class QLineEdit; @@ -10,28 +10,35 @@ class QPushButton; class QCheckBox; class QGroupBox; class QSpinBox; +class QDialogButtonBox; +class TabRoom; + +class Response; +class ServerInfo_Game; class DlgCreateGame : public QDialog { - Q_OBJECT + Q_OBJECT public: - DlgCreateGame(AbstractClient *_client, int _roomId, const QMap &_gameTypes, QWidget *parent = 0); + DlgCreateGame(TabRoom *_room, const QMap &_gameTypes, QWidget *parent = 0); + DlgCreateGame(const ServerInfo_Game &game, const QMap &_gameTypes, QWidget *parent = 0); private slots: - void actOK(); - void checkResponse(ResponseCode response); - void spectatorsAllowedChanged(int state); + void actOK(); + void checkResponse(const Response &response); + void spectatorsAllowedChanged(int state); private: - AbstractClient *client; - int roomId; - QMap gameTypes; - QMap gameTypeCheckBoxes; + TabRoom *room; + QMap gameTypes; + QMap gameTypeCheckBoxes; - QGroupBox *spectatorsGroupBox; - QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel; - QLineEdit *descriptionEdit, *passwordEdit; - QSpinBox *maxPlayersEdit; - QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; - QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, *spectatorsSeeEverythingCheckBox; - QPushButton *okButton, *cancelButton; + QGroupBox *spectatorsGroupBox; + QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel; + QLineEdit *descriptionEdit, *passwordEdit; + QSpinBox *maxPlayersEdit; + QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; + QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, *spectatorsSeeEverythingCheckBox; + QDialogButtonBox *buttonBox; + + void sharedCtor(); }; #endif diff --git a/cockatrice/src/dlg_edit_tokens.cpp b/cockatrice/src/dlg_edit_tokens.cpp new file mode 100644 index 000000000..e2de2cd4b --- /dev/null +++ b/cockatrice/src/dlg_edit_tokens.cpp @@ -0,0 +1,180 @@ +#include "dlg_edit_tokens.h" +#include "carddatabasemodel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DlgEditTokens::DlgEditTokens(CardDatabaseModel *_cardDatabaseModel, QWidget *parent) + : QDialog(parent), currentCard(0), cardDatabaseModel(_cardDatabaseModel) +{ + nameLabel = new QLabel(tr("&Name:")); + nameEdit = new QLineEdit; + nameEdit->setEnabled(false); + nameLabel->setBuddy(nameEdit); + + colorLabel = new QLabel(tr("C&olor:")); + colorEdit = new QComboBox; + colorEdit->addItem(tr("white"), "w"); + colorEdit->addItem(tr("blue"), "u"); + colorEdit->addItem(tr("black"), "b"); + colorEdit->addItem(tr("red"), "r"); + colorEdit->addItem(tr("green"), "g"); + colorEdit->addItem(tr("multicolor"), "m"); + colorEdit->addItem(tr("colorless"), QString()); + colorLabel->setBuddy(colorEdit); + connect(colorEdit, SIGNAL(currentIndexChanged(int)), this, SLOT(colorChanged(int))); + + ptLabel = new QLabel(tr("&P/T:")); + ptEdit = new QLineEdit; + ptLabel->setBuddy(ptEdit); + connect(ptEdit, SIGNAL(textChanged(QString)), this, SLOT(ptChanged(QString))); + + annotationLabel = new QLabel(tr("&Annotation:")); + annotationEdit = new QLineEdit; + annotationLabel->setBuddy(annotationEdit); + connect(annotationEdit, SIGNAL(textChanged(QString)), this, SLOT(annotationChanged(QString))); + + QGridLayout *grid = new QGridLayout; + grid->addWidget(nameLabel, 0, 0); + grid->addWidget(nameEdit, 0, 1); + grid->addWidget(colorLabel, 1, 0); + grid->addWidget(colorEdit, 1, 1); + grid->addWidget(ptLabel, 2, 0); + grid->addWidget(ptEdit, 2, 1); + grid->addWidget(annotationLabel, 3, 0); + grid->addWidget(annotationEdit, 3, 1); + + QGroupBox *tokenDataGroupBox = new QGroupBox(tr("Token data")); + tokenDataGroupBox->setLayout(grid); + + cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this); + cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel); + cardDatabaseDisplayModel->setIsToken(CardDatabaseDisplayModel::ShowTrue); + + chooseTokenView = new QTreeView; + chooseTokenView->setModel(cardDatabaseDisplayModel); + chooseTokenView->setUniformRowHeights(true); + chooseTokenView->setRootIsDecorated(false); + chooseTokenView->setAlternatingRowColors(true); + chooseTokenView->setSortingEnabled(true); + chooseTokenView->sortByColumn(0, Qt::AscendingOrder); + chooseTokenView->resizeColumnToContents(0); + chooseTokenView->header()->setStretchLastSection(false); + chooseTokenView->header()->hideSection(1); + chooseTokenView->header()->hideSection(2); +#if QT_VERSION < 0x050000 + chooseTokenView->header()->setResizeMode(3, QHeaderView::ResizeToContents); + chooseTokenView->header()->setResizeMode(4, QHeaderView::ResizeToContents); +#else + chooseTokenView->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + chooseTokenView->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents); +#endif + connect(chooseTokenView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), this, SLOT(tokenSelectionChanged(QModelIndex, QModelIndex))); + + QAction *aAddToken = new QAction(tr("Add token"), this); + aAddToken->setIcon(QIcon(":/resources/increment.svg")); + connect(aAddToken, SIGNAL(triggered()), this, SLOT(actAddToken())); + QAction *aRemoveToken = new QAction(tr("Remove token"), this); + aRemoveToken->setIcon(QIcon(":/resources/decrement.svg")); + connect(aRemoveToken, SIGNAL(triggered()), this, SLOT(actRemoveToken())); + + QToolBar *databaseToolBar = new QToolBar; + databaseToolBar->addAction(aAddToken); + databaseToolBar->addAction(aRemoveToken); + + QVBoxLayout *leftVBox = new QVBoxLayout; + leftVBox->addWidget(chooseTokenView); + leftVBox->addWidget(databaseToolBar); + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addLayout(leftVBox); + hbox->addWidget(tokenDataGroupBox); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(hbox); + mainLayout->addWidget(buttonBox); + + setLayout(mainLayout); + setWindowTitle(tr("Edit tokens")); +} + +void DlgEditTokens::tokenSelectionChanged(const QModelIndex ¤t, const QModelIndex & /* previous */) +{ + const QModelIndex realIndex = cardDatabaseDisplayModel->mapToSource(current); + CardInfo *cardInfo = current.row() >= 0 ? cardDatabaseModel->getCard(realIndex.row()) : cardDatabaseModel->getDatabase()->getCard(); + if (!cardInfo->getName().isEmpty()) + currentCard = cardInfo; + else + currentCard = 0; + + nameEdit->setText(cardInfo->getName()); + const QString cardColor = cardInfo->getColors().isEmpty() ? QString() : (cardInfo->getColors().size() > 1 ? QString("m") : cardInfo->getColors().first()); + colorEdit->setCurrentIndex(colorEdit->findData(cardColor, Qt::UserRole, Qt::MatchFixedString)); + ptEdit->setText(cardInfo->getPowTough()); + annotationEdit->setText(cardInfo->getText()); +} + +void DlgEditTokens::actAddToken() +{ + QString name; + bool askAgain; + do { + name = QInputDialog::getText(this, tr("Add token"), tr("Please enter the name of the token:")); + if (!name.isEmpty() && cardDatabaseModel->getDatabase()->getCard(name, false)) { + QMessageBox::critical(this, tr("Error"), tr("The chosen name conflicts with an existing card or token.")); + askAgain = true; + } else + askAgain = false; + } while (askAgain); + + if (name.isEmpty()) + return; + + CardInfo *card = new CardInfo(cardDatabaseModel->getDatabase(), name, true); + card->addToSet(cardDatabaseModel->getDatabase()->getSet("TK")); + card->setCardType("Token"); + cardDatabaseModel->getDatabase()->addCard(card); +} + +void DlgEditTokens::actRemoveToken() +{ + if (currentCard) { + CardInfo *cardToRemove = currentCard; // the currentCard property gets modified during db->removeCard() + currentCard = 0; + cardDatabaseModel->getDatabase()->removeCard(cardToRemove); + delete cardToRemove; + } +} + +void DlgEditTokens::colorChanged(int colorIndex) +{ + if (currentCard) + currentCard->setColors(QStringList() << colorEdit->itemData(colorIndex).toString()); +} + +void DlgEditTokens::ptChanged(const QString &_pt) +{ + if (currentCard) + currentCard->setPowTough(_pt); +} + +void DlgEditTokens::annotationChanged(const QString &_annotation) +{ + if (currentCard) + currentCard->setText(_annotation); +} diff --git a/cockatrice/src/dlg_edit_tokens.h b/cockatrice/src/dlg_edit_tokens.h new file mode 100644 index 000000000..61d941ffc --- /dev/null +++ b/cockatrice/src/dlg_edit_tokens.h @@ -0,0 +1,38 @@ +#ifndef DLG_EDIT_TOKENS_H +#define DLG_EDIT_TOKENS_H + +#include + +class QModelIndex; +class CardDatabaseModel; +class CardDatabaseDisplayModel; +class QLabel; +class QComboBox; +class QLineEdit; +class QTreeView; +class CardInfo; + +class DlgEditTokens : public QDialog { + Q_OBJECT +private slots: + void tokenSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void colorChanged(int _colorIndex); + void ptChanged(const QString &_pt); + void annotationChanged(const QString &_annotation); + + void actAddToken(); + void actRemoveToken(); +private: + CardInfo *currentCard; + CardDatabaseModel *cardDatabaseModel; + CardDatabaseDisplayModel *cardDatabaseDisplayModel; + QStringList predefinedTokens; + QLabel *nameLabel, *colorLabel, *ptLabel, *annotationLabel; + QComboBox *colorEdit; + QLineEdit *nameEdit, *ptEdit, *annotationEdit; + QTreeView *chooseTokenView; +public: + DlgEditTokens(CardDatabaseModel *_cardDatabaseModel, QWidget *parent = 0); +}; + +#endif diff --git a/cockatrice/src/dlg_filter_games.cpp b/cockatrice/src/dlg_filter_games.cpp new file mode 100644 index 000000000..eb2e8f78e --- /dev/null +++ b/cockatrice/src/dlg_filter_games.cpp @@ -0,0 +1,239 @@ +#include "dlg_filter_games.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, QWidget *parent) + : QDialog(parent), + allGameTypes(_allGameTypes) +{ + QSettings settings; + settings.beginGroup("filter_games"); + + unavailableGamesVisibleCheckBox = new QCheckBox(tr("Show &unavailable games")); + unavailableGamesVisibleCheckBox->setChecked( + settings.value("unavailable_games_visible", false).toBool() + ); + + passwordProtectedGamesVisibleCheckBox = new QCheckBox(tr("Show &password protected games")); + passwordProtectedGamesVisibleCheckBox->setChecked( + settings.value("password_protected_games_visible", false).toBool() + ); + + gameNameFilterEdit = new QLineEdit; + gameNameFilterEdit->setText( + settings.value("game_name_filter", "").toString() + ); + QLabel *gameNameFilterLabel = new QLabel(tr("Game &description:")); + gameNameFilterLabel->setBuddy(gameNameFilterEdit); + + creatorNameFilterEdit = new QLineEdit; + creatorNameFilterEdit->setText( + settings.value("creator_name_filter", "").toString() + ); + QLabel *creatorNameFilterLabel = new QLabel(tr("&Creator name:")); + creatorNameFilterLabel->setBuddy(creatorNameFilterEdit); + + QVBoxLayout *gameTypeFilterLayout = new QVBoxLayout; + QMapIterator gameTypesIterator(allGameTypes); + while (gameTypesIterator.hasNext()) { + gameTypesIterator.next(); + + QCheckBox *temp = new QCheckBox(gameTypesIterator.value()); + temp->setChecked( + settings.value( + "game_type/" + hashGameType(gameTypesIterator.value()), + false + ).toBool() + ); + + gameTypeFilterCheckBoxes.insert(gameTypesIterator.key(), temp); + gameTypeFilterLayout->addWidget(temp); + } + QGroupBox *gameTypeFilterGroupBox; + if (!allGameTypes.isEmpty()) { + gameTypeFilterGroupBox = new QGroupBox(tr("&Game types")); + gameTypeFilterGroupBox->setLayout(gameTypeFilterLayout); + } else + gameTypeFilterGroupBox = 0; + + QLabel *maxPlayersFilterMinLabel = new QLabel(tr("at &least:")); + maxPlayersFilterMinSpinBox = new QSpinBox; + maxPlayersFilterMinSpinBox->setMinimum(1); + maxPlayersFilterMinSpinBox->setMaximum(99); + maxPlayersFilterMinSpinBox->setValue( + settings.value("min_players", 1).toInt() + ); + maxPlayersFilterMinLabel->setBuddy(maxPlayersFilterMinSpinBox); + + QLabel *maxPlayersFilterMaxLabel = new QLabel(tr("at &most:")); + maxPlayersFilterMaxSpinBox = new QSpinBox; + maxPlayersFilterMaxSpinBox->setMinimum(1); + maxPlayersFilterMaxSpinBox->setMaximum(99); + maxPlayersFilterMaxSpinBox->setValue( + settings.value("max_players", 99).toInt() + ); + maxPlayersFilterMaxLabel->setBuddy(maxPlayersFilterMaxSpinBox); + + QGridLayout *maxPlayersFilterLayout = new QGridLayout; + maxPlayersFilterLayout->addWidget(maxPlayersFilterMinLabel, 0, 0); + maxPlayersFilterLayout->addWidget(maxPlayersFilterMinSpinBox, 0, 1); + maxPlayersFilterLayout->addWidget(maxPlayersFilterMaxLabel, 1, 0); + maxPlayersFilterLayout->addWidget(maxPlayersFilterMaxSpinBox, 1, 1); + + QGroupBox *maxPlayersGroupBox = new QGroupBox(tr("Maximum player count")); + maxPlayersGroupBox->setLayout(maxPlayersFilterLayout); + + QGridLayout *leftGrid = new QGridLayout; + leftGrid->addWidget(gameNameFilterLabel, 0, 0); + leftGrid->addWidget(gameNameFilterEdit, 0, 1); + leftGrid->addWidget(creatorNameFilterLabel, 1, 0); + leftGrid->addWidget(creatorNameFilterEdit, 1, 1); + leftGrid->addWidget(maxPlayersGroupBox, 2, 0, 1, 2); + leftGrid->addWidget(unavailableGamesVisibleCheckBox, 3, 0, 1, 2); + leftGrid->addWidget(passwordProtectedGamesVisibleCheckBox, 4, 0, 1, 2); + + QVBoxLayout *leftColumn = new QVBoxLayout; + leftColumn->addLayout(leftGrid); + leftColumn->addStretch(); + + QVBoxLayout *rightColumn = new QVBoxLayout; + rightColumn->addWidget(gameTypeFilterGroupBox); + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addLayout(leftColumn); + hbox->addLayout(rightColumn); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(hbox); + mainLayout->addWidget(buttonBox); + + setLayout(mainLayout); + setWindowTitle(tr("Filter games")); +} + +void DlgFilterGames::actOk() { + QSettings settings; + settings.beginGroup("filter_games"); + settings.setValue( + "unavailable_games_visible", + unavailableGamesVisibleCheckBox->isChecked() + ); + settings.setValue( + "password_protected_games_visible", + passwordProtectedGamesVisibleCheckBox->isChecked() + ); + settings.setValue("game_name_filter", gameNameFilterEdit->text()); + settings.setValue("creator_name_filter", creatorNameFilterEdit->text()); + + QMapIterator gameTypeIterator(allGameTypes); + QMapIterator checkboxIterator(gameTypeFilterCheckBoxes); + while (gameTypeIterator.hasNext()) { + gameTypeIterator.next(); + checkboxIterator.next(); + + settings.setValue( + "game_type/" + hashGameType(gameTypeIterator.value()), + checkboxIterator.value()->isChecked() + ); + } + + settings.setValue("min_players", maxPlayersFilterMinSpinBox->value()); + settings.setValue("max_players", maxPlayersFilterMaxSpinBox->value()); + + accept(); +} + +QString DlgFilterGames::hashGameType(const QString &gameType) const { + return QCryptographicHash::hash(gameType.toUtf8(), QCryptographicHash::Md5).toHex(); +} + +bool DlgFilterGames::getUnavailableGamesVisible() const +{ + return unavailableGamesVisibleCheckBox->isChecked(); +} + +void DlgFilterGames::setUnavailableGamesVisible(bool _unavailableGamesVisible) +{ + unavailableGamesVisibleCheckBox->setChecked(_unavailableGamesVisible); +} + +bool DlgFilterGames::getPasswordProtectedGamesVisible() const +{ + return passwordProtectedGamesVisibleCheckBox->isChecked(); +} + +void DlgFilterGames::setPasswordProtectedGamesVisible(bool _passwordProtectedGamesVisible) +{ + passwordProtectedGamesVisibleCheckBox->setChecked(_passwordProtectedGamesVisible); +} + +QString DlgFilterGames::getGameNameFilter() const +{ + return gameNameFilterEdit->text(); +} + +void DlgFilterGames::setGameNameFilter(const QString &_gameNameFilter) +{ + gameNameFilterEdit->setText(_gameNameFilter); +} + +QString DlgFilterGames::getCreatorNameFilter() const +{ + return creatorNameFilterEdit->text(); +} + +void DlgFilterGames::setCreatorNameFilter(const QString &_creatorNameFilter) +{ + creatorNameFilterEdit->setText(_creatorNameFilter); +} + +QSet DlgFilterGames::getGameTypeFilter() const +{ + QSet result; + QMapIterator i(gameTypeFilterCheckBoxes); + while (i.hasNext()) { + i.next(); + if (i.value()->isChecked()) + result.insert(i.key()); + } + return result; +} + +void DlgFilterGames::setGameTypeFilter(const QSet &_gameTypeFilter) +{ + QMapIterator i(gameTypeFilterCheckBoxes); + while (i.hasNext()) { + i.next(); + i.value()->setChecked(_gameTypeFilter.contains(i.key())); + } +} + +int DlgFilterGames::getMaxPlayersFilterMin() const +{ + return maxPlayersFilterMinSpinBox->value(); +} + +int DlgFilterGames::getMaxPlayersFilterMax() const +{ + return maxPlayersFilterMaxSpinBox->value(); +} + +void DlgFilterGames::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax) +{ + maxPlayersFilterMinSpinBox->setValue(_maxPlayersFilterMin); + maxPlayersFilterMaxSpinBox->setValue(_maxPlayersFilterMax == -1 ? maxPlayersFilterMaxSpinBox->maximum() : _maxPlayersFilterMax); +} diff --git a/cockatrice/src/dlg_filter_games.h b/cockatrice/src/dlg_filter_games.h new file mode 100644 index 000000000..2480e642f --- /dev/null +++ b/cockatrice/src/dlg_filter_games.h @@ -0,0 +1,50 @@ +#ifndef DLG_FILTER_GAMES_H +#define DLG_FILTER_GAMES_H + +#include +#include +#include + +class QCheckBox; +class QLineEdit; +class QSpinBox; + +class DlgFilterGames : public QDialog { + Q_OBJECT +private: + QCheckBox *unavailableGamesVisibleCheckBox; + QCheckBox *passwordProtectedGamesVisibleCheckBox; + QLineEdit *gameNameFilterEdit; + QLineEdit *creatorNameFilterEdit; + QMap gameTypeFilterCheckBoxes; + QSpinBox *maxPlayersFilterMinSpinBox; + QSpinBox *maxPlayersFilterMaxSpinBox; + + const QMap &allGameTypes; + + /* + * The game type might contain special characters, so to use it in + * QSettings we just hash it. + */ + QString hashGameType(const QString &gameType) const; +private slots: + void actOk(); +public: + DlgFilterGames(const QMap &allGameTypes, QWidget *parent = 0); + + bool getUnavailableGamesVisible() const; + void setUnavailableGamesVisible(bool _unavailableGamesVisible); + bool getPasswordProtectedGamesVisible() const; + void setPasswordProtectedGamesVisible(bool _passwordProtectedGamesVisible); + QString getGameNameFilter() const; + void setGameNameFilter(const QString &_gameNameFilter); + QString getCreatorNameFilter() const; + void setCreatorNameFilter(const QString &_creatorNameFilter); + QSet getGameTypeFilter() const; + void setGameTypeFilter(const QSet &_gameTypeFilter); + int getMaxPlayersFilterMin() const; + int getMaxPlayersFilterMax() const; + void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); +}; + +#endif diff --git a/cockatrice/src/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/dlg_load_deck_from_clipboard.cpp index 367415904..3eb3428f4 100644 --- a/cockatrice/src/dlg_load_deck_from_clipboard.cpp +++ b/cockatrice/src/dlg_load_deck_from_clipboard.cpp @@ -6,59 +6,53 @@ #include #include #include +#include #include #include "dlg_load_deck_from_clipboard.h" -#include "decklist.h" +#include "deck_loader.h" DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) - : QDialog(parent), deckList(0) + : QDialog(parent), deckList(0) { - contentsEdit = new QPlainTextEdit; - - refreshButton = new QPushButton(tr("&Refresh")); - refreshButton->setShortcut(QKeySequence("F5")); - okButton = new QPushButton(tr("&OK")); - okButton->setDefault(true); - cancelButton = new QPushButton(tr("&Cancel")); + contentsEdit = new QPlainTextEdit; + + refreshButton = new QPushButton(tr("&Refresh")); + refreshButton->setShortcut(QKeySequence("F5")); + connect(refreshButton, SIGNAL(clicked()), this, SLOT(actRefresh())); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttonBox->addButton(refreshButton, QDialogButtonBox::ActionRole); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOK())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(contentsEdit); + mainLayout->addWidget(buttonBox); - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addWidget(refreshButton); - buttonLayout->addStretch(); - buttonLayout->addWidget(okButton); - buttonLayout->addWidget(cancelButton); + setLayout(mainLayout); - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(contentsEdit); - mainLayout->addLayout(buttonLayout); - - setLayout(mainLayout); - - setWindowTitle(tr("Load deck from clipboard")); - resize(500, 500); - - connect(refreshButton, SIGNAL(clicked()), this, SLOT(actRefresh())); - connect(okButton, SIGNAL(clicked()), this, SLOT(actOK())); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); - - actRefresh(); + setWindowTitle(tr("Load deck from clipboard")); + resize(500, 500); + + actRefresh(); } void DlgLoadDeckFromClipboard::actRefresh() { - contentsEdit->setPlainText(QApplication::clipboard()->text()); + contentsEdit->setPlainText(QApplication::clipboard()->text()); } void DlgLoadDeckFromClipboard::actOK() { - QString buffer = contentsEdit->toPlainText(); - QTextStream stream(&buffer); - - DeckList *l = new DeckList; - if (l->loadFromStream_Plain(stream)) { - deckList = l; - accept(); - } else { - QMessageBox::critical(this, tr("Error"), tr("Invalid deck list.")); - delete l; - } + QString buffer = contentsEdit->toPlainText(); + QTextStream stream(&buffer); + + DeckLoader *l = new DeckLoader; + if (l->loadFromStream_Plain(stream)) { + deckList = l; + accept(); + } else { + QMessageBox::critical(this, tr("Error"), tr("Invalid deck list.")); + delete l; + } } diff --git a/cockatrice/src/dlg_load_deck_from_clipboard.h b/cockatrice/src/dlg_load_deck_from_clipboard.h index f3db6ce67..375c713de 100644 --- a/cockatrice/src/dlg_load_deck_from_clipboard.h +++ b/cockatrice/src/dlg_load_deck_from_clipboard.h @@ -3,23 +3,23 @@ #include -class DeckList; +class DeckLoader; class QPlainTextEdit; class QPushButton; class DlgLoadDeckFromClipboard : public QDialog { - Q_OBJECT + Q_OBJECT private slots: - void actOK(); - void actRefresh(); + void actOK(); + void actRefresh(); private: - DeckList *deckList; + DeckLoader *deckList; public: - DlgLoadDeckFromClipboard(QWidget *parent = 0); - DeckList *getDeckList() const { return deckList; } + DlgLoadDeckFromClipboard(QWidget *parent = 0); + DeckLoader *getDeckList() const { return deckList; } private: - QPlainTextEdit *contentsEdit; - QPushButton *refreshButton, *okButton, *cancelButton; + QPlainTextEdit *contentsEdit; + QPushButton *refreshButton; }; #endif diff --git a/cockatrice/src/dlg_load_remote_deck.cpp b/cockatrice/src/dlg_load_remote_deck.cpp index 10c383794..09b820ac3 100644 --- a/cockatrice/src/dlg_load_remote_deck.cpp +++ b/cockatrice/src/dlg_load_remote_deck.cpp @@ -1,47 +1,40 @@ #include #include #include +#include #include "remotedecklist_treewidget.h" #include "dlg_load_remote_deck.h" #include "main.h" DlgLoadRemoteDeck::DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent) - : QDialog(parent), client(_client) + : QDialog(parent), client(_client) { - dirView = new RemoteDeckList_TreeWidget(client); + dirView = new RemoteDeckList_TreeWidget(client); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - okButton = new QPushButton(tr("O&K")); - okButton->setDefault(true); - okButton->setAutoDefault(true); - okButton->setEnabled(false); - cancelButton = new QPushButton(tr("&Cancel")); + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(dirView); + mainLayout->addWidget(buttonBox); - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(); - buttonLayout->addWidget(okButton); - buttonLayout->addWidget(cancelButton); + setLayout(mainLayout); - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(dirView); - mainLayout->addLayout(buttonLayout); + setWindowTitle(tr("Load deck")); + setMinimumWidth(sizeHint().width()); + resize(400, 600); - setLayout(mainLayout); - - setWindowTitle(tr("Load deck")); - setMinimumWidth(sizeHint().width()); - resize(300, 500); - - connect(dirView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(currentItemChanged(const QModelIndex &, const QModelIndex &))); - connect(okButton, SIGNAL(clicked()), this, SLOT(accept())); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + connect(dirView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(currentItemChanged(const QModelIndex &, const QModelIndex &))); } void DlgLoadRemoteDeck::currentItemChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) { - okButton->setEnabled(dynamic_cast(dirView->getNode(current))); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(dynamic_cast(dirView->getNode(current))); } int DlgLoadRemoteDeck::getDeckId() const { - return dynamic_cast(dirView->getNode(dirView->selectionModel()->currentIndex()))->getId(); + return dynamic_cast(dirView->getNode(dirView->selectionModel()->currentIndex()))->getId(); } diff --git a/cockatrice/src/dlg_load_remote_deck.h b/cockatrice/src/dlg_load_remote_deck.h index ae57aaa51..5fd782581 100644 --- a/cockatrice/src/dlg_load_remote_deck.h +++ b/cockatrice/src/dlg_load_remote_deck.h @@ -7,18 +7,19 @@ class RemoteDeckList_TreeWidget; class QModelIndex; class AbstractClient; class QPushButton; +class QDialogButtonBox; class DlgLoadRemoteDeck: public QDialog { - Q_OBJECT + Q_OBJECT private: - AbstractClient *client; - RemoteDeckList_TreeWidget *dirView; - QPushButton *okButton, *cancelButton; + AbstractClient *client; + RemoteDeckList_TreeWidget *dirView; + QDialogButtonBox *buttonBox; private slots: - void currentItemChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentItemChanged(const QModelIndex ¤t, const QModelIndex &previous); public: - DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent = 0); - int getDeckId() const; + DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent = 0); + int getDeckId() const; }; #endif diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 43a643f1a..414550a9e 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -17,668 +17,837 @@ #include #include #include +#include +#include +#include #include "carddatabase.h" #include "dlg_settings.h" #include "main.h" #include "settingscache.h" +#include "priceupdater.h" GeneralSettingsPage::GeneralSettingsPage() { - languageLabel = new QLabel; - languageBox = new QComboBox; - - QString setLanguage = settingsCache->getLang(); - QStringList qmFiles = findQmFiles(); - for (int i = 0; i < qmFiles.size(); i++) { - QString langName = languageName(qmFiles[i]); - languageBox->addItem(langName, qmFiles[i]); - if ((qmFiles[i] == setLanguage) || (setLanguage.isEmpty() && langName == tr("English"))) - languageBox->setCurrentIndex(i); - } - - picDownloadCheckBox = new QCheckBox; - picDownloadCheckBox->setChecked(settingsCache->getPicDownload()); - - connect(languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); - connect(picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int))); - - QGridLayout *personalGrid = new QGridLayout; - personalGrid->addWidget(languageLabel, 0, 0); - personalGrid->addWidget(languageBox, 0, 1); - personalGrid->addWidget(picDownloadCheckBox, 1, 0, 1, 2); - - personalGroupBox = new QGroupBox; - personalGroupBox->setLayout(personalGrid); - - deckPathLabel = new QLabel; - deckPathEdit = new QLineEdit(settingsCache->getDeckPath()); - deckPathEdit->setReadOnly(true); - QPushButton *deckPathButton = new QPushButton("..."); - connect(deckPathButton, SIGNAL(clicked()), this, SLOT(deckPathButtonClicked())); - - picsPathLabel = new QLabel; - picsPathEdit = new QLineEdit(settingsCache->getPicsPath()); - picsPathEdit->setReadOnly(true); - QPushButton *picsPathButton = new QPushButton("..."); - connect(picsPathButton, SIGNAL(clicked()), this, SLOT(picsPathButtonClicked())); - - cardDatabasePathLabel = new QLabel; - cardDatabasePathEdit = new QLineEdit(settingsCache->getCardDatabasePath()); - cardDatabasePathEdit->setReadOnly(true); - QPushButton *cardDatabasePathButton = new QPushButton("..."); - connect(cardDatabasePathButton, SIGNAL(clicked()), this, SLOT(cardDatabasePathButtonClicked())); - - QGridLayout *pathsGrid = new QGridLayout; - pathsGrid->addWidget(deckPathLabel, 0, 0); - pathsGrid->addWidget(deckPathEdit, 0, 1); - pathsGrid->addWidget(deckPathButton, 0, 2); - pathsGrid->addWidget(picsPathLabel, 1, 0); - pathsGrid->addWidget(picsPathEdit, 1, 1); - pathsGrid->addWidget(picsPathButton, 1, 2); - pathsGrid->addWidget(cardDatabasePathLabel, 2, 0); - pathsGrid->addWidget(cardDatabasePathEdit, 2, 1); - pathsGrid->addWidget(cardDatabasePathButton, 2, 2); - pathsGroupBox = new QGroupBox; - pathsGroupBox->setLayout(pathsGrid); + languageLabel = new QLabel; + languageBox = new QComboBox; - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(personalGroupBox); - mainLayout->addWidget(pathsGroupBox); - - setLayout(mainLayout); + QString setLanguage = settingsCache->getLang(); + QStringList qmFiles = findQmFiles(); + for (int i = 0; i < qmFiles.size(); i++) { + QString langName = languageName(qmFiles[i]); + languageBox->addItem(langName, qmFiles[i]); + if ((qmFiles[i] == setLanguage) || (setLanguage.isEmpty() && langName == tr("English"))) + languageBox->setCurrentIndex(i); + } + + picDownloadCheckBox = new QCheckBox; + picDownloadCheckBox->setChecked(settingsCache->getPicDownload()); + + QPushButton *clearDownloadedPicsButton = new QPushButton(tr("Reset/Clear Downloaded Pictures")); + connect(clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked())); + + picDownloadHqCheckBox = new QCheckBox; + picDownloadHqCheckBox->setChecked(settingsCache->getPicDownloadHq()); + + connect(languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); + connect(picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int))); + connect(picDownloadHqCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownloadHq(int))); + + QGridLayout *personalGrid = new QGridLayout; + personalGrid->addWidget(languageLabel, 0, 0); + personalGrid->addWidget(languageBox, 0, 1); + personalGrid->addWidget(picDownloadCheckBox, 1, 0, 1, 2); + personalGrid->addWidget(picDownloadHqCheckBox, 2, 0, 1, 2); + personalGrid->addWidget(clearDownloadedPicsButton, 3, 0, 1, 1); + + personalGroupBox = new QGroupBox; + personalGroupBox->setLayout(personalGrid); + + deckPathLabel = new QLabel; + deckPathEdit = new QLineEdit(settingsCache->getDeckPath()); + deckPathEdit->setReadOnly(true); + QPushButton *deckPathButton = new QPushButton("..."); + connect(deckPathButton, SIGNAL(clicked()), this, SLOT(deckPathButtonClicked())); + + replaysPathLabel = new QLabel; + replaysPathEdit = new QLineEdit(settingsCache->getReplaysPath()); + replaysPathEdit->setReadOnly(true); + QPushButton *replaysPathButton = new QPushButton("..."); + connect(replaysPathButton, SIGNAL(clicked()), this, SLOT(replaysPathButtonClicked())); + + picsPathLabel = new QLabel; + picsPathEdit = new QLineEdit(settingsCache->getPicsPath()); + picsPathEdit->setReadOnly(true); + QPushButton *picsPathButton = new QPushButton("..."); + connect(picsPathButton, SIGNAL(clicked()), this, SLOT(picsPathButtonClicked())); + + cardDatabasePathLabel = new QLabel; + cardDatabasePathEdit = new QLineEdit(settingsCache->getCardDatabasePath()); + cardDatabasePathEdit->setReadOnly(true); + QPushButton *cardDatabasePathButton = new QPushButton("..."); + connect(cardDatabasePathButton, SIGNAL(clicked()), this, SLOT(cardDatabasePathButtonClicked())); + + tokenDatabasePathLabel = new QLabel; + tokenDatabasePathEdit = new QLineEdit(settingsCache->getTokenDatabasePath()); + tokenDatabasePathEdit->setReadOnly(true); + QPushButton *tokenDatabasePathButton = new QPushButton("..."); + connect(tokenDatabasePathButton, SIGNAL(clicked()), this, SLOT(tokenDatabasePathButtonClicked())); + + QGridLayout *pathsGrid = new QGridLayout; + pathsGrid->addWidget(deckPathLabel, 0, 0); + pathsGrid->addWidget(deckPathEdit, 0, 1); + pathsGrid->addWidget(deckPathButton, 0, 2); + pathsGrid->addWidget(replaysPathLabel, 1, 0); + pathsGrid->addWidget(replaysPathEdit, 1, 1); + pathsGrid->addWidget(replaysPathButton, 1, 2); + pathsGrid->addWidget(picsPathLabel, 2, 0); + pathsGrid->addWidget(picsPathEdit, 2, 1); + pathsGrid->addWidget(picsPathButton, 2, 2); + pathsGrid->addWidget(cardDatabasePathLabel, 3, 0); + pathsGrid->addWidget(cardDatabasePathEdit, 3, 1); + pathsGrid->addWidget(cardDatabasePathButton, 3, 2); + pathsGrid->addWidget(tokenDatabasePathLabel, 4, 0); + pathsGrid->addWidget(tokenDatabasePathEdit, 4, 1); + pathsGrid->addWidget(tokenDatabasePathButton, 4, 2); + pathsGroupBox = new QGroupBox; + pathsGroupBox->setLayout(pathsGrid); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(personalGroupBox); + mainLayout->addWidget(pathsGroupBox); + + setLayout(mainLayout); } QStringList GeneralSettingsPage::findQmFiles() { - QDir dir(":/translations"); - QStringList fileNames = dir.entryList(QStringList(translationPrefix + "_*.qm"), QDir::Files, QDir::Name); - fileNames.replaceInStrings(QRegExp(translationPrefix + "_(.*)\\.qm"), "\\1"); - return fileNames; + QDir dir(translationPath); + QStringList fileNames = dir.entryList(QStringList(translationPrefix + "_*.qm"), QDir::Files, QDir::Name); + fileNames.replaceInStrings(QRegExp(translationPrefix + "_(.*)\\.qm"), "\\1"); + return fileNames; } QString GeneralSettingsPage::languageName(const QString &qmFile) { - QTranslator translator; - translator.load(":/translations/" + translationPrefix + "_" + qmFile + ".qm"); - - return translator.translate("GeneralSettingsPage", "English"); + QTranslator translator; + translator.load(translationPrefix + "_" + qmFile + ".qm", translationPath); + + return translator.translate("GeneralSettingsPage", "English"); } void GeneralSettingsPage::deckPathButtonClicked() { - QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); - if (path.isEmpty()) - return; - - deckPathEdit->setText(path); - settingsCache->setDeckPath(path); + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); + if (path.isEmpty()) + return; + + deckPathEdit->setText(path); + settingsCache->setDeckPath(path); +} + +void GeneralSettingsPage::replaysPathButtonClicked() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); + if (path.isEmpty()) + return; + + replaysPathEdit->setText(path); + settingsCache->setReplaysPath(path); } void GeneralSettingsPage::picsPathButtonClicked() { - QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); - if (path.isEmpty()) - return; - - picsPathEdit->setText(path); - settingsCache->setPicsPath(path); + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); + if (path.isEmpty()) + return; + + picsPathEdit->setText(path); + settingsCache->setPicsPath(path); +} + +void GeneralSettingsPage::clearDownloadedPicsButtonClicked() +{ + QString picsPath = settingsCache->getPicsPath() + "/downloadedPics/"; + QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + bool outerSuccessRemove = true; + for (int i = 0; i < dirs.length(); i ++) { + QString currentPath = picsPath + dirs.at(i) + "/"; + QStringList files = QDir(currentPath).entryList(QDir::Files); + bool innerSuccessRemove = true; + for (int j = 0; j < files.length(); j ++) + if (!QDir(currentPath).remove(files.at(j))) { + qDebug() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8(); + outerSuccessRemove = false; + innerSuccessRemove = false; + } + if (innerSuccessRemove) + QDir(picsPath).rmdir(dirs.at(i)); + } + if (outerSuccessRemove) + QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset.")); + else + QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared.")); } void GeneralSettingsPage::cardDatabasePathButtonClicked() { - QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); - if (path.isEmpty()) - return; - - cardDatabasePathEdit->setText(path); - settingsCache->setCardDatabasePath(path); + QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); + if (path.isEmpty()) + return; + + cardDatabasePathEdit->setText(path); + settingsCache->setCardDatabasePath(path); +} + +void GeneralSettingsPage::tokenDatabasePathButtonClicked() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); + if (path.isEmpty()) + return; + + tokenDatabasePathEdit->setText(path); + settingsCache->setTokenDatabasePath(path); } void GeneralSettingsPage::languageBoxChanged(int index) { - settingsCache->setLang(languageBox->itemData(index).toString()); + settingsCache->setLang(languageBox->itemData(index).toString()); } void GeneralSettingsPage::retranslateUi() { - personalGroupBox->setTitle(tr("Personal settings")); - languageLabel->setText(tr("Language:")); - picDownloadCheckBox->setText(tr("Download card pictures on the fly")); - pathsGroupBox->setTitle(tr("Paths")); - deckPathLabel->setText(tr("Decks directory:")); - picsPathLabel->setText(tr("Pictures directory:")); - cardDatabasePathLabel->setText(tr("Path to card database:")); + personalGroupBox->setTitle(tr("Personal settings")); + languageLabel->setText(tr("Language:")); + picDownloadCheckBox->setText(tr("Download card pictures on the fly")); + picDownloadHqCheckBox->setText(tr("Download high-quality card pictures")); + pathsGroupBox->setTitle(tr("Paths")); + deckPathLabel->setText(tr("Decks directory:")); + replaysPathLabel->setText(tr("Replays directory:")); + picsPathLabel->setText(tr("Pictures directory:")); + cardDatabasePathLabel->setText(tr("Card database:")); + tokenDatabasePathLabel->setText(tr("Token database:")); } AppearanceSettingsPage::AppearanceSettingsPage() { - QIcon deleteIcon(":/resources/icon_delete.svg"); - - handBgLabel = new QLabel; - handBgEdit = new QLineEdit(settingsCache->getHandBgPath()); - handBgEdit->setReadOnly(true); - QPushButton *handBgClearButton = new QPushButton(deleteIcon, QString()); - connect(handBgClearButton, SIGNAL(clicked()), this, SLOT(handBgClearButtonClicked())); - QPushButton *handBgButton = new QPushButton("..."); - connect(handBgButton, SIGNAL(clicked()), this, SLOT(handBgButtonClicked())); - - stackBgLabel = new QLabel; - stackBgEdit = new QLineEdit(settingsCache->getStackBgPath()); - stackBgEdit->setReadOnly(true); - QPushButton *stackBgClearButton = new QPushButton(deleteIcon, QString()); - connect(stackBgClearButton, SIGNAL(clicked()), this, SLOT(stackBgClearButtonClicked())); - QPushButton *stackBgButton = new QPushButton("..."); - connect(stackBgButton, SIGNAL(clicked()), this, SLOT(stackBgButtonClicked())); + QIcon deleteIcon(":/resources/icon_delete.svg"); + + handBgLabel = new QLabel; + handBgEdit = new QLineEdit(settingsCache->getHandBgPath()); + handBgEdit->setReadOnly(true); + QPushButton *handBgClearButton = new QPushButton(deleteIcon, QString()); + connect(handBgClearButton, SIGNAL(clicked()), this, SLOT(handBgClearButtonClicked())); + QPushButton *handBgButton = new QPushButton("..."); + connect(handBgButton, SIGNAL(clicked()), this, SLOT(handBgButtonClicked())); + + stackBgLabel = new QLabel; + stackBgEdit = new QLineEdit(settingsCache->getStackBgPath()); + stackBgEdit->setReadOnly(true); + QPushButton *stackBgClearButton = new QPushButton(deleteIcon, QString()); + connect(stackBgClearButton, SIGNAL(clicked()), this, SLOT(stackBgClearButtonClicked())); + QPushButton *stackBgButton = new QPushButton("..."); + connect(stackBgButton, SIGNAL(clicked()), this, SLOT(stackBgButtonClicked())); - tableBgLabel = new QLabel; - tableBgEdit = new QLineEdit(settingsCache->getTableBgPath()); - tableBgEdit->setReadOnly(true); - QPushButton *tableBgClearButton = new QPushButton(deleteIcon, QString()); - connect(tableBgClearButton, SIGNAL(clicked()), this, SLOT(tableBgClearButtonClicked())); - QPushButton *tableBgButton = new QPushButton("..."); - connect(tableBgButton, SIGNAL(clicked()), this, SLOT(tableBgButtonClicked())); - - playerAreaBgLabel = new QLabel; - playerAreaBgEdit = new QLineEdit(settingsCache->getPlayerBgPath()); - playerAreaBgEdit->setReadOnly(true); - QPushButton *playerAreaBgClearButton = new QPushButton(deleteIcon, QString()); - connect(playerAreaBgClearButton, SIGNAL(clicked()), this, SLOT(playerAreaBgClearButtonClicked())); - QPushButton *playerAreaBgButton = new QPushButton("..."); - connect(playerAreaBgButton, SIGNAL(clicked()), this, SLOT(playerAreaBgButtonClicked())); - - cardBackPicturePathLabel = new QLabel; - cardBackPicturePathEdit = new QLineEdit(settingsCache->getCardBackPicturePath()); - cardBackPicturePathEdit->setReadOnly(true); - QPushButton *cardBackPicturePathClearButton = new QPushButton(deleteIcon, QString()); - connect(cardBackPicturePathClearButton, SIGNAL(clicked()), this, SLOT(cardBackPicturePathClearButtonClicked())); - QPushButton *cardBackPicturePathButton = new QPushButton("..."); - connect(cardBackPicturePathButton, SIGNAL(clicked()), this, SLOT(cardBackPicturePathButtonClicked())); - - QGridLayout *zoneBgGrid = new QGridLayout; - zoneBgGrid->addWidget(handBgLabel, 0, 0); - zoneBgGrid->addWidget(handBgEdit, 0, 1); - zoneBgGrid->addWidget(handBgClearButton, 0, 2); - zoneBgGrid->addWidget(handBgButton, 0, 3); - zoneBgGrid->addWidget(stackBgLabel, 1, 0); - zoneBgGrid->addWidget(stackBgEdit, 1, 1); - zoneBgGrid->addWidget(stackBgClearButton, 1, 2); - zoneBgGrid->addWidget(stackBgButton, 1, 3); - zoneBgGrid->addWidget(tableBgLabel, 2, 0); - zoneBgGrid->addWidget(tableBgEdit, 2, 1); - zoneBgGrid->addWidget(tableBgClearButton, 2, 2); - zoneBgGrid->addWidget(tableBgButton, 2, 3); - zoneBgGrid->addWidget(playerAreaBgLabel, 3, 0); - zoneBgGrid->addWidget(playerAreaBgEdit, 3, 1); - zoneBgGrid->addWidget(playerAreaBgClearButton, 3, 2); - zoneBgGrid->addWidget(playerAreaBgButton, 3, 3); - zoneBgGrid->addWidget(cardBackPicturePathLabel, 4, 0); - zoneBgGrid->addWidget(cardBackPicturePathEdit, 4, 1); - zoneBgGrid->addWidget(cardBackPicturePathClearButton, 4, 2); - zoneBgGrid->addWidget(cardBackPicturePathButton, 4, 3); + tableBgLabel = new QLabel; + tableBgEdit = new QLineEdit(settingsCache->getTableBgPath()); + tableBgEdit->setReadOnly(true); + QPushButton *tableBgClearButton = new QPushButton(deleteIcon, QString()); + connect(tableBgClearButton, SIGNAL(clicked()), this, SLOT(tableBgClearButtonClicked())); + QPushButton *tableBgButton = new QPushButton("..."); + connect(tableBgButton, SIGNAL(clicked()), this, SLOT(tableBgButtonClicked())); + + playerAreaBgLabel = new QLabel; + playerAreaBgEdit = new QLineEdit(settingsCache->getPlayerBgPath()); + playerAreaBgEdit->setReadOnly(true); + QPushButton *playerAreaBgClearButton = new QPushButton(deleteIcon, QString()); + connect(playerAreaBgClearButton, SIGNAL(clicked()), this, SLOT(playerAreaBgClearButtonClicked())); + QPushButton *playerAreaBgButton = new QPushButton("..."); + connect(playerAreaBgButton, SIGNAL(clicked()), this, SLOT(playerAreaBgButtonClicked())); + + cardBackPicturePathLabel = new QLabel; + cardBackPicturePathEdit = new QLineEdit(settingsCache->getCardBackPicturePath()); + cardBackPicturePathEdit->setReadOnly(true); + QPushButton *cardBackPicturePathClearButton = new QPushButton(deleteIcon, QString()); + connect(cardBackPicturePathClearButton, SIGNAL(clicked()), this, SLOT(cardBackPicturePathClearButtonClicked())); + QPushButton *cardBackPicturePathButton = new QPushButton("..."); + connect(cardBackPicturePathButton, SIGNAL(clicked()), this, SLOT(cardBackPicturePathButtonClicked())); + + QGridLayout *zoneBgGrid = new QGridLayout; + zoneBgGrid->addWidget(handBgLabel, 0, 0); + zoneBgGrid->addWidget(handBgEdit, 0, 1); + zoneBgGrid->addWidget(handBgClearButton, 0, 2); + zoneBgGrid->addWidget(handBgButton, 0, 3); + zoneBgGrid->addWidget(stackBgLabel, 1, 0); + zoneBgGrid->addWidget(stackBgEdit, 1, 1); + zoneBgGrid->addWidget(stackBgClearButton, 1, 2); + zoneBgGrid->addWidget(stackBgButton, 1, 3); + zoneBgGrid->addWidget(tableBgLabel, 2, 0); + zoneBgGrid->addWidget(tableBgEdit, 2, 1); + zoneBgGrid->addWidget(tableBgClearButton, 2, 2); + zoneBgGrid->addWidget(tableBgButton, 2, 3); + zoneBgGrid->addWidget(playerAreaBgLabel, 3, 0); + zoneBgGrid->addWidget(playerAreaBgEdit, 3, 1); + zoneBgGrid->addWidget(playerAreaBgClearButton, 3, 2); + zoneBgGrid->addWidget(playerAreaBgButton, 3, 3); + zoneBgGrid->addWidget(cardBackPicturePathLabel, 4, 0); + zoneBgGrid->addWidget(cardBackPicturePathEdit, 4, 1); + zoneBgGrid->addWidget(cardBackPicturePathClearButton, 4, 2); + zoneBgGrid->addWidget(cardBackPicturePathButton, 4, 3); - zoneBgGroupBox = new QGroupBox; - zoneBgGroupBox->setLayout(zoneBgGrid); + zoneBgGroupBox = new QGroupBox; + zoneBgGroupBox->setLayout(zoneBgGrid); - displayCardNamesCheckBox = new QCheckBox; - displayCardNamesCheckBox->setChecked(settingsCache->getDisplayCardNames()); - connect(displayCardNamesCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setDisplayCardNames(int))); - - QGridLayout *cardsGrid = new QGridLayout; - cardsGrid->addWidget(displayCardNamesCheckBox, 0, 0, 1, 2); - - cardsGroupBox = new QGroupBox; - cardsGroupBox->setLayout(cardsGrid); - - horizontalHandCheckBox = new QCheckBox; - horizontalHandCheckBox->setChecked(settingsCache->getHorizontalHand()); - connect(horizontalHandCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setHorizontalHand(int))); - - QGridLayout *handGrid = new QGridLayout; - handGrid->addWidget(horizontalHandCheckBox, 0, 0, 1, 2); - - handGroupBox = new QGroupBox; - handGroupBox->setLayout(handGrid); - - invertVerticalCoordinateCheckBox = new QCheckBox; - invertVerticalCoordinateCheckBox->setChecked(settingsCache->getInvertVerticalCoordinate()); - connect(invertVerticalCoordinateCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setInvertVerticalCoordinate(int))); - - minPlayersForMultiColumnLayoutLabel = new QLabel; - minPlayersForMultiColumnLayoutEdit = new QSpinBox; - minPlayersForMultiColumnLayoutEdit->setMinimum(2); - minPlayersForMultiColumnLayoutEdit->setValue(settingsCache->getMinPlayersForMultiColumnLayout()); - connect(minPlayersForMultiColumnLayoutEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setMinPlayersForMultiColumnLayout(int))); - minPlayersForMultiColumnLayoutLabel->setBuddy(minPlayersForMultiColumnLayoutEdit); - - QGridLayout *tableGrid = new QGridLayout; - tableGrid->addWidget(invertVerticalCoordinateCheckBox, 0, 0, 1, 2); - tableGrid->addWidget(minPlayersForMultiColumnLayoutLabel, 1, 0, 1, 1); - tableGrid->addWidget(minPlayersForMultiColumnLayoutEdit, 1, 1, 1, 1); - - tableGroupBox = new QGroupBox; - tableGroupBox->setLayout(tableGrid); - - zoneViewSortByNameCheckBox = new QCheckBox; - zoneViewSortByNameCheckBox->setChecked(settingsCache->getZoneViewSortByName()); - connect(zoneViewSortByNameCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setZoneViewSortByName(int))); - zoneViewSortByTypeCheckBox = new QCheckBox; - zoneViewSortByTypeCheckBox->setChecked(settingsCache->getZoneViewSortByType()); - connect(zoneViewSortByTypeCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setZoneViewSortByType(int))); + displayCardNamesCheckBox = new QCheckBox; + displayCardNamesCheckBox->setChecked(settingsCache->getDisplayCardNames()); + connect(displayCardNamesCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setDisplayCardNames(int))); + + QGridLayout *cardsGrid = new QGridLayout; + cardsGrid->addWidget(displayCardNamesCheckBox, 0, 0, 1, 2); + + cardsGroupBox = new QGroupBox; + cardsGroupBox->setLayout(cardsGrid); + + horizontalHandCheckBox = new QCheckBox; + horizontalHandCheckBox->setChecked(settingsCache->getHorizontalHand()); + connect(horizontalHandCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setHorizontalHand(int))); + + QGridLayout *handGrid = new QGridLayout; + handGrid->addWidget(horizontalHandCheckBox, 0, 0, 1, 2); + + handGroupBox = new QGroupBox; + handGroupBox->setLayout(handGrid); + + invertVerticalCoordinateCheckBox = new QCheckBox; + invertVerticalCoordinateCheckBox->setChecked(settingsCache->getInvertVerticalCoordinate()); + connect(invertVerticalCoordinateCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setInvertVerticalCoordinate(int))); + + minPlayersForMultiColumnLayoutLabel = new QLabel; + minPlayersForMultiColumnLayoutEdit = new QSpinBox; + minPlayersForMultiColumnLayoutEdit->setMinimum(2); + minPlayersForMultiColumnLayoutEdit->setValue(settingsCache->getMinPlayersForMultiColumnLayout()); + connect(minPlayersForMultiColumnLayoutEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setMinPlayersForMultiColumnLayout(int))); + minPlayersForMultiColumnLayoutLabel->setBuddy(minPlayersForMultiColumnLayoutEdit); + + QGridLayout *tableGrid = new QGridLayout; + tableGrid->addWidget(invertVerticalCoordinateCheckBox, 0, 0, 1, 2); + tableGrid->addWidget(minPlayersForMultiColumnLayoutLabel, 1, 0, 1, 1); + tableGrid->addWidget(minPlayersForMultiColumnLayoutEdit, 1, 1, 1, 1); + + tableGroupBox = new QGroupBox; + tableGroupBox->setLayout(tableGrid); + + zoneViewSortByNameCheckBox = new QCheckBox; + zoneViewSortByNameCheckBox->setChecked(settingsCache->getZoneViewSortByName()); + connect(zoneViewSortByNameCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setZoneViewSortByName(int))); + zoneViewSortByTypeCheckBox = new QCheckBox; + zoneViewSortByTypeCheckBox->setChecked(settingsCache->getZoneViewSortByType()); + connect(zoneViewSortByTypeCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setZoneViewSortByType(int))); - QGridLayout *zoneViewGrid = new QGridLayout; - zoneViewGrid->addWidget(zoneViewSortByNameCheckBox, 0, 0, 1, 2); - zoneViewGrid->addWidget(zoneViewSortByTypeCheckBox, 1, 0, 1, 2); + QGridLayout *zoneViewGrid = new QGridLayout; + zoneViewGrid->addWidget(zoneViewSortByNameCheckBox, 0, 0, 1, 2); + zoneViewGrid->addWidget(zoneViewSortByTypeCheckBox, 1, 0, 1, 2); - zoneViewGroupBox = new QGroupBox; - zoneViewGroupBox->setLayout(zoneViewGrid); - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(zoneBgGroupBox); - mainLayout->addWidget(cardsGroupBox); - mainLayout->addWidget(handGroupBox); - mainLayout->addWidget(tableGroupBox); - mainLayout->addWidget(zoneViewGroupBox); - - setLayout(mainLayout); + zoneViewGroupBox = new QGroupBox; + zoneViewGroupBox->setLayout(zoneViewGrid); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(zoneBgGroupBox); + mainLayout->addWidget(cardsGroupBox); + mainLayout->addWidget(handGroupBox); + mainLayout->addWidget(tableGroupBox); + mainLayout->addWidget(zoneViewGroupBox); + + setLayout(mainLayout); } void AppearanceSettingsPage::retranslateUi() { - zoneBgGroupBox->setTitle(tr("Zone background pictures")); - handBgLabel->setText(tr("Path to hand background:")); - stackBgLabel->setText(tr("Path to stack background:")); - tableBgLabel->setText(tr("Path to table background:")); - playerAreaBgLabel->setText(tr("Path to player info background:")); - cardBackPicturePathLabel->setText(tr("Path to picture of card back:")); - - cardsGroupBox->setTitle(tr("Card rendering")); - displayCardNamesCheckBox->setText(tr("Display card names on cards having a picture")); - - handGroupBox->setTitle(tr("Hand layout")); - horizontalHandCheckBox->setText(tr("Display hand horizontally (wastes space)")); - - tableGroupBox->setTitle(tr("Table grid layout")); - invertVerticalCoordinateCheckBox->setText(tr("Invert vertical coordinate")); - minPlayersForMultiColumnLayoutLabel->setText(tr("Minimum player count for multi-column layout:")); - - zoneViewGroupBox->setTitle(tr("Zone view layout")); - zoneViewSortByNameCheckBox->setText(tr("Sort by name")); - zoneViewSortByTypeCheckBox->setText(tr("Sort by type")); + zoneBgGroupBox->setTitle(tr("Zone background pictures")); + handBgLabel->setText(tr("Hand background:")); + stackBgLabel->setText(tr("Stack background:")); + tableBgLabel->setText(tr("Table background:")); + playerAreaBgLabel->setText(tr("Player info background:")); + cardBackPicturePathLabel->setText(tr("Card back:")); + + cardsGroupBox->setTitle(tr("Card rendering")); + displayCardNamesCheckBox->setText(tr("Display card names on cards having a picture")); + + handGroupBox->setTitle(tr("Hand layout")); + horizontalHandCheckBox->setText(tr("Display hand horizontally (wastes space)")); + + tableGroupBox->setTitle(tr("Table grid layout")); + invertVerticalCoordinateCheckBox->setText(tr("Invert vertical coordinate")); + minPlayersForMultiColumnLayoutLabel->setText(tr("Minimum player count for multi-column layout:")); + + zoneViewGroupBox->setTitle(tr("Zone view layout")); + zoneViewSortByNameCheckBox->setText(tr("Sort by name")); + zoneViewSortByTypeCheckBox->setText(tr("Sort by type")); } void AppearanceSettingsPage::handBgClearButtonClicked() { - handBgEdit->setText(QString()); - settingsCache->setHandBgPath(QString()); + handBgEdit->setText(QString()); + settingsCache->setHandBgPath(QString()); } void AppearanceSettingsPage::handBgButtonClicked() { - QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); - if (path.isEmpty()) - return; - - handBgEdit->setText(path); - settingsCache->setHandBgPath(path); + QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); + if (path.isEmpty()) + return; + + handBgEdit->setText(path); + settingsCache->setHandBgPath(path); } void AppearanceSettingsPage::stackBgClearButtonClicked() { - stackBgEdit->setText(QString()); - settingsCache->setStackBgPath(QString()); + stackBgEdit->setText(QString()); + settingsCache->setStackBgPath(QString()); } void AppearanceSettingsPage::stackBgButtonClicked() { - QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); - if (path.isEmpty()) - return; - - stackBgEdit->setText(path); - settingsCache->setStackBgPath(path); + QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); + if (path.isEmpty()) + return; + + stackBgEdit->setText(path); + settingsCache->setStackBgPath(path); } void AppearanceSettingsPage::tableBgClearButtonClicked() { - tableBgEdit->setText(QString()); - settingsCache->setTableBgPath(QString()); + tableBgEdit->setText(QString()); + settingsCache->setTableBgPath(QString()); } void AppearanceSettingsPage::tableBgButtonClicked() { - QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); - if (path.isEmpty()) - return; + QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); + if (path.isEmpty()) + return; - tableBgEdit->setText(path); - settingsCache->setTableBgPath(path); + tableBgEdit->setText(path); + settingsCache->setTableBgPath(path); } void AppearanceSettingsPage::playerAreaBgClearButtonClicked() { - playerAreaBgEdit->setText(QString()); - settingsCache->setPlayerBgPath(QString()); + playerAreaBgEdit->setText(QString()); + settingsCache->setPlayerBgPath(QString()); } void AppearanceSettingsPage::playerAreaBgButtonClicked() { - QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); - if (path.isEmpty()) - return; - - playerAreaBgEdit->setText(path); - settingsCache->setPlayerBgPath(path); + QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); + if (path.isEmpty()) + return; + + playerAreaBgEdit->setText(path); + settingsCache->setPlayerBgPath(path); } void AppearanceSettingsPage::cardBackPicturePathClearButtonClicked() { - cardBackPicturePathEdit->setText(QString()); - settingsCache->setCardBackPicturePath(QString()); + cardBackPicturePathEdit->setText(QString()); + settingsCache->setCardBackPicturePath(QString()); } void AppearanceSettingsPage::cardBackPicturePathButtonClicked() { - QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); - if (path.isEmpty()) - return; - - cardBackPicturePathEdit->setText(path); - settingsCache->setCardBackPicturePath(path); + QString path = QFileDialog::getOpenFileName(this, tr("Choose path")); + if (path.isEmpty()) + return; + + cardBackPicturePathEdit->setText(path); + settingsCache->setCardBackPicturePath(path); } UserInterfaceSettingsPage::UserInterfaceSettingsPage() { - QIcon deleteIcon(":/resources/icon_delete.svg"); + QIcon deleteIcon(":/resources/icon_delete.svg"); - doubleClickToPlayCheckBox = new QCheckBox; - doubleClickToPlayCheckBox->setChecked(settingsCache->getDoubleClickToPlay()); - connect(doubleClickToPlayCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setDoubleClickToPlay(int))); - - QGridLayout *generalGrid = new QGridLayout; - generalGrid->addWidget(doubleClickToPlayCheckBox, 0, 0); - - generalGroupBox = new QGroupBox; - generalGroupBox->setLayout(generalGrid); - - tapAnimationCheckBox = new QCheckBox; - tapAnimationCheckBox->setChecked(settingsCache->getTapAnimation()); - connect(tapAnimationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setTapAnimation(int))); - - soundEnabledCheckBox = new QCheckBox; - soundEnabledCheckBox->setChecked(settingsCache->getSoundEnabled()); - connect(soundEnabledCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setSoundEnabled(int))); - - soundPathLabel = new QLabel; - soundPathEdit = new QLineEdit(settingsCache->getSoundPath()); - soundPathEdit->setReadOnly(true); - QPushButton *soundPathClearButton = new QPushButton(deleteIcon, QString()); - connect(soundPathClearButton, SIGNAL(clicked()), this, SLOT(soundPathClearButtonClicked())); - QPushButton *soundPathButton = new QPushButton("..."); - connect(soundPathButton, SIGNAL(clicked()), this, SLOT(soundPathButtonClicked())); - - QGridLayout *soundGrid = new QGridLayout; - soundGrid->addWidget(soundEnabledCheckBox, 0, 0, 1, 4); - soundGrid->addWidget(soundPathLabel, 1, 0); - soundGrid->addWidget(soundPathEdit, 1, 1); - soundGrid->addWidget(soundPathClearButton, 1, 2); - soundGrid->addWidget(soundPathButton, 1, 3); - - soundGroupBox = new QGroupBox; - soundGroupBox->setLayout(soundGrid); - - QGridLayout *animationGrid = new QGridLayout; - animationGrid->addWidget(tapAnimationCheckBox, 0, 0); - - animationGroupBox = new QGroupBox; - animationGroupBox->setLayout(animationGrid); + notificationsEnabledCheckBox = new QCheckBox; + notificationsEnabledCheckBox->setChecked(settingsCache->getNotificationsEnabled()); + connect(notificationsEnabledCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotificationsEnabled(int))); - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(generalGroupBox); - mainLayout->addWidget(animationGroupBox); - mainLayout->addWidget(soundGroupBox); - - setLayout(mainLayout); + doubleClickToPlayCheckBox = new QCheckBox; + doubleClickToPlayCheckBox->setChecked(settingsCache->getDoubleClickToPlay()); + connect(doubleClickToPlayCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setDoubleClickToPlay(int))); + + playToStackCheckBox = new QCheckBox; + playToStackCheckBox->setChecked(settingsCache->getPlayToStack()); + connect(playToStackCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPlayToStack(int))); + + QGridLayout *generalGrid = new QGridLayout; + generalGrid->addWidget(notificationsEnabledCheckBox, 0, 0); + generalGrid->addWidget(doubleClickToPlayCheckBox, 1, 0); + generalGrid->addWidget(playToStackCheckBox, 2, 0); + + generalGroupBox = new QGroupBox; + generalGroupBox->setLayout(generalGrid); + + tapAnimationCheckBox = new QCheckBox; + tapAnimationCheckBox->setChecked(settingsCache->getTapAnimation()); + connect(tapAnimationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setTapAnimation(int))); + + soundEnabledCheckBox = new QCheckBox; + soundEnabledCheckBox->setChecked(settingsCache->getSoundEnabled()); + connect(soundEnabledCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setSoundEnabled(int))); + + soundPathLabel = new QLabel; + soundPathEdit = new QLineEdit(settingsCache->getSoundPath()); + soundPathEdit->setReadOnly(true); + QPushButton *soundPathClearButton = new QPushButton(deleteIcon, QString()); + connect(soundPathClearButton, SIGNAL(clicked()), this, SLOT(soundPathClearButtonClicked())); + QPushButton *soundPathButton = new QPushButton("..."); + connect(soundPathButton, SIGNAL(clicked()), this, SLOT(soundPathButtonClicked())); + + QGridLayout *soundGrid = new QGridLayout; + soundGrid->addWidget(soundEnabledCheckBox, 0, 0, 1, 4); + soundGrid->addWidget(soundPathLabel, 1, 0); + soundGrid->addWidget(soundPathEdit, 1, 1); + soundGrid->addWidget(soundPathClearButton, 1, 2); + soundGrid->addWidget(soundPathButton, 1, 3); + + soundGroupBox = new QGroupBox; + soundGroupBox->setLayout(soundGrid); + + QGridLayout *animationGrid = new QGridLayout; + animationGrid->addWidget(tapAnimationCheckBox, 0, 0); + + animationGroupBox = new QGroupBox; + animationGroupBox->setLayout(animationGrid); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(generalGroupBox); + mainLayout->addWidget(animationGroupBox); + mainLayout->addWidget(soundGroupBox); + + setLayout(mainLayout); } void UserInterfaceSettingsPage::retranslateUi() { - generalGroupBox->setTitle(tr("General interface settings")); - doubleClickToPlayCheckBox->setText(tr("&Double-click cards to play them (instead of single-click)")); - animationGroupBox->setTitle(tr("Animation settings")); - tapAnimationCheckBox->setText(tr("&Tap/untap animation")); - soundEnabledCheckBox->setText(tr("Enable &sounds")); - soundPathLabel->setText(tr("Path to sounds directory:")); + generalGroupBox->setTitle(tr("General interface settings")); + notificationsEnabledCheckBox->setText(tr("Enable notifications in taskbar")); + doubleClickToPlayCheckBox->setText(tr("&Double-click cards to play them (instead of single-click)")); + playToStackCheckBox->setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); + animationGroupBox->setTitle(tr("Animation settings")); + tapAnimationCheckBox->setText(tr("&Tap/untap animation")); + soundEnabledCheckBox->setText(tr("Enable &sounds")); + soundPathLabel->setText(tr("Path to sounds directory:")); } void UserInterfaceSettingsPage::soundPathClearButtonClicked() { - soundPathEdit->setText(QString()); - settingsCache->setSoundPath(QString()); + soundPathEdit->setText(QString()); + settingsCache->setSoundPath(QString()); } void UserInterfaceSettingsPage::soundPathButtonClicked() { - QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); - if (path.isEmpty()) - return; - - soundPathEdit->setText(path); - settingsCache->setSoundPath(path); + QString path = QFileDialog::getExistingDirectory(this, tr("Choose path")); + if (path.isEmpty()) + return; + + soundPathEdit->setText(path); + settingsCache->setSoundPath(path); } DeckEditorSettingsPage::DeckEditorSettingsPage() { - priceTagsCheckBox = new QCheckBox; - priceTagsCheckBox->setChecked(settingsCache->getPriceTagFeature()); - connect(priceTagsCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPriceTagFeature(int))); - - QGridLayout *generalGrid = new QGridLayout; - generalGrid->addWidget(priceTagsCheckBox, 0, 0); - - generalGroupBox = new QGroupBox; - generalGroupBox->setLayout(generalGrid); - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(generalGroupBox); - - setLayout(mainLayout); + priceTagsCheckBox = new QCheckBox; + priceTagsCheckBox->setChecked(settingsCache->getPriceTagFeature()); + connect(priceTagsCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPriceTagFeature(int))); + + priceTagSource0 = new QRadioButton; + priceTagSource1 = new QRadioButton; + + switch(settingsCache->getPriceTagSource()) + { + case AbstractPriceUpdater::DBPriceSource: + priceTagSource1->setChecked(true); + break; + case AbstractPriceUpdater::BLPPriceSource: + default: + priceTagSource0->setChecked(true); + break; + } + + connect(priceTagSource0, SIGNAL(toggled(bool)), this, SLOT(radioPriceTagSourceClicked(bool))); + connect(priceTagSource1, SIGNAL(toggled(bool)), this, SLOT(radioPriceTagSourceClicked(bool))); + + connect(this, SIGNAL(priceTagSourceChanged(int)), settingsCache, SLOT(setPriceTagSource(int))); + + QGridLayout *generalGrid = new QGridLayout; + generalGrid->addWidget(priceTagsCheckBox, 0, 0); + generalGrid->addWidget(priceTagSource0, 1, 0); + generalGrid->addWidget(priceTagSource1, 2, 0); + + generalGroupBox = new QGroupBox; + generalGroupBox->setLayout(generalGrid); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(generalGroupBox); + + setLayout(mainLayout); } void DeckEditorSettingsPage::retranslateUi() { - priceTagsCheckBox->setText(tr("Enable &price tag feature (using data from blacklotusproject.com)")); - generalGroupBox->setTitle(tr("General")); + priceTagsCheckBox->setText(tr("Enable &price tag feature")); + priceTagSource0->setText(tr("using data from blacklotusproject.com")); + priceTagSource1->setText(tr("using data from deckbrew.com")); + generalGroupBox->setTitle(tr("General")); +} + +void DeckEditorSettingsPage::radioPriceTagSourceClicked(bool checked) +{ + if(!checked) + return; + + int source=AbstractPriceUpdater::BLPPriceSource; + if(priceTagSource0->isChecked()) + source=AbstractPriceUpdater::BLPPriceSource; + if(priceTagSource1->isChecked()) + source=AbstractPriceUpdater::DBPriceSource; + + emit priceTagSourceChanged(source); } MessagesSettingsPage::MessagesSettingsPage() { - aAdd = new QAction(this); - connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd())); - aRemove = new QAction(this); - connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove())); - - messageList = new QListWidget; - QToolBar *messageToolBar = new QToolBar; - messageToolBar->setOrientation(Qt::Vertical); - messageToolBar->addAction(aAdd); - messageToolBar->addAction(aRemove); + aAdd = new QAction(this); + connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd())); + aRemove = new QAction(this); + connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove())); + + messageList = new QListWidget; + QToolBar *messageToolBar = new QToolBar; + messageToolBar->setOrientation(Qt::Vertical); + messageToolBar->addAction(aAdd); + messageToolBar->addAction(aRemove); - QSettings settings; - settings.beginGroup("messages"); - int count = settings.value("count", 0).toInt(); - for (int i = 0; i < count; i++) - messageList->addItem(settings.value(QString("msg%1").arg(i)).toString()); - - QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->addWidget(messageList); - mainLayout->addWidget(messageToolBar); + QSettings settings; + settings.beginGroup("messages"); + int count = settings.value("count", 0).toInt(); + for (int i = 0; i < count; i++) + messageList->addItem(settings.value(QString("msg%1").arg(i)).toString()); + + QHBoxLayout *mainLayout = new QHBoxLayout; + mainLayout->addWidget(messageList); + mainLayout->addWidget(messageToolBar); - setLayout(mainLayout); - - retranslateUi(); + setLayout(mainLayout); + + retranslateUi(); } void MessagesSettingsPage::storeSettings() { - QSettings settings; - settings.beginGroup("messages"); - settings.setValue("count", messageList->count()); - for (int i = 0; i < messageList->count(); i++) - settings.setValue(QString("msg%1").arg(i), messageList->item(i)->text()); + QSettings settings; + settings.beginGroup("messages"); + settings.setValue("count", messageList->count()); + for (int i = 0; i < messageList->count(); i++) + settings.setValue(QString("msg%1").arg(i), messageList->item(i)->text()); } void MessagesSettingsPage::actAdd() { - bool ok; - QString msg = QInputDialog::getText(this, tr("Add message"), tr("Message:"), QLineEdit::Normal, QString(), &ok); - if (ok) { - messageList->addItem(msg); - storeSettings(); - } + bool ok; + QString msg = QInputDialog::getText(this, tr("Add message"), tr("Message:"), QLineEdit::Normal, QString(), &ok); + if (ok) { + messageList->addItem(msg); + storeSettings(); + } } void MessagesSettingsPage::actRemove() { - if (messageList->currentItem()) { - delete messageList->takeItem(messageList->currentRow()); - storeSettings(); - } + if (messageList->currentItem()) { + delete messageList->takeItem(messageList->currentRow()); + storeSettings(); + } } void MessagesSettingsPage::retranslateUi() { - aAdd->setText(tr("&Add")); - aRemove->setText(tr("&Remove")); + aAdd->setText(tr("&Add")); + aRemove->setText(tr("&Remove")); } DlgSettings::DlgSettings(QWidget *parent) - : QDialog(parent) + : QDialog(parent) { - connect(settingsCache, SIGNAL(langChanged()), this, SLOT(updateLanguage())); - - contentsWidget = new QListWidget; - contentsWidget->setViewMode(QListView::IconMode); - contentsWidget->setIconSize(QSize(96, 84)); - contentsWidget->setMovement(QListView::Static); - contentsWidget->setMinimumWidth(130); - contentsWidget->setMaximumWidth(150); - contentsWidget->setSpacing(12); - - pagesWidget = new QStackedWidget; - pagesWidget->addWidget(new GeneralSettingsPage); - pagesWidget->addWidget(new AppearanceSettingsPage); - pagesWidget->addWidget(new UserInterfaceSettingsPage); - pagesWidget->addWidget(new DeckEditorSettingsPage); - pagesWidget->addWidget(new MessagesSettingsPage); - - closeButton = new QPushButton; - connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); - - createIcons(); - contentsWidget->setCurrentRow(0); - - QHBoxLayout *hboxLayout = new QHBoxLayout; - hboxLayout->addWidget(contentsWidget); - hboxLayout->addWidget(pagesWidget); - - QHBoxLayout *buttonsLayout = new QHBoxLayout; - buttonsLayout->addStretch(1); - buttonsLayout->addWidget(closeButton); - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addLayout(hboxLayout); - mainLayout->addSpacing(12); - mainLayout->addLayout(buttonsLayout); - setLayout(mainLayout); - - retranslateUi(); - - resize(800, 450); + connect(settingsCache, SIGNAL(langChanged()), this, SLOT(updateLanguage())); + + contentsWidget = new QListWidget; + contentsWidget->setViewMode(QListView::IconMode); + contentsWidget->setIconSize(QSize(96, 84)); + contentsWidget->setMovement(QListView::Static); + contentsWidget->setMinimumWidth(130); + contentsWidget->setMaximumWidth(150); + contentsWidget->setSpacing(12); + + pagesWidget = new QStackedWidget; + pagesWidget->addWidget(new GeneralSettingsPage); + pagesWidget->addWidget(new AppearanceSettingsPage); + pagesWidget->addWidget(new UserInterfaceSettingsPage); + pagesWidget->addWidget(new DeckEditorSettingsPage); + pagesWidget->addWidget(new MessagesSettingsPage); + + createIcons(); + contentsWidget->setCurrentRow(0); + + QHBoxLayout *hboxLayout = new QHBoxLayout; + hboxLayout->addWidget(contentsWidget); + hboxLayout->addWidget(pagesWidget); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(close())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(hboxLayout); + mainLayout->addSpacing(12); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); + + retranslateUi(); + + resize(800, 450); } void DlgSettings::createIcons() { - generalButton = new QListWidgetItem(contentsWidget); - generalButton->setTextAlignment(Qt::AlignHCenter); - generalButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - generalButton->setIcon(QIcon(":/resources/icon_config_general.svg")); - - appearanceButton = new QListWidgetItem(contentsWidget); - appearanceButton->setTextAlignment(Qt::AlignHCenter); - appearanceButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - appearanceButton->setIcon(QIcon(":/resources/icon_config_appearance.svg")); - - userInterfaceButton = new QListWidgetItem(contentsWidget); - userInterfaceButton->setTextAlignment(Qt::AlignHCenter); - userInterfaceButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - userInterfaceButton->setIcon(QIcon(":/resources/icon_config_interface.svg")); - - deckEditorButton = new QListWidgetItem(contentsWidget); - deckEditorButton->setTextAlignment(Qt::AlignHCenter); - deckEditorButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - deckEditorButton->setIcon(QIcon(":/resources/icon_config_deckeditor.svg")); - - messagesButton = new QListWidgetItem(contentsWidget); - messagesButton->setTextAlignment(Qt::AlignHCenter); - messagesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - messagesButton->setIcon(QIcon(":/resources/icon_config_messages.svg")); - - connect(contentsWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(changePage(QListWidgetItem *, QListWidgetItem *))); + generalButton = new QListWidgetItem(contentsWidget); + generalButton->setTextAlignment(Qt::AlignHCenter); + generalButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + generalButton->setIcon(QIcon(":/resources/icon_config_general.svg")); + + appearanceButton = new QListWidgetItem(contentsWidget); + appearanceButton->setTextAlignment(Qt::AlignHCenter); + appearanceButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + appearanceButton->setIcon(QIcon(":/resources/icon_config_appearance.svg")); + + userInterfaceButton = new QListWidgetItem(contentsWidget); + userInterfaceButton->setTextAlignment(Qt::AlignHCenter); + userInterfaceButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + userInterfaceButton->setIcon(QIcon(":/resources/icon_config_interface.svg")); + + deckEditorButton = new QListWidgetItem(contentsWidget); + deckEditorButton->setTextAlignment(Qt::AlignHCenter); + deckEditorButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + deckEditorButton->setIcon(QIcon(":/resources/icon_config_deckeditor.svg")); + + messagesButton = new QListWidgetItem(contentsWidget); + messagesButton->setTextAlignment(Qt::AlignHCenter); + messagesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + messagesButton->setIcon(QIcon(":/resources/icon_config_messages.svg")); + + connect(contentsWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(changePage(QListWidgetItem *, QListWidgetItem *))); } void DlgSettings::changePage(QListWidgetItem *current, QListWidgetItem *previous) { - if (!current) - current = previous; - - pagesWidget->setCurrentIndex(contentsWidget->row(current)); + if (!current) + current = previous; + + pagesWidget->setCurrentIndex(contentsWidget->row(current)); } void DlgSettings::updateLanguage() { - qApp->removeTranslator(translator); - installNewTranslator(); + qApp->removeTranslator(translator); + installNewTranslator(); } void DlgSettings::changeEvent(QEvent *event) { - if (event->type() == QEvent::LanguageChange) - retranslateUi(); - QDialog::changeEvent(event); + if (event->type() == QEvent::LanguageChange) + retranslateUi(); + QDialog::changeEvent(event); } void DlgSettings::closeEvent(QCloseEvent *event) { - if (!db->getLoadSuccess()) - if (QMessageBox::critical(this, tr("Error"), tr("Your card database is invalid. Would you like to go back and set the correct path?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - event->ignore(); - return; - } - if (!QDir(settingsCache->getDeckPath()).exists() || settingsCache->getDeckPath().isEmpty()) - if (QMessageBox::critical(this, tr("Error"), tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - event->ignore(); - return; - } - if (!QDir(settingsCache->getPicsPath()).exists() || settingsCache->getPicsPath().isEmpty()) - if (QMessageBox::critical(this, tr("Error"), tr("The path to your card pictures directory is invalid. Would you like to go back and set the correct path?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - event->ignore(); - return; - } - event->accept(); + bool showLoadError = true; + QString loadErrorMessage = tr("Unknown Error loading card database"); + LoadStatus loadStatus = db->getLoadStatus(); + qDebug() << "Card Database load status: " << loadStatus; + switch(loadStatus) { + case Ok: + showLoadError = false; + break; + case Invalid: + loadErrorMessage = + tr("Your card database is invalid.\n\n" + "Cockatrice may not function correctly with an invalid database\n\n" + "You may need to rerun oracle to update your card database.\n\n" + "Would you like to change your database location setting?"); + break; + case VersionTooOld: + loadErrorMessage = + tr("Your card database version is too old.\n\n" + "This can cause problems loading card information or images\n\n" + "Usually this can be fixed by rerunning oracle to to update your card database.\n\n" + "Would you like to change your database location setting?"); + break; + case NotLoaded: + loadErrorMessage = + tr("Your card database did not finish loading\n\n" + "Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached\n\n" + "Would you like to change your database location setting?"); + break; + case FileError: + loadErrorMessage = + tr("File Error loading your card database.\n\n" + "Would you like to change your database location setting?"); + break; + case NoCards: + loadErrorMessage = + tr("Your card database was loaded but contains no cards.\n\n" + "Would you like to change your database location setting?"); + break; + default: + loadErrorMessage = + tr("Unknown card database load status\n\n" + "Please file a ticket at http://github.com/Daenyth/Cockatrice/issues\n\n" + "Would you like to change your database location setting?"); + + break; + } + if (showLoadError) + if (QMessageBox::critical(this, tr("Error"), loadErrorMessage, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + event->ignore(); + return; + } + if (!QDir(settingsCache->getDeckPath()).exists() || settingsCache->getDeckPath().isEmpty()) + // TODO: Prompt to create it + if (QMessageBox::critical(this, tr("Error"), tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + event->ignore(); + return; + } + if (!QDir(settingsCache->getPicsPath()).exists() || settingsCache->getPicsPath().isEmpty()) + // TODO: Prompt to create it + if (QMessageBox::critical(this, tr("Error"), tr("The path to your card pictures directory is invalid. Would you like to go back and set the correct path?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + event->ignore(); + return; + } + event->accept(); } void DlgSettings::retranslateUi() { - setWindowTitle(tr("Settings")); - - generalButton->setText(tr("General")); - appearanceButton->setText(tr("Appearance")); - userInterfaceButton->setText(tr("User interface")); - deckEditorButton->setText(tr("Deck editor")); - messagesButton->setText(tr("Messages")); - - closeButton->setText(tr("&Close")); - - for (int i = 0; i < pagesWidget->count(); i++) - dynamic_cast(pagesWidget->widget(i))->retranslateUi(); + setWindowTitle(tr("Settings")); + + generalButton->setText(tr("General")); + appearanceButton->setText(tr("Appearance")); + userInterfaceButton->setText(tr("User interface")); + deckEditorButton->setText(tr("Deck editor")); + messagesButton->setText(tr("Messages")); + + for (int i = 0; i < pagesWidget->count(); i++) + dynamic_cast(pagesWidget->widget(i))->retranslateUi(); } diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index f7bd1a522..0303ceef7 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -15,128 +15,134 @@ class QCheckBox; class QLabel; class QCloseEvent; class QSpinBox; +class QRadioButton; class AbstractSettingsPage : public QWidget { public: - virtual void retranslateUi() = 0; + virtual void retranslateUi() = 0; }; class GeneralSettingsPage : public AbstractSettingsPage { - Q_OBJECT + Q_OBJECT public: - GeneralSettingsPage(); - void retranslateUi(); + GeneralSettingsPage(); + void retranslateUi(); private slots: - void deckPathButtonClicked(); - void picsPathButtonClicked(); - void cardDatabasePathButtonClicked(); - void languageBoxChanged(int index); -signals: - void picsPathChanged(const QString &path); - void cardDatabasePathChanged(const QString &path); - void changeLanguage(const QString &qmFile); - void picDownloadChanged(int state); + void deckPathButtonClicked(); + void replaysPathButtonClicked(); + void picsPathButtonClicked(); + void clearDownloadedPicsButtonClicked(); + void cardDatabasePathButtonClicked(); + void tokenDatabasePathButtonClicked(); + void languageBoxChanged(int index); private: - QStringList findQmFiles(); - QString languageName(const QString &qmFile); - QLineEdit *deckPathEdit, *picsPathEdit, *cardDatabasePathEdit; - QGroupBox *personalGroupBox, *pathsGroupBox; - QComboBox *languageBox; - QCheckBox *picDownloadCheckBox; - QLabel *languageLabel, *deckPathLabel, *picsPathLabel, *cardDatabasePathLabel; + QStringList findQmFiles(); + QString languageName(const QString &qmFile); + QLineEdit *deckPathEdit, *replaysPathEdit, *picsPathEdit, *cardDatabasePathEdit, *tokenDatabasePathEdit; + QGroupBox *personalGroupBox, *pathsGroupBox; + QComboBox *languageBox; + QCheckBox *picDownloadCheckBox; + QCheckBox *picDownloadHqCheckBox; + QLabel *languageLabel, *deckPathLabel, *replaysPathLabel, *picsPathLabel, *cardDatabasePathLabel, *tokenDatabasePathLabel; }; class AppearanceSettingsPage : public AbstractSettingsPage { - Q_OBJECT + Q_OBJECT private slots: - void handBgClearButtonClicked(); - void handBgButtonClicked(); - void stackBgClearButtonClicked(); - void stackBgButtonClicked(); - void tableBgClearButtonClicked(); - void tableBgButtonClicked(); - void playerAreaBgClearButtonClicked(); - void playerAreaBgButtonClicked(); - void cardBackPicturePathClearButtonClicked(); - void cardBackPicturePathButtonClicked(); + void handBgClearButtonClicked(); + void handBgButtonClicked(); + void stackBgClearButtonClicked(); + void stackBgButtonClicked(); + void tableBgClearButtonClicked(); + void tableBgButtonClicked(); + void playerAreaBgClearButtonClicked(); + void playerAreaBgButtonClicked(); + void cardBackPicturePathClearButtonClicked(); + void cardBackPicturePathButtonClicked(); signals: - void handBgChanged(const QString &path); - void stackBgChanged(const QString &path); - void tableBgChanged(const QString &path); - void playerAreaBgChanged(const QString &path); - void cardBackPicturePathChanged(const QString &path); + void handBgChanged(const QString &path); + void stackBgChanged(const QString &path); + void tableBgChanged(const QString &path); + void playerAreaBgChanged(const QString &path); + void cardBackPicturePathChanged(const QString &path); private: - QLabel *handBgLabel, *stackBgLabel, *tableBgLabel, *playerAreaBgLabel, *cardBackPicturePathLabel, *minPlayersForMultiColumnLayoutLabel; - QLineEdit *handBgEdit, *stackBgEdit, *tableBgEdit, *playerAreaBgEdit, *cardBackPicturePathEdit; - QCheckBox *displayCardNamesCheckBox, *horizontalHandCheckBox, *invertVerticalCoordinateCheckBox, *zoneViewSortByNameCheckBox, *zoneViewSortByTypeCheckBox; - QGroupBox *zoneBgGroupBox, *cardsGroupBox, *handGroupBox, *tableGroupBox, *zoneViewGroupBox; - QSpinBox *minPlayersForMultiColumnLayoutEdit; + QLabel *handBgLabel, *stackBgLabel, *tableBgLabel, *playerAreaBgLabel, *cardBackPicturePathLabel, *minPlayersForMultiColumnLayoutLabel; + QLineEdit *handBgEdit, *stackBgEdit, *tableBgEdit, *playerAreaBgEdit, *cardBackPicturePathEdit; + QCheckBox *displayCardNamesCheckBox, *horizontalHandCheckBox, *invertVerticalCoordinateCheckBox, *zoneViewSortByNameCheckBox, *zoneViewSortByTypeCheckBox; + QGroupBox *zoneBgGroupBox, *cardsGroupBox, *handGroupBox, *tableGroupBox, *zoneViewGroupBox; + QSpinBox *minPlayersForMultiColumnLayoutEdit; public: - AppearanceSettingsPage(); - void retranslateUi(); + AppearanceSettingsPage(); + void retranslateUi(); }; class UserInterfaceSettingsPage : public AbstractSettingsPage { - Q_OBJECT + Q_OBJECT private slots: - void soundPathClearButtonClicked(); - void soundPathButtonClicked(); + void soundPathClearButtonClicked(); + void soundPathButtonClicked(); signals: - void soundPathChanged(); + void soundPathChanged(); private: - QCheckBox *doubleClickToPlayCheckBox; - QCheckBox *tapAnimationCheckBox; - QCheckBox *soundEnabledCheckBox; - QLabel *soundPathLabel; - QLineEdit *soundPathEdit; - QGroupBox *generalGroupBox, *animationGroupBox, *soundGroupBox; + QCheckBox *notificationsEnabledCheckBox; + QCheckBox *doubleClickToPlayCheckBox; + QCheckBox *playToStackCheckBox; + QCheckBox *tapAnimationCheckBox; + QCheckBox *soundEnabledCheckBox; + QLabel *soundPathLabel; + QLineEdit *soundPathEdit; + QGroupBox *generalGroupBox, *animationGroupBox, *soundGroupBox; public: - UserInterfaceSettingsPage(); - void retranslateUi(); + UserInterfaceSettingsPage(); + void retranslateUi(); }; class DeckEditorSettingsPage : public AbstractSettingsPage { - Q_OBJECT + Q_OBJECT public: - DeckEditorSettingsPage(); - void retranslateUi(); + DeckEditorSettingsPage(); + void retranslateUi(); +private slots: + void radioPriceTagSourceClicked(bool checked); +signals: + void priceTagSourceChanged(int _priceTagSource); private: - QCheckBox *priceTagsCheckBox; - QGroupBox *generalGroupBox; + QCheckBox *priceTagsCheckBox; + QRadioButton *priceTagSource0, *priceTagSource1; + QGroupBox *generalGroupBox; }; class MessagesSettingsPage : public AbstractSettingsPage { - Q_OBJECT + Q_OBJECT public: - MessagesSettingsPage(); - void retranslateUi(); + MessagesSettingsPage(); + void retranslateUi(); private slots: - void actAdd(); - void actRemove(); + void actAdd(); + void actRemove(); private: - QListWidget *messageList; - QAction *aAdd, *aRemove; - - void storeSettings(); + QListWidget *messageList; + QAction *aAdd, *aRemove; + + void storeSettings(); }; class DlgSettings : public QDialog { - Q_OBJECT + Q_OBJECT public: - DlgSettings(QWidget *parent = 0); + DlgSettings(QWidget *parent = 0); private slots: - void changePage(QListWidgetItem *current, QListWidgetItem *previous); - void updateLanguage(); + void changePage(QListWidgetItem *current, QListWidgetItem *previous); + void updateLanguage(); private: - QListWidget *contentsWidget; - QStackedWidget *pagesWidget; - QListWidgetItem *generalButton, *appearanceButton, *userInterfaceButton, *deckEditorButton, *messagesButton; - QPushButton *closeButton; - void createIcons(); - void retranslateUi(); + QListWidget *contentsWidget; + QStackedWidget *pagesWidget; + QListWidgetItem *generalButton, *appearanceButton, *userInterfaceButton, *deckEditorButton, *messagesButton; + void createIcons(); + void retranslateUi(); protected: - void changeEvent(QEvent *event); - void closeEvent(QCloseEvent *event); + void changeEvent(QEvent *event); + void closeEvent(QCloseEvent *event); }; #endif diff --git a/cockatrice/src/filterbuilder.cpp b/cockatrice/src/filterbuilder.cpp new file mode 100644 index 000000000..f594a5de1 --- /dev/null +++ b/cockatrice/src/filterbuilder.cpp @@ -0,0 +1,84 @@ +#include "filterbuilder.h" + +#include +#include +#include +#include + +#include "cardfilter.h" + +FilterBuilder::FilterBuilder(QWidget *parent) + : QFrame(parent) +{ + int i; + + QVBoxLayout *layout = new QVBoxLayout; + QHBoxLayout *addFilter = new QHBoxLayout; + + filterCombo = new QComboBox; + for (i = 0; i < CardFilter::AttrEnd; i++) + filterCombo->addItem( + tr(CardFilter::attrName(static_cast(i))), + QVariant(i) + ); + + typeCombo = new QComboBox; + for (i = 0; i < CardFilter::TypeEnd; i++) + typeCombo->addItem( + tr(CardFilter::typeName(static_cast(i))), + QVariant(i) + ); + + QPushButton *ok = new QPushButton("+"); + ok->setMaximumSize(20, 20); + + addFilter->addWidget(ok); + addFilter->addWidget(typeCombo); + addFilter->addWidget(filterCombo, Qt::AlignLeft); + + edit = new QLineEdit; + edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + layout->addLayout(addFilter); + layout->addWidget(edit); + + setFrameStyle(QFrame::Panel | QFrame::Raised); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + + connect(edit, SIGNAL(returnPressed()), this, SLOT(emit_add())); + connect(ok, SIGNAL(released()), this, SLOT(emit_add())); + connect(filterCombo, SIGNAL(currentIndexChanged(int)), edit, SLOT(clear())); + fltr = NULL; +} + +FilterBuilder::~FilterBuilder() +{ + destroyFilter(); +} + +void FilterBuilder::destroyFilter() +{ + if (fltr) + delete fltr; +} + +static int comboCurrentIntData(const QComboBox *combo) +{ + return combo->itemData(combo->currentIndex()).toInt(); +} + +void FilterBuilder::emit_add() +{ + QString txt; + + txt = edit->text(); + if (txt.length() < 1) + return; + + destroyFilter(); + fltr = new CardFilter(txt, + static_cast(comboCurrentIntData(typeCombo)), + static_cast(comboCurrentIntData(filterCombo))); + emit add(fltr); + edit->clear(); +} diff --git a/cockatrice/src/filterbuilder.h b/cockatrice/src/filterbuilder.h new file mode 100644 index 000000000..6438e383f --- /dev/null +++ b/cockatrice/src/filterbuilder.h @@ -0,0 +1,35 @@ +#ifndef FILTERBUILDER_H +#define FILTERBUILDER_H + +#include + +class QCheckBox; +class QComboBox; +class QLineEdit; +class CardFilter; + +class FilterBuilder : public QFrame { + Q_OBJECT + +private: + QComboBox *typeCombo; + QComboBox *filterCombo; + QLineEdit *edit; + CardFilter *fltr; + + void destroyFilter(); + +public: + FilterBuilder(QWidget *parent = 0); + ~FilterBuilder(); + +signals: + void add(const CardFilter *f); + +public slots: +private slots: + void emit_add(); +protected: +}; + +#endif diff --git a/cockatrice/src/filtertree.cpp b/cockatrice/src/filtertree.cpp new file mode 100644 index 000000000..5fc516f39 --- /dev/null +++ b/cockatrice/src/filtertree.cpp @@ -0,0 +1,339 @@ +#include "filtertree.h" +#include "cardfilter.h" +#include "carddatabase.h" + +#include + +template +FilterTreeNode *FilterTreeBranch::nodeAt(int i) const +{ + return ((childNodes.size() > i)? childNodes.at(i) : NULL); +} + +template +void FilterTreeBranch::deleteAt(int i) +{ + preRemoveChild(this, i); + delete childNodes.takeAt(i); + postRemoveChild(this, i); + nodeChanged(); +} + +template +int FilterTreeBranch::childIndex(const FilterTreeNode *node) const +{ + FilterTreeNode *unconst; + T downcasted; + + /* to do the dynamic cast to T we will lose const'ness, but we can + * trust QList::indexOf */ + unconst = (FilterTreeNode *) node; + downcasted = dynamic_cast(unconst); + if (downcasted == NULL) + return -1; + return childNodes.indexOf(downcasted); +} + +template +FilterTreeBranch::~FilterTreeBranch() +{ + while (!childNodes.isEmpty()) + delete childNodes.takeFirst(); +} + +const FilterItemList *LogicMap::findTypeList(CardFilter::Type type) const +{ + QList::const_iterator i; + + for (i = childNodes.constBegin(); i != childNodes.constEnd(); i++) + if ((*i)->type == type) + return *i; + + return NULL; +} + +FilterItemList *LogicMap::typeList(CardFilter::Type type) +{ + QList::iterator i; + int count; + + count = 0; + for (i = childNodes.begin(); i != childNodes.end(); i++) { + if ((*i)->type == type) + break; + count++; + } + if (i == childNodes.end()) { + preInsertChild(this, count); + i = childNodes.insert(i, new FilterItemList(type, this)); + postInsertChild(this, count); + nodeChanged(); + } + + return *i; +} + +FilterTreeNode *LogicMap::parent() const +{ + return p; +} + +int FilterItemList::termIndex(const QString &term) const +{ + int i; + + for (i = 0; i < childNodes.count(); i++) + if ((childNodes.at(i))->term == term) + return i; + + return -1; +} + +FilterTreeNode *FilterItemList::termNode(const QString &term) +{ + int i, count; + FilterItem *fi; + + i = termIndex(term); + if (i < 0) { + fi = new FilterItem(term, this); + count = childNodes.count(); + preInsertChild(this, count); + childNodes.append(fi); + postInsertChild(this, count); + nodeChanged(); + return fi; + } + + return childNodes.at(i); +} + +bool FilterItemList::testTypeAnd(const CardInfo *info, CardFilter::Attr attr) const +{ + QList::const_iterator i; + + for (i = childNodes.constBegin(); i != childNodes.constEnd(); i++) { + if (!(*i)->isEnabled()) + continue; + if (!(*i)->acceptCardAttr(info, attr)) + return false; + } + + return true; +} + +bool FilterItemList::testTypeAndNot(const CardInfo *info, CardFilter::Attr attr) const +{ + // if any one in the list is true, return false + return !testTypeOr(info, attr); +} + +bool FilterItemList::testTypeOr(const CardInfo *info, CardFilter::Attr attr) const +{ + QList::const_iterator i; + + for (i = childNodes.constBegin(); i != childNodes.constEnd(); i++) { + if (!(*i)->isEnabled()) + continue; + if ((*i)->acceptCardAttr(info, attr)) + return true; + } + + return false; +} + +bool FilterItemList::testTypeOrNot(const CardInfo *info, CardFilter::Attr attr) const +{ + // if any one in the list is false, return true + return !testTypeAnd(info, attr); +} + +bool FilterItem::acceptName(const CardInfo *info) const +{ + return info->getName().contains(term, Qt::CaseInsensitive); +} + +bool FilterItem::acceptType(const CardInfo *info) const +{ + return info->getCardType().contains(term, Qt::CaseInsensitive); +} + +bool FilterItem::acceptColor(const CardInfo *info) const +{ + QStringList::const_iterator i; + bool status; + + status = false; + for (i = info->getColors().constBegin(); i != info->getColors().constEnd(); i++) + if ((*i).contains(term, Qt::CaseInsensitive)) { + status = true; + break; + } + + return status; +} + +bool FilterItem::acceptText(const CardInfo *info) const +{ + return info->getText().contains(term, Qt::CaseInsensitive); +} + +bool FilterItem::acceptSet(const CardInfo *info) const +{ + SetList::const_iterator i; + bool status; + + status = false; + for (i = info->getSets().constBegin(); i != info->getSets().constEnd(); i++) + if ((*i)->getShortName() == term + || (*i)->getLongName().contains(term, Qt::CaseInsensitive)) { + status = true; + break; + } + + return status; +} + +bool FilterItem::acceptManaCost(const CardInfo *info) const +{ + return (info->getManaCost() == term); +} + +bool FilterItem::acceptCardAttr(const CardInfo *info, CardFilter::Attr attr) const +{ + bool status; + + switch (attr) { + case CardFilter::AttrName: + status = acceptName(info); + break; + case CardFilter::AttrType: + status = acceptType(info); + break; + case CardFilter::AttrColor: + status = acceptColor(info); + break; + case CardFilter::AttrText: + status = acceptText(info); + break; + case CardFilter::AttrSet: + status = acceptSet(info); + break; + case CardFilter::AttrManaCost: + status = acceptManaCost(info); + break; + default: + status = true; /* ignore this attribute */ + } + + return status; +} + +/* need to define these here to make QT happy, otherwise + * moc doesnt find some of the FilterTreeBranch symbols. + */ +FilterTree::FilterTree() {} +FilterTree::~FilterTree() {} + +LogicMap *FilterTree::attrLogicMap(CardFilter::Attr attr) +{ + QList::iterator i; + int count; + + count = 0; + for (i = childNodes.begin(); i != childNodes.end(); i++) { + if ((*i)->attr == attr) + break; + count++; + } + + if (i == childNodes.end()) { + preInsertChild(this, count); + i = childNodes.insert(i, new LogicMap(attr, this)); + postInsertChild(this, count); + nodeChanged(); + } + + return *i; +} + +FilterItemList *FilterTree::attrTypeList(CardFilter::Attr attr, + CardFilter::Type type) +{ + return attrLogicMap(attr)->typeList(type); +} + +int FilterTree::findTermIndex(CardFilter::Attr attr, CardFilter::Type type, + const QString &term) +{ + return attrTypeList(attr, type)->termIndex(term); +} + +int FilterTree::findTermIndex(const CardFilter *f) +{ + return findTermIndex(f->attr(), f->type(), f->term()); +} + +FilterTreeNode *FilterTree::termNode(CardFilter::Attr attr, CardFilter::Type type, + const QString &term) +{ + return attrTypeList(attr, type)->termNode(term); +} + +FilterTreeNode *FilterTree::termNode(const CardFilter *f) +{ + return termNode(f->attr(), f->type(), f->term()); +} + +FilterTreeNode *FilterTree::attrTypeNode(CardFilter::Attr attr, + CardFilter::Type type) +{ + return attrTypeList(attr, type); +} + +bool FilterTree::testAttr(const CardInfo *info, const LogicMap *lm) const +{ + const FilterItemList *fil; + bool status; + + status = true; + + fil = lm->findTypeList(CardFilter::TypeAnd); + if (fil != NULL && fil->isEnabled() && !fil->testTypeAnd(info, lm->attr)) + return false; + + fil = lm->findTypeList(CardFilter::TypeAndNot); + if (fil != NULL && fil->isEnabled() && !fil->testTypeAndNot(info, lm->attr)) + return false; + + fil = lm->findTypeList(CardFilter::TypeOr); + if (fil != NULL && fil->isEnabled()) { + status = false; + // if this is true we can return because it is OR'd with the OrNot list + if (fil->testTypeOr(info, lm->attr)) + return true; + } + + fil = lm->findTypeList(CardFilter::TypeOrNot); + if (fil != NULL && fil->isEnabled() && fil->testTypeOrNot(info, lm->attr)) + return true; + + return status; +} + +bool FilterTree::acceptsCard(const CardInfo *info) const +{ + QList::const_iterator i; + + for (i = childNodes.constBegin(); i != childNodes.constEnd(); i++) + if ((*i)->isEnabled() && !testAttr(info, *i)) + return false; + + return true; +} + +void FilterTree::clear() +{ + while(childCount() > 0) + deleteAt(0); +} diff --git a/cockatrice/src/filtertree.h b/cockatrice/src/filtertree.h new file mode 100644 index 000000000..d945e5679 --- /dev/null +++ b/cockatrice/src/filtertree.h @@ -0,0 +1,164 @@ +#ifndef FILTERTREE_H +#define FILTERTREE_H + +#include +#include +#include + +#include "cardfilter.h" + +class CardInfo; + +class FilterTreeNode { +private: + bool enabled; +public: + FilterTreeNode() : enabled(true) {} + virtual bool isEnabled() const { return enabled; } + virtual void enable() { enabled = true; nodeChanged(); } + virtual void disable() { enabled = false; nodeChanged(); } + virtual FilterTreeNode *parent() const { return NULL; } + virtual FilterTreeNode *nodeAt(int /* i */) const { return NULL; } + virtual void deleteAt(int /* i */) {} + virtual int childCount() const { return 0; } + virtual int childIndex(const FilterTreeNode * /* node */) const { return -1; } + virtual int index() const { return (parent() != NULL)? parent()->childIndex(this) : -1; } + virtual QString text() const { return QString(textCStr()); } + virtual bool isLeaf() const { return false; } + virtual const char *textCStr() const { return ""; } + virtual void nodeChanged() const { + if (parent() != NULL) parent()->nodeChanged(); + } + virtual void preInsertChild(const FilterTreeNode *p, int i) const { + if (parent() != NULL) parent()->preInsertChild(p, i); + } + virtual void postInsertChild(const FilterTreeNode *p, int i) const { + if (parent() != NULL) parent()->postInsertChild(p, i); + } + virtual void preRemoveChild(const FilterTreeNode *p, int i) const { + if (parent() != NULL) parent()->preRemoveChild(p, i); + } + virtual void postRemoveChild(const FilterTreeNode *p, int i) const { + if (parent() != NULL) parent()->postRemoveChild(p, i); + } +}; + +template +class FilterTreeBranch : public FilterTreeNode { +protected: + QList childNodes; +public: + virtual ~FilterTreeBranch(); + FilterTreeNode *nodeAt(int i) const; + void deleteAt(int i); + int childCount() const { return childNodes.size(); } + int childIndex(const FilterTreeNode *node) const; +}; + +class FilterItemList; +class FilterTree; +class LogicMap : public FilterTreeBranch { + +private: + FilterTree *const p; + +public: + const CardFilter::Attr attr; + + LogicMap(CardFilter::Attr a, FilterTree *parent) + : p(parent), attr(a) {} + const FilterItemList *findTypeList(CardFilter::Type type) const; + FilterItemList *typeList(CardFilter::Type type); + FilterTreeNode *parent() const; + const char* textCStr() const { return CardFilter::attrName(attr); } +}; + +class FilterItem; +class FilterItemList : public FilterTreeBranch { +private: + LogicMap *const p; +public: + const CardFilter::Type type; + + FilterItemList(CardFilter::Type t, LogicMap *parent) + : p(parent), type(t) {} + CardFilter::Attr attr() const { return p->attr; } + FilterTreeNode *parent() const { return p; } + int termIndex(const QString &term) const; + FilterTreeNode *termNode(const QString &term); + const char *textCStr() const { return CardFilter::typeName(type); } + + bool testTypeAnd(const CardInfo *info, CardFilter::Attr attr) const; + bool testTypeAndNot(const CardInfo *info, CardFilter::Attr attr) const; + bool testTypeOr(const CardInfo *info, CardFilter::Attr attr) const; + bool testTypeOrNot(const CardInfo *info, CardFilter::Attr attr) const; +}; + +class FilterItem : public FilterTreeNode { +private: + FilterItemList *const p; +public: + const QString term; + + FilterItem(QString trm, FilterItemList *parent) + : p(parent), term(trm) {} + virtual ~FilterItem() {}; + + CardFilter::Attr attr() const { return p->attr(); } + CardFilter::Type type() const { return p->type; } + FilterTreeNode *parent() const { return p; } + QString text() const { return term; } + const char *textCStr() const { return term.toStdString().c_str(); } + bool isLeaf() const { return true; } + + bool acceptName(const CardInfo *info) const; + bool acceptType(const CardInfo *info) const; + bool acceptColor(const CardInfo *info) const; + bool acceptText(const CardInfo *info) const; + bool acceptSet(const CardInfo *info) const; + bool acceptManaCost(const CardInfo *info) const; + bool acceptCardAttr(const CardInfo *info, CardFilter::Attr attr) const; +}; + +class FilterTree : public QObject, public FilterTreeBranch { + Q_OBJECT + +signals: + void preInsertRow(const FilterTreeNode *parent, int i) const; + void postInsertRow(const FilterTreeNode *parent, int i) const; + void preRemoveRow(const FilterTreeNode *parent, int i) const; + void postRemoveRow(const FilterTreeNode *parent, int i) const; + void changed() const; + +private: + LogicMap *attrLogicMap(CardFilter::Attr attr); + FilterItemList *attrTypeList(CardFilter::Attr attr, + CardFilter::Type type); + + bool testAttr(const CardInfo *info, const LogicMap *lm) const; + + void nodeChanged() const { emit changed(); } + void preInsertChild(const FilterTreeNode *p, int i) const { emit preInsertRow(p, i); } + void postInsertChild(const FilterTreeNode *p, int i) const { emit postInsertRow(p, i); } + void preRemoveChild(const FilterTreeNode *p, int i) const { emit preRemoveRow(p, i); } + void postRemoveChild(const FilterTreeNode *p, int i) const { emit postRemoveRow(p, i); } + +public: + FilterTree(); + ~FilterTree(); + int findTermIndex(CardFilter::Attr attr, CardFilter::Type type, + const QString &term); + int findTermIndex(const CardFilter *f); + FilterTreeNode *termNode(CardFilter::Attr attr, CardFilter::Type type, + const QString &term); + FilterTreeNode *termNode(const CardFilter *f); + FilterTreeNode *attrTypeNode(CardFilter::Attr attr, + CardFilter::Type type); + const char *textCStr() { return "root"; } + int index() const { return 0; } + + bool acceptsCard(const CardInfo *info) const; + void clear(); +}; + +#endif diff --git a/cockatrice/src/filtertreemodel.cpp b/cockatrice/src/filtertreemodel.cpp new file mode 100644 index 000000000..5fed77669 --- /dev/null +++ b/cockatrice/src/filtertreemodel.cpp @@ -0,0 +1,272 @@ +#include +#include "filtertreemodel.h" +#include "filtertree.h" +#include "cardfilter.h" + +FilterTreeModel::FilterTreeModel(QObject *parent) + : QAbstractItemModel(parent) +{ + fTree = new FilterTree; + connect(fTree, + SIGNAL(preInsertRow(const FilterTreeNode *, int)), + this, SLOT(proxyBeginInsertRow(const FilterTreeNode *, int))); + connect(fTree, + SIGNAL(postInsertRow(const FilterTreeNode *, int)), + this, SLOT(proxyEndInsertRow(const FilterTreeNode *, int))); + connect(fTree, + SIGNAL(preRemoveRow(const FilterTreeNode *, int)), + this, SLOT(proxyBeginRemoveRow(const FilterTreeNode *, int))); + connect(fTree, + SIGNAL(postRemoveRow(const FilterTreeNode *, int)), + this, SLOT(proxyEndRemoveRow(const FilterTreeNode *, int))); +} + +FilterTreeModel::~FilterTreeModel() +{ + delete fTree; +} + +void FilterTreeModel::proxyBeginInsertRow(const FilterTreeNode *node, int i) +{ + int idx; + + idx = node->index(); + if (idx >= 0) + beginInsertRows(createIndex(idx, 0, (void *) node), i, i); +} + +void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int) +{ + int idx; + + idx = node->index(); + if (idx >= 0) + endInsertRows(); +} + +void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i) +{ + int idx; + + idx = node->index(); + if (idx >= 0) + beginRemoveRows(createIndex(idx, 0, (void *) node), i, i); +} + +void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int) +{ + int idx; + + idx = node->index(); + if (idx >= 0) + endRemoveRows(); +} + +FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const +{ + void *ip; + FilterTreeNode *node; + + if (!idx.isValid()) + return fTree; + + ip = idx.internalPointer(); + if (ip == NULL) + return fTree; + + node = static_cast(ip); + return node; +} + +void FilterTreeModel::addFilter(const CardFilter *f) +{ + emit layoutAboutToBeChanged(); + fTree->termNode(f); + emit layoutChanged(); +} + +int FilterTreeModel::rowCount(const QModelIndex &parent) const +{ + const FilterTreeNode *node; + int result; + + if (parent.column() > 0) + return 0; + + node = indexToNode(parent); + if (node) + result = node->childCount(); + else + result = 0; + + return result; +} + +int FilterTreeModel::columnCount(const QModelIndex &/*parent*/) const +{ + return 1; +} + +QVariant FilterTreeModel::data(const QModelIndex &index, int role) const +{ + const FilterTreeNode *node; + + if (!index.isValid()) + return QVariant(); + if (index.column() >= columnCount()) + return QVariant(); + + node = indexToNode(index); + if (node == NULL) + return QVariant(); + + switch (role) { + case Qt::FontRole: + if (!node->isLeaf()) { + QFont f; + f.setBold(true); + return f; + } + break; + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::ToolTipRole: + case Qt::StatusTipRole: + case Qt::WhatsThisRole: + if(!node->isLeaf()) + return tr(node->textCStr()); + else + return node->text(); + case Qt::CheckStateRole: + if (node->isEnabled()) + return Qt::Checked; + else + return Qt::Unchecked; + default: + return QVariant(); + } + + return QVariant(); +} + +bool FilterTreeModel::setData(const QModelIndex &index, + const QVariant &value, int role) +{ + FilterTreeNode *node; + + if (!index.isValid()) + return false; + if (index.column() >= columnCount()) + return false; + if (role != Qt::CheckStateRole ) + return false; + + node = indexToNode(index); + if (node == NULL || node == fTree) + return false; + + Qt::CheckState state = static_cast(value.toInt()); + if (state == Qt::Checked) + node->enable(); + else + node->disable(); + + emit dataChanged(index, index); + return true; +} + +Qt::ItemFlags FilterTreeModel::flags(const QModelIndex &index) const +{ + const FilterTreeNode *node; + Qt::ItemFlags result; + + if (!index.isValid()) + return 0; + + node = indexToNode(index); + if (node == NULL) + return 0; + + result = Qt::ItemIsEnabled; + if (node == fTree) + return result; + + result |= Qt::ItemIsSelectable; + result |= Qt::ItemIsUserCheckable; + + return result; +} + +QModelIndex FilterTreeModel::nodeIndex(const FilterTreeNode *node, int row, int column) const +{ + FilterTreeNode *child; + + if (column > 0 || row >= node->childCount()) + return QModelIndex(); + + child = node->nodeAt(row); + return createIndex(row, column, child); +} + +QModelIndex FilterTreeModel::index(int row, int column, + const QModelIndex &parent) const +{ + const FilterTreeNode *node; + + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + node = indexToNode(parent); + if (node == NULL) + return QModelIndex(); + + return nodeIndex(node, row, column); +} + +QModelIndex FilterTreeModel::parent(const QModelIndex &ind) const +{ + const FilterTreeNode *node; + FilterTreeNode *parent; + int row; + QModelIndex idx; + + if (!ind.isValid()) + return QModelIndex(); + + node = indexToNode(ind); + if (node == NULL || node == fTree) + return QModelIndex(); + + parent = node->parent(); + if (parent) { + row = parent->index(); + if (row < 0) + return QModelIndex(); + idx = createIndex(row, 0, parent); + return idx; + } + + return QModelIndex(); +} + +bool FilterTreeModel::removeRows(int row, int count, const QModelIndex & parent) +{ + FilterTreeNode *node; + int i, last; + + last = row+count-1; + if (!parent.isValid() || count < 1 || row < 0) + return false; + + node = indexToNode(parent); + if (node == NULL || last >= node->childCount()) + return false; + + for (i = 0; i < count; i++) + node->deleteAt(row); + + if (node != fTree && node->childCount() < 1) + return removeRow(parent.row(), parent.parent()); + + return true; +} diff --git a/cockatrice/src/filtertreemodel.h b/cockatrice/src/filtertreemodel.h new file mode 100644 index 000000000..ac90db572 --- /dev/null +++ b/cockatrice/src/filtertreemodel.h @@ -0,0 +1,44 @@ +#ifndef FILTERTREEMODEL_H +#define FILTERTREEMODEL_H + +#include + +class FilterTree; +class CardFilter; +class FilterTreeNode; + +class FilterTreeModel : public QAbstractItemModel { + Q_OBJECT +private: + FilterTree *fTree; + +public slots: + void addFilter(const CardFilter *f); + +private slots: + void proxyBeginInsertRow(const FilterTreeNode *, int); + void proxyEndInsertRow(const FilterTreeNode *, int); + void proxyBeginRemoveRow(const FilterTreeNode *, int); + void proxyEndRemoveRow(const FilterTreeNode *, int); + +private: + FilterTreeNode *indexToNode(const QModelIndex &idx) const; + QModelIndex nodeIndex(const FilterTreeNode *node, int row, int column) const; + +public: + FilterTreeModel(QObject *parent = 0); + ~FilterTreeModel(); + FilterTree *filterTree() const { return fTree; } + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole); + Qt::ItemFlags flags(const QModelIndex &index) const; + QModelIndex parent(const QModelIndex &ind) const; + QModelIndex index(int row, int column, + const QModelIndex &parent) const; + bool removeRows(int row, int count, const QModelIndex & parent); +}; + +#endif diff --git a/cockatrice/src/gamescene.cpp b/cockatrice/src/gamescene.cpp index 1ba225e62..8e777168b 100644 --- a/cockatrice/src/gamescene.cpp +++ b/cockatrice/src/gamescene.cpp @@ -4,6 +4,7 @@ #include "zoneviewzone.h" #include "phasestoolbar.h" #include "settingscache.h" +#include "carditem.h" #include #include #include @@ -12,101 +13,103 @@ #include GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) - : QGraphicsScene(parent), phasesToolbar(_phasesToolbar) + : QGraphicsScene(parent), phasesToolbar(_phasesToolbar), viewSize(QSize()) { - animationTimer = new QBasicTimer; - addItem(phasesToolbar); - connect(settingsCache, SIGNAL(minPlayersForMultiColumnLayoutChanged()), this, SLOT(rearrange())); + animationTimer = new QBasicTimer; + addItem(phasesToolbar); + connect(settingsCache, SIGNAL(minPlayersForMultiColumnLayoutChanged()), this, SLOT(rearrange())); + + rearrange(); } GameScene::~GameScene() { - delete animationTimer; + delete animationTimer; } void GameScene::retranslateUi() { - for (int i = 0; i < zoneViews.size(); ++i) - zoneViews[i]->retranslateUi(); + for (int i = 0; i < zoneViews.size(); ++i) + zoneViews[i]->retranslateUi(); } void GameScene::addPlayer(Player *player) { - qDebug("GameScene::addPlayer"); - players << player; - addItem(player); - connect(player, SIGNAL(sizeChanged()), this, SLOT(rearrange())); - connect(player, SIGNAL(gameConceded()), this, SLOT(rearrange())); + qDebug("GameScene::addPlayer"); + players << player; + addItem(player); + connect(player, SIGNAL(sizeChanged()), this, SLOT(rearrange())); + connect(player, SIGNAL(gameConceded()), this, SLOT(rearrange())); } void GameScene::removePlayer(Player *player) { - qDebug("GameScene::removePlayer"); - players.removeAt(players.indexOf(player)); - removeItem(player); - rearrange(); + qDebug("GameScene::removePlayer"); + players.removeAt(players.indexOf(player)); + removeItem(player); + rearrange(); } void GameScene::rearrange() { - playersByColumn.clear(); + playersByColumn.clear(); - QList playersPlaying; - int firstPlayer = -1; - for (int i = 0; i < players.size(); ++i) - if (!players[i]->getConceded()) { - playersPlaying.append(players[i]); - if ((firstPlayer == -1) && (players[i]->getLocal())) - firstPlayer = playersPlaying.size() - 1; - } - if (firstPlayer == -1) - firstPlayer = 0; - const int playersCount = playersPlaying.size(); - const int columns = playersCount < settingsCache->getMinPlayersForMultiColumnLayout() ? 1 : 2; - const int rows = ceil((qreal) playersCount / columns); - qreal sceneHeight = 0, sceneWidth = -playerAreaSpacing; - QList columnWidth; - int firstPlayerOfColumn = firstPlayer; - for (int col = 0; col < columns; ++col) { - playersByColumn.append(QList()); - columnWidth.append(0); - qreal thisColumnHeight = -playerAreaSpacing; - const int rowsInColumn = rows - (playersCount % columns) * col; // only correct for max. 2 cols - for (int j = 0; j < rowsInColumn; ++j) { - Player *player = playersPlaying[(firstPlayerOfColumn + j) % playersCount]; - if (col == 0) - playersByColumn[col].prepend(player); - else - playersByColumn[col].append(player); - thisColumnHeight += player->boundingRect().height() + playerAreaSpacing; - if (player->boundingRect().width() > columnWidth[col]) - columnWidth[col] = player->boundingRect().width(); - } - if (thisColumnHeight > sceneHeight) - sceneHeight = thisColumnHeight; - sceneWidth += columnWidth[col] + playerAreaSpacing; + QList playersPlaying; + int firstPlayer = -1; + for (int i = 0; i < players.size(); ++i) + if (!players[i]->getConceded()) { + playersPlaying.append(players[i]); + if ((firstPlayer == -1) && (players[i]->getLocal())) + firstPlayer = playersPlaying.size() - 1; + } + if (firstPlayer == -1) + firstPlayer = 0; + const int playersCount = playersPlaying.size(); + const int columns = playersCount < settingsCache->getMinPlayersForMultiColumnLayout() ? 1 : 2; + const int rows = ceil((qreal) playersCount / columns); + qreal sceneHeight = 0, sceneWidth = -playerAreaSpacing; + QList columnWidth; + int firstPlayerOfColumn = firstPlayer; + for (int col = 0; col < columns; ++col) { + playersByColumn.append(QList()); + columnWidth.append(0); + qreal thisColumnHeight = -playerAreaSpacing; + const int rowsInColumn = rows - (playersCount % columns) * col; // only correct for max. 2 cols + for (int j = 0; j < rowsInColumn; ++j) { + Player *player = playersPlaying[(firstPlayerOfColumn + j) % playersCount]; + if (col == 0) + playersByColumn[col].prepend(player); + else + playersByColumn[col].append(player); + thisColumnHeight += player->boundingRect().height() + playerAreaSpacing; + if (player->boundingRect().width() > columnWidth[col]) + columnWidth[col] = player->boundingRect().width(); + } + if (thisColumnHeight > sceneHeight) + sceneHeight = thisColumnHeight; + sceneWidth += columnWidth[col] + playerAreaSpacing; - firstPlayerOfColumn += rowsInColumn; - } + firstPlayerOfColumn += rowsInColumn; + } - phasesToolbar->setHeight(sceneHeight); - qreal phasesWidth = phasesToolbar->getWidth(); - sceneWidth += phasesWidth; + phasesToolbar->setHeight(sceneHeight); + qreal phasesWidth = phasesToolbar->getWidth(); + sceneWidth += phasesWidth; - qreal x = phasesWidth; - for (int col = 0; col < columns; ++col) { - qreal y = 0; - for (int row = 0; row < playersByColumn[col].size(); ++row) { - Player *player = playersByColumn[col][row]; - player->setPos(x, y); - player->setMirrored(row != rows - 1); - y += player->boundingRect().height() + playerAreaSpacing; - } - x += columnWidth[col] + playerAreaSpacing; - } + qreal x = phasesWidth; + for (int col = 0; col < columns; ++col) { + qreal y = 0; + for (int row = 0; row < playersByColumn[col].size(); ++row) { + Player *player = playersByColumn[col][row]; + player->setPos(x, y); + player->setMirrored(row != rows - 1); + y += player->boundingRect().height() + playerAreaSpacing; + } + x += columnWidth[col] + playerAreaSpacing; + } - setSceneRect(sceneRect().x(), sceneRect().y(), sceneWidth, sceneHeight); - processViewSizeChange(viewSize); + setSceneRect(sceneRect().x(), sceneRect().y(), sceneWidth, sceneHeight); + processViewSizeChange(viewSize); } void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards) @@ -120,166 +123,166 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb } } - ZoneViewWidget *item = new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false); + ZoneViewWidget *item = new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false); zoneViews.append(item); connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *))); - addItem(item); - item->setPos(50, 50); + addItem(item); + item->setPos(50, 50); } -void GameScene::addRevealedZoneView(Player *player, CardZone *zone, const QList &cardList) +void GameScene::addRevealedZoneView(Player *player, CardZone *zone, const QList &cardList, bool withWritePermission) { - ZoneViewWidget *item = new ZoneViewWidget(player, zone, -2, true, cardList); - zoneViews.append(item); + ZoneViewWidget *item = new ZoneViewWidget(player, zone, -2, true, withWritePermission, cardList); + zoneViews.append(item); connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *))); - addItem(item); - item->setPos(50, 50); + addItem(item); + item->setPos(50, 50); } void GameScene::removeZoneView(ZoneViewWidget *item) { zoneViews.removeAt(zoneViews.indexOf(item)); - removeItem(item); + removeItem(item); } void GameScene::clearViews() { - while (!zoneViews.isEmpty()) - zoneViews.first()->close(); + while (!zoneViews.isEmpty()) + zoneViews.first()->close(); } void GameScene::closeMostRecentZoneView() { - if (!zoneViews.isEmpty()) - zoneViews.last()->close(); + if (!zoneViews.isEmpty()) + zoneViews.last()->close(); } QTransform GameScene::getViewTransform() const { - return views().at(0)->transform(); + return views().at(0)->transform(); } QTransform GameScene::getViewportTransform() const { - return views().at(0)->viewportTransform(); + return views().at(0)->viewportTransform(); } void GameScene::processViewSizeChange(const QSize &newSize) { - viewSize = newSize; - - qreal newRatio = ((qreal) newSize.width()) / newSize.height(); - qreal minWidth = 0; - QList minWidthByColumn; - for (int col = 0; col < playersByColumn.size(); ++col) { - minWidthByColumn.append(0); - for (int row = 0; row < playersByColumn[col].size(); ++row) { - qreal w = playersByColumn[col][row]->getMinimumWidth(); - if (w > minWidthByColumn[col]) - minWidthByColumn[col] = w; - } - minWidth += minWidthByColumn[col]; - } - minWidth += phasesToolbar->getWidth(); - - qreal minRatio = minWidth / sceneRect().height(); - qreal newWidth; - if (minRatio > newRatio) { - // Aspect ratio is dominated by table width. - newWidth = minWidth; - } else { - // Aspect ratio is dominated by window dimensions. - newWidth = newRatio * sceneRect().height(); - } - setSceneRect(0, 0, newWidth, sceneRect().height()); + viewSize = newSize; + + qreal newRatio = ((qreal) newSize.width()) / newSize.height(); + qreal minWidth = 0; + QList minWidthByColumn; + for (int col = 0; col < playersByColumn.size(); ++col) { + minWidthByColumn.append(0); + for (int row = 0; row < playersByColumn[col].size(); ++row) { + qreal w = playersByColumn[col][row]->getMinimumWidth(); + if (w > minWidthByColumn[col]) + minWidthByColumn[col] = w; + } + minWidth += minWidthByColumn[col]; + } + minWidth += phasesToolbar->getWidth(); + + qreal minRatio = minWidth / sceneRect().height(); + qreal newWidth; + if (minRatio > newRatio) { + // Aspect ratio is dominated by table width. + newWidth = minWidth; + } else { + // Aspect ratio is dominated by window dimensions. + newWidth = newRatio * sceneRect().height(); + } + setSceneRect(0, 0, newWidth, sceneRect().height()); - qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size(); - for (int col = 0; col < playersByColumn.size(); ++col) - for (int row = 0; row < playersByColumn[col].size(); ++row) - playersByColumn[col][row]->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn); + qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size(); + for (int col = 0; col < playersByColumn.size(); ++col) + for (int row = 0; row < playersByColumn[col].size(); ++row) + playersByColumn[col][row]->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn); } void GameScene::updateHover(const QPointF &scenePos) { - QList itemList = items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform()); - - // Search for the topmost zone and ignore all cards not belonging to that zone. - CardZone *zone = 0; - for (int i = 0; i < itemList.size(); ++i) - if ((zone = qgraphicsitem_cast(itemList[i]))) - break; - - CardItem *maxZCard = 0; - if (zone) { - qreal maxZ = -1; - for (int i = 0; i < itemList.size(); ++i) { - CardItem *card = qgraphicsitem_cast(itemList[i]); - if (!card) - continue; - if (card->getAttachedTo()) { - if (card->getAttachedTo()->getZone() != zone) - continue; - } else if (card->getZone() != zone) - continue; - - if (card->getRealZValue() > maxZ) { - maxZ = card->getRealZValue(); - maxZCard = card; - } - } - } - if (hoveredCard && (maxZCard != hoveredCard)) - hoveredCard->setHovered(false); - if (maxZCard && (maxZCard != hoveredCard)) - maxZCard->setHovered(true); - hoveredCard = maxZCard; + QList itemList = items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform()); + + // Search for the topmost zone and ignore all cards not belonging to that zone. + CardZone *zone = 0; + for (int i = 0; i < itemList.size(); ++i) + if ((zone = qgraphicsitem_cast(itemList[i]))) + break; + + CardItem *maxZCard = 0; + if (zone) { + qreal maxZ = -1; + for (int i = 0; i < itemList.size(); ++i) { + CardItem *card = qgraphicsitem_cast(itemList[i]); + if (!card) + continue; + if (card->getAttachedTo()) { + if (card->getAttachedTo()->getZone() != zone) + continue; + } else if (card->getZone() != zone) + continue; + + if (card->getRealZValue() > maxZ) { + maxZ = card->getRealZValue(); + maxZCard = card; + } + } + } + if (hoveredCard && (maxZCard != hoveredCard)) + hoveredCard->setHovered(false); + if (maxZCard && (maxZCard != hoveredCard)) + maxZCard->setHovered(true); + hoveredCard = maxZCard; } bool GameScene::event(QEvent *event) { - if (event->type() == QEvent::GraphicsSceneMouseMove) - updateHover(static_cast(event)->scenePos()); - - return QGraphicsScene::event(event); + if (event->type() == QEvent::GraphicsSceneMouseMove) + updateHover(static_cast(event)->scenePos()); + + return QGraphicsScene::event(event); } void GameScene::timerEvent(QTimerEvent * /*event*/) { - QMutableSetIterator i(cardsToAnimate); - while (i.hasNext()) { - i.next(); - if (!i.value()->animationEvent()) - i.remove(); - } - if (cardsToAnimate.isEmpty()) - animationTimer->stop(); + QMutableSetIterator i(cardsToAnimate); + while (i.hasNext()) { + i.next(); + if (!i.value()->animationEvent()) + i.remove(); + } + if (cardsToAnimate.isEmpty()) + animationTimer->stop(); } void GameScene::registerAnimationItem(AbstractCardItem *card) { - cardsToAnimate.insert(static_cast(card)); - if (!animationTimer->isActive()) - animationTimer->start(50, this); + cardsToAnimate.insert(static_cast(card)); + if (!animationTimer->isActive()) + animationTimer->start(50, this); } void GameScene::unregisterAnimationItem(AbstractCardItem *card) { - cardsToAnimate.remove(static_cast(card)); - if (cardsToAnimate.isEmpty()) - animationTimer->stop(); + cardsToAnimate.remove(static_cast(card)); + if (cardsToAnimate.isEmpty()) + animationTimer->stop(); } void GameScene::startRubberBand(const QPointF &selectionOrigin) { - emit sigStartRubberBand(selectionOrigin); + emit sigStartRubberBand(selectionOrigin); } void GameScene::resizeRubberBand(const QPointF &cursorPoint) { - emit sigResizeRubberBand(cursorPoint); + emit sigResizeRubberBand(cursorPoint); } void GameScene::stopRubberBand() { - emit sigStopRubberBand(); + emit sigStopRubberBand(); } diff --git a/cockatrice/src/gamescene.h b/cockatrice/src/gamescene.h index e46b66610..b542f0ec6 100644 --- a/cockatrice/src/gamescene.h +++ b/cockatrice/src/gamescene.h @@ -16,49 +16,49 @@ class PhasesToolbar; class QBasicTimer; class GameScene : public QGraphicsScene { - Q_OBJECT + Q_OBJECT private: - static const int playerAreaSpacing = 5; - - PhasesToolbar *phasesToolbar; - QList players; - QList > playersByColumn; - QList zoneViews; - QSize viewSize; - QPointer hoveredCard; - QBasicTimer *animationTimer; - QSet cardsToAnimate; - void updateHover(const QPointF &scenePos); + static const int playerAreaSpacing = 5; + + PhasesToolbar *phasesToolbar; + QList players; + QList > playersByColumn; + QList zoneViews; + QSize viewSize; + QPointer hoveredCard; + QBasicTimer *animationTimer; + QSet cardsToAnimate; + void updateHover(const QPointF &scenePos); public: - GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = 0); - ~GameScene(); - void retranslateUi(); - void processViewSizeChange(const QSize &newSize); - QTransform getViewTransform() const; - QTransform getViewportTransform() const; - - void startRubberBand(const QPointF &selectionOrigin); - void resizeRubberBand(const QPointF &cursorPoint); - void stopRubberBand(); - - void registerAnimationItem(AbstractCardItem *item); - void unregisterAnimationItem(AbstractCardItem *card); + GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = 0); + ~GameScene(); + void retranslateUi(); + void processViewSizeChange(const QSize &newSize); + QTransform getViewTransform() const; + QTransform getViewportTransform() const; + + void startRubberBand(const QPointF &selectionOrigin); + void resizeRubberBand(const QPointF &cursorPoint); + void stopRubberBand(); + + void registerAnimationItem(AbstractCardItem *item); + void unregisterAnimationItem(AbstractCardItem *card); public slots: - void toggleZoneView(Player *player, const QString &zoneName, int numberCards); - void addRevealedZoneView(Player *player, CardZone *zone, const QList &cardList); - void removeZoneView(ZoneViewWidget *item); - void addPlayer(Player *player); - void removePlayer(Player *player); - void clearViews(); - void closeMostRecentZoneView(); - void rearrange(); + void toggleZoneView(Player *player, const QString &zoneName, int numberCards); + void addRevealedZoneView(Player *player, CardZone *zone, const QList &cardList, bool withWritePermission); + void removeZoneView(ZoneViewWidget *item); + void addPlayer(Player *player); + void removePlayer(Player *player); + void clearViews(); + void closeMostRecentZoneView(); + void rearrange(); protected: - bool event(QEvent *event); - void timerEvent(QTimerEvent *event); + bool event(QEvent *event); + void timerEvent(QTimerEvent *event); signals: - void sigStartRubberBand(const QPointF &selectionOrigin); - void sigResizeRubberBand(const QPointF &cursorPoint); - void sigStopRubberBand(); + void sigStartRubberBand(const QPointF &selectionOrigin); + void sigResizeRubberBand(const QPointF &cursorPoint); + void sigStopRubberBand(); }; #endif diff --git a/cockatrice/src/gameselector.cpp b/cockatrice/src/gameselector.cpp index 63ede6ec7..25bf2ebd0 100644 --- a/cockatrice/src/gameselector.cpp +++ b/cockatrice/src/gameselector.cpp @@ -8,146 +8,180 @@ #include #include "tab_supervisor.h" #include "dlg_creategame.h" -#include "abstractclient.h" -#include "protocol_items.h" +#include "dlg_filter_games.h" #include "gameselector.h" #include "gamesmodel.h" +#include "tab_room.h" +#include "pending_command.h" +#include "pb/room_commands.pb.h" +#include "pb/serverinfo_game.pb.h" +#include "pb/response.pb.h" -GameSelector::GameSelector(AbstractClient *_client, TabSupervisor *_tabSupervisor, TabRoom *_room, const QMap &_rooms, const QMap &_gameTypes, QWidget *parent) - : QGroupBox(parent), client(_client), tabSupervisor(_tabSupervisor), room(_room) +GameSelector::GameSelector(AbstractClient *_client, const TabSupervisor *_tabSupervisor, TabRoom *_room, const QMap &_rooms, const QMap &_gameTypes, QWidget *parent) + : QGroupBox(parent), client(_client), tabSupervisor(_tabSupervisor), room(_room) { - gameListView = new QTreeView; - gameListModel = new GamesModel(_rooms, _gameTypes, this); - gameListProxyModel = new GamesProxyModel(this); - gameListProxyModel->setSourceModel(gameListModel); - gameListProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - gameListView->setModel(gameListProxyModel); - gameListView->setSortingEnabled(true); - gameListView->setAlternatingRowColors(true); - gameListView->setRootIsDecorated(true); - if (_room) - gameListView->header()->hideSection(1); - gameListView->header()->setResizeMode(1, QHeaderView::ResizeToContents); + gameListView = new QTreeView; + gameListModel = new GamesModel(_rooms, _gameTypes, this); + gameListProxyModel = new GamesProxyModel(this, tabSupervisor->getUserInfo()); + gameListProxyModel->setSourceModel(gameListModel); + gameListProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + gameListView->setModel(gameListProxyModel); + gameListView->setSortingEnabled(true); + gameListView->setAlternatingRowColors(true); + gameListView->setRootIsDecorated(true); + if (_room) + gameListView->header()->hideSection(1); + else + gameListProxyModel->setUnavailableGamesVisible(true); +#if QT_VERSION < 0x050000 + gameListView->header()->setResizeMode(1, QHeaderView::ResizeToContents); +#else + gameListView->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); +#endif + filterButton = new QPushButton; + filterButton->setIcon(QIcon(":/resources/icon_search.svg")); + connect(filterButton, SIGNAL(clicked()), this, SLOT(actSetFilter())); + clearFilterButton = new QPushButton; + clearFilterButton->setIcon(QIcon(":/resources/icon_clearsearch.svg")); + clearFilterButton->setEnabled(false); + connect(clearFilterButton, SIGNAL(clicked()), this, SLOT(actClearFilter())); - showFullGamesCheckBox = new QCheckBox; - showRunningGamesCheckBox = new QCheckBox; - - QVBoxLayout *filterLayout = new QVBoxLayout; - filterLayout->addWidget(showFullGamesCheckBox); - filterLayout->addWidget(showRunningGamesCheckBox); - - if (room) - createButton = new QPushButton; - else - createButton = 0; - joinButton = new QPushButton; - spectateButton = new QPushButton; - - QHBoxLayout *buttonLayout = new QHBoxLayout; - if (room) - buttonLayout->addWidget(createButton); - buttonLayout->addWidget(joinButton); - buttonLayout->addWidget(spectateButton); - buttonLayout->setAlignment(Qt::AlignTop); - - QHBoxLayout *hbox = new QHBoxLayout; - hbox->addLayout(filterLayout); - hbox->addStretch(); - hbox->addLayout(buttonLayout); + if (room) { + createButton = new QPushButton; + connect(createButton, SIGNAL(clicked()), this, SLOT(actCreate())); + } else + createButton = 0; + joinButton = new QPushButton; + spectateButton = new QPushButton; + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addWidget(filterButton); + buttonLayout->addWidget(clearFilterButton); + buttonLayout->addStretch(); + if (room) + buttonLayout->addWidget(createButton); + buttonLayout->addWidget(joinButton); + buttonLayout->addWidget(spectateButton); + buttonLayout->setAlignment(Qt::AlignTop); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(gameListView); + mainLayout->addLayout(buttonLayout); - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(gameListView); - mainLayout->addLayout(hbox); + retranslateUi(); + setLayout(mainLayout); - retranslateUi(); - setLayout(mainLayout); + setMinimumWidth((qreal) (gameListView->columnWidth(0) * gameListModel->columnCount()) / 1.5); + setMinimumHeight(200); - setMinimumWidth((qreal) (gameListView->columnWidth(0) * gameListModel->columnCount()) / 1.5); - setMinimumHeight(200); - - connect(showFullGamesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(showFullGamesChanged(int))); - connect(showRunningGamesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(showRunningGamesChanged(int))); - connect(createButton, SIGNAL(clicked()), this, SLOT(actCreate())); - connect(joinButton, SIGNAL(clicked()), this, SLOT(actJoin())); - connect(spectateButton, SIGNAL(clicked()), this, SLOT(actJoin())); + connect(joinButton, SIGNAL(clicked()), this, SLOT(actJoin())); + connect(spectateButton, SIGNAL(clicked()), this, SLOT(actJoin())); } -void GameSelector::showFullGamesChanged(int state) +void GameSelector::actSetFilter() { - gameListProxyModel->setFullGamesVisible(state); + GameTypeMap gameTypeMap; + if (room) + gameTypeMap = gameListModel->getGameTypes().value(room->getRoomId()); + DlgFilterGames dlg(gameTypeMap, this); + + if (!dlg.exec()) + return; + + clearFilterButton->setEnabled(true); + + gameListProxyModel->setUnavailableGamesVisible(dlg.getUnavailableGamesVisible()); + gameListProxyModel->setPasswordProtectedGamesVisible(dlg.getPasswordProtectedGamesVisible()); + gameListProxyModel->setGameNameFilter(dlg.getGameNameFilter()); + gameListProxyModel->setCreatorNameFilter(dlg.getCreatorNameFilter()); + gameListProxyModel->setGameTypeFilter(dlg.getGameTypeFilter()); + gameListProxyModel->setMaxPlayersFilter(dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax()); } -void GameSelector::showRunningGamesChanged(int state) +void GameSelector::actClearFilter() { - gameListProxyModel->setRunningGamesVisible(state); + clearFilterButton->setEnabled(false); + + gameListProxyModel->resetFilterParameters(); } void GameSelector::actCreate() { - DlgCreateGame dlg(client, room->getRoomId(), room->getGameTypes(), this); - dlg.exec(); + DlgCreateGame dlg(room, room->getGameTypes(), this); + dlg.exec(); } -void GameSelector::checkResponse(ResponseCode response) +void GameSelector::checkResponse(const Response &response) { - if (createButton) - createButton->setEnabled(true); - joinButton->setEnabled(true); - spectateButton->setEnabled(true); + if (createButton) + createButton->setEnabled(true); + joinButton->setEnabled(true); + spectateButton->setEnabled(true); - switch (response) { - case RespNotInRoom: QMessageBox::critical(this, tr("Error"), tr("Please join the appropriate room first.")); break; - case RespWrongPassword: QMessageBox::critical(this, tr("Error"), tr("Wrong password.")); break; - case RespSpectatorsNotAllowed: QMessageBox::critical(this, tr("Error"), tr("Spectators are not allowed in this game.")); break; - case RespGameFull: QMessageBox::critical(this, tr("Error"), tr("The game is already full.")); break; - case RespNameNotFound: QMessageBox::critical(this, tr("Error"), tr("The game does not exist any more.")); break; - case RespUserLevelTooLow: QMessageBox::critical(this, tr("Error"), tr("This game is only open to registered users.")); break; - case RespOnlyBuddies: QMessageBox::critical(this, tr("Error"), tr("This game is only open to its creator's buddies.")); break; - case RespInIgnoreList: QMessageBox::critical(this, tr("Error"), tr("You are being ignored by the creator of this game.")); break; - default: ; - } + switch (response.response_code()) { + case Response::RespNotInRoom: QMessageBox::critical(this, tr("Error"), tr("Please join the appropriate room first.")); break; + case Response::RespWrongPassword: QMessageBox::critical(this, tr("Error"), tr("Wrong password.")); break; + case Response::RespSpectatorsNotAllowed: QMessageBox::critical(this, tr("Error"), tr("Spectators are not allowed in this game.")); break; + case Response::RespGameFull: QMessageBox::critical(this, tr("Error"), tr("The game is already full.")); break; + case Response::RespNameNotFound: QMessageBox::critical(this, tr("Error"), tr("The game does not exist any more.")); break; + case Response::RespUserLevelTooLow: QMessageBox::critical(this, tr("Error"), tr("This game is only open to registered users.")); break; + case Response::RespOnlyBuddies: QMessageBox::critical(this, tr("Error"), tr("This game is only open to its creator's buddies.")); break; + case Response::RespInIgnoreList: QMessageBox::critical(this, tr("Error"), tr("You are being ignored by the creator of this game.")); break; + default: ; + } } void GameSelector::actJoin() { - bool spectator = sender() == spectateButton; - - QModelIndex ind = gameListView->currentIndex(); - if (!ind.isValid()) - return; - ServerInfo_Game *game = gameListModel->getGame(ind.data(Qt::UserRole).toInt()); - bool overrideRestrictions = !tabSupervisor->getAdminLocked(); - QString password; - if (game->getHasPassword() && !(spectator && !game->getSpectatorsNeedPassword()) && !overrideRestrictions) { - bool ok; - password = QInputDialog::getText(this, tr("Join game"), tr("Password:"), QLineEdit::Password, QString(), &ok); - if (!ok) - return; - } + bool spectator = sender() == spectateButton; + + QModelIndex ind = gameListView->currentIndex(); + if (!ind.isValid()) + return; + const ServerInfo_Game &game = gameListModel->getGame(ind.data(Qt::UserRole).toInt()); + bool overrideRestrictions = !tabSupervisor->getAdminLocked(); + QString password; + if (game.with_password() && !(spectator && !game.spectators_need_password()) && !overrideRestrictions) { + bool ok; + password = QInputDialog::getText(this, tr("Join game"), tr("Password:"), QLineEdit::Password, QString(), &ok); + if (!ok) + return; + } + + Command_JoinGame cmd; + cmd.set_game_id(game.game_id()); + cmd.set_password(password.toStdString()); + cmd.set_spectator(spectator); + cmd.set_override_restrictions(overrideRestrictions); + + TabRoom *r = tabSupervisor->getRoomTabs().value(game.room_id()); + if (!r) { + QMessageBox::critical(this, tr("Error"), tr("Please join the respective room first.")); + return; + } + + PendingCommand *pend = r->prepareRoomCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(checkResponse(Response))); + r->sendRoomCommand(pend); - Command_JoinGame *commandJoinGame = new Command_JoinGame(game->getRoomId(), game->getGameId(), password, spectator, overrideRestrictions); - connect(commandJoinGame, SIGNAL(finished(ResponseCode)), this, SLOT(checkResponse(ResponseCode))); - client->sendCommand(commandJoinGame); - - if (createButton) - createButton->setEnabled(false); - joinButton->setEnabled(false); - spectateButton->setEnabled(false); + if (createButton) + createButton->setEnabled(false); + joinButton->setEnabled(false); + spectateButton->setEnabled(false); } void GameSelector::retranslateUi() { - setTitle(tr("Games")); - showFullGamesCheckBox->setText(tr("Show &full games")); - showRunningGamesCheckBox->setText(tr("Show &running games")); - if (createButton) - createButton->setText(tr("C&reate")); - joinButton->setText(tr("&Join")); - spectateButton->setText(tr("J&oin as spectator")); + setTitle(tr("Games")); + filterButton->setText(tr("&Filter games")); + clearFilterButton->setText(tr("C&lear filter")); + if (createButton) + createButton->setText(tr("C&reate")); + joinButton->setText(tr("&Join")); + spectateButton->setText(tr("J&oin as spectator")); } -void GameSelector::processGameInfo(ServerInfo_Game *info) +void GameSelector::processGameInfo(const ServerInfo_Game &info) { - gameListModel->updateGameList(info); + gameListModel->updateGameList(info); } - diff --git a/cockatrice/src/gameselector.h b/cockatrice/src/gameselector.h index 16fc2d7d9..47a9c3957 100644 --- a/cockatrice/src/gameselector.h +++ b/cockatrice/src/gameselector.h @@ -2,8 +2,6 @@ #define GAMESELECTOR_H #include -#include "protocol_datastructures.h" -#include "tab_room.h" #include "gametypemap.h" class QTreeView; @@ -14,31 +12,32 @@ class QCheckBox; class AbstractClient; class TabSupervisor; class TabRoom; +class ServerInfo_Game; +class Response; class GameSelector : public QGroupBox { - Q_OBJECT + Q_OBJECT private slots: - void showFullGamesChanged(int state); - void showRunningGamesChanged(int state); - void actCreate(); - void actJoin(); - void checkResponse(ResponseCode response); + void actSetFilter(); + void actClearFilter(); + void actCreate(); + void actJoin(); + void checkResponse(const Response &response); signals: - void gameJoined(int gameId); + void gameJoined(int gameId); private: - AbstractClient *client; - TabSupervisor *tabSupervisor; - TabRoom *room; + AbstractClient *client; + const TabSupervisor *tabSupervisor; + TabRoom *room; - QTreeView *gameListView; - GamesModel *gameListModel; - GamesProxyModel *gameListProxyModel; - QPushButton *createButton, *joinButton, *spectateButton; - QCheckBox *showFullGamesCheckBox, *showRunningGamesCheckBox; + QTreeView *gameListView; + GamesModel *gameListModel; + GamesProxyModel *gameListProxyModel; + QPushButton *filterButton, *clearFilterButton, *createButton, *joinButton, *spectateButton; public: - GameSelector(AbstractClient *_client, TabSupervisor *_tabSupervisor, TabRoom *_room, const QMap &_rooms, const QMap &_gameTypes, QWidget *parent = 0); - void retranslateUi(); - void processGameInfo(ServerInfo_Game *info); + GameSelector(AbstractClient *_client, const TabSupervisor *_tabSupervisor, TabRoom *_room, const QMap &_rooms, const QMap &_gameTypes, QWidget *parent = 0); + void retranslateUi(); + void processGameInfo(const ServerInfo_Game &info); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/gamesmodel.cpp b/cockatrice/src/gamesmodel.cpp index 031c4207f..df0ec8809 100644 --- a/cockatrice/src/gamesmodel.cpp +++ b/cockatrice/src/gamesmodel.cpp @@ -1,140 +1,190 @@ #include "gamesmodel.h" -#include "protocol_datastructures.h" +#include "pb/serverinfo_game.pb.h" +#include GamesModel::GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent) - : QAbstractTableModel(parent), rooms(_rooms), gameTypes(_gameTypes) + : QAbstractTableModel(parent), rooms(_rooms), gameTypes(_gameTypes) { } -GamesModel::~GamesModel() -{ - if (!gameList.isEmpty()) { - beginRemoveRows(QModelIndex(), 0, gameList.size() - 1); - for (int i = 0; i < gameList.size(); ++i) - delete gameList[i]; - gameList.clear(); - endRemoveRows(); - } -} - QVariant GamesModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - if (role == Qt::UserRole) - return index.row(); - if (role != Qt::DisplayRole) - return QVariant(); - if ((index.row() >= gameList.size()) || (index.column() >= columnCount())) - return QVariant(); - - ServerInfo_Game *g = gameList[index.row()]; - switch (index.column()) { - case 0: return g->getDescription(); - case 1: return rooms.value(g->getRoomId()); - case 2: return g->getCreatorInfo()->getName(); - case 3: { - QStringList result; - QList gameTypeList = g->getGameTypes(); - GameTypeMap gameTypeMap = gameTypes.value(g->getRoomId()); - for (int i = 0; i < gameTypeList.size(); ++i) - result.append(gameTypeMap.value(gameTypeList[i]->getData())); - return result.join(", "); - } - case 4: return g->getHasPassword() ? (g->getSpectatorsNeedPassword() ? tr("yes") : tr("yes, free for spectators")) : tr("no"); - case 5: { - QStringList result; - if (g->getOnlyBuddies()) - result.append(tr("buddies only")); - if (g->getOnlyRegistered()) - result.append(tr("reg. users only")); - return result.join(", "); - } - case 6: return QString("%1/%2").arg(g->getPlayerCount()).arg(g->getMaxPlayers()); - case 7: return g->getSpectatorsAllowed() ? QVariant(g->getSpectatorCount()) : QVariant(tr("not allowed")); - default: return QVariant(); - } + if (!index.isValid()) + return QVariant(); + if (role == Qt::UserRole) + return index.row(); + if (role != Qt::DisplayRole) + return QVariant(); + if ((index.row() >= gameList.size()) || (index.column() >= columnCount())) + return QVariant(); + + const ServerInfo_Game &g = gameList[index.row()]; + switch (index.column()) { + case 0: return QString::fromStdString(g.description()); + case 1: return rooms.value(g.room_id()); + case 2: return QString::fromStdString(g.creator_info().name()); + case 3: { + QStringList result; + GameTypeMap gameTypeMap = gameTypes.value(g.room_id()); + for (int i = g.game_types_size() - 1; i >= 0; --i) + result.append(gameTypeMap.value(g.game_types(i))); + return result.join(", "); + } + case 4: return g.with_password() ? ((g.spectators_need_password() || !g.spectators_allowed()) ? tr("yes") : tr("yes, free for spectators")) : tr("no"); + case 5: { + QStringList result; + if (g.only_buddies()) + result.append(tr("buddies only")); + if (g.only_registered()) + result.append(tr("reg. users only")); + return result.join(", "); + } + case 6: return QString("%1/%2").arg(g.player_count()).arg(g.max_players()); + case 7: return g.spectators_allowed() ? QVariant(g.spectators_count()) : QVariant(tr("not allowed")); + default: return QVariant(); + } } QVariant GamesModel::headerData(int section, Qt::Orientation orientation, int role) const { - if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) - return QVariant(); - switch (section) { - case 0: return tr("Description"); - case 1: return tr("Room"); - case 2: return tr("Creator"); - case 3: return tr("Game type"); - case 4: return tr("Password"); - case 5: return tr("Restrictions"); - case 6: return tr("Players"); - case 7: return tr("Spectators"); - default: return QVariant(); - } + if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) + return QVariant(); + switch (section) { + case 0: return tr("Description"); + case 1: return tr("Room"); + case 2: return tr("Creator"); + case 3: return tr("Game type"); + case 4: return tr("Password"); + case 5: return tr("Restrictions"); + case 6: return tr("Players"); + case 7: return tr("Spectators"); + default: return QVariant(); + } } -ServerInfo_Game *GamesModel::getGame(int row) +const ServerInfo_Game &GamesModel::getGame(int row) { - Q_ASSERT(row < gameList.size()); - return gameList[row]; + Q_ASSERT(row < gameList.size()); + return gameList[row]; } -void GamesModel::updateGameList(ServerInfo_Game *_game) +void GamesModel::updateGameList(const ServerInfo_Game &game) { - QList gameTypeList, oldGameTypeList = _game->getGameTypes(); - for (int i = 0; i < oldGameTypeList.size(); ++i) - gameTypeList.append(new GameTypeId(oldGameTypeList[i]->getData())); - - ServerInfo_Game *game = new ServerInfo_Game(_game->getRoomId(), _game->getGameId(), _game->getDescription(), _game->getHasPassword(), _game->getPlayerCount(), _game->getMaxPlayers(), _game->getStarted(), gameTypeList, new ServerInfo_User(_game->getCreatorInfo()), _game->getOnlyBuddies(), _game->getOnlyRegistered(), _game->getSpectatorsAllowed(), _game->getSpectatorsNeedPassword(), _game->getSpectatorCount()); - for (int i = 0; i < gameList.size(); i++) - if (gameList[i]->getGameId() == game->getGameId()) { - if (game->getPlayerCount() == 0) { - beginRemoveRows(QModelIndex(), i, i); - delete gameList.takeAt(i); - endRemoveRows(); - } else { - delete gameList[i]; - gameList[i] = game; - emit dataChanged(index(i, 0), index(i, 7)); - } - return; - } - if (game->getPlayerCount() == 0) - return; - beginInsertRows(QModelIndex(), gameList.size(), gameList.size()); - gameList.append(game); - endInsertRows(); + for (int i = 0; i < gameList.size(); i++) + if (gameList[i].game_id() == game.game_id()) { + if (game.closed()) { + beginRemoveRows(QModelIndex(), i, i); + gameList.removeAt(i); + endRemoveRows(); + } else { + gameList[i].MergeFrom(game); + emit dataChanged(index(i, 0), index(i, 7)); + } + return; + } + if (game.player_count() <= 0) + return; + beginInsertRows(QModelIndex(), gameList.size(), gameList.size()); + gameList.append(game); + endInsertRows(); } -GamesProxyModel::GamesProxyModel(QObject *parent) - : QSortFilterProxyModel(parent), fullGamesVisible(false) +GamesProxyModel::GamesProxyModel(QObject *parent, ServerInfo_User *_ownUser) + : QSortFilterProxyModel(parent), + ownUser(_ownUser), + unavailableGamesVisible(false), + passwordProtectedGamesVisible(false), + maxPlayersFilterMin(-1), + maxPlayersFilterMax(-1) { - setDynamicSortFilter(true); + setDynamicSortFilter(true); } -void GamesProxyModel::setFullGamesVisible(bool _fullGamesVisible) +void GamesProxyModel::setUnavailableGamesVisible(bool _unavailableGamesVisible) { - fullGamesVisible = _fullGamesVisible; - invalidateFilter(); + unavailableGamesVisible = _unavailableGamesVisible; + invalidateFilter(); } -void GamesProxyModel::setRunningGamesVisible(bool _runningGamesVisible) +void GamesProxyModel::setPasswordProtectedGamesVisible(bool _passwordProtectedGamesVisible) { - runningGamesVisible = _runningGamesVisible; - invalidateFilter(); + passwordProtectedGamesVisible = _passwordProtectedGamesVisible; + invalidateFilter(); +} + +void GamesProxyModel::setGameNameFilter(const QString &_gameNameFilter) +{ + gameNameFilter = _gameNameFilter; + invalidateFilter(); +} + +void GamesProxyModel::setCreatorNameFilter(const QString &_creatorNameFilter) +{ + creatorNameFilter = _creatorNameFilter; + invalidateFilter(); +} + +void GamesProxyModel::setGameTypeFilter(const QSet &_gameTypeFilter) +{ + gameTypeFilter = _gameTypeFilter; + invalidateFilter(); +} + +void GamesProxyModel::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax) +{ + maxPlayersFilterMin = _maxPlayersFilterMin; + maxPlayersFilterMax = _maxPlayersFilterMax; + invalidateFilter(); +} + +void GamesProxyModel::resetFilterParameters() +{ + unavailableGamesVisible = false; + passwordProtectedGamesVisible = false; + gameNameFilter = QString(); + creatorNameFilter = QString(); + gameTypeFilter.clear(); + maxPlayersFilterMin = -1; + maxPlayersFilterMax = -1; + + invalidateFilter(); } bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &/*sourceParent*/) const { - GamesModel *model = qobject_cast(sourceModel()); - if (!model) - return false; - - ServerInfo_Game *game = model->getGame(sourceRow); - if ((game->getPlayerCount() == game->getMaxPlayers()) && !fullGamesVisible) - return false; - if (game->getStarted() && !runningGamesVisible) - return false; - - return true; + GamesModel *model = qobject_cast(sourceModel()); + if (!model) + return false; + + const ServerInfo_Game &game = model->getGame(sourceRow); + if (!unavailableGamesVisible) { + if (game.player_count() == game.max_players()) + return false; + if (game.started()) + return false; + if (!(ownUser->user_level() & ServerInfo_User::IsRegistered)) + if (game.only_registered()) + return false; + } + if (!passwordProtectedGamesVisible && game.with_password()) + return false; + if (!gameNameFilter.isEmpty()) + if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive)) + return false; + if (!creatorNameFilter.isEmpty()) + if (!QString::fromStdString(game.creator_info().name()).contains(creatorNameFilter, Qt::CaseInsensitive)) + return false; + + QSet gameTypes; + for (int i = 0; i < game.game_types_size(); ++i) + gameTypes.insert(game.game_types(i)); + if (!gameTypeFilter.isEmpty() && gameTypes.intersect(gameTypeFilter).isEmpty()) + return false; + + if ((maxPlayersFilterMin != -1) && ((int)game.max_players() < maxPlayersFilterMin)) + return false; + if ((maxPlayersFilterMax != -1) && ((int)game.max_players() > maxPlayersFilterMax)) + return false; + + return true; } diff --git a/cockatrice/src/gamesmodel.h b/cockatrice/src/gamesmodel.h index 8461faf66..aeb37e95e 100644 --- a/cockatrice/src/gamesmodel.h +++ b/cockatrice/src/gamesmodel.h @@ -4,39 +4,58 @@ #include #include #include +#include #include "gametypemap.h" +#include "pb/serverinfo_game.pb.h" -class ServerInfo_Game; +class ServerInfo_User; class GamesModel : public QAbstractTableModel { - Q_OBJECT + Q_OBJECT private: - QList gameList; - QMap rooms; - QMap gameTypes; + QList gameList; + QMap rooms; + QMap gameTypes; public: - GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = 0); - ~GamesModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const { return parent.isValid() ? 0 : gameList.size(); } - int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return 8; } - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - - ServerInfo_Game *getGame(int row); - void updateGameList(ServerInfo_Game *game); + GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = 0); + int rowCount(const QModelIndex &parent = QModelIndex()) const { return parent.isValid() ? 0 : gameList.size(); } + int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return 8; } + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + const ServerInfo_Game &getGame(int row); + void updateGameList(const ServerInfo_Game &game); + const QMap &getGameTypes() { return gameTypes; } }; class GamesProxyModel : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT private: - bool fullGamesVisible; - bool runningGamesVisible; + ServerInfo_User *ownUser; + bool unavailableGamesVisible; + bool passwordProtectedGamesVisible; + QString gameNameFilter, creatorNameFilter; + QSet gameTypeFilter; + int maxPlayersFilterMin, maxPlayersFilterMax; public: - GamesProxyModel(QObject *parent = 0); - void setFullGamesVisible(bool _fullGamesVisible); - void setRunningGamesVisible(bool _runningGamesVisible); + GamesProxyModel(QObject *parent = 0, ServerInfo_User *_ownUser = 0); + + bool getUnavailableGamesVisible() const { return unavailableGamesVisible; } + void setUnavailableGamesVisible(bool _unavailableGamesVisible); + bool getPasswordProtectedGamesVisible() const { return passwordProtectedGamesVisible; } + void setPasswordProtectedGamesVisible(bool _passwordProtectedGamesVisible); + QString getGameNameFilter() const { return gameNameFilter; } + void setGameNameFilter(const QString &_gameNameFilter); + QString getCreatorNameFilter() const { return creatorNameFilter; } + void setCreatorNameFilter(const QString &_creatorNameFilter); + QSet getGameTypeFilter() const { return gameTypeFilter; } + void setGameTypeFilter(const QSet &_gameTypeFilter); + int getMaxPlayersFilterMin() const { return maxPlayersFilterMin; } + int getMaxPlayersFilterMax() const { return maxPlayersFilterMax; } + void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); + void resetFilterParameters(); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; }; #endif diff --git a/cockatrice/src/gametypemap.h b/cockatrice/src/gametypemap.h index fce161752..0235b4720 100644 --- a/cockatrice/src/gametypemap.h +++ b/cockatrice/src/gametypemap.h @@ -5,4 +5,4 @@ typedef QMap GameTypeMap; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/gameview.cpp b/cockatrice/src/gameview.cpp index d8de4893a..7a28b4924 100644 --- a/cockatrice/src/gameview.cpp +++ b/cockatrice/src/gameview.cpp @@ -5,57 +5,57 @@ #include GameView::GameView(QGraphicsScene *scene, QWidget *parent) - : QGraphicsView(scene, parent), rubberBand(0) + : QGraphicsView(scene, parent), rubberBand(0) { - setBackgroundBrush(QBrush(QColor(0, 0, 0))); - setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing/* | QPainter::SmoothPixmapTransform*/); - setFocusPolicy(Qt::NoFocus); - setViewportUpdateMode(BoundingRectViewportUpdate); + setBackgroundBrush(QBrush(QColor(0, 0, 0))); + setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); + setFocusPolicy(Qt::NoFocus); + setViewportUpdateMode(BoundingRectViewportUpdate); - connect(scene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &))); - - connect(scene, SIGNAL(sigStartRubberBand(const QPointF &)), this, SLOT(startRubberBand(const QPointF &))); - connect(scene, SIGNAL(sigResizeRubberBand(const QPointF &)), this, SLOT(resizeRubberBand(const QPointF &))); - connect(scene, SIGNAL(sigStopRubberBand()), this, SLOT(stopRubberBand())); + connect(scene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &))); + + connect(scene, SIGNAL(sigStartRubberBand(const QPointF &)), this, SLOT(startRubberBand(const QPointF &))); + connect(scene, SIGNAL(sigResizeRubberBand(const QPointF &)), this, SLOT(resizeRubberBand(const QPointF &))); + connect(scene, SIGNAL(sigStopRubberBand()), this, SLOT(stopRubberBand())); - aCloseMostRecentZoneView = new QAction(this); - aCloseMostRecentZoneView->setShortcut(tr("Esc")); - connect(aCloseMostRecentZoneView, SIGNAL(triggered()), scene, SLOT(closeMostRecentZoneView())); - addAction(aCloseMostRecentZoneView); + aCloseMostRecentZoneView = new QAction(this); + aCloseMostRecentZoneView->setShortcut(tr("Esc")); + connect(aCloseMostRecentZoneView, SIGNAL(triggered()), scene, SLOT(closeMostRecentZoneView())); + addAction(aCloseMostRecentZoneView); - rubberBand = new QRubberBand(QRubberBand::Rectangle, this); + rubberBand = new QRubberBand(QRubberBand::Rectangle, this); } void GameView::resizeEvent(QResizeEvent *event) { - QGraphicsView::resizeEvent(event); + QGraphicsView::resizeEvent(event); - GameScene *s = dynamic_cast(scene()); - if (s) { - s->processViewSizeChange(event->size()); - } - updateSceneRect(scene()->sceneRect()); + GameScene *s = dynamic_cast(scene()); + if (s) + s->processViewSizeChange(event->size()); + + updateSceneRect(scene()->sceneRect()); } void GameView::updateSceneRect(const QRectF &rect) { - fitInView(rect, Qt::KeepAspectRatio); + fitInView(rect, Qt::KeepAspectRatio); } void GameView::startRubberBand(const QPointF &_selectionOrigin) { - selectionOrigin = _selectionOrigin; - rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0))); - rubberBand->show(); + selectionOrigin = _selectionOrigin; + rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0))); + rubberBand->show(); } void GameView::resizeRubberBand(const QPointF &cursorPoint) { - if (rubberBand) - rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), cursorPoint.toPoint()).normalized()); + if (rubberBand) + rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), cursorPoint.toPoint()).normalized()); } void GameView::stopRubberBand() { - rubberBand->hide(); + rubberBand->hide(); } diff --git a/cockatrice/src/gameview.h b/cockatrice/src/gameview.h index 84acf45e5..e678f2939 100644 --- a/cockatrice/src/gameview.h +++ b/cockatrice/src/gameview.h @@ -6,21 +6,21 @@ class QRubberBand; class GameView : public QGraphicsView { - Q_OBJECT + Q_OBJECT private: - QAction *aCloseMostRecentZoneView; - QRubberBand *rubberBand; - QPointF selectionOrigin; + QAction *aCloseMostRecentZoneView; + QRubberBand *rubberBand; + QPointF selectionOrigin; protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event); private slots: - void startRubberBand(const QPointF &selectionOrigin); - void resizeRubberBand(const QPointF &cursorPoint); - void stopRubberBand(); + void startRubberBand(const QPointF &selectionOrigin); + void resizeRubberBand(const QPointF &cursorPoint); + void stopRubberBand(); public slots: - void updateSceneRect(const QRectF &rect); + void updateSceneRect(const QRectF &rect); public: - GameView(QGraphicsScene *scene, QWidget *parent = 0); + GameView(QGraphicsScene *scene, QWidget *parent = 0); }; #endif diff --git a/cockatrice/src/handcounter.cpp b/cockatrice/src/handcounter.cpp index f5210dd15..4aa33d69e 100644 --- a/cockatrice/src/handcounter.cpp +++ b/cockatrice/src/handcounter.cpp @@ -6,9 +6,9 @@ #include "cardzone.h" HandCounter::HandCounter(QGraphicsItem *parent) - : AbstractGraphicsItem(parent), number(0) + : AbstractGraphicsItem(parent), number(0) { - setCacheMode(DeviceCoordinateCache); + setCacheMode(DeviceCoordinateCache); } HandCounter::~HandCounter() @@ -17,43 +17,43 @@ HandCounter::~HandCounter() void HandCounter::updateNumber() { - number = static_cast(sender())->getCards().size(); - update(); + number = static_cast(sender())->getCards().size(); + update(); } QRectF HandCounter::boundingRect() const { - return QRectF(0, 0, 72, 72); + return QRectF(0, 0, 72, 72); } void HandCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - painter->save(); - QSize translatedSize = painter->combinedTransform().mapRect(boundingRect()).size().toSize(); - QPixmap cachedPixmap; + painter->save(); + QSize translatedSize = painter->combinedTransform().mapRect(boundingRect()).size().toSize(); + QPixmap cachedPixmap; #if QT_VERSION >= 0x040600 - if (!QPixmapCache::find("handCounter" + QString::number(translatedSize.width()), &cachedPixmap)) { + if (!QPixmapCache::find("handCounter" + QString::number(translatedSize.width()), &cachedPixmap)) { #else - if (!QPixmapCache::find("handCounter" + QString::number(translatedSize.width()), cachedPixmap)) { + if (!QPixmapCache::find("handCounter" + QString::number(translatedSize.width()), cachedPixmap)) { #endif - QSvgRenderer svg(QString(":/resources/hand.svg")); - cachedPixmap = QPixmap(translatedSize); - cachedPixmap.fill(Qt::transparent); - QPainter painter(&cachedPixmap); - svg.render(&painter, QRectF(0, 0, translatedSize.width(), translatedSize.height())); - QPixmapCache::insert("handCounter" + QString::number(translatedSize.width()), cachedPixmap); - } - painter->resetTransform(); - painter->drawPixmap(cachedPixmap.rect(), cachedPixmap, cachedPixmap.rect()); - painter->restore(); - - paintNumberEllipse(number, 24, Qt::white, -1, -1, painter); + QSvgRenderer svg(QString(":/resources/hand.svg")); + cachedPixmap = QPixmap(translatedSize); + cachedPixmap.fill(Qt::transparent); + QPainter painter(&cachedPixmap); + svg.render(&painter, QRectF(0, 0, translatedSize.width(), translatedSize.height())); + QPixmapCache::insert("handCounter" + QString::number(translatedSize.width()), cachedPixmap); + } + painter->resetTransform(); + painter->drawPixmap(cachedPixmap.rect(), cachedPixmap, cachedPixmap.rect()); + painter->restore(); + + paintNumberEllipse(number, 24, Qt::white, -1, -1, painter); } void HandCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::RightButton) { - emit showContextMenu(event->screenPos()); - event->accept(); - } + if (event->button() == Qt::RightButton) { + emit showContextMenu(event->screenPos()); + event->accept(); + } } diff --git a/cockatrice/src/handcounter.h b/cockatrice/src/handcounter.h index f7ef9608f..af1cf3e46 100644 --- a/cockatrice/src/handcounter.h +++ b/cockatrice/src/handcounter.h @@ -2,28 +2,28 @@ #define HANDCOUNTER_H #include -#include "abstractcarditem.h" +#include "abstractgraphicsitem.h" class QPainter; class QPixmap; class HandCounter : public AbstractGraphicsItem { - Q_OBJECT + Q_OBJECT private: - int number; + int number; protected: - void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); public slots: - void updateNumber(); + void updateNumber(); signals: - void showContextMenu(const QPoint &screenPos); + void showContextMenu(const QPoint &screenPos); public: - enum { Type = typeOther }; - int type() const { return Type; } - HandCounter(QGraphicsItem *parent = 0); - ~HandCounter(); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + enum { Type = typeOther }; + int type() const { return Type; } + HandCounter(QGraphicsItem *parent = 0); + ~HandCounter(); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); }; #endif diff --git a/cockatrice/src/handzone.cpp b/cockatrice/src/handzone.cpp index 92dfeaaa4..cd9ba9595 100644 --- a/cockatrice/src/handzone.cpp +++ b/cockatrice/src/handzone.cpp @@ -2,122 +2,143 @@ #include "handzone.h" #include "settingscache.h" #include "player.h" -#include "protocol_items.h" #include "carddragitem.h" +#include "carditem.h" + +#include "pb/command_move_card.pb.h" HandZone::HandZone(Player *_p, bool _contentsKnown, int _zoneHeight, QGraphicsItem *parent) - : SelectZone(_p, "hand", false, false, _contentsKnown, parent), zoneHeight(_zoneHeight) + : SelectZone(_p, "hand", false, false, _contentsKnown, parent), zoneHeight(_zoneHeight) { - connect(settingsCache, SIGNAL(handBgPathChanged()), this, SLOT(updateBgPixmap())); - updateBgPixmap(); - setCacheMode(DeviceCoordinateCache); + connect(settingsCache, SIGNAL(handBgPathChanged()), this, SLOT(updateBgPixmap())); + updateBgPixmap(); + setCacheMode(DeviceCoordinateCache); } void HandZone::updateBgPixmap() { - QString bgPath = settingsCache->getHandBgPath(); - if (!bgPath.isEmpty()) - bgPixmap.load(bgPath); - update(); + QString bgPath = settingsCache->getHandBgPath(); + if (!bgPath.isEmpty()) + bgPixmap.load(bgPath); + update(); } void HandZone::addCardImpl(CardItem *card, int x, int /*y*/) { - if (x == -1) - x = cards.size(); - cards.insert(x, card); + if (x == -1) + x = cards.size(); + cards.insert(x, card); - if (!cards.getContentsKnown()) { - card->setId(-1); - card->setName(); - } - card->setParentItem(this); - card->resetState(); - card->setVisible(true); - card->update(); + if (!cards.getContentsKnown()) { + card->setId(-1); + card->setName(); + } + card->setParentItem(this); + card->resetState(); + card->setVisible(true); + card->update(); } -void HandZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &/*dropPoint*/) +void HandZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint & dropPoint) { - QList idList; - for (int i = 0; i < dragItems.size(); ++i) - idList.append(new CardToMove(dragItems[i]->getId())); - - player->sendGameCommand(new Command_MoveCard(-1, startZone->getName(), idList, player->getId(), getName(), cards.size(), -1)); + QPoint point = dropPoint + scenePos().toPoint(); + int x = -1; + if (settingsCache->getHorizontalHand()) { + for (x = 0; x < cards.size(); x++) + if (point.x() < ((CardItem *) cards.at(x))->scenePos().x()) + break; + } else { + for (x = 0; x < cards.size(); x++) + if (point.y() < ((CardItem *) cards.at(x))->scenePos().y()) + break; + } + + Command_MoveCard cmd; + cmd.set_start_player_id(startZone->getPlayer()->getId()); + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_target_player_id(player->getId()); + cmd.set_target_zone(getName().toStdString()); + cmd.set_x(x); + cmd.set_y(-1); + + for (int i = 0; i < dragItems.size(); ++i) + cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId()); + + player->sendGameCommand(cmd); } QRectF HandZone::boundingRect() const { - if (settingsCache->getHorizontalHand()) - return QRectF(0, 0, width, CARD_HEIGHT + 10); - else - return QRectF(0, 0, 100, zoneHeight); + if (settingsCache->getHorizontalHand()) + return QRectF(0, 0, width, CARD_HEIGHT + 10); + else + return QRectF(0, 0, 100, zoneHeight); } -void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) +void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - if (bgPixmap.isNull()) - painter->fillRect(boundingRect(), Qt::darkGreen); - else - painter->fillRect(boundingRect(), QBrush(bgPixmap)); + if (bgPixmap.isNull()) + painter->fillRect(boundingRect(), Qt::darkGreen); + else + painter->fillRect(boundingRect(), QBrush(bgPixmap)); } void HandZone::reorganizeCards() { - if (!cards.isEmpty()) { - const int cardCount = cards.size(); - if (settingsCache->getHorizontalHand()) { - const int xPadding = 5; - qreal totalWidth = boundingRect().width() - 2 * xPadding; - qreal cardWidth = cards.at(0)->boundingRect().width(); - - for (int i = 0; i < cardCount; i++) { - CardItem *c = cards.at(i); - - // If the total width of the cards is smaller than the available width, - // the cards do not need to overlap and are displayed in the center of the area. - if (cardWidth * cardCount > totalWidth) - c->setPos(xPadding + ((qreal) i) * (totalWidth - cardWidth) / (cardCount - 1), 5); - else - c->setPos(xPadding + ((qreal) i) * cardWidth + (totalWidth - cardCount * cardWidth) / 2, 5); - c->setRealZValue(i); - } - } else { - qreal totalWidth = boundingRect().width(); - qreal totalHeight = boundingRect().height(); - qreal cardWidth = cards.at(0)->boundingRect().width(); - qreal cardHeight = cards.at(0)->boundingRect().height(); - qreal xspace = 5; - qreal x1 = xspace; - qreal x2 = totalWidth - xspace - cardWidth; - - for (int i = 0; i < cardCount; i++) { - CardItem *c = cards.at(i); - qreal x = i % 2 ? x2 : x1; - // If the total height of the cards is smaller than the available height, - // the cards do not need to overlap and are displayed in the center of the area. - if (cardHeight * cardCount > totalHeight) - c->setPos(x, ((qreal) i) * (totalHeight - cardHeight) / (cardCount - 1)); - else - c->setPos(x, ((qreal) i) * cardHeight + (totalHeight - cardCount * cardHeight) / 2); - c->setRealZValue(i); - } - } - } - update(); + if (!cards.isEmpty()) { + const int cardCount = cards.size(); + if (settingsCache->getHorizontalHand()) { + const int xPadding = 5; + qreal totalWidth = boundingRect().width() - 2 * xPadding; + qreal cardWidth = cards.at(0)->boundingRect().width(); + + for (int i = 0; i < cardCount; i++) { + CardItem *c = cards.at(i); + + // If the total width of the cards is smaller than the available width, + // the cards do not need to overlap and are displayed in the center of the area. + if (cardWidth * cardCount > totalWidth) + c->setPos(xPadding + ((qreal) i) * (totalWidth - cardWidth) / (cardCount - 1), 5); + else + c->setPos(xPadding + ((qreal) i) * cardWidth + (totalWidth - cardCount * cardWidth) / 2, 5); + c->setRealZValue(i); + } + } else { + qreal totalWidth = boundingRect().width(); + qreal totalHeight = boundingRect().height(); + qreal cardWidth = cards.at(0)->boundingRect().width(); + qreal cardHeight = cards.at(0)->boundingRect().height(); + qreal xspace = 5; + qreal x1 = xspace; + qreal x2 = totalWidth - xspace - cardWidth; + + for (int i = 0; i < cardCount; i++) { + CardItem *c = cards.at(i); + qreal x = i % 2 ? x2 : x1; + // If the total height of the cards is smaller than the available height, + // the cards do not need to overlap and are displayed in the center of the area. + if (cardHeight * cardCount > totalHeight) + c->setPos(x, ((qreal) i) * (totalHeight - cardHeight) / (cardCount - 1)); + else + c->setPos(x, ((qreal) i) * cardHeight + (totalHeight - cardCount * cardHeight) / 2); + c->setRealZValue(i); + } + } + } + update(); } void HandZone::setWidth(qreal _width) { - if (settingsCache->getHorizontalHand()) { - prepareGeometryChange(); - width = _width; - reorganizeCards(); - } + if (settingsCache->getHorizontalHand()) { + prepareGeometryChange(); + width = _width; + reorganizeCards(); + } } void HandZone::updateOrientation() { - prepareGeometryChange(); - reorganizeCards(); + prepareGeometryChange(); + reorganizeCards(); } diff --git a/cockatrice/src/handzone.h b/cockatrice/src/handzone.h index fb51cecbb..66245c50b 100644 --- a/cockatrice/src/handzone.h +++ b/cockatrice/src/handzone.h @@ -4,23 +4,23 @@ #include "selectzone.h" class HandZone : public SelectZone { - Q_OBJECT + Q_OBJECT private: - qreal width, zoneHeight; - QPixmap bgPixmap; + qreal width, zoneHeight; + QPixmap bgPixmap; private slots: - void updateBgPixmap(); + void updateBgPixmap(); public slots: - void updateOrientation(); + void updateOrientation(); public: - HandZone(Player *_p, bool _contentsKnown, int _zoneHeight, QGraphicsItem *parent = 0); - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); - void setWidth(qreal _width); + HandZone(Player *_p, bool _contentsKnown, int _zoneHeight, QGraphicsItem *parent = 0); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void reorganizeCards(); + void setWidth(qreal _width); protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y); }; #endif diff --git a/cockatrice/src/keysignals.cpp b/cockatrice/src/keysignals.cpp new file mode 100644 index 000000000..5db58cb5e --- /dev/null +++ b/cockatrice/src/keysignals.cpp @@ -0,0 +1,65 @@ +#include "keysignals.h" +#include + +bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event) { + QKeyEvent *kevent; + + if(event->type() != QEvent::KeyPress) + return false; + + kevent = static_cast(event); + switch(kevent->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + if (kevent->modifiers().testFlag(Qt::AltModifier) + && kevent->modifiers().testFlag(Qt::ControlModifier) ) + emit onCtrlAltEnter(); + else if (kevent->modifiers() & Qt::ControlModifier) + emit onCtrlEnter(); + else + emit onEnter(); + + break; + case Qt::Key_Right: + emit onRight(); + + break; + case Qt::Key_Left: + emit onLeft(); + + break; + case Qt::Key_Delete: + case Qt::Key_Backspace: + emit onDelete(); + + break; + case Qt::Key_Minus: + if (kevent->modifiers().testFlag(Qt::AltModifier) + && kevent->modifiers().testFlag(Qt::ControlModifier) ) + emit onCtrlAltMinus(); + + break; + case Qt::Key_Equal: + if (kevent->modifiers().testFlag(Qt::AltModifier) + && kevent->modifiers().testFlag(Qt::ControlModifier) ) + emit onCtrlAltEqual(); + + break; + case Qt::Key_BracketLeft: + if (kevent->modifiers().testFlag(Qt::AltModifier) + && kevent->modifiers().testFlag(Qt::ControlModifier) ) + emit onCtrlAltLBracket(); + + break; + case Qt::Key_BracketRight: + if (kevent->modifiers().testFlag(Qt::AltModifier) + && kevent->modifiers().testFlag(Qt::ControlModifier) ) + emit onCtrlAltRBracket(); + + break; + default: + return false; + } + + return false; +} diff --git a/cockatrice/src/keysignals.h b/cockatrice/src/keysignals.h new file mode 100644 index 000000000..693488c56 --- /dev/null +++ b/cockatrice/src/keysignals.h @@ -0,0 +1,26 @@ +#ifndef KEYSIGNALS_H +#define KEYSIGNALS_H + +#include +#include + +class KeySignals : public QObject { + Q_OBJECT + +signals: + void onEnter(); + void onCtrlEnter(); + void onCtrlAltEnter(); + void onLeft(); + void onRight(); + void onDelete(); + void onCtrlAltMinus(); + void onCtrlAltEqual(); + void onCtrlAltLBracket(); + void onCtrlAltRBracket(); + +protected: + virtual bool eventFilter(QObject *, QEvent *event); +}; + +#endif diff --git a/cockatrice/src/localclient.cpp b/cockatrice/src/localclient.cpp index 87b215ba8..3ad7c8095 100644 --- a/cockatrice/src/localclient.cpp +++ b/cockatrice/src/localclient.cpp @@ -1,27 +1,32 @@ #include "localclient.h" #include "localserverinterface.h" -#include "protocol.h" + +#include "pb/session_commands.pb.h" LocalClient::LocalClient(LocalServerInterface *_lsi, const QString &_playerName, QObject *parent) - : AbstractClient(parent), lsi(_lsi) + : AbstractClient(parent), lsi(_lsi) { - connect(lsi, SIGNAL(itemToClient(ProtocolItem *)), this, SLOT(itemFromServer(ProtocolItem *))); - sendCommand(new Command_Login(_playerName, QString())); - sendCommand(new Command_JoinRoom(0)); + connect(lsi, SIGNAL(itemToClient(const ServerMessage &)), this, SLOT(itemFromServer(const ServerMessage &))); + + Command_Login loginCmd; + loginCmd.set_user_name(_playerName.toStdString()); + sendCommand(prepareSessionCommand(loginCmd)); + + Command_JoinRoom joinCmd; + joinCmd.set_room_id(0); + sendCommand(prepareSessionCommand(joinCmd)); } LocalClient::~LocalClient() { } -void LocalClient::sendCommandContainer(CommandContainer *cont) +void LocalClient::sendCommandContainer(const CommandContainer &cont) { - cont->setReceiverMayDelete(false); - pendingCommands.insert(cont->getCmdId(), cont); - lsi->itemFromClient(cont); + lsi->itemFromClient(cont); } -void LocalClient::itemFromServer(ProtocolItem *item) +void LocalClient::itemFromServer(const ServerMessage &item) { - processProtocolItem(item); + processProtocolItem(item); } diff --git a/cockatrice/src/localclient.h b/cockatrice/src/localclient.h index 4ea8cf2b8..febacada5 100644 --- a/cockatrice/src/localclient.h +++ b/cockatrice/src/localclient.h @@ -6,19 +6,16 @@ class LocalServerInterface; class LocalClient : public AbstractClient { - Q_OBJECT + Q_OBJECT private: - LocalServerInterface *lsi; + LocalServerInterface *lsi; public: - LocalClient(LocalServerInterface *_lsi, const QString &_playerName, QObject *parent = 0); - ~LocalClient(); - - void sendCommandContainer(CommandContainer *cont); - + LocalClient(LocalServerInterface *_lsi, const QString &_playerName, QObject *parent = 0); + ~LocalClient(); + + void sendCommandContainer(const CommandContainer &cont); private slots: - void itemFromServer(ProtocolItem *item); -signals: - void itemToServer(ProtocolItem *item); + void itemFromServer(const ServerMessage &item); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/localserver.cpp b/cockatrice/src/localserver.cpp index 2793a28e2..a7eda3d1f 100644 --- a/cockatrice/src/localserver.cpp +++ b/cockatrice/src/localserver.cpp @@ -3,24 +3,37 @@ #include "server_room.h" LocalServer::LocalServer(QObject *parent) - : Server(parent) + : Server(false, parent) { - addRoom(new Server_Room(0, QString(), QString(), false, QString(), QStringList(), this)); + setDatabaseInterface(new LocalServer_DatabaseInterface(this)); + addRoom(new Server_Room(0, QString(), QString(), false, QString(), QStringList(), this)); } LocalServer::~LocalServer() { - prepareDestroy(); + prepareDestroy(); } LocalServerInterface *LocalServer::newConnection() { - LocalServerInterface *lsi = new LocalServerInterface(this); - addClient(lsi); - return lsi; + LocalServerInterface *lsi = new LocalServerInterface(this, getDatabaseInterface()); + addClient(lsi); + return lsi; } -ServerInfo_User *LocalServer::getUserData(const QString &name) +LocalServer_DatabaseInterface::LocalServer_DatabaseInterface(LocalServer *_localServer) + : Server_DatabaseInterface(_localServer), localServer(_localServer) { - return new ServerInfo_User(name); -} \ No newline at end of file +} + +ServerInfo_User LocalServer_DatabaseInterface::getUserData(const QString &name, bool /*withId*/) +{ + ServerInfo_User result; + result.set_name(name.toStdString()); + return result; +} + +AuthenticationResult LocalServer_DatabaseInterface::checkUserPassword(Server_ProtocolHandler * /* handler */, const QString & /* user */, const QString & /* password */, QString & /* reasonStr */, int & /* secondsLeft */) +{ + return UnknownUser; +} diff --git a/cockatrice/src/localserver.h b/cockatrice/src/localserver.h index 36838cc6d..f47187f23 100644 --- a/cockatrice/src/localserver.h +++ b/cockatrice/src/localserver.h @@ -2,30 +2,31 @@ #define LOCALSERVER_H #include "server.h" +#include "server_database_interface.h" class LocalServerInterface; class LocalServer : public Server { - Q_OBJECT + Q_OBJECT public: - LocalServer(QObject *parent = 0); - ~LocalServer(); - AuthenticationResult checkUserPassword(Server_ProtocolHandler * /*handler*/, const QString & /*user*/, const QString & /*password*/) { return UnknownUser; } - QString getLoginMessage() const { return QString(); } - bool getGameShouldPing() const { return false; } - int getMaxGameInactivityTime() const { return 9999999; } - int getMaxPlayerInactivityTime() const { return 9999999; } - bool getThreaded() const { return false; } - - LocalServerInterface *newConnection(); -protected: - int startSession(const QString & /*userName*/, const QString & /*address*/) { return -1; } - void endSession(int /*sessionId*/) { } - bool userExists(const QString & /*name*/) { return false; } - ServerInfo_User *getUserData(const QString &name); - QMap getBuddyList(const QString & /*name*/) { return QMap(); } - QMap getIgnoreList(const QString & /*name*/) { return QMap(); } + LocalServer(QObject *parent = 0); + ~LocalServer(); + + LocalServerInterface *newConnection(); }; -#endif \ No newline at end of file +class LocalServer_DatabaseInterface : public Server_DatabaseInterface { + Q_OBJECT +private: + LocalServer *localServer; +protected: + ServerInfo_User getUserData(const QString &name, bool withId = false); +public: + LocalServer_DatabaseInterface(LocalServer *_localServer); + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); + int getNextGameId() { return localServer->getNextLocalGameId(); } + int getNextReplayId() { return -1; } +}; + +#endif diff --git a/cockatrice/src/localserverinterface.cpp b/cockatrice/src/localserverinterface.cpp index 79734c40c..291da1467 100644 --- a/cockatrice/src/localserverinterface.cpp +++ b/cockatrice/src/localserverinterface.cpp @@ -2,25 +2,21 @@ #include "localserver.h" #include -LocalServerInterface::LocalServerInterface(LocalServer *_server) - : Server_ProtocolHandler(_server, _server) +LocalServerInterface::LocalServerInterface(LocalServer *_server, Server_DatabaseInterface *_databaseInterface) + : Server_ProtocolHandler(_server, _databaseInterface, _server) { } LocalServerInterface::~LocalServerInterface() { - prepareDestroy(); } -void LocalServerInterface::sendProtocolItem(ProtocolItem *item, bool deleteItem) +void LocalServerInterface::transmitProtocolItem(const ServerMessage &item) { - item->setReceiverMayDelete(false); - emit itemToClient(item); - if (deleteItem) - delete item; + emit itemToClient(item); } -void LocalServerInterface::itemFromClient(ProtocolItem *item) +void LocalServerInterface::itemFromClient(const CommandContainer &item) { - processCommandContainer(static_cast(item)); + processCommandContainer(item); } diff --git a/cockatrice/src/localserverinterface.h b/cockatrice/src/localserverinterface.h index ca5ba9ced..1ad895e70 100644 --- a/cockatrice/src/localserverinterface.h +++ b/cockatrice/src/localserverinterface.h @@ -7,32 +7,17 @@ class LocalServer; class LocalServerInterface : public Server_ProtocolHandler { - Q_OBJECT -private: - DeckList *getDeckFromDatabase(int /*deckId*/) { return 0; } - ResponseCode cmdAddToList(Command_AddToList * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdRemoveFromList(Command_RemoveFromList * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdDeckList(Command_DeckList * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdDeckNewDir(Command_DeckNewDir * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdDeckDelDir(Command_DeckDelDir * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdDeckDel(Command_DeckDel * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdDeckUpload(Command_DeckUpload * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdDeckDownload(Command_DeckDownload * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdBanFromServer(Command_BanFromServer * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdShutdownServer(Command_ShutdownServer * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } - ResponseCode cmdUpdateServerMessage(Command_UpdateServerMessage * /*cmd*/, CommandContainer * /*cont*/) { return RespFunctionNotAllowed; } -protected: - bool getCompressionSupport() const { return false; } + Q_OBJECT public: - LocalServerInterface(LocalServer *_server); - ~LocalServerInterface(); - - QString getAddress() const { return QString(); } - void sendProtocolItem(ProtocolItem *item, bool deleteItem = true); + LocalServerInterface(LocalServer *_server, Server_DatabaseInterface *_databaseInterface); + ~LocalServerInterface(); + + QString getAddress() const { return QString(); } + void transmitProtocolItem(const ServerMessage &item); signals: - void itemToClient(ProtocolItem *item); + void itemToClient(const ServerMessage &item); public slots: - void itemFromClient(ProtocolItem *item); + void itemFromClient(const CommandContainer &item); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 5225ee1b1..7e0eb3fa4 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -19,6 +19,8 @@ ***************************************************************************/ #include +#include +#include #include #include #include @@ -27,7 +29,8 @@ #include #include #include -#include +#include +#include #include "main.h" #include "window_main.h" @@ -46,99 +49,186 @@ SettingsCache *settingsCache; RNG_Abstract *rng; SoundEngine *soundEngine; -void myMessageOutput(QtMsgType /*type*/, const char *msg) +const QString translationPrefix = "cockatrice"; +#ifdef TRANSLATION_PATH +QString translationPath = TRANSLATION_PATH; +#else +QString translationPath = QString(); +#endif + +#if QT_VERSION < 0x050000 +static void myMessageOutput(QtMsgType /*type*/, const char *msg) { - static FILE *f = NULL; - if (!f) - f = fopen("qdebug.txt", "w"); - fprintf(f, "%s\n", msg); - fflush(f); + QFile file("qdebug.txt"); + file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text); + QTextStream out(&file); + out << msg << endl; + file.close(); } +#else +static void myMessageOutput(QtMsgType /*type*/, const QMessageLogContext &, const QString &msg) +{ + QFile file("qdebug.txt"); + file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text); + QTextStream out(&file); + out << msg << endl; + file.close(); +} +#endif void installNewTranslator() { - QString lang = settingsCache->getLang(); + QString lang = settingsCache->getLang(); - qtTranslator->load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); - qApp->installTranslator(qtTranslator); - - translator->load(translationPrefix + "_" + lang, ":/translations"); - qApp->installTranslator(translator); + qtTranslator->load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + qApp->installTranslator(qtTranslator); + translator->load(translationPrefix + "_" + lang, translationPath); + qApp->installTranslator(translator); +} + +bool settingsValid() +{ + return QDir(settingsCache->getDeckPath()).exists() && + !settingsCache->getDeckPath().isEmpty() && + QDir(settingsCache->getPicsPath()).exists() && + !settingsCache->getPicsPath().isEmpty(); } int main(int argc, char *argv[]) { -// qInstallMsgHandler(myMessageOutput); - QApplication app(argc, argv); -#ifdef Q_OS_MAC - QDir baseDir(app.applicationDirPath()); - baseDir.cdUp(); - baseDir.cdUp(); - baseDir.cdUp(); - QDir pluginsDir = baseDir; - pluginsDir.cd("PlugIns"); - app.addLibraryPath(pluginsDir.absolutePath()); + QApplication app(argc, argv); + + if (app.arguments().contains("--debug-output")) + { +#if QT_VERSION < 0x050000 + qInstallMsgHandler(myMessageOutput); +#else + qInstallMessageHandler(myMessageOutput); #endif + } #ifdef Q_OS_WIN - app.addLibraryPath(app.applicationDirPath() + "/plugins"); + app.addLibraryPath(app.applicationDirPath() + "/plugins"); #endif - QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); - QCoreApplication::setOrganizationName("Cockatrice"); - QCoreApplication::setOrganizationDomain("cockatrice.de"); - QCoreApplication::setApplicationName("Cockatrice"); - - rng = new RNG_SFMT; - settingsCache = new SettingsCache; - db = new CardDatabase; +#if QT_VERSION < 0x050000 + // gone in Qt5, all source files _MUST_ be utf8-encoded + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); +#endif - qtTranslator = new QTranslator; - translator = new QTranslator; - installNewTranslator(); - - qsrand(QDateTime::currentDateTime().toTime_t()); - - bool startMainProgram = true; + QCoreApplication::setOrganizationName("Cockatrice"); + QCoreApplication::setOrganizationDomain("cockatrice.de"); + QCoreApplication::setApplicationName("Cockatrice"); + + if (translationPath.isEmpty()) { #ifdef Q_OS_MAC - if (!db->getLoadSuccess()) - if (db->loadCardDatabase(baseDir.absolutePath() + "/cards.xml")) - settingsCache->setCardDatabasePath(baseDir.absolutePath() + "/cards.xml"); - if (!QDir(settingsCache->getDeckPath()).exists()) - settingsCache->setDeckPath(baseDir.absolutePath() + "/decks"); - if (!QDir(settingsCache->getPicsPath()).exists()) - settingsCache->setPicsPath(baseDir.absolutePath() + "/pics"); + translationPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); +#elif defined(Q_OS_WIN) + translationPath = app.applicationDirPath() + "/translations"; #endif - if (!db->getLoadSuccess() || !QDir(settingsCache->getDeckPath()).exists() || settingsCache->getDeckPath().isEmpty() || settingsCache->getPicsPath().isEmpty() || !QDir(settingsCache->getPicsPath()).exists()) { - DlgSettings dlgSettings; - dlgSettings.show(); - app.exec(); - startMainProgram = (db->getLoadSuccess() && QDir(settingsCache->getDeckPath()).exists() && !settingsCache->getDeckPath().isEmpty() && QDir(settingsCache->getPicsPath()).exists() && !settingsCache->getPicsPath().isEmpty()); - } - - if (startMainProgram) { - qDebug("main(): starting main program"); - soundEngine = new SoundEngine; - qDebug("main(): SoundEngine constructor finished"); + } - MainWindow ui; - qDebug("main(): MainWindow constructor finished"); - - QIcon icon(":/resources/appicon.svg"); - ui.setWindowIcon(icon); - - ui.show(); - qDebug("main(): ui.show() finished"); - - app.exec(); - } - - qDebug("Event loop finished, terminating..."); - delete db; - delete settingsCache; - delete rng; - PingPixmapGenerator::clear(); - CountryPixmapGenerator::clear(); - UserLevelPixmapGenerator::clear(); - - return 0; + rng = new RNG_SFMT; + settingsCache = new SettingsCache; + db = new CardDatabase; + + qtTranslator = new QTranslator; + translator = new QTranslator; + installNewTranslator(); + + qsrand(QDateTime::currentDateTime().toTime_t()); + +#if QT_VERSION < 0x050000 + const QString dataDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); +#else + const QString dataDir = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); +#endif + if (!db->getLoadSuccess()) + if (db->loadCardDatabase(dataDir + "/cards.xml")) + settingsCache->setCardDatabasePath(dataDir + "/cards.xml"); + if (settingsCache->getTokenDatabasePath().isEmpty()) + settingsCache->setTokenDatabasePath(dataDir + "/tokens.xml"); + if (!QDir(settingsCache->getDeckPath()).exists() || settingsCache->getDeckPath().isEmpty()) { + QDir().mkpath(dataDir + "/decks"); + settingsCache->setDeckPath(dataDir + "/decks"); + } + if (!QDir(settingsCache->getReplaysPath()).exists() || settingsCache->getReplaysPath().isEmpty()) { + QDir().mkpath(dataDir + "/replays"); + settingsCache->setReplaysPath(dataDir + "/replays"); + } + if (!QDir(settingsCache->getPicsPath()).exists() || settingsCache->getPicsPath().isEmpty()) { + QDir().mkpath(dataDir + "/pics"); + settingsCache->setPicsPath(dataDir + "/pics"); + } + if (!QDir().mkpath(settingsCache->getPicsPath() + "/CUSTOM")) + qDebug() << "Could not create " + settingsCache->getPicsPath().toUtf8() + "/CUSTOM. Will fall back on default card images."; + +#ifdef Q_OS_MAC + if(settingsCache->getHandBgPath().isEmpty() && + settingsCache->getStackBgPath().isEmpty() && + settingsCache->getTableBgPath().isEmpty() && + settingsCache->getPlayerBgPath().isEmpty()) + { + QString srcDir = QLibraryInfo::location(QLibraryInfo::DataPath); + QString destDir = dataDir + "/zonebg"; + QDir tmpDir(destDir); + if(!tmpDir.exists()) + { + // try to install the default images for the current user and set the settigs value + settingsCache->copyPath(srcDir + "/zonebg", destDir); + + settingsCache->setHandBgPath(destDir + "/fabric_green.png"); + settingsCache->setStackBgPath(destDir + "/fabric_red.png"); + settingsCache->setTableBgPath(destDir + "/fabric_blue.png"); + settingsCache->setPlayerBgPath(destDir + "/fabric_gray.png"); + } + } + + if(settingsCache->getSoundPath().isEmpty()) + { + QString srcDir = QLibraryInfo::location(QLibraryInfo::DataPath); + QString destDir = dataDir + "/sounds"; + QDir tmpDir(destDir); + if(!tmpDir.exists()) + { + // try to install the default sounds for the current user and set the settigs value + settingsCache->copyPath(srcDir + "/sounds", destDir); + + settingsCache->setSoundPath(destDir); + } + } +#endif + + if (!settingsValid() || db->getLoadStatus() != Ok) { + qDebug("main(): invalid settings or load status"); + DlgSettings dlgSettings; + dlgSettings.show(); + app.exec(); + } + + if (settingsValid()) { + qDebug("main(): starting main program"); + soundEngine = new SoundEngine; + qDebug("main(): SoundEngine constructor finished"); + + MainWindow ui; + qDebug("main(): MainWindow constructor finished"); + + QIcon icon(":/resources/appicon.svg"); + ui.setWindowIcon(icon); + + ui.show(); + qDebug("main(): ui.show() finished"); + + app.exec(); + } + + qDebug("Event loop finished, terminating..."); + delete db; + delete settingsCache; + delete rng; + PingPixmapGenerator::clear(); + CountryPixmapGenerator::clear(); + UserLevelPixmapGenerator::clear(); + + return 0; } diff --git a/cockatrice/src/main.h b/cockatrice/src/main.h index 34ee91dfc..9237f23cb 100644 --- a/cockatrice/src/main.h +++ b/cockatrice/src/main.h @@ -8,9 +8,11 @@ class SoundEngine; extern CardDatabase *db; extern QTranslator *translator; -const QString translationPrefix = "cockatrice"; -const QString versionString = "0.20111113"; +extern const QString translationPrefix; +extern QString translationPath; void installNewTranslator(); +bool settingsValid(); + #endif diff --git a/cockatrice/src/messagelogwidget.cpp b/cockatrice/src/messagelogwidget.cpp index 9fa1518ad..a25a3cd0c 100644 --- a/cockatrice/src/messagelogwidget.cpp +++ b/cockatrice/src/messagelogwidget.cpp @@ -1,788 +1,881 @@ #include "messagelogwidget.h" #include "player.h" #include "cardzone.h" -#include "protocol_items.h" +#include "carditem.h" #include "soundengine.h" +#include "tab_supervisor.h" +#include "pb/serverinfo_user.pb.h" +#include "pb/context_move_card.pb.h" +#include "pb/context_mulligan.pb.h" #include QString MessageLogWidget::sanitizeHtml(QString dirty) const { - return dirty - .replace("&", "&") - .replace("<", "<") - .replace(">", ">"); + return dirty + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """); +} + +QString MessageLogWidget::cardLink(const QString &cardName) const +{ + return QString("%2").arg(cardName).arg(cardName); } bool MessageLogWidget::isFemale(Player *player) const { - return player->getUserInfo()->getGender() == ServerInfo_User::Female; + return player->getUserInfo()->gender() == ServerInfo_User::Female; +} + +bool MessageLogWidget::userIsFemale() const +{ + return (tabSupervisor && tabSupervisor->getUserInfo() && (tabSupervisor->getUserInfo()->gender() & ServerInfo_User::Female)); } void MessageLogWidget::logGameJoined(int gameId) { - if (female) - appendHtml(tr("You have joined game #%1.", "female").arg(gameId)); - else - appendHtml(tr("You have joined game #%1.", "male").arg(gameId)); + if (userIsFemale()) + appendHtml(tr("You have joined game #%1.", "female").arg(gameId)); + else + appendHtml(tr("You have joined game #%1.", "male").arg(gameId)); +} + +void MessageLogWidget::logReplayStarted(int gameId) +{ + if (userIsFemale()) + appendHtml(tr("You are watching a replay of game #%1.", "female").arg(gameId)); + else + appendHtml(tr("You are watching a replay of game #%1.", "male").arg(gameId)); } void MessageLogWidget::logJoin(Player *player) { - soundEngine->cuckoo(); - if (isFemale(player)) - appendHtml(tr("%1 has joined the game.", "female").arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 has joined the game.", "male").arg(sanitizeHtml(player->getName()))); + soundEngine->cuckoo(); + if (isFemale(player)) + appendHtml(tr("%1 has joined the game.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 has joined the game.", "male").arg(sanitizeHtml(player->getName()))); } void MessageLogWidget::logLeave(Player *player) { - if (isFemale(player)) - appendHtml(tr("%1 has left the game.", "female").arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 has left the game.", "male").arg(sanitizeHtml(player->getName()))); + if (isFemale(player)) + appendHtml(tr("%1 has left the game.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 has left the game.", "male").arg(sanitizeHtml(player->getName()))); } void MessageLogWidget::logGameClosed() { - appendHtml(tr("The game has been closed.")); + appendHtml(tr("The game has been closed.")); +} + +void MessageLogWidget::logKicked() +{ + appendHtml(tr("You have been kicked out of the game.")); } void MessageLogWidget::logJoinSpectator(QString name) { - appendHtml(tr("%1 is now watching the game.").arg(sanitizeHtml(name))); + appendHtml(tr("%1 is now watching the game.").arg(sanitizeHtml(name))); } void MessageLogWidget::logLeaveSpectator(QString name) { - appendHtml(tr("%1 is not watching the game any more.").arg(sanitizeHtml(name))); + appendHtml(tr("%1 is not watching the game any more.").arg(sanitizeHtml(name))); } -void MessageLogWidget::logDeckSelect(Player *player, QString deckHash) +void MessageLogWidget::logDeckSelect(Player *player, QString deckHash, int sideboardSize) { - if (isFemale(player)) - appendHtml(tr("%1 has loaded a deck (%2).", "female").arg(sanitizeHtml(player->getName())).arg(deckHash)); - else - appendHtml(tr("%1 has loaded a deck (%2).", "male").arg(sanitizeHtml(player->getName())).arg(deckHash)); + const char* gender = isFemale(player) ? "female" : "male"; + if (sideboardSize < 0) + appendHtml(tr("%1 has loaded a deck (%2).", gender).arg(sanitizeHtml(player->getName())).arg(deckHash)); + else + appendHtml(tr("%1 has loaded a deck with %2 sideboard cards (%3).", gender). + arg(sanitizeHtml(player->getName())). + arg(sideboardSize). + arg(deckHash)); } void MessageLogWidget::logReadyStart(Player *player) { - if (isFemale(player)) - appendHtml(tr("%1 is ready to start the game.", "female").arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 is ready to start the game.", "male").arg(sanitizeHtml(player->getName()))); + if (isFemale(player)) + appendHtml(tr("%1 is ready to start the game.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 is ready to start the game.", "male").arg(sanitizeHtml(player->getName()))); } void MessageLogWidget::logNotReadyStart(Player *player) { - if (isFemale(player)) - appendHtml(tr("%1 is not ready to start the game any more.", "female").arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 is not ready to start the game any more.", "male").arg(sanitizeHtml(player->getName()))); + if (isFemale(player)) + appendHtml(tr("%1 is not ready to start the game any more.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 is not ready to start the game any more.", "male").arg(sanitizeHtml(player->getName()))); +} + +void MessageLogWidget::logSetSideboardLock(Player *player, bool locked) +{ + if (locked) { + if (isFemale(player)) + appendHtml(tr("%1 has locked her sideboard.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 has locked his sideboard.", "male").arg(sanitizeHtml(player->getName()))); + } else { + if (isFemale(player)) + appendHtml(tr("%1 has unlocked her sideboard.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 has unlocked his sideboard.", "male").arg(sanitizeHtml(player->getName()))); + } } void MessageLogWidget::logConcede(Player *player) { - if (isFemale(player)) - appendHtml(tr("%1 has conceded the game.", "female").arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 has conceded the game.", "male").arg(sanitizeHtml(player->getName()))); + if (isFemale(player)) + appendHtml(tr("%1 has conceded the game.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 has conceded the game.", "male").arg(sanitizeHtml(player->getName()))); } void MessageLogWidget::logGameStart() { - appendHtml(tr("The game has started.")); + appendHtml(tr("The game has started.")); } void MessageLogWidget::logConnectionStateChanged(Player *player, bool connectionState) { - if (connectionState) { - if (isFemale(player)) - appendHtml(tr("%1 has restored connection to the game.", "female").arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 has restored connection to the game.", "male").arg(sanitizeHtml(player->getName()))); - } else { - if (isFemale(player)) - appendHtml(tr("%1 has lost connection to the game.", "female").arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 has lost connection to the game.", "male").arg(sanitizeHtml(player->getName()))); - } + if (connectionState) { + if (isFemale(player)) + appendHtml(tr("%1 has restored connection to the game.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 has restored connection to the game.", "male").arg(sanitizeHtml(player->getName()))); + } else { + if (isFemale(player)) + appendHtml(tr("%1 has lost connection to the game.", "female").arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 has lost connection to the game.", "male").arg(sanitizeHtml(player->getName()))); + } } void MessageLogWidget::logSay(Player *player, QString message) { - appendMessage(player->getName(), message, QColor(), true); + appendMessage(message, player->getName(), UserLevelFlags(player->getUserInfo()->user_level()), true); } -void MessageLogWidget::logSpectatorSay(QString spectatorName, QString message) +void MessageLogWidget::logSpectatorSay(QString spectatorName, UserLevelFlags spectatorUserLevel, QString message) { - appendMessage(spectatorName, message, QColor(), false); + appendMessage(message, spectatorName, spectatorUserLevel, false); } void MessageLogWidget::logShuffle(Player *player, CardZone *zone) { - soundEngine->shuffle(); - if (currentContext != MessageContext_Mulligan) { - if (isFemale(player)) - appendHtml(tr("%1 shuffles %2.", "female").arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(true, CaseAccusative))); - else - appendHtml(tr("%1 shuffles %2.", "male").arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(true, CaseAccusative))); - } + soundEngine->shuffle(); + if (currentContext != MessageContext_Mulligan) { + appendHtml((isFemale(player) + ? tr("%1 shuffles %2.", "female") + : tr("%1 shuffles %2.", "male") + ).arg(sanitizeHtml(player->getName())) + .arg(zone->getTranslatedName(true, CaseShuffleZone))); + } } void MessageLogWidget::logRollDie(Player *player, int sides, int roll) { - if (isFemale(player)) - appendHtml(tr("%1 rolls a %2 with a %3-sided die.", "female").arg(sanitizeHtml(player->getName())).arg(roll).arg(sides)); - else - appendHtml(tr("%1 rolls a %2 with a %3-sided die.", "male").arg(sanitizeHtml(player->getName())).arg(roll).arg(sides)); + if (isFemale(player)) + appendHtml(tr("%1 rolls a %2 with a %3-sided die.", "female").arg(sanitizeHtml(player->getName())).arg(roll).arg(sides)); + else + appendHtml(tr("%1 rolls a %2 with a %3-sided die.", "male").arg(sanitizeHtml(player->getName())).arg(roll).arg(sides)); } void MessageLogWidget::logDrawCards(Player *player, int number) { - if (currentContext == MessageContext_Mulligan) - mulliganPlayer = player; - else { - soundEngine->draw(); - if (isFemale(player)) - appendHtml(tr("%1 draws %n card(s).", "female", number).arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 draws %n card(s).", "male", number).arg(sanitizeHtml(player->getName()))); - } + if (currentContext == MessageContext_Mulligan) + mulliganPlayer = player; + else { + soundEngine->draw(); + if (isFemale(player)) + appendHtml(tr("%1 draws %n card(s).", "female", number).arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 draws %n card(s).", "male", number).arg(sanitizeHtml(player->getName()))); + } } void MessageLogWidget::logUndoDraw(Player *player, QString cardName) { - if (cardName.isEmpty()) - appendHtml((isFemale(player) ? tr("%1 undoes her last draw.") : tr("%1 undoes his last draw.")).arg(sanitizeHtml(player->getName()))); - else - appendHtml((isFemale(player) ? tr("%1 undoes her last draw (%2).") : tr("%1 undoes his last draw (%2).")).arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(cardName)))); + if (cardName.isEmpty()) + appendHtml((isFemale(player) ? tr("%1 undoes her last draw.") : tr("%1 undoes his last draw.")).arg(sanitizeHtml(player->getName()))); + else + appendHtml((isFemale(player) ? tr("%1 undoes her last draw (%2).") : tr("%1 undoes his last draw (%2).")).arg(sanitizeHtml(player->getName())).arg(QString("%2").arg(sanitizeHtml(cardName)).arg(sanitizeHtml(cardName)))); } -QPair MessageLogWidget::getFromStr(CardZone *zone, QString cardName, int position) const +QPair MessageLogWidget::getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange) const { - bool cardNameContainsStartZone = false; - QString fromStr; - QString startName = zone->getName(); - - if (startName == "table") - fromStr = tr(" from table"); - else if (startName == "grave") - fromStr = tr(" from graveyard"); - else if (startName == "rfg") - fromStr = tr(" from exile"); - else if (startName == "hand") - fromStr = tr(" from hand"); - else if (startName == "deck") { - if (position >= zone->getCards().size() - 1) { - if (cardName.isEmpty()) { - cardName = isFemale(zone->getPlayer()) ? tr("the bottom card of her library") : tr("the bottom card of his library"); - cardNameContainsStartZone = true; - } else - fromStr = isFemale(zone->getPlayer()) ? tr(" from the bottom of her library") : tr(" from the bottom of his library"); - } else if (position == 0) { - if (cardName.isEmpty()) { - cardName = isFemale(zone->getPlayer()) ? tr("the top card of her library") : tr("the top card of his library"); - cardNameContainsStartZone = true; - } else - fromStr = isFemale(zone->getPlayer()) ? tr(" from the top of her library") : tr(" from the top of his library"); - } else - fromStr = tr(" from library"); - } else if (startName == "sb") - fromStr = tr(" from sideboard"); - else if (startName == "stack") - fromStr = tr(" from the stack"); + bool cardNameContainsStartZone = false; + QString fromStr; + QString startName = zone->getName(); + + if (startName == "table") + fromStr = tr(" from table"); + else if (startName == "grave") + fromStr = tr(" from graveyard"); + else if (startName == "rfg") + fromStr = tr(" from exile"); + else if (startName == "hand") + fromStr = tr(" from hand"); + else if (startName == "deck") { + if (position >= zone->getCards().size() - 1) { + if (cardName.isEmpty()) { + if (ownerChange) + cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getName()); + else + cardName = isFemale(zone->getPlayer()) ? tr("the bottom card of her library") : tr("the bottom card of his library"); + cardNameContainsStartZone = true; + } else { + if (ownerChange) + fromStr = tr(" from the bottom of %1's library").arg(zone->getPlayer()->getName()); + else + fromStr = isFemale(zone->getPlayer()) ? tr(" from the bottom of her library") : tr(" from the bottom of his library"); + } + } else if (position == 0) { + if (cardName.isEmpty()) { + if (ownerChange) + cardName = tr("the top card of %1's library").arg(zone->getPlayer()->getName()); + else + cardName = isFemale(zone->getPlayer()) ? tr("the top card of her library") : tr("the top card of his library"); + cardNameContainsStartZone = true; + } else { + if (ownerChange) + fromStr = tr(" from the top of %1's library").arg(zone->getPlayer()->getName()); + else + fromStr = isFemale(zone->getPlayer()) ? tr(" from the top of her library") : tr(" from the top of his library"); + } + } else { + if (ownerChange) + fromStr = tr(" from %1's library").arg(zone->getPlayer()->getName()); + else + fromStr = tr(" from library"); + } + } else if (startName == "sb") + fromStr = tr(" from sideboard"); + else if (startName == "stack") + fromStr = tr(" from the stack"); - if (!cardNameContainsStartZone) - cardName.clear(); - return QPair(cardName, fromStr); + if (!cardNameContainsStartZone) + cardName.clear(); + return QPair(cardName, fromStr); } void MessageLogWidget::doMoveCard(LogMoveCard &attributes) { - QString startName = attributes.startZone->getName(); - QString targetName = attributes.targetZone->getName(); - if (((startName == "table") && (targetName == "table") && (attributes.startZone == attributes.targetZone)) || ((startName == "hand") && (targetName == "hand"))) - return; - QString cardName = attributes.cardName; - QPair temp = getFromStr(attributes.startZone, cardName, attributes.oldX); - bool cardNameContainsStartZone = false; - if (!temp.first.isEmpty()) { - cardNameContainsStartZone = true; - cardName = temp.first; - } - QString fromStr = temp.second; - QString cardStr; - if (cardNameContainsStartZone) - cardStr = cardName; - else if (cardName.isEmpty()) - cardStr = tr("a card"); - else - cardStr = QString("%1").arg(sanitizeHtml(cardName)); - - if (attributes.startZone->getPlayer() != attributes.targetZone->getPlayer()) { - appendHtml(tr("%1 gives %2 control over %3.").arg(sanitizeHtml(attributes.player->getName())).arg(sanitizeHtml(attributes.targetZone->getPlayer()->getName())).arg(cardStr)); - return; - } - - QString finalStr; - if (targetName == "table") { - soundEngine->playCard(); - if (moveCardTapped.value(attributes.card)) - finalStr = tr("%1 puts %2 into play tapped%3."); - else - finalStr = tr("%1 puts %2 into play%3."); - } else if (targetName == "grave") - finalStr = tr("%1 puts %2%3 into graveyard."); - else if (targetName == "rfg") - finalStr = tr("%1 exiles %2%3."); - else if (targetName == "hand") - finalStr = tr("%1 moves %2%3 to hand."); - else if (targetName == "deck") { - if (attributes.newX == -1) - finalStr = isFemale(attributes.targetZone->getPlayer()) ? tr("%1 puts %2%3 into her library.") : tr("%1 puts %2%3 into his library."); - else if (attributes.newX == attributes.targetZone->getCards().size() - 1) - finalStr = isFemale(attributes.targetZone->getPlayer()) ? tr("%1 puts %2%3 on bottom of her library.") : tr("%1 puts %2%3 on bottom of his library."); - else if (attributes.newX == 0) - finalStr = isFemale(attributes.targetZone->getPlayer()) ? tr("%1 puts %2%3 on top of her library.") : tr("%1 puts %2%3 on top of his library."); - else - finalStr = isFemale(attributes.targetZone->getPlayer()) ? tr("%1 puts %2%3 into her library at position %4.") : tr("%1 puts %2%3 into his library at position %4."); - } else if (targetName == "sb") - finalStr = tr("%1 moves %2%3 to sideboard."); - else if (targetName == "stack") { - soundEngine->playCard(); - finalStr = tr("%1 plays %2%3."); - } - - appendHtml(finalStr.arg(sanitizeHtml(attributes.player->getName())).arg(cardStr).arg(fromStr).arg(attributes.newX)); + bool ownerChange = attributes.startZone->getPlayer() != attributes.targetZone->getPlayer(); + QString startName = attributes.startZone->getName(); + QString targetName = attributes.targetZone->getName(); + if (((startName == "table") && (targetName == "table") && (attributes.startZone == attributes.targetZone)) || ((startName == "hand") && (targetName == "hand"))) + return; + QString cardName = attributes.cardName; + QPair temp = getFromStr(attributes.startZone, cardName, attributes.oldX, ownerChange); + bool cardNameContainsStartZone = false; + if (!temp.first.isEmpty()) { + cardNameContainsStartZone = true; + cardName = temp.first; + } + QString fromStr = temp.second; + QString cardStr; + if (cardNameContainsStartZone) + cardStr = cardName; + else if (cardName.isEmpty()) + cardStr = tr("a card"); + else + cardStr = cardLink(cardName); + + if (ownerChange && (attributes.startZone->getPlayer() == attributes.player)) { + appendHtml(tr("%1 gives %2 control over %3.").arg(sanitizeHtml(attributes.player->getName())).arg(sanitizeHtml(attributes.targetZone->getPlayer()->getName())).arg(cardStr)); + return; + } + + QString finalStr; + if (targetName == "table") { + soundEngine->playCard(); + if (moveCardTapped.value(attributes.card)) + finalStr = tr("%1 puts %2 into play tapped%3."); + else + finalStr = tr("%1 puts %2 into play%3."); + } else if (targetName == "grave") + finalStr = tr("%1 puts %2%3 into graveyard."); + else if (targetName == "rfg") + finalStr = tr("%1 exiles %2%3."); + else if (targetName == "hand") + finalStr = tr("%1 moves %2%3 to hand."); + else if (targetName == "deck") { + if (attributes.newX == -1) + finalStr = isFemale(attributes.targetZone->getPlayer()) ? tr("%1 puts %2%3 into her library.") : tr("%1 puts %2%3 into his library."); + else if (attributes.newX == attributes.targetZone->getCards().size() - 1) + finalStr = isFemale(attributes.targetZone->getPlayer()) ? tr("%1 puts %2%3 on bottom of her library.") : tr("%1 puts %2%3 on bottom of his library."); + else if (attributes.newX == 0) + finalStr = isFemale(attributes.targetZone->getPlayer()) ? tr("%1 puts %2%3 on top of her library.") : tr("%1 puts %2%3 on top of his library."); + else + finalStr = isFemale(attributes.targetZone->getPlayer()) ? tr("%1 puts %2%3 into her library at position %4.") : tr("%1 puts %2%3 into his library at position %4."); + } else if (targetName == "sb") + finalStr = tr("%1 moves %2%3 to sideboard."); + else if (targetName == "stack") { + soundEngine->playCard(); + finalStr = tr("%1 plays %2%3."); + } + + appendHtml(finalStr.arg(sanitizeHtml(attributes.player->getName())).arg(cardStr).arg(fromStr).arg(attributes.newX)); } void MessageLogWidget::logMoveCard(Player *player, CardItem *card, CardZone *startZone, int oldX, CardZone *targetZone, int newX) { - LogMoveCard attributes = {player, card, card->getName(), startZone, oldX, targetZone, newX}; - if (currentContext == MessageContext_MoveCard) - moveCardQueue.append(attributes); - else if (currentContext == MessageContext_Mulligan) - mulliganPlayer = player; - else - doMoveCard(attributes); + LogMoveCard attributes = {player, card, card->getName(), startZone, oldX, targetZone, newX}; + if (currentContext == MessageContext_MoveCard) + moveCardQueue.append(attributes); + else if (currentContext == MessageContext_Mulligan) + mulliganPlayer = player; + else + doMoveCard(attributes); } void MessageLogWidget::logMulligan(Player *player, int number) { - if (!player) - return; + if (!player) + return; - if (number > -1) { - if (isFemale(player)) - appendHtml(tr("%1 takes a mulligan to %n.", "female", number).arg(sanitizeHtml(player->getName()))); - else - appendHtml(tr("%1 takes a mulligan to %n.", "male", number).arg(sanitizeHtml(player->getName()))); - } else - appendHtml((isFemale(player) ? tr("%1 draws her initial hand.") : tr("%1 draws his initial hand.")).arg(sanitizeHtml(player->getName()))); + if (number > -1) { + if (isFemale(player)) + appendHtml(tr("%1 takes a mulligan to %n.", "female", number).arg(sanitizeHtml(player->getName()))); + else + appendHtml(tr("%1 takes a mulligan to %n.", "male", number).arg(sanitizeHtml(player->getName()))); + } else + appendHtml((isFemale(player) ? tr("%1 draws her initial hand.") : tr("%1 draws his initial hand.")).arg(sanitizeHtml(player->getName()))); } void MessageLogWidget::logFlipCard(Player *player, QString cardName, bool faceDown) { - if (faceDown) { - if (isFemale(player)) - appendHtml(tr("%1 flips %2 face-down.", "female").arg(sanitizeHtml(player->getName())).arg(cardName)); - else - appendHtml(tr("%1 flips %2 face-down.", "male").arg(sanitizeHtml(player->getName())).arg(cardName)); - } else { - if (isFemale(player)) - appendHtml(tr("%1 flips %2 face-up.", "female").arg(sanitizeHtml(player->getName())).arg(cardName)); - else - appendHtml(tr("%1 flips %2 face-up.", "male").arg(sanitizeHtml(player->getName())).arg(cardName)); - } + if (faceDown) { + if (isFemale(player)) + appendHtml(tr("%1 flips %2 face-down.", "female").arg(sanitizeHtml(player->getName())).arg(cardName)); + else + appendHtml(tr("%1 flips %2 face-down.", "male").arg(sanitizeHtml(player->getName())).arg(cardName)); + } else { + if (isFemale(player)) + appendHtml(tr("%1 flips %2 face-up.", "female").arg(sanitizeHtml(player->getName())).arg(cardName)); + else + appendHtml(tr("%1 flips %2 face-up.", "male").arg(sanitizeHtml(player->getName())).arg(cardName)); + } } void MessageLogWidget::logDestroyCard(Player *player, QString cardName) { - if (isFemale(player)) - appendHtml(tr("%1 destroys %2.", "female").arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(cardName)))); - else - appendHtml(tr("%1 destroys %2.", "male").arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(cardName)))); + if (isFemale(player)) + appendHtml(tr("%1 destroys %2.", "female").arg(sanitizeHtml(player->getName())).arg(cardLink(cardName))); + else + appendHtml(tr("%1 destroys %2.", "male").arg(sanitizeHtml(player->getName())).arg(cardLink(cardName))); } void MessageLogWidget::logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName) { - QString str; - if (isFemale(player)) { - if (isFemale(targetPlayer)) - str = tr("%1 attaches %2 to %3's %4.", "p1 female, p2 female"); - else - str = tr("%1 attaches %2 to %3's %4.", "p1 female, p2 male"); - } else { - if (isFemale(targetPlayer)) - str = tr("%1 attaches %2 to %3's %4.", "p1 male, p2 female"); - else - str = tr("%1 attaches %2 to %3's %4.", "p1 male, p2 male"); - } - - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(cardName))).arg(sanitizeHtml(targetPlayer->getName())).arg(QString("%1").arg(sanitizeHtml(targetCardName)))); + QString str; + if (isFemale(player)) { + if (isFemale(targetPlayer)) + str = tr("%1 attaches %2 to %3's %4.", "p1 female, p2 female"); + else + str = tr("%1 attaches %2 to %3's %4.", "p1 female, p2 male"); + } else { + if (isFemale(targetPlayer)) + str = tr("%1 attaches %2 to %3's %4.", "p1 male, p2 female"); + else + str = tr("%1 attaches %2 to %3's %4.", "p1 male, p2 male"); + } + + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardLink(cardName)).arg(sanitizeHtml(targetPlayer->getName())).arg(cardLink(targetCardName))); } void MessageLogWidget::logUnattachCard(Player *player, QString cardName) { - if (isFemale(player)) - appendHtml(tr("%1 unattaches %2.", "female").arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(cardName)))); - else - appendHtml(tr("%1 unattaches %2.", "male").arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(cardName)))); + if (isFemale(player)) + appendHtml(tr("%1 unattaches %2.", "female").arg(sanitizeHtml(player->getName())).arg(cardLink(cardName))); + else + appendHtml(tr("%1 unattaches %2.", "male").arg(sanitizeHtml(player->getName())).arg(cardLink(cardName))); } void MessageLogWidget::logCreateToken(Player *player, QString cardName, QString pt) { - if (isFemale(player)) - appendHtml(tr("%1 creates token: %2%3.", "female").arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(cardName))).arg(pt.isEmpty() ? QString() : QString(" (%1)").arg(sanitizeHtml(pt)))); - else - appendHtml(tr("%1 creates token: %2%3.", "male").arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(cardName))).arg(pt.isEmpty() ? QString() : QString(" (%1)").arg(sanitizeHtml(pt)))); + if (isFemale(player)) + appendHtml(tr("%1 creates token: %2%3.", "female").arg(sanitizeHtml(player->getName())).arg(cardLink(cardName)).arg(pt.isEmpty() ? QString() : QString(" (%1)").arg(sanitizeHtml(pt)))); + else + appendHtml(tr("%1 creates token: %2%3.", "male").arg(sanitizeHtml(player->getName())).arg(cardLink(cardName)).arg(pt.isEmpty() ? QString() : QString(" (%1)").arg(sanitizeHtml(pt)))); } void MessageLogWidget::logCreateArrow(Player *player, Player *startPlayer, QString startCard, Player *targetPlayer, QString targetCard, bool playerTarget) { - startCard = QString("%1").arg(sanitizeHtml(startCard)); - targetCard = QString("%1").arg(sanitizeHtml(targetCard)); - QString str; - if (playerTarget) { - if ((player == startPlayer) && (player == targetPlayer)) { - if (isFemale(player)) - str = tr("%1 points from her %2 to herself.", "female"); - else - str = tr("%1 points from his %2 to himself.", "male"); - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(startCard)); - } else if (player == startPlayer) { - if (isFemale(player)) { - if (isFemale(targetPlayer)) - str = tr("%1 points from her %2 to %3.", "p1 female, p2 female"); - else - str = tr("%1 points from her %2 to %3.", "p1 female, p2 male"); - } else { - if (isFemale(targetPlayer)) - str = tr("%1 points from his %2 to %3.", "p1 male, p2 female"); - else - str = tr("%1 points from his %2 to %3.", "p1 male, p2 male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(startCard).arg(sanitizeHtml(targetPlayer->getName()))); - } else if (player == targetPlayer) { - if (isFemale(player)) { - if (isFemale(startPlayer)) - str = tr("%1 points from %2's %3 to herself.", "card owner female, target female"); - else - str = tr("%1 points from %2's %3 to herself.", "card owner male, target female"); - } else { - if (isFemale(startPlayer)) - str = tr("%1 points from %2's %3 to himself.", "card owner female, target male"); - else - str = tr("%1 points from %2's %3 to himself.", "card owner male, target male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(sanitizeHtml(startPlayer->getName())).arg(startCard)); - } else { - if (isFemale(player)) { - if (isFemale(startPlayer)) { - if (isFemale(targetPlayer)) - str = tr("%1 points from %2's %3 to %4.", "p1 female, p2 female, p3 female"); - else - str = tr("%1 points from %2's %3 to %4.", "p1 female, p2 female, p3 male"); - } else { - if (isFemale(targetPlayer)) - str = tr("%1 points from %2's %3 to %4.", "p1 female, p2 male, p3 female"); - else - str = tr("%1 points from %2's %3 to %4.", "p1 female, p2 male, p3 male"); - } - } else { - if (isFemale(startPlayer)) { - if (isFemale(targetPlayer)) - str = tr("%1 points from %2's %3 to %4.", "p1 male, p2 female, p3 female"); - else - str = tr("%1 points from %2's %3 to %4.", "p1 male, p2 female, p3 male"); - } else { - if (isFemale(targetPlayer)) - str = tr("%1 points from %2's %3 to %4.", "p1 male, p2 male, p3 female"); - else - str = tr("%1 points from %2's %3 to %4.", "p1 male, p2 male, p3 male"); - } - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(sanitizeHtml(startPlayer->getName())).arg(startCard).arg(sanitizeHtml(targetPlayer->getName()))); - } - } else { - if ((player == startPlayer) && (player == targetPlayer)) { - if (isFemale(player)) - str = tr("%1 points from her %2 to her %3.", "female"); - else - str = tr("%1 points from his %2 to his %3.", "male"); - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(startCard).arg(targetCard)); - } else if (player == startPlayer) { - if (isFemale(player)) { - if (isFemale(targetPlayer)) - str = tr("%1 points from her %2 to %3's %4.", "p1 female, p2 female"); - else - str = tr("%1 points from her %2 to %3's %4.", "p1 female, p2 male"); - } else { - if (isFemale(targetPlayer)) - str = tr("%1 points from his %2 to %3's %4.", "p1 male, p2 female"); - else - str = tr("%1 points from his %2 to %3's %4.", "p1 male, p2 male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(startCard).arg(sanitizeHtml(targetPlayer->getName())).arg(targetCard)); - } else if (player == targetPlayer) { - if (isFemale(player)) { - if (isFemale(startPlayer)) - str = tr("%1 points from %2's %3 to her own %4.", "card owner female, target female"); - else - str = tr("%1 points from %2's %3 to her own %4.", "card owner male, target female"); - } else { - if (isFemale(startPlayer)) - str = tr("%1 points from %2's %3 to his own %4.", "card owner female, target male"); - else - str = tr("%1 points from %2's %3 to his own %4.", "card owner male, target male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(sanitizeHtml(startPlayer->getName())).arg(startCard).arg(targetCard)); - } else { - if (isFemale(player)) { - if (isFemale(startPlayer)) { - if (isFemale(targetPlayer)) - str = tr("%1 points from %2's %3 to %4's %5.", "p1 female, p2 female, p3 female"); - else - str = tr("%1 points from %2's %3 to %4's %5.", "p1 female, p2 female, p3 male"); - } else { - if (isFemale(targetPlayer)) - str = tr("%1 points from %2's %3 to %4's %5.", "p1 female, p2 male, p3 female"); - else - str = tr("%1 points from %2's %3 to %4's %5.", "p1 female, p2 male, p3 male"); - } - } else { - if (isFemale(startPlayer)) { - if (isFemale(targetPlayer)) - str = tr("%1 points from %2's %3 to %4's %5.", "p1 male, p2 female, p3 female"); - else - str = tr("%1 points from %2's %3 to %4's %5.", "p1 male, p2 female, p3 male"); - } else { - if (isFemale(targetPlayer)) - str = tr("%1 points from %2's %3 to %4's %5.", "p1 male, p2 male, p3 female"); - else - str = tr("%1 points from %2's %3 to %4's %5.", "p1 male, p2 male, p3 male"); - } - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(sanitizeHtml(startPlayer->getName())).arg(startCard).arg(sanitizeHtml(targetPlayer->getName())).arg(targetCard)); - } - } + startCard = cardLink(startCard); + targetCard = cardLink(targetCard); + QString str; + if (playerTarget) { + if ((player == startPlayer) && (player == targetPlayer)) { + if (isFemale(player)) + str = tr("%1 points from her %2 to herself.", "female"); + else + str = tr("%1 points from his %2 to himself.", "male"); + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(startCard)); + } else if (player == startPlayer) { + if (isFemale(player)) { + if (isFemale(targetPlayer)) + str = tr("%1 points from her %2 to %3.", "p1 female, p2 female"); + else + str = tr("%1 points from her %2 to %3.", "p1 female, p2 male"); + } else { + if (isFemale(targetPlayer)) + str = tr("%1 points from his %2 to %3.", "p1 male, p2 female"); + else + str = tr("%1 points from his %2 to %3.", "p1 male, p2 male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(startCard).arg(sanitizeHtml(targetPlayer->getName()))); + } else if (player == targetPlayer) { + if (isFemale(player)) { + if (isFemale(startPlayer)) + str = tr("%1 points from %2's %3 to herself.", "card owner female, target female"); + else + str = tr("%1 points from %2's %3 to herself.", "card owner male, target female"); + } else { + if (isFemale(startPlayer)) + str = tr("%1 points from %2's %3 to himself.", "card owner female, target male"); + else + str = tr("%1 points from %2's %3 to himself.", "card owner male, target male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(sanitizeHtml(startPlayer->getName())).arg(startCard)); + } else { + if (isFemale(player)) { + if (isFemale(startPlayer)) { + if (isFemale(targetPlayer)) + str = tr("%1 points from %2's %3 to %4.", "p1 female, p2 female, p3 female"); + else + str = tr("%1 points from %2's %3 to %4.", "p1 female, p2 female, p3 male"); + } else { + if (isFemale(targetPlayer)) + str = tr("%1 points from %2's %3 to %4.", "p1 female, p2 male, p3 female"); + else + str = tr("%1 points from %2's %3 to %4.", "p1 female, p2 male, p3 male"); + } + } else { + if (isFemale(startPlayer)) { + if (isFemale(targetPlayer)) + str = tr("%1 points from %2's %3 to %4.", "p1 male, p2 female, p3 female"); + else + str = tr("%1 points from %2's %3 to %4.", "p1 male, p2 female, p3 male"); + } else { + if (isFemale(targetPlayer)) + str = tr("%1 points from %2's %3 to %4.", "p1 male, p2 male, p3 female"); + else + str = tr("%1 points from %2's %3 to %4.", "p1 male, p2 male, p3 male"); + } + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(sanitizeHtml(startPlayer->getName())).arg(startCard).arg(sanitizeHtml(targetPlayer->getName()))); + } + } else { + if ((player == startPlayer) && (player == targetPlayer)) { + if (isFemale(player)) + str = tr("%1 points from her %2 to her %3.", "female"); + else + str = tr("%1 points from his %2 to his %3.", "male"); + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(startCard).arg(targetCard)); + } else if (player == startPlayer) { + if (isFemale(player)) { + if (isFemale(targetPlayer)) + str = tr("%1 points from her %2 to %3's %4.", "p1 female, p2 female"); + else + str = tr("%1 points from her %2 to %3's %4.", "p1 female, p2 male"); + } else { + if (isFemale(targetPlayer)) + str = tr("%1 points from his %2 to %3's %4.", "p1 male, p2 female"); + else + str = tr("%1 points from his %2 to %3's %4.", "p1 male, p2 male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(startCard).arg(sanitizeHtml(targetPlayer->getName())).arg(targetCard)); + } else if (player == targetPlayer) { + if (isFemale(player)) { + if (isFemale(startPlayer)) + str = tr("%1 points from %2's %3 to her own %4.", "card owner female, target female"); + else + str = tr("%1 points from %2's %3 to her own %4.", "card owner male, target female"); + } else { + if (isFemale(startPlayer)) + str = tr("%1 points from %2's %3 to his own %4.", "card owner female, target male"); + else + str = tr("%1 points from %2's %3 to his own %4.", "card owner male, target male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(sanitizeHtml(startPlayer->getName())).arg(startCard).arg(targetCard)); + } else { + if (isFemale(player)) { + if (isFemale(startPlayer)) { + if (isFemale(targetPlayer)) + str = tr("%1 points from %2's %3 to %4's %5.", "p1 female, p2 female, p3 female"); + else + str = tr("%1 points from %2's %3 to %4's %5.", "p1 female, p2 female, p3 male"); + } else { + if (isFemale(targetPlayer)) + str = tr("%1 points from %2's %3 to %4's %5.", "p1 female, p2 male, p3 female"); + else + str = tr("%1 points from %2's %3 to %4's %5.", "p1 female, p2 male, p3 male"); + } + } else { + if (isFemale(startPlayer)) { + if (isFemale(targetPlayer)) + str = tr("%1 points from %2's %3 to %4's %5.", "p1 male, p2 female, p3 female"); + else + str = tr("%1 points from %2's %3 to %4's %5.", "p1 male, p2 female, p3 male"); + } else { + if (isFemale(targetPlayer)) + str = tr("%1 points from %2's %3 to %4's %5.", "p1 male, p2 male, p3 female"); + else + str = tr("%1 points from %2's %3 to %4's %5.", "p1 male, p2 male, p3 male"); + } + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(sanitizeHtml(startPlayer->getName())).arg(startCard).arg(sanitizeHtml(targetPlayer->getName())).arg(targetCard)); + } + } } void MessageLogWidget::logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue) { - QString finalStr, colorStr; - - int delta = abs(oldValue - value); - if (value > oldValue) { - if (isFemale(player)) - finalStr = tr("%1 places %n %2 counter(s) on %3 (now %4).", "female", delta); - else - finalStr = tr("%1 places %n %2 counter(s) on %3 (now %4).", "male", delta); - } else { - if (isFemale(player)) - finalStr = tr("%1 removes %n %2 counter(s) from %3 (now %4).", "female", delta); - else - finalStr = tr("%1 removes %n %2 counter(s) from %3 (now %4).", "male", delta); - } - - switch (counterId) { - case 0: colorStr = tr("red", "", delta); break; - case 1: colorStr = tr("yellow", "", delta); break; - case 2: colorStr = tr("green", "", delta); break; - default: ; - } - - appendHtml(finalStr.arg(sanitizeHtml(player->getName())).arg(colorStr).arg(QString("%1").arg(sanitizeHtml(cardName))).arg(value)); + QString finalStr, colorStr; + + int delta = abs(oldValue - value); + if (value > oldValue) { + if (isFemale(player)) + finalStr = tr("%1 places %n %2 counter(s) on %3 (now %4).", "female", delta); + else + finalStr = tr("%1 places %n %2 counter(s) on %3 (now %4).", "male", delta); + } else { + if (isFemale(player)) + finalStr = tr("%1 removes %n %2 counter(s) from %3 (now %4).", "female", delta); + else + finalStr = tr("%1 removes %n %2 counter(s) from %3 (now %4).", "male", delta); + } + + switch (counterId) { + case 0: colorStr = tr("red", "", delta); break; + case 1: colorStr = tr("yellow", "", delta); break; + case 2: colorStr = tr("green", "", delta); break; + default: ; + } + + appendHtml(finalStr.arg(sanitizeHtml(player->getName())).arg(colorStr).arg(cardLink(cardName)).arg(value)); } void MessageLogWidget::logSetTapped(Player *player, CardItem *card, bool tapped) { - if (tapped) - soundEngine->tap(); - else - soundEngine->untap(); - - if (currentContext == MessageContext_MoveCard) - moveCardTapped.insert(card, tapped); - else { - QString str; - if (!card) { - if (isFemale(player)) { - if (tapped) - str = tr("%1 taps her permanents.", "female"); - else - str = tr("%1 untaps her permanents.", "female"); - } else { - if (tapped) - str = tr("%1 taps his permanents.", "male"); - else - str = tr("%1 untaps his permanents.", "male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName()))); - } else { - if (isFemale(player)) { - if (tapped) - str = tr("%1 taps %2.", "female"); - else - str = tr("%1 untaps %2.", "female"); - } else { - if (tapped) - str = tr("%1 taps %2.", "male"); - else - str = tr("%1 untaps %2.", "male"); - } - QString cardStr = QString("%1").arg(sanitizeHtml(card->getName())); - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardStr)); - } - } + if (tapped) + soundEngine->tap(); + else + soundEngine->untap(); + + if (currentContext == MessageContext_MoveCard) + moveCardTapped.insert(card, tapped); + else { + QString str; + if (!card) { + if (isFemale(player)) { + if (tapped) + str = tr("%1 taps her permanents.", "female"); + else + str = tr("%1 untaps her permanents.", "female"); + } else { + if (tapped) + str = tr("%1 taps his permanents.", "male"); + else + str = tr("%1 untaps his permanents.", "male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName()))); + } else { + if (isFemale(player)) { + if (tapped) + str = tr("%1 taps %2.", "female"); + else + str = tr("%1 untaps %2.", "female"); + } else { + if (tapped) + str = tr("%1 taps %2.", "male"); + else + str = tr("%1 untaps %2.", "male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardLink(card->getName()))); + } + } } void MessageLogWidget::logSetCounter(Player *player, QString counterName, int value, int oldValue) { - QString str; - if (isFemale(player)) - str = tr("%1 sets counter %2 to %3 (%4%5).", "female"); - else - str = tr("%1 sets counter %2 to %3 (%4%5).", "male"); - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(counterName))).arg(QString("%1").arg(value)).arg(value > oldValue ? "+" : "").arg(value - oldValue)); + QString str; + if (isFemale(player)) + str = tr("%1 sets counter %2 to %3 (%4%5).", "female"); + else + str = tr("%1 sets counter %2 to %3 (%4%5).", "male"); + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(counterName))).arg(QString("%1").arg(value)).arg(value > oldValue ? "+" : "").arg(value - oldValue)); } void MessageLogWidget::logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap) { - QString str; - if (doesntUntap) { - if (isFemale(player)) - str = tr("%1 sets %2 to not untap normally.", "female"); - else - str = tr("%1 sets %2 to not untap normally.", "male"); - } else { - if (isFemale(player)) - str = tr("%1 sets %2 to untap normally.", "female"); - else - str = tr("%1 sets %2 to untap normally.", "male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(card->getName())))); + QString str; + if (doesntUntap) { + if (isFemale(player)) + str = tr("%1 sets %2 to not untap normally.", "female"); + else + str = tr("%1 sets %2 to not untap normally.", "male"); + } else { + if (isFemale(player)) + str = tr("%1 sets %2 to untap normally.", "female"); + else + str = tr("%1 sets %2 to untap normally.", "male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardLink(card->getName()))); } void MessageLogWidget::logSetPT(Player *player, CardItem *card, QString newPT) { - if (currentContext == MessageContext_MoveCard) - moveCardPT.insert(card, newPT); - else { - QString str; - if (isFemale(player)) - str = tr("%1 sets PT of %2 to %3.", "female"); - else - str = tr("%1 sets PT of %2 to %3.", "male"); - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(card->getName()))).arg(QString("%1").arg(sanitizeHtml(newPT)))); - } + if (currentContext == MessageContext_MoveCard) + moveCardPT.insert(card, newPT); + else { + QString str; + if (isFemale(player)) + str = tr("%1 sets PT of %2 to %3.", "female"); + else + str = tr("%1 sets PT of %2 to %3.", "male"); + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardLink(card->getName())).arg(QString("%1").arg(sanitizeHtml(newPT)))); + } } void MessageLogWidget::logSetAnnotation(Player *player, CardItem *card, QString newAnnotation) { - QString str; - if (isFemale(player)) - str = tr("%1 sets annotation of %2 to %3.", "female"); - else - str = tr("%1 sets annotation of %2 to %3.", "male"); - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(QString("%1").arg(sanitizeHtml(card->getName()))).arg(QString("%1").arg(sanitizeHtml(newAnnotation)))); + QString str; + if (isFemale(player)) + str = tr("%1 sets annotation of %2 to %3.", "female"); + else + str = tr("%1 sets annotation of %2 to %3.", "male"); + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardLink(card->getName())).arg(QString(""%1"").arg(sanitizeHtml(newAnnotation)))); } void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCards) { - if (numberCards != -1) { - if (isFemale(player)) - appendHtml(tr("%1 is looking at the top %2 cards %3.", "female").arg(sanitizeHtml(player->getName())).arg(numberCards).arg(zone->getTranslatedName(zone->getPlayer() == player, CaseGenitive))); - else - appendHtml(tr("%1 is looking at the top %2 cards %3.", "male").arg(sanitizeHtml(player->getName())).arg(numberCards).arg(zone->getTranslatedName(zone->getPlayer() == player, CaseGenitive))); - } else { - if (isFemale(player)) - appendHtml(tr("%1 is looking at %2.", "female").arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(zone->getPlayer() == player, CaseAccusative))); - else - appendHtml(tr("%1 is looking at %2.", "male").arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(zone->getPlayer() == player, CaseAccusative))); - } + if (numberCards == -1) + appendHtml((isFemale(player) + ? tr("%1 is looking at %2.", "female") + : tr("%1 is looking at %2.", "male") + ).arg(sanitizeHtml(player->getName())) + .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone))); + else + appendHtml((isFemale(player) + ? tr("%1 is looking at the top %n card(s) %2.", "female", numberCards) + : tr("%1 is looking at the top %n card(s) %2.", "male", numberCards) + ).arg(sanitizeHtml(player->getName())) + .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone))); } void MessageLogWidget::logStopDumpZone(Player *player, CardZone *zone) { - QString zoneName = zone->getTranslatedName(zone->getPlayer() == player, CaseAccusative); - if (isFemale(player)) - appendHtml(tr("%1 stops looking at %2.", "female").arg(sanitizeHtml(player->getName())).arg(zoneName)); - else - appendHtml(tr("%1 stops looking at %2.", "male").arg(sanitizeHtml(player->getName())).arg(zoneName)); + appendHtml((isFemale(player) + ? tr("%1 stops looking at %2.", "female") + : tr("%1 stops looking at %2.", "male") + ).arg(sanitizeHtml(player->getName())) + .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone))); } -void MessageLogWidget::logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer) +void MessageLogWidget::logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer, bool faceDown) { - QPair temp = getFromStr(zone, cardName, cardId); - bool cardNameContainsStartZone = false; - if (!temp.first.isEmpty()) { - cardNameContainsStartZone = true; - cardName = temp.first; - } - QString fromStr = temp.second; + QPair temp = getFromStr(zone, cardName, cardId, false); + bool cardNameContainsStartZone = false; + if (!temp.first.isEmpty()) { + cardNameContainsStartZone = true; + cardName = temp.first; + } + QString fromStr = temp.second; - QString cardStr; - if (cardNameContainsStartZone) - cardStr = cardName; - else if (cardName.isEmpty()) - cardStr = tr("a card"); - else - cardStr = QString("%1").arg(sanitizeHtml(cardName)); + QString cardStr; + if (cardNameContainsStartZone) + cardStr = cardName; + else if (cardName.isEmpty()) + cardStr = tr("a card"); + else + cardStr = cardLink(cardName); - QString str; - if (cardId == -1) { - if (otherPlayer) { - if (isFemale(player)) { - if (isFemale(otherPlayer)) - str = tr("%1 reveals %2 to %3.", "p1 female, p2 female"); - else - str = tr("%1 reveals %2 to %3.", "p1 female, p2 male"); - } else { - if (isFemale(otherPlayer)) - str = tr("%1 reveals %2 to %3.", "p1 male, p2 female"); - else - str = tr("%1 reveals %2 to %3.", "p1 male, p2 male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(true, CaseAccusative)).arg(sanitizeHtml(otherPlayer->getName()))); - } else { - if (isFemale(player)) - appendHtml(tr("%1 reveals %2.", "female").arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(true, CaseAccusative))); - else - appendHtml(tr("%1 reveals %2.", "male").arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(true, CaseAccusative))); - } - } else if (cardId == -2) { - if (otherPlayer) { - if (isFemale(player)) { - if (isFemale(otherPlayer)) - str = tr("%1 randomly reveals %2%3 to %4.", "p1 female, p2 female"); - else - str = tr("%1 randomly reveals %2%3 to %4.", "p1 female, p2 male"); - } else { - if (isFemale(otherPlayer)) - str = tr("%1 randomly reveals %2%3 to %4.", "p1 male, p2 female"); - else - str = tr("%1 randomly reveals %2%3 to %4.", "p1 male, p2 male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr).arg(sanitizeHtml(otherPlayer->getName()))); - } else { - if (isFemale(player)) - appendHtml(tr("%1 randomly reveals %2%3.", "female").arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(true, CaseAccusative))); - else - appendHtml(tr("%1 randomly reveals %2%3.", "male").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); - } - } else { - if (otherPlayer) { - if (isFemale(player)) { - if (isFemale(otherPlayer)) - str = tr("%1 reveals %2%3 to %4.", "p1 female, p2 female"); - else - str = tr("%1 reveals %2%3 to %4.", "p1 female, p2 male"); - } else { - if (isFemale(otherPlayer)) - str = tr("%1 reveals %2%3 to %4.", "p1 male, p2 female"); - else - str = tr("%1 reveals %2%3 to %4.", "p1 male, p2 male"); - } - appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr).arg(sanitizeHtml(otherPlayer->getName()))); - } else { - if (isFemale(player)) - appendHtml(tr("%1 reveals %2%3.", "female").arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(true, CaseAccusative))); - else - appendHtml(tr("%1 reveals %2%3.", "male").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); - } - } + QString str; + if (cardId == -1) { + if (otherPlayer) { + if (isFemale(player)) { + if (isFemale(otherPlayer)) + str = tr("%1 reveals %2 to %3.", "p1 female, p2 female"); + else + str = tr("%1 reveals %2 to %3.", "p1 female, p2 male"); + } else { + if (isFemale(otherPlayer)) + str = tr("%1 reveals %2 to %3.", "p1 male, p2 female"); + else + str = tr("%1 reveals %2 to %3.", "p1 male, p2 male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(zone->getTranslatedName(true, CaseRevealZone)).arg(sanitizeHtml(otherPlayer->getName()))); + } else { + appendHtml((isFemale(player) + ? tr("%1 reveals %2.", "female") + : tr("%1 reveals %2.", "male") + ).arg(sanitizeHtml(player->getName())) + .arg(zone->getTranslatedName(true, CaseRevealZone))); + } + } else if (cardId == -2) { + if (otherPlayer) { + if (isFemale(player)) { + if (isFemale(otherPlayer)) + str = tr("%1 randomly reveals %2%3 to %4.", "p1 female, p2 female"); + else + str = tr("%1 randomly reveals %2%3 to %4.", "p1 female, p2 male"); + } else { + if (isFemale(otherPlayer)) + str = tr("%1 randomly reveals %2%3 to %4.", "p1 male, p2 female"); + else + str = tr("%1 randomly reveals %2%3 to %4.", "p1 male, p2 male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr).arg(sanitizeHtml(otherPlayer->getName()))); + } else { + if (isFemale(player)) + appendHtml(tr("%1 randomly reveals %2%3.", "female").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); + else + appendHtml(tr("%1 randomly reveals %2%3.", "male").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); + } + } else { + if (faceDown && (player == otherPlayer)) { + if (cardName.isEmpty()) { + if (isFemale(player)) + str = tr("%1 peeks at face down card #%2.", "female"); + else + str = tr("%1 peeks at face down card #%2.", "male"); + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardId)); + } else { + if (isFemale(player)) + str = tr("%1 peeks at face down card #%2: %3.", "female"); + else + str = tr("%1 peeks at face down card #%2: %3.", "male"); + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardId).arg(cardStr)); + } + } else if (otherPlayer) { + if (isFemale(player)) { + if (isFemale(otherPlayer)) + str = tr("%1 reveals %2%3 to %4.", "p1 female, p2 female"); + else + str = tr("%1 reveals %2%3 to %4.", "p1 female, p2 male"); + } else { + if (isFemale(otherPlayer)) + str = tr("%1 reveals %2%3 to %4.", "p1 male, p2 female"); + else + str = tr("%1 reveals %2%3 to %4.", "p1 male, p2 male"); + } + appendHtml(str.arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr).arg(sanitizeHtml(otherPlayer->getName()))); + } else { + if (isFemale(player)) + appendHtml(tr("%1 reveals %2%3.", "female").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); + else + appendHtml(tr("%1 reveals %2%3.", "male").arg(sanitizeHtml(player->getName())).arg(cardStr).arg(fromStr)); + } + } +} + +void MessageLogWidget::logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal) +{ + appendHtml((reveal + ? tr("%1 is now keeping the top card %2 revealed.") + : tr("%1 is not revealing the top card %2 any longer.") + ).arg(sanitizeHtml(player->getName())) + .arg(zone->getTranslatedName(true, CaseTopCardsOfZone)) + ); } void MessageLogWidget::logSetActivePlayer(Player *player) { - soundEngine->notification(); - - QString str; - if (isFemale(player)) - str = tr("It is now %1's turn.", "female"); - else - str = tr("It is now %1's turn.", "male"); - appendHtml("
" + str.arg(player->getName()) + "
"); + soundEngine->notification(); + + QString str; + if (isFemale(player)) + str = tr("It is now %1's turn.", "female"); + else + str = tr("It is now %1's turn.", "male"); + appendHtml("
" + str.arg(player->getName()) + "
"); } void MessageLogWidget::logSetActivePhase(int phase) { - soundEngine->notification(); - QString phaseName; - switch (phase) { - case 0: phaseName = tr("untap step"); break; - case 1: phaseName = tr("upkeep step"); break; - case 2: phaseName = tr("draw step"); break; - case 3: phaseName = tr("first main phase"); break; - case 4: phaseName = tr("beginning of combat step"); break; - case 5: phaseName = tr("declare attackers step"); break; - case 6: phaseName = tr("declare blockers step"); break; - case 7: phaseName = tr("combat damage step"); break; - case 8: phaseName = tr("end of combat step"); break; - case 9: phaseName = tr("second main phase"); break; - case 10: phaseName = tr("ending phase"); break; - } - appendHtml("" + tr("It is now the %1.").arg(phaseName) + ""); + soundEngine->notification(); + QString phaseName; + switch (phase) { + case 0: phaseName = tr("untap step"); break; + case 1: phaseName = tr("upkeep step"); break; + case 2: phaseName = tr("draw step"); break; + case 3: phaseName = tr("first main phase"); break; + case 4: phaseName = tr("beginning of combat step"); break; + case 5: phaseName = tr("declare attackers step"); break; + case 6: phaseName = tr("declare blockers step"); break; + case 7: phaseName = tr("combat damage step"); break; + case 8: phaseName = tr("end of combat step"); break; + case 9: phaseName = tr("second main phase"); break; + case 10: phaseName = tr("ending phase"); break; + } + appendHtml("" + tr("It is now the %1.").arg(phaseName) + ""); } -void MessageLogWidget::containerProcessingStarted(GameEventContext *_context) +void MessageLogWidget::containerProcessingStarted(const GameEventContext &_context) { - if (qobject_cast(_context)) - currentContext = MessageContext_MoveCard; - else if (qobject_cast(_context)) { - currentContext = MessageContext_Mulligan; - mulliganPlayer = 0; - mulliganNumber = static_cast(_context)->getNumber(); - } + if (_context.HasExtension(Context_MoveCard::ext)) + currentContext = MessageContext_MoveCard; + else if (_context.HasExtension(Context_Mulligan::ext)) { + const Context_Mulligan &contextMulligan = _context.GetExtension(Context_Mulligan::ext); + currentContext = MessageContext_Mulligan; + mulliganPlayer = 0; + mulliganNumber = contextMulligan.number(); + } } void MessageLogWidget::containerProcessingDone() { - if (currentContext == MessageContext_MoveCard) { - for (int i = 0; i < moveCardQueue.size(); ++i) - doMoveCard(moveCardQueue[i]); - moveCardQueue.clear(); - moveCardPT.clear(); - moveCardTapped.clear(); - } else if (currentContext == MessageContext_Mulligan) { - logMulligan(mulliganPlayer, mulliganNumber); - mulliganPlayer = 0; - mulliganNumber = 0; - } - - currentContext = MessageContext_None; + if (currentContext == MessageContext_MoveCard) { + for (int i = 0; i < moveCardQueue.size(); ++i) + doMoveCard(moveCardQueue[i]); + moveCardQueue.clear(); + moveCardPT.clear(); + moveCardTapped.clear(); + } else if (currentContext == MessageContext_Mulligan) { + logMulligan(mulliganPlayer, mulliganNumber); + mulliganPlayer = 0; + mulliganNumber = 0; + } + + currentContext = MessageContext_None; } void MessageLogWidget::connectToPlayer(Player *player) { - connect(player, SIGNAL(logConnectionStateChanged(Player *, bool)), this, SLOT(logConnectionStateChanged(Player *, bool))); - connect(player, SIGNAL(logSay(Player *, QString)), this, SLOT(logSay(Player *, QString))); - connect(player, SIGNAL(logShuffle(Player *, CardZone *)), this, SLOT(logShuffle(Player *, CardZone *))); - connect(player, SIGNAL(logRollDie(Player *, int, int)), this, SLOT(logRollDie(Player *, int, int))); - connect(player, SIGNAL(logCreateArrow(Player *, Player *, QString, Player *, QString, bool)), this, SLOT(logCreateArrow(Player *, Player *, QString, Player *, QString, bool))); - connect(player, SIGNAL(logCreateToken(Player *, QString, QString)), this, SLOT(logCreateToken(Player *, QString, QString))); - connect(player, SIGNAL(logSetCounter(Player *, QString, int, int)), this, SLOT(logSetCounter(Player *, QString, int, int))); - connect(player, SIGNAL(logSetCardCounter(Player *, QString, int, int, int)), this, SLOT(logSetCardCounter(Player *, QString, int, int, int))); - connect(player, SIGNAL(logSetTapped(Player *, CardItem *, bool)), this, SLOT(logSetTapped(Player *, CardItem *, bool))); - connect(player, SIGNAL(logSetDoesntUntap(Player *, CardItem *, bool)), this, SLOT(logSetDoesntUntap(Player *, CardItem *, bool))); - connect(player, SIGNAL(logSetPT(Player *, CardItem *, QString)), this, SLOT(logSetPT(Player *, CardItem *, QString))); - connect(player, SIGNAL(logSetAnnotation(Player *, CardItem *, QString)), this, SLOT(logSetAnnotation(Player *, CardItem *, QString))); - connect(player, SIGNAL(logMoveCard(Player *, CardItem *, CardZone *, int, CardZone *, int)), this, SLOT(logMoveCard(Player *, CardItem *, CardZone *, int, CardZone *, int))); - connect(player, SIGNAL(logFlipCard(Player *, QString, bool)), this, SLOT(logFlipCard(Player *, QString, bool))); - connect(player, SIGNAL(logDestroyCard(Player *, QString)), this, SLOT(logDestroyCard(Player *, QString))); - connect(player, SIGNAL(logAttachCard(Player *, QString, Player *, QString)), this, SLOT(logAttachCard(Player *, QString, Player *, QString))); - connect(player, SIGNAL(logUnattachCard(Player *, QString)), this, SLOT(logUnattachCard(Player *, QString))); - connect(player, SIGNAL(logDumpZone(Player *, CardZone *, int)), this, SLOT(logDumpZone(Player *, CardZone *, int))); - connect(player, SIGNAL(logStopDumpZone(Player *, CardZone *)), this, SLOT(logStopDumpZone(Player *, CardZone *))); - connect(player, SIGNAL(logDrawCards(Player *, int)), this, SLOT(logDrawCards(Player *, int))); - connect(player, SIGNAL(logUndoDraw(Player *, QString)), this, SLOT(logUndoDraw(Player *, QString))); - connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *)), this, SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *))); + connect(player, SIGNAL(logSay(Player *, QString)), this, SLOT(logSay(Player *, QString))); + connect(player, SIGNAL(logShuffle(Player *, CardZone *)), this, SLOT(logShuffle(Player *, CardZone *))); + connect(player, SIGNAL(logRollDie(Player *, int, int)), this, SLOT(logRollDie(Player *, int, int))); + connect(player, SIGNAL(logCreateArrow(Player *, Player *, QString, Player *, QString, bool)), this, SLOT(logCreateArrow(Player *, Player *, QString, Player *, QString, bool))); + connect(player, SIGNAL(logCreateToken(Player *, QString, QString)), this, SLOT(logCreateToken(Player *, QString, QString))); + connect(player, SIGNAL(logSetCounter(Player *, QString, int, int)), this, SLOT(logSetCounter(Player *, QString, int, int))); + connect(player, SIGNAL(logSetCardCounter(Player *, QString, int, int, int)), this, SLOT(logSetCardCounter(Player *, QString, int, int, int))); + connect(player, SIGNAL(logSetTapped(Player *, CardItem *, bool)), this, SLOT(logSetTapped(Player *, CardItem *, bool))); + connect(player, SIGNAL(logSetDoesntUntap(Player *, CardItem *, bool)), this, SLOT(logSetDoesntUntap(Player *, CardItem *, bool))); + connect(player, SIGNAL(logSetPT(Player *, CardItem *, QString)), this, SLOT(logSetPT(Player *, CardItem *, QString))); + connect(player, SIGNAL(logSetAnnotation(Player *, CardItem *, QString)), this, SLOT(logSetAnnotation(Player *, CardItem *, QString))); + connect(player, SIGNAL(logMoveCard(Player *, CardItem *, CardZone *, int, CardZone *, int)), this, SLOT(logMoveCard(Player *, CardItem *, CardZone *, int, CardZone *, int))); + connect(player, SIGNAL(logFlipCard(Player *, QString, bool)), this, SLOT(logFlipCard(Player *, QString, bool))); + connect(player, SIGNAL(logDestroyCard(Player *, QString)), this, SLOT(logDestroyCard(Player *, QString))); + connect(player, SIGNAL(logAttachCard(Player *, QString, Player *, QString)), this, SLOT(logAttachCard(Player *, QString, Player *, QString))); + connect(player, SIGNAL(logUnattachCard(Player *, QString)), this, SLOT(logUnattachCard(Player *, QString))); + connect(player, SIGNAL(logDumpZone(Player *, CardZone *, int)), this, SLOT(logDumpZone(Player *, CardZone *, int))); + connect(player, SIGNAL(logStopDumpZone(Player *, CardZone *)), this, SLOT(logStopDumpZone(Player *, CardZone *))); + connect(player, SIGNAL(logDrawCards(Player *, int)), this, SLOT(logDrawCards(Player *, int))); + connect(player, SIGNAL(logUndoDraw(Player *, QString)), this, SLOT(logUndoDraw(Player *, QString))); + connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool)), this, SLOT(logRevealCards(Player *, CardZone *, int, QString, Player *, bool))); + connect(player, SIGNAL(logAlwaysRevealTopCard(Player *, CardZone *, bool)), this, SLOT(logAlwaysRevealTopCard(Player *, CardZone *, bool))); } -MessageLogWidget::MessageLogWidget(const QString &_ownName, bool _female, QWidget *parent) - : ChatView(_ownName, false, parent), female(_female) +MessageLogWidget::MessageLogWidget(const TabSupervisor *_tabSupervisor, TabGame *_game, QWidget *parent) + : ChatView(_tabSupervisor, _game, false, parent), currentContext(MessageContext_None) { } diff --git a/cockatrice/src/messagelogwidget.h b/cockatrice/src/messagelogwidget.h index b472dbd49..2b12b3c50 100644 --- a/cockatrice/src/messagelogwidget.h +++ b/cockatrice/src/messagelogwidget.h @@ -2,9 +2,8 @@ #define MESSAGELOGWIDGET_H #include "chatview.h" -#include #include "translation.h" -#include "protocol_datastructures.h" +#include "user_level.h" class Player; class CardZone; @@ -13,76 +12,81 @@ class GameEventContext; class CardItem; struct LogMoveCard { - Player *player; - CardItem *card; - QString cardName; - CardZone *startZone; - int oldX; - CardZone *targetZone; - int newX; + Player *player; + CardItem *card; + QString cardName; + CardZone *startZone; + int oldX; + CardZone *targetZone; + int newX; }; class MessageLogWidget : public ChatView { - Q_OBJECT + Q_OBJECT private: - enum MessageContext { MessageContext_None, MessageContext_MoveCard, MessageContext_Mulligan }; - - QString sanitizeHtml(QString dirty) const; - bool isFemale(Player *player) const; - QPair getFromStr(CardZone *zone, QString cardName, int position) const; - MessageContext currentContext; - bool female; - - QList moveCardQueue; - QMap moveCardPT; - QMap moveCardTapped; - - Player *mulliganPlayer; - int mulliganNumber; + enum MessageContext { MessageContext_None, MessageContext_MoveCard, MessageContext_Mulligan }; + + QString sanitizeHtml(QString dirty) const; + QString cardLink(const QString &cardName) const; + bool isFemale(Player *player) const; + bool userIsFemale() const; + QPair getFromStr(CardZone *zone, QString cardName, int position, bool ownerChange) const; + MessageContext currentContext; + + QList moveCardQueue; + QMap moveCardPT; + QMap moveCardTapped; + + Player *mulliganPlayer; + int mulliganNumber; public slots: - void logGameJoined(int gameId); - void logJoin(Player *player); - void logLeave(Player *player); - void logGameClosed(); - void logJoinSpectator(QString name); - void logLeaveSpectator(QString name); - void logDeckSelect(Player *player, QString deckHash); - void logReadyStart(Player *player); - void logNotReadyStart(Player *player); - void logConcede(Player *player); - void logGameStart(); - void logConnectionStateChanged(Player *player, bool connectionState); - void logSay(Player *player, QString message); - void logSpectatorSay(QString spectatorName, QString message); - void logShuffle(Player *player, CardZone *zone); - void logRollDie(Player *player, int sides, int roll); - void logDrawCards(Player *player, int number); - void logUndoDraw(Player *player, QString cardName); - void doMoveCard(LogMoveCard &attributes); - void logMoveCard(Player *player, CardItem *card, CardZone *startZone, int oldX, CardZone *targetZone, int newX); - void logMulligan(Player *player, int number); - void logFlipCard(Player *player, QString cardName, bool faceDown); - void logDestroyCard(Player *player, QString cardName); - void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName); - void logUnattachCard(Player *player, QString cardName); - void logCreateToken(Player *player, QString cardName, QString pt); - void logCreateArrow(Player *player, Player *startPlayer, QString startCard, Player *targetPlayer, QString targetCard, bool playerTarget); - void logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue); - void logSetTapped(Player *player, CardItem *card, bool tapped); - void logSetCounter(Player *player, QString counterName, int value, int oldValue); - void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap); - void logSetPT(Player *player, CardItem *card, QString newPT); - void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); - void logDumpZone(Player *player, CardZone *zone, int numberCards); - void logStopDumpZone(Player *player, CardZone *zone); - void logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer); - void logSetActivePlayer(Player *player); - void logSetActivePhase(int phase); - void containerProcessingStarted(GameEventContext *context); - void containerProcessingDone(); + void logGameJoined(int gameId); + void logReplayStarted(int gameId); + void logJoin(Player *player); + void logLeave(Player *player); + void logGameClosed(); + void logKicked(); + void logJoinSpectator(QString name); + void logLeaveSpectator(QString name); + void logDeckSelect(Player *player, QString deckHash, int sideboardSize); + void logReadyStart(Player *player); + void logNotReadyStart(Player *player); + void logSetSideboardLock(Player *player, bool locked); + void logConcede(Player *player); + void logGameStart(); + void logConnectionStateChanged(Player *player, bool connectionState); + void logSay(Player *player, QString message); + void logSpectatorSay(QString spectatorName, UserLevelFlags spectatorUserLevel, QString message); + void logShuffle(Player *player, CardZone *zone); + void logRollDie(Player *player, int sides, int roll); + void logDrawCards(Player *player, int number); + void logUndoDraw(Player *player, QString cardName); + void doMoveCard(LogMoveCard &attributes); + void logMoveCard(Player *player, CardItem *card, CardZone *startZone, int oldX, CardZone *targetZone, int newX); + void logMulligan(Player *player, int number); + void logFlipCard(Player *player, QString cardName, bool faceDown); + void logDestroyCard(Player *player, QString cardName); + void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName); + void logUnattachCard(Player *player, QString cardName); + void logCreateToken(Player *player, QString cardName, QString pt); + void logCreateArrow(Player *player, Player *startPlayer, QString startCard, Player *targetPlayer, QString targetCard, bool playerTarget); + void logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue); + void logSetTapped(Player *player, CardItem *card, bool tapped); + void logSetCounter(Player *player, QString counterName, int value, int oldValue); + void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap); + void logSetPT(Player *player, CardItem *card, QString newPT); + void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); + void logDumpZone(Player *player, CardZone *zone, int numberCards); + void logStopDumpZone(Player *player, CardZone *zone); + void logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer, bool faceDown); + void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal); + void logSetActivePlayer(Player *player); + void logSetActivePhase(int phase); + void containerProcessingStarted(const GameEventContext &context); + void containerProcessingDone(); public: - void connectToPlayer(Player *player); - MessageLogWidget(const QString &_ownName, bool _female, QWidget *parent = 0); + void connectToPlayer(Player *player); + MessageLogWidget(const TabSupervisor *_tabSupervisor, TabGame *_game, QWidget *parent = 0); }; #endif diff --git a/cockatrice/src/pending_command.cpp b/cockatrice/src/pending_command.cpp new file mode 100644 index 000000000..0ebb1d82a --- /dev/null +++ b/cockatrice/src/pending_command.cpp @@ -0,0 +1,31 @@ +#include "pending_command.h" + +PendingCommand::PendingCommand(const CommandContainer &_commandContainer, QVariant _extraData) + : commandContainer(_commandContainer), extraData(_extraData), ticks(0) +{ + +} + +CommandContainer & PendingCommand::getCommandContainer() +{ + return commandContainer; +} + +void PendingCommand::setExtraData(const QVariant &_extraData) { + extraData = _extraData; +} + +QVariant PendingCommand::getExtraData() const { + return extraData; +} + +void PendingCommand::processResponse(const Response &response) +{ + emit finished(response, commandContainer, extraData); + emit finished(response.response_code()); +} + +int PendingCommand::tick() +{ + return ++ticks; +} diff --git a/cockatrice/src/pending_command.h b/cockatrice/src/pending_command.h new file mode 100644 index 000000000..4ca9035ba --- /dev/null +++ b/cockatrice/src/pending_command.h @@ -0,0 +1,26 @@ +#ifndef PENDING_COMMAND_H +#define PENDING_COMMAND_H + +#include "pb/commands.pb.h" +#include "pb/response.pb.h" +#include + +class PendingCommand : public QObject { + Q_OBJECT +signals: + void finished(const Response &response, const CommandContainer &commandContainer, const QVariant &extraData); + void finished(Response::ResponseCode respCode); +private: + CommandContainer commandContainer; + QVariant extraData; + int ticks; +public: + PendingCommand(const CommandContainer &_commandContainer, QVariant _extraData = QVariant()); + CommandContainer &getCommandContainer(); + void setExtraData(const QVariant &_extraData); + QVariant getExtraData() const; + void processResponse(const Response &response); + int tick(); +}; + +#endif diff --git a/cockatrice/src/phasestoolbar.cpp b/cockatrice/src/phasestoolbar.cpp index 49b21d759..9b22f6118 100644 --- a/cockatrice/src/phasestoolbar.cpp +++ b/cockatrice/src/phasestoolbar.cpp @@ -3,239 +3,252 @@ #include #include #include -#include +#include +#ifdef _WIN32 +#include "round.h" +#endif /* _WIN32 */ #include "phasestoolbar.h" -#include "protocol_items.h" #include "pixmapgenerator.h" +#include "pb/command_set_active_phase.pb.h" +#include "pb/command_next_turn.pb.h" +#include "pb/command_set_card_attr.pb.h" +#include "pb/command_draw_cards.pb.h" + PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_doubleClickAction, bool _highlightable) - : QObject(), QGraphicsItem(parent), name(_name), active(false), highlightable(_highlightable), activeAnimationCounter(0), doubleClickAction(_doubleClickAction), width(50) + : QObject(), QGraphicsItem(parent), name(_name), active(false), highlightable(_highlightable), activeAnimationCounter(0), doubleClickAction(_doubleClickAction), width(50) { - if (highlightable) { - activeAnimationTimer = new QTimer(this); - connect(activeAnimationTimer, SIGNAL(timeout()), this, SLOT(updateAnimation())); - activeAnimationTimer->setSingleShot(false); - } else - activeAnimationCounter = 9.0; - - setCacheMode(DeviceCoordinateCache); + if (highlightable) { + activeAnimationTimer = new QTimer(this); + connect(activeAnimationTimer, SIGNAL(timeout()), this, SLOT(updateAnimation())); + activeAnimationTimer->setSingleShot(false); + } else + activeAnimationCounter = 9.0; + + setCacheMode(DeviceCoordinateCache); } QRectF PhaseButton::boundingRect() const { - return QRectF(0, 0, width, width); + return QRectF(0, 0, width, width); } void PhaseButton::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QRectF iconRect = boundingRect().adjusted(3, 3, -3, -3); - QRectF translatedIconRect = painter->combinedTransform().mapRect(iconRect); - qreal scaleFactor = translatedIconRect.width() / iconRect.width(); - QPixmap iconPixmap = PhasePixmapGenerator::generatePixmap(round(translatedIconRect.height()), name); - - painter->setBrush(QColor(220 * (activeAnimationCounter / 10.0), 220 * (activeAnimationCounter / 10.0), 220 * (activeAnimationCounter / 10.0))); - painter->setPen(Qt::gray); - painter->drawRect(0.5, 0.5, width - 1, width - 1); - painter->save(); - painter->resetTransform(); - painter->drawPixmap(iconPixmap.rect().translated(round(3 * scaleFactor), round(3 * scaleFactor)), iconPixmap, iconPixmap.rect()); - painter->restore(); - - painter->setBrush(QColor(0, 0, 0, 255 * ((10 - activeAnimationCounter) / 15.0))); - painter->setPen(Qt::gray); - painter->drawRect(0.5, 0.5, width - 1, width - 1); + QRectF iconRect = boundingRect().adjusted(3, 3, -3, -3); + QRectF translatedIconRect = painter->combinedTransform().mapRect(iconRect); + qreal scaleFactor = translatedIconRect.width() / iconRect.width(); + QPixmap iconPixmap = PhasePixmapGenerator::generatePixmap(round(translatedIconRect.height()), name); + + painter->setBrush(QColor(220 * (activeAnimationCounter / 10.0), 220 * (activeAnimationCounter / 10.0), 220 * (activeAnimationCounter / 10.0))); + painter->setPen(Qt::gray); + painter->drawRect(0, 0, width - 1, width - 1); + painter->save(); + painter->resetTransform(); + painter->drawPixmap(iconPixmap.rect().translated(round(3 * scaleFactor), round(3 * scaleFactor)), iconPixmap, iconPixmap.rect()); + painter->restore(); + + painter->setBrush(QColor(0, 0, 0, 255 * ((10 - activeAnimationCounter) / 15.0))); + painter->setPen(Qt::gray); + painter->drawRect(0, 0, width - 1, width - 1); } void PhaseButton::setWidth(double _width) { - prepareGeometryChange(); - width = _width; + prepareGeometryChange(); + width = _width; } void PhaseButton::setActive(bool _active) { - if ((active == _active) || !highlightable) - return; - - active = _active; - activeAnimationTimer->start(50); + if ((active == _active) || !highlightable) + return; + + active = _active; + activeAnimationTimer->start(50); } void PhaseButton::updateAnimation() { - if (!highlightable) - return; - - if (active) { - if (++activeAnimationCounter >= 10) - activeAnimationTimer->stop(); - } else { - if (--activeAnimationCounter <= 0) - activeAnimationTimer->stop(); - } - update(); + if (!highlightable) + return; + + if (active) { + if (++activeAnimationCounter >= 10) + activeAnimationTimer->stop(); + } else { + if (--activeAnimationCounter <= 0) + activeAnimationTimer->stop(); + } + update(); } void PhaseButton::mousePressEvent(QGraphicsSceneMouseEvent * /*event*/) { - emit clicked(); + emit clicked(); } -void PhaseButton::mouseDoubleClickEvent(QGraphicsSceneMouseEvent */*event*/) +void PhaseButton::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/) { - triggerDoubleClickAction(); + triggerDoubleClickAction(); } void PhaseButton::triggerDoubleClickAction() { - if (doubleClickAction) - doubleClickAction->trigger(); + if (doubleClickAction) + doubleClickAction->trigger(); } PhasesToolbar::PhasesToolbar(QGraphicsItem *parent) - : QGraphicsItem(parent), width(100), height(100) + : QGraphicsItem(parent), width(100), height(100), ySpacing(1), symbolSize(8) { - QAction *aUntapAll = new QAction(this); - connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll())); - QAction *aDrawCard = new QAction(this); - connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard())); - - PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll); -// untapButton->setShortcut(QKeySequence("F5")); - PhaseButton *upkeepButton = new PhaseButton("upkeep", this); - PhaseButton *drawButton = new PhaseButton("draw", this, aDrawCard); -// drawButton->setShortcut(QKeySequence("F6")); - PhaseButton *main1Button = new PhaseButton("main1", this); -// main1Button->setShortcut(QKeySequence("F7")); - PhaseButton *combatStartButton = new PhaseButton("combat_start", this); -// combatStartButton->setShortcut(QKeySequence("F8")); - PhaseButton *combatAttackersButton = new PhaseButton("combat_attackers", this); - PhaseButton *combatBlockersButton = new PhaseButton("combat_blockers", this); - PhaseButton *combatDamageButton = new PhaseButton("combat_damage", this); - PhaseButton *combatEndButton = new PhaseButton("combat_end", this); - PhaseButton *main2Button = new PhaseButton("main2", this); -// main2Button->setShortcut(QKeySequence("F9")); - PhaseButton *cleanupButton = new PhaseButton("cleanup", this); -// cleanupButton->setShortcut(QKeySequence("F10")); - - buttonList << untapButton << upkeepButton << drawButton << main1Button << combatStartButton - << combatAttackersButton << combatBlockersButton << combatDamageButton << combatEndButton - << main2Button << cleanupButton; - - for (int i = 0; i < buttonList.size(); ++i) - connect(buttonList[i], SIGNAL(clicked()), this, SLOT(phaseButtonClicked())); - - nextTurnButton = new PhaseButton("nextturn", this, 0, false); - connect(nextTurnButton, SIGNAL(clicked()), this, SLOT(actNextTurn())); - - rearrangeButtons(); - - retranslateUi(); + QAction *aUntapAll = new QAction(this); + connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll())); + QAction *aDrawCard = new QAction(this); + connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard())); + + PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll); + PhaseButton *upkeepButton = new PhaseButton("upkeep", this); + PhaseButton *drawButton = new PhaseButton("draw", this, aDrawCard); + PhaseButton *main1Button = new PhaseButton("main1", this); + PhaseButton *combatStartButton = new PhaseButton("combat_start", this); + PhaseButton *combatAttackersButton = new PhaseButton("combat_attackers", this); + PhaseButton *combatBlockersButton = new PhaseButton("combat_blockers", this); + PhaseButton *combatDamageButton = new PhaseButton("combat_damage", this); + PhaseButton *combatEndButton = new PhaseButton("combat_end", this); + PhaseButton *main2Button = new PhaseButton("main2", this); + PhaseButton *cleanupButton = new PhaseButton("cleanup", this); + + buttonList << untapButton << upkeepButton << drawButton << main1Button << combatStartButton + << combatAttackersButton << combatBlockersButton << combatDamageButton << combatEndButton + << main2Button << cleanupButton; + + for (int i = 0; i < buttonList.size(); ++i) + connect(buttonList[i], SIGNAL(clicked()), this, SLOT(phaseButtonClicked())); + + nextTurnButton = new PhaseButton("nextturn", this, 0, false); + connect(nextTurnButton, SIGNAL(clicked()), this, SLOT(actNextTurn())); + + rearrangeButtons(); + + retranslateUi(); } QRectF PhasesToolbar::boundingRect() const { - return QRectF(0, 0, width, height); + return QRectF(0, 0, width, height); } void PhasesToolbar::retranslateUi() { - for (int i = 0; i < buttonList.size(); ++i) - buttonList[i]->setToolTip(getLongPhaseName(i)); + for (int i = 0; i < buttonList.size(); ++i) + buttonList[i]->setToolTip(getLongPhaseName(i)); } QString PhasesToolbar::getLongPhaseName(int phase) const { - switch (phase) { - case 0: return tr("Untap step"); - case 1: return tr("Upkeep step"); - case 2: return tr("Draw step"); - case 3: return tr("First main phase"); - case 4: return tr("Beginning of combat step"); - case 5: return tr("Declare attackers step"); - case 6: return tr("Declare blockers step"); - case 7: return tr("Combat damage step"); - case 8: return tr("End of combat step"); - case 9: return tr("Second main phase"); - case 10: return tr("End of turn step"); - default: return QString(); - } + switch (phase) { + case 0: return tr("Untap step"); + case 1: return tr("Upkeep step"); + case 2: return tr("Draw step"); + case 3: return tr("First main phase"); + case 4: return tr("Beginning of combat step"); + case 5: return tr("Declare attackers step"); + case 6: return tr("Declare blockers step"); + case 7: return tr("Combat damage step"); + case 8: return tr("End of combat step"); + case 9: return tr("Second main phase"); + case 10: return tr("End of turn step"); + default: return QString(); + } } void PhasesToolbar::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - painter->fillRect(boundingRect(), QColor(50, 50, 50)); + painter->fillRect(boundingRect(), QColor(50, 50, 50)); } const double PhasesToolbar::marginSize = 3; void PhasesToolbar::rearrangeButtons() { - for (int i = 0; i < buttonList.size(); ++i) - buttonList[i]->setWidth(symbolSize); - nextTurnButton->setWidth(symbolSize); - - double y = marginSize; - buttonList[0]->setPos(marginSize, y); - buttonList[1]->setPos(marginSize, y += symbolSize); - buttonList[2]->setPos(marginSize, y += symbolSize); - y += ySpacing; - buttonList[3]->setPos(marginSize, y += symbolSize); - y += ySpacing; - buttonList[4]->setPos(marginSize, y += symbolSize); - buttonList[5]->setPos(marginSize, y += symbolSize); - buttonList[6]->setPos(marginSize, y += symbolSize); - buttonList[7]->setPos(marginSize, y += symbolSize); - buttonList[8]->setPos(marginSize, y += symbolSize); - y += ySpacing; - buttonList[9]->setPos(marginSize, y += symbolSize); - y += ySpacing; - buttonList[10]->setPos(marginSize, y += symbolSize); - y += ySpacing; - y += ySpacing; - nextTurnButton->setPos(marginSize, y += symbolSize); + for (int i = 0; i < buttonList.size(); ++i) + buttonList[i]->setWidth(symbolSize); + nextTurnButton->setWidth(symbolSize); + + double y = marginSize; + buttonList[0]->setPos(marginSize, y); + buttonList[1]->setPos(marginSize, y += symbolSize); + buttonList[2]->setPos(marginSize, y += symbolSize); + y += ySpacing; + buttonList[3]->setPos(marginSize, y += symbolSize); + y += ySpacing; + buttonList[4]->setPos(marginSize, y += symbolSize); + buttonList[5]->setPos(marginSize, y += symbolSize); + buttonList[6]->setPos(marginSize, y += symbolSize); + buttonList[7]->setPos(marginSize, y += symbolSize); + buttonList[8]->setPos(marginSize, y += symbolSize); + y += ySpacing; + buttonList[9]->setPos(marginSize, y += symbolSize); + y += ySpacing; + buttonList[10]->setPos(marginSize, y += symbolSize); + y += ySpacing; + y += ySpacing; + nextTurnButton->setPos(marginSize, y += symbolSize); } void PhasesToolbar::setHeight(double _height) { - prepareGeometryChange(); - - height = _height; - ySpacing = (height - 2 * marginSize) / (buttonCount * 5 + spaceCount); - symbolSize = ySpacing * 5; - width = symbolSize + 2 * marginSize; - - rearrangeButtons(); + prepareGeometryChange(); + + height = _height; + ySpacing = (height - 2 * marginSize) / (buttonCount * 5 + spaceCount); + symbolSize = ySpacing * 5; + width = symbolSize + 2 * marginSize; + + rearrangeButtons(); } void PhasesToolbar::setActivePhase(int phase) { - if (phase >= buttonList.size()) - return; - - for (int i = 0; i < buttonList.size(); ++i) - buttonList[i]->setActive(i == phase); + if (phase >= buttonList.size()) + return; + + for (int i = 0; i < buttonList.size(); ++i) + buttonList[i]->setActive(i == phase); } void PhasesToolbar::phaseButtonClicked() { - PhaseButton *button = qobject_cast(sender()); - if (button->getActive()) - button->triggerDoubleClickAction(); - emit sendGameCommand(new Command_SetActivePhase(-1, buttonList.indexOf(button)), -1); + PhaseButton *button = qobject_cast(sender()); + if (button->getActive()) + button->triggerDoubleClickAction(); + + Command_SetActivePhase cmd; + cmd.set_phase(buttonList.indexOf(button)); + + emit sendGameCommand(cmd, -1); } void PhasesToolbar::actNextTurn() { - emit sendGameCommand(new Command_NextTurn, -1); + emit sendGameCommand(Command_NextTurn(), -1); } void PhasesToolbar::actUntapAll() { - emit sendGameCommand(new Command_SetCardAttr(-1, "table", -1, "tapped", "0"), -1); + Command_SetCardAttr cmd; + cmd.set_zone("table"); + cmd.set_attribute(AttrTapped); + cmd.set_attr_value("0"); + + emit sendGameCommand(cmd, -1); } void PhasesToolbar::actDrawCard() { - emit sendGameCommand(new Command_DrawCards(-1, 1), -1); + Command_DrawCards cmd; + cmd.set_number(1); + + emit sendGameCommand(cmd, -1); } diff --git a/cockatrice/src/phasestoolbar.h b/cockatrice/src/phasestoolbar.h index 55620bb23..a91b020c4 100644 --- a/cockatrice/src/phasestoolbar.h +++ b/cockatrice/src/phasestoolbar.h @@ -5,66 +5,69 @@ #include #include +namespace google { namespace protobuf { class Message; } } class Player; class GameCommand; class PhaseButton : public QObject, public QGraphicsItem { - Q_OBJECT + Q_OBJECT + Q_INTERFACES(QGraphicsItem) private: - QString name; - bool active, highlightable; - int activeAnimationCounter; - QTimer *activeAnimationTimer; - QAction *doubleClickAction; - double width; - - void updatePixmap(QPixmap &pixmap); + QString name; + bool active, highlightable; + int activeAnimationCounter; + QTimer *activeAnimationTimer; + QAction *doubleClickAction; + double width; + + void updatePixmap(QPixmap &pixmap); private slots: - void updateAnimation(); + void updateAnimation(); public: - PhaseButton(const QString &_name, QGraphicsItem *parent = 0, QAction *_doubleClickAction = 0, bool _highlightable = true); - QRectF boundingRect() const; - void setWidth(double _width); - void setActive(bool _active); - bool getActive() const { return active; } - void triggerDoubleClickAction(); + PhaseButton(const QString &_name, QGraphicsItem *parent = 0, QAction *_doubleClickAction = 0, bool _highlightable = true); + QRectF boundingRect() const; + void setWidth(double _width); + void setActive(bool _active); + bool getActive() const { return active; } + void triggerDoubleClickAction(); signals: - void clicked(); + void clicked(); protected: - void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); + void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); }; class PhasesToolbar : public QObject, public QGraphicsItem { - Q_OBJECT + Q_OBJECT + Q_INTERFACES(QGraphicsItem) private: - QList buttonList; - PhaseButton *nextTurnButton; - double width, height, ySpacing, symbolSize; - static const int buttonCount = 12; - static const int spaceCount = 6; - static const double marginSize; - void rearrangeButtons(); + QList buttonList; + PhaseButton *nextTurnButton; + double width, height, ySpacing, symbolSize; + static const int buttonCount = 12; + static const int spaceCount = 6; + static const double marginSize; + void rearrangeButtons(); public: - PhasesToolbar(QGraphicsItem *parent = 0); - QRectF boundingRect() const; - void retranslateUi(); - void setHeight(double _height); - double getWidth() const { return width; } - int phaseCount() const { return buttonList.size(); } - QString getLongPhaseName(int phase) const; + PhasesToolbar(QGraphicsItem *parent = 0); + QRectF boundingRect() const; + void retranslateUi(); + void setHeight(double _height); + double getWidth() const { return width; } + int phaseCount() const { return buttonList.size(); } + QString getLongPhaseName(int phase) const; public slots: - void setActivePhase(int phase); + void setActivePhase(int phase); private slots: - void phaseButtonClicked(); - void actNextTurn(); - void actUntapAll(); - void actDrawCard(); + void phaseButtonClicked(); + void actNextTurn(); + void actUntapAll(); + void actDrawCard(); signals: - void sendGameCommand(GameCommand *command, int playerId); + void sendGameCommand(const ::google::protobuf::Message &command, int playerId); protected: - void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); + void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); }; #endif diff --git a/cockatrice/src/pilezone.cpp b/cockatrice/src/pilezone.cpp index aff44dfe2..ed9daa888 100644 --- a/cockatrice/src/pilezone.cpp +++ b/cockatrice/src/pilezone.cpp @@ -5,101 +5,118 @@ #include "player.h" #include "carddragitem.h" #include "zoneviewzone.h" -#include "protocol_items.h" +#include "carditem.h" + +#include "pb/command_move_card.pb.h" PileZone::PileZone(Player *_p, const QString &_name, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent) - : CardZone(_p, _name, false, _isShufflable, _contentsKnown, parent) + : CardZone(_p, _name, false, _isShufflable, _contentsKnown, parent) { - setCacheMode(DeviceCoordinateCache); // Do not move this line to the parent constructor! - setAcceptsHoverEvents(true); - setCursor(Qt::OpenHandCursor); - - setTransform(QTransform().translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2).rotate(90).translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2)); + setCacheMode(DeviceCoordinateCache); // Do not move this line to the parent constructor! +#if QT_VERSION < 0x050000 + setAcceptsHoverEvents(true); +#else + setAcceptHoverEvents(true); +#endif + setCursor(Qt::OpenHandCursor); + + setTransform(QTransform().translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2).rotate(90).translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2)); } QRectF PileZone::boundingRect() const { - return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); + return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); } void PileZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - if (!cards.isEmpty()) - cards.at(0)->paintPicture(painter, cards.at(0)->getTranslatedSize(painter), 90); + if (!cards.isEmpty()) + cards.at(0)->paintPicture(painter, cards.at(0)->getTranslatedSize(painter), 90); - painter->drawRect(QRectF(0.5, 0.5, CARD_WIDTH - 1, CARD_HEIGHT - 1)); - - painter->translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2); - painter->rotate(-90); - painter->translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2); - paintNumberEllipse(cards.size(), 28, Qt::white, -1, -1, painter); + painter->drawRect(QRectF(0.5, 0.5, CARD_WIDTH - 1, CARD_HEIGHT - 1)); + + painter->translate((float) CARD_WIDTH / 2, (float) CARD_HEIGHT / 2); + painter->rotate(-90); + painter->translate((float) -CARD_WIDTH / 2, (float) -CARD_HEIGHT / 2); + paintNumberEllipse(cards.size(), 28, Qt::white, -1, -1, painter); } void PileZone::addCardImpl(CardItem *card, int x, int /*y*/) { - cards.insert(x, card); - card->setPos(0, 0); - if (!contentsKnown()) { - card->setName(QString()); - card->setId(-1); - } - card->setVisible(false); - card->resetState(); - card->setParentItem(this); + connect(card, SIGNAL(sigPixmapUpdated()), this, SLOT(callUpdate())); + cards.insert(x, card); + card->setPos(0, 0); + if (!contentsKnown()) { + card->setName(QString()); + card->setId(-1); + // If we obscure a previously revealed card, its name has to be forgotten + if (cards.size() > x + 1) + cards.at(x + 1)->setName(QString()); + } + card->setVisible(false); + card->resetState(); + card->setParentItem(this); } void PileZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &/*dropPoint*/) { - QList idList; - for (int i = 0; i < dragItems.size(); ++i) - idList.append(new CardToMove(dragItems[i]->getId())); - - player->sendGameCommand(new Command_MoveCard(-1, startZone->getName(), idList, player->getId(), getName(), 0, 0)); + Command_MoveCard cmd; + cmd.set_start_player_id(startZone->getPlayer()->getId()); + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_target_player_id(player->getId()); + cmd.set_target_zone(getName().toStdString()); + cmd.set_x(0); + cmd.set_y(0); + + for (int i = 0; i < dragItems.size(); ++i) + cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId()); + + player->sendGameCommand(cmd); } void PileZone::reorganizeCards() { - update(); + update(); } void PileZone::mousePressEvent(QGraphicsSceneMouseEvent *event) { - CardZone::mousePressEvent(event); - if (event->isAccepted()) - return; + CardZone::mousePressEvent(event); + if (event->isAccepted()) + return; - if (event->button() == Qt::LeftButton) { - setCursor(Qt::ClosedHandCursor); - event->accept(); - } else - event->ignore(); + if (event->button() == Qt::LeftButton) { + setCursor(Qt::ClosedHandCursor); + event->accept(); + } else + event->ignore(); } void PileZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < QApplication::startDragDistance()) - return; + if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < QApplication::startDragDistance()) + return; - if (cards.isEmpty()) - return; + if (cards.isEmpty()) + return; - bool faceDown = event->modifiers().testFlag(Qt::ShiftModifier); - bool bottomCard = event->modifiers().testFlag(Qt::ControlModifier); - CardItem *card = bottomCard ? cards.last() : cards.first(); - const int cardid = contentsKnown() ? card->getId() : (bottomCard ? cards.size() - 1 : 0); - CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), faceDown); - drag->grabMouse(); - setCursor(Qt::OpenHandCursor); + bool faceDown = event->modifiers().testFlag(Qt::ShiftModifier); + bool bottomCard = event->modifiers().testFlag(Qt::ControlModifier); + CardItem *card = bottomCard ? cards.last() : cards.first(); + const int cardid = contentsKnown() ? card->getId() : (bottomCard ? cards.size() - 1 : 0); + CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), faceDown); + drag->grabMouse(); + setCursor(Qt::OpenHandCursor); } -void PileZone::mouseReleaseEvent(QGraphicsSceneMouseEvent */*event*/) +void PileZone::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/) { - setCursor(Qt::OpenHandCursor); + setCursor(Qt::OpenHandCursor); } void PileZone::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { - if (!cards.isEmpty()) - cards[0]->processHoverEvent(); - QGraphicsItem::hoverEnterEvent(event); + if (!cards.isEmpty()) + cards[0]->processHoverEvent(); + QGraphicsItem::hoverEnterEvent(event); } diff --git a/cockatrice/src/pilezone.h b/cockatrice/src/pilezone.h index 4c2188bf7..2469eb1eb 100644 --- a/cockatrice/src/pilezone.h +++ b/cockatrice/src/pilezone.h @@ -4,18 +4,21 @@ #include "cardzone.h" class PileZone : public CardZone { + Q_OBJECT +private slots: + void callUpdate() { update(); } public: - PileZone(Player *_p, const QString &_name, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent = 0); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); + PileZone(Player *_p, const QString &_name, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent = 0); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void reorganizeCards(); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); protected: - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - void hoverEnterEvent(QGraphicsSceneHoverEvent *event); - void addCardImpl(CardItem *card, int x, int y); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void addCardImpl(CardItem *card, int x, int y); }; #endif diff --git a/cockatrice/src/pixmapgenerator.cpp b/cockatrice/src/pixmapgenerator.cpp index 1c04e9226..8bae48539 100644 --- a/cockatrice/src/pixmapgenerator.cpp +++ b/cockatrice/src/pixmapgenerator.cpp @@ -1,160 +1,163 @@ #include "pixmapgenerator.h" -#include "protocol_datastructures.h" +#include "pb/serverinfo_user.pb.h" #include #include -#include +#include +#ifdef _WIN32 +#include "round.h" +#endif /* _WIN32 */ #include QMap PhasePixmapGenerator::pmCache; QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name) { - QString key = name + QString::number(height); - if (pmCache.contains(key)) - return pmCache.value(key); - - QSvgRenderer svg(QString(":/resources/phases/icon_phase_" + name + ".svg")); - - QPixmap pixmap(height, height); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - svg.render(&painter, QRectF(0, 0, height, height)); - pmCache.insert(key, pixmap); - return pixmap; + QString key = name + QString::number(height); + if (pmCache.contains(key)) + return pmCache.value(key); + + QSvgRenderer svg(QString(":/resources/phases/icon_phase_" + name + ".svg")); + + QPixmap pixmap(height, height); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + svg.render(&painter, QRectF(0, 0, height, height)); + pmCache.insert(key, pixmap); + return pixmap; } QMap CounterPixmapGenerator::pmCache; QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool highlight) { - if (highlight) - name.append("_highlight"); - QString key = name + QString::number(height); - if (pmCache.contains(key)) - return pmCache.value(key); - - QSvgRenderer svg(QString(":/resources/counters/" + name + ".svg")); - - if (!svg.isValid()) { - name = "general"; - if (highlight) - name.append("_highlight"); - svg.load(QString(":/resources/counters/" + name + ".svg")); - } - - int width = (int) round(height * (double) svg.defaultSize().width() / (double) svg.defaultSize().height()); - QPixmap pixmap(width, height); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - svg.render(&painter, QRectF(0, 0, width, height)); - pmCache.insert(key, pixmap); - return pixmap; + if (highlight) + name.append("_highlight"); + QString key = name + QString::number(height); + if (pmCache.contains(key)) + return pmCache.value(key); + + QSvgRenderer svg(QString(":/resources/counters/" + name + ".svg")); + + if (!svg.isValid()) { + name = "general"; + if (highlight) + name.append("_highlight"); + svg.load(QString(":/resources/counters/" + name + ".svg")); + } + + int width = (int) round(height * (double) svg.defaultSize().width() / (double) svg.defaultSize().height()); + QPixmap pixmap(width, height); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + svg.render(&painter, QRectF(0, 0, width, height)); + pmCache.insert(key, pixmap); + return pixmap; } QPixmap PingPixmapGenerator::generatePixmap(int size, int value, int max) { - int key = size * 1000000 + max * 1000 + value; - if (pmCache.contains(key)) - return pmCache.value(key); - - QPixmap pixmap(size, size); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - QColor color; - if ((max == -1) || (value == -1)) - color = Qt::black; - else - color.setHsv(120 * (1.0 - ((double) value / max)), 255, 255); - - QRadialGradient g(QPointF((double) pixmap.width() / 2, (double) pixmap.height() / 2), qMin(pixmap.width(), pixmap.height()) / 2.0); - g.setColorAt(0, color); - g.setColorAt(1, Qt::transparent); - painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QBrush(g)); - - pmCache.insert(key, pixmap); + int key = size * 1000000 + max * 1000 + value; + if (pmCache.contains(key)) + return pmCache.value(key); + + QPixmap pixmap(size, size); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + QColor color; + if ((max == -1) || (value == -1)) + color = Qt::black; + else + color.setHsv(120 * (1.0 - ((double) value / max)), 255, 255); + + QRadialGradient g(QPointF((double) pixmap.width() / 2, (double) pixmap.height() / 2), qMin(pixmap.width(), pixmap.height()) / 2.0); + g.setColorAt(0, color); + g.setColorAt(1, Qt::transparent); + painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QBrush(g)); + + pmCache.insert(key, pixmap); - return pixmap; + return pixmap; } QMap PingPixmapGenerator::pmCache; QPixmap GenderPixmapGenerator::generatePixmap(int height, int _gender) { - ServerInfo_User::Gender gender = static_cast(_gender); - if ((gender != ServerInfo_User::Male) && (gender != ServerInfo_User::Female)) - gender = ServerInfo_User::GenderUnknown; - - int key = gender * 100000 + height; - if (pmCache.contains(key)) - return pmCache.value(key); - - QString genderStr; - switch (gender) { - case ServerInfo_User::Male: genderStr = "male"; break; - case ServerInfo_User::Female: genderStr = "female"; break; - default: genderStr = "unknown"; - }; - - QSvgRenderer svg(QString(":/resources/genders/" + genderStr + ".svg")); - int width = (int) round(height * (double) svg.defaultSize().width() / (double) svg.defaultSize().height()); - QPixmap pixmap(width, height); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - svg.render(&painter, QRectF(0, 0, width, height)); - - pmCache.insert(key, pixmap); - return pixmap; + ServerInfo_User::Gender gender = static_cast(_gender); + if ((gender != ServerInfo_User::Male) && (gender != ServerInfo_User::Female)) + gender = ServerInfo_User::GenderUnknown; + + int key = gender * 100000 + height; + if (pmCache.contains(key)) + return pmCache.value(key); + + QString genderStr; + switch (gender) { + case ServerInfo_User::Male: genderStr = "male"; break; + case ServerInfo_User::Female: genderStr = "female"; break; + default: genderStr = "unknown"; + }; + + QSvgRenderer svg(QString(":/resources/genders/" + genderStr + ".svg")); + int width = (int) round(height * (double) svg.defaultSize().width() / (double) svg.defaultSize().height()); + QPixmap pixmap(width, height); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + svg.render(&painter, QRectF(0, 0, width, height)); + + pmCache.insert(key, pixmap); + return pixmap; } QMap GenderPixmapGenerator::pmCache; QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countryCode) { - if (countryCode.size() != 2) - return QPixmap(); - QString key = countryCode + QString::number(height); - if (pmCache.contains(key)) - return pmCache.value(key); - - QSvgRenderer svg(QString(":/resources/countries/" + countryCode + ".svg")); - int width = (int) round(height * (double) svg.defaultSize().width() / (double) svg.defaultSize().height()); - QPixmap pixmap(width, height); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - svg.render(&painter, QRectF(0, 0, width, height)); - painter.setPen(Qt::black); - painter.drawRect(0.5, 0.5, width - 1, height - 1); - - pmCache.insert(key, pixmap); - return pixmap; + if (countryCode.size() != 2) + return QPixmap(); + QString key = countryCode + QString::number(height); + if (pmCache.contains(key)) + return pmCache.value(key); + + QSvgRenderer svg(QString(":/resources/countries/" + countryCode + ".svg")); + int width = (int) round(height * (double) svg.defaultSize().width() / (double) svg.defaultSize().height()); + QPixmap pixmap(width, height); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + svg.render(&painter, QRectF(0, 0, width, height)); + painter.setPen(Qt::black); + painter.drawRect(0, 0, width - 1, height - 1); + + pmCache.insert(key, pixmap); + return pixmap; } QMap CountryPixmapGenerator::pmCache; -QPixmap UserLevelPixmapGenerator::generatePixmap(int height, int userLevel) +QPixmap UserLevelPixmapGenerator::generatePixmap(int height, UserLevelFlags userLevel) { - int key = height * 10000 + userLevel; - if (pmCache.contains(key)) - return pmCache.value(key); - - QString levelString; - if (userLevel & ServerInfo_User::IsAdmin) - levelString = "admin"; - else if (userLevel & ServerInfo_User::IsModerator) - levelString = "moderator"; - else if (userLevel &ServerInfo_User::IsRegistered) - levelString = "registered"; - else - levelString = "normal"; - QSvgRenderer svg(QString(":/resources/userlevels/" + levelString + ".svg")); - int width = (int) round(height * (double) svg.defaultSize().width() / (double) svg.defaultSize().height()); - QPixmap pixmap(width, height); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - svg.render(&painter, QRectF(0, 0, width, height)); - - pmCache.insert(key, pixmap); - return pixmap; + int key = height * 10000 + (int) userLevel; + if (pmCache.contains(key)) + return pmCache.value(key); + + QString levelString; + if (userLevel.testFlag(ServerInfo_User::IsAdmin)) + levelString = "admin"; + else if (userLevel.testFlag(ServerInfo_User::IsModerator)) + levelString = "moderator"; + else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) + levelString = "registered"; + else + levelString = "normal"; + QSvgRenderer svg(QString(":/resources/userlevels/" + levelString + ".svg")); + int width = (int) round(height * (double) svg.defaultSize().width() / (double) svg.defaultSize().height()); + QPixmap pixmap(width, height); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + svg.render(&painter, QRectF(0, 0, width, height)); + + pmCache.insert(key, pixmap); + return pixmap; } QMap UserLevelPixmapGenerator::pmCache; diff --git a/cockatrice/src/pixmapgenerator.h b/cockatrice/src/pixmapgenerator.h index 25fa00c96..67a8b2b2e 100644 --- a/cockatrice/src/pixmapgenerator.h +++ b/cockatrice/src/pixmapgenerator.h @@ -4,52 +4,54 @@ #include #include +#include "user_level.h" + class PhasePixmapGenerator { private: - static QMap pmCache; + static QMap pmCache; public: - static QPixmap generatePixmap(int size, QString name); - static void clear() { pmCache.clear(); } + static QPixmap generatePixmap(int size, QString name); + static void clear() { pmCache.clear(); } }; class CounterPixmapGenerator { private: - static QMap pmCache; + static QMap pmCache; public: - static QPixmap generatePixmap(int size, QString name, bool highlight); - static void clear() { pmCache.clear(); } + static QPixmap generatePixmap(int size, QString name, bool highlight); + static void clear() { pmCache.clear(); } }; class PingPixmapGenerator { private: - static QMap pmCache; + static QMap pmCache; public: - static QPixmap generatePixmap(int size, int value, int max); - static void clear() { pmCache.clear(); } + static QPixmap generatePixmap(int size, int value, int max); + static void clear() { pmCache.clear(); } }; class GenderPixmapGenerator { private: - static QMap pmCache; + static QMap pmCache; public: - static QPixmap generatePixmap(int height, int gender); - static void clear() { pmCache.clear(); } + static QPixmap generatePixmap(int height, int gender); + static void clear() { pmCache.clear(); } }; class CountryPixmapGenerator { private: - static QMap pmCache; + static QMap pmCache; public: - static QPixmap generatePixmap(int height, const QString &countryCode); - static void clear() { pmCache.clear(); } + static QPixmap generatePixmap(int height, const QString &countryCode); + static void clear() { pmCache.clear(); } }; class UserLevelPixmapGenerator { private: - static QMap pmCache; + static QMap pmCache; public: - static QPixmap generatePixmap(int height, int userLevel); - static void clear() { pmCache.clear(); } + static QPixmap generatePixmap(int height, UserLevelFlags userLevel); + static void clear() { pmCache.clear(); } }; #endif diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp index 5f78b4d8b..d8d21fd91 100644 --- a/cockatrice/src/player.cpp +++ b/cockatrice/src/player.cpp @@ -10,1557 +10,2225 @@ #include "tablezone.h" #include "handzone.h" #include "handcounter.h" +#include "carditem.h" #include "cardlist.h" #include "tab_game.h" -#include "protocol_items.h" #include "gamescene.h" #include "settingscache.h" #include "dlg_create_token.h" #include "carddatabase.h" +#include "color.h" +#include "deck_loader.h" +#include "main.h" #include #include #include #include +#include "pb/command_change_zone_properties.pb.h" +#include "pb/command_reveal_cards.pb.h" +#include "pb/command_shuffle.pb.h" +#include "pb/command_attach_card.pb.h" +#include "pb/command_set_card_attr.pb.h" +#include "pb/command_set_card_counter.pb.h" +#include "pb/command_mulligan.pb.h" +#include "pb/command_move_card.pb.h" +#include "pb/command_draw_cards.pb.h" +#include "pb/command_undo_draw.pb.h" +#include "pb/command_roll_die.pb.h" +#include "pb/command_create_token.pb.h" +#include "pb/command_flip_card.pb.h" +#include "pb/command_game_say.pb.h" +#include "pb/serverinfo_user.pb.h" +#include "pb/serverinfo_player.pb.h" +#include "pb/serverinfo_zone.pb.h" +#include "pb/context_move_card.pb.h" +#include "pb/context_undo_draw.pb.h" +#include "pb/event_game_say.pb.h" +#include "pb/event_shuffle.pb.h" +#include "pb/event_roll_die.pb.h" +#include "pb/event_create_arrow.pb.h" +#include "pb/event_delete_arrow.pb.h" +#include "pb/event_create_token.pb.h" +#include "pb/event_set_card_attr.pb.h" +#include "pb/event_set_card_counter.pb.h" +#include "pb/event_create_counter.pb.h" +#include "pb/event_set_counter.pb.h" +#include "pb/event_del_counter.pb.h" +#include "pb/event_dump_zone.pb.h" +#include "pb/event_stop_dump_zone.pb.h" +#include "pb/event_move_card.pb.h" +#include "pb/event_flip_card.pb.h" +#include "pb/event_destroy_card.pb.h" +#include "pb/event_attach_card.pb.h" +#include "pb/event_draw_cards.pb.h" +#include "pb/event_reveal_cards.pb.h" +#include "pb/event_change_zone_properties.pb.h" + PlayerArea::PlayerArea(QGraphicsItem *parentItem) - : QObject(), QGraphicsItem(parentItem) + : QObject(), QGraphicsItem(parentItem) { - setCacheMode(DeviceCoordinateCache); - connect(settingsCache, SIGNAL(playerBgPathChanged()), this, SLOT(updateBgPixmap())); - updateBgPixmap(); + setCacheMode(DeviceCoordinateCache); + connect(settingsCache, SIGNAL(playerBgPathChanged()), this, SLOT(updateBgPixmap())); + updateBgPixmap(); } void PlayerArea::updateBgPixmap() { - QString bgPath = settingsCache->getPlayerBgPath(); - if (bgPath.isEmpty()) - bgPixmapBrush = QBrush(QColor(200, 200, 200)); - else { - qDebug() << "loading" << bgPath; - bgPixmapBrush = QBrush(QPixmap(bgPath)); - } - update(); + QString bgPath = settingsCache->getPlayerBgPath(); + if (bgPath.isEmpty()) + bgPixmapBrush = QBrush(QColor(200, 200, 200)); + else { + qDebug() << "loading" << bgPath; + bgPixmapBrush = QBrush(QPixmap(bgPath)); + } + update(); } -void PlayerArea::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) +void PlayerArea::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - painter->fillRect(bRect, bgPixmapBrush); + painter->fillRect(bRect, bgPixmapBrush); } void PlayerArea::setSize(qreal width, qreal height) { - prepareGeometryChange(); - bRect = QRectF(0, 0, width, height); + prepareGeometryChange(); + bRect = QRectF(0, 0, width, height); } -Player::Player(ServerInfo_User *info, int _id, bool _local, TabGame *_parent) - : QObject(_parent), shortcutsActive(false), defaultNumberTopCards(3), lastTokenDestroy(true), userInfo(new ServerInfo_User(info)), id(_id), active(false), local(_local), mirrored(false), handVisible(false), conceded(false), dialogSemaphore(false) +Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_parent) + : QObject(_parent), + game(_parent), + shortcutsActive(false), + defaultNumberTopCards(3), + lastTokenDestroy(true), + id(_id), + active(false), + local(_local), + mirrored(false), + handVisible(false), + conceded(false), + dialogSemaphore(false), + deck(0) { - connect(settingsCache, SIGNAL(horizontalHandChanged()), this, SLOT(rearrangeZones())); - - playerArea = new PlayerArea(this); - - playerTarget = new PlayerTarget(this, playerArea); - qreal avatarMargin = (counterAreaWidth + CARD_HEIGHT + 15 - playerTarget->boundingRect().width()) / 2.0; - playerTarget->setPos(QPointF(avatarMargin, avatarMargin)); + userInfo = new ServerInfo_User; + userInfo->CopyFrom(info); + + connect(settingsCache, SIGNAL(horizontalHandChanged()), this, SLOT(rearrangeZones())); + + playerArea = new PlayerArea(this); + + playerTarget = new PlayerTarget(this, playerArea); + qreal avatarMargin = (counterAreaWidth + CARD_HEIGHT + 15 - playerTarget->boundingRect().width()) / 2.0; + playerTarget->setPos(QPointF(avatarMargin, avatarMargin)); - PileZone *deck = new PileZone(this, "deck", true, false, playerArea); - QPointF base = QPointF(counterAreaWidth + (CARD_HEIGHT - CARD_WIDTH + 15) / 2.0, 10 + playerTarget->boundingRect().height() + 5 - (CARD_HEIGHT - CARD_WIDTH) / 2.0); - deck->setPos(base); + PileZone *deck = new PileZone(this, "deck", true, false, playerArea); + QPointF base = QPointF(counterAreaWidth + (CARD_HEIGHT - CARD_WIDTH + 15) / 2.0, 10 + playerTarget->boundingRect().height() + 5 - (CARD_HEIGHT - CARD_WIDTH) / 2.0); + deck->setPos(base); - qreal h = deck->boundingRect().width() + 5; + qreal h = deck->boundingRect().width() + 5; - HandCounter *handCounter = new HandCounter(playerArea); - handCounter->setPos(base + QPointF(0, h + 10)); - qreal h2 = handCounter->boundingRect().height(); - - PileZone *grave = new PileZone(this, "grave", false, true, playerArea); - grave->setPos(base + QPointF(0, h + h2 + 10)); + HandCounter *handCounter = new HandCounter(playerArea); + handCounter->setPos(base + QPointF(0, h + 10)); + qreal h2 = handCounter->boundingRect().height(); + + PileZone *grave = new PileZone(this, "grave", false, true, playerArea); + grave->setPos(base + QPointF(0, h + h2 + 10)); - PileZone *rfg = new PileZone(this, "rfg", false, true, playerArea); - rfg->setPos(base + QPointF(0, 2 * h + h2 + 10)); + PileZone *rfg = new PileZone(this, "rfg", false, true, playerArea); + rfg->setPos(base + QPointF(0, 2 * h + h2 + 10)); - PileZone *sb = new PileZone(this, "sb", false, false, playerArea); - sb->setVisible(false); + PileZone *sb = new PileZone(this, "sb", false, false, playerArea); + sb->setVisible(false); - table = new TableZone(this, this); - connect(table, SIGNAL(sizeChanged()), this, SLOT(updateBoundingRect())); - - stack = new StackZone(this, (int) table->boundingRect().height(), this); - - hand = new HandZone(this, _local || (_parent->getSpectator() && _parent->getSpectatorsSeeEverything()), (int) table->boundingRect().height(), this); - connect(hand, SIGNAL(cardCountChanged()), handCounter, SLOT(updateNumber())); - connect(handCounter, SIGNAL(showContextMenu(const QPoint &)), hand, SLOT(showContextMenu(const QPoint &))); - - updateBoundingRect(); + table = new TableZone(this, this); + connect(table, SIGNAL(sizeChanged()), this, SLOT(updateBoundingRect())); + + stack = new StackZone(this, (int) table->boundingRect().height(), this); + + hand = new HandZone(this, _local || (_parent->getSpectator() && _parent->getSpectatorsSeeEverything()), (int) table->boundingRect().height(), this); + connect(hand, SIGNAL(cardCountChanged()), handCounter, SLOT(updateNumber())); + connect(handCounter, SIGNAL(showContextMenu(const QPoint &)), hand, SLOT(showContextMenu(const QPoint &))); + + updateBoundingRect(); - if (local) { - connect(_parent, SIGNAL(playerAdded(Player *)), this, SLOT(addPlayer(Player *))); - connect(_parent, SIGNAL(playerRemoved(Player *)), this, SLOT(removePlayer(Player *))); - - aMoveHandToTopLibrary = new QAction(this); - aMoveHandToTopLibrary->setData(QList() << "deck" << 0); - aMoveHandToBottomLibrary = new QAction(this); - aMoveHandToBottomLibrary->setData(QList() << "deck" << -1); - aMoveHandToGrave = new QAction(this); - aMoveHandToGrave->setData(QList() << "grave" << 0); - aMoveHandToRfg = new QAction(this); - aMoveHandToRfg->setData(QList() << "rfg" << 0); - - connect(aMoveHandToTopLibrary, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); - connect(aMoveHandToBottomLibrary, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); - connect(aMoveHandToGrave, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); - connect(aMoveHandToRfg, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); + if (local) { + connect(_parent, SIGNAL(playerAdded(Player *)), this, SLOT(addPlayer(Player *))); + connect(_parent, SIGNAL(playerRemoved(Player *)), this, SLOT(removePlayer(Player *))); + + aMoveHandToTopLibrary = new QAction(this); + aMoveHandToTopLibrary->setData(QList() << "deck" << 0); + aMoveHandToBottomLibrary = new QAction(this); + aMoveHandToBottomLibrary->setData(QList() << "deck" << -1); + aMoveHandToGrave = new QAction(this); + aMoveHandToGrave->setData(QList() << "grave" << 0); + aMoveHandToRfg = new QAction(this); + aMoveHandToRfg->setData(QList() << "rfg" << 0); + + connect(aMoveHandToTopLibrary, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); + connect(aMoveHandToBottomLibrary, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); + connect(aMoveHandToGrave, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); + connect(aMoveHandToRfg, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); - aMoveGraveToTopLibrary = new QAction(this); - aMoveGraveToTopLibrary->setData(QList() << "deck" << 0); - aMoveGraveToBottomLibrary = new QAction(this); - aMoveGraveToBottomLibrary->setData(QList() << "deck" << -1); - aMoveGraveToHand = new QAction(this); - aMoveGraveToHand->setData(QList() << "hand" << 0); - aMoveGraveToRfg = new QAction(this); - aMoveGraveToRfg->setData(QList() << "rfg" << 0); - - connect(aMoveGraveToTopLibrary, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); - connect(aMoveGraveToBottomLibrary, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); - connect(aMoveGraveToHand, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); - connect(aMoveGraveToRfg, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); + aMoveGraveToTopLibrary = new QAction(this); + aMoveGraveToTopLibrary->setData(QList() << "deck" << 0); + aMoveGraveToBottomLibrary = new QAction(this); + aMoveGraveToBottomLibrary->setData(QList() << "deck" << -1); + aMoveGraveToHand = new QAction(this); + aMoveGraveToHand->setData(QList() << "hand" << 0); + aMoveGraveToRfg = new QAction(this); + aMoveGraveToRfg->setData(QList() << "rfg" << 0); + + connect(aMoveGraveToTopLibrary, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); + connect(aMoveGraveToBottomLibrary, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); + connect(aMoveGraveToHand, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); + connect(aMoveGraveToRfg, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); - aMoveRfgToTopLibrary = new QAction(this); - aMoveRfgToTopLibrary->setData(QList() << "deck" << 0); - aMoveRfgToBottomLibrary = new QAction(this); - aMoveRfgToBottomLibrary->setData(QList() << "deck" << -1); - aMoveRfgToHand = new QAction(this); - aMoveRfgToHand->setData(QList() << "hand" << 0); - aMoveRfgToGrave = new QAction(this); - aMoveRfgToGrave->setData(QList() << "grave" << 0); - - connect(aMoveRfgToTopLibrary, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); - connect(aMoveRfgToBottomLibrary, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); - connect(aMoveRfgToHand, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); - connect(aMoveRfgToGrave, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); + aMoveRfgToTopLibrary = new QAction(this); + aMoveRfgToTopLibrary->setData(QList() << "deck" << 0); + aMoveRfgToBottomLibrary = new QAction(this); + aMoveRfgToBottomLibrary->setData(QList() << "deck" << -1); + aMoveRfgToHand = new QAction(this); + aMoveRfgToHand->setData(QList() << "hand" << 0); + aMoveRfgToGrave = new QAction(this); + aMoveRfgToGrave->setData(QList() << "grave" << 0); + + connect(aMoveRfgToTopLibrary, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); + connect(aMoveRfgToBottomLibrary, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); + connect(aMoveRfgToHand, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); + connect(aMoveRfgToGrave, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); - aViewLibrary = new QAction(this); - connect(aViewLibrary, SIGNAL(triggered()), this, SLOT(actViewLibrary())); - aViewTopCards = new QAction(this); - connect(aViewTopCards, SIGNAL(triggered()), this, SLOT(actViewTopCards())); - } + aViewLibrary = new QAction(this); + connect(aViewLibrary, SIGNAL(triggered()), this, SLOT(actViewLibrary())); + aViewTopCards = new QAction(this); + connect(aViewTopCards, SIGNAL(triggered()), this, SLOT(actViewTopCards())); + aAlwaysRevealTopCard = new QAction(this); + aAlwaysRevealTopCard->setCheckable(true); + connect(aAlwaysRevealTopCard, SIGNAL(triggered()), this, SLOT(actAlwaysRevealTopCard())); + aOpenDeckInDeckEditor = new QAction(this); + aOpenDeckInDeckEditor->setEnabled(false); + connect(aOpenDeckInDeckEditor, SIGNAL(triggered()), this, SLOT(actOpenDeckInDeckEditor())); + } - aViewGraveyard = new QAction(this); - connect(aViewGraveyard, SIGNAL(triggered()), this, SLOT(actViewGraveyard())); + aViewGraveyard = new QAction(this); + connect(aViewGraveyard, SIGNAL(triggered()), this, SLOT(actViewGraveyard())); - aViewRfg = new QAction(this); - connect(aViewRfg, SIGNAL(triggered()), this, SLOT(actViewRfg())); + aViewRfg = new QAction(this); + connect(aViewRfg, SIGNAL(triggered()), this, SLOT(actViewRfg())); - if (local) { - aViewSideboard = new QAction(this); - connect(aViewSideboard, SIGNAL(triggered()), this, SLOT(actViewSideboard())); - - aDrawCard = new QAction(this); - connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard())); - aDrawCards = new QAction(this); - connect(aDrawCards, SIGNAL(triggered()), this, SLOT(actDrawCards())); - aUndoDraw = new QAction(this); - connect(aUndoDraw, SIGNAL(triggered()), this, SLOT(actUndoDraw())); - aShuffle = new QAction(this); - connect(aShuffle, SIGNAL(triggered()), this, SLOT(actShuffle())); + if (local) { + aViewSideboard = new QAction(this); + connect(aViewSideboard, SIGNAL(triggered()), this, SLOT(actViewSideboard())); + + aDrawCard = new QAction(this); + connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard())); + aDrawCards = new QAction(this); + connect(aDrawCards, SIGNAL(triggered()), this, SLOT(actDrawCards())); + aUndoDraw = new QAction(this); + connect(aUndoDraw, SIGNAL(triggered()), this, SLOT(actUndoDraw())); + aShuffle = new QAction(this); + connect(aShuffle, SIGNAL(triggered()), this, SLOT(actShuffle())); aMulligan = new QAction(this); connect(aMulligan, SIGNAL(triggered()), this, SLOT(actMulligan())); - aMoveTopCardsToGrave = new QAction(this); - connect(aMoveTopCardsToGrave, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToGrave())); - aMoveTopCardsToExile = new QAction(this); - connect(aMoveTopCardsToExile, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToExile())); - aMoveTopCardToBottom = new QAction(this); - connect(aMoveTopCardToBottom, SIGNAL(triggered()), this, SLOT(actMoveTopCardToBottom())); - } + aMoveTopCardsToGrave = new QAction(this); + connect(aMoveTopCardsToGrave, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToGrave())); + aMoveTopCardsToExile = new QAction(this); + connect(aMoveTopCardsToExile, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToExile())); + aMoveTopCardToBottom = new QAction(this); + connect(aMoveTopCardToBottom, SIGNAL(triggered()), this, SLOT(actMoveTopCardToBottom())); + } - playerMenu = new QMenu(QString()); + playerMenu = new QMenu(QString()); + table->setMenu(playerMenu); - if (local) { - handMenu = playerMenu->addMenu(QString()); + if (local) { + handMenu = playerMenu->addMenu(QString()); handMenu->addAction(aMulligan); - handMenu->addAction(aMoveHandToTopLibrary); - handMenu->addAction(aMoveHandToBottomLibrary); - handMenu->addAction(aMoveHandToGrave); - handMenu->addAction(aMoveHandToRfg); - handMenu->addSeparator(); - playerLists.append(mRevealHand = handMenu->addMenu(QString())); - playerLists.append(mRevealRandomHandCard = handMenu->addMenu(QString())); - hand->setMenu(handMenu); + handMenu->addAction(aMoveHandToTopLibrary); + handMenu->addAction(aMoveHandToBottomLibrary); + handMenu->addAction(aMoveHandToGrave); + handMenu->addAction(aMoveHandToRfg); + handMenu->addSeparator(); + playerLists.append(mRevealHand = handMenu->addMenu(QString())); + playerLists.append(mRevealRandomHandCard = handMenu->addMenu(QString())); + hand->setMenu(handMenu); - libraryMenu = playerMenu->addMenu(QString()); - libraryMenu->addAction(aDrawCard); - libraryMenu->addAction(aDrawCards); - libraryMenu->addAction(aUndoDraw); - libraryMenu->addSeparator(); - libraryMenu->addAction(aShuffle); - libraryMenu->addSeparator(); - libraryMenu->addAction(aViewLibrary); - libraryMenu->addAction(aViewTopCards); - libraryMenu->addSeparator(); - playerLists.append(mRevealLibrary = libraryMenu->addMenu(QString())); - playerLists.append(mRevealTopCard = libraryMenu->addMenu(QString())); - libraryMenu->addSeparator(); - libraryMenu->addAction(aMoveTopCardsToGrave); - libraryMenu->addAction(aMoveTopCardsToExile); - libraryMenu->addAction(aMoveTopCardToBottom); - deck->setMenu(libraryMenu, aDrawCard); - } else { - handMenu = 0; - libraryMenu = 0; - } + libraryMenu = playerMenu->addMenu(QString()); + libraryMenu->addAction(aDrawCard); + libraryMenu->addAction(aDrawCards); + libraryMenu->addAction(aUndoDraw); + libraryMenu->addSeparator(); + libraryMenu->addAction(aShuffle); + libraryMenu->addSeparator(); + libraryMenu->addAction(aViewLibrary); + libraryMenu->addAction(aViewTopCards); + libraryMenu->addSeparator(); + playerLists.append(mRevealLibrary = libraryMenu->addMenu(QString())); + playerLists.append(mRevealTopCard = libraryMenu->addMenu(QString())); + libraryMenu->addAction(aAlwaysRevealTopCard); + libraryMenu->addAction(aOpenDeckInDeckEditor); + libraryMenu->addSeparator(); + libraryMenu->addAction(aMoveTopCardsToGrave); + libraryMenu->addAction(aMoveTopCardsToExile); + libraryMenu->addAction(aMoveTopCardToBottom); + deck->setMenu(libraryMenu, aDrawCard); + } else { + handMenu = 0; + libraryMenu = 0; + } - graveMenu = playerMenu->addMenu(QString()); - graveMenu->addAction(aViewGraveyard); - grave->setMenu(graveMenu, aViewGraveyard); + graveMenu = playerMenu->addMenu(QString()); + graveMenu->addAction(aViewGraveyard); + grave->setMenu(graveMenu, aViewGraveyard); - rfgMenu = playerMenu->addMenu(QString()); - rfgMenu->addAction(aViewRfg); - rfg->setMenu(rfgMenu, aViewRfg); + rfgMenu = playerMenu->addMenu(QString()); + rfgMenu->addAction(aViewRfg); + rfg->setMenu(rfgMenu, aViewRfg); - if (local) { - graveMenu->addSeparator(); - graveMenu->addAction(aMoveGraveToTopLibrary); - graveMenu->addAction(aMoveGraveToBottomLibrary); - graveMenu->addAction(aMoveGraveToHand); - graveMenu->addAction(aMoveGraveToRfg); + if (local) { + graveMenu->addSeparator(); + graveMenu->addAction(aMoveGraveToTopLibrary); + graveMenu->addAction(aMoveGraveToBottomLibrary); + graveMenu->addAction(aMoveGraveToHand); + graveMenu->addAction(aMoveGraveToRfg); - rfgMenu->addSeparator(); - rfgMenu->addAction(aMoveRfgToTopLibrary); - rfgMenu->addAction(aMoveRfgToBottomLibrary); - rfgMenu->addAction(aMoveRfgToHand); - rfgMenu->addAction(aMoveRfgToGrave); + rfgMenu->addSeparator(); + rfgMenu->addAction(aMoveRfgToTopLibrary); + rfgMenu->addAction(aMoveRfgToBottomLibrary); + rfgMenu->addAction(aMoveRfgToHand); + rfgMenu->addAction(aMoveRfgToGrave); - sbMenu = playerMenu->addMenu(QString()); - sbMenu->addAction(aViewSideboard); - sb->setMenu(sbMenu, aViewSideboard); + sbMenu = playerMenu->addMenu(QString()); + sbMenu->addAction(aViewSideboard); + sb->setMenu(sbMenu, aViewSideboard); - aUntapAll = new QAction(this); - connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll())); + aUntapAll = new QAction(this); + connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll())); - aRollDie = new QAction(this); - connect(aRollDie, SIGNAL(triggered()), this, SLOT(actRollDie())); - - aCreateToken = new QAction(this); - connect(aCreateToken, SIGNAL(triggered()), this, SLOT(actCreateToken())); - - aCreateAnotherToken = new QAction(this); - connect(aCreateAnotherToken, SIGNAL(triggered()), this, SLOT(actCreateAnotherToken())); - aCreateAnotherToken->setEnabled(false); + aRollDie = new QAction(this); + connect(aRollDie, SIGNAL(triggered()), this, SLOT(actRollDie())); + + aCreateToken = new QAction(this); + connect(aCreateToken, SIGNAL(triggered()), this, SLOT(actCreateToken())); + + aCreateAnotherToken = new QAction(this); + connect(aCreateAnotherToken, SIGNAL(triggered()), this, SLOT(actCreateAnotherToken())); + aCreateAnotherToken->setEnabled(false); + + createPredefinedTokenMenu = new QMenu(QString()); - playerMenu->addSeparator(); - countersMenu = playerMenu->addMenu(QString()); - playerMenu->addSeparator(); - playerMenu->addAction(aUntapAll); - playerMenu->addSeparator(); - playerMenu->addAction(aRollDie); - playerMenu->addSeparator(); - playerMenu->addAction(aCreateToken); - playerMenu->addAction(aCreateAnotherToken); - playerMenu->addSeparator(); - sayMenu = playerMenu->addMenu(QString()); - initSayMenu(); - - aCardMenu = new QAction(this); - playerMenu->addSeparator(); - playerMenu->addAction(aCardMenu); + playerMenu->addSeparator(); + countersMenu = playerMenu->addMenu(QString()); + playerMenu->addSeparator(); + playerMenu->addAction(aUntapAll); + playerMenu->addSeparator(); + playerMenu->addAction(aRollDie); + playerMenu->addSeparator(); + playerMenu->addAction(aCreateToken); + playerMenu->addAction(aCreateAnotherToken); + playerMenu->addMenu(createPredefinedTokenMenu); + playerMenu->addSeparator(); + sayMenu = playerMenu->addMenu(QString()); + initSayMenu(); + + aCardMenu = new QAction(this); + playerMenu->addSeparator(); + playerMenu->addAction(aCardMenu); - for (int i = 0; i < playerLists.size(); ++i) { - QAction *newAction = playerLists[i]->addAction(QString()); - newAction->setData(-1); - connect(newAction, SIGNAL(triggered()), this, SLOT(playerListActionTriggered())); - allPlayersActions.append(newAction); - playerLists[i]->addSeparator(); - } - - } else { - countersMenu = 0; - sbMenu = 0; - aCreateAnotherToken = 0; - aCardMenu = 0; - } - - const QList &players = _parent->getPlayers().values(); - for (int i = 0; i < players.size(); ++i) - addPlayer(players[i]); - - rearrangeZones(); - retranslateUi(); + for (int i = 0; i < playerLists.size(); ++i) { + QAction *newAction = playerLists[i]->addAction(QString()); + newAction->setData(-1); + connect(newAction, SIGNAL(triggered()), this, SLOT(playerListActionTriggered())); + allPlayersActions.append(newAction); + playerLists[i]->addSeparator(); + } + } else { + countersMenu = 0; + sbMenu = 0; + aCreateAnotherToken = 0; + createPredefinedTokenMenu = 0; + aCardMenu = 0; + } + + aTap = new QAction(this); + aTap->setData(cmTap); + connect(aTap, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + aUntap = new QAction(this); + aUntap->setData(cmUntap); + connect(aUntap, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + aDoesntUntap = new QAction(this); + aDoesntUntap->setData(cmDoesntUntap); + connect(aDoesntUntap, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + aAttach = new QAction(this); + connect(aAttach, SIGNAL(triggered()), this, SLOT(actAttach())); + aUnattach = new QAction(this); + connect(aUnattach, SIGNAL(triggered()), this, SLOT(actUnattach())); + aDrawArrow = new QAction(this); + connect(aDrawArrow, SIGNAL(triggered()), this, SLOT(actDrawArrow())); + aIncP = new QAction(this); + connect(aIncP, SIGNAL(triggered()), this, SLOT(actIncP())); + aDecP = new QAction(this); + connect(aDecP, SIGNAL(triggered()), this, SLOT(actDecP())); + aIncT = new QAction(this); + connect(aIncT, SIGNAL(triggered()), this, SLOT(actIncT())); + aDecT = new QAction(this); + connect(aDecT, SIGNAL(triggered()), this, SLOT(actDecT())); + aIncPT = new QAction(this); + connect(aIncPT, SIGNAL(triggered()), this, SLOT(actIncPT())); + aDecPT = new QAction(this); + connect(aDecPT, SIGNAL(triggered()), this, SLOT(actDecPT())); + aSetPT = new QAction(this); + connect(aSetPT, SIGNAL(triggered()), this, SLOT(actSetPT())); + aSetAnnotation = new QAction(this); + connect(aSetAnnotation, SIGNAL(triggered()), this, SLOT(actSetAnnotation())); + aFlip = new QAction(this); + aFlip->setData(cmFlip); + connect(aFlip, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + aPeek = new QAction(this); + aPeek->setData(cmPeek); + connect(aPeek, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + aClone = new QAction(this); + aClone->setData(cmClone); + connect(aClone, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + aMoveToTopLibrary = new QAction(this); + aMoveToTopLibrary->setData(cmMoveToTopLibrary); + aMoveToBottomLibrary = new QAction(this); + aMoveToBottomLibrary->setData(cmMoveToBottomLibrary); + aMoveToGraveyard = new QAction(this); + aMoveToGraveyard->setData(cmMoveToGraveyard); + aMoveToExile = new QAction(this); + aMoveToExile->setData(cmMoveToExile); + connect(aMoveToTopLibrary, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aMoveToBottomLibrary, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aMoveToGraveyard, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aMoveToExile, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + + aPlay = new QAction(this); + connect(aPlay, SIGNAL(triggered()), this, SLOT(actPlay())); + aHide = new QAction(this); + connect(aHide, SIGNAL(triggered()), this, SLOT(actHide())); + + for (int i = 0; i < 3; ++i) { + QAction *tempAddCounter = new QAction(this); + tempAddCounter->setData(9 + i * 1000); + QAction *tempRemoveCounter = new QAction(this); + tempRemoveCounter->setData(10 + i * 1000); + QAction *tempSetCounter = new QAction(this); + tempSetCounter->setData(11 + i * 1000); + aAddCounter.append(tempAddCounter); + aRemoveCounter.append(tempRemoveCounter); + aSetCounter.append(tempSetCounter); + connect(tempAddCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); + connect(tempRemoveCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); + connect(tempSetCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); + } + + const QList &players = game->getPlayers().values(); + for (int i = 0; i < players.size(); ++i) + addPlayer(players[i]); + + rearrangeZones(); + retranslateUi(); } Player::~Player() { - qDebug() << "Player destructor:" << getName(); + qDebug() << "Player destructor:" << getName(); - static_cast(scene())->removePlayer(this); - - clear(); - QMapIterator i(zones); - while (i.hasNext()) - delete i.next().value(); - zones.clear(); - - delete playerMenu; - delete userInfo; + static_cast(scene())->removePlayer(this); + + clear(); + QMapIterator i(zones); + while (i.hasNext()) + delete i.next().value(); + zones.clear(); + + delete playerMenu; + delete userInfo; } void Player::clear() { - clearArrows(); - - QMapIterator i(zones); - while (i.hasNext()) - i.next().value()->clearContents(); - - clearCounters(); + clearArrows(); + + QMapIterator i(zones); + while (i.hasNext()) + i.next().value()->clearContents(); + + clearCounters(); } void Player::addPlayer(Player *player) { - if (player == this) - return; - for (int i = 0; i < playerLists.size(); ++i) { - QAction *newAction = playerLists[i]->addAction(player->getName()); - newAction->setData(player->getId()); - connect(newAction, SIGNAL(triggered()), this, SLOT(playerListActionTriggered())); - } + if (player == this) + return; + for (int i = 0; i < playerLists.size(); ++i) { + QAction *newAction = playerLists[i]->addAction(player->getName()); + newAction->setData(player->getId()); + connect(newAction, SIGNAL(triggered()), this, SLOT(playerListActionTriggered())); + } } void Player::removePlayer(Player *player) { - for (int i = 0; i < playerLists.size(); ++i) { - QList actionList = playerLists[i]->actions(); - for (int j = 0; j < actionList.size(); ++j) - if (actionList[j]->data().toInt() == player->getId()) { - playerLists[i]->removeAction(actionList[j]); - actionList[j]->deleteLater(); - } - } + for (int i = 0; i < playerLists.size(); ++i) { + QList actionList = playerLists[i]->actions(); + for (int j = 0; j < actionList.size(); ++j) + if (actionList[j]->data().toInt() == player->getId()) { + playerLists[i]->removeAction(actionList[j]); + actionList[j]->deleteLater(); + } + } } void Player::playerListActionTriggered() { - QAction *action = static_cast(sender()); - QMenu *menu = static_cast(action->parentWidget()); - int otherPlayerId = action->data().toInt(); - - if (menu == mRevealLibrary) { - sendGameCommand(new Command_RevealCards(-1, "deck", -1, otherPlayerId)); - } else if (menu == mRevealTopCard) { - sendGameCommand(new Command_RevealCards(-1, "deck", 0, otherPlayerId)); - } else if (menu == mRevealHand) { - sendGameCommand(new Command_RevealCards(-1, "hand", -1, otherPlayerId)); - } else if (menu == mRevealRandomHandCard) { - sendGameCommand(new Command_RevealCards(-1, "hand", -2, otherPlayerId)); - } + QAction *action = static_cast(sender()); + QMenu *menu = static_cast(action->parentWidget()); + + Command_RevealCards cmd; + const int otherPlayerId = action->data().toInt(); + if (otherPlayerId != -1) + cmd.set_player_id(otherPlayerId); + + if (menu == mRevealLibrary) { + cmd.set_zone_name("deck"); + cmd.set_grant_write_access(true); + } else if (menu == mRevealTopCard) { + cmd.set_zone_name("deck"); + cmd.set_card_id(0); + } else if (menu == mRevealHand) + cmd.set_zone_name("hand"); + else if (menu == mRevealRandomHandCard) { + cmd.set_zone_name("hand"); + cmd.set_card_id(-2); + } else + return; + + sendGameCommand(cmd); } void Player::rearrangeZones() { - QPointF base = QPointF(CARD_HEIGHT + counterAreaWidth + 15, 0); - if (settingsCache->getHorizontalHand()) { - if (mirrored) { - if (hand->contentsKnown()) { - handVisible = true; - hand->setPos(base); - base += QPointF(0, hand->boundingRect().height()); - } else - handVisible = false; - - stack->setPos(base); - base += QPointF(stack->boundingRect().width(), 0); - - table->setPos(base); - } else { - stack->setPos(base); - - table->setPos(base.x() + stack->boundingRect().width(), 0); - base += QPointF(0, table->boundingRect().height()); - - if (hand->contentsKnown()) { - handVisible = true; - hand->setPos(base); - } else - handVisible = false; - } - hand->setWidth(table->getWidth() + stack->boundingRect().width()); - } else { - handVisible = true; - - hand->setPos(base); - base += QPointF(hand->boundingRect().width(), 0); - - stack->setPos(base); - base += QPointF(stack->boundingRect().width(), 0); - - table->setPos(base); - } - hand->setVisible(handVisible); - hand->updateOrientation(); - table->reorganizeCards(); - updateBoundingRect(); - rearrangeCounters(); + QPointF base = QPointF(CARD_HEIGHT + counterAreaWidth + 15, 0); + if (settingsCache->getHorizontalHand()) { + if (mirrored) { + if (hand->contentsKnown()) { + handVisible = true; + hand->setPos(base); + base += QPointF(0, hand->boundingRect().height()); + } else + handVisible = false; + + stack->setPos(base); + base += QPointF(stack->boundingRect().width(), 0); + + table->setPos(base); + } else { + stack->setPos(base); + + table->setPos(base.x() + stack->boundingRect().width(), 0); + base += QPointF(0, table->boundingRect().height()); + + if (hand->contentsKnown()) { + handVisible = true; + hand->setPos(base); + } else + handVisible = false; + } + hand->setWidth(table->getWidth() + stack->boundingRect().width()); + } else { + handVisible = true; + + hand->setPos(base); + base += QPointF(hand->boundingRect().width(), 0); + + stack->setPos(base); + base += QPointF(stack->boundingRect().width(), 0); + + table->setPos(base); + } + hand->setVisible(handVisible); + hand->updateOrientation(); + table->reorganizeCards(); + updateBoundingRect(); + rearrangeCounters(); } void Player::updateZones() { - table->reorganizeCards(); + table->reorganizeCards(); } void Player::updateBoundingRect() { - prepareGeometryChange(); - qreal width = CARD_HEIGHT + 15 + counterAreaWidth + stack->boundingRect().width(); - if (settingsCache->getHorizontalHand()) { - qreal handHeight = handVisible ? hand->boundingRect().height() : 0; - bRect = QRectF(0, 0, width + table->boundingRect().width(), table->boundingRect().height() + handHeight); - } else - bRect = QRectF(0, 0, width + hand->boundingRect().width() + table->boundingRect().width(), table->boundingRect().height()); - playerArea->setSize(CARD_HEIGHT + counterAreaWidth + 15, bRect.height()); - - emit sizeChanged(); + prepareGeometryChange(); + qreal width = CARD_HEIGHT + 15 + counterAreaWidth + stack->boundingRect().width(); + if (settingsCache->getHorizontalHand()) { + qreal handHeight = handVisible ? hand->boundingRect().height() : 0; + bRect = QRectF(0, 0, width + table->boundingRect().width(), table->boundingRect().height() + handHeight); + } else + bRect = QRectF(0, 0, width + hand->boundingRect().width() + table->boundingRect().width(), table->boundingRect().height()); + playerArea->setSize(CARD_HEIGHT + counterAreaWidth + 15, bRect.height()); + + emit sizeChanged(); } void Player::retranslateUi() { - aViewGraveyard->setText(tr("&View graveyard")); - aViewRfg->setText(tr("&View exile")); - playerMenu->setTitle(tr("Player \"%1\"").arg(userInfo->getName())); - graveMenu->setTitle(tr("&Graveyard")); - rfgMenu->setTitle(tr("&Exile")); - - if (local) { - aMoveHandToTopLibrary->setText(tr("Move to &top of library")); - aMoveHandToBottomLibrary->setText(tr("Move to &bottom of library")); - aMoveHandToGrave->setText(tr("Move to &graveyard")); - aMoveHandToRfg->setText(tr("Move to &exile")); - aMoveGraveToTopLibrary->setText(tr("Move to &top of library")); - aMoveGraveToBottomLibrary->setText(tr("Move to &bottom of library")); - aMoveGraveToHand->setText(tr("Move to &hand")); - aMoveGraveToRfg->setText(tr("Move to &exile")); - aMoveRfgToTopLibrary->setText(tr("Move to &top of library")); - aMoveRfgToBottomLibrary->setText(tr("Move to &bottom of library")); - aMoveRfgToHand->setText(tr("Move to &hand")); - aMoveRfgToGrave->setText(tr("Move to &graveyard")); - aViewLibrary->setText(tr("&View library")); - aViewTopCards->setText(tr("View &top cards of library...")); - mRevealLibrary->setTitle(tr("Reveal &library to")); - mRevealTopCard->setTitle(tr("Reveal t&op card to")); - aViewSideboard->setText(tr("&View sideboard")); - aDrawCard->setText(tr("&Draw card")); - aDrawCards->setText(tr("D&raw cards...")); - aUndoDraw->setText(tr("&Undo last draw")); - aMulligan->setText(tr("Take &mulligan")); - aShuffle->setText(tr("&Shuffle")); - aMoveTopCardsToGrave->setText(tr("Move top cards to &graveyard...")); - aMoveTopCardsToExile->setText(tr("Move top cards to &exile...")); - aMoveTopCardToBottom->setText(tr("Put top card on &bottom")); - - handMenu->setTitle(tr("&Hand")); - mRevealHand->setTitle(tr("&Reveal to")); - mRevealRandomHandCard->setTitle(tr("Reveal r&andom card to")); - sbMenu->setTitle(tr("&Sideboard")); - libraryMenu->setTitle(tr("&Library")); - countersMenu->setTitle(tr("&Counters")); + aViewGraveyard->setText(tr("&View graveyard")); + aViewRfg->setText(tr("&View exile")); + playerMenu->setTitle(tr("Player \"%1\"").arg(QString::fromStdString(userInfo->name()))); + graveMenu->setTitle(tr("&Graveyard")); + rfgMenu->setTitle(tr("&Exile")); + + if (local) { + aMoveHandToTopLibrary->setText(tr("Move to &top of library")); + aMoveHandToBottomLibrary->setText(tr("Move to &bottom of library")); + aMoveHandToGrave->setText(tr("Move to &graveyard")); + aMoveHandToRfg->setText(tr("Move to &exile")); + aMoveGraveToTopLibrary->setText(tr("Move to &top of library")); + aMoveGraveToBottomLibrary->setText(tr("Move to &bottom of library")); + aMoveGraveToHand->setText(tr("Move to &hand")); + aMoveGraveToRfg->setText(tr("Move to &exile")); + aMoveRfgToTopLibrary->setText(tr("Move to &top of library")); + aMoveRfgToBottomLibrary->setText(tr("Move to &bottom of library")); + aMoveRfgToHand->setText(tr("Move to &hand")); + aMoveRfgToGrave->setText(tr("Move to &graveyard")); + aViewLibrary->setText(tr("&View library")); + aViewTopCards->setText(tr("View &top cards of library...")); + mRevealLibrary->setTitle(tr("Reveal &library to")); + mRevealTopCard->setTitle(tr("Reveal t&op card to")); + aAlwaysRevealTopCard->setText(tr("&Always reveal top card")); + aOpenDeckInDeckEditor->setText(tr("O&pen deck in deck editor")); + aViewSideboard->setText(tr("&View sideboard")); + aDrawCard->setText(tr("&Draw card")); + aDrawCards->setText(tr("D&raw cards...")); + aUndoDraw->setText(tr("&Undo last draw")); + aMulligan->setText(tr("Take &mulligan")); + aShuffle->setText(tr("&Shuffle")); + aMoveTopCardsToGrave->setText(tr("Move top cards to &graveyard...")); + aMoveTopCardsToExile->setText(tr("Move top cards to &exile...")); + aMoveTopCardToBottom->setText(tr("Put top card on &bottom")); + + handMenu->setTitle(tr("&Hand")); + mRevealHand->setTitle(tr("&Reveal to")); + mRevealRandomHandCard->setTitle(tr("Reveal r&andom card to")); + sbMenu->setTitle(tr("&Sideboard")); + libraryMenu->setTitle(tr("&Library")); + countersMenu->setTitle(tr("&Counters")); - aUntapAll->setText(tr("&Untap all permanents")); - aRollDie->setText(tr("R&oll die...")); - aCreateToken->setText(tr("&Create token...")); - aCreateAnotherToken->setText(tr("C&reate another token")); - sayMenu->setTitle(tr("S&ay")); - - QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) - counterIterator.next().value()->retranslateUi(); + aUntapAll->setText(tr("&Untap all permanents")); + aRollDie->setText(tr("R&oll die...")); + aCreateToken->setText(tr("&Create token...")); + aCreateAnotherToken->setText(tr("C&reate another token")); + createPredefinedTokenMenu->setTitle(tr("Cr&eate predefined token")); + sayMenu->setTitle(tr("S&ay")); + + QMapIterator counterIterator(counters); + while (counterIterator.hasNext()) + counterIterator.next().value()->retranslateUi(); - aCardMenu->setText(tr("C&ard")); - - for (int i = 0; i < allPlayersActions.size(); ++i) - allPlayersActions[i]->setText(tr("&All players")); - } - - QMapIterator zoneIterator(zones); - while (zoneIterator.hasNext()) - zoneIterator.next().value()->retranslateUi(); + aCardMenu->setText(tr("C&ard")); + + for (int i = 0; i < allPlayersActions.size(); ++i) + allPlayersActions[i]->setText(tr("&All players")); + } + + aPlay->setText(tr("&Play")); + aHide->setText(tr("&Hide")); + + aTap->setText(tr("&Tap")); + aUntap->setText(tr("&Untap")); + aDoesntUntap->setText(tr("Toggle &normal untapping")); + aFlip->setText(tr("&Flip")); + aPeek->setText(tr("&Peek at card face")); + aClone->setText(tr("&Clone")); + aClone->setShortcut(tr("Ctrl+J")); + aAttach->setText(tr("Attac&h to card...")); + aAttach->setShortcut(tr("Ctrl+A")); + aUnattach->setText(tr("Unattac&h")); + aDrawArrow->setText(tr("&Draw arrow...")); + aIncP->setText(tr("&Increase power")); + aIncP->setShortcut(tr("Ctrl++")); + aDecP->setText(tr("&Decrease power")); + aDecP->setShortcut(tr("Ctrl+-")); + aIncT->setText(tr("I&ncrease toughness")); + aIncT->setShortcut(tr("Alt++")); + aDecT->setText(tr("D&ecrease toughness")); + aDecT->setShortcut(tr("Alt+-")); + aIncPT->setText(tr("In&crease power and toughness")); + aIncPT->setShortcut(tr("Ctrl+Alt++")); + aDecPT->setText(tr("Dec&rease power and toughness")); + aDecPT->setShortcut(tr("Ctrl+Alt+-")); + aSetPT->setText(tr("Set &power and toughness...")); + aSetPT->setShortcut(tr("Ctrl+P")); + aSetAnnotation->setText(tr("&Set annotation...")); + QStringList counterColors; + counterColors.append(tr("red")); + counterColors.append(tr("yellow")); + counterColors.append(tr("green")); + for (int i = 0; i < aAddCounter.size(); ++i) + aAddCounter[i]->setText(tr("&Add counter (%1)").arg(counterColors[i])); + for (int i = 0; i < aRemoveCounter.size(); ++i) + aRemoveCounter[i]->setText(tr("&Remove counter (%1)").arg(counterColors[i])); + for (int i = 0; i < aSetCounter.size(); ++i) + aSetCounter[i]->setText(tr("&Set counters (%1)...").arg(counterColors[i])); + aMoveToTopLibrary->setText(tr("&top of library")); + aMoveToBottomLibrary->setText(tr("&bottom of library")); + aMoveToGraveyard->setText(tr("&graveyard")); + aMoveToGraveyard->setShortcut(tr("Ctrl+Del")); + aMoveToExile->setText(tr("&exile")); + + QMapIterator zoneIterator(zones); + while (zoneIterator.hasNext()) + zoneIterator.next().value()->retranslateUi(); } void Player::setShortcutsActive() { - shortcutsActive = true; - - aViewSideboard->setShortcut(tr("Ctrl+F3")); - aViewLibrary->setShortcut(tr("F3")); - aViewTopCards->setShortcut(tr("Ctrl+W")); - aViewGraveyard->setShortcut(tr("F4")); - aDrawCard->setShortcut(tr("Ctrl+D")); - aDrawCards->setShortcut(tr("Ctrl+E")); - aUndoDraw->setShortcut(tr("Ctrl+Shift+D")); - aMulligan->setShortcut(tr("Ctrl+M")); - aShuffle->setShortcut(tr("Ctrl+S")); - aUntapAll->setShortcut(tr("Ctrl+U")); - aRollDie->setShortcut(tr("Ctrl+I")); - aCreateToken->setShortcut(tr("Ctrl+T")); - aCreateAnotherToken->setShortcut(tr("Ctrl+G")); + shortcutsActive = true; + + aViewSideboard->setShortcut(tr("Ctrl+F3")); + aViewLibrary->setShortcut(tr("F3")); + aViewTopCards->setShortcut(tr("Ctrl+W")); + aViewGraveyard->setShortcut(tr("F4")); + aDrawCard->setShortcut(tr("Ctrl+D")); + aDrawCards->setShortcut(tr("Ctrl+E")); + aUndoDraw->setShortcut(tr("Ctrl+Shift+D")); + aMulligan->setShortcut(tr("Ctrl+M")); + aShuffle->setShortcut(tr("Ctrl+S")); + aUntapAll->setShortcut(tr("Ctrl+U")); + aRollDie->setShortcut(tr("Ctrl+I")); + aCreateToken->setShortcut(tr("Ctrl+T")); + aCreateAnotherToken->setShortcut(tr("Ctrl+G")); - QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) - counterIterator.next().value()->setShortcutsActive(); + QMapIterator counterIterator(counters); + while (counterIterator.hasNext()) + counterIterator.next().value()->setShortcutsActive(); } void Player::setShortcutsInactive() { - shortcutsActive = false; - - aViewSideboard->setShortcut(QKeySequence()); - aViewLibrary->setShortcut(QKeySequence()); - aViewTopCards->setShortcut(QKeySequence()); - aViewGraveyard->setShortcut(QKeySequence()); - aDrawCard->setShortcut(QKeySequence()); - aDrawCards->setShortcut(QKeySequence()); - aUndoDraw->setShortcut(QKeySequence()); - aMulligan->setShortcut(QKeySequence()); - aShuffle->setShortcut(QKeySequence()); - aUntapAll->setShortcut(QKeySequence()); - aRollDie->setShortcut(QKeySequence()); - aCreateToken->setShortcut(QKeySequence()); - aCreateAnotherToken->setShortcut(QKeySequence()); + shortcutsActive = false; + + aViewSideboard->setShortcut(QKeySequence()); + aViewLibrary->setShortcut(QKeySequence()); + aViewTopCards->setShortcut(QKeySequence()); + aViewGraveyard->setShortcut(QKeySequence()); + aDrawCard->setShortcut(QKeySequence()); + aDrawCards->setShortcut(QKeySequence()); + aUndoDraw->setShortcut(QKeySequence()); + aMulligan->setShortcut(QKeySequence()); + aShuffle->setShortcut(QKeySequence()); + aUntapAll->setShortcut(QKeySequence()); + aRollDie->setShortcut(QKeySequence()); + aCreateToken->setShortcut(QKeySequence()); + aCreateAnotherToken->setShortcut(QKeySequence()); - QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) - counterIterator.next().value()->setShortcutsInactive(); + QMapIterator counterIterator(counters); + while (counterIterator.hasNext()) + counterIterator.next().value()->setShortcutsInactive(); } void Player::initSayMenu() { - sayMenu->clear(); + sayMenu->clear(); - QSettings settings; - settings.beginGroup("messages"); - int count = settings.value("count", 0).toInt(); - for (int i = 0; i < count; i++) { - QAction *newAction = new QAction(settings.value(QString("msg%1").arg(i)).toString(), this); - if (i <= 10) - newAction->setShortcut(QString("Ctrl+%1").arg((i + 1) % 10)); - connect(newAction, SIGNAL(triggered()), this, SLOT(actSayMessage())); - sayMenu->addAction(newAction); - } + QSettings settings; + settings.beginGroup("messages"); + int count = settings.value("count", 0).toInt(); + for (int i = 0; i < count; i++) { + QAction *newAction = new QAction(settings.value(QString("msg%1").arg(i)).toString(), this); + if (i <= 10) + newAction->setShortcut(QString("Ctrl+%1").arg((i + 1) % 10)); + connect(newAction, SIGNAL(triggered()), this, SLOT(actSayMessage())); + sayMenu->addAction(newAction); + } +} + +void Player::setDeck(const DeckLoader &_deck) +{ + deck = new DeckLoader(_deck); + aOpenDeckInDeckEditor->setEnabled(deck); + + createPredefinedTokenMenu->clear(); + predefinedTokens.clear(); + InnerDecklistNode *tokenZone = dynamic_cast(deck->getRoot()->findChild("tokens")); + if (tokenZone) + for (int i = 0; i < tokenZone->size(); ++i) { + const QString tokenName = tokenZone->at(i)->getName(); + predefinedTokens.append(tokenName); + QAction *a = createPredefinedTokenMenu->addAction(tokenName); + if (i < 10) + a->setShortcut("Alt+" + QString::number((i + 1) % 10)); + connect(a, SIGNAL(triggered()), this, SLOT(actCreatePredefinedToken())); + } } void Player::actViewLibrary() { - static_cast(scene())->toggleZoneView(this, "deck", -1); + static_cast(scene())->toggleZoneView(this, "deck", -1); } void Player::actViewTopCards() { - bool ok; - int number = QInputDialog::getInteger(0, tr("View top cards of library"), tr("Number of cards:"), defaultNumberTopCards, 1, 2000000000, 1, &ok); - if (ok) { - defaultNumberTopCards = number; - static_cast(scene())->toggleZoneView(this, "deck", number); - } + bool ok; + int number = +#if QT_VERSION < 0x050000 + QInputDialog::getInteger( +#else + QInputDialog::getInt( +#endif + 0, tr("View top cards of library"), tr("Number of cards:"), defaultNumberTopCards, 1, 2000000000, 1, &ok); + if (ok) { + defaultNumberTopCards = number; + static_cast(scene())->toggleZoneView(this, "deck", number); + } +} + +void Player::actAlwaysRevealTopCard() +{ + Command_ChangeZoneProperties cmd; + cmd.set_zone_name("deck"); + cmd.set_always_reveal_top_card(aAlwaysRevealTopCard->isChecked()); + + sendGameCommand(cmd); +} + +void Player::actOpenDeckInDeckEditor() +{ + emit openDeckEditor(deck); } void Player::actViewGraveyard() { - static_cast(scene())->toggleZoneView(this, "grave", -1); + static_cast(scene())->toggleZoneView(this, "grave", -1); } void Player::actViewRfg() { - static_cast(scene())->toggleZoneView(this, "rfg", -1); + static_cast(scene())->toggleZoneView(this, "rfg", -1); } void Player::actViewSideboard() { - static_cast(scene())->toggleZoneView(this, "sb", -1); + static_cast(scene())->toggleZoneView(this, "sb", -1); } void Player::actShuffle() { - sendGameCommand(new Command_Shuffle); + sendGameCommand(Command_Shuffle()); } void Player::actDrawCard() { - sendGameCommand(new Command_DrawCards(-1, 1)); + Command_DrawCards cmd; + cmd.set_number(1); + sendGameCommand(cmd); } void Player::actMulligan() { - sendGameCommand(new Command_Mulligan); + sendGameCommand(Command_Mulligan()); } void Player::actDrawCards() { - int number = QInputDialog::getInteger(0, tr("Draw cards"), tr("Number:")); - if (number) - sendGameCommand(new Command_DrawCards(-1, number)); + int number = +#if QT_VERSION < 0x050000 + QInputDialog::getInteger( +#else + QInputDialog::getInt( +#endif + 0, tr("Draw cards"), tr("Number:")); + if (number) { + Command_DrawCards cmd; + cmd.set_number(number); + sendGameCommand(cmd); + } } void Player::actUndoDraw() { - sendGameCommand(new Command_UndoDraw); + sendGameCommand(Command_UndoDraw()); } void Player::actMoveTopCardsToGrave() { - int number = QInputDialog::getInteger(0, tr("Move top cards to grave"), tr("Number:")); - if (!number) - return; + int number = +#if QT_VERSION < 0x050000 + QInputDialog::getInteger( +#else + QInputDialog::getInt( +#endif + 0, tr("Move top cards to grave"), tr("Number:")); + if (!number) + return; - QList commandList; - const int maxCards = zones.value("deck")->getCards().size(); - if (number > maxCards) - number = maxCards; - QList idList; - for (int i = 0; i < number; ++i) - idList.append(new CardToMove(i)); - sendGameCommand(new Command_MoveCard(-1, "deck", idList, getId(), "grave", 0, 0)); + const int maxCards = zones.value("deck")->getCards().size(); + if (number > maxCards) + number = maxCards; + + Command_MoveCard cmd; + cmd.set_start_zone("deck"); + cmd.set_target_player_id(getId()); + cmd.set_target_zone("grave"); + cmd.set_x(0); + cmd.set_y(0); + + for (int i = 0; i < number; ++i) + cmd.mutable_cards_to_move()->add_card()->set_card_id(i); + + sendGameCommand(cmd); } void Player::actMoveTopCardsToExile() { - int number = QInputDialog::getInteger(0, tr("Move top cards to exile"), tr("Number:")); - if (!number) - return; + int number = +#if QT_VERSION < 0x050000 + QInputDialog::getInteger( +#else + QInputDialog::getInt( +#endif + 0, tr("Move top cards to exile"), tr("Number:")); + if (!number) + return; - QList commandList; - const int maxCards = zones.value("deck")->getCards().size(); - if (number > maxCards) - number = maxCards; - QList idList; - for (int i = 0; i < number; ++i) - idList.append(new CardToMove(i)); - sendGameCommand(new Command_MoveCard(-1, "deck", idList, getId(), "rfg", 0, 0)); + const int maxCards = zones.value("deck")->getCards().size(); + if (number > maxCards) + number = maxCards; + + Command_MoveCard cmd; + cmd.set_start_zone("deck"); + cmd.set_target_player_id(getId()); + cmd.set_target_zone("rfg"); + cmd.set_x(0); + cmd.set_y(0); + + for (int i = 0; i < number; ++i) + cmd.mutable_cards_to_move()->add_card()->set_card_id(i); + + sendGameCommand(cmd); } void Player::actMoveTopCardToBottom() { - sendGameCommand(new Command_MoveCard(-1, "deck", QList() << new CardToMove(0), getId(), "deck", -1, 0)); + Command_MoveCard cmd; + cmd.set_start_zone("deck"); + cmd.mutable_cards_to_move()->add_card()->set_card_id(0); + cmd.set_target_player_id(getId()); + cmd.set_target_zone("deck"); + cmd.set_x(-1); + cmd.set_y(0); + + sendGameCommand(cmd); } void Player::actUntapAll() { - sendGameCommand(new Command_SetCardAttr(-1, "table", -1, "tapped", "0")); + Command_SetCardAttr cmd; + cmd.set_zone("table"); + cmd.set_attribute(AttrTapped); + cmd.set_attr_value("0"); + + sendGameCommand(cmd); } void Player::actRollDie() { - bool ok; - int sides = QInputDialog::getInteger(0, tr("Roll die"), tr("Number of sides:"), 20, 2, 1000, 1, &ok); - if (ok) - sendGameCommand(new Command_RollDie(-1, sides)); + bool ok; + int sides = +#if QT_VERSION < 0x050000 + QInputDialog::getInteger( +#else + QInputDialog::getInt( +#endif + 0, tr("Roll die"), tr("Number of sides:"), 20, 2, 1000, 1, &ok); + if (ok) { + Command_RollDie cmd; + cmd.set_sides(sides); + sendGameCommand(cmd); + } } void Player::actCreateToken() { - DlgCreateToken dlg; - if (!dlg.exec()) - return; - - lastTokenName = dlg.getName(); - lastTokenColor = dlg.getColor(); - lastTokenPT = dlg.getPT(); - lastTokenAnnotation = dlg.getAnnotation(); - lastTokenDestroy = dlg.getDestroy(); - aCreateAnotherToken->setEnabled(true); - - sendGameCommand(new Command_CreateToken(-1, "table", dlg.getName(), dlg.getColor(), dlg.getPT(), dlg.getAnnotation(), dlg.getDestroy(), -1, 0)); + DlgCreateToken dlg(predefinedTokens); + if (!dlg.exec()) + return; + + lastTokenName = dlg.getName(); + if (CardInfo *correctedCard = db->getCardBySimpleName(lastTokenName, false)) { + lastTokenName = correctedCard->getName(); + } + + lastTokenColor = dlg.getColor(); + lastTokenPT = dlg.getPT(); + lastTokenAnnotation = dlg.getAnnotation(); + lastTokenDestroy = dlg.getDestroy(); + aCreateAnotherToken->setEnabled(true); + + actCreateAnotherToken(); } void Player::actCreateAnotherToken() { - sendGameCommand(new Command_CreateToken(-1, "table", lastTokenName, lastTokenColor, lastTokenPT, lastTokenAnnotation, lastTokenDestroy, -1, 0)); + Command_CreateToken cmd; + cmd.set_zone("table"); + cmd.set_card_name(lastTokenName.toStdString()); + cmd.set_color(lastTokenColor.toStdString()); + cmd.set_pt(lastTokenPT.toStdString()); + cmd.set_annotation(lastTokenAnnotation.toStdString()); + cmd.set_destroy_on_zone_change(lastTokenDestroy); + cmd.set_x(-1); + cmd.set_y(0); + + sendGameCommand(cmd); +} + +void Player::actCreatePredefinedToken() +{ + QAction *action = static_cast(sender()); + CardInfo *cardInfo = db->getCard(action->text()); + + lastTokenName = cardInfo->getName(); + lastTokenColor = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().first().toLower(); + lastTokenPT = cardInfo->getPowTough(); + lastTokenAnnotation = cardInfo->getText(); + lastTokenDestroy = true; + aCreateAnotherToken->setEnabled(true); + + actCreateAnotherToken(); } void Player::actSayMessage() { - QAction *a = qobject_cast(sender()); - sendGameCommand(new Command_Say(-1, a->text())); + QAction *a = qobject_cast(sender()); + Command_GameSay cmd; + cmd.set_message(a->text().toStdString()); + sendGameCommand(cmd); } -void Player::setCardAttrHelper(GameEventContext *context, CardItem *card, const QString &aname, const QString &avalue, bool allCards) +void Player::setCardAttrHelper(const GameEventContext &context, CardItem *card, CardAttribute attribute, const QString &avalue, bool allCards) { - bool moveCardContext = qobject_cast(context); - if (aname == "tapped") { - bool tapped = avalue == "1"; - if (!(!tapped && card->getDoesntUntap() && allCards)) { - if (!allCards) - emit logSetTapped(this, card, tapped); - card->setTapped(tapped, !moveCardContext); - } - } else if (aname == "attacking") - card->setAttacking(avalue == "1"); - else if (aname == "facedown") - card->setFaceDown(avalue == "1"); - else if (aname == "annotation") { - emit logSetAnnotation(this, card, avalue); - card->setAnnotation(avalue); - } else if (aname == "doesnt_untap") { - bool value = (avalue == "1"); - emit logSetDoesntUntap(this, card, value); - card->setDoesntUntap(value); - } else if (aname == "pt") { - emit logSetPT(this, card, avalue); - card->setPT(avalue); - } + bool moveCardContext = context.HasExtension(Context_MoveCard::ext); + switch (attribute) { + case AttrTapped: { + bool tapped = avalue == "1"; + if (!(!tapped && card->getDoesntUntap() && allCards)) { + if (!allCards) + emit logSetTapped(this, card, tapped); + card->setTapped(tapped, !moveCardContext); + } + break; + } + case AttrAttacking: card->setAttacking(avalue == "1"); break; + case AttrFaceDown: card->setFaceDown(avalue == "1"); break; + case AttrColor: card->setColor(avalue); break; + case AttrAnnotation: { + emit logSetAnnotation(this, card, avalue); + card->setAnnotation(avalue); + break; + } + case AttrDoesntUntap: { + bool value = (avalue == "1"); + emit logSetDoesntUntap(this, card, value); + card->setDoesntUntap(value); + break; + } + case AttrPT: { + emit logSetPT(this, card, avalue); + card->setPT(avalue); + break; + } + } } -void Player::eventConnectionStateChanged(Event_ConnectionStateChanged *event) +void Player::eventGameSay(const Event_GameSay &event) { - emit logConnectionStateChanged(this, event->getConnected()); + emit logSay(this, QString::fromStdString(event.message())); } -void Player::eventSay(Event_Say *event) +void Player::eventShuffle(const Event_Shuffle &event) { - emit logSay(this, event->getMessage()); + CardZone *zone = zones.value(QString::fromStdString(event.zone_name())); + if (!zone) + return; + if (zone->getView()) + if (zone->getView()->getRevealZone()) + zone->getView()->setWriteableRevealZone(false); + emit logShuffle(this, zone); } -void Player::eventShuffle(Event_Shuffle * /*event*/) +void Player::eventRollDie(const Event_RollDie &event) { - emit logShuffle(this, zones.value("deck")); + emit logRollDie(this, event.sides(), event.value()); } -void Player::eventRollDie(Event_RollDie *event) +void Player::eventCreateArrow(const Event_CreateArrow &event) { - emit logRollDie(this, event->getSides(), event->getValue()); + ArrowItem *arrow = addArrow(event.arrow_info()); + if (!arrow) + return; + + CardItem *startCard = static_cast(arrow->getStartItem()); + CardItem *targetCard = qgraphicsitem_cast(arrow->getTargetItem()); + if (targetCard) + emit logCreateArrow(this, startCard->getOwner(), startCard->getName(), targetCard->getOwner(), targetCard->getName(), false); + else + emit logCreateArrow(this, startCard->getOwner(), startCard->getName(), arrow->getTargetItem()->getOwner(), QString(), true); } -void Player::eventCreateArrows(Event_CreateArrows *event) +void Player::eventDeleteArrow(const Event_DeleteArrow &event) { - const QList eventArrowList = event->getArrowList(); - for (int i = 0; i < eventArrowList.size(); ++i) { - ArrowItem *arrow = addArrow(eventArrowList[i]); - if (!arrow) - return; - - CardItem *startCard = static_cast(arrow->getStartItem()); - CardItem *targetCard = qgraphicsitem_cast(arrow->getTargetItem()); - if (targetCard) - emit logCreateArrow(this, startCard->getOwner(), startCard->getName(), targetCard->getOwner(), targetCard->getName(), false); - else - emit logCreateArrow(this, startCard->getOwner(), startCard->getName(), arrow->getTargetItem()->getOwner(), QString(), true); - } + delArrow(event.arrow_id()); } -void Player::eventDeleteArrow(Event_DeleteArrow *event) +void Player::eventCreateToken(const Event_CreateToken &event) { - delArrow(event->getArrowId()); -} + CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); + if (!zone) + return; -void Player::eventCreateToken(Event_CreateToken *event) -{ - CardZone *zone = zones.value(event->getZone(), 0); - if (!zone) - return; + CardItem *card = new CardItem(this, QString::fromStdString(event.card_name()), event.card_id()); + card->setColor(QString::fromStdString(event.color())); + card->setPT(QString::fromStdString(event.pt())); + card->setAnnotation(QString::fromStdString(event.annotation())); + card->setDestroyOnZoneChange(event.destroy_on_zone_change()); - CardItem *card = new CardItem(this, event->getCardName(), event->getCardId()); - card->setColor(event->getColor()); - card->setPT(event->getPt()); - card->setAnnotation(event->getAnnotation()); - card->setDestroyOnZoneChange(event->getDestroyOnZoneChange()); - - emit logCreateToken(this, card->getName(), card->getPT()); - zone->addCard(card, true, event->getX(), event->getY()); + emit logCreateToken(this, card->getName(), card->getPT()); + zone->addCard(card, true, event.x(), event.y()); } -void Player::eventSetCardAttr(Event_SetCardAttr *event, GameEventContext *context) +void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context) { - CardZone *zone = zones.value(event->getZone(), 0); - if (!zone) - return; + CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); + if (!zone) + return; - if (event->getCardId() == -1) { - const CardList &cards = zone->getCards(); - for (int i = 0; i < cards.size(); i++) - setCardAttrHelper(context, cards.at(i), event->getAttrName(), event->getAttrValue(), true); - if (event->getAttrName() == "tapped") - emit logSetTapped(this, 0, event->getAttrValue() == "1"); - } else { - CardItem *card = zone->getCard(event->getCardId(), QString()); - if (!card) { - qDebug() << "Player::eventSetCardAttr: card id=" << event->getCardId() << "not found"; - return; - } - setCardAttrHelper(context, card, event->getAttrName(), event->getAttrValue(), false); - } + if (!event.has_card_id()) { + const CardList &cards = zone->getCards(); + for (int i = 0; i < cards.size(); i++) + setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), true); + if (event.attribute() == AttrTapped) + emit logSetTapped(this, 0, event.attr_value() == "1"); + } else { + CardItem *card = zone->getCard(event.card_id(), QString()); + if (!card) { + qDebug() << "Player::eventSetCardAttr: card id=" << event.card_id() << "not found"; + return; + } + setCardAttrHelper(context, card, event.attribute(), QString::fromStdString(event.attr_value()), false); + } } -void Player::eventSetCardCounter(Event_SetCardCounter *event) +void Player::eventSetCardCounter(const Event_SetCardCounter &event) { - CardZone *zone = zones.value(event->getZone(), 0); - if (!zone) - return; + CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); + if (!zone) + return; - CardItem *card = zone->getCard(event->getCardId(), QString()); - if (!card) - return; - - int oldValue = card->getCounters().value(event->getCounterId(), 0); - card->setCounter(event->getCounterId(), event->getCounterValue()); - emit logSetCardCounter(this, card->getName(), event->getCounterId(), event->getCounterValue(), oldValue); + CardItem *card = zone->getCard(event.card_id(), QString()); + if (!card) + return; + + int oldValue = card->getCounters().value(event.counter_id(), 0); + card->setCounter(event.counter_id(), event.counter_value()); + emit logSetCardCounter(this, card->getName(), event.counter_id(), event.counter_value(), oldValue); } -void Player::eventCreateCounters(Event_CreateCounters *event) +void Player::eventCreateCounter(const Event_CreateCounter &event) { - const QList &eventCounterList = event->getCounterList(); - for (int i = 0; i < eventCounterList.size(); ++i) - addCounter(eventCounterList[i]); + addCounter(event.counter_info()); } -void Player::eventSetCounter(Event_SetCounter *event) +void Player::eventSetCounter(const Event_SetCounter &event) { - AbstractCounter *c = counters.value(event->getCounterId(), 0); - if (!c) - return; - int oldValue = c->getValue(); - c->setValue(event->getValue()); - emit logSetCounter(this, c->getName(), event->getValue(), oldValue); + AbstractCounter *c = counters.value(event.counter_id(), 0); + if (!c) + return; + int oldValue = c->getValue(); + c->setValue(event.value()); + emit logSetCounter(this, c->getName(), event.value(), oldValue); } -void Player::eventDelCounter(Event_DelCounter *event) +void Player::eventDelCounter(const Event_DelCounter &event) { - delCounter(event->getCounterId()); + delCounter(event.counter_id()); } -void Player::eventDumpZone(Event_DumpZone *event) +void Player::eventDumpZone(const Event_DumpZone &event) { - Player *zoneOwner = static_cast(parent())->getPlayers().value(event->getZoneOwnerId(), 0); - if (!zoneOwner) - return; - CardZone *zone = zoneOwner->getZones().value(event->getZone(), 0); - if (!zone) - return; - emit logDumpZone(this, zone, event->getNumberCards()); + Player *zoneOwner = game->getPlayers().value(event.zone_owner_id(), 0); + if (!zoneOwner) + return; + CardZone *zone = zoneOwner->getZones().value(QString::fromStdString(event.zone_name()), 0); + if (!zone) + return; + emit logDumpZone(this, zone, event.number_cards()); } -void Player::eventStopDumpZone(Event_StopDumpZone *event) +void Player::eventStopDumpZone(const Event_StopDumpZone &event) { - Player *zoneOwner = static_cast(parent())->getPlayers().value(event->getZoneOwnerId(), 0); - if (!zoneOwner) - return; - CardZone *zone = zoneOwner->getZones().value(event->getZone(), 0); - if (!zone) - return; - emit logStopDumpZone(this, zone); + Player *zoneOwner = game->getPlayers().value(event.zone_owner_id(), 0); + if (!zoneOwner) + return; + CardZone *zone = zoneOwner->getZones().value(QString::fromStdString(event.zone_name()), 0); + if (!zone) + return; + emit logStopDumpZone(this, zone); } -void Player::eventMoveCard(Event_MoveCard *event, GameEventContext *context) +void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext &context) { - CardZone *startZone = zones.value(event->getStartZone(), 0); - Player *targetPlayer = static_cast(parent())->getPlayers().value(event->getTargetPlayerId()); - if (!targetPlayer) - return; - CardZone *targetZone = targetPlayer->getZones().value(event->getTargetZone(), 0); - if (!startZone || !targetZone) - return; - - int position = event->getPosition(); - int x = event->getX(); - int y = event->getY(); + Player *startPlayer = game->getPlayers().value(event.start_player_id()); + if (!startPlayer) + return; + CardZone *startZone = startPlayer->getZones().value(QString::fromStdString(event.start_zone()), 0); + Player *targetPlayer = game->getPlayers().value(event.target_player_id()); + if (!targetPlayer) + return; + CardZone *targetZone; + if (event.has_target_zone()) + targetZone = targetPlayer->getZones().value(QString::fromStdString(event.target_zone()), 0); + else + targetZone = startZone; + if (!startZone || !targetZone) + return; + + int position = event.position(); + int x = event.x(); + int y = event.y(); - int logPosition = position; - int logX = x; - if (x == -1) - x = 0; - CardItem *card = startZone->takeCard(position, event->getCardId(), startZone != targetZone); - if (!card) - return; - if (startZone != targetZone) - card->deleteCardInfoPopup(); - card->setName(event->getCardName()); - - if (card->getAttachedTo() && (startZone != targetZone)) { - CardItem *parentCard = card->getAttachedTo(); - card->setAttachedTo(0); - parentCard->getZone()->reorganizeCards(); - } + int logPosition = position; + int logX = x; + if (x == -1) + x = 0; + CardItem *card = startZone->takeCard(position, event.card_id(), startZone != targetZone); + if (!card) + return; + if (startZone != targetZone) + card->deleteCardInfoPopup(); + if (event.has_card_name()) + card->setName(QString::fromStdString(event.card_name())); + + if (card->getAttachedTo() && (startZone != targetZone)) { + CardItem *parentCard = card->getAttachedTo(); + card->setAttachedTo(0); + parentCard->getZone()->reorganizeCards(); + } - card->deleteDragItem(); + card->deleteDragItem(); - card->setId(event->getNewCardId()); - card->setFaceDown(event->getFaceDown()); - if (startZone != targetZone) { - card->setBeingPointedAt(false); - card->setHovered(false); - - const QList &attachedCards = card->getAttachedCards(); - for (int i = 0; i < attachedCards.size(); ++i) - attachedCards[i]->setParentItem(targetZone); - - if (startZone->getPlayer() != targetZone->getPlayer()) - card->setOwner(targetZone->getPlayer()); - } + card->setId(event.new_card_id()); + card->setFaceDown(event.face_down()); + if (startZone != targetZone) { + card->setBeingPointedAt(false); + card->setHovered(false); + + const QList &attachedCards = card->getAttachedCards(); + for (int i = 0; i < attachedCards.size(); ++i) + attachedCards[i]->setParentItem(targetZone); + + if (startZone->getPlayer() != targetZone->getPlayer()) + card->setOwner(targetZone->getPlayer()); + } - // The log event has to be sent before the card is added to the target zone - // because the addCard function can modify the card object. - if (context) - switch (context->getItemId()) { - case ItemId_Context_UndoDraw: emit logUndoDraw(this, card->getName()); break; - default: emit logMoveCard(this, card, startZone, logPosition, targetZone, logX); - } - else - emit logMoveCard(this, card, startZone, logPosition, targetZone, logX); + // The log event has to be sent before the card is added to the target zone + // because the addCard function can modify the card object. + if (context.HasExtension(Context_UndoDraw::ext)) + emit logUndoDraw(this, card->getName()); + else + emit logMoveCard(this, card, startZone, logPosition, targetZone, logX); - targetZone->addCard(card, true, x, y); + targetZone->addCard(card, true, x, y); - // Look at all arrows from and to the card. - // If the card was moved to another zone, delete the arrows, otherwise update them. - QMapIterator playerIterator(static_cast(parent())->getPlayers()); - while (playerIterator.hasNext()) { - Player *p = playerIterator.next().value(); + // Look at all arrows from and to the card. + // If the card was moved to another zone, delete the arrows, otherwise update them. + QMapIterator playerIterator(game->getPlayers()); + while (playerIterator.hasNext()) { + Player *p = playerIterator.next().value(); - QList arrowsToDelete; - QMapIterator arrowIterator(p->getArrows()); - while (arrowIterator.hasNext()) { - ArrowItem *arrow = arrowIterator.next().value(); - if ((arrow->getStartItem() == card) || (arrow->getTargetItem() == card)) { - if (startZone == targetZone) - arrow->updatePath(); - else - arrowsToDelete.append(arrow); - } - } - for (int i = 0; i < arrowsToDelete.size(); ++i) - arrowsToDelete[i]->delArrow(); - } + QList arrowsToDelete; + QMapIterator arrowIterator(p->getArrows()); + while (arrowIterator.hasNext()) { + ArrowItem *arrow = arrowIterator.next().value(); + if ((arrow->getStartItem() == card) || (arrow->getTargetItem() == card)) { + if (startZone == targetZone) + arrow->updatePath(); + else + arrowsToDelete.append(arrow); + } + } + for (int i = 0; i < arrowsToDelete.size(); ++i) + arrowsToDelete[i]->delArrow(); + } } -void Player::eventFlipCard(Event_FlipCard *event) +void Player::eventFlipCard(const Event_FlipCard &event) { - CardZone *zone = zones.value(event->getZone(), 0); - if (!zone) - return; - CardItem *card = zone->getCard(event->getCardId(), event->getCardName()); - if (!card) - return; - emit logFlipCard(this, card->getName(), event->getFaceDown()); - card->setFaceDown(event->getFaceDown()); + CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); + if (!zone) + return; + CardItem *card = zone->getCard(event.card_id(), QString::fromStdString(event.card_name())); + if (!card) + return; + emit logFlipCard(this, card->getName(), event.face_down()); + card->setFaceDown(event.face_down()); } -void Player::eventDestroyCard(Event_DestroyCard *event) +void Player::eventDestroyCard(const Event_DestroyCard &event) { - CardZone *zone = zones.value(event->getZone(), 0); - if (!zone) - return; - - CardItem *card = zone->getCard(event->getCardId(), QString()); - if (!card) - return; - - QList attachedCards = card->getAttachedCards(); - // This list is always empty except for buggy server implementations. - for (int i = 0; i < attachedCards.size(); ++i) - attachedCards[i]->setAttachedTo(0); - - emit logDestroyCard(this, card->getName()); - zone->takeCard(-1, event->getCardId(), true); - card->deleteLater(); + CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); + if (!zone) + return; + + CardItem *card = zone->getCard(event.card_id(), QString()); + if (!card) + return; + + QList attachedCards = card->getAttachedCards(); + // This list is always empty except for buggy server implementations. + for (int i = 0; i < attachedCards.size(); ++i) + attachedCards[i]->setAttachedTo(0); + + emit logDestroyCard(this, card->getName()); + zone->takeCard(-1, event.card_id(), true); + card->deleteLater(); } -void Player::eventAttachCard(Event_AttachCard *event) +void Player::eventAttachCard(const Event_AttachCard &event) { - const QMap &playerList = static_cast(parent())->getPlayers(); - int targetPlayerId = event->getTargetPlayerId(); - Player *targetPlayer = 0; - CardZone *targetZone = 0; - CardItem *targetCard = 0; - if (targetPlayerId != -1) - targetPlayer = playerList.value(targetPlayerId, 0); - if (targetPlayer) - targetZone = targetPlayer->getZones().value(event->getTargetZone(), 0); - if (targetZone) - targetCard = targetZone->getCard(event->getTargetCardId(), QString()); - - CardZone *startZone = getZones().value(event->getStartZone(), 0); - if (!startZone) - return; - - CardItem *startCard = startZone->getCard(event->getCardId(), QString()); - if (!startCard) - return; - - CardItem *oldParent = startCard->getAttachedTo(); - - startCard->setAttachedTo(targetCard); - - startZone->reorganizeCards(); - if ((startZone != targetZone) && targetZone) - targetZone->reorganizeCards(); - if (oldParent) - oldParent->getZone()->reorganizeCards(); - - if (targetCard) - emit logAttachCard(this, startCard->getName(), targetPlayer, targetCard->getName()); - else - emit logUnattachCard(this, startCard->getName()); + const QMap &playerList = game->getPlayers(); + Player *targetPlayer = 0; + CardZone *targetZone = 0; + CardItem *targetCard = 0; + if (event.has_target_player_id()) { + targetPlayer = playerList.value(event.target_player_id(), 0); + if (targetPlayer) { + targetZone = targetPlayer->getZones().value(QString::fromStdString(event.target_zone()), 0); + if (targetZone) + targetCard = targetZone->getCard(event.target_card_id(), QString()); + } + } + + CardZone *startZone = getZones().value(QString::fromStdString(event.start_zone()), 0); + if (!startZone) + return; + + CardItem *startCard = startZone->getCard(event.card_id(), QString()); + if (!startCard) + return; + + CardItem *oldParent = startCard->getAttachedTo(); + + startCard->setAttachedTo(targetCard); + + startZone->reorganizeCards(); + if ((startZone != targetZone) && targetZone) + targetZone->reorganizeCards(); + if (oldParent) + oldParent->getZone()->reorganizeCards(); + + if (targetCard) + emit logAttachCard(this, startCard->getName(), targetPlayer, targetCard->getName()); + else + emit logUnattachCard(this, startCard->getName()); } -void Player::eventDrawCards(Event_DrawCards *event) +void Player::eventDrawCards(const Event_DrawCards &event) { - CardZone *deck = zones.value("deck"); - CardZone *hand = zones.value("hand"); - const QList &cardList = event->getCardList(); - if (!cardList.isEmpty()) - for (int i = 0; i < cardList.size(); ++i) { - CardItem *card = deck->takeCard(0, cardList[i]->getId()); - card->setName(cardList[i]->getName()); - hand->addCard(card, false, -1); - } - else - for (int i = 0; i < event->getNumberCards(); ++i) - hand->addCard(deck->takeCard(0, -1), false, -1); - - hand->reorganizeCards(); - deck->reorganizeCards(); - emit logDrawCards(this, event->getNumberCards()); + CardZone *deck = zones.value("deck"); + CardZone *hand = zones.value("hand"); + + const int listSize = event.cards_size(); + if (listSize) { + for (int i = 0; i < listSize; ++i) { + const ServerInfo_Card &cardInfo = event.cards(i); + CardItem *card = deck->takeCard(0, cardInfo.id()); + card->setName(QString::fromStdString(cardInfo.name())); + hand->addCard(card, false, -1); + } + } else { + const int number = event.number(); + for (int i = 0; i < number; ++i) + hand->addCard(deck->takeCard(0, -1), false, -1); + } + + hand->reorganizeCards(); + deck->reorganizeCards(); + emit logDrawCards(this, event.number()); } -void Player::eventRevealCards(Event_RevealCards *event) +void Player::eventRevealCards(const Event_RevealCards &event) { - CardZone *zone = zones.value(event->getZoneName()); - if (!zone) - return; - Player *otherPlayer = 0; - if (event->getOtherPlayerId() != -1) { - otherPlayer = static_cast(parent())->getPlayers().value(event->getOtherPlayerId()); - if (!otherPlayer) - return; - } - - QList cardList = event->getCardList(); - if (!cardList.isEmpty()) - static_cast(scene())->addRevealedZoneView(this, zone, cardList); - - QString cardName; - if (cardList.size() == 1) - cardName = cardList.first()->getName(); - emit logRevealCards(this, zone, event->getCardId(), cardName, otherPlayer); + CardZone *zone = zones.value(QString::fromStdString(event.zone_name())); + if (!zone) + return; + Player *otherPlayer = 0; + if (event.has_other_player_id()) { + otherPlayer = game->getPlayers().value(event.other_player_id()); + if (!otherPlayer) + return; + } + + bool peeking = false; + QList cardList; + const int cardListSize = event.cards_size(); + for (int i = 0; i < cardListSize; ++i) { + const ServerInfo_Card *temp = &event.cards(i); + if (temp->face_down()) + peeking = true; + cardList.append(temp); + } + + if (peeking) { + for (int i = 0; i < cardList.size(); ++i) { + QString cardName = QString::fromStdString(cardList.at(i)->name()); + CardItem *card = zone->getCard(cardList.at(i)->id(), QString()); + if (!card) + continue; + card->setName(cardName); + emit logRevealCards(this, zone, cardList.at(i)->id(), cardName, this, true); + } + } else { + bool showZoneView = true; + QString cardName; + if (cardList.size() == 1) { + cardName = QString::fromStdString(cardList.first()->name()); + if ((event.card_id() == 0) && dynamic_cast(zone)) { + zone->getCards().first()->setName(cardName); + zone->update(); + showZoneView = false; + } + } + if (showZoneView && !cardList.isEmpty()) + static_cast(scene())->addRevealedZoneView(this, zone, cardList, event.grant_write_access()); + + emit logRevealCards(this, zone, event.card_id(), cardName, otherPlayer, false); + } } -void Player::processGameEvent(GameEvent *event, GameEventContext *context) +void Player::eventChangeZoneProperties(const Event_ChangeZoneProperties &event) { - qDebug() << "player event: id=" << event->getItemId(); - switch (event->getItemId()) { - case ItemId_Event_ConnectionStateChanged: eventConnectionStateChanged(static_cast(event)); break; - case ItemId_Event_Say: eventSay(static_cast(event)); break; - case ItemId_Event_Shuffle: eventShuffle(static_cast(event)); break; - case ItemId_Event_RollDie: eventRollDie(static_cast(event)); break; - case ItemId_Event_CreateArrows: eventCreateArrows(static_cast(event)); break; - case ItemId_Event_DeleteArrow: eventDeleteArrow(static_cast(event)); break; - case ItemId_Event_CreateToken: eventCreateToken(static_cast(event)); break; - case ItemId_Event_SetCardAttr: eventSetCardAttr(static_cast(event), context); break; - case ItemId_Event_SetCardCounter: eventSetCardCounter(static_cast(event)); break; - case ItemId_Event_CreateCounters: eventCreateCounters(static_cast(event)); break; - case ItemId_Event_SetCounter: eventSetCounter(static_cast(event)); break; - case ItemId_Event_DelCounter: eventDelCounter(static_cast(event)); break; - case ItemId_Event_DumpZone: eventDumpZone(static_cast(event)); break; - case ItemId_Event_StopDumpZone: eventStopDumpZone(static_cast(event)); break; - case ItemId_Event_MoveCard: eventMoveCard(static_cast(event), context); break; - case ItemId_Event_FlipCard: eventFlipCard(static_cast(event)); break; - case ItemId_Event_DestroyCard: eventDestroyCard(static_cast(event)); break; - case ItemId_Event_AttachCard: eventAttachCard(static_cast(event)); break; - case ItemId_Event_DrawCards: eventDrawCards(static_cast(event)); break; - case ItemId_Event_RevealCards: eventRevealCards(static_cast(event)); break; - default: { - qDebug() << "unhandled game event"; - } - } + CardZone *zone = zones.value(QString::fromStdString(event.zone_name())); + if (!zone) + return; + + if (event.has_always_reveal_top_card()) { + zone->setAlwaysRevealTopCard(event.always_reveal_top_card()); + emit logAlwaysRevealTopCard(this, zone, event.always_reveal_top_card()); + } +} + +void Player::processGameEvent(GameEvent::GameEventType type, const GameEvent &event, const GameEventContext &context) +{ + switch (type) { + case GameEvent::GAME_SAY: eventGameSay(event.GetExtension(Event_GameSay::ext)); break; + case GameEvent::SHUFFLE: eventShuffle(event.GetExtension(Event_Shuffle::ext)); break; + case GameEvent::ROLL_DIE: eventRollDie(event.GetExtension(Event_RollDie::ext)); break; + case GameEvent::CREATE_ARROW: eventCreateArrow(event.GetExtension(Event_CreateArrow::ext)); break; + case GameEvent::DELETE_ARROW: eventDeleteArrow(event.GetExtension(Event_DeleteArrow::ext)); break; + case GameEvent::CREATE_TOKEN: eventCreateToken(event.GetExtension(Event_CreateToken::ext)); break; + case GameEvent::SET_CARD_ATTR: eventSetCardAttr(event.GetExtension(Event_SetCardAttr::ext), context); break; + case GameEvent::SET_CARD_COUNTER: eventSetCardCounter(event.GetExtension(Event_SetCardCounter::ext)); break; + case GameEvent::CREATE_COUNTER: eventCreateCounter(event.GetExtension(Event_CreateCounter::ext)); break; + case GameEvent::SET_COUNTER: eventSetCounter(event.GetExtension(Event_SetCounter::ext)); break; + case GameEvent::DEL_COUNTER: eventDelCounter(event.GetExtension(Event_DelCounter::ext)); break; + case GameEvent::DUMP_ZONE: eventDumpZone(event.GetExtension(Event_DumpZone::ext)); break; + case GameEvent::STOP_DUMP_ZONE: eventStopDumpZone(event.GetExtension(Event_StopDumpZone::ext)); break; + case GameEvent::MOVE_CARD: eventMoveCard(event.GetExtension(Event_MoveCard::ext), context); break; + case GameEvent::FLIP_CARD: eventFlipCard(event.GetExtension(Event_FlipCard::ext)); break; + case GameEvent::DESTROY_CARD: eventDestroyCard(event.GetExtension(Event_DestroyCard::ext)); break; + case GameEvent::ATTACH_CARD: eventAttachCard(event.GetExtension(Event_AttachCard::ext)); break; + case GameEvent::DRAW_CARDS: eventDrawCards(event.GetExtension(Event_DrawCards::ext)); break; + case GameEvent::REVEAL_CARDS: eventRevealCards(event.GetExtension(Event_RevealCards::ext)); break; + case GameEvent::CHANGE_ZONE_PROPERTIES: eventChangeZoneProperties(event.GetExtension(Event_ChangeZoneProperties::ext)); break; + default: { + qDebug() << "unhandled game event"; + } + } } void Player::setActive(bool _active) { - active = _active; - table->setActive(active); - update(); + active = _active; + table->setActive(active); + update(); } QRectF Player::boundingRect() const { - return bRect; + return bRect; } -void Player::paint(QPainter * /*painter*/, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) +void Player::paint(QPainter * /*painter*/, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { } -void Player::processPlayerInfo(ServerInfo_Player *info) +void Player::processPlayerInfo(const ServerInfo_Player &info) { - clearCounters(); - clearArrows(); - - QMapIterator zoneIt(zones); - while (zoneIt.hasNext()) - zoneIt.next().value()->clearContents(); - - QList zl = info->getZoneList(); - for (int i = 0; i < zl.size(); ++i) { - ServerInfo_Zone *zoneInfo = zl[i]; - CardZone *zone = zones.value(zoneInfo->getName(), 0); - if (!zone) - continue; - - const QList &cardList = zoneInfo->getCardList(); - if (cardList.isEmpty()) { - for (int j = 0; j < zoneInfo->getCardCount(); ++j) - zone->addCard(new CardItem(this), false, -1); - } else { - for (int j = 0; j < cardList.size(); ++j) { - CardItem *card = new CardItem(this); - card->processCardInfo(cardList[j]); - zone->addCard(card, false, cardList[j]->getX(), cardList[j]->getY()); - } - } - zone->reorganizeCards(); - } - - QList cl = info->getCounterList(); - for (int i = 0; i < cl.size(); ++i) { - addCounter(cl.at(i)); - } - - QList al = info->getArrowList(); - for (int i = 0; i < al.size(); ++i) - addArrow(al.at(i)); - - setConceded(info->getProperties()->getConceded()); + clearCounters(); + clearArrows(); + + QMapIterator zoneIt(zones); + while (zoneIt.hasNext()) + zoneIt.next().value()->clearContents(); + + const int zoneListSize = info.zone_list_size(); + for (int i = 0; i < zoneListSize; ++i) { + const ServerInfo_Zone &zoneInfo = info.zone_list(i); + CardZone *zone = zones.value(QString::fromStdString(zoneInfo.name()), 0); + if (!zone) + continue; + + const int cardListSize = zoneInfo.card_list_size(); + if (!cardListSize) { + for (int j = 0; j < zoneInfo.card_count(); ++j) + zone->addCard(new CardItem(this), false, -1); + } else { + for (int j = 0; j < cardListSize; ++j) { + const ServerInfo_Card &cardInfo = zoneInfo.card_list(j); + CardItem *card = new CardItem(this); + card->processCardInfo(cardInfo); + zone->addCard(card, false, cardInfo.x(), cardInfo.y()); + } + } + if (zoneInfo.has_always_reveal_top_card()) + zone->setAlwaysRevealTopCard(zoneInfo.always_reveal_top_card()); + + zone->reorganizeCards(); + } + + const int counterListSize = info.counter_list_size(); + for (int i = 0; i < counterListSize; ++i) + addCounter(info.counter_list(i)); + + setConceded(info.properties().conceded()); } -void Player::processCardAttachment(ServerInfo_Player *info) +void Player::processCardAttachment(const ServerInfo_Player &info) { - QList zl = info->getZoneList(); - for (int i = 0; i < zl.size(); ++i) { - ServerInfo_Zone *zoneInfo = zl[i]; - CardZone *zone = zones.value(zoneInfo->getName(), 0); - if (!zone) - continue; - - const QList &cardList = zoneInfo->getCardList(); - for (int j = 0; j < cardList.size(); ++j) { - ServerInfo_Card *cardInfo = cardList[j]; - if (cardInfo->getAttachPlayerId() != -1) { - CardItem *startCard = zone->getCard(cardInfo->getId(), QString()); - CardItem *targetCard = static_cast(parent())->getCard(cardInfo->getAttachPlayerId(), cardInfo->getAttachZone(), cardInfo->getAttachCardId()); - if (!targetCard) - continue; - - startCard->setAttachedTo(targetCard); - } - } - } + const int zoneListSize = info.zone_list_size(); + for (int i = 0; i < zoneListSize; ++i) { + const ServerInfo_Zone &zoneInfo = info.zone_list(i); + CardZone *zone = zones.value(QString::fromStdString(zoneInfo.name()), 0); + if (!zone) + continue; + + const int cardListSize = zoneInfo.card_list_size(); + for (int j = 0; j < cardListSize; ++j) { + const ServerInfo_Card &cardInfo = zoneInfo.card_list(j); + if (cardInfo.has_attach_player_id()) { + CardItem *startCard = zone->getCard(cardInfo.id(), QString()); + CardItem *targetCard = game->getCard(cardInfo.attach_player_id(), QString::fromStdString(cardInfo.attach_zone()), cardInfo.attach_card_id()); + if (!targetCard) + continue; + + startCard->setAttachedTo(targetCard); + } + } + } + + const int arrowListSize = info.arrow_list_size(); + for (int i = 0; i < arrowListSize; ++i) + addArrow(info.arrow_list(i)); } void Player::playCard(CardItem *c, bool faceDown, bool tapped) { - CardInfo *ci = c->getInfo(); - if (ci->getTableRow() == 3) - sendGameCommand(new Command_MoveCard(-1, c->getZone()->getName(), QList() << new CardToMove(c->getId()), getId(), "stack", 0, 0)); - else { - QPoint gridPoint = QPoint(-1, 2 - ci->getTableRow()); - sendGameCommand(new Command_MoveCard(-1, c->getZone()->getName(), QList() << new CardToMove(c->getId(), faceDown, ci->getPowTough(), tapped), getId(), "table", gridPoint.x(), gridPoint.y())); - } + Command_MoveCard cmd; + cmd.set_start_player_id(c->getZone()->getPlayer()->getId()); + cmd.set_start_zone(c->getZone()->getName().toStdString()); + cmd.set_target_player_id(getId()); + CardToMove *cardToMove = cmd.mutable_cards_to_move()->add_card(); + cardToMove->set_card_id(c->getId()); + + CardInfo *ci = c->getInfo(); + if ((!settingsCache->getPlayToStack() && ci->getTableRow() == 3) || + ((settingsCache->getPlayToStack() && ci->getTableRow() != 0) && + c->getZone()->getName().toStdString() != "stack")) { + cmd.set_target_zone("stack"); + cmd.set_x(0); + cmd.set_y(0); + } else { + QPoint gridPoint = QPoint(-1, 2 - ci->getTableRow()); + cardToMove->set_face_down(faceDown); + cardToMove->set_pt(ci->getPowTough().toStdString()); + cardToMove->set_tapped(tapped); + if(ci->getTableRow() != 3) + cmd.set_target_zone("table"); + cmd.set_x(gridPoint.x()); + cmd.set_y(gridPoint.y()); + } + sendGameCommand(cmd); } void Player::addCard(CardItem *c) { - emit newCardAdded(c); + emit newCardAdded(c); } void Player::deleteCard(CardItem *c) { - if (dialogSemaphore) - cardsToDelete.append(c); - else - c->deleteLater(); + if (dialogSemaphore) + cardsToDelete.append(c); + else + c->deleteLater(); } void Player::addZone(CardZone *z) { - zones.insert(z->getName(), z); + zones.insert(z->getName(), z); } -AbstractCounter *Player::addCounter(ServerInfo_Counter *counter) +AbstractCounter *Player::addCounter(const ServerInfo_Counter &counter) { - return addCounter(counter->getId(), counter->getName(), counter->getColor().getQColor(), counter->getRadius(), counter->getCount()); + return addCounter(counter.id(), QString::fromStdString(counter.name()), convertColorToQColor(counter.counter_color()), counter.radius(), counter.count()); } AbstractCounter *Player::addCounter(int counterId, const QString &name, QColor color, int radius, int value) { - qDebug() << "addCounter:" << getName() << counterId << name; - if (counters.contains(counterId)) - return 0; - - AbstractCounter *c; - if (name == "life") - c = playerTarget->addCounter(counterId, name, value); - else - c = new GeneralCounter(this, counterId, name, color, radius, value, this); - counters.insert(counterId, c); - if (countersMenu) - countersMenu->addMenu(c->getMenu()); - if (shortcutsActive) - c->setShortcutsActive(); - rearrangeCounters(); - return c; + qDebug() << "addCounter:" << getName() << counterId << name; + if (counters.contains(counterId)) + return 0; + + AbstractCounter *c; + if (name == "life") + c = playerTarget->addCounter(counterId, name, value); + else + c = new GeneralCounter(this, counterId, name, color, radius, value, this); + counters.insert(counterId, c); + if (countersMenu) + countersMenu->addMenu(c->getMenu()); + if (shortcutsActive) + c->setShortcutsActive(); + rearrangeCounters(); + return c; } void Player::delCounter(int counterId) { - AbstractCounter *c = counters.value(counterId, 0); - if (!c) - return; - if (c->getName() == "life") - playerTarget->delCounter(); - counters.remove(counterId); - c->delCounter(); - rearrangeCounters(); + AbstractCounter *c = counters.value(counterId, 0); + if (!c) + return; + + c->delCounter(); + counters.remove(counterId); + rearrangeCounters(); } void Player::clearCounters() { - QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) - counterIterator.next().value()->delCounter(); - counters.clear(); - playerTarget->delCounter(); + QMapIterator counterIterator(counters); + while (counterIterator.hasNext()) + counterIterator.next().value()->delCounter(); + counters.clear(); } -ArrowItem *Player::addArrow(ServerInfo_Arrow *arrow) +ArrowItem *Player::addArrow(const ServerInfo_Arrow &arrow) { - const QMap &playerList = static_cast(parent())->getPlayers(); - Player *startPlayer = playerList.value(arrow->getStartPlayerId(), 0); - Player *targetPlayer = playerList.value(arrow->getTargetPlayerId(), 0); - if (!startPlayer || !targetPlayer) - return 0; - - CardZone *startZone = startPlayer->getZones().value(arrow->getStartZone(), 0); - CardZone *targetZone = targetPlayer->getZones().value(arrow->getTargetZone(), 0); - if (!startZone || (!targetZone && !arrow->getTargetZone().isEmpty())) - return 0; - - CardItem *startCard = startZone->getCard(arrow->getStartCardId(), QString()); - CardItem *targetCard = 0; - if (targetZone) - targetCard = targetZone->getCard(arrow->getTargetCardId(), QString()); - if (!startCard || (!targetCard && !arrow->getTargetZone().isEmpty())) - return 0; - - if (targetCard) - return addArrow(arrow->getId(), startCard, targetCard, arrow->getColor().getQColor()); - else - return addArrow(arrow->getId(), startCard, targetPlayer->getPlayerTarget(), arrow->getColor().getQColor()); + const QMap &playerList = game->getPlayers(); + Player *startPlayer = playerList.value(arrow.start_player_id(), 0); + Player *targetPlayer = playerList.value(arrow.target_player_id(), 0); + if (!startPlayer || !targetPlayer) + return 0; + + CardZone *startZone = startPlayer->getZones().value(QString::fromStdString(arrow.start_zone()), 0); + CardZone *targetZone = 0; + if (arrow.has_target_zone()) + targetZone = targetPlayer->getZones().value(QString::fromStdString(arrow.target_zone()), 0); + if (!startZone || (!targetZone && arrow.has_target_zone())) + return 0; + + CardItem *startCard = startZone->getCard(arrow.start_card_id(), QString()); + CardItem *targetCard = 0; + if (targetZone) + targetCard = targetZone->getCard(arrow.target_card_id(), QString()); + if (!startCard || (!targetCard && arrow.has_target_card_id())) + return 0; + + if (targetCard) + return addArrow(arrow.id(), startCard, targetCard, convertColorToQColor(arrow.arrow_color())); + else + return addArrow(arrow.id(), startCard, targetPlayer->getPlayerTarget(), convertColorToQColor(arrow.arrow_color())); } ArrowItem *Player::addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color) { - ArrowItem *arrow = new ArrowItem(this, arrowId, startCard, targetItem, color); - arrows.insert(arrowId, arrow); - scene()->addItem(arrow); - return arrow; + ArrowItem *arrow = new ArrowItem(this, arrowId, startCard, targetItem, color); + arrows.insert(arrowId, arrow); + scene()->addItem(arrow); + return arrow; } void Player::delArrow(int arrowId) { - ArrowItem *a = arrows.value(arrowId, 0); - if (!a) - return; - a->delArrow(); + ArrowItem *a = arrows.value(arrowId, 0); + if (!a) + return; + a->delArrow(); } void Player::removeArrow(ArrowItem *arrow) { - if (arrow->getId() != -1) - arrows.remove(arrow->getId()); + if (arrow->getId() != -1) + arrows.remove(arrow->getId()); } void Player::clearArrows() { - QMapIterator arrowIterator(arrows); - while (arrowIterator.hasNext()) - arrowIterator.next().value()->delArrow(); - arrows.clear(); + QMapIterator arrowIterator(arrows); + while (arrowIterator.hasNext()) + arrowIterator.next().value()->delArrow(); + arrows.clear(); } void Player::rearrangeCounters() { - qreal marginTop = 80; - - // Determine total height of bounding rectangles - qreal totalHeight = 0; - QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) { - counterIterator.next(); - if (counterIterator.value()->getShownInCounterArea()) - totalHeight += counterIterator.value()->boundingRect().height(); - } - - const qreal padding = 5; - qreal y = boundingRect().y() + marginTop; - - // Place objects - for (counterIterator.toFront(); counterIterator.hasNext(); ) { - AbstractCounter *c = counterIterator.next().value(); + qreal marginTop = 80; + + // Determine total height of bounding rectangles + qreal totalHeight = 0; + QMapIterator counterIterator(counters); + while (counterIterator.hasNext()) { + counterIterator.next(); + if (counterIterator.value()->getShownInCounterArea()) + totalHeight += counterIterator.value()->boundingRect().height(); + } + + const qreal padding = 5; + qreal y = boundingRect().y() + marginTop; + + // Place objects + for (counterIterator.toFront(); counterIterator.hasNext(); ) { + AbstractCounter *c = counterIterator.next().value(); - if (!c->getShownInCounterArea()) - continue; - - QRectF br = c->boundingRect(); - c->setPos((counterAreaWidth - br.width()) / 2, y); - y += br.height() + padding; - } + if (!c->getShownInCounterArea()) + continue; + + QRectF br = c->boundingRect(); + c->setPos((counterAreaWidth - br.width()) / 2, y); + y += br.height() + padding; + } } -void Player::sendGameCommand(GameCommand *command) +PendingCommand * Player::prepareGameCommand(const google::protobuf::Message &cmd) { - static_cast(parent())->sendGameCommand(command, id); + return game->prepareGameCommand(cmd); } -void Player::sendCommandContainer(CommandContainer *cont) +PendingCommand * Player::prepareGameCommand(const QList &cmdList) { - static_cast(parent())->sendCommandContainer(cont, id); + return game->prepareGameCommand(cmdList); +} + +void Player::sendGameCommand(const google::protobuf::Message &command) +{ + game->sendGameCommand(command, id); +} + +void Player::sendGameCommand(PendingCommand *pend) +{ + game->sendGameCommand(pend, id); } bool Player::clearCardsToDelete() { - if (cardsToDelete.isEmpty()) - return false; - - for (int i = 0; i < cardsToDelete.size(); ++i) - cardsToDelete[i]->deleteLater(); - cardsToDelete.clear(); - - return true; + if (cardsToDelete.isEmpty()) + return false; + + for (int i = 0; i < cardsToDelete.size(); ++i) + cardsToDelete[i]->deleteLater(); + cardsToDelete.clear(); + + return true; } -void Player::cardMenuAction(QAction *a) +void Player::cardMenuAction() { - QList sel = scene()->selectedItems(); - QList cardList; - while (!sel.isEmpty()) - cardList.append(qgraphicsitem_cast(sel.takeFirst())); - - QList commandList; - if (a->data().toInt() <= 4) - for (int i = 0; i < cardList.size(); ++i) { - CardItem *card = cardList[i]; - switch (a->data().toInt()) { - case 0: - if (!card->getTapped()) - commandList.append(new Command_SetCardAttr(-1, card->getZone()->getName(), card->getId(), "tapped", "1")); - break; - case 1: - if (card->getTapped()) - commandList.append(new Command_SetCardAttr(-1, card->getZone()->getName(), card->getId(), "tapped", "0")); - break; - case 2: - commandList.append(new Command_SetCardAttr(-1, card->getZone()->getName(), card->getId(), "doesnt_untap", QString::number(!card->getDoesntUntap()))); - break; - case 3: { - QString zone = card->getZone()->getName(); - commandList.append(new Command_FlipCard(-1, zone, card->getId(), !card->getFaceDown())); - break; - } - case 4: - commandList.append(new Command_CreateToken(-1, card->getZone()->getName(), card->getName(), card->getColor(), card->getPT(), card->getAnnotation(), true, -1, card->getGridPoint().y())); - break; - } - } - else { - QList idList; - for (int i = 0; i < cardList.size(); ++i) - idList.append(new CardToMove(cardList[i]->getId())); - QString startZone = cardList[0]->getZone()->getName(); - - switch (a->data().toInt()) { - case 5: - commandList.append(new Command_MoveCard(-1, startZone, idList, getId(), "deck", 0, 0)); - break; - case 6: - commandList.append(new Command_MoveCard(-1, startZone, idList, getId(), "deck", -1, 0)); - break; - case 7: - commandList.append(new Command_MoveCard(-1, startZone, idList, getId(), "grave", 0, 0)); - break; - case 8: - commandList.append(new Command_MoveCard(-1, startZone, idList, getId(), "rfg", 0, 0)); - break; - default: ; - } - } - sendCommandContainer(new CommandContainer(commandList)); + QAction *a = static_cast(sender()); + QList sel = scene()->selectedItems(); + QList cardList; + while (!sel.isEmpty()) + cardList.append(qgraphicsitem_cast(sel.takeFirst())); + + QList< const ::google::protobuf::Message * > commandList; + if (a->data().toInt() <= (int) cmClone) + for (int i = 0; i < cardList.size(); ++i) { + CardItem *card = cardList[i]; + switch (static_cast(a->data().toInt())) { + case cmTap: + if (!card->getTapped()) { + Command_SetCardAttr *cmd = new Command_SetCardAttr; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_attribute(AttrTapped); + cmd->set_attr_value("1"); + commandList.append(cmd); + } + break; + case cmUntap: + if (card->getTapped()) { + Command_SetCardAttr *cmd = new Command_SetCardAttr; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_attribute(AttrTapped); + cmd->set_attr_value("0"); + commandList.append(cmd); + } + break; + case cmDoesntUntap: { + Command_SetCardAttr *cmd = new Command_SetCardAttr; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_attribute(AttrDoesntUntap); + cmd->set_attr_value(card->getDoesntUntap() ? "0" : "1"); + commandList.append(cmd); + break; + } + case cmFlip: { + Command_FlipCard *cmd = new Command_FlipCard; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_face_down(!card->getFaceDown()); + commandList.append(cmd); + break; + } + case cmPeek: { + Command_RevealCards *cmd = new Command_RevealCards; + cmd->set_zone_name(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_player_id(id); + commandList.append(cmd); + break; + } + case cmClone: { + Command_CreateToken *cmd = new Command_CreateToken; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_name(card->getName().toStdString()); + cmd->set_color(card->getColor().toStdString()); + cmd->set_pt(card->getPT().toStdString()); + cmd->set_annotation(card->getAnnotation().toStdString()); + cmd->set_destroy_on_zone_change(true); + cmd->set_x(-1); + cmd->set_y(card->getGridPoint().y()); + commandList.append(cmd); + break; + } + default: break; + } + } + else { + ListOfCardsToMove idList; + for (int i = 0; i < cardList.size(); ++i) + idList.add_card()->set_card_id(cardList[i]->getId()); + int startPlayerId = cardList[0]->getZone()->getPlayer()->getId(); + QString startZone = cardList[0]->getZone()->getName(); + + switch (static_cast(a->data().toInt())) { + case cmMoveToTopLibrary: { + Command_MoveCard *cmd = new Command_MoveCard; + cmd->set_start_player_id(startPlayerId); + cmd->set_start_zone(startZone.toStdString()); + cmd->mutable_cards_to_move()->CopyFrom(idList); + cmd->set_target_player_id(getId()); + cmd->set_target_zone("deck"); + cmd->set_x(0); + cmd->set_y(0); + commandList.append(cmd); + break; + } + case cmMoveToBottomLibrary: { + Command_MoveCard *cmd = new Command_MoveCard; + cmd->set_start_player_id(startPlayerId); + cmd->set_start_zone(startZone.toStdString()); + cmd->mutable_cards_to_move()->CopyFrom(idList); + cmd->set_target_player_id(getId()); + cmd->set_target_zone("deck"); + cmd->set_x(-1); + cmd->set_y(0); + commandList.append(cmd); + break; + } + case cmMoveToGraveyard: { + Command_MoveCard *cmd = new Command_MoveCard; + cmd->set_start_player_id(startPlayerId); + cmd->set_start_zone(startZone.toStdString()); + cmd->mutable_cards_to_move()->CopyFrom(idList); + cmd->set_target_player_id(getId()); + cmd->set_target_zone("grave"); + cmd->set_x(0); + cmd->set_y(0); + commandList.append(cmd); + break; + } + case cmMoveToExile: { + Command_MoveCard *cmd = new Command_MoveCard; + cmd->set_start_player_id(startPlayerId); + cmd->set_start_zone(startZone.toStdString()); + cmd->mutable_cards_to_move()->CopyFrom(idList); + cmd->set_target_player_id(getId()); + cmd->set_target_zone("rfg"); + cmd->set_x(0); + cmd->set_y(0); + commandList.append(cmd); + break; + } + default: ; + } + } + game->sendGameCommand(prepareGameCommand(commandList)); } void Player::actIncPT(int deltaP, int deltaT) { - QString ptString = "+" + QString::number(deltaP) + "/+" + QString::number(deltaT); - QListIterator j(scene()->selectedItems()); - while (j.hasNext()) { - CardItem *card = static_cast(j.next()); - sendGameCommand(new Command_SetCardAttr(-1, card->getZone()->getName(), card->getId(), "pt", ptString)); - } + QString ptString = "+" + QString::number(deltaP) + "/+" + QString::number(deltaT); + + QList< const ::google::protobuf::Message * > commandList; + QListIterator j(scene()->selectedItems()); + while (j.hasNext()) { + CardItem *card = static_cast(j.next()); + Command_SetCardAttr *cmd = new Command_SetCardAttr; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_attribute(AttrPT); + cmd->set_attr_value(ptString.toStdString()); + commandList.append(cmd); + } + sendGameCommand(prepareGameCommand(commandList)); } -void Player::actSetPT(QAction * /*a*/) +void Player::actSetPT() { - QString oldPT; - QListIterator i(scene()->selectedItems()); - while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - if (!card->getPT().isEmpty()) - oldPT = card->getPT(); - } - bool ok; - dialogSemaphore = true; - QString pt = QInputDialog::getText(0, tr("Set power/toughness"), tr("Please enter the new PT:"), QLineEdit::Normal, oldPT, &ok); - dialogSemaphore = false; - if (clearCardsToDelete()) - return; - if (!ok) - return; - - QListIterator j(scene()->selectedItems()); - while (j.hasNext()) { - CardItem *card = static_cast(j.next()); - sendGameCommand(new Command_SetCardAttr(-1, card->getZone()->getName(), card->getId(), "pt", pt)); - } + QString oldPT; + QListIterator i(scene()->selectedItems()); + while (i.hasNext()) { + CardItem *card = static_cast(i.next()); + if (!card->getPT().isEmpty()) + oldPT = card->getPT(); + } + bool ok; + dialogSemaphore = true; + QString pt = QInputDialog::getText(0, tr("Set power/toughness"), tr("Please enter the new PT:"), QLineEdit::Normal, oldPT, &ok); + dialogSemaphore = false; + if (clearCardsToDelete()) + return; + if (!ok) + return; + + QList< const ::google::protobuf::Message * > commandList; + QListIterator j(scene()->selectedItems()); + while (j.hasNext()) { + CardItem *card = static_cast(j.next()); + Command_SetCardAttr *cmd = new Command_SetCardAttr; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_attribute(AttrPT); + cmd->set_attr_value(pt.toStdString()); + commandList.append(cmd); + } + sendGameCommand(prepareGameCommand(commandList)); } -void Player::actSetAnnotation(QAction * /*a*/) +void Player::actDrawArrow() { - QString oldAnnotation; - QListIterator i(scene()->selectedItems()); - while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - if (!card->getAnnotation().isEmpty()) - oldAnnotation = card->getAnnotation(); - } - - bool ok; - dialogSemaphore = true; - QString annotation = QInputDialog::getText(0, tr("Set annotation"), tr("Please enter the new annotation:"), QLineEdit::Normal, oldAnnotation, &ok); - dialogSemaphore = false; - if (clearCardsToDelete()) - return; - if (!ok) - return; - - i.toFront(); - while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - sendGameCommand(new Command_SetCardAttr(-1, card->getZone()->getName(), card->getId(), "annotation", annotation)); - } + game->getActiveCard()->drawArrow(Qt::red); } -void Player::actAttach(QAction *a) +void Player::actIncP() { - CardItem *card = static_cast(a->parent()); - ArrowAttachItem *arrow = new ArrowAttachItem(card); - scene()->addItem(arrow); - arrow->grabMouse(); + actIncPT(1, 0); } -void Player::actUnattach(QAction *a) +void Player::actDecP() { - CardItem *card = static_cast(a->parent()); - sendGameCommand(new Command_AttachCard(-1, card->getZone()->getName(), card->getId(), -1, QString(), -1)); + actIncPT(-1, 0); } -void Player::actCardCounterTrigger(QAction *a) +void Player::actIncT() { - int counterId = a->data().toInt() / 1000; - int action = a->data().toInt() % 1000; - switch (action) { - case 9: { - QListIterator i(scene()->selectedItems()); - while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - if (card->getCounters().value(counterId, 0) < MAX_COUNTERS_ON_CARD) - sendGameCommand(new Command_SetCardCounter(-1, card->getZone()->getName(), card->getId(), counterId, card->getCounters().value(counterId, 0) + 1)); - } - break; - } - case 10: { - QListIterator i(scene()->selectedItems()); - while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - if (card->getCounters().value(counterId, 0)) - sendGameCommand(new Command_SetCardCounter(-1, card->getZone()->getName(), card->getId(), counterId, card->getCounters().value(counterId, 0) - 1)); - } - break; - } - case 11: { - bool ok; - dialogSemaphore = true; - int number = QInputDialog::getInteger(0, tr("Set counters"), tr("Number:"), 0, 0, MAX_COUNTERS_ON_CARD, 1, &ok); - dialogSemaphore = false; - if (clearCardsToDelete()) - return; - if (!ok) - return; - - QListIterator i(scene()->selectedItems()); - while (i.hasNext()) { - CardItem *card = static_cast(i.next()); - sendGameCommand(new Command_SetCardCounter(-1, card->getZone()->getName(), card->getId(), counterId, number)); - } - break; - } - default: ; - } + actIncPT(0, 1); +} + +void Player::actDecT() +{ + actIncPT(0, -1); +} + +void Player::actIncPT() +{ + actIncPT(1, 1); +} + +void Player::actDecPT() +{ + actIncPT(-1, -1); +} + +void Player::actSetAnnotation() +{ + QString oldAnnotation; + QListIterator i(scene()->selectedItems()); + while (i.hasNext()) { + CardItem *card = static_cast(i.next()); + if (!card->getAnnotation().isEmpty()) + oldAnnotation = card->getAnnotation(); + } + + bool ok; + dialogSemaphore = true; + QString annotation = QInputDialog::getText(0, tr("Set annotation"), tr("Please enter the new annotation:"), QLineEdit::Normal, oldAnnotation, &ok); + dialogSemaphore = false; + if (clearCardsToDelete()) + return; + if (!ok) + return; + + QList< const ::google::protobuf::Message * > commandList; + i.toFront(); + while (i.hasNext()) { + CardItem *card = static_cast(i.next()); + Command_SetCardAttr *cmd = new Command_SetCardAttr; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_attribute(AttrAnnotation); + cmd->set_attr_value(annotation.toStdString()); + commandList.append(cmd); + } + sendGameCommand(prepareGameCommand(commandList)); +} + +void Player::actAttach() +{ + ArrowAttachItem *arrow = new ArrowAttachItem(game->getActiveCard()); + scene()->addItem(arrow); + arrow->grabMouse(); +} + +void Player::actUnattach() +{ + Command_AttachCard cmd; + cmd.set_start_zone(game->getActiveCard()->getZone()->getName().toStdString()); + cmd.set_card_id(game->getActiveCard()->getId()); + sendGameCommand(cmd); +} + +void Player::actCardCounterTrigger() +{ + QAction *a = static_cast(sender()); + int counterId = a->data().toInt() / 1000; + int action = a->data().toInt() % 1000; + QList< const ::google::protobuf::Message * > commandList; + switch (action) { + case 9: { + QListIterator i(scene()->selectedItems()); + while (i.hasNext()) { + CardItem *card = static_cast(i.next()); + if (card->getCounters().value(counterId, 0) < MAX_COUNTERS_ON_CARD) { + Command_SetCardCounter *cmd = new Command_SetCardCounter; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_counter_id(counterId); + cmd->set_counter_value(card->getCounters().value(counterId, 0) + 1); + commandList.append(cmd); + } + } + break; + } + case 10: { + QListIterator i(scene()->selectedItems()); + while (i.hasNext()) { + CardItem *card = static_cast(i.next()); + if (card->getCounters().value(counterId, 0)) { + Command_SetCardCounter *cmd = new Command_SetCardCounter; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_counter_id(counterId); + cmd->set_counter_value(card->getCounters().value(counterId, 0) - 1); + commandList.append(cmd); + } + } + break; + } + case 11: { + bool ok; + dialogSemaphore = true; + int number = +#if QT_VERSION < 0x050000 + QInputDialog::getInteger( +#else + QInputDialog::getInt( +#endif + 0, tr("Set counters"), tr("Number:"), 0, 0, MAX_COUNTERS_ON_CARD, 1, &ok); + dialogSemaphore = false; + if (clearCardsToDelete()) + return; + if (!ok) + return; + + QListIterator i(scene()->selectedItems()); + while (i.hasNext()) { + CardItem *card = static_cast(i.next()); + Command_SetCardCounter *cmd = new Command_SetCardCounter; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_counter_id(counterId); + cmd->set_counter_value(number); + commandList.append(cmd); + } + break; + } + default: ; + } + sendGameCommand(prepareGameCommand(commandList)); +} + +void Player::actPlay() +{ + playCard(game->getActiveCard(), false, game->getActiveCard()->getInfo()->getCipt()); +} + +void Player::actHide() +{ + game->getActiveCard()->getZone()->removeCard(game->getActiveCard()); +} + +void Player::updateCardMenu(CardItem *card) +{ + QMenu *cardMenu = card->getCardMenu(); + QMenu *ptMenu = card->getPTMenu(); + QMenu *moveMenu = card->getMoveMenu(); + + cardMenu->clear(); + + bool revealedCard = false; + bool writeableCard = getLocal(); + if (card->getZone()) + if (card->getZone()->getIsView()) { + ZoneViewZone *view = static_cast(card->getZone()); + if (view->getRevealZone()) { + if (view->getWriteableRevealZone()) + writeableCard = true; + else + revealedCard = true; + } + } + + if (revealedCard) + cardMenu->addAction(aHide); + else if (writeableCard) { + if (moveMenu->isEmpty()) { + moveMenu->addAction(aMoveToTopLibrary); + moveMenu->addAction(aMoveToBottomLibrary); + moveMenu->addAction(aMoveToGraveyard); + moveMenu->addAction(aMoveToExile); + } + + if (card->getZone()) { + if (card->getZone()->getName() == "table") { + if (ptMenu->isEmpty()) { + ptMenu->addAction(aIncP); + ptMenu->addAction(aDecP); + ptMenu->addSeparator(); + ptMenu->addAction(aIncT); + ptMenu->addAction(aDecT); + ptMenu->addSeparator(); + ptMenu->addAction(aIncPT); + ptMenu->addAction(aDecPT); + ptMenu->addSeparator(); + ptMenu->addAction(aSetPT); + } + + cardMenu->addAction(aTap); + cardMenu->addAction(aUntap); + cardMenu->addAction(aDoesntUntap); + cardMenu->addAction(aFlip); + if (card->getFaceDown()) + cardMenu->addAction(aPeek); + cardMenu->addSeparator(); + cardMenu->addAction(aAttach); + if (card->getAttachedTo()) + cardMenu->addAction(aUnattach); + cardMenu->addAction(aDrawArrow); + cardMenu->addSeparator(); + cardMenu->addMenu(ptMenu); + cardMenu->addAction(aSetAnnotation); + cardMenu->addSeparator(); + cardMenu->addAction(aClone); + cardMenu->addMenu(moveMenu); + + for (int i = 0; i < aAddCounter.size(); ++i) { + cardMenu->addSeparator(); + cardMenu->addAction(aAddCounter[i]); + cardMenu->addAction(aRemoveCounter[i]); + cardMenu->addAction(aSetCounter[i]); + } + cardMenu->addSeparator(); + } else if (card->getZone()->getName() == "stack") { + cardMenu->addAction(aDrawArrow); + cardMenu->addMenu(moveMenu); + } else { + cardMenu->addAction(aPlay); + cardMenu->addMenu(moveMenu); + } + } else + cardMenu->addMenu(moveMenu); + } } void Player::setCardMenu(QMenu *menu) { - if (aCardMenu) - aCardMenu->setMenu(menu); + if (aCardMenu) + aCardMenu->setMenu(menu); } QMenu *Player::getCardMenu() const { - if (aCardMenu) - return aCardMenu->menu(); - return 0; + if (aCardMenu) + return aCardMenu->menu(); + return 0; } QString Player::getName() const { - return userInfo->getName(); + return QString::fromStdString(userInfo->name()); } qreal Player::getMinimumWidth() const { - qreal result = table->getMinimumWidth() + CARD_HEIGHT + 15 + counterAreaWidth + stack->boundingRect().width(); - if (!settingsCache->getHorizontalHand()) - result += hand->boundingRect().width(); - return result; + qreal result = table->getMinimumWidth() + CARD_HEIGHT + 15 + counterAreaWidth + stack->boundingRect().width(); + if (!settingsCache->getHorizontalHand()) + result += hand->boundingRect().width(); + return result; } void Player::setConceded(bool _conceded) { - conceded = _conceded; - setVisible(!conceded); - if (conceded) { - clear(); - emit gameConceded(); - } + conceded = _conceded; + setVisible(!conceded); + if (conceded) { + clear(); + emit gameConceded(); + } } void Player::setMirrored(bool _mirrored) { - if (mirrored != _mirrored) { - mirrored = _mirrored; - rearrangeZones(); - } + if (mirrored != _mirrored) { + mirrored = _mirrored; + rearrangeZones(); + } } void Player::processSceneSizeChange(int newPlayerWidth) { - qreal tableWidth = newPlayerWidth - CARD_HEIGHT - 15 - counterAreaWidth - stack->boundingRect().width(); - if (!settingsCache->getHorizontalHand()) - tableWidth -= hand->boundingRect().width(); - - table->setWidth(tableWidth); - hand->setWidth(tableWidth + stack->boundingRect().width()); + qreal tableWidth = newPlayerWidth - CARD_HEIGHT - 15 - counterAreaWidth - stack->boundingRect().width(); + if (!settingsCache->getHorizontalHand()) + tableWidth -= hand->boundingRect().width(); + + table->setWidth(tableWidth); + hand->setWidth(tableWidth + stack->boundingRect().width()); } diff --git a/cockatrice/src/player.h b/cockatrice/src/player.h index 72f19c260..d8396e4d0 100644 --- a/cockatrice/src/player.h +++ b/cockatrice/src/player.h @@ -4,14 +4,21 @@ #include #include #include -#include "carditem.h" +#include "abstractgraphicsitem.h" +#include "pb/game_event.pb.h" +#include "pb/card_attributes.pb.h" +namespace google { namespace protobuf { class Message; } } class CardDatabase; +class DeckLoader; class QMenu; class QAction; class ZoneViewZone; class TabGame; class AbstractCounter; +class AbstractCardItem; +class CardItem; +class ArrowTarget; class ArrowItem; class CardZone; class StackZone; @@ -27,15 +34,15 @@ class GameCommand; class GameEvent; class GameEventContext; class Event_ConnectionStateChanged; -class Event_Say; +class Event_GameSay; class Event_Shuffle; class Event_RollDie; -class Event_CreateArrows; +class Event_CreateArrow; class Event_DeleteArrow; class Event_CreateToken; class Event_SetCardAttr; class Event_SetCardCounter; -class Event_CreateCounters; +class Event_CreateCounter; class Event_SetCounter; class Event_DelCounter; class Event_DumpZone; @@ -46,216 +53,252 @@ class Event_DestroyCard; class Event_AttachCard; class Event_DrawCards; class Event_RevealCards; +class Event_ChangeZoneProperties; +class PendingCommand; class PlayerArea : public QObject, public QGraphicsItem { - Q_OBJECT -private: - QBrush bgPixmapBrush; - QRectF bRect; + Q_OBJECT + Q_INTERFACES(QGraphicsItem) +private: + QBrush bgPixmapBrush; + QRectF bRect; private slots: - void updateBgPixmap(); + void updateBgPixmap(); public: - enum { Type = typeOther }; - int type() const { return Type; } - - PlayerArea(QGraphicsItem *parent = 0); - QRectF boundingRect() const { return bRect; } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - - void setSize(qreal width, qreal height); + enum { Type = typeOther }; + int type() const { return Type; } + + PlayerArea(QGraphicsItem *parent = 0); + QRectF boundingRect() const { return bRect; } + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + void setSize(qreal width, qreal height); }; class Player : public QObject, public QGraphicsItem { - Q_OBJECT + Q_OBJECT + Q_INTERFACES(QGraphicsItem) signals: - void newCardAdded(AbstractCardItem *card); - // Log events - void logConnectionStateChanged(Player *player, bool connectionState); - void logSay(Player *player, QString message); - void logShuffle(Player *player, CardZone *zone); - void logRollDie(Player *player, int sides, int roll); - void logCreateArrow(Player *player, Player *startPlayer, QString startCard, Player *targetPlayer, QString targetCard, bool _playerTarget); - void logCreateToken(Player *player, QString cardName, QString pt); - void logDrawCards(Player *player, int number); - void logUndoDraw(Player *player, QString cardName); - void logMoveCard(Player *player, CardItem *card, CardZone *startZone, int oldX, CardZone *targetZone, int newX); - void logFlipCard(Player *player, QString cardName, bool faceDown); - void logDestroyCard(Player *player, QString cardName); - void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName); - void logUnattachCard(Player *player, QString cardName); - void logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue); - void logSetTapped(Player *player, CardItem *card, bool tapped); - void logSetCounter(Player *player, QString counterName, int value, int oldValue); - void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap); - void logSetPT(Player *player, CardItem *card, QString newPT); - void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); - void logDumpZone(Player *player, CardZone *zone, int numberCards); - void logStopDumpZone(Player *player, CardZone *zone); - void logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer); - - void sizeChanged(); - void gameConceded(); + void openDeckEditor(const DeckLoader *deck); + void newCardAdded(AbstractCardItem *card); + // Log events + void logSay(Player *player, QString message); + void logShuffle(Player *player, CardZone *zone); + void logRollDie(Player *player, int sides, int roll); + void logCreateArrow(Player *player, Player *startPlayer, QString startCard, Player *targetPlayer, QString targetCard, bool _playerTarget); + void logCreateToken(Player *player, QString cardName, QString pt); + void logDrawCards(Player *player, int number); + void logUndoDraw(Player *player, QString cardName); + void logMoveCard(Player *player, CardItem *card, CardZone *startZone, int oldX, CardZone *targetZone, int newX); + void logFlipCard(Player *player, QString cardName, bool faceDown); + void logDestroyCard(Player *player, QString cardName); + void logAttachCard(Player *player, QString cardName, Player *targetPlayer, QString targetCardName); + void logUnattachCard(Player *player, QString cardName); + void logSetCardCounter(Player *player, QString cardName, int counterId, int value, int oldValue); + void logSetTapped(Player *player, CardItem *card, bool tapped); + void logSetCounter(Player *player, QString counterName, int value, int oldValue); + void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap); + void logSetPT(Player *player, CardItem *card, QString newPT); + void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); + void logDumpZone(Player *player, CardZone *zone, int numberCards); + void logStopDumpZone(Player *player, CardZone *zone); + void logRevealCards(Player *player, CardZone *zone, int cardId, QString cardName, Player *otherPlayer, bool faceDown); + void logAlwaysRevealTopCard(Player *player, CardZone *zone, bool reveal); + + void sizeChanged(); + void gameConceded(); public slots: - void actUntapAll(); - void actRollDie(); - void actCreateToken(); - void actCreateAnotherToken(); - void actShuffle(); - void actDrawCard(); - void actDrawCards(); - void actUndoDraw(); + void actUntapAll(); + void actRollDie(); + void actCreateToken(); + void actCreateAnotherToken(); + void actShuffle(); + void actDrawCard(); + void actDrawCards(); + void actUndoDraw(); void actMulligan(); - void actMoveTopCardsToGrave(); - void actMoveTopCardsToExile(); - void actMoveTopCardToBottom(); + void actMoveTopCardsToGrave(); + void actMoveTopCardsToExile(); + void actMoveTopCardToBottom(); - void actViewLibrary(); - void actViewTopCards(); - void actViewGraveyard(); - void actViewRfg(); - void actViewSideboard(); - - void actSayMessage(); - - void actAttach(QAction *action); - void actUnattach(QAction *action); - void actIncPT(int deltaP, int deltaT); - void actSetPT(QAction *action); - void actSetAnnotation(QAction *action); - void cardMenuAction(QAction *action); - void actCardCounterTrigger(QAction *action); + void actViewLibrary(); + void actViewTopCards(); + void actAlwaysRevealTopCard(); + void actViewGraveyard(); + void actViewRfg(); + void actViewSideboard(); + + void actSayMessage(); private slots: - void addPlayer(Player *player); - void removePlayer(Player *player); - void playerListActionTriggered(); - - void updateBoundingRect(); - void rearrangeZones(); + void addPlayer(Player *player); + void removePlayer(Player *player); + void playerListActionTriggered(); + + void updateBoundingRect(); + void rearrangeZones(); + + void actOpenDeckInDeckEditor(); + void actCreatePredefinedToken(); + void cardMenuAction(); + void actCardCounterTrigger(); + void actAttach(); + void actUnattach(); + void actDrawArrow(); + void actIncPT(int deltaP, int deltaT); + void actSetPT(); + void actIncP(); + void actDecP(); + void actIncT(); + void actDecT(); + void actIncPT(); + void actDecPT(); + void actSetAnnotation(); + void actPlay(); + void actHide(); + private: - QMenu *playerMenu, *handMenu, *graveMenu, *rfgMenu, *libraryMenu, *sbMenu, *countersMenu, *sayMenu, - *mRevealLibrary, *mRevealTopCard, *mRevealHand, *mRevealRandomHandCard; - QList playerLists; - QList allPlayersActions; - QAction *aMoveHandToTopLibrary, *aMoveHandToBottomLibrary, *aMoveHandToGrave, *aMoveHandToRfg, - *aMoveGraveToTopLibrary, *aMoveGraveToBottomLibrary, *aMoveGraveToHand, *aMoveGraveToRfg, - *aMoveRfgToTopLibrary, *aMoveRfgToBottomLibrary, *aMoveRfgToHand, *aMoveRfgToGrave, - *aViewLibrary, *aViewTopCards, *aMoveTopCardsToGrave, *aMoveTopCardsToExile, *aMoveTopCardToBottom, - *aViewGraveyard, *aViewRfg, *aViewSideboard, + TabGame *game; + QMenu *playerMenu, *handMenu, *graveMenu, *rfgMenu, *libraryMenu, *sbMenu, *countersMenu, *sayMenu, *createPredefinedTokenMenu, + *mRevealLibrary, *mRevealTopCard, *mRevealHand, *mRevealRandomHandCard; + QList playerLists; + QList allPlayersActions; + QAction *aMoveHandToTopLibrary, *aMoveHandToBottomLibrary, *aMoveHandToGrave, *aMoveHandToRfg, + *aMoveGraveToTopLibrary, *aMoveGraveToBottomLibrary, *aMoveGraveToHand, *aMoveGraveToRfg, + *aMoveRfgToTopLibrary, *aMoveRfgToBottomLibrary, *aMoveRfgToHand, *aMoveRfgToGrave, + *aViewLibrary, *aViewTopCards, *aAlwaysRevealTopCard, *aOpenDeckInDeckEditor, *aMoveTopCardsToGrave, *aMoveTopCardsToExile, *aMoveTopCardToBottom, + *aViewGraveyard, *aViewRfg, *aViewSideboard, *aDrawCard, *aDrawCards, *aUndoDraw, *aMulligan, *aShuffle, - *aUntapAll, *aRollDie, *aCreateToken, *aCreateAnotherToken, - *aCardMenu; + *aUntapAll, *aRollDie, *aCreateToken, *aCreateAnotherToken, + *aCardMenu; + + QList aAddCounter, aSetCounter, aRemoveCounter; + QAction *aPlay, + *aHide, + *aTap, *aUntap, *aDoesntUntap, *aAttach, *aUnattach, *aDrawArrow, *aSetPT, *aIncP, *aDecP, *aIncT, *aDecT, *aIncPT, *aDecPT, *aSetAnnotation, *aFlip, *aPeek, *aClone, + *aMoveToTopLibrary, *aMoveToBottomLibrary, *aMoveToGraveyard, *aMoveToExile; - bool shortcutsActive; - int defaultNumberTopCards; - QString lastTokenName, lastTokenColor, lastTokenPT, lastTokenAnnotation; - bool lastTokenDestroy; - ServerInfo_User *userInfo; - int id; - bool active; - bool local; - bool mirrored; - bool handVisible; - bool conceded; - - bool dialogSemaphore; - bool clearCardsToDelete(); - QList cardsToDelete; - - PlayerArea *playerArea; - QMap zones; - StackZone *stack; - TableZone *table; - HandZone *hand; - PlayerTarget *playerTarget; - - void setCardAttrHelper(GameEventContext *context, CardItem *card, const QString &aname, const QString &avalue, bool allCards); + bool shortcutsActive; + int defaultNumberTopCards; + QString lastTokenName, lastTokenColor, lastTokenPT, lastTokenAnnotation; + bool lastTokenDestroy; + ServerInfo_User *userInfo; + int id; + bool active; + bool local; + bool mirrored; + bool handVisible; + bool conceded; + + bool dialogSemaphore; + bool clearCardsToDelete(); + QList cardsToDelete; + + DeckLoader *deck; + QStringList predefinedTokens; + + PlayerArea *playerArea; + QMap zones; + StackZone *stack; + TableZone *table; + HandZone *hand; + PlayerTarget *playerTarget; + + void setCardAttrHelper(const GameEventContext &context, CardItem *card, CardAttribute attribute, const QString &avalue, bool allCards); - QRectF bRect; + QRectF bRect; - QMap counters; - QMap arrows; - void rearrangeCounters(); - - void initSayMenu(); - - void eventConnectionStateChanged(Event_ConnectionStateChanged *event); - void eventSay(Event_Say *event); - void eventShuffle(Event_Shuffle *event); - void eventRollDie(Event_RollDie *event); - void eventCreateArrows(Event_CreateArrows *event); - void eventDeleteArrow(Event_DeleteArrow *event); - void eventCreateToken(Event_CreateToken *event); - void eventSetCardAttr(Event_SetCardAttr *event, GameEventContext *context); - void eventSetCardCounter(Event_SetCardCounter *event); - void eventCreateCounters(Event_CreateCounters *event); - void eventSetCounter(Event_SetCounter *event); - void eventDelCounter(Event_DelCounter *event); - void eventDumpZone(Event_DumpZone *event); - void eventStopDumpZone(Event_StopDumpZone *event); - void eventMoveCard(Event_MoveCard *event, GameEventContext *context); - void eventFlipCard(Event_FlipCard *event); - void eventDestroyCard(Event_DestroyCard *event); - void eventAttachCard(Event_AttachCard *event); - void eventDrawCards(Event_DrawCards *event); - void eventRevealCards(Event_RevealCards *event); + QMap counters; + QMap arrows; + void rearrangeCounters(); + + void initSayMenu(); + + void eventConnectionStateChanged(const Event_ConnectionStateChanged &event); + void eventGameSay(const Event_GameSay &event); + void eventShuffle(const Event_Shuffle &event); + void eventRollDie(const Event_RollDie &event); + void eventCreateArrow(const Event_CreateArrow &event); + void eventDeleteArrow(const Event_DeleteArrow &event); + void eventCreateToken(const Event_CreateToken &event); + void eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context); + void eventSetCardCounter(const Event_SetCardCounter &event); + void eventCreateCounter(const Event_CreateCounter &event); + void eventSetCounter(const Event_SetCounter &event); + void eventDelCounter(const Event_DelCounter &event); + void eventDumpZone(const Event_DumpZone &event); + void eventStopDumpZone(const Event_StopDumpZone &event); + void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context); + void eventFlipCard(const Event_FlipCard &event); + void eventDestroyCard(const Event_DestroyCard &event); + void eventAttachCard(const Event_AttachCard &event); + void eventDrawCards(const Event_DrawCards &event); + void eventRevealCards(const Event_RevealCards &event); + void eventChangeZoneProperties(const Event_ChangeZoneProperties &event); public: - static const int counterAreaWidth = 55; - - enum { Type = typeOther }; - int type() const { return Type; } - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - - void playCard(CardItem *c, bool faceDown, bool tapped); - void addCard(CardItem *c); - void deleteCard(CardItem *c); - void addZone(CardZone *z); + static const int counterAreaWidth = 55; + enum CardMenuActionType { cmTap, cmUntap, cmDoesntUntap, cmFlip, cmPeek, cmClone, cmMoveToTopLibrary, cmMoveToBottomLibrary, cmMoveToGraveyard, cmMoveToExile }; + + enum { Type = typeOther }; + int type() const { return Type; } + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + void playCard(CardItem *c, bool faceDown, bool tapped); + void addCard(CardItem *c); + void deleteCard(CardItem *c); + void addZone(CardZone *z); - AbstractCounter *addCounter(ServerInfo_Counter *counter); - AbstractCounter *addCounter(int counterId, const QString &name, QColor color, int radius, int value); - void delCounter(int counterId); - void clearCounters(); - - ArrowItem *addArrow(ServerInfo_Arrow *arrow); - ArrowItem *addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color); - void delArrow(int arrowId); - void removeArrow(ArrowItem *arrow); - void clearArrows(); - PlayerTarget *getPlayerTarget() const { return playerTarget; } + AbstractCounter *addCounter(const ServerInfo_Counter &counter); + AbstractCounter *addCounter(int counterId, const QString &name, QColor color, int radius, int value); + void delCounter(int counterId); + void clearCounters(); + + ArrowItem *addArrow(const ServerInfo_Arrow &arrow); + ArrowItem *addArrow(int arrowId, CardItem *startCard, ArrowTarget *targetItem, const QColor &color); + void delArrow(int arrowId); + void removeArrow(ArrowItem *arrow); + void clearArrows(); + PlayerTarget *getPlayerTarget() const { return playerTarget; } - Player(ServerInfo_User *info, int _id, bool _local, TabGame *_parent); - ~Player(); - void retranslateUi(); - void clear(); - QMenu *getPlayerMenu() const { return playerMenu; } - int getId() const { return id; } - QString getName() const; - ServerInfo_User *getUserInfo() const { return userInfo; } - bool getLocal() const { return local; } - bool getMirrored() const { return mirrored; } - const QMap &getZones() const { return zones; } - const QMap &getArrows() const { return arrows; } - void setCardMenu(QMenu *menu); - QMenu *getCardMenu() const; - bool getActive() const { return active; } - void setActive(bool _active); - void setShortcutsActive(); - void setShortcutsInactive(); - void updateZones(); - - void setConceded(bool _conceded); - bool getConceded() const { return conceded; } - - qreal getMinimumWidth() const; - void setMirrored(bool _mirrored); - void processSceneSizeChange(int newPlayerWidth); - - void processPlayerInfo(ServerInfo_Player *info); - void processCardAttachment(ServerInfo_Player *info); - - void processGameEvent(GameEvent *event, GameEventContext *context); - void sendGameCommand(GameCommand *command); - void sendCommandContainer(CommandContainer *cont); + Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_parent); + ~Player(); + void retranslateUi(); + void clear(); + TabGame *getGame() const { return game; } + void setDeck(const DeckLoader &_deck); + QMenu *getPlayerMenu() const { return playerMenu; } + int getId() const { return id; } + QString getName() const; + ServerInfo_User *getUserInfo() const { return userInfo; } + bool getLocal() const { return local; } + bool getMirrored() const { return mirrored; } + const QMap &getZones() const { return zones; } + const QMap &getArrows() const { return arrows; } + void setCardMenu(QMenu *menu); + QMenu *getCardMenu() const; + void updateCardMenu(CardItem *card); + bool getActive() const { return active; } + void setActive(bool _active); + void setShortcutsActive(); + void setShortcutsInactive(); + void updateZones(); + + void setConceded(bool _conceded); + bool getConceded() const { return conceded; } + + qreal getMinimumWidth() const; + void setMirrored(bool _mirrored); + void processSceneSizeChange(int newPlayerWidth); + + void processPlayerInfo(const ServerInfo_Player &info); + void processCardAttachment(const ServerInfo_Player &info); + + void processGameEvent(GameEvent::GameEventType type, const GameEvent &event, const GameEventContext &context); + + PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd); + PendingCommand *prepareGameCommand(const QList< const ::google::protobuf::Message * > &cmdList); + void sendGameCommand(PendingCommand *pend); + void sendGameCommand(const google::protobuf::Message &command); }; #endif diff --git a/cockatrice/src/playerlistwidget.cpp b/cockatrice/src/playerlistwidget.cpp index 44f2d8c3a..b535c2c1c 100644 --- a/cockatrice/src/playerlistwidget.cpp +++ b/cockatrice/src/playerlistwidget.cpp @@ -1,221 +1,182 @@ #include #include "playerlistwidget.h" -#include "protocol_datastructures.h" #include "pixmapgenerator.h" #include "abstractclient.h" #include "tab_game.h" #include "tab_supervisor.h" #include "tab_userlists.h" -#include "protocol_items.h" #include "userlist.h" #include "userinfobox.h" +#include "user_context_menu.h" #include #include #include +#include "pb/session_commands.pb.h" +#include "pb/command_kick_from_game.pb.h" +#include "pb/serverinfo_playerproperties.pb.h" + PlayerListItemDelegate::PlayerListItemDelegate(QObject *const parent) - : QStyledItemDelegate(parent) + : QStyledItemDelegate(parent) { } bool PlayerListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { - if ((event->type() == QEvent::MouseButtonPress) && index.isValid()) { - QMouseEvent *const mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::RightButton) { - static_cast(parent())->showContextMenu(mouseEvent->globalPos(), index); - return true; - } - } - return QStyledItemDelegate::editorEvent(event, model, option, index); + if ((event->type() == QEvent::MouseButtonPress) && index.isValid()) { + QMouseEvent *const mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::RightButton) { + static_cast(parent())->showContextMenu(mouseEvent->globalPos(), index); + return true; + } + } + return QStyledItemDelegate::editorEvent(event, model, option, index); } PlayerListTWI::PlayerListTWI() - : QTreeWidgetItem(Type) + : QTreeWidgetItem(Type) { } bool PlayerListTWI::operator<(const QTreeWidgetItem &other) const { - // Sort by spectator/player - if (data(1, Qt::UserRole) != other.data(1, Qt::UserRole)) - return data(1, Qt::UserRole).toBool(); - - // Sort by player ID - return data(4, Qt::UserRole + 1).toInt() < other.data(4, Qt::UserRole + 1).toInt(); + // Sort by spectator/player + if (data(1, Qt::UserRole) != other.data(1, Qt::UserRole)) + return data(1, Qt::UserRole).toBool(); + + // Sort by player ID + return data(4, Qt::UserRole + 1).toInt() < other.data(4, Qt::UserRole + 1).toInt(); } PlayerListWidget::PlayerListWidget(TabSupervisor *_tabSupervisor, AbstractClient *_client, TabGame *_game, QWidget *parent) - : QTreeWidget(parent), tabSupervisor(_tabSupervisor), client(_client), game(_game), gameStarted(false) + : QTreeWidget(parent), tabSupervisor(_tabSupervisor), client(_client), game(_game), gameStarted(false) { - readyIcon = QIcon(":/resources/icon_ready_start.svg"); - notReadyIcon = QIcon(":/resources/icon_not_ready_start.svg"); - concededIcon = QIcon(":/resources/icon_conceded.svg"); - playerIcon = QIcon(":/resources/icon_player.svg"); - spectatorIcon = QIcon(":/resources/icon_spectator.svg"); - - itemDelegate = new PlayerListItemDelegate(this); - setItemDelegate(itemDelegate); - - setMinimumHeight(60); - setIconSize(QSize(20, 15)); - setColumnCount(6); - setHeaderHidden(true); - setRootIsDecorated(false); - header()->setResizeMode(QHeaderView::ResizeToContents); - retranslateUi(); + readyIcon = QIcon(":/resources/icon_ready_start.svg"); + notReadyIcon = QIcon(":/resources/icon_not_ready_start.svg"); + concededIcon = QIcon(":/resources/icon_conceded.svg"); + playerIcon = QIcon(":/resources/icon_player.svg"); + spectatorIcon = QIcon(":/resources/icon_spectator.svg"); + lockIcon = QIcon(":/resources/lock.svg"); + + if (tabSupervisor) { + itemDelegate = new PlayerListItemDelegate(this); + setItemDelegate(itemDelegate); + + userContextMenu = new UserContextMenu(tabSupervisor, this, game); + connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); + } else + userContextMenu = 0; + + setMinimumHeight(60); + setIconSize(QSize(20, 15)); + setColumnCount(6); + setHeaderHidden(true); + setRootIsDecorated(false); +#if QT_VERSION < 0x050000 + header()->setResizeMode(QHeaderView::ResizeToContents); +#else + header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#endif + retranslateUi(); } void PlayerListWidget::retranslateUi() { } -void PlayerListWidget::addPlayer(ServerInfo_PlayerProperties *player) +void PlayerListWidget::addPlayer(const ServerInfo_PlayerProperties &player) { - QTreeWidgetItem *newPlayer = new PlayerListTWI; - players.insert(player->getPlayerId(), newPlayer); - updatePlayerProperties(player); - addTopLevelItem(newPlayer); - sortItems(1, Qt::AscendingOrder); + QTreeWidgetItem *newPlayer = new PlayerListTWI; + players.insert(player.player_id(), newPlayer); + updatePlayerProperties(player); + addTopLevelItem(newPlayer); + sortItems(1, Qt::AscendingOrder); } -void PlayerListWidget::updatePlayerProperties(ServerInfo_PlayerProperties *prop) +void PlayerListWidget::updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId) { - QTreeWidgetItem *player = players.value(prop->getPlayerId(), 0); - if (!player) - return; - - player->setIcon(1, prop->getSpectator() ? spectatorIcon : playerIcon); - player->setData(1, Qt::UserRole, !prop->getSpectator()); - player->setData(2, Qt::UserRole, prop->getConceded()); - player->setData(2, Qt::UserRole + 1, prop->getReadyStart()); - player->setIcon(2, gameStarted ? (prop->getConceded() ? concededIcon : QIcon()) : (prop->getReadyStart() ? readyIcon : notReadyIcon)); - player->setData(3, Qt::UserRole, prop->getUserInfo()->getUserLevel()); - player->setIcon(3, QIcon(UserLevelPixmapGenerator::generatePixmap(12, prop->getUserInfo()->getUserLevel()))); - player->setText(4, prop->getUserInfo()->getName()); - if (!prop->getUserInfo()->getCountry().isEmpty()) - player->setIcon(4, QIcon(CountryPixmapGenerator::generatePixmap(12, prop->getUserInfo()->getCountry()))); - player->setData(4, Qt::UserRole, prop->getUserInfo()->getName()); - player->setData(4, Qt::UserRole + 1, prop->getPlayerId()); - player->setText(5, prop->getDeckHash()); + if (playerId == -1) + playerId = prop.player_id(); + + QTreeWidgetItem *player = players.value(playerId, 0); + if (!player) + return; + + if (prop.has_spectator()) { + player->setIcon(1, prop.spectator() ? spectatorIcon : playerIcon); + player->setData(1, Qt::UserRole, !prop.spectator()); + } + if (prop.has_conceded()) + player->setData(2, Qt::UserRole, prop.conceded()); + if (prop.has_ready_start()) + player->setData(2, Qt::UserRole + 1, prop.ready_start()); + if (prop.has_conceded() || prop.has_ready_start()) + player->setIcon(2, gameStarted ? (prop.conceded() ? concededIcon : QIcon()) : (prop.ready_start() ? readyIcon : notReadyIcon)); + if (prop.has_user_info()) { + player->setData(3, Qt::UserRole, prop.user_info().user_level()); + player->setIcon(3, QIcon(UserLevelPixmapGenerator::generatePixmap(12, UserLevelFlags(prop.user_info().user_level())))); + player->setText(4, QString::fromStdString(prop.user_info().name())); + const QString country = QString::fromStdString(prop.user_info().country()); + if (!country.isEmpty()) + player->setIcon(4, QIcon(CountryPixmapGenerator::generatePixmap(12, country))); + player->setData(4, Qt::UserRole, QString::fromStdString(prop.user_info().name())); + } + if (prop.has_player_id()) + player->setData(4, Qt::UserRole + 1, prop.player_id()); + if (prop.has_deck_hash()) + player->setText(5, QString::fromStdString(prop.deck_hash())); + if (prop.has_sideboard_locked()) + player->setIcon(5, prop.sideboard_locked() ? lockIcon : QIcon()); + if (prop.has_ping_seconds()) + player->setIcon(0, QIcon(PingPixmapGenerator::generatePixmap(12, prop.ping_seconds(), 10))); } void PlayerListWidget::removePlayer(int playerId) { - QTreeWidgetItem *player = players.value(playerId, 0); - if (!player) - return; - players.remove(playerId); - delete takeTopLevelItem(indexOfTopLevelItem(player)); + QTreeWidgetItem *player = players.value(playerId, 0); + if (!player) + return; + players.remove(playerId); + delete takeTopLevelItem(indexOfTopLevelItem(player)); } void PlayerListWidget::setActivePlayer(int playerId) { - QMapIterator i(players); - while (i.hasNext()) { - i.next(); - QTreeWidgetItem *twi = i.value(); - QColor c = i.key() == playerId ? QColor(150, 255, 150) : Qt::white; - twi->setBackground(4, c); - } -} - -void PlayerListWidget::updatePing(int playerId, int pingTime) -{ - QTreeWidgetItem *twi = players.value(playerId, 0); - if (!twi) - return; - twi->setIcon(0, QIcon(PingPixmapGenerator::generatePixmap(12, pingTime, 10))); + QMapIterator i(players); + while (i.hasNext()) { + i.next(); + QTreeWidgetItem *twi = i.value(); + QColor c = i.key() == playerId ? QColor(150, 255, 150) : Qt::white; + twi->setBackground(4, c); + } } void PlayerListWidget::setGameStarted(bool _gameStarted, bool resuming) { - gameStarted = _gameStarted; - QMapIterator i(players); - while (i.hasNext()) { - QTreeWidgetItem *twi = i.next().value(); - if (gameStarted) { - if (resuming) - twi->setIcon(2, twi->data(2, Qt::UserRole).toBool() ? concededIcon : QIcon()); - else { - twi->setData(2, Qt::UserRole, false); - twi->setIcon(2, QIcon()); - } - } else - twi->setIcon(2, notReadyIcon); - } + gameStarted = _gameStarted; + QMapIterator i(players); + while (i.hasNext()) { + QTreeWidgetItem *twi = i.next().value(); + if (gameStarted) { + if (resuming) + twi->setIcon(2, twi->data(2, Qt::UserRole).toBool() ? concededIcon : QIcon()); + else { + twi->setData(2, Qt::UserRole, false); + twi->setIcon(2, QIcon()); + } + } else + twi->setIcon(2, notReadyIcon); + } } void PlayerListWidget::showContextMenu(const QPoint &pos, const QModelIndex &index) { - const QString &userName = index.sibling(index.row(), 4).data(Qt::UserRole).toString(); - int playerId = index.sibling(index.row(), 4).data(Qt::UserRole + 1).toInt(); - ServerInfo_User::UserLevelFlags userLevel = static_cast(index.sibling(index.row(), 3).data(Qt::UserRole).toInt()); - - QAction *aUserName = new QAction(userName, this); - aUserName->setEnabled(false); - QAction *aDetails = new QAction(tr("User &details"), this); - QAction *aChat = new QAction(tr("Direct &chat"), this); - QAction *aAddToBuddyList = new QAction(tr("Add to &buddy list"), this); - QAction *aRemoveFromBuddyList = new QAction(tr("Remove from &buddy list"), this); - QAction *aAddToIgnoreList = new QAction(tr("Add to &ignore list"), this); - QAction *aRemoveFromIgnoreList = new QAction(tr("Remove from &ignore list"), this); - QAction *aKick = new QAction(tr("Kick from &game"), this); - - QMenu *menu = new QMenu(this); - menu->addAction(aUserName); - menu->addSeparator(); - menu->addAction(aDetails); - menu->addAction(aChat); - if ((userLevel & ServerInfo_User::IsRegistered) && (tabSupervisor->getUserLevel() & ServerInfo_User::IsRegistered)) { - menu->addSeparator(); - if (tabSupervisor->getUserListsTab()->getBuddyList()->userInList(userName)) - menu->addAction(aRemoveFromBuddyList); - else - menu->addAction(aAddToBuddyList); - if (tabSupervisor->getUserListsTab()->getIgnoreList()->userInList(userName)) - menu->addAction(aRemoveFromIgnoreList); - else - menu->addAction(aAddToIgnoreList); - } - if (game->isHost() || !game->getTabSupervisor()->getAdminLocked()) { - menu->addSeparator(); - menu->addAction(aKick); - } - if (userName == game->getTabSupervisor()->getUserInfo()->getName()) { - aChat->setEnabled(false); - aAddToBuddyList->setEnabled(false); - aRemoveFromBuddyList->setEnabled(false); - aAddToIgnoreList->setEnabled(false); - aRemoveFromIgnoreList->setEnabled(false); - aKick->setEnabled(false); - } - - QAction *actionClicked = menu->exec(pos); - if (actionClicked == aDetails) { - UserInfoBox *infoWidget = new UserInfoBox(client, true, this, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint); - infoWidget->setAttribute(Qt::WA_DeleteOnClose); - infoWidget->updateInfo(userName); - } else if (actionClicked == aChat) - emit openMessageDialog(userName, true); - else if (actionClicked == aAddToBuddyList) - client->sendCommand(new Command_AddToList("buddy", userName)); - else if (actionClicked == aRemoveFromBuddyList) - client->sendCommand(new Command_RemoveFromList("buddy", userName)); - else if (actionClicked == aAddToIgnoreList) - client->sendCommand(new Command_AddToList("ignore", userName)); - else if (actionClicked == aRemoveFromIgnoreList) - client->sendCommand(new Command_RemoveFromList("ignore", userName)); - else if (actionClicked == aKick) - game->sendGameCommand(new Command_KickFromGame(-1, playerId)); - - delete menu; - delete aUserName; - delete aDetails; - delete aChat; - delete aAddToBuddyList; - delete aRemoveFromBuddyList; - delete aAddToIgnoreList; - delete aRemoveFromIgnoreList; - delete aKick; + if (!userContextMenu) + return; + + const QString &userName = index.sibling(index.row(), 4).data(Qt::UserRole).toString(); + int playerId = index.sibling(index.row(), 4).data(Qt::UserRole + 1).toInt(); + UserLevelFlags userLevel(index.sibling(index.row(), 3).data(Qt::UserRole).toInt()); + + userContextMenu->showContextMenu(pos, userName, userLevel, true, playerId); } diff --git a/cockatrice/src/playerlistwidget.h b/cockatrice/src/playerlistwidget.h index d2d956d89..ff932ce78 100644 --- a/cockatrice/src/playerlistwidget.h +++ b/cockatrice/src/playerlistwidget.h @@ -10,41 +10,42 @@ class ServerInfo_PlayerProperties; class TabSupervisor; class AbstractClient; class TabGame; +class UserContextMenu; class PlayerListItemDelegate : public QStyledItemDelegate { public: - PlayerListItemDelegate(QObject *const parent); - bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); + PlayerListItemDelegate(QObject *const parent); + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); }; class PlayerListTWI : public QTreeWidgetItem { public: - PlayerListTWI(); - bool operator<(const QTreeWidgetItem &other) const; + PlayerListTWI(); + bool operator<(const QTreeWidgetItem &other) const; }; class PlayerListWidget : public QTreeWidget { - Q_OBJECT + Q_OBJECT private: - PlayerListItemDelegate *itemDelegate; - QMap players; - TabSupervisor *tabSupervisor; - AbstractClient *client; - TabGame *game; - QIcon readyIcon, notReadyIcon, concededIcon, playerIcon, spectatorIcon; - bool gameStarted; + PlayerListItemDelegate *itemDelegate; + QMap players; + TabSupervisor *tabSupervisor; + AbstractClient *client; + TabGame *game; + UserContextMenu *userContextMenu; + QIcon readyIcon, notReadyIcon, concededIcon, playerIcon, spectatorIcon, lockIcon; + bool gameStarted; signals: - void openMessageDialog(const QString &userName, bool focus); + void openMessageDialog(const QString &userName, bool focus); public: - PlayerListWidget(TabSupervisor *_tabSupervisor, AbstractClient *_client, TabGame *_game, QWidget *parent = 0); - void retranslateUi(); - void addPlayer(ServerInfo_PlayerProperties *player); - void removePlayer(int playerId); - void setActivePlayer(int playerId); - void updatePing(int playerId, int pingTime); - void updatePlayerProperties(ServerInfo_PlayerProperties *prop); - void setGameStarted(bool _gameStarted, bool resuming); - void showContextMenu(const QPoint &pos, const QModelIndex &index); + PlayerListWidget(TabSupervisor *_tabSupervisor, AbstractClient *_client, TabGame *_game, QWidget *parent = 0); + void retranslateUi(); + void addPlayer(const ServerInfo_PlayerProperties &player); + void removePlayer(int playerId); + void setActivePlayer(int playerId); + void updatePlayerProperties(const ServerInfo_PlayerProperties &prop, int playerId = -1); + void setGameStarted(bool _gameStarted, bool resuming); + void showContextMenu(const QPoint &pos, const QModelIndex &index); }; #endif diff --git a/cockatrice/src/playertarget.cpp b/cockatrice/src/playertarget.cpp index 976563d69..98b266dfd 100644 --- a/cockatrice/src/playertarget.cpp +++ b/cockatrice/src/playertarget.cpp @@ -1,146 +1,160 @@ #include "playertarget.h" #include "player.h" -#include "protocol_datastructures.h" #include "pixmapgenerator.h" +#include "pb/serverinfo_user.pb.h" #include #include #include -#include +#include +#ifdef _WIN32 +#include "round.h" +#endif /* _WIN32 */ PlayerCounter::PlayerCounter(Player *_player, int _id, const QString &_name, int _value, QGraphicsItem *parent) - : AbstractCounter(_player, _id, _name, false, _value, parent) + : AbstractCounter(_player, _id, _name, false, _value, parent) { } QRectF PlayerCounter::boundingRect() const { - return QRectF(0, 0, 50, 30); + return QRectF(0, 0, 50, 30); } void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - const int radius = 8; - const qreal border = 1; - QPainterPath path(QPointF(50 - border / 2, border / 2)); - path.lineTo(radius, border / 2); - path.arcTo(border / 2, border / 2, 2 * radius, 2 * radius, 90, 90); - path.lineTo(border / 2, 30 - border / 2); - path.lineTo(50 - border / 2, 30 - border / 2); - path.closeSubpath(); - - QPen pen(QColor(100, 100, 100)); - pen.setWidth(border); - painter->setPen(pen); - painter->setBrush(hovered ? QColor(50, 50, 50, 160) : QColor(0, 0, 0, 160)); - - painter->drawPath(path); + const int radius = 8; + const qreal border = 1; + QPainterPath path(QPointF(50 - border / 2, border / 2)); + path.lineTo(radius, border / 2); + path.arcTo(border / 2, border / 2, 2 * radius, 2 * radius, 90, 90); + path.lineTo(border / 2, 30 - border / 2); + path.lineTo(50 - border / 2, 30 - border / 2); + path.closeSubpath(); + + QPen pen(QColor(100, 100, 100)); + pen.setWidth(border); + painter->setPen(pen); + painter->setBrush(hovered ? QColor(50, 50, 50, 160) : QColor(0, 0, 0, 160)); + + painter->drawPath(path); - QRectF translatedRect = painter->combinedTransform().mapRect(boundingRect()); - QSize translatedSize = translatedRect.size().toSize(); - painter->resetTransform(); - QFont font("Serif"); - font.setWeight(QFont::Bold); - font.setPixelSize(qMax((int) round(translatedSize.height() / 1.3), 9)); - painter->setFont(font); - painter->setPen(Qt::white); - painter->drawText(translatedRect, Qt::AlignCenter, QString::number(value)); + QRectF translatedRect = painter->combinedTransform().mapRect(boundingRect()); + QSize translatedSize = translatedRect.size().toSize(); + painter->resetTransform(); + QFont font("Serif"); + font.setWeight(QFont::Bold); + font.setPixelSize(qMax((int) round(translatedSize.height() / 1.3), 9)); + painter->setFont(font); + painter->setPen(Qt::white); + painter->drawText(translatedRect, Qt::AlignCenter, QString::number(value)); } PlayerTarget::PlayerTarget(Player *_owner, QGraphicsItem *parentItem) - : ArrowTarget(_owner, parentItem), playerCounter(0) + : ArrowTarget(_owner, parentItem), playerCounter(0) { - setCacheMode(DeviceCoordinateCache); + setCacheMode(DeviceCoordinateCache); + + const std::string &bmp = _owner->getUserInfo()->avatar_bmp(); + if (!fullPixmap.loadFromData((const uchar *) bmp.data(), bmp.size())) + fullPixmap = QPixmap(); +} - if (!fullPixmap.loadFromData(_owner->getUserInfo()->getAvatarBmp())) - fullPixmap = QPixmap(); +PlayerTarget::~PlayerTarget() +{ + // Explicit deletion is necessary in spite of parent/child relationship + // as we need this object to be alive to receive the destroyed() signal. + delete playerCounter; } QRectF PlayerTarget::boundingRect() const { - return QRectF(0, 0, 160, 64); + return QRectF(0, 0, 160, 64); } void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - ServerInfo_User *info = owner->getUserInfo(); + const ServerInfo_User *const info = owner->getUserInfo(); - const qreal border = 2; + const qreal border = 2; - QRectF avatarBoundingRect = boundingRect().adjusted(border, border, -border, -border); - QRectF translatedRect = painter->combinedTransform().mapRect(avatarBoundingRect); - QSize translatedSize = translatedRect.size().toSize(); - QPixmap cachedPixmap; - const QString cacheKey = "avatar" + QString::number(translatedSize.width()) + "_" + QString::number(info->getUserLevel()) + "_" + QString::number(fullPixmap.cacheKey()); + QRectF avatarBoundingRect = boundingRect().adjusted(border, border, -border, -border); + QRectF translatedRect = painter->combinedTransform().mapRect(avatarBoundingRect); + QSize translatedSize = translatedRect.size().toSize(); + QPixmap cachedPixmap; + const QString cacheKey = "avatar" + QString::number(translatedSize.width()) + "_" + QString::number(info->user_level()) + "_" + QString::number(fullPixmap.cacheKey()); #if QT_VERSION >= 0x040600 - if (!QPixmapCache::find(cacheKey, &cachedPixmap)) { + if (!QPixmapCache::find(cacheKey, &cachedPixmap)) { #else - if (!QPixmapCache::find(cacheKey, cachedPixmap)) { + if (!QPixmapCache::find(cacheKey, cachedPixmap)) { #endif - cachedPixmap = QPixmap(translatedSize.width(), translatedSize.height()); - - QPainter tempPainter(&cachedPixmap); - QRadialGradient grad(translatedRect.center(), sqrt(translatedSize.width() * translatedSize.width() + translatedSize.height() * translatedSize.height()) / 2); - grad.setColorAt(1, Qt::black); - grad.setColorAt(0, QColor(180, 180, 180)); - tempPainter.fillRect(QRectF(0, 0, translatedSize.width(), translatedSize.height()), grad); - - QPixmap tempPixmap; - if (fullPixmap.isNull()) - tempPixmap = UserLevelPixmapGenerator::generatePixmap(translatedSize.height(), info->getUserLevel()); - else - tempPixmap = fullPixmap.scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - tempPainter.drawPixmap((translatedSize.width() - tempPixmap.width()) / 2, (translatedSize.height() - tempPixmap.height()) / 2, tempPixmap); - QPixmapCache::insert(cacheKey, cachedPixmap); - } - - painter->save(); - painter->resetTransform(); - painter->translate((translatedSize.width() - cachedPixmap.width()) / 2.0, 0); - painter->drawPixmap(translatedRect, cachedPixmap, cachedPixmap.rect()); - painter->restore(); + cachedPixmap = QPixmap(translatedSize.width(), translatedSize.height()); + + QPainter tempPainter(&cachedPixmap); + // pow(foo, 0.5) equals to sqrt(foo), but using sqrt(foo) in this context will produce a compile error with MSVC++ + QRadialGradient grad(translatedRect.center(), pow(translatedSize.width() * translatedSize.width() + translatedSize.height() * translatedSize.height(), 0.5) / 2); + grad.setColorAt(1, Qt::black); + grad.setColorAt(0, QColor(180, 180, 180)); + tempPainter.fillRect(QRectF(0, 0, translatedSize.width(), translatedSize.height()), grad); + + QPixmap tempPixmap; + if (fullPixmap.isNull()) + tempPixmap = UserLevelPixmapGenerator::generatePixmap(translatedSize.height(), UserLevelFlags(info->user_level())); + else + tempPixmap = fullPixmap.scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + tempPainter.drawPixmap((translatedSize.width() - tempPixmap.width()) / 2, (translatedSize.height() - tempPixmap.height()) / 2, tempPixmap); + QPixmapCache::insert(cacheKey, cachedPixmap); + } + + painter->save(); + painter->resetTransform(); + painter->translate((translatedSize.width() - cachedPixmap.width()) / 2.0, 0); + painter->drawPixmap(translatedRect, cachedPixmap, cachedPixmap.rect()); + painter->restore(); - QRectF nameRect = QRectF(0, boundingRect().height() - 20, 110, 20); - painter->fillRect(nameRect, QColor(0, 0, 0, 160)); - QRectF translatedNameRect = painter->combinedTransform().mapRect(nameRect); - - painter->save(); - painter->resetTransform(); - - QString name = info->getName(); - if (name.size() > 13) - name = name.mid(0, 10) + "..."; - - QFont font; - font.setPixelSize(qMax((int) round(translatedNameRect.height() / 1.5), 9)); - painter->setFont(font); - painter->setPen(Qt::white); - painter->drawText(translatedNameRect, Qt::AlignVCenter | Qt::AlignLeft, " " + name); - painter->restore(); + QRectF nameRect = QRectF(0, boundingRect().height() - 20, 110, 20); + painter->fillRect(nameRect, QColor(0, 0, 0, 160)); + QRectF translatedNameRect = painter->combinedTransform().mapRect(nameRect); + + painter->save(); + painter->resetTransform(); + + QString name = QString::fromStdString(info->name()); + if (name.size() > 13) + name = name.mid(0, 10) + "..."; + + QFont font; + font.setPixelSize(qMax((int) round(translatedNameRect.height() / 1.5), 9)); + painter->setFont(font); + painter->setPen(Qt::white); + painter->drawText(translatedNameRect, Qt::AlignVCenter | Qt::AlignLeft, " " + name); + painter->restore(); - QPen pen(QColor(100, 100, 100)); - pen.setWidth(border); - pen.setJoinStyle(Qt::RoundJoin); - painter->setPen(pen); - painter->drawRect(boundingRect().adjusted(border / 2, border / 2, -border / 2, -border / 2)); - - if (getBeingPointedAt()) - painter->fillRect(boundingRect(), QBrush(QColor(255, 0, 0, 100))); + QPen pen(QColor(100, 100, 100)); + pen.setWidth(border); + pen.setJoinStyle(Qt::RoundJoin); + painter->setPen(pen); + painter->drawRect(boundingRect().adjusted(border / 2, border / 2, -border / 2, -border / 2)); + + if (getBeingPointedAt()) + painter->fillRect(boundingRect(), QBrush(QColor(255, 0, 0, 100))); } AbstractCounter *PlayerTarget::addCounter(int _counterId, const QString &_name, int _value) { - if (playerCounter) - return 0; - - playerCounter = new PlayerCounter(owner, _counterId, _name, _value, this); - playerCounter->setPos(boundingRect().width() - playerCounter->boundingRect().width(), boundingRect().height() - playerCounter->boundingRect().height()); - connect(playerCounter, SIGNAL(destroyed()), this, SLOT(delCounter())); - - return playerCounter; + if (playerCounter) { + disconnect(playerCounter, 0, this, 0); + playerCounter->delCounter(); + } + + playerCounter = new PlayerCounter(owner, _counterId, _name, _value, this); + playerCounter->setPos(boundingRect().width() - playerCounter->boundingRect().width(), boundingRect().height() - playerCounter->boundingRect().height()); + connect(playerCounter, SIGNAL(destroyed()), this, SLOT(counterDeleted())); + + return playerCounter; } -void PlayerTarget::delCounter() +void PlayerTarget::counterDeleted() { - playerCounter = 0; -} \ No newline at end of file + playerCounter = 0; +} diff --git a/cockatrice/src/playertarget.h b/cockatrice/src/playertarget.h index f8b6f2c42..8d36084ce 100644 --- a/cockatrice/src/playertarget.h +++ b/cockatrice/src/playertarget.h @@ -9,29 +9,30 @@ class Player; class PlayerCounter : public AbstractCounter { - Q_OBJECT + Q_OBJECT public: - PlayerCounter(Player *_player, int _id, const QString &_name, int _value, QGraphicsItem *parent = 0); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + PlayerCounter(Player *_player, int _id, const QString &_name, int _value, QGraphicsItem *parent = 0); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); }; class PlayerTarget : public ArrowTarget { - Q_OBJECT + Q_OBJECT private: - QPixmap fullPixmap; - PlayerCounter *playerCounter; + QPixmap fullPixmap; + PlayerCounter *playerCounter; public slots: - void delCounter(); + void counterDeleted(); public: - enum { Type = typePlayerTarget }; - int type() const { return Type; } - - PlayerTarget(Player *_player = 0, QGraphicsItem *parentItem = 0); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - - AbstractCounter *addCounter(int _counterId, const QString &_name, int _value); + enum { Type = typePlayerTarget }; + int type() const { return Type; } + + PlayerTarget(Player *_player = 0, QGraphicsItem *parentItem = 0); + ~PlayerTarget(); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + AbstractCounter *addCounter(int _counterId, const QString &_name, int _value); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/priceupdater.cpp b/cockatrice/src/priceupdater.cpp index e9f1a6953..89ff66c08 100644 --- a/cockatrice/src/priceupdater.cpp +++ b/cockatrice/src/priceupdater.cpp @@ -1,34 +1,54 @@ /** - * @author Marcio Ribeiro - * @version 1.0 + * @author Marcio Ribeiro , Max-Wilhelm Bruker + * @version 1.1 */ #include #include +#include -#include -#include +#include "qt-json/json.h" #include "priceupdater.h" +#include "main.h" +#include "carddatabase.h" + +#if QT_VERSION < 0x050000 + // for Qt::escape() + #include +#endif + /** * Constructor. * * @param _deck deck. */ -PriceUpdater::PriceUpdater(const DeckList *_deck) +AbstractPriceUpdater::AbstractPriceUpdater(const DeckList *_deck) { nam = new QNetworkAccessManager(this); deck = _deck; } +// blacklotusproject.com + +/** + * Constructor. + * + * @param _deck deck. + */ +BLPPriceUpdater::BLPPriceUpdater(const DeckList *_deck) +: AbstractPriceUpdater(_deck) +{ +} + /** * Update the prices of the cards in deckList. */ -void PriceUpdater::updatePrices() +void BLPPriceUpdater::updatePrices() { QString q = "http://blacklotusproject.com/json/?cards="; QStringList cards = deck->getCardList(); for (int i = 0; i < cards.size(); ++i) { - q += cards[i] + "|"; + q += cards[i].toLower() + "|"; } QUrl url(q.replace(' ', '+')); @@ -39,9 +59,35 @@ void PriceUpdater::updatePrices() /** * Called when the download of the json file with the prices is finished. */ -void PriceUpdater::downloadFinished() +void BLPPriceUpdater::downloadFinished() { - QMap cmap; + QNetworkReply *reply = static_cast(sender()); + bool ok; + QVariantMap resultMap = QtJson::Json::parse(QString(reply->readAll()), ok).toMap(); + if (!ok) { + reply->deleteLater(); + deleteLater(); + return; + } + + QMap cardsPrice; + + QListIterator it(resultMap.value("cards").toList()); + while (it.hasNext()) { + QVariantMap map = it.next().toMap(); + QString name = map.value("name").toString().toLower(); + float price = map.value("price").toString().toFloat(); + QString set = map.value("set_code").toString(); + + /** + * Make sure Masters Edition (MED) isn't the set, as it doesn't + * physically exist. Also check the price to see that the cheapest set + * ends up as the final price. + */ + if (set != "MED" && (!cardsPrice.contains(name) || cardsPrice.value(name) > price)) + cardsPrice.insert(name, price); + } + InnerDecklistNode *listRoot = deck->getRoot(); for (int i = 0; i < listRoot->size(); i++) { InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); @@ -49,33 +95,196 @@ void PriceUpdater::downloadFinished() DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); if (!currentCard) continue; - cmap.insert(currentCard->getName().toLower(), currentCard); - currentCard->setPrice(0); + currentCard->setPrice(cardsPrice[currentCard->getName().toLower()]); } } - - QNetworkReply *reply = static_cast(sender()); - QByteArray result = reply->readAll(); - QScriptValue sc; - QScriptEngine engine; - sc = engine.evaluate("value = " + result); - - if (sc.property("cards").isArray()) { - QScriptValueIterator it(sc.property("cards")); - while (it.hasNext()) { - it.next(); - QString name = it.value().property("name").toString().toLower(); - float price = it.value().property("average").toString().toFloat(); - DecklistCardNode *c = cmap[name]; - if (!c) - continue; - if (c->getPrice() == 0 || c->getPrice() > price) { - c->setPrice(price); - } - } - } - + reply->deleteLater(); deleteLater(); emit finishedUpdate(); } + +// deckbrew.com + +/** + * Constructor. + * + * @param _deck deck. + */ +DBPriceUpdater::DBPriceUpdater(const DeckList *_deck) +: AbstractPriceUpdater(_deck) +{ +} + +/** + * Update the prices of the cards in deckList. + */ +void DBPriceUpdater::updatePrices() +{ + QString base = "https://api.deckbrew.com/mtg/cards", q = ""; + QStringList cards = deck->getCardList(); + muidMap.clear(); + urls.clear(); + CardInfo * card; + int muid; + SetList sets; + bool bNotFirst=false; + + for (int i = 0; i < cards.size(); ++i) { + card = db->getCard(cards[i], false); + sets = card->getSets(); + for(int j = 0; j < sets.size(); ++j) + { + muid=card->getMuId(sets[j]->getShortName()); + + if (!muid) { + continue; + } + + //qDebug() << "muid " << muid << " card: " << cards[i] << endl; + if(bNotFirst) + { + q += QString("&m=%1").arg(muid); + } else { + q += QString("?m=%1").arg(muid); + bNotFirst = true; + } + muidMap.insert(muid, cards[i]); + + if(q.length() > 240) + { + urls.append(base + q); + bNotFirst=false; + q = ""; + } + } + } + if(q.length() > 0) + urls.append(base + q); + + requestNext(); +} + +void DBPriceUpdater::requestNext() +{ + if(urls.empty()) + return; + + QUrl url(urls.takeFirst(), QUrl::TolerantMode); + //qDebug() << "request prices from: " << url.toString() << endl; + QNetworkReply *reply = nam->get(QNetworkRequest(url)); + connect(reply, SIGNAL(finished()), this, SLOT(downloadFinished())); +} + +/** + * Called when the download of the json file with the prices is finished. + */ +void DBPriceUpdater::downloadFinished() +{ + QNetworkReply *reply = static_cast(sender()); + bool ok; + QString tmp = QString(reply->readAll()); + + // Errors are incapsulated in an object, check for them first + QVariantMap resultMap = QtJson::Json::parse(tmp, ok).toMap(); + if (!ok) { + QMessageBox::critical(this, tr("Error"), tr("A problem has occured while fetching card prices.")); + reply->deleteLater(); + if(urls.isEmpty()) + { + deleteLater(); + emit finishedUpdate(); + } else { + requestNext(); + } + } + + if(resultMap.contains("errors")) + { + QMessageBox::critical(this, tr("Error"), tr("A problem has occured while fetching card prices:") + + "
" + +#if QT_VERSION < 0x050000 + Qt::escape(resultMap["errors"].toList().first().toString()) +#else + resultMap["errors"].toList().first().toString().toHtmlEscaped() +#endif + ); + reply->deleteLater(); + if(urls.isEmpty()) + { + deleteLater(); + emit finishedUpdate(); + } else { + requestNext(); + } + } + + // Good results are a list + QVariantList resultList = QtJson::Json::parse(tmp, ok).toList(); + if (!ok) { + QMessageBox::critical(this, tr("Error"), tr("A problem has occured while fetching card prices.")); + reply->deleteLater(); + if(urls.isEmpty()) + { + deleteLater(); + emit finishedUpdate(); + } else { + requestNext(); + } + } + + QMap cardsPrice; + QListIterator it(resultList); + while (it.hasNext()) { + QVariantMap cardMap = it.next().toMap(); + + + // get sets list + QList editions = cardMap.value("editions").toList(); + foreach (QVariant ed, editions) + { + // retrieve card name "as we know it" from the muid + QVariantMap edition = ed.toMap(); + QString set = edition.value("set_id").toString(); + + int muid = edition.value("multiverse_id").toString().toInt(); + if(!muidMap.contains(muid)) + continue; + + QString name=muidMap.value(muid); + // Prices are in USD cents + float price = edition.value("price").toMap().value("median").toString().toFloat() / 100; + //qDebug() << "card " << name << " set " << set << " price " << price << endl; + + /** + * Make sure Masters Edition (MED) isn't the set, as it doesn't + * physically exist. Also check the price to see that the cheapest set + * ends up as the final price. + */ + if (set != "MED" && price > 0 && (!cardsPrice.contains(name) || cardsPrice.value(name) > price)) + cardsPrice.insert(name, price); + } + } + + InnerDecklistNode *listRoot = deck->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; + float price = cardsPrice[currentCard->getName()]; + if(price > 0) + currentCard->setPrice(price); + } + } + + reply->deleteLater(); + if(urls.isEmpty()) + { + deleteLater(); + emit finishedUpdate(); + } else { + requestNext(); + } +} diff --git a/cockatrice/src/priceupdater.h b/cockatrice/src/priceupdater.h index a95252eb8..afe582b65 100644 --- a/cockatrice/src/priceupdater.h +++ b/cockatrice/src/priceupdater.h @@ -1,28 +1,57 @@ #ifndef PRICEUPDATER_H #define PRICEUPDATER_H -#include +#include #include "decklist.h" class QNetworkAccessManager; +// If we don't typedef this, won't compile on OS X < 10.9 +typedef QMap MuidStringMap; + /** * Price Updater. * * @author Marcio Ribeiro */ -class PriceUpdater : public QObject +class AbstractPriceUpdater : public QWidget { Q_OBJECT -private: +public: + enum PriceSource { BLPPriceSource, DBPriceSource }; +protected: const DeckList *deck; QNetworkAccessManager *nam; signals: void finishedUpdate(); -private slots: - void downloadFinished(); +protected slots: + virtual void downloadFinished() = 0; public: - PriceUpdater(const DeckList *deck); - void updatePrices(); + AbstractPriceUpdater(const DeckList *deck); + virtual void updatePrices() = 0; +}; + +class BLPPriceUpdater : public AbstractPriceUpdater +{ + Q_OBJECT +protected: + virtual void downloadFinished(); +public: + BLPPriceUpdater(const DeckList *deck); + virtual void updatePrices(); +}; + +class DBPriceUpdater : public AbstractPriceUpdater +{ + Q_OBJECT +protected: + MuidStringMap muidMap; + QList urls; +protected: + virtual void downloadFinished(); + void requestNext(); +public: + DBPriceUpdater(const DeckList *deck); + virtual void updatePrices(); }; #endif diff --git a/cockatrice/src/qt-json/AUTHORS b/cockatrice/src/qt-json/AUTHORS new file mode 100644 index 000000000..29a85929f --- /dev/null +++ b/cockatrice/src/qt-json/AUTHORS @@ -0,0 +1,3 @@ +Eeli Reilin +Luis Gustavo S. Barreto +Stephen Kockentiedt diff --git a/cockatrice/src/qt-json/LICENSE b/cockatrice/src/qt-json/LICENSE new file mode 100644 index 000000000..3c42b515a --- /dev/null +++ b/cockatrice/src/qt-json/LICENSE @@ -0,0 +1,27 @@ +Copyright 2011 Eeli Reilin. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation +are those of the authors and should not be interpreted as representing +official policies, either expressed or implied, of Eeli Reilin. + diff --git a/cockatrice/src/qt-json/README b/cockatrice/src/qt-json/README new file mode 100644 index 000000000..b60c1599b --- /dev/null +++ b/cockatrice/src/qt-json/README @@ -0,0 +1,96 @@ +######################################################################## +1. INTRODUCTION + +The Json class is a simple class for parsing JSON data into a QVariant +hierarchies. Now, we can also reverse the process and serialize +QVariant hierarchies into valid JSON data. + + +######################################################################## +2. HOW TO USE + +The parser is really easy to use. Let's say we have the following +QString of JSON data: + +------------------------------------------------------------------------ +{ + "encoding" : "UTF-8", + "plug-ins" : [ + "python", + "c++", + "ruby" + ], + "indent" : { + "length" : 3, + "use_space" : true + } +} +------------------------------------------------------------------------ + +We would first call the parse-method: + +------------------------------------------------------------------------ +//Say that we're using the QtJson namespace +using namespace QtJson; +bool ok; +//json is a QString containing the JSON data +QVariantMap result = Json::parse(json, ok).toMap(); + +if(!ok) { + qFatal("An error occurred during parsing"); + exit(1); +} +------------------------------------------------------------------------ + +Assuming the parsing process completed without errors, we would then +go through the hierarchy: + +------------------------------------------------------------------------ +qDebug() << "encoding:" << result["encoding"].toString(); +qDebug() << "plugins:"; + +foreach(QVariant plugin, result["plug-ins"].toList()) { + qDebug() << "\t-" << plugin.toString(); +} + +QVariantMap nestedMap = result["indent"].toMap(); +qDebug() << "length:" << nestedMap["length"].toInt(); +qDebug() << "use_space:" << nestedMap["use_space"].toBool(); +------------------------------------------------------------------------ + +The previous code would print out the following: + +------------------------------------------------------------------------ +encoding: "UTF-8" +plugins: + - "python" + - "c++" + - "ruby" +length: 3 +use_space: true +------------------------------------------------------------------------ + +To write JSON data from Qt object is as simple as parsing: + +------------------------------------------------------------------------ +QVariantMap map; +map["name"] = "Name"; +map["age"] = 22; + +QByteArray data = Json::serialize(map); +------------------------------------------------------------------------ + +The byte array 'data' contains valid JSON data: + +------------------------------------------------------------------------ +{ + name: "Luis Gustavo", + age: 22, +} +------------------------------------------------------------------------ + + +######################################################################## +4. CONTRIBUTING + +The code is available to download at GitHub. Contribute if you dare! diff --git a/cockatrice/src/qt-json/json.cpp b/cockatrice/src/qt-json/json.cpp new file mode 100644 index 000000000..faf6601b3 --- /dev/null +++ b/cockatrice/src/qt-json/json.cpp @@ -0,0 +1,612 @@ +/* Copyright 2011 Eeli Reilin. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of Eeli Reilin. + */ + +/** + * \file json.cpp + */ + +#include "json.h" +#include + +namespace QtJson +{ + + +static QString sanitizeString(QString str) +{ + str.replace(QLatin1String("\\"), QLatin1String("\\\\")); + str.replace(QLatin1String("\""), QLatin1String("\\\"")); + str.replace(QLatin1String("\b"), QLatin1String("\\b")); + str.replace(QLatin1String("\f"), QLatin1String("\\f")); + str.replace(QLatin1String("\n"), QLatin1String("\\n")); + str.replace(QLatin1String("\r"), QLatin1String("\\r")); + str.replace(QLatin1String("\t"), QLatin1String("\\t")); + return QString(QLatin1String("\"%1\"")).arg(str); +} + +static QByteArray join(const QList &list, const QByteArray &sep) +{ + QByteArray res; + Q_FOREACH(const QByteArray &i, list) + { + if(!res.isEmpty()) + { + res += sep; + } + res += i; + } + return res; +} + +/** + * parse + */ +QVariant Json::parse(const QString &json) +{ + bool success = true; + return Json::parse(json, success); +} + +/** + * parse + */ +QVariant Json::parse(const QString &json, bool &success) +{ + success = true; + + //Return an empty QVariant if the JSON data is either null or empty + if(!json.isNull() || !json.isEmpty()) + { + QString data = json; + //We'll start from index 0 + int index = 0; + + //Parse the first value + QVariant value = Json::parseValue(data, index, success); + + //Return the parsed value + return value; + } + else + { + //Return the empty QVariant + return QVariant(); + } +} + +QByteArray Json::serialize(const QVariant &data) +{ + bool success = true; + return Json::serialize(data, success); +} + +QByteArray Json::serialize(const QVariant &data, bool &success) +{ + QByteArray str; + success = true; + + if(!data.isValid()) // invalid or null? + { + str = "null"; + } + else if((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list? + { + QList values; + const QVariantList list = data.toList(); + Q_FOREACH(const QVariant& v, list) + { + QByteArray serializedValue = serialize(v); + if(serializedValue.isNull()) + { + success = false; + break; + } + values << serializedValue; + } + + str = "[ " + join( values, ", " ) + " ]"; + } + else if(data.type() == QVariant::Hash) // variant is a hash? + { + const QVariantHash vhash = data.toHash(); + QHashIterator it( vhash ); + str = "{ "; + QList pairs; + + while(it.hasNext()) + { + it.next(); + QByteArray serializedValue = serialize(it.value()); + + if(serializedValue.isNull()) + { + success = false; + break; + } + + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + } + + str += join(pairs, ", "); + str += " }"; + } + else if(data.type() == QVariant::Map) // variant is a map? + { + const QVariantMap vmap = data.toMap(); + QMapIterator it( vmap ); + str = "{ "; + QList pairs; + while(it.hasNext()) + { + it.next(); + QByteArray serializedValue = serialize(it.value()); + if(serializedValue.isNull()) + { + success = false; + break; + } + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + } + str += join(pairs, ", "); + str += " }"; + } + else if((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array? + { + str = sanitizeString(data.toString()).toUtf8(); + } + else if(data.type() == QVariant::Double) // double? + { + str = QByteArray::number(data.toDouble(), 'g', 20); + if(!str.contains(".") && ! str.contains("e")) + { + str += ".0"; + } + } + else if (data.type() == QVariant::Bool) // boolean value? + { + str = data.toBool() ? "true" : "false"; + } + else if (data.type() == QVariant::ULongLong) // large unsigned number? + { + str = QByteArray::number(data.value()); + } + else if ( data.canConvert() ) // any signed number? + { + str = QByteArray::number(data.value()); + } + else if (data.canConvert()) + { + str = QString::number(data.value()).toUtf8(); + } + else if (data.canConvert()) // can value be converted to string? + { + // this will catch QDate, QDateTime, QUrl, ... + str = sanitizeString(data.toString()).toUtf8(); + } + else + { + success = false; + } + if (success) + { + return str; + } + else + { + return QByteArray(); + } +} + +/** + * parseValue + */ +QVariant Json::parseValue(const QString &json, int &index, bool &success) +{ + //Determine what kind of data we should parse by + //checking out the upcoming token + switch(Json::lookAhead(json, index)) + { + case JsonTokenString: + return Json::parseString(json, index, success); + case JsonTokenNumber: + return Json::parseNumber(json, index); + case JsonTokenCurlyOpen: + return Json::parseObject(json, index, success); + case JsonTokenSquaredOpen: + return Json::parseArray(json, index, success); + case JsonTokenTrue: + Json::nextToken(json, index); + return QVariant(true); + case JsonTokenFalse: + Json::nextToken(json, index); + return QVariant(false); + case JsonTokenNull: + Json::nextToken(json, index); + return QVariant(); + case JsonTokenNone: + break; + } + + //If there were no tokens, flag the failure and return an empty QVariant + success = false; + return QVariant(); +} + +/** + * parseObject + */ +QVariant Json::parseObject(const QString &json, int &index, bool &success) +{ + QVariantMap map; + int token; + + //Get rid of the whitespace and increment index + Json::nextToken(json, index); + + //Loop through all of the key/value pairs of the object + bool done = false; + while(!done) + { + //Get the upcoming token + token = Json::lookAhead(json, index); + + if(token == JsonTokenNone) + { + success = false; + return QVariantMap(); + } + else if(token == JsonTokenComma) + { + Json::nextToken(json, index); + } + else if(token == JsonTokenCurlyClose) + { + Json::nextToken(json, index); + return map; + } + else + { + //Parse the key/value pair's name + QString name = Json::parseString(json, index, success).toString(); + + if(!success) + { + return QVariantMap(); + } + + //Get the next token + token = Json::nextToken(json, index); + + //If the next token is not a colon, flag the failure + //return an empty QVariant + if(token != JsonTokenColon) + { + success = false; + return QVariant(QVariantMap()); + } + + //Parse the key/value pair's value + QVariant value = Json::parseValue(json, index, success); + + if(!success) + { + return QVariantMap(); + } + + //Assign the value to the key in the map + map[name] = value; + } + } + + //Return the map successfully + return QVariant(map); +} + +/** + * parseArray + */ +QVariant Json::parseArray(const QString &json, int &index, bool &success) +{ + QVariantList list; + + Json::nextToken(json, index); + + bool done = false; + while(!done) + { + int token = Json::lookAhead(json, index); + + if(token == JsonTokenNone) + { + success = false; + return QVariantList(); + } + else if(token == JsonTokenComma) + { + Json::nextToken(json, index); + } + else if(token == JsonTokenSquaredClose) + { + Json::nextToken(json, index); + break; + } + else + { + QVariant value = Json::parseValue(json, index, success); + + if(!success) + { + return QVariantList(); + } + + list.push_back(value); + } + } + + return QVariant(list); +} + +/** + * parseString + */ +QVariant Json::parseString(const QString &json, int &index, bool &success) +{ + QString s; + QChar c; + + Json::eatWhitespace(json, index); + + c = json[index++]; + + bool complete = false; + while(!complete) + { + if(index == json.size()) + { + break; + } + + c = json[index++]; + + if(c == '\"') + { + complete = true; + break; + } + else if(c == '\\') + { + if(index == json.size()) + { + break; + } + + c = json[index++]; + + if(c == '\"') + { + s.append('\"'); + } + else if(c == '\\') + { + s.append('\\'); + } + else if(c == '/') + { + s.append('/'); + } + else if(c == 'b') + { + s.append('\b'); + } + else if(c == 'f') + { + s.append('\f'); + } + else if(c == 'n') + { + s.append('\n'); + } + else if(c == 'r') + { + s.append('\r'); + } + else if(c == 't') + { + s.append('\t'); + } + else if(c == 'u') + { + int remainingLength = json.size() - index; + + if(remainingLength >= 4) + { + QString unicodeStr = json.mid(index, 4); + + int symbol = unicodeStr.toInt(0, 16); + + s.append(QChar(symbol)); + + index += 4; + } + else + { + break; + } + } + } + else + { + s.append(c); + } + } + + if(!complete) + { + success = false; + return QVariant(); + } + + return QVariant(s); +} + +/** + * parseNumber + */ +QVariant Json::parseNumber(const QString &json, int &index) +{ + Json::eatWhitespace(json, index); + + int lastIndex = Json::lastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + QString numberStr; + + numberStr = json.mid(index, charLength); + + index = lastIndex + 1; + + if (numberStr.contains('.')) { + return QVariant(numberStr.toDouble(NULL)); + } else if (numberStr.startsWith('-')) { + return QVariant(numberStr.toLongLong(NULL)); + } else { + return QVariant(numberStr.toULongLong(NULL)); + } +} + +/** + * lastIndexOfNumber + */ +int Json::lastIndexOfNumber(const QString &json, int index) +{ + int lastIndex; + + for(lastIndex = index; lastIndex < json.size(); lastIndex++) + { + if(QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) + { + break; + } + } + + return lastIndex -1; +} + +/** + * eatWhitespace + */ +void Json::eatWhitespace(const QString &json, int &index) +{ + for(; index < json.size(); index++) + { + if(QString(" \t\n\r").indexOf(json[index]) == -1) + { + break; + } + } +} + +/** + * lookAhead + */ +int Json::lookAhead(const QString &json, int index) +{ + int saveIndex = index; + return Json::nextToken(json, saveIndex); +} + +/** + * nextToken + */ +int Json::nextToken(const QString &json, int &index) +{ + Json::eatWhitespace(json, index); + + if(index == json.size()) + { + return JsonTokenNone; + } + + QChar c = json[index]; + index++; + switch(c.toLatin1()) + { + case '{': return JsonTokenCurlyOpen; + case '}': return JsonTokenCurlyClose; + case '[': return JsonTokenSquaredOpen; + case ']': return JsonTokenSquaredClose; + case ',': return JsonTokenComma; + case '"': return JsonTokenString; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': return JsonTokenNumber; + case ':': return JsonTokenColon; + } + + index--; + + int remainingLength = json.size() - index; + + //True + if(remainingLength >= 4) + { + if (json[index] == 't' && json[index + 1] == 'r' && + json[index + 2] == 'u' && json[index + 3] == 'e') + { + index += 4; + return JsonTokenTrue; + } + } + + //False + if (remainingLength >= 5) + { + if (json[index] == 'f' && json[index + 1] == 'a' && + json[index + 2] == 'l' && json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return JsonTokenFalse; + } + } + + //Null + if (remainingLength >= 4) + { + if (json[index] == 'n' && json[index + 1] == 'u' && + json[index + 2] == 'l' && json[index + 3] == 'l') + { + index += 4; + return JsonTokenNull; + } + } + + return JsonTokenNone; +} + + +} //end namespace diff --git a/cockatrice/src/qt-json/json.h b/cockatrice/src/qt-json/json.h new file mode 100644 index 000000000..cf0499d4e --- /dev/null +++ b/cockatrice/src/qt-json/json.h @@ -0,0 +1,204 @@ +/* Copyright 2011 Eeli Reilin. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of Eeli Reilin. + */ + +/** + * \file json.h + */ + +#ifndef JSON_H +#define JSON_H + +#include +#include + +namespace QtJson +{ + +/** + * \enum JsonToken + */ +enum JsonToken +{ + JsonTokenNone = 0, + JsonTokenCurlyOpen = 1, + JsonTokenCurlyClose = 2, + JsonTokenSquaredOpen = 3, + JsonTokenSquaredClose = 4, + JsonTokenColon = 5, + JsonTokenComma = 6, + JsonTokenString = 7, + JsonTokenNumber = 8, + JsonTokenTrue = 9, + JsonTokenFalse = 10, + JsonTokenNull = 11 +}; + +/** + * \class Json + * \brief A JSON data parser + * + * Json parses a JSON data into a QVariant hierarchy. + */ +class Json +{ + public: + /** + * Parse a JSON string + * + * \param json The JSON data + */ + static QVariant parse(const QString &json); + + /** + * Parse a JSON string + * + * \param json The JSON data + * \param success The success of the parsing + */ + static QVariant parse(const QString &json, bool &success); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + */ + static QByteArray serialize(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QByteArray Textual JSON representation + */ + static QByteArray serialize(const QVariant &data, bool &success); + + private: + /** + * Parses a value starting from index + * + * \param json The JSON data + * \param index The start index + * \param success The success of the parse process + * + * \return QVariant The parsed value + */ + static QVariant parseValue(const QString &json, int &index, + bool &success); + + /** + * Parses an object starting from index + * + * \param json The JSON data + * \param index The start index + * \param success The success of the object parse + * + * \return QVariant The parsed object map + */ + static QVariant parseObject(const QString &json, int &index, + bool &success); + + /** + * Parses an array starting from index + * + * \param json The JSON data + * \param index The starting index + * \param success The success of the array parse + * + * \return QVariant The parsed variant array + */ + static QVariant parseArray(const QString &json, int &index, + bool &success); + + /** + * Parses a string starting from index + * + * \param json The JSON data + * \param index The starting index + * \param success The success of the string parse + * + * \return QVariant The parsed string + */ + static QVariant parseString(const QString &json, int &index, + bool &success); + + /** + * Parses a number starting from index + * + * \param json The JSON data + * \param index The starting index + * + * \return QVariant The parsed number + */ + static QVariant parseNumber(const QString &json, int &index); + + /** + * Get the last index of a number starting from index + * + * \param json The JSON data + * \param index The starting index + * + * \return The last index of the number + */ + static int lastIndexOfNumber(const QString &json, int index); + + /** + * Skip unwanted whitespace symbols starting from index + * + * \param json The JSON data + * \param index The start index + */ + static void eatWhitespace(const QString &json, int &index); + + /** + * Check what token lies ahead + * + * \param json The JSON data + * \param index The starting index + * + * \return int The upcoming token + */ + static int lookAhead(const QString &json, int index); + + /** + * Get the next JSON token + * + * \param json The JSON data + * \param index The starting index + * + * \return int The next JSON token + */ + static int nextToken(const QString &json, int &index); +}; + + +} //end namespace + +#endif //JSON_H diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 401abb0df..4f8deaed6 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -1,154 +1,231 @@ #include -#include -#include -#include +#include #include "remoteclient.h" -#include "protocol.h" -#include "protocol_items.h" + +#include "pending_command.h" +#include "pb/commands.pb.h" +#include "pb/session_commands.pb.h" +#include "pb/response_login.pb.h" +#include "pb/server_message.pb.h" +#include "pb/event_server_identification.pb.h" + +static const unsigned int protocolVersion = 14; RemoteClient::RemoteClient(QObject *parent) - : AbstractClient(parent), timeRunning(0), lastDataReceived(0), topLevelItem(0) + : AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false), messageLength(0) { - ProtocolItem::initializeHash(); - - timer = new QTimer(this); - timer->setInterval(1000); - connect(timer, SIGNAL(timeout()), this, SLOT(ping())); + timer = new QTimer(this); + timer->setInterval(1000); + connect(timer, SIGNAL(timeout()), this, SLOT(ping())); - socket = new QTcpSocket(this); - connect(socket, SIGNAL(connected()), this, SLOT(slotConnected())); - connect(socket, SIGNAL(readyRead()), this, SLOT(readData())); - connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(slotSocketError(QAbstractSocket::SocketError))); - - xmlReader = new QXmlStreamReader; - xmlWriter = new QXmlStreamWriter; - xmlWriter->setDevice(socket); + socket = new QTcpSocket(this); + socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); + connect(socket, SIGNAL(connected()), this, SLOT(slotConnected())); + connect(socket, SIGNAL(readyRead()), this, SLOT(readData())); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(slotSocketError(QAbstractSocket::SocketError))); + + connect(this, SIGNAL(serverIdentificationEventReceived(const Event_ServerIdentification &)), this, SLOT(processServerIdentificationEvent(const Event_ServerIdentification &))); + connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this, SLOT(processConnectionClosedEvent(Event_ConnectionClosed))); + connect(this, SIGNAL(sigConnectToServer(QString, unsigned int, QString, QString)), this, SLOT(doConnectToServer(QString, unsigned int, QString, QString))); + connect(this, SIGNAL(sigDisconnectFromServer()), this, SLOT(doDisconnectFromServer())); } RemoteClient::~RemoteClient() { - disconnectFromServer(); + doDisconnectFromServer(); + thread()->quit(); } void RemoteClient::slotSocketError(QAbstractSocket::SocketError /*error*/) { - QString errorString = socket->errorString(); - disconnectFromServer(); - emit socketError(errorString); + QString errorString = socket->errorString(); + doDisconnectFromServer(); + emit socketError(errorString); } void RemoteClient::slotConnected() { - timeRunning = lastDataReceived = 0; - timer->start(); - setStatus(StatusAwaitingWelcome); + timeRunning = lastDataReceived = 0; + timer->start(); + + // dirty hack to be compatible with v14 server + sendCommandContainer(CommandContainer()); + getNewCmdId(); + // end of hack + + setStatus(StatusAwaitingWelcome); } -void RemoteClient::loginResponse(ProtocolResponse *response) +void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event) { - if (response->getResponseCode() == RespOk) { - Response_Login *resp = qobject_cast(response); - if (!resp) { - disconnectFromServer(); - return; - } - setStatus(StatusLoggedIn); - emit userInfoChanged(resp->getUserInfo()); - emit buddyListReceived(resp->getBuddyList()); - emit ignoreListReceived(resp->getIgnoreList()); - } else { - emit serverError(response->getResponseCode()); - setStatus(StatusDisconnecting); - } + if (event.protocol_version() != protocolVersion) { + emit protocolVersionMismatch(protocolVersion, event.protocol_version()); + setStatus(StatusDisconnecting); + return; + } + setStatus(StatusLoggingIn); + + Command_Login cmdLogin; + cmdLogin.set_user_name(userName.toStdString()); + cmdLogin.set_password(password.toStdString()); + + PendingCommand *pend = prepareSessionCommand(cmdLogin); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(loginResponse(Response))); + sendCommand(pend); +} + +void RemoteClient::processConnectionClosedEvent(const Event_ConnectionClosed & /*event*/) +{ + doDisconnectFromServer(); +} + +void RemoteClient::loginResponse(const Response &response) +{ + const Response_Login &resp = response.GetExtension(Response_Login::ext); + if (response.response_code() == Response::RespOk) { + setStatus(StatusLoggedIn); + emit userInfoChanged(resp.user_info()); + + QList buddyList; + for (int i = resp.buddy_list_size() - 1; i >= 0; --i) + buddyList.append(resp.buddy_list(i)); + emit buddyListReceived(buddyList); + + QList ignoreList; + for (int i = resp.ignore_list_size() - 1; i >= 0; --i) + ignoreList.append(resp.ignore_list(i)); + emit ignoreListReceived(ignoreList); + } else { + emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time()); + setStatus(StatusDisconnecting); + } } void RemoteClient::readData() { - QByteArray data = socket->readAll(); - qDebug() << data; - xmlReader->addData(data); - lastDataReceived = timeRunning; + lastDataReceived = timeRunning; + QByteArray data = socket->readAll(); - while (!xmlReader->atEnd()) { - xmlReader->readNext(); - if (topLevelItem) - topLevelItem->readElement(xmlReader); - else if (xmlReader->isStartElement() && (xmlReader->name().toString() == "cockatrice_server_stream")) { - int serverVersion = xmlReader->attributes().value("version").toString().toInt(); - if (serverVersion != ProtocolItem::protocolVersion) { - emit protocolVersionMismatch(ProtocolItem::protocolVersion, serverVersion); - disconnectFromServer(); - return; - } - xmlWriter->writeStartDocument(); - xmlWriter->writeStartElement("cockatrice_client_stream"); - xmlWriter->writeAttribute("version", QString::number(ProtocolItem::protocolVersion)); - xmlWriter->writeAttribute("comp", "1"); - - topLevelItem = new TopLevelProtocolItem; - connect(topLevelItem, SIGNAL(protocolItemReceived(ProtocolItem *)), this, SLOT(processProtocolItem(ProtocolItem *))); - - setStatus(StatusLoggingIn); - Command_Login *cmdLogin = new Command_Login(userName, password); - connect(cmdLogin, SIGNAL(finished(ProtocolResponse *)), this, SLOT(loginResponse(ProtocolResponse *))); - sendCommand(cmdLogin); - } - } - if (status == StatusDisconnecting) - disconnectFromServer(); + inputBuffer.append(data); + + do { + if (!messageInProgress) { + if (inputBuffer.size() >= 4) { + // dirty hack to be compatible with v14 server that sends 60 bytes of garbage at the beginning + if (!handshakeStarted) { + handshakeStarted = true; + if (inputBuffer.startsWith("write(xmlWriter); - pendingCommands.insert(cont->getCmdId(), cont); + QByteArray buf; + unsigned int size = cont.ByteSize(); +#ifdef QT_DEBUG + qDebug() << "OUT" << size << QString::fromStdString(cont.ShortDebugString()); +#endif + buf.resize(size + 4); + cont.SerializeToArray(buf.data() + 4, size); + buf.data()[3] = (unsigned char) size; + buf.data()[2] = (unsigned char) (size >> 8); + buf.data()[1] = (unsigned char) (size >> 16); + buf.data()[0] = (unsigned char) (size >> 24); + + socket->write(buf); } -void RemoteClient::connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password) +void RemoteClient::doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password) { - disconnectFromServer(); - - userName = _userName; - password = _password; - socket->connectToHost(hostname, port); - setStatus(StatusConnecting); + doDisconnectFromServer(); + + userName = _userName; + password = _password; + socket->connectToHost(hostname, port); + setStatus(StatusConnecting); } -void RemoteClient::disconnectFromServer() +void RemoteClient::doDisconnectFromServer() { - delete topLevelItem; - topLevelItem = 0; - - xmlReader->clear(); - - timer->stop(); + timer->stop(); + + messageInProgress = false; + handshakeStarted = false; + messageLength = 0; - QList pc = pendingCommands.values(); - for (int i = 0; i < pc.size(); i++) - delete pc[i]; - pendingCommands.clear(); + QList pc = pendingCommands.values(); + for (int i = 0; i < pc.size(); i++) { + Response response; + response.set_response_code(Response::RespNotConnected); + response.set_cmd_id(pc[i]->getCommandContainer().cmd_id()); + pc[i]->processResponse(response); + + delete pc[i]; + } + pendingCommands.clear(); - setStatus(StatusDisconnected); - socket->close(); + setStatus(StatusDisconnected); + socket->close(); } void RemoteClient::ping() { - QMutableMapIterator i(pendingCommands); - while (i.hasNext()) - if (i.next().value()->tick() > maxTimeout) { - CommandContainer *cont = i.value(); - i.remove(); - cont->deleteLater(); - } - - int maxTime = timeRunning - lastDataReceived; - emit maxPingTime(maxTime, maxTimeout); - if (maxTime >= maxTimeout) { - disconnectFromServer(); - emit serverTimeout(); - } else { - sendCommand(new Command_Ping); - ++timeRunning; - } + QMutableMapIterator i(pendingCommands); + while (i.hasNext()) { + PendingCommand *pend = i.next().value(); + if (pend->tick() > maxTimeout) { + i.remove(); + pend->deleteLater(); + } + } + + int maxTime = timeRunning - lastDataReceived; + emit maxPingTime(maxTime, maxTimeout); + if (maxTime >= maxTimeout) { + disconnectFromServer(); + emit serverTimeout(); + } else { + sendCommand(prepareSessionCommand(Command_Ping())); + ++timeRunning; + } +} + +void RemoteClient::connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password) +{ + emit sigConnectToServer(hostname, port, _userName, _password); +} + +void RemoteClient::disconnectFromServer() +{ + emit sigDisconnectFromServer(); } diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index 5e89a96eb..2cb84e904 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -5,41 +5,47 @@ #include "abstractclient.h" class QTimer; -class QXmlStreamReader; -class QXmlStreamWriter; class RemoteClient : public AbstractClient { - Q_OBJECT + Q_OBJECT signals: - void maxPingTime(int seconds, int maxSeconds); - void serverTimeout(); - void socketError(const QString &errorString); - void protocolVersionMismatch(int clientVersion, int serverVersion); - void protocolError(); + void maxPingTime(int seconds, int maxSeconds); + void serverTimeout(); + void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); + void socketError(const QString &errorString); + void protocolVersionMismatch(int clientVersion, int serverVersion); + void protocolError(); + void sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void sigDisconnectFromServer(); private slots: - void slotConnected(); - void readData(); - void slotSocketError(QAbstractSocket::SocketError error); - void ping(); - void loginResponse(ProtocolResponse *response); + void slotConnected(); + void readData(); + void slotSocketError(QAbstractSocket::SocketError error); + void ping(); + void processServerIdentificationEvent(const Event_ServerIdentification &event); + void processConnectionClosedEvent(const Event_ConnectionClosed &event); + void loginResponse(const Response &response); + void doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void doDisconnectFromServer(); private: - static const int maxTimeout = 10; - int timeRunning, lastDataReceived; - - QTimer *timer; - QTcpSocket *socket; - QXmlStreamReader *xmlReader; - QXmlStreamWriter *xmlWriter; - TopLevelProtocolItem *topLevelItem; -public: - RemoteClient(QObject *parent = 0); - ~RemoteClient(); - QString peerName() const { return socket->peerName(); } + static const int maxTimeout = 10; + int timeRunning, lastDataReceived; - void connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); - void disconnectFromServer(); - - void sendCommandContainer(CommandContainer *cont); + QByteArray inputBuffer; + bool messageInProgress; + bool handshakeStarted; + int messageLength; + + QTimer *timer; + QTcpSocket *socket; +protected slots: + void sendCommandContainer(const CommandContainer &cont); +public: + RemoteClient(QObject *parent = 0); + ~RemoteClient(); + QString peerName() const { return socket->peerName(); } + void connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void disconnectFromServer(); }; #endif diff --git a/cockatrice/src/remotedecklist_treewidget.cpp b/cockatrice/src/remotedecklist_treewidget.cpp index 0244ebc8d..cb96ef7f6 100644 --- a/cockatrice/src/remotedecklist_treewidget.cpp +++ b/cockatrice/src/remotedecklist_treewidget.cpp @@ -2,319 +2,339 @@ #include #include #include "remotedecklist_treewidget.h" -#include "protocol_items.h" #include "abstractclient.h" +#include "pending_command.h" +#include "pb/command_deck_list.pb.h" +#include "pb/response_deck_list.pb.h" +#include "pb/serverinfo_deckstorage.pb.h" + RemoteDeckList_TreeModel::DirectoryNode::DirectoryNode(const QString &_name, RemoteDeckList_TreeModel::DirectoryNode *_parent) - : RemoteDeckList_TreeModel::Node(_name, _parent) + : RemoteDeckList_TreeModel::Node(_name, _parent) { } RemoteDeckList_TreeModel::DirectoryNode::~DirectoryNode() { - clearTree(); + clearTree(); } void RemoteDeckList_TreeModel::DirectoryNode::clearTree() { - for (int i = 0; i < size(); ++i) - delete at(i); - clear(); + for (int i = 0; i < size(); ++i) + delete at(i); + clear(); } QString RemoteDeckList_TreeModel::DirectoryNode::getPath() const { - if (parent) { - QString parentPath = parent->getPath(); - if (parentPath.isEmpty()) - return name; - else - return parentPath + "/" + name; - } else - return name; + if (parent) { + QString parentPath = parent->getPath(); + if (parentPath.isEmpty()) + return name; + else + return parentPath + "/" + name; + } else + return name; } RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeModel::DirectoryNode::getNodeByPath(QStringList path) { - QString pathItem; - if (parent) { - if (path.isEmpty()) - return this; - pathItem = path.takeFirst(); - if (pathItem.isEmpty() && name.isEmpty()) - return this; - } - - for (int i = 0; i < size(); ++i) { - DirectoryNode *node = dynamic_cast(at(i)); - if (!node) - continue; - if (node->getName() == pathItem) - return node->getNodeByPath(path); - } - return 0; + QString pathItem; + if (parent) { + if (path.isEmpty()) + return this; + pathItem = path.takeFirst(); + if (pathItem.isEmpty() && name.isEmpty()) + return this; + } + + for (int i = 0; i < size(); ++i) { + DirectoryNode *node = dynamic_cast(at(i)); + if (!node) + continue; + if (node->getName() == pathItem) + return node->getNodeByPath(path); + } + return 0; } RemoteDeckList_TreeModel::FileNode *RemoteDeckList_TreeModel::DirectoryNode::getNodeById(int id) const { - for (int i = 0; i < size(); ++i) { - DirectoryNode *node = dynamic_cast(at(i)); - if (node) { - FileNode *result = node->getNodeById(id); - if (result) - return result; - } else { - FileNode *file = dynamic_cast(at(i)); - if (file->getId() == id) - return file; - } - } - return 0; + for (int i = 0; i < size(); ++i) { + DirectoryNode *node = dynamic_cast(at(i)); + if (node) { + FileNode *result = node->getNodeById(id); + if (result) + return result; + } else { + FileNode *file = dynamic_cast(at(i)); + if (file->getId() == id) + return file; + } + } + return 0; } RemoteDeckList_TreeModel::RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent) - : QAbstractItemModel(parent), client(_client) + : QAbstractItemModel(parent), client(_client) { - QFileIconProvider fip; - dirIcon = fip.icon(QFileIconProvider::Folder); - fileIcon = fip.icon(QFileIconProvider::File); + QFileIconProvider fip; + dirIcon = fip.icon(QFileIconProvider::Folder); + fileIcon = fip.icon(QFileIconProvider::File); - root = new DirectoryNode; - refreshTree(); + root = new DirectoryNode; + refreshTree(); } RemoteDeckList_TreeModel::~RemoteDeckList_TreeModel() { - delete root; + delete root; } int RemoteDeckList_TreeModel::rowCount(const QModelIndex &parent) const { - DirectoryNode *node = getNode(parent); - if (node) - return node->size(); - else - return 0; + DirectoryNode *node = getNode(parent); + if (node) + return node->size(); + else + return 0; } int RemoteDeckList_TreeModel::columnCount(const QModelIndex &/*parent*/) const { - return 3; + return 3; } QVariant RemoteDeckList_TreeModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - if (index.column() >= 3) - return QVariant(); + if (!index.isValid()) + return QVariant(); + if (index.column() >= 3) + return QVariant(); - Node *temp = static_cast(index.internalPointer()); - FileNode *file = dynamic_cast(temp); - if (!file) { - DirectoryNode *node = dynamic_cast(temp); - switch (role) { - case Qt::DisplayRole: { - switch (index.column()) { - case 0: return node->getName(); - default: - return QVariant(); - } - } - case Qt::DecorationRole: - return index.column() == 0 ? dirIcon : QVariant(); - default: return QVariant(); - } - } else { - switch (role) { - case Qt::DisplayRole: { - switch (index.column()) { - case 0: return file->getName(); - case 1: return file->getId(); - case 2: return file->getUploadTime(); - default: - return QVariant(); - } - } - case Qt::DecorationRole: - return index.column() == 0 ? fileIcon : QVariant(); - case Qt::TextAlignmentRole: - return index.column() == 1 ? Qt::AlignRight : Qt::AlignLeft; - default: return QVariant(); - } - } + Node *temp = static_cast(index.internalPointer()); + FileNode *file = dynamic_cast(temp); + if (!file) { + DirectoryNode *node = dynamic_cast(temp); + switch (role) { + case Qt::DisplayRole: { + switch (index.column()) { + case 0: return node->getName(); + default: + return QVariant(); + } + } + case Qt::DecorationRole: + return index.column() == 0 ? dirIcon : QVariant(); + default: return QVariant(); + } + } else { + switch (role) { + case Qt::DisplayRole: { + switch (index.column()) { + case 0: return file->getName(); + case 1: return file->getId(); + case 2: return file->getUploadTime(); + default: + return QVariant(); + } + } + case Qt::DecorationRole: + return index.column() == 0 ? fileIcon : QVariant(); + case Qt::TextAlignmentRole: + return index.column() == 1 ? Qt::AlignRight : Qt::AlignLeft; + default: return QVariant(); + } + } } QVariant RemoteDeckList_TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation != Qt::Horizontal) - return QVariant(); - switch (role) { - case Qt::TextAlignmentRole: - return section == 1 ? Qt::AlignRight : Qt::AlignLeft; - case Qt::DisplayRole: { - switch (section) { - case 0: return tr("Name"); - case 1: return tr("ID"); - case 2: return tr("Upload time"); - default: return QVariant(); - } - } - default: return QVariant(); - } + if (orientation != Qt::Horizontal) + return QVariant(); + switch (role) { + case Qt::TextAlignmentRole: + return section == 1 ? Qt::AlignRight : Qt::AlignLeft; + case Qt::DisplayRole: { + switch (section) { + case 0: return tr("Name"); + case 1: return tr("ID"); + case 2: return tr("Upload time"); + default: return QVariant(); + } + } + default: return QVariant(); + } } QModelIndex RemoteDeckList_TreeModel::index(int row, int column, const QModelIndex &parent) const { - if (!hasIndex(row, column, parent)) - return QModelIndex(); + if (!hasIndex(row, column, parent)) + return QModelIndex(); - DirectoryNode *parentNode = getNode(parent); - if (row >= parentNode->size()) - return QModelIndex(); + DirectoryNode *parentNode = getNode(parent); + if (row >= parentNode->size()) + return QModelIndex(); - return createIndex(row, column, parentNode->at(row)); + return createIndex(row, column, parentNode->at(row)); } QModelIndex RemoteDeckList_TreeModel::parent(const QModelIndex &ind) const { - if (!ind.isValid()) - return QModelIndex(); + if (!ind.isValid()) + return QModelIndex(); - return nodeToIndex(static_cast(ind.internalPointer())->getParent()); + return nodeToIndex(static_cast(ind.internalPointer())->getParent()); } Qt::ItemFlags RemoteDeckList_TreeModel::flags(const QModelIndex &index) const { - if (!index.isValid()) - return 0; + if (!index.isValid()) + return 0; - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } QModelIndex RemoteDeckList_TreeModel::nodeToIndex(Node *node) const { - if (node == root) - return QModelIndex(); - return createIndex(node->getParent()->indexOf(node), 0, node); + if (node == root) + return QModelIndex(); + return createIndex(node->getParent()->indexOf(node), 0, node); } -void RemoteDeckList_TreeModel::addFileToTree(DeckList_File *file, DirectoryNode *parent) +void RemoteDeckList_TreeModel::addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, DirectoryNode *parent) { - beginInsertRows(nodeToIndex(parent), parent->size(), parent->size()); - parent->append(new FileNode(file->getName(), file->getId(), file->getUploadTime(), parent)); - endInsertRows(); + const ServerInfo_DeckStorage_File &fileInfo = file.file(); + QDateTime time; + time.setTime_t(fileInfo.creation_time()); + + beginInsertRows(nodeToIndex(parent), parent->size(), parent->size()); + parent->append(new FileNode(QString::fromStdString(file.name()), file.id(), time, parent)); + endInsertRows(); } -void RemoteDeckList_TreeModel::addFolderToTree(DeckList_Directory *folder, DirectoryNode *parent) +void RemoteDeckList_TreeModel::addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, DirectoryNode *parent) { - addFolderToTree(folder->getName(), folder->getTreeItems(), parent); + DirectoryNode *newItem = addNamedFolderToTree(QString::fromStdString(folder.name()), parent); + const ServerInfo_DeckStorage_Folder &folderInfo = folder.folder(); + const int folderItemsSize = folderInfo.items_size(); + for (int i = 0; i < folderItemsSize; ++i) { + const ServerInfo_DeckStorage_TreeItem &subItem = folderInfo.items(i); + if (subItem.has_folder()) + addFolderToTree(subItem, newItem); + else + addFileToTree(subItem, newItem); + } } -void RemoteDeckList_TreeModel::addFolderToTree(const QString &name, const QList &folderItems, DirectoryNode *parent) +RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeModel::addNamedFolderToTree(const QString &name, DirectoryNode *parent) { - DirectoryNode *newItem = new DirectoryNode(name, parent); - beginInsertRows(nodeToIndex(parent), parent->size(), parent->size()); - parent->append(newItem); - endInsertRows(); - - for (int i = 0; i < folderItems.size(); ++i) { - DeckList_Directory *subFolder = dynamic_cast(folderItems[i]); - if (subFolder) - addFolderToTree(subFolder, newItem); - else - addFileToTree(dynamic_cast(folderItems[i]), newItem); - } + DirectoryNode *newItem = new DirectoryNode(name, parent); + beginInsertRows(nodeToIndex(parent), parent->size(), parent->size()); + parent->append(newItem); + endInsertRows(); + return newItem; } void RemoteDeckList_TreeModel::removeNode(RemoteDeckList_TreeModel::Node *node) { - int ind = node->getParent()->indexOf(node); - beginRemoveRows(nodeToIndex(node->getParent()), ind, ind); - node->getParent()->removeAt(ind); - endRemoveRows(); - delete node; + int ind = node->getParent()->indexOf(node); + beginRemoveRows(nodeToIndex(node->getParent()), ind, ind); + node->getParent()->removeAt(ind); + endRemoveRows(); + delete node; } void RemoteDeckList_TreeModel::refreshTree() { - Command_DeckList *command = new Command_DeckList; - connect(command, SIGNAL(finished(ProtocolResponse *)), this, SLOT(deckListFinished(ProtocolResponse *))); - client->sendCommand(command); + PendingCommand *pend = client->prepareSessionCommand(Command_DeckList()); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deckListFinished(const Response &))); + + client->sendCommand(pend); } -void RemoteDeckList_TreeModel::deckListFinished(ProtocolResponse *r) +void RemoteDeckList_TreeModel::deckListFinished(const Response &r) { - Response_DeckList *resp = qobject_cast(r); - if (!resp) - return; + const Response_DeckList &resp = r.GetExtension(Response_DeckList::ext); - root->clearTree(); - reset(); - - addFolderToTree(resp->getRoot(), root); - emit treeRefreshed(); + beginResetModel(); + + root->clearTree(); + + endResetModel(); + + ServerInfo_DeckStorage_TreeItem tempRoot; + tempRoot.set_id(0); + tempRoot.mutable_folder()->CopyFrom(resp.root()); + addFolderToTree(tempRoot, root); + + emit treeRefreshed(); } RemoteDeckList_TreeWidget::RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent) - : QTreeView(parent) + : QTreeView(parent) { - treeModel = new RemoteDeckList_TreeModel(_client, this); - proxyModel = new QSortFilterProxyModel(this); - proxyModel->setSourceModel(treeModel); - proxyModel->setDynamicSortFilter(true); - proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - setModel(proxyModel); - connect(treeModel, SIGNAL(treeRefreshed()), this, SLOT(expandAll())); + treeModel = new RemoteDeckList_TreeModel(_client, this); + proxyModel = new QSortFilterProxyModel(this); + proxyModel->setSourceModel(treeModel); + proxyModel->setDynamicSortFilter(true); + proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + setModel(proxyModel); + connect(treeModel, SIGNAL(treeRefreshed()), this, SLOT(expandAll())); - header()->setResizeMode(QHeaderView::ResizeToContents); - setUniformRowHeights(true); - setSortingEnabled(true); - proxyModel->sort(0, Qt::AscendingOrder); - header()->setSortIndicator(0, Qt::AscendingOrder); +#if QT_VERSION < 0x050000 + header()->setResizeMode(QHeaderView::ResizeToContents); +#else + header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#endif + setUniformRowHeights(true); + setSortingEnabled(true); + proxyModel->sort(0, Qt::AscendingOrder); + header()->setSortIndicator(0, Qt::AscendingOrder); } RemoteDeckList_TreeModel::Node *RemoteDeckList_TreeWidget::getNode(const QModelIndex &ind) const { - return treeModel->getNode(proxyModel->mapToSource(ind)); + return treeModel->getNode(proxyModel->mapToSource(ind)); } RemoteDeckList_TreeModel::Node *RemoteDeckList_TreeWidget::getCurrentItem() const { - return getNode(selectionModel()->currentIndex()); + return getNode(selectionModel()->currentIndex()); } RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeWidget::getNodeByPath(const QString &path) const { - return treeModel->getRoot()->getNodeByPath(path.split("/")); + return treeModel->getRoot()->getNodeByPath(path.split("/")); } RemoteDeckList_TreeModel::FileNode *RemoteDeckList_TreeWidget::getNodeById(int id) const { - return treeModel->getRoot()->getNodeById(id); + return treeModel->getRoot()->getNodeById(id); } -void RemoteDeckList_TreeWidget::addFileToTree(DeckList_File *file, RemoteDeckList_TreeModel::DirectoryNode *parent) +void RemoteDeckList_TreeWidget::addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, RemoteDeckList_TreeModel::DirectoryNode *parent) { - treeModel->addFileToTree(file, parent); + treeModel->addFileToTree(file, parent); } -void RemoteDeckList_TreeWidget::addFolderToTree(DeckList_Directory *folder, RemoteDeckList_TreeModel::DirectoryNode *parent) +void RemoteDeckList_TreeWidget::addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, RemoteDeckList_TreeModel::DirectoryNode *parent) { - treeModel->addFolderToTree(folder, parent); + treeModel->addFolderToTree(folder, parent); } void RemoteDeckList_TreeWidget::addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent) { - treeModel->addFolderToTree(name, QList(), parent); + treeModel->addNamedFolderToTree(name, parent); } void RemoteDeckList_TreeWidget::removeNode(RemoteDeckList_TreeModel::Node *node) { - treeModel->removeNode(node); + treeModel->removeNode(node); } void RemoteDeckList_TreeWidget::refreshTree() { - treeModel->refreshTree(); + treeModel->refreshTree(); } diff --git a/cockatrice/src/remotedecklist_treewidget.h b/cockatrice/src/remotedecklist_treewidget.h index 308d4bdba..ecb2ca182 100644 --- a/cockatrice/src/remotedecklist_treewidget.h +++ b/cockatrice/src/remotedecklist_treewidget.h @@ -5,100 +5,98 @@ #include #include -class ProtocolResponse; +class Response; class AbstractClient; class QSortFilterProxyModel; -class DeckList_File; -class DeckList_Directory; -class DeckList_TreeItem; +class ServerInfo_DeckStorage_TreeItem; class RemoteDeckList_TreeModel : public QAbstractItemModel { - Q_OBJECT + Q_OBJECT public: - class DirectoryNode; - class FileNode; - class Node { - protected: - DirectoryNode *parent; - QString name; - public: - Node(const QString &_name, DirectoryNode *_parent = 0) - : parent(_parent), name(_name) { } - virtual ~Node() { }; - DirectoryNode *getParent() const { return parent; } - QString getName() const { return name; } - }; - class DirectoryNode : public Node, public QList { - public: - DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = 0); - ~DirectoryNode(); - void clearTree(); - QString getPath() const; - DirectoryNode *getNodeByPath(QStringList path); - FileNode *getNodeById(int id) const; - }; - class FileNode : public Node { - private: - int id; - QDateTime uploadTime; - public: - FileNode(const QString &_name, int _id, const QDateTime &_uploadTime, DirectoryNode *_parent = 0) - : Node(_name, _parent), id(_id), uploadTime(_uploadTime) { } - int getId() const { return id; } - QDateTime getUploadTime() const { return uploadTime; } - }; - - template T getNode(const QModelIndex &index) const - { - if (!index.isValid()) - return dynamic_cast(root); - return dynamic_cast(static_cast(index.internalPointer())); - } + class DirectoryNode; + class FileNode; + class Node { + protected: + DirectoryNode *parent; + QString name; + public: + Node(const QString &_name, DirectoryNode *_parent = 0) + : parent(_parent), name(_name) { } + virtual ~Node() { }; + DirectoryNode *getParent() const { return parent; } + QString getName() const { return name; } + }; + class DirectoryNode : public Node, public QList { + public: + DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = 0); + ~DirectoryNode(); + void clearTree(); + QString getPath() const; + DirectoryNode *getNodeByPath(QStringList path); + FileNode *getNodeById(int id) const; + }; + class FileNode : public Node { + private: + int id; + QDateTime uploadTime; + public: + FileNode(const QString &_name, int _id, const QDateTime &_uploadTime, DirectoryNode *_parent = 0) + : Node(_name, _parent), id(_id), uploadTime(_uploadTime) { } + int getId() const { return id; } + QDateTime getUploadTime() const { return uploadTime; } + }; + + template T getNode(const QModelIndex &index) const + { + if (!index.isValid()) + return dynamic_cast(root); + return dynamic_cast(static_cast(index.internalPointer())); + } private: - AbstractClient *client; - DirectoryNode *root; - - QIcon fileIcon, dirIcon; - - QModelIndex nodeToIndex(Node *node) const; + AbstractClient *client; + DirectoryNode *root; + + QIcon fileIcon, dirIcon; + + QModelIndex nodeToIndex(Node *node) const; signals: - void treeRefreshed(); + void treeRefreshed(); private slots: - void deckListFinished(ProtocolResponse *r); + void deckListFinished(const Response &r); public: - RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = 0); - ~RemoteDeckList_TreeModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex &index) const; - Qt::ItemFlags flags(const QModelIndex &index) const; + RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = 0); + ~RemoteDeckList_TreeModel(); + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + Qt::ItemFlags flags(const QModelIndex &index) const; - DirectoryNode *getRoot() const { return root; } - void addFileToTree(DeckList_File *file, DirectoryNode *parent); - void addFolderToTree(DeckList_Directory *folder, DirectoryNode *parent); - void addFolderToTree(const QString &name, const QList &folderItems, DirectoryNode *parent); - void removeNode(Node *node); - void refreshTree(); + DirectoryNode *getRoot() const { return root; } + void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, DirectoryNode *parent); + void addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, DirectoryNode *parent); + DirectoryNode *addNamedFolderToTree(const QString &name, DirectoryNode *parent); + void removeNode(Node *node); + void refreshTree(); }; class RemoteDeckList_TreeWidget : public QTreeView { private: - RemoteDeckList_TreeModel *treeModel; - QSortFilterProxyModel *proxyModel; + RemoteDeckList_TreeModel *treeModel; + QSortFilterProxyModel *proxyModel; public: - RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = 0); - RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const; - RemoteDeckList_TreeModel::Node *getCurrentItem() const; - RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const; - RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const; - void addFileToTree(DeckList_File *file, RemoteDeckList_TreeModel::DirectoryNode *parent); - void addFolderToTree(DeckList_Directory *folder, RemoteDeckList_TreeModel::DirectoryNode *parent); - void addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent); - void removeNode(RemoteDeckList_TreeModel::Node *node); - void refreshTree(); + RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = 0); + RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const; + RemoteDeckList_TreeModel::Node *getCurrentItem() const; + RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const; + RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const; + void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, RemoteDeckList_TreeModel::DirectoryNode *parent); + void addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, RemoteDeckList_TreeModel::DirectoryNode *parent); + void addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent); + void removeNode(RemoteDeckList_TreeModel::Node *node); + void refreshTree(); }; #endif diff --git a/cockatrice/src/remotereplaylist_treewidget.cpp b/cockatrice/src/remotereplaylist_treewidget.cpp new file mode 100644 index 000000000..5357596f6 --- /dev/null +++ b/cockatrice/src/remotereplaylist_treewidget.cpp @@ -0,0 +1,301 @@ +#include +#include +#include +#include "remotereplaylist_treewidget.h" +#include "abstractclient.h" + +#include "pending_command.h" +#include "pb/command_replay_list.pb.h" +#include "pb/response_replay_list.pb.h" +#include "pb/serverinfo_replay.pb.h" + +const int RemoteReplayList_TreeModel::numberOfColumns = 6; + +RemoteReplayList_TreeModel::MatchNode::MatchNode(const ServerInfo_ReplayMatch &_matchInfo) + : RemoteReplayList_TreeModel::Node(QString::fromStdString(_matchInfo.game_name())), matchInfo(_matchInfo) +{ + for (int i = 0; i < matchInfo.replay_list_size(); ++i) + append(new ReplayNode(matchInfo.replay_list(i), this)); +} + +RemoteReplayList_TreeModel::MatchNode::~MatchNode() +{ + for (int i = 0; i < size(); ++i) + delete at(i); +} + +void RemoteReplayList_TreeModel::MatchNode::updateMatchInfo(const ServerInfo_ReplayMatch &_matchInfo) +{ + matchInfo.MergeFrom(_matchInfo); +} + +RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent) + : QAbstractItemModel(parent), client(_client) +{ + QFileIconProvider fip; + dirIcon = fip.icon(QFileIconProvider::Folder); + fileIcon = fip.icon(QFileIconProvider::File); + lockIcon = QIcon(":/resources/lock.svg"); + + refreshTree(); +} + +RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel() +{ + clearTree(); +} + +int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return replayMatches.size(); + + MatchNode *matchNode = dynamic_cast(static_cast(parent.internalPointer())); + if (matchNode) + return matchNode->size(); + else + return 0; +} + +QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.column() >= numberOfColumns) + return QVariant(); + + ReplayNode *replayNode = dynamic_cast(static_cast(index.internalPointer())); + if (replayNode) { + const ServerInfo_Replay &replayInfo = replayNode->getReplayInfo(); + switch (role) { + case Qt::TextAlignmentRole: + return index.column() == 0 ? Qt::AlignRight : Qt::AlignLeft; + case Qt::DisplayRole: { + switch (index.column()) { + case 0: return replayInfo.replay_id(); + case 1: return QString::fromStdString(replayInfo.replay_name()); + case 5: return replayInfo.duration(); + default: return QVariant(); + } + } + case Qt::DecorationRole: + return index.column() == 0 ? fileIcon : QVariant(); + } + } else { + MatchNode *matchNode = dynamic_cast(static_cast(index.internalPointer())); + const ServerInfo_ReplayMatch &matchInfo = matchNode->getMatchInfo(); + switch (role) { + case Qt::TextAlignmentRole: + switch (index.column()) { + case 0: + case 5: + return Qt::AlignRight; + default: + return Qt::AlignLeft; + } + case Qt::DisplayRole: { + switch (index.column()) { + case 0: return matchInfo.game_id(); + case 1: return QString::fromStdString(matchInfo.game_name()); + case 2: { + QStringList playerList; + for (int i = 0; i < matchInfo.player_names_size(); ++i) + playerList.append(QString::fromStdString(matchInfo.player_names(i))); + return playerList.join(", "); + } + case 4: return QDateTime::fromTime_t(matchInfo.time_started()); + case 5: return matchInfo.length(); + default: return QVariant(); + } + } + case Qt::DecorationRole: + switch (index.column()) { + case 0: return dirIcon; + case 3: return matchInfo.do_not_hide() ? lockIcon : QVariant(); + default: return QVariant(); + } + } + } + return QVariant(); +} + +QVariant RemoteReplayList_TreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal) + return QVariant(); + switch (role) { + case Qt::TextAlignmentRole: + switch (section) { + case 0: + case 5: + return Qt::AlignRight; + default: + return Qt::AlignLeft; + } + case Qt::DisplayRole: { + switch (section) { + case 0: return tr("ID"); + case 1: return tr("Name"); + case 2: return tr("Players"); + case 3: return tr("Keep"); + case 4: return tr("Time started"); + case 5: return tr("Duration (sec)"); + default: return QVariant(); + } + } + default: return QVariant(); + } +} + +QModelIndex RemoteReplayList_TreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + MatchNode *matchNode = dynamic_cast(static_cast(parent.internalPointer())); + if (matchNode) { + if (row >= matchNode->size()) + return QModelIndex(); + return createIndex(row, column, (void *) matchNode->at(row)); + } else { + if (row >= replayMatches.size()) + return QModelIndex(); + return createIndex(row, column, (void *) replayMatches[row]); + } +} + +QModelIndex RemoteReplayList_TreeModel::parent(const QModelIndex &ind) const +{ + MatchNode const *matchNode = dynamic_cast(static_cast(ind.internalPointer())); + if (matchNode) + return QModelIndex(); + else { + ReplayNode *replayNode = dynamic_cast(static_cast(ind.internalPointer())); + return createIndex(replayNode->getParent()->indexOf(replayNode), 0, replayNode); + } +} + +Qt::ItemFlags RemoteReplayList_TreeModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +ServerInfo_Replay const* RemoteReplayList_TreeModel::getReplay(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + ReplayNode *node = dynamic_cast(static_cast(index.internalPointer())); + if (!node) + return 0; + return &node->getReplayInfo(); +} + +ServerInfo_ReplayMatch const* RemoteReplayList_TreeModel::getReplayMatch(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + MatchNode *node = dynamic_cast(static_cast(index.internalPointer())); + if (!node) { + ReplayNode *node = dynamic_cast(static_cast(index.internalPointer())); + if (!node) + return 0; + return &node->getParent()->getMatchInfo(); + } else + return &node->getMatchInfo(); +} + +void RemoteReplayList_TreeModel::clearTree() +{ + for (int i = 0; i < replayMatches.size(); ++i) + delete replayMatches[i]; + replayMatches.clear(); +} + +void RemoteReplayList_TreeModel::refreshTree() +{ + PendingCommand *pend = client->prepareSessionCommand(Command_ReplayList()); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(replayListFinished(const Response &))); + + client->sendCommand(pend); +} + +void RemoteReplayList_TreeModel::addMatchInfo(const ServerInfo_ReplayMatch &matchInfo) +{ + beginInsertRows(QModelIndex(), replayMatches.size(), replayMatches.size()); + replayMatches.append(new MatchNode(matchInfo)); + endInsertRows(); + + emit treeRefreshed(); +} + +void RemoteReplayList_TreeModel::updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo) +{ + for (int i = 0; i < replayMatches.size(); ++i) + if (replayMatches[i]->getMatchInfo().game_id() == gameId) { + replayMatches[i]->updateMatchInfo(matchInfo); + emit dataChanged(createIndex(i, 0, (void *) replayMatches[i]), createIndex(i, numberOfColumns - 1, (void *) replayMatches[i])); + break; + } +} + +void RemoteReplayList_TreeModel::removeMatchInfo(int gameId) +{ + for (int i = 0; i < replayMatches.size(); ++i) + if (replayMatches[i]->getMatchInfo().game_id() == gameId) { + beginRemoveRows(QModelIndex(), i, i); + replayMatches.removeAt(i); + endRemoveRows(); + break; + } +} + +void RemoteReplayList_TreeModel::replayListFinished(const Response &r) +{ + const Response_ReplayList &resp = r.GetExtension(Response_ReplayList::ext); + + beginResetModel(); + clearTree(); + + for (int i = 0; i < resp.match_list_size(); ++i) + replayMatches.append(new MatchNode(resp.match_list(i))); + + endResetModel(); + emit treeRefreshed(); +} + +RemoteReplayList_TreeWidget::RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent) + : QTreeView(parent) +{ + treeModel = new RemoteReplayList_TreeModel(_client, this); + proxyModel = new QSortFilterProxyModel(this); + proxyModel->setSourceModel(treeModel); + proxyModel->setDynamicSortFilter(true); + proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + setModel(proxyModel); + +#if QT_VERSION < 0x050000 + header()->setResizeMode(QHeaderView::ResizeToContents); +#else + header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#endif + header()->setStretchLastSection(false); + setUniformRowHeights(true); + setSortingEnabled(true); + proxyModel->sort(0, Qt::AscendingOrder); + header()->setSortIndicator(0, Qt::AscendingOrder); +} + +ServerInfo_Replay const *RemoteReplayList_TreeWidget::getCurrentReplay() const +{ + return treeModel->getReplay(proxyModel->mapToSource(selectionModel()->currentIndex())); +} + +ServerInfo_ReplayMatch const *RemoteReplayList_TreeWidget::getCurrentReplayMatch() const +{ + return treeModel->getReplayMatch(proxyModel->mapToSource(selectionModel()->currentIndex())); +} diff --git a/cockatrice/src/remotereplaylist_treewidget.h b/cockatrice/src/remotereplaylist_treewidget.h new file mode 100644 index 000000000..986e2fa4c --- /dev/null +++ b/cockatrice/src/remotereplaylist_treewidget.h @@ -0,0 +1,93 @@ +#ifndef REMOTEREPLAYLIST_TREEWIDGET_H +#define REMOTEREPLAYLIST_TREEWIDGET_H + +#include +#include +#include +#include "pb/serverinfo_replay.pb.h" +#include "pb/serverinfo_replay_match.pb.h" + +class Response; +class AbstractClient; +class QSortFilterProxyModel; + +class RemoteReplayList_TreeModel : public QAbstractItemModel { + Q_OBJECT +private: + class MatchNode; + class ReplayNode; + class Node { + protected: + QString name; + public: + Node(const QString &_name) + : name(_name) { } + virtual ~Node() { }; + QString getName() const { return name; } + }; + class MatchNode : public Node, public QList { + private: + ServerInfo_ReplayMatch matchInfo; + public: + MatchNode(const ServerInfo_ReplayMatch &_matchInfo); + ~MatchNode(); + void clearTree(); + const ServerInfo_ReplayMatch &getMatchInfo() { return matchInfo; } + void updateMatchInfo(const ServerInfo_ReplayMatch &_matchInfo); + }; + class ReplayNode : public Node { + private: + MatchNode *parent; + ServerInfo_Replay replayInfo; + public: + ReplayNode(const ServerInfo_Replay &_replayInfo, MatchNode *_parent) + : Node(QString::fromStdString(_replayInfo.replay_name())), parent(_parent), replayInfo(_replayInfo) { } + MatchNode *getParent() const { return parent; } + const ServerInfo_Replay &getReplayInfo() { return replayInfo; } + }; + + AbstractClient *client; + QList replayMatches; + + QIcon dirIcon, fileIcon, lockIcon; + void clearTree(); + + static const int numberOfColumns; +signals: + void treeRefreshed(); +private slots: + void replayListFinished(const Response &r); +public: + RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = 0); + ~RemoteReplayList_TreeModel(); + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return numberOfColumns; } + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + void refreshTree(); + ServerInfo_Replay const* getReplay(const QModelIndex &index) const; + ServerInfo_ReplayMatch const* getReplayMatch(const QModelIndex &index) const; + void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo); + void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo); + void removeMatchInfo(int gameId); +}; + +class RemoteReplayList_TreeWidget : public QTreeView { +private: + RemoteReplayList_TreeModel *treeModel; + QSortFilterProxyModel *proxyModel; + ServerInfo_Replay const *getNode(const QModelIndex &ind) const; +public: + RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = 0); + ServerInfo_Replay const *getCurrentReplay() const; + ServerInfo_ReplayMatch const *getCurrentReplayMatch() const; + void refreshTree(); + void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo) { treeModel->addMatchInfo(matchInfo); } + void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo) { treeModel->updateMatchInfo(gameId, matchInfo); } + void removeMatchInfo(int gameId) { treeModel->removeMatchInfo(gameId); } +}; + +#endif diff --git a/cockatrice/src/replay_timeline_widget.cpp b/cockatrice/src/replay_timeline_widget.cpp new file mode 100644 index 000000000..fb3efc1dc --- /dev/null +++ b/cockatrice/src/replay_timeline_widget.cpp @@ -0,0 +1,104 @@ +#include "replay_timeline_widget.h" +#include +#include +#include +#include +#ifdef _WIN32 +#include "round.h" +#endif /* _WIN32 */ + +ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) + : QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentTime(0), currentEvent(0) +{ + replayTimer = new QTimer(this); + connect(replayTimer, SIGNAL(timeout()), this, SLOT(replayTimerTimeout())); +} + +const int ReplayTimelineWidget::binLength = 5000; + +void ReplayTimelineWidget::setTimeline(const QList &_replayTimeline) +{ + replayTimeline = _replayTimeline; + histogram.clear(); + int binEndTime = binLength - 1; + int binValue = 0; + for (int i = 0; i < replayTimeline.size(); ++i) { + if (replayTimeline[i] > binEndTime) { + histogram.append(binValue); + if (binValue > maxBinValue) + maxBinValue = binValue; + while (replayTimeline[i] > binEndTime + binLength) { + histogram.append(0); + binEndTime += binLength; + } + binValue = 1; + binEndTime += binLength; + } else + ++binValue; + } + histogram.append(binValue); + if (!replayTimeline.isEmpty()) + maxTime = replayTimeline.last(); + + update(); +} + +void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */) +{ + QPainter painter(this); + painter.drawRect(0, 0, width() - 1, height() - 1); + + qreal binWidth = (qreal) width() / histogram.size(); + QPainterPath path; + path.moveTo(0, height() - 1); + for (int i = 0; i < histogram.size(); ++i) + path.lineTo(round(i * binWidth), (height() - 1) * (1.0 - (qreal) histogram[i] / maxBinValue)); + path.lineTo(width() - 1, height() - 1); + path.lineTo(0, height() - 1); + painter.fillPath(path, Qt::black); + + const QColor barColor = QColor::fromHsv(120, 255, 255, 100); + painter.fillRect(0, 0, (width() - 1) * currentTime / maxTime, height() - 1, barColor); +} + +QSize ReplayTimelineWidget::sizeHint() const +{ + return QSize(-1, 50); +} + +QSize ReplayTimelineWidget::minimumSizeHint() const +{ + return QSize(400, 50); +} + +void ReplayTimelineWidget::replayTimerTimeout() +{ + currentTime += 200; + while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentTime)) { + emit processNextEvent(); + ++currentEvent; + } + if (currentEvent == replayTimeline.size()) { + emit replayFinished(); + replayTimer->stop(); + } + + if (!(currentTime % 1000)) + update(); +} + +void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor) +{ + timeScaleFactor = _timeScaleFactor; + replayTimer->setInterval(200 / timeScaleFactor); +} + +void ReplayTimelineWidget::startReplay() +{ + replayTimer->start(200 / timeScaleFactor); +} + +void ReplayTimelineWidget::stopReplay() +{ + replayTimer->stop(); +} diff --git a/cockatrice/src/replay_timeline_widget.h b/cockatrice/src/replay_timeline_widget.h new file mode 100644 index 000000000..8c6683938 --- /dev/null +++ b/cockatrice/src/replay_timeline_widget.h @@ -0,0 +1,40 @@ +#ifndef REPLAY_TIMELINE_WIDGET +#define REPLAY_TIMELINE_WIDGET + +#include +#include + +class QPaintEvent; +class QTimer; + +class ReplayTimelineWidget : public QWidget { + Q_OBJECT +signals: + void processNextEvent(); + void replayFinished(); +private: + QTimer *replayTimer; + QList replayTimeline; + QList histogram; + static const int binLength; + int maxBinValue, maxTime; + qreal timeScaleFactor; + int currentTime; + int currentEvent; +private slots: + void replayTimerTimeout(); +public: + ReplayTimelineWidget(QWidget *parent = 0); + void setTimeline(const QList &_replayTimeline); + QSize sizeHint() const; + QSize minimumSizeHint() const; + void setTimeScaleFactor(qreal _timeScaleFactor); + int getCurrentEvent() const { return currentEvent; } +public slots: + void startReplay(); + void stopReplay(); +protected: + void paintEvent(QPaintEvent *event); +}; + +#endif diff --git a/cockatrice/src/round.h b/cockatrice/src/round.h new file mode 100644 index 000000000..225da636b --- /dev/null +++ b/cockatrice/src/round.h @@ -0,0 +1,12 @@ +#ifndef MSVC_ROUND_FIX +#define MSVC_ROUND_FIX +/** + * This helper function exists only because MS VC++ 2010 does not support round() in + * . round() works with g++ and clang++ but is formally a C++11 extension. + * So this file exists for MS VC++ only. + */ +inline double round(double val) { + return floor(val + 0.5); +} + +#endif /* MSVC_ROUND_FIX */ diff --git a/cockatrice/src/selectzone.cpp b/cockatrice/src/selectzone.cpp index 729dfcc43..be966352c 100644 --- a/cockatrice/src/selectzone.cpp +++ b/cockatrice/src/selectzone.cpp @@ -1,54 +1,56 @@ #include #include "selectzone.h" #include "gamescene.h" +#include "carditem.h" #include + SelectZone::SelectZone(Player *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent, bool isView) - : CardZone(_player, _name, _hasCardAttr, _isShufflable, _contentsKnown, parent, isView) + : CardZone(_player, _name, _hasCardAttr, _isShufflable, _contentsKnown, parent, isView) { } void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if (event->buttons().testFlag(Qt::LeftButton)) { - QPointF pos = event->pos(); - if (pos.x() < 0) - pos.setX(0); - QRectF br = boundingRect(); - if (pos.x() > br.width()) - pos.setX(br.width()); - if (pos.y() < 0) - pos.setY(0); - if (pos.y() > br.height()) - pos.setY(br.height()); - - QRectF selectionRect = QRectF(selectionOrigin, pos).normalized(); - for (int i = 0; i < cards.size(); ++i) { - if (cards[i]->getAttachedTo()) - if (cards[i]->getAttachedTo()->getZone() != this) - continue; - cards[i]->setSelected(selectionRect.intersects(cards[i]->mapRectToParent(cards[i]->boundingRect()))); - } - static_cast(scene())->resizeRubberBand(deviceTransform(static_cast(scene())->getViewportTransform()).map(pos)); - event->accept(); - } + if (event->buttons().testFlag(Qt::LeftButton)) { + QPointF pos = event->pos(); + if (pos.x() < 0) + pos.setX(0); + QRectF br = boundingRect(); + if (pos.x() > br.width()) + pos.setX(br.width()); + if (pos.y() < 0) + pos.setY(0); + if (pos.y() > br.height()) + pos.setY(br.height()); + + QRectF selectionRect = QRectF(selectionOrigin, pos).normalized(); + for (int i = 0; i < cards.size(); ++i) { + if (cards[i]->getAttachedTo()) + if (cards[i]->getAttachedTo()->getZone() != this) + continue; + cards[i]->setSelected(selectionRect.intersects(cards[i]->mapRectToParent(cards[i]->boundingRect()))); + } + static_cast(scene())->resizeRubberBand(deviceTransform(static_cast(scene())->getViewportTransform()).map(pos)); + event->accept(); + } } void SelectZone::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - scene()->clearSelection(); - - selectionOrigin = event->pos(); - static_cast(scene())->startRubberBand(event->scenePos()); - event->accept(); - } else - CardZone::mousePressEvent(event); + if (event->button() == Qt::LeftButton) { + scene()->clearSelection(); + + selectionOrigin = event->pos(); + static_cast(scene())->startRubberBand(event->scenePos()); + event->accept(); + } else + CardZone::mousePressEvent(event); } void SelectZone::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - selectionOrigin = QPoint(); - static_cast(scene())->stopRubberBand(); - event->accept(); + selectionOrigin = QPoint(); + static_cast(scene())->stopRubberBand(); + event->accept(); } diff --git a/cockatrice/src/selectzone.h b/cockatrice/src/selectzone.h index 23e82d1c7..347e9a522 100644 --- a/cockatrice/src/selectzone.h +++ b/cockatrice/src/selectzone.h @@ -4,15 +4,15 @@ #include "cardzone.h" class SelectZone : public CardZone { - Q_OBJECT + Q_OBJECT private: - QPointF selectionOrigin; + QPointF selectionOrigin; protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); public: - SelectZone(Player *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent = 0, bool isView = false); + SelectZone(Player *_player, const QString &_name, bool _hasCardAttr, bool _isShufflable, bool _contentsKnown, QGraphicsItem *parent = 0, bool isView = false); }; #endif diff --git a/cockatrice/src/setsmodel.cpp b/cockatrice/src/setsmodel.cpp index f0ca77e7b..f11d73c4b 100644 --- a/cockatrice/src/setsmodel.cpp +++ b/cockatrice/src/setsmodel.cpp @@ -1,9 +1,9 @@ #include "setsmodel.h" SetsModel::SetsModel(CardDatabase *_db, QObject *parent) - : QAbstractTableModel(parent), sets(_db->getSetList()) + : QAbstractTableModel(parent), sets(_db->getSetList()) { - sets.sortByKey(); + sets.sortByKey(); } SetsModel::~SetsModel() @@ -12,82 +12,82 @@ SetsModel::~SetsModel() int SetsModel::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) - return 0; - else - return sets.size(); + if (parent.isValid()) + return 0; + else + return sets.size(); } QVariant SetsModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || (index.column() >= 2) || (index.row() >= rowCount()) || (role != Qt::DisplayRole)) - return QVariant(); + if (!index.isValid() || (index.column() >= 2) || (index.row() >= rowCount()) || (role != Qt::DisplayRole)) + return QVariant(); - CardSet *set = sets[index.row()]; - switch (index.column()) { - case 0: return set->getShortName(); - case 1: return set->getLongName(); - default: return QVariant(); - } + CardSet *set = sets[index.row()]; + switch (index.column()) { + case 0: return set->getShortName(); + case 1: return set->getLongName(); + default: return QVariant(); + } } QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int role) const { - if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) - return QVariant(); - switch (section) { - case 0: return tr("Short name"); - case 1: return tr("Long name"); - default: return QVariant(); - } + if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) + return QVariant(); + switch (section) { + case 0: return tr("Short name"); + case 1: return tr("Long name"); + default: return QVariant(); + } } Qt::ItemFlags SetsModel::flags(const QModelIndex &index) const { - Qt::ItemFlags result = QAbstractTableModel::flags(index); - return result | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + Qt::ItemFlags result = QAbstractTableModel::flags(index); + return result | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } Qt::DropActions SetsModel::supportedDropActions() const { - return Qt::MoveAction; + return Qt::MoveAction; } QMimeData *SetsModel::mimeData(const QModelIndexList &indexes) const { - if (indexes.isEmpty()) - return 0; + if (indexes.isEmpty()) + return 0; - SetsMimeData *result = new SetsMimeData(indexes[0].row()); - return qobject_cast(result); + SetsMimeData *result = new SetsMimeData(indexes[0].row()); + return qobject_cast(result); } bool SetsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) { - if (action != Qt::MoveAction) - return false; - if (row == -1) { - if (!parent.isValid()) - return false; - row = parent.row(); - } - int oldRow = qobject_cast(data)->getOldRow(); - beginRemoveRows(QModelIndex(), oldRow, oldRow); - CardSet *temp = sets.takeAt(oldRow); - endRemoveRows(); - if (oldRow < row) - row--; - beginInsertRows(QModelIndex(), row, row); - sets.insert(row, temp); - endInsertRows(); + if (action != Qt::MoveAction) + return false; + if (row == -1) { + if (!parent.isValid()) + return false; + row = parent.row(); + } + int oldRow = qobject_cast(data)->getOldRow(); + beginRemoveRows(QModelIndex(), oldRow, oldRow); + CardSet *temp = sets.takeAt(oldRow); + endRemoveRows(); + if (oldRow < row) + row--; + beginInsertRows(QModelIndex(), row, row); + sets.insert(row, temp); + endInsertRows(); - for (int i = 0; i < sets.size(); i++) - sets[i]->setSortKey(i); + for (int i = 0; i < sets.size(); i++) + sets[i]->setSortKey(i); - return true; + return true; } QStringList SetsModel::mimeTypes() const { - return QStringList() << "application/x-cockatricecardset"; + return QStringList() << "application/x-cockatricecardset"; } diff --git a/cockatrice/src/setsmodel.h b/cockatrice/src/setsmodel.h index 5d61b57c1..74d294431 100644 --- a/cockatrice/src/setsmodel.h +++ b/cockatrice/src/setsmodel.h @@ -6,32 +6,32 @@ #include "carddatabase.h" class SetsMimeData : public QMimeData { - Q_OBJECT + Q_OBJECT private: - int oldRow; + int oldRow; public: - SetsMimeData(int _oldRow) : oldRow(_oldRow) { } - int getOldRow() const { return oldRow; } - QStringList formats() const { return QStringList() << "application/x-cockatricecardset"; } + SetsMimeData(int _oldRow) : oldRow(_oldRow) { } + int getOldRow() const { return oldRow; } + QStringList formats() const { return QStringList() << "application/x-cockatricecardset"; } }; class SetsModel : public QAbstractTableModel { - Q_OBJECT + Q_OBJECT public: - SetsModel(CardDatabase *_db, QObject *parent = 0); - ~SetsModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &/*parent*/) const { return 2; } - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - Qt::DropActions supportedDropActions() const; + SetsModel(CardDatabase *_db, QObject *parent = 0); + ~SetsModel(); + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &/*parent*/) const { return 2; } + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + Qt::DropActions supportedDropActions() const; - QMimeData *mimeData(const QModelIndexList &indexes) const; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + QStringList mimeTypes() const; private: - SetList sets; + SetList sets; }; #endif diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 8c3cee479..5f504aa34 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -1,189 +1,296 @@ #include "settingscache.h" #include +#include +#include SettingsCache::SettingsCache() { - settings = new QSettings(this); - - lang = settings->value("personal/lang").toString(); - - deckPath = settings->value("paths/decks").toString(); - picsPath = settings->value("paths/pics").toString(); - cardDatabasePath = settings->value("paths/carddatabase").toString(); - - handBgPath = settings->value("zonebg/hand").toString(); - stackBgPath = settings->value("zonebg/stack").toString(); - tableBgPath = settings->value("zonebg/table").toString(); - playerBgPath = settings->value("zonebg/playerarea").toString(); - cardBackPicturePath = settings->value("paths/cardbackpicture").toString(); - - picDownload = settings->value("personal/picturedownload", true).toBool(); - doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool(); - cardInfoMinimized = settings->value("interface/cardinfominimized", 0).toInt(); - tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray(); - displayCardNames = settings->value("cards/displaycardnames", true).toBool(); - horizontalHand = settings->value("hand/horizontal", true).toBool(); - invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); - minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 5).toInt(); - tapAnimation = settings->value("cards/tapanimation", true).toBool(); - - zoneViewSortByName = settings->value("zoneview/sortbyname", true).toBool(); - zoneViewSortByType = settings->value("zoneview/sortbytype", true).toBool(); - - soundEnabled = settings->value("sound/enabled", false).toBool(); - soundPath = settings->value("sound/path").toString(); - - priceTagFeature = settings->value("deckeditor/pricetags", false).toBool(); + settings = new QSettings(this); + + lang = settings->value("personal/lang").toString(); + + deckPath = settings->value("paths/decks").toString(); + replaysPath = settings->value("paths/replays").toString(); + picsPath = settings->value("paths/pics").toString(); + cardDatabasePath = settings->value("paths/carddatabase").toString(); + tokenDatabasePath = settings->value("paths/tokendatabase").toString(); + + handBgPath = settings->value("zonebg/hand").toString(); + stackBgPath = settings->value("zonebg/stack").toString(); + tableBgPath = settings->value("zonebg/table").toString(); + playerBgPath = settings->value("zonebg/playerarea").toString(); + cardBackPicturePath = settings->value("paths/cardbackpicture").toString(); + + picDownload = settings->value("personal/picturedownload", true).toBool(); + picDownloadHq = settings->value("personal/picturedownloadhq", false).toBool(); + picUrl = settings->value("personal/picUrl", PIC_URL_DEFAULT).toString(); + picUrlHq = settings->value("personal/picUrlHq", PIC_URL_HQ_DEFAULT).toString(); + + mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray(); + notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool(); + doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool(); + playToStack = settings->value("interface/playtostack", false).toBool(); + cardInfoMinimized = settings->value("interface/cardinfominimized", 0).toInt(); + tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray(); + displayCardNames = settings->value("cards/displaycardnames", true).toBool(); + horizontalHand = settings->value("hand/horizontal", true).toBool(); + invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); + minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 5).toInt(); + tapAnimation = settings->value("cards/tapanimation", true).toBool(); + + zoneViewSortByName = settings->value("zoneview/sortbyname", true).toBool(); + zoneViewSortByType = settings->value("zoneview/sortbytype", true).toBool(); + + soundEnabled = settings->value("sound/enabled", false).toBool(); + soundPath = settings->value("sound/path").toString(); + + priceTagFeature = settings->value("deckeditor/pricetags", false).toBool(); + priceTagSource = settings->value("deckeditor/pricetagsource", 0).toInt(); + + ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool(); } void SettingsCache::setLang(const QString &_lang) { - lang = _lang; - settings->setValue("personal/lang", lang); - emit langChanged(); + lang = _lang; + settings->setValue("personal/lang", lang); + emit langChanged(); } void SettingsCache::setDeckPath(const QString &_deckPath) { - deckPath = _deckPath; - settings->setValue("paths/decks", deckPath); + deckPath = _deckPath; + settings->setValue("paths/decks", deckPath); +} + +void SettingsCache::setReplaysPath(const QString &_replaysPath) +{ + replaysPath = _replaysPath; + settings->setValue("paths/replays", replaysPath); } void SettingsCache::setPicsPath(const QString &_picsPath) { - picsPath = _picsPath; - settings->setValue("paths/pics", picsPath); - emit picsPathChanged(); + picsPath = _picsPath; + settings->setValue("paths/pics", picsPath); + emit picsPathChanged(); } void SettingsCache::setCardDatabasePath(const QString &_cardDatabasePath) { - cardDatabasePath = _cardDatabasePath; - settings->setValue("paths/carddatabase", cardDatabasePath); - emit cardDatabasePathChanged(); + cardDatabasePath = _cardDatabasePath; + settings->setValue("paths/carddatabase", cardDatabasePath); + emit cardDatabasePathChanged(); +} + +void SettingsCache::setTokenDatabasePath(const QString &_tokenDatabasePath) +{ + tokenDatabasePath = _tokenDatabasePath; + settings->setValue("paths/tokendatabase", tokenDatabasePath); + emit tokenDatabasePathChanged(); } void SettingsCache::setHandBgPath(const QString &_handBgPath) { - handBgPath = _handBgPath; - settings->setValue("zonebg/hand", handBgPath); - emit handBgPathChanged(); + handBgPath = _handBgPath; + settings->setValue("zonebg/hand", handBgPath); + emit handBgPathChanged(); } void SettingsCache::setStackBgPath(const QString &_stackBgPath) { - stackBgPath = _stackBgPath; - settings->setValue("zonebg/stack", stackBgPath); - emit stackBgPathChanged(); + stackBgPath = _stackBgPath; + settings->setValue("zonebg/stack", stackBgPath); + emit stackBgPathChanged(); } void SettingsCache::setTableBgPath(const QString &_tableBgPath) { - tableBgPath = _tableBgPath; - settings->setValue("zonebg/table", tableBgPath); - emit tableBgPathChanged(); + tableBgPath = _tableBgPath; + settings->setValue("zonebg/table", tableBgPath); + emit tableBgPathChanged(); } void SettingsCache::setPlayerBgPath(const QString &_playerBgPath) { - playerBgPath = _playerBgPath; - settings->setValue("zonebg/playerarea", playerBgPath); - emit playerBgPathChanged(); + playerBgPath = _playerBgPath; + settings->setValue("zonebg/playerarea", playerBgPath); + emit playerBgPathChanged(); } void SettingsCache::setCardBackPicturePath(const QString &_cardBackPicturePath) { - cardBackPicturePath = _cardBackPicturePath; - settings->setValue("paths/cardbackpicture", cardBackPicturePath); - emit cardBackPicturePathChanged(); + cardBackPicturePath = _cardBackPicturePath; + settings->setValue("paths/cardbackpicture", cardBackPicturePath); + emit cardBackPicturePathChanged(); } void SettingsCache::setPicDownload(int _picDownload) { - picDownload = _picDownload; - settings->setValue("personal/picturedownload", picDownload); - emit picDownloadChanged(); + picDownload = _picDownload; + settings->setValue("personal/picturedownload", picDownload); + emit picDownloadChanged(); +} + +void SettingsCache::setPicDownloadHq(int _picDownloadHq) +{ + picDownloadHq = _picDownloadHq; + settings->setValue("personal/picturedownloadhq", picDownloadHq); + emit picDownloadHqChanged(); +} + +void SettingsCache::setPicUrl(const QString &_picUrl) +{ + picUrl = _picUrl; + settings->setValue("personal/picUrl", picUrl); +} + +void SettingsCache::setPicUrlHq(const QString &_picUrlHq) +{ + picUrlHq = _picUrlHq; + settings->setValue("personal/picUrlHq", picUrlHq); +} + +void SettingsCache::setNotificationsEnabled(int _notificationsEnabled) +{ + notificationsEnabled = _notificationsEnabled; + settings->setValue("interface/notificationsenabled", notificationsEnabled); } void SettingsCache::setDoubleClickToPlay(int _doubleClickToPlay) { - doubleClickToPlay = _doubleClickToPlay; - settings->setValue("interface/doubleclicktoplay", doubleClickToPlay); + doubleClickToPlay = _doubleClickToPlay; + settings->setValue("interface/doubleclicktoplay", doubleClickToPlay); +} + +void SettingsCache::setPlayToStack(int _playToStack) +{ + playToStack = _playToStack; + settings->setValue("interface/playtostack", playToStack); } void SettingsCache::setCardInfoMinimized(int _cardInfoMinimized) { cardInfoMinimized = _cardInfoMinimized; - settings->setValue("interface/cardinfominimized", cardInfoMinimized); + settings->setValue("interface/cardinfominimized", cardInfoMinimized); } void SettingsCache::setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes) { - tabGameSplitterSizes = _tabGameSplitterSizes; - settings->setValue("interface/tabgame_splittersizes", tabGameSplitterSizes); + tabGameSplitterSizes = _tabGameSplitterSizes; + settings->setValue("interface/tabgame_splittersizes", tabGameSplitterSizes); } void SettingsCache::setDisplayCardNames(int _displayCardNames) { - displayCardNames = _displayCardNames; - settings->setValue("cards/displaycardnames", displayCardNames); - emit displayCardNamesChanged(); + displayCardNames = _displayCardNames; + settings->setValue("cards/displaycardnames", displayCardNames); + emit displayCardNamesChanged(); } void SettingsCache::setHorizontalHand(int _horizontalHand) { - horizontalHand = _horizontalHand; - settings->setValue("hand/horizontal", horizontalHand); - emit horizontalHandChanged(); + horizontalHand = _horizontalHand; + settings->setValue("hand/horizontal", horizontalHand); + emit horizontalHandChanged(); } void SettingsCache::setInvertVerticalCoordinate(int _invertVerticalCoordinate) { - invertVerticalCoordinate = _invertVerticalCoordinate; - settings->setValue("table/invert_vertical", invertVerticalCoordinate); - emit invertVerticalCoordinateChanged(); + invertVerticalCoordinate = _invertVerticalCoordinate; + settings->setValue("table/invert_vertical", invertVerticalCoordinate); + emit invertVerticalCoordinateChanged(); } void SettingsCache::setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout) { - minPlayersForMultiColumnLayout = _minPlayersForMultiColumnLayout; - settings->setValue("interface/min_players_multicolumn", minPlayersForMultiColumnLayout); - emit minPlayersForMultiColumnLayoutChanged(); + minPlayersForMultiColumnLayout = _minPlayersForMultiColumnLayout; + settings->setValue("interface/min_players_multicolumn", minPlayersForMultiColumnLayout); + emit minPlayersForMultiColumnLayoutChanged(); } void SettingsCache::setTapAnimation(int _tapAnimation) { - tapAnimation = _tapAnimation; - settings->setValue("cards/tapanimation", tapAnimation); + tapAnimation = _tapAnimation; + settings->setValue("cards/tapanimation", tapAnimation); } void SettingsCache::setZoneViewSortByName(int _zoneViewSortByName) { - zoneViewSortByName = _zoneViewSortByName; - settings->setValue("zoneview/sortbyname", zoneViewSortByName); + zoneViewSortByName = _zoneViewSortByName; + settings->setValue("zoneview/sortbyname", zoneViewSortByName); } void SettingsCache::setZoneViewSortByType(int _zoneViewSortByType) { - zoneViewSortByType = _zoneViewSortByType; - settings->setValue("zoneview/sortbytype", zoneViewSortByType); + zoneViewSortByType = _zoneViewSortByType; + settings->setValue("zoneview/sortbytype", zoneViewSortByType); } void SettingsCache::setSoundEnabled(int _soundEnabled) { - soundEnabled = _soundEnabled; - settings->setValue("sound/enabled", soundEnabled); + soundEnabled = _soundEnabled; + settings->setValue("sound/enabled", soundEnabled); + emit soundEnabledChanged(); } void SettingsCache::setSoundPath(const QString &_soundPath) { - soundPath = _soundPath; - settings->setValue("sound/path", soundPath); - emit soundPathChanged(); + soundPath = _soundPath; + settings->setValue("sound/path", soundPath); + emit soundPathChanged(); } void SettingsCache::setPriceTagFeature(int _priceTagFeature) { - priceTagFeature = _priceTagFeature; - settings->setValue("deckeditor/pricetags", priceTagFeature); + priceTagFeature = _priceTagFeature; + settings->setValue("deckeditor/pricetags", priceTagFeature); + emit priceTagFeatureChanged(priceTagFeature); +} + +void SettingsCache::setPriceTagSource(int _priceTagSource) +{ + priceTagSource = _priceTagSource; + settings->setValue("deckeditor/pricetagsource", priceTagSource); +} + +void SettingsCache::setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers) +{ + ignoreUnregisteredUsers = _ignoreUnregisteredUsers; + settings->setValue("chat/ignore_unregistered", ignoreUnregisteredUsers); + emit ignoreUnregisteredUsersChanged(); +} + +void SettingsCache::setMainWindowGeometry(const QByteArray &_mainWindowGeometry) +{ + mainWindowGeometry = _mainWindowGeometry; + settings->setValue("interface/main_window_geometry", mainWindowGeometry); +} + +void SettingsCache::copyPath(const QString &src, const QString &dst) +{ + // test source && return if inexistent + QDir dir(src); + if (! dir.exists()) + return; + // test destination && create if inexistent + QDir tmpDir(dst); + if (!tmpDir.exists()) + { + tmpDir.setPath(QDir::rootPath()); + if (!tmpDir.mkdir(dst) && !tmpDir.exists()) { + // TODO: this is probably not good. + qDebug() << "copyPath(): Failed to create dir: " << dst; + } + } + + foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QString dst_path = dst + QDir::separator() + d; + dir.mkpath(dst_path); + copyPath(src+ QDir::separator() + d, dst_path); + } + + foreach (QString f, dir.entryList(QDir::Files)) { + QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f); + } } diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index b3f1ae6c0..3b4d612d5 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -3,93 +3,130 @@ #include +#define PIC_URL_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card" +#define PIC_URL_HQ_DEFAULT "http://mtgimage.com/multiverseid/!cardid!.jpg" + class QSettings; class SettingsCache : public QObject { - Q_OBJECT + Q_OBJECT signals: - void langChanged(); - void picsPathChanged(); - void cardDatabasePathChanged(); - void handBgPathChanged(); - void stackBgPathChanged(); - void tableBgPathChanged(); - void playerBgPathChanged(); - void cardBackPicturePathChanged(); - void picDownloadChanged(); - void displayCardNamesChanged(); - void horizontalHandChanged(); - void invertVerticalCoordinateChanged(); - void minPlayersForMultiColumnLayoutChanged(); - void soundPathChanged(); + void langChanged(); + void picsPathChanged(); + void cardDatabasePathChanged(); + void tokenDatabasePathChanged(); + void handBgPathChanged(); + void stackBgPathChanged(); + void tableBgPathChanged(); + void playerBgPathChanged(); + void cardBackPicturePathChanged(); + void picDownloadChanged(); + void picDownloadHqChanged(); + void displayCardNamesChanged(); + void horizontalHandChanged(); + void invertVerticalCoordinateChanged(); + void minPlayersForMultiColumnLayoutChanged(); + void soundEnabledChanged(); + void soundPathChanged(); + void priceTagFeatureChanged(int enabled); + void ignoreUnregisteredUsersChanged(); private: - QSettings *settings; - - QString lang; - QString deckPath, picsPath, cardDatabasePath; - QString handBgPath, stackBgPath, tableBgPath, playerBgPath, cardBackPicturePath; - bool picDownload; - bool doubleClickToPlay; - int cardInfoMinimized; - QByteArray tabGameSplitterSizes; - bool displayCardNames; - bool horizontalHand; - bool invertVerticalCoordinate; - int minPlayersForMultiColumnLayout; - bool tapAnimation; - bool zoneViewSortByName, zoneViewSortByType; - bool soundEnabled; - QString soundPath; - bool priceTagFeature; + QSettings *settings; + + QByteArray mainWindowGeometry; + QString lang; + QString deckPath, replaysPath, picsPath, cardDatabasePath, tokenDatabasePath; + QString handBgPath, stackBgPath, tableBgPath, playerBgPath, cardBackPicturePath; + bool picDownload; + bool picDownloadHq; + bool notificationsEnabled; + bool doubleClickToPlay; + bool playToStack; + int cardInfoMinimized; + QByteArray tabGameSplitterSizes; + bool displayCardNames; + bool horizontalHand; + bool invertVerticalCoordinate; + int minPlayersForMultiColumnLayout; + bool tapAnimation; + bool zoneViewSortByName, zoneViewSortByType; + bool soundEnabled; + QString soundPath; + bool priceTagFeature; + int priceTagSource; + bool ignoreUnregisteredUsers; + QString picUrl; + QString picUrlHq; public: - SettingsCache(); - QString getLang() const { return lang; } - QString getDeckPath() const { return deckPath; } - QString getPicsPath() const { return picsPath; } - QString getCardDatabasePath() const { return cardDatabasePath; } - QString getHandBgPath() const { return handBgPath; } - QString getStackBgPath() const { return stackBgPath; } - QString getTableBgPath() const { return tableBgPath; } - QString getPlayerBgPath() const { return playerBgPath; } - QString getCardBackPicturePath() const { return cardBackPicturePath; } - bool getPicDownload() const { return picDownload; } - bool getDoubleClickToPlay() const { return doubleClickToPlay; } - int getCardInfoMinimized() const { return cardInfoMinimized; } - QByteArray getTabGameSplitterSizes() const { return tabGameSplitterSizes; } - bool getDisplayCardNames() const { return displayCardNames; } - bool getHorizontalHand() const { return horizontalHand; } - bool getInvertVerticalCoordinate() const { return invertVerticalCoordinate; } - int getMinPlayersForMultiColumnLayout() const { return minPlayersForMultiColumnLayout; } - bool getTapAnimation() const { return tapAnimation; } - bool getZoneViewSortByName() const { return zoneViewSortByName; } - bool getZoneViewSortByType() const { return zoneViewSortByType; } - bool getSoundEnabled() const { return soundEnabled; } - QString getSoundPath() const { return soundPath; } - bool getPriceTagFeature() const { return priceTagFeature; } + SettingsCache(); + const QByteArray &getMainWindowGeometry() const { return mainWindowGeometry; } + QString getLang() const { return lang; } + QString getDeckPath() const { return deckPath; } + QString getReplaysPath() const { return replaysPath; } + QString getPicsPath() const { return picsPath; } + QString getCardDatabasePath() const { return cardDatabasePath; } + QString getTokenDatabasePath() const { return tokenDatabasePath; } + QString getHandBgPath() const { return handBgPath; } + QString getStackBgPath() const { return stackBgPath; } + QString getTableBgPath() const { return tableBgPath; } + QString getPlayerBgPath() const { return playerBgPath; } + QString getCardBackPicturePath() const { return cardBackPicturePath; } + bool getPicDownload() const { return picDownload; } + bool getPicDownloadHq() const { return picDownloadHq; } + bool getNotificationsEnabled() const { return notificationsEnabled; } + bool getDoubleClickToPlay() const { return doubleClickToPlay; } + bool getPlayToStack() const { return playToStack; } + int getCardInfoMinimized() const { return cardInfoMinimized; } + QByteArray getTabGameSplitterSizes() const { return tabGameSplitterSizes; } + bool getDisplayCardNames() const { return displayCardNames; } + bool getHorizontalHand() const { return horizontalHand; } + bool getInvertVerticalCoordinate() const { return invertVerticalCoordinate; } + int getMinPlayersForMultiColumnLayout() const { return minPlayersForMultiColumnLayout; } + bool getTapAnimation() const { return tapAnimation; } + bool getZoneViewSortByName() const { return zoneViewSortByName; } + bool getZoneViewSortByType() const { return zoneViewSortByType; } + bool getSoundEnabled() const { return soundEnabled; } + QString getSoundPath() const { return soundPath; } + bool getPriceTagFeature() const { return priceTagFeature; } + int getPriceTagSource() const { return priceTagSource; } + bool getIgnoreUnregisteredUsers() const { return ignoreUnregisteredUsers; } + QString getPicUrl() const { return picUrl; } + QString getPicUrlHq() const { return picUrlHq; } + void copyPath(const QString &src, const QString &dst); public slots: - void setLang(const QString &_lang); - void setDeckPath(const QString &_deckPath); - void setPicsPath(const QString &_picsPath); - void setCardDatabasePath(const QString &_cardDatabasePath); - void setHandBgPath(const QString &_handBgPath); - void setStackBgPath(const QString &_stackBgPath); - void setTableBgPath(const QString &_tableBgPath); - void setPlayerBgPath(const QString &_playerBgPath); - void setCardBackPicturePath(const QString &_cardBackPicturePath); - void setPicDownload(int _picDownload); - void setDoubleClickToPlay(int _doubleClickToPlay); - void setCardInfoMinimized(int _cardInfoMinimized); - void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes); - void setDisplayCardNames(int _displayCardNames); - void setHorizontalHand(int _horizontalHand); - void setInvertVerticalCoordinate(int _invertVerticalCoordinate); - void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); - void setTapAnimation(int _tapAnimation); - void setZoneViewSortByName(int _zoneViewSortByName); - void setZoneViewSortByType(int _zoneViewSortByType); - void setSoundEnabled(int _soundEnabled); - void setSoundPath(const QString &_soundPath); - void setPriceTagFeature(int _priceTagFeature); + void setMainWindowGeometry(const QByteArray &_mainWindowGeometry); + void setLang(const QString &_lang); + void setDeckPath(const QString &_deckPath); + void setReplaysPath(const QString &_replaysPath); + void setPicsPath(const QString &_picsPath); + void setCardDatabasePath(const QString &_cardDatabasePath); + void setTokenDatabasePath(const QString &_tokenDatabasePath); + void setHandBgPath(const QString &_handBgPath); + void setStackBgPath(const QString &_stackBgPath); + void setTableBgPath(const QString &_tableBgPath); + void setPlayerBgPath(const QString &_playerBgPath); + void setCardBackPicturePath(const QString &_cardBackPicturePath); + void setPicDownload(int _picDownload); + void setPicDownloadHq(int _picDownloadHq); + void setNotificationsEnabled(int _notificationsEnabled); + void setDoubleClickToPlay(int _doubleClickToPlay); + void setPlayToStack(int _playToStack); + void setCardInfoMinimized(int _cardInfoMinimized); + void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes); + void setDisplayCardNames(int _displayCardNames); + void setHorizontalHand(int _horizontalHand); + void setInvertVerticalCoordinate(int _invertVerticalCoordinate); + void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); + void setTapAnimation(int _tapAnimation); + void setZoneViewSortByName(int _zoneViewSortByName); + void setZoneViewSortByType(int _zoneViewSortByType); + void setSoundEnabled(int _soundEnabled); + void setSoundPath(const QString &_soundPath); + void setPriceTagFeature(int _priceTagFeature); + void setPriceTagSource(int _priceTagSource); + void setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers); + void setPicUrl(const QString &_picUrl); + void setPicUrlHq(const QString &_picUrlHq); }; extern SettingsCache *settingsCache; diff --git a/cockatrice/src/soundengine.cpp b/cockatrice/src/soundengine.cpp index 247567ccc..89820f9d4 100644 --- a/cockatrice/src/soundengine.cpp +++ b/cockatrice/src/soundengine.cpp @@ -6,77 +6,96 @@ #include SoundEngine::SoundEngine(QObject *parent) - : QObject(parent) + : QObject(parent), audio(0) { - inputBuffer = new QBuffer; - QAudioFormat format; - format.setFrequency(44100); - format.setChannels(1); - format.setSampleSize(16); - format.setCodec("audio/pcm"); - format.setByteOrder(QAudioFormat::LittleEndian); - format.setSampleType(QAudioFormat::SignedInt); - audio = new QAudioOutput(format, this); - - connect(settingsCache, SIGNAL(soundPathChanged()), this, SLOT(cacheData())); - cacheData(); + inputBuffer = new QBuffer(this); + + connect(settingsCache, SIGNAL(soundPathChanged()), this, SLOT(cacheData())); + connect(settingsCache, SIGNAL(soundEnabledChanged()), this, SLOT(soundEnabledChanged())); + cacheData(); + soundEnabledChanged(); } void SoundEngine::cacheData() { - static const QStringList fileNames = QStringList() - << "notification" << "draw" << "playcard" << "shuffle" << "tap" << "untap" << "cuckoo"; - for (int i = 0; i < fileNames.size(); ++i) { - QFile file(settingsCache->getSoundPath() + "/" + fileNames[i] + ".raw"); - file.open(QIODevice::ReadOnly); - audioData.insert(fileNames[i], file.readAll()); - file.close(); - } + static const QStringList fileNames = QStringList() + << "notification" << "draw" << "playcard" << "shuffle" << "tap" << "untap" << "cuckoo"; + for (int i = 0; i < fileNames.size(); ++i) { + QFile file(settingsCache->getSoundPath() + "/" + fileNames[i] + ".raw"); + file.open(QIODevice::ReadOnly); + audioData.insert(fileNames[i], file.readAll()); + file.close(); + } +} + +void SoundEngine::soundEnabledChanged() +{ + if (settingsCache->getSoundEnabled()) { + qDebug("SoundEngine: enabling sound"); + QAudioFormat format; +#if QT_VERSION < 0x050000 + format.setFrequency(44100); + format.setChannels(1); +#else + format.setSampleRate(44100); + format.setChannelCount(1); +#endif + format.setSampleSize(16); + format.setCodec("audio/pcm"); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleType(QAudioFormat::SignedInt); + audio = new QAudioOutput(format, this); + } else if (audio) { + qDebug("SoundEngine: disabling sound"); + audio->stop(); + audio->deleteLater(); + audio = 0; + } } void SoundEngine::playSound(const QString &fileName) { - if (!settingsCache->getSoundEnabled()) - return; - - audio->stop(); - inputBuffer->close(); - inputBuffer->setData(audioData[fileName]); - inputBuffer->open(QIODevice::ReadOnly); - audio->start(inputBuffer); + if (!audio) + return; + + audio->stop(); + inputBuffer->close(); + inputBuffer->setData(audioData[fileName]); + inputBuffer->open(QIODevice::ReadOnly); + audio->start(inputBuffer); } void SoundEngine::notification() { - playSound("notification"); + playSound("notification"); } void SoundEngine::draw() { - playSound("draw"); + playSound("draw"); } void SoundEngine::playCard() { - playSound("playcard"); + playSound("playcard"); } void SoundEngine::shuffle() { - playSound("shuffle"); + playSound("shuffle"); } void SoundEngine::tap() { - playSound("tap"); + playSound("tap"); } void SoundEngine::untap() { - playSound("untap"); + playSound("untap"); } void SoundEngine::cuckoo() { - playSound("cuckoo"); + playSound("cuckoo"); } diff --git a/cockatrice/src/soundengine.h b/cockatrice/src/soundengine.h index d65b3f2dd..a3071fda1 100644 --- a/cockatrice/src/soundengine.h +++ b/cockatrice/src/soundengine.h @@ -8,26 +8,27 @@ class QAudioOutput; class QBuffer; class SoundEngine : public QObject { - Q_OBJECT + Q_OBJECT private: - void playSound(const QString &fileName); - QMap audioData; - QBuffer *inputBuffer; - QAudioOutput *audio; + void playSound(const QString &fileName); + QMap audioData; + QBuffer *inputBuffer; + QAudioOutput *audio; private slots: - void cacheData(); + void cacheData(); + void soundEnabledChanged(); public: - SoundEngine(QObject *parent = 0); + SoundEngine(QObject *parent = 0); public slots: - void notification(); - void draw(); - void playCard(); - void shuffle(); - void tap(); - void untap(); - void cuckoo(); + void notification(); + void draw(); + void playCard(); + void shuffle(); + void tap(); + void untap(); + void cuckoo(); }; extern SoundEngine *soundEngine; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/stackzone.cpp b/cockatrice/src/stackzone.cpp index f7e61338d..a60c4037b 100644 --- a/cockatrice/src/stackzone.cpp +++ b/cockatrice/src/stackzone.cpp @@ -4,97 +4,106 @@ #include "stackzone.h" #include "settingscache.h" #include "player.h" -#include "protocol_items.h" #include "carddragitem.h" +#include "carditem.h" + +#include "pb/command_move_card.pb.h" StackZone::StackZone(Player *_p, int _zoneHeight, QGraphicsItem *parent) - : SelectZone(_p, "stack", false, false, true, parent), zoneHeight(_zoneHeight) + : SelectZone(_p, "stack", false, false, true, parent), zoneHeight(_zoneHeight) { - connect(settingsCache, SIGNAL(stackBgPathChanged()), this, SLOT(updateBgPixmap())); - updateBgPixmap(); - setCacheMode(DeviceCoordinateCache); + connect(settingsCache, SIGNAL(stackBgPathChanged()), this, SLOT(updateBgPixmap())); + updateBgPixmap(); + setCacheMode(DeviceCoordinateCache); } void StackZone::updateBgPixmap() { - QString bgPath = settingsCache->getStackBgPath(); - if (!bgPath.isEmpty()) - bgPixmap.load(bgPath); - update(); + QString bgPath = settingsCache->getStackBgPath(); + if (!bgPath.isEmpty()) + bgPixmap.load(bgPath); + update(); } void StackZone::addCardImpl(CardItem *card, int x, int /*y*/) { - if (x == -1) - x = cards.size(); - cards.insert(x, card); + if (x == -1) + x = cards.size(); + cards.insert(x, card); - if (!cards.getContentsKnown()) { - card->setId(-1); - card->setName(); - } - card->setParentItem(this); - card->resetState(); - card->setVisible(true); - card->update(); + if (!cards.getContentsKnown()) { + card->setId(-1); + card->setName(); + } + card->setParentItem(this); + card->resetState(); + card->setVisible(true); + card->update(); } QRectF StackZone::boundingRect() const { - return QRectF(0, 0, 100, zoneHeight); + return QRectF(0, 0, 100, zoneHeight); } -void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) +void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - if (bgPixmap.isNull()) - painter->fillRect(boundingRect(), QColor(113, 43, 43)); - else - painter->fillRect(boundingRect(), QBrush(bgPixmap)); + if (bgPixmap.isNull()) + painter->fillRect(boundingRect(), QColor(113, 43, 43)); + else + painter->fillRect(boundingRect(), QBrush(bgPixmap)); } void StackZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &/*dropPoint*/) { - if (startZone == this) - return; - - QList idList; - for (int i = 0; i < dragItems.size(); ++i) - idList.append(new CardToMove(dragItems[i]->getId())); - - player->sendGameCommand(new Command_MoveCard(-1, startZone->getName(), idList, player->getId(), getName(), 0, 0)); + if (startZone == this) + return; + + Command_MoveCard cmd; + cmd.set_start_player_id(startZone->getPlayer()->getId()); + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_target_player_id(player->getId()); + cmd.set_target_zone(getName().toStdString()); + cmd.set_x(0); + cmd.set_y(0); + + for (int i = 0; i < dragItems.size(); ++i) + cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId()); + + player->sendGameCommand(cmd); } void StackZone::reorganizeCards() { - if (!cards.isEmpty()) { - QList arrowsToUpdate; - - const int cardCount = cards.size(); - qreal totalWidth = boundingRect().width(); - qreal totalHeight = boundingRect().height(); - qreal cardWidth = cards.at(0)->boundingRect().width(); - qreal cardHeight = cards.at(0)->boundingRect().height(); - qreal xspace = 5; - qreal x1 = xspace; - qreal x2 = totalWidth - xspace - cardWidth; - - for (int i = 0; i < cardCount; i++) { - CardItem *c = cards.at(i); - qreal x = i % 2 ? x2 : x1; - // If the total height of the cards is smaller than the available height, - // the cards do not need to overlap and are displayed in the center of the area. - if (cardHeight * cardCount > totalHeight) - c->setPos(x, ((qreal) i) * (totalHeight - cardHeight) / (cardCount - 1)); - else - c->setPos(x, ((qreal) i) * cardHeight + (totalHeight - cardCount * cardHeight) / 2); - c->setRealZValue(i); - - arrowsToUpdate.append(c->getArrowsFrom()); - arrowsToUpdate.append(c->getArrowsTo()); - } - QSetIterator arrowIterator(QSet::fromList(arrowsToUpdate)); - while (arrowIterator.hasNext()) - arrowIterator.next()->updatePath(); - } - update(); + if (!cards.isEmpty()) { + QList arrowsToUpdate; + + const int cardCount = cards.size(); + qreal totalWidth = boundingRect().width(); + qreal totalHeight = boundingRect().height(); + qreal cardWidth = cards.at(0)->boundingRect().width(); + qreal cardHeight = cards.at(0)->boundingRect().height(); + qreal xspace = 5; + qreal x1 = xspace; + qreal x2 = totalWidth - xspace - cardWidth; + + for (int i = 0; i < cardCount; i++) { + CardItem *c = cards.at(i); + qreal x = i % 2 ? x2 : x1; + // If the total height of the cards is smaller than the available height, + // the cards do not need to overlap and are displayed in the center of the area. + if (cardHeight * cardCount > totalHeight) + c->setPos(x, ((qreal) i) * (totalHeight - cardHeight) / (cardCount - 1)); + else + c->setPos(x, ((qreal) i) * cardHeight + (totalHeight - cardCount * cardHeight) / 2); + c->setRealZValue(i); + + arrowsToUpdate.append(c->getArrowsFrom()); + arrowsToUpdate.append(c->getArrowsTo()); + } + QSetIterator arrowIterator(QSet::fromList(arrowsToUpdate)); + while (arrowIterator.hasNext()) + arrowIterator.next()->updatePath(); + } + update(); } diff --git a/cockatrice/src/stackzone.h b/cockatrice/src/stackzone.h index 5fb2878b7..948d3bcb9 100644 --- a/cockatrice/src/stackzone.h +++ b/cockatrice/src/stackzone.h @@ -4,20 +4,20 @@ #include "selectzone.h" class StackZone : public SelectZone { - Q_OBJECT + Q_OBJECT private: - qreal zoneHeight; - QPixmap bgPixmap; + qreal zoneHeight; + QPixmap bgPixmap; private slots: - void updateBgPixmap(); + void updateBgPixmap(); public: - StackZone(Player *_p, int _zoneHeight, QGraphicsItem *parent = 0); - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); + StackZone(Player *_p, int _zoneHeight, QGraphicsItem *parent = 0); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void reorganizeCards(); protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y); }; #endif diff --git a/cockatrice/src/tab.cpp b/cockatrice/src/tab.cpp index 2eed9b546..28838ed27 100644 --- a/cockatrice/src/tab.cpp +++ b/cockatrice/src/tab.cpp @@ -2,31 +2,30 @@ #include "cardinfowidget.h" #include #include - +#include Tab::Tab(TabSupervisor *_tabSupervisor, QWidget *parent) - : QWidget(parent), tabMenu(0), tabSupervisor(_tabSupervisor), contentsChanged(false), infoPopup(0) + : QWidget(parent), tabSupervisor(_tabSupervisor), contentsChanged(false), infoPopup(0) { } void Tab::showCardInfoPopup(const QPoint &pos, const QString &cardName) { - infoPopup = new CardInfoWidget(CardInfoWidget::ModePopUp, 0, Qt::Widget | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint); - infoPopup->setAttribute(Qt::WA_TransparentForMouseEvents); - infoPopup->setCard(cardName); - QRect screenRect = qApp->desktop()->screenGeometry(this); - infoPopup->move( - qMax(screenRect.left(), qMin(pos.x() - infoPopup->width() / 2, screenRect.left() + screenRect.width() - infoPopup->width())), - qMax(screenRect.top(), qMin(pos.y() - infoPopup->height() / 2, screenRect.top() + screenRect.height() - infoPopup->height())) - ); - infoPopup->show(); + infoPopup = new CardInfoWidget(CardInfoWidget::ModePopUp, cardName, 0, Qt::Widget | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint); + infoPopup->setAttribute(Qt::WA_TransparentForMouseEvents); + QRect screenRect = qApp->desktop()->screenGeometry(this); + infoPopup->move( + qMax(screenRect.left(), qMin(pos.x() - infoPopup->width() / 2, screenRect.left() + screenRect.width() - infoPopup->width())), + qMax(screenRect.top(), qMin(pos.y() - infoPopup->height() / 2, screenRect.top() + screenRect.height() - infoPopup->height())) + ); + infoPopup->show(); } void Tab::deleteCardInfoPopup(const QString &cardName) { - if (infoPopup) { - if ((infoPopup->getCardName() == cardName) || (cardName == "_")) { - infoPopup->deleteLater(); - infoPopup = 0; - } - } + if (infoPopup) { + if ((infoPopup->getCardName() == cardName) || (cardName == "_")) { + infoPopup->deleteLater(); + infoPopup = 0; + } + } } diff --git a/cockatrice/src/tab.h b/cockatrice/src/tab.h index f7dc472fe..1b8b77049 100644 --- a/cockatrice/src/tab.h +++ b/cockatrice/src/tab.h @@ -8,27 +8,30 @@ class TabSupervisor; class CardInfoWidget; class Tab : public QWidget { - Q_OBJECT + Q_OBJECT signals: - void userEvent(bool globalEvent = true); + void userEvent(bool globalEvent = true); + void tabTextChanged(Tab *tab, const QString &newTabText); protected: - QMenu *tabMenu; - TabSupervisor *tabSupervisor; + TabSupervisor *tabSupervisor; + void addTabMenu(QMenu *menu) { tabMenus.append(menu); } protected slots: - void showCardInfoPopup(const QPoint &pos, const QString &cardName); - void deleteCardInfoPopup(const QString &cardName); + void showCardInfoPopup(const QPoint &pos, const QString &cardName); + void deleteCardInfoPopup(const QString &cardName); private: - bool contentsChanged; - CardInfoWidget *infoPopup; + bool contentsChanged; + CardInfoWidget *infoPopup; + QList tabMenus; public: - Tab(TabSupervisor *_tabSupervisor, QWidget *parent = 0); - QMenu *getTabMenu() const { return tabMenu; } - TabSupervisor *getTabSupervisor() const { return tabSupervisor; } - bool getContentsChanged() const { return contentsChanged; } - void setContentsChanged(bool _contentsChanged) { contentsChanged = _contentsChanged; } - virtual QString getTabText() const = 0; - virtual void retranslateUi() = 0; - virtual void closeRequest() { } + Tab(TabSupervisor *_tabSupervisor, QWidget *parent = 0); + const QList &getTabMenus() const { return tabMenus; } + TabSupervisor *getTabSupervisor() const { return tabSupervisor; } + bool getContentsChanged() const { return contentsChanged; } + void setContentsChanged(bool _contentsChanged) { contentsChanged = _contentsChanged; } + virtual QString getTabText() const = 0; + virtual void retranslateUi() = 0; + virtual void closeRequest() { } + virtual void tabActivated() { } }; #endif diff --git a/cockatrice/src/tab_admin.cpp b/cockatrice/src/tab_admin.cpp index 59614fcfb..71018186b 100644 --- a/cockatrice/src/tab_admin.cpp +++ b/cockatrice/src/tab_admin.cpp @@ -4,130 +4,129 @@ #include #include #include +#include #include #include #include #include "tab_admin.h" #include "abstractclient.h" -#include "protocol_items.h" + +#include "pb/admin_commands.pb.h" ShutdownDialog::ShutdownDialog(QWidget *parent) - : QDialog(parent) + : QDialog(parent) { - QLabel *reasonLabel = new QLabel(tr("&Reason for shutdown:")); - reasonEdit = new QLineEdit; - reasonLabel->setBuddy(reasonEdit); - QLabel *minutesLabel = new QLabel(tr("&Time until shutdown (minutes):")); - minutesEdit = new QSpinBox; - minutesLabel->setBuddy(minutesEdit); - minutesEdit->setMinimum(0); - minutesEdit->setValue(5); - - QPushButton *okButton = new QPushButton(tr("&OK")); - okButton->setAutoDefault(true); - okButton->setDefault(true); - connect(okButton, SIGNAL(clicked()), this, SLOT(accept())); - QPushButton *cancelButton = new QPushButton(tr("&Cancel")); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); - - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(); - buttonLayout->addWidget(okButton); - buttonLayout->addWidget(cancelButton); - - QGridLayout *mainLayout = new QGridLayout; - mainLayout->addWidget(reasonLabel, 0, 0); - mainLayout->addWidget(reasonEdit, 0, 1); - mainLayout->addWidget(minutesLabel, 1, 0); - mainLayout->addWidget(minutesEdit, 1, 1); - mainLayout->addLayout(buttonLayout, 2, 0, 1, 2); - - setLayout(mainLayout); - setWindowTitle(tr("Shut down server")); + QLabel *reasonLabel = new QLabel(tr("&Reason for shutdown:")); + reasonEdit = new QLineEdit; + reasonLabel->setBuddy(reasonEdit); + QLabel *minutesLabel = new QLabel(tr("&Time until shutdown (minutes):")); + minutesEdit = new QSpinBox; + minutesLabel->setBuddy(minutesEdit); + minutesEdit->setMinimum(0); + minutesEdit->setValue(5); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + QGridLayout *mainLayout = new QGridLayout; + mainLayout->addWidget(reasonLabel, 0, 0); + mainLayout->addWidget(reasonEdit, 0, 1); + mainLayout->addWidget(minutesLabel, 1, 0); + mainLayout->addWidget(minutesEdit, 1, 1); + mainLayout->addWidget(buttonBox, 2, 0, 1, 2); + + setLayout(mainLayout); + setWindowTitle(tr("Shut down server")); } QString ShutdownDialog::getReason() const { - return reasonEdit->text(); + return reasonEdit->text(); } int ShutdownDialog::getMinutes() const { - return minutesEdit->value(); + return minutesEdit->value(); } TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent) - : Tab(_tabSupervisor, parent), locked(true), client(_client), fullAdmin(_fullAdmin) + : Tab(_tabSupervisor, parent), locked(true), client(_client), fullAdmin(_fullAdmin) { - updateServerMessageButton = new QPushButton; - connect(updateServerMessageButton, SIGNAL(clicked()), this, SLOT(actUpdateServerMessage())); - shutdownServerButton = new QPushButton; - connect(shutdownServerButton, SIGNAL(clicked()), this, SLOT(actShutdownServer())); - - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(updateServerMessageButton); - vbox->addWidget(shutdownServerButton); - vbox->addStretch(); - - adminGroupBox = new QGroupBox; - adminGroupBox->setLayout(vbox); - adminGroupBox->setEnabled(false); - - unlockButton = new QPushButton; - connect(unlockButton, SIGNAL(clicked()), this, SLOT(actUnlock())); - lockButton = new QPushButton; - lockButton->setEnabled(false); - connect(lockButton, SIGNAL(clicked()), this, SLOT(actLock())); - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(adminGroupBox); - mainLayout->addWidget(unlockButton); - mainLayout->addWidget(lockButton); - - retranslateUi(); - setLayout(mainLayout); + updateServerMessageButton = new QPushButton; + connect(updateServerMessageButton, SIGNAL(clicked()), this, SLOT(actUpdateServerMessage())); + shutdownServerButton = new QPushButton; + connect(shutdownServerButton, SIGNAL(clicked()), this, SLOT(actShutdownServer())); + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(updateServerMessageButton); + vbox->addWidget(shutdownServerButton); + vbox->addStretch(); + + adminGroupBox = new QGroupBox; + adminGroupBox->setLayout(vbox); + adminGroupBox->setEnabled(false); + + unlockButton = new QPushButton; + connect(unlockButton, SIGNAL(clicked()), this, SLOT(actUnlock())); + lockButton = new QPushButton; + lockButton->setEnabled(false); + connect(lockButton, SIGNAL(clicked()), this, SLOT(actLock())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(adminGroupBox); + mainLayout->addWidget(unlockButton); + mainLayout->addWidget(lockButton); + + retranslateUi(); + setLayout(mainLayout); } void TabAdmin::retranslateUi() { - updateServerMessageButton->setText(tr("Update server &message")); - shutdownServerButton->setText(tr("&Shut down server")); - adminGroupBox->setTitle(tr("Server administration functions")); - - unlockButton->setText(tr("&Unlock functions")); - lockButton->setText(tr("&Lock functions")); + updateServerMessageButton->setText(tr("Update server &message")); + shutdownServerButton->setText(tr("&Shut down server")); + adminGroupBox->setTitle(tr("Server administration functions")); + + unlockButton->setText(tr("&Unlock functions")); + lockButton->setText(tr("&Lock functions")); } void TabAdmin::actUpdateServerMessage() { - client->sendCommand(new Command_UpdateServerMessage()); + client->sendCommand(client->prepareAdminCommand(Command_UpdateServerMessage())); } void TabAdmin::actShutdownServer() { - ShutdownDialog dlg; - if (dlg.exec()) - client->sendCommand(new Command_ShutdownServer(dlg.getReason(), dlg.getMinutes())); + ShutdownDialog dlg; + if (dlg.exec()) { + Command_ShutdownServer cmd; + cmd.set_reason(dlg.getReason().toStdString()); + cmd.set_minutes(dlg.getMinutes()); + + client->sendCommand(client->prepareAdminCommand(cmd)); + } } void TabAdmin::actUnlock() { - if (QMessageBox::question(this, tr("Unlock administration functions"), tr("Do you really want to unlock the administration functions?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (fullAdmin) - adminGroupBox->setEnabled(true); - lockButton->setEnabled(true); - unlockButton->setEnabled(false); - locked = false; - emit adminLockChanged(false); - } + if (QMessageBox::question(this, tr("Unlock administration functions"), tr("Do you really want to unlock the administration functions?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (fullAdmin) + adminGroupBox->setEnabled(true); + lockButton->setEnabled(true); + unlockButton->setEnabled(false); + locked = false; + emit adminLockChanged(false); + } } void TabAdmin::actLock() { - if (fullAdmin) - adminGroupBox->setEnabled(false); - lockButton->setEnabled(false); - unlockButton->setEnabled(true); - locked = true; - emit adminLockChanged(true); + if (fullAdmin) + adminGroupBox->setEnabled(false); + lockButton->setEnabled(false); + unlockButton->setEnabled(true); + locked = true; + emit adminLockChanged(true); } diff --git a/cockatrice/src/tab_admin.h b/cockatrice/src/tab_admin.h index f0cdd126d..cc5ae6a91 100644 --- a/cockatrice/src/tab_admin.h +++ b/cockatrice/src/tab_admin.h @@ -12,38 +12,38 @@ class QSpinBox; class QLineEdit; class ShutdownDialog : public QDialog { - Q_OBJECT + Q_OBJECT private: - QLineEdit *reasonEdit; - QSpinBox *minutesEdit; + QLineEdit *reasonEdit; + QSpinBox *minutesEdit; public: - ShutdownDialog(QWidget *parent = 0); - QString getReason() const; - int getMinutes() const; + ShutdownDialog(QWidget *parent = 0); + QString getReason() const; + int getMinutes() const; }; class TabAdmin : public Tab { - Q_OBJECT + Q_OBJECT private: - bool locked; - AbstractClient *client; - bool fullAdmin; - QPushButton *updateServerMessageButton, *shutdownServerButton; - QGroupBox *adminGroupBox; - QPushButton *unlockButton, *lockButton; + bool locked; + AbstractClient *client; + bool fullAdmin; + QPushButton *updateServerMessageButton, *shutdownServerButton; + QGroupBox *adminGroupBox; + QPushButton *unlockButton, *lockButton; signals: - void adminLockChanged(bool lock); + void adminLockChanged(bool lock); private slots: - void actUpdateServerMessage(); - void actShutdownServer(); - - void actUnlock(); - void actLock(); + void actUpdateServerMessage(); + void actShutdownServer(); + + void actUnlock(); + void actLock(); public: - TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent = 0); - void retranslateUi(); - QString getTabText() const { return tr("Administration"); } - bool getLocked() const { return locked; } + TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent = 0); + void retranslateUi(); + QString getTabText() const { return tr("Administration"); } + bool getLocked() const { return locked; } }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp new file mode 100644 index 000000000..c01389316 --- /dev/null +++ b/cockatrice/src/tab_deck_editor.cpp @@ -0,0 +1,727 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tab_deck_editor.h" +#include "window_sets.h" +#include "carddatabase.h" +#include "carddatabasemodel.h" +#include "decklistmodel.h" +#include "cardinfowidget.h" +#include "dlg_load_deck_from_clipboard.h" +#include "dlg_edit_tokens.h" +#include "main.h" +#include "settingscache.h" +#include "priceupdater.h" +#include "tab_supervisor.h" +#include "deckstats_interface.h" +#include "abstractclient.h" +#include "pending_command.h" +#include "pb/response.pb.h" +#include "pb/command_deck_upload.pb.h" +#include "filtertreemodel.h" +#include "cardframe.h" +#include "filterbuilder.h" + +void SearchLineEdit::keyPressEvent(QKeyEvent *event) +{ + if (treeView && ((event->key() == Qt::Key_Up) || (event->key() == Qt::Key_Down))) + QCoreApplication::sendEvent(treeView, event); + QLineEdit::keyPressEvent(event); +} + +TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent) + : Tab(_tabSupervisor, parent), modified(false) +{ + aClearSearch = new QAction(QString(), this); + aClearSearch->setIcon(QIcon(":/resources/icon_clearsearch.svg")); + connect(aClearSearch, SIGNAL(triggered()), this, SLOT(actClearSearch())); + + searchLabel = new QLabel(); + searchEdit = new SearchLineEdit; + searchLabel->setBuddy(searchEdit); + setFocusProxy(searchEdit); + setFocusPolicy(Qt::ClickFocus); + + searchEdit->installEventFilter(&searchKeySignals); + connect(searchEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &))); + connect(&searchKeySignals, SIGNAL(onEnter()), this, SLOT(actAddCard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actAddCard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltRBracket()), this, SLOT(actAddCardToSideboard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementCard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard())); + connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard())); + connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard())); + + QToolBar *deckEditToolBar = new QToolBar; + deckEditToolBar->setOrientation(Qt::Horizontal); + deckEditToolBar->setIconSize(QSize(24, 24)); + + QHBoxLayout *searchLayout = new QHBoxLayout; + searchLayout->addWidget(deckEditToolBar); + searchLayout->addWidget(searchLabel); + searchLayout->addWidget(searchEdit); + + databaseModel = new CardDatabaseModel(db, this); + databaseDisplayModel = new CardDatabaseDisplayModel(this); + databaseDisplayModel->setSourceModel(databaseModel); + databaseDisplayModel->setFilterKeyColumn(0); + databaseDisplayModel->sort(0, Qt::AscendingOrder); + + databaseView = new QTreeView(); + databaseView->setFocusProxy(searchEdit); + databaseView->setModel(databaseDisplayModel); + databaseView->setUniformRowHeights(true); + databaseView->setRootIsDecorated(false); + databaseView->setAlternatingRowColors(true); + databaseView->setSortingEnabled(true); + databaseView->sortByColumn(0, Qt::AscendingOrder); + databaseView->resizeColumnToContents(0); + connect(databaseView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(updateCardInfoLeft(const QModelIndex &, const QModelIndex &))); + connect(databaseView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actAddCard())); + searchEdit->setTreeView(databaseView); + + QVBoxLayout *leftFrame = new QVBoxLayout; + leftFrame->addLayout(searchLayout); + leftFrame->addWidget(databaseView); + + cardInfo = new CardFrame(250, 356); + aCardTextOnly = new QAction(QString(), this); + aCardTextOnly->setCheckable(true); + connect(aCardTextOnly, SIGNAL(triggered()), cardInfo, SLOT(toggleCardTextOnly())); + + filterModel = new FilterTreeModel(); + databaseDisplayModel->setFilterTree(filterModel->filterTree()); + filterView = new QTreeView; + filterView->setModel(filterModel); + filterView->setMaximumWidth(250); + filterView->setUniformRowHeights(true); + filterView->setHeaderHidden(true); + filterView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(filterModel, SIGNAL(layoutChanged()), filterView, SLOT(expandAll())); + connect(filterView, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(filterViewCustomContextMenu(const QPoint &))); + FilterBuilder *filterBuilder = new FilterBuilder; + filterBuilder->setMaximumWidth(250); + connect(filterBuilder, SIGNAL(add(const CardFilter *)), filterModel, SLOT(addFilter(const CardFilter *))); + + QVBoxLayout *filter = new QVBoxLayout; + filter->addWidget(filterBuilder, 0, Qt::AlignTop); + filter->addWidget(filterView, 10); + + QVBoxLayout *middleFrame = new QVBoxLayout; + middleFrame->addWidget(cardInfo, 0, Qt::AlignTop); + middleFrame->addLayout(filter, 10); + + deckModel = new DeckListModel(this); + connect(deckModel, SIGNAL(deckHashChanged()), this, SLOT(updateHash())); + deckView = new QTreeView(); + deckView->setModel(deckModel); + deckView->setUniformRowHeights(true); + deckView->setSortingEnabled(true); + deckView->sortByColumn(1, Qt::AscendingOrder); +#if QT_VERSION < 0x050000 + deckView->header()->setResizeMode(QHeaderView::ResizeToContents); +#else + deckView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#endif + deckView->installEventFilter(&deckViewKeySignals); + connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(updateCardInfoRight(const QModelIndex &, const QModelIndex &))); + connect(&deckViewKeySignals, SIGNAL(onEnter()), this, SLOT(actIncrement())); + connect(&deckViewKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actIncrement())); + connect(&deckViewKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrement())); + connect(&deckViewKeySignals, SIGNAL(onRight()), this, SLOT(actIncrement())); + connect(&deckViewKeySignals, SIGNAL(onLeft()), this, SLOT(actDecrement())); + connect(&deckViewKeySignals, SIGNAL(onDelete()), this, SLOT(actRemoveCard())); + + nameLabel = new QLabel(); + nameEdit = new QLineEdit; + nameLabel->setBuddy(nameEdit); + connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateName(const QString &))); + commentsLabel = new QLabel(); + commentsEdit = new QTextEdit; + commentsEdit->setMaximumHeight(70); + commentsLabel->setBuddy(commentsEdit); + connect(commentsEdit, SIGNAL(textChanged()), this, SLOT(updateComments())); + hashLabel1 = new QLabel(); + hashLabel = new QLabel; + + QGridLayout *grid = new QGridLayout; + grid->addWidget(nameLabel, 0, 0); + grid->addWidget(nameEdit, 0, 1); + + grid->addWidget(commentsLabel, 1, 0); + grid->addWidget(commentsEdit, 1, 1); + + grid->addWidget(hashLabel1, 2, 0); + grid->addWidget(hashLabel, 2, 1); + + // Update price + aUpdatePrices = new QAction(QString(), this); + aUpdatePrices->setIcon(QIcon(":/resources/icon_update.png")); + connect(aUpdatePrices, SIGNAL(triggered()), this, SLOT(actUpdatePrices())); + if (!settingsCache->getPriceTagFeature()) + aUpdatePrices->setVisible(false); + connect(settingsCache, SIGNAL(priceTagFeatureChanged(int)), this, SLOT(setPriceTagFeatureEnabled(int))); + + QToolBar *deckToolBar = new QToolBar; + deckToolBar->setOrientation(Qt::Vertical); + deckToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + deckToolBar->setIconSize(QSize(24, 24)); + deckToolBar->addAction(aUpdatePrices); + QHBoxLayout *deckToolbarLayout = new QHBoxLayout; + deckToolbarLayout->addStretch(); + deckToolbarLayout->addWidget(deckToolBar); + deckToolbarLayout->addStretch(); + + QVBoxLayout *rightFrame = new QVBoxLayout; + rightFrame->addLayout(grid); + rightFrame->addWidget(deckView, 10); + rightFrame->addLayout(deckToolbarLayout); + + QHBoxLayout *mainLayout = new QHBoxLayout; + mainLayout->addLayout(leftFrame, 10); + mainLayout->addLayout(middleFrame); + mainLayout->addLayout(rightFrame); + setLayout(mainLayout); + + aNewDeck = new QAction(QString(), this); + aNewDeck->setShortcuts(QKeySequence::New); + connect(aNewDeck, SIGNAL(triggered()), this, SLOT(actNewDeck())); + aLoadDeck = new QAction(QString(), this); + aLoadDeck->setShortcuts(QKeySequence::Open); + connect(aLoadDeck, SIGNAL(triggered()), this, SLOT(actLoadDeck())); + aSaveDeck = new QAction(QString(), this); + aSaveDeck->setShortcuts(QKeySequence::Save); + connect(aSaveDeck, SIGNAL(triggered()), this, SLOT(actSaveDeck())); + aSaveDeckAs = new QAction(QString(), this); +// aSaveDeckAs->setShortcuts(QKeySequence::SaveAs); + connect(aSaveDeckAs, SIGNAL(triggered()), this, SLOT(actSaveDeckAs())); + aLoadDeckFromClipboard = new QAction(QString(), this); + connect(aLoadDeckFromClipboard, SIGNAL(triggered()), this, SLOT(actLoadDeckFromClipboard())); + aLoadDeckFromClipboard->setShortcuts(QKeySequence::Paste); + aSaveDeckToClipboard = new QAction(QString(), this); + connect(aSaveDeckToClipboard, SIGNAL(triggered()), this, SLOT(actSaveDeckToClipboard())); + aSaveDeckToClipboard->setShortcuts(QKeySequence::Copy); + aPrintDeck = new QAction(QString(), this); + aPrintDeck->setShortcuts(QKeySequence::Print); + connect(aPrintDeck, SIGNAL(triggered()), this, SLOT(actPrintDeck())); + aAnalyzeDeck = new QAction(QString(), this); + connect(aAnalyzeDeck, SIGNAL(triggered()), this, SLOT(actAnalyzeDeck())); + aClose = new QAction(QString(), this); + connect(aClose, SIGNAL(triggered()), this, SLOT(closeRequest())); + + aEditSets = new QAction(QString(), this); + connect(aEditSets, SIGNAL(triggered()), this, SLOT(actEditSets())); + aEditTokens = new QAction(QString(), this); + connect(aEditTokens, SIGNAL(triggered()), this, SLOT(actEditTokens())); + + deckMenu = new QMenu(this); + deckMenu->addAction(aNewDeck); + deckMenu->addAction(aLoadDeck); + deckMenu->addAction(aSaveDeck); + deckMenu->addAction(aSaveDeckAs); + deckMenu->addSeparator(); + deckMenu->addAction(aLoadDeckFromClipboard); + deckMenu->addAction(aSaveDeckToClipboard); + deckMenu->addSeparator(); + deckMenu->addAction(aPrintDeck); + deckMenu->addSeparator(); + deckMenu->addAction(aAnalyzeDeck); + deckMenu->addSeparator(); + deckMenu->addAction(aClose); + addTabMenu(deckMenu); + + dbMenu = new QMenu(this); + dbMenu->addAction(aEditSets); + dbMenu->addAction(aEditTokens); + dbMenu->addSeparator(); + dbMenu->addAction(aClearSearch); + dbMenu->addAction(aCardTextOnly); + addTabMenu(dbMenu); + + aAddCard = new QAction(QString(), this); + aAddCard->setIcon(QIcon(":/resources/arrow_right_green.svg")); + connect(aAddCard, SIGNAL(triggered()), this, SLOT(actAddCard())); + aAddCardToSideboard = new QAction(QString(), this); + aAddCardToSideboard->setIcon(QIcon(":/resources/add_to_sideboard.svg")); + connect(aAddCardToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard())); + aRemoveCard = new QAction(QString(), this); + aRemoveCard->setIcon(QIcon(":/resources/remove_row.svg")); + connect(aRemoveCard, SIGNAL(triggered()), this, SLOT(actRemoveCard())); + aIncrement = new QAction(QString(), this); + aIncrement->setIcon(QIcon(":/resources/increment.svg")); + connect(aIncrement, SIGNAL(triggered()), this, SLOT(actIncrement())); + aDecrement = new QAction(QString(), this); + aDecrement->setIcon(QIcon(":/resources/decrement.svg")); + connect(aDecrement, SIGNAL(triggered()), this, SLOT(actDecrement())); + + deckEditToolBar->addAction(aAddCard); + deckEditToolBar->addAction(aAddCardToSideboard); + deckEditToolBar->addAction(aRemoveCard); + deckEditToolBar->addAction(aIncrement); + deckEditToolBar->addAction(aDecrement); + deckEditToolBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + retranslateUi(); + + resize(950, 700); +} + +TabDeckEditor::~TabDeckEditor() +{ + emit deckEditorClosing(this); +} + +void TabDeckEditor::retranslateUi() +{ + aCardTextOnly->setText(tr("Show card text only")); + aClearSearch->setText(tr("&Clear search")); + searchLabel->setText(tr("&Search for:")); + + nameLabel->setText(tr("Deck &name:")); + commentsLabel->setText(tr("&Comments:")); + hashLabel1->setText(tr("Hash:")); + + aUpdatePrices->setText(tr("&Update prices")); + aUpdatePrices->setShortcut(tr("Ctrl+U")); + + aNewDeck->setText(tr("&New deck")); + aLoadDeck->setText(tr("&Load deck...")); + aSaveDeck->setText(tr("&Save deck")); + aSaveDeckAs->setText(tr("Save deck &as...")); + aLoadDeckFromClipboard->setText(tr("Load deck from cl&ipboard...")); + aSaveDeckToClipboard->setText(tr("Save deck to clip&board")); + aPrintDeck->setText(tr("&Print deck...")); + aAnalyzeDeck->setText(tr("&Analyze deck on deckstats.net")); + aClose->setText(tr("&Close")); + aClose->setShortcut(tr("Ctrl+Q")); + + aAddCard->setText(tr("Add card to &maindeck")); + aAddCardToSideboard->setText(tr("Add card to &sideboard")); + + aRemoveCard->setText(tr("&Remove row")); + aRemoveCard->setShortcut(tr("Del")); + aIncrement->setText(tr("&Increment number")); + aIncrement->setShortcut(tr("+")); + aDecrement->setText(tr("&Decrement number")); + aDecrement->setShortcut(tr("-")); + + deckMenu->setTitle(tr("&Deck editor")); + dbMenu->setTitle(tr("C&ard database")); + + aEditSets->setText(tr("&Edit sets...")); + aEditTokens->setText(tr("Edit &tokens...")); +} + +QString TabDeckEditor::getTabText() const +{ + QString result = tr("Deck: %1").arg(nameEdit->text()); + if (modified) + result.prepend("* "); + return result; +} + +void TabDeckEditor::updateName(const QString &name) +{ + deckModel->getDeckList()->setName(name); + setModified(true); +} + +void TabDeckEditor::updateComments() +{ + deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); + setModified(true); +} + +void TabDeckEditor::updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &/*previous*/) +{ + cardInfo->setCard(current.sibling(current.row(), 0).data().toString()); +} + +void TabDeckEditor::updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &/*previous*/) +{ + if (!current.isValid()) + return; + if (!current.model()->hasChildren(current.sibling(current.row(), 0))) + cardInfo->setCard(current.sibling(current.row(), 1).data().toString()); +} + +void TabDeckEditor::updateSearch(const QString &search) +{ + databaseDisplayModel->setCardNameBeginning(search); + QModelIndexList sel = databaseView->selectionModel()->selectedRows(); + if (sel.isEmpty() && databaseDisplayModel->rowCount()) + databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); +} + +void TabDeckEditor::updateHash() +{ + hashLabel->setText(deckModel->getDeckList()->getDeckHash()); +} + +bool TabDeckEditor::confirmClose() +{ + if (modified) { + QMessageBox::StandardButton ret = QMessageBox::warning(this, tr("Are you sure?"), + tr("The decklist has been modified.\nDo you want to save the changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + if (ret == QMessageBox::Save) + return actSaveDeck(); + else if (ret == QMessageBox::Cancel) + return false; + } + return true; +} + +void TabDeckEditor::closeRequest() +{ + if (confirmClose()) + deleteLater(); +} + +void TabDeckEditor::actNewDeck() +{ + if (!confirmClose()) + return; + + deckModel->cleanList(); + nameEdit->setText(QString()); + commentsEdit->setText(QString()); + hashLabel->setText(QString()); + setModified(false); +} + +void TabDeckEditor::actLoadDeck() +{ + if (!confirmClose()) + return; + + QFileDialog dialog(this, tr("Load deck")); + dialog.setDirectory(settingsCache->getDeckPath()); + dialog.setNameFilters(DeckLoader::fileNameFilters); + if (!dialog.exec()) + return; + + QString fileName = dialog.selectedFiles().at(0); + DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); + + DeckLoader *l = new DeckLoader; + if (l->loadFromFile(fileName, fmt)) + setDeck(l); + else + delete l; +} + +void TabDeckEditor::saveDeckRemoteFinished(const Response &response) +{ + if (response.response_code() != Response::RespOk) + QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.")); + else + setModified(false); +} + +bool TabDeckEditor::actSaveDeck() +{ + DeckLoader *const deck = deckModel->getDeckList(); + if (deck->getLastRemoteDeckId() != -1) { + Command_DeckUpload cmd; + cmd.set_deck_id(deck->getLastRemoteDeckId()); + cmd.set_deck_list(deck->writeToString_Native().toStdString()); + + PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(saveDeckRemoteFinished(Response))); + tabSupervisor->getClient()->sendCommand(pend); + + return true; + } else if (deck->getLastFileName().isEmpty()) + return actSaveDeckAs(); + else if (deck->saveToFile(deck->getLastFileName(), deck->getLastFileFormat())) { + setModified(false); + return true; + } + QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); + return false; +} + +bool TabDeckEditor::actSaveDeckAs() +{ + QFileDialog dialog(this, tr("Save deck")); + dialog.setDirectory(settingsCache->getDeckPath()); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setConfirmOverwrite(true); + dialog.setDefaultSuffix("cod"); + dialog.setNameFilters(DeckLoader::fileNameFilters); + dialog.selectFile(deckModel->getDeckList()->getName()); + if (!dialog.exec()) + return false; + + QString fileName = dialog.selectedFiles().at(0); + DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); + + if (!deckModel->getDeckList()->saveToFile(fileName, fmt)) { + QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); + return false; + } + setModified(false); + return true; +} + +void TabDeckEditor::actLoadDeckFromClipboard() +{ + if (!confirmClose()) + return; + + DlgLoadDeckFromClipboard dlg; + if (!dlg.exec()) + return; + + setDeck(dlg.getDeckList()); + setModified(true); +} + +void TabDeckEditor::actSaveDeckToClipboard() +{ + QString buffer; + QTextStream stream(&buffer); + deckModel->getDeckList()->saveToStream_Plain(stream); + QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); + QApplication::clipboard()->setText(buffer, QClipboard::Selection); +} + +void TabDeckEditor::actPrintDeck() +{ + QPrintPreviewDialog *dlg = new QPrintPreviewDialog(this); + connect(dlg, SIGNAL(paintRequested(QPrinter *)), deckModel, SLOT(printDeckList(QPrinter *))); + dlg->exec(); +} + +void TabDeckEditor::actAnalyzeDeck() +{ + DeckStatsInterface *interface = new DeckStatsInterface( + *databaseModel->getDatabase(), + this + ); // it deletes itself when done + interface->analyzeDeck(deckModel->getDeckList()); +} + +void TabDeckEditor::actEditSets() +{ + WndSets *w = new WndSets; + w->setWindowModality(Qt::WindowModal); + w->show(); +} + +void TabDeckEditor::actEditTokens() +{ + DlgEditTokens dlg(databaseModel); + dlg.exec(); + db->saveToFile(settingsCache->getTokenDatabasePath(), true); +} + +void TabDeckEditor::actClearSearch() +{ + databaseDisplayModel->clearSearch(); +} + +void TabDeckEditor::recursiveExpand(const QModelIndex &index) +{ + if (index.parent().isValid()) + recursiveExpand(index.parent()); + deckView->expand(index); +} + +CardInfo *TabDeckEditor::currentCardInfo() const +{ + const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex(); + if (!currentIndex.isValid()) + return NULL; + const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString(); + + return db->getCard(cardName); +} + +void TabDeckEditor::addCardHelper(QString zoneName) +{ + const CardInfo *info; + + info = currentCardInfo(); + if(!info) + return; + if (info->getIsToken()) + zoneName = "tokens"; + + QModelIndex newCardIndex = deckModel->addCard(info->getName(), zoneName); + recursiveExpand(newCardIndex); + deckView->setCurrentIndex(newCardIndex); + setModified(true); +} + +void TabDeckEditor::actAddCard() +{ + addCardHelper("main"); +} + +void TabDeckEditor::actAddCardToSideboard() +{ + addCardHelper("side"); +} + +void TabDeckEditor::actRemoveCard() +{ + const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); + if (!currentIndex.isValid() || deckModel->hasChildren(currentIndex)) + return; + deckModel->removeRow(currentIndex.row(), currentIndex.parent()); + setModified(true); +} + +void TabDeckEditor::offsetCountAtIndex(const QModelIndex &idx, int offset) +{ + if (!idx.isValid() || offset == 0) + return; + + const QModelIndex numberIndex = idx.sibling(idx.row(), 0); + const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); + const int new_count = count + offset; + deckView->setCurrentIndex(numberIndex); + if (new_count <= 0) + deckModel->removeRow(idx.row(), idx.parent()); + else + deckModel->setData(numberIndex, new_count, Qt::EditRole); + setModified(true); +} + +void TabDeckEditor::decrementCardHelper(QString zoneName) +{ + const CardInfo *info; + QModelIndex idx; + + info = currentCardInfo(); + if(!info) + return; + if (info->getIsToken()) + zoneName = "tokens"; + + idx = deckModel->findCard(info->getName(), zoneName); + offsetCountAtIndex(idx, -1); +} + +void TabDeckEditor::actDecrementCard() +{ + decrementCardHelper("main"); +} + +void TabDeckEditor::actDecrementCardFromSideboard() +{ + decrementCardHelper("side"); +} + +void TabDeckEditor::actIncrement() +{ + const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); + offsetCountAtIndex(currentIndex, 1); +} + +void TabDeckEditor::actDecrement() +{ + const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); + offsetCountAtIndex(currentIndex, -1); +} + +void TabDeckEditor::setPriceTagFeatureEnabled(int enabled) +{ + aUpdatePrices->setVisible(enabled); + deckModel->pricesUpdated(); +} + +void TabDeckEditor::actUpdatePrices() +{ + aUpdatePrices->setDisabled(true); + AbstractPriceUpdater *up; + + switch(settingsCache->getPriceTagSource()) + { + case AbstractPriceUpdater::DBPriceSource: + up = new DBPriceUpdater(deckModel->getDeckList()); + break; + case AbstractPriceUpdater::BLPPriceSource: + default: + up = new BLPPriceUpdater(deckModel->getDeckList()); + break; + } + + connect(up, SIGNAL(finishedUpdate()), this, SLOT(finishedUpdatingPrices())); + up->updatePrices(); +} + +void TabDeckEditor::finishedUpdatingPrices() +{ + deckModel->pricesUpdated(); + setModified(true); + aUpdatePrices->setDisabled(false); +} + +void TabDeckEditor::setDeck(DeckLoader *_deck) +{ + deckModel->setDeckList(_deck); + + nameEdit->setText(deckModel->getDeckList()->getName()); + commentsEdit->setText(deckModel->getDeckList()->getComments()); + updateHash(); + deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); + deckView->expandAll(); + setModified(false); + + db->cacheCardPixmaps(deckModel->getDeckList()->getCardList()); + deckView->expandAll(); + setModified(false); +} + +void TabDeckEditor::setModified(bool _modified) +{ + modified = _modified; + emit tabTextChanged(this, getTabText()); +} + +void TabDeckEditor::filterViewCustomContextMenu(const QPoint &point) { + QMenu menu; + QAction *action; + QModelIndex idx; + + idx = filterView->indexAt(point); + if (!idx.isValid()) + return; + + action = menu.addAction(QString("delete")); + action->setData(point); + connect(&menu, SIGNAL(triggered(QAction *)), + this, SLOT(filterRemove(QAction *))); + menu.exec(filterView->mapToGlobal(point)); +} + +void TabDeckEditor::filterRemove(QAction *action) { + QPoint point; + QModelIndex idx; + + point = action->data().toPoint(); + idx = filterView->indexAt(point); + if (!idx.isValid()) + return; + + filterModel->removeRow(idx.row(), idx.parent()); +} diff --git a/cockatrice/src/tab_deck_editor.h b/cockatrice/src/tab_deck_editor.h new file mode 100644 index 000000000..b3ba8dcfe --- /dev/null +++ b/cockatrice/src/tab_deck_editor.h @@ -0,0 +1,119 @@ +#ifndef WINDOW_DECKEDITOR_H +#define WINDOW_DECKEDITOR_H + +#include "tab.h" +#include +#include +#include "keysignals.h" + +class CardDatabaseModel; +class CardDatabaseDisplayModel; +class DeckListModel; +class QTreeView; +class QTableView; +class CardFrame; +class QTextEdit; +class QLabel; +class DeckLoader; +class Response; +class FilterTreeModel; +class CardInfo; + +class SearchLineEdit : public QLineEdit { + private: + QTreeView *treeView; + protected: + void keyPressEvent(QKeyEvent *event); + public: + SearchLineEdit() : QLineEdit(), treeView(0) { } + void setTreeView(QTreeView *_treeView) { treeView = _treeView; } +}; + +class TabDeckEditor : public Tab { + Q_OBJECT +private slots: + void updateName(const QString &name); + void updateComments(); + void updateHash(); + void updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &previous); + void updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &previous); + void updateSearch(const QString &search); + + void actNewDeck(); + void actLoadDeck(); + bool actSaveDeck(); + bool actSaveDeckAs(); + void actLoadDeckFromClipboard(); + void actSaveDeckToClipboard(); + void actPrintDeck(); + void actAnalyzeDeck(); + + void actEditSets(); + void actEditTokens(); + + void actClearSearch(); + + void actAddCard(); + void actAddCardToSideboard(); + void actRemoveCard(); + void actIncrement(); + void actDecrement(); + void actDecrementCard(); + void actDecrementCardFromSideboard(); + + void actUpdatePrices(); + + void finishedUpdatingPrices(); + void saveDeckRemoteFinished(const Response &r); + void filterViewCustomContextMenu(const QPoint &point); + void filterRemove(QAction *action); + void setPriceTagFeatureEnabled(int enabled); +private: + CardInfo *currentCardInfo() const; + void addCardHelper(QString zoneName); + void offsetCountAtIndex(const QModelIndex &idx, int offset); + void decrementCardHelper(QString zoneName); + void recursiveExpand(const QModelIndex &index); + bool confirmClose(); + + CardDatabaseModel *databaseModel; + CardDatabaseDisplayModel *databaseDisplayModel; + DeckListModel *deckModel; + QTreeView *databaseView; + + QTreeView *deckView; + KeySignals deckViewKeySignals; + CardFrame *cardInfo; + QLabel *searchLabel; + SearchLineEdit *searchEdit; + KeySignals searchKeySignals; + + QLabel *nameLabel; + QLineEdit *nameEdit; + QLabel *commentsLabel; + QTextEdit *commentsEdit; + QLabel *hashLabel1; + QLabel *hashLabel; + FilterTreeModel *filterModel; + QTreeView *filterView; + + QMenu *deckMenu, *dbMenu; + QAction *aNewDeck, *aLoadDeck, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aSaveDeckToClipboard, *aPrintDeck, *aAnalyzeDeck, *aClose; + QAction *aEditSets, *aEditTokens, *aClearSearch, *aCardTextOnly; + QAction *aAddCard, *aAddCardToSideboard, *aRemoveCard, *aIncrement, *aDecrement, *aUpdatePrices; + + bool modified; +public: + TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent = 0); + ~TabDeckEditor(); + void retranslateUi(); + QString getTabText() const; + void setDeck(DeckLoader *_deckLoader); + void setModified(bool _windowModified); +public slots: + void closeRequest(); +signals: + void deckEditorClosing(TabDeckEditor *tab); +}; + +#endif diff --git a/cockatrice/src/tab_deck_storage.cpp b/cockatrice/src/tab_deck_storage.cpp index fc427266a..9898c9eea 100644 --- a/cockatrice/src/tab_deck_storage.cpp +++ b/cockatrice/src/tab_deck_storage.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -9,284 +8,346 @@ #include #include #include +#include #include "tab_deck_storage.h" #include "remotedecklist_treewidget.h" #include "abstractclient.h" #include "decklist.h" -#include "protocol_items.h" -#include "window_deckeditor.h" #include "settingscache.h" +#include "deck_loader.h" + +#include "pending_command.h" +#include "pb/response.pb.h" +#include "pb/response_deck_download.pb.h" +#include "pb/response_deck_upload.pb.h" +#include "pb/command_deck_upload.pb.h" +#include "pb/command_deck_download.pb.h" +#include "pb/command_deck_new_dir.pb.h" +#include "pb/command_deck_del_dir.pb.h" +#include "pb/command_deck_del.pb.h" TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client) - : Tab(_tabSupervisor), client(_client) + : Tab(_tabSupervisor), client(_client) { - localDirModel = new QFileSystemModel(this); - localDirModel->setRootPath(settingsCache->getDeckPath()); - - sortFilter = new QSortFilterProxyModel(this); - sortFilter->setSourceModel(localDirModel); - sortFilter->setDynamicSortFilter(true); - - localDirView = new QTreeView; - localDirView->setModel(sortFilter); - localDirView->setColumnHidden(1, true); - localDirView->setRootIndex(sortFilter->mapFromSource(localDirModel->index(localDirModel->rootPath(), 0))); - localDirView->setSortingEnabled(true); - localDirView->header()->setResizeMode(QHeaderView::ResizeToContents); - sortFilter->sort(0, Qt::AscendingOrder); - localDirView->header()->setSortIndicator(0, Qt::AscendingOrder); - - leftToolBar = new QToolBar; - leftToolBar->setOrientation(Qt::Horizontal); - leftToolBar->setIconSize(QSize(32, 32)); - QHBoxLayout *leftToolBarLayout = new QHBoxLayout; - leftToolBarLayout->addStretch(); - leftToolBarLayout->addWidget(leftToolBar); - leftToolBarLayout->addStretch(); + localDirModel = new QFileSystemModel(this); + localDirModel->setRootPath(settingsCache->getDeckPath()); + localDirModel->sort(0, Qt::AscendingOrder); + + localDirView = new QTreeView; + localDirView->setModel(localDirModel); + localDirView->setColumnHidden(1, true); + localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0)); + localDirView->setSortingEnabled(true); +#if QT_VERSION < 0x050000 + localDirView->header()->setResizeMode(QHeaderView::ResizeToContents); +#else + localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#endif + localDirView->header()->setSortIndicator(0, Qt::AscendingOrder); + + leftToolBar = new QToolBar; + leftToolBar->setOrientation(Qt::Horizontal); + leftToolBar->setIconSize(QSize(32, 32)); + QHBoxLayout *leftToolBarLayout = new QHBoxLayout; + leftToolBarLayout->addStretch(); + leftToolBarLayout->addWidget(leftToolBar); + leftToolBarLayout->addStretch(); - QVBoxLayout *leftVbox = new QVBoxLayout; - leftVbox->addWidget(localDirView); - leftVbox->addLayout(leftToolBarLayout); - leftGroupBox = new QGroupBox; - leftGroupBox->setLayout(leftVbox); - - rightToolBar = new QToolBar; - rightToolBar->setOrientation(Qt::Horizontal); - rightToolBar->setIconSize(QSize(32, 32)); - QHBoxLayout *rightToolBarLayout = new QHBoxLayout; - rightToolBarLayout->addStretch(); - rightToolBarLayout->addWidget(rightToolBar); - rightToolBarLayout->addStretch(); + QVBoxLayout *leftVbox = new QVBoxLayout; + leftVbox->addWidget(localDirView); + leftVbox->addLayout(leftToolBarLayout); + leftGroupBox = new QGroupBox; + leftGroupBox->setLayout(leftVbox); + + rightToolBar = new QToolBar; + rightToolBar->setOrientation(Qt::Horizontal); + rightToolBar->setIconSize(QSize(32, 32)); + QHBoxLayout *rightToolBarLayout = new QHBoxLayout; + rightToolBarLayout->addStretch(); + rightToolBarLayout->addWidget(rightToolBar); + rightToolBarLayout->addStretch(); - serverDirView = new RemoteDeckList_TreeWidget(client); + serverDirView = new RemoteDeckList_TreeWidget(client); - QVBoxLayout *rightVbox = new QVBoxLayout; - rightVbox->addWidget(serverDirView); - rightVbox->addLayout(rightToolBarLayout); - rightGroupBox = new QGroupBox; - rightGroupBox->setLayout(rightVbox); - - QHBoxLayout *hbox = new QHBoxLayout; - hbox->addWidget(leftGroupBox); - hbox->addWidget(rightGroupBox); - - aOpenLocalDeck = new QAction(this); - aOpenLocalDeck->setIcon(QIcon(":/resources/pencil.svg")); - connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck())); - aUpload = new QAction(this); - aUpload->setIcon(QIcon(":/resources/arrow_right_green.svg")); - connect(aUpload, SIGNAL(triggered()), this, SLOT(actUpload())); - aOpenRemoteDeck = new QAction(this); - aOpenRemoteDeck->setIcon(QIcon(":/resources/pencil.svg")); - connect(aOpenRemoteDeck, SIGNAL(triggered()), this, SLOT(actOpenRemoteDeck())); - aDownload = new QAction(this); - aDownload->setIcon(QIcon(":/resources/arrow_left_green.svg")); - connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload())); - aNewFolder = new QAction(this); - aNewFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); - connect(aNewFolder, SIGNAL(triggered()), this, SLOT(actNewFolder())); - aDelete = new QAction(this); - aDelete->setIcon(QIcon(":/resources/remove_row.svg")); - connect(aDelete, SIGNAL(triggered()), this, SLOT(actDelete())); - - leftToolBar->addAction(aOpenLocalDeck); - leftToolBar->addAction(aUpload); - rightToolBar->addAction(aOpenRemoteDeck); - rightToolBar->addAction(aDownload); - rightToolBar->addAction(aNewFolder); - rightToolBar->addAction(aDelete); - - retranslateUi(); - setLayout(hbox); + QVBoxLayout *rightVbox = new QVBoxLayout; + rightVbox->addWidget(serverDirView); + rightVbox->addLayout(rightToolBarLayout); + rightGroupBox = new QGroupBox; + rightGroupBox->setLayout(rightVbox); + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addWidget(leftGroupBox); + hbox->addWidget(rightGroupBox); + + aOpenLocalDeck = new QAction(this); + aOpenLocalDeck->setIcon(QIcon(":/resources/pencil.svg")); + connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck())); + aUpload = new QAction(this); + aUpload->setIcon(QIcon(":/resources/arrow_right_green.svg")); + connect(aUpload, SIGNAL(triggered()), this, SLOT(actUpload())); + aDeleteLocalDeck = new QAction(this); + aDeleteLocalDeck->setIcon(QIcon(":/resources/remove_row.svg")); + connect(aDeleteLocalDeck, SIGNAL(triggered()), this, SLOT(actDeleteLocalDeck())); + aOpenRemoteDeck = new QAction(this); + aOpenRemoteDeck->setIcon(QIcon(":/resources/pencil.svg")); + connect(aOpenRemoteDeck, SIGNAL(triggered()), this, SLOT(actOpenRemoteDeck())); + aDownload = new QAction(this); + aDownload->setIcon(QIcon(":/resources/arrow_left_green.svg")); + connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload())); + aNewFolder = new QAction(this); + aNewFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); + connect(aNewFolder, SIGNAL(triggered()), this, SLOT(actNewFolder())); + aDeleteRemoteDeck = new QAction(this); + aDeleteRemoteDeck->setIcon(QIcon(":/resources/remove_row.svg")); + connect(aDeleteRemoteDeck, SIGNAL(triggered()), this, SLOT(actDeleteRemoteDeck())); + + leftToolBar->addAction(aOpenLocalDeck); + leftToolBar->addAction(aUpload); + leftToolBar->addAction(aDeleteLocalDeck); + rightToolBar->addAction(aOpenRemoteDeck); + rightToolBar->addAction(aDownload); + rightToolBar->addAction(aNewFolder); + rightToolBar->addAction(aDeleteRemoteDeck); + + retranslateUi(); + setLayout(hbox); } void TabDeckStorage::retranslateUi() { - leftGroupBox->setTitle(tr("Local file system")); - rightGroupBox->setTitle(tr("Server deck storage")); - - aOpenLocalDeck->setText(tr("Open in deck editor")); - aUpload->setText(tr("Upload deck")); - aOpenRemoteDeck->setText(tr("Open in deck editor")); - aDownload->setText(tr("Download deck")); - aNewFolder->setText(tr("New folder")); - aDelete->setText(tr("Delete")); + leftGroupBox->setTitle(tr("Local file system")); + rightGroupBox->setTitle(tr("Server deck storage")); + + aOpenLocalDeck->setText(tr("Open in deck editor")); + aUpload->setText(tr("Upload deck")); + aOpenRemoteDeck->setText(tr("Open in deck editor")); + aDownload->setText(tr("Download deck")); + aNewFolder->setText(tr("New folder")); + aDeleteLocalDeck->setText(tr("Delete")); + aDeleteRemoteDeck->setText(tr("Delete")); } void TabDeckStorage::actOpenLocalDeck() { - QModelIndex curLeft = sortFilter->mapToSource(localDirView->selectionModel()->currentIndex()); - if (localDirModel->isDir(curLeft)) - return; - QString filePath = localDirModel->filePath(curLeft); - DeckList *deck = new DeckList; - if (!deck->loadFromFile(filePath, DeckList::CockatriceFormat)) - return; - - WndDeckEditor *deckEditor = new WndDeckEditor; - deckEditor->setDeck(deck, filePath, DeckList::CockatriceFormat); - deckEditor->show(); + QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (localDirModel->isDir(curLeft)) + return; + QString filePath = localDirModel->filePath(curLeft); + + DeckLoader deckLoader; + if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat)) + return; + + emit openDeckEditor(&deckLoader); } void TabDeckStorage::actUpload() { - QModelIndex curLeft = sortFilter->mapToSource(localDirView->selectionModel()->currentIndex()); - if (localDirModel->isDir(curLeft)) - return; - QString filePath = localDirModel->filePath(curLeft); - QFile deckFile(filePath); - QFileInfo deckFileInfo(deckFile); - DeckList *deck = new DeckList; - if (!deck->loadFromFile(filePath, DeckList::CockatriceFormat)) - return; - if (deck->getName().isEmpty()) { - bool ok; - QString deckName = QInputDialog::getText(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"), QLineEdit::Normal, deckFileInfo.completeBaseName(), &ok); - if (!ok) - return; - if (deckName.isEmpty()) - deckName = tr("Unnamed deck"); - deck->setName(deckName); - } - - QString targetPath; - RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem(); - if (!curRight) - return; - if (!dynamic_cast(curRight)) - curRight = curRight->getParent(); - targetPath = dynamic_cast(curRight)->getPath(); - - Command_DeckUpload *command = new Command_DeckUpload(deck, targetPath); - connect(command, SIGNAL(finished(ProtocolResponse *)), this, SLOT(uploadFinished(ProtocolResponse *))); - client->sendCommand(command); + QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (localDirModel->isDir(curLeft)) + return; + QString filePath = localDirModel->filePath(curLeft); + QFile deckFile(filePath); + QFileInfo deckFileInfo(deckFile); + DeckLoader deck; + if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) + return; + if (deck.getName().isEmpty()) { + bool ok; + QString deckName = QInputDialog::getText(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"), QLineEdit::Normal, deckFileInfo.completeBaseName(), &ok); + if (!ok) + return; + if (deckName.isEmpty()) + deckName = tr("Unnamed deck"); + deck.setName(deckName); + } + + QString targetPath; + RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem(); + if (!curRight) + return; + if (!dynamic_cast(curRight)) + curRight = curRight->getParent(); + targetPath = dynamic_cast(curRight)->getPath(); + + Command_DeckUpload cmd; + cmd.set_path(targetPath.toStdString()); + cmd.set_deck_list(deck.writeToString_Native().toStdString()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(uploadFinished(Response, CommandContainer))); + client->sendCommand(pend); } -void TabDeckStorage::uploadFinished(ProtocolResponse *r) +void TabDeckStorage::uploadFinished(const Response &r, const CommandContainer &commandContainer) { - Response_DeckUpload *resp = qobject_cast(r); - if (!resp) - return; - Command_DeckUpload *cmd = static_cast(sender()); + if (r.response_code() != Response::RespOk) + return; + + const Response_DeckUpload &resp = r.GetExtension(Response_DeckUpload::ext); + const Command_DeckUpload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckUpload::ext); + + serverDirView->addFileToTree(resp.new_file(), serverDirView->getNodeByPath(QString::fromStdString(cmd.path()))); +} - serverDirView->addFileToTree(resp->getFile(), serverDirView->getNodeByPath(cmd->getPath())); +void TabDeckStorage::actDeleteLocalDeck() +{ + QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + return; + + localDirModel->remove(curLeft); } void TabDeckStorage::actOpenRemoteDeck() { - RemoteDeckList_TreeModel::FileNode *curRight = dynamic_cast(serverDirView->getCurrentItem()); - if (!curRight) - return; - - Command_DeckDownload *command = new Command_DeckDownload(curRight->getId()); - connect(command, SIGNAL(finished(ProtocolResponse *)), this, SLOT(openRemoteDeckFinished(ProtocolResponse *))); - client->sendCommand(command); + RemoteDeckList_TreeModel::FileNode *curRight = dynamic_cast(serverDirView->getCurrentItem()); + if (!curRight) + return; + + Command_DeckDownload cmd; + cmd.set_deck_id(curRight->getId()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(openRemoteDeckFinished(Response, CommandContainer))); + client->sendCommand(pend); } -void TabDeckStorage::openRemoteDeckFinished(ProtocolResponse *r) +void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer) { - Response_DeckDownload *resp = qobject_cast(r); - if (!resp) - return; - - WndDeckEditor *deckEditor = new WndDeckEditor; - deckEditor->setDeck(new DeckList(resp->getDeck())); - deckEditor->show(); + if (r.response_code() != Response::RespOk) + return; + + const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); + const Command_DeckDownload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDownload::ext); + + DeckLoader loader; + if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id())) + return; + + emit openDeckEditor(&loader); } void TabDeckStorage::actDownload() { - QString filePath; - QModelIndex curLeft = sortFilter->mapToSource(localDirView->selectionModel()->currentIndex()); - if (!curLeft.isValid()) - filePath = localDirModel->rootPath(); - else { - while (!localDirModel->isDir(curLeft)) - curLeft = curLeft.parent(); - filePath = localDirModel->filePath(curLeft); - } + QString filePath; + QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (!curLeft.isValid()) + filePath = localDirModel->rootPath(); + else { + while (!localDirModel->isDir(curLeft)) + curLeft = curLeft.parent(); + filePath = localDirModel->filePath(curLeft); + } - RemoteDeckList_TreeModel::FileNode *curRight = dynamic_cast(serverDirView->getCurrentItem()); - if (!curRight) - return; - filePath += QString("/deck_%1.cod").arg(curRight->getId()); - - Command_DeckDownload *command = new Command_DeckDownload(curRight->getId()); - command->setExtraData(filePath); - connect(command, SIGNAL(finished(ProtocolResponse *)), this, SLOT(downloadFinished(ProtocolResponse *))); - client->sendCommand(command); + RemoteDeckList_TreeModel::FileNode *curRight = dynamic_cast(serverDirView->getCurrentItem()); + if (!curRight) + return; + filePath += QString("/deck_%1.cod").arg(curRight->getId()); + + Command_DeckDownload cmd; + cmd.set_deck_id(curRight->getId()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + pend->setExtraData(filePath); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(downloadFinished(Response, CommandContainer, QVariant))); + client->sendCommand(pend); } -void TabDeckStorage::downloadFinished(ProtocolResponse *r) +void TabDeckStorage::downloadFinished(const Response &r, const CommandContainer &/*commandContainer*/, const QVariant &extraData) { - Response_DeckDownload *resp = qobject_cast(r); - if (!resp) - return; - Command_DeckDownload *cmd = static_cast(sender()); - - QString filePath = cmd->getExtraData().toString(); - resp->getDeck()->saveToFile(filePath, DeckList::CockatriceFormat); + if (r.response_code() != Response::RespOk) + return; + + const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); + QString filePath = extraData.toString(); + + DeckLoader deck(QString::fromStdString(resp.deck())); + deck.saveToFile(filePath, DeckLoader::CockatriceFormat); } void TabDeckStorage::actNewFolder() { - QString folderName = QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:")); - if (folderName.isEmpty()) - return; + QString folderName = QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:")); + if (folderName.isEmpty()) + return; - QString targetPath; - RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem(); - if (!curRight) - return; - if (!dynamic_cast(curRight)) - curRight = curRight->getParent(); - RemoteDeckList_TreeModel::DirectoryNode *dir = dynamic_cast(curRight); - targetPath = dir->getPath(); - - Command_DeckNewDir *command = new Command_DeckNewDir(targetPath, folderName); - connect(command, SIGNAL(finished(ResponseCode)), this, SLOT(newFolderFinished(ResponseCode))); - client->sendCommand(command); + QString targetPath; + RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem(); + if (!curRight) + return; + if (!dynamic_cast(curRight)) + curRight = curRight->getParent(); + RemoteDeckList_TreeModel::DirectoryNode *dir = dynamic_cast(curRight); + targetPath = dir->getPath(); + + Command_DeckNewDir cmd; + cmd.set_path(targetPath.toStdString()); + cmd.set_dir_name(folderName.toStdString()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(newFolderFinished(Response, CommandContainer))); + client->sendCommand(pend); } -void TabDeckStorage::newFolderFinished(ResponseCode resp) +void TabDeckStorage::newFolderFinished(const Response &response, const CommandContainer &commandContainer) { - if (resp != RespOk) - return; - - Command_DeckNewDir *cmd = static_cast(sender()); - serverDirView->addFolderToTree(cmd->getDirName(), serverDirView->getNodeByPath(cmd->getPath())); + if (response.response_code() != Response::RespOk) + return; + + const Command_DeckNewDir &cmd = commandContainer.session_command(0).GetExtension(Command_DeckNewDir::ext); + serverDirView->addFolderToTree(QString::fromStdString(cmd.dir_name()), serverDirView->getNodeByPath(QString::fromStdString(cmd.path()))); } -void TabDeckStorage::actDelete() +void TabDeckStorage::actDeleteRemoteDeck() { - Command *command; - RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem(); - if (!curRight) - return; - RemoteDeckList_TreeModel::DirectoryNode *dir = dynamic_cast(curRight); - if (dir) { - QString path = dir->getPath(); - if (path.isEmpty()) - return; - command = new Command_DeckDelDir(path); - } else - command = new Command_DeckDel(dynamic_cast(curRight)->getId()); - connect(command, SIGNAL(finished(ResponseCode)), this, SLOT(deleteFinished(ResponseCode))); - client->sendCommand(command); + PendingCommand *pend; + RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem(); + if (!curRight) + return; + RemoteDeckList_TreeModel::DirectoryNode *dir = dynamic_cast(curRight); + if (dir) { + QString path = dir->getPath(); + if (path.isEmpty()) + return; + if (QMessageBox::warning(this, tr("Delete remote folder"), tr("Are you sure you want to delete \"%1\"?").arg(path), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + return; + Command_DeckDelDir cmd; + cmd.set_path(path.toStdString()); + pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deleteFolderFinished(Response, CommandContainer))); + } else { + RemoteDeckList_TreeModel::FileNode *deckNode = dynamic_cast(curRight); + if (QMessageBox::warning(this, tr("Delete remote deck"), tr("Are you sure you want to delete \"%1\"?").arg(deckNode->getName()), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + return; + + Command_DeckDel cmd; + cmd.set_deck_id(deckNode->getId()); + pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deleteDeckFinished(Response, CommandContainer))); + } + + client->sendCommand(pend); } -void TabDeckStorage::deleteFinished(ResponseCode resp) +void TabDeckStorage::deleteDeckFinished(const Response &response, const CommandContainer &commandContainer) { - if (resp != RespOk) - return; - - RemoteDeckList_TreeModel::Node *toDelete = 0; - Command_DeckDelDir *cmdDelDir = qobject_cast(sender()); - if (cmdDelDir) - toDelete = serverDirView->getNodeByPath(cmdDelDir->getPath()); - else - toDelete = serverDirView->getNodeById(static_cast(sender())->getDeckId()); - - if (toDelete) - serverDirView->removeNode(toDelete); + if (response.response_code() != Response::RespOk) + return; + + const Command_DeckDel &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDel::ext); + RemoteDeckList_TreeModel::Node *toDelete = serverDirView->getNodeById(cmd.deck_id()); + if (toDelete) + serverDirView->removeNode(toDelete); +} + +void TabDeckStorage::deleteFolderFinished(const Response &response, const CommandContainer &commandContainer) +{ + if (response.response_code() != Response::RespOk) + return; + + const Command_DeckDelDir &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDelDir::ext); + RemoteDeckList_TreeModel::Node *toDelete = serverDirView->getNodeByPath(QString::fromStdString(cmd.path())); + if (toDelete) + serverDirView->removeNode(toDelete); } diff --git a/cockatrice/src/tab_deck_storage.h b/cockatrice/src/tab_deck_storage.h index 758ed12b0..c6567ff14 100644 --- a/cockatrice/src/tab_deck_storage.h +++ b/cockatrice/src/tab_deck_storage.h @@ -2,52 +2,56 @@ #define TAB_DECK_STORAGE_H #include "tab.h" -#include "protocol.h" class AbstractClient; class QTreeView; class QFileSystemModel; -class QSortFilterProxyModel; class QToolBar; class QTreeWidget; class QTreeWidgetItem; class QGroupBox; -class ProtocolResponse; class RemoteDeckList_TreeWidget; +class CommandContainer; +class Response; +class DeckLoader; class TabDeckStorage : public Tab { - Q_OBJECT + Q_OBJECT private: - AbstractClient *client; - QTreeView *localDirView; - QFileSystemModel *localDirModel; - QSortFilterProxyModel *sortFilter; - QToolBar *leftToolBar, *rightToolBar; - RemoteDeckList_TreeWidget *serverDirView; - QGroupBox *leftGroupBox, *rightGroupBox; - - QAction *aOpenLocalDeck, *aUpload, *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDelete; + AbstractClient *client; + QTreeView *localDirView; + QFileSystemModel *localDirModel; + QToolBar *leftToolBar, *rightToolBar; + RemoteDeckList_TreeWidget *serverDirView; + QGroupBox *leftGroupBox, *rightGroupBox; + + QAction *aOpenLocalDeck, *aUpload, *aDeleteLocalDeck, *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck; private slots: - void actOpenLocalDeck(); - - void actUpload(); - void uploadFinished(ProtocolResponse *r); + void actOpenLocalDeck(); + + void actUpload(); + void uploadFinished(const Response &r, const CommandContainer &commandContainer); + + void actDeleteLocalDeck(); + + void actOpenRemoteDeck(); + void openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer); + + void actDownload(); + void downloadFinished(const Response &r, const CommandContainer &commandContainer, const QVariant &extraData); - void actOpenRemoteDeck(); - void openRemoteDeckFinished(ProtocolResponse *r); - - void actDownload(); - void downloadFinished(ProtocolResponse *r); + void actNewFolder(); + void newFolderFinished(const Response &response, const CommandContainer &commandContainer); - void actNewFolder(); - void newFolderFinished(ResponseCode resp); - - void actDelete(); - void deleteFinished(ResponseCode resp); + void actDeleteRemoteDeck(); + void deleteFolderFinished(const Response &response, const CommandContainer &commandContainer); + void deleteDeckFinished(const Response &response, const CommandContainer &commandContainer); public: - TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client); - void retranslateUi(); - QString getTabText() const { return tr("Deck storage"); } + TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client); + void retranslateUi(); + QString getTabText() const { return tr("Deck storage"); } +signals: + void openDeckEditor(const DeckLoader *deckLoader); }; #endif diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index 4040e8f3f..69d619258 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -5,6 +5,11 @@ #include #include #include +#include +#include +#include + +#include "dlg_creategame.h" #include "tab_game.h" #include "tab_supervisor.h" #include "cardinfowidget.h" @@ -17,8 +22,7 @@ #include "zoneviewzone.h" #include "zoneviewwidget.h" #include "deckview.h" -#include "decklist.h" -#include "protocol_items.h" +#include "deck_loader.h" #include "dlg_load_remote_deck.h" #include "abstractclient.h" #include "carditem.h" @@ -26,771 +30,1169 @@ #include "main.h" #include "settingscache.h" #include "carddatabase.h" +#include "replay_timeline_widget.h" -ReadyStartButton::ReadyStartButton(QWidget *parent) - : QPushButton(parent), readyStart(false) +#include +#include "pending_command.h" +#include "pb/game_replay.pb.h" +#include "pb/command_concede.pb.h" +#include "pb/command_deck_select.pb.h" +#include "pb/command_ready_start.pb.h" +#include "pb/command_set_sideboard_plan.pb.h" +#include "pb/command_set_sideboard_lock.pb.h" +#include "pb/command_leave_game.pb.h" +#include "pb/command_game_say.pb.h" +#include "pb/command_set_active_phase.pb.h" +#include "pb/command_next_turn.pb.h" +#include "pb/command_delete_arrow.pb.h" +#include "pb/response_deck_download.pb.h" +#include "pb/game_event_container.pb.h" +#include "pb/event_game_joined.pb.h" +#include "pb/event_game_say.pb.h" +#include "pb/event_game_state_changed.pb.h" +#include "pb/event_player_properties_changed.pb.h" +#include "pb/event_join.pb.h" +#include "pb/event_leave.pb.h" +#include "pb/event_kicked.pb.h" +#include "pb/event_game_host_changed.pb.h" +#include "pb/event_game_closed.pb.h" +#include "pb/event_set_active_player.pb.h" +#include "pb/event_set_active_phase.pb.h" +#include "pb/context_deck_select.pb.h" +#include "pb/context_connection_state_changed.pb.h" +#include "pb/context_ping_changed.pb.h" +#include "get_pb_extension.h" + +ToggleButton::ToggleButton(QWidget *parent) + : QPushButton(parent), state(false) { } -void ReadyStartButton::paintEvent(QPaintEvent *event) +void ToggleButton::paintEvent(QPaintEvent *event) { - QPushButton::paintEvent(event); - - QPainter painter(this); - if (readyStart) - painter.setPen(QPen(Qt::green, 3)); - else - painter.setPen(QPen(Qt::red, 3)); - painter.drawRect(1.5, 1.5, width() - 3, height() - 3); + QPushButton::paintEvent(event); + + QPainter painter(this); + if (state) + painter.setPen(QPen(Qt::green, 3)); + else + painter.setPen(QPen(Qt::red, 3)); + painter.drawRect(1, 1, width() - 3, height() - 3); } -void ReadyStartButton::setReadyStart(bool _readyStart) +void ToggleButton::setState(bool _state) { - readyStart = _readyStart; - update(); + state = _state; + emit stateChanged(); + update(); } -DeckViewContainer::DeckViewContainer(AbstractClient *_client, TabGame *parent) - : QWidget(parent), client(_client) +DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) + : QWidget(parent), playerId(_playerId) { - loadLocalButton = new QPushButton; - loadRemoteButton = new QPushButton; - readyStartButton = new ReadyStartButton; - readyStartButton->setEnabled(false); - - connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck())); - connect(loadRemoteButton, SIGNAL(clicked()), this, SLOT(loadRemoteDeck())); - connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart())); - - QHBoxLayout *buttonHBox = new QHBoxLayout; - buttonHBox->addWidget(loadLocalButton); - buttonHBox->addWidget(loadRemoteButton); - buttonHBox->addWidget(readyStartButton); - buttonHBox->addStretch(); - deckView = new DeckView; - connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); - connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged())); - - QVBoxLayout *deckViewLayout = new QVBoxLayout; - deckViewLayout->addLayout(buttonHBox); - deckViewLayout->addWidget(deckView); - setLayout(deckViewLayout); - - retranslateUi(); + loadLocalButton = new QPushButton; + loadRemoteButton = new QPushButton; + readyStartButton = new ToggleButton; + readyStartButton->setEnabled(false); + sideboardLockButton = new ToggleButton; + sideboardLockButton->setEnabled(false); + + connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck())); + connect(loadRemoteButton, SIGNAL(clicked()), this, SLOT(loadRemoteDeck())); + connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart())); + connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked())); + connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText())); + + QHBoxLayout *buttonHBox = new QHBoxLayout; + buttonHBox->addWidget(loadLocalButton); + buttonHBox->addWidget(loadRemoteButton); + buttonHBox->addWidget(readyStartButton); + buttonHBox->addWidget(sideboardLockButton); + buttonHBox->addStretch(); + deckView = new DeckView; + connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); + connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged())); + + QVBoxLayout *deckViewLayout = new QVBoxLayout; + deckViewLayout->addLayout(buttonHBox); + deckViewLayout->addWidget(deckView); + setLayout(deckViewLayout); + + retranslateUi(); } void DeckViewContainer::retranslateUi() { - loadLocalButton->setText(tr("Load &local deck")); - loadRemoteButton->setText(tr("Load d&eck from server")); - readyStartButton->setText(tr("Ready to s&tart")); + loadLocalButton->setText(tr("Load &local deck")); + loadRemoteButton->setText(tr("Load d&eck from server")); + readyStartButton->setText(tr("Ready to s&tart")); + updateSideboardLockButtonText(); } void DeckViewContainer::setButtonsVisible(bool _visible) { - loadLocalButton->setVisible(_visible); - loadRemoteButton->setVisible(_visible); - readyStartButton->setVisible(_visible); + loadLocalButton->setVisible(_visible); + loadRemoteButton->setVisible(_visible); + readyStartButton->setVisible(_visible); + sideboardLockButton->setVisible(_visible); +} + +void DeckViewContainer::updateSideboardLockButtonText() +{ + if (sideboardLockButton->getState()) + sideboardLockButton->setText(tr("S&ideboard unlocked")); + else + sideboardLockButton->setText(tr("S&ideboard locked")); } void DeckViewContainer::loadLocalDeck() { - QFileDialog dialog(this, tr("Load deck")); - dialog.setDirectory(settingsCache->getDeckPath()); - dialog.setNameFilters(DeckList::fileNameFilters); - if (!dialog.exec()) - return; + QFileDialog dialog(this, tr("Load deck")); + dialog.setDirectory(settingsCache->getDeckPath()); + dialog.setNameFilters(DeckLoader::fileNameFilters); + if (!dialog.exec()) + return; - QString fileName = dialog.selectedFiles().at(0); - DeckList::FileFormat fmt = DeckList::getFormatFromNameFilter(dialog.selectedNameFilter()); - DeckList *deck = new DeckList; - if (!deck->loadFromFile(fileName, fmt)) { - delete deck; - // Error message - return; - } - - Command_DeckSelect *cmd = new Command_DeckSelect(static_cast(parent())->getGameId(), deck, -1); - connect(cmd, SIGNAL(finished(ProtocolResponse *)), this, SLOT(deckSelectFinished(ProtocolResponse *))); - client->sendCommand(cmd); + QString fileName = dialog.selectedFiles().at(0); + DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); + DeckLoader deck; + if (!deck.loadFromFile(fileName, fmt)) { + QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded.")); + return; + } + + Command_DeckSelect cmd; + cmd.set_deck(deck.writeToString_Native().toStdString()); + PendingCommand *pend = static_cast(parent())->prepareGameCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deckSelectFinished(const Response &))); + static_cast(parent())->sendGameCommand(pend, playerId); } void DeckViewContainer::loadRemoteDeck() { - DlgLoadRemoteDeck dlg(client); - if (dlg.exec()) { - Command_DeckSelect *cmd = new Command_DeckSelect(static_cast(parent())->getGameId(), 0, dlg.getDeckId()); - connect(cmd, SIGNAL(finished(ProtocolResponse *)), this, SLOT(deckSelectFinished(ProtocolResponse *))); - client->sendCommand(cmd); - } + DlgLoadRemoteDeck dlg(static_cast(parent())->getClientForPlayer(playerId)); + if (dlg.exec()) { + Command_DeckSelect cmd; + cmd.set_deck_id(dlg.getDeckId()); + PendingCommand *pend = static_cast(parent())->prepareGameCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deckSelectFinished(const Response &))); + static_cast(parent())->sendGameCommand(pend, playerId); + } } -void DeckViewContainer::deckSelectFinished(ProtocolResponse *r) +void DeckViewContainer::deckSelectFinished(const Response &r) { - Response_DeckDownload *resp = qobject_cast(r); - if (!resp) - return; - - db->cacheCardPixmaps(resp->getDeck()->getCardList()); - deckView->setDeck(new DeckList(resp->getDeck())); - readyStartButton->setEnabled(true); + const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); + DeckLoader newDeck(QString::fromStdString(resp.deck())); + db->cacheCardPixmaps(newDeck.getCardList()); + setDeck(newDeck); } void DeckViewContainer::readyStart() { - client->sendCommand(new Command_ReadyStart(static_cast(parent())->getGameId(), !readyStartButton->getReadyStart())); + Command_ReadyStart cmd; + cmd.set_ready(!readyStartButton->getState()); + static_cast(parent())->sendGameCommand(cmd, playerId); +} + +void DeckViewContainer::sideboardLockButtonClicked() +{ + Command_SetSideboardLock cmd; + cmd.set_locked(sideboardLockButton->getState()); + + static_cast(parent())->sendGameCommand(cmd, playerId); } void DeckViewContainer::sideboardPlanChanged() { - QList newPlan = deckView->getSideboardPlan(); - client->sendCommand(new Command_SetSideboardPlan(static_cast(parent())->getGameId(), newPlan)); + Command_SetSideboardPlan cmd; + const QList &newPlan = deckView->getSideboardPlan(); + for (int i = 0; i < newPlan.size(); ++i) + cmd.add_move_list()->CopyFrom(newPlan[i]); + static_cast(parent())->sendGameCommand(cmd, playerId); } void DeckViewContainer::setReadyStart(bool ready) { - readyStartButton->setReadyStart(ready); - deckView->setLocked(ready); + readyStartButton->setState(ready); + deckView->setLocked(ready || !sideboardLockButton->getState()); } -void DeckViewContainer::setDeck(DeckList *deck) +void DeckViewContainer::setSideboardLocked(bool locked) { - deckView->setDeck(deck); - readyStartButton->setEnabled(true); + sideboardLockButton->setState(!locked); + deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState()); + if (locked) + deckView->resetSideboardPlan(); } -TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_clients, int _gameId, const QString &_gameDescription, int _hostId, int _localPlayerId, bool _spectator, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, bool _resuming) - : Tab(_tabSupervisor), clients(_clients), gameId(_gameId), gameDescription(_gameDescription), hostId(_hostId), localPlayerId(_localPlayerId), spectator(_spectator), spectatorsCanTalk(_spectatorsCanTalk), spectatorsSeeEverything(_spectatorsSeeEverything), gameStateKnown(false), started(false), resuming(_resuming), currentPhase(-1) +void DeckViewContainer::setDeck(const DeckLoader &deck) { - phasesToolbar = new PhasesToolbar; - phasesToolbar->hide(); - connect(phasesToolbar, SIGNAL(sendGameCommand(GameCommand *, int)), this, SLOT(sendGameCommand(GameCommand *, int))); - - scene = new GameScene(phasesToolbar, this); - gameView = new GameView(scene); - gameView->hide(); - - cardInfo = new CardInfoWidget(CardInfoWidget::ModeGameTab); - playerListWidget = new PlayerListWidget(tabSupervisor, clients.first(), this); - playerListWidget->setFocusPolicy(Qt::NoFocus); - connect(playerListWidget, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); - - timeElapsedLabel = new QLabel; - timeElapsedLabel->setAlignment(Qt::AlignCenter); - messageLog = new MessageLogWidget(tabSupervisor->getUserInfo()->getName(), tabSupervisor->getUserInfo()->getGender() == ServerInfo_User::Female); - connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfo, SLOT(setCard(QString))); - connect(messageLog, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); - connect(messageLog, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); - sayLabel = new QLabel; - sayEdit = new QLineEdit; - sayLabel->setBuddy(sayEdit); + deckView->setDeck(deck); + readyStartButton->setEnabled(true); + sideboardLockButton->setState(false); + sideboardLockButton->setEnabled(true); +} - QHBoxLayout *hLayout = new QHBoxLayout; - hLayout->addWidget(sayLabel); - hLayout->addWidget(sayEdit); - - deckViewContainerLayout = new QVBoxLayout; +TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) + : Tab(_tabSupervisor), + secondsElapsed(0), + hostId(-1), + localPlayerId(-1), + spectator(true), + gameStateKnown(false), + resuming(false), + currentPhase(-1), + activeCard(0), + gameClosed(false), + replay(_replay), + currentReplayStep(0) +{ + setAttribute(Qt::WA_DeleteOnClose); + + gameInfo.CopyFrom(replay->game_info()); + gameInfo.set_spectators_omniscient(true); + + // Create list: event number -> time [ms] + // Distribute simultaneous events evenly across 1 second. + unsigned int lastEventTimestamp = 0; + const int eventCount = replay->event_list_size(); + for (int i = 0; i < eventCount; ++i) { + int j = i + 1; + while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp)) + ++j; + + const int numberEventsThisSecond = j - i; + for (int k = 0; k < numberEventsThisSecond; ++k) + replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + (int) ((qreal) k / (qreal) numberEventsThisSecond * 1000)); + + if (j < eventCount) + lastEventTimestamp = replay->event_list(j).seconds_elapsed(); + i += numberEventsThisSecond - 1; + } + + phasesToolbar = new PhasesToolbar; + + scene = new GameScene(phasesToolbar, this); + gameView = new GameView(scene); + gameView->hide(); + + cardInfo = new CardInfoWidget(CardInfoWidget::ModeGameTab); + playerListWidget = new PlayerListWidget(0, 0, this); + playerListWidget->setFocusPolicy(Qt::NoFocus); + + timeElapsedLabel = new QLabel; + timeElapsedLabel->setAlignment(Qt::AlignCenter); + messageLog = new MessageLogWidget(tabSupervisor, this); + connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfo, SLOT(setCard(QString))); + connect(messageLog, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(messageLog, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); + sayLabel = 0; - QVBoxLayout *messageLogLayout = new QVBoxLayout; - messageLogLayout->addWidget(timeElapsedLabel); - messageLogLayout->addWidget(messageLog); - messageLogLayout->addLayout(hLayout); - - QWidget *messageLogLayoutWidget = new QWidget; - messageLogLayoutWidget->setLayout(messageLogLayout); - - splitter = new QSplitter(Qt::Vertical); - splitter->addWidget(cardInfo); - splitter->addWidget(playerListWidget); - splitter->addWidget(messageLogLayoutWidget); + deckViewContainerLayout = new QVBoxLayout; - mainLayout = new QHBoxLayout; - mainLayout->addWidget(gameView, 10); - mainLayout->addLayout(deckViewContainerLayout, 10); - mainLayout->addWidget(splitter); + QVBoxLayout *messageLogLayout = new QVBoxLayout; + messageLogLayout->addWidget(timeElapsedLabel); + messageLogLayout->addWidget(messageLog); + + QWidget *messageLogLayoutWidget = new QWidget; + messageLogLayoutWidget->setLayout(messageLogLayout); + + timelineWidget = new ReplayTimelineWidget; + timelineWidget->setTimeline(replayTimeline); + connect(timelineWidget, SIGNAL(processNextEvent()), this, SLOT(replayNextEvent())); + connect(timelineWidget, SIGNAL(replayFinished()), this, SLOT(replayFinished())); + + replayToStartButton = new QToolButton; + replayToStartButton->setIconSize(QSize(32, 32)); + replayToStartButton->setIcon(QIcon(":/resources/replay_tostart.svg")); + connect(replayToStartButton, SIGNAL(clicked()), this, SLOT(replayToStartButtonClicked())); + replayStartButton = new QToolButton; + replayStartButton->setIconSize(QSize(32, 32)); + replayStartButton->setIcon(QIcon(":/resources/replay_start.svg")); + connect(replayStartButton, SIGNAL(clicked()), this, SLOT(replayStartButtonClicked())); + replayPauseButton = new QToolButton; + replayPauseButton->setIconSize(QSize(32, 32)); + replayPauseButton->setEnabled(false); + replayPauseButton->setIcon(QIcon(":/resources/replay_pause.svg")); + connect(replayPauseButton, SIGNAL(clicked()), this, SLOT(replayPauseButtonClicked())); + replayStopButton = new QToolButton; + replayStopButton->setIconSize(QSize(32, 32)); + replayStopButton->setEnabled(false); + replayStopButton->setIcon(QIcon(":/resources/replay_stop.svg")); + connect(replayStopButton, SIGNAL(clicked()), this, SLOT(replayStopButtonClicked())); + replayFastForwardButton = new QToolButton; + replayFastForwardButton->setIconSize(QSize(32, 32)); + replayFastForwardButton->setEnabled(false); + replayFastForwardButton->setIcon(QIcon(":/resources/replay_fastforward.svg")); + replayFastForwardButton->setCheckable(true); + connect(replayFastForwardButton, SIGNAL(toggled(bool)), this, SLOT(replayFastForwardButtonToggled(bool))); + replayToEndButton = new QToolButton; + replayToEndButton->setIconSize(QSize(32, 32)); + replayToEndButton->setIcon(QIcon(":/resources/replay_toend.svg")); + connect(replayStopButton, SIGNAL(clicked()), this, SLOT(replayToEndButtonClicked())); + + splitter = new QSplitter(Qt::Vertical); + splitter->addWidget(cardInfo); + splitter->addWidget(playerListWidget); + splitter->addWidget(messageLogLayoutWidget); - if (spectator && !spectatorsCanTalk && tabSupervisor->getAdminLocked()) { - sayLabel->hide(); - sayEdit->hide(); - } - connect(tabSupervisor, SIGNAL(adminLockChanged(bool)), this, SLOT(adminLockChanged(bool))); - connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(actSay())); + mainLayout = new QHBoxLayout; + mainLayout->addWidget(gameView, 10); + mainLayout->addLayout(deckViewContainerLayout, 10); + mainLayout->addWidget(splitter); + + QHBoxLayout *replayControlLayout = new QHBoxLayout; + replayControlLayout->addWidget(timelineWidget, 10); + replayControlLayout->addWidget(replayToStartButton); + replayControlLayout->addWidget(replayStartButton); + replayControlLayout->addWidget(replayPauseButton); + replayControlLayout->addWidget(replayStopButton); + replayControlLayout->addWidget(replayFastForwardButton); + replayControlLayout->addWidget(replayToEndButton); + + QVBoxLayout *superMainLayout = new QVBoxLayout; + superMainLayout->addLayout(mainLayout); + superMainLayout->addLayout(replayControlLayout); + + aNextPhase = 0; + aNextTurn = 0; + aRemoveLocalArrows = 0; + aGameInfo = 0; + aConcede = 0; + aLeaveGame = 0; + aCloseReplay = new QAction(this); + connect(aCloseReplay, SIGNAL(triggered()), this, SLOT(actLeaveGame())); + + phasesMenu = 0; + gameMenu = new QMenu(this); + gameMenu->addAction(aCloseReplay); + addTabMenu(gameMenu); + + retranslateUi(); + setLayout(superMainLayout); - // Menu actions - aNextPhase = new QAction(this); - connect(aNextPhase, SIGNAL(triggered()), this, SLOT(actNextPhase())); - aNextTurn = new QAction(this); - connect(aNextTurn, SIGNAL(triggered()), this, SLOT(actNextTurn())); - aRemoveLocalArrows = new QAction(this); - connect(aRemoveLocalArrows, SIGNAL(triggered()), this, SLOT(actRemoveLocalArrows())); - aConcede = new QAction(this); - connect(aConcede, SIGNAL(triggered()), this, SLOT(actConcede())); - aLeaveGame = new QAction(this); - connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame())); - - phasesMenu = new QMenu(this); - for (int i = 0; i < phasesToolbar->phaseCount(); ++i) { - QAction *temp = new QAction(QString(), this); - connect(temp, SIGNAL(triggered()), this, SLOT(actPhaseAction())); - switch (i) { - case 0: temp->setShortcut(tr("F5")); break; - case 2: temp->setShortcut(tr("F6")); break; - case 3: temp->setShortcut(tr("F7")); break; - case 4: temp->setShortcut(tr("F8")); break; - case 9: temp->setShortcut(tr("F9")); break; - case 10: temp->setShortcut(tr("F10")); break; - default: ; - } - phasesMenu->addAction(temp); - phaseActions.append(temp); - } - phasesMenu->addSeparator(); - phasesMenu->addAction(aNextPhase); - - tabMenu = new QMenu(this); - playersSeparator = tabMenu->addSeparator(); - tabMenu->addMenu(phasesMenu); - tabMenu->addAction(aNextTurn); - tabMenu->addSeparator(); - tabMenu->addAction(aRemoveLocalArrows); - tabMenu->addSeparator(); - tabMenu->addAction(aConcede); - tabMenu->addAction(aLeaveGame); - - retranslateUi(); - setLayout(mainLayout); + splitter->restoreState(settingsCache->getTabGameSplitterSizes()); + + messageLog->logReplayStarted(gameInfo.game_id()); +} - splitter->restoreState(settingsCache->getTabGameSplitterSizes()); - - messageLog->logGameJoined(gameId); +TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_clients, const Event_GameJoined &event, const QMap &_roomGameTypes) + : Tab(_tabSupervisor), + clients(_clients), + gameInfo(event.game_info()), + roomGameTypes(_roomGameTypes), + hostId(event.host_id()), + localPlayerId(event.player_id()), + spectator(event.spectator()), + gameStateKnown(false), + resuming(event.resuming()), + currentPhase(-1), + activeCard(0), + gameClosed(false), + replay(0) +{ + gameInfo.set_started(false); + + gameTimer = new QTimer(this); + gameTimer->setInterval(1000); + connect(gameTimer, SIGNAL(timeout()), this, SLOT(incrementGameTime())); + gameTimer->start(); + + phasesToolbar = new PhasesToolbar; + connect(phasesToolbar, SIGNAL(sendGameCommand(const ::google::protobuf::Message &, int)), this, SLOT(sendGameCommand(const ::google::protobuf::Message &, int))); + + scene = new GameScene(phasesToolbar, this); + gameView = new GameView(scene); + gameView->hide(); + + cardInfo = new CardInfoWidget(CardInfoWidget::ModeGameTab); + playerListWidget = new PlayerListWidget(tabSupervisor, clients.first(), this); + playerListWidget->setFocusPolicy(Qt::NoFocus); + connect(playerListWidget, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); + + timeElapsedLabel = new QLabel; + timeElapsedLabel->setAlignment(Qt::AlignCenter); + messageLog = new MessageLogWidget(tabSupervisor, this); + connect(messageLog, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); + connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfo, SLOT(setCard(QString))); + connect(messageLog, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(messageLog, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); + sayLabel = new QLabel; + sayEdit = new QLineEdit; + sayLabel->setBuddy(sayEdit); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->addWidget(sayLabel); + hLayout->addWidget(sayEdit); + + deckViewContainerLayout = new QVBoxLayout; + + QVBoxLayout *messageLogLayout = new QVBoxLayout; + messageLogLayout->addWidget(timeElapsedLabel); + messageLogLayout->addWidget(messageLog); + messageLogLayout->addLayout(hLayout); + + QWidget *messageLogLayoutWidget = new QWidget; + messageLogLayoutWidget->setLayout(messageLogLayout); + + splitter = new QSplitter(Qt::Vertical); + splitter->addWidget(cardInfo); + splitter->addWidget(playerListWidget); + splitter->addWidget(messageLogLayoutWidget); + + mainLayout = new QHBoxLayout; + mainLayout->addWidget(gameView, 10); + mainLayout->addLayout(deckViewContainerLayout, 10); + mainLayout->addWidget(splitter); + + if (spectator && !gameInfo.spectators_can_chat() && tabSupervisor->getAdminLocked()) { + sayLabel->hide(); + sayEdit->hide(); + } + connect(tabSupervisor, SIGNAL(adminLockChanged(bool)), this, SLOT(adminLockChanged(bool))); + connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(actSay())); + + // Menu actions + aNextPhase = new QAction(this); + connect(aNextPhase, SIGNAL(triggered()), this, SLOT(actNextPhase())); + aNextTurn = new QAction(this); + connect(aNextTurn, SIGNAL(triggered()), this, SLOT(actNextTurn())); + aRemoveLocalArrows = new QAction(this); + connect(aRemoveLocalArrows, SIGNAL(triggered()), this, SLOT(actRemoveLocalArrows())); + aGameInfo = new QAction(this); + connect(aGameInfo, SIGNAL(triggered()), this, SLOT(actGameInfo())); + aConcede = new QAction(this); + connect(aConcede, SIGNAL(triggered()), this, SLOT(actConcede())); + aLeaveGame = new QAction(this); + connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame())); + aCloseReplay = 0; + + phasesMenu = new QMenu(this); + for (int i = 0; i < phasesToolbar->phaseCount(); ++i) { + QAction *temp = new QAction(QString(), this); + connect(temp, SIGNAL(triggered()), this, SLOT(actPhaseAction())); + switch (i) { + case 0: temp->setShortcut(tr("F5")); break; + case 2: temp->setShortcut(tr("F6")); break; + case 3: temp->setShortcut(tr("F7")); break; + case 4: temp->setShortcut(tr("F8")); break; + case 9: temp->setShortcut(tr("F9")); break; + case 10: temp->setShortcut(tr("F10")); break; + default: ; + } + phasesMenu->addAction(temp); + phaseActions.append(temp); + } + phasesMenu->addSeparator(); + phasesMenu->addAction(aNextPhase); + + gameMenu = new QMenu(this); + playersSeparator = gameMenu->addSeparator(); + gameMenu->addMenu(phasesMenu); + gameMenu->addAction(aNextTurn); + gameMenu->addSeparator(); + gameMenu->addAction(aRemoveLocalArrows); + gameMenu->addSeparator(); + gameMenu->addAction(aGameInfo); + gameMenu->addAction(aConcede); + gameMenu->addAction(aLeaveGame); + addTabMenu(gameMenu); + + retranslateUi(); + setLayout(mainLayout); + + splitter->restoreState(settingsCache->getTabGameSplitterSizes()); + + messageLog->logGameJoined(gameInfo.game_id()); } TabGame::~TabGame() { - settingsCache->setTabGameSplitterSizes(splitter->saveState()); + delete replay; + settingsCache->setTabGameSplitterSizes(splitter->saveState()); - QMapIterator i(players); - while (i.hasNext()) - delete i.next().value(); - players.clear(); - - delete deckViewContainerLayout; - - emit gameClosing(this); + QMapIterator i(players); + while (i.hasNext()) + delete i.next().value(); + players.clear(); + + delete deckViewContainerLayout; + + emit gameClosing(this); } void TabGame::retranslateUi() { - for (int i = 0; i < phaseActions.size(); ++i) - phaseActions[i]->setText(phasesToolbar->getLongPhaseName(i)); - phasesMenu->setTitle(tr("&Phases")); - - tabMenu->setTitle(tr("&Game")); - aNextPhase->setText(tr("Next &phase")); - aNextPhase->setShortcut(tr("Ctrl+Space")); - aNextTurn->setText(tr("Next &turn")); - aNextTurn->setShortcuts(QList() << QKeySequence(tr("Ctrl+Return")) << QKeySequence(tr("Ctrl+Enter"))); - aRemoveLocalArrows->setText(tr("&Remove all local arrows")); - aRemoveLocalArrows->setShortcut(tr("Ctrl+R")); - aConcede->setText(tr("&Concede")); - aConcede->setShortcut(tr("F2")); - aLeaveGame->setText(tr("&Leave game")); - aLeaveGame->setShortcut(tr("Ctrl+Q")); - - sayLabel->setText(tr("&Say:")); - cardInfo->retranslateUi(); + if (phasesMenu) { + for (int i = 0; i < phaseActions.size(); ++i) + phaseActions[i]->setText(phasesToolbar->getLongPhaseName(i)); + phasesMenu->setTitle(tr("&Phases")); + } + + gameMenu->setTitle(tr("&Game")); + if (aNextPhase) { + aNextPhase->setText(tr("Next &phase")); + aNextPhase->setShortcuts(QList() << QKeySequence(tr("Ctrl+Space")) << QKeySequence(tr("Tab"))); + } + if (aNextTurn) { + aNextTurn->setText(tr("Next &turn")); + aNextTurn->setShortcuts(QList() << QKeySequence(tr("Ctrl+Return")) << QKeySequence(tr("Ctrl+Enter"))); + } + if (aRemoveLocalArrows) { + aRemoveLocalArrows->setText(tr("&Remove all local arrows")); + aRemoveLocalArrows->setShortcut(tr("Ctrl+R")); + } + if (aGameInfo) + aGameInfo->setText(tr("Game &information")); + if (aConcede) { + aConcede->setText(tr("&Concede")); + aConcede->setShortcut(tr("F2")); + } + if (aLeaveGame) { + aLeaveGame->setText(tr("&Leave game")); + aLeaveGame->setShortcut(tr("Ctrl+Q")); + } + if (aCloseReplay) { + aCloseReplay->setText(tr("C&lose replay")); + aCloseReplay->setShortcut(tr("Ctrl+Q")); + } + + if (sayLabel) + sayLabel->setText(tr("&Say:")); + cardInfo->retranslateUi(); - QMapIterator i(players); - while (i.hasNext()) - i.next().value()->retranslateUi(); - QMapIterator j(deckViewContainers); - while (j.hasNext()) - j.next().value()->retranslateUi(); - - scene->retranslateUi(); + QMapIterator i(players); + while (i.hasNext()) + i.next().value()->retranslateUi(); + QMapIterator j(deckViewContainers); + while (j.hasNext()) + j.next().value()->retranslateUi(); + + scene->retranslateUi(); } void TabGame::closeRequest() { - actLeaveGame(); + actLeaveGame(); +} + +void TabGame::replayNextEvent() +{ + processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), 0); +} + +void TabGame::replayFinished() +{ + replayStartButton->setEnabled(true); + replayPauseButton->setEnabled(false); + replayStopButton->setEnabled(false); + replayFastForwardButton->setEnabled(false); +} + +void TabGame::replayToStartButtonClicked() +{ + // XXX +} + +void TabGame::replayStartButtonClicked() +{ + replayStartButton->setEnabled(false); + replayPauseButton->setEnabled(true); + replayStopButton->setEnabled(true); + replayFastForwardButton->setEnabled(true); + + timelineWidget->startReplay(); +} + +void TabGame::replayPauseButtonClicked() +{ + replayStartButton->setEnabled(true); + replayPauseButton->setEnabled(false); + replayFastForwardButton->setEnabled(false); + + timelineWidget->stopReplay(); +} + +void TabGame::replayStopButtonClicked() +{ + replayStartButton->setEnabled(true); + replayPauseButton->setEnabled(false); + replayStopButton->setEnabled(false); + replayFastForwardButton->setEnabled(false); + + timelineWidget->stopReplay(); + // XXX to start +} + +void TabGame::replayFastForwardButtonToggled(bool checked) +{ + timelineWidget->setTimeScaleFactor(checked ? 10.0 : 1.0); +} + +void TabGame::replayToEndButtonClicked() +{ + // XXX +} + +void TabGame::incrementGameTime() +{ + int seconds = ++secondsElapsed; + int minutes = seconds / 60; + seconds -= minutes * 60; + int hours = minutes / 60; + minutes -= hours * 60; + + timeElapsedLabel->setText(QString::number(hours).rightJustified(2, '0') + ":" + QString::number(minutes).rightJustified(2, '0') + ":" + QString::number(seconds).rightJustified(2, '0')); } void TabGame::adminLockChanged(bool lock) { - bool v = !(spectator && !spectatorsCanTalk && lock); - sayLabel->setVisible(v); - sayEdit->setVisible(v); + bool v = !(spectator && !gameInfo.spectators_can_chat() && lock); + sayLabel->setVisible(v); + sayEdit->setVisible(v); +} + +void TabGame::actGameInfo() +{ + DlgCreateGame dlg(gameInfo, roomGameTypes); + dlg.exec(); } void TabGame::actConcede() { - if (QMessageBox::question(this, tr("Concede"), tr("Are you sure you want to concede this game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) - return; + if (QMessageBox::question(this, tr("Concede"), tr("Are you sure you want to concede this game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + return; - sendGameCommand(new Command_Concede); + sendGameCommand(Command_Concede()); } void TabGame::actLeaveGame() { - if (!spectator) - if (QMessageBox::question(this, tr("Leave game"), tr("Are you sure you want to leave this game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) - return; - - sendGameCommand(new Command_LeaveGame); - deleteLater(); + if (!gameClosed) { + if (!spectator) + if (QMessageBox::question(this, tr("Leave game"), tr("Are you sure you want to leave this game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) + return; + + if (!replay) + sendGameCommand(Command_LeaveGame()); + } + deleteLater(); } void TabGame::actSay() { - if (!sayEdit->text().isEmpty()) { - sendGameCommand(new Command_Say(-1, sayEdit->text())); - sayEdit->clear(); - } + if (!sayEdit->text().isEmpty()) { + Command_GameSay cmd; + cmd.set_message(sayEdit->text().toStdString()); + sendGameCommand(cmd); + sayEdit->clear(); + } } void TabGame::actPhaseAction() { - int phase = phaseActions.indexOf(static_cast(sender())); - emit sendGameCommand(new Command_SetActivePhase(-1, phase), -1); + int phase = phaseActions.indexOf(static_cast(sender())); + Command_SetActivePhase cmd; + cmd.set_phase(phase); + sendGameCommand(cmd); } void TabGame::actNextPhase() { - int phase = currentPhase; - if (++phase >= phasesToolbar->phaseCount()) - phase = 0; - sendGameCommand(new Command_SetActivePhase(-1, phase)); + int phase = currentPhase; + if (++phase >= phasesToolbar->phaseCount()) + phase = 0; + Command_SetActivePhase cmd; + cmd.set_phase(phase); + sendGameCommand(cmd); } void TabGame::actNextTurn() { - sendGameCommand(new Command_NextTurn); + sendGameCommand(Command_NextTurn()); } void TabGame::actRemoveLocalArrows() { - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Player *player = playerIterator.next().value(); - if (!player->getLocal()) - continue; - QMapIterator arrowIterator(player->getArrows()); - while (arrowIterator.hasNext()) { - ArrowItem *a = arrowIterator.next().value(); - sendGameCommand(new Command_DeleteArrow(-1, a->getId())); - } - } + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Player *player = playerIterator.next().value(); + if (!player->getLocal()) + continue; + QMapIterator arrowIterator(player->getArrows()); + while (arrowIterator.hasNext()) { + ArrowItem *a = arrowIterator.next().value(); + Command_DeleteArrow cmd; + cmd.set_arrow_id(a->getId()); + sendGameCommand(cmd); + } + } } -Player *TabGame::addPlayer(int playerId, ServerInfo_User *info) +Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) { - bool local = ((clients.size() > 1) || (playerId == localPlayerId)); - Player *newPlayer = new Player(info, playerId, local, this); - scene->addPlayer(newPlayer); + bool local = ((clients.size() > 1) || (playerId == localPlayerId)); + Player *newPlayer = new Player(info, playerId, local, this); + connect(newPlayer, SIGNAL(openDeckEditor(const DeckLoader *)), this, SIGNAL(openDeckEditor(const DeckLoader *))); + scene->addPlayer(newPlayer); - connect(newPlayer, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); - messageLog->connectToPlayer(newPlayer); - - if (local && !spectator) { - AbstractClient *client; - if (clients.size() > 1) - client = clients.at(playerId); - else { - client = clients.first(); - newPlayer->setShortcutsActive(); - } - DeckViewContainer *deckView = new DeckViewContainer(client, this); - connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); - deckViewContainers.insert(playerId, deckView); - deckViewContainerLayout->addWidget(deckView); - } + connect(newPlayer, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); + messageLog->connectToPlayer(newPlayer); + + if (local && !spectator) { + if (clients.size() == 1) + newPlayer->setShortcutsActive(); + + DeckViewContainer *deckView = new DeckViewContainer(playerId, this); + connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); + deckViewContainers.insert(playerId, deckView); + deckViewContainerLayout->addWidget(deckView); + } - tabMenu->insertMenu(playersSeparator, newPlayer->getPlayerMenu()); - - players.insert(playerId, newPlayer); - emit playerAdded(newPlayer); + gameMenu->insertMenu(playersSeparator, newPlayer->getPlayerMenu()); + + players.insert(playerId, newPlayer); + emit playerAdded(newPlayer); - return newPlayer; + return newPlayer; } -void TabGame::processGameEventContainer(GameEventContainer *cont, AbstractClient *client) +void TabGame::processGameEventContainer(const GameEventContainer &cont, AbstractClient *client) { - const QList &eventList = cont->getEventList(); - GameEventContext *context = cont->getContext(); - messageLog->containerProcessingStarted(context); - for (int i = 0; i < eventList.size(); ++i) { - GameEvent *event = eventList[i]; - - if (spectators.contains(event->getPlayerId())) { - switch (event->getItemId()) { - case ItemId_Event_Say: eventSpectatorSay(static_cast(event), context); break; - case ItemId_Event_Leave: eventSpectatorLeave(static_cast(event), context); break; - default: { - qDebug() << "unhandled spectator game event"; - break; - } - } - } else { - if ((clients.size() > 1) && (event->getPlayerId() != -1)) - if (clients.at(event->getPlayerId()) != client) - continue; - - switch (event->getItemId()) { - case ItemId_Event_GameStateChanged: eventGameStateChanged(static_cast(event), context); break; - case ItemId_Event_PlayerPropertiesChanged: eventPlayerPropertiesChanged(static_cast(event), context); break; - case ItemId_Event_Join: eventJoin(static_cast(event), context); break; - case ItemId_Event_Leave: eventLeave(static_cast(event), context); break; - case ItemId_Event_Kicked: eventKicked(static_cast(event), context); break; - case ItemId_Event_GameHostChanged: eventGameHostChanged(static_cast(event), context); break; - case ItemId_Event_GameClosed: eventGameClosed(static_cast(event), context); break; - case ItemId_Event_SetActivePlayer: eventSetActivePlayer(static_cast(event), context); break; - case ItemId_Event_SetActivePhase: eventSetActivePhase(static_cast(event), context); break; - case ItemId_Event_Ping: eventPing(static_cast(event), context); break; - - default: { - Player *player = players.value(event->getPlayerId(), 0); - if (!player) { - qDebug() << "unhandled game event: invalid player id"; - break; - } - player->processGameEvent(event, context); - emit userEvent(); - } - } - } - } - messageLog->containerProcessingDone(); + const GameEventContext &context = cont.context(); + messageLog->containerProcessingStarted(context); + const int eventListSize = cont.event_list_size(); + for (int i = 0; i < eventListSize; ++i) { + const GameEvent &event = cont.event_list(i); + const int playerId = event.player_id(); + const GameEvent::GameEventType eventType = static_cast(getPbExtension(event)); + if (spectators.contains(playerId)) { + switch (eventType) { + case GameEvent::GAME_SAY: eventSpectatorSay(event.GetExtension(Event_GameSay::ext), playerId, context); break; + case GameEvent::LEAVE: eventSpectatorLeave(event.GetExtension(Event_Leave::ext), playerId, context); break; + default: { + qDebug() << "unhandled spectator game event"; + break; + } + } + } else { + if ((clients.size() > 1) && (playerId != -1)) + if (clients.at(playerId) != client) + continue; + + switch (eventType) { + case GameEvent::GAME_STATE_CHANGED: eventGameStateChanged(event.GetExtension(Event_GameStateChanged::ext), playerId, context); break; + case GameEvent::PLAYER_PROPERTIES_CHANGED: eventPlayerPropertiesChanged(event.GetExtension(Event_PlayerPropertiesChanged::ext), playerId, context); break; + case GameEvent::JOIN: eventJoin(event.GetExtension(Event_Join::ext), playerId, context); break; + case GameEvent::LEAVE: eventLeave(event.GetExtension(Event_Leave::ext), playerId, context); break; + case GameEvent::KICKED: eventKicked(event.GetExtension(Event_Kicked::ext), playerId, context); break; + case GameEvent::GAME_HOST_CHANGED: eventGameHostChanged(event.GetExtension(Event_GameHostChanged::ext), playerId, context); break; + case GameEvent::GAME_CLOSED: eventGameClosed(event.GetExtension(Event_GameClosed::ext), playerId, context); break; + case GameEvent::SET_ACTIVE_PLAYER: eventSetActivePlayer(event.GetExtension(Event_SetActivePlayer::ext), playerId, context); break; + case GameEvent::SET_ACTIVE_PHASE: eventSetActivePhase(event.GetExtension(Event_SetActivePhase::ext), playerId, context); break; + + default: { + Player *player = players.value(playerId, 0); + if (!player) { + qDebug() << "unhandled game event: invalid player id"; + break; + } + player->processGameEvent(eventType, event, context); + emit userEvent(); + } + } + } + } + messageLog->containerProcessingDone(); } -void TabGame::sendGameCommand(GameCommand *command, int playerId) +AbstractClient *TabGame::getClientForPlayer(int playerId) const { - command->setGameId(gameId); - AbstractClient *client; - if (clients.size() > 1) { - if (playerId == -1) - playerId = getActiveLocalPlayer()->getId(); + if (clients.size() > 1) { + if (playerId == -1) + playerId = getActiveLocalPlayer()->getId(); - client = clients.at(playerId); - } else - client = clients.first(); - client->sendCommand(command); + return clients.at(playerId); + } else if (clients.isEmpty()) + return 0; + else + return clients.first(); } -void TabGame::sendCommandContainer(CommandContainer *cont, int playerId) +void TabGame::sendGameCommand(PendingCommand *pend, int playerId) { - const QList &cmdList = cont->getCommandList(); - for (int i = 0; i < cmdList.size(); ++i) { - GameCommand *cmd = qobject_cast(cmdList[i]); - if (cmd) - cmd->setGameId(gameId); - } + AbstractClient *client = getClientForPlayer(playerId); + if (!client) + return; + client->sendCommand(pend); +} - AbstractClient *client; - if (clients.size() > 1) { - if (playerId == -1) - playerId = getActiveLocalPlayer()->getId(); - client = clients.at(playerId); - } else - client = clients.first(); - client->sendCommandContainer(cont); +void TabGame::sendGameCommand(const google::protobuf::Message &command, int playerId) +{ + AbstractClient *client = getClientForPlayer(playerId); + if (!client) + return; + client->sendCommand(prepareGameCommand(command)); +} + +PendingCommand *TabGame::prepareGameCommand(const ::google::protobuf::Message &cmd) +{ + CommandContainer cont; + cont.set_game_id(gameInfo.game_id()); + GameCommand *c = cont.add_game_command(); + c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); + return new PendingCommand(cont); +} + +PendingCommand *TabGame::prepareGameCommand(const QList< const ::google::protobuf::Message * > &cmdList) +{ + CommandContainer cont; + cont.set_game_id(gameInfo.game_id()); + for (int i = 0; i < cmdList.size(); ++i) { + GameCommand *c = cont.add_game_command(); + c->GetReflection()->MutableMessage(c, cmdList[i]->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*cmdList[i]); + delete cmdList[i]; + } + return new PendingCommand(cont); } void TabGame::startGame(bool resuming) { - currentPhase = -1; + currentPhase = -1; - QMapIterator i(deckViewContainers); - while (i.hasNext()) { - i.next(); - i.value()->setReadyStart(false); - i.value()->hide(); - } - mainLayout->removeItem(deckViewContainerLayout); - - if (!resuming) { - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) - playerIterator.next().value()->setConceded(false); - } + QMapIterator i(deckViewContainers); + while (i.hasNext()) { + i.next(); + i.value()->setReadyStart(false); + i.value()->hide(); + } + mainLayout->removeItem(deckViewContainerLayout); + + if (!resuming) { + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + playerIterator.next().value()->setConceded(false); + } - playerListWidget->setGameStarted(true, resuming); - started = true; - static_cast(gameView->scene())->rearrange(); - gameView->show(); - phasesToolbar->show(); + playerListWidget->setGameStarted(true, resuming); + gameInfo.set_started(true); + static_cast(gameView->scene())->rearrange(); + gameView->show(); } void TabGame::stopGame() { - currentPhase = -1; - activePlayer = -1; - - QMapIterator i(deckViewContainers); - while (i.hasNext()) { - i.next(); - i.value()->show(); - } - mainLayout->insertLayout(1, deckViewContainerLayout, 10); + currentPhase = -1; + activePlayer = -1; + + QMapIterator i(deckViewContainers); + while (i.hasNext()) { + i.next(); + i.value()->show(); + } + mainLayout->insertLayout(1, deckViewContainerLayout, 10); - playerListWidget->setActivePlayer(-1); - playerListWidget->setGameStarted(false, false); - started = false; - gameView->hide(); - phasesToolbar->hide(); + playerListWidget->setActivePlayer(-1); + playerListWidget->setGameStarted(false, false); + gameInfo.set_started(false); + gameView->hide(); } -void TabGame::eventSpectatorSay(Event_Say *event, GameEventContext * /*context*/) +void TabGame::closeGame() { - messageLog->logSpectatorSay(spectators.value(event->getPlayerId()), event->getMessage()); + gameInfo.set_started(false); + gameClosed = true; + + gameMenu->clear(); + gameMenu->addAction(aLeaveGame); } -void TabGame::eventSpectatorLeave(Event_Leave *event, GameEventContext * /*context*/) +void TabGame::eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext & /*context*/) { - int playerId = event->getPlayerId(); - messageLog->logLeaveSpectator(spectators.value(playerId)); - playerListWidget->removePlayer(playerId); - spectators.remove(playerId); - - emit userEvent(); + const ServerInfo_User &userInfo = spectators.value(eventPlayerId); + messageLog->logSpectatorSay(QString::fromStdString(userInfo.name()), UserLevelFlags(userInfo.user_level()), QString::fromStdString(event.message())); } -void TabGame::eventGameStateChanged(Event_GameStateChanged *event, GameEventContext * /*context*/) +void TabGame::eventSpectatorLeave(const Event_Leave & /*event*/, int eventPlayerId, const GameEventContext & /*context*/) { - const QList &plList = event->getPlayerList(); - for (int i = 0; i < plList.size(); ++i) { - ServerInfo_Player *pl = plList[i]; - ServerInfo_PlayerProperties *prop = pl->getProperties(); - if (prop->getSpectator()) { - if (!spectators.contains(prop->getPlayerId())) { - spectators.insert(prop->getPlayerId(), prop->getUserInfo()->getName()); - playerListWidget->addPlayer(prop); - } - } else { - Player *player = players.value(prop->getPlayerId(), 0); - if (!player) { - player = addPlayer(prop->getPlayerId(), prop->getUserInfo()); - playerListWidget->addPlayer(prop); - } - player->processPlayerInfo(pl); - if (player->getLocal() && !pl->getDeck()->isEmpty()) { - db->cacheCardPixmaps(pl->getDeck()->getCardList()); - deckViewContainers.value(player->getId())->setDeck(new DeckList(pl->getDeck())); - } - } - } - for (int i = 0; i < plList.size(); ++i) { - ServerInfo_Player *pl = plList[i]; - ServerInfo_PlayerProperties *prop = pl->getProperties(); - if (!prop->getSpectator()) { - Player *player = players.value(prop->getPlayerId(), 0); - if (!player) - continue; - player->processCardAttachment(pl); - } - } - if (event->getGameStarted() && !started) { - startGame(!gameStateKnown); - if (gameStateKnown) - messageLog->logGameStart(); - setActivePlayer(event->getActivePlayer()); - setActivePhase(event->getActivePhase()); - } else if (!event->getGameStarted() && started) { - stopGame(); - scene->clearViews(); - } - gameStateKnown = true; - emit userEvent(); + messageLog->logLeaveSpectator(QString::fromStdString(spectators.value(eventPlayerId).name())); + playerListWidget->removePlayer(eventPlayerId); + spectators.remove(eventPlayerId); + + emit userEvent(); } -void TabGame::eventPlayerPropertiesChanged(Event_PlayerPropertiesChanged *event, GameEventContext *context) +void TabGame::eventGameStateChanged(const Event_GameStateChanged &event, int /*eventPlayerId*/, const GameEventContext & /*context*/) { - Player *player = players.value(event->getProperties()->getPlayerId(), 0); - if (!player) - return; - playerListWidget->updatePlayerProperties(event->getProperties()); - if (context) switch (context->getItemId()) { - case ItemId_Context_ReadyStart: { - bool ready = event->getProperties()->getReadyStart(); - if (player->getLocal()) - deckViewContainers.value(player->getId())->setReadyStart(ready); - if (ready) - messageLog->logReadyStart(player); - else - messageLog->logNotReadyStart(player); - break; - } - case ItemId_Context_Concede: { - messageLog->logConcede(player); - player->setConceded(true); - - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) - playerIterator.next().value()->updateZones(); - - break; - } - case ItemId_Context_DeckSelect: messageLog->logDeckSelect(player, static_cast(context)->getDeckHash()); break; - default: ; - } + const int playerListSize = event.player_list_size(); + for (int i = 0; i < playerListSize; ++i) { + const ServerInfo_Player &playerInfo = event.player_list(i); + const ServerInfo_PlayerProperties &prop = playerInfo.properties(); + const int playerId = prop.player_id(); + if (prop.spectator()) { + if (!spectators.contains(playerId)) { + spectators.insert(playerId, prop.user_info()); + playerListWidget->addPlayer(prop); + } + } else { + Player *player = players.value(playerId, 0); + if (!player) { + player = addPlayer(playerId, prop.user_info()); + playerListWidget->addPlayer(prop); + } + player->processPlayerInfo(playerInfo); + if (player->getLocal()) { + DeckViewContainer *deckViewContainer = deckViewContainers.value(playerId); + if (playerInfo.has_deck_list()) { + DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list())); + db->cacheCardPixmaps(newDeck.getCardList()); + deckViewContainer->setDeck(newDeck); + player->setDeck(newDeck); + } + deckViewContainer->setReadyStart(prop.ready_start()); + deckViewContainer->setSideboardLocked(prop.sideboard_locked()); + } + } + } + for (int i = 0; i < playerListSize; ++i) { + const ServerInfo_Player &playerInfo = event.player_list(i); + const ServerInfo_PlayerProperties &prop = playerInfo.properties(); + if (!prop.spectator()) { + Player *player = players.value(prop.player_id(), 0); + if (!player) + continue; + player->processCardAttachment(playerInfo); + } + } + + secondsElapsed = event.seconds_elapsed(); + + if (event.game_started() && !gameInfo.started()) { + startGame(!gameStateKnown); + if (gameStateKnown) + messageLog->logGameStart(); + setActivePlayer(event.active_player_id()); + setActivePhase(event.active_phase()); + } else if (!event.game_started() && gameInfo.started()) { + stopGame(); + scene->clearViews(); + } + gameStateKnown = true; + emit userEvent(); } -void TabGame::eventJoin(Event_Join *event, GameEventContext * /*context*/) +void TabGame::eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event, int eventPlayerId, const GameEventContext &context) { - ServerInfo_PlayerProperties *playerInfo = event->getPlayer(); - if (players.contains(playerInfo->getPlayerId())) - return; - if (playerInfo->getSpectator()) { - spectators.insert(playerInfo->getPlayerId(), playerInfo->getUserInfo()->getName()); - messageLog->logJoinSpectator(playerInfo->getUserInfo()->getName()); - playerListWidget->addPlayer(playerInfo); - } else { - Player *newPlayer = addPlayer(playerInfo->getPlayerId(), playerInfo->getUserInfo()); - messageLog->logJoin(newPlayer); - playerListWidget->addPlayer(playerInfo); - } - emit userEvent(); + Player *player = players.value(eventPlayerId, 0); + if (!player) + return; + const ServerInfo_PlayerProperties &prop = event.player_properties(); + playerListWidget->updatePlayerProperties(prop, eventPlayerId); + + const GameEventContext::ContextType contextType = static_cast(getPbExtension(context)); + switch (contextType) { + case GameEventContext::READY_START: { + bool ready = prop.ready_start(); + if (player->getLocal()) + deckViewContainers.value(player->getId())->setReadyStart(ready); + if (ready) + messageLog->logReadyStart(player); + else + messageLog->logNotReadyStart(player); + break; + } + case GameEventContext::CONCEDE: { + messageLog->logConcede(player); + player->setConceded(true); + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + playerIterator.next().value()->updateZones(); + + break; + } + case GameEventContext::DECK_SELECT: { + Context_DeckSelect deckSelect = context.GetExtension(Context_DeckSelect::ext); + messageLog->logDeckSelect(player, + QString::fromStdString(deckSelect.deck_hash()), + deckSelect.sideboard_size()); + break; + } + case GameEventContext::SET_SIDEBOARD_LOCK: { + if (player->getLocal()) + deckViewContainers.value(player->getId())->setSideboardLocked(prop.sideboard_locked()); + messageLog->logSetSideboardLock(player, prop.sideboard_locked()); + break; + } + case GameEventContext::CONNECTION_STATE_CHANGED: { + messageLog->logConnectionStateChanged(player, prop.ping_seconds() != -1); + break; + } + default: ; + } } -void TabGame::eventLeave(Event_Leave *event, GameEventContext * /*context*/) +void TabGame::eventJoin(const Event_Join &event, int /*eventPlayerId*/, const GameEventContext & /*context*/) { - int playerId = event->getPlayerId(); - - Player *player = players.value(playerId, 0); - if (!player) - return; - - messageLog->logLeave(player); - playerListWidget->removePlayer(playerId); - players.remove(playerId); - emit playerRemoved(player); - player->clear(); - player->deleteLater(); - - // Rearrange all remaining zones so that attachment relationship updates take place - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) - playerIterator.next().value()->updateZones(); - - emit userEvent(); + const ServerInfo_PlayerProperties &playerInfo = event.player_properties(); + const int playerId = playerInfo.player_id(); + if (players.contains(playerId)) + return; + if (playerInfo.spectator()) { + spectators.insert(playerId, playerInfo.user_info()); + messageLog->logJoinSpectator(QString::fromStdString(playerInfo.user_info().name())); + } else { + Player *newPlayer = addPlayer(playerId, playerInfo.user_info()); + messageLog->logJoin(newPlayer); + } + playerListWidget->addPlayer(playerInfo); + emit userEvent(); } -void TabGame::eventKicked(Event_Kicked * /*event*/, GameEventContext * /*context*/) +void TabGame::eventLeave(const Event_Leave & /*event*/, int eventPlayerId, const GameEventContext & /*context*/) { - emit userEvent(); - QMessageBox::critical(this, tr("Kicked"), tr("You have been kicked out of the game.")); - deleteLater(); + Player *player = players.value(eventPlayerId, 0); + if (!player) + return; + + messageLog->logLeave(player); + playerListWidget->removePlayer(eventPlayerId); + players.remove(eventPlayerId); + emit playerRemoved(player); + player->clear(); + player->deleteLater(); + + // Rearrange all remaining zones so that attachment relationship updates take place + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + playerIterator.next().value()->updateZones(); + + emit userEvent(); } -void TabGame::eventGameHostChanged(Event_GameHostChanged *event, GameEventContext * /*context*/) +void TabGame::eventKicked(const Event_Kicked & /*event*/, int /*eventPlayerId*/, const GameEventContext & /*context*/) { - hostId = event->getPlayerId(); + closeGame(); + messageLog->logKicked(); + emit userEvent(); } -void TabGame::eventGameClosed(Event_GameClosed * /*event*/, GameEventContext * /*context*/) +void TabGame::eventGameHostChanged(const Event_GameHostChanged & /*event*/, int eventPlayerId, const GameEventContext & /*context*/) { - started = false; - messageLog->logGameClosed(); - emit userEvent(); + hostId = eventPlayerId; +} + +void TabGame::eventGameClosed(const Event_GameClosed & /*event*/, int /*eventPlayerId*/, const GameEventContext & /*context*/) +{ + closeGame(); + messageLog->logGameClosed(); + emit userEvent(); } Player *TabGame::setActivePlayer(int id) { - Player *player = players.value(id, 0); - if (!player) - return 0; - activePlayer = id; - playerListWidget->setActivePlayer(id); - QMapIterator i(players); - while (i.hasNext()) { - i.next(); - if (i.value() == player) { - i.value()->setActive(true); - if (clients.size() > 1) - i.value()->setShortcutsActive(); - } else { - i.value()->setActive(false); - if (clients.size() > 1) - i.value()->setShortcutsInactive(); - } - } - currentPhase = -1; - emit userEvent(); - return player; + Player *player = players.value(id, 0); + if (!player) + return 0; + activePlayer = id; + playerListWidget->setActivePlayer(id); + QMapIterator i(players); + while (i.hasNext()) { + i.next(); + if (i.value() == player) { + i.value()->setActive(true); + if (clients.size() > 1) + i.value()->setShortcutsActive(); + } else { + i.value()->setActive(false); + if (clients.size() > 1) + i.value()->setShortcutsInactive(); + } + } + currentPhase = -1; + emit userEvent(); + return player; } -void TabGame::eventSetActivePlayer(Event_SetActivePlayer *event, GameEventContext * /*context*/) +void TabGame::eventSetActivePlayer(const Event_SetActivePlayer &event, int /*eventPlayerId*/, const GameEventContext & /*context*/) { - Player *player = setActivePlayer(event->getActivePlayerId()); - if (!player) - return; - messageLog->logSetActivePlayer(player); - emit userEvent(); + Player *player = setActivePlayer(event.active_player_id()); + if (!player) + return; + messageLog->logSetActivePlayer(player); + emit userEvent(); } void TabGame::setActivePhase(int phase) { - if (currentPhase != phase) { - currentPhase = phase; - phasesToolbar->setActivePhase(phase); - } + if (currentPhase != phase) { + currentPhase = phase; + phasesToolbar->setActivePhase(phase); + } } -void TabGame::eventSetActivePhase(Event_SetActivePhase *event, GameEventContext * /*context*/) +void TabGame::eventSetActivePhase(const Event_SetActivePhase &event, int /*eventPlayerId*/, const GameEventContext & /*context*/) { - int phase = event->getPhase(); - if (currentPhase != phase) - messageLog->logSetActivePhase(phase); - setActivePhase(phase); - emit userEvent(); -} - -void TabGame::eventPing(Event_Ping *event, GameEventContext * /*context*/) -{ - const QList &pingList = event->getPingList(); - for (int i = 0; i < pingList.size(); ++i) - playerListWidget->updatePing(pingList[i]->getPlayerId(), pingList[i]->getPingTime()); - - int seconds = event->getSecondsElapsed(); - int minutes = seconds / 60; - seconds -= minutes * 60; - int hours = minutes / 60; - minutes -= hours * 60; - timeElapsedLabel->setText(QString::number(hours).rightJustified(2, '0') + ":" + QString::number(minutes).rightJustified(2, '0') + ":" + QString::number(seconds).rightJustified(2, '0')); + const int phase = event.phase(); + if (currentPhase != phase) + messageLog->logSetActivePhase(phase); + setActivePhase(phase); + emit userEvent(); } void TabGame::newCardAdded(AbstractCardItem *card) { - connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfo, SLOT(setCard(AbstractCardItem *))); - connect(card, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); - connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); + connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfo, SLOT(setCard(AbstractCardItem *))); + connect(card, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); + connect(card, SIGNAL(updateCardMenu(AbstractCardItem *)), this, SLOT(updateCardMenu(AbstractCardItem *))); } CardItem *TabGame::getCard(int playerId, const QString &zoneName, int cardId) const { - Player *player = players.value(playerId, 0); - if (!player) - return 0; - - CardZone *zone = player->getZones().value(zoneName, 0); - if (!zone) - return 0; - - return zone->getCard(cardId, QString()); + Player *player = players.value(playerId, 0); + if (!player) + return 0; + + CardZone *zone = player->getZones().value(zoneName, 0); + if (!zone) + return 0; + + return zone->getCard(cardId, QString()); +} + +QString TabGame::getTabText() const +{ + if (replay) + return tr("Replay %1: %2").arg(gameInfo.game_id()).arg(QString::fromStdString(gameInfo.description())); + else + return tr("Game %1: %2").arg(gameInfo.game_id()).arg(QString::fromStdString(gameInfo.description())); } Player *TabGame::getActiveLocalPlayer() const { - Player *active = players.value(activePlayer, 0); - if (active) - if (active->getLocal()) - return active; - - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Player *temp = playerIterator.next().value(); - if (temp->getLocal()) - return temp; - } - - return 0; + Player *active = players.value(activePlayer, 0); + if (active) + if (active->getLocal()) + return active; + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Player *temp = playerIterator.next().value(); + if (temp->getLocal()) + return temp; + } + + return 0; +} + +void TabGame::updateCardMenu(AbstractCardItem *card) +{ + Player *p; + if ((clients.size() > 1) || !players.contains(localPlayerId)) + p = card->getOwner(); + else + p = players.value(localPlayerId); + p->updateCardMenu(static_cast(card)); } diff --git a/cockatrice/src/tab_game.h b/cockatrice/src/tab_game.h index 220a49d09..50f77e1a1 100644 --- a/cockatrice/src/tab_game.h +++ b/cockatrice/src/tab_game.h @@ -4,6 +4,7 @@ #include #include #include "tab.h" +#include "pb/serverinfo_game.pb.h" class AbstractClient; class CardDatabase; @@ -12,20 +13,24 @@ class DeckView; class GameScene; class CardInfoWidget; class MessageLogWidget; +class QTimer; class QSplitter; class QLabel; class QLineEdit; class QPushButton; +class QToolButton; class QMenu; class ZoneViewLayout; class ZoneViewWidget; class PhasesToolbar; class PlayerListWidget; -class ProtocolResponse; +class ReplayTimelineWidget; +class Response; class GameEventContainer; class GameEventContext; class GameCommand; class CommandContainer; +class Event_GameJoined; class Event_GameStateChanged; class Event_PlayerPropertiesChanged; class Event_Join; @@ -36,148 +41,185 @@ class Event_GameStart; class Event_SetActivePlayer; class Event_SetActivePhase; class Event_Ping; -class Event_Say; +class Event_GameSay; class Event_Kicked; class Player; class CardZone; class AbstractCardItem; class CardItem; class TabGame; -class DeckList; +class DeckLoader; class QVBoxLayout; class QHBoxLayout; +class GameReplay; class ServerInfo_User; +class PendingCommand; -class ReadyStartButton : public QPushButton { - Q_OBJECT +class ToggleButton : public QPushButton { + Q_OBJECT private: - bool readyStart; + bool state; +signals: + void stateChanged(); public: - ReadyStartButton(QWidget *parent = 0); - bool getReadyStart() const { return readyStart; } - void setReadyStart(bool _readyStart); + ToggleButton(QWidget *parent = 0); + bool getState() const { return state; } + void setState(bool _state); protected: - void paintEvent(QPaintEvent *event); + void paintEvent(QPaintEvent *event); }; class DeckViewContainer : public QWidget { - Q_OBJECT + Q_OBJECT private: - QPushButton *loadLocalButton, *loadRemoteButton; - ReadyStartButton *readyStartButton; - DeckView *deckView; - AbstractClient *client; + QPushButton *loadLocalButton, *loadRemoteButton; + ToggleButton *readyStartButton, *sideboardLockButton; + DeckView *deckView; + int playerId; private slots: - void loadLocalDeck(); - void loadRemoteDeck(); - void readyStart(); - void deckSelectFinished(ProtocolResponse *r); - void sideboardPlanChanged(); + void loadLocalDeck(); + void loadRemoteDeck(); + void readyStart(); + void deckSelectFinished(const Response &r); + void sideboardPlanChanged(); + void sideboardLockButtonClicked(); + void updateSideboardLockButtonText(); signals: - void newCardAdded(AbstractCardItem *card); + void newCardAdded(AbstractCardItem *card); public: - DeckViewContainer(AbstractClient *_client, TabGame *parent = 0); - void retranslateUi(); - void setButtonsVisible(bool _visible); - void setReadyStart(bool ready); - void setDeck(DeckList *deck); + DeckViewContainer(int _playerId, TabGame *parent = 0); + void retranslateUi(); + void setButtonsVisible(bool _visible); + void setReadyStart(bool ready); + void setSideboardLocked(bool locked); + void setDeck(const DeckLoader &deck); }; class TabGame : public Tab { - Q_OBJECT + Q_OBJECT private: - QList clients; - int gameId; - QString gameDescription; - int hostId; - int localPlayerId; - bool spectator; - bool spectatorsCanTalk, spectatorsSeeEverything; - QMap players; - QMap spectators; - bool gameStateKnown; - bool started; - bool resuming; - QStringList phasesList; - int currentPhase; - int activePlayer; + QTimer *gameTimer; + int secondsElapsed; + QList clients; + ServerInfo_Game gameInfo; + QMap roomGameTypes; + int hostId; + int localPlayerId; + bool spectator; + QMap players; + QMap spectators; + bool gameStateKnown; + bool resuming; + QStringList phasesList; + int currentPhase; + int activePlayer; + CardItem *activeCard; + bool gameClosed; + + // Replay related members + GameReplay *replay; + int currentReplayStep; + QList replayTimeline; + ReplayTimelineWidget *timelineWidget; + QToolButton *replayToStartButton, *replayStartButton, *replayPauseButton, *replayStopButton, *replayFastForwardButton, *replayToEndButton; + + QSplitter *splitter; + CardInfoWidget *cardInfo; + PlayerListWidget *playerListWidget; + QLabel *timeElapsedLabel; + MessageLogWidget *messageLog; + QLabel *sayLabel; + QLineEdit *sayEdit; + PhasesToolbar *phasesToolbar; + GameScene *scene; + GameView *gameView; + QMap deckViewContainers; + QVBoxLayout *deckViewContainerLayout; + QHBoxLayout *mainLayout; + ZoneViewLayout *zoneLayout; + QAction *playersSeparator; + QMenu *gameMenu; + QMenu *phasesMenu; + QAction *aGameInfo, *aConcede, *aLeaveGame, *aCloseReplay, *aNextPhase, *aNextTurn, *aRemoveLocalArrows; + QList phaseActions; - QSplitter *splitter; - CardInfoWidget *cardInfo; - PlayerListWidget *playerListWidget; - QLabel *timeElapsedLabel; - MessageLogWidget *messageLog; - QLabel *sayLabel; - QLineEdit *sayEdit; - PhasesToolbar *phasesToolbar; - GameScene *scene; - GameView *gameView; - QMap deckViewContainers; - QVBoxLayout *deckViewContainerLayout; - QHBoxLayout *mainLayout; - ZoneViewLayout *zoneLayout; - QAction *playersSeparator; - QMenu *phasesMenu; - QAction *aConcede, *aLeaveGame, *aNextPhase, *aNextTurn, *aRemoveLocalArrows; - QList phaseActions; + Player *addPlayer(int playerId, const ServerInfo_User &info); - Player *addPlayer(int playerId, ServerInfo_User *info); + void startGame(bool resuming); + void stopGame(); + void closeGame(); - void startGame(bool resuming); - void stopGame(); - - void eventSpectatorSay(Event_Say *event, GameEventContext *context); - void eventSpectatorLeave(Event_Leave *event, GameEventContext *context); - - void eventGameStateChanged(Event_GameStateChanged *event, GameEventContext *context); - void eventPlayerPropertiesChanged(Event_PlayerPropertiesChanged *event, GameEventContext *context); - void eventJoin(Event_Join *event, GameEventContext *context); - void eventLeave(Event_Leave *event, GameEventContext *context); - void eventKicked(Event_Kicked *event, GameEventContext *context); - void eventGameHostChanged(Event_GameHostChanged *event, GameEventContext *context); - void eventGameClosed(Event_GameClosed *event, GameEventContext *context); - Player *setActivePlayer(int id); - void eventSetActivePlayer(Event_SetActivePlayer *event, GameEventContext *context); - void setActivePhase(int phase); - void eventSetActivePhase(Event_SetActivePhase *event, GameEventContext *context); - void eventPing(Event_Ping *event, GameEventContext *context); + void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context); + void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); + + void eventGameStateChanged(const Event_GameStateChanged &event, int eventPlayerId, const GameEventContext &context); + void eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event, int eventPlayerId, const GameEventContext &context); + void eventJoin(const Event_Join &event, int eventPlayerId, const GameEventContext &context); + void eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); + void eventKicked(const Event_Kicked &event, int eventPlayerId, const GameEventContext &context); + void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context); + void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context); + Player *setActivePlayer(int id); + void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context); + void setActivePhase(int phase); + void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context); + void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context); signals: - void gameClosing(TabGame *tab); - void playerAdded(Player *player); - void playerRemoved(Player *player); - void containerProcessingStarted(GameEventContext *context); - void containerProcessingDone(); - void openMessageDialog(const QString &userName, bool focus); + void gameClosing(TabGame *tab); + void playerAdded(Player *player); + void playerRemoved(Player *player); + void containerProcessingStarted(const GameEventContext &context); + void containerProcessingDone(); + void openMessageDialog(const QString &userName, bool focus); + void openDeckEditor(const DeckLoader *deck); private slots: - void adminLockChanged(bool lock); - void newCardAdded(AbstractCardItem *card); - - void actConcede(); - void actLeaveGame(); - void actRemoveLocalArrows(); - void actSay(); - void actPhaseAction(); - void actNextPhase(); - void actNextTurn(); + void replayNextEvent(); + void replayFinished(); + void replayToStartButtonClicked(); + void replayStartButtonClicked(); + void replayPauseButtonClicked(); + void replayStopButtonClicked(); + void replayFastForwardButtonToggled(bool checked); + void replayToEndButtonClicked(); + + void incrementGameTime(); + void adminLockChanged(bool lock); + void newCardAdded(AbstractCardItem *card); + void updateCardMenu(AbstractCardItem *card); + + void actGameInfo(); + void actConcede(); + void actLeaveGame(); + void actRemoveLocalArrows(); + void actSay(); + void actPhaseAction(); + void actNextPhase(); + void actNextTurn(); public: - TabGame(TabSupervisor *_tabSupervisor, QList &_clients, int _gameId, const QString &_gameDescription, int _hostId, int _localPlayerId, bool _spectator, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, bool _resuming); - ~TabGame(); - void retranslateUi(); - void closeRequest(); - const QMap &getPlayers() const { return players; } - CardItem *getCard(int playerId, const QString &zoneName, int cardId) const; - bool isHost() const { return hostId == localPlayerId; } - int getGameId() const { return gameId; } - QString getTabText() const { return tr("Game %1: %2").arg(gameId).arg(gameDescription); } - bool getSpectator() const { return spectator; } - bool getSpectatorsCanTalk() const { return spectatorsCanTalk; } - bool getSpectatorsSeeEverything() const { return spectatorsSeeEverything; } - Player *getActiveLocalPlayer() const; + TabGame(TabSupervisor *_tabSupervisor, QList &_clients, const Event_GameJoined &event, const QMap &_roomGameTypes); + TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay); + ~TabGame(); + void retranslateUi(); + void closeRequest(); + const QMap &getPlayers() const { return players; } + CardItem *getCard(int playerId, const QString &zoneName, int cardId) const; + bool isHost() const { return hostId == localPlayerId; } + int getGameId() const { return gameInfo.game_id(); } + QString getTabText() const; + bool getSpectator() const { return spectator; } + bool getSpectatorsSeeEverything() const { return gameInfo.spectators_omniscient(); } + Player *getActiveLocalPlayer() const; + AbstractClient *getClientForPlayer(int playerId) const; + + void setActiveCard(CardItem *_card) { activeCard = _card; } + CardItem *getActiveCard() const { return activeCard; } - void processGameEventContainer(GameEventContainer *cont, AbstractClient *client); + void processGameEventContainer(const GameEventContainer &cont, AbstractClient *client); + PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd); + PendingCommand *prepareGameCommand(const QList< const ::google::protobuf::Message * > &cmdList); public slots: - void sendGameCommand(GameCommand *command, int playerId = -1); - void sendCommandContainer(CommandContainer *cont, int playerId = -1); + void sendGameCommand(PendingCommand *pend, int playerId = -1); + void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1); }; #endif diff --git a/cockatrice/src/tab_message.cpp b/cockatrice/src/tab_message.cpp index f4efd7f57..6369f2a66 100644 --- a/cockatrice/src/tab_message.cpp +++ b/cockatrice/src/tab_message.cpp @@ -5,84 +5,114 @@ #include #include "tab_message.h" #include "abstractclient.h" -#include "protocol_items.h" #include "chatview.h" -TabMessage::TabMessage(TabSupervisor *_tabSupervisor, AbstractClient *_client, const QString &_ownName, const QString &_userName) - : Tab(_tabSupervisor), client(_client), userName(_userName), userOnline(true) +#include "pending_command.h" +#include "pb/session_commands.pb.h" +#include "pb/event_user_message.pb.h" +#include "pb/serverinfo_user.pb.h" + +TabMessage::TabMessage(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &_ownUserInfo, const ServerInfo_User &_otherUserInfo) + : Tab(_tabSupervisor), client(_client), ownUserInfo(new ServerInfo_User(_ownUserInfo)), otherUserInfo(new ServerInfo_User(_otherUserInfo)), userOnline(true) { - chatView = new ChatView(_ownName, true); - connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); - connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); - sayEdit = new QLineEdit; - connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); - - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(chatView); - vbox->addWidget(sayEdit); - - aLeave = new QAction(this); - connect(aLeave, SIGNAL(triggered()), this, SLOT(actLeave())); + chatView = new ChatView(tabSupervisor, 0, true); + connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); + sayEdit = new QLineEdit; + connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(chatView); + vbox->addWidget(sayEdit); + + aLeave = new QAction(this); + connect(aLeave, SIGNAL(triggered()), this, SLOT(actLeave())); - tabMenu = new QMenu(this); - tabMenu->addAction(aLeave); + messageMenu = new QMenu(this); + messageMenu->addAction(aLeave); + addTabMenu(messageMenu); - retranslateUi(); - setLayout(vbox); + retranslateUi(); + setLayout(vbox); } TabMessage::~TabMessage() { - emit talkClosing(this); + emit talkClosing(this); + delete ownUserInfo; + delete otherUserInfo; } void TabMessage::retranslateUi() { - tabMenu->setTitle(tr("Personal &talk")); - aLeave->setText(tr("&Leave")); + messageMenu->setTitle(tr("Personal &talk")); + aLeave->setText(tr("&Leave")); +} + +void TabMessage::tabActivated() +{ + if(!sayEdit->hasFocus()) + sayEdit->setFocus(); +} + +QString TabMessage::getUserName() const +{ + return QString::fromStdString(otherUserInfo->name()); +} + +QString TabMessage::getTabText() const +{ + return tr("Talking to %1").arg(QString::fromStdString(otherUserInfo->name())); } void TabMessage::closeRequest() { - actLeave(); + actLeave(); } void TabMessage::sendMessage() { - if (sayEdit->text().isEmpty() || !userOnline) - return; - - Command_Message *cmd = new Command_Message(userName, sayEdit->text()); - connect(cmd, SIGNAL(finished(ProtocolResponse *)), this, SLOT(messageSent(ProtocolResponse *))); - client->sendCommand(cmd); - sayEdit->clear(); + if (sayEdit->text().isEmpty() || !userOnline) + return; + + Command_Message cmd; + cmd.set_user_name(otherUserInfo->name()); + cmd.set_message(sayEdit->text().toStdString()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(messageSent(const Response &))); + client->sendCommand(pend); + + sayEdit->clear(); } -void TabMessage::messageSent(ProtocolResponse *response) +void TabMessage::messageSent(const Response &response) { - if (response->getResponseCode() == RespInIgnoreList) - chatView->appendMessage(QString(), tr("This user is ignoring you.")); + if (response.response_code() == Response::RespInIgnoreList) + chatView->appendMessage(tr("This user is ignoring you.")); } void TabMessage::actLeave() { - deleteLater(); + deleteLater(); } -void TabMessage::processMessageEvent(Event_Message *event) +void TabMessage::processUserMessageEvent(const Event_UserMessage &event) { - chatView->appendMessage(event->getSenderName(), event->getText()); - emit userEvent(); + const UserLevelFlags userLevel(event.sender_name() == otherUserInfo->name() ? otherUserInfo->user_level() : ownUserInfo->user_level()); + chatView->appendMessage(QString::fromStdString(event.message()), QString::fromStdString(event.sender_name()), userLevel); + emit userEvent(); } void TabMessage::processUserLeft() { - chatView->appendMessage(QString(), tr("%1 has left the server.").arg(userName)); - userOnline = false; + chatView->appendMessage(tr("%1 has left the server.").arg(QString::fromStdString(otherUserInfo->name()))); + userOnline = false; } -void TabMessage::processUserJoined() +void TabMessage::processUserJoined(const ServerInfo_User &_userInfo) { - chatView->appendMessage(QString(), tr("%1 has joined the server.").arg(userName)); - userOnline = true; + chatView->appendMessage(tr("%1 has joined the server.").arg(QString::fromStdString(otherUserInfo->name()))); + userOnline = true; + *otherUserInfo = _userInfo; } diff --git a/cockatrice/src/tab_message.h b/cockatrice/src/tab_message.h index b8543704c..80263975b 100644 --- a/cockatrice/src/tab_message.h +++ b/cockatrice/src/tab_message.h @@ -6,37 +6,41 @@ class AbstractClient; class ChatView; class QLineEdit; -class Event_Message; -class ProtocolResponse; +class Event_UserMessage; +class Response; +class ServerInfo_User; class TabMessage : public Tab { - Q_OBJECT + Q_OBJECT private: - AbstractClient *client; - QString userName; - bool userOnline; - - ChatView *chatView; - QLineEdit *sayEdit; + AbstractClient *client; + QMenu *messageMenu; + ServerInfo_User *ownUserInfo; + ServerInfo_User *otherUserInfo; + bool userOnline; + + ChatView *chatView; + QLineEdit *sayEdit; - QAction *aLeave; + QAction *aLeave; signals: - void talkClosing(TabMessage *tab); + void talkClosing(TabMessage *tab); private slots: - void sendMessage(); - void actLeave(); - void messageSent(ProtocolResponse *response); + void sendMessage(); + void actLeave(); + void messageSent(const Response &response); public: - TabMessage(TabSupervisor *_tabSupervisor, AbstractClient *_client, const QString &_ownName, const QString &_userName); - ~TabMessage(); - void retranslateUi(); - void closeRequest(); - QString getUserName() const { return userName; } - QString getTabText() const { return tr("Talking to %1").arg(userName); } + TabMessage(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &_ownUserInfo, const ServerInfo_User &_otherUserInfo); + ~TabMessage(); + void retranslateUi(); + void closeRequest(); + void tabActivated(); + QString getUserName() const; + QString getTabText() const; - void processMessageEvent(Event_Message *event); - void processUserLeft(); - void processUserJoined(); + void processUserMessageEvent(const Event_UserMessage &event); + void processUserLeft(); + void processUserJoined(const ServerInfo_User &_userInfo); }; #endif diff --git a/cockatrice/src/tab_replays.cpp b/cockatrice/src/tab_replays.cpp new file mode 100644 index 000000000..00a7f9c0c --- /dev/null +++ b/cockatrice/src/tab_replays.cpp @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tab_replays.h" +#include "remotereplaylist_treewidget.h" +#include "abstractclient.h" +#include "tab_game.h" +#include "settingscache.h" + +#include "pending_command.h" +#include "pb/game_replay.pb.h" +#include "pb/response.pb.h" +#include "pb/response_replay_download.pb.h" +#include "pb/command_replay_download.pb.h" +#include "pb/command_replay_modify_match.pb.h" +#include "pb/command_replay_delete_match.pb.h" +#include "pb/event_replay_added.pb.h" + +TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) + : Tab(_tabSupervisor), client(_client) +{ + localDirModel = new QFileSystemModel(this); + localDirModel->setRootPath(settingsCache->getReplaysPath()); + localDirModel->sort(0, Qt::AscendingOrder); + + localDirView = new QTreeView; + localDirView->setModel(localDirModel); + localDirView->setColumnHidden(1, true); + localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0)); + localDirView->setSortingEnabled(true); +#if QT_VERSION < 0x050000 + localDirView->header()->setResizeMode(QHeaderView::ResizeToContents); +#else + localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#endif + localDirView->header()->setSortIndicator(0, Qt::AscendingOrder); + + leftToolBar = new QToolBar; + leftToolBar->setOrientation(Qt::Horizontal); + leftToolBar->setIconSize(QSize(32, 32)); + QHBoxLayout *leftToolBarLayout = new QHBoxLayout; + leftToolBarLayout->addStretch(); + leftToolBarLayout->addWidget(leftToolBar); + leftToolBarLayout->addStretch(); + + QVBoxLayout *leftVbox = new QVBoxLayout; + leftVbox->addWidget(localDirView); + leftVbox->addLayout(leftToolBarLayout); + leftGroupBox = new QGroupBox; + leftGroupBox->setLayout(leftVbox); + + rightToolBar = new QToolBar; + rightToolBar->setOrientation(Qt::Horizontal); + rightToolBar->setIconSize(QSize(32, 32)); + QHBoxLayout *rightToolBarLayout = new QHBoxLayout; + rightToolBarLayout->addStretch(); + rightToolBarLayout->addWidget(rightToolBar); + rightToolBarLayout->addStretch(); + + serverDirView = new RemoteReplayList_TreeWidget(client); + + QVBoxLayout *rightVbox = new QVBoxLayout; + rightVbox->addWidget(serverDirView); + rightVbox->addLayout(rightToolBarLayout); + rightGroupBox = new QGroupBox; + rightGroupBox->setLayout(rightVbox); + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addWidget(leftGroupBox); + hbox->addWidget(rightGroupBox); + + aOpenLocalReplay = new QAction(this); + aOpenLocalReplay->setIcon(QIcon(":/resources/icon_view.svg")); + connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay())); + aDeleteLocalReplay = new QAction(this); + aDeleteLocalReplay->setIcon(QIcon(":/resources/remove_row.svg")); + connect(aDeleteLocalReplay, SIGNAL(triggered()), this, SLOT(actDeleteLocalReplay())); + aOpenRemoteReplay = new QAction(this); + aOpenRemoteReplay->setIcon(QIcon(":/resources/icon_view.svg")); + connect(aOpenRemoteReplay, SIGNAL(triggered()), this, SLOT(actOpenRemoteReplay())); + aDownload = new QAction(this); + aDownload->setIcon(QIcon(":/resources/arrow_left_green.svg")); + connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload())); + aKeep = new QAction(this); + aKeep->setIcon(QIcon(":/resources/lock.svg")); + connect(aKeep, SIGNAL(triggered()), this, SLOT(actKeepRemoteReplay())); + aDeleteRemoteReplay = new QAction(this); + aDeleteRemoteReplay->setIcon(QIcon(":/resources/remove_row.svg")); + connect(aDeleteRemoteReplay, SIGNAL(triggered()), this, SLOT(actDeleteRemoteReplay())); + + leftToolBar->addAction(aOpenLocalReplay); + leftToolBar->addAction(aDeleteLocalReplay); + rightToolBar->addAction(aOpenRemoteReplay); + rightToolBar->addAction(aDownload); + rightToolBar->addAction(aKeep); + rightToolBar->addAction(aDeleteRemoteReplay); + + retranslateUi(); + setLayout(hbox); + + connect(client, SIGNAL(replayAddedEventReceived(const Event_ReplayAdded &)), this, SLOT(replayAddedEventReceived(const Event_ReplayAdded &))); +} + +void TabReplays::retranslateUi() +{ + leftGroupBox->setTitle(tr("Local file system")); + rightGroupBox->setTitle(tr("Server replay storage")); + + aOpenLocalReplay->setText(tr("Watch replay")); + aDeleteLocalReplay->setText(tr("Delete")); + aOpenRemoteReplay->setText(tr("Watch replay")); + aDownload->setText(tr("Download replay")); + aKeep->setText(tr("Toggle expiration lock")); + aDeleteLocalReplay->setText(tr("Delete")); +} + +void TabReplays::actOpenLocalReplay() +{ + QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (localDirModel->isDir(curLeft)) + return; + QString filePath = localDirModel->filePath(curLeft); + + QFile f(filePath); + if (!f.open(QIODevice::ReadOnly)) + return; + QByteArray data = f.readAll(); + f.close(); + + GameReplay *replay = new GameReplay; + replay->ParseFromArray(data.data(), data.size()); + + emit openReplay(replay); +} + +void TabReplays::actDeleteLocalReplay() +{ + QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + return; + + localDirModel->remove(curLeft); +} + +void TabReplays::actOpenRemoteReplay() +{ + ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay(); + if (!curRight) + return; + + Command_ReplayDownload cmd; + cmd.set_replay_id(curRight->replay_id()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(openRemoteReplayFinished(const Response &))); + client->sendCommand(pend); +} + +void TabReplays::openRemoteReplayFinished(const Response &r) +{ + if (r.response_code() != Response::RespOk) + return; + + const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext); + GameReplay *replay = new GameReplay; + replay->ParseFromString(resp.replay_data()); + + emit openReplay(replay); +} + +void TabReplays::actDownload() +{ + QString filePath; + QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (!curLeft.isValid()) + filePath = localDirModel->rootPath(); + else { + while (!localDirModel->isDir(curLeft)) + curLeft = curLeft.parent(); + filePath = localDirModel->filePath(curLeft); + } + + ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay(); + if (!curRight) + return; + filePath += QString("/replay_%1.cor").arg(curRight->replay_id()); + + Command_ReplayDownload cmd; + cmd.set_replay_id(curRight->replay_id()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + pend->setExtraData(filePath); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(downloadFinished(Response, CommandContainer, QVariant))); + client->sendCommand(pend); +} + +void TabReplays::downloadFinished(const Response &r, const CommandContainer & /* commandContainer */, const QVariant &extraData) +{ + if (r.response_code() != Response::RespOk) + return; + + const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext); + QString filePath = extraData.toString(); + + const std::string &data = resp.replay_data(); + QFile f(filePath); + f.open(QIODevice::WriteOnly); + f.write((const char *) data.data(), data.size()); + f.close(); +} + +void TabReplays::actKeepRemoteReplay() +{ + ServerInfo_ReplayMatch const *curRight = serverDirView->getCurrentReplayMatch(); + if (!curRight) + return; + + Command_ReplayModifyMatch cmd; + cmd.set_game_id(curRight->game_id()); + cmd.set_do_not_hide(!curRight->do_not_hide()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(keepRemoteReplayFinished(Response, CommandContainer))); + client->sendCommand(pend); +} + +void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer) +{ + if (r.response_code() != Response::RespOk) + return; + + const Command_ReplayModifyMatch &cmd = commandContainer.session_command(0).GetExtension(Command_ReplayModifyMatch::ext); + + ServerInfo_ReplayMatch temp; + temp.set_do_not_hide(cmd.do_not_hide()); + + serverDirView->updateMatchInfo(cmd.game_id(), temp); +} + +void TabReplays::actDeleteRemoteReplay() +{ + ServerInfo_ReplayMatch const *curRight = serverDirView->getCurrentReplayMatch(); + if (!curRight) + return; + if (QMessageBox::warning(this, tr("Delete remote replay"), tr("Are you sure you want to delete the replay of game %1?").arg(curRight->game_id()), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + return; + + Command_ReplayDeleteMatch cmd; + cmd.set_game_id(curRight->game_id()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deleteRemoteReplayFinished(Response, CommandContainer))); + client->sendCommand(pend); +} + +void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer) +{ + if (r.response_code() != Response::RespOk) + return; + + const Command_ReplayDeleteMatch &cmd = commandContainer.session_command(0).GetExtension(Command_ReplayDeleteMatch::ext); + serverDirView->removeMatchInfo(cmd.game_id()); +} + +void TabReplays::replayAddedEventReceived(const Event_ReplayAdded &event) +{ + serverDirView->addMatchInfo(event.match_info()); +} diff --git a/cockatrice/src/tab_replays.h b/cockatrice/src/tab_replays.h new file mode 100644 index 000000000..99f1c6f42 --- /dev/null +++ b/cockatrice/src/tab_replays.h @@ -0,0 +1,54 @@ +#ifndef TAB_REPLAYS_H +#define TAB_REPLAYS_H + +#include "tab.h" + +class Response; +class AbstractClient; +class QTreeView; +class QFileSystemModel; +class QToolBar; +class QGroupBox; +class RemoteReplayList_TreeWidget; +class GameReplay; +class Event_ReplayAdded; +class CommandContainer; + +class TabReplays : public Tab { + Q_OBJECT +private: + AbstractClient *client; + QTreeView *localDirView; + QFileSystemModel *localDirModel; + QToolBar *leftToolBar, *rightToolBar; + RemoteReplayList_TreeWidget *serverDirView; + QGroupBox *leftGroupBox, *rightGroupBox; + + QAction *aOpenLocalReplay, *aDeleteLocalReplay, *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay; +private slots: + void actOpenLocalReplay(); + + void actDeleteLocalReplay(); + + void actOpenRemoteReplay(); + void openRemoteReplayFinished(const Response &r); + + void actDownload(); + void downloadFinished(const Response &r, const CommandContainer &commandContainer, const QVariant &extraData); + + void actKeepRemoteReplay(); + void keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer); + + void actDeleteRemoteReplay(); + void deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer); + + void replayAddedEventReceived(const Event_ReplayAdded &event); +signals: + void openReplay(GameReplay *replay); +public: + TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client); + void retranslateUi(); + QString getTabText() const { return tr("Game replays"); } +}; + +#endif diff --git a/cockatrice/src/tab_room.cpp b/cockatrice/src/tab_room.cpp index ed4188248..a4bb9ce0e 100644 --- a/cockatrice/src/tab_room.cpp +++ b/cockatrice/src/tab_room.cpp @@ -7,157 +7,222 @@ #include #include #include +#include #include #include "tab_supervisor.h" #include "tab_room.h" #include "tab_userlists.h" #include "userlist.h" #include "abstractclient.h" -#include "protocol_items.h" #include "chatview.h" #include "gameselector.h" +#include "settingscache.h" -TabRoom::TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, const QString &_ownName, ServerInfo_Room *info) - : Tab(_tabSupervisor), client(_client), roomId(info->getRoomId()), roomName(info->getName()), ownName(_ownName) +#include "get_pb_extension.h" +#include "pb/room_commands.pb.h" +#include "pb/serverinfo_room.pb.h" +#include "pb/event_list_games.pb.h" +#include "pb/event_join_room.pb.h" +#include "pb/event_leave_room.pb.h" +#include "pb/event_room_say.pb.h" +#include "pending_command.h" + +TabRoom::TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerInfo_User *_ownUser, const ServerInfo_Room &info) + : Tab(_tabSupervisor), client(_client), roomId(info.room_id()), roomName(QString::fromStdString(info.name())), ownUser(_ownUser) { - const QList gameTypeList = info->getGameTypeList(); - for (int i = 0; i < gameTypeList.size(); ++i) - gameTypes.insert(gameTypeList[i]->getGameTypeId(), gameTypeList[i]->getDescription()); - - QMap tempMap; - tempMap.insert(info->getRoomId(), gameTypes); - gameSelector = new GameSelector(client, tabSupervisor, this, QMap(), tempMap); - userList = new UserList(tabSupervisor, client, UserList::RoomList); - connect(userList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); - - chatView = new ChatView(ownName, true); - connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); - connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); - sayLabel = new QLabel; - sayEdit = new QLineEdit; - sayLabel->setBuddy(sayEdit); - connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); - - QHBoxLayout *sayHbox = new QHBoxLayout; - sayHbox->addWidget(sayLabel); - sayHbox->addWidget(sayEdit); - - QVBoxLayout *chatVbox = new QVBoxLayout; - chatVbox->addWidget(chatView); - chatVbox->addLayout(sayHbox); - - chatGroupBox = new QGroupBox; - chatGroupBox->setLayout(chatVbox); - - QSplitter *splitter = new QSplitter(Qt::Vertical); - splitter->addWidget(gameSelector); - splitter->addWidget(chatGroupBox); - - QHBoxLayout *hbox = new QHBoxLayout; - hbox->addWidget(splitter, 3); - hbox->addWidget(userList, 1); - - aLeaveRoom = new QAction(this); - connect(aLeaveRoom, SIGNAL(triggered()), this, SLOT(actLeaveRoom())); + const int gameTypeListSize = info.gametype_list_size(); + for (int i = 0; i < gameTypeListSize; ++i) + gameTypes.insert(info.gametype_list(i).game_type_id(), QString::fromStdString(info.gametype_list(i).description())); + + QMap tempMap; + tempMap.insert(info.room_id(), gameTypes); + gameSelector = new GameSelector(client, tabSupervisor, this, QMap(), tempMap); + userList = new UserList(tabSupervisor, client, UserList::RoomList); + connect(userList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); + + chatView = new ChatView(tabSupervisor, 0, true); + connect(chatView, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); + connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); + sayLabel = new QLabel; + sayEdit = new QLineEdit; + sayLabel->setBuddy(sayEdit); + connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); + + QMenu *chatSettingsMenu = new QMenu(this); + aIgnoreUnregisteredUsers = chatSettingsMenu->addAction(QString()); + aIgnoreUnregisteredUsers->setCheckable(true); + connect(aIgnoreUnregisteredUsers, SIGNAL(triggered()), this, SLOT(actIgnoreUnregisteredUsers())); + connect(settingsCache, SIGNAL(ignoreUnregisteredUsersChanged()), this, SLOT(ignoreUnregisteredUsersChanged())); + QToolButton *chatSettingsButton = new QToolButton; + chatSettingsButton->setIcon(QIcon(":/resources/icon_settings.svg")); + chatSettingsButton->setMenu(chatSettingsMenu); + chatSettingsButton->setPopupMode(QToolButton::InstantPopup); + + QHBoxLayout *sayHbox = new QHBoxLayout; + sayHbox->addWidget(sayLabel); + sayHbox->addWidget(sayEdit); + sayHbox->addWidget(chatSettingsButton); + + QVBoxLayout *chatVbox = new QVBoxLayout; + chatVbox->addWidget(chatView); + chatVbox->addLayout(sayHbox); + + chatGroupBox = new QGroupBox; + chatGroupBox->setLayout(chatVbox); + + QSplitter *splitter = new QSplitter(Qt::Vertical); + splitter->addWidget(gameSelector); + splitter->addWidget(chatGroupBox); + + QHBoxLayout *hbox = new QHBoxLayout; + hbox->addWidget(splitter, 3); + hbox->addWidget(userList, 1); + + aLeaveRoom = new QAction(this); + connect(aLeaveRoom, SIGNAL(triggered()), this, SLOT(actLeaveRoom())); - tabMenu = new QMenu(this); - tabMenu->addAction(aLeaveRoom); + roomMenu = new QMenu(this); + roomMenu->addAction(aLeaveRoom); + addTabMenu(roomMenu); - retranslateUi(); - setLayout(hbox); - - const QList users = info->getUserList(); - for (int i = 0; i < users.size(); ++i) - userList->processUserInfo(users[i], true); - userList->sortItems(); - - const QList games = info->getGameList(); - for (int i = 0; i < games.size(); ++i) - gameSelector->processGameInfo(games[i]); + retranslateUi(); + setLayout(hbox); + + const int userListSize = info.user_list_size(); + for (int i = 0; i < userListSize; ++i) + userList->processUserInfo(info.user_list(i), true); + userList->sortItems(); + + const int gameListSize = info.game_list_size(); + for (int i = 0; i < gameListSize; ++i) + gameSelector->processGameInfo(info.game_list(i)); } TabRoom::~TabRoom() { - emit roomClosing(this); + emit roomClosing(this); } void TabRoom::retranslateUi() { gameSelector->retranslateUi(); - sayLabel->setText(tr("&Say:")); - chatGroupBox->setTitle(tr("Chat")); - tabMenu->setTitle(tr("&Room")); - aLeaveRoom->setText(tr("&Leave room")); + chatView->retranslateUi(); + userList->retranslateUi(); + sayLabel->setText(tr("&Say:")); + chatGroupBox->setTitle(tr("Chat")); + roomMenu->setTitle(tr("&Room")); + aLeaveRoom->setText(tr("&Leave room")); + aIgnoreUnregisteredUsers->setText(tr("&Ignore unregistered users in chat")); } void TabRoom::closeRequest() { - actLeaveRoom(); + actLeaveRoom(); +} + +void TabRoom::tabActivated() +{ + if(!sayEdit->hasFocus()) + sayEdit->setFocus(); } QString TabRoom::sanitizeHtml(QString dirty) const { - return dirty - .replace("&", "&") - .replace("<", "<") - .replace(">", ">"); + return dirty + .replace("&", "&") + .replace("<", "<") + .replace(">", ">"); } void TabRoom::sendMessage() { - if (sayEdit->text().isEmpty()) - return; - - Command_RoomSay *cmd = new Command_RoomSay(roomId, sayEdit->text()); - connect(cmd, SIGNAL(finished(ProtocolResponse *)), this, SLOT(sayFinished(ProtocolResponse *))); - client->sendCommand(cmd); - sayEdit->clear(); + if (sayEdit->text().isEmpty()) + return; + + Command_RoomSay cmd; + cmd.set_message(sayEdit->text().toStdString()); + + PendingCommand *pend = prepareRoomCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(sayFinished(const Response &))); + sendRoomCommand(pend); + sayEdit->clear(); } -void TabRoom::sayFinished(ProtocolResponse *response) +void TabRoom::sayFinished(const Response &response) { - if (response->getResponseCode() == RespChatFlood) - chatView->appendMessage(QString(), tr("You are flooding the chat. Please wait a couple of seconds.")); + if (response.response_code() == Response::RespChatFlood) + chatView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds.")); } void TabRoom::actLeaveRoom() { - client->sendCommand(new Command_LeaveRoom(roomId)); - deleteLater(); + sendRoomCommand(prepareRoomCommand(Command_LeaveRoom())); + deleteLater(); } -void TabRoom::processRoomEvent(RoomEvent *event) +void TabRoom::actIgnoreUnregisteredUsers() { - switch (event->getItemId()) { - case ItemId_Event_ListGames: processListGamesEvent(qobject_cast(event)); break; - case ItemId_Event_JoinRoom: processJoinRoomEvent(qobject_cast(event)); break; - case ItemId_Event_LeaveRoom: processLeaveRoomEvent(qobject_cast(event)); break; - case ItemId_Event_RoomSay: processSayEvent(qobject_cast(event)); break; - default: ; - } + aIgnoreUnregisteredUsers->setChecked(!aIgnoreUnregisteredUsers->isChecked()); + settingsCache->setIgnoreUnregisteredUsers(!settingsCache->getIgnoreUnregisteredUsers()); } -void TabRoom::processListGamesEvent(Event_ListGames *event) +void TabRoom::ignoreUnregisteredUsersChanged() { - const QList &gameList = event->getGameList(); - for (int i = 0; i < gameList.size(); ++i) - gameSelector->processGameInfo(gameList[i]); + aIgnoreUnregisteredUsers->setChecked(settingsCache->getIgnoreUnregisteredUsers()); } -void TabRoom::processJoinRoomEvent(Event_JoinRoom *event) +void TabRoom::processRoomEvent(const RoomEvent &event) { - userList->processUserInfo(event->getUserInfo(), true); - userList->sortItems(); + switch (static_cast(getPbExtension(event))) { + case RoomEvent::LIST_GAMES: processListGamesEvent(event.GetExtension(Event_ListGames::ext)); break; + case RoomEvent::JOIN_ROOM: processJoinRoomEvent(event.GetExtension(Event_JoinRoom::ext)); break; + case RoomEvent::LEAVE_ROOM: processLeaveRoomEvent(event.GetExtension(Event_LeaveRoom::ext)); break; + case RoomEvent::ROOM_SAY: processRoomSayEvent(event.GetExtension(Event_RoomSay::ext)); break; + default: ; + } } -void TabRoom::processLeaveRoomEvent(Event_LeaveRoom *event) +void TabRoom::processListGamesEvent(const Event_ListGames &event) { - userList->deleteUser(event->getPlayerName()); + const int gameListSize = event.game_list_size(); + for (int i = 0; i < gameListSize; ++i) + gameSelector->processGameInfo(event.game_list(i)); } -void TabRoom::processSayEvent(Event_RoomSay *event) +void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event) { - if (!tabSupervisor->getUserListsTab()->getIgnoreList()->userInList(event->getPlayerName())) - chatView->appendMessage(event->getPlayerName(), event->getMessage()); - emit userEvent(false); + userList->processUserInfo(event.user_info(), true); + userList->sortItems(); +} + +void TabRoom::processLeaveRoomEvent(const Event_LeaveRoom &event) +{ + userList->deleteUser(QString::fromStdString(event.name())); +} + +void TabRoom::processRoomSayEvent(const Event_RoomSay &event) +{ + QString senderName = QString::fromStdString(event.name()); + if (tabSupervisor->getUserListsTab()->getIgnoreList()->getUsers().contains(senderName)) + return; + UserListTWI *twi = userList->getUsers().value(senderName); + UserLevelFlags userLevel; + if (twi) { + userLevel = UserLevelFlags(twi->getUserInfo().user_level()); + if (settingsCache->getIgnoreUnregisteredUsers() && !userLevel.testFlag(ServerInfo_User::IsRegistered)) + return; + } + chatView->appendMessage(QString::fromStdString(event.message()), senderName, userLevel); + emit userEvent(false); +} + +PendingCommand *TabRoom::prepareRoomCommand(const ::google::protobuf::Message &cmd) +{ + return client->prepareRoomCommand(cmd, roomId); +} + +void TabRoom::sendRoomCommand(PendingCommand *pend) +{ + client->sendCommand(pend); } diff --git a/cockatrice/src/tab_room.h b/cockatrice/src/tab_room.h index 37e6a3d14..857f37a25 100644 --- a/cockatrice/src/tab_room.h +++ b/cockatrice/src/tab_room.h @@ -5,6 +5,7 @@ #include #include +namespace google { namespace protobuf { class Message; } } class AbstractClient; class UserList; class QLabel; @@ -19,49 +20,60 @@ class Event_ListGames; class Event_JoinRoom; class Event_LeaveRoom; class Event_RoomSay; -class ProtocolResponse; class GameSelector; +class Response; +class PendingCommand; +class ServerInfo_User; class TabRoom : public Tab { - Q_OBJECT + Q_OBJECT private: - AbstractClient *client; - int roomId; - QString roomName; - QString ownName; - QMap gameTypes; - - GameSelector *gameSelector; - UserList *userList; - ChatView *chatView; - QLabel *sayLabel; - QLineEdit *sayEdit; - QGroupBox *chatGroupBox; - - QAction *aLeaveRoom; - QString sanitizeHtml(QString dirty) const; + AbstractClient *client; + int roomId; + QString roomName; + ServerInfo_User *ownUser; + QMap gameTypes; + + GameSelector *gameSelector; + UserList *userList; + ChatView *chatView; + QLabel *sayLabel; + QLineEdit *sayEdit; + QGroupBox *chatGroupBox; + + QMenu *roomMenu; + QAction *aLeaveRoom; + QAction *aIgnoreUnregisteredUsers; + QString sanitizeHtml(QString dirty) const; signals: - void roomClosing(TabRoom *tab); - void openMessageDialog(const QString &userName, bool focus); + void roomClosing(TabRoom *tab); + void openMessageDialog(const QString &userName, bool focus); private slots: - void sendMessage(); - void actLeaveRoom(); - void sayFinished(ProtocolResponse *response); - - void processListGamesEvent(Event_ListGames *event); - void processJoinRoomEvent(Event_JoinRoom *event); - void processLeaveRoomEvent(Event_LeaveRoom *event); - void processSayEvent(Event_RoomSay *event); + void sendMessage(); + void sayFinished(const Response &response); + void actLeaveRoom(); + void actIgnoreUnregisteredUsers(); + void ignoreUnregisteredUsersChanged(); + + void processListGamesEvent(const Event_ListGames &event); + void processJoinRoomEvent(const Event_JoinRoom &event); + void processLeaveRoomEvent(const Event_LeaveRoom &event); + void processRoomSayEvent(const Event_RoomSay &event); public: - TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, const QString &_ownName, ServerInfo_Room *info); - ~TabRoom(); - void retranslateUi(); - void closeRequest(); - void processRoomEvent(RoomEvent *event); - int getRoomId() const { return roomId; } - const QMap &getGameTypes() const { return gameTypes; } - QString getChannelName() const { return roomName; } - QString getTabText() const { return roomName; } + TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerInfo_User *_ownUser, const ServerInfo_Room &info); + ~TabRoom(); + void retranslateUi(); + void closeRequest(); + void tabActivated(); + void processRoomEvent(const RoomEvent &event); + int getRoomId() const { return roomId; } + const QMap &getGameTypes() const { return gameTypes; } + QString getChannelName() const { return roomName; } + QString getTabText() const { return roomName; } + const ServerInfo_User *getUserInfo() const { return ownUser; } + + PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd); + void sendRoomCommand(PendingCommand *pend); }; #endif diff --git a/cockatrice/src/tab_server.cpp b/cockatrice/src/tab_server.cpp index 6fd858ae4..b82248a34 100644 --- a/cockatrice/src/tab_server.cpp +++ b/cockatrice/src/tab_server.cpp @@ -11,137 +11,157 @@ #include #include "tab_server.h" #include "abstractclient.h" -#include "protocol.h" -#include "protocol_items.h" #include "userlist.h" #include "userinfobox.h" #include +#include "pending_command.h" +#include "pb/session_commands.pb.h" +#include "pb/event_list_rooms.pb.h" +#include "pb/event_server_message.pb.h" +#include "pb/response_join_room.pb.h" + RoomSelector::RoomSelector(AbstractClient *_client, QWidget *parent) - : QGroupBox(parent), client(_client) + : QGroupBox(parent), client(_client) { - roomList = new QTreeWidget; - roomList->setRootIsDecorated(false); - roomList->setColumnCount(4); - roomList->header()->setStretchLastSection(false); - roomList->header()->setResizeMode(0, QHeaderView::ResizeToContents); - roomList->header()->setResizeMode(1, QHeaderView::Stretch); - roomList->header()->setResizeMode(2, QHeaderView::ResizeToContents); - roomList->header()->setResizeMode(3, QHeaderView::ResizeToContents); - - joinButton = new QPushButton; - connect(joinButton, SIGNAL(clicked()), this, SLOT(joinClicked())); - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(); - buttonLayout->addWidget(joinButton); - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(roomList); - vbox->addLayout(buttonLayout); - - retranslateUi(); - setLayout(vbox); - - connect(client, SIGNAL(listRoomsEventReceived(Event_ListRooms *)), this, SLOT(processListRoomsEvent(Event_ListRooms *))); - client->sendCommand(new Command_ListRooms); + roomList = new QTreeWidget; + roomList->setRootIsDecorated(false); + roomList->setColumnCount(4); + roomList->header()->setStretchLastSection(false); +#if QT_VERSION < 0x050000 + roomList->header()->setResizeMode(0, QHeaderView::ResizeToContents); + roomList->header()->setResizeMode(1, QHeaderView::Stretch); + roomList->header()->setResizeMode(2, QHeaderView::ResizeToContents); + roomList->header()->setResizeMode(3, QHeaderView::ResizeToContents); +#else + roomList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + roomList->header()->setSectionResizeMode(1, QHeaderView::Stretch); + roomList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + roomList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); +#endif + joinButton = new QPushButton; + connect(joinButton, SIGNAL(clicked()), this, SLOT(joinClicked())); + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + buttonLayout->addWidget(joinButton); + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(roomList); + vbox->addLayout(buttonLayout); + + retranslateUi(); + setLayout(vbox); + + connect(client, SIGNAL(listRoomsEventReceived(const Event_ListRooms &)), this, SLOT(processListRoomsEvent(const Event_ListRooms &))); + client->sendCommand(client->prepareSessionCommand(Command_ListRooms())); } void RoomSelector::retranslateUi() { - setTitle(tr("Rooms")); - joinButton->setText(tr("Joi&n")); + setTitle(tr("Rooms")); + joinButton->setText(tr("Joi&n")); - QTreeWidgetItem *header = roomList->headerItem(); - header->setText(0, tr("Room")); - header->setText(1, tr("Description")); - header->setText(2, tr("Players")); - header->setText(3, tr("Games")); - header->setTextAlignment(2, Qt::AlignRight); - header->setTextAlignment(3, Qt::AlignRight); + QTreeWidgetItem *header = roomList->headerItem(); + header->setText(0, tr("Room")); + header->setText(1, tr("Description")); + header->setText(2, tr("Players")); + header->setText(3, tr("Games")); + header->setTextAlignment(2, Qt::AlignRight); + header->setTextAlignment(3, Qt::AlignRight); } -void RoomSelector::processListRoomsEvent(Event_ListRooms *event) +void RoomSelector::processListRoomsEvent(const Event_ListRooms &event) { - const QList &roomsToUpdate = event->getRoomList(); - for (int i = 0; i < roomsToUpdate.size(); ++i) { - ServerInfo_Room *room = roomsToUpdate[i]; - - for (int j = 0; j < roomList->topLevelItemCount(); ++j) { - QTreeWidgetItem *twi = roomList->topLevelItem(j); - if (twi->data(0, Qt::UserRole).toInt() == room->getRoomId()) { - twi->setData(0, Qt::DisplayRole, room->getName()); - twi->setData(1, Qt::DisplayRole, room->getDescription()); - twi->setData(2, Qt::DisplayRole, room->getPlayerCount()); - twi->setData(3, Qt::DisplayRole, room->getGameCount()); - return; - } - } - QTreeWidgetItem *twi = new QTreeWidgetItem; - twi->setData(0, Qt::UserRole, room->getRoomId()); - twi->setData(0, Qt::DisplayRole, room->getName()); - twi->setData(1, Qt::DisplayRole, room->getDescription()); - twi->setData(2, Qt::DisplayRole, room->getPlayerCount()); - twi->setData(3, Qt::DisplayRole, room->getGameCount()); - twi->setTextAlignment(2, Qt::AlignRight); - twi->setTextAlignment(3, Qt::AlignRight); - roomList->addTopLevelItem(twi); - if (room->getAutoJoin()) - joinRoom(room->getRoomId(), false); - } + const int roomListSize = event.room_list_size(); + for (int i = 0; i < roomListSize; ++i) { + const ServerInfo_Room &room = event.room_list(i); + + for (int j = 0; j < roomList->topLevelItemCount(); ++j) { + QTreeWidgetItem *twi = roomList->topLevelItem(j); + if (twi->data(0, Qt::UserRole).toInt() == room.room_id()) { + if (room.has_name()) + twi->setData(0, Qt::DisplayRole, QString::fromStdString(room.name())); + if (room.has_description()) + twi->setData(1, Qt::DisplayRole, QString::fromStdString(room.description())); + if (room.has_player_count()) + twi->setData(2, Qt::DisplayRole, room.player_count()); + if (room.has_game_count()) + twi->setData(3, Qt::DisplayRole, room.game_count()); + return; + } + } + QTreeWidgetItem *twi = new QTreeWidgetItem; + twi->setData(0, Qt::UserRole, room.room_id()); + if (room.has_name()) + twi->setData(0, Qt::DisplayRole, QString::fromStdString(room.name())); + if (room.has_description()) + twi->setData(1, Qt::DisplayRole, QString::fromStdString(room.description())); + twi->setData(2, Qt::DisplayRole, room.player_count()); + twi->setData(3, Qt::DisplayRole, room.game_count()); + twi->setTextAlignment(2, Qt::AlignRight); + twi->setTextAlignment(3, Qt::AlignRight); + + roomList->addTopLevelItem(twi); + if (room.has_auto_join()) + if (room.auto_join()) + joinRoom(room.room_id(), false); + } } void RoomSelector::joinRoom(int id, bool setCurrent) { - Command_JoinRoom *command = new Command_JoinRoom(id); - command->setExtraData(setCurrent); - connect(command, SIGNAL(finished(ProtocolResponse *)), this, SLOT(joinFinished(ProtocolResponse *))); - client->sendCommand(command); + Command_JoinRoom cmd; + cmd.set_room_id(id); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + pend->setExtraData(setCurrent); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(joinFinished(Response, CommandContainer, QVariant))); + + client->sendCommand(pend); } void RoomSelector::joinClicked() { - QTreeWidgetItem *twi = roomList->currentItem(); - if (!twi) - return; - - joinRoom(twi->data(0, Qt::UserRole).toInt(), true); + QTreeWidgetItem *twi = roomList->currentItem(); + if (!twi) + return; + + joinRoom(twi->data(0, Qt::UserRole).toInt(), true); } -void RoomSelector::joinFinished(ProtocolResponse *r) +void RoomSelector::joinFinished(const Response &r, const CommandContainer & /*commandContainer*/, const QVariant &extraData) { - if (r->getResponseCode() != RespOk) - return; - Response_JoinRoom *resp = qobject_cast(r); - if (!resp) - return; - - emit roomJoined(resp->getRoomInfo(), static_cast(sender())->getExtraData().toBool()); + if (r.response_code() != Response::RespOk) + return; + const Response_JoinRoom &resp = r.GetExtension(Response_JoinRoom::ext); + + emit roomJoined(resp.room_info(), extraData.toBool()); } TabServer::TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent) - : Tab(_tabSupervisor, parent), client(_client) + : Tab(_tabSupervisor, parent), client(_client) { - roomSelector = new RoomSelector(client); - serverInfoBox = new QTextBrowser; - serverInfoBox->setOpenExternalLinks(true); - - connect(roomSelector, SIGNAL(roomJoined(ServerInfo_Room *, bool)), this, SIGNAL(roomJoined(ServerInfo_Room *, bool))); - - connect(client, SIGNAL(serverMessageEventReceived(Event_ServerMessage *)), this, SLOT(processServerMessageEvent(Event_ServerMessage *))); - - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(roomSelector); - vbox->addWidget(serverInfoBox); - - setLayout(vbox); + roomSelector = new RoomSelector(client); + serverInfoBox = new QTextBrowser; + serverInfoBox->setOpenExternalLinks(true); + + connect(roomSelector, SIGNAL(roomJoined(const ServerInfo_Room &, bool)), this, SIGNAL(roomJoined(const ServerInfo_Room &, bool))); + + connect(client, SIGNAL(serverMessageEventReceived(const Event_ServerMessage &)), this, SLOT(processServerMessageEvent(const Event_ServerMessage &))); + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(roomSelector); + vbox->addWidget(serverInfoBox); + + setLayout(vbox); } void TabServer::retranslateUi() { - roomSelector->retranslateUi(); + roomSelector->retranslateUi(); } -void TabServer::processServerMessageEvent(Event_ServerMessage *event) +void TabServer::processServerMessageEvent(const Event_ServerMessage &event) { - serverInfoBox->setHtml(event->getMessage()); - emit userEvent(); + serverInfoBox->setHtml(QString::fromStdString(event.message())); + emit userEvent(); } diff --git a/cockatrice/src/tab_server.h b/cockatrice/src/tab_server.h index 556f0f977..a057660be 100644 --- a/cockatrice/src/tab_server.h +++ b/cockatrice/src/tab_server.h @@ -14,42 +14,43 @@ class QPushButton; class Event_ListRooms; class Event_ServerMessage; -class ProtocolResponse; +class Response; class ServerInfo_Room; +class CommandContainer; class RoomSelector : public QGroupBox { - Q_OBJECT + Q_OBJECT private: - QTreeWidget *roomList; - QPushButton *joinButton; - AbstractClient *client; - - void joinRoom(int id, bool setCurrent); + QTreeWidget *roomList; + QPushButton *joinButton; + AbstractClient *client; + + void joinRoom(int id, bool setCurrent); private slots: - void processListRoomsEvent(Event_ListRooms *event); - void joinClicked(); - void joinFinished(ProtocolResponse *resp); + void processListRoomsEvent(const Event_ListRooms &event); + void joinClicked(); + void joinFinished(const Response &resp, const CommandContainer &commandContainer, const QVariant &extraData); signals: - void roomJoined(ServerInfo_Room *info, bool setCurrent); + void roomJoined(const ServerInfo_Room &info, bool setCurrent); public: - RoomSelector(AbstractClient *_client, QWidget *parent = 0); - void retranslateUi(); + RoomSelector(AbstractClient *_client, QWidget *parent = 0); + void retranslateUi(); }; class TabServer : public Tab { - Q_OBJECT + Q_OBJECT signals: - void roomJoined(ServerInfo_Room *info, bool setCurrent); + void roomJoined(const ServerInfo_Room &info, bool setCurrent); private slots: - void processServerMessageEvent(Event_ServerMessage *event); + void processServerMessageEvent(const Event_ServerMessage &event); private: - AbstractClient *client; - RoomSelector *roomSelector; - QTextBrowser *serverInfoBox; + AbstractClient *client; + RoomSelector *roomSelector; + QTextBrowser *serverInfoBox; public: - TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent = 0); - void retranslateUi(); - QString getTabText() const { return tr("Server"); } + TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent = 0); + void retranslateUi(); + QString getTabText() const { return tr("Server"); } }; #endif diff --git a/cockatrice/src/tab_supervisor.cpp b/cockatrice/src/tab_supervisor.cpp index 62bf331e9..166a766ab 100644 --- a/cockatrice/src/tab_supervisor.cpp +++ b/cockatrice/src/tab_supervisor.cpp @@ -5,382 +5,488 @@ #include "tab_room.h" #include "tab_game.h" #include "tab_deck_storage.h" +#include "tab_replays.h" #include "tab_admin.h" #include "tab_message.h" #include "tab_userlists.h" -#include "protocol_items.h" +#include "tab_deck_editor.h" #include "pixmapgenerator.h" +#include "userlist.h" +#include "settingscache.h" #include #include +#include "pb/room_commands.pb.h" +#include "pb/room_event.pb.h" +#include "pb/game_event_container.pb.h" +#include "pb/event_user_message.pb.h" +#include "pb/event_game_joined.pb.h" +#include "pb/serverinfo_user.pb.h" +#include "pb/serverinfo_room.pb.h" + CloseButton::CloseButton(QWidget *parent) - : QAbstractButton(parent) + : QAbstractButton(parent) { - setFocusPolicy(Qt::NoFocus); - setCursor(Qt::ArrowCursor); - resize(sizeHint()); + setFocusPolicy(Qt::NoFocus); + setCursor(Qt::ArrowCursor); + resize(sizeHint()); } QSize CloseButton::sizeHint() const { - ensurePolished(); - int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, this); - int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, this); - return QSize(width, height); + ensurePolished(); + int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, this); + int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, this); + return QSize(width, height); } void CloseButton::enterEvent(QEvent *event) { - update(); - QAbstractButton::enterEvent(event); + update(); + QAbstractButton::enterEvent(event); } void CloseButton::leaveEvent(QEvent *event) { - update(); - QAbstractButton::leaveEvent(event); + update(); + QAbstractButton::leaveEvent(event); } void CloseButton::paintEvent(QPaintEvent * /*event*/) { - QPainter p(this); - QStyleOption opt; - opt.init(this); - opt.state |= QStyle::State_AutoRaise; - if (isEnabled() && underMouse() && !isChecked() && !isDown()) - opt.state |= QStyle::State_Raised; - if (isChecked()) - opt.state |= QStyle::State_On; - if (isDown()) - opt.state |= QStyle::State_Sunken; - - if (const QTabBar *tb = qobject_cast(parent())) { - int index = tb->currentIndex(); - QTabBar::ButtonPosition position = (QTabBar::ButtonPosition) style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tb); - if (tb->tabButton(index, position) == this) - opt.state |= QStyle::State_Selected; - } - - style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this); + QPainter p(this); + QStyleOption opt; + opt.init(this); + opt.state |= QStyle::State_AutoRaise; + if (isEnabled() && underMouse() && !isChecked() && !isDown()) + opt.state |= QStyle::State_Raised; + if (isChecked()) + opt.state |= QStyle::State_On; + if (isDown()) + opt.state |= QStyle::State_Sunken; + + if (const QTabBar *tb = qobject_cast(parent())) { + int index = tb->currentIndex(); + QTabBar::ButtonPosition position = (QTabBar::ButtonPosition) style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tb); + if (tb->tabButton(index, position) == this) + opt.state |= QStyle::State_Selected; + } + + style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this); } -TabSupervisor::TabSupervisor(QWidget *parent) - : QTabWidget(parent), client(0), tabServer(0), tabDeckStorage(0), tabAdmin(0) +TabSupervisor::TabSupervisor(AbstractClient *_client, QWidget *parent) + : QTabWidget(parent), userInfo(0), client(_client), tabServer(0), tabUserLists(0), tabDeckStorage(0), tabReplays(0), tabAdmin(0) { - tabChangedIcon = new QIcon(":/resources/icon_tab_changed.svg"); - setElideMode(Qt::ElideRight); - setIconSize(QSize(15, 15)); - connect(this, SIGNAL(currentChanged(int)), this, SLOT(updateCurrent(int))); + tabChangedIcon = new QIcon(":/resources/icon_tab_changed.svg"); + setElideMode(Qt::ElideRight); + setMovable(true); + setIconSize(QSize(15, 15)); + connect(this, SIGNAL(currentChanged(int)), this, SLOT(updateCurrent(int))); + + connect(client, SIGNAL(roomEventReceived(const RoomEvent &)), this, SLOT(processRoomEvent(const RoomEvent &))); + connect(client, SIGNAL(gameEventContainerReceived(const GameEventContainer &)), this, SLOT(processGameEventContainer(const GameEventContainer &))); + connect(client, SIGNAL(gameJoinedEventReceived(const Event_GameJoined &)), this, SLOT(gameJoined(const Event_GameJoined &))); + connect(client, SIGNAL(userMessageEventReceived(const Event_UserMessage &)), this, SLOT(processUserMessageEvent(const Event_UserMessage &))); + connect(client, SIGNAL(maxPingTime(int, int)), this, SLOT(updatePingTime(int, int))); + + retranslateUi(); } TabSupervisor::~TabSupervisor() { - stop(); - delete tabChangedIcon; + stop(); + delete tabChangedIcon; } void TabSupervisor::retranslateUi() { - QList tabs; - if (tabServer) - tabs.append(tabServer); - if (tabDeckStorage) - tabs.append(tabDeckStorage); - QMapIterator roomIterator(roomTabs); - while (roomIterator.hasNext()) - tabs.append(roomIterator.next().value()); - QMapIterator gameIterator(gameTabs); - while (gameIterator.hasNext()) - tabs.append(gameIterator.next().value()); - - for (int i = 0; i < tabs.size(); ++i) { - setTabText(indexOf(tabs[i]), tabs[i]->getTabText()); - tabs[i]->retranslateUi(); - } + QList tabs; + tabs.append(tabServer); + tabs.append(tabReplays); + tabs.append(tabDeckStorage); + tabs.append(tabAdmin); + tabs.append(tabUserLists); + QMapIterator roomIterator(roomTabs); + while (roomIterator.hasNext()) + tabs.append(roomIterator.next().value()); + QMapIterator gameIterator(gameTabs); + while (gameIterator.hasNext()) + tabs.append(gameIterator.next().value()); + QListIterator replayIterator(replayTabs); + while (replayIterator.hasNext()) + tabs.append(replayIterator.next()); + QListIterator deckEditorIterator(deckEditorTabs); + while (deckEditorIterator.hasNext()) + tabs.append(deckEditorIterator.next()); + QMapIterator messageIterator(messageTabs); + while (messageIterator.hasNext()) + tabs.append(messageIterator.next().value()); + + for (int i = 0; i < tabs.size(); ++i) + if (tabs[i]) { + setTabText(indexOf(tabs[i]), tabs[i]->getTabText()); + tabs[i]->retranslateUi(); + } +} + +AbstractClient *TabSupervisor::getClient() const +{ + return localClients.isEmpty() ? client : localClients.first(); } int TabSupervisor::myAddTab(Tab *tab) { - connect(tab, SIGNAL(userEvent(bool)), this, SLOT(tabUserEvent(bool))); - return addTab(tab, tab->getTabText()); + connect(tab, SIGNAL(userEvent(bool)), this, SLOT(tabUserEvent(bool))); + connect(tab, SIGNAL(tabTextChanged(Tab *, QString)), this, SLOT(updateTabText(Tab *, QString))); + return addTab(tab, tab->getTabText()); } -void TabSupervisor::start(AbstractClient *_client, ServerInfo_User *_userInfo) +void TabSupervisor::start(const ServerInfo_User &_userInfo) { - client = _client; - userInfo = new ServerInfo_User(_userInfo); - - connect(client, SIGNAL(roomEventReceived(RoomEvent *)), this, SLOT(processRoomEvent(RoomEvent *))); - connect(client, SIGNAL(gameEventContainerReceived(GameEventContainer *)), this, SLOT(processGameEventContainer(GameEventContainer *))); - connect(client, SIGNAL(gameJoinedEventReceived(Event_GameJoined *)), this, SLOT(gameJoined(Event_GameJoined *))); - connect(client, SIGNAL(messageEventReceived(Event_Message *)), this, SLOT(processMessageEvent(Event_Message *))); - connect(client, SIGNAL(maxPingTime(int, int)), this, SLOT(updatePingTime(int, int))); + userInfo = new ServerInfo_User(_userInfo); + + tabServer = new TabServer(this, client); + connect(tabServer, SIGNAL(roomJoined(const ServerInfo_Room &, bool)), this, SLOT(addRoomTab(const ServerInfo_Room &, bool))); + myAddTab(tabServer); + + tabUserLists = new TabUserLists(this, client, *userInfo); + connect(tabUserLists, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool))); + connect(tabUserLists, SIGNAL(userJoined(ServerInfo_User)), this, SLOT(processUserJoined(ServerInfo_User))); + connect(tabUserLists, SIGNAL(userLeft(const QString &)), this, SLOT(processUserLeft(const QString &))); + myAddTab(tabUserLists); + + updatePingTime(0, -1); + + if (userInfo->user_level() & ServerInfo_User::IsRegistered) { + tabDeckStorage = new TabDeckStorage(this, client); + connect(tabDeckStorage, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *))); + myAddTab(tabDeckStorage); + + tabReplays = new TabReplays(this, client); + connect(tabReplays, SIGNAL(openReplay(GameReplay *)), this, SLOT(openReplay(GameReplay *))); + myAddTab(tabReplays); + } else { + tabDeckStorage = 0; + tabReplays = 0; + } + + if (userInfo->user_level() & ServerInfo_User::IsModerator) { + tabAdmin = new TabAdmin(this, client, (userInfo->user_level() & ServerInfo_User::IsAdmin)); + connect(tabAdmin, SIGNAL(adminLockChanged(bool)), this, SIGNAL(adminLockChanged(bool))); + myAddTab(tabAdmin); + } else + tabAdmin = 0; - tabServer = new TabServer(this, client); - connect(tabServer, SIGNAL(roomJoined(ServerInfo_Room *, bool)), this, SLOT(addRoomTab(ServerInfo_Room *, bool))); - myAddTab(tabServer); - - tabUserLists = new TabUserLists(this, client, userInfo); - connect(tabUserLists, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool))); - connect(tabUserLists, SIGNAL(userJoined(const QString &)), this, SLOT(processUserJoined(const QString &))); - connect(tabUserLists, SIGNAL(userLeft(const QString &)), this, SLOT(processUserLeft(const QString &))); - myAddTab(tabUserLists); - - updatePingTime(0, -1); - - if (userInfo->getUserLevel() & ServerInfo_User::IsRegistered) { - tabDeckStorage = new TabDeckStorage(this, client); - myAddTab(tabDeckStorage); - } else - tabDeckStorage = 0; - - if (userInfo->getUserLevel() & ServerInfo_User::IsModerator) { - tabAdmin = new TabAdmin(this, client, (userInfo->getUserLevel() & ServerInfo_User::IsAdmin)); - connect(tabAdmin, SIGNAL(adminLockChanged(bool)), this, SIGNAL(adminLockChanged(bool))); - myAddTab(tabAdmin); - } else - tabAdmin = 0; - - retranslateUi(); + retranslateUi(); } void TabSupervisor::startLocal(const QList &_clients) { - tabUserLists = 0; - tabDeckStorage = 0; - tabAdmin = 0; - userInfo = new ServerInfo_User; - localClients = _clients; - for (int i = 0; i < localClients.size(); ++i) - connect(localClients[i], SIGNAL(gameEventContainerReceived(GameEventContainer *)), this, SLOT(processGameEventContainer(GameEventContainer *))); - connect(localClients.first(), SIGNAL(gameJoinedEventReceived(Event_GameJoined *)), this, SLOT(localGameJoined(Event_GameJoined *))); + tabUserLists = 0; + tabDeckStorage = 0; + tabReplays = 0; + tabAdmin = 0; + userInfo = new ServerInfo_User; + localClients = _clients; + for (int i = 0; i < localClients.size(); ++i) + connect(localClients[i], SIGNAL(gameEventContainerReceived(const GameEventContainer &)), this, SLOT(processGameEventContainer(const GameEventContainer &))); + connect(localClients.first(), SIGNAL(gameJoinedEventReceived(const Event_GameJoined &)), this, SLOT(localGameJoined(const Event_GameJoined &))); } void TabSupervisor::stop() { - if ((!client) && localClients.isEmpty()) - return; - - if (client) { - disconnect(client, 0, this, 0); - client = 0; - } - - if (!localClients.isEmpty()) { - for (int i = 0; i < localClients.size(); ++i) - localClients[i]->deleteLater(); - localClients.clear(); - - emit localGameEnded(); - } else { - tabUserLists->deleteLater(); - tabServer->deleteLater(); - tabDeckStorage->deleteLater(); - } - tabUserLists = 0; - tabServer = 0; - tabDeckStorage = 0; - clear(); - - QMapIterator roomIterator(roomTabs); - while (roomIterator.hasNext()) - roomIterator.next().value()->deleteLater(); - roomTabs.clear(); + if ((!client) && localClients.isEmpty()) + return; + + if (!localClients.isEmpty()) { + for (int i = 0; i < localClients.size(); ++i) + localClients[i]->deleteLater(); + localClients.clear(); + + emit localGameEnded(); + } else { + if (tabUserLists) + tabUserLists->deleteLater(); + if (tabServer) + tabServer->deleteLater(); + if (tabDeckStorage) + tabDeckStorage->deleteLater(); + if (tabReplays) + tabReplays->deleteLater(); + if (tabAdmin) + tabAdmin->deleteLater(); + } + tabUserLists = 0; + tabServer = 0; + tabDeckStorage = 0; + tabReplays = 0; + tabAdmin = 0; + + QMapIterator roomIterator(roomTabs); + while (roomIterator.hasNext()) + roomIterator.next().value()->deleteLater(); + roomTabs.clear(); - QMapIterator gameIterator(gameTabs); - while (gameIterator.hasNext()) - gameIterator.next().value()->deleteLater(); - gameTabs.clear(); + QMapIterator gameIterator(gameTabs); + while (gameIterator.hasNext()) + gameIterator.next().value()->deleteLater(); + gameTabs.clear(); - QMapIterator messageIterator(messageTabs); - while (messageIterator.hasNext()) - messageIterator.next().value()->deleteLater(); - messageTabs.clear(); - - delete userInfo; - userInfo = 0; + QListIterator replayIterator(replayTabs); + while (replayIterator.hasNext()) + replayIterator.next()->deleteLater(); + replayTabs.clear(); + + QMapIterator messageIterator(messageTabs); + while (messageIterator.hasNext()) + messageIterator.next().value()->deleteLater(); + messageTabs.clear(); + + delete userInfo; + userInfo = 0; } void TabSupervisor::updatePingTime(int value, int max) { - if (!tabServer) - return; - if (tabServer->getContentsChanged()) - return; - - setTabIcon(0, QIcon(PingPixmapGenerator::generatePixmap(15, value, max))); + if (!tabServer) + return; + if (tabServer->getContentsChanged()) + return; + + setTabIcon(indexOf(tabServer), QIcon(PingPixmapGenerator::generatePixmap(15, value, max))); } void TabSupervisor::closeButtonPressed() { - Tab *tab = static_cast(static_cast(sender())->property("tab").value()); - tab->closeRequest(); + Tab *tab = static_cast(static_cast(sender())->property("tab").value()); + tab->closeRequest(); } void TabSupervisor::addCloseButtonToTab(Tab *tab, int tabIndex) { - QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition) tabBar()->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tabBar()); - CloseButton *closeButton = new CloseButton; - connect(closeButton, SIGNAL(clicked()), this, SLOT(closeButtonPressed())); - closeButton->setProperty("tab", qVariantFromValue((QObject *) tab)); - tabBar()->setTabButton(tabIndex, closeSide, closeButton); + QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition) tabBar()->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tabBar()); + CloseButton *closeButton = new CloseButton; + connect(closeButton, SIGNAL(clicked()), this, SLOT(closeButtonPressed())); + closeButton->setProperty("tab", qVariantFromValue((QObject *) tab)); + tabBar()->setTabButton(tabIndex, closeSide, closeButton); } -void TabSupervisor::gameJoined(Event_GameJoined *event) +void TabSupervisor::gameJoined(const Event_GameJoined &event) { - TabGame *tab = new TabGame(this, QList() << client, event->getGameId(), event->getGameDescription(), event->getHostId(), event->getPlayerId(), event->getSpectator(), event->getSpectatorsCanTalk(), event->getSpectatorsSeeEverything(), event->getResuming()); - connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *))); - connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool))); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); - gameTabs.insert(event->getGameId(), tab); - setCurrentWidget(tab); + QMap roomGameTypes; + TabRoom *room = roomTabs.value(event.game_info().room_id()); + if (room) + roomGameTypes = room->getGameTypes(); + else + for (int i = 0; i < event.game_types_size(); ++i) + roomGameTypes.insert(event.game_types(i).game_type_id(), QString::fromStdString(event.game_types(i).description())); + + TabGame *tab = new TabGame(this, QList() << client, event, roomGameTypes); + connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *))); + connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool))); + connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *))); + int tabIndex = myAddTab(tab); + addCloseButtonToTab(tab, tabIndex); + gameTabs.insert(event.game_info().game_id(), tab); + setCurrentWidget(tab); } -void TabSupervisor::localGameJoined(Event_GameJoined *event) +void TabSupervisor::localGameJoined(const Event_GameJoined &event) { - TabGame *tab = new TabGame(this, localClients, event->getGameId(), event->getGameDescription(), event->getHostId(), event->getPlayerId(), event->getSpectator(), event->getSpectatorsCanTalk(), event->getSpectatorsSeeEverything(), event->getResuming()); - connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *))); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); - gameTabs.insert(event->getGameId(), tab); - setCurrentWidget(tab); - - for (int i = 1; i < localClients.size(); ++i) { - Command_JoinGame *cmd = new Command_JoinGame(0, event->getGameId()); - localClients[i]->sendCommand(cmd); - } + TabGame *tab = new TabGame(this, localClients, event, QMap()); + connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *))); + connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *))); + int tabIndex = myAddTab(tab); + addCloseButtonToTab(tab, tabIndex); + gameTabs.insert(event.game_info().game_id(), tab); + setCurrentWidget(tab); + + for (int i = 1; i < localClients.size(); ++i) { + Command_JoinGame cmd; + cmd.set_game_id(event.game_info().game_id()); + localClients[i]->sendCommand(localClients[i]->prepareRoomCommand(cmd, 0)); + } } void TabSupervisor::gameLeft(TabGame *tab) { - emit setMenu(0); + if (tab == currentWidget()) + emit setMenu(); - gameTabs.remove(tab->getGameId()); - removeTab(indexOf(tab)); - - if (!localClients.isEmpty()) - stop(); + gameTabs.remove(tab->getGameId()); + removeTab(indexOf(tab)); + + if (!localClients.isEmpty()) + stop(); } -void TabSupervisor::addRoomTab(ServerInfo_Room *info, bool setCurrent) +void TabSupervisor::addRoomTab(const ServerInfo_Room &info, bool setCurrent) { - TabRoom *tab = new TabRoom(this, client, userInfo->getName(), info); - connect(tab, SIGNAL(roomClosing(TabRoom *)), this, SLOT(roomLeft(TabRoom *))); - connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool))); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); - roomTabs.insert(info->getRoomId(), tab); - if (setCurrent) - setCurrentWidget(tab); + TabRoom *tab = new TabRoom(this, client, userInfo, info); + connect(tab, SIGNAL(roomClosing(TabRoom *)), this, SLOT(roomLeft(TabRoom *))); + connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool))); + int tabIndex = myAddTab(tab); + addCloseButtonToTab(tab, tabIndex); + roomTabs.insert(info.room_id(), tab); + if (setCurrent) + setCurrentWidget(tab); } void TabSupervisor::roomLeft(TabRoom *tab) { - emit setMenu(0); + if (tab == currentWidget()) + emit setMenu(); + + roomTabs.remove(tab->getRoomId()); + removeTab(indexOf(tab)); +} - roomTabs.remove(tab->getRoomId()); - removeTab(indexOf(tab)); +void TabSupervisor::openReplay(GameReplay *replay) +{ + TabGame *replayTab = new TabGame(this, replay); + connect(replayTab, SIGNAL(gameClosing(TabGame *)), this, SLOT(replayLeft(TabGame *))); + int tabIndex = myAddTab(replayTab); + addCloseButtonToTab(replayTab, tabIndex); + replayTabs.append(replayTab); + setCurrentWidget(replayTab); +} + +void TabSupervisor::replayLeft(TabGame *tab) +{ + if (tab == currentWidget()) + emit setMenu(); + + replayTabs.removeAt(replayTabs.indexOf(tab)); } TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus) { - if (receiverName == userInfo->getName()) - return 0; - - TabMessage *tab = new TabMessage(this, client, userInfo->getName(), receiverName); - connect(tab, SIGNAL(talkClosing(TabMessage *)), this, SLOT(talkLeft(TabMessage *))); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); - messageTabs.insert(receiverName, tab); - if (focus) - setCurrentWidget(tab); - return tab; + if (receiverName == QString::fromStdString(userInfo->name())) + return 0; + + ServerInfo_User otherUser; + UserListTWI *twi = tabUserLists->getAllUsersList()->getUsers().value(receiverName); + if (twi) + otherUser = twi->getUserInfo(); + else + otherUser.set_name(receiverName.toStdString()); + TabMessage *tab = new TabMessage(this, client, *userInfo, otherUser); + connect(tab, SIGNAL(talkClosing(TabMessage *)), this, SLOT(talkLeft(TabMessage *))); + int tabIndex = myAddTab(tab); + addCloseButtonToTab(tab, tabIndex); + messageTabs.insert(receiverName, tab); + if (focus) + setCurrentWidget(tab); + return tab; } void TabSupervisor::talkLeft(TabMessage *tab) { - emit setMenu(0); + if (tab == currentWidget()) + emit setMenu(); - messageTabs.remove(tab->getUserName()); - removeTab(indexOf(tab)); + messageTabs.remove(tab->getUserName()); + removeTab(indexOf(tab)); +} + +TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen) +{ + TabDeckEditor *tab = new TabDeckEditor(this); + if (deckToOpen) + tab->setDeck(new DeckLoader(*deckToOpen)); + connect(tab, SIGNAL(deckEditorClosing(TabDeckEditor *)), this, SLOT(deckEditorClosed(TabDeckEditor *))); + int tabIndex = myAddTab(tab); + addCloseButtonToTab(tab, tabIndex); + deckEditorTabs.append(tab); + setCurrentWidget(tab); + return tab; +} + +void TabSupervisor::deckEditorClosed(TabDeckEditor *tab) +{ + if (tab == currentWidget()) + emit setMenu(); + + deckEditorTabs.removeAt(deckEditorTabs.indexOf(tab)); + removeTab(indexOf(tab)); } void TabSupervisor::tabUserEvent(bool globalEvent) { - Tab *tab = static_cast(sender()); - if (tab != currentWidget()) { - tab->setContentsChanged(true); - setTabIcon(indexOf(tab), *tabChangedIcon); - } - if (globalEvent) - QApplication::alert(this); + Tab *tab = static_cast(sender()); + if (tab != currentWidget()) { + tab->setContentsChanged(true); + setTabIcon(indexOf(tab), *tabChangedIcon); + } + if (globalEvent && settingsCache->getNotificationsEnabled()) + QApplication::alert(this); } -void TabSupervisor::processRoomEvent(RoomEvent *event) +void TabSupervisor::updateTabText(Tab *tab, const QString &newTabText) { - TabRoom *tab = roomTabs.value(event->getRoomId(), 0); - if (tab) - tab->processRoomEvent(event); + setTabText(indexOf(tab), newTabText); } -void TabSupervisor::processGameEventContainer(GameEventContainer *cont) +void TabSupervisor::processRoomEvent(const RoomEvent &event) { - TabGame *tab = gameTabs.value(cont->getGameId()); - if (tab) { - qDebug() << "gameEvent gameId =" << cont->getGameId(); - tab->processGameEventContainer(cont, qobject_cast(sender())); - } else - qDebug() << "gameEvent: invalid gameId"; + TabRoom *tab = roomTabs.value(event.room_id(), 0); + if (tab) + tab->processRoomEvent(event); } -void TabSupervisor::processMessageEvent(Event_Message *event) +void TabSupervisor::processGameEventContainer(const GameEventContainer &cont) { - TabMessage *tab = messageTabs.value(event->getSenderName()); - if (!tab) - tab = messageTabs.value(event->getReceiverName()); - if (!tab) - tab = addMessageTab(event->getSenderName(), false); - if (!tab) - return; - tab->processMessageEvent(event); + TabGame *tab = gameTabs.value(cont.game_id()); + if (tab) + tab->processGameEventContainer(cont, qobject_cast(sender())); + else + qDebug() << "gameEvent: invalid gameId"; +} + +void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event) +{ + TabMessage *tab = messageTabs.value(QString::fromStdString(event.sender_name())); + if (!tab) + tab = messageTabs.value(QString::fromStdString(event.receiver_name())); + if (!tab) + tab = addMessageTab(QString::fromStdString(event.sender_name()), false); + if (!tab) + return; + tab->processUserMessageEvent(event); } void TabSupervisor::processUserLeft(const QString &userName) { - TabMessage *tab = messageTabs.value(userName); - if (tab) - tab->processUserLeft(); + TabMessage *tab = messageTabs.value(userName); + if (tab) + tab->processUserLeft(); } -void TabSupervisor::processUserJoined(const QString &userName) +void TabSupervisor::processUserJoined(const ServerInfo_User &userInfo) { - TabMessage *tab = messageTabs.value(userName); - if (tab) - tab->processUserJoined(); + TabMessage *tab = messageTabs.value(QString::fromStdString(userInfo.name())); + if (tab) + tab->processUserJoined(userInfo); } void TabSupervisor::updateCurrent(int index) { - if (index != -1) { - Tab *tab = static_cast(widget(index)); - if (tab->getContentsChanged()) { - setTabIcon(index, QIcon()); - tab->setContentsChanged(false); - } - emit setMenu(static_cast(widget(index))->getTabMenu()); - } else - emit setMenu(0); + if (index != -1) { + Tab *tab = static_cast(widget(index)); + if (tab->getContentsChanged()) { + setTabIcon(index, QIcon()); + tab->setContentsChanged(false); + } + emit setMenu(static_cast(widget(index))->getTabMenus()); + tab->tabActivated(); + } else + emit setMenu(); } bool TabSupervisor::getAdminLocked() const { - if (!tabAdmin) - return true; - return tabAdmin->getLocked(); -} - -int TabSupervisor::getUserLevel() const -{ - return userInfo->getUserLevel(); + if (!tabAdmin) + return true; + return tabAdmin->getLocked(); } diff --git a/cockatrice/src/tab_supervisor.h b/cockatrice/src/tab_supervisor.h index de05c58b3..48af3c8a3 100644 --- a/cockatrice/src/tab_supervisor.h +++ b/cockatrice/src/tab_supervisor.h @@ -4,6 +4,7 @@ #include #include #include +#include "deck_loader.h" class QMenu; class AbstractClient; @@ -12,77 +13,91 @@ class TabServer; class TabRoom; class TabGame; class TabDeckStorage; +class TabReplays; class TabAdmin; class TabMessage; class TabUserLists; +class TabDeckEditor; class RoomEvent; class GameEventContainer; class Event_GameJoined; -class Event_Message; +class Event_UserMessage; class ServerInfo_Room; class ServerInfo_User; +class GameReplay; +class DeckList; class CloseButton : public QAbstractButton { - Q_OBJECT + Q_OBJECT public: - CloseButton(QWidget *parent = 0); - QSize sizeHint() const; - inline QSize minimumSizeHint() const { return sizeHint(); } + CloseButton(QWidget *parent = 0); + QSize sizeHint() const; + inline QSize minimumSizeHint() const { return sizeHint(); } protected: - void enterEvent(QEvent *event); - void leaveEvent(QEvent *event); - void paintEvent(QPaintEvent *event); + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + void paintEvent(QPaintEvent *event); }; class TabSupervisor : public QTabWidget { - Q_OBJECT + Q_OBJECT private: - ServerInfo_User *userInfo; - QIcon *tabChangedIcon; - AbstractClient *client; - QList localClients; - TabServer *tabServer; - TabUserLists *tabUserLists; - TabDeckStorage *tabDeckStorage; - TabAdmin *tabAdmin; - QMap roomTabs; - QMap gameTabs; - QMap messageTabs; - int myAddTab(Tab *tab); - void addCloseButtonToTab(Tab *tab, int tabIndex); + ServerInfo_User *userInfo; + QIcon *tabChangedIcon; + AbstractClient *client; + QList localClients; + TabServer *tabServer; + TabUserLists *tabUserLists; + TabDeckStorage *tabDeckStorage; + TabReplays *tabReplays; + TabAdmin *tabAdmin; + QMap roomTabs; + QMap gameTabs; + QList replayTabs; + QMap messageTabs; + QList deckEditorTabs; + int myAddTab(Tab *tab); + void addCloseButtonToTab(Tab *tab, int tabIndex); public: - TabSupervisor(QWidget *parent = 0); - ~TabSupervisor(); - void retranslateUi(); - void start(AbstractClient *_client, ServerInfo_User *userInfo); - void startLocal(const QList &_clients); - void stop(); - int getGameCount() const { return gameTabs.size(); } - TabUserLists *getUserListsTab() const { return tabUserLists; } - ServerInfo_User *getUserInfo() const { return userInfo; } - bool getAdminLocked() const; - int getUserLevel() const; + TabSupervisor(AbstractClient *_client, QWidget *parent = 0); + ~TabSupervisor(); + void retranslateUi(); + void start(const ServerInfo_User &userInfo); + void startLocal(const QList &_clients); + void stop(); + int getGameCount() const { return gameTabs.size(); } + TabUserLists *getUserListsTab() const { return tabUserLists; } + ServerInfo_User *getUserInfo() const { return userInfo; } + AbstractClient *getClient() const; + const QMap &getRoomTabs() const { return roomTabs; } + bool getAdminLocked() const; signals: - void setMenu(QMenu *menu); - void localGameEnded(); - void adminLockChanged(bool lock); + void setMenu(const QList &newMenuList = QList()); + void localGameEnded(); + void adminLockChanged(bool lock); +public slots: + TabDeckEditor *addDeckEditorTab(const DeckLoader *deckToOpen); + void openReplay(GameReplay *replay); private slots: - void closeButtonPressed(); - void updateCurrent(int index); - void updatePingTime(int value, int max); - void gameJoined(Event_GameJoined *event); - void localGameJoined(Event_GameJoined *event); - void gameLeft(TabGame *tab); - void addRoomTab(ServerInfo_Room *info, bool setCurrent); - void roomLeft(TabRoom *tab); - TabMessage *addMessageTab(const QString &userName, bool focus); - void processUserLeft(const QString &userName); - void processUserJoined(const QString &userName); - void talkLeft(TabMessage *tab); - void tabUserEvent(bool globalEvent); - void processRoomEvent(RoomEvent *event); - void processGameEventContainer(GameEventContainer *cont); - void processMessageEvent(Event_Message *event); + void closeButtonPressed(); + void updateCurrent(int index); + void updatePingTime(int value, int max); + void gameJoined(const Event_GameJoined &event); + void localGameJoined(const Event_GameJoined &event); + void gameLeft(TabGame *tab); + void addRoomTab(const ServerInfo_Room &info, bool setCurrent); + void roomLeft(TabRoom *tab); + TabMessage *addMessageTab(const QString &userName, bool focus); + void replayLeft(TabGame *tab); + void processUserLeft(const QString &userName); + void processUserJoined(const ServerInfo_User &userInfo); + void talkLeft(TabMessage *tab); + void deckEditorClosed(TabDeckEditor *tab); + void tabUserEvent(bool globalEvent); + void updateTabText(Tab *tab, const QString &newTabText); + void processRoomEvent(const RoomEvent &event); + void processGameEventContainer(const GameEventContainer &cont); + void processUserMessageEvent(const Event_UserMessage &event); }; #endif diff --git a/cockatrice/src/tab_userlists.cpp b/cockatrice/src/tab_userlists.cpp index 825c9c4b3..61fe693c6 100644 --- a/cockatrice/src/tab_userlists.cpp +++ b/cockatrice/src/tab_userlists.cpp @@ -1,142 +1,208 @@ #include "tab_userlists.h" #include "userlist.h" #include "userinfobox.h" -#include "protocol_items.h" #include "abstractclient.h" -#include #include #include +#include +#include -TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerInfo_User *userInfo, QWidget *parent) - : Tab(_tabSupervisor, parent), client(_client) +#include "pending_command.h" +#include "pb/session_commands.pb.h" +#include "pb/response_list_users.pb.h" +#include "pb/event_user_joined.pb.h" +#include "pb/event_user_left.pb.h" +#include "pb/event_add_to_list.pb.h" +#include "pb/event_remove_from_list.pb.h" + +TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &userInfo, QWidget *parent) + : Tab(_tabSupervisor, parent), client(_client) { - allUsersList = new UserList(_tabSupervisor, client, UserList::AllUsersList); - buddyList = new UserList(_tabSupervisor, client, UserList::BuddyList); - ignoreList = new UserList(_tabSupervisor, client, UserList::IgnoreList); - userInfoBox = new UserInfoBox(client, false); - userInfoBox->updateInfo(userInfo); - - connect(allUsersList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); - connect(buddyList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); - connect(ignoreList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); - - connect(client, SIGNAL(userJoinedEventReceived(Event_UserJoined *)), this, SLOT(processUserJoinedEvent(Event_UserJoined *))); - connect(client, SIGNAL(userLeftEventReceived(Event_UserLeft *)), this, SLOT(processUserLeftEvent(Event_UserLeft *))); - connect(client, SIGNAL(buddyListReceived(const QList &)), this, SLOT(buddyListReceived(const QList &))); - connect(client, SIGNAL(ignoreListReceived(const QList &)), this, SLOT(ignoreListReceived(const QList &))); - connect(client, SIGNAL(addToListEventReceived(Event_AddToList *)), this, SLOT(processAddToListEvent(Event_AddToList *))); - connect(client, SIGNAL(removeFromListEventReceived(Event_RemoveFromList *)), this, SLOT(processRemoveFromListEvent(Event_RemoveFromList *))); - - Command_ListUsers *cmd = new Command_ListUsers; - connect(cmd, SIGNAL(finished(ProtocolResponse *)), this, SLOT(processListUsersResponse(ProtocolResponse *))); - client->sendCommand(cmd); - - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(userInfoBox); - vbox->addWidget(allUsersList); - - QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->addWidget(buddyList); - mainLayout->addWidget(ignoreList); - mainLayout->addLayout(vbox); - - setLayout(mainLayout); + allUsersList = new UserList(_tabSupervisor, client, UserList::AllUsersList); + buddyList = new UserList(_tabSupervisor, client, UserList::BuddyList); + ignoreList = new UserList(_tabSupervisor, client, UserList::IgnoreList); + userInfoBox = new UserInfoBox(client, false); + userInfoBox->updateInfo(userInfo); + + connect(allUsersList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); + connect(buddyList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); + connect(ignoreList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); + + connect(client, SIGNAL(userJoinedEventReceived(const Event_UserJoined &)), this, SLOT(processUserJoinedEvent(const Event_UserJoined &))); + connect(client, SIGNAL(userLeftEventReceived(const Event_UserLeft &)), this, SLOT(processUserLeftEvent(const Event_UserLeft &))); + connect(client, SIGNAL(buddyListReceived(const QList &)), this, SLOT(buddyListReceived(const QList &))); + connect(client, SIGNAL(ignoreListReceived(const QList &)), this, SLOT(ignoreListReceived(const QList &))); + connect(client, SIGNAL(addToListEventReceived(const Event_AddToList &)), this, SLOT(processAddToListEvent(const Event_AddToList &))); + connect(client, SIGNAL(removeFromListEventReceived(const Event_RemoveFromList &)), this, SLOT(processRemoveFromListEvent(const Event_RemoveFromList &))); + + PendingCommand *pend = client->prepareSessionCommand(Command_ListUsers()); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(processListUsersResponse(const Response &))); + client->sendCommand(pend); + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(userInfoBox); + vbox->addWidget(allUsersList); + + QHBoxLayout *addToBuddyList = new QHBoxLayout; + addBuddyEdit = new QLineEdit; + addBuddyEdit->setPlaceholderText(tr("Add to Buddy List")); + connect(addBuddyEdit, SIGNAL(returnPressed()), this, SLOT(addToBuddyList())); + QPushButton *addBuddyButton = new QPushButton("Add"); + connect(addBuddyButton, SIGNAL(clicked()), this, SLOT(addToBuddyList())); + addToBuddyList->addWidget(addBuddyEdit); + addToBuddyList->addWidget(addBuddyButton); + + QHBoxLayout *addToIgnoreList = new QHBoxLayout; + addIgnoreEdit = new QLineEdit; + addIgnoreEdit->setPlaceholderText(tr("Add to Ignore List")); + connect(addIgnoreEdit, SIGNAL(returnPressed()), this, SLOT(addToIgnoreList())); + QPushButton *addIgnoreButton = new QPushButton("Add"); + connect(addIgnoreButton, SIGNAL(clicked()), this, SLOT(addToIgnoreList())); + addToIgnoreList->addWidget(addIgnoreEdit); + addToIgnoreList->addWidget(addIgnoreButton); + + QVBoxLayout *buddyPanel = new QVBoxLayout; + buddyPanel->addWidget(buddyList); + buddyPanel->addLayout(addToBuddyList); + + QVBoxLayout *ignorePanel = new QVBoxLayout; + ignorePanel->addWidget(ignoreList); + ignorePanel->addLayout(addToIgnoreList); + + QHBoxLayout *mainLayout = new QHBoxLayout; + mainLayout->addLayout(buddyPanel); + mainLayout->addLayout(ignorePanel); + mainLayout->addLayout(vbox); + + setLayout(mainLayout); } +void TabUserLists::addToBuddyList() +{ + QString userName = addBuddyEdit->text(); + if (userName.length() < 1) return; + + std::string listName = "buddy"; + addToList(listName, userName); + addBuddyEdit->clear(); +} + +void TabUserLists::addToIgnoreList() +{ + QString userName = addIgnoreEdit->text(); + if (userName.length() < 1) return; + + std::string listName = "ignore"; + addToList(listName, userName); + addIgnoreEdit->clear(); +} + +void TabUserLists::addToList(const std::string &listName, const QString &userName) +{ + Command_AddToList cmd; + cmd.set_list(listName); + cmd.set_user_name(userName.toStdString()); + + client->sendCommand(client->prepareSessionCommand(cmd)); +} + + void TabUserLists::retranslateUi() { - allUsersList->retranslateUi(); - buddyList->retranslateUi(); - ignoreList->retranslateUi(); - userInfoBox->retranslateUi(); + allUsersList->retranslateUi(); + buddyList->retranslateUi(); + ignoreList->retranslateUi(); + userInfoBox->retranslateUi(); } -void TabUserLists::processListUsersResponse(ProtocolResponse *response) +void TabUserLists::processListUsersResponse(const Response &response) { - Response_ListUsers *resp = qobject_cast(response); - if (!resp) - return; - - const QList &respList = resp->getUserList(); - for (int i = 0; i < respList.size(); ++i) { - allUsersList->processUserInfo(respList[i], true); - ignoreList->setUserOnline(respList[i]->getName(), true); - buddyList->setUserOnline(respList[i]->getName(), true); - } - - allUsersList->sortItems(); - ignoreList->sortItems(); - buddyList->sortItems(); + const Response_ListUsers &resp = response.GetExtension(Response_ListUsers::ext); + + const int userListSize = resp.user_list_size(); + for (int i = 0; i < userListSize; ++i) { + const ServerInfo_User &info = resp.user_list(i); + const QString userName = QString::fromStdString(info.name()); + allUsersList->processUserInfo(info, true); + ignoreList->setUserOnline(userName, true); + buddyList->setUserOnline(userName, true); + } + + allUsersList->sortItems(); + ignoreList->sortItems(); + buddyList->sortItems(); } -void TabUserLists::processUserJoinedEvent(Event_UserJoined *event) +void TabUserLists::processUserJoinedEvent(const Event_UserJoined &event) { - ServerInfo_User *info = event->getUserInfo(); - allUsersList->processUserInfo(info, true); - ignoreList->setUserOnline(info->getName(), true); - buddyList->setUserOnline(info->getName(), true); - - allUsersList->sortItems(); - ignoreList->sortItems(); - buddyList->sortItems(); - - emit userJoined(event->getUserInfo()->getName()); + const ServerInfo_User &info = event.user_info(); + const QString userName = QString::fromStdString(info.name()); + + allUsersList->processUserInfo(info, true); + ignoreList->setUserOnline(userName, true); + buddyList->setUserOnline(userName, true); + + allUsersList->sortItems(); + ignoreList->sortItems(); + buddyList->sortItems(); + + emit userJoined(info); } -void TabUserLists::processUserLeftEvent(Event_UserLeft *event) +void TabUserLists::processUserLeftEvent(const Event_UserLeft &event) { - QString userName = event->getUserName(); - if (allUsersList->deleteUser(userName)) { - ignoreList->setUserOnline(userName, false); - buddyList->setUserOnline(userName, false); - ignoreList->sortItems(); - buddyList->sortItems(); - - emit userLeft(userName); - } + QString userName = QString::fromStdString(event.name()); + if (allUsersList->deleteUser(userName)) { + ignoreList->setUserOnline(userName, false); + buddyList->setUserOnline(userName, false); + ignoreList->sortItems(); + buddyList->sortItems(); + + emit userLeft(userName); + } } -void TabUserLists::buddyListReceived(const QList &_buddyList) +void TabUserLists::buddyListReceived(const QList &_buddyList) { - for (int i = 0; i < _buddyList.size(); ++i) - buddyList->processUserInfo(_buddyList[i], false); - buddyList->sortItems(); + for (int i = 0; i < _buddyList.size(); ++i) + buddyList->processUserInfo(_buddyList[i], false); + buddyList->sortItems(); } -void TabUserLists::ignoreListReceived(const QList &_ignoreList) +void TabUserLists::ignoreListReceived(const QList &_ignoreList) { - for (int i = 0; i < _ignoreList.size(); ++i) - ignoreList->processUserInfo(_ignoreList[i], false); - ignoreList->sortItems(); + for (int i = 0; i < _ignoreList.size(); ++i) + ignoreList->processUserInfo(_ignoreList[i], false); + ignoreList->sortItems(); } -void TabUserLists::processAddToListEvent(Event_AddToList *event) +void TabUserLists::processAddToListEvent(const Event_AddToList &event) { - ServerInfo_User *info = event->getUserInfo(); - bool online = allUsersList->userInList(info->getName()); - QString list = event->getList(); - UserList *userList = 0; - if (list == "buddy") - userList = buddyList; - else if (list == "ignore") - userList = ignoreList; - if (!userList) - return; - - userList->processUserInfo(info, online); - userList->sortItems(); + const ServerInfo_User &info = event.user_info(); + bool online = allUsersList->getUsers().contains(QString::fromStdString(info.name())); + QString list = QString::fromStdString(event.list_name()); + UserList *userList = 0; + if (list == "buddy") + userList = buddyList; + else if (list == "ignore") + userList = ignoreList; + if (!userList) + return; + + userList->processUserInfo(info, online); + userList->sortItems(); } -void TabUserLists::processRemoveFromListEvent(Event_RemoveFromList *event) +void TabUserLists::processRemoveFromListEvent(const Event_RemoveFromList &event) { - QString list = event->getList(); - QString user = event->getUserName(); - UserList *userList = 0; - if (list == "buddy") - userList = buddyList; - else if (list == "ignore") - userList = ignoreList; - if (!userList) - return; - userList->deleteUser(user); + QString list = QString::fromStdString(event.list_name()); + QString user = QString::fromStdString(event.user_name()); + UserList *userList = 0; + if (list == "buddy") + userList = buddyList; + else if (list == "ignore") + userList = ignoreList; + if (!userList) + return; + userList->deleteUser(user); } diff --git a/cockatrice/src/tab_userlists.h b/cockatrice/src/tab_userlists.h index e345be465..7511c9ea9 100644 --- a/cockatrice/src/tab_userlists.h +++ b/cockatrice/src/tab_userlists.h @@ -2,6 +2,8 @@ #define TAB_USERLISTS_H #include "tab.h" +#include "pb/serverinfo_user.pb.h" +#include class AbstractClient; class UserList; @@ -10,37 +12,43 @@ class UserInfoBox; class Event_ListRooms; class Event_UserJoined; class Event_UserLeft; -class ProtocolResponse; +class Response; class ServerInfo_User; class Event_AddToList; class Event_RemoveFromList; class TabUserLists : public Tab { - Q_OBJECT + Q_OBJECT signals: - void openMessageDialog(const QString &userName, bool focus); - void userLeft(const QString &userName); - void userJoined(const QString &userName); + void openMessageDialog(const QString &userName, bool focus); + void userLeft(const QString &userName); + void userJoined(const ServerInfo_User &userInfo); private slots: - void processListUsersResponse(ProtocolResponse *response); - void processUserJoinedEvent(Event_UserJoined *event); - void processUserLeftEvent(Event_UserLeft *event); - void buddyListReceived(const QList &_buddyList); - void ignoreListReceived(const QList &_ignoreList); - void processAddToListEvent(Event_AddToList *event); - void processRemoveFromListEvent(Event_RemoveFromList *event); + void processListUsersResponse(const Response &response); + void processUserJoinedEvent(const Event_UserJoined &event); + void processUserLeftEvent(const Event_UserLeft &event); + void buddyListReceived(const QList &_buddyList); + void ignoreListReceived(const QList &_ignoreList); + void processAddToListEvent(const Event_AddToList &event); + void processRemoveFromListEvent(const Event_RemoveFromList &event); + void addToIgnoreList(); + void addToBuddyList(); private: - AbstractClient *client; - UserList *allUsersList; - UserList *buddyList; - UserList *ignoreList; - UserInfoBox *userInfoBox; + AbstractClient *client; + UserList *allUsersList; + UserList *buddyList; + UserList *ignoreList; + UserInfoBox *userInfoBox; + QLineEdit *addBuddyEdit; + QLineEdit *addIgnoreEdit; + void addToList(const std::string &listName, const QString &userName); public: - TabUserLists(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerInfo_User *userInfo, QWidget *parent = 0); - void retranslateUi(); - QString getTabText() const { return tr("User lists"); } - UserList *getBuddyList() const { return buddyList; } - UserList *getIgnoreList() const { return ignoreList; } + TabUserLists(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &userInfo, QWidget *parent = 0); + void retranslateUi(); + QString getTabText() const { return tr("User lists"); } + const UserList *getAllUsersList() const { return allUsersList; } + const UserList *getBuddyList() const { return buddyList; } + const UserList *getIgnoreList() const { return ignoreList; } }; #endif diff --git a/cockatrice/src/tablezone.cpp b/cockatrice/src/tablezone.cpp index f64c16352..f716cfbcd 100644 --- a/cockatrice/src/tablezone.cpp +++ b/cockatrice/src/tablezone.cpp @@ -1,299 +1,326 @@ #include #include #include -#include +#include +#ifdef _WIN32 +#include "round.h" +#endif /* _WIN32 */ #include "tablezone.h" #include "player.h" -#include "protocol_items.h" #include "settingscache.h" #include "arrowitem.h" #include "carddragitem.h" #include "carddatabase.h" +#include "carditem.h" + +#include "pb/command_move_card.pb.h" +#include "pb/command_set_card_attr.pb.h" TableZone::TableZone(Player *_p, QGraphicsItem *parent) - : SelectZone(_p, "table", true, false, true, parent), active(false) + : SelectZone(_p, "table", true, false, true, parent), active(false) { - connect(settingsCache, SIGNAL(tableBgPathChanged()), this, SLOT(updateBgPixmap())); - connect(settingsCache, SIGNAL(invertVerticalCoordinateChanged()), this, SLOT(reorganizeCards())); - updateBgPixmap(); + connect(settingsCache, SIGNAL(tableBgPathChanged()), this, SLOT(updateBgPixmap())); + connect(settingsCache, SIGNAL(invertVerticalCoordinateChanged()), this, SLOT(reorganizeCards())); + updateBgPixmap(); - height = 2 * boxLineWidth + 3 * (CARD_HEIGHT + 20) + 2 * paddingY; - width = minWidth + 2 * marginX + 2 * boxLineWidth; - currentMinimumWidth = minWidth; + height = 2 * boxLineWidth + 3 * (CARD_HEIGHT + 20) + 2 * paddingY; + width = minWidth + 2 * marginX + 2 * boxLineWidth; + currentMinimumWidth = minWidth; - setCacheMode(DeviceCoordinateCache); - setAcceptsHoverEvents(true); + setCacheMode(DeviceCoordinateCache); +#if QT_VERSION < 0x050000 + setAcceptsHoverEvents(true); +#else + setAcceptHoverEvents(true); +#endif } void TableZone::updateBgPixmap() { - QString bgPath = settingsCache->getTableBgPath(); - if (!bgPath.isEmpty()) - bgPixmap.load(bgPath); - update(); + QString bgPath = settingsCache->getTableBgPath(); + if (!bgPath.isEmpty()) + bgPixmap.load(bgPath); + update(); } QRectF TableZone::boundingRect() const { - return QRectF(0, 0, width, height); + return QRectF(0, 0, width, height); } bool TableZone::isInverted() const { - return ((player->getMirrored() && !settingsCache->getInvertVerticalCoordinate()) || (!player->getMirrored() && settingsCache->getInvertVerticalCoordinate())); + return ((player->getMirrored() && !settingsCache->getInvertVerticalCoordinate()) || (!player->getMirrored() && settingsCache->getInvertVerticalCoordinate())); } -void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) +void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - if (bgPixmap.isNull()) - painter->fillRect(boundingRect(), QColor(0, 0, 100)); - else - painter->fillRect(boundingRect(), QBrush(bgPixmap)); - painter->setPen(QColor(255, 255, 255, 40)); - qreal separatorY = 2 * (CARD_HEIGHT + 20 + paddingY) + boxLineWidth - paddingY / 2; - if (isInverted()) - separatorY = height - separatorY; - painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); - - if (active) { - QColor color1(255, 255, 255, 150); - QColor color2(255, 255, 255, 0); - QLinearGradient grad1(0, 0, 0, 1); - grad1.setCoordinateMode(QGradient::ObjectBoundingMode); - grad1.setColorAt(0, color1); - grad1.setColorAt(1, color2); - painter->fillRect(QRectF(0, 0, width, boxLineWidth), QBrush(grad1)); - - grad1.setFinalStop(1, 0); - painter->fillRect(QRectF(0, 0, boxLineWidth, height), QBrush(grad1)); - - grad1.setStart(0, 1); - grad1.setFinalStop(0, 0); - painter->fillRect(QRectF(0, height - boxLineWidth, width, boxLineWidth), QBrush(grad1)); - - grad1.setStart(1, 0); - painter->fillRect(QRectF(width - boxLineWidth, 0, boxLineWidth, height), QBrush(grad1)); - } + if (bgPixmap.isNull()) + painter->fillRect(boundingRect(), QColor(0, 0, 100)); + else + painter->fillRect(boundingRect(), QBrush(bgPixmap)); + painter->setPen(QColor(255, 255, 255, 40)); + qreal separatorY = 2 * (CARD_HEIGHT + 20 + paddingY) + boxLineWidth - paddingY / 2; + if (isInverted()) + separatorY = height - separatorY; + painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); + + if (active) { + QColor color1(255, 255, 255, 150); + QColor color2(255, 255, 255, 0); + QLinearGradient grad1(0, 0, 0, 1); + grad1.setCoordinateMode(QGradient::ObjectBoundingMode); + grad1.setColorAt(0, color1); + grad1.setColorAt(1, color2); + painter->fillRect(QRectF(0, 0, width, boxLineWidth), QBrush(grad1)); + + grad1.setFinalStop(1, 0); + painter->fillRect(QRectF(0, 0, boxLineWidth, height), QBrush(grad1)); + + grad1.setStart(0, 1); + grad1.setFinalStop(0, 0); + painter->fillRect(QRectF(0, height - boxLineWidth, width, boxLineWidth), QBrush(grad1)); + + grad1.setStart(1, 0); + painter->fillRect(QRectF(width - boxLineWidth, 0, boxLineWidth, height), QBrush(grad1)); + } } void TableZone::addCardImpl(CardItem *card, int _x, int _y) { - cards.append(card); - card->setGridPoint(QPoint(_x, _y)); + cards.append(card); + card->setGridPoint(QPoint(_x, _y)); - card->setParentItem(this); - card->setVisible(true); - card->update(); + card->setParentItem(this); + card->setVisible(true); + card->update(); } void TableZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) { - handleDropEventByGrid(dragItems, startZone, mapToGrid(dropPoint)); + handleDropEventByGrid(dragItems, startZone, mapToGrid(dropPoint)); } void TableZone::handleDropEventByGrid(const QList &dragItems, CardZone *startZone, const QPoint &gridPoint) { - QList idList; - for (int i = 0; i < dragItems.size(); ++i) - idList.append(new CardToMove(dragItems[i]->getId(), dragItems[i]->getFaceDown(), startZone->getName() == name ? QString() : dragItems[i]->getItem()->getInfo()->getPowTough())); - - startZone->getPlayer()->sendGameCommand(new Command_MoveCard(-1, startZone->getName(), idList, player->getId(), getName(), gridPoint.x(), gridPoint.y())); + Command_MoveCard cmd; + cmd.set_start_player_id(startZone->getPlayer()->getId()); + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_target_player_id(player->getId()); + cmd.set_target_zone(getName().toStdString()); + cmd.set_x(gridPoint.x()); + cmd.set_y(gridPoint.y()); + + for (int i = 0; i < dragItems.size(); ++i) { + CardToMove *ctm = cmd.mutable_cards_to_move()->add_card(); + ctm->set_card_id(dragItems[i]->getId()); + ctm->set_face_down(dragItems[i]->getFaceDown()); + ctm->set_pt(startZone->getName() == name ? std::string() : dragItems[i]->getItem()->getInfo()->getPowTough().toStdString()); + } + + startZone->getPlayer()->sendGameCommand(cmd); } void TableZone::reorganizeCards() { - QList arrowsToUpdate; - - // Calculate table grid distortion so that the mapping functions work properly - QMap gridPointStackCount; - for (int i = 0; i < cards.size(); ++i) { - const QPoint &gridPoint = cards[i]->getGridPos(); - if (gridPoint.x() == -1) - continue; - - const int key = gridPoint.x() / 3 + gridPoint.y() * 1000; - gridPointStackCount.insert(key, gridPointStackCount.value(key, 0) + 1); - } - gridPointWidth.clear(); - for (int i = 0; i < cards.size(); ++i) { - const QPoint &gridPoint = cards[i]->getGridPos(); - if (gridPoint.x() == -1) - continue; - - const int key = gridPoint.x() / 3 + gridPoint.y() * 1000; - const int stackCount = gridPointStackCount.value(key, 0); - if (stackCount == 1) - gridPointWidth.insert(key, CARD_WIDTH * (1 + cards[i]->getAttachedCards().size() / 3.0)); - else - gridPointWidth.insert(key, CARD_WIDTH * (1 + (stackCount - 1) / 3.0)); - } - - for (int i = 0; i < cards.size(); ++i) { - QPoint gridPoint = cards[i]->getGridPos(); - if (gridPoint.x() == -1) - continue; - - QPointF mapPoint = mapFromGrid(gridPoint); - qreal x = mapPoint.x(); - qreal y = mapPoint.y(); - - int numberAttachedCards = cards[i]->getAttachedCards().size(); - qreal actualX = x + numberAttachedCards * CARD_WIDTH / 3.0; - qreal actualY = y; - if (numberAttachedCards) - actualY += 15; - - cards[i]->setPos(actualX, actualY); - cards[i]->setRealZValue((actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100); - - QListIterator attachedCardIterator(cards[i]->getAttachedCards()); - int j = 0; - while (attachedCardIterator.hasNext()) { - ++j; - CardItem *attachedCard = attachedCardIterator.next(); - qreal childX = actualX - j * CARD_WIDTH / 3.0; - qreal childY = y + 5; - attachedCard->setPos(childX, childY); - attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100); + QList arrowsToUpdate; + + // Calculate table grid distortion so that the mapping functions work properly + QMap gridPointStackCount; + for (int i = 0; i < cards.size(); ++i) { + const QPoint &gridPoint = cards[i]->getGridPos(); + if (gridPoint.x() == -1) + continue; + + const int key = gridPoint.x() / 3 + gridPoint.y() * 1000; + gridPointStackCount.insert(key, gridPointStackCount.value(key, 0) + 1); + } + gridPointWidth.clear(); + for (int i = 0; i < cards.size(); ++i) { + const QPoint &gridPoint = cards[i]->getGridPos(); + if (gridPoint.x() == -1) + continue; + + const int key = gridPoint.x() / 3 + gridPoint.y() * 1000; + const int stackCount = gridPointStackCount.value(key, 0); + if (stackCount == 1) + gridPointWidth.insert(key, CARD_WIDTH * (1 + cards[i]->getAttachedCards().size() / 3.0)); + else + gridPointWidth.insert(key, CARD_WIDTH * (1 + (stackCount - 1) / 3.0)); + } + + for (int i = 0; i < cards.size(); ++i) { + QPoint gridPoint = cards[i]->getGridPos(); + if (gridPoint.x() == -1) + continue; + + QPointF mapPoint = mapFromGrid(gridPoint); + qreal x = mapPoint.x(); + qreal y = mapPoint.y(); + + int numberAttachedCards = cards[i]->getAttachedCards().size(); + qreal actualX = x + numberAttachedCards * CARD_WIDTH / 3.0; + qreal actualY = y; + if (numberAttachedCards) + actualY += 15; + + cards[i]->setPos(actualX, actualY); + cards[i]->setRealZValue((actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100); + + QListIterator attachedCardIterator(cards[i]->getAttachedCards()); + int j = 0; + while (attachedCardIterator.hasNext()) { + ++j; + CardItem *attachedCard = attachedCardIterator.next(); + qreal childX = actualX - j * CARD_WIDTH / 3.0; + qreal childY = y + 5; + attachedCard->setPos(childX, childY); + attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100); - arrowsToUpdate.append(attachedCard->getArrowsFrom()); - arrowsToUpdate.append(attachedCard->getArrowsTo()); - } - - arrowsToUpdate.append(cards[i]->getArrowsFrom()); - arrowsToUpdate.append(cards[i]->getArrowsTo()); - } + arrowsToUpdate.append(attachedCard->getArrowsFrom()); + arrowsToUpdate.append(attachedCard->getArrowsTo()); + } + + arrowsToUpdate.append(cards[i]->getArrowsFrom()); + arrowsToUpdate.append(cards[i]->getArrowsTo()); + } - QSetIterator arrowIterator(QSet::fromList(arrowsToUpdate)); - while (arrowIterator.hasNext()) - arrowIterator.next()->updatePath(); - - resizeToContents(); - update(); + QSetIterator arrowIterator(QSet::fromList(arrowsToUpdate)); + while (arrowIterator.hasNext()) + arrowIterator.next()->updatePath(); + + resizeToContents(); + update(); } void TableZone::toggleTapped() { - QList selectedItems = scene()->selectedItems(); - bool tapAll = false; - for (int i = 0; i < selectedItems.size(); i++) - if (!qgraphicsitem_cast(selectedItems[i])->getTapped()) { - tapAll = true; - break; - } - QList cmdList; - for (int i = 0; i < selectedItems.size(); i++) { - CardItem *temp = qgraphicsitem_cast(selectedItems[i]); - if (temp->getTapped() != tapAll) - cmdList.append(new Command_SetCardAttr(-1, name, temp->getId(), "tapped", tapAll ? "1" : "0")); - } - player->sendCommandContainer(new CommandContainer(cmdList)); + QList selectedItems = scene()->selectedItems(); + bool tapAll = false; + for (int i = 0; i < selectedItems.size(); i++) + if (!qgraphicsitem_cast(selectedItems[i])->getTapped()) { + tapAll = true; + break; + } + QList< const ::google::protobuf::Message * > cmdList; + for (int i = 0; i < selectedItems.size(); i++) { + CardItem *temp = qgraphicsitem_cast(selectedItems[i]); + if (temp->getTapped() != tapAll) { + Command_SetCardAttr *cmd = new Command_SetCardAttr; + cmd->set_zone(name.toStdString()); + cmd->set_card_id(temp->getId()); + cmd->set_attribute(AttrTapped); + cmd->set_attr_value(tapAll ? "1" : "0"); + cmdList.append(cmd); + } + } + player->sendGameCommand(player->prepareGameCommand(cmdList)); } CardItem *TableZone::takeCard(int position, int cardId, bool canResize) { - CardItem *result = CardZone::takeCard(position, cardId); - if (canResize) - resizeToContents(); - return result; + CardItem *result = CardZone::takeCard(position, cardId); + if (canResize) + resizeToContents(); + return result; } void TableZone::resizeToContents() { - int xMax = 0; - for (int i = 0; i < cards.size(); ++i) - if (cards[i]->pos().x() > xMax) - xMax = (int) cards[i]->pos().x(); - xMax += 2 * CARD_WIDTH; - if (xMax < minWidth) - xMax = minWidth; - currentMinimumWidth = xMax + 2 * marginX + 2 * boxLineWidth; - if (currentMinimumWidth != width) { - prepareGeometryChange(); - width = currentMinimumWidth; - emit sizeChanged(); - } + int xMax = 0; + for (int i = 0; i < cards.size(); ++i) + if (cards[i]->pos().x() > xMax) + xMax = (int) cards[i]->pos().x(); + xMax += 2 * CARD_WIDTH; + if (xMax < minWidth) + xMax = minWidth; + currentMinimumWidth = xMax + 2 * marginX + 2 * boxLineWidth; + if (currentMinimumWidth != width) { + prepareGeometryChange(); + width = currentMinimumWidth; + emit sizeChanged(); + } } CardItem *TableZone::getCardFromGrid(const QPoint &gridPoint) const { - for (int i = 0; i < cards.size(); i++) - if (cards.at(i)->getGridPoint() == gridPoint) - return cards.at(i); - return 0; + for (int i = 0; i < cards.size(); i++) + if (cards.at(i)->getGridPoint() == gridPoint) + return cards.at(i); + return 0; } CardItem *TableZone::getCardFromCoords(const QPointF &point) const { - QPoint gridPoint = mapToGrid(point); - return getCardFromGrid(gridPoint); + QPoint gridPoint = mapToGrid(point); + return getCardFromGrid(gridPoint); } QPointF TableZone::mapFromGrid(QPoint gridPoint) const { - qreal x, y; - x = marginX + (gridPoint.x() % 3) * CARD_WIDTH / 3.0; - for (int i = 0; i < gridPoint.x() / 3; ++i) - x += gridPointWidth.value(gridPoint.y() * 1000 + i, CARD_WIDTH) + paddingX; - - if (isInverted()) - gridPoint.setY(2 - gridPoint.y()); - - y = boxLineWidth + gridPoint.y() * (CARD_HEIGHT + paddingY + 20) + (gridPoint.x() % 3) * 10; -/* - if (isInverted()) - y = height - CARD_HEIGHT - y; -*/ - return QPointF(x, y); + qreal x, y; + x = marginX + (gridPoint.x() % 3) * CARD_WIDTH / 3.0; + for (int i = 0; i < gridPoint.x() / 3; ++i) + x += gridPointWidth.value(gridPoint.y() * 1000 + i, CARD_WIDTH) + paddingX; + + if (isInverted()) + gridPoint.setY(2 - gridPoint.y()); + + y = boxLineWidth + gridPoint.y() * (CARD_HEIGHT + paddingY + 20) + (gridPoint.x() % 3) * 10; +/* + if (isInverted()) + y = height - CARD_HEIGHT - y; +*/ + return QPointF(x, y); } QPoint TableZone::mapToGrid(const QPointF &mapPoint) const { - qreal x = mapPoint.x() - marginX; - qreal y = mapPoint.y(); -/* if (isInverted()) - y = height - y; -*/ y -= boxLineWidth; - - if (x < 0) - x = 0; - else if (x > width - CARD_WIDTH - marginX) - x = width - CARD_WIDTH - marginX; - if (y < 0) - y = 0; - else if (y > height - CARD_HEIGHT) - y = height - CARD_HEIGHT; - - int resultY = round(y / (CARD_HEIGHT + paddingY + 20)); - if (isInverted()) - resultY = 2 - resultY; + qreal x = mapPoint.x() - marginX; + qreal y = mapPoint.y(); +/* if (isInverted()) + y = height - y; +*/ y -= boxLineWidth; + + if (x < 0) + x = 0; + else if (x > width - CARD_WIDTH - marginX) + x = width - CARD_WIDTH - marginX; + if (y < 0) + y = 0; + else if (y > height - CARD_HEIGHT) + y = height - CARD_HEIGHT; + + int resultY = round(y / (CARD_HEIGHT + paddingY + 20)); + if (isInverted()) + resultY = 2 - resultY; - int baseX = -1; - qreal oldTempX = 0, tempX = 0; - do { - ++baseX; - oldTempX = tempX; - tempX += gridPointWidth.value(resultY * 1000 + baseX, CARD_WIDTH) + paddingX; - } while (tempX < x + 1); - - qreal xdiff = x - oldTempX; - int resultX = baseX * 3 + qMin((int) floor(xdiff * 3 / CARD_WIDTH), 2); - return QPoint(resultX, resultY); + int baseX = -1; + qreal oldTempX = 0, tempX = 0; + do { + ++baseX; + oldTempX = tempX; + tempX += gridPointWidth.value(resultY * 1000 + baseX, CARD_WIDTH) + paddingX; + } while (tempX < x + 1); + + qreal xdiff = x - oldTempX; + int resultX = baseX * 3 + qMin((int) floor(xdiff * 3 / CARD_WIDTH), 2); + return QPoint(resultX, resultY); } QPointF TableZone::closestGridPoint(const QPointF &point) { - QPoint gridPoint = mapToGrid(point + QPoint(1, 1)); - gridPoint.setX((gridPoint.x() / 3) * 3); - if (getCardFromGrid(gridPoint)) - gridPoint.setX(gridPoint.x() + 1); - if (getCardFromGrid(gridPoint)) - gridPoint.setX(gridPoint.x() + 1); - return mapFromGrid(gridPoint); + QPoint gridPoint = mapToGrid(point + QPoint(1, 1)); + gridPoint.setX((gridPoint.x() / 3) * 3); + if (getCardFromGrid(gridPoint)) + gridPoint.setX(gridPoint.x() + 1); + if (getCardFromGrid(gridPoint)) + gridPoint.setX(gridPoint.x() + 1); + return mapFromGrid(gridPoint); } void TableZone::setWidth(qreal _width) { - prepareGeometryChange(); - width = _width; + prepareGeometryChange(); + width = _width; } diff --git a/cockatrice/src/tablezone.h b/cockatrice/src/tablezone.h index 262f2e43c..297f097fa 100644 --- a/cockatrice/src/tablezone.h +++ b/cockatrice/src/tablezone.h @@ -2,48 +2,49 @@ #define TABLEZONE_H #include "selectzone.h" +#include "abstractcarditem.h" class TableZone : public SelectZone { - Q_OBJECT + Q_OBJECT signals: - void sizeChanged(); + void sizeChanged(); private: - static const int boxLineWidth = 10; - static const int paddingX = 35; - static const int paddingY = 10; - static const int marginX = 20; - static const int minWidth = 15 * CARD_WIDTH / 2; + static const int boxLineWidth = 10; + static const int paddingX = 35; + static const int paddingY = 10; + static const int marginX = 20; + static const int minWidth = 15 * CARD_WIDTH / 2; - QMap gridPointWidth; - int width, height; - int currentMinimumWidth; - QPixmap bgPixmap; - bool active; - bool isInverted() const; -private slots: - void updateBgPixmap(); + QMap gridPointWidth; + int width, height; + int currentMinimumWidth; + QPixmap bgPixmap; + bool active; + bool isInverted() const; +private slots: + void updateBgPixmap(); public slots: - void reorganizeCards(); + void reorganizeCards(); public: - TableZone(Player *_p, QGraphicsItem *parent = 0); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void toggleTapped(); - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); - void handleDropEventByGrid(const QList &dragItems, CardZone *startZone, const QPoint &gridPoint); - CardItem *getCardFromGrid(const QPoint &gridPoint) const; - CardItem *getCardFromCoords(const QPointF &point) const; - QPointF mapFromGrid(QPoint gridPoint) const; - QPoint mapToGrid(const QPointF &mapPoint) const; - QPointF closestGridPoint(const QPointF &point); - CardItem *takeCard(int position, int cardId, bool canResize = true); - void resizeToContents(); - int getMinimumWidth() const { return currentMinimumWidth; } - void setWidth(qreal _width); - qreal getWidth() const { return width; } - void setActive(bool _active) { active = _active; update(); } + TableZone(Player *_p, QGraphicsItem *parent = 0); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void toggleTapped(); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); + void handleDropEventByGrid(const QList &dragItems, CardZone *startZone, const QPoint &gridPoint); + CardItem *getCardFromGrid(const QPoint &gridPoint) const; + CardItem *getCardFromCoords(const QPointF &point) const; + QPointF mapFromGrid(QPoint gridPoint) const; + QPoint mapToGrid(const QPointF &mapPoint) const; + QPointF closestGridPoint(const QPointF &point); + CardItem *takeCard(int position, int cardId, bool canResize = true); + void resizeToContents(); + int getMinimumWidth() const { return currentMinimumWidth; } + void setWidth(qreal _width); + qreal getWidth() const { return width; } + void setActive(bool _active) { active = _active; update(); } protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y); }; #endif diff --git a/cockatrice/src/translation.h b/cockatrice/src/translation.h index 92cb474bd..f690c33f2 100644 --- a/cockatrice/src/translation.h +++ b/cockatrice/src/translation.h @@ -1,6 +1,6 @@ #ifndef TRANSLATION_H #define TRANSLATION_H -enum GrammaticalCase { CaseNominative, CaseGenitive, CaseAccusative }; +enum GrammaticalCase { CaseNominative, CaseLookAtZone, CaseTopCardsOfZone, CaseRevealZone, CaseShuffleZone }; #endif diff --git a/cockatrice/src/user_context_menu.cpp b/cockatrice/src/user_context_menu.cpp new file mode 100644 index 000000000..df3246535 --- /dev/null +++ b/cockatrice/src/user_context_menu.cpp @@ -0,0 +1,199 @@ +#include +#include +#include "user_context_menu.h" +#include "tab_supervisor.h" +#include "tab_userlists.h" +#include "tab_game.h" +#include "userlist.h" +#include "abstractclient.h" +#include "userinfobox.h" +#include "gameselector.h" +#include "pending_command.h" + +#include "pb/commands.pb.h" +#include "pb/session_commands.pb.h" +#include "pb/moderator_commands.pb.h" +#include "pb/command_kick_from_game.pb.h" +#include "pb/response_get_games_of_user.pb.h" +#include "pb/response_get_user_info.pb.h" + +UserContextMenu::UserContextMenu(const TabSupervisor *_tabSupervisor, QWidget *parent, TabGame *_game) + : QObject(parent), client(_tabSupervisor->getClient()), tabSupervisor(_tabSupervisor), game(_game) +{ + aUserName = new QAction(QString(), this); + aUserName->setEnabled(false); + aDetails = new QAction(QString(), this); + aChat = new QAction(QString(), this); + aShowGames = new QAction(QString(), this); + aAddToBuddyList = new QAction(QString(), this); + aRemoveFromBuddyList = new QAction(QString(), this); + aAddToIgnoreList = new QAction(QString(), this); + aRemoveFromIgnoreList = new QAction(QString(), this); + aKick = new QAction(QString(), this); + aBan = new QAction(QString(), this); + + retranslateUi(); +} + +void UserContextMenu::retranslateUi() +{ + aDetails->setText(tr("User &details")); + aChat->setText(tr("Direct &chat")); + aShowGames->setText(tr("Show this user's &games")); + aAddToBuddyList->setText(tr("Add to &buddy list")); + aRemoveFromBuddyList->setText(tr("Remove from &buddy list")); + aAddToIgnoreList->setText(tr("Add to &ignore list")); + aRemoveFromIgnoreList->setText(tr("Remove from &ignore list")); + aKick->setText(tr("Kick from &game")); + aBan->setText(tr("Ban from &server")); +} + +void UserContextMenu::gamesOfUserReceived(const Response &resp, const CommandContainer &commandContainer) +{ + const Response_GetGamesOfUser &response = resp.GetExtension(Response_GetGamesOfUser::ext); + const Command_GetGamesOfUser &cmd = commandContainer.session_command(0).GetExtension(Command_GetGamesOfUser::ext); + + QMap gameTypeMap; + QMap roomMap; + const int roomListSize = response.room_list_size(); + for (int i = 0; i < roomListSize; ++i) { + const ServerInfo_Room &roomInfo = response.room_list(i); + roomMap.insert(roomInfo.room_id(), QString::fromStdString(roomInfo.name())); + GameTypeMap tempMap; + const int gameTypeListSize = roomInfo.gametype_list_size(); + for (int j = 0; j < gameTypeListSize; ++j) { + const ServerInfo_GameType &gameTypeInfo = roomInfo.gametype_list(j); + tempMap.insert(gameTypeInfo.game_type_id(), QString::fromStdString(gameTypeInfo.description())); + } + gameTypeMap.insert(roomInfo.room_id(), tempMap); + } + + GameSelector *selector = new GameSelector(client, tabSupervisor, 0, roomMap, gameTypeMap); + const int gameListSize = response.game_list_size(); + for (int i = 0; i < gameListSize; ++i) + selector->processGameInfo(response.game_list(i)); + + selector->setWindowTitle(tr("%1's games").arg(QString::fromStdString(cmd.user_name()))); + selector->setAttribute(Qt::WA_DeleteOnClose); + selector->show(); +} + +void UserContextMenu::banUser_processUserInfoResponse(const Response &r) +{ + const Response_GetUserInfo &response = r.GetExtension(Response_GetUserInfo::ext); + + // The dialog needs to be non-modal in order to not block the event queue of the client. + BanDialog *dlg = new BanDialog(response.user_info(), static_cast(parent())); + connect(dlg, SIGNAL(accepted()), this, SLOT(banUser_dialogFinished())); + dlg->show(); +} + +void UserContextMenu::banUser_dialogFinished() +{ + BanDialog *dlg = static_cast(sender()); + + Command_BanFromServer cmd; + cmd.set_user_name(dlg->getBanName().toStdString()); + cmd.set_address(dlg->getBanIP().toStdString()); + cmd.set_minutes(dlg->getMinutes()); + cmd.set_reason(dlg->getReason().toStdString()); + cmd.set_visible_reason(dlg->getVisibleReason().toStdString()); + + client->sendCommand(client->prepareModeratorCommand(cmd)); +} + +void UserContextMenu::showContextMenu(const QPoint &pos, const QString &userName, UserLevelFlags userLevel, bool online, int playerId) +{ + aUserName->setText(userName); + + QMenu *menu = new QMenu(static_cast(parent())); + menu->addAction(aUserName); + menu->addSeparator(); + menu->addAction(aDetails); + menu->addAction(aShowGames); + menu->addAction(aChat); + if (userLevel.testFlag(ServerInfo_User::IsRegistered) && (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsRegistered)) { + menu->addSeparator(); + if (tabSupervisor->getUserListsTab()->getBuddyList()->getUsers().contains(userName)) + menu->addAction(aRemoveFromBuddyList); + else + menu->addAction(aAddToBuddyList); + if (tabSupervisor->getUserListsTab()->getIgnoreList()->getUsers().contains(userName)) + menu->addAction(aRemoveFromIgnoreList); + else + menu->addAction(aAddToIgnoreList); + } + if (game && (game->isHost() || !tabSupervisor->getAdminLocked())) { + menu->addSeparator(); + menu->addAction(aKick); + } + if (!tabSupervisor->getAdminLocked()) { + menu->addSeparator(); + menu->addAction(aBan); + } + bool anotherUser = userName != QString::fromStdString(tabSupervisor->getUserInfo()->name()); + aDetails->setEnabled(online); + aChat->setEnabled(anotherUser && online); + aShowGames->setEnabled(anotherUser); + aAddToBuddyList->setEnabled(anotherUser); + aRemoveFromBuddyList->setEnabled(anotherUser); + aAddToIgnoreList->setEnabled(anotherUser); + aRemoveFromIgnoreList->setEnabled(anotherUser); + aKick->setEnabled(anotherUser); + aBan->setEnabled(anotherUser); + + QAction *actionClicked = menu->exec(pos); + if (actionClicked == aDetails) { + UserInfoBox *infoWidget = new UserInfoBox(client, true, static_cast(parent()), Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint); + infoWidget->setAttribute(Qt::WA_DeleteOnClose); + infoWidget->updateInfo(userName); + } else if (actionClicked == aChat) + emit openMessageDialog(userName, true); + else if (actionClicked == aShowGames) { + Command_GetGamesOfUser cmd; + cmd.set_user_name(userName.toStdString()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(gamesOfUserReceived(Response, CommandContainer))); + + client->sendCommand(pend); + } else if (actionClicked == aAddToBuddyList) { + Command_AddToList cmd; + cmd.set_list("buddy"); + cmd.set_user_name(userName.toStdString()); + + client->sendCommand(client->prepareSessionCommand(cmd)); + } else if (actionClicked == aRemoveFromBuddyList) { + Command_RemoveFromList cmd; + cmd.set_list("buddy"); + cmd.set_user_name(userName.toStdString()); + + client->sendCommand(client->prepareSessionCommand(cmd)); + } else if (actionClicked == aAddToIgnoreList) { + Command_AddToList cmd; + cmd.set_list("ignore"); + cmd.set_user_name(userName.toStdString()); + + client->sendCommand(client->prepareSessionCommand(cmd)); + } else if (actionClicked == aRemoveFromIgnoreList) { + Command_RemoveFromList cmd; + cmd.set_list("ignore"); + cmd.set_user_name(userName.toStdString()); + + client->sendCommand(client->prepareSessionCommand(cmd)); + } else if (actionClicked == aKick) { + Command_KickFromGame cmd; + cmd.set_player_id(playerId); + game->sendGameCommand(cmd); + } else if (actionClicked == aBan) { + Command_GetUserInfo cmd; + cmd.set_user_name(userName.toStdString()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(banUser_processUserInfoResponse(Response))); + + client->sendCommand(pend); + } + + delete menu; +} diff --git a/cockatrice/src/user_context_menu.h b/cockatrice/src/user_context_menu.h new file mode 100644 index 000000000..9aeadfd4a --- /dev/null +++ b/cockatrice/src/user_context_menu.h @@ -0,0 +1,42 @@ +#ifndef USER_CONTEXT_MENU_H +#define USER_CONTEXT_MENU_H + +#include +#include "user_level.h" + +class QAction; +class TabSupervisor; +class TabGame; +class QPoint; +class CommandContainer; +class Response; +class AbstractClient; + +class UserContextMenu : public QObject { + Q_OBJECT +private: + AbstractClient *client; + const TabSupervisor *tabSupervisor; + TabGame *game; + + QAction *aUserName; + QAction *aDetails; + QAction *aShowGames; + QAction *aChat; + QAction *aAddToBuddyList, *aRemoveFromBuddyList; + QAction *aAddToIgnoreList, *aRemoveFromIgnoreList; + QAction *aKick; + QAction *aBan; +signals: + void openMessageDialog(const QString &userName, bool focus); +private slots: + void banUser_processUserInfoResponse(const Response &resp); + void banUser_dialogFinished(); + void gamesOfUserReceived(const Response &resp, const CommandContainer &commandContainer); +public: + UserContextMenu(const TabSupervisor *_tabSupervisor, QWidget *_parent, TabGame *_game = 0); + void retranslateUi(); + void showContextMenu(const QPoint &pos, const QString &userName, UserLevelFlags userLevel, bool online = true, int playerId = -1); +}; + +#endif diff --git a/cockatrice/src/userinfobox.cpp b/cockatrice/src/userinfobox.cpp index 53b319c09..855cd748c 100644 --- a/cockatrice/src/userinfobox.cpp +++ b/cockatrice/src/userinfobox.cpp @@ -1,97 +1,101 @@ #include "userinfobox.h" -#include "protocol_datastructures.h" #include "pixmapgenerator.h" -#include "protocol_items.h" #include "abstractclient.h" #include #include +#include "pending_command.h" +#include "pb/session_commands.pb.h" +#include "pb/response_get_user_info.pb.h" + UserInfoBox::UserInfoBox(AbstractClient *_client, bool _fullInfo, QWidget *parent, Qt::WindowFlags flags) - : QWidget(parent, flags), client(_client), fullInfo(_fullInfo) + : QWidget(parent, flags), client(_client), fullInfo(_fullInfo) { - avatarLabel = new QLabel; - nameLabel = new QLabel; - QFont nameFont = nameLabel->font(); - nameFont.setBold(true); - nameFont.setPointSize(nameFont.pointSize() * 1.5); - nameLabel->setFont(nameFont); - realNameLabel1 = new QLabel; - realNameLabel2 = new QLabel; - genderLabel1 = new QLabel; - genderLabel2 = new QLabel; - countryLabel1 = new QLabel; - countryLabel2 = new QLabel; - userLevelLabel1 = new QLabel; - userLevelLabel2 = new QLabel; - userLevelLabel3 = new QLabel; - - QGridLayout *mainLayout = new QGridLayout; - mainLayout->addWidget(avatarLabel, 0, 0, 1, 3, Qt::AlignCenter); - mainLayout->addWidget(nameLabel, 1, 0, 1, 3); - mainLayout->addWidget(realNameLabel1, 2, 0, 1, 1); - mainLayout->addWidget(realNameLabel2, 2, 1, 1, 2); - mainLayout->addWidget(genderLabel1, 3, 0, 1, 1); - mainLayout->addWidget(genderLabel2, 3, 1, 1, 2); - mainLayout->addWidget(countryLabel1, 4, 0, 1, 1); - mainLayout->addWidget(countryLabel2, 4, 1, 1, 2); - mainLayout->addWidget(userLevelLabel1, 5, 0, 1, 1); - mainLayout->addWidget(userLevelLabel2, 5, 1, 1, 1); - mainLayout->addWidget(userLevelLabel3, 5, 2, 1, 1); - mainLayout->setColumnStretch(2, 10); - - setWindowTitle(tr("User information")); - setLayout(mainLayout); - retranslateUi(); + avatarLabel = new QLabel; + nameLabel = new QLabel; + QFont nameFont = nameLabel->font(); + nameFont.setBold(true); + nameFont.setPointSize(nameFont.pointSize() * 1.5); + nameLabel->setFont(nameFont); + realNameLabel1 = new QLabel; + realNameLabel2 = new QLabel; + genderLabel1 = new QLabel; + genderLabel2 = new QLabel; + countryLabel1 = new QLabel; + countryLabel2 = new QLabel; + userLevelLabel1 = new QLabel; + userLevelLabel2 = new QLabel; + userLevelLabel3 = new QLabel; + + QGridLayout *mainLayout = new QGridLayout; + mainLayout->addWidget(avatarLabel, 0, 0, 1, 3, Qt::AlignCenter); + mainLayout->addWidget(nameLabel, 1, 0, 1, 3); + mainLayout->addWidget(realNameLabel1, 2, 0, 1, 1); + mainLayout->addWidget(realNameLabel2, 2, 1, 1, 2); + mainLayout->addWidget(genderLabel1, 3, 0, 1, 1); + mainLayout->addWidget(genderLabel2, 3, 1, 1, 2); + mainLayout->addWidget(countryLabel1, 4, 0, 1, 1); + mainLayout->addWidget(countryLabel2, 4, 1, 1, 2); + mainLayout->addWidget(userLevelLabel1, 5, 0, 1, 1); + mainLayout->addWidget(userLevelLabel2, 5, 1, 1, 1); + mainLayout->addWidget(userLevelLabel3, 5, 2, 1, 1); + mainLayout->setColumnStretch(2, 10); + + setWindowTitle(tr("User information")); + setLayout(mainLayout); + retranslateUi(); } void UserInfoBox::retranslateUi() { - realNameLabel1->setText(tr("Real name:")); - genderLabel1->setText(tr("Gender:")); - countryLabel1->setText(tr("Location:")); - userLevelLabel1->setText(tr("User level:")); + realNameLabel1->setText(tr("Real name:")); + genderLabel1->setText(tr("Gender:")); + countryLabel1->setText(tr("Location:")); + userLevelLabel1->setText(tr("User level:")); } -void UserInfoBox::updateInfo(ServerInfo_User *user) +void UserInfoBox::updateInfo(const ServerInfo_User &user) { - int userLevel = user->getUserLevel(); - - QPixmap avatarPixmap; - if (!avatarPixmap.loadFromData(user->getAvatarBmp())) - avatarPixmap = UserLevelPixmapGenerator::generatePixmap(64, userLevel); - avatarLabel->setPixmap(avatarPixmap); - - nameLabel->setText(user->getName()); - realNameLabel2->setText(user->getRealName()); - genderLabel2->setPixmap(GenderPixmapGenerator::generatePixmap(15, user->getGender())); - countryLabel2->setPixmap(CountryPixmapGenerator::generatePixmap(15, user->getCountry())); - userLevelLabel2->setPixmap(UserLevelPixmapGenerator::generatePixmap(15, userLevel)); - QString userLevelText; - if (userLevel & ServerInfo_User::IsAdmin) - userLevelText = tr("Administrator"); - else if (userLevel & ServerInfo_User::IsModerator) - userLevelText = tr("Moderator"); - else if (userLevel & ServerInfo_User::IsRegistered) - userLevelText = tr("Registered user"); - else - userLevelText = tr("Unregistered user"); - userLevelLabel3->setText(userLevelText); + const UserLevelFlags userLevel(user.user_level()); + + QPixmap avatarPixmap; + const std::string bmp = user.avatar_bmp(); + if (!avatarPixmap.loadFromData((const uchar *) bmp.data(), bmp.size())) + avatarPixmap = UserLevelPixmapGenerator::generatePixmap(64, userLevel); + avatarLabel->setPixmap(avatarPixmap); + + nameLabel->setText(QString::fromStdString(user.name())); + realNameLabel2->setText(QString::fromStdString(user.real_name())); + genderLabel2->setPixmap(GenderPixmapGenerator::generatePixmap(15, user.gender())); + countryLabel2->setPixmap(CountryPixmapGenerator::generatePixmap(15, QString::fromStdString(user.country()))); + userLevelLabel2->setPixmap(UserLevelPixmapGenerator::generatePixmap(15, userLevel)); + QString userLevelText; + if (userLevel.testFlag(ServerInfo_User::IsAdmin)) + userLevelText = tr("Administrator"); + else if (userLevel.testFlag(ServerInfo_User::IsModerator)) + userLevelText = tr("Moderator"); + else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) + userLevelText = tr("Registered user"); + else + userLevelText = tr("Unregistered user"); + userLevelLabel3->setText(userLevelText); } void UserInfoBox::updateInfo(const QString &userName) { - Command_GetUserInfo *command = new Command_GetUserInfo(userName); - connect(command, SIGNAL(finished(ProtocolResponse *)), this, SLOT(processResponse(ProtocolResponse *))); - client->sendCommand(command); + Command_GetUserInfo cmd; + cmd.set_user_name(userName.toStdString()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(processResponse(const Response &))); + + client->sendCommand(pend); } -void UserInfoBox::processResponse(ProtocolResponse *r) +void UserInfoBox::processResponse(const Response &r) { - Response_GetUserInfo *response = qobject_cast(r); - if (!response) - return; - - updateInfo(response->getUserInfo()); - setFixedSize(sizeHint()); - show(); -} \ No newline at end of file + const Response_GetUserInfo &response = r.GetExtension(Response_GetUserInfo::ext); + updateInfo(response.user_info()); + setFixedSize(sizeHint()); + show(); +} diff --git a/cockatrice/src/userinfobox.h b/cockatrice/src/userinfobox.h index ee4fd6043..5c47c4a28 100644 --- a/cockatrice/src/userinfobox.h +++ b/cockatrice/src/userinfobox.h @@ -6,22 +6,22 @@ class QLabel; class ServerInfo_User; class AbstractClient; -class ProtocolResponse; +class Response; class UserInfoBox : public QWidget { - Q_OBJECT + Q_OBJECT private: - AbstractClient *client; - bool fullInfo; - QLabel *avatarLabel, *nameLabel, *realNameLabel1, *realNameLabel2, *genderLabel1, *genderLabel2, *countryLabel1, *countryLabel2, *userLevelLabel1, *userLevelLabel2, *userLevelLabel3; + AbstractClient *client; + bool fullInfo; + QLabel *avatarLabel, *nameLabel, *realNameLabel1, *realNameLabel2, *genderLabel1, *genderLabel2, *countryLabel1, *countryLabel2, *userLevelLabel1, *userLevelLabel2, *userLevelLabel3; public: - UserInfoBox(AbstractClient *_client, bool fullInfo, QWidget *parent = 0, Qt::WindowFlags flags = 0); - void retranslateUi(); + UserInfoBox(AbstractClient *_client, bool fullInfo, QWidget *parent = 0, Qt::WindowFlags flags = 0); + void retranslateUi(); private slots: - void processResponse(ProtocolResponse *r); + void processResponse(const Response &r); public slots: - void updateInfo(ServerInfo_User *user); - void updateInfo(const QString &userName); + void updateInfo(const ServerInfo_User &user); + void updateInfo(const QString &userName); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/userlist.cpp b/cockatrice/src/userlist.cpp index 38275ee3f..c6174a6e5 100644 --- a/cockatrice/src/userlist.cpp +++ b/cockatrice/src/userlist.cpp @@ -4,7 +4,7 @@ #include "abstractclient.h" #include "pixmapgenerator.h" #include "userinfobox.h" -#include "protocol_items.h" +#include "user_context_menu.h" #include "gameselector.h" #include #include @@ -20,414 +20,303 @@ #include #include -BanDialog::BanDialog(ServerInfo_User *info, QWidget *parent) - : QDialog(parent) +#include "pending_command.h" +#include "pb/session_commands.pb.h" +#include "pb/moderator_commands.pb.h" +#include "pb/response_get_games_of_user.pb.h" +#include "pb/response_get_user_info.pb.h" + +BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) + : QDialog(parent) { - setAttribute(Qt::WA_DeleteOnClose); - - nameBanCheckBox = new QCheckBox(tr("ban &user name")); - nameBanCheckBox->setChecked(true); - nameBanEdit = new QLineEdit(info->getName()); - ipBanCheckBox = new QCheckBox(tr("ban &IP address")); - ipBanCheckBox->setChecked(true); - ipBanEdit = new QLineEdit(info->getAddress()); - QGridLayout *banTypeGrid = new QGridLayout; - banTypeGrid->addWidget(nameBanCheckBox, 0, 0); - banTypeGrid->addWidget(nameBanEdit, 0, 1); - banTypeGrid->addWidget(ipBanCheckBox, 1, 0); - banTypeGrid->addWidget(ipBanEdit, 1, 1); - QGroupBox *banTypeGroupBox = new QGroupBox(tr("Ban type")); - banTypeGroupBox->setLayout(banTypeGrid); - - permanentRadio = new QRadioButton(tr("&permanent ban")); - temporaryRadio = new QRadioButton(tr("&temporary ban")); - temporaryRadio->setChecked(true); - connect(temporaryRadio, SIGNAL(toggled(bool)), this, SLOT(enableTemporaryEdits(bool))); - daysLabel = new QLabel(tr("&Days:")); - daysEdit = new QSpinBox; - daysEdit->setMinimum(0); - daysEdit->setValue(0); - daysEdit->setMaximum(10000); - daysLabel->setBuddy(daysEdit); - hoursLabel = new QLabel(tr("&Hours:")); - hoursEdit = new QSpinBox; - hoursEdit->setMinimum(0); - hoursEdit->setValue(0); - hoursEdit->setMaximum(24); - hoursLabel->setBuddy(hoursEdit); - minutesLabel = new QLabel(tr("&Minutes:")); - minutesEdit = new QSpinBox; - minutesEdit->setMinimum(0); - minutesEdit->setValue(5); - minutesEdit->setMaximum(60); - minutesLabel->setBuddy(minutesEdit); - QGridLayout *durationLayout = new QGridLayout; - durationLayout->addWidget(permanentRadio, 0, 0, 1, 6); - durationLayout->addWidget(temporaryRadio, 1, 0, 1, 6); - durationLayout->addWidget(daysLabel, 2, 0); - durationLayout->addWidget(daysEdit, 2, 1); - durationLayout->addWidget(hoursLabel, 2, 2); - durationLayout->addWidget(hoursEdit, 2, 3); - durationLayout->addWidget(minutesLabel, 2, 4); - durationLayout->addWidget(minutesEdit, 2, 5); - QGroupBox *durationGroupBox = new QGroupBox(tr("Duration of the ban")); - durationGroupBox->setLayout(durationLayout); - - QLabel *reasonLabel = new QLabel(tr("Please enter the reason for the ban.\nThis is only saved for moderators and cannot be seen by the banned person.")); - reasonEdit = new QPlainTextEdit; - - QPushButton *okButton = new QPushButton(tr("&OK")); - okButton->setAutoDefault(true); - connect(okButton, SIGNAL(clicked()), this, SLOT(okClicked())); - QPushButton *cancelButton = new QPushButton(tr("&Cancel")); - connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); - - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(); - buttonLayout->addWidget(okButton); - buttonLayout->addWidget(cancelButton); - - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(banTypeGroupBox); - vbox->addWidget(durationGroupBox); - vbox->addWidget(reasonLabel); - vbox->addWidget(reasonEdit); - vbox->addLayout(buttonLayout); - - setLayout(vbox); - setWindowTitle(tr("Ban user from server")); + setAttribute(Qt::WA_DeleteOnClose); + + nameBanCheckBox = new QCheckBox(tr("ban &user name")); + nameBanCheckBox->setChecked(true); + nameBanEdit = new QLineEdit(QString::fromStdString(info.name())); + ipBanCheckBox = new QCheckBox(tr("ban &IP address")); + ipBanCheckBox->setChecked(true); + ipBanEdit = new QLineEdit(QString::fromStdString(info.address())); + QGridLayout *banTypeGrid = new QGridLayout; + banTypeGrid->addWidget(nameBanCheckBox, 0, 0); + banTypeGrid->addWidget(nameBanEdit, 0, 1); + banTypeGrid->addWidget(ipBanCheckBox, 1, 0); + banTypeGrid->addWidget(ipBanEdit, 1, 1); + QGroupBox *banTypeGroupBox = new QGroupBox(tr("Ban type")); + banTypeGroupBox->setLayout(banTypeGrid); + + permanentRadio = new QRadioButton(tr("&permanent ban")); + temporaryRadio = new QRadioButton(tr("&temporary ban")); + temporaryRadio->setChecked(true); + connect(temporaryRadio, SIGNAL(toggled(bool)), this, SLOT(enableTemporaryEdits(bool))); + daysLabel = new QLabel(tr("&Days:")); + daysEdit = new QSpinBox; + daysEdit->setMinimum(0); + daysEdit->setValue(0); + daysEdit->setMaximum(10000); + daysLabel->setBuddy(daysEdit); + hoursLabel = new QLabel(tr("&Hours:")); + hoursEdit = new QSpinBox; + hoursEdit->setMinimum(0); + hoursEdit->setValue(0); + hoursEdit->setMaximum(24); + hoursLabel->setBuddy(hoursEdit); + minutesLabel = new QLabel(tr("&Minutes:")); + minutesEdit = new QSpinBox; + minutesEdit->setMinimum(0); + minutesEdit->setValue(5); + minutesEdit->setMaximum(60); + minutesLabel->setBuddy(minutesEdit); + QGridLayout *durationLayout = new QGridLayout; + durationLayout->addWidget(permanentRadio, 0, 0, 1, 6); + durationLayout->addWidget(temporaryRadio, 1, 0, 1, 6); + durationLayout->addWidget(daysLabel, 2, 0); + durationLayout->addWidget(daysEdit, 2, 1); + durationLayout->addWidget(hoursLabel, 2, 2); + durationLayout->addWidget(hoursEdit, 2, 3); + durationLayout->addWidget(minutesLabel, 2, 4); + durationLayout->addWidget(minutesEdit, 2, 5); + QGroupBox *durationGroupBox = new QGroupBox(tr("Duration of the ban")); + durationGroupBox->setLayout(durationLayout); + + QLabel *reasonLabel = new QLabel(tr("Please enter the reason for the ban.\nThis is only saved for moderators and cannot be seen by the banned person.")); + reasonEdit = new QPlainTextEdit; + + QLabel *visibleReasonLabel = new QLabel(tr("Please enter the reason for the ban that will be visible to the banned person.")); + visibleReasonEdit = new QPlainTextEdit; + + QPushButton *okButton = new QPushButton(tr("&OK")); + okButton->setAutoDefault(true); + connect(okButton, SIGNAL(clicked()), this, SLOT(okClicked())); + QPushButton *cancelButton = new QPushButton(tr("&Cancel")); + connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + buttonLayout->addWidget(okButton); + buttonLayout->addWidget(cancelButton); + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(banTypeGroupBox); + vbox->addWidget(durationGroupBox); + vbox->addWidget(reasonLabel); + vbox->addWidget(reasonEdit); + vbox->addWidget(visibleReasonLabel); + vbox->addWidget(visibleReasonEdit); + vbox->addLayout(buttonLayout); + + setLayout(vbox); + setWindowTitle(tr("Ban user from server")); } void BanDialog::okClicked() { - if (!nameBanCheckBox->isChecked() && !ipBanCheckBox->isChecked()) { - QMessageBox::critical(this, tr("Error"), tr("You have to select a name-based or IP-based ban, or both.")); - return; - } - accept(); + if (!nameBanCheckBox->isChecked() && !ipBanCheckBox->isChecked()) { + QMessageBox::critical(this, tr("Error"), tr("You have to select a name-based or IP-based ban, or both.")); + return; + } + accept(); } void BanDialog::enableTemporaryEdits(bool enabled) { - daysLabel->setEnabled(enabled); - daysEdit->setEnabled(enabled); - hoursLabel->setEnabled(enabled); - hoursEdit->setEnabled(enabled); - minutesLabel->setEnabled(enabled); - minutesEdit->setEnabled(enabled); + daysLabel->setEnabled(enabled); + daysEdit->setEnabled(enabled); + hoursLabel->setEnabled(enabled); + hoursEdit->setEnabled(enabled); + minutesLabel->setEnabled(enabled); + minutesEdit->setEnabled(enabled); } QString BanDialog::getBanName() const { - return nameBanCheckBox->isChecked() ? nameBanEdit->text() : QString(); + return nameBanCheckBox->isChecked() ? nameBanEdit->text() : QString(); } QString BanDialog::getBanIP() const { - return ipBanCheckBox->isChecked() ? ipBanEdit->text() : QString(); + return ipBanCheckBox->isChecked() ? ipBanEdit->text() : QString(); } int BanDialog::getMinutes() const { - return permanentRadio->isChecked() ? 0 : (daysEdit->value() * 24 * 60 + hoursEdit->value() * 60 + minutesEdit->value()); + return permanentRadio->isChecked() ? 0 : (daysEdit->value() * 24 * 60 + hoursEdit->value() * 60 + minutesEdit->value()); } QString BanDialog::getReason() const { - return reasonEdit->toPlainText(); + return reasonEdit->toPlainText(); +} + +QString BanDialog::getVisibleReason() const +{ + return visibleReasonEdit->toPlainText(); } UserListItemDelegate::UserListItemDelegate(QObject *const parent) - : QStyledItemDelegate(parent) + : QStyledItemDelegate(parent) { } bool UserListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { - if ((event->type() == QEvent::MouseButtonPress) && index.isValid()) { - QMouseEvent *const mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::RightButton) { - static_cast(parent())->showContextMenu(mouseEvent->globalPos(), index); - return true; - } - } - return QStyledItemDelegate::editorEvent(event, model, option, index); + if ((event->type() == QEvent::MouseButtonPress) && index.isValid()) { + QMouseEvent *const mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::RightButton) { + static_cast(parent())->showContextMenu(mouseEvent->globalPos(), index); + return true; + } + } + return QStyledItemDelegate::editorEvent(event, model, option, index); } -UserListTWI::UserListTWI() - : QTreeWidgetItem(Type) +UserListTWI::UserListTWI(const ServerInfo_User &_userInfo) + : QTreeWidgetItem(Type) { + setUserInfo(_userInfo); +} + +void UserListTWI::setUserInfo(const ServerInfo_User &_userInfo) +{ + userInfo = _userInfo; + + setData(0, Qt::UserRole, userInfo.user_level()); + setIcon(0, QIcon(UserLevelPixmapGenerator::generatePixmap(12, UserLevelFlags(userInfo.user_level())))); + setIcon(1, QIcon(CountryPixmapGenerator::generatePixmap(12, QString::fromStdString(userInfo.country())))); + setData(2, Qt::UserRole, QString::fromStdString(userInfo.name())); + setData(2, Qt::DisplayRole, QString::fromStdString(userInfo.name())); +} + +void UserListTWI::setOnline(bool online) +{ + setData(0, Qt::UserRole + 1, online); + setData(2, Qt::ForegroundRole, online ? QBrush() : QBrush(Qt::gray)); } bool UserListTWI::operator<(const QTreeWidgetItem &other) const { - // Sort by online/offline - if (data(0, Qt::UserRole + 1) != other.data(0, Qt::UserRole + 1)) - return data(0, Qt::UserRole + 1).toBool(); - - // Sort by user level - if (data(0, Qt::UserRole) != other.data(0, Qt::UserRole)) - return data(0, Qt::UserRole).toInt() > other.data(0, Qt::UserRole).toInt(); - - // Sort by name - return QString::localeAwareCompare(data(2, Qt::UserRole).toString(), other.data(2, Qt::UserRole).toString()) < 0; + // Sort by online/offline + if (data(0, Qt::UserRole + 1) != other.data(0, Qt::UserRole + 1)) + return data(0, Qt::UserRole + 1).toBool(); + + // Sort by user level + if (data(0, Qt::UserRole) != other.data(0, Qt::UserRole)) + return data(0, Qt::UserRole).toInt() > other.data(0, Qt::UserRole).toInt(); + + // Sort by name + return QString::localeAwareCompare(data(2, Qt::UserRole).toString(), other.data(2, Qt::UserRole).toString()) < 0; } UserList::UserList(TabSupervisor *_tabSupervisor, AbstractClient *_client, UserListType _type, QWidget *parent) - : QGroupBox(parent), tabSupervisor(_tabSupervisor), client(_client), type(_type), onlineCount(0) + : QGroupBox(parent), tabSupervisor(_tabSupervisor), client(_client), type(_type), onlineCount(0) { - itemDelegate = new UserListItemDelegate(this); - - userTree = new QTreeWidget; - userTree->setColumnCount(3); - userTree->header()->setResizeMode(QHeaderView::ResizeToContents); - userTree->setHeaderHidden(true); - userTree->setRootIsDecorated(false); - userTree->setIconSize(QSize(20, 12)); - userTree->setItemDelegate(itemDelegate); - userTree->setAlternatingRowColors(true); - connect(userTree, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(userClicked(QTreeWidgetItem *, int))); - - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(userTree); - - setLayout(vbox); - - retranslateUi(); + itemDelegate = new UserListItemDelegate(this); + userContextMenu = new UserContextMenu(tabSupervisor, this); + connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); + + userTree = new QTreeWidget; + userTree->setColumnCount(3); +#if QT_VERSION < 0x050000 + userTree->header()->setResizeMode(QHeaderView::ResizeToContents); +#else + userTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#endif + userTree->setHeaderHidden(true); + userTree->setRootIsDecorated(false); + userTree->setIconSize(QSize(20, 12)); + userTree->setItemDelegate(itemDelegate); + userTree->setAlternatingRowColors(true); + connect(userTree, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(userClicked(QTreeWidgetItem *, int))); + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(userTree); + + setLayout(vbox); + + retranslateUi(); } void UserList::retranslateUi() { - switch (type) { - case AllUsersList: titleStr = tr("Users online: %1"); break; - case RoomList: titleStr = tr("Users in this room: %1"); break; - case BuddyList: titleStr = tr("Buddies online: %1 / %2"); break; - case IgnoreList: titleStr = tr("Ignored users online: %1 / %2"); break; - } - updateCount(); + userContextMenu->retranslateUi(); + switch (type) { + case AllUsersList: titleStr = tr("Users online: %1"); break; + case RoomList: titleStr = tr("Users in this room: %1"); break; + case BuddyList: titleStr = tr("Buddies online: %1 / %2"); break; + case IgnoreList: titleStr = tr("Ignored users online: %1 / %2"); break; + } + updateCount(); } -void UserList::processUserInfo(ServerInfo_User *user, bool online) +void UserList::processUserInfo(const ServerInfo_User &user, bool online) { - QTreeWidgetItem *item = 0; - for (int i = 0; i < userTree->topLevelItemCount(); ++i) { - QTreeWidgetItem *temp = userTree->topLevelItem(i); - if (temp->data(2, Qt::UserRole) == user->getName()) { - item = temp; - break; - } - } - if (!item) { - item = new UserListTWI; - userTree->addTopLevelItem(item); - if (online) - ++onlineCount; - updateCount(); - } - item->setData(0, Qt::UserRole, user->getUserLevel()); - item->setIcon(0, QIcon(UserLevelPixmapGenerator::generatePixmap(12, user->getUserLevel()))); - item->setIcon(1, QIcon(CountryPixmapGenerator::generatePixmap(12, user->getCountry()))); - item->setData(2, Qt::UserRole, user->getName()); - item->setData(2, Qt::DisplayRole, user->getName()); - - item->setData(0, Qt::UserRole + 1, online); - if (online) - item->setData(2, Qt::ForegroundRole, QBrush()); - else - item->setData(2, Qt::ForegroundRole, QBrush(Qt::gray)); + const QString userName = QString::fromStdString(user.name()); + UserListTWI *item = users.value(userName); + if (item) + item->setUserInfo(user); + else { + item = new UserListTWI(user); + users.insert(userName, item); + userTree->addTopLevelItem(item); + if (online) + ++onlineCount; + updateCount(); + } + item->setOnline(online); } bool UserList::deleteUser(const QString &userName) { - for (int i = 0; i < userTree->topLevelItemCount(); ++i) - if (userTree->topLevelItem(i)->data(2, Qt::UserRole) == userName) { - QTreeWidgetItem *item = userTree->takeTopLevelItem(i); - if (item->data(0, Qt::UserRole + 1).toBool()) - --onlineCount; - delete item; - updateCount(); - return true; - } - - return false; -} - -void UserList::setUserOnline(QTreeWidgetItem *item, bool online) -{ - item->setData(0, Qt::UserRole + 1, online); - - if (online) { - item->setData(2, Qt::ForegroundRole, QBrush()); - ++onlineCount; - } else { - item->setData(2, Qt::ForegroundRole, QBrush(Qt::gray)); - --onlineCount; - } - updateCount(); + UserListTWI *twi = users.value(userName); + if (twi) { + users.remove(userName); + userTree->takeTopLevelItem(userTree->indexOfTopLevelItem(twi)); + if (twi->data(0, Qt::UserRole + 1).toBool()) + --onlineCount; + delete twi; + updateCount(); + return true; + } + + return false; } void UserList::setUserOnline(const QString &userName, bool online) { - for (int i = 0; i < userTree->topLevelItemCount(); ++i) { - QTreeWidgetItem *item = userTree->topLevelItem(i); - if (item->data(2, Qt::UserRole) == userName) { - setUserOnline(item, online); - break; - } - } + UserListTWI *twi = users.value(userName); + if (!twi) + return; + + twi->setOnline(online); + if (online) + ++onlineCount; + else + --onlineCount; + updateCount(); } void UserList::updateCount() { - QString str = titleStr; - if ((type == BuddyList) || (type == IgnoreList)) - str = str.arg(onlineCount); - setTitle(str.arg(userTree->topLevelItemCount())); + QString str = titleStr; + if ((type == BuddyList) || (type == IgnoreList)) + str = str.arg(onlineCount); + setTitle(str.arg(userTree->topLevelItemCount())); } void UserList::userClicked(QTreeWidgetItem *item, int /*column*/) { - emit openMessageDialog(item->data(2, Qt::UserRole).toString(), true); -} - -void UserList::gamesOfUserReceived(ProtocolResponse *resp) -{ - Command_GetGamesOfUser *command = static_cast(sender()); - Response_GetGamesOfUser *response = qobject_cast(resp); - if (!response) - return; - - QMap gameTypeMap; - QMap roomMap; - const QList roomList = response->getRoomList(); - for (int i = 0; i < roomList.size(); ++i) { - roomMap.insert(roomList[i]->getRoomId(), roomList[i]->getName()); - const QList gameTypeList = roomList[i]->getGameTypeList(); - GameTypeMap tempMap; - for (int j = 0; j < gameTypeList.size(); ++j) - tempMap.insert(gameTypeList[j]->getGameTypeId(), gameTypeList[j]->getDescription()); - gameTypeMap.insert(roomList[i]->getRoomId(), tempMap); - } - - GameSelector *selector = new GameSelector(client, tabSupervisor, 0, roomMap, gameTypeMap); - const QList gameList = response->getGameList(); - for (int i = 0; i < gameList.size(); ++i) - selector->processGameInfo(gameList[i]); - - selector->setWindowTitle(tr("%1's games").arg(command->getUserName())); - selector->setAttribute(Qt::WA_DeleteOnClose); - selector->show(); -} - -void UserList::banUser_processUserInfoResponse(ProtocolResponse *r) -{ - Response_GetUserInfo *response = qobject_cast(r); - if (!response) - return; - - // The dialog needs to be non-modal in order to not block the event queue of the client. - BanDialog *dlg = new BanDialog(response->getUserInfo(), this); - connect(dlg, SIGNAL(accepted()), this, SLOT(banUser_dialogFinished())); - dlg->show(); -} - -void UserList::banUser_dialogFinished() -{ - BanDialog *dlg = static_cast(sender()); - client->sendCommand(new Command_BanFromServer(dlg->getBanName(), dlg->getBanIP(), dlg->getMinutes(), dlg->getReason())); + emit openMessageDialog(item->data(2, Qt::UserRole).toString(), true); } void UserList::showContextMenu(const QPoint &pos, const QModelIndex &index) { - const QString &userName = index.sibling(index.row(), 2).data(Qt::UserRole).toString(); - ServerInfo_User::UserLevelFlags userLevel = static_cast(index.sibling(index.row(), 0).data(Qt::UserRole).toInt()); - - QAction *aUserName = new QAction(userName, this); - aUserName->setEnabled(false); - QAction *aDetails = new QAction(tr("User &details"), this); - QAction *aChat = new QAction(tr("Direct &chat"), this); - QAction *aShowGames = new QAction(tr("Show this user's &games"), this); - QAction *aAddToBuddyList = new QAction(tr("Add to &buddy list"), this); - QAction *aRemoveFromBuddyList = new QAction(tr("Remove from &buddy list"), this); - QAction *aAddToIgnoreList = new QAction(tr("Add to &ignore list"), this); - QAction *aRemoveFromIgnoreList = new QAction(tr("Remove from &ignore list"), this); - QAction *aBan = new QAction(tr("Ban from &server"), this); - - QMenu *menu = new QMenu(this); - menu->addAction(aUserName); - menu->addSeparator(); - menu->addAction(aDetails); - menu->addAction(aShowGames); - menu->addAction(aChat); - if ((userLevel & ServerInfo_User::IsRegistered) && (tabSupervisor->getUserLevel() & ServerInfo_User::IsRegistered)) { - menu->addSeparator(); - if (tabSupervisor->getUserListsTab()->getBuddyList()->userInList(userName)) - menu->addAction(aRemoveFromBuddyList); - else - menu->addAction(aAddToBuddyList); - if (tabSupervisor->getUserListsTab()->getIgnoreList()->userInList(userName)) - menu->addAction(aRemoveFromIgnoreList); - else - menu->addAction(aAddToIgnoreList); - } - if (!tabSupervisor->getAdminLocked()) { - menu->addSeparator(); - menu->addAction(aBan); - } - if (userName == tabSupervisor->getUserInfo()->getName()) { - aChat->setEnabled(false); - aAddToBuddyList->setEnabled(false); - aRemoveFromBuddyList->setEnabled(false); - aAddToIgnoreList->setEnabled(false); - aRemoveFromIgnoreList->setEnabled(false); - aBan->setEnabled(false); - } - - QAction *actionClicked = menu->exec(pos); - if (actionClicked == aDetails) { - UserInfoBox *infoWidget = new UserInfoBox(client, true, this, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint); - infoWidget->setAttribute(Qt::WA_DeleteOnClose); - infoWidget->updateInfo(userName); - } else if (actionClicked == aChat) - emit openMessageDialog(userName, true); - else if (actionClicked == aAddToBuddyList) - client->sendCommand(new Command_AddToList("buddy", userName)); - else if (actionClicked == aRemoveFromBuddyList) - client->sendCommand(new Command_RemoveFromList("buddy", userName)); - else if (actionClicked == aShowGames) { - Command *cmd = new Command_GetGamesOfUser(userName); - connect(cmd, SIGNAL(finished(ProtocolResponse *)), this, SLOT(gamesOfUserReceived(ProtocolResponse *))); - client->sendCommand(cmd); - } else if (actionClicked == aAddToIgnoreList) - client->sendCommand(new Command_AddToList("ignore", userName)); - else if (actionClicked == aRemoveFromIgnoreList) - client->sendCommand(new Command_RemoveFromList("ignore", userName)); - else if (actionClicked == aBan) { - Command_GetUserInfo *command = new Command_GetUserInfo(userName); - connect(command, SIGNAL(finished(ProtocolResponse *)), this, SLOT(banUser_processUserInfoResponse(ProtocolResponse *))); - client->sendCommand(command); - } - - delete menu; - delete aUserName; - delete aDetails; - delete aChat; - delete aAddToBuddyList; - delete aRemoveFromBuddyList; - delete aAddToIgnoreList; - delete aRemoveFromIgnoreList; - delete aBan; -} + const ServerInfo_User &userInfo = static_cast(userTree->topLevelItem(index.row()))->getUserInfo(); + bool online = index.sibling(index.row(), 0).data(Qt::UserRole + 1).toBool(); -bool UserList::userInList(const QString &userName) const -{ - for (int i = 0; i < userTree->topLevelItemCount(); ++i) - if (userTree->topLevelItem(i)->data(2, Qt::UserRole) == userName) - return true; - return false; + userContextMenu->showContextMenu(pos, QString::fromStdString(userInfo.name()), UserLevelFlags(userInfo.user_level()), online); } void UserList::sortItems() { - userTree->sortItems(1, Qt::AscendingOrder); + userTree->sortItems(1, Qt::AscendingOrder); } diff --git a/cockatrice/src/userlist.h b/cockatrice/src/userlist.h index 45d9eb5fb..fc043b1de 100644 --- a/cockatrice/src/userlist.h +++ b/cockatrice/src/userlist.h @@ -5,6 +5,7 @@ #include #include #include +#include "user_level.h" class QTreeWidget; class ServerInfo_User; @@ -15,74 +16,80 @@ class QCheckBox; class QSpinBox; class QRadioButton; class QPlainTextEdit; -class ProtocolResponse; +class Response; +class CommandContainer; +class UserContextMenu; class BanDialog : public QDialog { - Q_OBJECT + Q_OBJECT private: - QLabel *daysLabel, *hoursLabel, *minutesLabel; - QCheckBox *nameBanCheckBox, *ipBanCheckBox; - QLineEdit *nameBanEdit, *ipBanEdit; - QSpinBox *daysEdit, *hoursEdit, *minutesEdit; - QRadioButton *permanentRadio, *temporaryRadio; - QPlainTextEdit *reasonEdit; + QLabel *daysLabel, *hoursLabel, *minutesLabel; + QCheckBox *nameBanCheckBox, *ipBanCheckBox; + QLineEdit *nameBanEdit, *ipBanEdit; + QSpinBox *daysEdit, *hoursEdit, *minutesEdit; + QRadioButton *permanentRadio, *temporaryRadio; + QPlainTextEdit *reasonEdit, *visibleReasonEdit; private slots: - void okClicked(); - void enableTemporaryEdits(bool enabled); + void okClicked(); + void enableTemporaryEdits(bool enabled); public: - BanDialog(ServerInfo_User *info, QWidget *parent = 0); - QString getBanName() const; - QString getBanIP() const; - int getMinutes() const; - QString getReason() const; + BanDialog(const ServerInfo_User &info, QWidget *parent = 0); + QString getBanName() const; + QString getBanIP() const; + int getMinutes() const; + QString getReason() const; + QString getVisibleReason() const; }; class UserListItemDelegate : public QStyledItemDelegate { public: - UserListItemDelegate(QObject *const parent); - bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); + UserListItemDelegate(QObject *const parent); + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); }; class UserListTWI : public QTreeWidgetItem { +private: + ServerInfo_User userInfo; public: - UserListTWI(); - bool operator<(const QTreeWidgetItem &other) const; + UserListTWI(const ServerInfo_User &_userInfo); + const ServerInfo_User &getUserInfo() const { return userInfo; } + void setUserInfo(const ServerInfo_User &_userInfo); + void setOnline(bool online); + bool operator<(const QTreeWidgetItem &other) const; }; class UserList : public QGroupBox { - Q_OBJECT + Q_OBJECT public: - enum UserListType { AllUsersList, RoomList, BuddyList, IgnoreList }; + enum UserListType { AllUsersList, RoomList, BuddyList, IgnoreList }; private: - TabSupervisor *tabSupervisor; - AbstractClient *client; - UserListType type; - QTreeWidget *userTree; - UserListItemDelegate *itemDelegate; - int onlineCount; - QString titleStr; - void updateCount(); - void setUserOnline(QTreeWidgetItem *user, bool online); + QMap users; + TabSupervisor *tabSupervisor; + AbstractClient *client; + UserListType type; + QTreeWidget *userTree; + UserListItemDelegate *itemDelegate; + UserContextMenu *userContextMenu; + int onlineCount; + QString titleStr; + void updateCount(); private slots: - void userClicked(QTreeWidgetItem *item, int column); - void banUser_processUserInfoResponse(ProtocolResponse *resp); - void banUser_dialogFinished(); - void gamesOfUserReceived(ProtocolResponse *resp); + void userClicked(QTreeWidgetItem *item, int column); signals: - void openMessageDialog(const QString &userName, bool focus); - void addBuddy(const QString &userName); - void removeBuddy(const QString &userName); - void addIgnore(const QString &userName); - void removeIgnore(const QString &userName); + void openMessageDialog(const QString &userName, bool focus); + void addBuddy(const QString &userName); + void removeBuddy(const QString &userName); + void addIgnore(const QString &userName); + void removeIgnore(const QString &userName); public: - UserList(TabSupervisor *_tabSupervisor, AbstractClient *_client, UserListType _type, QWidget *parent = 0); - void retranslateUi(); - void processUserInfo(ServerInfo_User *user, bool online); - bool deleteUser(const QString &userName); - void setUserOnline(const QString &userName, bool online); - bool userInList(const QString &userName) const; - void showContextMenu(const QPoint &pos, const QModelIndex &index); - void sortItems(); + UserList(TabSupervisor *_tabSupervisor, AbstractClient *_client, UserListType _type, QWidget *parent = 0); + void retranslateUi(); + void processUserInfo(const ServerInfo_User &user, bool online); + bool deleteUser(const QString &userName); + void setUserOnline(const QString &userName, bool online); + const QMap &getUsers() const { return users; } + void showContextMenu(const QPoint &pos, const QModelIndex &index); + void sortItems(); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/window_deckeditor.cpp b/cockatrice/src/window_deckeditor.cpp deleted file mode 100644 index c8723f0a6..000000000 --- a/cockatrice/src/window_deckeditor.cpp +++ /dev/null @@ -1,529 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "window_deckeditor.h" -#include "window_sets.h" -#include "carddatabase.h" -#include "carddatabasemodel.h" -#include "decklistmodel.h" -#include "cardinfowidget.h" -#include "dlg_cardsearch.h" -#include "dlg_load_deck_from_clipboard.h" -#include "main.h" -#include "settingscache.h" -#include "priceupdater.h" - -void SearchLineEdit::keyPressEvent(QKeyEvent *event) -{ - if (treeView && ((event->key() == Qt::Key_Up) || (event->key() == Qt::Key_Down))) - QCoreApplication::sendEvent(treeView, event); - QLineEdit::keyPressEvent(event); -} - -WndDeckEditor::WndDeckEditor(QWidget *parent) - : QMainWindow(parent) -{ - aSearch = new QAction(tr("&Search..."), this); - aSearch->setIcon(QIcon(":/resources/icon_search.svg")); - connect(aSearch, SIGNAL(triggered()), this, SLOT(actSearch())); - aClearSearch = new QAction(tr("&Clear search"), this); - aClearSearch->setIcon(QIcon(":/resources/icon_clearsearch.svg")); - connect(aClearSearch, SIGNAL(triggered()), this, SLOT(actClearSearch())); - - QLabel *searchLabel = new QLabel(tr("&Search for:")); - searchEdit = new SearchLineEdit; - searchLabel->setBuddy(searchEdit); - connect(searchEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &))); - connect(searchEdit, SIGNAL(returnPressed()), this, SLOT(actAddCard())); - QToolButton *searchButton = new QToolButton; - searchButton->setDefaultAction(aSearch); - QToolButton *clearSearchButton = new QToolButton; - clearSearchButton->setDefaultAction(aClearSearch); - - QHBoxLayout *searchLayout = new QHBoxLayout; - searchLayout->addWidget(searchLabel); - searchLayout->addWidget(searchEdit); - searchLayout->addWidget(searchButton); - searchLayout->addWidget(clearSearchButton); - - databaseModel = new CardDatabaseModel(db, this); - databaseDisplayModel = new CardDatabaseDisplayModel(this); - databaseDisplayModel->setSourceModel(databaseModel); - databaseDisplayModel->setFilterKeyColumn(0); - databaseDisplayModel->sort(0, Qt::AscendingOrder); - databaseView = new QTreeView(); - databaseView->setModel(databaseDisplayModel); - databaseView->setUniformRowHeights(true); - databaseView->setRootIsDecorated(false); - databaseView->setAlternatingRowColors(true); - databaseView->setSortingEnabled(true); - databaseView->sortByColumn(0, Qt::AscendingOrder); - databaseView->resizeColumnToContents(0); - connect(databaseView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(updateCardInfoLeft(const QModelIndex &, const QModelIndex &))); - connect(databaseView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actAddCard())); - searchEdit->setTreeView(databaseView); - - QVBoxLayout *leftFrame = new QVBoxLayout; - leftFrame->addLayout(searchLayout); - leftFrame->addWidget(databaseView); - - cardInfo = new CardInfoWidget(CardInfoWidget::ModeDeckEditor); - cardInfo->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); - - QToolBar *verticalToolBar = new QToolBar; - verticalToolBar->setOrientation(Qt::Vertical); - verticalToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - verticalToolBar->setIconSize(QSize(24, 24)); - QHBoxLayout *verticalToolBarLayout = new QHBoxLayout; - verticalToolBarLayout->addStretch(); - verticalToolBarLayout->addWidget(verticalToolBar); - verticalToolBarLayout->addStretch(); - - QVBoxLayout *middleFrame = new QVBoxLayout; - middleFrame->addWidget(cardInfo, 10); - middleFrame->addLayout(verticalToolBarLayout); - - deckModel = new DeckListModel(this); - connect(deckModel, SIGNAL(deckHashChanged()), this, SLOT(updateHash())); - deckView = new QTreeView(); - deckView->setModel(deckModel); - deckView->setUniformRowHeights(true); - deckView->header()->setResizeMode(QHeaderView::ResizeToContents); - connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(updateCardInfoRight(const QModelIndex &, const QModelIndex &))); - - QLabel *nameLabel = new QLabel(tr("Deck &name:")); - nameEdit = new QLineEdit; - nameLabel->setBuddy(nameEdit); - connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateName(const QString &))); - QLabel *commentsLabel = new QLabel(tr("&Comments:")); - commentsEdit = new QTextEdit; - commentsEdit->setMaximumHeight(70); - commentsLabel->setBuddy(commentsEdit); - connect(commentsEdit, SIGNAL(textChanged()), this, SLOT(updateComments())); - QLabel *hashLabel1 = new QLabel(tr("Hash:")); - hashLabel = new QLabel; - - QGridLayout *grid = new QGridLayout; - grid->addWidget(nameLabel, 0, 0); - grid->addWidget(nameEdit, 0, 1); - - grid->addWidget(commentsLabel, 1, 0); - grid->addWidget(commentsEdit, 1, 1); - - grid->addWidget(hashLabel1, 2, 0); - grid->addWidget(hashLabel, 2, 1); - - // Update price - aUpdatePrices = new QAction(tr("&Update prices"), this); - aUpdatePrices->setShortcut(tr("Ctrl+U")); - aUpdatePrices->setIcon(QIcon(":/resources/icon_update.png")); - connect(aUpdatePrices, SIGNAL(triggered()), this, SLOT(actUpdatePrices())); - if (!settingsCache->getPriceTagFeature()) - aUpdatePrices->setVisible(false); - - QToolBar *deckToolBar = new QToolBar; - deckToolBar->setOrientation(Qt::Vertical); - deckToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - deckToolBar->setIconSize(QSize(24, 24)); - deckToolBar->addAction(aUpdatePrices); - QHBoxLayout *deckToolbarLayout = new QHBoxLayout; - deckToolbarLayout->addStretch(); - deckToolbarLayout->addWidget(deckToolBar); - deckToolbarLayout->addStretch(); - - QVBoxLayout *rightFrame = new QVBoxLayout; - rightFrame->addLayout(grid); - rightFrame->addWidget(deckView, 10); - rightFrame->addLayout(deckToolbarLayout); - - QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->addLayout(leftFrame, 10); - mainLayout->addLayout(middleFrame); - mainLayout->addLayout(rightFrame, 10); - - QWidget *centralWidget = new QWidget; - centralWidget->setLayout(mainLayout); - setCentralWidget(centralWidget); - - setWindowTitle(tr("Deck editor [*]")); - - aNewDeck = new QAction(tr("&New deck"), this); - aNewDeck->setShortcuts(QKeySequence::New); - connect(aNewDeck, SIGNAL(triggered()), this, SLOT(actNewDeck())); - aLoadDeck = new QAction(tr("&Load deck..."), this); - aLoadDeck->setShortcuts(QKeySequence::Open); - connect(aLoadDeck, SIGNAL(triggered()), this, SLOT(actLoadDeck())); - aSaveDeck = new QAction(tr("&Save deck"), this); - aSaveDeck->setShortcuts(QKeySequence::Save); - connect(aSaveDeck, SIGNAL(triggered()), this, SLOT(actSaveDeck())); - aSaveDeckAs = new QAction(tr("Save deck &as..."), this); -// aSaveDeckAs->setShortcuts(QKeySequence::SaveAs); - connect(aSaveDeckAs, SIGNAL(triggered()), this, SLOT(actSaveDeckAs())); - aLoadDeckFromClipboard = new QAction(tr("Load deck from cl&ipboard..."), this); - connect(aLoadDeckFromClipboard, SIGNAL(triggered()), this, SLOT(actLoadDeckFromClipboard())); - aLoadDeckFromClipboard->setShortcuts(QKeySequence::Paste); - aSaveDeckToClipboard = new QAction(tr("Save deck to clip&board"), this); - connect(aSaveDeckToClipboard, SIGNAL(triggered()), this, SLOT(actSaveDeckToClipboard())); - aSaveDeckToClipboard->setShortcuts(QKeySequence::Copy); - aPrintDeck = new QAction(tr("&Print deck..."), this); - aPrintDeck->setShortcuts(QKeySequence::Print); - connect(aPrintDeck, SIGNAL(triggered()), this, SLOT(actPrintDeck())); - aClose = new QAction(tr("&Close"), this); - aClose->setShortcut(tr("Ctrl+Q")); - connect(aClose, SIGNAL(triggered()), this, SLOT(close())); - - aEditSets = new QAction(tr("&Edit sets..."), this); - connect(aEditSets, SIGNAL(triggered()), this, SLOT(actEditSets())); - - deckMenu = menuBar()->addMenu(tr("&Deck")); - deckMenu->addAction(aNewDeck); - deckMenu->addAction(aLoadDeck); - deckMenu->addAction(aSaveDeck); - deckMenu->addAction(aSaveDeckAs); - deckMenu->addSeparator(); - deckMenu->addAction(aLoadDeckFromClipboard); - deckMenu->addAction(aSaveDeckToClipboard); - deckMenu->addSeparator(); - deckMenu->addAction(aPrintDeck); - deckMenu->addSeparator(); - deckMenu->addAction(aClose); - - dbMenu = menuBar()->addMenu(tr("&Card database")); - dbMenu->addAction(aEditSets); - dbMenu->addSeparator(); - dbMenu->addAction(aSearch); - dbMenu->addAction(aClearSearch); - - aAddCard = new QAction(tr("Add card to &maindeck"), this); - aAddCard->setShortcuts(QList() << QKeySequence(tr("Return")) << QKeySequence(tr("Enter"))); - aAddCard->setIcon(QIcon(":/resources/arrow_right_green.svg")); - connect(aAddCard, SIGNAL(triggered()), this, SLOT(actAddCard())); - aAddCardToSideboard = new QAction(tr("Add card to &sideboard"), this); - aAddCardToSideboard->setIcon(QIcon(":/resources/add_to_sideboard.svg")); - aAddCardToSideboard->setShortcuts(QList() << QKeySequence(tr("Ctrl+Return")) << QKeySequence(tr("Ctrl+Enter"))); - connect(aAddCardToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard())); - aRemoveCard = new QAction(tr("&Remove row"), this); - aRemoveCard->setShortcut(tr("Del")); - aRemoveCard->setIcon(QIcon(":/resources/remove_row.svg")); - connect(aRemoveCard, SIGNAL(triggered()), this, SLOT(actRemoveCard())); - aIncrement = new QAction(tr("&Increment number"), this); - aIncrement->setShortcut(tr("+")); - aIncrement->setIcon(QIcon(":/resources/increment.svg")); - connect(aIncrement, SIGNAL(triggered()), this, SLOT(actIncrement())); - aDecrement = new QAction(tr("&Decrement number"), this); - aDecrement->setIcon(QIcon(":/resources/decrement.svg")); - aDecrement->setShortcut(tr("-")); - connect(aDecrement, SIGNAL(triggered()), this, SLOT(actDecrement())); - - verticalToolBar->addAction(aAddCard); - verticalToolBar->addAction(aAddCardToSideboard); - verticalToolBar->addAction(aRemoveCard); - verticalToolBar->addAction(aIncrement); - verticalToolBar->addAction(aDecrement); - verticalToolBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - - dlgCardSearch = new DlgCardSearch(this); - - resize(950, 700); -} - -WndDeckEditor::~WndDeckEditor() -{ -} - -void WndDeckEditor::updateName(const QString &name) -{ - deckModel->getDeckList()->setName(name); - setWindowModified(true); -} - -void WndDeckEditor::updateComments() -{ - deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); - setWindowModified(true); -} - -void WndDeckEditor::updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &/*previous*/) -{ - cardInfo->setCard(current.sibling(current.row(), 0).data().toString()); -} - -void WndDeckEditor::updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &/*previous*/) -{ - if (!current.isValid()) - return; - if (!current.model()->hasChildren(current.sibling(current.row(), 0))) - cardInfo->setCard(current.sibling(current.row(), 1).data().toString()); -} - -void WndDeckEditor::updateSearch(const QString &search) -{ - databaseDisplayModel->setCardNameBeginning(search); - QModelIndexList sel = databaseView->selectionModel()->selectedRows(); - if (sel.isEmpty() && databaseDisplayModel->rowCount()) - databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); -} - -void WndDeckEditor::updateHash() -{ - hashLabel->setText(deckModel->getDeckList()->getDeckHash()); -} - -bool WndDeckEditor::confirmClose() -{ - if (isWindowModified()) { - QMessageBox::StandardButton ret = QMessageBox::warning(this, tr("Are you sure?"), - tr("The decklist has been modified.\nDo you want to save the changes?"), - QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); - if (ret == QMessageBox::Save) - return actSaveDeck(); - else if (ret == QMessageBox::Cancel) - return false; - } - return true; -} - -void WndDeckEditor::closeEvent(QCloseEvent *event) -{ - if (confirmClose()) { - event->accept(); - deleteLater(); - } else - event->ignore(); -} - -void WndDeckEditor::actNewDeck() -{ - if (!confirmClose()) - return; - - deckModel->cleanList(); - nameEdit->setText(QString()); - commentsEdit->setText(QString()); - lastFileName = QString(); - setWindowModified(false); -} - -void WndDeckEditor::actLoadDeck() -{ - if (!confirmClose()) - return; - - QFileDialog dialog(this, tr("Load deck")); - dialog.setDirectory(settingsCache->getDeckPath()); - dialog.setNameFilters(DeckList::fileNameFilters); - if (!dialog.exec()) - return; - - QString fileName = dialog.selectedFiles().at(0); - DeckList::FileFormat fmt = DeckList::getFormatFromNameFilter(dialog.selectedNameFilter()); - DeckList *l = new DeckList; - if (l->loadFromFile(fileName, fmt)) - setDeck(l, fileName, fmt); - else - delete l; -} - -bool WndDeckEditor::actSaveDeck() -{ - if (lastFileName.isEmpty()) - return actSaveDeckAs(); - else if (deckModel->getDeckList()->saveToFile(lastFileName, lastFileFormat)) { - setWindowModified(false); - return true; - } - QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); - return false; -} - -bool WndDeckEditor::actSaveDeckAs() -{ - QFileDialog dialog(this, tr("Save deck")); - dialog.setDirectory(settingsCache->getDeckPath()); - dialog.setAcceptMode(QFileDialog::AcceptSave); - dialog.setConfirmOverwrite(true); - dialog.setDefaultSuffix("cod"); - dialog.setNameFilters(DeckList::fileNameFilters); - dialog.selectFile(deckModel->getDeckList()->getName()); - if (!dialog.exec()) - return false; - - QString fileName = dialog.selectedFiles().at(0); - DeckList::FileFormat fmt = DeckList::getFormatFromNameFilter(dialog.selectedNameFilter()); - - if (deckModel->getDeckList()->saveToFile(fileName, fmt)) { - lastFileName = fileName; - lastFileFormat = fmt; - setWindowModified(false); - return true; - } - QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); - return false; -} - -void WndDeckEditor::actLoadDeckFromClipboard() -{ - if (!confirmClose()) - return; - - DlgLoadDeckFromClipboard dlg; - if (!dlg.exec()) - return; - - setDeck(dlg.getDeckList()); - setWindowModified(true); -} - -void WndDeckEditor::actSaveDeckToClipboard() -{ - QString buffer; - QTextStream stream(&buffer); - deckModel->getDeckList()->saveToStream_Plain(stream); - QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); - QApplication::clipboard()->setText(buffer, QClipboard::Selection); -} - -void WndDeckEditor::actPrintDeck() -{ - QPrintPreviewDialog *dlg = new QPrintPreviewDialog(this); - connect(dlg, SIGNAL(paintRequested(QPrinter *)), deckModel, SLOT(printDeckList(QPrinter *))); - dlg->exec(); -} - -void WndDeckEditor::actEditSets() -{ - WndSets *w = new WndSets; - w->setWindowModality(Qt::WindowModal); - w->show(); -} - -void WndDeckEditor::actSearch() -{ - if (dlgCardSearch->exec()) { - searchEdit->clear(); - databaseDisplayModel->setCardName(dlgCardSearch->getCardName()); - databaseDisplayModel->setCardText(dlgCardSearch->getCardText()); - databaseDisplayModel->setCardTypes(dlgCardSearch->getCardTypes()); - databaseDisplayModel->setCardColors(dlgCardSearch->getCardColors()); - } -} - -void WndDeckEditor::actClearSearch() -{ - databaseDisplayModel->clearSearch(); -} - -void WndDeckEditor::recursiveExpand(const QModelIndex &index) -{ - if (index.parent().isValid()) - recursiveExpand(index.parent()); - deckView->expand(index); -} - -void WndDeckEditor::addCardHelper(const QString &zoneName) -{ - const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex(); - if (!currentIndex.isValid()) - return; - const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString(); - - QModelIndex newCardIndex = deckModel->addCard(cardName, zoneName); - recursiveExpand(newCardIndex); - deckView->setCurrentIndex(newCardIndex); - - setWindowModified(true); -} - -void WndDeckEditor::actAddCard() -{ - addCardHelper("main"); -} - -void WndDeckEditor::actAddCardToSideboard() -{ - addCardHelper("side"); -} - -void WndDeckEditor::actRemoveCard() -{ - const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); - if (!currentIndex.isValid() || deckModel->hasChildren(currentIndex)) - return; - deckModel->removeRow(currentIndex.row(), currentIndex.parent()); - setWindowModified(true); -} - -void WndDeckEditor::actIncrement() -{ - const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); - if (!currentIndex.isValid()) - return; - const QModelIndex numberIndex = currentIndex.sibling(currentIndex.row(), 0); - const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); - deckView->setCurrentIndex(numberIndex); - deckModel->setData(numberIndex, count + 1, Qt::EditRole); - setWindowModified(true); -} - -void WndDeckEditor::actDecrement() -{ - const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); - if (!currentIndex.isValid()) - return; - const QModelIndex numberIndex = currentIndex.sibling(currentIndex.row(), 0); - const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); - deckView->setCurrentIndex(numberIndex); - if (count == 1) - deckModel->removeRow(currentIndex.row(), currentIndex.parent()); - else - deckModel->setData(numberIndex, count - 1, Qt::EditRole); - setWindowModified(true); -} - -void WndDeckEditor::actUpdatePrices() -{ - aUpdatePrices->setDisabled(true); - PriceUpdater *up = new PriceUpdater(deckModel->getDeckList()); - connect(up, SIGNAL(finishedUpdate()), this, SLOT(finishedUpdatingPrices())); - up->updatePrices(); -} - -void WndDeckEditor::finishedUpdatingPrices() -{ - deckModel->pricesUpdated(); - setWindowModified(true); - aUpdatePrices->setDisabled(false); -} - -void WndDeckEditor::setDeck(DeckList *_deck, const QString &_lastFileName, DeckList::FileFormat _lastFileFormat) -{ - deckModel->setDeckList(_deck); - - lastFileName = _lastFileName; - lastFileFormat = _lastFileFormat; - nameEdit->setText(_deck->getName()); - commentsEdit->setText(_deck->getComments()); - updateHash(); - deckModel->sort(1); - deckView->expandAll(); - setWindowModified(false); - - db->cacheCardPixmaps(_deck->getCardList()); - deckView->expandAll(); - setWindowModified(false); -} diff --git a/cockatrice/src/window_deckeditor.h b/cockatrice/src/window_deckeditor.h deleted file mode 100644 index 7f3dd927c..000000000 --- a/cockatrice/src/window_deckeditor.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef WINDOW_DECKEDITOR_H -#define WINDOW_DECKEDITOR_H - -#include -#include -#include -#include "decklist.h" - -class CardDatabaseModel; -class CardDatabaseDisplayModel; -class DeckListModel; -class QTreeView; -class QTableView; -class CardInfoWidget; -class QTextEdit; -class DlgCardSearch; -class QLabel; - -class SearchLineEdit : public QLineEdit { - private: - QTreeView *treeView; - protected: - void keyPressEvent(QKeyEvent *event); - public: - SearchLineEdit() : QLineEdit(), treeView(0) { } - void setTreeView(QTreeView *_treeView) { treeView = _treeView; } -}; - -class WndDeckEditor : public QMainWindow { - Q_OBJECT -private slots: - void updateName(const QString &name); - void updateComments(); - void updateHash(); - void updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &previous); - void updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &previous); - void updateSearch(const QString &search); - - void actNewDeck(); - void actLoadDeck(); - bool actSaveDeck(); - bool actSaveDeckAs(); - void actLoadDeckFromClipboard(); - void actSaveDeckToClipboard(); - void actPrintDeck(); - - void actEditSets(); - - void actSearch(); - void actClearSearch(); - - void actAddCard(); - void actAddCardToSideboard(); - void actRemoveCard(); - void actIncrement(); - void actDecrement(); - void actUpdatePrices(); - - void finishedUpdatingPrices(); -private: - void addCardHelper(const QString &zoneName); - void recursiveExpand(const QModelIndex &index); - bool confirmClose(); - - QString lastFileName; - DeckList::FileFormat lastFileFormat; - - CardDatabaseModel *databaseModel; - CardDatabaseDisplayModel *databaseDisplayModel; - DeckListModel *deckModel; - QTreeView *databaseView; - QTreeView *deckView; - CardInfoWidget *cardInfo; - SearchLineEdit *searchEdit; - QLineEdit *nameEdit; - QTextEdit *commentsEdit; - QLabel *hashLabel; - DlgCardSearch *dlgCardSearch; - - QMenu *deckMenu, *dbMenu; - QAction *aNewDeck, *aLoadDeck, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aSaveDeckToClipboard, *aPrintDeck, *aClose; - QAction *aEditSets, *aSearch, *aClearSearch; - QAction *aAddCard, *aAddCardToSideboard, *aRemoveCard, *aIncrement, *aDecrement, *aUpdatePrices; -public: - WndDeckEditor(QWidget *parent = 0); - ~WndDeckEditor(); - void setDeck(DeckList *_deck, const QString &_lastFileName = QString(), DeckList::FileFormat _lastFileFormat = DeckList::CockatriceFormat); -protected: - void closeEvent(QCloseEvent *event); -}; - -#endif diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 0f557832f..43b0624eb 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -24,317 +24,398 @@ #include #include #include +#include +#include +#include +#include #include "main.h" #include "window_main.h" #include "dlg_connect.h" #include "dlg_settings.h" -#include "window_deckeditor.h" #include "tab_supervisor.h" #include "remoteclient.h" #include "localserver.h" #include "localserverinterface.h" #include "localclient.h" +#include "settingscache.h" +#include "tab_game.h" + +#include "version_string.h" + +#include "pb/game_replay.pb.h" +#include "pb/room_commands.pb.h" +#include "pb/event_connection_closed.pb.h" +#include "pb/event_server_shutdown.pb.h" const QString MainWindow::appName = "Cockatrice"; -void MainWindow::updateTabMenu(QMenu *menu) +void MainWindow::updateTabMenu(const QList &newMenuList) { - if (tabMenu) - menuBar()->removeAction(tabMenu->menuAction()); - tabMenu = menu; - if (menu) - menuBar()->insertMenu(helpMenu->menuAction(), menu); + for (int i = 0; i < tabMenus.size(); ++i) + menuBar()->removeAction(tabMenus[i]->menuAction()); + tabMenus = newMenuList; + for (int i = 0; i < tabMenus.size(); ++i) + menuBar()->insertMenu(helpMenu->menuAction(), tabMenus[i]); } -void MainWindow::processConnectionClosedEvent(Event_ConnectionClosed *event) +void MainWindow::processConnectionClosedEvent(const Event_ConnectionClosed &event) { - QString reason = event->getReason(); - client->disconnectFromServer(); - QString reasonStr; - if (reason == "too_many_connections") - reasonStr = tr("There are too many concurrent connections from your address."); - else if (reason == "banned") - reasonStr = tr("Banned by moderator."); - else if (reason == "server_shutdown") - reasonStr = tr("Scheduled server shutdown."); - else - reasonStr = tr("Unknown reason."); - QMessageBox::critical(this, tr("Connection closed"), tr("The server has terminated your connection.\nReason: %1").arg(reasonStr)); + client->disconnectFromServer(); + QString reasonStr; + switch (event.reason()) { + case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: reasonStr = tr("There are too many concurrent connections from your address."); break; + case Event_ConnectionClosed::BANNED: { + reasonStr = tr("Banned by moderator"); + if (event.has_end_time()) + reasonStr.append("\n" + tr("Expected end time: %1").arg(QDateTime::fromTime_t(event.end_time()).toString())); + else + reasonStr.append("\n" + tr("This ban lasts indefinitely.")); + if (event.has_reason_str()) + reasonStr.append("\n\n" + QString::fromStdString(event.reason_str())); + break; + } + case Event_ConnectionClosed::SERVER_SHUTDOWN: reasonStr = tr("Scheduled server shutdown."); break; + case Event_ConnectionClosed::USERNAMEINVALID: reasonStr = tr("Invalid username."); break; + default: reasonStr = QString::fromStdString(event.reason_str()); + } + QMessageBox::critical(this, tr("Connection closed"), tr("The server has terminated your connection.\nReason: %1").arg(reasonStr)); } -void MainWindow::processServerShutdownEvent(Event_ServerShutdown *event) +void MainWindow::processServerShutdownEvent(const Event_ServerShutdown &event) { - QMessageBox::information(this, tr("Scheduled server shutdown"), tr("The server is going to be restarted in %n minute(s).\nAll running games will be lost.\nReason for shutdown: %1", "", event->getMinutes()).arg(event->getReason())); + QMessageBox::information(this, tr("Scheduled server shutdown"), tr("The server is going to be restarted in %n minute(s).\nAll running games will be lost.\nReason for shutdown: %1", "", event.minutes()).arg(QString::fromStdString(event.reason()))); } void MainWindow::statusChanged(ClientStatus _status) { - setClientStatusTitle(); - switch (_status) { - case StatusConnecting: - break; - case StatusDisconnected: - tabSupervisor->stop(); - aSinglePlayer->setEnabled(true); - aConnect->setEnabled(true); - aDisconnect->setEnabled(false); - break; - case StatusLoggingIn: - aSinglePlayer->setEnabled(false); - aConnect->setEnabled(false); - aDisconnect->setEnabled(true); - break; - case StatusLoggedIn: - break; - default: - break; - } + setClientStatusTitle(); + switch (_status) { + case StatusConnecting: + break; + case StatusDisconnected: + tabSupervisor->stop(); + aSinglePlayer->setEnabled(true); + aConnect->setEnabled(true); + aDisconnect->setEnabled(false); + break; + case StatusLoggingIn: + aSinglePlayer->setEnabled(false); + aConnect->setEnabled(false); + aDisconnect->setEnabled(true); + break; + case StatusLoggedIn: + break; + default: + break; + } } -void MainWindow::userInfoReceived(ServerInfo_User *info) +void MainWindow::userInfoReceived(const ServerInfo_User &info) { - tabSupervisor->start(client, info); + tabSupervisor->start(info); } // Actions void MainWindow::actConnect() { - DlgConnect dlg(this); - if (dlg.exec()) - client->connectToServer(dlg.getHost(), dlg.getPort(), dlg.getPlayerName(), dlg.getPassword()); + DlgConnect dlg(this); + if (dlg.exec()) + client->connectToServer(dlg.getHost(), dlg.getPort(), dlg.getPlayerName(), dlg.getPassword()); } void MainWindow::actDisconnect() { - client->disconnectFromServer(); + client->disconnectFromServer(); } void MainWindow::actSinglePlayer() { - bool ok; - int numberPlayers = QInputDialog::getInt(this, tr("Number of players"), tr("Please enter the number of players."), 2, 1, 8, 1, &ok); - if (!ok) - return; - - aConnect->setEnabled(false); - aSinglePlayer->setEnabled(false); - - localServer = new LocalServer(this); - LocalServerInterface *mainLsi = localServer->newConnection(); - LocalClient *mainClient = new LocalClient(mainLsi, tr("Player %1").arg(1), this); - QList localClients; - localClients.append(mainClient); - - for (int i = 0; i < numberPlayers - 1; ++i) { - LocalServerInterface *slaveLsi = localServer->newConnection(); - LocalClient *slaveClient = new LocalClient(slaveLsi, tr("Player %1").arg(i + 2), this); - localClients.append(slaveClient); - } - tabSupervisor->startLocal(localClients); - - Command_CreateGame *createCommand = new Command_CreateGame(0, QString(), QString(), numberPlayers, QList(), false, false, false, false); - mainClient->sendCommand(createCommand); + bool ok; + int numberPlayers = QInputDialog::getInt(this, tr("Number of players"), tr("Please enter the number of players."), 2, 1, 8, 1, &ok); + if (!ok) + return; + + aConnect->setEnabled(false); + aSinglePlayer->setEnabled(false); + + localServer = new LocalServer(this); + LocalServerInterface *mainLsi = localServer->newConnection(); + LocalClient *mainClient = new LocalClient(mainLsi, tr("Player %1").arg(1), this); + QList localClients; + localClients.append(mainClient); + + for (int i = 0; i < numberPlayers - 1; ++i) { + LocalServerInterface *slaveLsi = localServer->newConnection(); + LocalClient *slaveClient = new LocalClient(slaveLsi, tr("Player %1").arg(i + 2), this); + localClients.append(slaveClient); + } + tabSupervisor->startLocal(localClients); + + Command_CreateGame createCommand; + createCommand.set_max_players(numberPlayers); + mainClient->sendCommand(mainClient->prepareRoomCommand(createCommand, 0)); +} + +void MainWindow::actWatchReplay() +{ + QFileDialog dlg(this, tr("Load replay")); + dlg.setDirectory(settingsCache->getReplaysPath()); + dlg.setNameFilters(QStringList() << QObject::tr("Cockatrice replays (*.cor)")); + if (!dlg.exec()) + return; + + QString fileName = dlg.selectedFiles().at(0); + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return; + QByteArray buf = file.readAll(); + file.close(); + + GameReplay *replay = new GameReplay; + replay->ParseFromArray(buf.data(), buf.size()); + + tabSupervisor->openReplay(replay); } void MainWindow::localGameEnded() { - delete localServer; - localServer = 0; - - aConnect->setEnabled(true); - aSinglePlayer->setEnabled(true); + delete localServer; + localServer = 0; + + aConnect->setEnabled(true); + aSinglePlayer->setEnabled(true); } void MainWindow::actDeckEditor() { - WndDeckEditor *deckEditor = new WndDeckEditor(this); - deckEditor->show(); + tabSupervisor->addDeckEditorTab(0); } void MainWindow::actFullScreen(bool checked) { - if (checked) - setWindowState(windowState() | Qt::WindowFullScreen); - else - setWindowState(windowState() & ~Qt::WindowFullScreen); + if (checked) + setWindowState(windowState() | Qt::WindowFullScreen); + else + setWindowState(windowState() & ~Qt::WindowFullScreen); } void MainWindow::actSettings() { - DlgSettings dlg(this); - dlg.exec(); + DlgSettings dlg(this); + dlg.exec(); } void MainWindow::actExit() { - close(); + close(); } void MainWindow::actAbout() { - QMessageBox::about(this, tr("About Cockatrice"), QString( - "Cockatrice
" - + tr("Version %1").arg(versionString) - + "


" + tr("Authors:") + "
Max-Wilhelm Bruker
Marcus Schütz

" - + "" + tr("Translators:") + "
" - + tr("Spanish:") + " Víctor Martínez
" - + tr("Portugese (Portugal):") + " Milton Gonçalves
" - + tr("Portugese (Brazil):") + " Thiago Queiroz
" - + tr("French:") + " Yannick Hammer, Arnaud Faes
" - + tr("Japanese:") + " Nagase Task
" - + tr("Russian:") + " Alexander Davidov
" - + tr("Czech:") + " Ondřej Trhoň
" -// + tr("Slovak:") + " Ganjalf Rendy
" - + tr("Italian:") + " Luigi Sciolla
" - )); + QMessageBox::about(this, tr("About Cockatrice"), QString( + "Cockatrice
" + + tr("Version %1").arg(VERSION_STRING) + + "


" + tr("Authors:") + "
Max-Wilhelm Bruker
Marcus Schütz

" + + "" + tr("Translators:") + "
" + + tr("Spanish:") + " Víctor Martínez
" + + tr("Portugese (Portugal):") + " Milton Gonçalves
" + + tr("Portugese (Brazil):") + " Thiago Queiroz
" + + tr("French:") + " Yannick Hammer, Arnaud Faes
" + + tr("Japanese:") + " Nagase Task
" + + tr("Russian:") + " Alexander Davidov
" + + tr("Italian:") + " Luigi Sciolla
" + + tr("Swedish:") + " Jessica Dahl
" + )); } void MainWindow::serverTimeout() { - QMessageBox::critical(this, tr("Error"), tr("Server timeout")); + QMessageBox::critical(this, tr("Error"), tr("Server timeout")); } -void MainWindow::serverError(ResponseCode r) +void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime) { - switch (r) { - case RespWrongPassword: QMessageBox::critical(this, tr("Error"), tr("Invalid login data.")); break; - case RespWouldOverwriteOldSession: QMessageBox::critical(this, tr("Error"), tr("There is already an active session using this user name.\nPlease close that session first and re-login.")); break; - default: ; - } + switch (r) { + case Response::RespWrongPassword: + QMessageBox::critical(this, tr("Error"), tr("Incorrect username or password. Please check your authentication information and try again.")); + break; + case Response::RespWouldOverwriteOldSession: + QMessageBox::critical(this, tr("Error"), tr("There is already an active session using this user name.\nPlease close that session first and re-login.")); + break; + case Response::RespUserIsBanned: { + QString bannedStr; + if (endTime) + bannedStr = tr("You are banned until %1.").arg(QDateTime::fromTime_t(endTime).toString()); + else + bannedStr = tr("You are banned indefinitely."); + if (!reasonStr.isEmpty()) + bannedStr.append("\n\n" + reasonStr); + + QMessageBox::critical(this, tr("Error"), bannedStr); + break; + } + case Response::RespUsernameInvalid: + QMessageBox::critical(this, tr("Error"), tr("Invalid username.")); + break; + case Response::RespRegistrationRequired: + QMessageBox::critical(this, tr("Error"), tr("This server requires user registration.")); + break; + default: + QMessageBox::critical(this, tr("Error"), tr("Unknown login error: %1").arg(static_cast(r))); + } } void MainWindow::socketError(const QString &errorStr) { - QMessageBox::critical(this, tr("Error"), tr("Socket error: %1").arg(errorStr)); + QMessageBox::critical(this, tr("Error"), tr("Socket error: %1").arg(errorStr)); } void MainWindow::protocolVersionMismatch(int localVersion, int remoteVersion) { - if (localVersion > remoteVersion) - QMessageBox::critical(this, tr("Error"), tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server.\nLocal version is %1, remote version is %2.").arg(localVersion).arg(remoteVersion)); - else - QMessageBox::critical(this, tr("Error"), tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\nLocal version is %1, remote version is %2.").arg(localVersion).arg(remoteVersion)); + if (localVersion > remoteVersion) + QMessageBox::critical(this, tr("Error"), tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server.\nLocal version is %1, remote version is %2.").arg(localVersion).arg(remoteVersion)); + else + QMessageBox::critical(this, tr("Error"), tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\nLocal version is %1, remote version is %2.").arg(localVersion).arg(remoteVersion)); } void MainWindow::setClientStatusTitle() { - switch (client->getStatus()) { - case StatusConnecting: setWindowTitle(appName + " - " + tr("Connecting to %1...").arg(client->peerName())); break; - case StatusDisconnected: setWindowTitle(appName + " - " + tr("Disconnected")); break; - case StatusLoggedIn: setWindowTitle(appName + " - " + tr("Logged in at %1").arg(client->peerName())); break; - default: setWindowTitle(appName); - } + switch (client->getStatus()) { + case StatusConnecting: setWindowTitle(appName + " - " + tr("Connecting to %1...").arg(client->peerName())); break; + case StatusDisconnected: setWindowTitle(appName + " - " + tr("Disconnected")); break; + case StatusLoggingIn: setWindowTitle(appName + " - " + tr("Connected, logging in at %1").arg(client->peerName())); break; + case StatusLoggedIn: setWindowTitle(appName + " - " + tr("Logged in at %1").arg(client->peerName())); break; + default: setWindowTitle(appName); + } } void MainWindow::retranslateUi() { - setClientStatusTitle(); - - aConnect->setText(tr("&Connect...")); - aDisconnect->setText(tr("&Disconnect")); - aSinglePlayer->setText(tr("Start &local game...")); - aDeckEditor->setText(tr("&Deck editor")); - aFullScreen->setText(tr("&Full screen")); - aFullScreen->setShortcut(tr("Ctrl+F")); - aSettings->setText(tr("&Settings...")); - aExit->setText(tr("&Exit")); - - cockatriceMenu->setTitle(tr("&Cockatrice")); - - aAbout->setText(tr("&About Cockatrice")); - helpMenu->setTitle(tr("&Help")); - - tabSupervisor->retranslateUi(); + setClientStatusTitle(); + + aConnect->setText(tr("&Connect...")); + aDisconnect->setText(tr("&Disconnect")); + aSinglePlayer->setText(tr("Start &local game...")); + aWatchReplay->setText(tr("&Watch replay...")); + aDeckEditor->setText(tr("&Deck editor")); + aFullScreen->setText(tr("&Full screen")); + aFullScreen->setShortcut(tr("Ctrl+F")); + aSettings->setText(tr("&Settings...")); + aExit->setText(tr("&Exit")); + + cockatriceMenu->setTitle(tr("&Cockatrice")); + + aAbout->setText(tr("&About Cockatrice")); + helpMenu->setTitle(tr("&Help")); + + tabSupervisor->retranslateUi(); } void MainWindow::createActions() { - aConnect = new QAction(this); - connect(aConnect, SIGNAL(triggered()), this, SLOT(actConnect())); - aDisconnect = new QAction(this); - aDisconnect->setEnabled(false); - connect(aDisconnect, SIGNAL(triggered()), this, SLOT(actDisconnect())); - aSinglePlayer = new QAction(this); - connect(aSinglePlayer, SIGNAL(triggered()), this, SLOT(actSinglePlayer())); - aDeckEditor = new QAction(this); - connect(aDeckEditor, SIGNAL(triggered()), this, SLOT(actDeckEditor())); - aFullScreen = new QAction(this); - aFullScreen->setCheckable(true); - connect(aFullScreen, SIGNAL(toggled(bool)), this, SLOT(actFullScreen(bool))); - aSettings = new QAction(this); - connect(aSettings, SIGNAL(triggered()), this, SLOT(actSettings())); - aExit = new QAction(this); - connect(aExit, SIGNAL(triggered()), this, SLOT(actExit())); - - aAbout = new QAction(this); - connect(aAbout, SIGNAL(triggered()), this, SLOT(actAbout())); + aConnect = new QAction(this); + connect(aConnect, SIGNAL(triggered()), this, SLOT(actConnect())); + aDisconnect = new QAction(this); + aDisconnect->setEnabled(false); + connect(aDisconnect, SIGNAL(triggered()), this, SLOT(actDisconnect())); + aSinglePlayer = new QAction(this); + connect(aSinglePlayer, SIGNAL(triggered()), this, SLOT(actSinglePlayer())); + aWatchReplay = new QAction(this); + connect(aWatchReplay, SIGNAL(triggered()), this, SLOT(actWatchReplay())); + aDeckEditor = new QAction(this); + connect(aDeckEditor, SIGNAL(triggered()), this, SLOT(actDeckEditor())); + aFullScreen = new QAction(this); + aFullScreen->setCheckable(true); + connect(aFullScreen, SIGNAL(toggled(bool)), this, SLOT(actFullScreen(bool))); + aSettings = new QAction(this); + connect(aSettings, SIGNAL(triggered()), this, SLOT(actSettings())); + aExit = new QAction(this); + connect(aExit, SIGNAL(triggered()), this, SLOT(actExit())); + + aAbout = new QAction(this); + connect(aAbout, SIGNAL(triggered()), this, SLOT(actAbout())); } void MainWindow::createMenus() { - cockatriceMenu = menuBar()->addMenu(QString()); - cockatriceMenu->addAction(aConnect); - cockatriceMenu->addAction(aDisconnect); - cockatriceMenu->addAction(aSinglePlayer); - cockatriceMenu->addSeparator(); - cockatriceMenu->addAction(aDeckEditor); - cockatriceMenu->addSeparator(); - cockatriceMenu->addAction(aFullScreen); - cockatriceMenu->addSeparator(); - cockatriceMenu->addAction(aSettings); - cockatriceMenu->addSeparator(); - cockatriceMenu->addAction(aExit); - - helpMenu = menuBar()->addMenu(QString()); - helpMenu->addAction(aAbout); + cockatriceMenu = menuBar()->addMenu(QString()); + cockatriceMenu->addAction(aConnect); + cockatriceMenu->addAction(aDisconnect); + cockatriceMenu->addAction(aSinglePlayer); + cockatriceMenu->addAction(aWatchReplay); + cockatriceMenu->addSeparator(); + cockatriceMenu->addAction(aDeckEditor); + cockatriceMenu->addSeparator(); + cockatriceMenu->addAction(aFullScreen); + cockatriceMenu->addSeparator(); + cockatriceMenu->addAction(aSettings); + cockatriceMenu->addSeparator(); + cockatriceMenu->addAction(aExit); + + helpMenu = menuBar()->addMenu(QString()); + helpMenu->addAction(aAbout); } MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), tabMenu(0), localServer(0) + : QMainWindow(parent), localServer(0) { - QPixmapCache::setCacheLimit(200000); + QPixmapCache::setCacheLimit(200000); - client = new RemoteClient(this); - connect(client, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed *)), this, SLOT(processConnectionClosedEvent(Event_ConnectionClosed *))); - connect(client, SIGNAL(serverShutdownEventReceived(Event_ServerShutdown *)), this, SLOT(processServerShutdownEvent(Event_ServerShutdown *))); - connect(client, SIGNAL(serverError(ResponseCode)), this, SLOT(serverError(ResponseCode))); - connect(client, SIGNAL(socketError(const QString &)), this, SLOT(socketError(const QString &))); - connect(client, SIGNAL(serverTimeout()), this, SLOT(serverTimeout())); - connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus))); - connect(client, SIGNAL(protocolVersionMismatch(int, int)), this, SLOT(protocolVersionMismatch(int, int))); - connect(client, SIGNAL(userInfoChanged(ServerInfo_User *)), this, SLOT(userInfoReceived(ServerInfo_User *))); + client = new RemoteClient; + connect(client, SIGNAL(connectionClosedEventReceived(const Event_ConnectionClosed &)), this, SLOT(processConnectionClosedEvent(const Event_ConnectionClosed &))); + connect(client, SIGNAL(serverShutdownEventReceived(const Event_ServerShutdown &)), this, SLOT(processServerShutdownEvent(const Event_ServerShutdown &))); + connect(client, SIGNAL(loginError(Response::ResponseCode, QString, quint32)), this, SLOT(loginError(Response::ResponseCode, QString, quint32))); + connect(client, SIGNAL(socketError(const QString &)), this, SLOT(socketError(const QString &))); + connect(client, SIGNAL(serverTimeout()), this, SLOT(serverTimeout())); + connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus))); + connect(client, SIGNAL(protocolVersionMismatch(int, int)), this, SLOT(protocolVersionMismatch(int, int))); + connect(client, SIGNAL(userInfoChanged(const ServerInfo_User &)), this, SLOT(userInfoReceived(const ServerInfo_User &)), Qt::BlockingQueuedConnection); + + clientThread = new QThread(this); + client->moveToThread(clientThread); + clientThread->start(); - tabSupervisor = new TabSupervisor; - connect(tabSupervisor, SIGNAL(setMenu(QMenu *)), this, SLOT(updateTabMenu(QMenu *))); - connect(tabSupervisor, SIGNAL(localGameEnded()), this, SLOT(localGameEnded())); - - setCentralWidget(tabSupervisor); + createActions(); + createMenus(); + + tabSupervisor = new TabSupervisor(client); + connect(tabSupervisor, SIGNAL(setMenu(QList)), this, SLOT(updateTabMenu(QList))); + connect(tabSupervisor, SIGNAL(localGameEnded()), this, SLOT(localGameEnded())); + tabSupervisor->addDeckEditorTab(0); + + setCentralWidget(tabSupervisor); - createActions(); - createMenus(); - - retranslateUi(); - - resize(900, 700); + retranslateUi(); + + resize(900, 700); + restoreGeometry(settingsCache->getMainWindowGeometry()); + aFullScreen->setChecked(windowState() & Qt::WindowFullScreen); +} + +MainWindow::~MainWindow() +{ + client->deleteLater(); + clientThread->wait(); } void MainWindow::closeEvent(QCloseEvent *event) { - if (tabSupervisor->getGameCount()) { - if (QMessageBox::question(this, tr("Are you sure?"), tr("There are still open games. Are you sure you want to quit?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { - event->ignore(); - return; - } - } - event->accept(); - delete tabSupervisor; + if (tabSupervisor->getGameCount()) { + if (QMessageBox::question(this, tr("Are you sure?"), tr("There are still open games. Are you sure you want to quit?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { + event->ignore(); + return; + } + } + event->accept(); + settingsCache->setMainWindowGeometry(saveGeometry()); + delete tabSupervisor; } void MainWindow::changeEvent(QEvent *event) { - if (event->type() == QEvent::LanguageChange) - retranslateUi(); - QMainWindow::changeEvent(event); + if (event->type() == QEvent::LanguageChange) + retranslateUi(); + QMainWindow::changeEvent(event); } diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 4d8b45d60..07ad1d712 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -22,55 +22,61 @@ #include #include "abstractclient.h" -#include "protocol_datastructures.h" +#include "pb/response.pb.h" class TabSupervisor; class RemoteClient; class LocalClient; class LocalServer; class ServerInfo_User; +class QThread; class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT private slots: - void updateTabMenu(QMenu *menu); - void statusChanged(ClientStatus _status); - void processConnectionClosedEvent(Event_ConnectionClosed *event); - void processServerShutdownEvent(Event_ServerShutdown *event); - void serverTimeout(); - void serverError(ResponseCode r); - void socketError(const QString &errorStr); - void protocolVersionMismatch(int localVersion, int remoteVersion); - void userInfoReceived(ServerInfo_User *userInfo); - void localGameEnded(); + void updateTabMenu(const QList &newMenuList); + void statusChanged(ClientStatus _status); + void processConnectionClosedEvent(const Event_ConnectionClosed &event); + void processServerShutdownEvent(const Event_ServerShutdown &event); + void serverTimeout(); + void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime); + void socketError(const QString &errorStr); + void protocolVersionMismatch(int localVersion, int remoteVersion); + void userInfoReceived(const ServerInfo_User &userInfo); + void localGameEnded(); - void actConnect(); - void actDisconnect(); - void actSinglePlayer(); - void actDeckEditor(); - void actFullScreen(bool checked); - void actSettings(); - void actExit(); - - void actAbout(); + void actConnect(); + void actDisconnect(); + void actSinglePlayer(); + void actWatchReplay(); + void actDeckEditor(); + void actFullScreen(bool checked); + void actSettings(); + void actExit(); + + void actAbout(); private: - static const QString appName; - void setClientStatusTitle(); - void retranslateUi(); - void createActions(); - void createMenus(); - QMenu *cockatriceMenu, *tabMenu, *helpMenu; - QAction *aConnect, *aDisconnect, *aSinglePlayer, *aDeckEditor, *aFullScreen, *aSettings, *aExit, - *aAbout; - TabSupervisor *tabSupervisor; + static const QString appName; + void setClientStatusTitle(); + void retranslateUi(); + void createActions(); + void createMenus(); + QList tabMenus; + QMenu *cockatriceMenu, *helpMenu; + QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit, + *aAbout; + TabSupervisor *tabSupervisor; - RemoteClient *client; - LocalServer *localServer; + RemoteClient *client; + QThread *clientThread; + + LocalServer *localServer; public: - MainWindow(QWidget *parent = 0); + MainWindow(QWidget *parent = 0); + ~MainWindow(); protected: - void closeEvent(QCloseEvent *event); - void changeEvent(QEvent *event); + void closeEvent(QCloseEvent *event); + void changeEvent(QEvent *event); }; #endif diff --git a/cockatrice/src/window_sets.cpp b/cockatrice/src/window_sets.cpp index dd6bcd8a4..668b36e0e 100644 --- a/cockatrice/src/window_sets.cpp +++ b/cockatrice/src/window_sets.cpp @@ -5,29 +5,29 @@ #include WndSets::WndSets(QWidget *parent) - : QMainWindow(parent) + : QMainWindow(parent) { - model = new SetsModel(db, this); - view = new QTreeView; - view->setModel(model); - view->setAlternatingRowColors(true); - view->setUniformRowHeights(true); - view->setAllColumnsShowFocus(true); + model = new SetsModel(db, this); + view = new QTreeView; + view->setModel(model); + view->setAlternatingRowColors(true); + view->setUniformRowHeights(true); + view->setAllColumnsShowFocus(true); - view->setDragEnabled(true); - view->setAcceptDrops(true); - view->setDropIndicatorShown(true); - view->setDragDropMode(QAbstractItemView::InternalMove); + view->setDragEnabled(true); + view->setAcceptDrops(true); + view->setDropIndicatorShown(true); + view->setDragDropMode(QAbstractItemView::InternalMove); - QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->addWidget(view); + QHBoxLayout *mainLayout = new QHBoxLayout; + mainLayout->addWidget(view); - QWidget *centralWidget = new QWidget; - centralWidget->setLayout(mainLayout); - setCentralWidget(centralWidget); + QWidget *centralWidget = new QWidget; + centralWidget->setLayout(mainLayout); + setCentralWidget(centralWidget); - setWindowTitle(tr("Edit sets")); - resize(400, 400); + setWindowTitle(tr("Edit sets")); + resize(400, 400); } WndSets::~WndSets() diff --git a/cockatrice/src/window_sets.h b/cockatrice/src/window_sets.h index 4c10d249a..ce7cb6228 100644 --- a/cockatrice/src/window_sets.h +++ b/cockatrice/src/window_sets.h @@ -8,13 +8,13 @@ class QTreeView; class CardDatabase; class WndSets : public QMainWindow { - Q_OBJECT + Q_OBJECT private: - SetsModel *model; - QTreeView *view; + SetsModel *model; + QTreeView *view; public: - WndSets(QWidget *parent = 0); - ~WndSets(); + WndSets(QWidget *parent = 0); + ~WndSets(); }; #endif diff --git a/cockatrice/src/zoneviewwidget.cpp b/cockatrice/src/zoneviewwidget.cpp index a39ae0441..9fd132725 100644 --- a/cockatrice/src/zoneviewwidget.cpp +++ b/cockatrice/src/zoneviewwidget.cpp @@ -6,168 +6,210 @@ #include #include #include +#include #include "zoneviewwidget.h" #include "carditem.h" #include "zoneviewzone.h" #include "player.h" #include "gamescene.h" -#include "protocol_items.h" #include "settingscache.h" #include "gamescene.h" +#include "pb/command_stop_dump_zone.pb.h" +#include "pb/command_shuffle.pb.h" + TitleLabel::TitleLabel() - : QGraphicsWidget(), text(" ") + : QGraphicsWidget(), text(" ") { - setAcceptHoverEvents(true); + setAcceptHoverEvents(true); } void TitleLabel::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush windowBrush = palette().window(); - windowBrush.setColor(windowBrush.color().darker(150)); - painter->fillRect(boundingRect(), windowBrush); - painter->drawText(boundingRect(), Qt::AlignLeft | Qt::AlignVCenter, text); + QBrush windowBrush = palette().window(); + windowBrush.setColor(windowBrush.color().darker(150)); + painter->fillRect(boundingRect(), windowBrush); + painter->drawText(boundingRect(), Qt::AlignLeft | Qt::AlignVCenter, text); } QSizeF TitleLabel::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const { - QFont f; - QFontMetrics fm(f); - if (which == Qt::MaximumSize) - return QSizeF(constraint.width(), fm.size(Qt::TextSingleLine, text).height() + 10); - else - return fm.size(Qt::TextSingleLine, text) + QSizeF(10, 10); + QFont f; + QFontMetrics fm(f); + if (which == Qt::MaximumSize) + return QSizeF(constraint.width(), fm.size(Qt::TextSingleLine, text).height() + 10); + else + return fm.size(Qt::TextSingleLine, text) + QSizeF(10, 10); } void TitleLabel::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - buttonDownPos = static_cast(scene())->getViewTransform().inverted().map(event->pos()); - event->accept(); - } else - event->ignore(); + if (event->button() == Qt::LeftButton) { + buttonDownPos = static_cast(scene())->getViewTransform().inverted().map(event->pos()); + event->accept(); + } else + event->ignore(); } void TitleLabel::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - emit mouseMoved(event->scenePos() - buttonDownPos); + emit mouseMoved(event->scenePos() - buttonDownPos); } -ZoneViewWidget::ZoneViewWidget(Player *_player, CardZone *_origZone, int numberCards, bool _revealZone, const QList &cardList) - : QGraphicsWidget(0, Qt::Tool | Qt::FramelessWindowHint), player(_player) +ZoneViewWidget::ZoneViewWidget(Player *_player, CardZone *_origZone, int numberCards, bool _revealZone, bool _writeableRevealZone, const QList &cardList) + : QGraphicsWidget(0, Qt::Tool | Qt::FramelessWindowHint), player(_player) { - setAcceptHoverEvents(true); - setAttribute(Qt::WA_DeleteOnClose); - setZValue(2000000006); - setFlag(ItemIgnoresTransformations); - - QGraphicsLinearLayout *hbox = new QGraphicsLinearLayout(Qt::Horizontal); - titleLabel = new TitleLabel; - connect(titleLabel, SIGNAL(mouseMoved(QPointF)), this, SLOT(moveWidget(QPointF))); - closeButton = new QPushButton("X"); - connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); - closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - QGraphicsProxyWidget *closeButtonProxy = new QGraphicsProxyWidget; - closeButtonProxy->setWidget(closeButton); - - hbox->addItem(titleLabel); - hbox->addItem(closeButtonProxy); - QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical); - - vbox->addItem(hbox); - - if (numberCards < 0) { - sortByNameCheckBox = new QCheckBox; - QGraphicsProxyWidget *sortByNameProxy = new QGraphicsProxyWidget; - sortByNameProxy->setWidget(sortByNameCheckBox); - vbox->addItem(sortByNameProxy); + setAcceptHoverEvents(true); + setAttribute(Qt::WA_DeleteOnClose); + setZValue(2000000006); + setFlag(ItemIgnoresTransformations); + + QGraphicsLinearLayout *hbox = new QGraphicsLinearLayout(Qt::Horizontal); + titleLabel = new TitleLabel; + connect(titleLabel, SIGNAL(mouseMoved(QPointF)), this, SLOT(moveWidget(QPointF))); + closeButton = new QPushButton("X"); + connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); + closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + QGraphicsProxyWidget *closeButtonProxy = new QGraphicsProxyWidget; + closeButtonProxy->setWidget(closeButton); + + hbox->addItem(titleLabel); + hbox->addItem(closeButtonProxy); + QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical); + + vbox->addItem(hbox); + + if (numberCards < 0) { + sortByNameCheckBox = new QCheckBox; + QGraphicsProxyWidget *sortByNameProxy = new QGraphicsProxyWidget; + sortByNameProxy->setWidget(sortByNameCheckBox); + vbox->addItem(sortByNameProxy); - sortByTypeCheckBox = new QCheckBox; - QGraphicsProxyWidget *sortByTypeProxy = new QGraphicsProxyWidget; - sortByTypeProxy->setWidget(sortByTypeCheckBox); - vbox->addItem(sortByTypeProxy); - } else { - sortByNameCheckBox = 0; - sortByTypeCheckBox = 0; - } - - if (_origZone->getIsShufflable() && (numberCards == -1)) { - shuffleCheckBox = new QCheckBox; - shuffleCheckBox->setChecked(true); - QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget; - shuffleProxy->setWidget(shuffleCheckBox); - vbox->addItem(shuffleProxy); - } else - shuffleCheckBox = 0; - - extraHeight = vbox->sizeHint(Qt::PreferredSize).height(); - resize(150, 150); + sortByTypeCheckBox = new QCheckBox; + QGraphicsProxyWidget *sortByTypeProxy = new QGraphicsProxyWidget; + sortByTypeProxy->setWidget(sortByTypeCheckBox); + vbox->addItem(sortByTypeProxy); + } else { + sortByNameCheckBox = 0; + sortByTypeCheckBox = 0; + } + + if (_origZone->getIsShufflable() && (numberCards == -1)) { + shuffleCheckBox = new QCheckBox; + shuffleCheckBox->setChecked(true); + QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget; + shuffleProxy->setWidget(shuffleCheckBox); + vbox->addItem(shuffleProxy); + } else + shuffleCheckBox = 0; + + extraHeight = vbox->sizeHint(Qt::PreferredSize).height(); + resize(150, 150); + + QGraphicsLinearLayout *zoneHBox = new QGraphicsLinearLayout(Qt::Horizontal); + + zoneContainer = new QGraphicsWidget(this); + zoneContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + zoneContainer->setFlag(QGraphicsItem::ItemClipsChildrenToShape); + zoneHBox->addItem(zoneContainer); + + scrollBar = new QScrollBar(Qt::Vertical); + scrollBar->setMinimum(0); + scrollBar->setSingleStep(50); + connect(scrollBar, SIGNAL(valueChanged(int)), this, SLOT(handleScrollBarChange(int))); + QGraphicsProxyWidget *scrollBarProxy = new QGraphicsProxyWidget; + scrollBarProxy->setWidget(scrollBar); + zoneHBox->addItem(scrollBarProxy); + + vbox->addItem(zoneHBox); + + zone = new ZoneViewZone(player, _origZone, numberCards, _revealZone, _writeableRevealZone, zoneContainer); + connect(zone, SIGNAL(wheelEventReceived(QGraphicsSceneWheelEvent *)), this, SLOT(handleWheelEvent(QGraphicsSceneWheelEvent *))); + + if (sortByNameCheckBox) { + connect(sortByNameCheckBox, SIGNAL(stateChanged(int)), zone, SLOT(setSortByName(int))); + connect(sortByTypeCheckBox, SIGNAL(stateChanged(int)), zone, SLOT(setSortByType(int))); + sortByNameCheckBox->setChecked(settingsCache->getZoneViewSortByName()); + sortByTypeCheckBox->setChecked(settingsCache->getZoneViewSortByType()); + } - zone = new ZoneViewZone(player, _origZone, numberCards, _revealZone, this); - vbox->addItem(zone); - - if (sortByNameCheckBox) { - connect(sortByNameCheckBox, SIGNAL(stateChanged(int)), zone, SLOT(setSortByName(int))); - connect(sortByTypeCheckBox, SIGNAL(stateChanged(int)), zone, SLOT(setSortByType(int))); - sortByNameCheckBox->setChecked(settingsCache->getZoneViewSortByName()); - sortByTypeCheckBox->setChecked(settingsCache->getZoneViewSortByType()); - } - - retranslateUi(); - setLayout(vbox); - - connect(zone, SIGNAL(optimumRectChanged()), this, SLOT(resizeToZoneContents())); - connect(zone, SIGNAL(beingDeleted()), this, SLOT(zoneDeleted())); - zone->initializeCards(cardList); + retranslateUi(); + setLayout(vbox); + + connect(zone, SIGNAL(optimumRectChanged()), this, SLOT(resizeToZoneContents())); + connect(zone, SIGNAL(beingDeleted()), this, SLOT(zoneDeleted())); + zone->initializeCards(cardList); } void ZoneViewWidget::retranslateUi() { - titleLabel->setText(zone->getTranslatedName(false, CaseNominative)); - if (sortByNameCheckBox) - sortByNameCheckBox->setText(tr("sort by name")); - if (sortByTypeCheckBox) - sortByTypeCheckBox->setText(tr("sort by type")); - if (shuffleCheckBox) - shuffleCheckBox->setText(tr("shuffle when closing")); + titleLabel->setText(zone->getTranslatedName(false, CaseNominative)); + if (sortByNameCheckBox) + sortByNameCheckBox->setText(tr("sort by name")); + if (sortByTypeCheckBox) + sortByTypeCheckBox->setText(tr("sort by type")); + if (shuffleCheckBox) + shuffleCheckBox->setText(tr("shuffle when closing")); } void ZoneViewWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - painter->fillRect(boundingRect(), palette().color(QPalette::Window)); - QGraphicsWidget::paint(painter, option, widget); + painter->fillRect(boundingRect(), palette().color(QPalette::Window)); + QGraphicsWidget::paint(painter, option, widget); } void ZoneViewWidget::moveWidget(QPointF scenePos) { - setPos(scenePos); + setPos(scenePos); } void ZoneViewWidget::resizeToZoneContents() { - QRectF zoneRect = zone->getOptimumRect(); - QSizeF newSize(qMax(QGraphicsWidget::layout()->effectiveSizeHint(Qt::MinimumSize, QSizeF()).width(), zoneRect.width() + 10), zoneRect.height() + extraHeight + 10); - setMaximumSize(newSize); - resize(newSize); - if (layout()) - layout()->invalidate(); + QRectF zoneRect = zone->getOptimumRect(); + qreal totalZoneHeight = zoneRect.height(); + if (zoneRect.height() > 500) + zoneRect.setHeight(500); + QSizeF newSize(qMax(QGraphicsWidget::layout()->effectiveSizeHint(Qt::MinimumSize, QSizeF()).width(), zoneRect.width() + scrollBar->width() + 10), zoneRect.height() + extraHeight + 10); + setMaximumSize(newSize); + resize(newSize); + + zone->setGeometry(QRectF(0, -scrollBar->value(), zoneContainer->size().width(), totalZoneHeight)); + scrollBar->setMaximum(totalZoneHeight - zoneRect.height()); + + if (layout()) + layout()->invalidate(); +} + +void ZoneViewWidget::handleWheelEvent(QGraphicsSceneWheelEvent *event) +{ + QWheelEvent wheelEvent(QPoint(), event->delta(), event->buttons(), event->modifiers(), event->orientation()); + scrollBar->event(&wheelEvent); +} + +void ZoneViewWidget::handleScrollBarChange(int value) +{ + zone->setY(-value); } void ZoneViewWidget::closeEvent(QCloseEvent *event) { - disconnect(zone, SIGNAL(beingDeleted()), this, 0); - if (zone->getNumberCards() != -2) - player->sendGameCommand(new Command_StopDumpZone(-1, player->getId(), zone->getName())); - if (shuffleCheckBox) - if (shuffleCheckBox->isChecked()) - player->sendGameCommand(new Command_Shuffle); - emit closePressed(this); - deleteLater(); - event->accept(); + disconnect(zone, SIGNAL(beingDeleted()), this, 0); + if (zone->getNumberCards() != -2) { + Command_StopDumpZone cmd; + cmd.set_player_id(player->getId()); + cmd.set_zone_name(zone->getName().toStdString()); + player->sendGameCommand(cmd); + } + if (shuffleCheckBox) + if (shuffleCheckBox->isChecked()) + player->sendGameCommand(Command_Shuffle()); + emit closePressed(this); + deleteLater(); + event->accept(); } void ZoneViewWidget::zoneDeleted() { - emit closePressed(this); - deleteLater(); + emit closePressed(this); + deleteLater(); } diff --git a/cockatrice/src/zoneviewwidget.h b/cockatrice/src/zoneviewwidget.h index f85851b81..2c89a571d 100644 --- a/cockatrice/src/zoneviewwidget.h +++ b/cockatrice/src/zoneviewwidget.h @@ -14,49 +14,53 @@ class QCheckBox; class GameScene; class ServerInfo_Card; class QGraphicsSceneMouseEvent; +class QGraphicsSceneWheelEvent; class TitleLabel : public QGraphicsWidget { - Q_OBJECT + Q_OBJECT private: - QString text; - QPointF buttonDownPos; + QString text; + QPointF buttonDownPos; public: - TitleLabel(); - void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); - void setText(const QString &_text) { text = _text; update(); } + TitleLabel(); + void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/); + void setText(const QString &_text) { text = _text; update(); } signals: - void mouseMoved(QPointF scenePos); + void mouseMoved(QPointF scenePos); protected: - QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const; - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); }; class ZoneViewWidget : public QGraphicsWidget { - Q_OBJECT + Q_OBJECT private: - ZoneViewZone *zone; - - TitleLabel *titleLabel; - QPushButton *closeButton; - QScrollBar *scrollBar; - QCheckBox *sortByNameCheckBox, *sortByTypeCheckBox, *shuffleCheckBox; - - int extraHeight; - Player *player; + ZoneViewZone *zone; + QGraphicsWidget *zoneContainer; + + TitleLabel *titleLabel; + QPushButton *closeButton; + QScrollBar *scrollBar; + QCheckBox *sortByNameCheckBox, *sortByTypeCheckBox, *shuffleCheckBox; + + int extraHeight; + Player *player; signals: - void closePressed(ZoneViewWidget *zv); + void closePressed(ZoneViewWidget *zv); private slots: - void resizeToZoneContents(); - void zoneDeleted(); - void moveWidget(QPointF scenePos); + void resizeToZoneContents(); + void handleWheelEvent(QGraphicsSceneWheelEvent *event); + void handleScrollBarChange(int value); + void zoneDeleted(); + void moveWidget(QPointF scenePos); public: - ZoneViewWidget(Player *_player, CardZone *_origZone, int numberCards = 0, bool _revealZone = false, const QList &cardList = QList()); - ZoneViewZone *getZone() const { return zone; } - void retranslateUi(); + ZoneViewWidget(Player *_player, CardZone *_origZone, int numberCards = 0, bool _revealZone = false, bool _writeableRevealZone = false, const QList &cardList = QList()); + ZoneViewZone *getZone() const { return zone; } + void retranslateUi(); protected: - void closeEvent(QCloseEvent *event); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void closeEvent(QCloseEvent *event); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); }; #endif diff --git a/cockatrice/src/zoneviewzone.cpp b/cockatrice/src/zoneviewzone.cpp index 1aed33b7e..e3b342358 100644 --- a/cockatrice/src/zoneviewzone.cpp +++ b/cockatrice/src/zoneviewzone.cpp @@ -1,156 +1,190 @@ #include #include +#include #include "zoneviewzone.h" #include "player.h" -#include "protocol_items.h" #include "carddragitem.h" +#include "carditem.h" +#include "pb/command_dump_zone.pb.h" +#include "pb/command_move_card.pb.h" +#include "pb/serverinfo_card.pb.h" +#include "pb/response_dump_zone.pb.h" +#include "pending_command.h" -ZoneViewZone::ZoneViewZone(Player *_p, CardZone *_origZone, int _numberCards, bool _revealZone, QGraphicsItem *parent) - : SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0), numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone), sortByName(false), sortByType(false) +ZoneViewZone::ZoneViewZone(Player *_p, CardZone *_origZone, int _numberCards, bool _revealZone, bool _writeableRevealZone, QGraphicsItem *parent) + : SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0), numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone), writeableRevealZone(_writeableRevealZone), sortByName(false), sortByType(false) { - if (!revealZone) - origZone->setView(this); + if (!(revealZone && !writeableRevealZone)) + origZone->setView(this); } ZoneViewZone::~ZoneViewZone() { - emit beingDeleted(); - qDebug("ZoneViewZone destructor"); - if (!revealZone) - origZone->setView(NULL); + emit beingDeleted(); + qDebug("ZoneViewZone destructor"); + if (!(revealZone && !writeableRevealZone)) + origZone->setView(NULL); } QRectF ZoneViewZone::boundingRect() const { - return bRect; + return bRect; } -void ZoneViewZone::paint(QPainter */*painter*/, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/) +void ZoneViewZone::paint(QPainter * /*painter*/, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { } -void ZoneViewZone::initializeCards(const QList &cardList) +void ZoneViewZone::initializeCards(const QList &cardList) { - if (!cardList.isEmpty()) { - for (int i = 0; i < cardList.size(); ++i) - addCard(new CardItem(player, cardList[i]->getName(), cardList[i]->getId(), revealZone, this), false, i); - reorganizeCards(); - } else if (!origZone->contentsKnown()) { - Command_DumpZone *command = new Command_DumpZone(-1, player->getId(), name, numberCards); - connect(command, SIGNAL(finished(ProtocolResponse *)), this, SLOT(zoneDumpReceived(ProtocolResponse *))); - player->sendGameCommand(command); - } else { - const CardList &c = origZone->getCards(); - int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size()); - for (int i = 0; i < number; i++) { - CardItem *card = c.at(i); - addCard(new CardItem(player, card->getName(), card->getId(), revealZone, this), false, i); - } - reorganizeCards(); - } + if (!cardList.isEmpty()) { + for (int i = 0; i < cardList.size(); ++i) + addCard(new CardItem(player, QString::fromStdString(cardList[i]->name()), cardList[i]->id(), revealZone, this), false, i); + reorganizeCards(); + } else if (!origZone->contentsKnown()) { + Command_DumpZone cmd; + cmd.set_player_id(player->getId()); + cmd.set_zone_name(name.toStdString()); + cmd.set_number_cards(numberCards); + + PendingCommand *pend = player->prepareGameCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(zoneDumpReceived(const Response &))); + player->sendGameCommand(pend); + } else { + const CardList &c = origZone->getCards(); + int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size()); + for (int i = 0; i < number; i++) { + CardItem *card = c.at(i); + addCard(new CardItem(player, card->getName(), card->getId(), revealZone, this), false, i); + } + reorganizeCards(); + } } -void ZoneViewZone::zoneDumpReceived(ProtocolResponse *r) +void ZoneViewZone::zoneDumpReceived(const Response &r) { - Response_DumpZone *resp = qobject_cast(r); - if (!resp) - return; - - const QList &respCardList = resp->getZone()->getCardList(); - for (int i = 0; i < respCardList.size(); i++) { - CardItem *card = new CardItem(player, respCardList[i]->getName(), respCardList[i]->getId(), revealZone, this); - addCard(card, false, i); - } - - reorganizeCards(); + const Response_DumpZone &resp = r.GetExtension(Response_DumpZone::ext); + const int respCardListSize = resp.zone_info().card_list_size(); + for (int i = 0; i < respCardListSize; ++i) { + const ServerInfo_Card &cardInfo = resp.zone_info().card_list(i); + CardItem *card = new CardItem(player, QString::fromStdString(cardInfo.name()), cardInfo.id(), revealZone, this); + addCard(card, false, i); + } + + reorganizeCards(); } // Because of boundingRect(), this function must not be called before the zone was added to a scene. void ZoneViewZone::reorganizeCards() { - int cardCount = cards.size(); - if (!origZone->contentsKnown()) - for (int i = 0; i < cardCount; ++i) - cards[i]->setId(i); + int cardCount = cards.size(); + if (!origZone->contentsKnown()) + for (int i = 0; i < cardCount; ++i) + cards[i]->setId(i); - int cols = floor(sqrt((double) cardCount / 2)); - int rows = ceil((double) cardCount / cols); - if (rows < 1) - rows = 1; - if (minRows == 0) - minRows = rows; - else if (rows < minRows) { - rows = minRows; - cols = ceil((double) cardCount / minRows); - } - if (cols < 2) - cols = 2; - - qDebug() << "reorganizeCards: rows=" << rows << "cols=" << cols; + int cols = floor(sqrt((double) cardCount / 2)); + if (cols > 7) + cols = 7; + int rows = ceil((double) cardCount / cols); + if (rows < 1) + rows = 1; + if (minRows == 0) + minRows = rows; + else if (rows < minRows) { + rows = minRows; + cols = ceil((double) cardCount / minRows); + } + if (cols < 2) + cols = 2; + + qDebug() << "reorganizeCards: rows=" << rows << "cols=" << cols; - CardList cardsToDisplay(cards); - if (sortByName || sortByType) - cardsToDisplay.sort((sortByName ? CardList::SortByName : 0) | (sortByType ? CardList::SortByType : 0)); - - for (int i = 0; i < cardCount; i++) { - CardItem *c = cardsToDisplay.at(i); - qreal x = (i / rows) * CARD_WIDTH; - qreal y = (i % rows) * CARD_HEIGHT / 3; - c->setPos(x + 5, y + 5); - c->setRealZValue(i); - } + CardList cardsToDisplay(cards); + if (sortByName || sortByType) + cardsToDisplay.sort((sortByName ? CardList::SortByName : 0) | (sortByType ? CardList::SortByType : 0)); + + for (int i = 0; i < cardCount; i++) { + CardItem *c = cardsToDisplay.at(i); + qreal x = (i / rows) * CARD_WIDTH; + qreal y = (i % rows) * CARD_HEIGHT / 3; + c->setPos(x + 5, y + 5); + c->setRealZValue(i); + } - optimumRect = QRectF(0, 0, qMax(cols, 3) * CARD_WIDTH + 10, ((rows - 1) * CARD_HEIGHT) / 3 + CARD_HEIGHT + 10); - updateGeometry(); - emit optimumRectChanged(); + optimumRect = QRectF(0, 0, qMax(cols, 3) * CARD_WIDTH + 10, ((rows - 1) * CARD_HEIGHT) / 3 + CARD_HEIGHT + 10); + updateGeometry(); + emit optimumRectChanged(); } void ZoneViewZone::setSortByName(int _sortByName) { - sortByName = _sortByName; - reorganizeCards(); + sortByName = _sortByName; + reorganizeCards(); } void ZoneViewZone::setSortByType(int _sortByType) { - sortByType = _sortByType; - reorganizeCards(); + sortByType = _sortByType; + reorganizeCards(); } void ZoneViewZone::addCardImpl(CardItem *card, int x, int /*y*/) { - cards.insert(x, card); - card->setParentItem(this); - card->update(); + cards.insert(x, card); + card->setParentItem(this); + card->update(); } void ZoneViewZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &/*dropPoint*/) { - QList idList; - for (int i = 0; i < dragItems.size(); ++i) - idList.append(new CardToMove(dragItems[i]->getId())); - - player->sendGameCommand(new Command_MoveCard(-1, startZone->getName(), idList, player->getId(), getName(), 0, 0)); + Command_MoveCard cmd; + cmd.set_start_player_id(startZone->getPlayer()->getId()); + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_target_player_id(player->getId()); + cmd.set_target_zone(getName().toStdString()); + cmd.set_x(0); + cmd.set_y(0); + + for (int i = 0; i < dragItems.size(); ++i) + cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId()); + + player->sendGameCommand(cmd); } void ZoneViewZone::removeCard(int position) { - if (position >= cards.size()) - return; + if (position >= cards.size()) + return; - CardItem *card = cards.takeAt(position); - card->deleteLater(); - reorganizeCards(); + CardItem *card = cards.takeAt(position); + card->deleteLater(); + reorganizeCards(); } void ZoneViewZone::setGeometry(const QRectF &rect) { - prepareGeometryChange(); - setPos(rect.x(), rect.y()); - bRect = QRectF(0, 0, rect.width(), rect.height()); + prepareGeometryChange(); + setPos(rect.x(), rect.y()); + bRect = QRectF(0, 0, rect.width(), rect.height()); } QSizeF ZoneViewZone::sizeHint(Qt::SizeHint /*which*/, const QSizeF & /*constraint*/) const { - return optimumRect.size(); + return optimumRect.size(); +} + +void ZoneViewZone::setWriteableRevealZone(bool _writeableRevealZone) +{ + if (writeableRevealZone && !_writeableRevealZone) + origZone->setView(this); + else if (!writeableRevealZone && _writeableRevealZone) + origZone->setView(NULL); + + writeableRevealZone = _writeableRevealZone; + +} + +void ZoneViewZone::wheelEvent(QGraphicsSceneWheelEvent *event) +{ + emit wheelEventReceived(event); } diff --git a/cockatrice/src/zoneviewzone.h b/cockatrice/src/zoneviewzone.h index 2c6265ec2..43134c78c 100644 --- a/cockatrice/src/zoneviewzone.h +++ b/cockatrice/src/zoneviewzone.h @@ -5,40 +5,47 @@ #include class ZoneViewWidget; -class ProtocolResponse; +class Response; class ServerInfo_Card; +class QGraphicsSceneWheelEvent; class ZoneViewZone : public SelectZone, public QGraphicsLayoutItem { - Q_OBJECT + Q_OBJECT + Q_INTERFACES(QGraphicsLayoutItem) private: - QRectF bRect, optimumRect; - int minRows, numberCards; - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); - CardZone *origZone; - bool revealZone; - bool sortByName, sortByType; + QRectF bRect, optimumRect; + int minRows, numberCards; + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); + CardZone *origZone; + bool revealZone, writeableRevealZone; + bool sortByName, sortByType; public: - ZoneViewZone(Player *_p, CardZone *_origZone, int _numberCards = -1, bool _revealZone = false, QGraphicsItem *parent = 0); - ~ZoneViewZone(); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); - void initializeCards(const QList &cardList = QList()); - void removeCard(int position); - int getNumberCards() const { return numberCards; } - void setGeometry(const QRectF &rect); - QRectF getOptimumRect() const { return optimumRect; } + ZoneViewZone(Player *_p, CardZone *_origZone, int _numberCards = -1, bool _revealZone = false, bool _writeableRevealZone = false, QGraphicsItem *parent = 0); + ~ZoneViewZone(); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void reorganizeCards(); + void initializeCards(const QList &cardList = QList()); + void removeCard(int position); + int getNumberCards() const { return numberCards; } + void setGeometry(const QRectF &rect); + QRectF getOptimumRect() const { return optimumRect; } + bool getRevealZone() const { return revealZone; } + bool getWriteableRevealZone() const { return writeableRevealZone; } + void setWriteableRevealZone(bool _writeableRevealZone); public slots: - void setSortByName(int _sortByName); - void setSortByType(int _sortByType); + void setSortByName(int _sortByName); + void setSortByType(int _sortByType); private slots: - void zoneDumpReceived(ProtocolResponse *r); + void zoneDumpReceived(const Response &r); signals: - void beingDeleted(); - void optimumRectChanged(); + void beingDeleted(); + void optimumRectChanged(); + void wheelEventReceived(QGraphicsSceneWheelEvent *event); protected: - void addCardImpl(CardItem *card, int x, int y); - QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + void addCardImpl(CardItem *card, int x, int y); + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; + void wheelEvent(QGraphicsSceneWheelEvent *event); }; #endif diff --git a/cockatrice/translations/cockatrice_cs.ts b/cockatrice/translations/cockatrice_cs.ts index 20447575e..bfd7fb3be 100644 --- a/cockatrice/translations/cockatrice_cs.ts +++ b/cockatrice/translations/cockatrice_cs.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... &Nastavit žeton... - + Ctrl+L Ctrl+L - + F11 F11 - + F12 F12 - + Set counter Nastavit žeton - + New value for counter '%1': Nová hodnota pro žeton '%1': @@ -37,91 +37,111 @@ AppearanceSettingsPage - + Zone background pictures Pozadí zón - Path to hand background: - Cesta k pozadí ruky: + Cesta k pozadí ruky: - Path to stack background: - Cesta k pozadí stacku: + Cesta k pozadí stacku: - Path to table background: - Cesta k pozadí stolu: + Cesta k pozadí stolu: - Path to player info background: - Cesta k pozadí informací o uživateli: + Cesta k pozadí informací o uživateli: - Path to picture of card back: - Cesta k rubu karet: + Cesta k rubu karet: - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Card rendering Vykreslování karet - + Display card names on cards having a picture Zobrazit jména karet na kartách s obrázky - + Hand layout Rozvržení ruky - + Display hand horizontally (wastes space) Zobrazit ruku horizontálně (zabírá více místa) - + Table grid layout Rozložení herní mřížky - + Invert vertical coordinate Převrátit vertikální souřadnice - + Minimum player count for multi-column layout: - + Zone view layout Rozvržení zón - + Sort by name Seřadit dle jména - + Sort by type Seřadit dle typu - - - - - + + + + + Choose path Vyberte cestu @@ -135,78 +155,83 @@ Enter 0 for an indefinite ban. Hodnota 0 je pro ban bez omezení. - + ban &user name - + ban &IP address - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK &OK - + &Cancel - + Ban user from server - + Error Chyba - + You have to select a name-based or IP-based ban, or both. @@ -214,273 +239,263 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Jméno - + Sets Sady - + Mana cost Sesílací cena - + Card type Typ karty - + P/T S/O - CardInfoWidget + CardInfoText - - Hide card info - Skrýt informace o kartě + + Name: + Jméno: - + + Mana cost: + Sesílací cena: + + + + Card type: + Typ karty: + + + + P / T: + S / O: + + + + Loyalty: + + + + + CardInfoWidget + + Hide card info + Skrýt informace o kartě + + + Show card only Zobrazit pouze kartu - + Show text only Zobrazit pouze text - + Show full info Zobrazit všechny informace - + Name: Jméno: - + Mana cost: Sesílací cena: - + Card type: Typ karty: - + P / T: S / O: + + + Loyalty: + + CardItem - &Play - &Zahrát + &Zahrát - &Hide - &Skrýt + &Skrýt - &Tap - &Tapnout + &Tapnout - &Untap - &Odtapnout + &Odtapnout - Toggle &normal untapping - Přepnout &normální odtapnutí + Přepnout &normální odtapnutí - &Flip - &Otočit + &Otočit - &Clone - &Zdvojit + &Zdvojit - Ctrl+H - Ctrl+H + Ctrl+H - &Attach to card... - Připojit ke k&artě... + Připojit ke k&artě... - Ctrl+A - Ctrl+A + Ctrl+A - Unattac&h - Od&pojit + Od&pojit - - &Draw arrow... - - - - + &Power / toughness &Síla / odolnost - &Increase power - &Zvýšit sílu + &Zvýšit sílu - Ctrl++ - Ctrl++ + Ctrl++ - &Decrease power - &Snížit sílu + &Snížit sílu - Ctrl+- - Ctrl+- + Ctrl+- - I&ncrease toughness - &Zvýšit odolnost + &Zvýšit odolnost - Alt++ - Alt++ + Alt++ - D&ecrease toughness - &Snížit sílu + &Snížit sílu - Alt+- - Alt+- + Alt+- - In&crease power and toughness - &Zvýšit sílu a odolnost + &Zvýšit sílu a odolnost - Ctrl+Alt++ - Ctrl+Alt++ + Ctrl+Alt++ - Dec&rease power and toughness - &Snížit sílu a odolnost + &Snížit sílu a odolnost - Ctrl+Alt+- - Ctrl+Alt+- + Ctrl+Alt+- - Set &power and toughness... - Nastavit &sílu a odolnost... + Nastavit &sílu a odolnost... - Ctrl+P - Ctrl+P + Ctrl+P - &Set annotation... - Na&stavit poznámku... + Na&stavit poznámku... - red - červená + červená - yellow - žlutá + žlutá - green - zelená + zelená - &Add counter (%1) - Přid&at žeton (%1) + Přid&at žeton (%1) - &Remove counter (%1) - Odst&ranit žeton (%1) + Odst&ranit žeton (%1) - &Set counters (%1)... - Na&stavit žetony (%1)... + Na&stavit žetony (%1)... - &top of library - &vršek knihovny + &vršek knihovny - &bottom of library - &spodek knihovny + &spodek knihovny - &graveyard - &hřbitov + &hřbitov - Ctrl+Del - Ctrl+Del + Ctrl+Del - &exile - &exilnout + &exilnout - + &Move to &Přesunout @@ -638,375 +653,435 @@ This is only saved for moderators and cannot be seen by the banned person.sideboard hráče %1 - + her hand nominative, female owner - + %1's hand nominative, female owner ruka hráče %1 - + his hand nominative, male owner jeho ruka - - - %1's hand - nominative, male owner - ruka hráče %1 - - - - of her hand - genitive, female owner - - - - - of %1's hand - genitive, female owner - ruky hráče %1 - - - - of his hand - genitive, male owner - jeho ruky - - - - of %1's hand - genitive, male owner - ruky hráče %1 - - - - her hand - accusative, female owner - - - - - %1's hand - accusative, female owner - ruka hráče %1 - - - - his hand - accusative, male owner - jeho ruka - - - - %1's hand - accusative, male owner - ruka hráče %1 - + %1's hand + nominative, male owner + ruka hráče %1 + + + her library - nominative, female owner + look at zone, female owner - + %1's library - nominative, female owner + look at zone, female owner knihovna hráče %1 - + his library - nominative, male owner + look at zone, male owner - + %1's library - nominative, male owner + look at zone, male owner knihovna hráče %1 - + of her library - genitive, female owner + top cards of zone, female owner - + of %1's library - genitive, female owner + top cards of zone, female owner knihovny hráče %1 - + of his library - genitive, male owner + top cards of zone, male owner jeho knihovny - + of %1's library - genitive, male owner + top cards of zone, male owner knihovny hráče %1 - + her library - accusative, female owner + reveal zone, female owner - + %1's library - accusative, female owner + reveal zone, female owner knihovna hráče %1 - + his library - accusative, male owner + reveal zone, male owner - + %1's library - accusative, male owner + reveal zone, male owner knihovna hráče %1 - + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + knihovna hráče %1 + + + + his library + shuffle, male owner + + + + + %1's library + shuffle, male owner + knihovna hráče %1 + + + + her sideboard + look at zone, female owner + + + + + %1's sideboard + look at zone, female owner + sideboard hráče %1 + + + + his sideboard + look at zone, male owner + jeho sideboard + + + + %1's sideboard + look at zone, male owner + sideboard hráče %1 + + + of %1's hand + genitive, female owner + ruky hráče %1 + + + of his hand + genitive, male owner + jeho ruky + + + of %1's hand + genitive, male owner + ruky hráče %1 + + + %1's hand + accusative, female owner + ruka hráče %1 + + + his hand + accusative, male owner + jeho ruka + + + %1's hand + accusative, male owner + ruka hráče %1 + + + + her library + nominative, female owner + + + + + %1's library + nominative, female owner + knihovna hráče %1 + + + + his library + nominative, male owner + + + + + %1's library + nominative, male owner + knihovna hráče %1 + + + of %1's library + genitive, female owner + knihovny hráče %1 + + + of his library + genitive, male owner + jeho knihovny + + + of %1's library + genitive, male owner + knihovny hráče %1 + + + %1's library + accusative, female owner + knihovna hráče %1 + + + %1's library + accusative, male owner + knihovna hráče %1 + + + her graveyard nominative, female owner - + %1's graveyard nominative, female owner hřbitov hráče %1 - + his graveyard nominative, male owner jeho hřbitov - + %1's graveyard nominative, male owner hřbitov hráče %1 - - of her graveyard - genitive, female owner - - - - of %1's graveyard genitive, female owner - hřbitova hráče %1 + hřbitova hráče %1 - of his graveyard genitive, male owner - jeho hřbitova + jeho hřbitova - of %1's graveyard genitive, male owner - hřbitova hráče %1 + hřbitova hráče %1 - - her graveyard - accusative, female owner - - - - %1's graveyard accusative, female owner - hřbitov hráče %1 + hřbitov hráče %1 - his graveyard accusative, male owner - jeho hřbitov + jeho hřbitov - %1's graveyard accusative, male owner - hřbitov hráče %1 + hřbitov hráče %1 - + her exile nominative, female owner - + %1's exile nominative, female owner exilnuté karty hráče %1 - + his exile nominative, male owner jeho exilnuté karty - + %1's exile nominative, male owner exilnuté karty hráče %1 - - of her exile - genitive, female owner - - - - of %1's exile genitive, female owner - exilnutých karet hráče %1 + exilnutých karet hráče %1 - of his exile genitive, male owner - exilnutých karet + exilnutých karet - of %1's exile genitive, male owner - exilnutých karet hráče %1 + exilnutých karet hráče %1 - - her exile - accusative, female owner - - - - %1's exile accusative, female owner - exilnuté karty hráče %1 + exilnuté karty hráče %1 - his exile accusative, male owner - jeho exilnuté karty + jeho exilnuté karty - %1's exile accusative, male owner - exilnuté karty hráče %1 + exilnuté karty hráče %1 - + her sideboard nominative, female owner - + %1's sideboard nominative, female owner sideboard hráče %1 - + his sideboard nominative, male owner jeho sideboard - + %1's sideboard nominative, male owner sideboard hráče %1 - - of her sideboard - genitive, female owner - - - - of %1's sideboard genitive, female owner - sideboardu hráče %1 + sideboardu hráče %1 - of his sideboard genitive, male owner - sideboardu hráče + sideboardu hráče - of %1's sideboard genitive, male owner - sideboardu hráče %1 + sideboardu hráče %1 - - her sideboard + %1's sideboard accusative, female owner + sideboard hráče %1 + + + his sideboard + accusative, male owner + jeho sideboard + + + %1's sideboard + accusative, male owner + sideboard hráče %1 + + + + DBPriceUpdater + + + + + Error + Chyba + + + + + A problem has occured while fetching card prices. - - %1's sideboard - accusative, female owner - sideboard hráče %1 - - - - his sideboard - accusative, male owner - jeho sideboard - - - - %1's sideboard - accusative, male owner - sideboard hráče %1 + + A problem has occured while fetching card prices: + DeckEditorSettingsPage - Enable &price tag feature (using data from blacklotusproject.com) - &Povolit zobrazovaní cen (použijí se data z blacklotusproject.com) + &Povolit zobrazovaní cen (použijí se data z blacklotusproject.com) - + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General Obecné @@ -1014,43 +1089,77 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Number Počet - + Card Karta - + Price Cena + + DeckStatsInterface + + + + Error + Chyba + + + + The reply from the server could not be parsed. + + + DeckViewContainer - + Load &local deck Nahrát &lokální balíček - + Load d&eck from server Nahrát &balíček ze serveru - + Ready to s&tart Připraven ke &hře - + + S&ideboard unlocked + + + + + S&ideboard locked + + + + Load deck Nahrát balíček + + + Error + Chyba + + + + The selected file could not be loaded. + + DlgCardSearch @@ -1093,34 +1202,37 @@ This is only saved for moderators and cannot be seen by the banned person. DlgConnect - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: Jméno &hráče: - + P&assword: H&eslo: - - &OK - &OK + + &Save password + + + + &OK + &OK - &Cancel - &Zrušit + &Zrušit @@ -1131,87 +1243,90 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + &Description: &Popis: - + P&layers: H&ráči: - + Game type Formát - + &Password: &Heslo: - + Only &buddies can join Jen pro &přátele - + Only &registered users can join Jen pro &registrované - + Joining restrictions Omezení připojení - + &Spectators allowed &Diváci povoleni - + Spectators &need a password to join Diváci &musí znát heslo - + Spectators can &chat Diváci mohou &chatovat - + Spectators see &everything Diváci vidí &všechno - + Spectators Diváci - &OK - &OK + &OK - &Cancel - &Zrušit + &Zrušit - + Create game Vytvořit hru - + + Game information + + + + Error Chyba - + Server error. Chyba serveru. @@ -1219,115 +1334,277 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: &Jméno: - + Token Token - + C&olor: B&arva: - + white bílá - + blue modrá - + black černá - + red červená - + green zelená - + multicolor multicolor - + colorless bezbarvý - + &P/T: &S/O: - + &Annotation: &Poznámka: - + &Destroy token when it leaves the table &Token se při odchodu z bojiště zničí - + + Token data + + + + + Show &all tokens + + + + + Show tokens from this &deck + + + + + Choose token from list + + + &OK - &OK + &OK - &Cancel - &Zrušit + &Zrušit - + Create token Vytvořit token + + DlgEditTokens + + + &Name: + &Jméno: + + + + C&olor: + B&arva: + + + + white + bílá + + + + blue + modrá + + + + black + černá + + + + red + + + + + green + + + + + multicolor + multicolor + + + + colorless + bezbarvý + + + + &P/T: + &S/O: + + + + &Annotation: + &Poznámka: + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + Chyba + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + O&K + O&K + + + + Filter games + + + DlgLoadDeckFromClipboard - + &Refresh &Obnovit - &OK - &OK + &OK - &Cancel - &Zrušit + &Zrušit - + Load deck from clipboard Nahrát balíček ze schránky - + Error Chyba - + Invalid deck list. Neplatný formát balíčku. @@ -1335,17 +1612,15 @@ This is only saved for moderators and cannot be seen by the banned person. DlgLoadRemoteDeck - O&K - O&K + O&K - &Cancel - &Zrušit + &Zrušit - + Load deck Nahrát balíček @@ -1353,154 +1628,221 @@ This is only saved for moderators and cannot be seen by the banned person. DlgSettings - - - + + + Error Chyba - Your card database is invalid. Would you like to go back and set the correct path? - Cesta k databázi je neplatná. Chcete se vrátit a nastavit správnou? + Cesta k databázi je neplatná. Chcete se vrátit a nastavit správnou? - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s balíčky je neplatná. Chcete se vrátit a nastavit správnou? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s obrázky je neplatná. Chcete se vrátit a nastavit správnou? - + Settings Nastavení - + General Obecné - + Appearance Vzhled - + User interface Uživatelské rozhraní - + Deck editor Editor balíčků - + Messages Zprávy - &Close - &Zavřít + &Zavřít GameSelector - - - - - - - - + + + + + + + + + Error Chyba - + Please join the appropriate room first. - + Wrong password. Špatné heslo. - + Spectators are not allowed in this game. Do této hry je divákům přístup zakázán. - + The game is already full. Hra je plná. - + The game does not exist any more. Hra již neexistuje. - + This game is only open to registered users. Hra je určena jen pro registrované. - + This game is only open to its creator's buddies. Hra je dostupná jen pro přátele zakládajícícho. - + You are being ignored by the creator of this game. Zakladatel hry vás ignoruje. - + Join game Připojit ke hře - + Password: Heslo: - + + Please join the respective room first. + + + + Games Hry - - Show &full games - Ukázat &plné hry - - - - Show &running games + + &Filter games - + + C&lear filter + + + + Show &full games + Ukázat &plné hry + + + C&reate V&ytvořit - + &Join &Připojit - + J&oin as spectator P&řipojit se jako divák @@ -1516,72 +1858,72 @@ This is only saved for moderators and cannot be seen by the banned person. GamesModel - + yes ano - + yes, free for spectators diváci povoleni - + no ne - + buddies only jen pro přátele - + reg. users only jen pro registrované - + not allowed nepovolené - + Room Místnost - + Description Popis - + Creator Zakladatel - + Game type Formát - + Password Heslo - + Restrictions Omezení - + Players Hráči - + Spectators Diváci @@ -1589,95 +1931,139 @@ This is only saved for moderators and cannot be seen by the banned person. GeneralSettingsPage - - + + English Česky - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path Vyberte cestu - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Chyba + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings Osobní nastavení - + Language: Jazyk: - + Download card pictures on the fly Stahovat obrázky karet za běhu - + + Download high-quality card pictures + + + + Paths Cesty - + Decks directory: Adresář s balíčky: - + + Replays directory: + + + + Pictures directory: Adresář s obrázky: - + + Card database: + + + + + Token database: + + + Path to card database: - Karetní databáze: + Karetní databáze: MainWindow - + There are too many concurrent connections from your address. Z vaší adresy jde mnoho současných připojení. - Banned by moderator. - Zabanován moderátorem. + Zabanován moderátorem. - + Scheduled server shutdown. - Unknown reason. - Neznámý důvod. + Neznámý důvod. - + Connection closed Připojení uzavřeno - + The server has terminated your connection. Reason: %1 Server přerušil spojení. Důvod: %1 - + Scheduled server shutdown - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1688,208 +2074,276 @@ Reason for shutdown: %1 - + Number of players Počet hráčů - + Please enter the number of players. Vložte počet hráčů. - - + + Player %1 Hráč %1 - + About Cockatrice O Cockatrice - + Version %1 Verze %1 - + Authors: Autoři: - + Translators: Překlad: - + Spanish: Španělština: - + Portugese (Portugal): Portugalština (Portugalsko): - + Portugese (Brazil): Portugalština (Brazílie): - + French: Francouzština: - + Japanese: Japonština: - + Russian: Ruština: - Czech: - Čeština: + Čeština: Slovak: Slovenština: - + + Banned by moderator + + + + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + + Load replay + + + + Italian: - - - - - - + + Swedish: + + + + + + + + + + + + + Error Chyba - + Server timeout Vypršel časový limit - Invalid login data. - Nesprávné údaje uživatele. + Nesprávné údaje uživatele. - + + Incorrect username or password. Please check your authentication information and try again. + + + + There is already an active session using this user name. Please close that session first and re-login. S tímto uživatelským jménem jste již připojeni. Přerušte spojení a znovu se přihlašte. - + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + This server requires user registration. + + + + + Unknown login error: %1 + + + + Socket error: %1 Chyba socketu: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Snažíte se připojit na zastaralý server. Prosíme, stáhněte si nižší verzi Cockatrice, nebo se připojte k odpovídajícímu serveru. Lokální verze je %1, verze serveru je %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Váš klient je zastaralý. Prosíme, aktualizujte Cockatrice na vyšší verzi. Lokální verze je %1, verze serveru je %2. - + Connecting to %1... Připojování k %1... - + Disconnected Odpojeno - + + Connected, logging in at %1 + + + + Logged in at %1 Přihlášeno k %1 - + &Connect... &Připojit... - + &Disconnect &Odpojit - + Start &local game... Spustit &lokální hru... - + + &Watch replay... + + + + &Deck editor &Editor balíčků - + &Full screen &Celá obrazovka - + Ctrl+F CTRL+F - + &Settings... &Nastavení... - + &Exit &Konec - + &Cockatrice &Cockatrice - + &About Cockatrice &O Cockatrice - + &Help &Nápověda - + Are you sure? Jste si jisti? - + There are still open games. Are you sure you want to quit? Některé hry jsou stále otevřené. Opravdu chcete odejít? @@ -1933,17 +2387,22 @@ Lokální verze je %1, verze serveru je %2. %1 opustil hru. - + The game has been closed. Hra byla ukončena. - + + You have been kicked out of the game. + Byli jste vyhozeni ze hry. + + + %1 is now watching the game. %1 nyní sleduje hru. - + %1 is not watching the game any more. %1 přestal sledovat hru. @@ -1968,7 +2427,7 @@ Lokální verze je %1, verze serveru je %2. %1 ukončil hru. - + The game has started. Hra začíná. @@ -1989,159 +2448,203 @@ Lokální verze je %1, verze serveru je %2. - + You have joined game #%1. female Připojili jste se ke hře #%1. - - - You have joined game #%1. - male - Připojili jste se ke hře #%1. - - - - %1 has joined the game. - female - %1 se připojil ke hře. - - - - %1 has joined the game. - male - %1 se připojil ke hře. - - %1 has left the game. - female - %1 opustil hru. - - - - %1 has left the game. + You have joined game #%1. male - %1 opustil hru. + Připojili jste se ke hře #%1. - %1 has loaded a local deck. + + You are watching a replay of game #%1. female - %1 nahrál lokální balíček. + - %1 has loaded a local deck. + + You are watching a replay of game #%1. male - %1 nahrál lokální balíček. + - %1 has loaded deck #%2. + + %1 has joined the game. female - %1 nahrál balíček #%2. + %1 se připojil ke hře. - %1 has loaded deck #%2. + + %1 has joined the game. male - %1 nahrál balíček #%2. + %1 se připojil ke hře. - %1 has loaded a deck (%2). + %1 has left the game. female - %1 nahrál balíček %2. + %1 opustil hru. + %1 has left the game. + male + %1 opustil hru. + + + %1 has loaded a local deck. + female + %1 nahrál lokální balíček. + + + %1 has loaded a local deck. + male + %1 nahrál lokální balíček. + + + %1 has loaded deck #%2. + female + %1 nahrál balíček #%2. + + + %1 has loaded deck #%2. + male + %1 nahrál balíček #%2. + + + %1 has loaded a deck (%2). + female + %1 nahrál balíček %2. + + %1 has loaded a deck (%2). male - %1 nahrál balíček %2. + %1 nahrál balíček %2. - - %1 is ready to start the game. - female - %1 je připraven ke hře. + + %1 has loaded a deck (%2). + %1 nahrál balíček %2. - - %1 is ready to start the game. - male - %1 je připraven ke hře. - - - - %1 is not ready to start the game any more. - female - %1 již není připraven ke hře. - - - - %1 is not ready to start the game any more. - male - %1 již není připraven ke hře. - - - - %1 has conceded the game. - female - %1 ukončil hru. - - - - %1 has conceded the game. - male - %1 ukončil hru. - - - - %1 has restored connection to the game. - female + + %1 has loaded a deck with %2 sideboard cards (%3). + %1 is ready to start the game. + female + %1 je připraven ke hře. + + + + %1 is ready to start the game. + male + %1 je připraven ke hře. + + + + %1 is not ready to start the game any more. + female + %1 již není připraven ke hře. + + + + %1 is not ready to start the game any more. + male + %1 již není připraven ke hře. + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + %1 has conceded the game. + female + %1 ukončil hru. + + + + %1 has conceded the game. + male + %1 ukončil hru. + + + + %1 has restored connection to the game. + female + + + + %1 has restored connection to the game. male - + %1 has lost connection to the game. female - + %1 has lost connection to the game. male - + %1 shuffles %2. female - + %1 shuffles %2. male - + %1 rolls a %2 with a %3-sided die. female %1 hodil kostkou %2 (%3 stěn). - + %1 rolls a %2 with a %3-sided die. male %1 hodil kostkou %2 (%3 stěn). - + %1 draws %n card(s). female @@ -2151,7 +2654,7 @@ Lokální verze je %1, verze serveru je %2. - + %1 draws %n card(s). male @@ -2161,188 +2664,213 @@ Lokální verze je %1, verze serveru je %2. - + %1 undoes his last draw. %1 vrátil zpět svoje poslední líznutí. - + %1 undoes her last draw. - + %1 undoes his last draw (%2). %1 vrátil svoje poslední líznutí (%2). - + %1 undoes her last draw (%2). - + from table z bojiště - + from graveyard ze hřbitova - + from exile z exilnutých karet - + from hand z ruky - + + the bottom card of %1's library + + + + the bottom card of his library spodní kartu knihovny - + the bottom card of her library - + + from the bottom of %1's library + + + + from the bottom of his library ze spodku knihovny - + from the bottom of her library - + + the top card of %1's library + + + + the top card of his library vrchní kartu knihovny - + the top card of her library - + + from the top of %1's library + + + + from the top of his library z vršku knihovny - + from the top of her library - + + from %1's library + + + + from library z knihovny - + from sideboard ze sideboardu - + from the stack ze stacku - - + + a card kartu - + %1 gives %2 control over %3. %1 předává kontrolu hráči %2 karty %3. - + %1 puts %2 into play tapped%3. %1 dává kartu %2 do hry %3 tapnutou. - + %1 puts %2 into play%3. %1 dává kartu %2 %3 do hry. - + %1 puts %2%3 into graveyard. %1 dává kartu %2%3 do hřbitova. - + %1 exiles %2%3. %1 exiluje %2%3. - + %1 moves %2%3 to hand. %1 přesouvá %2%3 do ruky. - + %1 puts %2%3 into his library. %1 dává %2%3 do knihovny. - + %1 puts %2%3 into her library. - + %1 puts %2%3 on bottom of his library. %1 dává %2%3 na spodek knihovny. - + %1 puts %2%3 on bottom of her library. - + %1 puts %2%3 on top of his library. %1 dává %2%3 na vršek knihovny. - + %1 puts %2%3 on top of her library. - + %1 puts %2%3 into his library at position %4. %1 dává %2%3 knihovny na pozici %4. - + %1 puts %2%3 into her library at position %4. - + %1 moves %2%3 to sideboard. %1 přesouvá %2%3 do sideboardu. - + %1 plays %2%3. %1 sesílá %2%3. - + %1 takes a mulligan to %n. female @@ -2352,7 +2880,7 @@ Lokální verze je %1, verze serveru je %2. - + %1 takes a mulligan to %n. male @@ -2362,312 +2890,312 @@ Lokální verze je %1, verze serveru je %2. - + %1 draws her initial hand. - + %1 flips %2 face-down. female %1 otáčí %2 lícem dolů. - + %1 flips %2 face-down. male %1 otáčí %2 lícem dolů. - + %1 flips %2 face-up. female %1 otáčí %2 lícem vzhůru. - + %1 flips %2 face-up. male %1 otáčí %2 lícem vzhůru. - + %1 destroys %2. female %1 ničí %2. - + %1 destroys %2. male %1 ničí %2. - + %1 attaches %2 to %3's %4. p1 female, p2 female %1 připojuje %2 k %3 %4. - + %1 attaches %2 to %3's %4. p1 female, p2 male %1 připojuje %2 k %3 %4. - + %1 attaches %2 to %3's %4. p1 male, p2 female %1 připojuje %2 k %3 %4. - + %1 attaches %2 to %3's %4. p1 male, p2 male %1 připojuje %2 k %3 %4. - + %1 unattaches %2. female %1 odpojuje %2. - + %1 unattaches %2. male %1 odpojuje %2. - + %1 creates token: %2%3. female %1 vykládá token %2%3. - + %1 creates token: %2%3. male %1 vykládá token %2%3. - + %1 points from her %2 to herself. female - + %1 points from his %2 to himself. male - + %1 points from her %2 to %3. p1 female, p2 female - + %1 points from her %2 to %3. p1 female, p2 male - + %1 points from his %2 to %3. p1 male, p2 female - + %1 points from his %2 to %3. p1 male, p2 male - + %1 points from %2's %3 to herself. card owner female, target female - + %1 points from %2's %3 to herself. card owner male, target female - + %1 points from %2's %3 to himself. card owner female, target male - + %1 points from %2's %3 to himself. card owner male, target male - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female %1 ukazuje kartou %3 (hráče %2) na %4. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male %1 ukazuje kartou %3 (hráče %2) na %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female %1 ukazuje kartou %3 (hráče %2) na %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male %1 ukazuje kartou %3 (hráče %2) na %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female %1 ukazuje kartou %3 (hráče %2) na %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male %1 ukazuje kartou %3 (hráče %2) na %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female %1 ukazuje kartou %3 (hráče %2) na %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male %1 ukazuje kartou %3 (hráče %2) na %4. - + %1 points from her %2 to her %3. female - + %1 points from his %2 to his %3. male - + %1 points from her %2 to %3's %4. p1 female, p2 female - + %1 points from her %2 to %3's %4. p1 female, p2 male - + %1 points from his %2 to %3's %4. p1 male, p2 female - + %1 points from his %2 to %3's %4. p1 male, p2 male - + %1 points from %2's %3 to her own %4. card owner female, target female - + %1 points from %2's %3 to her own %4. card owner male, target female - + %1 points from %2's %3 to his own %4. card owner female, target male - + %1 points from %2's %3 to his own %4. card owner male, target male - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female %1 ukazuje kartou %3 (hráče %2) na %5 (hráče %4). - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male %1 ukazuje kartou %3 (hráče %2) na %5 (hráče %4). - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female %1 ukazuje kartou %3 (hráče %2) na %5 (hráče %4). - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male %1 ukazuje kartou %3 (hráče %2) na %5 (hráče %4). - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female %1 ukazuje kartou %3 (hráče %2) na %5 (hráče %4). - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male %1 ukazuje kartou %3 (hráče %2) na %5 (hráče %4). - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female %1 ukazuje kartou %3 (hráče %2) na %5 (hráče %4). - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male %1 ukazuje kartou %3 (hráče %2) na %5 (hráče %4). - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -2677,7 +3205,7 @@ Lokální verze je %1, verze serveru je %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -2687,7 +3215,7 @@ Lokální verze je %1, verze serveru je %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -2697,7 +3225,7 @@ Lokální verze je %1, verze serveru je %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -2707,265 +3235,317 @@ Lokální verze je %1, verze serveru je %2. - + %1 taps her permanents. female - + %1 untaps her permanents. female - + %1 taps his permanents. male - + %1 untaps his permanents. male - + %1 taps %2. female - + %1 untaps %2. female - - - %1 taps %2. - male - - - - - %1 untaps %2. - male - - - - - %1 sets counter %2 to %3 (%4%5). - female - %1 nastavuje počet %2 žetonů na %3 (%4%5). - - - - %1 sets counter %2 to %3 (%4%5). - male - %1 nastavuje počet %2 žetonů na %3 (%4%5). - - - - %1 sets %2 to not untap normally. - female - %1 nastavuje %2 na neodtapování. - - - - %1 sets %2 to not untap normally. - male - %1 nastavuje %2 na neodtapování. - - - - %1 sets %2 to untap normally. - female - %1 nastavuje %2 na standardní odtapnutí. - - - - %1 sets %2 to untap normally. - male - %1 nastavuje %2 na standardní odtapnutí. - - - - %1 sets PT of %2 to %3. - female - %1 nastavuje sílu a odolnost pro %2 na %3. - - - - %1 sets PT of %2 to %3. - male - %1 nastavuje sílu a odolnost pro %2 na %3. - - - - %1 sets annotation of %2 to %3. - female - %1 nastavuje poznámku pro %2 na %3. - - - - %1 sets annotation of %2 to %3. - male - %1 nastavuje poznámku pro %2 na %3. - - - - %1 is looking at the top %2 cards %3. - female - %1 si prohlíží %2 vrchních karet %3. - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - %1 si prohlíží %2 vrchních karet %3. + - - %1 is looking at %2. - female - %1 si prohlíží %2. - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 si prohlíží %2. + - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female - %1 si přestává prohlížet %2. + %1 nastavuje počet %2 žetonů na %3 (%4%5). + %1 sets counter %2 to %3 (%4%5). + male + %1 nastavuje počet %2 žetonů na %3 (%4%5). + + + + %1 sets %2 to not untap normally. + female + %1 nastavuje %2 na neodtapování. + + + + %1 sets %2 to not untap normally. + male + %1 nastavuje %2 na neodtapování. + + + + %1 sets %2 to untap normally. + female + %1 nastavuje %2 na standardní odtapnutí. + + + + %1 sets %2 to untap normally. + male + %1 nastavuje %2 na standardní odtapnutí. + + + + %1 sets PT of %2 to %3. + female + %1 nastavuje sílu a odolnost pro %2 na %3. + + + + %1 sets PT of %2 to %3. + male + %1 nastavuje sílu a odolnost pro %2 na %3. + + + + %1 sets annotation of %2 to %3. + female + %1 nastavuje poznámku pro %2 na %3. + + + + %1 sets annotation of %2 to %3. + male + %1 nastavuje poznámku pro %2 na %3. + + + %1 is looking at the top %2 cards %3. + female + %1 si prohlíží %2 vrchních karet %3. + + + %1 is looking at the top %2 cards %3. + male + %1 si prohlíží %2 vrchních karet %3. + + + + %1 is looking at %2. + female + %1 si prohlíží %2. + + + + %1 is looking at %2. + male + %1 si prohlíží %2. + + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + + + + %1 stops looking at %2. + female + %1 si přestává prohlížet %2. + + + %1 stops looking at %2. male %1 si přestává prohlížet %2. - + %1 reveals %2 to %3. p1 female, p2 female %1 ukazuje %2 hráči %3. - + %1 reveals %2 to %3. p1 female, p2 male %1 ukazuje %2 hráči %3. - + %1 reveals %2 to %3. p1 male, p2 female %1 ukazuje %2 hráči %3. - + %1 reveals %2 to %3. p1 male, p2 male %1 ukazuje %2 hráči %3. - + %1 reveals %2. female %1 ukazuje %2. - + %1 reveals %2. male %1 ukazuje %2. - + %1 randomly reveals %2%3 to %4. p1 female, p2 female %1 náhodně ukazuje %2%3 hráči %4. - + %1 randomly reveals %2%3 to %4. p1 female, p2 male %1 náhodně ukazuje %2%3 hráči %4. - + %1 randomly reveals %2%3 to %4. p1 male, p2 female %1 náhodně ukazuje %2%3 hráči %4. - + %1 randomly reveals %2%3 to %4. p1 male, p2 male %1 náhodně ukazuje %2%3 hráči %4. - + %1 randomly reveals %2%3. female %1 náhodně ukazuje %2%3. - + %1 randomly reveals %2%3. male %1 náhodně ukazuje %2%3. - + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + %1 reveals %2%3 to %4. p1 female, p2 female %1 ukazuje %2%3 hráči %4. - + %1 reveals %2%3 to %4. p1 female, p2 male %1 ukazuje %2%3 hráči %4. - + %1 reveals %2%3 to %4. p1 male, p2 female %1 ukazuje %2%3 hráči %4. - + %1 reveals %2%3 to %4. p1 male, p2 male %1 ukazuje %2%3 hráči %4. - + %1 reveals %2%3. female %1 ukazuje %2%3. - + %1 reveals %2%3. male %1 ukazuje %2%3. - + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + It is now %1's turn. female Nyní je kolo hráče %1. - + It is now %1's turn. male Nyní je kolo hráče %1. @@ -2979,7 +3559,7 @@ Lokální verze je %1, verze serveru je %2. - + %1 draws his initial hand. %1 líže startovní ruku. @@ -3032,7 +3612,7 @@ Lokální verze je %1, verze serveru je %2. - + red červený @@ -3041,7 +3621,7 @@ Lokální verze je %1, verze serveru je %2. - + yellow žlutý @@ -3050,7 +3630,7 @@ Lokální verze je %1, verze serveru je %2. - + green zelený @@ -3135,62 +3715,62 @@ Lokální verze je %1, verze serveru je %2. Nyní je kolo hráče %1. - + untap step Odtapovací fáze - + upkeep step Upkeep - + draw step Lízací fáze - + first main phase První hlavní fáze - + beginning of combat step Začátek bojové fáze - + declare attackers step Oznámení útočníků - + declare blockers step Oznámení blokujících - + combat damage step Udělení bojového zranění - + end of combat step Konec bojové fáze - + second main phase Druhá hlavní fáze - + ending phase Konec kola - + It is now the %1. Nyní je %1. @@ -3198,22 +3778,22 @@ Lokální verze je %1, verze serveru je %2. MessagesSettingsPage - + Add message Přidat zprávu - + Message: Zpráva: - + &Add &Přidat - + &Remove &Odstranit @@ -3221,57 +3801,57 @@ Lokální verze je %1, verze serveru je %2. PhasesToolbar - + Untap step Odtapovací fáze - + Upkeep step Upkeep - + Draw step Lízací fáze - + First main phase První hlavní fáze - + Beginning of combat step Začátek bojové fáze - + Declare attackers step Oznámení útočníků - + Declare blockers step Oznámení blokujících - + Combat damage step Udělení bojového zranění - + End of combat step Konec bojové fáze - + Second main phase Druhá hlavní fáze - + End of turn step Konec kola @@ -3279,322 +3859,536 @@ Lokální verze je %1, verze serveru je %2. Player - + &View graveyard &Zobrazit hřbitov - + &View exile &Zobrazit exilnuté karty - + Player "%1" Hráč "%1" - + &Graveyard &Hřbitov - + &Exile &Exilnuté karty - - - + + + Move to &top of library Přesunout na &vršek knihovny - - - + + + Move to &bottom of library Přesunout na &spodek knihovny - - + + Move to &graveyard Přesunout do &hřbitova - - + + Move to &exile &Exilnout - - + + Move to &hand Přesunout do &ruky - + &View library &Zobrazit knihovnu - + View &top cards of library... Zobrazit &vrchní karty knihovny... - + Reveal &library to Ukázat &knihovnu - + Reveal t&op card to Ukázat v&rchní kartu - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &View sideboard &Zobrazit sideboard - + &Draw card &Líznout kartu - + D&raw cards... L&íznout karty... - + &Undo last draw &Vrátit zpět poslední líznutí - + Take &mulligan &Mulliganovat - + &Shuffle &Zamíchat - + Move top cards to &graveyard... Přesunout vrchní karty do &hřbitova... - + Move top cards to &exile... &Exilnout vrchní karty... - + Put top card on &bottom Dát kartu na &spodek - + &Hand &Ruka - + &Reveal to &Ukázat - + Reveal r&andom card to Ukázat kartu n&áhodně - + &Sideboard &Sideboard - + &Library &Knihovna - + &Counters &Žetony - + &Untap all permanents &Odtapnout všechny permanenty - + R&oll die... H&odit kostkou... - + &Create token... &Vytvořit token... - + C&reate another token V&ytvořit další token - + + Cr&eate predefined token + + + + S&ay &Chat - + C&ard K&arta - + &All players &Všem hráčům - + + &Play + &Zahrát + + + + &Hide + &Skrýt + + + + &Tap + &Tapnout + + + + &Untap + &Odtapnout + + + + Toggle &normal untapping + Přepnout &normální odtapnutí + + + + &Flip + &Otočit + + + + &Peek at card face + + + + + &Clone + &Zdvojit + + + Ctrl+H + Ctrl+H + + + + Ctrl+J + + + + + Attac&h to card... + + + + + Ctrl+A + Ctrl+A + + + + Unattac&h + Od&pojit + + + + &Draw arrow... + + + + + &Increase power + &Zvýšit sílu + + + + Ctrl++ + Ctrl++ + + + + &Decrease power + &Snížit sílu + + + + Ctrl+- + Ctrl+- + + + + I&ncrease toughness + &Zvýšit odolnost + + + + Alt++ + Alt++ + + + + D&ecrease toughness + &Snížit sílu + + + + Alt+- + Alt+- + + + + In&crease power and toughness + &Zvýšit sílu a odolnost + + + + Ctrl+Alt++ + Ctrl+Alt++ + + + + Dec&rease power and toughness + &Snížit sílu a odolnost + + + + Ctrl+Alt+- + Ctrl+Alt+- + + + + Set &power and toughness... + Nastavit &sílu a odolnost... + + + + Ctrl+P + Ctrl+P + + + + &Set annotation... + Na&stavit poznámku... + + + + red + + + + + yellow + + + + + green + + + + + &Add counter (%1) + Přid&at žeton (%1) + + + + &Remove counter (%1) + Odst&ranit žeton (%1) + + + + &Set counters (%1)... + Na&stavit žetony (%1)... + + + + &top of library + &vršek knihovny + + + + &bottom of library + &spodek knihovny + + + + &graveyard + &hřbitov + + + + Ctrl+Del + Ctrl+Del + + + + &exile + &exilnout + + + Ctrl+F3 Ctrl+F3 - + F3 F3 - + Ctrl+W Ctrl+W - + F4 F4 - + Ctrl+D Ctrl+D - + Ctrl+E Ctrl+E - + Ctrl+Shift+D Ctrl+Shift+D - + Ctrl+M Ctrl+M - + Ctrl+S Ctrl+S - + Ctrl+U Ctrl+U - + Ctrl+I Ctrl+I - + Ctrl+T Ctrl+T - + Ctrl+G Ctrl+G - + View top cards of library Zobrazit vrchní karty knihovny - + Number of cards: Počet karet: - + Draw cards Líznout karty - - - - + + + + Number: Počet: - + Move top cards to grave Přesunout vrchní karty do hřbitova - + Move top cards to exile Exilnout vrchní karty - + Roll die Hodit kostkou - + Number of sides: Počet stran: - + Set power/toughness Nastavit sílu/odolnost - + Please enter the new PT: Vložte novou odolnost/sílu: - + Set annotation Nastavit poznámku - + Please enter the new annotation: Vložte novou poznámku: - + Set counters Nastavit žetony @@ -3610,116 +4404,148 @@ Lokální verze je %1, verze serveru je %2. bal94ek #%1 - User &details - Detaily &uživatele + Detaily &uživatele - Direct &chat - &Chatovat + &Chatovat - Add to &buddy list - Přidat mezi &přátele + Přidat mezi &přátele - Remove from &buddy list - Odstranit z &přátel + Odstranit z &přátel - Add to &ignore list - Přidat do &ignorovaných + Přidat do &ignorovaných - Remove from &ignore list - Odstranit z &ignorovaných + Odstranit z &ignorovaných - Kick from &game - Vyhodit ze &hry + Vyhodit ze &hry QObject - Maindeck - Maindeck + Maindeck - Sideboard - Sideboard + Sideboard - Cockatrice decks (*.cod) - Balíčky cockatrice (*.cod) + Balíčky cockatrice (*.cod) - Plain text decks (*.dec *.mwDeck) - Čistý text (*.dec*.mwDeck) + Čistý text (*.dec*.mwDeck) - + + Common deck formats (*.cod *.dec *.mwDeck) + + + + All files (*.*) - Všechny soubory (*.*) + Všechny soubory (*.*) + + + + Cockatrice replays (*.cor) + RemoteDeckList_TreeModel - + Name Jméno - + ID ID - + Upload time Průběh nahrávání + + RemoteReplayList_TreeModel + + + ID + ID + + + + Name + Jméno + + + + Players + Hráči + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + RoomSelector - + Rooms Místnosti - + Joi&n &Připojit - + Room Místnost - + Description Popis - + Players Hráči - + Games Hry @@ -3740,27 +4566,21 @@ Lokální verze je %1, verze serveru je %2. ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - &OK - &OK + &OK - - &Cancel - - - - + Shut down server @@ -3768,247 +4588,507 @@ Lokální verze je %1, verze serveru je %2. TabAdmin - + Update server &message Aktualizovat &zprávu serveru - + &Shut down server - + Server administration functions Administrační funkce serveru - + &Unlock functions &Odemknout funkce - + &Lock functions &Zamknout funkce - + Unlock administration functions Odemknout administrační funkce - + Do you really want to unlock the administration functions? Opravdu chcete odemknout administrační funkce? - Administration - Administrace + Administrace + + + + TabDeckEditor + + &Search... + &Hledat... + + + + Show card text only + + + + + &Clear search + &Vyčistit hledání + + + + &Search for: + &Vyhledat: + + + + Deck &name: + &Název balíčku: + + + + &Comments: + &Komentář: + + + + Hash: + + + + + &Update prices + Akt&ualizovat ceny + + + + Ctrl+U + + + + + &New deck + &Nový balíček + + + + &Load deck... + &Nahrát balíček... + + + + &Save deck + &Uložit balíček + + + + Save deck &as... + Uložit balíček &jako... + + + + Load deck from cl&ipboard... + Nahrát balíček ze s&chránky... + + + + Save deck to clip&board + Vložit balíček do &schránky + + + + &Print deck... + &Vytisknout balíček... + + + + &Analyze deck on deckstats.net + + + + + &Close + &Zavřít + + + + Ctrl+Q + CTRL+Q + + + + Add card to &maindeck + Přidat kartu do &balíčku + + + Return + Return + + + Enter + Enter + + + + Add card to &sideboard + Přidat kartu do &sideboardu + + + Ctrl+Return + Ctrl+Return + + + Ctrl+Enter + Ctrl+Enter + + + + &Remove row + &Smazat řádek + + + + Del + Del + + + + &Increment number + &Zvýšit počet + + + + + + + + + + + &Decrement number + &Snížit počet + + + + - + - + + + + &Deck editor + &Editor balíčků + + + + C&ard database + + + + + &Edit sets... + &Upravit sasy... + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + Jste si jisti? + + + + The decklist has been modified. +Do you want to save the changes? + Balíček byl upraven. +Chcete uložit změny? + + + + Load deck + Nahrát balíček + + + + + + Error + Chyba + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + Balíček nebylo možné uložit. +Zkontrolujte, jestli lze zapisovat do cílového adresáře a zkuste to znovu. + + + + Save deck + Uložit balíček TabDeckStorage - + Local file system Lokální systém souborů - + Server deck storage Balíčky na serveru - - + + Open in deck editor Otevřít v editoru balíčků - + Upload deck Nahrát balíček - + Download deck Stáhnout balíček - - + + New folder Nová složka - + + Delete Smazat - + Enter deck name Vložit jméno balíčku - + This decklist does not have a name. Please enter a name: Tentobalíček nemá jméno. Prosím vložte jméno: - + Unnamed deck Bezejmenný balíček - + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: Název nové složky: - + + Delete remote folder + + + + + Delete remote deck + + + Deck storage - Uložiště balíčků + Uložiště balíčků TabGame - + F5 F5 - + F6 F6 - + F7 F7 - + F8 F8 - + F9 F9 - + F10 F10 - + &Phases &Fáze - + &Game &Hra - + Next &phase Další &fáze - + Ctrl+Space Ctrl+Space - + + Tab + + + + Next &turn Další &kolo - + Ctrl+Return Ctrl+Return - + Ctrl+Enter Ctrl+Enter - + &Remove all local arrows &Odstranit všechny lokální šipky - + Ctrl+R CTRL+R - + + Game &information + + + + &Concede &Ukončit hru - + F2 F2 - + &Leave game &Opustit hru - + + Ctrl+Q CTRL+Q - + + C&lose replay + + + + &Say: &Chat: - + Concede Ukončit hru - + Are you sure you want to concede this game? Opravdu chcete ukončit tuto hru? - + Leave game Opustit hru - + Are you sure you want to leave this game? Opravdu chcete opustit tuto hru? - + + Replay %1: %2 + + + Kicked - Vyhozen + Vyhozen - You have been kicked out of the game. - Byli jste vyhozeni ze hry. + Byli jste vyhozeni ze hry. - + Game %1: %2 Hra %1: %2 @@ -4016,60 +5096,120 @@ Prosím vložte jméno: TabMessage - + Personal &talk Osobní &diskuze - + &Leave &Odejít - + This user is ignoring you. Tento uživatel vás ignoruje. - + %1 has left the server. %1 opustil server. - + %1 has joined the server. %1 se připojil k serveru. - + Talking to %1 Mlávíte k %1 + + TabReplays + + + Local file system + Lokální systém souborů + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + Smazat + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: &Chat: - + Chat Chatovat - + &Room &Místnost - + &Leave room &Opustit místnost - + + &Ignore unregistered users in chat + + + + You are flooding the chat. Please wait a couple of seconds. Píšete příliš intenzivně. Počkejte pár sekund. @@ -4077,53 +5217,114 @@ Prosím vložte jméno: TabServer - Server - Server + Server TabUserLists - User lists - Seznam uživatelů + Seznam uživatelů + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + + + + + Direct &chat + &Chatovat + + + + Show this user's &games + + + + + Add to &buddy list + Přidat mezi &přátele + + + + Remove from &buddy list + Odstranit z &přátel + + + + Add to &ignore list + + + + + Remove from &ignore list + Odstranit z &ignorovaných + + + + Kick from &game + Vyhodit ze &hry + + + + Ban from &server + &Zabanovat + + + + %1's games + UserInfoBox - + User information Informace o uživateli - + Real name: Pravé jméno: - + Gender: - + Location: Místo: - + User level: Úroveň: - + Administrator Administrátor - + Moderator @@ -4132,12 +5333,12 @@ Prosím vložte jméno: Judge - + Registered user Registrovaný - + Unregistered user Neregistrovaný @@ -4145,37 +5346,47 @@ Prosím vložte jméno: UserInterfaceSettingsPage - + General interface settings Obecné - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) &Pro zahraní karty je třeba dvojklik - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings Animace - + &Tap/untap animation &Animace tapnutí/odtapnutí - + Enable &sounds Povolit &zvuky - + Path to sounds directory: Adresář se zvuky: - + Choose path Vyberte cestu @@ -4183,69 +5394,52 @@ Prosím vložte jméno: UserList - + Users online: %1 Připojených uživatelů: %1 - + Users in this room: %1 Uživatelů v této místnosti: %1 - + Buddies online: %1 / %2 Přátelé online: %1 / %2 - + Ignored users online: %1 / %2 Ignorovaných online: %1 / %2 - - %1's games - - - - User &details - &Detaily uživatele + &Detaily uživatele - Direct &chat - &Chatovat + &Chatovat - - Show this user's &games - - - - Add to &buddy list - Přidat mezi &přátele + Přidat mezi &přátele - Remove from &buddy list - Odstranit z &přátel + Odstranit z &přátel - Add to &ignore list - Přidat mezi &ignorované + Přidat mezi &ignorované - Remove from &ignore list - Odstranit z &ignorovaných + Odstranit z &ignorovaných - Ban from &server - &Zabanovat + &Zabanovat Duration @@ -4261,205 +5455,160 @@ Hodnota 0 je pro ban bez omezení. WndDeckEditor - &Search... - &Hledat... + &Hledat... - &Clear search - &Vyčistit hledání + &Vyčistit hledání - &Search for: - &Vyhledat: + &Vyhledat: - Deck &name: - &Název balíčku: + &Název balíčku: - &Comments: - &Komentář: + &Komentář: - - Hash: - - - - &Update prices - Akt&ualizovat ceny + Akt&ualizovat ceny - Ctrl+U - CTRL+U + CTRL+U - Deck editor [*] - Editor balíčků [*] + Editor balíčků [*] - &New deck - &Nový balíček + &Nový balíček - &Load deck... - &Nahrát balíček... + &Nahrát balíček... - &Save deck - &Uložit balíček + &Uložit balíček - Save deck &as... - Uložit balíček &jako... + Uložit balíček &jako... - Load deck from cl&ipboard... - Nahrát balíček ze s&chránky... + Nahrát balíček ze s&chránky... - Save deck to clip&board - Vložit balíček do &schránky + Vložit balíček do &schránky - &Print deck... - &Vytisknout balíček... + &Vytisknout balíček... - &Close - &Zavřít + &Zavřít - Ctrl+Q - CTRL+Q + CTRL+Q - &Edit sets... - &Upravit sasy... + &Upravit sasy... - &Deck - &Balíček + &Balíček - &Card database - Databáze &karet + Databáze &karet - Add card to &maindeck - Přidat kartu do &balíčku + Přidat kartu do &balíčku - Return - Return + Return - Enter - Enter + Enter - Add card to &sideboard - Přidat kartu do &sideboardu + Přidat kartu do &sideboardu - Ctrl+Return - Ctrl+Return + Ctrl+Return - Ctrl+Enter - Ctrl+Enter + Ctrl+Enter - &Remove row - &Smazat řádek + &Smazat řádek - Del - Del + Del - &Increment number - &Zvýšit počet + &Zvýšit počet - + - + + + - &Decrement number - &Snížit počet + &Snížit počet - - - - + - - Are you sure? - Jste si jisti? + Jste si jisti? - The decklist has been modified. Do you want to save the changes? - Balíček byl upraven. + Balíček byl upraven. Chcete uložit změny? - Load deck - Nahrát balíček + Nahrát balíček - - Error - Chyba + Chyba - - The deck could not be saved. Please check that the directory is writable and try again. - Balíček nebylo možné uložit. + Balíček nebylo možné uložit. Zkontrolujte, jestli lze zapisovat do cílového adresáře a zkuste to znovu. - Save deck - Uložit balíček + Uložit balíček @@ -4473,17 +5622,17 @@ Zkontrolujte, jestli lze zapisovat do cílového adresáře a zkuste to znovu. ZoneViewWidget - + sort by name seřadit dle jména - + sort by type seřadit dle typu - + shuffle when closing zamíchat po zavření diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index 65e131f7b..fedd2a5a9 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -1,6 +1,6 @@ - + @@ -27,32 +27,32 @@ AbstractCounter - + &Set counter... Zähler &setzen... - + Ctrl+L Ctrl+L - + F11 F11 - + F12 F12 - + Set counter Zähler setzen - + New value for counter '%1': Neuer Wert für den Zähler '%1': @@ -60,62 +60,82 @@ AppearanceSettingsPage - + Zone background pictures Hintergrundbilder für Kartenzonen - Path to hand background: - Hintergrundbild für die Hand: + Hintergrundbild für die Hand: - Path to stack background: - Hintergrundbild für den Stapel: + Hintergrundbild für den Stapel: - Path to table background: - Hintergrundbild für das Spielfeld: + Hintergrundbild für das Spielfeld: - Path to player info background: - Hintergrundbild für den Spielerbereich: + Hintergrundbild für den Spielerbereich: - Path to picture of card back: - Pfad zum Bild der Kartenrückseite: + Pfad zum Bild der Kartenrückseite: - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Card rendering Kartendarstellung - + Display card names on cards having a picture Kartennamen darstellen auch bei Karten, die Bilder haben - + Hand layout Kartenhand - + Display hand horizontally (wastes space) Hand horizonal anzeigen (verschwendet Platz) - + Table grid layout Spielfeldraster - + Minimum player count for multi-column layout: Mindestspielerzahl für mehrspaltige Anordnung: @@ -124,7 +144,7 @@ Platzsparende Anordnung - + Invert vertical coordinate Vertikale Koordinate umkehren @@ -133,17 +153,17 @@ Platzsparende Anordnung - + Zone view layout Aussehen des Zonenbetrachters - + Sort by name nach Namen sortieren - + Sort by type nach Kartentypen sortieren @@ -152,11 +172,11 @@ standardmäßig alphabetisch sortieren - - - - - + + + + + Choose path Pfad auswählen @@ -174,153 +194,190 @@ Geben Sie 0 ein für einen unbefristeten Bann. Bitte geben Sie den Grund für den Bann ein. Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nicht gesehen werden. - + ban &user name - + &Benutzername - + ban &IP address - + &IP-Adresse - + Ban type - + Art des Banns - + &permanent ban - + &permanenter Bann - + &temporary ban - - - - - &Days: - + &temporärer Bann - &Hours: - + &Days: + T&age: + &Hours: + &Stunden: + + + &Minutes: - + &Minuten: - + Duration of the ban - + Länge des Banns - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Bitte geben Sie den Grund für den Bann ein. Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nicht gesehen werden. - + + Please enter the reason for the ban that will be visible to the banned person. + Bitte geben Sie den Grund ein, den die gebannte Person sehen kann. + + + &OK &OK - + &Cancel &Abbrechen - + Ban user from server Benutzer vom Server bannen - + Error - Fehler + Fehler - + You have to select a name-based or IP-based ban, or both. - + Bitte wählen Sie einen Namens- oder Adressbann. CardDatabaseModel - + Name Name - + Sets Editionen - + Mana cost Manakosten - + Card type Kartentyp - + P/T S/W - CardInfoWidget + CardInfoText - - Hide card info - Nichts anzeigen + + Name: + Name: - + + Mana cost: + Manakosten: + + + + Card type: + Kartentyp: + + + + P / T: + S/W: + + + + Loyalty: + Loyalität: + + + + CardInfoWidget + + Hide card info + Nichts anzeigen + + + Show card only nur Kartenbild - + Show text only nur Kartentext - + Show full info Alles anzeigen - + Name: Name: - + Mana cost: Manakosten: - + Card type: Kartentyp: - + P / T: S/W: + + + Loyalty: + Loyalität: + Error Fehler @@ -333,206 +390,168 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardItem - &Play - &Ausspielen + &Ausspielen - &Hide - &Verstecken + &Verstecken - &Tap - &Tappen + &Tappen - &Untap - E&nttappen + E&nttappen - Toggle &normal untapping - N&ormales Enttappen umschalten + N&ormales Enttappen umschalten - &Flip - &Umdrehen + &Umdrehen - &Clone - &Kopieren + &Kopieren - Ctrl+H - Ctrl+H + Ctrl+H - &Attach to card... - &An Karte anlegen... + &An Karte anlegen... - Ctrl+A - Ctrl+A + Ctrl+A - Unattac&h - &Von Karte lösen + &Von Karte lösen Set &P/T... &Kampfwerte setzen... - &Draw arrow... - &Pfeil zeichnen... + &Pfeil zeichnen... - + &Power / toughness &Kampfwerte - &Increase power - &Stärke erhöhen + &Stärke erhöhen - Ctrl++ - Ctrl++ + Ctrl++ - &Decrease power - S&tärke senken + S&tärke senken - Ctrl+- - Ctrl+- + Ctrl+- - I&ncrease toughness - &Widerstandskraft erhöhen + &Widerstandskraft erhöhen - Alt++ - Alt++ + Alt++ - D&ecrease toughness - W&iderstandskraft senken + W&iderstandskraft senken - Alt+- - Alt+- + Alt+- - In&crease power and toughness - Stärke und Widerstandskraft &erhöhen + Stärke und Widerstandskraft &erhöhen - Ctrl+Alt++ - Ctrl+Alt++ + Ctrl+Alt++ - Dec&rease power and toughness - Stärke und Widerstandskraft s&enken + Stärke und Widerstandskraft s&enken - Ctrl+Alt+- - Ctrl+Alt+- + Ctrl+Alt+- - Set &power and toughness... - &Kampfwerte setzen... + &Kampfwerte setzen... - Ctrl+P - Ctrl+P + Ctrl+P - &Set annotation... - &Hinweis setzen... + &Hinweis setzen... - red - rot + rot - yellow - gelb + gelb - green - grün + grün - &Add counter (%1) - Zählmarke &hinzufügen (%1) + Zählmarke &hinzufügen (%1) - &Remove counter (%1) - Zählmarke &entfernen (%1) + Zählmarke &entfernen (%1) - &Set counters (%1)... - Zählmarken &setzen (%1)... + Zählmarken &setzen (%1)... - &top of library - &auf die Bibliothek + &auf die Bibliothek - &bottom of library - &unter die Bibliothek + &unter die Bibliothek - &graveyard - in den &Friedhof + in den &Friedhof - Ctrl+Del - Ctrl+Del + Ctrl+Del - &exile - ins &Exil + ins &Exil - + &Move to &Verschieben @@ -700,364 +719,444 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic %1s Bibliothek - + her hand nominative, female owner ihre Hand - + %1's hand nominative, female owner %1s Hand - + his hand nominative, male owner seine Hand - - - %1's hand - nominative, male owner - %1s Hand - - - - of her hand - genitive, female owner - ihrer Hand - - - - of %1's hand - genitive, female owner - von %1s Hand - - - - of his hand - genitive, male owner - seiner Hand - - - - of %1's hand - genitive, male owner - von %1s Hand - - - - her hand - accusative, female owner - ihre Hand - - - - %1's hand - accusative, female owner - %1s Hand - - - - his hand - accusative, male owner - seine Hand - - - - %1's hand - accusative, male owner - %1s Hand - + %1's hand + nominative, male owner + %1s Hand + + + her library - nominative, female owner + look at zone, female owner ihre Bibliothek - + %1's library - nominative, female owner + look at zone, female owner %1s Bibliothek - + his library - nominative, male owner + look at zone, male owner seine Bibliothek - + %1's library - nominative, male owner + look at zone, male owner %1s Bibliothek - + of her library - genitive, female owner + top cards of zone, female owner ihrer Bibliothek - + of %1's library - genitive, female owner + top cards of zone, female owner von %1s Bibliothek - + of his library - genitive, male owner + top cards of zone, male owner seiner Bibliothek - + of %1's library - genitive, male owner + top cards of zone, male owner von %1s Bibliothek - + her library - accusative, female owner + reveal zone, female owner ihre Bibliothek - + %1's library - accusative, female owner + reveal zone, female owner %1s Bibliothek - + his library - accusative, male owner + reveal zone, male owner seine Bibliothek - + %1's library - accusative, male owner + reveal zone, male owner %1s Bibliothek - + + her library + shuffle, female owner + ihre Bibliothek + + + + %1's library + shuffle, female owner + %1s Bibliothek + + + + his library + shuffle, male owner + seine Bibliothek + + + + %1's library + shuffle, male owner + %1s Bibliothek + + + + her sideboard + look at zone, female owner + ihr Sideboard + + + + %1's sideboard + look at zone, female owner + %1s Sideboard + + + + his sideboard + look at zone, male owner + sein Sideboard + + + + %1's sideboard + look at zone, male owner + %1s Sideboard + + + of her hand + genitive, female owner + ihrer Hand + + + of %1's hand + genitive, female owner + von %1s Hand + + + of his hand + genitive, male owner + seiner Hand + + + of %1's hand + genitive, male owner + von %1s Hand + + + her hand + accusative, female owner + ihre Hand + + + %1's hand + accusative, female owner + %1s Hand + + + his hand + accusative, male owner + seine Hand + + + %1's hand + accusative, male owner + %1s Hand + + + + her library + nominative, female owner + ihre Bibliothek + + + + %1's library + nominative, female owner + %1s Bibliothek + + + + his library + nominative, male owner + seine Bibliothek + + + + %1's library + nominative, male owner + %1s Bibliothek + + + of her library + genitive, female owner + ihrer Bibliothek + + + of %1's library + genitive, female owner + von %1s Bibliothek + + + of his library + genitive, male owner + seiner Bibliothek + + + of %1's library + genitive, male owner + von %1s Bibliothek + + + her library + accusative, female owner + ihre Bibliothek + + + %1's library + accusative, female owner + %1s Bibliothek + + + his library + accusative, male owner + seine Bibliothek + + + %1's library + accusative, male owner + %1s Bibliothek + + + her graveyard nominative, female owner ihr Friedhof - + %1's graveyard nominative, female owner %1s Friedhof - + his graveyard nominative, male owner sein Friedhof - + %1's graveyard nominative, male owner %1s Friedhof - of her graveyard genitive, female owner - ihres Friedhofs + ihres Friedhofs - of %1's graveyard genitive, female owner - von %1s Friedhof + von %1s Friedhof - of his graveyard genitive, male owner - seines Friedhofs + seines Friedhofs - of %1's graveyard genitive, male owner - von %1s Friedhof + von %1s Friedhof - her graveyard accusative, female owner - ihren Friedhof + ihren Friedhof - %1's graveyard accusative, female owner - %1s Friedhof + %1s Friedhof - his graveyard accusative, male owner - seinen Friedhof + seinen Friedhof - %1's graveyard accusative, male owner - %1s Friedhof + %1s Friedhof - + her exile nominative, female owner ihr Exil - + %1's exile nominative, female owner %1s Exil - + his exile nominative, male owner sein Exil - + %1's exile nominative, male owner %1s Exil - of her exile genitive, female owner - ihres Exils + ihres Exils - of %1's exile genitive, female owner - von %1s Exil + von %1s Exil - of his exile genitive, male owner - seines Exils + seines Exils - of %1's exile genitive, male owner - von %1s Exil + von %1s Exil - her exile accusative, female owner - ihr Exil + ihr Exil - %1's exile accusative, female owner - %1s Exil + %1s Exil - his exile accusative, male owner - sein Exil + sein Exil - %1's exile accusative, male owner - %1s Exil + %1s Exil - + her sideboard nominative, female owner ihr Sideboard - + %1's sideboard nominative, female owner %1s Sideboard - + his sideboard nominative, male owner sein Sideboard - + %1's sideboard nominative, male owner %1s Sideboard - of her sideboard genitive, female owner - ihres Sideboards + ihres Sideboards - of %1's sideboard genitive, female owner - von %1s Sideboard + von %1s Sideboard - of his sideboard genitive, male owner - seines Sideboards + seines Sideboards - of %1's sideboard genitive, male owner - von %1s Sideboard + von %1s Sideboard - her sideboard accusative, female owner - ihr Sideboard + ihr Sideboard - %1's sideboard accusative, female owner - %1s Sideboard + %1s Sideboard - his sideboard accusative, male owner - sein Sideboard + sein Sideboard - %1's sideboard accusative, male owner - %1s Sideboard + %1s Sideboard his graveyard @@ -1261,14 +1360,49 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic - DeckEditorSettingsPage + DBPriceUpdater - - Enable &price tag feature (using data from blacklotusproject.com) - Karten&preisfunktionen anschalten (benutzt Daten von blacklotusproject.com) + + + + Error + Fehler - + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + Enable &price tag feature (using data from blacklotusproject.com) + Karten&preisfunktionen anschalten (benutzt Daten von blacklotusproject.com) + + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General Allgemeines @@ -1291,43 +1425,77 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckListModel - + Number Nummer - + Card Karte - + Price Preis + + DeckStatsInterface + + + + Error + Fehler + + + + The reply from the server could not be parsed. + Der Server hat eine ungültige Antwort geliefert. + + DeckViewContainer - + Load &local deck &Lokales Deck laden - + Load d&eck from server Deck vom Server l&aden - + Ready to s&tart Bereit zum S&tarten - + + S&ideboard unlocked + S&ideboard entsperrt + + + + S&ideboard locked + S&ideboard gesperrt + + + Load deck Deck laden + + + Error + Fehler + + + + The selected file could not be loaded. + Die gewählte Datei konnte nicht geladen werden. + Deck_PictureCacher @@ -1377,34 +1545,37 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DlgConnect - + &Host: &Server: - + &Port: &Port: - + Player &name: Spieler&name: - + P&assword: P&asswort: - - &OK - &OK + + &Save password + Passwort &speichern + + + &OK + &OK - &Cancel - &Abbruch + &Abbruch @@ -1415,47 +1586,47 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DlgCreateGame - + &Description: &Beschreibung: - + &Password: &Passwort: - + P&layers: &Spieler: - + Game type Spieltyp - + Only &buddies can join Nur &Freunde können teilnehmen - + Only &registered users can join Nur &registrierte Benutzer können teilnehmen - + Joining restrictions Teilnahmebedingungen - + &Spectators allowed &Zuschauer zugelassen - + Spectators &need a password to join Zuschauer brauchen &auch ein Passwort @@ -1464,37 +1635,40 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Zuschauer können sp&rechen - + Spectators can &chat Zuschauer können s&chreiben - + Spectators see &everything Zuschauer sehen &alles - + Spectators Zuschauer - &OK - &OK + &OK - &Cancel - &Abbruch + &Abbruch - + Create game Spiel erstellen - + + Game information + &Spielinformationen + + + Error Fehler @@ -1503,7 +1677,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Ungültige Anzahl an Spielern. - + Server error. Serverfehler. @@ -1515,82 +1689,100 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DlgCreateToken - + &Name: &Name: - + Token Spielstein - + C&olor: &Farbe: - + white weiß - + blue blau - + black schwarz - + red rot - + green grün - + multicolor mehrfarbig - + colorless farblos - + &P/T: &Kampfwerte: - + &Annotation: &Hinweis: - + &Destroy token when it leaves the table Spielstein &zerstören, wenn er das Spielfeld verlässt - + + Token data + Spielstein-Daten + + + + Show &all tokens + &Alle möglichen Spielsteine zeigen + + + + Show tokens from this &deck + Spielsteine dieses &Decks zeigen + + + + Choose token from list + Spielstein aus Liste auswählen + + &OK - &OK + &OK - &Cancel - A&bbrechen + A&bbrechen - + Create token Spielstein erstellen @@ -1623,34 +1815,178 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic - DlgLoadDeckFromClipboard + DlgEditTokens - - &Refresh - &Aktualisieren + + &Name: + &Name: - - &OK - &OK + + C&olor: + &Farbe: - - &Cancel - A&bbrechen + + white + weiß - - Load deck from clipboard - Deck aus der Zwischenablage laden + + blue + blau - + + black + schwarz + + + + red + rot + + + + green + grün + + + + multicolor + mehrfarbig + + + + colorless + farblos + + + + &P/T: + &Kampfwerte: + + + + &Annotation: + &Hinweis: + + + + Token data + Spielstein-Daten + + + + + Add token + Spielstein hinzufügen + + + + Remove token + Spielstein entfernen + + + + Edit tokens + Spielsteine bearbeiten + + + + Please enter the name of the token: + Bitte geben Sie den Namen des Spielsteins ein: + + + Error Fehler - + + The chosen name conflicts with an existing card or token. + Der gewählte Name existiert bereits als Karte oder Spielstein. + + + + DlgFilterGames + + + Show &unavailable games + &Nicht verfügbare Spiele zeigen + + + + Show &password protected games + + + + + Game &description: + &Spielbeschreibung: + + + + &Creator name: + &Name des Erstellers: + + + + &Game types + Spiel&typen + + + + at &least: + &mindestens: + + + + at &most: + &höchstens: + + + + Maximum player count + Maximale Spielerzahl + + + O&K + O&K + + + + Filter games + Spiele filtern + + + + DlgLoadDeckFromClipboard + + + &Refresh + &Aktualisieren + + + &OK + &OK + + + &Cancel + A&bbrechen + + + + Load deck from clipboard + Deck aus der Zwischenablage laden + + + + Error + Fehler + + + Invalid deck list. Ungültige Deckliste. @@ -1658,17 +1994,15 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DlgLoadRemoteDeck - O&K - O&K + O&K - &Cancel - &Abbrechen + &Abbrechen - + Load deck Deck laden @@ -1676,9 +2010,9 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DlgSettings - - - + + + Error Fehler @@ -1695,54 +2029,111 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Der Pfad zum Kartenbilderverzeichnis ist ungültig. - Your card database is invalid. Would you like to go back and set the correct path? - Ihre Kartendatenbank ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? + Ihre Kartendatenbank ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Deckordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Kartenbilderordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + Settings Einstellungen - + General Allgemeines - + Appearance Erscheinungsbild - + User interface Bedienung - + Deck editor Deckeditor - + Messages Nachrichten - &Close - S&chließen + S&chließen @@ -2006,24 +2397,25 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic GameSelector - + C&reate Spiel e&rstellen - + &Join &Teilnehmen - - - - - - - - + + + + + + + + + Error Fehler @@ -2032,76 +2424,89 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic XXX - + Please join the appropriate room first. Bitte betreten Sie erst den entsprechenden Raum. - + Wrong password. Falsches Passwort. - + Spectators are not allowed in this game. In diesem Spiel sind keine Zuschauer zugelassen. - + The game is already full. Das Spiel ist bereits voll. - + The game does not exist any more. Dieses Spiel gibt es nicht mehr. - + This game is only open to registered users. Dieses Spiel kann nur von registrierten Benutzern betreten werden. - + This game is only open to its creator's buddies. Dieses Spiel kann nur von Freunden des Erstellers betreten werden. - + You are being ignored by the creator of this game. Der Ersteller dieses Spiels ignoriert Sie. - + Join game Spiel beitreten - + Password: Passwort: - + + Please join the respective room first. + Bitte betreten Sie zuerst den entsprechenden Raum. + + + Games Spiele - - Show &full games - &Volle Spiele anzeigen + + &Filter games + Spiele &filtern + + + + C&lear filter + Filter &zurücksetzen + + + Show &full games + &Volle Spiele anzeigen - Show &running games - &Laufende Spiele anzeigen + &Laufende Spiele anzeigen &Show full games &Volle Spiele anzeigen - + J&oin as spectator &Zuschauen @@ -2117,12 +2522,12 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic GamesModel - + yes ja - + no nein @@ -2131,62 +2536,62 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Spiel ID - + Creator Ersteller - + Description Beschreibung - + yes, free for spectators ja, außer für Zuschauer - + buddies only nur Freunde - + reg. users only nur reg. Benutzer - + not allowed nicht erlaubt - + Room Raum - + Game type Spieltyp - + Password Passwort - + Restrictions Bedingungen - + Players Spieler - + Spectators Zuschauer @@ -2194,50 +2599,100 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic GeneralSettingsPage - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path Pfad auswählen - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Fehler + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings Persönliche Einstellungen - + Language: Sprache: - + Download card pictures on the fly Kartenbilder dynamisch herunterladen - + + Download high-quality card pictures + + + + Paths Pfade - + Decks directory: Verzeichnis mit Decklisten: - + + Replays directory: + Verzeichnis mit aufgezeichneten Spielen: + + + Pictures directory: Verzeichnis mit Bilddateien: - - Path to card database: - Pfad zur Kartendatenbank: + + Card database: + - - + + Token database: + + + + Path to card database: + Pfad zur Kartendatenbank: + + + Path to token database: + Pfad zur Spielsteindatenbank: + + + + English Deutsch @@ -2245,44 +2700,63 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic MainWindow - + There are too many concurrent connections from your address. Es gibt zu viele gleichzeitige Verbindungen von Ihrer Adresse. - Banned by moderator. - Gebannt von einem Moderator. + Gebannt von einem Moderator. - + Scheduled server shutdown. Planmäßige Serverabschaltung. - Unknown reason. - Unbekannter Grund. + Unbekannter Grund. - + + Banned by moderator + Gebannt von einem Moderator + + + + Expected end time: %1 + Voraussichtliches Ende: %1 + + + + This ban lasts indefinitely. + Dieser Bann ist unbefristet. + + + + + Invalid username. + + + + Connection closed Verbindung geschlossen - + The server has terminated your connection. Reason: %1 Der Server hat Ihre Verbindung beendet. Grund: %1 - + Scheduled server shutdown Planmäßige Serverabschaltung - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2296,126 +2770,173 @@ Grund für die Abschaltung: %1 - + Number of players Spieleranzahl - + Please enter the number of players. Bitte die Spieleranzahl eingeben: - - + + Player %1 Spieler %1 - + + Load replay + Aufgezeichnetes Spiel laden + + + About Cockatrice Über Cockatrice - + Version %1 Version %1 - + Authors: Autoren: - + Translators: Übersetzer: - + Spanish: Spanisch: - + Portugese (Portugal): Portugiesisch (Portugal): - + Portugese (Brazil): Portugiesisch (Brasilien): - + French: Französisch: - + Japanese: Japanisch: - + Russian: Russisch: - Czech: - Tschechisch: + Tschechisch: - + Italian: Italienisch: + + + Swedish: + Schwedisch: + + + + Incorrect username or password. Please check your authentication information and try again. + + + + + You are banned until %1. + Sie sind gebannt bis: %1. + + + + You are banned indefinitely. + Sie sind auf unbestimmte Zeit gebannt. + + + + This server requires user registration. + + + + + Unknown login error: %1 + Unbekannter Login-Fehler: %1 + Slovak: Slowakisch: - - - - - - + + + + + + + + + + Error Fehler - + Server timeout Server Zeitüberschreitung - Invalid login data. - Ungültige Anmeldedaten. + Ungültige Anmeldedaten. - + There is already an active session using this user name. Please close that session first and re-login. Es gibt bereits eine aktive Verbindung mit diesem Benutzernamen. Bitte schließen Sie diese Verbindung zuerst und versuchen Sie es dann erneut. - + You are banned. +%1 + Sie sind gebannt. +%1 + + + Unknown server error: %1 + Unbekannter Serverfehler: %1 + + + Socket error: %1 Netzwerkfehler: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Sie versuchen sich an einem veralteten Server anzumelden. Bitte verwenden Sie eine ältere Cockatrice-Version oder melden Sie sich an einem aktuellen Server an. Lokale Version ist %1, Serverversion ist %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Ihr Cockatrice-Client ist veraltet. Bitte laden Sie sich die neueste Version herunter. @@ -2426,52 +2947,62 @@ Lokale Version ist %1, Serverversion ist %2. Protokollversionen stimmen nicht überein. Lokale Version: %1, Serverversion: %2. - + Connecting to %1... Verbinde zu %1... - + Disconnected nicht verbunden - + + Connected, logging in at %1 + Verbunden, Anmeldung bei %1 + + + Logged in at %1 Angemeldet bei %1 - + &Connect... &Verbinden... - + &Disconnect Verbindung &trennen - + Start &local game... &Lokales Spiel starten... - + + &Watch replay... + &Aufgezeichnetes Spiel abspielen... + + + &About Cockatrice &Über Cockatrice - + &Help &Hilfe - + Are you sure? Sind Sie sicher? - + There are still open games. Are you sure you want to quit? Es gibt noch offene Spiele. Wollen Sie das Programm wirklich beenden? @@ -2488,27 +3019,27 @@ Lokale Version ist %1, Serverversion ist %2. Spiel ver&lassen - + &Deck editor &Deck-Editor - + &Full screen &Vollbild - + Ctrl+F Ctrl+F - + &Settings... &Einstellungen... - + &Exit &Beenden @@ -2521,7 +3052,7 @@ Lokale Version ist %1, Serverversion ist %2. Esc - + &Cockatrice &Cockatrice @@ -2601,8 +3132,8 @@ Lokale Version ist %1, Serverversion ist %2. %1 zieht %2 Karten - - + + a card eine Karte @@ -2659,7 +3190,7 @@ Lokale Version ist %1, Serverversion ist %2. %1s Sideboard - + The game has started. Das Spiel hat begonnen. @@ -2696,17 +3227,22 @@ Lokale Version ist %1, Serverversion ist %2. %1 hat das Spiel verlassen. - + The game has been closed. Das Spiel wurde geschlossen. - + + You have been kicked out of the game. + Sie wurden aus dem Spiel geworfen. + + + %1 is now watching the game. %1 schaut nun dem Spiel zu. - + %1 is not watching the game any more. %1 schaut dem Spiel nicht mehr zu. @@ -2762,37 +3298,49 @@ Lokale Version ist %1, Serverversion ist %2. - + You have joined game #%1. female Sie sind dem Spiel %1 beigetreten. - - - You have joined game #%1. - male - Sie sind dem Spiel %1 beigetreten. - - - - %1 has joined the game. - female - %1 ist dem Spiel beigetreten. - - - - %1 has joined the game. - male - %1 ist dem Spiel beigetreten. - + You have joined game #%1. + male + Sie sind dem Spiel %1 beigetreten. + + + + You are watching a replay of game #%1. + female + Dies ist eine Aufzeichnung des Spiels #%1. + + + + You are watching a replay of game #%1. + male + Dies ist eine Aufzeichnung des Spiels #%1. + + + + %1 has joined the game. + female + %1 ist dem Spiel beigetreten. + + + + %1 has joined the game. + male + %1 ist dem Spiel beigetreten. + + + %1 has left the game. female %1 hat das Spiel verlassen. - + %1 has left the game. male %1 hat das Spiel verlassen. @@ -2817,92 +3365,92 @@ Lokale Version ist %1, Serverversion ist %2. male %1 hat das Deck Nr. %2 geladen. - - - %1 is ready to start the game. - female - %1 ist bereit, das Spiel zu starten. - - - - %1 is ready to start the game. - male - %1 ist bereit, das Spiel zu starten. - - - - %1 is not ready to start the game any more. - female - %1 ist nicht mehr bereit, das Spiel zu starten. - - - - %1 is not ready to start the game any more. - male - %1 ist nicht mehr bereit, das Spiel zu starten. - - - - %1 has conceded the game. - female - %1 hat das Spiel aufgegeben. - - - - %1 has conceded the game. - male - %1 hat das Spiel aufgegeben. - - - - %1 has restored connection to the game. - female - %1 ist wieder mit dem Spiel verbunden. - + %1 is ready to start the game. + female + %1 ist bereit, das Spiel zu starten. + + + + %1 is ready to start the game. + male + %1 ist bereit, das Spiel zu starten. + + + + %1 is not ready to start the game any more. + female + %1 ist nicht mehr bereit, das Spiel zu starten. + + + + %1 is not ready to start the game any more. + male + %1 ist nicht mehr bereit, das Spiel zu starten. + + + + %1 has conceded the game. + female + %1 hat das Spiel aufgegeben. + + + + %1 has conceded the game. + male + %1 hat das Spiel aufgegeben. + + + + %1 has restored connection to the game. + female + %1 ist wieder mit dem Spiel verbunden. + + + %1 has restored connection to the game. male %1 ist wieder mit dem Spiel verbunden. - + %1 has lost connection to the game. female %1 hat die Verbindung zum Spiel verloren. - + %1 has lost connection to the game. male %1 hat die Verbindung zum Spiel verloren. - + %1 shuffles %2. female %1 mischt %2. - + %1 shuffles %2. male %1 mischt %2. - + %1 rolls a %2 with a %3-sided die. female %1 würfelt eine %2 mit einem %3-seitigen Würfel. - + %1 rolls a %2 with a %3-sided die. male %1 würfelt eine %2 mit einem %3-seitigen Würfel. - + %1 draws %n card(s). female @@ -2911,7 +3459,7 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 draws %n card(s). male @@ -2920,182 +3468,182 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 undoes his last draw. %1 legt die zuletzt gezogene Karte zurück. - + %1 undoes her last draw. %1 legt die zuletzt gezogene Karte zurück. - + %1 undoes his last draw (%2). %1 legt die zuletzt gezogene Karte zurück (%2). - + %1 undoes her last draw (%2). %1 legt die zuletzt gezogene Karte zurück (%2). - + from table vom Spielfeld - + from graveyard aus dem Friedhof - + from exile aus dem Exil - + from hand von der Hand - + the bottom card of his library die unterste Karte seiner Bibliothek - + the bottom card of her library die unterste Karte ihrer Bibliothek - + from the bottom of his library , die unterste Karte seiner Bibliothek, - + from the bottom of her library , die unterste Karte ihrer Bibliothek, - + the top card of his library die oberste Karte seiner Bibliothek - + the top card of her library die oberste Karte ihrer Bibliothek - + from the top of his library , die oberste Karte seiner Bibliothek, - + from the top of her library , die oberste Karte ihrer Bibliothek, - + from library aus der Bibliothek - + from sideboard aus dem Sideboard - + from the stack vom Stapel - + %1 gives %2 control over %3. %1 überlässt %2 die Kontrolle über %3. - + %1 puts %2 into play tapped%3. %1 bringt %2 getappt%3 ins Spiel. - + %1 puts %2 into play%3. %1 bringt %2%3 ins Spiel. - + %1 puts %2%3 into graveyard. %1 legt %2%3 auf den Friedhof. - + %1 exiles %2%3. %1 schickt %2%3 ins Exil. - + %1 moves %2%3 to hand. %1 nimmt %2%3 auf die Hand. - + %1 puts %2%3 into his library. %1 legt %2%3 in seine Bibliothek. - + %1 puts %2%3 into her library. %1 legt %2%3 in ihre Bibliothek. - + %1 puts %2%3 on bottom of his library. %1 legt %2%3 unter seine Bibliothek. - + %1 puts %2%3 on bottom of her library. %1 legt %2%3 unter ihre Bibliothek. - + %1 puts %2%3 on top of his library. %1 legt %2%3 auf die Bibliothek. - + %1 puts %2%3 on top of her library. %1 legt %2%3 auf die Bibliothek. - + %1 puts %2%3 into his library at position %4. %1 legt %2%3 in seine Bibliothek an %4. Stelle. - + %1 puts %2%3 into her library at position %4. %1 legt %2%3 in ihre Bibliothek an %4. Stelle. - + %1 moves %2%3 to sideboard. %1 legt %2%3 in sein Sideboard. - + %1 plays %2%3. %1 spielt %2%3 aus. - + %1 takes a mulligan to %n. female @@ -3104,7 +3652,7 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 takes a mulligan to %n. male @@ -3113,37 +3661,37 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 flips %2 face-down. female %1 wendet %2 auf die Rückseite. - + %1 flips %2 face-down. male %1 wendet %2 auf die Rückseite. - + %1 flips %2 face-up. female %1 wendet %2 auf die Vorderseite. - + %1 flips %2 face-up. male %1 wendet %2 auf die Vorderseite. - + %1 destroys %2. female %1 zerstört %2. - + %1 destroys %2. male %1 zerstört %2. @@ -3159,283 +3707,340 @@ Lokale Version ist %1, Serverversion ist %2. %1 legt %2 an %3s %4 an. - %1 has loaded a deck (%2). female - %1 hat ein Deck geladen (%2). + %1 hat ein Deck geladen (%2). - %1 has loaded a deck (%2). male - %1 hat ein Deck geladen (%2). + %1 hat ein Deck geladen (%2). - + + %1 has loaded a deck (%2). + %1 hat ein Deck geladen (%2). + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + + %1 has locked her sideboard. + female + %1 hat ihr Sideboard gesperrt. + + + + %1 has locked his sideboard. + male + %1 hat sein Sideboard gesperrt. + + + + %1 has unlocked her sideboard. + female + %1 hat ihr Sideboard entsperrt. + + + + %1 has unlocked his sideboard. + male + %1 hat sein Sideboard entsperrt. + + + + the bottom card of %1's library + die unterste Karte von %1s Bibliothek + + + + from the bottom of %1's library + von der Unterseite von %1s Bibliothek + + + + the top card of %1's library + die oberste Karte von %1s Bibliothek + + + + from the top of %1's library + oben von %1s Bibliothek + + + + from %1's library + aus %1s Bibliothek + + + %1 attaches %2 to %3's %4. p1 female, p2 female %1 legt %2 an %3s %4 an. - + %1 attaches %2 to %3's %4. p1 female, p2 male %1 legt %2 an %3s %4 an. - + %1 attaches %2 to %3's %4. p1 male, p2 female %1 legt %2 an %3s %4 an. - + %1 attaches %2 to %3's %4. p1 male, p2 male %1 legt %2 an %3s %4 an. - + %1 unattaches %2. female %1 löst %2 ab. - + %1 unattaches %2. male %1 löst %2 ab. - + %1 creates token: %2%3. female %1 erstellt Token: %2%3. - + %1 creates token: %2%3. male %1 erstellt Token: %2%3. - + %1 points from her %2 to herself. female %1 zeigt von ihrem %2 auf sich selbst. - + %1 points from his %2 to himself. male %1 zeigt von seinem %2 auf sich selbst. - + %1 points from her %2 to %3. p1 female, p2 female %1 zeigt von ihrem %2 auf %3. - + %1 points from her %2 to %3. p1 female, p2 male %1 zeigt von ihrem %2 auf %3. - + %1 points from his %2 to %3. p1 male, p2 female %1 zeigt von seinem %2 auf %3. - + %1 points from his %2 to %3. p1 male, p2 male %1 zeigt von seinem %2 auf %3. - + %1 points from %2's %3 to herself. card owner female, target female %1 zeigt von %2s %3 auf sich selbst. - + %1 points from %2's %3 to herself. card owner male, target female %1 zeigt von %2s %3 auf sich selbst. - + %1 points from %2's %3 to himself. card owner female, target male %1 zeigt von %2s %3 auf sich selbst. - + %1 points from %2's %3 to himself. card owner male, target male %1 zeigt von %2s %3 auf sich selbst. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female %1 zeigt von %2s %3 auf %4. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male %1 zeigt von %2s %3 auf %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female %1 zeigt von %2s %3 auf %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male %1 zeigt von %2s %3 auf %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female %1 zeigt von %2s %3 auf %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male %1 zeigt von %2s %3 auf %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female %1 zeigt von %2s %3 auf %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male %1 zeigt von %2s %3 auf %4. - + %1 points from her %2 to her %3. female %1 zeigt von ihrem %2 auf ihren %3. - + %1 points from his %2 to his %3. male %1 zeigt von seinem %2 auf seinen %3. - + %1 points from her %2 to %3's %4. p1 female, p2 female %1 zeigt von ihrem %2 auf %3s %4. - + %1 points from her %2 to %3's %4. p1 female, p2 male %1 zeigt von ihrem %2 auf %3s %4. - + %1 points from his %2 to %3's %4. p1 male, p2 female %1 zeigt von seinem %2 auf %3s %4. - + %1 points from his %2 to %3's %4. p1 male, p2 male %1 zeigt von seinem %2 auf %3s %4. - + %1 points from %2's %3 to her own %4. card owner female, target female %1 zeigt von %2s %3 auf ihren eigenen %4. - + %1 points from %2's %3 to her own %4. card owner male, target female %1 zeigt von %2s %3 auf ihren eigenen %4. - + %1 points from %2's %3 to his own %4. card owner female, target male %1 zeigt von %2s %3 auf seinen eigenen %4. - + %1 points from %2's %3 to his own %4. card owner male, target male %1 zeigt von %2s %3 auf seinen eigenen %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female %1 zeigt von %2s %3 auf %4s %5. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male %1 zeigt von %2s %3 auf %4s %5. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female %1 zeigt von %2s %3 auf %4s %5. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male %1 zeigt von %2s %3 auf %4s %5. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female %1 zeigt von %2s %3 auf %4s %5. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male %1 zeigt von %2s %3 auf %4s %5. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female %1 zeigt von %2s %3 auf %4s %5. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male %1 zeigt von %2s %3 auf %4s %5. - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -3444,7 +4049,7 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -3453,7 +4058,7 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -3462,7 +4067,7 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -3471,265 +4076,315 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 taps her permanents. female %1 tappt ihre bleibenden Karten. - + %1 untaps her permanents. female %1 enttappt ihre bleibenden Karten. - + %1 taps his permanents. male %1 tappt seine bleibenden Karten. - + %1 untaps his permanents. male %1 enttappt seine bleibenden Karten. - + %1 taps %2. female %1 tappt %2. - + %1 untaps %2. female %1 enttappt %2. - - - %1 taps %2. - male - %1 tappt %2. - - - - %1 untaps %2. - male - %1 enttappt %2. - - - - %1 sets counter %2 to %3 (%4%5). - female - %1 setzt Zähler %2 auf %3 (%4%5). - - - - %1 sets counter %2 to %3 (%4%5). - male - %1 setzt Zähler %2 auf %3 (%4%5). - - - - %1 sets %2 to not untap normally. - female - %1 setzt %2 auf explizites Enttappen. - - - - %1 sets %2 to not untap normally. - male - %1 setzt %2 auf explizites Enttappen. - - - - %1 sets %2 to untap normally. - female - %1 setzt %2 auf normales Enttappen. - - - - %1 sets %2 to untap normally. - male - %1 setzt %2 auf normales Enttappen. - - - - %1 sets PT of %2 to %3. - female - %1 setzt Kampfwerte von %2 auf %3. - - - - %1 sets PT of %2 to %3. - male - %1 setzt Kampfwerte von %2 auf %3. - - - - %1 sets annotation of %2 to %3. - female - %1 versieht %2 mit dem Hinweis %3. - - - - %1 sets annotation of %2 to %3. - male - %1 versieht %2 mit dem Hinweis %3. - - - - %1 is looking at the top %2 cards %3. - female - %1 sieht sich die obersten %2 Karten %3 an. - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - %1 sieht sich die obersten %2 Karten %3 an. + %1 tappt %2. - - %1 is looking at %2. - female - %1 sieht sich %2 an. - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 sieht sich %2 an. + %1 enttappt %2. - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female - %1 sieht sich %2 nicht mehr an. + %1 setzt Zähler %2 auf %3 (%4%5). + %1 sets counter %2 to %3 (%4%5). + male + %1 setzt Zähler %2 auf %3 (%4%5). + + + + %1 sets %2 to not untap normally. + female + %1 setzt %2 auf explizites Enttappen. + + + + %1 sets %2 to not untap normally. + male + %1 setzt %2 auf explizites Enttappen. + + + + %1 sets %2 to untap normally. + female + %1 setzt %2 auf normales Enttappen. + + + + %1 sets %2 to untap normally. + male + %1 setzt %2 auf normales Enttappen. + + + + %1 sets PT of %2 to %3. + female + %1 setzt Kampfwerte von %2 auf %3. + + + + %1 sets PT of %2 to %3. + male + %1 setzt Kampfwerte von %2 auf %3. + + + + %1 sets annotation of %2 to %3. + female + %1 versieht %2 mit dem Hinweis %3. + + + + %1 sets annotation of %2 to %3. + male + %1 versieht %2 mit dem Hinweis %3. + + + + %1 is looking at the top %n card(s) %2. + female + + %1 sieht sich die oberste Karte %2 an. + %1 sieht sich die obersten %n Karten %2 an. + + + + + %1 is looking at the top %n card(s) %2. + male + + %1 sieht sich die oberste Karte %2 an. + %1 sieht sich die obersten %n Karten %2 an. + + + + %1 is looking at the top %2 cards %3. + female + %1 sieht sich die obersten %2 Karten %3 an. + + + %1 is looking at the top %2 cards %3. + male + %1 sieht sich die obersten %2 Karten %3 an. + + + + %1 is looking at %2. + female + %1 sieht sich %2 an. + + + + %1 is looking at %2. + male + %1 sieht sich %2 an. + + + + %1 stops looking at %2. + female + %1 sieht sich %2 nicht mehr an. + + + %1 stops looking at %2. male %1 sieht sich %2 nicht mehr an. - + %1 reveals %2 to %3. p1 female, p2 female %1 zeigt %3 %2. - + %1 reveals %2 to %3. p1 female, p2 male %1 zeigt %3 %2. - + %1 reveals %2 to %3. p1 male, p2 female %1 zeigt %3 %2. - + %1 reveals %2 to %3. p1 male, p2 male %1 zeigt %3 %2. - + %1 reveals %2. female %1 zeigt %2 offen vor. - + %1 reveals %2. male %1 zeigt %2 offen vor. - + %1 randomly reveals %2%3 to %4. p1 female, p2 female %1 zeigt %4 zufällig %2%3 vor. - + %1 randomly reveals %2%3 to %4. p1 female, p2 male %1 zeigt %4 zufällig %2%3 vor. - + %1 randomly reveals %2%3 to %4. p1 male, p2 female %1 zeigt %4 zufällig %2%3 vor. - + %1 randomly reveals %2%3 to %4. p1 male, p2 male %1 zeigt %4 zufällig %2%3 vor. - + %1 randomly reveals %2%3. female %1 zeigt zufällig %2%3 offen vor. - + %1 randomly reveals %2%3. male %1 zeigt zufällig %2%3 offen vor. - + + %1 peeks at face down card #%2. + female + %1 schaut sich die umgedrehte Karte #%2 an. + + + + %1 peeks at face down card #%2. + male + %1 schaut sich die umgedrehte Karte #%2 an. + + + + %1 peeks at face down card #%2: %3. + female + %1 schaut sich die umgedrehte Karte #%2 an: %3. + + + + %1 peeks at face down card #%2: %3. + male + %1 schaut sich die umgedrehte Karte #%2 an: %3. + + + %1 reveals %2%3 to %4. p1 female, p2 female %1 zeigt %4 %2%3 vor. - + %1 reveals %2%3 to %4. p1 female, p2 male %1 zeigt %4 %2%3 vor. - + %1 reveals %2%3 to %4. p1 male, p2 female %1 zeigt %4 %2%3 vor. - + %1 reveals %2%3 to %4. p1 male, p2 male %1 zeigt %4 %2%3 vor. - + %1 reveals %2%3. female %1 zeigt %2%3 offen vor. - + %1 reveals %2%3. male %1 zeigt %2%3 offen vor. - + + %1 is now keeping the top card %2 revealed. + %1 lässt nun die oberste Karte %2 aufgedeckt. + + + + %1 is not revealing the top card %2 any longer. + %1 lässt die oberste Karte %2 nicht mehr aufgedeckt. + + + It is now %1's turn. female %1 ist am Zug. - + It is now %1's turn. male %1 ist am Zug. @@ -3742,12 +4397,12 @@ Lokale Version ist %1, Serverversion ist %2. - + %1 draws his initial hand. %1 zieht seine Starthand. - + %1 draws her initial hand. %1 zieht ihre Starthand. @@ -3836,7 +4491,7 @@ Lokale Version ist %1, Serverversion ist %2. - + red rote @@ -3844,7 +4499,7 @@ Lokale Version ist %1, Serverversion ist %2. - + yellow gelbe @@ -3852,7 +4507,7 @@ Lokale Version ist %1, Serverversion ist %2. - + green grüne @@ -4012,7 +4667,7 @@ Lokale Version ist %1, Serverversion ist %2. %1 zeigt %2 aus %3 offen vor. - + ending phase die Zugendphase @@ -4045,52 +4700,52 @@ Lokale Version ist %1, Serverversion ist %2. %1 ist am Zug. - + untap step das Enttappsegment - + upkeep step das Versorgungssegment - + draw step das Ziehsegment - + first main phase die erste Hauptphase - + beginning of combat step das Anfangssegment der Kampfphase - + declare attackers step das Angreifer-Deklarieren-Segment - + declare blockers step das Blocker-Deklarieren-Segment - + combat damage step das Kampfschadenssegment - + end of combat step das Endsegment der Kampfphase - + second main phase die zweite Hauptphase @@ -4099,7 +4754,7 @@ Lokale Version ist %1, Serverversion ist %2. das Ende-des-Zuges-Segment - + It is now the %1. Es ist nun %1. @@ -4167,12 +4822,12 @@ Lokale Version ist %1, Serverversion ist %2. MessagesSettingsPage - + &Add &Hinzufügen - + &Remove &Entfernen @@ -4185,12 +4840,12 @@ Lokale Version ist %1, Serverversion ist %2. Entfernen - + Add message Nachricht hinzufügen - + Message: Nachricht: @@ -4198,57 +4853,57 @@ Lokale Version ist %1, Serverversion ist %2. PhasesToolbar - + Untap step Enttappsegment - + Upkeep step Versorgungssegment - + Draw step Ziehsegment - + First main phase erste Hauptphase - + Beginning of combat step Anfangssegment der Kampfphase - + Declare attackers step Angreifer-Deklarieren-Segment - + Declare blockers step Blocker-Deklarieren-Segment - + Combat damage step Kampfschadenssegment - + End of combat step Endsegment der Kampfphase - + Second main phase zweite Hauptphase - + End of turn step Ende-des-Zuges-Segment @@ -4256,21 +4911,21 @@ Lokale Version ist %1, Serverversion ist %2. Player - - - + + + Move to &top of library Oben auf die Biblio&thek legen - - - + + + Move to &bottom of library Unter die &Bibliothek legen - + &View library Bibliothek &ansehen @@ -4279,37 +4934,186 @@ Lokale Version ist %1, Serverversion ist %2. Oberste Karten in den F&riedhof legen... - + Move top cards to &exile... Oberste Karten ins &Exil schicken... - + F3 F3 - + View &top cards of library... Oberste Karten der Bibliothek a&nsehen... - + &View graveyard &Zeige Friedhof - + + &Always reveal top card + &Oberste Karte aufgedeckt lassen + + + + O&pen deck in deck editor + Im &Deckeditor öffnen + + + + Cr&eate predefined token + &Vordefinierten Spielstein erstellen + + + &All players &allen Spielern - + + &Peek at card face + &Vorderseite anschauen + + + + &Clone + &Kopieren + + + Ctrl+H + Ctrl+H + + + + Attac&h to card... + An Karte &anlegen... + + + + Ctrl+A + Ctrl+A + + + + Unattac&h + &Von Karte lösen + + + + &Draw arrow... + &Pfeil zeichnen... + + + + &Increase power + &Stärke erhöhen + + + + Ctrl++ + Ctrl++ + + + + &Decrease power + S&tärke senken + + + + Ctrl+- + Ctrl+- + + + + I&ncrease toughness + &Widerstandskraft erhöhen + + + + Alt++ + Alt++ + + + + D&ecrease toughness + W&iderstandskraft senken + + + + Alt+- + Alt+- + + + + In&crease power and toughness + Stärke und Widerstandskraft &erhöhen + + + + Ctrl+Alt++ + Ctrl+Alt++ + + + + Dec&rease power and toughness + Stärke und Widerstandskraft s&enken + + + + Ctrl+Alt+- + Ctrl+Alt+- + + + + Set &power and toughness... + &Kampfwerte setzen... + + + + Ctrl+P + Ctrl+P + + + + red + rot + + + + yellow + gelb + + + + green + grün + + + + &Add counter (%1) + Zählmarke &hinzufügen (%1) + + + + &Remove counter (%1) + Zählmarke &entfernen (%1) + + + + &Set counters (%1)... + Zählmarken &setzen (%1)... + + + Ctrl+F3 Ctrl+F3 - + F4 F4 @@ -4318,73 +5122,73 @@ Lokale Version ist %1, Serverversion ist %2. Zeige ent&fernte Karten - + &View sideboard Zeige &Sideboard - + Player "%1" Spieler "%1" - - + + Move to &graveyard Auf den &Friedhof legen - + Reveal &library to &Bibliothek jemandem zeigen - + Reveal t&op card to &Oberste Karte jemandem zeigen - + &Undo last draw Zuletzt gezogene Karte zur&ücklegen - + Take &mulligan &Mulligan nehmen - + Move top cards to &graveyard... Oberste Karten auf den F&riedhof legen... - + Put top card on &bottom Oberste Karte nach &unten legen - + &Hand &Hand - + &Reveal to Jemandem &zeigen - + Reveal r&andom card to Z&ufällige Karte jemandem zeigen - + &Library Bib&liothek - + &Graveyard &Friedhof @@ -4393,7 +5197,7 @@ Lokale Version ist %1, Serverversion ist %2. Entfe&rnte Karten - + &Sideboard &Sideboard @@ -4402,37 +5206,38 @@ Lokale Version ist %1, Serverversion ist %2. &Kampfwerte setzen... + &Set annotation... - &Hinweis setzen... + &Hinweis setzen... - + View top cards of library Zeige die obersten Karten der Bibliothek - + Number of cards: Anzahl der Karten: - + &Draw card Karte &ziehen - + &View exile &Zeige Exil - + &Exile &Exil - - + + Move to &hand auf die &Hand nehmen @@ -4441,28 +5246,28 @@ Lokale Version ist %1, Serverversion ist %2. auf den &Friedhof legen - - + + Move to &exile ins &Exil schicken - + Ctrl+W Ctrl+W - + Ctrl+D Ctrl+D - + D&raw cards... Ka&rten ziehen... - + Ctrl+E Ctrl+E @@ -4471,37 +5276,42 @@ Lokale Version ist %1, Serverversion ist %2. &Mulligan nehmen... - + Ctrl+M Ctrl+M - + &Shuffle Mi&schen - + Ctrl+S Ctrl+S - + &Counters &Zähler - + &Untap all permanents &Enttappe alle bleibenden Karten - + + Ctrl+J + + + + Ctrl+Shift+D Ctrl+Shift+D - + Ctrl+U Ctrl+U @@ -4530,60 +5340,74 @@ Lokale Version ist %1, Serverversion ist %2. Ctrl+L - + R&oll die... &Würfeln... - + Ctrl+I Ctrl+I - + &Create token... Spiels&tein erstellen... - + Ctrl+T Ctrl+T - + C&reate another token &Noch einen Spielstein erstellen - + Ctrl+G Ctrl+G - + S&ay S&agen - + C&ard &Karte + + &Play + &Ausspielen + + + + &Hide + &Verstecken + + + &Tap - &Tappen + &Tappen + &Untap - E&nttappen + E&nttappen + Toggle &normal untapping - &Normales Enttappen umschalten + &Normales Enttappen umschalten + &Flip - &Umdrehen + &Umdrehen Counters (red) @@ -4610,24 +5434,29 @@ Lokale Version ist %1, Serverversion ist %2. &Setze Zählmarken... + &top of library - &auf die Bibliothek + &auf die Bibliothek + &bottom of library - &unter die Bibliothek + &unter die Bibliothek + &graveyard - in den &Friedhof + in den &Friedhof + Ctrl+Del - Ctrl+Del + Ctrl+Del + &exile - ins &Exil + ins &Exil &Move to @@ -4658,50 +5487,50 @@ Lokale Version ist %1, Serverversion ist %2. F10 - + Draw cards Karten ziehen - - - - + + + + Number: Anzahl: - + Move top cards to grave Oberste Karten in den Friedhof legen - + Move top cards to exile Oberste Karten ins Exil schicken - + Set power/toughness Kampfwerte setzen - + Please enter the new PT: Bitte die neuen Kampfwerte eingeben: - + Set annotation Hinweis setzen - + Please enter the new annotation: Bitte den Hinweis eingeben: - + Set counters Setze Zählmarken @@ -4714,12 +5543,12 @@ Lokale Version ist %1, Serverversion ist %2. Neue Lebenspunkte insgesamt: - + Roll die Würfeln - + Number of sides: Anzahl der Seiten: @@ -4767,39 +5596,32 @@ Lokale Version ist %1, Serverversion ist %2. Deck #%1 - User &details - Benutzer&details + Benutzer&details - Direct &chat - &Persönliches Gespräch + &Persönliches Gespräch - Add to &buddy list - Zur &Freundesliste hinzufügen + Zur &Freundesliste hinzufügen - Remove from &buddy list - Von &Freundesliste entfernen + Von &Freundesliste entfernen - Add to &ignore list - &Ignorieren + &Ignorieren - Remove from &ignore list - Nicht mehr &ignorieren + Nicht mehr &ignorieren - Kick from &game - Aus dem &Spiel werfen + Aus dem &Spiel werfen ID #%1 @@ -4821,78 +5643,117 @@ Lokale Version ist %1, Serverversion ist %2. QObject - Maindeck - Hauptdeck + Hauptdeck - Sideboard - Sideboard + Sideboard - Cockatrice decks (*.cod) - Cockatrice Decks (*.cod) + Cockatrice Decks (*.cod) - Plain text decks (*.dec *.mwDeck) - Text Decks (*.dec *.mwDeck) + Text Decks (*.dec *.mwDeck) - + + Common deck formats (*.cod *.dec *.mwDeck) + + + + All files (*.*) Alle Dateien (*.*) + + + Cockatrice replays (*.cor) + Aufgezeichnete Cockatrice-Spiele (*.cor) + RemoteDeckList_TreeModel - + Name Name - + ID ID - + Upload time Änderungsdatum - RoomSelector + RemoteReplayList_TreeModel - - Rooms - Räume + + ID + ID - - Joi&n - Teil&nehmen + + Name + Name - - Room - Raum - - - - Description - Beschreibung - - - + Players Spieler - + + Keep + Behalten + + + + Time started + Startzeit + + + + Duration (sec) + Länge (Sekunden) + + + + RoomSelector + + + Rooms + Räume + + + + Joi&n + Teil&nehmen + + + + Room + Raum + + + + Description + Beschreibung + + + + Players + Spieler + + + Games Spiele @@ -4920,27 +5781,25 @@ Lokale Version ist %1, Serverversion ist %2. ShutdownDialog - + &Reason for shutdown: G&rund für die Abschaltung: - + &Time until shutdown (minutes): &Zeit bis zur Abschaltung (Minuten): - &OK - &OK + &OK - &Cancel - &Abbrechen + &Abbrechen - + Shut down server Server abschalten @@ -4948,7 +5807,7 @@ Lokale Version ist %1, Serverversion ist %2. TabAdmin - + Update server &message Server&nachricht aktualisieren @@ -4957,39 +5816,38 @@ Lokale Version ist %1, Serverversion ist %2. Server abschalten - + &Shut down server &Server abschalten - + Server administration functions Funktionen zur Serverwartung - + &Unlock functions &Sperre aufheben - + &Lock functions Funktionen s&perren - + Unlock administration functions Wartungsfunktionen entsperren - + Do you really want to unlock the administration functions? Möchten Sie wirklich die Sperre der Wartungsfunktionen aufheben? - Administration - Wartung + Wartung @@ -5011,179 +5869,444 @@ Lokale Version ist %1, Serverversion ist %2. %1 hat den Raum verlassen. + + TabDeckEditor + + Deck editor [*] + Deck-Editor [*] + + + + &Print deck... + Deck &drucken... + + + + &Close + S&chließen + + + + Ctrl+Q + Ctrl+Q + + + + &Edit sets... + &Editionen bearbeiten... + + + &Search... + &Suchen... + + + + &Clear search + Suche a&ufheben + + + + &Search for: + &Suchen nach: + + + + Deck &name: + Deck&name: + + + + &Comments: + &Kommentare: + + + + Hash: + Hash: + + + + &Update prices + &Preise aktualisieren + + + + Ctrl+U + Ctrl+U + + + + &New deck + &Neues Deck + + + + &Load deck... + Deck &laden... + + + + &Save deck + Deck &speichern + + + + Save deck &as... + Deck s&peichern unter... + + + + Load deck from cl&ipboard... + Deck aus &Zwischenablage laden... + + + + Save deck to clip&board + Deck in Z&wischenablage speichern + + + + &Analyze deck on deckstats.net + Deck auf deckstats.net &analysieren + + + + Add card to &maindeck + Karte zu&m Hauptdeck hinzufügen + + + Return + Return + + + Enter + Enter + + + + Add card to &sideboard + Karte zum &Sideboard hinzufügen + + + Ctrl+Return + Ctrl+Return + + + Ctrl+Enter + Ctrl+Enter + + + + Show card text only + + + + + &Remove row + Zeile entfe&rnen + + + + Del + Del + + + + &Increment number + Anzahl er&höhen + + + + + + + + + + + &Decrement number + Anzahl v&erringern + + + + - + - + + + + &Deck editor + &Deck-Editor + + + + C&ard database + &Kartendatenbank + + + + Edit &tokens... + Spielsteine &bearbeiten... + + + + Deck: %1 + Deck: %1 + + + + Are you sure? + Sind Sie sicher? + + + + The decklist has been modified. +Do you want to save the changes? + Die Deckliste wurde verändert. +Möchten Sie die Änderungen speichern? + + + + Load deck + Deck laden + + + + + + Error + Fehler + + + + The deck could not be saved. + Das Deck konnte nicht gespeichert werden. + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + Das Deck konnte nicht gespeichert werden. +Bitte überprüfen Sie, dass Sie Schreibrechte in dem Verzeichnis haben, und versuchen Sie es erneut. + + + + Save deck + Deck speichern + + TabDeckStorage - + Local file system Lokales Dateisystem - + Server deck storage Deckspeicherplatz auf dem Server - - + + Open in deck editor Im Deckeditor öffnen - + Upload deck Deck hochladen - + Download deck Deck herunterladen - - + + New folder Neuer Ordner - + + Delete Löschen - + Enter deck name Decknamen eingeben - + This decklist does not have a name. Please enter a name: Diese Deckliste hat keinen Namen. Bitte geben Sie einen Namen ein: - + Unnamed deck Unbenanntes Deck - + + Delete local file + Lokale Datei löschen + + + + + + Are you sure you want to delete "%1"? + Sind Sie sicher, dass Sie "%1" löschen möchten? + + + Name of new folder: Name für den neuen Ordner: - + + Delete remote folder + + + + + Delete remote deck + Deck auf dem Server löschen + + Deck storage - Deckspeicherplatz + Deckspeicherplatz TabGame - + F5 F5 - + F6 F6 - + F7 F7 - + F8 F8 - + F9 F9 - + F10 F10 - + &Phases &Phasen - + &Game Spi&el - + Next &phase Nächste &Phase - + Ctrl+Space Ctrl+Space - + + Tab + + + + Next &turn Nächster &Zug - + Ctrl+Return Ctrl+Return - + Ctrl+Enter Ctrl+Enter - + &Remove all local arrows &Lokale Pfeile entfernen - + Ctrl+R Ctrl+R - + + Game &information + &Spielinformationen + + + &Concede &Aufgeben - + F2 F2 - + &Leave game Spiel ver&lassen - + + Ctrl+Q Ctrl+Q - - Kicked - Herausgeworfen + + C&lose replay + Replay sch&ließen + + + + Replay %1: %2 + Replay %1: %2 + + + Kicked + Herausgeworfen - You have been kicked out of the game. - Sie wurden aus dem Spiel geworfen. + Sie wurden aus dem Spiel geworfen. Load &local deck @@ -5202,7 +6325,7 @@ Bitte geben Sie einen Namen ein: Spiel s&tarten - + &Say: &Sagen: @@ -5215,22 +6338,22 @@ Bitte geben Sie einen Namen ein: Esc - + Concede Aufgeben - + Are you sure you want to concede this game? Sind Sie sicher, dass Sie das Spiel aufgeben möchten? - + Leave game Spiel verlassen - + Are you sure you want to leave this game? Sind Sie sicher, dass Sie das Spiel verlassen möchten? @@ -5239,7 +6362,7 @@ Bitte geben Sie einen Namen ein: Deck laden - + Game %1: %2 Spiel %1: %2 @@ -5247,60 +6370,124 @@ Bitte geben Sie einen Namen ein: TabMessage - + Personal &talk Persönliches &Gespräch - + &Leave Ver&lassen - + This user is ignoring you. Dieser Benutzer ignoriert Sie. - + %1 has left the server. %1 hat den Server verlassen. - + %1 has joined the server. %1 hat den Server betreten. - + Talking to %1 Gespräch mit %1 + + TabReplays + + + Local file system + Lokales Dateisystem + + + + Server replay storage + Replay-Speicherplatz auf dem Server + + + + + Watch replay + Replay abspielen + + + + + Delete + Löschen + + + + Download replay + Replay herunterladen + + + + Toggle expiration lock + automatische Löschung umschalten + + + + Delete local file + Lokale Datei löschen + + + + Are you sure you want to delete "%1"? + Sind Sie sicher, dass Sie "%1" löschen möchten? + + + + Delete remote replay + Replay auf dem Server löschen + + + + Are you sure you want to delete the replay of game %1? + Sind Sie sicher, dass Sie das Replay des Spiels %1 löschen möchten? + + + Game replays + Replays + + TabRoom - + &Say: &Sagen: - + Chat Unterhaltung - + &Room &Raum - + &Leave room Raum ver&lassen - + + &Ignore unregistered users in chat + Unregistrierte Benutzer im Chat &ignorieren + + + You are flooding the chat. Please wait a couple of seconds. Sie überfluten den Chatraum. Bitte warten Sie ein paar Sekunden. @@ -5316,9 +6503,8 @@ Bitte geben Sie einen Namen ein: TabServer - Server - Server + Server @@ -5339,45 +6525,107 @@ Bitte geben Sie einen Namen ein: TabUserLists - User lists - Benutzerlisten + Benutzerlisten + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + Benutzer&details + + + + Direct &chat + &Persönliches Gespräch + + + + Show this user's &games + Spiele dieses &Benutzers anzeigen + + + + Add to &buddy list + Zur &Freundesliste hinzufügen + + + + Remove from &buddy list + Von &Freundesliste entfernen + + + + Add to &ignore list + &Ignorieren + + + + Remove from &ignore list + Nicht mehr &ignorieren + + + + Kick from &game + Aus dem &Spiel werfen + + + + Ban from &server + Vom &Server bannen + + + + %1's games + %1s Spiele UserInfoBox - + User information Benutzerinformationen - + Real name: Richtiger Name: - + Gender: Geschlecht: - + Location: Ort: - + User level: Nutzerstatus: - + Administrator Administrator - + Moderator Moderator @@ -5386,12 +6634,12 @@ Bitte geben Sie einen Namen ein: Schiedsrichter - + Registered user Registrierter Benutzer - + Unregistered user Unregistrierter Benutzer @@ -5399,37 +6647,47 @@ Bitte geben Sie einen Namen ein: UserInterfaceSettingsPage - + General interface settings Allgemeine Bedienung - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) Karten durch &Doppelklick ausspielen (statt Einzelklick) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings Animationseinstellungen - + &Tap/untap animation Animiertes &Tappen/Enttappen - + Enable &sounds &Sound anschalten - + Path to sounds directory: Pfad zum Verzeichnis mit den Sounddateien: - + Choose path Pfad auswählen @@ -5437,69 +6695,60 @@ Bitte geben Sie einen Namen ein: UserList - + Users online: %1 Benutzer online: %1 - + Users in this room: %1 Benutzer in diesem Raum: %1 - + Buddies online: %1 / %2 Freunde online: %1 / %2 - + Ignored users online: %1 / %2 Ignorierte Benutzer online: %1 / %2 - %1's games - %1s Spiele + %1s Spiele - User &details - Benutzer&details + Benutzer&details - Direct &chat - &Persönliches Gespräch + &Persönliches Gespräch - Show this user's &games - Spiele dieses &Benutzers anzeigen + Spiele dieses &Benutzers anzeigen - Add to &buddy list - Zur &Freundesliste hinzufügen + Zur &Freundesliste hinzufügen - Remove from &buddy list - Von &Freundesliste entfernen + Von &Freundesliste entfernen - Add to &ignore list - &Ignorieren + &Ignorieren - Remove from &ignore list - Nicht mehr &ignorieren + Nicht mehr &ignorieren - Ban from &server - Vom &Server bannen + Vom &Server bannen Duration @@ -5515,221 +6764,180 @@ Geben Sie 0 ein für einen unbefristeten Bann. WndDeckEditor - &Search for: - &Suchen nach: + &Suchen nach: - Deck &name: - Deck &Name: + Deck &Name: - &Comments: - &Kommentare: + &Kommentare: - Deck editor [*] - Deck-Editor [*] + Deck-Editor [*] - &New deck - &Neues Deck + &Neues Deck - &Load deck... - Deck &laden... + Deck &laden... - &Save deck - Deck &speichern + Deck &speichern &Save deck as... Deck &speichern unter... - Save deck &as... - Deck s&peichern unter... + Deck s&peichern unter... - Save deck to clip&board - Deck in Z&wischenablage speichern + Deck in Z&wischenablage speichern - &Print deck... - Deck &drucken... + Deck &drucken... - &Close - S&chließen + S&chließen - Ctrl+Q - Ctrl+Q + Ctrl+Q - &Edit sets... - &Editionen bearbeiten... + &Editionen bearbeiten... - &Deck - &Deck + &Deck &Sets &Editionen - Add card to &maindeck - Karte zu&m Hauptdeck hinzufügen + Karte zu&m Hauptdeck hinzufügen - Return - Return + Return - Enter - Enter + Enter - Ctrl+Return - Ctrl+Return + Ctrl+Return - Ctrl+Enter - Ctrl+Enter + Ctrl+Enter Ctrl+M Ctrl+M - Add card to &sideboard - Karte zum &Sideboard hinzufügen + Karte zum &Sideboard hinzufügen Ctrl+N Ctrl+N - &Search... - &Suchen... + &Suchen... - &Clear search - Suche a&ufheben + Suche a&ufheben - Hash: - Hash: + Hash: - &Update prices - &Preise aktualisieren + &Preise aktualisieren - Ctrl+U - Ctrl+U + Ctrl+U - Load deck from cl&ipboard... - Deck aus &Zwischenablage laden... + Deck aus &Zwischenablage laden... - &Card database - &Kartendatenbank + &Kartendatenbank - &Remove row - Zeile entfe&rnen + Zeile entfe&rnen - Del - Entf + Entf - &Increment number - Anzahl er&höhen + Anzahl er&höhen - + - + + + - &Decrement number - Anzahl v&erringern + Anzahl v&erringern - - - - + - - Are you sure? - Bist du sicher? + Bist du sicher? - The decklist has been modified. Do you want to save the changes? - Die Deckliste wurde verändert. + Die Deckliste wurde verändert. Willst du die Änderungen speichern? - Load deck - Deck laden + Deck laden - - Error - Fehler + Fehler - - The deck could not be saved. Please check that the directory is writable and try again. - Das Deck konnte nicht gespeichert werden. + Das Deck konnte nicht gespeichert werden. Bitte überprüfen Sie, dass Sie Schreibrechte in dem Verzeichnis haben, und versuchen Sie es erneut. - Save deck - Deck speichern + Deck speichern @@ -5747,17 +6955,17 @@ Bitte überprüfen Sie, dass Sie Schreibrechte in dem Verzeichnis haben, und ver alphabetisch sortieren - + sort by name nach Namen sortieren - + sort by type nach Kartentypen sortieren - + shuffle when closing beim Schließen mischen diff --git a/cockatrice/translations/cockatrice_en.ts b/cockatrice/translations/cockatrice_en.ts index 2c2914e94..ea397a188 100644 --- a/cockatrice/translations/cockatrice_en.ts +++ b/cockatrice/translations/cockatrice_en.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... - + Ctrl+L - + F11 - + F12 - + Set counter - + New value for counter '%1': @@ -37,91 +37,91 @@ AppearanceSettingsPage - + Zone background pictures - - Path to hand background: + + Hand background: - - Path to stack background: + + Stack background: - - Path to table background: + + Table background: - - Path to player info background: + + Player info background: - - Path to picture of card back: + + Card back: - + Card rendering - + Display card names on cards having a picture - + Hand layout - + Display hand horizontally (wastes space) - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Zone view layout - + Sort by name - + Sort by type - - - - - + + + + + Choose path @@ -129,78 +129,83 @@ BanDialog - + ban &user name - + ban &IP address - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK - + &Cancel - + Ban user from server - + Error - + You have to select a name-based or IP-based ban, or both. @@ -208,273 +213,111 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name - + Sets - + Mana cost - + Card type - + P/T + + CardInfoText + + + Name: + + + + + Mana cost: + + + + + Card type: + + + + + P / T: + + + + + Loyalty: + + + CardInfoWidget - - Hide card info - - - - + Show card only - + Show text only - + Show full info - + Name: - + Mana cost: - + Card type: - + P / T: + + + Loyalty: + + CardItem - - &Play - - - - - &Hide - - - - - &Tap - - - - - &Untap - - - - - Toggle &normal untapping - - - - - &Flip - - - - - &Clone - - - - - Ctrl+H - - - - - &Attach to card... - - - - - Ctrl+A - - - - - Unattac&h - - - - - &Draw arrow... - - - - + &Power / toughness - - &Increase power - - - - - Ctrl++ - - - - - &Decrease power - - - - - Ctrl+- - - - - - I&ncrease toughness - - - - - Alt++ - - - - - D&ecrease toughness - - - - - Alt+- - - - - - In&crease power and toughness - - - - - Ctrl+Alt++ - - - - - Dec&rease power and toughness - - - - - Ctrl+Alt+- - - - - - Set &power and toughness... - - - - - Ctrl+P - - - - - &Set annotation... - - - - - red - - - - - yellow - - - - - green - - - - - &Add counter (%1) - - - - - &Remove counter (%1) - - - - - &Set counters (%1)... - - - - - &top of library - - - - - &bottom of library - - - - - &graveyard - - - - - Ctrl+Del - - - - - &exile - - - - + &Move to @@ -482,375 +325,286 @@ This is only saved for moderators and cannot be seen by the banned person. CardZone - + her hand nominative, female owner - + %1's hand nominative, female owner - + his hand nominative, male owner - - - %1's hand - nominative, male owner - - - - - of her hand - genitive, female owner - - - - - of %1's hand - genitive, female owner - - - - - of his hand - genitive, male owner - - - - - of %1's hand - genitive, male owner - - - - - her hand - accusative, female owner - - - - - %1's hand - accusative, female owner - - - - - his hand - accusative, male owner - - - - - %1's hand - accusative, male owner - - + %1's hand + nominative, male owner + + + + her library - nominative, female owner + look at zone, female owner - + %1's library - nominative, female owner + look at zone, female owner - + his library - nominative, male owner + look at zone, male owner - + %1's library - nominative, male owner + look at zone, male owner - + of her library - genitive, female owner + top cards of zone, female owner - + of %1's library - genitive, female owner + top cards of zone, female owner - + of his library - genitive, male owner + top cards of zone, male owner - + of %1's library - genitive, male owner + top cards of zone, male owner - + her library - accusative, female owner + reveal zone, female owner - + %1's library - accusative, female owner + reveal zone, female owner - + his library - accusative, male owner + reveal zone, male owner - + %1's library - accusative, male owner + reveal zone, male owner - + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + + + + + his library + shuffle, male owner + + + + + %1's library + shuffle, male owner + + + + + her sideboard + look at zone, female owner + + + + + %1's sideboard + look at zone, female owner + + + + + his sideboard + look at zone, male owner + + + + + %1's sideboard + look at zone, male owner + + + + + her library + nominative, female owner + + + + + %1's library + nominative, female owner + + + + + his library + nominative, male owner + + + + + %1's library + nominative, male owner + + + + her graveyard nominative, female owner - + %1's graveyard nominative, female owner - + his graveyard nominative, male owner - + %1's graveyard nominative, male owner - - of her graveyard - genitive, female owner - - - - - of %1's graveyard - genitive, female owner - - - - - of his graveyard - genitive, male owner - - - - - of %1's graveyard - genitive, male owner - - - - - her graveyard - accusative, female owner - - - - - %1's graveyard - accusative, female owner - - - - - his graveyard - accusative, male owner - - - - - %1's graveyard - accusative, male owner - - - - + her exile nominative, female owner - + %1's exile nominative, female owner - + his exile nominative, male owner - + %1's exile nominative, male owner - - of her exile - genitive, female owner - - - - - of %1's exile - genitive, female owner - - - - - of his exile - genitive, male owner - - - - - of %1's exile - genitive, male owner - - - - - her exile - accusative, female owner - - - - - %1's exile - accusative, female owner - - - - - his exile - accusative, male owner - - - - - %1's exile - accusative, male owner - - - - + her sideboard nominative, female owner - + %1's sideboard nominative, female owner - + his sideboard nominative, male owner - + %1's sideboard nominative, male owner + + + DBPriceUpdater - - of her sideboard - genitive, female owner + + + + Error - - of %1's sideboard - genitive, female owner + + + A problem has occured while fetching card prices. - - of his sideboard - genitive, male owner - - - - - of %1's sideboard - genitive, male owner - - - - - her sideboard - accusative, female owner - - - - - %1's sideboard - accusative, female owner - - - - - his sideboard - accusative, male owner - - - - - %1's sideboard - accusative, male owner + + A problem has occured while fetching card prices: DeckEditorSettingsPage - - Enable &price tag feature (using data from blacklotusproject.com) + + Enable &price tag feature - + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General @@ -858,43 +612,77 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Number - + Card - + Price + + DeckStatsInterface + + + + Error + + + + + The reply from the server could not be parsed. + + + DeckViewContainer - + Load &local deck - + Load d&eck from server - + Ready to s&tart - + + S&ideboard unlocked + + + + + S&ideboard locked + + + + Load deck + + + Error + + + + + The selected file could not be loaded. + + DlgCardSearch @@ -937,33 +725,28 @@ This is only saved for moderators and cannot be seen by the banned person. DlgConnect - + &Host: - + &Port: - + Player &name: - + P&assword: - - &OK - - - - - &Cancel + + &Save password @@ -975,87 +758,82 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + &Description: - + &Password: - + P&layers: - + Game type - + Only &buddies can join - + Only &registered users can join - + Joining restrictions - + &Spectators allowed - + Spectators &need a password to join - + Spectators can &chat - + Spectators see &everything - + Spectators - - &OK - - - - - &Cancel - - - - + Create game - + + Game information + + + + Error - + Server error. @@ -1063,115 +841,257 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: - + Token - + C&olor: - + white - + blue - + black - + red - + green - + multicolor - + colorless - + &P/T: - + &Annotation: - + &Destroy token when it leaves the table - - &OK + + Token data - - &Cancel + + Show &all tokens - + + Show tokens from this &deck + + + + + Choose token from list + + + + Create token + + DlgEditTokens + + + &Name: + + + + + C&olor: + + + + + white + + + + + blue + + + + + black + + + + + red + + + + + green + + + + + multicolor + + + + + colorless + + + + + &P/T: + + + + + &Annotation: + + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + + Filter games + + + DlgLoadDeckFromClipboard - + &Refresh - - &OK - - - - - &Cancel - - - - + Load deck from clipboard - + Error - + Invalid deck list. @@ -1179,17 +1099,7 @@ This is only saved for moderators and cannot be seen by the banned person. DlgLoadRemoteDeck - - O&K - - - - - &Cancel - - - - + Load deck @@ -1197,154 +1107,209 @@ This is only saved for moderators and cannot be seen by the banned person. DlgSettings - - - + + + Error - - Your card database is invalid. Would you like to go back and set the correct path? + + Unknown Error loading card database - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Appearance - + User interface - + Deck editor - + Messages - - - &Close - - GameSelector - + C&reate - + &Join - - - - - - - - + + + + + + + + + Error - + Please join the appropriate room first. - + Wrong password. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join game - + Password: - + + Please join the respective room first. + + + + Games - - Show &full games + + &Filter games - - Show &running games + + C&lear filter - + J&oin as spectator @@ -1360,72 +1325,72 @@ This is only saved for moderators and cannot be seen by the banned person. GamesModel - + yes - + no - + Creator - + Description - + yes, free for spectators - + buddies only - + reg. users only - + not allowed - + Room - + Game type - + Password - + Restrictions - + Players - + Spectators @@ -1433,50 +1398,92 @@ This is only saved for moderators and cannot be seen by the banned person. GeneralSettingsPage - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings - + Language: - + Download card pictures on the fly - + + Download high-quality card pictures + + + + Paths - + Decks directory: - + + Replays directory: + + + + Pictures directory: - - Path to card database: + + Card database: - - + + Token database: + + + + + English English @@ -1484,43 +1491,54 @@ This is only saved for moderators and cannot be seen by the banned person. MainWindow - + There are too many concurrent connections from your address. - - Banned by moderator. - - - - + Scheduled server shutdown. - - Unknown reason. + + Banned by moderator - + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + Connection closed - + The server has terminated your connection. Reason: %1 - + Scheduled server shutdown - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1530,201 +1548,240 @@ Reason for shutdown: %1 - + Number of players - + Please enter the number of players. - - + + Player %1 - + + Load replay + + + + About Cockatrice - + Version %1 - + Authors: - + Translators: - + Spanish: - + Portugese (Portugal): - + Portugese (Brazil): - + French: - + Japanese: - + Russian: - - Czech: - - - - + Italian: - - - - - - + + Swedish: + + + + + + + + + + + + + Error - + Server timeout - - Invalid login data. + + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + This server requires user registration. + + + + + Unknown login error: %1 + + + + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Disconnected - + + Connected, logging in at %1 + + + + Logged in at %1 - + &Connect... - + &Disconnect - + Start &local game... - + + &Watch replay... + + + + &Deck editor - + &Full screen - + Ctrl+F - + &Settings... - + &Exit - + &Cockatrice - + &About Cockatrice - + &Help - + Are you sure? - + There are still open games. Are you sure you want to quit? @@ -1732,17 +1789,17 @@ Local version is %1, remote version is %2. MessageLogWidget - + The game has been closed. - + %1 is now watching the game. - + %1 is not watching the game any more. @@ -1754,915 +1811,1031 @@ Local version is %1, remote version is %2. - + You have joined game #%1. female - - - You have joined game #%1. - male - - - - - %1 has joined the game. - female - - - - - %1 has joined the game. - male - - + You have joined game #%1. + male + + + + + You are watching a replay of game #%1. + female + + + + + You are watching a replay of game #%1. + male + + + + + %1 has joined the game. + female + + + + + %1 has joined the game. + male + + + + %1 has left the game. female - + %1 has left the game. male - - %1 is ready to start the game. - female + + You have been kicked out of the game. - - %1 is ready to start the game. - male + + %1 has loaded a deck (%2). - - %1 is not ready to start the game any more. - female - - - - - %1 is not ready to start the game any more. - male - - - - - %1 has conceded the game. - female - - - - - %1 has conceded the game. - male - - - - - %1 has restored connection to the game. - female + + %1 has loaded a deck with %2 sideboard cards (%3). + %1 is ready to start the game. + female + + + + + %1 is ready to start the game. + male + + + + + %1 is not ready to start the game any more. + female + + + + + %1 is not ready to start the game any more. + male + + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + %1 has conceded the game. + female + + + + + %1 has conceded the game. + male + + + + + %1 has restored connection to the game. + female + + + + %1 has restored connection to the game. male - + %1 has lost connection to the game. female - + %1 has lost connection to the game. male - + %1 shuffles %2. female - - - %1 shuffles %2. - male - - - - - %1 rolls a %2 with a %3-sided die. - female - - - - - %1 rolls a %2 with a %3-sided die. - male - - - - - %1 draws %n card(s). - female - - %1 draws a card. - %1 draws %n cards. - - - - - %1 draws %n card(s). - male - - %1 draws a card. - %1 draws %n cards. - - - - - %1 undoes his last draw. - - - - - %1 undoes her last draw. - - - - - %1 undoes his last draw (%2). - - - - - %1 undoes her last draw (%2). - - - - - from table - - - - - from graveyard - - - - - from exile - - + %1 shuffles %2. + male + + + + + %1 rolls a %2 with a %3-sided die. + female + + + + + %1 rolls a %2 with a %3-sided die. + male + + + + + %1 draws %n card(s). + female + + %1 draws a card. + %1 draws %n cards. + + + + + %1 draws %n card(s). + male + + %1 draws a card. + %1 draws %n cards. + + + + + %1 undoes his last draw. + + + + + %1 undoes her last draw. + + + + + %1 undoes his last draw (%2). + + + + + %1 undoes her last draw (%2). + + + + + from table + + + + + from graveyard + + + + + from exile + + + + from hand - + + the bottom card of %1's library + + + + the bottom card of his library - + the bottom card of her library - - - from the bottom of his library - - - - - from the bottom of her library - - - - - the top card of his library - - - - - the top card of her library - - - - - from the top of his library - - - - - from the top of her library - - - - - from library - - - - - from sideboard - - - - - from the stack - - - - - %1 gives %2 control over %3. - - - - - %1 puts %2 into play tapped%3. - - - %1 puts %2 into play%3. + from the bottom of %1's library - %1 puts %2%3 into graveyard. + from the bottom of his library - - %1 exiles %2%3. + + from the bottom of her library - - %1 moves %2%3 to hand. + + the top card of %1's library - %1 puts %2%3 into his library. + the top card of his library - %1 puts %2%3 into her library. - - - - - %1 puts %2%3 on bottom of his library. - - - - - %1 puts %2%3 on bottom of her library. + the top card of her library - %1 puts %2%3 on top of his library. - - - - - %1 puts %2%3 on top of her library. + from the top of %1's library - %1 puts %2%3 into his library at position %4. + from the top of his library - %1 puts %2%3 into her library at position %4. + from the top of her library - - %1 moves %2%3 to sideboard. + + from %1's library - - %1 plays %2%3. + + from library - - - %1 takes a mulligan to %n. - female - - - - - - - - %1 takes a mulligan to %n. - male - - - - + + + from sideboard + - - %1 flips %2 face-down. - female + + from the stack + %1 gives %2 control over %3. + + + + + %1 puts %2 into play tapped%3. + + + + + %1 puts %2 into play%3. + + + + + %1 puts %2%3 into graveyard. + + + + + %1 exiles %2%3. + + + + + %1 moves %2%3 to hand. + + + + + %1 puts %2%3 into his library. + + + + + %1 puts %2%3 into her library. + + + + + %1 puts %2%3 on bottom of his library. + + + + + %1 puts %2%3 on bottom of her library. + + + + + %1 puts %2%3 on top of his library. + + + + + %1 puts %2%3 on top of her library. + + + + + %1 puts %2%3 into his library at position %4. + + + + + %1 puts %2%3 into her library at position %4. + + + + + %1 moves %2%3 to sideboard. + + + + + %1 plays %2%3. + + + + + %1 takes a mulligan to %n. + female + + + + + + + + %1 takes a mulligan to %n. + male + + + + + + + + %1 flips %2 face-down. + female + + + + %1 flips %2 face-down. male - + %1 flips %2 face-up. female - + %1 flips %2 face-up. male - + %1 destroys %2. female - + %1 destroys %2. male - + %1 unattaches %2. female - + %1 unattaches %2. male - + %1 creates token: %2%3. female - + %1 creates token: %2%3. male - + %1 points from her %2 to herself. female - + %1 points from his %2 to himself. male - + %1 points from her %2 to %3. p1 female, p2 female - + %1 points from her %2 to %3. p1 female, p2 male - + %1 points from his %2 to %3. p1 male, p2 female - + %1 points from his %2 to %3. p1 male, p2 male - + %1 points from %2's %3 to herself. card owner female, target female - + %1 points from %2's %3 to herself. card owner male, target female - + %1 points from %2's %3 to himself. card owner female, target male - + %1 points from %2's %3 to himself. card owner male, target male - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male - + %1 points from her %2 to her %3. female - + %1 points from his %2 to his %3. male - + %1 points from her %2 to %3's %4. p1 female, p2 female - + %1 points from her %2 to %3's %4. p1 female, p2 male - + %1 points from his %2 to %3's %4. p1 male, p2 female - + %1 points from his %2 to %3's %4. p1 male, p2 male - + %1 points from %2's %3 to her own %4. card owner female, target female - + %1 points from %2's %3 to her own %4. card owner male, target female - + %1 points from %2's %3 to his own %4. card owner female, target male - + %1 points from %2's %3 to his own %4. card owner male, target male - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male - + %1 places %n %2 counter(s) on %3 (now %4). female - + %1 places a %2 counter on %3 (now %4). %1 places %n %2 counters on %3 (now %4). - + %1 places %n %2 counter(s) on %3 (now %4). male - + %1 places a %2 counter on %3 (now %4). %1 places %n %2 counters on %3 (now %4). - + %1 removes %n %2 counter(s) from %3 (now %4). female - + %1 removes a %2 counter from %3 (now %4). %1 removes %n %2 counters from %3 (now %4). - + %1 removes %n %2 counter(s) from %3 (now %4). male - + %1 removes a %2 counter from %3 (now %4). %1 removes %n %2 counters from %3 (now %4). - + %1 taps her permanents. female - + %1 untaps her permanents. female - + %1 taps his permanents. male - + %1 untaps his permanents. male - + %1 taps %2. female - + %1 untaps %2. female - - - %1 taps %2. - male - - - - - %1 untaps %2. - male - - - - - %1 sets counter %2 to %3 (%4%5). - female - - - - - %1 sets counter %2 to %3 (%4%5). - male - - - - - %1 sets %2 to not untap normally. - female - - - - - %1 sets %2 to not untap normally. - male - - - - - %1 sets %2 to untap normally. - female - - - - - %1 sets %2 to untap normally. - male - - - - - %1 sets PT of %2 to %3. - female - - - - - %1 sets PT of %2 to %3. - male - - - - - %1 sets annotation of %2 to %3. - female - - - - - %1 sets annotation of %2 to %3. - male - - - - - %1 is looking at the top %2 cards %3. - female - - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - - %1 is looking at %2. - female - - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female + %1 sets counter %2 to %3 (%4%5). + male + + + + + %1 sets %2 to not untap normally. + female + + + + + %1 sets %2 to not untap normally. + male + + + + + %1 sets %2 to untap normally. + female + + + + + %1 sets %2 to untap normally. + male + + + + + %1 sets PT of %2 to %3. + female + + + + + %1 sets PT of %2 to %3. + male + + + + + %1 sets annotation of %2 to %3. + female + + + + + %1 sets annotation of %2 to %3. + male + + + + + %1 is looking at %2. + female + + + + + %1 is looking at %2. + male + + + + + %1 is looking at the top %n card(s) %2. + female + + %1 is looking at the top card %2. + %1 is looking at the top %n cards %2. + + + + + %1 is looking at the top %n card(s) %2. + male + + %1 is looking at the top card %2. + %1 is looking at the top %n cards %2. + + + + + %1 stops looking at %2. + female + + + + %1 stops looking at %2. male - - - %1 reveals %2 to %3. - p1 female, p2 female - - - - - %1 reveals %2 to %3. - p1 female, p2 male - - - - - %1 reveals %2 to %3. - p1 male, p2 female - - - - - %1 reveals %2 to %3. - p1 male, p2 male - - - - - %1 reveals %2. - female - - - - - %1 reveals %2. - male - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 randomly reveals %2%3. - female - - - - - %1 randomly reveals %2%3. - male - - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 reveals %2%3. - female - - - - - %1 reveals %2%3. - male - - - - - It is now %1's turn. - female - - + %1 reveals %2 to %3. + p1 female, p2 female + + + + + %1 reveals %2 to %3. + p1 female, p2 male + + + + + %1 reveals %2 to %3. + p1 male, p2 female + + + + + %1 reveals %2 to %3. + p1 male, p2 male + + + + + %1 reveals %2. + female + + + + + %1 reveals %2. + male + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 randomly reveals %2%3. + female + + + + + %1 randomly reveals %2%3. + male + + + + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 reveals %2%3. + female + + + + + %1 reveals %2%3. + male + + + + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + + It is now %1's turn. + female + + + + It is now %1's turn. male - - + + a card @@ -2681,7 +2854,7 @@ Local version is %1, remote version is %2. - + red @@ -2689,7 +2862,7 @@ Local version is %1, remote version is %2. - + yellow @@ -2697,7 +2870,7 @@ Local version is %1, remote version is %2. - + green @@ -2705,17 +2878,17 @@ Local version is %1, remote version is %2. - + The game has started. - + %1 draws his initial hand. - + %1 draws her initial hand. @@ -2734,98 +2907,86 @@ Local version is %1, remote version is %2. - + ending phase - + untap step - - %1 has loaded a deck (%2). - female - - - - - %1 has loaded a deck (%2). - male - - - - + %1 attaches %2 to %3's %4. p1 female, p2 female - + %1 attaches %2 to %3's %4. p1 female, p2 male - + %1 attaches %2 to %3's %4. p1 male, p2 female - + %1 attaches %2 to %3's %4. p1 male, p2 male - + upkeep step - + draw step - + first main phase - + beginning of combat step - + declare attackers step - + declare blockers step - + combat damage step - + end of combat step - + second main phase - + It is now the %1. @@ -2833,22 +2994,22 @@ Local version is %1, remote version is %2. MessagesSettingsPage - + &Add - + &Remove - + Add message - + Message: @@ -2856,57 +3017,57 @@ Local version is %1, remote version is %2. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -2914,388 +3075,550 @@ Local version is %1, remote version is %2. Player - - - + + + Move to &top of library - - - + + + Move to &bottom of library - - + + Move to &graveyard - + &View library - + Reveal &library to - + Reveal t&op card to - + Move top cards to &graveyard... - + + Ctrl+J + + + + F3 - + View &top cards of library... - + &View graveyard - + F4 - + &View sideboard - + Player "%1" - + &Hand - + &Library - + &Graveyard - + &Sideboard - + View top cards of library - + Number of cards: - + &Draw card - + &View exile - + &Exile - - + + Move to &hand - - + + Move to &exile - + Ctrl+W - + Ctrl+D - + D&raw cards... - + Ctrl+E - + Take &mulligan - + Ctrl+M - + &Shuffle - + Ctrl+S - + &Counters - + &Untap all permanents - + Ctrl+U - + R&oll die... - + Ctrl+I - + &Create token... - + Ctrl+T - + C&reate another token - + Ctrl+G - + S&ay - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &Undo last draw - + Move top cards to &exile... - + Put top card on &bottom - + &Reveal to - + Reveal r&andom card to - + + Cr&eate predefined token + + + + C&ard - + &All players - + + &Play + + + + + &Hide + + + + + &Tap + + + + + &Untap + + + + + Toggle &normal untapping + + + + + &Flip + + + + + &Peek at card face + + + + + &Clone + + + + + Attac&h to card... + + + + + Ctrl+A + + + + + Unattac&h + + + + + &Draw arrow... + + + + + &Increase power + + + + + Ctrl++ + + + + + &Decrease power + + + + + Ctrl+- + + + + + I&ncrease toughness + + + + + Alt++ + + + + + D&ecrease toughness + + + + + Alt+- + + + + + In&crease power and toughness + + + + + Ctrl+Alt++ + + + + + Dec&rease power and toughness + + + + + Ctrl+Alt+- + + + + + Set &power and toughness... + + + + + Ctrl+P + + + + + &Set annotation... + + + + + red + + + + + yellow + + + + + green + + + + + &Add counter (%1) + + + + + &Remove counter (%1) + + + + + &Set counters (%1)... + + + + + &top of library + + + + + &bottom of library + + + + + &graveyard + + + + + Ctrl+Del + + + + + &exile + + + + Ctrl+F3 - + Ctrl+Shift+D - + Draw cards - - - - + + + + Number: - + Move top cards to grave - + Move top cards to exile - + Roll die - + Number of sides: - + Set power/toughness - + Please enter the new PT: - + Set annotation - + Please enter the new annotation: - + Set counters - - PlayerListWidget - - - User &details - - - - - Direct &chat - - - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Kick from &game - - - QObject - - Maindeck + + Cockatrice replays (*.cor) - - Sideboard + + Common deck formats (*.cod *.dec *.mwDeck) - - Cockatrice decks (*.cod) - - - - - Plain text decks (*.dec *.mwDeck) - - - - + All files (*.*) @@ -3303,50 +3626,83 @@ Local version is %1, remote version is %2. RemoteDeckList_TreeModel - + Name - + ID - + Upload time + + RemoteReplayList_TreeModel + + + ID + + + + + Name + + + + + Players + + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Players - + Games @@ -3367,27 +3723,17 @@ Local version is %1, remote version is %2. ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - - &OK - - - - - &Cancel - - - - + Shut down server @@ -3395,246 +3741,468 @@ Local version is %1, remote version is %2. TabAdmin - + Update server &message - + &Shut down server - + Server administration functions - + &Unlock functions - + &Lock functions - + Unlock administration functions - + Do you really want to unlock the administration functions? + + + TabDeckEditor - - Administration + + &Print deck... + + + + + &Close + + + + + Ctrl+Q + + + + + &Edit sets... + + + + + &Clear search + + + + + &Search for: + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Update prices + + + + + Ctrl+U + + + + + &New deck + + + + + &Load deck... + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Save deck to clip&board + + + + + &Analyze deck on deckstats.net + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + Show card text only + + + + + &Remove row + + + + + Del + + + + + &Increment number + + + + + + + + + + + &Decrement number + + + + + - + + + + + &Deck editor + + + + + C&ard database + + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + Load deck + + + + + + + Error + + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Upload deck - + Download deck - - + + New folder - + + Delete - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: - - Deck storage + + Delete remote folder + + + + + Delete remote deck TabGame - + F5 - + F6 - + F7 - + F8 - + F9 - + F10 - + &Phases - + &Game - + Next &phase - + Ctrl+Space - + + Tab + + + + Next &turn - + Ctrl+Return - + Ctrl+Enter - + &Remove all local arrows - + Ctrl+R - + + Game &information + + + + &Concede - + F2 - + &Leave game - + + Ctrl+Q - + + C&lose replay + + + + &Say: - + Concede - + Are you sure you want to concede this game? - + Leave game - + Are you sure you want to leave this game? - - Kicked + + Replay %1: %2 - - You have been kicked out of the game. - - - - + Game %1: %2 @@ -3642,124 +4210,234 @@ Please enter a name: TabMessage - + Personal &talk - + &Leave - + This user is ignoring you. - + %1 has left the server. - + %1 has joined the server. - + Talking to %1 + + TabReplays + + + Local file system + + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: - + Chat - + &Room - + &Leave room - - You are flooding the chat. Please wait a couple of seconds. + + &Ignore unregistered users in chat - - - TabServer - - Server + + You are flooding the chat. Please wait a couple of seconds. TabUserLists - - User lists + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + + + + + Direct &chat + + + + + Show this user's &games + + + + + Add to &buddy list + + + + + Remove from &buddy list + + + + + Add to &ignore list + + + + + Remove from &ignore list + + + + + Kick from &game + + + + + Ban from &server + + + + + %1's games UserInfoBox - + User information - + Real name: - + Gender: - + Location: - + User level: - + Administrator - + Moderator - + Registered user - + Unregistered user @@ -3767,37 +4445,47 @@ Please enter a name: UserInterfaceSettingsPage - + General interface settings - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings - + &Tap/untap animation - + Enable &sounds - + Path to sounds directory: - + Choose path @@ -3805,272 +4493,25 @@ Please enter a name: UserList - + Users online: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 - - - %1's games - - - - - User &details - - - - - Direct &chat - - - - - Show this user's &games - - - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Ban from &server - - - - - WndDeckEditor - - - &Search for: - - - - - Deck &name: - - - - - &Comments: - - - - - Deck editor [*] - - - - - &New deck - - - - - &Load deck... - - - - - Load deck from cl&ipboard... - - - - - &Save deck - - - - - Hash: - - - - - &Update prices - - - - - Ctrl+U - - - - - Save deck &as... - - - - - Save deck to clip&board - - - - - &Print deck... - - - - - &Close - - - - - Ctrl+Q - - - - - &Edit sets... - - - - - &Deck - - - - - Load deck - - - - - - Error - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - - - - Add card to &maindeck - - - - - Return - - - - - Enter - - - - - Ctrl+Return - - - - - Ctrl+Enter - - - - - Add card to &sideboard - - - - - &Search... - - - - - &Clear search - - - - - &Card database - - - - - &Remove row - - - - - Del - - - - - &Increment number - - - - - + - - - - - &Decrement number - - - - - - - - - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - WndSets @@ -4083,17 +4524,17 @@ Do you want to save the changes? ZoneViewWidget - + sort by name - + sort by type - + shuffle when closing diff --git a/cockatrice/translations/cockatrice_es.ts b/cockatrice/translations/cockatrice_es.ts index ed26ec533..3a77a6c97 100644 --- a/cockatrice/translations/cockatrice_es.ts +++ b/cockatrice/translations/cockatrice_es.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... E&stablecer contador... - + Ctrl+L Ctrl+L - + F11 F11 - + F12 F12 - + Set counter Establecer contador - + New value for counter '%1': Nuevo valor para el contador '%1': @@ -37,62 +37,82 @@ AppearanceSettingsPage - + Zone background pictures Imagenes de la zona de fondo - Path to hand background: - Ruta a la imagen de fondo de la mano: + Ruta a la imagen de fondo de la mano: - Path to stack background: - Ruta a la imagen de fondo de la pila: + Ruta a la imagen de fondo de la pila: - Path to table background: - Ruta a la imagen de fondo de la mesa: + Ruta a la imagen de fondo de la mesa: - Path to player info background: - Ruta a la imagen de fondo de la información del jugador: + Ruta a la imagen de fondo de la información del jugador: - Path to picture of card back: - Ruta al reverso de las cartas: + Ruta al reverso de las cartas: - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Card rendering Renderizado de las cartas - + Display card names on cards having a picture Mostrar nombre de las cartas en aquellas que tengan imagen - + Hand layout Disposición de la mano - + Display hand horizontally (wastes space) Mostrar la mano horizontalmente (desperdicia espacio) - + Table grid layout Disposición de la rejilla de la mesa - + Minimum player count for multi-column layout: Número minimo de jugadores para usar la rejilla multicolumna: @@ -101,7 +121,7 @@ Disposición Económica - + Invert vertical coordinate Invertir coordenada vertical @@ -110,26 +130,26 @@ Disposición económica - + Zone view layout Distribución de la zona de visionado - + Sort by name Ordenar por nombre - + Sort by type Ordenar por tipo - - - - - + + + + + Choose path Elija ruta @@ -143,357 +163,356 @@ Enter 0 for an indefinite ban. Indica 0 para un ban indefinido. - + ban &user name - + &usuario baneado - + ban &IP address - + dirección &IP baneada - + Ban type - + Tipo de Ban - + &permanent ban - + ban &permanente - + &temporary ban - - - - - &Days: - + ban &temporal - &Hours: - + &Days: + &Días: + &Hours: + &Horas: + + + &Minutes: - + &Minutos: - + Duration of the ban - + Duración del ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor, introduce el motivo del ban Se almacenará unicamente para moderadores y no podrá ser visto por la persona baneada. - + + Please enter the reason for the ban that will be visible to the banned person. + Por favor, introduce el motivo del ban que será visible para la persona baneada. + + + &OK &Aceptar - + &Cancel &Cancelar - + Ban user from server Banear usuario del servidor - + Error - Error + Error - + You have to select a name-based or IP-based ban, or both. - + Debes seleccionar banear por nombre, por IP o por ambos. CardDatabaseModel - + Name Nombre - + Sets Edición - + Mana cost Coste de mana - + Card type Tipo de carta - + P/T F/R - CardInfoWidget + CardInfoText - - Hide card info - Ocultar información de la carta + + Name: + Nombre: - + + Mana cost: + Coste de mana: + + + + Card type: + Tipo de carta: + + + + P / T: + F / R: + + + + Loyalty: + Lealtad: + + + + CardInfoWidget + + Hide card info + Ocultar información de la carta + + + Show card only Mostrar sólo la carta - + Show text only Mostrar sólo texto - + Show full info Mostrar información completa - + Name: Nombre: - + Mana cost: Coste de mana: - + Card type: Tipo de carta: - + P / T: F / R: + + + Loyalty: + Lealtad: + CardItem - &Play - &Jugar + &Jugar - &Hide - &Ocultar + &Ocultar - &Tap - &Girar + &Girar - &Untap - &Enderezar + &Enderezar - Toggle &normal untapping - Alternar enderezamiento &normal + Alternar enderezamiento &normal - &Flip - &Voltear + &Voltear - &Clone - &Clonar + &Clonar - Ctrl+H - Ctrl+H + Ctrl+H - &Attach to card... - Ane&xar a una carta... + Ane&xar a una carta... - Ctrl+A - Ctrl+A + Ctrl+A - Unattac&h - Desane&xar + Desane&xar Set &P/T... Establecer &F/R... - &Draw arrow... - &Dibujar flecha... + &Dibujar flecha... - + &Power / toughness &Fuerza / resistencia - &Increase power - &Incrementar fuerza + &Incrementar fuerza - Ctrl++ - Ctrl++ + Ctrl++ - &Decrease power - &Decrementar fuerza + &Decrementar fuerza - Ctrl+- - Ctrl+- + Ctrl+- - I&ncrease toughness - I&ncrementar resistencia + I&ncrementar resistencia - Alt++ - Alt++ + Alt++ - D&ecrease toughness - D&ecrementar resistencia + D&ecrementar resistencia - Alt+- - Alt+- + Alt+- - In&crease power and toughness - In&crementar fuerza y resistencia + In&crementar fuerza y resistencia - Ctrl+Alt++ - Ctrl+Alt++ + Ctrl+Alt++ - Dec&rease power and toughness - Dec&rementar fuerza y resistencia + Dec&rementar fuerza y resistencia - Ctrl+Alt+- - Ctrl+Alt+- + Ctrl+Alt+- - Set &power and toughness... - Establecer &fuerza y resistencia... + Establecer &fuerza y resistencia... - Ctrl+P - Ctrl+P + Ctrl+P - &Set annotation... - E&scribir anotación... + E&scribir anotación... - red - rojo + rojo - yellow - amarillo + amarillo - green - verde + verde - &Add counter (%1) - &Añadir contador (%1) + &Añadir contador (%1) - &Remove counter (%1) - &Quitar contador (%1) + &Quitar contador (%1) - &Set counters (%1)... - E&stablecer contadores (%1)... + E&stablecer contadores (%1)... - &top of library - &parte superior de la biblioteca + &parte superior de la biblioteca - &bottom of library - &fondo de la biblioteca + &fondo de la biblioteca - &graveyard - &cementerio + &cementerio - Ctrl+Del - Ctrl+Del + Ctrl+Del - &exile - &exilio + &exilio - + &Move to &Mover a @@ -661,364 +680,444 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona biblioteca &de %1 - + her hand nominative, female owner su mano - + %1's hand nominative, female owner mano de %1 - + his hand nominative, male owner su mano - + %1's hand nominative, male owner mano de %1 - + + her library + look at zone, female owner + su biblioteca + + + + %1's library + look at zone, female owner + biblioteca de %1 + + + + his library + look at zone, male owner + su biblioteca + + + + %1's library + look at zone, male owner + biblioteca de %1 + + + + of her library + top cards of zone, female owner + de su biblioteca + + + + of %1's library + top cards of zone, female owner + de la biblioteca de %1 + + + + of his library + top cards of zone, male owner + de su biblioteca + + + + of %1's library + top cards of zone, male owner + de la biblioteca de %1 + + + + her library + reveal zone, female owner + su biblioteca + + + + %1's library + reveal zone, female owner + biblioteca de %1 + + + + his library + reveal zone, male owner + su biblioteca + + + + %1's library + reveal zone, male owner + biblioteca de %1 + + + + her library + shuffle, female owner + su biblioteca + + + + %1's library + shuffle, female owner + biblioteca de %1 + + + + his library + shuffle, male owner + su biblioteca + + + + %1's library + shuffle, male owner + biblioteca de %1 + + + + her sideboard + look at zone, female owner + su reserva + + + + %1's sideboard + look at zone, female owner + la reserva de %1 + + + + his sideboard + look at zone, male owner + su reserva + + + + %1's sideboard + look at zone, male owner + la reserva de %1 + + of her hand genitive, female owner - de su mano + de su mano - of %1's hand genitive, female owner - de la mano de %1 + de la mano de %1 - of his hand genitive, male owner - de su mano + de su mano - of %1's hand genitive, male owner - de la mano de %1 + de la mano de %1 - her hand accusative, female owner - su mano + su mano - %1's hand accusative, female owner - mano de %1 + mano de %1 - his hand accusative, male owner - su mano + su mano - %1's hand accusative, male owner - mano de %1 + mano de %1 - + her library nominative, female owner su biblioteca - + %1's library nominative, female owner biblioteca de %1 - + his library nominative, male owner su biblioteca - + %1's library nominative, male owner biblioteca de %1 - of her library genitive, female owner - de su biblioteca + de su biblioteca - of %1's library genitive, female owner - de la biblioteca de %1 + de la biblioteca de %1 - of his library genitive, male owner - de su biblioteca + de su biblioteca - of %1's library genitive, male owner - de la biblioteca de %1 + de la biblioteca de %1 - her library accusative, female owner - su biblioteca + su biblioteca - %1's library accusative, female owner - biblioteca de %1 + biblioteca de %1 - his library accusative, male owner - su biblioteca + su biblioteca - %1's library accusative, male owner - biblioteca de %1 + biblioteca de %1 - + her graveyard nominative, female owner su cementerio - + %1's graveyard nominative, female owner cementerio de %1 - + his graveyard nominative, male owner su cementerio - + %1's graveyard nominative, male owner cementerio de %1 - of her graveyard genitive, female owner - de su cementerio + de su cementerio - of %1's graveyard genitive, female owner - del cementerio de %1 + del cementerio de %1 - of his graveyard genitive, male owner - de su cementerio + de su cementerio - of %1's graveyard genitive, male owner - del cementerio de %1 + del cementerio de %1 - her graveyard accusative, female owner - su cementerio + su cementerio - %1's graveyard accusative, female owner - cementerio de %1 + cementerio de %1 - his graveyard accusative, male owner - su cementerio + su cementerio - %1's graveyard accusative, male owner - cementerio de %1 + cementerio de %1 - + her exile nominative, female owner su exilio - + %1's exile nominative, female owner el exilio de %1 - + his exile nominative, male owner su exilio - + %1's exile nominative, male owner el exilio de %1 - of her exile genitive, female owner - de su exilio + de su exilio - of %1's exile genitive, female owner - del exilio de %1 + del exilio de %1 - of his exile genitive, male owner - de su exilio + de su exilio - of %1's exile genitive, male owner - del exilio de %1 + del exilio de %1 - her exile accusative, female owner - su exilio + su exilio - %1's exile accusative, female owner - el exilio de %1 + el exilio de %1 - his exile accusative, male owner - su exilio + su exilio - %1's exile accusative, male owner - el exilio de %1 + el exilio de %1 - + her sideboard nominative, female owner su reserva - + %1's sideboard nominative, female owner la reserva de %1 - + his sideboard nominative, male owner su reserva - + %1's sideboard nominative, male owner la reserva de %1 - of her sideboard genitive, female owner - de su reserva + de su reserva - of %1's sideboard genitive, female owner - de la reserva de %1 + de la reserva de %1 - of his sideboard genitive, male owner - de su reserva + de su reserva - of %1's sideboard genitive, male owner - de la reserva de %1 + de la reserva de %1 - her sideboard accusative, female owner - su reserva + su reserva - %1's sideboard accusative, female owner - la reserva de %1 + la reserva de %1 - his sideboard accusative, male owner - su reserva + su reserva - %1's sideboard accusative, male owner - la reserva de %1 + la reserva de %1 his graveyard @@ -1192,14 +1291,49 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona - DeckEditorSettingsPage + DBPriceUpdater - - Enable &price tag feature (using data from blacklotusproject.com) - Activar tag de &precios (usando datos de blacklotusproject.com) + + + + Error + Error - + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + Enable &price tag feature (using data from blacklotusproject.com) + Activar tag de &precios (usando datos de blacklotusproject.com) + + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General General @@ -1207,43 +1341,77 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckListModel - + Number Número - + Card Carta - + Price Precio + + DeckStatsInterface + + + + Error + Error + + + + The reply from the server could not be parsed. + La respuesta del servidor no puede ser interpretada. + + DeckViewContainer - + Load &local deck Cargar mazo &local - + Load d&eck from server Cargar mazo del &servidor - + Ready to s&tart Listo para &empezar - + + S&ideboard unlocked + &Reserva desbloqueada + + + + S&ideboard locked + R&eserva bloqueada + + + Load deck Cargar mazo + + + Error + Error + + + + The selected file could not be loaded. + El fichero seleccionado no puede cargarse. + Deck_PictureCacher @@ -1293,34 +1461,37 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DlgConnect - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + P&assword: &Contraseña: - - &OK - &Aceptar + + &Save password + &Guardar contraseña + + + &OK + &Aceptar - &Cancel - &Cancelar + &Cancelar @@ -1331,87 +1502,90 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DlgCreateGame - + &Description: &Descripción: - + &Password: &Contraseña: - + P&layers: &Jugadores: - + Game type Tipo de partida - + Only &buddies can join Sólo los &amigos pueden participar - + Only &registered users can join Sólo los usuarios &registrados pueden participar - + Joining restrictions Restricciones de participación - + &Spectators allowed Permitir e&spectadores - + Spectators &need a password to join Los espectadores &necesitan contraseña para unirse - + Spectators can &chat Los espectadores pueden &chatear - + Spectators see &everything Los espectadores pueden verlo &todo - + Spectators Espectadores - &OK - &Aceptar + &Aceptar - &Cancel - &Cancelar + &Cancelar - + Create game Crear partida - + + Game information + Información de la partida + + + Error Error - + Server error. Error del servidor. @@ -1419,115 +1593,281 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DlgCreateToken - + &Name: &Nombre: - + Token Ficha - + C&olor: &Color: - + white blanco - + blue azul - + black negro - + red rojo - + green verde - + multicolor multicolor - + colorless incoloro - + &P/T: &F/R: - + &Annotation: &Anotación: - + &Destroy token when it leaves the table &Destruir la ficha cuando deje la mesa - + + Token data + Información de la ficha + + + + Show &all tokens + Mostrar &todas los ficha + + + + Show tokens from this &deck + Mostrar todas las fichas de este &mazo + + + + Choose token from list + Elegir ficha de la lista + + &OK - &Aceptar + &Aceptar - &Cancel - &Cancelar + &Cancelar - + Create token Crear ficha - DlgLoadDeckFromClipboard + DlgEditTokens - - &Refresh - &Refrescar + + &Name: + &Nombre: - - &OK - &Aceptar + + C&olor: + &Color: - - &Cancel - &Cancelar + + white + blanco - - Load deck from clipboard - Cargar mazo del portapapeles + + blue + azul - + + black + negro + + + + red + rojo + + + + green + verde + + + + multicolor + multicolor + + + + colorless + incoloro + + + + &P/T: + &F/R: + + + + &Annotation: + &Anotación: + + + + Token data + Información de la ficha + + + + + Add token + Añadir ficha + + + + Remove token + Eliminar columna + + + + Edit tokens + Editar fichas + + + + Please enter the name of the token: + Por favor, introduzca el nombre de la ficha: + + + Error Error - + + The chosen name conflicts with an existing card or token. + El nombre elegido entra en conflicto con una carta o una ficha existente. + + + + DlgFilterGames + + + Show &unavailable games + Mostrar partidas &no disponibles + + + + Show &password protected games + + + + + Game &description: + &Descipción de la partida: + + + + &Creator name: + &Nombre del creador: + + + + &Game types + Tipo de &partida + + + + at &least: + al &menos: + + + + at &most: + &como máximo: + + + + Maximum player count + Máximo número de jugadores + + + O&K + &Aceptar + + + &Cancel + &Cancelar + + + + Filter games + Filtrar partidas + + + + DlgLoadDeckFromClipboard + + + &Refresh + &Refrescar + + + &OK + &Aceptar + + + &Cancel + &Cancelar + + + + Load deck from clipboard + Cargar mazo del portapapeles + + + + Error + Error + + + Invalid deck list. Lista de mazo invalida. @@ -1535,17 +1875,15 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DlgLoadRemoteDeck - O&K - &Aceptar + &Aceptar - &Cancel - &Cancelar + &Cancelar - + Load deck Cargar mazo @@ -1553,9 +1891,9 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DlgSettings - - - + + + Error Error @@ -1572,151 +1910,222 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona La ruta a tu directorio de imagenes de las cartas es invalida. - Your card database is invalid. Would you like to go back and set the correct path? - Tu base de datos de cartas es invalida. ¿Deseas volver y seleccionar la ruta correcta? + Tu base de datos de cartas es invalida. ¿Deseas volver y seleccionar la ruta correcta? - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de mazos es invalida. ¿Deseas volver y seleccionar la ruta correcta? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de imagenes de las cartas es invalida.¿Deseas volver y seleccionar la ruta correcta? - + Settings Preferencias - + General General - + Appearance Apariencia - + User interface Interfaz de usuario - + Deck editor Editor de mazos - + Messages Mensajes - &Close - &Cerrar + &Cerrar GameSelector - + C&reate C&rear - + &Join E&ntrar - - - - - - - - + + + + + + + + + Error Error - + Please join the appropriate room first. Por favor, entre en la sala adecuada primero. - + Wrong password. Contraseña incorrecta. - + Spectators are not allowed in this game. No se permiten espectadores en esta partida. - + The game is already full. La partida no tiene plazas libres. - + The game does not exist any more. La partida ya no existe. - + This game is only open to registered users. Esta partida está abierta sólo a usuarios registrados. - + This game is only open to its creator's buddies. Esta partida está abierta sólo a los amigos del creador. - + You are being ignored by the creator of this game. Estas siendo ignorado por el creador de la partida. - + Join game Entrar en la partida - + Password: Contraseña: - + + Please join the respective room first. + Por favor, entre primero en la sala respectiva. + + + Games Partidas - - Show &full games - Ver partidas &sin plazas libres + + &Filter games + &Filtrar partidas + + + + C&lear filter + &Limpiar filtros + + + Show &full games + Ver partidas &sin plazas libres - Show &running games - Mostrar partidas en &curso + Mostrar partidas en &curso &Show full games &Ver partidas sin plazas libres - + J&oin as spectator Entrar como e&spectador @@ -1732,72 +2141,72 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona GamesModel - + yes sí - + no no - + Creator Creador - + Description Descripción - + yes, free for spectators sí, libre para espectadores - + buddies only solo amigos - + reg. users only solo usuarios registrados - + not allowed no permitido - + Room Sala - + Game type Tipo de partida - + Password Contraseña - + Restrictions Restricciones - + Players Jugadores - + Spectators Espectadores @@ -1805,50 +2214,100 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona GeneralSettingsPage - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path Elija ruta - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Error + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings Preferencias personales - + Language: Idioma: - + Download card pictures on the fly Descargar imagenes de las cartas al vuelo - + + Download high-quality card pictures + + + + Paths Rutas - + Decks directory: Directorio de mazos: - + + Replays directory: + Directorio de replays: + + + Pictures directory: Directorio de imagenes: - - Path to card database: - Ruta a la base de datos de las cartas: + + Card database: + - - + + Token database: + + + + Path to card database: + Ruta a la base de datos de las cartas: + + + Path to token database: + Ruta a la base de datos de fichas: + + + + English Español @@ -1856,44 +2315,63 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona MainWindow - + There are too many concurrent connections from your address. Hay demasiadas conexiones simultaneas desde tu dirección. - Banned by moderator. - Baneado por el moderador. + Baneado por el moderador. - + Scheduled server shutdown. Desconexión programada del servidor. - Unknown reason. - Motivo desconocido. + Motivo desconocido. - + + Banned by moderator + Baneado por el moderador + + + + Expected end time: %1 + Tiempo de finalización estimado: %1 + + + + This ban lasts indefinitely. + Este ban permanecerá indefinidamente. + + + + + Invalid username. + + + + Connection closed Conexión cerrada - + The server has terminated your connection. Reason: %1 El servidor ha finalizado tu conexión. Motivo: %1 - + Scheduled server shutdown Desconexión programada del servidor - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1907,126 +2385,163 @@ Motivo para la desconexión: %1 - + Number of players Número de jugadores - + Please enter the number of players. Por favor, introduzca el número de jugadores. - - + + Player %1 Jugador %1 - + + Load replay + Cargar replay + + + About Cockatrice Acerca de Cockatrice - + Version %1 Versión %1 - + Authors: Autores: - + Translators: Traductores: - + Spanish: Español: - + Portugese (Portugal): Portugués (Portugal): - + Portugese (Brazil): Portugués (Brasil): - + French: Francés: - + Japanese: Japonés: - + Russian: Ruso: - Czech: - Checo: + Checo: - + Italian: + Italiano: + + + + Swedish: + Sueco: + + + + Incorrect username or password. Please check your authentication information and try again. + + + You are banned until %1. + Estás baneado hasta %1. + + + + You are banned indefinitely. + Estás baneado indefinidamente. + + + + This server requires user registration. + + + + + Unknown login error: %1 + Error de login desconocido: %1 + Slovak: Eslovaco: - - - - - - + + + + + + + + + + Error Error - + Server timeout Tiempo de espera del servidor agotado - Invalid login data. - Datos de conexión invalidos. + Datos de conexión invalidos. - + There is already an active session using this user name. Please close that session first and re-login. Ya existe una sesión activa usando ese nombre de usuario. Por favor, cierra esa sesión primero y reintentalo. - + Socket error: %1 Error del Socket: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Estás intentando conectar a un servidor obsoleto. Por favor, usa una versión anterior de Cockatrice o conecta a un servidor apropiado. La versión local es %1, la versión remota es %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Tu cliente de Cockatrice esta obsoleto. Por favor, actualiza tu versión de Cockatrice. @@ -2037,82 +2552,92 @@ La versión local es %1, la versión remota es %2. La versión del protocolo es diferente. Version local: %1, version remota: %2. - + Connecting to %1... Conectando a %1... - + Disconnected Desconectado - + + Connected, logging in at %1 + Conectado, hora de login: %1 + + + Logged in at %1 Conectado en %1 - + &Connect... &Conectar... - + &Disconnect &Desconectar - + Start &local game... Empezar partida &local... - + + &Watch replay... + &Ver replay... + + + &Deck editor Editor de &mazos - + &Full screen &Pantalla completa - + Ctrl+F CTRL+F - + &Settings... &Preferencias... - + &Exit &Salir - + &Cockatrice &Cockatrice - + &About Cockatrice &Acerca de Cockatrice - + &Help A&yuda - + Are you sure? ¿Estás seguro? - + There are still open games. Are you sure you want to quit? Todavía hay partidas abiertas. ¿Estás seguro que quieres salir? @@ -2136,17 +2661,22 @@ La versión local es %1, la versión remota es %2. Error del protocolo. - + The game has been closed. La partida ha sido cerrada. - + + You have been kicked out of the game. + Has sido expulsado de la partida. + + + %1 is now watching the game. %1 está ahora observando la partida. - + %1 is not watching the game any more. %1 ya no está observado más la partida. @@ -2166,37 +2696,49 @@ La versión local es %1, la versión remota es %2. - + You have joined game #%1. female Te has unido a la partida #%1. - - - You have joined game #%1. - male - Te has unido a la partida #%1. - - - - %1 has joined the game. - female - %1 se ha unido a la partida. - - - - %1 has joined the game. - male - %1 se ha unido a la partida. - + You have joined game #%1. + male + Te has unido a la partida #%1. + + + + You are watching a replay of game #%1. + female + Estás viendo un replay de la partida #%1. + + + + You are watching a replay of game #%1. + male + Estás viendo un replay de la partida #%1. + + + + %1 has joined the game. + female + %1 se ha unido a la partida. + + + + %1 has joined the game. + male + %1 se ha unido a la partida. + + + %1 has left the game. female %1 ha dejado la partida. - + %1 has left the game. male %1 ha dejado la partida. @@ -2221,92 +2763,92 @@ La versión local es %1, la versión remota es %2. male %1 ha cargado el mazo #%2. - - - %1 is ready to start the game. - female - %1 está preparado para empezar la partida. - - - - %1 is ready to start the game. - male - %1 está preparado para empezar la partida. - - - - %1 is not ready to start the game any more. - female - %1 ya no está listo para empezar el juego. - - - - %1 is not ready to start the game any more. - male - %1 ya no está listo para empezar el juego. - - - - %1 has conceded the game. - female - %1 ha concedido la partida. - - - - %1 has conceded the game. - male - %1 ha concedido la partida. - - - - %1 has restored connection to the game. - female - %1 ha recuperado la conexión a la partida. - + %1 is ready to start the game. + female + %1 está preparado para empezar la partida. + + + + %1 is ready to start the game. + male + %1 está preparado para empezar la partida. + + + + %1 is not ready to start the game any more. + female + %1 ya no está listo para empezar el juego. + + + + %1 is not ready to start the game any more. + male + %1 ya no está listo para empezar el juego. + + + + %1 has conceded the game. + female + %1 ha concedido la partida. + + + + %1 has conceded the game. + male + %1 ha concedido la partida. + + + + %1 has restored connection to the game. + female + %1 ha recuperado la conexión a la partida. + + + %1 has restored connection to the game. male %1 ha recuperado la conexión a la partida. - + %1 has lost connection to the game. female %1 ha perdido la conexión a la partida. - + %1 has lost connection to the game. male %1 ha perdido la conexión a la partida. - + %1 shuffles %2. female %1 baraja %2. - + %1 shuffles %2. male %1 baraja %2. - + %1 rolls a %2 with a %3-sided die. female %1 sacó un %2 con un dado de %3 caras. - + %1 rolls a %2 with a %3-sided die. male %1 sacó un %2 con un dado de %3 caras. - + %1 draws %n card(s). female @@ -2315,7 +2857,7 @@ La versión local es %1, la versión remota es %2. - + %1 draws %n card(s). male @@ -2324,182 +2866,182 @@ La versión local es %1, la versión remota es %2. - + %1 undoes his last draw. %1 deshace su último robo. - + %1 undoes her last draw. %1 deshace su último robo. - + %1 undoes his last draw (%2). %1 deshace su último robo (%2). - + %1 undoes her last draw (%2). %1 deshace su último robo (%2). - + from table de la mesa - + from graveyard del cementerio - + from exile del exilio - + from hand de la mano - + the bottom card of his library la carta de la parte inferior de su biblioteca - + the bottom card of her library la carta de la parte inferior de su biblioteca - + from the bottom of his library de la parte inferior de su biblioteca - + from the bottom of her library de la parte inferior de su biblioteca - + the top card of his library la parte superior de su biblioteca - + the top card of her library la carta superior de su biblioteca - + from the top of his library - de la parte superior de su biblioteca + desde la parte superior de su biblioteca - + from the top of her library - de la parte superior de su biblioteca + desde la parte superior de su biblioteca - + from library - de la biblioteca + desde la biblioteca - + from sideboard - de la reserva + desde la reserva - + from the stack - de la pila + desde la pila - + %1 gives %2 control over %3. %1 entrega a %2 el control sobre %3. - + %1 puts %2 into play tapped%3. %1 pone %2 en juego%3 girado. - + %1 puts %2 into play%3. %1 pone %2 en juego%3. - + %1 puts %2%3 into graveyard. %1 pone %2%3 en el cementerio. - + %1 exiles %2%3. %1 exilia %2%3. - + %1 moves %2%3 to hand. %1 mueve %2%3 a la mano. - + %1 puts %2%3 into his library. %1 pone %2%3 en su biblioteca. - + %1 puts %2%3 into her library. %1 pone %2%3 en su biblioteca. - + %1 puts %2%3 on bottom of his library. %1 pone %2%3 en la parte inferior de su biblioteca. - + %1 puts %2%3 on bottom of her library. %1 pone %2%3 en la parte inferior de su biblioteca. - + %1 puts %2%3 on top of his library. %1 pone %2%3 en la parte superior de su biblioteca. - + %1 puts %2%3 on top of her library. %1 pone %2%3 en la parte superior de su biblioteca. - + %1 puts %2%3 into his library at position %4. %1 pone %2%3 en su biblioteca en la posición %4. - + %1 puts %2%3 into her library at position %4. %1 pone %2%3 en su biblioteca en la posición %4. - + %1 moves %2%3 to sideboard. %1 mueve %2%3 a la reserva. - + %1 plays %2%3. %1 juega %2%3. - + %1 takes a mulligan to %n. female @@ -2508,7 +3050,7 @@ La versión local es %1, la versión remota es %2. - + %1 takes a mulligan to %n. male @@ -2517,37 +3059,37 @@ La versión local es %1, la versión remota es %2. - + %1 flips %2 face-down. female %1 voltea %2 boca abajo. - + %1 flips %2 face-down. male %1 voltea %2 boca abajo. - + %1 flips %2 face-up. female %1 voltea %2 boca arriba. - + %1 flips %2 face-up. male %1 voltea %2 boca arriba. - + %1 destroys %2. female %1 destruye %2. - + %1 destroys %2. male %1 destruye %2. @@ -2563,283 +3105,340 @@ La versión local es %1, la versión remota es %2. %1 anexa %2 a el %4 de %3. - %1 has loaded a deck (%2). female - %1 ha cargado el mazo %2. + %1 ha cargado el mazo %2. - %1 has loaded a deck (%2). male - %1 ha cargado el mazo %2. + %1 ha cargado el mazo %2. - + + %1 has loaded a deck (%2). + %1 ha cargado el mazo %2. + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + + %1 has locked her sideboard. + female + %1 ha bloqueado su reserva. + + + + %1 has locked his sideboard. + male + %1 ha bloqueado su reserva. + + + + %1 has unlocked her sideboard. + female + %1 ha desbloqueado su reserva. + + + + %1 has unlocked his sideboard. + male + %1 ha desbloqueado su reserva. + + + + the bottom card of %1's library + la carta de la parte inferior de la biblioteca de %1 + + + + from the bottom of %1's library + desde la parte inferior de la biblioteca de %1 + + + + the top card of %1's library + la carta superior de la biblioteca de %1 + + + + from the top of %1's library + desde la parte superior de la biblioteca de %1 + + + + from %1's library + desde la biblioteca de %1 + + + %1 attaches %2 to %3's %4. p1 female, p2 female %1 anexa %2 a el %4 de %3. - + %1 attaches %2 to %3's %4. p1 female, p2 male %1 anexa %2 a el %4 de %3. - + %1 attaches %2 to %3's %4. p1 male, p2 female %1 anexa %2 a el %4 de %3. - + %1 attaches %2 to %3's %4. p1 male, p2 male %1 anexa %2 a el %4 de %3. - + %1 unattaches %2. female %1 desanexa %2. - + %1 unattaches %2. male %1 desanexa %2. - + %1 creates token: %2%3. female %1 crea una ficha: %2%3. - + %1 creates token: %2%3. male %1 crea una ficha: %2%3. - + %1 points from her %2 to herself. female %1 apunta desde su %2 a si misma. - + %1 points from his %2 to himself. male %1 apunta desde su %2 a si mismo. - + %1 points from her %2 to %3. p1 female, p2 female %1 apunta desde su %2 a %3. - + %1 points from her %2 to %3. p1 female, p2 male %1 apunta desde su %2 a %3. - + %1 points from his %2 to %3. p1 male, p2 female %1 apunta desde su %2 a %3. - + %1 points from his %2 to %3. p1 male, p2 male %1 apunta desde su %2 a %3. - + %1 points from %2's %3 to herself. card owner female, target female %1 apunta desde el %3 de %2 a ella misma. - + %1 points from %2's %3 to herself. card owner male, target female %1 apunta desde el %3 de %2 a ella misma. - + %1 points from %2's %3 to himself. card owner female, target male %1 apunta desde el %3 de %2 a él mismo. - + %1 points from %2's %3 to himself. card owner male, target male %1 apunta desde el %3 de %2 a él mismo. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female %1 apunta desde el %3 de %2 a %4. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male %1 apunta desde el %3 de %2 a %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female %1 apunta desde el %3 de %2 a %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male %1 apunta desde el %3 de %2 a %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female %1 apunta desde el %3 de %2 a %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male %1 apunta desde el %3 de %2 a %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female %1 apunta desde el %3 de %2 a %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male %1 apunta desde el %3 de %2 a %4. - + %1 points from her %2 to her %3. female %1 apunta desde su %2 a su %3. - + %1 points from his %2 to his %3. male %1 apunta desde su %2 a su %3. - + %1 points from her %2 to %3's %4. p1 female, p2 female %1 apunta desde su %2 al %4 de %3. - + %1 points from her %2 to %3's %4. p1 female, p2 male %1 apunta desde su %2 al %4 de %3. - + %1 points from his %2 to %3's %4. p1 male, p2 female %1 apunta desde su %2 al %4 de %3. - + %1 points from his %2 to %3's %4. p1 male, p2 male %1 apunta desde su %2 al %4 de %3. - + %1 points from %2's %3 to her own %4. card owner female, target female %1 apunta desde el %3 de %2 a su propio %4. - + %1 points from %2's %3 to her own %4. card owner male, target female %1 apunta desde el %3 de %2 a su propio %4. - + %1 points from %2's %3 to his own %4. card owner female, target male %1 apunta desde el %3 de %2 a su propio %4. - + %1 points from %2's %3 to his own %4. card owner male, target male %1 apunta desde el %3 de %2 a su propio %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female %1 apunta desde el %3 de %2 al %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male %1 apunta desde el %3 de %2 al %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female %1 apunta desde el %3 de %2 al %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male %1 apunta desde el %3 de %2 al %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female %1 apunta desde el %3 de %2 al %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male %1 apunta desde el %3 de %2 al %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female %1 apunta desde el %3 de %2 al %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male %1 apunta desde el %3 de %2 al %5 de %4. - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -2848,7 +3447,7 @@ La versión local es %1, la versión remota es %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -2857,7 +3456,7 @@ La versión local es %1, la versión remota es %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -2866,7 +3465,7 @@ La versión local es %1, la versión remota es %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -2875,272 +3474,322 @@ La versión local es %1, la versión remota es %2. - + %1 taps her permanents. female %1 gira sus permanentes. - + %1 untaps her permanents. female %1 endereza sus permanentes. - + %1 taps his permanents. male %1 gira sus permanentes. - + %1 untaps his permanents. male %1 endereza sus permanentes. - + %1 taps %2. female %1 gira %2. - + %1 untaps %2. female %1 endereza %2. - - - %1 taps %2. - male - %1 gira %2. - - - - %1 untaps %2. - male - %1 endereza %2. - - - - %1 sets counter %2 to %3 (%4%5). - female - %1 establece los contadores de %2 a %3 (%4%5). - - - - %1 sets counter %2 to %3 (%4%5). - male - %1 establece los contadores de %2 a %3 (%4%5). - - - - %1 sets %2 to not untap normally. - female - %1 establece que %2 no se endereze normalmente. - - - - %1 sets %2 to not untap normally. - male - %1 establece que %2 no se endereze normalmente. - - - - %1 sets %2 to untap normally. - female - %1 establece que %2 se endereze normalmente. - - - - %1 sets %2 to untap normally. - male - %1 establece que %2 se endereze normalmente. - - - - %1 sets PT of %2 to %3. - female - %1 establece la F/R de %2 a %3. - - - - %1 sets PT of %2 to %3. - male - %1 establece la F/R de %2 a %3. - - - - %1 sets annotation of %2 to %3. - female - %1 establece la anotación de %2 a %3. - - - - %1 sets annotation of %2 to %3. - male - %1 establece la anotación de %2 a %3. - - - - %1 is looking at the top %2 cards %3. - female - %1 esta mirando las primeras %2 cartas de %3. - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - %1 esta mirando las primeras %2 cartas de %3. + %1 gira %2. - - %1 is looking at %2. - female - %1 está mirando a %2. - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 está mirando a %2. + %1 endereza %2. - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female - %1 termina de mirar a %2. + %1 establece los contadores de %2 a %3 (%4%5). + %1 sets counter %2 to %3 (%4%5). + male + %1 establece los contadores de %2 a %3 (%4%5). + + + + %1 sets %2 to not untap normally. + female + %1 establece que %2 no se endereze normalmente. + + + + %1 sets %2 to not untap normally. + male + %1 establece que %2 no se endereze normalmente. + + + + %1 sets %2 to untap normally. + female + %1 establece que %2 se endereze normalmente. + + + + %1 sets %2 to untap normally. + male + %1 establece que %2 se endereze normalmente. + + + + %1 sets PT of %2 to %3. + female + %1 establece la F/R de %2 a %3. + + + + %1 sets PT of %2 to %3. + male + %1 establece la F/R de %2 a %3. + + + + %1 sets annotation of %2 to %3. + female + %1 establece la anotación de %2 a %3. + + + + %1 sets annotation of %2 to %3. + male + %1 establece la anotación de %2 a %3. + + + %1 is looking at the top %2 cards %3. + female + %1 esta mirando las primeras %2 cartas de %3. + + + %1 is looking at the top %2 cards %3. + male + %1 esta mirando las primeras %2 cartas de %3. + + + + %1 is looking at %2. + female + %1 está mirando a %2. + + + + %1 is looking at %2. + male + %1 está mirando a %2. + + + + %1 is looking at the top %n card(s) %2. + female + + %1 esta mirando la primera %n carta de %2. + %1 esta mirando las primeras %n cartas de %2. + + + + + %1 is looking at the top %n card(s) %2. + male + + %1 esta mirando la primera %n carta de %2. + %1 esta mirando las primeras %n cartas de %2. + + + + + %1 stops looking at %2. + female + %1 termina de mirar a %2. + + + %1 stops looking at %2. male %1 termina de mirar a %2. - - - %1 reveals %2 to %3. - p1 female, p2 female - %1 revela %2 a %3. - - - - %1 reveals %2 to %3. - p1 female, p2 male - %1 revela %2 a %3. - - - - %1 reveals %2 to %3. - p1 male, p2 female - %1 revela %2 a %3. - - - - %1 reveals %2 to %3. - p1 male, p2 male - %1 revela %2 a %3. - - - - %1 reveals %2. - female - %1 revela %2. - - - - %1 reveals %2. - male - %1 revela %2. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - %1 revela aleatoriamente %2%3 a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - %1 revela aleatoriamente %2%3 a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - %1 revela aleatoriamente %2%3 a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - %1 revela aleatoriamente %2%3 a %4. - - - - %1 randomly reveals %2%3. - female - %1 revela aleatoriamente %2%3. - - - - %1 randomly reveals %2%3. - male - %1 revela aleatoriamente %2%3. - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - %1 revela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - %1 revela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - %1 revela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - %1 revela %2%3 a %4. - - - - %1 reveals %2%3. - female - %1 revela %2%3. - - - - %1 reveals %2%3. - male - %1 revela %2%3. - - - - It is now %1's turn. - female - Es el turno de %1. - + %1 reveals %2 to %3. + p1 female, p2 female + %1 revela %2 a %3. + + + + %1 reveals %2 to %3. + p1 female, p2 male + %1 revela %2 a %3. + + + + %1 reveals %2 to %3. + p1 male, p2 female + %1 revela %2 a %3. + + + + %1 reveals %2 to %3. + p1 male, p2 male + %1 revela %2 a %3. + + + + %1 reveals %2. + female + %1 revela %2. + + + + %1 reveals %2. + male + %1 revela %2. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + %1 revela aleatoriamente %2%3 a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + %1 revela aleatoriamente %2%3 a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + %1 revela aleatoriamente %2%3 a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + %1 revela aleatoriamente %2%3 a %4. + + + + %1 randomly reveals %2%3. + female + %1 revela aleatoriamente %2%3. + + + + %1 randomly reveals %2%3. + male + %1 revela aleatoriamente %2%3. + + + + %1 peeks at face down card #%2. + female + %1 mira la carta bocaabajo #%2. + + + + %1 peeks at face down card #%2. + male + %1 mira la carta bocaabajo #%2. + + + + %1 peeks at face down card #%2: %3. + female + %1 mira la carta bocaabajo #%2: %3. + + + + %1 peeks at face down card #%2: %3. + male + %1 mira la carta bocaabajo #%2: %3. + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + %1 revela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + %1 revela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + %1 revela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + %1 revela %2%3 a %4. + + + + %1 reveals %2%3. + female + %1 revela %2%3. + + + + %1 reveals %2%3. + male + %1 revela %2%3. + + + + %1 is now keeping the top card %2 revealed. + %1 está manteniendo la carta superior %2 revelada. + + + + %1 is not revealing the top card %2 any longer. + %1 ya no mantiene revelada la carta superior %2. + + + + It is now %1's turn. + female + Es el turno de %1. + + + It is now %1's turn. male Es el turno de %1. - - + + a card una carta @@ -3179,7 +3828,7 @@ La versión local es %1, la versión remota es %2. - + red rojo @@ -3187,7 +3836,7 @@ La versión local es %1, la versión remota es %2. - + yellow amarillo @@ -3195,7 +3844,7 @@ La versión local es %1, la versión remota es %2. - + green verde @@ -3219,7 +3868,7 @@ La versión local es %1, la versión remota es %2. %1 esta mirando las primeras %2 cartas de %3. - + The game has started. La partida ha comenzado. @@ -3314,7 +3963,7 @@ La versión local es %1, la versión remota es %2. %1 revela %2. - + ending phase fase de fin de turno @@ -3327,12 +3976,12 @@ La versión local es %1, la versión remota es %2. %1 baraja su biblioteca. - + %1 draws his initial hand. %1 roba su mano inicial. - + %1 draws her initial hand. %1 roba su mano inicial. @@ -3353,57 +4002,57 @@ La versión local es %1, la versión remota es %2. %1 revela %2%3. - + untap step paso de enderezar - + upkeep step paso de mantenimiento - + draw step paso de robar - + first main phase primera fase principal - + beginning of combat step paso de inicio de combate - + declare attackers step paso de declarar atacantes - + declare blockers step paso de declarar bloqueadores - + combat damage step paso de daño de combate - + end of combat step paso de fin de combate - + second main phase segunda fase principal - + It is now the %1. Ahora es el %1. @@ -3431,22 +4080,22 @@ La versión local es %1, la versión remota es %2. MessagesSettingsPage - + &Add &Añadir - + &Remove &Quitar - + Add message Añadir mensaje - + Message: Mensaje: @@ -3454,57 +4103,57 @@ La versión local es %1, la versión remota es %2. PhasesToolbar - + Untap step Paso de enderezar - + Upkeep step Paso de mantenimiento - + Draw step Paso de robar - + First main phase Primera fase principal - + Beginning of combat step Paso de inicio del combate - + Declare attackers step Paso de declarar atacantes - + Declare blockers step Paso de declarar bloqueadores - + Combat damage step Paso de daño de combate - + End of combat step Paso de final del combate - + Second main phase Segunda fase principal - + End of turn step Paso final @@ -3512,128 +4161,143 @@ La versión local es %1, la versión remota es %2. Player - - - + + + Move to &top of library Mover a la &parte superior de la biblioteca - - - + + + Move to &bottom of library Mover al &fondo de la biblioteca - - + + Move to &graveyard Mover al &cementerio - + &View library &Ver biblioteca - + Reveal &library to Revelar &biblioteca a - + Reveal t&op card to Revelar la carta &superior de la biblioteca a - + + &Always reveal top card + &Siempre revelar la carta superior + + + + O&pen deck in deck editor + &Abrir mazo en el editor de mazos + + + &Undo last draw &Deshacer último robo - + Move top cards to &graveyard... Mover cartas de la parte s&uperior de la biblioteca al cementerio... - + + Ctrl+J + + + + F3 F3 - + View &top cards of library... Ver cartas de la parte &superior de la biblioteca... - + &View graveyard Ver &Cementerio - + F4 F4 - + &View sideboard Ver &sideboard - + Player "%1" Jugador "%1" - + &Hand &Mano - + &Library &Biblioteca - + &Graveyard &Cementerio - + &Sideboard &Reserva - + View top cards of library Ver cartas de la parte superior de la biblioteca - + Number of cards: Número de cartas: - + &Draw card &Robar carta - + &View exile Ver &exilio - + &Exile &Exilio - - + + Move to &hand Mover a la m&ano @@ -3642,98 +4306,98 @@ La versión local es %1, la versión remota es %2. Mover al &cementerio - - + + Move to &exile Mover al &exilio - + Ctrl+W Ctrl+W - + Ctrl+D Ctrl+D - + D&raw cards... &Robar cartas... - + Ctrl+E Ctrl+E - + Take &mulligan Hacer &mulligan - + Ctrl+M Ctrl+M - + &Shuffle &Barajar - + Ctrl+S Ctrl+S - + &Counters &Contadores - + &Untap all permanents &Enderezar todos los permanentes - + Ctrl+U Ctrl+U - + R&oll die... &Lanzar dado... - + Ctrl+I Ctrl+I - + &Create token... Crear &Ficha... - + Ctrl+T Ctrl+T - + C&reate another token C&rea otra ficha - + Ctrl+G Ctrl+G - + S&ay D&ecir @@ -3742,100 +4406,299 @@ La versión local es %1, la versión remota es %2. Mover cartas superiores al ce&menterio... - + Move top cards to &exile... Mover cartas superiores al &exilio... - + Put top card on &bottom Poner carta superior en la parte &inferior - + &Reveal to &Revelar a - + Reveal r&andom card to Revelar carta &aleatoriamente a - + + Cr&eate predefined token + Cr&ear ficha predefinida + + + C&ard C&arta - + &All players &Todos los jugadores - + + &Play + &Jugar + + + + &Hide + &Ocultar + + + + &Tap + &Girar + + + + &Untap + &Enderezar + + + + Toggle &normal untapping + Alternar enderezamiento &normal + + + + &Flip + &Voltear + + + + &Peek at card face + &Mirar el dorso de la carta + + + + &Clone + &Clonar + + + Ctrl+H + Ctrl+H + + + + Attac&h to card... + Ane&xar a una carta... + + + + Ctrl+A + Ctrl+A + + + + Unattac&h + Desane&xar + + + + &Draw arrow... + &Dibujar flecha... + + + + &Increase power + &Incrementar fuerza + + + + Ctrl++ + Ctrl++ + + + + &Decrease power + &Decrementar fuerza + + + + Ctrl+- + Ctrl+- + + + + I&ncrease toughness + I&ncrementar resistencia + + + + Alt++ + Alt++ + + + + D&ecrease toughness + D&ecrementar resistencia + + + + Alt+- + Alt+- + + + + In&crease power and toughness + In&crementar fuerza y resistencia + + + + Ctrl+Alt++ + Ctrl+Alt++ + + + + Dec&rease power and toughness + Dec&rementar fuerza y resistencia + + + + Ctrl+Alt+- + Ctrl+Alt+- + + + + Set &power and toughness... + Establecer &fuerza y resistencia... + + + + Ctrl+P + Ctrl+P + + + + &Set annotation... + E&scribir anotación... + + + + red + rojo + + + + yellow + amarillo + + + + green + verde + + + + &Add counter (%1) + &Añadir contador (%1) + + + + &Remove counter (%1) + &Quitar contador (%1) + + + + &Set counters (%1)... + E&stablecer contadores (%1)... + + + + &top of library + &parte superior de la biblioteca + + + + &bottom of library + &fondo de la biblioteca + + + + &graveyard + &cementerio + + + + Ctrl+Del + Ctrl+Del + + + + &exile + &exilio + + + Ctrl+F3 Ctrl+F3 - + Ctrl+Shift+D Ctrl+Shift+D - + Draw cards Robar cartas - - - - + + + + Number: Número: - + Move top cards to grave Mover cartas superiores al cementerio - + Move top cards to exile Mover cartas superiores al exilio - + Roll die Lanzar dado - + Number of sides: Número de caras: - + Set power/toughness Establecer fuerza/resistencia - + Please enter the new PT: Por favor, introduzca la nueva F/R: - + Set annotation Escribir anotación - + Please enter the new annotation: Por favor, introduza la nueva anotación: - + Set counters Establecer contadores @@ -3875,39 +4738,32 @@ La versión local es %1, la versión remota es %2. mazo #%1 - User &details - &Detalles del usuario + &Detalles del usuario - Direct &chat - &Chat privado + &Chat privado - Add to &buddy list - Añadir a la lista de &amigos + Añadir a la lista de &amigos - Remove from &buddy list - Quitar de la lista de &amigos + Quitar de la lista de &amigos - Add to &ignore list - Añadir a la lista de &ignorados + Añadir a la lista de &ignorados - Remove from &ignore list - Quitar de la lista de &ignorados + Quitar de la lista de &ignorados - Kick from &game - Expulsar de la &partida + Expulsar de la &partida ID #%1 @@ -3917,78 +4773,117 @@ La versión local es %1, la versión remota es %2. QObject - Maindeck - Mazo principal + Mazo principal - Sideboard - Reserva + Reserva - Cockatrice decks (*.cod) - Mazos de Cockatrice (*.cod) + Mazos de Cockatrice (*.cod) - Plain text decks (*.dec *.mwDeck) - Archivos de texto plano (*.dec *.mwDeck) + Archivos de texto plano (*.dec *.mwDeck) - + + Common deck formats (*.cod *.dec *.mwDeck) + + + + All files (*.*) Todos los archivos (*.*) + + + Cockatrice replays (*.cor) + Replays de Cockatrice (*.cor) + RemoteDeckList_TreeModel - + Name Nombre - + ID ID - + Upload time Tiempo de subida - RoomSelector + RemoteReplayList_TreeModel - - Rooms - Salas + + ID + ID - - Joi&n - E&ntrar + + Name + Nombre - - Room - Sala - - - - Description - Descripción - - - + Players Jugadores - + + Keep + Mantener + + + + Time started + Tiempo de inicio + + + + Duration (sec) + Duración (sec) + + + + RoomSelector + + + Rooms + Salas + + + + Joi&n + E&ntrar + + + + Room + Sala + + + + Description + Descripción + + + + Players + Jugadores + + + Games Partidas @@ -4016,27 +4911,25 @@ La versión local es %1, la versión remota es %2. ShutdownDialog - + &Reason for shutdown: &Motivo para la desconexión: - + &Time until shutdown (minutes): &Tiempo hasta la desconexión (minutos): - &OK - &Aceptar + &Aceptar - &Cancel - &Cancelar + &Cancelar - + Shut down server Apagar servidor @@ -4044,44 +4937,43 @@ La versión local es %1, la versión remota es %2. TabAdmin - + Update server &message Actualizar &mensaje del servidor - + &Shut down server &Apagar servidor - + Server administration functions Funciones de administración del servidor - + &Unlock functions &Desbloquear funciones - + &Lock functions &Bloquear funciones - + Unlock administration functions Desbloquear funciones de administración - + Do you really want to unlock the administration functions? ¿Realmente quieres desbloquear las funciones de administración? - Administration - Administración + Administración @@ -4103,207 +4995,468 @@ La versión local es %1, la versión remota es %2. %1 ha abandonado el canal. + + TabDeckEditor + + &Search... + &Buscar... + + + + Show card text only + + + + + &Clear search + &Limpiar busqueda + + + + &Search for: + &Buscar por: + + + + Deck &name: + &Nombre del mazo: + + + + &Comments: + &Comentarios: + + + + Hash: + Hash: + + + + &Update prices + &Actualizar precios + + + + Ctrl+U + Ctrl+U + + + + &New deck + &Nuevo mazo + + + + &Load deck... + &Cargar mazo... + + + + &Save deck + &Guardar mazo + + + + Save deck &as... + Guardar mazo &como... + + + + Load deck from cl&ipboard... + Cargar mazo del &portapapeles... + + + + Save deck to clip&board + Guardar mazo al p&ortapales + + + + &Print deck... + Im&primir mazo... + + + + &Analyze deck on deckstats.net + A&nalizar mazo en deckstats.net + + + + &Close + &Cerrar + + + + Ctrl+Q + Ctrl+Q + + + + Add card to &maindeck + Añadir carta al &mazo principal + + + Return + Return + + + Enter + Enter + + + + Add card to &sideboard + Añadir carta a la &reserva + + + Ctrl+Return + Ctrl+Return + + + Ctrl+Enter + Ctrl+Enter + + + + &Remove row + &Eliminar columna + + + + Del + Del + + + + &Increment number + &Incrementar número + + + + + + + + + + + &Decrement number + &Decrementar número + + + + - + - + + + + &Deck editor + Editor de &mazos + + + + C&ard database + + + + + &Edit sets... + &Editar ediciones... + + + + Edit &tokens... + Editar &fichas... + + + + Deck: %1 + Mazo: %1 + + + + Are you sure? + ¿Estás seguro? + + + + The decklist has been modified. +Do you want to save the changes? + La lista del mazo ha sido modificada +¿Deseas guardar los cambios? + + + + Load deck + Cargar mazo + + + + + + Error + Error + + + + The deck could not be saved. + El mazo no puede ser guardado. + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + El mazo no puede ser guardado +Por favor, compruebe que tiene permisos de escritura en el directorio e intentelo de nuevo. + + + + Save deck + Guardar mazo + + TabDeckStorage - + Local file system Sistema de archivos local - + Server deck storage Almacen de mazos del servidor - - + + Open in deck editor Abrir en el editor de mazos - + Upload deck Subir mazo - + Download deck Descargar mazo - - + + New folder Nueva carpeta - + + Delete Borrar - + Enter deck name Introduzca el nombre del mazo - + This decklist does not have a name. Please enter a name: Esta lista de mazo no tiene un nombre. Por favor, introduzca un nombre: - + Unnamed deck Mazo sin nombre - + + Delete local file + Borrar fichero local + + + + + + Are you sure you want to delete "%1"? + ¿Estás seguro de que quieres borrar "%1"? + + + Name of new folder: Nombre de la nueva carpeta: - + + Delete remote folder + + + + + Delete remote deck + Borrar mazo remoto + + Deck storage - Almacen de mazos + Almacen de mazos TabGame - + F5 F5 - + F6 F6 - + F7 F7 - + F8 F8 - + F9 F9 - + F10 F10 - + &Phases &Fases - + &Game &Partida - + Next &phase Próxima &fase - + Ctrl+Space Ctrl+Space - + + Tab + + + + Next &turn Próximo &turno - + Ctrl+Return Ctrl+Return - + Ctrl+Enter Ctrl+Enter - + &Remove all local arrows &Retirar todas las flechas locales - + Ctrl+R Ctrl+R - + + Game &information + &Información de la partida + + + &Concede &Conceder - + F2 F2 - + &Leave game &Abandonar la partida - + + Ctrl+Q Ctrl+Q - + + C&lose replay + &Cerrar replay + + + &Say: &Decir: - + Concede Conceder - + Are you sure you want to concede this game? ¿Estás seguro de que quieres conceder esta partida? - + Leave game Abandonar la partida - + Are you sure you want to leave this game? ¿Estás seguro de que quieres abandonar la partida? - + + Replay %1: %2 + Replay %1: %2 + + Kicked - Expulsado + Expulsado - You have been kicked out of the game. - Has sido expulsado de la partida. + Has sido expulsado de la partida. - + Game %1: %2 Partida %1: %2 @@ -4311,60 +5464,124 @@ Por favor, introduzca un nombre: TabMessage - + Personal &talk &Conversación personal - + &Leave &Cerrar - + This user is ignoring you. Este usuario está ignorandote. - + %1 has left the server. %1 ha abandonado el servidor. - + %1 has joined the server. %1 se ha unido al servidor. - + Talking to %1 Hablando con %1 + + TabReplays + + + Local file system + Sistema de archivos local + + + + Server replay storage + Almacen de replays del servidor + + + + + Watch replay + Ver replay + + + + + Delete + Borrar + + + + Download replay + Descargar replay + + + + Toggle expiration lock + Establecer expiración del bloqueo + + + + Delete local file + Borrar fichero local + + + + Are you sure you want to delete "%1"? + ¿Estás seguro de que quieres borrar "%1"? + + + + Delete remote replay + Borrar replay remoto + + + + Are you sure you want to delete the replay of game %1? + ¿Estás seguro de que quieres borar el replay de la partida %1? + + + Game replays + Replays de partidas + + TabRoom - + &Say: &Decir: - + Chat Chat - + &Room &Sala - + &Leave room &Dejar sala - + + &Ignore unregistered users in chat + &Ignorar usuarios no registrados en el chat + + + You are flooding the chat. Please wait a couple of seconds. Estás floodeando el chat. Por favor, espera unos segundos. @@ -4380,53 +5597,114 @@ Por favor, introduzca un nombre: TabServer - Server - Servidor + Servidor TabUserLists - User lists - Lista de usuarios + Lista de usuarios + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + &Detalles del usuario + + + + Direct &chat + &Chat privado + + + + Show this user's &games + Mostrar &partidas de este usuario + + + + Add to &buddy list + Añadir a la lista de &amigos + + + + Remove from &buddy list + Quitar de la lista de &amigos + + + + Add to &ignore list + Añadir a la lista de &ignorados + + + + Remove from &ignore list + Quitar de la lista de &ignorados + + + + Kick from &game + Expulsar de la &partida + + + + Ban from &server + Banear del &servidor + + + + %1's games + Partidas de %1 UserInfoBox - + User information Información del usuario - + Real name: Nombre real: - + Gender: Género: - + Location: Localización: - + User level: Nivel de usuario: - + Administrator Administrador - + Moderator Moderador @@ -4435,12 +5713,12 @@ Por favor, introduzca un nombre: Juez - + Registered user Usuario registrado - + Unregistered user Usuario no registrado @@ -4448,37 +5726,47 @@ Por favor, introduzca un nombre: UserInterfaceSettingsPage - + General interface settings Preferencias generales de la interfaz - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) &Doble click en las cartas para jugarlas (en lugar de un solo click) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings Opciones de animación - + &Tap/untap animation Animación de &girar/enderezar - + Enable &sounds Activar &sonidos - + Path to sounds directory: Ruta al directorio de sonidos: - + Choose path Elija ruta @@ -4486,69 +5774,60 @@ Por favor, introduzca un nombre: UserList - + Users online: %1 Usuarios online: %1 - + Users in this room: %1 Usuarios en esta sala: %1 - + Buddies online: %1 / %2 Amigos online: %1 / %2 - + Ignored users online: %1 / %2 Usuarios ignorados online: %1 / %2 - %1's games - Partidas de %1 + Partidas de %1 - User &details - &Detalles del usuario + &Detalles del usuario - Direct &chat - &Chat privado + &Chat privado - Show this user's &games - Mostrar &partidas de este usuario + Mostrar &partidas de este usuario - Add to &buddy list - Añadir a la lista de &amigos + Añadir a la lista de &amigos - Remove from &buddy list - Quitar de la lista de &amigos + Quitar de la lista de &amigos - Add to &ignore list - Añadir a la lista de &ignorados + Añadir a la lista de &ignorados - Remove from &ignore list - Quitar de la lista de &ignorados + Quitar de la lista de &ignorados - Ban from &server - Banear del &servidor + Banear del &servidor Duration @@ -4564,204 +5843,159 @@ Indica 0 para un ban indefinido. WndDeckEditor - &Search for: - &Buscar por: + &Buscar por: - Deck &name: - &Nombre del mazo: + &Nombre del mazo: - &Comments: - &Comentarios: + &Comentarios: - Deck editor [*] - Editor de mazos [*] + Editor de mazos [*] - &New deck - &Nuevo mazo + &Nuevo mazo - &Load deck... - &Cargar mazo... + &Cargar mazo... - Load deck from cl&ipboard... - Cargar mazo del &portapapeles... + Cargar mazo del &portapapeles... - &Save deck - &Guardar mazo + &Guardar mazo - - Hash: - - - - &Update prices - &Actualizar precios + &Actualizar precios - Ctrl+U - Ctrl+U + Ctrl+U - Save deck &as... - Guardar mazo &como... + Guardar mazo &como... - Save deck to clip&board - Guardar mazo al p&ortapales + Guardar mazo al p&ortapales - &Print deck... - Im&primir mazo... + Im&primir mazo... - &Close - &Cerrar + &Cerrar - Ctrl+Q - Ctrl+Q + Ctrl+Q - &Edit sets... - &Editar ediciones... + &Editar ediciones... - &Deck - &Mazo + &Mazo - Load deck - Cargar mazo + Cargar mazo - - Error - Error + Error - - The deck could not be saved. Please check that the directory is writable and try again. - El mazo no puede guardarse + El mazo no puede guardarse Por favor, compruebe que tiene permisos de escritura en el directorio e intentelo de nuevo. - Save deck - Guardar mazo + Guardar mazo - Add card to &maindeck - Añadir carta al &mazo principal + Añadir carta al &mazo principal - Return - Return + Return - Enter - Enter + Enter - Ctrl+Return - Ctrl+Return + Ctrl+Return - Ctrl+Enter - Ctrl+Enter + Ctrl+Enter - Add card to &sideboard - Añadir carta a la &reserva + Añadir carta a la &reserva - &Search... - &Buscar... + &Buscar... - &Clear search - &Limpiar busqueda + &Limpiar busqueda - &Card database - &Base de datos de cartas + &Base de datos de cartas - &Remove row - &Eliminar columna + &Eliminar columna - Del - Del + Del - &Increment number - &Incrementar número + &Incrementar número - + - + + + - &Decrement number - &Decrementar número + &Decrementar número - - - - + - - Are you sure? - ¿Estás seguro? + ¿Estás seguro? - The decklist has been modified. Do you want to save the changes? - La lista del mazo ha sido modificada + La lista del mazo ha sido modificada ¿Deseas guardar los cambios? @@ -4776,17 +6010,17 @@ Do you want to save the changes? ZoneViewWidget - + sort by name ordenar por nombre - + sort by type ordenar por tipo - + shuffle when closing barajar al cerrar diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index e8409f928..6d205092a 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... &Nombre de marqueurs... - + Ctrl+L Ctrl+L - + F11 F11 - + F12 F12 - + Set counter Nombre de marqueurs - + New value for counter '%1': Nouveau nombre de marqueurs '%1': @@ -37,91 +37,111 @@ AppearanceSettingsPage - + Zone background pictures Zone images de fond - Path to hand background: - Chemin pour les images de fond de main: + Chemin pour les images de fond de main: - Path to stack background: - Chemin pour les images de fond de pile: + Chemin pour les images de fond de pile: - Path to table background: - Chemin pour les images d'arrière-plan: + Chemin pour les images d'arrière-plan: - Path to player info background: - Chemin pour les images de fond d'affichage d'informations: + Chemin pour les images de fond d'affichage d'informations: - Path to picture of card back: - Chemin pour les images de dos des cartes: + Chemin pour les images de dos des cartes: - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Card rendering Rendu des cartes - + Display card names on cards having a picture Afficher le nom des cartes ayant une image - + Hand layout Disposition de la main - + Display hand horizontally (wastes space) Montrer la main horizontalement - + Table grid layout Disposition en forme de grille - + Invert vertical coordinate Inverser la disposition du champ de bataille - + Minimum player count for multi-column layout: Nombre minimum de joueurs pour la disposition multi-colonnes : - + Zone view layout Voir disposition de la zone - + Sort by name Tri par nom - + Sort by type Tri par type - - - - - + + + + + Choose path Choisir le chemin @@ -135,79 +155,84 @@ Enter 0 for an indefinite ban. Entrez 0 pour une durée illimitée du ban. - + ban &user name - + ban &IP address - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Veuillez expliquer la raison du ban. Cette information ne sera consultable que par les modérateurs. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK &OK - + &Cancel &Annuler - + Ban user from server Bannir le joueur du serveur - + Error Erreur - + You have to select a name-based or IP-based ban, or both. @@ -215,277 +240,267 @@ Cette information ne sera consultable que par les modérateurs. CardDatabaseModel - + Name Nom - + Sets Éditions - + Mana cost Coût de mana - + Card type Type de carte - + P/T F/E - CardInfoWidget + CardInfoText - - Hide card info - Cacher les informations relatives aux cartes + + Name: + Nom: - + + Mana cost: + Cout de mana: + + + + Card type: + Type de carte: + + + + P / T: + F / E: + + + + Loyalty: + + + + + CardInfoWidget + + Hide card info + Cacher les informations relatives aux cartes + + + Show card only Montrer la carte uniquement - + Show text only Montrer le texte uniquement - + Show full info Montrer toutes les informations - + Name: Nom: - + Mana cost: Cout de mana: - + Card type: Type de carte: - + P / T: F / E: + + + Loyalty: + + CardItem - &Play - &Jouer + &Jouer - &Hide - &Cacher + &Cacher - &Tap - &Engager + &Engager - &Untap - &Dégager + &Dégager - Toggle &normal untapping - Activer/ Désactiver le dégagement &normal + Activer/ Désactiver le dégagement &normal - &Flip - &Retourner la carte + &Retourner la carte - &Clone - &Copier une carte + &Copier une carte - - Ctrl+H - - - - &Attach to card... - &Attacher à la carte... + &Attacher à la carte... - Ctrl+A - Ctrl+A + Ctrl+A - Unattac&h - Détac&her + Détac&her Set &P/T... Fixer &F/E... - &Draw arrow... - &Tracer une flèche... + &Tracer une flèche... - + &Power / toughness F&orce / Endurance - &Increase power - &Augmenter force + &Augmenter force - Ctrl++ - Ctrl++ + Ctrl++ - &Decrease power - &Diminuer force + &Diminuer force - Ctrl+- - Ctrl+- + Ctrl+- - I&ncrease toughness - A&ugmenter endurance + A&ugmenter endurance - Alt++ - Alt++ + Alt++ - D&ecrease toughness - D&iminuer endurance + D&iminuer endurance - Alt+- - Alt+- + Alt+- - In&crease power and toughness - Au&gmenter la force et l'endurance + Au&gmenter la force et l'endurance - Ctrl+Alt++ - Ctrl+Alt++ + Ctrl+Alt++ - Dec&rease power and toughness - Di&minuer la force et l'endurance + Di&minuer la force et l'endurance - Ctrl+Alt+- - Ctrl+Alt+- + Ctrl+Alt+- - Set &power and toughness... - Fi&xer la force et l'endurance... + Fi&xer la force et l'endurance... - Ctrl+P - Ctrl+P + Ctrl+P - &Set annotation... - A&jouter note... + A&jouter note... - red - rouge + rouge - yellow - jaune + jaune - green - vert + vert - &Add counter (%1) - &Ajouter compteur (%1) + &Ajouter compteur (%1) - &Remove counter (%1) - &Retirer compteur (%1) + &Retirer compteur (%1) - &Set counters (%1)... - &Fixer marqueurs (%1)... + &Fixer marqueurs (%1)... - &top of library - dessus de la &Bibliothèque + dessus de la &Bibliothèque - &bottom of library - &dessous de la bibliothèque + &dessous de la bibliothèque - &graveyard - &cimetière + &cimetière - Ctrl+Del - Ctrl+Del + Ctrl+Del - &exile - &exiler + &exiler - + &Move to &Aller @@ -553,364 +568,444 @@ Cette information ne sera consultable que par les modérateurs. bibliothèque de %1 - + her hand nominative, female owner sa main - + %1's hand nominative, female owner main de %1 - + his hand nominative, male owner sa main - + %1's hand nominative, male owner main de %1 - + + her library + look at zone, female owner + sa bibliothèque + + + + %1's library + look at zone, female owner + bibliothèque de %1 + + + + his library + look at zone, male owner + sa bibliothèque + + + + %1's library + look at zone, male owner + bibliothèque de %1 + + + + of her library + top cards of zone, female owner + de sa bibliothèque + + + + of %1's library + top cards of zone, female owner + de la bibliothèque de %1 + + + + of his library + top cards of zone, male owner + de sa bibliothèque + + + + of %1's library + top cards of zone, male owner + de la bibliothèque de %1 + + + + her library + reveal zone, female owner + sa bibliothèque + + + + %1's library + reveal zone, female owner + bibliothèque de %1 + + + + his library + reveal zone, male owner + sa bibliothèque + + + + %1's library + reveal zone, male owner + bibliothèque de %1 + + + + her library + shuffle, female owner + sa bibliothèque + + + + %1's library + shuffle, female owner + bibliothèque de %1 + + + + his library + shuffle, male owner + sa bibliothèque + + + + %1's library + shuffle, male owner + bibliothèque de %1 + + + + her sideboard + look at zone, female owner + sa réserve + + + + %1's sideboard + look at zone, female owner + la réserve de %1 + + + + his sideboard + look at zone, male owner + sa réserve + + + + %1's sideboard + look at zone, male owner + la réserve de %1 + + of her hand genitive, female owner - de sa main + de sa main - of %1's hand genitive, female owner - de la main de %1 + de la main de %1 - of his hand genitive, male owner - de sa main + de sa main - of %1's hand genitive, male owner - de la main de %1 + de la main de %1 - her hand accusative, female owner - sa main + sa main - %1's hand accusative, female owner - main de %1 + main de %1 - his hand accusative, male owner - sa main + sa main - %1's hand accusative, male owner - main de %1 + main de %1 - + her library nominative, female owner sa bibliothèque - + %1's library nominative, female owner bibliothèque de %1 - + his library nominative, male owner sa bibliothèque - + %1's library nominative, male owner bibliothèque de %1 - of her library genitive, female owner - de sa bibliothèque + de sa bibliothèque - of %1's library genitive, female owner - de la bibliothèque de %1 + de la bibliothèque de %1 - of his library genitive, male owner - de sa bibliothèque + de sa bibliothèque - of %1's library genitive, male owner - de la bibliothèque de %1 + de la bibliothèque de %1 - her library accusative, female owner - sa bibliothèque + sa bibliothèque - %1's library accusative, female owner - bibliothèque de %1 + bibliothèque de %1 - his library accusative, male owner - sa bibliothèque + sa bibliothèque - %1's library accusative, male owner - bibliothèque de %1 + bibliothèque de %1 - + her graveyard nominative, female owner son cimetière - + %1's graveyard nominative, female owner le cimetière de %1 - + his graveyard nominative, male owner son cimetière - + %1's graveyard nominative, male owner le cimetière de %1 - of her graveyard genitive, female owner - de son cimetière + de son cimetière - of %1's graveyard genitive, female owner - du cimetière de %1 + du cimetière de %1 - of his graveyard genitive, male owner - de son cimetière + de son cimetière - of %1's graveyard genitive, male owner - du cimetière de %1 + du cimetière de %1 - her graveyard accusative, female owner - son cimetière + son cimetière - %1's graveyard accusative, female owner - le cimetière de %1 + le cimetière de %1 - his graveyard accusative, male owner - son cimetière + son cimetière - %1's graveyard accusative, male owner - le cimetière de %1 + le cimetière de %1 - + her exile nominative, female owner sa zone exil - + %1's exile nominative, female owner la zone exil de %1 - + his exile nominative, male owner sa zone exil - + %1's exile nominative, male owner la zone exil de %1 - of her exile genitive, female owner - de sa zone exil + de sa zone exil - of %1's exile genitive, female owner - de la zone exil de %1 + de la zone exil de %1 - of his exile genitive, male owner - de sa zone exil + de sa zone exil - of %1's exile genitive, male owner - de la zone exil de %1 + de la zone exil de %1 - her exile accusative, female owner - sa zone exil + sa zone exil - %1's exile accusative, female owner - la zone exil de %1 + la zone exil de %1 - his exile accusative, male owner - sa zone exil + sa zone exil - %1's exile accusative, male owner - la zone exil de %1 + la zone exil de %1 - + her sideboard nominative, female owner sa réserve - + %1's sideboard nominative, female owner la réserve de %1 - + his sideboard nominative, male owner sa réserve - + %1's sideboard nominative, male owner la réserve de %1 - of her sideboard genitive, female owner - de sa réserve + de sa réserve - of %1's sideboard genitive, female owner - de la réserve de %1 + de la réserve de %1 - of his sideboard genitive, male owner - de sa réserve + de sa réserve - of %1's sideboard genitive, male owner - de la réserve de %1 + de la réserve de %1 - her sideboard accusative, female owner - sa réserve + sa réserve - %1's sideboard accusative, female owner - la réserve de %1 + la réserve de %1 - his sideboard accusative, male owner - sa réserve + sa réserve - %1's sideboard accusative, male owner - la réserve de %1 + la réserve de %1 his graveyard @@ -1054,14 +1149,49 @@ Cette information ne sera consultable que par les modérateurs. - DeckEditorSettingsPage + DBPriceUpdater - - Enable &price tag feature (using data from blacklotusproject.com) - Activer le guide de &prix des cartes (source : blacklotusproject.com) + + + + Error + Erreur - + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + Enable &price tag feature (using data from blacklotusproject.com) + Activer le guide de &prix des cartes (source : blacklotusproject.com) + + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General Géneral @@ -1069,43 +1199,77 @@ Cette information ne sera consultable que par les modérateurs. DeckListModel - + Number Nombre - + Card Carte - + Price Prix + + DeckStatsInterface + + + + Error + Erreur + + + + The reply from the server could not be parsed. + + + DeckViewContainer - + Load &local deck Charger un deck &local - + Load d&eck from server Charger un d&eck depuis le serveur - + Ready to s&tart P&rêt à démarrer - + + S&ideboard unlocked + + + + + S&ideboard locked + + + + Load deck Charger deck + + + Error + Erreur + + + + The selected file could not be loaded. + + Deck_PictureCacher @@ -1155,34 +1319,37 @@ Cette information ne sera consultable que par les modérateurs. DlgConnect - + &Host: &Hôte: - + &Port: &Port: - + Player &name: &Nom du joueur: - + P&assword: Mot de p&asse: - - &OK - &OK + + &Save password + + + + &OK + &OK - &Cancel - &Annuler + &Annuler @@ -1193,87 +1360,90 @@ Cette information ne sera consultable que par les modérateurs. DlgCreateGame - + &Description: &Description: - + &Password: Mot de &Passe: - + P&layers: &Joueurs: - + Game type Type de partie - + Only &buddies can join Seuls les &amis peuvent rejoindre - + Only &registered users can join Seules les personnes en&registrées peuvent rejoindre - + Joining restrictions Conditions pour rejoindre - + &Spectators allowed &Spectateurs autorisés - + Spectators &need a password to join Les spectateurs ont besoin d'un &mot de passe pour rejoindre - + Spectators can &chat Les spectateurs peuvent dis&cuter - + Spectators see &everything Les spectateurs p&euvent tout voir - + Spectators Spectateurs - &OK - &OK + &OK - &Cancel - &Annuler + &Annuler - + Create game Créer partie - + + Game information + + + + Error Erreur - + Server error. Erreur serveur. @@ -1281,115 +1451,281 @@ Cette information ne sera consultable que par les modérateurs. DlgCreateToken - + &Name: &Nom: - + Token Jeton - + C&olor: C&ouleur: - + white blanc - + blue bleu - + black noir - + red rouge - + green vert - + multicolor multicolore - + colorless incolore - + &P/T: &F/E: - + &Annotation: &Note: - + &Destroy token when it leaves the table &Detruire le jeton lorsqu'il quitte le champ de bataille - + + Token data + + + + + Show &all tokens + + + + + Show tokens from this &deck + + + + + Choose token from list + + + &OK - &OK + &OK - &Cancel - &Annuler + &Annuler - + Create token Créer jeton + + DlgEditTokens + + + &Name: + &Nom: + + + + C&olor: + C&ouleur: + + + + white + blanc + + + + blue + bleu + + + + black + noir + + + + red + + + + + green + + + + + multicolor + multicolore + + + + colorless + incolore + + + + &P/T: + &F/E: + + + + &Annotation: + &Note: + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + Erreur + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + O&K + O&K + + + &Cancel + &Annuler + + + + Filter games + + + DlgLoadDeckFromClipboard - + &Refresh &Rafraîchir - &OK - &OK + &OK - &Cancel - &Annuler + &Annuler - + Load deck from clipboard Charger deck depuis le presse-papier - + Error Erreur - + Invalid deck list. Liste de deck invalide. @@ -1397,17 +1733,15 @@ Cette information ne sera consultable que par les modérateurs. DlgLoadRemoteDeck - O&K - O&K + O&K - &Cancel - &Annuler + &Annuler - + Load deck Charger deck @@ -1415,141 +1749,212 @@ Cette information ne sera consultable que par les modérateurs. DlgSettings - - - + + + Error Erreur - Your card database is invalid. Would you like to go back and set the correct path? - Votre base de carte est invalide. Souhaitez-vous redéfinir le chemin d'accès? + Votre base de carte est invalide. Souhaitez-vous redéfinir le chemin d'accès? - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès pour le répertoire de votre deck est invalide. Souhaitez-vous redéfinir le chemin d'accès? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès pour le répertoire de vos images est invalide. Souhaitez-vous redéfinir le chemin d'accès? - + Settings Paramètres - + General Géneral - + Appearance Apparence - + User interface Interface utilisateur - + Deck editor Editeur de deck - + Messages Messages - &Close - &Fermer + &Fermer GameSelector - - - - - - - - + + + + + + + + + Error Erreur - + Please join the appropriate room first. Veuillez d'abord rejoindre le bon salon. - + Wrong password. Mot de passe erroné. - + Spectators are not allowed in this game. Les spectateurs ne sont pas autorisés dans cette partie. - + The game is already full. Cette partie est déjà pleine. - + The game does not exist any more. La partie n'existe plus. - + This game is only open to registered users. Cette partie n'est accessible qu'aux joueurs enregistrés. - + This game is only open to its creator's buddies. Cette partie n'est accessible qu'aux amis. - + You are being ignored by the creator of this game. Vous avez été ignoré par le créateur de la partie. - + Join game Rejoindre partie - + Password: Mot de passe: - + + Please join the respective room first. + + + + Games Parties - - Show &full games - Montrer les pa&rties pleines + + &Filter games + + + + + C&lear filter + + + + Show &full games + Montrer les pa&rties pleines - Show &running games - Montrer les &parties en cours + Montrer les &parties en cours &Show full games @@ -1557,17 +1962,17 @@ Cette information ne sera consultable que par les modérateurs. &Montrer toutes les parties - + C&reate C&réer - + &Join Re&joindre - + J&oin as spectator Rej&oindre en tant que spectateur @@ -1583,72 +1988,72 @@ Cette information ne sera consultable que par les modérateurs. GamesModel - + yes oui - + yes, free for spectators oui, libre pour les spectateurs - + no non - + buddies only invités uniquement - + reg. users only joueurs enregistrés uniquement - + not allowed non autorisé - + Room Salon - + Description Description - + Creator Créateur - + Game type Type de jeu - + Password Mot de passe - + Restrictions Restrictions - + Players Joueurs - + Spectators Spectateurs @@ -1656,148 +2061,212 @@ Cette information ne sera consultable que par les modérateurs. GeneralSettingsPage - - + + English Français - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path Choisir chemin d'accès - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Erreur + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings Paramètres personnels - + Language: Langue: - + Download card pictures on the fly Charger les images de cartes à la volée - + + Download high-quality card pictures + + + + Paths Chemins - + Decks directory: Répertoire des decks: - + + Replays directory: + + + + Pictures directory: Répertoire des images: - + + Card database: + + + + + Token database: + + + Path to card database: - Chemin vers la base de cartes: + Chemin vers la base de cartes: MainWindow - + Number of players Nombre de joueurs - + Please enter the number of players. Entrez s'il vous plait le nombre de joueurs. - - + + Player %1 Joueur %1 - + About Cockatrice à propos de Cockatrice - + <font size="8"><b>Cockatrice</b></font><br>Version %1<br><br><br><b>Authors:</b><br>Max-Wilhelm Bruker<br>Marcus Schütz<br>Marius van Zundert<br><br><b>Translators:</b><br>Spanish: Gocho<br>Portugese: Milton Gonçalves<br> <font size="8"><b>Cockatrice</b></font><br>Version %1<br><br><br><b>Auteurs:</b><br>Max-Wilhelm Bruker<br>Marcus Schütz<br>Marius van Zundert<br><br><b>Tranducteurs:</b><br>Espagnol: Gocho<br>Portugais: Milton Gonçalves<br>Portugais: Milton Gonçalves<br>Français: Yannick HAMMER<br> - + Version %1 Version %1 - + Authors: Auteurs: - + Translators: Traducteurs: - + Spanish: Espagnol: - + Portugese (Portugal): Portugais (Portugal): - + Portugese (Brazil): Portugais (Brésil): - + French: Français: - + Japanese: Japonais: - + Italian: - - - - - - + + + + + + + + + + Error Erreur - + Server timeout Délai de la demande dépassé - Invalid login data. - Information de connexion érronée. + Information de connexion érronée. - + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + Unknown login error: %1 + + + + Socket error: %1 Erreur de socket: %1 @@ -1806,44 +2275,42 @@ Cette information ne sera consultable que par les modérateurs. Version de protocole différente. Version locale: %1 ,version distante: %2. - + There are too many concurrent connections from your address. Il y a trop de connections simultanées depuis votre ordinateur. - Banned by moderator. - Vous avez été banni par le modérateur. + Vous avez été banni par le modérateur. - + Scheduled server shutdown. Fermeture prévue du serveur. - Unknown reason. - Raison inconnue. + Raison inconnue. - + Connection closed Connection fermée - + The server has terminated your connection. Reason: %1 Le serveur a coupé votre connexion. Raison: %1 - + Scheduled server shutdown Fermeture prévue du serveur - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1857,119 +2324,169 @@ Raison de la fermeture : %1 - + Russian: putted "France" instead of "Russe" -> meant that I must put the translation laguage country, isn't it? Russe : - Czech: - Tchèque : + Tchèque : Slovak: Slovaque : - + + Banned by moderator + + + + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + + Load replay + + + + + Swedish: + + + + + Incorrect username or password. Please check your authentication information and try again. + + + + There is already an active session using this user name. Please close that session first and re-login. Il y a déjà une session ouvert avec le même pseudo. Fermez cette session puis re-connectez-vous. - + + This server requires user registration. + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Vous tentez de vous connecter à un serveur obsolète. Chargez la nouvelle version de Cockatrice ou connectez-vous à un serveur approprié. La version la plus récente est %1, l'ancienne version est %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Votre client Cockatrice est obsolète. Veuillez charger la nouvelle version. La version la plus récente est %1, l'ancienne version est %2. - + Connecting to %1... Connexion à %1... - + Disconnected Déconnecté - + + Connected, logging in at %1 + + + + Logged in at %1 Connecté à %1 - + &Connect... à verifier &Connecter... - + &Disconnect &Déconnecter - + Start &local game... Démarrer une partie &locale... - + + &Watch replay... + + + + &Deck editor Éditeur de &deck - + &Full screen &Plein écran - + Ctrl+F Ctrl+F - + &Settings... &Paramètres... - + &Exit &Quitter - + &Cockatrice &Cockatrice - + &About Cockatrice À propos de Cock&atrice - + &Help A&ide - + Are you sure? Êtes-vous sûr? - + There are still open games. Are you sure you want to quit? Il y a encore des parties en cours. Êtes-vous sûr de vouloir quitter? @@ -2013,17 +2530,22 @@ La version la plus récente est %1, l'ancienne version est %2.%1 a quitté la partie. - + The game has been closed. La partie a été fermée. - + + You have been kicked out of the game. + Vous avez été exclu de la partie. + + + %1 is now watching the game. %1 est maintenant spectateur. - + %1 is not watching the game any more. %1 n'est plus spectateur. @@ -2049,7 +2571,7 @@ La version la plus récente est %1, l'ancienne version est %2.%1 a concédé la partie. - + The game has started. La partie commence. @@ -2071,37 +2593,49 @@ La version la plus récente est %1, l'ancienne version est %2.%1 pioche %2 cartes. - + You have joined game #%1. female Vous avez rejoint la partie #%1. - - - You have joined game #%1. - male - Vous avez rejoint la partie #%1. - - - - %1 has joined the game. - female - %1 a rejoint la partie. - - - - %1 has joined the game. - male - %1 a rejoint la partie. - + You have joined game #%1. + male + Vous avez rejoint la partie #%1. + + + + You are watching a replay of game #%1. + female + + + + + You are watching a replay of game #%1. + male + + + + + %1 has joined the game. + female + %1 a rejoint la partie. + + + + %1 has joined the game. + male + %1 a rejoint la partie. + + + %1 has left the game. female %1 a quitté la partie. - + %1 has left the game. male %1 a quitté la partie. @@ -2127,91 +2661,91 @@ La version la plus récente est %1, l'ancienne version est %2.%1 a chargé le deck #%2. - + %1 is ready to start the game. female %1 est prête à démarrer la partie. - + %1 is ready to start the game. male %1 est prêt à démarrer la partie. - + %1 is not ready to start the game any more. female %1 n'est plus prête à démarrer la partie. - + %1 is not ready to start the game any more. male %1 n'est plus prêt à démarrer la partie. - + %1 has conceded the game. female %1 a concédé la partie. - + %1 has conceded the game. male %1 a concédé la partie. - + %1 has restored connection to the game. female %1 est revenue dans la partie. - + %1 has restored connection to the game. male %1 est revenu dans la partie. - + %1 has lost connection to the game. female %1 s'est déconnectée de la partie. - + %1 has lost connection to the game. male %1 s'est déconnecté de la partie. - + %1 shuffles %2. female %1 mélange %2. - + %1 shuffles %2. male %1 mélange %2. - + %1 rolls a %2 with a %3-sided die. female %1 a fait %2 avec un dé %3 faces. - + %1 rolls a %2 with a %3-sided die. male %1 a fait %2 avec un dé %3 faces. - + %1 draws %n card(s). female @@ -2220,7 +2754,7 @@ La version la plus récente est %1, l'ancienne version est %2. - + %1 draws %n card(s). male @@ -2229,158 +2763,158 @@ La version la plus récente est %1, l'ancienne version est %2. - + from table depuis le champ de bataille - + from graveyard depuis son cimetière - + from exile depuis la zone exil - + from hand depuis sa main - + the bottom card of his library la carte du dessous de sa bibliothèque - + the bottom card of her library la carte du dessous de sa bibliothèque - + from the bottom of his library du dessous de sa bibliothèque - + from the bottom of her library du dessous de sa bibliothèque - + the top card of his library le carte du dessus de sa bibliothèque - + the top card of her library le carte du dessus de sa bibliothèque - + from the top of his library du dessus de sa bibliothèque - + from the top of her library du dessus de sa bibliothèque - + from library depuis sa bibliothèque - + from sideboard depuis sa réserve - + from the stack depuis la pile - + %1 puts %2 into play tapped%3. %1 met %2 en jeu engagé%3. - + %1 puts %2 into play%3. what is %3? plz exemple (resp. by Ranma : XX met island en jeu -depuis sa main-.) %1 met %2 en jeu %3. - + %1 puts %2%3 into graveyard. %1 met %2%3 dans son cimetière. - + %1 exiles %2%3. %1 exile %2%3. - + %1 moves %2%3 to hand. %1 met %2%3 dans sa main. - + %1 puts %2%3 into his library. %1 met %2%3 dans sa bibliothèque. - + %1 puts %2%3 into her library. %1 met %2%3 dans sa bibliothèque. - + %1 puts %2%3 on bottom of his library. %1 met %2%3 en-dessous de sa bibliothèque. - + %1 puts %2%3 on bottom of her library. %1 met %2%3 en-dessous de sa bibliothèque. - + %1 puts %2%3 on top of his library. %1 met %2%3 au-dessus de sa bibliothèque. - + %1 puts %2%3 on top of her library. %1 met %2%3 au-dessus de sa bibliothèque. - + %1 puts %2%3 into his library at position %4. %1 met %2%3 dans sa bibliothèque à la position n°%4. - + %1 puts %2%3 into her library at position %4. %1 met %2%3 dans sa bibliothèque à la position n°%4. - + %1 moves %2%3 to sideboard. %1 met %2%3 à sa réserve. - + %1 plays %2%3. %1 joue %2%3. - + %1 takes a mulligan to %n. female @@ -2389,7 +2923,7 @@ La version la plus récente est %1, l'ancienne version est %2. - + %1 takes a mulligan to %n. male @@ -2398,37 +2932,37 @@ La version la plus récente est %1, l'ancienne version est %2. - + %1 flips %2 face-down. female %1 retourne %2 face cachée. - + %1 flips %2 face-down. male %1 retourne %2 face cachée. - + %1 flips %2 face-up. female %1 retourne %2 face visible. - + %1 flips %2 face-up. male %1 retourne %2 face visible. - + %1 destroys %2. female %1 détruit %2. - + %1 destroys %2. male %1 détruit %2. @@ -2444,283 +2978,340 @@ La version la plus récente est %1, l'ancienne version est %2.%1 attache %2 sur %4 de %3. - %1 has loaded a deck (%2). female - %1 a chargé le deck %2. + %1 a chargé le deck %2. - %1 has loaded a deck (%2). male - %1 a chargé le deck %2. + %1 a chargé le deck %2. - + + %1 has loaded a deck (%2). + %1 a chargé le deck %2. + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + the bottom card of %1's library + + + + + from the bottom of %1's library + + + + + the top card of %1's library + + + + + from the top of %1's library + + + + + from %1's library + + + + %1 attaches %2 to %3's %4. p1 female, p2 female %1 attache %2 sur %4 de %3. - + %1 attaches %2 to %3's %4. p1 female, p2 male %1 attache %2 sur %4 de %3. - + %1 attaches %2 to %3's %4. p1 male, p2 female %1 attache %2 sur %4 de %3. - + %1 attaches %2 to %3's %4. p1 male, p2 male %1 attache %2 sur %4 de %3. - + %1 unattaches %2. female %1 détache %2. - + %1 unattaches %2. male %1 détache %2. - + %1 creates token: %2%3. female %1 crée un jeton %2%3. - + %1 creates token: %2%3. male %1 crée un jeton %2%3. - + %1 points from her %2 to herself. female %1 se cible avec %2. - + %1 points from his %2 to himself. male %1 se cible avec %2. - + %1 points from her %2 to %3. p1 female, p2 female %1 cible %3 avec %2. - + %1 points from her %2 to %3. p1 female, p2 male %1 cible %3 avec %2. - + %1 points from his %2 to %3. p1 male, p2 female %1 cible %3 avec %2. - + %1 points from his %2 to %3. p1 male, p2 male %1 cible %3 avec %2. - + %1 points from %2's %3 to herself. card owner female, target female %1 se cible avec %3 de %2. - + %1 points from %2's %3 to herself. card owner male, target female %1 se cible avec %3 de %2. - + %1 points from %2's %3 to himself. card owner female, target male %1 se cible avec %3 de %2. - + %1 points from %2's %3 to himself. card owner male, target male %1 se cible avec %3 de %2. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male %1 cible %4 avec %3 de %2. - + %1 points from her %2 to her %3. female %1 cible %3 avec %2. - + %1 points from his %2 to his %3. male %1 cible %3 avec %2. - + %1 points from her %2 to %3's %4. p1 female, p2 female %1 cible %3 de %4 avec %2. - + %1 points from her %2 to %3's %4. p1 female, p2 male %1 cible %3 de %4 avec %2. - + %1 points from his %2 to %3's %4. p1 male, p2 female %1 cible %3 de %4 avec %2. - + %1 points from his %2 to %3's %4. p1 male, p2 male %1 cible %3 de %4 avec %2. - + %1 points from %2's %3 to her own %4. card owner female, target female %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to her own %4. card owner male, target female %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to his own %4. card owner female, target male %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to his own %4. card owner male, target male %1 cible %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female %1 cible %5 de %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male %1 cible %5 de %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female %1 cible %5 de %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male %1 cible %5 de %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female %1 cible %5 de %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male %1 cible %5 de %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female %1 cible %5 de %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male %1 cible %5 de %4 avec %3 de %2. - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -2729,7 +3320,7 @@ La version la plus récente est %1, l'ancienne version est %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -2738,7 +3329,7 @@ La version la plus récente est %1, l'ancienne version est %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -2747,7 +3338,7 @@ La version la plus récente est %1, l'ancienne version est %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -2756,272 +3347,322 @@ La version la plus récente est %1, l'ancienne version est %2. - + %1 taps her permanents. female %1 engage ses permanents. - + %1 untaps her permanents. female %1 dégage ses permanents. - + %1 taps his permanents. male %1 engage ses permanents. - + %1 untaps his permanents. male %1 dégage ses permanents. - + %1 taps %2. female %1 engage %2. - + %1 untaps %2. female %1 dégage %2. - - - %1 taps %2. - male - %1 engage %2. - - - - %1 untaps %2. - male - %1 dégage %2. - - - - %1 sets counter %2 to %3 (%4%5). - female - %1 met les marqueurs %2 à %3 (%4%5). - - - - %1 sets counter %2 to %3 (%4%5). - male - %1 met les marqueurs %2 à %3 (%4%5). - - - - %1 sets %2 to not untap normally. - female - %2 de %1 ne se dégagera pas lors de l'étape de dégagement. - - - - %1 sets %2 to not untap normally. - male - %2 de %1 ne se dégagera pas lors de l'étape de dégagement. - - - - %1 sets %2 to untap normally. - female - %2 de %1 se dégagera lors de l'étape de dégagement. - - - - %1 sets %2 to untap normally. - male - %2 de %1 se dégagera lors de l'étape de dégagement. - - - - %1 sets PT of %2 to %3. - female - %1 change la F/E de %2 à %3. - - - - %1 sets PT of %2 to %3. - male - %1 change la F/E de %2 à %3. - - - - %1 sets annotation of %2 to %3. - female - %1 met l'annotation %3 à %2. - - - - %1 sets annotation of %2 to %3. - male - %1 met l'annotation %3 à %2. - - - - %1 is looking at the top %2 cards %3. - female - %1 regarde les %2 cartes du dessus %3. - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - %1 regarde les %2 cartes du dessus %3. + %1 engage %2. - - %1 is looking at %2. - female - %1 regarde %2. - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 regarde %2. + %1 dégage %2. - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female - %1 arrête de regarder %2. + %1 met les marqueurs %2 à %3 (%4%5). + %1 sets counter %2 to %3 (%4%5). + male + %1 met les marqueurs %2 à %3 (%4%5). + + + + %1 sets %2 to not untap normally. + female + %2 de %1 ne se dégagera pas lors de l'étape de dégagement. + + + + %1 sets %2 to not untap normally. + male + %2 de %1 ne se dégagera pas lors de l'étape de dégagement. + + + + %1 sets %2 to untap normally. + female + %2 de %1 se dégagera lors de l'étape de dégagement. + + + + %1 sets %2 to untap normally. + male + %2 de %1 se dégagera lors de l'étape de dégagement. + + + + %1 sets PT of %2 to %3. + female + %1 change la F/E de %2 à %3. + + + + %1 sets PT of %2 to %3. + male + %1 change la F/E de %2 à %3. + + + + %1 sets annotation of %2 to %3. + female + %1 met l'annotation %3 à %2. + + + + %1 sets annotation of %2 to %3. + male + %1 met l'annotation %3 à %2. + + + %1 is looking at the top %2 cards %3. + female + %1 regarde les %2 cartes du dessus %3. + + + %1 is looking at the top %2 cards %3. + male + %1 regarde les %2 cartes du dessus %3. + + + + %1 is looking at %2. + female + %1 regarde %2. + + + + %1 is looking at %2. + male + %1 regarde %2. + + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + + + %1 stops looking at %2. + female + %1 arrête de regarder %2. + + + %1 stops looking at %2. male %1 arrête de regarder %2. - - - %1 reveals %2 to %3. - p1 female, p2 female - %1 révèle %2 à %3. - - - - %1 reveals %2 to %3. - p1 female, p2 male - %1 révèle %2 à %3. - - - - %1 reveals %2 to %3. - p1 male, p2 female - %1 révèle %2 à %3. - - - - %1 reveals %2 to %3. - p1 male, p2 male - %1 révèle %2 à %3. - - - - %1 reveals %2. - female - %1 révèle %2. - - - - %1 reveals %2. - male - %1 révèle %2. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - %1 révèle au hasard %2%3 à %4. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - %1 révèle au hasard %2%3 à %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - %1 révèle au hasard %2%3 à %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - %1 révèle au hasard %2%3 à %4. - - - - %1 randomly reveals %2%3. - female - %1 révèle au hasard %2%3. - - - - %1 randomly reveals %2%3. - male - %1 révèle au hasard %2%3. - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - %1 révèle %2%3 à %4. - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - %1 révèle %2%3 à %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - %1 révèle %2%3 à %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - %1 révèle %2%3 à %4. - - - - %1 reveals %2%3. - female - %1 révèle %2%3. - - - - %1 reveals %2%3. - male - %1 révèle %2%3. - - - - It is now %1's turn. - female - C'est maintenant le tour de %1. - + %1 reveals %2 to %3. + p1 female, p2 female + %1 révèle %2 à %3. + + + + %1 reveals %2 to %3. + p1 female, p2 male + %1 révèle %2 à %3. + + + + %1 reveals %2 to %3. + p1 male, p2 female + %1 révèle %2 à %3. + + + + %1 reveals %2 to %3. + p1 male, p2 male + %1 révèle %2 à %3. + + + + %1 reveals %2. + female + %1 révèle %2. + + + + %1 reveals %2. + male + %1 révèle %2. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + %1 révèle au hasard %2%3 à %4. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + %1 révèle au hasard %2%3 à %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + %1 révèle au hasard %2%3 à %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + %1 révèle au hasard %2%3 à %4. + + + + %1 randomly reveals %2%3. + female + %1 révèle au hasard %2%3. + + + + %1 randomly reveals %2%3. + male + %1 révèle au hasard %2%3. + + + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + %1 révèle %2%3 à %4. + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + %1 révèle %2%3 à %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + %1 révèle %2%3 à %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + %1 révèle %2%3 à %4. + + + + %1 reveals %2%3. + female + %1 révèle %2%3. + + + + %1 reveals %2%3. + male + %1 révèle %2%3. + + + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + + It is now %1's turn. + female + C'est maintenant le tour de %1. + + + It is now %1's turn. male C'est maintenant le tour de %1. - - + + a card une carte @@ -3033,37 +3674,37 @@ La version la plus récente est %1, l'ancienne version est %2. - + %1 undoes his last draw. %1 annule sa dernière pioche. - + %1 undoes her last draw. %1 annule sa dernière pioche. - + %1 undoes his last draw (%2). %1 annule sa dernière pioche (%2). - + %1 undoes her last draw (%2). %1 annule sa dernière pioche (%2). - + %1 gives %2 control over %3. %1 donne le contrôle de %2 à %3. - + %1 draws his initial hand. %1 pioche sa main de départ. - + %1 draws her initial hand. %1 pioche sa main de départ. @@ -3133,7 +3774,7 @@ La version la plus récente est %1, l'ancienne version est %2. - + red rouge @@ -3141,7 +3782,7 @@ La version la plus récente est %1, l'ancienne version est %2. - + yellow jaune @@ -3149,7 +3790,7 @@ La version la plus récente est %1, l'ancienne version est %2. - + green vert @@ -3240,62 +3881,62 @@ La version la plus récente est %1, l'ancienne version est %2.C'est maintenant le tour de %1. - + untap step étape de dégagement - + upkeep step étape d'entretien - + draw step étape de pioche - + first main phase première phase principale - + beginning of combat step étape de début du combat - + declare attackers step étape de déclaration des attaquants - + declare blockers step étape de déclaration des bloqueurs - + combat damage step étape de répartition et de résolution des blessures - + end of combat step étape de fin de combat - + second main phase seconde phase principale - + ending phase phase de fin de tour - + It is now the %1. need exemple C'est maintenant %1. @@ -3304,22 +3945,22 @@ La version la plus récente est %1, l'ancienne version est %2. MessagesSettingsPage - + Add message Ajouter message - + Message: Message: - + &Add &Ajouter - + &Remove &Enlever @@ -3327,57 +3968,57 @@ La version la plus récente est %1, l'ancienne version est %2. PhasesToolbar - + Untap step Étape de dégagement - + Upkeep step Étape d'entretien - + Draw step Étape de pioche - + First main phase Première phase principale - + Beginning of combat step Étape de début de combat - + Declare attackers step Étape de déclaration des attaquants - + Declare blockers step Étape de déclaration des bloqueurs - + Combat damage step Étape de répartition et d'attribution des blessures - + End of combat step Étape de fin de combat - + Second main phase Seconde phase principale - + End of turn step Étape de fin de tour @@ -3385,323 +4026,533 @@ La version la plus récente est %1, l'ancienne version est %2. Player - + &View graveyard &Voir le cimetière - + &View exile &Voir la zone exil - + Player "%1" Joueur "%1" - + &Graveyard &Cimetière - + &Exile &Exil - - - + + + Move to &top of library Mettre au-dess&us de sa bibliothèque - - - + + + Move to &bottom of library Mettre en-dess&ous de sa bibliothèque - - + + Move to &graveyard Mettre dans son cime&tière - - + + Move to &exile Déplacer dans la zone &exil - - + + Move to &hand Mettre dans sa &main - + &View library &Voir la bibliothèque - + View &top cards of library... Voir les cartes du &dessus de la bibliothèque... - + Reveal &library to Révéler la &bibliothèque à - + Reveal t&op card to Révéler la carte du &dessus à - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &View sideboard &Voir la réserve - + &Draw card &Piocher une carte - + D&raw cards... P&iocher plusieurs cartes... - + &Undo last draw Annu&ler dernière pioche - + Take &mulligan &Mulliganer - + &Shuffle Mél&anger - + Move top cards to &graveyard... Déplacer les cartes du dessus vers le &cimetière... - + Move top cards to &exile... Déplacer les cartes du dessus vers la zone &exil... - + Put top card on &bottom Mettre la carte du dessus en &dessous - + &Hand &Main - + &Reveal to &Révéler à - + Reveal r&andom card to Révéler une carte au &hasard à - + &Sideboard Ré&serve - + &Library &Bibliothèque - + &Counters Mar&queurs - + &Untap all permanents Dé&gager tous les permanents - + R&oll die... Lancer un &dé... - + &Create token... &Créer un jeton... - + C&reate another token C&réer un autre jeton - + + Cr&eate predefined token + + + + S&ay D&ire - + C&ard C&arte - + &All players &Tous les joueurs - + + &Play + &Jouer + + + + &Hide + &Cacher + + + + &Tap + &Engager + + + + &Untap + &Dégager + + + + Toggle &normal untapping + Activer/ Désactiver le dégagement &normal + + + + &Flip + &Retourner la carte + + + + &Peek at card face + + + + + &Clone + &Copier une carte + + + + Ctrl+J + + + + + Attac&h to card... + + + + + Ctrl+A + Ctrl+A + + + + Unattac&h + Détac&her + + + + &Draw arrow... + &Tracer une flèche... + + + + &Increase power + &Augmenter force + + + + Ctrl++ + Ctrl++ + + + + &Decrease power + &Diminuer force + + + + Ctrl+- + Ctrl+- + + + + I&ncrease toughness + A&ugmenter endurance + + + + Alt++ + Alt++ + + + + D&ecrease toughness + D&iminuer endurance + + + + Alt+- + Alt+- + + + + In&crease power and toughness + Au&gmenter la force et l'endurance + + + + Ctrl+Alt++ + Ctrl+Alt++ + + + + Dec&rease power and toughness + Di&minuer la force et l'endurance + + + + Ctrl+Alt+- + Ctrl+Alt+- + + + + Set &power and toughness... + Fi&xer la force et l'endurance... + + + + Ctrl+P + Ctrl+P + + + + &Set annotation... + A&jouter note... + + + + red + + + + + yellow + + + + + green + + + + + &Add counter (%1) + &Ajouter compteur (%1) + + + + &Remove counter (%1) + &Retirer compteur (%1) + + + + &Set counters (%1)... + &Fixer marqueurs (%1)... + + + + &top of library + dessus de la &Bibliothèque + + + + &bottom of library + &dessous de la bibliothèque + + + + &graveyard + &cimetière + + + + Ctrl+Del + Ctrl+Del + + + + &exile + &exiler + + + Ctrl+F3 Ctrl+F3 - + F3 F3 - + Ctrl+W Ctrl+W - + F4 F4 - + Ctrl+D Ctrl+D - + Ctrl+E Ctrl+E - + Ctrl+Shift+D Ctrl+Shift+D - + Ctrl+M Ctrl+M - + Ctrl+S Ctrl+S - + Ctrl+U Ctrl+U - + Ctrl+I Ctrl+I - + Ctrl+T Ctrl+T - + Ctrl+G Ctrl+G - + View top cards of library Voir les cartes du dessus de la bibliothèque - + Number of cards: Nombre de cartes: - + Draw cards Piocher plusieurs cartes - - - - + + + + Number: Nombre: - + Move top cards to grave Mettre les cartes du dessus dans le cimetière - + Move top cards to exile Mettre les cartes du dessus dans la zone exil - + Roll die Lancer un dé - + Number of sides: Nombre de faces: - + Set power/toughness Fixer force/endurance - + Please enter the new PT: maybe better with / Entrer la nouvelle F/E: - + Set annotation Mettre une note - + Please enter the new annotation: Entrez la nouvelle note: - + Set counters Mettre des marqueurs @@ -3737,117 +4588,149 @@ La version la plus récente est %1, l'ancienne version est %2.deck #%1 - User &details - &Détails utilisateur + &Détails utilisateur - Direct &chat - &Chat direct + &Chat direct - Add to &buddy list - Ajouter à la liste d'&amis + Ajouter à la liste d'&amis - Remove from &buddy list - Retirer de la liste d'&amis + Retirer de la liste d'&amis - Add to &ignore list - Ajouter à la liste &noire + Ajouter à la liste &noire - Remove from &ignore list - Retirer de la liste &noire + Retirer de la liste &noire - Kick from &game - Exclure de la &partie + Exclure de la &partie QObject - Maindeck - Deck + Deck - Sideboard - Réserve + Réserve - Cockatrice decks (*.cod) - Decks format Cockatrice (*.cod) + Decks format Cockatrice (*.cod) - Plain text decks (*.dec *.mwDeck) - Decks au format texte (*.dec *.mwDeck) + Decks au format texte (*.dec *.mwDeck) - + + Common deck formats (*.cod *.dec *.mwDeck) + + + + All files (*.*) - Tous les fichiers (*.*) + Tous les fichiers (*.*) + + + + Cockatrice replays (*.cor) + RemoteDeckList_TreeModel - + Name Nom - + ID ID - + Upload time nedd exemple Temps de chargement + + RemoteReplayList_TreeModel + + + ID + ID + + + + Name + Nom + + + + Players + Joueurs + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + RoomSelector - + Rooms Salons - + Joi&n &Rejoindre - + Room Salon - + Description Description - + Players Joueurs - + Games Parties @@ -3875,27 +4758,25 @@ La version la plus récente est %1, l'ancienne version est %2. ShutdownDialog - + &Reason for shutdown: &Raison de la fermeture : - + &Time until shutdown (minutes): &Temps avant la fermeture (minutes): - &OK - &OK + &OK - &Cancel - &Annuler + &Annuler - + Shut down server Fermer le serveur @@ -3903,44 +4784,43 @@ La version la plus récente est %1, l'ancienne version est %2. TabAdmin - + Update server &message Mettre à jour le &message du serveur - + &Shut down server &Fermer le serveur - + Server administration functions Fonctions d'administration du serveur - + &Unlock functions &Débloquer fonctions - + &Lock functions &Bloquer fonctions - + Unlock administration functions Débloquer fonctions d'administration - + Do you really want to unlock the administration functions? Êtes-vous sûr de vouloir débloquer les fonctions d'administration? - Administration - Administration + Administration @@ -3962,207 +4842,468 @@ La version la plus récente est %1, l'ancienne version est %2.%1 a quitté le canal. + + TabDeckEditor + + &Search... + &Chercher... + + + + Show card text only + + + + + &Clear search + &Effacer la recherche + + + + &Search for: + &Rechercher: + + + + Deck &name: + &Nom du deck: + + + + &Comments: + &Commentaires: + + + + Hash: + + + + + &Update prices + Mettre à &jour les prix + + + + Ctrl+U + Ctrl+U + + + + &New deck + &Nouveau deck + + + + &Load deck... + Char&ger deck... + + + + &Save deck + &Sauvegarder le deck + + + + Save deck &as... + S&auvegarder le deck sous... + + + + Load deck from cl&ipboard... + Charger deck depuis le presse-pap&ier... + + + + Save deck to clip&board + Sauve&garder le deck dans le presse-papier + + + + &Print deck... + Im&primer le deck... + + + + &Analyze deck on deckstats.net + + + + + &Close + &Fermer + + + + Ctrl+Q + Ctrl+Q + + + + Add card to &maindeck + Ajouter carte au &deck + + + Return + Retour + + + Enter + Entrer + + + + Add card to &sideboard + Ajouter carte à la ré&serve + + + Ctrl+Return + Ctrl+Return + + + Ctrl+Enter + Ctrl+Enter + + + + &Remove row + &Retirer la ligne + + + + Del + Supprimer + + + + &Increment number + &Augmenter quantité + + + + + + + + + + + &Decrement number + &Diminuer quantité + + + + - + - + + + + &Deck editor + Éditeur de &deck + + + + C&ard database + + + + + &Edit sets... + &Editer les editions... + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + Êtes-vous sûr? + + + + The decklist has been modified. +Do you want to save the changes? + Le deck a été modifié. +Voulez vous enregistrer les modifications? + + + + Load deck + Charger deck + + + + + + Error + Erreur + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + Le deck n'a pas pu être enregistré. +Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. + + + + Save deck + Sauvegarder le deck + + TabDeckStorage - + Local file system Système de fichier local - + Server deck storage Serveur de stockage de deck - - + + Open in deck editor Ouvrir l'éditeur de deck - + Upload deck Envoyer deck - + Download deck Télécharger deck - - + + New folder Nouveau dossier - + + Delete Supprimer - + Enter deck name Entrez le nom du deck - + This decklist does not have a name. Please enter a name: Ce deck n'a pas de nom. Entrez un nom s'il vous plaît: - + Unnamed deck Deck sans nom - + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: Nom du nouveau dossier: - + + Delete remote folder + + + + + Delete remote deck + + + Deck storage - Stockage de deck + Stockage de deck TabGame - + F5 F5 - + F6 F6 - + F7 F7 - + F8 F8 - + F9 F9 - + F10 F10 - + &Phases - + &Game &Partie - + Next &phase &Prochaine phase - + Ctrl+Space Ctrl+Space - + + Tab + + + + Next &turn Prochain &Tour - + Ctrl+Return Ctrl+Return - + Ctrl+Enter Ctrl+Enter - + &Remove all local arrows &Retirer toutes les flèches locales - + Ctrl+R Ctrl+R - + + Game &information + + + + &Concede &Concéder - + F2 F2 - + &Leave game &Quitter la partie - + + Ctrl+Q Ctrl+Q - + + C&lose replay + + + + &Say: &Dire: - + Concede Concéder - + Are you sure you want to concede this game? Êtes-vous sûr de vouloir concéder la partie? - + Leave game Quitter la partie - + Are you sure you want to leave this game? Êtes-vous sûr de vouloir quitter la partie? - + + Replay %1: %2 + + + Kicked - Exclu + Exclu - You have been kicked out of the game. - Vous avez été exclu de la partie. + Vous avez été exclu de la partie. - + Game %1: %2 Partie %1:%2 @@ -4170,61 +5311,121 @@ Entrez un nom s'il vous plaît: TabMessage - + Personal &talk need exemple &Discussion privée - + &Leave &Quitter - + This user is ignoring you. Cet utilisateur vous a ignoré. - + %1 has left the server. %1 a quitté le serveur. - + %1 has joined the server. %1 a rejoint le serveur. - + Talking to %1 Vous parlez à %1 + + TabReplays + + + Local file system + Système de fichier local + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + Supprimer + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: &Dire: - + Chat Chat - + &Room &Salon - + &Leave room &Quitter le salon - + + &Ignore unregistered users in chat + + + + You are flooding the chat. Please wait a couple of seconds. Vous floodez le chat. Veuillez patienter quelques secondes. @@ -4240,53 +5441,114 @@ Entrez un nom s'il vous plaît: TabServer - Server - Serveur + Serveur TabUserLists - User lists - Listes de l'utilisateur + Listes de l'utilisateur + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + &Détails utilisateur + + + + Direct &chat + &Chat direct + + + + Show this user's &games + Montrer les &parties de ce joueur + + + + Add to &buddy list + Ajouter à la liste d'&amis + + + + Remove from &buddy list + Retirer de la liste d'&amis + + + + Add to &ignore list + Ajouter à la liste &noire + + + + Remove from &ignore list + Retirer de la liste &noire + + + + Kick from &game + Exclure de la &partie + + + + Ban from &server + Bannir du &serveur + + + + %1's games + Parties de %1 UserInfoBox - + User information Informations utilisateur - + Real name: Vrai nom: - + Gender: Sexe: - + Location: Localisation: - + User level: Rang utilisateur: - + Administrator Administrateur - + Moderator Modérateur @@ -4296,12 +5558,12 @@ Entrez un nom s'il vous plaît: Juge - + Registered user Utilisateur enregistré - + Unregistered user Utilisateur non enregistré @@ -4309,37 +5571,47 @@ Entrez un nom s'il vous plaît: UserInterfaceSettingsPage - + General interface settings Réglages généraux de l'interface - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) &Double cliquer sur la carte pour la jouer (au lieu d'un simple clic) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings Réglage des animations - + &Tap/untap animation &Animation d'engagement et de dégagement - + Enable &sounds Activer &sons - + Path to sounds directory: Chemin vers le fichier sons: - + Choose path Choisir fichier @@ -4347,70 +5619,61 @@ Entrez un nom s'il vous plaît: UserList - + Users online: %1 Utilisateurs en ligne:%1 - + Users in this room: %1 Utilisateurs dans ce salon: %1 - + Buddies online: %1 / %2 Amis connectés; %1 / %2 - + Ignored users online: %1 / %2 Personnes sur liste noire connectés: %1 / %2 - %1's games - Parties de %1 + Parties de %1 - User &details - &Détails utilisateur + &Détails utilisateur - Direct &chat - &Chat direct + &Chat direct - Show this user's &games Missing the female version : "Montrer les parties de cette joueuse" - Montrer les &parties de ce joueur + Montrer les &parties de ce joueur - Add to &buddy list - Ajouter à la liste d'&amis + Ajouter à la liste d'&amis - Remove from &buddy list - Retirer de la liste d'&amis + Retirer de la liste d'&amis - Add to &ignore list - Ajouter à la liste &noire + Ajouter à la liste &noire - Remove from &ignore list - Retirer de la liste &noire + Retirer de la liste &noire - Ban from &server - Bannir du &serveur + Bannir du &serveur Duration @@ -4426,207 +5689,162 @@ Entrez 0 pour une durée illimitée du ban. WndDeckEditor - &Search... - &Chercher... + &Chercher... - &Clear search - &Effacer la recherche + &Effacer la recherche - &Search for: - &Rechercher: + &Rechercher: - Deck &name: - &Nom du deck: + &Nom du deck: - &Comments: - &Commentaires: + &Commentaires: - - Hash: - - - - &Update prices - Mettre à &jour les prix + Mettre à &jour les prix - Ctrl+U - Ctrl+U + Ctrl+U - Deck editor [*] - Editeur de deck [*] + Editeur de deck [*] - &New deck - &Nouveau deck + &Nouveau deck - &Load deck... - Char&ger deck... + Char&ger deck... - &Save deck - &Sauvegarder le deck + &Sauvegarder le deck - Save deck &as... - S&auvegarder le deck sous... + S&auvegarder le deck sous... - Load deck from cl&ipboard... - Charger deck depuis le presse-pap&ier... + Charger deck depuis le presse-pap&ier... - Save deck to clip&board - Sauve&garder le deck dans le presse-papier + Sauve&garder le deck dans le presse-papier - &Print deck... - Im&primer le deck... + Im&primer le deck... - &Close - &Fermer + &Fermer - Ctrl+Q - Ctrl+Q + Ctrl+Q - &Edit sets... - &Editer les editions... + &Editer les editions... - &Deck - &Deck + &Deck - &Card database - Base de &cartes + Base de &cartes - Add card to &maindeck - Ajouter carte au &deck + Ajouter carte au &deck - Return - Retour + Retour - Enter - Entrer + Entrer - Add card to &sideboard - Ajouter carte à la ré&serve + Ajouter carte à la ré&serve - Ctrl+Return - Ctrl+Return + Ctrl+Return - Ctrl+Enter - Ctrl+Enter + Ctrl+Enter - &Remove row - &Retirer la ligne + &Retirer la ligne - Del - Supprimer + Supprimer - &Increment number to check - &Augmenter quantité + &Augmenter quantité - + - + + + - &Decrement number to check - &Diminuer quantité + &Diminuer quantité - - - - + - - Are you sure? - Êtes-vous sûr? + Êtes-vous sûr? - The decklist has been modified. Do you want to save the changes? - Le deck a été modifié. + Le deck a été modifié. Voulez vous enregistrer les modifications? - Load deck - Charger deck + Charger deck - - Error - Erreur + Erreur - - The deck could not be saved. Please check that the directory is writable and try again. - Le deck n'a pas pu être enregistré. + Le deck n'a pas pu être enregistré. Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. - Save deck - Sauvegarder le deck + Sauvegarder le deck @@ -4640,17 +5858,17 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. ZoneViewWidget - + sort by name tri par nom - + sort by type tri par type - + shuffle when closing mélanger en quittant diff --git a/cockatrice/translations/cockatrice_gd.ts b/cockatrice/translations/cockatrice_gd.ts index 40b936786..7003d25c7 100644 --- a/cockatrice/translations/cockatrice_gd.ts +++ b/cockatrice/translations/cockatrice_gd.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... - + Ctrl+L - + F11 - + F12 - + Set counter - + New value for counter '%1': @@ -37,91 +37,91 @@ AppearanceSettingsPage - + Zone background pictures - - Path to hand background: + + Hand background: - - Path to stack background: + + Stack background: - - Path to table background: + + Table background: - - Path to player info background: + + Player info background: - - Path to picture of card back: + + Card back: - + Card rendering - + Display card names on cards having a picture - + Hand layout - + Display hand horizontally (wastes space) - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Zone view layout - + Sort by name - + Sort by type - - - - - + + + + + Choose path @@ -129,79 +129,84 @@ BanDialog - + ban &user name - + ban &IP address - + Ban type Bac seòrsa - + &permanent ban Not sure if buan should be lenited or not. Sort of feels better lenited. &Bac bhuan - + &temporary ban Bac &sealach - + &Days: &Làithean: - + &Hours: &Uairean: - + &Minutes: &Mionaidean: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK &Ceart ma-thà - + &Cancel &Sguir dheth - + Ban user from server - + Error Mearachd - + You have to select a name-based or IP-based ban, or both. @@ -209,268 +214,139 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Ainm - + Sets - + Mana cost - + Card type Cairt seòrsa - + P/T + + CardInfoText + + + Name: + Ainm: + + + + Mana cost: + + + + + Card type: + Cairt seòrsa: + + + + P / T: + + + + + Loyalty: + + + CardInfoWidget - + Show card only - + Show text only - + Show full info - + Name: Ainm: - + Mana cost: - + Card type: Cairt seòrsa: - + P / T: + + + Loyalty: + + CardItem - - &Play - - - - &Hide - Falaic&h + Falaic&h - - &Tap - - - - - &Untap - - - - - Toggle &normal untapping - - - - &Flip - Dèan &flip + Dèan &flip - &Clone - &Copaig + &Copaig - - Ctrl+H - - - - - &Attach to card... - - - - - Ctrl+A - - - - - Unattac&h - - - - - &Draw arrow... - - - - + &Power / toughness - - &Increase power - - - - - Ctrl++ - - - - - &Decrease power - - - - - Ctrl+- - - - - - I&ncrease toughness - - - - - Alt++ - - - - - D&ecrease toughness - - - - - Alt+- - - - - - In&crease power and toughness - - - - - Ctrl+Alt++ - - - - - Dec&rease power and toughness - - - - - Ctrl+Alt+- - - - - - Set &power and toughness... - - - - - Ctrl+P - - - - &Set annotation... - &Sgrìobh nòtachadh... + &Sgrìobh nòtachadh... - red - dearg + dearg - yellow - buidhe + buidhe - green - uaine + uaine - - &Add counter (%1) - - - - - &Remove counter (%1) - - - - - &Set counters (%1)... - - - - - &top of library - - - - - &bottom of library - - - - - &graveyard - - - - - Ctrl+Del - - - - - &exile - - - - + &Move to @@ -478,375 +354,286 @@ This is only saved for moderators and cannot be seen by the banned person. CardZone - + her hand nominative, female owner - + %1's hand nominative, female owner - + his hand nominative, male owner - - - %1's hand - nominative, male owner - - - - - of her hand - genitive, female owner - - - - - of %1's hand - genitive, female owner - - - - - of his hand - genitive, male owner - - - - - of %1's hand - genitive, male owner - - - - - her hand - accusative, female owner - - - - - %1's hand - accusative, female owner - - - - - his hand - accusative, male owner - - - - - %1's hand - accusative, male owner - - + %1's hand + nominative, male owner + + + + her library - nominative, female owner + look at zone, female owner - + %1's library - nominative, female owner + look at zone, female owner - + his library - nominative, male owner + look at zone, male owner - + %1's library - nominative, male owner + look at zone, male owner - + of her library - genitive, female owner + top cards of zone, female owner - + of %1's library - genitive, female owner + top cards of zone, female owner - + of his library - genitive, male owner + top cards of zone, male owner - + of %1's library - genitive, male owner + top cards of zone, male owner - + her library - accusative, female owner + reveal zone, female owner - + %1's library - accusative, female owner + reveal zone, female owner - + his library - accusative, male owner + reveal zone, male owner - + %1's library - accusative, male owner + reveal zone, male owner - + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + + + + + his library + shuffle, male owner + + + + + %1's library + shuffle, male owner + + + + + her sideboard + look at zone, female owner + + + + + %1's sideboard + look at zone, female owner + + + + + his sideboard + look at zone, male owner + + + + + %1's sideboard + look at zone, male owner + + + + + her library + nominative, female owner + + + + + %1's library + nominative, female owner + + + + + his library + nominative, male owner + + + + + %1's library + nominative, male owner + + + + her graveyard nominative, female owner - + %1's graveyard nominative, female owner - + his graveyard nominative, male owner - + %1's graveyard nominative, male owner - - of her graveyard - genitive, female owner - - - - - of %1's graveyard - genitive, female owner - - - - - of his graveyard - genitive, male owner - - - - - of %1's graveyard - genitive, male owner - - - - - her graveyard - accusative, female owner - - - - - %1's graveyard - accusative, female owner - - - - - his graveyard - accusative, male owner - - - - - %1's graveyard - accusative, male owner - - - - + her exile nominative, female owner - + %1's exile nominative, female owner - + his exile nominative, male owner - + %1's exile nominative, male owner - - of her exile - genitive, female owner - - - - - of %1's exile - genitive, female owner - - - - - of his exile - genitive, male owner - - - - - of %1's exile - genitive, male owner - - - - - her exile - accusative, female owner - - - - - %1's exile - accusative, female owner - - - - - his exile - accusative, male owner - - - - - %1's exile - accusative, male owner - - - - + her sideboard nominative, female owner - + %1's sideboard nominative, female owner - + his sideboard nominative, male owner - + %1's sideboard nominative, male owner + + + DBPriceUpdater - - of her sideboard - genitive, female owner + + + + Error + Mearachd + + + + + A problem has occured while fetching card prices. - - of %1's sideboard - genitive, female owner - - - - - of his sideboard - genitive, male owner - - - - - of %1's sideboard - genitive, male owner - - - - - her sideboard - accusative, female owner - - - - - %1's sideboard - accusative, female owner - - - - - his sideboard - accusative, male owner - - - - - %1's sideboard - accusative, male owner + + A problem has occured while fetching card prices: DeckEditorSettingsPage - - Enable &price tag feature (using data from blacklotusproject.com) + + Enable &price tag feature - + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General Coitcheann @@ -854,43 +641,77 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Number Àireamh - + Card Cairt - + Price Prìs + + DeckStatsInterface + + + + Error + Mearachd + + + + The reply from the server could not be parsed. + + + DeckViewContainer - + Load &local deck - + Load d&eck from server - + Ready to s&tart - + + S&ideboard unlocked + + + + + S&ideboard locked + + + + Load deck + + + Error + Mearachd + + + + The selected file could not be loaded. + + DlgCardSearch @@ -934,34 +755,37 @@ This is only saved for moderators and cannot be seen by the banned person. DlgConnect - + &Host: Ò&stair: - + &Port: &Port: - + Player &name: &Ainm a' chluicheadair: - + P&assword: &Facal-faire: - - &OK - &Ceart ma-thà + + &Save password + + + + &OK + &Ceart ma-thà - &Cancel - &Sguir dheth + &Sguir dheth @@ -972,87 +796,90 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + &Description: &Tuairisgeul: - + P&layers: &Cluicheadairean: - + Game type - + &Password: &Facal-faire: - + Only &buddies can join - + Only &registered users can join - + Joining restrictions - + &Spectators allowed - + Spectators &need a password to join - + Spectators can &chat - + Spectators see &everything - + Spectators Amharcaiche - &OK - &Ceart ma-thà + &Ceart ma-thà - &Cancel - &Sguir dheth + &Sguir dheth - + Create game Cruthaich gaema - + + Game information + + + + Error Mearachd - + Server error. Not sure if this is correct Mearachd Fhrithealaiche. @@ -1061,116 +888,282 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: &Ainm: - + Token - + C&olor: &Dath: - + white bàn - + blue gorm - + black dubh - + red dearg - + green uaine - + multicolor - + colorless "without color" - not sure if there's something more idiomatic gun dath - + &P/T: - + &Annotation: - + &Destroy token when it leaves the table - + + Token data + + + + + Show &all tokens + + + + + Show tokens from this &deck + + + + + Choose token from list + + + &OK - &Ceart ma-thà + &Ceart ma-thà - &Cancel - &Sguir dheth + &Sguir dheth - + Create token + + DlgEditTokens + + + &Name: + &Ainm: + + + + C&olor: + &Dath: + + + + white + bàn + + + + blue + gorm + + + + black + dubh + + + + red + + + + + green + + + + + multicolor + + + + + colorless + gun dath + + + + &P/T: + + + + + &Annotation: + + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + Mearachd + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + O&K + &Ceart ma-thà + + + &Cancel + &Sguir dheth + + + + Filter games + + + DlgLoadDeckFromClipboard - + &Refresh Ù&raich - &OK - &Ceart ma-thà + &Ceart ma-thà - &Cancel - &Sguir dheth + &Sguir dheth - + Load deck from clipboard - + Error Mearachd - + Invalid deck list. @@ -1178,17 +1171,15 @@ This is only saved for moderators and cannot be seen by the banned person. DlgLoadRemoteDeck - O&K - &Ceart ma-thà + &Ceart ma-thà - &Cancel - &Sguir dheth + &Sguir dheth - + Load deck @@ -1196,149 +1187,213 @@ This is only saved for moderators and cannot be seen by the banned person. DlgSettings - - - + + + Error Mearachd - - Your card database is invalid. Would you like to go back and set the correct path? + + Unknown Error loading card database - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Roghainnean - + General Coitcheann - + Appearance Coltas - + User interface - + Deck editor - + Messages - &Close - &Dùin + &Dùin GameSelector - - - - - - - - + + + + + + + + + Error Mearachd - + Please join the appropriate room first. - + Wrong password. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join game Gabh pàirt ann an geama - + Password: Facal-faire: - + + Please join the respective room first. + + + + Games Geamannan - - Show u&navailable games + + &Filter games - + + C&lear filter + + + + C&reate - + &Join - + J&oin as spectator @@ -1354,72 +1409,72 @@ This is only saved for moderators and cannot be seen by the banned person. GamesModel - + yes tha - + yes, free for spectators - + no chan eil - + buddies only - + reg. users only - + not allowed - + Description Tuairisgeul - + Room Seòmar - + Creator Cruthadair - + Game type - + Password Facal-faire - + Restrictions - + Players Cluicheadairean - + Spectators Amharcaiche @@ -1427,299 +1482,392 @@ This is only saved for moderators and cannot be seen by the banned person. GeneralSettingsPage - - + + English Beurla - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Mearachd + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings - + Language: Cànan: - + Download card pictures on the fly - + + Download high-quality card pictures + + + + Paths - + Decks directory: - + + Replays directory: + + + + Pictures directory: - - Path to card database: + + Card database: + + + + + Token database: MainWindow - + There are too many concurrent connections from your address. - - Banned by moderator. - - - - + Scheduled server shutdown. - - Unknown reason. + + Banned by moderator - + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + Connection closed - + The server has terminated your connection. Reason: %1 - + Scheduled server shutdown - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 + - + Number of players Àireamh de cluicheadairean - + Please enter the number of players. - - + + Player %1 Cluicheadair %1 - + + Load replay + + + + About Cockatrice Mu Cockatrice - + Version %1 Tionndadh %1 - + Authors: - + Translators: - + Spanish: - + Portugese (Portugal): - + Portugese (Brazil): - + French: - + Japanese: - + Russian: - - Czech: - - - - + Italian: - - - - - - + + Swedish: + + + + + + + + + + + + + Error Mearachd - + Server timeout - - Invalid login data. + + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + This server requires user registration. + + + + + Unknown login error: %1 + + + + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... The conjugation on 'ri' will probably suck here. A' dèanamh ceangal ri %1... - + Disconnected Air dì-cheangal - + + Connected, logging in at %1 + + + + Logged in at %1 - + &Connect... A' dèanamh &ceangal... - + &Disconnect &Dì-cheangail - + Start &local game... - + + &Watch replay... + + + + &Deck editor - + &Full screen &Làn-sgrìn - + Ctrl+F - + &Settings... &Roghainnean... - + &Exit &Fàg an-seo - + &Cockatrice Considering this a proper name, so leaving untranslated &Cockatrice - + &About Cockatrice &Mu Cockatrice - + &Help &Cobhair - + Are you sure? A bheil thu cinnteach? - + There are still open games. Are you sure you want to quit? @@ -1727,1056 +1875,1169 @@ Local version is %1, remote version is %2. MessageLogWidget - + You have joined game #%1. female - - - You have joined game #%1. - male - - - - - %1 has joined the game. - female - - - - - %1 has joined the game. - male - - - %1 has left the game. - female - - - - - %1 has left the game. + You have joined game #%1. male - - The game has been closed. + + You are watching a replay of game #%1. + female - - %1 is now watching the game. + + You are watching a replay of game #%1. + male + + + + + %1 has joined the game. + female - %1 is not watching the game any more. + %1 has joined the game. + male - %1 has loaded a deck (%2). + %1 has left the game. female + %1 has left the game. + male + + + + + The game has been closed. + + + + + You have been kicked out of the game. + Chaidh do bhreabadh far an geama seo. + + + + %1 is now watching the game. + + + + + %1 is not watching the game any more. + + + + %1 has loaded a deck (%2). - male - - - - - %1 is ready to start the game. - female - - - - - %1 is ready to start the game. - male - - - - - %1 is not ready to start the game any more. - female - - - - - %1 is not ready to start the game any more. - male - - - - - %1 has conceded the game. - female - - - - - %1 has conceded the game. - male - The game has started. - Tha an geama air tòiseachadh. - - - - %1 has restored connection to the game. - female + %1 has loaded a deck with %2 sideboard cards (%3). + %1 is ready to start the game. + female + + + + + %1 is ready to start the game. + male + + + + + %1 is not ready to start the game any more. + female + + + + + %1 is not ready to start the game any more. + male + + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + %1 has conceded the game. + female + + + + + %1 has conceded the game. + male + + + + + The game has started. + Tha an geama air tòiseachadh. + + + + %1 has restored connection to the game. + female + + + + %1 has restored connection to the game. male - + %1 has lost connection to the game. female - + %1 has lost connection to the game. male - + %1 shuffles %2. female - - - %1 shuffles %2. - male - - - - - %1 rolls a %2 with a %3-sided die. - female - - - - - %1 rolls a %2 with a %3-sided die. - male - - - - - %1 draws %n card(s). - female - - - - - - - %1 draws %n card(s). - male - - - - - - - %1 undoes her last draw. - - - - - %1 undoes his last draw. - - - - - %1 undoes her last draw (%2). - - - - - %1 undoes his last draw (%2). - - - - - from table - - - - - from graveyard - - - - - from exile - - - from hand + %1 shuffles %2. + male - - the bottom card of her library + + %1 rolls a %2 with a %3-sided die. + female - - the bottom card of his library + + %1 rolls a %2 with a %3-sided die. + male + + + + + %1 draws %n card(s). + female + + + + + + + + %1 draws %n card(s). + male + + + + + + + + %1 undoes her last draw. - - from the bottom of her library + + %1 undoes his last draw. - - from the bottom of his library + + %1 undoes her last draw (%2). - - the top card of her library + + %1 undoes his last draw (%2). - - the top card of his library - - - - - from the top of her library - - - - - from the top of his library - - - - - from library - - - - - from sideboard - - - - - from the stack + + from table - - a card - cairt - - - - %1 gives %2 control over %3. + from graveyard - - %1 puts %2 into play tapped%3. + + from exile + + + + + from hand + + + + + the bottom card of %1's library + + + + + the bottom card of her library + + + + + the bottom card of his library - %1 puts %2 into play%3. + from the bottom of %1's library - %1 puts %2%3 into graveyard. + from the bottom of her library - - %1 exiles %2%3. + + from the bottom of his library - - %1 moves %2%3 to hand. + + the top card of %1's library - %1 puts %2%3 into her library. + the top card of her library - %1 puts %2%3 into his library. - - - - - %1 puts %2%3 on bottom of her library. - - - - - %1 puts %2%3 on bottom of his library. + the top card of his library - %1 puts %2%3 on top of her library. - - - - - %1 puts %2%3 on top of his library. + from the top of %1's library - %1 puts %2%3 into her library at position %4. + from the top of her library - %1 puts %2%3 into his library at position %4. + from the top of his library - - %1 moves %2%3 to sideboard. + + from %1's library - - %1 plays %2%3. - - - - - %1 takes a mulligan to %n. - female - - - - - - - %1 takes a mulligan to %n. - male - - - - - - - %1 draws her initial hand. + + from library - - %1 draws his initial hand. + + from sideboard - - %1 flips %2 face-down. - female + + from the stack + + + + a card + cairt + - %1 flips %2 face-down. - male + %1 gives %2 control over %3. - - %1 flips %2 face-up. - female + + %1 puts %2 into play tapped%3. - - %1 flips %2 face-up. - male + + %1 puts %2 into play%3. + %1 puts %2%3 into graveyard. + + + + + %1 exiles %2%3. + + + + + %1 moves %2%3 to hand. + + + + + %1 puts %2%3 into her library. + + + + + %1 puts %2%3 into his library. + + + + + %1 puts %2%3 on bottom of her library. + + + + + %1 puts %2%3 on bottom of his library. + + + + + %1 puts %2%3 on top of her library. + + + + + %1 puts %2%3 on top of his library. + + + + + %1 puts %2%3 into her library at position %4. + + + + + %1 puts %2%3 into his library at position %4. + + + + + %1 moves %2%3 to sideboard. + + + + + %1 plays %2%3. + + + + + %1 takes a mulligan to %n. + female + + + + + + + + %1 takes a mulligan to %n. + male + + + + + + + + %1 draws her initial hand. + + + + + %1 draws his initial hand. + + + + + %1 flips %2 face-down. + female + + + + + %1 flips %2 face-down. + male + + + + + %1 flips %2 face-up. + female + + + + + %1 flips %2 face-up. + male + + + + %1 destroys %2. female - + %1 destroys %2. male - + %1 attaches %2 to %3's %4. p1 female, p2 female - + %1 attaches %2 to %3's %4. p1 female, p2 male - + %1 attaches %2 to %3's %4. p1 male, p2 female - + %1 attaches %2 to %3's %4. p1 male, p2 male - + %1 unattaches %2. female - + %1 unattaches %2. male - + %1 creates token: %2%3. female - + %1 creates token: %2%3. male - + %1 points from her %2 to herself. female - + %1 points from his %2 to himself. male - + %1 points from her %2 to %3. p1 female, p2 female - + %1 points from her %2 to %3. p1 female, p2 male - + %1 points from his %2 to %3. p1 male, p2 female - + %1 points from his %2 to %3. p1 male, p2 male - + %1 points from %2's %3 to herself. card owner female, target female - + %1 points from %2's %3 to herself. card owner male, target female - + %1 points from %2's %3 to himself. card owner female, target male - + %1 points from %2's %3 to himself. card owner male, target male - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male - + %1 points from her %2 to her %3. female - + %1 points from his %2 to his %3. male - + %1 points from her %2 to %3's %4. p1 female, p2 female - + %1 points from her %2 to %3's %4. p1 female, p2 male - + %1 points from his %2 to %3's %4. p1 male, p2 female - + %1 points from his %2 to %3's %4. p1 male, p2 male - + %1 points from %2's %3 to her own %4. card owner female, target female - + %1 points from %2's %3 to her own %4. card owner male, target female - + %1 points from %2's %3 to his own %4. card owner female, target male - + %1 points from %2's %3 to his own %4. card owner male, target male - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male - + %1 places %n %2 counter(s) on %3 (now %4). female + - + %1 places %n %2 counter(s) on %3 (now %4). male + - + %1 removes %n %2 counter(s) from %3 (now %4). female - - - - - %1 removes %n %2 counter(s) from %3 (now %4). - male - - + + %1 removes %n %2 counter(s) from %3 (now %4). + male + + + + + + + red - - + dearg - + yellow + buidhe - + green + uaine - + %1 taps her permanents. female - + %1 untaps her permanents. female - + %1 taps his permanents. male - + %1 untaps his permanents. male - + %1 taps %2. female - + %1 untaps %2. female - - - %1 taps %2. - male - - - - - %1 untaps %2. - male - - - - - %1 sets counter %2 to %3 (%4%5). - female - - - - - %1 sets counter %2 to %3 (%4%5). - male - - - - - %1 sets %2 to not untap normally. - female - - - - - %1 sets %2 to not untap normally. - male - - - - - %1 sets %2 to untap normally. - female - - - - - %1 sets %2 to untap normally. - male - - - - - %1 sets PT of %2 to %3. - female - - - - - %1 sets PT of %2 to %3. - male - - - - - %1 sets annotation of %2 to %3. - female - - - - - %1 sets annotation of %2 to %3. - male - - - - - %1 is looking at the top %2 cards %3. - female - - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - - %1 is looking at %2. - female - - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female + %1 sets counter %2 to %3 (%4%5). + male + + + + + %1 sets %2 to not untap normally. + female + + + + + %1 sets %2 to not untap normally. + male + + + + + %1 sets %2 to untap normally. + female + + + + + %1 sets %2 to untap normally. + male + + + + + %1 sets PT of %2 to %3. + female + + + + + %1 sets PT of %2 to %3. + male + + + + + %1 sets annotation of %2 to %3. + female + + + + + %1 sets annotation of %2 to %3. + male + + + + + %1 is looking at %2. + female + + + + + %1 is looking at %2. + male + + + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + + + %1 stops looking at %2. + female + + + + %1 stops looking at %2. male - - - %1 reveals %2 to %3. - p1 female, p2 female - - - - - %1 reveals %2 to %3. - p1 female, p2 male - - - - - %1 reveals %2 to %3. - p1 male, p2 female - - - - - %1 reveals %2 to %3. - p1 male, p2 male - - - - - %1 reveals %2. - female - - - - - %1 reveals %2. - male - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 randomly reveals %2%3. - female - - - - - %1 randomly reveals %2%3. - male - - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 reveals %2%3. - female - - - - - %1 reveals %2%3. - male - - - - - It is now %1's turn. - female - - - It is now %1's turn. - male + %1 reveals %2 to %3. + p1 female, p2 female - - untap step + + %1 reveals %2 to %3. + p1 female, p2 male - - upkeep step - Ceum beòshlaint + + %1 reveals %2 to %3. + p1 male, p2 female + - - draw step - ceum tarraing + + %1 reveals %2 to %3. + p1 male, p2 male + - first main phase + %1 reveals %2. + female - beginning of combat step - - - - - declare attackers step - - - - - declare blockers step - - - - - combat damage step - - - - - end of combat step - - - - - second main phase - - - - - ending phase + %1 reveals %2. + male + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 randomly reveals %2%3. + female + + + + + %1 randomly reveals %2%3. + male + + + + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 reveals %2%3. + female + + + + + %1 reveals %2%3. + male + + + + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + + It is now %1's turn. + female + + + + + It is now %1's turn. + male + + + + + untap step + + + + + upkeep step + Ceum beòshlaint + + + + draw step + ceum tarraing + + + + first main phase + + + + + beginning of combat step + + + + + declare attackers step + + + + + declare blockers step + + + + + combat damage step + + + + + end of combat step + + + + + second main phase + + + + + ending phase + + + + It is now the %1. @@ -2784,22 +3045,22 @@ Local version is %1, remote version is %2. MessagesSettingsPage - + Add message - + Message: - + &Add - + &Remove @@ -2807,58 +3068,58 @@ Local version is %1, remote version is %2. PhasesToolbar - + Untap step - + Upkeep step I don't know if Ceum is the right word here - literally "step", but I don't know if there's some idiom that might work better. Ceum Beòshlaint - + Draw step Ceum tarraing - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -2866,388 +3127,550 @@ Local version is %1, remote version is %2. Player - + &View graveyard - + &View exile - + Player "%1" Cluicheadair "%1" - + &Graveyard - + &Exile - - - + + + Move to &top of library - - - + + + Move to &bottom of library - - + + Move to &graveyard - - + + Move to &exile - - + + Move to &hand - + &View library - + View &top cards of library... - + Reveal &library to - + Reveal t&op card to - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &View sideboard - + &Draw card &Tarraing cairt - + D&raw cards... Ta&rraing cairtean... - + &Undo last draw - + Take &mulligan - + &Shuffle - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + &Hand &Làmh - + &Reveal to - + Reveal r&andom card to - + &Sideboard - + &Library &Leabhar-lann - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + + Cr&eate predefined token + + + + S&ay - + C&ard C&airt - + &All players - + + &Play + + + + + &Hide + Falaic&h + + + + &Tap + + + + + &Untap + + + + + Toggle &normal untapping + + + + + &Flip + Dèan &flip + + + + &Peek at card face + + + + + &Clone + &Copaig + + + + Ctrl+J + + + + + Attac&h to card... + + + + + Ctrl+A + + + + + Unattac&h + + + + + &Draw arrow... + + + + + &Increase power + + + + + Ctrl++ + + + + + &Decrease power + + + + + Ctrl+- + + + + + I&ncrease toughness + + + + + Alt++ + + + + + D&ecrease toughness + + + + + Alt+- + + + + + In&crease power and toughness + + + + + Ctrl+Alt++ + + + + + Dec&rease power and toughness + + + + + Ctrl+Alt+- + + + + + Set &power and toughness... + + + + + Ctrl+P + + + + + &Set annotation... + &Sgrìobh nòtachadh... + + + + red + dearg + + + + yellow + buidhe + + + + green + uaine + + + + &Add counter (%1) + + + + + &Remove counter (%1) + + + + + &Set counters (%1)... + + + + + &top of library + + + + + &bottom of library + + + + + &graveyard + + + + + Ctrl+Del + + + + + &exile + + + + Ctrl+F3 - + F3 - + Ctrl+W - + F4 - + Ctrl+D - + Ctrl+E - + Ctrl+Shift+D - + Ctrl+M - + Ctrl+S - + Ctrl+U - + Ctrl+I - + Ctrl+T - + Ctrl+G - + View top cards of library - + Number of cards: - + Draw cards Tarraing cairtean - - - - + + + + Number: Àireamh: - + Move top cards to grave - + Move top cards to exile - + Roll die - + Number of sides: - + Set power/toughness - + Please enter the new PT: - + Set annotation Sgrìobh nòtachadh - + Please enter the new annotation: - + Set counters - - PlayerListWidget - - - User &details - - - - - Direct &chat - - - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Kick from &game - - - QObject - - Maindeck + + Cockatrice replays (*.cor) - - Sideboard + + Common deck formats (*.cod *.dec *.mwDeck) - - Cockatrice decks (*.cod) - - - - - Plain text decks (*.dec *.mwDeck) - - - - + All files (*.*) @@ -3255,50 +3678,83 @@ Local version is %1, remote version is %2. RemoteDeckList_TreeModel - + Name Ainm - + ID - + Upload time + + RemoteReplayList_TreeModel + + + ID + + + + + Name + Ainm + + + + Players + Cluicheadairean + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + RoomSelector - + Rooms Seòmaraichean - + Joi&n &Gabh pàirt - + Room Seòmar - + Description Tuairisgeul - + Players Cluicheadairean - + Games Geamannan @@ -3319,27 +3775,25 @@ Local version is %1, remote version is %2. ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - &OK - &Ceart ma-thà + &Ceart ma-thà - &Cancel - &Sguir dheth + &Sguir dheth - + Shut down server Dùin am frithealaiche @@ -3347,246 +3801,480 @@ Local version is %1, remote version is %2. TabAdmin - + Update server &message - + &Shut down server &Dùin am frithealaiche - + Server administration functions - + &Unlock functions - + &Lock functions - + Unlock administration functions - + Do you really want to unlock the administration functions? + + + TabDeckEditor - - Administration + &Search... + &Lorg... + + + + Show card text only + + + + + &Clear search + + + + + &Search for: + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Update prices + + + + + Ctrl+U + + + + + &New deck + + + + + &Load deck... + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Save deck to clip&board + + + + + &Print deck... + + + + + &Analyze deck on deckstats.net + + + + + &Close + &Dùin + + + + Ctrl+Q + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + &Remove row + + + + + Del + Del + + + + &Increment number + + + + + + + + + + + &Decrement number + + + + + - + + + + + &Deck editor + + + + + C&ard database + + + + + &Edit sets... + + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + A bheil thu cinnteach? + + + + The decklist has been modified. +Do you want to save the changes? + + + + + Load deck + + + + + + + Error + Mearachd + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Upload deck - + Download deck - - + + New folder - + + Delete Sguab às - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: - - Deck storage + + Delete remote folder + + + + + Delete remote deck TabGame - + F5 - + F6 - + F7 - + F8 - + F9 - + F10 - + &Phases - + &Game &Geama - + Next &phase - + Ctrl+Space - + + Tab + + + + Next &turn - + Ctrl+Return - + Ctrl+Enter - + &Remove all local arrows - + Ctrl+R - + + Game &information + + + + &Concede &Gèill an geama - + F2 - + &Leave game &Fàg an geama - + + Ctrl+Q - + + C&lose replay + + + + &Say: - + Concede Gèill an geama - + Are you sure you want to concede this game? A bheil thu cinnteach gu bheil thu airson gèilleadh an geama seo? - + Leave game Fàg an geama - + Are you sure you want to leave this game? A bheil thu cinnteach gu bheil thu airson fàgail an geama seo? - + + Replay %1: %2 + + + Kicked - Air a bhreabadh + Air a bhreabadh - You have been kicked out of the game. - Chaidh do bhreabadh far an geama seo. + Chaidh do bhreabadh far an geama seo. - + Game %1: %2 Geama %1: %2 @@ -3594,60 +4282,120 @@ Please enter a name: TabMessage - + Personal &talk - + &Leave - + This user is ignoring you. - + %1 has left the server. - + %1 has joined the server. - + Talking to %1 Bruidheann ri %1 + + TabReplays + + + Local file system + + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + Sguab às + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: - + Chat Cabadaich - + &Room &Seòmar - + &Leave room &Fàg an seòmar - + + &Ignore unregistered users in chat + + + + You are flooding the chat. Please wait a couple of seconds. @@ -3655,64 +4403,121 @@ Please enter a name: TabServer - Server - Fhrithealaiche + Fhrithealaiche TabUserLists - - User lists + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + + + + + Direct &chat + + + + + Show this user's &games + + + + + Add to &buddy list + + + + + Remove from &buddy list + + + + + Add to &ignore list + + + + + Remove from &ignore list + + + + + Kick from &game + + + + + Ban from &server + + + + + %1's games UserInfoBox - + User information No clue if this is right. Fiosrachadh a' cleachdaiche - + Real name: - + Gender: Gnè: - + Location: - + User level: - + Administrator - + Moderator - + Registered user - + Unregistered user @@ -3720,37 +4525,47 @@ Please enter a name: UserInterfaceSettingsPage - + General interface settings - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings - + &Tap/untap animation - + Enable &sounds - + Path to sounds directory: - + Choose path @@ -3758,271 +4573,47 @@ Please enter a name: UserList - + Users online: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 - - - %1's games - - - - - User &details - - - - - Direct &chat - - - - - Show this user's &games - - - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Ban from &server - - WndDeckEditor - &Search... - &Lorg... + &Lorg... - - &Clear search - - - - - &Search for: - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &Update prices - - - - - Ctrl+U - - - - - Deck editor [*] - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clip&board - - - - - &Print deck... - - - - &Close - &Dùin + &Dùin - - Ctrl+Q - - - - - &Edit sets... - - - - - &Deck - - - - - &Card database - - - - - Add card to &maindeck - - - - - Return - - - - - Enter - - - - - Add card to &sideboard - - - - - Ctrl+Return - - - - - Ctrl+Enter - - - - - &Remove row - - - - Del - Del + Del - - &Increment number - - - - - + - - - - - &Decrement number - - - - - - - - - - Are you sure? - A bheil thu cinnteach? + A bheil thu cinnteach? - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - Error - Mearachd - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - + Mearachd @@ -4036,17 +4627,17 @@ Please check that the directory is writable and try again. ZoneViewWidget - + sort by name - + sort by type - + shuffle when closing diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index 1596f41cc..111a70965 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... &Imposta segnalini... - + Ctrl+L Ctrl+L - + F11 F11 - + F12 F12 - + Set counter Imposta segnalini - + New value for counter '%1': Nuovo valore per il segnalino '%1': @@ -37,91 +37,111 @@ AppearanceSettingsPage - + Zone background pictures Immagini di Sfondo - Path to hand background: - Percorso sfondo della mano: + Percorso sfondo della mano: - Path to stack background: - Percorso sfondo della pila: + Percorso sfondo della pila: - Path to table background: - Percorso sfondo del tavolo: + Percorso sfondo del tavolo: - Path to player info background: - Percorso sfondo info giocatore: + Percorso sfondo info giocatore: - Path to picture of card back: - Percorso sfondo retro delle carte: + Percorso sfondo retro delle carte: - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Card rendering Visualizzazione delle carte - + Display card names on cards having a picture Visualizza nome delle carte sulle immagini di esse - + Hand layout Layout della mano - + Display hand horizontally (wastes space) Disponi la mano orizzontalmente - + Table grid layout Griglia di layout - + Invert vertical coordinate Inverti le coordinate verticali - + Minimum player count for multi-column layout: Numero di giocatori minimo per layout multicolonna: - + Zone view layout Layout zona in vista - + Sort by name Ordina per nome - + Sort by type Ordina per tipo - - - - - + + + + + Choose path Seleziona percorso @@ -135,353 +155,348 @@ Enter 0 for an indefinite ban. Inserisci 0 per un periodo indefinito. - + ban &user name - + Banna &user name - + ban &IP address - + Banna indirizzo &IP - + Ban type - + Tipo di ban - + &permanent ban - + Ban &permanente - + &temporary ban - - - - - &Days: - + Ban &temporaneo - &Hours: - + &Days: + &Giorni: + &Hours: + &Ore: + + + &Minutes: - + &Minuti: - + Duration of the ban - + Durata del ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Per favore inserisci la ragione del ban. Questo è solo visibile ai moderatori e non alla persona bannata. - + + Please enter the reason for the ban that will be visible to the banned person. + Per favore inserisci la ragione del ban che sarà visibile alla persona bannata. + + + &OK &OK - + &Cancel &Annulla - + Ban user from server Banna utente dal server - + Error - Errore + Errore - + You have to select a name-based or IP-based ban, or both. - + Seleziona un ban su nome, IP, o entrambi. CardDatabaseModel - + Name Nome - + Sets Sets - + Mana cost Costo - + Card type Tipo - + P/T F/C - CardInfoWidget + CardInfoText - - Hide card info - Nascondi info sulla carta + + Name: + Nome: - + + Mana cost: + Costo: + + + + Card type: + Tipo: + + + + P / T: + F / C: + + + + Loyalty: + Fedeltà: + + + + CardInfoWidget + + Hide card info + Nascondi info sulla carta + + + Show card only Visualizza solo la carta - + Show text only Visualizza solo il testo - + Show full info Visualizza tutte le info - + Name: Nome: - + Mana cost: Costo: - + Card type: Tipo: - + P / T: F / C: + + + Loyalty: + Fedeltà: + CardItem - &Play - &Gioca + &Gioca - &Hide - &Nascondi + &Nascondi - &Tap - &Tappa + &Tappa - &Untap - &Stappa + &Stappa - Toggle &normal untapping - Non &stappa normalmente + Non &stappa normalmente - &Flip - &Gira + &Gira - &Clone - &Copia + &Copia - - Ctrl+H - - - - &Attach to card... - &Attacca alla carta... + &Attacca alla carta... - Ctrl+A - Ctrl+A + Ctrl+A - Unattac&h - Stacc&a + Stacc&a - &Draw arrow... - &Disegna una freccia... + &Disegna una freccia... - + &Power / toughness &Forza / Costituzione - &Increase power - &Aumenta forza + &Aumenta forza - Ctrl++ - Ctrl++ + Ctrl++ - &Decrease power - &Diminuisci forza + &Diminuisci forza - Ctrl+- - Ctrl+- + Ctrl+- - I&ncrease toughness - A&umenta costituzione + A&umenta costituzione - Alt++ - Alt++ + Alt++ - D&ecrease toughness - D&iminuisci costituzione + D&iminuisci costituzione - Alt+- - Alt+- + Alt+- - In&crease power and toughness - Au&menta forza e costituzione + Au&menta forza e costituzione - Ctrl+Alt++ - Ctrl+Alt++ + Ctrl+Alt++ - Dec&rease power and toughness - Dim&inuisci forza e costituzione + Dim&inuisci forza e costituzione - Ctrl+Alt+- - Ctrl-Alt+- + Ctrl-Alt+- - Set &power and toughness... - Imposta &forza e costituzione... + Imposta &forza e costituzione... - Ctrl+P - Ctrl+P + Ctrl+P - &Set annotation... - &Imposta annotazioni... + &Imposta annotazioni... - red - rosso + rosso - yellow - giallo + giallo - green - verde + verde - &Add counter (%1) - &Aggiungi contatore (%1) + &Aggiungi contatore (%1) - &Remove counter (%1) - &Rimuovi contatore (%1) + &Rimuovi contatore (%1) - &Set counters (%1)... - &Imposta contatori (%1)... + &Imposta contatori (%1)... - &top of library - &cima al grimorio + &cima al grimorio - &bottom of library - &fondo del grimorio + &fondo del grimorio - &graveyard - &cimitero + &cimitero - Ctrl+Del - Ctrl+Del + Ctrl+Del - &exile - &esilio + &esilio - + &Move to &Metti in @@ -489,375 +504,480 @@ Questo è solo visibile ai moderatori e non alla persona bannata. CardZone - + her hand nominative, female owner la sua mano - + %1's hand nominative, female owner la mano di %1 - + his hand nominative, male owner la sua mano - - - %1's hand - nominative, male owner - la mano di %1 - - - - of her hand - genitive, female owner - della sua mano - - - - of %1's hand - genitive, female owner - della mano di %1 - - - - of his hand - genitive, male owner - della sua mano - - - - of %1's hand - genitive, male owner - della mano di %1 - - - - her hand - accusative, female owner - la sua mano - - - - %1's hand - accusative, female owner - la mano di %1 - - - - his hand - accusative, male owner - la sua mano - - - - %1's hand - accusative, male owner - la mano di %1 - + %1's hand + nominative, male owner + la mano di %1 + + + her library - nominative, female owner + look at zone, female owner il suo grimorio - + %1's library - nominative, female owner + look at zone, female owner il grimorio di %1 - + his library - nominative, male owner + look at zone, male owner il suo grimorio - + %1's library - nominative, male owner + look at zone, male owner il grimorio di %1 - + of her library - genitive, female owner + top cards of zone, female owner del suo grimorio - + of %1's library - genitive, female owner + top cards of zone, female owner del grimorio di %1 - + of his library - genitive, male owner - + top cards of zone, male owner + del suo grimorio - + of %1's library - genitive, male owner - + top cards of zone, male owner + del grimorio di %1 + + + + her library + reveal zone, female owner + il suo grimorio + + + + %1's library + reveal zone, female owner + il grimorio di %1 + + + + his library + reveal zone, male owner + il suo grimorio + + + + %1's library + reveal zone, male owner + il grimorio di %1 + + + + her library + shuffle, female owner + il suo grimorio + + + + %1's library + shuffle, female owner + il grimorio di %1 + + + + his library + shuffle, male owner + il suo grimorio + + + + %1's library + shuffle, male owner + il grimorio di %1 + + + + her sideboard + look at zone, female owner + la sua sideboard + + + + %1's sideboard + look at zone, female owner + la sideboard di %1 + + + + his sideboard + look at zone, male owner + la sua sideboard + + + + %1's sideboard + look at zone, male owner + la sideboard di %1 + + + of her hand + genitive, female owner + della sua mano + + + of %1's hand + genitive, female owner + della mano di %1 + + + of his hand + genitive, male owner + della sua mano + + + of %1's hand + genitive, male owner + della mano di %1 + + + her hand + accusative, female owner + la sua mano + + + %1's hand + accusative, female owner + la mano di %1 + + + his hand + accusative, male owner + la sua mano + + + %1's hand + accusative, male owner + la mano di %1 + + + + her library + nominative, female owner + il suo grimorio + + + + %1's library + nominative, female owner + il grimorio di %1 + + + + his library + nominative, male owner + il suo grimorio + + + + %1's library + nominative, male owner + il grimorio di %1 + + + of her library + genitive, female owner + del suo grimorio + + + of %1's library + genitive, female owner + del grimorio di %1 - her library accusative, female owner - il suo grimorio + il suo grimorio - %1's library accusative, female owner - il grimorio di %1 + il grimorio di %1 - his library accusative, male owner - il suo grimorio + il suo grimorio - %1's library accusative, male owner - il grimorio di %1 + il grimorio di %1 - + her graveyard nominative, female owner il suo cimitero - + %1's graveyard nominative, female owner il cimitero di %1 - + his graveyard nominative, male owner il suo cimitero - + %1's graveyard nominative, male owner il cimitero di %1 - of her graveyard genitive, female owner - del suo cimitero + del suo cimitero - of %1's graveyard genitive, female owner - del cimitero di %1 + del cimitero di %1 - of his graveyard genitive, male owner - del suo cimitero + del suo cimitero - of %1's graveyard genitive, male owner - del cimitero di %1 + del cimitero di %1 - her graveyard accusative, female owner - il suo cimitero + il suo cimitero - %1's graveyard accusative, female owner - il cimitero di %1 + il cimitero di %1 - his graveyard accusative, male owner - il suo cimitero + il suo cimitero - %1's graveyard accusative, male owner - il cimitero di %1 + il cimitero di %1 - + her exile nominative, female owner la sua zona di esilio - + %1's exile nominative, female owner la zona di esilio di %1 - + his exile nominative, male owner la sua zona di esilio - + %1's exile nominative, male owner la zona di esilio di %1 - of her exile genitive, female owner - della sua zona di esilio + della sua zona di esilio - of %1's exile genitive, female owner - della zona di esilio di %1 + della zona di esilio di %1 - of his exile genitive, male owner - della sua zona di esilio + della sua zona di esilio - of %1's exile genitive, male owner - della zona di esilio di %1 + della zona di esilio di %1 - her exile accusative, female owner - la sua zona di esilio + la sua zona di esilio - %1's exile accusative, female owner - la zona di esilio di %1 + la zona di esilio di %1 - his exile accusative, male owner - la sua zona di esilio + la sua zona di esilio - %1's exile accusative, male owner - la zona di esilio di %1 + la zona di esilio di %1 - + her sideboard nominative, female owner la sua sideboard - + %1's sideboard nominative, female owner la sideboard di %1 - + his sideboard nominative, male owner la sua sideboard - + %1's sideboard nominative, male owner la sideboard di %1 - of her sideboard genitive, female owner - della sua sideboard + della sua sideboard - of %1's sideboard genitive, female owner - della sideboard di %1 + della sideboard di %1 - of his sideboard genitive, male owner - della sua sideboard + della sua sideboard - of %1's sideboard genitive, male owner - della sideboard di %1 + della sideboard di %1 - her sideboard accusative, female owner - la sua sideboard + la sua sideboard - %1's sideboard accusative, female owner - la sideboard di %1 + la sideboard di %1 - his sideboard accusative, male owner - la sua sideboard + la sua sideboard - %1's sideboard accusative, male owner - la sideboard di %1 + la sideboard di %1 + + + + DBPriceUpdater + + + + + Error + Errore + + + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + DeckEditorSettingsPage - Enable &price tag feature (using data from blacklotusproject.com) - Abilita la &ricerca del costo (utilizzando i dati di blacklotusproject.com) + Abilita la &ricerca del costo (utilizzando i dati di blacklotusproject.com) - + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General Generale @@ -865,43 +985,77 @@ Questo è solo visibile ai moderatori e non alla persona bannata. DeckListModel - + Number Numero - + Card Carta - + Price Prezzo + + DeckStatsInterface + + + + Error + Errore + + + + The reply from the server could not be parsed. + La risposta del server non può essere analizzata. + + DeckViewContainer - + Load &local deck Carica &mazzo dal pc - + Load d&eck from server Carica &mazzo dal server - + Ready to s&tart Pronto a i&niziare - + + S&ideboard unlocked + S&ideboard sbloccato + + + + S&ideboard locked + S&ideboard bloccato + + + Load deck Carica mazzo + + + Error + Errore + + + + The selected file could not be loaded. + I file selezionati non posso essere caricati. + DlgCardSearch @@ -944,34 +1098,37 @@ Questo è solo visibile ai moderatori e non alla persona bannata. DlgConnect - + &Host: &Host: - + &Port: &Porta: - + Player &name: Nome &Giocatore: - + P&assword: P&assword: - - &OK - &OK + + &Save password + &Salva password + + + &OK + &OK - &Cancel - &Annulla + &Annulla @@ -982,87 +1139,90 @@ Questo è solo visibile ai moderatori e non alla persona bannata. DlgCreateGame - + &Description: &Descrizione: - + P&layers: G&iocatori: - + Game type Formato di gioco - + &Password: &Password: - + Only &buddies can join Solo gli &amici possono entrare - + Only &registered users can join Solo &utenti registrati possono entrare - + Joining restrictions Restrizioni d'ingresso - + &Spectators allowed - &Spettatori concessi + &Spettatori ammessi - + Spectators &need a password to join Spettatori &necessitano di password - + Spectators can &chat &Spettatori possono &chattare - + Spectators see &everything Gli spettatori vedono &tutto - + Spectators Spettatori - &OK - &OK + &OK - &Cancel - &Annulla + &Annulla - + Create game Crea partita - + + Game information + Informazioni partita + + + Error Errore - + Server error. Errore del server. @@ -1070,115 +1230,277 @@ Questo è solo visibile ai moderatori e non alla persona bannata. DlgCreateToken - + &Name: &Nome: - + Token Pedina - + C&olor: C&olore: - + white bianco - + blue blu - + black nero - + red rosso - + green verde - + multicolor multicolore - + colorless incolore - + &P/T: &F/C: - + &Annotation: &Appunti: - + &Destroy token when it leaves the table &Distriggi la pedina quando lascia il tavolo - + + Token data + Dati pedina + + + + Show &all tokens + Mostra &tutte le pedine + + + + Show tokens from this &deck + Mostra pedine da questo&mazzo + + + + Choose token from list + Scegli pedina dalla lista + + &OK - &OK + &OK - &Cancel - &Annulla + &Annulla - + Create token Crea pedina - DlgLoadDeckFromClipboard + DlgEditTokens - - &Refresh - &Aggiorna + + &Name: + &Nome: - - &OK - &OK + + C&olor: + C&olore: - - &Cancel - &Annulla + + white + bianco - - Load deck from clipboard - Carica mazzo dagli appunti + + blue + blu - + + black + nero + + + + red + rosso + + + + green + verde + + + + multicolor + multicolore + + + + colorless + incolore + + + + &P/T: + &F/C: + + + + &Annotation: + &Appunti: + + + + Token data + Dati pedina + + + + + Add token + Aggiungi pedina + + + + Remove token + Rimuovi pedina + + + + Edit tokens + Modifica pedine + + + + Please enter the name of the token: + Inserisci il nome della pedina: + + + Error Errore - + + The chosen name conflicts with an existing card or token. + Il nome scelto è in conflitto con un'altro nome di carta o pedina esistente. + + + + DlgFilterGames + + + Show &unavailable games + Mostra partite &non disponibili + + + + Show &password protected games + + + + + Game &description: + &Descrizione partita: + + + + &Creator name: + Nome &creatore: + + + + &Game types + Tipi di &partita + + + + at &least: + &almeno: + + + + at &most: + &almassimo: + + + + Maximum player count + Numero massimo di giocatori + + + O&K + O&K + + + + Filter games + Filtro partite + + + + DlgLoadDeckFromClipboard + + + &Refresh + &Aggiorna + + + &OK + &OK + + + &Cancel + &Annulla + + + + Load deck from clipboard + Carica mazzo dagli appunti + + + + Error + Errore + + + Invalid deck list. Lista del mazzo invalida. @@ -1186,17 +1508,15 @@ Questo è solo visibile ai moderatori e non alla persona bannata. DlgLoadRemoteDeck - O&K - O&K + O&K - &Cancel - &Annulla + &Annulla - + Load deck Carica mazzo @@ -1204,154 +1524,225 @@ Questo è solo visibile ai moderatori e non alla persona bannata. DlgSettings - - - + + + Error Errore - Your card database is invalid. Would you like to go back and set the correct path? - Il tuo database è invalido. Vuoi tornare indietro e impostare il percorso corretto? + Il tuo database è invalido. Vuoi tornare indietro e impostare il percorso corretto? - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella del mazzo non è valido. Vuoi tornare in dietro e impostare il percorso corretto? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella delle immagini delle carte è invilido. Vuoi tornare indietro e impostare il percorso corretto? - + Settings Impostazioni - + General Generale - + Appearance Aspetto - + User interface Interfaccia - + Deck editor Editore di mazzi - + Messages Messaggi - &Close - &Chiudi + &Chiudi GameSelector - - - - - - - - + + + + + + + + + Error Errore - + Please join the appropriate room first. Entra in una stanza appropriata. - + Wrong password. Password errata. - + Spectators are not allowed in this game. Spettatori non ammessi in questa partita. - + The game is already full. La partita è piena. - + The game does not exist any more. Questa stanza non esiste più. - + This game is only open to registered users. Questa partita è solo per utenti registrati. - + This game is only open to its creator's buddies. Questa stanza è solo aperta per gli amici del suo creatore. - + You are being ignored by the creator of this game. Sei stato ingnorato dal creatore di questa partita. - + Join game Entra nella partita - + Password: Password: - + + Please join the respective room first. + Si prega di entrare nella rispettiva stanza prima. + + + Games Partite - + + &Filter games + &Filtri partite + + + + C&lear filter + E&limina filtri + + Show &full games - Visualizza &tutte le partite + Visualizza &tutte le partite - Show &running games - Visualizza le partite &iniziate + Visualizza le partite &iniziate - + C&reate Cr&ea - + &Join &Entra - + J&oin as spectator E&ntra come spettatore @@ -1367,72 +1758,72 @@ Questo è solo visibile ai moderatori e non alla persona bannata. GamesModel - + yes sì - + yes, free for spectators sì, libero per gli spettatori - + no no - + buddies only solo amici - + reg. users only solo utenti registrati - + not allowed non ammessi - + Description Descrizione - + Room Stanza - + Creator Creatore - + Game type Formato di gioco - + Password Password - + Restrictions Restrizioni - + Players Giocatori - + Spectators Spettatori @@ -1440,95 +1831,143 @@ Questo è solo visibile ai moderatori e non alla persona bannata. GeneralSettingsPage - - + + English Italiano - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path Seleziona il percorso - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Errore + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings Impostazioni personali - + Language: Lingua: - + Download card pictures on the fly Download immagini delle carte - + + Download high-quality card pictures + + + + Paths Percorso - + Decks directory: Cartella mazzi: - + + Replays directory: + Cartella replay: + + + Pictures directory: Cartella figure: - + + Card database: + + + + + Token database: + + + Path to card database: - Percorso database delle carte: + Percorso database delle carte: + + + Path to token database: + Percorso database pedine: MainWindow - + There are too many concurrent connections from your address. Ci sono troppe connessioni dal tuo indirizzo. - Banned by moderator. - Bannato dal moderatore. + Bannato dal moderatore. - + Scheduled server shutdown. Spegnimento del server in programma. - Unknown reason. - Ragione sconosciuta. + Ragione sconosciuta. - + Connection closed Connessione chiusa - + The server has terminated your connection. Reason: %1 Hai perso la connessione con il server. Ragione: %1 - + Scheduled server shutdown Spegnimento del server in programma - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1542,208 +1981,276 @@ Ragione dello spegnimento: %1 - + Number of players Numero di giocatori - + Please enter the number of players. Inserisci il numero di giocatori. - - + + Player %1 Giocatore %1 - + About Cockatrice Info su Cockatrice - + Version %1 Versione %1 - + Authors: Autori: - + Translators: Traduttori: - + Spanish: Spagnolo: - + Portugese (Portugal): Portoghese (Portogallo): - + Portugese (Brazil): Portoghese (Brasile): - + French: Francese: - + Japanese: Giapponese: - + Russian: Russo: - Czech: - Ceco: + Ceco: Slovak: Slovacco: - + + Banned by moderator + Bannato dal moderatore + + + + Expected end time: %1 + Fine prevista: %1 + + + + This ban lasts indefinitely. + Questo ban dura a tempo indeterminato. + + + + + Invalid username. + + + + + Load replay + Carica replay + + + Italian: Italiano: - - - - - - + + Swedish: + Svedese: + + + + + + + + + + + + Error Errore - + Server timeout Timeout del server - Invalid login data. - Dati login non validi. + Dati login non validi. - + + Incorrect username or password. Please check your authentication information and try again. + + + + There is already an active session using this user name. Please close that session first and re-login. Sei già loggato in un'altra sessione con questo username. Chiudi prima quella sessione e riprova a loggare. - + + You are banned until %1. + Sei bannato fino a %1. + + + + You are banned indefinitely. + Sei stato bannato per tempo indeterminato. + + + + This server requires user registration. + + + + + Unknown login error: %1 + Errore di login sconosciuto: %1 + + + Socket error: %1 Errore nella connessione: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Stai cercando di connetterti a un server obsoleto. Declassa la versione di Cockatrice per farlo funzionare o connetti ad un altro server. La tua versione è la %1, la versione remota è la %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. La tua versione di Cockatrice è obsoleta. Aggiorna la tua versione di Cockatrice. La tua versione è la %1, la versione online è la %2. - + Connecting to %1... Connettendo a %1... - + Disconnected Disconnesso - + + Connected, logging in at %1 + Connettendo, loggando su %1 + + + Logged in at %1 Loggato su %1 - + &Connect... &Connetti... - + &Disconnect &Disconnetti - + Start &local game... Inizia &partita in locale... - + + &Watch replay... + &Visualizza replay... + + + &Deck editor &Editore di mazzi - + &Full screen &Schermo intero - + Ctrl+F Ctrl+F - + &Settings... &Impostazioni... - + &Exit &Esci - + &Cockatrice &Cockatrice - + &About Cockatrice &Info su Cockatrice - + &Help &Aiuto - + Are you sure? Sei sicuro? - + There are still open games. Are you sure you want to quit? Hai delle partite aperte. Sei sicuro di voler chiudere? @@ -1751,53 +2258,70 @@ La tua versione è la %1, la versione online è la %2. MessageLogWidget - + You have joined game #%1. female Sei entrata nella partita #%1. - + You have joined game #%1. male Sei entrato nella partita #%1. - + + You are watching a replay of game #%1. + female + Stai guardando il replay della partita #%1. + + + + You are watching a replay of game #%1. + male + Stai guardando il replay della partita #%1. + + + %1 has joined the game. female %1 è entrata nella partita. - + %1 has joined the game. male %1 è entrato nella partita. - + %1 has left the game. female %1 ha abbandonato la partia. - + %1 has left the game. male %1 ha abbandonato la partita. - + The game has been closed. La stanza è stata chiusa. - + + You have been kicked out of the game. + Sei stato cacciato dalla partita. + + + %1 is now watching the game. %1 sta adesso osservando la partita. - + %1 is not watching the game any more. %1 non sta più osservando la partita. @@ -1822,108 +2346,140 @@ La tua versione è la %1, la versione online è la %2. %1 ha aperto un mazzo #%2. - %1 has loaded a deck (%2). female - %1 ha aperto un mazzo %2. + %1 ha aperto un mazzo %2. - %1 has loaded a deck (%2). male - %1 ha aperto un mazzo %2. + %1 ha aperto un mazzo %2. - + + %1 has loaded a deck (%2). + %1 ha aperto un mazzo %2. + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + %1 is ready to start the game. female %1 è pronta a iniziare la partita. - + %1 is ready to start the game. male %1 è pronto a iniziare la partita. - + %1 is not ready to start the game any more. female %1 non è più pronta a iniziare la partita. - + %1 is not ready to start the game any more. male %1 non è più pronto a iniziare la partita. - + + %1 has locked her sideboard. + female + %1 ha bloccato la sua sideboard. + + + + %1 has locked his sideboard. + male + %1 ha bloccato la sua sideboard. + + + + %1 has unlocked her sideboard. + female + %1 ha sbloccato la sua sideboard. + + + + %1 has unlocked his sideboard. + male + %1 ha sbloccato la sua sideboard. + + + %1 has conceded the game. female %1 ha concesso la partita. - + %1 has conceded the game. male %1 ha concesso la partita. - + The game has started. La partita è iniziata. - - %1 has restored connection to the game. - female - %1 ha ripristinato il collegamento alla partita. - - - - %1 has restored connection to the game. - male - %1 ha ripristinato il collegamento alla partita. - - - - %1 has lost connection to the game. - female - %1 ha perso il collegamento alla partita. - - - - %1 has lost connection to the game. - male - %1 ha perso il collegamento alla partita. - - - - %1 shuffles %2. - female - %1 mischia %2. - - - - %1 shuffles %2. - male - %1 mischia %2. - - - - %1 rolls a %2 with a %3-sided die. - female - %1 lancia un %2 con %3 facce. - - - - %1 rolls a %2 with a %3-sided die. - male - %1 lancia un %2 con %3 facce. - - + %1 has restored connection to the game. + female + %1 ha ripristinato il collegamento alla partita. + + + + %1 has restored connection to the game. + male + %1 ha ripristinato il collegamento alla partita. + + + + %1 has lost connection to the game. + female + %1 ha perso il collegamento alla partita. + + + + %1 has lost connection to the game. + male + %1 ha perso il collegamento alla partita. + + + + %1 shuffles %2. + female + %1 mischia %2. + + + + %1 shuffles %2. + male + %1 mischia %2. + + + + %1 rolls a %2 with a %3-sided die. + female + %1 lancia un %2 con %3 facce. + + + + %1 rolls a %2 with a %3-sided die. + male + %1 lancia un %2 con %3 facce. + + + %1 draws %n card(s). female @@ -1932,7 +2488,7 @@ La tua versione è la %1, la versione online è la %2. - + %1 draws %n card(s). male @@ -1941,188 +2497,213 @@ La tua versione è la %1, la versione online è la %2. - + %1 undoes her last draw. %1 annulla la sua ultima pescata. - + %1 undoes his last draw. %1 annulla la sua ultima pescata. - + %1 undoes her last draw (%2). %1 annulla la sua ultima pescata (%2). - + %1 undoes his last draw (%2). %1 annulla la sua ultima pescata (%2). - + from table dal tavolo - + from graveyard dal cimitero - + from exile dall'esilio - + from hand dalla mano - + + the bottom card of %1's library + l'ultima carta del grimorio di %1 + + + the bottom card of her library l'ultima carta del suo grimorio - + the bottom card of his library l'ultima carta del suo grimorio - + + from the bottom of %1's library + dalla fine del grimorio di %1 + + + from the bottom of her library dal fondo del suo grimorio - + from the bottom of his library dal fondo del suo grimorio - + + the top card of %1's library + la prima carta del grimorio di %1 + + + the top card of her library la prima carta del suo grimorio - + the top card of his library la prima carta del suo grimorio - + + from the top of %1's library + dalla cima del grimorio di %1 + + + from the top of her library dalla cima del suo grimorio - + from the top of his library dalla cima del suo grimorio - + + from %1's library + dal grimorio di %1 + + + from library dal grimorio - + from sideboard dalla sideboard - + from the stack dalla pila - - + + a card una carta - + %1 gives %2 control over %3. %1 da il controllo di %2 a %3. - + %1 puts %2 into play tapped%3. %1 metti %2 in gioco tappata%3. - + %1 puts %2 into play%3. %1 mette %2 in gioco%3. - + %1 puts %2%3 into graveyard. %1 mette %2%3 nel cimitero. - + %1 exiles %2%3. %1 esilia %2%3. - + %1 moves %2%3 to hand. %1 prende %2%3 in mano. - + %1 puts %2%3 into her library. %1 mette %2%3 nel suo grimorio. - + %1 puts %2%3 into his library. %1 mette %2%3 nel suo grimorio. - + %1 puts %2%3 on bottom of her library. %1 mette %2%3 in fondo al suo grimorio. - + %1 puts %2%3 on bottom of his library. %1 mette %2%3 in fondo al suo grimorio. - + %1 puts %2%3 on top of her library. %1 mette %2%3 in cima al suo grimorio. - + %1 puts %2%3 on top of his library. %1 mette %2%3 in cima al suo grimorio. - + %1 puts %2%3 into her library at position %4. %1 mette %2%3 nel suo grimorio alla posizione %4. - + %1 puts %2%3 into his library at position %4. %1 mette %2%3 nel suo grimorio alla posizione %4. - + %1 moves %2%3 to sideboard. %1 mette %2%3 in sideboard. - + %1 plays %2%3. %1 gioca %2%3. - + %1 takes a mulligan to %n. female @@ -2131,7 +2712,7 @@ La tua versione è la %1, la versione online è la %2. - + %1 takes a mulligan to %n. male @@ -2140,317 +2721,317 @@ La tua versione è la %1, la versione online è la %2. - + %1 draws her initial hand. %1 pesca la sua mano iniziale. - + %1 draws his initial hand. %1 pesca la sua mano iniziale. - + %1 flips %2 face-down. female %1 gira %2 a faccia in giù. - + %1 flips %2 face-down. male %1 gira %2 a faccia in giù. - + %1 flips %2 face-up. female %1 gira %2 a faccia in sù. - + %1 flips %2 face-up. male - + %1 destroys %2. female %1 distrugge %2. - + %1 destroys %2. male %1 distrugge %2. - + %1 attaches %2 to %3's %4. p1 female, p2 female %1 attacca %2 a %3 di %4. - + %1 attaches %2 to %3's %4. p1 female, p2 male %1 attacca %2 a %3 di %4. - + %1 attaches %2 to %3's %4. p1 male, p2 female %1 attacca %2 a %3 di %4. - + %1 attaches %2 to %3's %4. p1 male, p2 male %1 attacca %2 a %3 di %4. - + %1 unattaches %2. female %1 stacca %2. - + %1 unattaches %2. male %1 stacca %2. - + %1 creates token: %2%3. female %1 crea una pedina: %2%3. - + %1 creates token: %2%3. male %1 crea una pedina: %2%3. - + %1 points from her %2 to herself. female %1 disegna una freccia dal suo %2 a sé stessa. - + %1 points from his %2 to himself. male %1 disegna una freccia dal suo %2 a sé stesso. - + %1 points from her %2 to %3. p1 female, p2 female %1 disegna una freccia dal suo %2 a %3. - + %1 points from her %2 to %3. p1 female, p2 male %1 disegna una freccia dal suo %2 a %3. - + %1 points from his %2 to %3. p1 male, p2 female %1 disegna una freccia dal suo %2 a %3. - + %1 points from his %2 to %3. p1 male, p2 male %1 disegna una freccia dal suo %2 a %3. - + %1 points from %2's %3 to herself. card owner female, target female %1 disegna una freccia dal %3 di %2 a sé stessa. - + %1 points from %2's %3 to herself. card owner male, target female %1 disegna una freccia dal %3 di %2 a sé stessa. - + %1 points from %2's %3 to himself. card owner female, target male %1 disegna una freccia dal %3 di %2 a sé stesso. - + %1 points from %2's %3 to himself. card owner male, target male %1 disegna una freccia dal %3 di %2 a sé stesso. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from her %2 to her %3. female %1 disegna una freccia dal suo %2 al suo %3. - + %1 points from his %2 to his %3. male %1 disegna una freccia dal suo %2 al suo %3. - + %1 points from her %2 to %3's %4. p1 female, p2 female %1 disegna una freccia dal suo %2 al %4 di %3. - + %1 points from her %2 to %3's %4. p1 female, p2 male %1 disegna una freccia dal suo %2 al %4 di %3. - + %1 points from his %2 to %3's %4. p1 male, p2 female %1 disegna una freccia dal suo %2 al %4 di %3. - + %1 points from his %2 to %3's %4. p1 male, p2 male %1 disegna una freccia dal suo %2 al %4 di %3. - + %1 points from %2's %3 to her own %4. card owner female, target female %1 disegna una freccia dal %3 di %2 al proprio %4. - + %1 points from %2's %3 to her own %4. card owner male, target female %1 disegna una freccia dal %3 di %2 al proprio %4. - + %1 points from %2's %3 to his own %4. card owner female, target male %1 disegna una freccia dal %3 di %2 al proprio %4. - + %1 points from %2's %3 to his own %4. card owner male, target male %1 disegna una freccia dal %3 di %2 al proprio %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -2459,7 +3040,7 @@ La tua versione è la %1, la versione online è la %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -2468,7 +3049,7 @@ La tua versione è la %1, la versione online è la %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -2477,7 +3058,7 @@ La tua versione è la %1, la versione online è la %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -2486,7 +3067,7 @@ La tua versione è la %1, la versione online è la %2. - + red rosso @@ -2494,7 +3075,7 @@ La tua versione è la %1, la versione online è la %2. - + yellow giallo @@ -2502,7 +3083,7 @@ La tua versione è la %1, la versione online è la %2. - + green verde @@ -2510,326 +3091,376 @@ La tua versione è la %1, la versione online è la %2. - + %1 taps her permanents. female %1 tappa i suoi permanenti. - + %1 untaps her permanents. female %1 stappa i suoi permanenti. - + %1 taps his permanents. male %1 tappa i suoi permanenti. - + %1 untaps his permanents. male %1 stappa i suoi permanenti. - + %1 taps %2. female %1 tappa %2. - + %1 untaps %2. female %1 stappa %2. - - - %1 taps %2. - male - %1 tappa %2. - - - - %1 untaps %2. - male - %1 stappa %2. - - - - %1 sets counter %2 to %3 (%4%5). - female - %1 imposta i segnalini %2 a %3 (%4%5). - - - - %1 sets counter %2 to %3 (%4%5). - male - %1 imposta i segnalini %2 a %3 (%4%5). - - - - %1 sets %2 to not untap normally. - female - %1 imposta che %2 non stappa normalmente. - - - - %1 sets %2 to not untap normally. - male - %1 imposta che %2 non stappa normalmente. - - - - %1 sets %2 to untap normally. - female - %1 imposta che %2 stappa normalmente. - - - - %1 sets %2 to untap normally. - male - %1 imposta che %2 stappa normalmente. - - - - %1 sets PT of %2 to %3. - female - %1 imposta la FC di %2 a %3. - - - - %1 sets PT of %2 to %3. - male - %1 imposta la FC di %2 a %3. - - - - %1 sets annotation of %2 to %3. - female - %1 imposta le annotazioni di %2 con %3. - - - - %1 sets annotation of %2 to %3. - male - %1 imposta le annotazioni di %2 con %3. - - - - %1 is looking at the top %2 cards %3. - female - %1 sta guardando le prime %2 carte di %3. - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - %1 sta guardando le prime %2 carte di %3. + %1 tappa %2. - - %1 is looking at %2. - female - %1 sta guarda il %2. - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 sta guarda il %2. + %1 stappa %2. - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female - %1 non sta più guardando il %2. + %1 imposta i segnalini %2 a %3 (%4%5). + %1 sets counter %2 to %3 (%4%5). + male + %1 imposta i segnalini %2 a %3 (%4%5). + + + + %1 sets %2 to not untap normally. + female + %1 imposta che %2 non stappa normalmente. + + + + %1 sets %2 to not untap normally. + male + %1 imposta che %2 non stappa normalmente. + + + + %1 sets %2 to untap normally. + female + %1 imposta che %2 stappa normalmente. + + + + %1 sets %2 to untap normally. + male + %1 imposta che %2 stappa normalmente. + + + + %1 sets PT of %2 to %3. + female + %1 imposta la FC di %2 a %3. + + + + %1 sets PT of %2 to %3. + male + %1 imposta la FC di %2 a %3. + + + + %1 sets annotation of %2 to %3. + female + %1 imposta le annotazioni di %2 con %3. + + + + %1 sets annotation of %2 to %3. + male + %1 imposta le annotazioni di %2 con %3. + + + %1 is looking at the top %2 cards %3. + female + %1 sta guardando le prime %2 carte di %3. + + + %1 is looking at the top %2 cards %3. + male + %1 sta guardando le prime %2 carte di %3. + + + + %1 is looking at %2. + female + %1 sta guarda il %2. + + + + %1 is looking at %2. + male + %1 sta guarda il %2. + + + + %1 is looking at the top %n card(s) %2. + female + + %1 sta guardando le prime %n carte %2. + + + + + + %1 is looking at the top %n card(s) %2. + male + + %1 sta guardando le prime %2 carte di %3. + + + + + + %1 stops looking at %2. + female + %1 non sta più guardando il %2. + + + %1 stops looking at %2. male %1 non sta più guardando il %2. - - - %1 reveals %2 to %3. - p1 female, p2 female - %1 rivela %2 a %3. - - - - %1 reveals %2 to %3. - p1 female, p2 male - %1 rivela %2 a %3. - - - - %1 reveals %2 to %3. - p1 male, p2 female - %1 rivela %2 a %3. - - - - %1 reveals %2 to %3. - p1 male, p2 male - %1 rivela %2 a %3. - - - - %1 reveals %2. - female - %1 rivela %2. - - - - %1 reveals %2. - male - %1 rivela %2. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - %1 rivela a caso %2%3 a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - %1 rivela a caso %2%3 a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - %1 rivela a caso %2%3 a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - %1 rivela a caso %2%3 a %4. - - - - %1 randomly reveals %2%3. - female - %1 rivela a caso %2%3. - - - - %1 randomly reveals %2%3. - male - %1 rivela a caso %2%3. - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - %1 rivela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - %1 rivela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - %1 rivela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - %1 rivela %2%3 a %4. - - - - %1 reveals %2%3. - female - %1 rivela %2%3. - - - - %1 reveals %2%3. - male - %1 rivela %2%3. - - - - It is now %1's turn. - female - È il turno di %1. - + %1 reveals %2 to %3. + p1 female, p2 female + %1 rivela %2 a %3. + + + + %1 reveals %2 to %3. + p1 female, p2 male + %1 rivela %2 a %3. + + + + %1 reveals %2 to %3. + p1 male, p2 female + %1 rivela %2 a %3. + + + + %1 reveals %2 to %3. + p1 male, p2 male + %1 rivela %2 a %3. + + + + %1 reveals %2. + female + %1 rivela %2. + + + + %1 reveals %2. + male + %1 rivela %2. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + %1 rivela a caso %2%3 a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + %1 rivela a caso %2%3 a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + %1 rivela a caso %2%3 a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + %1 rivela a caso %2%3 a %4. + + + + %1 randomly reveals %2%3. + female + %1 rivela a caso %2%3. + + + + %1 randomly reveals %2%3. + male + %1 rivela a caso %2%3. + + + + %1 peeks at face down card #%2. + female + %1 ha sbirciato la faccia in giù della carta #%2. + + + + %1 peeks at face down card #%2. + male + %1 ha sbirciato la faccia in giù della carta #%2. + + + + %1 peeks at face down card #%2: %3. + female + %1 ha sbirciato la faccia in giù della carta #%2: %3. + + + + %1 peeks at face down card #%2: %3. + male + %1 ha sbirciato la faccia in giù della carta #%2: %3. + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + %1 rivela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + %1 rivela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + %1 rivela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + %1 rivela %2%3 a %4. + + + + %1 reveals %2%3. + female + %1 rivela %2%3. + + + + %1 reveals %2%3. + male + %1 rivela %2%3. + + + + %1 is now keeping the top card %2 revealed. + %1 sta tenendo la prima carta %2 rivelata. + + + + %1 is not revealing the top card %2 any longer. + %1 non sta più rivelando la prima carta%2. + + + + It is now %1's turn. + female + È il turno di %1. + + + It is now %1's turn. male È il turno di %1. - + untap step Interfase di stappo - + upkeep step Interfase di matenimento - + draw step Interfase di pescaggio - + first main phase Prima fase principale - + beginning of combat step Interfase di inizio combattimento - + declare attackers step Interfase di dichiarazione degli attaccanti - + declare blockers step Interfase di dichiarazione dei bloccanti - + combat damage step Interfase di risoluzione del danno da combattimento - + end of combat step Interfase di fine combattimento - + second main phase Seconda fase principale - + ending phase Fase finale - + It is now the %1. È adesso la %1. @@ -2837,22 +3468,22 @@ La tua versione è la %1, la versione online è la %2. MessagesSettingsPage - + Add message Aggiungi messaggio - + Message: Messaggio: - + &Add &Aggiungi - + &Remove &Rimuovi @@ -2860,57 +3491,57 @@ La tua versione è la %1, la versione online è la %2. PhasesToolbar - + Untap step Interfase di stappo - + Upkeep step Interfase di matenimento - + Draw step Interfase di pescaggio - + First main phase Prima fase principale - + Beginning of combat step Interfase di inizio combattimento - + Declare attackers step Interfase di dichiarazione degli attaccanti - + Declare blockers step Interfase di dichiarazione dei bloccanti - + Combat damage step Interfase di risoluzione del danno da combattimento - + End of combat step Interfase di fine combattimento - + Second main phase Seconda fase principale - + End of turn step Interfase di fine turno @@ -2918,322 +3549,536 @@ La tua versione è la %1, la versione online è la %2. Player - + &View graveyard &Guarda cimitero - + &View exile &Guarda zona di esilio - + Player "%1" Giocatore "%1" - + &Graveyard &Cimitero - + &Exile &Esilio - - - + + + Move to &top of library Metti in &cima al mazzo - - - + + + Move to &bottom of library Metti in &fondo al mazzo - - + + Move to &graveyard Metti nel &cimitero - - + + Move to &exile Metti in &esilio - - + + Move to &hand Prendi in &mano - + &View library &Guarda mazzo - + View &top cards of library... Guarda &le prime carte del mazzo... - + Reveal &library to Rivela il &mazzo a - + Reveal t&op card to Revela le p&rime carte a - + + &Always reveal top card + Rivela &sempre la prima carta + + + + O&pen deck in deck editor + A&pri mazzo nell'editore di mazzi + + + &View sideboard &Guarda la sideboard - + &Draw card &Pesca una carta - + D&raw cards... P&esca carte... - + &Undo last draw &Annulla l'ultima pescata - + Take &mulligan Mu&lliga - + &Shuffle &Mischia - + Move top cards to &graveyard... Metti le prime carte nel &cimitero... - + Move top cards to &exile... Metti le prime carte in &esilio... - + Put top card on &bottom Metti la prima carta in &fondo - + &Hand &Mano - + &Reveal to &Rivela a - + Reveal r&andom card to Rivela una carta a c&aso a - + &Sideboard &Sideboard - + &Library &Mazzo - + &Counters &Segnalini - + &Untap all permanents &Stappa tutti i permanenti - + R&oll die... L&ancia un dado... - + &Create token... &Crea una pedina... - + C&reate another token C&rea un'altra pedina - + + Cr&eate predefined token + Cr&a pedina predefinita + + + S&ay P&arla - + C&ard C&arta - + &All players &Tutti i giocatori - + + &Play + &Gioca + + + + &Hide + &Nascondi + + + + &Tap + &Tappa + + + + &Untap + &Stappa + + + + Toggle &normal untapping + Non &stappa normalmente + + + + &Flip + &Gira + + + + &Peek at card face + &Sbircia la faccia della carta + + + + &Clone + &Copia + + + Ctrl+H + Ctrl+H + + + + Ctrl+J + + + + + Attac&h to card... + Attacc&a alla carta... + + + + Ctrl+A + Ctrl+A + + + + Unattac&h + Stacc&a + + + + &Draw arrow... + &Disegna una freccia... + + + + &Increase power + &Aumenta forza + + + + Ctrl++ + Ctrl++ + + + + &Decrease power + &Diminuisci forza + + + + Ctrl+- + Ctrl+- + + + + I&ncrease toughness + A&umenta costituzione + + + + Alt++ + Alt++ + + + + D&ecrease toughness + D&iminuisci costituzione + + + + Alt+- + Alt+- + + + + In&crease power and toughness + Au&menta forza e costituzione + + + + Ctrl+Alt++ + Ctrl+Alt++ + + + + Dec&rease power and toughness + Dim&inuisci forza e costituzione + + + + Ctrl+Alt+- + Ctrl-Alt+- + + + + Set &power and toughness... + Imposta &forza e costituzione... + + + + Ctrl+P + Ctrl+P + + + + &Set annotation... + &Imposta annotazioni... + + + + red + rosso + + + + yellow + giallo + + + + green + verde + + + + &Add counter (%1) + &Aggiungi contatore (%1) + + + + &Remove counter (%1) + &Rimuovi contatore (%1) + + + + &Set counters (%1)... + &Imposta contatori (%1)... + + + + &top of library + &cima al grimorio + + + + &bottom of library + &fondo del grimorio + + + + &graveyard + &cimitero + + + + Ctrl+Del + Ctrl+Del + + + + &exile + &esilio + + + Ctrl+F3 Ctrl+F3 - + F3 F3 - + Ctrl+W Ctrl+W - + F4 F4 - + Ctrl+D Ctrl+D - + Ctrl+E Ctrl+E - + Ctrl+Shift+D Ctrl+Shift+D - + Ctrl+M Ctrl+M - + Ctrl+S Ctrl+S - + Ctrl+U Ctrl+U - + Ctrl+I Ctrl+I - + Ctrl+T Ctrl+T - + Ctrl+G Ctrl+G - + View top cards of library Guarda le prima carte del grimorio - + Number of cards: Numero di carte: - + Draw cards Pesca carte - - - - + + + + Number: Numero: - + Move top cards to grave Metti le prima carte nel cimitero - + Move top cards to exile Metti le prime carte in esilio - + Roll die Lancia un dado - + Number of sides: Numero di facce: - + Set power/toughness Imposta forza/costituzione - + Please enter the new PT: Inserisci la nuova FC: - + Set annotation Imposta annotazione - + Please enter the new annotation: Inserisci le nuove annotazioni: - + Set counters Imposta i segnalini @@ -3249,116 +4094,148 @@ La tua versione è la %1, la versione online è la %2. deck #%1 - User &details - &Dettagli utente + &Dettagli utente - Direct &chat - &Chat diretta + &Chat diretta - Add to &buddy list - Aggiungi alla lista &amici + Aggiungi alla lista &amici - Remove from &buddy list - Rimuovi dalla lista &amici + Rimuovi dalla lista &amici - Add to &ignore list - Aggiungi alla lista &ignorati + Aggiungi alla lista &ignorati - Remove from &ignore list - Rimuovi dalla lista &ignorati + Rimuovi dalla lista &ignorati - Kick from &game - Caccia dalla &partita + Caccia dalla &partita QObject - Maindeck - Grimorio + Grimorio - Sideboard - Sideboard + Sideboard - Cockatrice decks (*.cod) - Cockatrice decks (*.cod) + Mazzi Cockatrice (*.cod) - Plain text decks (*.dec *.mwDeck) - Testo di mazzo + Mazzi in formato testo (*.dec *.mwDeck) - + + Common deck formats (*.cod *.dec *.mwDeck) + + + + All files (*.*) Tutti i files (*.*) + + + Cockatrice replays (*.cor) + Replay di Cockatrice (*.cor) + RemoteDeckList_TreeModel - + Name Nome - + ID ID - + Upload time Tempo di upload - RoomSelector + RemoteReplayList_TreeModel - - Rooms - Stanze + + ID + ID - - Joi&n - E&ntra + + Name + Nome - - Room - Stanza - - - - Description - Descrizione - - - + Players Giocatori - + + Keep + Tieni + + + + Time started + Inizio tempo + + + + Duration (sec) + Durata (sec) + + + + RoomSelector + + + Rooms + Stanze + + + + Joi&n + E&ntra + + + + Room + Stanza + + + + Description + Descrizione + + + + Players + Giocatori + + + Games Partite @@ -3379,27 +4256,25 @@ La tua versione è la %1, la versione online è la %2. ShutdownDialog - + &Reason for shutdown: &Ragione dello spegnimento: - + &Time until shutdown (minutes): &Tempo allo spegnimento (minuti): - &OK - &OK + &OK - &Cancel - &Annulla + &Annulla - + Shut down server Spegni il server @@ -3407,246 +4282,506 @@ La tua versione è la %1, la versione online è la %2. TabAdmin - + Update server &message Aggiorna &messaggio del server - + &Shut down server &Spegni il server - + Server administration functions Funzioni da amministratore del server - + &Unlock functions &Sblocca funzioni - + &Lock functions &Blocca funzioni - + Unlock administration functions Sblocca le funzioni da amministratore - + Do you really want to unlock the administration functions? Vuoi veramente sbloccare le funzioni da amministratore? - Administration - Amministrazione + Amministrazione + + + + TabDeckEditor + + &Search... + &Cerca... + + + + Show card text only + + + + + &Clear search + &Annulla ricerca + + + + &Search for: + &Cerca per: + + + + Deck &name: + &Nome mazzo: + + + + &Comments: + &Commenti: + + + + Hash: + Hash: + + + + &Update prices + &Aggiorna prezzo + + + + Ctrl+U + Ctrl+U + + + + &New deck + &Nuovo mazzo + + + + &Load deck... + &Carica mazzo... + + + + &Save deck + &Salva mazzo + + + + Save deck &as... + Salva mazzo &con nome... + + + + Load deck from cl&ipboard... + Carica mazzo dagli app&unti... + + + + Save deck to clip&board + Salva mazzo nei app&unti + + + + &Print deck... + &Stampa mazzo... + + + + &Analyze deck on deckstats.net + &Analizza il mazzo con deckstats.net + + + + &Close + &Chiudi + + + + Ctrl+Q + Ctrl+Q + + + + Add card to &maindeck + Aggiungi carta al &grimorio + + + Return + Return + + + Enter + Invio + + + + Add card to &sideboard + Aggiungi carta al &sideboard + + + Ctrl+Return + Ctrl+Return + + + Ctrl+Enter + Ctrl+Invio + + + + &Remove row + &Rimuovi carta + + + + Del + Elimina + + + + &Increment number + &Aumenta il numero + + + + + + + + + + + &Decrement number + &Diminuisci il numero + + + + - + - + + + + &Deck editor + &Editore di mazzi + + + + C&ard database + Database delle C&arte + + + + &Edit sets... + &Modifica impostazioni... + + + + Edit &tokens... + Modifica &pedine... + + + + Deck: %1 + Mazzo: %1 + + + + Are you sure? + Sei sicuro? + + + + The decklist has been modified. +Do you want to save the changes? + La lista del mazzo è stata modificata. +Vuoi salvare i cambiamenti? + + + + Load deck + Carica mazzo + + + + + + Error + Errore + + + + The deck could not be saved. + Il mazzo non può essere salvato. + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + Il mazzo non può essere salvato. +Controlla se la cartella è valida e prova ancora. + + + + Save deck + Salva mazzo TabDeckStorage - + Local file system File locali - + Server deck storage Mazzi su server - - + + Open in deck editor Apri nell'editore di mazzi - + Upload deck Upload mazzo - + Download deck Scarica mazzo - - + + New folder Nuova cartella - + + Delete Elimina - + Enter deck name Metti il nome del mazzo - + This decklist does not have a name. Please enter a name: Questo mazzo non ha un nome. Per favore inserisci un nome: - + Unnamed deck Mazzo senzanome - + + Delete local file + Elimina file locali + + + + + + Are you sure you want to delete "%1"? + Sei sicuro di voler eliminare %1? + + + Name of new folder: Nome della nuova cartella: - + + Delete remote folder + + + + + Delete remote deck + Elimina deck remoto + + Deck storage - Cartella mazzi + Cartella mazzi TabGame - + F5 F5 - + F6 F6 - + F7 F7 - + F8 F8 - + F9 F9 - + F10 F10 - + &Phases &Fase - + &Game &Partita - + Next &phase Prossima &fase - + Ctrl+Space Ctrl+Spazio - + + Tab + + + + Next &turn Prossimo &turno - + Ctrl+Return Ctrl+Return - + Ctrl+Enter Ctrl+Invio - + &Remove all local arrows &Rimuovi tutte le frecce - + Ctrl+R Ctrl+R - + + Game &information + &Informazioni partita + + + &Concede &Concedi - + F2 F2 - + &Leave game &Lascia partita - + + Ctrl+Q Ctrl+Q - + + C&lose replay + C&hiudi replay + + + &Say: &Parla: - + Concede Concedi - + Are you sure you want to concede this game? Vuoi veramente concedere la partita? - + Leave game Lascia la partita - + Are you sure you want to leave this game? Sei sicuro di voler lasciare la partita? - + + Replay %1: %2 + Replay %1: %2 + + Kicked - Caccia + Caccia - You have been kicked out of the game. - Sei stato cacciato dalla partita. + Sei stato cacciato dalla partita. - + Game %1: %2 Partita %1: %2 @@ -3654,60 +4789,124 @@ Please enter a name: TabMessage - + Personal &talk Chat &personale - + &Leave &Lascia - + This user is ignoring you. Questo utente ti sta ignorando. - + %1 has left the server. %1 è uscito dal server. - + %1 has joined the server. %1 è entrato nel server. - + Talking to %1 Parla con %1 + + TabReplays + + + Local file system + File locali + + + + Server replay storage + Replay sul server + + + + + Watch replay + Visualizza replay + + + + + Delete + Elimina + + + + Download replay + Download replay + + + + Toggle expiration lock + Scadenza blocco Toggle + + + + Delete local file + Elimina i file locali + + + + Are you sure you want to delete "%1"? + Sei sicuro di voler eliminare "%1"? + + + + Delete remote replay + Elimina replay remoto + + + + Are you sure you want to delete the replay of game %1? + Sei sicuro di voler eliminare il replay della partita %1? + + + Game replays + Replay partite + + TabRoom - + &Say: &Parla: - + Chat Chat - + &Room &Stanza - + &Leave room &Lascia stanza - + + &Ignore unregistered users in chat + &Ignore utenti non registrati in chat + + + You are flooding the chat. Please wait a couple of seconds. Stai spammando la chat. Attendi un paio di secondi. @@ -3715,63 +4914,124 @@ Please enter a name: TabServer - Server - Server + Server TabUserLists - User lists - Lista utenti + Lista utenti + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + &Dettagli utente + + + + Direct &chat + &Chat diretta + + + + Show this user's &games + Visualizza le &partite dell'utente + + + + Add to &buddy list + Aggiungi alla lista &amici + + + + Remove from &buddy list + Rimuovi dalla lista &amici + + + + Add to &ignore list + Aggiungi alla lista &ignorati + + + + Remove from &ignore list + Rimuovi dalla lista &ignorati + + + + Kick from &game + Caccia dalla &partita + + + + Ban from &server + Banna dal &server + + + + %1's games + Partite di %1 UserInfoBox - + User information Informazioni utente - + Real name: Nome reale: - + Gender: Sesso: - + Location: Posizione: - + User level: Livello utente: - + Administrator Amministratore - + Moderator Moderatore - + Registered user Utente registrato - + Unregistered user Utente non registrato @@ -3779,37 +5039,47 @@ Please enter a name: UserInterfaceSettingsPage - + General interface settings Impostazioni di interfaccia generale - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) &Doppio click sulle carte per giocarle (disabilitato un solo click) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings Impostazioni di animazione - + &Tap/untap animation Animazioni &Tappa/Stappa - + Enable &sounds Attiva &suoni - + Path to sounds directory: Percorso cartella suoni: - + Choose path Seleziona percorso @@ -3817,273 +5087,219 @@ Please enter a name: UserList - + Users online: %1 Utenti online: %1 - + Users in this room: %1 Utenti in questa stanza: %1 - + Buddies online: %1 / %2 Amici online: %1 / %2 - + Ignored users online: %1 / %2 Utenti ignorati online: %1 / %2 - %1's games - Partite di %1 + Partite di %1 - User &details - Dettagli &utente + Dettagli &utente - Direct &chat - Chat &diretta + Chat &diretta - Show this user's &games - Visualizza le &partite dell'utente + Visualizza le &partite dell'utente - Add to &buddy list - Aggiungi alla lista &amici + Aggiungi alla lista &amici - Remove from &buddy list - Rimuovi dalla lista &amici + Rimuovi dalla lista &amici - Add to &ignore list - Aggiungi alla lista &ignorati + Aggiungi alla lista &ignorati - Remove from &ignore list - Rimuovi dalla lista &ignorati + Rimuovi dalla lista &ignorati - Ban from &server - Banna dal &server + Banna dal &server WndDeckEditor - &Search... - &Cerca... + &Cerca... - &Clear search - &Annulla ricerca + &Annulla ricerca - &Search for: - &Cerca per: + &Cerca per: - Deck &name: - &Nome mazzo: + &Nome mazzo: - &Comments: - &Commenti: + &Commenti: - - Hash: - - - - &Update prices - &Aggiorna prezzo + &Aggiorna prezzo - Ctrl+U - Ctrl+U + Ctrl+U - Deck editor [*] - Editore di mazzi [*] + Editore di mazzi [*] - &New deck - &Nuovo mazzo + &Nuovo mazzo - &Load deck... - &Carica mazzo... + &Carica mazzo... - &Save deck - &Salva mazzo + &Salva mazzo - Save deck &as... - Salva mazzo &con nome... + Salva mazzo &con nome... - Load deck from cl&ipboard... - Carica mazzo dagli app&unti... + Carica mazzo dagli app&unti... - Save deck to clip&board - Salva mazzo nei app&unti + Salva mazzo nei app&unti - &Print deck... - &Stampa mazzo... + &Stampa mazzo... - &Close - &Chiudi + &Chiudi - Ctrl+Q - Ctrl+Q + Ctrl+Q - &Edit sets... - &Modifica impostazioni... + &Modifica impostazioni... - &Deck - &Mazzo + &Mazzo - &Card database - &Database delle carte + &Database delle carte - Add card to &maindeck - Aggiungi carta al &grimorio + Aggiungi carta al &grimorio - Return - Return + Return - Enter - Invio + Invio - Add card to &sideboard - Aggiungi carta al &sideboard + Aggiungi carta al &sideboard - Ctrl+Return - Ctrl+Return + Ctrl+Return - Ctrl+Enter - Ctrl+Invio + Ctrl+Invio - &Remove row - &Rimuovi carta + &Rimuovi carta - Del - Elimina + Elimina - &Increment number - &Aumenta il numero + &Aumenta il numero - + - + + + - &Decrement number - &Diminuisci il numero + &Diminuisci il numero - - - - + - - Are you sure? - Sei sicuro? + Sei sicuro? - The decklist has been modified. Do you want to save the changes? - La lista del mazzo è stata modificata. + La lista del mazzo è stata modificata. Vuoi salvare i cambiamenti? - Load deck - Carica mazzo + Carica mazzo - - Error - Errore + Errore - - The deck could not be saved. Please check that the directory is writable and try again. - Il mazzo non può essere salvato. + Il mazzo non può essere salvato. Controlla se la cartella è valida e prova ancora. - Save deck - Salva mazzo + Salva mazzo @@ -4097,17 +5313,17 @@ Controlla se la cartella è valida e prova ancora. ZoneViewWidget - + sort by name ordina per nome - + sort by type ordina per tipo - + shuffle when closing mescola quando chiudi diff --git a/cockatrice/translations/cockatrice_ja.ts b/cockatrice/translations/cockatrice_ja.ts index 039c874a2..a3e9da12d 100644 --- a/cockatrice/translations/cockatrice_ja.ts +++ b/cockatrice/translations/cockatrice_ja.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... カウンターの数を決める... - + Ctrl+L - + F11 - + F12 - + Set counter カウンターの設定 - + New value for counter '%1': カウンター '%1'の新しい値を設定する: @@ -37,58 +37,53 @@ AppearanceSettingsPage - + Zone background pictures 背景画像の設定 - Path to hand background: - 手札の背景画像へのパス: + 手札の背景画像へのパス: - Path to stack background: - スタックゾーンの背景画像へのパス: + スタックゾーンの背景画像へのパス: - Path to table background: - テーブルの背景画像へのパス: + テーブルの背景画像へのパス: - Path to player info background: - プレイヤー画像へのパス: + プレイヤー画像へのパス: - Path to picture of card back: - カード背面画像へのパス: + カード背面画像へのパス: - + Card rendering カードレンダリング - + Display card names on cards having a picture やや不明 画像持ちカードのカードネームを表示する - + Hand layout 手札のレイアウト - + Display hand horizontally (wastes space) 手札を横に並べる(スペースを消費します) - + Table grid layout テーブルグリッドのレイアウト @@ -97,36 +92,61 @@ 省スペースレイアウト(カード間間隔を細かくできます) - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Invert vertical coordinate 垂直反転調整 - + Minimum player count for multi-column layout: - + Zone view layout ゾーンビューレイアウト - + Sort by name 名前で整列 - + Sort by type タイプで整列 - - - - - + + + + + Choose path 画像の指定 @@ -139,358 +159,317 @@ Enter 0 for an indefinite ban. バンする期間を入力してください(分単位).0でバンを解除します. - + ban &user name - + ユーザー名でBAN - + ban &IP address - + IPアドレスでBAN - + Ban type - + BANタイプ - + &permanent ban - + - + &temporary ban - - - - - &Days: - + - &Hours: - + &Days: + + &Hours: + + + + &Minutes: - + - + Duration of the ban - + BAN期間 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Banの理由を入れてください.これはモデレーターによって保存されBanされた人間には見えません. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK OK - + &Cancel Cancel - + Ban user from server サーバーからBanされたユーザーです - + Error - エラー + エラー - + You have to select a name-based or IP-based ban, or both. - + 名前によるBANかIPによるBAN、もしくは両方を選択してください CardDatabaseModel - + Name カード名 - + Sets エクスパンション - + Mana cost マナコスト - + Card type カードタイプ - + P/T - CardInfoWidget + CardInfoText - - Hide card info - カードインフォを隠す + + Name: + カード名: - + + Mana cost: + マナコスト: + + + + Card type: + カードタイプ: + + + + P / T: + + + + + Loyalty: + 忠誠値: + + + + CardInfoWidget + + Hide card info + カードインフォを隠す + + + Show card only カードのみ表示 - + Show text only テキストのみ表示 - + Show full info 全て表示 - + Name: カード名: - + Mana cost: マナコスト: - + Card type: カードタイプ: - + P / T: + + + Loyalty: + 忠誠値: + CardItem - &Play - プレイ + プレイ - &Hide テスト版のため確認取れず再度チェック - 裏にしてプレイ + 裏にしてプレイ - &Tap - タップ + タップ - &Untap - アンタップ + アンタップ - Toggle &normal untapping - 通常のアンタップをしない + 通常のアンタップをしない - &Flip - 裏にする + 裏にする - &Clone - 複製する + 複製する - - Ctrl+H - - - - &Attach to card... - カードに付ける... + カードに付ける... - - Ctrl+A - - - - Unattac&h - 取り外す + 取り外す Set &P/T... P/Tを決める... - &Draw arrow... テストしていないので要修正 - 矢印を指定 + 矢印を指定 - + &Power / toughness パワー / タフネス - &Increase power - パワーを上げる + パワーを上げる - - Ctrl++ - - - - &Decrease power - パワーを下げる + パワーを下げる - - Ctrl+- - - - - I&ncrease toughness - タフネスを上げる + タフネスを上げる - - Alt++ - - - - D&ecrease toughness - タフネスを下げる + タフネスを下げる - - Alt+- - - - - In&crease power and toughness - パワーとタフネスを上げる + パワーとタフネスを上げる - - Ctrl+Alt++ - - - - Dec&rease power and toughness - パワーとタフネスを下げる + パワーとタフネスを下げる - - Ctrl+Alt+- - - - - Set &power and toughness... - パワーとタフネスを設定する... + パワーとタフネスを設定する... - - Ctrl+P - - - - &Set annotation... - 注釈を付ける... + 注釈を付ける... - red - 赤 + 赤 - yellow - 黄 + 黄 - green - 緑 + 緑 - &Add counter (%1) - カウンターを乗せる (%1) + カウンターを乗せる (%1) - &Remove counter (%1) - カウンターを取り除く (%1) + カウンターを取り除く (%1) - &Set counters (%1)... - カウンターの数を決める (%1)... + カウンターの数を決める (%1)... - &top of library - ライブラリーの一番上へ + ライブラリーの一番上へ - &bottom of library - ライブラリーの一番下へ + ライブラリーの一番下へ - &graveyard - 墓地へ + 墓地へ - - Ctrl+Del - - - - &exile - 追放領域へ + 追放領域へ - + &Move to 移動させる @@ -498,365 +477,245 @@ This is only saved for moderators and cannot be seen by the banned person. CardZone - + her hand nominative, female owner - + %1's hand nominative, female owner - + his hand nominative, male owner - - - %1's hand - nominative, male owner - - - - - of her hand - genitive, female owner - - - - - of %1's hand - genitive, female owner - - - - - of his hand - genitive, male owner - - - - - of %1's hand - genitive, male owner - - - - - her hand - accusative, female owner - - - - - %1's hand - accusative, female owner - - - - - his hand - accusative, male owner - - - - - %1's hand - accusative, male owner - - + %1's hand + nominative, male owner + + + + her library - nominative, female owner - + look at zone, female owner + - + %1's library - nominative, female owner - + look at zone, female owner + - + his library - nominative, male owner - + look at zone, male owner + - + %1's library - nominative, male owner - + look at zone, male owner + - + of her library - genitive, female owner - + top cards of zone, female owner + - + of %1's library - genitive, female owner - + top cards of zone, female owner + - + of his library - genitive, male owner - + top cards of zone, male owner + - + of %1's library - genitive, male owner - + top cards of zone, male owner + - + her library - accusative, female owner - + reveal zone, female owner + - + %1's library - accusative, female owner - + reveal zone, female owner + - + his library - accusative, male owner - + reveal zone, male owner + - + %1's library - accusative, male owner + reveal zone, male owner + + + + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + + + + + his library + shuffle, male owner + + + + + %1's library + shuffle, male owner + + + + + her sideboard + look at zone, female owner + + + + + %1's sideboard + look at zone, female owner + + + + + his sideboard + look at zone, male owner + + + + + %1's sideboard + look at zone, male owner + + + + + her library + nominative, female owner - + + %1's library + nominative, female owner + + + + + his library + nominative, male owner + + + + + %1's library + nominative, male owner + + + + her graveyard nominative, female owner - + %1's graveyard nominative, female owner - + his graveyard nominative, male owner - + %1's graveyard nominative, male owner - - of her graveyard - genitive, female owner - - - - - of %1's graveyard - genitive, female owner - - - - - of his graveyard - genitive, male owner - - - - - of %1's graveyard - genitive, male owner - - - - - her graveyard - accusative, female owner - - - - - %1's graveyard - accusative, female owner - - - - - his graveyard - accusative, male owner - - - - - %1's graveyard - accusative, male owner - - - - + her exile nominative, female owner - + %1's exile nominative, female owner - + his exile nominative, male owner - + %1's exile nominative, male owner - - of her exile - genitive, female owner - - - - - of %1's exile - genitive, female owner - - - - - of his exile - genitive, male owner - - - - - of %1's exile - genitive, male owner - - - - - her exile - accusative, female owner - - - - - %1's exile - accusative, female owner - - - - - his exile - accusative, male owner - - - - - %1's exile - accusative, male owner - - - - + her sideboard nominative, female owner - + %1's sideboard nominative, female owner - + his sideboard nominative, male owner - + %1's sideboard nominative, male owner - - - of her sideboard - genitive, female owner - - - - - of %1's sideboard - genitive, female owner - - - - - of his sideboard - genitive, male owner - - - - - of %1's sideboard - genitive, male owner - - - - - her sideboard - accusative, female owner - - - - - %1's sideboard - accusative, female owner - - - - - his sideboard - accusative, male owner - - - - - %1's sideboard - accusative, male owner - - ChatChannelSelector @@ -897,14 +756,49 @@ This is only saved for moderators and cannot be seen by the banned person. - DeckEditorSettingsPage + DBPriceUpdater - - Enable &price tag feature (using data from blacklotusproject.com) - 価格タグを表示可能に(blacklotusproject.comからのデータを使用) + + + + Error + エラー - + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + Enable &price tag feature (using data from blacklotusproject.com) + 価格タグを表示可能に(blacklotusproject.comからのデータを使用) + + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General 全般 @@ -912,43 +806,77 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Number カード枚数 - + Card カード名 - + Price 価格 + + DeckStatsInterface + + + + Error + エラー + + + + The reply from the server could not be parsed. + + + DeckViewContainer - + Load &local deck ローカルからデッキをロード - + Load d&eck from server サーバーからデッキをロード - + Ready to s&tart 開始準備完了 - + + S&ideboard unlocked + + + + + S&ideboard locked + + + + Load deck デッキをロード + + + Error + エラー + + + + The selected file could not be loaded. + + DlgCardSearch @@ -991,34 +919,29 @@ This is only saved for moderators and cannot be seen by the banned person. DlgConnect - + &Host: ホストIP: - + &Port: ポート: - + Player &name: プレイヤーネーム: - + P&assword: パスワード: - - &OK - - - - - &Cancel - + + &Save password + @@ -1029,87 +952,82 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + &Description: 説明: - + &Password: パスワード: - + P&layers: プレイヤー人数: - + Game type ゲームタイプ - + Only &buddies can join フレンドのみ参加可能 - + Only &registered users can join 登録済みプレイヤーのみ参加可能 - + Joining restrictions 参加制限 - + &Spectators allowed 観戦者を許可する - + Spectators &need a password to join 観戦者は参加にパスワードが必要 - + Spectators can &chat 観戦者はチャットに参加できる - + Spectators see &everything 観戦者は全て見れる - + Spectators 観戦者 - - &OK - - - - - &Cancel - - - - + Create game 部屋を作る - + + Game information + + + + Error エラー - + Server error. サーバーエラー. @@ -1117,115 +1035,257 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: トークン名: - + Token トークン - + C&olor: 色: - + white 白 - + blue 青 - + black 黒 - + red 赤 - + green 緑 - + multicolor 多色 - + colorless 無色 - + &P/T: - + &Annotation: 補足: - + &Destroy token when it leaves the table トークンがテーブルを離れる場合破壊する - - &OK - + + Token data + - - &Cancel - + + Show &all tokens + - + + Show tokens from this &deck + + + + + Choose token from list + + + + Create token トークンを作成 + + DlgEditTokens + + + &Name: + トークン名: + + + + C&olor: + 色: + + + + white + 白 + + + + blue + 青 + + + + black + 黒 + + + + red + 赤 + + + + green + 緑 + + + + multicolor + 多色 + + + + colorless + 無色 + + + + &P/T: + + + + + &Annotation: + 補足: + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + エラー + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + + Filter games + + + DlgLoadDeckFromClipboard - + &Refresh - - &OK - - - - - &Cancel - - - - + Load deck from clipboard クリップボードからデッキをロード - + Error エラー - + Invalid deck list. 無効なデッキリストです. @@ -1233,17 +1293,7 @@ This is only saved for moderators and cannot be seen by the banned person. DlgLoadRemoteDeck - - O&K - - - - - &Cancel - - - - + Load deck デッキをロード @@ -1251,158 +1301,229 @@ This is only saved for moderators and cannot be seen by the banned person. DlgSettings - - - + + + Error エラー - Your card database is invalid. Would you like to go back and set the correct path? - あなたのカードデータベースは無効です.前に戻って正しいパスを設定してください. + あなたのカードデータベースは無効です.前に戻って正しいパスを設定してください. - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? あなたのデッキディレクトリへのパスは無効です.前に戻って正しいパスを設定してください. - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? あなたのカード画像ディレクトリへのパスは無効です.前に戻って正しいパスを設定してください. - + Settings 設定 - + General 全般 - + Appearance 外観 - + User interface ユーザーインターフェース - + Deck editor デッキエディター - + Messages メッセージ - - - &Close - - GameSelector - + C&reate 部屋を作る - + &Join 参加する - - - - - - - - + + + + + + + + + Error エラー - + Please join the appropriate room first. 適切な部屋に参加してください. - + Wrong password. パスワードが間違っています. - + Spectators are not allowed in this game. この試合は観戦者は許可されていません. - + The game is already full. このゲームはすでに満員です. - + The game does not exist any more. このゲームはもう存在しません. - + This game is only open to registered users. このゲームは登録済みプレイヤーにのみ公開されています. - + This game is only open to its creator's buddies. このゲームは作成者のフレンドのみに公開されています. - + You are being ignored by the creator of this game. あなたはこのゲームの作成者によって拒否されています. - + Join game 参加 - + Password: パスワード: - + + Please join the respective room first. + + + + Games ゲーム - - Show &full games - 全てのゲームを見る + + &Filter games + + + + + C&lear filter + + + + Show u&navailable games + プレイ不可のゲームを見る + + + Show &full games + 全てのゲームを見る - Show &running games - 進行中のゲームを見る + 進行中のゲームを見る &Show full games 全てのゲームを見る - + J&oin as spectator 観戦者として参加 @@ -1418,72 +1539,72 @@ This is only saved for moderators and cannot be seen by the banned person. GamesModel - + yes あり - + no なし - + Creator 作成者 - + Description 説明 - + yes, free for spectators あり,観戦は自由 - + buddies only フレンドのみ - + reg. users only 登録済みユーザーのみ - + not allowed 不許可 - + Room 部屋 - + Game type ゲームタイプ - + Password パスワード - + Restrictions 制限 - + Players プレイヤー - + Spectators 観戦者 @@ -1491,50 +1612,96 @@ This is only saved for moderators and cannot be seen by the banned person. GeneralSettingsPage - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path パスを選択 - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + エラー + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings 個人設定 - + Language: 言語: - + Download card pictures on the fly - + + Download high-quality card pictures + + + + Paths パス - + Decks directory: デッキディレクトリ: - + + Replays directory: + + + + Pictures directory: カード画像のディレクトリ: - - Path to card database: - カードデータベースのパス: + + Card database: + - - + + Token database: + + + + Path to card database: + カードデータベースのパス: + + + + English Japanese @@ -1542,45 +1709,43 @@ This is only saved for moderators and cannot be seen by the banned person. MainWindow - + There are too many concurrent connections from your address. あなたのアドレスからあまりにも多くの接続があります. - Banned by moderator. 少し訳が怪しい - 管理者によってバンされた. + 管理者によってバンされた. - + Scheduled server shutdown. サーバーシャットダウン予定時間. - Unknown reason. - 不明な理由. + 不明な理由. - + Connection closed 通信切断 - + The server has terminated your connection. Reason: %1 サーバーはあなたの接続を切断しました. 理由: %1 - + Scheduled server shutdown サーバーシャットダウン予定時間 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1591,208 +1756,276 @@ Reason for shutdown: %1 - + Number of players プレイヤー人数 - + Please enter the number of players. プレイヤーの人数を入れてください. - - + + Player %1 プレイヤー %1 - + About Cockatrice Cockatriceについて - + Version %1 - + Authors: 著者: - + Translators: 翻訳者: - + Spanish: スペイン語: - + Portugese (Portugal): ポルトガル語(ポルトガル): - + Portugese (Brazil): ポルトガル語(ブラジル): - + French: フランス語: - + Japanese: 日本語: - + Russian: ロシア語: - Czech: - チェコ語: + チェコ語: Slovak: スロバキア語: - - Italian: + + Banned by moderator - - - - - - + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + + Load replay + + + + + Italian: + イタリア語: + + + + Swedish: + + + + + + + + + + + + + Error エラー - + Server timeout サーバータイムアウト - Invalid login data. - 無効なログインデータです. + 無効なログインデータです. - + + Incorrect username or password. Please check your authentication information and try again. + + + + There is already an active session using this user name. Please close that session first and re-login. これはすでにこのユーザー名で使われているアクティブなセッションです. まずこのセッションを閉じてログインしなおしてください. - + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + This server requires user registration. + + + + + Unknown login error: %1 + + + + Socket error: %1 ソケットエラー: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. あなたは古いVerのサーバーに接続しようとしています.CockatriceのVerをダウングレードするか適正なサーバーに接続してください. ローカルVer %1,リモートVer %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. あなたのCockatriceのVerが古いです.Cockatriceをアップデートしてください. ローカルVer %1,リモートVer %2. - + Connecting to %1... %1へ接続しています... - + Disconnected 切断されました - + + Connected, logging in at %1 + + + + Logged in at %1 %1にログイン中 - + &Connect... 接続... - + &Disconnect 切断 - + Start &local game... ローカルゲームを開始... - + + &Watch replay... + + + + &Deck editor デッキエディター - + &Full screen フルスクリーン - + Ctrl+F - + &Settings... 設定... - + &Exit 終了 - + &Cockatrice - + &About Cockatrice - + &Help ヘルプ - + Are you sure? よろしいですか? - + There are still open games. Are you sure you want to quit? ゲームがまだ開いています.本当に退出しますか? @@ -1800,52 +2033,52 @@ Local version is %1, remote version is %2. MessageLogWidget - + The game has been closed. - + %1 is now watching the game. - + %1 is not watching the game any more. - + You have joined game #%1. female あなたはゲーム#%1に参加しました. - - - You have joined game #%1. - male - あなたはゲーム#%1に参加しました. - - - - %1 has joined the game. - female - %1がゲームに参加しました. - - - - %1 has joined the game. - male - %1がゲームに参加しました. - + You have joined game #%1. + male + あなたはゲーム#%1に参加しました. + + + + %1 has joined the game. + female + %1がゲームに参加しました. + + + + %1 has joined the game. + male + %1がゲームに参加しました. + + + %1 has left the game. female %1はゲームから抜けました. - + %1 has left the game. male %1はゲームから抜けました. @@ -1870,93 +2103,117 @@ Local version is %1, remote version is %2. male %1はデッキ#%2をロードしました. - - - %1 is ready to start the game. - female - %1はゲーム開始の準備が出来ました. - - - - %1 is ready to start the game. - male - %1はゲーム開始の準備が出来ました. - - - - %1 is not ready to start the game any more. - female - %1はゲーム開始の準備がまだ出来ていません. - - - - %1 is not ready to start the game any more. - male - %1はゲーム開始の準備がまだ出来ていません. - - - - %1 has conceded the game. - female - %1が投了しました. - - - - %1 has conceded the game. - male - %1が投了しました. - - - - %1 has restored connection to the game. - female - %1がこのゲームに再接続しました. - + %1 is ready to start the game. + female + %1はゲーム開始の準備が出来ました. + + + + %1 is ready to start the game. + male + %1はゲーム開始の準備が出来ました. + + + + %1 is not ready to start the game any more. + female + %1はゲーム開始の準備がまだ出来ていません. + + + + %1 is not ready to start the game any more. + male + %1はゲーム開始の準備がまだ出来ていません. + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + %1 has conceded the game. + female + %1が投了しました. + + + + %1 has conceded the game. + male + %1が投了しました. + + + + %1 has restored connection to the game. + female + %1がこのゲームに再接続しました. + + + %1 has restored connection to the game. male %1がこのゲームに再接続しました. - + %1 has lost connection to the game. female %1はこのゲームから切断されました. - + %1 has lost connection to the game. male %1はこのゲームから切断されました. - + %1 shuffles %2. female %1は%2をシャッフルします. - + %1 shuffles %2. male %1は%2をシャッフルします. - + %1 rolls a %2 with a %3-sided die. female ちょっと怪しいです 要チェック %1は%3面ダイスを使用して%2を割り振ります. - + %1 rolls a %2 with a %3-sided die. male %1は%3面ダイスを使用して%2を割り振ります. - + %1 draws %n card(s). female @@ -1964,7 +2221,7 @@ Local version is %1, remote version is %2. - + %1 draws %n card(s). male @@ -1972,182 +2229,207 @@ Local version is %1, remote version is %2. - + %1 undoes his last draw. %1は最後のドローを取り消しました. - + %1 undoes her last draw. %1は最後のドローを取り消しました. - + %1 undoes his last draw (%2). %1は最後のドローを取り消しました(%2). - + %1 undoes her last draw (%2). %1は最後のドローを取り消しました(%2). - + from table - + from graveyard - + from exile - + from hand - + + the bottom card of %1's library + + + + the bottom card of his library - + the bottom card of her library - + + from the bottom of %1's library + + + + from the bottom of his library - + from the bottom of her library - + + the top card of %1's library + + + + the top card of his library - + the top card of her library - + + from the top of %1's library + + + + from the top of his library - + from the top of her library - + + from %1's library + + + + from library - + from sideboard - + from the stack - + %1 gives %2 control over %3. - + %1 puts %2 into play tapped%3. - + %1 puts %2 into play%3. - + %1 puts %2%3 into graveyard. - + %1 exiles %2%3. - + %1 moves %2%3 to hand. - + %1 puts %2%3 into his library. - + %1 puts %2%3 into her library. - + %1 puts %2%3 on bottom of his library. - + %1 puts %2%3 on bottom of her library. - + %1 puts %2%3 on top of his library. - + %1 puts %2%3 on top of her library. - + %1 puts %2%3 into his library at position %4. - + %1 puts %2%3 into her library at position %4. - + %1 moves %2%3 to sideboard. - + %1 plays %2%3. - + %1 takes a mulligan to %n. female @@ -2155,7 +2437,7 @@ Local version is %1, remote version is %2. - + %1 takes a mulligan to %n. male @@ -2163,283 +2445,283 @@ Local version is %1, remote version is %2. - + %1 flips %2 face-down. female %1は%2を裏返しにしました. - + %1 flips %2 face-down. male %1は%2を裏返しにしました. - + %1 flips %2 face-up. female %1は%2を表返しにしました. - + %1 flips %2 face-up. male %1は%2を表返しにしました. - + %1 destroys %2. female %1は%2を破壊しました. - + %1 destroys %2. male %1は%2を破壊しました. - + %1 unattaches %2. female %1は%2を外しました. - + %1 unattaches %2. male %1は%2を外しました. - + %1 creates token: %2%3. female %1はトークン:%2%3を作りました. - + %1 creates token: %2%3. male %1はトークン:%2%3を作りました. - + %1 points from her %2 to herself. female - + %1 points from his %2 to himself. male - + %1 points from her %2 to %3. p1 female, p2 female - + %1 points from her %2 to %3. p1 female, p2 male - + %1 points from his %2 to %3. p1 male, p2 female - + %1 points from his %2 to %3. p1 male, p2 male - + %1 points from %2's %3 to herself. card owner female, target female - + %1 points from %2's %3 to herself. card owner male, target female - + %1 points from %2's %3 to himself. card owner female, target male - + %1 points from %2's %3 to himself. card owner male, target male - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male - + %1 points from her %2 to her %3. female - + %1 points from his %2 to his %3. male - + %1 points from her %2 to %3's %4. p1 female, p2 female - + %1 points from her %2 to %3's %4. p1 female, p2 male - + %1 points from his %2 to %3's %4. p1 male, p2 female - + %1 points from his %2 to %3's %4. p1 male, p2 male - + %1 points from %2's %3 to her own %4. card owner female, target female - + %1 points from %2's %3 to her own %4. card owner male, target female - + %1 points from %2's %3 to his own %4. card owner female, target male - + %1 points from %2's %3 to his own %4. card owner male, target male - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -2447,7 +2729,7 @@ Local version is %1, remote version is %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -2455,7 +2737,7 @@ Local version is %1, remote version is %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -2463,7 +2745,7 @@ Local version is %1, remote version is %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -2471,272 +2753,310 @@ Local version is %1, remote version is %2. - + %1 taps her permanents. female - + %1 untaps her permanents. female - + %1 taps his permanents. male - + %1 untaps his permanents. male - + %1 taps %2. female - + %1 untaps %2. female - - - %1 taps %2. - male - - - - - %1 untaps %2. - male - - - - - %1 sets counter %2 to %3 (%4%5). - female - - - - - %1 sets counter %2 to %3 (%4%5). - male - - - - - %1 sets %2 to not untap normally. - female - - - - - %1 sets %2 to not untap normally. - male - - - - - %1 sets %2 to untap normally. - female - - - - - %1 sets %2 to untap normally. - male - - - - - %1 sets PT of %2 to %3. - female - - - - - %1 sets PT of %2 to %3. - male - - - - - %1 sets annotation of %2 to %3. - female - - - - - %1 sets annotation of %2 to %3. - male - - - - - %1 is looking at the top %2 cards %3. - female - - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - - %1 is looking at %2. - female - - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female + %1 sets counter %2 to %3 (%4%5). + male + + + + + %1 sets %2 to not untap normally. + female + + + + + %1 sets %2 to not untap normally. + male + + + + + %1 sets %2 to untap normally. + female + + + + + %1 sets %2 to untap normally. + male + + + + + %1 sets PT of %2 to %3. + female + + + + + %1 sets PT of %2 to %3. + male + + + + + %1 sets annotation of %2 to %3. + female + + + + + %1 sets annotation of %2 to %3. + male + + + + + %1 is looking at %2. + female + + + + + %1 is looking at %2. + male + + + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + + %1 stops looking at %2. + female + + + + %1 stops looking at %2. male - - - %1 reveals %2 to %3. - p1 female, p2 female - - - - - %1 reveals %2 to %3. - p1 female, p2 male - - - - - %1 reveals %2 to %3. - p1 male, p2 female - - - - - %1 reveals %2 to %3. - p1 male, p2 male - - - - - %1 reveals %2. - female - - - - - %1 reveals %2. - male - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 randomly reveals %2%3. - female - - - - - %1 randomly reveals %2%3. - male - - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 reveals %2%3. - female - - - - - %1 reveals %2%3. - male - - - - - It is now %1's turn. - female - - + %1 reveals %2 to %3. + p1 female, p2 female + + + + + %1 reveals %2 to %3. + p1 female, p2 male + + + + + %1 reveals %2 to %3. + p1 male, p2 female + + + + + %1 reveals %2 to %3. + p1 male, p2 male + + + + + %1 reveals %2. + female + + + + + %1 reveals %2. + male + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 randomly reveals %2%3. + female + + + + + %1 randomly reveals %2%3. + male + + + + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 reveals %2%3. + female + + + + + %1 reveals %2%3. + male + + + + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + + It is now %1's turn. + female + + + + It is now %1's turn. male - - + + a card @@ -2753,134 +3073,159 @@ Local version is %1, remote version is %2. - + red 赤 - + yellow 黄 - + green 緑 - + The game has started. - + + %1 has loaded a deck (%2). + %1はデッキ%2をロードしました. + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + %1 draws his initial hand. - + %1 draws her initial hand. - + ending phase - + untap step - + + You are watching a replay of game #%1. + female + + + + + You are watching a replay of game #%1. + male + + + + + You have been kicked out of the game. + あなたはこのゲームからキックされました. + + %1 has loaded a deck (%2). female - %1はデッキ%2をロードしました. + %1はデッキ%2をロードしました. - %1 has loaded a deck (%2). male - %1はデッキ%2をロードしました. + %1はデッキ%2をロードしました. - + %1 attaches %2 to %3's %4. p1 female, p2 female - + %1 attaches %2 to %3's %4. p1 female, p2 male - + %1 attaches %2 to %3's %4. p1 male, p2 female - + %1 attaches %2 to %3's %4. p1 male, p2 male - + upkeep step - + draw step - + first main phase - + beginning of combat step - + declare attackers step - + declare blockers step - + combat damage step - + end of combat step - + second main phase - + It is now the %1. @@ -2888,22 +3233,22 @@ Local version is %1, remote version is %2. MessagesSettingsPage - + &Add 追加 - + &Remove 削除 - + Add message メッセージを追加する - + Message: メッセージ: @@ -2911,57 +3256,57 @@ Local version is %1, remote version is %2. PhasesToolbar - + Untap step アンタップステップ - + Upkeep step アップキープステップ - + Draw step ドローステップ - + First main phase 第1メインフェイズ - + Beginning of combat step 戦闘前ステップ - + Declare attackers step 攻撃クリーチャー指定ステップ - + Declare blockers step ブロッククリーチャー指定ステップ - + Combat damage step 戦闘ダメージステップ - + End of combat step 戦闘終了ステップ - + Second main phase 第2メインフェイズ - + End of turn step ターン終了ステップ @@ -2969,322 +3314,532 @@ Local version is %1, remote version is %2. Player - - - + + + Move to &top of library ライブラリーの一番上へ移動 - - - + + + Move to &bottom of library ライブラリーの一番下へ移動 - - + + Move to &graveyard 墓地へ移動 - + &View library ライブラリーを見る - + Reveal &library to ライブラリーを公開する - + Reveal t&op card to 一番上のカードを公開する - + Move top cards to &graveyard... カードを上から墓地へ置く... - + + Ctrl+J + + + + F3 - + View &top cards of library... ライブラリーのカードを上からX枚見る - + &View graveyard 墓地を見る - + F4 - + &View sideboard サイドボードを見る - + Player "%1" プレイヤー "%1" - + &Hand 手札 - + &Library ライブラリー - + &Graveyard 墓地 - + &Sideboard サイドボード - + View top cards of library ライブラリーのカードを上からX枚見る - + Number of cards: カードの枚数: - + &Draw card カードを引く - + &View exile 追放領域を見る - + &Exile 追放領域 - - + + Move to &hand 手札に移動する - - + + Move to &exile 追放領域へ移動する - + Ctrl+W - + Ctrl+D - + D&raw cards... カードをX枚引く - + Ctrl+E - + Take &mulligan マリガンする - + Ctrl+M - + &Shuffle シャッフル - + Ctrl+S - + &Counters カウンター - + &Untap all permanents 全てのパーマネントをアンタップする - + Ctrl+U - + R&oll die... X面ダイスを振る - + Ctrl+I - + &Create token... トークンを作成する - + Ctrl+T - + C&reate another token 同じトークンを作成する - + Ctrl+G - + S&ay 発言する - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &Undo last draw 最後のドローを取り消す - + Move top cards to &exile... ライブラリーの一番上からX枚追放する - + Put top card on &bottom 一番上のカードを一番下に置く - + &Reveal to 公開する - + Reveal r&andom card to ランダムに公開する - + + Cr&eate predefined token + + + + C&ard カード - + &All players 全てのプレイヤー - + + &Play + プレイ + + + + &Hide + 裏にしてプレイ + + + + &Tap + タップ + + + + &Untap + アンタップ + + + + Toggle &normal untapping + 通常のアンタップをしない + + + + &Flip + 裏にする + + + + &Peek at card face + + + + + &Clone + 複製する + + + + Attac&h to card... + + + + + Ctrl+A + + + + + Unattac&h + 取り外す + + + + &Draw arrow... + 矢印を指定 + + + + &Increase power + パワーを上げる + + + + Ctrl++ + + + + + &Decrease power + パワーを下げる + + + + Ctrl+- + + + + + I&ncrease toughness + タフネスを上げる + + + + Alt++ + + + + + D&ecrease toughness + タフネスを下げる + + + + Alt+- + + + + + In&crease power and toughness + パワーとタフネスを上げる + + + + Ctrl+Alt++ + + + + + Dec&rease power and toughness + パワーとタフネスを下げる + + + + Ctrl+Alt+- + + + + + Set &power and toughness... + パワーとタフネスを設定する... + + + + Ctrl+P + + + + + &Set annotation... + 注釈を付ける... + + + + red + 赤 + + + + yellow + 黄 + + + + green + 緑 + + + + &Add counter (%1) + カウンターを乗せる (%1) + + + + &Remove counter (%1) + カウンターを取り除く (%1) + + + + &Set counters (%1)... + カウンターの数を決める (%1)... + + + + &top of library + ライブラリーの一番上へ + + + + &bottom of library + ライブラリーの一番下へ + + + + &graveyard + 墓地へ + + + + Ctrl+Del + + + + + &exile + 追放領域へ + + + Ctrl+F3 - + Ctrl+Shift+D - + Draw cards カードを引く - - - - + + + + Number: 枚数 - + Move top cards to grave ライブラリーのトップからX枚墓地へ置く - + Move top cards to exile ライブラリーのトップからX枚追放領域へ置く - + Roll die ダイスを振る - + Number of sides: 面の数: - + Set power/toughness パワーとタフネスを設定する - + Please enter the new PT: 新しいP/Tを入力してください - + Set annotation 補足を付ける - + Please enter the new annotation: 新しい補足を付けてください - + Set counters カウンターを設定する @@ -3312,116 +3867,140 @@ Local version is %1, remote version is %2. デッキ #%1 - User &details - ユーザー補足 + ユーザー補足 - Direct &chat - 個人チャット + 個人チャット - Add to &buddy list - フレンドリストに追加 + フレンドリストに追加 - Remove from &buddy list - フレンドリストから削除 + フレンドリストから削除 - Add to &ignore list - 無視リストに追加 + 無視リストに追加 - Remove from &ignore list - 無視リストから削除 + 無視リストから削除 - Kick from &game - ゲームからキックする + ゲームからキックする QObject - Maindeck - メインデッキ + メインデッキ - Sideboard - サイドボード + サイドボード - - Cockatrice decks (*.cod) - + + Common deck formats (*.cod *.dec *.mwDeck) + - - Plain text decks (*.dec *.mwDeck) - - - - + All files (*.*) 全てのファイル (*.*) + + + Cockatrice replays (*.cor) + + RemoteDeckList_TreeModel - + Name デッキ名 - + ID - + Upload time アップロード日 + + RemoteReplayList_TreeModel + + + ID + + + + + Name + + + + + Players + プレイヤー + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + RoomSelector - + Rooms 部屋 - + Joi&n 参加する - + Room 部屋 - + Description 説明 - + Players プレイヤー - + Games ゲーム @@ -3449,27 +4028,25 @@ Local version is %1, remote version is %2. ShutdownDialog - + &Reason for shutdown: シャットダウンの理由: - + &Time until shutdown (minutes): シャットダウンまでの時間(分): - &OK - OK + OK - &Cancel - Cancel + Cancel - + Shut down server サーバーシャットダウン @@ -3477,44 +4054,43 @@ Local version is %1, remote version is %2. TabAdmin - + Update server &message サーバー更新&メッセージ - + &Shut down server サーバーシャットダウン - + Server administration functions サーバー管理機能 - + &Unlock functions 機能をアンロックする - + &Lock functions 機能をロックする - + Unlock administration functions 管理機能をアンロックする - + Do you really want to unlock the administration functions? 本当に管理機能をアンロックしますか? - Administration - 管理者 + 管理者 @@ -3536,206 +4112,449 @@ Local version is %1, remote version is %2. %1はチャンネルから退出しました. + + TabDeckEditor + + &Search... + 検索... + + + + Show card text only + + + + + &Clear search + 検索を解除 + + + + &Search for: + 検索: + + + + Deck &name: + デッキ名: + + + + &Comments: + コメント: + + + + Hash: + ハッシュ: + + + + &Update prices + 価格を更新する + + + + Ctrl+U + + + + + &New deck + 新しいデッキ + + + + &Load deck... + デッキをロード... + + + + &Save deck + デッキを保存 + + + + Save deck &as... + 名前を付けてデッキを保存... + + + + Load deck from cl&ipboard... + クリップボードからデッキをロード... + + + + Save deck to clip&board + クリップボードにデッキを保存 + + + + &Print deck... + デッキを印刷... + + + + &Analyze deck on deckstats.net + + + + + &Close + 閉じる + + + + Ctrl+Q + + + + + Add card to &maindeck + メインデッキにカードを加える + + + + Add card to &sideboard + サイドボードにカードを加える + + + + &Remove row + 全て取り除く + + + + Del + + + + + &Increment number + 枚数を増やす + + + + + + + + + + &Decrement number + 枚数を減らす + + + + - + + + + + &Deck editor + デッキエディター + + + + C&ard database + + + + + &Edit sets... + セットの設定... + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + このデッキリストは変更されています.変更を保存しますか? + + + + Load deck + デッキをロード + + + + + + Error + エラー + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + このデッキは保存されていません. ディレクトリをチェックして再度上書きしてください. + + + + Save deck + デッキを保存 + + TabDeckStorage - + Local file system ローカルファイルシステム - + Server deck storage サーバーデッキ管理 - - + + Open in deck editor デッキエディターを開く - + Upload deck デッキをアップロード - + Download deck デッキをダウンロード - - + + New folder 新しいフォルダーを作成する - + + Delete 消去 - + Enter deck name デッキの名前を入れてください - + This decklist does not have a name. Please enter a name: このデッキリストには名前がありません.名前を入力してください: - + Unnamed deck - + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: 新しいフォルダの名前: - + + Delete remote folder + + + + + Delete remote deck + + + Deck storage - デッキストレージ + デッキストレージ TabGame - + F5 - + F6 - + F7 - + F8 - + F9 - + F10 - + &Phases フェイズ - + &Game ゲーム - + Next &phase 次のフェイズ - + Ctrl+Space - + + Tab + + + + Next &turn 次のターン - + Ctrl+Return - + Ctrl+Enter - + &Remove all local arrows 全ての矢印を消す - + Ctrl+R - + + Game &information + + + + &Concede 投了する - + F2 - + &Leave game ゲームを退出する - + + Ctrl+Q - + + C&lose replay + + + + &Say: 発言する - + Concede 投了する - + Are you sure you want to concede this game? 本当にこのゲームに投了しますか? - + Leave game ゲームから退出する - + Are you sure you want to leave this game? 本当にこのゲームから退出しますか? - + + Replay %1: %2 + + + Kicked - キック + キック - You have been kicked out of the game. - あなたはこのゲームからキックされました. + あなたはこのゲームからキックされました. - + Game %1: %2 ゲーム %1: %2 @@ -3743,60 +4562,120 @@ Please enter a name: TabMessage - + Personal &talk 個人会話 - + &Leave 退出する - + This user is ignoring you. このユーザーはあなたを無視しています. - + %1 has left the server. %1はサーバーから退出しました. - + %1 has joined the server. %1がサーバーに参加しました. - + Talking to %1 %1と会話 + + TabReplays + + + Local file system + ローカルファイルシステム + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + 消去 + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: 発言する - + Chat チャット - + &Room 部屋 - + &Leave room 部屋から出る - + + &Ignore unregistered users in chat + + + + You are flooding the chat. Please wait a couple of seconds. あなたはチャットルームから弾かれました.少々お待ちください. @@ -3812,53 +4691,114 @@ Please enter a name: TabServer - Server - サーバー + サーバー TabUserLists - User lists - ユーザーリスト + ユーザーリスト + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + ユーザー補足 + + + + Direct &chat + 個人チャット + + + + Show this user's &games + このユーザーのゲームを表示する + + + + Add to &buddy list + フレンドリストに追加 + + + + Remove from &buddy list + フレンドリストから削除 + + + + Add to &ignore list + 無視リストに追加 + + + + Remove from &ignore list + 無視リストから削除 + + + + Kick from &game + ゲームからキックする + + + + Ban from &server + サーバーからバンする + + + + %1's games + %1のゲーム UserInfoBox - + User information ユーザー情報 - + Real name: 本名: - + Gender: 性別: - + Location: 現在地: - + User level: ユーザーレベル: - + Administrator 管理者 - + Moderator モデレーター @@ -3867,12 +4807,12 @@ Please enter a name: ジャッジ - + Registered user 登録ユーザー - + Unregistered user 未登録ユーザー @@ -3880,37 +4820,47 @@ Please enter a name: UserInterfaceSettingsPage - + General interface settings インターフェース総合設定 - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) ダブルクリックでカードをプレイする(シングルクリックの代わり) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings アニメーション設定 - + &Tap/untap animation タップ/アンタップアニメーション - + Enable &sounds サウンドを許可 - + Path to sounds directory: サウンドディレクトリへのパス: - + Choose path パスを選ぶ @@ -3918,69 +4868,60 @@ Please enter a name: UserList - + Users online: %1 ユーザー オンライン: %1 - + Users in this room: %1 部屋のユーザー数: %1 - + Buddies online: %1 / %2 フレンドオンライン: %1 / %2 - + Ignored users online: %1 / %2 無視ユーザーオンライン: %1 / %2 - %1's games - %1のゲーム + %1のゲーム - User &details - ユーザー補足 + ユーザー補足 - Direct &chat - 個人チャット + 個人チャット - Show this user's &games - このユーザーのゲームを表示する + このユーザーのゲームを表示する - Add to &buddy list - フレンドリストに追加 + フレンドリストに追加 - Remove from &buddy list - フレンドリストから削除 + フレンドリストから削除 - Add to &ignore list - 無視リストに追加 + 無視リストに追加 - Remove from &ignore list - 無視リストから削除 + 無視リストから削除 - Ban from &server - サーバーからバンする + サーバーからバンする Duration @@ -3995,204 +4936,127 @@ Enter 0 for an indefinite ban. WndDeckEditor - &Search for: - 検索: + 検索: - Deck &name: - デッキ名: + デッキ名: - &Comments: - コメント: + コメント: - Deck editor [*] - デッキエディター [*] + デッキエディター [*] - &New deck - 新しいデッキ + 新しいデッキ - &Load deck... - デッキをロード... + デッキをロード... - Load deck from cl&ipboard... - クリップボードからデッキをロード... + クリップボードからデッキをロード... - &Save deck - デッキを保存 + デッキを保存 - Hash: - + ハッシュ: - &Update prices - 価格を更新する + 価格を更新する - - Ctrl+U - - - - Save deck &as... - 名前を付けてデッキを保存... + 名前を付けてデッキを保存... - Save deck to clip&board - クリップボードにデッキを保存 + クリップボードにデッキを保存 - &Print deck... - デッキを印刷... + デッキを印刷... - &Close - 閉じる + 閉じる - - Ctrl+Q - - - - &Edit sets... - セットの設定... + セットの設定... - &Deck - デッキ + デッキ - Load deck - デッキをロード + デッキをロード - - Error - エラー + エラー - - The deck could not be saved. Please check that the directory is writable and try again. 要検証 - このデッキは保存されていません. ディレクトリをチェックして再度上書きしてください. + このデッキは保存されていません. ディレクトリをチェックして再度上書きしてください. - Save deck - デッキを保存 + デッキを保存 - Add card to &maindeck - メインデッキにカードを加える + メインデッキにカードを加える - - Return - - - - - Enter - - - - - Ctrl+Return - - - - - Ctrl+Enter - - - - Add card to &sideboard - サイドボードにカードを加える + サイドボードにカードを加える - &Search... - 検索... + 検索... - &Clear search - 検索を解除 + 検索を解除 - &Card database - カードデータベース + カードデータベース - &Remove row - 全て取り除く + 全て取り除く - - Del - - - - &Increment number - 枚数を増やす + 枚数を増やす - - + - - - - &Decrement number - 枚数を減らす + 枚数を減らす - - - - - - - Are you sure? - 本当によろしいですか? + 本当によろしいですか? - The decklist has been modified. Do you want to save the changes? - このデッキリストは変更されています.変更を保存しますか? + このデッキリストは変更されています.変更を保存しますか? @@ -4206,17 +5070,17 @@ Do you want to save the changes? ZoneViewWidget - + sort by name 名前でソート - + sort by type タイプでソート - + shuffle when closing 閉じる時にシャッフル diff --git a/cockatrice/translations/cockatrice_pl.ts b/cockatrice/translations/cockatrice_pl.ts index a65e88614..7b5e8bede 100644 --- a/cockatrice/translations/cockatrice_pl.ts +++ b/cockatrice/translations/cockatrice_pl.ts @@ -1,480 +1,403 @@ - + AbstractCounter - + &Set counter... - + &Ustaw licznik... - + Ctrl+L - + Ctrl+L - + F11 - + F11 - + F12 - + F12 - + Set counter - + Ustaw licznik - + New value for counter '%1': - + Nowa wartość dla licznika '%1': AppearanceSettingsPage - + Zone background pictures - - Path to hand background: + + Hand background: - - Path to stack background: + + Stack background: - - Path to table background: + + Table background: - - Path to player info background: + + Player info background: - - Path to picture of card back: + + Card back: - + Card rendering - + Display card names on cards having a picture - + Hand layout - + Display hand horizontally (wastes space) - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Zone view layout - + Sort by name - + Sortuj według nazwy - + Sort by type - + Sortuj według typu - - - - - + + + + + Choose path - + Wybierz ścieżkę BanDialog - + ban &user name - + ban &IP address - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK - + &Cancel - + Ban user from server - + Error - + Błąd - + You have to select a name-based or IP-based ban, or both. CardDatabaseModel - - - Name - - - - - Sets - - - Mana cost - + Name + Nazwa - Card type - + Sets + Edycje + Mana cost + Koszt many + + + + Card type + Typ karty + + + P/T - + P/T + + + + CardInfoText + + + Name: + Nazwa: + + + + Mana cost: + Koszt many: + + + + Card type: + Typ karty: + + + + P / T: + P / T: + + + + Loyalty: + Lojalność: CardInfoWidget - - Hide card info - - - - + Show card only - + Pokazuj tylko kartę - + Show text only - + Pokazuj tylko tekst - + Show full info - + Pokazuj pełne informacje - + Name: - + Nazwa: - + Mana cost: - + Koszt many: - + Card type: - + Typ karty: - + P / T: - + P / T: + + + + Loyalty: + Lojalność: CardItem - &Play - + &Zagraj - &Hide - + &Ukryj - &Tap - + &Tapuj - &Untap - + &Odtapuj - Toggle &normal untapping - + Zmień &normalne odtapowywanie - &Flip - + &Odwróć - &Clone - + &Kopiuj - Ctrl+H - + Ctrl+H - &Attach to card... - + &Doczep do karty... - Ctrl+A - + Ctrl+A - Unattac&h - + &Odłącz - &Draw arrow... - + &Narysuj strzałkę... - + &Power / toughness - + &Siła / Obrona - &Increase power - + Zwiększ &Atak - Ctrl++ - + Ctrl++ - &Decrease power - + Zmniejsz A&tak - Ctrl+- - + Ctrl+- - I&ncrease toughness - + Zwiększ &Obronę - Alt++ - + Alt++ - D&ecrease toughness - + Zmniejsz O&bronę - Alt+- - + Alt+- - - In&crease power and toughness - - - - - Ctrl+Alt++ - - - - - Dec&rease power and toughness - - - - - Ctrl+Alt+- - - - - - Set &power and toughness... - - - - - Ctrl+P - - - - - &Set annotation... - - - - - red - - - - - yellow - - - - - green - - - - - &Add counter (%1) - - - - - &Remove counter (%1) - - - - - &Set counters (%1)... - - - - - &top of library - - - - - &bottom of library - - - - - &graveyard - - - - - Ctrl+Del - - - - - &exile - - - - + &Move to @@ -482,417 +405,366 @@ This is only saved for moderators and cannot be seen by the banned person. CardZone - + her hand nominative, female owner - + %1's hand nominative, female owner - + his hand nominative, male owner - - - %1's hand - nominative, male owner - - - - - of her hand - genitive, female owner - - - - - of %1's hand - genitive, female owner - - - - - of his hand - genitive, male owner - - - - - of %1's hand - genitive, male owner - - - - - her hand - accusative, female owner - - - - - %1's hand - accusative, female owner - - - - - his hand - accusative, male owner - - - - - %1's hand - accusative, male owner - - + %1's hand + nominative, male owner + + + + her library - nominative, female owner + look at zone, female owner - + %1's library - nominative, female owner + look at zone, female owner - + his library - nominative, male owner + look at zone, male owner - + %1's library - nominative, male owner + look at zone, male owner - + of her library - genitive, female owner + top cards of zone, female owner - + of %1's library - genitive, female owner + top cards of zone, female owner - + of his library - genitive, male owner + top cards of zone, male owner - + of %1's library - genitive, male owner + top cards of zone, male owner - + her library - accusative, female owner + reveal zone, female owner - + %1's library - accusative, female owner + reveal zone, female owner - + his library - accusative, male owner + reveal zone, male owner - + %1's library - accusative, male owner + reveal zone, male owner - + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + + + + + his library + shuffle, male owner + + + + + %1's library + shuffle, male owner + + + + + her sideboard + look at zone, female owner + + + + + %1's sideboard + look at zone, female owner + + + + + his sideboard + look at zone, male owner + + + + + %1's sideboard + look at zone, male owner + + + + + her library + nominative, female owner + + + + + %1's library + nominative, female owner + + + + + his library + nominative, male owner + + + + + %1's library + nominative, male owner + + + + her graveyard nominative, female owner - + %1's graveyard nominative, female owner - + his graveyard nominative, male owner - + %1's graveyard nominative, male owner - - of her graveyard - genitive, female owner - - - - - of %1's graveyard - genitive, female owner - - - - - of his graveyard - genitive, male owner - - - - - of %1's graveyard - genitive, male owner - - - - - her graveyard - accusative, female owner - - - - - %1's graveyard - accusative, female owner - - - - - his graveyard - accusative, male owner - - - - - %1's graveyard - accusative, male owner - - - - + her exile nominative, female owner - + %1's exile nominative, female owner - + his exile nominative, male owner - + %1's exile nominative, male owner - - of her exile - genitive, female owner - - - - - of %1's exile - genitive, female owner - - - - - of his exile - genitive, male owner - - - - - of %1's exile - genitive, male owner - - - - - her exile - accusative, female owner - - - - - %1's exile - accusative, female owner - - - - - his exile - accusative, male owner - - - - - %1's exile - accusative, male owner - - - - + her sideboard nominative, female owner - + %1's sideboard nominative, female owner - + his sideboard nominative, male owner - + %1's sideboard nominative, male owner + + + DBPriceUpdater - - of her sideboard - genitive, female owner + + + + Error + Błąd + + + + + A problem has occured while fetching card prices. - - of %1's sideboard - genitive, female owner - - - - - of his sideboard - genitive, male owner - - - - - of %1's sideboard - genitive, male owner - - - - - her sideboard - accusative, female owner - - - - - %1's sideboard - accusative, female owner - - - - - his sideboard - accusative, male owner - - - - - %1's sideboard - accusative, male owner + + A problem has occured while fetching card prices: DeckEditorSettingsPage - Enable &price tag feature (using data from blacklotusproject.com) + Włącz oznaczenia &ceny (korzystając z danych z blacklotusproject.com) + + + + Enable &price tag feature - - General + + using data from blacklotusproject.com + + + using data from deckbrew.com + + + + + General + Ogólne + DeckListModel - + Number - + Ilość - + Card - + Karta - + Price + Cena + + + + DeckStatsInterface + + + + Error + Błąd + + + + The reply from the server could not be parsed. DeckViewContainer - + Load &local deck - + Załaduj &lokalną talię - + Load d&eck from server - + Załaduj &talię z serwera - + Ready to s&tart + &Gotowy do gry + + + + S&ideboard unlocked - + + S&ideboard locked + + + + Load deck + Załaduj talię + + + + Error + Błąd + + + + The selected file could not be loaded. @@ -901,69 +773,64 @@ This is only saved for moderators and cannot be seen by the banned person. Card name: - + Nazwa: Card text: - + Tekst: Card type (OR): - + Typ karty (LUB): Color (OR): - + Kolor (LUB): O&K - + )&K &Cancel - + &Anuluj Card search - + Wyszukiwanie karty DlgConnect - + &Host: - + &Port: - + Player &name: - + P&assword: - - &OK - - - - - &Cancel + + &Save password @@ -975,376 +842,590 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + &Description: - + &Opis: - + P&layers: - + &Liczba graczy: - + Game type - + Format - + &Password: - + &Hasło: - + Only &buddies can join - + Tylko dla &przyjaciół - + Only &registered users can join - - - - - Joining restrictions - - - - - &Spectators allowed - - - - - Spectators &need a password to join - - - - - Spectators can &chat - + Tylko dla &zarejestrowanych użytkowników - Spectators see &everything - + Joining restrictions + Ograniczenia dostępu + + + + &Spectators allowed + &Widzowie dozwoleni + Spectators &need a password to join + Widzowie potrzebują h&asła + + + + Spectators can &chat + Widzowie mogą korzystać z &czatu + + + + Spectators see &everything + Widzowie widzą w&szystko + + + Spectators - + Widzowie - &OK - + O&K - &Cancel - + A&nuluj - + Create game + Stwórz grę + + + + Game information - + Error - + Błąd - + Server error. - + Błąd serwera. DlgCreateToken - - - &Name: - - - - - Token - - - - - C&olor: - - - white - + &Name: + &Nazwa: + Token + Token + + + + C&olor: + &Kolor: + + + + white + biały + + + blue - + niebieski - + black + czarny + + + + red + czerwony + + + + green + zielony + + + + multicolor + wielokolorowy + + + + colorless + bezkolorowy + + + + &P/T: + &P/T: + + + + &Annotation: + &Adnotacja: + + + + &Destroy token when it leaves the table + &Zniszcz tokena kiedy opuści stół + + + + Token data - + + Show &all tokens + + + + + Show tokens from this &deck + + + + + Choose token from list + + + + &OK + &OK + + + &Cancel + &Anuluj + + + + Create token + Tworzenie tokena + + + + DlgEditTokens + + + &Name: + &Nazwa: + + + + C&olor: + &Kolor: + + + + white + biały + + + + blue + niebieski + + + + black + czarny + + + red - + green - + multicolor - + wielokolorowy - + colorless - + bezkolorowy - + &P/T: - + &P/T: - + &Annotation: + &Adnotacja: + + + + Token data - - &Destroy token when it leaves the table + + + Add token - - &OK + + Remove token - - &Cancel + + Edit tokens - - Create token + + Please enter the name of the token: + + + + + Error + Błąd + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + + Filter games DlgLoadDeckFromClipboard - + &Refresh - + &Odśwież - &OK - + O&K - &Cancel - + &Anuluj - + Load deck from clipboard - + Załaduj talię ze schowka - + Error - + Błąd - + Invalid deck list. - + Nieprawidłowa lista talii. DlgLoadRemoteDeck - O&K - + &OK - &Cancel - + &Anuluj - + Load deck - + Wczytaj talię DlgSettings - - - + + + Error + Błąd + + + + Unknown Error loading card database - - Your card database is invalid. Would you like to go back and set the correct path? + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? - + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Ogólne - + Appearance - + User interface - + Deck editor - + Messages - - - &Close - - GameSelector - - - - - - - - + + + + + + + + + Error - + Błąd - + Please join the appropriate room first. - + Wrong password. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join game - + Password: - + + Please join the respective room first. + + + + Games - - Show &full games + + &Filter games - - Show &running games + + C&lear filter - + C&reate - + &Join - + J&oin as spectator @@ -1354,173 +1435,238 @@ This is only saved for moderators and cannot be seen by the banned person. Esc - + Esc GamesModel - + yes - + tak - + yes, free for spectators - + tak, widzowie nie potrzebują hasła - + no - + tak - + buddies only - + tylko przyjaciele - + reg. users only - + tylko zarejestrowani użytkownicy + + + + not allowed + niedozwoleni - not allowed - - - - Room - + Pokój - + Description - + Opis - + Creator - + Twórca - + Game type - + Format - + Password - + Hasło - + Restrictions - + Ograniczenia - + Players - + Gracze - + Spectators - + Widzowie GeneralSettingsPage - - + + English + Polski + + + + Reset/Clear Downloaded Pictures - - - - Choose path - - - - - Personal settings - - - - - Language: - - - - - Download card pictures on the fly - - - - - Paths - - - - - Decks directory: - - - - - Pictures directory: - - - + - Path to card database: + + + + Choose path + Wybierz ścieżkę + + + + Success + + + Downloaded card pictures have been reset. + + + + + Error + Błąd + + + + One or more downloaded card pictures could not be cleared. + + + + + Personal settings + Ustawienia osobiste + + + + Language: + Język: + + + + Download card pictures on the fly + Ściągaj obrazki na bieżąco + + + + Download high-quality card pictures + + + + + Paths + Ścieżki + + + + Decks directory: + Katalog z taliami: + + + + Replays directory: + + + + + Pictures directory: + Katalog z obrazkami: + + + + Card database: + + + + + Token database: + + + + Path to card database: + Ścieżka do bazy kart: + MainWindow - + There are too many concurrent connections from your address. - + Zbyt wiele podłączeń z twojego adresu. - Banned by moderator. - + Zbanowany przez moderatora. - + Scheduled server shutdown. - + Zaplanowane zamknięcie serwera. - Unknown reason. - + Nieznany powód. - + Connection closed + Połączenie zamknięte + + + + Scheduled server shutdown + Zaplanowane zamknięcie serwera + + + + Banned by moderator - + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + The server has terminated your connection. Reason: %1 - - - Scheduled server shutdown - - - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1531,1197 +1677,445 @@ Reason for shutdown: %1 - + Number of players - + Liczba graczy - + Please enter the number of players. - + Podaj liczbę graczy. - - + + Player %1 + Gracz %1 + + + + Load replay - + About Cockatrice - + O Cockatrice - + Version %1 - + Wersja %1 - + Authors: - + Autorzy: - + Translators: - + Tłumacze: - + Spanish: - + Hiszpański: - + Portugese (Portugal): - + Portugalski (Portugalia): - + Portugese (Brazil): - + Portugalski (Brazylia): - + French: - + Francuski: - + Japanese: - + Japoński: - + Russian: - + Rosyjski: - Czech: - + Czeski: - + Italian: + Włoski: + + + + Swedish: - - - - - - + + + + + + + + + + Error - + Błąd - + Server timeout - - Invalid login data. - - - - + There is already an active session using this user name. Please close that session first and re-login. - - Socket error: %1 + + You are banned until %1. - + + You are banned indefinitely. + + + + + Unknown login error: %1 + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - Connecting to %1... - - - - - Disconnected - - - - - Logged in at %1 - - - - - &Connect... - - - - - &Disconnect - - - - - Start &local game... - - - - - &Deck editor - - - - - &Full screen - - - - - Ctrl+F - - - - - &Settings... + + Socket error: %1 + Incorrect username or password. Please check your authentication information and try again. + + + + + This server requires user registration. + + + + + Connecting to %1... + + + + + Disconnected + + + + + Connected, logging in at %1 + + + + + Logged in at %1 + + + + + &Connect... + + + + + &Disconnect + + + + + Start &local game... + + + + + &Watch replay... + + + + + &Deck editor + + + + + &Full screen + + + + + Ctrl+F + Ctrl+F + + + + &Settings... + &Ustawienia... + + + &Exit - + &Wyjdź - + &Cockatrice - + &Cockatrice - + &About Cockatrice - + &O Cockatrice - + &Help - + &Pomoc - + Are you sure? - + Jesteś pewien? - + There are still open games. Are you sure you want to quit? - + Ciągle masz otwarte gry. Na pewno chcesz wyjść? MessageLogWidget - + The game has been closed. - + %1 is now watching the game. - + %1 is not watching the game any more. - + The game has started. - + You have joined game #%1. female - - - You have joined game #%1. - male - - - - - %1 has joined the game. - female - - - - - %1 has joined the game. - male - - - %1 has left the game. - female - - - - - %1 has left the game. + You have joined game #%1. male - - %1 is ready to start the game. + + You are watching a replay of game #%1. female - - %1 is ready to start the game. + + You are watching a replay of game #%1. male - - %1 is not ready to start the game any more. + + %1 has joined the game. female - - %1 is not ready to start the game any more. + + %1 has joined the game. male - - - %1 has conceded the game. - female - - - - - %1 has conceded the game. - male - - - - - %1 has restored connection to the game. - female - - - - - %1 has restored connection to the game. - male - - - - - %1 has lost connection to the game. - female - - - - - %1 has lost connection to the game. - male - - - - - %1 shuffles %2. - female - - - - - %1 shuffles %2. - male - - - - - %1 rolls a %2 with a %3-sided die. - female - - - - - %1 rolls a %2 with a %3-sided die. - male - - - - - %1 draws %n card(s). - female - - - - - - - - - %1 draws %n card(s). - male - - - - - - - - - %1 undoes his last draw. - - - - - %1 undoes her last draw. - - - - - %1 undoes his last draw (%2). - - - - - %1 undoes her last draw (%2). - - - - - from table - - - - - from graveyard - - - - - from exile - - - - - from hand - - - - - the bottom card of his library - - - - - the bottom card of her library - - - - - from the bottom of his library - - - - - from the bottom of her library - - - - - the top card of his library - - - - - the top card of her library - - - - - from the top of his library - - - - - from the top of her library - - - - - from library - - - - - from sideboard - - - - - from the stack - - - - - - a card - - - - - %1 gives %2 control over %3. - - - - - %1 puts %2 into play tapped%3. - - - - - %1 puts %2 into play%3. - - - - - %1 puts %2%3 into graveyard. - - - - - %1 exiles %2%3. - - - - - %1 moves %2%3 to hand. - - - - - %1 puts %2%3 into his library. - - - - - %1 puts %2%3 into her library. - - - - - %1 puts %2%3 on bottom of his library. - - - - - %1 puts %2%3 on bottom of her library. - - - - - %1 puts %2%3 on top of his library. - - - - - %1 puts %2%3 on top of her library. - - - - - %1 puts %2%3 into his library at position %4. - - - - - %1 puts %2%3 into her library at position %4. - - - - - %1 moves %2%3 to sideboard. - - - - - %1 plays %2%3. - - - - - %1 takes a mulligan to %n. - female - - - - - - - - - %1 takes a mulligan to %n. - male - - - - - - - - - %1 flips %2 face-down. - female - - - - - %1 flips %2 face-down. - male - - - - - %1 flips %2 face-up. - female - - - - - %1 flips %2 face-up. - male - - - - - %1 destroys %2. - female - - - - - %1 destroys %2. - male - - - - - %1 unattaches %2. - female - - - - - %1 unattaches %2. - male - - - - - %1 creates token: %2%3. - female - - - - - %1 creates token: %2%3. - male - - - - - %1 points from her %2 to herself. - female - - - - - %1 points from his %2 to himself. - male - - - - - %1 points from her %2 to %3. - p1 female, p2 female - - - - - %1 points from her %2 to %3. - p1 female, p2 male - - - - - %1 points from his %2 to %3. - p1 male, p2 female - - - - - %1 points from his %2 to %3. - p1 male, p2 male - - - - - %1 points from %2's %3 to herself. - card owner female, target female - - - - - %1 points from %2's %3 to herself. - card owner male, target female - - - - - %1 points from %2's %3 to himself. - card owner female, target male - - - - - %1 points from %2's %3 to himself. - card owner male, target male - - - - - %1 points from %2's %3 to %4. - p1 female, p2 female, p3 female - - - - - %1 points from %2's %3 to %4. - p1 female, p2 female, p3 male - - - - - %1 points from %2's %3 to %4. - p1 female, p2 male, p3 female - - - - - %1 points from %2's %3 to %4. - p1 female, p2 male, p3 male - - - - - %1 points from %2's %3 to %4. - p1 male, p2 female, p3 female - - - - - %1 points from %2's %3 to %4. - p1 male, p2 female, p3 male - - - - - %1 points from %2's %3 to %4. - p1 male, p2 male, p3 female - - - - - %1 points from %2's %3 to %4. - p1 male, p2 male, p3 male - - - - - %1 points from her %2 to her %3. - female - - - - - %1 points from his %2 to his %3. - male - - - - - %1 points from her %2 to %3's %4. - p1 female, p2 female - - - - - %1 points from her %2 to %3's %4. - p1 female, p2 male - - - - - %1 points from his %2 to %3's %4. - p1 male, p2 female - - - - - %1 points from his %2 to %3's %4. - p1 male, p2 male - - - - - %1 points from %2's %3 to her own %4. - card owner female, target female - - - - - %1 points from %2's %3 to her own %4. - card owner male, target female - - - - - %1 points from %2's %3 to his own %4. - card owner female, target male - - - - - %1 points from %2's %3 to his own %4. - card owner male, target male - - - - - %1 points from %2's %3 to %4's %5. - p1 female, p2 female, p3 female - - - - - %1 points from %2's %3 to %4's %5. - p1 female, p2 female, p3 male - - - - - %1 points from %2's %3 to %4's %5. - p1 female, p2 male, p3 female - - - - - %1 points from %2's %3 to %4's %5. - p1 female, p2 male, p3 male - - - - - %1 points from %2's %3 to %4's %5. - p1 male, p2 female, p3 female - - - - - %1 points from %2's %3 to %4's %5. - p1 male, p2 female, p3 male - - - - - %1 points from %2's %3 to %4's %5. - p1 male, p2 male, p3 female - - - - - %1 points from %2's %3 to %4's %5. - p1 male, p2 male, p3 male - - - - - %1 places %n %2 counter(s) on %3 (now %4). - female - - - - - - - - - %1 places %n %2 counter(s) on %3 (now %4). - male - - - - - - - - - %1 removes %n %2 counter(s) from %3 (now %4). - female - - - - - - - - - %1 removes %n %2 counter(s) from %3 (now %4). - male - - - - - - - - - %1 taps her permanents. - female - - - - - %1 untaps her permanents. - female - - - - - %1 taps his permanents. - male - - - - - %1 untaps his permanents. - male - - - - - %1 taps %2. - female - - - - - %1 untaps %2. - female - - - - - %1 taps %2. - male - - - - - %1 untaps %2. - male - - - - - %1 sets counter %2 to %3 (%4%5). - female - - - - - %1 sets counter %2 to %3 (%4%5). - male - - - - - %1 sets %2 to not untap normally. - female - - - - - %1 sets %2 to not untap normally. - male - - - - - %1 sets %2 to untap normally. - female - - - - - %1 sets %2 to untap normally. - male - - - - - %1 sets PT of %2 to %3. - female - - - - - %1 sets PT of %2 to %3. - male - - - - - %1 sets annotation of %2 to %3. - female - - - - - %1 sets annotation of %2 to %3. - male - - - - - %1 is looking at the top %2 cards %3. - female - - - - - %1 is looking at the top %2 cards %3. - male - - - - - %1 is looking at %2. - female - - - - - %1 is looking at %2. - male - - - - - %1 stops looking at %2. - female - - - - - %1 stops looking at %2. - male - - - - - %1 reveals %2 to %3. - p1 female, p2 female - - - - - %1 reveals %2 to %3. - p1 female, p2 male - - - - - %1 reveals %2 to %3. - p1 male, p2 female - - - - - %1 reveals %2 to %3. - p1 male, p2 male - - - - - %1 reveals %2. - female - - - - - %1 reveals %2. - male - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 randomly reveals %2%3. - female - - - - - %1 randomly reveals %2%3. - male - - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 reveals %2%3. - female - - - - - %1 reveals %2%3. - male - - - - - It is now %1's turn. - female - - - - - It is now %1's turn. - male - - - - - %1 draws his initial hand. - - - %1 has loaded a deck (%2). + %1 has left the game. female - %1 has loaded a deck (%2). + %1 has left the game. male - - %1 draws her initial hand. + + You have been kicked out of the game. - - %1 attaches %2 to %3's %4. - p1 female, p2 female + + %1 has loaded a deck (%2). - - %1 attaches %2 to %3's %4. - p1 female, p2 male + + %1 has loaded a deck with %2 sideboard cards (%3). - - %1 attaches %2 to %3's %4. - p1 male, p2 female + + %1 is ready to start the game. + female - - %1 attaches %2 to %3's %4. - p1 male, p2 male + + %1 is ready to start the game. + male + + + + + %1 is not ready to start the game any more. + female + + + + + %1 is not ready to start the game any more. + male + + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + %1 has conceded the game. + female + + + + + %1 has conceded the game. + male + + + + + %1 has restored connection to the game. + female + + + + + %1 has restored connection to the game. + male + + + + + %1 has lost connection to the game. + female + + + + + %1 has lost connection to the game. + male + + + + + %1 shuffles %2. + female + + + + + %1 shuffles %2. + male + + + + + %1 rolls a %2 with a %3-sided die. + female + + + + + %1 rolls a %2 with a %3-sided die. + male - - red + + %1 draws %n card(s). + female @@ -2729,7 +2123,908 @@ Local version is %1, remote version is %2. - + + %1 draws %n card(s). + male + + + + + + + + + %1 undoes his last draw. + + + + + %1 undoes her last draw. + + + + + %1 undoes his last draw (%2). + + + + + %1 undoes her last draw (%2). + + + + + from table + + + + + from graveyard + + + + + from exile + + + + + from hand + + + + + the bottom card of %1's library + + + + + the bottom card of his library + + + + + the bottom card of her library + + + + + from the bottom of %1's library + + + + + from the bottom of his library + + + + + from the bottom of her library + + + + + the top card of %1's library + + + + + the top card of his library + + + + + the top card of her library + + + + + from the top of %1's library + + + + + from the top of his library + + + + + from the top of her library + + + + + from %1's library + + + + + from library + + + + + from sideboard + + + + + from the stack + + + + + + a card + + + + + %1 gives %2 control over %3. + + + + + %1 puts %2 into play tapped%3. + + + + + %1 puts %2 into play%3. + + + + + %1 puts %2%3 into graveyard. + + + + + %1 exiles %2%3. + + + + + %1 moves %2%3 to hand. + + + + + %1 puts %2%3 into his library. + + + + + %1 puts %2%3 into her library. + + + + + %1 puts %2%3 on bottom of his library. + + + + + %1 puts %2%3 on bottom of her library. + + + + + %1 puts %2%3 on top of his library. + + + + + %1 puts %2%3 on top of her library. + + + + + %1 puts %2%3 into his library at position %4. + + + + + %1 puts %2%3 into her library at position %4. + + + + + %1 moves %2%3 to sideboard. + + + + + %1 plays %2%3. + + + + + %1 takes a mulligan to %n. + female + + + + + + + + + %1 takes a mulligan to %n. + male + + + + + + + + + %1 flips %2 face-down. + female + + + + + %1 flips %2 face-down. + male + + + + + %1 flips %2 face-up. + female + + + + + %1 flips %2 face-up. + male + + + + + %1 destroys %2. + female + + + + + %1 destroys %2. + male + + + + + %1 unattaches %2. + female + + + + + %1 unattaches %2. + male + + + + + %1 creates token: %2%3. + female + + + + + %1 creates token: %2%3. + male + + + + + %1 points from her %2 to herself. + female + + + + + %1 points from his %2 to himself. + male + + + + + %1 points from her %2 to %3. + p1 female, p2 female + + + + + %1 points from her %2 to %3. + p1 female, p2 male + + + + + %1 points from his %2 to %3. + p1 male, p2 female + + + + + %1 points from his %2 to %3. + p1 male, p2 male + + + + + %1 points from %2's %3 to herself. + card owner female, target female + + + + + %1 points from %2's %3 to herself. + card owner male, target female + + + + + %1 points from %2's %3 to himself. + card owner female, target male + + + + + %1 points from %2's %3 to himself. + card owner male, target male + + + + + %1 points from %2's %3 to %4. + p1 female, p2 female, p3 female + + + + + %1 points from %2's %3 to %4. + p1 female, p2 female, p3 male + + + + + %1 points from %2's %3 to %4. + p1 female, p2 male, p3 female + + + + + %1 points from %2's %3 to %4. + p1 female, p2 male, p3 male + + + + + %1 points from %2's %3 to %4. + p1 male, p2 female, p3 female + + + + + %1 points from %2's %3 to %4. + p1 male, p2 female, p3 male + + + + + %1 points from %2's %3 to %4. + p1 male, p2 male, p3 female + + + + + %1 points from %2's %3 to %4. + p1 male, p2 male, p3 male + + + + + %1 points from her %2 to her %3. + female + + + + + %1 points from his %2 to his %3. + male + + + + + %1 points from her %2 to %3's %4. + p1 female, p2 female + + + + + %1 points from her %2 to %3's %4. + p1 female, p2 male + + + + + %1 points from his %2 to %3's %4. + p1 male, p2 female + + + + + %1 points from his %2 to %3's %4. + p1 male, p2 male + + + + + %1 points from %2's %3 to her own %4. + card owner female, target female + + + + + %1 points from %2's %3 to her own %4. + card owner male, target female + + + + + %1 points from %2's %3 to his own %4. + card owner female, target male + + + + + %1 points from %2's %3 to his own %4. + card owner male, target male + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 female, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 female, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 male, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 male, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 female, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 female, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 male, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 male, p3 male + + + + + %1 places %n %2 counter(s) on %3 (now %4). + female + + + + + + + + + %1 places %n %2 counter(s) on %3 (now %4). + male + + + + + + + + + %1 removes %n %2 counter(s) from %3 (now %4). + female + + + + + + + + + %1 removes %n %2 counter(s) from %3 (now %4). + male + + + + + + + + + %1 taps her permanents. + female + + + + + %1 untaps her permanents. + female + + + + + %1 taps his permanents. + male + + + + + %1 untaps his permanents. + male + + + + + %1 taps %2. + female + + + + + %1 untaps %2. + female + + + + + %1 taps %2. + male + + + + + %1 untaps %2. + male + + + + + %1 sets counter %2 to %3 (%4%5). + female + + + + + %1 sets counter %2 to %3 (%4%5). + male + + + + + %1 sets %2 to not untap normally. + female + + + + + %1 sets %2 to not untap normally. + male + + + + + %1 sets %2 to untap normally. + female + + + + + %1 sets %2 to untap normally. + male + + + + + %1 sets PT of %2 to %3. + female + + + + + %1 sets PT of %2 to %3. + male + + + + + %1 sets annotation of %2 to %3. + female + + + + + %1 sets annotation of %2 to %3. + male + + + + + %1 is looking at %2. + female + + + + + %1 is looking at %2. + male + + + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + + + + %1 stops looking at %2. + female + + + + + %1 stops looking at %2. + male + + + + + %1 reveals %2 to %3. + p1 female, p2 female + + + + + %1 reveals %2 to %3. + p1 female, p2 male + + + + + %1 reveals %2 to %3. + p1 male, p2 female + + + + + %1 reveals %2 to %3. + p1 male, p2 male + + + + + %1 reveals %2. + female + + + + + %1 reveals %2. + male + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 randomly reveals %2%3. + female + + + + + %1 randomly reveals %2%3. + male + + + + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 reveals %2%3. + female + + + + + %1 reveals %2%3. + male + + + + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + + It is now %1's turn. + female + + + + + It is now %1's turn. + male + + + + + %1 draws his initial hand. + + + + + %1 draws her initial hand. + + + + + %1 attaches %2 to %3's %4. + p1 female, p2 female + + + + + %1 attaches %2 to %3's %4. + p1 female, p2 male + + + + + %1 attaches %2 to %3's %4. + p1 male, p2 female + + + + + %1 attaches %2 to %3's %4. + p1 male, p2 male + + + + + red + + czerwony + + + + + + yellow @@ -2738,71 +3033,71 @@ Local version is %1, remote version is %2. - + green - + zielony - + untap step - + upkeep step - + draw step - + first main phase - + beginning of combat step - + declare attackers step - + declare blockers step - + combat damage step - + end of combat step - + second main phase - + ending phase - + It is now the %1. @@ -2810,469 +3105,638 @@ Local version is %1, remote version is %2. MessagesSettingsPage - + Add message - + Dodaj wiadomość - + Message: - + Wiadomość: - + &Add - + &Dodaj - + &Remove - + &Usuń PhasesToolbar - - - Untap step - - - Upkeep step - + Untap step + Krok odtapowywania - Draw step + Upkeep step + No idea how to translate that, thinking. - First main phase - + Draw step + Krok dociągania - Beginning of combat step - + First main phase + Pierwsza faza główna - Declare attackers step - + Beginning of combat step + Początek walki - Declare blockers step - + Declare attackers step + Deklaracja ataku - Combat damage step - + Declare blockers step + Deklaracja obrony - End of combat step - + Combat damage step + Obrażenia - Second main phase - + End of combat step + Koniec walki + Second main phase + Druga faza główna + + + End of turn step - + Koniec tury Player - + &View graveyard - + &Pokaż cmentarz - + &View exile - + Player "%1" - + Translated as nominative, need to see in context. + Gracz "%1" - + &Graveyard - + Nominative again. + &Cmentarz - + &Exile - - - + + + Move to &top of library - - - + + + Move to &bottom of library - - + + Move to &graveyard - - + + Move to &exile - - + + Move to &hand - + &View library - + View &top cards of library... - + Reveal &library to - + Reveal t&op card to - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - + &Shuffle - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + &Hand - + &Reveal to - + Reveal r&andom card to - + &Sideboard - + &Library - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + + Cr&eate predefined token + + + + S&ay - + C&ard - + &All players - + + &Play + &Zagraj + + + + &Hide + &Ukryj + + + + &Tap + &Tapuj + + + + &Untap + &Odtapuj + + + + Toggle &normal untapping + Zmień &normalne odtapowywanie + + + + &Flip + &Odwróć + + + + &Peek at card face + + + + + &Clone + &Kopiuj + + + Ctrl+H + Ctrl+H + + + + Ctrl+J + + + + + Attac&h to card... + + + + + Ctrl+A + Ctrl+A + + + + Unattac&h + &Odłącz + + + + &Draw arrow... + &Narysuj strzałkę... + + + + &Increase power + Zwiększ &Atak + + + + Ctrl++ + Ctrl++ + + + + &Decrease power + Zmniejsz A&tak + + + + Ctrl+- + Ctrl+- + + + + I&ncrease toughness + Zwiększ &Obronę + + + + Alt++ + Alt++ + + + + D&ecrease toughness + Zmniejsz O&bronę + + + + Alt+- + Alt+- + + + + In&crease power and toughness + + + + + Ctrl+Alt++ + + + + + Dec&rease power and toughness + + + + + Ctrl+Alt+- + + + + + Set &power and toughness... + + + + + Ctrl+P + + + + + &Set annotation... + + + + + red + czerwony + + + + yellow + + + + + green + zielony + + + + &Add counter (%1) + + + + + &Remove counter (%1) + + + + + &Set counters (%1)... + + + + + &top of library + + + + + &bottom of library + + + + + &graveyard + + + + + Ctrl+Del + + + + + &exile + + + + Ctrl+F3 - + F3 - + F3 - + Ctrl+W - + F4 - + F4 - + Ctrl+D - + Ctrl+E - + Ctrl+Shift+D - + Ctrl+M - + Ctrl+S - + Ctrl+U - + Ctrl+I - + Ctrl+T - + Ctrl+G - + View top cards of library - + Number of cards: - + Draw cards - - - - + + + + Number: - + Move top cards to grave - + Move top cards to exile - + Roll die - + Number of sides: - + Set power/toughness - + Please enter the new PT: - + Set annotation - + Please enter the new annotation: - + Set counters - - PlayerListWidget - - - User &details - - - - - Direct &chat - - - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Kick from &game - - - QObject - - Maindeck + + Cockatrice replays (*.cor) - - Sideboard + + Common deck formats (*.cod *.dec *.mwDeck) - - Cockatrice decks (*.cod) - - - - - Plain text decks (*.dec *.mwDeck) - - - - + All files (*.*) @@ -3280,50 +3744,83 @@ Local version is %1, remote version is %2. RemoteDeckList_TreeModel - + Name - + Nazwa - + ID + ID + + + + Upload time + Data dodania + + + + RemoteReplayList_TreeModel + + + ID + ID + + + + Name + Nazwa + + + + Players + Gracze + + + + Keep - - Upload time + + Time started + + + + + Duration (sec) RoomSelector - + Rooms - + Joi&n - + Room - + Pokój - + Description - + Opis - + Players - + Gracze - + Games @@ -3333,38 +3830,28 @@ Local version is %1, remote version is %2. Short name - + Krótka nazwa Long name - + Krótka nazwa ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - - &OK - - - - - &Cancel - - - - + Shut down server @@ -3372,246 +3859,472 @@ Local version is %1, remote version is %2. TabAdmin - + Update server &message - + &Shut down server - + Server administration functions - + &Unlock functions - + &Lock functions - + Unlock administration functions - + Do you really want to unlock the administration functions? + + + TabDeckEditor - - Administration + + &Print deck... + + + + + &Close + + + + + Ctrl+Q + + + + + &Edit sets... + + + + + &Clear search + + + + + &Search for: + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Update prices + + + + + Ctrl+U + + + + + &New deck + + + + + &Load deck... + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Save deck to clip&board + + + + + &Analyze deck on deckstats.net + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + Show card text only + + + + + &Remove row + + + + + Del + + + + + &Increment number + + + + + + + + + + + &Decrement number + + + + + - + + + + + &Deck editor + + + + + C&ard database + + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + Jesteś pewien? + + + + The decklist has been modified. +Do you want to save the changes? + + + + + Load deck + + + + + + + Error + Błąd + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck TabDeckStorage - + Local file system - + Pliki lokalne - + Server deck storage - - + + Open in deck editor - + Otwórz w edytorze talii - + Upload deck - + Wyślij talię - + Download deck - + Ściągnij talię - - + + New folder - + Nowy folder - + + Delete - + Usuń - + Enter deck name - + Podaj nazwę talii - + This decklist does not have a name. Please enter a name: - + + Delete remote folder + + + + + Delete remote deck + + + + This decklist does not have a name.Please enter a name: + Ta talia nie ma nazwy. Proszę o podanie nazwy: + + + Unnamed deck + Nienazwana talia + + + + Delete local file - + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: - - - - - Deck storage - + Nazwa nowego folderu: TabGame - + F5 - + F5 - + F6 - + F6 - + F7 - + F7 - + F8 - + F8 - + F9 - + F9 - + F10 - + F10 - + &Phases - + &Game - + Next &phase - + Ctrl+Space - + + Tab + + + + Next &turn - + Ctrl+Return - + Ctrl+Enter - + &Remove all local arrows - + Ctrl+R - + + Game &information + + + + &Concede - + F2 - + F2 - + &Leave game - + + Ctrl+Q - - &Say: + + C&lose replay - + + &Say: + &Powiedz: + + + Concede - + Are you sure you want to concede this game? - + Leave game - + Are you sure you want to leave this game? - - Kicked + + Replay %1: %2 - - You have been kicked out of the game. - - - - + Game %1: %2 @@ -3619,434 +4332,354 @@ Please enter a name: TabMessage - + Personal &talk - + &Leave - + This user is ignoring you. - + %1 has left the server. - + %1 has joined the server. - + Talking to %1 + + TabReplays + + + Local file system + Pliki lokalne + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + Usuń + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: - + &Powiedz: - + Chat - + Czat - + &Room - + P&okój - + &Leave room + Op&uść pokój + + + + &Ignore unregistered users in chat - + You are flooding the chat. Please wait a couple of seconds. - + Spamujesz. Poczekaj chwilkę. TabServer - Server - + Serwer TabUserLists - User lists + Lista użytkowników + + + + Add to Buddy List + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + &Szczegóły użytkownika + + + + Direct &chat + &Czat + + + + Show this user's &games + &Pokaż gry + + + + Add to &buddy list + Dodaj do &znajomych + + + + Remove from &buddy list + &Usuń ze znajomych + + + + Add to &ignore list + &Ignoruj + + + + Remove from &ignore list + Przestań i&gnorować + + + + Kick from &game + + + + + Ban from &server + Z&banuj + + + + %1's games + Gry %1 + UserInfoBox - + User information - - - - - Real name: - - - - - Gender: - + Informacje użytkownika - Location: - + Real name: + Imię: + Gender: + Płeć: + + + + Location: + Lokacja: + + + User level: - + Rodzaj użytkownika: - + Administrator - + Administrator - + Moderator - + Moderator - + Registered user - + Zarejestrowany użytkownik - + Unregistered user - + Niezarejestrowany użytkownik UserInterfaceSettingsPage - + General interface settings - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings - + &Tap/untap animation - + Enable &sounds - + Path to sounds directory: - + Choose path - + Wybierz ścieżkę UserList - + Users online: %1 - + Użytkownicy online: %1 - + Users in this room: %1 - + Użytkownicy w tym pokoju: %1 - + Buddies online: %1 / %2 - + Znajomi online: %1 / %2 - + Ignored users online: %1 / %2 - + Ignorowani użytkownicy online: %1 / %2 - %1's games - + Gry %1 - User &details - + &Szczegóły użytkownika - Direct &chat - + &Czat - Show this user's &games - + &Pokaż gry - Add to &buddy list - + Dodaj do &znajomych - Remove from &buddy list - + &Usuń ze znajomych - Add to &ignore list - + &Ignoruj - Remove from &ignore list - + Przestań i&gnorować - Ban from &server - - - - - WndDeckEditor - - - &Search... - - - - - &Clear search - - - - - &Search for: - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &Update prices - - - - - Ctrl+U - - - - - Deck editor [*] - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clip&board - - - - - &Print deck... - - - - - &Close - - - - - Ctrl+Q - - - - - &Edit sets... - - - - - &Deck - - - - - &Card database - - - - - Add card to &maindeck - - - - - Return - - - - - Enter - - - - - Add card to &sideboard - - - - - Ctrl+Return - - - - - Ctrl+Enter - - - - - &Remove row - - - - - Del - - - - - &Increment number - - - - - + - - - - - &Decrement number - - - - - - - - - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - - Error - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - + Z&banuj @@ -4054,25 +4687,25 @@ Please check that the directory is writable and try again. Edit sets - + Edytuj edycje ZoneViewWidget - + sort by name - + sortuj według nazwy - + sort by type - + sortuj według typu - + shuffle when closing - + potasuj przy zamykaniu diff --git a/cockatrice/translations/cockatrice_pt-br.ts b/cockatrice/translations/cockatrice_pt-br.ts index d3c92603e..44a33758d 100644 --- a/cockatrice/translations/cockatrice_pt-br.ts +++ b/cockatrice/translations/cockatrice_pt-br.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... Alterar &marcador... - + Ctrl+L Ctrl+L - + F11 F11 - + F12 F12 - + Set counter Alterar marcador - + New value for counter '%1': Novo valor para o marcador '%1': @@ -37,57 +37,52 @@ AppearanceSettingsPage - + Zone background pictures Imagens de fundo das zonas - Path to hand background: - Caminho para o fundo da mão: + Caminho para o fundo da mão: - Path to stack background: - Caminho para o fundo da pilha: + Caminho para o fundo da pilha: - Path to table background: - Caminho para o fundo da mesa: + Caminho para o fundo da mesa: - Path to player info background: - Caminho para o fundo das informações do jogador: + Caminho para o fundo das informações do jogador: - Path to picture of card back: - Caminho para a imagem do verso dos cards: + Caminho para a imagem do verso dos cards: - + Card rendering Renderização do card - + Display card names on cards having a picture Mostrar o nome dos cards nos cards que tem imagem - + Hand layout Layout da mão - + Display hand horizontally (wastes space) Mostrar a mão na horizontal (desperdiça espaço) - + Table grid layout Layout do campo de batalha @@ -96,36 +91,61 @@ Layout econômico - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Invert vertical coordinate Inverter a coordenada vertical - + Minimum player count for multi-column layout: - + Zone view layout Layout de vista da zona - + Sort by name Ordenar por nome - + Sort by type Ordenar por tipo - - - - - + + + + + Choose path Escolher caminho @@ -139,78 +159,83 @@ Enter 0 for an indefinite ban. Digite 0 para banir indefinidamente. - + ban &user name - + ban &IP address - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK &OK - + &Cancel &Cancelar - + Ban user from server - + Error Erro - + You have to select a name-based or IP-based ban, or both. @@ -218,277 +243,263 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Nome - + Sets Expansões - + Mana cost Custo de mana - + Card type Tipo de card - + P/T P/R - CardInfoWidget + CardInfoText - - Hide card info - + + Name: + Nome: - + + Mana cost: + Custo de mana: + + + + Card type: + Tipo de card: + + + + P / T: + P / R: + + + + Loyalty: + + + + + CardInfoWidget + + Show card only - + Show text only - + Show full info - + Name: Nome: - + Mana cost: Custo de mana: - + Card type: Tipo de card: - + P / T: P / R: + + + Loyalty: + + CardItem - &Play - &Jogar + &Jogar - &Hide - &Ocultar + &Ocultar - &Tap - &Virar + &Virar - &Untap - &Desvirar + &Desvirar - Toggle &normal untapping - &Trocar o modo de desvirar + &Trocar o modo de desvirar - &Flip - Virar a &face + Virar a &face - &Clone - Clo&nar + Clo&nar - Ctrl+H - Ctrl+H + Ctrl+H - &Attach to card... - Ane&xar ao card... + Ane&xar ao card... - Ctrl+A - Ctrl+A + Ctrl+A - Unattac&h - De&sanexar + De&sanexar Set &P/T... Alterar &P/R... - - &Draw arrow... - - - - + &Power / toughness Po&der / resistência - &Increase power - Au&mentar poder + Au&mentar poder - Ctrl++ - Ctrl++ + Ctrl++ - &Decrease power - Dimi&nuir poder + Dimi&nuir poder - Ctrl+- - Ctrl+- + Ctrl+- - I&ncrease toughness - A&umentar resistência + A&umentar resistência - Alt++ - Alt++ + Alt++ - D&ecrease toughness - D&iminuir resistência + D&iminuir resistência - Alt+- - Alt+- + Alt+- - In&crease power and toughness - Aumen&tar poder e resistência + Aumen&tar poder e resistência - Ctrl+Alt++ - Ctrl+Alt++ + Ctrl+Alt++ - Dec&rease power and toughness - Diminuir p&oder e resistência + Diminuir p&oder e resistência - Ctrl+Alt+- - Ctrl+Alt+- + Ctrl+Alt+- - Set &power and toughness... - Alterar poder e resis&tência... + Alterar poder e resis&tência... - Ctrl+P - Ctrl+P + Ctrl+P - &Set annotation... - Alterar &nota... + Alterar &nota... - red - vermelho + vermelho - yellow - amarelo + amarelo - green - verde + verde - &Add counter (%1) - Adicionar &marcador (%1) + Adicionar &marcador (%1) - &Remove counter (%1) - &Remover marcador (%1) + &Remover marcador (%1) - &Set counters (%1)... - &Alterar marcadores (%1)... + &Alterar marcadores (%1)... - &top of library - topo do &grimório + topo do &grimório - &bottom of library - &fundo do grimório + &fundo do grimório - &graveyard - &cemitério + &cemitério - Ctrl+Del - Ctrl+Del + Ctrl+Del - &exile - &exílio + &exílio - + &Move to Mo&ver para @@ -556,364 +567,394 @@ This is only saved for moderators and cannot be seen by the banned person.grimório de %1 - + her hand nominative, female owner - + %1's hand nominative, female owner mão de %1 - + his hand nominative, male owner sua mão - - - %1's hand - nominative, male owner - mão de %1 - - - - of her hand - genitive, female owner - - - - - of %1's hand - genitive, female owner - da mão de %1 - - - - of his hand - genitive, male owner - da sua mão - - - - of %1's hand - genitive, male owner - da mão de %1 - - - - her hand - accusative, female owner - - - - - %1's hand - accusative, female owner - mão de %1 - - - - his hand - accusative, male owner - sua mão - - - - %1's hand - accusative, male owner - mão de %1 - + %1's hand + nominative, male owner + mão de %1 + + + her library - nominative, female owner + look at zone, female owner - + %1's library - nominative, female owner + look at zone, female owner grimório de %1 - + his library - nominative, male owner + look at zone, male owner seu grimório - + %1's library - nominative, male owner + look at zone, male owner grimório de %1 - + of her library - genitive, female owner + top cards of zone, female owner - + of %1's library - genitive, female owner + top cards of zone, female owner do grimório de %1 - + of his library - genitive, male owner + top cards of zone, male owner do seu grimório - + of %1's library - genitive, male owner + top cards of zone, male owner do grimório de %1 - + her library - accusative, female owner + reveal zone, female owner - + %1's library - accusative, female owner + reveal zone, female owner grimório de %1 - + his library - accusative, male owner + reveal zone, male owner seu grimório - + %1's library - accusative, male owner + reveal zone, male owner grimório de %1 - + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + grimório de %1 + + + + his library + shuffle, male owner + seu grimório + + + + %1's library + shuffle, male owner + grimório de %1 + + + + her sideboard + look at zone, female owner + + + + + %1's sideboard + look at zone, female owner + sideboard de %1 + + + + his sideboard + look at zone, male owner + seu sideboard + + + + %1's sideboard + look at zone, male owner + sideboard de %1 + + + of %1's hand + genitive, female owner + da mão de %1 + + + of his hand + genitive, male owner + da sua mão + + + of %1's hand + genitive, male owner + da mão de %1 + + + %1's hand + accusative, female owner + mão de %1 + + + his hand + accusative, male owner + sua mão + + + %1's hand + accusative, male owner + mão de %1 + + + + her library + nominative, female owner + + + + + %1's library + nominative, female owner + grimório de %1 + + + + his library + nominative, male owner + seu grimório + + + + %1's library + nominative, male owner + grimório de %1 + + + of %1's library + genitive, female owner + do grimório de %1 + + + of his library + genitive, male owner + do seu grimório + + + of %1's library + genitive, male owner + do grimório de %1 + + + %1's library + accusative, female owner + grimório de %1 + + + his library + accusative, male owner + seu grimório + + + %1's library + accusative, male owner + grimório de %1 + + + her graveyard nominative, female owner - + %1's graveyard nominative, female owner cemitério de %1 - + his graveyard nominative, male owner seu cemitério - + %1's graveyard nominative, male owner cemitério de %1 - - of her graveyard - genitive, female owner - - - - of %1's graveyard genitive, female owner - do cemitério de %1 + do cemitério de %1 - of his graveyard genitive, male owner - do seu cemitério + do seu cemitério - of %1's graveyard genitive, male owner - do cemitério de %1 + do cemitério de %1 - - her graveyard - accusative, female owner - - - - %1's graveyard accusative, female owner - cemitério de %1 + cemitério de %1 - his graveyard accusative, male owner - seu cemitério + seu cemitério - %1's graveyard accusative, male owner - cemitério de %1 + cemitério de %1 - + her exile nominative, female owner - + %1's exile nominative, female owner exílio de %1 - + his exile nominative, male owner seu exílio - + %1's exile nominative, male owner exílio de %1 - - of her exile - genitive, female owner - - - - of %1's exile genitive, female owner - do exílio de %1 + do exílio de %1 - of his exile genitive, male owner - do seu exílio + do seu exílio - of %1's exile genitive, male owner - do exílio de %1 + do exílio de %1 - - her exile - accusative, female owner - - - - %1's exile accusative, female owner - exílio de %1 + exílio de %1 - his exile accusative, male owner - seu exílio + seu exílio - %1's exile accusative, male owner - exílio de %1 + exílio de %1 - + her sideboard nominative, female owner - + %1's sideboard nominative, female owner sideboard de %1 - + his sideboard nominative, male owner seu sideboard - + %1's sideboard nominative, male owner sideboard de %1 - - of her sideboard - genitive, female owner - - - - of %1's sideboard genitive, female owner - do sideboard de %1 + do sideboard de %1 - of his sideboard genitive, male owner - do seu sideboard + do seu sideboard - of %1's sideboard genitive, male owner - do sideboard de %1 + do sideboard de %1 - - her sideboard - accusative, female owner - - - - %1's sideboard accusative, female owner - sideboard de %1 + sideboard de %1 - his sideboard accusative, male owner - seu sideboard + seu sideboard - %1's sideboard accusative, male owner - sideboard de %1 + sideboard de %1 his graveyard @@ -1057,14 +1098,45 @@ This is only saved for moderators and cannot be seen by the banned person. - DeckEditorSettingsPage + DBPriceUpdater - - Enable &price tag feature (using data from blacklotusproject.com) + + + + Error + Erro + + + + + A problem has occured while fetching card prices. - + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General Geral @@ -1072,43 +1144,77 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Number Número - + Card Card - + Price + + DeckStatsInterface + + + + Error + Erro + + + + The reply from the server could not be parsed. + + + DeckViewContainer - + Load &local deck Carregar dec&k local - + Load d&eck from server Carregar deck do &servidor - + Ready to s&tart &Pronto para começar - + + S&ideboard unlocked + + + + + S&ideboard locked + + + + Load deck Carregar deck + + + Error + Erro + + + + The selected file could not be loaded. + + Deck_PictureCacher @@ -1158,34 +1264,37 @@ This is only saved for moderators and cannot be seen by the banned person. DlgConnect - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Nome do &jogador: - + P&assword: S&enha: - - &OK - &OK + + &Save password + + + + &OK + &OK - &Cancel - &Cancelar + &Cancelar @@ -1196,87 +1305,90 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + &Description: &Descrição: - + &Password: S&enha: - + P&layers: &Jogadores: - + Game type Tipo de jogo - + Only &buddies can join Apenas ami&gos podem entrar - + Only &registered users can join Apenas usuários re&gistrados podem entrar - + Joining restrictions Restrições para entrar - + &Spectators allowed &Permitir visitantes - + Spectators &need a password to join Visitantes &precisam de senha para entrar - + Spectators can &chat Visitantes podem c&onversar - + Spectators see &everything Visitantes podem ver &tudo - + Spectators Visitantes - &OK - &OK + &OK - &Cancel - &Cancelar + &Cancelar - + Create game Criar jogo - + + Game information + + + + Error Erro - + Server error. Erro do servidor. @@ -1284,115 +1396,281 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: &Nome: - + Token Ficha - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolorido - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + &Destroy token when it leaves the table Destruir a &ficha quando ela sair do campo de batalha - + + Token data + + + + + Show &all tokens + + + + + Show tokens from this &deck + + + + + Choose token from list + + + &OK - &OK + &OK - &Cancel - &Cancelar + &Cancelar - + Create token Criar ficha + + DlgEditTokens + + + &Name: + &Nome: + + + + C&olor: + C&or: + + + + white + branco + + + + blue + azul + + + + black + preto + + + + red + + + + + green + + + + + multicolor + multicolorido + + + + colorless + incolor + + + + &P/T: + &P/R: + + + + &Annotation: + &Nota: + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + Erro + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + O&K + &OK + + + &Cancel + &Cancelar + + + + Filter games + + + DlgLoadDeckFromClipboard - + &Refresh &Atualizar - &OK - &OK + &OK - &Cancel - &Cancelar + &Cancelar - + Load deck from clipboard Carregar deck da área de transferência - + Error Erro - + Invalid deck list. Lista de deck inválida. @@ -1400,17 +1678,15 @@ This is only saved for moderators and cannot be seen by the banned person. DlgLoadRemoteDeck - O&K - &OK + &OK - &Cancel - &Cancelar + &Cancelar - + Load deck Carregar deck @@ -1418,158 +1694,225 @@ This is only saved for moderators and cannot be seen by the banned person. DlgSettings - - - + + + Error Erro - Your card database is invalid. Would you like to go back and set the correct path? - O seu banco de dados de cards é inválido. Você gostaria de voltar e corrigir o caminho? + O seu banco de dados de cards é inválido. Você gostaria de voltar e corrigir o caminho? - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de decks é inválido. Você gostaria de voltar e corrigir o caminho? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de imagens de cards é inválido. Você gostaria de voltar e corrigir o caminho? - + Settings Configurações - + General Geral - + Appearance Aparência - + User interface Interface do usuário - + Deck editor - + Messages Mensagens - &Close - &Fechar + &Fechar GameSelector - + C&reate &Criar - + &Join &Entrar - - - - - - - - + + + + + + + + + Error Erro - + Please join the appropriate room first. - + Wrong password. Senha incorreta. - + Spectators are not allowed in this game. Não são permitidos visitantes neste jogo. - + The game is already full. O jogo está cheio. - + The game does not exist any more. O jogo não existe mais. - + This game is only open to registered users. Este jogo é aberto apenas para usuários registrados. - + This game is only open to its creator's buddies. Este jogo é aberto apenas para os amigos de quem criou o jogo. - + You are being ignored by the creator of this game. Você está sendo ignorado pelo criador deste jogo. - + Join game Entrar no jogo - + Password: Senha: - + + Please join the respective room first. + + + + Games Jogos - - Show &full games - &Mostrar os jogos cheios + + &Filter games + - - Show &running games + + C&lear filter + + Show &full games + &Mostrar os jogos cheios + &Show full games &Mostrar os jogos cheios - + J&oin as spectator E&ntrar como visitante @@ -1585,72 +1928,72 @@ This is only saved for moderators and cannot be seen by the banned person. GamesModel - + yes sim - + no não - + Creator Criador - + Description Descrição - + yes, free for spectators sim, livre para visitantes - + buddies only apenas amigos - + reg. users only usuários reg. apenas - + not allowed não permitidos - + Room Sala - + Game type Tipo de jogo - + Password Senha - + Restrictions Restrições - + Players Jogadores - + Spectators Visitantes @@ -1658,50 +2001,96 @@ This is only saved for moderators and cannot be seen by the banned person. GeneralSettingsPage - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path Escolher caminho - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Erro + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings Configurações pessoais - + Language: Língua: - + Download card pictures on the fly Baixar a imagem dos cards em tempo real - + + Download high-quality card pictures + + + + Paths Caminhos - + Decks directory: Pasta de decks: - + + Replays directory: + + + + Pictures directory: Pasta de imagens: - - Path to card database: - Caminho para o banco de dados dos cards: + + Card database: + - - + + Token database: + + + + Path to card database: + Caminho para o banco de dados dos cards: + + + + English Português do Brasil @@ -1709,102 +2098,120 @@ This is only saved for moderators and cannot be seen by the banned person. MainWindow - + Number of players Número de jogadores - + Please enter the number of players. Por favor, entre o número de jogadores. - - + + Player %1 Jogador %1 - + About Cockatrice Sobre o Cockatrice - + <font size="8"><b>Cockatrice</b></font><br>Version %1<br><br><br><b>Authors:</b><br>Max-Wilhelm Bruker<br>Marcus Schütz<br>Marius van Zundert<br><br><b>Translators:</b><br>Spanish: Gocho<br>Portugese: Milton Gonçalves<br> <font size="8"><b>Cockatrice</b></font><br>Version %1<br><br><br><b>Authors:</b><br>Max-Wilhelm Bruker<br>Marcus Schütz<br>Marius van Zundert<br><br><b>Translators:</b><br>Spanish: Gocho<br>Portugese: Milton Gonçalves<br>Brazilian Portuguese: Thiago Queiroz<br> - + Version %1 Versão %1 - + Authors: Autores: - + Translators: Tradutores: - + Spanish: Espanhol: - + Portugese (Portugal): Português (Portugal): - + Portugese (Brazil): Português (Brasil): - + French: Francês: - + Japanese: Japonês: - + Russian: Russo: - + Italian: - - - - - - + + + + + + + + + + Error Erro - + Server timeout Tempo esgotado do servidor - Invalid login data. - Informações de login inválidas. + Informações de login inválidas. - + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + Unknown login error: %1 + + + + Socket error: %1 Erro de ligação:%1 @@ -1813,44 +2220,63 @@ This is only saved for moderators and cannot be seen by the banned person.Versão dos protocolos incompatível. Versão local:%1, versão remota:%2. - + There are too many concurrent connections from your address. Há conexões concorrentes demais vinds do seu endereço. - Banned by moderator. - Banido pelo moderador. + Banido pelo moderador. - + Scheduled server shutdown. - Unknown reason. - Razão desconhecida. + Razão desconhecida. - + + Banned by moderator + + + + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + Connection closed Conexão fechada - + The server has terminated your connection. Reason: %1 O servidor terminou sua conexão. Razão: %1 - + Scheduled server shutdown - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1860,108 +2286,133 @@ Reason for shutdown: %1 - - Czech: + + Load replay - + + Swedish: + + + + + Incorrect username or password. Please check your authentication information and try again. + + + + There is already an active session using this user name. Please close that session first and re-login. Já existe uma sessão ativa usando este nome de usuário. Por favor, feche a sessão primeiro e logue novamente. - + + This server requires user registration. + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Você está tentando conectar a um servidor obsoleto. Por favor, faça um downgrade na versão do seu Cockatrice ou conecte-se ao servidor correto. A versão local é %1 e a versão remota é %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. A versão do seu Cockatrice é obsoleta. Por favor, atualize a sua versão. A versão local é %1 e a versão remota é %2. - + Connecting to %1... Conectando a %1... - + Disconnected Desconectado - + + Connected, logging in at %1 + + + + Logged in at %1 Logado em %1 - + &Connect... &Conectar... - + &Disconnect &Desconectar - + Start &local game... Iniciar jogo &local... - + + &Watch replay... + + + + &Deck editor Editor de &decks - + &Full screen Tela &cheia - + Ctrl+F Ctrl+F - + &Settings... &Configurações... - + &Exit &Sair - + &Cockatrice &Cockatrice - + &About Cockatrice So&bre o Cockatrice - + &Help &Ajuda - + Are you sure? Você tem certeza? - + There are still open games. Are you sure you want to quit? Ainda existem jogos abertos. Você tem certeza que deseja sair? @@ -1985,17 +2436,22 @@ A versão local é %1 e a versão remota é %2. Erro de protocolo. - + The game has been closed. O jogo foi fechado. - + + You have been kicked out of the game. + Você foi chutado do jogo. + + + %1 is now watching the game. %1 está assistindo o jogo agora. - + %1 is not watching the game any more. %1 não está mais assistindo o jogo. @@ -2015,147 +2471,159 @@ A versão local é %1 e a versão remota é %2. - + You have joined game #%1. female Você entrou no jogo nº %1. - - - You have joined game #%1. - male - Você entrou no jogo nº %1. - - - - %1 has joined the game. - female - %1 entrou no jogo. - - - - %1 has joined the game. - male - %1 entrou no jogo. - - %1 has left the game. - female - %1 saiu do jogo. - - - - %1 has left the game. + You have joined game #%1. male - %1 saiu do jogo. + Você entrou no jogo nº %1. - %1 has loaded a local deck. - female - %1 carregou um deck local. - - - %1 has loaded a local deck. - male - %1 carregou um deck local. - - - %1 has loaded deck #%2. - female - %1 carregou o deck nº %2. - - - %1 has loaded deck #%2. - male - %1 carregou o deck nº %2. - - - - %1 is ready to start the game. - female - %1 está pronto para começar o jogo. - - - - %1 is ready to start the game. - male - %1 está pronto para começar o jogo. - - - - %1 is not ready to start the game any more. - female - %1 não está mais pronto para começar o jogo. - - - - %1 is not ready to start the game any more. - male - %1 não está mais pronto para começar o jogo. - - - - %1 has conceded the game. - female - %1 concedeu o jogo. - - - - %1 has conceded the game. - male - %1 concedeu o jogo. - - - - %1 has restored connection to the game. + + You are watching a replay of game #%1. female + + + You are watching a replay of game #%1. + male + + + + + %1 has joined the game. + female + %1 entrou no jogo. + + + + %1 has joined the game. + male + %1 entrou no jogo. + + + + %1 has left the game. + female + %1 saiu do jogo. + + + + %1 has left the game. + male + %1 saiu do jogo. + + + %1 has loaded a local deck. + female + %1 carregou um deck local. + + + %1 has loaded a local deck. + male + %1 carregou um deck local. + + + %1 has loaded deck #%2. + female + %1 carregou o deck nº %2. + + + %1 has loaded deck #%2. + male + %1 carregou o deck nº %2. + + %1 is ready to start the game. + female + %1 está pronto para começar o jogo. + + + + %1 is ready to start the game. + male + %1 está pronto para começar o jogo. + + + + %1 is not ready to start the game any more. + female + %1 não está mais pronto para começar o jogo. + + + + %1 is not ready to start the game any more. + male + %1 não está mais pronto para começar o jogo. + + + + %1 has conceded the game. + female + %1 concedeu o jogo. + + + + %1 has conceded the game. + male + %1 concedeu o jogo. + + + + %1 has restored connection to the game. + female + + + + %1 has restored connection to the game. male - + %1 has lost connection to the game. female - + %1 has lost connection to the game. male - + %1 shuffles %2. female - + %1 shuffles %2. male - + %1 rolls a %2 with a %3-sided die. female %1 tirou um %2 com um dado de %3 lados. - + %1 rolls a %2 with a %3-sided die. male %1 tirou um %2 com um dado de %3 lados. - + %1 draws %n card(s). female @@ -2164,7 +2632,7 @@ A versão local é %1 e a versão remota é %2. - + %1 draws %n card(s). male @@ -2173,182 +2641,182 @@ A versão local é %1 e a versão remota é %2. - + %1 undoes his last draw. %1 desfaz sua última compra. - + %1 undoes her last draw. - + %1 undoes his last draw (%2). %1 desfaz sua última compra (%2). - + %1 undoes her last draw (%2). - + from table vindo do campo de batalha - + from graveyard vindo do cemitério - + from exile vindo do exílio - + from hand vindo da mão - + the bottom card of his library o card do fundo do seu grimório - + the bottom card of her library - + from the bottom of his library vindo do fundo do seu grimório - + from the bottom of her library - + the top card of his library o card do topo do seu grimório - + the top card of her library - + from the top of his library vindo do topo do seu grimório - + from the top of her library - + from library vindo do grimório - + from sideboard vindo do sideboard - + from the stack vindo da pilha - + %1 gives %2 control over %3. %1 dá controle para %2 sobre %3. - + %1 puts %2 into play tapped%3. %1 põe %2 em jogo virado%3. - + %1 puts %2 into play%3. %1 põe %2 no campo de batalha %3. - + %1 puts %2%3 into graveyard. %1 põe %2 no cemitério%3. - + %1 exiles %2%3. %1 exila %2%3. - + %1 moves %2%3 to hand. %1 move %2 para a mão%3. - + %1 puts %2%3 into his library. %1 põe %2 no seu grimório%3. - + %1 puts %2%3 into her library. - + %1 puts %2%3 on bottom of his library. %1 põe %2 no fundo do seu grimório%3. - + %1 puts %2%3 on bottom of her library. - + %1 puts %2%3 on top of his library. %1 põe %2 no topo do seu grimório%3. - + %1 puts %2%3 on top of her library. - + %1 puts %2%3 into his library at position %4. %1 põe %2 no seu grimório na posição %4%3. - + %1 puts %2%3 into her library at position %4. - + %1 moves %2%3 to sideboard. %1 move %2 para o sideboard%3. - + %1 plays %2%3. %1 põe %2 na pilha%3. - + %1 takes a mulligan to %n. female @@ -2357,7 +2825,7 @@ A versão local é %1 e a versão remota é %2. - + %1 takes a mulligan to %n. male @@ -2366,37 +2834,37 @@ A versão local é %1 e a versão remota é %2. - + %1 flips %2 face-down. female %1 vira %2 para baixo. - + %1 flips %2 face-down. male %1 vira %2 para baixo. - + %1 flips %2 face-up. female %1 vira %2 para cima. - + %1 flips %2 face-up. male %1 vira %2 para cima. - + %1 destroys %2. female %1 destrói %2. - + %1 destroys %2. male %1 destrói %2. @@ -2412,283 +2880,340 @@ A versão local é %1 e a versão remota é %2. %1 anexa %2 a %4 de %3. - %1 has loaded a deck (%2). female - %1 carregou o deck %2. + %1 carregou o deck %2. - %1 has loaded a deck (%2). male + %1 carregou o deck %2. + + + + %1 has loaded a deck (%2). %1 carregou o deck %2. - + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + the bottom card of %1's library + + + + + from the bottom of %1's library + + + + + the top card of %1's library + + + + + from the top of %1's library + + + + + from %1's library + + + + %1 attaches %2 to %3's %4. p1 female, p2 female %1 anexa %2 a %4 de %3. - + %1 attaches %2 to %3's %4. p1 female, p2 male %1 anexa %2 a %4 de %3. - + %1 attaches %2 to %3's %4. p1 male, p2 female %1 anexa %2 a %4 de %3. - + %1 attaches %2 to %3's %4. p1 male, p2 male %1 anexa %2 a %4 de %3. - + %1 unattaches %2. female %1 desanexa %2. - + %1 unattaches %2. male %1 desanexa %2. - + %1 creates token: %2%3. female %1 cria a ficha: %2%3. - + %1 creates token: %2%3. male %1 cria a ficha: %2%3. - + %1 points from her %2 to herself. female - + %1 points from his %2 to himself. male - + %1 points from her %2 to %3. p1 female, p2 female - + %1 points from her %2 to %3. p1 female, p2 male - + %1 points from his %2 to %3. p1 male, p2 female - + %1 points from his %2 to %3. p1 male, p2 male - + %1 points from %2's %3 to herself. card owner female, target female - + %1 points from %2's %3 to herself. card owner male, target female - + %1 points from %2's %3 to himself. card owner female, target male - + %1 points from %2's %3 to himself. card owner male, target male - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female %1 aponta para %4 com %3 de %2 . - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male %1 aponta para %4 com %3 de %2 . - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female %1 aponta para %4 com %3 de %2 . - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male %1 aponta para %4 com %3 de %2 . - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female %1 aponta para %4 com %3 de %2 . - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male %1 aponta para %4 com %3 de %2 . - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female %1 aponta para %4 com %3 de %2 . - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male %1 aponta para %4 com %3 de %2 . - + %1 points from her %2 to her %3. female - + %1 points from his %2 to his %3. male - + %1 points from her %2 to %3's %4. p1 female, p2 female - + %1 points from her %2 to %3's %4. p1 female, p2 male - + %1 points from his %2 to %3's %4. p1 male, p2 female - + %1 points from his %2 to %3's %4. p1 male, p2 male - + %1 points from %2's %3 to her own %4. card owner female, target female - + %1 points from %2's %3 to her own %4. card owner male, target female - + %1 points from %2's %3 to his own %4. card owner female, target male - + %1 points from %2's %3 to his own %4. card owner male, target male - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female %1 aponta para %5 de %4 com %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male %1 aponta para %5 de %4 com %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female %1 aponta para %5 de %4 com %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male %1 aponta para %5 de %4 com %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female %1 aponta para %5 de %4 com %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male %1 aponta para %5 de %4 com %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female %1 aponta para %5 de %4 com %3 de %2. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male %1 aponta para %5 de %4 com %3 de %2. - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -2697,7 +3222,7 @@ A versão local é %1 e a versão remota é %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -2706,7 +3231,7 @@ A versão local é %1 e a versão remota é %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -2715,7 +3240,7 @@ A versão local é %1 e a versão remota é %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -2724,272 +3249,322 @@ A versão local é %1 e a versão remota é %2. - + %1 taps her permanents. female - + %1 untaps her permanents. female - + %1 taps his permanents. male - + %1 untaps his permanents. male - + %1 taps %2. female - + %1 untaps %2. female - - - %1 taps %2. - male - - - - - %1 untaps %2. - male - - - - - %1 sets counter %2 to %3 (%4%5). - female - %1 altera o marcador %2 para %3 (%4%5). - - - - %1 sets counter %2 to %3 (%4%5). - male - %1 altera o marcador %2 para %3 (%4%5). - - - - %1 sets %2 to not untap normally. - female - %1 define que %2 não desvira normalmente. - - - - %1 sets %2 to not untap normally. - male - %1 define que %2 não desvira normalmente. - - - - %1 sets %2 to untap normally. - female - %1 define que %2 desvira normalmente. - - - - %1 sets %2 to untap normally. - male - %1 define que %2 desvira normalmente. - - - - %1 sets PT of %2 to %3. - female - %1 altera o P/R de %2 para %3. - - - - %1 sets PT of %2 to %3. - male - %1 altera o P/R de %2 para %3. - - - - %1 sets annotation of %2 to %3. - female - %1 altera a nota de %2 para%3. - - - - %1 sets annotation of %2 to %3. - male - %1 altera a nota de %2 para%3. - - - - %1 is looking at the top %2 cards %3. - female - %1 está olhando para os %2 cards do topo %3. - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - %1 está olhando para os %2 cards do topo %3. + - - %1 is looking at %2. - female - %1 está olhando para %2. - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 está olhando para %2. + - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female - %1 para de olhar para %2. + %1 altera o marcador %2 para %3 (%4%5). + %1 sets counter %2 to %3 (%4%5). + male + %1 altera o marcador %2 para %3 (%4%5). + + + + %1 sets %2 to not untap normally. + female + %1 define que %2 não desvira normalmente. + + + + %1 sets %2 to not untap normally. + male + %1 define que %2 não desvira normalmente. + + + + %1 sets %2 to untap normally. + female + %1 define que %2 desvira normalmente. + + + + %1 sets %2 to untap normally. + male + %1 define que %2 desvira normalmente. + + + + %1 sets PT of %2 to %3. + female + %1 altera o P/R de %2 para %3. + + + + %1 sets PT of %2 to %3. + male + %1 altera o P/R de %2 para %3. + + + + %1 sets annotation of %2 to %3. + female + %1 altera a nota de %2 para%3. + + + + %1 sets annotation of %2 to %3. + male + %1 altera a nota de %2 para%3. + + + %1 is looking at the top %2 cards %3. + female + %1 está olhando para os %2 cards do topo %3. + + + %1 is looking at the top %2 cards %3. + male + %1 está olhando para os %2 cards do topo %3. + + + + %1 is looking at %2. + female + %1 está olhando para %2. + + + + %1 is looking at %2. + male + %1 está olhando para %2. + + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + + + %1 stops looking at %2. + female + %1 para de olhar para %2. + + + %1 stops looking at %2. male %1 para de olhar para %2. - - - %1 reveals %2 to %3. - p1 female, p2 female - %1 revela %2 para %3. - - - - %1 reveals %2 to %3. - p1 female, p2 male - %1 revela %2 para %3. - - - - %1 reveals %2 to %3. - p1 male, p2 female - %1 revela %2 para %3. - - - - %1 reveals %2 to %3. - p1 male, p2 male - %1 revela %2 para %3. - - - - %1 reveals %2. - female - %1 revela %2. - - - - %1 reveals %2. - male - %1 revela %2. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - %1 revela aleatoriamente %2%3. para %4. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - %1 revela aleatoriamente %2%3. para %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - %1 revela aleatoriamente %2%3. para %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - %1 revela aleatoriamente %2%3. para %4. - - - - %1 randomly reveals %2%3. - female - %1 revela aleatoriamente %2%3. - - - - %1 randomly reveals %2%3. - male - %1 revela aleatoriamente %2%3. - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - %1 revela %2%3 para %4. - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - %1 revela %2%3 para %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - %1 revela %2%3 para %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - %1 revela %2%3 para %4. - - - - %1 reveals %2%3. - female - %1 revela %2%3. - - - - %1 reveals %2%3. - male - %1 revela %2%3. - - - - It is now %1's turn. - female - Agora é o turno de %1. - + %1 reveals %2 to %3. + p1 female, p2 female + %1 revela %2 para %3. + + + + %1 reveals %2 to %3. + p1 female, p2 male + %1 revela %2 para %3. + + + + %1 reveals %2 to %3. + p1 male, p2 female + %1 revela %2 para %3. + + + + %1 reveals %2 to %3. + p1 male, p2 male + %1 revela %2 para %3. + + + + %1 reveals %2. + female + %1 revela %2. + + + + %1 reveals %2. + male + %1 revela %2. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + %1 revela aleatoriamente %2%3. para %4. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + %1 revela aleatoriamente %2%3. para %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + %1 revela aleatoriamente %2%3. para %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + %1 revela aleatoriamente %2%3. para %4. + + + + %1 randomly reveals %2%3. + female + %1 revela aleatoriamente %2%3. + + + + %1 randomly reveals %2%3. + male + %1 revela aleatoriamente %2%3. + + + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + %1 revela %2%3 para %4. + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + %1 revela %2%3 para %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + %1 revela %2%3 para %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + %1 revela %2%3 para %4. + + + + %1 reveals %2%3. + female + %1 revela %2%3. + + + + %1 reveals %2%3. + male + %1 revela %2%3. + + + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + + It is now %1's turn. + female + Agora é o turno de %1. + + + It is now %1's turn. male Agora é o turno de %1. - - + + a card um card @@ -3028,7 +3603,7 @@ A versão local é %1 e a versão remota é %2. - + red vermelho @@ -3036,7 +3611,7 @@ A versão local é %1 e a versão remota é %2. - + yellow amarelo @@ -3044,7 +3619,7 @@ A versão local é %1 e a versão remota é %2. - + green verde @@ -3068,7 +3643,7 @@ A versão local é %1 e a versão remota é %2. %1 está olhando para os %2 cards do topo %3. - + The game has started. O jogo começou. @@ -3163,7 +3738,7 @@ A versão local é %1 e a versão remota é %2. %1 revela %2. - + ending phase fase final @@ -3176,12 +3751,12 @@ A versão local é %1 e a versão remota é %2. %1 embaralha o seu grimório. - + %1 draws his initial hand. - + %1 draws her initial hand. @@ -3202,57 +3777,57 @@ A versão local é %1 e a versão remota é %2. %1 revela %2%3. - + untap step etapa de desvirar - + upkeep step etapa de manutenção - + draw step etapa de compra - + first main phase primeira fase principal - + beginning of combat step etapa de início de combate - + declare attackers step etapa de declaracão de atacantes - + declare blockers step etapa de declaração de bloqueadores - + combat damage step etapa de dano de combate - + end of combat step etapa de fim de combate - + second main phase segunda fase principal - + It is now the %1. Agora é a %1. @@ -3280,22 +3855,22 @@ A versão local é %1 e a versão remota é %2. MessagesSettingsPage - + &Add &Adicionar - + &Remove &Remover - + Add message Adicionar mensagem - + Message: Mensagem: @@ -3303,57 +3878,57 @@ A versão local é %1 e a versão remota é %2. PhasesToolbar - + Untap step Etapa de desvirar - + Upkeep step Etapa de manutenção - + Draw step Etapa de compra - + First main phase Primeira fase principal - + Beginning of combat step Etapa de início de combate - + Declare attackers step Etapa de declaracão de atacantes - + Declare blockers step Etapa de declaração de bloqueadores - + Combat damage step Etapa de dano de combate - + End of combat step Etapa de fim de combate - + Second main phase Segunda fase principal - + End of turn step Etapa de fim de combate @@ -3361,322 +3936,536 @@ A versão local é %1 e a versão remota é %2. Player - - - + + + Move to &top of library Mover para o &topo do grimório - - - + + + Move to &bottom of library Mover para o &fundo do grimório - - + + Move to &graveyard Mover para o &cemitério - + &View library &Ver grimório - + Reveal &library to Revelar o &grimório para - + Reveal t&op card to Revelar o card do t&opo para - + Move top cards to &graveyard... Mover os cards do topo para o ce&mitério... - + + Ctrl+J + + + + F3 F3 - + View &top cards of library... Ver os cards do to&po do grimório... - + &View graveyard V&er cemitério - + F4 F4 - + &View sideboard &Ver sideboard - + Player "%1" Jogador "%1" - + &Hand &Mão - + &Library &Grimório - + &Graveyard &Cemitério - + &Sideboard &Sideboard - + View top cards of library Ver os cards do topo do grimório - + Number of cards: Número de cards: - + &Draw card Co&mprar card - + &View exile &Ver exílio - + &Exile &Exílio - - + + Move to &hand Mo&ver para a mão - - + + Move to &exile Mover para o &exílio - + Ctrl+W Ctrl+W - + Ctrl+D Ctrl+D - + D&raw cards... Comprar car&ds... - + Ctrl+E Ctrl+E - + Take &mulligan Pedir mu&lligan - + Ctrl+M Ctrl+M - + &Shuffle &Embaralhar - + Ctrl+S Ctrl+S - + &Counters &Marcadores - + &Untap all permanents Des&virar todos as permanentes - + Ctrl+U Ctrl+U - + R&oll die... &Jogar dado... - + Ctrl+I Ctrl+I - + &Create token... Criar fich&a... - + Ctrl+T Ctrl+T - + C&reate another token Criar &outra ficha - + Ctrl+G Ctrl+G - + S&ay &Falar - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &Undo last draw Desfa&zer última compra - + Move top cards to &exile... Mover os cards do topo para o e&xílio... - + Put top card on &bottom Colocar o card do topo no &fundo - + &Reveal to Re&velar para - + Reveal r&andom card to Revelar card alea&tório para - + + Cr&eate predefined token + + + + C&ard C&ard - + &All players To&dos os jogadores - + + &Play + &Jogar + + + + &Hide + &Ocultar + + + + &Tap + &Virar + + + + &Untap + &Desvirar + + + + Toggle &normal untapping + &Trocar o modo de desvirar + + + + &Flip + Virar a &face + + + + &Peek at card face + + + + + &Clone + Clo&nar + + + Ctrl+H + Ctrl+H + + + + Attac&h to card... + + + + + Ctrl+A + Ctrl+A + + + + Unattac&h + De&sanexar + + + + &Draw arrow... + + + + + &Increase power + Au&mentar poder + + + + Ctrl++ + Ctrl++ + + + + &Decrease power + Dimi&nuir poder + + + + Ctrl+- + Ctrl+- + + + + I&ncrease toughness + A&umentar resistência + + + + Alt++ + Alt++ + + + + D&ecrease toughness + D&iminuir resistência + + + + Alt+- + Alt+- + + + + In&crease power and toughness + Aumen&tar poder e resistência + + + + Ctrl+Alt++ + Ctrl+Alt++ + + + + Dec&rease power and toughness + Diminuir p&oder e resistência + + + + Ctrl+Alt+- + Ctrl+Alt+- + + + + Set &power and toughness... + Alterar poder e resis&tência... + + + + Ctrl+P + Ctrl+P + + + + &Set annotation... + Alterar &nota... + + + + red + + + + + yellow + + + + + green + + + + + &Add counter (%1) + Adicionar &marcador (%1) + + + + &Remove counter (%1) + &Remover marcador (%1) + + + + &Set counters (%1)... + &Alterar marcadores (%1)... + + + + &top of library + topo do &grimório + + + + &bottom of library + &fundo do grimório + + + + &graveyard + &cemitério + + + + Ctrl+Del + Ctrl+Del + + + + &exile + &exílio + + + Ctrl+F3 Ctrl+F3 - + Ctrl+Shift+D Ctrl+Shift+D - + Draw cards Comprar cards - - - - + + + + Number: Número: - + Move top cards to grave Mover os cards do topo para o cemitério - + Move top cards to exile Mover os cards do topo para o exílio - + Roll die Jogar dado - + Number of sides: Número de lados: - + Set power/toughness Alterar poder/resistência - + Please enter the new PT: Por favor, entre com o novo P/R: - + Set annotation Alterar nota - + Please enter the new annotation: Por favor, entre com a nova nota: - + Set counters Alterar marcadores @@ -3712,116 +4501,148 @@ A versão local é %1 e a versão remota é %2. deck #%1 - User &details - &Detalhes do usuário + &Detalhes do usuário - Direct &chat - &Chat direto + &Chat direto - Add to &buddy list - Adicionar à &lista de amigos + Adicionar à &lista de amigos - Remove from &buddy list - Remover da li&sta de amigos + Remover da li&sta de amigos - Add to &ignore list - Adicionar à li&sta dos ignorados + Adicionar à li&sta dos ignorados - Remove from &ignore list - Remover da lista dos i&gnorados + Remover da lista dos i&gnorados - Kick from &game - C&hutar do jogo + C&hutar do jogo QObject - Maindeck - Deck principal + Deck principal - Sideboard - Sideboard + Sideboard - Cockatrice decks (*.cod) - Decks Cockatrice (*.cod) + Decks Cockatrice (*.cod) - Plain text decks (*.dec *.mwDeck) - Decks de texto simples (*.dec *.mwDeck) + Decks de texto simples (*.dec *.mwDeck) - + + Common deck formats (*.cod *.dec *.mwDeck) + + + + All files (*.*) - Todos os arquivos (*.*) + Todos os arquivos (*.*) + + + + Cockatrice replays (*.cor) + RemoteDeckList_TreeModel - + Name Nome - + ID ID - + Upload time Hora do upload + + RemoteReplayList_TreeModel + + + ID + ID + + + + Name + Nome + + + + Players + Jogadores + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + RoomSelector - + Rooms Salas - + Joi&n &Entrar - + Room Sala - + Description Descrição - + Players Jogadores - + Games Jogos @@ -3849,27 +4670,25 @@ A versão local é %1 e a versão remota é %2. ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - &OK - &OK + &OK - &Cancel - &Cancelar + &Cancelar - + Shut down server @@ -3877,44 +4696,43 @@ A versão local é %1 e a versão remota é %2. TabAdmin - + Update server &message &Atualizar mensagem do servidor - + &Shut down server - + Server administration functions Funções do administrador do servidor - + &Unlock functions &Desbloquear funções - + &Lock functions &Bloquear funções - + Unlock administration functions Desbloquear funções do administrador - + Do you really want to unlock the administration functions? Você quer mesmo desbloquear as funções do administrador? - Administration - Administração + Administração @@ -3936,207 +4754,468 @@ A versão local é %1 e a versão remota é %2. %1 saiu do canal. + + TabDeckEditor + + &Search... + B&uscar... + + + + Show card text only + + + + + &Clear search + &Limpar busca + + + + &Search for: + &Buscar por: + + + + Deck &name: + Nome do &deck: + + + + &Comments: + &Comentários: + + + + Hash: + + + + + &Update prices + + + + + Ctrl+U + Ctrl+U + + + + &New deck + &Novo deck + + + + &Load deck... + &Abrir deck... + + + + &Save deck + &Salvar deck + + + + Save deck &as... + Salvar deck c&omo... + + + + Load deck from cl&ipboard... + Carregar deck da área de &transferência... + + + + Save deck to clip&board + Salvar deck para a área de t&ransferência + + + + &Print deck... + &Imprimir deck... + + + + &Analyze deck on deckstats.net + + + + + &Close + &Fechar + + + + Ctrl+Q + Ctrl+Q + + + + Add card to &maindeck + Incluir no deck &principal + + + Return + Return + + + Enter + Enter + + + + Add card to &sideboard + Incluir no side&board + + + Ctrl+Return + Ctrl+Return + + + Ctrl+Enter + Ctrl+Enter + + + + &Remove row + &Apagar linha + + + + Del + Del + + + + &Increment number + &Aumentar quantidade + + + + + + + + + + + &Decrement number + &Diminuir quantidade + + + + - + - + + + + &Deck editor + Editor de &decks + + + + C&ard database + + + + + &Edit sets... + E&ditar expansões... + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + Você tem certeza? + + + + The decklist has been modified. +Do you want to save the changes? + O deck foi modificado. +Você deseja salvar as alterações? + + + + Load deck + + + + + + + Error + Erro + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + O deck não pôde ser salvo. +Por favor, verifique se o diretório não é somente leitura e tente novamente. + + + + Save deck + Salvar deck + + TabDeckStorage - + Local file system Sistema de arquivos local - + Server deck storage Armazenamento de decks no servidor - - + + Open in deck editor Abrir no editor de decks - + Upload deck Upload do deck - + Download deck Download do deck - - + + New folder Nova pasta - + + Delete Apagar - + Enter deck name Entre o nome do deck - + This decklist does not have a name. Please enter a name: Este deck não tem um nome. Por favor, entre um nome: - + Unnamed deck Deck sem nome - + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: Nome da nova pasta: - + + Delete remote folder + + + + + Delete remote deck + + + Deck storage - Armazenamento de decks + Armazenamento de decks TabGame - + F5 F5 - + F6 F6 - + F7 F7 - + F8 F8 - + F9 F9 - + F10 F10 - + &Phases &Etapas - + &Game &Jogo - + Next &phase Próxima &etapa - + Ctrl+Space Ctrl+Espaço - + + Tab + + + + Next &turn Próximo &turno - + Ctrl+Return Ctrl+Return - + Ctrl+Enter Ctrl+Enter - + &Remove all local arrows &Apagar todas as setas locais - + Ctrl+R Ctrl+R - + + Game &information + + + + &Concede &Conceder - + F2 F2 - + &Leave game &Sair do jogo - + + Ctrl+Q Ctrl+Q - + + C&lose replay + + + + &Say: &Falar: - + Concede Conceder - + Are you sure you want to concede this game? Você tem certeza que deseja conceder este jogo? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Você tem certeza que deseja sair deste jogo? - + + Replay %1: %2 + + + Kicked - Chutado + Chutado - You have been kicked out of the game. - Você foi chutado do jogo. + Você foi chutado do jogo. - + Game %1: %2 Jogo %1: %2 @@ -4144,60 +5223,120 @@ Por favor, entre um nome: TabMessage - + Personal &talk Chat &privado - + &Leave &Sair - + This user is ignoring you. Este usuário está ignorando você. - + %1 has left the server. %1 saiu do servidor. - + %1 has joined the server. %1 entrou no servidor. - + Talking to %1 Falando com %1 + + TabReplays + + + Local file system + Sistema de arquivos local + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + Apagar + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: &Falar: - + Chat Chat - + &Room &Sala - + &Leave room S&air da sala - + + &Ignore unregistered users in chat + + + + You are flooding the chat. Please wait a couple of seconds. Você está flodando o chat. Por favor, espere alguns segundos. @@ -4213,53 +5352,114 @@ Por favor, entre um nome: TabServer - Server - Servidor + Servidor TabUserLists - User lists - Listas de usuários + Listas de usuários + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + &Detalhes do usuário + + + + Direct &chat + &Chat direto + + + + Show this user's &games + + + + + Add to &buddy list + Adicionar à &lista de amigos + + + + Remove from &buddy list + Remover da li&sta de amigos + + + + Add to &ignore list + Adicionar à li&sta dos ignorados + + + + Remove from &ignore list + Remover da lista dos i&gnorados + + + + Kick from &game + C&hutar do jogo + + + + Ban from &server + Ban&ir do servidor + + + + %1's games + UserInfoBox - + User information Informação do usuário - + Real name: Nome real: - + Gender: - + Location: Localização: - + User level: Nível do usuário: - + Administrator Administrador - + Moderator @@ -4268,12 +5468,12 @@ Por favor, entre um nome: Juiz - + Registered user Usuário registrado - + Unregistered user Usuário não registrado @@ -4281,37 +5481,47 @@ Por favor, entre um nome: UserInterfaceSettingsPage - + General interface settings Configurações gerais de interface - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) &Duplo clique nos cards para jogá-los (ao invés de clique simples) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings Configurações de animação - + &Tap/untap animation Animação de &virar/desvirar - + Enable &sounds - + Path to sounds directory: - + Choose path Escolher caminho @@ -4319,69 +5529,52 @@ Por favor, entre um nome: UserList - + Users online: %1 Usuários online: %1 - + Users in this room: %1 Usuários nesta sala: %1 - + Buddies online: %1 / %2 Amigos online: %1 / %2 - + Ignored users online: %1 / %2 Usuários ignorados online: %1 / %2 - - %1's games - - - - User &details - &Detalhes do usuário + &Detalhes do usuário - Direct &chat - &Chat direto + &Chat direto - - Show this user's &games - - - - Add to &buddy list - Adicionar à &lista de amigos + Adicionar à &lista de amigos - Remove from &buddy list - Remover da li&sta de amigos + Remover da li&sta de amigos - Add to &ignore list - Adicionar à li&sta dos ignorados + Adicionar à li&sta dos ignorados - Remove from &ignore list - Remover da lista dos i&gnorados + Remover da lista dos i&gnorados - Ban from &server - Ban&ir do servidor + Ban&ir do servidor Duration @@ -4397,204 +5590,155 @@ Digite 0 para banir indefinidamente. WndDeckEditor - &Search for: - &Buscar por: + &Buscar por: - Deck &name: - Nome do &deck: + Nome do &deck: - &Comments: - &Comentários: + &Comentários: - Deck editor [*] - Editor de decks [*] + Editor de decks [*] - &New deck - &Novo deck + &Novo deck - &Load deck... - &Abrir deck... + &Abrir deck... - Load deck from cl&ipboard... - Carregar deck da área de &transferência... + Carregar deck da área de &transferência... - &Save deck - &Salvar deck + &Salvar deck - - Hash: - - - - - &Update prices - - - - Ctrl+U - Ctrl+U + Ctrl+U - Save deck &as... - Salvar deck c&omo... + Salvar deck c&omo... - Save deck to clip&board - Salvar deck para a área de t&ransferência + Salvar deck para a área de t&ransferência - &Print deck... - &Imprimir deck... + &Imprimir deck... - &Close - &Fechar + &Fechar - Ctrl+Q - Ctrl+Q + Ctrl+Q - &Edit sets... - E&ditar expansões... + E&ditar expansões... - &Deck - &Deck + &Deck - Load deck - Abrir deck + Abrir deck - - Error - Erro + Erro - - The deck could not be saved. Please check that the directory is writable and try again. - O deck não pôde ser salvo. + O deck não pôde ser salvo. Por favor, verifique se o diretório não é somente leitura e tente novamente. - Save deck - Salvar deck + Salvar deck - Add card to &maindeck - Incluir no deck &principal + Incluir no deck &principal - Return - Return + Return - Enter - Enter + Enter - Ctrl+Return - Ctrl+Return + Ctrl+Return - Ctrl+Enter - Ctrl+Enter + Ctrl+Enter - Add card to &sideboard - Incluir no side&board + Incluir no side&board - &Search... - B&uscar... + B&uscar... - &Clear search - &Limpar busca + &Limpar busca - &Card database - Banco de dados de &cards + Banco de dados de &cards - &Remove row - &Apagar linha + &Apagar linha - Del - Del + Del - &Increment number - &Aumentar quantidade + &Aumentar quantidade - + - + + + - &Decrement number - &Diminuir quantidade + &Diminuir quantidade - - - - + - - Are you sure? - Você tem certeza? + Você tem certeza? - The decklist has been modified. Do you want to save the changes? - O deck foi modificado. + O deck foi modificado. Você deseja salvar as alterações? @@ -4609,17 +5753,17 @@ Você deseja salvar as alterações? ZoneViewWidget - + sort by name ordenar por nome - + sort by type ordenar por tipo - + shuffle when closing embaralhar quando fechar diff --git a/cockatrice/translations/cockatrice_pt.ts b/cockatrice/translations/cockatrice_pt.ts index c85d16f5c..8364309f7 100644 --- a/cockatrice/translations/cockatrice_pt.ts +++ b/cockatrice/translations/cockatrice_pt.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... Definir &marcador... - + Ctrl+L Ctrl+L - + F11 F11 - + F12 F12 - + Set counter Definir marcador - + New value for counter '%1': Novo valor para o marcador '%1': @@ -37,57 +37,52 @@ AppearanceSettingsPage - + Zone background pictures Zona das imagens de fundo - Path to hand background: - Directório da imagem de fundo da mão: + Directório da imagem de fundo da mão: - Path to stack background: - Directório da imagem de fundo da pilha: + Directório da imagem de fundo da pilha: - Path to table background: - Directório da imagem de fundo do campo de batalha: + Directório da imagem de fundo do campo de batalha: - Path to player info background: - Directório da imagem de fundo da informação de jogador: + Directório da imagem de fundo da informação de jogador: - Path to picture of card back: - Directório da imagem do verso da carta: + Directório da imagem do verso da carta: - + Card rendering Rendering da carta - + Display card names on cards having a picture Mostrar o nome em cartas com imagem - + Hand layout Disposição da Mão - + Display hand horizontally (wastes space) Mostrar mão horizontalmente (desperdiça espaço) - + Table grid layout Esquema da mesa @@ -96,36 +91,61 @@ Esquema económico - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Invert vertical coordinate Inverter coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de kogadores para layout com múltiplas colunas: - + Zone view layout Distribuição da zona de vizualização - + Sort by name Ordenar por nome - + Sort by type Ordenar por tipo - - - - - + + + + + Choose path Escolher directório @@ -139,357 +159,356 @@ Enter 0 for an indefinite ban. Introduza 0 para um banimento indefinido. - + ban &user name - + Banir nome de &utilizador - + ban &IP address - + Banir endereço de &IP - + Ban type - + Tipo de banimento - + &permanent ban - + Banimento &permanente - + &temporary ban - - - - - &Days: - + Banimento &temporário - &Hours: - + &Days: + &Dias: + &Hours: + &Horas: + + + &Minutes: - + &Minutos: - + Duration of the ban - + Duração do banimento - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor introduza o motivo do banimento. Isto apenas é guardado para os moderadores e não é visível para a pessoa banida. - + + Please enter the reason for the ban that will be visible to the banned person. + Por favor introduza o motivo do banimento que será visível à pessoa banida. + + + &OK - + &Cancel &Cancelar - + Ban user from server Banir utilizador do servidor - + Error - Erro + Erro - + You have to select a name-based or IP-based ban, or both. - + Tem de seleccionar um banimento baseado no nome, no IP ou ambos. CardDatabaseModel - + Name Nome - + Sets Expansões - + Mana cost Custo de mana - + Card type Tipo de carta - + P/T P/R - CardInfoWidget + CardInfoText - - Hide card info - Esconder informação da carta + + Name: + Nome: - + + Mana cost: + Custo de Mana: + + + + Card type: + Tipo de carta: + + + + P / T: + P / R: + + + + Loyalty: + Lealdade: + + + + CardInfoWidget + + Hide card info + Esconder informação da carta + + + Show card only Mostrar informação da carta - + Show text only Mostrar apenas texto - + Show full info Mostrar informação completa - + Name: Nome: - + Mana cost: Custo de Mana: - + Card type: Tipo de carta: - + P / T: P / R: + + + Loyalty: + Lealdade: + CardItem - &Play - &Jogar + &Jogar - &Hide - Esco&nder + Esco&nder - &Tap - &Virar + &Virar - &Untap - Desv&irar + Desv&irar - Toggle &normal untapping - A&lterar desvirar normalmente + A&lterar desvirar normalmente - &Flip - Vol&tar + Vol&tar - &Clone - Copi&ar + Copi&ar - Ctrl+H - Ctrl+H + Ctrl+H - &Attach to card... - Ane&xar a carta... + Ane&xar a carta... - Ctrl+A - Ctrl+A + Ctrl+A - Unattac&h - De&sanexar + De&sanexar Set &P/T... Definir &P/R... - &Draw arrow... - Desen&har seta... + Desen&har seta... - + &Power / toughness &Poder / resistência - &Increase power - &Aumentar poder + &Aumentar poder - Ctrl++ - Ctrl++ + Ctrl++ - &Decrease power - &Diminuir poder + &Diminuir poder - Ctrl+- - Ctrl+- + Ctrl+- - I&ncrease toughness - A&umentar resistência + A&umentar resistência - Alt++ - Alt++ + Alt++ - D&ecrease toughness - Di&minuir resistência + Di&minuir resistência - Alt+- - Alt+- + Alt+- - In&crease power and toughness - Aumen&tar poder e resistência + Aumen&tar poder e resistência - Ctrl+Alt++ - Ctrl+Alt++ + Ctrl+Alt++ - Dec&rease power and toughness - Dimin&uir poder e resistência + Dimin&uir poder e resistência - Ctrl+Alt+- - Ctrl+Alt+- + Ctrl+Alt+- - Set &power and toughness... - Definir &poder e resistência... + Definir &poder e resistência... - Ctrl+P - Ctrl+P + Ctrl+P - &Set annotation... - Colocar &nota... + Colocar &nota... - red - vermelho + vermelho - yellow - amarelo + amarelo - green - verde + verde - &Add counter (%1) - Adicionar &marcador (%1) + Adicionar &marcador (%1) - &Remove counter (%1) - &Remover marcador (%1) + &Remover marcador (%1) - &Set counters (%1)... - &Denifir marcadores (%1)... + &Denifir marcadores (%1)... - &top of library - Topo do &grimório + Topo do &grimório - &bottom of library - &Fundo do grimório + &Fundo do grimório - &graveyard - &Cemitério + &Cemitério - Ctrl+Del - Ctrl+Del + Ctrl+Del - &exile - &Exílio + &Exílio - + &Move to M&over para @@ -557,364 +576,444 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban grimório de %1 - + her hand nominative, female owner sua mão - + %1's hand nominative, female owner mão de %1 - + his hand nominative, male owner sua mão - + %1's hand nominative, male owner mão de %1 - + + her library + look at zone, female owner + seu grimório + + + + %1's library + look at zone, female owner + grimório de %1 + + + + his library + look at zone, male owner + seu grimório + + + + %1's library + look at zone, male owner + grimório de %1 + + + + of her library + top cards of zone, female owner + do seu grimório + + + + of %1's library + top cards of zone, female owner + do grimório de %1 + + + + of his library + top cards of zone, male owner + do seu grimório + + + + of %1's library + top cards of zone, male owner + do grimório de %1 + + + + her library + reveal zone, female owner + seu grimório + + + + %1's library + reveal zone, female owner + grimório de %1 + + + + his library + reveal zone, male owner + seu grimório + + + + %1's library + reveal zone, male owner + grimório de %1 + + + + her library + shuffle, female owner + seu grimório + + + + %1's library + shuffle, female owner + grimório de %1 + + + + his library + shuffle, male owner + seu grimório + + + + %1's library + shuffle, male owner + grimório de %1 + + + + her sideboard + look at zone, female owner + seu sideboard + + + + %1's sideboard + look at zone, female owner + sideboard de %1 + + + + his sideboard + look at zone, male owner + seu sideboard + + + + %1's sideboard + look at zone, male owner + sideboard de %1 + + of her hand genitive, female owner - da sua mão + da sua mão - of %1's hand genitive, female owner - da mão de %1 + da mão de %1 - of his hand genitive, male owner - da sua mão + da sua mão - of %1's hand genitive, male owner - da mão de %1 + da mão de %1 - her hand accusative, female owner - sua mão + sua mão - %1's hand accusative, female owner - mão de %1 + mão de %1 - his hand accusative, male owner - sua mão + sua mão - %1's hand accusative, male owner - mão de %1 + mão de %1 - + her library nominative, female owner seu grimório - + %1's library nominative, female owner grimório de %1 - + his library nominative, male owner seu grimório - + %1's library nominative, male owner grimório de %1 - of her library genitive, female owner - do seu grimório + do seu grimório - of %1's library genitive, female owner - do grimório de %1 + do grimório de %1 - of his library genitive, male owner - do seu grimório + do seu grimório - of %1's library genitive, male owner - do grimório de %1 + do grimório de %1 - her library accusative, female owner - seu grimório + seu grimório - %1's library accusative, female owner - grimório de %1 + grimório de %1 - his library accusative, male owner - seu grimório + seu grimório - %1's library accusative, male owner - grimório de %1 + grimório de %1 - + her graveyard nominative, female owner seu cemitério - + %1's graveyard nominative, female owner cemitério de %1 - + his graveyard nominative, male owner seu cemitério - + %1's graveyard nominative, male owner cemitério de %1 - of her graveyard genitive, female owner - do seu cemitério + do seu cemitério - of %1's graveyard genitive, female owner - do cemitério de %1 + do cemitério de %1 - of his graveyard genitive, male owner - do seu cemitério + do seu cemitério - of %1's graveyard genitive, male owner - do cemitério de %1 + do cemitério de %1 - her graveyard accusative, female owner - seu cemitério + seu cemitério - %1's graveyard accusative, female owner - cemitério de %1 + cemitério de %1 - his graveyard accusative, male owner - seu cemitério + seu cemitério - %1's graveyard accusative, male owner - cemitério de %1 + cemitério de %1 - + her exile nominative, female owner seu exílio - + %1's exile nominative, female owner exílio de %1 - + his exile nominative, male owner seu exílio - + %1's exile nominative, male owner exílio de %1 - of her exile genitive, female owner - do seu exílio + do seu exílio - of %1's exile genitive, female owner - do exílio de %1 + do exílio de %1 - of his exile genitive, male owner - do seu exílio + do seu exílio - of %1's exile genitive, male owner - do exílio de %1 + do exílio de %1 - her exile accusative, female owner - seu exílio + seu exílio - %1's exile accusative, female owner - exílio de %1 + exílio de %1 - his exile accusative, male owner - seu exílio + seu exílio - %1's exile accusative, male owner - exílio de %1 + exílio de %1 - + her sideboard nominative, female owner seu sideboard - + %1's sideboard nominative, female owner sideboard de %1 - + his sideboard nominative, male owner seu sideboard - + %1's sideboard nominative, male owner sideboard de %1 - of her sideboard genitive, female owner - do seu sideboard + do seu sideboard - of %1's sideboard genitive, female owner - do sideboard de %1 + do sideboard de %1 - of his sideboard genitive, male owner - do seu sideboard + do seu sideboard - of %1's sideboard genitive, male owner - do sideboard de %1 + do sideboard de %1 - her sideboard accusative, female owner - seu sideboard + seu sideboard - %1's sideboard accusative, female owner - sideboard de %1 + sideboard de %1 - his sideboard accusative, male owner - seu sideboard + seu sideboard - %1's sideboard accusative, male owner - sideboard de %1 + sideboard de %1 his graveyard @@ -1058,14 +1157,49 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban - DeckEditorSettingsPage + DBPriceUpdater - - Enable &price tag feature (using data from blacklotusproject.com) - &Permitir função de tag de preços (utilizando informação de blacklotusproject.com) + + + + Error + Erro - + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + Enable &price tag feature (using data from blacklotusproject.com) + &Permitir função de tag de preços (utilizando informação de blacklotusproject.com) + + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General Geral @@ -1073,43 +1207,77 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckListModel - + Number Número - + Card Carta - + Price Preço + + DeckStatsInterface + + + + Error + Erro + + + + The reply from the server could not be parsed. + A resposta do servidor não pôde ser analisada. + + DeckViewContainer - + Load &local deck Carregar deck l&ocal - + Load d&eck from server Carregar deck do &servidor - + Ready to s&tart &Pronto para começar - + + S&ideboard unlocked + S&ideboard desbloqueado + + + + S&ideboard locked + S&ideboard bloqueado + + + Load deck Carregar deck + + + Error + Erro + + + + The selected file could not be loaded. + O ficheiro seleccionado não pôde ser carregado. + Deck_PictureCacher @@ -1159,34 +1327,37 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DlgConnect - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: P&assword: - - &OK - &OK + + &Save password + &Guardar password + + + &OK + &OK - &Cancel - &Cancelar + &Cancelar @@ -1197,87 +1368,90 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DlgCreateGame - + &Description: &Descrição: - + &Password: &Password: - + P&layers: &Jogadores: - + Game type Tipo de jogo - + Only &buddies can join Apenas &amigos podem entrar - + Only &registered users can join Apenas utilizadores &registados podem entrar - + Joining restrictions Restrições para ligar - + &Spectators allowed &Espectadores permitidos - + Spectators &need a password to join Espectadores &necessitam de password para aceder - + Spectators can &chat Espectadores podem c&onversar - + Spectators see &everything Espectadores podem ver &tudo - + Spectators Espectadores - &OK - O&K + O&K - &Cancel - &Cancelar + &Cancelar - + Create game Criar jogo - + + Game information + Informação do jogo + + + Error Erro - + Server error. Erro do servidor. @@ -1285,115 +1459,277 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DlgCreateToken - + &Name: &Nome: - + Token Ficha - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + &Destroy token when it leaves the table &Destruir ficha quando ela deixar a mesa - + + Token data + Informação de ficha + + + + Show &all tokens + Mostrar &todas as fichas + + + + Show tokens from this &deck + Mostrar &fichas deste deck + + + + Choose token from list + Escolha ficha da lista + + &OK - O&K + O&K - &Cancel - &Cancelar + &Cancelar - + Create token Criar ficha - DlgLoadDeckFromClipboard + DlgEditTokens - - &Refresh - &Refrescar + + &Name: + &Nome: - - &OK - O&K + + C&olor: + C&or: - - &Cancel - &Cancelar + + white + branco - - Load deck from clipboard - Carregar deck da memória + + blue + azul - + + black + preto + + + + red + vermelho + + + + green + verde + + + + multicolor + multicolor + + + + colorless + incolor + + + + &P/T: + &P/R: + + + + &Annotation: + &Nota: + + + + Token data + Informação de ficha + + + + + Add token + Adicionar ficha + + + + Remove token + Remover ficha + + + + Edit tokens + Editar fichas + + + + Please enter the name of the token: + Por favor introduza o nome da ficha: + + + Error Erro - + + The chosen name conflicts with an existing card or token. + O nome escolhido entra em conflicto com uma carta ou ficha existentes. + + + + DlgFilterGames + + + Show &unavailable games + Mostrar jogos &indisponíveis + + + + Show &password protected games + + + + + Game &description: + &Descrição de jogo: + + + + &Creator name: + &Nome do criador: + + + + &Game types + &Tipo de jogo + + + + at &least: + no &mínimo: + + + + at &most: + no m&áximo: + + + + Maximum player count + Limite máximo de jogadores + + + &Cancel + &Cancelar + + + + Filter games + Filtrar jogos + + + + DlgLoadDeckFromClipboard + + + &Refresh + &Refrescar + + + &OK + O&K + + + &Cancel + &Cancelar + + + + Load deck from clipboard + Carregar deck da memória + + + + Error + Erro + + + Invalid deck list. Lista de deck inválida. @@ -1401,17 +1737,15 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DlgLoadRemoteDeck - O&K - O&K + O&K - &Cancel - &Cancelar + &Cancelar - + Load deck Carregar deck @@ -1419,158 +1753,229 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DlgSettings - - - + + + Error Erro - Your card database is invalid. Would you like to go back and set the correct path? - A sua base de dados é inválida. Gostaria de voltar atrás e corrigir o directório? + A sua base de dados é inválida. Gostaria de voltar atrás e corrigir o directório? - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? O directório do seu deck é inválido. Gostaria de voltar atrás e corrigir o directório? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O directório das imagens das cartas é inválido. Gostaria de voltar atrás e corrigir o directório? - + Settings Definições - + General Geral - + Appearance Aparência - + User interface Interface do utilizador - + Deck editor Editor de Decks - + Messages Mensagens - &Close - &Fechar + &Fechar GameSelector - - - - - - - - + + + + + + + + + Error Erro - + Please join the appropriate room first. Por favor entre na sala apropriada primeiro. - + Wrong password. Password incorrecta. - + Spectators are not allowed in this game. Não são permitidos espectadores neste jogo. - + The game is already full. O jogo já se encontra cheio. - + The game does not exist any more. O jogo já não existe. - + This game is only open to registered users. Este jogo só está aberto a utilizadores registados. - + This game is only open to its creator's buddies. Este jogo só está aberto aos amigos do seu criador. - + You are being ignored by the creator of this game. Você está a ser ignorado pelo criador deste jogo. - + Join game Entrar no jogo - + Password: Password: - + + Please join the respective room first. + Por favor entre na sala respectiva primeiro. + + + Games Jogos - - Show &full games - &Mostrar jogos cheios + + &Filter games + &Filtrar jogos + + + + C&lear filter + &Limpar filtros + + + Show &full games + &Mostrar jogos cheios - Show &running games - Mostrar jogos a &decorrer + Mostrar jogos a &decorrer &Show full games &Mostrar jogos cheios - + C&reate &Criar - + &Join &Entrar - + J&oin as spectator Entrar como &espectador @@ -1586,72 +1991,72 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban GamesModel - + yes sim - + yes, free for spectators sim, livre para espectadores - + no não - + buddies only amigos apenas - + reg. users only utilizadores registados apenas - + not allowed não permitidos - + Room Sala - + Description Descrição - + Creator Criador - + Game type Tipo de jogo - + Password Password - + Restrictions Restrições - + Players Jogadores - + Spectators Espectadores @@ -1659,98 +2064,148 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban GeneralSettingsPage - - + + English Português - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path Escolher directório - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Erro + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings Defenições pessoais - + Language: Língua: - + Download card pictures on the fly Baixar a imagem das cartas ao passar - + + Download high-quality card pictures + + + + Paths Directórios - + Decks directory: Directório dos decks: - + + Replays directory: + Directório de replays: + + + Pictures directory: Directório das imagens: - + + Card database: + + + + + Token database: + + + Path to card database: - Directório da base de dados de cartas: + Directório da base de dados de cartas: + + + Path to token database: + Directório da base de dados de fichas: MainWindow - + Number of players Número de jogadores - + Please enter the number of players. Por favor introduza o número de jogadores. - - + + Player %1 Jogador %1 - + About Cockatrice Sobre o Cockatrice - + <font size="8"><b>Cockatrice</b></font><br>Version %1<br><br><br><b>Authors:</b><br>Max-Wilhelm Bruker<br>Marcus Schütz<br>Marius van Zundert<br><br><b>Translators:</b><br>Spanish: Gocho<br>Portugese: Milton Gonçalves<br> <font size="8"><b>Cockatrice</b></font><br>Versão %1<br><br><br><b>Autores:</b><br>Max-Wilhelm Bruker<br>Marcus Schütz<br>Marius van Zundert<br><br><b>Tradutores:</b><br>Espanhol: Gocho<br>Português: Milton Gonçalves<br> - + Version %1 Versão %1 - + Authors: Autores: - + Translators: Tradutores: - + Spanish: Espanhol: @@ -1759,57 +2214,75 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban Português: - + Portugese (Portugal): Português (Portugal): - + Portugese (Brazil): Português (Brasil): - + French: Francês: - + Japanese: Japonês: - + Russian: Russo: - + Italian: - + Italiano: - - - - - - + + + + + + + + + + Error Erro - + Server timeout Tempo do servidor esgotado - Invalid login data. - Informação de login incorrecta. + Informação de login incorrecta. - + + You are banned until %1. + Está banido até %1. + + + + You are banned indefinitely. + Está banido indefinidamente. + + + + Unknown login error: %1 + Erro de login desconhecido:%1 + + + Socket error: %1 Erro de ligação:%1 @@ -1818,44 +2291,42 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban Versão dos protocolos incompatível. Versão local:%1, versão remota:%2. - + There are too many concurrent connections from your address. Há demasiadas ligações concorrentes do seu endereço. - Banned by moderator. - Banido por um moderador. + Banido por um moderador. - + Scheduled server shutdown. Encerramento do servidor agendado. - Unknown reason. - Razão desconhecida. + Razão desconhecida. - + Connection closed Ligação terminada - + The server has terminated your connection. Reason: %1 O servidor terminou a sua ligação. Motivo: %1 - + Scheduled server shutdown Encerramento do servidor agendado - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1867,112 +2338,162 @@ Motivo para o encerramento: %1 - Czech: - Checo: + Checo: Slovak: Eslovaco: - + + Banned by moderator + Banido por moderador + + + + Expected end time: %1 + Tempo previsto para o final:%1 + + + + This ban lasts indefinitely. + Este banimento dura indefinidamente. + + + + + Invalid username. + + + + + Load replay + Carregar replay + + + + Swedish: + Sueco: + + + + Incorrect username or password. Please check your authentication information and try again. + + + + There is already an active session using this user name. Please close that session first and re-login. Já existe uma sessão activa com este nome de utilizador. Por favor termine essa sessão e volte a ligar-se. - + + This server requires user registration. + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Está a tentar ligar-se a um servidor obsoleto. Por favor faça downgrade à sua versão do Cockatrice ou ligue-se a servidor adequado. Versão local é %1, versão remota é %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. A sua versão do Cockatrice é obsoleta. Por favor actualize-a. Versão local é %1, versão remota é %2. - + Connecting to %1... Ligando a %1... - + Disconnected Desligado - + + Connected, logging in at %1 + Conectado, logando em %1 + + + Logged in at %1 Logado em %1 - + &Connect... &Ligar... - + &Disconnect &Desligar - + Start &local game... Começar &jogo local... - + + &Watch replay... + &Ver replay... + + + &Deck editor &Editor de decks - + &Full screen Ecrã &inteiro - + Ctrl+F Ctrl+F - + &Settings... &Configurações... - + &Exit &Sair - + &Cockatrice &Cockatrice - + &About Cockatrice S&obre o Cockatrice - + &Help &Ajuda - + Are you sure? Tens a certeza? - + There are still open games. Are you sure you want to quit? Ainda há jogos abertos. Tem a certeza que deseja sair? @@ -2016,17 +2537,17 @@ Versão local é %1, versão remota é %2. %1 abandonou o jogo. - + The game has been closed. Este jogo foi encerrado. - + %1 is now watching the game. %1 está agora a ver o jogo. - + %1 is not watching the game any more. %1 já não está a ver o jogo. @@ -2051,7 +2572,7 @@ Versão local é %1, versão remota é %2. %1 concedeu o jogo. - + The game has started. O jogo começou. @@ -2072,37 +2593,49 @@ Versão local é %1, versão remota é %2. %1 compra %2 cartas. - + You have joined game #%1. female Você entrou no jogo #%1. - - - You have joined game #%1. - male - Você entrou no jogo #%1. - - - - %1 has joined the game. - female - %1 entrou no jogo. - - - - %1 has joined the game. - male - %1 entrou no jogo. - + You have joined game #%1. + male + Você entrou no jogo #%1. + + + + You are watching a replay of game #%1. + female + Está a ver um replay do jogo #%1. + + + + You are watching a replay of game #%1. + male + Está a ver um replay do jogo #%1. + + + + %1 has joined the game. + female + %1 entrou no jogo. + + + + %1 has joined the game. + male + %1 entrou no jogo. + + + %1 has left the game. female %1 abandonou o jogo. - + %1 has left the game. male %1 abandonou o jogo. @@ -2128,91 +2661,115 @@ Versão local é %1, versão remota é %2. %1 carregou o deck #%2. - + %1 is ready to start the game. female %1 está pronta a começar o jogo. - + %1 is ready to start the game. male %1 está pronto a começar o jogo. - + %1 is not ready to start the game any more. female %1 já não está pronta a começar o jogo. - + %1 is not ready to start the game any more. male %1 já não está pronto a começar o jogo. - - %1 has conceded the game. + + %1 has locked her sideboard. female - %1 concedeu o jogo. + %1 bloqueou o seu sideboard. - - %1 has conceded the game. + + %1 has locked his sideboard. male - %1 concedeu o jogo. + %1 bloqueou o seu sideboard. - - %1 has restored connection to the game. + + %1 has unlocked her sideboard. female - %1 restabeleceu a ligação ao jogo. - - - - %1 has restored connection to the game. - male - %1 restabeleceu a ligação ao jogo. - - - - %1 has lost connection to the game. - female - %1 perdeu a ligação ao jogo. - - - - %1 has lost connection to the game. - male - %1 perdeu a ligação ao jogo. + %1 desbloqueou o seu sideboard. - %1 shuffles %2. - female - %1 baralha %2. + %1 has unlocked his sideboard. + male + %1 desbloqueou o seu sideboard. - - %1 shuffles %2. - male - %1 baralha %2. + + %1 has conceded the game. + female + %1 concedeu o jogo. - %1 rolls a %2 with a %3-sided die. - female - %1 obteve %2 com um dado de %3 faces. + %1 has conceded the game. + male + %1 concedeu o jogo. - - %1 rolls a %2 with a %3-sided die. - male - %1 obteve %2 com um dado de %3 faces. - - + %1 has restored connection to the game. + female + %1 restabeleceu a ligação ao jogo. + + + + %1 has restored connection to the game. + male + %1 restabeleceu a ligação ao jogo. + + + + %1 has lost connection to the game. + female + %1 perdeu a ligação ao jogo. + + + + %1 has lost connection to the game. + male + %1 perdeu a ligação ao jogo. + + + + %1 shuffles %2. + female + %1 baralha %2. + + + + %1 shuffles %2. + male + %1 baralha %2. + + + + %1 rolls a %2 with a %3-sided die. + female + %1 obteve %2 com um dado de %3 faces. + + + + %1 rolls a %2 with a %3-sided die. + male + %1 obteve %2 com um dado de %3 faces. + + + %1 draws %n card(s). female @@ -2221,7 +2778,7 @@ Versão local é %1, versão remota é %2. - + %1 draws %n card(s). male @@ -2230,157 +2787,182 @@ Versão local é %1, versão remota é %2. - + from table vindo da mesa - + from graveyard vindo do cemitério - + from exile vindo do exílio - + from hand vindo da mão - + + the bottom card of %1's library + a carta do fundo do grimório de %1 + + + the bottom card of his library a carta do fundo do seu grimório - + the bottom card of her library a carta do fundo do seu grimório - + + from the bottom of %1's library + do fundo do grimório de %1 + + + from the bottom of his library do fundo do seu grimório - + from the bottom of her library do fundo do seu grimório - + + the top card of %1's library + a carta do topo do grimório de %1 + + + the top card of his library a carta do topo do seu grimório - + the top card of her library a carta do topo do seu grimório - + + from the top of %1's library + do topo do grimório de %1 + + + from the top of his library do topo do seu grimório - + from the top of her library do topo do seu grimório - + + from %1's library + do grimório de %1 + + + from library do grimório - + from sideboard do sideboard - + from the stack da pilha - + %1 puts %2 into play tapped%3. %1 coloca %2 em jogo virado(a)%3. - + %1 puts %2 into play%3. %1 coloca %2 em jogo %3. - + %1 puts %2%3 into graveyard. %1 coloca %2%3 no cemitério. - + %1 exiles %2%3. %1 exila %2%3. - + %1 moves %2%3 to hand. %1 move %2%3 para a mão. - + %1 puts %2%3 into his library. %1 coloca %2%3 no seu grimório. - + %1 puts %2%3 into her library. %1 coloca %2%3 no seu grimório. - + %1 puts %2%3 on bottom of his library. %1 coloca %2%3 no fundo do seu grimório. - + %1 puts %2%3 on bottom of her library. %1 coloca %2%3 no fundo do seu grimório. - + %1 puts %2%3 on top of his library. %1 coloca %2%3 no topo do seu grimório. - + %1 puts %2%3 on top of her library. %1 coloca %2%3 no topo do seu grimório. - + %1 puts %2%3 into his library at position %4. %1 coloca %2%3 no seu grimório na posição %4. - + %1 puts %2%3 into her library at position %4. %1 coloca %2%3 no seu grimório na posição %4. - + %1 moves %2%3 to sideboard. %1 move %2%3 para o sideboard. - + %1 plays %2%3. %1 joga %2%3. - + %1 takes a mulligan to %n. female @@ -2389,7 +2971,7 @@ Versão local é %1, versão remota é %2. - + %1 takes a mulligan to %n. male @@ -2398,37 +2980,37 @@ Versão local é %1, versão remota é %2. - + %1 flips %2 face-down. female %1 volta a face de %2 para baixo. - + %1 flips %2 face-down. male %1 volta a face de %2 para baixo. - + %1 flips %2 face-up. female %1 volta a face de %2 para cima. - + %1 flips %2 face-up. male %1 volta a face de %2 para cima. - + %1 destroys %2. female %1 destrói %2. - + %1 destroys %2. male %1 destrói %2. @@ -2444,271 +3026,281 @@ Versão local é %1, versão remota é %2. %1 anexa %2 a %4 de %3. - + + %1 has loaded a deck (%2). + %1 carregou o deck %2. + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + %1 attaches %2 to %3's %4. p1 female, p2 female %1 anexa %2 a %4 de %3. - + %1 attaches %2 to %3's %4. p1 female, p2 male %1 anexa %2 a %4 de %3. - + %1 attaches %2 to %3's %4. p1 male, p2 female %1 anexa %2 a %4 de %3. - + %1 attaches %2 to %3's %4. p1 male, p2 male %1 anexa %2 a %4 de %3. - + %1 unattaches %2. female %1 desanexa %2. - + %1 unattaches %2. male %1 desanexa %2. - + %1 creates token: %2%3. female %1 cria ficha: %2%3. - + %1 creates token: %2%3. male %1 cria ficha: %2%3. - + %1 points from her %2 to herself. female %1 aponta do seu %2 para si própria. - + %1 points from his %2 to himself. male %1 aponta do seu %2 para si próprio. - + %1 points from her %2 to %3. p1 female, p2 female %1 aponta do seu %2 para %3. - + %1 points from her %2 to %3. p1 female, p2 male %1 aponta do seu %2 para %3. - + %1 points from his %2 to %3. p1 male, p2 female %1 aponta do seu %2 para %3. - + %1 points from his %2 to %3. p1 male, p2 male %1 aponta do seu %2 para %3. - + %1 points from %2's %3 to herself. card owner female, target female %1 aponta do %3 de %2 para si própria. - + %1 points from %2's %3 to herself. card owner male, target female %1 aponta do %3 de %2 para si própria. - + %1 points from %2's %3 to himself. card owner female, target male %1 aponta do %3 de %2 para si próprio. - + %1 points from %2's %3 to himself. card owner male, target male %1 aponta do %3 de %2 para si próprio. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female %1 aponta de %3 de %2 para %4. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male %1 aponta de %3 de %2 para %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female %1 aponta de %3 de %2 para %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male %1 aponta de %3 de %2 para %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female %1 aponta de %3 de %2 para %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male %1 aponta de %3 de %2 para %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female %1 aponta de %3 de %2 para %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male %1 aponta de %3 de %2 para %4. - + %1 points from her %2 to her %3. female %1 aponta do seu %2 para o seu %3. - + %1 points from his %2 to his %3. male %1 aponta do seu %2 para o seu %3. - + %1 points from her %2 to %3's %4. p1 female, p2 female %1 aponta do seu %2 para o %4 de %3. - + %1 points from her %2 to %3's %4. p1 female, p2 male %1 aponta do seu %2 para o %4 de %3. - + %1 points from his %2 to %3's %4. p1 male, p2 female %1 aponta do seu %2 para o %4 de %3. - + %1 points from his %2 to %3's %4. p1 male, p2 male %1 aponta do seu %2 para o %4 de %3. - + %1 points from %2's %3 to her own %4. card owner female, target female %1 aponta de %3 de %2 para o seu %4. - + %1 points from %2's %3 to her own %4. card owner male, target female %1 aponta de %3 de %2 para o seu %4. - + %1 points from %2's %3 to his own %4. card owner female, target male %1 aponta de %3 de %2 para o seu %4. - + %1 points from %2's %3 to his own %4. card owner male, target male %1 aponta de %3 de %2 para o seu %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female %1 aponta de %3 de %2 para %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male %1 aponta de %3 de %2 para %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female %1 aponta de %3 de %2 para %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male %1 aponta de %3 de %2 para %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female %1 aponta de %3 de %2 para %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male %1 aponta de %3 de %2 para %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female %1 aponta de %3 de %2 para %5 de %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male %1 aponta de %3 de %2 para %5 de %4. - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -2717,7 +3309,7 @@ Versão local é %1, versão remota é %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -2726,7 +3318,7 @@ Versão local é %1, versão remota é %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -2735,7 +3327,7 @@ Versão local é %1, versão remota é %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -2744,288 +3336,341 @@ Versão local é %1, versão remota é %2. - + %1 taps her permanents. female %1 vira as suas permanentes. - + %1 untaps her permanents. female %1 desvira as suas permanentes. - + %1 taps his permanents. male %1 vira as suas permanentes. - + %1 untaps his permanents. male %1 desvira as suas permanentes. - + %1 taps %2. female %1 vira %2. - + %1 untaps %2. female %1 desvira %2. - - - %1 taps %2. - male - %1 vira %2. - - - - %1 untaps %2. - male - %1 desvira %2. - - - - %1 sets counter %2 to %3 (%4%5). - female - %1 altera o número de marcadores %2 para %3(%4%5). - - - - %1 sets counter %2 to %3 (%4%5). - male - %1 altera o número de marcadores %2 para %3(%4%5). - - - - %1 sets %2 to not untap normally. - female - %1 define %2 para não desvirar normalmente. - - - - %1 sets %2 to not untap normally. - male - %1 define %2 para não desvirar normalmente. - - - - %1 sets %2 to untap normally. - female - %1 define %2 para desvirar normalmente. - - - - %1 sets %2 to untap normally. - male - %1 define %2 para desvirar normalmente. - - - - %1 sets PT of %2 to %3. - female - %1 define o P/R de %2 como %3. - - - - %1 sets PT of %2 to %3. - male - %1 define o P/R de %2 como %3. - - - - %1 sets annotation of %2 to %3. - female - %1 coloca uma nota de %2 em%3. - - - - %1 sets annotation of %2 to %3. - male - %1 coloca uma nota de %2 em%3. - - - - %1 is looking at the top %2 cards %3. - female - %1 está a olhar para as %2 cartas do topo %3. - - %1 is looking at the top %2 cards %3. + %1 taps %2. male - %1 está a olhar para as %2 cartas do topo %3. + %1 vira %2. - - %1 is looking at %2. - female - %1 está a olhar para %2. - - - - %1 is looking at %2. + + %1 untaps %2. male - %1 está a olhar para %2. + %1 desvira %2. - %1 stops looking at %2. + %1 sets counter %2 to %3 (%4%5). female - %1 pára de olhar para %2. + %1 altera o número de marcadores %2 para %3(%4%5). + %1 sets counter %2 to %3 (%4%5). + male + %1 altera o número de marcadores %2 para %3(%4%5). + + + + %1 sets %2 to not untap normally. + female + %1 define %2 para não desvirar normalmente. + + + + %1 sets %2 to not untap normally. + male + %1 define %2 para não desvirar normalmente. + + + + %1 sets %2 to untap normally. + female + %1 define %2 para desvirar normalmente. + + + + %1 sets %2 to untap normally. + male + %1 define %2 para desvirar normalmente. + + + + %1 sets PT of %2 to %3. + female + %1 define o P/R de %2 como %3. + + + + %1 sets PT of %2 to %3. + male + %1 define o P/R de %2 como %3. + + + + %1 sets annotation of %2 to %3. + female + %1 coloca uma nota de %2 em%3. + + + + %1 sets annotation of %2 to %3. + male + %1 coloca uma nota de %2 em%3. + + + %1 is looking at the top %2 cards %3. + female + %1 está a olhar para as %2 cartas do topo %3. + + + %1 is looking at the top %2 cards %3. + male + %1 está a olhar para as %2 cartas do topo %3. + + + + %1 is looking at %2. + female + %1 está a olhar para %2. + + + + %1 is looking at %2. + male + %1 está a olhar para %2. + + + + %1 is looking at the top %n card(s) %2. + female + + %1 está a olhar para as %n carta(s) do topo %2. + + + + + + %1 is looking at the top %n card(s) %2. + male + + %1 está a olhar para as %n carta(s) do topo %2. + + + + + + %1 stops looking at %2. + female + %1 pára de olhar para %2. + + + %1 stops looking at %2. male %1 pára de olhar para %2. - - - %1 reveals %2 to %3. - p1 female, p2 female - %1 revela %2 a %3. - - - - %1 reveals %2 to %3. - p1 female, p2 male - %1 revela %2 a %3. - - - - %1 reveals %2 to %3. - p1 male, p2 female - %1 revela %2 a %3. - - - - %1 reveals %2 to %3. - p1 male, p2 male - %1 revela %2 a %3. - - - - %1 reveals %2. - female - %1 revela %2. - - - - %1 reveals %2. - male - %1 revela %2. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - %1 revela aleatoreamente %2%3. a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - %1 revela aleatoreamente %2%3. a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - %1 revela aleatoreamente %2%3. a %4. - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - %1 revela aleatoreamente %2%3. a %4. - - - - %1 randomly reveals %2%3. - female - %1 revela aleatoreamente %2%3. - - - - %1 randomly reveals %2%3. - male - %1 revela aleatoreamente %2%3. - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - %1 revela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - %1 revela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - %1 revela %2%3 a %4. - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - %1 revela %2%3 a %4. - - - - %1 reveals %2%3. - female - %1 revela %2%3. - - - - %1 reveals %2%3. - male - %1 revela %2%3. - - - - It is now %1's turn. - female - É agora o turno de %1. - + %1 reveals %2 to %3. + p1 female, p2 female + %1 revela %2 a %3. + + + + %1 reveals %2 to %3. + p1 female, p2 male + %1 revela %2 a %3. + + + + %1 reveals %2 to %3. + p1 male, p2 female + %1 revela %2 a %3. + + + + %1 reveals %2 to %3. + p1 male, p2 male + %1 revela %2 a %3. + + + + %1 reveals %2. + female + %1 revela %2. + + + + %1 reveals %2. + male + %1 revela %2. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + %1 revela aleatoreamente %2%3. a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + %1 revela aleatoreamente %2%3. a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + %1 revela aleatoreamente %2%3. a %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + %1 revela aleatoreamente %2%3. a %4. + + + + %1 randomly reveals %2%3. + female + %1 revela aleatoreamente %2%3. + + + + %1 randomly reveals %2%3. + male + %1 revela aleatoreamente %2%3. + + + + %1 peeks at face down card #%2. + female + %1 espreita a carta virada para baixo #%2. + + + + %1 peeks at face down card #%2. + male + %1 espreita a carta virada para baixo #%2. + + + + %1 peeks at face down card #%2: %3. + female + %1 espreita a carta virada para baixo #%2: %3. + + + + %1 peeks at face down card #%2: %3. + male + %1 espreita a carta virada para baixo #%2: %3. + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + %1 revela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + %1 revela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + %1 revela %2%3 a %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + %1 revela %2%3 a %4. + + + + %1 reveals %2%3. + female + %1 revela %2%3. + + + + %1 reveals %2%3. + male + %1 revela %2%3. + + + + %1 is now keeping the top card %2 revealed. + %1 está agora a manter a carta do topo %2 revelada. + + + + %1 is not revealing the top card %2 any longer. + %1 já não está a manter a carta do topo %2 revelada. + + + + It is now %1's turn. + female + É agora o turno de %1. + + + It is now %1's turn. male É agora o turno de %1. - + %1 draws his initial hand. %1 compra a sua mão inicial. - + + You have been kicked out of the game. + Você foi expulso do jogo. + + %1 has loaded a deck (%2). female - %1 carregou o deck %2. + %1 carregou o deck %2. - %1 has loaded a deck (%2). male - %1 carregou o deck %2. + %1 carregou o deck %2. - + %1 draws her initial hand. %1 compra a sua mão inicial. @@ -3044,8 +3689,8 @@ Versão local é %1, versão remota é %2. - - + + a card uma carta @@ -3057,27 +3702,27 @@ Versão local é %1, versão remota é %2. - + %1 undoes his last draw. %1 desfaz a sua última compra. - + %1 undoes her last draw. %1 desfaz a sua última compra. - + %1 undoes his last draw (%2). %1 desfaz a sua última compra (%2). - + %1 undoes her last draw (%2). %1 desfaz a sua última compra (%2). - + %1 gives %2 control over %3. %1 dá controlo sobre %3 a %2. @@ -3128,7 +3773,7 @@ Versão local é %1, versão remota é %2. - + red vermelho @@ -3136,7 +3781,7 @@ Versão local é %1, versão remota é %2. - + yellow amarelo @@ -3144,7 +3789,7 @@ Versão local é %1, versão remota é %2. - + green verde @@ -3228,62 +3873,62 @@ Versão local é %1, versão remota é %2. É agora o turno de %1. - + untap step Etapa de Desvirar - + upkeep step Etapa de Manutenção - + draw step Etapa de Compra - + first main phase 1ª Fase Principal (pré-combate) - + beginning of combat step Etapa de Início de Combate - + declare attackers step Etapa de Declaração de Atacantes - + declare blockers step Etapa de Declaração de Bloqueadores - + combat damage step Etapa de Dano de Combate - + end of combat step Etapa de Fim de Combate - + second main phase 2ª Fase Principal (pós-combate) - + ending phase Fase Final - + It is now the %1. É agora a %1. @@ -3291,22 +3936,22 @@ Versão local é %1, versão remota é %2. MessagesSettingsPage - + Add message Adicionar mensagem - + Message: Mensagem: - + &Add &Adicionar - + &Remove &Remover @@ -3314,57 +3959,57 @@ Versão local é %1, versão remota é %2. PhasesToolbar - + Untap step Etapa de Desvirar - + Upkeep step Etapa de manutenção - + Draw step Etapa de compra - + First main phase 1ª Fase Principal (pré-combate) - + Beginning of combat step Etapa de Início de Combate - + Declare attackers step Etapa de Declaração de Atacantes - + Declare blockers step Etapa de Declaração de Bloqueadores - + Combat damage step Etapa de Dano de Combate - + End of combat step Etapa de Fim de Combate - + Second main phase 2ª Fase Principal (pós-combate) - + End of turn step Fase Final @@ -3372,322 +4017,536 @@ Versão local é %1, versão remota é %2. Player - + &View graveyard &Ver cemitério - + &View exile &Ver exílio - + Player "%1" Jogador "%1" - + &Graveyard &Cemitério - + &Exile &Exílio - - - + + + Move to &top of library Mover para o &topo do grimório - - - + + + Move to &bottom of library Mover para o &fundo do grimório - - + + Move to &graveyard Mover para o &cemitério - - + + Move to &exile Mover para o &exílio - - + + Move to &hand Mover para a &mão - + &View library &Ver grimório - + View &top cards of library... Ver as cartas do &topo do grimório... - + Reveal &library to Revelar &grimório a - + Reveal t&op card to Revelar carta do t&opo a - + + &Always reveal top card + &Revelar sempre carta do topo + + + + O&pen deck in deck editor + &Abrir deck no editor de decks + + + &View sideboard &Ver sideboard - + &Draw card &Comprar carta - + D&raw cards... C&omprar cartas... - + &Undo last draw Desfa&zer a última compra - + Take &mulligan Fazer &mulligan - + &Shuffle &Baralhar - + Move top cards to &graveyard... Mover as cartas do topo para o &cemitério... - + Move top cards to &exile... Mover as cartas do topo para o &exílio... - + Put top card on &bottom Colocar carta do topo no &fundo - + &Hand &Mão - + &Reveal to &Revelar a - + Reveal r&andom card to Revelar carta &aleatória a - + &Sideboard &Sideboard - + &Library &Grimório - + &Counters &Marcadores - + &Untap all permanents &Desvirar topas as permanentes - + R&oll die... &Lançar dado... - + &Create token... Criar fic&ha... - + C&reate another token Cr&iar outra ficha - + + Cr&eate predefined token + Criar fic&ha predefinida + + + S&ay &Dizer - + C&ard C&arta - + &All players Todos os &jogadores - + + &Play + &Jogar + + + + &Hide + Esco&nder + + + + &Tap + &Virar + + + + &Untap + Desv&irar + + + + Toggle &normal untapping + A&lterar desvirar normalmente + + + + &Flip + Vol&tar + + + + &Peek at card face + &Espreitar a face da carta + + + + &Clone + Copi&ar + + + Ctrl+H + Ctrl+H + + + + Ctrl+J + + + + + Attac&h to card... + Ane&xar a carta... + + + + Ctrl+A + Ctrl+A + + + + Unattac&h + De&sanexar + + + + &Draw arrow... + Desen&har seta... + + + + &Increase power + &Aumentar poder + + + + Ctrl++ + Ctrl++ + + + + &Decrease power + &Diminuir poder + + + + Ctrl+- + Ctrl+- + + + + I&ncrease toughness + A&umentar resistência + + + + Alt++ + Alt++ + + + + D&ecrease toughness + Di&minuir resistência + + + + Alt+- + Alt+- + + + + In&crease power and toughness + Aumen&tar poder e resistência + + + + Ctrl+Alt++ + Ctrl+Alt++ + + + + Dec&rease power and toughness + Dimin&uir poder e resistência + + + + Ctrl+Alt+- + Ctrl+Alt+- + + + + Set &power and toughness... + Definir &poder e resistência... + + + + Ctrl+P + Ctrl+P + + + + &Set annotation... + Colocar &nota... + + + + red + vermelho + + + + yellow + amarelo + + + + green + verde + + + + &Add counter (%1) + Adicionar &marcador (%1) + + + + &Remove counter (%1) + &Remover marcador (%1) + + + + &Set counters (%1)... + &Denifir marcadores (%1)... + + + + &top of library + Topo do &grimório + + + + &bottom of library + &Fundo do grimório + + + + &graveyard + &Cemitério + + + + Ctrl+Del + Ctrl+Del + + + + &exile + &Exílio + + + Ctrl+F3 Ctrl+F3 - + F3 F3 - + Ctrl+W Ctrl+W - + F4 F4 - + Ctrl+D Ctrl+D - + Ctrl+E Ctrl+E - + Ctrl+Shift+D Ctrl+Shift+D - + Ctrl+M Ctrl+M - + Ctrl+S Ctrl+S - + Ctrl+U Ctrl+U - + Ctrl+I Ctrl+I - + Ctrl+T Ctrl+T - + Ctrl+G Ctrl+G - + View top cards of library Ver as cartas do topo do grimório - + Number of cards: Número de cartas: - + Draw cards Comprar cartas - - - - + + + + Number: Número: - + Move top cards to grave Mover as cartas to topo para o cemitério - + Move top cards to exile Mover as cartas to topo para o exílio - + Roll die Lançar dado - + Number of sides: Número de faces: - + Set power/toughness Definir poder/resistência - + Please enter the new PT: Por favor introduza o novo P/R: - + Set annotation Colocar nota - + Please enter the new annotation: Por favor introduza a nova nota: - + Set counters Definir marcadores @@ -3723,116 +4582,148 @@ Versão local é %1, versão remota é %2. deck #%1 - User &details - Detalhes do &utilizador + Detalhes do &utilizador - Direct &chat - Conversação &directa + Conversação &directa - Add to &buddy list - Adicionar à lista de &amigos + Adicionar à lista de &amigos - Remove from &buddy list - Remover da lista de &amigos + Remover da lista de &amigos - Add to &ignore list - Adicionar à lista a &ignorar + Adicionar à lista a &ignorar - Remove from &ignore list - Remover da lista a &ignorar + Remover da lista a &ignorar - Kick from &game - Expulsar do &jogo + Expulsar do &jogo QObject - Maindeck - Maindeck + Maindeck - Sideboard - Sideboard + Sideboard - Cockatrice decks (*.cod) - Decks do Cockatrice (*.cod) + Decks do Cockatrice (*.cod) - Plain text decks (*.dec *.mwDeck) - Decks baseados em texto simples (*.dec *.mwDeck) + Decks baseados em texto simples (*.dec *.mwDeck) - + + Common deck formats (*.cod *.dec *.mwDeck) + + + + All files (*.*) Todos os ficheiros (*.*) + + + Cockatrice replays (*.cor) + Replays do Cockatrice (*.cor) + RemoteDeckList_TreeModel - + Name Nome - + ID ID - + Upload time Hora de upload - RoomSelector + RemoteReplayList_TreeModel - - Rooms - Salas + + ID + ID - - Joi&n - E&ntrar + + Name + Nome - - Room - Sala - - - - Description - Descrição - - - + Players Jogadores - + + Keep + Keep + + + + Time started + Tempo de começo + + + + Duration (sec) + Duração (seg) + + + + RoomSelector + + + Rooms + Salas + + + + Joi&n + E&ntrar + + + + Room + Sala + + + + Description + Descrição + + + + Players + Jogadores + + + Games Jogos @@ -3860,27 +4751,25 @@ Versão local é %1, versão remota é %2. ShutdownDialog - + &Reason for shutdown: &Motivos para o encerramento: - + &Time until shutdown (minutes): &Tempo até ao encerramento (minutos): - &OK - &OK + &OK - &Cancel - &Cancelar + &Cancelar - + Shut down server Encerrar servidor @@ -3888,44 +4777,43 @@ Versão local é %1, versão remota é %2. TabAdmin - + Update server &message &Actualizar mensagem do servidor - + &Shut down server &Encerrar servidor - + Server administration functions Funções do administrador do servidor - + &Unlock functions &Desbloquear funções - + &Lock functions &Bloquear funções - + Unlock administration functions Desbloquear funções de administração - + Do you really want to unlock the administration functions? Quer mesmo desbloquear as funçõesde administração? - Administration - Administração + Administração @@ -3947,207 +4835,468 @@ Versão local é %1, versão remota é %2. %1 abandonou o canal. + + TabDeckEditor + + &Search... + &Procurar... + + + + Show card text only + + + + + &Clear search + &Limpar pesquisa + + + + &Search for: + &Procurar por: + + + + Deck &name: + &Nome do deck: + + + + &Comments: + &Comentários: + + + + Hash: + Hash: + + + + &Update prices + Actualizar pre&ços + + + + Ctrl+U + Ctrl+U + + + + &New deck + &Novo deck + + + + &Load deck... + &Carregar deck... + + + + &Save deck + &Guardar deck + + + + Save deck &as... + G&uardar deck como... + + + + Load deck from cl&ipboard... + Carregar dec&k da memória... + + + + Save deck to clip&board + Guardar deck na &memória + + + + &Print deck... + &Imprimir deck... + + + + &Analyze deck on deckstats.net + Anali&zar o deck em deckstats.net + + + + &Close + &Fechar + + + + Ctrl+Q + Ctrl+Q + + + + Add card to &maindeck + Adicionar carta ao &maindeck + + + Return + Return + + + Enter + Enter + + + + Add card to &sideboard + Adicionar carta ao &sideboard + + + Ctrl+Return + Ctrl+Return + + + Ctrl+Enter + Ctrl+Enter + + + + &Remove row + &Remover linha + + + + Del + Del + + + + &Increment number + &Aumentar o número + + + + + + + + + + + &Decrement number + &Diminuir o número + + + + - + - + + + + &Deck editor + &Editor de decks + + + + C&ard database + &Base de dados das cartas + + + + &Edit sets... + &Editar expansões... + + + + Edit &tokens... + Editar &fichas... + + + + Deck: %1 + Deck:%1 + + + + Are you sure? + Tem a certeza? + + + + The decklist has been modified. +Do you want to save the changes? + A lista foi modificada. +Gostaria de guardar as alterações? + + + + Load deck + Carregar deck + + + + + + Error + Erro + + + + The deck could not be saved. + O deck não pode ser guardado. + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + O deck não pode ser guardado. +Por favor confirme se é possível escrever do directório e tente de novo. + + + + Save deck + Guardar deck + + TabDeckStorage - + Local file system Ficheiros locais - + Server deck storage Decks armazenados no servidor - - + + Open in deck editor Abrir no editor de decks - + Upload deck Upload do deck - + Download deck Download do deck - - + + New folder Nova pasta - + + Delete Apagar - + Enter deck name Introduza o nome do deck - + This decklist does not have a name. Please enter a name: Este deck nao tem um nome Por favor introduza um nome: - + Unnamed deck Deck sem nome - + + Delete local file + Apagar ficheiro local + + + + + + Are you sure you want to delete "%1"? + Tem a certeza que deseja apagar "%1"? + + + Name of new folder: Nome da nova pasta: - + + Delete remote folder + + + + + Delete remote deck + Apagar deck remoto + + Deck storage - Armazenamento de decks + Armazenamento de decks TabGame - + F5 F5 - + F6 F6 - + F7 F7 - + F8 F8 - + F9 F9 - + F10 F10 - + &Phases Fa&ses - + &Game &Jogo - + Next &phase Próxima &fase - + Ctrl+Space Ctrl+Space - + + Tab + + + + Next &turn Próximo &turno - + Ctrl+Return Ctrl+Return - + Ctrl+Enter Ctrl+Enter - + &Remove all local arrows &Remover todas as setas locais - + Ctrl+R Ctrl+R - + + Game &information + &Informação do jogo + + + &Concede &Conceder - + F2 F2 - + &Leave game Sair do &jogo - + + Ctrl+Q Ctrl+Q - + + C&lose replay + &Fechar replay + + + &Say: &Dizer: - + Concede Conceder - + Are you sure you want to concede this game? Tem a certeza que deseja conceder este jogo? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Tem a certeza que deseja sair deste jogo? - + + Replay %1: %2 + Replay %1: %2 + + Kicked - Expulso + Expulso - You have been kicked out of the game. - Você foi expulso do jogo. + Você foi expulso do jogo. - + Game %1: %2 Jogo %1: %2 @@ -4155,60 +5304,124 @@ Por favor introduza um nome: TabMessage - + Personal &talk Conversação &privada - + &Leave &Abandonar - + This user is ignoring you. Este utilizador esta a ignorar-te. - + %1 has left the server. %1 abandonou o servidor. - + %1 has joined the server. %1 entrou no servidor. - + Talking to %1 Falar para %1 + + TabReplays + + + Local file system + Ficheiros locais + + + + Server replay storage + Armazenamento de replays no servidor + + + + + Watch replay + Ver replay + + + + + Delete + Apagar + + + + Download replay + Download replay + + + + Toggle expiration lock + Alternar bloqueio de expiração + + + + Delete local file + Apagar ficheiro local + + + + Are you sure you want to delete "%1"? + Tem a certeza que deseja apagar %1? + + + + Delete remote replay + Apagar replay remoto + + + + Are you sure you want to delete the replay of game %1? + Tem a certeza que deseja apagar o replay do jogo %1? + + + Game replays + Replays de jogos + + TabRoom - + &Say: &Dizer: - + Chat - + Chat - + &Room &Sala - + &Leave room &Abandonar a sala - + + &Ignore unregistered users in chat + &Ignorar utilizadores não registados no chat + + + You are flooding the chat. Please wait a couple of seconds. Estás a inundar o chat .Por favor aguarde alguns segundos. @@ -4224,53 +5437,114 @@ Por favor introduza um nome: TabServer - Server - Servidor + Servidor TabUserLists - User lists - Lista de utilizadores + Lista de utilizadores + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + Detalhes do &utilizador + + + + Direct &chat + Conversação &directa + + + + Show this user's &games + Mostrar os &jogos deste utilizador + + + + Add to &buddy list + Adicionar à &lista de amigos + + + + Remove from &buddy list + Remover da lista de &amigos + + + + Add to &ignore list + Adicionar a lista a i&gnorar + + + + Remove from &ignore list + Remover da lista a &ignorar + + + + Kick from &game + Expulsar do &jogo + + + + Ban from &server + Banir do &servidor + + + + %1's games + jogos de %1 UserInfoBox - + User information Informação do utilizador - + Real name: Nome real: - + Gender: Sexo: - + Location: Localização: - + User level: Nível de utilizador: - + Administrator Administrador - + Moderator Moderador @@ -4279,12 +5553,12 @@ Por favor introduza um nome: Juiz - + Registered user Utilizador registado - + Unregistered user Utilizador não registado @@ -4292,37 +5566,47 @@ Por favor introduza um nome: UserInterfaceSettingsPage - + General interface settings Configurações gerais da interface - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) Clicar &duas vezes nas cartas para as jogar (ao invés de clicar apenas uma vez) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings Configurações de Animações - + &Tap/untap animation Animação de &virar/desvirar - + Enable &sounds Permitir &sons - + Path to sounds directory: Caminho para o directório dos sons: - + Choose path Escolher directório @@ -4330,69 +5614,60 @@ Por favor introduza um nome: UserList - + Users online: %1 Utilizadores online: %1 - + Users in this room: %1 Utilizadores nesta sala:%1 - + Buddies online: %1 / %2 Amigos online: %1 / %2 - + Ignored users online: %1 / %2 Utilizadores ignorados online %1 / %2 - %1's games - jogos de %1 + jogos de %1 - User &details - Detalhes do &utilizador + Detalhes do &utilizador - Direct &chat - Conversação &directa + Conversação &directa - Show this user's &games - Mostrar os &jogos deste utilizador + Mostrar os &jogos deste utilizador - Add to &buddy list - Adicionar a lista de &amigos + Adicionar a lista de &amigos - Remove from &buddy list - Remover da lista de &amigos + Remover da lista de &amigos - Add to &ignore list - Adicionar a lista a &ignorar + Adicionar a lista a &ignorar - Remove from &ignore list - Remover da lista a &ignorar + Remover da lista a &ignorar - Ban from &server - Banir do &servidor + Banir do &servidor Duration @@ -4408,205 +5683,160 @@ Introduza 0 para um banimento indefinido. WndDeckEditor - &Search... - &Procurar... + &Procurar... - &Clear search - &Limpar pesquisa + &Limpar pesquisa - &Search for: - &Procurar por: + &Procurar por: - Deck &name: - &Nome do deck: + &Nome do deck: - &Comments: - &Comentários: + &Comentários: - - Hash: - - - - &Update prices - Actualizar pre&ços + Actualizar pre&ços - Ctrl+U - Ctrl+U + Ctrl+U - Deck editor [*] - Editor de decks [*] + Editor de decks [*] - &New deck - &Novo deck + &Novo deck - &Load deck... - &Carregar deck... + &Carregar deck... - &Save deck - &Guardar deck + &Guardar deck - Save deck &as... - G&uardar deck como... + G&uardar deck como... - Load deck from cl&ipboard... - Carregar dec&k da memória... + Carregar dec&k da memória... - Save deck to clip&board - Guardar deck na &memória + Guardar deck na &memória - &Print deck... - &Imprimir deck... + &Imprimir deck... - &Close - &Fechar + &Fechar - Ctrl+Q - Ctrl+Q + Ctrl+Q - &Edit sets... - &Editar expansões... + &Editar expansões... - &Deck - &Deck + &Deck - &Card database - &Base de dados das cartas + &Base de dados das cartas - Add card to &maindeck - Adicionar carta ao &maindeck + Adicionar carta ao &maindeck - Return - Return + Return - Enter - Enter + Enter - Add card to &sideboard - Adicionar carta ao &sideboard + Adicionar carta ao &sideboard - Ctrl+Return - Ctrl+Return + Ctrl+Return - Ctrl+Enter - Ctrl+Enter + Ctrl+Enter - &Remove row - &Remover linha + &Remover linha - Del - Del + Del - &Increment number - &Aumentar o número + &Aumentar o número - + - + + + - &Decrement number - &Diminuir o número + &Diminuir o número - - - - + - - Are you sure? - Tem a certeza? + Tem a certeza? - The decklist has been modified. Do you want to save the changes? - A lista foi modificada. + A lista foi modificada. Gostaria de guardar as alterações? - Load deck - Carregar deck + Carregar deck - - Error - Erro + Erro - - The deck could not be saved. Please check that the directory is writable and try again. - O deck não pode ser guardado. + O deck não pode ser guardado. Por favor confirme se é possível escrever do directório e tente de novo. - Save deck - Guardar deck + Guardar deck @@ -4620,17 +5850,17 @@ Por favor confirme se é possível escrever do directório e tente de novo. ZoneViewWidget - + sort by name dispor por nome - + sort by type dispor por tipo - + shuffle when closing baralhar quando terminar diff --git a/cockatrice/translations/cockatrice_ru.ts b/cockatrice/translations/cockatrice_ru.ts index aecedc19c..9580ccd5d 100644 --- a/cockatrice/translations/cockatrice_ru.ts +++ b/cockatrice/translations/cockatrice_ru.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... &Установить жетоны... - + Ctrl+L - + F11 - + F12 - + Set counter Установить жетоны - + New value for counter '%1': Количество жетонов '%1': @@ -37,91 +37,111 @@ AppearanceSettingsPage - + Zone background pictures Фоновые изображения - Path to hand background: - Рука: + Рука: - Path to stack background: - Стек: + Стек: - Path to table background: - Поле битвы: + Поле битвы: - Path to player info background: - Панель игрока: + Панель игрока: - Path to picture of card back: - Рубашки карт: + Рубашки карт: - + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + Card rendering Отрисовка карт - + Display card names on cards having a picture Отображать название карты поверх изображения - + Hand layout Расположение руки - + Display hand horizontally (wastes space) Отбражать руку горизонтально - + Table grid layout Сетка - + Invert vertical coordinate Инвертировать вертикальные координаты - + Minimum player count for multi-column layout: Минимальное количество игроков для столбчатого расположения: - + Zone view layout Сортировка карт - + Sort by name Сортировать по имени - + Sort by type Сортировать по типу - - - - - + + + + + Choose path Выберите путь @@ -135,79 +155,84 @@ Enter 0 for an indefinite ban. Введите 0 чтобы забанить пожизненно. - + ban &user name - + ban &IP address - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Пожалуйста, назовите причину бана. Это необходимо для модераторов и не будет прочитано забаненным игроком. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK &Ок - + &Cancel &Отмена - + Ban user from server Забанить игрока на этом сервере - + Error Ошибка - + You have to select a name-based or IP-based ban, or both. @@ -215,277 +240,235 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Название - + Sets Издания - + Mana cost Манакост - + Card type Тип - + P/T P/T - CardInfoWidget + CardInfoText - - Hide card info - Скрыть информацию о карте + + Name: + Название: - + + Mana cost: + Манакост: + + + + Card type: + Тип: + + + + P / T: + Сила/Защита: + + + + Loyalty: + + + + + CardInfoWidget + + Hide card info + Скрыть информацию о карте + + + Show card only Показывать только карту - + Show text only Показывать только текст - + Show full info Показывать полную информацию - + Name: Название: - + Mana cost: Манакост: - + Card type: Тип: - + P / T: Сила/Защита: + + + Loyalty: + + CardItem - &Play - &Разыграть + &Разыграть - &Hide - &Cкрыть + &Cкрыть - &Tap - &Повернуть + &Повернуть - &Untap - &Развернуть + &Развернуть - Toggle &normal untapping - (Не) &Разворачивать как обычно + (Не) &Разворачивать как обычно - &Flip - &Рубашкой вверх (вниз) + &Рубашкой вверх (вниз) - &Clone - &Клонировать + &Клонировать - Ctrl+H - Ctrl+H + Ctrl+H - &Attach to card... - &Прикрепить к... + &Прикрепить к... - - Ctrl+A - - - - Unattac&h - &Открепить + &Открепить Set &P/T... Установить &Силу/Защиту... - &Draw arrow... - &Нарисовать стрелку... + &Нарисовать стрелку... - + &Power / toughness &Сила / защита - &Increase power - &Увеличить силу + &Увеличить силу - - Ctrl++ - - - - &Decrease power - У&меньшить силу + У&меньшить силу - - Ctrl+- - - - - I&ncrease toughness - У&величить защиту + У&величить защиту - - Alt++ - - - - D&ecrease toughness - Уменьшить &защиту + Уменьшить &защиту - - Alt+- - - - - In&crease power and toughness - Увеличить силу &и защиту + Увеличить силу &и защиту - - Ctrl+Alt++ - - - - Dec&rease power and toughness - Уменьшить силу и за&щиту + Уменьшить силу и за&щиту - - Ctrl+Alt+- - - - - Set &power and toughness... - Уст&ановить силу / защиту... + Уст&ановить силу / защиту... - - Ctrl+P - - - - &Set annotation... - &Пометить... + &Пометить... - red - Красный + Красный - yellow - Желтый + Желтый - green - Зеленый + Зеленый - &Add counter (%1) - &Добавить жетон (%1) + &Добавить жетон (%1) - &Remove counter (%1) - &Убрать жетон (%1) + &Убрать жетон (%1) - &Set counters (%1)... - &Установить жетоны (%1)... + &Установить жетоны (%1)... - &top of library - &Наверх библиотеки + &Наверх библиотеки - &bottom of library - &Вниз библиотеки + &Вниз библиотеки - &graveyard - &На кладбище + &На кладбище - - Ctrl+Del - - - - &exile - &Изгнать + &Изгнать - + &Move to &Переместить... @@ -553,364 +536,444 @@ This is only saved for moderators and cannot be seen by the banned person.библиотеку %1-го игрока - + her hand nominative, female owner её рука - + %1's hand nominative, female owner рука %1-го игрока - + his hand nominative, male owner его рука - + %1's hand nominative, male owner рука %1-го игрока - + + her library + look at zone, female owner + + + + + %1's library + look at zone, female owner + + + + + his library + look at zone, male owner + + + + + %1's library + look at zone, male owner + + + + + of her library + top cards of zone, female owner + её библиотеки + + + + of %1's library + top cards of zone, female owner + библиотеки %1-го игрока + + + + of his library + top cards of zone, male owner + его библиотеки + + + + of %1's library + top cards of zone, male owner + библиотеки %1-го игрока + + + + her library + reveal zone, female owner + + + + + %1's library + reveal zone, female owner + + + + + his library + reveal zone, male owner + + + + + %1's library + reveal zone, male owner + + + + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + + + + + his library + shuffle, male owner + + + + + %1's library + shuffle, male owner + + + + + her sideboard + look at zone, female owner + её сайд + + + + %1's sideboard + look at zone, female owner + сайд %1-го игрока + + + + his sideboard + look at zone, male owner + его сайд + + + + %1's sideboard + look at zone, male owner + сайд %1-го игрока + + of her hand genitive, female owner - её руки + её руки - of %1's hand genitive, female owner - руки %1-го игрока + руки %1-го игрока - of his hand genitive, male owner - его руки + его руки - of %1's hand genitive, male owner - руки %1-го игрока + руки %1-го игрока - her hand accusative, female owner - её руку + её руку - %1's hand accusative, female owner - руку %1-го игрока + руку %1-го игрока - his hand accusative, male owner - его руку + его руку - %1's hand accusative, male owner - руку %1-го игрока + руку %1-го игрока - + her library nominative, female owner её библиотека - + %1's library nominative, female owner библиотека %1-го игрока - + his library nominative, male owner его библиотека - + %1's library nominative, male owner библиотека %1-го игрока - of her library genitive, female owner - её библиотеки + её библиотеки - of %1's library genitive, female owner - библиотеки %1-го игрока + библиотеки %1-го игрока - of his library genitive, male owner - его библиотеки + его библиотеки - of %1's library genitive, male owner - библиотеки %1-го игрока + библиотеки %1-го игрока - her library accusative, female owner - её библиотеку + её библиотеку - %1's library accusative, female owner - библиотеку %1-го игрока + библиотеку %1-го игрока - his library accusative, male owner - его библиотеку + его библиотеку - %1's library accusative, male owner - библиотеку %1-го игрока + библиотеку %1-го игрока - + her graveyard nominative, female owner её кладбище - + %1's graveyard nominative, female owner кладбище %1-го игрока - + his graveyard nominative, male owner его кладбище - + %1's graveyard nominative, male owner кладбище %1-го игрока - of her graveyard genitive, female owner - её кладбища + её кладбища - of %1's graveyard genitive, female owner - кладбища %1-го игрока + кладбища %1-го игрока - of his graveyard genitive, male owner - его кладбища + его кладбища - of %1's graveyard genitive, male owner - кладбища %1-го игрока + кладбища %1-го игрока - her graveyard accusative, female owner - её кладбище + её кладбище - %1's graveyard accusative, female owner - кладбище %1-го игрока + кладбище %1-го игрока - his graveyard accusative, male owner - его кладбище + его кладбище - %1's graveyard accusative, male owner - кладбище %1-го игрока + кладбище %1-го игрока - + her exile nominative, female owner её изгнание - + %1's exile nominative, female owner изгнание %1-го игрока - + his exile nominative, male owner его изгнание - + %1's exile nominative, male owner изгнание %1-го игрока - of her exile genitive, female owner - её изгнания + её изгнания - of %1's exile genitive, female owner - изгнания %1-го игрока + изгнания %1-го игрока - of his exile genitive, male owner - его изгнания + его изгнания - of %1's exile genitive, male owner - изгнания %1-го игрока + изгнания %1-го игрока - her exile accusative, female owner - её изгнание + её изгнание - %1's exile accusative, female owner - изгнание %1-го игрока + изгнание %1-го игрока - his exile accusative, male owner - его изгнание + его изгнание - %1's exile accusative, male owner - изгнание %1-го игрока + изгнание %1-го игрока - + her sideboard nominative, female owner её сайд - + %1's sideboard nominative, female owner сайд %1-го игрока - + his sideboard nominative, male owner его сайд - + %1's sideboard nominative, male owner сайд %1-го игрока - of her sideboard genitive, female owner - её сайда + её сайда - of %1's sideboard genitive, female owner - сайда %1-го игрока + сайда %1-го игрока - of his sideboard genitive, male owner - его сайда + его сайда - of %1's sideboard genitive, male owner - сайда %1-го игрока + сайда %1-го игрока - her sideboard accusative, female owner - её сайд + её сайд - %1's sideboard accusative, female owner - сайд %1-го игрока + сайд %1-го игрока - his sideboard accusative, male owner - его сайд + его сайд - %1's sideboard accusative, male owner - сайд %1-го игрока + сайд %1-го игрока his graveyard @@ -1004,14 +1067,49 @@ This is only saved for moderators and cannot be seen by the banned person. - DeckEditorSettingsPage + DBPriceUpdater - - Enable &price tag feature (using data from blacklotusproject.com) - Подписывать &цены (по данным blacklotusproject.com) + + + + Error + Ошибка - + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + Enable &price tag feature (using data from blacklotusproject.com) + Подписывать &цены (по данным blacklotusproject.com) + + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General Основные @@ -1019,43 +1117,77 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Number Номер - + Card Название - + Price Цена + + DeckStatsInterface + + + + Error + Ошибка + + + + The reply from the server could not be parsed. + + + DeckViewContainer - + Load &local deck Загрузить &колоду с диска - + Load d&eck from server Загрузить к&олоду с сервера - + Ready to s&tart &Готов - + + S&ideboard unlocked + + + + + S&ideboard locked + + + + Load deck Загрузить колоду + + + Error + Ошибка + + + + The selected file could not be loaded. + + DlgCardSearch @@ -1098,34 +1230,37 @@ This is only saved for moderators and cannot be seen by the banned person. DlgConnect - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Ник: - + P&assword: П&ароль: - - &OK - &Ок + + &Save password + + + + &OK + &Ок - &Cancel - &Отмена + &Отмена @@ -1136,87 +1271,90 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + &Description: &Подпись: - + &Password: &Пароль: - + P&layers: &Количество игроков: - + Game type Формат игры - + Only &buddies can join Только для &своих - + Only &registered users can join Только для &зарег. пользователей - + Joining restrictions Ограничения - + &Spectators allowed &Разрешить зрителей - + Spectators &need a password to join Требовать &пароль у зрителей - + Spectators can &chat Позволить зрителям &комментировать - + Spectators see &everything Показывать зрителям &все - + Spectators Зрители - &OK - &Ок + &Ок - &Cancel - &Отмена + &Отмена - + Create game Создать игру - + + Game information + + + + Error Ошибка - + Server error. Ошибка сервера. @@ -1224,115 +1362,281 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: &Название: - + Token Фишка - + C&olor: &Цвет: - + white белый - + blue синий - + black черный - + red красный - + green зеленый - + multicolor многоцветный - + colorless бесцветный - + &P/T: &Сила/Защита: - + &Annotation: &Пометить... - + &Destroy token when it leaves the table &Уничтожить фишку, когда она покинет поле битвы - + + Token data + + + + + Show &all tokens + + + + + Show tokens from this &deck + + + + + Choose token from list + + + &OK - &Ок + &Ок - &Cancel - &Отмена + &Отмена - + Create token Создать фишку + + DlgEditTokens + + + &Name: + &Название: + + + + C&olor: + &Цвет: + + + + white + белый + + + + blue + синий + + + + black + черный + + + + red + + + + + green + + + + + multicolor + многоцветный + + + + colorless + бесцветный + + + + &P/T: + &Сила/Защита: + + + + &Annotation: + &Пометить... + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + Ошибка + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + O&K + О&к + + + &Cancel + &Отмена + + + + Filter games + + + DlgLoadDeckFromClipboard - + &Refresh &Обновить - &OK - &Ок + &Ок - &Cancel - &Отмена + &Отмена - + Load deck from clipboard Взять колоду из буфера - + Error Ошибка - + Invalid deck list. Неверный деклист. @@ -1340,17 +1644,15 @@ This is only saved for moderators and cannot be seen by the banned person. DlgLoadRemoteDeck - O&K - О&к + О&к - &Cancel - &Отмена + &Отмена - + Load deck Загрузить колоду @@ -1358,154 +1660,225 @@ This is only saved for moderators and cannot be seen by the banned person. DlgSettings - - - + + + Error Ошибка - Your card database is invalid. Would you like to go back and set the correct path? - База карт не найдена. Вернуться и задать правильный путь? + База карт не найдена. Вернуться и задать правильный путь? - + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ваши колоды отсутствуют в указанной папке. Вернуться и задать правильный путь? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Изображения карт не найдены. Вернуться и задать правильный путь? - + Settings Настройки - + General Основные - + Appearance Внешний вид - + User interface Интерфейс - + Deck editor Редактор колод - + Messages Сообщения - &Close - &Закрыть + &Закрыть GameSelector - - - - - - - - + + + + + + + + + Error Ошибка - + Please join the appropriate room first. Пожалуйста, сперва войдите в соответствующую комнату. - + Wrong password. Неверный пароль. - + Spectators are not allowed in this game. В эту игру не пускают зрителей. - + The game is already full. Все места заняты! =Ь - + The game does not exist any more. Эта игра была удалена. - + This game is only open to registered users. Доступно только для зарегистрированных. - + This game is only open to its creator's buddies. Доступно только для друзей. - + You are being ignored by the creator of this game. Вы добавлены в игнор-лист данного игрока. - + Join game Присоединиться - + Password: Пароль: - + + Please join the respective room first. + + + + Games Игры - + + &Filter games + + + + + C&lear filter + + + Show &full games - Показывать &текущие + Показывать &текущие - Show &running games - Показывать &текущие + Показывать &текущие - + C&reate С&оздать - + &Join &Присоединиться - + J&oin as spectator П&рисоединиться как зритель @@ -1521,72 +1894,72 @@ This is only saved for moderators and cannot be seen by the banned person. GamesModel - + yes да - + yes, free for spectators да, свободно для зрителей - + no нет - + buddies only только свои - + reg. users only только зарег. - + not allowed Не допускаются - + Room Комната - + Description Подпись - + Creator Создал - + Game type Формат игры - + Password Пароль - + Restrictions Ограничения - + Players Количество игроков - + Spectators Зрители @@ -1594,95 +1967,139 @@ This is only saved for moderators and cannot be seen by the banned person. GeneralSettingsPage - - + + English Русский - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path Путь - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Ошибка + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings Личные настройки - + Language: Язык: - + Download card pictures on the fly Загружать изображения карт на лету - + + Download high-quality card pictures + + + + Paths Расположение - + Decks directory: Колоды: - + + Replays directory: + + + + Pictures directory: Изображения карт: - + + Card database: + + + + + Token database: + + + Path to card database: - Путь к базе карт: + Путь к базе карт: MainWindow - + There are too many concurrent connections from your address. Слишком много одновременных подключений с Вашего адреса. - Banned by moderator. - Забанено модератором. + Забанено модератором. - + Scheduled server shutdown. Плановый перерыв в работе сервера. - Unknown reason. - Неизвестная причина. + Неизвестная причина. - + Connection closed Соединение прервано - + The server has terminated your connection. Reason: %1 Ваше подключение было прервано сервером. Причина: %1 - + Scheduled server shutdown Плановый перерыв в работе сервера - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1695,208 +2112,276 @@ Reason for shutdown: %1 - + Number of players Количество игроков - + Please enter the number of players. Введите количество игроков. - - + + Player %1 Игрок %1 - + About Cockatrice О программе - + Version %1 Версия %1 - + Authors: Разработчики: - + Translators: Переводчики: - + Spanish: Испанский: - + Portugese (Portugal): Португальский: - + Portugese (Brazil): Португальский (Brazil): - + French: Французский: - + Japanese: Японский: - + Russian: Русский: - Czech: - Чешский: + Чешский: Slovak: Словацкий: - + + Banned by moderator + + + + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + + Load replay + + + + Italian: - - - - - - + + Swedish: + + + + + + + + + + + + + Error Ошибка - + Server timeout Временная ошибка - Invalid login data. - Неверный логин/пароль. + Неверный логин/пароль. - + + Incorrect username or password. Please check your authentication information and try again. + + + + There is already an active session using this user name. Please close that session first and re-login. Пользователь с таким именем уже подключен. Пожалуйста, закройте это подключение и войдите заново. - + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + This server requires user registration. + + + + + Unknown login error: %1 + + + + Socket error: %1 Ошибка сокета: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Вы пытаетесь подключиться к несуществующему серверу. Пожалуйста, обновите Cockatrice или выберите другой сервер. Локальная версия %1, удаленная версия %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Ваш клиент Cockatrice устарел. Пожалуйста, обновите Cockatrice. Локальная версия %1, удаленная версия %2. - + Connecting to %1... Подключение к %1... - + Disconnected Подключение прервано - + + Connected, logging in at %1 + + + + Logged in at %1 Подключено к %1 - + &Connect... &Подключение... - + &Disconnect П&рервать подключение - + Start &local game... &Начать локальную игру... - + + &Watch replay... + + + + &Deck editor Редактор &колод - + &Full screen П&олный экран - + Ctrl+F - + &Settings... Н&астройки - + &Exit &Выход - + &Cockatrice - + &About Cockatrice О про&грамме - + &Help &Справка - + Are you sure? Вы уверены? - + There are still open games. Are you sure you want to quit? Вы подключены к игре. Выйти? @@ -1940,17 +2425,22 @@ Local version is %1, remote version is %2. %1 покиул игру. - + The game has been closed. Игра закрыта. - + + You have been kicked out of the game. + Вас выкинули из игры. + + + %1 is now watching the game. %1 вошел как зритель. - + %1 is not watching the game any more. %1 покинул зрительскую ложу. @@ -1975,7 +2465,7 @@ Local version is %1, remote version is %2. %1 решил сдаться. - + The game has started. Игра началась. @@ -1996,37 +2486,49 @@ Local version is %1, remote version is %2. %1 взял %2 карт. - + You have joined game #%1. female Вы присоединились к игре #%1. - + You have joined game #%1. male Вы присоединились к игре #%1. - + + You are watching a replay of game #%1. + female + + + + + You are watching a replay of game #%1. + male + + + + %1 has joined the game. female %1 присоединилась к игре. - + %1 has joined the game. male %1 присоединился к игре. - + %1 has left the game. female %1 покинула игру. - + %1 has left the game. male %1 покинул игру. @@ -2052,91 +2554,91 @@ Local version is %1, remote version is %2. %1 загрузил колоду #%2. - + %1 is ready to start the game. female %1 готова начать игру. - + %1 is ready to start the game. male %1 готов начать игру. - + %1 is not ready to start the game any more. female %1 все еще не готова. - + %1 is not ready to start the game any more. male %1 все еще не готов. - + %1 has conceded the game. female %1 решила сдаться. - + %1 has conceded the game. male %1 решил сдаться. - + %1 has restored connection to the game. female %1 вернулась к игре. - + %1 has restored connection to the game. male %1 вернулся к игре. - + %1 has lost connection to the game. female %1 потеряла соединение с сервером. - + %1 has lost connection to the game. male %1 потерял соединение с сервером. - + %1 shuffles %2. female %1 размешивает %2. - + %1 shuffles %2. male %1 размешивает %2. - + %1 rolls a %2 with a %3-sided die. female %1 выкинул %2 / %3. - + %1 rolls a %2 with a %3-sided die. male %1 выкинул %2 / %3. - + %1 draws %n card(s). female @@ -2146,7 +2648,7 @@ Local version is %1, remote version is %2. - + %1 draws %n card(s). male @@ -2156,133 +2658,133 @@ Local version is %1, remote version is %2. - + %1 undoes his last draw. %1 отменил последнее взятие. - + %1 undoes his last draw (%2). %1 отменил %2 последних взятий. - + from table с поля битвы - + from graveyard из кладбища - + from exile из изгнания - + from hand из руки - + the bottom card of his library нижнюю карту своей библиотеки - + from the bottom of his library со дна своей библиотеки - + the top card of his library верхнюю карту своей библиотеки - + from the top of his library с верха своей библиотеки - + from library из библиотеки - + from sideboard из сайда - + from the stack из стека - - + + a card карту - + %1 gives %2 control over %3. %1 передает %2 контроль над %3. - + %1 puts %2 into play%3. %1 поместил %2 на поле битвы %3. - + %1 puts %2%3 into graveyard. %1 поместил %2%3 на кладбище. - + %1 exiles %2%3. %1 изгоняет %2%3. - + %1 moves %2%3 to hand. %1 поместил %2%3 в руку. - + %1 puts %2%3 into his library. %1 поместил %2%3 в свою библиотеку. - + %1 puts %2%3 on bottom of his library. %1 поместил %2%3 на дно своей библиотеки. - + %1 puts %2%3 on top of his library. %1 поместил %2%3 на верх своей библиотеки. - + %1 puts %2%3 into his library at position %4. %1 поместил %2%3 в свою библиотеку %4 сверху. - + %1 moves %2%3 to sideboard. %1 поместил %2%3 в сайд. - + %1 plays %2%3. %1 разыгрывает %2%3. - + %1 takes a mulligan to %n. female @@ -2292,7 +2794,7 @@ Local version is %1, remote version is %2. - + %1 takes a mulligan to %n. male @@ -2302,37 +2804,37 @@ Local version is %1, remote version is %2. - + %1 flips %2 face-down. female %1 перевернула %2 лицом вниз. - + %1 flips %2 face-down. male %1 перевернул %2 лицом вниз. - + %1 flips %2 face-up. female %1 перевернула %2 лицом вверх. - + %1 flips %2 face-up. male %1 перевернул %2 лицом вверх. - + %1 destroys %2. female %1 уничтожила %2. - + %1 destroys %2. male %1 уничтожил %2. @@ -2348,283 +2850,340 @@ Local version is %1, remote version is %2. %1 присоединил %2 к %4 игрока %3. - %1 has loaded a deck (%2). female - %1 загрузила колоду %2. + %1 загрузила колоду %2. - %1 has loaded a deck (%2). male - %1 загрузил колоду %2. + %1 загрузил колоду %2. - + + %1 has loaded a deck (%2). + + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + the bottom card of %1's library + + + + + from the bottom of %1's library + + + + + the top card of %1's library + + + + + from the top of %1's library + + + + + from %1's library + + + + %1 attaches %2 to %3's %4. p1 female, p2 female %1 присоединила %2 к %4 игрока %3. - + %1 attaches %2 to %3's %4. p1 female, p2 male %1 присоединила %2 к %4 игрока %3. - + %1 attaches %2 to %3's %4. p1 male, p2 female %1 присоединил %2 к %4 игрока %3. - + %1 attaches %2 to %3's %4. p1 male, p2 male %1 присоединил %2 к %4 игрока %3. - + %1 unattaches %2. female %1 отсоединила %2. - + %1 unattaches %2. male %1 отсоединил %2. - + %1 creates token: %2%3. female %1 создала фишку: %2%3. - + %1 creates token: %2%3. male %1 создал фишку: %2%3. - + %1 points from her %2 to herself. female %1 указывает с %2 на себя-любимую. - + %1 points from his %2 to himself. male %1 указывает с %2 на себя. - + %1 points from her %2 to %3. p1 female, p2 female %1 указывает с %2 на %3. - + %1 points from her %2 to %3. p1 female, p2 male %1 указывает с %2 на %3. - + %1 points from his %2 to %3. p1 male, p2 female %1 указывает с %2 на %3. - + %1 points from his %2 to %3. p1 male, p2 male %1 указывает с %2 на %3. - + %1 points from %2's %3 to herself. card owner female, target female %1 указывает с %3, контролируемого %2, на себя. - + %1 points from %2's %3 to herself. card owner male, target female %1 указывает с %3, контролируемого %2, на себя. - + %1 points from %2's %3 to himself. card owner female, target male %1 указывает с %3, контролируемого %2, на себя. - + %1 points from %2's %3 to himself. card owner male, target male %1 указывает с %3, контролируемого %2, на себя. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 female %1 указывает с %3, контролируемого %2, на %4. - + %1 points from %2's %3 to %4. p1 female, p2 female, p3 male %1 указывает с %3, контролируемого %2, на %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 female %1 указывает с %3, контролируемого %2, на %4. - + %1 points from %2's %3 to %4. p1 female, p2 male, p3 male %1 указывает с %3, контролируемого %2, на %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 female %1 указывает с %3, контролируемого %2, на %4. - + %1 points from %2's %3 to %4. p1 male, p2 female, p3 male %1 указывает с %3, контролируемого %2, на %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 female %1 указывает с %3, контролируемого %2, на %4. - + %1 points from %2's %3 to %4. p1 male, p2 male, p3 male %1 указывает с %3, контролируемого %2 на %4. - + %1 points from her %2 to her %3. female %1 указывает с её %2 на её %3. - + %1 points from his %2 to his %3. male %1 указывает с его %2 на его %3. - + %1 points from her %2 to %3's %4. p1 female, p2 female %1 указывает с её %2 на %4 под контролем %3. - + %1 points from her %2 to %3's %4. p1 female, p2 male %1 указывает с её %2 на %4 под контролем %3. - + %1 points from his %2 to %3's %4. p1 male, p2 female %1 указывает с его %2 на %4 под контролем %3. - + %1 points from his %2 to %3's %4. p1 male, p2 male %1 указывает с его %2 на %4 под контролем %3. - + %1 points from %2's %3 to her own %4. card owner female, target female %1 указывает с %3 под контролем %2 на %4, владельцем которого она является. - + %1 points from %2's %3 to her own %4. card owner male, target female %1 указывает с %3 под контролем %2 на %4, владельцем которого он является. - + %1 points from %2's %3 to his own %4. card owner female, target male %1 указывает с %3 под контролем %2 на %4, владельцем которого она является. - + %1 points from %2's %3 to his own %4. card owner male, target male %1 указывает с %3 под контролем %2 на %4, владельцем которого он является. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 female %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 female, p3 male %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 female %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 points from %2's %3 to %4's %5. p1 female, p2 male, p3 male %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 female %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 female, p3 male %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 female %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 points from %2's %3 to %4's %5. p1 male, p2 male, p3 male %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 places %n %2 counter(s) on %3 (now %4). female @@ -2634,7 +3193,7 @@ Local version is %1, remote version is %2. - + %1 places %n %2 counter(s) on %3 (now %4). male @@ -2644,7 +3203,7 @@ Local version is %1, remote version is %2. - + %1 removes %n %2 counter(s) from %3 (now %4). female @@ -2654,7 +3213,7 @@ Local version is %1, remote version is %2. - + %1 removes %n %2 counter(s) from %3 (now %4). male @@ -2664,265 +3223,317 @@ Local version is %1, remote version is %2. - + %1 taps her permanents. female %1 повернула свои перманенты. - + %1 untaps her permanents. female %1 развернула свои перманенты. - + %1 taps his permanents. male %1 повернул свои перманенты. - + %1 untaps his permanents. male %1 развернул свои перманенты. - + %1 taps %2. female %1 повернула %2. - + %1 untaps %2. female %1 развернула %2. - + %1 taps %2. male %1 повернул %2. - + %1 untaps %2. male %1 развернул %2. - + %1 sets counter %2 to %3 (%4%5). female %1 установила жетон %2 на %3 (%4%5). - + %1 sets counter %2 to %3 (%4%5). male %1 установил жетон %2 на %3 (%4%5). - + %1 sets %2 to not untap normally. female %2 теперь не разворачивается как обычно (%1). - + %1 sets %2 to not untap normally. male %2 теперь не разворачивается как обычно (%1). - + %1 sets %2 to untap normally. female %2 теперь разворачивается как обычно (%1). - + %1 sets %2 to untap normally. male %2 теперь разворачивается как обычно (%1). - + %1 sets PT of %2 to %3. female %1 установила Силу/Защиту %2 %3. - + %1 sets PT of %2 to %3. male %1 установил Силу/Защиту %2 %3. - + %1 sets annotation of %2 to %3. female %1 пометила %2 "%3". - + %1 sets annotation of %2 to %3. male %1 пометил %2 "%3". - %1 is looking at the top %2 cards %3. female - %1 смотрит верхние %2 карт библиотеки %3. + %1 смотрит верхние %2 карт библиотеки %3. - %1 is looking at the top %2 cards %3. male - %1 смотрит верхние %2 карт библиотеки %3. + %1 смотрит верхние %2 карт библиотеки %3. - + %1 is looking at %2. female %1 просматривает %2. - + %1 is looking at %2. male %1 просматривает %2. + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + - + %1 stops looking at %2. female %1 закончила просматривать %2. - + %1 stops looking at %2. male %1 закончил просматривать %2. - + %1 reveals %2 to %3. p1 female, p2 female %1 показывает её %2 %3. - + %1 reveals %2 to %3. p1 female, p2 male %1 показывает её %2 %3. - + %1 reveals %2 to %3. p1 male, p2 female %1 показывает его %2 %3. - + %1 reveals %2 to %3. p1 male, p2 male %1 показывает его %2 %3. - + %1 reveals %2. female %1 открыла её %2. - + %1 reveals %2. male %1 открыл его %2. - + %1 randomly reveals %2%3 to %4. p1 female, p2 female %1 показывает случайно выбранную%3 карту (%2) %4. - + %1 randomly reveals %2%3 to %4. p1 female, p2 male %1 показывает случайно выбранную%3 карту (%2) %4. - + %1 randomly reveals %2%3 to %4. p1 male, p2 female %1 показывает случайно выбранную%3 карту (%2) %4. - + %1 randomly reveals %2%3 to %4. p1 male, p2 male %1 показывает случайно выбранную%3 карту (%2) %4. - + %1 randomly reveals %2%3. female %1 показывает случайно выбранную%3 карту (%2). - + %1 randomly reveals %2%3. male %1 показывает случайно выбранную%3 карту (%2). - + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + %1 reveals %2%3 to %4. p1 female, p2 female %1 показывает%2%3 %4. - + %1 reveals %2%3 to %4. p1 female, p2 male %1 показывает%2%3 %4. - + %1 reveals %2%3 to %4. p1 male, p2 female %1 показывает%2%3 %4. - + %1 reveals %2%3 to %4. p1 male, p2 male %1 показывает%2%3 %4. - + %1 reveals %2%3. female %1 показывает%2%3. - + %1 reveals %2%3. male %1 показывает%2%3. - + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + It is now %1's turn. female Ход очаровательной %1. - + It is now %1's turn. male Ход игрока %1. @@ -2984,67 +3595,67 @@ Local version is %1, remote version is %2. - + %1 undoes her last draw. %1 отменила последнее взятие. - + %1 undoes her last draw (%2). %1 отменила %2 последних взятий. - + the bottom card of her library нижнюю карту своей библиотеки - + from the bottom of her library со дна своей библиотеки - + the top card of her library верхнюю карту своей библиотеки - + from the top of her library с верха своей библиотеки - + %1 puts %2 into play tapped%3. %1 положил %2 повернутым на поле битвы%3. - + %1 puts %2%3 into her library. %1 поместила %2%3 в свою библиотеку. - + %1 puts %2%3 on bottom of her library. %1 поместила %2%3 на дно своей библиотеки. - + %1 puts %2%3 on top of her library. %1 поместила %2%3 на верх своей библиотеки. - + %1 puts %2%3 into her library at position %4. %1 поместила %2%3 в свою библиотеку %4 сверху. - + %1 draws his initial hand. %1 взял свою стартовую руку. - + %1 draws her initial hand. %1 взяла свою стартовую руку. @@ -3065,7 +3676,7 @@ Local version is %1, remote version is %2. - + red красный @@ -3074,7 +3685,7 @@ Local version is %1, remote version is %2. - + yellow желтый @@ -3083,7 +3694,7 @@ Local version is %1, remote version is %2. - + green зеленый @@ -3168,62 +3779,62 @@ Local version is %1, remote version is %2. Ход игрока %1. - + untap step шаг разворота - + upkeep step шаг поддержки - + draw step шаг взятия карты - + first main phase первая главная фаза - + beginning of combat step шаг начала битвы - + declare attackers step шаг назначения атакующих - + declare blockers step шаг назначения блокирующих - + combat damage step шаг нанесения повреждений - + end of combat step шаг завершения битвы - + second main phase вторая главная фаза - + ending phase заключительный шаг - + It is now the %1. Сейчас %1. @@ -3231,22 +3842,22 @@ Local version is %1, remote version is %2. MessagesSettingsPage - + Add message Добавить сообщение - + Message: Сообщение: - + &Add &Добавить - + &Remove &Удалить @@ -3254,57 +3865,57 @@ Local version is %1, remote version is %2. PhasesToolbar - + Untap step Шаг разворота - + Upkeep step Шаг поддержки - + Draw step Шаг взятия карты - + First main phase Первая главная фаза - + Beginning of combat step Шаг начала битвы - + Declare attackers step Шаг назначения атакующих - + Declare blockers step Шаг назначения блокирующих - + Combat damage step Шаг нанесения повреждений - + End of combat step Шаг завершения битвы - + Second main phase Вторая главная фаза - + End of turn step Заключительный шаг @@ -3312,322 +3923,536 @@ Local version is %1, remote version is %2. Player - + &View graveyard &Посмотреть кладбище - + &View exile П&осмотреть изгнанные карты - + Player "%1" Игрок "%1" - + &Graveyard &Кладбище - + &Exile &Изгнание - - - + + + Move to &top of library Поместить &наверх библиотеки - - - + + + Move to &bottom of library Поместить на &дно библиотеки - - + + Move to &graveyard поместить на клад&бище - - + + Move to &exile Поместить в изгнани&е - - + + Move to &hand Поместить в &руку - + &View library Просмортеть &библиотеку - + View &top cards of library... Посмтореть верхние карт&ы... - + Reveal &library to Показать б&иблиотеку... - + Reveal t&op card to Показать верхние карты... - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &View sideboard Просмотреть &сайд - + &Draw card В&зять карту - + D&raw cards... Вз&ять карты... - + &Undo last draw &Отменить последнее взятие - + Take &mulligan Взять стра&ховку - + &Shuffle Переме&шать - + Move top cards to &graveyard... Поместить верхние карты на кладби&ще... - + Move top cards to &exile... Поместить верхние карты в и&згнание... - + Put top card on &bottom Поместить верхн&юю карту на дно - + &Hand Р&ука - + &Reveal to &Показать... - + Reveal r&andom card to Показать &случайную карту... - + &Sideboard &Сайд - + &Library &Библиотека - + &Counters &Жетоны - + &Untap all permanents &Развернуть все перманенты - + R&oll die... Бросить &кубик... - + &Create token... Создать &фишку... - + C&reate another token Создать &еще одну фишку - + + Cr&eate predefined token + + + + S&ay Ска&зать - + C&ard Ка&рта - + &All players &Все игроки - + + &Play + &Разыграть + + + + &Hide + &Cкрыть + + + + &Tap + &Повернуть + + + + &Untap + &Развернуть + + + + Toggle &normal untapping + (Не) &Разворачивать как обычно + + + + &Flip + &Рубашкой вверх (вниз) + + + + &Peek at card face + + + + + &Clone + &Клонировать + + + Ctrl+H + Ctrl+H + + + + Ctrl+J + + + + + Attac&h to card... + + + + + Ctrl+A + + + + + Unattac&h + &Открепить + + + + &Draw arrow... + &Нарисовать стрелку... + + + + &Increase power + &Увеличить силу + + + + Ctrl++ + + + + + &Decrease power + У&меньшить силу + + + + Ctrl+- + + + + + I&ncrease toughness + У&величить защиту + + + + Alt++ + + + + + D&ecrease toughness + Уменьшить &защиту + + + + Alt+- + + + + + In&crease power and toughness + Увеличить силу &и защиту + + + + Ctrl+Alt++ + + + + + Dec&rease power and toughness + Уменьшить силу и за&щиту + + + + Ctrl+Alt+- + + + + + Set &power and toughness... + Уст&ановить силу / защиту... + + + + Ctrl+P + + + + + &Set annotation... + &Пометить... + + + + red + + + + + yellow + + + + + green + + + + + &Add counter (%1) + &Добавить жетон (%1) + + + + &Remove counter (%1) + &Убрать жетон (%1) + + + + &Set counters (%1)... + &Установить жетоны (%1)... + + + + &top of library + &Наверх библиотеки + + + + &bottom of library + &Вниз библиотеки + + + + &graveyard + &На кладбище + + + + Ctrl+Del + + + + + &exile + &Изгнать + + + Ctrl+F3 - + F3 - + Ctrl+W - + F4 - + Ctrl+D - + Ctrl+E - + Ctrl+Shift+D - + Ctrl+M - + Ctrl+S - + Ctrl+U - + Ctrl+I - + Ctrl+T - + Ctrl+G - + View top cards of library Просмотр верхних карт - + Number of cards: Количество: - + Draw cards Взять карты - - - - + + + + Number: Количество: - + Move top cards to grave Поместить верхние карты на кладбище - + Move top cards to exile Поместить верхние карты в изгнание - + Roll die Бросить кубик - + Number of sides: Количество граней: - + Set power/toughness Установить Силу/Защиту - + Please enter the new PT: Введите новые Силу/Защиту: - + Set annotation Пометка - + Please enter the new annotation: Введите текст: - + Set counters Установить жетоны @@ -3655,116 +4480,148 @@ Local version is %1, remote version is %2. колода №%1 - User &details - &Данные о пользователе + &Данные о пользователе - Direct &chat - Обратиться &лично + Обратиться &лично - Add to &buddy list - Добавить в &друзья + Добавить в &друзья - Remove from &buddy list - &Удалить из друзей + &Удалить из друзей - Add to &ignore list - Добавить в &игнор-лист + Добавить в &игнор-лист - Remove from &ignore list - Удалить и&з игнор-листа + Удалить и&з игнор-листа - Kick from &game - В&ыкинуть из игры + В&ыкинуть из игры QObject - Maindeck - Мейн + Мейн - Sideboard - Сайд + Сайд - Cockatrice decks (*.cod) - Cockatrice-деклисты (*.cod) + Cockatrice-деклисты (*.cod) - Plain text decks (*.dec *.mwDeck) - Текстовые деклисты (*.dec *.mwDeck) + Текстовые деклисты (*.dec *.mwDeck) - + + Common deck formats (*.cod *.dec *.mwDeck) + + + + All files (*.*) - Все файлы (*.*) + Все файлы (*.*) + + + + Cockatrice replays (*.cor) + RemoteDeckList_TreeModel - + Name Название - + ID - + Upload time Дата загрузки + + RemoteReplayList_TreeModel + + + ID + + + + + Name + Название + + + + Players + + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + RoomSelector - + Rooms Комнаты - + Joi&n &Присоединиться - + Room Комната - + Description Пометка - + Players Игроки - + Games Игры @@ -3785,27 +4642,25 @@ Local version is %1, remote version is %2. ShutdownDialog - + &Reason for shutdown: &Причина отключения: - + &Time until shutdown (minutes): &Время до отключения (в минутах): - &OK - &Ок + &Ок - &Cancel - &Отмена + &Отмена - + Shut down server Отключение сервера @@ -3813,247 +4668,491 @@ Local version is %1, remote version is %2. TabAdmin - + Update server &message Обновить сооб&щения сервера - + &Shut down server &Отключение сервера - + Server administration functions Функции администрирования сервера - + &Unlock functions &Разблокировать функции - + &Lock functions &Заблокировать функции - + Unlock administration functions Разблокировать административные права - + Do you really want to unlock the administration functions? Вы действительно хотите разблокировать административные права? - Administration - Администрирование + Администрирование + + + + TabDeckEditor + + &Search... + &Поиск... + + + + Show card text only + + + + + &Clear search + &Очистить строку поиска + + + + &Search for: + &Искать: + + + + Deck &name: + &Название колоды: + + + + &Comments: + Ко&мментарии: + + + + Hash: + + + + + &Update prices + &Обновить цены + + + + Ctrl+U + Ctrl+U + + + + &New deck + Новая коло&да + + + + &Load deck... + &Загрузить колоду... + + + + &Save deck + Со&хранить колоду + + + + Save deck &as... + Сохранить колоду к&ак... + + + + Load deck from cl&ipboard... + Взять колоду из &буфера... + + + + Save deck to clip&board + Копировать колоду в бу&фер + + + + &Print deck... + Пе&чать колоды... + + + + &Analyze deck on deckstats.net + + + + + &Close + &Закрыть + + + + Ctrl+Q + Ctrl+Q + + + + Add card to &maindeck + Добавить ме&йном + + + + Add card to &sideboard + Добавить в са&йд + + + + &Remove row + &Удалить строку + + + + Del + + + + + &Increment number + У&величить количество + + + + + + + + + + &Decrement number + У&меньшить количество + + + + - + + + + + &Deck editor + Редактор &колод + + + + C&ard database + + + + + &Edit sets... + Редактировать издани&я... + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + Вы уверены? + + + + The decklist has been modified. +Do you want to save the changes? + Деклист был отредактирован. +Сохранить изменения? + + + + Load deck + Загрузить колоду + + + + + + Error + Ошибка + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + Колода не может быть сохранена. +Убедитесь, что директория указана верно,а затем повторите попытку. + + + + Save deck + Сохранить колоду TabDeckStorage - + Local file system Локальная файловая система - + Server deck storage Хранилище колод на сервере - - + + Open in deck editor Открыть колоду в редакторе - + Upload deck Загрузить колоду в хранилище - + Download deck Загрузить колоду из хранилища - - + + New folder Новая папка - + + Delete Удалить - + Enter deck name Введите название колоды - + This decklist does not have a name. Please enter a name: У этой колоды нет названия. Пожалуйста, проявите фантазию: - + Unnamed deck Еще одна унылая дека - + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: Название новой папки: - + + Delete remote folder + + + + + Delete remote deck + + + Deck storage - Хранилище колод + Хранилище колод TabGame - + F5 - + F6 - + F7 - + F8 - + F9 - + F10 - + &Phases &Фазы - + &Game &Игра - + Next &phase Следующая &фаза - + Ctrl+Space - + + Tab + + + + Next &turn Следующий &ход - + Ctrl+Return - + Ctrl+Enter - + &Remove all local arrows &Удалить все указатели - + Ctrl+R - + + Game &information + + + + &Concede &Сдаться... - + F2 - + &Leave game Покинуть и&гру - + + Ctrl+Q Ctrl+Q - + + C&lose replay + + + + &Say: Ска&зать: - + Concede Сдаться - + Are you sure you want to concede this game? Вы точно хотите сдаться? - + Leave game Покинуть игру - + Are you sure you want to leave this game? Вы уверены, что хотите уйти? - + + Replay %1: %2 + + + Kicked - Выкинут + Выкинут - You have been kicked out of the game. - Вас выкинули из игры. + Вас выкинули из игры. - + Game %1: %2 Игра %1: %2 @@ -4061,60 +5160,120 @@ Please enter a name: TabMessage - + Personal &talk Личная &беседа - + &Leave &Покинуть - + This user is ignoring you. Этот пользователь добавил вас в игнор. - + %1 has left the server. %1 отключился. - + %1 has joined the server. %1 зашел на сервер. - + Talking to %1 Беседует с %1 + + TabReplays + + + Local file system + Локальная файловая система + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + Удалить + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: &Сказать: - + Chat Чат - + &Room &Комната - + &Leave room &Покинуть комнату - + + &Ignore unregistered users in chat + + + + You are flooding the chat. Please wait a couple of seconds. Кажется, Вы нафлудили. Пожалуйста, подождите пару секунд. @@ -4122,53 +5281,114 @@ Please enter a name: TabServer - Server - Сервер + Сервер TabUserLists - User lists - Панели пользователей + Панели пользователей + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + + + + + Direct &chat + Обратиться &лично + + + + Show this user's &games + Показывать игры &этого пользователя + + + + Add to &buddy list + + + + + Remove from &buddy list + &Удалить из друзей + + + + Add to &ignore list + Добавить в &игнор-лист + + + + Remove from &ignore list + Удалить и&з игнор-листа + + + + Kick from &game + В&ыкинуть из игры + + + + Ban from &server + За&банить на сервере + + + + %1's games + Игры %1 UserInfoBox - + User information Информация о пользователе - + Real name: Настоящее имя: - + Gender: Пол: - + Location: Местонахождение: - + User level: Уровень: - + Administrator Администратор - + Moderator Модератор @@ -4177,12 +5397,12 @@ Please enter a name: Судья - + Registered user Зарегистрированный пользователь - + Unregistered user Рандом @@ -4190,38 +5410,48 @@ Please enter a name: UserInterfaceSettingsPage - + General interface settings Основные настройки интерфейса - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) &Двойной клик чтобы разыграть карту (вместо одинарного) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings Настройки анимации - + &Tap/untap animation &Анимировать поворот/разворот карты - + Enable &sounds Yaaaaahoooo! All I needed for full happiness :) Вкючить &звуки - + Path to sounds directory: Путь к папке со звуками: - + Choose path Укажите путь @@ -4229,69 +5459,60 @@ Please enter a name: UserList - + Users online: %1 Пользователей онлайн: %1 - + Users in this room: %1 Пользователей в этой комнате: %1 - + Buddies online: %1 / %2 Друзей онлайн: %1 / %2 - + Ignored users online: %1 / %2 Игнорируемых пользователей онлайн: %1 / %2 - %1's games - Игры %1 + Игры %1 - User &details - Данные о &пользователе + Данные о &пользователе - Direct &chat - Обратиться &лично + Обратиться &лично - Show this user's &games - Показывать игры &этого пользователя + Показывать игры &этого пользователя - Add to &buddy list - Добавить в список &друзей + Добавить в список &друзей - Remove from &buddy list - &Удалить из друзей + &Удалить из друзей - Add to &ignore list - Добавить в &игнор-лист + Добавить в &игнор-лист - Remove from &ignore list - Удалить и&з игнор-листа + Удалить и&з игнор-листа - Ban from &server - За&банить на сервере + За&банить на сервере Duration @@ -4307,205 +5528,128 @@ Enter 0 for an indefinite ban. WndDeckEditor - &Search... - &Поиск... + &Поиск... - &Clear search - &Очистить строку поиска + &Очистить строку поиска - &Search for: - &Искать: + &Искать: - Deck &name: - &Название колоды: + &Название колоды: - &Comments: - Ко&мментарии: + Ко&мментарии: - - Hash: - - - - &Update prices - &Обновить цены + &Обновить цены - Ctrl+U - Ctrl+U + Ctrl+U - Deck editor [*] - Редактор колод [*] + Редактор колод [*] - &New deck - Новая коло&да + Новая коло&да - &Load deck... - &Загрузить колоду... + &Загрузить колоду... - &Save deck - Со&хранить колоду + Со&хранить колоду - Save deck &as... - Сохранить колоду к&ак... + Сохранить колоду к&ак... - Load deck from cl&ipboard... - Взять колоду из &буфера... + Взять колоду из &буфера... - Save deck to clip&board - Копировать колоду в бу&фер + Копировать колоду в бу&фер - &Print deck... - Пе&чать колоды... + Пе&чать колоды... - &Close - &Закрыть + &Закрыть - - Ctrl+Q - - - - &Edit sets... - Редактировать издани&я... + Редактировать издани&я... - &Deck - Ко&лода + Ко&лода - &Card database - База кар&т + База кар&т - Add card to &maindeck - Добавить ме&йном + Добавить ме&йном - - Return - - - - - Enter - - - - Add card to &sideboard - Добавить в са&йд + Добавить в са&йд - - Ctrl+Return - - - - - Ctrl+Enter - - - - &Remove row - &Удалить строку + &Удалить строку - - Del - - - - &Increment number - У&величить количество + У&величить количество - - + - - - - &Decrement number - У&меньшить количество + У&меньшить количество - - - - - - - Are you sure? - Вы уверены? + Вы уверены? - The decklist has been modified. Do you want to save the changes? - Деклист был отредактирован. + Деклист был отредактирован. Сохранить изменения? - Load deck - Загрузить колоду + Загрузить колоду - - Error - Ошибка + Ошибка - - The deck could not be saved. Please check that the directory is writable and try again. - Колода не может быть сохранена. + Колода не может быть сохранена. Убедитесь, что директория указана верно,а затем повторите попытку. - Save deck - Сохранить колоду + Сохранить колоду @@ -4519,17 +5663,17 @@ Please check that the directory is writable and try again. ZoneViewWidget - + sort by name Сортировать по имени - + sort by type сортировать по типу - + shuffle when closing Перемешать после просмотра diff --git a/cockatrice/translations/cockatrice_sk.ts b/cockatrice/translations/cockatrice_sk.ts index 268a6e5c7..7f8295f6a 100644 --- a/cockatrice/translations/cockatrice_sk.ts +++ b/cockatrice/translations/cockatrice_sk.ts @@ -1,35 +1,35 @@ - + AbstractCounter - + &Set counter... - + Ctrl+L - + F11 - + F12 - + Set counter - + New value for counter '%1': @@ -37,91 +37,91 @@ AppearanceSettingsPage - + Zone background pictures - - Path to hand background: + + Hand background: - - Path to stack background: + + Stack background: - - Path to table background: + + Table background: - - Path to player info background: + + Player info background: - - Path to picture of card back: + + Card back: - + Card rendering - + Display card names on cards having a picture - + Hand layout - + Display hand horizontally (wastes space) - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Zone view layout - + Sort by name - + Sort by type - - - - - + + + + + Choose path @@ -129,78 +129,83 @@ BanDialog - + ban &user name - + ban &IP address - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + + Please enter the reason for the ban that will be visible to the banned person. + + + + &OK - + &Cancel - + Ban user from server - + Error - + You have to select a name-based or IP-based ban, or both. @@ -208,273 +213,111 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name - + Sets - + Mana cost - + Card type - + P/T + + CardInfoText + + + Name: + + + + + Mana cost: + + + + + Card type: + + + + + P / T: + + + + + Loyalty: + + + CardInfoWidget - - Hide card info - - - - + Show card only - + Show text only - + Show full info - + Name: - + Mana cost: - + Card type: - + P / T: + + + Loyalty: + + CardItem - - &Play - - - - - &Hide - - - - - &Tap - - - - - &Untap - - - - - Toggle &normal untapping - - - - - &Flip - - - - - &Clone - - - - - Ctrl+H - - - - - &Attach to card... - - - - - Ctrl+A - - - - - Unattac&h - - - - - &Draw arrow... - - - - + &Power / toughness - - &Increase power - - - - - Ctrl++ - - - - - &Decrease power - - - - - Ctrl+- - - - - - I&ncrease toughness - - - - - Alt++ - - - - - D&ecrease toughness - - - - - Alt+- - - - - - In&crease power and toughness - - - - - Ctrl+Alt++ - - - - - Dec&rease power and toughness - - - - - Ctrl+Alt+- - - - - - Set &power and toughness... - - - - - Ctrl+P - - - - - &Set annotation... - - - - - red - - - - - yellow - - - - - green - - - - - &Add counter (%1) - - - - - &Remove counter (%1) - - - - - &Set counters (%1)... - - - - - &top of library - - - - - &bottom of library - - - - - &graveyard - - - - - Ctrl+Del - - - - - &exile - - - - + &Move to @@ -482,375 +325,286 @@ This is only saved for moderators and cannot be seen by the banned person. CardZone - + her hand nominative, female owner - + %1's hand nominative, female owner - + his hand nominative, male owner - - - %1's hand - nominative, male owner - - - - - of her hand - genitive, female owner - - - - - of %1's hand - genitive, female owner - - - - - of his hand - genitive, male owner - - - - - of %1's hand - genitive, male owner - - - - - her hand - accusative, female owner - - - - - %1's hand - accusative, female owner - - - - - his hand - accusative, male owner - - - - - %1's hand - accusative, male owner - - + %1's hand + nominative, male owner + + + + her library - nominative, female owner + look at zone, female owner - + %1's library - nominative, female owner + look at zone, female owner - + his library - nominative, male owner + look at zone, male owner - + %1's library - nominative, male owner + look at zone, male owner - + of her library - genitive, female owner + top cards of zone, female owner - + of %1's library - genitive, female owner + top cards of zone, female owner - + of his library - genitive, male owner + top cards of zone, male owner - + of %1's library - genitive, male owner + top cards of zone, male owner - + her library - accusative, female owner + reveal zone, female owner - + %1's library - accusative, female owner + reveal zone, female owner - + his library - accusative, male owner + reveal zone, male owner - + %1's library - accusative, male owner + reveal zone, male owner - + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + + + + + his library + shuffle, male owner + + + + + %1's library + shuffle, male owner + + + + + her sideboard + look at zone, female owner + + + + + %1's sideboard + look at zone, female owner + + + + + his sideboard + look at zone, male owner + + + + + %1's sideboard + look at zone, male owner + + + + + her library + nominative, female owner + + + + + %1's library + nominative, female owner + + + + + his library + nominative, male owner + + + + + %1's library + nominative, male owner + + + + her graveyard nominative, female owner - + %1's graveyard nominative, female owner - + his graveyard nominative, male owner - + %1's graveyard nominative, male owner - - of her graveyard - genitive, female owner - - - - - of %1's graveyard - genitive, female owner - - - - - of his graveyard - genitive, male owner - - - - - of %1's graveyard - genitive, male owner - - - - - her graveyard - accusative, female owner - - - - - %1's graveyard - accusative, female owner - - - - - his graveyard - accusative, male owner - - - - - %1's graveyard - accusative, male owner - - - - + her exile nominative, female owner - + %1's exile nominative, female owner - + his exile nominative, male owner - + %1's exile nominative, male owner - - of her exile - genitive, female owner - - - - - of %1's exile - genitive, female owner - - - - - of his exile - genitive, male owner - - - - - of %1's exile - genitive, male owner - - - - - her exile - accusative, female owner - - - - - %1's exile - accusative, female owner - - - - - his exile - accusative, male owner - - - - - %1's exile - accusative, male owner - - - - + her sideboard nominative, female owner - + %1's sideboard nominative, female owner - + his sideboard nominative, male owner - + %1's sideboard nominative, male owner + + + DBPriceUpdater - - of her sideboard - genitive, female owner + + + + Error - - of %1's sideboard - genitive, female owner + + + A problem has occured while fetching card prices. - - of his sideboard - genitive, male owner - - - - - of %1's sideboard - genitive, male owner - - - - - her sideboard - accusative, female owner - - - - - %1's sideboard - accusative, female owner - - - - - his sideboard - accusative, male owner - - - - - %1's sideboard - accusative, male owner + + A problem has occured while fetching card prices: DeckEditorSettingsPage - - Enable &price tag feature (using data from blacklotusproject.com) + + Enable &price tag feature - + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + General @@ -858,43 +612,77 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + Number - + Card - + Price + + DeckStatsInterface + + + + Error + + + + + The reply from the server could not be parsed. + + + DeckViewContainer - + Load &local deck - + Load d&eck from server - + Ready to s&tart - + + S&ideboard unlocked + + + + + S&ideboard locked + + + + Load deck + + + Error + + + + + The selected file could not be loaded. + + DlgCardSearch @@ -937,33 +725,28 @@ This is only saved for moderators and cannot be seen by the banned person. DlgConnect - + &Host: - + &Port: - + Player &name: - + P&assword: - - &OK - - - - - &Cancel + + &Save password @@ -975,87 +758,82 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + &Description: - + P&layers: - + Game type - + &Password: - + Only &buddies can join - + Only &registered users can join - + Joining restrictions - + &Spectators allowed - + Spectators &need a password to join - + Spectators can &chat - + Spectators see &everything - + Spectators - - &OK - - - - - &Cancel - - - - + Create game - + + Game information + + + + Error - + Server error. @@ -1063,115 +841,257 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: - + Token - + C&olor: - + white - + blue - + black - + red - + green - + multicolor - + colorless - + &P/T: - + &Annotation: - + &Destroy token when it leaves the table - - &OK + + Token data - - &Cancel + + Show &all tokens - + + Show tokens from this &deck + + + + + Choose token from list + + + + Create token + + DlgEditTokens + + + &Name: + + + + + C&olor: + + + + + white + + + + + blue + + + + + black + + + + + red + + + + + green + + + + + multicolor + + + + + colorless + + + + + &P/T: + + + + + &Annotation: + + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + + Filter games + + + DlgLoadDeckFromClipboard - + &Refresh - - &OK - - - - - &Cancel - - - - + Load deck from clipboard - + Error - + Invalid deck list. @@ -1179,17 +1099,7 @@ This is only saved for moderators and cannot be seen by the banned person. DlgLoadRemoteDeck - - O&K - - - - - &Cancel - - - - + Load deck @@ -1197,154 +1107,209 @@ This is only saved for moderators and cannot be seen by the banned person. DlgSettings - - - + + + Error - - Your card database is invalid. Would you like to go back and set the correct path? + + Unknown Error loading card database - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Appearance - + User interface - + Deck editor - + Messages - - - &Close - - GameSelector - - - - - - - - + + + + + + + + + Error - + Please join the appropriate room first. - + Wrong password. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join game - + Password: - + + Please join the respective room first. + + + + Games - - Show &full games + + &Filter games - - Show &running games + + C&lear filter - + C&reate - + &Join - + J&oin as spectator @@ -1360,72 +1325,72 @@ This is only saved for moderators and cannot be seen by the banned person. GamesModel - + yes - + yes, free for spectators - + no - + buddies only - + reg. users only - + not allowed - + Room - + Description - + Creator - + Game type - + Password - + Restrictions - + Players - + Spectators @@ -1433,94 +1398,147 @@ This is only saved for moderators and cannot be seen by the banned person. GeneralSettingsPage - - + + English - - - + + Reset/Clear Downloaded Pictures + + + + + + + + Choose path - + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + + + + + One or more downloaded card pictures could not be cleared. + + + + Personal settings - + Language: - + Download card pictures on the fly - + + Download high-quality card pictures + + + + Paths - + Decks directory: - + + Replays directory: + + + + Pictures directory: - - Path to card database: + + Card database: + + + + + Token database: MainWindow - + There are too many concurrent connections from your address. - - Banned by moderator. - - - - + Scheduled server shutdown. - - Unknown reason. + + Banned by moderator - + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + + Invalid username. + + + + Connection closed - + The server has terminated your connection. Reason: %1 - + Scheduled server shutdown - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -1531,201 +1549,240 @@ Reason for shutdown: %1 - + Number of players - + Please enter the number of players. - - + + Player %1 - + + Load replay + + + + About Cockatrice - + Version %1 - + Authors: - + Translators: - + Spanish: - + Portugese (Portugal): - + Portugese (Brazil): - + French: - + Japanese: - + Russian: - - Czech: - - - - + Italian: - - - - - - + + Swedish: + + + + + + + + + + + + + Error - + Server timeout - - Invalid login data. + + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + This server requires user registration. + + + + + Unknown login error: %1 + + + + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Disconnected - + + Connected, logging in at %1 + + + + Logged in at %1 - + &Connect... - + &Disconnect - + Start &local game... - + + &Watch replay... + + + + &Deck editor - + &Full screen - + Ctrl+F - + &Settings... - + &Exit - + &Cockatrice - + &About Cockatrice - + &Help - + Are you sure? - + There are still open games. Are you sure you want to quit? @@ -1733,994 +1790,1100 @@ Local version is %1, remote version is %2. MessageLogWidget - + The game has been closed. - + %1 is now watching the game. - + %1 is not watching the game any more. - + The game has started. - + You have joined game #%1. female - - - You have joined game #%1. - male - - - - - %1 has joined the game. - female - - - - - %1 has joined the game. - male - - - %1 has left the game. - female - - - - - %1 has left the game. + You have joined game #%1. male - - %1 is ready to start the game. + + You are watching a replay of game #%1. female - - %1 is ready to start the game. + + You are watching a replay of game #%1. male - - %1 is not ready to start the game any more. + + %1 has joined the game. female - - %1 is not ready to start the game any more. + + %1 has joined the game. male - - - %1 has conceded the game. - female - - - - - %1 has conceded the game. - male - - - - - %1 has restored connection to the game. - female - - - - - %1 has restored connection to the game. - male - - - - - %1 has lost connection to the game. - female - - - - - %1 has lost connection to the game. - male - - - - - %1 shuffles %2. - female - - - - - %1 shuffles %2. - male - - - - - %1 rolls a %2 with a %3-sided die. - female - - - - - %1 rolls a %2 with a %3-sided die. - male - - - - - %1 draws %n card(s). - female - - - - - - - - - %1 draws %n card(s). - male - - - - - - - - - %1 undoes his last draw. - - - - - %1 undoes her last draw. - - - - - %1 undoes his last draw (%2). - - - - - %1 undoes her last draw (%2). - - - - - from table - - - - - from graveyard - - - - - from exile - - - - - from hand - - - - - the bottom card of his library - - - - - the bottom card of her library - - - - - from the bottom of his library - - - - - from the bottom of her library - - - - - the top card of his library - - - - - the top card of her library - - - - - from the top of his library - - - - - from the top of her library - - - - - from library - - - - - from sideboard - - - - - from the stack - - - - - - a card - - - - - %1 gives %2 control over %3. - - - - - %1 puts %2 into play tapped%3. - - - - - %1 puts %2 into play%3. - - - - - %1 puts %2%3 into graveyard. - - - - - %1 exiles %2%3. - - - - - %1 moves %2%3 to hand. - - - - - %1 puts %2%3 into his library. - - - - - %1 puts %2%3 into her library. - - - - - %1 puts %2%3 on bottom of his library. - - - - - %1 puts %2%3 on bottom of her library. - - - - - %1 puts %2%3 on top of his library. - - - - - %1 puts %2%3 on top of her library. - - - - - %1 puts %2%3 into his library at position %4. - - - - - %1 puts %2%3 into her library at position %4. - - - - - %1 moves %2%3 to sideboard. - - - - - %1 plays %2%3. - - - - - %1 takes a mulligan to %n. - female - - - - - - - - - %1 takes a mulligan to %n. - male - - - - - - - - - %1 flips %2 face-down. - female - - - - - %1 flips %2 face-down. - male - - - - - %1 flips %2 face-up. - female - - - - - %1 flips %2 face-up. - male - - - - - %1 destroys %2. - female - - - - - %1 destroys %2. - male - - - - - %1 unattaches %2. - female - - - - - %1 unattaches %2. - male - - - - - %1 creates token: %2%3. - female - - - - - %1 creates token: %2%3. - male - - - - - %1 points from her %2 to herself. - female - - - - - %1 points from his %2 to himself. - male - - - - - %1 points from her %2 to %3. - p1 female, p2 female - - - - - %1 points from her %2 to %3. - p1 female, p2 male - - - - - %1 points from his %2 to %3. - p1 male, p2 female - - - - - %1 points from his %2 to %3. - p1 male, p2 male - - - - - %1 points from %2's %3 to herself. - card owner female, target female - - - - - %1 points from %2's %3 to herself. - card owner male, target female - - - - - %1 points from %2's %3 to himself. - card owner female, target male - - - - - %1 points from %2's %3 to himself. - card owner male, target male - - - - - %1 points from %2's %3 to %4. - p1 female, p2 female, p3 female - - - - - %1 points from %2's %3 to %4. - p1 female, p2 female, p3 male - - - - - %1 points from %2's %3 to %4. - p1 female, p2 male, p3 female - - - - - %1 points from %2's %3 to %4. - p1 female, p2 male, p3 male - - - - - %1 points from %2's %3 to %4. - p1 male, p2 female, p3 female - - - - - %1 points from %2's %3 to %4. - p1 male, p2 female, p3 male - - - - - %1 points from %2's %3 to %4. - p1 male, p2 male, p3 female - - - - - %1 points from %2's %3 to %4. - p1 male, p2 male, p3 male - - - - - %1 points from her %2 to her %3. - female - - - - - %1 points from his %2 to his %3. - male - - - - - %1 points from her %2 to %3's %4. - p1 female, p2 female - - - - - %1 points from her %2 to %3's %4. - p1 female, p2 male - - - - - %1 points from his %2 to %3's %4. - p1 male, p2 female - - - - - %1 points from his %2 to %3's %4. - p1 male, p2 male - - - - - %1 points from %2's %3 to her own %4. - card owner female, target female - - - - - %1 points from %2's %3 to her own %4. - card owner male, target female - - - - - %1 points from %2's %3 to his own %4. - card owner female, target male - - - - - %1 points from %2's %3 to his own %4. - card owner male, target male - - - - - %1 points from %2's %3 to %4's %5. - p1 female, p2 female, p3 female - - - - - %1 points from %2's %3 to %4's %5. - p1 female, p2 female, p3 male - - - - - %1 points from %2's %3 to %4's %5. - p1 female, p2 male, p3 female - - - - - %1 points from %2's %3 to %4's %5. - p1 female, p2 male, p3 male - - - - - %1 points from %2's %3 to %4's %5. - p1 male, p2 female, p3 female - - - - - %1 points from %2's %3 to %4's %5. - p1 male, p2 female, p3 male - - - - - %1 points from %2's %3 to %4's %5. - p1 male, p2 male, p3 female - - - - - %1 points from %2's %3 to %4's %5. - p1 male, p2 male, p3 male - - - - - %1 places %n %2 counter(s) on %3 (now %4). - female - - - - - - - - - %1 places %n %2 counter(s) on %3 (now %4). - male - - - - - - - - - %1 removes %n %2 counter(s) from %3 (now %4). - female - - - - - - - - - %1 removes %n %2 counter(s) from %3 (now %4). - male - - - - - - - - - %1 taps her permanents. - female - - - - - %1 untaps her permanents. - female - - - - - %1 taps his permanents. - male - - - - - %1 untaps his permanents. - male - - - - - %1 taps %2. - female - - - - - %1 untaps %2. - female - - - - - %1 taps %2. - male - - - - - %1 untaps %2. - male - - - - - %1 sets counter %2 to %3 (%4%5). - female - - - - - %1 sets counter %2 to %3 (%4%5). - male - - - - - %1 sets %2 to not untap normally. - female - - - - - %1 sets %2 to not untap normally. - male - - - - - %1 sets %2 to untap normally. - female - - - - - %1 sets %2 to untap normally. - male - - - - - %1 sets PT of %2 to %3. - female - - - - - %1 sets PT of %2 to %3. - male - - - - - %1 sets annotation of %2 to %3. - female - - - - - %1 sets annotation of %2 to %3. - male - - - - - %1 is looking at the top %2 cards %3. - female - - - - - %1 is looking at the top %2 cards %3. - male - - - - - %1 is looking at %2. - female - - - - - %1 is looking at %2. - male - - - - - %1 stops looking at %2. - female - - - - - %1 stops looking at %2. - male - - - - - %1 reveals %2 to %3. - p1 female, p2 female - - - - - %1 reveals %2 to %3. - p1 female, p2 male - - - - - %1 reveals %2 to %3. - p1 male, p2 female - - - - - %1 reveals %2 to %3. - p1 male, p2 male - - - - - %1 reveals %2. - female - - - - - %1 reveals %2. - male - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 randomly reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 randomly reveals %2%3. - female - - - - - %1 randomly reveals %2%3. - male - - - - - %1 reveals %2%3 to %4. - p1 female, p2 female - - - - - %1 reveals %2%3 to %4. - p1 female, p2 male - - - - - %1 reveals %2%3 to %4. - p1 male, p2 female - - - - - %1 reveals %2%3 to %4. - p1 male, p2 male - - - - - %1 reveals %2%3. - female - - - - - %1 reveals %2%3. - male - - - - - It is now %1's turn. - female - - - - - It is now %1's turn. - male - - - - - %1 draws his initial hand. - - - %1 has loaded a deck (%2). + %1 has left the game. female - %1 has loaded a deck (%2). + %1 has left the game. male - + + You have been kicked out of the game. + + + + + %1 has loaded a deck (%2). + + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + + %1 is ready to start the game. + female + + + + + %1 is ready to start the game. + male + + + + + %1 is not ready to start the game any more. + female + + + + + %1 is not ready to start the game any more. + male + + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + %1 has conceded the game. + female + + + + + %1 has conceded the game. + male + + + + + %1 has restored connection to the game. + female + + + + + %1 has restored connection to the game. + male + + + + + %1 has lost connection to the game. + female + + + + + %1 has lost connection to the game. + male + + + + + %1 shuffles %2. + female + + + + + %1 shuffles %2. + male + + + + + %1 rolls a %2 with a %3-sided die. + female + + + + + %1 rolls a %2 with a %3-sided die. + male + + + + + %1 draws %n card(s). + female + + + + + + + + + %1 draws %n card(s). + male + + + + + + + + + %1 undoes his last draw. + + + + + %1 undoes her last draw. + + + + + %1 undoes his last draw (%2). + + + + + %1 undoes her last draw (%2). + + + + + from table + + + + + from graveyard + + + + + from exile + + + + + from hand + + + + + the bottom card of %1's library + + + + + the bottom card of his library + + + + + the bottom card of her library + + + + + from the bottom of %1's library + + + + + from the bottom of his library + + + + + from the bottom of her library + + + + + the top card of %1's library + + + + + the top card of his library + + + + + the top card of her library + + + + + from the top of %1's library + + + + + from the top of his library + + + + + from the top of her library + + + + + from %1's library + + + + + from library + + + + + from sideboard + + + + + from the stack + + + + + + a card + + + + + %1 gives %2 control over %3. + + + + + %1 puts %2 into play tapped%3. + + + + + %1 puts %2 into play%3. + + + + + %1 puts %2%3 into graveyard. + + + + + %1 exiles %2%3. + + + + + %1 moves %2%3 to hand. + + + + + %1 puts %2%3 into his library. + + + + + %1 puts %2%3 into her library. + + + + + %1 puts %2%3 on bottom of his library. + + + + + %1 puts %2%3 on bottom of her library. + + + + + %1 puts %2%3 on top of his library. + + + + + %1 puts %2%3 on top of her library. + + + + + %1 puts %2%3 into his library at position %4. + + + + + %1 puts %2%3 into her library at position %4. + + + + + %1 moves %2%3 to sideboard. + + + + + %1 plays %2%3. + + + + + %1 takes a mulligan to %n. + female + + + + + + + + + %1 takes a mulligan to %n. + male + + + + + + + + + %1 flips %2 face-down. + female + + + + + %1 flips %2 face-down. + male + + + + + %1 flips %2 face-up. + female + + + + + %1 flips %2 face-up. + male + + + + + %1 destroys %2. + female + + + + + %1 destroys %2. + male + + + + + %1 unattaches %2. + female + + + + + %1 unattaches %2. + male + + + + + %1 creates token: %2%3. + female + + + + + %1 creates token: %2%3. + male + + + + + %1 points from her %2 to herself. + female + + + + + %1 points from his %2 to himself. + male + + + + + %1 points from her %2 to %3. + p1 female, p2 female + + + + + %1 points from her %2 to %3. + p1 female, p2 male + + + + + %1 points from his %2 to %3. + p1 male, p2 female + + + + + %1 points from his %2 to %3. + p1 male, p2 male + + + + + %1 points from %2's %3 to herself. + card owner female, target female + + + + + %1 points from %2's %3 to herself. + card owner male, target female + + + + + %1 points from %2's %3 to himself. + card owner female, target male + + + + + %1 points from %2's %3 to himself. + card owner male, target male + + + + + %1 points from %2's %3 to %4. + p1 female, p2 female, p3 female + + + + + %1 points from %2's %3 to %4. + p1 female, p2 female, p3 male + + + + + %1 points from %2's %3 to %4. + p1 female, p2 male, p3 female + + + + + %1 points from %2's %3 to %4. + p1 female, p2 male, p3 male + + + + + %1 points from %2's %3 to %4. + p1 male, p2 female, p3 female + + + + + %1 points from %2's %3 to %4. + p1 male, p2 female, p3 male + + + + + %1 points from %2's %3 to %4. + p1 male, p2 male, p3 female + + + + + %1 points from %2's %3 to %4. + p1 male, p2 male, p3 male + + + + + %1 points from her %2 to her %3. + female + + + + + %1 points from his %2 to his %3. + male + + + + + %1 points from her %2 to %3's %4. + p1 female, p2 female + + + + + %1 points from her %2 to %3's %4. + p1 female, p2 male + + + + + %1 points from his %2 to %3's %4. + p1 male, p2 female + + + + + %1 points from his %2 to %3's %4. + p1 male, p2 male + + + + + %1 points from %2's %3 to her own %4. + card owner female, target female + + + + + %1 points from %2's %3 to her own %4. + card owner male, target female + + + + + %1 points from %2's %3 to his own %4. + card owner female, target male + + + + + %1 points from %2's %3 to his own %4. + card owner male, target male + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 female, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 female, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 male, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 male, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 female, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 female, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 male, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 male, p3 male + + + + + %1 places %n %2 counter(s) on %3 (now %4). + female + + + + + + + + + %1 places %n %2 counter(s) on %3 (now %4). + male + + + + + + + + + %1 removes %n %2 counter(s) from %3 (now %4). + female + + + + + + + + + %1 removes %n %2 counter(s) from %3 (now %4). + male + + + + + + + + + %1 taps her permanents. + female + + + + + %1 untaps her permanents. + female + + + + + %1 taps his permanents. + male + + + + + %1 untaps his permanents. + male + + + + + %1 taps %2. + female + + + + + %1 untaps %2. + female + + + + + %1 taps %2. + male + + + + + %1 untaps %2. + male + + + + + %1 sets counter %2 to %3 (%4%5). + female + + + + + %1 sets counter %2 to %3 (%4%5). + male + + + + + %1 sets %2 to not untap normally. + female + + + + + %1 sets %2 to not untap normally. + male + + + + + %1 sets %2 to untap normally. + female + + + + + %1 sets %2 to untap normally. + male + + + + + %1 sets PT of %2 to %3. + female + + + + + %1 sets PT of %2 to %3. + male + + + + + %1 sets annotation of %2 to %3. + female + + + + + %1 sets annotation of %2 to %3. + male + + + + + %1 is looking at %2. + female + + + + + %1 is looking at %2. + male + + + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + + + + %1 stops looking at %2. + female + + + + + %1 stops looking at %2. + male + + + + + %1 reveals %2 to %3. + p1 female, p2 female + + + + + %1 reveals %2 to %3. + p1 female, p2 male + + + + + %1 reveals %2 to %3. + p1 male, p2 female + + + + + %1 reveals %2 to %3. + p1 male, p2 male + + + + + %1 reveals %2. + female + + + + + %1 reveals %2. + male + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 randomly reveals %2%3. + female + + + + + %1 randomly reveals %2%3. + male + + + + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 reveals %2%3. + female + + + + + %1 reveals %2%3. + male + + + + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + + It is now %1's turn. + female + + + + + It is now %1's turn. + male + + + + + %1 draws his initial hand. + + + + %1 draws her initial hand. - + %1 attaches %2 to %3's %4. p1 female, p2 female - + %1 attaches %2 to %3's %4. p1 female, p2 male - + %1 attaches %2 to %3's %4. p1 male, p2 female - + %1 attaches %2 to %3's %4. p1 male, p2 male - + red @@ -2729,7 +2892,7 @@ Local version is %1, remote version is %2. - + yellow @@ -2738,7 +2901,7 @@ Local version is %1, remote version is %2. - + green @@ -2747,62 +2910,62 @@ Local version is %1, remote version is %2. - + untap step - + upkeep step - + draw step - + first main phase - + beginning of combat step - + declare attackers step - + declare blockers step - + combat damage step - + end of combat step - + second main phase - + ending phase - + It is now the %1. @@ -2810,22 +2973,22 @@ Local version is %1, remote version is %2. MessagesSettingsPage - + Add message - + Message: - + &Add - + &Remove @@ -2833,57 +2996,57 @@ Local version is %1, remote version is %2. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -2891,388 +3054,550 @@ Local version is %1, remote version is %2. Player - + &View graveyard - + &View exile - + Player "%1" - + &Graveyard - + &Exile - - - + + + Move to &top of library - - - + + + Move to &bottom of library - - + + Move to &graveyard - - + + Move to &exile - - + + Move to &hand - + &View library - + View &top cards of library... - + Reveal &library to - + Reveal t&op card to - + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - + &Shuffle - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + &Hand - + &Reveal to - + Reveal r&andom card to - + &Sideboard - + &Library - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + + Cr&eate predefined token + + + + S&ay - + C&ard - + &All players - + + &Play + + + + + &Hide + + + + + &Tap + + + + + &Untap + + + + + Toggle &normal untapping + + + + + &Flip + + + + + &Peek at card face + + + + + &Clone + + + + + Ctrl+J + + + + + Attac&h to card... + + + + + Ctrl+A + + + + + Unattac&h + + + + + &Draw arrow... + + + + + &Increase power + + + + + Ctrl++ + + + + + &Decrease power + + + + + Ctrl+- + + + + + I&ncrease toughness + + + + + Alt++ + + + + + D&ecrease toughness + + + + + Alt+- + + + + + In&crease power and toughness + + + + + Ctrl+Alt++ + + + + + Dec&rease power and toughness + + + + + Ctrl+Alt+- + + + + + Set &power and toughness... + + + + + Ctrl+P + + + + + &Set annotation... + + + + + red + + + + + yellow + + + + + green + + + + + &Add counter (%1) + + + + + &Remove counter (%1) + + + + + &Set counters (%1)... + + + + + &top of library + + + + + &bottom of library + + + + + &graveyard + + + + + Ctrl+Del + + + + + &exile + + + + Ctrl+F3 - + F3 - + Ctrl+W - + F4 - + Ctrl+D - + Ctrl+E - + Ctrl+Shift+D - + Ctrl+M - + Ctrl+S - + Ctrl+U - + Ctrl+I - + Ctrl+T - + Ctrl+G - + View top cards of library - + Number of cards: - + Draw cards - - - - + + + + Number: - + Move top cards to grave - + Move top cards to exile - + Roll die - + Number of sides: - + Set power/toughness - + Please enter the new PT: - + Set annotation - + Please enter the new annotation: - + Set counters - - PlayerListWidget - - - User &details - - - - - Direct &chat - - - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Kick from &game - - - QObject - - Maindeck + + Cockatrice replays (*.cor) - - Sideboard + + Common deck formats (*.cod *.dec *.mwDeck) - - Cockatrice decks (*.cod) - - - - - Plain text decks (*.dec *.mwDeck) - - - - + All files (*.*) @@ -3280,50 +3605,83 @@ Local version is %1, remote version is %2. RemoteDeckList_TreeModel - + Name - + ID - + Upload time + + RemoteReplayList_TreeModel + + + ID + + + + + Name + + + + + Players + + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Players - + Games @@ -3344,27 +3702,17 @@ Local version is %1, remote version is %2. ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - - &OK - - - - - &Cancel - - - - + Shut down server @@ -3372,246 +3720,468 @@ Local version is %1, remote version is %2. TabAdmin - + Update server &message - + &Shut down server - + Server administration functions - + &Unlock functions - + &Lock functions - + Unlock administration functions - + Do you really want to unlock the administration functions? + + + TabDeckEditor - - Administration + + Show card text only + + + + + &Clear search + + + + + &Search for: + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Update prices + + + + + Ctrl+U + + + + + &New deck + + + + + &Load deck... + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Save deck to clip&board + + + + + &Print deck... + + + + + &Analyze deck on deckstats.net + + + + + &Close + + + + + Ctrl+Q + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + &Remove row + + + + + Del + + + + + &Increment number + + + + + + + + + + + &Decrement number + + + + + - + + + + + &Deck editor + + + + + C&ard database + + + + + &Edit sets... + + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + Load deck + + + + + + + Error + + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Upload deck - + Download deck - - + + New folder - + + Delete - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + Name of new folder: - - Deck storage + + Delete remote folder + + + + + Delete remote deck TabGame - + F5 - + F6 - + F7 - + F8 - + F9 - + F10 - + &Phases - + &Game - + Next &phase - + Ctrl+Space - + + Tab + + + + Next &turn - + Ctrl+Return - + Ctrl+Enter - + &Remove all local arrows - + Ctrl+R - + + Game &information + + + + &Concede - + F2 - + &Leave game - + + Ctrl+Q - + + C&lose replay + + + + &Say: - + Concede - + Are you sure you want to concede this game? - + Leave game - + Are you sure you want to leave this game? - - Kicked + + Replay %1: %2 - - You have been kicked out of the game. - - - - + Game %1: %2 @@ -3619,124 +4189,234 @@ Please enter a name: TabMessage - + Personal &talk - + &Leave - + This user is ignoring you. - + %1 has left the server. - + %1 has joined the server. - + Talking to %1 + + TabReplays + + + Local file system + + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + TabRoom - + &Say: - + Chat - + &Room - + &Leave room - - You are flooding the chat. Please wait a couple of seconds. + + &Ignore unregistered users in chat - - - TabServer - - Server + + You are flooding the chat. Please wait a couple of seconds. TabUserLists - - User lists + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + + + + + Direct &chat + + + + + Show this user's &games + + + + + Add to &buddy list + + + + + Remove from &buddy list + + + + + Add to &ignore list + + + + + Remove from &ignore list + + + + + Kick from &game + + + + + Ban from &server + + + + + %1's games UserInfoBox - + User information - + Real name: - + Gender: - + Location: - + User level: - + Administrator - + Moderator - + Registered user - + Unregistered user @@ -3744,37 +4424,47 @@ Please enter a name: UserInterfaceSettingsPage - + General interface settings - + + Enable notifications in taskbar + + + + &Double-click cards to play them (instead of single-click) - + + &Play all nonlands onto the stack (not the battlefield) by default + + + + Animation settings - + &Tap/untap animation - + Enable &sounds - + Path to sounds directory: - + Choose path @@ -3782,272 +4472,25 @@ Please enter a name: UserList - + Users online: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 - - - %1's games - - - - - User &details - - - - - Direct &chat - - - - - Show this user's &games - - - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Ban from &server - - - - - WndDeckEditor - - - &Search... - - - - - &Clear search - - - - - &Search for: - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &Update prices - - - - - Ctrl+U - - - - - Deck editor [*] - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clip&board - - - - - &Print deck... - - - - - &Close - - - - - Ctrl+Q - - - - - &Edit sets... - - - - - &Deck - - - - - &Card database - - - - - Add card to &maindeck - - - - - Return - - - - - Enter - - - - - Add card to &sideboard - - - - - Ctrl+Return - - - - - Ctrl+Enter - - - - - &Remove row - - - - - Del - - - - - &Increment number - - - - - + - - - - - &Decrement number - - - - - - - - - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - - Error - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - WndSets @@ -4060,17 +4503,17 @@ Please check that the directory is writable and try again. ZoneViewWidget - + sort by name - + sort by type - + shuffle when closing diff --git a/cockatrice/translations/cockatrice_sv.ts b/cockatrice/translations/cockatrice_sv.ts new file mode 100644 index 000000000..8a402f35a --- /dev/null +++ b/cockatrice/translations/cockatrice_sv.ts @@ -0,0 +1,5205 @@ + + + + + AbstractCounter + + + &Set counter... + &Placera polett... + + + + Ctrl+L + + + + + F11 + + + + + F12 + + + + + Set counter + Placera polett + + + + New value for counter '%1': + Nytt värde för '%1': + + + + AppearanceSettingsPage + + + Zone background pictures + Zonbakgrundsbilder + + + Path to hand background: + Sökväg till handbakgrund: + + + Path to stack background: + Sökväg till stapelbakgrund: + + + Path to table background: + Sökväg till bordbakgrund: + + + Path to player info background: + Sökväg till spelarinfobakgrund: + + + Path to picture of card back: + Sökväg till kortbaksidans bild: + + + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + + Card rendering + Kortrendering + + + + Display card names on cards having a picture + Visa kortnamn på kort som har bilder + + + + Hand layout + Handlayout + + + + Display hand horizontally (wastes space) + Visa hand horisontellt (slösar plats) + + + + Table grid layout + Bordets rutnätlayout + + + + Invert vertical coordinate + Invertera vertikal koordinat + + + + Minimum player count for multi-column layout: + Minst antal spelare för multi-kolumnlayout: + + + + Zone view layout + Zonvylayout + + + + Sort by name + Sortera efter namn + + + + Sort by type + Sortera efter typ + + + + + + + + Choose path + Välj sökväg + + + + BanDialog + + + ban &user name + bannlys användar&namn + + + + ban &IP address + bannlys &IP address + + + + Ban type + Typ av bannlysning + + + + &permanent ban + &permanent bannlysning + + + + &temporary ban + &temporär bannlysning + + + + &Days: + &Dagar: + + + + &Hours: + &Timmar: + + + + &Minutes: + &Minuter: + + + + Duration of the ban + Bannlysningens längd + + + + Please enter the reason for the ban. +This is only saved for moderators and cannot be seen by the banned person. + Vänligen ange anledningen till bannlysningen. +Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. + + + + Please enter the reason for the ban that will be visible to the banned person. + Vänligen ange en anledning för bannlysningen som kommer att visas för den bannlysta personen. + + + + &OK + &OK + + + + &Cancel + &Avbryt + + + + Ban user from server + Bannlys användaren från servern + + + + Error + Fel + + + + You have to select a name-based or IP-based ban, or both. + Du måste väja en namnbaserad eller IP-baserad bannlysning, eller både och. + + + + CardDatabaseModel + + + Name + Namn + + + + Sets + Utgåvor + + + + Mana cost + Manakostnad + + + + Card type + Korttyp + + + + P/T + P/T + + + + CardInfoText + + + Name: + Namn: + + + + Mana cost: + Manakostnad: + + + + Card type: + Korttyp: + + + + P / T: + P / T: + + + + Loyalty: + Lojalitet: + + + + CardInfoWidget + + + Show card only + Visa endast kort + + + + Show text only + Visa endast text + + + + Show full info + Vissa full info + + + + Name: + Namn: + + + + Mana cost: + Manakostnad: + + + + Card type: + Korttyp: + + + + P / T: + P / T: + + + + Loyalty: + Lojalitet: + + + + CardItem + + &Play + &Spela + + + &Hide + &Göm + + + &Tap + &Tappa + + + &Untap + Tappa &upp + + + Toggle &normal untapping + Växla &normal upptappning + + + &Flip + &Vänd + + + &Clone + &Klona + + + &Attach to card... + &Fäst på kort... + + + Unattac&h + Se&parera + + + &Draw arrow... + &Rita pil... + + + + &Power / toughness + Po&wer / toughness + + + &Increase power + &Öka power + + + &Decrease power + &Minska power + + + I&ncrease toughness + Öka toug&hness + + + D&ecrease toughness + Minska t&oughness + + + In&crease power and toughness + Öka power o&ch toughness + + + Dec&rease power and toughness + M&inska power och toughness + + + Set &power and toughness... + An&ge power och toughness... + + + &Set annotation... + Ang&e annotering... + + + red + röd + + + yellow + gul + + + green + grön + + + &Add counter (%1) + P&lacera polett (%1) + + + &Remove counter (%1) + Ta &bort polett (%1) + + + &Set counters (%1)... + Pl&acera poletter (%1)... + + + &top of library + &överst i leken + + + &bottom of library + &underst i leken + + + &graveyard + kyrkog&ård + + + &exile + e&xil + + + + &Move to + Fl&ytta till + + + + CardZone + + + her hand + nominative, female owner + sin hand + + + + %1's hand + nominative, female owner + %1's hand + + + + his hand + nominative, male owner + sin hand + + + + %1's hand + nominative, male owner + %1's hand + + + + her library + look at zone, female owner + sin lek + + + + %1's library + look at zone, female owner + %1's lek + + + + his library + look at zone, male owner + sin lek + + + + %1's library + look at zone, male owner + %1's lek + + + + of her library + top cards of zone, female owner + av sin lek + + + + of %1's library + top cards of zone, female owner + av %1's lek + + + + of his library + top cards of zone, male owner + av sin lek + + + + of %1's library + top cards of zone, male owner + av %1's lek + + + + her library + reveal zone, female owner + sinn lek + + + + %1's library + reveal zone, female owner + %1's lek + + + + his library + reveal zone, male owner + sin lek + + + + %1's library + reveal zone, male owner + %1's lek + + + + her library + shuffle, female owner + sin lek + + + + %1's library + shuffle, female owner + %1's lek + + + + his library + shuffle, male owner + sin lek + + + + %1's library + shuffle, male owner + %1's lek + + + + her sideboard + look at zone, female owner + sin sidbräda + + + + %1's sideboard + look at zone, female owner + %1's sidbräda + + + + his sideboard + look at zone, male owner + sin sidbräda + + + + %1's sideboard + look at zone, male owner + %1's sidbräda + + + of her hand + genitive, female owner + av sin hand + + + of %1's hand + genitive, female owner + av %1's hand + + + of his hand + genitive, male owner + av sin hand + + + of %1's hand + genitive, male owner + av %1's hand + + + her hand + accusative, female owner + sin hand + + + %1's hand + accusative, female owner + %1's hand + + + his hand + accusative, male owner + sin hand + + + %1's hand + accusative, male owner + %1's hand + + + + her library + nominative, female owner + sin lek + + + + %1's library + nominative, female owner + %1's lek + + + + his library + nominative, male owner + sin lek + + + + %1's library + nominative, male owner + %1's lek + + + of her library + genitive, female owner + av sin lek + + + of %1's library + genitive, female owner + av %1's lek + + + of his library + genitive, male owner + av sin lek + + + of %1's library + genitive, male owner + av %1's lek + + + her library + accusative, female owner + hennes lek + + + %1's library + accusative, female owner + %1's lek + + + his library + accusative, male owner + hans lek + + + %1's library + accusative, male owner + %1's lek + + + + her graveyard + nominative, female owner + sin kyrkogård + + + + %1's graveyard + nominative, female owner + %1's kyrkogård + + + + his graveyard + nominative, male owner + sin kyrkogård + + + + %1's graveyard + nominative, male owner + %1's kyrkogård + + + of her graveyard + genitive, female owner + av sin kyrkogård + + + of %1's graveyard + genitive, female owner + av %1's kyrkogård + + + of his graveyard + genitive, male owner + av sin kyrkogård + + + of %1's graveyard + genitive, male owner + av %1's kyrkogård + + + her graveyard + accusative, female owner + hennes kyrkogård + + + %1's graveyard + accusative, female owner + %1's kyrkogård + + + his graveyard + accusative, male owner + hans kyrkogård + + + %1's graveyard + accusative, male owner + %1's kyrkogård + + + + her exile + nominative, female owner + sin exil + + + + %1's exile + nominative, female owner + %1's exil + + + + his exile + nominative, male owner + sen exil + + + + %1's exile + nominative, male owner + %1's exil + + + of her exile + genitive, female owner + sin exil + + + of %1's exile + genitive, female owner + %1's exil + + + of his exile + genitive, male owner + sin exil + + + of %1's exile + genitive, male owner + %1's exil + + + her exile + accusative, female owner + hennes exil + + + %1's exile + accusative, female owner + %1's exil + + + his exile + accusative, male owner + hans exil + + + %1's exile + accusative, male owner + %1's exil + + + + her sideboard + nominative, female owner + sin sidbräda + + + + %1's sideboard + nominative, female owner + %1's sidbräda + + + + his sideboard + nominative, male owner + sin sidbräda + + + + %1's sideboard + nominative, male owner + %1's sidbräda + + + of her sideboard + genitive, female owner + av sin sidbräda + + + of %1's sideboard + genitive, female owner + av %1's sidbräda + + + of his sideboard + genitive, male owner + av sin sidbräda + + + of %1's sideboard + genitive, male owner + av %1's sidbräda + + + her sideboard + accusative, female owner + hennes sidbräda + + + %1's sideboard + accusative, female owner + %1's sidbräda + + + his sideboard + accusative, male owner + hans sidbräda + + + %1's sideboard + accusative, male owner + %1's sidbräda + + + + DBPriceUpdater + + + + + Error + Fel + + + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + Enable &price tag feature (using data from blacklotusproject.com) + Aktivera &prislappsfunktionen (använder data från blacklotusproject.com) + + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + + General + Allmänt + + + + DeckListModel + + + Number + Nummer + + + + Card + Kort + + + + Price + Pris + + + + DeckStatsInterface + + + + Error + Fel + + + + The reply from the server could not be parsed. + Svaret från servern kunde inte tolkas. + + + + DeckViewContainer + + + Load &local deck + &Ladda lokal lek + + + + Load d&eck from server + Ladda lek från &servern + + + + Ready to s&tart + Redo att &börja + + + + S&ideboard unlocked + S&idbräda upplåst + + + + S&ideboard locked + S&idbräda låst + + + + Load deck + Ladda lek + + + + Error + Fel + + + + The selected file could not be loaded. + Den valda filen kunde ej laddas. + + + + DlgCardSearch + + + Card name: + Kortnamn: + + + + Card text: + Korttext: + + + + Card type (OR): + Korttyp (ELLER): + + + + Color (OR): + Färg (ELLER): + + + + O&K + &OK + + + + &Cancel + &Avbryt + + + + Card search + Kortsökning + + + + DlgConnect + + + &Host: + &Värd: + + + + &Port: + &Port: + + + + Player &name: + Spelar&namn: + + + + P&assword: + &Lösenord: + + + + &Save password + &Spara lösenord + + + &OK + &OK + + + &Cancel + &Avbryt + + + + Connect to server + Anslut till server + + + + DlgCreateGame + + + &Description: + &Beskrivning: + + + + P&layers: + &Spelare: + + + + Game type + Speltyp + + + + &Password: + &Lösenord: + + + + Only &buddies can join + Endast &vänner kan ansluta + + + + Only &registered users can join + Endast &registerade användare kan ansluta + + + + Joining restrictions + Anslutingsbegränsningar + + + + &Spectators allowed + &Åskådare tillåtna + + + + Spectators &need a password to join + Åskådare &behöver ett lösenord för att ansluta + + + + Spectators can &chat + Åskådare kan &chatta + + + + Spectators see &everything + Åskådare kan s&e allt + + + + Spectators + Åskådare + + + &OK + &OK + + + &Cancel + &Avbryt + + + + Create game + Skapa spel + + + + Game information + Spelinformation + + + + Error + Fel + + + + Server error. + Serverfel. + + + + DlgCreateToken + + + &Name: + &Namn: + + + + Token + Jetong + + + + C&olor: + &Färg: + + + + white + vit + + + + blue + blå + + + + black + svart + + + + red + röd + + + + green + grön + + + + multicolor + multifärgad + + + + colorless + färglös + + + + &P/T: + &P/T: + + + + &Annotation: + &Annotering: + + + + &Destroy token when it leaves the table + &Förstör jetong när den lämnar bordet + + + + Token data + Jetongdata + + + + Show &all tokens + Visa alla &jetonger + + + + Show tokens from this &deck + Visa jetonger från denna &lek + + + + Choose token from list + Välj jetong från lista + + + &OK + &OK + + + &Cancel + &Avbryt + + + + Create token + Skapa jetong + + + + DlgEditTokens + + + &Name: + &Namn: + + + + C&olor: + &Färg: + + + + white + vit + + + + blue + blå + + + + black + svart + + + + red + röd + + + + green + grön + + + + multicolor + multifärgad + + + + colorless + färglös + + + + &P/T: + &P/T: + + + + &Annotation: + &Annotering: + + + + Token data + Jetongdata + + + + + Add token + Lägg till jetong + + + + Remove token + Ta bort jetong + + + + Edit tokens + Redigera jetong + + + + Please enter the name of the token: + Vänligen ange namnet på jetongen: + + + + Error + Fel + + + + The chosen name conflicts with an existing card or token. + Det valda namnet kommer i konflikt med ett redan existerande kort eller jetong. + + + + DlgFilterGames + + + Show &unavailable games + Visa &otillgängliga spel + + + + Show &password protected games + + + + + Game &description: + Spel&beskrivning: + + + + &Creator name: + S&kaparens namn: + + + + &Game types + &Speltyper + + + + at &least: + &Minst: + + + + at &most: + M&ax: + + + + Maximum player count + Maximalt antal spelare + + + + Filter games + Filtrera spel + + + + DlgLoadDeckFromClipboard + + + &Refresh + &Uppdatera + + + &OK + &OK + + + &Cancel + &Avbryt + + + + Load deck from clipboard + Ladda lek från urklipp + + + + Error + Fel + + + + Invalid deck list. + Ogiltig leklista. + + + + DlgLoadRemoteDeck + + O&K + &OK + + + &Cancel + &Avbryt + + + + Load deck + Ladda lek + + + + DlgSettings + + + + + Error + Fel + + + Your card database is invalid. Would you like to go back and set the correct path? + Din kortdatabas är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? + + + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? + Sökvägen till din lekkatalog är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? + + + + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? + Sökvägen till din kortbildsdatabas är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? + + + + Settings + Inställningar + + + + General + Allmänt + + + + Appearance + Utseende + + + + User interface + Användargränssnitt + + + + Deck editor + Lekredigeraren + + + + Messages + Meddelanden + + + &Close + &Stäng + + + + GameSelector + + + + + + + + + + + Error + Fel + + + + Please join the appropriate room first. + Vänligen anslut till det lämpliga rummet först. + + + + Wrong password. + Fel lösenord. + + + + Spectators are not allowed in this game. + Åskådare är ej tillåtna i detta spelet. + + + + The game is already full. + Spelet är redan fullt. + + + + The game does not exist any more. + Det här spelet finns inte längre. + + + + This game is only open to registered users. + Det här spelet är bara öppet för registrerade användare. + + + + This game is only open to its creator's buddies. + Det här spelet är bara öppet för skaparens vänner. + + + + You are being ignored by the creator of this game. + Spelets skapare ignorerar dig. + + + + Join game + Anslut till spel + + + + Password: + Lösenord: + + + + Please join the respective room first. + Vänligen anslut till respektive rum först. + + + + Games + Spel + + + + &Filter games + &Filtrera spel + + + + C&lear filter + &Rensa filter + + + Show u&navailable games + Visa &otillgängliga spel + + + + C&reate + &Skapa + + + + &Join + &Anslut + + + + J&oin as spectator + Anslut som &åskådare + + + + GameView + + + Esc + + + + + GamesModel + + + yes + ja + + + + yes, free for spectators + ja, fritt för åskådare + + + + no + nej + + + + buddies only + endast vänner + + + + reg. users only + endast reg. användare + + + + not allowed + ej tillåtna + + + + Description + Beskrivning + + + + Room + Rum + + + + Creator + Skapare + + + + Game type + Speltyp + + + + Password + Lösenord + + + + Restrictions + Begränsningar + + + + Players + Spelare + + + + Spectators + Åskådare + + + + GeneralSettingsPage + + + + English + Svenska + + + + Reset/Clear Downloaded Pictures + + + + + + + + + Choose path + Välj sökväg + + + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + Fel + + + + One or more downloaded card pictures could not be cleared. + + + + + Personal settings + Personliga inställningar + + + + Language: + Språk: + + + + Download card pictures on the fly + Ladda ner kortbilder på direkten + + + + Download high-quality card pictures + + + + + Paths + Sökvägar + + + + Decks directory: + Lekkatalog: + + + + Replays directory: + Repriskatalog: + + + + Pictures directory: + Bildkatalog: + + + + Card database: + + + + + Token database: + + + + Path to card database: + Sökväg till kortdatabas: + + + Path to token database: + Sökväg till jetongdatabas: + + + + MainWindow + + + There are too many concurrent connections from your address. + Din adress har för många uppkopplingar samtidigt. + + + Banned by moderator. + Bannlyst av moderator. + + + + Scheduled server shutdown. + Schemalagd serverstängning. + + + Unknown reason. + Okänd anledning. + + + + Banned by moderator + Bannlyst av moderator + + + + Expected end time: %1 + Förväntad sluttid: %1 + + + + This ban lasts indefinitely. + Denna bannlysning varar för evigt. + + + + + Invalid username. + + + + + Connection closed + Uppkoppling avslutad + + + + The server has terminated your connection. +Reason: %1 + Servern har avslutat din uppkoppling. +Anledning: %1 + + + + Scheduled server shutdown + Schemalagd serverstängning + + + + The server is going to be restarted in %n minute(s). +All running games will be lost. +Reason for shutdown: %1 + + Servern kommer att starts om om %n minut. +Alla pågående spel kommer att gå förlorade. +Anledning till nedstängning: %1 + Servern kommer att starts om om %n minuter. +Alla pågående spel kommer att gå förlorade. +Anledning till nedstängning: %1 + + + + + Number of players + Antal spelare + + + + Please enter the number of players. + Vänligen ange antal spelare. + + + + + Player %1 + Spelare %1 + + + + Load replay + Ladda repris + + + + About Cockatrice + Om Cockatrice + + + + Version %1 + Version %1 + + + + Authors: + Upphovsmän: + + + + Translators: + Översättare: + + + + Spanish: + Spanska: + + + + Portugese (Portugal): + Portugesiska (Portugal): + + + + Portugese (Brazil): + Portugesiska (Brasilien): + + + + French: + Franska: + + + + Japanese: + Japanska: + + + + Russian: + Ryska: + + + Czech: + Tjeckiska: + + + + Italian: + Italienska: + + + + Swedish: + Svenska: + + + + + + + + + + + + + Error + Fel + + + + Server timeout + Server timeout + + + Invalid login data. + Ogiltig inloggningsdata. + + + + Incorrect username or password. Please check your authentication information and try again. + + + + + There is already an active session using this user name. +Please close that session first and re-login. + Det finns redan en aktiv session med det användarnamnet. +Vänligen stäng den sessionen först och försök igen. + + + + You are banned until %1. + Du är bannlyst till %1. + + + + You are banned indefinitely. + Du är bannlyst för evigt. + + + + This server requires user registration. + + + + + Unknown login error: %1 + Okänt inloggningsfel: %1 + + + + Socket error: %1 + Socketfel: %1 + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. +Local version is %1, remote version is %2. + Du försöker koppla upp dig till en föråldrad server. Vänligen nergradera din version av Cockatrice eller koppla upp dig till en lämplig server. +Lokal version är %1, avlägsen version är %2. + + + + Your Cockatrice client is obsolete. Please update your Cockatrice version. +Local version is %1, remote version is %2. + Din version av Cockatrice är föråldrad. Vänligen uppdatera din version av Cockatrice. +Lokal version är %1, avlägsen version är %2. + + + + Connecting to %1... + Ansluter till %1... + + + + Disconnected + Frånkopplad + + + + Connected, logging in at %1 + Uppkopplad, loggar in hos %1 + + + + Logged in at %1 + Inloggad hos %1 + + + + &Connect... + &Anslut... + + + + &Disconnect + &Frånkoppla + + + + Start &local game... + Starta &lokalt spel... + + + + &Watch replay... + &Titta på repris... + + + + &Deck editor + Lek&redigeraren + + + + &Full screen + &Fullskärmsläge + + + + Ctrl+F + + + + + &Settings... + &Inställningar... + + + + &Exit + A&vsluta + + + + &Cockatrice + &Cockatrice + + + + &About Cockatrice + &Om Cockatrice + + + + &Help + &Hjälp + + + + Are you sure? + Är du säker? + + + + There are still open games. Are you sure you want to quit? + Du har fortfarande öppna spel. Är du säker på att du vill avsluta? + + + + MessageLogWidget + + + You have joined game #%1. + female + Du har gått med i spel #%1. + + + + You have joined game #%1. + male + Du har gått med i spel #%1. + + + + You are watching a replay of game #%1. + female + Du tittar på en repris av spel #%1. + + + + You are watching a replay of game #%1. + male + Du tittar på en repris av spel #%1. + + + + %1 has joined the game. + female + %1 har gått med i spelet. + + + + %1 has joined the game. + male + %1 har gått med i spelet. + + + + %1 has left the game. + female + %1 har lämnat spelet. + + + + %1 has left the game. + male + %1 har lämnat spelet. + + + + The game has been closed. + Spelet har stängts. + + + + You have been kicked out of the game. + Du har blivit utsparkad från spelet. + + + + %1 is now watching the game. + %1 kollar nu på spelet. + + + + %1 is not watching the game any more. + %1 kollar inte längre på spelet. + + + %1 has loaded a deck (%2). + female + %1 har laddat en lek (%2). + + + %1 has loaded a deck (%2). + male + %1 har laddat en lek (%2). + + + + %1 has loaded a deck (%2). + %1 har laddat en lek (%2). + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + + %1 is ready to start the game. + female + %1 är redo att börja spelet. + + + + %1 is ready to start the game. + male + %1 är redo att börja spelet. + + + + %1 is not ready to start the game any more. + female + %1 är inte längre redo att börja spelet. + + + + %1 is not ready to start the game any more. + male + %1 är inte längre redo att börja spelet. + + + + %1 has locked her sideboard. + female + %1 har låst sin sidbräda. + + + + %1 has locked his sideboard. + male + %1 har låst sin sidbräda. + + + + %1 has unlocked her sideboard. + female + %1 har låst upp sin sidbräda. + + + + %1 has unlocked his sideboard. + male + %1 har låst upp sin sidbräda. + + + + %1 has conceded the game. + female + %1 har gett upp spelet. + + + + %1 has conceded the game. + male + %1 har gett upp spelet. + + + + The game has started. + Spelet har börjat. + + + + %1 has restored connection to the game. + female + %1 har återställt uppkopplingen med spelet. + + + + %1 has restored connection to the game. + male + %1 har återställt uppkopplingen med spelet. + + + + %1 has lost connection to the game. + female + %1 har förlorat uppkopplingen med spelet. + + + + %1 has lost connection to the game. + male + %1 har förlorat uppkopplingen med spelet. + + + + %1 shuffles %2. + female + %1 blandar %2. + + + + %1 shuffles %2. + male + %1 blandar %2. + + + + %1 rolls a %2 with a %3-sided die. + female + %1 rullar en %2a med en %3-sidig tärning. + + + + %1 rolls a %2 with a %3-sided die. + male + %1 rullar en %2a med en %3-sidig tärning. + + + + %1 draws %n card(s). + female + + %1 drar %n kort. + %1 drar %n kort. + + + + + %1 draws %n card(s). + male + + %1 drar %n kort. + %1 drar %n kort. + + + + + %1 undoes her last draw. + %1 ångrar sitt senaste drag. + + + + %1 undoes his last draw. + %1 ångrar sitt senaste drag. + + + + %1 undoes her last draw (%2). + %1 ångrar sitt senaste drag (%2). + + + + %1 undoes his last draw (%2). + %1 ångrar sitt senaste drag (%2). + + + + from table + från bordet + + + + from graveyard + från kyrkogården + + + + from exile + från exil + + + + from hand + från sin hand + + + + the bottom card of %1's library + det sista kortet i %1's lek + + + + the bottom card of her library + det sista kortet i sin lek + + + + the bottom card of his library + det sista kortet i sin lek + + + + from the bottom of %1's library + från botten av %1's lek + + + + from the bottom of her library + från botten av sin lek + + + + from the bottom of his library + från botten av sin lek + + + + the top card of %1's library + det översta kortet av %1's lek + + + + the top card of her library + det översta kortet av sin lek + + + + the top card of his library + det översta kortet avsin lek + + + + from the top of %1's library + från toppen av %1's lek + + + + from the top of her library + från toppen av sin lek + + + + from the top of his library + från botten av sin lek + + + + from %1's library + från %1'a lek + + + + from library + från leken + + + + from sideboard + från sidbrädan + + + + from the stack + från stapeln + + + + + a card + ett kort + + + + %1 gives %2 control over %3. + %1 ger kontroll över %3 till %2. + + + + %1 puts %2 into play tapped%3. + %1 sätter %2 i spell tappad%3. + + + + %1 puts %2 into play%3. + %1 sätter %2 i spel%3. + + + + %1 puts %2%3 into graveyard. + %1 placerar %2%3 i kyrkogården. + + + + %1 exiles %2%3. + %1 sätter %2%3 i exil. + + + + %1 moves %2%3 to hand. + %1 flyttar %2%3 till sin hand. + + + + %1 puts %2%3 into her library. + %1 placerar %2%3 i sin lek. + + + + %1 puts %2%3 into his library. + %1 placerar %2%3 i sin lek. + + + + %1 puts %2%3 on bottom of her library. + %1 placerar %2%3 underst i sin lek. + + + + %1 puts %2%3 on bottom of his library. + %1 placerar %2%3 underst i sin lek. + + + + %1 puts %2%3 on top of her library. + %1 placerar %2%3 överst i sin lek. + + + + %1 puts %2%3 on top of his library. + %1 placerar %2%3 överst i sin lek. + + + + %1 puts %2%3 into her library at position %4. + %1 placerar %2%3 på plats %4 i sin lek. + + + + %1 puts %2%3 into his library at position %4. + %1 placerar %2%3 på plats %4 i sin lek. + + + + %1 moves %2%3 to sideboard. + %1 flyttar %2%3 till sidbrädan. + + + + %1 plays %2%3. + %1 spelar %2%3. + + + + %1 takes a mulligan to %n. + female + + %1 tar en mulligan till %n. + %1 tar en mulligan till %n. + + + + + %1 takes a mulligan to %n. + male + + %1 tar en mulligan till %n. + %1 tar en mulligan till %n. + + + + + %1 draws her initial hand. + %1 drar sin första hand. + + + + %1 draws his initial hand. + %1 drar sin första hand. + + + + %1 flips %2 face-down. + female + %1 vänder %2 nedåt. + + + + %1 flips %2 face-down. + male + %1 vänder %2 nedåt. + + + + %1 flips %2 face-up. + female + %1 vänder %2 uppåt. + + + + %1 flips %2 face-up. + male + %1 vänder %2 uppåt. + + + + %1 destroys %2. + female + %1 förstör %2. + + + + %1 destroys %2. + male + %1 förstör %2. + + + + %1 attaches %2 to %3's %4. + p1 female, p2 female + %1 fäster %2 på %3's %4. + + + + %1 attaches %2 to %3's %4. + p1 female, p2 male + %1 fäster %2 på %3's %4. + + + + %1 attaches %2 to %3's %4. + p1 male, p2 female + %1 fäster %2 på %3's %4. + + + + %1 attaches %2 to %3's %4. + p1 male, p2 male + %1 fäster %2 på %3's %4. + + + + %1 unattaches %2. + female + %1 separerar %2. + + + + %1 unattaches %2. + male + %1 separerar %2. + + + + %1 creates token: %2%3. + female + %1 skapar jetong: %2%3. + + + + %1 creates token: %2%3. + male + %1 skapar jetong: %2%3. + + + + %1 points from her %2 to herself. + female + %1 pekar från sin %2 till sig själv. + + + + %1 points from his %2 to himself. + male + %1 pekar från sin %2 till sig själv. + + + + %1 points from her %2 to %3. + p1 female, p2 female + %1 pekar från sin %2 till %3. + + + + %1 points from her %2 to %3. + p1 female, p2 male + %1 pekar från sin %2 till %3. + + + + %1 points from his %2 to %3. + p1 male, p2 female + %1 pekar från sin %2 till %3. + + + + %1 points from his %2 to %3. + p1 male, p2 male + %1 pekar från sin %2 till %3. + + + + %1 points from %2's %3 to herself. + card owner female, target female + %1 pekar från %2's %3 till sig själv. + + + + %1 points from %2's %3 to herself. + card owner male, target female + %1 pekar från %2's %3 till sig själv. + + + + %1 points from %2's %3 to himself. + card owner female, target male + %1 pekar från %2's %3 till sig själv. + + + + %1 points from %2's %3 to himself. + card owner male, target male + %1 pekar från %2's %3 till sig själv. + + + + %1 points from %2's %3 to %4. + p1 female, p2 female, p3 female + %1 pekar från %2's %3 till %4. + + + + %1 points from %2's %3 to %4. + p1 female, p2 female, p3 male + %1 pekar från %2's %3 till %4. + + + + %1 points from %2's %3 to %4. + p1 female, p2 male, p3 female + %1 pekar från %2's %3 till %4. + + + + %1 points from %2's %3 to %4. + p1 female, p2 male, p3 male + %1 pekar från %2's %3 till %4. + + + + %1 points from %2's %3 to %4. + p1 male, p2 female, p3 female + %1 pekar från %2's %3 till %4. + + + + %1 points from %2's %3 to %4. + p1 male, p2 female, p3 male + %1 pekar från %2's %3 till %4. + + + + %1 points from %2's %3 to %4. + p1 male, p2 male, p3 female + %1 pekar från %2's %3 till %4. + + + + %1 points from %2's %3 to %4. + p1 male, p2 male, p3 male + %1 pekar från %2's %3 till %4. + + + + %1 points from her %2 to her %3. + female + %1 pekar från sin %2 till sin %3. + + + + %1 points from his %2 to his %3. + male + %1 pekar från sin %2 till sin %3. + + + + %1 points from her %2 to %3's %4. + p1 female, p2 female + %1 pekar från sin %2 till %3's %4. + + + + %1 points from her %2 to %3's %4. + p1 female, p2 male + %1 pekar från sin %2 till %3's %4. + + + + %1 points from his %2 to %3's %4. + p1 male, p2 female + %1 pekar från sin %2 till %3's %4. + + + + %1 points from his %2 to %3's %4. + p1 male, p2 male + %1 pekar från sin %2 till %3's %4. + + + + %1 points from %2's %3 to her own %4. + card owner female, target female + %1 pekar från %2's %3 till sin %4. + + + + %1 points from %2's %3 to her own %4. + card owner male, target female + %1 pekar från %2's %3 till sin %4. + + + + %1 points from %2's %3 to his own %4. + card owner female, target male + %1 pekar från %2's %3 till sin %4. + + + + %1 points from %2's %3 to his own %4. + card owner male, target male + %1 pekar från %2's %3 till sin %4. + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 female, p3 female + %1 pekar från %2's %3 till %4's %5. + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 female, p3 male + %1 pekar från %2's %3 till %4's %5. + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 male, p3 female + %1 pekar från %2's %3 till %4's %5. + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 male, p3 male + %1 pekar från %2's %3 till %4's %5. + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 female, p3 female + %1 pekar från %2's %3 till %4's %5. + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 female, p3 male + %1 pekar från %2's %3 till %4's %5. + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 male, p3 female + %1 pekar från %2's %3 till %4's %5. + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 male, p3 male + %1 pekar från %2's %3 till %4's %5. + + + + %1 places %n %2 counter(s) on %3 (now %4). + female + + %1 placerar %n %2 polett på %3 (%4 totalt). + %1 placerar %n %2 poletter på %3 (%4 totalt). + + + + + %1 places %n %2 counter(s) on %3 (now %4). + male + + %1 placerar %n %2 polett på %3 (%4 totalt). + %1 placerar %n %2 poletter på %3 (%4 totalt). + + + + + %1 removes %n %2 counter(s) from %3 (now %4). + female + + %1 tar bort %n %2 polett från %3 (%4 totalt). + %1 tar bort %n %2 poletter från %3 (%4 totalt). + + + + + %1 removes %n %2 counter(s) from %3 (now %4). + male + + %1 tar bort %n %2 polett från %3 (%4 totalt). + %1 tar bort %n %2 poletter från %3 (%4 totalt). + + + + + red + + röd + röda + + + + + yellow + + gul + gula + + + + + green + + grön + gröna + + + + + %1 taps her permanents. + female + %1 tappar sina permanents. + + + + %1 untaps her permanents. + female + %1 tappar upp sina permanents. + + + + %1 taps his permanents. + male + %1 tappar sina permanents. + + + + %1 untaps his permanents. + male + %1 tappar upp sina permanents. + + + + %1 taps %2. + female + %1 tappar %2. + + + + %1 untaps %2. + female + %1 tappar upp %2. + + + + %1 taps %2. + male + %1 tappar %2. + + + + %1 untaps %2. + male + %1 tappar upp %2. + + + + %1 sets counter %2 to %3 (%4%5). + female + %1 ändrar antalet %2 poletter till %3 (%4%5). + + + + %1 sets counter %2 to %3 (%4%5). + male + %1 ändrar antalet %2 poletter till %3 (%4%5). + + + + %1 sets %2 to not untap normally. + female + %1 har inaktiverat normal upptappning för %2. + + + + %1 sets %2 to not untap normally. + male + %1 har inaktiverat normal upptappning för %2. + + + + %1 sets %2 to untap normally. + female + %1 har aktiverat normal upptappning för %2. + + + + %1 sets %2 to untap normally. + male + %1 har aktiverat normal upptappning för %2. + + + + %1 sets PT of %2 to %3. + female + %1 ställer %2's PT till %3. + + + + %1 sets PT of %2 to %3. + male + %1 ställer %2's PT till %3. + + + + %1 sets annotation of %2 to %3. + female + %1 ställer %2's annotering till %3. + + + + %1 sets annotation of %2 to %3. + male + %1 ställer %2's annotering till %3. + + + %1 is looking at the top %2 cards %3. + female + %1 tittar på de %2 översta korten %3. + + + %1 is looking at the top %2 cards %3. + male + %1 tittar på de %2 översta korten %3. + + + + %1 is looking at %2. + female + %1 tittar på %2. + + + + %1 is looking at %2. + male + %1 tittar på %2. + + + + %1 is looking at the top %n card(s) %2. + female + + %1 tittar på det översta kortet %2. + %1 tittar på de översta %n korten %2. + + + + + %1 is looking at the top %n card(s) %2. + male + + %1 tittar på det översta kortet %2. + %1 tittar på de översta %n korten %2. + + + + + %1 stops looking at %2. + female + %1 slutar titta på %2. + + + + %1 stops looking at %2. + male + %1 slutar titta på %2. + + + + %1 reveals %2 to %3. + p1 female, p2 female + %1 visar %2 för %3. + + + + %1 reveals %2 to %3. + p1 female, p2 male + %1 visar %2 för %3. + + + + %1 reveals %2 to %3. + p1 male, p2 female + %1 visar %2 för %3. + + + + %1 reveals %2 to %3. + p1 male, p2 male + %1 visar %2 för %3. + + + + %1 reveals %2. + female + %1 visar %2. + + + + %1 reveals %2. + male + %1 visar %2. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + %1 visar slumpmässigt %2%3 till %4. + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + %1 visar slumpmässigt %2%3 till %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + %1 visar slumpmässigt %2%3 till %4. + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + %1 visar slumpmässigt %2%3 till %4. + + + + %1 randomly reveals %2%3. + female + %1 visar slumpmässigt %2%3. + + + + %1 randomly reveals %2%3. + male + %1 visar slumpmässigt %2%3. + + + + %1 peeks at face down card #%2. + female + %1 kikar på nedåtvända kort #%2. + + + + %1 peeks at face down card #%2. + male + %1 kikar på nedåtvända kort #%2. + + + + %1 peeks at face down card #%2: %3. + female + %1 kikar på nedåtvända kort #%2: %3. + + + + %1 peeks at face down card #%2: %3. + male + %1 kikar på nedåtvända kort #%2: %3. + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + %1 visar %2%3 till %4. + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + %1 visar %2%3 till %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + %1 visar %2%3 till %4. + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + %1 visar %2%3 till %4. + + + + %1 reveals %2%3. + female + %1 visar %2%3. + + + + %1 reveals %2%3. + male + %1 visar %2%3. + + + + %1 is now keeping the top card %2 revealed. + %1 håller nu det översta kortet %2 avslöjat. + + + + %1 is not revealing the top card %2 any longer. + %1 håller inte längre det översta kortet %2 avslöjat. + + + + It is now %1's turn. + female + Det är nu %1's tur. + + + + It is now %1's turn. + male + Det är nu %1's tur. + + + + untap step + upptappningssteget + + + + upkeep step + underhållssteget + + + + draw step + dragsteget + + + + first main phase + första huvudfasen + + + + beginning of combat step + början av stridssteget + + + + declare attackers step + attacksteget + + + + declare blockers step + blockeringssteget + + + + combat damage step + skadesteget + + + + end of combat step + slutet av stridssteget + + + + second main phase + andra huvudfasen + + + + ending phase + slutfasen + + + + It is now the %1. + Det är nu %1. + + + + MessagesSettingsPage + + + Add message + Lägg till meddelande + + + + Message: + Meddelande: + + + + &Add + &Lägg till + + + + &Remove + &Ta bort + + + + PhasesToolbar + + + Untap step + Uptappningssteget + + + + Upkeep step + Underhållssteget + + + + Draw step + Dragsteget + + + + First main phase + Första huvudfasen + + + + Beginning of combat step + Början av stridssteget + + + + Declare attackers step + Attacksteget + + + + Declare blockers step + Blockeringssteget + + + + Combat damage step + Skadesteget + + + + End of combat step + Slutet av stridssteget + + + + Second main phase + Andra huvudfasen + + + + End of turn step + Slutsteget + + + + Player + + + &View graveyard + &Titta på kyrkogård + + + + &View exile + &Titta på exil + + + + Player "%1" + Spelare "%1" + + + + &Graveyard + &Kyrkogård + + + + &Exile + &Exil + + + + + + Move to &top of library + Placera &överst i leken + + + + + + Move to &bottom of library + Placera &underst i leken + + + + + Move to &graveyard + Flytta till &kyrkogården + + + + + Move to &exile + Placera i &exil + + + + + Move to &hand + Placera i &hand + + + + &View library + &Titta på leken + + + + View &top cards of library... + Titta på de &översta korten i leken... + + + + Reveal &library to + Visa &leken för + + + + Reveal t&op card to + &Visa översta kortet för + + + + &Always reveal top card + &Avslöja alltid det översta kortet + + + + O&pen deck in deck editor + &Öppna lek i lekredigeraren + + + + &View sideboard + &Titta på sidbrädan + + + + &Draw card + &Dra kort + + + + D&raw cards... + D&ra kort... + + + + &Undo last draw + &Ångra senaste drag + + + + Take &mulligan + &Mulligan + + + + &Shuffle + &Blanda + + + + Move top cards to &graveyard... + Flytta översta korten till &kyrkogården... + + + + Move top cards to &exile... + Flytta översta korten till &exil... + + + + Put top card on &bottom + Placera översta kortet &underst + + + + &Hand + &Hand + + + + &Reveal to + &Visa för + + + + Reveal r&andom card to + Visa slumpm&ässigt kort för + + + + &Sideboard + S&idbräda + + + + &Library + &Lek + + + + &Counters + &Poletter + + + + &Untap all permanents + Tappa upp alla perma&nenta kort + + + + R&oll die... + Rulla t&ärning... + + + + &Create token... + &Skapa jetong... + + + + C&reate another token + S&kapa en till jetong + + + + Cr&eate predefined token + Skapa fördefinierad &jetong + + + + S&ay + S&äg + + + + C&ard + K&ort + + + + &All players + A&lla spelare + + + + &Play + &Spela + + + + &Hide + &Göm + + + + &Tap + &Tappa + + + + &Untap + Tappa &upp + + + + Toggle &normal untapping + Växla &normal upptappning + + + + &Flip + &Vänd + + + + &Peek at card face + Kika på kort&framsida + + + + &Clone + &Klona + + + + Ctrl+J + + + + + Attac&h to card... + &Fäst på kort... + + + + Ctrl+A + + + + + Unattac&h + Se&parera + + + + &Draw arrow... + &Rita pil... + + + + &Increase power + &Öka power + + + + Ctrl++ + + + + + &Decrease power + &Minska power + + + + Ctrl+- + + + + + I&ncrease toughness + Öka toug&hness + + + + Alt++ + + + + + D&ecrease toughness + Minska t&oughness + + + + Alt+- + + + + + In&crease power and toughness + Öka power o&ch toughness + + + + Ctrl+Alt++ + + + + + Dec&rease power and toughness + M&inska power och toughness + + + + Ctrl+Alt+- + + + + + Set &power and toughness... + An&ge power och toughness... + + + + Ctrl+P + + + + + &Set annotation... + Ang&e annotering... + + + + red + röd + + + + yellow + gul + + + + green + grön + + + + &Add counter (%1) + P&lacera polett (%1) + + + + &Remove counter (%1) + Ta &bort polett (%1) + + + + &Set counters (%1)... + Pl&acera poletter (%1)... + + + + &top of library + &överst i leken + + + + &bottom of library + &underst i leken + + + + &graveyard + kyrkog&ård + + + + Ctrl+Del + + + + + &exile + e&xil + + + + Ctrl+F3 + + + + + F3 + + + + + Ctrl+W + + + + + F4 + + + + + Ctrl+D + + + + + Ctrl+E + + + + + Ctrl+Shift+D + + + + + Ctrl+M + + + + + Ctrl+S + + + + + Ctrl+U + + + + + Ctrl+I + + + + + Ctrl+T + + + + + Ctrl+G + + + + + View top cards of library + Titta på de översta korten i leken + + + + Number of cards: + Antal kort: + + + + Draw cards + Dra kort + + + + + + + Number: + Antal: + + + + Move top cards to grave + Flytta översta korten till kyrkogården + + + + Move top cards to exile + Flytta översta korten till exil + + + + Roll die + Rulla tärning + + + + Number of sides: + Antal sidor: + + + + Set power/toughness + Ange power/toughness + + + + Please enter the new PT: + Vänligen ange ny PT: + + + + Set annotation + Ange annotering + + + + Please enter the new annotation: + Vänligen ange den nya annoteringen: + + + + Set counters + Placera poletter + + + + PlayerListWidget + + User &details + &Användardetaljer + + + Direct &chat + Direkt &chatt + + + Add to &buddy list + Lägg till som &vän + + + Remove from &buddy list + Ta bort som &vän + + + Add to &ignore list + &Ignorera + + + Remove from &ignore list + Sluta &ignorera + + + Kick from &game + &Sparka från spelet + + + + QObject + + Maindeck + Huvudlek + + + Sideboard + Sidbräda + + + Cockatrice decks (*.cod) + Cockatricelekar (*.cod) + + + Plain text decks (*.dec *.mwDeck) + Klartextlekar (*.dec *.mwDeck) + + + + Common deck formats (*.cod *.dec *.mwDeck) + + + + + All files (*.*) + Alla filer (*.*) + + + + Cockatrice replays (*.cor) + Cockatricerepriser (*.cor) + + + + RemoteDeckList_TreeModel + + + Name + Namn + + + + ID + ID + + + + Upload time + Uppladdningstidpunkt + + + + RemoteReplayList_TreeModel + + + ID + ID + + + + Name + Namn + + + + Players + Spelare + + + + Keep + Behåll + + + + Time started + Tid startad + + + + Duration (sec) + Varaktighet (sek) + + + + RoomSelector + + + Rooms + Rum + + + + Joi&n + &Anslut + + + + Room + Rum + + + + Description + Beskrivning + + + + Players + Spelare + + + + Games + Spel + + + + SetsModel + + + Short name + Kort namn + + + + Long name + Långt namna + + + + ShutdownDialog + + + &Reason for shutdown: + Anledning till &nedstängning: + + + + &Time until shutdown (minutes): + &Tid till nedstängning (minuter): + + + &OK + &OK + + + &Cancel + &Avbryt + + + + Shut down server + Stäng ned server + + + + TabAdmin + + + Update server &message + &Uppdatera servermeddelande + + + + &Shut down server + &Stäng ned server + + + + Server administration functions + Serveradministrationsfunktioner + + + + &Unlock functions + &Lås upp funktioner + + + + &Lock functions + Lås &funktioner + + + + Unlock administration functions + Lås upp administrationsfunktioner + + + + Do you really want to unlock the administration functions? + Vill du verkligen låsa upp administrationsfunktionerna? + + + Administration + Administration + + + + TabDeckEditor + + &Search... + &Sök... + + + + Show card text only + + + + + &Clear search + &Rensa sökning + + + + &Search for: + S&ök efter: + + + + Deck &name: + &Leknamn: + + + + &Comments: + &Kommentarer: + + + + Hash: + Hash: + + + + &Update prices + &Uppdatera priser + + + + Ctrl+U + + + + + &New deck + &Ny lek + + + + &Load deck... + &Ladda lek... + + + + &Save deck + S&para lek + + + + Save deck &as... + Spa&ra lek som... + + + + Load deck from cl&ipboard... + Ladda lek &från urklipp... + + + + Save deck to clip&board + Spara lek som u&tklipp + + + + &Print deck... + Skri&v ut lek... + + + + &Analyze deck on deckstats.net + &Anslysera lek på deckstats.net + + + + &Close + S&täng + + + + Ctrl+Q + + + + + Add card to &maindeck + Lägg till kort till &huvudlek + + + + Add card to &sideboard + Lägg till kort i sidbr&äda + + + + &Remove row + Ta bort ra&d + + + + Del + + + + + &Increment number + &Öka antal + + + + + + + + + + &Decrement number + &Minska antal + + + + - + + + + + &Deck editor + Lek&redigeraren + + + + C&ard database + &Kortdatabas + + + + &Edit sets... + Redigera utg&åvor... + + + + Edit &tokens... + Redigera &jetonger... + + + + Deck: %1 + Lek: %1 + + + + Are you sure? + Är du säker? + + + + The decklist has been modified. +Do you want to save the changes? + Denna leklista har modifierats. Vill du spara ändringarna? + + + + Load deck + Ladda lek + + + + + + Error + Fel + + + + The deck could not be saved. + Leken kunde inte sparas. + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + Leken kunde inte sparas. +Vänligen se till att katalogen är skrivbar och försök igen. + + + + Save deck + Spara lek + + + + TabDeckStorage + + + Local file system + Lokalt filsystem + + + + Server deck storage + Serverns leklagring + + + + + Open in deck editor + Öppna i lekredigeraren + + + + Upload deck + Ladda upp lek + + + + Download deck + Ladda ner lek + + + + + New folder + Ny mapp + + + + + Delete + Radera + + + + Enter deck name + Ange leknamn + + + + This decklist does not have a name. +Please enter a name: + Denna leklista har inget namn. Vänligen ange ett namn: + + + + Unnamed deck + Namnlös lek + + + + Delete local file + Radera lokal fil + + + + + + Are you sure you want to delete "%1"? + Är du säker på att du vill radera "%1"? + + + + Name of new folder: + Namn på den nya mappen: + + + + Delete remote folder + + + + + Delete remote deck + Radera avlägsen lek + + + Deck storage + Leklagring + + + + TabGame + + + F5 + + + + + F6 + + + + + F7 + + + + + F8 + + + + + F9 + + + + + F10 + + + + + &Phases + &Faser + + + + &Game + &Spel + + + + Next &phase + Nästa &fas + + + + Ctrl+Space + + + + + Tab + + + + + Next &turn + Nästa &tur + + + + Ctrl+Return + + + + + Ctrl+Enter + + + + + &Remove all local arrows + Ta &bort alla lokala pilar + + + + Ctrl+R + + + + + Game &information + Spel&information + + + + &Concede + &Ge upp + + + + F2 + + + + + &Leave game + &Lämna spel + + + + + Ctrl+Q + + + + + C&lose replay + S&täng repris + + + + &Say: + S&äg: + + + + Concede + Ge upp + + + + Are you sure you want to concede this game? + Är du säker på att du vill ge upp detta spel? + + + + Leave game + Lämna spel + + + + Are you sure you want to leave this game? + Är du säker på att du vill lämna detta spel? + + + + Replay %1: %2 + Repris %1: %2 + + + Kicked + Utsparkad + + + You have been kicked out of the game. + Du har blivit utsparkad från spelet. + + + + Game %1: %2 + Spel %1: %2 + + + + TabMessage + + + Personal &talk + Personligt &samtal + + + + &Leave + &Lämna + + + + This user is ignoring you. + Denna användaren ignorerar dig. + + + + %1 has left the server. + %1 har lämnat servern. + + + + %1 has joined the server. + %1 har anslutit sig till servern. + + + + Talking to %1 + Pratar med %1 + + + + TabReplays + + + Local file system + Lokalt filsystem + + + + Server replay storage + Serverns reprislagring + + + + + Watch replay + Titta på repris + + + + + Delete + Radera + + + + Download replay + Ladda ner repris + + + + Toggle expiration lock + Växla utgångslås + + + + Delete local file + Radera lokal fil + + + + Are you sure you want to delete "%1"? + Är du säker på att du vill radera "%1"? + + + + Delete remote replay + Radera avlägsen repris + + + + Are you sure you want to delete the replay of game %1? + Är du säker på att du vill radera reprisen av spel %1? + + + Game replays + Spelrepriser + + + + TabRoom + + + &Say: + &Säg: + + + + Chat + Chatt + + + + &Room + &Rum + + + + &Leave room + &Lämna rum + + + + &Ignore unregistered users in chat + &Ignorera oregistrerade användare i chatten + + + + You are flooding the chat. Please wait a couple of seconds. + Du översvämmar chatten. Var vänlig vänta ett par sekunder. + + + + TabServer + + Server + Server + + + + TabUserLists + + User lists + Användarlistor + + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + Användar&detaljer + + + + Direct &chat + Direkt&chatt + + + + Show this user's &games + Visa denna användarens &spel + + + + Add to &buddy list + Lägg till som &vän + + + + Remove from &buddy list + Ta bort som &vän + + + + Add to &ignore list + &Ignorera + + + + Remove from &ignore list + Sluta &ignorera + + + + Kick from &game + &Sparka från spelet + + + + Ban from &server + &Bannlys från servern + + + + %1's games + %1's spel + + + + UserInfoBox + + + User information + Användarinformation + + + + Real name: + Riktiga namn: + + + + Gender: + Kön: + + + + Location: + Plats: + + + + User level: + Användarnivå: + + + + Administrator + Administratör + + + + Moderator + Moderator + + + + Registered user + Registrerad användare + + + + Unregistered user + Oregistrerad användare + + + + UserInterfaceSettingsPage + + + General interface settings + Allmänna gränssnittsinställningar + + + + Enable notifications in taskbar + + + + + &Double-click cards to play them (instead of single-click) + &Dubbelklicka på kort för att spela dem (istället för enkelklick) + + + + &Play all nonlands onto the stack (not the battlefield) by default + + + + + Animation settings + Animationsinställningar + + + + &Tap/untap animation + &Tappnings/Upptappningsanimation + + + + Enable &sounds + Aktivera &ljud + + + + Path to sounds directory: + Sökväg till ljudkatalog: + + + + Choose path + Välj sökväg + + + + UserList + + + Users online: %1 + Användare online: %1 + + + + Users in this room: %1 + Användare i detta rum: %1 + + + + Buddies online: %1 / %2 + Vänner online: %1 / %2 + + + + Ignored users online: %1 / %2 + Ignorerade användare online: %1 / %2 + + + %1's games + %1's spel + + + User &details + Användar&detaljer + + + Direct &chat + Direct&chatt + + + Show this user's &games + Visa denna användarens &spel + + + Add to &buddy list + Lägg till som &vän + + + Remove from &buddy list + Ta bort som &vän + + + Add to &ignore list + &Ignorera + + + Remove from &ignore list + Sluta &ignorera + + + Ban from &server + &Bannlys från servern + + + + WndDeckEditor + + &Search... + &Sök... + + + &Clear search + &Rensa sökning + + + &Search for: + S&ök efter: + + + Deck &name: + &Leknamn: + + + &Comments: + &Kommentarer: + + + Hash: + Hash: + + + &Update prices + &Uppdatera priser + + + Deck editor [*] + Lekredigerare [*] + + + &New deck + &Ny lek + + + &Load deck... + &Ladda lek... + + + &Save deck + S&para lek + + + Save deck &as... + Spa&ra lek som... + + + Load deck from cl&ipboard... + Ladda lek &från urklipp... + + + Save deck to clip&board + Spara lek som u&tklipp + + + &Print deck... + Skri&v ut lek... + + + &Close + Stän&g + + + &Edit sets... + Redigera utg&åvor... + + + &Deck + L&ek + + + &Card database + K&ortdatabas + + + Add card to &maindeck + Lägg till kort till &huvudlek + + + Return + Retur + + + Add card to &sideboard + Lägg till kort i sidbr&äda + + + &Remove row + Ta bort ra&d + + + &Increment number + &Öka antal + + + &Decrement number + &Minska antal + + + Are you sure? + Är du säker? + + + The decklist has been modified. +Do you want to save the changes? + Denna leklista har modifierats. Vill du spara ändringarna? + + + Load deck + Ladda lek + + + Error + Fel + + + The deck could not be saved. +Please check that the directory is writable and try again. + Leken kunde inte sparas. +Vänligen se till att katalogen är skrivbar och försök igen. + + + Save deck + Spara lek + + + + WndSets + + + Edit sets + Redigera utgåvor + + + + ZoneViewWidget + + + sort by name + sortera efter namn + + + + sort by type + sortera efter typ + + + + shuffle when closing + blanda när du stänger + + + diff --git a/cockatrice/translations/cockatrice_zh_CN.ts b/cockatrice/translations/cockatrice_zh_CN.ts new file mode 100644 index 000000000..04d26fcae --- /dev/null +++ b/cockatrice/translations/cockatrice_zh_CN.ts @@ -0,0 +1,4494 @@ + + + + + AbstractCounter + + + &Set counter... + + + + + Ctrl+L + + + + + F11 + + + + + F12 + + + + + Set counter + + + + + New value for counter '%1': + + + + + AppearanceSettingsPage + + + Zone background pictures + + + + + Hand background: + + + + + Stack background: + + + + + Table background: + + + + + Player info background: + + + + + Card back: + + + + + Card rendering + + + + + Display card names on cards having a picture + + + + + Hand layout + + + + + Display hand horizontally (wastes space) + + + + + Table grid layout + + + + + Invert vertical coordinate + + + + + Minimum player count for multi-column layout: + + + + + Zone view layout + + + + + Sort by name + + + + + Sort by type + + + + + + + + + Choose path + + + + + BanDialog + + + ban &user name + + + + + ban &IP address + + + + + Ban type + + + + + &permanent ban + + + + + &temporary ban + + + + + &Days: + + + + + &Hours: + + + + + &Minutes: + + + + + Duration of the ban + + + + + Please enter the reason for the ban. +This is only saved for moderators and cannot be seen by the banned person. + + + + + Please enter the reason for the ban that will be visible to the banned person. + + + + + &OK + + + + + &Cancel + + + + + Ban user from server + + + + + Error + + + + + You have to select a name-based or IP-based ban, or both. + + + + + CardDatabaseModel + + + Name + + + + + Sets + + + + + Mana cost + + + + + Card type + + + + + P/T + + + + + CardInfoText + + + Name: + + + + + Mana cost: + + + + + Card type: + + + + + P / T: + + + + + Loyalty: + + + + + CardInfoWidget + + + Show card only + + + + + Show text only + + + + + Show full info + + + + + Name: + + + + + Mana cost: + + + + + Card type: + + + + + P / T: + + + + + Loyalty: + + + + + CardItem + + + &Move to + + + + + &Power / toughness + + + + + CardZone + + + her hand + nominative, female owner + + + + + %1's hand + nominative, female owner + + + + + his hand + nominative, male owner + + + + + %1's hand + nominative, male owner + + + + + her library + look at zone, female owner + + + + + %1's library + look at zone, female owner + + + + + his library + look at zone, male owner + + + + + %1's library + look at zone, male owner + + + + + of her library + top cards of zone, female owner + + + + + of %1's library + top cards of zone, female owner + + + + + of his library + top cards of zone, male owner + + + + + of %1's library + top cards of zone, male owner + + + + + her library + reveal zone, female owner + + + + + %1's library + reveal zone, female owner + + + + + his library + reveal zone, male owner + + + + + %1's library + reveal zone, male owner + + + + + her library + shuffle, female owner + + + + + %1's library + shuffle, female owner + + + + + his library + shuffle, male owner + + + + + %1's library + shuffle, male owner + + + + + her library + nominative, female owner + + + + + %1's library + nominative, female owner + + + + + his library + nominative, male owner + + + + + %1's library + nominative, male owner + + + + + her graveyard + nominative, female owner + + + + + %1's graveyard + nominative, female owner + + + + + his graveyard + nominative, male owner + + + + + %1's graveyard + nominative, male owner + + + + + her exile + nominative, female owner + + + + + %1's exile + nominative, female owner + + + + + his exile + nominative, male owner + + + + + %1's exile + nominative, male owner + + + + + her sideboard + look at zone, female owner + + + + + %1's sideboard + look at zone, female owner + + + + + his sideboard + look at zone, male owner + + + + + %1's sideboard + look at zone, male owner + + + + + her sideboard + nominative, female owner + + + + + %1's sideboard + nominative, female owner + + + + + his sideboard + nominative, male owner + + + + + %1's sideboard + nominative, male owner + + + + + DBPriceUpdater + + + + + Error + + + + + + A problem has occured while fetching card prices. + + + + + A problem has occured while fetching card prices: + + + + + DeckEditorSettingsPage + + + Enable &price tag feature + + + + + using data from blacklotusproject.com + + + + + using data from deckbrew.com + + + + + General + + + + + DeckListModel + + + Number + + + + + Card + + + + + Price + + + + + DeckStatsInterface + + + + Error + + + + + The reply from the server could not be parsed. + + + + + DeckViewContainer + + + Load &local deck + + + + + Load d&eck from server + + + + + Ready to s&tart + + + + + S&ideboard unlocked + + + + + S&ideboard locked + + + + + Load deck + + + + + Error + + + + + The selected file could not be loaded. + + + + + DlgCardSearch + + + Card name: + + + + + Card text: + + + + + Card type (OR): + + + + + Color (OR): + + + + + O&K + + + + + &Cancel + + + + + Card search + + + + + DlgConnect + + + &Host: + + + + + &Port: + + + + + Player &name: + + + + + P&assword: + + + + + &Save password + + + + + Connect to server + + + + + DlgCreateGame + + + &Description: + + + + + P&layers: + + + + + Game type + + + + + &Password: + + + + + Only &buddies can join + + + + + Only &registered users can join + + + + + Joining restrictions + + + + + &Spectators allowed + + + + + Spectators &need a password to join + + + + + Spectators can &chat + + + + + Spectators see &everything + + + + + Spectators + + + + + Create game + + + + + Game information + + + + + Error + + + + + Server error. + + + + + DlgCreateToken + + + &Name: + + + + + Token + + + + + C&olor: + + + + + white + + + + + blue + + + + + black + + + + + red + + + + + green + + + + + multicolor + + + + + colorless + + + + + &P/T: + + + + + &Annotation: + + + + + &Destroy token when it leaves the table + + + + + Token data + + + + + Show &all tokens + + + + + Show tokens from this &deck + + + + + Choose token from list + + + + + Create token + + + + + DlgEditTokens + + + &Name: + + + + + C&olor: + + + + + white + + + + + blue + + + + + black + + + + + red + + + + + green + + + + + multicolor + + + + + colorless + + + + + &P/T: + + + + + &Annotation: + + + + + Token data + + + + + + Add token + + + + + Remove token + + + + + Edit tokens + + + + + Please enter the name of the token: + + + + + Error + + + + + The chosen name conflicts with an existing card or token. + + + + + DlgFilterGames + + + Show &unavailable games + + + + + Show &password protected games + + + + + Game &description: + + + + + &Creator name: + + + + + &Game types + + + + + at &least: + + + + + at &most: + + + + + Maximum player count + + + + + Filter games + + + + + DlgLoadDeckFromClipboard + + + &Refresh + + + + + Load deck from clipboard + + + + + Error + + + + + Invalid deck list. + + + + + DlgLoadRemoteDeck + + + Load deck + + + + + DlgSettings + + + + + Error + + + + + Unknown Error loading card database + + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + + + + + Your card database did not finish loading + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + + + + + File Error loading your card database. + +Would you like to change your database location setting? + + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + + + + + Unknown card database load status + +Please file a ticket at http://github.com/Daenyth/Cockatrice/issues + +Would you like to change your database location setting? + + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? + + + + + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? + + + + + Settings + + + + + General + + + + + Appearance + + + + + User interface + + + + + Deck editor + + + + + Messages + + + + + GameSelector + + + + + + + + + + + Error + + + + + Please join the appropriate room first. + + + + + Wrong password. + + + + + Spectators are not allowed in this game. + + + + + The game is already full. + + + + + The game does not exist any more. + + + + + This game is only open to registered users. + + + + + This game is only open to its creator's buddies. + + + + + You are being ignored by the creator of this game. + + + + + Join game + + + + + Password: + + + + + Please join the respective room first. + + + + + Games + + + + + &Filter games + + + + + C&lear filter + + + + + C&reate + + + + + &Join + + + + + J&oin as spectator + + + + + GameView + + + Esc + + + + + GamesModel + + + yes + + + + + yes, free for spectators + + + + + no + + + + + buddies only + + + + + reg. users only + + + + + not allowed + + + + + Description + + + + + Room + + + + + Creator + + + + + Game type + + + + + Password + + + + + Restrictions + + + + + Players + + + + + Spectators + + + + + GeneralSettingsPage + + + + English + don't translate "English", but "Chinese": 中文 ? + + + + + Reset/Clear Downloaded Pictures + + + + + + + + + Choose path + + + + + Success + + + + + Downloaded card pictures have been reset. + + + + + Error + + + + + One or more downloaded card pictures could not be cleared. + + + + + Personal settings + + + + + Language: + + + + + Download card pictures on the fly + + + + + Download high-quality card pictures + + + + + Paths + + + + + Decks directory: + + + + + Replays directory: + + + + + Pictures directory: + + + + + Card database: + + + + + Token database: + + + + + MainWindow + + + There are too many concurrent connections from your address. + + + + + Banned by moderator + + + + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + Scheduled server shutdown. + + + + + + Invalid username. + + + + + Connection closed + + + + + The server has terminated your connection. +Reason: %1 + + + + + Scheduled server shutdown + + + + + The server is going to be restarted in %n minute(s). +All running games will be lost. +Reason for shutdown: %1 + + + + + + + Number of players + + + + + Please enter the number of players. + + + + + + Player %1 + + + + + Load replay + + + + + About Cockatrice + + + + + Version %1 + + + + + Authors: + + + + + Translators: + + + + + Spanish: + + + + + Portugese (Portugal): + + + + + Portugese (Brazil): + + + + + French: + + + + + Japanese: + + + + + Russian: + + + + + Italian: + + + + + Swedish: + + + + + + + + + + + + + + Error + + + + + Server timeout + + + + + Incorrect username or password. Please check your authentication information and try again. + + + + + There is already an active session using this user name. +Please close that session first and re-login. + + + + + You are banned until %1. + + + + + You are banned indefinitely. + + + + + This server requires user registration. + + + + + Unknown login error: %1 + + + + + Socket error: %1 + + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. +Local version is %1, remote version is %2. + + + + + Your Cockatrice client is obsolete. Please update your Cockatrice version. +Local version is %1, remote version is %2. + + + + + Connecting to %1... + + + + + Disconnected + + + + + Connected, logging in at %1 + + + + + Logged in at %1 + + + + + &Connect... + + + + + &Disconnect + + + + + Start &local game... + + + + + &Watch replay... + + + + + &Deck editor + + + + + &Full screen + + + + + Ctrl+F + + + + + &Settings... + + + + + &Exit + + + + + &Cockatrice + + + + + &About Cockatrice + + + + + &Help + + + + + Are you sure? + + + + + There are still open games. Are you sure you want to quit? + + + + + MessageLogWidget + + + You have joined game #%1. + female + + + + + You have joined game #%1. + male + + + + + You are watching a replay of game #%1. + female + + + + + You are watching a replay of game #%1. + male + + + + + %1 has joined the game. + female + + + + + %1 has joined the game. + male + + + + + %1 has left the game. + female + + + + + %1 has left the game. + male + + + + + The game has been closed. + + + + + You have been kicked out of the game. + + + + + %1 is now watching the game. + + + + + %1 is not watching the game any more. + + + + + %1 has loaded a deck (%2). + + + + + %1 has loaded a deck with %2 sideboard cards (%3). + + + + + %1 is ready to start the game. + female + + + + + %1 is ready to start the game. + male + + + + + %1 is not ready to start the game any more. + female + + + + + %1 is not ready to start the game any more. + male + + + + + %1 has locked her sideboard. + female + + + + + %1 has locked his sideboard. + male + + + + + %1 has unlocked her sideboard. + female + + + + + %1 has unlocked his sideboard. + male + + + + + %1 has conceded the game. + female + + + + + %1 has conceded the game. + male + + + + + The game has started. + + + + + %1 has restored connection to the game. + female + + + + + %1 has restored connection to the game. + male + + + + + %1 has lost connection to the game. + female + + + + + %1 has lost connection to the game. + male + + + + + %1 shuffles %2. + female + + + + + %1 shuffles %2. + male + + + + + %1 rolls a %2 with a %3-sided die. + female + + + + + %1 rolls a %2 with a %3-sided die. + male + + + + + %1 draws %n card(s). + female + + + + + + + %1 draws %n card(s). + male + + + + + + + %1 undoes her last draw. + + + + + %1 undoes his last draw. + + + + + %1 undoes her last draw (%2). + + + + + %1 undoes his last draw (%2). + + + + + from table + + + + + from graveyard + + + + + from exile + + + + + from hand + + + + + the bottom card of %1's library + + + + + the bottom card of her library + + + + + the bottom card of his library + + + + + from the bottom of %1's library + + + + + from the bottom of her library + + + + + from the bottom of his library + + + + + the top card of %1's library + + + + + the top card of her library + + + + + the top card of his library + + + + + from the top of %1's library + + + + + from the top of her library + + + + + from the top of his library + + + + + from %1's library + + + + + from library + + + + + from sideboard + + + + + from the stack + + + + + + a card + + + + + %1 gives %2 control over %3. + + + + + %1 puts %2 into play tapped%3. + + + + + %1 puts %2 into play%3. + + + + + %1 puts %2%3 into graveyard. + + + + + %1 exiles %2%3. + + + + + %1 moves %2%3 to hand. + + + + + %1 puts %2%3 into her library. + + + + + %1 puts %2%3 into his library. + + + + + %1 puts %2%3 on bottom of her library. + + + + + %1 puts %2%3 on bottom of his library. + + + + + %1 puts %2%3 on top of her library. + + + + + %1 puts %2%3 on top of his library. + + + + + %1 puts %2%3 into her library at position %4. + + + + + %1 puts %2%3 into his library at position %4. + + + + + %1 moves %2%3 to sideboard. + + + + + %1 plays %2%3. + + + + + %1 takes a mulligan to %n. + female + + + + + + + %1 takes a mulligan to %n. + male + + + + + + + %1 draws her initial hand. + + + + + %1 draws his initial hand. + + + + + %1 flips %2 face-down. + female + + + + + %1 flips %2 face-down. + male + + + + + %1 flips %2 face-up. + female + + + + + %1 flips %2 face-up. + male + + + + + %1 destroys %2. + female + + + + + %1 destroys %2. + male + + + + + %1 attaches %2 to %3's %4. + p1 female, p2 female + + + + + %1 attaches %2 to %3's %4. + p1 female, p2 male + + + + + %1 attaches %2 to %3's %4. + p1 male, p2 female + + + + + %1 attaches %2 to %3's %4. + p1 male, p2 male + + + + + %1 unattaches %2. + female + + + + + %1 unattaches %2. + male + + + + + %1 creates token: %2%3. + female + + + + + %1 creates token: %2%3. + male + + + + + %1 points from her %2 to herself. + female + + + + + %1 points from his %2 to himself. + male + + + + + %1 points from her %2 to %3. + p1 female, p2 female + + + + + %1 points from her %2 to %3. + p1 female, p2 male + + + + + %1 points from his %2 to %3. + p1 male, p2 female + + + + + %1 points from his %2 to %3. + p1 male, p2 male + + + + + %1 points from %2's %3 to herself. + card owner female, target female + + + + + %1 points from %2's %3 to herself. + card owner male, target female + + + + + %1 points from %2's %3 to himself. + card owner female, target male + + + + + %1 points from %2's %3 to himself. + card owner male, target male + + + + + %1 points from %2's %3 to %4. + p1 female, p2 female, p3 female + + + + + %1 points from %2's %3 to %4. + p1 female, p2 female, p3 male + + + + + %1 points from %2's %3 to %4. + p1 female, p2 male, p3 female + + + + + %1 points from %2's %3 to %4. + p1 female, p2 male, p3 male + + + + + %1 points from %2's %3 to %4. + p1 male, p2 female, p3 female + + + + + %1 points from %2's %3 to %4. + p1 male, p2 female, p3 male + + + + + %1 points from %2's %3 to %4. + p1 male, p2 male, p3 female + + + + + %1 points from %2's %3 to %4. + p1 male, p2 male, p3 male + + + + + %1 points from her %2 to her %3. + female + + + + + %1 points from his %2 to his %3. + male + + + + + %1 points from her %2 to %3's %4. + p1 female, p2 female + + + + + %1 points from her %2 to %3's %4. + p1 female, p2 male + + + + + %1 points from his %2 to %3's %4. + p1 male, p2 female + + + + + %1 points from his %2 to %3's %4. + p1 male, p2 male + + + + + %1 points from %2's %3 to her own %4. + card owner female, target female + + + + + %1 points from %2's %3 to her own %4. + card owner male, target female + + + + + %1 points from %2's %3 to his own %4. + card owner female, target male + + + + + %1 points from %2's %3 to his own %4. + card owner male, target male + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 female, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 female, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 male, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 female, p2 male, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 female, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 female, p3 male + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 male, p3 female + + + + + %1 points from %2's %3 to %4's %5. + p1 male, p2 male, p3 male + + + + + %1 places %n %2 counter(s) on %3 (now %4). + female + + + + + + + %1 places %n %2 counter(s) on %3 (now %4). + male + + + + + + + %1 removes %n %2 counter(s) from %3 (now %4). + female + + + + + + + %1 removes %n %2 counter(s) from %3 (now %4). + male + + + + + + + red + + + + + + + yellow + + + + + + + green + + + + + + + %1 taps her permanents. + female + + + + + %1 untaps her permanents. + female + + + + + %1 taps his permanents. + male + + + + + %1 untaps his permanents. + male + + + + + %1 taps %2. + female + + + + + %1 untaps %2. + female + + + + + %1 taps %2. + male + + + + + %1 untaps %2. + male + + + + + %1 sets counter %2 to %3 (%4%5). + female + + + + + %1 sets counter %2 to %3 (%4%5). + male + + + + + %1 sets %2 to not untap normally. + female + + + + + %1 sets %2 to not untap normally. + male + + + + + %1 sets %2 to untap normally. + female + + + + + %1 sets %2 to untap normally. + male + + + + + %1 sets PT of %2 to %3. + female + + + + + %1 sets PT of %2 to %3. + male + + + + + %1 sets annotation of %2 to %3. + female + + + + + %1 sets annotation of %2 to %3. + male + + + + + %1 is looking at %2. + female + + + + + %1 is looking at %2. + male + + + + + %1 is looking at the top %n card(s) %2. + female + + + + + + + %1 is looking at the top %n card(s) %2. + male + + + + + + + %1 stops looking at %2. + female + + + + + %1 stops looking at %2. + male + + + + + %1 reveals %2 to %3. + p1 female, p2 female + + + + + %1 reveals %2 to %3. + p1 female, p2 male + + + + + %1 reveals %2 to %3. + p1 male, p2 female + + + + + %1 reveals %2 to %3. + p1 male, p2 male + + + + + %1 reveals %2. + female + + + + + %1 reveals %2. + male + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 randomly reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 randomly reveals %2%3. + female + + + + + %1 randomly reveals %2%3. + male + + + + + %1 peeks at face down card #%2. + female + + + + + %1 peeks at face down card #%2. + male + + + + + %1 peeks at face down card #%2: %3. + female + + + + + %1 peeks at face down card #%2: %3. + male + + + + + %1 reveals %2%3 to %4. + p1 female, p2 female + + + + + %1 reveals %2%3 to %4. + p1 female, p2 male + + + + + %1 reveals %2%3 to %4. + p1 male, p2 female + + + + + %1 reveals %2%3 to %4. + p1 male, p2 male + + + + + %1 reveals %2%3. + female + + + + + %1 reveals %2%3. + male + + + + + %1 is now keeping the top card %2 revealed. + + + + + %1 is not revealing the top card %2 any longer. + + + + + It is now %1's turn. + female + + + + + It is now %1's turn. + male + + + + + untap step + + + + + upkeep step + + + + + draw step + + + + + first main phase + + + + + beginning of combat step + + + + + declare attackers step + + + + + declare blockers step + + + + + combat damage step + + + + + end of combat step + + + + + second main phase + + + + + ending phase + + + + + It is now the %1. + + + + + MessagesSettingsPage + + + Add message + + + + + Message: + + + + + &Add + + + + + &Remove + + + + + PhasesToolbar + + + Untap step + + + + + Upkeep step + + + + + Draw step + + + + + First main phase + + + + + Beginning of combat step + + + + + Declare attackers step + + + + + Declare blockers step + + + + + Combat damage step + + + + + End of combat step + + + + + Second main phase + + + + + End of turn step + + + + + Player + + + &View graveyard + + + + + &View exile + + + + + Player "%1" + + + + + &Graveyard + + + + + &Exile + + + + + + + Move to &top of library + + + + + + + Move to &bottom of library + + + + + + Move to &graveyard + + + + + + Move to &exile + + + + + + Move to &hand + + + + + &View library + + + + + View &top cards of library... + + + + + Reveal &library to + + + + + Reveal t&op card to + + + + + &Always reveal top card + + + + + O&pen deck in deck editor + + + + + &View sideboard + + + + + &Draw card + + + + + D&raw cards... + + + + + &Undo last draw + + + + + Take &mulligan + + + + + &Shuffle + + + + + Move top cards to &graveyard... + + + + + Move top cards to &exile... + + + + + Put top card on &bottom + + + + + &Hand + + + + + &Reveal to + + + + + Reveal r&andom card to + + + + + &Sideboard + + + + + &Library + + + + + &Counters + + + + + &Untap all permanents + + + + + R&oll die... + + + + + &Create token... + + + + + C&reate another token + + + + + Cr&eate predefined token + + + + + S&ay + + + + + C&ard + + + + + &All players + + + + + &Play + + + + + &Hide + + + + + &Tap + + + + + &Untap + + + + + Toggle &normal untapping + + + + + &Flip + + + + + &Peek at card face + + + + + &Clone + + + + + Ctrl+J + + + + + Attac&h to card... + + + + + Ctrl+A + + + + + Unattac&h + + + + + &Draw arrow... + + + + + &Increase power + + + + + Ctrl++ + + + + + &Decrease power + + + + + Ctrl+- + + + + + I&ncrease toughness + + + + + Alt++ + + + + + D&ecrease toughness + + + + + Alt+- + + + + + In&crease power and toughness + + + + + Ctrl+Alt++ + + + + + Dec&rease power and toughness + + + + + Ctrl+Alt+- + + + + + Set &power and toughness... + + + + + Ctrl+P + + + + + &Set annotation... + + + + + red + + + + + yellow + + + + + green + + + + + &Add counter (%1) + + + + + &Remove counter (%1) + + + + + &Set counters (%1)... + + + + + &top of library + + + + + &bottom of library + + + + + &graveyard + + + + + Ctrl+Del + + + + + &exile + + + + + Ctrl+F3 + + + + + F3 + + + + + Ctrl+W + + + + + F4 + + + + + Ctrl+D + + + + + Ctrl+E + + + + + Ctrl+Shift+D + + + + + Ctrl+M + + + + + Ctrl+S + + + + + Ctrl+U + + + + + Ctrl+I + + + + + Ctrl+T + + + + + Ctrl+G + + + + + View top cards of library + + + + + Number of cards: + + + + + Draw cards + + + + + + + + Number: + + + + + Move top cards to grave + + + + + Move top cards to exile + + + + + Roll die + + + + + Number of sides: + + + + + Set power/toughness + + + + + Please enter the new PT: + + + + + Set annotation + + + + + Please enter the new annotation: + + + + + Set counters + + + + + QObject + + + Common deck formats (*.cod *.dec *.mwDeck) + + + + + All files (*.*) + + + + + Cockatrice replays (*.cor) + + + + + RemoteDeckList_TreeModel + + + Name + + + + + ID + + + + + Upload time + + + + + RemoteReplayList_TreeModel + + + ID + + + + + Name + + + + + Players + + + + + Keep + + + + + Time started + + + + + Duration (sec) + + + + + RoomSelector + + + Rooms + + + + + Joi&n + + + + + Room + + + + + Description + + + + + Players + + + + + Games + + + + + SetsModel + + + Short name + + + + + Long name + + + + + ShutdownDialog + + + &Reason for shutdown: + + + + + &Time until shutdown (minutes): + + + + + Shut down server + + + + + TabAdmin + + + Update server &message + + + + + &Shut down server + + + + + Server administration functions + + + + + &Unlock functions + + + + + &Lock functions + + + + + Unlock administration functions + + + + + Do you really want to unlock the administration functions? + + + + + TabDeckEditor + + + Show card text only + + + + + &Clear search + + + + + &Search for: + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Update prices + + + + + Ctrl+U + + + + + &New deck + + + + + &Load deck... + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Save deck to clip&board + + + + + &Print deck... + + + + + &Analyze deck on deckstats.net + + + + + &Close + + + + + Ctrl+Q + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + &Remove row + + + + + Del + + + + + &Increment number + + + + + + + + + + + &Decrement number + + + + + - + + + + + &Deck editor + + + + + C&ard database + + + + + &Edit sets... + + + + + Edit &tokens... + + + + + Deck: %1 + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + Load deck + + + + + + + Error + + + + + The deck could not be saved. + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + TabDeckStorage + + + Local file system + + + + + Server deck storage + + + + + + Open in deck editor + + + + + Upload deck + + + + + Download deck + + + + + + New folder + + + + + + Delete + + + + + Enter deck name + + + + + This decklist does not have a name. +Please enter a name: + + + + + Unnamed deck + + + + + Delete local file + + + + + + + Are you sure you want to delete "%1"? + + + + + Name of new folder: + + + + + Delete remote folder + + + + + Delete remote deck + + + + + TabGame + + + F5 + + + + + F6 + + + + + F7 + + + + + F8 + + + + + F9 + + + + + F10 + + + + + &Phases + + + + + &Game + + + + + Next &phase + + + + + Ctrl+Space + + + + + Tab + + + + + Next &turn + + + + + Ctrl+Return + + + + + Ctrl+Enter + + + + + &Remove all local arrows + + + + + Ctrl+R + + + + + Game &information + + + + + &Concede + + + + + F2 + + + + + &Leave game + + + + + + Ctrl+Q + + + + + C&lose replay + + + + + &Say: + + + + + Concede + + + + + Are you sure you want to concede this game? + + + + + Leave game + + + + + Are you sure you want to leave this game? + + + + + Replay %1: %2 + + + + + Game %1: %2 + + + + + TabMessage + + + Personal &talk + + + + + &Leave + + + + + Talking to %1 + + + + + This user is ignoring you. + + + + + %1 has left the server. + + + + + %1 has joined the server. + + + + + TabReplays + + + Local file system + + + + + Server replay storage + + + + + + Watch replay + + + + + + Delete + + + + + Download replay + + + + + Toggle expiration lock + + + + + Delete local file + + + + + Are you sure you want to delete "%1"? + + + + + Delete remote replay + + + + + Are you sure you want to delete the replay of game %1? + + + + + TabRoom + + + &Say: + + + + + Chat + + + + + &Room + + + + + &Leave room + + + + + &Ignore unregistered users in chat + + + + + You are flooding the chat. Please wait a couple of seconds. + + + + + TabUserLists + + + Add to Buddy List + + + + + Add to Ignore List + + + + + UserContextMenu + + + User &details + + + + + Direct &chat + + + + + Show this user's &games + + + + + Add to &buddy list + + + + + Remove from &buddy list + + + + + Add to &ignore list + + + + + Remove from &ignore list + + + + + Kick from &game + + + + + Ban from &server + + + + + %1's games + + + + + UserInfoBox + + + User information + + + + + Real name: + + + + + Gender: + + + + + Location: + + + + + User level: + + + + + Administrator + + + + + Moderator + + + + + Registered user + + + + + Unregistered user + + + + + UserInterfaceSettingsPage + + + General interface settings + + + + + Enable notifications in taskbar + + + + + &Double-click cards to play them (instead of single-click) + + + + + &Play all nonlands onto the stack (not the battlefield) by default + + + + + Animation settings + + + + + &Tap/untap animation + + + + + Enable &sounds + + + + + Path to sounds directory: + + + + + Choose path + + + + + UserList + + + Users online: %1 + + + + + Users in this room: %1 + + + + + Buddies online: %1 / %2 + + + + + Ignored users online: %1 / %2 + + + + + WndSets + + + Edit sets + + + + + ZoneViewWidget + + + sort by name + + + + + sort by type + + + + + shuffle when closing + + + + diff --git a/common/.directory b/common/.directory deleted file mode 100644 index 44c546691..000000000 --- a/common/.directory +++ /dev/null @@ -1,3 +0,0 @@ -[Dolphin] -Timestamp=2009,10,26,13,45,44 -ViewMode=1 diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 000000000..47dab1f5c --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1,51 @@ +# CMakeLists for common directory +# +# provides the common library + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +add_subdirectory(pb) + +SET(common_SOURCES + decklist.cpp + get_pb_extension.cpp + rng_abstract.cpp + rng_sfmt.cpp + server.cpp + server_abstractuserinterface.cpp + server_arrow.cpp + server_arrowtarget.h + server_card.cpp + server_cardzone.cpp + server_counter.cpp + server_game.cpp + server_database_interface.cpp + server_player.cpp + server_protocolhandler.cpp + server_remoteuserinterface.cpp + server_response_containers.cpp + server_room.cpp + serverinfo_user_container.cpp + sfmt/SFMT.c +) + +set(ORACLE_LIBS) + +# Qt4 stuff +if(Qt4_FOUND) + # Include directories + INCLUDE(${QT_USE_FILE}) + include_directories(${QT_INCLUDES}) +endif() + +# qt5 stuff +if(Qt5Widgets_FOUND) + include_directories(${Qt5Widgets_INCLUDE_DIRS}) +endif() + +INCLUDE_DIRECTORIES(pb) +INCLUDE_DIRECTORIES(sfmt) +INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) + +add_library(cockatrice_common ${common_SOURCES} ${common_MOC_SRCS}) +target_link_libraries(cockatrice_common cockatrice_protocol) diff --git a/common/color.h b/common/color.h index af498fdb4..1ea3e33df 100644 --- a/common/color.h +++ b/common/color.h @@ -5,23 +5,31 @@ #include #endif -class Color { -private: - int value; -public: - Color(int _value = 0) : value(_value) { } - Color(int r, int g, int b) : value(r * 65536 + g * 256 + b) { } - int getValue() const { return value; } -#ifdef QT_GUI_LIB - Color(const QColor &_color) - { - value = _color.red() * 65536 + _color.green() * 256 + _color.blue(); - } - QColor getQColor() const - { - return QColor(value / 65536, (value % 65536) / 256, value % 256); - } -#endif -}; +#include "pb/color.pb.h" -#endif \ No newline at end of file +#ifdef QT_GUI_LIB +inline QColor convertColorToQColor(const color &c) +{ + return QColor(c.r(), c.g(), c.b()); +} + +inline color convertQColorToColor(const QColor &c) +{ + color result; + result.set_r(c.red()); + result.set_g(c.green()); + result.set_b(c.blue()); + return result; +} +#endif + +inline color makeColor(int r, int g, int b) +{ + color result; + result.set_r(r); + result.set_g(g); + result.set_b(b); + return result; +} + +#endif diff --git a/common/decklist.cpp b/common/decklist.cpp index c4eefff72..0b2558b5d 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -1,131 +1,160 @@ #include #include -#include -#include #include #include +#include #include "decklist.h" -MoveCardToZone::MoveCardToZone(const QString &_cardName, const QString &_startZone, const QString &_targetZone) - : SerializableItem_Map("move_card_to_zone") +SideboardPlan::SideboardPlan(const QString &_name, const QList &_moveList) + : name(_name), moveList(_moveList) { - insertItem(new SerializableItem_String("card_name", _cardName)); - insertItem(new SerializableItem_String("start_zone", _startZone)); - insertItem(new SerializableItem_String("target_zone", _targetZone)); } -MoveCardToZone::MoveCardToZone(MoveCardToZone *other) - : SerializableItem_Map("move_card_to_zone") +void SideboardPlan::setMoveList(const QList &_moveList) { - insertItem(new SerializableItem_String("card_name", other->getCardName())); - insertItem(new SerializableItem_String("start_zone", other->getStartZone())); - insertItem(new SerializableItem_String("target_zone", other->getTargetZone())); + moveList = _moveList; } -SideboardPlan::SideboardPlan(const QString &_name, const QList &_moveList) - : SerializableItem_Map("sideboard_plan") +bool SideboardPlan::readElement(QXmlStreamReader *xml) { - insertItem(new SerializableItem_String("name", _name)); - - for (int i = 0; i < _moveList.size(); ++i) - itemList.append(_moveList[i]); + while (!xml->atEnd()) { + xml->readNext(); + const QString childName = xml->name().toString(); + if (xml->isStartElement()) { + if (childName == "name") + name = xml->readElementText(); + else if (childName == "move_card_to_zone") { + MoveCard_ToZone m; + while (!xml->atEnd()) { + xml->readNext(); + const QString childName2 = xml->name().toString(); + if (xml->isStartElement()) { + if (childName2 == "card_name") + m.set_card_name(xml->readElementText().toStdString()); + else if (childName2 == "start_zone") + m.set_start_zone(xml->readElementText().toStdString()); + else if (childName2 == "target_zone") + m.set_target_zone(xml->readElementText().toStdString()); + } else if (xml->isEndElement() && (childName2 == "move_card_to_zone")) { + moveList.append(m); + break; + } + } + } + } else if (xml->isEndElement() && (childName == "sideboard_plan")) + return true; + } + return false; } -void SideboardPlan::setMoveList(const QList &_moveList) +void SideboardPlan::write(QXmlStreamWriter *xml) { - for (int i = 0; i < itemList.size(); ++i) - delete itemList[i]; - itemList.clear(); - - for (int i = 0; i < _moveList.size(); ++i) - itemList.append(_moveList[i]); + xml->writeStartElement("sideboard_plan"); + xml->writeTextElement("name", name); + for (int i = 0; i < moveList.size(); ++i) { + xml->writeStartElement("move_card_to_zone"); + xml->writeTextElement("card_name", QString::fromStdString(moveList[i].card_name())); + xml->writeTextElement("start_zone", QString::fromStdString(moveList[i].start_zone())); + xml->writeTextElement("target_zone", QString::fromStdString(moveList[i].target_zone())); + xml->writeEndElement(); + } + xml->writeEndElement(); } AbstractDecklistNode::AbstractDecklistNode(InnerDecklistNode *_parent) - : parent(_parent), currentItem(0) + : parent(_parent) { - if (parent) - parent->append(this); + if (parent) + parent->append(this); } int AbstractDecklistNode::depth() const { - if (parent) - return parent->depth() + 1; - else - return 0; + if (parent) + return parent->depth() + 1; + else + return 0; } InnerDecklistNode::InnerDecklistNode(InnerDecklistNode *other, InnerDecklistNode *_parent) - : AbstractDecklistNode(_parent), name(other->getName()) + : AbstractDecklistNode(_parent), name(other->getName()) { - for (int i = 0; i < other->size(); ++i) { - InnerDecklistNode *inner = dynamic_cast(other->at(i)); - if (inner) - new InnerDecklistNode(inner, this); - else - new DecklistCardNode(dynamic_cast(other->at(i)), this); - } + for (int i = 0; i < other->size(); ++i) { + InnerDecklistNode *inner = dynamic_cast(other->at(i)); + if (inner) + new InnerDecklistNode(inner, this); + else + new DecklistCardNode(dynamic_cast(other->at(i)), this); + } } InnerDecklistNode::~InnerDecklistNode() { - clearTree(); + clearTree(); } QString InnerDecklistNode::visibleNameFromName(const QString &_name) { - if (_name == "main") - return QObject::tr("Maindeck"); - else if (_name == "side") - return QObject::tr("Sideboard"); - else - return _name; + if (_name == "main") + return QObject::tr("Maindeck"); + else if (_name == "side") + return QObject::tr("Sideboard"); + else if (_name == "tokens") + return QObject::tr("Tokens"); + else + return _name; +} + +void InnerDecklistNode::setSortMethod(DeckSortMethod method) +{ + sortMethod = method; + for (int i = 0; i < size(); i++) + at(i)->setSortMethod(method); } QString InnerDecklistNode::getVisibleName() const { - return visibleNameFromName(name); + return visibleNameFromName(name); } void InnerDecklistNode::clearTree() { - for (int i = 0; i < size(); i++) - delete at(i); - clear(); + for (int i = 0; i < size(); i++) + delete at(i); + clear(); } DecklistCardNode::DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent) - : AbstractDecklistCardNode(_parent), name(other->getName()), number(other->getNumber()), price(other->getPrice()) + : AbstractDecklistCardNode(_parent), name(other->getName()), number(other->getNumber()), price(other->getPrice()) { } AbstractDecklistNode *InnerDecklistNode::findChild(const QString &name) { - for (int i = 0; i < size(); i++) - if (at(i)->getName() == name) - return at(i); - return 0; + for (int i = 0; i < size(); i++) + if (at(i)->getName() == name) + return at(i); + return 0; } int InnerDecklistNode::height() const { - return at(0)->height() + 1; + return at(0)->height() + 1; } int InnerDecklistNode::recursiveCount(bool countTotalCards) const { - int result = 0; - for (int i = 0; i < size(); i++) { - InnerDecklistNode *node = dynamic_cast(at(i)); - if (node) - result += node->recursiveCount(countTotalCards); - else if (countTotalCards) - result += dynamic_cast(at(i))->getNumber(); - else - result += 1; - } - return result; + int result = 0; + for (int i = 0; i < size(); i++) { + InnerDecklistNode *node = dynamic_cast(at(i)); + if (node) + result += node->recursiveCount(countTotalCards); + else if (countTotalCards) + result += dynamic_cast(at(i))->getNumber(); + else + result += 1; + } + return result; } float InnerDecklistNode::recursivePrice(bool countTotalCards) const @@ -143,435 +172,576 @@ float InnerDecklistNode::recursivePrice(bool countTotalCards) const bool InnerDecklistNode::compare(AbstractDecklistNode *other) const { - InnerDecklistNode *other2 = dynamic_cast(other); - if (other2) - return (getName() > other->getName()); - else - return false; + switch (sortMethod) { + case 0: + return compareNumber(other); + case 1: + return compareName(other); + case 2: + return comparePrice(other); + } + return 0; +} + +bool InnerDecklistNode::compareNumber(AbstractDecklistNode *other) const +{ + InnerDecklistNode *other2 = dynamic_cast(other); + if (other2) { + int n1 = recursiveCount(true); + int n2 = other2->recursiveCount(true); + return (n1 != n2) ? (n1 > n2) : compareName(other); + } else { + return false; + } +} + +bool InnerDecklistNode::compareName(AbstractDecklistNode *other) const +{ + InnerDecklistNode *other2 = dynamic_cast(other); + if (other2) { + return (getName() > other2->getName()); + } else { + return false; + } +} + +bool InnerDecklistNode::comparePrice(AbstractDecklistNode *other) const +{ + InnerDecklistNode *other2 = dynamic_cast(other); + if (other2) { + int p1 = 100*recursivePrice(true); + int p2 = 100*other2->recursivePrice(true); + return (p1 != p2) ? (p1 > p2) : compareName(other); + } else { + return false; + } } bool AbstractDecklistCardNode::compare(AbstractDecklistNode *other) const { - AbstractDecklistCardNode *other2 = dynamic_cast(other); - if (other2) - return (getName() > other->getName()); - else - return true; + switch (sortMethod) { + case ByNumber: + return compareNumber(other); + case ByName: + return compareName(other); + case ByPrice: + return compareTotalPrice(other); + } + return 0; +} + +bool AbstractDecklistCardNode::compareNumber(AbstractDecklistNode *other) const +{ + AbstractDecklistCardNode *other2 = dynamic_cast(other); + if (other2) { + int n1 = getNumber(); + int n2 = other2->getNumber(); + return (n1 != n2) ? (n1 > n2) : compareName(other); + } else { + return true; + } +} + +bool AbstractDecklistCardNode::compareName(AbstractDecklistNode *other) const +{ + AbstractDecklistCardNode *other2 = dynamic_cast(other); + if (other2) { + return (getName() > other2->getName()); + } else { + return true; + } +} + +bool AbstractDecklistCardNode::compareTotalPrice(AbstractDecklistNode *other) const +{ + AbstractDecklistCardNode *other2 = dynamic_cast(other); + if (other2) { + int p1 = 100*getTotalPrice(); + int p2 = 100*other2->getTotalPrice(); + return (p1 != p2) ? (p1 > p2) : compareName(other); + } else { + return true; + } } class InnerDecklistNode::compareFunctor { private: - Qt::SortOrder order; + Qt::SortOrder order; public: - compareFunctor(Qt::SortOrder _order) : order(_order) { } - inline bool operator()(QPair a, QPair b) const - { - return (order == Qt::AscendingOrder) ^ (a.second->compare(b.second)); - } + compareFunctor(Qt::SortOrder _order) : order(_order) { } + inline bool operator()(QPair a, QPair b) const + { + return (order == Qt::AscendingOrder) ^ (a.second->compare(b.second)); + } }; bool InnerDecklistNode::readElement(QXmlStreamReader *xml) { - if (currentItem) { - if (currentItem->readElement(xml)) - currentItem = 0; - return false; - } - if (xml->isStartElement() && (xml->name() == "zone")) - currentItem = new InnerDecklistNode(xml->attributes().value("name").toString(), this); - else if (xml->isStartElement() && (xml->name() == "card")) { + while (!xml->atEnd()) { + xml->readNext(); + const QString childName = xml->name().toString(); + if (xml->isStartElement()) { + if (childName == "zone") { + InnerDecklistNode *newZone = new InnerDecklistNode(xml->attributes().value("name").toString(), this); + newZone->readElement(xml); + } else if (childName == "card") { float price = (xml->attributes().value("price") != NULL) ? xml->attributes().value("price").toString().toFloat() : 0; - currentItem = new DecklistCardNode(xml->attributes().value("name").toString(), xml->attributes().value("number").toString().toInt(), price, this); - } else if (xml->isEndElement() && (xml->name() == "zone")) - return true; - - return false; + DecklistCardNode *newCard = new DecklistCardNode(xml->attributes().value("name").toString(), xml->attributes().value("number").toString().toInt(), price, this); + newCard->readElement(xml); + } + } else if (xml->isEndElement() && (childName == "zone")) + return false; + } + return true; } void InnerDecklistNode::writeElement(QXmlStreamWriter *xml) { - xml->writeStartElement("zone"); - xml->writeAttribute("name", name); - for (int i = 0; i < size(); i++) - at(i)->writeElement(xml); - xml->writeEndElement(); // zone + xml->writeStartElement("zone"); + xml->writeAttribute("name", name); + for (int i = 0; i < size(); i++) + at(i)->writeElement(xml); + xml->writeEndElement(); // zone } bool AbstractDecklistCardNode::readElement(QXmlStreamReader *xml) { - if (xml->isEndElement()) - return true; - else - return false; + while (!xml->atEnd()) { + xml->readNext(); + if (xml->isEndElement() && xml->name() == "card") + return false; + } + return true; } void AbstractDecklistCardNode::writeElement(QXmlStreamWriter *xml) { - xml->writeEmptyElement("card"); - xml->writeAttribute("number", QString::number(getNumber())); + xml->writeEmptyElement("card"); + xml->writeAttribute("number", QString::number(getNumber())); xml->writeAttribute("price", QString::number(getPrice())); - xml->writeAttribute("name", getName()); + xml->writeAttribute("name", getName()); } QVector > InnerDecklistNode::sort(Qt::SortOrder order) { - QVector > result(size()); - - // Initialize temporary list with contents of current list - QVector > tempList(size()); - for (int i = size() - 1; i >= 0; --i) { - tempList[i].first = i; - tempList[i].second = at(i); - } - - // Sort temporary list - compareFunctor cmp(order); - qSort(tempList.begin(), tempList.end(), cmp); - - // Map old indexes to new indexes and - // copy temporary list to the current one - for (int i = size() - 1; i >= 0; --i) { - result[i].first = tempList[i].first; - result[i].second = i; - replace(i, tempList[i].second); - } + QVector > result(size()); + + // Initialize temporary list with contents of current list + QVector > tempList(size()); + for (int i = size() - 1; i >= 0; --i) { + tempList[i].first = i; + tempList[i].second = at(i); + } + + // Sort temporary list + compareFunctor cmp(order); + qSort(tempList.begin(), tempList.end(), cmp); + + // Map old indexes to new indexes and + // copy temporary list to the current one + for (int i = size() - 1; i >= 0; --i) { + result[i].first = tempList[i].first; + result[i].second = i; + replace(i, tempList[i].second); + } - return result; + return result; } -const QStringList DeckList::fileNameFilters = QStringList() - << QObject::tr("Cockatrice decks (*.cod)") - << QObject::tr("Plain text decks (*.dec *.mwDeck)") - << QObject::tr("All files (*.*)"); - DeckList::DeckList() - : SerializableItem("cockatrice_deck"), currentZone(0), currentSideboardPlan(0) { - root = new InnerDecklistNode; + root = new InnerDecklistNode; } -DeckList::DeckList(DeckList *other) - : SerializableItem("cockatrice_deck"), currentZone(0), currentSideboardPlan(0) +// TODO: http://qt-project.org/doc/qt-4.8/qobject.html#no-copy-constructor-or-assignment-operator +DeckList::DeckList(const DeckList &other) + : QObject(), + name(other.name), + comments(other.comments), + deckHash(other.deckHash) { - root = new InnerDecklistNode(other->getRoot()); - - QMapIterator spIterator(other->getSideboardPlans()); - while (spIterator.hasNext()) { - spIterator.next(); - QList newMoveList; - QList oldMoveList = spIterator.value()->getMoveList(); - for (int i = 0; i < oldMoveList.size(); ++i) - newMoveList.append(new MoveCardToZone(oldMoveList[i])); - sideboardPlans.insert(spIterator.key(), new SideboardPlan(spIterator.key(), newMoveList)); - } - updateDeckHash(); + root = new InnerDecklistNode(other.getRoot()); + + QMapIterator spIterator(other.getSideboardPlans()); + while (spIterator.hasNext()) { + spIterator.next(); + sideboardPlans.insert(spIterator.key(), new SideboardPlan(spIterator.key(), spIterator.value()->getMoveList())); + } + updateDeckHash(); +} + +DeckList::DeckList(const QString &nativeString) +{ + root = new InnerDecklistNode; + loadFromString_Native(nativeString); } DeckList::~DeckList() { - delete root; - - QMapIterator i(sideboardPlans); - while (i.hasNext()) - delete i.next().value(); + delete root; + + QMapIterator i(sideboardPlans); + while (i.hasNext()) + delete i.next().value(); } -QList DeckList::getCurrentSideboardPlan() +QList DeckList::getCurrentSideboardPlan() { - SideboardPlan *current = sideboardPlans.value(QString(), 0); - if (!current) - return QList(); - else - return current->getMoveList(); + SideboardPlan *current = sideboardPlans.value(QString(), 0); + if (!current) + return QList(); + else + return current->getMoveList(); } -void DeckList::setCurrentSideboardPlan(const QList &plan) +void DeckList::setCurrentSideboardPlan(const QList &plan) { - SideboardPlan *current = sideboardPlans.value(QString(), 0); - if (!current) { - current = new SideboardPlan; - sideboardPlans.insert(QString(), current); - } - - QList newList; - for (int i = 0; i < plan.size(); ++i) - newList.append(new MoveCardToZone(plan[i])); - current->setMoveList(newList); + SideboardPlan *current = sideboardPlans.value(QString(), 0); + if (!current) { + current = new SideboardPlan; + sideboardPlans.insert(QString(), current); + } + + current->setMoveList(plan); } bool DeckList::readElement(QXmlStreamReader *xml) { - if (currentZone) { - if (currentZone->readElement(xml)) - currentZone = 0; - } else if (currentSideboardPlan) { - if (currentSideboardPlan->readElement(xml)) { - sideboardPlans.insert(currentSideboardPlan->getName(), currentSideboardPlan); - currentSideboardPlan = 0; - } - return false; - } else if (xml->isEndElement()) { - if (xml->name() == "deckname") - name = currentElementText; - else if (xml->name() == "comments") - comments = currentElementText; - - currentElementText.clear(); - } else if (xml->isStartElement() && (xml->name() == "zone")) - currentZone = new InnerDecklistNode(xml->attributes().value("name").toString(), root); - else if (xml->isStartElement() && (xml->name() == "sideboard_plan")) { - currentSideboardPlan = new SideboardPlan; - if (currentSideboardPlan->readElement(xml)) { - sideboardPlans.insert(currentSideboardPlan->getName(), currentSideboardPlan); - currentSideboardPlan = 0; - } - } else if (xml->isCharacters() && !xml->isWhitespace()) - currentElementText = xml->text().toString(); - return SerializableItem::readElement(xml); + const QString childName = xml->name().toString(); + if (xml->isStartElement()) { + if (childName == "deckname") + name = xml->readElementText(); + else if (childName == "comments") + comments = xml->readElementText(); + else if (childName == "zone") { + InnerDecklistNode *newZone = new InnerDecklistNode(xml->attributes().value("name").toString(), root); + newZone->readElement(xml); + } else if (childName == "sideboard_plan") { + SideboardPlan *newSideboardPlan = new SideboardPlan; + if (newSideboardPlan->readElement(xml)) + sideboardPlans.insert(newSideboardPlan->getName(), newSideboardPlan); + else + delete newSideboardPlan; + } + } else if (xml->isEndElement() && (childName == "cockatrice_deck")) + return false; + return true; } -void DeckList::writeElement(QXmlStreamWriter *xml) +void DeckList::write(QXmlStreamWriter *xml) { - xml->writeAttribute("version", "1"); - xml->writeTextElement("deckname", name); - xml->writeTextElement("comments", comments); + xml->writeStartElement("cockatrice_deck"); + xml->writeAttribute("version", "1"); + xml->writeTextElement("deckname", name); + xml->writeTextElement("comments", comments); - for (int i = 0; i < root->size(); i++) - root->at(i)->writeElement(xml); - - QMapIterator i(sideboardPlans); - while (i.hasNext()) - i.next().value()->write(xml); + for (int i = 0; i < root->size(); i++) + root->at(i)->writeElement(xml); + + QMapIterator i(sideboardPlans); + while (i.hasNext()) + i.next().value()->write(xml); + xml->writeEndElement(); } -void DeckList::loadFromXml(QXmlStreamReader *xml) +bool DeckList::loadFromXml(QXmlStreamReader *xml) { - while (!xml->atEnd()) { - xml->readNext(); - if (xml->isStartElement()) { - if (xml->name() != "cockatrice_deck") - return; - while (!xml->atEnd()) { - xml->readNext(); - readElement(xml); - } - } - } - updateDeckHash(); + if (xml->error()) { + qDebug() << "Error loading deck from xml: " << xml->errorString(); + return false; + } + + cleanList(); + while (!xml->atEnd()) { + xml->readNext(); + if (xml->isStartElement()) { + if (xml->name() != "cockatrice_deck") + return false; + while (!xml->atEnd()) { + xml->readNext(); + if (!readElement(xml)) + break; + } + } + } + updateDeckHash(); + if (xml->error()) { + qDebug() << "Error loading deck from xml: " << xml->errorString(); + return false; + } + return true; +} + +bool DeckList::loadFromString_Native(const QString &nativeString) +{ + QXmlStreamReader xml(nativeString); + return loadFromXml(&xml); +} + +QString DeckList::writeToString_Native() +{ + QString result; + QXmlStreamWriter xml(&result); + xml.writeStartDocument(); + write(&xml); + xml.writeEndDocument(); + return result; } bool DeckList::loadFromFile_Native(QIODevice *device) { - QXmlStreamReader xml(device); - loadFromXml(&xml); - return true; + QXmlStreamReader xml(device); + return loadFromXml(&xml); } bool DeckList::saveToFile_Native(QIODevice *device) { - QXmlStreamWriter xml(device); - xml.setAutoFormatting(true); - xml.writeStartDocument(); + QXmlStreamWriter xml(device); + xml.setAutoFormatting(true); + xml.writeStartDocument(); - write(&xml); + write(&xml); - xml.writeEndDocument(); - return true; + xml.writeEndDocument(); + return true; } bool DeckList::loadFromStream_Plain(QTextStream &in) { - InnerDecklistNode *main = 0, *side = 0; + cleanList(); - int okRows = 0; - while (!in.atEnd()) { - QString line = in.readLine().simplified(); - if (line.startsWith("//")) - continue; + InnerDecklistNode *main = 0, *side = 0; + bool inSideboard = false; - InnerDecklistNode *zone; - if (line.startsWith("SB:", Qt::CaseInsensitive)) { - line = line.mid(3).trimmed(); - if (!side) - side = new InnerDecklistNode("side", root); - zone = side; - } else { - if (!main) - main = new InnerDecklistNode("main", root); - zone = main; - } + int okRows = 0; + while (!in.atEnd()) { + QString line = in.readLine().simplified(); + if (line.startsWith("//")) + continue; - // Filter out MWS edition symbols and basic land extras - QRegExp rx("\\[.*\\]"); - line.remove(rx); - rx.setPattern("\\(.*\\)"); - line.remove(rx); - line = line.simplified(); + InnerDecklistNode *zone; + if (line.startsWith("Sideboard", Qt::CaseInsensitive)) { + inSideboard = true; + continue; + } else if (line.startsWith("SB:", Qt::CaseInsensitive)) { + line = line.mid(3).trimmed(); + if (!side) + side = new InnerDecklistNode("side", root); + zone = side; + } else if (inSideboard) { + if (!side) + side = new InnerDecklistNode("side", root); + zone = side; + } else { + if (!main) + main = new InnerDecklistNode("main", root); + zone = main; + } - int i = line.indexOf(' '); - bool ok; - int number = line.left(i).toInt(&ok); - if (!ok) - continue; - ++okRows; - new DecklistCardNode(line.mid(i + 1), number, zone); - } - updateDeckHash(); - return (okRows > 0); + // Filter out MWS edition symbols and basic land extras + QRegExp rx("\\[.*\\]"); + line.remove(rx); + rx.setPattern("\\(.*\\)"); + line.remove(rx); + //Filter out post card name editions + rx.setPattern("\\|.*$"); + line.remove(rx); + line = line.simplified(); + + int i = line.indexOf(' '); + int cardNameStart = i + 1; + + // If the count ends with an 'x', ignore it. For example, + // "4x Storm Crow" will count 4 correctly. + if (i > 0 && line[i - 1] == 'x') { + i--; + } + + bool ok; + int number = line.left(i).toInt(&ok); + if (!ok) + continue; + + QString cardName = line.mid(cardNameStart); + // Common differences between cockatrice's card names + // and what's commonly used in decklists + rx.setPattern("’"); + cardName.replace(rx, "'"); + rx.setPattern("Æ"); + cardName.replace(rx, "AE"); + rx.setPattern("^Aether"); + cardName.replace(rx, "AEther"); + rx.setPattern("\\s*[|/]{1,2}\\s*"); + cardName.replace(rx, " // "); + + // Replace only if the ampersand is preceded by a non-capital letter, + // as would happen with acronyms. So 'Fire & Ice' is replaced but not + // 'R&D' or 'R & D'. + // + // Qt regexes don't support lookbehind so we capture and replace + // instead. + rx.setPattern("([^A-Z])\\s*&\\s*"); + if (rx.indexIn(cardName) != -1) { + cardName.replace(rx, QString("%1 // ").arg(rx.cap(1))); + } + + ++okRows; + new DecklistCardNode(cardName, number, zone); + } + updateDeckHash(); + return (okRows > 0); } bool DeckList::loadFromFile_Plain(QIODevice *device) { - QTextStream in(device); - return loadFromStream_Plain(in); + QTextStream in(device); + return loadFromStream_Plain(in); } +struct WriteToStream { + QTextStream &stream; + + WriteToStream(QTextStream &_stream) : stream(_stream) {} + + void operator()( + const InnerDecklistNode *node, + const DecklistCardNode *card + ) { + if (node->getName() == "side") { + stream << "SB: "; + } + stream << QString("%1 %2\n").arg( + card->getNumber() + ).arg( + card->getName() + ); + } +}; + bool DeckList::saveToStream_Plain(QTextStream &out) { - // Support for this is only possible if the internal structure doesn't get more complicated. - for (int i = 0; i < root->size(); i++) { - InnerDecklistNode *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - DecklistCardNode *card = dynamic_cast(node->at(j)); - out << QString("%1%2 %3\n").arg(node->getName() == "side" ? "SB: " : "").arg(card->getNumber()).arg(card->getName()); - } - } - return true; + WriteToStream writeToStream(out); + forEachCard(writeToStream); + return true; } bool DeckList::saveToFile_Plain(QIODevice *device) { - QTextStream out(device); - return saveToStream_Plain(out); + QTextStream out(device); + return saveToStream_Plain(out); } -bool DeckList::loadFromFile(const QString &fileName, FileFormat fmt) +QString DeckList::writeToString_Plain() { - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) - return false; - cleanList(); - - bool result = false; - switch (fmt) { - case PlainTextFormat: result = loadFromFile_Plain(&file); break; - case CockatriceFormat: result = loadFromFile_Native(&file); break; - } - if (result) - emit deckLoaded(); - return result; -} - -bool DeckList::saveToFile(const QString &fileName, FileFormat fmt) -{ - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) - return false; - - bool result = false; - switch (fmt) { - case PlainTextFormat: result = saveToFile_Plain(&file); break; - case CockatriceFormat: result = saveToFile_Native(&file); break; - } - return result; -} - -DeckList::FileFormat DeckList::getFormatFromNameFilter(const QString &selectedNameFilter) -{ - switch (fileNameFilters.indexOf(selectedNameFilter)) { - case 0: return CockatriceFormat; - case 1: return PlainTextFormat; - } - return PlainTextFormat; + QString result; + QTextStream out(&result); + saveToStream_Plain(out); + return result; } void DeckList::cleanList() { - root->clearTree(); - setName(); - setComments(); - updateDeckHash(); + root->clearTree(); + setName(); + setComments(); + deckHash = QString(); + emit deckHashChanged(); } void DeckList::getCardListHelper(InnerDecklistNode *item, QSet &result) const { - for (int i = 0; i < item->size(); ++i) { - DecklistCardNode *node = dynamic_cast(item->at(i)); - if (node) - result.insert(node->getName()); - else - getCardListHelper(dynamic_cast(item->at(i)), result); - } + for (int i = 0; i < item->size(); ++i) { + DecklistCardNode *node = dynamic_cast(item->at(i)); + if (node) + result.insert(node->getName()); + else + getCardListHelper(dynamic_cast(item->at(i)), result); + } } QStringList DeckList::getCardList() const { - QSet result; - getCardListHelper(root, result); - return result.toList(); + QSet result; + getCardListHelper(root, result); + return result.toList(); +} + +int DeckList::getSideboardSize() const +{ + int size = 0; + for (int i = 0; i < root->size(); ++i) { + InnerDecklistNode *node = dynamic_cast(root->at(i)); + if (node->getName() != "side") + continue; + for (int j = 0; j < node->size(); j++) { + DecklistCardNode *card = dynamic_cast(node->at(j)); + size += card->getNumber(); + } + } + return size; } DecklistCardNode *DeckList::addCard(const QString &cardName, const QString &zoneName) { - InnerDecklistNode *zoneNode = dynamic_cast(root->findChild(zoneName)); - if (!zoneNode) - zoneNode = new InnerDecklistNode(zoneName, root); - - DecklistCardNode *node = new DecklistCardNode(cardName, 1, zoneNode); - updateDeckHash(); - return node; + InnerDecklistNode *zoneNode = dynamic_cast(root->findChild(zoneName)); + if (!zoneNode) + zoneNode = new InnerDecklistNode(zoneName, root); + + DecklistCardNode *node = new DecklistCardNode(cardName, 1, zoneNode); + updateDeckHash(); + return node; } bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode) { - if (node == root) - return true; - bool updateHash = false; - if (!rootNode) { - rootNode = root; - updateHash = true; - } - - int index = rootNode->indexOf(node); - if (index != -1) { - delete rootNode->takeAt(index); - if (!rootNode->size()) - deleteNode(rootNode, rootNode->getParent()); - if (updateHash) - updateDeckHash(); - return true; - } - for (int i = 0; i < rootNode->size(); i++) { - InnerDecklistNode *inner = dynamic_cast(rootNode->at(i)); - if (inner) - if (deleteNode(node, inner)) { - if (updateHash) - updateDeckHash(); - return true; - } - } - return false; + if (node == root) + return true; + bool updateHash = false; + if (!rootNode) { + rootNode = root; + updateHash = true; + } + + int index = rootNode->indexOf(node); + if (index != -1) { + delete rootNode->takeAt(index); + if (!rootNode->size()) + deleteNode(rootNode, rootNode->getParent()); + if (updateHash) + updateDeckHash(); + return true; + } + for (int i = 0; i < rootNode->size(); i++) { + InnerDecklistNode *inner = dynamic_cast(rootNode->at(i)); + if (inner) + if (deleteNode(node, inner)) { + if (updateHash) + updateDeckHash(); + return true; + } + } + return false; } void DeckList::updateDeckHash() { - QStringList cardList; - for (int i = 0; i < root->size(); i++) { - InnerDecklistNode *node = dynamic_cast(root->at(i)); - for (int j = 0; j < node->size(); j++) { - DecklistCardNode *card = dynamic_cast(node->at(j)); - for (int k = 0; k < card->getNumber(); ++k) - cardList.append((node->getName() == "side" ? "SB:" : "") + card->getName().toLower()); - } - } - cardList.sort(); - QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); - quint64 number = (((quint64) (unsigned char) deckHashArray[0]) << 32) - + (((quint64) (unsigned char) deckHashArray[1]) << 24) - + (((quint64) (unsigned char) deckHashArray[2] << 16)) - + (((quint64) (unsigned char) deckHashArray[3]) << 8) - + (quint64) (unsigned char) deckHashArray[4]; - deckHash = QString::number(number, 32).rightJustified(8, '0'); - - emit deckHashChanged(); + QStringList cardList; + for (int i = 0; i < root->size(); i++) { + InnerDecklistNode *node = dynamic_cast(root->at(i)); + for (int j = 0; j < node->size(); j++) { + DecklistCardNode *card = dynamic_cast(node->at(j)); + for (int k = 0; k < card->getNumber(); ++k) + cardList.append((node->getName() == "side" ? "SB:" : "") + card->getName().toLower()); + } + } + cardList.sort(); + QByteArray deckHashArray = QCryptographicHash::hash(cardList.join(";").toUtf8(), QCryptographicHash::Sha1); + quint64 number = (((quint64) (unsigned char) deckHashArray[0]) << 32) + + (((quint64) (unsigned char) deckHashArray[1]) << 24) + + (((quint64) (unsigned char) deckHashArray[2] << 16)) + + (((quint64) (unsigned char) deckHashArray[3]) << 8) + + (quint64) (unsigned char) deckHashArray[4]; + deckHash = QString::number(number, 32).rightJustified(8, '0'); + + emit deckHashChanged(); } diff --git a/common/decklist.h b/common/decklist.h index 3e734c846..c20372056 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -7,168 +7,191 @@ #include #include #include -#include "serializable_item.h" +#include + +// Required on Mac. Forward declaration doesn't work. Don't ask why. +#include +#include + +#include "pb/move_card_to_zone.pb.h" class CardDatabase; class QIODevice; class QTextStream; -class QXmlStreamReader; -class QXmlStreamWriter; class InnerDecklistNode; -class MoveCardToZone : public SerializableItem_Map { +class SideboardPlan { +private: + QString name; + QList moveList; public: - MoveCardToZone(const QString &_cardName = QString(), const QString &_startZone = QString(), const QString &_targetZone = QString()); - MoveCardToZone(MoveCardToZone *other); - static SerializableItem *newItem() { return new MoveCardToZone; } - QString getCardName() const { return static_cast(itemMap.value("card_name"))->getData(); } - QString getStartZone() const { return static_cast(itemMap.value("start_zone"))->getData(); } - QString getTargetZone() const { return static_cast(itemMap.value("target_zone"))->getData(); } + SideboardPlan(const QString &_name = QString(), const QList &_moveList = QList()); + bool readElement(QXmlStreamReader *xml); + void write(QXmlStreamWriter *xml); + + QString getName() const { return name; } + const QList &getMoveList() const { return moveList; } + void setMoveList(const QList &_moveList); }; -class SideboardPlan : public SerializableItem_Map { -public: - SideboardPlan(const QString &_name = QString(), const QList &_moveList = QList()); - static SerializableItem *newItem() { return new SideboardPlan; } - QString getName() const { return static_cast(itemMap.value("name"))->getData(); } - QList getMoveList() const { return typecastItemList(); } - void setMoveList(const QList &_moveList); -}; +enum DeckSortMethod { ByNumber, ByName, ByPrice }; class AbstractDecklistNode { protected: - InnerDecklistNode *parent; - AbstractDecklistNode *currentItem; + InnerDecklistNode *parent; + DeckSortMethod sortMethod; public: - AbstractDecklistNode(InnerDecklistNode *_parent = 0); - virtual ~AbstractDecklistNode() { } - virtual QString getName() const = 0; - InnerDecklistNode *getParent() const { return parent; } - int depth() const; - virtual int height() const = 0; - virtual bool compare(AbstractDecklistNode *other) const = 0; - - virtual bool readElement(QXmlStreamReader *xml) = 0; - virtual void writeElement(QXmlStreamWriter *xml) = 0; + AbstractDecklistNode(InnerDecklistNode *_parent = 0); + virtual ~AbstractDecklistNode() { } + virtual void setSortMethod(DeckSortMethod method) { sortMethod = method; } + virtual QString getName() const = 0; + InnerDecklistNode *getParent() const { return parent; } + int depth() const; + virtual int height() const = 0; + virtual bool compare(AbstractDecklistNode *other) const = 0; + + virtual bool readElement(QXmlStreamReader *xml) = 0; + virtual void writeElement(QXmlStreamWriter *xml) = 0; }; class InnerDecklistNode : public AbstractDecklistNode, public QList { private: - QString name; - class compareFunctor; + QString name; + class compareFunctor; public: - InnerDecklistNode(const QString &_name = QString(), InnerDecklistNode *_parent = 0) : AbstractDecklistNode(_parent), name(_name) { } - InnerDecklistNode(InnerDecklistNode *other, InnerDecklistNode *_parent = 0); - virtual ~InnerDecklistNode(); - QString getName() const { return name; } - void setName(const QString &_name) { name = _name; } - static QString visibleNameFromName(const QString &_name); - virtual QString getVisibleName() const; - void clearTree(); - AbstractDecklistNode *findChild(const QString &name); - int height() const; - int recursiveCount(bool countTotalCards = false) const; + InnerDecklistNode(const QString &_name = QString(), InnerDecklistNode *_parent = 0) : AbstractDecklistNode(_parent), name(_name) { } + InnerDecklistNode(InnerDecklistNode *other, InnerDecklistNode *_parent = 0); + virtual ~InnerDecklistNode(); + void setSortMethod(DeckSortMethod method); + QString getName() const { return name; } + void setName(const QString &_name) { name = _name; } + static QString visibleNameFromName(const QString &_name); + virtual QString getVisibleName() const; + void clearTree(); + AbstractDecklistNode *findChild(const QString &name); + int height() const; + int recursiveCount(bool countTotalCards = false) const; float recursivePrice(bool countTotalCards = false) const; - bool compare(AbstractDecklistNode *other) const; - QVector > sort(Qt::SortOrder order = Qt::AscendingOrder); - - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); + bool compare(AbstractDecklistNode *other) const; + bool compareNumber(AbstractDecklistNode *other) const; + bool compareName(AbstractDecklistNode *other) const; + bool comparePrice(AbstractDecklistNode *other) const; + QVector > sort(Qt::SortOrder order = Qt::AscendingOrder); + + bool readElement(QXmlStreamReader *xml); + void writeElement(QXmlStreamWriter *xml); }; class AbstractDecklistCardNode : public AbstractDecklistNode { public: - AbstractDecklistCardNode(InnerDecklistNode *_parent = 0) : AbstractDecklistNode(_parent) { } - virtual int getNumber() const = 0; - virtual void setNumber(int _number) = 0; - virtual QString getName() const = 0; - virtual void setName(const QString &_name) = 0; + AbstractDecklistCardNode(InnerDecklistNode *_parent = 0) : AbstractDecklistNode(_parent) { } + virtual int getNumber() const = 0; + virtual void setNumber(int _number) = 0; + virtual QString getName() const = 0; + virtual void setName(const QString &_name) = 0; virtual float getPrice() const = 0; - virtual void setPrice(float _price) = 0; + virtual void setPrice(const float _price) = 0; float getTotalPrice() const { return getNumber() * getPrice(); } - int height() const { return 0; } - bool compare(AbstractDecklistNode *other) const; - - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); + int height() const { return 0; } + bool compare(AbstractDecklistNode *other) const; + bool compareNumber(AbstractDecklistNode *other) const; + bool compareName(AbstractDecklistNode *other) const; + bool compareTotalPrice(AbstractDecklistNode *other) const; + + bool readElement(QXmlStreamReader *xml); + void writeElement(QXmlStreamWriter *xml); }; class DecklistCardNode : public AbstractDecklistCardNode { private: - QString name; - int number; + QString name; + int number; float price; public: DecklistCardNode(const QString &_name = QString(), int _number = 1, float _price = 0, InnerDecklistNode *_parent = 0) : AbstractDecklistCardNode(_parent), name(_name), number(_number), price(_price) { } DecklistCardNode(const QString &_name = QString(), int _number = 1, InnerDecklistNode *_parent = 0) : AbstractDecklistCardNode(_parent), name(_name), number(_number), price(0) { } - DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent); - int getNumber() const { return number; } - void setNumber(int _number) { number = _number; } - QString getName() const { return name; } - void setName(const QString &_name) { name = _name; } + DecklistCardNode(DecklistCardNode *other, InnerDecklistNode *_parent); + int getNumber() const { return number; } + void setNumber(int _number) { number = _number; } + QString getName() const { return name; } + void setName(const QString &_name) { name = _name; } float getPrice() const { return price; } + void setPrice(const float _price) { price = _price; } - }; +}; -class DeckList : public SerializableItem { - Q_OBJECT -public: - enum FileFormat { PlainTextFormat, CockatriceFormat }; +class DeckList : public QObject { + Q_OBJECT private: - QString name, comments; - QString lastFileName; - QString deckHash; - FileFormat lastFileFormat; - QMap sideboardPlans; - InnerDecklistNode *root; - InnerDecklistNode *currentZone; - SideboardPlan *currentSideboardPlan; - QString currentElementText; - void getCardListHelper(InnerDecklistNode *node, QSet &result) const; + QString name, comments; + QString deckHash; + QMap sideboardPlans; + InnerDecklistNode *root; + void getCardListHelper(InnerDecklistNode *node, QSet &result) const; signals: - void deckLoaded(); - void deckHashChanged(); + void deckHashChanged(); public slots: - void setName(const QString &_name = QString()) { name = _name; } - void setComments(const QString &_comments = QString()) { comments = _comments; } + void setName(const QString &_name = QString()) { name = _name; } + void setComments(const QString &_comments = QString()) { comments = _comments; } public: - static const QStringList fileNameFilters; - DeckList(); - DeckList(DeckList *other); - ~DeckList(); - QString getName() const { return name; } - QString getComments() const { return comments; } - QString getLastFileName() const { return lastFileName; } - FileFormat getLastFileFormat() const { return lastFileFormat; } - QList getCurrentSideboardPlan(); - void setCurrentSideboardPlan(const QList &plan); - const QMap &getSideboardPlans() const { return sideboardPlans; } + DeckList(); + DeckList(const DeckList &other); + DeckList(const QString &nativeString); + ~DeckList(); + QString getName() const { return name; } + QString getComments() const { return comments; } + QList getCurrentSideboardPlan(); + void setCurrentSideboardPlan(const QList &plan); + const QMap &getSideboardPlans() const { return sideboardPlans; } - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); - void loadFromXml(QXmlStreamReader *xml); - - bool loadFromFile_Native(QIODevice *device); - bool saveToFile_Native(QIODevice *device); - bool loadFromStream_Plain(QTextStream &stream); - bool loadFromFile_Plain(QIODevice *device); - bool saveToStream_Plain(QTextStream &stream); - bool saveToFile_Plain(QIODevice *device); - bool loadFromFile(const QString &fileName, FileFormat fmt); - bool saveToFile(const QString &fileName, FileFormat fmt); - static FileFormat getFormatFromNameFilter(const QString &selectedNameFilter); + bool readElement(QXmlStreamReader *xml); + void write(QXmlStreamWriter *xml); + bool loadFromXml(QXmlStreamReader *xml); + bool loadFromString_Native(const QString &nativeString); + QString writeToString_Native(); + bool loadFromFile_Native(QIODevice *device); + bool saveToFile_Native(QIODevice *device); + bool loadFromStream_Plain(QTextStream &stream); + bool loadFromFile_Plain(QIODevice *device); + bool saveToStream_Plain(QTextStream &stream); + bool saveToFile_Plain(QIODevice *device); + QString writeToString_Plain(); - void cleanList(); - bool isEmpty() const { return root->isEmpty() && name.isEmpty() && comments.isEmpty() && sideboardPlans.isEmpty(); } - QStringList getCardList() const; - - QString getDeckHash() const { return deckHash; } - void updateDeckHash(); + void cleanList(); + bool isEmpty() const { return root->isEmpty() && name.isEmpty() && comments.isEmpty() && sideboardPlans.isEmpty(); } + QStringList getCardList() const; - InnerDecklistNode *getRoot() const { return root; } - DecklistCardNode *addCard(const QString &cardName, const QString &zoneName); - bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = 0); + int getSideboardSize() const; + + QString getDeckHash() const { return deckHash; } + void updateDeckHash(); + + InnerDecklistNode *getRoot() const { return root; } + DecklistCardNode *addCard(const QString &cardName, const QString &zoneName); + bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = 0); + + /** + * Calls a given function object for each card in the deck. It must + * take a InnerDecklistNode* as its first argument and a + * DecklistCardNode* as its second. + */ + template + void forEachCard(Callback &callback) const { + // Support for this is only possible if the internal structure + // doesn't get more complicated. + for (int i = 0; i < root->size(); i++) { + const InnerDecklistNode *node = + dynamic_cast(root->at(i)); + for (int j = 0; j < node->size(); j++) { + const DecklistCardNode *card = + dynamic_cast( + node->at(j) + ); + callback(node, card); + } + } + } }; #endif diff --git a/common/get_pb_extension.cpp b/common/get_pb_extension.cpp new file mode 100644 index 000000000..1e980f4b6 --- /dev/null +++ b/common/get_pb_extension.cpp @@ -0,0 +1,13 @@ +#include "get_pb_extension.h" +#include +#include + +int getPbExtension(const ::google::protobuf::Message &message) +{ + std::vector< const ::google::protobuf::FieldDescriptor * > fieldList; + message.GetReflection()->ListFields(message, &fieldList); + for (unsigned int j = 0; j < fieldList.size(); ++j) + if (fieldList[j]->is_extension()) + return fieldList[j]->number(); + return -1; +} diff --git a/common/get_pb_extension.h b/common/get_pb_extension.h new file mode 100644 index 000000000..bebef39ee --- /dev/null +++ b/common/get_pb_extension.h @@ -0,0 +1,12 @@ +#ifndef GET_PB_EXTENSION_H +#define GET_PB_EXTENSION_H + +namespace google { + namespace protobuf { + class Message; + } +} + +int getPbExtension(const ::google::protobuf::Message &message); + +#endif diff --git a/common/pb/CMakeLists.txt b/common/pb/CMakeLists.txt new file mode 100644 index 000000000..7b0051573 --- /dev/null +++ b/common/pb/CMakeLists.txt @@ -0,0 +1,163 @@ +# CMakeLists for common directory +# +# provides the protobuf interfaces + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +SET(PROTO_FILES + admin_commands.proto + card_attributes.proto + color.proto + command_attach_card.proto + command_change_zone_properties.proto + command_concede.proto + command_create_arrow.proto + command_create_counter.proto + command_create_token.proto + command_deck_del_dir.proto + command_deck_del.proto + command_deck_download.proto + command_deck_list.proto + command_deck_new_dir.proto + command_deck_select.proto + command_deck_upload.proto + command_del_counter.proto + command_delete_arrow.proto + command_draw_cards.proto + command_dump_zone.proto + command_flip_card.proto + command_game_say.proto + command_inc_card_counter.proto + command_inc_counter.proto + command_kick_from_game.proto + command_leave_game.proto + command_move_card.proto + command_mulligan.proto + command_next_turn.proto + command_ready_start.proto + command_replay_delete_match.proto + command_replay_list.proto + command_replay_download.proto + command_replay_modify_match.proto + command_reveal_cards.proto + command_roll_die.proto + command_set_active_phase.proto + command_set_card_attr.proto + command_set_card_counter.proto + command_set_counter.proto + command_set_sideboard_plan.proto + command_set_sideboard_lock.proto + command_shuffle.proto + commands.proto + command_stop_dump_zone.proto + command_undo_draw.proto + context_concede.proto + context_connection_state_changed.proto + context_deck_select.proto + context_move_card.proto + context_mulligan.proto + context_ping_changed.proto + context_ready_start.proto + context_set_sideboard_lock.proto + context_undo_draw.proto + event_add_to_list.proto + event_attach_card.proto + event_change_zone_properties.proto + event_connection_closed.proto + event_create_arrow.proto + event_create_counter.proto + event_create_token.proto + event_del_counter.proto + event_delete_arrow.proto + event_destroy_card.proto + event_draw_cards.proto + event_dump_zone.proto + event_flip_card.proto + event_game_closed.proto + event_game_host_changed.proto + event_game_joined.proto + event_game_say.proto + event_game_state_changed.proto + event_join.proto + event_join_room.proto + event_kicked.proto + event_leave.proto + event_leave_room.proto + event_list_games.proto + event_list_rooms.proto + event_move_card.proto + event_player_properties_changed.proto + event_remove_from_list.proto + event_replay_added.proto + event_reveal_cards.proto + event_roll_die.proto + event_room_say.proto + event_server_complete_list.proto + event_server_identification.proto + event_server_message.proto + event_server_shutdown.proto + event_set_active_phase.proto + event_set_active_player.proto + event_set_card_attr.proto + event_set_card_counter.proto + event_set_counter.proto + event_shuffle.proto + event_stop_dump_zone.proto + event_user_joined.proto + event_user_left.proto + event_user_message.proto + game_commands.proto + game_event_container.proto + game_event_context.proto + game_event.proto + game_replay.proto + isl_message.proto + moderator_commands.proto + move_card_to_zone.proto + response_deck_download.proto + response_deck_list.proto + response_deck_upload.proto + response_dump_zone.proto + response_get_games_of_user.proto + response_get_user_info.proto + response_join_room.proto + response_list_users.proto + response_login.proto + response_replay_download.proto + response_replay_list.proto + response.proto + room_commands.proto + room_event.proto + serverinfo_arrow.proto + serverinfo_cardcounter.proto + serverinfo_card.proto + serverinfo_counter.proto + serverinfo_deckstorage.proto + serverinfo_game.proto + serverinfo_gametype.proto + serverinfo_playerping.proto + serverinfo_playerproperties.proto + serverinfo_player.proto + serverinfo_replay.proto + serverinfo_replay_match.proto + serverinfo_room.proto + serverinfo_user.proto + serverinfo_zone.proto + server_message.proto + session_commands.proto + session_event.proto +) + +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ${PROTO_FILES}) + +add_library(cockatrice_protocol ${PROTO_SRCS} ${PROTO_HDRS}) +set(cockatrice_protocol_LIBS ${PROTOBUF_LIBRARIES}) +if (MSVC) + set(cockatrice_protocol_LIBS ${cockatrice_protocol_LIBS} -lprotobuf) +endif (MSVC) +if (UNIX) + set(cockatrice_protocol_LIBS ${cockatrice_protocol_LIBS} -lpthread) +endif (UNIX) +target_link_libraries(cockatrice_protocol ${cockatrice_protocol_LIBS}) diff --git a/common/pb/admin_commands.proto b/common/pb/admin_commands.proto new file mode 100644 index 000000000..233ec5325 --- /dev/null +++ b/common/pb/admin_commands.proto @@ -0,0 +1,21 @@ +message AdminCommand { + enum AdminCommandType { + UPDATE_SERVER_MESSAGE = 1000; + SHUTDOWN_SERVER = 1001; + } + extensions 100 to max; +} + +message Command_UpdateServerMessage { + extend AdminCommand { + optional Command_UpdateServerMessage ext = 1000; + } +} + +message Command_ShutdownServer { + extend AdminCommand { + optional Command_ShutdownServer ext = 1001; + } + optional string reason = 1; + optional uint32 minutes = 2; +} diff --git a/common/pb/card_attributes.proto b/common/pb/card_attributes.proto new file mode 100644 index 000000000..d7a9d1e0d --- /dev/null +++ b/common/pb/card_attributes.proto @@ -0,0 +1,9 @@ +enum CardAttribute { + AttrTapped = 1; + AttrAttacking = 2; + AttrFaceDown = 3; + AttrColor = 4; + AttrPT = 5; + AttrAnnotation = 6; + AttrDoesntUntap = 7; +} diff --git a/common/pb/color.proto b/common/pb/color.proto new file mode 100644 index 000000000..90fdb7818 --- /dev/null +++ b/common/pb/color.proto @@ -0,0 +1,6 @@ +message color { + optional uint32 r = 1; + optional uint32 g = 2; + optional uint32 b = 3; + optional uint32 a = 4; +} \ No newline at end of file diff --git a/common/pb/command_attach_card.proto b/common/pb/command_attach_card.proto new file mode 100644 index 000000000..02fbb9ce3 --- /dev/null +++ b/common/pb/command_attach_card.proto @@ -0,0 +1,13 @@ +import "game_commands.proto"; +message Command_AttachCard { + extend GameCommand { + optional Command_AttachCard ext = 1009; + } + optional string start_zone = 1; + optional sint32 card_id = 2 [default = -1]; + optional sint32 target_player_id = 3 [default = -1]; + optional string target_zone = 4; + optional sint32 target_card_id = 5 [default = -1]; +} + + diff --git a/common/pb/command_change_zone_properties.proto b/common/pb/command_change_zone_properties.proto new file mode 100644 index 000000000..5bbc29001 --- /dev/null +++ b/common/pb/command_change_zone_properties.proto @@ -0,0 +1,10 @@ +import "game_commands.proto"; + +message Command_ChangeZoneProperties { + extend GameCommand { + optional Command_ChangeZoneProperties ext = 1031; + } + optional string zone_name = 1; + + optional bool always_reveal_top_card = 10; +} diff --git a/common/pb/command_concede.proto b/common/pb/command_concede.proto new file mode 100644 index 000000000..22028af2b --- /dev/null +++ b/common/pb/command_concede.proto @@ -0,0 +1,6 @@ +import "game_commands.proto"; +message Command_Concede { + extend GameCommand { + optional Command_Concede ext = 1017; + } +} diff --git a/common/pb/command_create_arrow.proto b/common/pb/command_create_arrow.proto new file mode 100644 index 000000000..31c29c5ba --- /dev/null +++ b/common/pb/command_create_arrow.proto @@ -0,0 +1,15 @@ +import "game_commands.proto"; +import "color.proto"; + +message Command_CreateArrow { + extend GameCommand { + optional Command_CreateArrow ext = 1011; + } + optional sint32 start_player_id = 1 [default = -1]; + optional string start_zone = 2; + optional sint32 start_card_id = 3 [default = -1]; + optional sint32 target_player_id = 4 [default = -1]; + optional string target_zone = 5; + optional sint32 target_card_id = 6 [default = -1]; + optional color arrow_color = 7; +} diff --git a/common/pb/command_create_counter.proto b/common/pb/command_create_counter.proto new file mode 100644 index 000000000..047b65517 --- /dev/null +++ b/common/pb/command_create_counter.proto @@ -0,0 +1,12 @@ +import "game_commands.proto"; +import "color.proto"; + +message Command_CreateCounter { + extend GameCommand { + optional Command_CreateCounter ext = 1019; + } + optional string counter_name = 1; + optional color counter_color = 2; + optional uint32 radius = 3; + optional sint32 value = 4; +} diff --git a/common/pb/command_create_token.proto b/common/pb/command_create_token.proto new file mode 100644 index 000000000..574fcb21c --- /dev/null +++ b/common/pb/command_create_token.proto @@ -0,0 +1,16 @@ +import "game_commands.proto"; +message Command_CreateToken { + extend GameCommand { + optional Command_CreateToken ext = 1010; + } + optional string zone = 1; + optional string card_name = 2; + optional string color = 3; + optional string pt = 4; + optional string annotation = 5; + optional bool destroy_on_zone_change = 6; + optional sint32 x = 7; + optional sint32 y = 8; +} + + diff --git a/common/pb/command_deck_del.proto b/common/pb/command_deck_del.proto new file mode 100644 index 000000000..261ad630b --- /dev/null +++ b/common/pb/command_deck_del.proto @@ -0,0 +1,8 @@ +import "session_commands.proto"; + +message Command_DeckDel { + extend SessionCommand { + optional Command_DeckDel ext = 1011; + } + optional sint32 deck_id = 1 [default = -1]; +} diff --git a/common/pb/command_deck_del_dir.proto b/common/pb/command_deck_del_dir.proto new file mode 100644 index 000000000..2eb80f7c8 --- /dev/null +++ b/common/pb/command_deck_del_dir.proto @@ -0,0 +1,9 @@ +import "session_commands.proto"; + +message Command_DeckDelDir { + extend SessionCommand { + optional Command_DeckDelDir ext = 1010; + } + optional string path = 1; +} + diff --git a/common/pb/command_deck_download.proto b/common/pb/command_deck_download.proto new file mode 100644 index 000000000..822754caa --- /dev/null +++ b/common/pb/command_deck_download.proto @@ -0,0 +1,9 @@ +import "session_commands.proto"; + +message Command_DeckDownload { + extend SessionCommand { + optional Command_DeckDownload ext = 1012; + } + optional sint32 deck_id = 1 [default = -1]; +} + diff --git a/common/pb/command_deck_list.proto b/common/pb/command_deck_list.proto new file mode 100644 index 000000000..a1e992088 --- /dev/null +++ b/common/pb/command_deck_list.proto @@ -0,0 +1,7 @@ +import "session_commands.proto"; + +message Command_DeckList { + extend SessionCommand { + optional Command_DeckList ext = 1008; + } +} diff --git a/common/pb/command_deck_new_dir.proto b/common/pb/command_deck_new_dir.proto new file mode 100644 index 000000000..a88d15136 --- /dev/null +++ b/common/pb/command_deck_new_dir.proto @@ -0,0 +1,10 @@ +import "session_commands.proto"; + +message Command_DeckNewDir { + extend SessionCommand { + optional Command_DeckNewDir ext = 1009; + } + optional string path = 1; + optional string dir_name = 2; +} + \ No newline at end of file diff --git a/common/pb/command_deck_select.proto b/common/pb/command_deck_select.proto new file mode 100644 index 000000000..e696dae01 --- /dev/null +++ b/common/pb/command_deck_select.proto @@ -0,0 +1,8 @@ +import "game_commands.proto"; +message Command_DeckSelect { + extend GameCommand { + optional Command_DeckSelect ext = 1029; + } + optional string deck = 1; + optional sint32 deck_id = 2 [default = -1]; +} diff --git a/common/pb/command_deck_upload.proto b/common/pb/command_deck_upload.proto new file mode 100644 index 000000000..41f43ab4f --- /dev/null +++ b/common/pb/command_deck_upload.proto @@ -0,0 +1,10 @@ +import "session_commands.proto"; + +message Command_DeckUpload { + extend SessionCommand { + optional Command_DeckUpload ext = 1013; + } + optional string path = 1; // to upload a new deck + optional uint32 deck_id = 2; // to replace an existing deck + optional string deck_list = 3; +} diff --git a/common/pb/command_del_counter.proto b/common/pb/command_del_counter.proto new file mode 100644 index 000000000..1e59b3364 --- /dev/null +++ b/common/pb/command_del_counter.proto @@ -0,0 +1,7 @@ +import "game_commands.proto"; +message Command_DelCounter { + extend GameCommand { + optional Command_DelCounter ext = 1021; + } + optional sint32 counter_id = 1 [default = -1]; +} diff --git a/common/pb/command_delete_arrow.proto b/common/pb/command_delete_arrow.proto new file mode 100644 index 000000000..ffccacf00 --- /dev/null +++ b/common/pb/command_delete_arrow.proto @@ -0,0 +1,7 @@ +import "game_commands.proto"; +message Command_DeleteArrow { + extend GameCommand { + optional Command_DeleteArrow ext = 1012; + } + optional sint32 arrow_id = 1 [default = -1]; +} diff --git a/common/pb/command_draw_cards.proto b/common/pb/command_draw_cards.proto new file mode 100644 index 000000000..2873d9150 --- /dev/null +++ b/common/pb/command_draw_cards.proto @@ -0,0 +1,8 @@ +import "game_commands.proto"; +message Command_DrawCards { + extend GameCommand { + optional Command_DrawCards ext = 1006; + } + optional uint32 number = 1; +} + diff --git a/common/pb/command_dump_zone.proto b/common/pb/command_dump_zone.proto new file mode 100644 index 000000000..15fbe3c01 --- /dev/null +++ b/common/pb/command_dump_zone.proto @@ -0,0 +1,9 @@ +import "game_commands.proto"; +message Command_DumpZone { + extend GameCommand { + optional Command_DumpZone ext = 1024; + } + optional sint32 player_id = 1 [default = -1]; + optional string zone_name = 2; + optional sint32 number_cards = 3; +} diff --git a/common/pb/command_flip_card.proto b/common/pb/command_flip_card.proto new file mode 100644 index 000000000..25692f9cf --- /dev/null +++ b/common/pb/command_flip_card.proto @@ -0,0 +1,11 @@ +import "game_commands.proto"; +message Command_FlipCard { + extend GameCommand { + optional Command_FlipCard ext = 1008; + } + optional string zone = 1; + optional sint32 card_id = 2 [default = -1]; + optional bool face_down = 3; +} + + diff --git a/common/pb/command_game_say.proto b/common/pb/command_game_say.proto new file mode 100644 index 000000000..906bd6f1f --- /dev/null +++ b/common/pb/command_game_say.proto @@ -0,0 +1,9 @@ +import "game_commands.proto"; +message Command_GameSay { + extend GameCommand { + optional Command_GameSay ext = 1002; + } + optional string message = 1; +} + + diff --git a/common/pb/command_inc_card_counter.proto b/common/pb/command_inc_card_counter.proto new file mode 100644 index 000000000..e8b5f1cad --- /dev/null +++ b/common/pb/command_inc_card_counter.proto @@ -0,0 +1,10 @@ +import "game_commands.proto"; +message Command_IncCardCounter { + extend GameCommand { + optional Command_IncCardCounter ext = 1015; + } + optional string zone = 1; + optional sint32 card_id = 2 [default = -1]; + optional sint32 counter_id = 3 [default = -1]; + optional sint32 counter_delta = 4; +} diff --git a/common/pb/command_inc_counter.proto b/common/pb/command_inc_counter.proto new file mode 100644 index 000000000..a14a2801f --- /dev/null +++ b/common/pb/command_inc_counter.proto @@ -0,0 +1,8 @@ +import "game_commands.proto"; +message Command_IncCounter { + extend GameCommand { + optional Command_IncCounter ext = 1018; + } + optional sint32 counter_id = 1 [default = -1]; + optional sint32 delta = 2; +} diff --git a/common/pb/command_kick_from_game.proto b/common/pb/command_kick_from_game.proto new file mode 100644 index 000000000..b340cb070 --- /dev/null +++ b/common/pb/command_kick_from_game.proto @@ -0,0 +1,8 @@ +import "game_commands.proto"; +message Command_KickFromGame { + extend GameCommand { + optional Command_KickFromGame ext = 1000; +} + optional sint32 player_id = 1 [default = -1]; +} + diff --git a/common/pb/command_leave_game.proto b/common/pb/command_leave_game.proto new file mode 100644 index 000000000..cdae43af9 --- /dev/null +++ b/common/pb/command_leave_game.proto @@ -0,0 +1,7 @@ +import "game_commands.proto"; +message Command_LeaveGame { + extend GameCommand { + optional Command_LeaveGame ext = 1001; + } +} + diff --git a/common/pb/command_move_card.proto b/common/pb/command_move_card.proto new file mode 100644 index 000000000..315d64235 --- /dev/null +++ b/common/pb/command_move_card.proto @@ -0,0 +1,25 @@ +import "game_commands.proto"; +message CardToMove { + optional sint32 card_id = 1 [default = -1]; + optional bool face_down = 2; + optional string pt = 3; + optional bool tapped = 4; +} + +message ListOfCardsToMove { + repeated CardToMove card = 1; +} + +message Command_MoveCard { + extend GameCommand { + optional Command_MoveCard ext = 1027; + } + optional sint32 start_player_id = 1 [default = -1]; + optional string start_zone = 2; + optional ListOfCardsToMove cards_to_move = 3; + optional sint32 target_player_id = 4 [default = -1]; + optional string target_zone = 5; + optional sint32 x = 6 [default = -1]; + optional sint32 y = 7 [default = -1]; +} + diff --git a/common/pb/command_mulligan.proto b/common/pb/command_mulligan.proto new file mode 100644 index 000000000..79c3fde26 --- /dev/null +++ b/common/pb/command_mulligan.proto @@ -0,0 +1,8 @@ +import "game_commands.proto"; +message Command_Mulligan { + extend GameCommand { + optional Command_Mulligan ext = 1004; + } +} + + diff --git a/common/pb/command_next_turn.proto b/common/pb/command_next_turn.proto new file mode 100644 index 000000000..d3012df8e --- /dev/null +++ b/common/pb/command_next_turn.proto @@ -0,0 +1,6 @@ +import "game_commands.proto"; +message Command_NextTurn { + extend GameCommand { + optional Command_NextTurn ext = 1022; + } +} diff --git a/common/pb/command_ready_start.proto b/common/pb/command_ready_start.proto new file mode 100644 index 000000000..5f797a0f2 --- /dev/null +++ b/common/pb/command_ready_start.proto @@ -0,0 +1,7 @@ +import "game_commands.proto"; +message Command_ReadyStart { + extend GameCommand { + optional Command_ReadyStart ext = 1016; + } + optional bool ready = 1; +} diff --git a/common/pb/command_replay_delete_match.proto b/common/pb/command_replay_delete_match.proto new file mode 100644 index 000000000..841e7cfca --- /dev/null +++ b/common/pb/command_replay_delete_match.proto @@ -0,0 +1,8 @@ +import "session_commands.proto"; + +message Command_ReplayDeleteMatch { + extend SessionCommand { + optional Command_ReplayDeleteMatch ext = 1103; + } + optional sint32 game_id = 1 [default = -1]; +} diff --git a/common/pb/command_replay_download.proto b/common/pb/command_replay_download.proto new file mode 100644 index 000000000..ff7e2b34e --- /dev/null +++ b/common/pb/command_replay_download.proto @@ -0,0 +1,8 @@ +import "session_commands.proto"; + +message Command_ReplayDownload { + extend SessionCommand { + optional Command_ReplayDownload ext = 1101; + } + optional sint32 replay_id = 1 [default = -1]; +} diff --git a/common/pb/command_replay_list.proto b/common/pb/command_replay_list.proto new file mode 100644 index 000000000..2dc0e92e3 --- /dev/null +++ b/common/pb/command_replay_list.proto @@ -0,0 +1,7 @@ +import "session_commands.proto"; + +message Command_ReplayList { + extend SessionCommand { + optional Command_ReplayList ext = 1100; + } +} diff --git a/common/pb/command_replay_modify_match.proto b/common/pb/command_replay_modify_match.proto new file mode 100644 index 000000000..28e0fc0a7 --- /dev/null +++ b/common/pb/command_replay_modify_match.proto @@ -0,0 +1,10 @@ +import "session_commands.proto"; + +message Command_ReplayModifyMatch { + extend SessionCommand { + optional Command_ReplayModifyMatch ext = 1102; + } + optional sint32 game_id = 1 [default = -1]; + optional bool do_not_hide = 2; +} + diff --git a/common/pb/command_reveal_cards.proto b/common/pb/command_reveal_cards.proto new file mode 100644 index 000000000..af108abce --- /dev/null +++ b/common/pb/command_reveal_cards.proto @@ -0,0 +1,10 @@ +import "game_commands.proto"; +message Command_RevealCards { + extend GameCommand { + optional Command_RevealCards ext = 1026; + } + optional string zone_name = 1; + optional sint32 card_id = 2 [default = -1]; + optional sint32 player_id = 3 [default = -1]; + optional bool grant_write_access = 4; +} diff --git a/common/pb/command_roll_die.proto b/common/pb/command_roll_die.proto new file mode 100644 index 000000000..f5d9c72ea --- /dev/null +++ b/common/pb/command_roll_die.proto @@ -0,0 +1,8 @@ +import "game_commands.proto"; +message Command_RollDie { + extend GameCommand { + optional Command_RollDie ext = 1005; + } + optional uint32 sides = 1; +} + diff --git a/common/pb/command_set_active_phase.proto b/common/pb/command_set_active_phase.proto new file mode 100644 index 000000000..66404cd21 --- /dev/null +++ b/common/pb/command_set_active_phase.proto @@ -0,0 +1,7 @@ +import "game_commands.proto"; +message Command_SetActivePhase { + extend GameCommand { + optional Command_SetActivePhase ext = 1023; + } + optional uint32 phase = 1; +} diff --git a/common/pb/command_set_card_attr.proto b/common/pb/command_set_card_attr.proto new file mode 100644 index 000000000..b8a8fb602 --- /dev/null +++ b/common/pb/command_set_card_attr.proto @@ -0,0 +1,12 @@ +import "game_commands.proto"; +import "card_attributes.proto"; + +message Command_SetCardAttr { + extend GameCommand { + optional Command_SetCardAttr ext = 1013; + } + optional string zone = 1; + optional sint32 card_id = 2 [default = -1]; + optional CardAttribute attribute = 3; + optional string attr_value = 4; +} diff --git a/common/pb/command_set_card_counter.proto b/common/pb/command_set_card_counter.proto new file mode 100644 index 000000000..b258a8616 --- /dev/null +++ b/common/pb/command_set_card_counter.proto @@ -0,0 +1,10 @@ +import "game_commands.proto"; +message Command_SetCardCounter { + extend GameCommand { + optional Command_SetCardCounter ext = 1014; + } + optional string zone = 1; + optional sint32 card_id = 2 [default = -1]; + optional sint32 counter_id = 3 [default = -1]; + optional sint32 counter_value = 4; +} diff --git a/common/pb/command_set_counter.proto b/common/pb/command_set_counter.proto new file mode 100644 index 000000000..a2f34cc2f --- /dev/null +++ b/common/pb/command_set_counter.proto @@ -0,0 +1,8 @@ +import "game_commands.proto"; +message Command_SetCounter { + extend GameCommand { + optional Command_SetCounter ext = 1020; + } + optional sint32 counter_id = 1 [default = -1]; + optional sint32 value = 2; +} diff --git a/common/pb/command_set_sideboard_lock.proto b/common/pb/command_set_sideboard_lock.proto new file mode 100644 index 000000000..1f9e8ba14 --- /dev/null +++ b/common/pb/command_set_sideboard_lock.proto @@ -0,0 +1,7 @@ + import "game_commands.proto"; +message Command_SetSideboardLock { + extend GameCommand { + optional Command_SetSideboardLock ext = 1030; + } + optional bool locked = 1; +} diff --git a/common/pb/command_set_sideboard_plan.proto b/common/pb/command_set_sideboard_plan.proto new file mode 100644 index 000000000..09061c8b2 --- /dev/null +++ b/common/pb/command_set_sideboard_plan.proto @@ -0,0 +1,10 @@ +import "game_commands.proto"; +import "move_card_to_zone.proto"; + +message Command_SetSideboardPlan { + extend GameCommand { + optional Command_SetSideboardPlan ext = 1028; + } + repeated MoveCard_ToZone move_list = 1; +} + diff --git a/common/pb/command_shuffle.proto b/common/pb/command_shuffle.proto new file mode 100644 index 000000000..26ee0422c --- /dev/null +++ b/common/pb/command_shuffle.proto @@ -0,0 +1,7 @@ +import "game_commands.proto"; +message Command_Shuffle { + extend GameCommand { + optional Command_Shuffle ext = 1003; + } +} + diff --git a/common/pb/command_stop_dump_zone.proto b/common/pb/command_stop_dump_zone.proto new file mode 100644 index 000000000..1a7539c1c --- /dev/null +++ b/common/pb/command_stop_dump_zone.proto @@ -0,0 +1,8 @@ +import "game_commands.proto"; +message Command_StopDumpZone { + extend GameCommand { + optional Command_StopDumpZone ext = 1025; + } + optional sint32 player_id = 1; + optional string zone_name = 2; +} diff --git a/common/pb/command_undo_draw.proto b/common/pb/command_undo_draw.proto new file mode 100644 index 000000000..f51cb46c0 --- /dev/null +++ b/common/pb/command_undo_draw.proto @@ -0,0 +1,6 @@ +import "game_commands.proto"; +message Command_UndoDraw { + extend GameCommand { + optional Command_UndoDraw ext = 1007; + } +} diff --git a/common/pb/commands.proto b/common/pb/commands.proto new file mode 100644 index 000000000..3796190d2 --- /dev/null +++ b/common/pb/commands.proto @@ -0,0 +1,18 @@ +import "session_commands.proto"; +import "game_commands.proto"; +import "room_commands.proto"; +import "moderator_commands.proto"; +import "admin_commands.proto"; + +message CommandContainer { + optional uint64 cmd_id = 1; + + optional uint32 game_id = 10; + optional uint32 room_id = 20; + + repeated SessionCommand session_command = 100; + repeated GameCommand game_command = 101; + repeated RoomCommand room_command = 102; + repeated ModeratorCommand moderator_command = 103; + repeated AdminCommand admin_command = 104; +} diff --git a/common/pb/context_concede.proto b/common/pb/context_concede.proto new file mode 100644 index 000000000..2ece178f7 --- /dev/null +++ b/common/pb/context_concede.proto @@ -0,0 +1,7 @@ +import "game_event_context.proto"; + +message Context_Concede { + extend GameEventContext { + optional Context_Concede ext = 1001; + } +} diff --git a/common/pb/context_connection_state_changed.proto b/common/pb/context_connection_state_changed.proto new file mode 100644 index 000000000..d1f592ee5 --- /dev/null +++ b/common/pb/context_connection_state_changed.proto @@ -0,0 +1,7 @@ +import "game_event_context.proto"; + +message Context_ConnectionStateChanged { + extend GameEventContext { + optional Context_ConnectionStateChanged ext = 1007; + } +} diff --git a/common/pb/context_deck_select.proto b/common/pb/context_deck_select.proto new file mode 100644 index 000000000..f36c8b6c2 --- /dev/null +++ b/common/pb/context_deck_select.proto @@ -0,0 +1,9 @@ +import "game_event_context.proto"; + +message Context_DeckSelect { + extend GameEventContext { + optional Context_DeckSelect ext = 1002; + } + optional string deck_hash = 1; + optional int32 sideboard_size = 2 [default = -1]; +} diff --git a/common/pb/context_move_card.proto b/common/pb/context_move_card.proto new file mode 100644 index 000000000..d8f7139d6 --- /dev/null +++ b/common/pb/context_move_card.proto @@ -0,0 +1,7 @@ +import "game_event_context.proto"; + +message Context_MoveCard { + extend GameEventContext { + optional Context_MoveCard ext = 1004; + } +} diff --git a/common/pb/context_mulligan.proto b/common/pb/context_mulligan.proto new file mode 100644 index 000000000..d5ef0bcce --- /dev/null +++ b/common/pb/context_mulligan.proto @@ -0,0 +1,8 @@ +import "game_event_context.proto"; + +message Context_Mulligan { + extend GameEventContext { + optional Context_Mulligan ext = 1005; + } + optional uint32 number = 1; +} diff --git a/common/pb/context_ping_changed.proto b/common/pb/context_ping_changed.proto new file mode 100644 index 000000000..8ea43c5f4 --- /dev/null +++ b/common/pb/context_ping_changed.proto @@ -0,0 +1,7 @@ +import "game_event_context.proto"; + +message Context_PingChanged { + extend GameEventContext { + optional Context_PingChanged ext = 1006; + } +} diff --git a/common/pb/context_ready_start.proto b/common/pb/context_ready_start.proto new file mode 100644 index 000000000..ac16ef64a --- /dev/null +++ b/common/pb/context_ready_start.proto @@ -0,0 +1,7 @@ +import "game_event_context.proto"; + +message Context_ReadyStart { + extend GameEventContext { + optional Context_ReadyStart ext = 1000; + } +} diff --git a/common/pb/context_set_sideboard_lock.proto b/common/pb/context_set_sideboard_lock.proto new file mode 100644 index 000000000..c62c5bd1f --- /dev/null +++ b/common/pb/context_set_sideboard_lock.proto @@ -0,0 +1,7 @@ +import "game_event_context.proto"; + +message Context_SetSideboardLock { + extend GameEventContext { + optional Context_SetSideboardLock ext = 1008; + } +} diff --git a/common/pb/context_undo_draw.proto b/common/pb/context_undo_draw.proto new file mode 100644 index 000000000..513aa9ad3 --- /dev/null +++ b/common/pb/context_undo_draw.proto @@ -0,0 +1,7 @@ +import "game_event_context.proto"; + +message Context_UndoDraw { + extend GameEventContext { + optional Context_UndoDraw ext = 1003; + } +} diff --git a/common/pb/event_add_to_list.proto b/common/pb/event_add_to_list.proto new file mode 100644 index 000000000..5f168eea1 --- /dev/null +++ b/common/pb/event_add_to_list.proto @@ -0,0 +1,10 @@ +import "session_event.proto"; +import "serverinfo_user.proto"; + +message Event_AddToList { + extend SessionEvent { + optional Event_AddToList ext = 1005; + } + optional string list_name = 1; + optional ServerInfo_User user_info = 2; +} diff --git a/common/pb/event_attach_card.proto b/common/pb/event_attach_card.proto new file mode 100644 index 000000000..56eef093e --- /dev/null +++ b/common/pb/event_attach_card.proto @@ -0,0 +1,12 @@ +import "game_event.proto"; + +message Event_AttachCard { + extend GameEvent { + optional Event_AttachCard ext = 2012; + } + optional string start_zone = 1; + optional sint32 card_id = 2; + optional sint32 target_player_id = 3; + optional string target_zone = 4; + optional sint32 target_card_id = 5; +} diff --git a/common/pb/event_change_zone_properties.proto b/common/pb/event_change_zone_properties.proto new file mode 100644 index 000000000..7e86fe0ac --- /dev/null +++ b/common/pb/event_change_zone_properties.proto @@ -0,0 +1,10 @@ +import "game_event.proto"; + +message Event_ChangeZoneProperties { + extend GameEvent { + optional Event_ChangeZoneProperties ext = 2020; + } + optional string zone_name = 1; + + optional bool always_reveal_top_card = 10; +} diff --git a/common/pb/event_connection_closed.proto b/common/pb/event_connection_closed.proto new file mode 100644 index 000000000..e516f172f --- /dev/null +++ b/common/pb/event_connection_closed.proto @@ -0,0 +1,17 @@ +import "session_event.proto"; + +message Event_ConnectionClosed { + extend SessionEvent { + optional Event_ConnectionClosed ext = 1002; + } + enum CloseReason { + OTHER = 1; + SERVER_SHUTDOWN = 2; + TOO_MANY_CONNECTIONS = 3; + BANNED = 4; + USERNAMEINVALID = 5; + } + optional CloseReason reason = 1; + optional string reason_str = 2; + optional uint32 end_time = 3; +} diff --git a/common/pb/event_create_arrow.proto b/common/pb/event_create_arrow.proto new file mode 100644 index 000000000..6cbd04f34 --- /dev/null +++ b/common/pb/event_create_arrow.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; +import "serverinfo_arrow.proto"; + +message Event_CreateArrow { + extend GameEvent { + optional Event_CreateArrow ext = 2000; + } + optional ServerInfo_Arrow arrow_info = 1; +} diff --git a/common/pb/event_create_counter.proto b/common/pb/event_create_counter.proto new file mode 100644 index 000000000..09499964c --- /dev/null +++ b/common/pb/event_create_counter.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; +import "serverinfo_counter.proto"; + +message Event_CreateCounter { + extend GameEvent { + optional Event_CreateCounter ext = 2002; + } + optional ServerInfo_Counter counter_info = 1; +} diff --git a/common/pb/event_create_token.proto b/common/pb/event_create_token.proto new file mode 100644 index 000000000..4dde7251a --- /dev/null +++ b/common/pb/event_create_token.proto @@ -0,0 +1,16 @@ +import "game_event.proto"; + +message Event_CreateToken { + extend GameEvent { + optional Event_CreateToken ext = 2013; + } + optional string zone_name = 1; + optional sint32 card_id = 2; + optional string card_name = 3; + optional string color = 4; + optional string pt = 5; + optional string annotation = 6; + optional bool destroy_on_zone_change = 7; + optional sint32 x = 8; + optional sint32 y = 9; +} diff --git a/common/pb/event_del_counter.proto b/common/pb/event_del_counter.proto new file mode 100644 index 000000000..85616e4e5 --- /dev/null +++ b/common/pb/event_del_counter.proto @@ -0,0 +1,8 @@ +import "game_event.proto"; + +message Event_DelCounter { + extend GameEvent { + optional Event_DelCounter ext = 2004; + } + optional sint32 counter_id = 1; +} diff --git a/common/pb/event_delete_arrow.proto b/common/pb/event_delete_arrow.proto new file mode 100644 index 000000000..f1d40d244 --- /dev/null +++ b/common/pb/event_delete_arrow.proto @@ -0,0 +1,8 @@ +import "game_event.proto"; + +message Event_DeleteArrow { + extend GameEvent { + optional Event_DeleteArrow ext = 2001; + } + optional sint32 arrow_id = 1; +} diff --git a/common/pb/event_destroy_card.proto b/common/pb/event_destroy_card.proto new file mode 100644 index 000000000..6bea092c2 --- /dev/null +++ b/common/pb/event_destroy_card.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; + +message Event_DestroyCard { + extend GameEvent { + optional Event_DestroyCard ext = 2011; + } + optional string zone_name = 1; + optional uint32 card_id = 2; +} diff --git a/common/pb/event_draw_cards.proto b/common/pb/event_draw_cards.proto new file mode 100644 index 000000000..a4165d297 --- /dev/null +++ b/common/pb/event_draw_cards.proto @@ -0,0 +1,10 @@ +import "game_event.proto"; +import "serverinfo_card.proto"; + +message Event_DrawCards { + extend GameEvent { + optional Event_DrawCards ext = 2005; + } + optional sint32 number = 1; + repeated ServerInfo_Card cards = 2; +} diff --git a/common/pb/event_dump_zone.proto b/common/pb/event_dump_zone.proto new file mode 100644 index 000000000..933b9c50f --- /dev/null +++ b/common/pb/event_dump_zone.proto @@ -0,0 +1,10 @@ +import "game_event.proto"; + +message Event_DumpZone { + extend GameEvent { + optional Event_DumpZone ext = 2018; + } + optional sint32 zone_owner_id = 1; + optional string zone_name = 2; + optional sint32 number_cards = 3; +} diff --git a/common/pb/event_flip_card.proto b/common/pb/event_flip_card.proto new file mode 100644 index 000000000..4d8704402 --- /dev/null +++ b/common/pb/event_flip_card.proto @@ -0,0 +1,11 @@ +import "game_event.proto"; + +message Event_FlipCard { + extend GameEvent { + optional Event_FlipCard ext = 2010; + } + optional string zone_name = 1; + optional sint32 card_id = 2; + optional string card_name = 3; + optional bool face_down = 4; +} diff --git a/common/pb/event_game_closed.proto b/common/pb/event_game_closed.proto new file mode 100644 index 000000000..723f82634 --- /dev/null +++ b/common/pb/event_game_closed.proto @@ -0,0 +1,7 @@ +import "game_event.proto"; + +message Event_GameClosed { + extend GameEvent { + optional Event_GameClosed ext = 1002; + } +} diff --git a/common/pb/event_game_host_changed.proto b/common/pb/event_game_host_changed.proto new file mode 100644 index 000000000..c4c42f6d0 --- /dev/null +++ b/common/pb/event_game_host_changed.proto @@ -0,0 +1,8 @@ +import "game_event.proto"; + +message Event_GameHostChanged { + extend GameEvent { + optional Event_GameHostChanged ext = 1003; + } +} + diff --git a/common/pb/event_game_joined.proto b/common/pb/event_game_joined.proto new file mode 100644 index 000000000..aa344ade1 --- /dev/null +++ b/common/pb/event_game_joined.proto @@ -0,0 +1,15 @@ +import "session_event.proto"; +import "serverinfo_game.proto"; +import "serverinfo_gametype.proto"; + +message Event_GameJoined { + extend SessionEvent { + optional Event_GameJoined ext = 1009; + } + optional ServerInfo_Game game_info = 1; + repeated ServerInfo_GameType game_types = 2; + optional sint32 host_id = 3; + optional sint32 player_id = 4; + optional bool spectator = 5; + optional bool resuming = 6; +} diff --git a/common/pb/event_game_say.proto b/common/pb/event_game_say.proto new file mode 100644 index 000000000..4746b7b50 --- /dev/null +++ b/common/pb/event_game_say.proto @@ -0,0 +1,8 @@ +import "game_event.proto"; + +message Event_GameSay { + extend GameEvent { + optional Event_GameSay ext = 1009; + } + optional string message = 1; +} diff --git a/common/pb/event_game_state_changed.proto b/common/pb/event_game_state_changed.proto new file mode 100644 index 000000000..a4055c02f --- /dev/null +++ b/common/pb/event_game_state_changed.proto @@ -0,0 +1,13 @@ +import "game_event.proto"; +import "serverinfo_player.proto"; + +message Event_GameStateChanged { + extend GameEvent { + optional Event_GameStateChanged ext = 1005; + } + repeated ServerInfo_Player player_list = 1; + optional bool game_started = 2; + optional sint32 active_player_id = 3; + optional sint32 active_phase = 4; + optional uint32 seconds_elapsed = 5; +} diff --git a/common/pb/event_join.proto b/common/pb/event_join.proto new file mode 100644 index 000000000..947578388 --- /dev/null +++ b/common/pb/event_join.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; +import "serverinfo_playerproperties.proto"; + +message Event_Join { + extend GameEvent { + optional Event_Join ext = 1000; + } + optional ServerInfo_PlayerProperties player_properties = 1; +} diff --git a/common/pb/event_join_room.proto b/common/pb/event_join_room.proto new file mode 100644 index 000000000..f031eee9b --- /dev/null +++ b/common/pb/event_join_room.proto @@ -0,0 +1,9 @@ +import "room_event.proto"; +import "serverinfo_user.proto"; + +message Event_JoinRoom { + extend RoomEvent { + optional Event_JoinRoom ext = 1001; + } + optional ServerInfo_User user_info = 1; +} diff --git a/common/pb/event_kicked.proto b/common/pb/event_kicked.proto new file mode 100644 index 000000000..0163f5c59 --- /dev/null +++ b/common/pb/event_kicked.proto @@ -0,0 +1,7 @@ +import "game_event.proto"; + +message Event_Kicked { + extend GameEvent { + optional Event_Kicked ext = 1004; + } +} diff --git a/common/pb/event_leave.proto b/common/pb/event_leave.proto new file mode 100644 index 000000000..19b46e257 --- /dev/null +++ b/common/pb/event_leave.proto @@ -0,0 +1,7 @@ +import "game_event.proto"; + +message Event_Leave { + extend GameEvent { + optional Event_Leave ext = 1001; + } +} diff --git a/common/pb/event_leave_room.proto b/common/pb/event_leave_room.proto new file mode 100644 index 000000000..a49737a41 --- /dev/null +++ b/common/pb/event_leave_room.proto @@ -0,0 +1,11 @@ +import "room_event.proto"; + +message Event_LeaveRoom { + extend RoomEvent { + optional Event_LeaveRoom ext = 1000; + } + optional string name = 1; +} + + + diff --git a/common/pb/event_list_games.proto b/common/pb/event_list_games.proto new file mode 100644 index 000000000..e3c5b8ac7 --- /dev/null +++ b/common/pb/event_list_games.proto @@ -0,0 +1,9 @@ +import "room_event.proto"; +import "serverinfo_game.proto"; + +message Event_ListGames { + extend RoomEvent { + optional Event_ListGames ext = 1003; + } + repeated ServerInfo_Game game_list = 1; +} diff --git a/common/pb/event_list_rooms.proto b/common/pb/event_list_rooms.proto new file mode 100644 index 000000000..3969cbd54 --- /dev/null +++ b/common/pb/event_list_rooms.proto @@ -0,0 +1,9 @@ +import "session_event.proto"; +import "serverinfo_room.proto"; + +message Event_ListRooms { + extend SessionEvent { + optional Event_ListRooms ext = 1004; + } + repeated ServerInfo_Room room_list = 1; +} diff --git a/common/pb/event_move_card.proto b/common/pb/event_move_card.proto new file mode 100644 index 000000000..048f3a8d9 --- /dev/null +++ b/common/pb/event_move_card.proto @@ -0,0 +1,18 @@ +import "game_event.proto"; + +message Event_MoveCard { + extend GameEvent { + optional Event_MoveCard ext = 2009; + } + optional sint32 card_id = 1 [default = -1]; + optional string card_name = 2; + optional sint32 start_player_id = 3 [default = -1]; + optional string start_zone = 4; + optional sint32 position = 5 [default = -1]; + optional sint32 target_player_id = 6 [default = -1]; + optional string target_zone = 7; + optional sint32 x = 8 [default = -1]; + optional sint32 y = 9 [default = -1]; + optional sint32 new_card_id = 10 [default = -1]; + optional bool face_down = 11; +} diff --git a/common/pb/event_player_properties_changed.proto b/common/pb/event_player_properties_changed.proto new file mode 100644 index 000000000..03a12c246 --- /dev/null +++ b/common/pb/event_player_properties_changed.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; +import "serverinfo_playerproperties.proto"; + +message Event_PlayerPropertiesChanged { + extend GameEvent { + optional Event_PlayerPropertiesChanged ext = 1007; + } + optional ServerInfo_PlayerProperties player_properties = 1; +} diff --git a/common/pb/event_remove_from_list.proto b/common/pb/event_remove_from_list.proto new file mode 100644 index 000000000..4b90b1e7a --- /dev/null +++ b/common/pb/event_remove_from_list.proto @@ -0,0 +1,9 @@ +import "session_event.proto"; + +message Event_RemoveFromList { + extend SessionEvent { + optional Event_RemoveFromList ext = 1006; + } + optional string list_name = 1; + optional string user_name = 2; +} diff --git a/common/pb/event_replay_added.proto b/common/pb/event_replay_added.proto new file mode 100644 index 000000000..c3de20828 --- /dev/null +++ b/common/pb/event_replay_added.proto @@ -0,0 +1,9 @@ +import "session_event.proto"; +import "serverinfo_replay_match.proto"; + +message Event_ReplayAdded { + extend SessionEvent { + optional Event_ReplayAdded ext = 1100; + } + optional ServerInfo_ReplayMatch match_info = 1; +} diff --git a/common/pb/event_reveal_cards.proto b/common/pb/event_reveal_cards.proto new file mode 100644 index 000000000..1bb386e2d --- /dev/null +++ b/common/pb/event_reveal_cards.proto @@ -0,0 +1,13 @@ +import "game_event.proto"; +import "serverinfo_card.proto"; + +message Event_RevealCards { + extend GameEvent { + optional Event_RevealCards ext = 2006; + } + optional string zone_name = 1; + optional sint32 card_id = 2 [default = -1]; + optional sint32 other_player_id = 3 [default = -1]; + repeated ServerInfo_Card cards = 4; + optional bool grant_write_access = 5; +} diff --git a/common/pb/event_roll_die.proto b/common/pb/event_roll_die.proto new file mode 100644 index 000000000..dcc87bce9 --- /dev/null +++ b/common/pb/event_roll_die.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; + +message Event_RollDie { + extend GameEvent { + optional Event_RollDie ext = 2008; + } + optional uint32 sides = 1; + optional uint32 value = 2; +} diff --git a/common/pb/event_room_say.proto b/common/pb/event_room_say.proto new file mode 100644 index 000000000..e5cc17924 --- /dev/null +++ b/common/pb/event_room_say.proto @@ -0,0 +1,9 @@ +import "room_event.proto"; + +message Event_RoomSay { + extend RoomEvent { + optional Event_RoomSay ext = 1002; + } + optional string name = 1; + optional string message = 2; +} diff --git a/common/pb/event_server_complete_list.proto b/common/pb/event_server_complete_list.proto new file mode 100644 index 000000000..4e402e9be --- /dev/null +++ b/common/pb/event_server_complete_list.proto @@ -0,0 +1,12 @@ +import "session_event.proto"; +import "serverinfo_user.proto"; +import "serverinfo_room.proto"; + +message Event_ServerCompleteList { + extend SessionEvent { + optional Event_ServerCompleteList ext = 600; + } + optional uint32 server_id = 1; + repeated ServerInfo_User user_list = 2; + repeated ServerInfo_Room room_list = 3; +} diff --git a/common/pb/event_server_identification.proto b/common/pb/event_server_identification.proto new file mode 100644 index 000000000..d4417c03b --- /dev/null +++ b/common/pb/event_server_identification.proto @@ -0,0 +1,10 @@ +import "session_event.proto"; + +message Event_ServerIdentification { + extend SessionEvent { + optional Event_ServerIdentification ext = 500; + } + optional string server_name = 1; + optional string server_version = 2; + optional uint32 protocol_version = 3; +} diff --git a/common/pb/event_server_message.proto b/common/pb/event_server_message.proto new file mode 100644 index 000000000..a0fc252de --- /dev/null +++ b/common/pb/event_server_message.proto @@ -0,0 +1,8 @@ +import "session_event.proto"; + +message Event_ServerMessage { + extend SessionEvent { + optional Event_ServerMessage ext = 1000; + } + optional string message = 1; +} diff --git a/common/pb/event_server_shutdown.proto b/common/pb/event_server_shutdown.proto new file mode 100644 index 000000000..9a3439a76 --- /dev/null +++ b/common/pb/event_server_shutdown.proto @@ -0,0 +1,9 @@ +import "session_event.proto"; + +message Event_ServerShutdown { + extend SessionEvent { + optional Event_ServerShutdown ext = 1001; + } + optional string reason = 1; + optional uint32 minutes = 2; +} diff --git a/common/pb/event_set_active_phase.proto b/common/pb/event_set_active_phase.proto new file mode 100644 index 000000000..fe4a8088a --- /dev/null +++ b/common/pb/event_set_active_phase.proto @@ -0,0 +1,8 @@ +import "game_event.proto"; + +message Event_SetActivePhase { + extend GameEvent { + optional Event_SetActivePhase ext = 2017; + } + optional sint32 phase = 1; +} diff --git a/common/pb/event_set_active_player.proto b/common/pb/event_set_active_player.proto new file mode 100644 index 000000000..fe872b26d --- /dev/null +++ b/common/pb/event_set_active_player.proto @@ -0,0 +1,8 @@ +import "game_event.proto"; + +message Event_SetActivePlayer { + extend GameEvent { + optional Event_SetActivePlayer ext = 2016; + } + optional sint32 active_player_id = 1; +} diff --git a/common/pb/event_set_card_attr.proto b/common/pb/event_set_card_attr.proto new file mode 100644 index 000000000..a41bfc31f --- /dev/null +++ b/common/pb/event_set_card_attr.proto @@ -0,0 +1,12 @@ +import "game_event.proto"; +import "card_attributes.proto"; + +message Event_SetCardAttr { + extend GameEvent { + optional Event_SetCardAttr ext = 2014; + } + optional string zone_name = 1; + optional sint32 card_id = 2; + optional CardAttribute attribute = 3; + optional string attr_value = 4; +} diff --git a/common/pb/event_set_card_counter.proto b/common/pb/event_set_card_counter.proto new file mode 100644 index 000000000..6072b18f8 --- /dev/null +++ b/common/pb/event_set_card_counter.proto @@ -0,0 +1,11 @@ +import "game_event.proto"; + +message Event_SetCardCounter { + extend GameEvent { + optional Event_SetCardCounter ext = 2015; + } + optional string zone_name = 1; + optional sint32 card_id = 2; + optional sint32 counter_id = 3; + optional sint32 counter_value = 4; +} diff --git a/common/pb/event_set_counter.proto b/common/pb/event_set_counter.proto new file mode 100644 index 000000000..941f72018 --- /dev/null +++ b/common/pb/event_set_counter.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; + +message Event_SetCounter { + extend GameEvent { + optional Event_SetCounter ext = 2003; + } + optional sint32 counter_id = 1; + optional sint32 value = 2; +} diff --git a/common/pb/event_shuffle.proto b/common/pb/event_shuffle.proto new file mode 100644 index 000000000..e8a461c5f --- /dev/null +++ b/common/pb/event_shuffle.proto @@ -0,0 +1,8 @@ +import "game_event.proto"; + +message Event_Shuffle { + extend GameEvent { + optional Event_Shuffle ext = 2007; + } + optional string zone_name = 1; +} diff --git a/common/pb/event_stop_dump_zone.proto b/common/pb/event_stop_dump_zone.proto new file mode 100644 index 000000000..f0cff4dd3 --- /dev/null +++ b/common/pb/event_stop_dump_zone.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; + +message Event_StopDumpZone { + extend GameEvent { + optional Event_StopDumpZone ext = 2019; + } + optional sint32 zone_owner_id = 1; + optional string zone_name = 2; +} diff --git a/common/pb/event_user_joined.proto b/common/pb/event_user_joined.proto new file mode 100644 index 000000000..87e117e11 --- /dev/null +++ b/common/pb/event_user_joined.proto @@ -0,0 +1,9 @@ +import "session_event.proto"; +import "serverinfo_user.proto"; + +message Event_UserJoined { + extend SessionEvent { + optional Event_UserJoined ext = 1007; + } + optional ServerInfo_User user_info = 1; +} diff --git a/common/pb/event_user_left.proto b/common/pb/event_user_left.proto new file mode 100644 index 000000000..b2bf1976f --- /dev/null +++ b/common/pb/event_user_left.proto @@ -0,0 +1,8 @@ +import "session_event.proto"; + +message Event_UserLeft { + extend SessionEvent { + optional Event_UserLeft ext = 1008; + } + optional string name = 1; +} diff --git a/common/pb/event_user_message.proto b/common/pb/event_user_message.proto new file mode 100644 index 000000000..b41585c2e --- /dev/null +++ b/common/pb/event_user_message.proto @@ -0,0 +1,10 @@ +import "session_event.proto"; + +message Event_UserMessage { + extend SessionEvent { + optional Event_UserMessage ext = 1003; + } + optional string sender_name = 1; + optional string receiver_name = 2; + optional string message = 3; +} diff --git a/common/pb/game_commands.proto b/common/pb/game_commands.proto new file mode 100644 index 000000000..a9f28699b --- /dev/null +++ b/common/pb/game_commands.proto @@ -0,0 +1,37 @@ +message GameCommand { + enum GameCommandType { + KICK_FROM_GAME = 1000; + LEAVE_GAME = 1001; + GAME_SAY = 1002; + SHUFFLE = 1003; + MULLIGAN = 1004; + ROLL_DIE = 1005; + DRAW_CARDS = 1006; + UNDO_DRAW = 1007; + FLIP_CARD = 1008; + ATTACH_CARD = 1009; + CREATE_TOKEN = 1010; + CREATE_ARROW = 1011; + DELETE_ARROW = 1012; + SET_CARD_ATTR = 1013; + SET_CARD_COUNTER = 1014; + INC_CARD_COUNTER = 1015; + READY_START = 1016; + CONCEDE = 1017; + INC_COUNTER = 1018; + CREATE_COUNTER = 1019; + SET_COUNTER = 1020; + DEL_COUNTER = 1021; + NEXT_TURN = 1022; + SET_ACTIVE_PHASE = 1023; + DUMP_ZONE = 1024; + STOP_DUMP_ZONE = 1025; + REVEAL_CARDS = 1026; + MOVE_CARD = 1027; + SET_SIDEBOARD_PLAN = 1028; + DECK_SELECT = 1029; + SET_SIDEBOARD_LOCK = 1030; + CHANGE_ZONE_PROPERTIES = 1031; + } + extensions 100 to max; +} diff --git a/common/pb/game_event.proto b/common/pb/game_event.proto new file mode 100644 index 000000000..830c783c4 --- /dev/null +++ b/common/pb/game_event.proto @@ -0,0 +1,35 @@ +message GameEvent { + enum GameEventType { + JOIN = 1000; + LEAVE = 1001; + GAME_CLOSED = 1002; + GAME_HOST_CHANGED = 1003; + KICKED = 1004; + GAME_STATE_CHANGED = 1005; + PLAYER_PROPERTIES_CHANGED = 1007; + GAME_SAY = 1009; + CREATE_ARROW = 2000; + DELETE_ARROW = 2001; + CREATE_COUNTER = 2002; + SET_COUNTER = 2003; + DEL_COUNTER = 2004; + DRAW_CARDS = 2005; + REVEAL_CARDS = 2006; + SHUFFLE = 2007; + ROLL_DIE = 2008; + MOVE_CARD = 2009; + FLIP_CARD = 2010; + DESTROY_CARD = 2011; + ATTACH_CARD = 2012; + CREATE_TOKEN = 2013; + SET_CARD_ATTR = 2014; + SET_CARD_COUNTER = 2015; + SET_ACTIVE_PLAYER = 2016; + SET_ACTIVE_PHASE = 2017; + DUMP_ZONE = 2018; + STOP_DUMP_ZONE = 2019; + CHANGE_ZONE_PROPERTIES = 2020; + } + optional sint32 player_id = 1 [default = -1]; + extensions 100 to max; +} diff --git a/common/pb/game_event_container.proto b/common/pb/game_event_container.proto new file mode 100644 index 000000000..69b92d23b --- /dev/null +++ b/common/pb/game_event_container.proto @@ -0,0 +1,9 @@ +import "game_event.proto"; +import "game_event_context.proto"; + +message GameEventContainer { + optional uint32 game_id = 1; + repeated GameEvent event_list = 2; + optional GameEventContext context = 3; + optional uint32 seconds_elapsed = 4; +} diff --git a/common/pb/game_event_context.proto b/common/pb/game_event_context.proto new file mode 100644 index 000000000..6abd35dbf --- /dev/null +++ b/common/pb/game_event_context.proto @@ -0,0 +1,14 @@ +message GameEventContext { + enum ContextType { + READY_START = 1000; + CONCEDE = 1001; + DECK_SELECT = 1002; + UNDO_DRAW = 1003; + MOVE_CARD = 1004; + MULLIGAN = 1005; + PING_CHANGED = 1006; + CONNECTION_STATE_CHANGED = 1007; + SET_SIDEBOARD_LOCK = 1008; + } + extensions 100 to max; +} diff --git a/common/pb/game_replay.proto b/common/pb/game_replay.proto new file mode 100644 index 000000000..05bc67d04 --- /dev/null +++ b/common/pb/game_replay.proto @@ -0,0 +1,9 @@ +import "serverinfo_game.proto"; +import "game_event_container.proto"; + +message GameReplay { + optional uint64 replay_id = 1; + optional ServerInfo_Game game_info = 2; + repeated GameEventContainer event_list = 3; + optional uint32 duration_seconds = 4; +} diff --git a/common/pb/isl_message.proto b/common/pb/isl_message.proto new file mode 100644 index 000000000..20dec29d7 --- /dev/null +++ b/common/pb/isl_message.proto @@ -0,0 +1,29 @@ +import "response.proto"; +import "session_event.proto"; +import "commands.proto"; +import "game_event_container.proto"; +import "room_event.proto"; + +message IslMessage { + enum MessageType { + GAME_COMMAND_CONTAINER = 0; + ROOM_COMMAND_CONTAINER = 1; + + RESPONSE = 10; + SESSION_EVENT = 11; + GAME_EVENT_CONTAINER = 12; + ROOM_EVENT = 13; + } + optional MessageType message_type = 1; + + optional uint64 session_id = 9; + optional sint32 player_id = 10 [default = -1]; + + optional CommandContainer game_command = 100; + optional CommandContainer room_command = 101; + + optional Response response = 200; + optional SessionEvent session_event = 201; + optional GameEventContainer game_event_container = 202; + optional RoomEvent room_event = 203; +} diff --git a/common/pb/moderator_commands.proto b/common/pb/moderator_commands.proto new file mode 100644 index 000000000..18324ef1d --- /dev/null +++ b/common/pb/moderator_commands.proto @@ -0,0 +1,17 @@ +message ModeratorCommand { + enum ModeratorCommandType { + BAN_FROM_SERVER = 1000; + } + extensions 100 to max; +} + +message Command_BanFromServer { + extend ModeratorCommand { + optional Command_BanFromServer ext = 1000; + } + optional string user_name = 1; + optional string address = 2; + optional uint32 minutes = 3; + optional string reason = 4; + optional string visible_reason = 5; +} diff --git a/common/pb/move_card_to_zone.proto b/common/pb/move_card_to_zone.proto new file mode 100644 index 000000000..edb03219d --- /dev/null +++ b/common/pb/move_card_to_zone.proto @@ -0,0 +1,5 @@ +message MoveCard_ToZone { + optional string card_name = 1; + optional string start_zone = 2; + optional string target_zone = 3; +} diff --git a/common/pb/response.proto b/common/pb/response.proto new file mode 100644 index 000000000..0f13c4156 --- /dev/null +++ b/common/pb/response.proto @@ -0,0 +1,45 @@ +message Response { + enum ResponseCode { + RespNotConnected = -1; + RespNothing = 0; + RespOk = 1; + RespNotInRoom = 2; + RespInternalError = 3; + RespInvalidCommand = 4; + RespInvalidData = 5; + RespNameNotFound = 6; + RespLoginNeeded = 7; + RespFunctionNotAllowed = 8; + RespGameNotStarted = 9; + RespGameFull = 10; + RespContextError = 11; + RespWrongPassword = 12; + RespSpectatorsNotAllowed = 13; + RespOnlyBuddies = 14; + RespUserLevelTooLow = 15; + RespInIgnoreList = 16; + RespWouldOverwriteOldSession = 17; + RespChatFlood = 18; + RespUserIsBanned = 19; + RespAccessDenied = 20; + RespUsernameInvalid = 21; + RespRegistrationRequired = 22; + } + enum ResponseType { + JOIN_ROOM = 1000; + LIST_USERS = 1001; + GET_GAMES_OF_USER = 1002; + GET_USER_INFO = 1003; + DUMP_ZONE = 1004; + LOGIN = 1005; + DECK_LIST = 1006; + DECK_DOWNLOAD = 1007; + DECK_UPLOAD = 1008; + REPLAY_LIST = 1100; + REPLAY_DOWNLOAD = 1101; + } + required uint64 cmd_id = 1; + optional ResponseCode response_code = 2; + + extensions 100 to max; +} diff --git a/common/pb/response_deck_download.proto b/common/pb/response_deck_download.proto new file mode 100644 index 000000000..804ba47f5 --- /dev/null +++ b/common/pb/response_deck_download.proto @@ -0,0 +1,8 @@ +import "response.proto"; + +message Response_DeckDownload { + extend Response { + optional Response_DeckDownload ext = 1007; + } + optional string deck = 1; +} diff --git a/common/pb/response_deck_list.proto b/common/pb/response_deck_list.proto new file mode 100644 index 000000000..37aa82767 --- /dev/null +++ b/common/pb/response_deck_list.proto @@ -0,0 +1,9 @@ +import "response.proto"; +import "serverinfo_deckstorage.proto"; + +message Response_DeckList { + extend Response { + optional Response_DeckList ext = 1006; + } + optional ServerInfo_DeckStorage_Folder root = 1; +} diff --git a/common/pb/response_deck_upload.proto b/common/pb/response_deck_upload.proto new file mode 100644 index 000000000..eb978fe85 --- /dev/null +++ b/common/pb/response_deck_upload.proto @@ -0,0 +1,9 @@ +import "response.proto"; +import "serverinfo_deckstorage.proto"; + +message Response_DeckUpload { + extend Response { + optional Response_DeckUpload ext = 1008; + } + optional ServerInfo_DeckStorage_TreeItem new_file = 1; +} diff --git a/common/pb/response_dump_zone.proto b/common/pb/response_dump_zone.proto new file mode 100644 index 000000000..8df62ea71 --- /dev/null +++ b/common/pb/response_dump_zone.proto @@ -0,0 +1,9 @@ +import "response.proto"; +import "serverinfo_zone.proto"; + +message Response_DumpZone { + extend Response { + optional Response_DumpZone ext = 1004; + } + optional ServerInfo_Zone zone_info = 1; +} diff --git a/common/pb/response_get_games_of_user.proto b/common/pb/response_get_games_of_user.proto new file mode 100644 index 000000000..1c64377fe --- /dev/null +++ b/common/pb/response_get_games_of_user.proto @@ -0,0 +1,12 @@ +import "response.proto"; +import "serverinfo_game.proto"; +import "serverinfo_room.proto"; + +message Response_GetGamesOfUser { + extend Response { + optional Response_GetGamesOfUser ext = 1002; + } + repeated ServerInfo_Room room_list = 1; + repeated ServerInfo_Game game_list = 2; +} + diff --git a/common/pb/response_get_user_info.proto b/common/pb/response_get_user_info.proto new file mode 100644 index 000000000..b1c3b90c3 --- /dev/null +++ b/common/pb/response_get_user_info.proto @@ -0,0 +1,11 @@ +import "response.proto"; +import "serverinfo_user.proto"; + +message Response_GetUserInfo { + extend Response { + optional Response_GetUserInfo ext = 1003; + } + optional ServerInfo_User user_info = 1; +} + + diff --git a/common/pb/response_join_room.proto b/common/pb/response_join_room.proto new file mode 100644 index 000000000..c9f9fcd69 --- /dev/null +++ b/common/pb/response_join_room.proto @@ -0,0 +1,9 @@ +import "response.proto"; +import "serverinfo_room.proto"; + +message Response_JoinRoom { + extend Response { + optional Response_JoinRoom ext = 1000; + } + optional ServerInfo_Room room_info = 1; +} diff --git a/common/pb/response_list_users.proto b/common/pb/response_list_users.proto new file mode 100644 index 000000000..c9fe98473 --- /dev/null +++ b/common/pb/response_list_users.proto @@ -0,0 +1,10 @@ +import "response.proto"; +import "serverinfo_user.proto"; + +message Response_ListUsers { + extend Response { + optional Response_ListUsers ext = 1001; + } + repeated ServerInfo_User user_list = 1; +} + diff --git a/common/pb/response_login.proto b/common/pb/response_login.proto new file mode 100644 index 000000000..a554ca092 --- /dev/null +++ b/common/pb/response_login.proto @@ -0,0 +1,13 @@ +import "response.proto"; +import "serverinfo_user.proto"; + +message Response_Login { + extend Response { + optional Response_Login ext = 1005; + } + optional ServerInfo_User user_info = 1; + repeated ServerInfo_User buddy_list = 2; + repeated ServerInfo_User ignore_list = 3; + optional string denied_reason_str = 4; + optional uint64 denied_end_time = 5; +} diff --git a/common/pb/response_replay_download.proto b/common/pb/response_replay_download.proto new file mode 100644 index 000000000..a3df3d657 --- /dev/null +++ b/common/pb/response_replay_download.proto @@ -0,0 +1,9 @@ +import "response.proto"; + +message Response_ReplayDownload { + extend Response { + optional Response_ReplayDownload ext = 1101; + } + optional bytes replay_data = 1; +} + diff --git a/common/pb/response_replay_list.proto b/common/pb/response_replay_list.proto new file mode 100644 index 000000000..93ffb8bbc --- /dev/null +++ b/common/pb/response_replay_list.proto @@ -0,0 +1,9 @@ +import "response.proto"; +import "serverinfo_replay_match.proto"; + +message Response_ReplayList { + extend Response { + optional Response_ReplayList ext = 1100; + } + repeated ServerInfo_ReplayMatch match_list = 1; +} diff --git a/common/pb/room_commands.proto b/common/pb/room_commands.proto new file mode 100644 index 000000000..e74116741 --- /dev/null +++ b/common/pb/room_commands.proto @@ -0,0 +1,48 @@ +message RoomCommand { + enum RoomCommandType { + LEAVE_ROOM = 1000; + ROOM_SAY = 1001; + CREATE_GAME = 1002; + JOIN_GAME = 1003; + } + extensions 100 to max; +} + +message Command_LeaveRoom { + extend RoomCommand { + optional Command_LeaveRoom ext = 1000; + } +} + +message Command_RoomSay { + extend RoomCommand { + optional Command_RoomSay ext = 1001; + } + optional string message = 1; +} + +message Command_CreateGame { + extend RoomCommand { + optional Command_CreateGame ext = 1002; + } + optional string description = 1; + optional string password = 2; + optional uint32 max_players = 3; + optional bool only_buddies = 4; + optional bool only_registered = 5; + optional bool spectators_allowed = 6; + optional bool spectators_need_password = 7; + optional bool spectators_can_talk = 8; + optional bool spectators_see_everything = 9; + repeated uint32 game_type_ids = 10; +} + +message Command_JoinGame { + extend RoomCommand { + optional Command_JoinGame ext = 1003; + } + optional sint32 game_id = 1 [default = -1]; + optional string password = 2; + optional bool spectator = 3; + optional bool override_restrictions = 4; +} diff --git a/common/pb/room_event.proto b/common/pb/room_event.proto new file mode 100644 index 000000000..9a34f4c4c --- /dev/null +++ b/common/pb/room_event.proto @@ -0,0 +1,10 @@ +message RoomEvent { + enum RoomEventType { + LEAVE_ROOM = 1000; + JOIN_ROOM = 1001; + ROOM_SAY = 1002; + LIST_GAMES = 1003; + } + optional sint32 room_id = 1; + extensions 100 to max; +} diff --git a/common/pb/server_message.proto b/common/pb/server_message.proto new file mode 100644 index 000000000..b19055985 --- /dev/null +++ b/common/pb/server_message.proto @@ -0,0 +1,19 @@ +import "response.proto"; +import "session_event.proto"; +import "game_event_container.proto"; +import "room_event.proto"; + +message ServerMessage { + enum MessageType { + RESPONSE = 0; + SESSION_EVENT = 1; + GAME_EVENT_CONTAINER = 2; + ROOM_EVENT = 3; + } + optional MessageType message_type = 1; + + optional Response response = 2; + optional SessionEvent session_event = 3; + optional GameEventContainer game_event_container = 4; + optional RoomEvent room_event = 5; +} diff --git a/common/pb/serverinfo_arrow.proto b/common/pb/serverinfo_arrow.proto new file mode 100644 index 000000000..635659135 --- /dev/null +++ b/common/pb/serverinfo_arrow.proto @@ -0,0 +1,12 @@ +import "color.proto"; + +message ServerInfo_Arrow { + optional sint32 id = 1; + optional sint32 start_player_id = 2; + optional string start_zone = 3; + optional sint32 start_card_id = 4; + optional sint32 target_player_id = 5; + optional string target_zone = 6; + optional sint32 target_card_id = 7; + optional color arrow_color = 8; +} diff --git a/common/pb/serverinfo_card.proto b/common/pb/serverinfo_card.proto new file mode 100644 index 000000000..b628a271d --- /dev/null +++ b/common/pb/serverinfo_card.proto @@ -0,0 +1,20 @@ +import "serverinfo_cardcounter.proto"; + +message ServerInfo_Card { + optional sint32 id = 1 [default = -1]; + optional string name = 2; + optional sint32 x = 3 [default = -1]; + optional sint32 y = 4 [default = -1]; + optional bool face_down = 5; + optional bool tapped = 6; + optional bool attacking = 7; + optional string color = 8; + optional string pt = 9; + optional string annotation = 10; + optional bool destroy_on_zone_change = 11; + optional bool doesnt_untap = 12; + repeated ServerInfo_CardCounter counter_list = 13; + optional sint32 attach_player_id = 14 [default = -1]; + optional string attach_zone = 15; + optional sint32 attach_card_id = 16 [default = -1]; +} diff --git a/common/pb/serverinfo_cardcounter.proto b/common/pb/serverinfo_cardcounter.proto new file mode 100644 index 000000000..d3c6a1179 --- /dev/null +++ b/common/pb/serverinfo_cardcounter.proto @@ -0,0 +1,4 @@ +message ServerInfo_CardCounter { + optional sint32 id = 1; + optional sint32 value = 2; +} diff --git a/common/pb/serverinfo_counter.proto b/common/pb/serverinfo_counter.proto new file mode 100644 index 000000000..eaf082007 --- /dev/null +++ b/common/pb/serverinfo_counter.proto @@ -0,0 +1,9 @@ +import "color.proto"; + +message ServerInfo_Counter { + optional sint32 id = 1; + optional string name = 2; + optional color counter_color = 3; + optional sint32 radius = 4; + optional sint32 count = 5; +} diff --git a/common/pb/serverinfo_deckstorage.proto b/common/pb/serverinfo_deckstorage.proto new file mode 100644 index 000000000..531a613d8 --- /dev/null +++ b/common/pb/serverinfo_deckstorage.proto @@ -0,0 +1,14 @@ +message ServerInfo_DeckStorage_File { + optional uint32 creation_time = 1; +} + +message ServerInfo_DeckStorage_Folder { + repeated ServerInfo_DeckStorage_TreeItem items = 1; +} + +message ServerInfo_DeckStorage_TreeItem { + optional uint32 id = 1; + optional string name = 2; + optional ServerInfo_DeckStorage_File file = 10; + optional ServerInfo_DeckStorage_Folder folder = 11; +} diff --git a/common/pb/serverinfo_game.proto b/common/pb/serverinfo_game.proto new file mode 100644 index 000000000..3a0e10d43 --- /dev/null +++ b/common/pb/serverinfo_game.proto @@ -0,0 +1,23 @@ +import "serverinfo_user.proto"; + +message ServerInfo_Game { + optional sint32 server_id = 1 [default = -1]; + optional sint32 room_id = 2 [default = -1]; + optional sint32 game_id = 3 [default = -1]; + optional string description = 4; + optional bool with_password = 5; + optional uint32 max_players = 6; + repeated sint32 game_types = 7; + optional ServerInfo_User creator_info = 8; + optional bool only_buddies = 9; + optional bool only_registered = 10; + optional bool spectators_allowed = 11; + optional bool spectators_need_password = 12; + optional bool spectators_can_chat = 13; + optional bool spectators_omniscient = 14; + optional uint32 player_count = 30; + optional uint32 spectators_count = 31; + optional bool started = 50; + optional uint32 start_time = 51; + optional bool closed = 52; +} diff --git a/common/pb/serverinfo_gametype.proto b/common/pb/serverinfo_gametype.proto new file mode 100644 index 000000000..977b9abbe --- /dev/null +++ b/common/pb/serverinfo_gametype.proto @@ -0,0 +1,5 @@ +message ServerInfo_GameType { + optional sint32 game_type_id = 1; + optional string description = 2; +}; + diff --git a/common/pb/serverinfo_player.proto b/common/pb/serverinfo_player.proto new file mode 100644 index 000000000..21911f5fd --- /dev/null +++ b/common/pb/serverinfo_player.proto @@ -0,0 +1,12 @@ +import "serverinfo_zone.proto"; +import "serverinfo_counter.proto"; +import "serverinfo_arrow.proto"; +import "serverinfo_playerproperties.proto"; + +message ServerInfo_Player { + optional ServerInfo_PlayerProperties properties = 1; + optional string deck_list = 2; + repeated ServerInfo_Zone zone_list = 3; + repeated ServerInfo_Counter counter_list = 4; + repeated ServerInfo_Arrow arrow_list = 5; +} diff --git a/common/pb/serverinfo_playerping.proto b/common/pb/serverinfo_playerping.proto new file mode 100644 index 000000000..78ef63938 --- /dev/null +++ b/common/pb/serverinfo_playerping.proto @@ -0,0 +1,4 @@ +message ServerInfo_PlayerPing { + optional sint32 player_id = 1; + optional sint32 ping_time = 2; +} diff --git a/common/pb/serverinfo_playerproperties.proto b/common/pb/serverinfo_playerproperties.proto new file mode 100644 index 000000000..87ef5cd95 --- /dev/null +++ b/common/pb/serverinfo_playerproperties.proto @@ -0,0 +1,12 @@ +import "serverinfo_user.proto"; + +message ServerInfo_PlayerProperties { + optional sint32 player_id = 1; + optional ServerInfo_User user_info = 2; + optional bool spectator = 3; + optional bool conceded = 4; + optional bool ready_start = 5; + optional string deck_hash = 6; + optional sint32 ping_seconds = 7; + optional bool sideboard_locked = 8; +} diff --git a/common/pb/serverinfo_replay.proto b/common/pb/serverinfo_replay.proto new file mode 100644 index 000000000..18f68317a --- /dev/null +++ b/common/pb/serverinfo_replay.proto @@ -0,0 +1,5 @@ +message ServerInfo_Replay { + optional sint32 replay_id = 1 [default = -1]; + optional string replay_name = 2; + optional uint32 duration = 3; +} diff --git a/common/pb/serverinfo_replay_match.proto b/common/pb/serverinfo_replay_match.proto new file mode 100644 index 000000000..64576ecca --- /dev/null +++ b/common/pb/serverinfo_replay_match.proto @@ -0,0 +1,13 @@ +import "serverinfo_replay.proto"; + +message ServerInfo_ReplayMatch { + repeated ServerInfo_Replay replay_list = 1; + + optional sint32 game_id = 2 [default = -1]; + optional string room_name = 3; + optional uint32 time_started = 4; + optional uint32 length = 5; + optional string game_name = 6; + repeated string player_names = 7; + optional bool do_not_hide = 8; +} diff --git a/common/pb/serverinfo_room.proto b/common/pb/serverinfo_room.proto new file mode 100644 index 000000000..6455ad897 --- /dev/null +++ b/common/pb/serverinfo_room.proto @@ -0,0 +1,15 @@ +import "serverinfo_game.proto"; +import "serverinfo_user.proto"; +import "serverinfo_gametype.proto"; + +message ServerInfo_Room { + optional sint32 room_id = 1; + optional string name = 2; + optional string description = 3; + optional uint32 game_count = 4; + optional uint32 player_count = 5; + optional bool auto_join = 6; + repeated ServerInfo_Game game_list = 7; + repeated ServerInfo_User user_list = 8; + repeated ServerInfo_GameType gametype_list = 9; +} diff --git a/common/pb/serverinfo_user.proto b/common/pb/serverinfo_user.proto new file mode 100644 index 000000000..b06f7bbad --- /dev/null +++ b/common/pb/serverinfo_user.proto @@ -0,0 +1,24 @@ +message ServerInfo_User { + enum UserLevelFlag { + IsNothing = 0; + IsUser = 1; + IsRegistered = 2; + IsModerator = 4; + IsAdmin = 8; + }; + enum Gender { + GenderUnknown = -1; + Male = 0; + Female = 1; + }; + optional string name = 1; + optional uint32 user_level = 2; + optional string address = 3; + optional string real_name = 4; + optional Gender gender = 5 [default = GenderUnknown]; + optional string country = 6; + optional bytes avatar_bmp = 7; + optional sint32 id = 8 [default = -1]; + optional sint32 server_id = 9 [default = -1]; + optional uint64 session_id = 10; +} diff --git a/common/pb/serverinfo_zone.proto b/common/pb/serverinfo_zone.proto new file mode 100644 index 000000000..aee914adb --- /dev/null +++ b/common/pb/serverinfo_zone.proto @@ -0,0 +1,24 @@ +import "serverinfo_card.proto"; + +message ServerInfo_Zone { + enum ZoneType { + // PrivateZone: Contents of the zone are always visible to the owner, + // but not to anyone else. + // PublicZone: Contents of the zone are always visible to anyone. + // HiddenZone: Contents of the zone are never visible to anyone. + // However, the owner of the zone can issue a dump_zone command, + // setting beingLookedAt to true. + // Cards in a zone with the type HiddenZone are referenced by their + // list index, whereas cards in any other zone are referenced by their ids. + + PrivateZone = 0; + PublicZone = 1; + HiddenZone = 2; + } + optional string name = 1; + optional ZoneType type = 2; + optional bool with_coords = 3; + optional sint32 card_count = 4; + repeated ServerInfo_Card card_list = 5; + optional bool always_reveal_top_card = 10; +} diff --git a/common/pb/session_commands.proto b/common/pb/session_commands.proto new file mode 100644 index 000000000..bbb5e81d3 --- /dev/null +++ b/common/pb/session_commands.proto @@ -0,0 +1,96 @@ +message SessionCommand { + enum SessionCommandType { + PING = 1000; + LOGIN = 1001; + MESSAGE = 1002; + LIST_USERS = 1003; + GET_GAMES_OF_USER = 1004; + GET_USER_INFO = 1005; + ADD_TO_LIST = 1006; + REMOVE_FROM_LIST = 1007; + DECK_LIST = 1008; + DECK_NEW_DIR = 1009; + DECK_DEL_DIR = 1010; + DECK_DEL = 1011; + DECK_DOWNLOAD = 1012; + DECK_UPLOAD = 1013; + LIST_ROOMS = 1014; + JOIN_ROOM = 1015; + REPLAY_LIST = 1100; + REPLAY_DOWNLOAD = 1101; + REPLAY_MODIFY_MATCH = 1102; + REPLAY_DELETE_MATCH = 1103; + } + extensions 100 to max; +} + +message Command_Ping { + extend SessionCommand { + optional Command_Ping ext = 1000; + } +} + +message Command_Login { + extend SessionCommand { + optional Command_Login ext = 1001; + } + optional string user_name = 1; + optional string password = 2; +} + +message Command_Message { + extend SessionCommand { + optional Command_Message ext = 1002; + } + optional string user_name = 1; + optional string message = 2; +} + +message Command_ListUsers { + extend SessionCommand { + optional Command_ListUsers ext = 1003; + } +} + +message Command_GetGamesOfUser { + extend SessionCommand { + optional Command_GetGamesOfUser ext = 1004; + } + optional string user_name = 1; +} + +message Command_GetUserInfo { + extend SessionCommand { + optional Command_GetUserInfo ext = 1005; + } + optional string user_name = 1; +} + +message Command_AddToList { + extend SessionCommand { + optional Command_AddToList ext = 1006; + } + optional string list = 1; + optional string user_name = 2; +} + +message Command_RemoveFromList { + extend SessionCommand { + optional Command_RemoveFromList ext = 1007; + } + optional string list = 1; + optional string user_name = 2; +} + +message Command_ListRooms { + extend SessionCommand { + optional Command_ListRooms ext = 1014; + } +} + +message Command_JoinRoom { + extend SessionCommand { + optional Command_JoinRoom ext = 1015; + } + optional uint32 room_id = 1; +} diff --git a/common/pb/session_event.proto b/common/pb/session_event.proto new file mode 100644 index 000000000..a7b14d00c --- /dev/null +++ b/common/pb/session_event.proto @@ -0,0 +1,18 @@ +message SessionEvent { + enum SessionEventType { + SERVER_IDENTIFICATION = 500; + SERVER_COMPLETE_LIST = 600; + SERVER_MESSAGE = 1000; + SERVER_SHUTDOWN = 1001; + CONNECTION_CLOSED = 1002; + USER_MESSAGE = 1003; + LIST_ROOMS = 1004; + ADD_TO_LIST = 1005; + REMOVE_FROM_LIST = 1006; + USER_JOINED = 1007; + USER_LEFT = 1008; + GAME_JOINED = 1009; + REPLAY_ADDED = 1100; + } + extensions 100 to max; +} diff --git a/common/protocol.cpp b/common/protocol.cpp deleted file mode 100644 index 23b3deb7a..000000000 --- a/common/protocol.cpp +++ /dev/null @@ -1,547 +0,0 @@ -#include -#include -#include "protocol.h" -#include "protocol_items.h" -#include "decklist.h" - -ProtocolItem::ProtocolItem(const QString &_itemType, const QString &_itemSubType) - : SerializableItem_Map(_itemType, _itemSubType), receiverMayDelete(true) -{ -} - -void ProtocolItem::initializeHash() -{ - initializeHashAuto(); - - registerSerializableItem("move_card_to_zone", MoveCardToZone::newItem); - registerSerializableItem("room", ServerInfo_Room::newItem); - registerSerializableItem("user", ServerInfo_User::newItem); - registerSerializableItem("game", ServerInfo_Game::newItem); - registerSerializableItem("game_type", ServerInfo_GameType::newItem); - registerSerializableItem("card_counter", ServerInfo_CardCounter::newItem); - registerSerializableItem("card", ServerInfo_Card::newItem); - registerSerializableItem("zone", ServerInfo_Zone::newItem); - registerSerializableItem("counter", ServerInfo_Counter::newItem); - registerSerializableItem("arrow", ServerInfo_Arrow::newItem); - registerSerializableItem("player_properties", ServerInfo_PlayerProperties::newItem); - registerSerializableItem("player", ServerInfo_Player::newItem); - registerSerializableItem("player_ping", ServerInfo_PlayerPing::newItem); - registerSerializableItem("file", DeckList_File::newItem); - registerSerializableItem("directory", DeckList_Directory::newItem); - registerSerializableItem("card_to_move", CardToMove::newItem); - registerSerializableItem("game_type_id", GameTypeId::newItem); - - registerSerializableItem("containercmd", CommandContainer::newItem); - registerSerializableItem("containergame_event", GameEventContainer::newItem); - - registerSerializableItem("cmdcreate_game", Command_CreateGame::newItem); - registerSerializableItem("cmddeck_upload", Command_DeckUpload::newItem); - registerSerializableItem("cmddeck_select", Command_DeckSelect::newItem); - registerSerializableItem("cmdset_sideboard_plan", Command_SetSideboardPlan::newItem); - registerSerializableItem("cmdmove_card", Command_MoveCard::newItem); - - registerSerializableItem("resp", ProtocolResponse::newItem); - ProtocolResponse::initializeHash(); - registerSerializableItem("respjoin_room", Response_JoinRoom::newItem); - registerSerializableItem("resplist_users", Response_ListUsers::newItem); - registerSerializableItem("respget_games_of_user", Response_GetGamesOfUser::newItem); - registerSerializableItem("respget_user_info", Response_GetUserInfo::newItem); - registerSerializableItem("respdeck_list", Response_DeckList::newItem); - registerSerializableItem("respdeck_download", Response_DeckDownload::newItem); - registerSerializableItem("respdeck_upload", Response_DeckUpload::newItem); - registerSerializableItem("respdump_zone", Response_DumpZone::newItem); - registerSerializableItem("resplogin", Response_Login::newItem); - - registerSerializableItem("room_eventlist_games", Event_ListGames::newItem); - registerSerializableItem("room_eventjoin_room", Event_JoinRoom::newItem); - registerSerializableItem("generic_eventadd_to_list", Event_AddToList::newItem); - registerSerializableItem("generic_eventuser_joined", Event_UserJoined::newItem); - registerSerializableItem("generic_eventlist_rooms", Event_ListRooms::newItem); - registerSerializableItem("game_eventjoin", Event_Join::newItem); - registerSerializableItem("game_eventgame_state_changed", Event_GameStateChanged::newItem); - registerSerializableItem("game_eventplayer_properties_changed", Event_PlayerPropertiesChanged::newItem); - registerSerializableItem("game_eventcreate_arrows", Event_CreateArrows::newItem); - registerSerializableItem("game_eventcreate_counters", Event_CreateCounters::newItem); - registerSerializableItem("game_eventdraw_cards", Event_DrawCards::newItem); - registerSerializableItem("game_eventreveal_cards", Event_RevealCards::newItem); - registerSerializableItem("game_eventping", Event_Ping::newItem); -} - -TopLevelProtocolItem::TopLevelProtocolItem() - : SerializableItem(QString()), currentItem(0) -{ -} - -bool TopLevelProtocolItem::readCurrentItem(QXmlStreamReader *xml) -{ - if (currentItem) { - if (currentItem->read(xml)) { - emit protocolItemReceived(currentItem); - currentItem = 0; - } - return true; - } else - return false; -} - -bool TopLevelProtocolItem::readElement(QXmlStreamReader *xml) -{ - if (!readCurrentItem(xml) && (xml->isStartElement())) { - QString childName = xml->name().toString(); - QString childSubType = xml->attributes().value("type").toString(); - - currentItem = dynamic_cast(getNewItem(childName + childSubType)); - if (!currentItem) - currentItem = new ProtocolItem_Invalid; - if (xml->attributes().value("comp").toString().toInt() == 1) - currentItem->setCompressed(true); - - readCurrentItem(xml); - } - return SerializableItem::readElement(xml); -} - -void TopLevelProtocolItem::writeElement(QXmlStreamWriter * /*xml*/) -{ -} - -int CommandContainer::lastCmdId = 0; - -Command::Command(const QString &_itemName) - : ProtocolItem("cmd", _itemName) -{ -} - -void Command::processResponse(ProtocolResponse *response) -{ - emit finished(response); - emit finished(response->getResponseCode()); -} - -CommandContainer::CommandContainer(const QList &_commandList, int _cmdId) - : ProtocolItem("container", "cmd"), ticks(0), resp(0), gameEventQueuePublic(0), gameEventQueueOmniscient(0), gameEventQueuePrivate(0), privatePlayerId(-1) -{ - if (_cmdId == -1) - _cmdId = lastCmdId++; - insertItem(new SerializableItem_Int("cmd_id", _cmdId)); - - for (int i = 0; i < _commandList.size(); ++i) - itemList.append(_commandList[i]); -} - -void CommandContainer::processResponse(ProtocolResponse *response) -{ - emit finished(response); - emit finished(response->getResponseCode()); - - const QList &cmdList = getCommandList(); - for (int i = 0; i < cmdList.size(); ++i) - cmdList[i]->processResponse(response); -} - -void CommandContainer::setResponse(ProtocolResponse *_resp) -{ - delete resp; - resp = _resp; -} - -void CommandContainer::enqueueGameEventPublic(GameEvent *event, int gameId, GameEventContext *context) -{ - if (!gameEventQueuePublic) - gameEventQueuePublic = new GameEventContainer(QList(), gameId); - gameEventQueuePublic->addGameEvent(event); - if (context) - gameEventQueuePublic->setContext(context); -} - -void CommandContainer::enqueueGameEventOmniscient(GameEvent *event, int gameId, GameEventContext *context) -{ - if (!gameEventQueueOmniscient) - gameEventQueueOmniscient = new GameEventContainer(QList(), gameId); - gameEventQueueOmniscient->addGameEvent(event); - if (context) - gameEventQueueOmniscient->setContext(context); -} - -void CommandContainer::enqueueGameEventPrivate(GameEvent *event, int gameId, int playerId, GameEventContext *context) -{ - if (!gameEventQueuePrivate) - gameEventQueuePrivate = new GameEventContainer(QList(), gameId); - gameEventQueuePrivate->addGameEvent(event); - privatePlayerId = playerId; - if (context) - gameEventQueuePrivate->setContext(context); -} - -Command_CreateGame::Command_CreateGame(int _roomId, const QString &_description, const QString &_password, int _maxPlayers, const QList &_gameTypes, bool _onlyBuddies, bool _onlyRegistered, bool _spectatorsAllowed, bool _spectatorsNeedPassword, bool _spectatorsCanTalk, bool _spectatorsSeeEverything) - : RoomCommand("create_game", _roomId) -{ - insertItem(new SerializableItem_String("description", _description)); - insertItem(new SerializableItem_String("password", _password)); - insertItem(new SerializableItem_Int("max_players", _maxPlayers)); - insertItem(new SerializableItem_Bool("only_buddies", _onlyBuddies)); - insertItem(new SerializableItem_Bool("only_registered", _onlyRegistered)); - insertItem(new SerializableItem_Bool("spectators_allowed", _spectatorsAllowed)); - insertItem(new SerializableItem_Bool("spectators_need_password", _spectatorsNeedPassword)); - insertItem(new SerializableItem_Bool("spectators_can_talk", _spectatorsCanTalk)); - insertItem(new SerializableItem_Bool("spectators_see_everything", _spectatorsSeeEverything)); - - for (int i = 0; i < _gameTypes.size(); ++i) - itemList.append(_gameTypes[i]); -} - -Command_DeckUpload::Command_DeckUpload(DeckList *_deck, const QString &_path) - : Command("deck_upload") -{ - insertItem(new SerializableItem_String("path", _path)); - if (!_deck) - _deck = new DeckList; - insertItem(_deck); -} - -DeckList *Command_DeckUpload::getDeck() const -{ - return static_cast(itemMap.value("cockatrice_deck")); -} - -Command_DeckSelect::Command_DeckSelect(int _gameId, DeckList *_deck, int _deckId) - : GameCommand("deck_select", _gameId) -{ - insertItem(new SerializableItem_Int("deck_id", _deckId)); - if (!_deck) - _deck = new DeckList; - insertItem(_deck); -} - -DeckList *Command_DeckSelect::getDeck() const -{ - return static_cast(itemMap.value("cockatrice_deck")); -} - -Command_SetSideboardPlan::Command_SetSideboardPlan(int _gameId, const QList &_moveList) - : GameCommand("set_sideboard_plan", _gameId) -{ - for (int i = 0; i < _moveList.size(); ++i) - itemList.append(_moveList[i]); -} - -QList Command_SetSideboardPlan::getMoveList() const -{ - return typecastItemList(); -} - -Command_MoveCard::Command_MoveCard(int _gameId, const QString &_startZone, const QList &_cards, int _targetPlayerId, const QString &_targetZone, int _x, int _y) - : GameCommand("move_card", _gameId) -{ - insertItem(new SerializableItem_String("start_zone", _startZone)); - insertItem(new SerializableItem_Int("target_player_id", _targetPlayerId)); - insertItem(new SerializableItem_String("target_zone", _targetZone)); - insertItem(new SerializableItem_Int("x", _x)); - insertItem(new SerializableItem_Int("y", _y)); - - for (int i = 0; i < _cards.size(); ++i) - itemList.append(_cards[i]); -} - -QHash ProtocolResponse::responseHash; - -ProtocolResponse::ProtocolResponse(int _cmdId, ResponseCode _responseCode, const QString &_itemName) - : ProtocolItem("resp", _itemName) -{ - insertItem(new SerializableItem_Int("cmd_id", _cmdId)); - insertItem(new SerializableItem_String("response_code", responseHash.key(_responseCode))); -} - -void ProtocolResponse::initializeHash() -{ - responseHash.insert(QString(), RespNothing); - responseHash.insert("ok", RespOk); - responseHash.insert("not_in_room", RespNotInRoom); - responseHash.insert("internal_error", RespInternalError); - responseHash.insert("invalid_command", RespInvalidCommand); - responseHash.insert("name_not_found", RespNameNotFound); - responseHash.insert("login_needed", RespLoginNeeded); - responseHash.insert("function_not_allowed", RespFunctionNotAllowed); - responseHash.insert("game_not_started", RespGameNotStarted); - responseHash.insert("game_full", RespGameFull); - responseHash.insert("context_error", RespContextError); - responseHash.insert("wrong_password", RespWrongPassword); - responseHash.insert("spectators_not_allowed", RespSpectatorsNotAllowed); - responseHash.insert("only_buddies", RespOnlyBuddies); - responseHash.insert("user_level_too_low", RespUserLevelTooLow); - responseHash.insert("in_ignore_list", RespInIgnoreList); - responseHash.insert("would_overwrite_old_session", RespWouldOverwriteOldSession); - responseHash.insert("chat_flood", RespChatFlood); -} - -Response_JoinRoom::Response_JoinRoom(int _cmdId, ResponseCode _responseCode, ServerInfo_Room *_roomInfo) - : ProtocolResponse(_cmdId, _responseCode, "join_room") -{ - if (!_roomInfo) - _roomInfo = new ServerInfo_Room; - insertItem(_roomInfo); -} - -Response_ListUsers::Response_ListUsers(int _cmdId, ResponseCode _responseCode, const QList &_userList) - : ProtocolResponse(_cmdId, _responseCode, "list_users") -{ - for (int i = 0; i < _userList.size(); ++i) - itemList.append(_userList[i]); -} - -Response_DeckList::Response_DeckList(int _cmdId, ResponseCode _responseCode, DeckList_Directory *_root) - : ProtocolResponse(_cmdId, _responseCode, "deck_list") -{ - if (!_root) - _root = new DeckList_Directory; - insertItem(_root); -} - -Response_GetGamesOfUser::Response_GetGamesOfUser(int _cmdId, ResponseCode _responseCode, const QList &_roomList, const QList &_gameList) - : ProtocolResponse(_cmdId, _responseCode, "get_games_of_user") -{ - roomList = _roomList; - for (int i = 0; i < _roomList.size(); ++i) - itemList.append(_roomList[i]); - - gameList = _gameList; - for (int i = 0; i < _gameList.size(); ++i) - itemList.append(_gameList[i]); -} - -void Response_GetGamesOfUser::extractData() -{ - for (int i = 0; i < itemList.size(); ++i) { - ServerInfo_Room *room = dynamic_cast(itemList[i]); - if (room) { - roomList.append(room); - continue; - } - ServerInfo_Game *game = dynamic_cast(itemList[i]); - if (game) { - gameList.append(game); - continue; - } - } -} - -Response_GetUserInfo::Response_GetUserInfo(int _cmdId, ResponseCode _responseCode, ServerInfo_User *_user) - : ProtocolResponse(_cmdId, _responseCode, "get_user_info") -{ - if (!_user) - _user = new ServerInfo_User; - insertItem(_user); -} - -Response_DeckDownload::Response_DeckDownload(int _cmdId, ResponseCode _responseCode, DeckList *_deck) - : ProtocolResponse(_cmdId, _responseCode, "deck_download") -{ - if (!_deck) - _deck = new DeckList; - insertItem(_deck); -} - -DeckList *Response_DeckDownload::getDeck() const -{ - return static_cast(itemMap.value("cockatrice_deck")); -} - -Response_DeckUpload::Response_DeckUpload(int _cmdId, ResponseCode _responseCode, DeckList_File *_file) - : ProtocolResponse(_cmdId, _responseCode, "deck_upload") -{ - if (!_file) - _file = new DeckList_File; - insertItem(_file); -} - -Response_DumpZone::Response_DumpZone(int _cmdId, ResponseCode _responseCode, ServerInfo_Zone *_zone) - : ProtocolResponse(_cmdId, _responseCode, "dump_zone") -{ - if (!_zone) - _zone = new ServerInfo_Zone; - insertItem(_zone); -} - -Response_Login::Response_Login(int _cmdId, ResponseCode _responseCode, ServerInfo_User *_userInfo, const QList &_buddyList, const QList &_ignoreList) - : ProtocolResponse(_cmdId, _responseCode, "login") -{ - if (!_userInfo) - _userInfo = new ServerInfo_User; - insertItem(_userInfo); - - insertItem(new ServerInfo_UserList("buddy_list", _buddyList)); - insertItem(new ServerInfo_UserList("ignore_list", _ignoreList)); -} - -GameEvent::GameEvent(const QString &_eventName, int _playerId) - : ProtocolItem("game_event", _eventName) -{ - insertItem(new SerializableItem_Int("player_id", _playerId)); -} - -GameEventContext::GameEventContext(const QString &_contextName) - : ProtocolItem("game_event_context", _contextName) -{ -} - -RoomEvent::RoomEvent(const QString &_eventName, int _roomId) - : ProtocolItem("room_event", _eventName) -{ - insertItem(new SerializableItem_Int("room_id", _roomId)); -} - -Event_ListRooms::Event_ListRooms(const QList &_roomList) - : GenericEvent("list_rooms") -{ - for (int i = 0; i < _roomList.size(); ++i) - itemList.append(_roomList[i]); -} - -Event_JoinRoom::Event_JoinRoom(int _roomId, ServerInfo_User *_info) - : RoomEvent("join_room", _roomId) -{ - if (!_info) - _info = new ServerInfo_User; - insertItem(_info); -} - -Event_ListGames::Event_ListGames(int _roomId, const QList &_gameList) - : RoomEvent("list_games", _roomId) -{ - for (int i = 0; i < _gameList.size(); ++i) - itemList.append(_gameList[i]); -} - -Event_AddToList::Event_AddToList(const QString &_list, ServerInfo_User *_userInfo) - : GenericEvent("add_to_list") -{ - if (!_userInfo) - _userInfo = new ServerInfo_User; - insertItem(_userInfo); - insertItem(new SerializableItem_String("list", _list)); -} - -Event_UserJoined::Event_UserJoined(ServerInfo_User *_userInfo) - : GenericEvent("user_joined") -{ - if (!_userInfo) - _userInfo = new ServerInfo_User; - insertItem(_userInfo); -} - -Event_Join::Event_Join(ServerInfo_PlayerProperties *player) - : GameEvent("join", -1) -{ - if (!player) - player = new ServerInfo_PlayerProperties; - insertItem(player); -} - -GameEventContainer::GameEventContainer(const QList &_eventList, int _gameId, GameEventContext *_context) - : ProtocolItem("container", "game_event") -{ - insertItem(new SerializableItem_Int("game_id", _gameId)); - - context = _context; - if (_context) - itemList.append(_context); - - eventList = _eventList; - for (int i = 0; i < _eventList.size(); ++i) - itemList.append(_eventList[i]); -} - -void GameEventContainer::extractData() -{ - for (int i = 0; i < itemList.size(); ++i) { - GameEvent *_event = dynamic_cast(itemList[i]); - GameEventContext *_context = dynamic_cast(itemList[i]); - if (_event) - eventList.append(_event); - else if (_context) - context = _context; - } -} - -void GameEventContainer::setContext(GameEventContext *_context) -{ - for (int i = 0; i < itemList.size(); ++i) { - GameEventContext *temp = qobject_cast(itemList[i]); - if (temp) { - delete temp; - itemList.removeAt(i); - break; - } - } - itemList.append(_context); - context = _context; -} - -void GameEventContainer::addGameEvent(GameEvent *event) -{ - appendItem(event); - eventList.append(event); -} - -GameEventContainer *GameEventContainer::makeNew(GameEvent *event, int _gameId) -{ - return new GameEventContainer(QList() << event, _gameId); -} - -Event_GameStateChanged::Event_GameStateChanged(bool _gameStarted, int _activePlayer, int _activePhase, const QList &_playerList) - : GameEvent("game_state_changed", -1) -{ - insertItem(new SerializableItem_Bool("game_started", _gameStarted)); - insertItem(new SerializableItem_Int("active_player", _activePlayer)); - insertItem(new SerializableItem_Int("active_phase", _activePhase)); - for (int i = 0; i < _playerList.size(); ++i) - itemList.append(_playerList[i]); -} - -Event_PlayerPropertiesChanged::Event_PlayerPropertiesChanged(int _playerId, ServerInfo_PlayerProperties *_properties) - : GameEvent("player_properties_changed", _playerId) -{ - if (!_properties) - _properties = new ServerInfo_PlayerProperties; - insertItem(_properties); -} - -Event_Ping::Event_Ping(int _secondsElapsed, const QList &_pingList) - : GameEvent("ping", -1) -{ - insertItem(new SerializableItem_Int("seconds_elapsed", _secondsElapsed)); - for (int i = 0; i < _pingList.size(); ++i) - itemList.append(_pingList[i]); -} - -Event_CreateArrows::Event_CreateArrows(int _playerId, const QList &_arrowList) - : GameEvent("create_arrows", _playerId) -{ - for (int i = 0; i < _arrowList.size(); ++i) - itemList.append(_arrowList[i]); -} - -Event_CreateCounters::Event_CreateCounters(int _playerId, const QList &_counterList) - : GameEvent("create_counters", _playerId) -{ - for (int i = 0; i < _counterList.size(); ++i) - itemList.append(_counterList[i]); -} - -Event_DrawCards::Event_DrawCards(int _playerId, int _numberCards, const QList &_cardList) - : GameEvent("draw_cards", _playerId) -{ - insertItem(new SerializableItem_Int("number_cards", _numberCards)); - for (int i = 0; i < _cardList.size(); ++i) - itemList.append(_cardList[i]); -} - -Event_RevealCards::Event_RevealCards(int _playerId, const QString &_zoneName, int _cardId, int _otherPlayerId, const QList &_cardList) - : GameEvent("reveal_cards", _playerId) -{ - insertItem(new SerializableItem_String("zone_name", _zoneName)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Int("other_player_id", _otherPlayerId)); - for (int i = 0; i < _cardList.size(); ++i) - itemList.append(_cardList[i]); -} diff --git a/common/protocol.h b/common/protocol.h deleted file mode 100644 index e95c21893..000000000 --- a/common/protocol.h +++ /dev/null @@ -1,534 +0,0 @@ -#ifndef PROTOCOL_H -#define PROTOCOL_H - -#include -#include -#include -#include -#include -#include "protocol_item_ids.h" -#include "protocol_datastructures.h" - -class QXmlStreamReader; -class QXmlStreamWriter; -class QXmlStreamAttributes; - -class ProtocolResponse; -class DeckList; -class GameEvent; -class GameEventContainer; -class GameEventContext; -class MoveCardToZone; - -enum ItemId { - ItemId_CommandContainer = ItemId_Other + 50, - ItemId_GameEventContainer = ItemId_Other + 51, - ItemId_Command_CreateGame = ItemId_Other + 99, - ItemId_Command_DeckUpload = ItemId_Other + 100, - ItemId_Command_DeckSelect = ItemId_Other + 101, - ItemId_Command_SetSideboardPlan = ItemId_Other + 102, - ItemId_Command_MoveCard = ItemId_Other + 103, - ItemId_Event_ListRooms = ItemId_Other + 200, - ItemId_Event_JoinRoom = ItemId_Other + 201, - ItemId_Event_ListGames = ItemId_Other + 203, - ItemId_Event_UserJoined = ItemId_Other + 204, - ItemId_Event_GameStateChanged = ItemId_Other + 205, - ItemId_Event_PlayerPropertiesChanged = ItemId_Other + 206, - ItemId_Event_CreateArrows = ItemId_Other + 207, - ItemId_Event_CreateCounters = ItemId_Other + 208, - ItemId_Event_DrawCards = ItemId_Other + 209, - ItemId_Event_RevealCards = ItemId_Other + 210, - ItemId_Event_Join = ItemId_Other + 211, - ItemId_Event_Ping = ItemId_Other + 212, - ItemId_Event_AddToList = ItemId_Other + 213, - ItemId_Response_ListUsers = ItemId_Other + 300, - ItemId_Response_GetGamesOfUser = ItemId_Other + 301, - ItemId_Response_GetUserInfo = ItemId_Other + 302, - ItemId_Response_DeckList = ItemId_Other + 303, - ItemId_Response_DeckDownload = ItemId_Other + 304, - ItemId_Response_DeckUpload = ItemId_Other + 305, - ItemId_Response_DumpZone = ItemId_Other + 306, - ItemId_Response_JoinRoom = ItemId_Other + 307, - ItemId_Response_Login = ItemId_Other + 308, - ItemId_Invalid = ItemId_Other + 1000 -}; - -class ProtocolItem : public SerializableItem_Map { - Q_OBJECT -private: - static void initializeHashAuto(); - bool receiverMayDelete; -public: - static const int protocolVersion = 13; - static void initializeHash(); - virtual int getItemId() const = 0; - bool getReceiverMayDelete() const { return receiverMayDelete; } - void setReceiverMayDelete(bool _receiverMayDelete) { receiverMayDelete = _receiverMayDelete; } - ProtocolItem(const QString &_itemType, const QString &_itemSubType); - bool isEmpty() const { return false; } -}; - -class ProtocolItem_Invalid : public ProtocolItem { -public: - ProtocolItem_Invalid() : ProtocolItem(QString(), QString()) { } - int getItemId() const { return ItemId_Invalid; } -}; - -class TopLevelProtocolItem : public SerializableItem { - Q_OBJECT -signals: - void protocolItemReceived(ProtocolItem *item); -private: - ProtocolItem *currentItem; - bool readCurrentItem(QXmlStreamReader *xml); -public: - TopLevelProtocolItem(); - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); - bool isEmpty() const { return false; } -}; - -// ---------------- -// --- COMMANDS --- -// ---------------- - -class Command : public ProtocolItem { - Q_OBJECT -signals: - void finished(ProtocolResponse *response); - void finished(ResponseCode response); -private: - QVariant extraData; -public: - Command(const QString &_itemName = QString()); - void setExtraData(const QVariant &_extraData) { extraData = _extraData; } - QVariant getExtraData() const { return extraData; } - void processResponse(ProtocolResponse *response); -}; - -class CommandContainer : public ProtocolItem { - Q_OBJECT -signals: - void finished(ProtocolResponse *response); - void finished(ResponseCode response); -private: - int ticks; - static int lastCmdId; - - // XXX Move these out. They are only for processing inside the server. - ProtocolResponse *resp; - QList itemQueue; - GameEventContext *gameEventContext; - GameEventContainer *gameEventQueuePublic; - GameEventContainer *gameEventQueueOmniscient; - GameEventContainer *gameEventQueuePrivate; - int privatePlayerId; -public: - CommandContainer(const QList &_commandList = QList(), int _cmdId = -1); - static SerializableItem *newItem() { return new CommandContainer; } - int getItemId() const { return ItemId_CommandContainer; } - int getCmdId() const { return static_cast(itemMap.value("cmd_id"))->getData(); } - int tick() { return ++ticks; } - void processResponse(ProtocolResponse *response); - QList getCommandList() const { return typecastItemList(); } - - ProtocolResponse *getResponse() const { return resp; } - void setResponse(ProtocolResponse *_resp); - const QList &getItemQueue() const { return itemQueue; } - void enqueueItem(ProtocolItem *item) { itemQueue.append(item); } - GameEventContainer *getGameEventQueuePublic() const { return gameEventQueuePublic; } - void enqueueGameEventPublic(GameEvent *event, int gameId, GameEventContext *context = 0); - GameEventContainer *getGameEventQueueOmniscient() const { return gameEventQueueOmniscient; } - void enqueueGameEventOmniscient(GameEvent *event, int gameId, GameEventContext *context = 0); - GameEventContainer *getGameEventQueuePrivate() const { return gameEventQueuePrivate; } - void enqueueGameEventPrivate(GameEvent *event, int gameId, int playerId = -1, GameEventContext *context = 0); - int getPrivatePlayerId() const { return privatePlayerId; } -}; - -class RoomCommand : public Command { - Q_OBJECT -public: - RoomCommand(const QString &_cmdName, int _roomId) - : Command(_cmdName) - { - insertItem(new SerializableItem_Int("room_id", _roomId)); - } - int getRoomId() const { return static_cast(itemMap.value("room_id"))->getData(); } -}; - -class GameCommand : public Command { - Q_OBJECT -public: - GameCommand(const QString &_cmdName, int _gameId) - : Command(_cmdName) - { - insertItem(new SerializableItem_Int("game_id", _gameId)); - } - int getGameId() const { return static_cast(itemMap.value("game_id"))->getData(); } - void setGameId(int _gameId) { static_cast(itemMap.value("game_id"))->setData(_gameId); } -}; - -class ModeratorCommand : public Command { - Q_OBJECT -public: - ModeratorCommand(const QString &_cmdName) - : Command(_cmdName) - { - } -}; - -class AdminCommand : public Command { - Q_OBJECT -public: - AdminCommand(const QString &_cmdName) - : Command(_cmdName) - { - } -}; - -class Command_CreateGame : public RoomCommand { - Q_OBJECT -public: - Command_CreateGame(int _roomId = -1, const QString &_description = QString(), const QString &_password = QString(), int _maxPlayers = -1, const QList &_gameTypes = QList(), bool _onlyBuddies = false, bool _onlyRegistered = false, bool _spectatorsAllowed = false, bool _spectatorsNeedPassword = false, bool _spectatorsCanTalk = false, bool _spectatorsSeeEverything = false); - QString getDescription() const { return static_cast(itemMap.value("description"))->getData(); }; - QString getPassword() const { return static_cast(itemMap.value("password"))->getData(); }; - int getMaxPlayers() const { return static_cast(itemMap.value("max_players"))->getData(); }; - bool getOnlyBuddies() const { return static_cast(itemMap.value("only_buddies"))->getData(); }; - bool getOnlyRegistered() const { return static_cast(itemMap.value("only_registered"))->getData(); }; - bool getSpectatorsAllowed() const { return static_cast(itemMap.value("spectators_allowed"))->getData(); }; - bool getSpectatorsNeedPassword() const { return static_cast(itemMap.value("spectators_need_password"))->getData(); }; - bool getSpectatorsCanTalk() const { return static_cast(itemMap.value("spectators_can_talk"))->getData(); }; - bool getSpectatorsSeeEverything() const { return static_cast(itemMap.value("spectators_see_everything"))->getData(); }; - QList getGameTypes() const { return typecastItemList(); } - static SerializableItem *newItem() { return new Command_CreateGame; } - int getItemId() const { return ItemId_Command_CreateGame; } -}; - -class Command_DeckUpload : public Command { - Q_OBJECT -public: - Command_DeckUpload(DeckList *_deck = 0, const QString &_path = QString()); - static SerializableItem *newItem() { return new Command_DeckUpload; } - int getItemId() const { return ItemId_Command_DeckUpload; } - DeckList *getDeck() const; - QString getPath() const { return static_cast(itemMap.value("path"))->getData(); } -}; - -class Command_DeckSelect : public GameCommand { - Q_OBJECT -public: - Command_DeckSelect(int _gameId = -1, DeckList *_deck = 0, int _deckId = -1); - static SerializableItem *newItem() { return new Command_DeckSelect; } - int getItemId() const { return ItemId_Command_DeckSelect; } - DeckList *getDeck() const; - int getDeckId() const { return static_cast(itemMap.value("deck_id"))->getData(); } -}; - -class Command_SetSideboardPlan : public GameCommand { - Q_OBJECT -public: - Command_SetSideboardPlan(int _gameId = -1, const QList &_moveList = QList()); - static SerializableItem *newItem() { return new Command_SetSideboardPlan; } - int getItemId() const { return ItemId_Command_SetSideboardPlan; } - QList getMoveList() const; -}; - -class Command_MoveCard : public GameCommand { - Q_OBJECT -public: - Command_MoveCard(int _gameId = -1, const QString &_startZone = QString(), const QList &_cards = QList(), int _targetPlayerId = -1, const QString &_targetZone = QString(), int _x = -1, int _y = -1); - QString getStartZone() const { return static_cast(itemMap.value("start_zone"))->getData(); } - QList getCards() const { return typecastItemList(); } - int getTargetPlayerId() const { return static_cast(itemMap.value("target_player_id"))->getData(); } - QString getTargetZone() const { return static_cast(itemMap.value("target_zone"))->getData(); } - int getX() const { return static_cast(itemMap.value("x"))->getData(); } - int getY() const { return static_cast(itemMap.value("y"))->getData(); } - static SerializableItem *newItem() { return new Command_MoveCard; } - int getItemId() const { return ItemId_Command_MoveCard; } -}; - -// ----------------- -// --- RESPONSES --- -// ----------------- - -class ProtocolResponse : public ProtocolItem { - Q_OBJECT -private: - static QHash responseHash; -public: - ProtocolResponse(int _cmdId = -1, ResponseCode _responseCode = RespNothing, const QString &_itemName = QString()); - int getItemId() const { return ItemId_Other; } - static void initializeHash(); - static SerializableItem *newItem() { return new ProtocolResponse; } - int getCmdId() const { return static_cast(itemMap.value("cmd_id"))->getData(); } - ResponseCode getResponseCode() const { return responseHash.value(static_cast(itemMap.value("response_code"))->getData(), RespOk); } -}; - -class Response_JoinRoom : public ProtocolResponse { - Q_OBJECT -public: - Response_JoinRoom(int _cmdId = -1, ResponseCode _responseCode = RespOk, ServerInfo_Room *_roomInfo = 0); - int getItemId() const { return ItemId_Response_JoinRoom; } - static SerializableItem *newItem() { return new Response_JoinRoom; } - ServerInfo_Room *getRoomInfo() const { return static_cast(itemMap.value("room")); } -}; - -class Response_ListUsers : public ProtocolResponse { - Q_OBJECT -public: - Response_ListUsers(int _cmdId = -1, ResponseCode _responseCode = RespOk, const QList &_userList = QList()); - int getItemId() const { return ItemId_Response_ListUsers; } - static SerializableItem *newItem() { return new Response_ListUsers; } - QList getUserList() const { return typecastItemList(); } -}; - -class Response_GetGamesOfUser : public ProtocolResponse { - Q_OBJECT -private: - QList gameList; - QList roomList; -protected: - void extractData(); -public: - Response_GetGamesOfUser(int _cmdId = -1, ResponseCode _responseCode = RespOk, const QList &_roomList = QList(), const QList &_gameList = QList()); - int getItemId() const { return ItemId_Response_GetGamesOfUser; } - static SerializableItem *newItem() { return new Response_GetGamesOfUser; } - QList getRoomList() const { return roomList; } - QList getGameList() const { return gameList; } -}; - -class Response_GetUserInfo : public ProtocolResponse { - Q_OBJECT -public: - Response_GetUserInfo(int _cmdId = -1, ResponseCode _responseCode = RespOk, ServerInfo_User *_userInfo = 0); - int getItemId() const { return ItemId_Response_GetUserInfo; } - static SerializableItem *newItem() { return new Response_GetUserInfo; } - ServerInfo_User *getUserInfo() const { return static_cast(itemMap.value("user")); } -}; - -class Response_DeckList : public ProtocolResponse { - Q_OBJECT -public: - Response_DeckList(int _cmdId = -1, ResponseCode _responseCode = RespOk, DeckList_Directory *_root = 0); - int getItemId() const { return ItemId_Response_DeckList; } - static SerializableItem *newItem() { return new Response_DeckList; } - DeckList_Directory *getRoot() const { return static_cast(itemMap.value("directory")); } -}; - -class Response_DeckDownload : public ProtocolResponse { - Q_OBJECT -public: - Response_DeckDownload(int _cmdId = -1, ResponseCode _responseCode = RespOk, DeckList *_deck = 0); - int getItemId() const { return ItemId_Response_DeckDownload; } - static SerializableItem *newItem() { return new Response_DeckDownload; } - DeckList *getDeck() const; -}; - -class Response_DeckUpload : public ProtocolResponse { - Q_OBJECT -public: - Response_DeckUpload(int _cmdId = -1, ResponseCode _responseCode = RespOk, DeckList_File *_file = 0); - int getItemId() const { return ItemId_Response_DeckUpload; } - static SerializableItem *newItem() { return new Response_DeckUpload; } - DeckList_File *getFile() const { return static_cast(itemMap.value("file")); } -}; - -class Response_DumpZone : public ProtocolResponse { - Q_OBJECT -public: - Response_DumpZone(int _cmdId = -1, ResponseCode _responseCode = RespOk, ServerInfo_Zone *zone = 0); - int getItemId() const { return ItemId_Response_DumpZone; } - static SerializableItem *newItem() { return new Response_DumpZone; } - ServerInfo_Zone *getZone() const { return static_cast(itemMap.value("zone")); } -}; - -class Response_Login : public ProtocolResponse { - Q_OBJECT -public: - Response_Login(int _cmdId = -1, ResponseCode _responseCode = RespOk, ServerInfo_User *_userInfo = 0, const QList &_buddyList = QList(), const QList &_ignoreList = QList()); - int getItemId() const { return ItemId_Response_Login; } - static SerializableItem *newItem() { return new Response_Login; } - ServerInfo_User *getUserInfo() const { return static_cast(itemMap.value("user")); } - QList getBuddyList() const { return static_cast(itemMap.value("buddy_list"))->getUserList(); } - QList getIgnoreList() const { return static_cast(itemMap.value("ignore_list"))->getUserList(); } -}; - -// -------------- -// --- EVENTS --- -// -------------- - -class GenericEvent : public ProtocolItem { - Q_OBJECT -public: - GenericEvent(const QString &_eventName) - : ProtocolItem("generic_event", _eventName) { } -}; - -class GameEvent : public ProtocolItem { - Q_OBJECT -public: - GameEvent(const QString &_eventName, int _playerId); - int getPlayerId() const { return static_cast(itemMap.value("player_id"))->getData(); } -}; - -class GameEventContext : public ProtocolItem { - Q_OBJECT -public: - GameEventContext(const QString &_contextName); -}; - -class GameEventContainer : public ProtocolItem { - Q_OBJECT -private: - QList eventList; - GameEventContext *context; -protected: - void extractData(); -public: - GameEventContainer(const QList &_eventList = QList(), int _gameId = -1, GameEventContext *_context = 0); - static SerializableItem *newItem() { return new GameEventContainer; } - int getItemId() const { return ItemId_GameEventContainer; } - QList getEventList() const { return eventList; } - GameEventContext *getContext() const { return context; } - void setContext(GameEventContext *_context); - void addGameEvent(GameEvent *event); - static GameEventContainer *makeNew(GameEvent *event, int _gameId); - - int getGameId() const { return static_cast(itemMap.value("game_id"))->getData(); } - void setGameId(int _gameId) { static_cast(itemMap.value("game_id"))->setData(_gameId); } -}; - -class RoomEvent : public ProtocolItem { - Q_OBJECT -public: - RoomEvent(const QString &_eventName, int _roomId); - int getRoomId() const { return static_cast(itemMap.value("room_id"))->getData(); } -}; - -class Event_ListRooms : public GenericEvent { - Q_OBJECT -public: - Event_ListRooms(const QList &_roomList = QList()); - int getItemId() const { return ItemId_Event_ListRooms; } - static SerializableItem *newItem() { return new Event_ListRooms; } - QList getRoomList() const { return typecastItemList(); } -}; - -class Event_JoinRoom : public RoomEvent { - Q_OBJECT -public: - Event_JoinRoom(int _roomId = -1, ServerInfo_User *_info = 0); - int getItemId() const { return ItemId_Event_JoinRoom; } - static SerializableItem *newItem() { return new Event_JoinRoom; } - ServerInfo_User *getUserInfo() const { return static_cast(itemMap.value("user")); } -}; - -class Event_ListGames : public RoomEvent { - Q_OBJECT -public: - Event_ListGames(int _roomId = -1, const QList &_gameList = QList()); - int getItemId() const { return ItemId_Event_ListGames; } - static SerializableItem *newItem() { return new Event_ListGames; } - QList getGameList() const { return typecastItemList(); } -}; - -class Event_AddToList : public GenericEvent { - Q_OBJECT -public: - Event_AddToList(const QString &_list = QString(), ServerInfo_User *_userInfo = 0); - int getItemId() const { return ItemId_Event_AddToList; } - static SerializableItem *newItem() { return new Event_AddToList; } - ServerInfo_User *getUserInfo() const { return static_cast(itemMap.value("user")); } - QString getList() const { return static_cast(itemMap.value("list"))->getData(); } -}; - -class Event_UserJoined : public GenericEvent { - Q_OBJECT -public: - Event_UserJoined(ServerInfo_User *_userInfo = 0); - int getItemId() const { return ItemId_Event_UserJoined; } - static SerializableItem *newItem() { return new Event_UserJoined; } - ServerInfo_User *getUserInfo() const { return static_cast(itemMap.value("user")); } -}; - -class Event_Join : public GameEvent { - Q_OBJECT -public: - Event_Join(ServerInfo_PlayerProperties *player = 0); - static SerializableItem *newItem() { return new Event_Join; } - int getItemId() const { return ItemId_Event_Join; } - ServerInfo_PlayerProperties *getPlayer() const { return static_cast(itemMap.value("player_properties")); } -}; - -class Event_GameStateChanged : public GameEvent { - Q_OBJECT -public: - Event_GameStateChanged(bool _gameStarted = false, int _activePlayer = -1, int _activePhase = -1, const QList &_playerList = QList()); - static SerializableItem *newItem() { return new Event_GameStateChanged; } - int getItemId() const { return ItemId_Event_GameStateChanged; } - QList getPlayerList() const { return typecastItemList(); } - bool getGameStarted() const { return static_cast(itemMap.value("game_started"))->getData(); } - int getActivePlayer() const { return static_cast(itemMap.value("active_player"))->getData(); } - int getActivePhase() const { return static_cast(itemMap.value("active_phase"))->getData(); } -}; - -class Event_PlayerPropertiesChanged : public GameEvent { - Q_OBJECT -public: - Event_PlayerPropertiesChanged(int _playerId = -1, ServerInfo_PlayerProperties *_properties = 0); - static SerializableItem *newItem() { return new Event_PlayerPropertiesChanged; } - int getItemId() const { return ItemId_Event_PlayerPropertiesChanged; } - ServerInfo_PlayerProperties *getProperties() const { return static_cast(itemMap.value("player_properties")); } -}; - -class Event_Ping : public GameEvent { - Q_OBJECT -public: - Event_Ping(int _secondsElapsed = -1, const QList &_pingList = QList()); - static SerializableItem *newItem() { return new Event_Ping; } - int getItemId() const { return ItemId_Event_Ping; } - int getSecondsElapsed() const { return static_cast(itemMap.value("seconds_elapsed"))->getData(); } - QList getPingList() const { return typecastItemList(); } -}; - -class Event_CreateArrows : public GameEvent { - Q_OBJECT -public: - Event_CreateArrows(int _playerId = -1, const QList &_arrowList = QList()); - int getItemId() const { return ItemId_Event_CreateArrows; } - static SerializableItem *newItem() { return new Event_CreateArrows; } - QList getArrowList() const { return typecastItemList(); } -}; - -class Event_CreateCounters : public GameEvent { - Q_OBJECT -public: - Event_CreateCounters(int _playerId = -1, const QList &_counterList = QList()); - int getItemId() const { return ItemId_Event_CreateCounters; } - static SerializableItem *newItem() { return new Event_CreateCounters; } - QList getCounterList() const { return typecastItemList(); } -}; - -class Event_DrawCards : public GameEvent { - Q_OBJECT -public: - Event_DrawCards(int _playerId = -1, int numberCards = -1, const QList &_cardList = QList()); - int getItemId() const { return ItemId_Event_DrawCards; } - static SerializableItem *newItem() { return new Event_DrawCards; } - int getNumberCards() const { return static_cast(itemMap.value("number_cards"))->getData(); } - QList getCardList() const { return typecastItemList(); } -}; - -class Event_RevealCards : public GameEvent { - Q_OBJECT -public: - Event_RevealCards(int _playerId = -1, const QString &_zoneName = QString(), int cardId = -1, int _otherPlayerId = -1, const QList &_cardList = QList()); - int getItemId() const { return ItemId_Event_RevealCards; } - static SerializableItem *newItem() { return new Event_RevealCards; } - QString getZoneName() const { return static_cast(itemMap.value("zone_name"))->getData(); } - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); } - int getOtherPlayerId() const { return static_cast(itemMap.value("other_player_id"))->getData(); } - QList getCardList() const { return typecastItemList(); } -}; - -#endif diff --git a/common/protocol_datastructures.cpp b/common/protocol_datastructures.cpp deleted file mode 100644 index 62f52182c..000000000 --- a/common/protocol_datastructures.cpp +++ /dev/null @@ -1,292 +0,0 @@ -#include "protocol_datastructures.h" -#include "decklist.h" -#include -#include - -CardToMove::CardToMove(int _cardId, bool _faceDown, const QString &_pt, bool _tapped) - : SerializableItem_Map("card_to_move") -{ - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Bool("facedown", _faceDown)); - insertItem(new SerializableItem_String("pt", _pt)); - insertItem(new SerializableItem_Bool("tapped", _tapped)); -} - -ServerInfo_User::ServerInfo_User(const QString &_name, int _userLevel, const QString &_address, const QString &_realName, Gender _gender, const QString &_country, const QByteArray &_avatarBmp) - : SerializableItem_Map("user") -{ - insertItem(new SerializableItem_String("name", _name)); - insertItem(new SerializableItem_Int("userlevel", _userLevel)); - insertItem(new SerializableItem_String("address", _address)); - insertItem(new SerializableItem_String("real_name", _realName)); - insertItem(new SerializableItem_Int("gender", _gender)); - insertItem(new SerializableItem_String("country", _country)); - insertItem(new SerializableItem_ByteArray("avatar_bmp", _avatarBmp)); -} - -ServerInfo_User::ServerInfo_User(const ServerInfo_User *other, bool complete, bool moderatorInfo) - : SerializableItem_Map("user") -{ - insertItem(new SerializableItem_String("name", other->getName())); - insertItem(new SerializableItem_Int("userlevel", other->getUserLevel())); - insertItem(new SerializableItem_String("address", moderatorInfo ? other->getAddress() : QString())); - insertItem(new SerializableItem_String("real_name", other->getRealName())); - insertItem(new SerializableItem_Int("gender", other->getGender())); - insertItem(new SerializableItem_String("country", other->getCountry())); - insertItem(new SerializableItem_ByteArray("avatar_bmp", complete ? other->getAvatarBmp() : QByteArray())); -} - -ServerInfo_UserList::ServerInfo_UserList(const QString &_itemType, const QList &_userList) - : SerializableItem_Map(_itemType) -{ - for (int i = 0; i < _userList.size(); ++i) - itemList.append(_userList[i]); -} - -ServerInfo_Game::ServerInfo_Game(int _roomId, int _gameId, const QString &_description, bool _hasPassword, int _playerCount, int _maxPlayers, bool _started, const QList &_gameTypes, ServerInfo_User *_creatorInfo, bool _onlyBuddies, bool _onlyRegistered, bool _spectatorsAllowed, bool _spectatorsNeedPassword, int _spectatorCount) - : SerializableItem_Map("game") -{ - insertItem(new SerializableItem_Int("room_id", _roomId)); - insertItem(new SerializableItem_Int("game_id", _gameId)); - insertItem(new SerializableItem_String("description", _description)); - insertItem(new SerializableItem_Bool("has_password", _hasPassword)); - insertItem(new SerializableItem_Int("player_count", _playerCount)); - insertItem(new SerializableItem_Int("max_players", _maxPlayers)); - insertItem(new SerializableItem_Bool("started", _started)); - if (!_creatorInfo) - _creatorInfo = new ServerInfo_User; - insertItem(_creatorInfo); - insertItem(new SerializableItem_Bool("only_buddies", _onlyBuddies)); - insertItem(new SerializableItem_Bool("only_registered", _onlyRegistered)); - insertItem(new SerializableItem_Bool("spectators_allowed", _spectatorsAllowed)); - insertItem(new SerializableItem_Bool("spectators_need_password", _spectatorsNeedPassword)); - insertItem(new SerializableItem_Int("spectator_count", _spectatorCount)); - - for (int i = 0; i < _gameTypes.size(); ++i) - itemList.append(_gameTypes[i]); -} - -ServerInfo_GameType::ServerInfo_GameType(int _gameTypeId, const QString &_description) - : SerializableItem_Map("game_type") -{ - insertItem(new SerializableItem_Int("game_type_id", _gameTypeId)); - insertItem(new SerializableItem_String("description", _description)); -} - -ServerInfo_Room::ServerInfo_Room(int _roomId, const QString &_name, const QString &_description, int _gameCount, int _playerCount, bool _autoJoin, const QList &_gameList, const QList &_userList, const QList &_gameTypeList) - : SerializableItem_Map("room") -{ - insertItem(new SerializableItem_Int("room_id", _roomId)); - insertItem(new SerializableItem_String("name", _name)); - insertItem(new SerializableItem_String("description", _description)); - insertItem(new SerializableItem_Int("game_count", _gameCount)); - insertItem(new SerializableItem_Int("player_count", _playerCount)); - insertItem(new SerializableItem_Bool("auto_join", _autoJoin)); - - gameList = _gameList; - for (int i = 0; i < _gameList.size(); ++i) - itemList.append(_gameList[i]); - userList = _userList; - for (int i = 0; i < _userList.size(); ++i) - itemList.append(_userList[i]); - gameTypeList = _gameTypeList; - for (int i = 0; i < _gameTypeList.size(); ++i) - itemList.append(_gameTypeList[i]); -} - -void ServerInfo_Room::extractData() -{ - for (int i = 0; i < itemList.size(); ++i) { - ServerInfo_User *user = dynamic_cast(itemList[i]); - if (user) { - userList.append(user); - continue; - } - ServerInfo_Game *game = dynamic_cast(itemList[i]); - if (game) { - gameList.append(game); - continue; - } - ServerInfo_GameType *gameType = dynamic_cast(itemList[i]); - if (gameType) { - gameTypeList.append(gameType); - continue; - } - } -} - -ServerInfo_CardCounter::ServerInfo_CardCounter(int _id, int _value) - : SerializableItem_Map("card_counter") -{ - insertItem(new SerializableItem_Int("id", _id)); - insertItem(new SerializableItem_Int("value", _value)); -} - -ServerInfo_Card::ServerInfo_Card(int _id, const QString &_name, int _x, int _y, bool _faceDown, bool _tapped, bool _attacking, const QString &_color, const QString &_pt, const QString &_annotation, bool _destroyOnZoneChange, bool _doesntUntap, const QList &_counters, int _attachPlayerId, const QString &_attachZone, int _attachCardId) - : SerializableItem_Map("card") -{ - insertItem(new SerializableItem_Int("id", _id)); - insertItem(new SerializableItem_String("name", _name)); - insertItem(new SerializableItem_Int("x", _x)); - insertItem(new SerializableItem_Int("y", _y)); - insertItem(new SerializableItem_Bool("facedown", _faceDown)); - insertItem(new SerializableItem_Bool("tapped", _tapped)); - insertItem(new SerializableItem_Bool("attacking", _attacking)); - insertItem(new SerializableItem_String("color", _color)); - insertItem(new SerializableItem_String("pt", _pt)); - insertItem(new SerializableItem_String("annotation", _annotation)); - insertItem(new SerializableItem_Bool("destroy_on_zone_change", _destroyOnZoneChange)); - insertItem(new SerializableItem_Bool("doesnt_untap", _doesntUntap)); - insertItem(new SerializableItem_Int("attach_player_id", _attachPlayerId)); - insertItem(new SerializableItem_String("attach_zone", _attachZone)); - insertItem(new SerializableItem_Int("attach_card_id", _attachCardId)); - - for (int i = 0; i < _counters.size(); ++i) - itemList.append(_counters[i]); -} - -ServerInfo_Zone::ServerInfo_Zone(const QString &_name, ZoneType _type, bool _hasCoords, int _cardCount, const QList &_cardList) - : SerializableItem_Map("zone") -{ - insertItem(new SerializableItem_String("name", _name)); - insertItem(new SerializableItem_String("zone_type", typeToString(_type))); - insertItem(new SerializableItem_Bool("has_coords", _hasCoords)); - insertItem(new SerializableItem_Int("card_count", _cardCount)); - - for (int i = 0; i < _cardList.size(); ++i) - itemList.append(_cardList[i]); -} - -ZoneType ServerInfo_Zone::typeFromString(const QString &type) const -{ - if (type == "private") - return PrivateZone; - else if (type == "hidden") - return HiddenZone; - return PublicZone; -} - -QString ServerInfo_Zone::typeToString(ZoneType type) const -{ - switch (type) { - case PrivateZone: return "private"; - case HiddenZone: return "hidden"; - default: return "public"; - } -} - -QList ServerInfo_Zone::getCardList() const -{ - QList result; - for (int i = 0; i < itemList.size(); ++i) { - ServerInfo_Card *card = dynamic_cast(itemList[i]); - if (card) - result.append(card); - } - return result; -} - -ServerInfo_Counter::ServerInfo_Counter(int _id, const QString &_name, const Color &_color, int _radius, int _count) - : SerializableItem_Map("counter") -{ - insertItem(new SerializableItem_Int("id", _id)); - insertItem(new SerializableItem_String("name", _name)); - insertItem(new SerializableItem_Color("color", _color)); - insertItem(new SerializableItem_Int("radius", _radius)); - insertItem(new SerializableItem_Int("count", _count)); -} - -ServerInfo_Arrow::ServerInfo_Arrow(int _id, int _startPlayerId, const QString &_startZone, int _startCardId, int _targetPlayerId, const QString &_targetZone, int _targetCardId, const Color &_color) - : SerializableItem_Map("arrow") -{ - insertItem(new SerializableItem_Int("id", _id)); - insertItem(new SerializableItem_Int("start_player_id", _startPlayerId)); - insertItem(new SerializableItem_String("start_zone", _startZone)); - insertItem(new SerializableItem_Int("start_card_id", _startCardId)); - insertItem(new SerializableItem_Int("target_player_id", _targetPlayerId)); - insertItem(new SerializableItem_String("target_zone", _targetZone)); - insertItem(new SerializableItem_Int("target_card_id", _targetCardId)); - insertItem(new SerializableItem_Color("color", _color)); -} - -ServerInfo_PlayerProperties::ServerInfo_PlayerProperties(int _playerId, ServerInfo_User *_userInfo, bool _spectator, bool _conceded, bool _readyStart, const QString &_deckHash) - : SerializableItem_Map("player_properties") -{ - insertItem(new SerializableItem_Int("player_id", _playerId)); - if (!_userInfo) - _userInfo = new ServerInfo_User; - insertItem(_userInfo); - insertItem(new SerializableItem_Bool("spectator", _spectator)); - insertItem(new SerializableItem_Bool("conceded", _conceded)); - insertItem(new SerializableItem_Bool("ready_start", _readyStart)); - insertItem(new SerializableItem_String("deck_hash", _deckHash)); -} - -ServerInfo_Player::ServerInfo_Player(ServerInfo_PlayerProperties *_properties, DeckList *_deck, const QList &_zoneList, const QList &_counterList, const QList &_arrowList) - : SerializableItem_Map("player"), zoneList(_zoneList), counterList(_counterList), arrowList(_arrowList) -{ - if (!_properties) - insertItem(new ServerInfo_PlayerProperties); - else - insertItem(_properties); - - if (!_deck) - insertItem(new DeckList); - else - insertItem(new DeckList(_deck)); - - zoneList = _zoneList; - for (int i = 0; i < _zoneList.size(); ++i) - itemList.append(_zoneList[i]); - counterList = _counterList; - for (int i = 0; i < _counterList.size(); ++i) - itemList.append(_counterList[i]); - arrowList = _arrowList; - for (int i = 0; i < _arrowList.size(); ++i) - itemList.append(_arrowList[i]); -} - -void ServerInfo_Player::extractData() -{ - for (int i = 0; i < itemList.size(); ++i) { - ServerInfo_Zone *zone = dynamic_cast(itemList[i]); - ServerInfo_Counter *counter = dynamic_cast(itemList[i]); - ServerInfo_Arrow *arrow = dynamic_cast(itemList[i]); - if (zone) - zoneList.append(zone); - else if (counter) - counterList.append(counter); - else if (arrow) - arrowList.append(arrow); - } -} - -DeckList *ServerInfo_Player::getDeck() const -{ - return static_cast(itemMap.value("cockatrice_deck")); -} - -ServerInfo_PlayerPing::ServerInfo_PlayerPing(int _playerId, int _pingTime) - : SerializableItem_Map("player_ping") -{ - insertItem(new SerializableItem_Int("player_id", _playerId)); - insertItem(new SerializableItem_Int("ping_time", _pingTime)); -} - -DeckList_TreeItem::DeckList_TreeItem(const QString &_itemType, const QString &_name, int _id) - : SerializableItem_Map(_itemType) -{ - insertItem(new SerializableItem_String("name", _name)); - insertItem(new SerializableItem_Int("id", _id)); -} - -DeckList_File::DeckList_File(const QString &_name, int _id, QDateTime _uploadTime) - : DeckList_TreeItem("file", _name, _id) -{ - insertItem(new SerializableItem_DateTime("upload_time", _uploadTime)); -} - -DeckList_Directory::DeckList_Directory(const QString &_name, int _id) - : DeckList_TreeItem("directory", _name, _id) -{ -} diff --git a/common/protocol_datastructures.h b/common/protocol_datastructures.h deleted file mode 100644 index 705227065..000000000 --- a/common/protocol_datastructures.h +++ /dev/null @@ -1,248 +0,0 @@ -#ifndef PROTOCOL_DATASTRUCTURES_H -#define PROTOCOL_DATASTRUCTURES_H - -#include -#include -#include "serializable_item.h" -#include "color.h" - -class DeckList; - -enum ResponseCode { RespNothing, RespOk, RespNotInRoom, RespInternalError, RespInvalidCommand, RespInvalidData, RespNameNotFound, RespLoginNeeded, RespFunctionNotAllowed, RespGameNotStarted, RespGameFull, RespContextError, RespWrongPassword, RespSpectatorsNotAllowed, RespOnlyBuddies, RespUserLevelTooLow, RespInIgnoreList, RespWouldOverwriteOldSession, RespChatFlood }; - -// PrivateZone: Contents of the zone are always visible to the owner, -// but not to anyone else. -// PublicZone: Contents of the zone are always visible to anyone. -// HiddenZone: Contents of the zone are never visible to anyone. -// However, the owner of the zone can issue a dump_zone command, -// setting beingLookedAt to true. -// Cards in a zone with the type HiddenZone are referenced by their -// list index, whereas cards in any other zone are referenced by their ids. -enum ZoneType { PrivateZone, PublicZone, HiddenZone }; - -class CardToMove : public SerializableItem_Map { -public: - CardToMove(int _cardId = -1, bool _faceDown = false, const QString &_pt = QString(), bool _tapped = false); - static SerializableItem *newItem() { return new CardToMove; } - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); } - bool getFaceDown() const { return static_cast(itemMap.value("facedown"))->getData(); } - QString getPT() const { return static_cast(itemMap.value("pt"))->getData(); } - bool getTapped() const { return static_cast(itemMap.value("tapped"))->getData(); } -}; - -class GameTypeId : public SerializableItem_Int { -public: - GameTypeId(int _gameTypeId = -1) : SerializableItem_Int("game_type_id", _gameTypeId) { } - static SerializableItem *newItem() { return new GameTypeId; } -}; - -class ServerInfo_User : public SerializableItem_Map { -public: - enum UserLevelFlags { - IsNothing = 0x00, - IsUser = 0x01, - IsRegistered = 0x02, - IsModerator = 0x04, - IsAdmin = 0x08 - }; - enum Gender { - GenderUnknown = -1, - Male = 0, - Female = 1 - }; - ServerInfo_User(const QString &_name = QString(), int _userLevel = IsNothing, const QString &_address = QString(), const QString &_realName = QString(), Gender _gender = GenderUnknown, const QString &_country = QString(), const QByteArray &_avatarBmp = QByteArray()); - ServerInfo_User(const ServerInfo_User *other, bool complete = true, bool moderatorInfo = false); - static SerializableItem *newItem() { return new ServerInfo_User; } - QString getName() const { return static_cast(itemMap.value("name"))->getData(); } - void setName(const QString &_name) { static_cast(itemMap.value("name"))->setData(_name); } - int getUserLevel() const { return static_cast(itemMap.value("userlevel"))->getData(); } - void setUserLevel(int _userLevel) { static_cast(itemMap.value("userlevel"))->setData(_userLevel); } - QString getAddress() const { return static_cast(itemMap.value("address"))->getData(); } - void setAddress(const QString &_address) { static_cast(itemMap.value("address"))->setData(_address); } - QString getRealName() const { return static_cast(itemMap.value("real_name"))->getData(); } - Gender getGender() const { return static_cast(static_cast(itemMap.value("gender"))->getData()); } - QString getCountry() const { return static_cast(itemMap.value("country"))->getData(); } - QByteArray getAvatarBmp() const { return static_cast(itemMap.value("avatar_bmp"))->getData(); } -}; - -class ServerInfo_UserList : public SerializableItem_Map { -public: - ServerInfo_UserList(const QString &_itemType, const QList &_userList = QList()); - QList getUserList() const { return typecastItemList(); } -}; - -class ServerInfo_Game : public SerializableItem_Map { -public: - ServerInfo_Game(int _roomId = -1, int _gameId = -1, const QString &_description = QString(), bool _hasPassword = false, int _playerCount = -1, int _maxPlayers = -1, bool _started = false, const QList &_gameTypes = QList(), ServerInfo_User *creatorInfo = 0, bool _onlyBuddies = false, bool _onlyRegistered = false, bool _spectatorsAllowed = false, bool _spectatorsNeedPassword = false, int _spectatorCount = -1); - static SerializableItem *newItem() { return new ServerInfo_Game; } - int getRoomId() const { return static_cast(itemMap.value("room_id"))->getData(); } - int getGameId() const { return static_cast(itemMap.value("game_id"))->getData(); } - QString getDescription() const { return static_cast(itemMap.value("description"))->getData(); } - bool getHasPassword() const { return static_cast(itemMap.value("has_password"))->getData(); } - int getPlayerCount() const { return static_cast(itemMap.value("player_count"))->getData(); } - int getMaxPlayers() const { return static_cast(itemMap.value("max_players"))->getData(); } - bool getStarted() const { return static_cast(itemMap.value("started"))->getData(); } - QList getGameTypes() const { return typecastItemList(); } - ServerInfo_User *getCreatorInfo() const { return static_cast(itemMap.value("user")); } - bool getOnlyBuddies() const { return static_cast(itemMap.value("only_buddies"))->getData(); } - bool getOnlyRegistered() const { return static_cast(itemMap.value("only_registered"))->getData(); } - bool getSpectatorsAllowed() const { return static_cast(itemMap.value("spectators_allowed"))->getData(); } - bool getSpectatorsNeedPassword() const { return static_cast(itemMap.value("spectators_need_password"))->getData(); } - int getSpectatorCount() const { return static_cast(itemMap.value("spectator_count"))->getData(); } -}; - -class ServerInfo_GameType : public SerializableItem_Map { -public: - ServerInfo_GameType(int _gameTypeId = -1, const QString &_description = QString()); - static SerializableItem *newItem() { return new ServerInfo_GameType; } - int getGameTypeId() const { return static_cast(itemMap.value("game_type_id"))->getData(); } - QString getDescription() const { return static_cast(itemMap.value("description"))->getData(); } -}; - -class ServerInfo_Room : public SerializableItem_Map { -private: - QList gameList; - QList userList; - QList gameTypeList; -protected: - void extractData(); -public: - ServerInfo_Room(int _id = -1, const QString &_name = QString(), const QString &_description = QString(), int _gameCount = -1, int _playerCount = -1, bool _autoJoin = false, const QList &_gameList = QList(), const QList &_userList = QList(), const QList &_gameTypeList = QList()); - static SerializableItem *newItem() { return new ServerInfo_Room; } - int getRoomId() const { return static_cast(itemMap.value("room_id"))->getData(); } - QString getName() const { return static_cast(itemMap.value("name"))->getData(); } - QString getDescription() const { return static_cast(itemMap.value("description"))->getData(); } - int getGameCount() const { return static_cast(itemMap.value("game_count"))->getData(); } - int getPlayerCount() const { return static_cast(itemMap.value("player_count"))->getData(); } - bool getAutoJoin() const { return static_cast(itemMap.value("auto_join"))->getData(); } - const QList &getGameList() const { return gameList; } - const QList &getUserList() const { return userList; } - const QList &getGameTypeList() const { return gameTypeList; } -}; - -class ServerInfo_CardCounter : public SerializableItem_Map { -public: - ServerInfo_CardCounter(int _id = -1, int _value = 0); - static SerializableItem *newItem() { return new ServerInfo_CardCounter; } - int getId() const { return static_cast(itemMap.value("id"))->getData(); } - int getValue() const { return static_cast(itemMap.value("value"))->getData(); } -}; - -class ServerInfo_Card : public SerializableItem_Map { -public: - ServerInfo_Card(int _id = -1, const QString &_name = QString(), int _x = -1, int _y = -1, bool _faceDown = false, bool _tapped = false, bool _attacking = false, const QString &_color = QString(), const QString &_pt = QString(), const QString &_annotation = QString(), bool _destroyOnZoneChange = false, bool _doesntUntap = false, const QList &_counterList = QList(), int attachPlayerId = -1, const QString &_attachZone = QString(), int attachCardId = -1); - static SerializableItem *newItem() { return new ServerInfo_Card; } - int getId() const { return static_cast(itemMap.value("id"))->getData(); } - QString getName() const { return static_cast(itemMap.value("name"))->getData(); } - int getX() const { return static_cast(itemMap.value("x"))->getData(); } - int getY() const { return static_cast(itemMap.value("y"))->getData(); } - bool getFaceDown() const { return static_cast(itemMap.value("facedown"))->getData(); } - bool getTapped() const { return static_cast(itemMap.value("tapped"))->getData(); } - bool getAttacking() const { return static_cast(itemMap.value("attacking"))->getData(); } - QString getColor() const { return static_cast(itemMap.value("color"))->getData(); } - QString getPT() const { return static_cast(itemMap.value("pt"))->getData(); } - QString getAnnotation() const { return static_cast(itemMap.value("annotation"))->getData(); } - bool getDestroyOnZoneChange() const { return static_cast(itemMap.value("destroy_on_zone_change"))->getData(); } - bool getDoesntUntap() const { return static_cast(itemMap.value("doesnt_untap"))->getData(); } - QList getCounters() const { return typecastItemList(); } - int getAttachPlayerId() const { return static_cast(itemMap.value("attach_player_id"))->getData(); } - QString getAttachZone() const { return static_cast(itemMap.value("attach_zone"))->getData(); } - int getAttachCardId() const { return static_cast(itemMap.value("attach_card_id"))->getData(); } -}; - -class ServerInfo_Zone : public SerializableItem_Map { -private: - ZoneType typeFromString(const QString &type) const; - QString typeToString(ZoneType type) const; -public: - ServerInfo_Zone(const QString &_name = QString(), ZoneType _type = PrivateZone, bool _hasCoords = false, int _cardCount = -1, const QList &_cardList = QList()); - static SerializableItem *newItem() { return new ServerInfo_Zone; } - QString getName() const { return static_cast(itemMap.value("name"))->getData(); } - ZoneType getType() const { return typeFromString(static_cast(itemMap.value("type"))->getData()); } - bool getHasCoords() const { return static_cast(itemMap.value("has_coords"))->getData(); } - int getCardCount() const { return static_cast(itemMap.value("card_count"))->getData(); } - QList getCardList() const; -}; - -class ServerInfo_Counter : public SerializableItem_Map { -public: - ServerInfo_Counter(int _id = -1, const QString &_name = QString(), const Color &_color = Color(), int _radius = -1, int _count = -1); - static SerializableItem *newItem() { return new ServerInfo_Counter; } - int getId() const { return static_cast(itemMap.value("id"))->getData(); } - QString getName() const { return static_cast(itemMap.value("name"))->getData(); } - Color getColor() const { return static_cast(itemMap.value("color"))->getData(); } - int getRadius() const { return static_cast(itemMap.value("radius"))->getData(); } - int getCount() const { return static_cast(itemMap.value("count"))->getData(); } -}; - -class ServerInfo_Arrow : public SerializableItem_Map { -public: - ServerInfo_Arrow(int _id = -1, int _startPlayerId = -1, const QString &_startZone = QString(), int _startCardId = -1, int _targetPlayerId = -1, const QString &_targetZone = QString(), int _targetCardId = -1, const Color &_color = Color()); - static SerializableItem *newItem() { return new ServerInfo_Arrow; } - int getId() const { return static_cast(itemMap.value("id"))->getData(); } - int getStartPlayerId() const { return static_cast(itemMap.value("start_player_id"))->getData(); } - QString getStartZone() const { return static_cast(itemMap.value("start_zone"))->getData(); } - int getStartCardId() const { return static_cast(itemMap.value("start_card_id"))->getData(); } - int getTargetPlayerId() const { return static_cast(itemMap.value("target_player_id"))->getData(); } - QString getTargetZone() const { return static_cast(itemMap.value("target_zone"))->getData(); } - int getTargetCardId() const { return static_cast(itemMap.value("target_card_id"))->getData(); } - Color getColor() const { return static_cast(itemMap.value("color"))->getData(); } -}; - -class ServerInfo_PlayerProperties : public SerializableItem_Map { -public: - ServerInfo_PlayerProperties(int _playerId = -1, ServerInfo_User *_userInfo = 0, bool _spectator = false, bool _conceded = false, bool _readyStart = false, const QString &_deckHash = QString()); - static SerializableItem *newItem() { return new ServerInfo_PlayerProperties; } - int getPlayerId() const { return static_cast(itemMap.value("player_id"))->getData(); } - ServerInfo_User *getUserInfo() const { return static_cast(itemMap.value("user")); } - bool getSpectator() const { return static_cast(itemMap.value("spectator"))->getData(); } - bool getConceded() const { return static_cast(itemMap.value("conceded"))->getData(); } - bool getReadyStart() const { return static_cast(itemMap.value("ready_start"))->getData(); } - QString getDeckHash() const { return static_cast(itemMap.value("deck_hash"))->getData(); } -}; - -class ServerInfo_Player : public SerializableItem_Map { -private: - QList zoneList; - QList counterList; - QList arrowList; -protected: - void extractData(); -public: - ServerInfo_Player(ServerInfo_PlayerProperties *_properties = 0, DeckList *_deck = 0, const QList &_zoneList = QList(), const QList &_counterList = QList(), const QList &_arrowList = QList()); - static SerializableItem *newItem() { return new ServerInfo_Player; } - ServerInfo_PlayerProperties *getProperties() const { return static_cast(itemMap.value("player_properties")); } - DeckList *getDeck() const; - const QList &getZoneList() const { return zoneList; } - const QList &getCounterList() const { return counterList; } - const QList &getArrowList() const { return arrowList; } -}; - -class ServerInfo_PlayerPing : public SerializableItem_Map { -public: - ServerInfo_PlayerPing(int _playerId = -1, int _pingTime = -1); - static SerializableItem *newItem() { return new ServerInfo_PlayerPing; } - int getPlayerId() const { return static_cast(itemMap.value("player_id"))->getData(); } - int getPingTime() const { return static_cast(itemMap.value("ping_time"))->getData(); } -}; - -class DeckList_TreeItem : public SerializableItem_Map { -public: - DeckList_TreeItem(const QString &_itemType, const QString &_name, int _id); - QString getName() const { return static_cast(itemMap.value("name"))->getData(); } - int getId() const { return static_cast(itemMap.value("id"))->getData(); } -}; -class DeckList_File : public DeckList_TreeItem { -public: - DeckList_File(const QString &_name = QString(), int _id = -1, QDateTime _uploadTime = QDateTime()); - static SerializableItem *newItem() { return new DeckList_File; } - QDateTime getUploadTime() const { return static_cast(itemMap.value("upload_time"))->getData(); } -}; -class DeckList_Directory : public DeckList_TreeItem { -public: - DeckList_Directory(const QString &_name = QString(), int _id = 0); - static SerializableItem *newItem() { return new DeckList_Directory; } - QList getTreeItems() const { return typecastItemList(); } -}; - -#endif diff --git a/common/protocol_item_ids.h b/common/protocol_item_ids.h deleted file mode 100644 index 0187628f0..000000000 --- a/common/protocol_item_ids.h +++ /dev/null @@ -1,88 +0,0 @@ -enum AutoItemId { -ItemId_Command_Ping = 1001, -ItemId_Command_Login = 1002, -ItemId_Command_Message = 1003, -ItemId_Command_ListUsers = 1004, -ItemId_Command_GetGamesOfUser = 1005, -ItemId_Command_GetUserInfo = 1006, -ItemId_Command_AddToList = 1007, -ItemId_Command_RemoveFromList = 1008, -ItemId_Command_DeckList = 1009, -ItemId_Command_DeckNewDir = 1010, -ItemId_Command_DeckDelDir = 1011, -ItemId_Command_DeckDel = 1012, -ItemId_Command_DeckDownload = 1013, -ItemId_Command_ListRooms = 1014, -ItemId_Command_JoinRoom = 1015, -ItemId_Command_LeaveRoom = 1016, -ItemId_Command_RoomSay = 1017, -ItemId_Command_JoinGame = 1018, -ItemId_Command_KickFromGame = 1019, -ItemId_Command_LeaveGame = 1020, -ItemId_Command_Say = 1021, -ItemId_Command_Shuffle = 1022, -ItemId_Command_Mulligan = 1023, -ItemId_Command_RollDie = 1024, -ItemId_Command_DrawCards = 1025, -ItemId_Command_UndoDraw = 1026, -ItemId_Command_FlipCard = 1027, -ItemId_Command_AttachCard = 1028, -ItemId_Command_CreateToken = 1029, -ItemId_Command_CreateArrow = 1030, -ItemId_Command_DeleteArrow = 1031, -ItemId_Command_SetCardAttr = 1032, -ItemId_Command_SetCardCounter = 1033, -ItemId_Command_IncCardCounter = 1034, -ItemId_Command_ReadyStart = 1035, -ItemId_Command_Concede = 1036, -ItemId_Command_IncCounter = 1037, -ItemId_Command_CreateCounter = 1038, -ItemId_Command_SetCounter = 1039, -ItemId_Command_DelCounter = 1040, -ItemId_Command_NextTurn = 1041, -ItemId_Command_SetActivePhase = 1042, -ItemId_Command_DumpZone = 1043, -ItemId_Command_StopDumpZone = 1044, -ItemId_Command_RevealCards = 1045, -ItemId_Event_ConnectionStateChanged = 1046, -ItemId_Event_Say = 1047, -ItemId_Event_Leave = 1048, -ItemId_Event_GameClosed = 1049, -ItemId_Event_GameHostChanged = 1050, -ItemId_Event_Kicked = 1051, -ItemId_Event_Shuffle = 1052, -ItemId_Event_RollDie = 1053, -ItemId_Event_MoveCard = 1054, -ItemId_Event_FlipCard = 1055, -ItemId_Event_DestroyCard = 1056, -ItemId_Event_AttachCard = 1057, -ItemId_Event_CreateToken = 1058, -ItemId_Event_DeleteArrow = 1059, -ItemId_Event_SetCardAttr = 1060, -ItemId_Event_SetCardCounter = 1061, -ItemId_Event_SetCounter = 1062, -ItemId_Event_DelCounter = 1063, -ItemId_Event_SetActivePlayer = 1064, -ItemId_Event_SetActivePhase = 1065, -ItemId_Event_DumpZone = 1066, -ItemId_Event_StopDumpZone = 1067, -ItemId_Event_RemoveFromList = 1068, -ItemId_Event_ServerMessage = 1069, -ItemId_Event_ServerShutdown = 1070, -ItemId_Event_ConnectionClosed = 1071, -ItemId_Event_Message = 1072, -ItemId_Event_GameJoined = 1073, -ItemId_Event_UserLeft = 1074, -ItemId_Event_LeaveRoom = 1075, -ItemId_Event_RoomSay = 1076, -ItemId_Context_ReadyStart = 1077, -ItemId_Context_Concede = 1078, -ItemId_Context_DeckSelect = 1079, -ItemId_Context_UndoDraw = 1080, -ItemId_Context_MoveCard = 1081, -ItemId_Context_Mulligan = 1082, -ItemId_Command_UpdateServerMessage = 1083, -ItemId_Command_ShutdownServer = 1084, -ItemId_Command_BanFromServer = 1085, -ItemId_Other = 1086 -}; diff --git a/common/protocol_items.cpp b/common/protocol_items.cpp deleted file mode 100644 index d46e88872..000000000 --- a/common/protocol_items.cpp +++ /dev/null @@ -1,593 +0,0 @@ -#include "protocol.h" -#include "protocol_items.h" - -Command_Ping::Command_Ping() - : Command("ping") -{ -} -Command_Login::Command_Login(const QString &_username, const QString &_password) - : Command("login") -{ - insertItem(new SerializableItem_String("username", _username)); - insertItem(new SerializableItem_String("password", _password)); -} -Command_Message::Command_Message(const QString &_userName, const QString &_text) - : Command("message") -{ - insertItem(new SerializableItem_String("user_name", _userName)); - insertItem(new SerializableItem_String("text", _text)); -} -Command_ListUsers::Command_ListUsers() - : Command("list_users") -{ -} -Command_GetGamesOfUser::Command_GetGamesOfUser(const QString &_userName) - : Command("get_games_of_user") -{ - insertItem(new SerializableItem_String("user_name", _userName)); -} -Command_GetUserInfo::Command_GetUserInfo(const QString &_userName) - : Command("get_user_info") -{ - insertItem(new SerializableItem_String("user_name", _userName)); -} -Command_AddToList::Command_AddToList(const QString &_list, const QString &_userName) - : Command("add_to_list") -{ - insertItem(new SerializableItem_String("list", _list)); - insertItem(new SerializableItem_String("user_name", _userName)); -} -Command_RemoveFromList::Command_RemoveFromList(const QString &_list, const QString &_userName) - : Command("remove_from_list") -{ - insertItem(new SerializableItem_String("list", _list)); - insertItem(new SerializableItem_String("user_name", _userName)); -} -Command_DeckList::Command_DeckList() - : Command("deck_list") -{ -} -Command_DeckNewDir::Command_DeckNewDir(const QString &_path, const QString &_dirName) - : Command("deck_new_dir") -{ - insertItem(new SerializableItem_String("path", _path)); - insertItem(new SerializableItem_String("dir_name", _dirName)); -} -Command_DeckDelDir::Command_DeckDelDir(const QString &_path) - : Command("deck_del_dir") -{ - insertItem(new SerializableItem_String("path", _path)); -} -Command_DeckDel::Command_DeckDel(int _deckId) - : Command("deck_del") -{ - insertItem(new SerializableItem_Int("deck_id", _deckId)); -} -Command_DeckDownload::Command_DeckDownload(int _deckId) - : Command("deck_download") -{ - insertItem(new SerializableItem_Int("deck_id", _deckId)); -} -Command_ListRooms::Command_ListRooms() - : Command("list_rooms") -{ -} -Command_JoinRoom::Command_JoinRoom(int _roomId) - : Command("join_room") -{ - insertItem(new SerializableItem_Int("room_id", _roomId)); -} -Command_LeaveRoom::Command_LeaveRoom(int _roomId) - : RoomCommand("leave_room", _roomId) -{ -} -Command_RoomSay::Command_RoomSay(int _roomId, const QString &_message) - : RoomCommand("room_say", _roomId) -{ - insertItem(new SerializableItem_String("message", _message)); -} -Command_JoinGame::Command_JoinGame(int _roomId, int _gameId, const QString &_password, bool _spectator, bool _overrideRestrictions) - : RoomCommand("join_game", _roomId) -{ - insertItem(new SerializableItem_Int("game_id", _gameId)); - insertItem(new SerializableItem_String("password", _password)); - insertItem(new SerializableItem_Bool("spectator", _spectator)); - insertItem(new SerializableItem_Bool("override_restrictions", _overrideRestrictions)); -} -Command_KickFromGame::Command_KickFromGame(int _gameId, int _playerId) - : GameCommand("kick_from_game", _gameId) -{ - insertItem(new SerializableItem_Int("player_id", _playerId)); -} -Command_LeaveGame::Command_LeaveGame(int _gameId) - : GameCommand("leave_game", _gameId) -{ -} -Command_Say::Command_Say(int _gameId, const QString &_message) - : GameCommand("say", _gameId) -{ - insertItem(new SerializableItem_String("message", _message)); -} -Command_Shuffle::Command_Shuffle(int _gameId) - : GameCommand("shuffle", _gameId) -{ -} -Command_Mulligan::Command_Mulligan(int _gameId) - : GameCommand("mulligan", _gameId) -{ -} -Command_RollDie::Command_RollDie(int _gameId, int _sides) - : GameCommand("roll_die", _gameId) -{ - insertItem(new SerializableItem_Int("sides", _sides)); -} -Command_DrawCards::Command_DrawCards(int _gameId, int _number) - : GameCommand("draw_cards", _gameId) -{ - insertItem(new SerializableItem_Int("number", _number)); -} -Command_UndoDraw::Command_UndoDraw(int _gameId) - : GameCommand("undo_draw", _gameId) -{ -} -Command_FlipCard::Command_FlipCard(int _gameId, const QString &_zone, int _cardId, bool _faceDown) - : GameCommand("flip_card", _gameId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Bool("face_down", _faceDown)); -} -Command_AttachCard::Command_AttachCard(int _gameId, const QString &_startZone, int _cardId, int _targetPlayerId, const QString &_targetZone, int _targetCardId) - : GameCommand("attach_card", _gameId) -{ - insertItem(new SerializableItem_String("start_zone", _startZone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Int("target_player_id", _targetPlayerId)); - insertItem(new SerializableItem_String("target_zone", _targetZone)); - insertItem(new SerializableItem_Int("target_card_id", _targetCardId)); -} -Command_CreateToken::Command_CreateToken(int _gameId, const QString &_zone, const QString &_cardName, const QString &_color, const QString &_pt, const QString &_annotation, bool _destroy, int _x, int _y) - : GameCommand("create_token", _gameId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_String("card_name", _cardName)); - insertItem(new SerializableItem_String("color", _color)); - insertItem(new SerializableItem_String("pt", _pt)); - insertItem(new SerializableItem_String("annotation", _annotation)); - insertItem(new SerializableItem_Bool("destroy", _destroy)); - insertItem(new SerializableItem_Int("x", _x)); - insertItem(new SerializableItem_Int("y", _y)); -} -Command_CreateArrow::Command_CreateArrow(int _gameId, int _startPlayerId, const QString &_startZone, int _startCardId, int _targetPlayerId, const QString &_targetZone, int _targetCardId, const Color &_color) - : GameCommand("create_arrow", _gameId) -{ - insertItem(new SerializableItem_Int("start_player_id", _startPlayerId)); - insertItem(new SerializableItem_String("start_zone", _startZone)); - insertItem(new SerializableItem_Int("start_card_id", _startCardId)); - insertItem(new SerializableItem_Int("target_player_id", _targetPlayerId)); - insertItem(new SerializableItem_String("target_zone", _targetZone)); - insertItem(new SerializableItem_Int("target_card_id", _targetCardId)); - insertItem(new SerializableItem_Color("color", _color)); -} -Command_DeleteArrow::Command_DeleteArrow(int _gameId, int _arrowId) - : GameCommand("delete_arrow", _gameId) -{ - insertItem(new SerializableItem_Int("arrow_id", _arrowId)); -} -Command_SetCardAttr::Command_SetCardAttr(int _gameId, const QString &_zone, int _cardId, const QString &_attrName, const QString &_attrValue) - : GameCommand("set_card_attr", _gameId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_String("attr_name", _attrName)); - insertItem(new SerializableItem_String("attr_value", _attrValue)); -} -Command_SetCardCounter::Command_SetCardCounter(int _gameId, const QString &_zone, int _cardId, int _counterId, int _counterValue) - : GameCommand("set_card_counter", _gameId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Int("counter_id", _counterId)); - insertItem(new SerializableItem_Int("counter_value", _counterValue)); -} -Command_IncCardCounter::Command_IncCardCounter(int _gameId, const QString &_zone, int _cardId, int _counterId, int _counterDelta) - : GameCommand("inc_card_counter", _gameId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Int("counter_id", _counterId)); - insertItem(new SerializableItem_Int("counter_delta", _counterDelta)); -} -Command_ReadyStart::Command_ReadyStart(int _gameId, bool _ready) - : GameCommand("ready_start", _gameId) -{ - insertItem(new SerializableItem_Bool("ready", _ready)); -} -Command_Concede::Command_Concede(int _gameId) - : GameCommand("concede", _gameId) -{ -} -Command_IncCounter::Command_IncCounter(int _gameId, int _counterId, int _delta) - : GameCommand("inc_counter", _gameId) -{ - insertItem(new SerializableItem_Int("counter_id", _counterId)); - insertItem(new SerializableItem_Int("delta", _delta)); -} -Command_CreateCounter::Command_CreateCounter(int _gameId, const QString &_counterName, const Color &_color, int _radius, int _value) - : GameCommand("create_counter", _gameId) -{ - insertItem(new SerializableItem_String("counter_name", _counterName)); - insertItem(new SerializableItem_Color("color", _color)); - insertItem(new SerializableItem_Int("radius", _radius)); - insertItem(new SerializableItem_Int("value", _value)); -} -Command_SetCounter::Command_SetCounter(int _gameId, int _counterId, int _value) - : GameCommand("set_counter", _gameId) -{ - insertItem(new SerializableItem_Int("counter_id", _counterId)); - insertItem(new SerializableItem_Int("value", _value)); -} -Command_DelCounter::Command_DelCounter(int _gameId, int _counterId) - : GameCommand("del_counter", _gameId) -{ - insertItem(new SerializableItem_Int("counter_id", _counterId)); -} -Command_NextTurn::Command_NextTurn(int _gameId) - : GameCommand("next_turn", _gameId) -{ -} -Command_SetActivePhase::Command_SetActivePhase(int _gameId, int _phase) - : GameCommand("set_active_phase", _gameId) -{ - insertItem(new SerializableItem_Int("phase", _phase)); -} -Command_DumpZone::Command_DumpZone(int _gameId, int _playerId, const QString &_zoneName, int _numberCards) - : GameCommand("dump_zone", _gameId) -{ - insertItem(new SerializableItem_Int("player_id", _playerId)); - insertItem(new SerializableItem_String("zone_name", _zoneName)); - insertItem(new SerializableItem_Int("number_cards", _numberCards)); -} -Command_StopDumpZone::Command_StopDumpZone(int _gameId, int _playerId, const QString &_zoneName) - : GameCommand("stop_dump_zone", _gameId) -{ - insertItem(new SerializableItem_Int("player_id", _playerId)); - insertItem(new SerializableItem_String("zone_name", _zoneName)); -} -Command_RevealCards::Command_RevealCards(int _gameId, const QString &_zoneName, int _cardId, int _playerId) - : GameCommand("reveal_cards", _gameId) -{ - insertItem(new SerializableItem_String("zone_name", _zoneName)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Int("player_id", _playerId)); -} -Event_ConnectionStateChanged::Event_ConnectionStateChanged(int _playerId, bool _connected) - : GameEvent("connection_state_changed", _playerId) -{ - insertItem(new SerializableItem_Bool("connected", _connected)); -} -Event_Say::Event_Say(int _playerId, const QString &_message) - : GameEvent("say", _playerId) -{ - insertItem(new SerializableItem_String("message", _message)); -} -Event_Leave::Event_Leave(int _playerId) - : GameEvent("leave", _playerId) -{ -} -Event_GameClosed::Event_GameClosed(int _playerId) - : GameEvent("game_closed", _playerId) -{ -} -Event_GameHostChanged::Event_GameHostChanged(int _playerId) - : GameEvent("game_host_changed", _playerId) -{ -} -Event_Kicked::Event_Kicked(int _playerId) - : GameEvent("kicked", _playerId) -{ -} -Event_Shuffle::Event_Shuffle(int _playerId) - : GameEvent("shuffle", _playerId) -{ -} -Event_RollDie::Event_RollDie(int _playerId, int _sides, int _value) - : GameEvent("roll_die", _playerId) -{ - insertItem(new SerializableItem_Int("sides", _sides)); - insertItem(new SerializableItem_Int("value", _value)); -} -Event_MoveCard::Event_MoveCard(int _playerId, int _cardId, const QString &_cardName, const QString &_startZone, int _position, int _targetPlayerId, const QString &_targetZone, int _x, int _y, int _newCardId, bool _faceDown) - : GameEvent("move_card", _playerId) -{ - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_String("card_name", _cardName)); - insertItem(new SerializableItem_String("start_zone", _startZone)); - insertItem(new SerializableItem_Int("position", _position)); - insertItem(new SerializableItem_Int("target_player_id", _targetPlayerId)); - insertItem(new SerializableItem_String("target_zone", _targetZone)); - insertItem(new SerializableItem_Int("x", _x)); - insertItem(new SerializableItem_Int("y", _y)); - insertItem(new SerializableItem_Int("new_card_id", _newCardId)); - insertItem(new SerializableItem_Bool("face_down", _faceDown)); -} -Event_FlipCard::Event_FlipCard(int _playerId, const QString &_zone, int _cardId, const QString &_cardName, bool _faceDown) - : GameEvent("flip_card", _playerId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_String("card_name", _cardName)); - insertItem(new SerializableItem_Bool("face_down", _faceDown)); -} -Event_DestroyCard::Event_DestroyCard(int _playerId, const QString &_zone, int _cardId) - : GameEvent("destroy_card", _playerId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); -} -Event_AttachCard::Event_AttachCard(int _playerId, const QString &_startZone, int _cardId, int _targetPlayerId, const QString &_targetZone, int _targetCardId) - : GameEvent("attach_card", _playerId) -{ - insertItem(new SerializableItem_String("start_zone", _startZone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Int("target_player_id", _targetPlayerId)); - insertItem(new SerializableItem_String("target_zone", _targetZone)); - insertItem(new SerializableItem_Int("target_card_id", _targetCardId)); -} -Event_CreateToken::Event_CreateToken(int _playerId, const QString &_zone, int _cardId, const QString &_cardName, const QString &_color, const QString &_pt, const QString &_annotation, bool _destroyOnZoneChange, int _x, int _y) - : GameEvent("create_token", _playerId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_String("card_name", _cardName)); - insertItem(new SerializableItem_String("color", _color)); - insertItem(new SerializableItem_String("pt", _pt)); - insertItem(new SerializableItem_String("annotation", _annotation)); - insertItem(new SerializableItem_Bool("destroy_on_zone_change", _destroyOnZoneChange)); - insertItem(new SerializableItem_Int("x", _x)); - insertItem(new SerializableItem_Int("y", _y)); -} -Event_DeleteArrow::Event_DeleteArrow(int _playerId, int _arrowId) - : GameEvent("delete_arrow", _playerId) -{ - insertItem(new SerializableItem_Int("arrow_id", _arrowId)); -} -Event_SetCardAttr::Event_SetCardAttr(int _playerId, const QString &_zone, int _cardId, const QString &_attrName, const QString &_attrValue) - : GameEvent("set_card_attr", _playerId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_String("attr_name", _attrName)); - insertItem(new SerializableItem_String("attr_value", _attrValue)); -} -Event_SetCardCounter::Event_SetCardCounter(int _playerId, const QString &_zone, int _cardId, int _counterId, int _counterValue) - : GameEvent("set_card_counter", _playerId) -{ - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("card_id", _cardId)); - insertItem(new SerializableItem_Int("counter_id", _counterId)); - insertItem(new SerializableItem_Int("counter_value", _counterValue)); -} -Event_SetCounter::Event_SetCounter(int _playerId, int _counterId, int _value) - : GameEvent("set_counter", _playerId) -{ - insertItem(new SerializableItem_Int("counter_id", _counterId)); - insertItem(new SerializableItem_Int("value", _value)); -} -Event_DelCounter::Event_DelCounter(int _playerId, int _counterId) - : GameEvent("del_counter", _playerId) -{ - insertItem(new SerializableItem_Int("counter_id", _counterId)); -} -Event_SetActivePlayer::Event_SetActivePlayer(int _playerId, int _activePlayerId) - : GameEvent("set_active_player", _playerId) -{ - insertItem(new SerializableItem_Int("active_player_id", _activePlayerId)); -} -Event_SetActivePhase::Event_SetActivePhase(int _playerId, int _phase) - : GameEvent("set_active_phase", _playerId) -{ - insertItem(new SerializableItem_Int("phase", _phase)); -} -Event_DumpZone::Event_DumpZone(int _playerId, int _zoneOwnerId, const QString &_zone, int _numberCards) - : GameEvent("dump_zone", _playerId) -{ - insertItem(new SerializableItem_Int("zone_owner_id", _zoneOwnerId)); - insertItem(new SerializableItem_String("zone", _zone)); - insertItem(new SerializableItem_Int("number_cards", _numberCards)); -} -Event_StopDumpZone::Event_StopDumpZone(int _playerId, int _zoneOwnerId, const QString &_zone) - : GameEvent("stop_dump_zone", _playerId) -{ - insertItem(new SerializableItem_Int("zone_owner_id", _zoneOwnerId)); - insertItem(new SerializableItem_String("zone", _zone)); -} -Event_RemoveFromList::Event_RemoveFromList(const QString &_list, const QString &_userName) - : GenericEvent("remove_from_list") -{ - insertItem(new SerializableItem_String("list", _list)); - insertItem(new SerializableItem_String("user_name", _userName)); -} -Event_ServerMessage::Event_ServerMessage(const QString &_message) - : GenericEvent("server_message") -{ - insertItem(new SerializableItem_String("message", _message)); -} -Event_ServerShutdown::Event_ServerShutdown(const QString &_reason, int _minutes) - : GenericEvent("server_shutdown") -{ - insertItem(new SerializableItem_String("reason", _reason)); - insertItem(new SerializableItem_Int("minutes", _minutes)); -} -Event_ConnectionClosed::Event_ConnectionClosed(const QString &_reason) - : GenericEvent("connection_closed") -{ - insertItem(new SerializableItem_String("reason", _reason)); -} -Event_Message::Event_Message(const QString &_senderName, const QString &_receiverName, const QString &_text) - : GenericEvent("message") -{ - insertItem(new SerializableItem_String("sender_name", _senderName)); - insertItem(new SerializableItem_String("receiver_name", _receiverName)); - insertItem(new SerializableItem_String("text", _text)); -} -Event_GameJoined::Event_GameJoined(int _gameId, const QString &_gameDescription, int _hostId, int _playerId, bool _spectator, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, bool _resuming) - : GenericEvent("game_joined") -{ - insertItem(new SerializableItem_Int("game_id", _gameId)); - insertItem(new SerializableItem_String("game_description", _gameDescription)); - insertItem(new SerializableItem_Int("host_id", _hostId)); - insertItem(new SerializableItem_Int("player_id", _playerId)); - insertItem(new SerializableItem_Bool("spectator", _spectator)); - insertItem(new SerializableItem_Bool("spectators_can_talk", _spectatorsCanTalk)); - insertItem(new SerializableItem_Bool("spectators_see_everything", _spectatorsSeeEverything)); - insertItem(new SerializableItem_Bool("resuming", _resuming)); -} -Event_UserLeft::Event_UserLeft(const QString &_userName) - : GenericEvent("user_left") -{ - insertItem(new SerializableItem_String("user_name", _userName)); -} -Event_LeaveRoom::Event_LeaveRoom(int _roomId, const QString &_playerName) - : RoomEvent("leave_room", _roomId) -{ - insertItem(new SerializableItem_String("player_name", _playerName)); -} -Event_RoomSay::Event_RoomSay(int _roomId, const QString &_playerName, const QString &_message) - : RoomEvent("room_say", _roomId) -{ - insertItem(new SerializableItem_String("player_name", _playerName)); - insertItem(new SerializableItem_String("message", _message)); -} -Context_ReadyStart::Context_ReadyStart() - : GameEventContext("ready_start") -{ -} -Context_Concede::Context_Concede() - : GameEventContext("concede") -{ -} -Context_DeckSelect::Context_DeckSelect(const QString &_deckHash) - : GameEventContext("deck_select") -{ - insertItem(new SerializableItem_String("deck_hash", _deckHash)); -} -Context_UndoDraw::Context_UndoDraw() - : GameEventContext("undo_draw") -{ -} -Context_MoveCard::Context_MoveCard() - : GameEventContext("move_card") -{ -} -Context_Mulligan::Context_Mulligan(int _number) - : GameEventContext("mulligan") -{ - insertItem(new SerializableItem_Int("number", _number)); -} -Command_UpdateServerMessage::Command_UpdateServerMessage() - : AdminCommand("update_server_message") -{ -} -Command_ShutdownServer::Command_ShutdownServer(const QString &_reason, int _minutes) - : AdminCommand("shutdown_server") -{ - insertItem(new SerializableItem_String("reason", _reason)); - insertItem(new SerializableItem_Int("minutes", _minutes)); -} -Command_BanFromServer::Command_BanFromServer(const QString &_userName, const QString &_address, int _minutes, const QString &_reason) - : ModeratorCommand("ban_from_server") -{ - insertItem(new SerializableItem_String("user_name", _userName)); - insertItem(new SerializableItem_String("address", _address)); - insertItem(new SerializableItem_Int("minutes", _minutes)); - insertItem(new SerializableItem_String("reason", _reason)); -} -void ProtocolItem::initializeHashAuto() -{ - itemNameHash.insert("cmdping", Command_Ping::newItem); - itemNameHash.insert("cmdlogin", Command_Login::newItem); - itemNameHash.insert("cmdmessage", Command_Message::newItem); - itemNameHash.insert("cmdlist_users", Command_ListUsers::newItem); - itemNameHash.insert("cmdget_games_of_user", Command_GetGamesOfUser::newItem); - itemNameHash.insert("cmdget_user_info", Command_GetUserInfo::newItem); - itemNameHash.insert("cmdadd_to_list", Command_AddToList::newItem); - itemNameHash.insert("cmdremove_from_list", Command_RemoveFromList::newItem); - itemNameHash.insert("cmddeck_list", Command_DeckList::newItem); - itemNameHash.insert("cmddeck_new_dir", Command_DeckNewDir::newItem); - itemNameHash.insert("cmddeck_del_dir", Command_DeckDelDir::newItem); - itemNameHash.insert("cmddeck_del", Command_DeckDel::newItem); - itemNameHash.insert("cmddeck_download", Command_DeckDownload::newItem); - itemNameHash.insert("cmdlist_rooms", Command_ListRooms::newItem); - itemNameHash.insert("cmdjoin_room", Command_JoinRoom::newItem); - itemNameHash.insert("cmdleave_room", Command_LeaveRoom::newItem); - itemNameHash.insert("cmdroom_say", Command_RoomSay::newItem); - itemNameHash.insert("cmdjoin_game", Command_JoinGame::newItem); - itemNameHash.insert("cmdkick_from_game", Command_KickFromGame::newItem); - itemNameHash.insert("cmdleave_game", Command_LeaveGame::newItem); - itemNameHash.insert("cmdsay", Command_Say::newItem); - itemNameHash.insert("cmdshuffle", Command_Shuffle::newItem); - itemNameHash.insert("cmdmulligan", Command_Mulligan::newItem); - itemNameHash.insert("cmdroll_die", Command_RollDie::newItem); - itemNameHash.insert("cmddraw_cards", Command_DrawCards::newItem); - itemNameHash.insert("cmdundo_draw", Command_UndoDraw::newItem); - itemNameHash.insert("cmdflip_card", Command_FlipCard::newItem); - itemNameHash.insert("cmdattach_card", Command_AttachCard::newItem); - itemNameHash.insert("cmdcreate_token", Command_CreateToken::newItem); - itemNameHash.insert("cmdcreate_arrow", Command_CreateArrow::newItem); - itemNameHash.insert("cmddelete_arrow", Command_DeleteArrow::newItem); - itemNameHash.insert("cmdset_card_attr", Command_SetCardAttr::newItem); - itemNameHash.insert("cmdset_card_counter", Command_SetCardCounter::newItem); - itemNameHash.insert("cmdinc_card_counter", Command_IncCardCounter::newItem); - itemNameHash.insert("cmdready_start", Command_ReadyStart::newItem); - itemNameHash.insert("cmdconcede", Command_Concede::newItem); - itemNameHash.insert("cmdinc_counter", Command_IncCounter::newItem); - itemNameHash.insert("cmdcreate_counter", Command_CreateCounter::newItem); - itemNameHash.insert("cmdset_counter", Command_SetCounter::newItem); - itemNameHash.insert("cmddel_counter", Command_DelCounter::newItem); - itemNameHash.insert("cmdnext_turn", Command_NextTurn::newItem); - itemNameHash.insert("cmdset_active_phase", Command_SetActivePhase::newItem); - itemNameHash.insert("cmddump_zone", Command_DumpZone::newItem); - itemNameHash.insert("cmdstop_dump_zone", Command_StopDumpZone::newItem); - itemNameHash.insert("cmdreveal_cards", Command_RevealCards::newItem); - itemNameHash.insert("game_eventconnection_state_changed", Event_ConnectionStateChanged::newItem); - itemNameHash.insert("game_eventsay", Event_Say::newItem); - itemNameHash.insert("game_eventleave", Event_Leave::newItem); - itemNameHash.insert("game_eventgame_closed", Event_GameClosed::newItem); - itemNameHash.insert("game_eventgame_host_changed", Event_GameHostChanged::newItem); - itemNameHash.insert("game_eventkicked", Event_Kicked::newItem); - itemNameHash.insert("game_eventshuffle", Event_Shuffle::newItem); - itemNameHash.insert("game_eventroll_die", Event_RollDie::newItem); - itemNameHash.insert("game_eventmove_card", Event_MoveCard::newItem); - itemNameHash.insert("game_eventflip_card", Event_FlipCard::newItem); - itemNameHash.insert("game_eventdestroy_card", Event_DestroyCard::newItem); - itemNameHash.insert("game_eventattach_card", Event_AttachCard::newItem); - itemNameHash.insert("game_eventcreate_token", Event_CreateToken::newItem); - itemNameHash.insert("game_eventdelete_arrow", Event_DeleteArrow::newItem); - itemNameHash.insert("game_eventset_card_attr", Event_SetCardAttr::newItem); - itemNameHash.insert("game_eventset_card_counter", Event_SetCardCounter::newItem); - itemNameHash.insert("game_eventset_counter", Event_SetCounter::newItem); - itemNameHash.insert("game_eventdel_counter", Event_DelCounter::newItem); - itemNameHash.insert("game_eventset_active_player", Event_SetActivePlayer::newItem); - itemNameHash.insert("game_eventset_active_phase", Event_SetActivePhase::newItem); - itemNameHash.insert("game_eventdump_zone", Event_DumpZone::newItem); - itemNameHash.insert("game_eventstop_dump_zone", Event_StopDumpZone::newItem); - itemNameHash.insert("generic_eventremove_from_list", Event_RemoveFromList::newItem); - itemNameHash.insert("generic_eventserver_message", Event_ServerMessage::newItem); - itemNameHash.insert("generic_eventserver_shutdown", Event_ServerShutdown::newItem); - itemNameHash.insert("generic_eventconnection_closed", Event_ConnectionClosed::newItem); - itemNameHash.insert("generic_eventmessage", Event_Message::newItem); - itemNameHash.insert("generic_eventgame_joined", Event_GameJoined::newItem); - itemNameHash.insert("generic_eventuser_left", Event_UserLeft::newItem); - itemNameHash.insert("room_eventleave_room", Event_LeaveRoom::newItem); - itemNameHash.insert("room_eventroom_say", Event_RoomSay::newItem); - itemNameHash.insert("game_event_contextready_start", Context_ReadyStart::newItem); - itemNameHash.insert("game_event_contextconcede", Context_Concede::newItem); - itemNameHash.insert("game_event_contextdeck_select", Context_DeckSelect::newItem); - itemNameHash.insert("game_event_contextundo_draw", Context_UndoDraw::newItem); - itemNameHash.insert("game_event_contextmove_card", Context_MoveCard::newItem); - itemNameHash.insert("game_event_contextmulligan", Context_Mulligan::newItem); - itemNameHash.insert("cmdupdate_server_message", Command_UpdateServerMessage::newItem); - itemNameHash.insert("cmdshutdown_server", Command_ShutdownServer::newItem); - itemNameHash.insert("cmdban_from_server", Command_BanFromServer::newItem); -} diff --git a/common/protocol_items.dat b/common/protocol_items.dat deleted file mode 100644 index a638db35b..000000000 --- a/common/protocol_items.dat +++ /dev/null @@ -1,85 +0,0 @@ -0:ping -0:login:s,username:s,password -0:message:s,user_name:s,text -0:list_users -0:get_games_of_user:s,user_name -0:get_user_info:s,user_name -0:add_to_list:s,list:s,user_name -0:remove_from_list:s,list:s,user_name -0:deck_list -0:deck_new_dir:s,path:s,dir_name -0:deck_del_dir:s,path -0:deck_del:i,deck_id -0:deck_download:i,deck_id -0:list_rooms -0:join_room:i,room_id -1:leave_room -1:room_say:s,message -1:join_game:i,game_id:s,password:b,spectator:b,override_restrictions -2:kick_from_game:i,player_id -2:leave_game -2:say:s,message -2:shuffle -2:mulligan -2:roll_die:i,sides -2:draw_cards:i,number -2:undo_draw -2:flip_card:s,zone:i,card_id:b,face_down -2:attach_card:s,start_zone:i,card_id:i,target_player_id:s,target_zone:i,target_card_id -2:create_token:s,zone:s,card_name:s,color:s,pt:s,annotation:b,destroy:i,x:i,y -2:create_arrow:i,start_player_id:s,start_zone:i,start_card_id:i,target_player_id:s,target_zone:i,target_card_id:c,color -2:delete_arrow:i,arrow_id -2:set_card_attr:s,zone:i,card_id:s,attr_name:s,attr_value -2:set_card_counter:s,zone:i,card_id:i,counter_id:i,counter_value -2:inc_card_counter:s,zone:i,card_id:i,counter_id:i,counter_delta -2:ready_start:b,ready -2:concede -2:inc_counter:i,counter_id:i,delta -2:create_counter:s,counter_name:c,color:i,radius:i,value -2:set_counter:i,counter_id:i,value -2:del_counter:i,counter_id -2:next_turn -2:set_active_phase:i,phase -2:dump_zone:i,player_id:s,zone_name:i,number_cards -2:stop_dump_zone:i,player_id:s,zone_name -2:reveal_cards:s,zone_name:i,card_id:i,player_id -3:connection_state_changed:b,connected -3:say:s,message -3:leave -3:game_closed -3:game_host_changed -3:kicked -3:shuffle -3:roll_die:i,sides:i,value -3:move_card:i,card_id:s,card_name:s,start_zone:i,position:i,target_player_id:s,target_zone:i,x:i,y:i,new_card_id:b,face_down -3:flip_card:s,zone:i,card_id:s,card_name:b,face_down -3:destroy_card:s,zone:i,card_id -3:attach_card:s,start_zone:i,card_id:i,target_player_id:s,target_zone:i,target_card_id -3:create_token:s,zone:i,card_id:s,card_name:s,color:s,pt:s,annotation:b,destroy_on_zone_change:i,x:i,y -3:delete_arrow:i,arrow_id -3:set_card_attr:s,zone:i,card_id:s,attr_name:s,attr_value -3:set_card_counter:s,zone:i,card_id:i,counter_id:i,counter_value -3:set_counter:i,counter_id:i,value -3:del_counter:i,counter_id -3:set_active_player:i,active_player_id -3:set_active_phase:i,phase -3:dump_zone:i,zone_owner_id:s,zone:i,number_cards -3:stop_dump_zone:i,zone_owner_id:s,zone -4:remove_from_list:s,list:s,user_name -4:server_message:s,message -4:server_shutdown:s,reason:i,minutes -4:connection_closed:s,reason -4:message:s,sender_name:s,receiver_name:s,text -4:game_joined:i,game_id:s,game_description:i,host_id:i,player_id:b,spectator:b,spectators_can_talk:b,spectators_see_everything:b,resuming -4:user_left:s,user_name -5:leave_room:s,player_name -5:room_say:s,player_name:s,message -6:ready_start -6:concede -6:deck_select:s,deck_hash -6:undo_draw -6:move_card -6:mulligan:i,number -7:update_server_message -7:shutdown_server:s,reason:i,minutes -8:ban_from_server:s,user_name:s,address:i,minutes:s,reason diff --git a/common/protocol_items.h b/common/protocol_items.h deleted file mode 100644 index ac26ef712..000000000 --- a/common/protocol_items.h +++ /dev/null @@ -1,764 +0,0 @@ -#ifndef PROTOCOL_ITEMS_H -#define PROTOCOL_ITEMS_H - -#include "protocol.h" - -class Command_Ping : public Command { - Q_OBJECT -public: - Command_Ping(); - static SerializableItem *newItem() { return new Command_Ping; } - int getItemId() const { return ItemId_Command_Ping; } -}; -class Command_Login : public Command { - Q_OBJECT -public: - Command_Login(const QString &_username = QString(), const QString &_password = QString()); - QString getUsername() const { return static_cast(itemMap.value("username"))->getData(); }; - QString getPassword() const { return static_cast(itemMap.value("password"))->getData(); }; - static SerializableItem *newItem() { return new Command_Login; } - int getItemId() const { return ItemId_Command_Login; } -}; -class Command_Message : public Command { - Q_OBJECT -public: - Command_Message(const QString &_userName = QString(), const QString &_text = QString()); - QString getUserName() const { return static_cast(itemMap.value("user_name"))->getData(); }; - QString getText() const { return static_cast(itemMap.value("text"))->getData(); }; - static SerializableItem *newItem() { return new Command_Message; } - int getItemId() const { return ItemId_Command_Message; } -}; -class Command_ListUsers : public Command { - Q_OBJECT -public: - Command_ListUsers(); - static SerializableItem *newItem() { return new Command_ListUsers; } - int getItemId() const { return ItemId_Command_ListUsers; } -}; -class Command_GetGamesOfUser : public Command { - Q_OBJECT -public: - Command_GetGamesOfUser(const QString &_userName = QString()); - QString getUserName() const { return static_cast(itemMap.value("user_name"))->getData(); }; - static SerializableItem *newItem() { return new Command_GetGamesOfUser; } - int getItemId() const { return ItemId_Command_GetGamesOfUser; } -}; -class Command_GetUserInfo : public Command { - Q_OBJECT -public: - Command_GetUserInfo(const QString &_userName = QString()); - QString getUserName() const { return static_cast(itemMap.value("user_name"))->getData(); }; - static SerializableItem *newItem() { return new Command_GetUserInfo; } - int getItemId() const { return ItemId_Command_GetUserInfo; } -}; -class Command_AddToList : public Command { - Q_OBJECT -public: - Command_AddToList(const QString &_list = QString(), const QString &_userName = QString()); - QString getList() const { return static_cast(itemMap.value("list"))->getData(); }; - QString getUserName() const { return static_cast(itemMap.value("user_name"))->getData(); }; - static SerializableItem *newItem() { return new Command_AddToList; } - int getItemId() const { return ItemId_Command_AddToList; } -}; -class Command_RemoveFromList : public Command { - Q_OBJECT -public: - Command_RemoveFromList(const QString &_list = QString(), const QString &_userName = QString()); - QString getList() const { return static_cast(itemMap.value("list"))->getData(); }; - QString getUserName() const { return static_cast(itemMap.value("user_name"))->getData(); }; - static SerializableItem *newItem() { return new Command_RemoveFromList; } - int getItemId() const { return ItemId_Command_RemoveFromList; } -}; -class Command_DeckList : public Command { - Q_OBJECT -public: - Command_DeckList(); - static SerializableItem *newItem() { return new Command_DeckList; } - int getItemId() const { return ItemId_Command_DeckList; } -}; -class Command_DeckNewDir : public Command { - Q_OBJECT -public: - Command_DeckNewDir(const QString &_path = QString(), const QString &_dirName = QString()); - QString getPath() const { return static_cast(itemMap.value("path"))->getData(); }; - QString getDirName() const { return static_cast(itemMap.value("dir_name"))->getData(); }; - static SerializableItem *newItem() { return new Command_DeckNewDir; } - int getItemId() const { return ItemId_Command_DeckNewDir; } -}; -class Command_DeckDelDir : public Command { - Q_OBJECT -public: - Command_DeckDelDir(const QString &_path = QString()); - QString getPath() const { return static_cast(itemMap.value("path"))->getData(); }; - static SerializableItem *newItem() { return new Command_DeckDelDir; } - int getItemId() const { return ItemId_Command_DeckDelDir; } -}; -class Command_DeckDel : public Command { - Q_OBJECT -public: - Command_DeckDel(int _deckId = -1); - int getDeckId() const { return static_cast(itemMap.value("deck_id"))->getData(); }; - static SerializableItem *newItem() { return new Command_DeckDel; } - int getItemId() const { return ItemId_Command_DeckDel; } -}; -class Command_DeckDownload : public Command { - Q_OBJECT -public: - Command_DeckDownload(int _deckId = -1); - int getDeckId() const { return static_cast(itemMap.value("deck_id"))->getData(); }; - static SerializableItem *newItem() { return new Command_DeckDownload; } - int getItemId() const { return ItemId_Command_DeckDownload; } -}; -class Command_ListRooms : public Command { - Q_OBJECT -public: - Command_ListRooms(); - static SerializableItem *newItem() { return new Command_ListRooms; } - int getItemId() const { return ItemId_Command_ListRooms; } -}; -class Command_JoinRoom : public Command { - Q_OBJECT -public: - Command_JoinRoom(int _roomId = -1); - int getRoomId() const { return static_cast(itemMap.value("room_id"))->getData(); }; - static SerializableItem *newItem() { return new Command_JoinRoom; } - int getItemId() const { return ItemId_Command_JoinRoom; } -}; -class Command_LeaveRoom : public RoomCommand { - Q_OBJECT -public: - Command_LeaveRoom(int _roomId = -1); - static SerializableItem *newItem() { return new Command_LeaveRoom; } - int getItemId() const { return ItemId_Command_LeaveRoom; } -}; -class Command_RoomSay : public RoomCommand { - Q_OBJECT -public: - Command_RoomSay(int _roomId = -1, const QString &_message = QString()); - QString getMessage() const { return static_cast(itemMap.value("message"))->getData(); }; - static SerializableItem *newItem() { return new Command_RoomSay; } - int getItemId() const { return ItemId_Command_RoomSay; } -}; -class Command_JoinGame : public RoomCommand { - Q_OBJECT -public: - Command_JoinGame(int _roomId = -1, int _gameId = -1, const QString &_password = QString(), bool _spectator = false, bool _overrideRestrictions = false); - int getGameId() const { return static_cast(itemMap.value("game_id"))->getData(); }; - QString getPassword() const { return static_cast(itemMap.value("password"))->getData(); }; - bool getSpectator() const { return static_cast(itemMap.value("spectator"))->getData(); }; - bool getOverrideRestrictions() const { return static_cast(itemMap.value("override_restrictions"))->getData(); }; - static SerializableItem *newItem() { return new Command_JoinGame; } - int getItemId() const { return ItemId_Command_JoinGame; } -}; -class Command_KickFromGame : public GameCommand { - Q_OBJECT -public: - Command_KickFromGame(int _gameId = -1, int _playerId = -1); - int getPlayerId() const { return static_cast(itemMap.value("player_id"))->getData(); }; - static SerializableItem *newItem() { return new Command_KickFromGame; } - int getItemId() const { return ItemId_Command_KickFromGame; } -}; -class Command_LeaveGame : public GameCommand { - Q_OBJECT -public: - Command_LeaveGame(int _gameId = -1); - static SerializableItem *newItem() { return new Command_LeaveGame; } - int getItemId() const { return ItemId_Command_LeaveGame; } -}; -class Command_Say : public GameCommand { - Q_OBJECT -public: - Command_Say(int _gameId = -1, const QString &_message = QString()); - QString getMessage() const { return static_cast(itemMap.value("message"))->getData(); }; - static SerializableItem *newItem() { return new Command_Say; } - int getItemId() const { return ItemId_Command_Say; } -}; -class Command_Shuffle : public GameCommand { - Q_OBJECT -public: - Command_Shuffle(int _gameId = -1); - static SerializableItem *newItem() { return new Command_Shuffle; } - int getItemId() const { return ItemId_Command_Shuffle; } -}; -class Command_Mulligan : public GameCommand { - Q_OBJECT -public: - Command_Mulligan(int _gameId = -1); - static SerializableItem *newItem() { return new Command_Mulligan; } - int getItemId() const { return ItemId_Command_Mulligan; } -}; -class Command_RollDie : public GameCommand { - Q_OBJECT -public: - Command_RollDie(int _gameId = -1, int _sides = -1); - int getSides() const { return static_cast(itemMap.value("sides"))->getData(); }; - static SerializableItem *newItem() { return new Command_RollDie; } - int getItemId() const { return ItemId_Command_RollDie; } -}; -class Command_DrawCards : public GameCommand { - Q_OBJECT -public: - Command_DrawCards(int _gameId = -1, int _number = -1); - int getNumber() const { return static_cast(itemMap.value("number"))->getData(); }; - static SerializableItem *newItem() { return new Command_DrawCards; } - int getItemId() const { return ItemId_Command_DrawCards; } -}; -class Command_UndoDraw : public GameCommand { - Q_OBJECT -public: - Command_UndoDraw(int _gameId = -1); - static SerializableItem *newItem() { return new Command_UndoDraw; } - int getItemId() const { return ItemId_Command_UndoDraw; } -}; -class Command_FlipCard : public GameCommand { - Q_OBJECT -public: - Command_FlipCard(int _gameId = -1, const QString &_zone = QString(), int _cardId = -1, bool _faceDown = false); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - bool getFaceDown() const { return static_cast(itemMap.value("face_down"))->getData(); }; - static SerializableItem *newItem() { return new Command_FlipCard; } - int getItemId() const { return ItemId_Command_FlipCard; } -}; -class Command_AttachCard : public GameCommand { - Q_OBJECT -public: - Command_AttachCard(int _gameId = -1, const QString &_startZone = QString(), int _cardId = -1, int _targetPlayerId = -1, const QString &_targetZone = QString(), int _targetCardId = -1); - QString getStartZone() const { return static_cast(itemMap.value("start_zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - int getTargetPlayerId() const { return static_cast(itemMap.value("target_player_id"))->getData(); }; - QString getTargetZone() const { return static_cast(itemMap.value("target_zone"))->getData(); }; - int getTargetCardId() const { return static_cast(itemMap.value("target_card_id"))->getData(); }; - static SerializableItem *newItem() { return new Command_AttachCard; } - int getItemId() const { return ItemId_Command_AttachCard; } -}; -class Command_CreateToken : public GameCommand { - Q_OBJECT -public: - Command_CreateToken(int _gameId = -1, const QString &_zone = QString(), const QString &_cardName = QString(), const QString &_color = QString(), const QString &_pt = QString(), const QString &_annotation = QString(), bool _destroy = false, int _x = -1, int _y = -1); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - QString getCardName() const { return static_cast(itemMap.value("card_name"))->getData(); }; - QString getColor() const { return static_cast(itemMap.value("color"))->getData(); }; - QString getPt() const { return static_cast(itemMap.value("pt"))->getData(); }; - QString getAnnotation() const { return static_cast(itemMap.value("annotation"))->getData(); }; - bool getDestroy() const { return static_cast(itemMap.value("destroy"))->getData(); }; - int getX() const { return static_cast(itemMap.value("x"))->getData(); }; - int getY() const { return static_cast(itemMap.value("y"))->getData(); }; - static SerializableItem *newItem() { return new Command_CreateToken; } - int getItemId() const { return ItemId_Command_CreateToken; } -}; -class Command_CreateArrow : public GameCommand { - Q_OBJECT -public: - Command_CreateArrow(int _gameId = -1, int _startPlayerId = -1, const QString &_startZone = QString(), int _startCardId = -1, int _targetPlayerId = -1, const QString &_targetZone = QString(), int _targetCardId = -1, const Color &_color = Color()); - int getStartPlayerId() const { return static_cast(itemMap.value("start_player_id"))->getData(); }; - QString getStartZone() const { return static_cast(itemMap.value("start_zone"))->getData(); }; - int getStartCardId() const { return static_cast(itemMap.value("start_card_id"))->getData(); }; - int getTargetPlayerId() const { return static_cast(itemMap.value("target_player_id"))->getData(); }; - QString getTargetZone() const { return static_cast(itemMap.value("target_zone"))->getData(); }; - int getTargetCardId() const { return static_cast(itemMap.value("target_card_id"))->getData(); }; - Color getColor() const { return static_cast(itemMap.value("color"))->getData(); }; - static SerializableItem *newItem() { return new Command_CreateArrow; } - int getItemId() const { return ItemId_Command_CreateArrow; } -}; -class Command_DeleteArrow : public GameCommand { - Q_OBJECT -public: - Command_DeleteArrow(int _gameId = -1, int _arrowId = -1); - int getArrowId() const { return static_cast(itemMap.value("arrow_id"))->getData(); }; - static SerializableItem *newItem() { return new Command_DeleteArrow; } - int getItemId() const { return ItemId_Command_DeleteArrow; } -}; -class Command_SetCardAttr : public GameCommand { - Q_OBJECT -public: - Command_SetCardAttr(int _gameId = -1, const QString &_zone = QString(), int _cardId = -1, const QString &_attrName = QString(), const QString &_attrValue = QString()); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - QString getAttrName() const { return static_cast(itemMap.value("attr_name"))->getData(); }; - QString getAttrValue() const { return static_cast(itemMap.value("attr_value"))->getData(); }; - static SerializableItem *newItem() { return new Command_SetCardAttr; } - int getItemId() const { return ItemId_Command_SetCardAttr; } -}; -class Command_SetCardCounter : public GameCommand { - Q_OBJECT -public: - Command_SetCardCounter(int _gameId = -1, const QString &_zone = QString(), int _cardId = -1, int _counterId = -1, int _counterValue = -1); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - int getCounterId() const { return static_cast(itemMap.value("counter_id"))->getData(); }; - int getCounterValue() const { return static_cast(itemMap.value("counter_value"))->getData(); }; - static SerializableItem *newItem() { return new Command_SetCardCounter; } - int getItemId() const { return ItemId_Command_SetCardCounter; } -}; -class Command_IncCardCounter : public GameCommand { - Q_OBJECT -public: - Command_IncCardCounter(int _gameId = -1, const QString &_zone = QString(), int _cardId = -1, int _counterId = -1, int _counterDelta = -1); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - int getCounterId() const { return static_cast(itemMap.value("counter_id"))->getData(); }; - int getCounterDelta() const { return static_cast(itemMap.value("counter_delta"))->getData(); }; - static SerializableItem *newItem() { return new Command_IncCardCounter; } - int getItemId() const { return ItemId_Command_IncCardCounter; } -}; -class Command_ReadyStart : public GameCommand { - Q_OBJECT -public: - Command_ReadyStart(int _gameId = -1, bool _ready = false); - bool getReady() const { return static_cast(itemMap.value("ready"))->getData(); }; - static SerializableItem *newItem() { return new Command_ReadyStart; } - int getItemId() const { return ItemId_Command_ReadyStart; } -}; -class Command_Concede : public GameCommand { - Q_OBJECT -public: - Command_Concede(int _gameId = -1); - static SerializableItem *newItem() { return new Command_Concede; } - int getItemId() const { return ItemId_Command_Concede; } -}; -class Command_IncCounter : public GameCommand { - Q_OBJECT -public: - Command_IncCounter(int _gameId = -1, int _counterId = -1, int _delta = -1); - int getCounterId() const { return static_cast(itemMap.value("counter_id"))->getData(); }; - int getDelta() const { return static_cast(itemMap.value("delta"))->getData(); }; - static SerializableItem *newItem() { return new Command_IncCounter; } - int getItemId() const { return ItemId_Command_IncCounter; } -}; -class Command_CreateCounter : public GameCommand { - Q_OBJECT -public: - Command_CreateCounter(int _gameId = -1, const QString &_counterName = QString(), const Color &_color = Color(), int _radius = -1, int _value = -1); - QString getCounterName() const { return static_cast(itemMap.value("counter_name"))->getData(); }; - Color getColor() const { return static_cast(itemMap.value("color"))->getData(); }; - int getRadius() const { return static_cast(itemMap.value("radius"))->getData(); }; - int getValue() const { return static_cast(itemMap.value("value"))->getData(); }; - static SerializableItem *newItem() { return new Command_CreateCounter; } - int getItemId() const { return ItemId_Command_CreateCounter; } -}; -class Command_SetCounter : public GameCommand { - Q_OBJECT -public: - Command_SetCounter(int _gameId = -1, int _counterId = -1, int _value = -1); - int getCounterId() const { return static_cast(itemMap.value("counter_id"))->getData(); }; - int getValue() const { return static_cast(itemMap.value("value"))->getData(); }; - static SerializableItem *newItem() { return new Command_SetCounter; } - int getItemId() const { return ItemId_Command_SetCounter; } -}; -class Command_DelCounter : public GameCommand { - Q_OBJECT -public: - Command_DelCounter(int _gameId = -1, int _counterId = -1); - int getCounterId() const { return static_cast(itemMap.value("counter_id"))->getData(); }; - static SerializableItem *newItem() { return new Command_DelCounter; } - int getItemId() const { return ItemId_Command_DelCounter; } -}; -class Command_NextTurn : public GameCommand { - Q_OBJECT -public: - Command_NextTurn(int _gameId = -1); - static SerializableItem *newItem() { return new Command_NextTurn; } - int getItemId() const { return ItemId_Command_NextTurn; } -}; -class Command_SetActivePhase : public GameCommand { - Q_OBJECT -public: - Command_SetActivePhase(int _gameId = -1, int _phase = -1); - int getPhase() const { return static_cast(itemMap.value("phase"))->getData(); }; - static SerializableItem *newItem() { return new Command_SetActivePhase; } - int getItemId() const { return ItemId_Command_SetActivePhase; } -}; -class Command_DumpZone : public GameCommand { - Q_OBJECT -public: - Command_DumpZone(int _gameId = -1, int _playerId = -1, const QString &_zoneName = QString(), int _numberCards = -1); - int getPlayerId() const { return static_cast(itemMap.value("player_id"))->getData(); }; - QString getZoneName() const { return static_cast(itemMap.value("zone_name"))->getData(); }; - int getNumberCards() const { return static_cast(itemMap.value("number_cards"))->getData(); }; - static SerializableItem *newItem() { return new Command_DumpZone; } - int getItemId() const { return ItemId_Command_DumpZone; } -}; -class Command_StopDumpZone : public GameCommand { - Q_OBJECT -public: - Command_StopDumpZone(int _gameId = -1, int _playerId = -1, const QString &_zoneName = QString()); - int getPlayerId() const { return static_cast(itemMap.value("player_id"))->getData(); }; - QString getZoneName() const { return static_cast(itemMap.value("zone_name"))->getData(); }; - static SerializableItem *newItem() { return new Command_StopDumpZone; } - int getItemId() const { return ItemId_Command_StopDumpZone; } -}; -class Command_RevealCards : public GameCommand { - Q_OBJECT -public: - Command_RevealCards(int _gameId = -1, const QString &_zoneName = QString(), int _cardId = -1, int _playerId = -1); - QString getZoneName() const { return static_cast(itemMap.value("zone_name"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - int getPlayerId() const { return static_cast(itemMap.value("player_id"))->getData(); }; - static SerializableItem *newItem() { return new Command_RevealCards; } - int getItemId() const { return ItemId_Command_RevealCards; } -}; -class Event_ConnectionStateChanged : public GameEvent { - Q_OBJECT -public: - Event_ConnectionStateChanged(int _playerId = -1, bool _connected = false); - bool getConnected() const { return static_cast(itemMap.value("connected"))->getData(); }; - static SerializableItem *newItem() { return new Event_ConnectionStateChanged; } - int getItemId() const { return ItemId_Event_ConnectionStateChanged; } -}; -class Event_Say : public GameEvent { - Q_OBJECT -public: - Event_Say(int _playerId = -1, const QString &_message = QString()); - QString getMessage() const { return static_cast(itemMap.value("message"))->getData(); }; - static SerializableItem *newItem() { return new Event_Say; } - int getItemId() const { return ItemId_Event_Say; } -}; -class Event_Leave : public GameEvent { - Q_OBJECT -public: - Event_Leave(int _playerId = -1); - static SerializableItem *newItem() { return new Event_Leave; } - int getItemId() const { return ItemId_Event_Leave; } -}; -class Event_GameClosed : public GameEvent { - Q_OBJECT -public: - Event_GameClosed(int _playerId = -1); - static SerializableItem *newItem() { return new Event_GameClosed; } - int getItemId() const { return ItemId_Event_GameClosed; } -}; -class Event_GameHostChanged : public GameEvent { - Q_OBJECT -public: - Event_GameHostChanged(int _playerId = -1); - static SerializableItem *newItem() { return new Event_GameHostChanged; } - int getItemId() const { return ItemId_Event_GameHostChanged; } -}; -class Event_Kicked : public GameEvent { - Q_OBJECT -public: - Event_Kicked(int _playerId = -1); - static SerializableItem *newItem() { return new Event_Kicked; } - int getItemId() const { return ItemId_Event_Kicked; } -}; -class Event_Shuffle : public GameEvent { - Q_OBJECT -public: - Event_Shuffle(int _playerId = -1); - static SerializableItem *newItem() { return new Event_Shuffle; } - int getItemId() const { return ItemId_Event_Shuffle; } -}; -class Event_RollDie : public GameEvent { - Q_OBJECT -public: - Event_RollDie(int _playerId = -1, int _sides = -1, int _value = -1); - int getSides() const { return static_cast(itemMap.value("sides"))->getData(); }; - int getValue() const { return static_cast(itemMap.value("value"))->getData(); }; - static SerializableItem *newItem() { return new Event_RollDie; } - int getItemId() const { return ItemId_Event_RollDie; } -}; -class Event_MoveCard : public GameEvent { - Q_OBJECT -public: - Event_MoveCard(int _playerId = -1, int _cardId = -1, const QString &_cardName = QString(), const QString &_startZone = QString(), int _position = -1, int _targetPlayerId = -1, const QString &_targetZone = QString(), int _x = -1, int _y = -1, int _newCardId = -1, bool _faceDown = false); - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - QString getCardName() const { return static_cast(itemMap.value("card_name"))->getData(); }; - QString getStartZone() const { return static_cast(itemMap.value("start_zone"))->getData(); }; - int getPosition() const { return static_cast(itemMap.value("position"))->getData(); }; - int getTargetPlayerId() const { return static_cast(itemMap.value("target_player_id"))->getData(); }; - QString getTargetZone() const { return static_cast(itemMap.value("target_zone"))->getData(); }; - int getX() const { return static_cast(itemMap.value("x"))->getData(); }; - int getY() const { return static_cast(itemMap.value("y"))->getData(); }; - int getNewCardId() const { return static_cast(itemMap.value("new_card_id"))->getData(); }; - bool getFaceDown() const { return static_cast(itemMap.value("face_down"))->getData(); }; - static SerializableItem *newItem() { return new Event_MoveCard; } - int getItemId() const { return ItemId_Event_MoveCard; } -}; -class Event_FlipCard : public GameEvent { - Q_OBJECT -public: - Event_FlipCard(int _playerId = -1, const QString &_zone = QString(), int _cardId = -1, const QString &_cardName = QString(), bool _faceDown = false); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - QString getCardName() const { return static_cast(itemMap.value("card_name"))->getData(); }; - bool getFaceDown() const { return static_cast(itemMap.value("face_down"))->getData(); }; - static SerializableItem *newItem() { return new Event_FlipCard; } - int getItemId() const { return ItemId_Event_FlipCard; } -}; -class Event_DestroyCard : public GameEvent { - Q_OBJECT -public: - Event_DestroyCard(int _playerId = -1, const QString &_zone = QString(), int _cardId = -1); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - static SerializableItem *newItem() { return new Event_DestroyCard; } - int getItemId() const { return ItemId_Event_DestroyCard; } -}; -class Event_AttachCard : public GameEvent { - Q_OBJECT -public: - Event_AttachCard(int _playerId = -1, const QString &_startZone = QString(), int _cardId = -1, int _targetPlayerId = -1, const QString &_targetZone = QString(), int _targetCardId = -1); - QString getStartZone() const { return static_cast(itemMap.value("start_zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - int getTargetPlayerId() const { return static_cast(itemMap.value("target_player_id"))->getData(); }; - QString getTargetZone() const { return static_cast(itemMap.value("target_zone"))->getData(); }; - int getTargetCardId() const { return static_cast(itemMap.value("target_card_id"))->getData(); }; - static SerializableItem *newItem() { return new Event_AttachCard; } - int getItemId() const { return ItemId_Event_AttachCard; } -}; -class Event_CreateToken : public GameEvent { - Q_OBJECT -public: - Event_CreateToken(int _playerId = -1, const QString &_zone = QString(), int _cardId = -1, const QString &_cardName = QString(), const QString &_color = QString(), const QString &_pt = QString(), const QString &_annotation = QString(), bool _destroyOnZoneChange = false, int _x = -1, int _y = -1); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - QString getCardName() const { return static_cast(itemMap.value("card_name"))->getData(); }; - QString getColor() const { return static_cast(itemMap.value("color"))->getData(); }; - QString getPt() const { return static_cast(itemMap.value("pt"))->getData(); }; - QString getAnnotation() const { return static_cast(itemMap.value("annotation"))->getData(); }; - bool getDestroyOnZoneChange() const { return static_cast(itemMap.value("destroy_on_zone_change"))->getData(); }; - int getX() const { return static_cast(itemMap.value("x"))->getData(); }; - int getY() const { return static_cast(itemMap.value("y"))->getData(); }; - static SerializableItem *newItem() { return new Event_CreateToken; } - int getItemId() const { return ItemId_Event_CreateToken; } -}; -class Event_DeleteArrow : public GameEvent { - Q_OBJECT -public: - Event_DeleteArrow(int _playerId = -1, int _arrowId = -1); - int getArrowId() const { return static_cast(itemMap.value("arrow_id"))->getData(); }; - static SerializableItem *newItem() { return new Event_DeleteArrow; } - int getItemId() const { return ItemId_Event_DeleteArrow; } -}; -class Event_SetCardAttr : public GameEvent { - Q_OBJECT -public: - Event_SetCardAttr(int _playerId = -1, const QString &_zone = QString(), int _cardId = -1, const QString &_attrName = QString(), const QString &_attrValue = QString()); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - QString getAttrName() const { return static_cast(itemMap.value("attr_name"))->getData(); }; - QString getAttrValue() const { return static_cast(itemMap.value("attr_value"))->getData(); }; - static SerializableItem *newItem() { return new Event_SetCardAttr; } - int getItemId() const { return ItemId_Event_SetCardAttr; } -}; -class Event_SetCardCounter : public GameEvent { - Q_OBJECT -public: - Event_SetCardCounter(int _playerId = -1, const QString &_zone = QString(), int _cardId = -1, int _counterId = -1, int _counterValue = -1); - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getCardId() const { return static_cast(itemMap.value("card_id"))->getData(); }; - int getCounterId() const { return static_cast(itemMap.value("counter_id"))->getData(); }; - int getCounterValue() const { return static_cast(itemMap.value("counter_value"))->getData(); }; - static SerializableItem *newItem() { return new Event_SetCardCounter; } - int getItemId() const { return ItemId_Event_SetCardCounter; } -}; -class Event_SetCounter : public GameEvent { - Q_OBJECT -public: - Event_SetCounter(int _playerId = -1, int _counterId = -1, int _value = -1); - int getCounterId() const { return static_cast(itemMap.value("counter_id"))->getData(); }; - int getValue() const { return static_cast(itemMap.value("value"))->getData(); }; - static SerializableItem *newItem() { return new Event_SetCounter; } - int getItemId() const { return ItemId_Event_SetCounter; } -}; -class Event_DelCounter : public GameEvent { - Q_OBJECT -public: - Event_DelCounter(int _playerId = -1, int _counterId = -1); - int getCounterId() const { return static_cast(itemMap.value("counter_id"))->getData(); }; - static SerializableItem *newItem() { return new Event_DelCounter; } - int getItemId() const { return ItemId_Event_DelCounter; } -}; -class Event_SetActivePlayer : public GameEvent { - Q_OBJECT -public: - Event_SetActivePlayer(int _playerId = -1, int _activePlayerId = -1); - int getActivePlayerId() const { return static_cast(itemMap.value("active_player_id"))->getData(); }; - static SerializableItem *newItem() { return new Event_SetActivePlayer; } - int getItemId() const { return ItemId_Event_SetActivePlayer; } -}; -class Event_SetActivePhase : public GameEvent { - Q_OBJECT -public: - Event_SetActivePhase(int _playerId = -1, int _phase = -1); - int getPhase() const { return static_cast(itemMap.value("phase"))->getData(); }; - static SerializableItem *newItem() { return new Event_SetActivePhase; } - int getItemId() const { return ItemId_Event_SetActivePhase; } -}; -class Event_DumpZone : public GameEvent { - Q_OBJECT -public: - Event_DumpZone(int _playerId = -1, int _zoneOwnerId = -1, const QString &_zone = QString(), int _numberCards = -1); - int getZoneOwnerId() const { return static_cast(itemMap.value("zone_owner_id"))->getData(); }; - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - int getNumberCards() const { return static_cast(itemMap.value("number_cards"))->getData(); }; - static SerializableItem *newItem() { return new Event_DumpZone; } - int getItemId() const { return ItemId_Event_DumpZone; } -}; -class Event_StopDumpZone : public GameEvent { - Q_OBJECT -public: - Event_StopDumpZone(int _playerId = -1, int _zoneOwnerId = -1, const QString &_zone = QString()); - int getZoneOwnerId() const { return static_cast(itemMap.value("zone_owner_id"))->getData(); }; - QString getZone() const { return static_cast(itemMap.value("zone"))->getData(); }; - static SerializableItem *newItem() { return new Event_StopDumpZone; } - int getItemId() const { return ItemId_Event_StopDumpZone; } -}; -class Event_RemoveFromList : public GenericEvent { - Q_OBJECT -public: - Event_RemoveFromList(const QString &_list = QString(), const QString &_userName = QString()); - QString getList() const { return static_cast(itemMap.value("list"))->getData(); }; - QString getUserName() const { return static_cast(itemMap.value("user_name"))->getData(); }; - static SerializableItem *newItem() { return new Event_RemoveFromList; } - int getItemId() const { return ItemId_Event_RemoveFromList; } -}; -class Event_ServerMessage : public GenericEvent { - Q_OBJECT -public: - Event_ServerMessage(const QString &_message = QString()); - QString getMessage() const { return static_cast(itemMap.value("message"))->getData(); }; - static SerializableItem *newItem() { return new Event_ServerMessage; } - int getItemId() const { return ItemId_Event_ServerMessage; } -}; -class Event_ServerShutdown : public GenericEvent { - Q_OBJECT -public: - Event_ServerShutdown(const QString &_reason = QString(), int _minutes = -1); - QString getReason() const { return static_cast(itemMap.value("reason"))->getData(); }; - int getMinutes() const { return static_cast(itemMap.value("minutes"))->getData(); }; - static SerializableItem *newItem() { return new Event_ServerShutdown; } - int getItemId() const { return ItemId_Event_ServerShutdown; } -}; -class Event_ConnectionClosed : public GenericEvent { - Q_OBJECT -public: - Event_ConnectionClosed(const QString &_reason = QString()); - QString getReason() const { return static_cast(itemMap.value("reason"))->getData(); }; - static SerializableItem *newItem() { return new Event_ConnectionClosed; } - int getItemId() const { return ItemId_Event_ConnectionClosed; } -}; -class Event_Message : public GenericEvent { - Q_OBJECT -public: - Event_Message(const QString &_senderName = QString(), const QString &_receiverName = QString(), const QString &_text = QString()); - QString getSenderName() const { return static_cast(itemMap.value("sender_name"))->getData(); }; - QString getReceiverName() const { return static_cast(itemMap.value("receiver_name"))->getData(); }; - QString getText() const { return static_cast(itemMap.value("text"))->getData(); }; - static SerializableItem *newItem() { return new Event_Message; } - int getItemId() const { return ItemId_Event_Message; } -}; -class Event_GameJoined : public GenericEvent { - Q_OBJECT -public: - Event_GameJoined(int _gameId = -1, const QString &_gameDescription = QString(), int _hostId = -1, int _playerId = -1, bool _spectator = false, bool _spectatorsCanTalk = false, bool _spectatorsSeeEverything = false, bool _resuming = false); - int getGameId() const { return static_cast(itemMap.value("game_id"))->getData(); }; - QString getGameDescription() const { return static_cast(itemMap.value("game_description"))->getData(); }; - int getHostId() const { return static_cast(itemMap.value("host_id"))->getData(); }; - int getPlayerId() const { return static_cast(itemMap.value("player_id"))->getData(); }; - bool getSpectator() const { return static_cast(itemMap.value("spectator"))->getData(); }; - bool getSpectatorsCanTalk() const { return static_cast(itemMap.value("spectators_can_talk"))->getData(); }; - bool getSpectatorsSeeEverything() const { return static_cast(itemMap.value("spectators_see_everything"))->getData(); }; - bool getResuming() const { return static_cast(itemMap.value("resuming"))->getData(); }; - static SerializableItem *newItem() { return new Event_GameJoined; } - int getItemId() const { return ItemId_Event_GameJoined; } -}; -class Event_UserLeft : public GenericEvent { - Q_OBJECT -public: - Event_UserLeft(const QString &_userName = QString()); - QString getUserName() const { return static_cast(itemMap.value("user_name"))->getData(); }; - static SerializableItem *newItem() { return new Event_UserLeft; } - int getItemId() const { return ItemId_Event_UserLeft; } -}; -class Event_LeaveRoom : public RoomEvent { - Q_OBJECT -public: - Event_LeaveRoom(int _roomId = -1, const QString &_playerName = QString()); - QString getPlayerName() const { return static_cast(itemMap.value("player_name"))->getData(); }; - static SerializableItem *newItem() { return new Event_LeaveRoom; } - int getItemId() const { return ItemId_Event_LeaveRoom; } -}; -class Event_RoomSay : public RoomEvent { - Q_OBJECT -public: - Event_RoomSay(int _roomId = -1, const QString &_playerName = QString(), const QString &_message = QString()); - QString getPlayerName() const { return static_cast(itemMap.value("player_name"))->getData(); }; - QString getMessage() const { return static_cast(itemMap.value("message"))->getData(); }; - static SerializableItem *newItem() { return new Event_RoomSay; } - int getItemId() const { return ItemId_Event_RoomSay; } -}; -class Context_ReadyStart : public GameEventContext { - Q_OBJECT -public: - Context_ReadyStart(); - static SerializableItem *newItem() { return new Context_ReadyStart; } - int getItemId() const { return ItemId_Context_ReadyStart; } -}; -class Context_Concede : public GameEventContext { - Q_OBJECT -public: - Context_Concede(); - static SerializableItem *newItem() { return new Context_Concede; } - int getItemId() const { return ItemId_Context_Concede; } -}; -class Context_DeckSelect : public GameEventContext { - Q_OBJECT -public: - Context_DeckSelect(const QString &_deckHash = QString()); - QString getDeckHash() const { return static_cast(itemMap.value("deck_hash"))->getData(); }; - static SerializableItem *newItem() { return new Context_DeckSelect; } - int getItemId() const { return ItemId_Context_DeckSelect; } -}; -class Context_UndoDraw : public GameEventContext { - Q_OBJECT -public: - Context_UndoDraw(); - static SerializableItem *newItem() { return new Context_UndoDraw; } - int getItemId() const { return ItemId_Context_UndoDraw; } -}; -class Context_MoveCard : public GameEventContext { - Q_OBJECT -public: - Context_MoveCard(); - static SerializableItem *newItem() { return new Context_MoveCard; } - int getItemId() const { return ItemId_Context_MoveCard; } -}; -class Context_Mulligan : public GameEventContext { - Q_OBJECT -public: - Context_Mulligan(int _number = -1); - int getNumber() const { return static_cast(itemMap.value("number"))->getData(); }; - static SerializableItem *newItem() { return new Context_Mulligan; } - int getItemId() const { return ItemId_Context_Mulligan; } -}; -class Command_UpdateServerMessage : public AdminCommand { - Q_OBJECT -public: - Command_UpdateServerMessage(); - static SerializableItem *newItem() { return new Command_UpdateServerMessage; } - int getItemId() const { return ItemId_Command_UpdateServerMessage; } -}; -class Command_ShutdownServer : public AdminCommand { - Q_OBJECT -public: - Command_ShutdownServer(const QString &_reason = QString(), int _minutes = -1); - QString getReason() const { return static_cast(itemMap.value("reason"))->getData(); }; - int getMinutes() const { return static_cast(itemMap.value("minutes"))->getData(); }; - static SerializableItem *newItem() { return new Command_ShutdownServer; } - int getItemId() const { return ItemId_Command_ShutdownServer; } -}; -class Command_BanFromServer : public ModeratorCommand { - Q_OBJECT -public: - Command_BanFromServer(const QString &_userName = QString(), const QString &_address = QString(), int _minutes = -1, const QString &_reason = QString()); - QString getUserName() const { return static_cast(itemMap.value("user_name"))->getData(); }; - QString getAddress() const { return static_cast(itemMap.value("address"))->getData(); }; - int getMinutes() const { return static_cast(itemMap.value("minutes"))->getData(); }; - QString getReason() const { return static_cast(itemMap.value("reason"))->getData(); }; - static SerializableItem *newItem() { return new Command_BanFromServer; } - int getItemId() const { return ItemId_Command_BanFromServer; } -}; - -#endif diff --git a/common/protocol_mc.pl b/common/protocol_mc.pl deleted file mode 100755 index 2b1c96b76..000000000 --- a/common/protocol_mc.pl +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/perl - -$initializeHash = ''; -$itemId = 1000; - -$headerfileBuffer = ''; - -open(idfile, ">protocol_item_ids.h"); - -open(headerfile, ">protocol_items.h"); -print headerfile "#ifndef PROTOCOL_ITEMS_H\n" - . "#define PROTOCOL_ITEMS_H\n\n" - . "#include \"protocol.h\"\n\n"; - -open(cppfile, ">protocol_items.cpp"); -print cppfile "#include \"protocol.h\"\n" - . "#include \"protocol_items.h\"\n\n"; - -open(file, "protocol_items.dat"); -while () { - s/\s+$//; - @line = split(/:/); - $type = shift(@line); - $name1 = shift(@line); - ($name2 = $name1) =~ s/_(.)/\U$1\E/g; - $name2 =~ s/^(.)/\U$1\E/; - if ($type == 0) { - $type = 'cmd'; - $namePrefix = 'Command'; - $baseClass = 'Command'; - $parentConstructorCall = "$baseClass(\"$name1\")"; - $constructorParamsH = ""; - $constructorParamsCpp = ""; - } elsif ($type == 1) { - $type = 'cmd'; - $namePrefix = 'Command'; - $baseClass = 'RoomCommand'; - $parentConstructorCall = "$baseClass(\"$name1\", _roomId)"; - $constructorParamsH = "int _roomId = -1"; - $constructorParamsCpp = "int _roomId"; - } elsif ($type == 2) { - $type = 'cmd'; - $namePrefix = 'Command'; - $baseClass = 'GameCommand'; - $parentConstructorCall = "$baseClass(\"$name1\", _gameId)"; - $constructorParamsH = "int _gameId = -1"; - $constructorParamsCpp = "int _gameId"; - } elsif ($type == 3) { - $type = 'game_event'; - $namePrefix = 'Event'; - $baseClass = 'GameEvent'; - $parentConstructorCall = "$baseClass(\"$name1\", _playerId)"; - $constructorParamsH = "int _playerId = -1"; - $constructorParamsCpp = "int _playerId"; - } elsif ($type == 4) { - $type = 'generic_event'; - $namePrefix = 'Event'; - $baseClass = 'GenericEvent'; - $parentConstructorCall = "$baseClass(\"$name1\")"; - $constructorParamsH = ""; - $constructorParamsCpp = ""; - } elsif ($type == 5) { - $type = 'room_event'; - $namePrefix = 'Event'; - $baseClass = 'RoomEvent'; - $parentConstructorCall = "$baseClass(\"$name1\", _roomId)"; - $constructorParamsH = "int _roomId = -1"; - $constructorParamsCpp = "int _roomId"; - } elsif ($type == 6) { - $type = 'game_event_context'; - $namePrefix = 'Context'; - $baseClass = 'GameEventContext'; - $parentConstructorCall = "$baseClass(\"$name1\")"; - $constructorParamsH = ""; - $constructorParamsCpp = ""; - } elsif ($type == 7) { - $type = 'cmd'; - $namePrefix = 'Command'; - $baseClass = 'AdminCommand'; - $parentConstructorCall = "$baseClass(\"$name1\")"; - $constructorParamsH = ""; - $constructorParamsCpp = ""; - } elsif ($type == 8) { - $type = 'cmd'; - $namePrefix = 'Command'; - $baseClass = 'ModeratorCommand'; - $parentConstructorCall = "$baseClass(\"$name1\")"; - $constructorParamsH = ""; - $constructorParamsCpp = ""; - } - $className = $namePrefix . '_' . $name2; - $itemEnum .= "ItemId_$className = " . ++$itemId . ",\n"; - $headerfileBuffer .= "class $className : public $baseClass {\n" - . "\tQ_OBJECT\n"; - $constructorCode = ''; - $getFunctionCode = ''; - while ($param = shift(@line)) { - ($key, $value) = split(/,/, $param); - ($prettyVarName = $value) =~ s/_(.)/\U$1\E/g; - if (!($constructorParamsH eq '')) { - $constructorParamsH .= ', '; - } - if (!($constructorParamsCpp eq '')) { - $constructorParamsCpp .= ', '; - } - ($prettyVarName2 = $prettyVarName) =~ s/^(.)/\U$1\E/; - if ($key eq 'b') { - $dataType = 'bool'; - $constructorParamsH .= "bool _$prettyVarName = false"; - $constructorParamsCpp .= "bool _$prettyVarName"; - $constructorCode .= "\tinsertItem(new SerializableItem_Bool(\"$value\", _$prettyVarName));\n"; - $getFunctionCode .= "\t$dataType get$prettyVarName2() const { return static_cast(itemMap.value(\"$value\"))->getData(); };\n"; - } elsif ($key eq 's') { - $dataType = 'QString'; - $constructorParamsH .= "const QString &_$prettyVarName = QString()"; - $constructorParamsCpp .= "const QString &_$prettyVarName"; - $constructorCode .= "\tinsertItem(new SerializableItem_String(\"$value\", _$prettyVarName));\n"; - $getFunctionCode .= "\t$dataType get$prettyVarName2() const { return static_cast(itemMap.value(\"$value\"))->getData(); };\n"; - } elsif ($key eq 'i') { - $dataType = 'int'; - $constructorParamsH .= "int _$prettyVarName = -1"; - $constructorParamsCpp .= "int _$prettyVarName"; - $constructorCode .= "\tinsertItem(new SerializableItem_Int(\"$value\", _$prettyVarName));\n"; - $getFunctionCode .= "\t$dataType get$prettyVarName2() const { return static_cast(itemMap.value(\"$value\"))->getData(); };\n"; - } elsif ($key eq 'c') { - $dataType = 'Color'; - $constructorParamsH .= "const Color &_$prettyVarName = Color()"; - $constructorParamsCpp .= "const Color &_$prettyVarName"; - $constructorCode .= "\tinsertItem(new SerializableItem_Color(\"$value\", _$prettyVarName));\n"; - $getFunctionCode .= "\t$dataType get$prettyVarName2() const { return static_cast(itemMap.value(\"$value\"))->getData(); };\n"; - } - } - $headerfileBuffer .= "public:\n" - . "\t$className($constructorParamsH);\n" - . $getFunctionCode - . "\tstatic SerializableItem *newItem() { return new $className; }\n" - . "\tint getItemId() const { return ItemId_$className; }\n" - . "};\n"; - print cppfile $className . "::$className($constructorParamsCpp)\n" - . "\t: $parentConstructorCall\n" - . "{\n" - . $constructorCode - . "}\n"; - $initializeHash .= "\titemNameHash.insert(\"$type$name1\", $className" . "::newItem);\n"; -} -close(file); - -print idfile "enum AutoItemId {\n" - . $itemEnum - . "ItemId_Other = " . ++$itemId . "\n" - . "};\n"; -close(idfile); - -print headerfile $headerfileBuffer - . "\n#endif\n"; -close(headerfile); - -print cppfile "void ProtocolItem::initializeHashAuto()\n" - . "{\n" - . $initializeHash - . "}\n"; -close(cppfile); diff --git a/common/rng_abstract.cpp b/common/rng_abstract.cpp index 2919d4373..790549086 100644 --- a/common/rng_abstract.cpp +++ b/common/rng_abstract.cpp @@ -4,27 +4,27 @@ QVector RNG_Abstract::makeNumbersVector(int n, int min, int max) { - const int bins = max - min + 1; - QVector result(bins); - for (int i = 0; i < n; ++i) { - int number = getNumber(min, max); - if ((number < min) || (number > max)) - qDebug() << "getNumber(" << min << "," << max << ") returned " << number; - else - result[number - min]++; - } - return result; + const int bins = max - min + 1; + QVector result(bins); + for (int i = 0; i < n; ++i) { + int number = rand(min, max); + if ((number < min) || (number > max)) + qDebug() << "rand(" << min << "," << max << ") returned " << number; + else + result[number - min]++; + } + return result; } double RNG_Abstract::testRandom(const QVector &numbers) const { - int n = 0; - for (int i = 0; i < numbers.size(); ++i) - n += numbers[i]; - double expected = (double) n / (double) numbers.size(); - double chisq = 0; - for (int i = 0; i < numbers.size(); ++i) - chisq += ((double) numbers[i] - expected) * ((double) numbers[i] - expected) / expected; - - return chisq; + int n = 0; + for (int i = 0; i < numbers.size(); ++i) + n += numbers[i]; + double expected = (double) n / (double) numbers.size(); + double chisq = 0; + for (int i = 0; i < numbers.size(); ++i) + chisq += ((double) numbers[i] - expected) * ((double) numbers[i] - expected) / expected; + + return chisq; } diff --git a/common/rng_abstract.h b/common/rng_abstract.h index 5d60033ff..0423930cd 100644 --- a/common/rng_abstract.h +++ b/common/rng_abstract.h @@ -5,12 +5,12 @@ #include class RNG_Abstract : public QObject { - Q_OBJECT + Q_OBJECT public: - RNG_Abstract(QObject *parent = 0) : QObject(parent) { } - virtual unsigned int getNumber(unsigned int min, unsigned int max) = 0; - QVector makeNumbersVector(int n, int min, int max); - double testRandom(const QVector &numbers) const; + RNG_Abstract(QObject *parent = 0) : QObject(parent) { } + virtual unsigned int rand(int min, int max) = 0; + QVector makeNumbersVector(int n, int min, int max); + double testRandom(const QVector &numbers) const; }; extern RNG_Abstract *rng; diff --git a/common/rng_qt.cpp b/common/rng_qt.cpp deleted file mode 100644 index 6704cdb80..000000000 --- a/common/rng_qt.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "rng_qt.h" -#include -#include - -RNG_Qt::RNG_Qt(QObject *parent) - : RNG_Abstract(parent) -{ - int seed = QDateTime::currentDateTime().toTime_t(); - qsrand(seed); -} - -unsigned int RNG_Qt::getNumber(unsigned int min, unsigned int max) -{ - int r = qrand(); - return min + (unsigned int) (((double) (max + 1 - min)) * r / (RAND_MAX + 1.0)); -} diff --git a/common/rng_qt.h b/common/rng_qt.h deleted file mode 100644 index e30719ef3..000000000 --- a/common/rng_qt.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef RNG_QT_H -#define RNG_QT_H - -#include "rng_abstract.h" - -class RNG_Qt : public RNG_Abstract { - Q_OBJECT -public: - RNG_Qt(QObject *parent = 0); - unsigned int getNumber(unsigned int min, unsigned int max); -}; - -#endif diff --git a/common/rng_sfmt.cpp b/common/rng_sfmt.cpp index 48a2a5e67..783594a04 100644 --- a/common/rng_sfmt.cpp +++ b/common/rng_sfmt.cpp @@ -1,22 +1,136 @@ #include "rng_sfmt.h" -#include "sfmt/SFMT.h" #include -#include -#include +#include +#include +#include + +// This is from gcc sources, namely from fixincludes/inclhack.def +// On C++11 systems, could be included instead. +#ifndef UINT64_MAX +#define UINT64_MAX (~(uint64_t)0) +#endif RNG_SFMT::RNG_SFMT(QObject *parent) - : RNG_Abstract(parent) + : RNG_Abstract(parent) { - std::cerr << "Using SFMT random number generator." << std::endl; - - int seed = QDateTime::currentDateTime().toTime_t(); - init_gen_rand(seed); - for (int i = 0; i < 100000; ++i) - gen_rand64(); + // initialize the random number generator with a 32bit integer seed (timestamp) + sfmt_init_gen_rand(&sfmt, QDateTime::currentDateTime().toTime_t()); } -unsigned int RNG_SFMT::getNumber(unsigned int min, unsigned int max) -{ - uint64_t r = gen_rand64(); - return min + (unsigned int) (((double) (max + 1 - min)) * r / (18446744073709551616.0 + 1.0)); +/** + * This method is the rand() equivalent which calls the cdf with proper bounds. + * + * It is possible to generate random numbers from [-min, +/-max] though the RNG uses + * unsigned numbers only, so this wrapper handles some special cases for min and max. + * + * It is only necessary that the upper bound is larger or equal to the lower bound - with the exception + * that someone wants something like rand() % -foo. + */ +unsigned int RNG_SFMT::rand(int min, int max) { + /* If min is negative, it would be possible to calculate + * cdf(0, max - min) + min + * There has been no use for negative random numbers with rand() though, so it's treated as error. + */ + if(min < 0) { + throw std::invalid_argument( + QString("Invalid bounds for RNG: Got min " + + QString::number(min) + " < 0!\n").toStdString()); + // at this point, the method exits. No return value is needed, because + // basically the exception itself is returned. + } + + // For complete fairness and equal timing, this should be a roll, but let's skip it anyway + if(min == max) + return max; + + // This is actually not used in Cockatrice: + // Someone wants rand() % -foo, so we compute -rand(0, +foo) + // This is the only time where min > max is (sort of) legal. + // Not handling this will cause the application to crash. + if(min == 0 && max < 0) { + return -cdf(0, -max); + } + + // No special cases are left, except !(min > max) which is caught in the cdf itself. + return cdf(min, max); +} + +/** + * Much thought went into this, please read this comment before you modify the code. + * Let SFMT() be an alias for sfmt_genrand_uint64() aka SFMT's rand() function. + * + * SMFT() returns a uniformly distributed pseudorandom number from 0 to UINT64_MAX. + * As SFMT() operates on a limited integer range, it is a _discrete_ function. + * + * We want a random number from a given interval [min, max] though, so we need to + * implement the (discrete) cumulative distribution function SFMT(min, max), which + * returns a random number X from [min, max]. + * + * This CDF is by formal definition: + * SFMT(X; min, max) = (floor(X) - min + 1) / (max - min + 1) + * + * To get out the random variable, solve for X: + * floor(X) = SFMT(X; min, max) * (max - min + 1) + min - 1 + * So this is, what rand(min, max) should look like. + * Problem: SFMT(X; min, max) * (max - min + 1) could produce an integer overflow, + * so it is not safe. + * + * One solution is to divide the universe into buckets of equal size depending on the + * range [min, max] and assign X to the bucket that contains the number generated + * by SFMT(). This equals to modulo computation and is not satisfying: + * If the buckets don't divide the universe equally, because the bucket size is not + * a divisor of 2, there will be a range in the universe that is biased because one + * bucket is too small thus will be chosen less equally! + * + * This is solved by rejection sampling: + * As SFMT() is assumed to be unbiased, we are allowed to ignore those random numbers + * from SFMT() that would force us to have an unequal bucket and generate new random + * numbers until one number fits into one of the other buckets. + * This can be compared to an ideal six sided die that is rolled until only sides + * 1-5 show up, while 6 represents something that you don't want. So you basically roll + * a five sided die. + * + * Note: If you replace the SFMT RNG with some other rand() function in the future, + * then you _need_ to change the UINT64_MAX constant to the largest possible random + * number which can be created by the new rand() function. This value is often defined + * in a RAND_MAX constant. + * Otherwise you will probably skew the outcome of the rand() method or worsen the + * performance of the application. + */ +unsigned int RNG_SFMT::cdf(unsigned int min, unsigned int max) +{ + // This all makes no sense if min > max, which should never happen. + if(min > max) { + throw std::invalid_argument( + QString("Invalid bounds for RNG: min > max! Values were: min = " + + QString::number(min) + ", max = " + + QString::number(max)).toStdString()); + // at this point, the method exits. No return value is needed, because + // basically the exception itself is returned. + } + + // First compute the diameter (aka size, length) of the [min, max] interval + const unsigned int diameter = max - min + 1; + + // Compute how many buckets (each in size of the diameter) will fit into the + // universe. + // If the division has a remainder, the result is floored automatically. + const uint64_t buckets = UINT64_MAX / diameter; + + // Compute the last valid random number. All numbers beyond have to be ignored. + // If there was no remainder in the previous step, limit is equal to UINT64_MAX. + const uint64_t limit = diameter * buckets; + + uint64_t rand; + // To make the random number generation thread-safe, a mutex is created around + // the generation. Outside of the loop of course, to avoid lock/unlock overhead. + mutex.lock(); + do { + rand = sfmt_genrand_uint64(&sfmt); + } while (rand >= limit); + mutex.unlock(); + + // Now determine the bucket containing the SFMT() random number and after adding + // the lower bound, a random number from [min, max] can be returned. + return (unsigned int) (rand / buckets + min); } diff --git a/common/rng_sfmt.h b/common/rng_sfmt.h index 964eaa597..ec7a75c97 100644 --- a/common/rng_sfmt.h +++ b/common/rng_sfmt.h @@ -1,13 +1,40 @@ #ifndef RNG_SFMT_H #define RNG_SFMT_H +#include +#include +#include "sfmt/SFMT.h" #include "rng_abstract.h" +/** + * This class encapsulates a state of the art PRNG and can be used + * to return uniformly distributed integer random numbers from a range [min, max]. + * Though technically possible, min must be >= 0 and max should always be > 0. + * If max < 0 and min == 0 it is assumed that rand() % -max is wanted and the result will + * be -rand(0, -max). + * This is the only exception to the rule that !(min > max) and is actually unused in + * Cockatrice. + * + * Technical details: + * The RNG uses the SIMD-oriented Fast Mersenne Twister code v1.4.1 from + * http://www.math.sci.hiroshima-u.ac.jp/~%20m-mat/MT/SFMT/index.html + * The SFMT RNG creates unsigned int 64bit pseudo random numbers. + * + * These are mapped to values from the interval [min, max] without bias by using Knuth's + * "Algorithm S (Selection sampling technique)" from "The Art of Computer Programming 3rd + * Edition Volume 2 / Seminumerical Algorithms". + */ + class RNG_SFMT : public RNG_Abstract { - Q_OBJECT + Q_OBJECT +private: + QMutex mutex; + sfmt_t sfmt; + // The discrete cumulative distribution function for the RNG + unsigned int cdf(unsigned int min, unsigned int max); public: - RNG_SFMT(QObject *parent = 0); - unsigned int getNumber(unsigned int min, unsigned int max); + RNG_SFMT(QObject *parent = 0); + unsigned int rand(int min, int max); }; #endif diff --git a/common/serializable_item.cpp b/common/serializable_item.cpp deleted file mode 100644 index 42c142514..000000000 --- a/common/serializable_item.cpp +++ /dev/null @@ -1,207 +0,0 @@ -#include "serializable_item.h" -#include -#include -#include - -QHash SerializableItem::itemNameHash; - -SerializableItem *SerializableItem::getNewItem(const QString &name) -{ - if (!itemNameHash.contains(name)) - return 0; - return itemNameHash.value(name)(); -} - -void SerializableItem::registerSerializableItem(const QString &name, NewItemFunction func) -{ - itemNameHash.insert(name, func); -} - -bool SerializableItem::read(QXmlStreamReader *xml) -{ - if (!compressed) - return readElement(xml); - if (xml->isEndElement() && (xml->name() == itemType)) { - QByteArray uncompressedData = "" + qUncompress(QByteArray::fromBase64(compressedData)) + ""; - compressedData.clear(); - QBuffer compressedBuffer(&uncompressedData); - compressedBuffer.open(QIODevice::ReadOnly); - QXmlStreamReader *xml2 = new QXmlStreamReader(&compressedBuffer); - while (!xml2->atEnd()) { - xml2->readNext(); - if (xml2->name() == "d") - continue; - readElement(xml2); - } - delete xml2; - compressedBuffer.close(); - - return readElement(xml); - } else { - compressedData.append(xml->text().toString()); - return false; - } -} - -bool SerializableItem::readElement(QXmlStreamReader *xml) -{ - if (xml->isEndElement() && (xml->name() == itemType)) - return true; - return false; -} - -void SerializableItem::write(QXmlStreamWriter *xml) -{ - if (isEmpty()) - return; - - xml->writeStartElement(itemType); - if (!itemSubType.isEmpty()) - xml->writeAttribute("type", itemSubType); - if (compressed) { - xml->writeAttribute("comp", "1"); - - QBuffer compressBuffer; - compressBuffer.open(QIODevice::WriteOnly); - QXmlStreamWriter *xml2 = new QXmlStreamWriter(&compressBuffer); - writeElement(xml2); - delete xml2; - compressBuffer.close(); - - xml->writeCharacters(qCompress(compressBuffer.data()).toBase64()); - } else - writeElement(xml); - xml->writeEndElement(); -} - -SerializableItem_Map::~SerializableItem_Map() -{ - QMapIterator mapIterator(itemMap); - while (mapIterator.hasNext()) - delete mapIterator.next().value(); - for (int i = 0; i < itemList.size(); ++i) - delete itemList[i]; -} - -bool SerializableItem_Map::readElement(QXmlStreamReader *xml) -{ - if (currentItem) { - if (currentItem->read(xml)) - currentItem = 0; - return false; - } else if (firstItem) - firstItem = false; - else if (xml->isEndElement() && (xml->name() == itemType)) - extractData(); - else if (xml->isStartElement()) { - QString childName = xml->name().toString(); - QString childSubType = xml->attributes().value("type").toString(); - bool childCompressed = xml->attributes().value("comp").toString().toInt() == 1; - currentItem = itemMap.value(childName); - if (!currentItem) { - currentItem = getNewItem(childName + childSubType); - itemList.append(currentItem); - if (!currentItem) - currentItem = new SerializableItem_Invalid(childName); - } - currentItem->setCompressed(childCompressed); - if (currentItem->read(xml)) - currentItem = 0; - } - return SerializableItem::readElement(xml); -} - -void SerializableItem_Map::writeElement(QXmlStreamWriter *xml) -{ - QMapIterator mapIterator(itemMap); - while (mapIterator.hasNext()) - mapIterator.next().value()->write(xml); - for (int i = 0; i < itemList.size(); ++i) - itemList[i]->write(xml); -} - -bool SerializableItem_String::readElement(QXmlStreamReader *xml) -{ - // This function is sometimes called multiple times if there are - // entities in the strings, so we have to make sure the data is - // not overwritten but appended to. - if (xml->isCharacters() && !xml->isWhitespace()) - data.append(xml->text().toString()); - return SerializableItem::readElement(xml); -} - -void SerializableItem_String::writeElement(QXmlStreamWriter *xml) -{ - xml->writeCharacters(data); -} - -bool SerializableItem_Int::readElement(QXmlStreamReader *xml) -{ - if (xml->isCharacters() && !xml->isWhitespace()) { - bool ok; - data = xml->text().toString().toInt(&ok); - if (!ok) - data = -1; - } - return SerializableItem::readElement(xml); -} - -void SerializableItem_Int::writeElement(QXmlStreamWriter *xml) -{ - xml->writeCharacters(QString::number(data)); -} - -bool SerializableItem_Bool::readElement(QXmlStreamReader *xml) -{ - if (xml->isCharacters() && !xml->isWhitespace()) - data = xml->text().toString() == "1"; - return SerializableItem::readElement(xml); -} - -void SerializableItem_Bool::writeElement(QXmlStreamWriter *xml) -{ - xml->writeCharacters(data ? "1" : "0"); -} - -bool SerializableItem_Color::readElement(QXmlStreamReader *xml) -{ - if (xml->isCharacters() && !xml->isWhitespace()) { - bool ok; - int colorValue = xml->text().toString().toInt(&ok); - data = ok ? Color(colorValue) : Color(); - } - return SerializableItem::readElement(xml); -} - -void SerializableItem_Color::writeElement(QXmlStreamWriter *xml) -{ - xml->writeCharacters(QString::number(data.getValue())); -} - -bool SerializableItem_DateTime::readElement(QXmlStreamReader *xml) -{ - if (xml->isCharacters() && !xml->isWhitespace()) { - bool ok; - unsigned int dateTimeValue = xml->text().toString().toUInt(&ok); - data = ok ? QDateTime::fromTime_t(dateTimeValue) : QDateTime(); - } - return SerializableItem::readElement(xml); -} - -void SerializableItem_DateTime::writeElement(QXmlStreamWriter *xml) -{ - xml->writeCharacters(QString::number(data.toTime_t())); -} - -bool SerializableItem_ByteArray::readElement(QXmlStreamReader *xml) -{ - if (xml->isCharacters() && !xml->isWhitespace()) - data = qUncompress(QByteArray::fromBase64(xml->text().toString().toAscii())); - - return SerializableItem::readElement(xml); -} - -void SerializableItem_ByteArray::writeElement(QXmlStreamWriter *xml) -{ - xml->writeCharacters(QString(qCompress(data).toBase64())); -} diff --git a/common/serializable_item.h b/common/serializable_item.h deleted file mode 100644 index 50da5e368..000000000 --- a/common/serializable_item.h +++ /dev/null @@ -1,167 +0,0 @@ -#ifndef SERIALIZABLE_ITEM_H -#define SERIALIZABLE_ITEM_H - -#include -#include -#include -#include -#include -#include -#include -#include "color.h" - -class QXmlStreamReader; -class QXmlStreamWriter; - -class SerializableItem : public QObject { - Q_OBJECT -private: - bool compressed; - QByteArray compressedData; - QXmlStreamReader *compressedReader; -protected: - typedef SerializableItem *(*NewItemFunction)(); - static QHash itemNameHash; - - QString itemType, itemSubType; - bool firstItem; -public: - SerializableItem(const QString &_itemType, const QString &_itemSubType = QString()) - : QObject(), compressed(false), itemType(_itemType), itemSubType(_itemSubType), firstItem(true) { } - static void registerSerializableItem(const QString &name, NewItemFunction func); - static SerializableItem *getNewItem(const QString &name); - const QString &getItemType() const { return itemType; } - const QString &getItemSubType() const { return itemSubType; } - virtual bool readElement(QXmlStreamReader *xml); - virtual void writeElement(QXmlStreamWriter *xml) = 0; - virtual bool isEmpty() const = 0; - void setCompressed(bool _compressed) { compressed = _compressed; } - bool read(QXmlStreamReader *xml); - void write(QXmlStreamWriter *xml); -}; - -class SerializableItem_Invalid : public SerializableItem { -public: - SerializableItem_Invalid(const QString &_itemType) : SerializableItem(_itemType) { } - void writeElement(QXmlStreamWriter * /*xml*/) { } - bool isEmpty() const { return true; } -}; - -class SerializableItem_Map : public SerializableItem { -private: - SerializableItem *currentItem; -protected: - QMap itemMap; - QList itemList; - virtual void extractData() { } - void insertItem(SerializableItem *item) - { - itemMap.insert(item->getItemType(), item); - } - template QList typecastItemList() const - { - QList result; - for (int i = 0; i < itemList.size(); ++i) { - T item = dynamic_cast(itemList[i]); - if (item) - result.append(item); - } - return result; - } -public: - SerializableItem_Map(const QString &_itemType, const QString &_itemSubType = QString()) - : SerializableItem(_itemType, _itemSubType), currentItem(0) - { - } - ~SerializableItem_Map(); - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); - bool isEmpty() const { return itemMap.isEmpty() && itemList.isEmpty(); } - void appendItem(SerializableItem *item) { itemList.append(item); } -}; - -class SerializableItem_String : public SerializableItem { -private: - QString data; -protected: - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); -public: - SerializableItem_String(const QString &_itemType, const QString &_data = QString()) - : SerializableItem(_itemType), data(_data) { } - const QString &getData() { return data; } - void setData(const QString &_data) { data = _data; } - bool isEmpty() const { return data.isEmpty(); } -}; - -class SerializableItem_Int : public SerializableItem { -private: - int data; -protected: - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); -public: - SerializableItem_Int(const QString &_itemType, int _data = -1) - : SerializableItem(_itemType), data(_data) { } - int getData() { return data; } - void setData(int _data) { data = _data; } - bool isEmpty() const { return data == -1; } -}; - -class SerializableItem_Bool : public SerializableItem { -private: - bool data; -protected: - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); -public: - SerializableItem_Bool(const QString &_itemType, bool _data = false) - : SerializableItem(_itemType), data(_data) { } - bool getData() { return data; } - void setData(bool _data) { data = _data; } - bool isEmpty() const { return data == false; } -}; - -class SerializableItem_Color : public SerializableItem { -private: - Color data; -protected: - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); -public: - SerializableItem_Color(const QString &_itemType, const Color &_data = Color()) - : SerializableItem(_itemType), data(_data) { } - const Color &getData() { return data; } - void setData(const Color &_data) { data = _data; } - bool isEmpty() const { return data.getValue() == 0; } -}; - -class SerializableItem_DateTime : public SerializableItem { -private: - QDateTime data; -protected: - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); -public: - SerializableItem_DateTime(const QString &_itemType, const QDateTime &_data = QDateTime()) - : SerializableItem(_itemType), data(_data) { } - const QDateTime &getData() { return data; } - void setData(const QDateTime &_data) { data = _data; } - bool isEmpty() const { return data == QDateTime(); } -}; - -class SerializableItem_ByteArray : public SerializableItem { -private: - QByteArray data; -protected: - bool readElement(QXmlStreamReader *xml); - void writeElement(QXmlStreamWriter *xml); -public: - SerializableItem_ByteArray(const QString &_itemType, const QByteArray &_data = QByteArray()) - : SerializableItem(_itemType), data(_data) { } - const QByteArray &getData() { return data; } - void setData(const QByteArray &_data) { data = _data; } - bool isEmpty() const { return data.isEmpty(); } -}; - -#endif diff --git a/common/server.cpp b/common/server.cpp index 4a37dadc2..6ab4750e2 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -19,16 +19,36 @@ ***************************************************************************/ #include "server.h" #include "server_game.h" +#include "server_player.h" #include "server_counter.h" #include "server_room.h" #include "server_protocolhandler.h" -#include "protocol_datastructures.h" +#include "server_remoteuserinterface.h" +#include "server_metatypes.h" +#include "server_database_interface.h" +#include "pb/event_user_joined.pb.h" +#include "pb/event_user_left.pb.h" +#include "pb/event_list_rooms.pb.h" +#include "pb/session_event.pb.h" +#include "pb/isl_message.pb.h" #include +#include #include +#include -Server::Server(QObject *parent) - : QObject(parent), serverMutex(QMutex::Recursive), nextGameId(0) +Server::Server(bool _threaded, QObject *parent) + : QObject(parent), threaded(_threaded), nextLocalGameId(0) { + qRegisterMetaType("ServerInfo_Game"); + qRegisterMetaType("ServerInfo_Room"); + qRegisterMetaType("ServerInfo_User"); + qRegisterMetaType("CommandContainer"); + qRegisterMetaType("Response"); + qRegisterMetaType("GameEventContainer"); + qRegisterMetaType("IslMessage"); + qRegisterMetaType("Command_JoinGame"); + + connect(this, SIGNAL(sigSendIslMessage(IslMessage, int)), this, SLOT(doSendIslMessage(IslMessage, int)), Qt::QueuedConnection); } Server::~Server() @@ -37,129 +57,540 @@ Server::~Server() void Server::prepareDestroy() { - QMutexLocker locker(&serverMutex); - - while (!clients.isEmpty()) - delete clients.takeFirst(); - - QMapIterator roomIterator(rooms); - while (roomIterator.hasNext()) - delete roomIterator.next().value(); + // dirty :( + if (threaded) { + clientsLock.lockForRead(); + for (int i = 0; i < clients.size(); ++i) + QMetaObject::invokeMethod(clients.at(i), "prepareDestroy", Qt::QueuedConnection); + clientsLock.unlock(); + + bool done = false; + + class SleeperThread : public QThread + { + public: + static void msleep(unsigned long msecs) { QThread::usleep(msecs); } + }; + + do { + SleeperThread::msleep(10); + clientsLock.lockForRead(); + if (clients.isEmpty()) + done = true; + clientsLock.unlock(); + } while (!done); + } else { + // no locking is needed in unthreaded mode + while (!clients.isEmpty()) + clients.first()->prepareDestroy(); + } + + roomsLock.lockForWrite(); + QMapIterator roomIterator(rooms); + while (roomIterator.hasNext()) + delete roomIterator.next().value(); + rooms.clear(); + roomsLock.unlock(); } -AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString &name, const QString &password) +void Server::setDatabaseInterface(Server_DatabaseInterface *_databaseInterface) { - QMutexLocker locker(&serverMutex); - if (name.size() > 35) - name = name.left(35); - AuthenticationResult authState = checkUserPassword(session, name, password); - if (authState == PasswordWrong) - return authState; - - ServerInfo_User *data = getUserData(name); - data->setAddress(session->getAddress()); - name = data->getName(); // Compensate for case indifference - - if (authState == PasswordRight) { - if (users.contains(name)) { - qDebug("Login denied: would overwrite old session"); - delete data; - return WouldOverwriteOldSession; - } - } else if (authState == UnknownUser) { - // Change user name so that no two users have the same names, - // don't interfere with registered user names though. - QString tempName = name; - int i = 0; - while (users.contains(tempName) || userExists(tempName)) - tempName = name + "_" + QString::number(++i); - name = tempName; - data->setName(name); - } - - session->setUserInfo(data); - - users.insert(name, session); - qDebug() << "Server::loginUser: name=" << name; - - session->setSessionId(startSession(name, session->getAddress())); - qDebug() << "session id:" << session->getSessionId(); - - Event_UserJoined *event = new Event_UserJoined(new ServerInfo_User(data, false)); - for (int i = 0; i < clients.size(); ++i) - if (clients[i]->getAcceptsUserListChanges()) - clients[i]->sendProtocolItem(event, false); - delete event; - - return authState; + connect(this, SIGNAL(endSession(qint64)), _databaseInterface, SLOT(endSession(qint64))); + databaseInterfaces.insert(QThread::currentThread(), _databaseInterface); +} + +Server_DatabaseInterface *Server::getDatabaseInterface() const +{ + return databaseInterfaces.value(QThread::currentThread()); +} + +AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reasonStr, int &secondsLeft) +{ + if (name.size() > 35) + name = name.left(35); + + Server_DatabaseInterface *databaseInterface = getDatabaseInterface(); + + QWriteLocker locker(&clientsLock); + + AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, reasonStr, secondsLeft); + if ((authState == NotLoggedIn) || (authState == UserIsBanned || authState == UsernameInvalid)) + return authState; + + ServerInfo_User data = databaseInterface->getUserData(name, true); + data.set_address(session->getAddress().toStdString()); + name = QString::fromStdString(data.name()); // Compensate for case indifference + + databaseInterface->lockSessionTables(); + + if (authState == PasswordRight) { + if (users.contains(name) || databaseInterface->userSessionExists(name)) { + qDebug("Login denied: would overwrite old session"); + databaseInterface->unlockSessionTables(); + return WouldOverwriteOldSession; + } + } else if (authState == UnknownUser) { + // Change user name so that no two users have the same names, + // don't interfere with registered user names though. + QSettings settings("servatrice.ini", QSettings::IniFormat); + bool requireReg = settings.value("authentication/regonly", 0).toBool(); + if (requireReg) { + qDebug("Login denied: registration required"); + databaseInterface->unlockSessionTables(); + return RegistrationRequired; + } + + QString tempName = name; + int i = 0; + while (users.contains(tempName) || databaseInterface->userExists(tempName) || databaseInterface->userSessionExists(tempName)) + tempName = name + "_" + QString::number(++i); + name = tempName; + data.set_name(name.toStdString()); + } + + users.insert(name, session); + qDebug() << "Server::loginUser:" << session << "name=" << name; + + data.set_session_id(databaseInterface->startSession(name, session->getAddress())); + databaseInterface->unlockSessionTables(); + + usersBySessionId.insert(data.session_id(), session); + + qDebug() << "session id:" << data.session_id(); + session->setUserInfo(data); + + Event_UserJoined event; + event.mutable_user_info()->CopyFrom(session->copyUserInfo(false)); + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); + for (int i = 0; i < clients.size(); ++i) + if (clients[i]->getAcceptsUserListChanges()) + clients[i]->sendProtocolItem(*se); + delete se; + + event.mutable_user_info()->CopyFrom(session->copyUserInfo(true, true, true)); + locker.unlock(); + + se = Server_ProtocolHandler::prepareSessionEvent(event); + sendIsl_SessionEvent(*se); + delete se; + + return authState; +} + +void Server::addPersistentPlayer(const QString &userName, int roomId, int gameId, int playerId) +{ + QWriteLocker locker(&persistentPlayersLock); + persistentPlayers.insert(userName, PlayerReference(roomId, gameId, playerId)); +} + +void Server::removePersistentPlayer(const QString &userName, int roomId, int gameId, int playerId) +{ + QWriteLocker locker(&persistentPlayersLock); + persistentPlayers.remove(userName, PlayerReference(roomId, gameId, playerId)); +} + +QList Server::getPersistentPlayerReferences(const QString &userName) const +{ + QReadLocker locker(&persistentPlayersLock); + return persistentPlayers.values(userName); +} + +Server_AbstractUserInterface *Server::findUser(const QString &userName) const +{ + // Call this only with clientsLock set. + + Server_AbstractUserInterface *userHandler = users.value(userName); + if (userHandler) + return userHandler; + else + return externalUsers.value(userName); } void Server::addClient(Server_ProtocolHandler *client) { - QMutexLocker locker(&serverMutex); - clients << client; + QWriteLocker locker(&clientsLock); + clients << client; } void Server::removeClient(Server_ProtocolHandler *client) { - QMutexLocker locker(&serverMutex); - clients.removeAt(clients.indexOf(client)); - ServerInfo_User *data = client->getUserInfo(); - if (data) { - Event_UserLeft *event = new Event_UserLeft(data->getName()); - for (int i = 0; i < clients.size(); ++i) - if (clients[i]->getAcceptsUserListChanges()) - clients[i]->sendProtocolItem(event, false); - delete event; - - users.remove(data->getName()); - qDebug() << "Server::removeClient: name=" << data->getName(); - - if (client->getSessionId() != -1) - endSession(client->getSessionId()); - qDebug() << "closed session id:" << client->getSessionId(); - } - qDebug() << "Server::removeClient:" << clients.size() << "clients; " << users.size() << "users left"; + QWriteLocker locker(&clientsLock); + clients.removeAt(clients.indexOf(client)); + ServerInfo_User *data = client->getUserInfo(); + if (data) { + Event_UserLeft event; + event.set_name(data->name()); + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); + for (int i = 0; i < clients.size(); ++i) + if (clients[i]->getAcceptsUserListChanges()) + clients[i]->sendProtocolItem(*se); + sendIsl_SessionEvent(*se); + delete se; + + users.remove(QString::fromStdString(data->name())); + qDebug() << "Server::removeClient: name=" << QString::fromStdString(data->name()); + + if (data->has_session_id()) { + const qint64 sessionId = data->session_id(); + usersBySessionId.remove(sessionId); + emit endSession(sessionId); + qDebug() << "closed session id:" << sessionId; + } + } + qDebug() << "Server::removeClient: removed" << (void *) client << ";" << clients.size() << "clients; " << users.size() << "users left"; } -void Server::broadcastRoomUpdate() +void Server::externalUserJoined(const ServerInfo_User &userInfo) { - QMutexLocker locker(&serverMutex); - Server_Room *room = static_cast(sender()); - QList eventRoomList; - room->roomMutex.lock(); - eventRoomList.append(new ServerInfo_Room(room->getId(), room->getName(), room->getDescription(), room->getGames().size(), room->size(), room->getAutoJoin())); - room->roomMutex.unlock(); - Event_ListRooms *event = new Event_ListRooms(eventRoomList); + // This function is always called from the main thread via signal/slot. + clientsLock.lockForWrite(); + + Server_RemoteUserInterface *newUser = new Server_RemoteUserInterface(this, ServerInfo_User_Container(userInfo)); + externalUsers.insert(QString::fromStdString(userInfo.name()), newUser); + externalUsersBySessionId.insert(userInfo.session_id(), newUser); + + Event_UserJoined event; + event.mutable_user_info()->CopyFrom(userInfo); + + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); + for (int i = 0; i < clients.size(); ++i) + if (clients[i]->getAcceptsUserListChanges()) + clients[i]->sendProtocolItem(*se); + delete se; + clientsLock.unlock(); + + ResponseContainer rc(-1); + newUser->joinPersistentGames(rc); + newUser->sendResponseContainer(rc, Response::RespNothing); +} - for (int i = 0; i < clients.size(); ++i) - if (clients[i]->getAcceptsRoomListChanges()) - clients[i]->sendProtocolItem(event, false); - delete event; +void Server::externalUserLeft(const QString &userName) +{ + // This function is always called from the main thread via signal/slot. + + clientsLock.lockForWrite(); + Server_AbstractUserInterface *user = externalUsers.take(userName); + externalUsersBySessionId.remove(user->getUserInfo()->session_id()); + clientsLock.unlock(); + + QMap > userGames(user->getGames()); + QMapIterator > userGamesIterator(userGames); + roomsLock.lockForRead(); + while (userGamesIterator.hasNext()) { + userGamesIterator.next(); + Server_Room *room = rooms.value(userGamesIterator.value().first); + if (!room) + continue; + + QReadLocker roomGamesLocker(&room->gamesLock); + Server_Game *game = room->getGames().value(userGamesIterator.key()); + if (!game) + continue; + + QMutexLocker gameLocker(&game->gameMutex); + Server_Player *player = game->getPlayers().value(userGamesIterator.value().second); + if (!player) + continue; + + player->disconnectClient(); + } + roomsLock.unlock(); + + delete user; + + Event_UserLeft event; + event.set_name(userName.toStdString()); + + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); + clientsLock.lockForRead(); + for (int i = 0; i < clients.size(); ++i) + if (clients[i]->getAcceptsUserListChanges()) + clients[i]->sendProtocolItem(*se); + clientsLock.unlock(); + delete se; +} + +void Server::externalRoomUserJoined(int roomId, const ServerInfo_User &userInfo) +{ + // This function is always called from the main thread via signal/slot. + QReadLocker locker(&roomsLock); + + Server_Room *room = rooms.value(roomId); + if (!room) { + qDebug() << "externalRoomUserJoined: room id=" << roomId << "not found"; + return; + } + room->addExternalUser(userInfo); +} + +void Server::externalRoomUserLeft(int roomId, const QString &userName) +{ + // This function is always called from the main thread via signal/slot. + QReadLocker locker(&roomsLock); + + Server_Room *room = rooms.value(roomId); + if (!room) { + qDebug() << "externalRoomUserLeft: room id=" << roomId << "not found"; + return; + } + room->removeExternalUser(userName); +} + +void Server::externalRoomSay(int roomId, const QString &userName, const QString &message) +{ + // This function is always called from the main thread via signal/slot. + QReadLocker locker(&roomsLock); + + Server_Room *room = rooms.value(roomId); + if (!room) { + qDebug() << "externalRoomSay: room id=" << roomId << "not found"; + return; + } + room->say(userName, message, false); +} + +void Server::externalRoomGameListChanged(int roomId, const ServerInfo_Game &gameInfo) +{ + // This function is always called from the main thread via signal/slot. + QReadLocker locker(&roomsLock); + + Server_Room *room = rooms.value(roomId); + if (!room) { + qDebug() << "externalRoomGameListChanged: room id=" << roomId << "not found"; + return; + } + room->updateExternalGameList(gameInfo); +} + +void Server::externalJoinGameCommandReceived(const Command_JoinGame &cmd, int cmdId, int roomId, int serverId, qint64 sessionId) +{ + // This function is always called from the main thread via signal/slot. + + try { + QReadLocker roomsLocker(&roomsLock); + QReadLocker clientsLocker(&clientsLock); + + Server_Room *room = rooms.value(roomId); + if (!room) { + qDebug() << "externalJoinGameCommandReceived: room id=" << roomId << "not found"; + throw Response::RespNotInRoom; + } + Server_AbstractUserInterface *userInterface = externalUsersBySessionId.value(sessionId); + if (!userInterface) { + qDebug() << "externalJoinGameCommandReceived: session id=" << sessionId << "not found"; + throw Response::RespNotInRoom; + } + + ResponseContainer responseContainer(cmdId); + Response::ResponseCode responseCode = room->processJoinGameCommand(cmd, responseContainer, userInterface); + userInterface->sendResponseContainer(responseContainer, responseCode); + } catch (Response::ResponseCode code) { + Response response; + response.set_cmd_id(cmdId); + response.set_response_code(code); + + sendIsl_Response(response, serverId, sessionId); + } +} + +void Server::externalGameCommandContainerReceived(const CommandContainer &cont, int playerId, int serverId, qint64 sessionId) +{ + // This function is always called from the main thread via signal/slot. + + try { + ResponseContainer responseContainer(cont.cmd_id()); + Response::ResponseCode finalResponseCode = Response::RespOk; + + QReadLocker roomsLocker(&roomsLock); + Server_Room *room = rooms.value(cont.room_id()); + if (!room) { + qDebug() << "externalGameCommandContainerReceived: room id=" << cont.room_id() << "not found"; + throw Response::RespNotInRoom; + } + + QReadLocker roomGamesLocker(&room->gamesLock); + Server_Game *game = room->getGames().value(cont.game_id()); + if (!game) { + qDebug() << "externalGameCommandContainerReceived: game id=" << cont.game_id() << "not found"; + throw Response::RespNotInRoom; + } + + QMutexLocker gameLocker(&game->gameMutex); + Server_Player *player = game->getPlayers().value(playerId); + if (!player) { + qDebug() << "externalGameCommandContainerReceived: player id=" << playerId << "not found"; + throw Response::RespNotInRoom; + } + + GameEventStorage ges; + for (int i = cont.game_command_size() - 1; i >= 0; --i) { + const GameCommand &sc = cont.game_command(i); + qDebug() << "[ISL]" << QString::fromStdString(sc.ShortDebugString()); + + Response::ResponseCode resp = player->processGameCommand(sc, responseContainer, ges); + + if (resp != Response::RespOk) + finalResponseCode = resp; + } + ges.sendToGame(game); + + if (finalResponseCode != Response::RespNothing) { + player->playerMutex.lock(); + player->getUserInterface()->sendResponseContainer(responseContainer, finalResponseCode); + player->playerMutex.unlock(); + } + } catch (Response::ResponseCode code) { + Response response; + response.set_cmd_id(cont.cmd_id()); + response.set_response_code(code); + + sendIsl_Response(response, serverId, sessionId); + } +} + +void Server::externalGameEventContainerReceived(const GameEventContainer &cont, qint64 sessionId) +{ + // This function is always called from the main thread via signal/slot. + + QReadLocker usersLocker(&clientsLock); + + Server_ProtocolHandler *client = usersBySessionId.value(sessionId); + if (!client) { + qDebug() << "externalGameEventContainerReceived: session" << sessionId << "not found"; + return; + } + client->sendProtocolItem(cont); +} + +void Server::externalResponseReceived(const Response &resp, qint64 sessionId) +{ + // This function is always called from the main thread via signal/slot. + + QReadLocker usersLocker(&clientsLock); + + Server_ProtocolHandler *client = usersBySessionId.value(sessionId); + if (!client) { + qDebug() << "externalResponseReceived: session" << sessionId << "not found"; + return; + } + client->sendProtocolItem(resp); +} + +void Server::broadcastRoomUpdate(const ServerInfo_Room &roomInfo, bool sendToIsl) +{ + // This function is always called from the main thread via signal/slot. + + Event_ListRooms event; + event.add_room_list()->CopyFrom(roomInfo); + + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); + + clientsLock.lockForRead(); + for (int i = 0; i < clients.size(); ++i) + if (clients[i]->getAcceptsRoomListChanges()) + clients[i]->sendProtocolItem(*se); + clientsLock.unlock(); + + if (sendToIsl) + sendIsl_SessionEvent(*se); + + delete se; } void Server::addRoom(Server_Room *newRoom) { - QMutexLocker locker(&serverMutex); - rooms.insert(newRoom->getId(), newRoom); - connect(newRoom, SIGNAL(roomInfoChanged()), this, SLOT(broadcastRoomUpdate())); + QWriteLocker locker(&roomsLock); + qDebug() << "Adding room: ID=" << newRoom->getId() << "name=" << newRoom->getName(); + rooms.insert(newRoom->getId(), newRoom); + connect(newRoom, SIGNAL(roomInfoChanged(ServerInfo_Room)), this, SLOT(broadcastRoomUpdate(const ServerInfo_Room &)), Qt::QueuedConnection); } int Server::getUsersCount() const { - QMutexLocker locker(&serverMutex); - return users.size(); + QReadLocker locker(&clientsLock); + return users.size(); } int Server::getGamesCount() const { - int result = 0; - QMutexLocker locker(&serverMutex); - QMapIterator roomIterator(rooms); - while (roomIterator.hasNext()) { - Server_Room *room = roomIterator.next().value(); - QMutexLocker roomLocker(&room->roomMutex); - result += room->getGames().size(); - } - return result; + int result = 0; + QReadLocker locker(&roomsLock); + QMapIterator roomIterator(rooms); + while (roomIterator.hasNext()) { + Server_Room *room = roomIterator.next().value(); + QReadLocker roomLocker(&room->gamesLock); + result += room->getGames().size(); + } + return result; +} + +void Server::sendIsl_Response(const Response &item, int serverId, qint64 sessionId) +{ + IslMessage msg; + msg.set_message_type(IslMessage::RESPONSE); + if (sessionId != -1) + msg.set_session_id(sessionId); + msg.mutable_response()->CopyFrom(item); + + emit sigSendIslMessage(msg, serverId); +} + +void Server::sendIsl_SessionEvent(const SessionEvent &item, int serverId, qint64 sessionId) +{ + IslMessage msg; + msg.set_message_type(IslMessage::SESSION_EVENT); + if (sessionId != -1) + msg.set_session_id(sessionId); + msg.mutable_session_event()->CopyFrom(item); + + emit sigSendIslMessage(msg, serverId); +} + +void Server::sendIsl_GameEventContainer(const GameEventContainer &item, int serverId, qint64 sessionId) +{ + IslMessage msg; + msg.set_message_type(IslMessage::GAME_EVENT_CONTAINER); + if (sessionId != -1) + msg.set_session_id(sessionId); + msg.mutable_game_event_container()->CopyFrom(item); + + emit sigSendIslMessage(msg, serverId); +} + +void Server::sendIsl_RoomEvent(const RoomEvent &item, int serverId, qint64 sessionId) +{ + IslMessage msg; + msg.set_message_type(IslMessage::ROOM_EVENT); + if (sessionId != -1) + msg.set_session_id(sessionId); + msg.mutable_room_event()->CopyFrom(item); + + emit sigSendIslMessage(msg, serverId); +} + +void Server::sendIsl_GameCommand(const CommandContainer &item, int serverId, qint64 sessionId, int roomId, int playerId) +{ + IslMessage msg; + msg.set_message_type(IslMessage::GAME_COMMAND_CONTAINER); + msg.set_session_id(sessionId); + msg.set_player_id(playerId); + + CommandContainer *cont = msg.mutable_game_command(); + cont->CopyFrom(item); + cont->set_room_id(roomId); + + emit sigSendIslMessage(msg, serverId); +} + +void Server::sendIsl_RoomCommand(const CommandContainer &item, int serverId, qint64 sessionId, int roomId) +{ + IslMessage msg; + msg.set_message_type(IslMessage::ROOM_COMMAND_CONTAINER); + msg.set_session_id(sessionId); + + CommandContainer *cont = msg.mutable_room_command(); + cont->CopyFrom(item); + cont->set_room_id(roomId); + + emit sigSendIslMessage(msg, serverId); } diff --git a/common/server.h b/common/server.h index 467642f65..810886226 100644 --- a/common/server.h +++ b/common/server.h @@ -4,61 +4,114 @@ #include #include #include +#include #include +#include +#include "pb/serverinfo_user.pb.h" +#include "server_player_reference.h" +class Server_DatabaseInterface; class Server_Game; class Server_Room; class Server_ProtocolHandler; -class ServerInfo_User; +class Server_AbstractUserInterface; +class GameReplay; +class IslMessage; +class SessionEvent; +class RoomEvent; +class DeckList; +class ServerInfo_Game; +class ServerInfo_Room; +class Response; +class GameEventContainer; +class CommandContainer; +class Command_JoinGame; -enum AuthenticationResult { PasswordWrong = 0, PasswordRight = 1, UnknownUser = 2, WouldOverwriteOldSession = 3 }; +enum AuthenticationResult { NotLoggedIn = 0, PasswordRight = 1, UnknownUser = 2, WouldOverwriteOldSession = 3, UserIsBanned = 4, UsernameInvalid = 5, RegistrationRequired = 6 }; class Server : public QObject { - Q_OBJECT + Q_OBJECT signals: - void pingClockTimeout(); + void pingClockTimeout(); + void sigSendIslMessage(const IslMessage &message, int serverId); + void endSession(qint64 sessionId); private slots: - void broadcastRoomUpdate(); + void broadcastRoomUpdate(const ServerInfo_Room &roomInfo, bool sendToIsl = false); public: - mutable QMutex serverMutex; - Server(QObject *parent = 0); - ~Server(); - AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password); - const QMap &getRooms() { return rooms; } - int getNextGameId() { return nextGameId++; } - - const QMap &getUsers() const { return users; } - void addClient(Server_ProtocolHandler *player); - void removeClient(Server_ProtocolHandler *player); - virtual QString getLoginMessage() const = 0; - - virtual bool getGameShouldPing() const = 0; - virtual int getMaxGameInactivityTime() const = 0; - virtual int getMaxPlayerInactivityTime() const = 0; - virtual int getMessageCountingInterval() const { return 0; } - virtual int getMaxMessageCountPerInterval() const { return 0; } - virtual int getMaxMessageSizePerInterval() const { return 0; } - virtual int getMaxGamesPerUser() const { return 0; } - virtual bool getThreaded() const = 0; - - virtual QMap getBuddyList(const QString &name) = 0; - virtual QMap getIgnoreList(const QString &name) = 0; + mutable QReadWriteLock clientsLock, roomsLock; // locking order: roomsLock before clientsLock + Server(bool _threaded, QObject *parent = 0); + ~Server(); + void setThreaded(bool _threaded) { threaded = _threaded; } + AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason, int &secondsLeft); + const QMap &getRooms() { return rooms; } + + Server_AbstractUserInterface *findUser(const QString &userName) const; + const QMap &getUsers() const { return users; } + const QMap &getUsersBySessionId() const { return usersBySessionId; } + void addClient(Server_ProtocolHandler *player); + void removeClient(Server_ProtocolHandler *player); + virtual QString getLoginMessage() const { return QString(); } + + virtual bool getGameShouldPing() const { return false; } + virtual int getMaxGameInactivityTime() const { return 9999999; } + virtual int getMaxPlayerInactivityTime() const { return 9999999; } + virtual int getMessageCountingInterval() const { return 0; } + virtual int getMaxMessageCountPerInterval() const { return 0; } + virtual int getMaxMessageSizePerInterval() const { return 0; } + virtual int getMaxGamesPerUser() const { return 0; } + virtual bool getThreaded() const { return false; } + + Server_DatabaseInterface *getDatabaseInterface() const; + int getNextLocalGameId() { QMutexLocker locker(&nextLocalGameIdMutex); return ++nextLocalGameId; } + + void sendIsl_Response(const Response &item, int serverId = -1, qint64 sessionId = -1); + void sendIsl_SessionEvent(const SessionEvent &item, int serverId = -1, qint64 sessionId = -1); + void sendIsl_GameEventContainer(const GameEventContainer &item, int serverId = -1, qint64 sessionId = -1); + void sendIsl_RoomEvent(const RoomEvent &item, int serverId = -1, qint64 sessionId = -1); + void sendIsl_GameCommand(const CommandContainer &item, int serverId, qint64 sessionId, int roomId, int playerId); + void sendIsl_RoomCommand(const CommandContainer &item, int serverId, qint64 sessionId, int roomId); + + void addExternalUser(const ServerInfo_User &userInfo); + void removeExternalUser(const QString &userName); + const QMap &getExternalUsers() const { return externalUsers; } + + void addPersistentPlayer(const QString &userName, int roomId, int gameId, int playerId); + void removePersistentPlayer(const QString &userName, int roomId, int gameId, int playerId); + QList getPersistentPlayerReferences(const QString &userName) const; +private: + bool threaded; + QMultiMap persistentPlayers; + mutable QReadWriteLock persistentPlayersLock; + int nextLocalGameId; + QMutex nextLocalGameIdMutex; +protected slots: + void externalUserJoined(const ServerInfo_User &userInfo); + void externalUserLeft(const QString &userName); + void externalRoomUserJoined(int roomId, const ServerInfo_User &userInfo); + void externalRoomUserLeft(int roomId, const QString &userName); + void externalRoomSay(int roomId, const QString &userName, const QString &message); + void externalRoomGameListChanged(int roomId, const ServerInfo_Game &gameInfo); + void externalJoinGameCommandReceived(const Command_JoinGame &cmd, int cmdId, int roomId, int serverId, qint64 sessionId); + void externalGameCommandContainerReceived(const CommandContainer &cont, int playerId, int serverId, qint64 sessionId); + void externalGameEventContainerReceived(const GameEventContainer &cont, qint64 sessionId); + void externalResponseReceived(const Response &resp, qint64 sessionId); + + virtual void doSendIslMessage(const IslMessage & /* msg */, int /* serverId */) { } protected: - void prepareDestroy(); - QList clients; - QMap users; - QMap rooms; - - virtual int startSession(const QString &userName, const QString &address) = 0; - virtual void endSession(int sessionId) = 0; - virtual bool userExists(const QString &user) = 0; - virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password) = 0; - virtual ServerInfo_User *getUserData(const QString &name) = 0; - int getUsersCount() const; - int getGamesCount() const; - int nextGameId; - void addRoom(Server_Room *newRoom); + void prepareDestroy(); + void setDatabaseInterface(Server_DatabaseInterface *_databaseInterface); + QList clients; + QMap usersBySessionId; + QMap users; + QMap externalUsersBySessionId; + QMap externalUsers; + QMap rooms; + QMap databaseInterfaces; + + int getUsersCount() const; + int getGamesCount() const; + void addRoom(Server_Room *newRoom); }; #endif diff --git a/common/server_abstractuserinterface.cpp b/common/server_abstractuserinterface.cpp new file mode 100644 index 000000000..d00c5034b --- /dev/null +++ b/common/server_abstractuserinterface.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include "server_abstractuserinterface.h" +#include "server_game.h" +#include "server_response_containers.h" +#include "server_player_reference.h" +#include "server.h" +#include "server_room.h" +#include "server_game.h" +#include "server_player.h" +#include "pb/event_game_joined.pb.h" +#include "pb/event_game_state_changed.pb.h" +#include + +void Server_AbstractUserInterface::sendProtocolItemByType(ServerMessage::MessageType type, const ::google::protobuf::Message &item) +{ + switch (type) { + case ServerMessage::RESPONSE: sendProtocolItem(static_cast(item)); break; + case ServerMessage::SESSION_EVENT: sendProtocolItem(static_cast(item)); break; + case ServerMessage::GAME_EVENT_CONTAINER: sendProtocolItem(static_cast(item)); break; + case ServerMessage::ROOM_EVENT: sendProtocolItem(static_cast(item)); break; + } +} + +SessionEvent *Server_AbstractUserInterface::prepareSessionEvent(const ::google::protobuf::Message &sessionEvent) +{ + SessionEvent *event = new SessionEvent; + event->GetReflection()->MutableMessage(event, sessionEvent.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(sessionEvent); + return event; +} + +void Server_AbstractUserInterface::sendResponseContainer(const ResponseContainer &responseContainer, Response::ResponseCode responseCode) +{ + const QList > &preResponseQueue = responseContainer.getPreResponseQueue(); + for (int i = 0; i < preResponseQueue.size(); ++i) + sendProtocolItemByType(preResponseQueue[i].first, *preResponseQueue[i].second); + + if (responseCode != Response::RespNothing) { + Response response; + response.set_cmd_id(responseContainer.getCmdId()); + response.set_response_code(responseCode); + ::google::protobuf::Message *responseExtension = responseContainer.getResponseExtension(); + if (responseExtension) + response.GetReflection()->MutableMessage(&response, responseExtension->GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(*responseExtension); + sendProtocolItem(response); + } + + const QList > &postResponseQueue = responseContainer.getPostResponseQueue(); + for (int i = 0; i < postResponseQueue.size(); ++i) + sendProtocolItemByType(postResponseQueue[i].first, *postResponseQueue[i].second); +} + +void Server_AbstractUserInterface::playerRemovedFromGame(Server_Game *game) +{ + qDebug() << "Server_AbstractUserInterface::playerRemovedFromGame(): gameId =" << game->getGameId(); + + QMutexLocker locker(&gameListMutex); + games.remove(game->getGameId()); +} + +void Server_AbstractUserInterface::playerAddedToGame(int gameId, int roomId, int playerId) +{ + qDebug() << "Server_AbstractUserInterface::playerAddedToGame(): gameId =" << gameId; + + QMutexLocker locker(&gameListMutex); + games.insert(gameId, QPair(roomId, playerId)); +} + +void Server_AbstractUserInterface::joinPersistentGames(ResponseContainer &rc) +{ + QList gamesToJoin = server->getPersistentPlayerReferences(QString::fromStdString(userInfo->name())); + + server->roomsLock.lockForRead(); + for (int i = 0; i < gamesToJoin.size(); ++i) { + const PlayerReference &pr = gamesToJoin.at(i); + + Server_Room *room = server->getRooms().value(pr.getRoomId()); + if (!room) + continue; + QReadLocker roomGamesLocker(&room->gamesLock); + + Server_Game *game = room->getGames().value(pr.getGameId()); + if (!game) + continue; + QMutexLocker gameLocker(&game->gameMutex); + + Server_Player *player = game->getPlayers().value(pr.getPlayerId()); + + player->setUserInterface(this); + playerAddedToGame(game->getGameId(), room->getId(), player->getPlayerId()); + + game->createGameJoinedEvent(player, rc, true); + } + server->roomsLock.unlock(); +} diff --git a/common/server_abstractuserinterface.h b/common/server_abstractuserinterface.h new file mode 100644 index 000000000..1253c0f4d --- /dev/null +++ b/common/server_abstractuserinterface.h @@ -0,0 +1,48 @@ +#ifndef SERVER_ABSTRACTUSERINTERFACE +#define SERVER_ABSTRACTUSERINTERFACE + +#include +#include +#include +#include "serverinfo_user_container.h" +#include "pb/server_message.pb.h" +#include "pb/response.pb.h" + +class SessionEvent; +class GameEventContainer; +class RoomEvent; +class ResponseContainer; + +class Server; +class Server_Game; + +class Server_AbstractUserInterface : public ServerInfo_User_Container { +private: + mutable QMutex gameListMutex; + QMap > games; // gameId -> (roomId, playerId) +protected: + Server *server; +public: + Server_AbstractUserInterface(Server *_server) : server(_server) { } + Server_AbstractUserInterface(Server *_server, const ServerInfo_User_Container &other) : ServerInfo_User_Container(other), server(_server) { } + virtual ~Server_AbstractUserInterface() { } + + virtual int getLastCommandTime() const = 0; + + void playerRemovedFromGame(Server_Game *game); + void playerAddedToGame(int gameId, int roomId, int playerId); + void joinPersistentGames(ResponseContainer &rc); + + QMap > getGames() const { QMutexLocker locker(&gameListMutex); return games; } + + virtual void sendProtocolItem(const Response &item) = 0; + virtual void sendProtocolItem(const SessionEvent &item) = 0; + virtual void sendProtocolItem(const GameEventContainer &item) = 0; + virtual void sendProtocolItem(const RoomEvent &item) = 0; + void sendProtocolItemByType(ServerMessage::MessageType type, const ::google::protobuf::Message &item); + + static SessionEvent *prepareSessionEvent(const ::google::protobuf::Message &sessionEvent); + void sendResponseContainer(const ResponseContainer &responseContainer, Response::ResponseCode responseCode); +}; + +#endif diff --git a/common/server_arrow.cpp b/common/server_arrow.cpp new file mode 100644 index 000000000..a668cc25d --- /dev/null +++ b/common/server_arrow.cpp @@ -0,0 +1,30 @@ +#include "server_arrow.h" +#include "server_player.h" +#include "server_card.h" +#include "server_cardzone.h" +#include "pb/serverinfo_arrow.pb.h" + +Server_Arrow::Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const color &_arrowColor) + : id(_id), + startCard(_startCard), + targetItem(_targetItem), + arrowColor(_arrowColor) +{ +} + +void Server_Arrow::getInfo(ServerInfo_Arrow *info) +{ + info->set_id(id); + info->set_start_player_id(startCard->getZone()->getPlayer()->getPlayerId()); + info->set_start_zone(startCard->getZone()->getName().toStdString()); + info->set_start_card_id(startCard->getId()); + info->mutable_arrow_color()->CopyFrom(arrowColor); + + Server_Card *targetCard = qobject_cast(targetItem); + if (targetCard) { + info->set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerId()); + info->set_target_zone(targetCard->getZone()->getName().toStdString()); + info->set_target_card_id(targetCard->getId()); + } else + info->set_target_player_id(static_cast(targetItem)->getPlayerId()); +} diff --git a/common/server_arrow.h b/common/server_arrow.h index e608a20b4..01c472ef3 100644 --- a/common/server_arrow.h +++ b/common/server_arrow.h @@ -1,24 +1,26 @@ #ifndef SERVER_ARROW_H #define SERVER_ARROW_H -#include "color.h" +#include "pb/color.pb.h" class Server_Card; class Server_ArrowTarget; +class ServerInfo_Arrow; class Server_Arrow { private: - int id; - Server_Card *startCard; - Server_ArrowTarget *targetItem; - Color color; + int id; + Server_Card *startCard; + Server_ArrowTarget *targetItem; + color arrowColor; public: - Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const Color &_color) - : id(_id), startCard(_startCard), targetItem(_targetItem), color(_color) { } - int getId() const { return id; } - Server_Card *getStartCard() const { return startCard; } - Server_ArrowTarget *getTargetItem() const { return targetItem; } - const Color &getColor() const { return color; } + Server_Arrow(int _id, Server_Card *_startCard, Server_ArrowTarget *_targetItem, const color &_arrowColor); + int getId() const { return id; } + Server_Card *getStartCard() const { return startCard; } + Server_ArrowTarget *getTargetItem() const { return targetItem; } + const color &getColor() const { return arrowColor; } + + void getInfo(ServerInfo_Arrow *info); }; #endif diff --git a/common/server_arrowtarget.cpp b/common/server_arrowtarget.cpp new file mode 100644 index 000000000..1b3385d58 --- /dev/null +++ b/common/server_arrowtarget.cpp @@ -0,0 +1,2 @@ + +#include "server_arrowtarget.h" diff --git a/common/server_arrowtarget.h b/common/server_arrowtarget.h index 589dfa8a7..d94fc4b71 100644 --- a/common/server_arrowtarget.h +++ b/common/server_arrowtarget.h @@ -4,7 +4,7 @@ #include class Server_ArrowTarget : public QObject { - Q_OBJECT + Q_OBJECT }; -#endif \ No newline at end of file +#endif diff --git a/common/server_card.cpp b/common/server_card.cpp index 6cafdd903..39e4aadcf 100644 --- a/common/server_card.cpp +++ b/common/server_card.cpp @@ -18,106 +18,143 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "server_card.h" +#include "server_cardzone.h" +#include "server_player.h" +#include "pb/serverinfo_card.pb.h" Server_Card::Server_Card(QString _name, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone) - : zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), name(_name), tapped(false), attacking(false), facedown(false), color(QString()), power(-1), toughness(-1), annotation(QString()), destroyOnZoneChange(false), doesntUntap(false), parentCard(0) + : zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), name(_name), tapped(false), attacking(false), facedown(false), color(QString()), power(-1), toughness(-1), annotation(QString()), destroyOnZoneChange(false), doesntUntap(false), parentCard(0) { } Server_Card::~Server_Card() { - // setParentCard(0) leads to the item being removed from our list, so we can't iterate properly - while (!attachedCards.isEmpty()) - attachedCards.first()->setParentCard(0); - - if (parentCard) - parentCard->removeAttachedCard(this); + // setParentCard(0) leads to the item being removed from our list, so we can't iterate properly + while (!attachedCards.isEmpty()) + attachedCards.first()->setParentCard(0); + + if (parentCard) + parentCard->removeAttachedCard(this); } void Server_Card::resetState() { - counters.clear(); - setTapped(false); - setAttacking(false); - power = -1; - toughness = -1; - setAnnotation(QString()); - setDoesntUntap(false); + counters.clear(); + setTapped(false); + setAttacking(false); + power = -1; + toughness = -1; + setAnnotation(QString()); + setDoesntUntap(false); } -QString Server_Card::setAttribute(const QString &aname, const QString &avalue, bool allCards) +QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, bool allCards) { - if (aname == "tapped") { - bool value = avalue == "1"; - if (!(!value && allCards && doesntUntap)) - setTapped(value); - } else if (aname == "attacking") { - setAttacking(avalue == "1"); - } else if (aname == "facedown") { - setFaceDown(avalue == "1"); - } else if (aname == "color") { - setColor(avalue); - } else if (aname == "pt") { - setPT(avalue); - return getPT(); - } else if (aname == "annotation") { - setAnnotation(avalue); - } else if (aname == "doesnt_untap") { - setDoesntUntap(avalue == "1"); - } else - return QString(); - - return avalue; + switch (attribute) { + case AttrTapped: { + bool value = avalue == "1"; + if (!(!value && allCards && doesntUntap)) + setTapped(value); + break; + } + case AttrAttacking: setAttacking(avalue == "1"); break; + case AttrFaceDown: setFaceDown(avalue == "1"); break; + case AttrColor: setColor(avalue); break; + case AttrPT: setPT(avalue); return getPT(); + case AttrAnnotation: setAnnotation(avalue); break; + case AttrDoesntUntap: setDoesntUntap(avalue == "1"); break; + } + return avalue; } void Server_Card::setCounter(int id, int value) { - if (value) - counters.insert(id, value); - else - counters.remove(id); + if (value) + counters.insert(id, value); + else + counters.remove(id); } void Server_Card::setPT(const QString &_pt) { - if (_pt.isEmpty()) { - power = 0; - toughness = -1; - } else { - int sep = _pt.indexOf('/'); - QString p1 = _pt.left(sep); - QString p2 = _pt.mid(sep + 1); - if (p1.isEmpty() || p2.isEmpty()) - return; - if ((p1[0] == '+') || (p2[0] == '+')) { - if (power < 0) - power = 0; - if (toughness < 0) - toughness = 0; - } - if (p1[0] == '+') - power += p1.mid(1).toInt(); - else - power = p1.toInt(); - if (p2[0] == '+') - toughness += p2.mid(1).toInt(); - else - toughness = p2.toInt(); - } + if (_pt.isEmpty()) { + power = 0; + toughness = -1; + } else { + int sep = _pt.indexOf('/'); + QString p1 = _pt.left(sep); + QString p2 = _pt.mid(sep + 1); + if (p1.isEmpty() || p2.isEmpty()) + return; + if ((p1[0] == '+') || (p2[0] == '+')) { + if (power < 0) + power = 0; + if (toughness < 0) + toughness = 0; + } + if (p1[0] == '+') + power += p1.mid(1).toInt(); + else + power = p1.toInt(); + if (p2[0] == '+') + toughness += p2.mid(1).toInt(); + else + toughness = p2.toInt(); + } } QString Server_Card::getPT() const { - if (toughness < 0) - return QString(""); - return QString::number(power) + "/" + QString::number(toughness); + if (toughness < 0) + return QString(""); + return QString::number(power) + "/" + QString::number(toughness); } void Server_Card::setParentCard(Server_Card *_parentCard) { - if (parentCard) - parentCard->removeAttachedCard(this); - parentCard = _parentCard; - if (parentCard) - parentCard->addAttachedCard(this); + if (parentCard) + parentCard->removeAttachedCard(this); + parentCard = _parentCard; + if (parentCard) + parentCard->addAttachedCard(this); +} + +void Server_Card::getInfo(ServerInfo_Card *info) +{ + QString displayedName = facedown ? QString() : name; + + info->set_id(id); + info->set_name(displayedName.toStdString()); + info->set_x(coord_x); + info->set_y(coord_y); + if (facedown) + info->set_face_down(true); + info->set_tapped(tapped); + if (attacking) + info->set_attacking(true); + if (!color.isEmpty()) + info->set_color(color.toStdString()); + const QString ptStr = getPT(); + if (!ptStr.isEmpty()) + info->set_pt(ptStr.toStdString()); + if (!annotation.isEmpty()) + info->set_annotation(annotation.toStdString()); + if (destroyOnZoneChange) + info->set_destroy_on_zone_change(true); + if (doesntUntap) + info->set_doesnt_untap(true); + + QMapIterator cardCounterIterator(counters); + while (cardCounterIterator.hasNext()) { + cardCounterIterator.next(); + ServerInfo_CardCounter *counterInfo = info->add_counter_list(); + counterInfo->set_id(cardCounterIterator.key()); + counterInfo->set_value(cardCounterIterator.value()); + } + + if (parentCard) { + info->set_attach_player_id(parentCard->getZone()->getPlayer()->getPlayerId()); + info->set_attach_zone(parentCard->getZone()->getName().toStdString()); + info->set_attach_card_id(parentCard->getId()); + } } diff --git a/common/server_card.h b/common/server_card.h index a908befaf..e3c76f05a 100644 --- a/common/server_card.h +++ b/common/server_card.h @@ -21,72 +21,76 @@ #define SERVER_CARD_H #include "server_arrowtarget.h" +#include "pb/card_attributes.pb.h" #include #include class Server_CardZone; +class ServerInfo_Card; class Server_Card : public Server_ArrowTarget { - Q_OBJECT + Q_OBJECT private: - Server_CardZone *zone; - int id; - int coord_x, coord_y; - QString name; - QMap counters; - bool tapped; - bool attacking; - bool facedown; - QString color; - int power, toughness; - QString annotation; - bool destroyOnZoneChange; - bool doesntUntap; - - Server_Card *parentCard; - QList attachedCards; + Server_CardZone *zone; + int id; + int coord_x, coord_y; + QString name; + QMap counters; + bool tapped; + bool attacking; + bool facedown; + QString color; + int power, toughness; + QString annotation; + bool destroyOnZoneChange; + bool doesntUntap; + + Server_Card *parentCard; + QList attachedCards; public: - Server_Card(QString _name, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone = 0); - ~Server_Card(); - - Server_CardZone *getZone() const { return zone; } - void setZone(Server_CardZone *_zone) { zone = _zone; } - - int getId() const { return id; } - int getX() const { return coord_x; } - int getY() const { return coord_y; } - QString getName() const { return name; } - const QMap &getCounters() const { return counters; } - int getCounter(int id) const { return counters.value(id, 0); } - bool getTapped() const { return tapped; } - bool getAttacking() const { return attacking; } - bool getFaceDown() const { return facedown; } - QString getColor() const { return color; } - QString getPT() const; - QString getAnnotation() const { return annotation; } - bool getDoesntUntap() const { return doesntUntap; } - bool getDestroyOnZoneChange() const { return destroyOnZoneChange; } - Server_Card *getParentCard() const { return parentCard; } - const QList &getAttachedCards() const { return attachedCards; } + Server_Card(QString _name, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone = 0); + ~Server_Card(); + + Server_CardZone *getZone() const { return zone; } + void setZone(Server_CardZone *_zone) { zone = _zone; } + + int getId() const { return id; } + int getX() const { return coord_x; } + int getY() const { return coord_y; } + QString getName() const { return name; } + const QMap &getCounters() const { return counters; } + int getCounter(int id) const { return counters.value(id, 0); } + bool getTapped() const { return tapped; } + bool getAttacking() const { return attacking; } + bool getFaceDown() const { return facedown; } + QString getColor() const { return color; } + QString getPT() const; + QString getAnnotation() const { return annotation; } + bool getDoesntUntap() const { return doesntUntap; } + bool getDestroyOnZoneChange() const { return destroyOnZoneChange; } + Server_Card *getParentCard() const { return parentCard; } + const QList &getAttachedCards() const { return attachedCards; } - void setId(int _id) { id = _id; } - void setCoords(int x, int y) { coord_x = x; coord_y = y; } - void setName(const QString &_name) { name = _name; } - void setCounter(int id, int value); - void setTapped(bool _tapped) { tapped = _tapped; } - void setAttacking(bool _attacking) { attacking = _attacking; } - void setFaceDown(bool _facedown) { facedown = _facedown; } - void setColor(const QString &_color) { color = _color; } - void setPT(const QString &_pt); - void setAnnotation(const QString &_annotation) { annotation = _annotation; } - void setDestroyOnZoneChange(bool _destroy) { destroyOnZoneChange = _destroy; } - void setDoesntUntap(bool _doesntUntap) { doesntUntap = _doesntUntap; } - void setParentCard(Server_Card *_parentCard); - void addAttachedCard(Server_Card *card) { attachedCards.append(card); } - void removeAttachedCard(Server_Card *card) { attachedCards.removeAt(attachedCards.indexOf(card)); } - - void resetState(); - QString setAttribute(const QString &aname, const QString &avalue, bool allCards); + void setId(int _id) { id = _id; } + void setCoords(int x, int y) { coord_x = x; coord_y = y; } + void setName(const QString &_name) { name = _name; } + void setCounter(int id, int value); + void setTapped(bool _tapped) { tapped = _tapped; } + void setAttacking(bool _attacking) { attacking = _attacking; } + void setFaceDown(bool _facedown) { facedown = _facedown; } + void setColor(const QString &_color) { color = _color; } + void setPT(const QString &_pt); + void setAnnotation(const QString &_annotation) { annotation = _annotation; } + void setDestroyOnZoneChange(bool _destroy) { destroyOnZoneChange = _destroy; } + void setDoesntUntap(bool _doesntUntap) { doesntUntap = _doesntUntap; } + void setParentCard(Server_Card *_parentCard); + void addAttachedCard(Server_Card *card) { attachedCards.append(card); } + void removeAttachedCard(Server_Card *card) { attachedCards.removeAt(attachedCards.indexOf(card)); } + + void resetState(); + QString setAttribute(CardAttribute attribute, const QString &avalue, bool allCards); + + void getInfo(ServerInfo_Card *info); }; #endif diff --git a/common/server_cardzone.cpp b/common/server_cardzone.cpp index dd1db3eee..c6456f5fd 100644 --- a/common/server_cardzone.cpp +++ b/common/server_cardzone.cpp @@ -23,205 +23,271 @@ #include "rng_abstract.h" #include #include -#include "server_game.h" +#include "pb/command_move_card.pb.h" -Server_CardZone::Server_CardZone(Server_Player *_player, const QString &_name, bool _has_coords, ZoneType _type) - : player(_player), name(_name), has_coords(_has_coords), type(_type), cardsBeingLookedAt(0) +Server_CardZone::Server_CardZone(Server_Player *_player, const QString &_name, bool _has_coords, ServerInfo_Zone::ZoneType _type) + : player(_player), + name(_name), + has_coords(_has_coords), + type(_type), + cardsBeingLookedAt(0), + alwaysRevealTopCard(false) { } Server_CardZone::~Server_CardZone() { - qDebug() << "Server_CardZone destructor:" << name; - clear(); + qDebug() << "Server_CardZone destructor:" << name; + clear(); } void Server_CardZone::shuffle() { - QMutexLocker locker(&player->getGame()->gameMutex); - - QList temp; - for (int i = cards.size(); i; i--) - temp.append(cards.takeAt(rng->getNumber(0, i - 1))); - cards = temp; + // Size 0 or 1 decks are sorted + if (cards.size() < 2) return; + for (int i = cards.size() - 1; i > 0; i--){ + int j = rng->rand(0, i); + cards.swap(j,i); + } + playersWithWritePermission.clear(); +} + + +void Server_CardZone::removeCardFromCoordMap(Server_Card *card, int oldX, int oldY) +{ + if (oldX < 0) + return; + + const int baseX = (oldX / 3) * 3; + QMap &coordMap = coordinateMap[oldY]; + + if (coordMap.contains(baseX) && coordMap.contains(baseX + 1) && coordMap.contains(baseX + 2)) + // If the removal of this card has opened up a previously full pile... + freePilesMap[oldY].insert(coordMap.value(baseX)->getName(), baseX); + + coordMap.remove(oldX); + + if (!(coordMap.contains(baseX) && coordMap.value(baseX)->getName() == card->getName()) && !(coordMap.contains(baseX + 1) && coordMap.value(baseX + 1)->getName() == card->getName()) && !(coordMap.contains(baseX + 2) && coordMap.value(baseX + 2)->getName() == card->getName())) + // If this card was the last one with this name... + freePilesMap[oldY].remove(card->getName(), baseX); + + if (!coordMap.contains(baseX) && !coordMap.contains(baseX + 1) && !coordMap.contains(baseX + 2)) { + // If the removal of this card has freed a whole pile, i.e. it was the last card in it... + if (baseX < freeSpaceMap[oldY]) + freeSpaceMap[oldY] = baseX; + } +} + +void Server_CardZone::insertCardIntoCoordMap(Server_Card *card, int x, int y) +{ + if (x < 0) + return; + + coordinateMap[y].insert(x, card); + if (!(x % 3)) { + if (!freePilesMap[y].contains(card->getName(), x) && card->getAttachedCards().isEmpty()) + freePilesMap[y].insert(card->getName(), x); + if (freeSpaceMap[y] == x) { + int nextFreeX = x; + do { + nextFreeX += 3; + } while (coordinateMap[y].contains(nextFreeX) || coordinateMap[y].contains(nextFreeX + 1) || coordinateMap[y].contains(nextFreeX + 2)); + freeSpaceMap[y] = nextFreeX; + } + } else if (!((x - 2) % 3)) { + const int baseX = (x / 3) * 3; + freePilesMap[y].remove(coordinateMap[y].value(baseX)->getName(), baseX); + } } int Server_CardZone::removeCard(Server_Card *card) { - QMutexLocker locker(&player->getGame()->gameMutex); - - int index = cards.indexOf(card); - cards.removeAt(index); - card->setZone(0); - - return index; + int index = cards.indexOf(card); + cards.removeAt(index); + if (has_coords) + removeCardFromCoordMap(card, card->getX(), card->getY()); + card->setZone(0); + + return index; } -Server_Card *Server_CardZone::getCard(int id, int *position) +Server_Card *Server_CardZone::getCard(int id, int *position, bool remove) { - QMutexLocker locker(&player->getGame()->gameMutex); - - if (type != HiddenZone) { - QListIterator CardIterator(cards); - int i = 0; - while (CardIterator.hasNext()) { - Server_Card *tmp = CardIterator.next(); - if (tmp->getId() == id) { - if (position) - *position = i; - return tmp; - } - i++; - } - return NULL; - } else { - if ((id >= cards.size()) || (id < 0)) - return NULL; - Server_Card *tmp = cards[id]; - if (position) - *position = id; - return tmp; - } + if (type != ServerInfo_Zone::HiddenZone) { + for (int i = 0; i < cards.size(); ++i) { + Server_Card *tmp = cards[i]; + if (tmp->getId() == id) { + if (position) + *position = i; + if (remove) { + cards.removeAt(i); + tmp->setZone(0); + } + return tmp; + } + } + return NULL; + } else { + if ((id >= cards.size()) || (id < 0)) + return NULL; + Server_Card *tmp = cards[id]; + if (position) + *position = id; + if (remove) { + cards.removeAt(id); + tmp->setZone(0); + } + return tmp; + } } int Server_CardZone::getFreeGridColumn(int x, int y, const QString &cardName) const { - QMutexLocker locker(&player->getGame()->gameMutex); - - QMap coordMap; - for (int i = 0; i < cards.size(); ++i) - if (cards[i]->getY() == y) - coordMap.insert(cards[i]->getX(), cards[i]); - - int resultX = 0; - if (x == -1) { - for (int i = 0; i < cards.size(); ++i) - if ((cards[i]->getName() == cardName) && !(cards[i]->getX() % 3) && (cards[i]->getY() == y)) { - if (!cards[i]->getAttachedCards().isEmpty()) - continue; - if (!coordMap.value(cards[i]->getX() + 1)) - return cards[i]->getX() + 1; - if (!coordMap.value(cards[i]->getX() + 2)) - return cards[i]->getX() + 2; - } - } else if (x == -2) { - } else { - x = (x / 3) * 3; - if (!coordMap.contains(x)) - resultX = x; - else if (!coordMap.value(x)->getAttachedCards().isEmpty()) { - resultX = x; - x = -1; - } else if (!coordMap.contains(x + 1)) - resultX = x + 1; - else if (!coordMap.contains(x + 2)) - resultX = x + 2; - else { - resultX = x; - x = -1; - } - } - - if (x < 0) - while (coordMap.value(resultX)) - resultX += 3; + const QMap &coordMap = coordinateMap.value(y); + if (x == -1) { + if (freePilesMap[y].contains(cardName)) { + x = (freePilesMap[y].value(cardName) / 3) * 3; + if (!coordMap.contains(x)) + return x; + else if (!coordMap.contains(x + 1)) + return x + 1; + else + return x + 2; + } + } else if (x >= 0) { + int resultX = 0; + x = (x / 3) * 3; + if (!coordMap.contains(x)) + resultX = x; + else if (!coordMap.value(x)->getAttachedCards().isEmpty()) { + resultX = x; + x = -1; + } else if (!coordMap.contains(x + 1)) + resultX = x + 1; + else if (!coordMap.contains(x + 2)) + resultX = x + 2; + else { + resultX = x; + x = -1; + } + if (x < 0) + while (coordMap.contains(resultX)) + resultX += 3; - return resultX; + return resultX; + } + + return freeSpaceMap[y]; } bool Server_CardZone::isColumnStacked(int x, int y) const { - if (!has_coords) - return false; - - QMutexLocker locker(&player->getGame()->gameMutex); - - QMap coordMap; - for (int i = 0; i < cards.size(); ++i) - if (cards[i]->getY() == y) - coordMap.insert(cards[i]->getX(), cards[i]); - - return coordMap.contains((x / 3) * 3 + 1); + if (!has_coords) + return false; + + return coordinateMap[y].contains((x / 3) * 3 + 1); } bool Server_CardZone::isColumnEmpty(int x, int y) const { - if (!has_coords) - return true; - - QMutexLocker locker(&player->getGame()->gameMutex); - - QMap coordMap; - for (int i = 0; i < cards.size(); ++i) - if (cards[i]->getY() == y) - coordMap.insert(cards[i]->getX(), cards[i]); - - return !coordMap.contains((x / 3) * 3); + if (!has_coords) + return true; + + return !coordinateMap[y].contains((x / 3) * 3); } -void Server_CardZone::moveCard(CommandContainer *cont, QMap &coordMap, Server_Card *card, int x, int y) +void Server_CardZone::moveCardInRow(GameEventStorage &ges, Server_Card *card, int x, int y) { - QMutexLocker locker(&player->getGame()->gameMutex); - - coordMap.remove(card->getY() * 10000 + card->getX()); - - CardToMove *cardToMove = new CardToMove(card->getId()); - player->moveCard(cont, this, QList() << cardToMove, this, x, y, card->getFaceDown(), false); - delete cardToMove; - - coordMap.insert(y * 10000 + x, card); + CardToMove *cardToMove = new CardToMove; + cardToMove->set_card_id(card->getId()); + player->moveCard(ges, this, QList() << cardToMove, this, x, y, false, false); + delete cardToMove; } -void Server_CardZone::fixFreeSpaces(CommandContainer *cont) +void Server_CardZone::fixFreeSpaces(GameEventStorage &ges) { - QMutexLocker locker(&player->getGame()->gameMutex); - - QMap coordMap; - QSet placesToLook; - for (int i = 0; i < cards.size(); ++i) { - coordMap.insert(cards[i]->getY() * 10000 + cards[i]->getX(), cards[i]); - placesToLook.insert(cards[i]->getY() * 10000 + (cards[i]->getX() / 3) * 3); - } - - QSetIterator placeIterator(placesToLook); - while (placeIterator.hasNext()) { - int foo = placeIterator.next(); - int y = foo / 10000; - int baseX = foo - y * 10000; - - if (!coordMap.contains(y * 10000 + baseX)) { - if (coordMap.contains(y * 10000 + baseX + 1)) - moveCard(cont, coordMap, coordMap.value(y * 10000 + baseX + 1), baseX, y); - else if (coordMap.contains(y * 10000 + baseX + 2)) { - moveCard(cont, coordMap, coordMap.value(y * 10000 + baseX + 2), baseX, y); - continue; - } else - continue; - } - if (!coordMap.contains(y * 10000 + baseX + 1) && coordMap.contains(y * 10000 + baseX + 2)) - moveCard(cont, coordMap, coordMap.value(y * 10000 + baseX + 2), baseX + 1, y); - } + if (!has_coords) + return; + + QSet > placesToLook; + for (int i = 0; i < cards.size(); ++i) + placesToLook.insert(QPair((cards[i]->getX() / 3) * 3, cards[i]->getY())); + + QSetIterator > placeIterator(placesToLook); + while (placeIterator.hasNext()) { + const QPair &foo = placeIterator.next(); + int baseX = foo.first; + int y = foo.second; + + if (!coordinateMap[y].contains(baseX)) { + if (coordinateMap[y].contains(baseX + 1)) + moveCardInRow(ges, coordinateMap[y].value(baseX + 1), baseX, y); + else if (coordinateMap[y].contains(baseX + 2)) { + moveCardInRow(ges, coordinateMap[y].value(baseX + 2), baseX, y); + continue; + } else + continue; + } + if (!coordinateMap[y].contains(baseX + 1) && coordinateMap[y].contains(baseX + 2)) + moveCardInRow(ges, coordinateMap[y].value(baseX + 2), baseX + 1, y); + } +} + +void Server_CardZone::updateCardCoordinates(Server_Card *card, int oldX, int oldY) +{ + if (!has_coords) + return; + + if (oldX != -1) + removeCardFromCoordMap(card, oldX, oldY); + insertCardIntoCoordMap(card, card->getX(), card->getY()); } void Server_CardZone::insertCard(Server_Card *card, int x, int y) { - QMutexLocker locker(&player->getGame()->gameMutex); - - if (hasCoords()) { - card->setCoords(x, y); - cards.append(card); - } else { - card->setCoords(0, 0); - cards.insert(x, card); - } - card->setZone(this); + if (hasCoords()) { + card->setCoords(x, y); + cards.append(card); + insertCardIntoCoordMap(card, x, y); + } else { + card->setCoords(0, 0); + if (x == -1) + cards.append(card); + else + cards.insert(x, card); + } + card->setZone(this); } void Server_CardZone::clear() { - QMutexLocker locker(&player->getGame()->gameMutex); - - for (int i = 0; i < cards.size(); i++) - delete cards.at(i); - cards.clear(); + for (int i = 0; i < cards.size(); i++) + delete cards.at(i); + cards.clear(); + coordinateMap.clear(); + freePilesMap.clear(); + freeSpaceMap.clear(); + playersWithWritePermission.clear(); +} + +void Server_CardZone::addWritePermission(int playerId) +{ + playersWithWritePermission.insert(playerId); +} + +void Server_CardZone::getInfo(ServerInfo_Zone *info, Server_Player *playerWhosAsking, bool omniscient) +{ + info->set_name(name.toStdString()); + info->set_type(type); + info->set_with_coords(has_coords); + info->set_card_count(cards.size()); + info->set_always_reveal_top_card(alwaysRevealTopCard); + if ( + (((playerWhosAsking == player) || omniscient) && (type != ServerInfo_Zone::HiddenZone)) + || ((playerWhosAsking != player) && (type == ServerInfo_Zone::PublicZone)) + ) { + QListIterator cardIterator(cards); + while (cardIterator.hasNext()) + cardIterator.next()->getInfo(info->add_card_list()); + } } diff --git a/common/server_cardzone.h b/common/server_cardzone.h index 898e89c78..a00b367a8 100644 --- a/common/server_cardzone.h +++ b/common/server_cardzone.h @@ -22,43 +22,59 @@ #include #include -#include "protocol_datastructures.h" +#include +#include +#include "pb/serverinfo_zone.pb.h" class Server_Card; class Server_Player; class Server_Game; -class CommandContainer; +class GameEventStorage; class Server_CardZone { private: - Server_Player *player; - QString name; - bool has_coords; - ZoneType type; - int cardsBeingLookedAt; + Server_Player *player; + QString name; + bool has_coords; + ServerInfo_Zone::ZoneType type; + int cardsBeingLookedAt; + QSet playersWithWritePermission; + bool alwaysRevealTopCard; + QList cards; + QMap > coordinateMap; // y -> (x -> card) + QMap > freePilesMap; // y -> (cardName -> x) + QMap freeSpaceMap; // y -> x + void removeCardFromCoordMap(Server_Card *card, int oldX, int oldY); + void insertCardIntoCoordMap(Server_Card *card, int x, int y); public: - Server_CardZone(Server_Player *_player, const QString &_name, bool _has_coords, ZoneType _type); - ~Server_CardZone(); + Server_CardZone(Server_Player *_player, const QString &_name, bool _has_coords, ServerInfo_Zone::ZoneType _type); + ~Server_CardZone(); + + const QList &getCards() const { return cards; } + int removeCard(Server_Card *card); + Server_Card *getCard(int id, int *position = NULL, bool remove = false); - int removeCard(Server_Card *card); - Server_Card *getCard(int id, int *position = NULL); - - int getCardsBeingLookedAt() const { return cardsBeingLookedAt; } - void setCardsBeingLookedAt(int _cardsBeingLookedAt) { cardsBeingLookedAt = _cardsBeingLookedAt; } - bool hasCoords() const { return has_coords; } - ZoneType getType() const { return type; } - QString getName() const { return name; } - Server_Player *getPlayer() const { return player; } - - int getFreeGridColumn(int x, int y, const QString &cardName) const; - bool isColumnEmpty(int x, int y) const; - bool isColumnStacked(int x, int y) const; - void fixFreeSpaces(CommandContainer *cont); - void moveCard(CommandContainer *cont, QMap &coordMap, Server_Card *card, int x, int y); - QList cards; - void insertCard(Server_Card *card, int x, int y); - void shuffle(); - void clear(); + int getCardsBeingLookedAt() const { return cardsBeingLookedAt; } + void setCardsBeingLookedAt(int _cardsBeingLookedAt) { cardsBeingLookedAt = _cardsBeingLookedAt; } + bool hasCoords() const { return has_coords; } + ServerInfo_Zone::ZoneType getType() const { return type; } + QString getName() const { return name; } + Server_Player *getPlayer() const { return player; } + void getInfo(ServerInfo_Zone *info, Server_Player *playerWhosAsking, bool omniscient); + + int getFreeGridColumn(int x, int y, const QString &cardName) const; + bool isColumnEmpty(int x, int y) const; + bool isColumnStacked(int x, int y) const; + void fixFreeSpaces(GameEventStorage &ges); + void moveCardInRow(GameEventStorage &ges, Server_Card *card, int x, int y); + void insertCard(Server_Card *card, int x, int y); + void updateCardCoordinates(Server_Card *card, int oldX, int oldY); + void shuffle(); + void clear(); + void addWritePermission(int playerId); + const QSet &getPlayersWithWritePermission() const { return playersWithWritePermission; } + bool getAlwaysRevealTopCard() const { return alwaysRevealTopCard; } + void setAlwaysRevealTopCard(bool _alwaysRevealTopCard) { alwaysRevealTopCard = _alwaysRevealTopCard; } }; #endif diff --git a/common/server_counter.cpp b/common/server_counter.cpp new file mode 100644 index 000000000..df8dcd03c --- /dev/null +++ b/common/server_counter.cpp @@ -0,0 +1,20 @@ +#include "server_counter.h" +#include "pb/serverinfo_counter.pb.h" + +Server_Counter::Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count) + : id(_id), + name(_name), + counterColor(_counterColor), + radius(_radius), + count(_count) +{ +} + +void Server_Counter::getInfo(ServerInfo_Counter *info) +{ + info->set_id(id); + info->set_name(name.toStdString()); + info->mutable_counter_color()->CopyFrom(counterColor); + info->set_radius(radius); + info->set_count(count); +} diff --git a/common/server_counter.h b/common/server_counter.h index 20a438a67..60b70e699 100644 --- a/common/server_counter.h +++ b/common/server_counter.h @@ -21,24 +21,28 @@ #define SERVER_COUNTER_H #include -#include "color.h" +#include "pb/color.pb.h" + +class ServerInfo_Counter; class Server_Counter { protected: - int id; - QString name; - Color color; - int radius; - int count; + int id; + QString name; + color counterColor; + int radius; + int count; public: - Server_Counter(int _id, const QString &_name, const Color &_color, int _radius, int _count = 0) : id(_id), name(_name), color(_color), radius(_radius), count(_count) { } - ~Server_Counter() { } - int getId() const { return id; } - QString getName() const { return name; } - Color getColor() const { return color; } - int getRadius() const { return radius; } - int getCount() const { return count; } - void setCount(int _count) { count = _count; } + Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count = 0); + ~Server_Counter() { } + int getId() const { return id; } + QString getName() const { return name; } + const color &getColor() const { return counterColor; } + int getRadius() const { return radius; } + int getCount() const { return count; } + void setCount(int _count) { count = _count; } + + void getInfo(ServerInfo_Counter *info); }; #endif diff --git a/common/server_database_interface.cpp b/common/server_database_interface.cpp new file mode 100644 index 000000000..a375f6031 --- /dev/null +++ b/common/server_database_interface.cpp @@ -0,0 +1,2 @@ + +#include "server_database_interface.h" diff --git a/common/server_database_interface.h b/common/server_database_interface.h new file mode 100644 index 000000000..798ae9d92 --- /dev/null +++ b/common/server_database_interface.h @@ -0,0 +1,37 @@ +#ifndef SERVER_DATABASE_INTERFACE_H +#define SERVER_DATABASE_INTERFACE_H + +#include + +#include "server.h" + +class Server_DatabaseInterface : public QObject { + Q_OBJECT +public: + Server_DatabaseInterface(QObject *parent = 0) + : QObject(parent) { } + + virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft) = 0; + virtual bool userExists(const QString & /* user */) { return false; } + virtual QMap getBuddyList(const QString & /* name */) { return QMap(); } + virtual QMap getIgnoreList(const QString & /* name */) { return QMap(); } + virtual bool isInBuddyList(const QString & /* whoseList */, const QString & /* who */) { return false; } + virtual bool isInIgnoreList(const QString & /* whoseList */, const QString & /* who */) { return false; } + virtual ServerInfo_User getUserData(const QString &name, bool withId = false) = 0; + virtual void storeGameInformation(const QString & /* roomName */, const QStringList & /* roomGameTypes */, const ServerInfo_Game & /* gameInfo */, const QSet & /* allPlayersEver */, const QSet & /* allSpectatorsEver */, const QList & /* replayList */) { } + virtual DeckList *getDeckFromDatabase(int /* deckId */, int /* userId */) { return 0; } + + virtual qint64 startSession(const QString & /* userName */, const QString & /* address */) { return 0; } +public slots: + virtual void endSession(qint64 /* sessionId */ ) { } +public: + virtual int getNextGameId() = 0; + virtual int getNextReplayId() = 0; + + virtual void clearSessionTables() { } + virtual void lockSessionTables() { } + virtual void unlockSessionTables() { } + virtual bool userSessionExists(const QString & /* userName */) { return false; } +}; + +#endif diff --git a/common/server_game.cpp b/common/server_game.cpp index 734db1cb2..647aa22e3 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -20,532 +20,722 @@ #include "server.h" #include "server_room.h" #include "server_game.h" +#include "server_player.h" #include "server_protocolhandler.h" #include "server_arrow.h" #include "server_card.h" #include "server_cardzone.h" -#include "server_counter.h" +#include "server_database_interface.h" +#include "decklist.h" +#include "pb/context_connection_state_changed.pb.h" +#include "pb/context_ping_changed.pb.h" +#include "pb/event_player_properties_changed.pb.h" +#include "pb/event_game_closed.pb.h" +#include "pb/event_game_joined.pb.h" +#include "pb/event_game_host_changed.pb.h" +#include "pb/event_game_state_changed.pb.h" +#include "pb/event_kicked.pb.h" +#include "pb/event_join.pb.h" +#include "pb/event_leave.pb.h" +#include "pb/event_delete_arrow.pb.h" +#include "pb/event_set_active_player.pb.h" +#include "pb/event_set_active_phase.pb.h" +#include "pb/serverinfo_playerping.pb.h" +#include "pb/game_replay.pb.h" +#include "pb/event_replay_added.pb.h" +#include #include #include -Server_Game::Server_Game(Server_ProtocolHandler *_creator, int _gameId, const QString &_description, const QString &_password, int _maxPlayers, const QList &_gameTypes, bool _onlyBuddies, bool _onlyRegistered, bool _spectatorsAllowed, bool _spectatorsNeedPassword, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, Server_Room *_room) - : QObject(), room(_room), hostId(0), creatorInfo(new ServerInfo_User(_creator->getUserInfo())), gameStarted(false), gameId(_gameId), description(_description), password(_password), maxPlayers(_maxPlayers), gameTypes(_gameTypes), activePlayer(-1), activePhase(-1), onlyBuddies(_onlyBuddies), onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed), spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk), spectatorsSeeEverything(_spectatorsSeeEverything), inactivityCounter(0), secondsElapsed(0), gameMutex(QMutex::Recursive) +Server_Game::Server_Game(const ServerInfo_User &_creatorInfo, int _gameId, const QString &_description, const QString &_password, int _maxPlayers, const QList &_gameTypes, bool _onlyBuddies, bool _onlyRegistered, bool _spectatorsAllowed, bool _spectatorsNeedPassword, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, Server_Room *_room) + : QObject(), + room(_room), + nextPlayerId(0), + hostId(0), + creatorInfo(new ServerInfo_User(_creatorInfo)), + gameStarted(false), + gameClosed(false), + gameId(_gameId), + description(_description), + password(_password), + maxPlayers(_maxPlayers), + gameTypes(_gameTypes), + activePlayer(-1), + activePhase(-1), + onlyBuddies(_onlyBuddies), + onlyRegistered(_onlyRegistered), + spectatorsAllowed(_spectatorsAllowed), + spectatorsNeedPassword(_spectatorsNeedPassword), + spectatorsCanTalk(_spectatorsCanTalk), + spectatorsSeeEverything(_spectatorsSeeEverything), + inactivityCounter(0), + startTimeOfThisGame(0), + secondsElapsed(0), + firstGameStarted(false), + startTime(QDateTime::currentDateTime()), + gameMutex(QMutex::Recursive) { - connect(this, SIGNAL(sigStartGameIfReady()), this, SLOT(doStartGameIfReady()), Qt::QueuedConnection); - - addPlayer(_creator, false, false); + currentReplay = new GameReplay; + currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId()); + + connect(this, SIGNAL(sigStartGameIfReady()), this, SLOT(doStartGameIfReady()), Qt::QueuedConnection); + + getInfo(*currentReplay->mutable_game_info()); - if (room->getServer()->getGameShouldPing()) { - pingClock = new QTimer(this); - connect(pingClock, SIGNAL(timeout()), this, SLOT(pingClockTimeout())); - pingClock->start(1000); - } + if (room->getServer()->getGameShouldPing()) { + pingClock = new QTimer(this); + connect(pingClock, SIGNAL(timeout()), this, SLOT(pingClockTimeout())); + pingClock->start(1000); + } } Server_Game::~Server_Game() { - QMutexLocker roomLocker(&room->roomMutex); - QMutexLocker locker(&gameMutex); - - sendGameEvent(new Event_GameClosed); - - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) - playerIterator.next().value()->prepareDestroy(); - players.clear(); - - room->removeGame(this); - delete creatorInfo; - qDebug() << "Server_Game destructor: gameId=" << gameId; + room->gamesLock.lockForWrite(); + gameMutex.lock(); + + gameClosed = true; + sendGameEventContainer(prepareGameEvent(Event_GameClosed(), -1)); + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + playerIterator.next().value()->prepareDestroy(); + players.clear(); + + room->removeGame(this); + delete creatorInfo; + creatorInfo = 0; + + gameMutex.unlock(); + room->gamesLock.unlock(); + + currentReplay->set_duration_seconds(secondsElapsed - startTimeOfThisGame); + replayList.append(currentReplay); + storeGameInformation(); + + for (int i = 0; i < replayList.size(); ++i) + delete replayList[i]; + + qDebug() << "Server_Game destructor: gameId=" << gameId; +} + +void Server_Game::storeGameInformation() +{ + const ServerInfo_Game &gameInfo = replayList.first()->game_info(); + + Event_ReplayAdded replayEvent; + ServerInfo_ReplayMatch *replayMatchInfo = replayEvent.mutable_match_info(); + replayMatchInfo->set_game_id(gameInfo.game_id()); + replayMatchInfo->set_room_name(room->getName().toStdString()); + replayMatchInfo->set_time_started(QDateTime::currentDateTime().addSecs(-secondsElapsed).toTime_t()); + replayMatchInfo->set_length(secondsElapsed); + replayMatchInfo->set_game_name(gameInfo.description()); + + const QStringList &allGameTypes = room->getGameTypes(); + QStringList gameTypes; + for (int i = gameInfo.game_types_size() - 1; i >= 0; --i) + gameTypes.append(allGameTypes[gameInfo.game_types(i)]); + + QSetIterator playerIterator(allPlayersEver); + while (playerIterator.hasNext()) + replayMatchInfo->add_player_names(playerIterator.next().toStdString()); + + for (int i = 0; i < replayList.size(); ++i) { + ServerInfo_Replay *replayInfo = replayMatchInfo->add_replay_list(); + replayInfo->set_replay_id(replayList[i]->replay_id()); + replayInfo->set_replay_name(gameInfo.description()); + replayInfo->set_duration(replayList[i]->duration_seconds()); + } + + QSet allUsersInGame = allPlayersEver + allSpectatorsEver; + QSetIterator allUsersIterator(allUsersInGame); + + SessionEvent *sessionEvent = Server_ProtocolHandler::prepareSessionEvent(replayEvent); + Server *server = room->getServer(); + server->clientsLock.lockForRead(); + while (allUsersIterator.hasNext()) { + Server_AbstractUserInterface *userHandler = server->findUser(allUsersIterator.next()); + if (userHandler) + userHandler->sendProtocolItem(*sessionEvent); + } + server->clientsLock.unlock(); + delete sessionEvent; + + server->getDatabaseInterface()->storeGameInformation(room->getName(), gameTypes, gameInfo, allPlayersEver, allSpectatorsEver, replayList); } void Server_Game::pingClockTimeout() { - QMutexLocker locker(&gameMutex); - ++secondsElapsed; - - QList pingList; - QMapIterator playerIterator(players); - bool allPlayersInactive = true; - int playerCount = 0; - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); - if (!player->getSpectator()) - ++playerCount; - int pingTime; - if (player->getProtocolHandler()) { - pingTime = player->getProtocolHandler()->getLastCommandTime(); - if (!player->getSpectator()) - allPlayersInactive = false; - } else - pingTime = -1; - pingList.append(new ServerInfo_PlayerPing(player->getPlayerId(), pingTime)); - } - sendGameEvent(new Event_Ping(secondsElapsed, pingList)); - - const int maxTime = room->getServer()->getMaxGameInactivityTime(); - if (allPlayersInactive) { - if (((++inactivityCounter >= maxTime) && (maxTime > 0)) || (playerCount < maxPlayers)) - deleteLater(); - } else - inactivityCounter = 0; + QMutexLocker locker(&gameMutex); + ++secondsElapsed; + + GameEventStorage ges; + ges.setGameEventContext(Context_PingChanged()); + + QList pingList; + QMapIterator playerIterator(players); + bool allPlayersInactive = true; + int playerCount = 0; + while (playerIterator.hasNext()) { + Server_Player *player = playerIterator.next().value(); + if (!player->getSpectator()) + ++playerCount; + + const int oldPingTime = player->getPingTime(); + player->playerMutex.lock(); + int newPingTime; + if (player->getUserInterface()) + newPingTime = player->getUserInterface()->getLastCommandTime(); + else + newPingTime = -1; + player->playerMutex.unlock(); + + if ((newPingTime != -1) && !player->getSpectator()) + allPlayersInactive = false; + + if ((abs(oldPingTime - newPingTime) > 1) || ((newPingTime == -1) && (oldPingTime != -1)) || ((newPingTime != -1) && (oldPingTime == -1))) { + player->setPingTime(newPingTime); + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_ping_seconds(newPingTime); + ges.enqueueGameEvent(event, player->getPlayerId()); + } + } + ges.sendToGame(this); + + const int maxTime = room->getServer()->getMaxGameInactivityTime(); + if (allPlayersInactive) { + if (((++inactivityCounter >= maxTime) && (maxTime > 0)) || (playerCount < maxPlayers)) + deleteLater(); + } else + inactivityCounter = 0; } int Server_Game::getPlayerCount() const { - QMutexLocker locker(&gameMutex); - - QMapIterator playerIterator(players); - int result = 0; - while (playerIterator.hasNext()) - if (!playerIterator.next().value()->getSpectator()) - ++result; - return result; + QMutexLocker locker(&gameMutex); + + QMapIterator playerIterator(players); + int result = 0; + while (playerIterator.hasNext()) + if (!playerIterator.next().value()->getSpectator()) + ++result; + return result; } int Server_Game::getSpectatorCount() const { - QMutexLocker locker(&gameMutex); - - QMapIterator playerIterator(players); - int result = 0; - while (playerIterator.hasNext()) - if (playerIterator.next().value()->getSpectator()) - ++result; - return result; + QMutexLocker locker(&gameMutex); + + QMapIterator playerIterator(players); + int result = 0; + while (playerIterator.hasNext()) + if (playerIterator.next().value()->getSpectator()) + ++result; + return result; +} + +void Server_Game::createGameStateChangedEvent(Event_GameStateChanged *event, Server_Player *playerWhosAsking, bool omniscient, bool withUserInfo) +{ + event->set_seconds_elapsed(secondsElapsed); + if (gameStarted) { + event->set_game_started(true); + event->set_active_player_id(0); + event->set_active_phase(0); + } else + event->set_game_started(false); + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + playerIterator.next().value()->getInfo(event->add_player_list(), playerWhosAsking, omniscient, withUserInfo); +} + +void Server_Game::sendGameStateToPlayers() +{ + // game state information for replay and omniscient spectators + Event_GameStateChanged omniscientEvent; + createGameStateChangedEvent(&omniscientEvent, 0, true, false); + + GameEventContainer *replayCont = prepareGameEvent(omniscientEvent, -1); + replayCont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame); + replayCont->clear_game_id(); + currentReplay->add_event_list()->CopyFrom(*replayCont); + delete replayCont; + + // If spectators are not omniscient, we need an additional createGameStateChangedEvent call, otherwise we can use the data we used for the replay. + // All spectators are equal, so we don't need to make a createGameStateChangedEvent call for each one. + Event_GameStateChanged spectatorEvent; + if (spectatorsSeeEverything) + spectatorEvent = omniscientEvent; + else + createGameStateChangedEvent(&spectatorEvent, 0, false, false); + + // send game state info to clients according to their role in the game + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Server_Player *player = playerIterator.next().value(); + GameEventContainer *gec; + if (player->getSpectator()) + gec = prepareGameEvent(spectatorEvent, -1); + else { + Event_GameStateChanged event; + createGameStateChangedEvent(&event, player, false, false); + + gec = prepareGameEvent(event, -1); + } + player->sendGameEvent(*gec); + delete gec; + } } void Server_Game::doStartGameIfReady() { - QMutexLocker locker(&gameMutex); - - if (getPlayerCount() < maxPlayers) - return; - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - if (!p->getReadyStart() && !p->getSpectator()) - return; - } - playerIterator.toFront(); - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - if (!p->getSpectator()) - p->setupZones(); - } + Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface(); + QMutexLocker locker(&gameMutex); + + if (getPlayerCount() < maxPlayers) + return; + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Server_Player *p = playerIterator.next().value(); + if (!p->getReadyStart() && !p->getSpectator()) + return; + } + playerIterator.toFront(); + while (playerIterator.hasNext()) { + Server_Player *p = playerIterator.next().value(); + if (!p->getSpectator()) + p->setupZones(); + } - gameStarted = true; - playerIterator.toFront(); - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); - player->setConceded(false); - player->setReadyStart(false); - } - playerIterator.toFront(); - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); - sendGameEventToPlayer(player, new Event_GameStateChanged(gameStarted, 0, 0, getGameState(player))); - } - -/* QSqlQuery query; - query.prepare("insert into games (id, descr, password, time_started) values(:id, :descr, :password, now())"); - query.bindValue(":id", gameId); - query.bindValue(":descr", description); - query.bindValue(":password", !password.isEmpty()); - query.exec(); - - QMapIterator playerIterator2(players); - while (playerIterator2.hasNext()) { - Server_Player *player = playerIterator2.next().value(); - query.prepare("insert into games_players (id_game, player) values(:id, :player)"); - query.bindValue(":id", gameId); - query.bindValue(":player", player->getPlayerName()); - query.exec(); - } -*/ - activePlayer = -1; - nextTurn(); - - room->broadcastGameListUpdate(this); + gameStarted = true; + playerIterator.toFront(); + while (playerIterator.hasNext()) { + Server_Player *player = playerIterator.next().value(); + player->setConceded(false); + player->setReadyStart(false); + } + + if (firstGameStarted) { + currentReplay->set_duration_seconds(secondsElapsed - startTimeOfThisGame); + replayList.append(currentReplay); + currentReplay = new GameReplay; + currentReplay->set_replay_id(databaseInterface->getNextReplayId()); + ServerInfo_Game *gameInfo = currentReplay->mutable_game_info(); + getInfo(*gameInfo); + gameInfo->set_started(false); + + Event_GameStateChanged omniscientEvent; + createGameStateChangedEvent(&omniscientEvent, 0, true, true); + + GameEventContainer *replayCont = prepareGameEvent(omniscientEvent, -1); + replayCont->set_seconds_elapsed(0); + replayCont->clear_game_id(); + currentReplay->add_event_list()->CopyFrom(*replayCont); + delete replayCont; + + startTimeOfThisGame = secondsElapsed; + } else + firstGameStarted = true; + + sendGameStateToPlayers(); + + activePlayer = -1; + nextTurn(); + + locker.unlock(); + + ServerInfo_Game gameInfo; + gameInfo.set_room_id(room->getId()); + gameInfo.set_game_id(gameId); + gameInfo.set_started(true); + emit gameInfoChanged(gameInfo); } void Server_Game::startGameIfReady() { - emit sigStartGameIfReady(); + emit sigStartGameIfReady(); } void Server_Game::stopGameIfFinished() { - QMutexLocker locker(&gameMutex); - - QMapIterator playerIterator(players); - int playing = 0; - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - if (!p->getConceded() && !p->getSpectator()) - ++playing; - } - if (playing > 1) - return; + QMutexLocker locker(&gameMutex); + + QMapIterator playerIterator(players); + int playing = 0; + while (playerIterator.hasNext()) { + Server_Player *p = playerIterator.next().value(); + if (!p->getConceded() && !p->getSpectator()) + ++playing; + } + if (playing > 1) + return; - gameStarted = false; + gameStarted = false; - playerIterator.toFront(); - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - p->clearZones(); - p->setConceded(false); - } + playerIterator.toFront(); + while (playerIterator.hasNext()) { + Server_Player *p = playerIterator.next().value(); + p->clearZones(); + p->setConceded(false); + } - playerIterator.toFront(); - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); - sendGameEventToPlayer(player, new Event_GameStateChanged(gameStarted, -1, -1, getGameState(player))); - } + sendGameStateToPlayers(); + + locker.unlock(); + + ServerInfo_Game gameInfo; + gameInfo.set_room_id(room->getId()); + gameInfo.set_game_id(gameId); + gameInfo.set_started(false); + emit gameInfoChanged(gameInfo); } -ResponseCode Server_Game::checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions) +Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions) { - if (!(overrideRestrictions && (user->getUserLevel() & ServerInfo_User::IsModerator))) { - if ((_password != password) && !(spectator && !spectatorsNeedPassword)) - return RespWrongPassword; - if (!(user->getUserLevel() & ServerInfo_User::IsRegistered) && onlyRegistered) - return RespUserLevelTooLow; - if (onlyBuddies) - if (!room->getServer()->getBuddyList(creatorInfo->getName()).contains(user->getName())) - return RespOnlyBuddies; - if (room->getServer()->getIgnoreList(creatorInfo->getName()).contains(user->getName())) - return RespInIgnoreList; - if (spectator) { - if (!spectatorsAllowed) - return RespSpectatorsNotAllowed; - } - } - if (!spectator && (gameStarted || (getPlayerCount() >= getMaxPlayers()))) - return RespGameFull; - - return RespOk; + Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface(); + { + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + if (playerIterator.next().value()->getUserInfo()->name() == user->name()) + return Response::RespContextError; + } + if (!(overrideRestrictions && (user->user_level() & ServerInfo_User::IsModerator))) { + if ((_password != password) && !(spectator && !spectatorsNeedPassword)) + return Response::RespWrongPassword; + if (!(user->user_level() & ServerInfo_User::IsRegistered) && onlyRegistered) + return Response::RespUserLevelTooLow; + if (onlyBuddies && (user->name() != creatorInfo->name())) + if (!databaseInterface->isInBuddyList(QString::fromStdString(creatorInfo->name()), QString::fromStdString(user->name()))) + return Response::RespOnlyBuddies; + if (databaseInterface->isInIgnoreList(QString::fromStdString(creatorInfo->name()), QString::fromStdString(user->name()))) + return Response::RespInIgnoreList; + if (spectator) { + if (!spectatorsAllowed) + return Response::RespSpectatorsNotAllowed; + } + } + if (!spectator && (gameStarted || (getPlayerCount() >= getMaxPlayers()))) + return Response::RespGameFull; + + return Response::RespOk; } bool Server_Game::containsUser(const QString &userName) const { - QMutexLocker locker(&gameMutex); - - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) - if (playerIterator.next().value()->getUserInfo()->getName() == userName) - return true; - return false; + QMutexLocker locker(&gameMutex); + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + if (playerIterator.next().value()->getUserInfo()->name() == userName.toStdString()) + return true; + return false; } -Server_Player *Server_Game::addPlayer(Server_ProtocolHandler *handler, bool spectator, bool broadcastUpdate) +void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, ResponseContainer &rc, bool spectator, bool broadcastUpdate) { - QMutexLocker locker(&gameMutex); - - const QList &keyList = players.keys(); - int playerId = keyList.isEmpty() ? 0 : (keyList.last() + 1); - - Server_Player *newPlayer = new Server_Player(this, playerId, handler->getUserInfo(), spectator, handler); - newPlayer->moveToThread(thread()); - sendGameEvent(new Event_Join(newPlayer->getProperties())); - players.insert(playerId, newPlayer); - if (newPlayer->getUserInfo()->getName() == creatorInfo->getName()) { - hostId = playerId; - sendGameEvent(new Event_GameHostChanged(playerId)); - } - - if (broadcastUpdate) - room->broadcastGameListUpdate(this); - - return newPlayer; + QMutexLocker locker(&gameMutex); + + Server_Player *newPlayer = new Server_Player(this, nextPlayerId++, userInterface->copyUserInfo(true, true), spectator, userInterface); + newPlayer->moveToThread(thread()); + + Event_Join joinEvent; + newPlayer->getProperties(*joinEvent.mutable_player_properties(), true); + sendGameEventContainer(prepareGameEvent(joinEvent, -1)); + + const QString playerName = QString::fromStdString(newPlayer->getUserInfo()->name()); + if (spectator) + allSpectatorsEver.insert(playerName); + else + allPlayersEver.insert(playerName); + players.insert(newPlayer->getPlayerId(), newPlayer); + if (newPlayer->getUserInfo()->name() == creatorInfo->name()) { + hostId = newPlayer->getPlayerId(); + sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId)); + } + + if (broadcastUpdate) { + ServerInfo_Game gameInfo; + gameInfo.set_room_id(room->getId()); + gameInfo.set_game_id(gameId); + gameInfo.set_player_count(getPlayerCount()); + gameInfo.set_spectators_count(getSpectatorCount()); + emit gameInfoChanged(gameInfo); + } + + if ((newPlayer->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) && !spectator) + room->getServer()->addPersistentPlayer(playerName, room->getId(), gameId, newPlayer->getPlayerId()); + + userInterface->playerAddedToGame(gameId, room->getId(), newPlayer->getPlayerId()); + + createGameJoinedEvent(newPlayer, rc, false); } void Server_Game::removePlayer(Server_Player *player) { - QMutexLocker roomLocker(&room->roomMutex); - QMutexLocker locker(&gameMutex); - - players.remove(player->getPlayerId()); - removeArrowsToPlayer(player); - - sendGameEvent(new Event_Leave(player->getPlayerId())); - bool playerActive = activePlayer == player->getPlayerId(); - bool playerHost = hostId == player->getPlayerId(); - bool spectator = player->getSpectator(); - player->prepareDestroy(); - - if (!getPlayerCount()) - deleteLater(); - else if (!spectator) { - if (playerHost) { - int newHostId = -1; - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - if (!p->getSpectator()) { - newHostId = p->getPlayerId(); - break; - } - } - if (newHostId != -1) { - hostId = newHostId; - sendGameEvent(new Event_GameHostChanged(hostId)); - } - } - stopGameIfFinished(); - if (gameStarted && playerActive) - nextTurn(); - } - room->broadcastGameListUpdate(this); + room->getServer()->removePersistentPlayer(QString::fromStdString(player->getUserInfo()->name()), room->getId(), gameId, player->getPlayerId()); + players.remove(player->getPlayerId()); + + GameEventStorage ges; + removeArrowsRelatedToPlayer(ges, player); + unattachCards(ges, player); + ges.enqueueGameEvent(Event_Leave(), player->getPlayerId()); + ges.sendToGame(this); + + bool playerActive = activePlayer == player->getPlayerId(); + bool playerHost = hostId == player->getPlayerId(); + bool spectator = player->getSpectator(); + player->prepareDestroy(); + + if (!getPlayerCount()) { + gameClosed = true; + deleteLater(); + return; + } else if (!spectator) { + if (playerHost) { + int newHostId = -1; + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Server_Player *p = playerIterator.next().value(); + if (!p->getSpectator()) { + newHostId = p->getPlayerId(); + break; + } + } + if (newHostId != -1) { + hostId = newHostId; + sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId)); + } + } + stopGameIfFinished(); + if (gameStarted && playerActive) + nextTurn(); + } + + ServerInfo_Game gameInfo; + gameInfo.set_room_id(room->getId()); + gameInfo.set_game_id(gameId); + gameInfo.set_player_count(getPlayerCount()); + gameInfo.set_spectators_count(getSpectatorCount()); + emit gameInfoChanged(gameInfo); } -void Server_Game::removeArrowsToPlayer(Server_Player *player) +void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Player *player) { - QMutexLocker locker(&gameMutex); - - // Remove all arrows of other players pointing to the player being removed or to one of his cards. - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - QList arrows = p->getArrows().values(); - QList toDelete; - for (int i = 0; i < arrows.size(); ++i) { - Server_Arrow *a = arrows[i]; - Server_Card *targetCard = qobject_cast(a->getTargetItem()); - if (targetCard) { - if (targetCard->getZone()->getPlayer() == player) - toDelete.append(a); - } else if ((static_cast(a->getTargetItem()) == player) || (a->getStartCard()->getZone()->getPlayer() == player)) - toDelete.append(a); - } - for (int i = 0; i < toDelete.size(); ++i) { - sendGameEvent(new Event_DeleteArrow(p->getPlayerId(), toDelete[i]->getId())); - p->deleteArrow(toDelete[i]->getId()); - } - } + QMutexLocker locker(&gameMutex); + + // Remove all arrows of other players pointing to the player being removed or to one of his cards. + // Also remove all arrows starting at one of his cards. This is necessary since players can create + // arrows that start at another person's cards. + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Server_Player *p = playerIterator.next().value(); + QList arrows = p->getArrows().values(); + QList toDelete; + for (int i = 0; i < arrows.size(); ++i) { + Server_Arrow *a = arrows[i]; + Server_Card *targetCard = qobject_cast(a->getTargetItem()); + if (targetCard) { + if (targetCard->getZone()->getPlayer() == player) + toDelete.append(a); + } else if (static_cast(a->getTargetItem()) == player) + toDelete.append(a); + + // Don't use else here! It has to happen regardless of whether targetCard == 0. + if (a->getStartCard()->getZone()->getPlayer() == player) + toDelete.append(a); + } + for (int i = 0; i < toDelete.size(); ++i) { + Event_DeleteArrow event; + event.set_arrow_id(toDelete[i]->getId()); + ges.enqueueGameEvent(event, p->getPlayerId()); + + p->deleteArrow(toDelete[i]->getId()); + } + } +} + +void Server_Game::unattachCards(GameEventStorage &ges, Server_Player *player) +{ + QMutexLocker locker(&gameMutex); + + QMapIterator zoneIterator(player->getZones()); + while (zoneIterator.hasNext()) { + Server_CardZone *zone = zoneIterator.next().value(); + for (int i = 0; i < zone->getCards().size(); ++i) { + Server_Card *card = zone->getCards().at(i); + + // Make a copy of the list because the original one gets modified during the loop + QList attachedCards = card->getAttachedCards(); + for (int i = 0; i < attachedCards.size(); ++i) + attachedCards[i]->getZone()->getPlayer()->unattachCard(ges, attachedCards[i]); + } + } } bool Server_Game::kickPlayer(int playerId) { - QMutexLocker roomLocker(&room->roomMutex); - QMutexLocker locker(&gameMutex); - - Server_Player *playerToKick = players.value(playerId); - if (!playerToKick) - return false; - - sendGameEventToPlayer(playerToKick, new Event_Kicked); - removePlayer(playerToKick); - - return true; + QMutexLocker locker(&gameMutex); + + Server_Player *playerToKick = players.value(playerId); + if (!playerToKick) + return false; + + GameEventContainer *gec = prepareGameEvent(Event_Kicked(), -1); + playerToKick->sendGameEvent(*gec); + delete gec; + + removePlayer(playerToKick); + + return true; } void Server_Game::setActivePlayer(int _activePlayer) { - QMutexLocker locker(&gameMutex); - - activePlayer = _activePlayer; - sendGameEvent(new Event_SetActivePlayer(activePlayer, activePlayer)); - setActivePhase(0); + QMutexLocker locker(&gameMutex); + + activePlayer = _activePlayer; + + Event_SetActivePlayer event; + event.set_active_player_id(activePlayer); + sendGameEventContainer(prepareGameEvent(event, -1)); + + setActivePhase(0); } void Server_Game::setActivePhase(int _activePhase) { - QMutexLocker locker(&gameMutex); - - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); - QList toDelete = player->getArrows().values(); - for (int i = 0; i < toDelete.size(); ++i) { - Server_Arrow *a = toDelete[i]; - sendGameEvent(new Event_DeleteArrow(player->getPlayerId(), a->getId())); - player->deleteArrow(a->getId()); - } - } - - activePhase = _activePhase; - sendGameEvent(new Event_SetActivePhase(-1, activePhase)); + QMutexLocker locker(&gameMutex); + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Server_Player *player = playerIterator.next().value(); + 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; + + Event_SetActivePhase event; + event.set_phase(activePhase); + sendGameEventContainer(prepareGameEvent(event, -1)); } void Server_Game::nextTurn() { - QMutexLocker locker(&gameMutex); - - const QList keys = players.keys(); - int listPos = -1; - if (activePlayer != -1) - listPos = keys.indexOf(activePlayer); - do { - ++listPos; - if (listPos == keys.size()) - listPos = 0; - } while (players.value(keys[listPos])->getSpectator() || players.value(keys[listPos])->getConceded()); - - setActivePlayer(keys[listPos]); + QMutexLocker locker(&gameMutex); + + const QList keys = players.keys(); + int listPos = -1; + if (activePlayer != -1) + listPos = keys.indexOf(activePlayer); + do { + ++listPos; + if (listPos == keys.size()) + listPos = 0; + } while (players.value(keys[listPos])->getSpectator() || players.value(keys[listPos])->getConceded()); + + setActivePlayer(keys[listPos]); } -void Server_Game::postConnectionStatusUpdate(Server_Player *player, bool connectionStatus) +void Server_Game::createGameJoinedEvent(Server_Player *player, ResponseContainer &rc, bool resuming) { - QMutexLocker locker(&gameMutex); - - sendGameEvent(new Event_ConnectionStateChanged(player->getPlayerId(), connectionStatus)); + Event_GameJoined event1; + getInfo(*event1.mutable_game_info()); + event1.set_host_id(hostId); + event1.set_player_id(player->getPlayerId()); + event1.set_spectator(player->getSpectator()); + event1.set_resuming(resuming); + if (resuming) { + const QStringList &allGameTypes = room->getGameTypes(); + for (int i = 0; i < allGameTypes.size(); ++i) { + ServerInfo_GameType *newGameType = event1.add_game_types(); + newGameType->set_game_type_id(i); + newGameType->set_description(allGameTypes[i].toStdString()); + } + } + rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, Server_AbstractUserInterface::prepareSessionEvent(event1)); + + Event_GameStateChanged event2; + event2.set_seconds_elapsed(secondsElapsed); + event2.set_game_started(gameStarted); + event2.set_active_player_id(activePlayer); + event2.set_active_phase(activePhase); + + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) + playerIterator.next().value()->getInfo(event2.add_player_list(), player, player->getSpectator() && spectatorsSeeEverything, true); + + rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, prepareGameEvent(event2, -1)); } -QList Server_Game::getGameState(Server_Player *playerWhosAsking) const +void Server_Game::sendGameEventContainer(GameEventContainer *cont, GameEventStorageItem::EventRecipients recipients, int privatePlayerId) { - QMutexLocker locker(&gameMutex); - - QList result; - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); - - QList arrowList; - QMapIterator arrowIterator(player->getArrows()); - while (arrowIterator.hasNext()) { - Server_Arrow *arrow = arrowIterator.next().value(); - Server_Card *targetCard = qobject_cast(arrow->getTargetItem()); - if (targetCard) - arrowList.append(new ServerInfo_Arrow( - arrow->getId(), - arrow->getStartCard()->getZone()->getPlayer()->getPlayerId(), - arrow->getStartCard()->getZone()->getName(), - arrow->getStartCard()->getId(), - targetCard->getZone()->getPlayer()->getPlayerId(), - targetCard->getZone()->getName(), - targetCard->getId(), - arrow->getColor() - )); - else - arrowList.append(new ServerInfo_Arrow( - arrow->getId(), - arrow->getStartCard()->getZone()->getPlayer()->getPlayerId(), - arrow->getStartCard()->getZone()->getName(), - arrow->getStartCard()->getId(), - qobject_cast(arrow->getTargetItem())->getPlayerId(), - QString(), - -1, - arrow->getColor() - )); - } - - QList counterList; - QMapIterator counterIterator(player->getCounters()); - while (counterIterator.hasNext()) { - Server_Counter *counter = counterIterator.next().value(); - counterList.append(new ServerInfo_Counter(counter->getId(), counter->getName(), counter->getColor(), counter->getRadius(), counter->getCount())); - } - - QList zoneList; - QMapIterator zoneIterator(player->getZones()); - while (zoneIterator.hasNext()) { - Server_CardZone *zone = zoneIterator.next().value(); - QList cardList; - if ( - (((playerWhosAsking == player) || (playerWhosAsking->getSpectator() && spectatorsSeeEverything)) && (zone->getType() != HiddenZone)) - || ((playerWhosAsking != player) && (zone->getType() == PublicZone)) - ) { - QListIterator cardIterator(zone->cards); - while (cardIterator.hasNext()) { - Server_Card *card = cardIterator.next(); - QString displayedName = card->getFaceDown() ? QString() : card->getName(); - - QList cardCounterList; - QMapIterator cardCounterIterator(card->getCounters()); - while (cardCounterIterator.hasNext()) { - cardCounterIterator.next(); - cardCounterList.append(new ServerInfo_CardCounter(cardCounterIterator.key(), cardCounterIterator.value())); - } - - int attachPlayerId = -1; - QString attachZone; - int attachCardId = -1; - if (card->getParentCard()) { - attachPlayerId = card->getParentCard()->getZone()->getPlayer()->getPlayerId(); - attachZone = card->getParentCard()->getZone()->getName(); - attachCardId = card->getParentCard()->getId(); - } - cardList.append(new ServerInfo_Card(card->getId(), displayedName, card->getX(), card->getY(), card->getFaceDown(), card->getTapped(), card->getAttacking(), card->getColor(), card->getPT(), card->getAnnotation(), card->getDestroyOnZoneChange(), card->getDoesntUntap(), cardCounterList, attachPlayerId, attachZone, attachCardId)); - } - } - zoneList.append(new ServerInfo_Zone(zone->getName(), zone->getType(), zone->hasCoords(), zone->cards.size(), cardList)); - } - - result.append(new ServerInfo_Player(player->getProperties(), player == playerWhosAsking ? player->getDeck() : 0, zoneList, counterList, arrowList)); - } - return result; + QMutexLocker locker(&gameMutex); + + cont->set_game_id(gameId); + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Server_Player *p = playerIterator.next().value(); + const bool playerPrivate = (p->getPlayerId() == privatePlayerId) || (p->getSpectator() && spectatorsSeeEverything); + if ((recipients.testFlag(GameEventStorageItem::SendToPrivate) && playerPrivate) || (recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate)) + p->sendGameEvent(*cont); + } + if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) { + cont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame); + cont->clear_game_id(); + currentReplay->add_event_list()->CopyFrom(*cont); + } + + delete cont; } -void Server_Game::sendGameEvent(GameEvent *event, GameEventContext *context, Server_Player *exclude) +GameEventContainer *Server_Game::prepareGameEvent(const ::google::protobuf::Message &gameEvent, int playerId, GameEventContext *context) { - sendGameEventContainer(new GameEventContainer(QList() << event, -1, context), exclude); + GameEventContainer *cont = new GameEventContainer; + cont->set_game_id(gameId); + if (context) + cont->mutable_context()->CopyFrom(*context); + GameEvent *event = cont->add_event_list(); + if (playerId != -1) + event->set_player_id(playerId); + event->GetReflection()->MutableMessage(event, gameEvent.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(gameEvent); + return cont; } -void Server_Game::sendGameEventContainer(GameEventContainer *cont, Server_Player *exclude, bool excludeOmniscient) +void Server_Game::getInfo(ServerInfo_Game &result) const { - QMutexLocker locker(&gameMutex); - - cont->setGameId(gameId); - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - if ((p != exclude) && !(excludeOmniscient && p->getSpectator() && spectatorsSeeEverything)) - p->sendProtocolItem(cont, false); - } - - delete cont; -} - -void Server_Game::sendGameEventContainerOmniscient(GameEventContainer *cont, Server_Player *exclude) -{ - QMutexLocker locker(&gameMutex); - - cont->setGameId(gameId); - QMapIterator playerIterator(players); - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - if ((p != exclude) && (p->getSpectator() && spectatorsSeeEverything)) - p->sendProtocolItem(cont, false); - } - - delete cont; -} - -void Server_Game::sendGameEventToPlayer(Server_Player *player, GameEvent *event) -{ - player->sendProtocolItem(new GameEventContainer(QList() << event, gameId)); -} - -ServerInfo_Game *Server_Game::getInfo() const -{ - QMutexLocker locker(&gameMutex); - - if (players.isEmpty()) - // Game is closing - return new ServerInfo_Game(room->getId(), getGameId(), QString(), false, 0, getMaxPlayers(), false, QList(), 0, false, 0); - else { - // Game is open - - QList gameTypeList; - for (int i = 0; i < gameTypes.size(); ++i) - gameTypeList.append(new GameTypeId(gameTypes[i])); - - return new ServerInfo_Game( - room->getId(), - getGameId(), - getDescription(), - !getPassword().isEmpty(), - getPlayerCount(), - getMaxPlayers(), - gameStarted, - gameTypeList, - new ServerInfo_User(getCreatorInfo(), false), - onlyBuddies, - onlyRegistered, - getSpectatorsAllowed(), - getSpectatorsNeedPassword(), - getSpectatorCount() - ); - } + QMutexLocker locker(&gameMutex); + + result.set_room_id(room->getId()); + result.set_game_id(gameId); + if (gameClosed) + result.set_closed(true); + else { + for (int i = 0; i < gameTypes.size(); ++i) + result.add_game_types(gameTypes[i]); + + result.set_max_players(getMaxPlayers()); + result.set_description(getDescription().toStdString()); + result.set_with_password(!getPassword().isEmpty()); + result.set_player_count(getPlayerCount()); + result.set_started(gameStarted); + result.mutable_creator_info()->CopyFrom(*getCreatorInfo()); + result.set_only_buddies(onlyBuddies); + result.set_only_registered(onlyRegistered); + result.set_spectators_allowed(getSpectatorsAllowed()); + result.set_spectators_need_password(getSpectatorsNeedPassword()); + result.set_spectators_can_chat(spectatorsCanTalk); + result.set_spectators_omniscient(spectatorsSeeEverything); + result.set_spectators_count(getSpectatorCount()); + result.set_start_time(startTime.toTime_t()); + } } diff --git a/common/server_game.h b/common/server_game.h index 0b1ecc8ed..cf7eb0862 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -21,83 +21,107 @@ #define SERVERGAME_H #include -#include #include #include -#include "server_player.h" -#include "protocol.h" +#include +#include +#include +#include "server_response_containers.h" +#include "pb/response.pb.h" +#include "pb/serverinfo_game.pb.h" class QTimer; +class GameEventContainer; +class GameReplay; class Server_Room; +class Server_Player; class ServerInfo_User; +class ServerInfo_Player; +class ServerInfo_Game; +class Server_AbstractUserInterface; +class Event_GameStateChanged; class Server_Game : public QObject { - Q_OBJECT + Q_OBJECT private: - Server_Room *room; - int hostId; - ServerInfo_User *creatorInfo; - QMap players; - bool gameStarted; - int gameId; - QString description; - QString password; - int maxPlayers; - QList gameTypes; - int activePlayer, activePhase; - bool onlyBuddies, onlyRegistered; - bool spectatorsAllowed; - bool spectatorsNeedPassword; - bool spectatorsCanTalk; - bool spectatorsSeeEverything; - int inactivityCounter; - int secondsElapsed; - QTimer *pingClock; + Server_Room *room; + int nextPlayerId; + int hostId; + ServerInfo_User *creatorInfo; + QMap players; + QSet allPlayersEver, allSpectatorsEver; + bool gameStarted; + bool gameClosed; + int gameId; + QString description; + QString password; + int maxPlayers; + QList gameTypes; + int activePlayer, activePhase; + bool onlyBuddies, onlyRegistered; + bool spectatorsAllowed; + bool spectatorsNeedPassword; + bool spectatorsCanTalk; + bool spectatorsSeeEverything; + int inactivityCounter; + int startTimeOfThisGame, secondsElapsed; + bool firstGameStarted; + QDateTime startTime; + QTimer *pingClock; + QList replayList; + GameReplay *currentReplay; + + void createGameStateChangedEvent(Event_GameStateChanged *event, Server_Player *playerWhosAsking, bool omniscient, bool withUserInfo); + void sendGameStateToPlayers(); + void storeGameInformation(); signals: - void sigStartGameIfReady(); + void sigStartGameIfReady(); + void gameInfoChanged(ServerInfo_Game gameInfo); private slots: - void pingClockTimeout(); - void doStartGameIfReady(); + void pingClockTimeout(); + void doStartGameIfReady(); public: - mutable QMutex gameMutex; - Server_Game(Server_ProtocolHandler *_creator, int _gameId, const QString &_description, const QString &_password, int _maxPlayers, const QList &_gameTypes, bool _onlyBuddies, bool _onlyRegistered, bool _spectatorsAllowed, bool _spectatorsNeedPassword, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, Server_Room *parent); - ~Server_Game(); - ServerInfo_Game *getInfo() const; - int getHostId() const { return hostId; } - ServerInfo_User *getCreatorInfo() const { return creatorInfo; } - bool getGameStarted() const { return gameStarted; } - int getPlayerCount() const; - int getSpectatorCount() const; - const QMap &getPlayers() const { return players; } - Server_Player *getPlayer(int playerId) const { return players.value(playerId, 0); } - int getGameId() const { return gameId; } - QString getDescription() const { return description; } - QString getPassword() const { return password; } - int getMaxPlayers() const { return maxPlayers; } - bool getSpectatorsAllowed() const { return spectatorsAllowed; } - bool getSpectatorsNeedPassword() const { return spectatorsNeedPassword; } - bool getSpectatorsCanTalk() const { return spectatorsCanTalk; } - bool getSpectatorsSeeEverything() const { return spectatorsSeeEverything; } - ResponseCode checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions); - bool containsUser(const QString &userName) const; - Server_Player *addPlayer(Server_ProtocolHandler *handler, bool spectator, bool broadcastUpdate = true); - void removePlayer(Server_Player *player); - void removeArrowsToPlayer(Server_Player *player); - bool kickPlayer(int playerId); - void startGameIfReady(); - void stopGameIfFinished(); - int getActivePlayer() const { return activePlayer; } - int getActivePhase() const { return activePhase; } - void setActivePlayer(int _activePlayer); - void setActivePhase(int _activePhase); - void nextTurn(); - void postConnectionStatusUpdate(Server_Player *player, bool connectionStatus); + mutable QMutex gameMutex; + Server_Game(const ServerInfo_User &_creatorInfo, int _gameId, const QString &_description, const QString &_password, int _maxPlayers, const QList &_gameTypes, bool _onlyBuddies, bool _onlyRegistered, bool _spectatorsAllowed, bool _spectatorsNeedPassword, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, Server_Room *parent); + ~Server_Game(); + Server_Room *getRoom() const { return room; } + void getInfo(ServerInfo_Game &result) const; + int getHostId() const { return hostId; } + ServerInfo_User *getCreatorInfo() const { return creatorInfo; } + bool getGameStarted() const { return gameStarted; } + int getPlayerCount() const; + int getSpectatorCount() const; + const QMap &getPlayers() const { return players; } + int getGameId() const { return gameId; } + QString getDescription() const { return description; } + QString getPassword() const { return password; } + int getMaxPlayers() const { return maxPlayers; } + bool getSpectatorsAllowed() const { return spectatorsAllowed; } + bool getSpectatorsNeedPassword() const { return spectatorsNeedPassword; } + bool getSpectatorsCanTalk() const { return spectatorsCanTalk; } + bool getSpectatorsSeeEverything() const { return spectatorsSeeEverything; } + Response::ResponseCode checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions); + bool containsUser(const QString &userName) const; + void addPlayer(Server_AbstractUserInterface *userInterface, ResponseContainer &rc, bool spectator, bool broadcastUpdate = true); + void removePlayer(Server_Player *player); + void removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Player *player); + void unattachCards(GameEventStorage &ges, Server_Player *player); + bool kickPlayer(int playerId); + void startGameIfReady(); + void stopGameIfFinished(); + int getActivePlayer() const { return activePlayer; } + int getActivePhase() const { return activePhase; } + void setActivePlayer(int _activePlayer); + void setActivePhase(int _activePhase); + void nextTurn(); + int getSecondsElapsed() const { return secondsElapsed; } - QList getGameState(Server_Player *playerWhosAsking) const; - void sendGameEvent(GameEvent *event, GameEventContext *context = 0, Server_Player *exclude = 0); - void sendGameEventContainer(GameEventContainer *cont, Server_Player *exclude = 0, bool excludeOmniscient = false); - void sendGameEventContainerOmniscient(GameEventContainer *cont, Server_Player *exclude = 0); - void sendGameEventToPlayer(Server_Player *player, GameEvent *event); + void createGameJoinedEvent(Server_Player *player, ResponseContainer &rc, bool resuming); + + GameEventContainer *prepareGameEvent(const ::google::protobuf::Message &gameEvent, int playerId, GameEventContext *context = 0); + GameEventContext prepareGameEventContext(const ::google::protobuf::Message &gameEventContext); + + void sendGameEventContainer(GameEventContainer *cont, GameEventStorageItem::EventRecipients recipients = GameEventStorageItem::SendToPrivate | GameEventStorageItem::SendToOthers, int privatePlayerId = -1); }; #endif diff --git a/common/server_metatypes.h b/common/server_metatypes.h new file mode 100644 index 000000000..4266218e4 --- /dev/null +++ b/common/server_metatypes.h @@ -0,0 +1,24 @@ +#ifndef SERVER_METATYPES_H +#define SERVER_METATYPES_H + +#include + +#include "pb/serverinfo_user.pb.h" +#include "pb/serverinfo_room.pb.h" +#include "pb/serverinfo_game.pb.h" +#include "pb/commands.pb.h" +#include "pb/response.pb.h" +#include "pb/game_event_container.pb.h" +#include "pb/isl_message.pb.h" +#include "pb/room_commands.pb.h" + +Q_DECLARE_METATYPE(ServerInfo_User) +Q_DECLARE_METATYPE(ServerInfo_Room) +Q_DECLARE_METATYPE(ServerInfo_Game) +Q_DECLARE_METATYPE(CommandContainer) +Q_DECLARE_METATYPE(Response) +Q_DECLARE_METATYPE(GameEventContainer) +Q_DECLARE_METATYPE(IslMessage) +Q_DECLARE_METATYPE(Command_JoinGame) + +#endif diff --git a/common/server_player.cpp b/common/server_player.cpp index fe977bb62..b9277a2d4 100644 --- a/common/server_player.cpp +++ b/common/server_player.cpp @@ -1,17 +1,90 @@ +#include "server.h" #include "server_player.h" #include "server_card.h" #include "server_counter.h" #include "server_arrow.h" #include "server_cardzone.h" #include "server_game.h" -#include "server_protocolhandler.h" -#include "protocol.h" -#include "protocol_items.h" +#include "server_room.h" +#include "server_abstractuserinterface.h" +#include "server_database_interface.h" #include "decklist.h" +#include "color.h" +#include "rng_abstract.h" +#include "get_pb_extension.h" + +#include "pb/response.pb.h" +#include "pb/response_deck_download.pb.h" +#include "pb/response_dump_zone.pb.h" +#include "pb/command_attach_card.pb.h" +#include "pb/command_change_zone_properties.pb.h" +#include "pb/command_concede.pb.h" +#include "pb/command_create_arrow.pb.h" +#include "pb/command_create_counter.pb.h" +#include "pb/command_create_token.pb.h" +#include "pb/command_deck_select.pb.h" +#include "pb/command_set_sideboard_lock.pb.h" +#include "pb/command_del_counter.pb.h" +#include "pb/command_delete_arrow.pb.h" +#include "pb/command_draw_cards.pb.h" +#include "pb/command_dump_zone.pb.h" +#include "pb/command_flip_card.pb.h" +#include "pb/command_game_say.pb.h" +#include "pb/command_inc_card_counter.pb.h" +#include "pb/command_inc_counter.pb.h" +#include "pb/command_kick_from_game.pb.h" +#include "pb/command_leave_game.pb.h" +#include "pb/command_move_card.pb.h" +#include "pb/command_mulligan.pb.h" +#include "pb/command_next_turn.pb.h" +#include "pb/command_ready_start.pb.h" +#include "pb/command_reveal_cards.pb.h" +#include "pb/command_roll_die.pb.h" +#include "pb/command_set_active_phase.pb.h" +#include "pb/command_set_card_attr.pb.h" +#include "pb/command_set_card_counter.pb.h" +#include "pb/command_set_counter.pb.h" +#include "pb/command_set_sideboard_plan.pb.h" +#include "pb/command_shuffle.pb.h" +#include "pb/command_stop_dump_zone.pb.h" +#include "pb/command_undo_draw.pb.h" +#include "pb/serverinfo_user.pb.h" +#include "pb/serverinfo_player.pb.h" +#include "pb/event_attach_card.pb.h" +#include "pb/event_change_zone_properties.pb.h" +#include "pb/event_create_arrow.pb.h" +#include "pb/event_create_counter.pb.h" +#include "pb/event_create_token.pb.h" +#include "pb/event_delete_arrow.pb.h" +#include "pb/event_del_counter.pb.h" +#include "pb/event_draw_cards.pb.h" +#include "pb/event_destroy_card.pb.h" +#include "pb/event_dump_zone.pb.h" +#include "pb/event_flip_card.pb.h" +#include "pb/event_game_say.pb.h" +#include "pb/event_move_card.pb.h" +#include "pb/event_player_properties_changed.pb.h" +#include "pb/event_set_card_attr.pb.h" +#include "pb/event_shuffle.pb.h" +#include "pb/event_roll_die.pb.h" +#include "pb/event_set_card_counter.pb.h" +#include "pb/event_set_counter.pb.h" +#include "pb/event_stop_dump_zone.pb.h" +#include "pb/event_reveal_cards.pb.h" + +#include "pb/context_connection_state_changed.pb.h" +#include "pb/context_concede.pb.h" +#include "pb/context_deck_select.pb.h" +#include "pb/context_set_sideboard_lock.pb.h" +#include "pb/context_move_card.pb.h" +#include "pb/context_mulligan.pb.h" +#include "pb/context_undo_draw.pb.h" +#include "pb/context_ready_start.pb.h" + #include -Server_Player::Server_Player(Server_Game *_game, int _playerId, ServerInfo_User *_userInfo, bool _spectator, Server_ProtocolHandler *_handler) - : game(_game), handler(_handler), userInfo(new ServerInfo_User(_userInfo)), deck(0), playerId(_playerId), spectator(_spectator), nextCardId(0), readyStart(false), conceded(false) +Server_Player::Server_Player(Server_Game *_game, int _playerId, const ServerInfo_User &_userInfo, bool _spectator, Server_AbstractUserInterface *_userInterface) + : ServerInfo_User_Container(_userInfo), game(_game), userInterface(_userInterface), deck(0), pingTime(0), playerId(_playerId), spectator(_spectator), nextCardId(0), readyStart(false), conceded(false), sideboardLocked(true) { } @@ -21,511 +94,1624 @@ Server_Player::~Server_Player() void Server_Player::prepareDestroy() { - QMutexLocker locker(&game->gameMutex); - - delete deck; - - playerMutex.lock(); - if (handler) - handler->playerRemovedFromGame(game); - playerMutex.unlock(); - - delete userInfo; - - clearZones(); - - deleteLater(); -} - -void Server_Player::moveToThread(QThread *thread) -{ - QObject::moveToThread(thread); - userInfo->moveToThread(thread); + delete deck; + + playerMutex.lock(); + if (userInterface) + userInterface->playerRemovedFromGame(game); + playerMutex.unlock(); + + clearZones(); + + deleteLater(); } int Server_Player::newCardId() { - QMutexLocker locker(&game->gameMutex); - - return nextCardId++; + return nextCardId++; } int Server_Player::newCounterId() const { - QMutexLocker locker(&game->gameMutex); - - int id = 0; - QMapIterator i(counters); - while (i.hasNext()) { - Server_Counter *c = i.next().value(); - if (c->getId() > id) - id = c->getId(); - } - return id + 1; + int id = 0; + QMapIterator i(counters); + while (i.hasNext()) { + Server_Counter *c = i.next().value(); + if (c->getId() > id) + id = c->getId(); + } + return id + 1; } int Server_Player::newArrowId() const { - QMutexLocker locker(&game->gameMutex); - - int id = 0; - QMapIterator i(arrows); - while (i.hasNext()) { - Server_Arrow *a = i.next().value(); - if (a->getId() > id) - id = a->getId(); - } - return id + 1; + int id = 0; + QMapIterator i(arrows); + while (i.hasNext()) { + Server_Arrow *a = i.next().value(); + if (a->getId() > id) + id = a->getId(); + } + return id + 1; } void Server_Player::setupZones() { - QMutexLocker locker(&game->gameMutex); - - // This may need to be customized according to the game rules. - // ------------------------------------------------------------------ + // This may need to be customized according to the game rules. + // ------------------------------------------------------------------ - // Create zones - Server_CardZone *deckZone = new Server_CardZone(this, "deck", false, HiddenZone); - addZone(deckZone); - Server_CardZone *sbZone = new Server_CardZone(this, "sb", false, HiddenZone); - addZone(sbZone); - addZone(new Server_CardZone(this, "table", true, PublicZone)); - addZone(new Server_CardZone(this, "hand", false, PrivateZone)); - addZone(new Server_CardZone(this, "stack", false, PublicZone)); - addZone(new Server_CardZone(this, "grave", false, PublicZone)); - addZone(new Server_CardZone(this, "rfg", false, PublicZone)); + // Create zones + Server_CardZone *deckZone = new Server_CardZone(this, "deck", false, ServerInfo_Zone::HiddenZone); + addZone(deckZone); + Server_CardZone *sbZone = new Server_CardZone(this, "sb", false, ServerInfo_Zone::HiddenZone); + addZone(sbZone); + addZone(new Server_CardZone(this, "table", true, ServerInfo_Zone::PublicZone)); + addZone(new Server_CardZone(this, "hand", false, ServerInfo_Zone::PrivateZone)); + addZone(new Server_CardZone(this, "stack", false, ServerInfo_Zone::PublicZone)); + addZone(new Server_CardZone(this, "grave", false, ServerInfo_Zone::PublicZone)); + addZone(new Server_CardZone(this, "rfg", false, ServerInfo_Zone::PublicZone)); - addCounter(new Server_Counter(0, "life", Color(255, 255, 255), 25, 20)); - addCounter(new Server_Counter(1, "w", Color(255, 255, 150), 20, 0)); - addCounter(new Server_Counter(2, "u", Color(150, 150, 255), 20, 0)); - addCounter(new Server_Counter(3, "b", Color(150, 150, 150), 20, 0)); - addCounter(new Server_Counter(4, "r", Color(250, 150, 150), 20, 0)); - addCounter(new Server_Counter(5, "g", Color(150, 255, 150), 20, 0)); - addCounter(new Server_Counter(6, "x", Color(255, 255, 255), 20, 0)); - addCounter(new Server_Counter(7, "storm", Color(255, 255, 255), 20, 0)); + addCounter(new Server_Counter(0, "life", makeColor(255, 255, 255), 25, 20)); + addCounter(new Server_Counter(1, "w", makeColor(255, 255, 150), 20, 0)); + addCounter(new Server_Counter(2, "u", makeColor(150, 150, 255), 20, 0)); + addCounter(new Server_Counter(3, "b", makeColor(150, 150, 150), 20, 0)); + addCounter(new Server_Counter(4, "r", makeColor(250, 150, 150), 20, 0)); + addCounter(new Server_Counter(5, "g", makeColor(150, 255, 150), 20, 0)); + addCounter(new Server_Counter(6, "x", makeColor(255, 255, 255), 20, 0)); + addCounter(new Server_Counter(7, "storm", makeColor(255, 255, 255), 20, 0)); - initialCards = 7; + initialCards = 7; - // ------------------------------------------------------------------ + // ------------------------------------------------------------------ - // Assign card ids and create deck from decklist - InnerDecklistNode *listRoot = deck->getRoot(); - nextCardId = 0; - for (int i = 0; i < listRoot->size(); ++i) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - Server_CardZone *z; - if (currentZone->getName() == "main") - z = deckZone; - else if (currentZone->getName() == "side") - z = sbZone; - else - continue; - - for (int j = 0; j < currentZone->size(); ++j) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; - for (int k = 0; k < currentCard->getNumber(); ++k) - z->cards.append(new Server_Card(currentCard->getName(), nextCardId++, 0, 0, z)); - } - } - - const QList &sideboardPlan = deck->getCurrentSideboardPlan(); - for (int i = 0; i < sideboardPlan.size(); ++i) { - MoveCardToZone *m = sideboardPlan[i]; - - Server_CardZone *start, *target; - if (m->getStartZone() == "main") - start = deckZone; - else if (m->getStartZone() == "side") - start = sbZone; - else - continue; - if (m->getTargetZone() == "main") - target = deckZone; - else if (m->getTargetZone() == "side") - target = sbZone; - else - continue; - - for (int j = 0; j < start->cards.size(); ++j) - if (start->cards[j]->getName() == m->getCardName()) { - Server_Card *card = start->cards[j]; - start->cards.removeAt(j); - target->cards.append(card); - break; - } - } - - deckZone->shuffle(); + // Assign card ids and create deck from decklist + InnerDecklistNode *listRoot = deck->getRoot(); + nextCardId = 0; + for (int i = 0; i < listRoot->size(); ++i) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + Server_CardZone *z; + if (currentZone->getName() == "main") + z = deckZone; + else if (currentZone->getName() == "side") + z = sbZone; + else + continue; + + for (int j = 0; j < currentZone->size(); ++j) { + DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); + if (!currentCard) + continue; + for (int k = 0; k < currentCard->getNumber(); ++k) + z->insertCard(new Server_Card(currentCard->getName(), nextCardId++, 0, 0, z), -1, 0); + } + } + + const QList &sideboardPlan = deck->getCurrentSideboardPlan(); + for (int i = 0; i < sideboardPlan.size(); ++i) { + const MoveCard_ToZone &m = sideboardPlan[i]; + const QString startZone = QString::fromStdString(m.start_zone()); + const QString targetZone = QString::fromStdString(m.target_zone()); + + Server_CardZone *start, *target; + if (startZone == "main") + start = deckZone; + else if (startZone == "side") + start = sbZone; + else + continue; + if (targetZone == "main") + target = deckZone; + else if (targetZone == "side") + target = sbZone; + else + continue; + + for (int j = 0; j < start->getCards().size(); ++j) + if (start->getCards()[j]->getName() == QString::fromStdString(m.card_name())) { + Server_Card *card = start->getCard(j, NULL, true); + target->insertCard(card, -1, 0); + break; + } + } + + deckZone->shuffle(); } void Server_Player::clearZones() { - QMutexLocker locker(&game->gameMutex); - - QMapIterator zoneIterator(zones); - while (zoneIterator.hasNext()) - delete zoneIterator.next().value(); - zones.clear(); + QMapIterator zoneIterator(zones); + while (zoneIterator.hasNext()) + delete zoneIterator.next().value(); + zones.clear(); - QMapIterator counterIterator(counters); - while (counterIterator.hasNext()) - delete counterIterator.next().value(); - counters.clear(); - - QMapIterator arrowIterator(arrows); - while (arrowIterator.hasNext()) - delete arrowIterator.next().value(); - arrows.clear(); + QMapIterator counterIterator(counters); + while (counterIterator.hasNext()) + delete counterIterator.next().value(); + counters.clear(); + + QMapIterator arrowIterator(arrows); + while (arrowIterator.hasNext()) + delete arrowIterator.next().value(); + arrows.clear(); - lastDrawList.clear(); + lastDrawList.clear(); } -ServerInfo_PlayerProperties *Server_Player::getProperties() +void Server_Player::getProperties(ServerInfo_PlayerProperties &result, bool withUserInfo) { - QMutexLocker locker(&game->gameMutex); - - return new ServerInfo_PlayerProperties(playerId, new ServerInfo_User(userInfo), spectator, conceded, readyStart, deck ? deck->getDeckHash() : QString()); -} - -void Server_Player::setDeck(DeckList *_deck) -{ - QMutexLocker locker(&game->gameMutex); - - delete deck; - deck = _deck; + result.set_player_id(playerId); + if (withUserInfo) + copyUserInfo(*(result.mutable_user_info()), true); + result.set_spectator(spectator); + if (!spectator) { + result.set_conceded(conceded); + result.set_sideboard_locked(sideboardLocked); + result.set_ready_start(readyStart); + } + if (deck) + result.set_deck_hash(deck->getDeckHash().toStdString()); + result.set_ping_seconds(pingTime); } void Server_Player::addZone(Server_CardZone *zone) { - QMutexLocker locker(&game->gameMutex); - - zones.insert(zone->getName(), zone); + zones.insert(zone->getName(), zone); } void Server_Player::addArrow(Server_Arrow *arrow) { - QMutexLocker locker(&game->gameMutex); - - arrows.insert(arrow->getId(), arrow); + arrows.insert(arrow->getId(), arrow); } bool Server_Player::deleteArrow(int arrowId) { - QMutexLocker locker(&game->gameMutex); - - Server_Arrow *arrow = arrows.value(arrowId, 0); - if (!arrow) - return false; - arrows.remove(arrowId); - delete arrow; - return true; + Server_Arrow *arrow = arrows.value(arrowId, 0); + if (!arrow) + return false; + arrows.remove(arrowId); + delete arrow; + return true; } void Server_Player::addCounter(Server_Counter *counter) { - QMutexLocker locker(&game->gameMutex); - - counters.insert(counter->getId(), counter); + counters.insert(counter->getId(), counter); } -bool Server_Player::deleteCounter(int counterId) +Response::ResponseCode Server_Player::drawCards(GameEventStorage &ges, int number) { - QMutexLocker locker(&game->gameMutex); - - Server_Counter *counter = counters.value(counterId, 0); - if (!counter) - return false; - counters.remove(counterId); - delete counter; - return true; -} - -ResponseCode Server_Player::drawCards(CommandContainer *cont, int number) -{ - QMutexLocker locker(&game->gameMutex); - - Server_CardZone *deckZone = zones.value("deck"); - Server_CardZone *handZone = zones.value("hand"); - if (deckZone->cards.size() < number) - number = deckZone->cards.size(); - - QList cardListPrivate; - QList cardListOmniscient; - for (int i = 0; i < number; ++i) { - Server_Card *card = deckZone->cards.takeFirst(); - handZone->cards.append(card); - lastDrawList.append(card->getId()); - cardListPrivate.append(new ServerInfo_Card(card->getId(), card->getName())); - cardListOmniscient.append(new ServerInfo_Card(card->getId(), card->getName())); - } - cont->enqueueGameEventPrivate(new Event_DrawCards(playerId, cardListPrivate.size(), cardListPrivate), game->getGameId()); - cont->enqueueGameEventOmniscient(new Event_DrawCards(playerId, cardListOmniscient.size(), cardListOmniscient), game->getGameId()); - cont->enqueueGameEventPublic(new Event_DrawCards(playerId, cardListPrivate.size()), game->getGameId()); - - return RespOk; -} - -ResponseCode Server_Player::undoDraw(CommandContainer *cont) -{ - QMutexLocker locker(&game->gameMutex); - - if (lastDrawList.isEmpty()) - return RespContextError; - - ResponseCode retVal; - CardToMove *cardToMove = new CardToMove(lastDrawList.takeLast()); - retVal = moveCard(cont, zones.value("hand"), QList() << cardToMove, zones.value("deck"), 0, 0, false, true); - delete cardToMove; - return retVal; -} - -ResponseCode Server_Player::moveCard(CommandContainer *cont, const QString &_startZone, const QList &_cards, int targetPlayerId, const QString &_targetZone, int x, int y) -{ - QMutexLocker locker(&game->gameMutex); - - Server_CardZone *startzone = getZones().value(_startZone); - Server_Player *targetPlayer = game->getPlayers().value(targetPlayerId); - if (!targetPlayer) - return RespNameNotFound; - Server_CardZone *targetzone = targetPlayer->getZones().value(_targetZone); - if ((!startzone) || (!targetzone)) - return RespNameNotFound; - - return moveCard(cont, startzone, _cards, targetzone, x, y); + Server_CardZone *deckZone = zones.value("deck"); + Server_CardZone *handZone = zones.value("hand"); + if (deckZone->getCards().size() < number) + number = deckZone->getCards().size(); + + Event_DrawCards eventOthers; + eventOthers.set_number(number); + Event_DrawCards eventPrivate(eventOthers); + + for (int i = 0; i < number; ++i) { + Server_Card *card = deckZone->getCard(0, NULL, true); + handZone->insertCard(card, -1, 0); + lastDrawList.append(card->getId()); + + ServerInfo_Card *cardInfo = eventPrivate.add_cards(); + cardInfo->set_id(card->getId()); + cardInfo->set_name(card->getName().toStdString()); + } + + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + + if (deckZone->getAlwaysRevealTopCard() && !deckZone->getCards().isEmpty()) { + Event_RevealCards revealEvent; + revealEvent.set_zone_name(deckZone->getName().toStdString()); + revealEvent.set_card_id(0); + deckZone->getCards().first()->getInfo(revealEvent.add_cards()); + + ges.enqueueGameEvent(revealEvent, playerId); + } + + return Response::RespOk; } class Server_Player::MoveCardCompareFunctor { private: - int x; + int x; public: - MoveCardCompareFunctor(int _x) : x(_x) { } - inline bool operator()(QPair a, QPair b) - { - if (a.second < x) { - if (b.second >= x) - return false; - else - return (a.second > b.second); - } else { - if (b.second < x) - return true; - else - return (a.second < b.second); - } - } + MoveCardCompareFunctor(int _x) : x(_x) { } + inline bool operator()(QPair a, QPair b) + { + if (a.second < x) { + if (b.second >= x) + return false; + else + return (a.second > b.second); + } else { + if (b.second < x) + return true; + else + return (a.second < b.second); + } + } }; -ResponseCode Server_Player::moveCard(CommandContainer *cont, Server_CardZone *startzone, const QList &_cards, Server_CardZone *targetzone, int x, int y, bool fixFreeSpaces, bool undoingDraw) +Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges, Server_CardZone *startzone, const QList &_cards, Server_CardZone *targetzone, int x, int y, bool fixFreeSpaces, bool undoingDraw) { - QMutexLocker locker(&game->gameMutex); - - // Disallow controller change to other zones than the table. - if (((targetzone->getType() != PublicZone) || !targetzone->hasCoords()) && (startzone->getPlayer() != targetzone->getPlayer())) - return RespContextError; - - if (!targetzone->hasCoords() && (x == -1)) - x = targetzone->cards.size(); - - QList > cardsToMove; - QMap cardProperties; - for (int i = 0; i < _cards.size(); ++i) { - int position; - Server_Card *card = startzone->getCard(_cards[i]->getCardId(), &position); - if (!card) - return RespNameNotFound; - if (!card->getAttachedCards().isEmpty() && !targetzone->isColumnEmpty(x, y)) - return RespContextError; - cardsToMove.append(QPair(card, position)); - cardProperties.insert(card, _cards[i]); - } - - MoveCardCompareFunctor cmp(startzone == targetzone ? -1 : x); - qSort(cardsToMove.begin(), cardsToMove.end(), cmp); - - bool secondHalf = false; - int xIndex = -1; - for (int cardIndex = 0; cardIndex < cardsToMove.size(); ++cardIndex) { - Server_Card *card = cardsToMove[cardIndex].first; - CardToMove *thisCardProperties = cardProperties.value(card); - int originalPosition = cardsToMove[cardIndex].second; - int position = startzone->removeCard(card); - if (startzone->getName() == "hand") { - if (undoingDraw) - lastDrawList.removeAt(lastDrawList.indexOf(card->getId())); - else if (lastDrawList.contains(card->getId())) - lastDrawList.clear(); - } - - if ((startzone == targetzone) && !startzone->hasCoords()) { - if (!secondHalf && (originalPosition < x)) { - xIndex = -1; - secondHalf = true; - } else if (secondHalf) - --xIndex; - else - ++xIndex; - } else - ++xIndex; - int newX = x + xIndex; - - // Attachment relationships can be retained when moving a card onto the opponent's table - if (startzone->getName() != targetzone->getName()) { - // Delete all attachment relationships - if (card->getParentCard()) - card->setParentCard(0); - - // Make a copy of the list because the original one gets modified during the loop - QList attachedCards = card->getAttachedCards(); - for (int i = 0; i < attachedCards.size(); ++i) - attachedCards[i]->getZone()->getPlayer()->unattachCard(cont, attachedCards[i]); - } - - if (startzone != targetzone) { - // Delete all arrows from and to the card - const QList &players = game->getPlayers().values(); - for (int i = 0; i < players.size(); ++i) { - QList arrowsToDelete; - QMapIterator arrowIterator(players[i]->getArrows()); - while (arrowIterator.hasNext()) { - Server_Arrow *arrow = arrowIterator.next().value(); - if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) - arrowsToDelete.append(arrow->getId()); - } - for (int j = 0; j < arrowsToDelete.size(); ++j) - players[i]->deleteArrow(arrowsToDelete[j]); - } - } - - if (card->getDestroyOnZoneChange() && (startzone->getName() != targetzone->getName())) { - cont->enqueueGameEventPrivate(new Event_DestroyCard(getPlayerId(), startzone->getName(), card->getId()), game->getGameId(), -1, new Context_MoveCard); - cont->enqueueGameEventOmniscient(new Event_DestroyCard(getPlayerId(), startzone->getName(), card->getId()), game->getGameId(), new Context_MoveCard); - cont->enqueueGameEventPublic(new Event_DestroyCard(getPlayerId(), startzone->getName(), card->getId()), game->getGameId(), new Context_MoveCard); - card->deleteLater(); - } else { - if (!targetzone->hasCoords()) { - y = 0; - card->resetState(); - } else - newX = targetzone->getFreeGridColumn(newX, y, card->getName()); - - targetzone->insertCard(card, newX, y); - - bool targetBeingLookedAt = (targetzone->getType() != HiddenZone) || (targetzone->getCardsBeingLookedAt() > newX) || (targetzone->getCardsBeingLookedAt() == -1); - bool sourceBeingLookedAt = (startzone->getType() != HiddenZone) || (startzone->getCardsBeingLookedAt() > position) || (startzone->getCardsBeingLookedAt() == -1); - - bool targetHiddenToPlayer = thisCardProperties->getFaceDown() || !targetBeingLookedAt; - bool targetHiddenToOthers = thisCardProperties->getFaceDown() || (targetzone->getType() != PublicZone); - bool sourceHiddenToPlayer = card->getFaceDown() || !sourceBeingLookedAt; - bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != PublicZone); - - QString privateCardName, publicCardName; - if (!(sourceHiddenToPlayer && targetHiddenToPlayer)) - privateCardName = card->getName(); - if (!(sourceHiddenToOthers && targetHiddenToOthers)) - publicCardName = card->getName(); - - int oldCardId = card->getId(); - if (thisCardProperties->getFaceDown() || (targetzone->getPlayer() != startzone->getPlayer())) - card->setId(targetzone->getPlayer()->newCardId()); - card->setFaceDown(thisCardProperties->getFaceDown()); - - // The player does not get to see which card he moved if it moves between two parts of hidden zones which - // are not being looked at. - int privateNewCardId = card->getId(); - int privateOldCardId = oldCardId; - if (!targetBeingLookedAt && !sourceBeingLookedAt) { - privateOldCardId = -1; - privateNewCardId = -1; - privateCardName = QString(); - } - int privatePosition = -1; - if (startzone->getType() == HiddenZone) - privatePosition = position; - cont->enqueueGameEventPrivate(new Event_MoveCard(getPlayerId(), privateOldCardId, privateCardName, startzone->getName(), privatePosition, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), newX, y, privateNewCardId, thisCardProperties->getFaceDown()), game->getGameId(), -1, undoingDraw ? static_cast(new Context_UndoDraw) : static_cast(new Context_MoveCard)); - cont->enqueueGameEventOmniscient(new Event_MoveCard(getPlayerId(), privateOldCardId, privateCardName, startzone->getName(), privatePosition, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), newX, y, privateNewCardId, thisCardProperties->getFaceDown()), game->getGameId(), undoingDraw ? static_cast(new Context_UndoDraw) : static_cast(new Context_MoveCard)); - - // 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() == HiddenZone) && ((startzone->getCardsBeingLookedAt() > position) || (startzone->getCardsBeingLookedAt() == -1))) - || (startzone->getType() == PublicZone) - ) - position = -1; - if ((targetzone->getType() == HiddenZone) && ((targetzone->getCardsBeingLookedAt() > newX) || (targetzone->getCardsBeingLookedAt() == -1))) - newX = -1; - - if ((startzone->getType() == PublicZone) || (targetzone->getType() == PublicZone)) - cont->enqueueGameEventPublic(new Event_MoveCard(getPlayerId(), oldCardId, publicCardName, startzone->getName(), position, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), newX, y, card->getId(), thisCardProperties->getFaceDown()), game->getGameId(), undoingDraw ? static_cast(new Context_UndoDraw) : static_cast(new Context_MoveCard)); - else - cont->enqueueGameEventPublic(new Event_MoveCard(getPlayerId(), -1, QString(), startzone->getName(), position, targetzone->getPlayer()->getPlayerId(), targetzone->getName(), newX, y, -1, false), game->getGameId(), undoingDraw ? static_cast(new Context_UndoDraw) : static_cast(new Context_MoveCard)); - - if (thisCardProperties->getTapped()) - setCardAttrHelper(cont, targetzone->getName(), card->getId(), "tapped", "1"); - if (!thisCardProperties->getPT().isEmpty() && !thisCardProperties->getFaceDown()) - setCardAttrHelper(cont, targetzone->getName(), card->getId(), "pt", thisCardProperties->getPT()); - } - } - if (startzone->hasCoords() && fixFreeSpaces) - startzone->fixFreeSpaces(cont); - - return RespOk; + // Disallow controller change to other zones than the table. + if (((targetzone->getType() != ServerInfo_Zone::PublicZone) || !targetzone->hasCoords()) && (startzone->getPlayer() != targetzone->getPlayer())) + return Response::RespContextError; + + if (!targetzone->hasCoords() && (x == -1)) + x = targetzone->getCards().size(); + + QList > cardsToMove; + QMap cardProperties; + QSet cardIdsToMove; + for (int i = 0; i < _cards.size(); ++i) { + // The same card being moved twice would lead to undefined behaviour. + if (cardIdsToMove.contains(_cards[i]->card_id())) + continue; + cardIdsToMove.insert(_cards[i]->card_id()); + + // Consistency checks. In case the command contains illegal moves, try to resolve the legal ones still. + int position; + Server_Card *card = startzone->getCard(_cards[i]->card_id(), &position); + if (!card) + return Response::RespNameNotFound; + if (card->getParentCard()) + continue; + if (!card->getAttachedCards().isEmpty() && !targetzone->isColumnEmpty(x, y)) + continue; + cardsToMove.append(QPair(card, position)); + cardProperties.insert(card, _cards[i]); + } + // In case all moves were filtered out, abort. + if (cardsToMove.isEmpty()) + return Response::RespContextError; + + MoveCardCompareFunctor cmp(startzone == targetzone ? -1 : x); + qSort(cardsToMove.begin(), cardsToMove.end(), cmp); + + bool secondHalf = false; + int xIndex = -1; + for (int cardIndex = 0; cardIndex < cardsToMove.size(); ++cardIndex) { + Server_Card *card = cardsToMove[cardIndex].first; + const CardToMove *thisCardProperties = cardProperties.value(card); + bool faceDown = thisCardProperties->has_face_down() ? thisCardProperties->face_down() : card->getFaceDown(); + if (!targetzone->hasCoords()) + faceDown = false; + + int originalPosition = cardsToMove[cardIndex].second; + int position = startzone->removeCard(card); + if (startzone->getName() == "hand") { + if (undoingDraw) + lastDrawList.removeAt(lastDrawList.indexOf(card->getId())); + else if (lastDrawList.contains(card->getId())) + lastDrawList.clear(); + } + + if ((startzone == targetzone) && !startzone->hasCoords()) { + if (!secondHalf && (originalPosition < x)) { + xIndex = -1; + secondHalf = true; + } else if (secondHalf) + --xIndex; + else + ++xIndex; + } else + ++xIndex; + int newX = x + xIndex; + + // Attachment relationships can be retained when moving a card onto the opponent's table + if (startzone->getName() != targetzone->getName()) { + // Delete all attachment relationships + if (card->getParentCard()) + card->setParentCard(0); + + // Make a copy of the list because the original one gets modified during the loop + QList attachedCards = card->getAttachedCards(); + for (int i = 0; i < attachedCards.size(); ++i) + attachedCards[i]->getZone()->getPlayer()->unattachCard(ges, attachedCards[i]); + } + + if (startzone != targetzone) { + // Delete all arrows from and to the card + const QList &players = game->getPlayers().values(); + for (int i = 0; i < players.size(); ++i) { + QList arrowsToDelete; + QMapIterator arrowIterator(players[i]->getArrows()); + while (arrowIterator.hasNext()) { + Server_Arrow *arrow = arrowIterator.next().value(); + if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card)) + arrowsToDelete.append(arrow->getId()); + } + for (int j = 0; j < arrowsToDelete.size(); ++j) + players[i]->deleteArrow(arrowsToDelete[j]); + } + } + + int publicNewX; + if (card->getDestroyOnZoneChange() && (startzone->getName() != targetzone->getName())) { + Event_DestroyCard event; + event.set_zone_name(startzone->getName().toStdString()); + event.set_card_id(card->getId()); + ges.enqueueGameEvent(event, playerId); + + card->deleteLater(); + } else { + if (!targetzone->hasCoords()) { + y = 0; + card->resetState(); + } else + newX = targetzone->getFreeGridColumn(newX, y, card->getName()); + + targetzone->insertCard(card, newX, y); + + bool targetBeingLookedAt = (targetzone->getType() != ServerInfo_Zone::HiddenZone) || (targetzone->getCardsBeingLookedAt() > newX) || (targetzone->getCardsBeingLookedAt() == -1); + bool sourceBeingLookedAt = (startzone->getType() != ServerInfo_Zone::HiddenZone) || (startzone->getCardsBeingLookedAt() > position) || (startzone->getCardsBeingLookedAt() == -1); + + bool targetHiddenToPlayer = faceDown || !targetBeingLookedAt; + bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone); + bool sourceHiddenToPlayer = card->getFaceDown() || !sourceBeingLookedAt; + bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone); + + QString privateCardName, publicCardName; + if (!(sourceHiddenToPlayer && targetHiddenToPlayer)) + privateCardName = card->getName(); + if (!(sourceHiddenToOthers && targetHiddenToOthers)) + publicCardName = card->getName(); + + int oldCardId = card->getId(); + if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) + card->setId(targetzone->getPlayer()->newCardId()); + card->setFaceDown(faceDown); + + // The player does not get to see which card he moved if it moves between two parts of hidden zones which + // are not being looked at. + int privateNewCardId = card->getId(); + int privateOldCardId = oldCardId; + if (!targetBeingLookedAt && !sourceBeingLookedAt) { + privateOldCardId = -1; + privateNewCardId = -1; + privateCardName = QString(); + } + int privatePosition = -1; + if (startzone->getType() == ServerInfo_Zone::HiddenZone) + privatePosition = position; + + publicNewX = newX; + + Event_MoveCard eventOthers; + eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId()); + eventOthers.set_start_zone(startzone->getName().toStdString()); + eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId()); + if (startzone != targetzone) + eventOthers.set_target_zone(targetzone->getName().toStdString()); + eventOthers.set_y(y); + eventOthers.set_face_down(faceDown); + + Event_MoveCard eventPrivate(eventOthers); + eventPrivate.set_card_id(privateOldCardId); + if (!privateCardName.isEmpty()) + eventPrivate.set_card_name(privateCardName.toStdString()); + eventPrivate.set_position(privatePosition); + eventPrivate.set_new_card_id(privateNewCardId); + 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) + ) + position = -1; + if ((targetzone->getType() == ServerInfo_Zone::HiddenZone) && ((targetzone->getCardsBeingLookedAt() > newX) || (targetzone->getCardsBeingLookedAt() == -1))) + publicNewX = -1; + + eventOthers.set_x(publicNewX); + eventOthers.set_position(position); + if ((startzone->getType() == ServerInfo_Zone::PublicZone) || (targetzone->getType() == ServerInfo_Zone::PublicZone)) { + eventOthers.set_card_id(oldCardId); + if (!publicCardName.isEmpty()) + eventOthers.set_card_name(publicCardName.toStdString()); + eventOthers.set_new_card_id(card->getId()); + } + + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + + if (thisCardProperties->tapped()) + setCardAttrHelper(ges, targetzone->getName(), card->getId(), AttrTapped, "1"); + QString ptString = QString::fromStdString(thisCardProperties->pt()); + if (!ptString.isEmpty() && !faceDown) + setCardAttrHelper(ges, targetzone->getName(), card->getId(), AttrPT, ptString); + } + if (startzone->getAlwaysRevealTopCard() && !startzone->getCards().isEmpty() && (originalPosition == 0)) { + Event_RevealCards revealEvent; + revealEvent.set_zone_name(startzone->getName().toStdString()); + revealEvent.set_card_id(0); + startzone->getCards().first()->getInfo(revealEvent.add_cards()); + + ges.enqueueGameEvent(revealEvent, playerId); + } + if (targetzone->getAlwaysRevealTopCard() && !targetzone->getCards().isEmpty() && (newX == 0)) { + Event_RevealCards revealEvent; + revealEvent.set_zone_name(targetzone->getName().toStdString()); + revealEvent.set_card_id(0); + targetzone->getCards().first()->getInfo(revealEvent.add_cards()); + + ges.enqueueGameEvent(revealEvent, playerId); + } + } + if (undoingDraw) + ges.setGameEventContext(Context_UndoDraw()); + else + ges.setGameEventContext(Context_MoveCard()); + + if (startzone->hasCoords() && fixFreeSpaces) + startzone->fixFreeSpaces(ges); + + return Response::RespOk; } -void Server_Player::unattachCard(CommandContainer *cont, Server_Card *card) +void Server_Player::unattachCard(GameEventStorage &ges, Server_Card *card) { - QMutexLocker locker(&game->gameMutex); - - Server_CardZone *zone = card->getZone(); - - card->setParentCard(0); - cont->enqueueGameEventPrivate(new Event_AttachCard(getPlayerId(), zone->getName(), card->getId(), -1, QString(), -1), game->getGameId()); - cont->enqueueGameEventPublic(new Event_AttachCard(getPlayerId(), zone->getName(), card->getId(), -1, QString(), -1), game->getGameId()); - - CardToMove *cardToMove = new CardToMove(card->getId()); - moveCard(cont, zone, QList() << cardToMove, zone, -1, card->getY(), card->getFaceDown()); - delete cardToMove; + Server_CardZone *zone = card->getZone(); + Server_Card *parentCard = card->getParentCard(); + card->setParentCard(0); + + Event_AttachCard event; + event.set_start_zone(zone->getName().toStdString()); + event.set_card_id(card->getId()); + ges.enqueueGameEvent(event, playerId); + + CardToMove *cardToMove = new CardToMove; + cardToMove->set_card_id(card->getId()); + moveCard(ges, zone, QList() << cardToMove, zone, -1, card->getY(), card->getFaceDown()); + delete cardToMove; + + if (parentCard->getZone()) + parentCard->getZone()->updateCardCoordinates(parentCard, parentCard->getX(), parentCard->getY()); } -ResponseCode Server_Player::setCardAttrHelper(CommandContainer *cont, const QString &zoneName, int cardId, const QString &attrName, const QString &attrValue) +Response::ResponseCode Server_Player::setCardAttrHelper(GameEventStorage &ges, const QString &zoneName, int cardId, CardAttribute attribute, const QString &attrValue) { - QMutexLocker locker(&game->gameMutex); - - Server_CardZone *zone = getZones().value(zoneName); - if (!zone) - return RespNameNotFound; - if (!zone->hasCoords()) - return RespContextError; + Server_CardZone *zone = getZones().value(zoneName); + if (!zone) + return Response::RespNameNotFound; + if (!zone->hasCoords()) + return Response::RespContextError; - QString result; - if (cardId == -1) { - QListIterator CardIterator(zone->cards); - while (CardIterator.hasNext()) { - result = CardIterator.next()->setAttribute(attrName, attrValue, true); - if (result.isNull()) - return RespInvalidCommand; - } - } else { - Server_Card *card = zone->getCard(cardId); - if (!card) - return RespNameNotFound; - result = card->setAttribute(attrName, attrValue, false); - if (result.isNull()) - return RespInvalidCommand; - } - cont->enqueueGameEventPrivate(new Event_SetCardAttr(getPlayerId(), zone->getName(), cardId, attrName, result), game->getGameId()); - cont->enqueueGameEventPublic(new Event_SetCardAttr(getPlayerId(), zone->getName(), cardId, attrName, result), game->getGameId()); - cont->enqueueGameEventOmniscient(new Event_SetCardAttr(getPlayerId(), zone->getName(), cardId, attrName, result), game->getGameId()); - return RespOk; + QString result; + if (cardId == -1) { + QListIterator CardIterator(zone->getCards()); + while (CardIterator.hasNext()) { + result = CardIterator.next()->setAttribute(attribute, attrValue, true); + if (result.isNull()) + return Response::RespInvalidCommand; + } + } else { + Server_Card *card = zone->getCard(cardId); + if (!card) + return Response::RespNameNotFound; + result = card->setAttribute(attribute, attrValue, false); + if (result.isNull()) + return Response::RespInvalidCommand; + } + + Event_SetCardAttr event; + event.set_zone_name(zone->getName().toStdString()); + if (cardId != -1) + event.set_card_id(cardId); + event.set_attribute(attribute); + event.set_attr_value(result.toStdString()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; } -void Server_Player::sendProtocolItem(ProtocolItem *item, bool deleteItem) +Response::ResponseCode Server_Player::cmdLeaveGame(const Command_LeaveGame & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/) { - QMutexLocker locker(&playerMutex); - - if (handler) - handler->sendProtocolItem(item, deleteItem); + game->removePlayer(this); + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdKickFromGame(const Command_KickFromGame &cmd, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/) +{ + if ((game->getHostId() != playerId) && !(userInfo->user_level() & ServerInfo_User::IsModerator)) + return Response::RespFunctionNotAllowed; + + if (!game->kickPlayer(cmd.player_id())) + return Response::RespNameNotFound; + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &rc, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + DeckList *newDeck; + if (cmd.has_deck_id()) { + try { + newDeck = game->getRoom()->getServer()->getDatabaseInterface()->getDeckFromDatabase(cmd.deck_id(), userInfo->id()); + } catch(Response::ResponseCode r) { + return r; + } + } else + newDeck = new DeckList(QString::fromStdString(cmd.deck())); + + if (!newDeck) + return Response::RespInternalError; + + delete deck; + deck = newDeck; + sideboardLocked = true; + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_sideboard_locked(true); + event.mutable_player_properties()->set_deck_hash(deck->getDeckHash().toStdString()); + ges.enqueueGameEvent(event, playerId); + + Context_DeckSelect context; + context.set_deck_hash(deck->getDeckHash().toStdString()); + context.set_sideboard_size(deck->getSideboardSize()); + ges.setGameEventContext(context); + + Response_DeckDownload *re = new Response_DeckDownload; + re->set_deck(deck->writeToString_Native().toStdString()); + + rc.setResponseExtension(re); + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdSetSideboardPlan(const Command_SetSideboardPlan &cmd, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + if (readyStart) + return Response::RespContextError; + if (!deck) + return Response::RespContextError; + if (sideboardLocked) + return Response::RespContextError; + + QList sideboardPlan; + for (int i = 0; i < cmd.move_list_size(); ++i) + sideboardPlan.append(cmd.move_list(i)); + deck->setCurrentSideboardPlan(sideboardPlan); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdSetSideboardLock(const Command_SetSideboardLock &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + if (readyStart) + return Response::RespContextError; + if (!deck) + return Response::RespContextError; + if (sideboardLocked == cmd.locked()) + return Response::RespContextError; + + sideboardLocked = cmd.locked(); + if (sideboardLocked) + deck->setCurrentSideboardPlan(QList()); + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_sideboard_locked(sideboardLocked); + ges.enqueueGameEvent(event, playerId); + ges.setGameEventContext(Context_SetSideboardLock()); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdConcede(const Command_Concede & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + setConceded(true); + game->removeArrowsRelatedToPlayer(ges, this); + game->unattachCards(ges, this); + clearZones(); + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_conceded(true); + ges.enqueueGameEvent(event, playerId); + ges.setGameEventContext(Context_Concede()); + + game->stopGameIfFinished(); + if (game->getGameStarted() && (game->getActivePlayer() == playerId)) + game->nextTurn(); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!deck || game->getGameStarted()) + return Response::RespContextError; + + if (readyStart == cmd.ready()) + return Response::RespContextError; + + setReadyStart(cmd.ready()); + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_ready_start(cmd.ready()); + ges.enqueueGameEvent(event, playerId); + ges.setGameEventContext(Context_ReadyStart()); + + if (cmd.ready()) + game->startGameIfReady(); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdGameSay(const Command_GameSay &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator && !game->getSpectatorsCanTalk() && !(userInfo->user_level() & ServerInfo_User::IsModerator)) + return Response::RespFunctionNotAllowed; + + Event_GameSay event; + event.set_message(cmd.message()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdShuffle(const Command_Shuffle & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_CardZone *deckZone = zones.value("deck"); + deckZone->shuffle(); + + Event_Shuffle event; + event.set_zone_name("deck"); + ges.enqueueGameEvent(event, playerId); + + if (deckZone->getAlwaysRevealTopCard() && !deckZone->getCards().isEmpty()) { + Event_RevealCards revealEvent; + revealEvent.set_zone_name(deckZone->getName().toStdString()); + revealEvent.set_card_id(0); + deckZone->getCards().first()->getInfo(revealEvent.add_cards()); + + ges.enqueueGameEvent(revealEvent, playerId); + } + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdMulligan(const Command_Mulligan & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_CardZone *hand = zones.value("hand"); + int number = (hand->getCards().size() <= 1) ? initialCards : hand->getCards().size() - 1; + + Server_CardZone *deck = zones.value("deck"); + while (!hand->getCards().isEmpty()) { + CardToMove *cardToMove = new CardToMove; + cardToMove->set_card_id(hand->getCards().first()->getId()); + moveCard(ges, hand, QList() << cardToMove, deck, 0, 0, false); + delete cardToMove; + } + + deck->shuffle(); + ges.enqueueGameEvent(Event_Shuffle(), playerId); + + drawCards(ges, number); + + if (number == initialCards) + number = -1; + + Context_Mulligan context; + context.set_number(number); + ges.setGameEventContext(context); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdRollDie(const Command_RollDie &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + if (conceded) + return Response::RespContextError; + + Event_RollDie event; + event.set_sides(cmd.sides()); + event.set_value(rng->rand(1, cmd.sides())); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdDrawCards(const Command_DrawCards &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + return drawCards(ges, cmd.number()); +} + +Response::ResponseCode Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + if (lastDrawList.isEmpty()) + return Response::RespContextError; + + Response::ResponseCode retVal; + CardToMove *cardToMove = new CardToMove; + cardToMove->set_card_id(lastDrawList.takeLast()); + retVal = moveCard(ges, zones.value("hand"), QList() << cardToMove, zones.value("deck"), 0, 0, false, true); + delete cardToMove; + + return retVal; +} + +Response::ResponseCode Server_Player::cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_Player *startPlayer = game->getPlayers().value(cmd.has_start_player_id() ? cmd.start_player_id() : playerId); + if (!startPlayer) + return Response::RespNameNotFound; + Server_CardZone *startZone = startPlayer->getZones().value(QString::fromStdString(cmd.start_zone())); + if (!startZone) + return Response::RespNameNotFound; + + if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId))) + return Response::RespContextError; + + Server_Player *targetPlayer = game->getPlayers().value(cmd.target_player_id()); + if (!targetPlayer) + return Response::RespNameNotFound; + Server_CardZone *targetZone = targetPlayer->getZones().value(QString::fromStdString(cmd.target_zone())); + if (!targetZone) + return Response::RespNameNotFound; + + if ((startPlayer != this) && (targetPlayer != this)) + return Response::RespContextError; + + QList cardsToMove; + for (int i = 0; i < cmd.cards_to_move().card_size(); ++i) + cardsToMove.append(&cmd.cards_to_move().card(i)); + + return moveCard(ges, startZone, cardsToMove, targetZone, cmd.x(), cmd.y()); +} + +Response::ResponseCode Server_Player::cmdFlipCard(const Command_FlipCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_CardZone *zone = zones.value(QString::fromStdString(cmd.zone())); + if (!zone) + return Response::RespNameNotFound; + if (!zone->hasCoords()) + return Response::RespContextError; + + Server_Card *card = zone->getCard(cmd.card_id()); + if (!card) + return Response::RespNameNotFound; + + const bool faceDown = cmd.face_down(); + if (faceDown == card->getFaceDown()) + return Response::RespContextError; + + card->setFaceDown(faceDown); + + Event_FlipCard event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + if (!faceDown) + event.set_card_name(card->getName().toStdString()); + event.set_face_down(faceDown); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdAttachCard(const Command_AttachCard &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_CardZone *startzone = zones.value(QString::fromStdString(cmd.start_zone())); + if (!startzone) + return Response::RespNameNotFound; + + Server_Card *card = startzone->getCard(cmd.card_id()); + if (!card) + return Response::RespNameNotFound; + + Server_Player *targetPlayer = 0; + Server_CardZone *targetzone = 0; + Server_Card *targetCard = 0; + + if (cmd.has_target_player_id()) { + targetPlayer = game->getPlayers().value(cmd.target_player_id()); + if (!targetPlayer) + return Response::RespNameNotFound; + } else if (!card->getParentCard()) + return Response::RespContextError; + if (targetPlayer) + targetzone = targetPlayer->getZones().value(QString::fromStdString(cmd.target_zone())); + if (targetzone) { + // This is currently enough to make sure cards don't get attached to a card that is not on the table. + // Possibly a flag will have to be introduced for this sometime. + if (!targetzone->hasCoords()) + return Response::RespContextError; + if (cmd.has_target_card_id()) + targetCard = targetzone->getCard(cmd.target_card_id()); + if (targetCard) { + if (targetCard->getParentCard()) + return Response::RespContextError; + } else + return Response::RespNameNotFound; + } + if (!startzone->hasCoords()) + return Response::RespContextError; + + // Get all arrows pointing to or originating from the card being attached and delete them. + QMapIterator playerIterator(game->getPlayers()); + while (playerIterator.hasNext()) { + Server_Player *p = playerIterator.next().value(); + QList arrows = p->getArrows().values(); + QList toDelete; + for (int i = 0; i < arrows.size(); ++i) { + Server_Arrow *a = arrows[i]; + Server_Card *tCard = qobject_cast(a->getTargetItem()); + if ((tCard == card) || (a->getStartCard() == card)) + toDelete.append(a); + } + for (int i = 0; i < toDelete.size(); ++i) { + Event_DeleteArrow event; + event.set_arrow_id(toDelete[i]->getId()); + ges.enqueueGameEvent(event, p->getPlayerId()); + p->deleteArrow(toDelete[i]->getId()); + } + } + + if (targetCard) { + // Unattach all cards attached to the card being attached. + // Make a copy of the list because its contents change during the loop otherwise. + QList attachedList = card->getAttachedCards(); + for (int i = 0; i < attachedList.size(); ++i) + attachedList[i]->getZone()->getPlayer()->unattachCard(ges, attachedList[i]); + + card->setParentCard(targetCard); + const int oldX = card->getX(); + card->setCoords(-1, card->getY()); + startzone->updateCardCoordinates(card, oldX, card->getY()); + + if (targetzone->isColumnStacked(targetCard->getX(), targetCard->getY())) { + CardToMove *cardToMove = new CardToMove; + cardToMove->set_card_id(targetCard->getId()); + targetPlayer->moveCard(ges, targetzone, QList() << cardToMove, targetzone, targetzone->getFreeGridColumn(-2, targetCard->getY(), targetCard->getName()), targetCard->getY(), targetCard->getFaceDown()); + delete cardToMove; + } + + Event_AttachCard event; + event.set_start_zone(startzone->getName().toStdString()); + event.set_card_id(card->getId()); + event.set_target_player_id(targetPlayer->getPlayerId()); + event.set_target_zone(targetzone->getName().toStdString()); + event.set_target_card_id(targetCard->getId()); + ges.enqueueGameEvent(event, playerId); + + startzone->fixFreeSpaces(ges); + } else + unattachCard(ges, card); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_CardZone *zone = zones.value(QString::fromStdString(cmd.zone())); + if (!zone) + return Response::RespNameNotFound; + + QString cardName = QString::fromStdString(cmd.card_name()); + int x = cmd.x(); + int y = cmd.y(); + if (zone->hasCoords()) + x = zone->getFreeGridColumn(x, y, cardName); + if (x < 0) + x = 0; + if (y < 0) + y = 0; + + Server_Card *card = new Server_Card(cardName, newCardId(), x, y); + card->moveToThread(thread()); + card->setPT(QString::fromStdString(cmd.pt())); + card->setColor(QString::fromStdString(cmd.color())); + card->setAnnotation(QString::fromStdString(cmd.annotation())); + card->setDestroyOnZoneChange(cmd.destroy_on_zone_change()); + + zone->insertCard(card, x, y); + + Event_CreateToken event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + event.set_card_name(card->getName().toStdString()); + event.set_color(card->getColor().toStdString()); + event.set_pt(card->getPT().toStdString()); + event.set_annotation(card->getAnnotation().toStdString()); + event.set_destroy_on_zone_change(card->getDestroyOnZoneChange()); + event.set_x(x); + event.set_y(y); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_Player *startPlayer = game->getPlayers().value(cmd.start_player_id()); + Server_Player *targetPlayer = game->getPlayers().value(cmd.target_player_id()); + if (!startPlayer || !targetPlayer) + return Response::RespNameNotFound; + QString startZoneName = QString::fromStdString(cmd.start_zone()); + Server_CardZone *startZone = startPlayer->getZones().value(startZoneName); + bool playerTarget = !cmd.has_target_zone(); + Server_CardZone *targetZone = 0; + if (!playerTarget) + targetZone = targetPlayer->getZones().value(QString::fromStdString(cmd.target_zone())); + if (!startZone || (!targetZone && !playerTarget)) + return Response::RespNameNotFound; + if (startZone->getType() != ServerInfo_Zone::PublicZone) + return Response::RespContextError; + Server_Card *startCard = startZone->getCard(cmd.start_card_id()); + if (!startCard) + return Response::RespNameNotFound; + Server_Card *targetCard = 0; + if (!playerTarget) { + if (targetZone->getType() != ServerInfo_Zone::PublicZone) + return Response::RespContextError; + targetCard = targetZone->getCard(cmd.target_card_id()); + } + + Server_ArrowTarget *targetItem; + if (playerTarget) + targetItem = targetPlayer; + else + targetItem = targetCard; + if (!targetItem) + return Response::RespNameNotFound; + + QMapIterator arrowIterator(arrows); + while (arrowIterator.hasNext()) { + Server_Arrow *temp = arrowIterator.next().value(); + if ((temp->getStartCard() == startCard) && (temp->getTargetItem() == targetItem)) + return Response::RespContextError; + } + + Server_Arrow *arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color()); + addArrow(arrow); + + Event_CreateArrow event; + ServerInfo_Arrow *arrowInfo = event.mutable_arrow_info(); + arrowInfo->set_id(arrow->getId()); + arrowInfo->set_start_player_id(startPlayer->getPlayerId()); + arrowInfo->set_start_zone(startZoneName.toStdString()); + arrowInfo->set_start_card_id(startCard->getId()); + arrowInfo->set_target_player_id(targetPlayer->getPlayerId()); + if (!playerTarget) { + arrowInfo->set_target_zone(cmd.target_zone()); + arrowInfo->set_target_card_id(cmd.target_card_id()); + } + arrowInfo->mutable_arrow_color()->CopyFrom(cmd.arrow_color()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdDeleteArrow(const Command_DeleteArrow &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + if (!deleteArrow(cmd.arrow_id())) + return Response::RespNameNotFound; + + Event_DeleteArrow event; + event.set_arrow_id(cmd.arrow_id()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdSetCardAttr(const Command_SetCardAttr &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + return setCardAttrHelper(ges, QString::fromStdString(cmd.zone()), cmd.card_id(), cmd.attribute(), QString::fromStdString(cmd.attr_value())); +} + +Response::ResponseCode Server_Player::cmdSetCardCounter(const Command_SetCardCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_CardZone *zone = zones.value(QString::fromStdString(cmd.zone())); + if (!zone) + return Response::RespNameNotFound; + if (!zone->hasCoords()) + return Response::RespContextError; + + Server_Card *card = zone->getCard(cmd.card_id()); + if (!card) + return Response::RespNameNotFound; + + card->setCounter(cmd.counter_id(), cmd.counter_value()); + + Event_SetCardCounter event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + event.set_counter_id(cmd.counter_id()); + event.set_counter_value(cmd.counter_value()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdIncCardCounter(const Command_IncCardCounter &cmd, ResponseContainer &/*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_CardZone *zone = zones.value(QString::fromStdString(cmd.zone())); + if (!zone) + return Response::RespNameNotFound; + if (!zone->hasCoords()) + return Response::RespContextError; + + Server_Card *card = zone->getCard(cmd.card_id()); + if (!card) + return Response::RespNameNotFound; + + int newValue = card->getCounter(cmd.counter_id()) + cmd.counter_delta(); + card->setCounter(cmd.counter_id(), newValue); + + Event_SetCardCounter event; + event.set_zone_name(zone->getName().toStdString()); + event.set_card_id(card->getId()); + event.set_counter_id(cmd.counter_id()); + event.set_counter_value(newValue); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_Counter *c = counters.value(cmd.counter_id(), 0); + if (!c) + return Response::RespNameNotFound; + + c->setCount(c->getCount() + cmd.delta()); + + Event_SetCounter event; + event.set_counter_id(c->getId()); + event.set_value(c->getCount()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdCreateCounter(const Command_CreateCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_Counter *c = new Server_Counter(newCounterId(), QString::fromStdString(cmd.counter_name()), cmd.counter_color(), cmd.radius(), cmd.value()); + addCounter(c); + + Event_CreateCounter event; + ServerInfo_Counter *counterInfo = event.mutable_counter_info(); + counterInfo->set_id(c->getId()); + counterInfo->set_name(c->getName().toStdString()); + counterInfo->mutable_counter_color()->CopyFrom(cmd.counter_color()); + counterInfo->set_radius(c->getRadius()); + counterInfo->set_count(c->getCount()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_Counter *c = counters.value(cmd.counter_id(), 0);; + if (!c) + return Response::RespNameNotFound; + + c->setCount(cmd.value()); + + Event_SetCounter event; + event.set_counter_id(c->getId()); + event.set_value(c->getCount()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_Counter *counter = counters.value(cmd.counter_id(), 0); + if (!counter) + return Response::RespNameNotFound; + counters.remove(cmd.counter_id()); + delete counter; + + Event_DelCounter event; + event.set_counter_id(cmd.counter_id()); + ges.enqueueGameEvent(event, playerId); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdNextTurn(const Command_NextTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + game->nextTurn(); + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdSetActivePhase(const Command_SetActivePhase &cmd, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + if (game->getActivePlayer() != playerId) + return Response::RespContextError; + game->setActivePhase(cmd.phase()); + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, GameEventStorage &ges) +{ + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + + Server_Player *otherPlayer = game->getPlayers().value(cmd.player_id()); + if (!otherPlayer) + return Response::RespNameNotFound; + Server_CardZone *zone = otherPlayer->getZones().value(QString::fromStdString(cmd.zone_name())); + if (!zone) + return Response::RespNameNotFound; + if (!((zone->getType() == ServerInfo_Zone::PublicZone) || (this == otherPlayer))) + return Response::RespContextError; + + int numberCards = cmd.number_cards(); + const QList &cards = zone->getCards(); + + Response_DumpZone *re = new Response_DumpZone; + ServerInfo_Zone *zoneInfo = re->mutable_zone_info(); + zoneInfo->set_name(zone->getName().toStdString()); + zoneInfo->set_type(zone->getType()); + zoneInfo->set_with_coords(zone->hasCoords()); + zoneInfo->set_card_count(numberCards < cards.size() ? cards.size() : numberCards); + + for (int i = 0; (i < cards.size()) && (i < numberCards || numberCards == -1); ++i) { + Server_Card *card = cards[i]; + QString displayedName = card->getFaceDown() ? QString() : card->getName(); + ServerInfo_Card *cardInfo = zoneInfo->add_card_list(); + cardInfo->set_name(displayedName.toStdString()); + if (zone->getType() == ServerInfo_Zone::HiddenZone) + cardInfo->set_id(i); + else { + cardInfo->set_id(card->getId()); + cardInfo->set_x(card->getX()); + cardInfo->set_y(card->getY()); + cardInfo->set_face_down(card->getFaceDown()); + cardInfo->set_tapped(card->getTapped()); + cardInfo->set_attacking(card->getAttacking()); + cardInfo->set_color(card->getColor().toStdString()); + cardInfo->set_pt(card->getPT().toStdString()); + cardInfo->set_annotation(card->getAnnotation().toStdString()); + cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); + cardInfo->set_doesnt_untap(card->getDoesntUntap()); + + QMapIterator cardCounterIterator(card->getCounters()); + while (cardCounterIterator.hasNext()) { + cardCounterIterator.next(); + ServerInfo_CardCounter *counterInfo = cardInfo->add_counter_list(); + counterInfo->set_id(cardCounterIterator.key()); + counterInfo->set_value(cardCounterIterator.value()); + } + + if (card->getParentCard()) { + cardInfo->set_attach_player_id(card->getParentCard()->getZone()->getPlayer()->getPlayerId()); + cardInfo->set_attach_zone(card->getParentCard()->getZone()->getName().toStdString()); + cardInfo->set_attach_card_id(card->getParentCard()->getId()); + } + } + } + if (zone->getType() == ServerInfo_Zone::HiddenZone) { + zone->setCardsBeingLookedAt(numberCards); + + Event_DumpZone event; + event.set_zone_owner_id(otherPlayer->getPlayerId()); + event.set_zone_name(zone->getName().toStdString()); + event.set_number_cards(numberCards); + ges.enqueueGameEvent(event, playerId); + } + rc.setResponseExtension(re); + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdStopDumpZone(const Command_StopDumpZone &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_Player *otherPlayer = game->getPlayers().value(cmd.player_id()); + if (!otherPlayer) + return Response::RespNameNotFound; + Server_CardZone *zone = otherPlayer->getZones().value(QString::fromStdString(cmd.zone_name())); + if (!zone) + return Response::RespNameNotFound; + + if (zone->getType() == ServerInfo_Zone::HiddenZone) { + zone->setCardsBeingLookedAt(0); + + Event_StopDumpZone event; + event.set_zone_owner_id(cmd.player_id()); + event.set_zone_name(zone->getName().toStdString()); + ges.enqueueGameEvent(event, playerId); + } + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) +{ + if (spectator) + return Response::RespFunctionNotAllowed; + + if (!game->getGameStarted()) + return Response::RespGameNotStarted; + if (conceded) + return Response::RespContextError; + + Server_Player *otherPlayer = 0; + if (cmd.has_player_id()) { + otherPlayer = game->getPlayers().value(cmd.player_id()); + if (!otherPlayer) + return Response::RespNameNotFound; + } + Server_CardZone *zone = zones.value(QString::fromStdString(cmd.zone_name())); + if (!zone) + return Response::RespNameNotFound; + + QList cardsToReveal; + if (!cmd.has_card_id()) + cardsToReveal = zone->getCards(); + else if (cmd.card_id() == -2) { + if (zone->getCards().isEmpty()) + return Response::RespContextError; + cardsToReveal.append(zone->getCards().at(rng->rand(0, zone->getCards().size() - 1))); + } else { + Server_Card *card = zone->getCard(cmd.card_id()); + if (!card) + return Response::RespNameNotFound; + cardsToReveal.append(card); + } + + Event_RevealCards eventOthers; + eventOthers.set_grant_write_access(cmd.grant_write_access()); + eventOthers.set_zone_name(zone->getName().toStdString()); + if (cmd.has_card_id()) + eventOthers.set_card_id(cmd.card_id()); + if (cmd.has_player_id()) + eventOthers.set_other_player_id(cmd.player_id()); + + Event_RevealCards eventPrivate(eventOthers); + + for (int i = 0; i < cardsToReveal.size(); ++i) { + Server_Card *card = cardsToReveal[i]; + ServerInfo_Card *cardInfo = eventPrivate.add_cards(); + + cardInfo->set_id(card->getId()); + cardInfo->set_name(card->getName().toStdString()); + cardInfo->set_x(card->getX()); + cardInfo->set_y(card->getY()); + cardInfo->set_face_down(card->getFaceDown()); + cardInfo->set_tapped(card->getTapped()); + cardInfo->set_attacking(card->getAttacking()); + cardInfo->set_color(card->getColor().toStdString()); + cardInfo->set_pt(card->getPT().toStdString()); + cardInfo->set_annotation(card->getAnnotation().toStdString()); + cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); + cardInfo->set_doesnt_untap(card->getDoesntUntap()); + + QMapIterator cardCounterIterator(card->getCounters()); + while (cardCounterIterator.hasNext()) { + cardCounterIterator.next(); + ServerInfo_CardCounter *counterInfo = cardInfo->add_counter_list(); + counterInfo->set_id(cardCounterIterator.key()); + counterInfo->set_value(cardCounterIterator.value()); + } + + if (card->getParentCard()) { + cardInfo->set_attach_player_id(card->getParentCard()->getZone()->getPlayer()->getPlayerId()); + cardInfo->set_attach_zone(card->getParentCard()->getZone()->getName().toStdString()); + cardInfo->set_attach_card_id(card->getParentCard()->getId()); + } + } + + if (cmd.has_player_id()) { + if (cmd.grant_write_access()) + zone->addWritePermission(cmd.player_id()); + + ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, cmd.player_id()); + ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers); + } else { + if (cmd.grant_write_access()) { + const QList &playerIds = game->getPlayers().keys(); + for (int i = 0; i < playerIds.size(); ++i) + zone->addWritePermission(playerIds[i]); + } + + ges.enqueueGameEvent(eventPrivate, playerId); + } + + return Response::RespOk; +} + +Response::ResponseCode Server_Player::cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer & /* rc */, GameEventStorage &ges) +{ + Server_CardZone *zone = zones.value(QString::fromStdString(cmd.zone_name())); + if (!zone) + return Response::RespNameNotFound; + + Event_ChangeZoneProperties event; + event.set_zone_name(cmd.zone_name()); + + if (cmd.has_always_reveal_top_card()) { + if (zone->getAlwaysRevealTopCard() == cmd.always_reveal_top_card()) + return Response::RespContextError; + zone->setAlwaysRevealTopCard(cmd.always_reveal_top_card()); + event.set_always_reveal_top_card(cmd.always_reveal_top_card()); + + ges.enqueueGameEvent(event, playerId); + + if (!zone->getCards().isEmpty() && cmd.always_reveal_top_card()) { + Event_RevealCards revealEvent; + revealEvent.set_zone_name(zone->getName().toStdString()); + revealEvent.set_card_id(0); + zone->getCards().first()->getInfo(revealEvent.add_cards()); + + ges.enqueueGameEvent(revealEvent, playerId); + } + return Response::RespOk; + } else + return Response::RespContextError; +} + +Response::ResponseCode Server_Player::processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges) +{ + switch ((GameCommand::GameCommandType) getPbExtension(command)) { + case GameCommand::KICK_FROM_GAME: return cmdKickFromGame(command.GetExtension(Command_KickFromGame::ext), rc, ges); break; + case GameCommand::LEAVE_GAME: return cmdLeaveGame(command.GetExtension(Command_LeaveGame::ext), rc, ges); break; + case GameCommand::GAME_SAY: return cmdGameSay(command.GetExtension(Command_GameSay::ext), rc, ges); break; + case GameCommand::SHUFFLE: return cmdShuffle(command.GetExtension(Command_Shuffle::ext), rc, ges); break; + case GameCommand::MULLIGAN: return cmdMulligan(command.GetExtension(Command_Mulligan::ext), rc, ges); break; + case GameCommand::ROLL_DIE: return cmdRollDie(command.GetExtension(Command_RollDie::ext), rc, ges); break; + case GameCommand::DRAW_CARDS: return cmdDrawCards(command.GetExtension(Command_DrawCards::ext), rc, ges); break; + case GameCommand::UNDO_DRAW: return cmdUndoDraw(command.GetExtension(Command_UndoDraw::ext), rc, ges); break; + case GameCommand::FLIP_CARD: return cmdFlipCard(command.GetExtension(Command_FlipCard::ext), rc, ges); break; + case GameCommand::ATTACH_CARD: return cmdAttachCard(command.GetExtension(Command_AttachCard::ext), rc, ges); break; + case GameCommand::CREATE_TOKEN: return cmdCreateToken(command.GetExtension(Command_CreateToken::ext), rc, ges); break; + case GameCommand::CREATE_ARROW: return cmdCreateArrow(command.GetExtension(Command_CreateArrow::ext), rc, ges); break; + case GameCommand::DELETE_ARROW: return cmdDeleteArrow(command.GetExtension(Command_DeleteArrow::ext), rc, ges); break; + case GameCommand::SET_CARD_ATTR: return cmdSetCardAttr(command.GetExtension(Command_SetCardAttr::ext), rc, ges); break; + case GameCommand::SET_CARD_COUNTER: return cmdSetCardCounter(command.GetExtension(Command_SetCardCounter::ext), rc, ges); break; + case GameCommand::INC_CARD_COUNTER: return cmdIncCardCounter(command.GetExtension(Command_IncCardCounter::ext), rc, ges); break; + case GameCommand::READY_START: return cmdReadyStart(command.GetExtension(Command_ReadyStart::ext), rc, ges); break; + case GameCommand::CONCEDE: return cmdConcede(command.GetExtension(Command_Concede::ext), rc, ges); break; + case GameCommand::INC_COUNTER: return cmdIncCounter(command.GetExtension(Command_IncCounter::ext), rc, ges); break; + case GameCommand::CREATE_COUNTER: return cmdCreateCounter(command.GetExtension(Command_CreateCounter::ext), rc, ges); break; + case GameCommand::SET_COUNTER: return cmdSetCounter(command.GetExtension(Command_SetCounter::ext), rc, ges); break; + case GameCommand::DEL_COUNTER: return cmdDelCounter(command.GetExtension(Command_DelCounter::ext), rc, ges); break; + case GameCommand::NEXT_TURN: return cmdNextTurn(command.GetExtension(Command_NextTurn::ext), rc, ges); break; + case GameCommand::SET_ACTIVE_PHASE: return cmdSetActivePhase(command.GetExtension(Command_SetActivePhase::ext), rc, ges); break; + case GameCommand::DUMP_ZONE: return cmdDumpZone(command.GetExtension(Command_DumpZone::ext), rc, ges); break; + case GameCommand::STOP_DUMP_ZONE: return cmdStopDumpZone(command.GetExtension(Command_StopDumpZone::ext), rc, ges); break; + case GameCommand::REVEAL_CARDS: return cmdRevealCards(command.GetExtension(Command_RevealCards::ext), rc, ges); break; + case GameCommand::MOVE_CARD: return cmdMoveCard(command.GetExtension(Command_MoveCard::ext), rc, ges); break; + case GameCommand::SET_SIDEBOARD_PLAN: return cmdSetSideboardPlan(command.GetExtension(Command_SetSideboardPlan::ext), rc, ges); break; + case GameCommand::DECK_SELECT: return cmdDeckSelect(command.GetExtension(Command_DeckSelect::ext), rc, ges); break; + case GameCommand::SET_SIDEBOARD_LOCK: return cmdSetSideboardLock(command.GetExtension(Command_SetSideboardLock::ext), rc, ges); break; + case GameCommand::CHANGE_ZONE_PROPERTIES: return cmdChangeZoneProperties(command.GetExtension(Command_ChangeZoneProperties::ext), rc, ges); break; + default: return Response::RespInvalidCommand; + } +} + +void Server_Player::sendGameEvent(const GameEventContainer &cont) +{ + QMutexLocker locker(&playerMutex); + + if (userInterface) + userInterface->sendProtocolItem(cont); +} + +void Server_Player::setUserInterface(Server_AbstractUserInterface *_userInterface) +{ + playerMutex.lock(); + userInterface = _userInterface; + playerMutex.unlock(); + + pingTime = _userInterface ? 0 : -1; + + Event_PlayerPropertiesChanged event; + event.mutable_player_properties()->set_ping_seconds(pingTime); + + GameEventStorage ges; + ges.setGameEventContext(Context_ConnectionStateChanged()); + ges.enqueueGameEvent(event, playerId); + ges.sendToGame(game); +} + +void Server_Player::disconnectClient() +{ + if (!(userInfo->user_level() & ServerInfo_User::IsRegistered) || spectator) + game->removePlayer(this); + else + setUserInterface(0); +} + +void Server_Player::getInfo(ServerInfo_Player *info, Server_Player *playerWhosAsking, bool omniscient, bool withUserInfo) +{ + getProperties(*info->mutable_properties(), withUserInfo); + if (playerWhosAsking == this) + if (deck) + info->set_deck_list(deck->writeToString_Native().toStdString()); + + QMapIterator arrowIterator(arrows); + while (arrowIterator.hasNext()) + arrowIterator.next().value()->getInfo(info->add_arrow_list()); + + QMapIterator counterIterator(counters); + while (counterIterator.hasNext()) + counterIterator.next().value()->getInfo(info->add_counter_list()); + + QMapIterator zoneIterator(zones); + while (zoneIterator.hasNext()) + zoneIterator.next().value()->getInfo(info->add_zone_list(), playerWhosAsking, omniscient); } diff --git a/common/server_player.h b/common/server_player.h index 64f99add2..873a4a761 100644 --- a/common/server_player.h +++ b/common/server_player.h @@ -2,11 +2,14 @@ #define PLAYER_H #include "server_arrowtarget.h" +#include "serverinfo_user_container.h" #include #include #include #include -#include "protocol_datastructures.h" + +#include "pb/response.pb.h" +#include "pb/card_attributes.pb.h" class DeckList; class Server_Game; @@ -14,79 +17,149 @@ class Server_CardZone; class Server_Counter; class Server_Arrow; class Server_Card; -class Server_ProtocolHandler; -class ProtocolItem; +class Server_AbstractUserInterface; class ServerInfo_User; +class ServerInfo_Player; class ServerInfo_PlayerProperties; class CommandContainer; +class CardToMove; +class GameEventContainer; +class GameEventStorage; +class ResponseContainer; +class GameCommand; -class Server_Player : public Server_ArrowTarget { - Q_OBJECT +class Command_KickFromGame; +class Command_LeaveGame; +class Command_GameSay; +class Command_Shuffle; +class Command_Mulligan; +class Command_RollDie; +class Command_DrawCards; +class Command_UndoDraw; +class Command_FlipCard; +class Command_AttachCard; +class Command_CreateToken; +class Command_CreateArrow; +class Command_DeleteArrow; +class Command_SetCardAttr; +class Command_SetCardCounter; +class Command_IncCardCounter; +class Command_ReadyStart; +class Command_Concede; +class Command_IncCounter; +class Command_CreateCounter; +class Command_SetCounter; +class Command_DelCounter; +class Command_NextTurn; +class Command_SetActivePhase; +class Command_DumpZone; +class Command_StopDumpZone; +class Command_RevealCards; +class Command_MoveCard; +class Command_SetSideboardPlan; +class Command_DeckSelect; +class Command_SetSideboardLock; +class Command_ChangeZoneProperties; + +class Server_Player : public Server_ArrowTarget, public ServerInfo_User_Container { + Q_OBJECT private: - mutable QMutex playerMutex; - class MoveCardCompareFunctor; - Server_Game *game; - Server_ProtocolHandler *handler; - ServerInfo_User *userInfo; - DeckList *deck; - QMap zones; - QMap counters; - QMap arrows; - QList lastDrawList; - int playerId; - bool spectator; - int initialCards; - int nextCardId; - bool readyStart; - bool conceded; + class MoveCardCompareFunctor; + Server_Game *game; + Server_AbstractUserInterface *userInterface; + DeckList *deck; + QMap zones; + QMap counters; + QMap arrows; + QList lastDrawList; + int pingTime; + int playerId; + bool spectator; + int initialCards; + int nextCardId; + bool readyStart; + bool conceded; + bool sideboardLocked; public: - Server_Player(Server_Game *_game, int _playerId, ServerInfo_User *_userInfo, bool _spectator, Server_ProtocolHandler *_handler); - ~Server_Player(); - void prepareDestroy(); - void moveToThread(QThread *thread); - Server_ProtocolHandler *getProtocolHandler() const { return handler; } - void setProtocolHandler(Server_ProtocolHandler *_handler) { playerMutex.lock(); handler = _handler; playerMutex.unlock(); } - - void setPlayerId(int _id) { playerId = _id; } - int getInitialCards() const { return initialCards; } - void setInitialCards(int _initialCards) { initialCards = _initialCards; } - bool getReadyStart() const { return readyStart; } - void setReadyStart(bool _readyStart) { readyStart = _readyStart; } - int getPlayerId() const { return playerId; } - bool getSpectator() const { return spectator; } - bool getConceded() const { return conceded; } - void setConceded(bool _conceded) { conceded = _conceded; } - ServerInfo_User *getUserInfo() const { return userInfo; } - void setDeck(DeckList *_deck); - DeckList *getDeck() const { return deck; } - Server_Game *getGame() const { return game; } - const QMap &getZones() const { return zones; } - const QMap &getCounters() const { return counters; } - const QMap &getArrows() const { return arrows; } + mutable QMutex playerMutex; + Server_Player(Server_Game *_game, int _playerId, const ServerInfo_User &_userInfo, bool _spectator, Server_AbstractUserInterface *_handler); + ~Server_Player(); + void prepareDestroy(); + Server_AbstractUserInterface *getUserInterface() const { return userInterface; } + void setUserInterface(Server_AbstractUserInterface *_userInterface); + void disconnectClient(); + + void setPlayerId(int _id) { playerId = _id; } + bool getReadyStart() const { return readyStart; } + void setReadyStart(bool _readyStart) { readyStart = _readyStart; } + int getPlayerId() const { return playerId; } + bool getSpectator() const { return spectator; } + bool getConceded() const { return conceded; } + void setConceded(bool _conceded) { conceded = _conceded; } + DeckList *getDeck() const { return deck; } + Server_Game *getGame() const { return game; } + const QMap &getZones() const { return zones; } + const QMap &getCounters() const { return counters; } + const QMap &getArrows() const { return arrows; } + + int getPingTime() const { return pingTime; } + void setPingTime(int _pingTime) { pingTime = _pingTime; } + void getProperties(ServerInfo_PlayerProperties &result, bool withUserInfo); + + int newCardId(); + int newCounterId() const; + int newArrowId() const; + + void addZone(Server_CardZone *zone); + void addArrow(Server_Arrow *arrow); + bool deleteArrow(int arrowId); + void addCounter(Server_Counter *counter); + + void clearZones(); + void setupZones(); - ServerInfo_PlayerProperties *getProperties(); - - int newCardId(); - int newCounterId() const; - int newArrowId() const; - - void addZone(Server_CardZone *zone); - void addArrow(Server_Arrow *arrow); - bool deleteArrow(int arrowId); - void addCounter(Server_Counter *counter); - bool deleteCounter(int counterId); - - void clearZones(); - void setupZones(); + Response::ResponseCode drawCards(GameEventStorage &ges, int number); + Response::ResponseCode moveCard(GameEventStorage &ges, Server_CardZone *startzone, const QList &_cards, Server_CardZone *targetzone, int x, int y, bool fixFreeSpaces = true, bool undoingDraw = false); + void unattachCard(GameEventStorage &ges, Server_Card *card); + Response::ResponseCode setCardAttrHelper(GameEventStorage &ges, const QString &zone, int cardId, CardAttribute attribute, const QString &attrValue); - ResponseCode drawCards(CommandContainer *cont, int number); - ResponseCode undoDraw(CommandContainer *cont); - ResponseCode moveCard(CommandContainer *cont, const QString &_startZone, const QList &_cards, int _targetPlayer, const QString &_targetZone, int _x, int _y); - ResponseCode moveCard(CommandContainer *cont, Server_CardZone *startzone, const QList &_cards, Server_CardZone *targetzone, int x, int y, bool fixFreeSpaces = true, bool undoingDraw = false); - void unattachCard(CommandContainer *cont, Server_Card *card); - ResponseCode setCardAttrHelper(CommandContainer *cont, const QString &zone, int cardId, const QString &attrName, const QString &attrValue); - - void sendProtocolItem(ProtocolItem *item, bool deleteItem = true); + Response::ResponseCode cmdLeaveGame(const Command_LeaveGame &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdKickFromGame(const Command_KickFromGame &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdConcede(const Command_Concede &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdSetSideboardPlan(const Command_SetSideboardPlan &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdSetSideboardLock(const Command_SetSideboardLock &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdGameSay(const Command_GameSay &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdShuffle(const Command_Shuffle &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdMulligan(const Command_Mulligan &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdRollDie(const Command_RollDie &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdDrawCards(const Command_DrawCards &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdUndoDraw(const Command_UndoDraw &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdFlipCard(const Command_FlipCard &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdAttachCard(const Command_AttachCard &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdCreateArrow(const Command_CreateArrow &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdDeleteArrow(const Command_DeleteArrow &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdSetCardAttr(const Command_SetCardAttr &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdSetCardCounter(const Command_SetCardCounter &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdIncCardCounter(const Command_IncCardCounter &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdCreateCounter(const Command_CreateCounter &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdNextTurn(const Command_NextTurn &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdSetActivePhase(const Command_SetActivePhase &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdDumpZone(const Command_DumpZone &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdStopDumpZone(const Command_StopDumpZone &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdRevealCards(const Command_RevealCards &cmd, ResponseContainer &rc, GameEventStorage &ges); + Response::ResponseCode cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer &rc, GameEventStorage &ges); + + Response::ResponseCode processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges); + void sendGameEvent(const GameEventContainer &event); + + void getInfo(ServerInfo_Player *info, Server_Player *playerWhosAsking, bool omniscient, bool withUserInfo); }; #endif diff --git a/common/server_player_reference.h b/common/server_player_reference.h new file mode 100644 index 000000000..868b40b6a --- /dev/null +++ b/common/server_player_reference.h @@ -0,0 +1,17 @@ +#ifndef SERVER_PLAYER_REFERENCE_H +#define SERVER_PLAYER_REFERENCE_H + +class PlayerReference { +private: + int roomId; + int gameId; + int playerId; +public: + PlayerReference(int _roomId, int _gameId, int _playerId) : roomId(_roomId), gameId(_gameId), playerId(_playerId) { } + int getRoomId() const { return roomId; } + int getGameId() const { return gameId; } + int getPlayerId() const { return playerId; } + bool operator==(const PlayerReference &other) { return ((roomId == other.roomId) && (gameId == other.gameId) && (playerId == other.playerId)); } +}; + +#endif diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 8a7336f81..2ca3902a9 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -1,1316 +1,582 @@ #include -#include "rng_abstract.h" +#include #include "server_protocolhandler.h" -#include "protocol.h" -#include "protocol_items.h" +#include "server_database_interface.h" #include "server_room.h" -#include "server_card.h" -#include "server_arrow.h" -#include "server_cardzone.h" -#include "server_counter.h" #include "server_game.h" #include "server_player.h" -#include "decklist.h" -#include +#include "get_pb_extension.h" +#include "pb/commands.pb.h" +#include "pb/response.pb.h" +#include "pb/response_login.pb.h" +#include "pb/response_list_users.pb.h" +#include "pb/response_get_games_of_user.pb.h" +#include "pb/response_get_user_info.pb.h" +#include "pb/response_join_room.pb.h" +#include "pb/event_list_rooms.pb.h" +#include "pb/event_server_message.pb.h" +#include "pb/event_user_message.pb.h" +#include "pb/event_game_joined.pb.h" +#include "pb/event_room_say.pb.h" +#include -Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, QObject *parent) - : QObject(parent), server(_server), authState(PasswordWrong), acceptsUserListChanges(false), acceptsRoomListChanges(false), userInfo(0), sessionId(-1), timeRunning(0), lastDataReceived(0), gameListMutex(QMutex::Recursive) +Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, Server_DatabaseInterface *_databaseInterface, QObject *parent) + : QObject(parent), + Server_AbstractUserInterface(_server), + deleted(false), + databaseInterface(_databaseInterface), + authState(NotLoggedIn), + acceptsUserListChanges(false), + acceptsRoomListChanges(false), + timeRunning(0), + lastDataReceived(0) { - connect(server, SIGNAL(pingClockTimeout()), this, SLOT(pingClockTimeout())); + connect(server, SIGNAL(pingClockTimeout()), this, SLOT(pingClockTimeout())); } Server_ProtocolHandler::~Server_ProtocolHandler() { } -// This is essentially the destructor, but it needs to be called from the -// child's destructor so that the server mutex does not get unlocked during -// finalization. +// This function must only be called from the thread this object lives in. +// The thread must not hold any server locks when calling this (e.g. clientsLock, roomsLock). void Server_ProtocolHandler::prepareDestroy() { - QMutexLocker locker(&server->serverMutex); - qDebug("Server_ProtocolHandler::prepareDestroy"); - - server->removeClient(this); - - QMapIterator roomIterator(rooms); - while (roomIterator.hasNext()) - roomIterator.next().value()->removeClient(this); - - gameListMutex.lock(); - QMapIterator > gameIterator(games); - while (gameIterator.hasNext()) { - gameIterator.next(); - Server_Game *g = gameIterator.value().first; - Server_Player *p = gameIterator.value().second; - - if ((authState == UnknownUser) || p->getSpectator()) - g->removePlayer(p); - else { - p->setProtocolHandler(0); - g->postConnectionStatusUpdate(p, false); - } - } - gameListMutex.unlock(); - - delete userInfo; - QMapIterator i(buddyList); - while (i.hasNext()) - delete i.next().value(); - QMapIterator j(ignoreList); - while (j.hasNext()) - delete j.next().value(); + if (deleted) + return; + deleted = true; + + QMapIterator roomIterator(rooms); + while (roomIterator.hasNext()) + roomIterator.next().value()->removeClient(this); + + QMap > tempGames(getGames()); + + server->roomsLock.lockForRead(); + QMapIterator > gameIterator(tempGames); + while (gameIterator.hasNext()) { + gameIterator.next(); + + Server_Room *r = server->getRooms().value(gameIterator.value().first); + if (!r) + continue; + r->gamesLock.lockForRead(); + Server_Game *g = r->getGames().value(gameIterator.key()); + if (!g) { + r->gamesLock.unlock(); + continue; + } + g->gameMutex.lock(); + Server_Player *p = g->getPlayers().value(gameIterator.value().second); + if (!p) { + g->gameMutex.unlock(); + r->gamesLock.unlock(); + continue; + } + + p->disconnectClient(); + + g->gameMutex.unlock(); + r->gamesLock.unlock(); + } + server->roomsLock.unlock(); + + server->removeClient(this); + + deleteLater(); } -void Server_ProtocolHandler::playerRemovedFromGame(Server_Game *game) +void Server_ProtocolHandler::sendProtocolItem(const Response &item) { - qDebug() << "Server_ProtocolHandler::playerRemovedFromGame(): gameId =" << game->getGameId(); - - QMutexLocker locker(&gameListMutex); - games.remove(game->getGameId()); + ServerMessage msg; + msg.mutable_response()->CopyFrom(item); + msg.set_message_type(ServerMessage::RESPONSE); + + transmitProtocolItem(msg); } -ResponseCode Server_ProtocolHandler::processCommandHelper(Command *command, CommandContainer *cont) +void Server_ProtocolHandler::sendProtocolItem(const SessionEvent &item) { - RoomCommand *roomCommand = qobject_cast(command); - if (roomCommand) { - qDebug() << "received RoomCommand: roomId =" << roomCommand->getRoomId(); - if (authState == PasswordWrong) - return RespLoginNeeded; - - Server_Room *room = rooms.value(roomCommand->getRoomId(), 0); - if (!room) - return RespNotInRoom; - - QMutexLocker locker(&room->roomMutex); - - switch (command->getItemId()) { - case ItemId_Command_LeaveRoom: return cmdLeaveRoom(static_cast(command), cont, room); - case ItemId_Command_RoomSay: return cmdRoomSay(static_cast(command), cont, room); - case ItemId_Command_CreateGame: return cmdCreateGame(static_cast(command), cont, room); - case ItemId_Command_JoinGame: return cmdJoinGame(static_cast(command), cont, room); - default: return RespInvalidCommand; - } - } - GameCommand *gameCommand = qobject_cast(command); - if (gameCommand) { - qDebug() << "received GameCommand: game =" << gameCommand->getGameId(); - if (authState == PasswordWrong) - return RespLoginNeeded; - - gameListMutex.lock(); - if (!games.contains(gameCommand->getGameId())) { - qDebug() << "invalid game"; - return RespNotInRoom; - } - QPair gamePair = games.value(gameCommand->getGameId()); - Server_Game *game = gamePair.first; - Server_Player *player = gamePair.second; - - QMutexLocker locker(&game->gameMutex); - gameListMutex.unlock(); - - switch (command->getItemId()) { - case ItemId_Command_DeckSelect: return cmdDeckSelect(static_cast(command), cont, game, player); - case ItemId_Command_SetSideboardPlan: return cmdSetSideboardPlan(static_cast(command), cont, game, player); - case ItemId_Command_LeaveGame: return cmdLeaveGame(static_cast(command), cont, game, player); - case ItemId_Command_KickFromGame: return cmdKickFromGame(static_cast(command), cont, game, player); - case ItemId_Command_ReadyStart: return cmdReadyStart(static_cast(command), cont, game, player); - case ItemId_Command_Concede: return cmdConcede(static_cast(command), cont, game, player); - case ItemId_Command_Say: return cmdSay(static_cast(command), cont, game, player); - case ItemId_Command_Shuffle: return cmdShuffle(static_cast(command), cont, game, player); - case ItemId_Command_Mulligan: return cmdMulligan(static_cast(command), cont, game, player); - case ItemId_Command_RollDie: return cmdRollDie(static_cast(command), cont, game, player); - case ItemId_Command_DrawCards: return cmdDrawCards(static_cast(command), cont, game, player); - case ItemId_Command_UndoDraw: return cmdUndoDraw(static_cast(command), cont, game, player); - case ItemId_Command_MoveCard: return cmdMoveCard(static_cast(command), cont, game, player); - case ItemId_Command_FlipCard: return cmdFlipCard(static_cast(command), cont, game, player); - case ItemId_Command_AttachCard: return cmdAttachCard(static_cast(command), cont, game, player); - case ItemId_Command_CreateToken: return cmdCreateToken(static_cast(command), cont, game, player); - case ItemId_Command_CreateArrow: return cmdCreateArrow(static_cast(command), cont, game, player); - case ItemId_Command_DeleteArrow: return cmdDeleteArrow(static_cast(command), cont, game, player); - case ItemId_Command_SetCardAttr: return cmdSetCardAttr(static_cast(command), cont, game, player); - case ItemId_Command_SetCardCounter: return cmdSetCardCounter(static_cast(command), cont, game, player); - case ItemId_Command_IncCardCounter: return cmdIncCardCounter(static_cast(command), cont, game, player); - case ItemId_Command_IncCounter: return cmdIncCounter(static_cast(command), cont, game, player); - case ItemId_Command_CreateCounter: return cmdCreateCounter(static_cast(command), cont, game, player); - case ItemId_Command_SetCounter: return cmdSetCounter(static_cast(command), cont, game, player); - case ItemId_Command_DelCounter: return cmdDelCounter(static_cast(command), cont, game, player); - case ItemId_Command_NextTurn: return cmdNextTurn(static_cast(command), cont, game, player); - case ItemId_Command_SetActivePhase: return cmdSetActivePhase(static_cast(command), cont, game, player); - case ItemId_Command_DumpZone: return cmdDumpZone(static_cast(command), cont, game, player); - case ItemId_Command_StopDumpZone: return cmdStopDumpZone(static_cast(command), cont, game, player); - case ItemId_Command_RevealCards: return cmdRevealCards(static_cast(command), cont, game, player); - default: return RespInvalidCommand; - } - } - ModeratorCommand *moderatorCommand = qobject_cast(command); - if (moderatorCommand) { - qDebug() << "received ModeratorCommand"; - if (!userInfo) - return RespLoginNeeded; - if (!(userInfo->getUserLevel() & ServerInfo_User::IsModerator)) - return RespLoginNeeded; - - switch (command->getItemId()) { - case ItemId_Command_BanFromServer: return cmdBanFromServer(static_cast(command), cont); - default: return RespInvalidCommand; - } - } - AdminCommand *adminCommand = qobject_cast(command); - if (adminCommand) { - qDebug() << "received AdminCommand"; - if (!userInfo) - return RespLoginNeeded; - if (!(userInfo->getUserLevel() & ServerInfo_User::IsAdmin)) - return RespLoginNeeded; - - switch (command->getItemId()) { - case ItemId_Command_ShutdownServer: return cmdShutdownServer(static_cast(command), cont); - case ItemId_Command_UpdateServerMessage: return cmdUpdateServerMessage(static_cast(command), cont); - default: return RespInvalidCommand; - } - } - switch (command->getItemId()) { - case ItemId_Command_Ping: return cmdPing(static_cast(command), cont); - case ItemId_Command_Login: return cmdLogin(static_cast(command), cont); - case ItemId_Command_Message: return cmdMessage(static_cast(command), cont); - case ItemId_Command_AddToList: return cmdAddToList(static_cast(command), cont); - case ItemId_Command_RemoveFromList: return cmdRemoveFromList(static_cast(command), cont); - case ItemId_Command_DeckList: return cmdDeckList(static_cast(command), cont); - case ItemId_Command_DeckNewDir: return cmdDeckNewDir(static_cast(command), cont); - case ItemId_Command_DeckDelDir: return cmdDeckDelDir(static_cast(command), cont); - case ItemId_Command_DeckDel: return cmdDeckDel(static_cast(command), cont); - case ItemId_Command_DeckUpload: return cmdDeckUpload(static_cast(command), cont); - case ItemId_Command_DeckDownload: return cmdDeckDownload(static_cast(command), cont); - case ItemId_Command_GetGamesOfUser: return cmdGetGamesOfUser(static_cast(command), cont); - case ItemId_Command_GetUserInfo: return cmdGetUserInfo(static_cast(command), cont); - case ItemId_Command_ListRooms: return cmdListRooms(static_cast(command), cont); - case ItemId_Command_JoinRoom: return cmdJoinRoom(static_cast(command), cont); - case ItemId_Command_ListUsers: return cmdListUsers(static_cast(command), cont); - default: return RespInvalidCommand; - } + ServerMessage msg; + msg.mutable_session_event()->CopyFrom(item); + msg.set_message_type(ServerMessage::SESSION_EVENT); + + transmitProtocolItem(msg); } -void Server_ProtocolHandler::processCommandContainer(CommandContainer *cont) +void Server_ProtocolHandler::sendProtocolItem(const GameEventContainer &item) { - lastDataReceived = timeRunning; - - const QList &cmdList = cont->getCommandList(); - ResponseCode finalResponseCode = RespOk; - for (int i = 0; i < cmdList.size(); ++i) { - ResponseCode resp = processCommandHelper(cmdList[i], cont); - if ((resp != RespOk) && (resp != RespNothing)) - finalResponseCode = resp; - } - - ProtocolResponse *pr = cont->getResponse(); - if (!pr) - pr = new ProtocolResponse(cont->getCmdId(), finalResponseCode); - - gameListMutex.lock(); - GameEventContainer *gQPublic = cont->getGameEventQueuePublic(); - if (gQPublic) { - QPair gamePlayerPair = games.value(gQPublic->getGameId()); - if (gamePlayerPair.first) { - GameEventContainer *gQPrivate = cont->getGameEventQueuePrivate(); - GameEventContainer *gQOmniscient = cont->getGameEventQueueOmniscient(); - if (gQPrivate) { - int privatePlayerId = cont->getPrivatePlayerId(); - Server_Player *privatePlayer; - if (privatePlayerId == -1) - privatePlayer = gamePlayerPair.second; - else - privatePlayer = gamePlayerPair.first->getPlayer(privatePlayerId); - if (gQOmniscient) { - gamePlayerPair.first->sendGameEventContainer(gQPublic, privatePlayer, true); - gamePlayerPair.first->sendGameEventContainerOmniscient(gQOmniscient, privatePlayer); - } else - gamePlayerPair.first->sendGameEventContainer(gQPublic, privatePlayer); - privatePlayer->sendProtocolItem(gQPrivate); - } else - gamePlayerPair.first->sendGameEventContainer(gQPublic); - } - } - gameListMutex.unlock(); - - const QList &iQ = cont->getItemQueue(); - for (int i = 0; i < iQ.size(); ++i) - sendProtocolItem(iQ[i]); - - sendProtocolItem(pr); - - while (!itemQueue.isEmpty()) - sendProtocolItem(itemQueue.takeFirst()); + ServerMessage msg; + msg.mutable_game_event_container()->CopyFrom(item); + msg.set_message_type(ServerMessage::GAME_EVENT_CONTAINER); + + transmitProtocolItem(msg); +} - if (cont->getReceiverMayDelete()) - delete cont; +void Server_ProtocolHandler::sendProtocolItem(const RoomEvent &item) +{ + ServerMessage msg; + msg.mutable_room_event()->CopyFrom(item); + msg.set_message_type(ServerMessage::ROOM_EVENT); + + transmitProtocolItem(msg); +} + +Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(const CommandContainer &cont, ResponseContainer &rc) +{ + Response::ResponseCode finalResponseCode = Response::RespOk; + for (int i = cont.session_command_size() - 1; i >= 0; --i) { + Response::ResponseCode resp = Response::RespInvalidCommand; + const SessionCommand &sc = cont.session_command(i); + const int num = getPbExtension(sc); + if (num != SessionCommand::PING) { // don't log ping commands + if (num == SessionCommand::LOGIN) { // log login commands, but hide passwords + SessionCommand debugSc(sc); + debugSc.MutableExtension(Command_Login::ext)->clear_password(); + logDebugMessage(QString::fromStdString(debugSc.ShortDebugString())); + } else + logDebugMessage(QString::fromStdString(sc.ShortDebugString())); + } + switch ((SessionCommand::SessionCommandType) num) { + case SessionCommand::PING: resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc); break; + case SessionCommand::LOGIN: resp = cmdLogin(sc.GetExtension(Command_Login::ext), rc); break; + case SessionCommand::MESSAGE: resp = cmdMessage(sc.GetExtension(Command_Message::ext), rc); break; + case SessionCommand::GET_GAMES_OF_USER: resp = cmdGetGamesOfUser(sc.GetExtension(Command_GetGamesOfUser::ext), rc); break; + case SessionCommand::GET_USER_INFO: resp = cmdGetUserInfo(sc.GetExtension(Command_GetUserInfo::ext), rc); break; + case SessionCommand::LIST_ROOMS: resp = cmdListRooms(sc.GetExtension(Command_ListRooms::ext), rc); break; + case SessionCommand::JOIN_ROOM: resp = cmdJoinRoom(sc.GetExtension(Command_JoinRoom::ext), rc); break; + case SessionCommand::LIST_USERS: resp = cmdListUsers(sc.GetExtension(Command_ListUsers::ext), rc); break; + default: resp = processExtendedSessionCommand(num, sc, rc); + } + if (resp != Response::RespOk) + finalResponseCode = resp; + } + return finalResponseCode; +} + +Response::ResponseCode Server_ProtocolHandler::processRoomCommandContainer(const CommandContainer &cont, ResponseContainer &rc) +{ + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + QReadLocker locker(&server->roomsLock); + Server_Room *room = rooms.value(cont.room_id(), 0); + if (!room) + return Response::RespNotInRoom; + + Response::ResponseCode finalResponseCode = Response::RespOk; + for (int i = cont.room_command_size() - 1; i >= 0; --i) { + Response::ResponseCode resp = Response::RespInvalidCommand; + const RoomCommand &sc = cont.room_command(i); + const int num = getPbExtension(sc); + logDebugMessage(QString::fromStdString(sc.ShortDebugString())); + switch ((RoomCommand::RoomCommandType) num) { + case RoomCommand::LEAVE_ROOM: resp = cmdLeaveRoom(sc.GetExtension(Command_LeaveRoom::ext), room, rc); break; + case RoomCommand::ROOM_SAY: resp = cmdRoomSay(sc.GetExtension(Command_RoomSay::ext), room, rc); break; + case RoomCommand::CREATE_GAME: resp = cmdCreateGame(sc.GetExtension(Command_CreateGame::ext), room, rc); break; + case RoomCommand::JOIN_GAME: resp = cmdJoinGame(sc.GetExtension(Command_JoinGame::ext), room, rc); break; + } + if (resp != Response::RespOk) + finalResponseCode = resp; + } + return finalResponseCode; +} + +Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const CommandContainer &cont, ResponseContainer &rc) +{ + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + QMap > gameMap = getGames(); + if (!gameMap.contains(cont.game_id())) + return Response::RespNotInRoom; + const QPair roomIdAndPlayerId = gameMap.value(cont.game_id()); + + QReadLocker roomsLocker(&server->roomsLock); + Server_Room *room = server->getRooms().value(roomIdAndPlayerId.first); + if (!room) + return Response::RespNotInRoom; + + QReadLocker roomGamesLocker(&room->gamesLock); + Server_Game *game = room->getGames().value(cont.game_id()); + if (!game) { + if (room->getExternalGames().contains(cont.game_id())) { + server->sendIsl_GameCommand(cont, + room->getExternalGames().value(cont.game_id()).server_id(), + userInfo->session_id(), + roomIdAndPlayerId.first, + roomIdAndPlayerId.second + ); + return Response::RespNothing; + } + return Response::RespNotInRoom; + } + + QMutexLocker gameLocker(&game->gameMutex); + Server_Player *player = game->getPlayers().value(roomIdAndPlayerId.second); + if (!player) + return Response::RespNotInRoom; + + GameEventStorage ges; + Response::ResponseCode finalResponseCode = Response::RespOk; + for (int i = cont.game_command_size() - 1; i >= 0; --i) { + const GameCommand &sc = cont.game_command(i); + logDebugMessage(QString("game %1 player %2: ").arg(cont.game_id()).arg(roomIdAndPlayerId.second) + QString::fromStdString(sc.ShortDebugString())); + + Response::ResponseCode resp = player->processGameCommand(sc, rc, ges); + + if (resp != Response::RespOk) + finalResponseCode = resp; + } + ges.sendToGame(game); + + return finalResponseCode; +} + +Response::ResponseCode Server_ProtocolHandler::processModeratorCommandContainer(const CommandContainer &cont, ResponseContainer &rc) +{ + if (!userInfo) + return Response::RespLoginNeeded; + if (!(userInfo->user_level() & ServerInfo_User::IsModerator)) + return Response::RespLoginNeeded; + + Response::ResponseCode finalResponseCode = Response::RespOk; + for (int i = cont.moderator_command_size() - 1; i >= 0; --i) { + Response::ResponseCode resp = Response::RespInvalidCommand; + const ModeratorCommand &sc = cont.moderator_command(i); + const int num = getPbExtension(sc); + logDebugMessage(QString::fromStdString(sc.ShortDebugString())); + + resp = processExtendedModeratorCommand(num, sc, rc); + if (resp != Response::RespOk) + finalResponseCode = resp; + } + return finalResponseCode; +} + +Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(const CommandContainer &cont, ResponseContainer &rc) +{ + if (!userInfo) + return Response::RespLoginNeeded; + if (!(userInfo->user_level() & ServerInfo_User::IsAdmin)) + return Response::RespLoginNeeded; + + Response::ResponseCode finalResponseCode = Response::RespOk; + for (int i = cont.admin_command_size() - 1; i >= 0; --i) { + Response::ResponseCode resp = Response::RespInvalidCommand; + const AdminCommand &sc = cont.admin_command(i); + const int num = getPbExtension(sc); + logDebugMessage(QString::fromStdString(sc.ShortDebugString())); + + resp = processExtendedAdminCommand(num, sc, rc); + if (resp != Response::RespOk) + finalResponseCode = resp; + } + return finalResponseCode; +} + +void Server_ProtocolHandler::processCommandContainer(const CommandContainer &cont) +{ + // Command processing must be disabled after prepareDestroy() has been called. + if (deleted) + return; + + lastDataReceived = timeRunning; + + ResponseContainer responseContainer(cont.has_cmd_id() ? cont.cmd_id() : -1); + Response::ResponseCode finalResponseCode; + + if (cont.game_command_size()) + finalResponseCode = processGameCommandContainer(cont, responseContainer); + else if (cont.room_command_size()) + finalResponseCode = processRoomCommandContainer(cont, responseContainer); + else if (cont.session_command_size()) + finalResponseCode = processSessionCommandContainer(cont, responseContainer); + else if (cont.moderator_command_size()) + finalResponseCode = processModeratorCommandContainer(cont, responseContainer); + else if (cont.admin_command_size()) + finalResponseCode = processAdminCommandContainer(cont, responseContainer); + else + finalResponseCode = Response::RespInvalidCommand; + + if ((finalResponseCode != Response::RespNothing)) + sendResponseContainer(responseContainer, finalResponseCode); } void Server_ProtocolHandler::pingClockTimeout() { - int interval = server->getMessageCountingInterval(); - if (interval > 0) { - messageSizeOverTime.prepend(0); - if (messageSizeOverTime.size() > server->getMessageCountingInterval()) - messageSizeOverTime.removeLast(); - messageCountOverTime.prepend(0); - if (messageCountOverTime.size() > server->getMessageCountingInterval()) - messageCountOverTime.removeLast(); - } - - if (timeRunning - lastDataReceived > server->getMaxPlayerInactivityTime()) - deleteLater(); - ++timeRunning; + int interval = server->getMessageCountingInterval(); + if (interval > 0) { + messageSizeOverTime.prepend(0); + if (messageSizeOverTime.size() > server->getMessageCountingInterval()) + messageSizeOverTime.removeLast(); + messageCountOverTime.prepend(0); + if (messageCountOverTime.size() > server->getMessageCountingInterval()) + messageCountOverTime.removeLast(); + } + + if (timeRunning - lastDataReceived > server->getMaxPlayerInactivityTime()) + prepareDestroy(); + ++timeRunning; } -void Server_ProtocolHandler::enqueueProtocolItem(ProtocolItem *item) +Response::ResponseCode Server_ProtocolHandler::cmdPing(const Command_Ping & /*cmd*/, ResponseContainer & /*rc*/) { - itemQueue.append(item); + return Response::RespOk; } -QPair Server_ProtocolHandler::getGame(int gameId) const +Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd, ResponseContainer &rc) { - if (games.contains(gameId)) - return games.value(gameId); - return QPair(0, 0); + QString userName = QString::fromStdString(cmd.user_name()).simplified(); + if (userName.isEmpty() || (userInfo != 0)) + return Response::RespContextError; + QString reasonStr; + int banSecondsLeft = 0; + AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft); + switch (res) { + case UserIsBanned: { + Response_Login *re = new Response_Login; + re->set_denied_reason_str(reasonStr.toStdString()); + if (banSecondsLeft != 0) + re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsLeft).toTime_t()); + rc.setResponseExtension(re); + return Response::RespUserIsBanned; + } + case NotLoggedIn: return Response::RespWrongPassword; + case WouldOverwriteOldSession: return Response::RespWouldOverwriteOldSession; + case UsernameInvalid: return Response::RespUsernameInvalid; + case RegistrationRequired: return Response::RespRegistrationRequired; + default: authState = res; + } + + userName = QString::fromStdString(userInfo->name()); + Event_ServerMessage event; + event.set_message(server->getLoginMessage().toStdString()); + rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); + + Response_Login *re = new Response_Login; + re->mutable_user_info()->CopyFrom(copyUserInfo(true)); + + if (authState == PasswordRight) { + QMapIterator buddyIterator(databaseInterface->getBuddyList(userName)); + while (buddyIterator.hasNext()) + re->add_buddy_list()->CopyFrom(buddyIterator.next().value()); + + QMapIterator ignoreIterator(databaseInterface->getIgnoreList(userName)); + while (ignoreIterator.hasNext()) + re->add_ignore_list()->CopyFrom(ignoreIterator.next().value()); + } + + joinPersistentGames(rc); + + rc.setResponseExtension(re); + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdPing(Command_Ping * /*cmd*/, CommandContainer * /*cont*/) +Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message &cmd, ResponseContainer &rc) { - return RespOk; + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + QReadLocker locker(&server->clientsLock); + + QString receiver = QString::fromStdString(cmd.user_name()); + Server_AbstractUserInterface *userInterface = server->findUser(receiver); + if (!userInterface) + return Response::RespNameNotFound; + if (databaseInterface->isInIgnoreList(receiver, QString::fromStdString(userInfo->name()))) + return Response::RespInIgnoreList; + + Event_UserMessage event; + event.set_sender_name(userInfo->name()); + event.set_receiver_name(cmd.user_name()); + event.set_message(cmd.message()); + + SessionEvent *se = prepareSessionEvent(event); + userInterface->sendProtocolItem(*se); + rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, se); + + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdLogin(Command_Login *cmd, CommandContainer *cont) +Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc) { - QString userName = cmd->getUsername().simplified(); - if (userName.isEmpty() || (userInfo != 0)) - return RespContextError; - authState = server->loginUser(this, userName, cmd->getPassword()); - if (authState == PasswordWrong) - return RespWrongPassword; - if (authState == WouldOverwriteOldSession) - return RespWouldOverwriteOldSession; - - ProtocolItem *serverMessage = new Event_ServerMessage(server->getLoginMessage()); - if (getCompressionSupport()) - serverMessage->setCompressed(true); - enqueueProtocolItem(serverMessage); - - QList _buddyList, _ignoreList; - if (authState == PasswordRight) { - buddyList = server->getBuddyList(userInfo->getName()); - - QMapIterator buddyIterator(buddyList); - while (buddyIterator.hasNext()) - _buddyList.append(new ServerInfo_User(buddyIterator.next().value())); - - ignoreList = server->getIgnoreList(userInfo->getName()); - - QMapIterator ignoreIterator(ignoreList); - while (ignoreIterator.hasNext()) - _ignoreList.append(new ServerInfo_User(ignoreIterator.next().value())); - } - - server->serverMutex.lock(); - - QList gameList; - QMapIterator roomIterator(server->getRooms()); - QMutexLocker gameListLocker(&gameListMutex); - while (roomIterator.hasNext()) { - Server_Room *room = roomIterator.next().value(); - room->roomMutex.lock(); - QMapIterator gameIterator(room->getGames()); - while (gameIterator.hasNext()) { - Server_Game *game = gameIterator.next().value(); - QMutexLocker gameLocker(&game->gameMutex); - const QList &gamePlayers = game->getPlayers().values(); - for (int j = 0; j < gamePlayers.size(); ++j) - if (gamePlayers[j]->getUserInfo()->getName() == userInfo->getName()) { - gamePlayers[j]->setProtocolHandler(this); - game->postConnectionStatusUpdate(gamePlayers[j], true); - games.insert(game->getGameId(), QPair(game, gamePlayers[j])); - - enqueueProtocolItem(new Event_GameJoined(game->getGameId(), game->getDescription(), game->getHostId(), gamePlayers[j]->getPlayerId(), gamePlayers[j]->getSpectator(), game->getSpectatorsCanTalk(), game->getSpectatorsSeeEverything(), true)); - enqueueProtocolItem(GameEventContainer::makeNew(new Event_GameStateChanged(game->getGameStarted(), game->getActivePlayer(), game->getActivePhase(), game->getGameState(gamePlayers[j])), game->getGameId())); - - break; - } - } - room->roomMutex.unlock(); - } - server->serverMutex.unlock(); - - ProtocolResponse *resp = new Response_Login(cont->getCmdId(), RespOk, new ServerInfo_User(userInfo, true), _buddyList, _ignoreList); - if (getCompressionSupport()) - resp->setCompressed(true); - cont->setResponse(resp); - return RespNothing; + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + // We don't need to check whether the user is logged in; persistent games should also work. + // The client needs to deal with an empty result list. + + Response_GetGamesOfUser *re = new Response_GetGamesOfUser; + server->roomsLock.lockForRead(); + QMapIterator roomIterator(server->getRooms()); + while (roomIterator.hasNext()) { + Server_Room *room = roomIterator.next().value(); + room->gamesLock.lockForRead(); + room->getInfo(*re->add_room_list(), false, true); + QListIterator gameIterator(room->getGamesOfUser(QString::fromStdString(cmd.user_name()))); + while (gameIterator.hasNext()) + re->add_game_list()->CopyFrom(gameIterator.next()); + room->gamesLock.unlock(); + } + server->roomsLock.unlock(); + + rc.setResponseExtension(re); + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdMessage(Command_Message *cmd, CommandContainer *cont) +Response::ResponseCode Server_ProtocolHandler::cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc) { - if (authState == PasswordWrong) - return RespLoginNeeded; - - QString receiver = cmd->getUserName(); - Server_ProtocolHandler *userHandler = server->getUsers().value(receiver); - qDebug() << "cmdMessage: recv=" << receiver << (userHandler == 0 ? "not found" : "found"); - if (!userHandler) - return RespNameNotFound; - if (userHandler->getIgnoreList().contains(userInfo->getName())) - return RespInIgnoreList; - - cont->enqueueItem(new Event_Message(userInfo->getName(), receiver, cmd->getText())); - userHandler->sendProtocolItem(new Event_Message(userInfo->getName(), receiver, cmd->getText())); - return RespOk; + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + QString userName = QString::fromStdString(cmd.user_name()); + Response_GetUserInfo *re = new Response_GetUserInfo; + if (userName.isEmpty()) + re->mutable_user_info()->CopyFrom(*userInfo); + else { + + QReadLocker locker(&server->clientsLock); + + ServerInfo_User_Container *infoSource = server->findUser(userName); + if (!infoSource) + return Response::RespNameNotFound; + + re->mutable_user_info()->CopyFrom(infoSource->copyUserInfo(true, false, userInfo->user_level() & ServerInfo_User::IsModerator)); + } + + rc.setResponseExtension(re); + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(Command_GetGamesOfUser *cmd, CommandContainer *cont) +Response::ResponseCode Server_ProtocolHandler::cmdListRooms(const Command_ListRooms & /*cmd*/, ResponseContainer &rc) { - if (authState == PasswordWrong) - return RespLoginNeeded; - - server->serverMutex.lock(); - if (!server->getUsers().contains(cmd->getUserName())) - return RespNameNotFound; - - QList roomList; - QList gameList; - QMapIterator roomIterator(server->getRooms()); - while (roomIterator.hasNext()) { - Server_Room *room = roomIterator.next().value(); - room->roomMutex.lock(); - roomList.append(room->getInfo(false, true)); - gameList << room->getGamesOfUser(cmd->getUserName()); - room->roomMutex.unlock(); - } - server->serverMutex.unlock(); - - ProtocolResponse *resp = new Response_GetGamesOfUser(cont->getCmdId(), RespOk, roomList, gameList); - if (getCompressionSupport()) - resp->setCompressed(true); - cont->setResponse(resp); - return RespNothing; + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + Event_ListRooms event; + QMapIterator roomIterator(server->getRooms()); + while (roomIterator.hasNext()) + roomIterator.next().value()->getInfo(*event.add_room_list(), false); + rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); + + acceptsRoomListChanges = true; + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdGetUserInfo(Command_GetUserInfo *cmd, CommandContainer *cont) +Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoom &cmd, ResponseContainer &rc) { - if (authState == PasswordWrong) - return RespLoginNeeded; - - ServerInfo_User *result; - if (cmd->getUserName().isEmpty()) - result = new ServerInfo_User(userInfo); - else { - Server_ProtocolHandler *handler = server->getUsers().value(cmd->getUserName()); - if (!handler) - return RespNameNotFound; - result = new ServerInfo_User(handler->getUserInfo(), true, userInfo->getUserLevel() & ServerInfo_User::IsModerator); - } - - cont->setResponse(new Response_GetUserInfo(cont->getCmdId(), RespOk, result)); - return RespNothing; + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + if (rooms.contains(cmd.room_id())) + return Response::RespContextError; + + QReadLocker serverLocker(&server->roomsLock); + Server_Room *r = server->getRooms().value(cmd.room_id(), 0); + if (!r) + return Response::RespNameNotFound; + + r->addClient(this); + rooms.insert(r->getId(), r); + + Event_RoomSay joinMessageEvent; + joinMessageEvent.set_message(r->getJoinMessage().toStdString()); + rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(joinMessageEvent)); + + Response_JoinRoom *re = new Response_JoinRoom; + r->getInfo(*re->mutable_room_info(), true); + + rc.setResponseExtension(re); + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdListRooms(Command_ListRooms * /*cmd*/, CommandContainer *cont) +Response::ResponseCode Server_ProtocolHandler::cmdListUsers(const Command_ListUsers & /*cmd*/, ResponseContainer &rc) { - if (authState == PasswordWrong) - return RespLoginNeeded; - - QList eventRoomList; - QMapIterator roomIterator(server->getRooms()); - while (roomIterator.hasNext()) - eventRoomList.append(roomIterator.next().value()->getInfo(false)); - cont->enqueueItem(new Event_ListRooms(eventRoomList)); - - acceptsRoomListChanges = true; - return RespOk; + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + Response_ListUsers *re = new Response_ListUsers; + server->clientsLock.lockForRead(); + QMapIterator userIterator = server->getUsers(); + while (userIterator.hasNext()) + re->add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(false)); + QMapIterator extIterator = server->getExternalUsers(); + while (extIterator.hasNext()) + re->add_user_list()->CopyFrom(extIterator.next().value()->copyUserInfo(false)); + + acceptsUserListChanges = true; + server->clientsLock.unlock(); + + rc.setResponseExtension(re); + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdJoinRoom(Command_JoinRoom *cmd, CommandContainer *cont) +Response::ResponseCode Server_ProtocolHandler::cmdLeaveRoom(const Command_LeaveRoom & /*cmd*/, Server_Room *room, ResponseContainer & /*rc*/) { - if (authState == PasswordWrong) - return RespLoginNeeded; - - if (rooms.contains(cmd->getRoomId())) - return RespContextError; - - Server_Room *r = server->getRooms().value(cmd->getRoomId(), 0); - if (!r) - return RespNameNotFound; - - QMutexLocker serverLocker(&server->serverMutex); - QMutexLocker roomLocker(&r->roomMutex); - r->addClient(this); - rooms.insert(r->getId(), r); - - enqueueProtocolItem(new Event_RoomSay(r->getId(), QString(), r->getJoinMessage())); - - ServerInfo_Room *info = r->getInfo(true); - if (getCompressionSupport()) - info->setCompressed(true); - cont->setResponse(new Response_JoinRoom(cont->getCmdId(), RespOk, info)); - return RespNothing; + rooms.remove(room->getId()); + room->removeClient(this); + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdLeaveRoom(Command_LeaveRoom * /*cmd*/, CommandContainer * /*cont*/, Server_Room *room) +Response::ResponseCode Server_ProtocolHandler::cmdRoomSay(const Command_RoomSay &cmd, Server_Room *room, ResponseContainer & /*rc*/) { - rooms.remove(room->getId()); - room->removeClient(this); - return RespOk; + QString msg = QString::fromStdString(cmd.message()); + + if (server->getMessageCountingInterval() > 0) { + int totalSize = 0, totalCount = 0; + if (messageSizeOverTime.isEmpty()) + messageSizeOverTime.prepend(0); + messageSizeOverTime[0] += msg.size(); + for (int i = 0; i < messageSizeOverTime.size(); ++i) + totalSize += messageSizeOverTime[i]; + + if (messageCountOverTime.isEmpty()) + messageCountOverTime.prepend(0); + ++messageCountOverTime[0]; + for (int i = 0; i < messageCountOverTime.size(); ++i) + totalCount += messageCountOverTime[i]; + + if ((totalSize > server->getMaxMessageSizePerInterval()) || (totalCount > server->getMaxMessageCountPerInterval())) + return Response::RespChatFlood; + } + msg.replace(QChar('\n'), QChar(' ')); + + room->say(QString::fromStdString(userInfo->name()), msg); + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdRoomSay(Command_RoomSay *cmd, CommandContainer * /*cont*/, Server_Room *room) +Response::ResponseCode Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room *room, ResponseContainer &rc) { - QString msg = cmd->getMessage(); - - if (server->getMessageCountingInterval() > 0) { - int totalSize = 0, totalCount = 0; - if (messageSizeOverTime.isEmpty()) - messageSizeOverTime.prepend(0); - messageSizeOverTime[0] += msg.size(); - for (int i = 0; i < messageSizeOverTime.size(); ++i) - totalSize += messageSizeOverTime[i]; - - if (messageCountOverTime.isEmpty()) - messageCountOverTime.prepend(0); - ++messageCountOverTime[0]; - for (int i = 0; i < messageCountOverTime.size(); ++i) - totalCount += messageCountOverTime[i]; - - if ((totalSize > server->getMaxMessageSizePerInterval()) || (totalCount > server->getMaxMessageCountPerInterval())) - return RespChatFlood; - } - msg.replace(QChar('\n'), QChar(' ')); - - room->say(this, msg); - return RespOk; + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + const int gameId = databaseInterface->getNextGameId(); + if (gameId == -1) + return Response::RespInternalError; + + if (server->getMaxGamesPerUser() > 0) + if (room->getGamesCreatedByUser(QString::fromStdString(userInfo->name())) >= server->getMaxGamesPerUser()) + return Response::RespContextError; + + QList gameTypes; + for (int i = cmd.game_type_ids_size() - 1; i >= 0; --i) + gameTypes.append(cmd.game_type_ids(i)); + + QString description = QString::fromStdString(cmd.description()); + if (description.size() > 60) + description = description.left(60); + + Server_Game *game = new Server_Game(copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()), cmd.max_players(), gameTypes, cmd.only_buddies(), cmd.only_registered(), cmd.spectators_allowed(), cmd.spectators_need_password(), cmd.spectators_can_talk(), cmd.spectators_see_everything(), room); + game->addPlayer(this, rc, false, false); + room->addGame(game); + + return Response::RespOk; } -ResponseCode Server_ProtocolHandler::cmdListUsers(Command_ListUsers * /*cmd*/, CommandContainer *cont) +Response::ResponseCode Server_ProtocolHandler::cmdJoinGame(const Command_JoinGame &cmd, Server_Room *room, ResponseContainer &rc) { - if (authState == PasswordWrong) - return RespLoginNeeded; - - QList resultList; - QMapIterator userIterator = server->getUsers(); - while (userIterator.hasNext()) - resultList.append(new ServerInfo_User(userIterator.next().value()->getUserInfo(), false)); - - acceptsUserListChanges = true; - - ProtocolResponse *resp = new Response_ListUsers(cont->getCmdId(), RespOk, resultList); - if (getCompressionSupport()) - resp->setCompressed(true); - cont->setResponse(resp); - return RespNothing; -} - -ResponseCode Server_ProtocolHandler::cmdCreateGame(Command_CreateGame *cmd, CommandContainer * /*cont*/, Server_Room *room) -{ - if (authState == PasswordWrong) - return RespLoginNeeded; - - if (server->getMaxGamesPerUser() > 0) - if (room->getGamesCreatedByUser(userInfo->getName()) >= server->getMaxGamesPerUser()) - return RespContextError; - - QList gameTypes; - QList gameTypeList = cmd->getGameTypes(); - for (int i = 0; i < gameTypeList.size(); ++i) - gameTypes.append(gameTypeList[i]->getData()); - - QString description = cmd->getDescription(); - if (description.size() > 60) - description = description.left(60); - Server_Game *game = room->createGame(description, cmd->getPassword(), cmd->getMaxPlayers(), gameTypes, cmd->getOnlyBuddies(), cmd->getOnlyRegistered(), cmd->getSpectatorsAllowed(), cmd->getSpectatorsNeedPassword(), cmd->getSpectatorsCanTalk(), cmd->getSpectatorsSeeEverything(), this); - - Server_Player *creator = game->getPlayers().values().first(); - - QMutexLocker gameListLocker(&gameListMutex); - games.insert(game->getGameId(), QPair(game, creator)); - - sendProtocolItem(new Event_GameJoined(game->getGameId(), game->getDescription(), creator->getPlayerId(), creator->getPlayerId(), false, game->getSpectatorsCanTalk(), game->getSpectatorsSeeEverything(), false)); - sendProtocolItem(GameEventContainer::makeNew(new Event_GameStateChanged(game->getGameStarted(), game->getActivePlayer(), game->getActivePhase(), game->getGameState(creator)), game->getGameId())); - - game->gameMutex.unlock(); - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdJoinGame(Command_JoinGame *cmd, CommandContainer * /*cont*/, Server_Room *room) -{ - if (authState == PasswordWrong) - return RespLoginNeeded; - - QMutexLocker gameListLocker(&gameListMutex); - - if (games.contains(cmd->getGameId())) - return RespContextError; - - Server_Game *g = room->getGames().value(cmd->getGameId()); - if (!g) - return RespNameNotFound; - - QMutexLocker locker(&g->gameMutex); - - ResponseCode result = g->checkJoin(userInfo, cmd->getPassword(), cmd->getSpectator(), cmd->getOverrideRestrictions()); - if (result == RespOk) { - Server_Player *player = g->addPlayer(this, cmd->getSpectator()); - games.insert(cmd->getGameId(), QPair(g, player)); - enqueueProtocolItem(new Event_GameJoined(cmd->getGameId(), g->getDescription(), g->getHostId(), player->getPlayerId(), cmd->getSpectator(), g->getSpectatorsCanTalk(), g->getSpectatorsSeeEverything(), false)); - enqueueProtocolItem(GameEventContainer::makeNew(new Event_GameStateChanged(g->getGameStarted(), g->getActivePlayer(), g->getActivePhase(), g->getGameState(player)), cmd->getGameId())); - } - return result; -} - -ResponseCode Server_ProtocolHandler::cmdLeaveGame(Command_LeaveGame * /*cmd*/, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - game->removePlayer(player); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdKickFromGame(Command_KickFromGame *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if ((game->getHostId() != player->getPlayerId()) && !(userInfo->getUserLevel() & ServerInfo_User::IsModerator)) - return RespFunctionNotAllowed; - - if (!game->kickPlayer(cmd->getPlayerId())) - return RespNameNotFound; - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdDeckSelect(Command_DeckSelect *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - DeckList *deck; - if (cmd->getDeckId() == -1) { - if (!cmd->getDeck()) - return RespInvalidData; - deck = new DeckList(cmd->getDeck()); - } else { - try { - deck = getDeckFromDatabase(cmd->getDeckId()); - } catch(ResponseCode r) { - return r; - } - } - player->setDeck(deck); - - game->sendGameEvent(new Event_PlayerPropertiesChanged(player->getPlayerId(), player->getProperties()), new Context_DeckSelect(deck->getDeckHash())); - - cont->setResponse(new Response_DeckDownload(cont->getCmdId(), RespOk, new DeckList(deck))); - return RespNothing; -} - -ResponseCode Server_ProtocolHandler::cmdSetSideboardPlan(Command_SetSideboardPlan *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - if (player->getReadyStart()) - return RespContextError; - - DeckList *deck = player->getDeck(); - if (!deck) - return RespContextError; - - deck->setCurrentSideboardPlan(cmd->getMoveList()); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdConcede(Command_Concede * /*cmd*/, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - player->setConceded(true); - game->removeArrowsToPlayer(player); - player->clearZones(); - game->sendGameEvent(new Event_PlayerPropertiesChanged(player->getPlayerId(), player->getProperties()), new Context_Concede); - game->stopGameIfFinished(); - if (game->getGameStarted() && (game->getActivePlayer() == player->getPlayerId())) - game->nextTurn(); - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdReadyStart(Command_ReadyStart *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!player->getDeck() || game->getGameStarted()) - return RespContextError; - - if (player->getReadyStart() == cmd->getReady()) - return RespContextError; - - player->setReadyStart(cmd->getReady()); - game->sendGameEvent(new Event_PlayerPropertiesChanged(player->getPlayerId(), player->getProperties()), new Context_ReadyStart); - game->startGameIfReady(); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdSay(Command_Say *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator() && !game->getSpectatorsCanTalk() && !(userInfo->getUserLevel() & ServerInfo_User::IsModerator)) - return RespFunctionNotAllowed; - - game->sendGameEvent(new Event_Say(player->getPlayerId(), cmd->getMessage())); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdShuffle(Command_Shuffle * /*cmd*/, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - player->getZones().value("deck")->shuffle(); - game->sendGameEvent(new Event_Shuffle(player->getPlayerId())); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdMulligan(Command_Mulligan * /*cmd*/, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_CardZone *hand = player->getZones().value("hand"); - int number = (hand->cards.size() <= 1) ? player->getInitialCards() : hand->cards.size() - 1; - - Server_CardZone *deck = player->getZones().value("deck"); - while (!hand->cards.isEmpty()) { - CardToMove *cardToMove = new CardToMove(hand->cards.first()->getId()); - player->moveCard(cont, hand, QList() << cardToMove, deck, 0, 0, false); - delete cardToMove; - } - - deck->shuffle(); - cont->enqueueGameEventPrivate(new Event_Shuffle(player->getPlayerId()), game->getGameId()); - cont->enqueueGameEventOmniscient(new Event_Shuffle(player->getPlayerId()), game->getGameId()); - cont->enqueueGameEventPublic(new Event_Shuffle(player->getPlayerId()), game->getGameId()); - - player->drawCards(cont, number); - - if (number == player->getInitialCards()) - number = -1; - cont->getGameEventQueuePrivate()->setContext(new Context_Mulligan(number)); - cont->getGameEventQueuePublic()->setContext(new Context_Mulligan(number)); - cont->getGameEventQueueOmniscient()->setContext(new Context_Mulligan(number)); - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdRollDie(Command_RollDie *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - if (player->getConceded()) - return RespContextError; - - game->sendGameEvent(new Event_RollDie(player->getPlayerId(), cmd->getSides(), rng->getNumber(1, cmd->getSides()))); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdDrawCards(Command_DrawCards *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - return player->drawCards(cont, cmd->getNumber()); -} - -ResponseCode Server_ProtocolHandler::cmdUndoDraw(Command_UndoDraw * /*cmd*/, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - return player->undoDraw(cont); -} - -ResponseCode Server_ProtocolHandler::cmdMoveCard(Command_MoveCard *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - return player->moveCard(cont, cmd->getStartZone(), cmd->getCards(), cmd->getTargetPlayerId(), cmd->getTargetZone(), cmd->getX(), cmd->getY()); -} - -ResponseCode Server_ProtocolHandler::cmdFlipCard(Command_FlipCard *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_CardZone *zone = player->getZones().value(cmd->getZone()); - if (!zone) - return RespNameNotFound; - if (!zone->hasCoords()) - return RespContextError; - - Server_Card *card = zone->getCard(cmd->getCardId()); - if (!card) - return RespNameNotFound; - - const bool faceDown = cmd->getFaceDown(); - if (faceDown == card->getFaceDown()) - return RespContextError; - - card->setFaceDown(faceDown); - cont->enqueueGameEventPrivate(new Event_FlipCard(player->getPlayerId(), zone->getName(), card->getId(), card->getName(), faceDown), game->getGameId()); - cont->enqueueGameEventPublic(new Event_FlipCard(player->getPlayerId(), zone->getName(), card->getId(), card->getName(), faceDown), game->getGameId()); - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdAttachCard(Command_AttachCard *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_CardZone *startzone = player->getZones().value(cmd->getStartZone()); - if (!startzone) - return RespNameNotFound; - - Server_Card *card = startzone->getCard(cmd->getCardId()); - if (!card) - return RespNameNotFound; - - int playerId = cmd->getTargetPlayerId(); - Server_Player *targetPlayer = 0; - Server_CardZone *targetzone = 0; - Server_Card *targetCard = 0; - - if (playerId != -1) { - targetPlayer = game->getPlayer(cmd->getTargetPlayerId()); - if (!targetPlayer) - return RespNameNotFound; - } else if (!card->getParentCard()) - return RespContextError; - if (targetPlayer) - targetzone = targetPlayer->getZones().value(cmd->getTargetZone()); - if (targetzone) { - // This is currently enough to make sure cards don't get attached to a card that is not on the table. - // Possibly a flag will have to be introduced for this sometime. - if (!targetzone->hasCoords()) - return RespContextError; - targetCard = targetzone->getCard(cmd->getTargetCardId()); - if (targetCard) - if (targetCard->getParentCard()) - return RespContextError; - } - if (!startzone->hasCoords()) - return RespContextError; - - // Get all arrows pointing to or originating from the card being attached and delete them. - QMapIterator playerIterator(game->getPlayers()); - while (playerIterator.hasNext()) { - Server_Player *p = playerIterator.next().value(); - QList arrows = p->getArrows().values(); - QList toDelete; - for (int i = 0; i < arrows.size(); ++i) { - Server_Arrow *a = arrows[i]; - Server_Card *tCard = qobject_cast(a->getTargetItem()); - if ((tCard == card) || (a->getStartCard() == card)) - toDelete.append(a); - } - for (int i = 0; i < toDelete.size(); ++i) { - cont->enqueueGameEventPrivate(new Event_DeleteArrow(p->getPlayerId(), toDelete[i]->getId()), game->getGameId()); - cont->enqueueGameEventPublic(new Event_DeleteArrow(p->getPlayerId(), toDelete[i]->getId()), game->getGameId()); - p->deleteArrow(toDelete[i]->getId()); - } - } - - if (targetCard) { - // Unattach all cards attached to the card being attached. - // Make a copy of the list because its contents change during the loop otherwise. - QList attachedList = card->getAttachedCards(); - for (int i = 0; i < attachedList.size(); ++i) - attachedList[i]->getZone()->getPlayer()->unattachCard(cont, attachedList[i]); - - if (targetzone->isColumnStacked(targetCard->getX(), targetCard->getY())) { - CardToMove *cardToMove = new CardToMove(targetCard->getId()); - targetPlayer->moveCard(cont, targetzone, QList() << cardToMove, targetzone, targetzone->getFreeGridColumn(-2, targetCard->getY(), targetCard->getName()), targetCard->getY(), targetCard->getFaceDown()); - delete cardToMove; - } - - card->setParentCard(targetCard); - card->setCoords(-1, card->getY()); - cont->enqueueGameEventPrivate(new Event_AttachCard(player->getPlayerId(), startzone->getName(), card->getId(), targetPlayer->getPlayerId(), targetzone->getName(), targetCard->getId()), game->getGameId()); - cont->enqueueGameEventPublic(new Event_AttachCard(player->getPlayerId(), startzone->getName(), card->getId(), targetPlayer->getPlayerId(), targetzone->getName(), targetCard->getId()), game->getGameId()); - startzone->fixFreeSpaces(cont); - } else - player->unattachCard(cont, card); - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdCreateToken(Command_CreateToken *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_CardZone *zone = player->getZones().value(cmd->getZone()); - if (!zone) - return RespNameNotFound; - - int x = cmd->getX(); - int y = cmd->getY(); - if (zone->hasCoords()) - x = zone->getFreeGridColumn(x, y, cmd->getCardName()); - if (x < 0) - x = 0; - if (y < 0) - y = 0; - - Server_Card *card = new Server_Card(cmd->getCardName(), player->newCardId(), x, y); - card->moveToThread(player->thread()); - card->setPT(cmd->getPt()); - card->setColor(cmd->getColor()); - card->setAnnotation(cmd->getAnnotation()); - card->setDestroyOnZoneChange(cmd->getDestroy()); - - zone->insertCard(card, x, y); - game->sendGameEvent(new Event_CreateToken(player->getPlayerId(), zone->getName(), card->getId(), card->getName(), cmd->getColor(), cmd->getPt(), cmd->getAnnotation(), cmd->getDestroy(), x, y)); - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdCreateArrow(Command_CreateArrow *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_Player *startPlayer = game->getPlayer(cmd->getStartPlayerId()); - Server_Player *targetPlayer = game->getPlayer(cmd->getTargetPlayerId()); - if (!startPlayer || !targetPlayer) - return RespNameNotFound; - Server_CardZone *startZone = startPlayer->getZones().value(cmd->getStartZone()); - bool playerTarget = cmd->getTargetZone().isEmpty(); - Server_CardZone *targetZone = 0; - if (!playerTarget) - targetZone = targetPlayer->getZones().value(cmd->getTargetZone()); - if (!startZone || (!targetZone && !playerTarget)) - return RespNameNotFound; - if (startZone->getType() != PublicZone) - return RespContextError; - Server_Card *startCard = startZone->getCard(cmd->getStartCardId()); - if (!startCard) - return RespNameNotFound; - Server_Card *targetCard = 0; - if (!playerTarget) { - if (targetZone->getType() != PublicZone) - return RespContextError; - targetCard = targetZone->getCard(cmd->getTargetCardId()); - } - - Server_ArrowTarget *targetItem; - if (playerTarget) - targetItem = targetPlayer; - else - targetItem = targetCard; - if (!targetItem) - return RespNameNotFound; - - QMapIterator arrowIterator(player->getArrows()); - while (arrowIterator.hasNext()) { - Server_Arrow *temp = arrowIterator.next().value(); - if ((temp->getStartCard() == startCard) && (temp->getTargetItem() == targetItem)) - return RespContextError; - } - - Server_Arrow *arrow = new Server_Arrow(player->newArrowId(), startCard, targetItem, cmd->getColor()); - player->addArrow(arrow); - game->sendGameEvent(new Event_CreateArrows(player->getPlayerId(), QList() << new ServerInfo_Arrow( - arrow->getId(), - startPlayer->getPlayerId(), - startZone->getName(), - startCard->getId(), - targetPlayer->getPlayerId(), - cmd->getTargetZone(), - cmd->getTargetCardId(), - cmd->getColor() - ))); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdDeleteArrow(Command_DeleteArrow *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - if (!player->deleteArrow(cmd->getArrowId())) - return RespNameNotFound; - - game->sendGameEvent(new Event_DeleteArrow(player->getPlayerId(), cmd->getArrowId())); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdSetCardAttr(Command_SetCardAttr *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - return player->setCardAttrHelper(cont, cmd->getZone(), cmd->getCardId(), cmd->getAttrName(), cmd->getAttrValue()); -} - -ResponseCode Server_ProtocolHandler::cmdSetCardCounter(Command_SetCardCounter *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_CardZone *zone = player->getZones().value(cmd->getZone()); - if (!zone) - return RespNameNotFound; - if (!zone->hasCoords()) - return RespContextError; - - Server_Card *card = zone->getCard(cmd->getCardId()); - if (!card) - return RespNameNotFound; - - card->setCounter(cmd->getCounterId(), cmd->getCounterValue()); - - cont->enqueueGameEventPrivate(new Event_SetCardCounter(player->getPlayerId(), zone->getName(), card->getId(), cmd->getCounterId(), cmd->getCounterValue()), game->getGameId()); - cont->enqueueGameEventPublic(new Event_SetCardCounter(player->getPlayerId(), zone->getName(), card->getId(), cmd->getCounterId(), cmd->getCounterValue()), game->getGameId()); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdIncCardCounter(Command_IncCardCounter *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_CardZone *zone = player->getZones().value(cmd->getZone()); - if (!zone) - return RespNameNotFound; - if (!zone->hasCoords()) - return RespContextError; - - Server_Card *card = zone->getCard(cmd->getCardId()); - if (!card) - return RespNameNotFound; - - int newValue = card->getCounter(cmd->getCounterId()) + cmd->getCounterDelta(); - card->setCounter(cmd->getCounterId(), newValue); - - cont->enqueueGameEventPrivate(new Event_SetCardCounter(player->getPlayerId(), zone->getName(), card->getId(), cmd->getCounterId(), newValue), game->getGameId()); - cont->enqueueGameEventPublic(new Event_SetCardCounter(player->getPlayerId(), zone->getName(), card->getId(), cmd->getCounterId(), newValue), game->getGameId()); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdIncCounter(Command_IncCounter *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - const QMap counters = player->getCounters(); - Server_Counter *c = counters.value(cmd->getCounterId(), 0); - if (!c) - return RespNameNotFound; - - c->setCount(c->getCount() + cmd->getDelta()); - game->sendGameEvent(new Event_SetCounter(player->getPlayerId(), c->getId(), c->getCount())); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdCreateCounter(Command_CreateCounter *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_Counter *c = new Server_Counter(player->newCounterId(), cmd->getCounterName(), cmd->getColor(), cmd->getRadius(), cmd->getValue()); - player->addCounter(c); - game->sendGameEvent(new Event_CreateCounters(player->getPlayerId(), QList() << new ServerInfo_Counter(c->getId(), c->getName(), c->getColor(), c->getRadius(), c->getCount()))); - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdSetCounter(Command_SetCounter *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_Counter *c = player->getCounters().value(cmd->getCounterId(), 0);; - if (!c) - return RespNameNotFound; - - c->setCount(cmd->getValue()); - game->sendGameEvent(new Event_SetCounter(player->getPlayerId(), c->getId(), c->getCount())); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdDelCounter(Command_DelCounter *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - if (!player->deleteCounter(cmd->getCounterId())) - return RespNameNotFound; - game->sendGameEvent(new Event_DelCounter(player->getPlayerId(), cmd->getCounterId())); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdNextTurn(Command_NextTurn * /*cmd*/, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - game->nextTurn(); - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdSetActivePhase(Command_SetActivePhase *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - if (game->getActivePlayer() != player->getPlayerId()) - return RespContextError; - game->setActivePhase(cmd->getPhase()); - - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdDumpZone(Command_DumpZone *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (!game->getGameStarted()) - return RespGameNotStarted; - - Server_Player *otherPlayer = game->getPlayer(cmd->getPlayerId()); - if (!otherPlayer) - return RespNameNotFound; - Server_CardZone *zone = otherPlayer->getZones().value(cmd->getZoneName()); - if (!zone) - return RespNameNotFound; - if (!((zone->getType() == PublicZone) || (player == otherPlayer))) - return RespContextError; - - int numberCards = cmd->getNumberCards(); - QList respCardList; - for (int i = 0; (i < zone->cards.size()) && (i < numberCards || numberCards == -1); ++i) { - Server_Card *card = zone->cards[i]; - QString displayedName = card->getFaceDown() ? QString() : card->getName(); - if (zone->getType() == HiddenZone) - respCardList.append(new ServerInfo_Card(i, displayedName)); - else { - QList cardCounterList; - QMapIterator cardCounterIterator(card->getCounters()); - while (cardCounterIterator.hasNext()) { - cardCounterIterator.next(); - cardCounterList.append(new ServerInfo_CardCounter(cardCounterIterator.key(), cardCounterIterator.value())); - } - - int attachPlayerId = -1; - QString attachZone; - int attachCardId = -1; - if (card->getParentCard()) { - attachPlayerId = card->getParentCard()->getZone()->getPlayer()->getPlayerId(); - attachZone = card->getParentCard()->getZone()->getName(); - attachCardId = card->getParentCard()->getId(); - } - - respCardList.append(new ServerInfo_Card(card->getId(), displayedName, card->getX(), card->getY(), card->getFaceDown(), card->getTapped(), card->getAttacking(), card->getColor(), card->getPT(), card->getAnnotation(), card->getDestroyOnZoneChange(), card->getDoesntUntap(), cardCounterList, attachPlayerId, attachZone, attachCardId)); - } - } - if (zone->getType() == HiddenZone) { - zone->setCardsBeingLookedAt(numberCards); - game->sendGameEvent(new Event_DumpZone(player->getPlayerId(), otherPlayer->getPlayerId(), zone->getName(), numberCards)); - } - cont->setResponse(new Response_DumpZone(cont->getCmdId(), RespOk, new ServerInfo_Zone(zone->getName(), zone->getType(), zone->hasCoords(), numberCards < zone->cards.size() ? zone->cards.size() : numberCards, respCardList))); - return RespNothing; -} - -ResponseCode Server_ProtocolHandler::cmdStopDumpZone(Command_StopDumpZone *cmd, CommandContainer * /*cont*/, Server_Game *game, Server_Player *player) -{ - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_Player *otherPlayer = game->getPlayer(cmd->getPlayerId()); - if (!otherPlayer) - return RespNameNotFound; - Server_CardZone *zone = otherPlayer->getZones().value(cmd->getZoneName()); - if (!zone) - return RespNameNotFound; - - if (zone->getType() == HiddenZone) { - zone->setCardsBeingLookedAt(0); - game->sendGameEvent(new Event_StopDumpZone(player->getPlayerId(), cmd->getPlayerId(), zone->getName())); - } - return RespOk; -} - -ResponseCode Server_ProtocolHandler::cmdRevealCards(Command_RevealCards *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player) -{ - if (player->getSpectator()) - return RespFunctionNotAllowed; - - if (!game->getGameStarted()) - return RespGameNotStarted; - if (player->getConceded()) - return RespContextError; - - Server_Player *otherPlayer = 0; - if (cmd->getPlayerId() != -1) { - otherPlayer = game->getPlayer(cmd->getPlayerId()); - if (!otherPlayer) - return RespNameNotFound; - } - Server_CardZone *zone = player->getZones().value(cmd->getZoneName()); - if (!zone) - return RespNameNotFound; - - QList cardsToReveal; - if (cmd->getCardId() == -1) - cardsToReveal = zone->cards; - else if (cmd->getCardId() == -2) { - if (zone->cards.isEmpty()) - return RespContextError; - cardsToReveal.append(zone->cards.at(rng->getNumber(0, zone->cards.size() - 1))); - } else { - Server_Card *card = zone->getCard(cmd->getCardId()); - if (!card) - return RespNameNotFound; - cardsToReveal.append(card); - } - - QList respCardListPrivate, respCardListOmniscient; - for (int i = 0; i < cardsToReveal.size(); ++i) { - Server_Card *card = cardsToReveal[i]; - - QList cardCounterListPrivate, cardCounterListOmniscient; - QMapIterator cardCounterIterator(card->getCounters()); - while (cardCounterIterator.hasNext()) { - cardCounterIterator.next(); - cardCounterListPrivate.append(new ServerInfo_CardCounter(cardCounterIterator.key(), cardCounterIterator.value())); - cardCounterListOmniscient.append(new ServerInfo_CardCounter(cardCounterIterator.key(), cardCounterIterator.value())); - } - - int attachPlayerId = -1; - QString attachZone; - int attachCardId = -1; - if (card->getParentCard()) { - attachPlayerId = card->getParentCard()->getZone()->getPlayer()->getPlayerId(); - attachZone = card->getParentCard()->getZone()->getName(); - attachCardId = card->getParentCard()->getId(); - } - - if (cmd->getPlayerId() != -1) - respCardListPrivate.append(new ServerInfo_Card(card->getId(), card->getName(), card->getX(), card->getY(), card->getFaceDown(), card->getTapped(), card->getAttacking(), card->getColor(), card->getPT(), card->getAnnotation(), card->getDestroyOnZoneChange(), card->getDoesntUntap(), cardCounterListPrivate, attachPlayerId, attachZone, attachCardId)); - respCardListOmniscient.append(new ServerInfo_Card(card->getId(), card->getName(), card->getX(), card->getY(), card->getFaceDown(), card->getTapped(), card->getAttacking(), card->getColor(), card->getPT(), card->getAnnotation(), card->getDestroyOnZoneChange(), card->getDoesntUntap(), cardCounterListOmniscient, attachPlayerId, attachZone, attachCardId)); - } - - if (cmd->getPlayerId() == -1) - cont->enqueueGameEventPublic(new Event_RevealCards(player->getPlayerId(), zone->getName(), cmd->getCardId(), -1, respCardListOmniscient), game->getGameId()); - else { - cont->enqueueGameEventPublic(new Event_RevealCards(player->getPlayerId(), zone->getName(), cmd->getCardId(), otherPlayer->getPlayerId()), game->getGameId()); - cont->enqueueGameEventPrivate(new Event_RevealCards(player->getPlayerId(), zone->getName(), cmd->getCardId(), otherPlayer->getPlayerId(), respCardListPrivate), game->getGameId(), otherPlayer->getPlayerId()); - cont->enqueueGameEventOmniscient(new Event_RevealCards(player->getPlayerId(), zone->getName(), cmd->getCardId(), otherPlayer->getPlayerId(), respCardListOmniscient), game->getGameId()); - } - - return RespOk; + if (authState == NotLoggedIn) + return Response::RespLoginNeeded; + + return room->processJoinGameCommand(cmd, rc, this); } diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index 05b07ce26..98e0f6cc2 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -4,120 +4,100 @@ #include #include #include "server.h" -#include "protocol.h" -#include "protocol_items.h" +#include "server_abstractuserinterface.h" +#include "pb/response.pb.h" +#include "pb/server_message.pb.h" +class Server_DatabaseInterface; class Server_Player; -class Server_Card; class ServerInfo_User; class Server_Room; class QTimer; -class Server_ProtocolHandler : public QObject { - Q_OBJECT +class ServerMessage; +class Response; +class SessionEvent; +class GameEventContainer; +class RoomEvent; +class ResponseContainer; + +class CommandContainer; +class SessionCommand; +class ModeratorCommand; +class AdminCommand; + +class Command_Ping; +class Command_Login; +class Command_Message; +class Command_ListUsers; +class Command_GetGamesOfUser; +class Command_GetUserInfo; +class Command_ListRooms; +class Command_JoinRoom; +class Command_LeaveRoom; +class Command_RoomSay; +class Command_CreateGame; +class Command_JoinGame; + +class Server_ProtocolHandler : public QObject, public Server_AbstractUserInterface { + Q_OBJECT protected: - Server *server; - QMap > games; - QMap rooms; - - Server *getServer() const { return server; } - QPair getGame(int gameId) const; - - AuthenticationResult authState; - bool acceptsUserListChanges; - bool acceptsRoomListChanges; - ServerInfo_User *userInfo; - QMap buddyList, ignoreList; - - void prepareDestroy(); - virtual bool getCompressionSupport() const = 0; - int sessionId; + QMap rooms; + + bool deleted; + Server_DatabaseInterface *databaseInterface; + AuthenticationResult authState; + bool acceptsUserListChanges; + bool acceptsRoomListChanges; + virtual void logDebugMessage(const QString & /* message */) { } private: - QList itemQueue; - QList messageSizeOverTime, messageCountOverTime; - int timeRunning, lastDataReceived; - QTimer *pingClock; + QList messageSizeOverTime, messageCountOverTime; + int timeRunning, lastDataReceived; + QTimer *pingClock; - virtual DeckList *getDeckFromDatabase(int deckId) = 0; - - ResponseCode cmdPing(Command_Ping *cmd, CommandContainer *cont); - ResponseCode cmdLogin(Command_Login *cmd, CommandContainer *cont); - ResponseCode cmdMessage(Command_Message *cmd, CommandContainer *cont); - virtual ResponseCode cmdAddToList(Command_AddToList *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdRemoveFromList(Command_RemoveFromList *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdDeckList(Command_DeckList *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdDeckNewDir(Command_DeckNewDir *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdDeckDelDir(Command_DeckDelDir *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdDeckDel(Command_DeckDel *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdDeckUpload(Command_DeckUpload *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdDeckDownload(Command_DeckDownload *cmd, CommandContainer *cont) = 0; - ResponseCode cmdGetGamesOfUser(Command_GetGamesOfUser *cmd, CommandContainer *cont); - ResponseCode cmdGetUserInfo(Command_GetUserInfo *cmd, CommandContainer *cont); - ResponseCode cmdListRooms(Command_ListRooms *cmd, CommandContainer *cont); - ResponseCode cmdJoinRoom(Command_JoinRoom *cmd, CommandContainer *cont); - ResponseCode cmdLeaveRoom(Command_LeaveRoom *cmd, CommandContainer *cont, Server_Room *room); - ResponseCode cmdRoomSay(Command_RoomSay *cmd, CommandContainer *cont, Server_Room *room); - ResponseCode cmdListUsers(Command_ListUsers *cmd, CommandContainer *cont); - ResponseCode cmdCreateGame(Command_CreateGame *cmd, CommandContainer *cont, Server_Room *room); - ResponseCode cmdJoinGame(Command_JoinGame *cmd, CommandContainer *cont, Server_Room *room); - ResponseCode cmdLeaveGame(Command_LeaveGame *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdKickFromGame(Command_KickFromGame *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdConcede(Command_Concede *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdReadyStart(Command_ReadyStart *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdDeckSelect(Command_DeckSelect *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdSetSideboardPlan(Command_SetSideboardPlan *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdSay(Command_Say *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdShuffle(Command_Shuffle *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdMulligan(Command_Mulligan *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdRollDie(Command_RollDie *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdDrawCards(Command_DrawCards *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdUndoDraw(Command_UndoDraw *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdMoveCard(Command_MoveCard *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdFlipCard(Command_FlipCard *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdAttachCard(Command_AttachCard *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdCreateToken(Command_CreateToken *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdCreateArrow(Command_CreateArrow *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdDeleteArrow(Command_DeleteArrow *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdSetCardAttr(Command_SetCardAttr *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdSetCardCounter(Command_SetCardCounter *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdIncCardCounter(Command_IncCardCounter *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdIncCounter(Command_IncCounter *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdCreateCounter(Command_CreateCounter *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdSetCounter(Command_SetCounter *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdDelCounter(Command_DelCounter *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdNextTurn(Command_NextTurn *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdSetActivePhase(Command_SetActivePhase *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdDumpZone(Command_DumpZone *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdStopDumpZone(Command_StopDumpZone *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - ResponseCode cmdRevealCards(Command_RevealCards *cmd, CommandContainer *cont, Server_Game *game, Server_Player *player); - virtual ResponseCode cmdBanFromServer(Command_BanFromServer *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdShutdownServer(Command_ShutdownServer *cmd, CommandContainer *cont) = 0; - virtual ResponseCode cmdUpdateServerMessage(Command_UpdateServerMessage *cmd, CommandContainer *cont) = 0; - - ResponseCode processCommandHelper(Command *command, CommandContainer *cont); + virtual void transmitProtocolItem(const ServerMessage &item) = 0; + + Response::ResponseCode cmdPing(const Command_Ping &cmd, ResponseContainer &rc); + Response::ResponseCode cmdLogin(const Command_Login &cmd, ResponseContainer &rc); + Response::ResponseCode cmdMessage(const Command_Message &cmd, ResponseContainer &rc); + Response::ResponseCode cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc); + Response::ResponseCode cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc); + Response::ResponseCode cmdListRooms(const Command_ListRooms &cmd, ResponseContainer &rc); + Response::ResponseCode cmdJoinRoom(const Command_JoinRoom &cmd, ResponseContainer &rc); + Response::ResponseCode cmdListUsers(const Command_ListUsers &cmd, ResponseContainer &rc); + Response::ResponseCode cmdLeaveRoom(const Command_LeaveRoom &cmd, Server_Room *room, ResponseContainer &rc); + Response::ResponseCode cmdRoomSay(const Command_RoomSay &cmd, Server_Room *room, ResponseContainer &rc); + Response::ResponseCode cmdCreateGame(const Command_CreateGame &cmd, Server_Room *room, ResponseContainer &rc); + Response::ResponseCode cmdJoinGame(const Command_JoinGame &cmd, Server_Room *room, ResponseContainer &rc); + + Response::ResponseCode processSessionCommandContainer(const CommandContainer &cont, ResponseContainer &rc); + virtual Response::ResponseCode processExtendedSessionCommand(int /* cmdType */, const SessionCommand & /* cmd */, ResponseContainer & /* rc */) { return Response::RespFunctionNotAllowed; } + Response::ResponseCode processRoomCommandContainer(const CommandContainer &cont, ResponseContainer &rc); + Response::ResponseCode processGameCommandContainer(const CommandContainer &cont, ResponseContainer &rc); + Response::ResponseCode processModeratorCommandContainer(const CommandContainer &cont, ResponseContainer &rc); + virtual Response::ResponseCode processExtendedModeratorCommand(int /* cmdType */, const ModeratorCommand & /* cmd */, ResponseContainer & /* rc */) { return Response::RespFunctionNotAllowed; } + Response::ResponseCode processAdminCommandContainer(const CommandContainer &cont, ResponseContainer &rc); + virtual Response::ResponseCode processExtendedAdminCommand(int /* cmdType */, const AdminCommand & /* cmd */, ResponseContainer & /* rc */) { return Response::RespFunctionNotAllowed; } private slots: - void pingClockTimeout(); + void pingClockTimeout(); +public slots: + void prepareDestroy(); public: - QMutex gameListMutex; - - Server_ProtocolHandler(Server *_server, QObject *parent = 0); - ~Server_ProtocolHandler(); - void playerRemovedFromGame(Server_Game *game); - - bool getAcceptsUserListChanges() const { return acceptsUserListChanges; } - bool getAcceptsRoomListChanges() const { return acceptsRoomListChanges; } - ServerInfo_User *getUserInfo() const { return userInfo; } - virtual QString getAddress() const = 0; - void setUserInfo(ServerInfo_User *_userInfo) { userInfo = _userInfo; } - const QMap &getBuddyList() const { return buddyList; } - const QMap &getIgnoreList() const { return ignoreList; } - int getSessionId() const { return sessionId; } - void setSessionId(int _sessionId) { sessionId = _sessionId; } + Server_ProtocolHandler(Server *_server, Server_DatabaseInterface *_databaseInterface, QObject *parent = 0); + ~Server_ProtocolHandler(); + + bool getAcceptsUserListChanges() const { return acceptsUserListChanges; } + bool getAcceptsRoomListChanges() const { return acceptsRoomListChanges; } + virtual QString getAddress() const = 0; + Server_DatabaseInterface *getDatabaseInterface() const { return databaseInterface; } - int getLastCommandTime() const { return timeRunning - lastDataReceived; } - void processCommandContainer(CommandContainer *cont); - virtual void sendProtocolItem(ProtocolItem *item, bool deleteItem = true) = 0; - void enqueueProtocolItem(ProtocolItem *item); + int getLastCommandTime() const { return timeRunning - lastDataReceived; } + void processCommandContainer(const CommandContainer &cont); + + void sendProtocolItem(const Response &item); + void sendProtocolItem(const SessionEvent &item); + void sendProtocolItem(const GameEventContainer &item); + void sendProtocolItem(const RoomEvent &item); }; #endif diff --git a/common/server_remoteuserinterface.cpp b/common/server_remoteuserinterface.cpp new file mode 100644 index 000000000..7bbcf9632 --- /dev/null +++ b/common/server_remoteuserinterface.cpp @@ -0,0 +1,23 @@ +#include "server_remoteuserinterface.h" +#include "server.h" +#include "pb/serverinfo_user.pb.h" + +void Server_RemoteUserInterface::sendProtocolItem(const Response &item) +{ + server->sendIsl_Response(item, userInfo->server_id(), userInfo->session_id()); +} + +void Server_RemoteUserInterface::sendProtocolItem(const SessionEvent &item) +{ + server->sendIsl_SessionEvent(item, userInfo->server_id(), userInfo->session_id()); +} + +void Server_RemoteUserInterface::sendProtocolItem(const GameEventContainer &item) +{ + server->sendIsl_GameEventContainer(item, userInfo->server_id(), userInfo->session_id()); +} + +void Server_RemoteUserInterface::sendProtocolItem(const RoomEvent &item) +{ + server->sendIsl_RoomEvent(item, userInfo->server_id(), userInfo->session_id()); +} diff --git a/common/server_remoteuserinterface.h b/common/server_remoteuserinterface.h new file mode 100644 index 000000000..311f0516b --- /dev/null +++ b/common/server_remoteuserinterface.h @@ -0,0 +1,18 @@ +#ifndef SERVER_REMOTEUSERINTERFACE_H +#define SERVER_REMOTEUSERINTERFACE_H + +#include "server_abstractuserinterface.h" + +class Server_RemoteUserInterface : public Server_AbstractUserInterface { +public: + Server_RemoteUserInterface(Server *_server, const ServerInfo_User_Container &_userInfoContainer) : Server_AbstractUserInterface(_server, _userInfoContainer) { } + + int getLastCommandTime() const { return 0; } + + void sendProtocolItem(const Response &item); + void sendProtocolItem(const SessionEvent &item); + void sendProtocolItem(const GameEventContainer &item); + void sendProtocolItem(const RoomEvent &item); +}; + +#endif diff --git a/common/server_response_containers.cpp b/common/server_response_containers.cpp new file mode 100644 index 000000000..e327ee9df --- /dev/null +++ b/common/server_response_containers.cpp @@ -0,0 +1,78 @@ +#include "server_response_containers.h" +#include +#include "server_game.h" + +GameEventStorageItem::GameEventStorageItem(const ::google::protobuf::Message &_event, int _playerId, EventRecipients _recipients) + : event(new GameEvent), recipients(_recipients) +{ + event->GetReflection()->MutableMessage(event, _event.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(_event); + event->set_player_id(_playerId); +} + +GameEventStorageItem::~GameEventStorageItem() +{ + delete event; +} + +GameEventStorage::GameEventStorage() + : gameEventContext(0), privatePlayerId(0) +{ +} + +GameEventStorage::~GameEventStorage() +{ + delete gameEventContext; + for (int i = 0; i < gameEventList.size(); ++i) + delete gameEventList[i]; +} + +void GameEventStorage::setGameEventContext(const ::google::protobuf::Message &_gameEventContext) +{ + delete gameEventContext; + gameEventContext = new GameEventContext; + gameEventContext->GetReflection()->MutableMessage(gameEventContext, _gameEventContext.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(_gameEventContext); +} + +void GameEventStorage::enqueueGameEvent(const ::google::protobuf::Message &event, int playerId, GameEventStorageItem::EventRecipients recipients, int _privatePlayerId) +{ + gameEventList.append(new GameEventStorageItem(event, playerId, recipients)); + if (_privatePlayerId != -1) + privatePlayerId = _privatePlayerId; +} + +void GameEventStorage::sendToGame(Server_Game *game) +{ + if (gameEventList.isEmpty()) + return; + + GameEventContainer *contPrivate = new GameEventContainer; + GameEventContainer *contOthers = new GameEventContainer; + for (int i = 0; i < gameEventList.size(); ++i) { + const GameEvent &event = gameEventList[i]->getGameEvent(); + const GameEventStorageItem::EventRecipients recipients = gameEventList[i]->getRecipients(); + if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) + contPrivate->add_event_list()->CopyFrom(event); + if (recipients.testFlag(GameEventStorageItem::SendToOthers)) + contOthers->add_event_list()->CopyFrom(event); + } + if (gameEventContext) { + contPrivate->mutable_context()->CopyFrom(*gameEventContext); + contOthers->mutable_context()->CopyFrom(*gameEventContext); + } + game->sendGameEventContainer(contPrivate, GameEventStorageItem::SendToPrivate, privatePlayerId); + game->sendGameEventContainer(contOthers, GameEventStorageItem::SendToOthers, privatePlayerId); +} + +ResponseContainer::ResponseContainer(int _cmdId) + : cmdId(_cmdId), responseExtension(0) +{ +} + +ResponseContainer::~ResponseContainer() +{ + delete responseExtension; + for (int i = 0; i < preResponseQueue.size(); ++i) + delete preResponseQueue[i].second; + for (int i = 0; i < postResponseQueue.size(); ++i) + delete postResponseQueue[i].second; +} diff --git a/common/server_response_containers.h b/common/server_response_containers.h new file mode 100644 index 000000000..5363f8021 --- /dev/null +++ b/common/server_response_containers.h @@ -0,0 +1,63 @@ +#ifndef SERVER_RESPONSE_CONTAINERS_H +#define SERVER_RESPONSE_CONTAINERS_H + +#include +#include +#include "pb/server_message.pb.h" + +namespace google { namespace protobuf { class Message; } } +class Server_Game; + +class GameEventStorageItem { +public: + enum EventRecipient { SendToPrivate = 0x01, SendToOthers = 0x02}; + Q_DECLARE_FLAGS(EventRecipients, EventRecipient) +private: + GameEvent *event; + EventRecipients recipients; +public: + GameEventStorageItem(const ::google::protobuf::Message &_event, int _playerId, EventRecipients _recipients); + ~GameEventStorageItem(); + + const GameEvent &getGameEvent() const { return *event; } + EventRecipients getRecipients() const { return recipients; } +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(GameEventStorageItem::EventRecipients) + +class GameEventStorage { +private: + ::google::protobuf::Message *gameEventContext; + QList gameEventList; + int privatePlayerId; +public: + GameEventStorage(); + ~GameEventStorage(); + + void setGameEventContext(const ::google::protobuf::Message &_gameEventContext); + ::google::protobuf::Message *getGameEventContext() const { return gameEventContext; } + const QList &getGameEventList() const { return gameEventList; } + int getPrivatePlayerId() const { return privatePlayerId; } + + void enqueueGameEvent(const ::google::protobuf::Message &event, int playerId, GameEventStorageItem::EventRecipients recipients = GameEventStorageItem::SendToPrivate | GameEventStorageItem::SendToOthers, int _privatePlayerId = -1); + void sendToGame(Server_Game *game); +}; + +class ResponseContainer { +private: + int cmdId; + ::google::protobuf::Message *responseExtension; + QList > preResponseQueue, postResponseQueue; +public: + ResponseContainer(int _cmdId); + ~ResponseContainer(); + + int getCmdId() const { return cmdId; } + void setResponseExtension(::google::protobuf::Message *_responseExtension) { responseExtension = _responseExtension; } + ::google::protobuf::Message *getResponseExtension() const { return responseExtension; } + void enqueuePreResponseItem(ServerMessage::MessageType type, ::google::protobuf::Message *item) { preResponseQueue.append(qMakePair(type, item)); } + void enqueuePostResponseItem(ServerMessage::MessageType type, ::google::protobuf::Message *item) { postResponseQueue.append(qMakePair(type, item)); } + const QList > &getPreResponseQueue() const { return preResponseQueue; } + const QList > &getPostResponseQueue() const { return postResponseQueue; } +}; + +#endif diff --git a/common/server_room.cpp b/common/server_room.cpp index 9e9aa571c..985fb543b 100644 --- a/common/server_room.cpp +++ b/common/server_room.cpp @@ -3,142 +3,336 @@ #include "server_game.h" #include +#include "pb/commands.pb.h" +#include "pb/room_commands.pb.h" +#include "pb/event_join_room.pb.h" +#include "pb/event_leave_room.pb.h" +#include "pb/event_list_games.pb.h" +#include "pb/event_room_say.pb.h" +#include "pb/serverinfo_room.pb.h" +#include + Server_Room::Server_Room(int _id, const QString &_name, const QString &_description, bool _autoJoin, const QString &_joinMessage, const QStringList &_gameTypes, Server *parent) - : QObject(parent), id(_id), name(_name), description(_description), autoJoin(_autoJoin), joinMessage(_joinMessage), gameTypes(_gameTypes), roomMutex(QMutex::Recursive) + : QObject(parent), id(_id), name(_name), description(_description), autoJoin(_autoJoin), joinMessage(_joinMessage), gameTypes(_gameTypes), gamesLock(QReadWriteLock::Recursive) { + connect(this, SIGNAL(gameListChanged(ServerInfo_Game)), this, SLOT(broadcastGameListUpdate(ServerInfo_Game)), Qt::QueuedConnection); } Server_Room::~Server_Room() { - QMutexLocker locker(&roomMutex); - qDebug("Server_Room destructor"); - - const QList gameList = games.values(); - for (int i = 0; i < gameList.size(); ++i) - delete gameList[i]; - games.clear(); - - clear(); + qDebug("Server_Room destructor"); + + gamesLock.lockForWrite(); + const QList gameList = games.values(); + for (int i = 0; i < gameList.size(); ++i) + delete gameList[i]; + games.clear(); + gamesLock.unlock(); + + usersLock.lockForWrite(); + users.clear(); + usersLock.unlock(); } Server *Server_Room::getServer() const { - return static_cast(parent()); + return static_cast(parent()); } -ServerInfo_Room *Server_Room::getInfo(bool complete, bool showGameTypes) const +const ServerInfo_Room &Server_Room::getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes, bool includeExternalData) const { - QMutexLocker locker(&roomMutex); - - QList gameList; - QList userList; - QList gameTypeList; - if (complete) { - QMapIterator gameIterator(games); - while (gameIterator.hasNext()) - gameList.append(gameIterator.next().value()->getInfo()); - - for (int i = 0; i < size(); ++i) - userList.append(new ServerInfo_User(at(i)->getUserInfo(), false)); - } - if (complete || showGameTypes) - for (int i = 0; i < gameTypes.size(); ++i) - gameTypeList.append(new ServerInfo_GameType(i, gameTypes[i])); - - return new ServerInfo_Room(id, name, description, games.size(), size(), autoJoin, gameList, userList, gameTypeList); + result.set_room_id(id); + + result.set_name(name.toStdString()); + result.set_description(description.toStdString()); + result.set_auto_join(autoJoin); + + gamesLock.lockForRead(); + result.set_game_count(games.size() + externalGames.size()); + if (complete) { + QMapIterator gameIterator(games); + while (gameIterator.hasNext()) + gameIterator.next().value()->getInfo(*result.add_game_list()); + if (includeExternalData) { + QMapIterator externalGameIterator(externalGames); + while (externalGameIterator.hasNext()) + result.add_game_list()->CopyFrom(externalGameIterator.next().value()); + } + } + gamesLock.unlock(); + + usersLock.lockForRead(); + result.set_player_count(users.size() + externalUsers.size()); + if (complete) { + QMapIterator userIterator(users); + while (userIterator.hasNext()) + result.add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(false)); + if (includeExternalData) { + QMapIterator externalUserIterator(externalUsers); + while (externalUserIterator.hasNext()) + result.add_user_list()->CopyFrom(externalUserIterator.next().value().copyUserInfo(false)); + } + } + usersLock.unlock(); + + if (complete || showGameTypes) + for (int i = 0; i < gameTypes.size(); ++i) { + ServerInfo_GameType *gameTypeInfo = result.add_gametype_list(); + gameTypeInfo->set_game_type_id(i); + gameTypeInfo->set_description(gameTypes[i].toStdString()); + } + + return result; +} + +RoomEvent *Server_Room::prepareRoomEvent(const ::google::protobuf::Message &roomEvent) +{ + RoomEvent *event = new RoomEvent; + event->set_room_id(id); + event->GetReflection()->MutableMessage(event, roomEvent.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(roomEvent); + return event; } void Server_Room::addClient(Server_ProtocolHandler *client) { - QMutexLocker locker(&roomMutex); - - sendRoomEvent(new Event_JoinRoom(id, new ServerInfo_User(client->getUserInfo(), false))); - append(client); - emit roomInfoChanged(); + Event_JoinRoom event; + event.mutable_user_info()->CopyFrom(client->copyUserInfo(false)); + sendRoomEvent(prepareRoomEvent(event)); + + ServerInfo_Room roomInfo; + roomInfo.set_room_id(id); + + usersLock.lockForWrite(); + users.insert(QString::fromStdString(client->getUserInfo()->name()), client); + roomInfo.set_player_count(users.size() + externalUsers.size()); + usersLock.unlock(); + + // XXX This can be removed during the next client update. + gamesLock.lockForRead(); + roomInfo.set_game_count(games.size() + externalGames.size()); + gamesLock.unlock(); + // ----------- + + emit roomInfoChanged(roomInfo); } void Server_Room::removeClient(Server_ProtocolHandler *client) { - QMutexLocker locker(&roomMutex); - - removeAt(indexOf(client)); - sendRoomEvent(new Event_LeaveRoom(id, client->getUserInfo()->getName())); - emit roomInfoChanged(); + usersLock.lockForWrite(); + users.remove(QString::fromStdString(client->getUserInfo()->name())); + + ServerInfo_Room roomInfo; + roomInfo.set_room_id(id); + roomInfo.set_player_count(users.size() + externalUsers.size()); + usersLock.unlock(); + + Event_LeaveRoom event; + event.set_name(client->getUserInfo()->name()); + sendRoomEvent(prepareRoomEvent(event)); + + // XXX This can be removed during the next client update. + gamesLock.lockForRead(); + roomInfo.set_game_count(games.size() + externalGames.size()); + gamesLock.unlock(); + // ----------- + + emit roomInfoChanged(roomInfo); } -void Server_Room::say(Server_ProtocolHandler *client, const QString &s) +void Server_Room::addExternalUser(const ServerInfo_User &userInfo) { - sendRoomEvent(new Event_RoomSay(id, client->getUserInfo()->getName(), s)); + // This function is always called from the Server thread with server->roomsMutex locked. + ServerInfo_User_Container userInfoContainer(userInfo); + Event_JoinRoom event; + event.mutable_user_info()->CopyFrom(userInfoContainer.copyUserInfo(false)); + sendRoomEvent(prepareRoomEvent(event), false); + + ServerInfo_Room roomInfo; + roomInfo.set_room_id(id); + + usersLock.lockForWrite(); + externalUsers.insert(QString::fromStdString(userInfo.name()), userInfoContainer); + roomInfo.set_player_count(users.size() + externalUsers.size()); + usersLock.unlock(); + + emit roomInfoChanged(roomInfo); } -void Server_Room::sendRoomEvent(RoomEvent *event) +void Server_Room::removeExternalUser(const QString &name) { - QMutexLocker locker(&roomMutex); - - for (int i = 0; i < size(); ++i) - at(i)->sendProtocolItem(event, false); - delete event; + // This function is always called from the Server thread with server->roomsMutex locked. + ServerInfo_Room roomInfo; + roomInfo.set_room_id(id); + + usersLock.lockForWrite(); + if (externalUsers.contains(name)) + externalUsers.remove(name); + roomInfo.set_player_count(users.size() + externalUsers.size()); + usersLock.unlock(); + + Event_LeaveRoom event; + event.set_name(name.toStdString()); + sendRoomEvent(prepareRoomEvent(event), false); + + emit roomInfoChanged(roomInfo); } -void Server_Room::broadcastGameListUpdate(Server_Game *game) +void Server_Room::updateExternalGameList(const ServerInfo_Game &gameInfo) { - QMutexLocker locker(&roomMutex); - - Event_ListGames *event = new Event_ListGames(id, QList() << game->getInfo()); - - for (int i = 0; i < size(); i++) - at(i)->sendProtocolItem(event, false); - delete event; + // This function is always called from the Server thread with server->roomsMutex locked. + ServerInfo_Room roomInfo; + roomInfo.set_room_id(id); + + gamesLock.lockForWrite(); + if (!gameInfo.has_player_count() && externalGames.contains(gameInfo.game_id())) + externalGames.remove(gameInfo.game_id()); + else + externalGames.insert(gameInfo.game_id(), gameInfo); + roomInfo.set_game_count(games.size() + externalGames.size()); + gamesLock.unlock(); + + broadcastGameListUpdate(gameInfo, false); + emit roomInfoChanged(roomInfo); } -Server_Game *Server_Room::createGame(const QString &description, const QString &password, int maxPlayers, const QList &gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator) +Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGame &cmd, ResponseContainer &rc, Server_AbstractUserInterface *userInterface) { - QMutexLocker locker(&roomMutex); - - Server_Game *newGame = new Server_Game(creator, static_cast(parent())->getNextGameId(), description, password, maxPlayers, gameTypes, onlyBuddies, onlyRegistered, spectatorsAllowed, spectatorsNeedPassword, spectatorsCanTalk, spectatorsSeeEverything, this); - newGame->moveToThread(thread()); - // This mutex needs to be unlocked by the caller. - newGame->gameMutex.lock(); - games.insert(newGame->getGameId(), newGame); - - broadcastGameListUpdate(newGame); - - emit roomInfoChanged(); - - return newGame; + // This function is called from the Server thread and from the S_PH thread. + // server->roomsMutex is always locked. + + QReadLocker roomGamesLocker(&gamesLock); + Server_Game *g = games.value(cmd.game_id()); + if (!g) { + if (externalGames.contains(cmd.game_id())) { + CommandContainer cont; + cont.set_cmd_id(rc.getCmdId()); + RoomCommand *roomCommand = cont.add_room_command(); + roomCommand->GetReflection()->MutableMessage(roomCommand, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd); + getServer()->sendIsl_RoomCommand(cont, externalGames.value(cmd.game_id()).server_id(), userInterface->getUserInfo()->session_id(), id); + + return Response::RespNothing; + } else + return Response::RespNameNotFound; + } + + QMutexLocker gameLocker(&g->gameMutex); + + Response::ResponseCode result = g->checkJoin(userInterface->getUserInfo(), QString::fromStdString(cmd.password()), cmd.spectator(), cmd.override_restrictions()); + if (result == Response::RespOk) + g->addPlayer(userInterface, rc, cmd.spectator()); + + return result; +} + + +void Server_Room::say(const QString &userName, const QString &s, bool sendToIsl) +{ + Event_RoomSay event; + event.set_name(userName.toStdString()); + event.set_message(s.toStdString()); + sendRoomEvent(prepareRoomEvent(event), sendToIsl); +} + +void Server_Room::sendRoomEvent(RoomEvent *event, bool sendToIsl) +{ + usersLock.lockForRead(); + { + QMapIterator userIterator(users); + while (userIterator.hasNext()) + userIterator.next().value()->sendProtocolItem(*event); + } + usersLock.unlock(); + + if (sendToIsl) + static_cast(parent())->sendIsl_RoomEvent(*event); + + delete event; +} + +void Server_Room::broadcastGameListUpdate(const ServerInfo_Game &gameInfo, bool sendToIsl) +{ + Event_ListGames event; + event.add_game_list()->CopyFrom(gameInfo); + sendRoomEvent(prepareRoomEvent(event), sendToIsl); +} + +void Server_Room::addGame(Server_Game *game) +{ + ServerInfo_Room roomInfo; + roomInfo.set_room_id(id); + + gamesLock.lockForWrite(); + connect(game, SIGNAL(gameInfoChanged(ServerInfo_Game)), this, SLOT(broadcastGameListUpdate(ServerInfo_Game))); + + game->gameMutex.lock(); + games.insert(game->getGameId(), game); + ServerInfo_Game gameInfo; + game->getInfo(gameInfo); + roomInfo.set_game_count(games.size() + externalGames.size()); + game->gameMutex.unlock(); + gamesLock.unlock(); + + // XXX This can be removed during the next client update. + usersLock.lockForRead(); + roomInfo.set_player_count(users.size() + externalUsers.size()); + usersLock.unlock(); + // ----------- + + emit gameListChanged(gameInfo); + emit roomInfoChanged(roomInfo); } void Server_Room::removeGame(Server_Game *game) { - // No need to lock roomMutex or gameMutex. This method is only - // called from ~Server_Game, which locks both mutexes anyway beforehand. - - broadcastGameListUpdate(game); - games.remove(game->getGameId()); - - emit roomInfoChanged(); + // No need to lock gamesLock or gameMutex. This method is only + // called from ~Server_Game, which locks both mutexes anyway beforehand. + + disconnect(game, 0, this, 0); + + ServerInfo_Game gameInfo; + game->getInfo(gameInfo); + emit gameListChanged(gameInfo); + + games.remove(game->getGameId()); + + ServerInfo_Room roomInfo; + roomInfo.set_room_id(id); + roomInfo.set_game_count(games.size() + externalGames.size()); + + // XXX This can be removed during the next client update. + usersLock.lockForRead(); + roomInfo.set_player_count(users.size() + externalUsers.size()); + usersLock.unlock(); + // ----------- + + emit roomInfoChanged(roomInfo); } int Server_Room::getGamesCreatedByUser(const QString &userName) const { - QMutexLocker locker(&roomMutex); - - QMapIterator gamesIterator(games); - int result = 0; - while (gamesIterator.hasNext()) - if (gamesIterator.next().value()->getCreatorInfo()->getName() == userName) - ++result; - return result; + QReadLocker locker(&gamesLock); + + QMapIterator gamesIterator(games); + int result = 0; + while (gamesIterator.hasNext()) + if (gamesIterator.next().value()->getCreatorInfo()->name() == userName.toStdString()) + ++result; + return result; } -QList Server_Room::getGamesOfUser(const QString &userName) const +QList Server_Room::getGamesOfUser(const QString &userName) const { - QList result; - QMapIterator gamesIterator(games); - while (gamesIterator.hasNext()) { - Server_Game *game = gamesIterator.next().value(); - if (game->containsUser(userName)) - result.append(game->getInfo()); - } - return result; + QReadLocker locker(&gamesLock); + + QList result; + QMapIterator gamesIterator(games); + while (gamesIterator.hasNext()) { + Server_Game *game = gamesIterator.next().value(); + if (game->containsUser(userName)) { + ServerInfo_Game gameInfo; + game->getInfo(gameInfo); + result.append(gameInfo); + } + } + return result; } diff --git a/common/server_room.h b/common/server_room.h index 663c9cee3..bf91510cb 100644 --- a/common/server_room.h +++ b/common/server_room.h @@ -6,7 +6,11 @@ #include #include #include +#include +#include "serverinfo_user_container.h" +#include "pb/response.pb.h" +class Server_DatabaseInterface; class Server_ProtocolHandler; class RoomEvent; class ServerInfo_User; @@ -15,41 +19,63 @@ class ServerInfo_Game; class Server_Game; class Server; -class Server_Room : public QObject, public QList { - Q_OBJECT +class Command_JoinGame; +class ResponseContainer; +class Server_AbstractUserInterface; + +class Server_Room : public QObject { + Q_OBJECT signals: - void roomInfoChanged(); + void roomInfoChanged(const ServerInfo_Room &roomInfo); + void gameListChanged(const ServerInfo_Game &gameInfo); private: - int id; - QString name; - QString description; - bool autoJoin; - QString joinMessage; - QStringList gameTypes; - QMap games; + int id; + QString name; + QString description; + bool autoJoin; + QString joinMessage; + QStringList gameTypes; + QMap games; + QMap externalGames; + QMap users; + QMap externalUsers; +private slots: + void broadcastGameListUpdate(const ServerInfo_Game &gameInfo, bool sendToIsl = true); public: - mutable QMutex roomMutex; - Server_Room(int _id, const QString &_name, const QString &_description, bool _autoJoin, const QString &_joinMessage, const QStringList &_gameTypes, Server *parent); - ~Server_Room(); - int getId() const { return id; } - QString getName() const { return name; } - QString getDescription() const { return description; } - bool getAutoJoin() const { return autoJoin; } - QString getJoinMessage() const { return joinMessage; } - const QMap &getGames() const { return games; } - Server *getServer() const; - ServerInfo_Room *getInfo(bool complete, bool showGameTypes = false) const; - int getGamesCreatedByUser(const QString &name) const; - QList getGamesOfUser(const QString &name) const; - - void addClient(Server_ProtocolHandler *client); - void removeClient(Server_ProtocolHandler *client); - void say(Server_ProtocolHandler *client, const QString &s); - void broadcastGameListUpdate(Server_Game *game); - Server_Game *createGame(const QString &description, const QString &password, int maxPlayers, const QList &_gameTypes, bool onlyBuddies, bool onlyRegistered, bool spectatorsAllowed, bool spectatorsNeedPassword, bool spectatorsCanTalk, bool spectatorsSeeEverything, Server_ProtocolHandler *creator); - void removeGame(Server_Game *game); - - void sendRoomEvent(RoomEvent *event); + mutable QReadWriteLock usersLock; + mutable QReadWriteLock gamesLock; + Server_Room(int _id, const QString &_name, const QString &_description, bool _autoJoin, const QString &_joinMessage, const QStringList &_gameTypes, Server *parent); + ~Server_Room(); + int getId() const { return id; } + QString getName() const { return name; } + QString getDescription() const { return description; } + bool getAutoJoin() const { return autoJoin; } + QString getJoinMessage() const { return joinMessage; } + const QStringList &getGameTypes() const { return gameTypes; } + const QMap &getGames() const { return games; } + const QMap &getExternalGames() const { return externalGames; } + Server *getServer() const; + const ServerInfo_Room &getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes = false, bool includeExternalData = true) const; + int getGamesCreatedByUser(const QString &name) const; + QList getGamesOfUser(const QString &name) const; + + void addClient(Server_ProtocolHandler *client); + void removeClient(Server_ProtocolHandler *client); + + void addExternalUser(const ServerInfo_User &userInfo); + void removeExternalUser(const QString &name); + const QMap &getExternalUsers() const { return externalUsers; } + void updateExternalGameList(const ServerInfo_Game &gameInfo); + + Response::ResponseCode processJoinGameCommand(const Command_JoinGame &cmd, ResponseContainer &rc, Server_AbstractUserInterface *userInterface); + + void say(const QString &userName, const QString &s, bool sendToIsl = true); + + void addGame(Server_Game *game); + void removeGame(Server_Game *game); + + void sendRoomEvent(RoomEvent *event, bool sendToIsl = true); + RoomEvent *prepareRoomEvent(const ::google::protobuf::Message &roomEvent); }; #endif diff --git a/common/serverinfo_user_container.cpp b/common/serverinfo_user_container.cpp new file mode 100644 index 000000000..14435f755 --- /dev/null +++ b/common/serverinfo_user_container.cpp @@ -0,0 +1,52 @@ +#include "serverinfo_user_container.h" +#include "pb/serverinfo_user.pb.h" + +ServerInfo_User_Container::ServerInfo_User_Container(ServerInfo_User *_userInfo) + : userInfo(_userInfo) +{ +} + +ServerInfo_User_Container::ServerInfo_User_Container(const ServerInfo_User &_userInfo) + : userInfo(new ServerInfo_User(_userInfo)) +{ +} + +ServerInfo_User_Container::ServerInfo_User_Container(const ServerInfo_User_Container &other) +{ + if (other.userInfo) + userInfo = new ServerInfo_User(*other.userInfo); + else + userInfo = 0; +} + +ServerInfo_User_Container::~ServerInfo_User_Container() +{ + delete userInfo; +} + +void ServerInfo_User_Container::setUserInfo(const ServerInfo_User &_userInfo) +{ + userInfo = new ServerInfo_User(_userInfo); +} + +ServerInfo_User &ServerInfo_User_Container::copyUserInfo(ServerInfo_User &result, bool complete, bool internalInfo, bool sessionInfo) const +{ + if (userInfo) { + result.CopyFrom(*userInfo); + if (!sessionInfo) { + result.clear_session_id(); + result.clear_address(); + } + if (!internalInfo) + result.clear_id(); + if (!complete) + result.clear_avatar_bmp(); + } + return result; +} + +ServerInfo_User ServerInfo_User_Container::copyUserInfo(bool complete, bool internalInfo, bool sessionInfo) const +{ + ServerInfo_User result; + return copyUserInfo(result, complete, internalInfo, sessionInfo); +} diff --git a/common/serverinfo_user_container.h b/common/serverinfo_user_container.h new file mode 100644 index 000000000..d7fb76f8f --- /dev/null +++ b/common/serverinfo_user_container.h @@ -0,0 +1,20 @@ +#ifndef SERVERINFO_USER_CONTAINER +#define SERVERINFO_USER_CONTAINER + +class ServerInfo_User; + +class ServerInfo_User_Container { +protected: + ServerInfo_User *userInfo; +public: + ServerInfo_User_Container(ServerInfo_User *_userInfo = 0); + ServerInfo_User_Container(const ServerInfo_User &_userInfo); + ServerInfo_User_Container(const ServerInfo_User_Container &other); + virtual ~ServerInfo_User_Container(); + ServerInfo_User *getUserInfo() const { return userInfo; } + void setUserInfo(const ServerInfo_User &_userInfo); + ServerInfo_User ©UserInfo(ServerInfo_User &result, bool complete, bool internalInfo = false, bool sessionInfo = false) const; + ServerInfo_User copyUserInfo(bool complete, bool internalInfo = false, bool sessionInfo = false) const; +}; + +#endif diff --git a/common/sfmt/LICENSE.txt b/common/sfmt/LICENSE.txt index decb29915..6f9c4a6d6 100644 --- a/common/sfmt/LICENSE.txt +++ b/common/sfmt/LICENSE.txt @@ -1,5 +1,8 @@ Copyright (c) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima -University. All rights reserved. +University. +Copyright (c) 2012 Mutsuo Saito, Makoto Matsumoto, Hiroshima University +and The University of Tokyo. +All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -11,10 +14,10 @@ met: copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of the Hiroshima University nor the names of - its contributors may be used to endorse or promote products - derived from this software without specific prior written - permission. + * Neither the names of Hiroshima University, The University of + Tokyo nor the names of its contributors may be used to endorse + or promote products derived from this software without specific + prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT diff --git a/common/sfmt/SFMT-common.h b/common/sfmt/SFMT-common.h new file mode 100644 index 000000000..c7d8aa9fb --- /dev/null +++ b/common/sfmt/SFMT-common.h @@ -0,0 +1,164 @@ +#pragma once +/** + * @file SFMT-common.h + * + * @brief SIMD oriented Fast Mersenne Twister(SFMT) pseudorandom + * number generator with jump function. This file includes common functions + * used in random number generation and jump. + * + * @author Mutsuo Saito (Hiroshima University) + * @author Makoto Matsumoto (The University of Tokyo) + * + * Copyright (C) 2006, 2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima + * University. + * Copyright (C) 2012 Mutsuo Saito, Makoto Matsumoto, Hiroshima + * University and The University of Tokyo. + * All rights reserved. + * + * The 3-clause BSD License is applied to this software, see + * LICENSE.txt + */ +#ifndef SFMT_COMMON_H +#define SFMT_COMMON_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#include "SFMT.h" + +inline static void do_recursion(w128_t * r, w128_t * a, w128_t * b, + w128_t * c, w128_t * d); + +inline static void rshift128(w128_t *out, w128_t const *in, int shift); +inline static void lshift128(w128_t *out, w128_t const *in, int shift); + +/** + * This function simulates SIMD 128-bit right shift by the standard C. + * The 128-bit integer given in in is shifted by (shift * 8) bits. + * This function simulates the LITTLE ENDIAN SIMD. + * @param out the output of this function + * @param in the 128-bit data to be shifted + * @param shift the shift value + */ +#ifdef ONLY64 +inline static void rshift128(w128_t *out, w128_t const *in, int shift) { + uint64_t th, tl, oh, ol; + + th = ((uint64_t)in->u[2] << 32) | ((uint64_t)in->u[3]); + tl = ((uint64_t)in->u[0] << 32) | ((uint64_t)in->u[1]); + + oh = th >> (shift * 8); + ol = tl >> (shift * 8); + ol |= th << (64 - shift * 8); + out->u[0] = (uint32_t)(ol >> 32); + out->u[1] = (uint32_t)ol; + out->u[2] = (uint32_t)(oh >> 32); + out->u[3] = (uint32_t)oh; +} +#else +inline static void rshift128(w128_t *out, w128_t const *in, int shift) +{ + uint64_t th, tl, oh, ol; + + th = ((uint64_t)in->u[3] << 32) | ((uint64_t)in->u[2]); + tl = ((uint64_t)in->u[1] << 32) | ((uint64_t)in->u[0]); + + oh = th >> (shift * 8); + ol = tl >> (shift * 8); + ol |= th << (64 - shift * 8); + out->u[1] = (uint32_t)(ol >> 32); + out->u[0] = (uint32_t)ol; + out->u[3] = (uint32_t)(oh >> 32); + out->u[2] = (uint32_t)oh; +} +#endif +/** + * This function simulates SIMD 128-bit left shift by the standard C. + * The 128-bit integer given in in is shifted by (shift * 8) bits. + * This function simulates the LITTLE ENDIAN SIMD. + * @param out the output of this function + * @param in the 128-bit data to be shifted + * @param shift the shift value + */ +#ifdef ONLY64 +inline static void lshift128(w128_t *out, w128_t const *in, int shift) { + uint64_t th, tl, oh, ol; + + th = ((uint64_t)in->u[2] << 32) | ((uint64_t)in->u[3]); + tl = ((uint64_t)in->u[0] << 32) | ((uint64_t)in->u[1]); + + oh = th << (shift * 8); + ol = tl << (shift * 8); + oh |= tl >> (64 - shift * 8); + out->u[0] = (uint32_t)(ol >> 32); + out->u[1] = (uint32_t)ol; + out->u[2] = (uint32_t)(oh >> 32); + out->u[3] = (uint32_t)oh; +} +#else +inline static void lshift128(w128_t *out, w128_t const *in, int shift) +{ + uint64_t th, tl, oh, ol; + + th = ((uint64_t)in->u[3] << 32) | ((uint64_t)in->u[2]); + tl = ((uint64_t)in->u[1] << 32) | ((uint64_t)in->u[0]); + + oh = th << (shift * 8); + ol = tl << (shift * 8); + oh |= tl >> (64 - shift * 8); + out->u[1] = (uint32_t)(ol >> 32); + out->u[0] = (uint32_t)ol; + out->u[3] = (uint32_t)(oh >> 32); + out->u[2] = (uint32_t)oh; +} +#endif +/** + * This function represents the recursion formula. + * @param r output + * @param a a 128-bit part of the internal state array + * @param b a 128-bit part of the internal state array + * @param c a 128-bit part of the internal state array + * @param d a 128-bit part of the internal state array + */ +#ifdef ONLY64 +inline static void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c, + w128_t *d) { + w128_t x; + w128_t y; + + lshift128(&x, a, SFMT_SL2); + rshift128(&y, c, SFMT_SR2); + r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SFMT_SR1) & SFMT_MSK2) ^ y.u[0] + ^ (d->u[0] << SFMT_SL1); + r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SFMT_SR1) & SFMT_MSK1) ^ y.u[1] + ^ (d->u[1] << SFMT_SL1); + r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SFMT_SR1) & SFMT_MSK4) ^ y.u[2] + ^ (d->u[2] << SFMT_SL1); + r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SFMT_SR1) & SFMT_MSK3) ^ y.u[3] + ^ (d->u[3] << SFMT_SL1); +} +#else +inline static void do_recursion(w128_t *r, w128_t *a, w128_t *b, + w128_t *c, w128_t *d) +{ + w128_t x; + w128_t y; + + lshift128(&x, a, SFMT_SL2); + rshift128(&y, c, SFMT_SR2); + r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SFMT_SR1) & SFMT_MSK1) + ^ y.u[0] ^ (d->u[0] << SFMT_SL1); + r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SFMT_SR1) & SFMT_MSK2) + ^ y.u[1] ^ (d->u[1] << SFMT_SL1); + r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SFMT_SR1) & SFMT_MSK3) + ^ y.u[2] ^ (d->u[2] << SFMT_SL1); + r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SFMT_SR1) & SFMT_MSK4) + ^ y.u[3] ^ (d->u[3] << SFMT_SL1); +} +#endif +#endif + +#if defined(__cplusplus) +} +#endif diff --git a/common/sfmt/SFMT-params.h b/common/sfmt/SFMT-params.h index 661bbf26a..372e6f11a 100644 --- a/common/sfmt/SFMT-params.h +++ b/common/sfmt/SFMT-params.h @@ -1,95 +1,96 @@ +#pragma once #ifndef SFMT_PARAMS_H #define SFMT_PARAMS_H -#if !defined(MEXP) -#ifdef __GNUC__ - #warning "MEXP is not defined. I assume MEXP is 19937." +#if !defined(SFMT_MEXP) +#if defined(__GNUC__) && !defined(__ICC) + #warning "SFMT_MEXP is not defined. I assume MEXP is 19937." #endif - #define MEXP 19937 + #define SFMT_MEXP 19937 #endif /*----------------- BASIC DEFINITIONS -----------------*/ -/** Mersenne Exponent. The period of the sequence +/** Mersenne Exponent. The period of the sequence * is a multiple of 2^MEXP-1. - * #define MEXP 19937 */ + * #define SFMT_MEXP 19937 */ /** SFMT generator has an internal state array of 128-bit integers, * and N is its size. */ -#define N (MEXP / 128 + 1) +#define SFMT_N (SFMT_MEXP / 128 + 1) /** N32 is the size of internal state array when regarded as an array * of 32-bit integers.*/ -#define N32 (N * 4) +#define SFMT_N32 (SFMT_N * 4) /** N64 is the size of internal state array when regarded as an array * of 64-bit integers.*/ -#define N64 (N * 2) +#define SFMT_N64 (SFMT_N * 2) /*---------------------- the parameters of SFMT following definitions are in paramsXXXX.h file. ----------------------*/ /** the pick up position of the array. -#define POS1 122 +#define SFMT_POS1 122 */ /** the parameter of shift left as four 32-bit registers. -#define SL1 18 +#define SFMT_SL1 18 */ -/** the parameter of shift left as one 128-bit register. - * The 128-bit integer is shifted by (SL2 * 8) bits. -#define SL2 1 +/** the parameter of shift left as one 128-bit register. + * The 128-bit integer is shifted by (SFMT_SL2 * 8) bits. +#define SFMT_SL2 1 */ /** the parameter of shift right as four 32-bit registers. -#define SR1 11 +#define SFMT_SR1 11 */ -/** the parameter of shift right as one 128-bit register. - * The 128-bit integer is shifted by (SL2 * 8) bits. -#define SR2 1 +/** the parameter of shift right as one 128-bit register. + * The 128-bit integer is shifted by (SFMT_SL2 * 8) bits. +#define SFMT_SR21 1 */ /** A bitmask, used in the recursion. These parameters are introduced * to break symmetry of SIMD. -#define MSK1 0xdfffffefU -#define MSK2 0xddfecb7fU -#define MSK3 0xbffaffffU -#define MSK4 0xbffffff6U +#define SFMT_MSK1 0xdfffffefU +#define SFMT_MSK2 0xddfecb7fU +#define SFMT_MSK3 0xbffaffffU +#define SFMT_MSK4 0xbffffff6U */ /** These definitions are part of a 128-bit period certification vector. -#define PARITY1 0x00000001U -#define PARITY2 0x00000000U -#define PARITY3 0x00000000U -#define PARITY4 0xc98e126aU +#define SFMT_PARITY1 0x00000001U +#define SFMT_PARITY2 0x00000000U +#define SFMT_PARITY3 0x00000000U +#define SFMT_PARITY4 0xc98e126aU */ -#if MEXP == 607 +#if SFMT_MEXP == 607 #include "SFMT-params607.h" -#elif MEXP == 1279 +#elif SFMT_MEXP == 1279 #include "SFMT-params1279.h" -#elif MEXP == 2281 +#elif SFMT_MEXP == 2281 #include "SFMT-params2281.h" -#elif MEXP == 4253 +#elif SFMT_MEXP == 4253 #include "SFMT-params4253.h" -#elif MEXP == 11213 +#elif SFMT_MEXP == 11213 #include "SFMT-params11213.h" -#elif MEXP == 19937 +#elif SFMT_MEXP == 19937 #include "SFMT-params19937.h" -#elif MEXP == 44497 +#elif SFMT_MEXP == 44497 #include "SFMT-params44497.h" -#elif MEXP == 86243 +#elif SFMT_MEXP == 86243 #include "SFMT-params86243.h" -#elif MEXP == 132049 +#elif SFMT_MEXP == 132049 #include "SFMT-params132049.h" -#elif MEXP == 216091 +#elif SFMT_MEXP == 216091 #include "SFMT-params216091.h" #else -#ifdef __GNUC__ - #error "MEXP is not valid." - #undef MEXP +#if defined(__GNUC__) && !defined(__ICC) + #error "SFMT_MEXP is not valid." + #undef SFMT_MEXP #else - #undef MEXP + #undef SFMT_MEXP #endif #endif diff --git a/common/sfmt/SFMT-params19937.h b/common/sfmt/SFMT-params19937.h index 04708cdf3..fc49fa14e 100644 --- a/common/sfmt/SFMT-params19937.h +++ b/common/sfmt/SFMT-params19937.h @@ -1,46 +1,50 @@ +#pragma once #ifndef SFMT_PARAMS19937_H #define SFMT_PARAMS19937_H -#define POS1 122 -#define SL1 18 -#define SL2 1 -#define SR1 11 -#define SR2 1 -#define MSK1 0xdfffffefU -#define MSK2 0xddfecb7fU -#define MSK3 0xbffaffffU -#define MSK4 0xbffffff6U -#define PARITY1 0x00000001U -#define PARITY2 0x00000000U -#define PARITY3 0x00000000U -#define PARITY4 0x13c9e684U +#define SFMT_POS1 122 +#define SFMT_SL1 18 +#define SFMT_SL2 1 +#define SFMT_SR1 11 +#define SFMT_SR2 1 +#define SFMT_MSK1 0xdfffffefU +#define SFMT_MSK2 0xddfecb7fU +#define SFMT_MSK3 0xbffaffffU +#define SFMT_MSK4 0xbffffff6U +#define SFMT_PARITY1 0x00000001U +#define SFMT_PARITY2 0x00000000U +#define SFMT_PARITY3 0x00000000U +#define SFMT_PARITY4 0x13c9e684U /* PARAMETERS FOR ALTIVEC */ #if defined(__APPLE__) /* For OSX */ - #define ALTI_SL1 (vector unsigned int)(SL1, SL1, SL1, SL1) - #define ALTI_SR1 (vector unsigned int)(SR1, SR1, SR1, SR1) - #define ALTI_MSK (vector unsigned int)(MSK1, MSK2, MSK3, MSK4) - #define ALTI_MSK64 \ - (vector unsigned int)(MSK2, MSK1, MSK4, MSK3) - #define ALTI_SL2_PERM \ + #define SFMT_ALTI_SL1 \ + (vector unsigned int)(SFMT_SL1, SFMT_SL1, SFMT_SL1, SFMT_SL1) + #define SFMT_ALTI_SR1 \ + (vector unsigned int)(SFMT_SR1, SFMT_SR1, SFMT_SR1, SFMT_SR1) + #define SFMT_ALTI_MSK \ + (vector unsigned int)(SFMT_MSK1, SFMT_MSK2, SFMT_MSK3, SFMT_MSK4) + #define SFMT_ALTI_MSK64 \ + (vector unsigned int)(SFMT_MSK2, SFMT_MSK1, SFMT_MSK4, SFMT_MSK3) + #define SFMT_ALTI_SL2_PERM \ (vector unsigned char)(1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8) - #define ALTI_SL2_PERM64 \ + #define SFMT_ALTI_SL2_PERM64 \ (vector unsigned char)(1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0) - #define ALTI_SR2_PERM \ + #define SFMT_ALTI_SR2_PERM \ (vector unsigned char)(7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14) - #define ALTI_SR2_PERM64 \ + #define SFMT_ALTI_SR2_PERM64 \ (vector unsigned char)(15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14) #else /* For OTHER OSs(Linux?) */ - #define ALTI_SL1 {SL1, SL1, SL1, SL1} - #define ALTI_SR1 {SR1, SR1, SR1, SR1} - #define ALTI_MSK {MSK1, MSK2, MSK3, MSK4} - #define ALTI_MSK64 {MSK2, MSK1, MSK4, MSK3} - #define ALTI_SL2_PERM {1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8} - #define ALTI_SL2_PERM64 {1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0} - #define ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} - #define ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} + #define SFMT_ALTI_SL1 {SFMT_SL1, SFMT_SL1, SFMT_SL1, SFMT_SL1} + #define SFMT_ALTI_SR1 {SFMT_SR1, SFMT_SR1, SFMT_SR1, SFMT_SR1} + #define SFMT_ALTI_MSK {SFMT_MSK1, SFMT_MSK2, SFMT_MSK3, SFMT_MSK4} + #define SFMT_ALTI_MSK64 {SFMT_MSK2, SFMT_MSK1, SFMT_MSK4, SFMT_MSK3} + #define SFMT_ALTI_SL2_PERM {1,2,3,23,5,6,7,0,9,10,11,4,13,14,15,8} + #define SFMT_ALTI_SL2_PERM64 {1,2,3,4,5,6,7,31,9,10,11,12,13,14,15,0} + #define SFMT_ALTI_SR2_PERM {7,0,1,2,11,4,5,6,15,8,9,10,17,12,13,14} + #define SFMT_ALTI_SR2_PERM64 {15,0,1,2,3,4,5,6,17,8,9,10,11,12,13,14} #endif /* For OSX */ -#define IDSTR "SFMT-19937:122-18-1-11-1:dfffffef-ddfecb7f-bffaffff-bffffff6" +#define SFMT_IDSTR "SFMT-19937:122-18-1-11-1:dfffffef-ddfecb7f-bffaffff-bffffff6" #endif /* SFMT_PARAMS19937_H */ diff --git a/common/sfmt/SFMT.c b/common/sfmt/SFMT.c index d36465d9e..2652df7de 100644 --- a/common/sfmt/SFMT.c +++ b/common/sfmt/SFMT.c @@ -1,19 +1,31 @@ -/** +/** * @file SFMT.c * @brief SIMD oriented Fast Mersenne Twister(SFMT) * * @author Mutsuo Saito (Hiroshima University) * @author Makoto Matsumoto (Hiroshima University) * - * Copyright (C) 2006,2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima - * University. All rights reserved. + * Copyright (C) 2006, 2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima + * University. + * Copyright (C) 2012 Mutsuo Saito, Makoto Matsumoto, Hiroshima + * University and The University of Tokyo. + * Copyright (C) 2013 Mutsuo Saito, Makoto Matsumoto and Hiroshima + * University. + * All rights reserved. * - * The new BSD License is applied to this software, see LICENSE.txt + * The 3-clause BSD License is applied to this software, see + * LICENSE.txt */ + +#if defined(__cplusplus) +extern "C" { +#endif + #include #include #include "SFMT.h" #include "SFMT-params.h" +#include "SFMT-common.h" #if defined(__BIG_ENDIAN__) && !defined(__amd64) && !defined(BIG_ENDIAN64) #define BIG_ENDIAN64 1 @@ -27,74 +39,20 @@ #endif #undef ONLY64 #endif -/*------------------------------------------------------ - 128-bit SIMD data type for Altivec, SSE2 or standard C - ------------------------------------------------------*/ -#if defined(HAVE_ALTIVEC) - #if !defined(__APPLE__) - #include - #endif -/** 128-bit data structure */ -union W128_T { - vector unsigned int s; - uint32_t u[4]; -}; -/** 128-bit data type */ -typedef union W128_T w128_t; - -#elif defined(HAVE_SSE2) - #include - -/** 128-bit data structure */ -union W128_T { - __m128i si; - uint32_t u[4]; -}; -/** 128-bit data type */ -typedef union W128_T w128_t; - -#else - -/** 128-bit data structure */ -struct W128_T { - uint32_t u[4]; -}; -/** 128-bit data type */ -typedef struct W128_T w128_t; - -#endif - -/*-------------------------------------- - FILE GLOBAL VARIABLES - internal state, index counter and flag - --------------------------------------*/ -/** the 128-bit internal state array */ -static w128_t sfmt[N]; -/** the 32bit integer pointer to the 128-bit internal state array */ -static uint32_t *psfmt32 = &sfmt[0].u[0]; -#if !defined(BIG_ENDIAN64) || defined(ONLY64) -/** the 64bit integer pointer to the 128-bit internal state array */ -static uint64_t *psfmt64 = (uint64_t *)&sfmt[0].u[0]; -#endif -/** index counter to the 32-bit internal state array */ -static int idx; -/** a flag: it is 0 if and only if the internal state is not yet - * initialized. */ -static int initialized = 0; -/** a parity check vector which certificate the period of 2^{MEXP} */ -static uint32_t parity[4] = {PARITY1, PARITY2, PARITY3, PARITY4}; +/** + * parameters used by sse2. + */ +static const w128_t sse2_param_mask = {{SFMT_MSK1, SFMT_MSK2, + SFMT_MSK3, SFMT_MSK4}}; /*---------------- STATIC FUNCTIONS ----------------*/ inline static int idxof(int i); -inline static void rshift128(w128_t *out, w128_t const *in, int shift); -inline static void lshift128(w128_t *out, w128_t const *in, int shift); -inline static void gen_rand_all(void); -inline static void gen_rand_array(w128_t *array, int size); +inline static void gen_rand_array(sfmt_t * sfmt, w128_t *array, int size); inline static uint32_t func1(uint32_t x); inline static uint32_t func2(uint32_t x); -static void period_certification(void); +static void period_certification(sfmt_t * sfmt); #if defined(BIG_ENDIAN64) && !defined(ONLY64) inline static void swap(w128_t *array, int size); #endif @@ -102,11 +60,15 @@ inline static void swap(w128_t *array, int size); #if defined(HAVE_ALTIVEC) #include "SFMT-alti.h" #elif defined(HAVE_SSE2) - #include "SFMT-sse2.h" + #if defined(_MSC_VER) + #include "SFMT-sse2-msc.h" + #else + #include "SFMT-sse2.h" + #endif #endif /** - * This function simulate a 64-bit index of LITTLE ENDIAN + * This function simulate a 64-bit index of LITTLE ENDIAN * in BIG ENDIAN machine. */ #ifdef ONLY64 @@ -118,190 +80,48 @@ inline static int idxof(int i) { return i; } #endif -/** - * This function simulates SIMD 128-bit right shift by the standard C. - * The 128-bit integer given in in is shifted by (shift * 8) bits. - * This function simulates the LITTLE ENDIAN SIMD. - * @param out the output of this function - * @param in the 128-bit data to be shifted - * @param shift the shift value - */ -#ifdef ONLY64 -inline static void rshift128(w128_t *out, w128_t const *in, int shift) { - uint64_t th, tl, oh, ol; - - th = ((uint64_t)in->u[2] << 32) | ((uint64_t)in->u[3]); - tl = ((uint64_t)in->u[0] << 32) | ((uint64_t)in->u[1]); - - oh = th >> (shift * 8); - ol = tl >> (shift * 8); - ol |= th << (64 - shift * 8); - out->u[0] = (uint32_t)(ol >> 32); - out->u[1] = (uint32_t)ol; - out->u[2] = (uint32_t)(oh >> 32); - out->u[3] = (uint32_t)oh; -} -#else -inline static void rshift128(w128_t *out, w128_t const *in, int shift) { - uint64_t th, tl, oh, ol; - - th = ((uint64_t)in->u[3] << 32) | ((uint64_t)in->u[2]); - tl = ((uint64_t)in->u[1] << 32) | ((uint64_t)in->u[0]); - - oh = th >> (shift * 8); - ol = tl >> (shift * 8); - ol |= th << (64 - shift * 8); - out->u[1] = (uint32_t)(ol >> 32); - out->u[0] = (uint32_t)ol; - out->u[3] = (uint32_t)(oh >> 32); - out->u[2] = (uint32_t)oh; -} -#endif -/** - * This function simulates SIMD 128-bit left shift by the standard C. - * The 128-bit integer given in in is shifted by (shift * 8) bits. - * This function simulates the LITTLE ENDIAN SIMD. - * @param out the output of this function - * @param in the 128-bit data to be shifted - * @param shift the shift value - */ -#ifdef ONLY64 -inline static void lshift128(w128_t *out, w128_t const *in, int shift) { - uint64_t th, tl, oh, ol; - - th = ((uint64_t)in->u[2] << 32) | ((uint64_t)in->u[3]); - tl = ((uint64_t)in->u[0] << 32) | ((uint64_t)in->u[1]); - - oh = th << (shift * 8); - ol = tl << (shift * 8); - oh |= tl >> (64 - shift * 8); - out->u[0] = (uint32_t)(ol >> 32); - out->u[1] = (uint32_t)ol; - out->u[2] = (uint32_t)(oh >> 32); - out->u[3] = (uint32_t)oh; -} -#else -inline static void lshift128(w128_t *out, w128_t const *in, int shift) { - uint64_t th, tl, oh, ol; - - th = ((uint64_t)in->u[3] << 32) | ((uint64_t)in->u[2]); - tl = ((uint64_t)in->u[1] << 32) | ((uint64_t)in->u[0]); - - oh = th << (shift * 8); - ol = tl << (shift * 8); - oh |= tl >> (64 - shift * 8); - out->u[1] = (uint32_t)(ol >> 32); - out->u[0] = (uint32_t)ol; - out->u[3] = (uint32_t)(oh >> 32); - out->u[2] = (uint32_t)oh; -} -#endif - -/** - * This function represents the recursion formula. - * @param r output - * @param a a 128-bit part of the internal state array - * @param b a 128-bit part of the internal state array - * @param c a 128-bit part of the internal state array - * @param d a 128-bit part of the internal state array - */ -#if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2)) -#ifdef ONLY64 -inline static void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c, - w128_t *d) { - w128_t x; - w128_t y; - - lshift128(&x, a, SL2); - rshift128(&y, c, SR2); - r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK2) ^ y.u[0] - ^ (d->u[0] << SL1); - r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK1) ^ y.u[1] - ^ (d->u[1] << SL1); - r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK4) ^ y.u[2] - ^ (d->u[2] << SL1); - r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK3) ^ y.u[3] - ^ (d->u[3] << SL1); -} -#else -inline static void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c, - w128_t *d) { - w128_t x; - w128_t y; - - lshift128(&x, a, SL2); - rshift128(&y, c, SR2); - r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK1) ^ y.u[0] - ^ (d->u[0] << SL1); - r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK2) ^ y.u[1] - ^ (d->u[1] << SL1); - r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK3) ^ y.u[2] - ^ (d->u[2] << SL1); - r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK4) ^ y.u[3] - ^ (d->u[3] << SL1); -} -#endif -#endif #if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2)) -/** - * This function fills the internal state array with pseudorandom - * integers. - */ -inline static void gen_rand_all(void) { - int i; - w128_t *r1, *r2; - - r1 = &sfmt[N - 2]; - r2 = &sfmt[N - 1]; - for (i = 0; i < N - POS1; i++) { - do_recursion(&sfmt[i], &sfmt[i], &sfmt[i + POS1], r1, r2); - r1 = r2; - r2 = &sfmt[i]; - } - for (; i < N; i++) { - do_recursion(&sfmt[i], &sfmt[i], &sfmt[i + POS1 - N], r1, r2); - r1 = r2; - r2 = &sfmt[i]; - } -} - /** * This function fills the user-specified array with pseudorandom * integers. * - * @param array an 128-bit array to be filled by pseudorandom numbers. + * @param sfmt SFMT internal state + * @param array an 128-bit array to be filled by pseudorandom numbers. * @param size number of 128-bit pseudorandom numbers to be generated. */ -inline static void gen_rand_array(w128_t *array, int size) { +inline static void gen_rand_array(sfmt_t * sfmt, w128_t *array, int size) { int i, j; w128_t *r1, *r2; - r1 = &sfmt[N - 2]; - r2 = &sfmt[N - 1]; - for (i = 0; i < N - POS1; i++) { - do_recursion(&array[i], &sfmt[i], &sfmt[i + POS1], r1, r2); - r1 = r2; - r2 = &array[i]; + r1 = &sfmt->state[SFMT_N - 2]; + r2 = &sfmt->state[SFMT_N - 1]; + for (i = 0; i < SFMT_N - SFMT_POS1; i++) { + do_recursion(&array[i], &sfmt->state[i], &sfmt->state[i + SFMT_POS1], r1, r2); + r1 = r2; + r2 = &array[i]; } - for (; i < N; i++) { - do_recursion(&array[i], &sfmt[i], &array[i + POS1 - N], r1, r2); - r1 = r2; - r2 = &array[i]; + for (; i < SFMT_N; i++) { + do_recursion(&array[i], &sfmt->state[i], + &array[i + SFMT_POS1 - SFMT_N], r1, r2); + r1 = r2; + r2 = &array[i]; } - for (; i < size - N; i++) { - do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2); - r1 = r2; - r2 = &array[i]; + for (; i < size - SFMT_N; i++) { + do_recursion(&array[i], &array[i - SFMT_N], + &array[i + SFMT_POS1 - SFMT_N], r1, r2); + r1 = r2; + r2 = &array[i]; } - for (j = 0; j < 2 * N - size; j++) { - sfmt[j] = array[j + size - N]; + for (j = 0; j < 2 * SFMT_N - size; j++) { + sfmt->state[j] = array[j + size - SFMT_N]; } for (; i < size; i++, j++) { - do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2); - r1 = r2; - r2 = &array[i]; - sfmt[j] = array[i]; + do_recursion(&array[i], &array[i - SFMT_N], + &array[i + SFMT_POS1 - SFMT_N], r1, r2); + r1 = r2; + r2 = &array[i]; + sfmt->state[j] = array[i]; } } #endif @@ -312,12 +132,12 @@ inline static void swap(w128_t *array, int size) { uint32_t x, y; for (i = 0; i < size; i++) { - x = array[i].u[0]; - y = array[i].u[2]; - array[i].u[0] = array[i].u[1]; - array[i].u[2] = array[i].u[3]; - array[i].u[1] = x; - array[i].u[3] = y; + x = array[i].u[0]; + y = array[i].u[2]; + array[i].u[0] = array[i].u[1]; + array[i].u[2] = array[i].u[3]; + array[i].u[1] = x; + array[i].u[3] = y; } } #endif @@ -343,114 +163,101 @@ static uint32_t func2(uint32_t x) { /** * This function certificate the period of 2^{MEXP} + * @param sfmt SFMT internal state */ -static void period_certification(void) { +static void period_certification(sfmt_t * sfmt) { int inner = 0; int i, j; uint32_t work; + uint32_t *psfmt32 = &sfmt->state[0].u[0]; + const uint32_t parity[4] = {SFMT_PARITY1, SFMT_PARITY2, + SFMT_PARITY3, SFMT_PARITY4}; for (i = 0; i < 4; i++) - inner ^= psfmt32[idxof(i)] & parity[i]; + inner ^= psfmt32[idxof(i)] & parity[i]; for (i = 16; i > 0; i >>= 1) - inner ^= inner >> i; + inner ^= inner >> i; inner &= 1; /* check OK */ if (inner == 1) { - return; + return; } /* check NG, and modification */ for (i = 0; i < 4; i++) { - work = 1; - for (j = 0; j < 32; j++) { - if ((work & parity[i]) != 0) { - psfmt32[idxof(i)] ^= work; - return; - } - work = work << 1; - } + work = 1; + for (j = 0; j < 32; j++) { + if ((work & parity[i]) != 0) { + psfmt32[idxof(i)] ^= work; + return; + } + work = work << 1; + } } } /*---------------- PUBLIC FUNCTIONS ----------------*/ +#define UNUSED_VARIABLE(x) (void)(x) /** * This function returns the identification string. * The string shows the word size, the Mersenne exponent, * and all parameters of this generator. + * @param sfmt SFMT internal state */ -const char *get_idstring(void) { - return IDSTR; +const char *sfmt_get_idstring(sfmt_t * sfmt) { + UNUSED_VARIABLE(sfmt); + return SFMT_IDSTR; } /** * This function returns the minimum size of array used for \b * fill_array32() function. + * @param sfmt SFMT internal state * @return minimum size of array used for fill_array32() function. */ -int get_min_array_size32(void) { - return N32; +int sfmt_get_min_array_size32(sfmt_t * sfmt) { + UNUSED_VARIABLE(sfmt); + return SFMT_N32; } /** * This function returns the minimum size of array used for \b * fill_array64() function. + * @param sfmt SFMT internal state * @return minimum size of array used for fill_array64() function. */ -int get_min_array_size64(void) { - return N64; +int sfmt_get_min_array_size64(sfmt_t * sfmt) { + UNUSED_VARIABLE(sfmt); + return SFMT_N64; } -#ifndef ONLY64 +#if !defined(HAVE_SSE2) && !defined(HAVE_ALTIVEC) /** - * This function generates and returns 32-bit pseudorandom number. - * init_gen_rand or init_by_array must be called before this function. - * @return 32-bit pseudorandom number + * This function fills the internal state array with pseudorandom + * integers. + * @param sfmt SFMT internal state */ -uint32_t gen_rand32(void) { - uint32_t r; +void sfmt_gen_rand_all(sfmt_t * sfmt) { + int i; + w128_t *r1, *r2; - assert(initialized); - if (idx >= N32) { - gen_rand_all(); - idx = 0; + r1 = &sfmt->state[SFMT_N - 2]; + r2 = &sfmt->state[SFMT_N - 1]; + for (i = 0; i < SFMT_N - SFMT_POS1; i++) { + do_recursion(&sfmt->state[i], &sfmt->state[i], + &sfmt->state[i + SFMT_POS1], r1, r2); + r1 = r2; + r2 = &sfmt->state[i]; + } + for (; i < SFMT_N; i++) { + do_recursion(&sfmt->state[i], &sfmt->state[i], + &sfmt->state[i + SFMT_POS1 - SFMT_N], r1, r2); + r1 = r2; + r2 = &sfmt->state[i]; } - r = psfmt32[idx++]; - return r; } #endif -/** - * This function generates and returns 64-bit pseudorandom number. - * init_gen_rand or init_by_array must be called before this function. - * The function gen_rand64 should not be called after gen_rand32, - * unless an initialization is again executed. - * @return 64-bit pseudorandom number - */ -uint64_t gen_rand64(void) { -#if defined(BIG_ENDIAN64) && !defined(ONLY64) - uint32_t r1, r2; -#else - uint64_t r; -#endif - - assert(initialized); - assert(idx % 2 == 0); - - if (idx >= N32) { - gen_rand_all(); - idx = 0; - } -#if defined(BIG_ENDIAN64) && !defined(ONLY64) - r1 = psfmt32[idx]; - r2 = psfmt32[idx + 1]; - idx += 2; - return ((uint64_t)r2 << 32) | r1; -#else - r = psfmt64[idx / 2]; - idx += 2; - return r; -#endif -} #ifndef ONLY64 /** @@ -464,6 +271,7 @@ uint64_t gen_rand64(void) { * before the first call of this function. This function can not be * used after calling gen_rand function, without initialization. * + * @param sfmt SFMT internal state * @param array an array where pseudorandom 32-bit integers are filled * by this function. The pointer to the array must be \b "aligned" * (namely, must be a multiple of 16) in the SIMD version, since it @@ -478,14 +286,13 @@ uint64_t gen_rand64(void) { * memory. Mac OSX doesn't have these functions, but \b malloc of OSX * returns the pointer to the aligned memory block. */ -void fill_array32(uint32_t *array, int size) { - assert(initialized); - assert(idx == N32); +void sfmt_fill_array32(sfmt_t * sfmt, uint32_t *array, int size) { + assert(sfmt->idx == SFMT_N32); assert(size % 4 == 0); - assert(size >= N32); + assert(size >= SFMT_N32); - gen_rand_array((w128_t *)array, size / 4); - idx = N32; + gen_rand_array(sfmt, (w128_t *)array, size / 4); + sfmt->idx = SFMT_N32; } #endif @@ -496,6 +303,7 @@ void fill_array32(uint32_t *array, int size) { * multiple of two. The generation by this function is much faster * than the following gen_rand function. * + * @param sfmt SFMT internal state * For initialization, init_gen_rand or init_by_array must be called * before the first call of this function. This function can not be * used after calling gen_rand function, without initialization. @@ -514,14 +322,13 @@ void fill_array32(uint32_t *array, int size) { * memory. Mac OSX doesn't have these functions, but \b malloc of OSX * returns the pointer to the aligned memory block. */ -void fill_array64(uint64_t *array, int size) { - assert(initialized); - assert(idx == N32); +void sfmt_fill_array64(sfmt_t * sfmt, uint64_t *array, int size) { + assert(sfmt->idx == SFMT_N32); assert(size % 2 == 0); - assert(size >= N64); + assert(size >= SFMT_N64); - gen_rand_array((w128_t *)array, size / 2); - idx = N32; + gen_rand_array(sfmt, (w128_t *)array, size / 2); + sfmt->idx = SFMT_N32; #if defined(BIG_ENDIAN64) && !defined(ONLY64) swap((w128_t *)array, size /2); @@ -532,54 +339,58 @@ void fill_array64(uint64_t *array, int size) { * This function initializes the internal state array with a 32-bit * integer seed. * + * @param sfmt SFMT internal state * @param seed a 32-bit integer used as the seed. */ -void init_gen_rand(uint32_t seed) { +void sfmt_init_gen_rand(sfmt_t * sfmt, uint32_t seed) { int i; + uint32_t *psfmt32 = &sfmt->state[0].u[0]; + psfmt32[idxof(0)] = seed; - for (i = 1; i < N32; i++) { - psfmt32[idxof(i)] = 1812433253UL * (psfmt32[idxof(i - 1)] - ^ (psfmt32[idxof(i - 1)] >> 30)) - + i; + for (i = 1; i < SFMT_N32; i++) { + psfmt32[idxof(i)] = 1812433253UL * (psfmt32[idxof(i - 1)] + ^ (psfmt32[idxof(i - 1)] >> 30)) + + i; } - idx = N32; - period_certification(); - initialized = 1; + sfmt->idx = SFMT_N32; + period_certification(sfmt); } /** * This function initializes the internal state array, * with an array of 32-bit integers used as the seeds + * @param sfmt SFMT internal state * @param init_key the array of 32-bit integers, used as a seed. * @param key_length the length of init_key. */ -void init_by_array(uint32_t *init_key, int key_length) { +void sfmt_init_by_array(sfmt_t * sfmt, uint32_t *init_key, int key_length) { int i, j, count; uint32_t r; int lag; int mid; - int size = N * 4; + int size = SFMT_N * 4; + uint32_t *psfmt32 = &sfmt->state[0].u[0]; if (size >= 623) { - lag = 11; + lag = 11; } else if (size >= 68) { - lag = 7; + lag = 7; } else if (size >= 39) { - lag = 5; + lag = 5; } else { - lag = 3; + lag = 3; } mid = (size - lag) / 2; - memset(sfmt, 0x8b, sizeof(sfmt)); - if (key_length + 1 > N32) { - count = key_length + 1; + memset(sfmt, 0x8b, sizeof(sfmt_t)); + if (key_length + 1 > SFMT_N32) { + count = key_length + 1; } else { - count = N32; + count = SFMT_N32; } - r = func1(psfmt32[idxof(0)] ^ psfmt32[idxof(mid)] - ^ psfmt32[idxof(N32 - 1)]); + r = func1(psfmt32[idxof(0)] ^ psfmt32[idxof(mid)] + ^ psfmt32[idxof(SFMT_N32 - 1)]); psfmt32[idxof(mid)] += r; r += key_length; psfmt32[idxof(mid + lag)] += r; @@ -587,34 +398,36 @@ void init_by_array(uint32_t *init_key, int key_length) { count--; for (i = 1, j = 0; (j < count) && (j < key_length); j++) { - r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % N32)] - ^ psfmt32[idxof((i + N32 - 1) % N32)]); - psfmt32[idxof((i + mid) % N32)] += r; - r += init_key[j] + i; - psfmt32[idxof((i + mid + lag) % N32)] += r; - psfmt32[idxof(i)] = r; - i = (i + 1) % N32; + r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % SFMT_N32)] + ^ psfmt32[idxof((i + SFMT_N32 - 1) % SFMT_N32)]); + psfmt32[idxof((i + mid) % SFMT_N32)] += r; + r += init_key[j] + i; + psfmt32[idxof((i + mid + lag) % SFMT_N32)] += r; + psfmt32[idxof(i)] = r; + i = (i + 1) % SFMT_N32; } for (; j < count; j++) { - r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % N32)] - ^ psfmt32[idxof((i + N32 - 1) % N32)]); - psfmt32[idxof((i + mid) % N32)] += r; - r += i; - psfmt32[idxof((i + mid + lag) % N32)] += r; - psfmt32[idxof(i)] = r; - i = (i + 1) % N32; + r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % SFMT_N32)] + ^ psfmt32[idxof((i + SFMT_N32 - 1) % SFMT_N32)]); + psfmt32[idxof((i + mid) % SFMT_N32)] += r; + r += i; + psfmt32[idxof((i + mid + lag) % SFMT_N32)] += r; + psfmt32[idxof(i)] = r; + i = (i + 1) % SFMT_N32; } - for (j = 0; j < N32; j++) { - r = func2(psfmt32[idxof(i)] + psfmt32[idxof((i + mid) % N32)] - + psfmt32[idxof((i + N32 - 1) % N32)]); - psfmt32[idxof((i + mid) % N32)] ^= r; - r -= i; - psfmt32[idxof((i + mid + lag) % N32)] ^= r; - psfmt32[idxof(i)] = r; - i = (i + 1) % N32; + for (j = 0; j < SFMT_N32; j++) { + r = func2(psfmt32[idxof(i)] + psfmt32[idxof((i + mid) % SFMT_N32)] + + psfmt32[idxof((i + SFMT_N32 - 1) % SFMT_N32)]); + psfmt32[idxof((i + mid) % SFMT_N32)] ^= r; + r -= i; + psfmt32[idxof((i + mid + lag) % SFMT_N32)] ^= r; + psfmt32[idxof(i)] = r; + i = (i + 1) % SFMT_N32; } - idx = N32; - period_certification(); - initialized = 1; + sfmt->idx = SFMT_N32; + period_certification(sfmt); } +#if defined(__cplusplus) +} +#endif diff --git a/common/sfmt/SFMT.h b/common/sfmt/SFMT.h index f0aba1985..dca308a00 100644 --- a/common/sfmt/SFMT.h +++ b/common/sfmt/SFMT.h @@ -1,24 +1,28 @@ -/** - * @file SFMT.h +#pragma once +/** + * @file SFMT.h * * @brief SIMD oriented Fast Mersenne Twister(SFMT) pseudorandom - * number generator + * number generator using C structure. * * @author Mutsuo Saito (Hiroshima University) - * @author Makoto Matsumoto (Hiroshima University) + * @author Makoto Matsumoto (The University of Tokyo) * * Copyright (C) 2006, 2007 Mutsuo Saito, Makoto Matsumoto and Hiroshima - * University. All rights reserved. + * University. + * Copyright (C) 2012 Mutsuo Saito, Makoto Matsumoto, Hiroshima + * University and The University of Tokyo. + * All rights reserved. * - * The new BSD License is applied to this software. - * see LICENSE.txt + * The 3-clause BSD License is applied to this software, see + * LICENSE.txt * * @note We assume that your system has inttypes.h. If your system * doesn't have inttypes.h, you have to typedef uint32_t and uint64_t, * and you have to define PRIu64 and PRIx64 in this file as follows: * @verbatim typedef unsigned int uint32_t - typedef unsigned long long uint64_t + typedef unsigned long long uint64_t #define PRIu64 "llu" #define PRIx64 "llx" @endverbatim @@ -28,14 +32,14 @@ * unsigned int and 64-bit unsigned int in hexadecimal format. */ -#ifndef SFMT_H -#define SFMT_H - -#ifdef __cplusplus +#ifndef SFMTST_H +#define SFMTST_H +#if defined(__cplusplus) extern "C" { #endif #include +#include #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) #include @@ -60,105 +64,232 @@ extern "C" { #endif #endif -#if defined(__GNUC__) -#define ALWAYSINLINE __attribute__((always_inline)) -#else -#define ALWAYSINLINE -#endif +#include "SFMT-params.h" -#if defined(_MSC_VER) - #if _MSC_VER >= 1200 - #define PRE_ALWAYS __forceinline - #else - #define PRE_ALWAYS inline +/*------------------------------------------ + 128-bit SIMD like data type for standard C + ------------------------------------------*/ +#if defined(HAVE_ALTIVEC) + #if !defined(__APPLE__) + #include #endif +/** 128-bit data structure */ +union W128_T { + vector unsigned int s; + uint32_t u[4]; + uint64_t u64[2]; +}; +#elif defined(HAVE_SSE2) + #include + +/** 128-bit data structure */ +union W128_T { + uint32_t u[4]; + uint64_t u64[2]; + __m128i si; +}; #else - #define PRE_ALWAYS inline +/** 128-bit data structure */ +union W128_T { + uint32_t u[4]; + uint64_t u64[2]; +}; #endif -uint32_t gen_rand32(void); -uint64_t gen_rand64(void); -void fill_array32(uint32_t *array, int size); -void fill_array64(uint64_t *array, int size); -void init_gen_rand(uint32_t seed); -void init_by_array(uint32_t *init_key, int key_length); -const char *get_idstring(void); -int get_min_array_size32(void); -int get_min_array_size64(void); +/** 128-bit data type */ +typedef union W128_T w128_t; -/* These real versions are due to Isaku Wada */ -/** generates a random number on [0,1]-real-interval */ -inline static double to_real1(uint32_t v) -{ - return v * (1.0/4294967295.0); - /* divided by 2^32-1 */ +/** + * SFMT internal state + */ +struct SFMT_T { + /** the 128-bit internal state array */ + w128_t state[SFMT_N]; + /** index counter to the 32-bit internal state array */ + int idx; +}; + +typedef struct SFMT_T sfmt_t; + +void sfmt_fill_array32(sfmt_t * sfmt, uint32_t * array, int size); +void sfmt_fill_array64(sfmt_t * sfmt, uint64_t * array, int size); +void sfmt_init_gen_rand(sfmt_t * sfmt, uint32_t seed); +void sfmt_init_by_array(sfmt_t * sfmt, uint32_t * init_key, int key_length); +const char * sfmt_get_idstring(sfmt_t * sfmt); +int sfmt_get_min_array_size32(sfmt_t * sfmt); +int sfmt_get_min_array_size64(sfmt_t * sfmt); +void sfmt_gen_rand_all(sfmt_t * sfmt); + +#ifndef ONLY64 +/** + * This function generates and returns 32-bit pseudorandom number. + * init_gen_rand or init_by_array must be called before this function. + * @param sfmt SFMT internal state + * @return 32-bit pseudorandom number + */ +inline static uint32_t sfmt_genrand_uint32(sfmt_t * sfmt) { + uint32_t r; + uint32_t * psfmt32 = &sfmt->state[0].u[0]; + + if (sfmt->idx >= SFMT_N32) { + sfmt_gen_rand_all(sfmt); + sfmt->idx = 0; + } + r = psfmt32[sfmt->idx++]; + return r; +} +#endif +/** + * This function generates and returns 64-bit pseudorandom number. + * init_gen_rand or init_by_array must be called before this function. + * The function gen_rand64 should not be called after gen_rand32, + * unless an initialization is again executed. + * @param sfmt SFMT internal state + * @return 64-bit pseudorandom number + */ +inline static uint64_t sfmt_genrand_uint64(sfmt_t * sfmt) { +#if defined(BIG_ENDIAN64) && !defined(ONLY64) + uint32_t * psfmt32 = &sfmt->state[0].u[0]; + uint32_t r1, r2; +#else + uint64_t r; +#endif + uint64_t * psfmt64 = &sfmt->state[0].u64[0]; + assert(sfmt->idx % 2 == 0); + + if (sfmt->idx >= SFMT_N32) { + sfmt_gen_rand_all(sfmt); + sfmt->idx = 0; + } +#if defined(BIG_ENDIAN64) && !defined(ONLY64) + r1 = psfmt32[sfmt->idx]; + r2 = psfmt32[sfmt->idx + 1]; + sfmt->idx += 2; + return ((uint64_t)r2 << 32) | r1; +#else + r = psfmt64[sfmt->idx / 2]; + sfmt->idx += 2; + return r; +#endif } -/** generates a random number on [0,1]-real-interval */ -inline static double genrand_real1(void) +/* ================================================= + The following real versions are due to Isaku Wada + ================================================= */ +/** + * converts an unsigned 32-bit number to a double on [0,1]-real-interval. + * @param v 32-bit unsigned integer + * @return double on [0,1]-real-interval + */ +inline static double sfmt_to_real1(uint32_t v) { - return to_real1(gen_rand32()); + return v * (1.0/4294967295.0); + /* divided by 2^32-1 */ } -/** generates a random number on [0,1)-real-interval */ -inline static double to_real2(uint32_t v) +/** + * generates a random number on [0,1]-real-interval + * @param sfmt SFMT internal state + * @return double on [0,1]-real-interval + */ +inline static double sfmt_genrand_real1(sfmt_t * sfmt) { - return v * (1.0/4294967296.0); + return sfmt_to_real1(sfmt_genrand_uint32(sfmt)); +} + +/** + * converts an unsigned 32-bit integer to a double on [0,1)-real-interval. + * @param v 32-bit unsigned integer + * @return double on [0,1)-real-interval + */ +inline static double sfmt_to_real2(uint32_t v) +{ + return v * (1.0/4294967296.0); /* divided by 2^32 */ } -/** generates a random number on [0,1)-real-interval */ -inline static double genrand_real2(void) +/** + * generates a random number on [0,1)-real-interval + * @param sfmt SFMT internal state + * @return double on [0,1)-real-interval + */ +inline static double sfmt_genrand_real2(sfmt_t * sfmt) { - return to_real2(gen_rand32()); + return sfmt_to_real2(sfmt_genrand_uint32(sfmt)); } -/** generates a random number on (0,1)-real-interval */ -inline static double to_real3(uint32_t v) +/** + * converts an unsigned 32-bit integer to a double on (0,1)-real-interval. + * @param v 32-bit unsigned integer + * @return double on (0,1)-real-interval + */ +inline static double sfmt_to_real3(uint32_t v) { - return (((double)v) + 0.5)*(1.0/4294967296.0); + return (((double)v) + 0.5)*(1.0/4294967296.0); /* divided by 2^32 */ } -/** generates a random number on (0,1)-real-interval */ -inline static double genrand_real3(void) +/** + * generates a random number on (0,1)-real-interval + * @param sfmt SFMT internal state + * @return double on (0,1)-real-interval + */ +inline static double sfmt_genrand_real3(sfmt_t * sfmt) { - return to_real3(gen_rand32()); -} -/** These real versions are due to Isaku Wada */ - -/** generates a random number on [0,1) with 53-bit resolution*/ -inline static double to_res53(uint64_t v) -{ - return v * (1.0/18446744073709551616.0L); + return sfmt_to_real3(sfmt_genrand_uint32(sfmt)); } -/** generates a random number on [0,1) with 53-bit resolution from two - * 32 bit integers */ -inline static double to_res53_mix(uint32_t x, uint32_t y) -{ - return to_res53(x | ((uint64_t)y << 32)); -} - -/** generates a random number on [0,1) with 53-bit resolution +/** + * converts an unsigned 32-bit integer to double on [0,1) + * with 53-bit resolution. + * @param v 32-bit unsigned integer + * @return double on [0,1)-real-interval with 53-bit resolution. */ -inline static double genrand_res53(void) -{ - return to_res53(gen_rand64()); -} +inline static double sfmt_to_res53(uint64_t v) +{ + return v * (1.0/18446744073709551616.0); +} -/** generates a random number on [0,1) with 53-bit resolution - using 32bit integer. +/** + * generates a random number on [0,1) with 53-bit resolution + * @param sfmt SFMT internal state + * @return double on [0,1) with 53-bit resolution */ -inline static double genrand_res53_mix(void) -{ +inline static double sfmt_genrand_res53(sfmt_t * sfmt) +{ + return sfmt_to_res53(sfmt_genrand_uint64(sfmt)); +} + + +/* ================================================= + The following function are added by Saito. + ================================================= */ +/** + * generates a random number on [0,1) with 53-bit resolution from two + * 32 bit integers + */ +inline static double sfmt_to_res53_mix(uint32_t x, uint32_t y) +{ + return sfmt_to_res53(x | ((uint64_t)y << 32)); +} + +/** + * generates a random number on [0,1) with 53-bit resolution + * using two 32bit integers. + * @param sfmt SFMT internal state + * @return double on [0,1) with 53-bit resolution + */ +inline static double sfmt_genrand_res53_mix(sfmt_t * sfmt) +{ uint32_t x, y; - x = gen_rand32(); - y = gen_rand32(); - return to_res53_mix(x, y); -} -#ifdef __cplusplus + x = sfmt_genrand_uint32(sfmt); + y = sfmt_genrand_uint32(sfmt); + return sfmt_to_res53_mix(x, y); +} + +#if defined(__cplusplus) } #endif + #endif diff --git a/common/user_level.h b/common/user_level.h new file mode 100644 index 000000000..cf743b8d3 --- /dev/null +++ b/common/user_level.h @@ -0,0 +1,10 @@ +#ifndef USER_LEVEL_H +#define USER_LEVEL_H + +#include "pb/serverinfo_user.pb.h" +#include + +Q_DECLARE_FLAGS(UserLevelFlags, ServerInfo_User::UserLevelFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(UserLevelFlags) + +#endif diff --git a/doc/add_sets.md b/doc/add_sets.md new file mode 100644 index 000000000..5dce79441 --- /dev/null +++ b/doc/add_sets.md @@ -0,0 +1,62 @@ +Add Sets +=== + +The oracle program creates the cockatrice card database and needs a set database to achieve this. +The set database is written in XML and has the following skeleton: + +``` + + + http://www.wizards.com/global/images/magic/general/%1.jpg + http://gatherer.wizards.com/Pages/Search/Default.aspx?output=spoiler&method=text&set=["!longname!"] + + + Set Code + Set Name + + + + Another Set Code + Another Set Name + + + ... + + +``` + +The full set database file called "set.xml" should be included in the cockatrice git-sources. + +* You could change the picture_url to another source if you want, e.g. if you have HQ scans or another alternative server. +* The set_url is the search URL for Wizard's Gatherer, where the oracle program will fetch it's information from. +* If the Gatherer changes, adapt the URL and keep in mind that '&', '"' and other special characters should be HTML encoded (like '&' and '"'). +* Further, the oracle replaces substrings included in '!' characters with information from the set entries, like '!longname!' with the set name. +* If you add a new set to the database, you have to create a new set entry like + + AVR + Avacyn Restored + +and update the date in the version string. If you set import="1" the set's checkbox in the oracle's GUI will be set, otherwise unset. + + +Here is a more detailed step by step manual found in the internet: + +1. Load the Oracle program that's installed with Cockatrice. The Oracle program can usually be found by click the Start button and going to All Programs > Cockatrice. +2. Click the File menu item and select 'Download sets information...' +3. Copy the URL from the 'Load sets' screen (the URL should be something like http://www.cockatrice.de/files/sets.xml) and paste it into your internet browser. +4. Right click on the web page and select Save As +5. Save the XML file to somewhere on your PC, preferably somewhere that's easy to get to +6. Right click on the XML file and select 'Open With...' and choose Notepad from the list +7. Before the end of the notepad document (should be something like ) add: + + RTR + Return to Ravnica + +8. Save and close the XML file +9. Back in the Oracle progam if the 'Load Sets' screen is still open click the Cancel button +10. Click the File menu item and click 'Load Sets from File' +11. Browse to where you saved the XML file and double click on it to select it +12. Click the Start Download button +13. Once the download is complete close the Oracle program and run Cockatrice. The Return to Ravnica cards should now be displayed. + +Hopefully the steps above are helpful and easy to follow. \ No newline at end of file diff --git a/doc/cards.xsd b/doc/cards.xsd new file mode 100644 index 000000000..d10032a7e --- /dev/null +++ b/doc/cards.xsd @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/create_sets_xml.sh b/doc/create_sets_xml.sh new file mode 100755 index 000000000..14abd0e3c --- /dev/null +++ b/doc/create_sets_xml.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# Print the header +echo '' +echo '' +echo 'http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card' +echo 'http://gatherer.wizards.com/Pages/Search/Default.aspx?output=spoiler&method=text&set=["!longname!"]&special=true' + +# Download the sets and print them in XML +lynx -dump -source http://magiccards.info/sitemap.html |grep Gatecrash|sed 's/
  • @\t@g'|sed 's@\n\t\t"toupper($1)"\n\t\t"$2"\n\t"}' + +# Print the footer +echo '' diff --git a/doc/faq.md b/doc/faq.md new file mode 100644 index 000000000..32703d20e --- /dev/null +++ b/doc/faq.md @@ -0,0 +1,138 @@ +FAQ +=== + +How to update the card database +--- + +When a new set comes out, you need to update your card database to be able to +see the new cards in the deck editor and use them in the game. To do this, +open the Oracle tool in the Cockatrice folder. It's a stand-alone program and +is run directly out of the folder without running Cockatrice. + +When the Oracle tool is running, open the menu and select 'Download sets +information'. Use the suggested address and click OK. After at most a few +seconds, you should be presented a list of sets to be downloaded. Do not +uncheck the ones you already have: they should be downloaded again. Click +'Start download' and wait. When the process is finished, the database should +be up to date. + + +Change Card Art / Custom Card Art +--- + +Is it possible to change the card art for a specif card, not by changing the +set order in my deck editor? + +YES! It is actually very simple. + +All you have to do is get a .JPG of the card you wish to change and name the +actual file of the image to the cards (exact) name and add ”.Full” to the end. +You can then take this new card image and replace the old one in your +downloaded image folder. + +*NOTE: Image size does not matter. Try to use a high resolution .JPG of the +card you want to use for best quality.* + +EXAMPLE: Let's say you wish to turn your M11 Sun Titan from its original art +to a promo version. + +If you are using Windows 7, you can find the default location for the +Cockatrice card art files under + +``` +C:\Program Files (x86)\Cockatrice\pics\downloadedPics +``` + +Once there, look for the folder of the +set for the card we are replacing. For this example, Sun Titan is in the M11 +folder. Open the folder and paste/replace your new “Promo” Sun Titan.full[.JPG] +into this folder, discarding the original copy. (If you have +not yet downloaded the original image of a card by selecting it in the deck +editor or playing it in an online game, the card image will not yet be there) + +Once you have the new card image in the proper folder, you can now start +Cockatrice (Or re-start if you already had the program open)and select the +deck editor to see your new card image. + +If you are changing the art of a card that has multiple versions in other +sets, make sure you put the new card in whatever folder (or set) is highest +on your card database set list. + +*NOTE: Other players on Cockatrice will NOT see your new card image. They will +only see whatever version of the card is in their database.* + + +Use Higher Resolution Cards +--- + +Can you get better/higher resolution card art than the default downloaded card +images already used? + +Yes! + +When you click on a card for the first time in the Deck Editor, Cockatrice +goes onto the internet and finds an image of that card from a database on +another website. + +If you find a higher resolution .JPG of a card that you wish to use on your +Cockatrice, you can replace the image with no problem. If you can find a card +image in the 3,000 by 1,000 pixel range, and save it as the cards (exact) name +and add .Full to the end. + +If you are using Windows 7, you can find the default location for the +Cockatrice card art files under + +``` +C:\Program Files(x86)\Cockatrice\pics\downloadedPics +``` + +Once there, look for the folder of the set for the card you are replacing. If you are not running Cockatrice as an +administrator, look in + +``` +C:\Users\username\AppData\Local\VirtualStore\Program Files (x86)\Cockatrice\pics\downloadedPics +``` + +Open the folder of the set with the card, and paste/replace the new higher +resolution .JPG image with the old low quality one. + +This process is the same for changing cards to custom images. + + +*NOTE: Other Cockatrice players will not see your higher resolution card art, +they will only see the image they have in their own database.* + + +Booster Drafts +--- +Cockatrice does not support booster drafts, so players need to use an +external service for this. + +Most people use [ccgdecks.com](http://ccgdecks.com/) so it's probably not a bad idea to +register an account there before joining a game in Cockatrice. + + +How to link a card in the Cockatrice chat. +--- + +To link a card in the Cockatrice chat, type out the full name of the card, +surrounded by the [card] and [/card] tags. + +For example: + +``` +[card]Black Lotus[/card] +``` + +How to link a URL in the Cockatrice chat. +--- +To link a URL in the Cockatrice chat, type out the url, surrounded by the +[url] and [/url] tags. + +For example: + +``` +[url]http://www.cockatrice.de[/url] +[url]cockatrice.de[/url] +``` + diff --git a/doc/sets.xml b/doc/sets.xml new file mode 100644 index 000000000..bcc7c20ef --- /dev/null +++ b/doc/sets.xml @@ -0,0 +1,514 @@ + + +http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card + +http://mtgjson.com/json/!name!.json + + ARB + Alara Reborn + + + ALL + Alliances + + + ATQ + Antiquities + + + APC + Apocalypse + + + ARN + Arabian Nights + + + ARC + Archenemy + + + AVR + Avacyn Restored + + + BRB + Battle Royale Box Set + + + BTD + Beatdown Box Set + + + BOK + Betrayers of Kamigawa + + + BNG + Born of the Gods + + + CHK + Champions of Kamigawa + + + CHR + Chronicles + + + 6ED + Classic Sixth Edition + + + CSP + Coldsnap + + + CON + Conflux + + + CMA + Commander's Arsenal + + ^M + C13^M + Commander 2013 Edition^M + + + DST + Darksteel + + + DKA + Dark Ascension + + + DIS + Dissension + + + DGM + Dragon's Maze + + + DDH + Duel Decks: Ajani vs. Nicol Bolas + + + DDC + Duel Decks: Divine vs. Demonic + + + DDF + Duel Decks: Elspeth vs. Tezzeret + + + EVG + Duel Decks: Elves vs. Goblins + + + DDD + Duel Decks: Garruk vs. Liliana + + + DDL + Duel Decks: Heroes vs. Monsters + + + DDJ + Duel Decks: Izzet vs. Golgari + + + DD2 + Duel Decks: Jace vs. Chandra + + + DDM + Duel Decks: Jace vs. Vraska + + + DDG + Duel Decks: Knights vs. Dragons + + + DDE + Duel Decks: Phyrexia vs. the Coalition + + + DDK + Duel Decks: Sorin vs. Tibalt + + + DDI + Duel Decks: Venser vs. Koth + + + 8ED + Eighth Edition + + + EVE + Eventide + + + EXO + Exodus + + + FEM + Fallen Empires + + + 5DN + Fifth Dawn + + + 5ED + Fifth Edition + + + 4ED + Fourth Edition + + + DRB + From the Vault: Dragons + + + V09 + From the Vault: Exiled + + + V11 + From the Vault: Legends + + + V12 + From the Vault: Realms + + + V10 + From the Vault: Relics + + + V13 + From the Vault: Twenty + + + FUT + Future Sight + + + GPT + Guildpact + + + GTC + Gatecrash + + + HML + Homelands + + + ICE + Ice Age + + + ISD + Innistrad + + + INV + Invasion + + + JOU + Journey into Nyx + + + JUD + Judgment + + + LEG + Legends + + + LGN + Legions + + + LEA + Limited Edition Alpha + + + LEB + Limited Edition Beta + + + LRW + Lorwyn + + + M10 + Magic 2010 + + + M11 + Magic 2011 + + + M12 + Magic 2012 + + + M13 + Magic 2013 + + + M14 + Magic 2014 Core Set + + + CMD + Magic: The Gathering-Commander + + + CNS + Magic: The Gathering—Conspiracy + + + MED + Masters Edition + + + ME2 + Masters Edition II + + + ME3 + Masters Edition III + + + ME4 + Masters Edition IV + + + MMQ + Mercadian Masques + + + MIR + Mirage + + + MRD + Mirrodin + + + MBS + Mirrodin Besieged + + ^M + MMA^M + Modern Masters^M + ^ + + MOR + Morningtide + + + NMS + Nemesis + + + NPH + New Phyrexia + + + 9ED + Ninth Edition + + + ODY + Odyssey + + + ONS + Onslaught + + + PLC + Planar Chaos + + + HOP + Planechase + + + PC2 + Planechase 2012 Edition + + + PLS + Planeshift + + + POR + Portal + + + PO2 + Portal Second Age + + + PTK + Portal Three Kingdoms + + + PD2 + Premium Deck Series: Fire and Lightning + + + PD3 + Premium Deck Series: Graveborn + + + H09 + Premium Deck Series: Slivers + + + PPR + Promo set for Gatherer + + + PCY + Prophecy + + + RAV + Ravnica: City of Guilds + + + RTR + Return to Ravnica + + + 3ED + Revised Edition + + + ROE + Rise of the Eldrazi + + + SOK + Saviors of Kamigawa + + + SOM + Scars of Mirrodin + + + SCG + Scourge + + + 7ED + Seventh Edition + + + SHM + Shadowmoor + + + ALA + Shards of Alara + + + S99 + Starter 1999 + + + S00 + Starter 2000 + + + STH + Stronghold + + + TMP + Tempest + + + 10E + Tenth Edition + + + DRK + The Dark + + + THS + Theros + + + TSP + Time Spiral + + + TSB + Time Spiral "Timeshifted" + + + TOR + Torment + + + UGL + Unglued + + + UNH + Unhinged + + + 2ED + Unlimited Edition + + + UDS + Urza's Destiny + + + ULG + Urza's Legacy + + + USG + Urza's Saga + + + VAN + Vanguard + + + VIS + Visions + + + WTH + Weatherlight + + + WWK + Worldwake + + + ZEN + Zendikar + + diff --git a/doc/shortcuts.md b/doc/shortcuts.md new file mode 100644 index 000000000..68e512e92 --- /dev/null +++ b/doc/shortcuts.md @@ -0,0 +1,48 @@ +Keyboard Shortcuts +================== + +Keyboard Shortcuts can be used while playing a game on Cockatrice. Players use +them to do simple and recurring tasks without the use of looking though menus +to find the proper action. + +Shortcut Action +---------------------- + +| key | action +| ------------------------ | --------------------------------------------------------------------- +| F3 | View library +| F4 | View graveyard +| Ctrl+F3 | View sideboard +| Ctrl+W | View top car(s) of library (Choose a number) +| Ctrl+S | Shuffle deck +| Ctrl+I | Roll dice +| Ctrl+M | Draw 7, then mulligan successively +| Ctrl+D | Draw a card +| Ctrl+E | Draw Cards (Choose a number) +| Ctrl+Shift+D | Undo last draw +| Ctrl+L | Set Life (useful when adding or subtracting a large number of life) +| F11 | Minus 1 life +| F12 | Add 1 life +| Ctrl+U | Untap all permanents you control +| Ctrl+R | Remove all local arrows +| Ctrl+T | Brings up menu to Create a token +| Ctrl+G | Creates last token you made +| (Select card) Ctrl+Del | Move to graveyard +| (Select card) Ctrl+A | Attach Card (For equipment and auras) +| (Select card) Ctrl+H | Clone (Make copy of) selected card +| (Select card) Ctrl++ | Increase power +| (Select card) Ctrl+- | Decrease power +| (Select card) Alt++ | Increase toughness +| (Select card) Alt+- | Decrease toughness +| (Select card) Ctrl+Alt++ | Increase power and toughness +| (Select card) Ctrl+Alt+- | Decrease power and toughness +| (Select card) Ctrl+P | Set power and toughness +| Ctrl+Enter (Return) | Next turn +| Ctrl+Space | Move to next phase +| F5 | Untap step +| F6 | Draw step +| F7 | First main phase +| F8 | Beginning of combat step +| F9 | Second main phase +| F10 | End of turn step +| F2 | Concede game diff --git a/doc/usermanual/Usermanual.md b/doc/usermanual/Usermanual.md new file mode 100644 index 000000000..1656be143 --- /dev/null +++ b/doc/usermanual/Usermanual.md @@ -0,0 +1,1257 @@ +Preface +======= + +This manual is basically a dump from the cockatrice.de dokuwiki. +Cockatrice has some legal problems right now and the page is down. This +document tries to save the documentation about the software, beautify +and extend it in the future. Please contribute to the project, it is +much too precious to be destroyed. + +Getting Started +=============== + +Making A User Profile +--------------------- + +Not available anymore, site is down. If someone runs his own server +where you can register a user profile, read his documentation. + +Downloading and Installing the Cockatrice Program +------------------------------------------------- + +Due to a legal dispute there are currently no official builds left, so +currently you have to build your own binaries. + +### Client compilation + +#### Windows + +There should be two ways to compile Cockatrice: With Visual Studio 2010 +and with MinGW. As the Visual Studio method is more complicated and +might not work, you should do it with MinGW. The following howto has +been tested with Windows 7 64Bit. + +##### Prerequisites + +We need the Cockatrice sourcecode, it’s dependencies and build tools. +Everything is freely available: + +1. MinGW is needed to compile everything, it ships the compiler and + other tools for this task. + +2. git is needed to download the latest Cockatrice source code. + +3. cmake is needed to create Cockatrice’s project files for MinGW. + +4. Qt 4.8 is a dependency for Cockatrice (download the MinGW version!) + +5. protobuf 2.5 is another dependency for Cockatrice (download the zip + file with the sourcecode). + +6. Nullsoft Scriptable Install System (NSIS) which can be used to + create an installer for your own Cockatrice version. + +All downloadlinks together: + +##### Installation of the Prerequisites + +1. Download MinGW (mingw-get-inst), git, cmake, protobuf 2.5 sources + zip and Qt 4.8.4 MinGW (see links above). + +2. Make a standard installation for git, cmake and NSIS. + +3. Install MinGW: + + 1. Select default values everywhere, except: Also check the C++ + Compiler, check the MSYS Basis System and MinGW Developer + Toolkit. + + 2. Append `C:\MinGW\bin` to your PATH variable (google how to do it + for your Windows version, if you don’t know it). This is very + important and overseen many times! You can keep the PATH + configuration dialog open for the next step. + +4. Install Qt, select default values everywhere, ignore the message + regarding win32.h and append `C:\Qt\4.8.4\bin` to your PATH variable + like you did for MinGW. + +5. Unpack protobuf-2.5.0.zip in `C:\MinGW\msys\1.0\home\YOURLOGIN` + + 1. Open MinGW Shell, type the following commands exactly: + + 2. `cd protobuf-2.5.0/protobuf-2.5.0` + + 3. `./configure –prefix=‘cd /mingw; pwd -W‘` (these apostrophs are + backticks and do not forget the dot at the beginning!) + + 4. `make; make install` (this builds and installs protobuf, which + needs some time) + + 5. Close MinGW Shell + +Your system is now able to compile Cockatrice. You do not have to repeat +the process so far ever again. + +##### Cockatrice Compilation + +1. Checkout Cockatrice: + + 1. Start Git Bash and type the following command exactly: + + 2. `git clone https://github.com/Daenyth/Cockatrice` + + 3. Close Git Bash + +2. Start CMake (cmake-gui) and do the following: + + 1. Where is the source code: Point to the Cockatrice directory + + 2. Where to build the binaries: Point to the Cockatrice/build + directory (it doesn’t matter if it exists; if it doesn’t, cmake + will ask later if it shall create this directory) + + 3. Check Advanced + + 4. Click Configure, choose MinGW, leave the rest default + + 5. An error will occur, set the following: + + - Set PROTOBUF\_INCLUDE\_DIR to + C:/MinGW/include/google/protobuf + + - Set PROTOBUF\_LIBRARY to C:/MinGW/lib/libprotobuf.dll.a + + - Set PROTOBUF\_LITE\_LIBRARY to + C:/MinGW/lib/libprotobuf-lite.dll.a + + - Set PROTOBUF\_PROTOC\_EXECUTABLE to C:/MinGW/bin/protoc.exe + + - Set PROTOBUF\_PROTOC\_LIBRARY to + C:/MinGW/lib/libprotoc.dll.a + + 6. Click Configure again, then Generate + + 7. Close CMake + +3. Start cmd.exe (the Windows Command Prompt) and type the following + commands: + + 1. `cd Cockatrice/build` (this changes to the build directory) + + 2. `make` (this builds everything and might need some time) + +Cockatrice has now been downloaded and built for the first time. You do +not have to repeat the process so far ever again. + +##### Updating your Cockatrice build + +If you just compiled Cockatrice for the first time, you skip this step +obviously. But if you want to update Cockatrice after the source code +changed on github, do it like this: + +1. Start Git Bash and update Cockatrice: + + 1. `git pull origin master` + + 2. `Close Git Bash` + +2. Start cmd.exe, change to Cockatrice/build, type make like you did + previously. + +Cockatrice has now been updated and built. You may repeat this process +every time when the source code changed. + +##### Cockatrice installation + +To install Cockatrice, you have to create an installer with NSIS now. +Change to the directory `nsis` in the Cockatrice root directory, right +click the cockatrice.nsi file and select `Compile NSIS Script`. The NSIS +program then creates a file called +cockatrice\_win32\_YYYYmmdd\_git-xxxxxxx.exe. This is the complete, +redistributable installer for your Cockatrice build. + +Now install Cockatrice by executing the installer. Note: if you +installed MinGW or Qt in other than the default paths, you have to fix +the paths in the cockatrice.nsi file (also if some libraries change); +you can edit this file with a text editor. + +##### Create a card database + +Start the oracle.exe (the installer does this automatically) and let it +generate a current cards.xml file: + +1. File $\to$ Download Sets Information $\to$ OK (if there are no MtG + sets listed) + +2. Check All, Start Download + +Congratulations, you may now use Cockatrice! + +#### Linux and BSD + +The following procedures have been tested with Debian Wheezy, Fedora 18 +and FreeBSD 9.1. If you use Gentoo with KDE you have the needed +prerequisites and may continue with downloading the source. If you use +Bodhi or Arch Linux (AUR) or another distribution that includes +Cockatrice, you might install Cockatrice from the default packages – +though the package might be old, so you probably should continue with +this howto. + +Before you install new software, you should update your system. The +following instructions failed on a fresh installation of Fedora 18 and +FreeBSD 9.1 until the systems were updated. + +1. You need to install the build tools and dependencies. This varies + between the Linux distributions. + + Debian, Ubuntu and spin-offs + : `sudo apt-get install build-essential git libqt4-dev qtmobility-dev libprotobuf-dev protobuf-compiler cmake` + + Fedora + : `sudo yum groupinstall "Development Tools" yum install qt-devel qt-mobility-devel protobuf-devel protobuf-compiler cmake` + + FreeBSD + : `pkg_add -r qt4 qt4-linguist qt4-moc qt4-qmake qt4-rcc qt4-uic git cmake protobuf` + +2. Download the sources from github via + `git clone https://github.com/Daenyth/Cockatrice.git` + +3. To compile the sources, change into the newly created directory, + create a build directory and invoke cmake: + + i. `cd Cockatrice` + + ii. `mkdir build` + + iii. `cd build` + + iv. `cmake ..` + + v. `make` + + If you have some issues with pthread\_ add ’pthread’ to the + “target\_link\_libraries” entry in the `CMakeFiles.txt` in + `Cockatrice/common`. + +4. You may install the program into the directory `/usr/local` by + typing `sudo make install` but you should also be able to start + cockatrice and the oracle from the build directory. + +5. Before you start Cockatrice for the first time, run `oracle -dlsets` + and download available cards, denn run `cockatrice`. The default + paths for decks, pics, cards and tokens are located in + `/home//.local/share/data/Cockatrice/Cockatrice`. + +#### MacOS X + +TODO, please contribute this section! See Linux section, then use the +`prepareMacRelease.sh` script from Cockatrice. + +### Server compilation + +You don’t need your own server if you plan to play only. But as +Cockatrice is open source you are free to run your own. The compilation +works like already written above, but instead of invoking `cmake ..`, +you have to do it like this: + +- If you want to build the server, use: + `cmake -DWITH_SERVER=1 ..` + +- If you want to build the server, but not the client, use: + `cmake -DWITH_SERVER=1 -DWITHOUT_CLIENT=1 ..` + +There is more information on compiling and running Servatrice on CentOS +6 in chapter [servatrice] on page . + +Downloading Card Database Using the Oracle +------------------------------------------ + +If you are installing Cockatrice for the first time, changing what sets +are in your database or even adding the newest set to your database, +this tutorial will show you how to do it properly. + +The Oracle will automatically run after the initial setup of Cockatrice. +If you would like to re-install your database or add new sets you can +find it (for Windows) by clicking the start menu, going to all programs, +selecting the Cockatrice folder, and in there you will find the Oracle +tool. + +- When the Oracle importer opens, click on “File” in the top left + corner and select “Download sets information…” + + ![image](pics/fetch554a.jpg) + +- This will bring up a box where you can input the URL of a card + database. The default address is + this was an XML file found + on the Cockatrice website that has the current set listings for + Magic the Gathering. As the page is down, you have to import the + file which is distributed with the Cockatrice sources. This can also + be done from the file menu. + +- Select “OK” to load the set listings. + +- A list of all current sets will be brought up. A default selection + of sets will automatically be checked. From here you can check or + uncheck all sets, or you may only download specific sets that you + wish to play with. + + ![image](pics/fetchfc3d.jpg) + + NOTE: If you are playing against someone who is using a card that is + not in your database, you will not see a card image or oracle text + for that card. Some players like to download all sets to avoid this + issue, but other players who only play specific formats (Like T2, + Standard, or Extended) wish to keep their database small with only + cards they will be using. + +- After you select which sets you wish to download, select “Start + download” at the bottom of the Oracle to download the selected sets + information. + +- After download is complete, close the Oracle and run Cockatrice. + +- We are now ready to sort our set information in our deck editor. + +Editing Set Order and Preference of Card Art +-------------------------------------------- + +Many cards have been re-printed in different sets, and in return have +different versions of artwork (like the card “Cancel” which can be found +in many sets, but has different artwork for each, e.g. Zendikar versus +M11: + +![image](pics/fetchc18b.jpg) ![image](pics/fetche1f4.jpg) + +Some players like to have the most current artwork displayed on their +cards, while other players have a favorite set they wish to display +instead. + +- Run Cockatrice and select “Deck editor” from the top right + Cockatrice menu. This will bring up the Deck Editor along with a + list of all cards that are currently in your database which you + downloaded using the Oracle Tool. + +- To change what version of the cards will be shown, click on “Card + database” on the top left of the Deck editor window, and select + “Edit sets…” + + ![image](pics/fetchf924.jpg) + +- This will bring up a new window that has a list of all sets you + currently have downloaded to your database. To change the order of + the sets, simply drag and drop them into place. This will determine + which artwork is shown for your cards. If a card is found in + multiple sets, whichever set is closest to the top of this list will + be the art displayed. Example: If M11 is above Zendikar, The M11 + version of the card “Cancel” will be displayed in your Deck editor + and Cockatrice games. + +NOTE: Your opponent will NOT see what artwork you have selected for each +of your cards. They will only see what they have selected for their own. + +The Deck Editor / Making a Deck +------------------------------- + +The Cockatrice Deck Editor is a tool you can use to make decks to play +online. The cards shown in the Deck Editor are from a database that you +downloaded with the Oracle Tool. If you are missing cards or a new set +has come out, you must re-run the Oracle and download set information. + +![image](pics/fetch52e0.jpg) + +1. Search Bar +: The search bar lets you type in the name of a card and the editor + will only show cards that start with whatever you typed in. Example: + Typing in ‘B’ will show all cards that start with the letter ‘B’ and + typing in ‘Dark’ will show you all cards that start with ‘Dark’ and + so on. If you were looking for the card “Sun Titan”, you would not + type in ‘Titan’ you would have to type in ‘Sun’ first. Typing in + ‘Titan’ will only show you any cards that start with ‘Titan’. + +2. Card Search/Filter +: The Card search button will bring up a new window that helps you + filter out cards more specifically. A variety of check boxes will + help find what you need. Card name lets you filter out only cards + that have a cretin word in them. Card text can help you find key + words like “Haste” or “Infect”. If you were to uncheck all boxes + except for “Instant” along with “Artifact” and “U”, the Deck editor + will only show you all Blue Instant and Artifact cards. + + |Letter | Card Type | + |-------|-----------| + | U | Blue | + | W | White | + | X | Colorless | + | G | Green | + | R | Red | + | B | Black | + +3. Card Data +: This section shows the Oracle text for the card that you currently + have selected. It will show you up-to-date information on the card + such as the Name, Mana cost, Card type, Power/Toughness, and any + abilities the card has. It will not show you flavor text. + +4. Adding/Removing Card Buttons +: The buttons in the bottom middle will add or remove cards from your + Deck List, as well as ad a card specifically to your Sideboard. + Having a card selected on the left column and hitting the Enter key + will also add it to your deck list. + +5. Deck Name/Comments +: The area in the top right lets you name your deck as well as give + any comments or descriptions such as how to play the deck, where the + deck came from, or explain a theme. Putting something in the “Deck + Name” space will NOT be what the file name of the file for your + deck. That is spate in the “save” selection under the “Deck” menu + found at the top left of the Deck Editor window. + +6. Main Deck List +: This area will show you what cards you currently have added to your + deck list. It is sorted by card type and also shows you how many of + each card and card type you have added, as well as keeps track of + how many total cards you have added to your main deck list. This + will not add any number of cards you have added to your Sideboard. + +7. Sideboard List +: The bottom section of the deck list shows any cards you have added + to your sideboard. Again these are split into card types and it will + keep track of how many of each card you have as well as how many of + each card type and total cards in sideboard. This section will not + add any cards from the main deck. Once you have all of your cards + added to your deck, you must save it as a file Cockatrice can read. + Select “Deck” from the top left corner of the Deck Editor screen, + and select “Save Deck” or “Save Deck As…” and it will bring up a new + window where you can select where you would like to save your deck, + as well as assign it a file name. Cockatrice decks are saved as .cod + files. + +Loading a deck list from your clipboard +--------------------------------------- + +If you find a deck online, or you have a deck list saved in a word +document, it is easy to transfer it over into a Cockatrice deck file as +long as it is in a simple deck list format. The simple deck list format +is a list where each line begins with a number, followed by a +whitespace, followed by the cardname, e.g. + + 2 Doom Blade + 13 Island + 10 Swamp + 4 Cancel + ... + +Simply find the word document or deck list online that you wish to save +as a deck, and select the text and copy it to your clipboard. Next, open +the Deck Editor screen, and click on the Deck menu from the top left +corner. Select “Load deck from clipboard…” and the deck editor will +bring up a new window that has the deck list you had copied to your +clipboard. Make sure the Deck list looks correct and hit “OK” in the +bottom right corner of the window. The Deck editor will now add all the +cards in the list to your main deck list. + +![image](pics/loaddeck_clip.jpg) + +![image](pics/okdecklist.jpg) + +NOTE: If you add a card to a deck list with this function that you do +not have downloaded to your personal cockatrice database though the +Oracle tool, the card will take a spot in your main deck list, and count +toward the total number of cards, but it will show up as a blank image +with no Oracle data or card information. + +Cockatrice Settings +------------------- + +TODO + +Learning the Ropes / Starting a Solitaire Game +---------------------------------------------- + +The best way to get familiar with the way Cockatrice plays is to start a +local game that you can play around in by yourself. You could also jump +online and start slowly learning, and let other players help you. + +To start a Solo Local game, in the main Cockatrice window, click on +“Cockatrice” on the top left, and select “Start local game…”. + +![image](pics/fetch2ab8.jpg) + +This will bring up a small window that lets you select how many players +will be in this local game you are creating. For right now, since we +want to do a solo Solitaire game, select one player and hit “OK”. + +![image](pics/fetchf010.jpg) + +### Loading a Deck / Using Sideboard + +This will now bring you to a screen where you load a deck to play with. +On the top left part of this screen you will find a button that is +labeled “Load Local Deck”. Click that button and it will bring up a +window where you can find and select what deck you would like to play +with. + +![image](pics/fetchf0d2.jpg) ![image](pics/fetch55a7.jpg) + +Select a deck or a .cod file and click “Open”. + +![image](pics/fetch9b89.jpg) + +After the file has loaded you will see all of the cards in that deck +laid out on the table. If you hover your mouse over a card, the card +image and Oracle info will show on the right side of the screen. If you +have a sideboard made for the deck there will be a second section on the +table for this sideboard. This screen gives you the ability to double +check your deck to make sure it is not only the correct deck you want to +play with, but it lets you see that all card images have downloaded +properly. If you have cards not showing up at all or they are just blank +cards with names on them, you may not have that set downloaded with your +Oracle tool. If you have a sideboard, you can drag and drop cards from +your main deck to your sideboard or vice-versa. You can do this by +clicking and dragging a card to or from your main deck or sideboard. + +NOTE: Moving cards from your main deck to sideboard will NOT change how +your deck file is saved, it will only change it temporary for the game +you are playing or until you load a new deck. + +When you are satisfied with your deck choice and/or sideboarding +options, click on the red outlined “Ready to start” button found a the +top of the screen. + +### Finding Your Way Around + +The main game screenlooks like this + +![image](pics/fetch7cf0.jpg) + +(Please note your screen will look different due to background image +options.) + +#### Main Table / Play area + +Split into four areas, this is where all the action will go down. + +The Stack +: The area on the left side of the table where Instant and Sorcery + cards will be played. This is for things that will only temporarily + be put on the table, then into the graveyard. Multiple cards may be + added to this area at the same time. Anything on this part of the + table will be seen by all players. + +Battlefield +: This is the soul part of the game table. this is where creatures, + enchantments, artifacts, and even plainswalkers will be placed. As + cards are moved from your had to the table, they will be aligned to + an invisible grid and moved around from there. Tap cards by double + clicking them. Anything on this part of the table will be seen by + all players. + +Land +: This space is for land cards, but any card may be placed here. Tap + cards here by double clicking them. Anything on this part of the + table will be seen by all players. + +Hand +: Every time you draw a card it will go here to your hand. You may + also drag cards from the table back to your hand. Your opponents can + not see what is in your hand. + +#### Player Info Section + +![image](pics/fetch0300.jpg) + +Player Avatar +: This is a $156\times 60$ pixel JPG image that can be uploaded though + the main Cockatrice website. All players in the game room can see + this image. It serves nothing more than an online identity for you + and other players. + +Player Name +: Your online name that you picked though the main Cockatrice website. + +Life Total +: Your in-game life. Using your mouse, if left-clicked will raise this + number by one, and if right-clicked lowered by one. There are also + keyboard shortcuts to change your life total. + +Counters +: These seven multicolored circles are used as counters. They can be + seen by all players and can be changed by left or right clicking on + them to add or subtract a number. Players use them for various digit + counting but primarily used for adding and subtracting floating mana + produced by card effects. The bottom two white counters can be used + for other things like Poison. + +Library +: This is your deck of cards. The number in the middle reflects how + many cards are left in your library. Double clicking the deck lets + you draw a card and add it to your hand, you can also drag cards off + the top into the battlefield or to your hand. Right-clicking the + deck brings up a menu that allows other things to happen like + reviling the top number of cards, shuffling, or moving cards + directly into the graveyard. + +Number of cards in hand +: The number in the middle represents how many cards are currently in + your hand. Other players can see this number but can not see the + cards actually in your hand. + +Graveyard +: Cards can be dragged and dropped into your graveyard from play or + vise-versa, the stack, your hand, or even your library. The number + in the middle represents how many cards are currently in your + graveyard. Any player may right-click on the graveyard and bring up + a menu that shows what cards are in it. + +Exile +: Cards can be dragged and dropped into exile from play or vise-versa, + the stack, your hand, or even your library. The number in the middle + represents how many cards are currently in your exile. Any player + may right-click on the exile and bring up a menu that shows what + cards are in it. + +#### Turn Phases + +![image](pics/fetchfebd.jpg) + +This bar located on the left most side of the screen represents the 11 +steps in a players turn. To go from one phase to the next, you can click +on the square of the phase you want to move to, or you can hit +Ctrl+space to move down to the next. Some phases even have their own +keyboard shortcut. Going from one phase to the next does not actually do +anything to your or your cards, it is only a place marker for your +opponents to see and keep up with what you are doing in your turn. For +example, clicking to the “Draw Phase” will not automatically draw you a +card. It is customary for a player to end their turn on the “End of turn +step” and let their opponent hit the “next turn” button. This is a +courtesy for other players if they wish to do something like use an +instant at the end of your turn, or in response to something you did. + +NOTE: Players sometimes use the term EOT which stands for “End Of Turn”. +This is to let other players know they are doing something in response +to the end of the current turn. + +#### Info/Chat Bar + +![image](pics/fetcha0de.jpg) + +Split into three sections, the Info/Chat bar lets you see a close-up +image of the card your mouse was last over, as well as gives you the +card info for that card. At the bottom of this bar there is a chat log +that helps keep track of events during the game as well as lets you +communicate with other players. if a card is placed on the table, +pointed at, or tapped it will get noted in the chat log as well as has a +link to the card that you can hover over and see an image of at the top +of the bar. + +### Basic Functions + +#### Rolling Dice + +At the beginning of a game players decide who is going first by rolling +a 20 sided die. In Cockatrice we do this by pressing Ctrl-I and hitting +enter. Hitting Ctrl-I brings up a die window and lets you select how +many sides you want on your die. Default is 20, and pressing enter will +“roll” the die. This action will show up in the cat log at on the bottom +right of the screen. You can also find this in the “game” menu at the +top of the window, selecting “player” and clicking on “roll die…” + +![image](pics/fetch7486.jpg) + +![image](pics/fetch3705.jpg) + +#### Draw Cards / Mulligan + +When a game starts and the first player has been selected, all players +will draw seven cards. this can be done by pressing Ctrl-M. Seven cards +will go from your library to your hand. Pressing Ctrl-M again will put +the seven cards from your hand back into your library, shuffle your +library and deal out six new cards to you. Each time you press Ctrl-M it +will give you one less card until you get down to one card, then it will +re-start at seven cards. This function can be found by clicking the +“game” menu on the top of the window, selecting “player” then selecting +“hand” and then “take Mulligan”. If you are playing a friendly game, +press Ctrl-M as normal, but then press Ctrl-D to draw cards until you +have a total of seven again. + +![image](pics/fetchfe20.jpg) + +#### Tapping + +Tapping cards is very basic. If a card is on the table under your +control, you can double click it to tap it and then double click again +to untap it. You can select multiple cards on the table by clicking and +dragging your mouse, then tap or untap all of the selected cards at the +same time. Other players can not tap or untap your cards. Pressing +Ctrl-U will untap everything you control. + +- Untapped + + ![image](pics/fetchb6fe.jpg) + +- Tapped + + ![image](pics/fetch867f.jpg) + +#### Attaching Cards to Cards + +Sometimes an Enchantment -Aura or Equipment cards need to be attached to +other cards that are already on the table. simply put the enchantment or +equipment on the table. Right-click the card and select “attach” (this +can also be done with Ctrl-A). A green arrow will appear, point and +click on the card you wish to attach. You can also attach cards to other +people’s cards. + +![image](pics/fetch100e.jpg) + +![image](pics/fetchb17a.jpg) + +#### Changing Power/Toughness + +Enchantments, Equipment, and other effects sometimes change a creatures +power or toughness. This can be done by right-clicking the card, and +selecting “power / toughness” then selecting which one you wish to do. +Other players can not change your creatures power and toughness. This +can also be done though a series of keyboard shortcuts seen below. + + -------------------------- ------------------------------ + (Select card) Ctrl++ Increase power + (Select card) Ctrl+- Decrease power + (Select card) Alt++ Increase toughness + (Select card) Alt+- Decrease toughness + (Select card) Ctrl+Alt++ Increase power and toughness + (Select card) Ctrl+Alt+- Decrease power and toughness + -------------------------- ------------------------------ + +![image](pics/fetche3fc.jpg) + +![image](pics/fetchd922.jpg) + +#### Adding Counters to Cards + +Sometimes Counters are needed to be placed on cards that the counters on +the side of the screen are not able to track. Cockatrice offers three +different counter color options, Red, Green, and Yellow. Although there +is no set standard on what color stands for what, it is mostly player +preference. Green could be used for +1/+1, red -1/-1, leaving yellow for +charge and quest counters, this is not a set rule. Adding counters is as +simple as right clicking on the card you wish to add counters too, and +currently there is no keyboard shortcut for this process. Removing +counters is the same process, right click and select remove. Other +players can not add or remove counters to or from your cards. + +![image](pics/fetch5170.jpg) + +(One of each counter) + +#### Pointing at Cards / Arrows + +Pointing at cards is needed for resolving spells, or declaring attackers +and blockers. All you need to do is right-click over a card and drag an +arrow over to what you are pointing at. Permanents, spells in the stack, +and even a players life total can be pointed at. You can point at your +opponents cards and life total, and they can point at yours. When your +arrows are no loner needed, press Ctrl-R to remove them from the screen. + +![image](pics/fetch98fd.jpg) + +![image](pics/fetch74b2.jpg) + +#### Creating Tokens + +Creating tokens can sometimes be tedious, but is well worth the effort +to keep a clean and organized game. Pressing Ctrl-T will bring up a +small window to assist you in creating a token. Simply enter the name of +the token you are creating, select its color, and give it a power and +toughness (\#/\#). You can also bring up this token window by selecting +“game” from the top menu, selecting “Player” then clicking on “Create +Token…”. A copy of the Last token made can be done by pressing \*Ctrl+G +or right-clicking on a already made token (or any card on the table) and +selecting “clone” or pressing Ctrl-H\*\*. When a token or clone leaves +play, it will be destroyed and vanish. + +![image](pics/fetch2c36.jpg) + +![image](pics/fetch9bff.jpg) + +![image](pics/fetche6b2.jpg) + +![image](pics/fetch84a2.jpg) + +Make copies of your last token by pressing Ctrl-G. + +![image](pics/fetch6847.jpg) + +Playing Online +============== + +With Cockatrice you will most likely play Magic games over the Internet +with real people all around the world. In order to help maintain a +pleasant environment for users, please read the messages below: + +- User Code of Conduct[^1] – Must Read for all Users + +- How to Report Abuse[^2] – It is recommended to read this as well + +Connect to Server +----------------- + +To connect to the Cockatrice server, launch the Cockatrice program, go +to the “Cockatrice” menu at the top left, and select “Connect”. A window +will appear (see image below). + +![image](pics/fetch23f3.jpg) + +If you have registered with Cockatrice, then enter your Username in the +“Player Name” field and your password in the “Password” field then click +“OK”. You may check the “Remember Password” box if you wish. If you do, +then the next time “Connect” is selected from the “Cockatrice” menu, the +window that appears will already have your Username and Password already +filled. Please take this into consideration if you share a computer with +other people, seeing that you are responsible for anything that happens +on the server with your username (As noted here). If you did not +register with Cockatrice, then simply fill in the Username with whatever +you like and click “OK”. If you would like to become a registered user, +read the instructions from the server’s website. + +Once you are connected to the server, more tabs will appear at the top +of the screen next to the “Deck” tab that you are already on. + +All About Games +--------------- + +This page is about creating, joining, watching, and searching for games +on the Cockatrice Server. In order to participate in any games, you have +to be connected to the server. The games on the server are where all of +the action take place. There will be many games happening on the server +at the same time. Basically, first a game is created by a player (it +could be you). Then other players join the game until the number of +players reaches the number set by that game’s creator. When the game has +no players participating in it, the game disappears. Creating a Game + +To Create a game, go to the “MTG room” tab. Click on the “Create” button +below the Games list. A window will appear (see below). + +![image](pics/fetch54df.jpg) + +Here are all of the options for creating a game: + +Description +: Describe the game in your own words (i.e. “Competitive Standard”, + “Casual EDH- No Infinites”, “RavnicaDraft”, “Here is Chris”) + +Players +: Specify the number of Players in the game. This cannot be changed + after the game is created. The game can only begin when the + specified number of players join. + +Spectators +: Spectators are users that are in a game, but they are not one of the + players. Spectators can see all of the public zones of the game and + everything displayed in the Info/Chat Bar. Any number of users can + join a game as a Spectator (as long as the “Spectators Allowed” box + is checked). + + Spectators Allowed + : Unchecking this box will prohibit any/all users from joining the + game as a Spectator. + + Spectators Need a Password to Join + : Checking this box will make it so that in order for a user to + join as a Spectator, they need to type the password you specify + in the Password Field. + + Spectators can Chat + : Checking this box will allow Spectators to type comments in the + Chat bar during the game. + + Spectators See Everything + : Checking this box will allow Spectators to view cards in all + private zones of all players (hands, libraries, face-down + cards). + +Password Field +: If you type anything in this field, a Player (or Spectator if the + “Spectators Need Password” box is checked) will need to type the + exact same thing you typed in order to join the game (and it is + case-sensitive). + +Only Buddies Can Join +: Checking this box will prevent any user who is not in your Buddy + List from joining the game as a Player or Spectator. NOTE: Your + username is not on your buddy list. If you leave a game you created, + and this box is checked, you will not be able to rejoin. + +Only Registered Users Can Join +: Checking this box will prevent anyone who has not registered on the + Cockatrice website from joining as a Player or Spectator. + +Game Type +: These check boxes have no effect on the game. They inform other + Users browsing the Games list of what format your game is. Users can + choose to view only games of a certain Type/Format. + () + +### Joining a Game + +Most of the time, to join a game you click on the “MTG Room” tab, click +on a game in the Games list, then click Join. If the Game’s creator +specified a password then you will have to type that password in a small +window that appears after you click Join (the password is +case-sensitive). If your User Profile meets the criteria of the Game’s +creator then a new tab will open with that game. There is also an easy +way to join a game in which a User in your Buddy List is playing. Go to +the “User Lists” tab, right click any Username from the Buddies Online +list (at the left of the window) to make a menu appear, and select “Show +this user’s games”. A window will open with a list of games that the +User is either playing or watching. In the same manner as described with +the “MTG Room” tab, simply click on a game and click Join. To watch a +game, the instructions are the same except that you click the “Join as +Spectator” button instead of the Join button. NOTE: If you are a player +in a game and you wish to become a spectator in that game, you must +first leave the game then rejoin as a Spectator. Same thing if you are a +Spectator and wish to play. + +### Searching for Games + +The Games list in the “MTG Room” tab displays by default all games that +have not reached the specified number of players. The “Filter Games” +button makes looking through this list easier if you are looking to join +a particular kind of game. When this button is selected, a window +appears (see below). + +![image](pics/fetchd30e.jpg) + +Game Description +: Displays games with certain descriptions. You can even search + partial names. + +Creator +: Displays games created by Users with that username. It even searches + for partial names. + +Player Count +: Displays all games where the specified number of players is + greater-than or equal to the “at least” number and less-than or + equal to the “at most” number. For instance, setting both numbers to + 3 will display all games whose creators made as 3-player games. + +Show Unavailable Games +: Checking this box will display games that are full and in progress. + You can still join these games as a Spectator if the game’s creator + allows it. + +Game Types +: Displays games with the selected types. Bear in mind that the + Cockatrice software does not enforce deck construction for formats. + So just because a game’s type is EDH/Commander, doesn’t necessarily + mean that is what’s being played in the game. Players can agree to + switch formats in a game. + +Keeping Track of Buddies +------------------------ + +Cockatrice allows registered users to keep track of other registered +users in a buddy list. You won’t be able to do anything with this list +(or other registered users at all) unless you are a registered user and +connected to the server. + +To add a User to your Buddy list, right-click their username and, in the +menu that appears, select “Add to buddy list”. You can right-click and +add a User in this manner anywhere you see their username (under the +“User lists” tab, the “MTG Room” tab, in a game, or in a direct chat). +When you add a User, their username will appear in the list “Buddies +Online” located under the “User lists” tab. If the username appears in a +light shade of gray, then that User is not connected to the server. If +it appears in black, then that User is connected to the server. + +If you see that one of your Buddies is connected, you can see the games +he/she is currently in. Right-click their username, and in the menu that +appears select “Show this user’s games”. A window will appear from which +you can watch or play in a game that your buddy is currently in. + +You can Direct Chat with a Buddy (or any user for that matter) by +right-clicking their username and, from the menu that appears, selecting +“Direct Chat”. When you do this, a new tab will open (both on your +window and on the other User’s window) containing a chat room that can +only be seen and used by the two of you. The place where you type in +your messages is at the very bottom of the window. + +To remove a User from your Buddy list, right-click their username, and +from the menu that appears select “Remove from buddy list”. + +Dealing with and Preventing Unpleasantries +------------------------------------------ + +Make a screenshot, there are several free programs available. +Right-Click a username to add to Ignore List. + +Frequently Asked Questions (FAQ) +================================ + +How to update the card database +------------------------------- + +When a new set comes out, you need to update your card database to be +able to see the new cards in the deck editor and use them in the game. +To do this, open the Oracle tool in the Cockatrice folder. It’s a +stand-alone program and is run directly out of the folder without +running Cockatrice. + +When the Oracle tool is running, open the menu and select ’Download sets +information’. Use the suggested address and click OK. After at most a +few seconds, you should be presented a list of sets to be downloaded. Do +not uncheck the ones you already have: they should be downloaded again. +Click ’Start download’ and wait. When the process is finished, the +database should be up to date. + +Change Card Art / Custom Card Art +--------------------------------- + +Is it possible to change the card art for a specif card, not by changing +the set order in my deck editor? + +YES! It is actually very simple. + +All you have to do is get a .JPG of the card you wish to change and name +the actual file of the image to the cards (exact) name and add ”.Full” +to the end. You can then take this new card image and replace the old +one in your downloaded image folder. + +NOTE: Image size does not matter. Try to use a high resolution .JPG of +the card you want to use for best quality. + +EXAMPLE: Let’s say you wish to turn your M11 Sun Titan from its original +art to a promo version. + +![image](pics/fetch74e3.jpg) + +If you are using Windows 7, you can find the default location for the +Cockatrice card art files under +`C:\Program Files (x86)\Cockatrice\pics\downloadedPics` Once there, look +for the folder of the set for the card we are replacing. For this +example, Sun Titan is in the M11 folder. Open the folder and +paste/replace your new “Promo” Sun Titan.full[.JPG] into this folder, +discarding the original copy. (If you have not yet downloaded the +original image of a card by selecting it in the deck editor or playing +it in an online game, the card image will not yet be there) + +Once you have the new card image in the proper folder, you can now start +Cockatrice (Or re-start if you already had the program open)and select +the deck editor to see your new card image. + +If you are changing the art of a card that has multiple versions in +other sets, make sure you put the new card in whatever folder (or set) +is highest on your card database set list. + +NOTE: Other players on Cockatrice will NOT see your new card image. They +will only see whatever version of the card is in their database. + +Use Higher Resolution Cards +--------------------------- + +Can you get better/higher resolution card art than the default +downloaded card images already used? + +Yes! + +When you click on a card for the first time in the Deck Editor, +Cockatrice goes onto the internet and finds an image of that card from a +database on another website. + +If you find a higher resolution .JPG of a card that you wish to use on +your Cockatrice, you can replace the image with no problem. If you can +find a card image in the 3,000 by 1,000 pixel range, and save it as the +cards (exact) name and add .Full to the end. + +If you are using Windows 7, you can find the default location for the +Cockatrice card art files under +`C:\Program Files (x86)\Cockatrice\pics\downloadedPics`. Once there, +look for the folder of the set for the card you are replacing. (If you +are not running Cockatrice as an administrator, look in +`C:\Users\username\AppData\Local\VirtualStore\Program Files (x86)\Cockatrice\pics\downloadedPics`.) + +Open the folder of the set with the card, and paste/replace the new +higher resolution .JPG image with the old low quality one. + +This process is the same for changing cards to custom images. + +NOTE: Other Cockatrice players will not see your higher resolution card +art, they will only see the image they have in their own database. + +Booster drafts +-------------- + +Cockatrice does not support booster drafts, so players need to use an +external service for this. + +Most people use so it’s probably not a bad idea +to register an account there before joining a game in Cockatrice. + +What is Legacy / Vintage / EDH? +------------------------------- + +See . + +Linking cards and URLs in the Cockatrice chat. +---------------------------------------------- + +The Cockatrice chat supports linking of cards and URLs by use of certain +tags around a word or phrase. + +### How to link a card in the Cockatrice chat + +To link a card in the Cockatrice chat, type out the full name of the +card, surrounded by the [card] and [/card] tags. + +For example: + + [card]Black Lotus[/card] + +### How to link a URL in the Cockatrice chat + +To link a URL in the Cockatrice chat, type out the url, surrounded by +the [url] and [/url] tags. + +For example: + + [url]http://www.cockatrice.de[/url] + [url]cockatrice.de[/url] + +Servatrice on CentOS 6 +====================== + +[servatrice] This Howto from woogerworks.com will help you to run your +own Cockatrice server (called Servatrice). An installation script can be +downloaded at: + +BEFORE CONTINUING, PLEASE UNDERSTAND THESE ARE THE VERY MOST BASIC +STEPS. THIS WILL NOT CONFIGURE SECURITY SETTINGS (YOU ARE RESPONSIBLE +FOR YOUR OWN SECURITY)! THIS WILL NOT CONNECT THE SERVER TO A DATABASE! +USE AT YOUR OWN RISK. + +1. Open a command shell and install the prerequisites + + 1. `yum -y groupinstall "development tools"` + + 2. `rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm` + + 3. `yum -y install qt-mysql qt-devel qt-mobility-devel protobuf-devel protobuf-compiler cmake28 libgcrypt-devel` + + 4. `cd` + +2. Download the source code: + + 1. `git clone https://github.com/Daenyth/Cockatrice` + + 2. `cd Cockatrice` + + 3. `mkdir build` + + 4. `cd build` + + 5. `cmake28 -DWITH_SERVER=1 ..` + + 6. `make` + + 7. `sudo make install` + +3. Create a servatrice.ini file. There is an example file you can + convert: + `sed -e "s/number_pools=1/number_pools=0/" ../servatrice/servatrice.ini.example > /servatrice.ini` + Here is one example file: + + [server] + port=4747 + statusupdate=15000 + logfile=server.log + name="My own Cockatrice server" + id=1 + number_pools=0 + + [servernetwork] + active=0 + port=14747 + ssl_cert=ssl_cert.pem + ssl_key=ssl_key.pem + + [authentication] + method=none + + [database] + type=none + prefix=cockatrice + hostname=localhost + database=servatrice + user=MYUSERNAMEHERE + password=MYSECUREPASSWORDHERE + + [rooms] + method=config + roomlist\size=1 + roomlist\1\name="FIRST ROOM NAME" + roomlist\1\description="FIRST ROOM DESCRIPTION" + roomlist\1\autojoin=true + roomlist\1\joinmessage="THIS IS A JOIN MESSAGE" + roomlist\1\game_types\size=11 + roomlist\1\game_types\1\name="Vintage (T1)" + roomlist\1\game_types\2\name="Legacy (T1.5)" + roomlist\1\game_types\3\name="Extended (T1.X)" + roomlist\1\game_types\4\name="Modern" + roomlist\1\game_types\5\name="Standard (T2)" + roomlist\1\game_types\6\name="Block Constructed" + roomlist\1\game_types\7\name="EDH/Commander" + roomlist\1\game_types\8\name="Highlander" + roomlist\1\game_types\9\name="2HG" + roomlist\1\game_types\10\name="Draft/Sealed" + roomlist\1\game_types\11\name="Other" + + [game] + max_game_inactivity_time=120 + max_player_inactivity_time=15 + + [security] + max_users_per_address=8 + message_counting_interval=10 + max_message_size_per_interval=1000 + max_message_count_per_interval=10 + max_games_per_user=5 + +4. Install the database server (optional) + + 1. `sudo yum -y install mysql-server mysql php-mysql` + + 2. `sudo service mysqld start` + + 3. `sudo chkconfig mysqld on` + + 4. `mysqladmin -u root password ’password’` + + 5. `mysql -u root -ppassword -e "create database servatrice;"` + + 6. `mysql -u root -ppassword -e "create user ’servatrice’@’localhost’ identified by ’password’;"` + + 7. `mysql -u root -ppassword -e "grant all privileges on servatrice.* to ’servatrice’@’localhost’;"` + + 8. `mysql -u servatrice -ppassword servatrice < ../servatrice/servatrice.sql` + + 9. `mysql -u servatrice -ppassword -e "insert into servatrice.cockatrice_users \ (admin,name,password_sha512,active) values (1,’servatrice’,’MYSHA512PASSWORD’,1);"` + + 10. `sed -i.bak -e "s/password=foobar/password=password/" -e "s/type=none/type=mysql/" \ -e "s/method=none/method=sql/"  /servatrice.ini` + +5. Start Servatrice for testing: `/usr/local/bin/servatrice`. I + recommend to run it later within a `screen` session or write an + init-script. + +You should be able to log in as a regular user using any account name. +You can login using the username *servatrice* and the password +*password* as the first moderator. + +If everything succeeded, you should tweak the servatrice.ini and set +your root password to something strong if not already done (at least 8 +characters, upper case, lower case, numbers, special characters). + +[^1]: TODO, dead forum link + +[^2]: TODO, dead forum link diff --git a/doc/usermanual/Usermanual.pdf b/doc/usermanual/Usermanual.pdf new file mode 100644 index 000000000..3d2d562da Binary files /dev/null and b/doc/usermanual/Usermanual.pdf differ diff --git a/doc/usermanual/Usermanual.tex b/doc/usermanual/Usermanual.tex new file mode 100644 index 000000000..23fa5580c --- /dev/null +++ b/doc/usermanual/Usermanual.tex @@ -0,0 +1,814 @@ +\documentclass[a4paper]{scrbook} +\usepackage[T1]{fontenc} % Fontencoding for pdf searches +\usepackage{textcomp} % EUR symbol for OT and T1 +\usepackage[osf,sc]{mathpazo} % Palatinofont +\usepackage{ellipsis} % better spaces for ellipses (…) +\usepackage{microtype} % Typographic finetuning +\usepackage{fixltx2e} % LaTeX fixes + +\usepackage[euler]{textgreek} +\usepackage[utf8]{inputenc} % utf8 input +\usepackage{graphicx,framed} % pictures, frames +\usepackage{makeidx} % Index +%\usepackage{fancyhdr} +\usepackage{listings} % source listings, e.g. for shell commands + +\usepackage{flafter} % floating objects +\usepackage{placeins} % \FloatBarrier command, http://www.tex.ac.uk/cgi-bin/texfaq2html?label=floats +\usepackage{color} % colors +\usepackage{tikz} % draw stuff +\usepackage{enumerate} % more enumerations +\usepackage{longtable} % multipage tables +\usepackage{booktabs} % better tables: http://www.ctan.org/tex-archive/macros/latex/contrib/booktabs/ + +\usepackage[nottoc]{tocbibind} % Index + Bibliographie -> toc + +%\setcounter{tocdepth}{3} % Depth for tocs; default = 2 + +%PDF stuff +\usepackage[unicode,linktocpage, +colorlinks, +bookmarks, +bookmarksopen, +pdfpagelabels=true]{hyperref} + +% pdf-specific, no line breaks! +\usepackage{xcolor} +\hypersetup{ + pdftitle = {Cockatrice Usermanual}, + pdfsubject = {Cockatrice, Servatrice, Manual}, + pdfauthor = {}, + pdfkeywords = {Cockatrice, Servatrice, Magic}, + linkcolor=blue!30!black, + citecolor=green!30!black, + urlcolor=blue!25!green!25!black, + % do not underline links + frenchlinks, + % break long links + breaklinks = true, +} + +% better Text/Float-ratio +\setcounter{topnumber}{3} +\renewcommand\topfraction{1} +\setcounter{bottomnumber}{3} +\renewcommand\bottomfraction{1} +\setcounter{totalnumber}{9} +\renewcommand\textfraction{.01} +\renewcommand\floatpagefraction{1} + +\makeindex +%opening +\title{Cockatrice} +\subtitle{Usermanual} + +%\pagestyle{fancy} + +\newcommand{\shellcmd}[1]{\texttt{\scriptsize #1}} + +\begin{document} +\maketitle +\tableofcontents + +\chapter{Preface} +This manual is basically a dump from the cockatrice.de dokuwiki. Cockatrice has some legal problems right now and the page is down. +This document tries to save the documentation about the software, beautify and extend it in the future. +Please contribute to the project, it is much too precious to be destroyed. + +\chapter{Getting Started} +\section{Making A User Profile} +Not available anymore, site is down. +If someone runs his own server where you can register a user profile, read his documentation. + +\section{Downloading and Installing the Cockatrice Program} +Due to a legal dispute there are currently no official builds left, so currently you have to build your own binaries. + +\subsection{Building the Client} +\subsubsection{Windows} +To build Cockatrice, we need the Cockatrice sourcecode, its dependencies and build tools of course. +Everything is freely available. + +There should be two ways to compile Cockatrice: +With Microsoft Visual Studio 2010 (Visual C++ 2010) and with MinGW, a minimalist GNU environment for Windows. + +We suggest to use Visual Studio because there is a severe problem with MinGW: +It doesn't offer a compatible version of its tools. +The Qt library needs a certain, old MinGW version\footnote{The officially needed version is MinGW 4.8.2, the last known working version was MinGW 20120426} which is unavailable on the internet because they use an online installer and don't offer old, stable releases. +Trying to build with the current version of MinGW will result in application crashes! +So MinGW and Qt force us to focus on Visual Studio and don't support MinGW. + +Gladly, Microsoft offers Visual Studio Express free of charge, which is a limited but sufficient version of Visual Studio. +It only requires a free of charge registration after 30 days. + +The following howto which uses Visual C++ 2010 Express has been tested with an up to date Windows 7 64Bit. +The resulting build is a 32bit binary, which runs on both 32bit and 64bit systems. + +\paragraph{Prerequisites} +Here is an introduction of all dependencies and tools needed for Cockatrice, followed by a list of downloadlinks in the same order. +\begin{enumerate} + \item The Microsoft Windows SDK for Windows 7 and .NET Framework 4 provides tools and libraries to create Windows applications. + \item The Visual C++ 2010 SP 1 Compiler Update for Windows SDK 7.1 (KB2519277) is a necessary update for the SDK. + \item Microsoft Visual C++ 2010 Express is the actual development environment. + \item Microsoft Visual Studio 2010 Service Pack 1 (VS10sp1-KB983509) is an update for Visual Studio. + \item The Qt libraries 4.8.x for Windows (VS 2010) are the main dependency for Cockatrice. + \item protobuf 2.5.0 is another dependency for Cockatrice (it has no installer, you need to download the zip file with the sourcecode). + \item cmake version 2.8.12.x is needed to create Cockatrice's project files for Visual Studio. Version 3 has not been tested yet with Cockatrice. + \item git is needed to download the latest Cockatrice source code. + \item Nullsoft Scriptable Install System (NSIS 3.0b0) is a program to create the Windows installer for Cockatrice. +\end{enumerate} + +All downloadlinks together: +\footnotesize{\begin{enumerate} + \item \url{http://www.microsoft.com/en-us/download/details.aspx?id=8279} % Win SDK + \item \url{http://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=4422} % SDK Update + \item \url{http://go.microsoft.com/?linkid=9709949} % MSVC + \item \url{http://www.microsoft.com/en-us/download/details.aspx?id=23691} % MSVC SP1 + \item \url{http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-windows-x86-vs2010-4.8.6.exe} + \item \url{http://www.cmake.org/files/v2.8/cmake-2.8.12.2-win32-x86.exe} + \item \url{http://git-scm.com/download/win} + \item \url{http://protobuf.googlecode.com/files/protobuf-2.5.0.zip} + \item \url{http://nsis.sourceforge.net/Download} +\end{enumerate}} + +\paragraph{Installation of the Prerequisites} +Problems will occur if you don't install the first four steps (all the Microsoft SDK and Visual Studio packages) in the exact same order as printed here! +\begin{enumerate} + \item Install Microsoft Windows SDK for Windows 7 and .NET Framework 4. + \item Apply the Visual C++ 2010 SP 1 Compiler Update for Windows SDK 7.1 + \item Install Visual C++ 2010 Express (Start the web installer, you may disable Silverlight) + \item Apply Visual C++ 2010 Express Service Pack 1 + \item Install Qt4 VS 2010 edition. + \item Install NSIS. + \item Install cmake. Choose to add CMake to the System path. + \item Install git. Choose Run Git from Windows Command Prompt. +\end{enumerate} + +As protobuf does neither provide an installer nor the libraries needed for Cockatrice, you have to build those with Visual Studio from the protobuf sources. +The Cockatrice installer will search for the protobuf libraries within the Cockatrice sources, so before unpacking and building protobuf, you need to download the Cockatrice sources first: +\begin{enumerate} + \item Right click into a folder, select Git Gui, then Clone Existing Directory, + \item enter Source Location \url{https://github.com/Daenyth/Cockatrice} + \item enter target directory (from now on called Cockatrice), click clone and wait until the sources have been downloaded, + \item close Git Gui. +\end{enumerate} + +Now we prepare protobuf: +\begin{enumerate} + \item Create a \shellcmd{build} directory inside the Cockatrice directory. + \item Copy the protobuf-2.5.0.zip into the build directory. + \item Rightclick the zip, choose Extract all. This uses the Windows included zip unpacker to extract the archive. +\end{enumerate} +To be clear: you will (and must) have a Cockatrice/build/protobuf-2.5.0/protobuf-2.5.0 directory hierarchy after that! + +Now the protobuf dependencies for Cockatrice will be built: +\begin{enumerate} + \item Start Visual C++ 2010 Express and from the File menu Open Project/Solution, move to the Cockatrice/build/protobuf-2.5.0/protobuf-2.5.0/vsprojects directory and choose protobuf.sln. + \item Let you guide through the Conversion Wizard, you don't need to create a backup. + \item After the conversion is finished, open the projects (uncheck ``Ask me for every project in this solution'' and click OK). + \item You don't need to look at the conversion report. + \item Select Release or Debug in the toolbar. Release optimizes the code for size and speed, while Debug creates larger, slower binaries, used for development. You need to use the same setting for Cockatrice later! We suggest to use Release. + \item Rightclick on libprotobuf, choose Build. Note that the output should say: Build started: Project libprotobuf, Configuration: Release Win32 + \item Then rightclick on protoc, choose Build. This will build libprotoc and the protoc executable, also as Release Win32. + \item After the build succeeded, close Visual C++. +\end{enumerate} +The Cockatrice/build/protobuf-2.5.0/protobuf-2.5.0/vsprojects/Release directory now contains libprotobuf.dll, libprotobuf.lib and protoc.exe which are needed for Cockatrice. + +\paragraph{Cockatrice Compilation} +Now everything is ready to compile Cockatrice. +\begin{enumerate} + \item Start CMake GUI, locate the Cockatrice directory and locate the Cockatrice/build directory. + Then click Configure and choose Visual Studio 10. + An error will occur during the Configure process because CMake does not know the location of the protobuf library. + \item To satisfy CMake, check Advanced, enter ``protobuf'' in the Search field, + \begin{enumerate} + \item Set PROTOBUF\_INCLUDE\_DIR to Cockatrice/build/protobuf-2.5.0/protobuf-2.5.0/src/ + \item Set PROTOBUF\_LIBRARY to Cockatrice/build/protobuf-2.5.0/protobuf-2.5.0/vsprojects/Release/libprotobuf.lib + \item Set PROTOBUF\_PROTOC\_EXECUTABLE to Cockatrice/build/protobuf-2.5.0/protobuf-2.5.0/vsprojects/Release/protoc.exe + \item Set PROTOBUF\_PROTOC\_LIBRARY to Cockatrice/build/protobuf-2.5.0/protobuf-2.5.0/vsprojects/Release/libprotoc.lib % TODO: Is this needed? I don't think so; but it doesn't hurt either. + \item Click Configure again. + \end{enumerate} + \item Click Generate, then close CMake GUI. The project files have been generated. + \item Start Visual C++ 2010 Express and from the File menu select Open Project/Solution, point to the Cockatrice/build directory and choose Cockatrice.sln. + \item Select Release or Debug in the toolbar exactly as you did for protobuf. We suggest Release. + \item Rightclick on ALL\_BUILD, choose Build. +\end{enumerate} + +\paragraph{Updating your Cockatrice build} +If you just compiled Cockatrice for the first time, you skip this step obviously. +But if you want to update Cockatrice after the source code changed on github, do it like this: +\begin{enumerate} + \item Start Git Bash and update Cockatrice: + \begin{enumerate} + \item \shellcmd{git pull origin master} + \item \shellcmd{Close Git Bash} + \end{enumerate} + \item Start Visual C++ 2010 Express and build the Cockatrice sources again. +\end{enumerate} +Cockatrice has now been updated and built. You may repeat this process every time when the source code changed. + +\paragraph{Cockatrice installation} +To install Cockatrice, you have to create an installer with NSIS now. +Change to the directory \shellcmd{nsis} in the Cockatrice directory, right click the cockatrice.nsi file and select \shellcmd{Compile NSIS Script}. +The NSIS program then creates a file called cockatrice\_win32\_YYYYmmdd\_git-xxxxxxx.exe. This is the complete, redistributable installer for your Cockatrice build. + +Now install Cockatrice by executing the installer. +Note: if you installed Qt in other than the default path, you have to fix the paths in the cockatrice.nsi file; you can edit this file with a text editor. + +We will remove this static NSIS file in the future and let CMake create the NSIS file on the fly. + +\paragraph{Create a card database} +Start the oracle.exe (the installer does this automatically) and let it generate a current cards.xml file: +\begin{enumerate} + \item File $\to$ Download Sets Information $\to$ OK (if there are no MtG sets listed) + \item Check All, Start Download +\end{enumerate} +Congratulations, you may now use Cockatrice! + +\subsubsection{Linux, BSD, OS X} +The following procedures have been tested with Debian Wheezy, Fedora 18, XUbuntu 13.10, FreeBSD 9.1 and 10.0. +If you use Gentoo with KDE you have the needed prerequisites and may continue with downloading the source. +If you use Bodhi or Arch Linux (AUR) or another distribution that includes Cockatrice, you might install Cockatrice from the default packages -- though the package might be old, +so you probably should continue with this howto. + +Before you install new software, you should update your system. The following instructions failed on a fresh installation of Fedora 18 and FreeBSD 9.1 until the systems were updated. +\begin{enumerate} + \item You need to install the build tools and dependencies. This varies between the Linux distributions. + \begin{description} + \item[Debian, Ubuntu and spin-offs] \shellcmd{sudo apt-get install build-essential git libqt4-dev qtmobility-dev libprotobuf-dev protobuf-compiler cmake} + \item[Fedora] \shellcmd{sudo yum groupinstall "Development Tools"\\ + yum install qt-devel qt-mobility-devel protobuf-devel protobuf-compiler cmake} + \item[FreeBSD 9] \shellcmd{pkg\_add -r qt4 qt4-linguist qt4-moc qt4-qmake qt4-rcc qt4-uic git cmake protobuf} + \item[FreeBSD 10] \shellcmd{pkg install qt4 qt4-linguist qt4-moc qt4-qmake qt4-rcc qt4-uic git cmake protobuf} + \item[OS X] \shellcmd{brew install qt cmake protobuf} + \end{description} + \item Download the sources from github via \\ \shellcmd{cd\\ git clone https://github.com/Daenyth/Cockatrice.git} + \item To compile the sources, change into the newly created directory, create a build directory and invoke cmake:\\ + \shellcmd{cd Cockatrice \\ +mkdir build \\ +cd build \\ +cmake ..\\ +make}\\ + \item You may install the program into the directory \shellcmd{/usr/local} by typing \shellcmd{sudo make install} but you should also be able to start + cockatrice and the oracle from the build directory. + \item Before you start Cockatrice for the first time, run \shellcmd{oracle -dlsets} and download available cards, then run \shellcmd{cockatrice}. +The default paths for decks, pics, cards and tokens are located in \\ \shellcmd{/home//.local/share/data/Cockatrice/Cockatrice}. +\end{enumerate} + +\subsection{Building the Server} +You don't need your own server if you plan to play only. But as Cockatrice is open source you are free to run your own. +The compilation works like already written above, but instead of invoking \shellcmd{cmake ..}, you have to do it like this: +\begin{itemize} + \item If you want to build the server, use:\\ \shellcmd{cmake -DWITH\_SERVER=1 ..} + \item If you want to build the server, but not the client, use:\\ \shellcmd{cmake -DWITH\_SERVER=1 -DWITHOUT\_CLIENT=1 ..} +\end{itemize} +Further, the server has a dependency on libgcrypt, so you need to install it as well: +\begin{description} + \item[FreeBSD 10] \shellcmd{pkg install libgcrypt} +\end{description} +There is more information on compiling and running Servatrice on CentOS 6 in chapter \ref{servatrice} on page \pageref{servatrice}. + +\section{Downloading Card Database Using the Oracle} +If you are installing Cockatrice for the first time, changing what sets are in your database or even adding the newest set to your database, this tutorial will show you how to do it properly. + +The Oracle will automatically run after the initial setup of Cockatrice. If you would like to re-install your database or add new sets you can find it (for Windows) by clicking the start menu, going to all programs, selecting the Cockatrice folder, and in there you will find the Oracle tool. +\begin{itemize} + \item When the Oracle importer opens, click on “File” in the top left corner and select “Download sets information…” + \begin{center} +\includegraphics[scale=0.8]{pics/fetch554a} + \end{center} + \item This will bring up a box where you can input the URL of a card database. The default address is \url{http://www.cockatrice.de/files/sets.xml} this was an XML file found on the Cockatrice website that has the current set listings for Magic the Gathering. As the page is down, you have to import the file which is distributed with the Cockatrice sources. + This can also be done from the file menu. + \item Select “OK” to load the set listings. + \item A list of all current sets will be brought up. A default selection of sets will automatically be checked. From here you can check or uncheck all sets, or you may only download specific sets that you wish to play with. + \begin{center} +\includegraphics[scale=0.8]{pics/fetchfc3d} + \end{center} +NOTE: If you are playing against someone who is using a card that is not in your database, you will not see a card image or oracle text for that card. Some players like to download all sets to avoid this issue, but other players who only play specific formats (Like \textsc{T2}, \textsc{Standard}, or \textsc{Extended}) wish to keep their database small with only cards they will be using. + \item After you select which sets you wish to download, select “Start download” at the bottom of the Oracle to download the selected sets information. + \item After download is complete, close the Oracle and run Cockatrice. + \item We are now ready to sort our set information in our deck editor. +\end{itemize} + +\section{Editing Set Order and Preference of Card Art} +Many cards have been re-printed in different sets, and in return have different versions of artwork (like the card “Cancel” which can be found in many sets, but has different artwork for each, e.g. \textsc{Zendikar} versus \textsc{M11}: +\begin{center} +\includegraphics{pics/fetchc18b} +\includegraphics{pics/fetche1f4} +\end{center} + +Some players like to have the most current artwork displayed on their cards, while other players have a favorite set they wish to display instead. + +\begin{itemize} + \item Run Cockatrice and select “Deck editor” from the top right Cockatrice menu. This will bring up the Deck Editor along with a list of all cards that are currently in your database which you downloaded using the Oracle Tool. + \item To change what version of the cards will be shown, click on “Card database” on the top left of the Deck editor window, and select “Edit sets…” + \begin{center} +\includegraphics[scale=0.55]{pics/fetchf924} + \end{center} + \item This will bring up a new window that has a list of all sets you currently have downloaded to your database. To change the order of the sets, simply drag and drop them into place. + This will determine which artwork is shown for your cards. If a card is found in multiple sets, whichever set is closest to the top of this list will be the art displayed. + Example: If \textsc{M11} is above \textsc{Zendikar}, The M11 version of the card “Cancel” will be displayed in your Deck editor and Cockatrice games. +\end{itemize} +NOTE: Your opponent will NOT see what artwork you have selected for each of your cards. They will only see what they have selected for their own. + +\section{The Deck Editor / Making a Deck} +The Cockatrice Deck Editor is a tool you can use to make decks to play online. +The cards shown in the Deck Editor are from a database that you downloaded with the Oracle Tool. If you are missing cards or a new set has come out, you must re-run the Oracle and download set information. +\begin{center} +\includegraphics[scale=0.55]{pics/fetch52e0} +\end{center} + +\begin{description} + \item[1. Search Bar] The search bar lets you type in the name of a card and the editor will only show cards that start with whatever you typed in. Example: Typing in ‘B’ will show all cards that start with the letter ‘B’ and typing in ‘Dark’ will show you all cards that start with ‘Dark’ and so on. If you were looking for the card “Sun Titan”, you would not type in ‘Titan’ you would have to type in ‘Sun’ first. Typing in ‘Titan’ will only show you any cards that start with ‘Titan’. + \item[2. Card Search/Filter] The Card search button will bring up a new window that helps you filter out cards more specifically. A variety of check boxes will help find what you need. Card name lets you filter out only cards that have a cretin word in them. Card text can help you find key words like “Haste” or “Infect”. If you were to uncheck all boxes except for “Instant” along with “Artifact” and “U”, the Deck editor will only show you all Blue Instant and Artifact cards. +\begin{center} +\begin{tabular}{ll} +\toprule Letter & Card Type \\ \midrule +U & Blue \\ +W & White \\ +X & Colorless \\ +G & Green \\ +R & Red\\ +B & Black \\ \bottomrule +\end{tabular} +\end{center} + \item[3. Card Data] This section shows the Oracle text for the card that you currently have selected. It will show you up-to-date information on the card such as the Name, Mana cost, Card type, Power/Toughness, and any abilities the card has. It will not show you flavor text. + \item[4. Adding/Removing Card Buttons] +The buttons in the bottom middle will add or remove cards from your Deck List, as well as ad a card specifically to your Sideboard. Having a card selected on the left column and hitting the Enter key will also add it to your deck list. + \item[5. Deck Name/Comments] +The area in the top right lets you name your deck as well as give any comments or descriptions such as how to play the deck, where the deck came from, or explain a theme. Putting something in the “Deck Name” space will NOT be what the file name of the file for your deck. That is spate in the “save” selection under the “Deck” menu found at the top left of the Deck Editor window. + \item[6. Main Deck List] +This area will show you what cards you currently have added to your deck list. It is sorted by card type and also shows you how many of each card and card type you have added, as well as keeps track of how many total cards you have added to your main deck list. This will not add any number of cards you have added to your Sideboard. + \item[7. Sideboard List] +The bottom section of the deck list shows any cards you have added to your sideboard. Again these are split into card types and it will keep track of how many of each card you have as well as how many of each card type and total cards in sideboard. This section will not add any cards from the main deck. Once you have all of your cards added to your deck, you must save it as a file Cockatrice can read. Select “Deck” from the top left corner of the Deck Editor screen, and select “Save Deck” or “Save Deck As…” and it will bring up a new window where you can select where you would like to save your deck, as well as assign it a file name. Cockatrice decks are saved as .cod files. +\end{description} + +\section{Loading a deck list from your clipboard} +If you find a deck online, or you have a deck list saved in a word document, it is easy to transfer it over into a Cockatrice deck file as long as it is in a simple deck list format. +The simple deck list format is a list where each line begins with a number, followed by a whitespace, followed by the cardname, e.g. +\begin{verbatim} +2 Doom Blade +13 Island +10 Swamp +4 Cancel +... +\end{verbatim} + +Simply find the word document or deck list online that you wish to save as a deck, and select the text and copy it to your clipboard. Next, open the Deck Editor screen, and click on the Deck menu from the top left corner. Select “Load deck from clipboard…” and the deck editor will bring up a new window that has the deck list you had copied to your clipboard. Make sure the Deck list looks correct and hit “OK” in the bottom right corner of the window. The Deck editor will now add all the cards in the list to your main deck list. + +\begin{center} +\includegraphics{pics/loaddeck_clip} +\end{center} + +\begin{center} +\includegraphics{pics/okdecklist} +\end{center} + +NOTE: If you add a card to a deck list with this function that you do not have downloaded to your personal cockatrice database though the Oracle tool, the card will take a spot in your main deck list, and count toward the total number of cards, but it will show up as a blank image with no Oracle data or card information. + +\section{Cockatrice Settings} +TODO + +\section{Learning the Ropes / Starting a Solitaire Game} +The best way to get familiar with the way Cockatrice plays is to start a local game that you can play around in by yourself. You could also jump online and start slowly learning, and let other players help you. + +To start a Solo Local game, in the main Cockatrice window, click on “Cockatrice” on the top left, and select “Start local game…”. + +\begin{center} +\includegraphics{pics/fetch2ab8} +\end{center} + +This will bring up a small window that lets you select how many players will be in this local game you are creating. For right now, since we want to do a solo Solitaire game, select one player and hit “OK”. + +\begin{center} +\includegraphics{pics/fetchf010} +\end{center} + +\subsection{Loading a Deck / Using Sideboard} + +This will now bring you to a screen where you load a deck to play with. On the top left part of this screen you will find a button that is labeled “Load Local Deck”. Click that button and it will bring up a window where you can find and select what deck you would like to play with. + +\begin{center} +\includegraphics{pics/fetchf0d2} +\includegraphics[scale=0.8]{pics/fetch55a7} +\end{center} +Select a deck or a .cod file and click “Open”. + +\begin{center} +\includegraphics[scale=0.4]{pics/fetch9b89} +\end{center} + +After the file has loaded you will see all of the cards in that deck laid out on the table. If you hover your mouse over a card, the card image and Oracle info will show on the right side of the screen. If you have a sideboard made for the deck there will be a second section on the table for this sideboard. This screen gives you the ability to double check your deck to make sure it is not only the correct deck you want to play with, but it lets you see that all card images have downloaded properly. If you have cards not showing up at all or they are just blank cards with names on them, you may not have that set downloaded with your Oracle tool. If you have a sideboard, you can drag and drop cards from your main deck to your sideboard or vice-versa. You can do this by clicking and dragging a card to or from your main deck or sideboard. + +NOTE: Moving cards from your main deck to sideboard will NOT change how your deck file is saved, it will only change it temporary for the game you are playing or until you load a new deck. + +When you are satisfied with your deck choice and/or sideboarding options, click on the red outlined “Ready to start” button found a the top of the screen. + +\subsection{Finding Your Way Around} +The main game screenlooks like this +\begin{center} +\includegraphics[scale=0.4]{pics/fetch7cf0} +\end{center} +(Please note your screen will look different due to background image options.) + +\subsubsection{Main Table / Play area} +Split into four areas, this is where all the action will go down. +\begin{description} + \item[The Stack] The area on the left side of the table where Instant and Sorcery cards will be played. This is for things that will only temporarily be put on the table, then into the graveyard. Multiple cards may be added to this area at the same time. Anything on this part of the table will be seen by all players. + \item[Battlefield] This is the soul part of the game table. this is where creatures, enchantments, artifacts, and even plainswalkers will be placed. As cards are moved from your had to the table, they will be aligned to an invisible grid and moved around from there. Tap cards by double clicking them. Anything on this part of the table will be seen by all players. + \item[Land] This space is for land cards, but any card may be placed here. Tap cards here by double clicking them. Anything on this part of the table will be seen by all players. + \item[Hand] Every time you draw a card it will go here to your hand. You may also drag cards from the table back to your hand. Your opponents can not see what is in your hand. +\end{description} + +\subsubsection{Player Info Section} +\begin{center} +\includegraphics[scale=0.7]{pics/fetch0300} +\end{center} + +\begin{description} + \item[Player Avatar] This is a $156\times 60$ pixel JPG image that can be uploaded though the main Cockatrice website. All players in the game room can see this image. It serves nothing more than an online identity for you and other players. + \item[Player Name] Your online name that you picked though the main Cockatrice website. + \item[Life Total] Your in-game life. Using your mouse, if left-clicked will raise this number by one, and if right-clicked lowered by one. There are also keyboard shortcuts to change your life total. + \item[Counters] These seven multicolored circles are used as counters. They can be seen by all players and can be changed by left or right clicking on them to add or subtract a number. Players use them for various digit counting but primarily used for adding and subtracting floating mana produced by card effects. The bottom two white counters can be used for other things like Poison. + \item[Library] This is your deck of cards. The number in the middle reflects how many cards are left in your library. Double clicking the deck lets you draw a card and add it to your hand, you can also drag cards off the top into the battlefield or to your hand. Right-clicking the deck brings up a menu that allows other things to happen like reviling the top number of cards, shuffling, or moving cards directly into the graveyard. + \item[Number of cards in hand] The number in the middle represents how many cards are currently in your hand. Other players can see this number but can not see the cards actually in your hand. + \item[Graveyard] Cards can be dragged and dropped into your graveyard from play or vise-versa, the stack, your hand, or even your library. The number in the middle represents how many cards are currently in your graveyard. Any player may right-click on the graveyard and bring up a menu that shows what cards are in it. + \item[Exile] Cards can be dragged and dropped into exile from play or vise-versa, the stack, your hand, or even your library. The number in the middle represents how many cards are currently in your exile. Any player may right-click on the exile and bring up a menu that shows what cards are in it. +\end{description} + +\subsubsection{Turn Phases} +\begin{center} +\includegraphics[scale=0.7]{pics/fetchfebd} +\end{center} +This bar located on the left most side of the screen represents the 11 steps in a players turn. To go from one phase to the next, you can click on the square of the phase you want to move to, or you can hit Ctrl+space to move down to the next. Some phases even have their own keyboard shortcut. Going from one phase to the next does not actually do anything to your or your cards, it is only a place marker for your opponents to see and keep up with what you are doing in your turn. For example, clicking to the “Draw Phase” will not automatically draw you a card. It is customary for a player to end their turn on the “End of turn step” and let their opponent hit the “next turn” button. This is a courtesy for other players if they wish to do something like use an instant at the end of your turn, or in response to something you did. + +NOTE: Players sometimes use the term EOT which stands for “End Of Turn”. This is to let other players know they are doing something in response to the end of the current turn. + +\subsubsection{Info/Chat Bar} +\begin{center} +\includegraphics[scale=0.8]{pics/fetcha0de} +\end{center} +Split into three sections, the Info/Chat bar lets you see a close-up image of the card your mouse was last over, as well as gives you the card info for that card. At the bottom of this bar there is a chat log that helps keep track of events during the game as well as lets you communicate with other players. if a card is placed on the table, pointed at, or tapped it will get noted in the chat log as well as has a link to the card that you can hover over and see an image of at the top of the bar. + +\subsection{Basic Functions} +\subsubsection{Rolling Dice} +At the beginning of a game players decide who is going first by rolling a 20 sided die. In Cockatrice we do this by pressing Ctrl-I and hitting enter. Hitting Ctrl-I brings up a die window and lets you select how many sides you want on your die. Default is 20, and pressing enter will “roll” the die. This action will show up in the cat log at on the bottom right of the screen. You can also find this in the “game” menu at the top of the window, selecting “player” and clicking on “roll die…” +\begin{center} + \includegraphics{pics/fetch7486}\\ + \includegraphics{pics/fetch3705} +\end{center} + +\subsubsection{Draw Cards / Mulligan} +When a game starts and the first player has been selected, all players will draw seven cards. this can be done by pressing Ctrl-M. Seven cards will go from your library to your hand. Pressing Ctrl-M again will put the seven cards from your hand back into your library, shuffle your library and deal out six new cards to you. Each time you press Ctrl-M it will give you one less card until you get down to one card, then it will re-start at seven cards. This function can be found by clicking the “game” menu on the top of the window, selecting “player” then selecting “hand” and then “take Mulligan”. If you are playing a friendly game, press Ctrl-M as normal, but then press Ctrl-D to draw cards until you have a total of seven again. +\begin{center} +\includegraphics[scale=0.5]{pics/fetchfe20} +\end{center} + + +\subsubsection{Tapping} +Tapping cards is very basic. If a card is on the table under your control, you can double click it to tap it and then double click again to untap it. You can select multiple cards on the table by clicking and dragging your mouse, then tap or untap all of the selected cards at the same time. Other players can not tap or untap your cards. Pressing Ctrl-U will untap everything you control. + +\begin{itemize} + \item Untapped + \begin{center} + \includegraphics{pics/fetchb6fe} + \end{center} + + \item Tapped + \begin{center} + \includegraphics{pics/fetch867f} + \end{center} +\end{itemize} + +\subsubsection{Attaching Cards to Cards} +Sometimes an Enchantment -Aura or Equipment cards need to be attached to other cards that are already on the table. simply put the enchantment or equipment on the table. Right-click the card and select “attach” (this can also be done with Ctrl-A). A green arrow will appear, point and click on the card you wish to attach. You can also attach cards to other people's cards. +\begin{center} + \includegraphics{pics/fetch100e}\\ + \includegraphics{pics/fetchb17a} +\end{center} + + +\subsubsection{Changing Power/Toughness} +Enchantments, Equipment, and other effects sometimes change a creatures power or toughness. This can be done by right-clicking the card, and selecting “power / toughness” then selecting which one you wish to do. Other players can not change your creatures power and toughness. This can also be done though a series of keyboard shortcuts seen below. + +\begin{center} +\begin{tabular}{ll} +(Select card) Ctrl++ & Increase power \\ +(Select card) Ctrl+- & Decrease power \\ +(Select card) Alt++ & Increase toughness \\ +(Select card) Alt+- & Decrease toughness \\ +(Select card) Ctrl+Alt++ & Increase power and toughness \\ +(Select card) Ctrl+Alt+- & Decrease power and toughness +\end{tabular} +\includegraphics[scale=0.5]{pics/fetche3fc}\\ +\includegraphics{pics/fetchd922} +\end{center} + +\subsubsection{Adding Counters to Cards} +Sometimes Counters are needed to be placed on cards that the counters on the side of the screen are not able to track. Cockatrice offers three different counter color options, Red, Green, and Yellow. Although there is no set standard on what color stands for what, it is mostly player preference. Green could be used for +1/+1, red -1/-1, leaving yellow for charge and quest counters, this is not a set rule. Adding counters is as simple as right clicking on the card you wish to add counters too, and currently there is no keyboard shortcut for this process. Removing counters is the same process, right click and select remove. Other players can not add or remove counters to or from your cards. + +\begin{center} +\includegraphics{pics/fetch5170} +\end{center} +(One of each counter) + +\subsubsection{Pointing at Cards / Arrows} +Pointing at cards is needed for resolving spells, or declaring attackers and blockers. All you need to do is right-click over a card and drag an arrow over to what you are pointing at. Permanents, spells in the stack, and even a players life total can be pointed at. You can point at your opponents cards and life total, and they can point at yours. When your arrows are no loner needed, press Ctrl-R to remove them from the screen. + +\begin{center} +\includegraphics[scale=0.5]{pics/fetch98fd} \\ +\includegraphics[scale=0.5]{pics/fetch74b2} +\end{center} + +\subsubsection{Creating Tokens} +Creating tokens can sometimes be tedious, but is well worth the effort to keep a clean and organized game. Pressing Ctrl-T will bring up a small window to assist you in creating a token. Simply enter the name of the token you are creating, select its color, and give it a power and toughness (\#/\#). You can also bring up this token window by selecting “game” from the top menu, selecting “Player” then clicking on “Create Token…”. A copy of the Last token made can be done by pressing *Ctrl+G or right-clicking on a already made token (or any card on the table) and selecting “clone” or pressing Ctrl-H**. When a token or clone leaves play, it will be destroyed and vanish. + +\begin{center} +\includegraphics[scale=0.5]{pics/fetch2c36} \\ +\includegraphics[scale=0.5]{pics/fetch9bff} \\ +\includegraphics[scale=0.5]{pics/fetche6b2} \\ +\includegraphics[scale=0.5]{pics/fetch84a2} +\end{center} + +Make copies of your last token by pressing Ctrl-G. +\begin{center} +\includegraphics[scale=0.5]{pics/fetch6847} +\end{center} + +\chapter{Playing Online} + With Cockatrice you will most likely play Magic games over the Internet with real people all around the world. In order to help maintain a pleasant environment for users, please read the messages below: + +\begin{itemize} + \item User Code of Conduct\footnote{TODO, dead forum link} -- Must Read for all Users + \item How to Report Abuse\footnote{TODO, dead forum link} -- It is recommended to read this as well +\end{itemize} + +\section{Connect to Server} +To connect to the Cockatrice server, launch the Cockatrice program, go to the “Cockatrice” menu at the top left, and select “Connect”. A window will appear (see image below). +\begin{center} +\includegraphics[scale=0.5]{pics/fetch23f3} +\end{center} +If you have registered with Cockatrice, then enter your Username in the “Player Name” field and your password in the “Password” field then click “OK”. You may check the “Remember Password” box if you wish. If you do, then the next time “Connect” is selected from the “Cockatrice” menu, the window that appears will already have your Username and Password already filled. Please take this into consideration if you share a computer with other people, seeing that you are responsible for anything that happens on the server with your username (As noted here). If you did not register with Cockatrice, then simply fill in the Username with whatever you like and click “OK”. If you would like to become a registered user, read the instructions from the server's website. + +Once you are connected to the server, more tabs will appear at the top of the screen next to the “Deck” tab that you are already on. + +\section{All About Games} +This page is about creating, joining, watching, and searching for games on the Cockatrice Server. In order to participate in any games, you have to be connected to the server. The games on the server are where all of the action take place. There will be many games happening on the server at the same time. Basically, first a game is created by a player (it could be you). Then other players join the game until the number of players reaches the number set by that game's creator. When the game has no players participating in it, the game disappears. +Creating a Game + +To Create a game, go to the “MTG room” tab. Click on the “Create” button below the Games list. A window will appear (see below). +\begin{center} +\includegraphics[scale=0.5]{pics/fetch54df} +\end{center} + +Here are all of the options for creating a game: +\begin{description} + \item[Description] Describe the game in your own words (i.e. “Competitive Standard”, “Casual EDH- No Infinites”, “RavnicaDraft”, “Here is Chris”) + \item[Players] Specify the number of Players in the game. This cannot be changed after the game is created. The game can only begin when the specified number of players join. + \item[Spectators] Spectators are users that are in a game, but they are not one of the players. Spectators can see all of the public zones of the game and everything displayed in the Info/Chat Bar. Any number of users can join a game as a Spectator (as long as the “Spectators Allowed” box is checked). + \begin{description} + \item[Spectators Allowed] Unchecking this box will prohibit any/all users from joining the game as a Spectator. + \item[Spectators Need a Password to Join] Checking this box will make it so that in order for a user to join as a Spectator, they need to type the password you specify in the Password Field. + \item[Spectators can Chat] Checking this box will allow Spectators to type comments in the Chat bar during the game. + \item[Spectators See Everything] Checking this box will allow Spectators to view cards in all private zones of all players (hands, libraries, face-down cards). + \end{description} + \item[Password Field] If you type anything in this field, a Player (or Spectator if the “Spectators Need Password” box is checked) will need to type the exact same thing you typed in order to join the game (and it is case-sensitive). + \item[Only Buddies Can Join] Checking this box will prevent any user who is not in your Buddy List from joining the game as a Player or Spectator. NOTE: Your username is not on your buddy list. If you leave a game you created, and this box is checked, you will not be able to rejoin. + \item[Only Registered Users Can Join] Checking this box will prevent anyone who has not registered on the Cockatrice website from joining as a Player or Spectator. + \item[Game Type] These check boxes have no effect on the game. They inform other Users browsing the Games list of what format your game is. Users can choose to view only games of a certain Type/Format. (\url{http://en.wikipedia.org/wiki/Magic:_The_Gathering_formats}) +\end{description} + +\subsection{Joining a Game} +Most of the time, to join a game you click on the “MTG Room” tab, click on a game in the Games list, then click Join. +If the Game's creator specified a password then you will have to type that password in a small window that appears after you click Join (the password is case-sensitive). +If your User Profile meets the criteria of the Game's creator then a new tab will open with that game. +There is also an easy way to join a game in which a User in your Buddy List is playing. +Go to the “User Lists” tab, right click any Username from the Buddies Online list (at the left of the window) to make a menu appear, and select “Show this user's games”. +A window will open with a list of games that the User is either playing or watching. In the same manner as described with the “MTG Room” tab, simply click on a game and click Join. +To watch a game, the instructions are the same except that you click the “Join as Spectator” button instead of the Join button. +NOTE: If you are a player in a game and you wish to become a spectator in that game, you must first leave the game then rejoin as a Spectator. Same thing if you are a Spectator and wish to play. + +\subsection{Searching for Games} +The Games list in the “MTG Room” tab displays by default all games that have not reached the specified number of players. The “Filter Games” button makes looking through this list easier if you are looking to join a particular kind of game. When this button is selected, a window appears (see below). +\begin{center} +\includegraphics[scale=0.5]{pics/fetchd30e} +\end{center} +\begin{description} + \item[Game Description] Displays games with certain descriptions. You can even search partial names. + \item[Creator] Displays games created by Users with that username. It even searches for partial names. + \item[Player Count] Displays all games where the specified number of players is greater-than or equal to the “at least” number and less-than or equal to the “at most” number. For instance, setting both numbers to 3 will display all games whose creators made as 3-player games. + \item[Show Unavailable Games] Checking this box will display games that are full and in progress. You can still join these games as a Spectator if the game's creator allows it. + \item[Game Types] Displays games with the selected types. Bear in mind that the Cockatrice software does not enforce deck construction for formats. So just because a game's type is EDH/Commander, doesn't necessarily mean that is what's being played in the game. Players can agree to switch formats in a game. +\end{description} + +\section{Keeping Track of Buddies} + + +Cockatrice allows registered users to keep track of other registered users in a buddy list. You won't be able to do anything with this list (or other registered users at all) unless you are a registered user and connected to the server. + +To add a User to your Buddy list, right-click their username and, in the menu that appears, select “Add to buddy list”. You can right-click and add a User in this manner anywhere you see their username (under the “User lists” tab, the “MTG Room” tab, in a game, or in a direct chat). When you add a User, their username will appear in the list “Buddies Online” located under the “User lists” tab. If the username appears in a light shade of gray, then that User is not connected to the server. If it appears in black, then that User is connected to the server. + +If you see that one of your Buddies is connected, you can see the games he/she is currently in. Right-click their username, and in the menu that appears select “Show this user's games”. A window will appear from which you can watch or play in a game that your buddy is currently in. + +You can Direct Chat with a Buddy (or any user for that matter) by right-clicking their username and, from the menu that appears, selecting “Direct Chat”. When you do this, a new tab will open (both on your window and on the other User's window) containing a chat room that can only be seen and used by the two of you. The place where you type in your messages is at the very bottom of the window. + +To remove a User from your Buddy list, right-click their username, and from the menu that appears select “Remove from buddy list”. + +\section{Dealing with and Preventing Unpleasantries} +Make a screenshot, there are several free programs available. +Right-Click a username to add to Ignore List. + +\chapter{Frequently Asked Questions (FAQ)} +\section{How to update the card database} +When a new set comes out, you need to update your card database to be able to see the new cards in the deck editor and use them in the game. To do this, open the Oracle tool in the Cockatrice folder. It's a stand-alone program and is run directly out of the folder without running Cockatrice. + +When the Oracle tool is running, open the menu and select 'Download sets information'. Use the suggested address and click OK. After at most a few seconds, you should be presented a list of sets to be downloaded. Do not uncheck the ones you already have: they should be downloaded again. Click 'Start download' and wait. When the process is finished, the database should be up to date. + +\section{Change Card Art / Custom Card Art} + Is it possible to change the card art for a specif card, not by changing the set order in my deck editor? + +YES! It is actually very simple. + +All you have to do is get a .JPG of the card you wish to change and name the actual file of the image to the cards (exact) name and add ”.Full” to the end. You can then take this new card image and replace the old one in your downloaded image folder. + +NOTE: Image size does not matter. Try to use a high resolution .JPG of the card you want to use for best quality. + +EXAMPLE: Let's say you wish to turn your M11 Sun Titan from its original art to a promo version. + +\begin{center} + \includegraphics[scale=0.5]{pics/fetch74e3} +\end{center} +If you are using Windows 7, you can find the default location for the Cockatrice card art files under \shellcmd{C:\textbackslash Program Files (x86)\textbackslash Cockatrice\textbackslash pics\textbackslash downloadedPics} Once there, look for the folder of the set for the card we are replacing. For this example, Sun Titan is in the M11 folder. Open the folder and paste/replace your new “Promo” Sun Titan.full[.JPG] into this folder, discarding the original copy. (If you have not yet downloaded the original image of a card by selecting it in the deck editor or playing it in an online game, the card image will not yet be there) + +Once you have the new card image in the proper folder, you can now start Cockatrice (Or re-start if you already had the program open)and select the deck editor to see your new card image. + +If you are changing the art of a card that has multiple versions in other sets, make sure you put the new card in whatever folder (or set) is highest on your card database set list. + +NOTE: Other players on Cockatrice will NOT see your new card image. They will only see whatever version of the card is in their database. + +\section{Use Higher Resolution Cards} +Can you get better/higher resolution card art than the default downloaded card images already used? + +Yes! + +When you click on a card for the first time in the Deck Editor, Cockatrice goes onto the internet and finds an image of that card from a database on another website. + +If you find a higher resolution .JPG of a card that you wish to use on your Cockatrice, you can replace the image with no problem. If you can find a card image in the 3,000 by 1,000 pixel range, and save it as the cards (exact) name and add .Full to the end. + +If you are using Windows 7, you can find the default location for the Cockatrice card art files under \shellcmd{C:\textbackslash Program Files (x86)\textbackslash Cockatrice\textbackslash pics\textbackslash downloadedPics}. Once there, look for the folder of the set for the card you are replacing. (If you are not running Cockatrice as an administrator, look in \shellcmd{C:\textbackslash Users\textbackslash username\textbackslash AppData\textbackslash Local\textbackslash VirtualStore\textbackslash Program Files (x86)\textbackslash Cockatrice\textbackslash pics\textbackslash downloadedPics}.) + +Open the folder of the set with the card, and paste/replace the new higher resolution .JPG image with the old low quality one. + +This process is the same for changing cards to custom images. + +NOTE: Other Cockatrice players will not see your higher resolution card art, they will only see the image they have in their own database. + +\section{Booster drafts} + Cockatrice does not support booster drafts, so players need to use an external service for this. + +Most people use \url{http://ccgdecks.com/} so it's probably not a bad idea to register an account there before joining a game in Cockatrice. + +\section{What is Legacy / Vintage / EDH?} +See \url{http://en.wikipedia.org/wiki/Magic:_The_Gathering_formats}. + +\section{Linking cards and URLs in the Cockatrice chat.} +The Cockatrice chat supports linking of cards and URLs by use of certain tags around a word or phrase. + +\subsection{How to link a card in the Cockatrice chat} +To link a card in the Cockatrice chat, type out the full name of the card, surrounded by the [card] and [/card] tags. + +For example: +\begin{verbatim} +[card]Black Lotus[/card] +\end{verbatim} + +\subsection{How to link a URL in the Cockatrice chat} +To link a URL in the Cockatrice chat, type out the url, surrounded by the [url] and [/url] tags. + +For example: +\begin{verbatim} +[url]http://www.cockatrice.de[/url] +[url]cockatrice.de[/url] +\end{verbatim} + + +\chapter{Servatrice on CentOS 6} +\label{servatrice} +This Howto from woogerworks.com will help you to run your own Cockatrice server (called Servatrice). +An installation script can be downloaded at: \url{http://www.woogerworks.com/cockatrice/installatrice} + +BEFORE CONTINUING, PLEASE UNDERSTAND THESE ARE THE VERY MOST BASIC STEPS. +THIS WILL NOT CONFIGURE SECURITY SETTINGS (YOU ARE RESPONSIBLE FOR YOUR OWN SECURITY)! +THIS WILL NOT CONNECT THE SERVER TO A DATABASE! +USE AT YOUR OWN RISK. + +\begin{enumerate} + \item Open a command shell and install the prerequisites +\begin{enumerate} + \item \shellcmd{cd /etc/yum.repos.d/} + \item \shellcmd{sudo wget http://kdeforge.unl.edu/apt/kde-redhat/epel/kde.repo} + \item \shellcmd{yum -y groupinstall "development tools"} + \item \shellcmd{rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86\_64/epel-release-6-8.noarch.rpm} + \item \shellcmd{yum -y install qt-mysql qt-devel qt-mobility-devel protobuf-devel protobuf-compiler cmake28 libgcrypt-devel} + \item \shellcmd{cd} +\end{enumerate} + + \item Download the source code: +\begin{enumerate} + \item \shellcmd{git clone https://github.com/Daenyth/Cockatrice} + \item \shellcmd{cd Cockatrice} + \item \shellcmd{mkdir build} + \item \shellcmd{cd build} + \item \shellcmd{cmake28 -DWITH\_SERVER=1 ..} + \item \shellcmd{make} + \item \shellcmd{sudo make install} +\end{enumerate} + + \item Create a servatrice.ini file. There is an example file you can convert: +\shellcmd{sed -e "s/number\_pools=1/number\_pools=0/" ../servatrice/servatrice.ini.example > \textasciitilde/servatrice.ini} +Here is one example file: +\begin{framed} +\begin{verbatim} +[server] +port=4747 +statusupdate=15000 +logfile=server.log +name="My own Cockatrice server" +id=1 +number_pools=0 + +[servernetwork] +active=0 +port=14747 +ssl_cert=ssl_cert.pem +ssl_key=ssl_key.pem + +[authentication] +method=none + +[database] +type=none +prefix=cockatrice +hostname=localhost +database=servatrice +user=MYUSERNAMEHERE +password=MYSECUREPASSWORDHERE + +[rooms] +method=config +roomlist\size=1 +roomlist\1\name="FIRST ROOM NAME" +roomlist\1\description="FIRST ROOM DESCRIPTION" +roomlist\1\autojoin=true +roomlist\1\joinmessage="THIS IS A JOIN MESSAGE" +roomlist\1\game_types\size=11 +roomlist\1\game_types\1\name="Vintage (T1)" +roomlist\1\game_types\2\name="Legacy (T1.5)" +roomlist\1\game_types\3\name="Extended (T1.X)" +roomlist\1\game_types\4\name="Modern" +roomlist\1\game_types\5\name="Standard (T2)" +roomlist\1\game_types\6\name="Block Constructed" +roomlist\1\game_types\7\name="EDH/Commander" +roomlist\1\game_types\8\name="Highlander" +roomlist\1\game_types\9\name="2HG" +roomlist\1\game_types\10\name="Draft/Sealed" +roomlist\1\game_types\11\name="Other" + +[game] +max_game_inactivity_time=120 +max_player_inactivity_time=15 + +[security] +max_users_per_address=8 +message_counting_interval=10 +max_message_size_per_interval=1000 +max_message_count_per_interval=10 +max_games_per_user=5 +\end{verbatim} +\end{framed} + + \item Install the database server (optional) +\begin{enumerate} + \item \shellcmd{sudo yum -y install mysql-server mysql php-mysql} + \item \shellcmd{sudo service mysqld start} + \item \shellcmd{sudo chkconfig mysqld on} + \item \shellcmd{mysqladmin -u root password 'password'} + \item \shellcmd{mysql -u root -ppassword -e "create database servatrice;"} + \item \shellcmd{mysql -u root -ppassword -e "create user 'servatrice'@'localhost' identified by 'password';"} + \item \shellcmd{mysql -u root -ppassword -e "grant all privileges on servatrice.* to 'servatrice'@'localhost';"} + \item \shellcmd{mysql -u servatrice -ppassword servatrice < ../servatrice/servatrice.sql} + \item \shellcmd{mysql -u servatrice -ppassword -e "insert into servatrice.cockatrice\_users \textbackslash\\ (admin,name,password\_sha512,active) values (1,'servatrice','MYSHA512PASSWORD',1);"} + \item \shellcmd{sed -i.bak -e "s/password=foobar/password=password/" -e "s/type=none/type=mysql/" \textbackslash\\ -e "s/method=none/method=sql/" ~/servatrice.ini} +\end{enumerate} + + \item Start Servatrice for testing: \shellcmd{/usr/local/bin/servatrice}. I recommend to run it later within a \shellcmd{screen} session or write an init-script. +\end{enumerate} +You should be able to log in as a regular user using any account name. +You can login using the username \textit{servatrice} and the password \textit{password} as the first moderator. + +If everything succeeded, you should tweak the servatrice.ini and set your root password to something strong if not already done (at least 8 characters, upper case, lower case, numbers, special characters). + +%\listoffigures +%\listoftables +%\printindex + +\end{document} diff --git a/doc/usermanual/pics/fetch0300.jpg b/doc/usermanual/pics/fetch0300.jpg new file mode 100644 index 000000000..90bdcf44e Binary files /dev/null and b/doc/usermanual/pics/fetch0300.jpg differ diff --git a/doc/usermanual/pics/fetch100e.jpg b/doc/usermanual/pics/fetch100e.jpg new file mode 100644 index 000000000..00e9c9ad8 Binary files /dev/null and b/doc/usermanual/pics/fetch100e.jpg differ diff --git a/doc/usermanual/pics/fetch23f3.jpg b/doc/usermanual/pics/fetch23f3.jpg new file mode 100644 index 000000000..eccdc58ed Binary files /dev/null and b/doc/usermanual/pics/fetch23f3.jpg differ diff --git a/doc/usermanual/pics/fetch2ab8.jpg b/doc/usermanual/pics/fetch2ab8.jpg new file mode 100644 index 000000000..fdb4e61eb Binary files /dev/null and b/doc/usermanual/pics/fetch2ab8.jpg differ diff --git a/doc/usermanual/pics/fetch2c36.jpg b/doc/usermanual/pics/fetch2c36.jpg new file mode 100644 index 000000000..d64557791 Binary files /dev/null and b/doc/usermanual/pics/fetch2c36.jpg differ diff --git a/doc/usermanual/pics/fetch3705.jpg b/doc/usermanual/pics/fetch3705.jpg new file mode 100644 index 000000000..e0262bd16 Binary files /dev/null and b/doc/usermanual/pics/fetch3705.jpg differ diff --git a/doc/usermanual/pics/fetch5170.jpg b/doc/usermanual/pics/fetch5170.jpg new file mode 100644 index 000000000..23b04704c Binary files /dev/null and b/doc/usermanual/pics/fetch5170.jpg differ diff --git a/doc/usermanual/pics/fetch52e0.jpg b/doc/usermanual/pics/fetch52e0.jpg new file mode 100644 index 000000000..73548870e Binary files /dev/null and b/doc/usermanual/pics/fetch52e0.jpg differ diff --git a/doc/usermanual/pics/fetch54df.jpg b/doc/usermanual/pics/fetch54df.jpg new file mode 100644 index 000000000..a6d990ae2 Binary files /dev/null and b/doc/usermanual/pics/fetch54df.jpg differ diff --git a/doc/usermanual/pics/fetch554a.jpg b/doc/usermanual/pics/fetch554a.jpg new file mode 100644 index 000000000..95e3c39df Binary files /dev/null and b/doc/usermanual/pics/fetch554a.jpg differ diff --git a/doc/usermanual/pics/fetch55a7.jpg b/doc/usermanual/pics/fetch55a7.jpg new file mode 100644 index 000000000..a16ac41a3 Binary files /dev/null and b/doc/usermanual/pics/fetch55a7.jpg differ diff --git a/doc/usermanual/pics/fetch6847.jpg b/doc/usermanual/pics/fetch6847.jpg new file mode 100644 index 000000000..3e489efb2 Binary files /dev/null and b/doc/usermanual/pics/fetch6847.jpg differ diff --git a/doc/usermanual/pics/fetch7486.jpg b/doc/usermanual/pics/fetch7486.jpg new file mode 100644 index 000000000..30cbf1a81 Binary files /dev/null and b/doc/usermanual/pics/fetch7486.jpg differ diff --git a/doc/usermanual/pics/fetch74b2.jpg b/doc/usermanual/pics/fetch74b2.jpg new file mode 100644 index 000000000..019f1e99c Binary files /dev/null and b/doc/usermanual/pics/fetch74b2.jpg differ diff --git a/doc/usermanual/pics/fetch74e3.jpg b/doc/usermanual/pics/fetch74e3.jpg new file mode 100644 index 000000000..ddfaef2ec Binary files /dev/null and b/doc/usermanual/pics/fetch74e3.jpg differ diff --git a/doc/usermanual/pics/fetch7cf0.jpg b/doc/usermanual/pics/fetch7cf0.jpg new file mode 100644 index 000000000..342474b1b Binary files /dev/null and b/doc/usermanual/pics/fetch7cf0.jpg differ diff --git a/doc/usermanual/pics/fetch84a2.jpg b/doc/usermanual/pics/fetch84a2.jpg new file mode 100644 index 000000000..bbf941db3 Binary files /dev/null and b/doc/usermanual/pics/fetch84a2.jpg differ diff --git a/doc/usermanual/pics/fetch867f.jpg b/doc/usermanual/pics/fetch867f.jpg new file mode 100644 index 000000000..2b11b7244 Binary files /dev/null and b/doc/usermanual/pics/fetch867f.jpg differ diff --git a/doc/usermanual/pics/fetch98fd.jpg b/doc/usermanual/pics/fetch98fd.jpg new file mode 100644 index 000000000..36b3f9184 Binary files /dev/null and b/doc/usermanual/pics/fetch98fd.jpg differ diff --git a/doc/usermanual/pics/fetch9b89.jpg b/doc/usermanual/pics/fetch9b89.jpg new file mode 100644 index 000000000..424f449bf Binary files /dev/null and b/doc/usermanual/pics/fetch9b89.jpg differ diff --git a/doc/usermanual/pics/fetch9bff.jpg b/doc/usermanual/pics/fetch9bff.jpg new file mode 100644 index 000000000..8334c3137 Binary files /dev/null and b/doc/usermanual/pics/fetch9bff.jpg differ diff --git a/doc/usermanual/pics/fetcha0de.jpg b/doc/usermanual/pics/fetcha0de.jpg new file mode 100644 index 000000000..6f756f763 Binary files /dev/null and b/doc/usermanual/pics/fetcha0de.jpg differ diff --git a/doc/usermanual/pics/fetchb17a.jpg b/doc/usermanual/pics/fetchb17a.jpg new file mode 100644 index 000000000..9953a2548 Binary files /dev/null and b/doc/usermanual/pics/fetchb17a.jpg differ diff --git a/doc/usermanual/pics/fetchb6fe.jpg b/doc/usermanual/pics/fetchb6fe.jpg new file mode 100644 index 000000000..bcafcb289 Binary files /dev/null and b/doc/usermanual/pics/fetchb6fe.jpg differ diff --git a/doc/usermanual/pics/fetchc18b.jpg b/doc/usermanual/pics/fetchc18b.jpg new file mode 100644 index 000000000..843741383 Binary files /dev/null and b/doc/usermanual/pics/fetchc18b.jpg differ diff --git a/doc/usermanual/pics/fetchd30e.jpg b/doc/usermanual/pics/fetchd30e.jpg new file mode 100644 index 000000000..63dc69616 Binary files /dev/null and b/doc/usermanual/pics/fetchd30e.jpg differ diff --git a/doc/usermanual/pics/fetchd922.jpg b/doc/usermanual/pics/fetchd922.jpg new file mode 100644 index 000000000..368363572 Binary files /dev/null and b/doc/usermanual/pics/fetchd922.jpg differ diff --git a/doc/usermanual/pics/fetche1f4.jpg b/doc/usermanual/pics/fetche1f4.jpg new file mode 100644 index 000000000..456d77466 Binary files /dev/null and b/doc/usermanual/pics/fetche1f4.jpg differ diff --git a/doc/usermanual/pics/fetche3fc.jpg b/doc/usermanual/pics/fetche3fc.jpg new file mode 100644 index 000000000..565d8737a Binary files /dev/null and b/doc/usermanual/pics/fetche3fc.jpg differ diff --git a/doc/usermanual/pics/fetche6b2.jpg b/doc/usermanual/pics/fetche6b2.jpg new file mode 100644 index 000000000..ea64940bc Binary files /dev/null and b/doc/usermanual/pics/fetche6b2.jpg differ diff --git a/doc/usermanual/pics/fetchf010.jpg b/doc/usermanual/pics/fetchf010.jpg new file mode 100644 index 000000000..28edbbaf8 Binary files /dev/null and b/doc/usermanual/pics/fetchf010.jpg differ diff --git a/doc/usermanual/pics/fetchf0d2.jpg b/doc/usermanual/pics/fetchf0d2.jpg new file mode 100644 index 000000000..2046c0e0f Binary files /dev/null and b/doc/usermanual/pics/fetchf0d2.jpg differ diff --git a/doc/usermanual/pics/fetchf924.jpg b/doc/usermanual/pics/fetchf924.jpg new file mode 100644 index 000000000..494c1f422 Binary files /dev/null and b/doc/usermanual/pics/fetchf924.jpg differ diff --git a/doc/usermanual/pics/fetchfc3d.jpg b/doc/usermanual/pics/fetchfc3d.jpg new file mode 100644 index 000000000..879c9c058 Binary files /dev/null and b/doc/usermanual/pics/fetchfc3d.jpg differ diff --git a/doc/usermanual/pics/fetchfe20.jpg b/doc/usermanual/pics/fetchfe20.jpg new file mode 100644 index 000000000..c5b1730ea Binary files /dev/null and b/doc/usermanual/pics/fetchfe20.jpg differ diff --git a/doc/usermanual/pics/fetchfebd.jpg b/doc/usermanual/pics/fetchfebd.jpg new file mode 100644 index 000000000..f44cbbe61 Binary files /dev/null and b/doc/usermanual/pics/fetchfebd.jpg differ diff --git a/doc/usermanual/pics/loaddeck_clip.jpg b/doc/usermanual/pics/loaddeck_clip.jpg new file mode 100644 index 000000000..5c0da095b Binary files /dev/null and b/doc/usermanual/pics/loaddeck_clip.jpg differ diff --git a/doc/usermanual/pics/okdecklist.jpg b/doc/usermanual/pics/okdecklist.jpg new file mode 100644 index 000000000..306fe6259 Binary files /dev/null and b/doc/usermanual/pics/okdecklist.jpg differ diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt new file mode 100644 index 000000000..c14b0cf6c --- /dev/null +++ b/oracle/CMakeLists.txt @@ -0,0 +1,191 @@ +# CMakeLists for oracle directory +# +# provides the oracle binary + +PROJECT(oracle) + +# paths +set(DESKTOPDIR share/applications CACHE STRING "path to .desktop files") + +SET(oracle_SOURCES + src/main.cpp + src/oraclewizard.cpp + src/oracleimporter.cpp + ../cockatrice/src/carddatabase.cpp + ../cockatrice/src/settingscache.cpp + ../cockatrice/src/qt-json/json.cpp + ) + +set(oracle_RESOURCES oracle.qrc) + +if(WIN32) + set(oracle_SOURCES ${oracle_SOURCES} oracle.rc) +endif(WIN32) + + +if(APPLE) + set(MACOSX_BUNDLE_ICON_FILE appicon.icns) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + set(oracle_SOURCES ${oracle_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns) +ENDIF(APPLE) + +set(ORACLE_LIBS) + +# Qt4 stuff +if(Qt4_FOUND) + SET(QT_USE_QTNETWORK TRUE) + SET(QT_USE_QTXML TRUE) + SET(QT_USE_QTSVG TRUE) + + # Include directories + INCLUDE(${QT_USE_FILE}) + include_directories(${QT_INCLUDES}) + LIST(APPEND ORACLE_LIBS ${QT_QTMAIN_LIBRARY}) + LIST(APPEND ORACLE_LIBS ${QT_LIBRARIES}) + + # Let cmake chew Qt4's translations and resource files + # Note: header files are MOC-ed automatically by cmake + QT4_ADD_RESOURCES(oracle_RESOURCES_RCC ${oracle_RESOURCES}) +endif() + +# qt5 stuff +if(Qt5Widgets_FOUND) + include_directories(${Qt5Widgets_INCLUDE_DIRS}) + list(APPEND ORACLE_LIBS Widgets) + + # QtConcurrent + find_package(Qt5Concurrent) + if(Qt5Concurrent_FOUND) + include_directories(${Qt5Concurrent_INCLUDE_DIRS}) + list(APPEND ORACLE_LIBS Concurrent) + endif() + + # QtNetwork + find_package(Qt5Network) + if(Qt5Network_FOUND) + include_directories(${Qt5Network_INCLUDE_DIRS}) + list(APPEND ORACLE_LIBS Network) + endif() + + # QtXml + find_package(Qt5Xml) + if(Qt5Xml_FOUND) + include_directories(${Qt5Xml_INCLUDE_DIRS}) + list(APPEND ORACLE_LIBS Xml) + endif() + + # QtSvg + find_package(Qt5Svg) + if(Qt5Svg_FOUND) + include_directories(${Qt5Svg_INCLUDE_DIRS}) + list(APPEND ORACLE_LIBS Svg) + endif() + + # Let cmake chew Qt5's translations and resource files + # Note: header files are MOC-ed automatically by cmake + QT5_ADD_RESOURCES(oracle_RESOURCES_RCC ${oracle_RESOURCES}) + + # guess plugins and libraries directory + set(QT_PLUGINS_DIR "${Qt5Widgets_DIR}/../../../plugins") + get_target_property(QT_LIBRARY_DIR Qt5::Core LOCATION) + get_filename_component(QT_LIBRARY_DIR ${QT_LIBRARY_DIR} PATH) +endif() + +INCLUDE_DIRECTORIES(../cockatrice/src) + +# Build oracle binary and link it +ADD_EXECUTABLE(oracle WIN32 MACOSX_BUNDLE ${oracle_SOURCES} ${oracle_RESOURCES_RCC} ${oracle_MOC_SRCS}) + +if(Qt4_FOUND) + if(MSVC) + set(QT_USE_QTMAIN true) + endif() + TARGET_LINK_LIBRARIES(oracle ${ORACLE_LIBS}) +endif() +if(Qt5Widgets_FOUND) + if(MSVC) + TARGET_LINK_LIBRARIES(oracle Qt5::WinMain) + endif() + qt5_use_modules(oracle ${ORACLE_LIBS}) +endif() + +if(UNIX) + if(APPLE) + INSTALL(TARGETS oracle BUNDLE DESTINATION ./) + else() + # Assume linux + INSTALL(TARGETS oracle RUNTIME DESTINATION bin/) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/oracle.png DESTINATION ${ICONDIR}/hicolor/48x48/apps) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/oracle.svg DESTINATION ${ICONDIR}/hicolor/scalable/apps) + endif() +elseif(WIN32) + INSTALL(TARGETS oracle RUNTIME DESTINATION ./) +endif() + +IF (NOT WIN32 AND NOT APPLE) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/oracle.desktop DESTINATION ${DESKTOPDIR}) +ENDIF (NOT WIN32 AND NOT APPLE) + +if(APPLE) + # these needs to be relative to CMAKE_INSTALL_PREFIX + set(plugin_dest_dir oracle.app/Contents/Plugins) + set(qtconf_dest_dir oracle.app/Contents/Resources) + + # note: no codecs in qt5 + # note: phonon_backend => mediaservice + # note: needs platform on osx + + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*_debug\\.dylib") + else() + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/[^_]*\\.dylib") + endif() + + install(CODE " + file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] +Plugins = Plugins +Translations = Resources/translations\") + " COMPONENT Runtime) + + install(CODE " + file(GLOB_RECURSE QTPLUGINS + \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dylib\") + set(BU_CHMOD_BUNDLE_ITEMS ON) + include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/oracle.app\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\") + " COMPONENT Runtime) +endif() + +IF(WIN32) + # these needs to be relative to CMAKE_INSTALL_PREFIX + set(plugin_dest_dir Plugins) + set(qtconf_dest_dir .) + + # note: no codecs in qt5 + # note: phonon_backend => mediaservice + # note: needs platform on osx + + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*d\\.dll") + else() + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*[^d]\\.dll") + endif() + + install(CODE " + file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] +Plugins = Plugins +Translations = Resources/translations\") + " COMPONENT Runtime) + + install(CODE " + file(GLOB_RECURSE QTPLUGINS + \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dll\") + set(BU_CHMOD_BUNDLE_ITEMS ON) + include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/oracle.exe\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\") + " COMPONENT Runtime) +endif() \ No newline at end of file diff --git a/oracle/oracle.desktop b/oracle/oracle.desktop new file mode 100644 index 000000000..c4943b859 --- /dev/null +++ b/oracle/oracle.desktop @@ -0,0 +1,8 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Version=1.0 +Type=Application +Name=Cockatrice Oracle downloader +Exec=oracle +Icon=oracle +Categories=Game;CardGame; diff --git a/oracle/oracle.pro b/oracle/oracle.pro deleted file mode 100644 index b2d1b41ff..000000000 --- a/oracle/oracle.pro +++ /dev/null @@ -1,15 +0,0 @@ -TEMPLATE = app -TARGET = -DEPENDPATH += . src -INCLUDEPATH += . src ../cockatrice/src -MOC_DIR = build -OBJECTS_DIR = build -QT += network svg xml - -HEADERS += src/oracleimporter.h src/window_main.h ../cockatrice/src/carddatabase.h ../cockatrice/src/settingscache.h -SOURCES += src/main.cpp src/oracleimporter.cpp src/window_main.cpp ../cockatrice/src/carddatabase.cpp ../cockatrice/src/settingscache.cpp - -macx { - CONFIG += x86 ppc x86_64 release - LIBS += -bind_at_load -} diff --git a/oracle/oracle.qrc b/oracle/oracle.qrc new file mode 100644 index 000000000..b58518c41 --- /dev/null +++ b/oracle/oracle.qrc @@ -0,0 +1,5 @@ + + + resources/oracle.svg + + diff --git a/oracle/oracle.rc b/oracle/oracle.rc new file mode 100644 index 000000000..cf949f313 --- /dev/null +++ b/oracle/oracle.rc @@ -0,0 +1 @@ +ID1_ICON1 ICON DISCARDABLE "resources/appicon.ico" diff --git a/oracle/resources/appicon.icns b/oracle/resources/appicon.icns new file mode 100644 index 000000000..76c188c93 Binary files /dev/null and b/oracle/resources/appicon.icns differ diff --git a/oracle/resources/appicon.ico b/oracle/resources/appicon.ico new file mode 100644 index 000000000..112bf0100 Binary files /dev/null and b/oracle/resources/appicon.ico differ diff --git a/oracle/resources/oracle.png b/oracle/resources/oracle.png new file mode 100644 index 000000000..72e8f6a2e Binary files /dev/null and b/oracle/resources/oracle.png differ diff --git a/oracle/resources/oracle.svg b/oracle/resources/oracle.svg new file mode 100644 index 000000000..29740ffe8 --- /dev/null +++ b/oracle/resources/oracle.svg @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + o + + diff --git a/oracle/sets.xml b/oracle/sets.xml deleted file mode 100644 index 81e272e13..000000000 --- a/oracle/sets.xml +++ /dev/null @@ -1,395 +0,0 @@ - - - http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card - - - http://gatherer.wizards.com/Pages/Search/Default.aspx?output=spoiler&method=text&set=["!longname!"]&special=true - - ARB - Alara Reborn - - - AL - Alliances - - - AQ - Antiquities - - - AP - Apocalypse - - - AN - Arabian Nights - - - AE - Archenemy - - - BOK - Betrayers of Kamigawa - - - CHK - Champions of Kamigawa - - - CH - Chronicles - - - 6E - Classic Sixth Edition - - - CS - Coldsnap - - - CFX - Conflux - - - DS - Darksteel - - - DIS - Dissension - - - DDH - Duel Decks: Ajani vs. Nicol Bolas - - - DDC - Duel Decks: Divine vs. Demonic - - - DDF - Duel Decks: Elspeth vs. Tezzeret - - - EVG - Duel Decks: Elves vs. Goblins - - - DDD - Duel Decks: Garruk vs. Liliana - - - DD2 - Duel Decks: Jace vs. Chandra - - - DDG - Duel Decks: Knights vs. Dragons - - - DDE - Duel Decks: Phyrexia vs. the Coalition - - - 8E - Eighth Edition - - - EVE - Eventide - - - EX - Exodus - - - FE - Fallen Empires - - - FD - Fifth Dawn - - - 5E - Fifth Edition - - - 4E - Fourth Edition - - - DRB - From the Vault: Dragons - - - FVE - From the Vault: Exiled - - - V10 - From the Vault: Relics - - - FUT - Future Sight - - - GP - Guildpact - - - HL - Homelands - - - IA - Ice Age - - - ISD - Innistrad - - - IN - Invasion - - - JU - Judgment - - - LG - Legends - - - LE - Legions - - - A - Limited Edition Alpha - - - B - Limited Edition Beta - - - LRW - Lorwyn - - - M10 - Magic 2010 - - - M11 - Magic 2011 - - - M12 - Magic 2012 - - - COM - Magic: The Gathering-Commander - - - MM - Mercadian Masques - - - MI - Mirage - - - MR - Mirrodin - - - MBS - Mirrodin Besieged - - - MOR - Morningtide - - - NE - Nemesis - - - NPH - New Phyrexia - - - 9E - Ninth Edition - - - OD - Odyssey - - - ON - Onslaught - - - PLC - Planar Chaos - - - PCH - Planechase - - - PS - Planeshift - - - PT - Portal - - - P2 - Portal Second Age - - - P3 - Portal Three Kingdoms - - - PD2 - Premium Deck Series: Fire and Lightning - - - H09 - Premium Deck Series: Slivers - - - PR - Promo set for Gatherer - - - PY - Prophecy - - - RAV - Ravnica: City of Guilds - - - R - Revised Edition - - - ROE - Rise of the Eldrazi - - - SOK - Saviors of Kamigawa - - - SOM - Scars of Mirrodin - - - SC - Scourge - - - 7E - Seventh Edition - - - SHM - Shadowmoor - - - ALA - Shards of Alara - - - ST - Starter 1999 - - - ST2K - Starter 2000 - - - SH - Stronghold - - - TE - Tempest - - - 10E - Tenth Edition - - - DK - The Dark - - - TSP - Time Spiral - - - TSB - Time Spiral "Timeshifted" - - - TO - Torment - - - UG - Unglued - - - UNH - Unhinged - - - U - Unlimited Edition - - - UD - Urza's Destiny - - - UL - Urza's Legacy - - - US - Urza's Saga - - - VG - Vanguard - - - VI - Visions - - - WL - Weatherlight - - - WWK - Worldwake - - - ZEN - Zendikar - - diff --git a/oracle/src/main.cpp b/oracle/src/main.cpp index 3886f924b..dcc5afdcc 100644 --- a/oracle/src/main.cpp +++ b/oracle/src/main.cpp @@ -1,7 +1,7 @@ #include #include -#include "oracleimporter.h" -#include "window_main.h" +#include +#include "oraclewizard.h" #include "settingscache.h" SettingsCache *settingsCache; @@ -9,13 +9,25 @@ SettingsCache *settingsCache; int main(int argc, char *argv[]) { QApplication app(argc, argv); - + +#if QT_VERSION < 0x050000 + // gone in Qt5, all source files _MUST_ be utf8-encoded QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); +#endif + + QCoreApplication::setOrganizationName("Cockatrice"); + QCoreApplication::setOrganizationDomain("cockatrice"); + // this can't be changed, as it influences the default savepath for cards.xml + QCoreApplication::setApplicationName("Cockatrice"); settingsCache = new SettingsCache; - - WindowMain wnd; - wnd.show(); - + + OracleWizard wizard; + + QIcon icon(":/resources/appicon.svg"); + wizard.setWindowIcon(icon); + + wizard.show(); + return app.exec(); } diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 18b1be498..6a099eb4a 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -1,323 +1,253 @@ #include "oracleimporter.h" -#include -#include -#include -#include +#if QT_VERSION < 0x050000 + #include +#else + #include +#endif #include +#include "qt-json/json.h" + OracleImporter::OracleImporter(const QString &_dataDir, QObject *parent) - : CardDatabase(parent), dataDir(_dataDir), setIndex(-1) + : CardDatabase(parent), dataDir(_dataDir) { - buffer = new QBuffer(this); - http = new QHttp(this); - connect(http, SIGNAL(requestFinished(int, bool)), this, SLOT(httpRequestFinished(int, bool))); - connect(http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), this, SLOT(readResponseHeader(const QHttpResponseHeader &))); - connect(http, SIGNAL(dataReadProgress(int, int)), this, SIGNAL(dataReadProgress(int, int))); -} - -bool OracleImporter::readSetsFromFile(const QString &fileName) -{ - QFile setsFile(fileName); - if (!setsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox::critical(0, tr("Error"), tr("Cannot open file '%1'.").arg(fileName)); - return false; - } - - QXmlStreamReader xml(&setsFile); - return readSetsFromXml(xml); } bool OracleImporter::readSetsFromByteArray(const QByteArray &data) { - QXmlStreamReader xml(data); - return readSetsFromXml(xml); -} + QList newSetList; + + bool ok; + setsMap = QtJson::Json::parse(QString(data), ok).toMap(); + if (!ok) { + qDebug() << "error: QtJson::Json::parse()"; + return 0; + } + + QListIterator it(setsMap.values()); + QVariantMap map; -bool OracleImporter::readSetsFromXml(QXmlStreamReader &xml) -{ - QList newSetList; - - QString edition; - QString editionLong; - QString editionURL; - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "set") { - QString shortName, longName; - bool import = xml.attributes().value("import").toString().toInt(); - while (!xml.atEnd()) { - if (xml.readNext() == QXmlStreamReader::EndElement) - break; - if (xml.name() == "name") - edition = xml.readElementText(); - else if (xml.name() == "longname") - editionLong = xml.readElementText(); - else if (xml.name() == "url") - editionURL = xml.readElementText(); - } - newSetList.append(SetToDownload(edition, editionLong, editionURL, import)); - edition = editionLong = editionURL = QString(); - } else if (xml.name() == "picture_url") - pictureUrl = xml.readElementText(); - else if (xml.name() == "picture_url_hq") - pictureUrlHq = xml.readElementText(); - else if (xml.name() == "picture_url_st") - pictureUrlSt = xml.readElementText(); - else if (xml.name() == "set_url") - setUrl = xml.readElementText(); - } - if (newSetList.isEmpty()) - return false; - allSets = newSetList; - return true; + QString edition; + QString editionLong; + QVariant editionCards; + bool import; + + while (it.hasNext()) { + map = it.next().toMap(); + edition = map.value("code").toString(); + editionLong = map.value("name").toString(); + editionCards = map.value("cards"); + + // core and expansion sets are marked to be imported by default + import = (0 == QString::compare(map.value("type").toString(), QString("core"), Qt::CaseInsensitive) || + 0 == QString::compare(map.value("type").toString(), QString("expansion"), Qt::CaseInsensitive)); + + newSetList.append(SetToDownload(edition, editionLong, editionCards, import)); + } + + qSort(newSetList); + + if (newSetList.isEmpty()) + return false; + allSets = newSetList; + return true; } CardInfo *OracleImporter::addCard(const QString &setName, - QString cardName, - int cardId, - const QString &cardCost, - const QString &cardType, - const QString &cardPT, - int cardLoyalty, - const QStringList &cardText) + QString cardName, + bool isToken, + int cardId, + QString &cardCost, + const QString &cardType, + const QString &cardPT, + int cardLoyalty, + const QStringList &cardText) { - QString fullCardText = cardText.join("\n"); - bool splitCard = false; - if (cardName.contains('(')) { - cardName.remove(QRegExp(" \\(.*\\)")); - splitCard = true; - } - // Workaround for card name weirdness - if (cardName.contains("XX")) - cardName.remove("XX"); - cardName = cardName.replace("Æ", "AE"); - - CardInfo *card; - if (cardHash.contains(cardName)) { - card = cardHash.value(cardName); - if (splitCard && !card->getText().contains(fullCardText)) - card->setText(card->getText() + "\n---\n" + fullCardText); - } else { - bool mArtifact = false; - if (cardType.endsWith("Artifact")) - for (int i = 0; i < cardText.size(); ++i) - if (cardText[i].contains("{T}") && cardText[i].contains("to your mana pool")) - mArtifact = true; - - QStringList colors; - QStringList allColors = QStringList() << "W" << "U" << "B" << "R" << "G"; - for (int i = 0; i < allColors.size(); i++) - if (cardCost.contains(allColors[i])) - colors << allColors[i]; - - if (cardText.contains(cardName + " is white.")) - colors << "W"; - if (cardText.contains(cardName + " is blue.")) - colors << "U"; - if (cardText.contains(cardName + " is black.")) - colors << "B"; - if (cardText.contains(cardName + " is red.")) - colors << "R"; - if (cardText.contains(cardName + " is green.")) - colors << "G"; - - bool cipt = (cardText.contains(cardName + " enters the battlefield tapped.")); - - card = new CardInfo(this, cardName, cardCost, cardType, cardPT, fullCardText, colors, cipt); - int tableRow = 1; - QString mainCardType = card->getMainCardType(); - if ((mainCardType == "Land") || mArtifact) - tableRow = 0; - else if ((mainCardType == "Sorcery") || (mainCardType == "Instant")) - tableRow = 3; - else if (mainCardType == "Creature") - tableRow = 2; - card->setTableRow(tableRow); + QString fullCardText = cardText.join("\n"); + bool splitCard = false; + if (cardName.contains('(')) { + cardName.remove(QRegExp(" \\(.*\\)")); + splitCard = true; + } + // Workaround for card name weirdness + if (cardName.contains("XX")) + cardName.remove("XX"); + cardName = cardName.replace("Æ", "AE"); + cardName = cardName.replace("’", "'"); - if (mainCardType == "Planeswalker") - card->setLoyalty(cardLoyalty); + // Remove {} around mana costs + cardCost.remove(QChar('{')); + cardCost.remove(QChar('}')); - cardHash.insert(cardName, card); - } - card->setPicURL(setName, getPictureUrl(pictureUrl, cardId, cardName, setName)); - card->setPicURLHq(setName, getPictureUrl(pictureUrlHq, cardId, cardName, setName)); - card->setPicURLSt(setName, getPictureUrl(pictureUrlSt, cardId, cardName, setName)); - return card; + CardInfo *card; + if (cards.contains(cardName)) { + card = cards.value(cardName); + if (splitCard && !card->getText().contains(fullCardText)) + card->setText(card->getText() + "\n---\n" + fullCardText); + } else { + bool mArtifact = false; + if (cardType.endsWith("Artifact")) + for (int i = 0; i < cardText.size(); ++i) + if (cardText[i].contains("{T}") && cardText[i].contains("to your mana pool")) + mArtifact = true; + + QStringList colors; + QStringList allColors = QStringList() << "W" << "U" << "B" << "R" << "G"; + for (int i = 0; i < allColors.size(); i++) + if (cardCost.contains(allColors[i])) + colors << allColors[i]; + + if (cardText.contains(cardName + " is white.")) + colors << "W"; + if (cardText.contains(cardName + " is blue.")) + colors << "U"; + if (cardText.contains(cardName + " is black.")) + colors << "B"; + if (cardText.contains(cardName + " is red.")) + colors << "R"; + if (cardText.contains(cardName + " is green.")) + colors << "G"; + + bool cipt = (cardText.contains(cardName + " enters the battlefield tapped.")); + + card = new CardInfo(this, cardName, isToken, cardCost, cardType, cardPT, fullCardText, colors, cardLoyalty, cipt); + int tableRow = 1; + QString mainCardType = card->getMainCardType(); + if ((mainCardType == "Land") || mArtifact) + tableRow = 0; + else if ((mainCardType == "Sorcery") || (mainCardType == "Instant")) + tableRow = 3; + else if (mainCardType == "Creature") + tableRow = 2; + card->setTableRow(tableRow); + + cards.insert(cardName, card); + } + card->setMuId(setName, cardId); + + return card; } -int OracleImporter::importTextSpoiler(CardSet *set, const QByteArray &data) +int OracleImporter::importTextSpoiler(CardSet *set, const QVariant &data) { - int cards = 0; - QString bufferContents(data); - - // Workaround for ampersand bug in text spoilers - int index = -1; - while ((index = bufferContents.indexOf('&', index + 1)) != -1) { - int semicolonIndex = bufferContents.indexOf(';', index); - if (semicolonIndex > 5) { - bufferContents.insert(index + 1, "amp;"); - index += 4; - } - } - - QDomDocument doc; - QString errorMsg; - int errorLine, errorColumn; - if (!doc.setContent(bufferContents, &errorMsg, &errorLine, &errorColumn)) - qDebug() << "error:" << errorMsg << "line:" << errorLine << "column:" << errorColumn; + int cards = 0; + + QListIterator it(data.toList()); + QVariantMap map; + QString cardName; + QString cardCost; + QString cardType; + QString cardPT; + QString cardText; + int cardId; + int cardLoyalty; + bool cardIsToken = false; + QMap splitCards; - QDomNodeList divs = doc.elementsByTagName("div"); - for (int i = 0; i < divs.size(); ++i) { - QDomElement div = divs.at(i).toElement(); - QDomNode divClass = div.attributes().namedItem("class"); - if (divClass.nodeValue() == "textspoiler") { - QString cardName, cardCost, cardType, cardPT, cardText; - int cardId = 0; - int cardLoyalty = 0; - - QDomNodeList trs = div.elementsByTagName("tr"); - for (int j = 0; j < trs.size(); ++j) { - QDomElement tr = trs.at(j).toElement(); - QDomNodeList tds = tr.elementsByTagName("td"); - if (tds.size() != 2) { - QStringList cardTextSplit = cardText.split("\n"); - for (int i = 0; i < cardTextSplit.size(); ++i) - cardTextSplit[i] = cardTextSplit[i].trimmed(); - - CardInfo *card = addCard(set->getShortName(), cardName, cardId, cardCost, cardType, cardPT, cardLoyalty, cardTextSplit); - if (!set->contains(card)) { - card->addToSet(set); - cards++; - } - cardName = cardCost = cardType = cardPT = cardText = QString(); - } else { - QString v1 = tds.at(0).toElement().text().simplified(); - QString v2 = tds.at(1).toElement().text().replace(trUtf8("—"), "-"); - - if (v1 == "Name:") { - QDomElement a = tds.at(1).toElement().elementsByTagName("a").at(0).toElement(); - QString href = a.attributes().namedItem("href").nodeValue(); - cardId = href.mid(href.indexOf("multiverseid=") + 13).toInt(); - cardName = v2.simplified(); - } else if (v1 == "Cost:") - cardCost = v2.simplified(); - else if (v1 == "Type:") - cardType = v2.simplified(); - else if (v1 == "Pow/Tgh:") - cardPT = v2.simplified().remove('(').remove(')'); - else if (v1 == "Rules Text:") - cardText = v2.trimmed(); - else if (v1 == "Loyalty:") - cardLoyalty = v2.trimmed().remove('(').remove(')').toInt(); - } - } - break; - } - } - return cards; + while (it.hasNext()) { + map = it.next().toMap(); + if(0 == QString::compare(map.value("layout").toString(), QString("split"), Qt::CaseInsensitive)) + { + // Split card handling + cardId = map.contains("multiverseid") ? map.value("multiverseid").toInt() : 0; + if(splitCards.contains(cardId)) + { + // merge two split cards + QVariantMap tmpMap = splitCards.take(cardId); + QVariantMap * card1 = 0, * card2 = 0; + // same cardid + cardId = map.contains("multiverseid") ? map.value("multiverseid").toInt() : 0; + // this is currently an integer; can't accept 2 values + cardLoyalty = 0; + + // determine which subcard is the first one in the split + QStringList names=map.contains("names") ? map.value("names").toStringList() : QStringList(""); + if(names.count()>0 && + map.contains("name") && + 0 == QString::compare(map.value("name").toString(), names.at(0))) + { + // map is the left part of the split card, tmpMap is right part + card1 = ↦ + card2 = &tmpMap; + } else { + //tmpMap is the left part of the split card, map is right part + card1 = &tmpMap; + card2 = ↦ + } + + // add first card's data + cardName = card1->contains("name") ? card1->value("name").toString() : QString(""); + cardCost = card1->contains("manaCost") ? card1->value("manaCost").toString() : QString(""); + cardType = card1->contains("type") ? card1->value("type").toString() : QString(""); + cardPT = card1->contains("power") || card1->contains("toughness") ? card1->value("power").toString() + QString('/') + card1->value("toughness").toString() : QString(""); + cardText = card1->contains("text") ? card1->value("text").toString() : QString(""); + + // add second card's data + cardName += card2->contains("name") ? QString(" // ") + card2->value("name").toString() : QString(""); + cardCost += card2->contains("manaCost") ? QString(" // ") + card2->value("manaCost").toString() : QString(""); + cardType += card2->contains("type") ? QString(" // ") + card2->value("type").toString() : QString(""); + cardPT += card2->contains("power") || card2->contains("toughness") ? QString(" // ") + card2->value("power").toString() + QString('/') + card2->value("toughness").toString() : QString(""); + cardText += card2->contains("text") ? QString("\n\n---\n\n") + card2->value("text").toString() : QString(""); + } else { + // first card od a pair; enqueue for later merging + splitCards.insert(cardId, map); + continue; + } + } else { + // normal cards handling + cardName = map.contains("name") ? map.value("name").toString() : QString(""); + cardCost = map.contains("manaCost") ? map.value("manaCost").toString() : QString(""); + cardType = map.contains("type") ? map.value("type").toString() : QString(""); + cardPT = map.contains("power") || map.contains("toughness") ? map.value("power").toString() + QString('/') + map.value("toughness").toString() : QString(""); + cardText = map.contains("text") ? map.value("text").toString() : QString(""); + cardId = map.contains("multiverseid") ? map.value("multiverseid").toInt() : 0; + cardLoyalty = map.contains("loyalty") ? map.value("loyalty").toInt() : 0; + cardIsToken = map.value("layout") == "token"; + + // Distinguish Vanguard cards from regular cards of the same name. + if (map.value("layout") == "vanguard") { + cardName += " Avatar"; + } + } + + CardInfo *card = addCard(set->getShortName(), cardName, cardIsToken, cardId, cardCost, cardType, cardPT, cardLoyalty, cardText.split("\n")); + + if (!set->contains(card)) { + card->addToSet(set); + cards++; + } + } + + return cards; } -QString OracleImporter::getPictureUrl(QString url, int cardId, QString name, const QString &setName) const +int OracleImporter::startImport() { - if ((name == "Island") || (name == "Swamp") || (name == "Mountain") || (name == "Plains") || (name == "Forest")) - name.append("1"); - return url.replace("!cardid!", QString::number(cardId)).replace("!set!", setName).replace("!name!", name - .replace("ö", "o") -// .remove('\'') - .remove(" // ") -// .remove(',') -// .remove(':') -// .remove('.') - .remove(QRegExp("\\(.*\\)")) - .simplified() -// .replace(' ', '_') -// .replace('-', '_') - ); -} + clear(); -int OracleImporter::startDownload() -{ - setsToDownload.clear(); - for (int i = 0; i < allSets.size(); ++i) - if (allSets[i].getImport()) - setsToDownload.append(allSets[i]); - - if (setsToDownload.isEmpty()) - return 0; - setIndex = 0; - emit setIndexChanged(0, 0, setsToDownload[0].getLongName()); - - downloadNextFile(); - return setsToDownload.size(); -} + int setCards = 0, setIndex= 0; + QListIterator it(allSets); + const SetToDownload * curSet; -void OracleImporter::downloadNextFile() -{ - QString urlString = setsToDownload[setIndex].getUrl(); - if (urlString.isEmpty()) - urlString = setUrl; - urlString = urlString.replace("!longname!", setsToDownload[setIndex].getLongName()); - if (urlString.startsWith("http://")) { - QUrl url(urlString); - http->setHost(url.host(), QHttp::ConnectionModeHttp, url.port() == -1 ? 0 : url.port()); - QString path = QUrl::toPercentEncoding(urlString.mid(url.host().size() + 7).replace(' ', '+'), "?!$&'()*+,;=:@/"); - - buffer->close(); - buffer->setData(QByteArray()); - buffer->open(QIODevice::ReadWrite | QIODevice::Text); - reqId = http->get(path, buffer); - } else { - QFile file(dataDir + "/" + urlString); - file.open(QIODevice::ReadOnly | QIODevice::Text); - - buffer->close(); - buffer->setData(file.readAll()); - buffer->open(QIODevice::ReadWrite | QIODevice::Text); - reqId = 0; - httpRequestFinished(reqId, false); - } -} + while (it.hasNext()) + { + curSet = & it.next(); + if(!curSet->getImport()) + continue; + + CardSet *set = new CardSet(curSet->getShortName(), curSet->getLongName()); + if (!sets.contains(set->getShortName())) + sets.insert(set->getShortName(), set); -void OracleImporter::httpRequestFinished(int requestId, bool error) -{ - if (error) { - QMessageBox::information(0, tr("HTTP"), tr("Error.")); - return; - } - if (requestId != reqId) - return; + int setCards = importTextSpoiler(set, curSet->getCards()); - CardSet *set = new CardSet(setsToDownload[setIndex].getShortName(), setsToDownload[setIndex].getLongName()); - if (!setHash.contains(set->getShortName())) - setHash.insert(set->getShortName(), set); - - buffer->seek(0); - buffer->close(); - int cards = importTextSpoiler(set, buffer->data()); - ++setIndex; - - if (setIndex == setsToDownload.size()) { - emit setIndexChanged(cards, setIndex, QString()); - setIndex = -1; - } else { - downloadNextFile(); - emit setIndexChanged(cards, setIndex, setsToDownload[setIndex].getLongName()); - } -} + ++setIndex; + + emit setIndexChanged(setCards, setIndex, curSet->getLongName()); + } + + emit setIndexChanged(setCards, setIndex, QString()); -void OracleImporter::readResponseHeader(const QHttpResponseHeader &responseHeader) -{ - switch (responseHeader.statusCode()) { - case 200: - case 301: - case 302: - case 303: - case 307: - break; - default: - QMessageBox::information(0, tr("HTTP"), tr("Download failed: %1.").arg(responseHeader.reasonPhrase())); - http->abort(); - deleteLater(); - } + // total number of sets + return setIndex; } diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h index c29a78832..4c36a211c 100644 --- a/oracle/src/oracleimporter.h +++ b/oracle/src/oracleimporter.h @@ -1,53 +1,42 @@ #ifndef ORACLEIMPORTER_H #define ORACLEIMPORTER_H -#include -#include +#include -class QBuffer; -class QXmlStreamReader; +#include class SetToDownload { private: - QString shortName, longName, url; + QString shortName, longName; bool import; + QVariant cards; public: const QString &getShortName() const { return shortName; } const QString &getLongName() const { return longName; } - const QString &getUrl() const { return url; } + const QVariant &getCards() const { return cards; } bool getImport() const { return import; } void setImport(bool _import) { import = _import; } - SetToDownload(const QString &_shortName, const QString &_longName, const QString &_url, bool _import) - : shortName(_shortName), longName(_longName), url(_url), import(_import) { } + SetToDownload(const QString &_shortName, const QString &_longName, const QVariant &_cards, bool _import) + : shortName(_shortName), longName(_longName), import(_import), cards(_cards) { } + bool operator<(const SetToDownload &set) const { return longName.compare(set.longName, Qt::CaseInsensitive) < 0; } }; class OracleImporter : public CardDatabase { Q_OBJECT private: - QList allSets, setsToDownload; - QString pictureUrl, pictureUrlHq, pictureUrlSt, setUrl; + QList allSets; + QVariantMap setsMap; QString dataDir; - int setIndex; - int reqId; - QBuffer *buffer; - QHttp *http; - QString getPictureUrl(QString url, int cardId, QString name, const QString &setName) const; - void downloadNextFile(); - bool readSetsFromXml(QXmlStreamReader &xml); - CardInfo *addCard(const QString &setName, QString cardName, int cardId, const QString &cardCost, const QString &cardType, const QString &cardPT, int cardLoyalty, const QStringList &cardText); -private slots: - void httpRequestFinished(int requestId, bool error); - void readResponseHeader(const QHttpResponseHeader &responseHeader); + CardInfo *addCard(const QString &setName, QString cardName, bool isToken, int cardId, QString &cardCost, const QString &cardType, const QString &cardPT, int cardLoyalty, const QStringList &cardText); signals: - void setIndexChanged(int cardsImported, int setIndex, const QString &nextSetName); + void setIndexChanged(int cardsImported, int setIndex, const QString &setName); void dataReadProgress(int bytesRead, int totalBytes); public: OracleImporter(const QString &_dataDir, QObject *parent = 0); bool readSetsFromByteArray(const QByteArray &data); - bool readSetsFromFile(const QString &fileName); - int startDownload(); - int importTextSpoiler(CardSet *set, const QByteArray &data); + int startImport(); + int importTextSpoiler(CardSet *set, const QVariant &data); QList &getSets() { return allSets; } const QString &getDataDir() const { return dataDir; } }; diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp new file mode 100644 index 000000000..c1c8b9829 --- /dev/null +++ b/oracle/src/oraclewizard.cpp @@ -0,0 +1,442 @@ +#include +#if QT_VERSION < 0x050000 + #include +#else + #include + #include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "oraclewizard.h" +#include "oracleimporter.h" + +#define ALLSETS_URL "http://mtgjson.com/json/AllSets.json" + +OracleWizard::OracleWizard(QWidget *parent) + : QWizard(parent) +{ + settings = new QSettings(this); + + importer = new OracleImporter( +#if QT_VERSION < 0x050000 + QDesktopServices::storageLocation(QDesktopServices::DataLocation) +#else + QStandardPaths::standardLocations(QStandardPaths::DataLocation).first() +#endif + , this); + + addPage(new IntroPage); + addPage(new LoadSetsPage); + addPage(new ChooseSetsPage); + addPage(new SaveSetsPage); + + setWindowTitle(tr("Oracle Importer")); + QWizard::setButtonText(QWizard::FinishButton, tr("Save")); +} + +void OracleWizard::accept() +{ + QDialog::accept(); +} + +void OracleWizard::enableButtons() +{ + button(QWizard::NextButton)->setDisabled(false); + button(QWizard::BackButton)->setDisabled(false); +} + +void OracleWizard::disableButtons() +{ + button(QWizard::NextButton)->setDisabled(true); + button(QWizard::BackButton)->setDisabled(true); +} + +IntroPage::IntroPage(QWidget *parent) + : OracleWizardPage(parent) +{ + setTitle(tr("Introduction")); + + label = new QLabel(tr("This wizard will import the list of sets and cards " + "that will be used by Cockatrice. You will need to " + "specify an url or a filename that will be used as a " + "source, and then choose the wanted sets from the list " + "of the available ones."), + this); + label->setWordWrap(true); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(label); + setLayout(layout); +} + +LoadSetsPage::LoadSetsPage(QWidget *parent) + : OracleWizardPage(parent), nam(0) +{ + setTitle(tr("Source selection")); + setSubTitle(tr("Please specify a source for the list of sets and cards. " + "You can specify an url address that will be download or " + "use an existing file from your computer.")); + + urlRadioButton = new QRadioButton(tr("Download url:"), this); + fileRadioButton = new QRadioButton(tr("Local file:"), this); + + urlLineEdit = new QLineEdit(this); + fileLineEdit = new QLineEdit(this); + + progressLabel = new QLabel(this); + progressBar = new QProgressBar(this); + + urlRadioButton->setChecked(true); + + fileButton = new QPushButton(tr("Choose file..."), this); + connect(fileButton, SIGNAL(clicked()), this, SLOT(actLoadSetsFile())); + + QGridLayout *layout = new QGridLayout(this); + layout->addWidget(urlRadioButton, 0, 0); + layout->addWidget(urlLineEdit, 0, 1); + layout->addWidget(fileRadioButton, 1, 0); + layout->addWidget(fileLineEdit, 1, 1); + layout->addWidget(fileButton, 2, 1, Qt::AlignRight); + layout->addWidget(progressLabel, 3, 0); + layout->addWidget(progressBar, 3, 1); + + connect(&watcher, SIGNAL(finished()), this, SLOT(importFinished())); + + setLayout(layout); +} + +void LoadSetsPage::initializePage() +{ + urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString()); + + progressLabel->hide(); + progressBar->hide(); +} + +void LoadSetsPage::actLoadSetsFile() +{ + QFileDialog dialog(this, tr("Load sets file")); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setNameFilter("Sets JSON file (*.json)"); + + if(!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) + dialog.selectFile(fileLineEdit->text()); + + if (!dialog.exec()) + return; + + fileLineEdit->setText(dialog.selectedFiles().at(0)); +} + +bool LoadSetsPage::validatePage() +{ + // once the import is finished, we call next(); skip validation + if(wizard()->importer->getSets().count() > 0) + return true; + + // else, try to import sets + if(urlRadioButton->isChecked()) + { + QUrl url = QUrl::fromUserInput(urlLineEdit->text()); + if(!url.isValid()) + { + QMessageBox::critical(this, tr("Error"), tr("The provided url is not valid.")); + return false; + } + + progressLabel->setText(tr("Downloading (0MB)")); + // show an infinite progressbar + progressBar->setMaximum(0); + progressBar->setMinimum(0); + progressBar->setValue(0); + progressLabel->show(); + progressBar->show(); + + wizard()->disableButtons(); + setEnabled(false); + + if(!nam) + nam = new QNetworkAccessManager(this); + QNetworkReply *reply = nam->get(QNetworkRequest(url)); + + connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSetsFile())); + connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(actDownloadProgressSetsFile(qint64, qint64))); + + } else if(fileRadioButton->isChecked()) { + QFile setsFile(fileLineEdit->text()); + if(!setsFile.exists()) + { + QMessageBox::critical(this, tr("Error"), tr("Please choose a file.")); + return false; + } + + if (!setsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(0, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text())); + return false; + } + + wizard()->disableButtons(); + setEnabled(false); + + readSetsFromByteArray(setsFile.readAll()); + + } + return false; +} + +void LoadSetsPage::actDownloadProgressSetsFile(qint64 received, qint64 total) +{ + if(total > 0 && progressBar->maximum()==0) + { + progressBar->setMaximum(total); + progressBar->setValue(received); + } + progressLabel->setText(tr("Downloading (%1MB)").arg((int) received / 1048576)); +} + +void LoadSetsPage::actDownloadFinishedSetsFile() +{ + progressLabel->hide(); + progressBar->hide(); + + // check for a reply + QNetworkReply *reply = static_cast(sender()); + QNetworkReply::NetworkError errorCode = reply->error(); + if (errorCode != QNetworkReply::NoError) { + QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString())); + + wizard()->enableButtons(); + setEnabled(true); + + reply->deleteLater(); + return; + } + + // save allsets.json url, but only if the user customized it and download was successfull + if(urlLineEdit->text() != QString(ALLSETS_URL)) + wizard()->settings->setValue("allsetsurl", urlLineEdit->text()); + else + wizard()->settings->remove("allsetsurl"); + + readSetsFromByteArray(reply->readAll()); + reply->deleteLater(); +} + +void LoadSetsPage::readSetsFromByteArray(QByteArray data) +{ + // show an infinite progressbar + progressBar->setMaximum(0); + progressBar->setMinimum(0); + progressBar->setValue(0); + progressLabel->setText(tr("Parsing file")); + progressLabel->show(); + progressBar->show(); + + // Start the computation. + future = QtConcurrent::run(wizard()->importer, &OracleImporter::readSetsFromByteArray, data); + watcher.setFuture(future); +} + +void LoadSetsPage::importFinished() +{ + wizard()->enableButtons(); + setEnabled(true); + progressLabel->hide(); + progressBar->hide(); + + if(watcher.future().result()) + { + wizard()->next(); + } else { + QMessageBox::critical(this, tr("Error"), tr("The file was retrieved successfully, but it does not contain any sets data.")); + } +} + +ChooseSetsPage::ChooseSetsPage(QWidget *parent) + : OracleWizardPage(parent) +{ + setTitle(tr("Sets selection")); + setSubTitle(tr("The following sets has been found in the source file. " + "Please mark the sets that will be imported.")); + + checkBoxLayout = new QVBoxLayout; + + QWidget *checkboxFrame = new QWidget(this); + checkboxFrame->setLayout(checkBoxLayout); + + QScrollArea *checkboxArea = new QScrollArea(this); + checkboxArea->setWidget(checkboxFrame); + checkboxArea->setWidgetResizable(true); + + checkAllButton = new QPushButton(tr("&Check all")); + connect(checkAllButton, SIGNAL(clicked()), this, SLOT(actCheckAll())); + uncheckAllButton = new QPushButton(tr("&Uncheck all")); + connect(uncheckAllButton, SIGNAL(clicked()), this, SLOT(actUncheckAll())); + + QGridLayout *layout = new QGridLayout(this); + layout->addWidget(checkboxArea, 0, 0, 1, 2); + layout->addWidget(checkAllButton, 1, 0); + layout->addWidget(uncheckAllButton, 1, 1); + + setLayout(layout); +} + +void ChooseSetsPage::initializePage() +{ + // populate checkbox list + for (int i = 0; i < checkBoxList.size(); ++i) + delete checkBoxList[i]; + checkBoxList.clear(); + + QList &sets = wizard()->importer->getSets(); + for (int i = 0; i < sets.size(); ++i) { + QCheckBox *checkBox = new QCheckBox(sets[i].getLongName()); + checkBox->setChecked(sets[i].getImport()); + connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxChanged(int))); + checkBoxLayout->addWidget(checkBox); + checkBoxList << checkBox; + } +} + +void ChooseSetsPage::checkBoxChanged(int state) +{ + QCheckBox *checkBox = qobject_cast(sender()); + QList &sets = wizard()->importer->getSets(); + for (int i = 0; i < sets.size(); ++i) + if (sets[i].getLongName() == checkBox->text()) { + sets[i].setImport(state); + break; + } +} + +void ChooseSetsPage::actCheckAll() +{ + for (int i = 0; i < checkBoxList.size(); ++i) + checkBoxList[i]->setChecked(true); +} + +void ChooseSetsPage::actUncheckAll() +{ + for (int i = 0; i < checkBoxList.size(); ++i) + checkBoxList[i]->setChecked(false); +} + +bool ChooseSetsPage::validatePage() +{ + for (int i = 0; i < checkBoxList.size(); ++i) + { + if(checkBoxList[i]->isChecked()) + return true; + } + + QMessageBox::critical(this, tr("Error"), tr("Please mark at least one set.")); + return false; +} + +SaveSetsPage::SaveSetsPage(QWidget *parent) + : OracleWizardPage(parent) +{ + setTitle(tr("Sets imported")); + setSubTitle(tr("The following sets has been imported. " + "Press \"Save\" to save the imported cards to the Cockatrice database.")); + + defaultPathCheckBox = new QCheckBox(this); + defaultPathCheckBox->setText(tr("Save to the default path (recommended)")); + defaultPathCheckBox->setChecked(true); + + messageLog = new QTextEdit(this); + messageLog->setReadOnly(true); + + QGridLayout *layout = new QGridLayout(this); + layout->addWidget(defaultPathCheckBox, 0, 0); + layout->addWidget(messageLog, 1, 0); + + setLayout(layout); +} + +void SaveSetsPage::cleanupPage() +{ + disconnect(wizard()->importer, SIGNAL(setIndexChanged(int, int, const QString &)), 0, 0); +} + +void SaveSetsPage::initializePage() +{ + messageLog->clear(); + + connect(wizard()->importer, SIGNAL(setIndexChanged(int, int, const QString &)), this, SLOT(updateTotalProgress(int, int, const QString &))); + + if (!wizard()->importer->startImport()) + QMessageBox::critical(this, tr("Error"), tr("No set has been imported.")); +} + +void SaveSetsPage::updateTotalProgress(int cardsImported, int /* setIndex */, const QString &setName) +{ + if (setName.isEmpty()) { + messageLog->append("" + tr("Import finished: %1 cards.").arg(wizard()->importer->getCardList().size()) + ""); + } else { + messageLog->append(tr("%1: %2 cards imported").arg(setName).arg(cardsImported)); + } + messageLog->verticalScrollBar()->setValue(messageLog->verticalScrollBar()->maximum()); +} + +bool SaveSetsPage::validatePage() +{ + bool ok = false; + const QString dataDir = +#if QT_VERSION < 0x050000 + QDesktopServices::storageLocation(QDesktopServices::DataLocation); +#else + QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); +#endif + QSettings* settings = new QSettings(this); + QString savePath = settings->value("paths/carddatabase").toString(); + if (savePath.isEmpty()) { + QDir().mkpath(dataDir); + } + QString windowName = tr("Save card database"); + QString fileType = tr("XML; card database (*.xml)"); + do { + QString fileName; + if (savePath.isEmpty()) { + if (defaultPathCheckBox->isChecked()) + fileName = dataDir + "/cards.xml"; + else + fileName = QFileDialog::getSaveFileName(this, windowName, dataDir + "/cards.xml", fileType); + settings->setValue("paths/carddatabase", fileName); + } + else { + if (defaultPathCheckBox->isChecked()) + fileName = savePath; + else + fileName = QFileDialog::getSaveFileName(this, windowName, savePath, fileType); + } + if (fileName.isEmpty()) { + return false; + } + if (wizard()->importer->saveToFile(fileName)) + { + ok = true; + QMessageBox::information(this, tr("Success"), tr("The card database has been saved successfully.")); + } else { + QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to the desired location.")); + if (defaultPathCheckBox->isChecked()) + defaultPathCheckBox->setChecked(false); + } + } while (!ok); + + return true; +} diff --git a/oracle/src/oraclewizard.h b/oracle/src/oraclewizard.h new file mode 100644 index 000000000..6e6a6ea80 --- /dev/null +++ b/oracle/src/oraclewizard.h @@ -0,0 +1,114 @@ +#ifndef ORACLEWIZARD_H +#define ORACLEWIZARD_H + +#include +#include +#include + +class QCheckBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QRadioButton; +class QProgressBar; +class QNetworkAccessManager; +class QTextEdit; +class QVBoxLayout; +class OracleImporter; +class QSettings; + +class OracleWizard : public QWizard +{ + Q_OBJECT +public: + OracleWizard(QWidget *parent = 0); + void accept(); + void enableButtons(); + void disableButtons(); +public: + OracleImporter *importer; + QSettings * settings; +}; + + +class OracleWizardPage : public QWizardPage +{ + Q_OBJECT +public: + OracleWizardPage(QWidget *parent = 0): QWizardPage(parent) {}; +protected: + inline OracleWizard *wizard() { return (OracleWizard*) QWizardPage::wizard(); }; +}; + +class IntroPage : public OracleWizardPage +{ + Q_OBJECT +public: + IntroPage(QWidget *parent = 0); +private: + QLabel *label; +}; + +class LoadSetsPage : public OracleWizardPage +{ + Q_OBJECT +public: + LoadSetsPage(QWidget *parent = 0); +protected: + void initializePage(); + bool validatePage(); + void readSetsFromByteArray(QByteArray data); +private: + QRadioButton *urlRadioButton; + QRadioButton *fileRadioButton; + QLineEdit *urlLineEdit; + QLineEdit *fileLineEdit; + QPushButton *fileButton; + QLabel *progressLabel; + QProgressBar * progressBar; + + QNetworkAccessManager *nam; + QFutureWatcher watcher; + QFuture future; +private slots: + void actLoadSetsFile(); + void actDownloadProgressSetsFile(qint64 received, qint64 total); + void actDownloadFinishedSetsFile(); + void importFinished(); +}; + +class ChooseSetsPage : public OracleWizardPage +{ + Q_OBJECT +public: + ChooseSetsPage(QWidget *parent = 0); +protected: + void initializePage(); + bool validatePage(); +private: + QPushButton *checkAllButton, *uncheckAllButton; + QVBoxLayout *checkBoxLayout; + QList checkBoxList; +private slots: + void actCheckAll(); + void actUncheckAll(); + void checkBoxChanged(int state); +}; + +class SaveSetsPage : public OracleWizardPage +{ + Q_OBJECT +public: + SaveSetsPage(QWidget *parent = 0); +private: + QTextEdit *messageLog; + QCheckBox * defaultPathCheckBox; +protected: + void initializePage(); + void cleanupPage(); + bool validatePage(); +private slots: + void updateTotalProgress(int cardsImported, int setIndex, const QString &setName); +}; + +#endif \ No newline at end of file diff --git a/oracle/src/window_main.cpp b/oracle/src/window_main.cpp deleted file mode 100644 index 9cf164e8d..000000000 --- a/oracle/src/window_main.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include -#include -#include -#include "window_main.h" -#include "oracleimporter.h" - -const QString WindowMain::defaultSetsUrl = QString("http://www.cockatrice.de/files/sets.xml"); - -WindowMain::WindowMain(QWidget *parent) - : QMainWindow(parent) -{ - QDir dataDir(qApp->applicationDirPath()); -#ifdef Q_OS_MAC - dataDir.cdUp(); - dataDir.cdUp(); - dataDir.cdUp(); -#endif - importer = new OracleImporter(dataDir.absolutePath(), this); - nam = new QNetworkAccessManager(this); - - checkBoxLayout = new QVBoxLayout; - - QWidget *checkboxFrame = new QWidget; - checkboxFrame->setLayout(checkBoxLayout); - - QScrollArea *checkboxArea = new QScrollArea; - checkboxArea->setWidget(checkboxFrame); - checkboxArea->setWidgetResizable(true); - - checkAllButton = new QPushButton(tr("&Check all")); - connect(checkAllButton, SIGNAL(clicked()), this, SLOT(actCheckAll())); - uncheckAllButton = new QPushButton(tr("&Uncheck all")); - connect(uncheckAllButton, SIGNAL(clicked()), this, SLOT(actUncheckAll())); - - QHBoxLayout *checkAllButtonLayout = new QHBoxLayout; - checkAllButtonLayout->addWidget(checkAllButton); - checkAllButtonLayout->addWidget(uncheckAllButton); - - startButton = new QPushButton(tr("&Start download")); - connect(startButton, SIGNAL(clicked()), this, SLOT(actStart())); - - QVBoxLayout *settingsLayout = new QVBoxLayout; - settingsLayout->addWidget(checkboxArea); - settingsLayout->addLayout(checkAllButtonLayout); - settingsLayout->addWidget(startButton); - - totalLabel = new QLabel(tr("Total progress:")); - totalProgressBar = new QProgressBar; - nextSetLabel1 = new QLabel(tr("Current file:")); - nextSetLabel2 = new QLabel; - fileLabel = new QLabel(tr("Progress:")); - fileProgressBar = new QProgressBar; - - messageLog = new QTextEdit; - messageLog->setReadOnly(true); - - QGridLayout *grid = new QGridLayout; - grid->addWidget(totalLabel, 0, 0); - grid->addWidget(totalProgressBar, 0, 1); - grid->addWidget(nextSetLabel1, 1, 0); - grid->addWidget(nextSetLabel2, 1, 1); - grid->addWidget(fileLabel, 2, 0); - grid->addWidget(fileProgressBar, 2, 1); - grid->addWidget(messageLog, 3, 0, 1, 2); - - QHBoxLayout *mainLayout = new QHBoxLayout; - mainLayout->addLayout(settingsLayout); - mainLayout->addSpacing(10); - mainLayout->addLayout(grid); - - QWidget *centralWidget = new QWidget; - centralWidget->setLayout(mainLayout); - setCentralWidget(centralWidget); - - connect(importer, SIGNAL(setIndexChanged(int, int, const QString &)), this, SLOT(updateTotalProgress(int, int, const QString &))); - connect(importer, SIGNAL(dataReadProgress(int, int)), this, SLOT(updateFileProgress(int, int))); - - aLoadSetsFile = new QAction(tr("Load sets information from &file..."), this); - connect(aLoadSetsFile, SIGNAL(triggered()), this, SLOT(actLoadSetsFile())); - aDownloadSetsFile = new QAction(tr("&Download sets information..."), this); - connect(aDownloadSetsFile, SIGNAL(triggered()), this, SLOT(actDownloadSetsFile())); - aExit = new QAction(tr("E&xit"), this); - connect(aExit, SIGNAL(triggered()), this, SLOT(close())); - - fileMenu = menuBar()->addMenu(tr("&File")); - fileMenu->addAction(aLoadSetsFile); - fileMenu->addAction(aDownloadSetsFile); - fileMenu->addSeparator(); - fileMenu->addAction(aExit); - - setWindowTitle(tr("Oracle importer")); - setFixedSize(600, 500); - - QStringList args = qApp->arguments(); - if (args.contains("-dlsets")) - downloadSetsFile(defaultSetsUrl); - - statusLabel = new QLabel; - statusBar()->addWidget(statusLabel); - statusLabel->setText(tr("Sets data not loaded.")); -} - -void WindowMain::updateSetList() -{ - for (int i = 0; i < checkBoxList.size(); ++i) - delete checkBoxList[i]; - checkBoxList.clear(); - - QList &sets = importer->getSets(); - for (int i = 0; i < sets.size(); ++i) { - QCheckBox *checkBox = new QCheckBox(sets[i].getLongName()); - checkBox->setChecked(sets[i].getImport()); - connect(checkBox, SIGNAL(stateChanged(int)), this, SLOT(checkBoxChanged(int))); - checkBoxLayout->addWidget(checkBox); - checkBoxList << checkBox; - } - statusLabel->setText(tr("Ready.")); -} - -void WindowMain::actLoadSetsFile() -{ - QFileDialog dialog(this, tr("Load sets file")); - dialog.setFileMode(QFileDialog::ExistingFile); - dialog.setNameFilter("Sets XML file (*.xml)"); - if (!dialog.exec()) - return; - - QString fileName = dialog.selectedFiles().at(0); - if (importer->readSetsFromFile(fileName)) - updateSetList(); - else - QMessageBox::critical(this, tr("Error"), tr("This file does not contain any sets data.")); -} - -void WindowMain::actDownloadSetsFile() -{ - QString url = QInputDialog::getText(this, tr("Load sets from URL"), tr("Please enter the URL of the sets file:"), QLineEdit::Normal, defaultSetsUrl); - if (!url.isEmpty()) - downloadSetsFile(url); -} - -void WindowMain::downloadSetsFile(const QString &url) -{ - QNetworkReply *reply = nam->get(QNetworkRequest(url)); - connect(reply, SIGNAL(finished()), this, SLOT(setsDownloadFinished())); -} - -void WindowMain::setsDownloadFinished() -{ - QNetworkReply *reply = static_cast(sender()); - QNetworkReply::NetworkError errorCode = reply->error(); - if (errorCode == QNetworkReply::NoError) { - if (importer->readSetsFromByteArray(reply->readAll())) - updateSetList(); - else - QMessageBox::critical(this, tr("Error"), tr("The file was retrieved successfully, but it does not contain any sets data.")); - } else - QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString())); - reply->deleteLater(); -} - -void WindowMain::updateTotalProgress(int cardsImported, int setIndex, const QString &nextSetName) -{ - if (setIndex != 0) - messageLog->append(QString("%1: %2 cards imported").arg(nextSetLabel2->text()).arg(cardsImported)); - totalProgressBar->setValue(setIndex); - if (nextSetName.isEmpty()) { - QMessageBox::information(this, tr("Oracle importer"), tr("Import finished: %1 cards.").arg(importer->getCardList().size())); - bool ok = false; - QString savePath = importer->getDataDir() + "/cards.xml"; - do { - QString fileName; - if (savePath.isEmpty()) - fileName = QFileDialog::getSaveFileName(this, tr("Save card database"), importer->getDataDir() + "/cards.xml", tr("XML card database (*.xml)")); - else { - fileName = savePath; - savePath.clear(); - } - if (fileName.isEmpty()) - qApp->quit(); - if (importer->saveToFile(fileName)) - ok = true; - else - QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to the desired location.")); - } while (!ok); - qApp->quit(); - } else { - nextSetLabel2->setText(nextSetName); - } -} - -void WindowMain::updateFileProgress(int bytesRead, int totalBytes) -{ - fileProgressBar->setMaximum(totalBytes); - fileProgressBar->setValue(bytesRead); -} - -void WindowMain::actCheckAll() -{ - for (int i = 0; i < checkBoxList.size(); ++i) - checkBoxList[i]->setChecked(true); -} - -void WindowMain::actUncheckAll() -{ - for (int i = 0; i < checkBoxList.size(); ++i) - checkBoxList[i]->setChecked(false); -} - -void WindowMain::actStart() -{ - int setsCount = importer->startDownload(); - if (!setsCount) { - QMessageBox::critical(this, tr("Error"), tr("No sets to download selected.")); - return; - } - for (int i = 0; i < checkBoxList.size(); ++i) - checkBoxList[i]->setEnabled(false); - startButton->setEnabled(false); - totalProgressBar->setMaximum(setsCount); - statusLabel->setText(tr("Downloading card data...")); -} - -void WindowMain::checkBoxChanged(int state) -{ - QCheckBox *checkBox = qobject_cast(sender()); - QList &sets = importer->getSets(); - for (int i = 0; i < sets.size(); ++i) - if (sets[i].getLongName() == checkBox->text()) { - sets[i].setImport(state); - break; - } -} diff --git a/oracle/src/window_main.h b/oracle/src/window_main.h deleted file mode 100644 index 1fdba235b..000000000 --- a/oracle/src/window_main.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef WINDOW_MAIN_H -#define WINDOW_MAIN_H - -#include -#include - -class OracleImporter; -class QLabel; -class QProgressBar; -class QTextEdit; -class QPushButton; -class QCheckBox; -class QVBoxLayout; -class QMenu; -class QAction; -class QNetworkAccessManager; - -class WindowMain : public QMainWindow { - Q_OBJECT -private: - static const QString defaultSetsUrl; - - OracleImporter *importer; - QNetworkAccessManager *nam; - - QMenu *fileMenu; - QAction *aLoadSetsFile, *aDownloadSetsFile, *aExit; - QPushButton *checkAllButton, *uncheckAllButton, *startButton; - QLabel *totalLabel, *fileLabel, *nextSetLabel1, *nextSetLabel2; - QProgressBar *totalProgressBar, *fileProgressBar; - QTextEdit *messageLog; - QVBoxLayout *checkBoxLayout; - QList checkBoxList; - QLabel *statusLabel; - - void downloadSetsFile(const QString &url); -private slots: - void updateTotalProgress(int cardsImported, int setIndex, const QString &nextSetName); - void updateFileProgress(int bytesRead, int totalBytes); - void updateSetList(); - void actCheckAll(); - void actUncheckAll(); - void actStart(); - void actLoadSetsFile(); - void actDownloadSetsFile(); - void setsDownloadFinished(); - void checkBoxChanged(int state); -public: - WindowMain(QWidget *parent = 0); -}; - -#endif diff --git a/prepareMacRelease.sh b/prepareMacRelease.sh deleted file mode 100644 index c1cbf8179..000000000 --- a/prepareMacRelease.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh - -FRAMEWORKS="Core Gui Multimedia Network Svg Xml Script" -DATE=`date '+%Y%m%d'` -#QTDIR="/Users/brukie/QtSDK/Desktop/Qt/474/gcc" -QTLIB="/usr/local/Qt4.7/lib" -QTPLUGINS="/usr/local/Qt4.7/plugins" -DIR=cockatrice_mac_$DATE -if [ -d $DIR ]; then echo "delete old dir first"; exit 1; fi - -mkdir $DIR - -cp -R cockatrice/cockatrice.app $DIR -cp -R oracle/oracle.app $DIR -mkdir $DIR/decks -mkdir $DIR/pics -mkdir $DIR/Frameworks -cp -R sounds zonebg $DIR -cp -R $QTPLUGINS $DIR/PlugIns -for f in $FRAMEWORKS; do - cp -R $QTLIB/Qt"$f".framework $DIR/Frameworks -done -find $DIR/Frameworks -name '*debug*'|xargs -n 1 rm -f -find $DIR/Frameworks -name 'Headers'|xargs -n 1 rm -rf -find $DIR/PlugIns -name '*debug*'|xargs -n 1 rm -f - - -cd $DIR -for f in $FRAMEWORKS; do - echo "Framework $f" - echo "step 1" - install_name_tool -id @executable_path/../../../Frameworks/Qt"$f".framework/Versions/4/Qt"$f" Frameworks/Qt"$f".framework/Versions/4/Qt"$f" - for g in $FRAMEWORKS; do - install_name_tool -change $QTLIB/Qt"$f".framework/Versions/4/Qt"$f" @executable_path/../../../Frameworks/Qt"$f".framework/Versions/4/Qt"$f" Frameworks/Qt"$g".framework/Versions/4/Qt"$g" - done - echo "step 2" - for g in cockatrice oracle; do - install_name_tool -change $QTLIB/Qt"$f".framework/Versions/4/Qt"$f" @executable_path/../../../Frameworks/Qt"$f".framework/Versions/4/Qt"$f" "$g".app/Contents/MacOS/$g - done - echo "step 3" - for g in `find . -name '*.dylib'`; do - install_name_tool -change $QTLIB/Qt"$f".framework/Versions/4/Qt"$f" @executable_path/../../../Frameworks/Qt"$f".framework/Versions/4/Qt"$f" "$g" - done -done - -cd .. - -VOL=cockatrice_mac_${DATE} -DMG="tmp-$VOL.dmg" - -SIZE=`du -sk $DIR | cut -f1` -SIZE=$((${SIZE}/1000+10)) -hdiutil create "$DMG" -megabytes ${SIZE} -ov -type UDIF -fs HFS+ -volname "$VOL" - -hdid "$DMG" -cp -R "${DIR}" "/Volumes/$VOL" -hdiutil detach "/Volumes/$VOL" -#osascript -e "tell application "Finder" to eject disk "$VOL"" && - -# convert to compressed image, delete temp image -rm -f "${VOL}.dmg" -hdiutil convert "$DMG" -format UDZO -o "${VOL}.dmg" -rm -f "$DMG" diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt new file mode 100644 index 000000000..42e2bb74a --- /dev/null +++ b/servatrice/CMakeLists.txt @@ -0,0 +1,160 @@ +# CMakeLists for servatrice directory +# +# provides the servatrice binary + +PROJECT(servatrice) + +FIND_PACKAGE(Libgcrypt REQUIRED) + +SET(servatrice_SOURCES + src/main.cpp + src/passwordhasher.cpp + src/servatrice.cpp + src/servatrice_connection_pool.cpp + src/servatrice_database_interface.cpp + src/server_logger.cpp + src/serversocketinterface.cpp + src/isl_interface.cpp + ${VERSION_STRING_CPP} +) + +set(SERVATRICE_LIBS) + +# Qt4 stuff +if(Qt4_FOUND) + SET(QT_USE_QTNETWORK TRUE) + SET(QT_USE_QTSQL TRUE) + + # Include directories + INCLUDE(${QT_USE_FILE}) + include_directories(${QT_INCLUDES}) + LIST(APPEND SERVATRICE_LIBS ${QT_LIBRARIES}) +endif() + +# qt5 stuff +if(Qt5Widgets_FOUND) + include_directories(${Qt5Widgets_INCLUDE_DIRS}) + list(APPEND SERVATRICE_LIBS Widgets) + + # QtNetwork + find_package(Qt5Network) + if(Qt5Network_FOUND) + include_directories(${Qt5Network_INCLUDE_DIRS}) + list(APPEND SERVATRICE_LIBS Network) + endif() + + # QtSql + find_package(Qt5Sql) + if(Qt5Sql_FOUND) + include_directories(${Qt5Sql_INCLUDE_DIRS}) + list(APPEND SERVATRICE_LIBS Sql) + endif() + + # guess plugins and libraries directory + set(QT_PLUGINS_DIR "${Qt5Widgets_DIR}/../../../plugins") + get_target_property(QT_LIBRARY_DIR Qt5::Core LOCATION) + get_filename_component(QT_LIBRARY_DIR ${QT_LIBRARY_DIR} PATH) +endif() + +SET(QT_DONT_USE_QTGUI TRUE) + +# Include directories +INCLUDE_DIRECTORIES(../common) +INCLUDE_DIRECTORIES(${LIBGCRYPT_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}/../common) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) + +# Build servatrice binary and link it +ADD_EXECUTABLE(servatrice MACOSX_BUNDLE ${servatrice_SOURCES} ${servatrice_MOC_SRCS}) + +if(Qt4_FOUND) + if(MSVC) + set(QT_USE_QTMAIN true) + endif() + TARGET_LINK_LIBRARIES(servatrice cockatrice_common ${SERVATRICE_LIBS} ${LIBGCRYPT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) +endif() +if(Qt5Widgets_FOUND) + if(MSVC) + TARGET_LINK_LIBRARIES(servatrice cockatrice_common ${LIBGCRYPT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} Qt5::WinMain) + else() + TARGET_LINK_LIBRARIES(servatrice cockatrice_common ${LIBGCRYPT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) + endif() + qt5_use_modules(servatrice ${SERVATRICE_LIBS}) +endif() + +# install rules +if(UNIX) + if(APPLE) + INSTALL(TARGETS servatrice BUNDLE DESTINATION ./) + else() + # Assume linux + INSTALL(TARGETS servatrice RUNTIME DESTINATION bin/) + endif() +elseif(WIN32) + INSTALL(TARGETS servatrice RUNTIME DESTINATION ./) +endif() + +if(APPLE) + # these needs to be relative to CMAKE_INSTALL_PREFIX + set(plugin_dest_dir servatrice.app/Contents/Plugins) + set(qtconf_dest_dir servatrice.app/Contents/Resources) + + # note: no codecs in qt5 + # note: phonon_backend => mediaservice + # note: needs platform on osx + + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*_debug\\.dylib") + else() + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/[^_]*\\.dylib") + endif() + + install(CODE " + file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] +Plugins = Plugins +Translations = Resources/translations\") + " COMPONENT Runtime) + + install(CODE " + file(GLOB_RECURSE QTPLUGINS + \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dylib\") + set(BU_CHMOD_BUNDLE_ITEMS ON) + include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/servatrice.app\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\") + " COMPONENT Runtime) +endif() + +if(WIN32) + # these needs to be relative to CMAKE_INSTALL_PREFIX + set(plugin_dest_dir Plugins) + set(qtconf_dest_dir .) + + # note: no codecs in qt5 + # note: phonon_backend => mediaservice + # note: needs platform on osx + + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*d\\.dll") + else() + install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime + FILES_MATCHING REGEX "(codecs|iconengines|imageformats|mediaservice|phonon_backend|platforms)/.*[^d]\\.dll") + endif() + + install(CODE " + file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths] +Plugins = Plugins +Translations = Resources/translations\") + " COMPONENT Runtime) + + install(CODE " + file(GLOB_RECURSE QTPLUGINS + \"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dll\") + set(BU_CHMOD_BUNDLE_ITEMS ON) + include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/servatrice.exe\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\") + " COMPONENT Runtime) +endif() \ No newline at end of file diff --git a/servatrice/Doxyfile b/servatrice/Doxyfile deleted file mode 100755 index 6f10ab004..000000000 --- a/servatrice/Doxyfile +++ /dev/null @@ -1,311 +0,0 @@ -# Doxyfile 1.5.5-KDevelop - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- -DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = servertest -PROJECT_NUMBER = 0.1 -OUTPUT_DIRECTORY = -CREATE_SUBDIRS = NO -OUTPUT_LANGUAGE = English -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = YES -STRIP_FROM_PATH = /home/brukie/ -STRIP_FROM_INC_PATH = -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -QT_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = NO -DETAILS_AT_TOP = NO -INHERIT_DOCS = YES -SEPARATE_MEMBER_PAGES = NO -TAB_SIZE = 8 -ALIASES = -OPTIMIZE_OUTPUT_FOR_C = NO -OPTIMIZE_OUTPUT_JAVA = NO -OPTIMIZE_FOR_FORTRAN = NO -OPTIMIZE_OUTPUT_VHDL = NO -BUILTIN_STL_SUPPORT = NO -CPP_CLI_SUPPORT = NO -SIP_SUPPORT = NO -IDL_PROPERTY_SUPPORT = YES -DISTRIBUTE_GROUP_DOC = NO -SUBGROUPING = YES -TYPEDEF_HIDES_STRUCT = NO -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- -EXTRACT_ALL = NO -EXTRACT_PRIVATE = NO -EXTRACT_STATIC = NO -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -EXTRACT_ANON_NSPACES = NO -HIDE_UNDOC_MEMBERS = NO -HIDE_UNDOC_CLASSES = NO -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = YES -HIDE_SCOPE_NAMES = NO -SHOW_INCLUDE_FILES = YES -INLINE_INFO = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -SHOW_DIRECTORIES = NO -SHOW_FILES = YES -SHOW_NAMESPACES = YES -FILE_VERSION_FILTER = -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- -INPUT = /home/brukie/servertest -INPUT_ENCODING = UTF-8 -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.d \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.idl \ - *.odl \ - *.cs \ - *.php \ - *.php3 \ - *.inc \ - *.m \ - *.mm \ - *.dox \ - *.py \ - *.f90 \ - *.f \ - *.vhd \ - *.vhdl \ - *.C \ - *.CC \ - *.C++ \ - *.II \ - *.I++ \ - *.H \ - *.HH \ - *.H++ \ - *.CS \ - *.PHP \ - *.PHP3 \ - *.M \ - *.MM \ - *.PY \ - *.F90 \ - *.F \ - *.VHD \ - *.VHDL \ - *.C \ - *.H \ - *.tlh \ - *.diff \ - *.patch \ - *.moc \ - *.xpm \ - *.dox -RECURSIVE = yes -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = -EXCLUDE_SYMBOLS = -EXAMPLE_PATH = -EXAMPLE_PATTERNS = * -EXAMPLE_RECURSIVE = NO -IMAGE_PATH = -INPUT_FILTER = -FILTER_PATTERNS = -FILTER_SOURCE_FILES = NO -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- -SOURCE_BROWSER = NO -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = NO -REFERENCES_RELATION = NO -REFERENCES_LINK_SOURCE = YES -USE_HTAGS = NO -VERBATIM_HEADERS = YES -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- -ALPHABETICAL_INDEX = NO -COLS_IN_ALPHA_INDEX = 5 -IGNORE_PREFIX = -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = -HTML_STYLESHEET = -HTML_ALIGN_MEMBERS = YES -GENERATE_HTMLHELP = NO -GENERATE_DOCSET = NO -DOCSET_FEEDNAME = "Doxygen generated docs" -DOCSET_BUNDLE_ID = org.doxygen.Project -HTML_DYNAMIC_SECTIONS = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -BINARY_TOC = NO -TOC_EXPAND = NO -DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 4 -GENERATE_TREEVIEW = NONE -TREEVIEW_WIDTH = 250 -FORMULA_FONTSIZE = 10 -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- -GENERATE_LATEX = YES -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4wide -EXTRA_PACKAGES = -LATEX_HEADER = -PDF_HYPERLINKS = YES -USE_PDFLATEX = YES -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- -GENERATE_MAN = NO -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_LINKS = NO -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- -GENERATE_XML = yes -XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = -XML_PROGRAMLISTING = YES -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- -GENERATE_AUTOGEN_DEF = NO -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- -TAGFILES = -GENERATE_TAGFILE = servertest.tag -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -PERL_PATH = /usr/bin/perl -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- -CLASS_DIAGRAMS = YES -MSCGEN_PATH = -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = NO -DOT_FONTNAME = FreeSans -DOT_FONTPATH = -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -GROUP_GRAPHS = YES -UML_LOOK = NO -TEMPLATE_RELATIONS = NO -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -CALLER_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DIRECTORY_GRAPH = YES -DOT_IMAGE_FORMAT = png -DOT_PATH = -DOTFILE_DIRS = -DOT_GRAPH_MAX_NODES = 50 -MAX_DOT_GRAPH_DEPTH = 1000 -DOT_TRANSPARENT = YES -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = YES -DOT_CLEANUP = YES -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- -SEARCHENGINE = NO diff --git a/servatrice/scripts/maint_replays b/servatrice/scripts/maint_replays new file mode 100644 index 000000000..1ce1b400d --- /dev/null +++ b/servatrice/scripts/maint_replays @@ -0,0 +1,3 @@ +#!/bin/bash +# SCHEDULE WITH CRONTAB BASED ON TIME PERIOD REPLAYS SHOULD BE SAVED UNTIL (EX: SCHEDULE ONCE A WEEK TO KEEP A WEEKS WORTH OF REPLAYS IN THE DB) +mysql --defaults-file=./mysql.cnf -h localhost -e 'truncate table servatrice.cockatrice_replays;truncate table servatrice.cockatrice_replays_access' diff --git a/servatrice/scripts/maint_sessions b/servatrice/scripts/maint_sessions new file mode 100644 index 000000000..2c41da6da --- /dev/null +++ b/servatrice/scripts/maint_sessions @@ -0,0 +1,3 @@ +#!/bin/bash +# SCHEDULE WITH CRONTAB TO RUN ONCE A MONTH +mysql --defaults-file=./mysql.cnf -h localhost -e "delete from servatrice.cockatrice_sessions where start_time < DATE_SUB(now(), INTERVAL 1 MONTH)" diff --git a/servatrice/scripts/mysql.cnf.example b/servatrice/scripts/mysql.cnf.example new file mode 100644 index 000000000..e5d901be6 --- /dev/null +++ b/servatrice/scripts/mysql.cnf.example @@ -0,0 +1,3 @@ +[client] +user={db_username} +password={db_password} diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index 8a01bc85f..14926463b 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -2,11 +2,21 @@ port=4747 statusupdate=15000 logfile=server.log +name="My Cockatrice server" id=1 -threaded=0 +number_pools=1 +writelog=1 +logfilters="" + +[servernetwork] +active=0 +port=14747 +ssl_cert=ssl_cert.pem +ssl_key=ssl_key.pem [authentication] method=none +regonly=0 [database] type=none @@ -17,15 +27,16 @@ user=servatrice password=foobar [rooms] -size=1 -1\name="General room" -1\description="Play anything here." -1\autojoin=true -1\joinmessage="This message is only here to show that rooms can have a join message." -1\game_types\size=3 -1\game_types\1\name="GameType1" -1\game_types\2\name="GameType2" -1\game_types\3\name="GameType3" +method=config +roomlist\size=1 +roomlist\1\name="General room" +roomlist\1\description="Play anything here." +roomlist\1\autojoin=true +roomlist\1\joinmessage="This message is only here to show that rooms can have a join message." +roomlist\1\game_types\size=3 +roomlist\1\game_types\1\name="GameType1" +roomlist\1\game_types\2\name="GameType2" +roomlist\1\game_types\3\name="GameType3" [game] max_game_inactivity_time=120 diff --git a/servatrice/servatrice.pro b/servatrice/servatrice.pro deleted file mode 100755 index ab7dc8537..000000000 --- a/servatrice/servatrice.pro +++ /dev/null @@ -1,62 +0,0 @@ -###################################################################### -# Automatically generated by qmake (2.01a) Fr Sep 5 18:02:29 2008 -###################################################################### - -TEMPLATE = app -TARGET = -DEPENDPATH += . src ../common -INCLUDEPATH += . src ../common -MOC_DIR = build -OBJECTS_DIR = build -LIBS += -lgcrypt - -CONFIG += qt debug -QT += network sql -QT -= gui - -HEADERS += src/main.h \ - src/servatrice.h \ - src/serversocketinterface.h \ - src/server_logger.h \ - src/serversocketthread.h \ - src/passwordhasher.h \ - ../common/color.h \ - ../common/serializable_item.h \ - ../common/decklist.h \ - ../common/protocol.h \ - ../common/protocol_items.h \ - ../common/protocol_datastructures.h \ - ../common/rng_abstract.h \ - ../common/rng_sfmt.h \ - ../common/server.h \ - ../common/server_arrow.h \ - ../common/server_card.h \ - ../common/server_cardzone.h \ - ../common/server_room.h \ - ../common/server_counter.h \ - ../common/server_game.h \ - ../common/server_player.h \ - ../common/server_protocolhandler.h \ - ../common/server_arrowtarget.h - -SOURCES += src/main.cpp \ - src/servatrice.cpp \ - src/serversocketinterface.cpp \ - src/server_logger.cpp \ - src/serversocketthread.cpp \ - src/passwordhasher.cpp \ - ../common/serializable_item.cpp \ - ../common/decklist.cpp \ - ../common/protocol.cpp \ - ../common/protocol_items.cpp \ - ../common/protocol_datastructures.cpp \ - ../common/rng_abstract.cpp \ - ../common/rng_sfmt.cpp \ - ../common/sfmt/SFMT.c \ - ../common/server.cpp \ - ../common/server_card.cpp \ - ../common/server_cardzone.cpp \ - ../common/server_room.cpp \ - ../common/server_game.cpp \ - ../common/server_player.cpp \ - ../common/server_protocolhandler.cpp diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index 192cdef7c..6e83f1f2d 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -28,13 +28,13 @@ SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, `id_folder` int(7) unsigned zerofill NOT NULL, - `user` varchar(35) NOT NULL, + `id_user` int(7) unsigned NULL, `name` varchar(50) NOT NULL, `upload_time` datetime NOT NULL, `content` text NOT NULL, PRIMARY KEY (`id`), - KEY `FolderPlusUser` (`id_folder`,`user`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=550 ; + KEY `FolderPlusUser` (`id_folder`,`id_user`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -45,11 +45,11 @@ CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` ( CREATE TABLE IF NOT EXISTS `cockatrice_decklist_folders` ( `id` int(7) unsigned zerofill NOT NULL auto_increment, `id_parent` int(7) unsigned zerofill NOT NULL, - `user` varchar(35) NOT NULL, + `id_user` int(7) unsigned NULL, `name` varchar(30) NOT NULL, PRIMARY KEY (`id`), - KEY `ParentPlusUser` (`id_parent`,`user`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=80 ; + KEY `ParentPlusUser` (`id_parent`,`id_user`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -58,13 +58,17 @@ CREATE TABLE IF NOT EXISTS `cockatrice_decklist_folders` ( -- CREATE TABLE IF NOT EXISTS `cockatrice_games` ( - `id` int(7) unsigned zerofill NOT NULL, + `room_name` varchar(255) NOT NULL, + `id` int(7) unsigned NOT NULL auto_increment, `descr` varchar(50) default NULL, - `password` tinyint(1) default NULL, + `creator_name` varchar(255) NOT NULL, + `password` tinyint(1) NOT NULL, + `game_types` varchar(255) NOT NULL, + `player_count` tinyint(3) NOT NULL, `time_started` datetime default NULL, `time_finished` datetime default NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -74,9 +78,9 @@ CREATE TABLE IF NOT EXISTS `cockatrice_games` ( CREATE TABLE IF NOT EXISTS `cockatrice_games_players` ( `id_game` int(7) unsigned zerofill NOT NULL, - `player` varchar(35) default NULL, + `player_name` varchar(255) NOT NULL, KEY `id_game` (`id_game`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -91,7 +95,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_news` ( `subject` varchar(255) NOT NULL, `content` text NOT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ; +) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- -------------------------------------------------------- @@ -111,10 +115,12 @@ CREATE TABLE IF NOT EXISTS `cockatrice_users` ( `avatar_bmp` blob NOT NULL, `registrationDate` datetime NOT NULL, `active` tinyint(1) NOT NULL, - `token` char(32) NOT NULL, + `token` binary(16) NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=915 ; + UNIQUE KEY `name` (`name`), + KEY `token` (`token`), + KEY `email` (`email`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE `cockatrice_uptime` ( `id_server` tinyint(3) NOT NULL, @@ -157,16 +163,48 @@ CREATE TABLE `cockatrice_bans` ( `time_from` datetime NOT NULL, `minutes` int(6) NOT NULL, `reason` text NOT NULL, + `visible_reason` text NOT NULL, PRIMARY KEY (`user_name`,`time_from`), - KEY `time_from` (`time_from`,`ip_address`) + KEY `time_from` (`time_from`,`ip_address`), + KEY `ip_address` (`ip_address`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; CREATE TABLE `cockatrice_sessions` ( `id` int(9) NOT NULL AUTO_INCREMENT, `user_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `id_server` tinyint(3) NOT NULL, `ip_address` char(15) COLLATE utf8_unicode_ci NOT NULL, `start_time` datetime NOT NULL, `end_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `username` (`user_name`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +CREATE TABLE `cockatrice_servers` ( + `id` mediumint(8) unsigned NOT NULL, + `ssl_cert` text COLLATE utf8_unicode_ci NOT NULL, + `hostname` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `address` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `game_port` mediumint(8) unsigned NOT NULL, + `control_port` mediumint(9) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +CREATE TABLE `cockatrice_replays` ( + `id` int(7) NOT NULL AUTO_INCREMENT, + `id_game` int(7) NOT NULL, + `duration` int(7) NOT NULL, + `replay` mediumblob NOT NULL, + PRIMARY KEY (`id`), + KEY `id_game` (`id_game`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +CREATE TABLE `cockatrice_replays_access` ( + `id_game` int(7) NOT NULL, + `id_player` int(7) NOT NULL, + `replay_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `do_not_hide` tinyint(1) NOT NULL, + KEY `id_player` (`id_player`), + KEY `id_game` (`id_game`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + diff --git a/servatrice/src/isl_interface.cpp b/servatrice/src/isl_interface.cpp new file mode 100644 index 000000000..2a9ea41b2 --- /dev/null +++ b/servatrice/src/isl_interface.cpp @@ -0,0 +1,415 @@ +#include "isl_interface.h" +#include +#include "server_logger.h" +#include "main.h" +#include "server_protocolhandler.h" +#include "server_room.h" + +#include "get_pb_extension.h" +#include "pb/isl_message.pb.h" +#include "pb/event_game_joined.pb.h" +#include "pb/event_server_complete_list.pb.h" +#include "pb/event_user_message.pb.h" +#include "pb/event_user_joined.pb.h" +#include "pb/event_user_left.pb.h" +#include "pb/event_join_room.pb.h" +#include "pb/event_leave_room.pb.h" +#include "pb/event_room_say.pb.h" +#include "pb/event_list_games.pb.h" +#include + +void IslInterface::sharedCtor(const QSslCertificate &cert, const QSslKey &privateKey) +{ + socket = new QSslSocket(this); + socket->setLocalCertificate(cert); + socket->setPrivateKey(privateKey); + + connect(socket, SIGNAL(readyRead()), this, SLOT(readClient()), Qt::QueuedConnection); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(catchSocketError(QAbstractSocket::SocketError))); + connect(this, SIGNAL(outputBufferChanged()), this, SLOT(flushOutputBuffer()), Qt::QueuedConnection); +} + +IslInterface::IslInterface(int _socketDescriptor, const QSslCertificate &cert, const QSslKey &privateKey, Servatrice *_server) + : QObject(), socketDescriptor(_socketDescriptor), server(_server), messageInProgress(false) +{ + sharedCtor(cert, privateKey); +} + +IslInterface::IslInterface(int _serverId, const QString &_peerHostName, const QString &_peerAddress, int _peerPort, const QSslCertificate &_peerCert, const QSslCertificate &cert, const QSslKey &privateKey, Servatrice *_server) + : QObject(), serverId(_serverId), peerHostName(_peerHostName), peerAddress(_peerAddress), peerPort(_peerPort), peerCert(_peerCert), server(_server), messageInProgress(false) +{ + sharedCtor(cert, privateKey); +} + +IslInterface::~IslInterface() +{ + logger->logMessage("[ISL] session ended", this); + + flushOutputBuffer(); + + // As these signals are connected with Qt::QueuedConnection implicitly, + // we don't need to worry about them modifying the lists while we're iterating. + + server->roomsLock.lockForRead(); + QMapIterator roomIterator(server->getRooms()); + while (roomIterator.hasNext()) { + Server_Room *room = roomIterator.next().value(); + room->usersLock.lockForRead(); + QMapIterator roomUsers(room->getExternalUsers()); + while (roomUsers.hasNext()) { + roomUsers.next(); + if (roomUsers.value().getUserInfo()->server_id() == serverId) + emit externalRoomUserLeft(room->getId(), roomUsers.key()); + } + room->usersLock.unlock(); + } + server->roomsLock.unlock(); + + server->clientsLock.lockForRead(); + QMapIterator extUsers(server->getExternalUsers()); + while (extUsers.hasNext()) { + extUsers.next(); + if (extUsers.value()->getUserInfo()->server_id() == serverId) + emit externalUserLeft(extUsers.key()); + } + server->clientsLock.unlock(); +} + +void IslInterface::initServer() +{ + socket->setSocketDescriptor(socketDescriptor); + + logger->logMessage(QString("[ISL] incoming connection: %1").arg(socket->peerAddress().toString())); + + QList serverList = server->getServerList(); + int listIndex = -1; + for (int i = 0; i < serverList.size(); ++i) + if (serverList[i].address == socket->peerAddress()) { + listIndex = i; + break; + } + if (listIndex == -1) { + logger->logMessage(QString("[ISL] address %1 unknown, terminating connection").arg(socket->peerAddress().toString())); + deleteLater(); + return; + } + + socket->startServerEncryption(); + if (!socket->waitForEncrypted(5000)) { + QList sslErrors(socket->sslErrors()); + if (sslErrors.isEmpty()) + qDebug() << "[ISL] SSL handshake timeout, terminating connection"; + else + qDebug() << "[ISL] SSL errors:" << sslErrors; + deleteLater(); + return; + } + + if (serverList[listIndex].cert == socket->peerCertificate()) + logger->logMessage(QString("[ISL] Peer authenticated as " + serverList[listIndex].hostname)); + else { + logger->logMessage(QString("[ISL] Authentication failed, terminating connection")); + deleteLater(); + return; + } + serverId = serverList[listIndex].id; + + Event_ServerCompleteList event; + event.set_server_id(server->getServerId()); + + server->clientsLock.lockForRead(); + QMapIterator userIterator(server->getUsers()); + while (userIterator.hasNext()) + event.add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(true, true)); + server->clientsLock.unlock(); + + server->roomsLock.lockForRead(); + QMapIterator roomIterator(server->getRooms()); + while (roomIterator.hasNext()) { + Server_Room *room = roomIterator.next().value(); + room->usersLock.lockForRead(); + room->gamesLock.lockForRead(); + room->getInfo(*event.add_room_list(), true, true, false); + } + + IslMessage message; + message.set_message_type(IslMessage::SESSION_EVENT); + SessionEvent *sessionEvent = message.mutable_session_event(); + sessionEvent->GetReflection()->MutableMessage(sessionEvent, event.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(event); + + server->islLock.lockForWrite(); + if (server->islConnectionExists(serverId)) { + qDebug() << "[ISL] Duplicate connection to #" << serverId << "terminating connection"; + deleteLater(); + } else { + transmitMessage(message); + server->addIslInterface(serverId, this); + } + server->islLock.unlock(); + + roomIterator.toFront(); + while (roomIterator.hasNext()) { + roomIterator.next(); + roomIterator.value()->gamesLock.unlock(); + roomIterator.value()->usersLock.unlock(); + } + server->roomsLock.unlock(); +} + +void IslInterface::initClient() +{ + QList expectedErrors; + expectedErrors.append(QSslError(QSslError::SelfSignedCertificate, peerCert)); + socket->ignoreSslErrors(expectedErrors); + + qDebug() << "[ISL] Connecting to #" << serverId << ":" << peerAddress << ":" << peerPort; + + socket->connectToHostEncrypted(peerAddress, peerPort, peerHostName); + if (!socket->waitForConnected(5000)) { + qDebug() << "[ISL] Socket error:" << socket->errorString(); + deleteLater(); + return; + } + if (!socket->waitForEncrypted(5000)) { + QList sslErrors(socket->sslErrors()); + if (sslErrors.isEmpty()) + qDebug() << "[ISL] SSL handshake timeout, terminating connection"; + else + qDebug() << "[ISL] SSL errors:" << sslErrors; + deleteLater(); + return; + } + + server->islLock.lockForWrite(); + if (server->islConnectionExists(serverId)) { + qDebug() << "[ISL] Duplicate connection to #" << serverId << "terminating connection"; + deleteLater(); + return; + } + + server->addIslInterface(serverId, this); + server->islLock.unlock(); +} + +void IslInterface::flushOutputBuffer() +{ + QMutexLocker locker(&outputBufferMutex); + if (outputBuffer.isEmpty()) + return; + server->incTxBytes(outputBuffer.size()); + socket->write(outputBuffer); + socket->flush(); + outputBuffer.clear(); +} + +void IslInterface::readClient() +{ + QByteArray data = socket->readAll(); + server->incRxBytes(data.size()); + inputBuffer.append(data); + + do { + if (!messageInProgress) { + if (inputBuffer.size() >= 4) { + messageLength = (((quint32) (unsigned char) inputBuffer[0]) << 24) + + (((quint32) (unsigned char) inputBuffer[1]) << 16) + + (((quint32) (unsigned char) inputBuffer[2]) << 8) + + ((quint32) (unsigned char) inputBuffer[3]); + inputBuffer.remove(0, 4); + messageInProgress = true; + } else + return; + } + if (inputBuffer.size() < messageLength) + return; + + IslMessage newMessage; + newMessage.ParseFromArray(inputBuffer.data(), messageLength); + inputBuffer.remove(0, messageLength); + messageInProgress = false; + + processMessage(newMessage); + } while (!inputBuffer.isEmpty()); +} + +void IslInterface::catchSocketError(QAbstractSocket::SocketError socketError) +{ + qDebug() << "[ISL] Socket error:" << socketError; + + server->islLock.lockForWrite(); + server->removeIslInterface(serverId); + server->islLock.unlock(); + + deleteLater(); +} + +void IslInterface::transmitMessage(const IslMessage &item) +{ + QByteArray buf; + unsigned int size = item.ByteSize(); + buf.resize(size + 4); + item.SerializeToArray(buf.data() + 4, size); + buf.data()[3] = (unsigned char) size; + buf.data()[2] = (unsigned char) (size >> 8); + buf.data()[1] = (unsigned char) (size >> 16); + buf.data()[0] = (unsigned char) (size >> 24); + + outputBufferMutex.lock(); + outputBuffer.append(buf); + outputBufferMutex.unlock(); + emit outputBufferChanged(); +} + +void IslInterface::sessionEvent_ServerCompleteList(const Event_ServerCompleteList &event) +{ + for (int i = 0; i < event.user_list_size(); ++i) { + ServerInfo_User temp(event.user_list(i)); + temp.set_server_id(serverId); + emit externalUserJoined(temp); + } + for (int i = 0; i < event.room_list_size(); ++i) { + const ServerInfo_Room &room = event.room_list(i); + for (int j = 0; j < room.user_list_size(); ++j) { + ServerInfo_User userInfo(room.user_list(j)); + userInfo.set_server_id(serverId); + emit externalRoomUserJoined(room.room_id(), userInfo); + } + for (int j = 0; j < room.game_list_size(); ++j) { + ServerInfo_Game gameInfo(room.game_list(j)); + gameInfo.set_server_id(serverId); + emit externalRoomGameListChanged(room.room_id(), gameInfo); + } + } +} + +void IslInterface::sessionEvent_UserJoined(const Event_UserJoined &event) +{ + ServerInfo_User userInfo(event.user_info()); + userInfo.set_server_id(serverId); + emit externalUserJoined(userInfo); +} + +void IslInterface::sessionEvent_UserLeft(const Event_UserLeft &event) +{ + emit externalUserLeft(QString::fromStdString(event.name())); +} + +void IslInterface::roomEvent_UserJoined(int roomId, const Event_JoinRoom &event) +{ + ServerInfo_User userInfo(event.user_info()); + userInfo.set_server_id(serverId); + emit externalRoomUserJoined(roomId, userInfo); +} + +void IslInterface::roomEvent_UserLeft(int roomId, const Event_LeaveRoom &event) +{ + emit externalRoomUserLeft(roomId, QString::fromStdString(event.name())); +} + +void IslInterface::roomEvent_Say(int roomId, const Event_RoomSay &event) +{ + emit externalRoomSay(roomId, QString::fromStdString(event.name()), QString::fromStdString(event.message())); +} + +void IslInterface::roomEvent_ListGames(int roomId, const Event_ListGames &event) +{ + for (int i = 0; i < event.game_list_size(); ++i) { + ServerInfo_Game gameInfo(event.game_list(i)); + gameInfo.set_server_id(serverId); + emit externalRoomGameListChanged(roomId, gameInfo); + } +} + +void IslInterface::roomCommand_JoinGame(const Command_JoinGame &cmd, int cmdId, int roomId, qint64 sessionId) +{ + emit joinGameCommandReceived(cmd, cmdId, roomId, serverId, sessionId); +} + +void IslInterface::processSessionEvent(const SessionEvent &event, qint64 sessionId) +{ + switch (getPbExtension(event)) { + case SessionEvent::SERVER_COMPLETE_LIST: sessionEvent_ServerCompleteList(event.GetExtension(Event_ServerCompleteList::ext)); break; + case SessionEvent::USER_JOINED: sessionEvent_UserJoined(event.GetExtension(Event_UserJoined::ext)); break; + case SessionEvent::USER_LEFT: sessionEvent_UserLeft(event.GetExtension(Event_UserLeft::ext)); break; + case SessionEvent::GAME_JOINED: { + QReadLocker clientsLocker(&server->clientsLock); + Server_AbstractUserInterface *client = server->getUsersBySessionId().value(sessionId); + if (!client) { + qDebug() << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; + break; + } + const Event_GameJoined &gameJoined = event.GetExtension(Event_GameJoined::ext); + client->playerAddedToGame(gameJoined.game_info().game_id(), gameJoined.game_info().room_id(), gameJoined.player_id()); + client->sendProtocolItem(event); + break; + } + case SessionEvent::USER_MESSAGE: + case SessionEvent::REPLAY_ADDED: { + QReadLocker clientsLocker(&server->clientsLock); + Server_AbstractUserInterface *client = server->getUsersBySessionId().value(sessionId); + if (!client) { + qDebug() << "IslInterface::processSessionEvent: session id" << sessionId << "not found"; + break; + } + + client->sendProtocolItem(event); + break; + } + default: ; + } +} + +void IslInterface::processRoomEvent(const RoomEvent &event) +{ + switch (getPbExtension(event)) { + case RoomEvent::JOIN_ROOM: roomEvent_UserJoined(event.room_id(), event.GetExtension(Event_JoinRoom::ext)); break; + case RoomEvent::LEAVE_ROOM: roomEvent_UserLeft(event.room_id(), event.GetExtension(Event_LeaveRoom::ext)); break; + case RoomEvent::ROOM_SAY: roomEvent_Say(event.room_id(), event.GetExtension(Event_RoomSay::ext)); break; + case RoomEvent::LIST_GAMES: roomEvent_ListGames(event.room_id(), event.GetExtension(Event_ListGames::ext)); break; + default: ; + } +} + +void IslInterface::processRoomCommand(const CommandContainer &cont, qint64 sessionId) +{ + for (int i = 0; i < cont.room_command_size(); ++i) { + const RoomCommand &roomCommand = cont.room_command(i); + switch (static_cast(getPbExtension(roomCommand))) { + case RoomCommand::JOIN_GAME: roomCommand_JoinGame(roomCommand.GetExtension(Command_JoinGame::ext), cont.cmd_id(), cont.room_id(), sessionId); + default: ; + } + } +} + +void IslInterface::processMessage(const IslMessage &item) +{ + qDebug() << QString::fromStdString(item.DebugString()); + + switch (item.message_type()) { + case IslMessage::ROOM_COMMAND_CONTAINER: { + processRoomCommand(item.room_command(), item.session_id()); + break; + } + case IslMessage::GAME_COMMAND_CONTAINER: { + emit gameCommandContainerReceived(item.game_command(), item.player_id(), serverId, item.session_id()); + break; + } + case IslMessage::SESSION_EVENT: { + processSessionEvent(item.session_event(), item.session_id()); + break; + } + case IslMessage::RESPONSE: { + emit responseReceived(item.response(), item.session_id()); + break; + } + case IslMessage::GAME_EVENT_CONTAINER: { + emit gameEventContainerReceived(item.game_event_container(), item.session_id()); + break; + } + case IslMessage::ROOM_EVENT: { + processRoomEvent(item.room_event()); break; + break; + } + default: ; + } +} diff --git a/servatrice/src/isl_interface.h b/servatrice/src/isl_interface.h new file mode 100644 index 000000000..cd1787ff1 --- /dev/null +++ b/servatrice/src/isl_interface.h @@ -0,0 +1,88 @@ +#ifndef ISL_INTERFACE_H +#define ISL_INTERFACE_H + +#include "servatrice.h" +#include +#include +#include "pb/serverinfo_user.pb.h" +#include "pb/serverinfo_room.pb.h" +#include "pb/serverinfo_game.pb.h" + +class Servatrice; +class QSslSocket; +class QSslKey; +class IslMessage; + +class Event_ServerCompleteList; +class Event_UserMessage; +class Event_UserJoined; +class Event_UserLeft; +class Event_JoinRoom; +class Event_LeaveRoom; +class Event_RoomSay; +class Event_ListGames; +class Command_JoinGame; + +class IslInterface : public QObject { + Q_OBJECT +private slots: + void readClient(); + void catchSocketError(QAbstractSocket::SocketError socketError); + void flushOutputBuffer(); +signals: + void outputBufferChanged(); + + void externalUserJoined(ServerInfo_User userInfo); + void externalUserLeft(QString userName); + void externalRoomUserJoined(int roomId, ServerInfo_User userInfo); + void externalRoomUserLeft(int roomId, QString userName); + void externalRoomSay(int roomId, QString userName, QString message); + void externalRoomGameListChanged(int roomId, ServerInfo_Game gameInfo); + void joinGameCommandReceived(const Command_JoinGame &cmd, int cmdId, int roomId, int serverId, qint64 sessionId); + void gameCommandContainerReceived(const CommandContainer &cont, int playerId, int serverId, qint64 sessionId); + void responseReceived(const Response &resp, qint64 sessionId); + void gameEventContainerReceived(const GameEventContainer &cont, qint64 sessionId); +private: + int serverId; + int socketDescriptor; + QString peerHostName, peerAddress; + int peerPort; + QSslCertificate peerCert; + + QMutex outputBufferMutex; + Servatrice *server; + QSslSocket *socket; + + QByteArray inputBuffer, outputBuffer; + bool messageInProgress; + int messageLength; + + void sessionEvent_ServerCompleteList(const Event_ServerCompleteList &event); + void sessionEvent_UserJoined(const Event_UserJoined &event); + void sessionEvent_UserLeft(const Event_UserLeft &event); + + void roomEvent_UserJoined(int roomId, const Event_JoinRoom &event); + void roomEvent_UserLeft(int roomId, const Event_LeaveRoom &event); + void roomEvent_Say(int roomId, const Event_RoomSay &event); + void roomEvent_ListGames(int roomId, const Event_ListGames &event); + + void roomCommand_JoinGame(const Command_JoinGame &cmd, int cmdId, int roomId, qint64 sessionId); + + void processSessionEvent(const SessionEvent &event, qint64 sessionId); + void processRoomEvent(const RoomEvent &event); + void processRoomCommand(const CommandContainer &cont, qint64 sessionId); + + void processMessage(const IslMessage &item); + void sharedCtor(const QSslCertificate &cert, const QSslKey &privateKey); +public slots: + void initServer(); + void initClient(); +public: + IslInterface(int socketDescriptor, const QSslCertificate &cert, const QSslKey &privateKey, Servatrice *_server); + IslInterface(int _serverId, const QString &peerHostName, const QString &peerAddress, int peerPort, const QSslCertificate &peerCert, const QSslCertificate &cert, const QSslKey &privateKey, Servatrice *_server); + ~IslInterface(); + + void transmitMessage(const IslMessage &item); +}; + +#endif diff --git a/servatrice/src/main.cpp b/servatrice/src/main.cpp index 02da15df3..d90e06349 100644 --- a/servatrice/src/main.cpp +++ b/servatrice/src/main.cpp @@ -28,13 +28,32 @@ #include "servatrice.h" #include "server_logger.h" #include "rng_sfmt.h" +#include "version_string.h" +#include #ifdef Q_OS_UNIX #include #endif RNG_Abstract *rng; ServerLogger *logger; -ServerLoggerThread *loggerThread; +QThread *loggerThread; + +/* Prototypes */ + +void testRNG(); +void testHash(); +#if QT_VERSION < 0x050000 +void myMessageOutput(QtMsgType type, const char *msg); +void myMessageOutput2(QtMsgType type, const char *msg); +#else +void myMessageOutput(QtMsgType type, const QMessageLogContext &, const QString &msg); +void myMessageOutput2(QtMsgType type, const QMessageLogContext &, const QString &msg); +#endif +#ifdef Q_OS_UNIX +void sigSegvHandler(int sig); +#endif + +/* Implementations */ void testRNG() { @@ -81,11 +100,30 @@ void testHash() std::cerr << startTime.secsTo(endTime) << "secs" << std::endl; } +#if QT_VERSION < 0x050000 void myMessageOutput(QtMsgType /*type*/, const char *msg) { logger->logMessage(msg); } +void myMessageOutput2(QtMsgType /*type*/, const char *msg) +{ + logger->logMessage(msg); + std::cerr << msg << std::endl; +} +#else +void myMessageOutput(QtMsgType /*type*/, const QMessageLogContext &, const QString &msg) +{ + logger->logMessage(msg); +} + +void myMessageOutput2(QtMsgType /*type*/, const QMessageLogContext &, const QString &msg) +{ + logger->logMessage(msg); + std::cerr << msg.toStdString() << std::endl; +} +#endif + #ifdef Q_OS_UNIX void sigSegvHandler(int sig) { @@ -93,7 +131,11 @@ void sigSegvHandler(int sig) logger->logMessage("CRASH: SIGSEGV"); else if (sig == SIGABRT) logger->logMessage("CRASH: SIGABRT"); + + logger->deleteLater(); + loggerThread->wait(); delete loggerThread; + raise(sig); } #endif @@ -107,19 +149,37 @@ int main(int argc, char *argv[]) QStringList args = app.arguments(); bool testRandom = args.contains("--test-random"); bool testHashFunction = args.contains("--test-hash"); + bool logToConsole = args.contains("--log-to-console"); qRegisterMetaType >("QList"); - + +#if QT_VERSION < 0x050000 + // gone in Qt5, all source files _MUST_ be utf8-encoded QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); - +#endif + QSettings *settings = new QSettings("servatrice.ini", QSettings::IniFormat); - loggerThread = new ServerLoggerThread(settings->value("server/logfile").toString()); - loggerThread->start(); - loggerThread->waitForInit(); - logger = loggerThread->getLogger(); + loggerThread = new QThread; + loggerThread->setObjectName("logger"); + logger = new ServerLogger(logToConsole); + logger->moveToThread(loggerThread); - qInstallMsgHandler(myMessageOutput); + loggerThread->start(); + QMetaObject::invokeMethod(logger, "startLog", Qt::BlockingQueuedConnection, Q_ARG(QString, settings->value("server/logfile").toString())); + +#if QT_VERSION < 0x050000 + if (logToConsole) + qInstallMsgHandler(myMessageOutput); + else + qInstallMsgHandler(myMessageOutput2); +#else + if (logToConsole) + qInstallMessageHandler(myMessageOutput); + else + qInstallMessageHandler(myMessageOutput2); +#endif + #ifdef Q_OS_UNIX struct sigaction hup; hup.sa_handler = ServerLogger::hupSignalHandler; @@ -134,12 +194,16 @@ int main(int argc, char *argv[]) sigemptyset(&segv.sa_mask); sigaction(SIGSEGV, &segv, 0); sigaction(SIGABRT, &segv, 0); + + signal(SIGPIPE, SIG_IGN); #endif rng = new RNG_SFMT; - std::cerr << "Servatrice " << Servatrice::versionString.toStdString() << " starting." << std::endl; + std::cerr << "Servatrice " << VERSION_STRING << " starting." << std::endl; std::cerr << "-------------------------" << std::endl; + PasswordHasher::initialize(); + if (testRandom) testRNG(); if (testHashFunction) @@ -147,18 +211,31 @@ int main(int argc, char *argv[]) Servatrice *server = new Servatrice(settings); QObject::connect(server, SIGNAL(destroyed()), &app, SLOT(quit()), Qt::QueuedConnection); - - std::cerr << "-------------------------" << std::endl; - std::cerr << "Server initialized." << std::endl; - - int retval = app.exec(); + int retval = 0; + if (server->initServer()) { + std::cerr << "-------------------------" << std::endl; + std::cerr << "Server initialized." << std::endl; - std::cerr << "Server quit." << std::endl; - std::cerr << "-------------------------" << std::endl; +#if QT_VERSION < 0x050000 + qInstallMsgHandler(myMessageOutput); +#else + qInstallMessageHandler(myMessageOutput); +#endif + retval = app.exec(); + + std::cerr << "Server quit." << std::endl; + std::cerr << "-------------------------" << std::endl; + } delete rng; delete settings; + + logger->deleteLater(); + loggerThread->wait(); delete loggerThread; + // Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); + return retval; } diff --git a/servatrice/src/passwordhasher.cpp b/servatrice/src/passwordhasher.cpp index 48fe7b66c..0d8cda36e 100644 --- a/servatrice/src/passwordhasher.cpp +++ b/servatrice/src/passwordhasher.cpp @@ -3,19 +3,30 @@ #include #include +void PasswordHasher::initialize() +{ + // These calls are required by libgcrypt before we use any of its functions. + gcry_check_version(0); + gcry_control(GCRYCTL_DISABLE_SECMEM, 0); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +} + QString PasswordHasher::computeHash(const QString &password, const QString &salt) { const int algo = GCRY_MD_SHA512; const int rounds = 1000; - QByteArray passwordBuffer = (salt + password).toAscii(); + QByteArray passwordBuffer = (salt + password).toUtf8(); int hashLen = gcry_md_get_algo_dlen(algo); - char hash[hashLen], tmp[hashLen]; + char *hash = new char[hashLen], *tmp = new char[hashLen]; gcry_md_hash_buffer(algo, hash, passwordBuffer.data(), passwordBuffer.size()); for (int i = 1; i < rounds; ++i) { memcpy(tmp, hash, hashLen); gcry_md_hash_buffer(algo, hash, tmp, hashLen); } - return salt + QString(QByteArray(hash, hashLen).toBase64()); + QString hashedPass = salt + QString(QByteArray(hash, hashLen).toBase64()); + delete[] tmp; + delete[] hash; + return hashedPass; } diff --git a/servatrice/src/passwordhasher.h b/servatrice/src/passwordhasher.h index 2487b3222..0cb6744c3 100644 --- a/servatrice/src/passwordhasher.h +++ b/servatrice/src/passwordhasher.h @@ -5,6 +5,7 @@ class PasswordHasher { public: + static void initialize(); static QString computeHash(const QString &password, const QString &salt); }; diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index c3c103b9f..4c9b4a067 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -17,42 +17,288 @@ * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include +#include #include +#include +#include +#include #include #include #include "servatrice.h" +#include "servatrice_database_interface.h" +#include "servatrice_connection_pool.h" #include "server_room.h" #include "serversocketinterface.h" -#include "serversocketthread.h" -#include "protocol.h" +#include "isl_interface.h" #include "server_logger.h" #include "main.h" -#include "passwordhasher.h" +#include "decklist.h" +#include "pb/event_server_message.pb.h" +#include "pb/event_server_shutdown.pb.h" +#include "pb/event_connection_closed.pb.h" -void Servatrice_TcpServer::incomingConnection(int socketDescriptor) +Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent) + : QTcpServer(parent), + server(_server) { - if (threaded) { - ServerSocketThread *sst = new ServerSocketThread(socketDescriptor, server, this); - sst->start(); - } else { - QTcpSocket *socket = new QTcpSocket; - socket->setSocketDescriptor(socketDescriptor); - ServerSocketInterface *ssi = new ServerSocketInterface(server, socket); - logger->logMessage(QString("incoming connection: %1").arg(socket->peerAddress().toString()), ssi); - } + if (_numberPools == 0) { + server->setThreaded(false); + Servatrice_DatabaseInterface *newDatabaseInterface = new Servatrice_DatabaseInterface(0, server); + Servatrice_ConnectionPool *newPool = new Servatrice_ConnectionPool(newDatabaseInterface); + + server->addDatabaseInterface(thread(), newDatabaseInterface); + newDatabaseInterface->initDatabase(_sqlDatabase); + + connectionPools.append(newPool); + } else + for (int i = 0; i < _numberPools; ++i) { + Servatrice_DatabaseInterface *newDatabaseInterface = new Servatrice_DatabaseInterface(i, server); + Servatrice_ConnectionPool *newPool = new Servatrice_ConnectionPool(newDatabaseInterface); + + QThread *newThread = new QThread; + newThread->setObjectName("pool_" + QString::number(i)); + newPool->moveToThread(newThread); + newDatabaseInterface->moveToThread(newThread); + server->addDatabaseInterface(newThread, newDatabaseInterface); + + newThread->start(); + QMetaObject::invokeMethod(newDatabaseInterface, "initDatabase", Qt::BlockingQueuedConnection, Q_ARG(QSqlDatabase, _sqlDatabase)); + + connectionPools.append(newPool); + } +} + +Servatrice_GameServer::~Servatrice_GameServer() +{ + for (int i = 0; i < connectionPools.size(); ++i) { + logger->logMessage(QString("Closing pool %1...").arg(i)); + QThread *poolThread = connectionPools[i]->thread(); + connectionPools[i]->deleteLater(); // pool destructor calls thread()->quit() + poolThread->wait(); + } +} + +#if QT_VERSION < 0x050000 +void Servatrice_GameServer::incomingConnection(int socketDescriptor) +#else +void Servatrice_GameServer::incomingConnection(qintptr socketDescriptor) +#endif +{ + // Determine connection pool with smallest client count + int minClientCount = -1; + int poolIndex = -1; + QStringList debugStr; + for (int i = 0; i < connectionPools.size(); ++i) { + const int clientCount = connectionPools[i]->getClientCount(); + if ((poolIndex == -1) || (clientCount < minClientCount)) { + minClientCount = clientCount; + poolIndex = i; + } + debugStr.append(QString::number(clientCount)); + } + qDebug() << "Pool utilisation:" << debugStr; + Servatrice_ConnectionPool *pool = connectionPools[poolIndex]; + + ServerSocketInterface *ssi = new ServerSocketInterface(server, pool->getDatabaseInterface()); + ssi->moveToThread(pool->thread()); + pool->addClient(); + connect(ssi, SIGNAL(destroyed()), pool, SLOT(removeClient())); + + QMetaObject::invokeMethod(ssi, "initConnection", Qt::QueuedConnection, Q_ARG(int, socketDescriptor)); +} + +void Servatrice_IslServer::incomingConnection(int socketDescriptor) +{ + QThread *thread = new QThread; + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + IslInterface *interface = new IslInterface(socketDescriptor, cert, privateKey, server); + interface->moveToThread(thread); + connect(interface, SIGNAL(destroyed()), thread, SLOT(quit())); + + thread->start(); + QMetaObject::invokeMethod(interface, "initServer", Qt::QueuedConnection); } Servatrice::Servatrice(QSettings *_settings, QObject *parent) - : Server(parent), dbMutex(QMutex::Recursive), settings(_settings), uptime(0), shutdownTimer(0) + : Server(true, parent), settings(_settings), uptime(0), shutdownTimer(0) { + qRegisterMetaType("QSqlDatabase"); +} + +Servatrice::~Servatrice() +{ + gameServer->close(); + prepareDestroy(); +} + +bool Servatrice::initServer() +{ + serverName = settings->value("server/name").toString(); + serverId = settings->value("server/id", 0).toInt(); + bool regServerOnly = settings->value("server/regonly", 0).toBool(); + + const QString authenticationMethodStr = settings->value("authentication/method").toString(); + if (authenticationMethodStr == "sql") { + authenticationMethod = AuthenticationSql; + } else { + if (regServerOnly) { + qDebug() << "Registration only server enabled but no DB Connection : Error."; + return false; + } + authenticationMethod = AuthenticationNone; + } + + QString dbTypeStr = settings->value("database/type").toString(); + if (dbTypeStr == "mysql") + databaseType = DatabaseMySql; + else + databaseType = DatabaseNone; + + servatriceDatabaseInterface = new Servatrice_DatabaseInterface(-1, this); + setDatabaseInterface(servatriceDatabaseInterface); + + if (databaseType != DatabaseNone) { + settings->beginGroup("database"); + dbPrefix = settings->value("prefix").toString(); + servatriceDatabaseInterface->initDatabase("QMYSQL", + settings->value("hostname").toString(), + settings->value("database").toString(), + settings->value("user").toString(), + settings->value("password").toString()); + settings->endGroup(); + + updateServerList(); + + qDebug() << "Clearing previous sessions..."; + servatriceDatabaseInterface->clearSessionTables(); + } + + const QString roomMethod = settings->value("rooms/method").toString(); + if (roomMethod == "sql") { + QSqlQuery query(servatriceDatabaseInterface->getDatabase()); + query.prepare("select id, name, descr, auto_join, join_message from " + dbPrefix + "_rooms order by id asc"); + servatriceDatabaseInterface->execSqlQuery(query); + while (query.next()) { + QSqlQuery query2(servatriceDatabaseInterface->getDatabase()); + query2.prepare("select name from " + dbPrefix + "_rooms_gametypes where id_room = :id_room"); + query2.bindValue(":id_room", query.value(0).toInt()); + servatriceDatabaseInterface->execSqlQuery(query2); + QStringList gameTypes; + while (query2.next()) + gameTypes.append(query2.value(0).toString()); + + addRoom(new Server_Room(query.value(0).toInt(), + query.value(1).toString(), + query.value(2).toString(), + query.value(3).toInt(), + query.value(4).toString(), + gameTypes, + this + )); + } + } else { + int size = settings->beginReadArray("rooms/roomlist"); + for (int i = 0; i < size; ++i) { + settings->setArrayIndex(i); + + QStringList gameTypes; + int size2 = settings->beginReadArray("game_types"); + for (int j = 0; j < size2; ++j) { + settings->setArrayIndex(j); + gameTypes.append(settings->value("name").toString()); + } + settings->endArray(); + + Server_Room *newRoom = new Server_Room( + i, + settings->value("name").toString(), + settings->value("description").toString(), + settings->value("autojoin").toBool(), + settings->value("joinmessage").toString(), + gameTypes, + this + ); + addRoom(newRoom); + } + settings->endArray(); + } + + updateLoginMessage(); + + maxGameInactivityTime = settings->value("game/max_game_inactivity_time").toInt(); + maxPlayerInactivityTime = settings->value("game/max_player_inactivity_time").toInt(); + + maxUsersPerAddress = settings->value("security/max_users_per_address").toInt(); + messageCountingInterval = settings->value("security/message_counting_interval").toInt(); + maxMessageCountPerInterval = settings->value("security/max_message_count_per_interval").toInt(); + maxMessageSizePerInterval = settings->value("security/max_message_size_per_interval").toInt(); + maxGamesPerUser = settings->value("security/max_games_per_user").toInt(); + + try { if (settings->value("servernetwork/active", 0).toInt()) { + qDebug() << "Connecting to ISL network."; + const QString certFileName = settings->value("servernetwork/ssl_cert").toString(); + const QString keyFileName = settings->value("servernetwork/ssl_key").toString(); + qDebug() << "Loading certificate..."; + QFile certFile(certFileName); + if (!certFile.open(QIODevice::ReadOnly)) + throw QString("Error opening certificate file: %1").arg(certFileName); + QSslCertificate cert(&certFile); +#if QT_VERSION < 0x050000 + if (!cert.isValid()) + throw(QString("Invalid certificate.")); +#else + const QDateTime currentTime = QDateTime::currentDateTime(); + if(currentTime < cert.effectiveDate() || + currentTime > cert.expiryDate() || + cert.isBlacklisted()) + throw(QString("Invalid certificate.")); +#endif + qDebug() << "Loading private key..."; + QFile keyFile(keyFileName); + if (!keyFile.open(QIODevice::ReadOnly)) + throw QString("Error opening private key file: %1").arg(keyFileName); + QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + if (key.isNull()) + throw QString("Invalid private key."); + + QMutableListIterator serverIterator(serverList); + while (serverIterator.hasNext()) { + const ServerProperties &prop = serverIterator.next(); + if (prop.cert == cert) { + serverIterator.remove(); + continue; + } + + QThread *thread = new QThread; + thread->setObjectName("isl_" + QString::number(prop.id)); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + IslInterface *interface = new IslInterface(prop.id, prop.hostname, prop.address.toString(), prop.controlPort, prop.cert, cert, key, this); + interface->moveToThread(thread); + connect(interface, SIGNAL(destroyed()), thread, SLOT(quit())); + + thread->start(); + QMetaObject::invokeMethod(interface, "initClient", Qt::BlockingQueuedConnection); + } + + const int networkPort = settings->value("servernetwork/port", 14747).toInt(); + qDebug() << "Starting ISL server on port" << networkPort; + + islServer = new Servatrice_IslServer(this, cert, key, this); + if (islServer->listen(QHostAddress::Any, networkPort)) + qDebug() << "ISL server listening."; + else + throw QString("islServer->listen()"); + } } catch (QString error) { + qDebug() << "ERROR --" << error; + return false; + } + pingClock = new QTimer(this); connect(pingClock, SIGNAL(timeout()), this, SIGNAL(pingClockTimeout())); pingClock->start(1000); - ProtocolItem::initializeHash(); - - serverId = settings->value("server/id", 0).toInt(); int statusUpdateTime = settings->value("server/statusupdate").toInt(); statusUpdateClock = new QTimer(this); connect(statusUpdateClock, SIGNAL(timeout()), this, SLOT(statusUpdate())); @@ -61,424 +307,224 @@ Servatrice::Servatrice(QSettings *_settings, QObject *parent) statusUpdateClock->start(statusUpdateTime); } - threaded = settings->value("server/threaded", false).toInt(); - tcpServer = new Servatrice_TcpServer(this, threaded, this); - int port = settings->value("server/port", 4747).toInt(); - qDebug() << "Starting server on port" << port; - if (tcpServer->listen(QHostAddress::Any, port)) + const int numberPools = settings->value("server/number_pools", 1).toInt(); + gameServer = new Servatrice_GameServer(this, numberPools, servatriceDatabaseInterface->getDatabase(), this); + gameServer->setMaxPendingConnections(1000); + const int gamePort = settings->value("server/port", 4747).toInt(); + qDebug() << "Starting server on port" << gamePort; + if (gameServer->listen(QHostAddress::Any, gamePort)) qDebug() << "Server listening."; - else - qDebug() << "tcpServer->listen(): Error."; - - QString dbType = settings->value("database/type").toString(); - dbPrefix = settings->value("database/prefix").toString(); - if (dbType == "mysql") - openDatabase(); - - int size = settings->beginReadArray("rooms"); - for (int i = 0; i < size; ++i) { - settings->setArrayIndex(i); - - QStringList gameTypes; - int size2 = settings->beginReadArray("game_types"); - for (int j = 0; j < size2; ++j) { - settings->setArrayIndex(j); - gameTypes.append(settings->value("name").toString()); - } - settings->endArray(); - - Server_Room *newRoom = new Server_Room( - i, - settings->value("name").toString(), - settings->value("description").toString(), - settings->value("autojoin").toBool(), - settings->value("joinmessage").toString(), - gameTypes, - this - ); - addRoom(newRoom); - } - settings->endArray(); - - updateLoginMessage(); - - maxGameInactivityTime = settings->value("game/max_game_inactivity_time").toInt(); - maxPlayerInactivityTime = settings->value("game/max_player_inactivity_time").toInt(); - - maxUsersPerAddress = settings->value("security/max_users_per_address").toInt(); - messageCountingInterval = settings->value("security/message_counting_interval").toInt(); - maxMessageCountPerInterval = settings->value("security/max_message_count_per_interval").toInt(); - maxMessageSizePerInterval = settings->value("security/max_message_size_per_interval").toInt(); - maxGamesPerUser = settings->value("security/max_games_per_user").toInt(); -} - -Servatrice::~Servatrice() -{ - prepareDestroy(); - QSqlDatabase::database().close(); -} - -bool Servatrice::openDatabase() -{ - if (!QSqlDatabase::connectionNames().isEmpty()) - QSqlDatabase::removeDatabase(QSqlDatabase::database().connectionNames().at(0)); - - settings->beginGroup("database"); - QSqlDatabase sqldb = QSqlDatabase::addDatabase("QMYSQL"); - sqldb.setHostName(settings->value("hostname").toString()); - sqldb.setDatabaseName(settings->value("database").toString()); - sqldb.setUserName(settings->value("user").toString()); - sqldb.setPassword(settings->value("password").toString()); - settings->endGroup(); - - std::cerr << "Opening database..."; - if (!sqldb.open()) { - std::cerr << "error" << std::endl; + else { + qDebug() << "gameServer->listen(): Error."; return false; } - std::cerr << "OK" << std::endl; - - if (!nextGameId) { - QSqlQuery query; - if (!query.exec("select max(id) from " + dbPrefix + "_games")) - return false; - if (!query.next()) - return false; - nextGameId = query.value(0).toInt() + 1; - qDebug() << "set nextGameId to " << nextGameId; - } return true; } -void Servatrice::checkSql() +void Servatrice::addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface) { - QMutexLocker locker(&dbMutex); - if (!QSqlDatabase::database().exec("select 1").isActive()) - openDatabase(); + databaseInterfaces.insert(thread, databaseInterface); } -bool Servatrice::execSqlQuery(QSqlQuery &query) +void Servatrice::updateServerList() { - if (query.exec()) - return true; - qCritical() << "Database error:" << query.lastError().text(); - return false; -} - -AuthenticationResult Servatrice::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password) -{ - QMutexLocker locker(&dbMutex); - const QString method = settings->value("authentication/method").toString(); - if (method == "none") - return UnknownUser; - else if (method == "sql") { - checkSql(); - - QSqlQuery ipBanQuery; - ipBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))) < 0, b.minutes <=> 0 from " + dbPrefix + "_bans b where b.time_from = (select max(c.time_from) from " + dbPrefix + "_bans c where c.ip_address = :address) and b.ip_address = :address2"); - ipBanQuery.bindValue(":address", static_cast(handler)->getPeerAddress().toString()); - ipBanQuery.bindValue(":address2", static_cast(handler)->getPeerAddress().toString()); - if (!execSqlQuery(ipBanQuery)) { - qDebug("Login denied: SQL error"); - return PasswordWrong; - } - - if (ipBanQuery.next()) - if (ipBanQuery.value(0).toInt() || ipBanQuery.value(1).toInt()) { - qDebug("Login denied: banned by address"); - return PasswordWrong; - } - - QSqlQuery nameBanQuery; - nameBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))) < 0, b.minutes <=> 0 from " + dbPrefix + "_bans b where b.time_from = (select max(c.time_from) from " + dbPrefix + "_bans c where c.user_name = :name2) and b.user_name = :name1"); - nameBanQuery.bindValue(":name1", user); - nameBanQuery.bindValue(":name2", user); - if (!execSqlQuery(nameBanQuery)) { - qDebug("Login denied: SQL error"); - return PasswordWrong; - } - - if (nameBanQuery.next()) - if (nameBanQuery.value(0).toInt() || nameBanQuery.value(1).toInt()) { - qDebug("Login denied: banned by name"); - return PasswordWrong; - } - - QSqlQuery passwordQuery; - passwordQuery.prepare("select password_sha512 from " + dbPrefix + "_users where name = :name and active = 1"); - passwordQuery.bindValue(":name", user); - if (!execSqlQuery(passwordQuery)) { - qDebug("Login denied: SQL error"); - return PasswordWrong; - } - - if (passwordQuery.next()) { - const QString correctPassword = passwordQuery.value(0).toString(); - if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) { - qDebug("Login accepted: password right"); - return PasswordRight; - } else { - qDebug("Login denied: password wrong"); - return PasswordWrong; - } - } else { - qDebug("Login accepted: unknown user"); - return UnknownUser; - } - } else - return UnknownUser; -} - -bool Servatrice::userExists(const QString &user) -{ - QMutexLocker locker(&dbMutex); - const QString method = settings->value("authentication/method").toString(); - if (method == "sql") { - checkSql(); + qDebug() << "Updating server list..."; - QSqlQuery query; - query.prepare("select 1 from " + dbPrefix + "_users where name = :name and active = 1"); - query.bindValue(":name", user); - if (!execSqlQuery(query)) - return false; - return query.next(); - } else return false; + serverListMutex.lock(); + serverList.clear(); + + QSqlQuery query(servatriceDatabaseInterface->getDatabase()); + query.prepare("select id, ssl_cert, hostname, address, game_port, control_port from " + dbPrefix + "_servers order by id asc"); + servatriceDatabaseInterface->execSqlQuery(query); + while (query.next()) { + ServerProperties prop(query.value(0).toInt(), QSslCertificate(query.value(1).toString().toUtf8()), query.value(2).toString(), QHostAddress(query.value(3).toString()), query.value(4).toInt(), query.value(5).toInt()); + serverList.append(prop); + qDebug() << QString("#%1 CERT=%2 NAME=%3 IP=%4:%5 CPORT=%6").arg(prop.id).arg(QString(prop.cert.digest().toHex())).arg(prop.hostname).arg(prop.address.toString()).arg(prop.gamePort).arg(prop.controlPort); + } + + serverListMutex.unlock(); } -ServerInfo_User *Servatrice::evalUserQueryResult(const QSqlQuery &query, bool complete) +QList Servatrice::getServerList() const { - QString name = query.value(0).toString(); - int is_admin = query.value(1).toInt(); - QString realName = query.value(2).toString(); - QString genderStr = query.value(3).toString(); - QString country = query.value(4).toString(); - QByteArray avatarBmp; - if (complete) - avatarBmp = query.value(5).toByteArray(); - - ServerInfo_User::Gender gender; - if (genderStr == "m") - gender = ServerInfo_User::Male; - else if (genderStr == "f") - gender = ServerInfo_User::Female; - else - gender = ServerInfo_User::GenderUnknown; - - int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered; - if (is_admin == 1) - userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator; - else if (is_admin == 2) - userLevel |= ServerInfo_User::IsModerator; - - return new ServerInfo_User( - name, - userLevel, - QString(), - realName, - gender, - country, - avatarBmp - ); -} - -ServerInfo_User *Servatrice::getUserData(const QString &name) -{ - QMutexLocker locker(&dbMutex); - const QString method = settings->value("authentication/method").toString(); - if (method == "sql") { - checkSql(); - - QSqlQuery query; - query.prepare("select name, admin, realname, gender, country, avatar_bmp from " + dbPrefix + "_users where name = :name and active = 1"); - query.bindValue(":name", name); - if (!execSqlQuery(query)) - return new ServerInfo_User(name, ServerInfo_User::IsUser); - - if (query.next()) - return evalUserQueryResult(query, true); - else - return new ServerInfo_User(name, ServerInfo_User::IsUser); - } else - return new ServerInfo_User(name, ServerInfo_User::IsUser); + serverListMutex.lock(); + QList result = serverList; + serverListMutex.unlock(); + + return result; } int Servatrice::getUsersWithAddress(const QHostAddress &address) const { - QMutexLocker locker(&serverMutex); - int result = 0; - for (int i = 0; i < clients.size(); ++i) - if (static_cast(clients[i])->getPeerAddress() == address) - ++result; - return result; + int result = 0; + QReadLocker locker(&clientsLock); + for (int i = 0; i < clients.size(); ++i) + if (static_cast(clients[i])->getPeerAddress() == address) + ++result; + return result; } -int Servatrice::startSession(const QString &userName, const QString &address) +QList Servatrice::getUsersWithAddressAsList(const QHostAddress &address) const { - QMutexLocker locker(&dbMutex); - checkSql(); - - QSqlQuery query; - query.prepare("insert into " + dbPrefix + "_sessions (user_name, ip_address, start_time) values(:user_name, :ip_address, NOW())"); - query.bindValue(":user_name", userName); - query.bindValue(":ip_address", address); - if (execSqlQuery(query)) - return query.lastInsertId().toInt(); - return -1; -} - -void Servatrice::endSession(int sessionId) -{ - QMutexLocker locker(&dbMutex); - checkSql(); - - QSqlQuery query; - query.prepare("update " + dbPrefix + "_sessions set end_time=NOW() where id = :id_session"); - query.bindValue(":id_session", sessionId); - execSqlQuery(query); -} - -QMap Servatrice::getBuddyList(const QString &name) -{ - QMutexLocker locker(&dbMutex); - QMap result; - - const QString method = settings->value("authentication/method").toString(); - if (method == "sql") { - checkSql(); - - QSqlQuery query; - query.prepare("select a.name, a.admin, a.realname, a.gender, a.country from " + dbPrefix + "_users a left join " + dbPrefix + "_buddylist b on a.id = b.id_user2 left join " + dbPrefix + "_users c on b.id_user1 = c.id where c.name = :name"); - query.bindValue(":name", name); - if (!execSqlQuery(query)) - return result; - - while (query.next()) { - ServerInfo_User *temp = evalUserQueryResult(query, false); - result.insert(temp->getName(), temp); - } - } - return result; -} - -QMap Servatrice::getIgnoreList(const QString &name) -{ - QMutexLocker locker(&dbMutex); - QMap result; - - const QString method = settings->value("authentication/method").toString(); - if (method == "sql") { - checkSql(); - - QSqlQuery query; - query.prepare("select a.name, a.admin, a.realname, a.gender, a.country from " + dbPrefix + "_users a left join " + dbPrefix + "_ignorelist b on a.id = b.id_user2 left join " + dbPrefix + "_users c on b.id_user1 = c.id where c.name = :name"); - query.bindValue(":name", name); - if (!execSqlQuery(query)) - return result; - - while (query.next()) { - ServerInfo_User *temp = evalUserQueryResult(query, false); - result.insert(temp->getName(), temp); - } - } - return result; + QList result; + QReadLocker locker(&clientsLock); + for (int i = 0; i < clients.size(); ++i) + if (static_cast(clients[i])->getPeerAddress() == address) + result.append(static_cast(clients[i])); + return result; } void Servatrice::updateLoginMessage() { - QMutexLocker locker(&dbMutex); - checkSql(); - QSqlQuery query; - query.prepare("select message from " + dbPrefix + "_servermessages where id_server = :id_server order by timest desc limit 1"); - query.bindValue(":id_server", serverId); - if (execSqlQuery(query)) - if (query.next()) { - loginMessage = query.value(0).toString(); - - Event_ServerMessage *event = new Event_ServerMessage(loginMessage); - QMapIterator usersIterator(users); - while (usersIterator.hasNext()) { - usersIterator.next().value()->sendProtocolItem(event, false); - } - delete event; - } + if (!servatriceDatabaseInterface->checkSql()) + return; + + QSqlQuery query(servatriceDatabaseInterface->getDatabase()); + query.prepare("select message from " + dbPrefix + "_servermessages where id_server = :id_server order by timest desc limit 1"); + query.bindValue(":id_server", serverId); + if (servatriceDatabaseInterface->execSqlQuery(query)) + if (query.next()) { + const QString newLoginMessage = query.value(0).toString(); + + loginMessageMutex.lock(); + loginMessage = newLoginMessage; + loginMessageMutex.unlock(); + + Event_ServerMessage event; + event.set_message(newLoginMessage.toStdString()); + SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); + QMapIterator usersIterator(users); + while (usersIterator.hasNext()) + usersIterator.next().value()->sendProtocolItem(*se); + delete se; + } } void Servatrice::statusUpdate() { - const int uc = getUsersCount(); // for correct mutex locking order - const int gc = getGamesCount(); - - uptime += statusUpdateClock->interval() / 1000; - - txBytesMutex.lock(); - quint64 tx = txBytes; - txBytes = 0; - txBytesMutex.unlock(); - rxBytesMutex.lock(); - quint64 rx = rxBytes; - rxBytes = 0; - rxBytesMutex.unlock(); - - QMutexLocker locker(&dbMutex); - checkSql(); - - QSqlQuery query; - query.prepare("insert into " + dbPrefix + "_uptime (id_server, timest, uptime, users_count, games_count, tx_bytes, rx_bytes) values(:id, NOW(), :uptime, :users_count, :games_count, :tx, :rx)"); - query.bindValue(":id", serverId); - query.bindValue(":uptime", uptime); - query.bindValue(":users_count", uc); - query.bindValue(":games_count", gc); - query.bindValue(":tx", tx); - query.bindValue(":rx", rx); - execSqlQuery(query); + if (!servatriceDatabaseInterface->checkSql()) + return; + + const int uc = getUsersCount(); // for correct mutex locking order + const int gc = getGamesCount(); + + uptime += statusUpdateClock->interval() / 1000; + + txBytesMutex.lock(); + quint64 tx = txBytes; + txBytes = 0; + txBytesMutex.unlock(); + rxBytesMutex.lock(); + quint64 rx = rxBytes; + rxBytes = 0; + rxBytesMutex.unlock(); + + QSqlQuery query(servatriceDatabaseInterface->getDatabase()); + query.prepare("insert into " + dbPrefix + "_uptime (id_server, timest, uptime, users_count, games_count, tx_bytes, rx_bytes) values(:id, NOW(), :uptime, :users_count, :games_count, :tx, :rx)"); + query.bindValue(":id", serverId); + query.bindValue(":uptime", uptime); + query.bindValue(":users_count", uc); + query.bindValue(":games_count", gc); + query.bindValue(":tx", tx); + query.bindValue(":rx", rx); + servatriceDatabaseInterface->execSqlQuery(query); } void Servatrice::scheduleShutdown(const QString &reason, int minutes) { - QMutexLocker locker(&serverMutex); - - shutdownReason = reason; - shutdownMinutes = minutes + 1; - if (minutes > 0) { - shutdownTimer = new QTimer; - connect(shutdownTimer, SIGNAL(timeout()), this, SLOT(shutdownTimeout())); - shutdownTimer->start(60000); - } - shutdownTimeout(); + shutdownReason = reason; + shutdownMinutes = minutes + 1; + if (minutes > 0) { + shutdownTimer = new QTimer; + connect(shutdownTimer, SIGNAL(timeout()), this, SLOT(shutdownTimeout())); + shutdownTimer->start(60000); + } + shutdownTimeout(); } void Servatrice::incTxBytes(quint64 num) { - txBytesMutex.lock(); - txBytes += num; - txBytesMutex.unlock(); + txBytesMutex.lock(); + txBytes += num; + txBytesMutex.unlock(); } void Servatrice::incRxBytes(quint64 num) { - rxBytesMutex.lock(); - rxBytes += num; - rxBytesMutex.unlock(); + rxBytesMutex.lock(); + rxBytes += num; + rxBytesMutex.unlock(); } void Servatrice::shutdownTimeout() { - QMutexLocker locker(&serverMutex); - - --shutdownMinutes; - - GenericEvent *event; - if (shutdownMinutes) - event = new Event_ServerShutdown(shutdownReason, shutdownMinutes); - else - event = new Event_ConnectionClosed("server_shutdown"); - - for (int i = 0; i < clients.size(); ++i) - clients[i]->sendProtocolItem(event, false); - delete event; - - if (!shutdownMinutes) - deleteLater(); + --shutdownMinutes; + + SessionEvent *se; + if (shutdownMinutes) { + Event_ServerShutdown event; + event.set_reason(shutdownReason.toStdString()); + event.set_minutes(shutdownMinutes); + se = Server_ProtocolHandler::prepareSessionEvent(event); + } else { + Event_ConnectionClosed event; + event.set_reason(Event_ConnectionClosed::SERVER_SHUTDOWN); + se = Server_ProtocolHandler::prepareSessionEvent(event); + } + + clientsLock.lockForRead(); + for (int i = 0; i < clients.size(); ++i) + clients[i]->sendProtocolItem(*se); + clientsLock.unlock(); + delete se; + + if (!shutdownMinutes) + deleteLater(); } -const QString Servatrice::versionString = "Servatrice 0.20111113"; +bool Servatrice::islConnectionExists(int serverId) const +{ + // Only call with islLock locked at least for reading + + return islInterfaces.contains(serverId); +} + +void Servatrice::addIslInterface(int serverId, IslInterface *interface) +{ + // Only call with islLock locked for writing + + islInterfaces.insert(serverId, interface); + connect(interface, SIGNAL(externalUserJoined(ServerInfo_User)), this, SLOT(externalUserJoined(ServerInfo_User))); + connect(interface, SIGNAL(externalUserLeft(QString)), this, SLOT(externalUserLeft(QString))); + connect(interface, SIGNAL(externalRoomUserJoined(int, ServerInfo_User)), this, SLOT(externalRoomUserJoined(int, ServerInfo_User))); + connect(interface, SIGNAL(externalRoomUserLeft(int, QString)), this, SLOT(externalRoomUserLeft(int, QString))); + connect(interface, SIGNAL(externalRoomSay(int, QString, QString)), this, SLOT(externalRoomSay(int, QString, QString))); + connect(interface, SIGNAL(externalRoomGameListChanged(int, ServerInfo_Game)), this, SLOT(externalRoomGameListChanged(int, ServerInfo_Game))); + connect(interface, SIGNAL(joinGameCommandReceived(Command_JoinGame, int, int, int, qint64)), this, SLOT(externalJoinGameCommandReceived(Command_JoinGame, int, int, int, qint64))); + connect(interface, SIGNAL(gameCommandContainerReceived(CommandContainer, int, int, qint64)), this, SLOT(externalGameCommandContainerReceived(CommandContainer, int, int, qint64))); + connect(interface, SIGNAL(responseReceived(Response, qint64)), this, SLOT(externalResponseReceived(Response, qint64))); + connect(interface, SIGNAL(gameEventContainerReceived(GameEventContainer, qint64)), this, SLOT(externalGameEventContainerReceived(GameEventContainer, qint64))); +} + +void Servatrice::removeIslInterface(int serverId) +{ + // Only call with islLock locked for writing + + // XXX we probably need to delete everything that belonged to it... + islInterfaces.remove(serverId); +} + +void Servatrice::doSendIslMessage(const IslMessage &msg, int serverId) +{ + QReadLocker locker(&islLock); + + if (serverId == -1) { + QMapIterator islIterator(islInterfaces); + while (islIterator.hasNext()) + islIterator.next().value()->transmitMessage(msg); + } else { + IslInterface *interface = islInterfaces.value(serverId); + if (interface) + interface->transmitMessage(msg); + } +} diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index e22aa01cc..2b991512f 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -22,43 +22,117 @@ #include #include +#include +#include +#include +#include +#include +#include #include "server.h" -class QSqlDatabase; +Q_DECLARE_METATYPE(QSqlDatabase) + class QSettings; class QSqlQuery; class QTimer; +class GameReplay; class Servatrice; +class Servatrice_ConnectionPool; +class Servatrice_DatabaseInterface; class ServerSocketInterface; +class IslInterface; -class Servatrice_TcpServer : public QTcpServer { +class Servatrice_GameServer : public QTcpServer { Q_OBJECT private: Servatrice *server; - bool threaded; + QList connectionPools; public: - Servatrice_TcpServer(Servatrice *_server, bool _threaded, QObject *parent = 0) - : QTcpServer(parent), server(_server), threaded(_threaded) { } + Servatrice_GameServer(Servatrice *_server, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent = 0); + ~Servatrice_GameServer(); +protected: +#if QT_VERSION < 0x050000 + void incomingConnection(int socketDescriptor); +#else + void incomingConnection(qintptr socketDescriptor); +#endif +}; + +class Servatrice_IslServer : public QTcpServer { + Q_OBJECT +private: + Servatrice *server; + QSslCertificate cert; + QSslKey privateKey; +public: + Servatrice_IslServer(Servatrice *_server, const QSslCertificate &_cert, const QSslKey &_privateKey, QObject *parent = 0) + : QTcpServer(parent), server(_server), cert(_cert), privateKey(_privateKey) { } protected: void incomingConnection(int socketDescriptor); }; +class ServerProperties { +public: + int id; + QSslCertificate cert; + QString hostname; + QHostAddress address; + int gamePort; + int controlPort; + + ServerProperties(int _id, const QSslCertificate &_cert, const QString &_hostname, const QHostAddress &_address, int _gamePort, int _controlPort) + : id(_id), cert(_cert), hostname(_hostname), address(_address), gamePort(_gamePort), controlPort(_controlPort) { } +}; + class Servatrice : public Server { Q_OBJECT +public: + enum AuthenticationMethod { AuthenticationNone, AuthenticationSql }; private slots: void statusUpdate(); void shutdownTimeout(); +protected: + void doSendIslMessage(const IslMessage &msg, int serverId); +private: + enum DatabaseType { DatabaseNone, DatabaseMySql }; + AuthenticationMethod authenticationMethod; + DatabaseType databaseType; + QTimer *pingClock, *statusUpdateClock; + Servatrice_GameServer *gameServer; + Servatrice_IslServer *islServer; + QString serverName; + mutable QMutex loginMessageMutex; + QString loginMessage; + QString dbPrefix; + QSettings *settings; + Servatrice_DatabaseInterface *servatriceDatabaseInterface; + int serverId; + int uptime; + QMutex txBytesMutex, rxBytesMutex; + quint64 txBytes, rxBytes; + int maxGameInactivityTime, maxPlayerInactivityTime; + int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser; + + QString shutdownReason; + int shutdownMinutes; + QTimer *shutdownTimer; + + mutable QMutex serverListMutex; + QList serverList; + void updateServerList(); + + QMap islInterfaces; +public slots: + void scheduleShutdown(const QString &reason, int minutes); + void updateLoginMessage(); public: - QMutex dbMutex; - static const QString versionString; Servatrice(QSettings *_settings, QObject *parent = 0); ~Servatrice(); - bool openDatabase(); - void checkSql(); - bool execSqlQuery(QSqlQuery &query); - QString getLoginMessage() const { return loginMessage; } + bool initServer(); + QString getServerName() const { return serverName; } + QString getLoginMessage() const { QMutexLocker locker(&loginMessageMutex); return loginMessage; } bool getGameShouldPing() const { return true; } int getMaxGameInactivityTime() const { return maxGameInactivityTime; } int getMaxPlayerInactivityTime() const { return maxPlayerInactivityTime; } @@ -67,39 +141,21 @@ public: int getMaxMessageCountPerInterval() const { return maxMessageCountPerInterval; } int getMaxMessageSizePerInterval() const { return maxMessageSizePerInterval; } int getMaxGamesPerUser() const { return maxGamesPerUser; } - bool getThreaded() const { return threaded; } + AuthenticationMethod getAuthenticationMethod() const { return authenticationMethod; } QString getDbPrefix() const { return dbPrefix; } - void updateLoginMessage(); - ServerInfo_User *getUserData(const QString &name); + int getServerId() const { return serverId; } int getUsersWithAddress(const QHostAddress &address) const; - QMap getBuddyList(const QString &name); - QMap getIgnoreList(const QString &name); - void scheduleShutdown(const QString &reason, int minutes); + QList getUsersWithAddressAsList(const QHostAddress &address) const; void incTxBytes(quint64 num); void incRxBytes(quint64 num); -protected: - int startSession(const QString &userName, const QString &address); - void endSession(int sessionId); - bool userExists(const QString &user); - AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password); -private: - QTimer *pingClock, *statusUpdateClock; - QTcpServer *tcpServer; - QString loginMessage; - QString dbPrefix; - QSettings *settings; - int serverId; - bool threaded; - int uptime; - QMutex txBytesMutex, rxBytesMutex; - quint64 txBytes, rxBytes; - int maxGameInactivityTime, maxPlayerInactivityTime; - int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser; - ServerInfo_User *evalUserQueryResult(const QSqlQuery &query, bool complete); + void addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface); - QString shutdownReason; - int shutdownMinutes; - QTimer *shutdownTimer; + bool islConnectionExists(int serverId) const; + void addIslInterface(int serverId, IslInterface *interface); + void removeIslInterface(int serverId); + QReadWriteLock islLock; + + QList getServerList() const; }; #endif diff --git a/servatrice/src/servatrice_connection_pool.cpp b/servatrice/src/servatrice_connection_pool.cpp new file mode 100644 index 000000000..a2f849be4 --- /dev/null +++ b/servatrice/src/servatrice_connection_pool.cpp @@ -0,0 +1,15 @@ +#include "servatrice_connection_pool.h" +#include "servatrice_database_interface.h" +#include + +Servatrice_ConnectionPool::Servatrice_ConnectionPool(Servatrice_DatabaseInterface *_databaseInterface) + : databaseInterface(_databaseInterface), + clientCount(0) +{ +} + +Servatrice_ConnectionPool::~Servatrice_ConnectionPool() +{ + delete databaseInterface; + thread()->quit(); +} diff --git a/servatrice/src/servatrice_connection_pool.h b/servatrice/src/servatrice_connection_pool.h new file mode 100644 index 000000000..f1bbc26f7 --- /dev/null +++ b/servatrice/src/servatrice_connection_pool.h @@ -0,0 +1,29 @@ +#ifndef SERVATRICE_CONNECTION_POOL_H +#define SERVATRICE_CONNECTION_POOL_H + +#include +#include +#include + +class Servatrice_DatabaseInterface; + +class Servatrice_ConnectionPool : public QObject { + Q_OBJECT +private: + Servatrice_DatabaseInterface *databaseInterface; + bool threaded; + mutable QMutex clientCountMutex; + int clientCount; +public: + Servatrice_ConnectionPool(Servatrice_DatabaseInterface *_databaseInterface); + ~Servatrice_ConnectionPool(); + + Servatrice_DatabaseInterface *getDatabaseInterface() const { return databaseInterface; } + + int getClientCount() const { QMutexLocker locker(&clientCountMutex); return clientCount; } + void addClient() { QMutexLocker locker(&clientCountMutex); ++clientCount; } +public slots: + void removeClient() { QMutexLocker locker(&clientCountMutex); --clientCount; } +}; + +#endif diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp new file mode 100644 index 000000000..58856f4fc --- /dev/null +++ b/servatrice/src/servatrice_database_interface.cpp @@ -0,0 +1,527 @@ +#include "servatrice.h" +#include "servatrice_database_interface.h" +#include "passwordhasher.h" +#include "serversocketinterface.h" +#include "decklist.h" +#include "pb/game_replay.pb.h" +#include +#include +#include + +Servatrice_DatabaseInterface::Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server) + : instanceId(_instanceId), + sqlDatabase(QSqlDatabase()), + server(_server) +{ +} + +Servatrice_DatabaseInterface::~Servatrice_DatabaseInterface() +{ + sqlDatabase.close(); +} + +void Servatrice_DatabaseInterface::initDatabase(const QSqlDatabase &_sqlDatabase) +{ + if (_sqlDatabase.isValid()) { + sqlDatabase = QSqlDatabase::cloneDatabase(_sqlDatabase, "pool_" + QString::number(instanceId)); + openDatabase(); + } +} + +void Servatrice_DatabaseInterface::initDatabase(const QString &type, const QString &hostName, const QString &databaseName, const QString &userName, const QString &password) +{ + sqlDatabase = QSqlDatabase::addDatabase(type, "main"); + sqlDatabase.setHostName(hostName); + sqlDatabase.setDatabaseName(databaseName); + sqlDatabase.setUserName(userName); + sqlDatabase.setPassword(password); + + openDatabase(); +} + +bool Servatrice_DatabaseInterface::openDatabase() +{ + if (sqlDatabase.isOpen()) + sqlDatabase.close(); + + const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); + qDebug() << QString("[%1] Opening database...").arg(poolStr); + if (!sqlDatabase.open()) { + qCritical() << QString("[%1] Error opening database: %2").arg(poolStr).arg(sqlDatabase.lastError().text()); + return false; + } + + return true; +} + +bool Servatrice_DatabaseInterface::checkSql() +{ + if (!sqlDatabase.isValid()) + return false; + + if (!sqlDatabase.exec("select 1").isActive()) + return openDatabase(); + return true; +} + +bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery &query) +{ + if (query.exec()) + return true; + const QString poolStr = instanceId == -1 ? QString("main") : QString("pool %1").arg(instanceId); + qCritical() << QString("[%1] Error executing query: %2").arg(poolStr).arg(query.lastError().text()); + return false; +} + +bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user) +{ + QString result; + result.reserve(user.size()); + foreach (const QChar& c, user) { + switch (c.category()) { + // TODO: Figure out exactly which categories are OK and not + case QChar::Other_Control: break; + default: result += c; + } + } + result = result.trimmed(); + return (result.size() > 0); +} + +AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) +{ + switch (server->getAuthenticationMethod()) { + case Servatrice::AuthenticationNone: return UnknownUser; + case Servatrice::AuthenticationSql: { + if (!checkSql()) + return UnknownUser; + + if (!usernameIsValid(user)) + return UsernameInvalid; + + QSqlQuery ipBanQuery(sqlDatabase); + ipBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + server->getDbPrefix() + "_bans b where b.time_from = (select max(c.time_from) from " + server->getDbPrefix() + "_bans c where c.ip_address = :address) and b.ip_address = :address2"); + ipBanQuery.bindValue(":address", static_cast(handler)->getPeerAddress().toString()); + ipBanQuery.bindValue(":address2", static_cast(handler)->getPeerAddress().toString()); + if (!execSqlQuery(ipBanQuery)) { + qDebug("Login denied: SQL error"); + return NotLoggedIn; + } + + if (ipBanQuery.next()) { + const int secondsLeft = -ipBanQuery.value(0).toInt(); + const bool permanentBan = ipBanQuery.value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + reasonStr = ipBanQuery.value(2).toString(); + banSecondsLeft = permanentBan ? 0 : secondsLeft; + qDebug("Login denied: banned by address"); + return UserIsBanned; + } + } + + QSqlQuery nameBanQuery(sqlDatabase); + nameBanQuery.prepare("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from " + server->getDbPrefix() + "_bans b where b.time_from = (select max(c.time_from) from " + server->getDbPrefix() + "_bans c where c.user_name = :name2) and b.user_name = :name1"); + nameBanQuery.bindValue(":name1", user); + nameBanQuery.bindValue(":name2", user); + if (!execSqlQuery(nameBanQuery)) { + qDebug("Login denied: SQL error"); + return NotLoggedIn; + } + + if (nameBanQuery.next()) { + const int secondsLeft = -nameBanQuery.value(0).toInt(); + const bool permanentBan = nameBanQuery.value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + reasonStr = nameBanQuery.value(2).toString(); + banSecondsLeft = permanentBan ? 0 : secondsLeft; + qDebug("Login denied: banned by name"); + return UserIsBanned; + } + } + + QSqlQuery passwordQuery(sqlDatabase); + passwordQuery.prepare("select password_sha512 from " + server->getDbPrefix() + "_users where name = :name and active = 1"); + passwordQuery.bindValue(":name", user); + if (!execSqlQuery(passwordQuery)) { + qDebug("Login denied: SQL error"); + return NotLoggedIn; + } + + if (passwordQuery.next()) { + const QString correctPassword = passwordQuery.value(0).toString(); + if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) { + qDebug("Login accepted: password right"); + return PasswordRight; + } else { + qDebug("Login denied: password wrong"); + return NotLoggedIn; + } + } else { + qDebug("Login accepted: unknown user"); + return UnknownUser; + } + } + } + return UnknownUser; +} + +bool Servatrice_DatabaseInterface::userExists(const QString &user) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + checkSql(); + + QSqlQuery query(sqlDatabase); + query.prepare("select 1 from " + server->getDbPrefix() + "_users where name = :name and active = 1"); + query.bindValue(":name", user); + if (!execSqlQuery(query)) + return false; + return query.next(); + } + return false; +} + +int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + QSqlQuery query(sqlDatabase); + query.prepare("select id from " + server->getDbPrefix() + "_users where name = :name and active = 1"); + query.bindValue(":name", name); + if (!execSqlQuery(query)) + return -1; + if (!query.next()) + return -1; + return query.value(0).toInt(); + } + return -1; +} + +bool Servatrice_DatabaseInterface::isInBuddyList(const QString &whoseList, const QString &who) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + return false; + + if (!checkSql()) + return false; + + int id1 = getUserIdInDB(whoseList); + int id2 = getUserIdInDB(who); + + QSqlQuery query(sqlDatabase); + query.prepare("select 1 from " + server->getDbPrefix() + "_buddylist where id_user1 = :id_user1 and id_user2 = :id_user2"); + query.bindValue(":id_user1", id1); + query.bindValue(":id_user2", id2); + if (!execSqlQuery(query)) + return false; + return query.next(); +} + +bool Servatrice_DatabaseInterface::isInIgnoreList(const QString &whoseList, const QString &who) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + return false; + + if (!checkSql()) + return false; + + int id1 = getUserIdInDB(whoseList); + int id2 = getUserIdInDB(who); + + QSqlQuery query(sqlDatabase); + query.prepare("select 1 from " + server->getDbPrefix() + "_ignorelist where id_user1 = :id_user1 and id_user2 = :id_user2"); + query.bindValue(":id_user1", id1); + query.bindValue(":id_user2", id2); + if (!execSqlQuery(query)) + return false; + return query.next(); +} + +ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId) +{ + ServerInfo_User result; + + if (withId) + result.set_id(query.value(0).toInt()); + result.set_name(query.value(1).toString().toStdString()); + + const QString country = query.value(5).toString(); + if (!country.isEmpty()) + result.set_country(country.toStdString()); + + if (complete) { + const QByteArray avatarBmp = query.value(6).toByteArray(); + if (avatarBmp.size()) + result.set_avatar_bmp(avatarBmp.data(), avatarBmp.size()); + } + + const QString genderStr = query.value(4).toString(); + if (genderStr == "m") + result.set_gender(ServerInfo_User::Male); + else if (genderStr == "f") + result.set_gender(ServerInfo_User::Female); + + const int is_admin = query.value(2).toInt(); + int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered; + if (is_admin == 1) + userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator; + else if (is_admin == 2) + userLevel |= ServerInfo_User::IsModerator; + result.set_user_level(userLevel); + + const QString realName = query.value(3).toString(); + if (!realName.isEmpty()) + result.set_real_name(realName.toStdString()); + + return result; +} + +ServerInfo_User Servatrice_DatabaseInterface::getUserData(const QString &name, bool withId) +{ + ServerInfo_User result; + result.set_name(name.toStdString()); + result.set_user_level(ServerInfo_User::IsUser); + + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + if (!checkSql()) + return result; + + QSqlQuery query(sqlDatabase); + query.prepare("select id, name, admin, realname, gender, country, avatar_bmp from " + server->getDbPrefix() + "_users where name = :name and active = 1"); + query.bindValue(":name", name); + if (!execSqlQuery(query)) + return result; + + if (query.next()) + return evalUserQueryResult(query, true, withId); + else + return result; + } else + return result; +} + +void Servatrice_DatabaseInterface::clearSessionTables() +{ + lockSessionTables(); + QSqlQuery query(sqlDatabase); + query.prepare("update " + server->getDbPrefix() + "_sessions set end_time=now() where end_time is null and id_server = :id_server"); + query.bindValue(":id_server", server->getServerId()); + query.exec(); + unlockSessionTables(); +} + +void Servatrice_DatabaseInterface::lockSessionTables() +{ + QSqlQuery("lock tables " + server->getDbPrefix() + "_sessions write, " + server->getDbPrefix() + "_users read", sqlDatabase).exec(); +} + +void Servatrice_DatabaseInterface::unlockSessionTables() +{ + QSqlQuery("unlock tables", sqlDatabase).exec(); +} + +bool Servatrice_DatabaseInterface::userSessionExists(const QString &userName) +{ + // Call only after lockSessionTables(). + + QSqlQuery query(sqlDatabase); + query.prepare("select 1 from " + server->getDbPrefix() + "_sessions where user_name = :user_name and end_time is null"); + query.bindValue(":user_name", userName); + query.exec(); + return query.next(); +} + +qint64 Servatrice_DatabaseInterface::startSession(const QString &userName, const QString &address) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + return -1; + + if (!checkSql()) + return -1; + + QSqlQuery query(sqlDatabase); + query.prepare("insert into " + server->getDbPrefix() + "_sessions (user_name, id_server, ip_address, start_time) values(:user_name, :id_server, :ip_address, NOW())"); + query.bindValue(":user_name", userName); + query.bindValue(":id_server", server->getServerId()); + query.bindValue(":ip_address", address); + if (execSqlQuery(query)) + return query.lastInsertId().toInt(); + return -1; +} + +void Servatrice_DatabaseInterface::endSession(qint64 sessionId) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationNone) + return; + + if (!checkSql()) + return; + + QSqlQuery query(sqlDatabase); + query.exec("lock tables " + server->getDbPrefix() + "_sessions write"); + query.prepare("update " + server->getDbPrefix() + "_sessions set end_time=NOW() where id = :id_session"); + query.bindValue(":id_session", sessionId); + execSqlQuery(query); + query.exec("unlock tables"); +} + +QMap Servatrice_DatabaseInterface::getBuddyList(const QString &name) +{ + QMap result; + + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + checkSql(); + + QSqlQuery query(sqlDatabase); + query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + server->getDbPrefix() + "_users a left join " + server->getDbPrefix() + "_buddylist b on a.id = b.id_user2 left join " + server->getDbPrefix() + "_users c on b.id_user1 = c.id where c.name = :name"); + query.bindValue(":name", name); + if (!execSqlQuery(query)) + return result; + + while (query.next()) { + const ServerInfo_User &temp = evalUserQueryResult(query, false); + result.insert(QString::fromStdString(temp.name()), temp); + } + } + return result; +} + +QMap Servatrice_DatabaseInterface::getIgnoreList(const QString &name) +{ + QMap result; + + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + checkSql(); + + QSqlQuery query(sqlDatabase); + query.prepare("select a.id, a.name, a.admin, a.realname, a.gender, a.country from " + server->getDbPrefix() + "_users a left join " + server->getDbPrefix() + "_ignorelist b on a.id = b.id_user2 left join " + server->getDbPrefix() + "_users c on b.id_user1 = c.id where c.name = :name"); + query.bindValue(":name", name); + if (!execSqlQuery(query)) + return result; + + while (query.next()) { + ServerInfo_User temp = evalUserQueryResult(query, false); + result.insert(QString::fromStdString(temp.name()), temp); + } + } + return result; +} + +int Servatrice_DatabaseInterface::getNextGameId() +{ + if (!sqlDatabase.isValid()) + return server->getNextLocalGameId(); + + if (!checkSql()) + return -1; + + QSqlQuery query(sqlDatabase); + query.prepare("insert into " + server->getDbPrefix() + "_games (time_started) values (now())"); + execSqlQuery(query); + + return query.lastInsertId().toInt(); +} + +int Servatrice_DatabaseInterface::getNextReplayId() +{ + if (!checkSql()) + return -1; + + QSqlQuery query(sqlDatabase); + query.prepare("insert into " + server->getDbPrefix() + "_replays () values ()"); + execSqlQuery(query); + + return query.lastInsertId().toInt(); +} + +void Servatrice_DatabaseInterface::storeGameInformation(const QString &roomName, const QStringList &roomGameTypes, const ServerInfo_Game &gameInfo, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replayList) +{ + if (!checkSql()) + return; + + QVariantList gameIds1, playerNames, gameIds2, userIds, replayNames; + QSetIterator playerIterator(allPlayersEver); + while (playerIterator.hasNext()) { + gameIds1.append(gameInfo.game_id()); + const QString &playerName = playerIterator.next(); + playerNames.append(playerName); + } + QSet allUsersInGame = allPlayersEver + allSpectatorsEver; + QSetIterator allUsersIterator(allUsersInGame); + while (allUsersIterator.hasNext()) { + int id = getUserIdInDB(allUsersIterator.next()); + if (id == -1) + continue; + gameIds2.append(gameInfo.game_id()); + userIds.append(id); + replayNames.append(QString::fromStdString(gameInfo.description())); + } + + QVariantList replayIds, replayGameIds, replayDurations, replayBlobs; + for (int i = 0; i < replayList.size(); ++i) { + QByteArray blob; + const unsigned int size = replayList[i]->ByteSize(); + blob.resize(size); + replayList[i]->SerializeToArray(blob.data(), size); + + replayIds.append(QVariant((qulonglong) replayList[i]->replay_id())); + replayGameIds.append(gameInfo.game_id()); + replayDurations.append(replayList[i]->duration_seconds()); + replayBlobs.append(blob); + } + + { + QSqlQuery query(sqlDatabase); + query.prepare("update " + server->getDbPrefix() + "_games set room_name=:room_name, descr=:descr, creator_name=:creator_name, password=:password, game_types=:game_types, player_count=:player_count, time_finished=now() where id=:id_game"); + query.bindValue(":room_name", roomName); + query.bindValue(":id_game", gameInfo.game_id()); + query.bindValue(":descr", QString::fromStdString(gameInfo.description())); + query.bindValue(":creator_name", QString::fromStdString(gameInfo.creator_info().name())); + query.bindValue(":password", gameInfo.with_password() ? 1 : 0); + query.bindValue(":game_types", roomGameTypes.isEmpty() ? QString("") : roomGameTypes.join(", ")); + query.bindValue(":player_count", gameInfo.max_players()); + if (!execSqlQuery(query)) + return; + } + { + QSqlQuery query(sqlDatabase); + query.prepare("insert into " + server->getDbPrefix() + "_games_players (id_game, player_name) values (:id_game, :player_name)"); + query.bindValue(":id_game", gameIds1); + query.bindValue(":player_name", playerNames); + query.execBatch(); + } + { + QSqlQuery query(sqlDatabase); + query.prepare("update " + server->getDbPrefix() + "_replays set id_game=:id_game, duration=:duration, replay=:replay where id=:id_replay"); + query.bindValue(":id_replay", replayIds); + query.bindValue(":id_game", replayGameIds); + query.bindValue(":duration", replayDurations); + query.bindValue(":replay", replayBlobs); + query.execBatch(); + } + { + QSqlQuery query(sqlDatabase); + query.prepare("insert into " + server->getDbPrefix() + "_replays_access (id_game, id_player, replay_name) values (:id_game, :id_player, :replay_name)"); + query.bindValue(":id_game", gameIds2); + query.bindValue(":id_player", userIds); + query.bindValue(":replay_name", replayNames); + query.execBatch(); + } +} + +DeckList *Servatrice_DatabaseInterface::getDeckFromDatabase(int deckId, int userId) +{ + checkSql(); + + QSqlQuery query(sqlDatabase); + + query.prepare("select content from " + server->getDbPrefix() + "_decklist_files where id = :id and id_user = :id_user"); + query.bindValue(":id", deckId); + query.bindValue(":id_user", userId); + execSqlQuery(query); + if (!query.next()) + throw Response::RespNameNotFound; + + DeckList *deck = new DeckList; + deck->loadFromString_Native(query.value(0).toString()); + + return deck; +} diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h new file mode 100644 index 000000000..e1d6d813d --- /dev/null +++ b/servatrice/src/servatrice_database_interface.h @@ -0,0 +1,56 @@ +#ifndef SERVATRICE_DATABASE_INTERFACE_H +#define SERVATRICE_DATABASE_INTERFACE_H + +#include +#include + +#include "server.h" +#include "server_database_interface.h" + +class Servatrice; + +class Servatrice_DatabaseInterface : public Server_DatabaseInterface { + Q_OBJECT +private: + int instanceId; + QSqlDatabase sqlDatabase; + Servatrice *server; + ServerInfo_User evalUserQueryResult(const QSqlQuery &query, bool complete, bool withId = false); + bool usernameIsValid(const QString &user); +protected: + AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); +public slots: + void initDatabase(const QSqlDatabase &_sqlDatabase); +public: + Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server); + ~Servatrice_DatabaseInterface(); + void initDatabase(const QString &type, const QString &hostName, const QString &databaseName, const QString &userName, const QString &password); + bool openDatabase(); + bool checkSql(); + bool execSqlQuery(QSqlQuery &query); + const QSqlDatabase &getDatabase() { return sqlDatabase; } + + bool userExists(const QString &user); + int getUserIdInDB(const QString &name); + QMap getBuddyList(const QString &name); + QMap getIgnoreList(const QString &name); + bool isInBuddyList(const QString &whoseList, const QString &who); + bool isInIgnoreList(const QString &whoseList, const QString &who); + ServerInfo_User getUserData(const QString &name, bool withId = false); + void storeGameInformation(const QString &roomName, const QStringList &roomGameTypes, const ServerInfo_Game &gameInfo, const QSet &allPlayersEver, const QSet &allSpectatorsEver, const QList &replayList); + DeckList *getDeckFromDatabase(int deckId, int userId); + + int getNextGameId(); + int getNextReplayId(); + + qint64 startSession(const QString &userName, const QString &address); + void endSession(qint64 sessionId); + + void clearSessionTables(); + void lockSessionTables(); + void unlockSessionTables(); + bool userSessionExists(const QString &userName); + +}; + +#endif diff --git a/servatrice/src/server_logger.cpp b/servatrice/src/server_logger.cpp index ae073a9ca..a3e92affa 100644 --- a/servatrice/src/server_logger.cpp +++ b/servatrice/src/server_logger.cpp @@ -3,127 +3,133 @@ #include #include #include +#include +#include #ifdef Q_OS_UNIX # include # include +# include #endif -ServerLogger::ServerLogger(const QString &logFileName, QObject *parent) - : QObject(parent), flushRunning(false) +ServerLogger::ServerLogger(bool _logToConsole, QObject *parent) + : QObject(parent), logToConsole(_logToConsole), flushRunning(false) { - if (!logFileName.isEmpty()) { - logFile = new QFile("server.log", this); - logFile->open(QIODevice::Append); -#ifdef Q_OS_UNIX - ::socketpair(AF_UNIX, SOCK_STREAM, 0, sigHupFD); - - snHup = new QSocketNotifier(sigHupFD[1], QSocketNotifier::Read, this); - connect(snHup, SIGNAL(activated(int)), this, SLOT(handleSigHup())); -#endif - } else - logFile = 0; - - connect(this, SIGNAL(sigFlushBuffer()), this, SLOT(flushBuffer()), Qt::QueuedConnection); } ServerLogger::~ServerLogger() { - flushBuffer(); + flushBuffer(); + // This does not work with the destroyed() signal as this destructor is called after the main event loop is done. + thread()->quit(); } -void ServerLogger::logMessage(QString message, ServerSocketInterface *ssi) +void ServerLogger::startLog(const QString &logFileName) { - if (!logFile) - return; - - bufferMutex.lock(); - QString ssiString; - if (ssi) - ssiString = QString::number((qulonglong) ssi, 16) + " "; - buffer.append(QDateTime::currentDateTime().toString() + " " + QString::number((qulonglong) QThread::currentThread(), 16) + " " + ssiString + message); - bufferMutex.unlock(); - - emit sigFlushBuffer(); + if (!logFileName.isEmpty()) { + logFile = new QFile("server.log", this); + logFile->open(QIODevice::Append); +#ifdef Q_OS_UNIX + ::socketpair(AF_UNIX, SOCK_STREAM, 0, sigHupFD); + + snHup = new QSocketNotifier(sigHupFD[1], QSocketNotifier::Read, this); + connect(snHup, SIGNAL(activated(int)), this, SLOT(handleSigHup())); +#endif + } else + logFile = 0; + + connect(this, SIGNAL(sigFlushBuffer()), this, SLOT(flushBuffer()), Qt::QueuedConnection); +} + +void ServerLogger::logMessage(QString message, void *caller) +{ + if (!logFile) + return; + + QString callerString; + if (caller) + callerString = QString::number((qulonglong) caller, 16) + " "; + + //filter out all log entries based on values in configuration file + QSettings settings("servatrice.ini", QSettings::IniFormat); + bool shouldWeWriteLog = settings.value("server/writelog").toBool(); + QString logFilters = settings.value("server/logfilters").toString(); + QStringList listlogFilters = logFilters.split(",", QString::SkipEmptyParts); + bool shouldWeSkipLine = false; + + if (!shouldWeWriteLog) + return; + + if (!logFilters.trimmed().isEmpty()){ + shouldWeSkipLine = true; + foreach(QString logFilter, listlogFilters){ + if (message.contains(logFilter, Qt::CaseInsensitive)){ + shouldWeSkipLine = false; + break; + } + } + } + + if (shouldWeSkipLine) + return; + + bufferMutex.lock(); + buffer.append(QDateTime::currentDateTime().toString() + " " + callerString + message); + bufferMutex.unlock(); + emit sigFlushBuffer(); } void ServerLogger::flushBuffer() { - if (flushRunning) - return; - - flushRunning = true; - QTextStream stream(logFile); - forever { - bufferMutex.lock(); - if (buffer.isEmpty()) { - bufferMutex.unlock(); - flushRunning = false; - return; - } - QString message = buffer.takeFirst(); - bufferMutex.unlock(); - - stream << message << "\n"; - stream.flush(); - } + if (flushRunning) + return; + + flushRunning = true; + QTextStream stream(logFile); + forever { + bufferMutex.lock(); + if (buffer.isEmpty()) { + bufferMutex.unlock(); + flushRunning = false; + return; + } + QString message = buffer.takeFirst(); + bufferMutex.unlock(); + + stream << message << "\n"; + stream.flush(); + + if (logToConsole) + std::cout << message.toStdString() << std::endl; + } } -#ifdef Q_OS_UNIX void ServerLogger::hupSignalHandler(int /*unused*/) { - if (!logFile) - return; - - char a = 1; - ::write(sigHupFD[0], &a, sizeof(a)); +#ifdef Q_OS_UNIX + if (!logFile) + return; + + char a = 1; + ::write(sigHupFD[0], &a, sizeof(a)); +#endif } void ServerLogger::handleSigHup() { - if (!logFile) - return; - - snHup->setEnabled(false); - char tmp; - ::read(sigHupFD[1], &tmp, sizeof(tmp)); - - logFile->close(); - logFile->open(QIODevice::Append); - - snHup->setEnabled(true); -} +#ifdef Q_OS_UNIX + if (!logFile) + return; + + snHup->setEnabled(false); + char tmp; + ::read(sigHupFD[1], &tmp, sizeof(tmp)); + + logFile->close(); + logFile->open(QIODevice::Append); + + snHup->setEnabled(true); #endif +} QFile *ServerLogger::logFile; int ServerLogger::sigHupFD[2]; - -ServerLoggerThread::ServerLoggerThread(const QString &_fileName, QObject *parent) - : QThread(parent), fileName(_fileName) -{ -} - -ServerLoggerThread::~ServerLoggerThread() -{ - quit(); - wait(); -} - -void ServerLoggerThread::run() -{ - logger = new ServerLogger(fileName); - - usleep(100); - initWaitCondition.wakeAll(); - - exec(); - - delete logger; -} - -void ServerLoggerThread::waitForInit() -{ - QMutex mutex; - mutex.lock(); - initWaitCondition.wait(&mutex); - mutex.unlock(); -} diff --git a/servatrice/src/server_logger.h b/servatrice/src/server_logger.h index 77c7f12d7..67b293cf9 100644 --- a/servatrice/src/server_logger.h +++ b/servatrice/src/server_logger.h @@ -9,24 +9,24 @@ class QSocketNotifier; class QFile; -class ServerSocketInterface; +class Server_ProtocolHandler; class ServerLogger : public QObject { Q_OBJECT public: - ServerLogger(const QString &logFileName, QObject *parent = 0); + ServerLogger(bool _logToConsole, QObject *parent = 0); ~ServerLogger(); static void hupSignalHandler(int unused); public slots: - void logMessage(QString message, ServerSocketInterface *ssi = 0); + void startLog(const QString &logFileName); + void logMessage(QString message, void *caller = 0); private slots: -#ifdef Q_OS_UNIX void handleSigHup(); -#endif void flushBuffer(); signals: void sigFlushBuffer(); private: + bool logToConsole; static int sigHupFD[2]; QSocketNotifier *snHup; static QFile *logFile; @@ -35,19 +35,4 @@ private: QMutex bufferMutex; }; -class ServerLoggerThread : public QThread { - Q_OBJECT -private: - QString fileName; - ServerLogger *logger; - QWaitCondition initWaitCondition; -protected: - void run(); -public: - ServerLoggerThread(const QString &_fileName, QObject *parent = 0); - ~ServerLoggerThread(); - ServerLogger *getLogger() const { return logger; } - void waitForInit(); -}; - #endif diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index ecd1c000c..f91029d70 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -18,212 +18,320 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include -#include -#include +#include #include #include +#include #include "serversocketinterface.h" #include "servatrice.h" -#include "protocol.h" -#include "protocol_items.h" +#include "servatrice_database_interface.h" #include "decklist.h" #include "server_player.h" #include "main.h" #include "server_logger.h" +#include "server_response_containers.h" +#include "pb/commands.pb.h" +#include "pb/command_deck_list.pb.h" +#include "pb/command_deck_upload.pb.h" +#include "pb/command_deck_download.pb.h" +#include "pb/command_deck_new_dir.pb.h" +#include "pb/command_deck_del_dir.pb.h" +#include "pb/command_deck_del.pb.h" +#include "pb/command_replay_list.pb.h" +#include "pb/command_replay_download.pb.h" +#include "pb/command_replay_modify_match.pb.h" +#include "pb/command_replay_delete_match.pb.h" +#include "pb/event_connection_closed.pb.h" +#include "pb/event_server_message.pb.h" +#include "pb/event_server_identification.pb.h" +#include "pb/event_add_to_list.pb.h" +#include "pb/event_remove_from_list.pb.h" +#include "pb/response_deck_list.pb.h" +#include "pb/response_deck_download.pb.h" +#include "pb/response_deck_upload.pb.h" +#include "pb/response_replay_list.pb.h" +#include "pb/response_replay_download.pb.h" +#include "pb/serverinfo_replay.pb.h" +#include "pb/serverinfo_user.pb.h" +#include "pb/serverinfo_deckstorage.pb.h" -ServerSocketInterface::ServerSocketInterface(Servatrice *_server, QTcpSocket *_socket, QObject *parent) - : Server_ProtocolHandler(_server, parent), servatrice(_server), socket(_socket), topLevelItem(0), compressionSupport(false) +#include "version_string.h" +#include +#include + +static const int protocolVersion = 14; + +ServerSocketInterface::ServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface, QObject *parent) + : Server_ProtocolHandler(_server, _databaseInterface, parent), + servatrice(_server), + sqlInterface(reinterpret_cast(databaseInterface)), + messageInProgress(false), + handshakeStarted(false) { - xmlWriter = new QXmlStreamWriter(&xmlBuffer); - xmlReader = new QXmlStreamReader; - + socket = new QTcpSocket(this); + socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); connect(socket, SIGNAL(readyRead()), this, SLOT(readClient())); - connect(socket, SIGNAL(disconnected()), this, SLOT(deleteLater())); connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(catchSocketError(QAbstractSocket::SocketError))); - connect(this, SIGNAL(xmlBufferChanged()), this, SLOT(flushXmlBuffer()), Qt::QueuedConnection); - xmlWriter->writeStartDocument(); - xmlWriter->writeStartElement("cockatrice_server_stream"); - xmlWriter->writeAttribute("version", QString::number(ProtocolItem::protocolVersion)); - flushXmlBuffer(); - - int maxUsers = _server->getMaxUsersPerAddress(); - if ((maxUsers > 0) && (_server->getUsersWithAddress(socket->peerAddress()) >= maxUsers)) { - sendProtocolItem(new Event_ConnectionClosed("too_many_connections")); - deleteLater(); - } else - sendProtocolItem(new Event_ServerMessage(Servatrice::versionString)); - - server->addClient(this); + // Never call flushOutputQueue directly from outputQueueChanged. In case of a socket error, + // it could lead to this object being destroyed while another function is still on the call stack. -> mutex deadlocks etc. + connect(this, SIGNAL(outputQueueChanged()), this, SLOT(flushOutputQueue()), Qt::QueuedConnection); } ServerSocketInterface::~ServerSocketInterface() { logger->logMessage("ServerSocketInterface destructor", this); - prepareDestroy(); + flushOutputQueue(); +} + +void ServerSocketInterface::initConnection(int socketDescriptor) +{ + // Add this object to the server's list of connections before it can receive socket events. + // Otherwise, in case a of a socket error, it could be removed from the list before it is added. + server->addClient(this); - flushXmlBuffer(); - delete xmlWriter; - delete xmlReader; - delete socket; - socket = 0; - delete topLevelItem; + socket->setSocketDescriptor(socketDescriptor); + logger->logMessage(QString("Incoming connection: %1").arg(socket->peerAddress().toString()), this); + initSessionDeprecated(); } -void ServerSocketInterface::processProtocolItem(ProtocolItem *item) +void ServerSocketInterface::initSessionDeprecated() { - CommandContainer *cont = qobject_cast(item); - if (!cont) - sendProtocolItem(new ProtocolResponse(cont->getCmdId(), RespInvalidCommand)); - else - processCommandContainer(cont); -} - -void ServerSocketInterface::flushXmlBuffer() -{ - QMutexLocker locker(&xmlBufferMutex); - if (xmlBuffer.isEmpty()) - return; - servatrice->incTxBytes(xmlBuffer.size()); - socket->write(xmlBuffer.toUtf8()); + // dirty hack to make v13 client display the correct error message + + QByteArray buf; + buf.append(""); + socket->write(buf); socket->flush(); - xmlBuffer.clear(); +} + +bool ServerSocketInterface::initSession() +{ + Event_ServerIdentification identEvent; + identEvent.set_server_name(servatrice->getServerName().toStdString()); + identEvent.set_server_version(VERSION_STRING); + identEvent.set_protocol_version(protocolVersion); + SessionEvent *identSe = prepareSessionEvent(identEvent); + sendProtocolItem(*identSe); + delete identSe; + + int maxUsers = servatrice->getMaxUsersPerAddress(); + if ((maxUsers > 0) && (servatrice->getUsersWithAddress(socket->peerAddress()) >= maxUsers)) { + Event_ConnectionClosed event; + event.set_reason(Event_ConnectionClosed::TOO_MANY_CONNECTIONS); + SessionEvent *se = prepareSessionEvent(event); + sendProtocolItem(*se); + delete se; + + return false; + } + + return true; } void ServerSocketInterface::readClient() { QByteArray data = socket->readAll(); servatrice->incRxBytes(data.size()); - if (!data.contains("logMessage(QString(data), this); - xmlReader->addData(data); + inputBuffer.append(data); - while (!xmlReader->atEnd()) { - xmlReader->readNext(); - if (topLevelItem) - topLevelItem->readElement(xmlReader); - else if (xmlReader->isStartElement() && (xmlReader->name().toString() == "cockatrice_client_stream")) { - if (xmlReader->attributes().value("comp").toString().toInt() == 1) - compressionSupport = true; - topLevelItem = new TopLevelProtocolItem; - connect(topLevelItem, SIGNAL(protocolItemReceived(ProtocolItem *)), this, SLOT(processProtocolItem(ProtocolItem *))); + do { + if (!messageInProgress) { + if (inputBuffer.size() >= 4) { + messageLength = (((quint32) (unsigned char) inputBuffer[0]) << 24) + + (((quint32) (unsigned char) inputBuffer[1]) << 16) + + (((quint32) (unsigned char) inputBuffer[2]) << 8) + + ((quint32) (unsigned char) inputBuffer[3]); + inputBuffer.remove(0, 4); + messageInProgress = true; + } else + return; } - } + if (inputBuffer.size() < messageLength) + return; + + CommandContainer newCommandContainer; + newCommandContainer.ParseFromArray(inputBuffer.data(), messageLength); + inputBuffer.remove(0, messageLength); + messageInProgress = false; + + // dirty hack to make v13 client display the correct error message + if (handshakeStarted) + processCommandContainer(newCommandContainer); + else if (!newCommandContainer.has_cmd_id()) { + handshakeStarted = true; + if (!initSession()) + prepareDestroy(); + } + // end of hack + } while (!inputBuffer.isEmpty()); } void ServerSocketInterface::catchSocketError(QAbstractSocket::SocketError socketError) { qDebug() << "Socket error:" << socketError; - deleteLater(); + prepareDestroy(); } -void ServerSocketInterface::sendProtocolItem(ProtocolItem *item, bool deleteItem) +void ServerSocketInterface::transmitProtocolItem(const ServerMessage &item) { - QMutexLocker locker(&xmlBufferMutex); + outputQueueMutex.lock(); + outputQueue.append(item); + outputQueueMutex.unlock(); - item->write(xmlWriter); - if (deleteItem) - delete item; - - emit xmlBufferChanged(); + emit outputQueueChanged(); } -int ServerSocketInterface::getUserIdInDB(const QString &name) const +void ServerSocketInterface::flushOutputQueue() { - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; - query.prepare("select id from " + servatrice->getDbPrefix() + "_users where name = :name"); - query.bindValue(":name", name); - if (!servatrice->execSqlQuery(query)) - return -1; - if (!query.next()) - return -1; - return query.value(0).toInt(); + QMutexLocker locker(&outputQueueMutex); + if (outputQueue.isEmpty()) + return; + + int totalBytes = 0; + while (!outputQueue.isEmpty()) { + ServerMessage item = outputQueue.takeFirst(); + locker.unlock(); + + QByteArray buf; + unsigned int size = item.ByteSize(); + buf.resize(size + 4); + item.SerializeToArray(buf.data() + 4, size); + buf.data()[3] = (unsigned char) size; + buf.data()[2] = (unsigned char) (size >> 8); + buf.data()[1] = (unsigned char) (size >> 16); + buf.data()[0] = (unsigned char) (size >> 24); + // In case socket->write() calls catchSocketError(), the mutex must not be locked during this call. + socket->write(buf); + + totalBytes += size + 4; + locker.relock(); + } + locker.unlock(); + servatrice->incTxBytes(totalBytes); + // see above wrt mutex + socket->flush(); } -ResponseCode ServerSocketInterface::cmdAddToList(Command_AddToList *cmd, CommandContainer *cont) +void ServerSocketInterface::logDebugMessage(const QString &message) +{ + logger->logMessage(message, this); +} + +Response::ResponseCode ServerSocketInterface::processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc) +{ + switch ((SessionCommand::SessionCommandType) cmdType) { + case SessionCommand::ADD_TO_LIST: return cmdAddToList(cmd.GetExtension(Command_AddToList::ext), rc); + case SessionCommand::REMOVE_FROM_LIST: return cmdRemoveFromList(cmd.GetExtension(Command_RemoveFromList::ext), rc); + case SessionCommand::DECK_LIST: return cmdDeckList(cmd.GetExtension(Command_DeckList::ext), rc); + case SessionCommand::DECK_NEW_DIR: return cmdDeckNewDir(cmd.GetExtension(Command_DeckNewDir::ext), rc); + case SessionCommand::DECK_DEL_DIR: return cmdDeckDelDir(cmd.GetExtension(Command_DeckDelDir::ext), rc); + case SessionCommand::DECK_DEL: return cmdDeckDel(cmd.GetExtension(Command_DeckDel::ext), rc); + case SessionCommand::DECK_UPLOAD: return cmdDeckUpload(cmd.GetExtension(Command_DeckUpload::ext), rc); + case SessionCommand::DECK_DOWNLOAD: return cmdDeckDownload(cmd.GetExtension(Command_DeckDownload::ext), rc); + case SessionCommand::REPLAY_LIST: return cmdReplayList(cmd.GetExtension(Command_ReplayList::ext), rc); + case SessionCommand::REPLAY_DOWNLOAD: return cmdReplayDownload(cmd.GetExtension(Command_ReplayDownload::ext), rc); + case SessionCommand::REPLAY_MODIFY_MATCH: return cmdReplayModifyMatch(cmd.GetExtension(Command_ReplayModifyMatch::ext), rc); + case SessionCommand::REPLAY_DELETE_MATCH: return cmdReplayDeleteMatch(cmd.GetExtension(Command_ReplayDeleteMatch::ext), rc); + default: return Response::RespFunctionNotAllowed; + } +} + +Response::ResponseCode ServerSocketInterface::processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc) +{ + switch ((ModeratorCommand::ModeratorCommandType) cmdType) { + case ModeratorCommand::BAN_FROM_SERVER: return cmdBanFromServer(cmd.GetExtension(Command_BanFromServer::ext), rc); + default: return Response::RespFunctionNotAllowed; + } +} + +Response::ResponseCode ServerSocketInterface::processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc) +{ + switch ((AdminCommand::AdminCommandType) cmdType) { + case AdminCommand::SHUTDOWN_SERVER: return cmdShutdownServer(cmd.GetExtension(Command_ShutdownServer::ext), rc); + case AdminCommand::UPDATE_SERVER_MESSAGE: return cmdUpdateServerMessage(cmd.GetExtension(Command_UpdateServerMessage::ext), rc); + default: return Response::RespFunctionNotAllowed; + } +} + +Response::ResponseCode ServerSocketInterface::cmdAddToList(const Command_AddToList &cmd, ResponseContainer &rc) { if (authState != PasswordRight) - return RespFunctionNotAllowed; + return Response::RespFunctionNotAllowed; - QString list = cmd->getList(); - QString user = cmd->getUserName(); + QString list = QString::fromStdString(cmd.list()); + QString user = QString::fromStdString(cmd.user_name()); if ((list != "buddy") && (list != "ignore")) - return RespContextError; + return Response::RespContextError; - if ((list == "buddy") && buddyList.contains(user)) - return RespContextError; - if ((list == "ignore") && ignoreList.contains(user)) - return RespContextError; + if (list == "buddy") + if (databaseInterface->isInBuddyList(QString::fromStdString(userInfo->name()), user)) + return Response::RespContextError; + if (list == "ignore") + if (databaseInterface->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) + return Response::RespContextError; - int id1 = getUserIdInDB(userInfo->getName()); - int id2 = getUserIdInDB(user); + int id1 = userInfo->id(); + int id2 = sqlInterface->getUserIdInDB(user); if (id2 < 0) - return RespNameNotFound; + return Response::RespNameNotFound; if (id1 == id2) - return RespContextError; + return Response::RespContextError; - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; + QSqlQuery query(sqlInterface->getDatabase()); query.prepare("insert into " + servatrice->getDbPrefix() + "_" + list + "list (id_user1, id_user2) values(:id1, :id2)"); query.bindValue(":id1", id1); query.bindValue(":id2", id2); - if (!servatrice->execSqlQuery(query)) - return RespInternalError; + if (!sqlInterface->execSqlQuery(query)) + return Response::RespInternalError; - ServerInfo_User *info = servatrice->getUserData(user); - if (list == "buddy") - buddyList.insert(info->getName(), info); - else if (list == "ignore") - ignoreList.insert(info->getName(), info); + Event_AddToList event; + event.set_list_name(cmd.list()); + event.mutable_user_info()->CopyFrom(databaseInterface->getUserData(user)); + rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); - cont->enqueueItem(new Event_AddToList(list, new ServerInfo_User(info))); - return RespOk; + return Response::RespOk; } -ResponseCode ServerSocketInterface::cmdRemoveFromList(Command_RemoveFromList *cmd, CommandContainer *cont) +Response::ResponseCode ServerSocketInterface::cmdRemoveFromList(const Command_RemoveFromList &cmd, ResponseContainer &rc) { if (authState != PasswordRight) - return RespFunctionNotAllowed; + return Response::RespFunctionNotAllowed; - QString list = cmd->getList(); - QString user = cmd->getUserName(); + QString list = QString::fromStdString(cmd.list()); + QString user = QString::fromStdString(cmd.user_name()); if ((list != "buddy") && (list != "ignore")) - return RespContextError; + return Response::RespContextError; - if ((list == "buddy") && !buddyList.contains(user)) - return RespContextError; - if ((list == "ignore") && !ignoreList.contains(user)) - return RespContextError; + if (list == "buddy") + if (!databaseInterface->isInBuddyList(QString::fromStdString(userInfo->name()), user)) + return Response::RespContextError; + if (list == "ignore") + if (!databaseInterface->isInIgnoreList(QString::fromStdString(userInfo->name()), user)) + return Response::RespContextError; - int id1 = getUserIdInDB(userInfo->getName()); - int id2 = getUserIdInDB(user); + int id1 = userInfo->id(); + int id2 = sqlInterface->getUserIdInDB(user); if (id2 < 0) - return RespNameNotFound; + return Response::RespNameNotFound; - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; + QSqlQuery query(sqlInterface->getDatabase()); query.prepare("delete from " + servatrice->getDbPrefix() + "_" + list + "list where id_user1 = :id1 and id_user2 = :id2"); query.bindValue(":id1", id1); query.bindValue(":id2", id2); - if (!servatrice->execSqlQuery(query)) - return RespInternalError; + if (!sqlInterface->execSqlQuery(query)) + return Response::RespInternalError; - if (list == "buddy") { - delete buddyList.value(user); - buddyList.remove(user); - } else if (list == "ignore") { - delete ignoreList.value(user); - ignoreList.remove(user); - } + Event_RemoveFromList event; + event.set_list_name(cmd.list()); + event.set_user_name(cmd.user_name()); + rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); - cont->enqueueItem(new Event_RemoveFromList(list, user)); - return RespOk; + return Response::RespOk; } int ServerSocketInterface::getDeckPathId(int basePathId, QStringList path) @@ -233,13 +341,12 @@ int ServerSocketInterface::getDeckPathId(int basePathId, QStringList path) if (path[0].isEmpty()) return 0; - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; - query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and name = :name and user = :user"); + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and name = :name and id_user = :id_user"); query.bindValue(":id_parent", basePathId); query.bindValue(":name", path.takeFirst()); - query.bindValue(":user", userInfo->getName()); - if (!servatrice->execSqlQuery(query)) + query.bindValue(":id_user", userInfo->id()); + if (!sqlInterface->execSqlQuery(query)) return -1; if (!query.next()) return -1; @@ -255,32 +362,37 @@ int ServerSocketInterface::getDeckPathId(const QString &path) return getDeckPathId(0, path.split("/")); } -bool ServerSocketInterface::deckListHelper(DeckList_Directory *folder) +bool ServerSocketInterface::deckListHelper(int folderId, ServerInfo_DeckStorage_Folder *folder) { - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; - query.prepare("select id, name from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and user = :user"); - query.bindValue(":id_parent", folder->getId()); - query.bindValue(":user", userInfo->getName()); - if (!servatrice->execSqlQuery(query)) + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("select id, name from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent and id_user = :id_user"); + query.bindValue(":id_parent", folderId); + query.bindValue(":id_user", userInfo->id()); + if (!sqlInterface->execSqlQuery(query)) return false; while (query.next()) { - DeckList_Directory *newFolder = new DeckList_Directory(query.value(1).toString(), query.value(0).toInt()); - folder->appendItem(newFolder); - if (!deckListHelper(newFolder)) + ServerInfo_DeckStorage_TreeItem *newItem = folder->add_items(); + newItem->set_id(query.value(0).toInt()); + newItem->set_name(query.value(1).toString().toStdString()); + + if (!deckListHelper(newItem->id(), newItem->mutable_folder())) return false; } - query.prepare("select id, name, upload_time from " + servatrice->getDbPrefix() + "_decklist_files where id_folder = :id_folder and user = :user"); - query.bindValue(":id_folder", folder->getId()); - query.bindValue(":user", userInfo->getName()); - if (!servatrice->execSqlQuery(query)) + query.prepare("select id, name, upload_time from " + servatrice->getDbPrefix() + "_decklist_files where id_folder = :id_folder and id_user = :id_user"); + query.bindValue(":id_folder", folderId); + query.bindValue(":id_user", userInfo->id()); + if (!sqlInterface->execSqlQuery(query)) return false; while (query.next()) { - DeckList_File *newFile = new DeckList_File(query.value(1).toString(), query.value(0).toInt(), query.value(2).toDateTime()); - folder->appendItem(newFile); + ServerInfo_DeckStorage_TreeItem *newItem = folder->add_items(); + newItem->set_id(query.value(0).toInt()); + newItem->set_name(query.value(1).toString().toStdString()); + + ServerInfo_DeckStorage_File *newFile = newItem->mutable_file(); + newFile->set_creation_time(query.value(2).toDateTime().toTime_t()); } return true; @@ -289,220 +401,359 @@ bool ServerSocketInterface::deckListHelper(DeckList_Directory *folder) // CHECK AUTHENTICATION! // Also check for every function that data belonging to other users cannot be accessed. -ResponseCode ServerSocketInterface::cmdDeckList(Command_DeckList * /*cmd*/, CommandContainer *cont) +Response::ResponseCode ServerSocketInterface::cmdDeckList(const Command_DeckList & /*cmd*/, ResponseContainer &rc) { if (authState != PasswordRight) - return RespFunctionNotAllowed; + return Response::RespFunctionNotAllowed; - servatrice->checkSql(); + sqlInterface->checkSql(); - DeckList_Directory *root = new DeckList_Directory(QString()); - QSqlQuery query; - if (!deckListHelper(root)) - return RespContextError; + Response_DeckList *re = new Response_DeckList; + ServerInfo_DeckStorage_Folder *root = re->mutable_root(); - ProtocolResponse *resp = new Response_DeckList(cont->getCmdId(), RespOk, root); - if (getCompressionSupport()) - resp->setCompressed(true); - cont->setResponse(resp); + if (!deckListHelper(0, root)) + return Response::RespContextError; - return RespNothing; + rc.setResponseExtension(re); + return Response::RespOk; } -ResponseCode ServerSocketInterface::cmdDeckNewDir(Command_DeckNewDir *cmd, CommandContainer * /*cont*/) +Response::ResponseCode ServerSocketInterface::cmdDeckNewDir(const Command_DeckNewDir &cmd, ResponseContainer & /*rc*/) { if (authState != PasswordRight) - return RespFunctionNotAllowed; + return Response::RespFunctionNotAllowed; - servatrice->checkSql(); + sqlInterface->checkSql(); - int folderId = getDeckPathId(cmd->getPath()); + int folderId = getDeckPathId(QString::fromStdString(cmd.path())); if (folderId == -1) - return RespNameNotFound; + return Response::RespNameNotFound; - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; - query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_folders (id_parent, user, name) values(:id_parent, :user, :name)"); + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_folders (id_parent, id_user, name) values(:id_parent, :id_user, :name)"); query.bindValue(":id_parent", folderId); - query.bindValue(":user", userInfo->getName()); - query.bindValue(":name", cmd->getDirName()); - if (!servatrice->execSqlQuery(query)) - return RespContextError; - return RespOk; + query.bindValue(":id_user", userInfo->id()); + query.bindValue(":name", QString::fromStdString(cmd.dir_name())); + if (!sqlInterface->execSqlQuery(query)) + return Response::RespContextError; + return Response::RespOk; } void ServerSocketInterface::deckDelDirHelper(int basePathId) { - servatrice->checkSql(); - - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; + sqlInterface->checkSql(); + QSqlQuery query(sqlInterface->getDatabase()); query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_folders where id_parent = :id_parent"); query.bindValue(":id_parent", basePathId); - servatrice->execSqlQuery(query); + sqlInterface->execSqlQuery(query); while (query.next()) deckDelDirHelper(query.value(0).toInt()); query.prepare("delete from " + servatrice->getDbPrefix() + "_decklist_files where id_folder = :id_folder"); query.bindValue(":id_folder", basePathId); - servatrice->execSqlQuery(query); + sqlInterface->execSqlQuery(query); query.prepare("delete from " + servatrice->getDbPrefix() + "_decklist_folders where id = :id"); query.bindValue(":id", basePathId); - servatrice->execSqlQuery(query); + sqlInterface->execSqlQuery(query); } -ResponseCode ServerSocketInterface::cmdDeckDelDir(Command_DeckDelDir *cmd, CommandContainer * /*cont*/) +Response::ResponseCode ServerSocketInterface::cmdDeckDelDir(const Command_DeckDelDir &cmd, ResponseContainer & /*rc*/) { if (authState != PasswordRight) - return RespFunctionNotAllowed; + return Response::RespFunctionNotAllowed; - servatrice->checkSql(); + sqlInterface->checkSql(); - int basePathId = getDeckPathId(cmd->getPath()); - if (basePathId == -1) - return RespNameNotFound; + int basePathId = getDeckPathId(QString::fromStdString(cmd.path())); + if ((basePathId == -1) || (basePathId == 0)) + return Response::RespNameNotFound; deckDelDirHelper(basePathId); - return RespOk; + return Response::RespOk; } -ResponseCode ServerSocketInterface::cmdDeckDel(Command_DeckDel *cmd, CommandContainer * /*cont*/) +Response::ResponseCode ServerSocketInterface::cmdDeckDel(const Command_DeckDel &cmd, ResponseContainer & /*rc*/) { if (authState != PasswordRight) - return RespFunctionNotAllowed; + return Response::RespFunctionNotAllowed; - servatrice->checkSql(); + sqlInterface->checkSql(); + QSqlQuery query(sqlInterface->getDatabase()); - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; - - query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_files where id = :id and user = :user"); - query.bindValue(":id", cmd->getDeckId()); - query.bindValue(":user", userInfo->getName()); - servatrice->execSqlQuery(query); + query.prepare("select id from " + servatrice->getDbPrefix() + "_decklist_files where id = :id and id_user = :id_user"); + query.bindValue(":id", cmd.deck_id()); + query.bindValue(":id_user", userInfo->id()); + sqlInterface->execSqlQuery(query); if (!query.next()) - return RespNameNotFound; + return Response::RespNameNotFound; query.prepare("delete from " + servatrice->getDbPrefix() + "_decklist_files where id = :id"); - query.bindValue(":id", cmd->getDeckId()); - servatrice->execSqlQuery(query); + query.bindValue(":id", cmd.deck_id()); + sqlInterface->execSqlQuery(query); - return RespOk; + return Response::RespOk; } -ResponseCode ServerSocketInterface::cmdDeckUpload(Command_DeckUpload *cmd, CommandContainer *cont) +Response::ResponseCode ServerSocketInterface::cmdDeckUpload(const Command_DeckUpload &cmd, ResponseContainer &rc) { if (authState != PasswordRight) - return RespFunctionNotAllowed; + return Response::RespFunctionNotAllowed; - servatrice->checkSql(); + if (!cmd.has_deck_list()) + return Response::RespInvalidData; - if (!cmd->getDeck()) - return RespInvalidData; - int folderId = getDeckPathId(cmd->getPath()); - if (folderId == -1) - return RespNameNotFound; + sqlInterface->checkSql(); - QString deckContents; - QXmlStreamWriter deckWriter(&deckContents); - deckWriter.writeStartDocument(); - cmd->getDeck()->write(&deckWriter); - deckWriter.writeEndDocument(); + QString deckStr = QString::fromStdString(cmd.deck_list()); + DeckList deck(deckStr); - QString deckName = cmd->getDeck()->getName(); + QString deckName = deck.getName(); if (deckName.isEmpty()) deckName = "Unnamed deck"; - - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; - query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_files (id_folder, user, name, upload_time, content) values(:id_folder, :user, :name, NOW(), :content)"); - query.bindValue(":id_folder", folderId); - query.bindValue(":user", userInfo->getName()); - query.bindValue(":name", deckName); - query.bindValue(":content", deckContents); - servatrice->execSqlQuery(query); - cont->setResponse(new Response_DeckUpload(cont->getCmdId(), RespOk, new DeckList_File(deckName, query.lastInsertId().toInt(), QDateTime::currentDateTime()))); - return RespNothing; + if (cmd.has_path()) { + int folderId = getDeckPathId(QString::fromStdString(cmd.path())); + if (folderId == -1) + return Response::RespNameNotFound; + + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("insert into " + servatrice->getDbPrefix() + "_decklist_files (id_folder, id_user, name, upload_time, content) values(:id_folder, :id_user, :name, NOW(), :content)"); + query.bindValue(":id_folder", folderId); + query.bindValue(":id_user", userInfo->id()); + query.bindValue(":name", deckName); + query.bindValue(":content", deckStr); + sqlInterface->execSqlQuery(query); + + Response_DeckUpload *re = new Response_DeckUpload; + ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file(); + fileInfo->set_id(query.lastInsertId().toInt()); + fileInfo->set_name(deckName.toStdString()); + fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toTime_t()); + rc.setResponseExtension(re); + } else if (cmd.has_deck_id()) { + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("update " + servatrice->getDbPrefix() + "_decklist_files set name=:name, upload_time=NOW(), content=:content where id = :id_deck and id_user = :id_user"); + query.bindValue(":id_deck", cmd.deck_id()); + query.bindValue(":id_user", userInfo->id()); + query.bindValue(":name", deckName); + query.bindValue(":content", deckStr); + sqlInterface->execSqlQuery(query); + + if (query.numRowsAffected() == 0) + return Response::RespNameNotFound; + + Response_DeckUpload *re = new Response_DeckUpload; + ServerInfo_DeckStorage_TreeItem *fileInfo = re->mutable_new_file(); + fileInfo->set_id(cmd.deck_id()); + fileInfo->set_name(deckName.toStdString()); + fileInfo->mutable_file()->set_creation_time(QDateTime::currentDateTime().toTime_t()); + rc.setResponseExtension(re); + } else + return Response::RespInvalidData; + + return Response::RespOk; } -DeckList *ServerSocketInterface::getDeckFromDatabase(int deckId) -{ - servatrice->checkSql(); - - QMutexLocker locker(&servatrice->dbMutex); - QSqlQuery query; - - query.prepare("select content from " + servatrice->getDbPrefix() + "_decklist_files where id = :id and user = :user"); - query.bindValue(":id", deckId); - query.bindValue(":user", userInfo->getName()); - servatrice->execSqlQuery(query); - if (!query.next()) - throw RespNameNotFound; - - QXmlStreamReader deckReader(query.value(0).toString()); - DeckList *deck = new DeckList; - deck->loadFromXml(&deckReader); - - return deck; -} - -ResponseCode ServerSocketInterface::cmdDeckDownload(Command_DeckDownload *cmd, CommandContainer *cont) +Response::ResponseCode ServerSocketInterface::cmdDeckDownload(const Command_DeckDownload &cmd, ResponseContainer &rc) { if (authState != PasswordRight) - return RespFunctionNotAllowed; + return Response::RespFunctionNotAllowed; DeckList *deck; try { - deck = getDeckFromDatabase(cmd->getDeckId()); - } catch(ResponseCode r) { + deck = sqlInterface->getDeckFromDatabase(cmd.deck_id(), userInfo->id()); + } catch(Response::ResponseCode r) { return r; } - cont->setResponse(new Response_DeckDownload(cont->getCmdId(), RespOk, deck)); - return RespNothing; + + Response_DeckDownload *re = new Response_DeckDownload; + re->set_deck(deck->writeToString_Native().toStdString()); + rc.setResponseExtension(re); + delete deck; + + return Response::RespOk; } +Response::ResponseCode ServerSocketInterface::cmdReplayList(const Command_ReplayList & /*cmd*/, ResponseContainer &rc) +{ + if (authState != PasswordRight) + return Response::RespFunctionNotAllowed; + + Response_ReplayList *re = new Response_ReplayList; + + QSqlQuery query1(sqlInterface->getDatabase()); + query1.prepare("select a.id_game, a.replay_name, b.room_name, b.time_started, b.time_finished, b.descr, a.do_not_hide from cockatrice_replays_access a left join cockatrice_games b on b.id = a.id_game where a.id_player = :id_player and (a.do_not_hide = 1 or date_add(b.time_started, interval 7 day) > now())"); + query1.bindValue(":id_player", userInfo->id()); + sqlInterface->execSqlQuery(query1); + while (query1.next()) { + ServerInfo_ReplayMatch *matchInfo = re->add_match_list(); + + const int gameId = query1.value(0).toInt(); + matchInfo->set_game_id(gameId); + matchInfo->set_room_name(query1.value(2).toString().toStdString()); + const int timeStarted = query1.value(3).toDateTime().toTime_t(); + const int timeFinished = query1.value(4).toDateTime().toTime_t(); + matchInfo->set_time_started(timeStarted); + matchInfo->set_length(timeFinished - timeStarted); + matchInfo->set_game_name(query1.value(5).toString().toStdString()); + const QString replayName = query1.value(1).toString(); + matchInfo->set_do_not_hide(query1.value(6).toBool()); + + { + QSqlQuery query2(sqlInterface->getDatabase()); + query2.prepare("select player_name from cockatrice_games_players where id_game = :id_game"); + query2.bindValue(":id_game", gameId); + sqlInterface->execSqlQuery(query2); + while (query2.next()) + matchInfo->add_player_names(query2.value(0).toString().toStdString()); + } + { + QSqlQuery query3(sqlInterface->getDatabase()); + query3.prepare("select id, duration from " + servatrice->getDbPrefix() + "_replays where id_game = :id_game"); + query3.bindValue(":id_game", gameId); + sqlInterface->execSqlQuery(query3); + while (query3.next()) { + ServerInfo_Replay *replayInfo = matchInfo->add_replay_list(); + replayInfo->set_replay_id(query3.value(0).toInt()); + replayInfo->set_replay_name(replayName.toStdString()); + replayInfo->set_duration(query3.value(1).toInt()); + } + } + } + + rc.setResponseExtension(re); + return Response::RespOk; +} + +Response::ResponseCode ServerSocketInterface::cmdReplayDownload(const Command_ReplayDownload &cmd, ResponseContainer &rc) +{ + if (authState != PasswordRight) + return Response::RespFunctionNotAllowed; + + { + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("select 1 from " + servatrice->getDbPrefix() + "_replays_access a left join " + servatrice->getDbPrefix() + "_replays b on a.id_game = b.id_game where b.id = :id_replay and a.id_player = :id_player"); + query.bindValue(":id_replay", cmd.replay_id()); + query.bindValue(":id_player", userInfo->id()); + if (!sqlInterface->execSqlQuery(query)) + return Response::RespInternalError; + if (!query.next()) + return Response::RespAccessDenied; + } + + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("select replay from " + servatrice->getDbPrefix() + "_replays where id = :id_replay"); + query.bindValue(":id_replay", cmd.replay_id()); + if (!sqlInterface->execSqlQuery(query)) + return Response::RespInternalError; + if (!query.next()) + return Response::RespNameNotFound; + + QByteArray data = query.value(0).toByteArray(); + + Response_ReplayDownload *re = new Response_ReplayDownload; + re->set_replay_data(data.data(), data.size()); + rc.setResponseExtension(re); + + return Response::RespOk; +} + +Response::ResponseCode ServerSocketInterface::cmdReplayModifyMatch(const Command_ReplayModifyMatch &cmd, ResponseContainer & /*rc*/) +{ + if (authState != PasswordRight) + return Response::RespFunctionNotAllowed; + + if (!sqlInterface->checkSql()) + return Response::RespInternalError; + + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("update " + servatrice->getDbPrefix() + "_replays_access set do_not_hide=:do_not_hide where id_player = :id_player and id_game = :id_game"); + query.bindValue(":id_player", userInfo->id()); + query.bindValue(":id_game", cmd.game_id()); + query.bindValue(":do_not_hide", cmd.do_not_hide()); + + if (!sqlInterface->execSqlQuery(query)) + return Response::RespInternalError; + return query.numRowsAffected() > 0 ? Response::RespOk : Response::RespNameNotFound; +} + +Response::ResponseCode ServerSocketInterface::cmdReplayDeleteMatch(const Command_ReplayDeleteMatch &cmd, ResponseContainer & /*rc*/) +{ + if (authState != PasswordRight) + return Response::RespFunctionNotAllowed; + + if (!sqlInterface->checkSql()) + return Response::RespInternalError; + + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("delete from " + servatrice->getDbPrefix() + "_replays_access where id_player = :id_player and id_game = :id_game"); + query.bindValue(":id_player", userInfo->id()); + query.bindValue(":id_game", cmd.game_id()); + + if (!sqlInterface->execSqlQuery(query)) + return Response::RespInternalError; + return query.numRowsAffected() > 0 ? Response::RespOk : Response::RespNameNotFound; +} + + // MODERATOR FUNCTIONS. // May be called by admins and moderators. Permission is checked by the calling function. -ResponseCode ServerSocketInterface::cmdBanFromServer(Command_BanFromServer *cmd, CommandContainer * /*cont*/) +Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer & /*rc*/) { - QString userName = cmd->getUserName(); - QString address = cmd->getAddress(); - int minutes = cmd->getMinutes(); + if (!sqlInterface->checkSql()) + return Response::RespInternalError; - servatrice->dbMutex.lock(); - QSqlQuery query; - query.prepare("insert into " + servatrice->getDbPrefix() + "_bans (user_name, ip_address, id_admin, time_from, minutes, reason) values(:user_name, :ip_address, :id_admin, NOW(), :minutes, :reason)"); + QString userName = QString::fromStdString(cmd.user_name()); + QString address = QString::fromStdString(cmd.address()); + int minutes = cmd.minutes(); + + QSqlQuery query(sqlInterface->getDatabase()); + query.prepare("insert into " + servatrice->getDbPrefix() + "_bans (user_name, ip_address, id_admin, time_from, minutes, reason, visible_reason) values(:user_name, :ip_address, :id_admin, NOW(), :minutes, :reason, :visible_reason)"); query.bindValue(":user_name", userName); query.bindValue(":ip_address", address); - query.bindValue(":id_admin", getUserIdInDB(userInfo->getName())); + query.bindValue(":id_admin", userInfo->id()); query.bindValue(":minutes", minutes); - query.bindValue(":reason", cmd->getReason() + "\n"); - servatrice->execSqlQuery(query); - servatrice->dbMutex.unlock(); + query.bindValue(":reason", QString::fromStdString(cmd.reason())); + query.bindValue(":visible_reason", QString::fromStdString(cmd.visible_reason())); + sqlInterface->execSqlQuery(query); + servatrice->clientsLock.lockForRead(); + QList userList = servatrice->getUsersWithAddressAsList(QHostAddress(address)); ServerSocketInterface *user = static_cast(server->getUsers().value(userName)); - if (user) { - user->sendProtocolItem(new Event_ConnectionClosed("banned")); - user->deleteLater(); + if (user && !userList.contains(user)) + userList.append(user); + if (!userList.isEmpty()) { + Event_ConnectionClosed event; + event.set_reason(Event_ConnectionClosed::BANNED); + if (cmd.has_visible_reason()) + event.set_reason_str(cmd.visible_reason()); + if (minutes) + event.set_end_time(QDateTime::currentDateTime().addSecs(60 * minutes).toTime_t()); + for (int i = 0; i < userList.size(); ++i) { + SessionEvent *se = userList[i]->prepareSessionEvent(event); + userList[i]->sendProtocolItem(*se); + delete se; + QMetaObject::invokeMethod(userList[i], "prepareDestroy", Qt::QueuedConnection); + } } + servatrice->clientsLock.unlock(); - return RespOk; + return Response::RespOk; } // ADMIN FUNCTIONS. // Permission is checked by the calling function. -ResponseCode ServerSocketInterface::cmdUpdateServerMessage(Command_UpdateServerMessage * /*cmd*/, CommandContainer * /*cont*/) +Response::ResponseCode ServerSocketInterface::cmdUpdateServerMessage(const Command_UpdateServerMessage & /*cmd*/, ResponseContainer & /*rc*/) { - servatrice->updateLoginMessage(); - return RespOk; + QMetaObject::invokeMethod(server, "updateLoginMessage"); + return Response::RespOk; } -ResponseCode ServerSocketInterface::cmdShutdownServer(Command_ShutdownServer *cmd, CommandContainer * /*cont*/) +Response::ResponseCode ServerSocketInterface::cmdShutdownServer(const Command_ShutdownServer &cmd, ResponseContainer & /*rc*/) { - servatrice->scheduleShutdown(cmd->getReason(), cmd->getMinutes()); - return RespOk; + QMetaObject::invokeMethod(server, "scheduleShutdown", Q_ARG(QString, QString::fromStdString(cmd.reason())), Q_ARG(int, cmd.minutes())); + return Response::RespOk; } diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index fad934eaf..6a2a41f8e 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -27,11 +27,26 @@ class QTcpSocket; class Servatrice; -class QXmlStreamReader; -class QXmlStreamWriter; +class Servatrice_DatabaseInterface; class DeckList; -class TopLevelProtocolItem; -class QByteArray; +class ServerInfo_DeckStorage_Folder; + +class Command_AddToList; +class Command_RemoveFromList; +class Command_DeckList; +class Command_DeckNewDir; +class Command_DeckDelDir; +class Command_DeckDel; +class Command_DeckDownload; +class Command_DeckUpload; +class Command_ReplayList; +class Command_ReplayDownload; +class Command_ReplayModifyMatch; +class Command_ReplayDeleteMatch; + +class Command_BanFromServer; +class Command_UpdateServerMessage; +class Command_ShutdownServer; class ServerSocketInterface : public Server_ProtocolHandler { @@ -39,46 +54,58 @@ class ServerSocketInterface : public Server_ProtocolHandler private slots: void readClient(); void catchSocketError(QAbstractSocket::SocketError socketError); - void processProtocolItem(ProtocolItem *item); - void flushXmlBuffer(); + void flushOutputQueue(); signals: - void xmlBufferChanged(); + void outputQueueChanged(); +protected: + void logDebugMessage(const QString &message); private: - QMutex xmlBufferMutex; + QMutex outputQueueMutex; Servatrice *servatrice; + Servatrice_DatabaseInterface *sqlInterface; QTcpSocket *socket; - QXmlStreamWriter *xmlWriter; - QXmlStreamReader *xmlReader; - QString xmlBuffer; - TopLevelProtocolItem *topLevelItem; - bool compressionSupport; - int getUserIdInDB(const QString &name) const; - - ResponseCode cmdAddToList(Command_AddToList *cmd, CommandContainer *cont); - ResponseCode cmdRemoveFromList(Command_RemoveFromList *cmd, CommandContainer *cont); + + QByteArray inputBuffer; + QList outputQueue; + bool messageInProgress; + bool handshakeStarted; + int messageLength; + + Response::ResponseCode cmdAddToList(const Command_AddToList &cmd, ResponseContainer &rc); + Response::ResponseCode cmdRemoveFromList(const Command_RemoveFromList &cmd, ResponseContainer &rc); int getDeckPathId(int basePathId, QStringList path); int getDeckPathId(const QString &path); - bool deckListHelper(DeckList_Directory *folder); - ResponseCode cmdDeckList(Command_DeckList *cmd, CommandContainer *cont); - ResponseCode cmdDeckNewDir(Command_DeckNewDir *cmd, CommandContainer *cont); + bool deckListHelper(int folderId, ServerInfo_DeckStorage_Folder *folder); + Response::ResponseCode cmdDeckList(const Command_DeckList &cmd, ResponseContainer &rc); + Response::ResponseCode cmdDeckNewDir(const Command_DeckNewDir &cmd, ResponseContainer &rc); void deckDelDirHelper(int basePathId); - ResponseCode cmdDeckDelDir(Command_DeckDelDir *cmd, CommandContainer *cont); - ResponseCode cmdDeckDel(Command_DeckDel *cmd, CommandContainer *cont); - ResponseCode cmdDeckUpload(Command_DeckUpload *cmd, CommandContainer *cont); + Response::ResponseCode cmdDeckDelDir(const Command_DeckDelDir &cmd, ResponseContainer &rc); + Response::ResponseCode cmdDeckDel(const Command_DeckDel &cmd, ResponseContainer &rc); + Response::ResponseCode cmdDeckUpload(const Command_DeckUpload &cmd, ResponseContainer &rc); DeckList *getDeckFromDatabase(int deckId); - ResponseCode cmdDeckDownload(Command_DeckDownload *cmd, CommandContainer *cont); - ResponseCode cmdBanFromServer(Command_BanFromServer *cmd, CommandContainer *cont); - ResponseCode cmdShutdownServer(Command_ShutdownServer *cmd, CommandContainer *cont); - ResponseCode cmdUpdateServerMessage(Command_UpdateServerMessage *cmd, CommandContainer *cont); -protected: - bool getCompressionSupport() const { return compressionSupport; } + Response::ResponseCode cmdDeckDownload(const Command_DeckDownload &cmd, ResponseContainer &rc); + Response::ResponseCode cmdReplayList(const Command_ReplayList &cmd, ResponseContainer &rc); + Response::ResponseCode cmdReplayDownload(const Command_ReplayDownload &cmd, ResponseContainer &rc); + Response::ResponseCode cmdReplayModifyMatch(const Command_ReplayModifyMatch &cmd, ResponseContainer &rc); + Response::ResponseCode cmdReplayDeleteMatch(const Command_ReplayDeleteMatch &cmd, ResponseContainer &rc); + Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc); + Response::ResponseCode cmdShutdownServer(const Command_ShutdownServer &cmd, ResponseContainer &rc); + Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc); + + Response::ResponseCode processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc); + Response::ResponseCode processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc); + Response::ResponseCode processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc); public: - ServerSocketInterface(Servatrice *_server, QTcpSocket *_socket, QObject *parent = 0); + ServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface, QObject *parent = 0); ~ServerSocketInterface(); + void initSessionDeprecated(); + bool initSession(); QHostAddress getPeerAddress() const { return socket->peerAddress(); } QString getAddress() const { return socket->peerAddress().toString(); } - void sendProtocolItem(ProtocolItem *item, bool deleteItem = true); + void transmitProtocolItem(const ServerMessage &item); +public slots: + void initConnection(int socketDescriptor); }; #endif diff --git a/servatrice/src/serversocketthread.cpp b/servatrice/src/serversocketthread.cpp deleted file mode 100644 index 9c08ce1b9..000000000 --- a/servatrice/src/serversocketthread.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "serversocketthread.h" -#include "serversocketinterface.h" -#include "server_logger.h" -#include "main.h" - -ServerSocketThread::ServerSocketThread(int _socketDescriptor, Servatrice *_server, QObject *parent) - : QThread(parent), server(_server), socketDescriptor(_socketDescriptor) -{ -} - -ServerSocketThread::~ServerSocketThread() -{ - quit(); - wait(); -} - -void ServerSocketThread::run() -{ - QTcpSocket *socket = new QTcpSocket; - socket->setSocketDescriptor(socketDescriptor); - logger->logMessage(QString("incoming connection: %1").arg(socket->peerAddress().toString())); - - ssi = new ServerSocketInterface(server, socket); - connect(ssi, SIGNAL(destroyed()), this, SLOT(deleteLater())); - - exec(); -} diff --git a/servatrice/src/serversocketthread.h b/servatrice/src/serversocketthread.h deleted file mode 100644 index 2aa6dc8bd..000000000 --- a/servatrice/src/serversocketthread.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef SERVERSOCKETTHREAD_H -#define SERVERSOCKETTHREAD_H - -#include - -class Servatrice; -class ServerSocketInterface; - -class ServerSocketThread : public QThread { - Q_OBJECT -private: - Servatrice *server; - ServerSocketInterface *ssi; - int socketDescriptor; -public: - ServerSocketThread(int _socketDescriptor, Servatrice *_server, QObject *parent = 0); - ~ServerSocketThread(); -protected: - void run(); -}; - -#endif diff --git a/sounds/CMakeLists.txt b/sounds/CMakeLists.txt new file mode 100644 index 000000000..cd9f6c331 --- /dev/null +++ b/sounds/CMakeLists.txt @@ -0,0 +1,16 @@ +# CMakeLists for sounds/ directory +# +# Installs default sound files + +FILE(GLOB sounds "${CMAKE_CURRENT_SOURCE_DIR}/*.raw") + +if(UNIX) + if(APPLE) + INSTALL(FILES ${sounds} DESTINATION cockatrice.app/Contents/Resources/sounds/) + else() + # Assume linux + INSTALL(FILES ${sounds} DESTINATION share/cockatrice/sounds/) + endif() +elseif(WIN32) + INSTALL(FILES ${sounds} DESTINATION sounds/) +endif() \ No newline at end of file diff --git a/travis-dependencies.sh b/travis-dependencies.sh new file mode 100755 index 000000000..10af11cd0 --- /dev/null +++ b/travis-dependencies.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [[ $TRAVIS_OS_NAME == "osx" ]] ; then + brew update + brew install qt protobuf libgcrypt +else + sudo apt-get update -qq + sudo apt-get install -y qtmobility-dev libprotobuf-dev protobuf-compiler libqt4-dev +fi diff --git a/zonebg/CMakeLists.txt b/zonebg/CMakeLists.txt new file mode 100644 index 000000000..b250a44b0 --- /dev/null +++ b/zonebg/CMakeLists.txt @@ -0,0 +1,16 @@ +# CMakeLists for zonebg/ directory +# +# Installs default "zone background" files + +FILE(GLOB zonebg "${CMAKE_CURRENT_SOURCE_DIR}/*.png" "${CMAKE_CURRENT_SOURCE_DIR}/*.jpg") + +if(UNIX) + if(APPLE) + INSTALL(FILES ${zonebg} DESTINATION cockatrice.app/Contents/Resources/zonebg/) + else() + # Assume linux + INSTALL(FILES ${zonebg} DESTINATION share/cockatrice/zonebg/) + endif() +elseif(WIN32) + INSTALL(FILES ${zonebg} DESTINATION zonebg/) +endif() \ No newline at end of file