Pular para o conteúdo principal

Shops

Menus de loja com preços fixos, dinâmicos ou de escambo, estoque, limites, impostos e três tipos de moeda.

Gratuitasv1.12Economia e criação
RequerTypeWriter0.9GUI0.17+VaultopcionalPlaceholderAPIopcionalDiscordopcional

Visão geral​

O Shops permite que os jogadores comprem e vendam itens por um menu de inventário que você monta no painel web do TypeWriter. Uma loja é uma única entry Shop Definition: ela guarda a lista de itens à venda, seus preços, a moeda, os limites, as mensagens e o layout do menu. O jogador a abre com /tw shop <id> ou por meio de uma ação Open Shop disparada por uma quest, um NPC ou um botão de outro menu.

Cada item tem o preço definido de uma de três formas: um preço fixo, um preço dinâmico que acompanha o estoque ou escambo, em que o item é pago com outros itens em vez de dinheiro. Compras e vendas passam pelo Vault, por uma economia do PlaceholderAPI ou por um saldo de pontos embutido.

O menu em si é desenhado com a extensão GUI. Você coloca marcadores em um layout onde os produtos aparecem (SHOP_ITEM) e botões marcados para paginação, fechamento e quantidades, e o Shops os preenche para cada jogador.

Recursos​

  • Preços fixos, dinâmicos e em itens — um preço fixo por unidade, um preço que sobe conforme o estoque cai ou uma lista de itens a entregar.
  • Escambo — priceMode ITEMS vende um artigo em troca de um ou vários itens, com um preço padrão para toda a loja e o token de lore {item_price}.
  • Três moedas — VAULT, PLACEHOLDER (saldo lido de um placeholder do PlaceholderAPI e alterado com comandos de console) e um saldo POINTS embutido.
  • Ações de clique e quantidades — compre ou venda 1, uma pilha, o máximo, tudo, uma quantidade personalizada a partir de um diálogo ou uma quantidade fixa como 10 em um submenu.
  • Estoque, limites e resets — estoque por item, limites por jogador e globais, tetos de compra e venda para toda a loja e resets por cron, intervalo, diário, semanal ou mensal.
  • Impostos e promoções — um imposto global, substituições de imposto por item e descontos percentuais com prazo.
  • Menu no painel — pool de layouts, submenu, item de preenchimento, lore de preço e formatos padrão de nome e lore.
  • Aba de um menu compartilhado — uma loja pode se tornar uma view de um chassi GUI maior por meio de baseMenuId.
  • Comandos de admin — liste, inspecione, abra, redefina, edite o estoque e leia o histórico de transações.
  • Anúncios no Discord — as transações são anunciadas como um evento que a extensão Discord repassa a um webhook.

Instalação​

  1. Instale a extensão GUI (versão 0.17 ou mais recente) e o TypeWriter em um servidor Paper ou Folia.
  2. Opcional, para a moeda VAULT: instale o Vault e um plugin de economia que se registre nele.
  3. Opcional, para placeholders em títulos e lore e para a moeda PLACEHOLDER: instale o PlaceholderAPI.
  4. Baixe o JAR do Shops nas releases do GitHub (o botão no painel de resumo acima), coloque-o em plugins/Typewriter/extensions/ e reinicie o servidor.
  5. No painel web, crie uma entry Shop Artifact e chame-a de shop_artifact. O Shops lê e grava seu estoque, limites e pontos por meio dessa entry.

Primeiros passos​

Monte uma pequena loja de armas paga com dinheiro do Vault:

  1. Em uma page static, adicione uma entry Shop Artifact chamada shop_artifact.
  2. Adicione uma entry Shop Definition. Defina title como <gold>Weapons, rows como 3 e currency como VAULT.
  3. Em items, adicione um item. Escolha uma espada de diamante em item, defina name como <aqua>Sword, priceMode como FIXED, fixedBuyPrice como 500 e fixedSellPrice como 100. Deixe primaryAction em BUY_1 e secondaryAction em SELL_1.
  4. Em layoutPool, adicione um layout simple com o id main e escreva main em mainLayoutId. Adicione a ele um item com buttonPrefix shop_button:, buttonType SHOP_ITEM, x 1, y 1, count 7 e direction right. Essa linha de marcadores é onde os produtos aparecem.
  5. Adicione mais três itens ao layout com buttonPrefix shop_button: e buttonType definido como PREV_PAGE, NEXT_PAGE e CLOSE, colocados na linha inferior. Dê a cada um um item e um displayName.
  6. Defina fillerItem como um painel de vidro colorido cinza para que os slots não usados sejam preenchidos.
  7. Publique e depois execute /tw shop admin list no jogo para ler o id da loja e abra-a com /tw shop <id>. Para abri-la a partir de uma quest ou de um NPC, adicione uma ação Open Shop apontando para a loja.

O clique esquerdo compra uma espada, o clique direito vende uma e shift + clique esquerdo abre o submenu de compra, se você configurou um.

Itens exibidos, itens vendidos

Uma loja mostra tantos produtos por página quantos slots de marcador SHOP_ITEM ela tiver, e os botões de paginação percorrem o restante. Se não existir nenhum marcador, o menu abre sem produto algum.

Como funciona​

Ao abrir, a loja verifica seus criteria de acesso, aplica um reset pendente e então monta o menu para aquele jogador. Um clique envia a ação ao tratador de transações, que verifica os limites, calcula o preço com promoções e imposto, cobra o pagamento, entrega o item e registra o estoque, os contadores de limite e o histórico.

Layout e botões​

Um menu de loja é um layout simple no layoutPool da Shop Definition, selecionado por mainLayoutId. Os itens do layout carregam um buttonPrefix de shop_button: e um buttonType:

buttonTypeFunção
SHOP_ITEMUm slot onde um produto é colocado. Repita-o com count, direction, gap e repeatY. O prefixo pode ficar vazio para este marcador.
NEXT_PAGE, PREV_PAGEPercorrem as páginas de produtos.
INFOEnvia as linhas de infoMessage.
CLOSEFecha o menu.
BACKVolta ao menu anterior, por exemplo a partir de um submenu.
BUY_1, SELL_1Compram ou vendem uma unidade. Em um submenu, BUY_1:10 e SELL_1:10 negociam 10 unidades; qualquer número funciona.
BUY_STACK, SELL_STACKNegociam uma pilha completa do item.
BUY_MAX, SELL_ALLCompram o máximo possível ou vendem tudo o que corresponde.
BUY_CUSTOM, SELL_CUSTOMPedem ao jogador uma quantidade em um diálogo.
BUY_SUBMENU, SELL_SUBMENUAbrem o submenu do item.

Os botões diferentes de SHOP_ITEM, NEXT_PAGE, PREV_PAGE, INFO, CLOSE e BACK agem sobre o item que o jogador selecionou, portanto pertencem ao layout do submenu. Defina sempre o buttonPrefix como shop_button:: o prefixo padrão pertence a outra extensão e o botão ficaria inerte.

Crie um segundo layout simple no pool, coloque nele os botões de quantidade e escreva o id dele em subMenuLayoutId. O título vem de subMenuTitle, em que {item_name} é o name do item. Os itens o abrem com as ações BUY_SUBMENU e SELL_SUBMENU, que são o shift + clique padrão.

Ações de clique​

Cada item tem três slots de ação:

CampoPadrãoClique padrão
primaryActionBUY_1Clique esquerdo
secondaryActionSELL_1Clique direito
shiftActionBUY_SUBMENUShift + clique esquerdo

As ações possíveis são NONE, BUY_1, SELL_1, BUY_STACK, SELL_STACK, BUY_MAX, SELL_ALL, BUY_SUBMENU, SELL_SUBMENU, BUY_CUSTOM e SELL_CUSTOM. O interactionConfig de toda a loja decide qual clique dispara cada ação (comprar 1, vender 1, comprar pilha, vender tudo, submenu, quantidade personalizada, navegação). Cada tipo de clique só pode executar uma ação por item, então, com a configuração padrão, várias ações mapeadas para o clique esquerdo se substituem entre si: altere interactionConfig se você usar mais de três ações.

buyMaxEnabled desativa BUY_MAX para um item.

Diálogo de quantidade​

