Skip to content

Instantly share code, notes, and snippets.

@patrickporto
Created October 10, 2021 14:48
Show Gist options
  • Save patrickporto/b5a41dd25539728c0b4ef9c3903eedee to your computer and use it in GitHub Desktop.
Save patrickporto/b5a41dd25539728c0b4ef9c3903eedee to your computer and use it in GitHub Desktop.
fvtt-merchant-macro
const i18n = {
"MERCHANT.Buy": "Comprar",
"MERCHANT.Sell": "Vender"
}
class Merchant {
constructor({
merchant,
player,
merchantExchange,
playerExchange,
}) {
this.merchant = merchant
this.player = player
this.merchantItems = getProperty(merchant, "data.items")
this.playerItems = getProperty(player, "data.items")
this.merchantExchange = merchantExchange
this.playerExchange = playerExchange
}
handlerToggleItemInfo({target}) {
const itemInfo = $(target).closest(".item").find(".spellinfo")
itemInfo.toggle()
}
async handleCoinsClick({which, target}) {
const LEFT_BUTTON = 1
const RIGHT_BUTTON = 3
const actor = $(target).closest(".coins").data("actor")
const currency = $(target).closest(".coins").data("currency")
switch (which) {
case LEFT_BUTTON:
if (actor === "player") {
await this.playerExchange.upgrade(currency)
} else {
await this.merchantExchange.upgrade(currency)
}
break;
case RIGHT_BUTTON:
if (actor === "player") {
await this.playerExchange.downgrade(currency)
} else {
await this.merchantExchange.downgrade(currency)
}
break;
}
this.updateMoney()
}
async handleBuyButton({target}) {
const itemElement = $(target).closest(".item")
const itemId = itemElement.data("item-id")
const item = this.merchantItems.get(itemId)
await this.handleBuyItem(item)
itemElement.remove()
$(".playerItems").append(this.renderPlayerItem(item))
this.updateMoney()
}
async handleSellButton({target}) {
const itemElement = $(target).closest(".item")
const itemId = itemElement.data("item-id")
const item = this.playerItems.get(itemId)
await this.handleSellItem(item)
itemElement.remove()
$(".merchantItems").append(this.renderMerchantItem(item))
this.updateMoney()
}
async handleBuyItem(item) {
const [rawPrice, rawCurrency] = item.data.data.value.split(" ")
const price = Number(rawPrice)
const currency = this.playerExchange.getCurrencyAlias(rawCurrency)
await this.playerExchange.withdraw(currency, price)
await this.merchantExchange.deposit(currency, price)
const newItem = item.clone().toObject()
await this.player.createEmbeddedDocuments("Item", [newItem])
if (getProperty(item, "data.data.quantity") < 2) {
await this.merchant.deleteEmbeddedDocuments("Item", [item.id])
} else {
item.quantity -= 1
await this.merchant.updateEmbeddedDocuments("Item", [item])
}
}
async handleSellItem(item) {
const [rawPrice, rawCurrency] = item.data.data.value.split(" ")
const price = Number(rawPrice)
const currency = this.merchantExchange.getCurrencyAlias(rawCurrency)
await this.merchantExchange.withdraw(currency, price)
await this.playerExchange.deposit(currency, price)
const newItem = item.clone().toObject()
await this.merchant.createEmbeddedDocuments("Item", [newItem])
if (getProperty(item, "data.data.quantity") < 2) {
await this.player.deleteEmbeddedDocuments("Item", [item.id])
} else {
item.quantity -= 1
await this.player.updateEmbeddedDocuments("Item", [item])
}
}
updateMoney() {
for(const [currency, value] of Object.entries(this.merchantExchange.moneybag)) {
$(`[data-actor=merchant][data-currency=${currency}] > .resource-label`).text(value)
}
for(const [currency, value] of Object.entries(this.playerExchange.moneybag)) {
$(`[data-actor=player][data-currency=${currency}] > .resource-label`).text(value)
}
}
activateListeners(html) {
html.find(".toggleItemInfo").on("click", this.handlerToggleItemInfo.bind(this))
html.find(".buyButton").on("click", this.handleBuyButton.bind(this))
html.find(".sellButton").on("click", this.handleSellButton.bind(this))
html.find(".coins").on("mousedown", this.handleCoinsClick.bind(this))
}
getTemplateData() {
return {
merchantItems: Array.from(this.merchantItems.values()),
playerItems: Array.from(this.playerItems.values())
}
}
renderMerchantItem(item) {
const template = `
<li class="table-row item dropitem" data-item-id="${item.id}" draggable="true">
<div class="col col-1" data-label="ItemName">
<div class="item-image">
<img src="${item.data.img}" title="${item.data.name}" width="30" height="30">
</div>
<div class="itemtile ">${item.data.name}</div>
<div class="toggleItemInfo"><i class="fas fa-angle-right"></i> Informação do item</div>
<div class="spellinfo" style="display: none;">
${item.data.data.description}
</div>
</div>
<div class="col col-2 item-uses" data-label="Amount">
${item.data.data.quantity} <i class="fas fa-arrows-alt-v"></i>
</div>
<div class="col col-3" data-label="Value">${item.data.data.value}</div>
<div class="col col-4">
<button class="buyButton">${i18n["MERCHANT.Buy"]}</button>
</div>
</li>
`
return template
}
renderPlayerItem(item) {
const template = `
<li class="table-row item dropitem" data-item-id="${item.id}" draggable="true">
<div class="col col-1" data-label="ItemName">
<div class="item-image">
<img src="${item.data.img}" title="${item.data.name}" width="30" height="30">
</div>
<div class="itemtile ">${item.data.name}</div>
<div class="toggleItemInfo"><i class="fas fa-angle-right"></i> Informação do item</div>
<div class="spellinfo" style="display: none;">
${item.data.data.description}
</div>
</div>
<div class="col col-2 item-uses" data-label="Amount">
${item.data.data.quantity} <i class="fas fa-arrows-alt-v"></i>
</div>
<div class="col col-3" data-label="Value">${item.data.data.value}</div>
<div class="col col-4">
<button class="sellButton">${i18n["MERCHANT.Sell"]}</button>
</div>
</li>
`
return template
}
async render() {
const templateData = this.getTemplateData()
const merchantItemsTemplate = templateData.merchantItems.map(this.renderMerchantItem.bind(this)).join("")
const playerItemsTemplate = templateData.playerItems.map(this.renderPlayerItem.bind(this)).join("")
const merchantTemplate = `
<div class="demonlord grid grid-2col">
<style>
.coins {
text-align: center;
cursor: pointer;
}
.cointitle {
display: inline-flex;
font-weight: bold;
}
</style>
<section class="colleft" style="border-right: 1px solid #333;">
<div class="grid grid-4col">
<div class="coins" data-actor="player" data-currency="gc">
<div class="cointitle">CO: </div>
<label class="resource-label">${this.playerExchange.moneybag.gc}<label>
</div>
<div class="coins" data-actor="player" data-currency="ss">
<div class="cointitle">XP: </div>
<label class="resource-label">${this.playerExchange.moneybag.ss}</label>
</div>
<div class="coins" data-actor="player" data-currency="cp">
<div class="cointitle">CC: </div>
<label class="resource-label">${this.playerExchange.moneybag.cp}</label>
</div>
<div class="coins" data-actor="player" data-currency="bits">
<div class="cointitle">MA: </div>
<label class="resource-label">${this.playerExchange.moneybag.bits}</label>
</div>
</div>
<ul class="responsive-table playerItems">
<li class="table-header">
<div class="col col-1">Itens</div>
<div class="col col-2">Quantidade</div>
<div class="col col-3">Preço</div>
<div class="col col-4"></div>
</li>
${playerItemsTemplate}
</ul>
</section>
<section class="colright">
<div class="grid grid-4col">
<div class="coins" data-actor="merchant" data-currency="gc">
<div class="cointitle">CO: </div>
<label class="resource-label">${this.merchantExchange.moneybag.gc}</label>
</div>
<div class="coins" data-actor="merchant" data-currency="ss">
<div class="cointitle">XP: </div>
<label class="resource-label">${this.merchantExchange.moneybag.ss}</label>
</div>
<div class="coins" data-actor="merchant" data-currency="cp">
<div class="cointitle">CC: </div>
<label class="resource-label">${this.merchantExchange.moneybag.cp}</label>
</div>
<div class="coins" data-actor="merchant" data-currency="bits">
<div class="cointitle">MA: </div>
<label class="resource-label">${this.merchantExchange.moneybag.bits}</label>
</div>
</div>
<ul class="responsive-table merchantItems">
<li class="table-header">
<div class="col col-1">Itens</div>
<div class="col col-2">Quantidade</div>
<div class="col col-3">Preço</div>
<div class="col col-4"></div>
</li>
${merchantItemsTemplate}
</ul>
</section>
</div>
`
new Dialog({
title: "Merchant",
content: merchantTemplate,
buttons:{},
render: this.activateListeners.bind(this),
}, {width: 970,}).render(true);
}
}
class DemonLordExchange {
constructor(actor) {
this.actor = actor
this.moneybag = {
"bits": Number(getProperty(actor, "data.data.wealth.bits")),
"cp": Number(getProperty(actor, "data.data.wealth.cp")),
"ss": Number(getProperty(actor, "data.data.wealth.ss")),
"gc": Number(getProperty(actor, "data.data.wealth.gc")),
}
this.currencyConversion = {
"gc": {into: "ss", each: 10},
"ss": {into: "cp", each: 10},
"cp": {into: "bits", each: 10},
}
}
getCurrencyAlias(currency) {
const alias = {
[game.i18n.translations.DL.CharWealthBits.toLowerCase()]: "bits",
[game.i18n.translations.DL.CharWealthSP.toLowerCase()]: "cp",
[game.i18n.translations.DL.CharWealthSS.toLowerCase()]: "ss",
[game.i18n.translations.DL.CharWealthGC.toLowerCase()]: "gc",
}
return alias[currency.toLowerCase()] || currency
}
async upgrade(currency) {
const amount = 1
try {
const [conversionCurrency, conversion] = Object.entries(this.currencyConversion).find(([conversionCurrency, conversion]) => conversion.into === currency)
await this.withdraw(currency, conversion.each * amount)
await this.deposit(conversionCurrency, amount)
} catch {}
}
async downgrade(currency) {
const amount = 1
try {
const conversion = this.currencyConversion[currency]
await this.withdraw(currency, amount)
await this.deposit(conversion.into, conversion.each * amount)
} catch {}
}
async withdraw(currency, amount) {
const balance = this.moneybag[currency] - amount
if (balance < 0) {
ui.notifications.error("Moedas insuficiente")
throw new Error("insufficient money")
}
await this.actor.update({
[`data.wealth.${currency}`]: balance
});
this.moneybag[currency] = balance
}
async deposit(currency, amount) {
const balance = this.moneybag[currency] + amount
await this.actor.update({
[`data.wealth.${currency}`]: balance
});
this.moneybag[currency] = balance
}
}
const createMerchant = async () => {
const merchantActor = canvas.tokens.placeables.find(t => t.name === args[0]).actor
const playerExchange = new DemonLordExchange(game.user.character)
const merchantExchange = new DemonLordExchange(merchantActor)
const merchant = new Merchant({
merchantExchange,
playerExchange,
player: game.user.character,
merchant: merchantActor
})
merchant.render()
}
createMerchant()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment