-- Procedures de personagem (23/08).
--
-- Motivo de existirem: a criacao estava em SQL solto no db.py, com
--     SELECT ISNULL(MAX(entity_id),...) + 1   e depois   INSERT
-- em duas idas separadas ao banco. Duas criacoes simultaneas leriam o MESMO maximo e a
-- segunda quebraria na chave unica. Aqui vira uma transacao unica com o lock certo.

USE Rosh;
GO

CREATE OR ALTER PROCEDURE dbo.criar_personagem
    @usuario    NVARCHAR(64),
    @nome       NVARCHAR(64),
    @classe     INT,
    @aparencia  INT,
    @primeiro_id INT = 20000,
    @resultado  NVARCHAR(32) OUTPUT,   -- ok | conta_inexistente | nome_em_uso |
                                       -- classe_invalida | limite_de_personagens
    @entity_id  INT          OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;
    SET @entity_id = NULL;

    DECLARE @conta_id INT, @hp INT, @mp INT,
            @fo INT, @de INT, @vi INT, @it INT, @sa INT;

    BEGIN TRAN;

    SELECT @conta_id = conta_id FROM dbo.conta WITH (UPDLOCK, HOLDLOCK)
     WHERE usuario = @usuario AND bloqueada = 0;
    IF @conta_id IS NULL
    BEGIN
        SET @resultado = N'conta_inexistente';
        ROLLBACK TRAN; RETURN;
    END

    -- limite de 4 personagens por conta, igual ao numero de slots que o client desenha
    IF (SELECT COUNT(*) FROM dbo.personagem WHERE conta_id = @conta_id) >= 4
    BEGIN
        SET @resultado = N'limite_de_personagens';
        ROLLBACK TRAN; RETURN;
    END

    SELECT @hp = hp_base, @mp = mp_base, @fo = forca, @de = destreza,
           @vi = vitalidade, @it = inteligencia, @sa = sabedoria
      FROM dbo.classe WHERE classe_id = @classe;
    IF @hp IS NULL
    BEGIN
        SET @resultado = N'classe_invalida';
        ROLLBACK TRAN; RETURN;
    END

    IF EXISTS (SELECT 1 FROM dbo.personagem WITH (UPDLOCK, HOLDLOCK) WHERE nome = @nome)
    BEGIN
        SET @resultado = N'nome_em_uso';
        ROLLBACK TRAN; RETURN;
    END

    -- UPDLOCK/HOLDLOCK na leitura do maximo: e o que impede duas criacoes concorrentes de
    -- escolherem o mesmo id.
    SELECT @entity_id = ISNULL(MAX(entity_id), @primeiro_id - 1) + 1
      FROM dbo.personagem WITH (UPDLOCK, HOLDLOCK);

    INSERT INTO dbo.personagem
        (conta_id, entity_id, nome, classe, aparencia, hp, hp_max, mp, mp_max,
         forca, destreza, vitalidade, inteligencia, sabedoria)
    VALUES
        (@conta_id, @entity_id, @nome, @classe, @aparencia, @hp, @hp, @mp, @mp,
         @fo, @de, @vi, @it, @sa);

    SET @resultado = N'ok';
    COMMIT TRAN;
END
GO

CREATE OR ALTER PROCEDURE dbo.apagar_personagem
    @entity_id INT,
    @usuario   NVARCHAR(64) = NULL,   -- se informado, so apaga se o personagem for dessa conta
    @apagados  INT OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    DELETE p FROM dbo.personagem p
      LEFT JOIN dbo.conta c ON c.conta_id = p.conta_id
     WHERE p.entity_id = @entity_id
       AND (@usuario IS NULL OR c.usuario = @usuario);
    SET @apagados = @@ROWCOUNT;
END
GO

CREATE OR ALTER PROCEDURE dbo.listar_personagens
    @usuario NVARCHAR(64)
AS
BEGIN
    SET NOCOUNT ON;
    -- A ORDEM E A LISTA TEM DE BATER COM db._COLUNAS, byte a byte: o leitor faz
    -- zip(_COLUNAS, linha) e o zip PARA na lista mais curta, calado. Uma coluna a menos
    -- aqui nao da erro nenhum -- so faz os campos do fim sumirem do dicionario.
    -- Foi o que aconteceu com pos_x/pos_y/pos_z: sem elas, build_self_appear via
    -- personagem.get("pos_x") == None e nao escrevia posicao nenhuma no pacote.
    SELECT p.entity_id, p.nome, p.nivel, p.exp, p.classe, p.aparencia,
           p.hp, p.hp_max, p.mp, p.mp_max,
           p.forca, p.destreza, p.vitalidade, p.inteligencia, p.sabedoria,
           p.cabelo, p.rosto, p.cor_r, p.cor_g, p.cor_b, p.cor_a,
           p.pos_x, p.pos_y, p.pos_z
      FROM dbo.personagem p
      JOIN dbo.conta c ON c.conta_id = p.conta_id
     WHERE c.usuario = @usuario
     ORDER BY p.personagem_id;
END
GO


-- ---------------------------------------------------------------------------
-- Opcoes especiais POR INSTANCIA de item.
--
-- As opcoes FIXAS de um item (as que a caixa de texto do client desenha sozinha) vem de
-- Data/Items/ItemBonus.ini e nao ficam aqui: sao iguais em toda copia do item. Esta tabela
-- e para o outro tipo, o que o jogo sorteia quando o item cai -- e, por enquanto, para o
-- que o administrador escolhe a mao na ferramenta web.
--
-- `tipo` e o caso do switch FUN_006076cb do AMo.exe, o mesmo numero da coluna `tipo` do
-- ItemBonus.ini. `unidade` e 'abs' ou '%'. Ver docs/opcoes-de-item.md.
-- ---------------------------------------------------------------------------
IF OBJECT_ID('dbo.personagem_item_bonus') IS NULL
CREATE TABLE dbo.personagem_item_bonus (
    personagem_item_id INT           NOT NULL,
    ordem              INT           NOT NULL,
    tipo               INT           NOT NULL,
    valor              FLOAT         NOT NULL,
    unidade            VARCHAR(4)    NOT NULL DEFAULT 'abs',
    CONSTRAINT pk_personagem_item_bonus PRIMARY KEY (personagem_item_id, ordem)
);
GO
