Pular para conteúdo

Triggers e Stored Procedure

Introdução

Triggers são um recurso do SQL (Structured Query Language) que permite a execução automática de uma ação definida (como uma instrução INSERT, UPDATE ou DELETE) quando um evento específico ocorre em uma tabela ou visão. Elas são usadas para garantir a integridade dos dados, automatizar processos, e implementar regras de negócios diretamente no banco de dados, sem a necessidade de intervenção manual.

Para acessar o script completo, clique no link a seguir: Visualizar TSP no GitHub

Funções

Funções programadas no próprio SQL para realizar atividades basicas do jogo como mover um jogador, pegar um item, realizar troca entre outros.

Security Definer

Funções programadas no próprio SQL para realizar atividades basicas do jogo como mover um jogador, pegar um item, realizar troca entre outros.(Coloca a foto embaixo).

Triggers Normais

Triggers comuns que não precisam de permissão de um superusuario para serem executados.

Figura 1:

Figura 2:

Figura 3:


Código

-- ROLLBACK;
/*
ARQUIVO: triggers-stored_procedures.sql
=====================================
        HISTÓRICO DE VERSÕES
=====================================
VERSÃO: 0.1
DATA: 28/06/2025
AUTOR: Wanjo Christopher
DESCRIÇÃO: Criação dos triggers e stored procedures para a generalização e especialização de personagens jogáveis e NPCs.

VERSÃO: 0.2
DATA: 28/06/2025
AUTOR: Wanjo Christopher
DESCRIÇÃO: Criação dos triggers e stored procedures para criação de personagens jogáveis e validação de atributos.

VERSÃO: 0.3
DATA: 28/06/2025
AUTOR: Wanjo Christopher
DESCRIÇÃO: Reorganização do arquivo e do nome das funções e triggers.

VERSÃO: 0.4
DATA: 28/06/2025
AUTOR: Wanjo Christopher
DESCRIÇÃO: Criação de stored procedure para criação de monstros, com validações de regras de negócio e exclusividade.

VERSÃO: 0.5
DATA: 28/06/2025
AUTOR: João Marcos
DESCRIÇÃO: Adição das funções e triggers para validação e criação de NPCs, e correção no DROP FUNCTION.

VERSÃO: 0.6
DATA: 28/06/2025
AUTOR: João Marcos
DESCRIÇÃO: Criação de triggers e stored procedures para missões, incluindo validações de regras de negócio e exclusividade.

VERSÃO: 0.7 
DATA: 05/07/2025
AUTOR: Luiz Guilherme
DESCRIÇÃO: Correção de bugs no arquivo

VERSÃO: 0.8
DATA: 05/07/2025
AUTOR: Wanjo Christopher
DESCRIÇÃO: Cria triggers e stored procedures para armas e armaduras, incluindo validações de regras de negócio e exclusividade.

VERSÃO: 0.9
DATA: 05/07/2025
AUTOR: Luiz Guilherme
DESCRIÇÃO: Criação do procedure para permitir o respawn de monstros e itens no jogo

VERSÃO: 0.10
DATA: 05/07/2025
AUTOR: Luiz Guilherme
DESCRIÇÃO: Criação dos procedures: vasculhar sala, olhar inventario, pegar item da sala

VERSÃO: 0.11
DATA: 05/07/2025
AUTOR: João Marcos
DESCRIÇÃO: Organiza so triggers de npc e personagem jogável.

VERSÃO: 0.12
DATA: 05/07/2025
AUTOR: João Marcos
DESCRIÇÃO: Criando o ROLE usuario_padrao e concedendo permissões de acesso ao banco de dados.

VERSÃO: 0.13
DATA: 05/07/2025
AUTOR: Wanjo Christopher
DESCRIÇÃO: Cria triggers, stored procedures e functions para itens consumíveis (cura e mágicos) e feitiços.

VERSÃO: 0.14
DATA: 06/07/2025
AUTOR: Luiz Guilherme
DESCRIÇÃO: Cria o stored procedure para movimentar os monstros de local.

VERSÃO: 0.15
DATA: 06/07/2025
AUTOR: Luiz Guilherme
DESCRIÇÃO: Corrige bugs da função encontrar monstros.

VERSÃO: 0.16
DATA: 05/07/2025
AUTOR: João Marcos
DESCRIÇÃO: Cria triggers, stored procedures e functions para Batalha e conclusão de missões.

VERSÃO: 0.17
DATA: 05/07/2025
AUTOR: João Marcos
DESCRIÇÃO: Cria triggers, stored procedures e functions para movimentação de jogadores, equipar itens e gerenciar durabilidade de itens.

VERSÃO: 0.18
DATA: 05/07/2025
AUTOR: Wanjo Chritopher
DESCRIÇÃO: Adiciona as procedures sp_desequipar_item e atualiza sp_equipar_item para gerenciar os atributos do personagem ao equipar e desequipar itens.

VERSÃO: 0.19
DATA: 05/07/2025
AUTOR: Wanjo Chritopher
DESCRIÇÃO: Corrige sp_executar_batalha para buscar vida da instância. Melhora sp_ver_inventario para retornar os bônus dos itens.

VERSÃO: 0.20
DATA: 05/07/2025
AUTOR: Wanjo Chritopher
DESCRIÇÃO: Cria procedure de insperior monstro e melhora a de checar inventário

VERSÃO: 0.21
DATA: 05/07/2025
AUTOR: João Marcos
DESCRIÇÃO: Cria procedure de Sanidade e pericias.

VERSÃO: 0.22
DATA: 05/07/2025
AUTOR: João Marcos
DESCRIÇÃO: Cria procedure para executar turno batalha, que calcula dano e atualiza vida de personagens e monstros.

VERSÃO: 0.23
DATA: 06/07/2025
AUTOR: João Marcos
DESCRIÇÃO: Cria procedure para LÓGICA DE BATALHA (NOVO - POR TURNO) E UTILITÁRIOS e STORED PROCEDURE: Realizar um teste de perícia

VERSÃO: 1.0
DATA: 06/07/2025
AUTOR: João Marcos
DESCRIÇÃO: Cria procedure para distribuição de pontos de perícia iniciais ao criar personagem jogável.
*/


-- -- ===============================================================================
-- --          0.1. DROP, CREATE, GRANK E REVOKE DE USUÁRIO PADRÃO DO BANCO 
-- -- ===============================================================================
-- DROP ROLE IF EXISTS usuario_padrao;

-- CREATE ROLE usuario_padrao
--     WITH LOGIN
--     NOSUPERUSER
--     NOCREATEDB
--     NOCREATEROLE
--     INHERIT
--     NOREPLICATION
--     CONNECTION LIMIT -1
--     PASSWORD 'usuario_padrao';
-- COMMENT ON ROLE usuario_padrao IS 'Usuário padrão para acesso ao banco de dados';

-- -- ===============================================================================
-- -- Permissões para o usuário padrão
-- -- ===============================================================================

-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO usuario_padrao;

-- REVOKE INSERT, UPDATE, DELETE ON public.personagens_jogaveis FROM usuario_padrao;


-- =================================================================================
--         0.2. DROP TRIGGER E DROP FUNCTIONS
-- Para que a criação de triggers e funções não gere erros, é necessário remover as existentes
-- =================================================================================

-- ======== DROP DE TRIGGERS ========
DROP TRIGGER IF EXISTS trigger_valida_unicidade_personagem_jogavel ON public.personagens_jogaveis CASCADE;
DROP TRIGGER IF EXISTS trigger_valida_unicidade_npc ON public.npcs CASCADE;
DROP TRIGGER IF EXISTS trigger_validar_atributos_personagem ON public.personagens_jogaveis CASCADE;
DROP TRIGGER IF EXISTS trigger_ajustar_atributos_personagem ON public.personagens_jogaveis CASCADE;
DROP TRIGGER IF EXISTS trigger_validar_atributos_npc ON public.npcs CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_monstros ON public.tipos_monstro CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_agressivos ON public.agressivos CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_pacificos ON public.pacificos CASCADE;
DROP TRIGGER IF EXISTS trigger_validar_dados_missao ON public.missoes CASCADE;
DROP TRIGGER IF EXISTS trigger_valida_atributos_item ON public.itens CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_itens ON public.itens CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_armas ON public.armas CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_armaduras ON public.armaduras CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_itens_curas ON public.curas CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_itens_magicos ON public.magicos CASCADE;
DROP TRIGGER IF EXISTS trigger_valida_exclusividade_id_arma ON public.armas CASCADE;
DROP TRIGGER IF EXISTS trigger_valida_exclusividade_id_armadura ON public.armaduras CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_tipos_feitico ON public.tipos_feitico CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_feiticos_status ON public.feiticos_status CASCADE;
DROP TRIGGER IF EXISTS trigger_bloqueia_insert_feiticos_dano ON public.feiticos_dano CASCADE;
DROP TRIGGER IF EXISTS trigger_checar_insanidade_personagem ON public.personagens_jogaveis CASCADE;
DROP TRIGGER IF EXISTS trigger_checar_durabilidade_arma ON public.armas CASCADE;
DROP TRIGGER IF EXISTS trigger_checar_durabilidade_armadura ON public.armaduras CASCADE;
DROP TRIGGER IF EXISTS trigger_checar_usos_cura ON public.curas CASCADE;

-- ======== DROP DE FUNÇÕES ========
-- Personagens e NPCs
DROP FUNCTION IF EXISTS public.func_valida_exclusividade_id_pj() CASCADE;
DROP FUNCTION IF EXISTS public.func_valida_exclusividade_id_npc() CASCADE;
DROP FUNCTION IF EXISTS public.func_validar_atributos_personagem() CASCADE;
DROP FUNCTION IF EXISTS public.func_ajustar_atributos_personagem() CASCADE;
DROP FUNCTION IF EXISTS public.sp_criar_personagem_jogavel(public.nome, public.ocupacao, public.residencia, public.local_nascimento, public.idade, public.sexo) CASCADE;
DROP FUNCTION IF EXISTS public.func_validar_atributos_npc() CASCADE;
DROP FUNCTION IF EXISTS public.sp_criar_npc(public.nome, public.ocupacao, public.residencia, public.local_nascimento, public.idade, public.sexo) CASCADE;
DROP FUNCTION IF EXISTS public.sp_atribuir_pericias_iniciais(public.id_personagem_jogavel, public.ocupacao) CASCADE;

-- Monstros
DROP FUNCTION IF EXISTS public.sp_criar_monstro(public.nome, public.descricao, public.tipo_monstro, SMALLINT, SMALLINT, SMALLINT, public.gatilho_agressividade, SMALLINT, public.tipo_monstro_agressivo, SMALLINT, SMALLINT, SMALLINT, public.dano, SMALLINT, SMALLINT, SMALLINT, public.comportamento_pacifico, public.tipo_monstro_pacifico, CHARACTER, CHARACTER) CASCADE;
DROP FUNCTION IF EXISTS public.func_bloquear_insert_direto_monstro() CASCADE;

-- Missões
DROP FUNCTION IF EXISTS public.sp_criar_missao(public.nome, CHARACTER, public.tipo_missao, CHARACTER, public.id_personagem_npc) CASCADE;
DROP FUNCTION IF EXISTS public.func_validar_dados_missao() CASCADE;
DROP FUNCTION IF EXISTS public.sp_entregar_missao(public.id_personagem_jogavel, public.id_missao) CASCADE;
DROP FUNCTION IF EXISTS public.sp_verificar_e_entregar_missao(public.id_personagem_jogavel, public.id_missao) CASCADE;

-- Itens
DROP FUNCTION IF EXISTS public.func_valida_atributos_item() CASCADE;
DROP FUNCTION IF EXISTS public.func_bloquear_insert_direto_itens() CASCADE;
DROP FUNCTION IF EXISTS public.sp_criar_arma(public.nome, public.descricao, SMALLINT, public.tipo_atributo_personagem, SMALLINT, SMALLINT, public.funcao_arma, SMALLINT, public.tipo_municao, public.tipo_dano, public.dano) CASCADE;
DROP FUNCTION IF EXISTS public.func_valida_exclusividade_id_arma() CASCADE;
DROP FUNCTION IF EXISTS public.sp_criar_armadura(public.nome, public.descricao, SMALLINT, public.tipo_atributo_personagem, SMALLINT, public.funcao_armadura, SMALLINT, public.tipo_atributo_personagem, SMALLINT, SMALLINT) CASCADE;
DROP FUNCTION IF EXISTS public.sp_criar_armadura(p_nome public.nome, p_descricao public.descricao, p_valor smallint, p_atributo_necessario public.tipo_atributo_personagem, p_durabilidade smallint, p_funcao public.funcao_armadura, p_qtd_atributo_recebe smallint, p_qtd_atributo_necessario smallint, p_tipo_atributo_recebe public.tipo_atributo_personagem, p_qtd_dano_mitigado smallint) CASCADE;
DROP FUNCTION IF EXISTS public.func_valida_exclusividade_id_armadura() CASCADE;
DROP FUNCTION IF EXISTS public.sp_criar_item_cura(public.nome, public.descricao, SMALLINT, public.funcao_cura, SMALLINT, SMALLINT, SMALLINT) CASCADE;
DROP FUNCTION IF EXISTS public.sp_criar_item_magico(public.nome, public.descricao, SMALLINT, public.funcao_magica, SMALLINT, SMALLINT, public.id_feitico) CASCADE;

-- Feitiços
DROP FUNCTION IF EXISTS public.sp_criar_feitico(public.nome, public.descricao, SMALLINT, public.funcao_feitico, BOOLEAN, SMALLINT, public.tipo_de_status, public.tipo_dano, public.dano) CASCADE;
DROP FUNCTION IF EXISTS public.func_bloquear_insert_direto_feitico() CASCADE;

-- Interação e Jogo
DROP FUNCTION IF EXISTS public.lua_de_sangue() CASCADE;
DROP FUNCTION IF EXISTS public.sp_vasculhar_local(public.id_local) CASCADE;
DROP FUNCTION IF EXISTS public.sp_adicionar_item_ao_inventario(public.id_personagem, public.id_instancia_de_item) CASCADE;
DROP FUNCTION IF EXISTS public.sp_ver_inventario(public.id_personagem) CASCADE;
DROP FUNCTION IF EXISTS public.sp_encontrar_monstros_no_local(public.id_local) CASCADE;
DROP FUNCTION IF EXISTS public.sp_matar_monstros_no_local(public.id_local) CASCADE;
DROP FUNCTION IF EXISTS public.sp_executar_batalha(public.id_personagem_jogavel, public.id_instancia_de_monstro) CASCADE;
DROP FUNCTION IF EXISTS public.sp_mover_jogador(public.id_personagem_jogavel, public.id_local) CASCADE;
DROP FUNCTION IF EXISTS public.sp_equipar_item(public.id_personagem_jogavel, public.id_instancia_de_item) CASCADE;
DROP FUNCTION IF EXISTS public.sp_desequipar_item(public.id_personagem_jogavel, TEXT) CASCADE;
DROP FUNCTION IF EXISTS public.sp_desequipar_item(public.id_personagem_jogavel, public.tipo_item) CASCADE;
DROP FUNCTION IF EXISTS public.sp_usar_item_cura(public.id_personagem_jogavel, public.id_instancia_de_item) CASCADE;
DROP FUNCTION IF EXISTS public.func_gerenciar_durabilidade_item() CASCADE;

