Created
October 10, 2021 14:48
-
-
Save patrickporto/b5a41dd25539728c0b4ef9c3903eedee to your computer and use it in GitHub Desktop.
fvtt-merchant-macro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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