Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM mcr.microsoft.com/devcontainers/php:7.4

# Remove Yarn repo, then install Node.js 18
#RUN rm -f /etc/apt/sources.list.d/*yarn* && \
# curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
# apt-get update && \
# apt-get install -y nodejs npm && \
# docker-php-ext-install pdo_mysql && \
# rm -rf /var/lib/apt/lists/*

# Install Node.js 18 using official binary distribution
RUN curl -fsSL https://nodejs.org/dist/v18.19.1/node-v18.19.1-linux-x64.tar.xz | tar -xJ -C /usr/local --strip-components=1 && \
docker-php-ext-install pdo_mysql && \
rm -rf /var/lib/apt/lists/*
18 changes: 18 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "ThronesDB",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"forwardPorts": [8000, 3306],
"customizations": {
"vscode": {
"extensions": [
"felixbecker.php-debug",
"ms-vscode.php-tools",
"eamodio.gitlens"
]
}
},
"postCreateCommand": "bash .devcontainer/post-create.sh",
"postStartCommand": "bash .devcontainer/post-start.sh"
}
22 changes: 22 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '3.9'
services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
- ..:/workspace:cached
environment:
DATABASE_URL: mysql://app:password@db:3306/thronesdb
depends_on:
- db

db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: thronesdb
MYSQL_USER: app
MYSQL_PASSWORD: password
ports:
- "3307:3306"
55 changes: 55 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash
set -e

echo "Waiting for MySQL to be ready..."
for i in {1..30}; do
if mysqladmin ping -h db -u app -ppassword &> /dev/null; then
echo "MySQL is ready!"
break
fi
echo "Waiting... attempt $i/30"
sleep 2
done

echo "Installing PHP dependencies..."
composer install

echo "Installing Node dependencies..."
npm install

echo "Creating database..."
php bin/console doctrine:database:create --if-not-exists

echo "Running migrations..."
php bin/console doctrine:migrations:migrate

echo "Loading fixtures..."
php bin/console doctrine:fixtures:load --env=dev

echo "Cloning json data repository..."
if [ ! -d "/workspace/throneteki-json-data" ]; then
git clone https://github.com/kayorga/throneteki-json-data.git -b draft throneteki-json-data
fi

echo "Importing json data..."
php bin/console app:import:std /workspace/throneteki-json-data

echo "Importing restriction lists..."
php bin/console app:restrictions:import /workspace/throneteki-json-data

echo "Activating restriction list..."
php bin/console app:restrictions:activate

echo "Dumping translations..."
php bin/console bazinga:js-translation:dump assets/js

echo "Dumping routes..."
php bin/console fos:js-routing:dump --target=public/js/fos_js_routes.js

echo "Building assets..."
npx gulp

echo "Clearing cache..."
php bin/console cache:clear --env=dev

echo "Setup complete!"
13 changes: 13 additions & 0 deletions .devcontainer/post-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
set -e

echo "Creating user..."
php bin/console fos:user:create dev dev@thronesdb.com password123 --no-interaction

echo "Activating user..."
php bin/console fos:user:activate dev

echo "Promoting user..."
php bin/console fos:user:promote --super dev

echo "Setup complete!"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

# ignore FOS JS routing output
/public/js/fos_js_routes.js
/web/js/fos_js_routes.js

# ignore node modules
/node_modules
Expand Down
17 changes: 17 additions & 0 deletions assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,23 @@ ol {
height: 70px;
}

.qty-select {
width: auto;
display: inline-block;
padding: 2px 6px;
font-size: 12px;
height: 24px;
line-height: 1.5;
min-width: 69px;
}
.modal .qty-select {
font-size: 14px;
padding: 6px 12px;
min-width: 90px;
height: auto;
line-height: 1.5;
}

/** Description panel **/
div#description img,
div#description-preview img {
Expand Down
16 changes: 12 additions & 4 deletions assets/js/app.card_modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,19 @@
var qtyelt = modal.find('.modal-qty');
if(qtyelt) {

var qty = '';
for(var i = 0; i <= card.maxqty; i++) {
qty += '<label class="btn btn-default"><input type="radio" name="qty" value="' + i + '">' + i + '</label>';
var qtyHtml = '';
if(card.maxqty > 3) {
qtyHtml = '<select class="qty-select form-control">';
for(var i = 0; i <= card.maxqty; i++) {
qtyHtml += '<option value="' + i + '"' + (i === card.indeck ? ' selected' : '') + '>' + i + '</option>';
}
qtyHtml += '</select>';
} else {
for(var i = 0; i <= card.maxqty; i++) {
qtyHtml += '<label class="btn btn-default"><input type="radio" name="qty" value="' + i + '">' + i + '</label>';
}
}
qtyelt.html(qty);
qtyelt.html(qtyHtml);

qtyelt.find('label').each(function (index, element)
{
Expand Down
110 changes: 97 additions & 13 deletions assets/js/app.deck.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@
}

/*
* Checks a given card's text has the "Shadow" keyword.
* Checks a given card's text has the given keyword.
* @param {Object} card
* @param {String} shadow The i18n'ed word "Shadow".
* @param {String} keyword The i18n'ed word for a keyword.
* @returns {boolean}
*/
var card_has_shadow_keyword = function(card, shadow) {
// "Shadow (<cost>).", with <cost> being either digits or the letter "X"
var regex = new RegExp(shadow + ' \\(([0-9]+|X)\\)\\.');
var card_has_keyword = function(card, keyword) {
// "Keyword (<cost>).", with <cost> being either digits or the letter "X"
var regex = new RegExp(keyword + ' \\(([0-9]+|X)\\)\\.');
var text = card.text || '';
// check if first line in the card text has that keyword.
var textLines = text.split("\n");
Expand Down Expand Up @@ -627,6 +627,12 @@
{
nb_packs[card.pack_code] = Math.max(nb_packs[card.pack_code] || 0, card.indeck / card.quantity);
});

// for draft packs, do not calculate pack number
['ToJ', 'VDS'].forEach(function(pack_code){
nb_packs[pack_code] = 1;
});

var pack_codes = _.uniq(_.pluck(cards, 'pack_code'));
var packs = app.data.packs.find({
'code': {
Expand Down Expand Up @@ -1027,12 +1033,6 @@
{
return [
'00030', // The King's Voice (VHotK)
'00362', // Sealing the Pact (ToJ)
'00363', // Unknown and Unknowable (ToJ)
'00364', // Pass Beneath a Shadow (ToJ)
'00365', // Seeking Fortunes (ToJ)
'00366', // Join Forces (ToJ)
'00367', // Desperate Hope (ToJ)
];
};

Expand Down Expand Up @@ -1065,6 +1065,8 @@
expectedMinCardCount = 100;
} else if (agenda && agenda.code === '21030') {
expectedPlotDeckSize = 10;
} else if (agenda && ['00362', '00363', '00364', '00365', '00366', '00367'].indexOf(agenda.code) > -1) {
expectedMinCardCount = 40;
}
});
// exactly 7 plots
Expand Down Expand Up @@ -1345,6 +1347,56 @@
})) >= 12;
}

