mirror of
git://git.savannah.nongnu.org/eliot.git
synced 2024-12-25 21:59:30 +01:00
- Players can now have a name
- Use player names in the ncurses interface - In training mode, create the hidden player in the constructor, not in start() - When the AI has nothing to play, change the letters instead of simply passing - New Makefile to build the win32 dependencies automatically (INSTALL file updated)
This commit is contained in:
parent
8c3708fa99
commit
0a4b342f78
9 changed files with 247 additions and 91 deletions
117
INSTALL
117
INSTALL
|
@ -1,83 +1,86 @@
|
|||
Installation on Linux/Unix
|
||||
==========================
|
||||
|
||||
Installation sous Linux (Un*x) (bien/facile)
|
||||
------------------------------
|
||||
* Pour installer à partir de l'archive CVS :
|
||||
In the following, do not forget that the ./configure command can take options.
|
||||
Run ./configure --help to have the list of available options.
|
||||
|
||||
./bootstrap
|
||||
* If you build from a CVS snapshot, run the following commands:
|
||||
./bootstrap
|
||||
./configure
|
||||
make
|
||||
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
Then, as root:
|
||||
make install
|
||||
|
||||
* Pour installer à partir de l'archive tar.gz
|
||||
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
No graphical interface is built by default, see below for more details.
|
||||
|
||||
|
||||
Il existe en fait 3 versions d'eliot, une en mode texte, une avec une
|
||||
interface curses et une avec wxwidgets. Les modes peuvent être
|
||||
sélectionnés à l'aide de la commande configure lors de la compilation
|
||||
du programme.
|
||||
* If you build from a release tarball, run the following commands:
|
||||
./configure
|
||||
make
|
||||
|
||||
./configure --enable-text --enable-ncurses --enable-wxwidgets
|
||||
Then, as root:
|
||||
make install
|
||||
|
||||
No graphical interface is built by default, see below for more details.
|
||||
|
||||
|
||||
There are in fact several interfaces to Eliot:
|
||||
- one in text mode: mostly useful to debug Eliot
|
||||
- one using the ncursesw library: nice but not really graphical
|
||||
- a wxWidgets interface: complete, but it does not allow multiplayer
|
||||
modes, only training mode
|
||||
- a Qt interface: currently under construction, it will replace the
|
||||
wxWidgets interface when finished. It will support all Eliot features.
|
||||
|
||||
These interfaces can be enabled or disabled at configuration time. Example:
|
||||
./configure --disable-text --enable-ncurses --disable-wxwidgets --enable-qt
|
||||
|
||||
|
||||
|
||||
Installation pour Windows (moins bien/facile)
|
||||
-------------------------
|
||||
Windows build
|
||||
=============
|
||||
|
||||
Il y a 2 principales façons de procéder :
|
||||
* directement depuis Windows, en utilisant Cygwin (http://www.cygwin.com/).
|
||||
* depuis GNU/Linux, en utilisant le cross-compilateur Mingw32.
|
||||
There are 2 ways to proceed:
|
||||
* cross-compilation from a Linux host, using the mingw32 cross-compiler
|
||||
* directly on Windows, using Cygwin (http://www.cygwin.com/)
|
||||
Only the cross-compilation is officially supported (but adapting these
|
||||
instructions for Cygwin shouldn't be too hard; patches welcome!).
|
||||
|
||||
Here are the steps for the cross-compilation:
|
||||
|
||||
Dans les 2 cas, les étapes sont les mêmes :
|
||||
* installation de l'environnement de compilation (cette étape n'est pas
|
||||
décrite ici, car elle ne rentre pas dans le cadre de ce document)
|
||||
* install the build environment (this step is not documented here,
|
||||
as it is out of the scope of this document)
|
||||
|
||||
* compilation et installation des dépendances (même remarque):
|
||||
* build and install dependencies:
|
||||
The Makefile in the 'extras/contrib' directory should be able to do it for you:
|
||||
|
||||
- wxWidgets (http://www.wxwidgets.org/), version 2.4.2 ou ultérieure, avec
|
||||
support de l'unicode
|
||||
cd extras/contrib && make all
|
||||
|
||||
- libiconv (http://www.gnu.org/software/libiconv/), de préférence compilée
|
||||
en mode statique (--disable-shared --enable-static)
|
||||
Eliot dependencies (libiconv, boost, wxWidgets and Qt) will be downloaded
|
||||
and cross-compiled, except Qt, which is only downloaded. Install it with
|
||||
Wine, ignoring the warning that mingw is not found.
|
||||
The dependencies are installed in 'extras/contrib/inst'
|
||||
|
||||
- boost (http://www.boost.org/). Eliot n'utilise pas de librairie de Boost
|
||||
(uniquement des headers), donc il n'y a pas vraiment besoin de compiler
|
||||
* build Eliot:
|
||||
|
||||
* compilation d'Eliot :
|
||||
|
||||
- si vous utilisez l'archive CVS, il faut générer le script 'configure'
|
||||
(aussi bien sous Cygwin que sous GNU/Linux) :
|
||||
- if you don't have the 'configure' script, generate it:
|
||||
./bootstrap
|
||||
|
||||
- à cause d'un bug de gettext, il faut appliquer un patch aux fichiers installés
|
||||
dans intl/ :
|
||||
- télécharger le patch ici (lien en haut à gauche) :
|
||||
- because of a bug in gettext, you need to apply a little patch to the files
|
||||
installed in the 'intl' directory:
|
||||
- download the patch here (link in the top-left-hand corner)
|
||||
http://www.koders.com/noncode/fid46DF595700FEB564B6EF45BFF55067F95DCF0420.aspx
|
||||
- exécuter la commande suivante :
|
||||
- apply the patch:
|
||||
patch -p2 < gettext-win32.patch
|
||||
|
||||
- avec Cygwin, configurer avec la ligne de commande suivante :
|
||||
CPPFLAGS=-I/path/to/installs/include LDFLAGS=-L/path/to/installs/lib \
|
||||
CC="gcc -mno-cygwin" CXX="g++ -mno-cygwin" \
|
||||
./configure --enable-wxwidgets --with-wx-config=/path/to/wx-config \
|
||||
--with-boost=/path/to/boost/installs
|
||||
en prenant soin de remplacer les différents chemins par les bonnes valeurs.
|
||||
Ensuite, un simple 'make' suffit pour terminer la compilation,
|
||||
éventuellement suivi de 'make install'.
|
||||
|
||||
- pour la cross-compilation depuis GNU/Linux, configurer avec la ligne
|
||||
de commande suivante :
|
||||
CPPFLAGS=-I/path/to/installs/include LDFLAGS=-L/path/to/installs/lib \
|
||||
- configure with the following command:
|
||||
export INST=`pwd`/extras/contrib/inst && \
|
||||
CPPFLAGS=-I${INST}/include LDFLAGS=-L${INST}/lib \
|
||||
CC=i586-mingw32msvc-gcc CXX=i586-mingw32msvc-g++ \
|
||||
./configure --host=i586-mingw32msvc --build=i386-linux \
|
||||
--enable-wxwidgets --with-wx-config=/path/to/wx-config \
|
||||
--with-boost=/path/to/installs
|
||||
en prenant soin de remplacer les différents chemins par les bonnes valeurs.
|
||||
Ensuite, un simple 'make' suffit pour terminer la compilation,
|
||||
éventuellement suivi de 'make install'.
|
||||
--enable-wxwidgets --with-wx-config=${INST}/bin/wx-config \
|
||||
--with-boost=${INST}
|
||||
|
||||
- to compile, run 'make', possibly followed with 'make install'
|
||||
|
||||
|
|
85
extras/contrib/Makefile
Normal file
85
extras/contrib/Makefile
Normal file
|
@ -0,0 +1,85 @@
|
|||
ICONV_VERSION = 1.12
|
||||
WX_VERSION = 2.6.4
|
||||
BOOST_VERSION = 1_34_1
|
||||
QT_VERSION = 4.3.3
|
||||
|
||||
PREFIX = $(shell pwd)/inst
|
||||
WGET = wget -c
|
||||
|
||||
# XXX: Hardcoded for mingw on linux, at the moment
|
||||
CC = i586-mingw32msvc-gcc
|
||||
CXX = i586-mingw32msvc-g++
|
||||
CPPFLAGS += -I$(PREFIX)/include
|
||||
CONFIGURE = CC=$(CC) CXX=$(CXX) CPPFLAGS=$(CPPFLAGS) ./configure --host=i586-mingw32msvc --build=i386-linux --prefix=$(PREFIX)
|
||||
|
||||
.PHONY: help all
|
||||
|
||||
help:
|
||||
echo "Usage: make all"
|
||||
|
||||
all: .iconv .wxWidgets .boost .qt
|
||||
|
||||
|
||||
### iconv ###
|
||||
|
||||
ICONV_DIR = libiconv-$(ICONV_VERSION)
|
||||
ICONV_ARCHIVE = libiconv-$(ICONV_VERSION).tar.gz
|
||||
|
||||
$(ICONV_DIR):
|
||||
$(WGET) http://ftp.gnu.org/pub/gnu/libiconv/$(ICONV_ARCHIVE)
|
||||
tar xzf $(ICONV_ARCHIVE)
|
||||
|
||||
.iconv: $(ICONV_DIR)
|
||||
(cd $< && $(CONFIGURE) --enable-static --disable-shared && make && make install)
|
||||
touch $@
|
||||
|
||||
|
||||
### wxWidgets ###
|
||||
|
||||
WX_ARCHIVE = wxMSW-$(WX_VERSION).zip
|
||||
WX_DIR = wxWidgets-$(WX_VERSION)
|
||||
WX_DOS_FILES = config* *.sh install-sh mkinstalldirs
|
||||
|
||||
$(WX_DIR):
|
||||
$(WGET) http://heanet.dl.sourceforge.net/sourceforge/wxwindows/$(WX_ARCHIVE)
|
||||
unzip $(WX_ARCHIVE)
|
||||
|
||||
.wxWidgets: $(WX_DIR)
|
||||
(cd $< && dos2unix $(WX_DOS_FILES) && chmod +x $(WX_DOS_FILES))
|
||||
(cd $< && $(CONFIGURE) --disable-shared --enable-optimise --disable-debug --enable-unicode --without-libtiff --without-expat --without-zlib --without-libpng --without-libjpeg --without-regex --disable-mediactrl && make && make install)
|
||||
dos2unix $(PREFIX)/bin/wx-config
|
||||
touch $@
|
||||
|
||||
|
||||
### Boost ###
|
||||
|
||||
BOOST_DIR = boost_$(BOOST_VERSION)
|
||||
BOOST_ARCHIVE = boost_$(BOOST_VERSION).tar.bz2
|
||||
|
||||
$(BOOST_DIR):
|
||||
$(WGET) http://garr.dl.sourceforge.net/sourceforge/boost/$(BOOST_ARCHIVE)
|
||||
tar xjf $(BOOST_ARCHIVE)
|
||||
|
||||
# We don't build any library, because we don't need them (and it is really
|
||||
# hard to cross-compile with their crappy build system)
|
||||
.boost: $(BOOST_DIR)
|
||||
#(cd $< && ./configure --prefix=$(PREFIX) && ./tools/jam/src/bin.linuxx86/bjam --toolset=gcc --prefix=$(PREFIX) --without-date_time --without-filesystem --without-graph --without-iostreams --without-program_options --without-python --without-regex --without-serialization --without-signals --without-test --without-thread --without-wave install)
|
||||
cp -r $</boost $(PREFIX)/include
|
||||
touch $@
|
||||
|
||||
|
||||
|
||||
### Qt ###
|
||||
|
||||
# FIXME: No automated way at the moment :-(
|
||||
QT_ARCHIVE = qt-win-opensource-$(QT_VERSION)-mingw.exe
|
||||
|
||||
$(QT_ARCHIVE):
|
||||
$(WGET) ftp://ftp.trolltech.com/qt/source/qt-win-opensource-4.3.3-mingw.exe
|
||||
|
||||
.qt: $(QT_ARCHIVE)
|
||||
@echo "=============== Important note ==============="
|
||||
@echo "You need to install $(QT_ARCHIVE) yourself using wine!"
|
||||
@echo "=============================================="
|
||||
touch $@
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
noinst_LIBRARIES = libgame.a
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/dic
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/dic -I../intl -I$(top_srcdir)/intl
|
||||
|
||||
libgame_a_SOURCES= \
|
||||
ai_percent.cpp ai_percent.h \
|
||||
|
|
|
@ -53,9 +53,12 @@ Move AIPercent::getMove() const
|
|||
{
|
||||
if (m_results.size() == 0)
|
||||
{
|
||||
// If there is no result, simply pass the turn
|
||||
// XXX: it is forbidden in duplicate mode, but well, what else to do?
|
||||
return Move(L"");
|
||||
// If there is no result, change all the letters
|
||||
// XXX: it is forbidden in duplicate mode (even passing is forbidden),
|
||||
// but well, what else to do?
|
||||
Rack rack;
|
||||
getCurrentRack().getRack(rack);
|
||||
return Move(rack.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -26,18 +26,27 @@
|
|||
#include <fstream>
|
||||
#include <exception>
|
||||
|
||||
#if ENABLE_NLS
|
||||
# include <libintl.h>
|
||||
# define _(String) gettext(String)
|
||||
#else
|
||||
# define _(String) String
|
||||
#endif
|
||||
|
||||
#include "game_factory.h"
|
||||
#include "game.h"
|
||||
#include "training.h"
|
||||
#include "freegame.h"
|
||||
#include "duplicate.h"
|
||||
#include "player.h"
|
||||
#include "dic.h"
|
||||
#include "encoding.h"
|
||||
|
||||
|
||||
GameFactory *GameFactory::m_factory = NULL;
|
||||
|
||||
|
||||
GameFactory::GameFactory(): m_dic(NULL), m_human(0), m_ai(0), m_joker(false)
|
||||
GameFactory::GameFactory(): m_dic(NULL), m_joker(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -94,12 +103,12 @@ Game *GameFactory::createFromCmdLine(int argc, char **argv)
|
|||
{"dictionary", required_argument, NULL, 'd'},
|
||||
{"dict", required_argument, NULL, 'd'},
|
||||
{"mode", required_argument, NULL, 'm'},
|
||||
{"human", no_argument, NULL, 300},
|
||||
{"ai", no_argument, NULL, 400},
|
||||
{"human", required_argument, NULL, 'u'},
|
||||
{"ai", required_argument, NULL, 'a'},
|
||||
{"joker", no_argument, NULL, 500},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
static char short_options[] = "hvd:m:";
|
||||
static char short_options[] = "hvd:m:u:a:";
|
||||
|
||||
int option_index = 1;
|
||||
int res;
|
||||
|
@ -126,11 +135,22 @@ Game *GameFactory::createFromCmdLine(int argc, char **argv)
|
|||
m_modeStr = optarg;
|
||||
found_m = true;
|
||||
break;
|
||||
case 300:
|
||||
m_human++;
|
||||
break;
|
||||
case 400:
|
||||
m_ai++;
|
||||
case 'u':
|
||||
case 'a':
|
||||
// Handle both types of players together
|
||||
{
|
||||
wstring name;
|
||||
if (optarg == NULL)
|
||||
{
|
||||
// TODO: use Boost.Format
|
||||
char s[200];
|
||||
snprintf(s, 200, _("Player %u"), m_players.size() + 1);
|
||||
name = convertToWc(s);
|
||||
}
|
||||
else
|
||||
name = convertToWc(optarg);
|
||||
m_players.push_back(make_pair<bool, wstring>(res == 'u', name));
|
||||
}
|
||||
break;
|
||||
case 500:
|
||||
m_joker = true;
|
||||
|
@ -184,10 +204,15 @@ Game *GameFactory::createFromCmdLine(int argc, char **argv)
|
|||
}
|
||||
|
||||
// 5) Add the players
|
||||
for (int i = 0; i < m_human; i++)
|
||||
game->addHumanPlayer();
|
||||
for (int i = 0; i < m_ai; i++)
|
||||
game->addAIPlayer();
|
||||
for (unsigned int i = 0; i < m_players.size(); ++i)
|
||||
{
|
||||
// Human?
|
||||
if (m_players[i].first)
|
||||
game->addHumanPlayer();
|
||||
else
|
||||
game->addAIPlayer();
|
||||
const_cast<Player*>(&game->getPlayer(i))->setName(m_players[i].second);
|
||||
}
|
||||
|
||||
// 6) Set the variant
|
||||
if (m_joker)
|
||||
|
@ -225,9 +250,9 @@ void GameFactory::printUsage(const string &iBinaryName) const
|
|||
<< " -v, --version Print version information and exit" << endl
|
||||
<< " -m, --mode {duplicate,d,freegame,f,training,t}" << endl
|
||||
<< " Choose game mode (mandatory)" << endl
|
||||
<< " -d, --dict <string> Choose a dictionary (mandatory)" << endl
|
||||
<< " --human Add a human player" << endl
|
||||
<< " --ai Add a AI (Artificial Intelligence) player" << endl
|
||||
<< " -d, --dict <path> Choose a dictionary (mandatory)" << endl
|
||||
<< " -u --human <name> Add a human player" << endl
|
||||
<< " -a --ai <name> Add a AI (Artificial Intelligence) player" << endl
|
||||
<< " --joker Play with the \"Joker game\" variant" << endl;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,12 @@
|
|||
#define _GAME_FACTORY_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using std::string;
|
||||
using std::wstring;
|
||||
using std::vector;
|
||||
using std::pair;
|
||||
|
||||
class Dictionary;
|
||||
class Game;
|
||||
|
@ -85,15 +89,12 @@ private:
|
|||
/// Game mode
|
||||
string m_modeStr;
|
||||
|
||||
/// Number of human players
|
||||
int m_human;
|
||||
|
||||
/// Number of AI players
|
||||
int m_ai;
|
||||
|
||||
/// Variant of the game
|
||||
bool m_joker;
|
||||
|
||||
typedef pair<bool, wstring> PlayerDesc;
|
||||
vector<PlayerDesc> m_players;
|
||||
|
||||
//@}
|
||||
|
||||
/// Print command-line usage
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
#include "pldrack.h"
|
||||
#include "history.h"
|
||||
|
||||
using std::wstring;
|
||||
|
||||
class Turn;
|
||||
|
||||
|
||||
|
@ -43,12 +45,17 @@ public:
|
|||
// Pseudo RTTI
|
||||
virtual bool isHuman() const = 0;
|
||||
|
||||
/// Get the name of the player
|
||||
const wstring & getName() const { return m_name; }
|
||||
/// Set the name of the player
|
||||
void setName(const wstring &iName) { m_name = iName; }
|
||||
|
||||
/**************************
|
||||
* General getters
|
||||
**************************/
|
||||
// Get the (possibly incomplete) rack of the player
|
||||
/// Get the (possibly incomplete) rack of the player
|
||||
const PlayedRack & getCurrentRack() const;
|
||||
// Get the previous rack
|
||||
/// Get the previous rack
|
||||
const PlayedRack & getLastRack() const;
|
||||
/// Get the previous move (corresponding to the previous rack...)
|
||||
const Move & getLastMove() const;
|
||||
|
@ -56,6 +63,7 @@ public:
|
|||
void setCurrentRack(const PlayedRack &iPld);
|
||||
|
||||
const History& getHistory() const { return m_history; }
|
||||
|
||||
/// Remove last turn
|
||||
void removeLastTurn();
|
||||
|
||||
|
@ -84,6 +92,9 @@ private:
|
|||
/// Score of the player
|
||||
int m_score;
|
||||
|
||||
/// Name of the player
|
||||
wstring m_name;
|
||||
|
||||
/// History of the racks and rounds for the player
|
||||
History m_history;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,13 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#if ENABLE_NLS
|
||||
# include <libintl.h>
|
||||
# define _(String) gettext(String)
|
||||
#else
|
||||
# define _(String) String
|
||||
#endif
|
||||
|
||||
#include "dic.h"
|
||||
#include "tile.h"
|
||||
#include "rack.h"
|
||||
|
@ -37,6 +44,9 @@
|
|||
Training::Training(const Dictionary &iDic)
|
||||
: Game(iDic)
|
||||
{
|
||||
// Training mode implicitly uses 1 human player
|
||||
Game::addHumanPlayer();
|
||||
m_players[0]->setName(convertToWc(_("Training")));
|
||||
}
|
||||
|
||||
|
||||
|
@ -124,8 +134,6 @@ int Training::start()
|
|||
if (getNPlayers() != 0)
|
||||
return 1;
|
||||
|
||||
// Training mode implicitly uses 1 human player
|
||||
Game::addHumanPlayer();
|
||||
m_currPlayer = 0;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include <ctype.h>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ncurses.h"
|
||||
#include "dic.h"
|
||||
|
@ -266,14 +267,27 @@ void CursesIntf::drawBoard(WINDOW *win, int y, int x) const
|
|||
|
||||
void CursesIntf::drawScoresRacks(WINDOW *win, int y, int x) const
|
||||
{
|
||||
// Compute the longest player name
|
||||
unsigned int longest = 0;
|
||||
for (unsigned int i = 0; i < m_game->getNPlayers(); i++)
|
||||
{
|
||||
longest = std::max(longest, m_game->getPlayer(i).getName().size());
|
||||
}
|
||||
|
||||
Box box(win, y, x, m_game->getNPlayers() + 2, 25);
|
||||
box.draw(_("Scores"));
|
||||
// Magic formula to truncate too long names
|
||||
unsigned int maxForScores =
|
||||
std::min(longest,
|
||||
box.getWidth() - strlen(_("%s: %d")) - 1);
|
||||
for (unsigned int i = 0; i < m_game->getNPlayers(); i++)
|
||||
{
|
||||
if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer())
|
||||
attron(A_BOLD);
|
||||
mvwprintw(win, y + i + 1, x + 2,
|
||||
_("Player %d: %d"), i, m_game->getPlayer(i).getPoints());
|
||||
mvwprintw(win, y + i + 1, x + 2, _("%s: %d"),
|
||||
truncOrPad(convertToMb(m_game->getPlayer(i).getName()),
|
||||
maxForScores).c_str(),
|
||||
m_game->getPlayer(i).getPoints());
|
||||
if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer())
|
||||
attroff(A_BOLD);
|
||||
}
|
||||
|
@ -283,13 +297,19 @@ void CursesIntf::drawScoresRacks(WINDOW *win, int y, int x) const
|
|||
|
||||
Box box2(win, y + yOff, x, m_game->getNPlayers() + 2, 25);
|
||||
box2.draw(_("Racks"));
|
||||
// Magic formula to truncate too long names
|
||||
unsigned int maxForRacks =
|
||||
std::min(longest,
|
||||
box.getWidth() - strlen(_("%s: %ls")) - 4);
|
||||
for (unsigned int i = 0; i < m_game->getNPlayers(); i++)
|
||||
{
|
||||
if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer())
|
||||
attron(A_BOLD);
|
||||
wstring rack = m_game->getPlayer(i).getCurrentRack().toString(PlayedRack::RACK_SIMPLE);
|
||||
mvwprintw(win, y + yOff + i + 1, x + 2,
|
||||
_("Player %d: %ls"), i, rack.c_str());
|
||||
mvwprintw(win, y + yOff + i + 1, x + 2, _("%s: %ls"),
|
||||
truncOrPad(convertToMb(m_game->getPlayer(i).getName()),
|
||||
maxForRacks).c_str(),
|
||||
rack.c_str());
|
||||
if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer())
|
||||
attroff(A_BOLD);
|
||||
// Force to refresh the whole rack
|
||||
|
|
Loading…
Reference in a new issue