-- Mecânicas Avançadas
DROP FUNCTION IF EXISTS public.func_verificar_insanidade() CASCADE;
DROP FUNCTION IF EXISTS public.sp_aplicar_dano_sanidade(public.id_personagem_jogavel, SMALLINT) CASCADE;
DROP FUNCTION IF EXISTS public.sp_realizar_teste_pericia(public.id_personagem_jogavel, public.nome) CASCADE;
DROP FUNCTION IF EXISTS public.sp_inspecionar_monstro(public.id_instancia_de_monstro) CASCADE;
DROP FUNCTION IF EXISTS public.sp_dialogar_com_npc(public.id_personagem_npc) CASCADE;
DROP FUNCTION IF EXISTS public.sp_executar_turno_batalha(public.id_personagem_jogavel, public.id_instancia_de_monstro) CASCADE;

-- =================================================================================
--         1. REGRAS GERAIS DE PERSONAGENS
--         Lógica para garantir a exclusividade entre PJ e NPC (Regra T,E)
-- =================================================================================
-------------------------------------------------------------
-- Função/Trigger: Garante que um PJ não possa ser um NPC
-------------------------------------------------------------
CREATE FUNCTION public.func_valida_exclusividade_id_pj()
RETURNS TRIGGER AS $$
BEGIN
    -- Verifica se o ID que está sendo inserido/atualizado em PJ já existe na tabela de NPCs.
    IF EXISTS (SELECT 1 FROM public.npcs WHERE id = NEW.id) THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: O ID % já existe na tabela de NPCs. Um personagem não pode ser Jogável e NPC ao mesmo tempo.', NEW.id;
    END IF;

    -- Se a verificação passar, permite que a operação continue.
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_valida_unicidade_personagem_jogavel
    BEFORE INSERT OR UPDATE ON public.personagens_jogaveis
    FOR EACH ROW EXECUTE FUNCTION public.func_valida_exclusividade_id_pj();

-------------------------------------------------------------
-- Função/Trigger: Garante que um NPC não possa ser um PJ
-------------------------------------------------------------
CREATE FUNCTION public.func_valida_exclusividade_id_npc()
RETURNS TRIGGER AS $$
BEGIN
    -- Verifica se o ID que está sendo inserido/atualizado em NPC já existe na tabela de PJs.
    IF EXISTS (SELECT 1 FROM public.personagens_jogaveis WHERE id = NEW.id) THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: O ID % já existe na tabela de Personagens Jogáveis. Um NPC não pode ser NPC e Jogável ao mesmo tempo.', NEW.id;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_valida_unicidade_npc
    BEFORE INSERT OR UPDATE ON public.npcs
    FOR EACH ROW EXECUTE FUNCTION public.func_valida_exclusividade_id_npc();


-- =================================================================================
--         1.2. REGRAS E PROCEDIMENTOS DE PERSONAGENS JOGÁVEIS (PJ)
-- =================================================================================

-------------------------------------------------------------
-- Função/Trigger: Valida os dados de entrada de um novo PJ
-------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.func_validar_atributos_personagem()
RETURNS TRIGGER AS $$
BEGIN
    -- Validação do nome
    IF NEW.nome IS NULL OR TRIM(NEW.nome) = '' OR NEW.nome ~ '[0-9]' THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: O nome do personagem não pode ser nulo, vazio ou conter números.';
    END IF;

    -- Validação de outros campos de texto obrigatórios
    IF TRIM(NEW.ocupacao) = '' OR TRIM(NEW.residencia) = '' OR TRIM(NEW.local_nascimento) = '' THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Os campos "ocupacao", "residencia" e "local_nascimento" não podem ser nulos ou vazios.';
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_validar_atributos_personagem
    BEFORE INSERT ON public.personagens_jogaveis
    FOR EACH ROW EXECUTE FUNCTION public.func_validar_atributos_personagem();

-------------------------------------------------------------
-- Função/Trigger: Ajusta atributos calculados de um novo PJ
-------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.func_ajustar_atributos_personagem()
RETURNS TRIGGER AS $$
BEGIN
    -- Cálculo do MOVIMENTO
    IF NEW.destreza < NEW.tamanho AND NEW.forca < NEW.tamanho THEN
        NEW.movimento := 7;
    ELSIF NEW.destreza > NEW.tamanho AND NEW.forca > NEW.tamanho THEN
        NEW.movimento := 9;
    ELSE
        NEW.movimento := 8;
    END IF;

    -- Cálculo de Sanidade, Vida e PM iniciais, usando as funções auxiliares do DDL.
    NEW.sanidade_atual := public.calcular_sanidade(NEW.poder);
    NEW.pontos_de_vida_atual := public.calcular_pts_de_vida(NEW.constituicao, NEW.tamanho);
    NEW.pm_base := NEW.poder;
    NEW.pm_max := NEW.poder;

    -- Definindo valores iniciais padrão para colunas booleanas.
    NEW.insanidade_temporaria := FALSE;
    NEW.insanidade_indefinida := FALSE;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_ajustar_atributos_personagem
    BEFORE INSERT ON public.personagens_jogaveis
    FOR EACH ROW EXECUTE FUNCTION public.func_ajustar_atributos_personagem();

-------------------------------------------------------------
--  STORED PROCEDURE: Criação de PJs
-------------------------------------------------------------
/*
    Este procedimento encapsula a lógica de criação de um novo Personagem Jogável.
    'p_' indica um parâmetro de entrada.
    'v_' indica uma variável local.
*/
CREATE FUNCTION public.sp_criar_personagem_jogavel(
    p_nome public.nome,
    p_ocupacao public.ocupacao,
    p_residencia public.residencia,
    p_local_nascimento public.local_nascimento,
    p_idade public.idade,
    p_sexo public.sexo
)
RETURNS public.id_personagem_jogavel AS $$
DECLARE
    v_novo_inventario_id public.id_inventario;
    v_novo_personagem_id public.id_personagem_jogavel;
BEGIN
    -- 1. Cria o inventário para o novo personagem.
    INSERT INTO public.inventarios (tamanho) VALUES (32) RETURNING id INTO v_novo_inventario_id;

    -- 2. Insere os dados básicos na tabela. Os atributos calculados e as validações
    --    serão processados automaticamente pelas triggers 'trigger_ajustar_atributos_personagem'
    --    e 'trigger_validar_atributos_personagem'.
    INSERT INTO public.personagens_jogaveis (
        nome, ocupacao, residencia, local_nascimento, idade, sexo,
        id_inventario, id_local
    ) VALUES (
        p_nome, p_ocupacao, p_residencia, p_local_nascimento, p_idade, p_sexo,
        v_novo_inventario_id, (SELECT id FROM public.local WHERE descricao LIKE 'O ar pesa%' AND tipo_local = 'Sala' LIMIT 1) -- Define uma sala inicial padrão
    ) RETURNING id INTO v_novo_personagem_id;

    -- 3. ADICIONE ESTA LINHA PARA ATRIBUIR AS PERÍCIAS
    PERFORM public.sp_atribuir_pericias_iniciais(v_novo_personagem_id, p_ocupacao);

    -- 4. Retorna o ID do personagem recém-criado.
    RETURN v_novo_personagem_id;
END;
$$ LANGUAGE plpgsql;


-- =================================================================================
--         1.3. REGRAS E PROCEDIMENTOS DE NPCs
-- =================================================================================

-------------------------------------------------------------
-- Função/Trigger: Valida os dados de entrada de um novo NPC
-------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.func_validar_atributos_npc()
RETURNS TRIGGER AS $$
BEGIN
    -- Validação do nome (similar ao PJ)
    IF NEW.nome IS NULL OR TRIM(NEW.nome) = '' OR NEW.nome ~ '[0-9]' THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: O nome do NPC não pode ser nulo, vazio ou conter números.';
    END IF;

    -- Validação de outros campos de texto obrigatórios
    IF TRIM(NEW.ocupacao) = '' OR TRIM(NEW.residencia) = '' OR TRIM(NEW.local_nascimento) = '' THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Os campos "ocupacao", "residencia" e "local_nascimento" do NPC não podem ser nulos ou vazios.';
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_validar_atributos_npc
    BEFORE INSERT ON public.npcs
    FOR EACH ROW EXECUTE FUNCTION public.func_validar_atributos_npc();

-------------------------------------------------------------
-- STORED PROCEDURE: Criação de NPCs
-------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_criar_npc(
    p_nome public.nome,
    p_ocupacao public.ocupacao,
    p_residencia public.residencia,
    p_local_nascimento public.local_nascimento,
    p_idade public.idade,
    p_sexo public.sexo,
    p_id_local public.id_local DEFAULT 40300003
)
RETURNS public.id_personagem_npc AS $$
DECLARE
    v_novo_inventario_id public.id_inventario;
    v_novo_npc_id public.id_personagem_npc;
BEGIN
    INSERT INTO public.inventarios (tamanho) VALUES (32) RETURNING id INTO v_novo_inventario_id;
    -- Insere os dados do NPC. A validação será feita pela trigger 'trigger_validar_atributos_npc'.
    INSERT INTO public.npcs (
        nome, ocupacao, residencia, local_nascimento, idade, sexo, id_inventario,
        id_local -- Localização inicial
    ) VALUES (
        p_nome, p_ocupacao, p_residencia, p_local_nascimento, p_idade, p_sexo, v_novo_inventario_id,
        p_id_local -- Sala inicial padrão para NPCs (exemplo, pode ser alterado ou passado como parâmetro)
    ) RETURNING id INTO v_novo_npc_id;

    RETURN v_novo_npc_id;
END;
$$ LANGUAGE plpgsql;

-- =================================================================================
--         1.4. STORED PROCEDURE PARA ATRIBUIR PERÍCIAS INICIAIS
--         Esta procedure é chamada pela sp_criar_personagem_jogavel
-- =================================================================================

CREATE OR REPLACE FUNCTION public.sp_atribuir_pericias_iniciais(p_id_jogador public.id_personagem_jogavel, p_ocupacao public.ocupacao) RETURNS VOID AS $$
DECLARE
    -- Variáveis para os pontos de perícia
    v_pontos_ocupacao INT;
    v_pontos_pessoais INT;
    v_educacao INT;
    v_inteligencia INT;

    -- Variáveis para a lógica de distribuição
    v_pericias_ocupacao_ids INT[];
    v_pericia_escolhida_id INT;
    v_pontos_a_gastar INT;
    v_pericia_rec RECORD;
    v_base_valor INT;
BEGIN
    -- 1. Obter os atributos do jogador para calcular os pontos de perícia
    SELECT educacao, inteligencia INTO v_educacao, v_inteligencia
    FROM public.personagens_jogaveis WHERE id = p_id_jogador;

    -- 2. Calcular os pontos de perícia baseados nas regras do Call of Cthulhu (EDU*4 para ocupação, INT*2 para pessoais)
    v_pontos_ocupacao := v_educacao * 4;
    v_pontos_pessoais := v_inteligencia * 2;

    -- 3. Mapear a Ocupação para uma lista de IDs de perícias
    CASE p_ocupacao
        WHEN 'Medico' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Primeiros Socorros', 'Medicina', 'Biologia', 'Farmácia', 'Ciência', 'Psicanálise', 'Língua, Outra', 'Lábia'));
        WHEN 'Doutor' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Medicina', 'Ciência', 'Usar Bibliotecas', 'Língua, Outra', 'Psicologia', 'Farmácia', 'Persuasão', 'Direito'));
        WHEN 'Arqueologo' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Arqueologia', 'História', 'Encontrar', 'Usar Bibliotecas', 'Língua, Outra', 'Arte e Ofício', 'Avaliação', 'Mundo Natural'));
        WHEN 'Detetive' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Psicologia', 'Encontrar', 'Fotografia', 'Usar Bibliotecas', 'Lábia', 'Intimidação', 'Disfarce', 'Briga'));
        WHEN 'Jornalista' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Psicologia', 'Lábia', 'Fotografia', 'Usar Bibliotecas', 'História', 'Charme', 'Furtividade', 'Língua (Nativa)'));
        WHEN 'Professor' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Psicologia', 'Usar Bibliotecas', 'Nível de Crédito', 'Língua, Outra', 'História', 'Ciência', 'Persuasão', 'Charme'));
        WHEN 'Engenheiro' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Engenharia', 'Ciência', 'Consertos Elétricos', 'Consertos Mecânicos', 'Usar Bibliotecas', 'Geologia', 'Física', 'Matemática'));
        WHEN 'Artista' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Arte e Ofício', 'Belas Artes', 'Fotografia', 'Psicologia', 'Charme', 'Persuasão', 'Encontrar', 'Furtividade'));
        WHEN 'Soldado' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Esquivar', 'Briga', 'Furtividade', 'Primeiros Socorros', 'Sobrevivencia', 'Rifles', 'Metralhadoras', 'Intimidação'));
        WHEN 'Explorador' THEN
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE nome IN ('Antropologia', 'Arqueologia', 'Mundo Natural', 'Escalar', 'Navegação', 'Sobrevivencia', 'Rastrear', 'Armas de Fogo'));
        ELSE
            v_pericias_ocupacao_ids := ARRAY(SELECT id FROM public.pericias WHERE eh_de_ocupacao = TRUE ORDER BY random() LIMIT 8);
    END CASE;

    -- 4. Distribuir os pontos de Ocupação
    WHILE v_pontos_ocupacao > 0 LOOP
        v_pericia_escolhida_id := v_pericias_ocupacao_ids[1 + floor(random() * array_length(v_pericias_ocupacao_ids, 1))];
        v_pontos_a_gastar := LEAST(v_pontos_ocupacao, 5 + floor(random() * 11));

        INSERT INTO public.personagens_possuem_pericias (id_personagem, id_pericia, valor_atual)
        VALUES (p_id_jogador, v_pericia_escolhida_id, v_pontos_a_gastar)
        ON CONFLICT (id_personagem, id_pericia) DO UPDATE
        SET valor_atual = personagens_possuem_pericias.valor_atual + v_pontos_a_gastar;

        v_pontos_ocupacao := v_pontos_ocupacao - v_pontos_a_gastar;
    END LOOP;

    -- 5. Distribuir os pontos Pessoais
    WHILE v_pontos_pessoais > 0 LOOP
        SELECT id INTO v_pericia_escolhida_id FROM public.pericias
        WHERE id <> ALL(v_pericias_ocupacao_ids) AND nome <> 'Mythos de Cthulhu'
        ORDER BY random() LIMIT 1;

        v_pontos_a_gastar := LEAST(v_pontos_pessoais, 5 + floor(random() * 11));

        INSERT INTO public.personagens_possuem_pericias (id_personagem, id_pericia, valor_atual)
        VALUES (p_id_jogador, v_pericia_escolhida_id, v_pontos_a_gastar)
        ON CONFLICT (id_personagem, id_pericia) DO UPDATE
        SET valor_atual = personagens_possuem_pericias.valor_atual + v_pontos_a_gastar;

        v_pontos_pessoais := v_pontos_pessoais - v_pontos_a_gastar;
    END LOOP;

    -- 6. Adicionar os valores base a todas as perícias atribuídas
    FOR v_pericia_rec IN SELECT id_pericia, valor_atual FROM public.personagens_possuem_pericias WHERE id_personagem = p_id_jogador LOOP
        SELECT valor INTO v_base_valor FROM public.pericias WHERE id = v_pericia_rec.id_pericia;
        UPDATE public.personagens_possuem_pericias
        SET valor_atual = v_pericia_rec.valor_atual + v_base_valor
        WHERE id_personagem = p_id_jogador AND id_pericia = v_pericia_rec.id_pericia;
    END LOOP;

    -- 7. Adicionar/Atualizar perícias essenciais que todos devem ter
    -- CORREÇÃO: Usando ON CONFLICT para evitar erro se 'Língua (Nativa)' já foi adicionada como perícia de ocupação.
    INSERT INTO public.personagens_possuem_pericias(id_personagem, id_pericia, valor_atual)
    VALUES (p_id_jogador, (SELECT id FROM pericias WHERE nome = 'Língua (Nativa)'), v_educacao)
    ON CONFLICT (id_personagem, id_pericia) DO UPDATE SET valor_atual = personagens_possuem_pericias.valor_atual + v_educacao;

    -- Mythos de Cthulhu sempre começa em 0
    INSERT INTO public.personagens_possuem_pericias(id_personagem, id_pericia, valor_atual)
    VALUES (p_id_jogador, (SELECT id FROM pericias WHERE nome = 'Mythos de Cthulhu'), 0)
    ON CONFLICT (id_personagem, id_pericia) DO NOTHING;