BUY_CUSTOM e SELL_CUSTOM abrem um diálogo com um campo de texto. Personalize-o com buyAmountPrompt, sellAmountPrompt, amountConfirmButton, amountInputLabel, amountInputPlaceholder, amountInputWidth e amountInputMaxLength. A resposta deve ser um número inteiro positivo; caso contrário, invalidAmountMessage é enviada. As verificações normais de preço, limite e saldo são executadas quando a quantidade é confirmada.

Exibição dos itens​

As linhas de priceLore são acrescentadas a cada produto, depois da lore do próprio item e do seu lore. defaultNameFormat e defaultLoreFormat valem para itens sem name ou lore próprios. Nesses campos e no name e lore de um item, os tokens a seguir são substituídos; uma linha que pede um preço inexistente é descartada.

TokenSubstituído por
{item}O nome do próprio item.
{buy}, {sell}Preço unitário formatado. Descartado quando o item não pode ser comprado ou vendido, quando o preço é zero e em itens de escambo.
{item_price}O preço em itens, uma linha por item solicitado, como - 12 × Copper Ingot (texto definido por itemPriceLineFormat). Descartado em itens que não são pagos em itens.
{stock}, {stock_max}Estoque atual e máximo.
{currency}O currencySymbol.

priceFormat molda o preço exibido: {amount} é o número com duas casas decimais e {currency} é o símbolo.

Preços e economia​

Modos de preço. FIXED usa fixedBuyPrice e fixedSellPrice. DYNAMIC usa dynamicPricing: o preço começa em price (e sellPrice para vendas) quando o estoque está cheio e se move em direção a max conforme o estoque cai, em passos de step de stockMax, limitado por min e max e arredondado para rounding. ITEMS é o escambo.

Escambo. Com priceMode ITEMS, liste o custo no itemPrice do item, ou uma única vez para toda a loja em defaultItemPrice. Cada linha é um item e uma amount por unidade comprada. Várias linhas são todas exigidas ao mesmo tempo, e comprar várias unidades multiplica cada linha. A lista própria de um artigo substitui a lista da loja, nunca é somada a ela. Um item sem preço em lugar nenhum é recusado e registrado no log, nunca entregue de graça. O pagamento é verificado em todo o inventário antes de qualquer coisa ser removida, o espaço para o item comprado é verificado primeiro, e os itens de catálogo são comparados por identidade e não apenas por material. BUY_MAX compra tantas unidades quantas o inventário puder pagar. Um item de escambo não pode ser revendido por dinheiro. Os itens que faltam são informados com cannotAffordItemsMessage, em que {missing} os lista e {amount} é a quantidade.

Moedas.

currencyComportamento
VAULTUsa a economia registrada no Vault. Exige o plugin Vault e um provedor.
PLACEHOLDERLê o saldo de balancePlaceholder e o altera executando addCommand e removeCommand pelo console, onde {player} e {amount} são substituídos. Os três devem ser definidos. Números como 1,250.5 e 12.5k são compreendidos.
POINTSUm saldo mantido pelo Shops em seu artifact. Começa em 0 e cresce quando os jogadores vendem itens; a release não tem comando para conceder pontos.

Impostos. taxRate é uma porcentagem acrescentada às compras e subtraída das vendas. Com taxMode PER_ITEM, o taxRateOverride de um item (0 ou mais) o substitui para esse item; -1 mantém a taxa da loja.

Promoções. Uma Shop Promotion dá um desconto percentual no preço de compra e na recompensa de venda de uma loja, para todos os itens ou para os targetItemIndices listados (posição em items, a partir de 0).

