diff --git a/README.md b/README.md index fbed78c..17dc446 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,29 @@ -![](https://img.shields.io/badge/Foundry-v10-informational) - - - +# Item Piles: Auctioneer - - - +![Latest Release Download Count](https://img.shields.io/github/downloads/fantasycalendar/FoundryVTT-ItemPiles-Auctioneer/latest/module.zip?color=2b82fc&label=DOWNLOADS&style=for-the-badge) [![Forge Installs](https://img.shields.io/badge/dynamic/json?label=Forge%20Installs&query=package.installs&suffix=%25&url=https%3A%2F%2Fforge-vtt.com%2Fapi%2Fbazaar%2Fpackage%2Fitem-piles-auctioneer&colorB=006400&style=for-the-badge)](https://forge-vtt.com/bazaar#package=item-piles-auctioneer) ![Foundry Core Compatible Version](https://img.shields.io/badge/dynamic/json.svg?url=https%3A%2F%2Fgithub.com%2Ffantasycalendar%2FFoundryVTT-ItemPiles-Auctioneer%2Freleases%2Flatest%2Fdownload%2Fmodule.json&label=Foundry%20Compatible%20Version&query=$.compatibility.minimum&colorB=orange&style=for-the-badge) ![Foundry Core Supported Version](https://img.shields.io/badge/dynamic/json.svg?url=https%3A%2F%2Fgithub.com%2Ffantasycalendar%2FFoundryVTT-ItemPiles-Auctioneer%2Freleases%2Flatest%2Fdownload%2Fmodule.json&label=Foundry%20Verified%20Version&query=$.compatibility.verified&colorB=orange&style=for-the-badge) ![Latest Version](https://img.shields.io/badge/dynamic/json.svg?url=https%3A%2F%2Fgithub.com%2Ffantasycalendar%2FFoundryVTT-ItemPiles-Auctioneer%2Freleases%2Flatest%2Fdownload%2Fmodule.json&label=Latest%20Release&prefix=v&query=$.version&colorB=red&style=for-the-badge) +--- -# How to use this Template to create a versioned Release +Fantasy Computerworks Logo -1. Open your repository's releases page. +A module made by [Fantasy Computerworks](http://fantasycomputer.works/). -![Where to click to open repository releases.](https://user-images.githubusercontent.com/7644614/93409301-9fd25080-f864-11ea-9e0c-bdd09e4418e4.png) +Other works by us: -2. Click "Draft a new release" +- [Fantasy Calendar](https://app.fantasy-calendar.com) - The best calendar creator and management app on the internet +- [Sequencer](https://foundryvtt.com/packages/sequencer) - Wow your players by playing visual effects on the canvas +- [Item Piles](https://foundryvtt.com/packages/item-piles) - Drag & drop items into the scene to drop item piles that you can then easily pick up +- [Tagger](https://foundryvtt.com/packages/tagger) - Tag objects in the scene and retrieve them with a powerful API +- [Token Ease](https://foundryvtt.com/packages/token-ease) - Make your tokens _feel good_ to move around on the board +- [Rest Recovery](https://foundryvtt.com/packages/rest-recovery) - Automate most D&D 5e long and short rest mechanics -![Draft a new release button.](https://user-images.githubusercontent.com/7644614/93409364-c1333c80-f864-11ea-89f1-abfcb18a8d9f.png) +Like what we've done? Buy us a coffee! -3. Fill out the release version as the tag name. +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/fantasycomputerworks) -If you want to add details at this stage you can, or you can always come back later and edit them. +--- -![Release Creation Form](https://user-images.githubusercontent.com/7644614/93409543-225b1000-f865-11ea-9a19-f1906a724421.png) +## What is Item Piles: Auctioneer? -4. Hit submit. +This module allows you to create self-sustained auction houses. Players can create auctions, bid on existing auctions, claim auctions that they have won, renew or renew expired auctions they have created. -5. Wait a few minutes. - -A Github Action will run to populate the `module.json` and `module.zip` with the correct urls that you can then use to distribute this release. You can check on its status in the "Actions" tab. - -![Actions Tab](https://user-images.githubusercontent.com/7644614/93409820-c1800780-f865-11ea-8c6b-c3792e35e0c8.png) - -6. Grab the module.json url from the release's details page. - -![image](https://user-images.githubusercontent.com/7644614/93409960-10c63800-f866-11ea-83f6-270cc5d10b71.png) - -This `module.json` will only ever point at this release's `module.zip`, making it useful for sharing a specific version for compatibility purposes. - -7. You can use the url `https://github.com///releases/latest/download/module.json` to refer to the manifest. - -This is the url you want to use to install the module typically, as it will get updated automatically. - -# How to List Your Releases on Package Admin - -To request a package listing for your first release, go to the [Package Submission Form](https://foundryvtt.com/packages/submit) (accessible via a link at the bottom of the "[Systems and Modules](https://foundryvtt.com/packages/)" page on the Foundry website). - -Fill in the form. "Package Name" must match the name in the module manifest. Package Title will be the display name for the package. Package URL should be your repo URL. -![image](https://user-images.githubusercontent.com/36359784/120664263-b49e5500-c482-11eb-9126-af7006389903.png) - - -One of the Foundry staff will typically get back to you with an approval or any further questions within a few days, and give you access to the package admin pages. - -Once you have access to the [module admin page](https://foundryvtt.com/admin/packages/package/), you can release a new version by going into the page for your module, scrolling to the bottom, and filling in a new Package Version. - -When listing a new version, Version should be the version number you set above, and the Manifest URL should be the manifest __for that specific version__ (do not use /latest/ here). -![image](https://user-images.githubusercontent.com/36359784/120664346-c4b63480-c482-11eb-9d8b-731b50d70939.png) - -> ### :warning: Important :warning: -> -> It is very important that you use the specific release manifest url, and not the `/latest` url here. For more details about why this is important and how Foundry Installs/Updates packages, read [this wiki article](https://foundryvtt.wiki/en/development/guides/releases-and-history). - -Clicking "Save" in the bottom right will save the new version, which means that anyone installing your module from within Foundry will get that version, and a post will be generated in the #release-announcements channel on the official Foundry VTT Discord. - - -# FoundryVTT Module - -Does something, probably - -## Changelog diff --git a/module.json b/module.json index 99798af..9218a3f 100644 --- a/module.json +++ b/module.json @@ -31,8 +31,8 @@ "id": "item-piles", "type": "module", "compatibility": { - "minimum": "2.8.7", - "verified": "2.8.7" + "minimum": "2.8.8", + "verified": "2.8.8" } }, { diff --git a/src/applications/auctioneer/Auctions/Auctions.svelte b/src/applications/auctioneer/Auctions/Auctions.svelte index fc33d92..960440e 100644 --- a/src/applications/auctioneer/Auctions/Auctions.svelte +++ b/src/applications/auctioneer/Auctions/Auctions.svelte @@ -32,7 +32,7 @@
- + - .auctions { - display: grid; - grid-template-columns: 200px auto; - grid-column-gap: 0.25rem; - } + .auctions { + display: grid; + grid-template-columns: 200px auto; + grid-column-gap: 0.25rem; + } .item-list { display: flex; @@ -61,11 +61,11 @@ border: var(--item-piles-auctioneer-border); border-radius: var(--item-piles-auctioneer-border-radius); height: calc(var(--item-piles-auctioneer-ui-height) - 83px); - max-height: calc(var(--item-piles-auctioneer-ui-height) - 83px); + max-height: calc(var(--item-piles-auctioneer-ui-height) - 83px); overflow: hidden; } - :global.virtual-scroll-root{ + :global.virtual-scroll-root { overflow-x: hidden; } diff --git a/src/applications/auctioneer/Auctions/CreateAuctionSidebar.svelte b/src/applications/auctioneer/Auctions/CreateAuctionSidebar.svelte index 6c25c84..f08ef4e 100644 --- a/src/applications/auctioneer/Auctions/CreateAuctionSidebar.svelte +++ b/src/applications/auctioneer/Auctions/CreateAuctionSidebar.svelte @@ -176,7 +176,8 @@
Auction deposit: - {depositCurrenciesString || "None"} + {depositCurrenciesString || "None"}
{/if} {#if $flagStore.auctionBidVisibility === CONSTANTS.VISIBILITY_KEYS.USER} @@ -264,8 +265,8 @@
{/if} {/if} -
+
Duration: - +
{/each}
@@ -51,7 +51,7 @@ {#each secondaryCurrencies as currency}
- +
{/each} diff --git a/src/applications/auctioneer/Auctions/currency-store.js b/src/applications/auctioneer/Auctions/currency-store.js index 86f4a87..c022d8c 100644 --- a/src/applications/auctioneer/Auctions/currency-store.js +++ b/src/applications/auctioneer/Auctions/currency-store.js @@ -13,7 +13,7 @@ export default function CurrencyStore(store) { const { set, update, subscribe } = currencyStore; - function reset(){ + function reset() { update(data => { data.primaryCurrencies = data.primaryCurrencies.map(currency => { currency.quantity = 0; diff --git a/src/applications/auctioneer/Bids/BidItem.svelte b/src/applications/auctioneer/Bids/BidItem.svelte index c713d8b..a5550d6 100644 --- a/src/applications/auctioneer/Bids/BidItem.svelte +++ b/src/applications/auctioneer/Bids/BidItem.svelte @@ -18,8 +18,8 @@
{ store.entryClicked(bid.auction.uuid) }} > diff --git a/src/applications/auctioneer/Bids/Bids.svelte b/src/applications/auctioneer/Bids/Bids.svelte index b709b5a..cec8ae7 100644 --- a/src/applications/auctioneer/Bids/Bids.svelte +++ b/src/applications/auctioneer/Bids/Bids.svelte @@ -59,7 +59,7 @@ overflow: hidden; } - :global.virtual-scroll-root{ + :global.virtual-scroll-root { overflow-x: hidden; } diff --git a/src/applications/auctioneer/Browse/AuctionItem.svelte b/src/applications/auctioneer/Browse/AuctionItem.svelte index 70b568f..0b1189d 100644 --- a/src/applications/auctioneer/Browse/AuctionItem.svelte +++ b/src/applications/auctioneer/Browse/AuctionItem.svelte @@ -38,29 +38,30 @@ {CONSTANTS.BID_VISIBILITY_UI_LABELS[auction.bidVisibility]}
{#if showStartPrice || !showReserve} -
-
- {#each auction.bidPriceData.currencies as currency (currency.id)} -
- {currency.quantity} - -
- {/each} -
- {#if auction.buyoutPriceData} -
- {#each auction.buyoutPriceData.currencies as currency (currency.id)} +
+
+ {#each auction.bidPriceData.currencies as currency (currency.id)}
{currency.quantity}
{/each}
- {/if} -
+ {#if auction.buyoutPriceData} +
+ {#each auction.buyoutPriceData.currencies as currency (currency.id)} +
+ {currency.quantity} + +
+ {/each} +
+ {/if} +
{:else if showReserve && !showStartPrice} {#if auction.reservePrice} -
+
{#each auction.reservePriceData.currencies as currency (currency.id)}
diff --git a/src/applications/auctioneer/Browse/Browse.svelte b/src/applications/auctioneer/Browse/Browse.svelte index c69cf58..fe429a6 100644 --- a/src/applications/auctioneer/Browse/Browse.svelte +++ b/src/applications/auctioneer/Browse/Browse.svelte @@ -72,7 +72,7 @@ .item-list { display: flex; - flex-direction: column; + flex-direction: column; border: var(--item-piles-auctioneer-border); border-radius: var(--item-piles-auctioneer-border-radius); height: calc(var(--item-piles-auctioneer-ui-height) - 114px); @@ -81,7 +81,7 @@ } - :global.virtual-scroll-root{ + :global.virtual-scroll-root { overflow-x: hidden; } diff --git a/src/applications/auctioneer/Browse/TopBar.svelte b/src/applications/auctioneer/Browse/TopBar.svelte index 53c9976..750c428 100644 --- a/src/applications/auctioneer/Browse/TopBar.svelte +++ b/src/applications/auctioneer/Browse/TopBar.svelte @@ -29,33 +29,35 @@ --border-hover="var(--item-piles-auctioneer-border)" --border-radius="var(--item-piles-auctioneer-border-radius)" --chevron-width="20px" - --multi-select-input-margin="none" - --internal-padding="0 0.15rem" - --value-container-padding="0" - --multi-select-padding="0 0.15rem" - --multi-item-margin="0" - --multi-item-padding="0 0.25rem" - --multi-item-gap="0" --font-family="inherit" --font-size="0.75rem" --height="27px" --input-color="black" + --internal-padding="0 0.15rem" + --margin="0 0.1rem 0 0.25rem" --max-height="27px" - --text-overflow="ellipsis" --multi-item-bg="rgba(255, 255, 255, 0.25)" + --multi-item-gap="0" + --multi-item-margin="0" + --multi-item-padding="0 0.25rem" + --multi-select-input-margin="none" + --multi-select-padding="0 0.15rem" + --text-overflow="ellipsis" + --value-container-padding="0" --width="100%" - --margin="0 0.1rem 0 0.25rem" - multiFullItemClearable={true} - multiple={true} - clearable={false} bind:value={$selectedCategoriesStore} + clearable={false} floatingConfig={{ strategy: "fixed", placement: "bottom" }} items={$categoryStore} - showChevron={true} + multiFullItemClearable={true} + multiple={true} placeholder="Select type filter..." + showChevron={true} > - + Ignore deposit payment

`, yes: (html) => { @@ -732,20 +734,21 @@ export function createStore(auctioneer) { options: { classes: ["dialog", "item-piles-auctioneer"] } }); if (!proceed) return; - if(!ignoreDeposit) { - await game.itempiles.API.removeCurrencies(actor, existingAuctions[indexToRefresh].depositPrice); + if (!ignoreDeposit) { + await game.itempiles.API.removeCurrencies(actor, duplicatedAuction.depositPrice); } - }else { + } else { const proceed = await Dialog.confirm({ title: "Relist Failed Auction", - content: `

By relisting this auction, ${actor.name} must pay ${existingAuctions[indexToRefresh].depositPrice} - are you sure you want to do this?

`, + content: `

By relisting this auction, ${actor.name} must pay ${duplicatedAuction.depositPrice} - are you sure you want to do this?

`, options: { classes: ["dialog", "item-piles-auctioneer"] } }); if (!proceed) return; - await game.itempiles.API.removeCurrencies(actor, existingAuctions[indexToRefresh].depositPrice); + await game.itempiles.API.removeCurrencies(actor, duplicatedAuction.depositPrice); } } } + existingAuctions.push(duplicatedAuction); ui.notifications.notify(`The auction for ${auction.item.name} has been relisted.`); return game.user.setFlag(CONSTANTS.MODULE_NAME, CONSTANTS.AUCTIONS_FLAG, existingAuctions); } @@ -837,7 +840,7 @@ export function getAuctioneerActorData(auctioneer) { && auctions[source.auctionUuid] && !auctions[source.auctionUuid].won; }).forEach(source => { - const bid = lib.makeBid(auctioneer, source, auctions); + const bid = lib.makeBid(auctioneer, source, auctions); bids[bid.id] = bid; }); @@ -855,10 +858,10 @@ export function getAuctioneerActorData(auctioneer) { auction.bidPrice = auction.bids.length ? ownedBids[0].price : false; } - auction.minBidPrice = auction.minBidPrice + auction.actualMininumBidPrice = auction.minBidPrice && auction.bidPrice ? game.itempiles.API.calculateCurrencies(auction.bidPrice, auction.minBidPrice, false) - : auction.bidPrice || auction.startPrice; - auction.minBidPriceData = lib.getPriceFromData(auction.minBidPrice); + : auction.minBidPrice || auction.bidPrice; + auction.actualMininumBidPriceData = lib.getPriceFromData(auction.actualMininumBidPrice); if (auction.bids.length && auction.buyoutPrice) { if (lib.isPriceHigherThan(auction.bids[0].priceData, auction.buyoutPriceData)) { diff --git a/src/applications/auctioneer/auctioneer.js b/src/applications/auctioneer/auctioneer.js index e7e7835..87dc849 100644 --- a/src/applications/auctioneer/auctioneer.js +++ b/src/applications/auctioneer/auctioneer.js @@ -16,10 +16,6 @@ export default class Auctioneer extends SvelteApplication { this.actor = options.auctioneer; } - onDropData(data){ - return this.store.onDropData(data); - } - /** @inheritdoc */ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { @@ -52,7 +48,9 @@ export default class Auctioneer extends SvelteApplication { }) } - + onDropData(data) { + return this.store.onDropData(data); + } /** @override */ _getHeaderButtons() { @@ -60,6 +58,14 @@ export default class Auctioneer extends SvelteApplication { const canConfigure = game.user.isGM; if (canConfigure) { buttons = [ + { + label: !game.settings.get('item-piles', 'hideActorHeaderText') ? "ITEM-PILES.Inspect.OpenSheet" : "", + class: "item-piles-open-actor-sheet", + icon: "fas fa-user", + onclick: () => { + this.actor.sheet.render(true, { focus: true, bypassItemPiles: true }); + } + }, { label: !game.settings.get("item-piles", "hideActorHeaderText") ? "ITEM-PILES.HUD.Configure" : "", class: "item-piles-configure-pile", diff --git a/src/constants.js b/src/constants.js index cb7e695..3d11f89 100644 --- a/src/constants.js +++ b/src/constants.js @@ -38,10 +38,12 @@ const CONSTANTS = { * @property {AuctionTimeType} timeType * @property {string} minTimeLimit * @property {string} maxTimeLimit + * @property {Item/boolean} entryItem * @property {boolean} displayEntryItem + * @property {Actor/boolean} owner */ ACTOR_DEFAULTS: { - auctionFee: 5, + auctionFee: "@finalAuctionCost * 0.05", auctionDeposit: "@itemCost * 0.05", allowSecondaryCurrencies: true, enableMinimumBid: false, @@ -53,7 +55,9 @@ const CONSTANTS = { timeType: "realTime", minTimeLimit: "12hours", maxTimeLimit: "2days", - displayEntryItem: false, + entryItem: false, + displayEntryItem: false, + owner: false, }, AUCTION_TIME_LIMITS: { @@ -75,8 +79,8 @@ const CONSTANTS = { DEFAULTS: { /** * @typedef {Object} Auction - * @property {String} id - * @property {String} userId + * @property {String} id + * @property {String} userId * @property {String} uuid * @property {String} actorUuid * @property {Object} item @@ -92,7 +96,7 @@ const CONSTANTS = { */ AUCTION: { id: "", - userId: "", + userId: "", uuid: "AUCTIONID-AUCTIONEERUUID-USERUUID", actorUuid: "", itemData: {}, @@ -114,14 +118,14 @@ const CONSTANTS = { /** * @typedef {Object} Bid * @property {String} id - * @property {String} userId + * @property {String} userId * @property {String} auctionUuid * @property {Number} price * @property {Number} date */ BID: { id: "", - userId: "", + userId: "", auctionUuid: "", actorUuid: "", price: "", @@ -139,7 +143,7 @@ const CONSTANTS = { */ BUYOUT: { id: "", - userId: "", + userId: "", auctionUuid: "", actorUuid: "", price: "", @@ -195,12 +199,12 @@ CONSTANTS.AUCTIONEER_SETTINGS = { auctionFee: { title: "Auction Fee Formula", label: "This is the percentage of the total sell price that the auctioneer takes as a cut from any successful auction.", - type: Number, + type: String, value: CONSTANTS.ACTOR_DEFAULTS.auctionFee }, - auctionDeposit: { + auctionDeposit: { title: "Deposit Fee Formula", - label: "This is the formula to calculate the price that someone must pay to put up an auction.", + label: "This is the formula to calculate the price that someone must pay to put up an auction. This is returned to the creator of the auction if the auction is successful, but kept by the auctioneer if failed", type: String, value: CONSTANTS.ACTOR_DEFAULTS.auctionDeposit }, @@ -217,7 +221,7 @@ CONSTANTS.AUCTIONEER_SETTINGS = { type: Boolean, value: CONSTANTS.ACTOR_DEFAULTS.allowSecondaryCurrencies }, - visibleTimeLeft: { + visibleTimeLeft: { title: "Show full time left", label: "When enabled, instead of 'very long' or 'short' strings for auction duration, this will show an exact amount of time left before auctions expire.", type: Boolean, @@ -263,24 +267,30 @@ CONSTANTS.AUCTIONEER_SETTINGS = { options: CONSTANTS.AUCTION_TIME_LIMITS, value: CONSTANTS.ACTOR_DEFAULTS.maxTimeLimit }, - allowBankerVaults: { - title: "Allow banker vault impersonation", - label: "When enabled, this allows users to impersonate the vaults connected to bankers when interacting with the auctioneer. They can buy and sell items with it.", - type: Boolean, - value: CONSTANTS.ACTOR_DEFAULTS.allowBankerVaults - }, + allowBankerVaults: { + title: "Allow banker vault impersonation", + label: "When enabled, this allows users to impersonate the vaults connected to bankers when interacting with the auctioneer. They can buy and sell items with it.", + type: Boolean, + value: CONSTANTS.ACTOR_DEFAULTS.allowBankerVaults + }, entryItem: { title: "Entry Item", label: "This configures an item that the character must possess in their inventory in order to access the auction house.", type: Item, value: false }, - displayEntryItem: { - title: "Display Entry Item", - label: "This determines whether the auctioneer accepts secondary currencies for its auctions", - type: Boolean, - value: CONSTANTS.ACTOR_DEFAULTS.displayEntryItem - }, + displayEntryItem: { + title: "Display Entry Item", + label: "This determines whether the auctioneer accepts secondary currencies for its auctions", + type: Boolean, + value: CONSTANTS.ACTOR_DEFAULTS.displayEntryItem + }, + owner: { + title: "Owner", + label: "This configures whether a character owns this auctioneer or not. All auction fees and deposit fees are sent to this character's inventory when a GM logs on.", + type: Actor, + value: false + }, }; export default CONSTANTS; diff --git a/src/lib.js b/src/lib.js index e3cdb9c..f6b917e 100644 --- a/src/lib.js +++ b/src/lib.js @@ -1,102 +1,103 @@ import CONSTANTS from "~/constants.js"; import Auctioneer from "~/applications/auctioneer/auctioneer.js"; import moment from "moment"; +import { getAuctioneerActorData } from "~/applications/auctioneer/auctioneer-store.js"; export function auctioneerRendered(itemPile) { - const auctioneer = itemPile?.actor ?? itemPile; + const auctioneer = itemPile?.actor ?? itemPile; - const flags = auctioneer.getFlag("item-piles", 'data'); + const flags = auctioneer.getFlag("item-piles", 'data'); - if (flags?.type !== CONSTANTS.AUCTIONEER) return; + if (flags?.type !== CONSTANTS.AUCTIONEER) return; - Auctioneer.show({ auctioneer }); + Auctioneer.show({ auctioneer }); - return false; + return false; } export function getActiveApps(id, single = false) { - const apps = Object.values(ui.windows).filter(app => { - return app.id.startsWith(id) && app._state > Application.RENDER_STATES.CLOSED; - }); - if (single) { - return apps?.[0] ?? false; - } - return apps; + const apps = Object.values(ui.windows).filter(app => { + return app.id.startsWith(id) && app._state > Application.RENDER_STATES.CLOSED; + }); + if (single) { + return apps?.[0] ?? false; + } + return apps; } export function abbreviateNumbers(number, decPlaces = 2) { - // 2 decimal places => 100, 3 => 1000, etc - decPlaces = Math.pow(10, decPlaces) + // 2 decimal places => 100, 3 => 1000, etc + decPlaces = Math.pow(10, decPlaces) - // Enumerate number abbreviations - let abbrev = ['k', 'm', 'b', 't'] + // Enumerate number abbreviations + let abbrev = ['k', 'm', 'b', 't'] - // Go through the array backwards, so we do the largest first - for (let i = abbrev.length - 1; i >= 0; i--) { + // Go through the array backwards, so we do the largest first + for (let i = abbrev.length - 1; i >= 0; i--) { - // Convert array index to "1000", "1000000", etc - let size = Math.pow(10, (i + 1) * 3) + // Convert array index to "1000", "1000000", etc + let size = Math.pow(10, (i + 1) * 3) - // If the number is bigger or equal do the abbreviation - if (size <= number) { - // Here, we multiply by decPlaces, round, and then divide by decPlaces. - // This gives us nice rounding to a particular decimal place. - number = Math.round((number * decPlaces) / size) / decPlaces + // If the number is bigger or equal do the abbreviation + if (size <= number) { + // Here, we multiply by decPlaces, round, and then divide by decPlaces. + // This gives us nice rounding to a particular decimal place. + number = Math.round((number * decPlaces) / size) / decPlaces - // Handle special case where we round up to the next abbreviation - if (number === 1000 && i < abbrev.length - 1) { - number = 1 - i++ - } + // Handle special case where we round up to the next abbreviation + if (number === 1000 && i < abbrev.length - 1) { + number = 1 + i++ + } - // Add the letter for the abbreviation - number += abbrev[i] + // Add the letter for the abbreviation + number += abbrev[i] - // We are done... stop - break; - } - } + // We are done... stop + break; + } + } - return number + return number } const relativeDateStrings = [ - [0, "Auction Failed"], - [0.5, "Short"], - [2, "Medium"], - [12, "Long"], - [24, "Very Long"], + [0, "Auction Failed"], + [0.5, "Short"], + [2, "Medium"], + [12, "Long"], + [24, "Very Long"], ] export function dateNumberToRelativeString(auctioneer, date) { - const flags = getAuctioneerActorFlags(auctioneer); + const flags = getAuctioneerActorFlags(auctioneer); - const now = evaluateFoundryTime(auctioneer); + const now = evaluateFoundryTime(auctioneer); - if (flags.visibleTimeLeft) { - const value = (date - now); - return { - value: value, - label: value > 0 ? dateTimeToString(auctioneer, date, true) : "Auction Expired" - } - } + if (flags.visibleTimeLeft) { + const value = (date - now); + return { + value: value, + label: value > 0 ? dateTimeToString(auctioneer, date, true) : "Auction Expired" + } + } - const hours = (date - now) / 1000 / 60 / 60; + const hours = (date - now) / 1000 / 60 / 60; - for (const [value, label] of relativeDateStrings) { - if (hours <= value) return { value, label }; - } + for (const [value, label] of relativeDateStrings) { + if (hours <= value) return { value, label }; + } - return { - value: hours, - label: Math.floor(hours / 24) + " day" + (Math.floor(hours / 24) > 1 ? "s" : "") - }; + return { + value: hours, + label: Math.floor(hours / 24) + " day" + (Math.floor(hours / 24) > 1 ? "s" : "") + }; } @@ -107,66 +108,66 @@ export function dateNumberToRelativeString(auctioneer, date) { */ export function getPriceFromData(priceFlag, actor = false) { - if (!priceFlag) { - return { - valid: false, - canBuy: false, - currencies: [], - totalPrice: 0 - }; - } - - const paymentData = game.itempiles.API.getPaymentData(priceFlag, { target: actor }); - - const currencies = paymentData.finalPrices.reverse(); - - let primaryCurrency; - for (const currency of currencies) { - if (!currency.exchangeRate || currency.exchangeRate < 1) continue; - if (currency.exchangeRate === 1) { - primaryCurrency = currency; - } else if (primaryCurrency && primaryCurrency.quantity >= (currency.exchangeRate * 1000)) { - currency.quantity = Math.floor(primaryCurrency.quantity / currency.exchangeRate) - primaryCurrency.quantity -= Math.floor(primaryCurrency.quantity / currency.exchangeRate) * currency.exchangeRate; - } - } - - return { - ...paymentData, - valid: true, - currencies: currencies.reverse().filter(currency => currency.quantity), - totalPrice: paymentData.totalCurrencyCost + paymentData.finalPrices - .filter(currency => currency.secondary && currency.quantity) - .reduce((acc, currency) => { - return acc + currency.quantity; - }, 0) - }; + if (!priceFlag) { + return { + valid: false, + canBuy: false, + currencies: [], + totalPrice: 0 + }; + } + + const paymentData = game.itempiles.API.getPaymentData(priceFlag, { target: actor }); + + const currencies = paymentData.finalPrices.reverse(); + + let primaryCurrency; + for (const currency of currencies) { + if (!currency.exchangeRate || currency.exchangeRate < 1) continue; + if (currency.exchangeRate === 1) { + primaryCurrency = currency; + } else if (primaryCurrency && primaryCurrency.quantity >= (currency.exchangeRate * 1000)) { + currency.quantity = Math.floor(primaryCurrency.quantity / currency.exchangeRate) + primaryCurrency.quantity -= Math.floor(primaryCurrency.quantity / currency.exchangeRate) * currency.exchangeRate; + } + } + + return { + ...paymentData, + valid: true, + currencies: currencies.reverse().filter(currency => currency.quantity), + totalPrice: paymentData.totalCurrencyCost + paymentData.finalPrices + .filter(currency => currency.secondary && currency.quantity) + .reduce((acc, currency) => { + return acc + currency.quantity; + }, 0) + }; } export function getValidCurrenciesForPrice(currencies) { - const defaultIncomingCurrencies = currencies.filter(currency => !currency.secondary); + const defaultIncomingCurrencies = currencies.filter(currency => !currency.secondary); - const defaultCurrencies = defaultIncomingCurrencies.length > 0 - ? game.itempiles.API.CURRENCIES.map(currency => { - currency.quantity = 0; - currency.secondary = false; - return currency; - }) : []; + const defaultCurrencies = defaultIncomingCurrencies.length > 0 + ? game.itempiles.API.CURRENCIES.map(currency => { + currency.quantity = 0; + currency.secondary = false; + return currency; + }) : []; - const secondaryIncomingCurrencies = currencies.filter(currency => currency.secondary); - const secondaryCurrencies = game.itempiles.API.SECONDARY_CURRENCIES - .filter(currency => secondaryIncomingCurrencies.some(inCurrency => - (currency.data.uuid && inCurrency.data.uuid && currency.data.uuid === inCurrency.data.uuid) - || (currency.data.path && inCurrency.data.path && currency.data.path === inCurrency.data.path)) - ).map(currency => { - currency.quantity = 0; - currency.secondary = true; - return currency; - }); + const secondaryIncomingCurrencies = currencies.filter(currency => currency.secondary); + const secondaryCurrencies = game.itempiles.API.SECONDARY_CURRENCIES + .filter(currency => secondaryIncomingCurrencies.some(inCurrency => + (currency.data.uuid && inCurrency.data.uuid && currency.data.uuid === inCurrency.data.uuid) + || (currency.data.path && inCurrency.data.path && currency.data.path === inCurrency.data.path)) + ).map(currency => { + currency.quantity = 0; + currency.secondary = true; + return currency; + }); - return defaultCurrencies.concat(secondaryCurrencies) + return defaultCurrencies.concat(secondaryCurrencies) } @@ -175,410 +176,473 @@ export function getValidCurrenciesForPrice(currencies) { * @returns {ActorFlagDefaults} */ export function getAuctioneerActorFlags(actor) { - const defaults = foundry.utils.deepClone(CONSTANTS.ACTOR_DEFAULTS); - const actorFlags = foundry.utils.deepClone(actor ? actor.getFlag(CONSTANTS.ITEM_PILES_MODULE, "data") : {}); - return foundry.utils.mergeObject(defaults, actorFlags); + const defaults = foundry.utils.deepClone(CONSTANTS.ACTOR_DEFAULTS); + const actorFlags = foundry.utils.deepClone(actor ? actor.getFlag(CONSTANTS.ITEM_PILES_MODULE, "data") : {}); + return foundry.utils.mergeObject(defaults, actorFlags); } export function getCurrencies(actor) { - const flags = getAuctioneerActorFlags(actor); + const flags = getAuctioneerActorFlags(actor); - const defaultCurrencies = foundry.utils.deepClone(game.itempiles.API.CURRENCIES).map(currency => { - currency.quantity = 0; - currency.secondary = false; - return currency; - }); + const defaultCurrencies = foundry.utils.deepClone(game.itempiles.API.CURRENCIES).map(currency => { + currency.quantity = 0; + currency.secondary = false; + return currency; + }); - const secondaryCurrencies = flags.allowSecondaryCurrencies - ? foundry.utils.deepClone(game.itempiles.API.SECONDARY_CURRENCIES).map(currency => { - currency.quantity = 0; - currency.secondary = true; - return currency; - }) - : []; + const secondaryCurrencies = flags.allowSecondaryCurrencies + ? foundry.utils.deepClone(game.itempiles.API.SECONDARY_CURRENCIES).map(currency => { + currency.quantity = 0; + currency.secondary = true; + return currency; + }) + : []; - return defaultCurrencies.concat(secondaryCurrencies); + return defaultCurrencies.concat(secondaryCurrencies); } export function turnCurrenciesIntoString(currencies, abbreviate = false) { - return currencies.filter(currencies => currencies.quantity) - .reduce((acc, currency) => { - const quantity = abbreviate ? abbreviateNumbers(currency.quantity) + " " : currency.quantity; - return `${acc} ${currency.abbreviation.replace('{#}', quantity)}`; - }, "").trim(); + return currencies.filter(currencies => currencies.quantity) + .reduce((acc, currency) => { + const quantity = abbreviate ? abbreviateNumbers(currency.quantity) + " " : currency.quantity; + return `${acc} ${currency.abbreviation.replace('{#}', quantity)}`; + }, "").trim(); } export function isPriceHigherThan(priceDataA, priceDataB) { - if (priceDataA.primary) { - return priceDataA.totalPrice >= priceDataB.totalPrice; - } - const mixedPrices = priceDataB.currencies.map(currencyB => { - const currencyA = priceDataA.currencies.find(currencyA => currencyA.id === currencyB.id) ?? { quantity: -Infinity }; - return [currencyA, currencyB]; - }) - return mixedPrices.some(([currencyA, currencyB]) => currencyA.quantity >= currencyB.quantity); + if (priceDataA.primary) { + return priceDataA.totalPrice >= priceDataB.totalPrice; + } + const mixedPrices = priceDataB.currencies.map(currencyB => { + const currencyA = priceDataA.currencies.find(currencyA => currencyA.id === currencyB.id) ?? { quantity: -Infinity }; + return [currencyA, currencyB]; + }) + return mixedPrices.some(([currencyA, currencyB]) => currencyA.quantity >= currencyB.quantity); } const DATE_REGEX = new RegExp("^(\\d+)(\\w+)$", "g") export function evaluateFoundryTime(auctioneer, duration = "now") { - const flags = getAuctioneerActorFlags(auctioneer); - - if (flags.timeType === CONSTANTS.AUCTION_TIME_TYPE_KEYS.REAL_TIME || !window?.SimpleCalendar?.api) { - if (duration === "now") return Date.now(); - const parts = [...duration.matchAll(DATE_REGEX)]; - const [_, number, dateType] = parts[0]; - return moment().add(Number(number), dateType).valueOf(); - } - - const currentTimestamp = window?.SimpleCalendar.api.timestamp(); - if (duration === "now") return currentTimestamp; - - const parts = [...duration.matchAll(DATE_REGEX)]; - const [_, number, dateType] = parts[0]; - - const newDateType = { - "minutes": "minute", - "hours": "hour", - "days": "day", - "months": "month", - "years": "year", - }[dateType] ?? dateType; - - return window?.SimpleCalendar.api.timestampPlusInterval(currentTimestamp, { - [newDateType]: Number(number) - }); + const flags = getAuctioneerActorFlags(auctioneer); + + if (flags.timeType === CONSTANTS.AUCTION_TIME_TYPE_KEYS.REAL_TIME || !window?.SimpleCalendar?.api) { + if (duration === "now") return Date.now(); + const parts = [...duration.matchAll(DATE_REGEX)]; + const [_, number, dateType] = parts[0]; + return moment().add(Number(number), dateType).valueOf(); + } + + const currentTimestamp = window?.SimpleCalendar.api.timestamp(); + if (duration === "now") return currentTimestamp; + + const parts = [...duration.matchAll(DATE_REGEX)]; + const [_, number, dateType] = parts[0]; + + const newDateType = { + "minutes": "minute", + "hours": "hour", + "days": "day", + "months": "month", + "years": "year", + }[dateType] ?? dateType; + + return window?.SimpleCalendar.api.timestampPlusInterval(currentTimestamp, { + [newDateType]: Number(number) + }); } export function dateTimeToString(auctioneer, datetime, relative = false) { - const flags = getAuctioneerActorFlags(auctioneer); + const flags = getAuctioneerActorFlags(auctioneer); - if (flags.timeType === CONSTANTS.AUCTION_TIME_TYPE_KEYS.REAL_TIME || !window?.SimpleCalendar?.api) { - const diffDate = moment(datetime); - return relative ? capitalize(moment.duration(diffDate.diff(moment())).humanize()) : diffDate.format("Y-M-D - HH:mm:ss") - } + if (flags.timeType === CONSTANTS.AUCTION_TIME_TYPE_KEYS.REAL_TIME || !window?.SimpleCalendar?.api) { + const diffDate = moment(datetime); + return relative ? capitalize(moment.duration(diffDate.diff(moment())).humanize()) : diffDate.format("Y-M-D - HH:mm:ss") + } - if (relative) { - const currentTimestamp = window?.SimpleCalendar.api.timestamp(); - const interval = Object.entries(window?.SimpleCalendar.api.secondsToInterval(datetime - currentTimestamp)).filter(e => e[1]) - const biggestInterval = interval[interval.length - 1]; - let [label, value] = biggestInterval; - label = pluralize(label, value > 1); - return `${value} ${label}` - } + if (relative) { + const currentTimestamp = window?.SimpleCalendar.api.timestamp(); + const interval = Object.entries(window?.SimpleCalendar.api.secondsToInterval(datetime - currentTimestamp)).filter(e => e[1]) + const biggestInterval = interval[interval.length - 1]; + let [label, value] = biggestInterval; + label = pluralize(label, value > 1); + return `${value} ${label}` + } - const timestamp = window?.SimpleCalendar.api.timestampToDate(datetime); - return `${timestamp.display.year}-${timestamp.display.month}-${timestamp.display.day} - ${timestamp.display.time}`; + const timestamp = window?.SimpleCalendar.api.timestampToDate(datetime); + return `${timestamp.display.year}-${timestamp.display.month}-${timestamp.display.day} - ${timestamp.display.time}`; } function capitalize(str) { - return str.slice(0, 1).toUpperCase() + str.slice(1); + return str.slice(0, 1).toUpperCase() + str.slice(1); } function pluralize(str, doPluralize = true) { - if (doPluralize) { - return str.endsWith("s") ? str : str + "s"; - } - return str.endsWith("s") ? str.slice(0, -1) : str; + if (doPluralize) { + return str.endsWith("s") ? str : str + "s"; + } + return str.endsWith("s") ? str.slice(0, -1) : str; } export function getUserAuctions(user = false) { - if (!user) user = game.user; - return foundry.utils.deepClone(user.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.AUCTIONS_FLAG) ?? []); + if (!user) user = game.user; + return foundry.utils.deepClone(user.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.AUCTIONS_FLAG) ?? []); } export function getUserBids(user = false) { - if (!user) user = game.user; - return foundry.utils.deepClone(user.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.BIDS_FLAG) ?? []); + if (!user) user = game.user; + return foundry.utils.deepClone(user.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.BIDS_FLAG) ?? []); } export function getUserBuyouts(user = false) { - if (!user) user = game.user; - return foundry.utils.deepClone(user.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.BUYOUTS_FLAG) ?? []); + if (!user) user = game.user; + return foundry.utils.deepClone(user.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.BUYOUTS_FLAG) ?? []); } export function getItemColorElement(item) { - const color = game.modules.get("rarity-colors")?.active && game.modules.get("rarity-colors")?.api - ? game.modules.get("rarity-colors").api.getColorFromItem(item) - : false; - const rarity = capitalize(CONFIG?.DND5E?.itemRarity?.[item?.system?.rarity] ?? ""); - return color ? ` ` : ""; + const color = game.modules.get("rarity-colors")?.active && game.modules.get("rarity-colors")?.api + ? game.modules.get("rarity-colors").api.getColorFromItem(item) + : false; + const rarity = capitalize(CONFIG?.DND5E?.itemRarity?.[item?.system?.rarity] ?? ""); + return color ? ` ` : ""; } export function evaluateFormula(formula, data, warn = false) { - const rollFormula = Roll.replaceFormulaData(formula, data, { warn }); - return new Roll(rollFormula).evaluate({ async: false }); + const rollFormula = Roll.replaceFormulaData(formula, data, { warn }); + return new Roll(rollFormula).evaluate({ async: false }); } export function isActiveGM(user) { - return user.active && user.isGM; + return user.active && user.isGM; } export function getActiveGMs() { - return game.users.filter(isActiveGM); + return game.users.filter(isActiveGM); } export function isResponsibleGM() { - if (!game.user.isGM) { - return false; - } - return !getActiveGMs().some(other => other.id < game.user.id); + if (!game.user.isGM) { + return false; + } + return !getActiveGMs().some(other => other.id < game.user.id); } -function cleanUserFlags(acc, entry, flagPath){ - const userEntries = acc[entry.userId] ?? game.users.get(entry.userId).getFlag(CONSTANTS.MODULE_NAME, flagPath); - if (userEntries.length) { - const auctionIndex = userEntries.findIndex(userAuction => userAuction.uuid === entry.uuid); - userEntries.splice(auctionIndex, 1); - acc[entry.userId] = { - [`flags.${CONSTANTS.MODULE_NAME}.${flagPath}`]: userEntries - }; - } - return acc; +function cleanUserFlags(acc, entry, flagPath) { + const userEntries = acc[entry.userId] ?? game.users.get(entry.userId).getFlag(CONSTANTS.MODULE_NAME, flagPath); + if (userEntries.length) { + const auctionIndex = userEntries.findIndex(userAuction => userAuction.uuid === entry.uuid); + userEntries.splice(auctionIndex, 1); + acc[entry.userId] = { + [`flags.${CONSTANTS.MODULE_NAME}.${flagPath}`]: userEntries + }; + } + return acc; } -export function getLogs(auctioneer, { auctions = [], bids = [], buyouts = [] } = {}) { - - const claimedAuctions = auctions.filter(auction => auction.claimed).length > 50 - ? auctions.filter(auction => auction.claimed).slice(0, 25).map(auction => auction._source) - : [] - const claimedBids = bids.filter(bid => bid.claimed).length > 50 - ? bids.filter(bid => bid.claimed).slice(0, 25).map(bid => bid._source) - : []; - const claimedBuyouts = buyouts.filter(buyout => buyout.claimed).length > 50 - ? buyouts.filter(buyout => buyout.claimed).slice(0, 25).map(buyout => buyout._source) - : []; - - const maps = { - auctions: {}, - bids: {}, - buyouts: {} - }; - const auctioneerAuctionsData = auctioneer.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.AUCTIONS_FLAG) ?? []; - const auctioneerAuctions = auctioneerAuctionsData.map(source => { - const auction = makeAuction(auctioneer, source); - maps.auctions[auction.uuid] = auction; - return auction; - }); - - const auctioneerBidsData = auctioneer.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.BIDS_FLAG) ?? []; - const auctioneerBids = auctioneerBidsData.map(source => { - const bid = makeBid(auctioneer, source, maps.auctions); - maps.bids[bid.id] = bid; - return bid; - }); - - const auctioneerBuyoutsData = auctioneer.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.BUYOUTS_FLAG) ?? []; - const auctioneerBuyouts = auctioneerBuyoutsData.map(source => { - const buyout = makeBuyout(auctioneer, source, maps.auctions); - maps.buyouts[buyout.id] = buyout; - return buyout; - }); - - const actorUpdates = {}; - if(claimedAuctions.length) { - if(!actorUpdates["_id"]) actorUpdates["_id"] = auctioneer.id; - actorUpdates[CONSTANTS.AUCTIONS_FULL_FLAG] = auctioneerAuctionsData.concat(claimedAuctions) - } - if(claimedBids.length) { - if(!actorUpdates["_id"]) actorUpdates["_id"] = auctioneer.id; - actorUpdates[CONSTANTS.BIDS_FULL_FLAG] = auctioneerBidsData.concat(claimedBids) - } - if(claimedBuyouts.length) { - if(!actorUpdates["_id"]) actorUpdates["_id"] = auctioneer.id; - actorUpdates[CONSTANTS.BUYOUTS_FULL_FLAG] = auctioneerBuyoutsData.concat(claimedBuyouts) - } - - let userUpdates = {}; - const userAuctionUpdates = claimedAuctions.reduce((acc, auction) => { - return cleanUserFlags(acc, auction, CONSTANTS.AUCTIONS_FLAG); - }, {}); - if(!foundry.utils.isEmpty(userAuctionUpdates)) { - userUpdates = foundry.utils.mergeObject(userUpdates, userAuctionUpdates); - } - - const userBidUpdates = claimedBids.reduce((acc, bid) => { - return cleanUserFlags(acc, bid, CONSTANTS.BIDS_FLAG); - }, {}) - if(!foundry.utils.isEmpty(userBidUpdates)) { - userUpdates = foundry.utils.mergeObject(userUpdates, userBidUpdates); - } - - const userBuyoutUpdates = claimedBuyouts.reduce((acc, buyout) => { - return cleanUserFlags(acc, buyout, CONSTANTS.BUYOUTS_FLAG); - }, {}) - if(!foundry.utils.isEmpty(userBuyoutUpdates)) { - userUpdates = foundry.utils.mergeObject(userUpdates, userBuyoutUpdates); - } - - userUpdates = Object.entries(userUpdates).map(([userId, updates]) => { - return { - _id: userId, - ...updates - } - }) - - const currentDatetime = evaluateFoundryTime(auctioneer); - - const auctionLogsMap = auctions - .concat(auctioneerAuctions) - .reduce((acc, auction) => { - if (!acc[auction.id]) { - acc[auction.id] = { - data: auction, - type: "AuctionLog", - id: auction.id, - date: auction.date, - visible: true - }; - } - if (auction.expired && !auction.cancelled) { - if (!auction.won && !acc[auction.id + "-expired"]) { - acc[auction.id + "-expired"] = { - data: auction, - type: "ExpiredAuctionLog", - id: auction.id + "-expired", - date: auction.expiryDate, - visible: true - }; - } - if (auction.claimed && currentDatetime >= auction.claimedDate && !acc[auction.id + "-claimed"]) { - acc[auction.id + "-claimed"] = { - data: auction, - type: "ClaimedAuctionLog", - id: auction.id + "-claimed", - date: auction.claimedDate, - visible: true - }; - } - } - if (auction.cancelled && auction.claimed && currentDatetime >= auction.claimedDate && !acc[auction.id + "-cancelled"]) { - acc[auction.id + "-cancelled"] = { - data: auction, - type: "CancelledAuctionLog", - id: auction.id + "-cancelled", - date: auction.claimedDate, - visible: true - }; - } - return acc; - }, {}) - - const auctionLogs = Object.values(auctionLogsMap); - const bidLogs = Object.values(bids.concat(auctioneerBids).reduce((acc, bid) => { - if(acc[bid.id]) return acc; - acc[bid.id] = { - data: bid, - type: "BidLog", - id: bid.id, - date: bid.date, - visible: true - } - return acc; - }, {})); - const buyoutLogs = Object.values(buyouts.concat(auctioneerBuyouts).reduce((acc, buyout) => { - if(acc[buyout.id]) return acc; - acc[buyout.id] = { - data: buyout, - type: "BuyoutLog", - id: buyout.id, - date: buyout.date, - visible: true - } - return acc; - }, {})); - - return { - logs: auctionLogs - .concat(bidLogs) - .concat(buyoutLogs) - .map((entry, index) => ({ - index, ...entry - })) - .sort((a, b) => { - return b.date === a.date ? b.index - a.index : b.date - a.date; - }), - userUpdates, - actorUpdates - }; +export function getLogData(auctioneer, { auctions = [], bids = [], buyouts = [] } = {}) { + + const claimedAuctions = auctions.filter(auction => auction.claimed).map(auction => auction._source); + const claimedBids = bids.filter(bid => bid.claimed).map(bid => bid._source); + const claimedBuyouts = buyouts.filter(buyout => buyout.claimed).map(buyout => buyout._source); + + const maps = { + auctions: {}, + bids: {}, + buyouts: {} + }; + const auctioneerAuctionsData = auctioneer.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.AUCTIONS_FLAG) ?? []; + const auctioneerAuctions = auctioneerAuctionsData.map(source => { + const auction = makeAuction(auctioneer, source); + maps.auctions[auction.uuid] = auction; + return auction; + }); + + const auctioneerBidsData = auctioneer.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.BIDS_FLAG) ?? []; + const auctioneerBids = auctioneerBidsData.map(source => { + const bid = makeBid(auctioneer, source, maps.auctions); + maps.bids[bid.id] = bid; + return bid; + }); + + const auctioneerBuyoutsData = auctioneer.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.BUYOUTS_FLAG) ?? []; + const auctioneerBuyouts = auctioneerBuyoutsData.map(source => { + const buyout = makeBuyout(auctioneer, source, maps.auctions); + maps.buyouts[buyout.id] = buyout; + return buyout; + }); + + let recipientCurrencies = ""; + const flags = getAuctioneerActorFlags(auctioneer); + const actorUpdates = {}; + if (claimedAuctions.length) { + + if (!actorUpdates["_id"]) actorUpdates["_id"] = auctioneer.id; + actorUpdates[CONSTANTS.AUCTIONS_FULL_FLAG] = auctioneerAuctionsData.concat(claimedAuctions) + + for (const claimedAuction of claimedAuctions) { + if (!claimedAuction.won) { + recipientCurrencies = recipientCurrencies + ? game.itempiles.API.calculateCurrencies(recipientCurrencies, claimedAuction.depositPrice, false) + : claimedAuction.depositPrice; + } else if (flags.auctionFee) { + const auctionFee = Math.max(0, flags.auctionFee ?? 0); + const fee = game.itempiles.API.calculateCurrencies(price, auctionFee / 100); + recipientCurrencies = recipientCurrencies + ? game.itempiles.API.calculateCurrencies(recipientCurrencies, fee, false) + : fee; + } + } + } + if (claimedBids.length) { + if (!actorUpdates["_id"]) actorUpdates["_id"] = auctioneer.id; + actorUpdates[CONSTANTS.BIDS_FULL_FLAG] = auctioneerBidsData.concat(claimedBids) + } + if (claimedBuyouts.length) { + if (!actorUpdates["_id"]) actorUpdates["_id"] = auctioneer.id; + actorUpdates[CONSTANTS.BUYOUTS_FULL_FLAG] = auctioneerBuyoutsData.concat(claimedBuyouts) + } + + let userUpdates = {}; + const userAuctionUpdates = claimedAuctions.reduce((acc, auction) => { + return cleanUserFlags(acc, auction, CONSTANTS.AUCTIONS_FLAG); + }, {}); + if (!foundry.utils.isEmpty(userAuctionUpdates)) { + userUpdates = foundry.utils.mergeObject(userUpdates, userAuctionUpdates); + } + + const userBidUpdates = claimedBids.reduce((acc, bid) => { + return cleanUserFlags(acc, bid, CONSTANTS.BIDS_FLAG); + }, {}) + if (!foundry.utils.isEmpty(userBidUpdates)) { + userUpdates = foundry.utils.mergeObject(userUpdates, userBidUpdates); + } + + const userBuyoutUpdates = claimedBuyouts.reduce((acc, buyout) => { + return cleanUserFlags(acc, buyout, CONSTANTS.BUYOUTS_FLAG); + }, {}) + if (!foundry.utils.isEmpty(userBuyoutUpdates)) { + userUpdates = foundry.utils.mergeObject(userUpdates, userBuyoutUpdates); + } + + userUpdates = Object.entries(userUpdates).map(([userId, updates]) => { + return { + _id: userId, + ...updates + } + }) + + const currentDatetime = evaluateFoundryTime(auctioneer); + + const auctionLogsMap = auctions + .concat(auctioneerAuctions) + .reduce((acc, auction) => { + if (!acc[auction.id]) { + acc[auction.id] = { + data: auction, + type: "AuctionLog", + id: auction.id, + date: auction.date, + visible: true + }; + } + if (auction.expired && !auction.cancelled) { + if (!auction.won && !acc[auction.id + "-expired"]) { + acc[auction.id + "-expired"] = { + data: auction, + type: "ExpiredAuctionLog", + id: auction.id + "-expired", + date: auction.expiryDate, + visible: true + }; + } + if (auction.claimed && currentDatetime >= auction.claimedDate && !acc[auction.id + "-claimed"]) { + acc[auction.id + "-claimed"] = { + data: auction, + type: "ClaimedAuctionLog", + id: auction.id + "-claimed", + date: auction.claimedDate, + visible: true + }; + } + } + if (auction.cancelled && auction.claimed && currentDatetime >= auction.claimedDate && !acc[auction.id + "-cancelled"]) { + acc[auction.id + "-cancelled"] = { + data: auction, + type: "CancelledAuctionLog", + id: auction.id + "-cancelled", + date: auction.claimedDate, + visible: true + }; + } + return acc; + }, {}) + + const auctionLogs = Object.values(auctionLogsMap); + const bidLogs = Object.values(bids.concat(auctioneerBids).reduce((acc, bid) => { + if (acc[bid.id]) return acc; + acc[bid.id] = { + data: bid, + type: "BidLog", + id: bid.id, + date: bid.date, + visible: true + } + return acc; + }, {})); + const buyoutLogs = Object.values(buyouts.concat(auctioneerBuyouts).reduce((acc, buyout) => { + if (acc[buyout.id]) return acc; + acc[buyout.id] = { + data: buyout, + type: "BuyoutLog", + id: buyout.id, + date: buyout.date, + visible: true + } + return acc; + }, {})); + + return { + logs: auctionLogs + .concat(bidLogs) + .concat(buyoutLogs) + .map((entry, index) => ({ + index, ...entry + })) + .sort((a, b) => { + return b.date === a.date ? b.index - a.index : b.date - a.date; + }), + userUpdates, + actorUpdates, + recipientCurrencies + }; } export function makeAuction(auctioneer, source) { - const auction = {}; - auction._source = foundry.utils.mergeObject(foundry.utils.deepClone(CONSTANTS.DEFAULTS.AUCTION), source); - auction.type = "auction"; - auction.id = auction._source.id; - auction.uuid = auction._source.uuid; - auction.cancelled = auction._source.cancelled; - auction.claimed = auction._source.claimed; - auction.gmClaimed = auction._source.gmClaimed; - auction.item = new Item.implementation(auction._source.itemData) - auction.user = game.users.get(auction._source.userId); - auction.actor = auction._source.actorUuid ? fromUuidSync(auction._source.actorUuid) : false; - auction.date = auction._source.date; - auction.claimedDate = auction._source.claimedDate; - auction.expiryDate = auction._source.expiryDate; - auction.expired = evaluateFoundryTime(auctioneer) >= auction._source.expiryDate; - auction.timeLeft = dateNumberToRelativeString(auctioneer, auction._source.expiryDate); - auction.quantity = auction._source.quantity; - auction.bidVisibility = auction._source.bidVisibility; - auction.startPrice = auction._source.startPrice; - auction.buyoutPrice = auction._source.buyoutPrice; - auction.reservePrice = auction._source.reservePrice; - auction.minBidPrice = auction._source.minBidPrice; - auction.depositPrice = auction._source.depositPrice; - - auction.startPriceData = getPriceFromData(auction._source.startPrice); - auction.buyoutPriceData = getPriceFromData(auction._source.buyoutPrice); - auction.reservePriceData = getPriceFromData(auction._source.reservePrice); - auction.minBidPriceData = getPriceFromData(auction._source.minBidPrice); - auction.depositPriceData = getPriceFromData(auction._source.depositPrice); - - auction.won = false; - auction.bids = []; - auction.bidPriceData = false; - auction.highestOwnedBid = false; - return auction; + const auction = {}; + auction._source = foundry.utils.mergeObject(foundry.utils.deepClone(CONSTANTS.DEFAULTS.AUCTION), source); + auction.type = "auction"; + auction.id = auction._source.id; + auction.uuid = auction._source.uuid; + auction.cancelled = auction._source.cancelled; + auction.claimed = auction._source.claimed; + auction.gmClaimed = auction._source.gmClaimed; + auction.item = new Item.implementation(auction._source.itemData) + auction.user = game.users.get(auction._source.userId); + auction.actor = auction._source.actorUuid ? fromUuidSync(auction._source.actorUuid) : false; + auction.date = auction._source.date; + auction.claimedDate = auction._source.claimedDate; + auction.expiryDate = auction._source.expiryDate; + auction.expired = evaluateFoundryTime(auctioneer) >= auction._source.expiryDate; + auction.timeLeft = dateNumberToRelativeString(auctioneer, auction._source.expiryDate); + auction.quantity = auction._source.quantity; + auction.bidVisibility = auction._source.bidVisibility; + auction.startPrice = auction._source.startPrice; + auction.buyoutPrice = auction._source.buyoutPrice; + auction.reservePrice = auction._source.reservePrice; + auction.minBidPrice = auction._source.minBidPrice; + auction.depositPrice = auction._source.depositPrice; + + auction.startPriceData = getPriceFromData(auction._source.startPrice); + auction.buyoutPriceData = getPriceFromData(auction._source.buyoutPrice); + auction.reservePriceData = getPriceFromData(auction._source.reservePrice); + auction.minBidPriceData = getPriceFromData(auction._source.minBidPrice); + auction.depositPriceData = getPriceFromData(auction._source.depositPrice); + + auction.won = false; + auction.bids = []; + auction.bidPriceData = false; + auction.highestOwnedBid = false; + return auction; } export function makeBuyout(auctioneer, source, auctions) { - const buyout = {}; - buyout._source = foundry.utils.mergeObject(foundry.utils.deepClone(CONSTANTS.DEFAULTS.BUYOUT), source); - buyout.type = "buyout"; - buyout.id = buyout._source.id; - buyout.date = buyout._source.date; - buyout.user = game.users.get(buyout._source.userId); - buyout.actor = buyout._source.actorUuid ? fromUuidSync(buyout._source.actorUuid) : false; - buyout.priceData = getPriceFromData(buyout._source.price); - buyout.price = buyout._source.price; - buyout.claimed = buyout._source.claimed; - buyout.auctionUuid = buyout._source.auctionUuid; - buyout.auction = auctions[buyout._source.auctionUuid]; - buyout.auction.won = buyout; - return buyout; + const buyout = {}; + buyout._source = foundry.utils.mergeObject(foundry.utils.deepClone(CONSTANTS.DEFAULTS.BUYOUT), source); + buyout.type = "buyout"; + buyout.id = buyout._source.id; + buyout.date = buyout._source.date; + buyout.user = game.users.get(buyout._source.userId); + buyout.actor = buyout._source.actorUuid ? fromUuidSync(buyout._source.actorUuid) : false; + buyout.priceData = getPriceFromData(buyout._source.price); + buyout.price = buyout._source.price; + buyout.claimed = buyout._source.claimed; + buyout.auctionUuid = buyout._source.auctionUuid; + buyout.auction = auctions[buyout._source.auctionUuid]; + buyout.auction.won = buyout; + return buyout; } export function makeBid(auctioneer, source, auctions) { - const bid = {}; - bid._source = foundry.utils.mergeObject(foundry.utils.deepClone(CONSTANTS.DEFAULTS.BID), source); - bid.type = "bid"; - bid.id = bid._source.id; - bid.date = bid._source.date; - bid.user = game.users.get(bid._source.userId); - bid.actor = bid._source.actorUuid ? fromUuidSync(bid._source.actorUuid) : false; - bid.priceData = getPriceFromData(bid._source.price); - bid.price = bid._source.price; - bid.claimed = bid._source.claimed; - bid.bidStatus = { value: -Infinity, label: "Low Bid" }; - bid.auctionUuid = bid._source.auctionUuid; - bid.auction = auctions[bid._source.auctionUuid]; - bid.auction.bids.push(bid); - return bid; + const bid = {}; + bid._source = foundry.utils.mergeObject(foundry.utils.deepClone(CONSTANTS.DEFAULTS.BID), source); + bid.type = "bid"; + bid.id = bid._source.id; + bid.date = bid._source.date; + bid.user = game.users.get(bid._source.userId); + bid.actor = bid._source.actorUuid ? fromUuidSync(bid._source.actorUuid) : false; + bid.priceData = getPriceFromData(bid._source.price); + bid.price = bid._source.price; + bid.claimed = bid._source.claimed; + bid.bidStatus = { value: -Infinity, label: "Low Bid" }; + bid.auctionUuid = bid._source.auctionUuid; + bid.auction = auctions[bid._source.auctionUuid]; + bid.auction.bids.push(bid); + return bid; +} + +export async function migrateData(auctioneer = false) { + + if (!game.user.isGM) return; + + const auctioneersActors = auctioneer ? [auctioneer] : game.actors + .filter(actor => { + return actor.getFlag("item-piles", 'data')?.type === CONSTANTS.AUCTIONEER; + }) + + const auctioneers = auctioneersActors + .map(auctioneer => { + const auctioneerData = getAuctioneerActorData(auctioneer); + const flags = getAuctioneerActorFlags(auctioneer); + const { userUpdates, actorUpdates, recipientCurrencies } = getLogData(auctioneer, auctioneerData); + const recipient = flags.owner?.uuid ? fromUuidSync(flags.owner?.uuid) ?? auctioneer : auctioneer; + return { + id: auctioneer.id, + userUpdates, + actorUpdates, + recipientCurrencies, + recipient + } + }) + .filter(data => { + return !foundry.utils.isEmpty(data.actorUpdates) || data.userUpdates.length || data.recipientCurrencies; + }); + + if (!auctioneers.length) return; + + const actorUpdates = auctioneers.map(auctioneer => auctioneer.actorUpdates); + const userUpdates = auctioneers.map(auctioneer => auctioneer.userUpdates) + .deepFlatten() + .reduce((acc, update) => { + const foundIndex = acc.findIndex(pastUpdate => pastUpdate._id === update._id); + if (foundIndex > -1) { + acc[foundIndex] = foundry.utils.mergeObject(acc[foundIndex], update); + } else { + acc.push(update); + } + return acc; + }, []); + + await Actor.updateDocuments(actorUpdates) + await User.updateDocuments(userUpdates); + for (const auctioneer of auctioneers) { + if (!auctioneer.recipientCurrencies) continue; + await game.itempiles.API.addCurrencies(auctioneer.recipient, auctioneer.recipientCurrencies); + } + } diff --git a/src/module.js b/src/module.js index 8f94eab..e8cfe0f 100644 --- a/src/module.js +++ b/src/module.js @@ -1,48 +1,10 @@ import "./styles/styles.scss"; import CONSTANTS from "./constants.js"; import * as lib from "./lib.js"; -import { getAuctioneerActorData } from "~/applications/auctioneer/auctioneer-store.js"; Hooks.once("ready", () => { document.documentElement.style.setProperty("--item-piles-auctioneer-ui-height", `${CONSTANTS.AUCTION_UI_HEIGHT}px`); Hooks.on(game.itempiles.hooks.PRE_RENDER_INTERFACE, lib.auctioneerRendered); game.itempiles.API.registerItemPileType(CONSTANTS.AUCTIONEER, "Auctioneer", CONSTANTS.AUCTIONEER_SETTINGS); - migrateData(); + if (lib.isResponsibleGM()) lib.migrateData(); }); - -async function migrateData() { - if(!lib.isResponsibleGM()) return; - const auctioneers = game.actors - .filter(actor => { - return actor.getFlag("item-piles", 'data')?.type === CONSTANTS.AUCTIONEER; - }) - .map(auctioneer => { - const auctioneerData = getAuctioneerActorData(auctioneer); - const { userUpdates, actorUpdates } = lib.getLogs(auctioneer, auctioneerData); - return { - id: auctioneer.id, - userUpdates, - actorUpdates - } - }) - .filter(auctioneer => { - return !foundry.utils.isEmpty(auctioneer.actorUpdates) || auctioneer.userUpdates.length; - }); - if(!auctioneers.length) return; - - const actorUpdates = auctioneers.map(auctioneer => auctioneer.actorUpdates); - const userUpdates = auctioneers.map(auctioneer => auctioneer.userUpdates) - .deepFlatten() - .reduce((acc, update) => { - const foundIndex = acc.findIndex(pastUpdate => pastUpdate._id === update._id); - if(foundIndex > -1){ - acc[foundIndex] = foundry.utils.mergeObject(acc[foundIndex], update); - }else{ - acc.push(update); - } - return acc; - }, []); - - await Actor.updateDocuments(actorUpdates) - await User.updateDocuments(userUpdates); -} diff --git a/src/styles/styles.scss b/src/styles/styles.scss index e17ef58..18e10dc 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -10,15 +10,15 @@ display: grid; &.browse { - grid-template-columns: 555px 85px 110px 77px 150px; + grid-template-columns: 553px 85px 110px 79px 150px; } &.bids { - grid-template-columns: 401px 85px 150px 77px 110px 155px; + grid-template-columns: 399px 85px 150px 79px 110px 155px; } &.auctions { - grid-template-columns: 340px 85px 100px 77px 165px; + grid-template-columns: 338px 85px 100px 79px 165px; } &.wins { @@ -34,6 +34,13 @@ padding: 0.15rem; --tjs-app-overflow: visible; + &.sheet { + max-width: 1000px; + max-height: var(--item-piles-auctioneer-ui-height); + min-width: 1000px; + min-height: var(--item-piles-auctioneer-ui-height); + } + &.dialog .dialog-content { text-align: center; @@ -150,7 +157,7 @@ .item-row { align-items: center; - font-size: 0.8rem; + font-size: 0.875rem; height: 38px; cursor: pointer; border-bottom: 1px solid rgba(0, 0, 0, 0.05); @@ -199,7 +206,7 @@ .auction-entry-text { display: flex; justify-content: center; - font-size: 0.75rem; + font-size: 0.875rem; text-align: center; word-break: break-word; @@ -322,7 +329,7 @@ align-items: center; width: 100%; - &.small-warning{ + &.small-warning { font-size: 0.75rem; color: #640000; text-align: center; @@ -365,7 +372,7 @@ padding: 0.15rem; border: var(--item-piles-auctioneer-border); border-radius: var(--item-piles-auctioneer-border-radius); - font-size: 0.8rem; + font-size: 0.875rem; width: 100%; background-color: #cbc9bc; @@ -423,7 +430,7 @@ grid-template-columns: 1fr 1fr; .auction-title { - font-size: 0.8rem; + font-size: 0.875rem; i { margin-left: 0.25rem; @@ -504,7 +511,6 @@ } - .log-entry { display: flex;