END;
$$ LANGUAGE plpgsql;

/*
=================================================================================
        2. LÓGICA PARA MONSTROS
=================================================================================
*/

-- ---------------------------------------------------------------------------------
--         2.1. STORED PROCEDURE PARA CRIAÇÃO DE MONSTROS
--         Este SP é o único ponto de entrada e garante as regras Total, Exclusiva e de Atributos.
-- ---------------------------------------------------------------------------------

CREATE FUNCTION public.sp_criar_monstro(
    -- Parâmetros padrão para ambas tabelas
    p_nome public.nome,
    p_descricao public.descricao,
    -- Parâmetro para public.tipos_monstro
    p_tipo public.tipo_monstro,

    -- Parâmetros para monstro agressivo
    p_agressivo_defesa SMALLINT DEFAULT NULL,
    p_agressivo_vida SMALLINT DEFAULT NULL,
    p_agressivo_vida_total SMALLINT DEFAULT NULL,
    p_agressivo_catalisador public.gatilho_agressividade DEFAULT NULL,
    p_agressivo_poder SMALLINT DEFAULT NULL,
    p_agressivo_tipo public.tipo_monstro_agressivo DEFAULT NULL,
    p_agressivo_velocidade SMALLINT DEFAULT NULL,
    p_agressivo_loucura SMALLINT DEFAULT NULL,
    p_agressivo_pm SMALLINT DEFAULT NULL,
    p_agressivo_dano public.dano DEFAULT NULL,

    -- Parâmetros para monstro pacífico
    p_pacifico_defesa SMALLINT DEFAULT NULL,
    p_pacifico_vida SMALLINT DEFAULT NULL,
    p_pacifico_vida_total SMALLINT DEFAULT NULL,
    p_pacifico_motivo public.comportamento_pacifico DEFAULT NULL,
    p_pacifico_tipo public.tipo_monstro_pacifico DEFAULT NULL,
    p_pacifico_conhecimento_geo CHARACTER(128) DEFAULT NULL,
    p_pacifico_conhecimento_proibido CHARACTER(128) DEFAULT NULL
) 
RETURNS public.id_monstro 
LANGUAGE plpgsql AS $$
DECLARE
    v_novo_monstro_id public.id_monstro;
BEGIN
    -- == Funcionamento de current_setting('nome_da_config', 'missing_ok') ==
    -- Caso 'missing_ok' seja 'true', não gera erro caso a configuração não exista
    -- Caso seja 'false', gera Exception, o que daria problema pois testamos se o valor é inexistente (NULL)
    SET LOCAL bd_cthulhu.inserir = 'true';

    -- =================== VALIDAÇÃO E INSERÇÃO ===================
    IF p_tipo = 'agressivo' THEN
        IF p_agressivo_vida IS NULL OR p_agressivo_dano IS NULL OR p_agressivo_tipo IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Para monstros agressivos, os campos vida, dano e tipo_agressivo são obrigatórios.';
        END IF;
        IF p_agressivo_tipo = 'psiquico' AND p_agressivo_loucura IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Monstros do tipo "psiquico" devem ter valor para "loucura_induzida".';
        ELSIF p_agressivo_tipo = 'magico' AND p_agressivo_pm IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Monstros do tipo "magico" devem ter valor para "ponto_magia".';
        ELSIF p_agressivo_tipo = 'fisico' AND p_agressivo_velocidade IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Monstros do tipo "fisico" devem ter valor para "velocidade_ataque".';
        END IF;

        v_novo_monstro_id := public.gerar_id_monstro_agressivo();

        -- Insere na tabela de tipos_monstro
        INSERT INTO public.tipos_monstro (id, tipo)
            VALUES (v_novo_monstro_id, p_tipo);

        -- Insere na tabela de monstros agressivos
        INSERT INTO public.agressivos (id, nome, descricao, defesa, vida, vida_total, catalisador_agressividade, poder, tipo_agressivo, velocidade_ataque, loucura_induzida, ponto_magia, dano)
            VALUES (v_novo_monstro_id, p_nome, p_descricao, p_agressivo_defesa, p_agressivo_vida, p_agressivo_vida_total, p_agressivo_catalisador, p_agressivo_poder, p_agressivo_tipo, p_agressivo_velocidade, p_agressivo_loucura, p_agressivo_pm, p_agressivo_dano);
    ELSIF p_tipo = 'pacífico' THEN
        IF p_pacifico_vida IS NULL OR p_pacifico_defesa IS NULL OR p_pacifico_motivo IS NULL OR p_pacifico_tipo IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Para monstros pacíficos, os campos vida, defesa, motivo_passividade e tipo_pacifico são obrigatórios.';
        END IF;
        IF p_pacifico_tipo = 'sobrenatural' AND p_pacifico_conhecimento_proibido IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Monstros do tipo "sobrenatural" devem ter valor para "conhecimento_proibido".';
        ELSIF p_pacifico_tipo = 'humanoide' AND p_pacifico_conhecimento_geo IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Monstros do tipo "humanoide" devem ter valor para "conhecimento_geografico".';
        END IF;

        v_novo_monstro_id := public.gerar_id_monstro_pacifico();
        -- Insere na tabela de tipos_monstro
        INSERT INTO public.tipos_monstro (id, tipo)
            VALUES (v_novo_monstro_id, p_tipo);

        -- Insere na tabela de monstros pacíficos
        INSERT INTO public.pacificos (id, nome, descricao, defesa, vida, vida_total, motivo_passividade, tipo_pacifico, conhecimento_geografico, conhecimento_proibido)
            VALUES (v_novo_monstro_id, p_nome, p_descricao, p_pacifico_defesa, p_pacifico_vida, p_pacifico_vida_total, p_pacifico_motivo, p_pacifico_tipo, p_pacifico_conhecimento_geo, p_pacifico_conhecimento_proibido);
    ELSE
        RAISE EXCEPTION 'Tipo de monstro inválido: %. Use "agressivo" ou "pacífico".', p_tipo;
    END IF;

    RETURN v_novo_monstro_id;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro na criação do monstro: %', SQLERRM;
        RAISE;
END;
$$;
-- =================================================================================
--         2.2 STORED PROCEDURE: Inspecionar detalhes de um monstro (Versão Única e Corrigida)
-- =================================================================================
CREATE OR REPLACE FUNCTION public.sp_inspecionar_monstro(
    p_id_instancia_monstro public.id_instancia_de_monstro
)
RETURNS TABLE (
    monstro_nome public.nome,
    monstro_descricao public.descricao,
    tipo_monstro public.tipo_monstro,
    vida_atual SMALLINT,
    vida_total SMALLINT,
    defesa SMALLINT,
    dano public.dano,
    detalhes_especificos JSON
)
LANGUAGE plpgsql AS $$
BEGIN
    RETURN QUERY
    SELECT
        COALESCE(a.nome, p.nome),
        COALESCE(a.descricao, p.descricao),
        tm.tipo,
        im.vida,
        COALESCE(a.vida_total, p.vida_total) AS vida_total,
        COALESCE(a.defesa, p.defesa) AS defesa,
        a.dano,
        CASE
            WHEN tm.tipo = 'agressivo' THEN json_build_object(
                'tipo_agressivo', a.tipo_agressivo,
                'catalisador', a.catalisador_agressividade,
                'poder', a.poder,
                'velocidade_ataque', a.velocidade_ataque,
                'loucura_induzida', a.loucura_induzida,
                'ponto_magia', a.ponto_magia
            )
            WHEN tm.tipo = 'pacífico' THEN json_build_object(
                'tipo_pacifico', p.tipo_pacifico,
                'motivo_passividade', p.motivo_passividade,
                'conhecimento_geografico', p.conhecimento_geografico,
                'conhecimento_proibido', p.conhecimento_proibido
            )
        END AS detalhes_especificos
    FROM public.instancias_monstros im
    JOIN public.tipos_monstro tm ON im.id_monstro = tm.id
    LEFT JOIN public.agressivos a ON tm.id = a.id
    LEFT JOIN public.pacificos p ON tm.id = p.id
    WHERE im.id = p_id_instancia_monstro;
END;
$$;

/*
=================================================================================
        3. LÓGICA PARA MISSÕES
=================================================================================
*/
-- =================================================================================
--         3.1. FUNÇÕES, TRIGGERS E STORED PROCEDURES PARA MISSÕES
-- =================================================================================
-------------------------------------------------------------
-- STORED PROCEDURE: Facilita a criação de novas missões
-------------------------------------------------------------
CREATE FUNCTION public.sp_criar_missao(
    p_nome public.nome,
    p_descricao CHARACTER(512),
    p_tipo public.tipo_missao,
    p_ordem CHARACTER(128),
    p_id_npc public.id_personagem_npc
)
RETURNS public.id_missao 
LANGUAGE plpgsql AS $$
DECLARE
    v_nova_missao_id public.id_missao;
BEGIN
    -- A inserção irá automaticamente disparar a trigger 'trigger_validar_dados_missao'
    -- para garantir a integridade dos dados antes de confirmar a operação.
    INSERT INTO public.missoes (
        nome,
        descricao,
        tipo,
        ordem,
        id_npc
    ) VALUES (
        p_nome,
        p_descricao,
        p_tipo,
        p_ordem,
        p_id_npc
    ) RETURNING id INTO v_nova_missao_id;

    RETURN v_nova_missao_id;
END;
$$;

-------------------------------------------------------------
-- Função/Trigger: Valida os dados de uma nova Missão
-------------------------------------------------------------
CREATE FUNCTION public.func_validar_dados_missao()
RETURNS TRIGGER AS $$
BEGIN
    -- 1. Validação dos campos de texto obrigatórios
    IF NEW.nome IS NULL OR TRIM(NEW.nome) = '' THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: O nome da missão não pode ser nulo ou vazio.';
    END IF;

    IF NEW.descricao IS NULL OR TRIM(NEW.descricao) = '' THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: A descrição da missão não pode ser nula ou vazia.';
    END IF;

    -- 2. Validação da existência do NPC
    -- Garante que o NPC que entrega a missão realmente existe.
    IF NOT EXISTS (SELECT 1 FROM public.npcs WHERE id = NEW.id_npc) THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA DE FK: O NPC com ID % não existe. Não é possível criar uma missão para um NPC inexistente.', NEW.id_npc;
    END IF;

    -- 3. Validação do tipo de missão (embora o DOMAIN já faça isso, é uma boa prática reforçar)
    IF NEW.tipo IS NULL THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: O tipo da missão não pode ser nulo.';
    END IF;

    -- Se todas as validações passarem, permite a operação.
    RETURN NEW;
END;
$$ LANGUAGE plpgsql ;

CREATE TRIGGER trigger_validar_dados_missao
    BEFORE INSERT OR UPDATE ON public.missoes
    FOR EACH ROW EXECUTE FUNCTION public.func_validar_dados_missao();

/*
=================================================================================
        4. FUNÇÕES DE ITENS (GERAL)
=================================================================================
*/
-- ---------------------------------------------------------------------------------
-- Função/Trigger: Valida os atributos de um item antes de inseri-lo ou atualizá-lo
-- ---------------------------------------------------------------------------------
CREATE FUNCTION public.func_valida_atributos_item()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.id IS NULL THEN
        RAISE EXCEPTION '[VIOLAÇÃO DE REGRA] O ID do item não pode ser nulo.';
    ELSIF NEW.nome IS NULL OR TRIM(NEW.nome) = '' THEN
        RAISE EXCEPTION '[VIOLAÇÃO DE REGRA] O nome do item não pode ser nulo ou vazio.';
    ELSIF NEW.descricao IS NULL OR TRIM(NEW.descricao) = '' THEN
        RAISE EXCEPTION '[VIOLAÇÃO DE REGRA] A descrição do item não pode ser nula ou vazia.';
    ELSIF NEW.valor IS NULL OR NOT (NEW.valor BETWEEN 0 AND 999) THEN
        RAISE EXCEPTION '[VIOLAÇÃO DE REGRA] O valor do item não pode ser nulo ou negativo ou maior que 999.';
    ELSIF NEW.tipo IS NULL THEN
        RAISE EXCEPTION '[VIOLAÇÃO DE REGRA] O tipo do item não pode ser nulo.';
    END IF;
    RETURN NEW;
END;  
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_valida_atributos_item
    BEFORE INSERT OR UPDATE ON public.itens
    FOR EACH ROW
    EXECUTE FUNCTION public.func_valida_atributos_item();

-- ---------------------------------------------------------------------------------
-- Função/Trigger: Bloqueia inserções diretas na tabela 'itens', 'armas', 'armaduras', 'curas' e 'magicos'
-- ---------------------------------------------------------------------------------
CREATE FUNCTION public.func_bloquear_insert_direto_itens()
RETURNS TRIGGER AS $$
BEGIN
    IF current_setting('bd_cthulhu.inserir', true) IS DISTINCT FROM 'true' THEN
        RAISE EXCEPTION 'Inserção direta na tabela "itens" não é permitida. Utilize a função apropriada.';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_bloqueia_insert_itens
    BEFORE INSERT ON public.itens 
    FOR EACH ROW 
    EXECUTE FUNCTION public.func_bloquear_insert_direto_itens();

