Refactor to be more object-oriented, move to separate modules

This commit is contained in:
Adam Goldsmith 2022-01-06 14:50:13 -05:00
parent 31a367e5fa
commit 25a029e69b
5 changed files with 400 additions and 379 deletions

View File

@ -10,4 +10,4 @@ catalog-homepage = https://github.com/ad1217/SE3-TTSDeck
catalog-description = This plugin for allows you to generate images and \
corresponding "Saved Object" JSONs for decks which can be used in Tabletop Simulator.
res://ttsdeck/TTSDeck.js
res://ttsdeck/plugin.js

96
resources/ttsdeck/Card.js Normal file
View File

@ -0,0 +1,96 @@
useLibrary("project");
function Card(member, copies_list) {
this.member = member;
this.copies_list = copies_list;
this.component = ResourceKit.getGameComponentFromFile(member.file);
}
Card.getImageFile = function getImageFile(parent, format, page_num) {
return new File(
parent.file,
parent.getName() + "_" + page_num + "." + format
);
};
Card.prototype.makeImageUncached = function makeImageUncached(
resolution,
back
) {
println("Generating image for card ", this.member);
const sheets = this.component.createDefaultSheets();
const card_image = sheets[back ? 1 : 0].paint(
arkham.sheet.RenderTarget.EXPORT,
resolution
);
return card_image;
};
// export front face, or retrive it from a cached file
// TODO: handle two-sided cards
Card.prototype.makeImage = function makeImage(format, resolution) {
const cache_dir = new File(this.member.parent.file, ".ttsdeck_cache");
const cached_file = new File(cache_dir, this.member.file.name + "." + format);
if (
cached_file.exists() &&
cached_file.lastModified() > this.member.file.lastModified()
) {
println("Got cached image for card", this.member);
return ImageUtils.read(cached_file);
} else {
const card_image = this.makeImageUncached(resolution);
cache_dir.mkdir();
ImageUtils.write(card_image, cached_file, format, -1, false, resolution);
return card_image;
}
};
// TODO: allow setting a default copy count
// Hack to override the default return value of 1
Card.prototype.copyCount = function copyCount() {
const entries = this.copies_list.getListEntries().map((x) => String(x));
if (entries.indexOf(String(this.member.baseName)) == -1) {
return 1;
} else {
return this.copies_list.getCopyCount(this.member.baseName);
}
};
Card.prototype.makeJSON = function makeJSON(card_id, description) {
return {
Name: "Card",
Transform: {
posX: 0,
posY: 0,
posZ: 0,
rotX: 0,
rotY: 0,
rotZ: 0,
scaleX: 1.0,
scaleY: 1.0,
scaleZ: 1.0,
},
Nickname: String(this.component.getName()),
CardID: card_id,
Description: String(description || ""),
ColorDiffuse: {
r: 0.713235259,
g: 0.713235259,
b: 0.713235259,
},
Locked: false,
Grid: true,
Snap: true,
Autoraise: true,
Sticky: true,
Tooltip: true,
SidewaysCard: false,
};
};
module.exports = Card;

View File

