-
Notifications
You must be signed in to change notification settings - Fork 13
[feature] inventory system v1 #192
Description
He4rt Developers — Sistema de Inventário e Equipamento (V1)
Visão geral
Este documento descreve a modelagem e regras de negócio do sistema de itens, inventário, equipamento, loja e trade P2P para a plataforma He4rt Developers. O sistema é puramente cosmético na V1 — itens não possuem efeitos mecânicos (boosts de XP, multiplicadores, etc.) mas a modelagem está preparada para suportá-los no futuro.
Decisões de design
| Decisão | Escolha V1 |
|---|---|
| Natureza dos itens | Cosméticos (visual do avatar) |
| Durabilidade | Permanente (sem desgaste) |
| Obtenção | Drop por atividade, compra com coins, reward de season |
| Trade | Direto entre 2 usuários (P2P) |
| Classes de personagem | Referência futura (ver seção final) |
| Boosts mecânicos | Modelado mas desativado na V1 |
| Consumíveis | Roadmap futuro |
Slots de equipamento
O avatar é composto por slots de base (aparência do personagem) e equipamento (itens cosméticos equipáveis).
Slots base (aparência)
| Slot | Descrição |
|---|---|
skin |
Cor/tipo de pele |
gender |
Tipo corporal |
hair |
Estilo de cabelo |
hair_color |
Cor do cabelo |
face |
Formato/expressão facial |
Slots de equipamento
| Slot | Descrição |
|---|---|
cap |
Chapéu/boné/capacete |
mask |
Máscara facial |
eyes |
Óculos/acessório ocular |
ears |
Brincos/acessório auricular |
coat |
Roupa superior/casaco |
pants |
Calça/roupa inferior |
shoes |
Sapatos/botas |
glove |
Luvas |
cape |
Capa |
shield |
Escudo |
weapon |
Arma |
Cada item possui seu próprio asset gráfico que é renderizado na composição do avatar.
Modelo de dados
Tabelas novas
item_slots
Define os slots disponíveis para equipamento.
CREATE TABLE public.item_slots (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES public.tenants ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
slot_type VARCHAR(50) NOT NULL DEFAULT 'equipment',
display_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP(0),
updated_at TIMESTAMP(0),
CONSTRAINT item_slots_tenant_slug_unique UNIQUE (tenant_id, slug)
);
COMMENT ON COLUMN public.item_slots.slot_type IS 'base (aparência) ou equipment (equipável)';item_rarities
Tiers de raridade com peso para cálculo de drop.
CREATE TABLE public.item_rarities (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES public.tenants ON DELETE SET NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
color VARCHAR(7) NOT NULL,
drop_weight INTEGER NOT NULL DEFAULT 100,
created_at TIMESTAMP(0),
updated_at TIMESTAMP(0),
CONSTRAINT item_rarities_tenant_slug_unique UNIQUE (tenant_id, slug)
);
COMMENT ON COLUMN public.item_rarities.drop_weight IS 'Peso relativo para cálculo de drop. Maior = mais comum.';
COMMENT ON COLUMN public.item_rarities.color IS 'Cor hex para exibição (ex: #FFFFFF)';Seed data sugerido:
| Nome | Slug | Cor | Drop Weight |
|---|---|---|---|
| Comum | common |
#B0B0B0 |
1000 |
| Incomum | uncommon |
#4CAF50 |
500 |
| Raro | rare |
#2196F3 |
200 |
| Épico | epic |
#9C27B0 |
50 |
| Lendário | legendary |
#FF9800 |
10 |
items
Catálogo de itens — definição do item, não instância.
CREATE TABLE public.items (
id UUID PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES public.tenants ON DELETE SET NULL,
slot_id BIGINT NOT NULL REFERENCES public.item_slots,
rarity_id BIGINT NOT NULL REFERENCES public.item_rarities,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
description TEXT,
is_tradeable BOOLEAN NOT NULL DEFAULT TRUE,
is_purchasable BOOLEAN NOT NULL DEFAULT FALSE,
price INTEGER,
drop_rate DECIMAL(5, 4),
level_required INTEGER NOT NULL DEFAULT 0,
active BOOLEAN NOT NULL DEFAULT TRUE,
metadata JSONB,
created_at TIMESTAMP(0),
updated_at TIMESTAMP(0),
CONSTRAINT items_tenant_slug_unique UNIQUE (tenant_id, slug)
);
CREATE INDEX idx_items_slot ON public.items (slot_id);
CREATE INDEX idx_items_rarity ON public.items (rarity_id);
CREATE INDEX idx_items_active ON public.items (tenant_id, active);
COMMENT ON COLUMN public.items.price IS 'Preço em coins para compra direta (se is_purchasable)';
COMMENT ON COLUMN public.items.drop_rate IS 'Probabilidade de drop (0.0000 a 1.0000)';
COMMENT ON COLUMN public.items.level_required IS 'Nível mínimo do character para usar o item';
COMMENT ON COLUMN public.items.metadata IS 'Dados extensíveis: asset_url, tags, efeitos futuros';Estrutura sugerida do metadata JSONB:
{
"asset_url": "/items/cap/pirate-hat.png",
"asset_layer_order": 5,
"tags": ["pirate", "event-halloween-2026"],
"effects": []
}O campo effects fica vazio na V1 mas está preparado para boosts futuros.
character_items
Inventário — registro de posse de um item por um character.
CREATE TABLE public.character_items (
id UUID PRIMARY KEY,
character_id UUID NOT NULL REFERENCES public.characters ON DELETE CASCADE,
item_id UUID NOT NULL REFERENCES public.items,
tenant_id BIGINT NOT NULL REFERENCES public.tenants ON DELETE SET NULL,
acquired_via VARCHAR(50) NOT NULL,
acquired_at TIMESTAMP(0) NOT NULL,
created_at TIMESTAMP(0),
updated_at TIMESTAMP(0),
CONSTRAINT character_items_unique UNIQUE (character_id, item_id)
);
CREATE INDEX idx_character_items_character ON public.character_items (character_id);
CREATE INDEX idx_character_items_item ON public.character_items (item_id);
COMMENT ON COLUMN public.character_items.acquired_via IS 'drop, purchase, trade, reward';Nota: A constraint UNIQUE(character_id, item_id) significa que um character não pode ter duplicatas do mesmo item. Cada item é único no inventário. Se no futuro quiser permitir múltiplas cópias, remover essa constraint e adicionar um campo
quantity.
character_equipment
Itens atualmente equipados por slot.
CREATE TABLE public.character_equipment (
id UUID PRIMARY KEY,
character_id UUID NOT NULL REFERENCES public.characters ON DELETE CASCADE,
slot_id BIGINT NOT NULL REFERENCES public.item_slots,
character_item_id UUID NOT NULL REFERENCES public.character_items ON DELETE CASCADE,
tenant_id BIGINT NOT NULL REFERENCES public.tenants ON DELETE SET NULL,
equipped_at TIMESTAMP(0) NOT NULL,
created_at TIMESTAMP(0),
updated_at TIMESTAMP(0),
CONSTRAINT character_equipment_slot_unique UNIQUE (character_id, slot_id)
);
CREATE INDEX idx_character_equipment_character ON public.character_equipment (character_id);
COMMENT ON CONSTRAINT character_equipment_slot_unique ON public.character_equipment IS 'Um item por slot por character';shop_listings
Itens disponíveis para compra na loja.
CREATE TABLE public.shop_listings (
id UUID PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES public.tenants ON DELETE SET NULL,
item_id UUID NOT NULL REFERENCES public.items,
price INTEGER NOT NULL,
stock INTEGER,
available_from TIMESTAMP(0),
available_until TIMESTAMP(0),
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP(0),
updated_at TIMESTAMP(0)
);
CREATE INDEX idx_shop_listings_active ON public.shop_listings (tenant_id, active);
CREATE INDEX idx_shop_listings_item ON public.shop_listings (item_id);
COMMENT ON COLUMN public.shop_listings.stock IS 'NULL = estoque ilimitado';
COMMENT ON COLUMN public.shop_listings.price IS 'Preço em coins (pode sobrescrever o preço do item)';trades
Registro de trade P2P entre dois characters.
CREATE TABLE public.trades (
id UUID PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES public.tenants ON DELETE SET NULL,
initiator_character_id UUID NOT NULL REFERENCES public.characters ON DELETE CASCADE,
receiver_character_id UUID NOT NULL REFERENCES public.characters ON DELETE CASCADE,
status VARCHAR(50) NOT NULL DEFAULT 'pending',
resolved_at TIMESTAMP(0),
created_at TIMESTAMP(0),
updated_at TIMESTAMP(0)
);
CREATE INDEX idx_trades_initiator ON public.trades (initiator_character_id, status);
CREATE INDEX idx_trades_receiver ON public.trades (receiver_character_id, status);
COMMENT ON COLUMN public.trades.status IS 'pending, accepted, rejected, cancelled';trade_items
Itens envolvidos em um trade.
CREATE TABLE public.trade_items (
id UUID PRIMARY KEY,
trade_id UUID NOT NULL REFERENCES public.trades ON DELETE CASCADE,
character_item_id UUID NOT NULL REFERENCES public.character_items,
direction VARCHAR(20) NOT NULL,
created_at TIMESTAMP(0)
);
CREATE INDEX idx_trade_items_trade ON public.trade_items (trade_id);
COMMENT ON COLUMN public.trade_items.direction IS 'offer (A→B) ou request (B→A)';Regras de negócio
Inventário
- Um character possui no máximo 1 unidade de cada item (UNIQUE constraint em
character_items). - O campo
acquired_viarastreia a origem:drop,purchase,trade,reward. - Itens permanecem no inventário indefinidamente (sem durabilidade/expiração na V1).
Equipamento
- Apenas 1 item por slot pode estar equipado (UNIQUE constraint em
character_equipment). - Para equipar, o item deve estar no inventário (
character_items) do character. - O character deve atender ao
level_requireddo item. - Desequipar remove o registro de
character_equipmentmas mantém emcharacter_items. - Um item equipado não pode ser tradado — deve ser desequipado primeiro.
Loja
shop_listingspode sobrescrever opricedoitems(preços promocionais).stock = NULLsignifica estoque ilimitado.available_fromeavailable_untilpermitem itens sazonais/evento.- Compra debita coins da
walletsviatransactions(sistema existente). - Se
stock> 0, decrementar atomicamente. Sestock= 0, compra bloqueada.
Drop
- Ao processar uma
interaction(mensagem, voice, meeting), rolar drop chance. - Algoritmo de drop:
- Verificar se a interaction é elegível para drop (cooldown, tipo).
- Rolar probabilidade geral de drop (ex: 5% por mensagem qualificada).
- Se drop ativado, selecionar item usando weighted random baseado em
item_rarities.drop_weight. - Verificar se o character já possui o item. Se sim, re-rolar ou dar coins como compensação.
- Criar
character_itemscomacquired_via = 'drop'.
- Itens com
drop_rateespecífico usam esse valor em vez do sistema geral de raridade.
Trade P2P
- Player A cria trade selecionando itens do próprio inventário para oferecer.
- Validações ao criar:
- Item deve ter
is_tradeable = true. - Item não pode estar equipado.
- Item não pode estar em outro trade pendente.
- Item deve ter
- Player B recebe notificação (via
notificationsexistente). - Player B pode: aceitar, rejeitar.
- Player A pode: cancelar (enquanto pendente).
- Ao aceitar:
- Dentro de uma DB transaction:
- Revalidar que todos os itens ainda são válidos.
- Atualizar
character_items.character_iddos itens de A → B. - Atualizar
character_items.acquired_viapara'trade'. - Atualizar
character_items.acquired_atpara o momento da troca. - Atualizar
trades.statuspara'accepted'. - Atualizar
trades.resolved_at.
- Dentro de uma DB transaction:
- Trades com mais de 24h pendentes podem ser auto-cancelados (configurável).
Reward de season
- Ao finalizar uma season, itens exclusivos podem ser distribuídos baseado no ranking.
- Criar
character_itemscomacquired_via = 'reward'. - Itens de reward podem ter
is_tradeable = false(exclusividade).
Avatar rendering
O avatar é renderizado compondo layers na seguinte ordem (de trás pra frente):
cape(capa — atrás do corpo)skin(base corporal)pants(calça)shoes(sapatos)coat(roupa superior)glove(luvas)shield(escudo — mão esquerda)weapon(arma — mão direita)face(rosto)eyes(óculos/acessório)ears(brincos)hair(cabelo, comhair_coloraplicado)mask(máscara)cap(chapéu — por cima de tudo)
O campo metadata.asset_layer_order no item pode sobrescrever a ordem padrão se necessário.
Cada asset é uma imagem PNG com transparência, dimensões padronizadas (ex: 256x256 ou 512x512), registrada em items.metadata.asset_url.
Integração com sistema existente
| Sistema existente | Integração |
|---|---|
characters |
FK em character_items e character_equipment |
wallets / transactions |
Débito de coins na compra via loja |
interactions |
Trigger de drop ao processar interaction |
seasons_rankings |
Distribuição de itens reward ao encerrar season |
notifications |
Notificação ao receber proposta de trade |
media |
Assets dos itens podem usar o sistema de media existente |
tenants |
Todas as novas tabelas possuem tenant_id |
Roadmap (pós-V1)
| Feature | Descrição | Prioridade |
|---|---|---|
| Boosts mecânicos | metadata.effects com XP multiplier, coin bonus, etc. |
Alta |
| Consumíveis | Itens de uso único (XP potion, name change token) | Média |
| Durabilidade | Campo durability e desgaste por uso/tempo |
Baixa |
| Crafting | Combinar itens para criar novos | Baixa |
| Marketplace | Loja P2P com listagem e busca | Média |
| Sets de itens | Bônus por equipar conjunto completo | Média |
| Loot boxes | Pacotes com itens aleatórios (compráveis) | Baixa |
Referência futura: Sistema de classes
O brainstorming mencionou classes de personagem que restringem o uso de itens. Esta feature não faz parte da V1 mas a modelagem a suporta:
Conceito: Cada character teria uma class (ex: Warrior, Mage, Rogue, Healer) que determina quais itens pode equipar.
Impacto na modelagem:
- Adicionar tabela
character_classes(id, name, slug, description). - Adicionar coluna
class_idemcharacters. - Adicionar tabela pivot
item_class_restrictions(item_id, class_id) — se vazia, item é universal. - Regra de equip: verificar se o item é compatível com a classe do character.
Impacto no campo metadata de items:
{
"class_restrictions": ["warrior", "rogue"],
"effects": [
{ "type": "xp_multiplier", "value": 1.5, "duration_hours": 24 }
]
}A abordagem via metadata JSONB permite experimentação sem migrações, mas a tabela pivot é recomendada para queries performáticas quando o sistema de classes for implementado.