CREATE TRIGGER trigger_bloqueia_insert_armas
    BEFORE INSERT ON public.armas 
    FOR EACH ROW 
    EXECUTE FUNCTION public.func_bloquear_insert_direto_itens();

CREATE TRIGGER trigger_bloqueia_insert_armaduras
    BEFORE INSERT ON public.armaduras 
    FOR EACH ROW 
    EXECUTE FUNCTION public.func_bloquear_insert_direto_itens();

CREATE TRIGGER trigger_bloqueia_insert_itens_curas
    BEFORE INSERT ON public.curas 
    FOR EACH ROW 
    EXECUTE FUNCTION public.func_bloquear_insert_direto_itens();

CREATE TRIGGER trigger_bloqueia_insert_itens_magicos
    BEFORE INSERT ON public.magicos 
    FOR EACH ROW 
    EXECUTE FUNCTION public.func_bloquear_insert_direto_itens();

-- =================================================================================
--         4.1.  FUNÇÕES, TRIGGERS E STORED PROCEDURES PARA ARMAS
-- =================================================================================
CREATE FUNCTION public.sp_criar_arma(
    p_nome public.nome,
    p_descricao public.descricao,
    p_valor SMALLINT,

    -- Parâmetros específicos de arma
    p_atributo_necessario public.tipo_atributo_personagem,
    p_qtd_atributo_necessario SMALLINT DEFAULT NULL,
    p_durabilidade SMALLINT DEFAULT NULL,
    p_funcao public.funcao_arma DEFAULT NULL,
    p_alcance SMALLINT DEFAULT NULL,
    p_tipo_municao public.tipo_municao DEFAULT NULL,
    p_tipo_dano public.tipo_dano DEFAULT NULL,
    p_dano public.dano DEFAULT NULL
)
RETURNS public.id_item 
LANGUAGE plpgsql AS $$
DECLARE
    v_novo_item_id public.id_item;
    v_pericia_id public.id_pericia;
BEGIN
    -- Passe livre para inserção de itens, sem bloqueio de triggers.
    -- Esse set volta a ter valor 'NULL' ao final da transação, garantindo que nenhuma tupla seja inserida sem passsar pelo sp_criar_arma.
    SET LOCAL bd_cthulhu.inserir = 'true';

    -- =================== VALIDAÇÃO ===================
    SELECT id INTO v_pericia_id FROM public.pericias WHERE nome = 'Briga';

    IF NOT FOUND THEN
        RAISE EXCEPTION 'A perícia obrigatória "Briga" não foi encontrada na tabela public.pericias.';
    END IF;
    -- =================== INSERÇÃO ===================
    v_novo_item_id := public.gerar_id_item_arma();

    -- Insere na tabela pai 'itens'
    INSERT INTO public.itens(id, tipo, nome, descricao, valor)
    VALUES(v_novo_item_id, 'arma', p_nome, p_descricao, p_valor);

    -- Insere na tabela filha correta
    INSERT INTO public.armas (id, atributo_necessario, qtd_atributo_necessario, durabilidade, funcao, alcance, tipo_municao, tipo_dano, dano, id_pericia_necessaria)
    VALUES (v_novo_item_id, p_atributo_necessario, p_qtd_atributo_necessario, p_durabilidade, p_funcao, p_alcance, p_tipo_municao, p_tipo_dano, p_dano, v_pericia_id);

    RETURN v_novo_item_id;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro na criação da arma: %', SQLERRM;
        RAISE; -- Re-lança a exceção para que a transação seja desfeita.
END;
$$;

-- ---------------------------------------------------------------------------------
-- Função/Trigger: Garante que uma armadura não possa ser uma arma
-- ---------------------------------------------------------------------------------
CREATE FUNCTION public.func_valida_exclusividade_id_arma()
RETURNS TRIGGER AS $$
BEGIN
    IF EXISTS (SELECT 1 FROM public.armaduras WHERE id = NEW.id) THEN
        RAISE EXCEPTION 'O ID % já existe na tabela de armaduras. Um item do tipo arma não pode ser do tipo armadura.', NEW.id;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_valida_exclusividade_id_arma
    BEFORE INSERT OR UPDATE ON public.armas
    FOR EACH ROW EXECUTE FUNCTION public.func_valida_exclusividade_id_arma();

-- =================================================================================
--         4.2.  FUNÇÕES, TRIGGERS E STORED PROCEDURE PARA ARMADURAS
-- =================================================================================

CREATE OR REPLACE FUNCTION public.sp_criar_armadura(
    p_nome public.nome,
    p_descricao public.descricao,
    p_valor SMALLINT,
    p_atributo_necessario public.tipo_atributo_personagem,
    p_durabilidade SMALLINT,
    p_funcao funcao_armadura,
    p_qtd_atributo_recebe SMALLINT,
    p_qtd_atributo_necessario SMALLINT,
    p_tipo_atributo_recebe public.tipo_atributo_personagem,
    p_qtd_dano_mitigado SMALLINT
)
RETURNS public.id_item 
LANGUAGE plpgsql AS $$
DECLARE
    v_novo_item_id public.id_item;
    v_pericia_id public.id_pericia;
BEGIN
    SET LOCAL bd_cthulhu.inserir = 'true';

    -- =================== VALIDAÇÃO ===================
    SELECT id INTO v_pericia_id FROM public.pericias WHERE nome = 'Uso de Armadura';

    IF NOT FOUND THEN
        RAISE EXCEPTION 'A perícia obrigatória "Uso de Armadura" não foi encontrada na tabela public.pericias.';
    END IF;

    -- =================== INSERÇÃO ===================
    v_novo_item_id := public.gerar_id_item_de_armadura();

    -- Insere na tabela pai 'itens'
    INSERT INTO public.itens(id, tipo, nome, descricao, valor)
        VALUES(v_novo_item_id, 'armadura', p_nome, p_descricao, p_valor);

    -- Insere na tabela filha correta
    INSERT INTO public.armaduras (id, atributo_necessario, durabilidade, funcao, qtd_atributo_recebe, qtd_atributo_necessario, tipo_atributo_recebe, qtd_dano_mitigado, id_pericia_necessaria)
        VALUES (v_novo_item_id, p_atributo_necessario, p_durabilidade, p_funcao , p_qtd_atributo_recebe, p_qtd_atributo_necessario, p_tipo_atributo_recebe, p_qtd_dano_mitigado, v_pericia_id);

    RETURN v_novo_item_id;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro na criação da armadura: %', SQLERRM;
        RAISE; -- Re-lança a exceção para que a transação seja desfeita.
END;
$$;

-- ---------------------------------------------------------------------------------
-- Função/Trigger: Garante que uma arma não possa ser uma armadura
-- ---------------------------------------------------------------------------------
CREATE FUNCTION public.func_valida_exclusividade_id_armadura()
RETURNS TRIGGER AS $$
BEGIN
    IF EXISTS (SELECT 1 FROM public.armas WHERE id = NEW.id) THEN
        RAISE EXCEPTION 'O ID % já existe na tabela de armas. Um item do tipo armadura não pode ser do tipo arma.', NEW.id;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_valida_exclusividade_id_armadura
    BEFORE INSERT OR UPDATE ON public.armaduras
    FOR EACH ROW EXECUTE FUNCTION public.func_valida_exclusividade_id_armadura();

-- =================================================================================
--         4.3.  STORED PROCEDURE PARA ITENS CONSUMÍVEIS DE CURA 
-- =================================================================================
--  Atributos levados em consideração para min e max dos efeitos de cura e magia:
--  ==== Sanidade Máxima (poder * 5) ====
--      Mínima: 3 de poder = 15 de Sanidade
--      Média: 10 de poder = 50 de Sanidade
--      Máxima: 18 de poder = 90 de Sanidade

-- ==== Pontos de Vida Máximos ((constituicao + tamanho) / 2) ====
--      Mínimo: (3+3)/2 = 3 PV
--      Médio: (10+10)/2 = 10 PV
--      Máximo: (18+18)/2 = 18 PV
-- =================================================================================
CREATE FUNCTION public.sp_criar_item_cura(
    p_nome public.nome,
    p_descricao public.descricao,
    p_valor SMALLINT,

    -- Parâmetros específicos de cura
    p_funcao public.funcao_cura,
    p_qts_usos SMALLINT,
    p_qtd_pontos_sanidade_recupera SMALLINT,
    p_qtd_pontos_vida_recupera SMALLINT
)
RETURNS public.id_item_de_cura
LANGUAGE plpgsql AS $$
DECLARE
    v_novo_item_id public.id_item_de_cura;
BEGIN
    SET LOCAL bd_cthulhu.inserir = 'true';
    -- =================== VALIDAÇÃO ===================
    IF p_funcao IS NULL THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: A função de cura não pode ser nula.';
    END IF;
    IF p_qts_usos <= 0 THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: A quantidade de usos deve ser maior que zero.';
    END IF;
    IF NOT(p_qtd_pontos_sanidade_recupera BETWEEN 1 AND 8) THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: A quantidade de pontos de sanidade recuperados deve estar entre 1 e 8.';
    END IF;
    IF NOT (p_qtd_pontos_vida_recupera BETWEEN 1 AND 10) THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: A quantidade de pontos de vida recuperados deve estar entre 1 e 10.';
    END IF;

    -- =================== INSERÇÃO ===================
    v_novo_item_id := public.gerar_id_item_de_cura();
    -- Insere na tabela pai 'itens'
    INSERT INTO public.itens(id, tipo, nome, descricao, valor)
        VALUES(v_novo_item_id, 'cura', p_nome, p_descricao, p_valor);
    -- Insere na tabela filha correta
    INSERT INTO public.curas (id, funcao, qts_usos, qtd_pontos_sanidade_recupera, qtd_pontos_vida_recupera)
        VALUES (v_novo_item_id, p_funcao, p_qts_usos, p_qtd_pontos_sanidade_recupera, p_qtd_pontos_vida_recupera);

    RETURN v_novo_item_id;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro na criação do item de cura: %', SQLERRM;      
        RAISE; -- Re-lança a exceção para que a transação seja desfeita.
END $$;

-- =================================================================================
--         4.4.  STORED PROCEDURE PARA ITENS CONSUMÍVEIS MÁGICOS
-- =================================================================================
CREATE FUNCTION public.sp_criar_item_magico(
    p_nome public.nome,
    p_descricao public.descricao,
    p_valor SMALLINT,

    -- Parâmetros específicos de item mágico
    p_funcao public.funcao_magica,
    p_qts_usos SMALLINT,
    p_custo_sanidade SMALLINT,
    p_id_feitico public.id_feitico
)
RETURNS public.id_item_magico
LANGUAGE plpgsql AS $$
DECLARE
    v_novo_item_id public.id_item_magico;
BEGIN
    SET LOCAL bd_cthulhu.inserir = 'true';
    -- =================== VALIDAÇÃO ===================
    IF p_funcao IS NULL THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: A função mágica não pode ser nula.';
    ELSIF p_qts_usos <= 0 THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: A quantidade de usos deve ser maior que zero.';
    ELSIF NOT (p_custo_sanidade BETWEEN 0 AND 15) THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: O custo de sanidade deve estar entre 1 e 10.';
    ELSIF p_id_feitico IS NULL THEN
        RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: O ID do feitiço não pode ser nulo.';
    END IF;

    -- =================== INSERÇÃO ===================
    v_novo_item_id := public.gerar_id_item_magico();
    -- Insere na tabela pai 'itens'
    INSERT INTO public.itens(id, tipo, nome, descricao, valor)
        VALUES(v_novo_item_id, 'magico', p_nome, p_descricao, p_valor);
    -- Insere na tabela filha correta
    INSERT INTO public.magicos (id, funcao, qts_usos, custo_sanidade, id_feitico)
        VALUES (v_novo_item_id, p_funcao, p_qts_usos, p_custo_sanidade, p_id_feitico); 

    RETURN v_novo_item_id;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro na criação do item mágico: %', SQLERRM;      
        RAISE; -- Re-lança a exceção para que a transação seja desfeita.
END $$;


/*
=================================================================================
        5. FUNÇÕES DE FEITIÇO (GERAL)
=================================================================================
*/
-- =========================================================================
--        5.1. FUNÇÕES, TRIGGERS E STORED PROCEDURES PARA FEITIÇOS
-- =========================================================================
CREATE OR REPLACE FUNCTION public.sp_criar_feitico(
    -- Parâmetros comuns
    p_nome public.nome,
    p_descricao public.descricao,
    p_qtd_pontos_de_magia SMALLINT,
    -- Parâmetros para o tipo de feitiço da tabela pai 'tipos_feitico'
    p_tipo_feitico public.funcao_feitico, -- status ou dano

    -- Parâmetros para feitiços de status
    p_status_buff_debuff BOOLEAN DEFAULT NULL,
    p_status_qtd_buff_debuff SMALLINT DEFAULT NULL,
    p_status_afetado public.tipo_de_status DEFAULT NULL,

    -- Parâmetros para feitiços de dano
    p_dano_tipo public.tipo_dano DEFAULT NULL,
    p_dano_qtd public.dano DEFAULT NULL
)
RETURNS public.id_feitico 
LANGUAGE plpgsql
AS $$
DECLARE
    v_novo_feitico_id INTEGER; 
BEGIN
    SET LOCAL bd_cthulhu.inserir_feitico = 'true';

    -- =================== VALIDAÇÃO e INSERT  ===================
    IF p_tipo_feitico = 'status' THEN
        IF p_status_buff_debuff IS NULL OR p_status_afetado IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Para feitiços de status, os campos "buff_debuff" e "status_afetado" são obrigatórios.';
        ELSIF p_status_buff_debuff = TRUE AND p_status_qtd_buff_debuff IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Feitiços que são buff/debuff devem ter um valor para "qtd_buff_debuff".';
        END IF;
        INSERT INTO public.feiticos_status (nome, descricao, qtd_pontos_de_magia, buff_debuff, qtd_buff_debuff, status_afetado) 
            VALUES (p_nome, p_descricao, p_qtd_pontos_de_magia, p_status_buff_debuff, p_status_qtd_buff_debuff, p_status_afetado)
        RETURNING id INTO v_novo_feitico_id; 
    ELSIF p_tipo_feitico = 'dano' THEN
        IF p_dano_tipo IS NULL OR p_dano_qtd IS NULL THEN
            RAISE EXCEPTION 'VIOLAÇÃO DE REGRA: Para feitiços de dano, os campos "tipo_dano" e "qtd_dano" são obrigatórios.';
        END IF;
        INSERT INTO public.feiticos_dano (nome, descricao, qtd_pontos_de_magia, tipo_dano, qtd_dano) 
            VALUES (p_nome, p_descricao, p_qtd_pontos_de_magia, p_dano_tipo, p_dano_qtd)
        RETURNING id INTO v_novo_feitico_id; 
    ELSE
        RAISE EXCEPTION 'Tipo de feitiço inválido: %. Use "status" ou "dano".', p_tipo_feitico;
    END IF;

    INSERT INTO public.tipos_feitico (id, tipo)
        VALUES (v_novo_feitico_id, p_tipo_feitico);

    RETURN v_novo_feitico_id;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro na criação do feitiço: %', SQLERRM;
        RAISE; 