Estoque, limites e resets​

  • dynamicPricing.stockMax define o estoque de um item. Ele começa cheio, diminui quando os jogadores compram e aumenta quando vendem. 0 significa sem controle de estoque. Com perPlayer ativado, cada jogador tem o seu próprio estoque de cada item; um reset reabastece o de todos. Enquanto uma loja está aberta, defaultRefreshTicks (e o refreshTicks de um item) fazem com que ela seja redesenhada quando o estoque exibido muda, de modo que se vejam as compras de outros jogadores. O guiSlot de um item o fixa em um slot específico, e sellAllEnabled desativa a ação de vender tudo. Detalhes em Shop Definition.
  • playerLimit e globalLimit limitam quantas unidades de um item um jogador, ou todos juntos, podem comprar por ciclo de reset. globalPlayerLimit, globalBuyLimit e globalSellLimit limitam a loja inteira; 0 significa ilimitado. limitReachedMessage é enviada quando um limite é atingido.
  • reset restaura o estoque e limpa os contadores de limite. As políticas são none, cron (uma expressão como 0 0 * * *), interval (segundos), daily, weekly (o dia 1 é segunda-feira) e monthly. Os horários usam o fuso horário do servidor. A primeira verificação de um ciclo acontece quando a loja é aberta, e uma verificação também é executada a cada minuto.
  • criteria restringe o acesso à loja e envia criteriaFailMessage quando não correspondem. Um item cujos próprios criteria não correspondem é exibido como lockedItem e não pode ser comprado.

Comandos​

Subcomandos de /typewriter, normalmente digitado como /tw.

ComandoDescrição
/tw shop <shop>Abre uma loja para você. Exige typewriter.shop.open.
/tw shop adminLista os comandos de admin. Exige typewriter.shop.admin.
/tw shop admin listLista as lojas carregadas com id, nome, número de itens e número de pools.
/tw shop admin info <shop>Mostra a definição de uma loja e seus itens com o índice.
/tw shop admin open <shop> [target]Abre uma loja para um jogador.
/tw shop admin refresh [target]Redesenha a loja aberta de um jogador.
/tw shop admin close [target]Fecha a loja aberta de um jogador.
/tw shop admin reset <shop>Redefine o estoque e os limites agora.
/tw shop admin stock <shop> <item> getMostra o estoque do item nesse índice.
/tw shop admin stock <shop> <item> set <amount>Define o estoque. Também add e remove.
/tw shop admin history [target]Mostra as últimas 10 transações de um jogador desde o último reinício.

<shop> é o entry id, completado com a tecla Tab. <item> é a posição do item em items da loja, começando em 0.

Textos editáveis​

Nenhum texto exibido no jogo está fixo no código. Três tipos de texto podem ser alterados sem recompilar nada.

O que um jogador lê ao negociar é um campo da Shop Definition: purchaseMessage, sellMessage, cannotAffordMessage, limitReachedMessage, priceLore e os demais descritos acima.

Os textos de preço, os ícones de tendência e as linhas de pool são campos da entrada a que pertencem. O padrão é o texto que a extensão sempre exibiu.

CampoEntradaPadrãoUso
trendUpIconShop Definition<red>▲</red>O placeholder trend_icon de um item cujo preço subiu desde a última compra ou venda.
trendDownIconShop Definition<green>▼</green>O mesmo placeholder quando o preço caiu.
trendStableIconShop Definition<gray>●</gray>O mesmo placeholder quando o preço não mudou, ou quando nenhum preço anterior é conhecido.
itemPriceLineFormatShop Definition- {amount} × {name}Uma linha do token de lore {item_price}, para cada item que um preço em itens pede. {amount} é a quantidade e {name} o nome do item.
unknownItemNameShop Definitionunknown itemO nome de um item de preço cujo nome próprio não pode ser lido, em {item_price} e na lista de itens faltantes.
missingItemFormatShop Definition{amount} × {name}Um elemento da lista {missing} de cannotAffordItemsMessage. {amount} é a quantidade que falta.
missingItemSeparatorShop Definition, O que fica entre dois elementos da lista {missing}.
defaultItemNameShop DefinitionItemO {item_name} de subMenuTitle para um item sem name.
noPriceTextShop Definition—Exibido no lugar de um número onde um preço em itens não tem um: os placeholders buy_price e sell_price, e {price} na mensagem de inventário cheio de um item assim.
cooldownFormatShop Definition{d}d:{h}h:{m}m:{s}sO padrão do placeholder cooldown quando o placeholder não informa nenhum. {d} dias, {h}, {m}, {s} preenchidos com 2 dígitos.
limitedLoreShop Pool<yellow>Limited: <red>{limit}A última linha de lore de um item limitado do pool. {limit} é a quantidade disponível.
unlimitedLoreShop Pool<yellow>UnlimitedA última linha de lore de um item do pool sem limite.