@ -1,132 +1,61 @@
/*
* TTSDeck.js
*
* Creates a deck image and corresponding "Saved Object" JSON for use
* in Tabletop Simulator
*/
useLibrary("project");
useLibrary("imageutils");
useLibrary("threads");
useLibrary("uilayout");
useLibrary("uicontrols");
importClass(arkham.project.CopiesList);
const TTSJson = require("./TTSJson.js");
const Card = require("./Card.js");
const TTS_CARDS_PER_IMAGE = 69;
const TTS_MAX_ROWS = 7;
const getName = () => "TTSDeck";
const getDescription = () => "Generates a TTS deck image and JSON file";
const getVersion = () => 1.0;
const getPluginType = () => arkham.plugins.Plugin.INJECTED;
function TTSDeckPage(image_format, image_resolution, page_num, cards) {
this.image_format = image_format;
this.image_resolution = image_resolution;
this.page_num = page_num;
this.cards = cards;
function unload() {
unregisterAll();
}
// Creates a test button during development that calls unload() to clean up.
testProjectScript();
// TODO: allow setting a default copy count
// Hack to override the default return value of 1
function copyCount(copies_list, name) {
const entries = copies_list.getListEntries().map((x) => String(x));
if (entries.indexOf(String(name)) == -1) {
return 1;
} else {
return copies_list.getCopyCount(name);
}
}
function getImageFile(parent, format, page_num) {
return new File(
parent.file,
parent.getName() + "_" + page_num + "." + format
);
}
function Card(member) {
this.member = member;
this.component = ResourceKit.getGameComponentFromFile(member.file);
this.makeImageUncached = function makeImageUncached(resolution, back) {
println("Generating image for card ", this.member);
const sheets = this.component.createDefaultSheets();
const card_image = sheets[back ? 1 : 0].paint(
arkham.sheet.RenderTarget.EXPORT,
resolution
);
return card_image;
};
// export front face, or retrive it from a cached file
// TODO: handle two-sided cards
this.makeImage = function makeImage(format, resolution) {
const cache_dir = new File(this.member.parent.file, ".ttsdeck_cache");
const cached_file = new File(
cache_dir,
this.member.file.name + "." + format
);
if (
cached_file.exists() &&
cached_file.lastModified() > this.member.file.lastModified()
) {
println("Got cached image for card", this.member);
return ImageUtils.read(cached_file);
} else {
const card_image = this.makeImageUncached(resolution);
cache_dir.mkdir();
ImageUtils.write(card_image, cached_file, format, -1, false, resolution);
return card_image;
}
};
}
function TTSDeckPage(
busy_props,
image_format,
image_resolution,
page_num,
page_cards,
copies_list
) {
this.rows = Math.min(Math.ceil(Math.sqrt(page_cards.length)), TTS_MAX_ROWS);
this.columns = Math.ceil(page_cards.length / this.rows);
this.rows = Math.min(Math.ceil(Math.sqrt(cards.length)), TTS_MAX_ROWS);
this.columns = Math.ceil(cards.length / this.rows);
this.deck_image = null;
this.card_jsons = [];
this.face_url = String(
Card.getImageFile(cards[0].member.parent, image_format, this.page_num)
.toPath()
.toUri()
);
this.back_url = String(
Card.getImageFile(cards[0].member.parent, image_format, "back")
.toPath()
.toUri()
);
}
TTSDeckPage.prototype.build = function build(busy_props) {
let deck_graphics;
this.card_jsons = [];
for (let row = 0; row < this.rows; row++) {
for (
let col = 0;
col < this.columns && row * this.columns + col < page_cards.length;
col < this.columns && row * this.columns + col < this.cards.length;
col++
) {
if (busy_props.cancelled) return;
if (busy_props.cancelled) return this;
let index = row * this.columns + col;
let card = page_cards[index];
let card = this.cards[index];
busy_props.status = "Processing Card " + card.member;
busy_props.currentProgress = (page_num - 1) * TTS_CARDS_PER_IMAGE + index;
busy_props.currentProgress =
(this.page_num - 1) * TTS_CARDS_PER_IMAGE + index;
try {
let copies = copyCount(copies_list, card.baseName);
let copies = card.copyCount();
for (let ii = 0; ii < copies; ii++) {
this.card_jsons.push(
TTSJson.makeCardJSON(
page_num * 100 + index,
card.component.getName()
)
);
this.card_jsons.push(card.makeJSON(this.page_num * 100 + index));
}
let card_image = card.makeImage(image_format, image_resolution);
let card_image = card.makeImage(
this.image_format,
this.image_resolution
);
if (!this.deck_image) {
this.deck_image = ImageUtils.create(
@ -152,225 +81,94 @@ function TTSDeckPage(
println("End of Row ", row);
}
this.face_url = String(
getImageFile(page_cards[0].member.parent, image_format, page_num)
.toPath()
.toUri()
);
this.back_url = String(
getImageFile(page_cards[0].member.parent, image_format, "back")
.toPath()
.toUri()
);
return this;
};
function TTSDeck(image_format, image_resolution, cards) {
this.image_format = image_format;
this.image_resolution = image_resolution;
this.cards = cards;
this.pages = [];
}
function makeTTSDeck(
busy_props,
image_format,
image_resolution,
cards,
copies_list
) {
const pages = [];
TTSDeck.prototype.build = function build(busy_props) {
busy_props.title = "Processing Cards";
busy_props.maximumProgress = cards.length;
busy_props.maximumProgress = this.cards.length;
for (
let page_num = 0;
page_num * TTS_CARDS_PER_IMAGE < cards.length;
page_num * TTS_CARDS_PER_IMAGE < this.cards.length;
page_num++
) {
let page_cards = cards.slice(
if (busy_props.cancelled) return this;
let page_cards = this.cards.slice(
page_num * TTS_CARDS_PER_IMAGE,
(page_num + 1) * TTS_CARDS_PER_IMAGE
);
printf("Making page %d, with %d cards:\n", page_num + 1, page_cards.length);
pages.push(
this.pages.push(
new TTSDeckPage(
busy_props,
image_format,
image_resolution,
this.image_format,
this.image_resolution,
page_num + 1,
page_cards,
copies_list
)
page_cards
).build(busy_props)
);
if (busy_props.cancelled) return [,];
}
const deck_json = TTSJson.makeDeckJSON(pages);
return this;
};
return [deck_json, pages.map((page) => page.deck_image)];
}
TTSDeck.prototype.getImages = function getImages() {
return this.pages.map((page) => page.deck_image);
};
function settingsDialog(deck_task) {
const task_settings = deck_task.getSettings();
const image_format_field = comboBox([
ImageUtils.FORMAT_JPEG,
ImageUtils.FORMAT_PNG,
]);
image_format_field.setSelectedItem(
task_settings.get("tts_image_format", "jpg")
);
const resolution_field = textField(
task_settings.get("tts_image_resolution", "200"),
15
);
const clear_cache_button = button("Clear Cache", undefined, function (e) {
const cache_dir = new File(deck_task.file, ".ttsdeck_cache");
cache_dir.listFiles().forEach((file) => file.delete());
});
const panel = new Grid();
// prettier-ignore
panel.place(
"Image Format", "",
image_format_field, "grow,span",
"Resolution", "",
resolution_field, "grow,span",
clear_cache_button, "grow,span"
);
const close_button = panel.createDialog("TTS Export").showDialog();
return [
close_button,
image_format_field.getSelectedItem(),
Number(resolution_field.text),
];
}
function run() {
const ttsDeckAction = JavaAdapter(TaskAction, {
getLabel: () => "Generate TTS Deck",
getActionName: () => "ttsdeck",
// Applies to Deck Tasks
appliesTo: function appliesTo(project, task, member) {
if (member != null || task == null) {
return false;
}
const type = task.settings.get(Task.KEY_TYPE);
if (NewTaskType.DECK_TYPE.equals(type)) {
return true;
}
return false;
TTSDeck.prototype.makeJSON = function makeJSON(nickname, description) {
return {
Name: "DeckCustom",
Transform: {
posX: 0,
posY: 0,
posZ: 0,
rotX: 0,
rotY: 0.0,
rotZ: 0.0,
scaleX: 1.0,
scaleY: 1.0,
scaleZ: 1.0,
},
perform: function perform(project, task, member) {
let deck_task = ProjectUtilities.simplify(project, task, member);
const [close_button, image_format, image_resolution] =
settingsDialog(deck_task);
// User canceled the dialog or closed it without pressing ok
if (close_button != 1) {
return;
}
// persist settings
const task_settings = deck_task.getSettings();
task_settings.set("tts_image_format", image_format);
task_settings.set("tts_image_resolution", image_resolution);
deck_task.writeTaskSettings();
Eons.setWaitCursor(true);
try {
Thread.busyWindow(
(busy_props) =>
this.performImpl(
busy_props,
image_format,
image_resolution,
deck_task
),
"Setting up...",
true
);
} catch (ex) {
Error.handleUncaught(ex);
} finally {
Eons.setWaitCursor(false);
}
Nickname: String(nickname || ""),
Description: String(description || ""),
ColorDiffuse: {
r: 0.713239133,
g: 0.713239133,
b: 0.713239133,
},
performImpl: function performImpl(
busy_props,
image_format,
image_resolution,
member
) {
let copies_list;
try {
copies_list = new CopiesList(member);
} catch (ex) {
copies_list = new CopiesList();
alert(
"unable to read copies list, using card count of 2 for all files",
true
);
}
Grid: true,
Locked: false,
SidewaysCard: false,
DeckIDs: this.pages
.map((page) => page.card_jsons.map((card) => card.CardID))
.reduce((acc, val) => acc.concat(val), []),
CustomDeck: this.pages.reduce((acc, page, index) => {
acc[String(index + 1)] = {
FaceURL: String(page.face_url),
BackURL: String(page.back_url),
NumWidth: page.columns,
NumHeight: page.rows,
BackIsHidden: true,
};
return acc;
}, {}),
ContainedObjects: this.pages
.map((page) => page.card_jsons)
.reduce((acc, val) => acc.concat(val), []),
};
};
const children = member.getChildren();
const cards = children
.map((child) => {
if (ProjectUtilities.matchExtension(child, "eon")) {
let card = new Card(child);
if (card.component.isDeckLayoutSupported()) {
return card;
}
}
return undefined;
})
.filter((card) => card !== undefined);
const [deck_json, deck_images] = makeTTSDeck(
busy_props,
image_format,
image_resolution,
cards,
copies_list
);
if (busy_props.cancelled) return;
const saved_object = TTSJson.makeSavedObjectJSON(
[deck_json],
member.getName()
);
busy_props.status = "";
busy_props.maximumProgress = -1;
busy_props.title = "Writing JSON";
const json_file = new File(member.file, member.getName() + ".json");
ProjectUtilities.writeTextFile(
json_file,
JSON.stringify(saved_object, null, 4)
);
busy_props.title = "Writing Images";
busy_props.maximumProgress = deck_images.length;
deck_images.forEach((deck_image, index) => {
busy_props.currentProgress = index;
const image_file = getImageFile(member, image_format, index + 1);
ImageUtils.write(
deck_image,
image_file,
image_format,
-1,
false,
image_resolution
);
});
let back_image = cards[0].makeImageUncached(image_resolution, true);
const back_image_file = getImageFile(member, image_format, "back");
ImageUtils.write(
back_image,
back_image_file,
image_format,
-1,
false,
image_resolution
);
member.synchronize();
},
});
ActionRegistry.register(ttsDeckAction, Actions.PRIORITY_IMPORT_EXPORT);
}
module.exports = {
TTSDeckPage: TTSDeckPage,
TTSDeck: TTSDeck,
};

View File

@ -1,80 +1,5 @@
// Helper functions for Tabletop Simulator JSON output
exports.makeCardJSON = function makeCardJSON(card_id, nickname, description) {
return {
Name: "Card",
Transform: {
posX: 0,
posY: 0,
posZ: 0,
rotX: 0,
rotY: 0,
rotZ: 0,
scaleX: 1.0,
scaleY: 1.0,
scaleZ: 1.0,
},
Nickname: String(nickname),
CardID: card_id,
Description: String(description || ""),
ColorDiffuse: {
r: 0.713235259,
g: 0.713235259,
b: 0.713235259,
},
Locked: false,
Grid: true,
Snap: true,
Autoraise: true,
Sticky: true,
Tooltip: true,
SidewaysCard: false,
};
};
exports.makeDeckJSON = function makeDeckJSON(pages, nickname, description) {
return {
Name: "DeckCustom",
Transform: {
posX: 0,
posY: 0,
posZ: 0,
rotX: 0,
rotY: 0.0,
rotZ: 0.0,
scaleX: 1.0,
scaleY: 1.0,
scaleZ: 1.0,
},
Nickname: String(nickname || ""),
Description: String(description || ""),
ColorDiffuse: {
r: 0.713239133,
g: 0.713239133,
b: 0.713239133,
},
Grid: true,
Locked: false,
SidewaysCard: false,
DeckIDs: pages
.map((page) => page.card_jsons.map((card) => card.CardID))
.reduce((acc, val) => acc.concat(val), []),
CustomDeck: pages.reduce((acc, page, index) => {
acc[String(index + 1)] = {
FaceURL: String(page.face_url),
BackURL: String(page.back_url),
NumWidth: page.columns,
NumHeight: page.rows,
BackIsHidden: true,
};
return acc;
}, {}),
ContainedObjects: pages
.map((page) => page.card_jsons)
.reduce((acc, val) => acc.concat(val), []),
};
};
exports.makeSavedObjectJSON = function makeSavedObjectJSON(objects, save_name) {
return {
SaveName: String(save_name || ""),

202
resources/ttsdeck/plugin.js Normal file
View File

@ -0,0 +1,202 @@
/*
* TTSDeck.js
*
* Creates a deck image and corresponding "Saved Object" JSON for use
* in Tabletop Simulator
*/
useLibrary("project");
useLibrary("imageutils");
useLibrary("threads");
useLibrary("uilayout");
useLibrary("uicontrols");
importClass(arkham.project.CopiesList);
const Card = require("./Card.js");
const { TTSDeck } = require("./TTSDeck.js");
const TTSJson = require("./TTSJson.js");
const getName = () => "TTSDeck";
const getDescription = () => "Generates a TTS deck image and JSON file";
const getVersion = () => 1.0;
const getPluginType = () => arkham.plugins.Plugin.INJECTED;
function unload() {
unregisterAll();
}
// Creates a test button during development that calls unload() to clean up.
testProjectScript();
function settingsDialog(deck_task) {
const task_settings = deck_task.getSettings();
const image_format_field = comboBox([
ImageUtils.FORMAT_JPEG,
ImageUtils.FORMAT_PNG,
]);
image_format_field.setSelectedItem(
task_settings.get("tts_image_format", "jpg")
);
const resolution_field = textField(
task_settings.get("tts_image_resolution", "200"),
15
);
const clear_cache_button = button("Clear Cache", undefined, function (e) {
const cache_dir = new File(deck_task.file, ".ttsdeck_cache");
cache_dir.listFiles().forEach((file) => file.delete());
});
const panel = new Grid();
// prettier-ignore
panel.place(
"Image Format", "",
image_format_field, "grow,span",
"Resolution", "",
resolution_field, "grow,span",
clear_cache_button, "grow,span"
);
const close_button = panel.createDialog("TTS Export").showDialog();
return [
close_button,
image_format_field.getSelectedItem(),
Number(resolution_field.text),
];
}
function run() {
const ttsDeckAction = JavaAdapter(TaskAction, {
getLabel: () => "Generate TTS Deck",
getActionName: () => "ttsdeck",
// Applies to Deck Tasks
appliesTo: function appliesTo(project, task, member) {
if (member != null || task == null) {
return false;
}
const type = task.settings.get(Task.KEY_TYPE);
if (NewTaskType.DECK_TYPE.equals(type)) {
return true;
}
return false;
},
perform: function perform(project, task, member) {
let deck_task = ProjectUtilities.simplify(project, task, member);
const [close_button, image_format, image_resolution] =
settingsDialog(deck_task);
// User canceled the dialog or closed it without pressing ok
if (close_button != 1) {
return;
}
// persist settings
const task_settings = deck_task.getSettings();
task_settings.set("tts_image_format", image_format);
task_settings.set("tts_image_resolution", image_resolution);
deck_task.writeTaskSettings();
Eons.setWaitCursor(true);
try {
Thread.busyWindow(
(busy_props) =>
this.performImpl(
busy_props,
image_format,
image_resolution,
deck_task
),
"Setting up...",
true
);
} catch (ex) {
Error.handleUncaught(ex);
} finally {
Eons.setWaitCursor(false);
}
},
performImpl: function performImpl(
busy_props,
image_format,
image_resolution,
member
) {
let copies_list;
try {
copies_list = new CopiesList(member);
} catch (ex) {
copies_list = new CopiesList();
alert(
"unable to read copies list, using card count of 2 for all files",
true
);
}
const children = member.getChildren();
const cards = children
.map((child) => {
if (ProjectUtilities.matchExtension(child, "eon")) {
let card = new Card(child, copies_list);
if (card.component.isDeckLayoutSupported()) {
return card;
}
}
return undefined;
})
.filter((card) => card !== undefined);
const deck = new TTSDeck(
image_format,
image_resolution,
cards,
copies_list
).build(busy_props);
if (busy_props.cancelled) return;
const saved_object = TTSJson.makeSavedObjectJSON(
[deck.makeJSON()],
member.getName()
);
busy_props.status = "";
busy_props.maximumProgress = -1;
busy_props.title = "Writing JSON";
const json_file = new File(member.file, member.getName() + ".json");
ProjectUtilities.writeTextFile(
json_file,
JSON.stringify(saved_object, null, 4)
);
const deck_images = deck.getImages();
busy_props.title = "Writing Images";
busy_props.maximumProgress = deck_images.length;
deck_images.forEach((deck_image, index) => {
busy_props.currentProgress = index;
const image_file = Card.getImageFile(member, image_format, index + 1);
ImageUtils.write(
deck_image,
image_file,
image_format,
-1,
false,
image_resolution
);
});
let back_image = cards[0].makeImageUncached(image_resolution, true);
const back_image_file = Card.getImageFile(member, image_format, "back");
ImageUtils.write(
back_image,
back_image_file,
image_format,
-1,
false,
image_resolution
);
member.synchronize();
},
});
ActionRegistry.register(ttsDeckAction, Actions.PRIORITY_IMPORT_EXPORT);
}