END;
$$;

-- ----------------------------------------------------------------
-- Função/Trigger: Bloqueia inserções diretas nas tabelas de feitiços
-- ----------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.func_bloquear_insert_direto_feitico()
RETURNS TRIGGER AS $$
BEGIN
    IF current_setting('bd_cthulhu.inserir_feitico', true) IS DISTINCT FROM 'true' THEN
        RAISE EXCEPTION '[PERMISSION DENIED] Utilize a Stored Procedure "sp_criar_feitico" para criar feitiços.';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger para bloquear inserção direta na tabela 'tipos_feitico'
CREATE TRIGGER trigger_bloqueia_insert_tipos_feitico
    BEFORE INSERT ON public.tipos_feitico
    FOR EACH ROW EXECUTE FUNCTION public.func_bloquear_insert_direto_feitico();

CREATE TRIGGER trigger_bloqueia_insert_magicos
    BEFORE INSERT ON public.magicos 
    FOR EACH ROW 
    EXECUTE FUNCTION public.func_bloquear_insert_direto_itens();
-- =================================================================================
--    6. STORED PROCEDURE PARA RESPAWN DE MONSTROS E ITENS (LUA DE SANGUE)
--=================================================================================

CREATE FUNCTION public.lua_de_sangue()
RETURNS VOID 
LANGUAGE plpgsql
AS $$
BEGIN
    -- Primeiro passo: Respawn de Monstros
    -- Atualiza os monstros que não estão em um local (monstros mortos tem id_local = NULL)
    UPDATE public.instancias_monstros im
    SET
        id_local = im.id_local_de_spawn,
        vida = COALESCE(
            (SELECT a.vida_total FROM public.agressivos a WHERE a.id = im.id_monstro),
            (SELECT p.vida_total FROM public.pacificos p WHERE p.id = im.id_monstro)
        )
    WHERE im.id_local IS NULL;

    -- Segundo passo: Respawn de itens
    -- Adiciona os itens para os seus locais de origem
    UPDATE public.instancias_de_itens ii
    SET
        id_local = ii.id_local_de_spawn,
        durabilidade = ii.durabilidade_total
    WHERE ii.id_local IS NULL
    AND NOT EXISTS ( -- Para não duplicar itens que estão no inventário do jogador
        SELECT 1
        FROM public.inventarios_possuem_instancias_item ipii 
        WHERE ipii.id_instancias_de_item = ii.id
    );


    RAISE NOTICE 'Uma lua de sangue está ocorrendo. Monstros que foram derrotados voltam para vingar sua morte. Itens que já foram coletados podem ser encontrados novamente';

END;
$$;

--===============================================================================
--        7. STORED PROCEDURE PARA VASCULHAR A SALA EM BUSCA DE ITENS 
--===============================================================================

CREATE FUNCTION public.sp_vasculhar_local(
    p_local_id public.id_local
)
RETURNS TABLE (
    instancia_item_id public.id_instancia_de_item,
    durabilidade SMALLINT,
    durabilidade_total SMALLINT,
    item_base_id public.id_item,
    item_nome public.nome,
    item_descricao public.descricao,
    item_tipo public.tipo_item,
    item_valor SMALLINT
)
LANGUAGE plpgsql AS $$
BEGIN

    /*
    Pesquisa os itens presentes em uma sala fazendo uma junção das informações do item: informações
    básicas, presentes na tabela item, e informações da instância presente na tabela da instância,
    para garantir que somente os itens que estejam naquela sala apareçam é feito um 
    WHERE que pega o local que o jogador está, com esse id ele faz a seleção dos itens que estão
    neste id
    */

    RETURN QUERY
    SELECT
        iii.id AS instancia_item_id,
        iii.durabilidade,
        iii.durabilidade_total,
        it.id AS item_base_id,
        it.nome AS item_nome,
        it.descricao AS item_descricao,
        it.tipo AS item_tipo,
        it.valor AS item_valor
    FROM 
        public.instancias_de_itens iii
    JOIN
        public.itens it ON iii.id_item = it.id
    WHERE 
        iii.id_local = p_local_id;

    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE 'Ocorreu um erro ao vasculhar o local %: %', p_local_id, SQLERRM;
            RETURN;
END;
$$;

--===============================================================================
--        8. STORED PROCEDURE PARA ADICIONAR ITEM EM INVENTÁRIO
--===============================================================================

CREATE FUNCTION public.sp_adicionar_item_ao_inventario(
    p_jogador_id public.id_personagem,
    p_instancia_item_id public.id_instancia_de_item
)
RETURNS BOOLEAN
LANGUAGE plpgsql AS $$
DECLARE
    v_id_inventario public.id_inventario;
    v_local_atual_item public.id_local;
BEGIN
    -- Verifica se o jogador existe e obtém o ID do inventário dele
    SELECT id_inventario INTO v_id_inventario
    FROM public.personagens_jogaveis
    WHERE id = p_jogador_id;

    IF v_id_inventario IS NULL THEN
        RAISE EXCEPTION 'Jogador com ID % nao encontrado ou nao possui inventario.', p_jogador_id;
    END IF;

    -- Verifica o local atual da instancia do item
    SELECT id_local INTO v_local_atual_item
    FROM public.instancias_de_itens
    WHERE id = p_instancia_item_id;

    IF v_local_atual_item IS NULL THEN
        -- O item ja foi pego ou nao esta em nenhum local
        RAISE NOTICE 'Item % ja foi pego ou nao esta em nenhum local.', p_instancia_item_id;
        RETURN FALSE;
    END IF;

    -- Inicia uma transação para garantir que ambas as operações sejam atômicas
    BEGIN
        -- 1. Remove o item do local (coloca o id_local do item como NULL)
        UPDATE public.instancias_de_itens
        SET id_local = NULL
        WHERE id = p_instancia_item_id;

        -- 2. Adiciona o item à tabela de junção inventarios_possuem_instancias_item
        INSERT INTO public.inventarios_possuem_instancias_item (id_instancias_de_item, id_inventario)
        VALUES (p_instancia_item_id, v_id_inventario);

        RETURN TRUE; -- Sucesso, o item foi adicionado ao inventário do jogador
    END;

EXCEPTION
    WHEN unique_violation THEN
        RAISE NOTICE 'Item % ja esta no inventario do jogador %.', p_instancia_item_id, p_jogador_id;
        RETURN TRUE; -- Consideramos sucesso se já está no inventário
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao adicionar o item % ao inventario do jogador %: %', p_instancia_item_id, p_jogador_id, SQLERRM;
        RETURN FALSE; -- Falha
END;
$$;

--===============================================================================
--        9. STORED PROCEDURE PARA VER O INVENTÁRIO
--===============================================================================

    CREATE OR REPLACE FUNCTION public.sp_ver_inventario(
        p_jogador_id public.id_personagem
    )
    RETURNS TABLE (
        instancia_item_id public.id_instancia_de_item,
        item_nome public.nome,
        item_descricao public.descricao,
        durabilidade SMALLINT,
        durabilidade_total SMALLINT,
        item_tipo public.tipo_item,
        item_valor SMALLINT,
        dano public.dano,
        qtd_dano_mitigado SMALLINT,
        qtd_atributo_recebe SMALLINT,
        tipo_atributo_recebe public.tipo_atributo_personagem,
        esta_equipado BOOLEAN 
)
LANGUAGE plpgsql AS $$
DECLARE
    v_id_inventario public.id_inventario;
BEGIN
    SELECT id_inventario INTO v_id_inventario
    FROM public.personagens_jogaveis
    WHERE id = p_jogador_id;

    IF v_id_inventario IS NULL THEN
        RAISE NOTICE 'Jogador com ID % nao encontrado ou nao possui um inventario associado.', p_jogador_id;
        RETURN;
    END IF;

    RETURN QUERY
    SELECT
        ipii.id_instancias_de_item,
        it.nome,
        it.descricao,
        iii.durabilidade,
        iii.durabilidade_total,
        it.tipo,
        it.valor,
        a.dano,
        ar.qtd_dano_mitigado, 
        ar.qtd_atributo_recebe,
        ar.tipo_atributo_recebe,
        -- Lógica para verificar se o item está equipado
        CASE
            WHEN ipii.id_instancias_de_item = (SELECT pj.id_arma FROM public.personagens_jogaveis pj WHERE pj.id = p_jogador_id)
              OR ipii.id_instancias_de_item = (SELECT pj.id_armadura FROM public.personagens_jogaveis pj WHERE pj.id = p_jogador_id)
            THEN TRUE
            ELSE FALSE
        END AS esta_equipado
    FROM
        public.inventarios_possuem_instancias_item ipii
    JOIN
        public.instancias_de_itens iii ON ipii.id_instancias_de_item = iii.id
    JOIN
        public.itens it ON iii.id_item = it.id
    LEFT JOIN
        public.armas a ON it.id = a.id
    LEFT JOIN
        public.armaduras ar ON it.id = ar.id
    WHERE
        ipii.id_inventario = v_id_inventario;

EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao verificar o inventario do jogador %: %', p_jogador_id, SQLERRM;
        RETURN;
END;
$$;

--===============================================================================
--        10. STORED PROCEDURE PARA ENCONTRAR MONSTROS NO LOCAL
--===============================================================================

CREATE OR REPLACE FUNCTION public.sp_encontrar_monstros_no_local(
    p_local_id public.id_local,
    p_id_missao_ativa public.id_missao DEFAULT NULL
)
RETURNS TABLE (
    instancia_monstro_id public.id_instancia_de_monstro,
    monstro_base_id public.id_monstro,
    monstro_nome public.nome,
    monstro_descricao public.descricao,
    monstro_tipo public.tipo_monstro,
    vida_atual SMALLINT,
    vida_total SMALLINT,
    defesa SMALLINT
)
LANGUAGE plpgsql AS $$
BEGIN
    RETURN QUERY
    -- Primeiro, busca todos os monstros AGRESSIVOS no local
    SELECT
        im.id AS instancia_monstro_id,
        im.id_monstro AS monstro_base_id,
        COALESCE(a.nome, p.nome) AS monstro_nome,
        COALESCE(a.descricao, p.descricao) AS monstro_descricao,
        tm.tipo AS monstro_tipo,
        im.vida AS vida_atual,
        COALESCE(a.vida_total, p.vida_total) AS vida_total,
        COALESCE(a.defesa, p.defesa) AS defesa
    FROM
        public.instancias_monstros im
    JOIN
        public.tipos_monstro tm ON im.id_monstro = tm.id
    LEFT JOIN
        public.agressivos a ON im.id_monstro = a.id AND tm.tipo = 'agressivo'
    LEFT JOIN
        public.pacificos p ON im.id_monstro = p.id AND tm.tipo = 'pacífico'
    WHERE
        im.id_local = p_local_id
        AND (
            im.id_missao_vinculada IS NULL OR -- O monstro é visível para todos
            im.id_missao_vinculada = p_id_missao_ativa -- O monstro só é visível se o jogador estiver na missão correta
        );

EXCEPTION
    WHEN OTHERS THEN
        -- Este bloco ajuda a capturar erros inesperados durante a execução
        RAISE NOTICE 'Ocorreu um erro ao encontrar monstros no local %: %', p_local_id, SQLERRM;
        RETURN;
END;
$$;

--===============================================================================
--        11. STORED PROCEDURE PARA MATAR TODOS OS MONSTROS DO LOCAL
--===============================================================================

-- Somente para testes do banco

CREATE FUNCTION public.sp_matar_monstros_no_local(
    p_local_id public.id_local
)
RETURNS INTEGER -- Retorna o número de monstros "mortos"
LANGUAGE plpgsql AS $$
DECLARE
    v_monstros_mortos INTEGER := 0;
BEGIN
    -- Atualiza o id_local dos monstros para NULL e coloca a vida deles como 0
    UPDATE public.instancias_monstros im
    SET
        id_local = NULL, -- Remove da sala para que possam ser "respawnados" pela Lua de Sangue
        vida = 0
    WHERE im.id_local = p_local_id
    RETURNING 1 INTO v_monstros_mortos; -- Conta as linhas afetadas (monstros mortos)

    -- Se nenhum monstro foi encontrado, v_monstros_mortos sera 0, pois RETURNING 1 INTO
    -- incrementa a variavel para cada linha afetada.

    RAISE NOTICE 'Monstros no local % foram derrotados. Total: %', p_local_id, v_monstros_mortos;

    RETURN v_monstros_mortos;

EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao tentar matar monstros no local %: %', p_local_id, SQLERRM;
        RETURN -1; -- Retorna -1 para indicar um erro
END;
$$;

--===============================================================================
--        12. STORED PROCEDURE PARA MOVIMENTAR OS MONSTROS AUTOMATICAMENTE
--===============================================================================

/*
A stored procedure sp_movimentar_monstros seleciona um vetor com todos os locais do tipo sala
e itera sobre cada instância de monstro mudando o seu local para um dos locais do 
vetor de salas
*/

CREATE FUNCTION public.sp_movimentar_monstros()
RETURNS VOID AS $$
DECLARE
    r_monster RECORD;
    v_new_local_id public.id_local;
    v_sala_ids public.id_local[];
BEGIN
    -- Obter todos os IDs de locais que são 'Sala'
    SELECT ARRAY(SELECT id FROM public.local WHERE tipo_local = 'Sala') INTO v_sala_ids;

    IF v_sala_ids IS NULL OR array_length(v_sala_ids, 1) = 0 THEN
        RAISE NOTICE 'Nenhuma sala encontrada para movimentar monstros.';
        RETURN;
    END IF;

    -- Iterar sobre cada instância de monstro que NÃO é essencial para a história
    FOR r_monster IN SELECT id FROM public.instancias_monstros WHERE is_essencial_historia = FALSE LOOP
        -- Selecionar um ID de sala aleatoriamente
        v_new_local_id := v_sala_ids[floor(random() * array_length(v_sala_ids, 1)) + 1];

        -- Atualizar a localização do monstro
        UPDATE public.instancias_monstros
        SET id_local = v_new_local_id
        WHERE id = r_monster.id;
    END LOOP;

    RAISE NOTICE 'Monstros movimentados para novas salas.';
END;
$$ LANGUAGE plpgsql;/*
=================================================================================
        12. LÓGICA PARA BATALHAS
=================================================================================
*/