var validate_sealing_the_pact = function() {
var outOfFactionCards = deck.get_cards(
null,
{faction_code: {$nin: [deck.get_faction_code(), 'neutral']}},
);
var minorFactions = [];
outOfFactionCards.forEach(function(card) {
if (!minorFactions.includes(card.faction_code)) {
minorFactions.push(card.faction_code);
}
});
return minorFactions.length <= 1;
}

var validate_unknown_and_unknowable = function() {
var outOfFactionCards = deck.get_cards(
null,
{faction_code: {$nin: [deck.get_faction_code(), 'neutral']}},
);
var minorFactions = [];
outOfFactionCards.forEach(function(card) {
if (!minorFactions.includes(card.faction_code)) {
minorFactions.push(card.faction_code);
}
});
return minorFactions.length <= 2;
}

var validate_join_forces = function() {
var outOfFactionCards = deck.get_cards(
null,
{faction_code: {$nin: [deck.get_faction_code(), 'neutral']}},
);
var commonTraits = [];
outOfFactionCards.forEach(function(card) {
var traits = card.traits.split('.')
.map(function(trait) { return trait.trim(); })
.filter(function(trait) { return trait.length > 0; });
if (commonTraits.length === 0) {
commonTraits = traits;
} else {
commonTraits = _.intersection(commonTraits, traits);
if (commonTraits.length === 0) {
return;
}
}
});
return commonTraits.length > 0;
}

