Author SHA1 Message Date
Erzangel f7409fe7c3 fix: image adjustments, flavor text font size
release-tag / release-image (push) Has been cancelled
2026-03-21 18:04:02 +01:00
Erzangel 79abb8452c feat: flavor text, new card skins, gitignore updates
release-tag / release-image (push) Has been cancelled
2026-03-21 17:38:01 +01:00
Erzangel 569aa630a2 feat(wip): flavor text sample
release-tag / release-image (push) Has been cancelled
2026-03-20 17:07:10 +01:00
Erzangel 4da86dd40f docs: update todo list
release-tag / release-image (push) Has been cancelled
2026-03-15 20:13:17 +01:00
Erzangel 151aaadb3f docs: push todo list
release-tag / release-image (push) Has been cancelled
2026-03-15 19:57:50 +01:00
Erzangel c368536d79 feat: add svg to png conversion capabilities
release-tag / release-image (push) Has been cancelled
2026-03-15 17:51:37 +01:00
Erzangel 8a024929bc feat: 1st version of icons: champion, trash, yellow faction (so far)
release-tag / release-image (push) Has been cancelled
2026-03-15 16:23:18 +01:00
Erzangel 1410f02e0b chore: lfs for psd
release-tag / release-image (push) Successful in 13s
2024-12-27 00:43:09 +01:00
Erzangel dac0ced73e fix: update some old pics
release-tag / release-image (push) Successful in 29s
2024-12-27 00:41:03 +01:00
legonzaur b68a313e0f Fix : gradients id not unique
release-tag / release-image (push) Successful in 14s
2024-08-27 15:22:18 +02:00
Erzangel 2704033e72 feat: prettify the way subeffects are written
release-tag / release-image (push) Successful in 27s
2024-08-09 16:10:50 +02:00
Erzangel 1fb0ddc2a7 feat: new images, translation fixes
release-tag / release-image (push) Successful in 15s
2024-08-07 17:02:33 +02:00
Erzangel 37cd61c268 feat: colors, images for later cards & more
release-tag / release-image (push) Successful in 14s
2024-08-07 00:06:53 +02:00
Erzangel 67c525a65f feat: icon colors, no faction for base, full base
release-tag / release-image (push) Successful in 14s
2024-08-06 19:24:12 +02:00
legonzaur 11dbcef970 feat : actual aspect ratios images
release-tag / release-image (push) Successful in 12s
Closes #5
2024-08-05 18:32:03 +02:00
Erzangel b7abbb646d feat: draw vertical or, fix horizontal or for more than 2
release-tag / release-image (push) Successful in 15s
2024-08-05 15:50:57 +02:00
Erzangel 2d03a3b36a Merge branch 'main' of git.legonzaur.fr:heronfief/cards-gen
release-tag / release-image (push) Successful in 13s
2024-08-04 17:33:56 +02:00
Erzangel 5b72b60aa2 fix: or condition, effects translation for upcoming cards 2024-08-04 17:33:34 +02:00
legonzaur f766c559d6 ci : Include images in image
release-tag / release-image (push) Successful in 14s
2024-08-03 16:47:00 +02:00
Erzangel 1a3219e6e6 feat(img): provide imgs for all of base game
release-tag / release-image (push) Successful in 12s
2024-08-03 12:56:08 +02:00
41 changed files with 569 additions and 106 deletions
+1
View File
@@ -1,3 +1,4 @@
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
+5 -1
View File
@@ -161,4 +161,8 @@ cython_debug/
#.idea/
typings/*
output/*
output/*
.idea/*
src/convert/output/**/*
+1
View File
@@ -8,4 +8,5 @@ RUN pip install -r requirements.txt
# étape de production
COPY src ./src
COPY img ./img
CMD ["python3", "-u", "./src/generate_cards_mvp.py"]
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 963 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+1
View File
@@ -14,6 +14,7 @@ class Translation(TypedDict):
class SkinData(TypedDict):
title: str
flavor_text_type: str | None
flavor_text_bottom: str | None
image_path: str
+17
View File
@@ -0,0 +1,17 @@
# TODO next steps
local file used to track next stuff to do
- Add details on Damage
- Add SVG image for heal
- Add bleeding-safe deadzone around PNG output for printing
- Different icons for factions?
- Add shadow on some pictures that do not cut well (as a skin option, transparent gradient)
- Fix larger names
- Fix 20
- Texte du Con Artist avec la bonne couleur; Pareil pour Kikuri
## Idées
- 69: demonic cloud -> darkness CSM
View File
+23
View File
@@ -0,0 +1,23 @@
import os
gtkbin = r'C:\Program Files\GTK3-Runtime Win64\bin'
add_dll_dir = getattr(os, 'add_dll_directory', None)
if callable(add_dll_dir):
add_dll_dir(gtkbin)
else:
os.environ['PATH'] = os.pathsep.join((gtkbin, os.environ['PATH']))
import cairosvg
from pathlib import Path
folder = Path(r"C:\Users\Luka\Documents\dev\cards-gen\output\heron")
for svg_file in folder.glob("*.svg"):
print("Processing" + str(svg_file))
cairosvg.svg2png(
url=str(svg_file),
scale=3,
write_to=f'output/{str(svg_file.name).replace(".svg", ".png")}'
)
+77 -24
View File
@@ -13,22 +13,23 @@
},
"3": {
"title": "Commandement",
"flavor_text_bottom": "Haniwas, à l'attaque! Go!",
"image_path": "../../img/skins/heron/003.png"
},
"4": {
"title": "Saki, Pégase de Guerre",
"flavor_text_type": "Cowboy",
"flavor_text_bottom": "Howdy, cowboy!",
"image_path": "../../img/skins/heron/004.png"
},
"5": {
"title": "Domination",
"image_path": "../../img/skins/heron/005.png"
},
"6": {
"title": "Raiden, le Juste",
"image_path": "../../img/skins/heron/006.png"
},
"7": {
@@ -44,6 +45,7 @@
"9": {
"title": "Dlanor A. Knox",
"flavor_text_type": "Inquisitrice",
"flavor_text_bottom": "Dame Erika vous veut MORT.",
"image_path": "../../img/skins/heron/009.png"
},
"10": {
@@ -53,7 +55,7 @@
},
"11": {
"title": "Recrutement",
"image_path": "../../img/skins/heron/011.png"
},
"12": {
@@ -68,27 +70,28 @@
},
"14": {
"title": "Parole de Puissance",
"image_path": "../../img/skins/heron/014.png"
},
"15": {
"title": "Madeline, Mercenaire",
"flavor_text_type": "Guerrière",
"flavor_text_bottom": "Salut, c'est Madeline! Dites,\n vous n'auriez pas vu Mauvier?",
"image_path": "../../img/skins/heron/015.png"
},
"16": {
"title": "Pot-de-Vin",
"image_path": "../../img/skins/heron/016.png"
},
"17": {
"title": "Menace de Mort",
"flavor_text_type": "Takano",
"flavor_text_type": "Pas gentil",
"image_path": "../../img/skins/heron/017.png"
},
"18": {
"title": "Fourberie",
"image_path": "../../img/skins/heron/018.png"
},
"19": {
@@ -98,12 +101,12 @@
},
"20": {
"title": "Tit job",
"image_path": "../../img/skins/heron/020.png"
},
"21": {
"title": "Intimidation",
"image_path": "../../img/skins/heron/021.png"
},
"22": {
@@ -118,12 +121,12 @@
},
"24": {
"title": "Bénéfice",
"image_path": "../../img/skins/heron/024.png"
},
"25": {
"title": "Jester, Maître Assassin",
"flavor_text_bottom": "Son nom maintient les employés\nde la compagnie éveillés.",
"image_path": "../../img/skins/heron/025.png"
},
"26": {
@@ -139,6 +142,7 @@
"28": {
"title": "Cirno",
"flavor_text_type": "Baka",
"flavor_text_bottom": "Dansu no chansu ni pankuna\nranbu de anta no mausu wa anguri\n nan chi ~yuu fanki!",
"image_path": "../../img/skins/heron/028.png"
},
"29": {
@@ -153,12 +157,13 @@
},
"31": {
"title": "Sombre Récompense",
"image_path": "../../img/skins/heron/031.png"
},
"32": {
"title": "Bracken",
"flavor_text_type": "Entité",
"flavor_text_bottom": "\"MAIS REND MON POTE\"",
"image_path": "../../img/skins/heron/032.png"
},
"33": {
@@ -173,7 +178,7 @@
},
"35": {
"title": "Trick-or-Treat!",
"image_path": "../../img/skins/heron/035.png"
},
"36": {
@@ -213,7 +218,7 @@
},
"43": {
"title": "Panette, la Berserker",
"image_path": "../../img/skins/heron/043.png"
},
"44": {
@@ -238,7 +243,7 @@
},
"48": {
"title": "Don de la Nature",
"image_path": "../../img/skins/heron/048.png"
},
"49": {
@@ -248,22 +253,22 @@
},
"50": {
"title": "Impulsion Temporelle",
"image_path": "../../img/skins/heron/050.png"
},
"51": {
"title": "Baiken, Samurai Slasher",
"flavor_text_bottom": "Il n'y a pas de victoire ou défaite.\nJuste du savoir sur soi-même.",
"image_path": "../../img/skins/heron/051.png"
},
"52": {
"title": "Étincelle de Caine",
"image_path": "../../img/skins/heron/052.png"
},
"53": {
"title": "Forme de Loup",
"image_path": "../../img/skins/heron/053.png"
},
"54": {
@@ -278,27 +283,75 @@
},
"56": {
"title": "Live",
"image_path": "../../img/skins/heron/056.png"
},
"57": {
"title": "Superkill",
"image_path": "../../img/skins/heron/057.png"
},
"58": {
"title": "Kill",
"image_path": "../../img/skins/heron/058.png"
},
"59": {
"title": "Live Forever",
"image_path": "../../img/skins/heron/059.png"
},
"60": {
"title": "Collection de Bounty",
"image_path": "../../img/skins/heron/060.png"
},
"61": {
"title": "Dangereuse échappée",
"image_path": "../../img/skins/heron/061.png"
},
"62": {
"title": "Festival de Watanagashi",
"image_path": "../../img/skins/heron/062.png"
},
"63": {
"title": "Plus la même",
"image_path": "../../img/skins/heron/063.png"
},
"65": {
"title": "Starlight Convergence",
"image_path": "../../img/skins/heron/065.png"
},
"68": {
"title": "Confisquer",
"image_path": "../../img/skins/heron/068.png"
},
"70": {
"title": "Jus de Prune",
"image_path": "../../img/skins/heron/070.png"
},
"71": {
"title": "Rosser",
"image_path": "../../img/skins/heron/071.png"
},
"72": {
"title": "Rakiie Swiftkick",
"image_path": "../../img/skins/heron/072.png"
},
"73": {
"title": "Trading Card",
"image_path": "../../img/skins/heron/073.png"
},
"74": {
"title": "Scout, Nain de DRG",
"image_path": "../../img/skins/heron/074.png"
},
"84": {
"title": "ULTRAKILL",
"image_path": "../../img/skins/heron/084.png"
},
"85": {
"title": "Ultralive",
"image_path": "../../img/skins/heron/085.png"
}
}
+38 -11
View File
@@ -1,33 +1,60 @@
{
"card_type":{
"action":"Action",
"champion":"Champion"
"champion":"Champion",
"item": "Objet"
},
"colors":{
"red": "rouge",
"green": "verte",
"blue": "bleu",
"yellow": "jaune"
},
"numbers":{
"1": "une",
"2": "deux"
"2": "deux",
"3": "trois",
"4": "quatre"
},
"effects":{
"draw":"Piochez une carte.",
"draw_amount":"Piochez %a cartes.",
"stun":"Assomez le champion ciblé.",
"stack_next_card_bought":"Mettez la prochaine carte dont\nvous faites l'acquisition ce tour-ci\nau dessus de votre pioche.",
"stack_next_action_bought": "Mettez la prochaine action dont\nvous faites l'acquisition ce tour-ci\nau dessus de votre pioche.",
"sacrifice":"Vous pouvez sacrifier une carte\nde votre main ou de votre défausse.",
"sacrifice_times":"Vous pouvez sacrifier jusqu'à %a\ncartes de votre main et/ou de votre défausse.",
"restack_discarded_champion":"Mettez un champion de votre défausse\nau dessus de votre pioche.",
"restack_discarded_card": "Vous pouvez mettre une carte de votre défausse\nau dessus de votre pioche.",
"restack_discarded_champion":"Mettez un champion de\nvotre défausse au dessus\nde votre pioche.",
"restack_discarded_action":"Mettez une action de\nvotre défausse au dessus\nde votre pioche.",
"make_discard":"L'adversaire ciblé%nse défausse d'une carte.",
"draw_and_discard":"Vous pouvez piocher une carte, puis\n vous défausser d'une carte.",
"draw_and_discard_times":"Vous pouvez piocher jusqu'à %a cartes,\ndéfaussez-vous ensuite d'autant de cartes",
"draw_and_discard_times":"Vous pouvez piocher jusqu'à %a cartes,\ndéfaussez-vous ensuite d'autant de cartes.",
"play_next_card_bought":"Mettez la prochaine carte dont\nvous faites l'acquisition ce tour-ci\n dans votre main.",
"prepare":"Mobilisez un Champion"
"prepare":"Mobilisez un Champion.",
"prepare_all_champions":"Mobilisez vos Champions.",
"prepare_another_champion":"Mobilisez un autre Champion.",
"cheaper_champions_passive":"Les Champions que vous achetez ce tour-ci\nvous coûtent 1 de moins.",
"cheaper_champion": "Le prochain Champion que vous achetez\nvous coûte 1 de moins.",
"control_opposing_champion_passive":"Prenez le contrôle d'un Champion\nennemi jusqu'à la fin du tour.",
"buy_for_free_amount":"Vous pouvez acquérir gratuitement\nune carte de coût %a ou moins.",
"damage_all_champions_amount":"Infligez %a dégats à tous\nles Champions adverses.",
"discard_x_and_draw_x":"Vous pouvez vous défausser de\nn'importe quel nombre de cartes\net en piocher autant.",
"buy_gem_for_free": "Obtenez une gemme de coût 2\n ou moins gratuitement."
},
"per":{
"champion":"pour chaque Champion\nque vous avez en jeu",
"other_champion":"pour chaque autre Champion\nque vous avez en jeu",
"other_guard":"pour chaque autre\nGarde que vous avez en jeu",
"other_card_of_same_faction":"pour chaque autre x\nque vous avez en jeu",
"champion_of_same_faction":"pour chaque Champion x\nque vous avez en jeu",
"other_knife_played":"pour chaque autre Couteau\nque vous avez en jeu"
"champion":"pour chaque Champion\nque vous avez en jeu.",
"other_champion":"pour chaque autre\nChampion que vous avez\nen jeu.",
"other_guard":"pour chaque autre\nGarde que vous avez en jeu.",
"other_card_of_same_faction":"pour chaque autre\n carte %c que vous avez\nen jeu.",
"champion_of_same_faction":"pour chaque \nChampion %c que vous\navez en jeu.",
"other_knife_played":"pour chaque autre Couteau\nque vous avez en jeu.",
"stunned_champion": "pour chaque Champion\nassommé ce tour-ci."
},
"per_effect":{
"damage":"dégats",
"heal":"PV",
"gold":"$"
}
}
+272 -29
View File
@@ -1,10 +1,12 @@
from math import sin, cos, pi
from typing import Optional
import drawsvg as dw
import json
from itertools import groupby
from card_types import Card, Effect, Translation
from card_types import Card, Effect, Translation, SkinData
stats_radius = 20
effect_text_size = 14
@@ -26,6 +28,7 @@ def is_base_effect(effect: Effect):
def create_icon(
amount: int,
fill_circle: str,
outline_circle: Optional[str] = None,
effect_id: str | None = None,
fill_text: str = "black",
radius: float = stats_radius,
@@ -35,7 +38,9 @@ def create_icon(
else:
click_function = js_event_function(effect_id)
group = dw.Group(**{"class": "svg_resource_icon", "v-on:click": click_function})
group.append(dw.Circle(cx=0, cy=0, r=radius, fill=fill_circle, x=0, y=0))
if outline_circle:
group.append(dw.Circle(cx=0, cy=0, r=radius, fill=outline_circle, x=0, y=0))
group.append(dw.Circle(cx=0, cy=0, r=radius-2, fill=fill_circle, x=0, y=0))
group.append(
dw.Text(
str(amount),
@@ -58,6 +63,7 @@ def create_damage_icon(
amount,
radius=radius,
fill_circle="red",
outline_circle="black",
fill_text="white",
effect_id=effect_id,
)
@@ -70,6 +76,7 @@ def create_heal_icon(
amount,
radius=radius,
fill_circle="green",
outline_circle="black",
fill_text="white",
effect_id=effect_id,
)
@@ -82,6 +89,7 @@ def create_gold_icon(
amount,
radius=radius,
fill_circle="yellow",
outline_circle="goldenrod",
effect_id=effect_id,
)
@@ -192,18 +200,52 @@ resourceIcons = {
"damage": create_damage_icon,
}
### This went unused, but I left it commented cause it might be cool as a card background? idk
# def create_background_with_text(x_length: int, height: int, color: str):
# font_size = 8
# gap = 11
# group = []
# y_offset = int((height % gap)/2)
# for i in range(int(height/gap)):
# group.append(
# dw.Text(
# f"{'choose' if i%2==0 else ''} one choose one choose one choose one{' choose' if i%2==1 else ''}",
# x=0,
# y=-18 + gap*i + y_offset,
# text_anchor="middle",
# fill=color,
# font_size=font_size,
# font_family="Helvetica",
# )
# )
# return group
def create_effect_text(effects: list[Effect], height: float, width: int):
def create_effect_text(
card: Card,
effects: list[Effect],
height: float,
width: int,
has_mutex: bool = False,
):
group = []
effect_text = ""
for e in effects:
for [i, e] in enumerate(effects):
if effect_text != "":
effect_text += " "
per = e.get("per")
if per is not None:
translation = f"+ x {translation_fr.get('per').get(per)}"
print(e.get("effect"))
translation = f"+ {e.get('amount', '1')} {translation_fr.get('per_effect').get(e.get('effect'))} {translation_fr.get('per').get(per)}"
translation = translation.replace(
"%c",
translation_fr.get("colors").get(
card.get("faction"), card.get("faction")
)
+ " ",
) # FIXME: card color is needed here
elif e.get("amount", 1) > 1:
translation = (
translation_fr.get("effects")
@@ -231,6 +273,20 @@ def create_effect_text(effects: list[Effect], height: float, width: int):
effect_text += "\n"
effect_text += translation
# Handle sub_effects
if len(e.get("sub_effects", [])) > 0:
effect_text += "\nSi vous le faites, gagnez "
for se in e.get("sub_effects"):
effect_text += (
str(se.get("amount", 1))
+ " "
+ translation_fr.get("per_effect").get(se.get("effect"))
)
effect_text += "."
if has_mutex and i is not len(effects) - 1:
effect_text += "\nou"
if width < 250:
effect_text = effect_text.replace("%n", "\n")
else:
@@ -265,8 +321,35 @@ def create_effect_text(effects: list[Effect], height: float, width: int):
return group
def create_arrow():
return dw.Lines(7, 10,
7, -5,
-5, -5,
-5, -9,
-10, -2,
-5, 4,
-5, 0,
2, 0,
2, 10,
stroke='black', fill='white', close='true')
def create_effect_row(effects: list[Effect], type: str, height: int, width: int):
def create_faction_icon(faction: str, transform: str = None):
if faction == "yellow":
return dw.Lines(9, 0,
2, -2,
0, -5,
-2, -2,
-9, 0,
-2, 2,
0, 5,
2, 2,
stroke='grey', fill='white', close='true', transform=transform)
def create_effect_row(
card: Card, effects: list[Effect], type: str, height: int, width: int
):
group = dw.Group(**{"class": "svg_effect"})
has_icon = False
if type == "faction_combo":
@@ -276,10 +359,13 @@ def create_effect_row(effects: list[Effect], type: str, height: int, width: int)
cx=30,
cy=height / 2,
r=15,
fill="url(#grad2)",
fill=f'url(#grad2-{card["id"]})',
filter="url(#drop-shadow)",
)
)
subg = dw.Group(transform=f"translate({30},{height / 2})")
subg.append(create_faction_icon(faction="yellow"))
group.append(subg)
elif type == "suicide":
has_icon = True
group.append(
@@ -287,10 +373,34 @@ def create_effect_row(effects: list[Effect], type: str, height: int, width: int)
cx=30,
cy=height / 2,
r=15,
fill="black",
fill="url(#gradient_suicide)",
filter="url(#drop-shadow)",
)
)
group.append(
dw.Line(
sx=19,
sy=(height / 2) - 11,
ex=41,
ey=(height / 2) + 11,
stroke="black",
stroke_width=2,
stroke_dasharray='3,1',
stroke_opacity=0.3,
)
)
group.append(
dw.Line(
sx=41,
sy=(height / 2) - 11,
ex=19,
ey=(height / 2) + 11,
stroke="black",
stroke_width=2,
stroke_dasharray='3,1',
stroke_opacity=0.3,
)
)
elif type == "champion_action":
has_icon = True
@@ -299,10 +409,24 @@ def create_effect_row(effects: list[Effect], type: str, height: int, width: int)
cx=30,
cy=height / 2,
r=15,
fill="#444444",
filter="url(#drop-shadow)",
)
)
group.append(
dw.Circle(
cx=30,
cy=height / 2,
r=13,
fill="gray",
filter="url(#drop-shadow)",
)
)
subg = dw.Group(transform=f"translate({30},{height / 2})")
subg.append(create_arrow())
group.append(subg)
effect_with_icons = list(
filter(lambda x: x.get("effect") in resourceIcons and not x.get("per"), effects)
)
@@ -326,10 +450,46 @@ def create_effect_row(effects: list[Effect], type: str, height: int, width: int)
text_width = width
if has_icon:
text_width = width - 70
is_mutex = False
if "mutex" in effects[0]:
print("mutex exists for a card")
is_mutex = True
if len(effect_with_text) > 0:
has_text_mutex = False
if is_mutex:
if len(effect_with_icons) > 0:
icon_y_pos = icon_y_pos - 15
group.append(
dw.Group(
[
dw.Text(
"ou",
font_size=14,
x=width / 2,
y=text_y_pos - 19,
text_anchor="middle",
fill="black",
font_family="Helvetica",
font_style="italic",
),
],
**{"v-bind:class": f"{{used:used_effects_indexes.length > 0}}"},
)
)
text_y_pos = text_y_pos + 7
elif len(effect_with_text) > 1:
has_text_mutex = True
group.append(
dw.Group(
create_effect_text(effect_with_text, text_height, text_width),
create_effect_text(
card=card,
effects=effect_with_text,
height=text_height,
width=text_width,
has_mutex=has_text_mutex,
),
transform=f"translate({width / 2},{text_y_pos})",
**{
"class": "svg_effect",
@@ -358,6 +518,28 @@ def create_effect_row(effects: list[Effect], type: str, height: int, width: int)
},
)
)
if is_mutex and i != 0:
group.append(
dw.Group(
[
dw.Text(
"ou",
# x=(width/2) - ((len(effect_with_icons) - 1) * icon_radius * 1.5) + icon_radius + 10,
x=(width / 2)
- ((len(effect_with_icons) - 1) * icon_radius * 1.5)
+ icon_radius
+ 10
+ ((i - 1) * icon_radius * 3),
y=icon_y_pos + 3,
text_anchor="middle",
fill="black",
font_size=12,
font_family="Helvetica",
),
],
**{"v-bind:class": f"{{used:used_effects_indexes.length > 0}}"},
)
)
return group
@@ -385,7 +567,7 @@ def generate_subeffect_index(effect: Effect):
generate_subeffect_index(e)
def create_effects(effects: list[Effect], height: int, width: int):
def create_effects(card: Card, effects: list[Effect], height: int, width: int, flavor_text: Optional[str]):
for [i, e] in enumerate(effects):
e["index"] = str(i)
generate_subeffect_index(e)
@@ -401,18 +583,72 @@ def create_effects(effects: list[Effect], height: int, width: int):
effect_types = list(grouped_effects.keys())
if len(grouped_effects) == 1:
group.append(
create_effect_row(
grouped_effects[effect_types[0]], effect_types[0], height, width
),
)
if flavor_text is None:
group.append(
create_effect_row(
card=card,
effects=grouped_effects[effect_types[0]],
type=effect_types[0],
height=height,
width=width,
),
)
else:
group.append(
dw.Line(sx=10, sy=90, ex=width - 10, ey=90, stroke="black", stroke_width=1)
)
group.append(
create_effect_row(
card=card,
effects=grouped_effects[effect_types[0]],
type=effect_types[0],
height=85,
width=width,
),
)
font_width = 0.5
line_width = (
max(*[len(x) for x in flavor_text.split("\n")], len(flavor_text)) * font_width
)
# Take width in account
font_size = effect_text_size - 1
font_size = min(
font_size, (font_size * 3) - line_width, font_size * width / (line_width or 1)
)
# Take height in account
font_size = min((height) / (flavor_text.count("\n") or 1), font_size)
font_size = max(font_size, 11)
group.append(
dw.Group(
[
dw.Text(
text=flavor_text, # TODO: mettre en italique & gris
x=0,
y=(-font_size / 2) * flavor_text.count("\n"),
text_anchor="middle",
fill="black",
font_size=font_size,
font_style="italic",
font_family="Helvetica",
transform=f"translate({width / 2},{height - 73})"
)
],
transform=f"translate({0},{55})"
)
)
if len(grouped_effects) == 2:
group.append(
dw.Line(sx=10, sy=80, ex=width - 10, ey=80, stroke="black", stroke_width=1)
)
group.append(
create_effect_row(
grouped_effects[effect_types[0]], effect_types[0], 80, width
card=card,
effects=grouped_effects[effect_types[0]],
type=effect_types[0],
height=80,
width=width,
),
)
@@ -420,10 +656,11 @@ def create_effects(effects: list[Effect], height: int, width: int):
dw.Group(
[
create_effect_row(
grouped_effects[effect_types[1]],
effect_types[1],
height - 80,
width,
card=card,
effects=grouped_effects[effect_types[1]],
type=effect_types[1],
height=height - 80,
width=width,
)
],
transform=f"translate({0},{80})",
@@ -439,7 +676,11 @@ def create_effects(effects: list[Effect], height: int, width: int):
)
group.append(
create_effect_row(
grouped_effects[effect_types[0]], effect_types[0], 45, width
card=card,
effects=grouped_effects[effect_types[0]],
type=effect_types[0],
height=45,
width=width,
)
)
@@ -447,10 +688,11 @@ def create_effects(effects: list[Effect], height: int, width: int):
dw.Group(
[
create_effect_row(
grouped_effects[effect_types[1]],
effect_types[1],
45,
width,
card=card,
effects=grouped_effects[effect_types[1]],
type=effect_types[1],
height=45,
width=width,
)
],
transform=f"translate({0},{45})",
@@ -461,10 +703,11 @@ def create_effects(effects: list[Effect], height: int, width: int):
dw.Group(
[
create_effect_row(
grouped_effects[effect_types[2]],
effect_types[2],
45,
width,
card=card,
effects=grouped_effects[effect_types[2]],
type=effect_types[2],
height=45,
width=width,
)
],
transform=f"translate({0},{90})",
+65 -29
View File
@@ -1,3 +1,5 @@
import base64
import drawsvg as dw
import json
import os
@@ -10,6 +12,7 @@ from card_types import Card, SkinData, SkinFile, Translation
from drawsvg.drawing import Drawing
from edit_card import finish_card
from src.draw_functions import create_faction_icon
with open("src/data/translation_fr.json", encoding="utf-8") as f:
translation_fr: Translation = json.load(f)
@@ -24,6 +27,8 @@ for p in Path("src/data/cards-json").glob("*.json"):
cards.extend(temp)
# Params
embedded_images = False
x_width = 298
y_height = 417
border_width_x = 12
@@ -41,7 +46,7 @@ cost_right_offset = 38
background_colors = {
"blue": "lightblue",
"red": "lightcoral",
"yellow": "rgb(206, 182, 167)",
"yellow": "rgb(247, 227, 175)",
"green": "lightgreen",
"base": "lightgrey",
}
@@ -56,32 +61,31 @@ gradient_colors = {
icon_colors = {
"yellow": ["#ff9935", "#ffe744"],
"blue": ["#ff9935", "#ffe744"],
"red": ["#ff9935", "#ffe744"],
"green": ["#ff9935", "#ffe744"],
"base": ["#ff9935", "#ffe744"],
"blue": ["#3399ff", "#aa44ff"],
"red": ["#ff99ff", "#ffe744"],
"green": ["#009935", "#35dd35"],
"base": ["#8899aa", "#aa9988"],
}
fullscreen_card_ids = [56, 57, 58, 59, 84, 85]
shadowFilter = dw.Filter(id="drop-shadow")
shadowFilter.append(dw.FilterItem("feDropShadow", dx=1, dy=1, stdDeviation=0))
# Draw faction icon
faction_icon = dw.Group(id="faction_icon")
faction_icon.append(dw.Circle(cx=1, cy=1, r=20, fill="black", filter=shadowFilter))
faction_icon.append(
dw.Circle(
cx=0,
cy=0,
r=20,
fill="url(#grad2)",
)
)
for card_data in cards:
# Get card skin data
try:
card_skin_data = all_skin_data["cards"][str(card_data["id"])]
except KeyError:
if embedded_images:
with open(card_skin_data["image_path"].replace("../../", ""), "rb") as img:
b64 = base64.b64encode(img.read()).decode()
data_uri = f"data:image/png;base64,{b64}"
card_skin_data["image_path"] = data_uri
except KeyError as e:
print(
"Missing skin data for card id:", card_data["id"], "using default skin data"
)
@@ -91,9 +95,9 @@ for card_data in cards:
flavor_text_type=None,
)
# Check if card image really exists
if not Path(card_skin_data["image_path"].replace("../../", "")).absolute().exists():
print("Missing image for card id: ", card_data["id"], " , using default image")
card_skin_data["image_path"] = "../../img/missing.png"
# if not Path(card_skin_data["image_path"].replace("../../", "")).absolute().exists():
# print("Missing image for card id: ", card_data["id"], " , using default image")
# card_skin_data["image_path"] = "../../img/missing.png"
# Create a new SVG drawing
output_dir = "output/" + all_skin_data["skin_name"]
os.makedirs(output_dir, exist_ok=True)
@@ -110,6 +114,20 @@ for card_data in cards:
)
d.append(shadowFilter)
faction_icon = dw.Group(id="faction_icon")
faction_icon.append(dw.Circle(cx=1, cy=1, r=20, fill="black", filter=shadowFilter))
faction_icon.append(
dw.Circle(
cx=0,
cy=0,
r=20,
fill=f'url(#grad2-{card_data["id"]})',
)
)
faction_icon.append(
create_faction_icon("yellow", transform="scale(1.5)"),
)
# Define linear colored gradient
gradient = dw.LinearGradient(
0, 0, 1, 0, gradientUnits="objectBoundingBox", id="grad1"
@@ -120,12 +138,20 @@ for card_data in cards:
# Define linear colored gradient for faction icon
gradient_faction_icon = dw.LinearGradient(
0, 1, 0, 0, gradientUnits="objectBoundingBox", id="grad2"
0, 1, 0, 0, gradientUnits="objectBoundingBox", id=f'grad2-{card_data["id"]}'
)
gradient_faction_icon.add_stop(0, icon_colors[card_data["faction"]][0], opacity=1)
gradient_faction_icon.add_stop(1, icon_colors[card_data["faction"]][1], opacity=1)
d.append_def(gradient_faction_icon)
# Define linear colored gradient for suicide icon
gradient_suicide = dw.LinearGradient(
0, 1, 0, 0, gradientUnits="objectBoundingBox", id="gradient_suicide"
)
gradient_suicide.add_stop(0, "#8a8a8a", opacity=1)
gradient_suicide.add_stop(1, "#ddeedd", opacity=1)
d.append_def(gradient_suicide)
print(card_data)
# Draw card background
background = dw.Group(id="background")
background.append(
@@ -195,13 +221,14 @@ for card_data in cards:
)
d.append(title)
# Draw faction icon
d.append(
dw.Group(
children=[faction_icon],
transform=f"translate({border_width_x + 23 + 1},{border_width_y - border_upper_offset + 20 + 4 + 1})",
# Draw faction icon if not a base card
if card_data["faction"] != "base":
d.append(
dw.Group(
children=[faction_icon],
transform=f"translate({border_width_x + 23 + 1},{border_width_y - border_upper_offset + 20 + 4 + 1})",
)
)
)
# Draw card cost
if card_data.get("cost") is not None:
d.append(
@@ -213,13 +240,19 @@ for card_data in cards:
# Draw image
d.append(dw.Rectangle(x=1, y=65, width=x_width - 2, height=200))
image_height = (
200
if card_data["id"] not in fullscreen_card_ids
else y_height - 65 - border_width_y
)
d.append(
dw.Image(
x=border_width_x,
y=65,
width=x_width - (border_width_x * 2),
height=200,
height=image_height,
path=card_skin_data["image_path"],
preserveAspectRatio="xMidYMid slice",
)
)
d.append(dw.Rectangle(x=2, y=65 - 1, width=border_width_x - 2, height=200 + 2))
@@ -236,11 +269,14 @@ for card_data in cards:
# Draw effects
effects = card_data.get("effects")
if effects is not None:
effects = create_effects(effects=effects, height=135, width=x_internal_width)
effects_height = 265 if card_data["id"] not in fullscreen_card_ids else 220
effects = create_effects(
card=card_data, effects=effects, height=135, width=x_internal_width, flavor_text=card_skin_data["flavor_text_bottom"] if "flavor_text_bottom" in card_skin_data.keys() else None
)
d.append(
dw.Group(
effects,
transform=f"translate({border_width_x},{265})",
transform=f"translate({border_width_x},{effects_height})",
)
)