-- ---------------------------------------------------------------------------------
--  STORED PROCEDURE: Executa uma batalha completa entre um jogador e um monstro.
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_executar_batalha(
    p_id_jogador public.id_personagem_jogavel,
    p_id_instancia_monstro public.id_instancia_de_monstro
)
RETURNS TEXT -- Retorna uma mensagem com o resultado da batalha
LANGUAGE plpgsql AS $$
DECLARE
    -- Variáveis para os status do Jogador
    v_vida_jogador SMALLINT;
    v_dano_jogador SMALLINT;
    v_defesa_jogador SMALLINT;
    v_id_inventario public.id_inventario;

    -- Variáveis para os status do Monstro
    v_id_monstro_base public.id_monstro;
    v_vida_monstro SMALLINT;
    v_dano_monstro SMALLINT;
    v_defesa_monstro SMALLINT;
    v_nome_monstro public.nome;
    v_item_drop_id public.id_instancia_de_item;

    -- Variável de resultado
    v_resultado TEXT;
BEGIN
    -- 1. BUSCAR DADOS DO JOGADOR
    SELECT
        pj.pontos_de_vida_atual,
        COALESCE(a.dano, 1), 
        COALESCE(ar.qtd_dano_mitigado, 0),
        pj.id_inventario
    INTO
        v_vida_jogador, v_dano_jogador, v_defesa_jogador, v_id_inventario
    FROM public.personagens_jogaveis pj
    LEFT JOIN public.instancias_de_itens ii_arma ON pj.id_arma = ii_arma.id
    LEFT JOIN public.armas a ON ii_arma.id_item = a.id
    LEFT JOIN public.instancias_de_itens ii_armadura ON pj.id_armadura = ii_armadura.id
    LEFT JOIN public.armaduras ar ON ii_armadura.id_item = ar.id
    WHERE pj.id = p_id_jogador;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Jogador com ID % não encontrado.', p_id_jogador;
    END IF;

    -- 2. BUSCAR DADOS DO MONSTRO
    -- CORREÇÃO: A query agora busca im.vida para obter a vida atual da instância.
    SELECT
        im.id_monstro,
        im.id_instancia_de_item,
        COALESCE(ag.nome, pa.nome),
        im.vida, 
        ag.dano,
        COALESCE(ag.defesa, pa.defesa, 0)
    INTO
        v_id_monstro_base, v_item_drop_id, v_nome_monstro, v_vida_monstro, v_dano_monstro, v_defesa_monstro
    FROM public.instancias_monstros im
    JOIN public.tipos_monstro tm ON im.id_monstro = tm.id
    LEFT JOIN public.agressivos ag ON im.id_monstro = ag.id AND tm.tipo = 'agressivo'
    LEFT JOIN public.pacificos pa ON im.id_monstro = pa.id AND tm.tipo = 'pacífico'
    WHERE im.id = p_id_instancia_monstro AND tm.tipo = 'agressivo';

    IF NOT FOUND THEN
        RAISE EXCEPTION 'Instância de monstro agressivo com ID % não encontrada.', p_id_instancia_monstro;
    END IF;

    RAISE NOTICE 'Batalha iniciada! Jogador (Vida: %) vs % (Vida: %)', v_vida_jogador, v_nome_monstro, v_vida_monstro;

    -- 3. LOOP DE COMBATE
    WHILE v_vida_jogador > 0 AND v_vida_monstro > 0 LOOP
        v_vida_monstro := v_vida_monstro - GREATEST(1, v_dano_jogador - v_defesa_monstro);
        RAISE NOTICE 'Jogador ataca! Vida do monstro: %', v_vida_monstro;
        IF v_vida_monstro <= 0 THEN EXIT; END IF;

        v_vida_jogador := v_vida_jogador - GREATEST(1, v_dano_monstro - v_defesa_jogador);
        RAISE NOTICE 'Monstro ataca! Vida do jogador: %', v_vida_jogador;
    END LOOP;

    -- 4. DETERMINAR RESULTADO
    IF v_vida_monstro <= 0 THEN
        v_resultado := 'VITÓRIA! O monstro ' || v_nome_monstro || ' foi derrotado.';
        RAISE NOTICE '%', v_resultado;
        UPDATE public.instancias_monstros SET id_local = NULL, vida = 0 WHERE id = p_id_instancia_monstro;

        IF v_item_drop_id IS NOT NULL THEN
            INSERT INTO public.inventarios_possuem_instancias_item (id_instancias_de_item, id_inventario) VALUES (v_item_drop_id, v_id_inventario) ON CONFLICT DO NOTHING;
            UPDATE public.instancias_de_itens SET id_local = NULL WHERE id = v_item_drop_id;
            v_resultado := v_resultado || ' Você recebeu um item como recompensa!';
            RAISE NOTICE 'O jogador recebeu o item de ID %.', v_item_drop_id;
        END IF;

    ELSE
        v_resultado := 'DERROTA! Você foi vencido pelo ' || v_nome_monstro || '.';
        RAISE NOTICE '%', v_resultado;
        UPDATE public.personagens_jogaveis SET pontos_de_vida_atual = 0 WHERE id = p_id_jogador;
    END IF;

    RETURN v_resultado;
END;
$$;


/*
=================================================================================
        13. LÓGICA PARA CONCLUSÃO DE MISSÕES
=================================================================================
*/

-- ---------------------------------------------------------------------------------
--  STORED PROCEDURE: Finaliza uma missão e entrega a recompensa ao jogador.
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_entregar_missao(
    p_id_jogador public.id_personagem_jogavel,
    p_id_missao public.id_missao
)
RETURNS TEXT -- Retorna uma mensagem de sucesso
LANGUAGE plpgsql AS $$
DECLARE
    v_id_recompensa public.id_instancia_de_item;
    v_nome_recompensa public.nome;
    v_id_inventario public.id_inventario;
    v_id_npc_missao public.id_personagem_npc;
BEGIN
    -- 1. VERIFICAR SE A MISSÃO EXISTE E OBTER O NPC ASSOCIADO
    SELECT id_npc INTO v_id_npc_missao FROM public.missoes WHERE id = p_id_missao;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Missão com ID % não encontrada.', p_id_missao;
    END IF;

    -- 2. ENCONTRAR O ITEM DE RECOMPENSA PARA A MISSÃO
    SELECT ii.id, i.nome
    INTO v_id_recompensa, v_nome_recompensa
    FROM public.instancias_de_itens ii
    JOIN public.itens i ON ii.id_item = i.id
    WHERE ii.id_missao_recompensa = p_id_missao;

    -- 3. SE HOUVER RECOMPENSA, ADICIONAR AO INVENTÁRIO DO JOGADOR
    IF FOUND THEN
        -- Obter o inventário do jogador
        SELECT id_inventario INTO v_id_inventario FROM public.personagens_jogaveis WHERE id = p_id_jogador;

        -- Adicionar o item de recompensa à tabela de junção do inventário
        INSERT INTO public.inventarios_possuem_instancias_item (id_instancias_de_item, id_inventario)
        VALUES (v_id_recompensa, v_id_inventario);

        -- Remover o item do mapa e desassociar da missão (para não ser pego de novo)
        UPDATE public.instancias_de_itens
        SET id_local = NULL, id_missao_recompensa = NULL
        WHERE id = v_id_recompensa;

        RAISE NOTICE 'O jogador % completou a missão % e recebeu a recompensa: %.', p_id_jogador, p_id_missao, v_nome_recompensa;
    ELSE
        RAISE NOTICE 'O jogador % completou a missão %, mas não havia recompensa em item associada.', p_id_jogador, p_id_missao;
    END IF;

    -- 4. REGISTRAR A CONCLUSÃO DA MISSÃO NA TABELA 'entregas_missoes'
    -- Isso previne que a mesma missão seja entregue novamente.
    INSERT INTO public.entregas_missoes (id_jogador, id_npc)
    VALUES (p_id_jogador, v_id_npc_missao)
    ON CONFLICT (id_jogador, id_npc) DO NOTHING; -- Não faz nada se o jogador já entregou uma missão para este NPC

    RETURN 'Missão concluída com sucesso!';
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao entregar a missão: %', SQLERRM;
        RAISE;
END;
$$;

/*
=================================================================================
        14. LÓGICA PARA AÇÕES DO JOGADOR E MANUTENÇÃO DO JOGO
=================================================================================
*/
-- ---------------------------------------------------------------------------------
--  14.1 STORED PROCEDURE: Mover o jogador para um novo local
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_mover_jogador(
    p_id_jogador public.id_personagem_jogavel,
    p_id_novo_local public.id_local
)
RETURNS TEXT -- Retorna uma mensagem de confirmação
LANGUAGE plpgsql AS $$
DECLARE
    v_local_atual public.id_local;
    v_pode_mover BOOLEAN := FALSE;
BEGIN
    -- 1. Verifica a localização atual do jogador
    SELECT id_local INTO v_local_atual FROM public.personagens_jogaveis WHERE id = p_id_jogador;

    IF v_local_atual IS NULL THEN
        RAISE EXCEPTION 'Jogador % não está em um local válido.', p_id_jogador;
    END IF;

    -- 2. Verifica se o novo local é adjacente ao local atual
    SELECT TRUE INTO v_pode_mover
    FROM public.local
    WHERE id = v_local_atual AND p_id_novo_local IN (
        local_norte, local_sul, local_leste, local_oeste,
        local_nordeste, local_noroeste, local_sudeste, local_sudoeste,
        local_cima, local_baixo
    );

    IF v_pode_mover IS NOT TRUE THEN
        RAISE EXCEPTION 'Movimento inválido. O local % não é adjacente ao local atual %.', p_id_novo_local, v_local_atual;
    END IF;

    -- 3. Atualiza a localização do jogador
    UPDATE public.personagens_jogaveis
    SET id_local = p_id_novo_local
    WHERE id = p_id_jogador;

    RETURN 'Jogador movido para o local ' || p_id_novo_local || '.';
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao mover o jogador: %', SQLERRM;
        RAISE;
END;
$$;

-- ---------------------------------------------------------------------------------
--  14.2 STORED PROCEDURE: Desequipar uma arma ou armadura
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_desequipar_item(
    p_id_jogador public.id_personagem_jogavel,
    p_tipo_slot public.tipo_item -- 'arma' ou 'armadura'
)
RETURNS TEXT
LANGUAGE plpgsql AS $$
DECLARE
    v_id_instancia_item public.id_instancia_de_item;
    v_armadura_info RECORD;
    v_update_query TEXT;
BEGIN
    -- 1. Identifica qual item está no slot especificado
    IF p_tipo_slot = 'armadura' THEN
        SELECT id_armadura INTO v_id_instancia_item FROM public.personagens_jogaveis WHERE id = p_id_jogador;
    ELSIF p_tipo_slot = 'arma' THEN
        SELECT id_arma INTO v_id_instancia_item FROM public.personagens_jogaveis WHERE id = p_id_jogador;
    ELSE
        RAISE EXCEPTION 'Tipo de slot inválido: %. Use ''arma'' ou ''armadura''.', p_tipo_slot;
    END IF;

    -- 2. Se não houver item no slot, não faz nada
    IF v_id_instancia_item IS NULL THEN
        RETURN 'Nenhum item do tipo ' || p_tipo_slot || ' para desequipar.';
    END IF;

    -- 3. Se for uma armadura, reverte o bônus de atributo
    IF p_tipo_slot = 'armadura' THEN
        -- Busca as informações da armadura para saber qual atributo reverter
        SELECT ar.qtd_atributo_recebe, ar.tipo_atributo_recebe
        INTO v_armadura_info
        FROM public.armaduras ar
        JOIN public.instancias_de_itens ii ON ar.id = ii.id_item
        WHERE ii.id = v_id_instancia_item;

        -- Se a armadura concede um bônus, constrói e executa o UPDATE para removê-lo
        IF v_armadura_info.qtd_atributo_recebe IS NOT NULL AND v_armadura_info.tipo_atributo_recebe IS NOT NULL THEN
            v_update_query := format(
                'UPDATE public.personagens_jogaveis SET %I = %I - %s WHERE id = %s',
                v_armadura_info.tipo_atributo_recebe, -- Nome da coluna do atributo
                v_armadura_info.tipo_atributo_recebe, -- Nome da coluna novamente
                v_armadura_info.qtd_atributo_recebe,  -- Valor a subtrair
                p_id_jogador                          -- ID do jogador
            );
            EXECUTE v_update_query;
        END IF;
    END IF;

    -- 4. Limpa o slot do item na tabela do jogador
    IF p_tipo_slot = 'armadura' THEN
        UPDATE public.personagens_jogaveis SET id_armadura = NULL WHERE id = p_id_jogador;
    ELSIF p_tipo_slot = 'arma' THEN
        UPDATE public.personagens_jogaveis SET id_arma = NULL WHERE id = p_id_jogador;
    END IF;

    RETURN 'Item ' || p_tipo_slot || ' desequipado com sucesso.';
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao desequipar o item: %', SQLERRM;
        RAISE;
END;
$$;


-- ---------------------------------------------------------------------------------
--  14.3 STORED PROCEDURE: Equipar uma arma ou armadura
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_equipar_item(
    p_id_jogador public.id_personagem_jogavel,
    p_id_instancia_item public.id_instancia_de_item
)
RETURNS TEXT
LANGUAGE plpgsql AS $$
DECLARE
    v_item_info RECORD;
    v_id_inventario public.id_inventario;
    v_update_query TEXT;
BEGIN
    -- 1. Verifica se o item está no inventário do jogador
    SELECT pj.id_inventario INTO v_id_inventario FROM public.personagens_jogaveis pj WHERE pj.id = p_id_jogador;
    IF NOT EXISTS (
        SELECT 1 FROM public.inventarios_possuem_instancias_item
        WHERE id_inventario = v_id_inventario AND id_instancias_de_item = p_id_instancia_item
    ) THEN
        RAISE EXCEPTION 'O item % não está no inventário do jogador.', p_id_instancia_item;
    END IF;

    -- 2. Descobre o tipo do item e suas propriedades
    SELECT i.id, i.tipo, ar.qtd_atributo_recebe, ar.tipo_atributo_recebe
    INTO v_item_info
    FROM public.instancias_de_itens ii
    JOIN public.itens i ON ii.id_item = i.id
    LEFT JOIN public.armaduras ar ON i.id = ar.id 
    WHERE ii.id = p_id_instancia_item;

    -- 3. Desequipa qualquer item que já esteja no slot correspondente
    IF v_item_info.tipo = 'arma' THEN
        PERFORM public.sp_desequipar_item(p_id_jogador, 'arma');
    ELSIF v_item_info.tipo = 'armadura' THEN
        PERFORM public.sp_desequipar_item(p_id_jogador, 'armadura');
    ELSE
        RAISE EXCEPTION 'O item % não é um equipamento (arma ou armadura).', p_id_instancia_item;
    END IF;

    -- 4. Aplica os bônus do novo item (se for uma armadura com bônus)
    IF v_item_info.tipo = 'armadura' AND v_item_info.qtd_atributo_recebe IS NOT NULL AND v_item_info.tipo_atributo_recebe IS NOT NULL THEN
        v_update_query := format(
            'UPDATE public.personagens_jogaveis SET %I = %I + %s WHERE id = %s',
            v_item_info.tipo_atributo_recebe, -- Nome da coluna
            v_item_info.tipo_atributo_recebe, -- Nome da coluna novamente
            v_item_info.qtd_atributo_recebe,  -- Valor a adicionar
            p_id_jogador                      -- ID do jogador
        );
        EXECUTE v_update_query;
    END IF;

    -- 5. Equipa o novo item no slot correspondente
    IF v_item_info.tipo = 'arma' THEN
        UPDATE public.personagens_jogaveis SET id_arma = p_id_instancia_item WHERE id = p_id_jogador;
        RETURN 'Arma equipada com sucesso.';
    ELSIF v_item_info.tipo = 'armadura' THEN
        UPDATE public.personagens_jogaveis SET id_armadura = p_id_instancia_item WHERE id = p_id_jogador;
        RETURN 'Armadura equipada com sucesso.';
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao equipar o item: %', SQLERRM;
        RAISE;