switch(agenda.code) {
case '01027':
if(deck.get_nb_cards(deck.get_cards(null, {type_code: {$in: ['character', 'attachment', 'location', 'event']}, faction_code: 'neutral'})) > 15) {
Expand Down Expand Up @@ -1439,6 +1491,12 @@
return validate_sight_of_the_three_eyed_crow();
case '27620':
return validate_streets_of_kings_landing();
case '00362':
return validate_sealing_the_pact();
case '00363':
return validate_unknown_and_unknowable();
case '00366':
return validate_join_forces();
}
return true;
};
Expand Down Expand Up @@ -1500,6 +1558,23 @@
{
var agendas = deck.get_agendas();
var agendaCodes = agendas.map(function(agenda) { return agenda.code });

// non-ToJ cards with a ToJ agenda => no
var tojAgendaCodes = ['00362', '00363', '00364', '00365', '00366', '00367'];
if(card.pack_code !== 'ToJ' && agendaCodes.some(function(code) { return tojAgendaCodes.includes(code) })) {
return false;
}

// variant draw deck cards with a non-variant agenda => no
var variantAgendaCodes = ['00001', '00002', '00003', '00004', '00030'];
var variantPackCodes = ['VDS', 'ToJ'];
if (variantPackCodes.includes(card.pack_code) && agendaCodes.every(
function(code) {
return !variantAgendaCodes.includes(code) && !tojAgendaCodes.includes(code);
})) {
return false;
}

// neutral card => yes, unless the agenda is redesigned "Sea of Blood" and the card is an event.
if(card.faction_code === 'neutral') {
return !(-1 !== agendaCodes.indexOf('17149') && card.type_code === 'event');
Expand All @@ -1510,7 +1585,7 @@
return true;

// out-of-house and loyal => no
if(card.is_loyal)
if(card.is_loyal && card.pack_code !== 'ToJ')
return false;

// agenda => yes
Expand Down Expand Up @@ -1544,7 +1619,7 @@
return card.type_code === 'character' && card.traits.indexOf(Translator.trans('card.traits.maester')) !== -1;
case '13079': // Kingdom of Shadows (BtRK)
case '17148': // Kingdom of Shadows (R)
return card.type_code === 'character' && card_has_shadow_keyword(card, Translator.trans('card.keywords.shadow'));
return card.type_code === 'character' && card_has_keyword(card, Translator.trans('card.keywords.shadow'));
case '13099':
return card.type_code === 'character' && card.traits.indexOf(Translator.trans('card.traits.kingsguard')) !== -1;
case '17150':
Expand All @@ -1560,6 +1635,15 @@
return card.type_code === 'location' && card.traits.indexOf(Translator.trans('card.traits.warship')) !== -1;
case '27619':
return card.type_code === 'character' && card.traits.indexOf(Translator.trans('card.traits.guard')) !== -1;
case '00362': // Sealing the Pact
case '00363': // Unknown and Unknowable
case '00366': // Join Forces
case '00367': // Desperate Hope
return card.pack_code === 'ToJ';
case '00364': // Pass Beneath the Shadow
return card.pack_code === 'ToJ' && card_has_keyword(card, Translator.trans('card.keywords.shadow'));
case '00365': // Seeking Fortunes
return card.pack_code === 'ToJ' && card_has_keyword(card, Translator.trans('keyword.bestow.name'));
}
};
})(app.deck = {}, jQuery);
Loading