Use %typewriter_shop:<shop>:<n>:trend_icon% no lore de um item para exibir o ícone. %typewriter_shop:<shop>:<n>:trend% retorna up, down, stable ou unknown, e essas quatro palavras são fixas porque as condições as comparam.

O que os comandos /tw shop respondem, os comandos /tw shop admin incluídos, é um snippet do TypeWriter. Abra plugins/Typewriter/snippets.yml, encontre a chave em content.shop, altere o valor e execute /tw reload. A extensão registra todos os seus snippets com o texto padrão ao iniciar, então eles aparecem no arquivo após a primeira inicialização. Mantenha os padrões e nada muda.

As respostas são MiniMessage e são enviadas sem o prefixo do Typewriter, então a cor de uma resposta faz parte do seu texto. Um {name} em um texto é substituído pelo valor indicado na tabela; um placeholder que não existe permanece como escrito, e um valor nunca é examinado de novo em busca de placeholders. Um valor vindo das suas páginas (o nome de uma loja, o nome de um jogador, o título de um inventário) é exibido como escrito: suas tags nunca são interpretadas. O log do console do servidor continua em inglês.

Snippet (em content.shop.)PadrãoPlaceholders
help<gold>Shop commands:</gold> <yellow>/tw shop <id></yellow> to open a shop, <yellow>/tw shop admin</yellow> for operator tools.nenhum
unknown_shop_errorUnknown shop: {shop} (texto simples){shop} (o que foi digitado)
admin.helpa lista de comandos de administração, um por linhanenhum
admin.unknown_shop<red>Unknown shop: {shop}{shop}
admin.list_empty<yellow>No shop definitions are currently loaded.nenhum
admin.list_header<gold>Loaded shops ({count}):{count}
admin.list_line<white>- {id} | {name} | items={items} | pools={pools}{id}, {name}, {items}, {pools}
admin.opened<green>Opened {shop} for {player}.{shop}, {player}
admin.refreshed<green>Refreshed the shop session for {player}.{player}
admin.closed<green>Closed the shop session for {player}.{player}
admin.reset<green>Reset runtime stock and limits for {shop}.{shop}
admin.history_header<gold>Recent transactions for {player} ({count}):{player}, {count}
admin.history_empty<yellow>No transactions recorded in the current runtime.nenhum
admin.history_line<white>{time} | {type} | {shop}[{index}] | amount={amount} | price={price}{time}, {type}, {shop}, {index}, {amount}, {price}
admin.info_title<gold>=== Shop {shop} ==={shop}
admin.info_name<white>Name: {name}{name}
admin.info_inventory_title<white>Title: {title}{title}
admin.info_currency<white>Currency: {currency} | rows={rows}{currency}, {rows}
admin.info_items<white>Items: {items} direct | pools={pools} | access criteria={criteria}{items}, {pools}, {criteria}
admin.info_reset<white>Reset policy: {policy}{policy}
admin.info_item<white>[{index}] {name} | buy={buy} sell={sell} | playerLimit={playerLimit} globalLimit={globalLimit} | item criteria={criteria}{index}, {name}, {buy}, {sell}, {playerLimit}, {globalLimit}, {criteria}
admin.info_item_unnamed<unnamed> (texto simples)nenhum; é o {name} de um item sem nome
admin.stock_unlimited<yellow>{shop}[{index}] uses unlimited stock.{shop}, {index}
admin.stock_value<green>{shop}[{index}] stock: {stock}/{max}.{shop}, {index}, {stock}, {max}
admin.stock_out_of_range<red>Stock must stay between 0 and {max}.{max}
admin.stock_updated<green>{shop}[{index}] stock set to {stock}/{max}.{shop}, {index}, {stock}, {max}
admin.unknown_item<red>Unknown direct item index {index} in {shop}.{index}, {shop}

unknown_shop_error é um erro de comando, exibido em vermelho pelo jogo enquanto o jogador digita: é texto simples, sem MiniMessage.

Permissões​

PermissãoConcede
typewriter.shop.open/tw shop <shop>. Concedida a todos por padrão.
typewriter.shop.adminTodos os comandos /tw shop admin. Apenas operadores por padrão.

Placeholders​