END;
$$;


-- ---------------------------------------------------------------------------------
--  14.4 STORED PROCEDURE: Usar um item de cura
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_usar_item_cura(
    p_id_jogador public.id_personagem_jogavel,
    p_id_instancia_item public.id_instancia_de_item
)
RETURNS TEXT
LANGUAGE plpgsql AS $$
DECLARE
    v_item_cura RECORD;
    v_jogador RECORD;
    v_vida_max SMALLINT;
    v_sanidade_max SMALLINT;
BEGIN
    -- 1. Verifica se o item é de cura e está no inventário do jogador
    SELECT c.* INTO v_item_cura
    FROM public.curas c
    JOIN public.instancias_de_itens ii ON c.id = ii.id_item
    JOIN public.inventarios_possuem_instancias_item ipi ON ii.id = ipi.id_instancias_de_item
    JOIN public.personagens_jogaveis pj ON ipi.id_inventario = pj.id_inventario
    WHERE pj.id = p_id_jogador AND ii.id = p_id_instancia_item;

    IF NOT FOUND THEN
        RAISE EXCEPTION 'O item de cura % não foi encontrado no inventário do jogador %.', p_id_instancia_item, p_id_jogador;
    END IF;

    -- 2. Aplica os efeitos de cura
    SELECT *, public.calcular_pts_de_vida(constituicao, tamanho) as vida_max, public.calcular_sanidade(poder) as sanidade_max
    INTO v_jogador
    FROM public.personagens_jogaveis WHERE id = p_id_jogador;

    UPDATE public.personagens_jogaveis
    SET
        pontos_de_vida_atual = LEAST(v_jogador.vida_max, pontos_de_vida_atual + v_item_cura.qtd_pontos_vida_recupera),
        sanidade_atual = LEAST(v_jogador.sanidade_max, sanidade_atual + v_item_cura.qtd_pontos_sanidade_recupera)
    WHERE id = p_id_jogador;

    -- 3. Consome um uso do item
    UPDATE public.curas SET qts_usos = qts_usos - 1 WHERE id = v_item_cura.id;

    RETURN 'Você usou ' || (SELECT nome FROM public.itens WHERE id = v_item_cura.id) || ' e se sente melhor.';
END;
$$;

-- ---------------------------------------------------------------------------------
--  14.5 TRIGGER: Gerencia a durabilidade e quebra de itens
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.func_gerenciar_durabilidade_item()
RETURNS TRIGGER AS $$
BEGIN
    -- Se a durabilidade de um item chegar a 0 ou menos, ele "quebra".
    IF NEW.durabilidade <= 0 THEN
        RAISE NOTICE 'ATENÇÃO: O item de ID % quebrou!', NEW.id;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger para armas
CREATE TRIGGER trigger_checar_durabilidade_arma
    BEFORE UPDATE ON public.armas
    FOR EACH ROW
    WHEN (OLD.durabilidade IS DISTINCT FROM NEW.durabilidade)
    EXECUTE FUNCTION public.func_gerenciar_durabilidade_item();

-- Trigger para armaduras
CREATE TRIGGER trigger_checar_durabilidade_armadura
    BEFORE UPDATE ON public.armaduras
    FOR EACH ROW
    WHEN (OLD.durabilidade IS DISTINCT FROM NEW.durabilidade)
    EXECUTE FUNCTION public.func_gerenciar_durabilidade_item();

-- Trigger para itens de cura (usos)
CREATE TRIGGER trigger_checar_usos_cura
    BEFORE UPDATE ON public.curas
    FOR EACH ROW
    WHEN (OLD.qts_usos IS DISTINCT FROM NEW.qts_usos)
    EXECUTE FUNCTION public.func_gerenciar_durabilidade_item(); -- Reutilizando a lógica para usos

/*
=================================================================================
        15. FUNÇÕES DE TRANSAÇÃO (COMPRA/TRANSFERÊNCIA)
=================================================================================
*/

-- =================================================================================
--         15.1.  STORED PROCEDURE PARA TRANSFERIR ITEM DO NPC PARA O PJ
-- =================================================================================

-- Cole isso no seu arquivo de Triggers/Procedures e execute no banco
CREATE OR REPLACE FUNCTION public.sp_comprar_item_do_npc(
    p_id_personagem_jogavel public.id_personagem_jogavel,
    p_id_npc public.id_personagem_npc,
    p_id_instancia_item public.id_instancia_de_item,
    p_valor_pago SMALLINT
)
RETURNS TEXT
LANGUAGE plpgsql AS $$
DECLARE
    v_id_inventario_npc public.id_inventario;
    v_id_inventario_pj public.id_inventario;
    v_valor_item SMALLINT;
    v_ouro_jogador INTEGER;
BEGIN
    -- Validações (Jogador, NPC, Inventários)
    SELECT id_inventario, ouro INTO v_id_inventario_pj, v_ouro_jogador FROM public.personagens_jogaveis WHERE id = p_id_personagem_jogavel;
    IF NOT FOUND THEN RAISE EXCEPTION 'COMPRA FALHOU: Personagem Jogável com ID % não encontrado.', p_id_personagem_jogavel; END IF;

    SELECT id_inventario INTO v_id_inventario_npc FROM public.npcs WHERE id = p_id_npc;
    IF NOT FOUND THEN RAISE EXCEPTION 'COMPRA FALHOU: NPC com ID % não encontrado.', p_id_npc; END IF;
    IF v_id_inventario_npc IS NULL THEN RAISE EXCEPTION 'COMPRA FALHOU: O NPC não possui um inventário.'; END IF;

    -- Validações do Item
    IF NOT EXISTS (SELECT 1 FROM public.inventarios_possuem_instancias_item WHERE id_inventario = v_id_inventario_npc AND id_instancias_de_item = p_id_instancia_item) THEN
        RAISE EXCEPTION 'COMPRA FALHOU: O NPC não possui o item especificado.';
    END IF;

    SELECT i.valor INTO v_valor_item FROM public.itens i JOIN public.instancias_de_itens ii ON i.id = ii.id_item WHERE ii.id = p_id_instancia_item;

    -- Validações de Negócio (Ouro e Espaço)
    IF v_ouro_jogador < v_valor_item THEN RAISE EXCEPTION 'COMPRA FALHOU: Ouro insuficiente. Você tem %, mas o item custa %.', v_ouro_jogador, v_valor_item; END IF;
    -- (Adicionar verificação de espaço no inventário se necessário)

    -- === Execução da Transação ===
    -- 1. Remove o item do inventário do NPC
    DELETE FROM public.inventarios_possuem_instancias_item WHERE id_inventario = v_id_inventario_npc AND id_instancias_de_item = p_id_instancia_item;

    -- 2. Adiciona o item ao inventário do PJ
    INSERT INTO public.inventarios_possuem_instancias_item (id_inventario, id_instancias_de_item) VALUES (v_id_inventario_pj, p_id_instancia_item);

    -- 3. Deduz o ouro do jogador (NOVO)
    UPDATE public.personagens_jogaveis SET ouro = ouro - v_valor_item WHERE id = p_id_personagem_jogavel;

    RETURN 'Item comprado com sucesso!';
EXCEPTION
    WHEN OTHERS THEN
        RETURN SQLERRM; -- Retorna a mensagem de erro do banco
END;
$$;
--===============================================================================
--        10. STORED PROCEDURE PARA ENCONTRAR VENDEDOR NO LOCAL
--===============================================================================

CREATE OR REPLACE FUNCTION public.sp_encontrar_vendedor_no_local(p_id_local public.id_local)
RETURNS TABLE (
    id_personagem_npc public.id_personagem_npc,
    nome public.nome,
    ocupacao public.ocupacao,
    idade public.idade,
    sexo public.sexo,
    residencia public.residencia,
    local_nascimento public.local_nascimento,
    id_local public.id_local,
    id_inventario public.id_inventario,
    script_dialogo public.script_dialogo
)
LANGUAGE plpgsql AS $$
BEGIN
    -- Retorna todos os Vendedores que estão atualmente no local especificado, incluindo um de seus diálogos.
    RETURN QUERY
    SELECT
        n.id,
        n.nome,
        n.ocupacao,
        n.idade,
        n.sexo,
        n.residencia,
        n.local_nascimento,
        n.id_local,
        n.id_inventario,
        (SELECT d.script_dialogo FROM public.dialogos d WHERE d.npc_id = n.id LIMIT 1)
    FROM
        public.npcs AS n
    WHERE
        -- Filtra para buscar apenas NPCs no local especificado que sejam vendedores.
        n.id_local = p_id_local AND n.ocupacao LIKE 'Vendedor%';
END;
$$;

CREATE OR REPLACE FUNCTION public.sp_ver_inventario_npc(
        p_npc_id public.id_personagem_npc
    )
    RETURNS TABLE (
        -- Colunas da Instância
        instancia_item_id public.id_instancia_de_item,
        durabilidade_atual SMALLINT,

        -- Colunas Gerais do Item Base (tabela itens)
        item_nome public.nome,
        item_descricao public.descricao,
        item_tipo public.tipo_item,
        item_valor SMALLINT,
        durabilidade_total SMALLINT,

        -- Colunas de Equipamentos (armas e armaduras)
        dano public.dano,
        alcance SMALLINT,
        tipo_municao public.tipo_municao,
        qtd_dano_mitigado SMALLINT,
        atributo_necessario public.tipo_atributo_personagem,
        qtd_atributo_necessario SMALLINT,

        -- Colunas de Itens Consumíveis (cura e magicos)
        qts_usos SMALLINT,
        qtd_pontos_vida_recupera SMALLINT,
        qtd_pontos_sanidade_recupera SMALLINT,
        custo_sanidade SMALLINT
)
LANGUAGE plpgsql AS $$
DECLARE
    v_id_inventario public.id_inventario;
BEGIN
    -- Busca o ID do inventário do NPC especificado
    SELECT id_inventario INTO v_id_inventario
    FROM public.npcs
    WHERE id = p_npc_id;

    IF v_id_inventario IS NULL THEN
        RAISE NOTICE 'NPC com ID % nao encontrado ou nao possui um inventario associado.', p_npc_id;
        RETURN;
    END IF;

    -- Retorna a consulta com os dados do inventário do NPC
    RETURN QUERY
    SELECT
        -- Dados da Instância
        iii.id,
        iii.durabilidade,

        -- Dados Gerais do Item
        it.nome,
        it.descricao,
        it.tipo,
        it.valor,
        COALESCE(a.durabilidade, ar.durabilidade),

        -- Atributos Específicos (serão NULL se o item não for do tipo correspondente)
        a.dano,
        a.alcance,
        a.tipo_municao,
        ar.qtd_dano_mitigado,
        COALESCE(a.atributo_necessario, ar.atributo_necessario),
        COALESCE(a.qtd_atributo_necessario, ar.qtd_atributo_necessario),
        COALESCE(c.qts_usos, m.qts_usos),
        c.qtd_pontos_vida_recupera,
        c.qtd_pontos_sanidade_recupera,
        m.custo_sanidade
    FROM
        public.inventarios_possuem_instancias_item ipii
    JOIN
        public.instancias_de_itens iii ON ipii.id_instancias_de_item = iii.id
    JOIN
        public.itens it ON iii.id_item = it.id
    LEFT JOIN
        public.armas a ON it.id = a.id
    LEFT JOIN
        public.armaduras ar ON it.id = ar.id
    LEFT JOIN
        public.curas c ON it.id = c.id
    LEFT JOIN
        public.magicos m ON it.id = m.id
    WHERE
        ipii.id_inventario = v_id_inventario;

EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao verificar o inventario do NPC %: %', p_npc_id, SQLERRM;
        RETURN;
END;
$$;

CREATE OR REPLACE FUNCTION public.sp_jogador_vende_item(
    p_id_jogador public.id_personagem_jogavel,
    p_id_npc public.id_personagem_npc,
    p_id_instancia_item public.id_instancia_de_item
)
RETURNS TEXT
LANGUAGE plpgsql AS $$
DECLARE
    v_id_inventario_pj public.id_inventario;
    v_id_inventario_npc public.id_inventario;
    v_valor_item SMALLINT;
BEGIN
    -- === VALIDAÇÕES ===
    -- 1. Verifica se o jogador existe e tem o item no inventário
    SELECT id_inventario INTO v_id_inventario_pj FROM public.personagens_jogaveis WHERE id = p_id_jogador;
    IF NOT FOUND THEN RAISE EXCEPTION 'VENDA FALHOU: Jogador com ID % não encontrado.', p_id_jogador; END IF;

    IF NOT EXISTS (SELECT 1 FROM public.inventarios_possuem_instancias_item WHERE id_inventario = v_id_inventario_pj AND id_instancias_de_item = p_id_instancia_item) THEN
        RAISE EXCEPTION 'VENDA FALHOU: Você não possui este item em seu inventário.';
    END IF;

    -- 2. Verifica se o NPC existe e tem um inventário para receber o item
    SELECT id_inventario INTO v_id_inventario_npc FROM public.npcs WHERE id = p_id_npc;
    IF NOT FOUND THEN RAISE EXCEPTION 'VENDA FALHOU: NPC com ID % não encontrado.', p_id_npc; END IF;
    IF v_id_inventario_npc IS NULL THEN RAISE EXCEPTION 'VENDA FALHOU: Este NPC não pode comprar itens (não tem inventário).'; END IF;

    -- 3. Impede a venda de itens equipados
    IF EXISTS (SELECT 1 FROM public.personagens_jogaveis WHERE id = p_id_jogador AND (id_arma = p_id_instancia_item OR id_armadura = p_id_instancia_item)) THEN
        RAISE EXCEPTION 'VENDA FALHOU: Você não pode vender um item que está equipado. Desequipe-o primeiro.';
    END IF;

    -- Pega o valor do item
    SELECT i.valor INTO v_valor_item FROM public.itens i JOIN public.instancias_de_itens ii ON i.id = ii.id_item WHERE ii.id = p_id_instancia_item;
    IF v_valor_item IS NULL THEN v_valor_item := 0; END IF;

    -- === TRANSAÇÃO ===
    -- 1. Remove o item do inventário do jogador
    DELETE FROM public.inventarios_possuem_instancias_item WHERE id_inventario = v_id_inventario_pj AND id_instancias_de_item = p_id_instancia_item;

    -- 2. Adiciona o item ao inventário do NPC
    INSERT INTO public.inventarios_possuem_instancias_item (id_inventario, id_instancias_de_item) VALUES (v_id_inventario_npc, p_id_instancia_item);

    -- 3. Adiciona o ouro ao jogador
    UPDATE public.personagens_jogaveis SET ouro = ouro + v_valor_item WHERE id = p_id_jogador;

    RETURN 'Item vendido com sucesso por ' || v_valor_item || ' de ouro!';
