Initial commit
working: hero decks and character cards, without art
This commit is contained in:
commit
f4273338d8
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*.png
|
||||
/*.json
|
139
SotMDeckBuilder.py
Normal file
139
SotMDeckBuilder.py
Normal file
@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
#note: image templates: https://boardgamegeek.com/thread/813176/card-templates
|
||||
|
||||
#TODO: dynamic text sizes
|
||||
#TODO: remote upload?
|
||||
|
||||
import json
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import textwrap
|
||||
import math
|
||||
import os
|
||||
|
||||
IMG_HERO_CHAR_FRONT = Image.open("images/HeroCharFront.png")
|
||||
IMG_HERO_CHAR_BACK = Image.open("images/HeroCharBack.png")
|
||||
IMG_HERO_DECK = Image.open("images/HeroCard.png")
|
||||
IMG_TARGET_HP = Image.open("images/targetHP.png")
|
||||
|
||||
FONT_KEYWORD = ImageFont.truetype("fonts/RedStateBlueStateBB_reg.otf", size=40)
|
||||
FONT_TITLE = ImageFont.truetype("fonts/CrashLandingBB.otf", size=60)
|
||||
FONT_DESCRIPTION = ImageFont.truetype("fonts/RedStateBlueStateBB_reg.otf", size=30)
|
||||
FONT_TARGET_HP = ImageFont.truetype("fonts/CrashLandingBB.otf", size=120)
|
||||
FONT_CHAR_HP = ImageFont.truetype("fonts/ap.ttf", size=120)
|
||||
|
||||
def drawTextIf(draw, position, font, card, key, wrap=False):
|
||||
if key in card:
|
||||
text = str(card[key])
|
||||
|
||||
if wrap:
|
||||
# badly wrap text to fit on card
|
||||
text = '\n'.join(textwrap.fill(line.strip(), wrap,
|
||||
break_long_words=False,
|
||||
replace_whitespace=False)
|
||||
for line in text.splitlines())
|
||||
draw.text(position, text, font=font, fill="#000000")
|
||||
|
||||
def addCardToBase(baseImg, baseX, cardNum, cardImg):
|
||||
baseImg.paste(cardImg, ((cardNum % baseX) * cardImg.width,
|
||||
int(cardNum / baseX) * cardImg.height))
|
||||
|
||||
def makeFaces(deckJson, outfile):
|
||||
# TODO: probably a more optimal way to fit cards
|
||||
baseX = math.ceil(math.sqrt(len(deckJson['deck']) +
|
||||
len(deckJson['character']) * 2))
|
||||
baseImage = Image.new('RGB', (IMG_HERO_DECK.width * baseX,
|
||||
IMG_HERO_DECK.height * baseX))
|
||||
|
||||
cardNum = 0
|
||||
# Make a card for each hero character card
|
||||
for card in deckJson['character']:
|
||||
cardFrontImg = IMG_HERO_CHAR_FRONT.copy()
|
||||
draw = ImageDraw.Draw(cardFrontImg)
|
||||
drawTextIf(draw, (150, 70), FONT_CHAR_HP, card, 'name')
|
||||
drawTextIf(draw, (100, 900), FONT_DESCRIPTION, card, 'power', 40)
|
||||
drawTextIf(draw, (53, 260), FONT_CHAR_HP, card, 'hp')
|
||||
addCardToBase(baseImage, baseX, cardNum, cardFrontImg)
|
||||
cardNum += 1
|
||||
|
||||
cardBackImg = IMG_HERO_CHAR_BACK.copy()
|
||||
draw = ImageDraw.Draw(cardBackImg)
|
||||
drawTextIf(draw, (150, 70), FONT_CHAR_HP, card, 'name')
|
||||
drawTextIf(draw, (80, 800), FONT_DESCRIPTION, card, 'incapacitated', 43)
|
||||
addCardToBase(baseImage, baseX, cardNum, cardBackImg)
|
||||
cardNum += 1
|
||||
|
||||
# Make a card for each card in the deck
|
||||
for card in deckJson['deck']:
|
||||
cardImg = IMG_HERO_DECK.copy()
|
||||
|
||||
if 'hp' in card: # Draw HP marker, if it exists
|
||||
# self mask for transparency
|
||||
cardImg.paste(IMG_TARGET_HP, (510, 36), IMG_TARGET_HP)
|
||||
|
||||
draw = ImageDraw.Draw(cardImg)
|
||||
drawTextIf(draw, (52, 50), FONT_TITLE, card, 'name')
|
||||
drawTextIf(draw, (85, 610), FONT_KEYWORD, card, 'type')
|
||||
drawTextIf(draw, (52, 670), FONT_DESCRIPTION, card, 'description', 46)
|
||||
drawTextIf(draw, (120, 910), FONT_DESCRIPTION, card, 'flavor')
|
||||
drawTextIf(draw, (600, 40), FONT_TARGET_HP, card, 'hp')
|
||||
addCardToBase(baseImage, baseX, cardNum, cardImg)
|
||||
cardNum += 1
|
||||
|
||||
baseImage.save(outfile + ".png", "PNG")
|
||||
return (baseX, baseX)
|
||||
|
||||
def makeJson(deckJson, imgWidth, imgHeight, outfile):
|
||||
with open("templates/deck.json") as f:
|
||||
outJson = json.load(f)
|
||||
with open("templates/card.json") as f:
|
||||
cardTemplate = json.load(f)
|
||||
|
||||
# number of cards in x and y direction
|
||||
outJson['ObjectStates'][0]['CustomDeck']['1'].update(
|
||||
{"NumWidth": imgWidth,
|
||||
"NumHeight": imgHeight,
|
||||
#"FaceURL": "http://adam-desktop.dyn.wpi.edu:8000/" + outfile + ".png",
|
||||
"FaceURL": "file://" + os.getcwd() + "/" + outfile + ".png",
|
||||
"BackURL": "http://cloud-3.steamusercontent.com/ugc/156906385556221451/CE2C3AFE1759790CB0B532FFD636D05A99EC91F4/"})
|
||||
|
||||
# decks start at (10 * deck id)
|
||||
cardNum = 100
|
||||
for card in deckJson['character']:
|
||||
outJson['ObjectStates'][0]['DeckIDs'].append(cardNum)
|
||||
|
||||
# add front card with back as second state
|
||||
cardOutBack = cardTemplate.copy()
|
||||
cardOutBack.update({"Nickname": card['name'],
|
||||
"Description": "Incapacitated",
|
||||
"CardID": cardNum + 1})
|
||||
cardOut = cardTemplate.copy()
|
||||
cardOut.update({"Nickname": card['name'],
|
||||
"Description": "Active",
|
||||
"CardID": cardNum,
|
||||
"States": {"2": cardOutBack}})
|
||||
outJson['ObjectStates'][0]['ContainedObjects'].append(cardOut)
|
||||
cardNum += 2
|
||||
|
||||
for card in deckJson['deck']:
|
||||
for i in range(0, card['count']):
|
||||
outJson['ObjectStates'][0]['DeckIDs'].append(cardNum)
|
||||
|
||||
# add a card object thing for each card, and give it a name
|
||||
cardOut = cardTemplate.copy()
|
||||
cardOut.update({"Nickname": card['name'],
|
||||
"Description": card.get('type', ""),
|
||||
"CardID": cardNum})
|
||||
outJson['ObjectStates'][0]['ContainedObjects'].append(cardOut)
|
||||
cardNum += 1
|
||||
|
||||
with open(outfile + ".json", "w") as f:
|
||||
json.dump(outJson, f)
|
||||
|
||||
with open("hero_3.json") as f:
|
||||
deckJson = json.load(f)
|
||||
|
||||
outfile = "out"
|
||||
|
||||
imgWidth, imgHeight = makeFaces(deckJson, outfile)
|
||||
makeJson(deckJson, imgWidth, imgHeight, outfile)
|
BIN
fonts/CrashLandingBB.otf
Normal file
BIN
fonts/CrashLandingBB.otf
Normal file
Binary file not shown.
BIN
fonts/RedStateBlueStateBB_reg.otf
Normal file
BIN
fonts/RedStateBlueStateBB_reg.otf
Normal file
Binary file not shown.
BIN
fonts/ap.ttf
Normal file
BIN
fonts/ap.ttf
Normal file
Binary file not shown.
BIN
images/HeroCard.png
Normal file
BIN
images/HeroCard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 216 KiB |
BIN
images/HeroCharBack.png
Normal file
BIN
images/HeroCharBack.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
BIN
images/HeroCharFront.png
Normal file
BIN
images/HeroCharFront.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
images/HeroFront.png
Normal file
BIN
images/HeroFront.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 225 KiB |
BIN
images/source/Hero Card.psd
Normal file
BIN
images/source/Hero Card.psd
Normal file
Binary file not shown.
BIN
images/source/Hero Character (Back).psd
Normal file
BIN
images/source/Hero Character (Back).psd
Normal file
Binary file not shown.
BIN
images/source/Hero Character (Front).psd
Normal file
BIN
images/source/Hero Character (Front).psd
Normal file
Binary file not shown.
BIN
images/targetHP.png
Normal file
BIN
images/targetHP.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
Reference in New Issue
Block a user