Placeholders do PlaceholderAPI, com a expansão typewriter. <shop> é o nome ou id da entry da loja e <n> a posição do item em items, a partir de 0.

PlaceholderRetorna
%typewriter_shop:<shop>:name%Nome da entry.
%typewriter_shop:<shop>:entryid%Id da entry.
%typewriter_shop:<shop>:cooldown%Tempo até o próximo reset, por padrão no formato 0d:00h:00m:00s (o campo cooldownFormat da loja; versões anteriores usavam j para dias). Adicione um formato depois de um sublinhado, por exemplo cooldown_{h}h{m}m, usando {d}, {h}, {m} e {s}.
%typewriter_shop:<shop>:<n>:buy_price%Preço de compra do item; o noPriceText da loja (— por padrão) nos itens de troca.
%typewriter_shop:<shop>:<n>:sell_price%Preço de venda do item; o noPriceText da loja (— por padrão) nos itens de troca.
%typewriter_shop:<shop>:<n>:stock%Estoque atual.
%typewriter_shop:<shop>:<n>:stock_max%Estoque máximo.
%typewriter_shop:<shop>:<n>:has_promotion%true quando uma promoção se aplica ao item.

Integração​

  • GUI — o Shops abre seus menus pelo serviço de sessões do GUI, então gui:back, sons, chassis estendidos e baseMenuId se comportam como em qualquer outro menu. Defina baseMenuId como um shell Open GUI compartilhado, adicione views e a loja se torna uma aba: contentX, contentY, contentColumns e contentRows posicionam os produtos dentro do shell.
  • Discord — após cada compra ou venda, o Shops dispara um ShopTransactionEvent. Quando a extensão Discord está instalada e a loja define notificationWebhookId, uma entry Shop Notification formata o anúncio.
  • Sons — buySound, sellSound e deniedSound tocam no resultado correspondente.

Entries​

6 entradas, geradas a partir do código-fonte da extensão.

Ações1
Artefatos1
Manifestos4

Solução de problemas​

O menu abre, mas não mostra produto. O layout não tem marcador SHOP_ITEM, mainLayoutId não corresponde a nenhum id de layout em layoutPool, ou o layout não é um layout simple. Os produtos só aparecem nos slots de marcador.

Um botão não faz nada. Seu buttonPrefix não é shop_button:, ou seu buttonType está escrito errado (os tipos são em maiúsculas). Um botão de paginação ou de fechar também precisa do prefixo; só SHOP_ITEM tolera um prefixo vazio.

A loja diz "No economy available". Com VAULT, o Vault ou seu provedor de economia está ausente. Com PLACEHOLDER, um entre balancePlaceholder, addCommand ou removeCommand está vazio. Em um item de escambo, a mensagem também aparece quando nem itemPrice nem defaultItemPrice estão definidos, e o console registra o id da loja e o número do item.

Estoque e limites se perdem ao reiniciar, ou nunca são salvos. Deve existir uma entry Shop Artifact cujo id ou nome seja shop_artifact.

Um item não pode ser vendido. O preço de venda dele é zero (os itens de escambo sempre são), sellEnabled está desativado ou o jogador não possui itens correspondentes suficientes. Só contam itens com tipo e dados de item idênticos.

O console avisa que um artigo não pode ser exibido ou foi movido. Um item com guiSlot ocupa um marcador SHOP_ITEM. Quando os itens fixados ocupam todos os marcadores, os demais itens são colocados nas células livres da área de conteúdo; se não houver nenhuma célula livre, o item não é exibido. Adicione marcadores SHOP_ITEM ou libere um slot.

Itens de itemPools aparecem, mas não podem ser comprados. Nesta release, apenas as entradas de items são tratadas pelas ações de compra e venda; os itens dos pools são exibidos depois delas.

Os limites nunca são redefinidos. reset é none. Escolha uma política ou use /tw shop admin reset <shop>.

/tw shop <id> diz que a loja é desconhecida. O comando recebe o entry id, não o título. Use /tw shop admin list.

Extensões relacionadas​

  • GUI — layouts, sessões e o mecanismo de marcadores usado pelos menus de loja.
  • Discord — anuncia as transações da loja em um webhook.
  • Quest Codex — usa a mesma ideia de chassi para os menus de quests.