EXCEPTION
    WHEN OTHERS THEN
        RETURN SQLERRM; -- Retorna a mensagem de erro do banco
END;
$$;

/*
=================================================================================
        15. LÓGICA DE MECÂNICAS AVANÇADAS DE JOGO
=================================================================================
*/

-- ---------------------------------------------------------------------------------
--  15.1 FUNÇÃO E TRIGGER: Gerenciamento de Sanidade e Loucura
-- ---------------------------------------------------------------------------------
-- Função que é chamada por uma trigger sempre que a sanidade do jogador é alterada.
CREATE OR REPLACE FUNCTION public.func_verificar_insanidade()
RETURNS TRIGGER AS $$
BEGIN
    -- Se a sanidade chegar a zero, o personagem enlouquece permanentemente.
    IF NEW.sanidade_atual <= 0 THEN
        NEW.sanidade_atual := 0;
        NEW.insanidade_indefinida := TRUE;
        RAISE NOTICE 'ALERTA: O personagem % (ID: %) sucumbiu à loucura permanente!', NEW.nome, NEW.id;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger que ativa a função acima sempre que a coluna 'sanidade_atual' é atualizada.
CREATE TRIGGER trigger_checar_insanidade_personagem
    BEFORE UPDATE ON public.personagens_jogaveis
    FOR EACH ROW
    WHEN (OLD.sanidade_atual IS DISTINCT FROM NEW.sanidade_atual)
    EXECUTE FUNCTION public.func_verificar_insanidade();

-- Procedure para aplicar dano à sanidade do jogador.
CREATE OR REPLACE FUNCTION public.sp_aplicar_dano_sanidade(
    p_id_jogador public.id_personagem_jogavel,
    p_quantidade_dano SMALLINT
)
RETURNS TEXT
LANGUAGE plpgsql AS $$
DECLARE
    v_sanidade_restante SMALLINT;
BEGIN
    UPDATE public.personagens_jogaveis
    SET sanidade_atual = sanidade_atual - p_quantidade_dano
    WHERE id = p_id_jogador
    RETURNING sanidade_atual INTO v_sanidade_restante;

    RETURN 'Você perdeu ' || p_quantidade_dano || ' pontos de sanidade. Sanidade restante: ' || v_sanidade_restante;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao aplicar dano de sanidade: %', SQLERRM;
        RAISE;
END;
$$;

-- ---------------------------------------------------------------------------------
--  15.2 STORED PROCEDURE: Realizar um teste de perícia
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_realizar_teste_pericia(
    p_id_jogador public.id_personagem_jogavel,
    p_nome_pericia public.nome
)
RETURNS BOOLEAN -- Retorna TRUE para sucesso, FALSE para falha.
LANGUAGE plpgsql AS $$
DECLARE
    v_valor_pericia SMALLINT;
    v_rolagem_d100 SMALLINT;
BEGIN
    -- 1. Busca o valor da perícia para o jogador.
    SELECT ppp.valor_atual INTO v_valor_pericia
    FROM public.personagens_possuem_pericias ppp
    JOIN public.pericias p ON ppp.id_pericia = p.id
    WHERE ppp.id_personagem = p_id_jogador AND p.nome = p_nome_pericia;

    -- Se o jogador não tiver a perícia, assume um valor base ou falha.
    IF NOT FOUND THEN
        SELECT valor INTO v_valor_pericia FROM public.pericias WHERE nome = p_nome_pericia;
        IF NOT FOUND THEN
             RAISE EXCEPTION 'Perícia % não existe no sistema.', p_nome_pericia;
        END IF;
    END IF;

    -- 2. Rola um dado de 100 lados (d100).
    v_rolagem_d100 := floor(random() * 100 + 1);

    RAISE NOTICE 'Teste de %: Valor da perícia: %, Rolagem (d100): %', p_nome_pericia, v_valor_pericia, v_rolagem_d100;

    -- 3. Compara a rolagem com o valor da perícia.
    IF v_rolagem_d100 <= v_valor_pericia THEN
        RAISE NOTICE 'Sucesso!';
        RETURN TRUE;
    ELSE
        RAISE NOTICE 'Falha.';
        RETURN FALSE;
    END IF;
END;
$$;

/*
=================================================================================
        18. LÓGICA DE BATALHA (NOVO - POR TURNO) E UTILITÁRIOS
=================================================================================
*/

-- ---------------------------------------------------------------------------------
--  18.1 STORED PROCEDURE: Executa UM turno de batalha.
--  Esta função calcula o dano do jogador no monstro e do monstro no jogador,
--  atualiza a vida de ambos e retorna o resultado do turno.
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_executar_turno_batalha(
    p_id_jogador public.id_personagem_jogavel,
    p_id_instancia_monstro public.id_instancia_de_monstro
)
RETURNS TABLE (
    log_turno TEXT,
    vida_jogador_nova SMALLINT,
    vida_monstro_nova SMALLINT
)
LANGUAGE plpgsql AS $$
DECLARE
    -- Variáveis do Jogador
    v_jogador RECORD;
    v_dano_jogador SMALLINT;

    -- Variáveis do Monstro
    v_monstro RECORD;
    v_dano_monstro SMALLINT;

    -- Lógica do Turno
    v_dano_final_no_monstro SMALLINT;
    v_dano_final_no_jogador SMALLINT;
    v_log TEXT := '';
BEGIN
    -- 1. BUSCAR DADOS DO JOGADOR (incluindo força para dano desarmado)
    SELECT
        pj.nome,
        pj.pontos_de_vida_atual,
        pj.forca,
        COALESCE(a.dano, 0) as dano_arma, -- Dano da arma, ou 0 se desarmado
        COALESCE(ar.qtd_dano_mitigado, 0) as defesa_armadura
    INTO v_jogador
    FROM public.personagens_jogaveis pj
    LEFT JOIN public.instancias_de_itens ii_arma ON pj.id_arma = ii_arma.id
    LEFT JOIN public.armas a ON ii_arma.id_item = a.id
    LEFT JOIN public.instancias_de_itens ii_armadura ON pj.id_armadura = ii_armadura.id
    LEFT JOIN public.armaduras ar ON ii_armadura.id_item = ar.id
    WHERE pj.id = p_id_jogador;

    -- MELHORIA: Dano desarmado agora é baseado na Força. Se tiver arma, usa o dano da arma.
    IF v_jogador.dano_arma > 0 THEN
        v_dano_jogador := v_jogador.dano_arma;
    ELSE
        v_dano_jogador := floor(v_jogador.forca / 5) + 1; -- Ex: 10 de Força = 3 de dano
    END IF;

    -- 2. BUSCAR DADOS DO MONSTRO
    SELECT im.vida, ag.nome, ag.dano, COALESCE(ag.defesa, 0) as defesa
    INTO v_monstro
    FROM public.instancias_monstros im
    JOIN public.agressivos ag ON im.id_monstro = ag.id
    WHERE im.id = p_id_instancia_monstro;

    -- 3. CALCULAR DANO E ATUALIZAR VIDAS
    v_dano_final_no_monstro := GREATEST(0, v_dano_jogador - v_monstro.defesa);
    UPDATE public.instancias_monstros SET vida = vida - v_dano_final_no_monstro WHERE id = p_id_instancia_monstro RETURNING vida INTO vida_monstro_nova;
    v_log := v_log || v_jogador.nome || ' ataca ' || v_monstro.nome || ' causando ' || v_dano_final_no_monstro || ' de dano.';

    IF vida_monstro_nova > 0 THEN
        v_dano_final_no_jogador := GREATEST(0, v_monstro.dano - v_jogador.defesa_armadura);
        UPDATE public.personagens_jogaveis SET pontos_de_vida_atual = pontos_de_vida_atual - v_dano_final_no_jogador WHERE id = p_id_jogador RETURNING pontos_de_vida_atual INTO vida_jogador_nova;
        v_log := v_log || ' ' || v_monstro.nome || ' revida, causando ' || v_dano_final_no_jogador || ' de dano.';
    ELSE
        vida_jogador_nova := v_jogador.pontos_de_vida_atual;
    END IF;

    -- 4. RETORNAR
    log_turno := v_log;
    RETURN NEXT;
END;
$$;

-- ---------------------------------------------------------------------------------
--  18.2 STORED PROCEDURE: Ataque somente do monstro (para quando o jogador falha em uma ação)
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_monstro_ataca_sozinho(
    p_id_jogador public.id_personagem_jogavel,
    p_id_instancia_monstro public.id_instancia_de_monstro
)
RETURNS TEXT
LANGUAGE plpgsql AS $$
DECLARE
    v_defesa_jogador SMALLINT;
    v_dano_monstro SMALLINT;
    v_nome_monstro public.nome;
    v_dano_final_no_jogador SMALLINT;
BEGIN
    -- Busca dados do jogador e do monstro
    SELECT COALESCE(ar.qtd_dano_mitigado, 0) INTO v_defesa_jogador FROM public.personagens_jogaveis pj LEFT JOIN public.instancias_de_itens ii_armadura ON pj.id_armadura = ii_armadura.id LEFT JOIN public.armaduras ar ON ii_armadura.id_item = ar.id WHERE pj.id = p_id_jogador;
    SELECT COALESCE(ag.nome, pa.nome), ag.dano INTO v_nome_monstro, v_dano_monstro FROM public.instancias_monstros im JOIN public.tipos_monstro tm ON im.id_monstro = tm.id LEFT JOIN public.agressivos ag ON im.id_monstro = ag.id LEFT JOIN public.pacificos pa ON im.id_monstro = pa.id WHERE im.id = p_id_instancia_monstro;

    -- Calcula e aplica o dano
    v_dano_final_no_jogador := GREATEST(0, v_dano_monstro - v_defesa_jogador);
    UPDATE public.personagens_jogaveis SET pontos_de_vida_atual = pontos_de_vida_atual - v_dano_final_no_jogador WHERE id = p_id_jogador;

    RETURN v_nome_monstro || ' aproveita sua hesitação e ataca, causando ' || v_dano_final_no_jogador || ' de dano!';
END;
$$;

-- ---------------------------------------------------------------------------------
--  18.3 STORED PROCEDURE: Resetar o status do jogador após a morte
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_resetar_status_jogador(
    p_id_jogador public.id_personagem_jogavel
)
RETURNS VOID
LANGUAGE plpgsql AS $$
DECLARE
    v_vida_max SMALLINT;
    v_sanidade_max SMALLINT;
BEGIN
    -- Calcula a vida e sanidade máximas usando as funções existentes
    SELECT public.calcular_pts_de_vida(constituicao, tamanho), public.calcular_sanidade(poder)
    INTO v_vida_max, v_sanidade_max
    FROM public.personagens_jogaveis
    WHERE id = p_id_jogador;

    -- Atualiza a vida e sanidade atuais para seus valores máximos
    UPDATE public.personagens_jogaveis
    SET
        pontos_de_vida_atual = v_vida_max,
        sanidade_atual = v_sanidade_max -- Opcional: resetar sanidade também
    WHERE id = p_id_jogador;
END;
$$;

/*
=================================================================================
        19. LÓGICA DO MODO HISTÓRIA
=================================================================================
*/

-- ---------------------------------------------------------------------------------
--  19.1 STORED PROCEDURE: Iniciar o Modo História para um jogador
--  Esta procedure ativa a missão inicial e move o jogador para o local de início.
-- ---------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sp_iniciar_modo_historia(
    p_id_jogador public.id_personagem_jogavel,
    p_nome_missao_inicial public.nome
)
RETURNS TEXT -- Retorna uma mensagem de confirmação
LANGUAGE plpgsql AS $$
DECLARE
    v_missao_info RECORD;
BEGIN
    -- 1. Encontrar a missão inicial, seu local e sua descrição
    SELECT
        m.id,
        m.id_local_alvo,
        m.descricao -- Adicionado para buscar a descrição
    INTO v_missao_info
    FROM public.missoes m
    WHERE m.nome = p_nome_missao_inicial AND m.tipo = 'principal';

    -- 2. Validações
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Modo História não pôde ser iniciado: Missão principal "%" não encontrada.', p_nome_missao_inicial;
    END IF;

    IF NOT EXISTS (SELECT 1 FROM public.personagens_jogaveis WHERE id = p_id_jogador) THEN
        RAISE EXCEPTION 'Modo História não pôde ser iniciado: Jogador com ID % não encontrado.', p_id_jogador;
    END IF;

    IF v_missao_info.id_local_alvo IS NULL THEN
        RAISE EXCEPTION 'Modo História não pôde ser iniciado: A missão "%" não tem um local de início definido (id_local_alvo).', p_nome_missao_inicial;
    END IF;

    -- 3. Atualizar o estado do jogador para iniciar a missão
    UPDATE public.personagens_jogaveis
    SET
        id_missao_historia_ativa = v_missao_info.id,
        id_local = v_missao_info.id_local_alvo
    WHERE id = p_id_jogador;

    -- 4. Retornar a descrição da missão
    RETURN v_missao_info.descricao;

EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE 'Ocorreu um erro ao iniciar o Modo História: %', SQLERRM;
        RAISE;
END;
$$;

📚 Referencia

Prison Trading Disponível em:https://sbd1.github.io/2024.1-Prison-Trading/#/Modulo-3/Triggers Acesso em 05 de julho de 2025.

📚 Bibliografia

ELMASRI, R.; NAVATHE, S. B. Sistemas de Banco de Dados. 7. ed. Pearson Education do Brasil, 2018.

DATE, C. J. An Introduction to Database Systems. 8. ed. Addison-Wesley, 2003.

SILBERSCHATZ, A.; KORTH, H. F.; SUDARSHAN, S. Database System Concepts. 7. ed. McGraw-Hill Education, 2019.

Oracle Database SQL Language Reference. Disponível em: https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/index.html (Acesso em 28 de maio de 2025).

PostgreSQL Documentation. Disponível em: https://www.postgresql.org/docs/ (Acesso em 28 de maio de 2025).

Microsoft SQL Server Documentation. Disponível em: https://docs.microsoft.com/en-us/sql/sql-server/sql-server-documentation (Acesso em 28 de maio de 2025).

📑 Histórico de Versões

Versão Descrição Autor(es) Data de Produção Revisor(es) Data de Revisão
0.1 Criação do documento João Marcos 05/07/25 Christopher 05/07/25
1.0 Finaliza documento João Marcos 08/07/25 Luiz 08/07/25
Cayo Cayo Christopher Christopher Igor Igor João Marcos
João Marcos
Luiz Luiz